Manual

Building a model consists of several different steps:

  • Conceptualizing the model
  • Diagraming the model in Insight Maker
  • Adding equations to the model
  • Running the model and observing the results
  • Carrying out sensitivity testing and model verification and validation activities

Not all these steps will be carried out for every model, but in general they will occur in an iterative cycle where feedback from a later step is used to adjust the product from an earlier step.

The fundamental building blocks of any Insight Maker model are a set of primitives. Primitives are basic tools provided by Insight Maker and may be used to simulate virtually any kind of system. There are a number of built-in primitives including: Stocks, Flows, Variables, Converters, and Links.

Model Diagramming

Insight Maker enables you to build models graphically and it supports a rich set of diagramming features. In fact, many Insight Maker models consist only of the diagram itself without a simulation component.

You can add elements to your model by selecting "Add Primitive" in the toolbar then choosing your desired primitive type.

Another way to create a primitive is to right-click on the model canvas. A contextual menu will open that lets you select a primitive to insert into the model.

There are many different types of primitives. Some — Stocks, Flows, Converters — have specific meaning and play roles in simulations. Others — Text, Pictures — are general primitives that you can use to illustrate your model but do not play a role in simulations. You can learn more about primitives here

Once you have added primitives to your model, you can connect them together using Flows, Transitions and Links. Flows and Transitions have a very specific simulation meaning and can only be used to connect certain types of primitives (Stocks for Flows and States for Transitions). Links on the other hand can be used to connect any primitives.

To create a Link, make sure "Links" is selected in the toolbar.

Then, hover your mouse over a primitive. A blue arrow will appear in the middle of the primitive that you can click and drag to another primitive. Let go of the mouse, and a Link will be created.

Insight Maker has many styling options you can use to configure the primitives in your model. You can adjust colors, fonts and more. You can also assign a picture to different primitives. Insight Maker has a large library of built-in pictures, but you can add your own by linking to the URL of a picture that has been uploaded to the internet.

Ghosting

As a model becomes more and more complex in Insight Maker, the number of connections between different components can start to become daunting. For complex models, Links and Flows may criss-cross each other in many places leading to a "spaghetti"-like model appearance that can be difficult to follow. Ghosting is one tool that can be used to combat this graphical complexity and to keep even the most complex models straightforward.

In short, ghosting allows you to make a reference to, or a Ghost of, a primitive in your model. The Ghost can be moved around in your model independently of the original primitive or its Source. This Ghost isn't a duplicate of the Source primitive (you don't now have two separate primitives) instead it is more of a mirror: it references the Source primitives value and state at all times. Any primitive that is connected to the Ghost primitive is automatically connect to the Ghost's Source and vice versa.

In Insight Maker, Ghosts of primitives are shown with a partially transparent graphical style. The above figure shows one example of a Ghosted primitive. If the Effect of EL LOS primitive had been connected directly to the EL LOS Product primitive, a Link would have to been made that cut across many other parts of the model. By using a Ghost of the primitive instead, we have achieved the same effect but the model diagram is now much cleaner.


Keyboard Shortcuts

There are a number of Shortcut Keys defined for Insight Maker.

  • Control/Command + B Bold the style of the currently selected primitives.
  • Control/Command + I Italicize the style of the currently selected primitives.
  • Control/Command + U Underline the style of the currently selected primitives.

  • Control/Command + C Copy the selected primitives. Note that Insight Maker does not actually have access to your system's clipboard so you can only Copy and Paste within the context of a single Insight.
  • Control/Command + V Paste whatever is on the local clipboard.
  • Control/Command + X Cut the currently selected primitives to the local clipboard.
  • Delete Deletes the currently selected Primitives.

  • Control/Command + Z Undo the previous command.
  • Control/Command + Y Redo the previous command.

  • Control/Command + F Opens the Find & Replace dialogue which will allow you to search for text in Primitive Labels, Notes and Values. You may specify if the search is to be case sensitive.
  • Control/Command + G Find Next instances of specified text.

  • Control/Command + ] Step forward when in Storytelling mode.
  • Control/Command + . Open the Note Editor dialogue.
  • Control/Command + L Open the Time Settings dialogue.
  • Control/Command + K Switch the canvas to Scratchpad mode so you can draw on it.
  • Control/Command + P Create a printer friendly image in a new window.
  • Control/Command + S Save the insight. Note that if it hasn't been saved at least once the description dialogue will open. You typically don't need the Save shortcut because Insight Maker does an automatic Save after each change you make.
  • Control/Command + Enter Run the simulation.

  • Right Click Open a drop down from which you can select primitives to be created at the current mouse pointer location.
  • Option Click on a Link Primitive Create a waypoint you can move to curve the link.

Equations and Formulae

Many primitives allow you to enter equations that determine their values or behavior. To access the equation editor for a primitive, hover your mouse over the primitive and click the "=" sign that appears. You can also use the configuration panel on the right side of the main window to edit the equation when the primitive is selected.

The equation editor allows you to type in an equation. On the bottom of the editor are a list of functions built into Insight Maker. If you hover over a function, a tooltip will describe what it does. On the right side of the editor are a list of other primitives this primitive can reference. You can click on one of these to insert it into the equation.

These equations can be arbitrary mathematical or programming expressions. For instance, take the example of the following equation:

Sin(Years()*2) + [Rain Flow]^0.05

This example displays a number of features that the Insight Maker equation engine supports:

  • ‘Years’ refers to the current simulation time as measured in years.
  • ‘[Rain Flow]’ refers to the value of another primitive. Primitives can reference each other using this square-bracket notation.
  •  ‘Sin’ applies the trigonometric sine function.
  • ‘+’, ‘*’, and ‘^’ are all standard mathematical operators representing addition, multiplication and exponentiation respectively.

There are a wide range of built-in functions you can use to develop equations and many advanced programming features that can also be utilized.

Built-In Functions

In addition to standard algebraic and logical operators, Insight Maker has many powerful built-in functions. The following is a list of these functions along with descriptions and sample usages.


Advanced Equations

It would be useful to discuss some of the more advanced programming constructs that Insight Maker's equation engine supports. These advanced constructs are particularly useful for developing complex logic for Macros or Agent Based Modeling.

Basic Programming

Defining Variables

Insight Maker allows the definition and modification of variables (in the programatic sense, not in sense of "Variable" primitives) using the "<-" operator. For instance, the following code will create two variables — x and xSquared — which will be 20 and 100 respectively at the end of the code's evaluation.

x <- 10
xSquared <- x^2 # xSquared = 100
x <- x*2 # x = 20

Insight Maker uses block scoping so variables first declared within a block will not be accessible outside that block.

Comments

Insight Maker supports several forms of comments. Comments are parts of equations that will not be evaluated by the equation engine:

  • "#" will comment out the rest of the line:
    1+2^3 # this is ignored
  • "//" will comment out the rest of the line:
    1+2^3 // this is ignored
  • "/*" and "*/" will comment out a region:
    1+/* this is ignored */2^3

Defining Functions

Insight Maker supports several forms for function definition. One is a short form that is shown below:

myFn(a, b, c) <- sin((a+b+c)/(a*b*c))

And the second is a longer form that can create multi-line functions:

function myFn(a, b, c) 
   numerator <- a+b+c 
   denominator <- a*b*c 
   sin(numerator/denominator) # the last evaluated expression is returned from a function, you may also use the 'return' statement
end function

