Discrete-Time Dynamical Systems

Overview and Objectives

This lecture introduces simulation modeling via system-dynamics. The presentation is exercise based. A sequence of exercises guide the development of a simple discrete-time simulation model of exponential aggregate population growth. This illustrative application of simulation methods contains no explicit agents. Nevertheless, it serves as a springboard for the development of modeling, programming, and data visualization skills that are required by the agent-based models of future lectures.

The lecture exercises lead to a complete computational implementation of a simple population growth model, along with a controlling GUI and visualizations. Before attempting the exercises below, skim this lecture from start to finish to understand the overall structure. Be sure to garner a good comprehension of the conceptual model. Then work through the lecture step by step, implementing the computational model as you go. Keep your computational implementation runnable by using stubs whenever this is helpful. Verify the evolving implementation at each step by running the provided tests or by creating your own tests.

Goals and Outcomes

This lecture introduces model formulation, model implementation, and system simulation. It explores the attendant simulation issues in a tentative way, which later lectures elaborate in detail and depth. Central to this lecture is the design and implementation of a simple system-dynamics model of population growth.

Prerequisites

Each section of this lecture includes exercises are structured to foster deeper understanding through active exploration of the core material. These exercises introduce and refine programming skills that future lectures require. The exercises below presume the mastery of a small amount of preliminary material, indicated by the following prerequisites.

Modeling and Simulation

  • system: components + relations

  • model types: static and dynamic

  • modeling strategies: abstraction and simplification

  • model development: conceptual model vs computational implementation

  • why simulate?

Systems and Models

A system is an object or process with distinguishable components, along with the structure of their interrelations. This course uses the term model to denote any representation of a system—either an actual system, or an imaginable system. This is an extremely broad notion of what constitutes a model. Not only does it encompass normal human understandings of the quotidian world, it also applies to fictional worlds that exist only in a modeler’s imagination.

Despite this broad understanding of the fundamental nature of a model, the models considered in this course are rather narrow in scope. They are intended to elucidate some aspect of the real world, however indirectly. Even more narrowly, this course focuses on models of systems that have particular interest for social scientists. Finally, the models in this course have a pedagogical purpose: they foster key skills use in agent-based modeling and simulation.

Model Development

Model development encompasses all efforts to conceptualize, implement, or improve a model. Model development is typically driven by specific interests in a particular system, known as the target system. This lecture develops a system-dynamics model of population growth; the target system is a population undergoing exponential growth.

Some models are attempts to approximate all the salient properties of a real world system. Other models are highly simplified and abstract attempts to explore the implications of conditions that have little likelihood of occurring in the real world. In either case, model development requires simplification and abstraction: the modeler ignores real-world features that either are too costly to consider or are judged to be irrelevant to the model-development goals.

Modeling choices reflect the goals of the modeler as well as perceived constraints on model development. For the most part, the simulation models in this course are highly simplified. Their primary purpose is instructional, and the level of abstraction is very high. For example, the population growth model of this lecture treats the entire world population as a single entity, and the growth rate of this population is constant. (Later lectures address these particular simplifications.)

Model Development Process

Model development typically begins with a crude conceptual model. This is an abstract and perhaps somewhat vague representation of a system. Model development elaborates this initial conceptual model so that it becomes more precisely specified and more useful.

Sometimes model development is purely verbal or graphical. However, scientific model development often involves implementing the conceptual model as a mathematical model, computational model, or physical model. The focus of this course is computational social science, and it emphasizes turning conceptual models into computational models. For example, as a simple introduction, this lecture shows how to convert a basic conceptual understanding of exponential population growth into a computational model that produces population projections.

Relatively simple computational models can often solve problems that are mathematically challenging or even intractable [Humphreys-2002-PhilSci]. For example, as shown in subsequent lectures, computational models facilitate the characterization of complex dynamics and can provide ready answers to vexing distributional questions.

Figure f:modelingProcess provides a stylized illustration of key steps in model development and analysis. [1] Scientific model development typically involves hypothesis generation, experimentation, and data analysis. This course correspondingly shows how to run simulation experiments, to analyze the resulting data, and to report the experimental results.

Activity diagram of the process of modeling.

Stylized Simulation Modeling Process

Static and Dynamical Models

A model may be static or dynamical. A static model characterizes the relationships between the parts of an unchanging system. Social scientists often use static models to describe equilibrium states of a system. For example, a first course in economics typically illustrates price determination in a single market as a static equilibrium between supply and demand. In contrast, a dynamical model characterizes the evolution of a system over time. This course focuses on dynamical models.

Social scientists often use dynamical models to describe the outcomes of interactions between components of a social system. Examples include the evolution of voting patterns, the determinants of technological change, the process of population growth, or the spread of a contagion. In both static and dynamical models, the notion of time is model specific and may be only loosely related to real-world temporality.

Dynamical models can describe the behavior of a system that remains out of equilibrium or moves towards equilibrium. Although the evolution of the system over time may be deterministic, as in the current lecture, dynamical simulation models often include a stochastic component. That is, they build in some randomness, which often represents aspects of the target system about which we are ignorant or uncertain. Developing a detailed description of the behavior of a real-world stochastic dynamical system is very challenging. However, the models in this course are simple enough to be built quickly and understood fully.

Conceptual Models and Their Implementations

Model development often begins with a somewhat vague and incomplete conceptual model. This conceptual model is typically descriptive. It might be verbal, diagrammatic, or even mathematical, but it is not implemented in a programming language. This course demonstrates how to transform conceptual models into computational models. A central goal of each lecture is to implement a conceptual model as a computational model, in order to simulate the behavior of a target system.

