import dash
from dash import dcc, html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import numpy as np
import plotly.graph_objs as go
# PID Controller class
class PID:
def __init__(self, Kp, Ki, Kd, setpoint):
self.Kp = Kp
self.Ki = Ki
self.Kd = Kd
self.setpoint = setpoint
self.integral = 0
self.previous_error = 0
def update(self, measured_value, dt):
error = self.setpoint - measured_value
self.integral += error * dt
derivative = (error - self.previous_error) / dt
output = self.Kp * error + self.Ki * self.integral + self.Kd * derivative
self.previous_error = error
return output
# Function to generate different setpoint shapes
def generate_setpoint(shape, time, amplitude=50):
if shape == 'constant':
return np.ones_like(time) * amplitude
elif shape == 'sine wave':
return amplitude + amplitude * np.sin(time)
elif shape == 'sine wave with increasing frequency':
return amplitude + amplitude * np.sin(time * time)
elif shape == 'square wave':
return amplitude + amplitude * np.sign(np.sin(time))
elif shape == 'random noise':
return amplitude + np.random.normal(0, amplitude/2, len(time))
else:
return np.ones_like(time) * amplitude
# Initialize the app
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
# Layout of the app
app.layout = dbc.Container([
dbc.Row([
dbc.Col([
html.H2("PID Controller Simulation"),
html.Label("Setpoint Shape"),
dcc.Dropdown(
id='setpoint-shape-dropdown',
options=[
{'label': 'Constant', 'value': 'constant'},
{'label': 'Sine Wave', 'value': 'sine wave'},
{'label': 'Sine Wave with Increasing Frequency', 'value': 'sine wave with increasing frequency'},
{'label': 'Square Wave', 'value': 'square wave'},
{'label': 'Random Noise', 'value': 'random noise'}
],
value='constant'
),
html.Label("Kp"),
dcc.Slider(id='kp-slider', min=0, max=10, step=0.1, value=1,
marks={i: str(i) for i in range(0, 11)}),
html.Label("Ki"),
dcc.Slider(id='ki-slider', min=0, max=10, step=0.1, value=0.1,
marks={i: str(i) for i in range(0, 11)}),
html.Label("Kd"),
dcc.Slider(id='kd-slider', min=0, max=10, step=0.1, value=0.01,
marks={i: str(i) for i in range(0, 11)})
], width=3),
dbc.Col([
dcc.Graph(id='pid-graph')
], width=9)
])
])
# Callback to update the graph
@app.callback(
Output('pid-graph', 'figure'),
Input('setpoint-shape-dropdown', 'value'),
Input('kp-slider', 'value'),
Input('ki-slider', 'value'),
Input('kd-slider', 'value')
)
def update_graph(setpoint_shape, kp, ki, kd):
dt = 0.1
time = np.arange(0, 10, dt)
setpoint = generate_setpoint(setpoint_shape, time)
pid = PID(Kp=kp, Ki=ki, Kd=kd, setpoint=setpoint[0])
measured_value = 0
process_variable = []
for i, t in enumerate(time):
control_signal = pid.update(measured_value, dt)
measured_value += control_signal * dt
process_variable.append(measured_value)
if i < len(setpoint) - 1:
pid.setpoint = setpoint[i + 1]
figure = go.Figure()
figure.add_trace(go.Scatter(x=time, y=process_variable, mode='lines', name='Process Variable'))
figure.add_trace(go.Scatter(x=time, y=setpoint, mode='lines', name='Setpoint'))
figure.update_layout(title='PID Controller Response', xaxis_title='Time', yaxis_title='Process Variable')
return figure
# Run the app
if __name__ == '__main__':
app.run_server(debug=True)