Functions also support default parameter values:

takePower(a, b = 2) <- a^b

takePower(2, 1) # = 2
takePower(3, 3) # = 27
takePower(3) # = 9

If Then Else Statements

In addition to Insight Maker's standard ifThenElse() function, a multi-line form is also supported:

if x > 10 then
   # do something
else if x < 5 then
   # do something else 
else
   # some other action
end if 

You may have as many "else-if"clauses as desired and the final "else" clause is optional.

While Loops

While loops allow the repetition of expressions multiple times until as long as a logical condition is satisfied. For example:

x <- 10
while x < 20
   x <- x +x/10
end loop
x # = 21.436

For Loops

For loops repeat some code a fixed number of times. For instance:

total <- 0 
for x from 1 to 10 
   total <- total + x 
end loop
total # = the sum of the numbers from 1 to 10

An optional "by" control parameter can be specified to change the step to some other value than one:

total <- 0 
for x from 1 to 9 by 2
   total <- total + x
end loop
total # = the sum of the odd numbers from 1 to 9

A special form of the for loop, the for-in loop, exists for iterating across all elements of a vector:

total <- 0 
for x in {1, 2, -5, 10}
   total <- total + x
end loop 
total # sums up the elements in the vector

Returning Values

A return statement may be used to return values from functions or equations. If a return statement is not used, the last evaluated expression will automatically be returned.

For example:
1+1
2+2
3+3 # 6 will be returned by this equation

Or:

1+1
return 2+2 # 4 will be returned by this equation
3+3 # this won't be evaluated

Or:

if 10 > 20 then
   1*2
else
   2*2 # 4 is returned from both the if-statement and then returned for the expression overall
end if

Error Handling

Insight Maker uses an exception mechanism to handle errors. For instance, say you attempted to access an element of a vector that does not exist:

{1, 4, 9}{5} # There is no element '5' in the vector!

When this occurs, Insight Maker throws what is called an "exception". An exception is basically an error that will propagate up through the model ultimately aborting the simulation unless something handles the error. Handling the error is known as "catching" the error. You can catch errors in your equations using a Try-Catch block. An example of a Try-Catch block is below:

Try
   x <- getVector()
   mean(x)
Catch err
   0
End Try

What this equation does is to first attempt to execute the code finding the mean of the vector. If that code executes successfully, everything between the Catch and the End Try are skipped and not evaluated. But, something very interesting happens if an error occurs when we attempt to calculate the mean.

Supposed the getVector function returns a vector without any elements. The mean of an empty vector is undefined and Insight Maker throws an exception when this occurs. Normally, this would terminate your simulation. However, when an exception occurs in a Try-Catch block, the exception can be "caught" by the second part of the block.

If an exception occurs in our example, the exception is assigned to the variable immediately following the Catch. In this case the variable is err and if we tried to take the mean of an empty vector, err might be assigned the string "Must have at least one element to determine the mean of a vector.". Next the code following the Catch is executed. In our example here, this code is simple and just returns 0, but it could be more complex. Thus, our illustrative example attempts to calculate the mean of a vector; and if it cannot calculate the mean, it returns 0 instead.

In addition to Insight Maker's many built-in exceptions you can also create your own exceptions using the Throw keyword. For example we could do the following:

x <- getVector()
If x.length() = 0 then
   throw "The length of x must be greater than 0."
end if

Your custom exceptions will be handled just like regular Insight Maker exceptions.

Destructuring Assignment

Insight Maker supports destructuring assignment. Though the name is fancy, the concept is simple. Basically, destructuring assignment provides a straightforward way to assign the elements of a vector to a set of variables. For example:

x, y <- {10, 20}
x # = 10
y # = 20

Functional Programming

Functional programming is a approach to programming that focuses on the use of functions rather than variables and procedural logic. Insight Maker supports functional programming as its functions are first class objects that can be created on the fly, assigned to variables, and returned as the results of other functions.

For instance, we could take the built in Mean() function, assign it to a variable and then apply it to a set of numbers:

myFunction <- mean myFunction(1, 2, 3) # = 2

Similarly, we could use the Map function with a vector of functions to calculate summary statistics for a set of data values:

{Min, Median, Max}.Map(x(7, 5, 8, 1, 6)) # = {1, 6, 8}

Anonymous Functions

Generally, when programming a function is given a name when it is created and it can later be referred to using that name. In functional programming, a key tool are anonymous functions: functions created without a name. Anonymous functions are defined much the same way as regular functions, but without an explicit name. For instance, the following creates an anonymous function and assigns it to the variable f:

f <- function(x,y)
   Sqrt(x^2+y^2)
end function

f(3, 4) # = 5

There is also a shorthand syntax available for single line anonymous functions:

f <- function(x,y) Sqrt(x^2+y^2)

f(3, 4) # = 5

Anonymous functions are very useful when using functions like Map() and Filter(). For instance:

{1, 2, 3}.map(function(value)
   cos(value^2)
end function)

Closure

Closure is a key tool for functional programming. Closure is a bit of a technical concept which basically means that functions declared within a scope continue to have access that scope even after it has been released. Let us look at an example that uses closure to generate counter functions:

function MakeCounter()
   countTally <- 0
   function()
      countTally <- countTally+1
      countTally
   end function
end function

c1 <- MakeCounter()
c2 <- MakeCounter()

{c1(), c1(), c2(), c2(), c1()} # = {1, 2, 1, 2, 3}

Looking at this code we should recognize that countTally is a local variable to the MakeCounter function. Once the function is complete, the countTally variable goes out of scope and we cannot access it outside the function. However, due to closure, the function we declare within the MakeCounter function still has access to the countTally variable even after MakeCounter has finished

Thus we can continue to use the countTally variable in this anonymous function when we call it later on. It is effect now a private variable that only the generated function can access. A new countTally variable is created for each call of MakeCounter, so we can keep track of separate counts. Closure is a powerful tool that has many uses for complex programs

The Elegance of Functional Programming

Functional programming techniques are really quite elegant for many practical programming uses. Take the following example which implements the Lotka-Volterra predator prey model using Insight Makers programming features. Euler's method is used to solve the differential equations. Due to the elegance of functional programming, once we have defined our system, the entire differential equation solver requires just a single loop containing only two lines of code!

state <- {
    Predator: 20,
    Prey: 560
}

derivatives <- {
    Predator: function(state) 0.0002*state.Prey*state.Predator-0.25*state.Predator,
    Prey: function(state) 0.25*state.Prey-0.008*state.Predator*state.Prey
}

startTime <- 0
endTime <- 20
timeStep <- 1

for t in startTime:timeStep:(endTime-timeStep)
    slopes <- derivatives.map(x(state))
    state <- state + slopes*timeStep
end loop

alert(state)

Object Oriented Programming

Object oriented programming is a technique where objects are defined in a program. These objects are generally collections of properties and functions that may manipulate the object or carry out some behavior based on the object's state. Insight Maker's equation engine supports what is known as prototype-based object oriented programming. This is a flexible and powerful technique for building programs using objects.

Creating Objects

Object are based on named vectors. Let's take the following instance of a named vector as an example:

Person <- {
   firstName: "John",
   lastName: "Smith"
}

This "object", which is what we refer to named vectors as in this section, represents a person named John Smith. We can access this person's first and last name using the following syntax:

Person.firstName # = "John"
Person.lastName # = "Smith"