A computational model is an algorithmic implementation of a conceptual model that is executable on some kind of computer. Modern simulations typically run on electronic digital computers. There is a natural give and take between conceptual models and computational models. Concepts drive computational modeling, and the process of computational modeling can lead to improvements in the conceptual model. In Figure f:modelingProcess, refinement of the conceptual model and of the implementation take place in parallel, making use of the results of previous refinements.

Scientists often build models in order to explore how a target system behaves. A computational implementation produces a computational model of the system. This is a simulation model: it should behave like the target system. Running the model on a computer produces a simulation.

A dynamical simulation explores the evolution of a system over time. One goal of simulation is to generate insights into the behavior of the target system. Some simulations are purely playful explorations. More often, the purpose of a simulation experiment is to gain insight into the behavior of a real world dynamical system.

Simulation has proved fruitful in elucidating many real-world systems. Simulation modeling has therefore spread widely, including applications in science, engineering, and business. An example from physical science would be simulations of the interplay of molecular forces during protein folding. An important example from medicine arises in the drug discovery process, which involves simulating the interaction between chemical compounds and biological targets. An example from engineering would be simulations of airflow over different wing designs to detect effect on lift and control. An example from business would be simulations of how supply chains affect the reliability of product distribution.

Why Use Simulation?

  • practicality

  • ethicality

The applied physical sciences often use simulation to produce speedier and cheaper investigations of target systems. Practically, simulations can be the most cost effective means to rigorously investigate the behavior of a physical system.

This course emphasizes social-science applications of modeling and simulation. Social scientists use simulations for additional reasons, both practical and ethical. Simulations are often be the only practicable means of investigation. For example, it is infeasible to design a large-scale evacuation plan by deliberately experimenting with large-scale evacuations in the real world. We may also be interested in responses of systems to variables that we cannot ethically manipulate, including environmental variables that affect human safety.

Key competitors to simulation modeling are narrative modeling and mathematical modeling. Narrative modeling is the verbal presentation and analysis of a conceptual model. Mathematical modeling is the mathematical presentation and analysis of a conceptual model. Both of these can share with simulation modeling the ability to conduct experiments that would be infeasible or unethical to attempt in the real world. Each has its strengths and weaknesses; there is no best approach to modeling. The choice must be pragmatic, considering the appropriateness of each approach to the problem at hand. As a key consideration, simulation modeling often enables the incorporation of salient details that might bedevil mathematical modelers while avoiding the lack of rigor that may bedevil narrative modelers.

System Dynamics

  • stock variables and flow variables

  • physical implementations vs software implementations

  • discrete-time dynamical systems

  • system state

  • evolution rule

  • function iteration.

A dynamical system evolves over time. A model of a dynamical system characterizes this evolution. Abstractly, a dynamical model must characterize the possible states of the system and provide an evolution rule for the system. The evolution rule describes how the system moves from one system state to another. Given an initial system state, the evolution rule determines how the system state evolves over time. It determines the trajectory of this system.

Modeling Dynamical Systems

System dynamics and agent-based modeling are two prominent yet divergent approaches to the computational modeling and simulation of evolving systems. Both approaches have proved their worth for social scientists. This course focuses on the agent-based approach, but this lecture and the next provide a brief introduction to the system-dynamics approach. This introduction emphasizes a basic structure of simulation modeling that both approaches share.

Stocks and Flows

  • stock variable: measurement at any point in time

  • flow variable: measurement refers to a period of time

The system-dynamics approach to social-science modeling and simulation gained popularity in the 1950s. In system-dynamics models, aggregate outcomes result from the dynamic interplay of aggregate quantities. The model dynamics result from the interplay of aggregative stock variables and flow variables.

A flow variable represents a rate of change. Flow variables must be defined with respect to a period of time, with reference to a specific time unit. In contrast, a stock variable is defined at any point of time, without reference to a time unit.

For example, in a model of human disease transmission, the stock variables might include the number of susceptible individuals, the number of infected and infectious individuals, and the number recovered and immune individuals. The magnitudes of these variables can be measured at any point in time without reference to the passage of time. The rates of change of these stocks are flow variables, which must reference a (real or virtual) period of time. As another example, the total population is a stock variable in a simple model of population growth, while the annual population growth is a flow variable.

A system-dynamics model characterizes how the overall behavior of a system is determined by the feedback between the stocks and flows in the system. In an infectious disease model, when the number of infectious individuals and the number of susceptible individuals are both large, then we expect a high rate of flow from the susceptible group to the infectious group. In a population growth model, there may be feedback from the level of the population to the size of its change. Dynamic interactions between the stocks and flows determine the evolution of the system.

Mathematical versus Computational Implementations

A system-dynamics model is typically implemented as a collection of computational functions that characterize these stock-flow interactions. However, other implementations are possible. For example, very simple models are amenable to exact mathematical solution. The population model of this lecture is in this class. Such models are pedagogically useful, since the exact correspondence between the mathematical model and the simulation model permits an easy comparison between the simulation results and the mathematical solution. This provides helpful guidance to simulation novices, who can readily check the validity of their computational implementation.

MONIAC

Among economists, a particularly famous system-dynamics model is the MONIAC. The name is considered an acronym for monetary national income analogue computer. The MONIAC is an early computational implementation of a macroeconomic model, but it did not run on an electronic computer. Instead, the original implementation used a special-purpose hydro-mechanical analog computer. This physical implementation of the model is widely known as the Phillips Machine, after its creator A.W. Phillips. It is also known as the Newlyn-Phillips Machine, which adds attribution to Walter Newlyn, an associate of Phillips who did much of the construction and assisted with the simulation experiments.

