Minnetonka API reference

minnetonka.py: value modeling in python

model(variables=[], treatments=[''], initialize=True, timestep=1, start_time=0, end_time=None)

Create and initialize a model, an instance of Model

A model is a collection of variables, with one or more treatments. A model can be simulated, changing the value of variables with each simulated step.

A model can be created via Model(), after treatment objects have been explicitly created. But typically this function is used instead, as it is simpler.

A model sets a context, so variables can be defined for the newly created model, as in the example below.

Parameters:
  • variables (list of Variable, optional) – List of variables that are part of the model. If not specified, the default is [], no variables. An alternative to creating the variables first, then the model, is to define the variables within the model context, as in the example below.
  • treatments (list of str, or list of tuple of (str, str), optional) – List of treatment specs. Each treatment specs is a simulation scenario, simulated in parallel. Typical treatments might include ‘As is’, ‘To be’, ‘At risk’, ‘Currently’, With minor intervention’, etc. A treatment can be either a string—the name of the treatment—or a tuple of two strings—the name and a short description. See examples below. If not specified, the default is [''], a single treatment named by the empty string.
  • initialize (bool, optional) – After the variables are added to the model, should all the variables be given their initial values? If more variables need to be added to the model, wait to initialize. Default: True
  • timestep (int, optional) – How much simulated time should elapse between each step? Default: 1 time unit
  • start_time (int, optional) – At what time should the simulated clock start? Default: start at 0
  • end_time (int, optional) – At what simulated time should the simulatation end? Default: None, never end
Returns:

the newly created model

Return type:

Model

See also

Model
a model, once created
variable()
Create a Variable to put in a model
constant()
Create a Constant to put in a model
previous()
Create a Previous to put in a model
stock()
Create a system dynamics Stock, to put in a model
accum()
Create an Accum, to put in a model

Examples

Create a model with no variables and only the null treatment:

>>> m = model()

A model that defines two treatments:

>>> model(treatments=['As is', 'To be'])

One of the treatments has a description:

>>> model(treatments=[('As is', 'The current situation'), 'To be'])

A model with two variables:

>>> m = model([DischargeBegins, DischargeEnds])

Variables can be defined when the model is created:

>>> m = model([
...         variable('Revenue', np.array([30.1, 15, 20])),
...         variable('Cost', np.array([10, 10, 10])),
...         variable('Earnings', lambda r, c: r - c, 'Revenue', 'Cost')
...    ])

A model is a context, supporting variable addition:

>>> with model() as m:
...  variable('Revenue', np.array([30.1, 15, 20]))
...  variable('Cost', np.array([10, 10, 10]))
...  variable('Earnings', lambda r, c: r - c, 'Revenue', 'Cost')
variable(variable_name, [description, ]specifier, *dependencies)

Create a variable.

A variable has a value—called an ‘amount’—that changes over simulated time. A single variable can have a different amount in each model treatment. The amount of a variable can be any Python object. The amount of a variable in a particular treatmant can be found using subscription brackets, e.g. Earnings[‘as is’].

A variable differs from other variable-like objects (e.g. stocks) in that it keeps no state. At any timestep, its amount depends entirely on its specifier, and the amounts of dependencies.

The specifier is a callable, and is called once at each timestep for each treatment, using as arguments the amounts of the dependencies in that treatment.

The amount of a variable in a treatment can be changed explicitly, outside the model logic, e.g. Earnings[‘as is’] = 2.1. Once changed explicitly, the amount of the variable never changes again, until the simulation is reset or the amount is changed again explicitly. See examples below.

Parameters:
  • variable_name (str) – Name of the variable. The name is unique within a single model.
  • description (str, optional) – Docstring-like description of the variable.
  • specifier (callable) – The specifier is called at every timestep. Zero or more dependencies are supplied.
  • dependencies (list of str) – Names of variables (or constants or stocks or …) used as arguments for the callable specifier. Might be empty, if callable requires no arguments.
Returns:

the newly-created variable

Return type:

Variable

See also

Variable
a variable, once created
constant()
Create a variable whose amount does not change
stock()
Create a system dynamics stock
previous()
Create a variable for the prior amount of some other variable
PerTreatment
for defining how a variable has a different amount for each treatment

Examples

A variable can take a different amount every timestep, via a lambda …

>>> RandomValue = variable('RandomValue', lambda: random.random() + 1)

… or via any Python callable.

>>> RandomValue = variable('RandomValue', random.random)

The callable can depend on the amount of another variable in the model …

>>> DischargeProgress = variable(
...     'DischargeProgress', lambda db: (current_step - db) / 4,
...     'DischargeBegins')

… or depend on multiple variables.

>>> Earnings = variable('Earnings', lambda r, c: r - c, 'Revenue', 'Cost')

A variable can use different callables in different treatments.

