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/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