The MONIAC is very early example of a system-dynamics simulation model. It is a computational implementation of a macroeconomic model of the UK that was popular in the 1940s. Water levels represent stock variables (such as the money supply), and water flow rates represent flow variables (such as the rate of government expenditure). Phillips used this model to simulate the macroeconomic effects of changes in monetary and fiscal policy. The simulations also illustrated the importance of coordinating monetary and fiscal policy. These simulations were fundamentally dynamical: hydrological interactions evolved over time, and the full effects of policy changes emerged gradually.

Photograph of Bill Phillips with a MONIAC.

Bill Phillips with a MONIAC

Source: LSE Library

Methodological Differences: Aggregation

As subsequent lectures demonstrate, the macrostate in an agent-based model emerges from the interactions of many individual agents. System-dynamics models adopt a fundamentally different methodological approach: they rely heavily on aggregation. For example, the MONIAC is a purely aggregative model. It produces aggregate macroeconomic outcomes (such as GDP) through the interaction of macroeconomic aggregates (such as total consumption). Individual agents are nowhere to be found in the model implementation, even if they are implicit in the underlying conceptual model.

Implementation Differences

The most striking difference of the MONIAC from contemporary simulation methods is its physical implementation. Nowadays, system-dynamics models are typically implemented in software. A typical implementation of a system-dynamics model on a digital computer comprises a set of computational functions. The computer executes these functions in order to simulate the dynamic behavior of a system.

While this difference is practically important, it is not conceptually fundamental. Nevertheless, it is practically important, because physical models are costly and time-consuming to build (and to run). Implementing the same computational model on a modern digital computer, using a modern computer programming language, results in substantially lower costs. Indeed, for a while the Reserve Bank of New Zealand website featured a MONIAC implemented in software.

Discrete-Time Modeling and Simulation

The hydrological MONIAC implemented a continuous-time dynamical system. In this model, time moves continuously as water flows between physical chambers. In contrast, the population model of the present lecture characterizes the system only at discrete points in time. It is a discrete-time dynamical system: time progresses in discrete increments. A single increment is often called a step, model step, time step, period, or tick.

The conceptual size of a model step is the time scale of the model. The real-world temporal interpretation of a single step is model-specific; an increment can be as crude or refined the target system requires. For example, one model step may represent an hour, a day, a month, or a year. In the population growth model of this lecture, a single model step represents one year.

State Transition

Abstractly, the evolution of a computational model from its current state to the next state is governed by a function. This is the evolution rule or the state-transition function. In the discrete-time simulation models of this course, the current model state directly determines the subsequent model state. Letting \(s_t\) represent the model state at time \(t\), and letting \(f\) represent the state-transition function, this becomes \(s_{t+1} = f[s_{t}]\). [2]

Later lectures consider models where the only feasible representation of the state transition function is the actual computational implementation of the entire model. However, a very simple model may imply an easily understood mathematical representation of the state-transition function. The population growth model of this lecture provides an example. Given an initial population, the model iteratively produces a sequence of population levels by applying a simple exponential-growth rule.

Introduction to Function Iteration

Iteration is the repetition of action. Function iteration is the repeated application of a function to produce a sequence of values. (See the Glossary for more details.) For example, starting from an initial state of \(s_0\), we may repeatedly apply an evolution rule \(f\) to produce the following iterates of \(f\).

\begin{align*} s_1 &= f[s_0] \\ s_2 &= f[s_1] = f[f[s_0]] \\ s_3 &= f[s_2] = f[f[f[s_0]]] \end{align*}

As a convenient mathematical notation, write \(f^{\circ n}[s_0]\) to denote the result of iteratively applying the function \(f\) a total of \(n\) times, given the initial value \(s_0\).

State Trajectory

At time \(t\), a discrete-time dynamical system is in some state. Call it \(s_t\). For example, in a population growth model, this might be the current level of the population. By applying the evolution rule of the system, it transitions to another state, say \(s_{t+1}\). Given an initial system state, repeated application of the evolution rule produces future system states. That is, given an evolution rule (\(f\)) and an initial state (\(s_0\)), function iteration generates the implied state trajectory of this model. For example, given an annual growth rate and the current population, we can determine the population trajectory for subsequent years.

Mathematical and Computational Functions

  • mathematical vs computational functions (pure & impure)

  • command-line tests

  • the concept of a computational procedure

  • test procedures

As illustrated by Figure f:functionBlockDiagram, it is often convenient to think of a mathematical function as a rule for transforming inputs into outputs. From this perspective, a univariate real function accepts a real number as an input and produces a real number as an output. For example, a function might transform a number into its square by multiplying the number by itself. This conceptualization applies as well to computational functions, which provide a way to perform such transformations on a computer.

Block diagram for conceptualizaing a function.

Block Diagram for an Abstract Function

What is a Function?

As another example, consider transforming a number by multiplying it by \(1.01\). This is a very specific example of a univariate real function. A typical mathematical representation of this function is \(x \mapsto (1 + 0.01) * x\). (Pronounce the \(\mapsto\) arrow as maps to; the term map is a synonym for function.) This representation of the function simply states its transformation rule.

Applying this specific function to an input value of \(100.0\) produces an output value of \(101.0\). We do not even need a computer for this simple computation. Nevertheless, as shown in the present lecture, this function can represent behavior in a system of interest. Specifically, interpret this function as the yearly evolution rule for the world population. Under this interpretation, the function characterizes the evolution of the population in a world that has a fixed annual population growth rate of one percent.

Function Parameters as Bound Variables

  • mathematically, distinguish bound variables vs. free variables

  • computationally, distinguish local variables vs global variables