Since Insight Maker's equation engine is a functional language with first class functions, we can also assign functions to the properties of this object. For instance, we could add a function to return the person's full name:

Person <- {
   firstName: "John",
   lastName: "Smith",
   fullName: function()
      "John Smith"
   end function
}

We would then obtain the full name of the person object like so:

Person.fullName() # = "John Smith"

However, this function we wrote is not very smart. Our object already has all the information needed to find the person's full name, so repeating the name in the function is redundant. We can do better than this. To do so, we use a special variable: Self. When used in an object's function, Self refers to the object itself. Using this knowledge, we can rewrite our full name function to be smarter. In this new form, the full name function will give the correct full name even if we later change the object's first or last name.

Person <- {
   firstName: "John",
   lastName: "Smith",
   fullName: function()
      self.firstName+" "+self.lastName
   end function
}

Inheritance

Unlike some object-oriented languages, in Insight Maker each object is both a fully usable object and also a class definition other objects can inherit from. We use the new keyword to create instances of existing objects.

For example, we could create two new people objects like so:

chris <- new Person
chris.fistName <- "Chris"
john <- new Person

chris.firstName # = "Chris"
john.firstName # = "John"

You can also create multiple levels of inheritance. For instance, imagine we wanted to create a Student class. The Student class will be a subclass of the person class with two new properties: school and grade.

Student <- new Person
Student.grade <- 10
Student.school <- "Midfield High"

chris <- new Student # Chris is both a Student and a Person

Constructors

Constructors are functions that are called when a new instance of a class is created. Constructors are created by defining a property "constructor" in the object definition. If a constructor is available it will be called when the a new instance of an object is created. For instance, the following is a constructor that makes it easy to create people with a given name:

Person <- {
   firstName: "John",
   lastName: "Smith",
   fullName: function() self.firstName+" "+self.lastName,
   constructor: function(first, last)
      self.firstName <- first
      self.lastName <- last
   end function
}

chris <- new Person("Chris", "McDonald")
chris.fullName() # = "Chris McDonald"

Monkey-Patching

All classes in Insight Maker are fully dynamic. This means that you can add properties to the class definition and all instances (current and future) of that class will immediately have access to those properties. This is sometimes known as "Monkey Patching". This capability can be used to extend to Insight Maker's internal classes. For instance, all strings in Insight Maker inherit from the StringBase object and all vectors inherit from the VectorBase object. We can modify these objects to extend Insight Maker functionality.

As an example, let's extend Insight Maker strings with a Reverse() function. Such a function is not built into Insight Maker, but it might be nice to have. Let's give it a try:

StringBase.reverse <- function()
   self.range(self.length():1)
end function

"Test".reverse() # = "tseT"

Parent

The object an instance inherits from is known as its "Parent". A variable by this name is available in the object's functions in order to obtain a reference to the object's parent. This is especially useful for stringing constructors together. For instance, we may want to create a Student subclass of Person which calls its parent's constructor:

Student <- new Person("","")
Student.grade <- 10
Student.school <- "Midfield High"
Student.constructor <- function(first, last, grade, school)
   self.grade <- grade
   self.school <- school
   parent.constructor(first, last)
end function

Types of Primitives

Insight Maker supports the following key types of primitives that affect model operation. In addition to these, Insight Maker also supports a Text primitive and a Picture primitive that can be used to annotate or explain a model.

Stocks

In its simplest form, a stock is a bucket into which a something can accumulate or be withdrawn from.  What the bucket holds is specific to your model; it could be water, or money, or even deer. You can set an initial value or level for the stock that it will take on at the start of the simulation. The initial value may be a simple number or a more complex mathematical expression.

Stocks are flexible. For Instance, They can hold People, Water, Or Viruses.

One example of the use of a stock would be to simulate a reservoir. Water is added to the reservoir by rivers and then released from the reservoir for irrigation or consumption. Another example of a good use of a stock would be the population of a city. People migrate to the city and emigrate from the city. In this case the units for the stock would in numbers of people.

The value of a stock – or any other valued primitive for that matter – can be referenced in equations using a pair of square brackets around the primitive’s name. For instance, if you had a stock called My Lake, you could obtain twice its value in an equation using:

[My Lake]*2

The standard stock is in effect one large mixing bowl.  Materials are added directly to the bowl, completely mixed with the existing bowl contents, and then removed from the bowl. For most cases this is the desired behavior. For some cases, however, users will desire slightly different behavior. For instance, let us presume we are attempting to simulate an aging population. One way to simulate this would be to create a number of different stocks representing different age groups. For instance, we could have a Young stock, a Middle-aged stock, and an Old stock. Each time step we could transfer a fixed fraction of the young people to the middle-aged stock, and a fraction of the middle-aged people to the old person stock. We could simulate births by adding new individuals to the young stock, and deaths by removing people from the old stock. This would work pretty well if we had a population where the demographics remained roughly constant. Let’s assume, however, that we encounter a shock like the baby-boom generation in the United States. This shock would add a large number of people to the young stock instantly. In reality these individuals should age a large number of years before they are passed on to the middle-aged stock. Unfortunately, because we are moving a fixed fraction of the young people to the middle-aged stock each period, the shock is felt in the middle-age population immediately. This is not the desired behavior and will lead to incorrect demographic modeling.

We can attempt to remedy this by increasing the number of stocks. Instead of three, we could have six, or 30, or 1000. The more stocks we added, the more accurate our modeling. Creating such a large number of stocks, however, would be infeasible! Fortunately, Insight Maker allows you to convert a stock into a conveyor stocks, thereby creating a theoretically unlimited number of sub compartments within one stock.

Diagram of a Conveyor Stock With Fixed Delay

Conveyor stocks are special in that there are two types of values you can access. The regular single bracket notation gets you the value of material that has exited the converter. A new double bracket notation shows the total amount of material including that still in transit.

Exited Number In Stock = [Population]

Total Number In Stock = [[Population]]


Flows

A flow transports a material from one stock to another. As a designer, you must enter the rate of the flow. So, for instance, in our river example the magnitude of the flow could be a million gallons of water and the period over which it flows could be one day: in effect a million gallons per day. Flows may connect two stocks together. Alternatively, a flow can lack a start or an end. In this case, the unconnected end acts as an unlimited stock of material.

As with the initial value of a stock, the rate of a flow can be a simple number or a more complex equation. The equation can be based on many factors including the values of the stocks the flow is connected to and the current time. For example, if we had a stock representing the population of rabbits (called Rabbit Population), we could make the flow into the stock be the number of rabbits times a constant birthrate. This equation would result in geometric growth in the rabbit population:

[Rabbit Population]*0.01

Alternatively, if we had a flow representing rainfall, we could set the flow rate to be based on the current time of the year. Rainfall over the course of a year can be approximated using a sinusoidal function like the following:

 sin(2*pi*(Weeks()+10)/52)*30+35

Where weeks is a Insight Maker function that automatically takes on the value of the current simulation time measured in weeks, pi is the constant 3.14159…, and sin is another Insight Maker function that applies the trigonometric sine function to an angle inputted using radians.

A flow can be restricted to positive flows only.  In this case, the flow will only be applied if the calculated magnitude of the flow rate is positive. This restriction would be applicable for both our rabbit and rain examples. We know that neither of the flows should ever be negative (you cannot have negative births or negative rain) so we might want to set them to only allow positive flows just to insure that such an impossible event never happens (though it never will in any event given the equations we specified).

