Skip to content

Commit dfd4bd8

Browse files
authored
Merge pull request #40 from NACLab/v3
Merge/transfer from v3 to main branch (roll-out of ngc-sim-lib v3); already obtained approval internally from dev-team.
2 parents e7cd973 + 9e3960c commit dfd4bd8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+2625
-3638
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ build
55
ngcsimlib.egg-info
66

77
__pycache__
8+
/ngcsimlib/old/

CITATION.cff

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ message: "NGC-Sim-Lib: A Simulation Framework for Supporting NGC-Learn."
33
authors:
44
- family-names: Gebhardt, Ororbia
55
given-names: William, Alex
6+
orcid: https://orcid.org/0009-0008-7456-6556
67
- family-names: Ororbia
78
given-names: Alexander
89
orcid: https://orcid.org/0000-0002-2590-1310
910
title: "ngc-sim-lib"
10-
version: 0.2.0
11+
version: 3.0.0
1112
identifiers:
1213
- type: doi
1314
value: 10.5281/zenodo.10888211

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ $ pip install --editable . # OR pip install -e .
4141
</pre>
4242

4343
**Version:**<br>
44-
1.0.1 <!-- -Beta --> <!-- -Alpha -->
44+
3.0.0 <!-- -Beta --> <!-- -Alpha -->
4545

4646
Authors:
4747
William Gebhardt, Alexander G. Ororbia II<br>
@@ -50,10 +50,10 @@ Rochester Institute of Technology, Department of Computer Science
5050

5151
## <b>Copyright:</b>
5252

53-
Copyright (C) 2023 The Neural Adaptive Computing Laboratory - All Rights Reserved<br>
53+
Copyright (C) 2021 The Neural Adaptive Computing Laboratory - All Rights Reserved<br>
5454
You may use, distribute and modify this code under the
5555
terms of the BSD 3-clause license.
5656

5757
You should have received a copy of the BSD 3-clause license with
58-
this software.<br>
58+
this software.<br>
5959
If not, please [email us](mailto:[email protected])

docs/compartments.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Compartments
2+
3+
Within NGC-Sim-Lib, the global state serves as the backbone of any given model.
4+
This global state is essentially the culmination of all of the dynamic or changing parts of the model itself. Each
5+
value that builds this state is stored within a special "container" that helps track these changes over time -- this
6+
is referred to as a `Compartment`.
7+
8+
## Practical Information
9+
10+
Practically, when working with compartments, there are a few simple things to keep in mind despite the fact that most
11+
of NGC-Sim-Lib's primary operation is behind-the-scenes bookkeeping. The two main points to note are:
12+
1. Each compartment holds a value and, thus, setting a compartment with `myCompartment = newValue` will not function as
13+
intended since this will overwrite the Python object, i.e., the compartment with `newValue`. Instead, it is
14+
important to make use of the `.set()` method to update the value stored inside a compartment so
15+
`myCompartment = newValue` becomes `myCompartment.set(newValue)`.
16+
2. In order to retrieve a value from a compartment, use `myCompartment.get()`. These methods of getting and setting
17+
data inside a compartment are important to use when both working with and designing a multi-compartment component
18+
(i.e., `Component`).
19+
20+
## Technical Information
21+
22+
The follow sections are devoted to explication of more technical information regarding how a compartment functions
23+
with in the broader scope of NGC-Sim-Lib and, furthermore, to explain how to leverage this information.
24+
25+
### How Data is Stored (Within a Model Context)
26+
27+
The data stored inside of a compartment is not actually physically stored within a compartment. Instead, it is stored
28+
inside of the global state and each compartment effectively holds the path or `key` to the right spot in the global
29+
state, allowing it to pull out a specific piece of information. As such, it is technically possible to manipulate the
30+
value of a compartment without actually touching the compartment object itself within any given component. By default,
31+
compartments have in-built safeguards in order to prevent this from happening accidentally; however, directly addressing
32+
the compartment within the global state directly has no such safeguards.
33+
34+
### What is "Targeting"?
35+
36+
As discussed in the model building section, there is notion of "wiring" together different compartments of different
37+
components -- this is at the core of NGC-Learn's and NGC-Sim-Lib's "nodes-and-cables system". These wires are created
38+
through the concept of "targeting,", which is, in essence, just the updating of the path stored within a compartment
39+
using the path of a different compartment. This means that, if the targeted compartment goes to retrieve the value
40+
stored within it, it will actually retrieve the value of a different compartment (as dictated by the target). When a
41+
compartment is in this state -- where it is targeting another compartment -- it is set to read-only, which only means that
42+
it cannot modify a different compartment.
43+
44+