The expression \(x \mapsto (1 + 0.01)*x\) represents a function by specifying its transformation rule. To do this, we need an abstract way to refer to any particular input the function might receive. Here, this is the variable named x, called the function parameter. In the function definition, a parameter does not denote a particular value. Instead, it represents any actual value that the function may be applied to.

Correspondingly, any other name will do as well, as long as all occurrences in the function definition change in the same way. For example, the expression \(p \mapsto (1 + 0.01) * p\) represents exactly the same function. In mathematical settings it is common to use a single letter to represent the possible input value, but this is just a convention. For example, the expression \(\square \mapsto (1 + 0.01) * \square\) can represent exactly the same function.

The expressions represent the same function because they describe the same relationship between inputs and outputs. Changing the parameter name does not change the meaning of the expression. The parameter is fundamentally a placeholder that makes it easier to understand the transformation of inputs into outputs. The name chosen to designate the input value has no affect how the function behaves. In fact, it has no meaning outside of this expression.

Here is another way to say the same thing: a function’s parameters are bound by the functions definition. This is true of both mathematical functions and computational functions. This means that the name chosen for a function parameter does not interact with the surrounding context, whether mathematical or computational. In fact, it has no meaning at all outside its particular function definition. So when naming a parameter, just choose a name that best aids a reader to understand the function.

From Mathematical to Computational Functions

This need to create computational functions is so common that almost all programming languages make it extremely simple to define computational functions. This allows computer programs to incorporate useful mathematical functionality by implementing mathematical functions as computational functions. For example, this section shows how to include the population growth function in a computer program.

Recall Figure f:functionBlockDiagram, which characterizes a function as a transformation of inputs into outputs. A particular input value to a computational function is called the function argument, and the corresponding output value is called the return value. Applying a computational function to an argument (of the right type) produces a return value.

What is a Pure Function?

A pure function is

  • closed

  • side-effect free

A function whose output is determined solely by its explicit input is closed to other influences. The result of applying a closed function to an argument depends only on the value of the argument; it does not depend on the context in which the function is executed. Correspondingly, the definition of a pure function ensures that any given input value always produces the same output value. Roughly speaking, when a computational function is closed, knowing just the value of the argument is sufficient to predict the return value.

A function that is closed does not respond to its enclosing environment. This makes it easier to understand the function’s behavior. Additionally, programmers care about changes to that environment. Examples of environmental modification include changing the value of a nonlocal variable, writing data to a file, or communicating with a computer peripheral. A function is side-effect free if it executing it does not modify the environment in which it runs. This is often a desirable property, yet producing side effects is often desirable as well. Later sections return to this problem.

A pure function is closed and side-effect free. (See the Glossary for more details.) Many programming languages provide facilities for defining pure functions, which are the computational analogues of familiar mathematical functions.

Planning the Implementation

A typical implementation of the population growth function above produces a pure function. Good implementations sometimes require extensive planning. However, in a case as simple as this population growth function, very little planning is needed. Even so, it can be useful to sketch a basic function summary, if for no other reason than to nurture the habit.

A function summary may be extremely informal and sketchy, or it may be quite formal and detailed. The level of detail should reflect both the immediate needs of the programmer and the anticipated needs of possible future consumers of the code (i.e., anyone who might try to understand the code). One common to detailed description is pseudocode, which roughly resembles an implementation in a programming language, Instead of pseudocode, this course sketches functions (and other subroutines) in a simple outline format. Here is a function sketch for the population growth function, named nextExponential01.

function:

nextExponential01: Real -> Real

function expression:

\(p \mapsto 1.01 * p\)

parameter:

\(p\), the population

summary:

Grow the population by 1%.

Notation in Code Sketches

Code sketches in this course freely use simple mathematical notation, which often resembles but need not match the notation in any particular computational implementation. For example, it is convenient to provide a very short name to represent an arbitrary input; this is the function parameter (here, p). A function sketch typically expresses the output as an expression involving the function parameter (here, \(1.01 * p\)). In actual code, one may prefer a longer, more informative parameter name (e.g., pop or even population).

Additionally, the function sketches in this course usually state the data type of the valid inputs and of the return values. The notation Real -> Real means that the function maps a real-valued input to a real-valued output. This specifies the input type and output type to both be Real, which name the typical computational data type for representing real numbers. (The exact meaning of this varies across programming languages.)

Create a project named PopulationGrowth01. Begin this project by implementing the nextExponential01 function, following the code outline above. Include helpful comments in your code, and remember to save your model after changing it.

Be sure to define a pure function. It must not depend on any other code. It must do no more than return a numerical output when applied to a numerical input. It must have no side effects. (For example, it must not print.) It must completely specify a deterministic relationship between numerical inputs and numerical outputs.

Ad Hoc Testing of nextExponential01

Untested code should be presumed broken. So, after creating the nextExponential01 function, it is time to test it. A first and simplest test is to apply this function to an argument and then print the result, in order to check that it behaves as expected. The best way to do this is language dependent.

Apply the nextExponential01 function to an argument of 100 and print the result. Confirm that the value produced equals 101. Apply the nextExponential01 function to an argument of 1000 and print the result. Confirm that the value produced equals 1010.

Simple Unit Tests Formalize Expectations

This course provides a very limited discussion of software testing, focusing on ad hoc tests and simple test procedures. Ad hoc testing by printing and then inspecting outputs is often useful, but it quickly becomes tedious. Test procedures take a small step towards more automated testing. An automated test of a single component of a program, such as a function, is often called a unit test.

What Is A Procedure?