Every flow has two special properties that may be referenced in its equations. The first is Alpha which refers to the stock at the start of the flow. The second is Omega which refers to the stock at the end of the flow. In your equations you may use either of these instead of needing to know the actual names of a flow’s start or end stocks. For instance if you wanted 5% of the water from a lake to evaporate out of it each time period, you could use the following statement for the rate of a flow whose beginning was the lake:

[Alpha]*0.05


Variables

A variable is a pre-calculated or dynamically calculated value and is represented by an ellipse in the model.

Variables can be useful to synthesize data or supply calibration variables to your model. For instance, in a demographic model, a variable could be created to specify the birth rate. Alternatively, if you have a population model that has multiple age groups, you could dynamically calculate a variable to add up all the age groups and determine the total population size using the following equation (you would have to make sure that the Total Population variable was connected with links to the necessary stocks; see the next section for more information on links):

[Young Population]+[Middle Aged Population]+[Old Population]

Variables can also be used to create forcing functions which are based on the current time. Insight Maker supports a number of functions that return the current time in the specified units. These functions are: seconds, minutes, hours, days, weeks, and years. The following equation could be used to create a variable that returns the square of the current simulation time as measured in hours:

Hours()^2

Sometimes it is hard to decide which parts of your model should be simulated using stocks and which parts with variables. Generally stocks should only be used to model items that have inflows or outflows. If you have a stock in your model without either an inflow or an outflow, it would almost certainly be modeled better with a variable.


Links

A link makes two objects (flows, stocks, converters, or parameters) aware of each other. This allows the two primitives to reference the value of each other in their equations. A flow is automatically aware of both its Alpha and Omega and, conversely, its Alpha and Omega are aware of the flow. In any other case, if two primitives are not connected by a link, you will be unable to reference the value of the first in the equations of the second or vice versa.

For an example of the use of links, take the following model that only contains two parameters: A and B. A has some arbitrary value that is not important to us. B’s value is set to the following (the carrot sign “^” means to carry out a power operation, so the function here returns the square of the value of A):

[A]^2

We could construct our model like this:

a b.png

When we run this model though, Insight Maker will print out an error that no connection to A was found from primitive B. This is because A and B are not connected in any way so B is unaware that A exists. Therefore you cannot refer to the value of A from B. This problem is easy to fix though; just create a link joining A and B like this:

a-b.png

This time, the model runs perfectly.

Curved Links

By default, Insight Maker links are straight. Sometimes you will want to create links with bends in them. To do so, click on the link where you want the bend while holding down the "Shift" key. This will create a waypoint in the link that you can move to create bends. You can create as many waypoints in a link as you like; which allows you to define complex curves. To remove a waypoint, shift-click it. All waypoints will be removed when you change the connection end points of a link.


Converters

Often you will want to add structured input or empirical data to your model. For instance, if you were modeling a lake, you might want your model to have access to historical precipitation data that was recorded periodically. Alternatively, you might want to know the actual, surveyed surface area of the lake, given the volume of the lake.

Insight Maker makes using this data very easy with its converter primitive (similar to graphical function) which allows you to create an input-output relationship or graph. When the input source to a converter primitive takes on an input value, the converter takes on the corresponding output value which may then be used by other primitives that reference the converter. The input can be the current time (in seconds, minutes, hours, etc…) or the value of some other primitive that is linked to the converter.

For converters, if you have not specified a specific output value for a given input, the outputs closest to the input will be combined to develop an appropriate output based on an interpolation method you have specified. For illustration, take the following example input-output table for a converter whose input source is the current time in years and where linear interpolation has been selected.

At the start of the simulation (when the time is 0 years), the converter will take on the value of 0. Ten years into the simulation (when the time is 10 years), the converter will take on a value of 100..

You can enter data into the converter by hand or import a two-columned comma separated dataset (CSV file) that may be generated by Microsoft’s Excel or other spreadsheet programs.


States

A State is in effect an on/off switch. When the State is active, the model is in that state; when the State is inactive, the model is not in that state.

States are primarily useful when carrying out Agent Based Modeling. One or more sets of States may be placed in an agent to describe the current state of that agent. For instance, if you are modeling a population of individuals, you could have your agent definition contain a State representing whether or not an agent was a smoker. Similarly you could have a State representing whether or not an agent was a female. For continuous variable (such as age) a Stock should be used; but for binary or dichotomous variables, a State is a more natural primitive.

A State is configured using its initial value equation. This equation may be a simple value such as: 1 or 'true' for active, or 0 or 'false' for inactive. The equation can also be a logical equation such as:

not true # Which is false

Or a simple mathematical equation:

5*5 > 10 # Which is true

Or a function that depends on other primitives:

not ([Smoker] and [Diabetic])


Transitions

Transitions are to States as Flows are to Stocks: they move the model between States.

Imagine a simple Agent-Based disease model. In this model each agent might have two States: Healthy and Infected. The agents start in the Healthy State and then transition to the Infected State according to some infection rule. This transition is controlled by a Transition primitive that will be connect the two state. When the transition is activated, an agent that is in the Healthy State will be moved to the Infected State. A schematic of this simple agent with the two States and a Transition labeled Infection is shown below.

The key configuration option for a Transition is what triggers the Transition. There are three different types of triggers:

  • Timeout: A timeout trigger for the infection Transition would switch the healthy person to an infected person at a fixed time after the person became healthy. If all the agents in the model started in the Healthy State. They would all transition at the same time.
  • Probability: A probability trigger for the infection Transition would result in a fixed probability of transitioning every unit of time. The probability is the probability of transitioning per time unit. So if the time is specified as years and the probability is 0.5, roughly 50% of healthy agents will become infected every year.
  • Condition: The condition trigger allows the creation of a Transition that is based on logical relationships to other agents or events in the model. For instance, a trigger condition equation could be written that looked geographically at nearby agents and would cause a transition if an infected agent was within some nearby radius of susceptibility.

An example of a condition trigger would be to trigger the transition if some other state in the system occurs. For example:

Years > 20 # Transition if the time is 20 years or higher

Or:

[Health] < [Desired Health] # Transition if the desired health is greater than the current health


Actions

An Action can be used to manipulate a model during a simulation. Actions are primarily used in Agent Based Modeling.

An Action is defined by two features:

  • Trigger: What triggers the Action (a timeout, a probability, or a condition). See the Transition primitive description for more information on triggers.
  • The Action: What the Action does when triggered. This can be to move an agent, to change a primitive's value, to add a connection to an agent, or many other actions.

An example of an action for an agent might be to have the agent die off if its health falls below a threshold:

if [Health] < 0 then Self.remove() end if

Agent Populations

Agent Populations are a key ingredient in the construction of Agent Based Models. Agent Populations are used to actually store the agents during a simulation. There are two primary factors that control an Agent Population: the size of the population (the number of agents), and the agent base for the population (what type of agent will populate that population).

The population size is the number of agents in the population at the start of the simulation (the size can also be changed dynamically during the simulation by destroying agents or creating new agents).

The agent base defines what types of agents will be contained by the population. Your model can contain one or more definitions of agents (just like it can contain one or more populations of agents), each definition or Agent Base is specified by creating a Folder primitive around the portion of the model that will define that agent. Then the Extension property of the resulting Folder must be set to "Agent". After this is done, the Agent Base property of the Agent Population can be set to the Folder's name.

