ngcsimlib 0.2b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ngcsimlib/__init__.py +50 -0
- ngcsimlib/bundle_rules.py +64 -0
- ngcsimlib/commands/__init__.py +11 -0
- ngcsimlib/commands/advanceState.py +25 -0
- ngcsimlib/commands/clamp.py +58 -0
- ngcsimlib/commands/command.py +34 -0
- ngcsimlib/commands/compound.py +39 -0
- ngcsimlib/commands/evolve.py +41 -0
- ngcsimlib/commands/multiclamp.py +42 -0
- ngcsimlib/commands/reset.py +40 -0
- ngcsimlib/commands/save.py +46 -0
- ngcsimlib/commands/snapshot.py +39 -0
- ngcsimlib/commands/track.py +50 -0
- ngcsimlib/component.py +359 -0
- ngcsimlib/controller.py +378 -0
- ngcsimlib/utils.py +237 -0
- ngcsimlib-0.2b0.dist-info/AUTHORS +10 -0
- ngcsimlib-0.2b0.dist-info/LICENSE +29 -0
- ngcsimlib-0.2b0.dist-info/METADATA +69 -0
- ngcsimlib-0.2b0.dist-info/RECORD +22 -0
- ngcsimlib-0.2b0.dist-info/WHEEL +5 -0
- ngcsimlib-0.2b0.dist-info/top_level.txt +1 -0
ngcsimlib/component.py
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import warnings
|
|
3
|
+
import ngcsimlib.utils as utils
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _VerboseDict(dict):
|
|
7
|
+
"""
|
|
8
|
+
The Verbose Dictionary functions like a traditional python dictionary with
|
|
9
|
+
more specific warnings and errors.
|
|
10
|
+
Specifically each verbose dictionary logs when new keys are added to it,
|
|
11
|
+
and when a key is asked for that is not present in the dictionary, it will
|
|
12
|
+
throw a runtime error that includes the name (retrieved from an ngcsimlib
|
|
13
|
+
component) to make debugging/tracing easier.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
seq: sequence of items to add to the verbose dictionary
|
|
17
|
+
|
|
18
|
+
name: the string name of this verbose dictionary
|
|
19
|
+
|
|
20
|
+
kwargs: the keyword arguments to first try to extract from
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, seq=None, name=None, verboseDict_showSet=False, verboseDict_autoMap=False,
|
|
24
|
+
componentClass=None, **kwargs):
|
|
25
|
+
seq = {} if seq is None else seq
|
|
26
|
+
name = 'unnamed' if name is None else str(name)
|
|
27
|
+
|
|
28
|
+
super().__init__(seq, **kwargs)
|
|
29
|
+
self.name = name
|
|
30
|
+
self.componentClass = componentClass
|
|
31
|
+
|
|
32
|
+
self.showSet = verboseDict_showSet
|
|
33
|
+
self.autoMap = verboseDict_autoMap
|
|
34
|
+
self.mapping = {}
|
|
35
|
+
|
|
36
|
+
def __setitem__(self, key, value):
|
|
37
|
+
if self.showSet and key not in self.keys():
|
|
38
|
+
warnings.warn("Adding key \"" + str(key) + "\" to " + self.name)
|
|
39
|
+
super().__setitem__(key, value)
|
|
40
|
+
|
|
41
|
+
def __getitem__(self, item):
|
|
42
|
+
if item in self.mapping.keys():
|
|
43
|
+
return super().__getitem__(self.mapping[item])
|
|
44
|
+
|
|
45
|
+
if item not in self.keys():
|
|
46
|
+
if not isinstance(getattr(self.componentClass, item, None), property):
|
|
47
|
+
raise RuntimeError("Failed to find compartment \"" + str(item) + "\" in " + self.name +
|
|
48
|
+
"\nAvailable compartments " + str(self.keys()))
|
|
49
|
+
|
|
50
|
+
nameGetter = getattr(self.componentClass, item + "CompartmentName", None)
|
|
51
|
+
|
|
52
|
+
if nameGetter is None:
|
|
53
|
+
raise RuntimeError("Failed to find compartment \"" + str(item) + "\" in " + self.name +
|
|
54
|
+
"\n\"" + str(item) + "\" is a property of " + self.name +
|
|
55
|
+
", but unable to find key for requested compartment based on property name"
|
|
56
|
+
"\nAvailable compartments " + str(self.keys()))
|
|
57
|
+
|
|
58
|
+
else:
|
|
59
|
+
expectedName = nameGetter()
|
|
60
|
+
if not self.autoMap:
|
|
61
|
+
raise RuntimeError("Failed to find compartment \"" + str(item) + "\" in " + self.name +
|
|
62
|
+
"\nThe correct compartment for \"" + str(item) + "\" is " + str(expectedName) +
|
|
63
|
+
"\nAll available compartments " + str(self.keys()))
|
|
64
|
+
warnings.warn("Failed to find compartment \"" + str(item) + "\" in " + self.name +
|
|
65
|
+
"\nThe correct compartment for \"" + str(item) + "\" is " + str(expectedName) +
|
|
66
|
+
"\nMapping \"" + str(item) + "\" to \"" + str(expectedName) + "\"")
|
|
67
|
+
self.mapping[item] = expectedName
|
|
68
|
+
return super().__getitem__(self.mapping[item])
|
|
69
|
+
return super().__getitem__(item)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class _ComponentMetadata:
|
|
73
|
+
"""
|
|
74
|
+
Component Metadata is used to track the incoming and outgoing connections
|
|
75
|
+
to a component. This is also where all of the root calls exist for verifying
|
|
76
|
+
that components have the correct number of connections.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
name: the string name of this component metadata object
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, name, **kwargs):
|
|
83
|
+
self.component_name = name
|
|
84
|
+
self._incoming_connections = {}
|
|
85
|
+
self._outgoing_connections = {}
|
|
86
|
+
|
|
87
|
+
def add_outgoing_connection(self, compartment_name):
|
|
88
|
+
"""
|
|
89
|
+
Adds an outgoing connection ("cable") to this component to a compartment
|
|
90
|
+
in a node/component elsewhere.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
compartment_name: compartment target in an outgoing component/node
|
|
94
|
+
"""
|
|
95
|
+
if compartment_name not in self._outgoing_connections.keys():
|
|
96
|
+
self._outgoing_connections[compartment_name] = 1
|
|
97
|
+
else:
|
|
98
|
+
self._outgoing_connections[compartment_name] += 1
|
|
99
|
+
|
|
100
|
+
def add_incoming_connection(self, compartment_name):
|
|
101
|
+
"""
|
|
102
|
+
Adds an incoming connection ("cable") to this component from a compartment
|
|
103
|
+
in a node/component elsewhere.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
compartment_name: compartment source in an incoming component/node
|
|
107
|
+
"""
|
|
108
|
+
if compartment_name not in self._incoming_connections.keys():
|
|
109
|
+
self._incoming_connections[compartment_name] = 1
|
|
110
|
+
else:
|
|
111
|
+
self._incoming_connections[compartment_name] += 1
|
|
112
|
+
|
|
113
|
+
def check_incoming_connections(self, compartment, min_connections=None, max_connections=None):
|
|
114
|
+
"""
|
|
115
|
+
Checks/validates the incoming information source structure/flow into this component.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
compartment: compartment from incoming source to check
|
|
119
|
+
|
|
120
|
+
min_connections: minimum number of incoming connections this component should receive
|
|
121
|
+
|
|
122
|
+
max_connections: maximum number of incoming connections this component should receive
|
|
123
|
+
"""
|
|
124
|
+
if compartment not in self._incoming_connections.keys() and min_connections is not None:
|
|
125
|
+
raise RuntimeError(
|
|
126
|
+
str(self.component_name) + " has an incorrect number of incoming connections.\nMinimum connections: " +
|
|
127
|
+
str(min_connections) + "\t Actual Connections: None")
|
|
128
|
+
|
|
129
|
+
if compartment in self._incoming_connections.keys():
|
|
130
|
+
count = self._incoming_connections[compartment]
|
|
131
|
+
if min_connections is not None and count < min_connections:
|
|
132
|
+
raise RuntimeError(
|
|
133
|
+
str(self.component_name) + "has an incorrect number of incoming connections.\nMinimum "
|
|
134
|
+
"connections: " +
|
|
135
|
+
str(min_connections) + "\tActual Connections: " + str(count))
|
|
136
|
+
if max_connections is not None and count > max_connections:
|
|
137
|
+
raise RuntimeError(
|
|
138
|
+
str(self.component_name) + "has an incorrect number of incoming connections.\nMaximum "
|
|
139
|
+
"connections: " +
|
|
140
|
+
str(max_connections) + "\tActual Connections: " + str(count))
|
|
141
|
+
|
|
142
|
+
def check_outgoing_connections(self, compartment, min_connections=None, max_connections=None):
|
|
143
|
+
"""
|
|
144
|
+
Checks/validates the outgoing information structure/flow from this component.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
compartment: compartment from incoming source to check
|
|
148
|
+
|
|
149
|
+
min_connections: minimum number of incoming connections this component should receive
|
|
150
|
+
|
|
151
|
+
max_connections: maximum number of incoming connections this component should receive
|
|
152
|
+
"""
|
|
153
|
+
if compartment not in self._outgoing_connections.keys() and min_connections is not None:
|
|
154
|
+
raise RuntimeError(
|
|
155
|
+
str(self.component_name) + " has an incorrect number of outgoing connections.\nMinimum connections: " +
|
|
156
|
+
str(min_connections) + "\t Actual Connections: None")
|
|
157
|
+
|
|
158
|
+
if compartment in self._outgoing_connections.keys():
|
|
159
|
+
count = self._outgoing_connections[compartment]
|
|
160
|
+
if min_connections is not None and count < min_connections:
|
|
161
|
+
raise RuntimeError(
|
|
162
|
+
str(self.component_name) + " has an incorrect number of outgoing connections.\nMinimum "
|
|
163
|
+
"connections: " +
|
|
164
|
+
str(min_connections) + "\tActual Connections: " + str(count))
|
|
165
|
+
if max_connections is not None and count > max_connections:
|
|
166
|
+
raise RuntimeError(
|
|
167
|
+
str(self.component_name) + " has an incorrect number of outgoing connections.\nMaximum "
|
|
168
|
+
"connections: " +
|
|
169
|
+
str(max_connections) + "\tActual Connections: " + str(count))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Component(ABC):
|
|
173
|
+
"""
|
|
174
|
+
Components are a foundational part of ngclearn and its component/command
|
|
175
|
+
structure. In ngclearn, all stateful parts of a model take the form of
|
|
176
|
+
components. The internal storage of the state within a component takes one
|
|
177
|
+
of two forms, either as a compartment or as a member variable. The member
|
|
178
|
+
variables are values such as hyperparameters and weights/synaptic efficacies,
|
|
179
|
+
where the transfer of their individual state from component to component is
|
|
180
|
+
not needed.
|
|
181
|
+
Compartments, on the other hand, are where the state information, both from
|
|
182
|
+
and for other components, are stored. As the components are the stateful
|
|
183
|
+
pieces of the model, they also contain the methods and logic behind advancing
|
|
184
|
+
their internal state (values) forward in time.
|
|
185
|
+
|
|
186
|
+
The use of this abstract base class for components is completely optional.
|
|
187
|
+
There is no part of ngclearn that strictly dictates/requires its use; however,
|
|
188
|
+
starting here will provide a good foundation for development and help avoid
|
|
189
|
+
errors produced from missing attributes. That being said, this is not an
|
|
190
|
+
exhaustive/comprehensive base class. There are some commands such as `Evolve`
|
|
191
|
+
that requires an additional method called `evolve` to be present within the
|
|
192
|
+
component.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
def __init__(self, name, useVerboseDict=False, **kwargs):
|
|
196
|
+
"""
|
|
197
|
+
The only truly required parameter for any component in ngclearn is a
|
|
198
|
+
name value. These names should be unique; otherwise, there will be
|
|
199
|
+
undefined behavior present if multiple components in a model have the
|
|
200
|
+
same name.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
name: the name of the component
|
|
204
|
+
|
|
205
|
+
useVerboseDict: a boolean value that controls if a more debug
|
|
206
|
+
friendly dictionary is used for this component's compartments.
|
|
207
|
+
This dictionary will monitor when new keys are added to the
|
|
208
|
+
compartments dictionary and tell you which component key errors
|
|
209
|
+
occur on. It is not recommended to have these turned on when
|
|
210
|
+
training as they add additional logic that might cause a
|
|
211
|
+
performance decrease. (Default: False)
|
|
212
|
+
|
|
213
|
+
kwargs: additional keyword arguments. These are not used in the base class,
|
|
214
|
+
but this is here for future use if needed.
|
|
215
|
+
"""
|
|
216
|
+
# Component Data
|
|
217
|
+
self.name = name
|
|
218
|
+
|
|
219
|
+
self.compartments = _VerboseDict(name=self.name, componentClass=self.__class__, **kwargs) if useVerboseDict else {}
|
|
220
|
+
self.bundle_rules = {}
|
|
221
|
+
self.sources = []
|
|
222
|
+
|
|
223
|
+
# Meta Data
|
|
224
|
+
self.metadata = _ComponentMetadata(name=self.name, **kwargs)
|
|
225
|
+
|
|
226
|
+
##Intialization Methods
|
|
227
|
+
def create_outgoing_connection(self, source_compartment):
|
|
228
|
+
"""
|
|
229
|
+
Creates a callback function to a specific compartment of the component.
|
|
230
|
+
These connections are how other components will read specific parts of
|
|
231
|
+
this components state at run time.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
source_compartment: the specific compartment whose state will be
|
|
235
|
+
returned by the callback function
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
a callback function that takes no parameters and returns the state
|
|
239
|
+
of the specified compartment
|
|
240
|
+
"""
|
|
241
|
+
self.metadata.add_outgoing_connection(source_compartment)
|
|
242
|
+
return lambda: self.compartments[source_compartment]
|
|
243
|
+
|
|
244
|
+
def create_incoming_connection(self, source, target_compartment, bundle=None):
|
|
245
|
+
"""
|
|
246
|
+
Binds callback function to a specific local compartment.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
source: The defined callback function generally produced by
|
|
250
|
+
`create_outgoing_connection`
|
|
251
|
+
|
|
252
|
+
target_compartment: The local compartment the value should be stored in
|
|
253
|
+
|
|
254
|
+
bundle: The bundle or input rule to be used to put the data into the
|
|
255
|
+
compartment, this is overwriting the value by default, but rules
|
|
256
|
+
like appending and adding are also provided in `bundle_rules.py`
|
|
257
|
+
"""
|
|
258
|
+
self.metadata.add_incoming_connection(target_compartment)
|
|
259
|
+
if bundle not in self.bundle_rules.keys():
|
|
260
|
+
self.create_bundle(bundle, bundle)
|
|
261
|
+
self.sources.append((source, target_compartment, bundle))
|
|
262
|
+
|
|
263
|
+
def create_bundle(self, bundle_name, bundle_rule_name):
|
|
264
|
+
"""
|
|
265
|
+
This tracks the all the bundle rules that the component will need while gathering information
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
bundle_name: the local name of the bundle name
|
|
269
|
+
|
|
270
|
+
bundle_rule_name: the rule name or keyword defined in `modules.json`
|
|
271
|
+
"""
|
|
272
|
+
if bundle_name is not None:
|
|
273
|
+
rule = utils.load_attribute(bundle_rule_name)
|
|
274
|
+
self.bundle_rules[bundle_name] = rule
|
|
275
|
+
else:
|
|
276
|
+
try:
|
|
277
|
+
rule = utils.load_attribute(bundle_rule_name)
|
|
278
|
+
except:
|
|
279
|
+
from .bundle_rules import overwrite
|
|
280
|
+
rule = overwrite
|
|
281
|
+
self.bundle_rules[bundle_name] = rule
|
|
282
|
+
|
|
283
|
+
## Runtime Methods
|
|
284
|
+
def clamp(self, compartment, value):
|
|
285
|
+
"""
|
|
286
|
+
Sets a value of a compartment to the provided value
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
compartment: targeted compartment
|
|
290
|
+
|
|
291
|
+
value: provided Value
|
|
292
|
+
"""
|
|
293
|
+
self.compartments[compartment] = value
|
|
294
|
+
|
|
295
|
+
def process_incoming(self):
|
|
296
|
+
"""
|
|
297
|
+
Calls each callback function and yields results from all connections
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
yields tuples of the form (value, target_compartment, bundle)
|
|
301
|
+
"""
|
|
302
|
+
for (source, target_compartment, bundle) in self.sources:
|
|
303
|
+
yield source(), target_compartment, bundle
|
|
304
|
+
|
|
305
|
+
def pre_gather(self):
|
|
306
|
+
"""
|
|
307
|
+
Optionally implemented method that is called before all the connections
|
|
308
|
+
are processed. An example use case for this method is that if a compartment
|
|
309
|
+
should be reset before multiple cables under the additive bundle rule
|
|
310
|
+
are processed.
|
|
311
|
+
"""
|
|
312
|
+
pass
|
|
313
|
+
|
|
314
|
+
def gather(self):
|
|
315
|
+
"""
|
|
316
|
+
The method that does all the processing of incoming value including
|
|
317
|
+
calling pre_gather, and using the correct bundle rule.
|
|
318
|
+
"""
|
|
319
|
+
self.pre_gather()
|
|
320
|
+
for val, dest_comp, bundle in self.process_incoming():
|
|
321
|
+
self.bundle_rules[bundle](self, val, dest_comp)
|
|
322
|
+
|
|
323
|
+
##Abstract Methods
|
|
324
|
+
@abstractmethod
|
|
325
|
+
def verify_connections(self):
|
|
326
|
+
"""
|
|
327
|
+
An abstract method that generally uses component metadata to verify that
|
|
328
|
+
there are the correct number of connections. This should error if it
|
|
329
|
+
fails the verification.
|
|
330
|
+
"""
|
|
331
|
+
pass
|
|
332
|
+
|
|
333
|
+
@abstractmethod
|
|
334
|
+
def advance_state(self, **kwargs):
|
|
335
|
+
"""
|
|
336
|
+
An abstract method to advance the state of the component to the next one
|
|
337
|
+
(a component transitions from its current state at time t to a new one
|
|
338
|
+
at time t + dt)
|
|
339
|
+
"""
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
@abstractmethod
|
|
343
|
+
def reset(self, **kwargs):
|
|
344
|
+
"""
|
|
345
|
+
An abstract method that should be implemented to models can be returned
|
|
346
|
+
to their original state.
|
|
347
|
+
"""
|
|
348
|
+
pass
|
|
349
|
+
|
|
350
|
+
@abstractmethod
|
|
351
|
+
def save(self, directory, **kwargs):
|
|
352
|
+
"""
|
|
353
|
+
An abstract method to save component specific state to the provided
|
|
354
|
+
directory
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
directory: the directory to save the state to
|
|
358
|
+
"""
|
|
359
|
+
pass
|