Note: PySyDy has evolved into OpenCLD — a more feature-rich hybrid System Dynamics and AI library, published on PyPI as
opencld. For new projects, we recommend using OpenCLD. This repository is maintained for reference and as a foundation for the newer library.
pip install opencldPySyDy (Python System Dynamics) is a lightweight and intuitive Python library for building and simulating system dynamics models. It provides core components for representing stocks, flows, and auxiliary variables, making it easy to create and explore dynamic systems. The library also includes advanced features such as delays, behavior modes, feedback loops, and visualization capabilities.
System dynamics is a methodology for understanding the behavior of complex systems over time. PySyDy provides a Python implementation of system dynamics concepts, allowing you to build models that capture the structure and behavior of systems with feedback loops, delays, and nonlinear relationships.
You can install PySyDy using pip:
pip install pysydyOr clone the repository and install from source:
git clone https://github.com/pietroviero/PySyDy.git
cd PySyDy
pip install -e .A Stock represents an accumulation or level in your system. Stocks change over time through inflows and outflows.
Key Attributes:
name: The name of the stockvalue: The current value of the stockinflows: List of flows that increase the stockoutflows: List of flows that decrease the stock
Key Methods:
add_inflow(flow): Adds a flow that increases the stockadd_outflow(flow): Adds a flow that decreases the stockupdate(timestep): Updates the stock's value based on its inflows and outflowsget_value(): Returns the current value of the stock
Example:
# Create a stock representing a population of 1000 people
population = pysydy.Stock('Population', 1000)A Flow represents a rate of change that affects stocks. Flows can be inflows (increasing a stock) or outflows (decreasing a stock).
Key Attributes:
name: The name of the flowsource_stock: The stock that the flow originates from (can be None for sources)target_stock: The stock that the flow goes to (can be None for sinks)rate_function: A function that calculates the flow raterate: The current flow rate
Key Methods:
calculate_rate(system_state): Calculates the flow rate using the provided rate functionget_rate(): Returns the current flow rate
Example:
# Create a birth flow that increases the population
births = pysydy.Flow(
name='Births',
source_stock=None, # External source
target_stock=population,
rate_function=lambda state: state['stocks']['Population'].value * 0.05 # 5% birth rate
)An Auxiliary variable represents intermediate calculations that help define relationships between stocks and flows. These variables vary over time and are recalculated at each simulation step.
Key Attributes:
name: The name of the auxiliary variablecalculation_function: A function that calculates the auxiliary variable's valueinputs: A list of input variables that this auxiliary variable depends onvalue: The current value of the auxiliary variable
Key Methods:
calculate_value(system_state): Calculates the value using the provided calculation functionget_value(): Returns the current valueget_value_updated(system_state): Recalculates and returns the updated value
Example:
# Create an auxiliary variable for population density
population_density = pysydy.Auxiliary(
name='Population Density',
calculation_function=lambda state: state['stocks']['Population'].value / 100, # people per square km
inputs=['Population']
)A Parameter represents a constant value that does not change during a simulation. Parameters are used in calculations within flows and auxiliary variables.
Key Attributes:
name: The name of the parametervalue: The numerical value of the parameterunits: Units of measurement for the parameterdescription: A description of what the parameter represents
Key Methods:
get_value(): Returns the value of the parameter
Example:
# Create a parameter for the land area
land_area = pysydy.Parameter('Land Area', 100, units='square kilometers',
description='Total land area available')The Simulation class manages the simulation of a system dynamics model, handling time stepping, calculation order, and data collection.
Key Attributes:
stocks: List of stocks in the modelflows: List of flows in the modelauxiliaries: List of auxiliary variables in the modelparameters: List of parameters in the modeltimestep: The time interval for each simulation steptime: The current simulation timehistory: A list of system states recorded during the simulation
Key Methods:
step(): Advances the simulation by one timesteprun(duration): Runs the simulation for a given durationget_results(): Returns a DataFrame with the simulation results
Example:
# Create and run a simulation
sim = pysydy.Simulation(
stocks=[population],
flows=[births, deaths],
auxiliaries=[population_density],
parameters=[land_area],
timestep=0.1
)
# Run for 50 time units
sim.run(50)
# Get and plot results
results = sim.get_results()Delays represent time lags in a system. PySyDy provides three types of delays:
Material delays occur when physical entities take time to move through a process. They conserve the quantity being delayed.
Key Attributes:
name: The name of the delaydelay_time: The average time it takes for material to flow through the delayorder: The order of the delay (number of stages)outflow: The current outflow rate
Key Methods:
update(inflow, timestep): Updates the delay based on the current inflowget_outflow(): Returns the current outflow rate
Information delays occur when information takes time to be perceived, processed, or transmitted. They smooth out fluctuations in the input signal.
Key Attributes:
name: The name of the delaydelay_time: The average time it takes for information to be processedorder: The order of the delay (number of stages)output: The current output value
Key Methods:
update(input_value, timestep): Updates the delay based on the current inputget_output(): Returns the current output value
Fixed delays represent a precise time lag where the output exactly matches the input after a fixed time period.
Key Attributes:
name: The name of the delaydelay_time: The exact time it takes for the input to appear at the outputhistory: A queue of past input values
Key Methods:
update(input_value, timestep): Updates the delay based on the current inputget_output(): Returns the current output value
Example:
# Create a material delay for a manufacturing process
production_delay = pysydy.MaterialDelay(
name="Production Delay",
delay_time=5.0, # 5 time units to complete production
initial_value=0.0,
order=3 # Third-order delay for more realistic behavior
)
# Update the delay with the current production rate
output_rate = production_delay.update(input_rate, timestep)Behavior modes represent common patterns of system behavior over time. PySyDy provides implementations of these patterns:
Represents exponential growth behavior where a quantity increases at a rate proportional to its current value.
Represents exponential decay behavior where a quantity decreases at a rate proportional to its current value.
Represents goal-seeking behavior where a quantity adjusts toward a target value over time.
Represents oscillatory behavior where a quantity fluctuates around a goal value due to delays in the system.
Represents S-shaped or logistic growth where growth is initially exponential but slows as it approaches a carrying capacity.
Represents overshoot and collapse behavior where a system exceeds its carrying capacity and then collapses.
Example:
# Create an exponential growth model for a population
population_growth = pysydy.ExponentialGrowth(
name="Population Growth",
initial_value=100.0,
growth_rate=0.05 # 5% growth rate per time unit
)
# Update the population for one time unit
new_population = population_growth.update(1.0)Feedback loops are circular causal relationships in a system. PySyDy provides classes to document and analyze these loops:
Reinforcing loops amplify changes in a system, creating exponential growth or collapse.
Key Attributes:
name: The name of the feedback loopcomponents: A list of components that form the loopdescription: A description of the feedback mechanismpolarity: The polarity of the loop (positive for reinforcing loops)
Key Methods:
get_components(): Returns the components that form the feedback loop
Balancing loops counteract changes in a system, creating goal-seeking or oscillatory behavior.
Key Attributes:
name: The name of the feedback loopcomponents: A list of components that form the loopdescription: A description of the feedback mechanismpolarity: The polarity of the loop (negative for balancing loops)
Key Methods:
get_components(): Returns the components that form the feedback loop
Represents a collection of feedback loops in a system dynamics model, helping to analyze the overall feedback structure.
Key Attributes:
name: The name of the feedback structureloops: A list of feedback loops in the structure
Key Methods:
add_loop(loop): Adds a feedback loop to the structureget_loops(): Returns all loops in the structureget_reinforcing_loops(): Returns only reinforcing loopsget_balancing_loops(): Returns only balancing loops
Example:
# Document a reinforcing feedback loop in a population model
population_loop = pysydy.ReinforcingLoop(
name="Population Growth Loop",
components=[population, births],
description="More people lead to more births, which further increases the population."
)The Graph class provides visualization capabilities for system dynamics models, showing the relationships between stocks, flows, auxiliaries, and parameters.
Key Methods:
plot(figsize, node_size, font_size, show_values): Plots the graph representation of the modelupdate_graph(): Updates the graph with current values from the simulationhighlight_loop(loop): Highlights a feedback loop in the graphhighlight_path(components): Highlights a path of components in the graph
Example:
# Create a graph visualization of the model
model_graph = pysydy.Graph(sim)
# Plot the graph
fig, ax = model_graph.plot(figsize=(10, 8), show_values=True)The Chart class provides time series visualization capabilities for system dynamics models, showing how stocks, flows, and auxiliary variables change over the simulation period.
Key Methods:
plot_stocks_time_series(): Plots the time series of stock valuesplot_flows_time_series(): Plots the time series of flow ratesplot_auxiliaries_time_series(): Plots the time series of auxiliary variable valuesplot_all_variables(): Plots all variables in the modelplot_variable(variable_name): Plots a specific variableplot_multiple_variables(variable_names): Plots multiple variables togetherplot_phase_diagram(x_var, y_var): Creates a phase diagram of two variables
Example:
# Create a chart visualization of the simulation results
model_chart = pysydy.Chart(sim)
# Plot the time series of stock values
fig, ax = model_chart.plot_stocks_time_series()The PySyDy package includes several example models in the examples directory:
- SIR Model: A basic Susceptible-Infected-Recovered epidemiological model
- Delay Examples: Demonstrations of different delay types
- Behavior Modes: Examples of common system behavior patterns
- Feedback Loops: Examples of reinforcing and balancing feedback structures
- Graph Visualization: Examples of model structure visualization
import pysydy
import matplotlib.pyplot as plt
# Create stocks (compartments)
susceptible = pysydy.Stock('Susceptible', 9999)
infected = pysydy.Stock('Infected', 1)
recovered = pysydy.Stock('Recovered', 0)
# Create parameters
contact_rate = pysydy.Parameter('contact_rate', 6.0, units='contacts/person/day')
infectivity = pysydy.Parameter('infectivity', 0.25, units='probability')
recovery_rate = pysydy.Parameter('recovery_rate', 0.5, units='1/day')
total_population = pysydy.Parameter('total_population', 10000.0, units='people')
# Create flows
infection_flow = pysydy.Flow(
name='infection',
source_stock=susceptible,
target_stock=infected,
rate_function=lambda state: (
state['parameters']['contact_rate'].value *
state['parameters']['infectivity'].value *
state['stocks']['Susceptible'].value *
state['stocks']['Infected'].value /
state['parameters']['total_population'].value
)
)
recovery_flow = pysydy.Flow(
name='recovery',
source_stock=infected,
target_stock=recovered,
rate_function=lambda state: (
state['parameters']['recovery_rate'].value *
state['stocks']['Infected'].value
)
)
# Create and run simulation
sim = pysydy.Simulation(
stocks=[susceptible, infected, recovered],
flows=[infection_flow, recovery_flow],
parameters=[contact_rate, infectivity, recovery_rate, total_population],
timestep=0.125
)
sim.run(30) # Run for 30 days
# Get and plot results
results = sim.get_results()
plt.figure(figsize=(10, 6))
plt.plot(results.index, results['Susceptible'], label='Susceptible')
plt.plot(results.index, results['Infected'], label='Infected')
plt.plot(results.index, results['Recovered'], label='Recovered')
plt.xlabel('Time (days)')
plt.ylabel('Population')
plt.title('SIR Model')
plt.legend()
plt.grid(True)
plt.show()This project is licensed under the MIT License - see the LICENSE file for details.