Introduction ============ Welcome to the se-lib User's Guide with examples. The examples after the User's Guide can be run separately from their cells, copied and pasted. -------------- Installation ------------ Run the cell below once at the beginning of each session to install the se-lib library. The ``from selib import *`` statement will import all its functions and classes to be called with no alias in the notebook. -------------- :: !pip install -q se-lib from selib import * -------------- -------------- System Dynamics Modeling User's Guide ===================================== July 21, 2025 ~~~~~~~~~~~~~ Table of Contents ----------------- - `1. Overview <#1-overview>`__ - `2. Getting Started with se-lib <#2-getting-started-with-se-lib>`__ - `3. Modeling Guidance and Tips <#3-modeling-guidance-and-tips>`__ - `4. Basic Modeling Patterns <#4-basic-modeling-patterns>`__ - `4.1 Negative Feedback Loop <#41-negative-feedback-loop>`__ - `4.2 Exponential Growth <#42-exponential-growth>`__ - `4.3 Balancing Loop <#43-balancing-loop>`__ - `4.4 Goal-Gap Structure <#44-goal-gap-structure>`__ - `4.5 First-Order Delay <#45-first-order-delay>`__ - `4.6 Second-Order Delay <#46-second-order-delay>`__ - `5. Displaying Output <#5-displaying-output>`__ - `6. Graphing Functions <#6-graphing-functions>`__ - `7. Random Number Functions <#7-random-number-functions>`__ - `8. Utility and Test Functions <#8-utility-and-test-functions>`__ - `9. Advanced Usage <#9-advanced-usage>`__ - `9.1 Extending a Model Instance <#91-extending-a-model-instance>`__ - `10. Appendix A - Function Reference <#10-appendix-a---function-reference>`__ -------------- 1. Overview ----------- This manual provides an introduction and reference for modeling continuous systems using the *se-lib* Python package. se-lib supports the construction and simulation of system dynamics models using standard stock and flow structures using both procedural and object-oriented approaches. It adheres to the XMILE standard for system dynamics models and integrates with `PySD `__ for simulation execution. Models are compatible with Vensim and iThink/Stella tools using XMILE and MDL formats for importing and exporting. The object-oriented interface is built around the ``SystemDynamicsModel`` class, enabling encapsulation of models, concurrent instantiation, and extensions of the model class. Users may also use the original procedural API for quick prototyping or compatibility with existing scripts. The procedural approach provides a more implicit modeling, but can be less flexible. The manual includes reusable modeling templates, core system dynamics patterns such as goal-gap control and balancing loops, recommended modeling practices, detailed API documentation, and visual representations of feedback logic. It is suitable for a wide range of applications in systems thinking, behavioral simulation, engineering analysis, and education. Detailed function references and more examples are available online at http://selib.org/function_reference.html#system-dynamics. -------------- 2. Getting Started with SE-Lib ------------------------------ To install the latest version of se-lib, use pip: :: pip install se-lib A system model is described by defining the standard elements for stocks (levels), flows (rates), and auxiliary constants or equations. Utility functions are available for equation formulation, data collection and displaying output. Model construction begins by importing se-lib and initializing a model with the required time parameters. Simulations operate over a user-defined time horizon and return data as pandas DataFrames, which can be used for analysis and visualization. :: from selib import * init_sd_model(start=0, stop=30, dt=1) Refer to Section 11 for a complete description of the modeling API. -------------- 3. Modeling Guidance and Tips ----------------------------- Modeling with se-lib requires attention to syntax, naming conventions, and logical structure. Names of model elements are specified as character strings and should not contain spaces; underscores (``_``) must be used to separate terms (e.g., ``Production_Rate``). Variable names are case-sensitive and must exactly match those used in equations. Equations are expressed as strings using standard arithmetic operators. Variables referenced in equations must already be defined within the model scope. The simulator does not check equations at entry time, so errors from undefined variables or misspellings will only become apparent at runtime. Conditional statements can be expressed with logical constructs ``or``, ``and``, and ``not`` or the XMILE uppercase standards ``OR``, ``AND``, and ``NOT``. The ``if_then_else(condition, value if true, value if false)`` or uppercase ``IF_THEN_ELSE(condition, value if true, value if false)`` are available. Random number functions such as ``random()`` and ``random.uniform(a, b)`` are supported and internally translated into XMILE-compliant expressions. These generate new values at each timestep and are useful for modeling uncertainty or variability in external influences. Time in se-lib is dimensionless. Modelers must maintain consistency in time unit interpretation (e.g., using all variables in units of days, months, etc.). Inconsistent use of time units can lead to misleading or invalid results. The ``draw_model()`` function is recommended for verifying model structure prior to simulation. This visual representation helps ensure the correctness of stock-flow relationships and the presence of intended feedback loops. -------------- 4. Basic Modeling Patterns -------------------------- 4.1 Negative Feedback Loop ~~~~~~~~~~~~~~~~~~~~~~~~~~ This classic balancing structure adjusts a level toward a target over time. It works by calculating the gap between a goal and the current level, then feeding a corrective flow in the direction that reduces this gap. The delay serves as a time constant, governing the speed of the system's response. Over time, the level asymptotically approaches the goal value. :: from selib import * # negative feedback init_sd_model(start=0, stop=5, dt=.1) add_stock("level", 50, inflows=["rate"]) add_auxiliary("delay", .5) add_auxiliary("goal", 100) add_flow("rate", "(goal - level) / delay") draw_model() run_model() plot_graph("level") |negative_feedback_diagram| |negative_feedback_run_output| |negative_feedback_level_graph| 4.2 Exponential Growth ~~~~~~~~~~~~~~~~~~~~~~ This reinforcing loop demonstrates positive feedback. The flow increases as the stock increases, which in turn further amplifies the flow. This results in exponential behavior and is frequently seen in unchecked population growth, viral spread, or compound interest. :: from selib import * # exponential growth init_sd_model(start=0, stop=10, dt=.5) add_stock("level", 100, inflows=["rate"]) add_auxiliary("growth_fraction", .8) add_flow("rate", "growth_fraction * level") draw_model() run_model() plot_graph('level') |exponential_growth_diagram| |exponential_growth_run_output| |exponential_growth_level_graph| 4.3 Balancing Loop ~~~~~~~~~~~~~~~~~~ A balancing loop with inventory control adjusts production to maintain a desired stock level. When inventory falls below target, production increases; when it's above target, production slows. This loop exhibits goal-seeking behavior and helps stabilize system performance. External demand acts as an opposing outflow. :: from selib import * init_sd_model(0, 10, 1) add_stock("Inventory", 50, inflows=["Production_Rate"], outflows=["Demand_Rate"]) add_auxiliary("Target_Inventory", 100) add_auxiliary("Adj_Time", 4) add_flow("Production_Rate", "(Target_Inventory - Inventory) / Adj_Time") add_auxiliary("Demand", 10) add_flow("Demand_Rate", "Demand") draw_model() run_model() plot_graph(["Inventory", "Production_Rate"]) |balancing_loop_model_diagram| |balancing_loop_model_run_output| |balancing_loop_model_output| 4.4 Goal-Gap Structure ~~~~~~~~~~~~~~~~~~~~~~ This control structure reduces the deviation between a system's state and a set point. A feedback signal (difference between goal and current value) is amplified by a gain factor and used to drive the stock toward the goal. :: from selib import * init_sd_model(0, 30, 1) add_stock("Temperature", 20, inflows=["Heating"]) add_auxiliary("Set_Point", 22) add_auxiliary("Gain", 1.5) add_flow("Heating", "(Set_Point - Temperature) * Gain") draw_model() run_model() plot_graph(["Temperature", "Set_Point"]) |goal_gap_structure_delay_model_diagram| |goal_gap_structure_model_run_output| |goal_gap_structure_model_output| 4.5 First-Order Delay ~~~~~~~~~~~~~~~~~~~~~ First-order delays represent lagged responses where a stock adjusts toward an input with exponential smoothing. The greater the delay time, the slower the convergence. :: from selib import * init_sd_model(0, 15, 1) add_stock("Output", 0, inflows=["Flow"]) add_auxiliary("Input", 100) add_auxiliary("Delay_Time", 5) add_flow("Flow", "(Input - Output) / Delay_Time") draw_model() run_model() plot_graph(["Input", "Output"]) |first_order_delay_model_diagram| |first_order_delay_model_run_output| |first_order_delay_model_output| 4.6 Second-Order Delay ~~~~~~~~~~~~~~~~~~~~~~ A second-order delay structure creates smoother responses by cascading two first-order delays. This is useful when modeling systems with inertia or buffer stages such as material transport, bureaucratic processes, or cognitive recognition lags. :: from selib import * init_sd_model(0, 30, 1) add_stock("Stage1", 0, inflows=["Flow1"], outflows=["Flow2"]) add_stock("Stage2", 0, inflows=["Flow2"]) add_auxiliary("Input", 100) add_auxiliary("DelayTime", 5) add_flow("Flow1", "(Input - Stage1) / DelayTime") add_flow("Flow2", "(Stage1 - Stage2) / DelayTime") draw_model() run_model() plot_graph(["Stage1", "Stage2"]) |second_order_delay_model_diagram| |second_order_delay_model_run_output| ... |second_order_delay_model_output| -------------- 5. Displaying Output -------------------- Upon execution of a system dynamics model, se-lib returns simulation results as a ``pandas.DataFrame`` object. This output contains the time-indexed values of all defined stocks, flows, and auxiliaries. The DataFrame facilitates further analysis, visualization, or export to external tools. The :literal:`\`run_model()` function will execute a simulation and display a Pandas dataframe of the output. To execute a model and capture its output: :: results = run_model() Each column in the DataFrame corresponds to a model variable, and each row represents a timestep, as defined by the start, stop, and ``dt`` parameters provided during model initialization. To inspect specific variables or values at particular timesteps, standard pandas indexing may be used: :: results['Population'] # Full time series for Population results['Births'][10] # Value of Births at the 10th timestep results[['Population', 'Births']] # Multiple variables This structured output enables direct integration with plotting functions, statistical analysis routines, or export mechanisms. All graphing and saving operations described in Section 6 operate directly on this result structure. -------------- 6. Graphing Functions --------------------- se-lib provides built-in graphing capabilities to visualize model outputs directly from the simulation results. These graphs are generated using the ``matplotlib`` library and support both single-variable and multi-variable plots. Plotting to Screen ~~~~~~~~~~~~~~~~~~ The ``plot_graph()`` function displays simulation results interactively. It supports multiple input formats: single variable names, separate arguments, or a list of variables. :: plot_graph("Population") plot_graph("Births", "Deaths") plot_graph(["Population", "Births", "Deaths"]) When a list is used, variables are plotted together on a shared axis. When passed as separate arguments, they are displayed on individual subplots. Saving to File ~~~~~~~~~~~~~~ The ``save_graph()`` function saves output graphs to image files. The default format is PNG, and the format may be changed by modifying the filename extension. :: save_graph("Population", filename="population_plot.png") save_graph(["Births", "Deaths"], filename="flow_graph.svg") Saved graphs are rendered as separate figures and written to disk, which is useful for reporting or presentation workflows. As with plotting, graphing functions operate on the output returned by ``run_model()``. -------------- 7. Random Number Functions -------------------------- se-lib supports stochastic modeling using random number generation within auxiliary and flow equations. This allows the representation of uncertainty, noise, or nondeterministic behavior in the modeled system. In equations for auxiliaries and rates, the random number functions supported are called as if the following import has occurred: ``from random import random, random.uniform``. The random functions supported are: - ``random()``: returns a new float uniformly distributed between 0 and 1 at each timestep. - ``random.uniform(min, max)``: returns a new float uniformly distributed between ``min`` and ``max`` at each timestep. These functions are automatically translated to XMILE-compatible expressions when the model is compiled for simulation. For xmile format compatibility, the functions ``RANDOM_0_1`` and ``RANDOM_UNIFORM(min, max)`` are equivalent and also acceptable. Example ~~~~~~~ The following example defines an auxiliary variable using random noise: :: init_sd_model(start=0, stop=3, dt=.5) add_auxiliary("random_parameter", "20*random()") run_model() :: ( INITIAL TIME FINAL TIME TIME STEP SAVEPER random_parameter 0.0 0 3 0.5 0.5 0.087466 0.5 0 3 0.5 0.5 11.174929 1.0 0 3 0.5 0.5 13.506344 1.5 0 3 0.5 0.5 4.342211 2.0 0 3 0.5 0.5 5.449781 2.5 0 3 0.5 0.5 7.323554 3.0 0 3 0.5 0.5 17.220789, Each timestep generates a new random value for ``Stochastic_Input``, producing a non-repeating time series. It is important to note that random values are recomputed each time the model is run, and results may vary unless reproducibility measures (e.g., seeding) are implemented outside SE-Lib. -------------- 8. Utility and Test Functions ----------------------------- Standard test functions are available for pulse, ramp and step inputs as shown below. The full set of `functions available in PySD `__ can be used but have not all been tested. :: init_sd_model(0, 10, 1) add_stock("Level", 0, inflows=["Pulse", "Ramp"]) add_flow("Pulse", "pulse(100, 2)") add_flow("Ramp", "ramp(3, 5)") run_model() plot_graph("Pulse", "Ramp", "Level") -------------- 9. Advanced Usage ----------------- 9.1 Extending a Model Instance ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Create a new class that extends a population model by adding carrying capacity with the following. :: class PopulationWithCapacityModel(SystemDynamicsModel): def __init__(self, start, stop, dt, initial_population, carrying_capacity): super().__init__(start, stop, dt) self.add_stock("Population", initial=initial_population, inflows=["Births"], outflows=["Deaths"]) self.add_auxiliary("BirthRate", equation=0.02) self.add_auxiliary("CarryingCapacity", equation=carrying_capacity) self.add_auxiliary("CrowdingEffect", equation="Population / CarryingCapacity") self.add_auxiliary("EffectiveGrowthRate", equation="BirthRate * (1 - CrowdingEffect)") self.add_flow("Births", equation="Population * EffectiveGrowthRate") self.add_auxiliary("DeathRate", equation=0.01) self.add_flow("Deaths", equation="Population * DeathRate") # Keep a simple death rate # Create an instance of the extended model population_capacity_model = PopulationWithCapacityModel(start=0, stop=200, dt=1, initial_population=50, carrying_capacity=500) 9.1 PySD Integration ~~~~~~~~~~~~~~~~~~~~ SE-Lib is fully compatible with PySD. Any PySD function or supported XMILE structure may be used where appropriate. See `PySD Docs `__ for advanced formulations. -------------- 10. Appendix A - Function Reference ----------------------------------- The following functions and methods define the modeling API for se-lib. All functions can be used in either procedural or object-oriented workflows. ``init_sd_model(start, stop, dt, stop_when=None)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Initializes a new system dynamics model over the specified simulation time frame. **Args**: - ``start`` (float): Start time of the simulation. - ``stop`` (float): End time of the simulation. - ``dt`` (float): Time step for numerical integration. - ``stop_when`` (float): Logical condition using model variables to stop the simulation. **Returns**: - SystemDynamicsModel instance **Example**: :: init_sd_model(start=0, stop=50, dt=1.0) **Examples with stop_when**: :: init_sd_model(start=0, stop=50, dt=1.0, stop_when("stock1 >= 100") :: init_sd_model(start=0, stop=50, dt=1.0, stop_when("stock1 >= 100 or stock2 >=80") -------------- ``add_stock(name, initial, inflows=[], outflows=[])`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Adds a stock variable (level) to the system dynamics model. **Args**: - ``name`` (str): Name of the stock. - ``initial`` (float): Initial value of the stock. - ``inflows`` (list, optional): Names of inflow variables affecting this stock. - ``outflows`` (list, optional): Names of outflow variables affecting this stock. **Returns**: - None **Example**: :: add_stock("Population", initial=100, inflows=["Births"], outflows=["Deaths"]) -------------- ``add_flow(name, equation)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines a flow (rate of change) for use in stock updates. **Args**: - ``name`` (str): Name of the flow. - ``equation`` (str): Equation as a string to compute the flow value. **Returns**: - None **Example**: :: add_flow("Births", "Birth_Rate * Population") -------------- ``add_auxiliary(name, equation)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines an auxiliary variable or constant used in the model. **Args**: - ``name`` (str): Name of the auxiliary variable. - ``equation`` (str): String representation of the equation or constant value. **Returns**: - None **Example**: :: add_auxiliary("Birth_Rate", "0.02") -------------- ``add_auxiliary_lookup(name, input, xpoints, ypoints)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Creates a lookup function for a nonlinear relationship. **Args**: - ``name`` (str): Name of the auxiliary variable. - ``input`` (str): Name of the input variable used in the lookup. - ``xpoints`` (list): List of input values (x-axis). - ``ypoints`` (list): List of output values (y-axis). **Returns**: - None **Example**: :: add_auxiliary_lookup("Saturation", "Population", [0, 1000, 2000], [1, 0.5, 0.1]) -------------- ``plot_graph(*outputs)`` ~~~~~~~~~~~~~~~~~~~~~~~~ Plots model results using ``matplotlib``. **Args**: - ``*outputs`` (str or list): Names of variables to plot (e.g., "Population", ["Births", "Deaths"]). **Returns**: - None **Example**: :: plot_graph("Population") plot_graph(["Births", "Deaths"]) -------------- ``save_graph(*outputs, filename='graph.png')`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Saves a plot of the simulation draw_sd_model(filename=None, format='svg') ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Generates and renders a visual system dynamics model diagram using Graphviz. get_stop_time() ~~~~~~~~~~~~~~~ Returns the logical end time based on a stop_when condition. get_model_structure() ~~~~~~~~~~~~~~~~~~~~~ Returns a dictionary of model components (stocks, flows, auxiliaries). -------------- Examples ======== -------------- .. _exponential-growth-1: Exponential Growth ------------------ -------------- :: from selib import * # exponential_growth init_sd_model(start=0, stop=10, dt=.5) add_stock("level", 100, inflows=["rate"]) add_auxiliary("growth_fraction", .8) add_flow("rate", "growth_fraction * level") draw_model() run_model() save_graph('level', filename="exponential_growth.png") -------------- :: -------------- |SVG Diagram 1| -------------- -------------- -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER growth_fraction \ time 0.0 0 10 0.5 0.5 0.8 0.5 0 10 0.5 0.5 0.8 1.0 0 10 0.5 0.5 0.8 1.5 0 10 0.5 0.5 0.8 2.0 0 10 0.5 0.5 0.8 2.5 0 10 0.5 0.5 0.8 3.0 0 10 0.5 0.5 0.8 3.5 0 10 0.5 0.5 0.8 4.0 0 10 0.5 0.5 0.8 4.5 0 10 0.5 0.5 0.8 5.0 0 10 0.5 0.5 0.8 5.5 0 10 0.5 0.5 0.8 6.0 0 10 0.5 0.5 0.8 6.5 0 10 0.5 0.5 0.8 7.0 0 10 0.5 0.5 0.8 7.5 0 10 0.5 0.5 0.8 8.0 0 10 0.5 0.5 0.8 8.5 0 10 0.5 0.5 0.8 9.0 0 10 0.5 0.5 0.8 9.5 0 10 0.5 0.5 0.8 10.0 0 10 0.5 0.5 0.8 rate level time 0.0 80.000000 100.000000 0.5 112.000000 140.000000 1.0 156.800000 196.000000 1.5 219.520000 274.400000 2.0 307.328000 384.160000 2.5 430.259200 537.824000 3.0 602.362880 752.953600 3.5 843.308032 1054.135040 4.0 1180.631245 1475.789056 4.5 1652.883743 2066.104678 5.0 2314.037240 2892.546550 5.5 3239.652136 4049.565170 6.0 4535.512990 5669.391238 6.5 6349.718186 7937.147733 7.0 8889.605460 11112.006826 7.5 12445.447645 15556.809556 8.0 17423.626702 21779.533378 8.5 24393.077383 30491.346729 9.0 34150.308337 42687.885421 9.5 47810.431672 59763.039589 10.0 66934.604340 83668.255425 -------------- -------------- :: -------------- |PNG Image 2| -------------- Negative Feedback ----------------- -------------- :: # negative feedback from selib import * init_sd_model(start=0, stop=10, dt=.1) add_stock("level", 50, inflows=["rate"]) add_auxiliary("delay", .5) add_auxiliary("goal", 100) add_flow("rate", "(goal - level) / delay") draw_model() run_model() save_graph(['level', 'goal', 'rate'], filename="negative_feedback.svg") -------------- :: -------------- |SVG Diagram 3| -------------- -------------- -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER delay goal rate \ time 0.0 0 10 0.1 0.1 0.5 100 1.000000e+02 0.1 0 10 0.1 0.1 0.5 100 8.000000e+01 0.2 0 10 0.1 0.1 0.5 100 6.400000e+01 0.3 0 10 0.1 0.1 0.5 100 5.120000e+01 0.4 0 10 0.1 0.1 0.5 100 4.096000e+01 ... ... ... ... ... ... ... ... 9.6 0 10 0.1 0.1 0.5 100 4.973231e-08 9.7 0 10 0.1 0.1 0.5 100 3.978585e-08 9.8 0 10 0.1 0.1 0.5 100 3.182868e-08 9.9 0 10 0.1 0.1 0.5 100 2.546295e-08 10.0 0 10 0.1 0.1 0.5 100 2.037035e-08 level time 0.0 50.00 0.1 60.00 0.2 68.00 0.3 74.40 0.4 79.52 ... ... 9.6 100.00 9.7 100.00 9.8 100.00 9.9 100.00 10.0 100.00 [101 rows x 8 columns] -------------- -------------- :: -------------- |PNG Image 4| -------------- Goal Seeking Behavior --------------------- -------------- :: from selib import * # goal seeking behavior with negative feedback init_sd_model(start=0, stop=10, dt=.1) add_stock("level", 50, inflows=["rate"]) add_auxiliary("time_constant", .5) add_auxiliary("goal", 200) add_flow("rate", "(goal - level) / time_constant") draw_model() run_model() plot_graph(['goal', 'level']) -------------- :: -------------- |SVG Diagram 5| -------------- -------------- -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER time_constant goal \ time 0.0 0 10 0.1 0.1 0.5 200 0.1 0 10 0.1 0.1 0.5 200 0.2 0 10 0.1 0.1 0.5 200 0.3 0 10 0.1 0.1 0.5 200 0.4 0 10 0.1 0.1 0.5 200 ... ... ... ... ... ... ... 9.6 0 10 0.1 0.1 0.5 200 9.7 0 10 0.1 0.1 0.5 200 9.8 0 10 0.1 0.1 0.5 200 9.9 0 10 0.1 0.1 0.5 200 10.0 0 10 0.1 0.1 0.5 200 rate level time 0.0 3.000000e+02 50.00 0.1 2.400000e+02 80.00 0.2 1.920000e+02 104.00 0.3 1.536000e+02 123.20 0.4 1.228800e+02 138.56 ... ... ... 9.6 1.491970e-07 200.00 9.7 1.193576e-07 200.00 9.8 9.548609e-08 200.00 9.9 7.638886e-08 200.00 10.0 6.111111e-08 200.00 [101 rows x 8 columns] -------------- -------------- :: -------------- |PNG Image 6| -------------- Rayleigh Curve Staffing ----------------------- -------------- :: # Rayleigh Curve Staffing from selib import * init_sd_model(start=0, stop=6, dt=.2) add_stock("cumulative_effort", 0, inflows=["effort rate"]) add_flow("effort rate", "learning_function * (estimated_total_effort - cumulative_effort)") add_auxiliary("learning_function", "manpower_buildup_parameter * time") add_auxiliary("manpower_buildup_parameter", .5) add_auxiliary("estimated_total_effort", 30) draw_model() run_model() save_graph("effort rate", filename="rayleigh_curve_effort_rate.png") save_graph(["effort rate", "cumulative_effort"], filename="rayleigh_curve_components.png") -------------- :: -------------- |SVG Diagram 7| -------------- -------------- -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER learning_function \ time 0.0 0 6 0.2 0.2 0.0 0.2 0 6 0.2 0.2 0.1 0.4 0 6 0.2 0.2 0.2 0.6 0 6 0.2 0.2 0.3 0.8 0 6 0.2 0.2 0.4 1.0 0 6 0.2 0.2 0.5 1.2 0 6 0.2 0.2 0.6 1.4 0 6 0.2 0.2 0.7 1.6 0 6 0.2 0.2 0.8 1.8 0 6 0.2 0.2 0.9 2.0 0 6 0.2 0.2 1.0 2.2 0 6 0.2 0.2 1.1 2.4 0 6 0.2 0.2 1.2 2.6 0 6 0.2 0.2 1.3 2.8 0 6 0.2 0.2 1.4 3.0 0 6 0.2 0.2 1.5 3.2 0 6 0.2 0.2 1.6 3.4 0 6 0.2 0.2 1.7 3.6 0 6 0.2 0.2 1.8 3.8 0 6 0.2 0.2 1.9 4.0 0 6 0.2 0.2 2.0 4.2 0 6 0.2 0.2 2.1 4.4 0 6 0.2 0.2 2.2 4.6 0 6 0.2 0.2 2.3 4.8 0 6 0.2 0.2 2.4 5.0 0 6 0.2 0.2 2.5 5.2 0 6 0.2 0.2 2.6 5.4 0 6 0.2 0.2 2.7 5.6 0 6 0.2 0.2 2.8 5.8 0 6 0.2 0.2 2.9 6.0 0 6 0.2 0.2 3.0 manpower_buildup_parameter estimated_total_effort effort rate \ time 0.0 0.5 30 0.000000 0.2 0.5 30 3.000000 0.4 0.5 30 5.880000 0.6 0.5 30 8.467200 0.8 0.5 30 10.612224 1.0 0.5 30 12.204058 1.2 0.5 30 13.180382 1.4 0.5 30 13.531859 1.6 0.5 30 13.299884 1.8 0.5 30 12.568391 2.0 0.5 30 11.451200 2.2 0.5 30 10.077056 2.4 0.5 30 8.574659 2.6 0.5 30 7.059802 2.8 0.5 30 5.626120 3.0 0.5 30 4.340149 3.2 0.5 30 3.240645 3.4 0.5 30 2.341366 3.6 0.5 30 1.636202 3.8 0.5 30 1.105345 4.0 0.5 30 0.721383 4.2 0.5 30 0.454471 4.4 0.5 30 0.276145 4.6 0.5 30 0.161671 4.8 0.5 30 0.091098 5.0 0.5 30 0.049345 5.2 0.5 30 0.025659 5.4 0.5 30 0.012790 5.6 0.5 30 0.006101 5.8 0.5 30 0.002780 6.0 0.5 30 0.001208 cumulative_effort time 0.0 0.000000 0.2 0.000000 0.4 0.600000 0.6 1.776000 0.8 3.469440 1.0 5.591885 1.2 8.032696 1.4 10.668773 1.6 13.375145 1.8 16.035121 2.0 18.548800 2.2 20.839040 2.4 22.854451 2.6 24.569383 2.8 25.981343 3.0 27.106567 3.2 27.974597 3.4 28.622726 3.6 29.090999 3.8 29.418239 4.0 29.639308 4.2 29.783585 4.4 29.874479 4.6 29.929708 4.8 29.962043 5.0 29.980262 5.2 29.990131 5.4 29.995263 5.6 29.997821 5.8 29.999041 6.0 29.999597 -------------- -------------- :: -------------- |PNG Image 8| -------------- :: -------------- |PNG Image 9| -------------- Random Functions and Measuring Stop Time ---------------------------------------- This example illustrates measuring elapsed simulation time using the ``stop_when`` initialization parameter and ``get_stop_time()`` function. It captures the project end when staffing effort rate goes to zero. -------------- :: from selib import * init_sd_model(start=0, stop=6, dt=.2, stop_when="effort_rate <= 0") add_stock("cumulative_effort", 0, inflows=["effort rate"]) add_flow("effort_rate", "learning_function * (estimated_total_effort - cumulative_effort)") add_auxiliary("learning_function", "manpower_buildup_parameter * time") add_auxiliary("manpower_buildup_parameter", 'random.uniform(.2, .7)') add_auxiliary("estimated_total_effort", 'random.uniform(25, 35)') run_model() print(f"Project end time is {get_stop_time()}") -------------- :: Simulation ends at time = 3.0 -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER learning_function \ time 0.0 0 6 0.2 0.2 0.000000 0.2 0 6 0.2 0.2 0.122395 0.4 0 6 0.2 0.2 0.235581 0.6 0 6 0.2 0.2 0.206761 0.8 0 6 0.2 0.2 0.304143 1.0 0 6 0.2 0.2 0.207023 1.2 0 6 0.2 0.2 0.459536 1.4 0 6 0.2 0.2 0.320776 1.6 0 6 0.2 0.2 0.781662 1.8 0 6 0.2 0.2 0.851055 2.0 0 6 0.2 0.2 1.260378 2.2 0 6 0.2 0.2 1.045175 2.4 0 6 0.2 0.2 1.072665 2.6 0 6 0.2 0.2 1.511612 2.8 0 6 0.2 0.2 1.299925 3.0 0 6 0.2 0.2 1.825976 manpower_buildup_parameter estimated_total_effort effort_rate \ time 0.0 0.682189 34.323998 0.000000 0.2 0.611974 27.620483 3.380603 0.4 0.588953 30.330142 6.985926 0.6 0.344601 29.166838 5.601875 0.8 0.380179 33.010074 9.068448 1.0 0.207023 31.018010 5.384799 1.2 0.382947 32.624585 12.196203 1.4 0.229126 28.600847 6.440316 1.6 0.488538 30.481037 16.156478 1.8 0.472808 29.680247 14.159276 2.0 0.630189 31.585909 19.801960 2.2 0.475079 25.422697 5.839935 2.4 0.446944 31.348129 11.096682 2.6 0.581389 29.838478 10.000791 2.8 0.464259 30.166600 6.426755 3.0 0.608659 25.307849 -2.191463 cumulative_effort time 0.0 0.000000 0.2 0.000000 0.4 0.676121 0.6 2.073306 0.8 3.193681 1.0 5.007370 1.2 6.084330 1.4 8.523571 1.6 9.811634 1.8 13.042930 2.0 15.874785 2.2 19.835177 2.4 21.003164 2.6 23.222500 2.8 25.222658 3.0 26.508009 -------------- -------------- :: Project end time is 3.0 -------------- -------------- Predator Prey Model ------------------- -------------- :: # Predator Prey Model with Carrying Capacity import selib as se # Initialize model init_sd_model(start=0, stop=300, dt=0.25) # Auxiliaries add_auxiliary_lookup("Carrying_Capacity_Effect", "Hares", ["0, 500, 1000, 1500, 2000"], ["1, .8, .5, .2, .05"]) add_auxiliary("Hare_Growth_Rate", "0.162 * Carrying_Capacity_Effect") add_auxiliary("Hare_Death_Rate", ".0008 * Lynx") add_auxiliary("Lynx_Death_Rate", "0.12") add_auxiliary("Lynx_Birth_Rate", "0.001 * Hares") # Stocks add_stock("Hares", "150", inflows=["Hare_Births"], outflows=["Hare_Deaths"]) add_stock("Lynx", "100", inflows=["Lynx_Births"], outflows=["Lynx_Deaths"]) # Flows add_flow("Hare_Births", "Hare_Growth_Rate * Hares") add_flow("Hare_Deaths", "Hare_Death_Rate * Hares") add_flow("Lynx_Births", "Lynx_Birth_Rate * Lynx") add_flow("Lynx_Deaths", "Lynx_Death_Rate * Lynx") d = draw_model_diagram(filename="sd") results = run_model() save_graph(["Hares", "Lynx"]) -------------- :: -------------- |SVG Diagram 10| -------------- -------------- -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER \ time 0.00 0 300 0.25 0.25 0.25 0 300 0.25 0.25 0.50 0 300 0.25 0.25 0.75 0 300 0.25 0.25 1.00 0 300 0.25 0.25 ... ... ... ... ... 299.00 0 300 0.25 0.25 299.25 0 300 0.25 0.25 299.50 0 300 0.25 0.25 299.75 0 300 0.25 0.25 300.00 0 300 0.25 0.25 Carrying_Capacity_Effect Hare_Growth_Rate Hare_Death_Rate \ time 0.00 0.940000 0.152280 0.080000 0.25 0.938916 0.152104 0.080600 0.50 0.937824 0.151927 0.081259 0.75 0.936725 0.151750 0.081979 1.00 0.935622 0.151571 0.082762 ... ... ... ... 299.00 0.942403 0.152669 0.212617 299.25 0.943266 0.152809 0.213893 299.50 0.944132 0.152949 0.215060 299.75 0.945000 0.153090 0.216118 300.00 0.945866 0.153230 0.217063 Lynx_Death_Rate Lynx_Birth_Rate Hare_Births Hare_Deaths \ time 0.00 0.12 0.150000 22.842000 12.000000 0.25 0.12 0.152710 23.227933 12.308466 0.50 0.12 0.155440 23.615661 12.630947 0.75 0.12 0.158187 24.004731 12.967987 1.00 0.12 0.160946 24.394660 13.320143 ... ... ... ... ... 299.00 0.12 0.143993 21.983347 30.615492 299.25 0.12 0.141835 21.673716 30.337545 299.50 0.12 0.139669 21.362341 30.037341 299.75 0.12 0.137501 21.049955 29.716338 300.00 0.12 0.135334 20.737271 29.376063 Lynx_Births Lynx_Deaths Hares Lynx time 0.00 15.000000 12.000000 150.000000 100.000000 0.25 15.385583 12.090000 152.710500 100.750000 0.50 15.788684 12.188867 155.440367 101.573896 0.75 16.209984 12.296862 158.186545 102.473850 1.00 16.650179 12.414256 160.945731 103.452130 ... ... ... ... ... 299.00 38.269365 31.892621 143.993301 265.771845 299.25 37.921932 32.083924 141.835264 267.366031 299.50 37.546676 32.259064 139.669307 268.825533 299.75 37.145423 32.417692 137.500557 270.147436 300.00 36.720078 32.559524 135.333961 271.329369 [1201 rows x 15 columns] -------------- -------------- :: -------------- |PNG Image 11| -------------- :: # patch to increase padding for model diagrams import graphviz # Store original methods _original_digraph_init = graphviz.Digraph.__init__ def patched_digraph_init(self, *args, **kwargs): _original_digraph_init(self, *args, **kwargs) # Apply defaults self.attr('graph', pad='1') # Apply patches graphviz.Digraph.__init__ = patched_digraph_init -------------- Brooks's Law Model ------------------ -------------- :: # Brooks's Law Model from selib import * init_sd_model(start=0, stop=10, dt=.1) add_stock("requirements", 500, inflows=[], outflows=["software development rate"]) add_stock("developed_software", 0, inflows=["software development rate"]) add_flow("software development rate", "nominal_productivity*(1.-communication_overhead/100.)*(.8*new_project_personnel+1.2*(experienced_personnel-experienced_personnel_needed_for_training))") add_stock("experienced_personnel", 10.0, inflows=["assimilation rate"]) add_stock("new_project_personnel", 10.0, inflows=["people_added"], outflows=["assimilation rate"]) add_flow("assimilation rate", "new_project_personnel/20") add_flow("people_added", "pulse(10,5)") add_auxiliary("nominal_productivity", .1) add_auxiliary("training_overhead_FTE_experienced", 25.) add_auxiliary("experienced_personnel_needed_for_training", "new_project_personnel*training_overhead_FTE_experienced/100.") add_auxiliary_lookup("communication_overhead", "experienced_personnel + new_project_personnel", ["0, 5, 10, 15, 20, 25, 30"], ["0, 1.5, 6, 13.5, 24, 37.5, 54"]) draw_sd_model() output = run_model() plot_graph("developed_software") plot_graph("software development rate") plot_graph(["software development rate", "communication_overhead"]) plot_graph(["new_project_personnel", "people_added", "experienced_personnel_needed_for_training"]) -------------- :: -------------- |SVG Diagram 12| -------------- -------------- -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER nominal_productivity \ time 0.0 0 10 0.1 0.1 0.1 0.1 0 10 0.1 0.1 0.1 0.2 0 10 0.1 0.1 0.1 0.3 0 10 0.1 0.1 0.1 0.4 0 10 0.1 0.1 0.1 ... ... ... ... ... ... 9.6 0 10 0.1 0.1 0.1 9.7 0 10 0.1 0.1 0.1 9.8 0 10 0.1 0.1 0.1 9.9 0 10 0.1 0.1 0.1 10.0 0 10 0.1 0.1 0.1 training_overhead_FTE_experienced \ time 0.0 25.0 0.1 25.0 0.2 25.0 0.3 25.0 0.4 25.0 ... ... 9.6 25.0 9.7 25.0 9.8 25.0 9.9 25.0 10.0 25.0 experienced_personnel_needed_for_training communication_overhead \ time 0.0 2.500000 24.0 0.1 2.487500 24.0 0.2 2.475062 24.0 0.3 2.462687 24.0 0.4 2.450374 24.0 ... ... ... 9.6 3.540261 54.0 9.7 3.522560 54.0 9.8 3.504947 54.0 9.9 3.487422 54.0 10.0 3.469985 54.0 software development rate assimilation rate people_added \ time 0.0 1.292000 0.500000 0.0 0.1 1.294660 0.497500 0.0 0.2 1.297307 0.495012 0.0 0.3 1.299940 0.492537 0.0 0.4 1.302560 0.490075 0.0 ... ... ... ... 9.6 1.200014 0.708052 0.0 9.7 1.202294 0.704512 0.0 9.8 1.204563 0.700989 0.0 9.9 1.206820 0.697484 0.0 10.0 1.209066 0.693997 0.0 requirements developed_software experienced_personnel \ time 0.0 500.000000 0.000000 10.000000 0.1 499.870800 0.129200 10.050000 0.2 499.741334 0.258666 10.099750 0.3 499.611603 0.388397 10.149251 0.4 499.481609 0.518391 10.198505 ... ... ... ... 9.6 487.953319 12.046681 15.838955 9.7 487.833317 12.166683 15.909760 9.8 487.713088 12.286912 15.980211 9.9 487.592632 12.407368 16.050310 10.0 487.471950 12.528050 16.120059 new_project_personnel time 0.0 10.000000 0.1 9.950000 0.2 9.900250 0.3 9.850749 0.4 9.801495 ... ... 9.6 14.161045 9.7 14.090240 9.8 14.019789 9.9 13.949690 10.0 13.879941 [101 rows x 15 columns] -------------- -------------- :: -------------- |PNG Image 13| -------------- :: -------------- |PNG Image 14| -------------- :: -------------- |PNG Image 15| -------------- :: -------------- |PNG Image 16| -------------- :: -------------- Object Oriented Simulation Using Classes ---------------------------------------- -------------- :: # @title Capability Development Model with Stop Condition from selib import * # Initialize SystemDynamicsModel model instance model = SystemDynamicsModel(start=0, stop=25, dt=0.1, stop_when="developed_capabilities >= 100") model.add_stock(name="capabilities_to_do", initial=100, outflows=["development_rate"]) model.add_stock(name="developed_capabilities", initial=0, inflows=["development_rate"]) model.add_flow(name="development_rate", equation="IF capabilities_to_do > 0 THEN 10 ELSE 0") # Draw the model diagram model.draw_model() # Run the model simulation results = model.run_model() # Plot the 'developed_capabilities' model.plot_graph('developed_capabilities') # Print the stop time stop_time = model.get_stop_time() print(f'stop time = {stop_time}') -------------- :: -------------- |SVG Diagram 17| -------------- -------------- -------------- :: Simulation ends at time = 10.0 -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER development_rate \ time 0.0 0 25 0.1 0.1 10 0.1 0 25 0.1 0.1 10 0.2 0 25 0.1 0.1 10 0.3 0 25 0.1 0.1 10 0.4 0 25 0.1 0.1 10 ... ... ... ... ... ... 9.6 0 25 0.1 0.1 10 9.7 0 25 0.1 0.1 10 9.8 0 25 0.1 0.1 10 9.9 0 25 0.1 0.1 10 10.0 0 25 0.1 0.1 0 capabilities_to_do developed_capabilities time 0.0 100.0 0.0 0.1 99.0 1.0 0.2 98.0 2.0 0.3 97.0 3.0 0.4 96.0 4.0 ... ... ... 9.6 4.0 96.0 9.7 3.0 97.0 9.8 2.0 98.0 9.9 1.0 99.0 10.0 0.0 100.0 [101 rows x 7 columns] -------------- -------------- :: -------------- |PNG Image 18| -------------- :: stop time = 10.0 -------------- Monte Carlo Simulation and Output Analysis ------------------------------------------ -------------- :: # Monte Carlo simulation for Rayleigh Curve Staffing and end time output analysis from selib import * import numpy as np number_runs = 100 # vary manpower_buildup_parameter and estimated_total_effort in model definition end_times = [] # initialize output data list for run in range(number_runs): init_sd_model(start=0, stop=6, dt=.2, stop_when="effort_rate <=0") add_stock("cumulative_effort", 0, inflows=["effort rate"]) add_flow( "effort_rate", "learning_function * (estimated_total_effort - cumulative_effort)") add_auxiliary("learning_function", "manpower_buildup_parameter * time") add_auxiliary("manpower_buildup_parameter", 'random.uniform(.2, .7)') add_auxiliary("estimated_total_effort", f'random.uniform(25, 35)') run_model(verbose=False) end_times.append(get_stop_time()) print(f'End Time Mean for {number_runs} runs = ', np.mean(end_times)) # PDF histogram with Matplotlib fig, axis = plt.subplots() axis.hist(end_times) axis.set(title=f'End Times PDF', xlabel='End Time', ylabel='Frequency') plt.savefig(f"end_times_PDF") # CDF with NumPy and Matplotlib fig, axis = plt.subplots() # Use the histogram function to bin the data histogram_bin_counts, bin_edges = np.histogram (end_times, density=True) cdf = np.cumsum (histogram_bin_counts) axis.plot (bin_edges[1:], cdf/cdf[-1]) axis.set(title=f'End Times CDF', xlabel='End Time', ylabel='Cumulative Probability') plt.savefig(f"end_times_CDF") -------------- :: End Time Mean for 100 runs = 3.4560000000000004 -------------- :: -------------- |PNG Image 19| -------------- :: -------------- |PNG Image 20| -------------- Special Functions ================= -------------- :: from selib import * init_sd_model(start=0, stop=6, dt=.2, stop_when="effort_rate <= 0") add_stock("cumulative_effort", 0, inflows=["effort rate"]) add_flow("effort_rate", "learning_function * (estimated_total_effort - cumulative_effort)") add_auxiliary("learning_function", "manpower_buildup_parameter * time") add_auxiliary("manpower_buildup_parameter", 'random.uniform(.2, .7)') add_auxiliary("estimated_total_effort", 'random.uniform(25, 35)') run_model() print(f"Project end time is {get_stop_time()}") -------------- Stop When Condition ------------------- -------------- :: # demonstrate stop_when condition to end simulation and the get_stop function # collect time when the stock reaches zero from selib import * init_sd_model(start=0, stop=10, dt=.5, stop_when="stock <= 0") add_stock("stock", 50, outflows=["outflow_rate"]) add_flow("outflow_rate", "20") run_model() print('stop time = ', get_stop_time()) -------------- :: Simulation ends at time = 2.5 -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER outflow_rate stock time 0.0 0 10 0.5 0.5 20 50.0 0.5 0 10 0.5 0.5 20 40.0 1.0 0 10 0.5 0.5 20 30.0 1.5 0 10 0.5 0.5 20 20.0 2.0 0 10 0.5 0.5 20 10.0 2.5 0 10 0.5 0.5 20 0.0 -------------- -------------- :: stop time = 2.5 -------------- If Then Else ------------ -------------- :: # demonstrate if then else function from selib import * init_sd_model(start=0, stop=5, dt=1) # set conditional variable # if time > 2 then 1 else 0 add_auxiliary("conditional", "if_then_else(time > 2, 1, 0)") add_auxiliary("conditional2", "if_then_else(time < 4, .5, .7)") run_model() -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER conditional conditional2 time 0 0 5 1 1 0 0.5 1 0 5 1 1 0 0.5 2 0 5 1 1 0 0.5 3 0 5 1 1 1 0.5 4 0 5 1 1 1 0.7 5 0 5 1 1 1 0.7 -------------- -------------- :: INITIAL TIME FINAL TIME TIME STEP SAVEPER conditional conditional2 time 0 0 5 1 1 0 0.5 1 0 5 1 1 0 0.5 2 0 5 1 1 0 0.5 3 0 5 1 1 1 0.5 4 0 5 1 1 1 0.7 5 0 5 1 1 1 0.7 -------------- .. |negative_feedback_diagram| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/negative_feedback_diagram.svg .. |negative_feedback_run_output| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/negative_feedback_run_output.png .. |negative_feedback_level_graph| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/negative_feedback_level_graph.svg .. |exponential_growth_diagram| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/exponential_growth_model_diagram.svg .. |exponential_growth_run_output| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/exponential_growth_run_output.png .. |exponential_growth_level_graph| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/exponential_growth_graph_output.svg .. |balancing_loop_model_diagram| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/balancing_loop_model_diagram.svg .. |balancing_loop_model_run_output| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/balancing_loop_model_run_output.png .. |balancing_loop_model_output| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/balancing_loop_model_output.svg .. |goal_gap_structure_delay_model_diagram| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/goal_gap_structure_model_diagram.svg .. |goal_gap_structure_model_run_output| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures//goal_gap_structure_model_run_output.png .. |goal_gap_structure_model_output| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures//goal_gap_structure_model_output.svg .. |first_order_delay_model_diagram| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/first_order_delay_model_diagram.svg .. |first_order_delay_model_run_output| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/first_order_delay_model_run_output.png .. |first_order_delay_model_output| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/first_order_delay_model_output.svg .. |second_order_delay_model_diagram| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/second_order_delay_model_diagram.svg .. |second_order_delay_model_run_output| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/second_order_delay_model_run_output.png .. |second_order_delay_model_output| image:: https://github.com/se-lib/se-lib/raw/main/docs/figures/second_order_delay_model_output.svg .. |SVG Diagram 1| image:: _static/diagram_001.svg :class: sphinx-svg .. |PNG Image 2| image:: _static/image_002.png .. |SVG Diagram 3| image:: _static/diagram_003.svg :class: sphinx-svg .. |PNG Image 4| image:: _static/image_004.png .. |SVG Diagram 5| image:: _static/diagram_005.svg :class: sphinx-svg .. |PNG Image 6| image:: _static/image_006.png .. |SVG Diagram 7| image:: _static/diagram_007.svg :class: sphinx-svg .. |PNG Image 8| image:: _static/image_008.png .. |PNG Image 9| image:: _static/image_009.png .. |SVG Diagram 10| image:: _static/diagram_010.svg :class: sphinx-svg .. |PNG Image 11| image:: _static/image_011.png .. |SVG Diagram 12| image:: _static/diagram_012.svg :class: sphinx-svg .. |PNG Image 13| image:: _static/image_013.png .. |PNG Image 14| image:: _static/image_014.png .. |PNG Image 15| image:: _static/image_015.png .. |PNG Image 16| image:: _static/image_016.png .. |SVG Diagram 17| image:: _static/diagram_017.svg :class: sphinx-svg .. |PNG Image 18| image:: _static/image_018.png .. |PNG Image 19| image:: _static/image_019.png .. |PNG Image 20| image:: _static/image_020.png