The following illustration shows what a resulting model might look like after this setup is completed. The Agent Population primitive is Population while the agent base Folder is labeled Person:

Accessing Individual Agents

Insight Maker includes a number of functions to access the individual agents within a population. The simplest of these is the FindAll() function. Given an agent population primitive that we'll call Population, the FindAll function returns a vector containing all the agents within that agent population:

[Population].FindAll()

If your agent population currently contained 100 agents, FindAll would return a vector with 100 elements where the first element referred to the first agent, the second element referred to the second agent, and so on. It is important to note that these elements are agent references, not numbers. So you can use a function like Reverse() on the resulting vector, but you cannot directly use functions like Mean(), as the agent references are not numerical values. We will see how to access the values for agents next.

In addition to the FindAll function, other find functions return a subset of the agents in the model. For instance, the FindState() and FindNotState() functions return, respectively, agents that either have the given state active or not active. For instance, imagine an agent-based disease model where our agents had a state primitive called Infected that represented whether the agent was currently sick. We could get a vector of the agents in our population that were currently sick using the following:

[Population].FindState([Infected])

And we could obtain a vector of the agents that were not currently infected with:

[Population].FindNotState([Infected])

Find functions can also be chained together. For instance, if we added a Male state primitive to our agents to represent whether or not the agent was a man; we could obtain a vector of all currently infected men with something like the following:

[Population].FindState([Infected]).FindState([Male])

Nesting find statements is effectively using Boolean AND logic (like you might use on a search engine: "Infected AND Male"). To perform Boolean OR logic (e.g. "Infected OR Male") and return all the agents that are either infected or a man (or both), you can use the Union function to merge two vectors:

Union([Population].FindState([Infected]), [Population].FindState([Male]))

If you wanted the agents that were either infected or men (but not both simultaneously), you could use:

Difference([Population].FindState([Infected]), [Population].FindState([Male]))

Agent Values

Once you have a vector of agents, you can extract the values of the specific primitives in those agents using the Value() and SetValue() functions.

The Value function uses two arguments: a vector of agents and the primitive for which you want the value. It returns the value of that primitive in each of the agents. For instance, let's say our agents have a primitive named Height. We could get a vector of the height of all the people in the model like so:

[Population].FindAll().Value([Height])

A vector of heights by itself is generally of not much use. Often we will want to summarize the vector of agents: converting the vector to a single number that represents some property of the population. For instance, we could determine the average height of individuals in the population. The following equation calculates the mean value of an agent's height:

Mean([Population].FindAll().Value([Height]))

In addition to determining the value of a primitive in an agent, you can also manually set the agents’ primitive values using the SetValue function. It takes the same arguments as the Value function, in addition to the value to which you want to set primitives. For instance, we could use the following to set the height of all our agents to 2.1:

[Population].FindAll().SetValue([Height], 2.1)

Creating and Removing Agents

You can programmatically create new agents in an agent population by using the add() function of the agent population. For example, the following will create a new agent that is part of the population My Population:

[My Population].add()

The add() function will return the newly created agent allowing you to configure it. For example:

newAgent <- [My Population].add()
newAgent.SetLocation({x: 100, y: 50}) # Set the location of the agent
newAgent.SetValue([Age], 72) # Set the agent's [Age] primitive value

You can use the remove() function of an agent to remove it from an agent population. This can be thought of as deleting or killing the agent.

Relationships Between Agents

Each Agent Population contains both a spatial geometry and a network geometry that may be used to relate agents.

Additional Agent Functions

Insight Maker has a large number of additional functions related to agents listed on the functions page.


Running a Model

Once you have completed diagraming a model and specifying model equations, you are almost ready to run a simulation, but first you need to configure the Time Settings for the model. This can be done by clicking the Time Settings button in the toolbar.

This button will open the Time Settings window that lets you control the key factors driving the simulation. In brief these factors are as follows:

  • Simulation Start: The start time for the simulation.
  • Simulation Length: The length of the simulation.
  • Simulation Time Step: The length of time between separate evaluations of the model by the simulation engine. The smaller this number, the more accurate the simulation but the longer the simulation will take.
  • Time Units: The time units. The Simulation Start, Simulation Length and Simulation Time Step will all be measured in these units.
  • Analysis Algorithm: There are two different algorithms that can be used for simulations. The fourth-order Runge-Kutta algorithm is generally more accurate when dealing with System Dynamics models. However, the Euler algorithm is faster and is preferred for Agent Based Models or models containing discontinuities.
  • Pause Interval: If set, the simulation will be paused at every interval. You will have the chance to adjust the values of any primitives you have defined sliders for, before resuming the simulation. This allows you to create interactive flight simulators or games.

Once your Time Settings have been configured, you can run your model by pressing the Run Simulation button in the toolbar.

Your model results will be shown in a new window. Results are organized as a set of displays each of which contains some data from the model. You can create and configure new displays to examine your model from different perspectives. A number of different display types are supported including time series charts and tables. You can also export your results to your computer for additional analysis.

Model Verification

Ensuring model accuracy and validity is a critical task and there are a number of procedures that may be carried out in order to help obtain confidence in your model results. These confidence-building activities include:

·         Historical Tests: Comparing the results of the model to known historical values.

·         Extreme Value Tests: Testing how the model behaves when extreme values are inputted into it. Does the model behave as one would expect or does it exhibit wild behavior?

·         Submodel Tests: For large models it is sometimes possible to isolate one portion of the model and compare it to some other more detailed model designed for analyzing that specific feature.

In addition to these methods, Insight Maker includes a rich suite of features designed for documenting the assumptions that your model is based on and ensuring that these assumptions are not violated during model simulation.

For instance, imagine you are designing a model simulating an ecosystem. As part of this complex model, you may have a stock representing the quantity of water in a lake. When designing your model, you know that the value of the lake stock should never become negative. If it does, there is clearly something wrong with your results and you should analyze the model to see what is causing this error. Unfortunately, though this constraint is fresh in your mind when you are focusing on the lake, it is very easy to forget about it when you focus on a different item. When you move on to another part of your model you might inadvertently make a change to your model that violates the assumption about the lake. Possibly you add another flow that results in the lake stock becoming negative and, since your focus distracted, you do not realize this mistake until much later.

In order to prevent mistake like these, Insight Maker includes a set of tools for documenting and enforcing your model’s assumptions as you develop them. These tools include: Unit Checking and Constraint Checking.  All of these tools can be accessed by clicking the model verification button that appears in the configuration pane when you select a stock, flow, converter, or parameter within your model. Once you have defined verification checks for a primitive, a checkmark will appear on the primitive’s verification button letting you know that primitive will be validated during the simulation.

Constraints

Insight Maker constraints allow you to very simply set the maximum and minimum allowable values for a primitive. Thus, in an example of a lake, we would set a minimum value of 0. For a variable representing a proportion, the value could range from 0 to 1.

If the constraints are ever violated for a primitive during the simulation, an error message will be displayed and the simulation will be terminated.


Units

Units are at the heart of Insight Maker. The flows into a stock should have the same units as the contents of that stock. Similarly, you should be able to specify the units for the different parameters and converters in your model. This is an underlying assumption of System Dynamics models, and it is one that you should adhere to as you construct your models. If you do construct models with disregard to units, it is still possible to develop accurate simulations, however it is much more likely that you will create errors or your model will fail to reflect the underlying processes driving reality.

