Spaces:
Build error
Build error
| import streamlit as st | |
| import numpy as np | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| import time | |
| # ===================================================== | |
| # PAGE CONFIGURATION | |
| # ===================================================== | |
| # ===================================================== | |
| # FIX FOR STREAMLIT CLOUD / HUGGING FACE | |
| # ===================================================== | |
| st.set_page_config( | |
| page_title="Industrial PLC-DCS-SCADA Simulator", | |
| layout="wide" | |
| ) | |
| st.title("π Industrial Chemical Plant Simulator") | |
| st.markdown("## PLC + DCS + SCADA Integrated Control System") | |
| # ===================================================== | |
| # SIDEBAR | |
| # ===================================================== | |
| st.sidebar.header("Plant Parameters") | |
| setpoint = st.sidebar.slider( | |
| "Reactor Temperature Setpoint", | |
| 40, | |
| 120, | |
| 80 | |
| ) | |
| simulation_time = st.sidebar.slider( | |
| "Simulation Time", | |
| 100, | |
| 500, | |
| 250 | |
| ) | |
| noise = st.sidebar.slider( | |
| "Sensor Noise", | |
| 0.0, | |
| 2.0, | |
| 0.3 | |
| ) | |
| # ===================================================== | |
| # PROCESS MODEL | |
| # ===================================================== | |
| ambient_temp = 25 | |
| process_gain = 0.12 | |
| cooling_gain = 0.20 | |
| thermal_mass = 18 | |
| # ===================================================== | |
| # PLC TAGS | |
| # ===================================================== | |
| class PLC: | |
| def __init__(self): | |
| self.tags = { | |
| "TT101": 0, | |
| "CV101": 0, | |
| "SP101": setpoint, | |
| "ALARM_HIGH": False, | |
| "PUMP_RUN": True | |
| } | |
| def update(self, temperature, valve): | |
| self.tags["TT101"] = temperature | |
| self.tags["CV101"] = valve | |
| if temperature > setpoint + 10: | |
| self.tags["ALARM_HIGH"] = True | |
| else: | |
| self.tags["ALARM_HIGH"] = False | |
| # ===================================================== | |
| # CONTROL SIMULATION | |
| # ===================================================== | |
| def simulate(controller): | |
| plc = PLC() | |
| temp = 30 | |
| integral = 0 | |
| previous_error = 0 | |
| temperatures = [] | |
| valves = [] | |
| alarms = [] | |
| times = [] | |
| for t in range(simulation_time): | |
| error = setpoint - temp | |
| # ============================================= | |
| # PLC + ON/OFF CONTROL | |
| # ============================================= | |
| if controller == "ONOFF": | |
| if temp < setpoint: | |
| valve = 0 | |
| else: | |
| valve = 100 | |
| # ============================================= | |
| # PLC + PID CONTROL | |
| # ============================================= | |
| elif controller == "PID": | |
| kp = 2.5 | |
| ki = 0.04 | |
| kd = 0.7 | |
| integral += error | |
| derivative = error - previous_error | |
| valve = kp * error + ki * integral + kd * derivative | |
| valve = np.clip(valve, 0, 100) | |
| previous_error = error | |
| # ============================================= | |
| # PLC + MPC CONTROL | |
| # ============================================= | |
| elif controller == "MPC": | |
| future_error = error * 0.85 | |
| valve = 1.8 * future_error + 25 | |
| valve = np.clip(valve, 0, 100) | |
| # ============================================= | |
| # PROCESS DYNAMICS | |
| # ============================================= | |
| heating = process_gain * (100 - temp) | |
| cooling = cooling_gain * (valve / 100) * (temp - ambient_temp) | |
| dT = (heating - cooling) / thermal_mass | |
| temp += dT + np.random.normal(0, noise) | |
| # PLC UPDATE | |
| plc.update(temp, valve) | |
| temperatures.append(temp) | |
| valves.append(valve) | |
| alarms.append(plc.tags["ALARM_HIGH"]) | |
| times.append(t) | |
| return pd.DataFrame({ | |
| "Time": times, | |
| "Temperature": temperatures, | |
| "Valve": valves, | |
| "Alarm": alarms | |
| }) | |
| # ===================================================== | |
| # RUN ALL CONTROLLERS | |
| # ===================================================== | |
| onoff_df = simulate("ONOFF") | |
| pid_df = simulate("PID") | |
| mpc_df = simulate("MPC") | |
| # ===================================================== | |
| # SCADA HEADER | |
| # ===================================================== | |
| st.markdown("---") | |
| sc1, sc2, sc3, sc4 = st.columns(4) | |
| with sc1: | |
| st.metric("Plant Status", "RUNNING") | |
| with sc2: | |
| st.metric("PLC Status", "ONLINE") | |
| with sc3: | |
| st.metric("DCS Network", "CONNECTED") | |
| with sc4: | |
| st.metric("SCADA", "ACTIVE") | |
| # ===================================================== | |
| # SECTION 1 : PLC + ON-OFF CONTROL | |
| # ===================================================== | |
| st.markdown("---") | |
| st.header("π’ SECTION 1 : PLC + ON-OFF CONTROL") | |
| import streamlit as st | |
| import numpy as np | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| import time | |
| # ------------------------------------------------- | |
| # PAGE CONFIG | |
| # ------------------------------------------------- | |
| st.set_page_config( | |
| page_title="Chemical Plant Control Simulator", | |
| layout="wide" | |
| ) | |
| st.title("π Chemical Plant DCS / SCADA Simulator") | |
| st.markdown("### Temperature Control of a Chemical Reactor") | |
| # ------------------------------------------------- | |
| # SIDEBAR | |
| # ------------------------------------------------- | |
| st.sidebar.header("Simulation Settings") | |
| setpoint = st.sidebar.slider( | |
| "Temperature Setpoint (Β°C)", | |
| 40, | |
| 120, | |
| 80 | |
| ) | |
| simulation_time = st.sidebar.slider( | |
| "Simulation Time", | |
| 50, | |
| 500, | |
| 200 | |
| ) | |
| noise_level = st.sidebar.slider( | |
| "Process Noise", | |
| 0.0, | |
| 2.0, | |
| 0.3 | |
| ) | |
| # ------------------------------------------------- | |
| # PROCESS MODEL | |
| # ------------------------------------------------- | |
| ambient_temp = 25 | |
| process_gain = 0.12 | |
| cooling_gain = 0.18 | |
| thermal_mass = 18 | |
| # ------------------------------------------------- | |
| # SIMULATION FUNCTION | |
| # ------------------------------------------------- | |
| def simulate(controller_type): | |
| temp = 30 | |
| integral = 0 | |
| previous_error = 0 | |
| temperatures = [] | |
| valve_positions = [] | |
| times = [] | |
| for t in range(simulation_time): | |
| error = setpoint - temp | |
| # ----------------------------------------- | |
| # ON-OFF CONTROL | |
| # ----------------------------------------- | |
| if controller_type == "ON-OFF": | |
| if temp < setpoint: | |
| valve = 0 | |
| else: | |
| valve = 100 | |
| # ----------------------------------------- | |
| # PID CONTROL | |
| # ----------------------------------------- | |
| elif controller_type == "PID": | |
| kp = 2.5 | |
| ki = 0.05 | |
| kd = 0.8 | |
| integral += error | |
| derivative = error - previous_error | |
| valve = ( | |
| kp * error | |
| + ki * integral | |
| + kd * derivative | |
| ) | |
| valve = np.clip(valve, 0, 100) | |
| previous_error = error | |
| # ----------------------------------------- | |
| # MPC-LIKE CONTROL | |
| # ----------------------------------------- | |
| elif controller_type == "MPC": | |
| future_error = error * 0.85 | |
| valve = 1.8 * future_error + 25 | |
| valve = np.clip(valve, 0, 100) | |
| # ----------------------------------------- | |
| # PROCESS DYNAMICS | |
| # ----------------------------------------- | |
| heating = process_gain * (100 - temp) | |
| cooling = cooling_gain * (valve / 100) * (temp - ambient_temp) | |
| dT = (heating - cooling) / thermal_mass | |
| temp += dT + np.random.normal(0, noise_level) | |
| temperatures.append(temp) | |
| valve_positions.append(valve) | |
| times.append(t) | |
| return pd.DataFrame({ | |
| "Time": times, | |
| "Temperature": temperatures, | |
| "Valve": valve_positions | |
| }) | |
| # ------------------------------------------------- | |
| # RUN SIMULATIONS | |
| # ------------------------------------------------- | |
| onoff_df = simulate("ON-OFF") | |
| pid_df = simulate("PID") | |
| mpc_df = simulate("MPC") | |
| # ------------------------------------------------- | |
| # LIVE SCADA CARDS | |
| # ------------------------------------------------- | |
| col1, col2, col3 = st.columns(3) | |
| latest_onoff = round(onoff_df["Temperature"].iloc[-1], 2) | |
| latest_pid = round(pid_df["Temperature"].iloc[-1], 2) | |
| latest_mpc = round(mpc_df["Temperature"].iloc[-1], 2) | |
| with col1: | |
| st.metric("ON-OFF Temp", f"{latest_onoff} Β°C") | |
| with col2: | |
| st.metric("PID Temp", f"{latest_pid} Β°C") | |
| with col3: | |
| st.metric("MPC Temp", f"{latest_mpc} Β°C") | |
| # ------------------------------------------------- | |
| # DCS STYLE REACTOR DIAGRAM | |
| # ------------------------------------------------- | |
| st.markdown("---") | |
| st.subheader("βοΈ Reactor DCS Overview") | |
| reactor_col1, reactor_col2 = st.columns([2, 1]) | |
| with reactor_col1: | |
| fig_reactor = go.Figure() | |
| fig_reactor.add_shape( | |
| type="rect", | |
| x0=1, | |
| y0=1, | |
| x1=4, | |
| y1=8, | |
| line=dict(color="cyan", width=4), | |
| fillcolor="rgba(0,255,255,0.1)" | |
| ) | |
| fig_reactor.add_annotation( | |
| x=2.5, | |
| y=4.5, | |
| text=f"Reactor\nSP = {setpoint}Β°C", | |
| showarrow=False, | |
| font=dict(size=18) | |
| ) | |
| fig_reactor.add_shape( | |
| type="line", | |
| x0=4, | |
| y0=4, | |
| x1=7, | |
| y1=4, | |
| line=dict(color="orange", width=8) | |
| ) | |
| fig_reactor.add_annotation( | |
| x=6, | |
| y=4.5, | |
| text="Cooling Water", | |
| showarrow=False | |
| ) | |
| fig_reactor.update_layout( | |
| height=400, | |
| paper_bgcolor="black", | |
| plot_bgcolor="black", | |
| font_color="white", | |
| xaxis=dict(visible=False), | |
| yaxis=dict(visible=False) | |
| ) | |
| st.plotly_chart(fig_reactor, use_container_width=True) | |
| with reactor_col2: | |
| gauge = go.Figure(go.Indicator( | |
| mode="gauge+number", | |
| value=latest_pid, | |
| title={'text': "PID Reactor Temp"}, | |
| gauge={ | |
| 'axis': {'range': [None, 120]}, | |
| 'bar': {'color': "red"}, | |
| 'steps': [ | |
| {'range': [0, 60], 'color': "lightgreen"}, | |
| {'range': [60, 90], 'color': "yellow"}, | |
| {'range': [90, 120], 'color': "orange"} | |
| ] | |
| } | |
| )) | |
| gauge.update_layout(height=400) | |
| st.plotly_chart(gauge, use_container_width=True) | |
| # ------------------------------------------------- | |
| # TEMPERATURE COMPARISON | |
| # ------------------------------------------------- | |
| st.markdown("---") | |
| st.subheader("π Temperature Response Comparison") | |
| fig = go.Figure() | |
| fig.add_trace(go.Scatter( | |
| x=onoff_df["Time"], | |
| y=onoff_df["Temperature"], | |
| mode='lines', | |
| name='ON-OFF' | |
| )) | |
| fig.add_trace(go.Scatter( | |
| x=pid_df["Time"], | |
| y=pid_df["Temperature"], | |
| mode='lines', | |
| name='PID' | |
| )) | |
| fig.add_trace(go.Scatter( | |
| x=mpc_df["Time"], | |
| y=mpc_df["Temperature"], | |
| mode='lines', | |
| name='MPC' | |
| )) | |
| fig.add_hline( | |
| y=setpoint, | |
| line_dash="dash", | |
| annotation_text="Setpoint" | |
| ) | |
| fig.update_layout( | |
| template="plotly_dark", | |
| height=500, | |
| xaxis_title="Time", | |
| yaxis_title="Temperature (Β°C)" | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # ------------------------------------------------- | |
| # VALVE POSITION COMPARISON | |
| # ------------------------------------------------- | |
| st.markdown("---") | |
| st.subheader("π Cooling Valve Position") | |
| fig2 = make_subplots(rows=1, cols=3, | |
| subplot_titles=("ON-OFF", "PID", "MPC")) | |
| fig2.add_trace( | |
| go.Scatter( | |
| x=onoff_df["Time"], | |
| y=onoff_df["Valve"], | |
| mode='lines' | |
| ), | |
| row=1, | |
| col=1 | |
| ) | |
| fig2.add_trace( | |
| go.Scatter( | |
| x=pid_df["Time"], | |
| y=pid_df["Valve"], | |
| mode='lines' | |
| ), | |
| row=1, | |
| col=2 | |
| ) | |
| fig2.add_trace( | |
| go.Scatter( | |
| x=mpc_df["Time"], | |
| y=mpc_df["Valve"], | |
| mode='lines' | |
| ), | |
| row=1, | |
| col=3 | |
| ) | |
| fig2.update_layout( | |
| template="plotly_dark", | |
| height=450, | |
| showlegend=False | |
| ) | |
| st.plotly_chart(fig2, use_container_width=True) | |
| # ------------------------------------------------- | |
| # CONTROL SYSTEM EXPLANATION | |
| # ------------------------------------------------- | |
| st.markdown("---") | |
| st.subheader("π Control System Explanation") | |
| st.markdown(""" | |
| ### 1. ON-OFF Control | |
| - Simplest controller | |
| - Valve fully opens or fully closes | |
| - Causes oscillation | |
| - Used in household thermostats | |
| ### 2. PID Control | |
| - Most common industrial controller | |
| - Smooth response | |
| - Reduces overshoot | |
| - Widely used in DCS systems | |
| ### 3. MPC Control | |
| - Predictive strategy | |
| - Estimates future behavior | |
| - Used in advanced chemical plants | |
| - Common in refineries and petrochemical plants | |
| """) | |
| # ------------------------------------------------- | |
| # PERFORMANCE TABLE | |
| # ------------------------------------------------- | |
| st.markdown("---") | |
| st.subheader("π Performance Comparison") | |
| performance = pd.DataFrame({ | |
| "Controller": ["ON-OFF", "PID", "MPC"], | |
| "Final Temp": [ | |
| round(onoff_df["Temperature"].iloc[-1], 2), | |
| round(pid_df["Temperature"].iloc[-1], 2), | |
| round(mpc_df["Temperature"].iloc[-1], 2) | |
| ], | |
| "Valve Stability": [ | |
| "Low", | |
| "High", | |
| "Very High" | |
| ], | |
| "Industrial Use": [ | |
| "Basic", | |
| "Very Common", | |
| "Advanced" | |
| ] | |
| }) | |
| st.dataframe(performance, use_container_width=True) | |
| # ------------------------------------------------- | |
| # FOOTER | |
| # ------------------------------------------------- | |
| st.markdown("---") | |
| st.markdown("## β Simulation Complete") | |
| st.markdown("Designed for Chemical Engineering DCS / SCADA Learning") | |