Python Simulation Modeling Explained: Building Dynamic Models for Real-World Scenarios

Dive into the world of simulation modeling in Python, empowering users to create accurate and efficient simulations for various applications.

Overview

A simulation is a process that represents the real-world system and is often utilized when conducting an experiment on systems that is impossible or not viable. Businesses over the years have been using simulations to test various scenarios to make business decisions with more confidence eg: Optimizing resource utilization, product pricing, etc. If the simulations are made interactive and if the results can be measured instantly, it would not only help businesses to understand the system better but also build trust quickly.

In this blog, we will be using python to build an app for simulating the probability of pipe failure. The simulated results would help businesses to decide on whether to continue with the current design or to review and redesign the pipe. It is suggested to go through my previous blog — Monte Carlo Simulation For Pipe Failure In Python for a better understanding of the implementation. The focus of this blog is on extending the functionality and building a ploty dash app in python. By the end of the blog, we will build an app as below.

Source: Author

Project Set-Up

It is recommended that a virtual environment is created for this project by following the below steps.

Step 1: Install the virtual environment if not already done

pip install virtualenv

Step 2: Create a virtual environment by the name eg: hoop_stress

virtualenv hoop_stress

Step 3: We can switch to hoop_stress environment and activate it using the below command.

hoop_stress\scripts\activate

Once, the environment is ready, we will load the libraries.

Loading Python Packages

You can install these packages individually with the below command or use the requirement.txt from Github.

pip install <package name>
import dash
import time
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_daq as daq
import dash_bootstrap_components as dbc
import numpy as np
import pandas as pd
import plotly.graph_objs as go
import plotly.express as px
import layout
import pre_processing as ppor
pip install -r requirements.txt

Building the UI

The entire app can be built into one single file app.py but in this case, we will segregate frontend and backend. In the end, we stitch everything together in app.py. We will start with designing all the input variables namely diameter, thickness, yield strength, and respective covariances. In this case, a slider control will be useful. Here is an example of simple slider control for diameter.

daq.Slider(
id='val-diameter',
min=0,
max=100,
value=65,
handleLabel={
"showCurrentValue": True, "label": "Value"},
step=10
)
daq.Slider(
id='val-diameter-cov',
min=0,
max=10,
value=7,
handleLabel={
"showCurrentValue": True, "label": "Value"},
step=1
)
Source: Author

We will design the rest of the controls the same way. Here is the complete file layout.py

We will create a button with the name “RUN SIMULATION” and on click would trigger the simulation process.

html.Button('RUN SIMULATION', id='btn-run-simulation', n_clicks=0, style = gray_button_style)

Building the server-side logic

We will build our back-end logic in pre_processing.py and here is the list of functions we will create to process the data.

initiate_simulation: This will be the starting point of the execution.

def initiate_simulation(
# iter_start = simulation_data.iter_start,
# iter_end = simulation_data.iter_end,
# iter_step = simulation_data.iter_step,
diameter_mean=simulation_data.diameter_mean,
diameter_cov=simulation_data.diameter_cov,
thickness_mean=simulation_data.thickness_mean,
thickness_cov=simulation_data.thickness_cov,
strength_mean=simulation_data.yield_mean,
strength_cov=simulation_data.yield_cov,
internal_pressure=simulation_data.internal_pressure):
"""summaryArgs:
iter_start (int, optional): define starting point for generating simulation values. Defaults to simulation_data.iter_start.
iter_end (int, optional): define end point for generating simulation values. Defaults to simulation_data.iter_end.
iter_step (int, optional): define the increment. Defaults to simulation_data.iter_step.
"""
try:
# print("---------------------------------------Initiating Simulations ---------------------------------------")
runs = list(range(simulation_data.iter_start,
simulation_data.iter_end, simulation_data.iter_step))
sim_res = []
for run in runs:
sim_results = run_simulation(run)
sim_res.append(sim_results)
# print(f'Simulation run for {run}: {sim_results}')
# print(f'sim_res: {sim_res}')
# print("Before plot generation")
return plot_linechart(runs, sim_res)
# plot_histogram(runs, sim_res)except Exception as e:
print(
f'An exception occurred while trying to initiate the simulation: {e}')

run_simulation: This function will iterate through the process a defined number of times.

def run_simulation(iterations):
"""summaryReturns:
float: The function returns the number of negative values found in the simulation - negative value indicates that pipe failedHoop Stress:
int: Hoop stress =(Internal Pressure * Diameter)/(2 * Thickness)

Objective:
int: Failure = Yield - Hoop stress
"""
try:lst_diameter = simulate_values(
simulation_data.diameter_mean, simulation_data.diameter_std, iterations, False)lst_thickness = simulate_values(
simulation_data.thickness_mean, simulation_data.thickness_std, iterations, False)lst_yield = simulate_values(
simulation_data.yield_mean, simulation_data.yield_std, iterations, False)df_final = pd.DataFrame(list(zip(lst_diameter, lst_thickness, lst_yield)), columns=[
'Diameter', 'Thickness', 'Yield'])df_final['Hoop_Stress'] = simulation_data.internal_pressure * \
df_final['Diameter'] / (2 * df_final['Thickness'])df_final['Objective'] = df_final['Yield'] - df_final["Hoop_Stress"]min_stress = df_final['Hoop_Stress'].min()max_stress = df_final['Hoop_Stress'].max()return df_final[df_final['Objective'] < 0].shape[0] / iterationsexcept Exception as e:
print(f'An exception occurred with running the simulation: {e}')