Insight Maker allows you to enforce units using its unit mechanisms. As you construct your model, you may define the units for the different parts of your model. For instance, you may add a stock representing a lake with units of Cubic Meters. You may have a river flowing into the lake with units of Cubic Meters per Second. When you run a simulation, Insight Maker will check to make sure that the units you have defined are consistent. If they are not consistent (for instance you try to subtract a number with units of Meters from one with units of Liters) Insight Maker will display an error and terminate the simulation.

When you define a number in Insight Maker, it is automatically defined as Unitless. When the value of a valued primitive (stock, flow, parameter, or converter) whose units are defined is referenced elsewhere in your model, Insight Maker will evaluate its equation. If the result of this evaluation is a Unitless number, Insight Maker will then assign the units of that primitive to the value. If, however, the value is not Unitless and the units conflict with the primitive’s units, then an error will be displayed. You may add and multiply Unitless numbers by and to numbers with defined units and the results will take on the units of the number for which units were defined. You may also define a number with specified units in your equations using a special, curly-bracket notation like follows:

{2 Meters} + {10 Centimeters}

The above equation defines a value of 2.1 meters. Insight Maker has the ability to carry out automatic unit conversion between certain sets of units. Thus if you subtract a value of 10 centimeters from a value of 2 meters the result will be 190 centimeters or 1.9 meters.  The following table illustrates some examples of unit operations within Insight Maker:

Equation

Results

1+3

4 (No Units)

{1 Centimeter}+{3 Centimeters}

4 Centimeters

{1 Meter}+{3 Centimeters}

1.03 Meters

{1 Grams}+{3 Centimeters}

Error – Inconsistent Units

1+{3 Centimeters}

Error – Inconsistent Units

2*7

14 (No Units)

2*{7 Meters}

14 Meters

{2 Meters}*{7 Meters}

14 Square Meters

{2 Meters}/{4 Seconds}

0.5 Meters/Seconds

{2 Meters/Seconds}/4

0.5 Meters/Seconds

{25 Square Meters}^(1/2)

5 Meters

3*{200 Centimeters}*{4 Square Meters}

24 Cubic Meters

{100 Inches} – {1 Meters}

154 Centimeters

{10 Meters/Second}*{1 Minute}

600 Meters

{7 Widgets/Years^2}*{10 Years}

70 Widgets/Year

Insight Maker comes with a large number of units built into it. However, it also contains the ability for you to define custom units, like "Widgets" above, by simply typing their name into the unit field for a primitive.


Storytelling

Storytelling lets you guide the viewer of your insight through the model step-by-step. It allows you to highlight and hide certain parts of the model structure in order to help the viewer focus on specific elements. Storytelling is a powerful tool used to help share and communicate your models to others.

The model's story is configured as a set of sequential steps that can be specified in the Story Designer. Each step carries out an action that moves the viewer further through the story. The following types of steps are available:

  • Change Visibility: You can use this step to change the opacity of primitives in the model. An opacity value of 0 means a primitive is completely hidden, while a value of 100 means it is fully shown. A value in between will make the primitive partially transparent. A great technique to reveal your model is to make primitives just slightly visible so the viewer will have a hint of what is coming as the model is slowly revealed to them.
  • Show Message: You can display a message to the viewer describing what they are seeing. You can include basic text formatting or links to other web pages.
  • Show Note as Message: You can display a message to the viewer describing what they are seeing. In this case, the message is taken from one or more existing primitives that have notes attached to them.
  • Run Simulation: You can trigger a run of your model. The results will be displayed to the user as if they had clicked the Run Simulation button.
  • Toggle Folders: You can expand or collapse a folder primitive in the model to reveal or hide certain parts of the model.
  • Run Action: This is a powerful step which can be used to implement arbitrary actions and functions. When this step is executed, the specified action will be run as if a button primitive had been pressed. You can use this to run any Insight Maker API Commands. For instance, if you wanted to run a simulation at a certain step to show the results to the user, you could use the "runModel()" command.

Each time the user clicks the "Step Forward" button, a single step will be executed. If you want to execute multiple distinct steps as a batch, you can add a Group step. Any steps that are within a group will all be executed together in one go.

Once you have completed your Story, you can publish it as an "Article". Articles take your story and convert it to a beautiful, static web-page that is easy to share with others.


Agent Geography

One of the key strengths of Agent Based Modeling is that it allows us to study the geographic relationship between our agents. So if we are developing a disease model we do not have to assume that all the agents are perfectly mixed together like atoms in a gas (such as we generally would in System Dynamics). Instead, using Agent Based Modeling we can explicitly define the physical relationship between the different agents and study how this geography affects the spread of the disease.

In general when we talk about geography we mean spatial geography: the locations of people within a region in terms of their latitude and longitude (and sometimes their elevation). Insight Maker supports this kind of geography, but it also supports a second kind of geography: network geography. Insight Maker allows the specification of "connections" between agents. This leads to a new type of geography where you have centrally located agents (ones connected to many other agents) and agents far from the network's center (those that are unconnected or just connected to a very few other agents).

Both these types of geographies can be useful in exploring important features of real-world systems. In the following sections, we will introduce their properties and show you how to utilize them in your own models.

Spatial Geography

In Insight Maker, each Agent Population can be given dimensions in terms of a width and a height. By default, agents are placed at a random location within this region. You can, however, choose a different placement method for the starting position of the agents. The following placement methods are available:

Random: The default. Agents are placed at random positions within the geometry specified for the agent population.

Grid: Agents are aligned in a grid within the population. When using this placement method, you will need to ensure that you have enough agents so that the grid is complete. You might need to experiment with increasing or decreasing the number of agents to make the grid fit perfectly for a given set of region dimensions.

Ellipse: Agents are arranged in a single ellipse within the region. If the region geometry is a square, then the agents will be arranged in a circle.

Network:: Assuming network connections between agents have been specified, the agents will be arranged in an attempt to create a pleasing layout of the network structure.

Custom Function: Here you can specify a custom function to control the layout of the agents. This function will be called once for each agent in the population and should return a two-element vector where the first element is the x-coordinate of the agent, and the second element is the y-coordinate. The variable Self in this function will refer to the agent that is being positioned.

Spatial Find Functions

When working with a spatially explicit model, a number of additional find functions are available for you to obtain references to agents that match a given spatial criteria.

FindNearby() is a function that returns a vector of agents that are within a given proximity to a target agent within an agent population. It takes two arguments: the agent target for which you want nearby neighbors and a distance. All agents within the agent population within the specified distance to the target agent will be returned as a vector.

It is useful now to introduce a concept that will be very helpful to you. When used in an Agent, Self always refers to the agent itself. If you have a primitive within an agent, Self can be used from that primitive to get a reference to the agent containing the primitive. So the following equation in an agent will return a vector of agents that are within 15 miles of the agent itself:

[Population].FindNearby(Self, {15 Miles})

Two other useful functions for finding agents in spatial relation to each other are FindNearest() and FindFurthest(). FindNearest returns the nearest agent to the target in a population while FindFurthest returns the agent that is farthest away from it. Each of them also supports an optional second argument determining how many nearby (or far away) agents to return (this optional argument defaults to one when omitted).

For example, the following equation finds the nearest agent to the current agent:

[Population].FindNearest(Self)

While this finds the three agents that are furthest from the current agent:

[Population].FindFurthest(Self, 3)

Movement Functions