This course distinguishes between functions and procedures as follows: a function returns a value for subsequent use, whereas a procedure does not. Both are subroutines that can repeatedly be invoked as a single unit. But since a procedure does not return a usable value, to be of interest it must have some other kind of effect. These other effects are called side effects.

The purpose of a procedure is to produce side effects. Calling the resulting changes side effects may feel a bit misleading, since producing these changes is the very purpose, but it is a standard terminology. Side-effects may be as simple and innocuous as printing some text.

Test Procedures

A test procedure formalizes some expectations about how code should behave. For example, a test procedure may check that a function, when applied to known arguments, returns the correct outputs. This is a type of unit test, where the unit is the function that is tested. A useful test must provide some kind of warning if the test fails. One big advantage of test procedures is that whenever we refactor a function, we can just rerun the existing test to ensure that our changes did not accidentally introduce new, undesirable behavior.

A pure function is particularly easy to test, since the correct output for any specified input is completely predictable. More concretely, consider a computational implementation of the mathematical function \(p \mapsto (1 + 0.01) * p\). One test of this implementation might apply the computational implementation of this function to the value 0.0 and test that the return value is 0.0. If the return value differs from 0.0, the test procedure must signal this failure in some way.

In order to better automate testing of the nextExponential01 function, create a simple test procedure named testNextExponential01. This procedure should test that output is correct for at least the following inputs: -100.0, 0.0, and 100.0. For now, keep test creation as simple as possible: a test failure can just raise an error, causing the test to stop running at that point. If you wish, print out a celebration if all tests pass. Run your test procedure to confirm that nextExponential01 behaves as expected.

Exponential Population Growth

  • population dynamics

  • rates of change vs. growth rates.

  • exponential growth.

  • application: exponential population growth

This section employs the tools developed above to produce a particularly simple forecast of future population levels. The stylized fact motivating this model is the remarkable increase in the world population since the First Agricultural Revolution [BocquetAppel-2011-Science]. Figure historicalWorldPopulationLevels illustrates world population estimates over several thousand years. Clearly, human population growth has not been a linear function of the time passed.

Time-series plot of the estimated world population, 10000BCE–1950CE.

World Population Estimates (10000BCE–1950CE)

Data Source: United Stated Bureau of the Census

This section will demonstrate that a very simple model can produce such nonlinearity. As a preliminary, it presents the difference between rates of change and rates of growth, and applies this distinction to the concept of exponential growth. It then develops a simple discrete-time dynamic model of the world population. Treating the world population as a single stock implicitly aggregates across many individual attributes. For many social-science questions a less aggregative approach is advantageous, and later lectures explore such disaggregation in some detail. However, this lecture first illustrates some basic simulation tools by exploring a simple system-dynamics model at the highest level of aggregation.

Population Dynamics

This section models the world population as a discrete-time dynamical system. The current population (\(P_{t}\)) is the state of this system. The system state comprises this single stock variable. (Recall that it is a stock variable, because population can be measured at any point in time, without any reference to a period of time.) Since the world population can only assume nonnegative values, the possible states of the system correspondingly are nonnegative numbers.

The net addition to the population in a specified period is a flow variable. In a system dynamics model, flows affect stocks. The total population changes as a result of any net addition. The resulting change in the population is a change in the state of the system.

Rate of Change vs Growth Rate

If \(P_t\) is the population at time \(t\), then \(P_{t+1}-P_{t}\) is the rate of change for the period from \(t\) to \(t+1\). Define the one-period growth rate from \(t\) to \(t+1\) to be the proportional rate of change over the period. (This is sometimes called the proportional growth rate.) Let \(g_{t,t+1}\) denote this growth rate, computed as follows.

\begin{align*} g_{t,t+1} &= \frac{P_{t+1} - P_{t}}{P_t} \\ &= \frac{P_{t+1}}{P_t} - 1 \end{align*}

For example, Figure populationLevelsGrowthUS illustrates historical population levels in the United States, along with the associated annual growth rates. Evidently, there can be substantial short-run variation around the mean growth rate even in a large country. For example, even though it is not particularly evident in the plot of the population levels, it is very easy to detect a post-WWII baby boom in the plot of the annual population growth rates.

Plots of the population levels and the annual population growth rates for the US.

US Population Levels and Annual Growth Rates

Data Source: FRED series B230RC0A052NBEA.

Exponential Growth

Real-world population growth is complicated to model and predict. Changing population growth rates affect demographic projections, but as an initial introduction to population growth, this lecture focuses entirely on the average growth rate of the population. To support the development of both conceptual understanding and modeling skills, the section starts with an extremely simple initial model: the growth rate is a constant, \(g\).

\begin{equation*} P_{t+1} = (1 + g) P_{t} \end{equation*}

This is the evolution rule for the population. It states that there is a simple dependence of next period’s population on the current population. In this simple dynamical system, the evolution rule implies exponential growth at a constant rate \(g\).

Exponential Population Growth via Iteration

In the discrete-time population growth model, the population \(P_{t}\) constitutes the system state at time t. A state trajectory is a list of populations over time. From any given initial population, iteratively applying the evolution rule produces future-population projections. Concretely, suppose we are given a growth rate \(g\) and an initial population \(P_0\). Compute the population \(P_t\) by iteratively (i.e., repeatedly) multiplying by \((1+g)\). Here are the first three iterations.

\begin{align*} P_1 &= (1 + g) P_{0} \\ P_2 &= (1 + g) P_{1} \\ P_3 &= (1 + g) P_{2} \end{align*}

Simulation Modeling vs Mathematical Modeling

