Difference between revisions of "NovaScript Introduction"
Line 22: | Line 22: | ||
===Simulators=== | ===Simulators=== | ||
The '''Capsule''' is one type of simulator . A simulator is a container whose components are programmed to interact, creating a runnable simulation. The other NovaScript simulators are the four aggregating components described in [[Component Guide | The '''Capsule''' is one type of simulator . A simulator is a container whose components are programmed to interact, creating a runnable simulation. The other NovaScript simulators are the four aggregating components described in [[Component Guide II: Containers]]. A '''Capsule''' can contain '''Stocks''', '''Flows''', '''Terms''', '''Commands''', '''Codechips''', etc., but also any of the simulator types (a chip is just a '''Capsule''' contained in a parent '''Capsule'''). Aggregator components (called members), however, must be Capsules. | ||
Note that components such as Stocks, Terms and Flows do not contain other components as constituents. We’ll use the term ''base components'' to distinguish '''Stocks''', '''Terms''' and '''Flows''' from simulators such as '''Capsules''', '''CellMatrices''' and '''AgentVectors'''. | Note that components such as Stocks, Terms and Flows do not contain other components as constituents. We’ll use the term ''base components'' to distinguish '''Stocks''', '''Terms''' and '''Flows''' from simulators such as '''Capsules''', '''CellMatrices''' and '''AgentVectors'''. | ||
Line 28: | Line 28: | ||
Except for the top level Capsule, every simulator is the component of some other simulator. For example, Capsule A may contain a chip containing an instance of Capsule B. Capsule A may also contain one of the aggregators, and that aggregator will contain instances of Capsule C. We use the terms container and component to describe this relationship. Every simulator in a model has a container except for the top-level Capsule. | Except for the top level Capsule, every simulator is the component of some other simulator. For example, Capsule A may contain a chip containing an instance of Capsule B. Capsule A may also contain one of the aggregators, and that aggregator will contain instances of Capsule C. We use the terms container and component to describe this relationship. Every simulator in a model has a container except for the top-level Capsule. | ||
In order to actually carry out a simulation, a simulator must be associated with a clock. With | In order to actually carry out a simulation, a simulator must be associated with a clock. With one exception a simulator’s clock is used by all of its components, so that only a single top-level system clock is required. However, new clocks are introduced with Clocked Chips<!--, and when running in batch mode-->. (See [[Component Guide II: Containers#Clocked Chips|Clocked Chips]].) | ||
===Component Objects and State Objects; the ''Self'' property=== | ===Component Objects and State Objects; the ''Self'' property=== | ||
As mentioned above, components such a '''Stocks''', '''Terms''', '''Floats'''; but also simulators such as '''Capsules''', ''' | As mentioned above, components such a '''Stocks''', '''Terms''', '''Floats'''; but also simulators such as '''Capsules''', '''CellMatrices''', etc. are represented in NovaScript as JavaScript objects called ''component objects''. Component objects provide the computational mechanisms for carrying out simulations. As a simulation proceeds each of these objects provide access to its current value through its <tt>value()</tt> method. | ||
Each type of simulator object provides a way of accessing the component objects of its constituents. In the simplest case, a '''Capsule''' points to each of its components by name. '''CellMatrices''', '''AgentVectors''', '''SimWorlds''', '''NodeNetworks''' and '''NetWorlds''' also have methods that produce the component objects of their constituents. Consequently the complete state of any simulator can be found by traversing its structure to extract the value() at all the '''Stocks''', '''Flows''' and '''Terms'''. | Each type of simulator object provides a way of accessing the component objects of its constituents. In the simplest case, a '''Capsule''' points to each of its components by name. '''CellMatrices''', '''AgentVectors''', '''SimWorlds''', '''NodeNetworks''' and '''NetWorlds''' also have methods that produce the component objects of their constituents. Consequently the complete state of any simulator can be found by traversing its structure to extract the <tt>value()</tt> at all the '''Stocks''', '''Flows''' and '''Terms'''. | ||
NovaScript however, provides a simpler approach: each simulator has a <tt>Self</tt> property that references a special state object, in which base component names are bound to the current values of those components. The values in | NovaScript however, provides a simpler approach: each simulator has a <tt>Self</tt> property that references a special state object called its '''Scope''', in which base component names are bound to the current values of those components. The values in the Scope change as the simulation progresses. If a simulator is not at the top-level, then the Scope of its container is the value of the property <tt>Super</tt><ref>These names are chosen for historic reasons, and also because they are not likely to conflict with the names of some actual components.</ref>. One can follow the chain of container state objects all the way to the top-level by using <tt>Super</tt>, <tt>Super.Super</tt>, etc. | ||
===Scenarios=== | ===Scenarios=== | ||
When you capture a visual model Numerus creates a set of special objects called scenarios that define every element of the current project. Each scenario describes either a '''Capsule''', graph, table, cell matrix, or some other complex entity. Since we are actually writing JavaScript, each scenario is in fact a JavaScript object with various fields containing the descriptions of constituent parts. Included are the component definitions that you provide when creating the model. When a project is loaded, these scenarios provide the blueprints for | When you capture a visual model Numerus creates a set of special objects called scenarios that define every element of the current project. Each scenario describes either a '''Capsule''', graph, table, cell matrix, or some other complex entity. Since we are actually writing JavaScript, each scenario is in fact a JavaScript object with various fields containing the descriptions of constituent parts. Included are the component definitions that you provide when creating the model. When a project is loaded, these scenarios provide the blueprints for constructing the actual NovaScript objects. When a NovaScript program is run, the objects come to life and produce the expected simulation. | ||
For those familiar with object oriented programming, the scenario serves as a class definition for the | For those familiar with object oriented programming, the scenario serves as a class definition for the | ||
Line 48: | Line 48: | ||
A NovaScript property is a symbol that represents a single constant value throughout the execution of a Numerus program. Properties can be defined in several ways, as detailed below. | A NovaScript property is a symbol that represents a single constant value throughout the execution of a Numerus program. Properties can be defined in several ways, as detailed below. | ||
[[Component Guide | [[Component Guide II: Containers|Aggregating components]] introduce special properties and primops. Those properties and primops may be used in '''Capsules''' contained by the aggregator, but are generally not meaningful outside of the aggregator environment, and their use may cause errors. To distinguish those from properties and primops that may be used anywhere, we’ll refer to the former as ''restricted'' and the latter as ''universal''. When describing a primop, we will always distinguish it as either universal or restricted. | ||
Lists of all properties and primops, both universal and restricted to one or more aggregator, are found in the [[Primitive Operators and Properties | Primop Reference]]. | Lists of all properties and primops, both universal and restricted to one or more aggregator, are found in the [[Primitive Operators and Properties | Primop Reference]]. |
Revision as of 19:25, 11 October 2019
What is NovaScript?
All running Numerus simulations are expressed in a language called NovaScript. Even in the simplest stock- flow model, you are already writing NovaScript code when you enter expressions for initial Stock values, Flows, and Terms (we’ll refer to these as component definitions). For these models, however, the code is generally restricted to simple numerical expressions. You will see that to express the relationships required of complex models your definitions will necessarily include a broader set of expressions.
NovaScript is an extension of a well-known and widely used language called JavaScript. This means that NovaScript uses JavaScript syntax for all of its code; moreover, any legal JavaScript program is also a NovaScript program. This includes the code used for initial Stock values, Flows and Terms. Fortunately, there are many good sources for learning to program in JavaScript. Please read the introduction to JavaScript contained here before continuing with this section.
A complete Numerus model is expressed in NovaScript. You create the program for this model when you click the Launch button. Numerus is able to construct the scaffold for your model using the structure of the visual elements placed on the canvas. As author you are expected to provide the details connecting the various components through the component expressions. In systems dynamics models component expressions generally operate solely on numerical data; i.e., they are simple arithmetic expressions.
As you extend your use of Numerus into more complex applications the coding used to link components will also become more complex. This complexity manifests along the following dimensions:
- The data structures used. Simple applications can rely on simple numerical data. For more advanced use you will need to introduce arrays and objects, and may also want to create your own functions.
- The program structures required. Multi-line computations involving loops and conditionals generally accompany the use of data structures such as arrays and objects.
- The use of primitive operators. Models using Numerus aggregating components rely heavily on primitive operators, or primops. You will need to become familiar with these primops and how they provide necessary information to the constituents of the aggregators.
As mentioned above, NovaScript is embedded in JavaScript. Precisely, this means that NovaScript is JavaScript with additional functionality and special objects. The concept of extending the JavaScript core with special objects is not new. In fact, we’ve seen that JavaScript itself does this with the Math object, used to bundle together a substantial set of mathematical functions [1]. In order for JavaScript to control the behavior of a Web browser, the JavaScript environment is extended with a Document [2] object that contains fields specifically designed for Web browser functionality.
NovaScript similarly extends JavaScript with a set of object specifically engineered to express the design of Numerus. Thus there are Stock, Term and Flow objects corresponding to those components in a visually rendered Numerus model. We call these component objects. Moreover, there is a Capsule representing an entire model or submodel, referencing all of its constituent parts. There are also Table, Graph, Slider and Spinner objects for input/output, and a single NovaScript object type, VPlugin, acting as a surrogate for every type of plug-in
Simulators
The Capsule is one type of simulator . A simulator is a container whose components are programmed to interact, creating a runnable simulation. The other NovaScript simulators are the four aggregating components described in Component Guide II: Containers. A Capsule can contain Stocks, Flows, Terms, Commands, Codechips, etc., but also any of the simulator types (a chip is just a Capsule contained in a parent Capsule). Aggregator components (called members), however, must be Capsules.
Note that components such as Stocks, Terms and Flows do not contain other components as constituents. We’ll use the term base components to distinguish Stocks, Terms and Flows from simulators such as Capsules, CellMatrices and AgentVectors.
Except for the top level Capsule, every simulator is the component of some other simulator. For example, Capsule A may contain a chip containing an instance of Capsule B. Capsule A may also contain one of the aggregators, and that aggregator will contain instances of Capsule C. We use the terms container and component to describe this relationship. Every simulator in a model has a container except for the top-level Capsule.
In order to actually carry out a simulation, a simulator must be associated with a clock. With one exception a simulator’s clock is used by all of its components, so that only a single top-level system clock is required. However, new clocks are introduced with Clocked Chips. (See Clocked Chips.)
Component Objects and State Objects; the Self property
As mentioned above, components such a Stocks, Terms, Floats; but also simulators such as Capsules, CellMatrices, etc. are represented in NovaScript as JavaScript objects called component objects. Component objects provide the computational mechanisms for carrying out simulations. As a simulation proceeds each of these objects provide access to its current value through its value() method.
Each type of simulator object provides a way of accessing the component objects of its constituents. In the simplest case, a Capsule points to each of its components by name. CellMatrices, AgentVectors, SimWorlds, NodeNetworks and NetWorlds also have methods that produce the component objects of their constituents. Consequently the complete state of any simulator can be found by traversing its structure to extract the value() at all the Stocks, Flows and Terms.
NovaScript however, provides a simpler approach: each simulator has a Self property that references a special state object called its Scope, in which base component names are bound to the current values of those components. The values in the Scope change as the simulation progresses. If a simulator is not at the top-level, then the Scope of its container is the value of the property Super[3]. One can follow the chain of container state objects all the way to the top-level by using Super, Super.Super, etc.
Scenarios
When you capture a visual model Numerus creates a set of special objects called scenarios that define every element of the current project. Each scenario describes either a Capsule, graph, table, cell matrix, or some other complex entity. Since we are actually writing JavaScript, each scenario is in fact a JavaScript object with various fields containing the descriptions of constituent parts. Included are the component definitions that you provide when creating the model. When a project is loaded, these scenarios provide the blueprints for constructing the actual NovaScript objects. When a NovaScript program is run, the objects come to life and produce the expected simulation.
For those familiar with object oriented programming, the scenario serves as a class definition for the creation of NovaScript objects. As we shall see below, the Numerus Codechip component is a tool for method definition in such a system.
Universal Properties and Primops
A NovaScript property is a symbol that represents a single constant value throughout the execution of a Numerus program. Properties can be defined in several ways, as detailed below.
Aggregating components introduce special properties and primops. Those properties and primops may be used in Capsules contained by the aggregator, but are generally not meaningful outside of the aggregator environment, and their use may cause errors. To distinguish those from properties and primops that may be used anywhere, we’ll refer to the former as restricted and the latter as universal. When describing a primop, we will always distinguish it as either universal or restricted.
Lists of all properties and primops, both universal and restricted to one or more aggregator, are found in the Primop Reference.
Special NovaScript objects
NovaScript also implements several special objects, including the Clock object, Coord object (which models a pair of matrix coordinates with fields for row and column) and RunData object, which contains the history of a Stock’s value over an entire run.
Extending the model with code
In simple models your coding responsibility is generally limited to the arithmetic expressions used for component expressions; all other programmatic content is expressed through the semantics of the components themselves; i.e., what they represent and how they operate. One part of extending Numerus is introducing more complex components (e.g. aggregators and plug-ins). However, a second mode of extension is through algorithms and other textually programmed computations that go beyond simple arithmetic expressions. Numerus has several ways of integrating new code into the existing visual structure. The most important of these is the Codechip. A Codechip is like a new component designed to operate on the content of a particular model. However, many Codechips express computations as universal as Numerus’s hard-wired components, and so can be reused in multiple settings. Codechips are described in detail here.
Other ways of extending the model with code by using more elaborate code for component expressions (see below), and with code entered into the component's Property Pane. The latter is used to define global constants, variables, and functions; and local properties, variables and methods.
Component Expressions
Component expressions are often a single line of mathematical code; e.g.
rate * population
or
(TIME() < 100) ? x : y
You may, however, use any sequence of commands, separated by semicolons, followed by an expression:
<command 1>; <command 2>; ... <command n>; <expressions>
This is most often seen when using print statements for debugging (See Section 13):
var ans = rate * population; print(ans); ans
Using the Programming Window
Each submodel has a unique Programming Window, together with its unique Modeling Canvas and Dashboard. This window may contain up to 4 segments of code, as shown at right . The segments are determined by the placement of the labeled dashed lines, as shown (4 or morel dashes to the left of the label are required; any number may follow). Not all segments are required, and, except for the Global segment, they may be in any order. Any legal NovaScript code can appear in the Global segment. The remaining segments must follow a strict form that resembles the definition of an object constant:
<name 1>: <value 1>, <name 2>: <value 2>, ...
Note that the final comma is required. Examples will be given below.
- Global segment
- Code in this section transfers exactly as written into the NovaScript program upon capture. This code is executed when that program is loaded, and so should consist of global constants, functions and any required initialization steps. Global segment code can appear with any Capsule, but since the code is global it can be referenced from any Capsule. Stylistically, it is probably best to only include global code in the top level model.
- Properties segment
- This and the remaning segments introduce local bindings that are only visible in the current submodel. Properties are identifiers that are bound to values at the beginning of a run and do not change throughout the run. Here is an example properties segment:
init_x: cols * RANDOM(), init_y: rows * RANDOM(), population: 50,
Properties may also be created using Term components in which the P roperty box has been checked. Local variable segment: Local variables are similar to properties, however their values may be changed during the run of the program. The format of their declaration is the same as that of properties:
u: 100, v: 3.14,
In this case 100 and 3.14 are initial values for u and v, respectively. Stock components generally play the role of local variables in model designs, however it can be convenient to define a few local variables to facilitate communication among interacting Capsules. Local variable assignment should only occur in a post-update Command. Methods: Methods are functions local to the Capsule. An example definition would be fact:
function(n){if (n == 0) return 0; else return fact(n-1);},
Methods may refer to Capsule components, properties or local variables. The role of method has been subsumed by the Codechip, and so it is only being included here for completeness. You should not need to define any
Clock Objects
The clock’s function is to keep track of model time and to sequence the cycle of strobes that update the simulation. The clock is programmed with values for start and end times, update interval (dt), and integration method. Most of the time a single system clock suffices, maintaining a uniform synchronized processing environment.
A chip designated as a Clocked Chip introduces a new clock with its own parameter settings for use with the chip’s Capsule. Each strobe on the clock of the chip’s container corresponds to a complete run of the chip’s clock. The effect is to synchronously subdivide the container’s update interval.
Clocked Chips are particularly useful for sensitivity analysis, and to help facilitate this Numerus provides a batch mode, which creates the necessary Clocked Chip structure for repeated runs over sets of parameters.
Notes
- ↑ Similarly, there is a JavaScript Date object used to contain date and day-of-week data.
- ↑ For this reason, the JavaScript Web browser environment is often called the document object model, or DOM.
- ↑ These names are chosen for historic reasons, and also because they are not likely to conflict with the names of some actual components.