SimGen is a simulation language, originally created by Simularity, Inc.
In May 2019 Simularity released SimGen under the terms of the BSD 3-clause license.
SimGen is a library for SWI-Prolog that runs simulations expressed in the BT language.
BT is a declarative language based on behavior trees. You describe how things behave by combining primitive behavior elements, nodes, into descriptions of behavior.
So, what is behavior?
A behavior is a gerund, a verbal noun. And much of human thinking is in terms of gerunds. "We'll all go eat and then see a movie, then Lisa will drive Betty home". Three behaviors (eat, see movie, drive home), in sequence.
These involve some different 'objects' - the complete group of people, Lisa, Betty, the movie theater, and so on, but the core of what's happening is in the behaviors. BT is about behaviors.The 'things' of BT are behaviors, rather than the objects of an OO language.
So a play, a revolution, or an airplane flight are behaviors. A play involves the audience buying tickets, being seated, watching the actors in a series of acts, applauding, and leaving the theater. That's a play. If the audience stays home and the actors sit quietly in the theater seats for an hour while the director burns the sets, then the stage hands applaud, and the curtain opens, we don't really have a play.
Behaviors are constructed from primitive behaviors we call nodes. BT provides a limited number of types of nodes.
Each type of node provides some way of combining child behaviors. For example, the sequence node combines its' children by running them one after the other.
So if we already have all_go_eat
, see_movie
, and lisa_drives_betty_home
nodes, we can make evening_out
by using the sequence operator.
evening_out ->
all_go_eat,
see_movie,
lisa_drives_betty_home.
We can talk about abstract behaviors that can be instantiated. "Driving" is abstract behavior, while "Bob driving his 1923 Roadster down route 22 right now" is an instantiation of that behavior.
A whole play is a behavior. Seating the audience is a behavior. Since one part of a play is seating the audience, seat_audience
is a child of perform_play
. And seat_audience
might have behaviors for ushers helping people find their seats, a concession stand selling goodies, reminder to turn off cell phones, and so on.
An abstract behavior might occur in several settings. For example, seat_audience
could also be a sub-behavior of a concert or movie. Since several behaviors might have a similar sub-behavior, abstract behaviors form a directed acyclic graph (DAG).
Probably not all of that graph is happening at once. Our party isn't both eating dinner and seeing the movie at 7:42PM, though they might be eating popcorn and watching the movie at the same time. So the graph of currently active nodes is a subgraph of the abstract behavior DAG.
When an actual, specific audience is seated, they have come to the theater expecting to see Hamlet, not to hear Pink Floyd. Even though seat_audience
abstractly has multiple parents (a concert, a movie, a play), this audience is here for Hamlet. So each concrete, active node has a single parent. And hence the concrete graph of active nodes would naturally form a tree.
However, we can externally start additional nodes from Prolog, and contexts can interact. So there can be multiple root nodes running. And this makes the set of running nodes a forest.
A node is started, runs, and then stops in one of 3 ways:
When you shut off a car, a lot of things stop moving - the fuel pump stops pumping, the radio stops playing, the crankshaft stops rotating... Stopping the 'run car' action terminates all these lesser actions.
SimGen keeps the running nodes a forest by terminating all running children under a parent when the parent stops.
What happens when a second source tries to start a node that's already running? Nothing. There is no restart in SimGen. When A node ends, all parents are notified. However, only the parent that actually spawned the node will terminate the node when it dies. Note that this can 'hang' a second parent. Generally, multi-parenting is a minefield. It's a good rule to only multiparent if you're confident only one will actually try to instantiate the node.
SimGen provides variables, which are floats, and conditions, which are booleans.
SimGen variables and conditions need not be declared. All are per context scope.
In the future we want to move SimGen towards making all variables 'understand' partial differential equations, known as PDQ nodes, so that most PDQ nodes don't need to explicitly be simulated.
All theaters operate in more or less the same way, but we might have several theaters that each have their own specific values, and interact. We call these contexts.
Note that a context is not an object, but rather a whole copy of the 'world'.
Suppose we want to model a large boiler fed by four pumps. We could have a context for the boiler itself and most other equipment, and a context for each pump.
In SimGen all variables are context scope.
Into the simulation you add contexts. For our play example, we might have multiple theaters, putting on different plays, but generally doing the same thing. When a theater actually opens, we start a context.
A context is a set of current values, current conditions, a time since the context started, and a set of running nodes.
When we start the context we give the name of a single node.
Inter-context interaction isn't implemented yet. When it is, it will be possible to run a node in another context.
You supply a set of definitions of how some things behave (abstract behaviors), in a BT language file with .bt
extension.
The .bt
file can be loaded with use_bt/1
.
You then create a simulation using start_simulation/4
.
Into the simulation you add contexts with start_context/3
.
Call end_simulation/0
to end the simulation.
SimGen depends heavily on library(broadcast)
. Much of the interaction that occurs in SimGen is facilitated by registering listeners using library(broadcast)
.
Messages of the form tick(Extern, Tick, NewExtern)
occur each tick.
When the simulation is started, an Extern value is passed. This is external state available for Prolog.
When a tick occurs, the tick listener binds the new value of Extern to NewExtern.
When a node starts, a starting(Context-Name)
message is emitted.
When a node stops, a stopped(Context-Name, Reason)
message is emitted.
Reason
will have one of the following values:
done
the node completed successfullyfail
the node failedterminated
the node was terminated by a higher nodeWhen the pin
node is used, pin_drop(Context, Time, event)
and pin_drop(Context, Time, '-event')
events are broadcast.
Whenever a value is changed, reading(Time, Module, Context, Name, Value)
events are emitted.
See getval/2
and setval/3
in module valuator
to read/change values from Prolog.
See set_guard/2
, clear_guard/2
, and guard/2
in module guard_manager
to read/change conditions from prolog.
A useful idiom
wait_for_prolog =>
{clear my_condition},
{-? my_condition}.
... in prolog code ....
set_guard(Context, my_condition)
TODO make a more elegant call node type
SimGen advances in ticks.
Internally times are represented in 'nanos', nanoseconds since the unix epoch.
We can choose any time unit we like. If we want minutes, then 60_000_000_000
nanos is one unit.
We can start the simulation at any time we choose.
The simulation runs in discrete ticks, and we can choose the length of a tick.
Each context also gets a clock, which starts at zero when the context starts.
This isn't working yet. Next day or two I get to work on SimGen it goes in.
When making a simulation, what you leave out is just as important as what you include.
TODO
TODO update
behavior - We humans talk about systems in terms of their behavior. Servers start, stop, handle requests, generate 404 errors, call the database, etc. Shoppers check online, then check a store, perhaps learn from a clerk that they actually want something else, realize they have the something else... BT describes the world in terms of a set of fundamental behaviors.
BT - the language SimGen programs are defined in. Stored in files ending .bt
A context - Often it's useful to have more than one 'copy' of a thing - A simulation of people might have a bunch of people modelled with identical code. The external Prolog program starts a single behavior (node) with a context.
A C-N Pair - Context-node pair. internal name for an active node
A node - a fundamental unit of behavior - do things in sequence. Do them in parallel. Try things until one succeeds. We're migrating away from describing nodes to talking of behaviors and sub-behaviors.
A simulation - a collection of interacting systems that can be run forward in time.
values - floating point variables. All SimGen values have context scope.
conditions - boolean variables. All SimGen conditions have context scope.
A BT file is a sequence of nodes, and comments.
% from percent sign to end of line is a comment
/* multi-line C style comments are supported */
Nodes have the format
<head> <operator>
<type dependent child info>
.
is an atom, the name of the node.
Anywere a _child_ can occur, an _anonymous node_ can be substituted.
````
{