This population model is so simple that computer simulation is unnecessary for its solution. For example, this model implies an interesting, useful, and particularly simple mathematical relation: \(P_{t} = (1+g)^t P_{0}\). This concisely characterizes the population at any time \(t\), and it is clear that a simple calculator can quickly produce a projection for any future population level. There is no need to simulate the evolution of the system in order to determine the population projection for any future point in time.

This means that there is no technical reason to implement a computational model of this particular dynamical system. However, many dynamical systems lack a simple mathematical solution yet are nevertheless tractable for simulation (subject to relevant limitations of the computer). Although simulating this simple population model is unnecessary, it serves to plainly illustrate a simulation process that applies to more complex systems.

Looping Constructs

Beginning with a conceptual model of a dynamical system, simulation modeling begins by implementing in code a computational model of the system. Executing the computational model—running the simulation—describes the evolution of the system over time. This typically involves iterating the evolution rule for the system. The only requirements for a first population simulation are an initial population, a particular population growth rule, and an iteration technique.

Even with a simple exponential-growth population model, iteration by hand quickly becomes tiresome. Fortunately, computers are excellent tools for repetitive tasks. The computer instructions for performing any action once provides the basis for performing the same action repeatedly. In a discrete-time dynamical system, looping constructs allow easy iteration of the evolution rule. A useful looping construct must provide a way to start and end a sequence of iterations. An oft-repeated joke describes a programmer who cannot stop shampooing, because the instructions are “lather; rinse; repeat.”

Population Projection

While modelers sometimes care only about the final state achieved by a simulation, it is common to track the evolution of the simulation over time. Tracking requires collecting simulation data at regular intervals, often at the end of each iteration. The sequence of collected data is the output trajectory for the simulation. For example, population modelers often produce annual projections for a number of decades into the future. These projections derive from the output trajectories produced by their simulations.

Imagine that you are a demographer in 2022, with a world population of approximately \(8.1\) billion. Given a constant population growth rate of 1% per year, predict the world population each year for the next \(50\) years. Use the nextEponential01 function to produce these projections.

Model Parameters

In order to produce population projections, the exponential growth model requires only an initial population and a population growth rate. These are the model parameters; they do not change as a simulation runs. Our first simulation exercise relied entirely on hardcoding these parameters. For example, the nextExponential01 function includes the population growth rate of 1% literally in the function definition. An alternative approach is to introduce model parameters as global constants. (The next lecture considers yet another approach.) As an example of a named model parameter, let pop0 denote the initial population level.

Global Variables and Constants

Recall that pop0 is a model parameter, which should not change as the simulation runs. Some languages include features for enforcing of this constancy; others leave it up to the care of the programmer. In any case, running the simulation should not change its value.

Let the name pop0 be visible everywhere in the PopulationGrowth01 model. Any code, anywhere in the model, can access it. Then we say that this name has has global scope. We typically call it a global variable, even when it must remain constant.

Roughly speaking, any portion of a model’s code can refer to any global variable. Problematically, in some languages this means that any portion of a model’s code can change any global variable. One part of the model might change a global variable that another part expects to be unchanged.

Keeping track of such expectations can be difficult. For this reason, programmers should introduce global variables reluctantly and handle them carefully. Despite these serious drawbacks, the use of global variables can simplify early model development, and this course rather freely introduces global variables. This often produces easier to read example code, from which global variables can be purged by more experienced readers.

Initializing the Global Variables

A simulation requires initialization of the model parameters before it can run. This course typically implements this initialization during the setup phase of the model. In the exponential-growth population model, initialization is trivial: just set the value of pop0 (the initial world population).

Add pop0 as a parameter of the PopulationGrowth01 model. Reproduce the same 50 year population projections, but this time, make use of the value of the pop0 model parameter.

Exponential Population Growth: A Standardized Simulation

  • standardizing the simulation structure.

  • initializing a simulation model.

  • defining a simulation schedule.

  • create runtime data visualizations.

This section elaborates the computational implementation of the population growth model introduced above. This elaboration provides our first steps towards a standard structure for discrete-time simulation modeling. Topics include the concept of a simulation schedule and approaches to viewing simulation data.

High-Level Simulation Structure

At the highest level of abstraction, running the population growth simulation may be divided into two main phases: a setup phase, and an iteration phase. The setup phase comprises preparations to run a simulation schedule, including setting up the initial model state (e.g., the initial population). The iteration phase repeatedly executes a simulation schedule, which leads to updates of the model state (e.g., new values of the total population). Later lectures add a wrap-up phase, comprising things to do when the simulation is complete. Figure f:runsim01 outlines this typical simulation structure.

images/runsim.svg

Stylized Discrete-Time Simulation

Iteration Phase

As illustrated by figure f:runsim01, a typical discrete-time simulation iterates a schedule until a stopping criterion is satisfied. This simulation schedule runs the evolution rule for the model. One execution of this schedule is called a model iteration or a simulation step.

In the population model of this section, each simulation step updates the world population (pop). This involves applying the nextExponential01 function to the current value of pop in order to produce the new population. This is the core of the iteration phase of the simulation.

A runsim Procedure for the Population Model

A typical discrete-time simulation begins with a setup phase, which may include the initialization of global variables. Next, the simulation enters the iteration phase, repeatedly running the simulation schedule until the simulation terminates. The following sketch of a runsim procedure encompasses this standard structure.

procedure name:

runsim

summary:
  • Set up the simulation, as needed.

  • Starting with the initial population stored in pop0, iterate the simulation schedule a total of 50 times to produce the 50 annual population projections.

In this population model, the simulation runs \(50\) iterations of the simulation schedule. During the iteration phase, we often attempt to gather data from the simulation. There are many ways to do this, which later lectures explore in detail. For the moment, continue to simply print the simulation values at the end of each iteration.