docs/compiling.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Compiling
2+
3+
The term "compiling" for NGC-Sim-Lib refers to automatic step that happens
4+
inside of a context that produces a transformed method for all of its
5+
components. This step is the most complicated part of the library and, in
6+
general, does not need to be touched or interacted with. Nevertheless, this
7+
section will cover most of the steps that the NGC-Sim-Lib compilation process
8+
does at a high level. This section contains advanced technical/developer-level
9+
information: there is an expectation that the reader has an understanding of
10+
Python abstract syntax trees (ASTs), Python namespaces, and how to
11+
dynamically compile Python code and execute it.
12+
13+
## The Decorator
14+
15+
In NGC-Sim-Lib, there is a decorator marked as `@compilable` which is used to
16+
add a flag to methods that the user wants to compile. On its own, this will not
17+
do anything; however, this decorator lets the parser distinguish between methods
18+
that should be compiled and methods that should be ignored.
19+
20+
## The Step-by-Step NGC-Sim-Lib Parsing Process
21+
22+
The process starts by telling the parser to compile a specific object.
23+
24+
### Step 1: Compile Children
25+
26+
The first step to compile any object is to make sure that all of the
27+
"compilable" objects of the top level object are compiled. As a
28+
result, NGC-Sim-Lib will loop through all of the whole object and will compile
29+
each part that it finds that is flagged as compilable (via the decorators
30+
mentioned above) and is, furthermore, an instance of a class.
31+
32+
### Step 2: Extract Methods to Compile
33+
34+
While the parser is looping through all of the parts of the top-level object, it
35+
is also extracting the methods on/embedded to the object that are flagged as
36+
compilable (with the decorator above). NGC-Sim-Lib stores them for later;
37+
however, this lets the parser only loop over the object once.
38+
39+
### Step 3: Parse Each Method
40+
41+
As each method is its own entry-point into the transformer, this step will run
42+
for each method in the top-level object.
43+
44+
### Step 3a: Set up a Transformer
45+
46+
This step sets up a `ContextTransformer`, which further makes use of a
47+
`ast.NodeTransformer`, and will convert methods from class methods (with the use
48+
of `self`), as well as other methods that need to be removed / ignored, into
49+
their more context-friendly counterparts.
50+
51+
### Step 3b: Transform the Function
52+
53+
There are quite a few pieces of common Python that need to be transformed. This
54+
step happens with the overall goal of replacing all object-focused parts with a
55+
more global view. This means that a compartment's `.get` and `.set` calls are
56+
replaced with direct setting and getting from the global state, based on the
57+
compartment's target. This also means that all temporally constant values --
58+
such as `batch_size` -- are moved into the globals space for that specific file
59+
and ultimately replaced with the naming convention of `object_path_constant`.
60+
One more key step that is performed is to ensure that there is no branching in
61+
the code. Specifically, if there is a branch, i.e., an if-statement, NGC-Sim-Lib
62+
will evaluate it and only keep the branch it will traverse down. This means that
63+
there cannot be any branch logic based on inputs or computed values (this is a
64+
common restriction for just-in-time compiling).
65+
66+
### Step 3c: Parse Sub-Methods
67+
68+
Since it is possible to have other class methods that are not marked as
69+
entry-points for compilation but still need to be compiled, as step 3b happens,
70+
NGC-Sim-Lib tracks all of the sub-methods required. Notably, this step goes
71+
through and repeats steps 3a and 3b for each of the (sub-)methods with a naming
72+
convention similar to the temporally constant values for each method.
73+
74+
### Step 3d: Compile the Abstract Syntax Tree (AST)
75+
76+
Once we have all of the namespace and globals needed to execute the
77+
properly-transformed method, the method is compiled with Python and finally
78+
executed.
79+
80+
### Step 3e: Binding
81+
82+
The final step per method is to bind each to their original method; this
83+
replaces each method with an object which, when called, will act like the
84+
normal, uncompiled version but has the addition of the `.compiled` attribute.
85+
This attribute contains all of the compiled information to be used later (for
86+
model / system simulation). This crucially allows for the end user to
87+
call `myComponent.myMethod.compiled()` and have it run. The exact type for
88+
a `compiled` value can be found
89+
in `ngcsimlib._src.parser.utils:CompiledMethod`.
90+
91+
### Step 4: Finishing Up / Final Processing
92+
93+
Some objects, such as the processes, entail additional steps to modify
94+
themselves or their compiled methods in order to align themselves with needed
95+
functionality. However, this operation/functionality is found within each
96+
class's expanded `compile` method and should be referred to by looking at those
97+
methods specifically.
98+