You can also move agents to new locations during simulation. To do this, it is helpful to introduce a new primitive we have not yet discussed. This primitive is the Action primitive. Action primitives are designed to execute some action that changes the state of your model. For instance, they can be used to move agents or change the values of the primitives within an agent. An action is triggered in the same way a transition is triggered. Like a transition, there are three possible methods of triggering the action: timeout, probability, and condition.

For instance, we can use an action primitive in an agent and the Move() function to make agents move during the simulation. The Move function takes one argument: a vector containing the x- and y-distances to move the agent. Thus, we could place an action primitive in our agent and give it the following action property to make the agent move randomly over time. The equation will move the agent a random distance between -0.5 and 0.5 units in the x-direction and a random distance between -0.5 and 0.5 units in the y-direction.

Self.Move({rand(), rand()}-0.5)

Another useful movement function is the MoveTowards() function. MoveTowards moves an agent towards (or away from) the location of another agent. MoveTowards takes two arguments: the target agent to move towards and how far to move towards that agent (with negative values indicating movement away). The following command would move an agent one meter closer to its nearest neighbor in the population.

Self.MoveTowards([Population].FindNearest(Self), {1 Meter})

Network Geography

To create connections and remove connections between agents you can use the Connect() and Unconnect() functions. Both of these functions take one argument: the agents that should be connected or disconnected. For example, to connect an agent to its nearest neighbor, you could use the following:

Self.Connect([Population].FindNearest(Self))

To disconnect an agent from its nearest neighbor (assuming they are connected), you would use:

Self.Unconnect([Population].FindNearest(Self))

To obtain a vector of connections to an agent, use the Connected() function:

Self.Connected()

Connections are not directed so creating a connection from agent A to agent B is the same as creating a connection from agent B to agent A. Also only one connection between a given pair of agents will exist at a time. So creating two connections between a given pair of agents will have the same effect as creating a single connection.

By default, no connections are created when a simulation is initially started. If you change the Network Structure configuration property of the agent population primitive, you can specify a function to create connections when the simulation is started. This function is called once for each pair of agents in the model. The agents are available in the function as the variables a and b. If the function evaluates to true, then the agents will start connected. If the function evaluates to false, the agents will not be initially connected.

You could use this function to, for instance, specify that 40% of agents will be directly connected to each other at the start of the simulation. The following equation would do that by generating a random true/false value with 40% probability of returning true each time it is called:

RandBoolean(0.4)

Sensitivity Testing

Often the precise values of model parameters or stocks will not be known. When you build a model, you will use the information available to you to create an estimate for a parameter value, but there will always be some degree of uncertainty in this estimate. Sensitivity analysis allows you to rapidly explore the repercussion of this uncertainty to see how robust your results and findings are to changes in parameter values. Sensitivity testing works by carrying out multiple simulations of your model using different initial values. It then aggregates the results of the simulation and presents a concise report on the potential simulation outcomes.

To use sensitivity testing, replace one or more of the values in your model with random variables. For instance if you had a stock where your best estimate for the initial value was 100 and you determined you could model an estimate for the true initial value using a random variable with a standard deviation of 7, you would set the initial value of the stock to be the following:

RandNormal(100, 7)

You may then run the sensitivity testing analysis algorithm to repeat the simulations many times, each time the value of the stock will take on a different initial value and you may see how the resulting simulation paths change (this is a classic Monte Carlo algorithm). The following is an illustration of the results that can be obtained by carrying out a sensitivity analysis. The regions that contain the densest 50%, 75%, 95% and 100% of simulation results are clearly marked. A table of these same results is also available for export to Excel or other analysis programs.

It is important to know that setting the value of a constant parameter is not as easy as it is with a stock. If you just place the random function in the parameter’s equation, the parameter will take on a different value each time step of each simulation. If you want to have the parameter take on a constant random value for each simulation, you need to use Insight Maker’s Fix function like so:

Fix(RandNormal(100, 7))

The first parameter to the Fix function is the source function that will be aggregated, the second optional argument is the period of how frequently to sample from this source (-1 means only sample a single time at the start of the simulation), and the last value is a unique identifying string that should be changed each time you use the Fix function in your model.


Optimization

Insight Maker includes built-in optimization functionality to automatically adjust one or more parameter values in order to achieve a desired goal. At one level this can be used in a very similar way to Microsoft Excel's Goal Seek functionality.

Imagine a model of a company that would forecast future revenue for the company. Of the variables managers have control over, let's assume that there are two they are particularly curious about: how much to invest in marketing and how much to invest in research and development. Using the company's model, the managers could experiment with different values for these parameters and see how they affected revenues. They could even seek to find the combination of values that maximized revenue. However, it might take a very long time and a lot of experimentation in order to find the precise combination of values that did this. However, using the optimizer function of Insight Maker, you can have Insight Maker very quickly search through a wide range of values to find the set that best achieves a certain goal such as maximizing revenue.

There are several key options that must be configured when setting up the optimizer. These include:

  • Goal Primitive: This is the primitive that you want to influence. In the business example, it is the revenue taken in by the company.
  • Goal: This is what we want to do to the goal primitive. It is either to maximize the primitive (which would be the desired case for revenue) or to minimize it if the goal primitive represented a measure of cost or error.
  • Goal Type: If we only care about the final value of the revenue, we would use the "Final Value" goal type. If, however, we wanted to maximize the revenue over all years in the simulation, we would use the "Integral" goal type.
  • Primitives to Change: These are the primitives Insight Maker automatically changes to achieve the goal. In the model of the company we want to adjust the marketing expenses along with the research and development expenses.
  • Bounds: For the primitives that will be adjusted, you need to specify an upper and lower bound. Insight Maker will adjust the primitive values within these bounds.
  • Accuracy: This is the minimum desired level of accuracy for that primitive value. Once Insight Maker has moved the primitive value to within that range of where it knows the true value should be, it will stop optimizing and show you the results. The smaller the accuracy, the longer the optimization will take.

Once these options are configured, you can press the Optimize button and Insight Maker will automatically search to find the best values for the parameters. This optimization may take some time. The length of optimization time increases exponentially with the number of primitives to change so it is best to start with just one or two and increase your scope slowly from there. In addition to these configuration options, there are also a set of advanced options:

  • Random Starts: The optimizer uses a deterministic optimization algorithm. When you have local minimums or maximums in the solution space, it might locate one of those instead of the global maximum or minimum. You can use random starting locations to carry out a more comprehensive search of the parameter space and gain confidence you have found the true optimum.
  • Maximum Number of Iterations: The optimizer will stop when all the parameter values are within their desired accuracy of the true values. If this is taking too long and you want it to stop earlier, you can specify a maximum number of iterations prior to stopping.
  • Step Reduction Factor: This controls how fast the optimizer narrows in on a value.

One typical use of the optimizer is to fit a model to historical data. Imagine we had historical revenue data in addition to the simulated revenue values in the business model. Rather than trying to maximize revenue, we would like to adjust the parameter values in the model in order to make the simulated data best match the historical data. We can do this using the optimizer by first creating a variable in the model that measures the error for the fit of the simulated to historical data. We could use, for example, classic squared error:

([Simulated Revenue] - [Historical Revenue])^2

Or we could use a more robust measure of error that is less sensitive to outliers:

Abs([Simulated Revenue] - [Historical Revenue])

Then we could use the "Integral" goal type to minimize the value of this variable thereby optimizing our model's fit to the historical data.


Vectors