Following the procedure sketch above, add a runsim procedure to the PopulationGrowth01 project. The setup phase should fully prepare for the iteration phase. Use this runsim procedure to run the same population simulation as before. That is, during the iteration phase, use nextExponential01 to update the population. For this exercise, output the projections any way you wish. (For example, you may simply print them.)

Alternative Approach: Create a runsim function that returns a sequence of projections (e.g., a list or array).

Stretch Goal: Change runsim so that it requires one argument: the initial population.

Data Visualization

So far, our only access to the simulation trajectory has been through the examination of the printed population forecasts. This is far from adequate: the printed results provide very limited insight into the simulation outcomes. Later lectures explore a variety of tools for improving our understanding of such data.

Data visualization can provide useful insights into simulation outcomes. Data visualizations may expose patterns in the evolution of a simulation or in its final outcomes that might be missed in summary statistics. A normal human eye can process a remarkable amount of visual information very quickly, often detecting even faint patterns or small anomalies with ease.

Charts displaying the generated data can therefore promote understanding of the simulation outcomes. Of course, humans are subject to perceptual illusions and may detect patterns where there are none. Nevertheless, visual representations of simulation output often prove indispensable in its analysis. This section introduces a univariate visualization that has repeatedly proved particularly useful: the run-sequence plot or time-series plot.

Run-Sequence Plots

Creating an attractive and communicative plot can be difficult. However, programming languages and simulation toolkits often provide extensive facilities for data visualization. These plotting facilities vary widely across languages, and the ease of use of these facilities varies considerably. In some languages the builtin plotting facilities are so limited that it typically pays to export the data to a file for use by a separate plotting program. (A spreadsheet is often adequate to this task; a more advanced choice is gnuplot.)

Future lectures will discuss how to export data from simulations. However, the rest of this section assumes that the language in use provides ready access to basic plotting facilities. When a simulation model runs quickly enough, as with this population-projection model, the data may even be displayed dynamically (i.e., as the simulation runs).

Population-Projection Plots

The best approach to plotting is highly language specific. Some languages support easy GUI building that includes facilities for easy dynamic plotting. Others require additional substantial additional code even for simple plots. Regardless, plot creation generally has two components: setting up the options for the plot window, and plotting the data.

Add a run-sequence plot to your PopulationGrowth01 project. This should display a plot of the annual population projections produced by the population simulation. Choose your plot options, which may include the numerical limits of the two axes and an informative choice of labels. Finally, create a runsimG procedure that modifies your runsim procedure by adding a plot. Use this runsimG procedure to plot your annual population projections for \(50\) years.

A animated plot can create the appearance of change over time by sequentially redrawing the plot to reflect updates to the model state. In the context of simulation modeling, an animated plot is dynamic if it redraws the new model states as the simulation runs. Using an animated plot or dynamic plot for this exercise is optional. In either case, plot updates should be sequential and should add a single point to the run-sequence plot. For a dynamic plot, include plotting of the initial model state in the setup phase.

Sample Population-Projection Plot

Figure f:exponentialGrowthPlot provides an example of a chart based on the data generated by this population model. The smooth upward trend in the population is readily apparent, as is the increasing slope of the population trajectory. The ease with which we discover these features in the data demonstrates the analytical utility of data visualizations. Plots can make it easy to detect data behaviors that are hard to detect in printed output.

Time-series plot of poulation projections, assuming exponential growth.

Population Projections (Exponential Growth)

Summary and Conclusions

This lecture introduces the process of modeling and simulation by discussing discrete-time system-dynamics. Core system dynamics is the interaction between aggregated stock variables and flow variables. This lecture emphasized the effect of flows on stocks, but future lectures will explore two-way interactions. System dynamics summarizes these aggregate interactions as functions, so this lecture explored the relationship between mathematical and computational functions.

In the terminology of this course, a computational function transforms inputs into outputs. A pure function ensures that a given input always produces the same output, with no other side effects. This contrast with a procedure, which does not return a value but instead produces changes in the program state. In many programming languages, simulations are controlled by procedures, which in turn depend on functions. This lecture illustrates this with a simple population simulation, where runsim procedure iteratively applies the nextExponential01 function in order to produce a sequence of population projections.

This lecture also explored the relationship between a conceptual model and its computational implementation. The computational model of population growth derives from an underlying conceptual model of population growth. In order to practice decomposition—breaking models into simple, reusable pieces—this lecture constructs the simulation model from extremely simple functions and procedures. In order to promote the development of correct and maintainable code, testing receives substantial emphasis.

Plots facilitate data analysis and need not be difficult to produce. The ultimate population model from this lecture produces a run-sequence plot of the population projections. Figure f:exponentialGrowthPlot provides an example of a chart based on the data generated by this simulation model.

The nonlinearity of exponential population growth is somewhat visible in this plot. It quickly becomes more visible if we extend the population projections further into the future. In fact, a 1% growth rate of population produces doubling approximately every \(70\) years. So although the \(50\) year projections may appear somewhat shocking, projections \(150\) years appear utterly impossible. The projections of this lecture assume a constant rate of population growth, but this assumption is dropped in the next lecture.

