Processes
Processes in NGC-Sim-Lib offer a central way of defining a specific transition to be taken within a given model (this effectively sets up the behavior of the state-machine that defines the desired dynamical system one wants to simulate). In effect, processes take in as many compilable methods as possible across any number of components; they work to produce a single top-level method and a varying number of sub-methods needed to execute the entire chain of compilable methods in one (single) step. This is ultimately done to interface nicely with just-in-time (JIT) compilers, such as the one inherent to JAX, and to minimize the amount of read and write calls done across a chain of methods.
Building the (Command) Chain
Building the chain that a process will use is done through an iterative process. Once
the process object is created, steps are added using either .then() or >>.
As an example:
myProcess.then(myCompA.forward).then(myCompB.forward).then(myCompA.evolve).then(myCompB.evolve)
or
myProcess >> myCompA.forward >> myCompB.forward >> myCompA.evolve >> myCompB.evolve
In both cases, this process will chain the four methods together into a single step, only updating the final state after all steps are complete.
Types of Processes
There are two types of processes: the above example would be with what is
referred to as a MethodProcess – these are used to chain together any
compilable methods from any number of different components. The other second
type of process, called a JointProcess in NGC-Sim-Lib, is used to chain
together entire processes.
JointProcesses are especially useful if there are multiple method processes that
need to be called but different orders of the processes are needed at different
times. These allow for the specification of complex events / behaviors in a
dynamical system that one will simulate.
Extra Elements
There are a few extra methods that come standard with each process type which can be useful for both regular operation as well as debugging.
Viewing the Compiled Method
Behind the scenes, a process is transforming and compiling down all of the steps
used to build it; this means that the exact code it is running to do its
set of calculations will ultimately not be what the user wrote. To allow for
the end user to view and make sure that the two pieces of code – theirs and
the compiled version – are equivalent (and yielding expected behavior), every
process has a view_compiled_method() call which can be used after the (final) model
is compiled. This call will return the code (block) that it will be running as a
string. There will be some stark differences between the produced/generated code and
the code in the (Python) components used to build the steps. Please refer to the
compiling page for a more in-depth guide to comparing the outputs between these
two stages of code.
Needed Keywords
Since some methods will require external values such as t (for time) or dt
(for integration time / the temporal delta) for a given execution, a process
will also track all the keyword arguments that are needed to run their compiled
process. To view which keywords a given process is expecting, one may use the
command: get_keywords().
This is mostly used for debugging and/or as a sanity check.
Packing Keywords
To add onto the needed keywords, the process also provides an interface to
produce the keywords needed to run in the form of two methods. The first method
is pack_keywords(...); this method packs together a single row of values that
are needed to run a single execution (step) of the process. The arguments are
the row_seed, which is a seed that is to be passed to all of the keyword
generators (only needed if generators are being used).
The second set of arguments are keyword arguments that are either constant,
such as dt=0.1, or generators, such as lambda row_seed: 0.1 * row_seed.
The second method for generating the keywords for a process is with pack_rows(...).
This method will create many sets of keywords that are needed to run multiple
iterations of the process. Note that the arguments are slightly different: first,
it now utilizes a length argument to indicate the number of rows being produced and,
second, it features a seed_generator that is used to generate the seed of each row
(for instance, to have only even seed values: seed_generator = lambda x: 2 * x); if
the generator is None, then seed_generator = lamda x: x is used.
After this, the same keyword arguments to define the needed parameters are used as in pack_keywords.