import streamlit as st import matplotlib.pyplot as plt import numpy as np import random from matplotlib.patches import FancyArrowPatch, Rectangle, Polygon # Function to plot equipment shapes def plot_shape(ax, shape, x, y, size_or_diameter, height=None, width=None, label=None): """ Plot a basic 2D shape based on input parameters. """ if shape == "circle": theta = np.linspace(0, 2 * np.pi, 100) x_vals = (size_or_diameter / 2) * np.cos(theta) + x y_vals = (size_or_diameter / 2) * np.sin(theta) + y ax.plot(x_vals, y_vals, label="Circle") ax.fill(x_vals, y_vals, color="lightblue") elif shape == "square": half = size_or_diameter / 2 x_vals = [x - half, x + half, x + half, x - half, x - half] y_vals = [y - half, y - half, y + half, y + half, y - half] ax.plot(x_vals, y_vals, label="Square") ax.fill(x_vals, y_vals, color="lightblue") elif shape == "rectangle": if height is None or width is None: raise ValueError("Rectangle requires both height and width.") half_h = height / 2 half_w = width / 2 x_vals = [x - half_w, x + half_w, x + half_w, x - half_w, x - half_w] y_vals = [y - half_h, y - half_h, y + half_h, y + half_h, y - half_h] ax.plot(x_vals, y_vals, label="Rectangle") ax.fill(x_vals, y_vals, color="lightblue") elif shape == "cylinder": if height is None or size_or_diameter is None: raise ValueError("Cylinder requires both diameter and height.") radius = size_or_diameter / 2 # Top and bottom semi-circles theta_top = np.linspace(0, np.pi, 100) theta_bottom = np.linspace(np.pi, 2 * np.pi, 100) x_top = radius * np.cos(theta_top) + x y_top = (height / 2) + radius * np.sin(theta_top) + y x_bottom = radius * np.cos(theta_bottom) + x y_bottom = -(height / 2) + radius * np.sin(theta_bottom) + y # Rectangle body x_rect = [x - radius, x + radius, x + radius, x - radius, x - radius] y_rect = [y - height / 2, y - height / 2, y + height / 2, y + height / 2, y - height / 2] ax.plot(x_top, y_top, label="Cylinder Top") ax.plot(x_bottom, y_bottom, label="Cylinder Bottom") ax.plot(x_rect, y_rect, label="Cylinder Body") ax.fill(x_rect, y_rect, color="lightblue") elif shape == "capsule": if height is None or width is None: raise ValueError("Capsule requires both height and width.") radius = width / 2 # Top and bottom semi-circles theta_top = np.linspace(0, np.pi, 100) theta_bottom = np.linspace(np.pi, 2 * np.pi, 100) x_top = radius * np.cos(theta_top) + x y_top = (height / 2) + radius * np.sin(theta_top) + y x_bottom = radius * np.cos(theta_bottom) + x y_bottom = -(height / 2) + radius * np.sin(theta_bottom) + y # Rectangle part x_rect = [x - radius, x + radius, x + radius, x - radius, x - radius] y_rect = [y - height / 2, y - height / 2, y + height / 2, y + height / 2, y - height / 2] ax.plot(x_top, y_top, label="Capsule Top") ax.plot(x_bottom, y_bottom, label="Capsule Bottom") ax.plot(x_rect, y_rect, label="Capsule Body") ax.fill(x_rect, y_rect, color="lightblue") elif shape == "triangle": height = (np.sqrt(3) / 2) * size_or_diameter x_vals = [x - size_or_diameter / 2, x + size_or_diameter / 2, x, x - size_or_diameter / 2] y_vals = [y, y, y + height, y] ax.plot(x_vals, y_vals, label="Triangle") ax.fill(x_vals, y_vals, color="lightblue") else: raise ValueError(f"Unsupported shape: {shape}") if label: ax.text(x, y, label, horizontalalignment="center", verticalalignment="center", fontsize=10) ax.set_aspect('equal', adjustable='box') # Function to draw a straight connection line between equipment def draw_connection(ax, start_x, start_y, end_x, end_y): ax.plot([start_x, end_x], [start_y, end_y], color="gray", lw=1.5) # Streamlit UI to take inputs def main(): st.title("Distillation Plant Layout Optimizer") # Inputs from the user area_length = st.number_input("Enter plant area length (m):", min_value=10, max_value=1000, value=50) area_width = st.number_input("Enter plant area width (m):", min_value=10, max_value=1000, value=30) equipment_types = [ "Distillation Column", "Reboiler", "Condenser", "Feed Preheater", "Cooling Tower", "Storage Tank", "Pump", "Heat Exchanger", "Safety Valve", "Instrument Panel" ] selected_equipment = st.multiselect( "Select the equipment to place in the plant layout:", options=equipment_types, default=equipment_types[:5] # Default to first 5 equipment ) if st.button("Generate Layout"): st.write("### Optimized Layout") # Equipment shapes and dimensions equipment_shapes = { "Distillation Column": ("cylinder", 4, 8), "Reboiler": ("cylinder", 5, 6), "Condenser": ("rectangle", 6, 4, 8), "Feed Preheater": ("triangle", 6), "Cooling Tower": ("capsule", 4, 10, 2), "Storage Tank": ("capsule", 4, 8, 6), "Pump": ("circle", 3), "Heat Exchanger": ("square", 6), "Safety Valve": ("square", 5), "Instrument Panel": ("rectangle", 6, 6, 4) } # Create figure fig, ax = plt.subplots(figsize=(12, 8)) # Draw the plant area ax.set_xlim(0, area_length) ax.set_ylim(0, area_width) ax.add_patch( plt.Rectangle((0, 0), area_length, area_width, edgecolor="black", facecolor="lightgrey", alpha=0.3) ) ax.set_title("Distillation Plant Layout") # Generate random positions for equipment with a check for non-overlap equipment_positions = [] min_distance = 8 # Minimum distance to prevent overlap for _ in range(len(selected_equipment)): while True: x = random.uniform(5, area_length - 5) y = random.uniform(5, area_width - 5) # Check if the equipment overlaps with any previously placed equipment overlap = False for (prev_x, prev_y) in equipment_positions: if np.hypot(prev_x - x, prev_y - y) < min_distance: overlap = True break if not overlap: equipment_positions.append((x, y)) break # Plot the equipment prev_x, prev_y = None, None for i, equip_name in enumerate(selected_equipment): x, y = equipment_positions[i] shape, *dims = equipment_shapes[equip_name] plot_shape(ax, shape, x, y, *dims, label=equip_name) # Draw connections (straight lines) between equipment if prev_x is not None and prev_y is not None: draw_connection(ax, prev_x, prev_y, x, y) prev_x, prev_y = x, y plt.xlabel("Length (m)") plt.ylabel("Width (m)") plt.grid(True) st.pyplot(fig) # Display the plot in Streamlit if __name__ == "__main__": main()