Explorations

  1. Helpful code comments are a good coding practice. Comments can specify the meaning of each model parameter or global variable. In some languages, we may also find comments useful for expressing the intended type of each variable. For example, a comment can specify that a variable should always have an Integer type. This is useful when the implementation language does not support explicit typing of variables. Add such comments to the PopulationGrowth01 project.

  2. Discrete-time simulations often include a global measure of time elapsed (i.e., the number of iterations). Conventionally this is often named ticks. Add a ticks variable to the population model, initializing it to 0 and incrementing it near the end of each iteration. This ticks variable is inessential: it simply tracks virtual time in our simulation. In this population simulation, one tick will represent the passage of one year. This is the time-scale of the simulation model.

  3. Allow the growth rate each period to be subject to a transient random shock. How does this affect your population simulations? Allow the growth rate each period to be subject to a persistent random shock. How does this affect your population simulations?

  4. (Advanced) Export the simulation data and use a spreadsheet to plot your population projections.

  5. (Advanced) Instead of hardcoding the population growth rate, make it a model parameter (say, gE). Control its value with a slider in the model’s GUI.

  6. (Advanced) Change runsim into a function that accepts one argument, (the number of years of projections) and returns the trajectory of projections (e.g., as a list or array).

Additional Resources

Readers wanting a refresher for basic mathematical concepts may find [Feldman-2012-OxfordUP] particularly accessible and helpful. See chapter 1 for an elementary review of functions. Chapters 2 and 5 provide a brief, elementary overview of the concept of function iteration. The Wikipedia entry on iterated functions is also a useful resource.

[Frigg.Nguyen-2016-Monist] provide a probing discussion of the relationship between models and their target systems, complete with useful examples and many helpful references. Also see [Swoyer-1991-Synthese], whose broader notion of surrogative reasoning links to the suggestion in the present lecture that researchers experiment with models in order to learn about the target systems.

As a crude rule of thumb, never assume that untested code works as intended. Accordingly, computational models typically include tests, which help ensure that the code is working as intended. Some programming languages include extensive facilities for testing. Software testing is a huge topic that the present course treats in a cursory manner. Among the many excellent books and articles on how to test and maintain computer software, [Myers-2012-Wiley] and [Jorgensen-2014-CRC] provide good introductions to serious software testing.

For additional historical details about the MONIAC, see [Bissell-2007-IEEECS]. [Ng.Wright-2007-RBNZB], and Phillips’s early published description [Phillips-1950-Economica]. In addition, Part II of [Leeson-2000-CambridgeUP] is devoted to the MONIAC. [Colander-2011-EconomiaPolitica] draws lessons from the MONIAC for contemporary macromodeling efforts. [Ryder.Cavana_Cavana.etal-2021-Springer] provide a detailed exploration of how to translate knowledge of the MONIAC into a modern system-dynamics formulation.

Currently a number of web videos of the MONIAC are available on the Internet. One of them is [Making.Money.Flow-2017-RBNZ], created by the Reserve Bank of New Zealand using its still-working MONIAC. A more extended demonstration is offered by the University of Cambridge. A wonderful bit of MONIAC history is provided by Tim Harford’s lecture on Bill Phillips.

References

[Bissell-2007-IEEECS]

Bissell, Chris. (2007) The Moniac: A Hydromechanical Analog Computer of the 1950s. IEEE Control Systems 27, 69--74.

[BocquetAppel-2011-Science]

Bocquet-Appel, Jean-Pierre. (2011) When the World's Population Took Off: The Springboard of the Neolithic Demographic Transition. Science 333, 560--561. https://science.sciencemag.org/content/333/6042/560

[Colander-2011-EconomiaPolitica]

Colander, David. (2011) The MONIAC, Modeling, and Macroeconomics. Economia Politica: Journal of Analytical and Institutional Economics , 63--82.

[Feldman-2012-OxfordUP]

Feldman, David P. (2012) Chaos and Fractals: An Elementary Introduction. : Oxford University Press.

[Frigg.Nguyen-2016-Monist]

Frigg, Roman, and James Nguyen. (2016) The Fiction View of Models Reloaded. The Monist 99, 225--242. http://dx.doi.org/10.1093/monist/onw002

[Humphreys-2002-PhilSci]

Humphreys, Paul. (2002) Computational Models. Philosophy of Science 69, S1--S11. www.jstor.org/stable/10.1086/341763

[Jorgensen-2014-CRC]

Jorgensen, Paul. (2014) Software Testing: A Craftsman's Approach. Boca Raton, Florida: CRC Press.

[Leeson-2000-CambridgeUP]

Leeson, Robert. () AWH Phillips: Collected Works in Contemporary Perspective. : Cambridge University Press.

[Myers-2012-Wiley]

Myers, Glenford J. (2012) The Art of Software Testing. Hoboken, N.J: John Wiley and Sons.

[Ng.Wright-2007-RBNZB]

Ng, Tim, and Matthew Wright. (2007) Introducing the MONIAC: An Early and Innovative Economic Model. Reserve Bank Bulletin 70, 46--52. https://www.rbnz.govt.nz/research-and-publications/reserve-bank-bulletin/2007/rbb2007-70-04-04

[Phillips-1950-Economica]

Phillips, A. W. (1950) Mechanical Models in Economic Dynamics. Economica 17, 283--305. http://www.jstor.org/stable/2549721

[Ryder.Cavana_Cavana.etal-2021-Springer]

Ryder, William H., and Robert Y. Cavana. (2021) A System Dynamics Translation of the Phillips Machine. In: Feedback Economics: Economic Modeling with System Dynamics, 97--134. Cham: Springer International Publishing. https://doi.org/10.1007/978-3-030-67190-7_5

[Swoyer-1991-Synthese]

Swoyer, C. (1991) Structural Representation and Surrogative Reasoning. Synthese 87, 449--508.

[Making.Money.Flow-2017-RBNZ]

Reserve Bank of New Zealand,. (2017) Making Money Flow: The MONIAC.

version:

2023-07-14