Vectors are a powerful tool that play a key role in agent-based modeling. Vectors go beyond this single usage case however, and you can use vectors to easily extend a model to represent different categories or classes of behavior.

For example, imagine you wanted to create a population model of North America. You could build the model using a single stock representing the population for the entire continent. Once you completed this model, you may decide that you now want to track the populations for Mexico, Canada and the United States separately. One way to do this would be to duplicate your model structure for each of these three countries. Though workable, this process would be laborious and prone to error. With vectors, you can achieve the same effect as duplicating the model structure just by adjusting your model equations slightly.

In this section, we will introduce vectors to you and show you how to use them to keep track of different categories in your model.

What is a Vector

A vector is just an ordered list of numbers surrounded by curly braces. This is a vector of the squares of the numbers one, two, and three :

{1, 4, 9}

You can do mathematical operations on vectors like you would with regular numbers. For example:

sqrt({1, 4, 9}) # = {1, 2, 3} 5+{1, 4, 9} # = {6, 9, 14} {0, 1, 2}*{1, 4, 9} # = {0, 4, 18}

In addition to regular vectors, Insight Maker also has named vectors. In named vectors each element of the vector has a name (which is case-insensitive). For example:

{cats: 1, dogs: 3, birds: 9}

You can apply mathematical operations to named vectors just like you would for regular vectors. For example:

2*{cats: 1, dogs: 3} # = {cats: 2, dogs: 6} {cats: 1, dogs: 3} + {dogs: 4, cats: -1 } # = {cats: 0, dogs: 7}

Vectors are sometimes referred to as "arrays" or "lists" in other languages and named vectors are sometimes referred to as "associative arrays", "hash tables" or "dictionaries".

Vectorizing your Model

Imagine we wanted to extend Insight Maker's standard rabbit growth model to keep track of male and female rabbits separately. We could do this either by duplicating the entire model structure, or more simply by using vectors.

To take the vector approach, we simply need to replace the values of the primitives in our model with named vectors. For example, replace the Rabbit stock's initial value ("200") with:

{Males: 200, Females: 100}

That equation tells Insight Maker that the stock contains a vector with 200 "Males" and 100 "Females". Now run the model. Insight Maker will automatically vectorize its calculations and track the two distinct populations of rabbits without any additional configuration. The before and after results of the vectorization are shown below:

Now, edit the Birth Rate variable to be:

{Males: 0.1, Females: 0.2}

Again run the model and everything will work automatically. Your "Males" and "Females" populations of rabbits will have different initial population sizes and birth rates. Insight Maker keeps track of everything for you and automatically applies the correct vectorization.

Multidimensional Vectors (Matrices)

A matrix in Insight Maker is simply a vector of vectors. For instance to define a 2x2 matrix you could use the following:

{ {1, 4}, {9, 16} }

You can do the same with named vectors. Say for instance we wanted to track rabbits by both gender and country, we could use something like:

{Canada: {Males: 200, Females: 100}, USA: {Males: 150, Females: 50} }

If you put this is the initial value for the stock, you will now be simulating four distinct rabbit populations.

In this case, for the birth rate equation, Insight Maker will work correctly either with one number (all the populations have the same birth rate), or a vector for the genders (each gender has a different birth rate), or a vector for the countries (each country has a different birth rate), or a matrix of countries and genders (each country and gender has a different birth rate). Insight Maker will do the right thing for you in each case.

Multidimensional vectors can start to become clunky if you type them in manually. If you have the same values for some of the elements (such as all the countries have the same population sizes) you can use other vector functions to help construct the vector. For instance the "Repeat" function is quite useful:

Repeat({Males: 200, Females: 100}, {"Canada", "USA", "Mexico", "France", "Germany"}) # = {Canada: {Males: 200, Females: 100}, USA: {Males: 200, Females: 100}, ...... }

Selecting Elements and Collapsing Matrices

You can select an element from a vector like so:

{1, 4, 9}{2} # = 4

You can do the same with named vectors:

{Males: 200, Females: 100}{"Males"} # = 200

For named vectors you can also use a shorthand notation "." to select single elements:

{Males: 200, Females: 100}.Males # = 200

You can also use vectors in your selections to select multiple elements at a time:

{1, 4, 9}{ {2, 1} } # = {4, 1} {Males: 200, Females: 100}{ {"Females", "Males}} } # = {Females: 100, Males: 200}

For matrices, you list your selectors sequentially dimension by dimension. For example, you can do something like the following:

[Rabbits] <- {Canada: {Males: 200, Females: 100}, USA: {Males: 150, Females: 50} } [Rabbits]{"Canada", "Males"} # = 200 [Rabbits].Canada.Males # = 200

If you use a "*" instead of an element name, Insight Maker returns the whole vector along that dimension:

[Rabbits]{"Canada", *} # = {Males: 200, Females: 100} [Rabbits]{*, "Males"} # = {Canada: 200, USA: 150}

You can also collapse or summarize elements from a matrix by using the name of a function in the selection. The function is used to aggregate elements along that dimension. For example, to get the total number of rabbits by country:

[Rabbits]{*, sum} # = {Canada: 300, USA: 200}

To get the average number of males and females in the countries:

[Rabbits]{mean, *} # = {Males: 175, Females: 75}

All standard Insight Maker vector functions (Mean, Median, StdDev, etc...) can be used in this way. Additionally, you can also write and use custom functions.

Wildcards

"*" in vectors is a wildcard and will match missing elements. For instance if we wanted the birth rates of Canada and USA to be 0.1, but for all other countries to be 0.2, we could use:

{USA: 0.1, Canada: 0.1, *: 0.2}

Multi-word Names

If you have a name that contains spaces, you need to quote it when you declare the vector:

{USA: 0.1, Canada: 0.3, "Great Britain": 0.2}

Macros

Macros allow you to define custom model code that is included in the model.

For instance, you could use macros to define a custom function that is accessible in all the equations in your model. For example, imagine you had a model where you needed to calculate the sine of the division of the sum and product of three numbers in many different places. Now you could write this equation out wherever it occurred. However, that might be tedious and prone to error. Additionally, if you later decided you had to use the the cosine instead of sine, there would be many different parts of your model you would need to update.

By defining macros for models we can create a function to carry out this repetitive task. Insight Maker can define single-line functions like so:

myFn(a, b, c) <- sin((a+b+c)/(a*b*c))

Multiline function can be defined using this syntax:

Function myFn(a, b, c) x <- (a+b+c) y <- (a*b*c) return sin(x/y) End Function

Macros can also be used to define variables that will be available in the model such as a counter. Another use of macros is to set the random seed for the model so the same set of random numbers will be generated each model run:

setRandSeed(99)

See the Advanced Equations section of this manual for more information on the syntactical constructs you can use in your macros.


Model Scripting

Sometimes you will want to add interactivity to your model. For instance, maybe you would like to:

  • Dynamically show and hide different parts of the model in response to user actions
  • Reconfigure large parts of the model based on user inputs
  • Develop a custom analysis algorithm that explores the model
  • Develop a custom user interface for a model

Insight Maker supports a large API that let's you script and take control of a model. The language to use this API is JavaScript: a widely used language that is an integral component of most web applications. A list of all the commands and features in this API is available here.

The simplest way to add interactivity to your model is to add a Button primitive to the model. Each Button has an action which is JavaScript code that may call API commands. When the Button primitive is clicked by the user, its action is run.