docs/components.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Components
2+
3+
Living one step above compartments in the NGC-Learn dynamical systems hierachy rests the component.
4+
A component (`ngcsimlib.Component`) holds a collection of both temporally constant values as well as dynamic (time-evolving)
5+
compartments. In addition, they are the core place where logic governing the dynamics of a system are
6+
defined. Generally, components serve as the building blocks that are to be reused multiple times
7+
when constructing a complete model of a dynmical system.
8+
9+
## Temporally Constant versus Dynamic Compartments
10+
11+
One important distinction that needs to be highlighted within a component is the
12+
difference between a temporally constant value and a dynamic (time-varying) compartment.
13+
Compartments themselves house values that change over time and, generally, they will have the
14+
type `ngcsimlib.Compartment`; note that compartments are to be used to track the internal values
15+
of a component. These internal values can be ones such inputs, decaying values, counters, etc.
16+
The second kind of values found within a component are known as temporally constant values; these
17+
are values (e.g., hyper-parameters, structural parameters, etc.) that will remain fixed
18+
within constructed model dynamical system. These types of values tend to include common configuration
19+
and meta-parameter settings, such as matrix shapes and coefficients.
20+
21+
## Defining Compilable Methods
22+
23+
Inside of a component, it is expected that there will be methods defined that govern the
24+
temporal dynamics of the system component. These compilable methods are decorated
25+
with `@compilable` and are defined like any other regular (Python) method. Within a compilable
26+
method, there will be access to `self`, which means that, to reference a compartment's
27+
value, one must write out such a call as: `self.myCompartment.get()`. The only requirement is
28+
that any method that is decorated <b>cannot</b> have a return value; values should be stored
29+
inside their respective compartments (by making an appeal to their respective set routine, i.e.,
30+
`self.myCompartment.set(value)`). In an external (compilation) step, outside of the developer's
31+
definition of a component, an NGC-Sim-Lib transformer will change/convert all of these (decorated)
32+
methods into ones that function with the rest of the NGC-Sim-Lib back-end.