simulate_values: This function generates the random values within the defined distribution.

def simulate_values(mu, sigma, iterations, print_output=True):
"""summaryArgs:
mu (int): Define the mean of the diameter, thickness and yield
sigma (int): Define the standard deviation of the diameter, thickness and yield
iterations (int): Define number of iterations the simulations to be run
print_output (bool, optional): Set value to True to view the print statement at various stages and set False to skip the prints. Defaults to True.Returns:
List: The function returns the list which has the simulation values for diameter, thickess and yield
"""
try:
result = []
for i in range(iterations):
prob_value = round(random.uniform(.01, 0.99), 3)
sim_value = round(NormalDist(
mu=mu, sigma=sigma).inv_cdf(prob_value), 3)if print_output:
print(
f"The prob value is {prob_value} and the simulated value is {sim_value}")
result.append(sim_value)
return result
except Exception as e:
print(f'An exception occurred while generating simulation values: {e}')

plot_linechart: To plot the line chart

def plot_linechart(runs, sim_res):
try:
y_mean = np.mean(sim_res)
# print(f'y_mean:{y_mean}')
df_res = pd.DataFrame(zip(runs, sim_res), columns=['runs', 'sim_res'])
fig = px.line(df_res, x="runs", y="sim_res",
title='Monte Carlo Simulation - Probability Of Pipe Failure')
fig.add_hline(y=y_mean, line_color="red",line_dash="dash")
fig.add_annotation(x= simulation_data.iter_end/2 , y= y_mean,
text= f'Mean Probability of failure ({round(y_mean,5)})',
showarrow=True,
arrowhead=1),
fig.update_layout(
showlegend=False,
xaxis_title="No of iteration/simulations",
yaxis_title= "Probability of failure")
return figexcept Exception as e:
print(f'An error occurred while trying to generate line chart {e}')
Source: Author

For more details and an explanation of each of the functions, please read my previous blog — Monte Carlo Simulation For Pipe Failure In Python

Establishing interactivity

Now, that we have a front end and back end of the app ready, it’s time to bring in interactivity between the slider controls and the line chart meaning every time we make a different selection on the slider value and click the RUN SIMULATION button, the simulation is triggered and the plot is updated.

@app.callback(    
[
Output("plt-failure", 'figure'),
Output('textarea-description', 'children')
],

[Input('btn-run-simulation', 'n_clicks')],
state = [State("val-diameter", "value"),
State("val-diameter-cov", "value"),
State("val-thickness", "value"),
State("val-thickness-cov", "value"),
State("val-strength", "value"),
State("val-strength-cov", "value"),
State("val-internal-pressure", "value")]

)
def updatechart(n_clicks, diameter, diameter_cov, thickness, thickness_cov, strength, strength_cov, internal_pressue):
"""_summary_Args:
n_clicks (_type_): _description_
diameter (int): Diameter of the pipe
diameter_cov (int): Covariance of the diameter
thickness (int): Thickness of the pipe
thickness_cov (int): Covariance of the thickness
strength (int): Strength / Yield strength of the pipe
strength_cov (int): Covariance of the yield strength
internal_pressue (int): Inter Pressue in the pipe.Returns:
Figure: Return a line chart fig
str: Returns the description of the hoop stress.
"""

str_description = "The pipe will fail if the Hoop stress becomes greater than its yield strength. This app will help simulate this scenario hundreds or maybe a thousand times, and calculates the probability of failure for each iteration. Depending on the result, the decision can be made to either continue to the current design or to review and redesign the pipe. Here is the mathematical way to define the hoop stress."
fig_linechart = pp.initiate_simulation(diameter, diameter_cov, thickness, thickness_cov, strength, strength_cov, internal_pressue)
return fig_linechart,str_description

How to run the application

If you have reached this far and have faced no errors along the way then the next steps should allow you to run the application.

Step1: Open the terminal, navigate to the project directory

Step 2: Type the below command

<working directory>python app.py

Step 3: The terminal will run the application.

Source: Author

Step 4: Navigate to the browser and type the localhost URL. The app will be loaded as shown below.

http:127.0.0.1:8050
Source: Author

Now set different values for each of the sliders and click the button to trigger the simulation and view the chart getting updated. For selected values of inputs if the probability of failure crosses a threshold defined then the pipe design should be reviewed.

Closing Note

We have successfully built a simulation app in python. In this blog, we learned how to build the front end and back end of the application. We stitched everything together and brought interactivity between various controls to view the simulated results instantly.

In my previous blog, we did something similar by changing the values of variables in the config file and running the app to see the simulated results. This is good for short experimentation and exploration but not something one would want to do repeatedly. The app we just built is a big step forward and moves toward interactive simulation for instant insights.

Possible enhancement: A lot more features can be implemented like having the user define a number of iterations from the UI, having plots showcasing the impact of variable selection on pipe failure, saving the simulated results in the database, and of course deployment of the app on the cloud would be the end goal.

Hope the blog was useful and would encourage you to build one of your own.

You can connect with me on Linkedin

You can find the code on Github for reference.

References

https://dash.plotly.com/introduction

Leave a Reply

Your email address will not be published. Required fields are marked *