Difference between revisions of "Numerus Simulation Architecture"
(33 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
Numerus supports a multi-level architecture comprised of '''Simulators'''. The lowest level atomic simulator is called a '''Capsule''', and it is composed of simulation '''components''' (Stocks, Flows, etc.). | Numerus supports a multi-level architecture comprised of '''Simulators'''. The lowest level atomic simulator is called a '''Capsule''', and it is composed of simulation '''components''' (Stocks, Flows, etc.). '''Containers''' are component simulators that contain Capsule instances as elements. | ||
A Numerus model is called a '''project'''. At a minimum a project contains a top-level Capsule instance called '''main'''. The ''main'' Capsule will contain components, some of which may be Containers. Each Container contains 1 or more instances of some Capsule<ref>or Agent, Cell, or Node, which are extended versions of the Capsule for use with specific Containers</ref>. Such a Capsule may include its own Containers among its components, and so we obtain a hierarchical structure<ref>Using this approach to obtain a hierarchy of submodels is described [[Modeling_in_Numerus#capsule|here]]. More complex hierarchies are obtained when using one or more aggregators.</ref>. | |||
== | ==Simulators and Aggregators== | ||
The simplest Container is the '''Chip''', which wraps a Capsule for insertion as a component in a parent Capsule. Other containers are called '''aggregators''' since they bind together multiple Capsule elements in a specific topology. Currently there are 5 aggregators: '''CellMatrix''', '''AgentVector''', '''SimWorld''', '''NodeNetwork''' and '''NetWorld'''. Each of these is described in detail [[Component_Guide_II:_Containers|elsewhere]]. The Capsule type used in each aggregator is an extension to the fundamental Capsule type, designed to complement the topology introduced by that aggregator. For example, the contents of a CellMatrix are of a Cell type which is a Capsule that is aware of its row/column location. | |||
Consequently, the unadorned reference ''pop'' is ambiguous: do we mean the object or the value? In order to make this precise we introduce the concept of '''scope'''. Each simulator (Capsule, AgemtVector, CellMatrix, etc.) has a ''scope'' object which is contained in its scope property. | ==Scope== | ||
===Components and Scope=== | |||
Every value-producing component (Stock, Term, Flow, etc.) is part of the architecture in two ways. First there is the component object itself which holds the machinery for producing its value, and then there is the value that is produced. So if a particular component ''pop'' is part of some Capsule, we might refer to ''pop'' either to obtain its current value (such references are almost always used in the equations defining the model); or we might need to obtain a reference to the component object to, say, look up an old value or access some parameter<ref>Given a component object ''c'', its current value is obtained using <code>c.value()</code></ref>. While the latter is uncommon, such situations occasionally arise. | |||
Consequently, the unadorned reference ''pop'' is ambiguous: do we mean the object or the value? In order to make this precise we introduce the concept of '''scope'''. Each simulator (Capsule, AgemtVector, CellMatrix, etc.) has a ''scope'' object which is contained in its ''scope'' property. The scope is a property list<ref>A ''property list'' associates property names, which are strings, with values. For example, if ''P'' is a property list containing values for properties ''a'', ''b'', and ''c'', then the notation <code>P.a</code>, <code>P.b</code>, and <code>P.c</code> is used to extract the property values from the list. Alternatively we could use <code>P["a"]</code>, <code>P["b"]</code> and <code>P["c"]</code>.</ref> mapping the names of all components in the Simulator to their current values. The scope also contains references to primitive operators (primops) and properties germane to that Simulator. | |||
The scope also contains a special property, '''_cap_'''<ref>in Nova 2 this was called '''Self'''</ref> referring to the Simulator for which it ths scope. For example, if ''Y'' is the scope object for Simulator ''X'', then <code>Y._cap_</code> will produce X. Thus we have a way of going from Simulator to scope (using the "scope" property) and from scope to Simulator (using the "_cap_" property). This provides a means for retrieving either the component object or its value from any component in a Capsule. | |||
Example: Assume Capsule ''C'' contains components ''a'', ''b'' and ''c'', with current values 10, 20 and 30, respectively. Let ''CS'' be the scope of ''C''. Then | |||
C.scope == CS | |||
CS._cap_ == C | |||
CS.a == 10 | |||
CS.b == 20 | |||
CS.c == 30 | |||
C.a == a | |||
C.b == b | |||
C.c == c | |||
CS._cap_.a == a | |||
CS._cap_.a.value() == 10 | |||
etc. | |||
===Scope and Formulas=== | |||
The "connective tissue" that links the components in a model is comprised of algebraic formulas or program code that compute values of components using the values of other components. A reference to a component in such a formula should represent its scope value rather than its component value. Thus we have the following rule: | |||
<big>Every equation, formula or algorithm used to compute the value of a component does so with respect to the scope of the Capsule instance containing that component.</big> | |||
So, for example, if we evaluate the formula <code>3*a+4</code> in the previous example, the result will be 34, since the value of ''a'' is obtained using the scope ''CS''. | |||
===Scope and the Console=== | |||
When Numerus is launched an interactive Javascript console is provided to facilitate interactions with the simulation. This console uses the scope of the ''main'' Capsule for evaluating expressions. Thus, if <code>3*a+4</code> is typed into console when the Capsule C is ''main'', the result will again be 34. | |||
===Computing with Simulators=== | |||
Much of the power of Numerus comes from its extended programming landscape. Numerus formulas may be extended algorithms that reference objects from various parts of the model. For example, using a Numerus primitive operator, a Cell in a CellMatrix may obtain a list of its ''neighbors''; i.e. all other Cells that are within a given distance from that Cell. However, to be most useful <em>this is not a list of the Cell objects, but rather a list of their scopes</em>. This way the values of components in these neighbors can be readily accessed.<ref>Of course, the component object can be retrieved if needed using the ''_cap_'' property</ref>. This makes for a more natural programming style since component values are generally of greatest interest. | |||
The descriptions of those primops that collect sets of Simulators are careful to point out that it is actually the scope objects of those Simulators that are being collected. | |||
==Notes== |
Latest revision as of 19:45, 9 June 2018
Numerus supports a multi-level architecture comprised of Simulators. The lowest level atomic simulator is called a Capsule, and it is composed of simulation components (Stocks, Flows, etc.). Containers are component simulators that contain Capsule instances as elements.
A Numerus model is called a project. At a minimum a project contains a top-level Capsule instance called main. The main Capsule will contain components, some of which may be Containers. Each Container contains 1 or more instances of some Capsule[1]. Such a Capsule may include its own Containers among its components, and so we obtain a hierarchical structure[2].
Simulators and Aggregators
The simplest Container is the Chip, which wraps a Capsule for insertion as a component in a parent Capsule. Other containers are called aggregators since they bind together multiple Capsule elements in a specific topology. Currently there are 5 aggregators: CellMatrix, AgentVector, SimWorld, NodeNetwork and NetWorld. Each of these is described in detail elsewhere. The Capsule type used in each aggregator is an extension to the fundamental Capsule type, designed to complement the topology introduced by that aggregator. For example, the contents of a CellMatrix are of a Cell type which is a Capsule that is aware of its row/column location.
Scope
Components and Scope
Every value-producing component (Stock, Term, Flow, etc.) is part of the architecture in two ways. First there is the component object itself which holds the machinery for producing its value, and then there is the value that is produced. So if a particular component pop is part of some Capsule, we might refer to pop either to obtain its current value (such references are almost always used in the equations defining the model); or we might need to obtain a reference to the component object to, say, look up an old value or access some parameter[3]. While the latter is uncommon, such situations occasionally arise.
Consequently, the unadorned reference pop is ambiguous: do we mean the object or the value? In order to make this precise we introduce the concept of scope. Each simulator (Capsule, AgemtVector, CellMatrix, etc.) has a scope object which is contained in its scope property. The scope is a property list[4] mapping the names of all components in the Simulator to their current values. The scope also contains references to primitive operators (primops) and properties germane to that Simulator.
The scope also contains a special property, _cap_[5] referring to the Simulator for which it ths scope. For example, if Y is the scope object for Simulator X, then Y._cap_
will produce X. Thus we have a way of going from Simulator to scope (using the "scope" property) and from scope to Simulator (using the "_cap_" property). This provides a means for retrieving either the component object or its value from any component in a Capsule.
Example: Assume Capsule C contains components a, b and c, with current values 10, 20 and 30, respectively. Let CS be the scope of C. Then
C.scope == CS CS._cap_ == C CS.a == 10 CS.b == 20 CS.c == 30 C.a == a C.b == b C.c == c CS._cap_.a == a CS._cap_.a.value() == 10
etc.
Scope and Formulas
The "connective tissue" that links the components in a model is comprised of algebraic formulas or program code that compute values of components using the values of other components. A reference to a component in such a formula should represent its scope value rather than its component value. Thus we have the following rule:
Every equation, formula or algorithm used to compute the value of a component does so with respect to the scope of the Capsule instance containing that component.
So, for example, if we evaluate the formula 3*a+4
in the previous example, the result will be 34, since the value of a is obtained using the scope CS.
Scope and the Console
When Numerus is launched an interactive Javascript console is provided to facilitate interactions with the simulation. This console uses the scope of the main Capsule for evaluating expressions. Thus, if 3*a+4
is typed into console when the Capsule C is main, the result will again be 34.
Computing with Simulators
Much of the power of Numerus comes from its extended programming landscape. Numerus formulas may be extended algorithms that reference objects from various parts of the model. For example, using a Numerus primitive operator, a Cell in a CellMatrix may obtain a list of its neighbors; i.e. all other Cells that are within a given distance from that Cell. However, to be most useful this is not a list of the Cell objects, but rather a list of their scopes. This way the values of components in these neighbors can be readily accessed.[6]. This makes for a more natural programming style since component values are generally of greatest interest.
The descriptions of those primops that collect sets of Simulators are careful to point out that it is actually the scope objects of those Simulators that are being collected.
Notes
- ↑ or Agent, Cell, or Node, which are extended versions of the Capsule for use with specific Containers
- ↑ Using this approach to obtain a hierarchy of submodels is described here. More complex hierarchies are obtained when using one or more aggregators.
- ↑ Given a component object c, its current value is obtained using
c.value()
- ↑ A property list associates property names, which are strings, with values. For example, if P is a property list containing values for properties a, b, and c, then the notation
P.a
,P.b
, andP.c
is used to extract the property values from the list. Alternatively we could useP["a"]
,P["b"]
andP["c"]
. - ↑ in Nova 2 this was called Self
- ↑ Of course, the component object can be retrieved if needed using the _cap_ property