docs/context.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Contexts
2+
3+
Contexts, in NGC-Sim-Lib, are the top-level containers that hold everything used to
4+
define a model / dynamical system. On their own, contexts have no runtime logic;
5+
they rely on their internal processes and components to build a complete, working model.
6+
7+
## Defining a Context
8+
9+
To define a context (`ngcsimlib.Context`), NGC-Sim-Lib leverages the `with` block; this
10+
means that to create a new context, simply start with the statement
11+
`with Context("myContext") as ctx:` and a new context will be created.
12+
(<i>Important Note</i>: names are unique; if a context is created with the same name,
13+
they will be the same context and, thus, there might be conflicts).
14+
A defined context does not do anything on its own.
15+
16+
## Adding Components
17+
18+
To add components to a context, simply initialize components while inside
19+
the `with` block of the context. Any component defined while inside this block
20+
will automatically be added and tacked-on to the context object.
21+
22+
## Wiring Components
23+
24+
Inside of a model / dynamical system, components will need to pass data to one
25+
another; this is configured within the context. To connect the compartments of
26+
two components, follow the pattern: `mySource.output >> myDestination.input`,
27+
where `output` and `input` are compartments inside their respective components.
28+
This format will ensure that, when processes are being run, the value will
29+
flow properly from component to component.
30+
31+
### Operators
32+
33+
There is a special type of wire called an operator; this performs a simple
34+
operation on the compartment values as the data flows from one component to
35+
another. Generally, these are use for simple mathematical operations, such as
36+
negation `Negate(mySource.output) >> myDestination.input` or the summation of
37+
multiple compartments into
38+
one `Summation(mySource1.output, mySource2.output, ...) >> myDestination.input`.
39+
Note that operators can be chained, so it would be possible to negate one or
40+
more of the inputs that flow into the summation.
41+
42+
## Adding Processes
43+
44+
To add processes to a context, simply initialize the process and add all of its
45+
steps while inside the `with`-block of the process.
46+
47+
## Exiting the `with` block
48+
49+
When the context exits the `with`-block, it will re-compile the entire model.
50+
Behind the scenes, this is calling `recompile` on the context
51+
itself; it is possible to manually trigger the recompile step, but doing so can
52+
break certain connections (between components/compartments), so use this
53+
functionality sparingly.
54+
55+
## Saving and Loading
56+
57+
The context's one unique job is the handling of the "saving" (serialization) and
58+
"loading" (de-serialization) of models to disk. By default, calling
59+
`save_to_json(...)` will create the correct file structure as well as the core files
60+
needed and load the context in the future. To load / de-serialize a model,
61+
calling `Context.load(...)` will load the context in from a directory; something
62+
important to note is that loading in a context entails effectively
63+
recreating the components with their initial values using their arguments as well as
64+
keywords arguments (excluding those that cannot be serialized). This means that,
65+
if you have a trained model, ensure that your components have a save method
66+
defined that will handle the saving and loading of all values within their compartments.
67+

docs/global_state.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# The Global State
2+
3+
Since NGC-Sim-Lib is a simulation library focused on temporal models and dynamical
4+
systems, or models that change over time there, it is foundational that all models
5+
(and their respective elements) have some concept of a "state". These states
6+
might be comprised of a single value that changes/evolves or of a complex set of values
7+
that, when combined all together, make up the full dynamical system that underwrites the
8+
final model. In both cases, these sets of values are stored in what is known as the
9+
<i>global state</i>.
10+
11+
## Interacting with the Global State
12+
13+
Since the global state will contain a large amount of information describing a given
14+
model, there will be a need to facilitate interaction with and modification of the values
15+
contained within the global state. In most use-cases, this is not done directly. The
16+
most common way to interact with the global state is through the use of the state-manager.
17+
The state-manager exists to provide a set of helper methods for interacting with the
18+
global state itself. Note that, although the manager is there to assist you, it will not stop
19+
you from changing the state (or "breaking" the state). When changing the state -- beyond
20+
setting it through the specificaiton of <i>processes</i> -- be careful to not add or remove
21+
anything that is needed for your actual model.
22+
23+
### Adding New Fields to the Global State
24+
25+
If you are new to using NGC-Sim-Lib and looking for a way to add values to the
26+
global state directly and explicitly, stop for a moment and reconsider. Unless
27+
you know exactly what you are doing (i.e., doing core development), it is strongly
28+
advised to not manually add values to the global state; instead, work through the
29+
mechanisms afforded by compartments and/or components, as these are built to afford you the
30+
most common ways for adding fields to the global state itself. The dynamical systems
31+
semantics inherent to compartments and components is meant to ensure carefully-constrained
32+
design and simulation of flexible models.
33+
34+
If you actually intend to manually and directly add values to the global state itself, it
35+
is done through the use of the `add_key` method. This will create the appropriate key in
36+
the global state for the given path and name; furthermore, its value can be retrieved
37+
with `from_key` calls. This value, however, is not linked to a compartment and, therefore,
38+
will be hard to get working properly in the compiled methods without some specific references.
39+
<i>Please take extra care when working directly and explicitly with the global state.</i>
40+
41+
### Getting the Current State
42+
43+
To get the current state, simply call `global_state_manager.state`; this will give
44+
you a (shallow) copy of the current state, which means that any modifications made to it will
45+
not be reflected in the global state.
46+
47+
### Updating the Global State
48+
49+
To manually update the global state after modifying a local copy; please write an overriding
50+
call command: `global_state_manager.state = new_state`. This will update the state with the
51+
`.update` call to its underlying dictionaries, which means that a partial state will still update correctly.
52+

0 commit comments

Comments
 (0)