>>> DischargeEnds = variable('DischargeEnds',
...     PerTreatment(
...         {'As is': lambda db: db + 10, 'To be': lambda db: db + 5}),
...     DischargeBegins')

An callable can use the model itself, instead of a variable in the model.

>>> Time = variable('Time', lambda md: md.TIME, '__model__')
constant(constant_name, [description, ]specifier[, *dependencies])

Create a constant.

A constant is similar to a variable except that its amount does not vary. Its amount is set on initialization, and then does not change over the course of the simulation run. When the model is reset, the constant can take a new amount.

The amount of a constant can be any Python object, except a string or a Python callable. It can be defined in terms of other variables, using a callable as the specifier. See examples below.

A single constant can take a different amount in each model treatment. The amount of a constant in a particular treatment can be found using the subscription brackets, e.g. Interest[‘to be’]. See examples below.

The amount of a constant can be changed explicitly, outside the model logic, e.g. Interest[‘to be’] = 0.07. Once changed, the amount of the constant remains the same, until the model is reset or the amount is again changed explicitly. See examples below.

Parameters:
  • constant_name (str) – Name of the constant. The name is unique within a single model.
  • description (str, optional) – Docstring-like description of the constant.
  • specifier (callable or Any) – The specifier can be a callable. If a callable, it is called once, at the beginning of the simulation run. Zero or more dependencies are supplied, names of variables whose amounts are provided when the callable is called. If not a callable, specifier can be any Python object except a string, but no dependencies are supplied. If not a callable, the specifier is provided as the amount at the beginning of the simulation run.
  • dependencies (list of str) – Names of variables (or stocks or constants or …) used as arguments for the callable specifier. Empty list unless specifier is a callable. See examples below.
Returns:

the newly-created constant

Return type:

Constant

See also

Constant
a constant, once created
variable()
Create a variable whose amount might change over the simulation
stock()
Create a system dynamics stock
PerTreatment
for defining how a constant takes different (constant) amounts for each treatment

Examples

Create a constant without a description.

>>> constant('KitchenCloses', 22)

Create constant with a description.

>>> constant('KitchenCloses',
...     '''What time is the kitchen scheduled to close?''',
...     22.0)

Create a constant whose amount is a Python dictionary.

>>> constant('KitchenCloses',
...     {'Friday': 23.5, 'Saturday': 23.5, 'Sunday': 21.0,
...      'Monday': 22.0, 'Tuesday': 22.0, 'Wednesday': 22.0,
...      'Thursday': 22.5})

Create a constant whose amount differs by treatment.

>>> constant('KitchenOpens',
...     PerTreatment({'As is': 17.0, 'Open early': 15.5}))

Create a constant whose (non-varying) amount is calculated from other variables.

>>> constant('KitchenDuration',
...     '''How many hours is the kitchen open each day?''',
...     lambda c, o: c - o, 'KitchenCloses', 'KitchenOpens')

Create a model with one variable and two constants.

>>> import random
>>> with model() as m:
...     C1 = constant('C1', lambda: random.randint(0, 9)
...     V = variable('V', lambda: random.randint(0, 9))
...     C2 = constant('C2', lambda v: v, 'V')

C2 and V have the same amount, a random integer between 0 and 9. C1 has a different amount.

>>> V['']
2
>>> C2['']
2
>>> C1['']
0

The simulation is advanced by one step. The variable V has a new amount, but the constants C1 and C2 remain the same.

>>> m.step()
>>> V['']
7
>>> C2['']
2
>>> C1['']
0

The simulation is reset. Now C2 and V have the same value, again.

>>> m.reset()
>>> V['']
6
>>> C2['']
6
>>> C1['']
8

The amount of C2 is changed, outside of the model logic.

>>> C2[''] = 99
>>> C2['']
99

C2 still stays constant, even after a step.

>>> m.step()
>>> C2['']
99

But on reset, C2’s amount is once again changed to V’s amount.

>>> m.reset()
>>> V['']
1
>>> C2['']
1

Show the details of C2

>>> C2.show()
Constant: C1
Amounts: {'': 6}
Definition: C1 = constant('C1', lambda: random.randint(0, 9))
Depends on: []
stock(stock_name, *args)

stock(stock_name, [description,] increment [,[increment_dependencies,] initial [, initial_dependencies]])

Create a system dynamics stock.

In system dynamics, a stock is used to model something that accumulates or depletes over time. The stock defines both an initial amount and an increment.

At any simulated period, the stock has an amount. The amount changes over time, incrementing or decrementing at each timestep. The amount can be a simple numeric like a Python integer or a Python float. Or it might be some more complex Python object: a list, a tuple, a numpy array, or an instance of a user-defined class. In any case, the stock’s amount must support addition and multiplication. (Addition and multiplication are supported for dicts, tuples, and named tuples via foreach().)

If the model in which the stock lives has multiple treatments, the stock may have several amounts, one for each treatment. The amount of a stock in a particular treatment can be accessed using subscription brackets, e.g. Savings[‘to be’].

A stock definition has two parts: an initial and an increment. The initial is either a callable or any non-callable Python object except a string. If a callable, the initial has a (possibly empty) tuple of dependencies. If a non-callable, initial_dependencies is an empty tuple.

If initial is a callable, that callable is called once for each treatment, at model initialization, with the initial amount of each of the dependencies for that treatment. The names of the dependencies are provided: initial_dependencies is a tuple of strings. Each dependency named can either be a (plain) variable (i.e. an instance of Variable) or a stock or a constant or any of the other variable elements of a model. The result of the execution of the callable becomes the initial amount of the stock, for that treatment.

The stock increment is also either a callable or any non-callable Python object except a string. If a callable, the increment has a (possibly empty) tuple of dependencies. If a non-callable, increment_dependencies is an empty tuple.

If increment is a callable, the callable is called once every period for each treatment, using as arguments the amounts of each of the dependencies in that treatment. Each dependency can be the name of a (plain) variable (i.e. an instance of Variable) or a stock or a constant or any of the variable elements of a model. The callable is given the amounts of the variables at the previous timestep, not the current timestep, to determine the increment of the stock for this period.

The increment is how much the stock’s amount changes in each unit of time. If the timestep of the model is 1.0, the stock’s amount will change by exactly that increment. If the timestep is not 1.0, the stock’s amount will change by a different quantity. For example, if the timestep is 0.5, the stock’s amount will change by half the increment, at every step. (For more on the timestep, see model().)

The initial amount and the increment amount may vary by treatment, either because one or more of the the dependencies vary by treatment, or because of an explicit PerTreatment expression. See examples below.

The amount of a stock in a treatment can be changed explicitly, outside the model logic, e.g. Savings[‘to be’] = 1000. Once changed explicitly, the amount of the stock never changes again (in that treatment), until the simulation is reset or the amount is changed again explicitly.

Parameters:
  • stock_name (str) – Name of the stock. The name must be unique within the model.
  • description (str, optional) – Docstring-like description of the stock.
  • increment (callable or Any) –

    The increment can be either a callable or any Python object, except a string. If a callable, the increment is called once for each treatment at every timestep, with arguments the amounts of increment_dependencies in that treatment. The result of the callable execution for a single treatment is the unit time change in amount for that treatment. See examples below.

    If increment is not a callable, it is interpreted as the unit time change in amount, unchanging with each timestep.

    Using PerTreatment, a different amount or different callable can be provided for different treatments. See examples below.

  • increment_dependencies (tuple of str, optional) – Names of dependencies—i.e. names of (plain) variables or constants or other stocks or …— used as arguments for the callable increment. Might be an empty tuple, the default, either if callable increment requires no arguments, or if increment is not a callable.
  • initial (callable or Any, optional) –

    The initial can be either a callable or any Python object, except a string. If a callable, the initial is called once for each treatment at the beginning of the simulation, with arguments of the amounts of initial_dependencies. The results of the callable execution for a single treatment becomes the initial amount of the stock, for that treatment.

    If initial is not a callable, it is interpreted as the initial amount for the stock.

    Using PerTreatment, a different amount or different callable can be provided for different treatments. See examples below.

  • initial_dependencies (tuple of str, optional) – Names of dependencies—i.e. names of (plain) variables or constants or other stocks or …— used as arguments for the callable initial. Might be an empty tuple, the default, either if callable initial requires no arguments, or if increment is not a callable.
Returns:

the newly-created stock

Return type:

Stock

See also

variable()
Create a variable whose amount might change
constant()
Create a variable whose amount does not change
accum()
Create an accum, much like a stock except that it uses the amounts of the variables in the current period, instead of the previous period.
PerTreatment
for defining how an increment or initial varies from treatment to treatment

Examples

A stock that starts with the amount 2018, and increments the amount by 1 at each period.

>>> stock('Year', 1, 2019)

The initial amount defaults to zero.

>>> stock('Age', 1)

A stock can take a docstring-like description.

>>> stock('Year', '''the current year''', 1, 2019)

The initial amount can be different in each treatment.

>>> stock('MenuItemCount', 1, PerTreatment({'As is': 20, 'To be': 22}))

The increment can be different for each treatment.

>>> stock('MenuItemCount', PerTreatment({'As is': 1, 'To be': 2}), 20)

The increment can be a callable with no dependencies. Note the empty tuple of dependencies.

>>> stock('MenuItemCount', lambda: random.randint(0,2), (), 20)

The initial amount can be a callable. If the initial amount is a callable, the increment must also be a callable. Note the empty tuples.

>>> stock('MenuItemCount',
.,,     lambda: random.randint(15, 18), (),
...     lambda: random.randint(20, 22), ())

Dependencies can be provided for the increment callable.

>>> stock('Savings', lambda interest: interest, ('Interest',), 0)

Dependencies can be provided for the initial callable.

>>> stock('Savings',
...     lambda interest, additions: interest + additions,
...     ('Interest', 'AdditionsToSavings'),
...     lambda initial: initial,
...     ('InitialSavings',))

Feedback is supported.

>>> stock('Savings', lambda interest: interest, ('Interest',), 1000)
... variable('Rate', 0.05)
... variable('Interest',
...     lambda savings, rate: savings * rate, 'Savings', 'Rate')

The amounts can be numpy arrays, or other Python objects.

>>> stock('Revenue', np.array([5, 5, 10]), np.array([0, 0, 0]))
... variable('Cost', np.array([10, 10, 10]))
... variable('Earnings', lambda r, c: r - c, 'Revenue', 'Cost')
previous(previous_name, [description, ]prior[, initial_amount])

Create a previous.

Create a new previous, a variable whose amount is the amount of another variable—the one named by prior—in the previous timestep.

If the model in which the previous lives has multiple treatments, and its prior has a different amount for each treatment, so will the previous. The amount of the previous in a particular treatment can be accessed using subscription brackets, e.g. YesterdaySales[‘as is’].

When the model is initialized, the amount of the previous is either set to initial_amount, or if no initial amount is provided, it is set to the amount of prior.

Parameters:
  • variable_name (str) – Name of the previous. The name must be unique within a single model.
  • description (str, optional) – Docstring-like description of the previous.
  • prior (str) – The name of a variable (or constant or stock or …). The amount of the prior in the last timestep becomes the new amount of the previous in this timestep.
  • initial_amount (Any, optional) – Any non-string and non-callable Python object. But typically this is some kind of numeric: an int or a float or a numpy array of floats or the like. If provided, when the model is initialized, the initial amount of prior is set to initial_amount.
Returns:

the newly created previous

Return type:

Previous

See also

variable()
Create a variable whose amount might change
constant()
Create a variable whose amount does not change

Examples

Finding yesterday’s sales, when the timestep is one day.

>>> previous('YesterdaySales', 'Sales')

A previous might have a description.

>>> previous('YesterdaySales',
    '''Total sales in the prior day''',
    'Sales')

A previous might have an initial amount, if that amount needs to be different from the initial amount of the prior.

>>> previous('YesterdaySales',
    '''Total sales in the prior day''',
    'Sales',
    3000)
accum(accum_name, *args)

accum(accum_name, [description,] increment [,[increment_dependencies,] initial [, initial_dependencies]])

Create a system dynamics accum.

An accum is much like a Stock, modeling something that accumulates or depletes over time. Like a stock, an accum defines both an initial amount and an increment.

There is an important difference between a stock and an accum: an accum is incremented with the current amounts of its dependencies, not the amounts in the last period. This seemingly minor difference has a big impact: a circular dependency can be created with a stock, but not with an accum. The stock Savings can depend on Interest, which depends in turn on Savings. But this only works if Savings is a stock. If Savings is an accum, the same circular dependency is a model error.

At any simulated period, the accum has an amount. The amount changes over time, incrementing or decrementing at each period. The amount can be a simple numeric like a Python integer or a Python float. Or it might be some more complex Python object: a list, a tuple, a numpy array, or an instance of a user-defined class. In any case, the accum’s amount must support addition. (Addition is supported for dicts, tuples, and named tuples via foreach().)

If the model in which the accum lives has multiple treatments, the accum may have several amounts, one for each treatment in the model. The amount of an accum in a particular treatment can be accessed using subscription brackets, e.g. RevenueYearToDate[‘as is’].

An accum definition has two parts: an initial and an increment. The initial is either a callable or any non-callable Python object except a string. If a callable, the initial has a (posssibly empty) tuple of dependencies. If a non-callable, iniitial_dependences is an empty tuple.

If initial is a callable, that callable is called once for each treatment, at model initialization, with the initial amounts of each of the dependencies. The names of the dependencies are provided: initial_dependencies is a tuple of strings. Each dependency named can either be a (plain) variable (i.e. an instance of Variable) or a stock or a constant or any of the other variable elements of a model. The result of the execution of the callable becomes the initial amount of the accum, for that treatment.

The accum increment is also either a callable or any non-callable Python object except a string. If a callable, the increment has a (possibly empty) tuple of dependencies. If a non-callable, increment_dependencies is an empty tuple.

If increment is a callable, the callable is called once every period for each treatment, using as arguments the amounts of each of the dependencies in that treatment. Each dependency can be the name of a (plain) variable (i.e. an instance of Variable) or a stock or a constant or any of the variable elements of a model. The callable is given the amounts of the variables at the current period, to determine the increment of the accume for this period.

The increment is how much the accum’s amount changes in each period. Note that this is another difference between an accum and a stock: for a stock the amount incremented depends on the timestep; for an accum it does not. For example, if both a stock S and an accum A have an increment of 10, and the timestep is 0.5, S will increase by 5 every period but A will increase by 10.

The initial amount and the increment amount may vary by treatment, either because one or more of the the dependencies vary by treatment, or because of an explicit PerTreatment expression.

The amount of an accum in a treatment can be changed explicitly, outside the model logic, e.g. RevenueYearToDate[‘as is’] = 1000. Once changed explicitly, the amount of the accum never changes again (in that treatment), until the simulation is reset or the amount is changed again explicitly.

Parameters:
  • accum_name (str) – Name of the accum. The name must be unique within the model.
  • description (str, optional) – Docstring-like description of the accum.
  • increment (callable or Any) –

    The increment can be either a callable or any Python object, except a string. If a callable, the increment is called once for each treatment at every timestep, with arguments the amounts of increment_dependencies in that treatment. The result of the callable execution for a single treatment is the change in amount for that treatment. See examples below.

    If increment is not a callable, it is interpreted as the change in amount, unchanging with each timestep.

    Using PerTreatment, a different amount or different callable can be provided for different treatments. See examples below.

  • increment_dependencies (tuple of str, optional) – Names of dependencies—i.e. names of (plain) variables or constants or other stocks or …— used as arguments for the callable increment. Might be an empty tuple, the default, either if callable increment requires no arguments, or if increment is not a callable.
  • initial (callable or Any, optional) –

    The initial can be either a callable or any Python object, except a string. If a callable, the initial is called once for each treatment at the beginning of the simulation, with arguments of the amounts of initial_dependencies. The results of the callable execution for a single treatment becomes the initial amount of the stock, for that treatment.

    If initial is not a callable, it is interpreted as the initial amount for the stock.

    Using PerTreatment, a different amount or different callable can be provided for different treatments. See examples below.

  • initial_dependencies (tuple of str, optional) – Names of dependencies—i.e. names of (plain) variables or constants or other stocks or …— used as arguments for the callable initial. Might be an empty tuple, the default, either if callable initial requires no arguments, or if increment is not a callable.
Returns:

the newly-created accum

Return type:

Accum

See also

variable()
Create a non-stock variable
constant()
Create a non-stock variable whose amount does not change
stock()
Create an stock, much like a stock except that it uses the amounts of the variables in the prior period, instead of the current period
PerTreatment
for defining how an increment or initial varies from treatment to treatment

Examples

An accum that collects all the revenue to date.

>>> accum('Year', 1, 2019)

The initial amount defaults to zero.

>>> accum('Age', 1)

An accum can take a docstring-like description.

>>> accum('Year', '''the current year''', 1, 2019)

The initial amount can be different in each treatment.

>>> accum('MenuItemCount', 1, PerTreatment({'As is': 20, 'To be': 22}))

The increment can be different for each treatment.

>>> accum('MenuItemCount', PerTreatment({'As is': 1, 'To be': 2}), 20)

The increment can be a callable that uses no variables. Note the empty tuple of variables.

>>> accum('MenuItemCount', lambda: random.randint(0,2), (), 20)

The initial amount can be a callable. If the initial amount is a callable, the increment must also be a callable. Note the empty tuples.

>>> accum('MenuItemCount',
...     lambda: random.randint(15, 18), (),
...     lambda: random.randint(20, 22), ())

Variables can be provided for the increment callable.

>>> accum('Savings', lambda interest: interest, ('Interest',), 0)

Variables can be provided for the initial callable.

>>> accum('Savings',
...     lambda interest, additions: interest + additions,
...     ('Interest', 'AdditionsToSavings'),
...     lambda initial: initial,
...     ('InitialSavings',))
foreach(by_item_callable)

Return a new callable iterates across dicts or tuples.

Variables often take simple values: ints or floats. But sometimes they take more complex values: dicts or tuples. Consider a business model of a group of five restaurants, all owned by the same company. Each individual restaurant is managed differently, with its own opening and closing hours, its own table count, its own daily revenue, its own mix of party sizes. But although the variables take different values for each restaurant, they participate in the same structure. Earnings is always revenue minus cost. (This restaurant example is borrowed from a book on business modeling.)

A good approach to model the restaurants is to have each variable take a Python dict as a value, with the name of the restaurant as the key and a numeric value for each key. For example the variable DailyRevenue might take a value of {'Portia': 7489, 'Nola': 7136, 'Viola': 4248, 'Zona': 6412, 'Adelina': 4826}, assuming the restaurants are named Portia, Nola, etc.

But how should the specifier of variable DailyEarnings be modeled if both DailyRevenue and DailyCosts are dicts? Although earnings is revenue minus cost, the specifier cannot be lambda revenue, cost: revenue - cost because revenue and cost are both dicts, and subtraction is unsupported for dicts in Python.

One approach is to write a custom specifier for DailyEarnings, a Python custom function that takes the two dicts, and returns a third dict, subtracting each restaurant’s cost from its revenues. A better approach is to use foreach: foreach(lambda revenue, cost: revenue - cost). See example below.

foreach() takes a single callable that operates on individual values (e.g. the revenue and cost of a single restaurant), and returns a callable that operates on dicts as a whole (e.g. the revenue of all the restaurants as a dict, the costs of all the restaurants as a dict).

The dict that is the amount of the second variable must contain the same keys as the dict that is the amount of the first variable, or else the foreach-generated callable raises a MinnetonkaError. The second dict can contain additional keys, not present in the first dict. Those additional keys are ignored. Similarly, the third dict (if present) must contain the same keys as the first dict. And so on.

foreach() also works on tuples. For example, suppose instead of a dict, the revenue of the restaurants were represented as a tuple, with the first element of the tuple being Portia’s revenue, the second element Nola’s revenue and so on. The cost of the restaurants are also represented as a tuple. Then the specifier of DailyEarnings could be provided as foreach(lambda revenue, cost: revenue - cost), the same foreach() expression as with dicts.

The tuple that is the amount of the second variable can be shorter (of lessor length) than the dict that is the amount of the first variable. The foreach-generated callable uses the shortest of the tuples as the length of the resultant tuple.

If the first variable has an amount that is a tuple, the second variable can be a scalar, as can the third variable, etc. When encountering a scalar in subsequent amounts, the foreach-generated callable interprets the scalar as if an iterator repeated the scalar, as often as needed for the length of the first tuple.

foreach() works on Python named tuples as well. The result of the foreach-generated specifier is a named tuple of the same type as the first dependency.

foreach() can be nested, if the amounts of the dependencies are nested dicts, or nested tuples, or nested named tuples, or some nested combination of dicts, tuples, or named tuples. See example below.

foreach() can be used in defining variables, stocks, accums, or constants, anywhere that a callable can be provided.

Parameters:by_item_callable (callable) – A callable that is to be called on individual elements, either elements of a tuple or elements of a named tuple or values of a dict.
Returns:A new callable that can be called on dicts or tuples or named tuples, calling by_item_callable as on each element of the dict or tuple or named tuple.
Return type:callable

Examples

Suppose there are five restaurants. Each of the restaurants has a weekly cost and a weekly revenue. (In practice the cost and revenue would themselves be variables, not constants, and dependent on other variables, like weekly customers, order mix, number of cooks, number of waitstaff, etc.)

>>> with model(treatments=['as is', 'to be']) as m:
...     Revenue = constant('Revenue',
...         {'Portia': 44929, 'Nola': 42798, 'Viola': 25490, 'Zona': 38477,
...          'Adelina': 28956})
...     Cost = constant('Cost',
...         {'Portia': 40440, 'Nola': 42031, 'Viola': 28819, 'Zona': 41103,
...          'Adelina': 25770})

Earnings is revenue minus cost, for each restaurant.

>>> with m:
...     Earnings = variable('Earnings',
...         foreach(lambda revenue, cost: revenue - cost),
...         'Revenue', 'Cost')
>>> Earnings['']
{'Portia': 4489,
 'Nola': 767,
 'Viola': -3329,
 'Zona': -2626,
 'Adelina': 3186}

Stocks can also use foreach(). Suppose each restaurant has a stock of regular customers. Every week, some customers are delighted with the restaurant and become regulars. Every week some of the regulars attrit, growing tired with the restaurant they once frequented, or move away to somewhere else, and are no longer able to enjoy the restaurant regularly.

>>> with model(treatments=['as is', 'to be']) as m:
...     Regulars = stock('Regulars',
...         foreach(lambda add, lost: add - lost),
...         ('NewRegulars', 'LostRegulars'),
...         {'Portia': 420, 'Nola': 382, 'Viola': 0, 'Zona': 294,
...          'Adelina': 23})
...     NewRegulars = constant('NewRegulars',
...         {'Portia': 4, 'Nola': 1, 'Viola': 1, 'Zona': 2, 'Adelina': 4})
...     LostRegulars = variable('LostRegulars',
...         foreach(lambda regulars, lossage_prop: regulars * lossage_prop),
...         'Regulars', 'LossageProportion')
...     LossageProportion = constant('LossageProportion',
...         PerTreatment({'as is': 0.01, 'to be': 0.0075}))
>>> Regulars['as is']
{'Portia': 420, 'Nola': 382, 'Viola': 0, 'Zona': 294, 'Adelina': 23}
>>> m.step()
>>> Regulars['as is']
{'Portia': 419.8, 'Nola': 379.18, 'Viola': 1.0, 'Zona': 293.06,
 'Adelina': 26.77}

A variable can take an amount that is a dict of named tuples (or any other combination of dicts, named tuples, and tuples). Nested foreach calls can work on these nested variables.

In this example, menu items change over time, as items are added or removed for each restaurant. Note that Shape serves only to give other variables the right shape: a dict of restaurants with values that are instances of Course named tuples.

>>> import collections
>>> import random
>>> Course=collections.namedtuple(
...     'Course', ['appetizer', 'salad', 'entree', 'desert'])
>>> with model() as m:
...     MenuItemCount = stock('MenuItemCount',
...         foreach(foreach(lambda new, old: new - old)),
...         ('AddedMenuItem', 'RemovedMenuItem'),
...         {'Portia': Course(6, 4, 12, 7),
...          'Nola': Course(3, 3, 8, 4),
...          'Viola': Course(17, 8, 9, 12),
...          'Zona': Course(10, 4, 20, 6),
...          'Adelina': Course(6, 9, 9, 3)})
...     AddedMenuItem = variable('AddedMenuItem',
...         foreach(foreach(lambda s: 1 if random.random() < 0.1 else 0)),
...         'Shape')
...     RemovedMenuItem = variable('RemovedMenuItem',
...         foreach(foreach(lambda s: 1 if random.random() < 0.08 else 0)),
...         'Shape')
...     Shape = constant('Shape',
...         lambda: {r: Course(0, 0, 0, 0)
...             for r in ['Portia', 'Nola', 'Viola', 'Zona', 'Adelina']})
>>> MenuItemCount['']
{'Portia': Course(appetizer=6, salad=4, entree=12, desert=7),
 'Nola': Course(appetizer=3, salad=3, entree=8, desert=4),
 'Viola': Course(appetizer=17, salad=8, entree=9, desert=12),
 'Zona': Course(appetizer=10, salad=4, entree=20, desert=6),
 'Adelina': Course(appetizer=6, salad=9, entree=9, desert=3)}
>>> m.step(10)
>>> MenuItemCount['']
{'Portia': Course(appetizer=7, salad=3, entree=12, desert=4),
 'Nola': Course(appetizer=4, salad=4, entree=8, desert=4),
 'Viola': Course(appetizer=18, salad=6, entree=8, desert=13),
 'Zona': Course(appetizer=12, salad=5, entree=18, desert=9),
 'Adelina': Course(appetizer=4, salad=8, entree=7, desert=3)}
class Model(treatments, timestep=1, start_time=0, end_time=None)

A collection of variables, that can be simulated.

A model is a self-contained collection of variables and treatments. A model can be simulated, perhaps running one step at a time, perhaps multiple steps, perhaps until the end.

Typically a model is defined as a context using model(), with variables and stocks within the model context. See example below.

Parameters:
  • treatments (list of Treatment) – The treatments defined for the model. Each treatment is a different simulated scenario, run in parallel.
  • timestep (int or float, optional) – The simulated duration of each call to step(). The default is 1.
  • start_time (int or float, optional) – The first time period, before the first call to step(). Default: 0
  • end_time (int or float, optional) – The last time period, after a call to step() with to_end=True. Default: None, meaning never end

See also

model()
the typical way to create a model

Examples

Create a model with two treatments and three variables:

>>> with model(treatments=['As is', 'To be']) as m:
...     variable('Revenue', np.array([30.1, 15, 20]))
...     variable('Cost',
...         PerTreatment({'As is': np.array([10, 10, 10]),
...                      {'To be': np.array([5, 5, 20])})
...     variable('Earnings', lambda r, c: r - c, 'Revenue', 'Cost')
TIME = None

Current time in the model, accessible in a specifier. See example detailed in variable()

recalculate()

Recalculate all variables, without advancing the step.

Recalculation is only necessary when the amount of a variable (or constant or stock) is changed explicitly, outside of the model logic. The variables that depend on that changed variable will take amounts that do not reflect the changes, at least until the model is stepped. If that is not appropriate, a call to recalculate() will calculate new updated amounts for all those dependent variables.

Example

>>> with model() as m:
...    Foo = constant('Foo', 9)
...    Bar = variable('Bar', lambda x: x+2, 'Foo')
>>> Bar['']
11
>>> Foo[''] = 7

Bar still takes the amount based on the previous amount of Foo.

>>> Bar['']
11

Recalculating updates the amounts.

>>> m.recalculate()
>>> Bar['']
9
reset(reset_external_vars=True)

Reset simulation, back to the begining.

Reset simulation time back to the beginning time, and reset the amounts of all variables back to their initial amounts.

Parameters:reset_external_vars (bool, optional) – Sometimes variables are set to amounts outside the model logic. (See example below, and more examples with constant(), variable(), and stock().) Should these externally-defined variables be reset to their initial amounts when the model as a whole is reset? Default: True, reset those externally-defined variables.
Returns:
Return type:None

Examples

Create a simple model.

>>> m = model([stock('Year', 1, 2019)])
>>> m['Year']['']
2019

Step the model.

>>> m.step()
>>> m['Year']['']
2020
>>> m.step()
>>> m['Year']['']
2021

Reset the model.

>>> m.reset()
>>> m['Year']['']
2019

Change the amount of year. Year is now externally defined.

>>> m['Year'][''] = 1955
>>> m['Year']['']
1955

Reset the model again.

>>> m.reset(reset_external_vars=False)
>>> m['Year']['']
1955

Reset one more time.

>>> m.reset()
>>> m['Year']['']
2019
step(n=1, to_end=False)

Simulate the model n steps.

Simulate the model, either one step (default), or n steps, or until the model’s end.

Parameters:
  • n (int, optional) – Number of steps to advance. The default is 1, one step at a time.
  • to_end (bool, optional) – If True, simulate the model until its end time
Returns:

Return type:

None

Raises:

MinnetonkaError – If to_end is True but the model has no end time.

Examples

A model can simulate one step at a time:

>>> m = model([stock('Year', 1, 2019)])
>>> m.step()
>>> m['Year']['']
2020
>>> m.step()
>>> m['Year']['']
2021

A model can simulate several steps at a time:

>>> m2 = model([stock('Year', 1, 2019)])
>>> m2.step(n=10)
>>> m2['Year']['']
2029

A model can simulate until the end:

>>> m3 = model([stock('Year', 1, 2019)], end_time=20)
>>> m3.step(to_end=True)
>>> m3['Year']['']
2039
variable(variable_name)

Return a single variable from the model, by name.

Return a single variable—or stock or constant or accum or previous— from the model, by providing the variable’s name.

A variable is typically accessed from a model by subscription, like a dictionary value from a dictionary, e.g. modl['var']. The subscription syntax is syntactic sugar for variable().

Note that variable() returns a variable object, not the current amount of the variable. To find the variable’s current amount in a particular treatment, use a further subscription with the treatment name, e.g. modl['var']['']. See examples below.

Parameters:variable_name (str) – The name of the variable. The variable might be a plain variable, a stock, an accum, a constant, or any of the variable-like objects known by the model.
Returns:Variable
Return type:newly-defined variable with name variable_name
Raises:MinnetonkaError – If no variable named variable_name exists in the model

Examples

Create a model m with three variables, and only the default treatment.

>>> with model() as m:
...     variable('Earnings', lambda r, c: r - c, 'Revenue', 'Cost')
...     variable('Cost', 10)
...     variable('Revenue', 12)

Find the variable Cost

>>> m.variable('Cost')
variable('Cost')

… or use subscription syntax to do the same thing

>>> m['Cost']
variable('Cost')
>>> m.variable('Cost') == m['Cost']
True

Find the current amount of Cost in the default treatment.

>>> m['Cost']['']
10
class Variable

A variable whose amount is calculated from amounts of other variables.

A variable has a value—called an ‘amount’—that changes over simulated time. A single variable can take a different amount in each model treatment. The amount of a variable can be any Python object. A variable can be defined in terms of the amounts of other variables.

A variable differs from other variable-like objects (e.g. stocks) in that it keeps no state. Its amount depends entirely on its definition, and the amounts of other variables used in the definition.

A single variable can take a different amount in each model treatment. The amount of a variable in a particular treatmant can be found using subscription brackets, e.g. Earnings[‘as is’]. See examples below.

The amount of a variable can be changed explicitly, outside the model logic, e.g. Earnings[‘as is’] = 2.1. Once changed explicitly, the amount of the variable never changes again, until the simulation is reset or the amount is changed again explicitly. See examples below.

See also

variable
Create a Variable
Constant
a variable that does not vary

Examples

Find the current amount of the variable Earnings, in the as is treatment.

>>> Earnings['as is']
2.0

Change the current amount of the variable Earnings in the as is treatment.

>>> Earnings['as is'] = 2.1

Show everything important about the variable Earnings.

>>> Earnings.show()
Variable: Earnings
Amounts: {'as is': 2.1, 'To be': 4.0}
Definition: Earnings = variable('Earnings', lambda r, c: r - c, 'Revenue', 'Cost')
Depends on: ['Revenue', 'Cost']
[Variable('Revenue'), Variable('Cost')]
__getitem__(treatment_name)

Retrieve the current amount of the variable in the treatment with the name treatment_name.

Example

Find the current amount of the variable Earnings, in the as is treatment.

>>> Earnings['as is']
2.0
__setitem__(treatment_name, amount)

Change the current amount of the variable in the treatment with the name treatment_name.

Examples

Change the current amount of the variable Earnings in the as is treatment to 2.1.

>>> Earnings['as is'] = 2.1

Change the current amount of the variable Taxes in all treatments at once.

>>> Earnings['__all__'] = 2.1
all()

Return a dict of all current amounts, one for each treatment.

Example

>>> Earnings.all()
{'as is': 2.1, 'to be': 4.0}
history(treatment_name, step)

Return the amount at a past timestep for a particular treatment.

Minnetonka tracks the past amounts of a variable over the course of a single simulation run, accessible with this function.

Parameters:
  • treatment_name (str) – the name of some treatment defined in the model
  • step (int) – the step number in the past

Example

Create a model with a single variable RandomVariable.

>>> import random
>>> with model() as m:
...     RandomValue = variable(
...         'RandomValue', lambda: random.random() / 2)
>>> RandomValue['']
0.4292118957243861

Advance the simulation. RandomVariable changes value.

>>> m.step()
>>> RandomValue['']
0.39110555756064735
>>> m.step()
>>> RandomValue['']
0.23809270739004534

Find the old values of RandomVarable.

>>> RandomValue.history('', 0)
0.4292118957243861
>>> RandomValue.history('', 1)
0.39110555756064735
show()

Show everything important about the variable.

Example

>>> Earnings.show()
Variable: Earnings
Amounts: {'as is': 2.1, 'To be': 4.0}
Definition: Earnings = variable('Earnings', lambda r, c: r - c, 'Revenue', 'Cost')
Depends on: ['Revenue', 'Cost']
[Variable('Revenue'), Variable('Cost')]
class Constant

A constant, whose amount does not change.

A constant is similar to a variable except that its amount does not vary. Its amount is set on initialization, and then does not change over the course of the simulation. When the model is reset, the constant can take a new amount.

The amount of a constant can be any Python object, except a string or a Python callable. It can be defined in terms of other variables, using a callable in the definition.

A single constant can take a different amount in each model treatment. The amount of a constant in a particular treatment can be found using the subscription brackets, e.g. InterestRate[‘to be’]. See examples below.

The amount of a constant can be changed explicitly, outside the model logic, e.g. InterestRate[‘to be’] = 0.07. Once changed, the amount of the constant remains the same, until the model is reset or the amount is again changed explicitly. See examples below.

See also

constant
Create a Constant
Variable
a variable whose amount might vary

Examples

Find the current amount of the constant InterestRate, in the to be treatment.

>>> InterestRate['to be']
0.08

Change the current amount of the constant InterestRate in the to be treatment.

>>> InterestRate['to be'] = 0.075

Show everything important about the constant InterestRate.

>>> InterestRate.show()
Constant: InterestRate
Amounts: {'as is': 0.09, 'to be': 0.075}
Definition: PerTreatment({"as is": 0.09, "to be": 0.08})
Depends on: []
[]
__getitem__(treatment_name)

Retrieve the current amount of the constant in the treatment with the name treatment_name.

Example

Find the current amount of the constant InterestRate, in the to be treatment.

>>> InterestRate['to be']
0.08
__setitem__(treatment_name, amount)

Change the current amount of the variable in the treatment with the name treatment_name.

Examples

Change the current amount of the constant InterestRate in the to be treatment to 0.075.

>>> InterestRate['to be'] = 0.075

Change the current amount of the constant InterestRate in all treatments at once.

>>> InterestRate['__all__'] = 0.06
all()

Return a dict of all current amounts, one for each treatment.

Example

>>> InterestRate.all()
{'as is': 0.09, 'to be': 0.08}
history(treatment_name, step)

Return the amount at a past timestep for a particular treatment.

Minnetonka tracks the past amounts of a constant over the course of a single simulation run, accessible with this function. Of course, constants do not change value, except by explicit setting, outside of model logic. So history() serves to return the history of those extra-model changes.

Parameters:
  • treatment_name (str) – the name of some treatment defined in the model
  • step (int) – the step number in the past

Example

Create a model with a single constant InterestRate.

>>> import random
>>> with model(treatments=['as is', 'to be']) as m:
...     InterestRate = variable('InterestRate',
...         PerTreatment({"as is": 0.09, "to be": 0.08}))
>>> InterestRate['to be']
0.08

Advance the simulation. InterestRate stays the same

>>> m.step()
>>> InterestRate['to be']
0.08
>>> m.step()
>>> InterestRate['to be']
0.08

Change the amount of InterestRate explicitly.

>>> InterestRate['to be'] = 0.075

Find the old values of RandomVarable.

>>> InterestRate.history('to be', 0)
0.08
>>> InterestRate.history('to be', 1)
0.08
>>> InterestRate.history('to be', 2)
0.075
show()

Show everything important about the constant.

Example

>>> InterestRate.show()
Constant: InterestRate
Amounts: {'as is': 0.09, 'to be': 0.075}
Definition: PerTreatment({"as is": 0.09, "to be": 0.08})
Depends on: []
[]
class Previous

A previous.

A previous is a variable whose amount is that of some other variable in the prior timestep. A previous allows a reach into the past from within the model.

If the model in which the previous lives has multiple treatments, and its prior has a different amount for each treatment, so will the previous. The amount of the previous in a particular treatment can be accessed using subscription brackets, e.g. YesterdaySales[‘as is’].

See also

previous
Create a Previous
Variable
a variable whose amount is calculated from other vars

Examples

Find yesterday’s sales, when the timestep is one day.

>>> YesterdaySales['as is']
13

Show everything important about the previous YesterdaySales.

>>> YesterdaySales.show()
Previous: YesterdaySales
Amounts: {'as is': 13, 'to be': 9}
Previous variable: Sales
[variable('Sales')]
__getitem__(treatment_name)

Retrieve the current amount of the previous in the treatment with the name treatment_name.

Example

Find the current amount of the variable PriorEarnings, in the as is treatment.

>>> PriorEarnings['as is']
1.9
all()

Return a dict of all current amounts, one for each treatment.

Example

>>> PreviousEarnings.all()
{'as is': 1.9, 'to be': 2.4}
history(treatment_name, step)

Return the amount at a past timestep for a particular treatment.

Minnetonka tracks the past amounts of a previous over the course of a single simulation run, accessible with this function.

Parameters:
  • treatment_name (str) – the name of some treatment defined in the model
  • step (int) – the step number in the past

Example

Create a model with a stock Year, and a previous LastYear.

>>> with model() as m:
...     Year = stock('Year', 1, 2019)
...     LastYear = previous('LastYear', 'Year', None)

Advance the simulation ten years.

>>> m.step(10)

Find the value of both Year and LastYear in year 5.

>>> Year.history('', 5)
2024
>>> LastYear.history('', 5)
2023
show()

Show everything important about the previous.

Example

>>> YesterdaySales.show()
Previous: YesterdaySales
Amounts: {'as is': 13, 'to be': 9}
Previous variable: Sales
[variable('Sales')]
class Stock

A system dynamics stock.

In system dynamics, a stock is used to model something that accumulates or depletes over time.

At any simulated period, the stock has an amount. The amount changes over time, incrementing or decrementing at each timestep. The amount can be a simple numeric like a Python integer or a Python float. Or it might be some more complex Python object: a list, a tuple, a numpy array, or an instance of a user-defined class. In any case, the stock’s amount must support addition and multiplication. (Addition and multiplication are supported for dicts, tuples, and named tuples via foreach().)

If the model in which the stock lives has multiple treatments, the stock may have several amounts, one for each treatment. The amount of a stock in a particular treatment can be accessed using subscription brackets, e.g. Savings[‘to be’].

The amount of a stock in a treatment can be changed explicitly, outside the model logic, e.g. Savings[‘to be’] = 16000. Once changed explicitly, the amount of the stock never changes again (in that treatment), until the simulation is reset or the amount is changed again explicitly.

See also

stock
Create a Stock
Variable
a variable whose amount is calculated from other vars
Constant
a variable that does not vary
Previous
a variable that has the previous amount of some other variable
Accum
a stock-like variable that uses current amounts

Examples

Find the current amount of the stock Savings, in the to be treatment.

>>> Savings['to be']
16288.94

Change the current amount of the stock Savings in the to be treatment.

>>> Savings['to be'] = 16000

Show everything important about the stock Savings.

>>> Savings.show()
Stock: Savings
Amounts: {'as is': 14802.442849183435, 'to be': 16000}
Initial definition: 10000.0
Initial depends on: []
Incremental definition: Savings = stock('Savings', lambda i: i, ('Interest',), 10000.0)
Incremental depends on: ['Interest']
[variable('Interest')]
__getitem__(treatment_name)

Retrieve the current amount of the stock in the treatment with the name treatment_name.

Example

Find the current amount of the stock Savings, in the as is treatment.

>>> Savings['as is']
14802.442849183435
__setitem__(treatment_name, amount)

Change the current amount of the stock in the treatment with the name treatment_name.

Examples

Change the current amount of the stock Savings in the as is treatment to 2.1.

>>> Savings['as is'] = 14000

Change the current amount of the stock Taxes in all treatments at once.

>>> Savings['__all__'] = 10000
all()

Return a dict of all current amounts, one for each treatment.

Example

>>> Savings.all()
{'as is': 14090, 'to be': 16000}
history(treatment_name, step)

Return the amount at a past timestep for a particular treatment.

Minnetonka tracks the past amounts of a stock over the course of a single simulation run, accessible with this function.

Parameters:
  • treatment_name (str) – the name of some treatment defined in the model
  • step (int) – the step number in the past

Example

Create a model with a single stock Year.

>>> with model() as m:
...     Year = stock('Year', 1, 2019)
>>> Year['']
2019

Advance the simulation. Year changes value.

>>> m.step()
>>> Year['']
2020
>>> m.step()
>>> Year['']
2021

Find the old values of Year

>>> Year.history('', 0)
2019
>>> Year.history('', 1)
2020
show()

Show everything important about the stock.

Example

>>> Savings.show()
Stock: Savings
Amounts: {'as is': 14802.442849183435, 'to be': 16000}
Initial definition: 10000.0
Initial depends on: []
Incremental definition: Savings = stock('Savings', lambda i: i, ('Interest',), 10000.0)
Incremental depends on: ['Interest']
[variable('Interest')]
class Accum

A stock-like incrementer, with a couple of differences from a stock.

An accum is much like a Stock, modeling something that accumulates or depletes over time. Like a stock, an accum defines both an initial amount and an increment.

There is an important difference between a stock and an accum: an accum is incremented with the current amounts of its dependencies, not the amounts in the last period. This seemingly minor difference has a big impact: a circular dependency can be created with a stock, but not with an accum. The stock Savings can depend on Interest, which depends in turn on Savings. But this only works if Savings is a stock. If Savings is an accum, the same circular dependency is a model error.

At any simulated period, the accum has an amount. The amount changes over time, incrementing or decrementing at each period. The amount can be a simple numeric like a Python integer or a Python float. Or it might be some more complex Python object: a list, a tuple, a numpy array, or an instance of a user-defined class. In any case, the accum’s amount must support addition. (Addition is supported for dicts, tuples, and named tuples via foreach().)

If the model in which the accum lives has multiple treatments, the accum may have several amounts, one for each treatment in the model. The amount of an accum in a particular treatment can be accessed using subscription brackets, e.g. RevenueYearToDate[‘as is’].

The amount of an accum in a treatment can be changed explicitly, outside the model logic, e.g. RevenueYearToDate[‘as is’] = 1000. Once changed explicitly, the amount of the accum never changes again (in that treatment), until the simulation is reset or the amount is changed again explicitly.

See also

accum
Create an Accum
Stock
a system dynamics stock
Variable
a variable whose amount is calculated from other vars
Constant
a variable that does not vary
Previous
a variable that has the previous amount of some other variable

Examples

Find the current amount of the accum RevenueYearToDate in the cautious treatment.

>>> RevenueYearToDate['cautious']
224014.87326935912

Change the current amount of the accum RevenueYearToDate in the cautious treatment.

>>> RevenueYearToDate['cautious'] = 200000

Show everything important about the accum RevenueYearToDate

>>> RevenueYearToDate.show()
Accum: RevenueYearToDate
Amounts: {'as is': 186679.06105779926, 'cautious': 200000, 'aggressive': 633395.3052889963}
Initial definition: 0
Initial depends on: []
Incremental definition: RevenueYearToDate = accum('RevenueYearToDate', lambda x: x, ('Revenue',), 0)
Incremental depends on: ['Revenue']
[variable('Revenue')]
__getitem__(treatment_name)

Retrieve the current amount of the accum in the treatment with the name treatment_name.

Example

Find the current amount of the accum RevenueYearToDate, in the as is treatment.

>>> RevenueYearToDate['as is']
186679.06105779926
__setitem__(treatment_name, amount)

Change the current amount of the accum in the treatment with the name treatment_name.

Examples

Change the current amount of the accum RevenueYearToDate in the as is treatment to 2.1.

>>> RevenueYearToDate['as is'] = 190000

Change the current amount of the accum RevenueYearToDate in all treatments at once.

>>> RevenueYearToDate['__all__'] = 0
all()

Return a dict of all current amounts, one for each treatment.

Example

>>> RevenueYearToDate.all()
{'as is': 186679.06105779926,
 'cautious': 224014.87326935912,
 'aggressive': 633395.3052889963}
history(treatment_name, step)

Return the amount at a past timestep for a particular treatment.

Minnetonka tracks the past amounts of an accum over the course of a single simulation run, accessible with this function.

Parameters:
  • treatment_name (str) – the name of some treatment defined in the model
  • step (int) – the step number in the past

Example

Create a model with an accum and three treatments

>>> with model(treatments=['as is', 'cautious', 'aggressive']) as m:
...     RevenueYearToDate = accum('RevenueYearToDate',
...         lambda x: x, ('Revenue',), 0)
...     Revenue = variable('Revenue',
...         lambda lst, mst, w: lst + w * (mst - lst),
...         'Least', 'Most', 'Weather')
...     Weather = variable('Weather',
...         lambda: random.random())
...     Least = constant('Least',
...         PerTreatment(
...             {'as is': 0, 'cautious': 0, 'aggressive': -100000}))
...     Most = constant('Most',
...         PerTreatment(
...             {'as is': 100000, 'cautious': 120000,
...              'aggressive': 400000}))

Advance the simulation. RevenueYearToDate changes value.

>>> m.step()
>>> RevenueYearToDate['aggressive']
240076.8319119932
>>> m.step()
>>> RevenueYearToDate['aggressive']
440712.80369068065
>>> m.step()
>>> RevenueYearToDate['aggressive']
633395.3052889963

Find the old values of RevenueYearToDate

>>> RevenueYearToDate.history('aggressive', 1)
240076.8319119932
>>> RevenueYearToDate.history('aggressive', 2)
440712.80369068065
show()

Show everything important about the accum.

Example

>>> RevenueYearToDate.show()
Accum: RevenueYearToDate
Amounts: {'as is': 186679.06105779926, 'cautious': 200000, 'aggressive': 633395.3052889963}
Initial definition: 0
Initial depends on: []
Incremental definition: RevenueYearToDate = accum('RevenueYearToDate', lambda x: x, ('Revenue',), 0)
Incremental depends on: ['Revenue']
[variable('Revenue')]
class PerTreatment(treatments_and_amounts)

Specify different amounts for each treatment.

A variable’s amount can be any Python object, except a string or a callable. But how to indicate that the amount differs across the treatments? Use this class.

Parameters:treatments_and_amounts (dict) – The keys of the dict are treatment names and the value are amounts

Examples

Create a constant whose amount differs by treatment.

>>> constant('KitchenOpens',
...     PerTreatment({'As is': 17.0, 'Open early': 15.5}))

Create a variable whose amount is calculated from different expressions in each treatment.

>>> variable('KitchenCloses',
...     PerTreatment(
...         {'As is': lambda lst: lst + 15,
...          'Open early': lambda lst: lst}),
...     'LastCustomerOrders')

Indices and tables