ahmad123445 commited on
Commit
ea27aa1
·
verified ·
1 Parent(s): 2606806

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +542 -1
app.py CHANGED
@@ -1,4 +1,545 @@
1
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  st.subheader("📚 Control System Explanation")
3
 
4
  st.markdown("""
@@ -55,4 +596,4 @@ st.dataframe(performance, use_container_width=True)
55
 
56
  st.markdown("---")
57
  st.markdown("## ✅ Simulation Complete")
58
- st.markdown("Designed for Chemical Engineering DCS / SCADA Learning")
 
1
  import streamlit as st
2
+ import numpy as np
3
+ import pandas as pd
4
+ import plotly.graph_objects as go
5
+ from plotly.subplots import make_subplots
6
+ import time
7
+
8
+ # =====================================================
9
+ # PAGE CONFIGURATION
10
+ # =====================================================
11
+
12
+ # =====================================================
13
+ # FIX FOR STREAMLIT CLOUD / HUGGING FACE
14
+ # =====================================================
15
+
16
+ st.set_page_config(
17
+ page_title="Industrial PLC-DCS-SCADA Simulator",
18
+ layout="wide"
19
+ )
20
+
21
+ st.title("🏭 Industrial Chemical Plant Simulator")
22
+ st.markdown("## PLC + DCS + SCADA Integrated Control System")
23
+
24
+ # =====================================================
25
+ # SIDEBAR
26
+ # =====================================================
27
+
28
+ st.sidebar.header("Plant Parameters")
29
+
30
+ setpoint = st.sidebar.slider(
31
+ "Reactor Temperature Setpoint",
32
+ 40,
33
+ 120,
34
+ 80
35
+ )
36
+
37
+ simulation_time = st.sidebar.slider(
38
+ "Simulation Time",
39
+ 100,
40
+ 500,
41
+ 250
42
+ )
43
+
44
+ noise = st.sidebar.slider(
45
+ "Sensor Noise",
46
+ 0.0,
47
+ 2.0,
48
+ 0.3
49
+ )
50
+
51
+ # =====================================================
52
+ # PROCESS MODEL
53
+ # =====================================================
54
+
55
+ ambient_temp = 25
56
+ process_gain = 0.12
57
+ cooling_gain = 0.20
58
+ thermal_mass = 18
59
+
60
+ # =====================================================
61
+ # PLC TAGS
62
+ # =====================================================
63
+
64
+ class PLC:
65
+
66
+ def __init__(self):
67
+ self.tags = {
68
+ "TT101": 0,
69
+ "CV101": 0,
70
+ "SP101": setpoint,
71
+ "ALARM_HIGH": False,
72
+ "PUMP_RUN": True
73
+ }
74
+
75
+ def update(self, temperature, valve):
76
+ self.tags["TT101"] = temperature
77
+ self.tags["CV101"] = valve
78
+
79
+ if temperature > setpoint + 10:
80
+ self.tags["ALARM_HIGH"] = True
81
+ else:
82
+ self.tags["ALARM_HIGH"] = False
83
+
84
+ # =====================================================
85
+ # CONTROL SIMULATION
86
+ # =====================================================
87
+
88
+ def simulate(controller):
89
+
90
+ plc = PLC()
91
+
92
+ temp = 30
93
+ integral = 0
94
+ previous_error = 0
95
+
96
+ temperatures = []
97
+ valves = []
98
+ alarms = []
99
+ times = []
100
+
101
+ for t in range(simulation_time):
102
+
103
+ error = setpoint - temp
104
+
105
+ # =============================================
106
+ # PLC + ON/OFF CONTROL
107
+ # =============================================
108
+
109
+ if controller == "ONOFF":
110
+
111
+ if temp < setpoint:
112
+ valve = 0
113
+ else:
114
+ valve = 100
115
+
116
+ # =============================================
117
+ # PLC + PID CONTROL
118
+ # =============================================
119
+
120
+ elif controller == "PID":
121
+
122
+ kp = 2.5
123
+ ki = 0.04
124
+ kd = 0.7
125
+
126
+ integral += error
127
+ derivative = error - previous_error
128
+
129
+ valve = kp * error + ki * integral + kd * derivative
130
+ valve = np.clip(valve, 0, 100)
131
+
132
+ previous_error = error
133
+
134
+ # =============================================
135
+ # PLC + MPC CONTROL
136
+ # =============================================
137
+
138
+ elif controller == "MPC":
139
+
140
+ future_error = error * 0.85
141
+ valve = 1.8 * future_error + 25
142
+ valve = np.clip(valve, 0, 100)
143
+
144
+ # =============================================
145
+ # PROCESS DYNAMICS
146
+ # =============================================
147
+
148
+ heating = process_gain * (100 - temp)
149
+ cooling = cooling_gain * (valve / 100) * (temp - ambient_temp)
150
+
151
+ dT = (heating - cooling) / thermal_mass
152
+
153
+ temp += dT + np.random.normal(0, noise)
154
+
155
+ # PLC UPDATE
156
+ plc.update(temp, valve)
157
+
158
+ temperatures.append(temp)
159
+ valves.append(valve)
160
+ alarms.append(plc.tags["ALARM_HIGH"])
161
+ times.append(t)
162
+
163
+ return pd.DataFrame({
164
+ "Time": times,
165
+ "Temperature": temperatures,
166
+ "Valve": valves,
167
+ "Alarm": alarms
168
+ })
169
+
170
+ # =====================================================
171
+ # RUN ALL CONTROLLERS
172
+ # =====================================================
173
+
174
+ onoff_df = simulate("ONOFF")
175
+ pid_df = simulate("PID")
176
+ mpc_df = simulate("MPC")
177
+
178
+ # =====================================================
179
+ # SCADA HEADER
180
+ # =====================================================
181
+
182
+ st.markdown("---")
183
+
184
+ sc1, sc2, sc3, sc4 = st.columns(4)
185
+
186
+ with sc1:
187
+ st.metric("Plant Status", "RUNNING")
188
+
189
+ with sc2:
190
+ st.metric("PLC Status", "ONLINE")
191
+
192
+ with sc3:
193
+ st.metric("DCS Network", "CONNECTED")
194
+
195
+ with sc4:
196
+ st.metric("SCADA", "ACTIVE")
197
+
198
+ # =====================================================
199
+ # SECTION 1 : PLC + ON-OFF CONTROL
200
+ # =====================================================
201
+
202
+ st.markdown("---")
203
+ st.header("🟢 SECTION 1 : PLC + ON-OFF CONTROL")
204
+ import streamlit as st
205
+ import numpy as np
206
+ import pandas as pd
207
+ import plotly.graph_objects as go
208
+ from plotly.subplots import make_subplots
209
+ import time
210
+
211
+ # -------------------------------------------------
212
+ # PAGE CONFIG
213
+ # -------------------------------------------------
214
+
215
+ st.set_page_config(
216
+ page_title="Chemical Plant Control Simulator",
217
+ layout="wide"
218
+ )
219
+
220
+ st.title("🏭 Chemical Plant DCS / SCADA Simulator")
221
+ st.markdown("### Temperature Control of a Chemical Reactor")
222
+
223
+ # -------------------------------------------------
224
+ # SIDEBAR
225
+ # -------------------------------------------------
226
+
227
+ st.sidebar.header("Simulation Settings")
228
+
229
+ setpoint = st.sidebar.slider(
230
+ "Temperature Setpoint (°C)",
231
+ 40,
232
+ 120,
233
+ 80
234
+ )
235
+
236
+ simulation_time = st.sidebar.slider(
237
+ "Simulation Time",
238
+ 50,
239
+ 500,
240
+ 200
241
+ )
242
+
243
+ noise_level = st.sidebar.slider(
244
+ "Process Noise",
245
+ 0.0,
246
+ 2.0,
247
+ 0.3
248
+ )
249
+
250
+ # -------------------------------------------------
251
+ # PROCESS MODEL
252
+ # -------------------------------------------------
253
+
254
+ ambient_temp = 25
255
+ process_gain = 0.12
256
+ cooling_gain = 0.18
257
+ thermal_mass = 18
258
+
259
+ # -------------------------------------------------
260
+ # SIMULATION FUNCTION
261
+ # -------------------------------------------------
262
+
263
+ def simulate(controller_type):
264
+
265
+ temp = 30
266
+ integral = 0
267
+ previous_error = 0
268
+
269
+ temperatures = []
270
+ valve_positions = []
271
+ times = []
272
+
273
+ for t in range(simulation_time):
274
+
275
+ error = setpoint - temp
276
+
277
+ # -----------------------------------------
278
+ # ON-OFF CONTROL
279
+ # -----------------------------------------
280
+ if controller_type == "ON-OFF":
281
+
282
+ if temp < setpoint:
283
+ valve = 0
284
+ else:
285
+ valve = 100
286
+
287
+ # -----------------------------------------
288
+ # PID CONTROL
289
+ # -----------------------------------------
290
+ elif controller_type == "PID":
291
+
292
+ kp = 2.5
293
+ ki = 0.05
294
+ kd = 0.8
295
+
296
+ integral += error
297
+ derivative = error - previous_error
298
+
299
+ valve = (
300
+ kp * error
301
+ + ki * integral
302
+ + kd * derivative
303
+ )
304
+
305
+ valve = np.clip(valve, 0, 100)
306
+
307
+ previous_error = error
308
+
309
+ # -----------------------------------------
310
+ # MPC-LIKE CONTROL
311
+ # -----------------------------------------
312
+ elif controller_type == "MPC":
313
+
314
+ future_error = error * 0.85
315
+ valve = 1.8 * future_error + 25
316
+ valve = np.clip(valve, 0, 100)
317
+
318
+ # -----------------------------------------
319
+ # PROCESS DYNAMICS
320
+ # -----------------------------------------
321
+
322
+ heating = process_gain * (100 - temp)
323
+ cooling = cooling_gain * (valve / 100) * (temp - ambient_temp)
324
+
325
+ dT = (heating - cooling) / thermal_mass
326
+
327
+ temp += dT + np.random.normal(0, noise_level)
328
+
329
+ temperatures.append(temp)
330
+ valve_positions.append(valve)
331
+ times.append(t)
332
+
333
+ return pd.DataFrame({
334
+ "Time": times,
335
+ "Temperature": temperatures,
336
+ "Valve": valve_positions
337
+ })
338
+
339
+ # -------------------------------------------------
340
+ # RUN SIMULATIONS
341
+ # -------------------------------------------------
342
+
343
+ onoff_df = simulate("ON-OFF")
344
+ pid_df = simulate("PID")
345
+ mpc_df = simulate("MPC")
346
+
347
+ # -------------------------------------------------
348
+ # LIVE SCADA CARDS
349
+ # -------------------------------------------------
350
+
351
+ col1, col2, col3 = st.columns(3)
352
+
353
+ latest_onoff = round(onoff_df["Temperature"].iloc[-1], 2)
354
+ latest_pid = round(pid_df["Temperature"].iloc[-1], 2)
355
+ latest_mpc = round(mpc_df["Temperature"].iloc[-1], 2)
356
+
357
+ with col1:
358
+ st.metric("ON-OFF Temp", f"{latest_onoff} °C")
359
+
360
+ with col2:
361
+ st.metric("PID Temp", f"{latest_pid} °C")
362
+
363
+ with col3:
364
+ st.metric("MPC Temp", f"{latest_mpc} °C")
365
+
366
+ # -------------------------------------------------
367
+ # DCS STYLE REACTOR DIAGRAM
368
+ # -------------------------------------------------
369
+
370
+ st.markdown("---")
371
+ st.subheader("⚙️ Reactor DCS Overview")
372
+
373
+ reactor_col1, reactor_col2 = st.columns([2, 1])
374
+
375
+ with reactor_col1:
376
+
377
+ fig_reactor = go.Figure()
378
+
379
+ fig_reactor.add_shape(
380
+ type="rect",
381
+ x0=1,
382
+ y0=1,
383
+ x1=4,
384
+ y1=8,
385
+ line=dict(color="cyan", width=4),
386
+ fillcolor="rgba(0,255,255,0.1)"
387
+ )
388
+
389
+ fig_reactor.add_annotation(
390
+ x=2.5,
391
+ y=4.5,
392
+ text=f"Reactor\nSP = {setpoint}°C",
393
+ showarrow=False,
394
+ font=dict(size=18)
395
+ )
396
+
397
+ fig_reactor.add_shape(
398
+ type="line",
399
+ x0=4,
400
+ y0=4,
401
+ x1=7,
402
+ y1=4,
403
+ line=dict(color="orange", width=8)
404
+ )
405
+
406
+ fig_reactor.add_annotation(
407
+ x=6,
408
+ y=4.5,
409
+ text="Cooling Water",
410
+ showarrow=False
411
+ )
412
+
413
+ fig_reactor.update_layout(
414
+ height=400,
415
+ paper_bgcolor="black",
416
+ plot_bgcolor="black",
417
+ font_color="white",
418
+ xaxis=dict(visible=False),
419
+ yaxis=dict(visible=False)
420
+ )
421
+
422
+ st.plotly_chart(fig_reactor, use_container_width=True)
423
+
424
+ with reactor_col2:
425
+
426
+ gauge = go.Figure(go.Indicator(
427
+ mode="gauge+number",
428
+ value=latest_pid,
429
+ title={'text': "PID Reactor Temp"},
430
+ gauge={
431
+ 'axis': {'range': [None, 120]},
432
+ 'bar': {'color': "red"},
433
+ 'steps': [
434
+ {'range': [0, 60], 'color': "lightgreen"},
435
+ {'range': [60, 90], 'color': "yellow"},
436
+ {'range': [90, 120], 'color': "orange"}
437
+ ]
438
+ }
439
+ ))
440
+
441
+ gauge.update_layout(height=400)
442
+
443
+ st.plotly_chart(gauge, use_container_width=True)
444
+
445
+ # -------------------------------------------------
446
+ # TEMPERATURE COMPARISON
447
+ # -------------------------------------------------
448
+
449
+ st.markdown("---")
450
+ st.subheader("📈 Temperature Response Comparison")
451
+
452
+ fig = go.Figure()
453
+
454
+ fig.add_trace(go.Scatter(
455
+ x=onoff_df["Time"],
456
+ y=onoff_df["Temperature"],
457
+ mode='lines',
458
+ name='ON-OFF'
459
+ ))
460
+
461
+ fig.add_trace(go.Scatter(
462
+ x=pid_df["Time"],
463
+ y=pid_df["Temperature"],
464
+ mode='lines',
465
+ name='PID'
466
+ ))
467
+
468
+ fig.add_trace(go.Scatter(
469
+ x=mpc_df["Time"],
470
+ y=mpc_df["Temperature"],
471
+ mode='lines',
472
+ name='MPC'
473
+ ))
474
+
475
+ fig.add_hline(
476
+ y=setpoint,
477
+ line_dash="dash",
478
+ annotation_text="Setpoint"
479
+ )
480
+
481
+ fig.update_layout(
482
+ template="plotly_dark",
483
+ height=500,
484
+ xaxis_title="Time",
485
+ yaxis_title="Temperature (°C)"
486
+ )
487
+
488
+ st.plotly_chart(fig, use_container_width=True)
489
+
490
+ # -------------------------------------------------
491
+ # VALVE POSITION COMPARISON
492
+ # -------------------------------------------------
493
+
494
+ st.markdown("---")
495
+ st.subheader("🌀 Cooling Valve Position")
496
+
497
+ fig2 = make_subplots(rows=1, cols=3,
498
+ subplot_titles=("ON-OFF", "PID", "MPC"))
499
+
500
+ fig2.add_trace(
501
+ go.Scatter(
502
+ x=onoff_df["Time"],
503
+ y=onoff_df["Valve"],
504
+ mode='lines'
505
+ ),
506
+ row=1,
507
+ col=1
508
+ )
509
+
510
+ fig2.add_trace(
511
+ go.Scatter(
512
+ x=pid_df["Time"],
513
+ y=pid_df["Valve"],
514
+ mode='lines'
515
+ ),
516
+ row=1,
517
+ col=2
518
+ )
519
+
520
+ fig2.add_trace(
521
+ go.Scatter(
522
+ x=mpc_df["Time"],
523
+ y=mpc_df["Valve"],
524
+ mode='lines'
525
+ ),
526
+ row=1,
527
+ col=3
528
+ )
529
+
530
+ fig2.update_layout(
531
+ template="plotly_dark",
532
+ height=450,
533
+ showlegend=False
534
+ )
535
+
536
+ st.plotly_chart(fig2, use_container_width=True)
537
+
538
+ # -------------------------------------------------
539
+ # CONTROL SYSTEM EXPLANATION
540
+ # -------------------------------------------------
541
+
542
+ st.markdown("---")
543
  st.subheader("📚 Control System Explanation")
544
 
545
  st.markdown("""
 
596
 
597
  st.markdown("---")
598
  st.markdown("## ✅ Simulation Complete")
599
+ st.markdown("Designed for Chemical Engineering DCS / SCADA Learning")