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/controller.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
from ngcsimlib.utils import check_attributes, load_from_path, make_unique_path, check_serializable
|
|
2
|
+
import json, os, warnings, inspect
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Controller:
|
|
6
|
+
"""
|
|
7
|
+
The ngc controller is the foundation of all ngclearn models and the central
|
|
8
|
+
operational construct for simulating complex systems. The controller is the
|
|
9
|
+
object that organizes all of the components, commands, and connections
|
|
10
|
+
that characterize a complex system and/or model to be simulated over time
|
|
11
|
+
(and was referred to as the "nodes-and-cables" system in earlier versions of
|
|
12
|
+
ngc-learn).
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.steps = []
|
|
16
|
+
self.commands = {}
|
|
17
|
+
self.components = {} ## components/nodes that characterize system/simulation object
|
|
18
|
+
self.connections = [] ## cables that characterize system/simulation object
|
|
19
|
+
|
|
20
|
+
self._json_objects = {
|
|
21
|
+
"commands": [],
|
|
22
|
+
"steps": [],
|
|
23
|
+
"components": [],
|
|
24
|
+
"connections": []
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def __getitem__(self, item):
|
|
28
|
+
return self.__getattribute__(item)
|
|
29
|
+
|
|
30
|
+
def runCycle(self, **kwargs):
|
|
31
|
+
"""
|
|
32
|
+
Runs all of the commands that have been added to a "compute" cycle in
|
|
33
|
+
the order that they were added. All of the keyword arguments that are
|
|
34
|
+
needed to run the commands within the cycle are passed in here and will
|
|
35
|
+
ultimately be forwarded to each command.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
kwargs: all keyword arguments that are needed to run every command
|
|
39
|
+
within the cycle
|
|
40
|
+
"""
|
|
41
|
+
for step in self.steps:
|
|
42
|
+
self[step](**kwargs)
|
|
43
|
+
|
|
44
|
+
def verify_connections(self, skip_components=None):
|
|
45
|
+
"""
|
|
46
|
+
Loops through all components within the controller and calls the
|
|
47
|
+
`verify_connections` method on/for each.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
skip_components: a list of component names to skip over while
|
|
51
|
+
verifying connections (Default: None)
|
|
52
|
+
"""
|
|
53
|
+
for component in self.components.keys():
|
|
54
|
+
if skip_components is not None and component.name in skip_components:
|
|
55
|
+
continue
|
|
56
|
+
else:
|
|
57
|
+
self.components[component].verify_connections()
|
|
58
|
+
|
|
59
|
+
def connect(self, source_component_name, source_compartment_name, destination_component_name,
|
|
60
|
+
target_compartment_name, bundle=None):
|
|
61
|
+
"""
|
|
62
|
+
Creates a cable from one component to another.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
source_component_name: the name of the component providing the value
|
|
66
|
+
|
|
67
|
+
source_compartment_name: the name of the compartment containing the
|
|
68
|
+
source value
|
|
69
|
+
|
|
70
|
+
destination_component_name: the name of the component receiving
|
|
71
|
+
the value
|
|
72
|
+
|
|
73
|
+
target_compartment_name: the name of the compartment to store the
|
|
74
|
+
source value in
|
|
75
|
+
|
|
76
|
+
bundle: the number of the bundle rule to be used when using this
|
|
77
|
+
cable (Default: None)
|
|
78
|
+
"""
|
|
79
|
+
self.components[destination_component_name].create_incoming_connection(
|
|
80
|
+
self.components[source_component_name].create_outgoing_connection(source_compartment_name), target_compartment_name,
|
|
81
|
+
bundle)
|
|
82
|
+
self.connections.append((source_component_name, source_compartment_name, destination_component_name, target_compartment_name, bundle))
|
|
83
|
+
self._json_objects['connections'].append({
|
|
84
|
+
"source_component_name": source_component_name,
|
|
85
|
+
"source_compartment_name": source_compartment_name,
|
|
86
|
+
"target_component_name": destination_component_name,
|
|
87
|
+
"target_compartment_name": target_compartment_name,
|
|
88
|
+
"bundle": bundle})
|
|
89
|
+
|
|
90
|
+
def make_connections(self, path_to_cables_file):
|
|
91
|
+
"""
|
|
92
|
+
Loads a collection of cables from a json file. Follow `cables.schema`
|
|
93
|
+
for the specific format of the required json file.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
path_to_cables_file: the path to the file, including the name and
|
|
97
|
+
extension
|
|
98
|
+
"""
|
|
99
|
+
with open(path_to_cables_file, 'r') as file:
|
|
100
|
+
cables = json.load(file)
|
|
101
|
+
for cable in cables:
|
|
102
|
+
self.connect(**cable)
|
|
103
|
+
|
|
104
|
+
def make_components(self, path_to_components_file, custom_file_dir=None):
|
|
105
|
+
"""
|
|
106
|
+
Loads a collection of components from a json file. Follow
|
|
107
|
+
`components.schema` for the specific format of the json file.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
path_to_components_file: the path to the file, including the name
|
|
111
|
+
and extension
|
|
112
|
+
|
|
113
|
+
custom_file_dir: the path to the custom directory for custom load methods,
|
|
114
|
+
this directory is named `custom` if the save_to_json method is
|
|
115
|
+
used. (Default: None)
|
|
116
|
+
"""
|
|
117
|
+
with open(path_to_components_file, 'r') as file:
|
|
118
|
+
components = json.load(file)
|
|
119
|
+
for component in components:
|
|
120
|
+
self.add_component(**component, directory=custom_file_dir)
|
|
121
|
+
|
|
122
|
+
def make_steps(self, path_to_steps_file):
|
|
123
|
+
"""
|
|
124
|
+
Loads a collection of steps from a json file. Follow `steps.schema` for
|
|
125
|
+
the specific format of this json file.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
path_to_steps_file: the path of the file, including the name and extension
|
|
129
|
+
"""
|
|
130
|
+
with open(path_to_steps_file, 'r') as file:
|
|
131
|
+
steps = json.load(file)
|
|
132
|
+
for step in steps:
|
|
133
|
+
self.add_step(**step)
|
|
134
|
+
|
|
135
|
+
def make_commands(self, path_to_commands_file):
|
|
136
|
+
"""
|
|
137
|
+
Loads a collection of commands from a json file. Follow `commands.schema`
|
|
138
|
+
for the specific format of this json file.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
path_to_commands_file: the path of the file, including the name
|
|
142
|
+
and extension
|
|
143
|
+
"""
|
|
144
|
+
with open(path_to_commands_file, 'r') as file:
|
|
145
|
+
commands = json.load(file)
|
|
146
|
+
for command in commands:
|
|
147
|
+
self.add_command(**command)
|
|
148
|
+
|
|
149
|
+
def add_step(self, command_name):
|
|
150
|
+
"""
|
|
151
|
+
Adds a command to the built-in cycle. When the cycle is run/executed,
|
|
152
|
+
the commands will be called in the order that they are added in (when
|
|
153
|
+
setting up the controller simulation object).
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
command_name: the name of the command to be added
|
|
157
|
+
"""
|
|
158
|
+
if command_name not in self.commands.keys():
|
|
159
|
+
raise RuntimeError(str(command_name) + " is not a registered command")
|
|
160
|
+
self.steps.append(command_name)
|
|
161
|
+
self._json_objects['steps'].append({"command_name": command_name})
|
|
162
|
+
|
|
163
|
+
def add_component(self, component_type, match_case=False, absolute_path=False, **kwargs):
|
|
164
|
+
"""
|
|
165
|
+
Acts as a component factory for the controller.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
component_type: A string that is linked to the component class to be
|
|
169
|
+
created. If the class was loaded with the modules.json file this
|
|
170
|
+
can be the keywords defined in that file. Otherwise, it will have
|
|
171
|
+
to be dynamically loaded using the functions found in ngcsimlib.utils.
|
|
172
|
+
|
|
173
|
+
match_case: A boolean that represents if the exact case should be
|
|
174
|
+
matched when dynamically loading the component class (Default: False)
|
|
175
|
+
|
|
176
|
+
absolute_path: A boolean that represents if the component class
|
|
177
|
+
should be treated as an absolute path when dynamically loading
|
|
178
|
+
the component class (Default: False)
|
|
179
|
+
|
|
180
|
+
kwargs: All of the keyword arguments that are needed to initialize
|
|
181
|
+
the loaded component class. The function will try to crash nicely
|
|
182
|
+
if keyword arguments are missing. This list of arguments will
|
|
183
|
+
also be stored to allow for the component to be rebuilt, but if
|
|
184
|
+
a given value is not serializable it will drop that from the
|
|
185
|
+
keyword arguments.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
the created component (Component is also automatically added to the controller)
|
|
189
|
+
"""
|
|
190
|
+
Component_class = load_from_path(path=component_type, match_case=match_case, absolute_path=absolute_path)
|
|
191
|
+
|
|
192
|
+
if inspect.isclass(Component_class):
|
|
193
|
+
call = Component_class.__init__
|
|
194
|
+
elif inspect.isfunction(Component_class):
|
|
195
|
+
call = Component_class
|
|
196
|
+
else:
|
|
197
|
+
raise RuntimeError("Given component type " + str(component_type)
|
|
198
|
+
+ " is not callable")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
count = call.__code__.co_argcount - 1
|
|
203
|
+
named_args = call.__code__.co_varnames[1:count]
|
|
204
|
+
try:
|
|
205
|
+
component = Component_class(**kwargs)
|
|
206
|
+
except TypeError as E:
|
|
207
|
+
print(E)
|
|
208
|
+
raise RuntimeError(str(E) + "\nProvided keyword arguments:\t" + str(list(kwargs.keys())) +
|
|
209
|
+
"\nRequired keyword arguments:\t" + str(list(named_args)))
|
|
210
|
+
|
|
211
|
+
check_attributes(component, ["name", "verify_connections"], fatal=True)
|
|
212
|
+
self.components[component.name] = component
|
|
213
|
+
|
|
214
|
+
obj = {"component_type": component_type, "match_case": match_case, "absolute_path": absolute_path} | kwargs
|
|
215
|
+
bad_keys = check_serializable(obj)
|
|
216
|
+
for key in bad_keys:
|
|
217
|
+
del obj[key]
|
|
218
|
+
print("Failed to serialize \"" + str(key) + "\" in " + component.name)
|
|
219
|
+
|
|
220
|
+
self._json_objects['components'].append(obj)
|
|
221
|
+
|
|
222
|
+
return component
|
|
223
|
+
|
|
224
|
+
def add_command(self, command_type, command_name, match_case=False, absolute_path=False, component_names=None,
|
|
225
|
+
**kwargs):
|
|
226
|
+
"""
|
|
227
|
+
Acts as a factory to create/synthesize commands.
|
|
228
|
+
In addition to adding command objects to the controllers internal
|
|
229
|
+
command list, commands are also set to their attributes on the controller.
|
|
230
|
+
For example, if a command named `step` is added myController.runCommand("step", ...)
|
|
231
|
+
is equivalent to myController.step(...).
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
command_type: A string that is linked to the command class to be
|
|
235
|
+
created. If the class was loaded with the modules.json file this
|
|
236
|
+
can be the keywords defined in that file. Otherwise, it will
|
|
237
|
+
have to be dynamically loaded using the functions found
|
|
238
|
+
in ngcsimlib.utils.
|
|
239
|
+
|
|
240
|
+
command_name: A string that is the name of the command, this is the
|
|
241
|
+
keyword that will be called elsewhere to execute this command.
|
|
242
|
+
|
|
243
|
+
match_case: A boolean that represents if the exact case should be
|
|
244
|
+
matched when dynamically loading the command class (Default: False)
|
|
245
|
+
|
|
246
|
+
absolute_path: A boolean that represents if the command class should
|
|
247
|
+
be treated as an absolute path when dynamically loading the
|
|
248
|
+
command class (Default: False)
|
|
249
|
+
|
|
250
|
+
component_names: A list of component names to be passed to the
|
|
251
|
+
command's constructor. Internally it will convert the strings to
|
|
252
|
+
the actual component objects so they must exist in the controller
|
|
253
|
+
prior to this function being called.
|
|
254
|
+
|
|
255
|
+
kwargs: All the keyword arguments that are needed to initialize the
|
|
256
|
+
loaded command class. The function will try to crash nicely if
|
|
257
|
+
keyword arguments are missing. This list of arguments will also
|
|
258
|
+
be stored to allow for the component to be rebuilt, but if a
|
|
259
|
+
given value is not serializable it will drop that from the
|
|
260
|
+
keyword arguments.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
the created command (Command is also automatically added to the controller)
|
|
264
|
+
"""
|
|
265
|
+
Command_class = load_from_path(path=command_type, match_case=match_case, absolute_path=absolute_path)
|
|
266
|
+
if not callable(Command_class):
|
|
267
|
+
raise RuntimeError("The object named \"" + Command_class.__name__ + "\" is not callable. Please make sure "
|
|
268
|
+
"the object is callable and returns a "
|
|
269
|
+
"callable object")
|
|
270
|
+
if component_names is not None:
|
|
271
|
+
componentObjs = [self.components[name] for name in component_names]
|
|
272
|
+
else:
|
|
273
|
+
componentObjs = []
|
|
274
|
+
|
|
275
|
+
if inspect.isclass(Command_class):
|
|
276
|
+
call = Command_class.__init__
|
|
277
|
+
elif inspect.isfunction(Command_class):
|
|
278
|
+
call = Command_class
|
|
279
|
+
else:
|
|
280
|
+
raise RuntimeError("Given command type " + str(command_type)
|
|
281
|
+
+ " is not callable")
|
|
282
|
+
|
|
283
|
+
count = call.__code__.co_argcount - 1
|
|
284
|
+
named_args = call.__code__.co_varnames[1:count]
|
|
285
|
+
try:
|
|
286
|
+
command = Command_class(components=componentObjs, controller=self, command_name=command_name, **kwargs)
|
|
287
|
+
except TypeError as E:
|
|
288
|
+
print(E)
|
|
289
|
+
raise RuntimeError(str(E) + "\nProvided keyword arguments:\t" + str(list(kwargs.keys())) +
|
|
290
|
+
"\nRequired keyword arguments:\t" + str(list(named_args)))
|
|
291
|
+
|
|
292
|
+
self.commands[command_name] = command
|
|
293
|
+
self.__setattr__(command_name, command)
|
|
294
|
+
|
|
295
|
+
obj = {"command_type": command_type, "command_name": command_name, "match_case": match_case,
|
|
296
|
+
"absolute_path": absolute_path, "component_names": component_names} | kwargs
|
|
297
|
+
bad_keys = check_serializable(obj)
|
|
298
|
+
for key in bad_keys:
|
|
299
|
+
del obj[key]
|
|
300
|
+
print("Failed to serialize \"" + str(key) + "\" in " + command_name)
|
|
301
|
+
|
|
302
|
+
self._json_objects['commands'].append(obj)
|
|
303
|
+
return command
|
|
304
|
+
|
|
305
|
+
def runCommand(self, command_name, *args, **kwargs):
|
|
306
|
+
"""
|
|
307
|
+
Runs the given command.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
command_name: the name of the command to run
|
|
311
|
+
|
|
312
|
+
args: positional arguments to be passed into the command
|
|
313
|
+
|
|
314
|
+
kwargs: keyword arguments to be passed into the command
|
|
315
|
+
"""
|
|
316
|
+
command = self.commands.get(command_name, None)
|
|
317
|
+
if command is None:
|
|
318
|
+
raise RuntimeError("Can not find command: " + str(command_name))
|
|
319
|
+
command(*args, **kwargs)
|
|
320
|
+
|
|
321
|
+
def save_to_json(self, directory, model_name=None, custom_save=True):
|
|
322
|
+
"""
|
|
323
|
+
Dumps all the required json files to rebuild the current controller to a specified directory. If there is a
|
|
324
|
+
`save` command present on the controller and custom_save is True, it will run that command as well.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
directory: The top level directory to save the model to
|
|
328
|
+
|
|
329
|
+
model_name: The name of the model, if None or if there is already a
|
|
330
|
+
model with that name a uid will be used or appended to the name
|
|
331
|
+
respectively. (Default: None)
|
|
332
|
+
|
|
333
|
+
custom_save: A boolean that if true will attempt to call the `save`
|
|
334
|
+
command if present on the controller (Default: True)
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
a tuple where the first value is the path to the model, and the
|
|
338
|
+
second is the path to the custom save folder if custom_save is
|
|
339
|
+
true and None if false
|
|
340
|
+
"""
|
|
341
|
+
path = make_unique_path(directory, model_name)
|
|
342
|
+
|
|
343
|
+
with open(path + "/commands.json", 'w') as fp:
|
|
344
|
+
json.dump(self._json_objects['commands'], fp, indent=4)
|
|
345
|
+
|
|
346
|
+
with open(path + "/steps.json", 'w') as fp:
|
|
347
|
+
json.dump(self._json_objects['steps'], fp, indent=4)
|
|
348
|
+
|
|
349
|
+
with open(path + "/components.json", 'w') as fp:
|
|
350
|
+
json.dump(self._json_objects['components'], fp, indent=4)
|
|
351
|
+
|
|
352
|
+
with open(path + "/connections.json", 'w') as fp:
|
|
353
|
+
json.dump(self._json_objects['connections'], fp, indent=4)
|
|
354
|
+
|
|
355
|
+
if custom_save:
|
|
356
|
+
os.mkdir(path + "/custom")
|
|
357
|
+
if check_attributes(self, ['save']):
|
|
358
|
+
self.save(path + "/custom")
|
|
359
|
+
else:
|
|
360
|
+
warnings.warn("Controller doesn't have a save command registered. No custom saving happened")
|
|
361
|
+
|
|
362
|
+
return (path, path + "/custom") if custom_save else (path, None)
|
|
363
|
+
|
|
364
|
+
def load_from_dir(self, directory, custom_folder="/custom"):
|
|
365
|
+
"""
|
|
366
|
+
Builds a controller from a directory. Designed to be used with
|
|
367
|
+
`save_to_json`.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
directory: the path to the model
|
|
371
|
+
|
|
372
|
+
custom_folder: The name of the custom data folder for building
|
|
373
|
+
components. (Default: `/custom`)
|
|
374
|
+
"""
|
|
375
|
+
self.make_components(directory + "/components.json", directory + custom_folder)
|
|
376
|
+
self.make_connections(directory + "/connections.json")
|
|
377
|
+
self.make_commands(directory + "/commands.json")
|
|
378
|
+
self.make_steps(directory + "/steps.json")
|
ngcsimlib/utils.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The utilities file for ngcsimlib supports the foundation of how ngclearn does its
|
|
3
|
+
dynamic loading of attributes and modules. When the file is imported, it will
|
|
4
|
+
automatically search for a file (default `json_files/modules.json`, or pass a
|
|
5
|
+
default --modules="json_files/modules.json") to load all the modules for dynamic
|
|
6
|
+
loading. Without this file, when the controller goes on to try to load a component
|
|
7
|
+
or command class, it will be unable to do so/find them. Please see the `modules.schema`
|
|
8
|
+
file in the `json_schemes` folder for more details on how to create the
|
|
9
|
+
modules.json file.
|
|
10
|
+
"""
|
|
11
|
+
import sys, uuid, os, json
|
|
12
|
+
from importlib import import_module
|
|
13
|
+
|
|
14
|
+
## Globally tracking all the modules, and attributes have been dynamically loaded
|
|
15
|
+
_Loaded_Attributes = {}
|
|
16
|
+
_Loaded_Modules = {}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def check_attributes(obj, required, fatal=False):
|
|
20
|
+
"""
|
|
21
|
+
This function will verify that a provided object has the requested attributes.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
obj: Object that should have the attributes
|
|
25
|
+
|
|
26
|
+
required: A list of required attributes by string name
|
|
27
|
+
|
|
28
|
+
fatal: If true an Attribute error will be thrown (default False)
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Boolean only returns if not fatal, if the object has the required attributes
|
|
32
|
+
"""
|
|
33
|
+
if required is None:
|
|
34
|
+
return True
|
|
35
|
+
for atr in required:
|
|
36
|
+
if not hasattr(obj, atr):
|
|
37
|
+
if not fatal:
|
|
38
|
+
return False
|
|
39
|
+
if hasattr(obj, "name"):
|
|
40
|
+
raise AttributeError(str(obj.name) + " is missing the required attribute of " + atr)
|
|
41
|
+
else:
|
|
42
|
+
raise AttributeError("Checked object is missing the required attribute of " + atr)
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_module(module_path, match_case=False, absolute_path=False):
|
|
47
|
+
"""
|
|
48
|
+
Trys to load a module from the provided path.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
module_path: Module path, supports compound modules such as `ngcsimlib.commands`
|
|
52
|
+
|
|
53
|
+
match_case: If true the module must case match exactly (default false)
|
|
54
|
+
|
|
55
|
+
absolute_path: If true tries to import exactly what is passed to module
|
|
56
|
+
path (default false)
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
the module that has been loaded
|
|
60
|
+
"""
|
|
61
|
+
#Return if we have already loaded this module
|
|
62
|
+
if module_path in _Loaded_Modules.keys():
|
|
63
|
+
return _Loaded_Modules[module_path]
|
|
64
|
+
#Unkown module
|
|
65
|
+
module_name = None
|
|
66
|
+
if absolute_path:
|
|
67
|
+
module_name = module_path
|
|
68
|
+
else:
|
|
69
|
+
#Extract the final module from the module_path
|
|
70
|
+
final_mod = module_path.split('.')[-1]
|
|
71
|
+
final_mod = final_mod if match_case else final_mod.lower()
|
|
72
|
+
|
|
73
|
+
#Try to match the final module to any currently loaded module
|
|
74
|
+
for module in sys.modules:
|
|
75
|
+
last_mod = module.split('.')[-1]
|
|
76
|
+
last_mod = last_mod if match_case else last_mod.lower()
|
|
77
|
+
if final_mod == last_mod:
|
|
78
|
+
print("Loading module from " + module)
|
|
79
|
+
module_name = module
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
#Will only be None if no imported modules match the import name
|
|
83
|
+
if module_name is None:
|
|
84
|
+
raise RuntimeError("Failed to find dynamic import for \"" + module_path + "\"")
|
|
85
|
+
|
|
86
|
+
mod = import_module(module_name)
|
|
87
|
+
_Loaded_Modules[module_path] = mod
|
|
88
|
+
return mod
|
|
89
|
+
|
|
90
|
+
def load_from_path(path, match_case=False, absolute_path=False):
|
|
91
|
+
"""
|
|
92
|
+
Loads an attribute/module from a specified path. If not using the absolute path the module name and attribute
|
|
93
|
+
names will be assumed to be the same.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
path: path to attribute/module to load, will try to find the attribute/module if not already loaded
|
|
97
|
+
|
|
98
|
+
match_case: If true the module must case match exactly (default false)
|
|
99
|
+
|
|
100
|
+
absolute_path: If true tries to import exactly what is passed to module path (default false)
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
The attribute at the path
|
|
104
|
+
"""
|
|
105
|
+
if absolute_path is True:
|
|
106
|
+
module_name = '.'.join(path.split('.')[:-1])
|
|
107
|
+
class_name = path.split('.')[-1]
|
|
108
|
+
match_case = True
|
|
109
|
+
else:
|
|
110
|
+
module_name = path
|
|
111
|
+
class_name = path
|
|
112
|
+
|
|
113
|
+
return load_attribute(module_path=module_name, attribute_name=class_name,
|
|
114
|
+
match_case=match_case, absolute_path=absolute_path)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def load_attribute(attribute_name, module_path=None, match_case=False, absolute_path=False):
|
|
118
|
+
"""
|
|
119
|
+
Loads a specific attribute from a specified module
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
attribute_name: name of the attribute to load
|
|
123
|
+
|
|
124
|
+
module_path: module to load the attribute from, will use the attribute
|
|
125
|
+
name if None (default None)
|
|
126
|
+
|
|
127
|
+
match_case: If true the module must case match exactly (default false)
|
|
128
|
+
|
|
129
|
+
absolute_path: If true tries to import exactly what is passed to module
|
|
130
|
+
path (default false)
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
the loaded attribute
|
|
134
|
+
"""
|
|
135
|
+
if attribute_name in _Loaded_Attributes.keys():
|
|
136
|
+
return _Loaded_Attributes[attribute_name]
|
|
137
|
+
|
|
138
|
+
if attribute_name is None:
|
|
139
|
+
raise RuntimeError()
|
|
140
|
+
|
|
141
|
+
mod = load_module(attribute_name if module_path is None else module_path, match_case=match_case, absolute_path=absolute_path)
|
|
142
|
+
|
|
143
|
+
attribute_name = attribute_name if match_case else attribute_name[0].upper() + attribute_name[1:]
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
attr = getattr(mod, attribute_name)
|
|
147
|
+
except AttributeError:
|
|
148
|
+
raise RuntimeError("Could not find an attribute with name \"" + attribute_name + "\" in module " + mod.__name__) \
|
|
149
|
+
from None
|
|
150
|
+
|
|
151
|
+
_Loaded_Attributes[attribute_name] = attr
|
|
152
|
+
return attr
|
|
153
|
+
|
|
154
|
+
def make_unique_path(directory, root_name=None):
|
|
155
|
+
"""
|
|
156
|
+
This block of code will make a uniquely named directory inside the specified output folder.
|
|
157
|
+
If the root name already exists it will append a UID to the root name to not overwrite data
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
directory: The root directory to save models to
|
|
161
|
+
|
|
162
|
+
root_name: (Default None) The root name for the model to be saved to,
|
|
163
|
+
if none it will just use the UID
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
path to created directory
|
|
167
|
+
"""
|
|
168
|
+
uid = uuid.uuid4()
|
|
169
|
+
if root_name is None:
|
|
170
|
+
root_name = str(uid)
|
|
171
|
+
print("generated path will be named \"" + str(root_name) + "\"")
|
|
172
|
+
|
|
173
|
+
elif os.path.isdir(directory + "/" + root_name):
|
|
174
|
+
root_name += "_" + str(uid)
|
|
175
|
+
print("root path already exists, generated path will be named \"" + str(root_name) + "\"")
|
|
176
|
+
|
|
177
|
+
path = directory + "/" + str(root_name)
|
|
178
|
+
os.mkdir(path)
|
|
179
|
+
return path
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def check_serializable(dict):
|
|
183
|
+
"""
|
|
184
|
+
Verifies that all the values of a dictionary are serializable
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
dict: dictionary to check
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
list of all the keys that are not serializable
|
|
191
|
+
"""
|
|
192
|
+
bad_keys = []
|
|
193
|
+
for key in dict.keys():
|
|
194
|
+
try:
|
|
195
|
+
json.dumps(dict[key])
|
|
196
|
+
except:
|
|
197
|
+
bad_keys.append(key)
|
|
198
|
+
return bad_keys
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def extract_args(keywords=None, *args, **kwargs):
|
|
202
|
+
"""
|
|
203
|
+
Extracts the given keywords from the provided args and kwargs. This method first finds all the matching keywords
|
|
204
|
+
then for each missing keyword it takes the next value in args and assigns it. It will throw and error if there are
|
|
205
|
+
not enough kwargs and args to satisfy all provided keywords
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
keywords: a list of keywords to extract
|
|
209
|
+
|
|
210
|
+
args: the positional arguments to use as a fallback over keyword arguments
|
|
211
|
+
|
|
212
|
+
kwargs: the keyword arguments to first try to extract from
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
a dictionary for where each keyword is a key, and the value is assigned
|
|
216
|
+
argument. Will throw a RuntimeError if it fails to match and argument
|
|
217
|
+
to each keyword.
|
|
218
|
+
"""
|
|
219
|
+
if keywords is None:
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
a = {key: None for key in keywords}
|
|
223
|
+
missing = []
|
|
224
|
+
for key in keywords:
|
|
225
|
+
if key in kwargs.keys():
|
|
226
|
+
a[key] = kwargs.get(key, None)
|
|
227
|
+
else:
|
|
228
|
+
missing.append(key)
|
|
229
|
+
|
|
230
|
+
if len(missing) > len(args):
|
|
231
|
+
raise RuntimeError("Missing arguments")
|
|
232
|
+
|
|
233
|
+
else:
|
|
234
|
+
for idx, key in enumerate(missing):
|
|
235
|
+
a[key] = args[idx]
|
|
236
|
+
|
|
237
|
+
return a
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# This file contains an official list of authors of this framework.
|
|
2
|
+
|
|
3
|
+
# Names should be added to this file as:
|
|
4
|
+
# Name or Organization <email address>
|
|
5
|
+
# The email address is not required for organizations.
|
|
6
|
+
|
|
7
|
+
Core Team
|
|
8
|
+
William Gebhardt <wdg1351@rit.edu>
|
|
9
|
+
Alexander Ororbia <ago@cs.rit.edu>
|
|
10
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, The Neural Adaptive Computing Laboratory
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|