engforge 0.3.0__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.
- engforge/__init__.py +26 -0
- engforge/_testing_components.py +864 -0
- engforge/analysis.py +257 -0
- engforge/attr_dynamics.py +222 -0
- engforge/attr_plotting.py +811 -0
- engforge/attr_signals.py +116 -0
- engforge/attr_slots.py +211 -0
- engforge/attr_solver.py +443 -0
- engforge/attributes.py +326 -0
- engforge/common.py +76 -0
- engforge/component_collections.py +276 -0
- engforge/components.py +55 -0
- engforge/configuration.py +673 -0
- engforge/dataframe.py +185 -0
- engforge/datastores/__init__.py +116 -0
- engforge/datastores/data.py +519 -0
- engforge/datastores/gdocs.py +2650 -0
- engforge/datastores/reporting.py +935 -0
- engforge/datastores/secrets.py +549 -0
- engforge/dynamics.py +662 -0
- engforge/eng/__init__.py +0 -0
- engforge/eng/costs.py +1459 -0
- engforge/eng/fluid_material.py +279 -0
- engforge/eng/geometry.py +1060 -0
- engforge/eng/pipes.py +1211 -0
- engforge/eng/prediction.py +366 -0
- engforge/eng/solid_materials.py +445 -0
- engforge/eng/structure.py +2228 -0
- engforge/eng/structure_beams.py +821 -0
- engforge/eng/thermodynamics.py +271 -0
- engforge/engforge_attributes.py +380 -0
- engforge/env_var.py +214 -0
- engforge/locations.py +18 -0
- engforge/logging.py +290 -0
- engforge/patterns.py +449 -0
- engforge/problem_context.py +2251 -0
- engforge/properties.py +501 -0
- engforge/reporting.py +595 -0
- engforge/solveable.py +964 -0
- engforge/solver.py +316 -0
- engforge/solver_utils.py +726 -0
- engforge/system.py +143 -0
- engforge/system_reference.py +299 -0
- engforge/tabulation.py +288 -0
- engforge/test/__init__.py +2 -0
- engforge/test/_pre_test_structures.py +221 -0
- engforge/test/report_testing.py +158 -0
- engforge/test/test_airfilter.py +91 -0
- engforge/test/test_analysis.py +3 -0
- engforge/test/test_comp_iter.py +196 -0
- engforge/test/test_composition.py +139 -0
- engforge/test/test_costs.py +338 -0
- engforge/test/test_dynamics.py +109 -0
- engforge/test/test_dynamics_spaces.py +39 -0
- engforge/test/test_four_bar.py +97 -0
- engforge/test/test_modules.py +72 -0
- engforge/test/test_performance.py +93 -0
- engforge/test/test_pipes.py +49 -0
- engforge/test/test_problem.py +411 -0
- engforge/test/test_problem_deepscoping.py +68 -0
- engforge/test/test_slider_crank.py +51 -0
- engforge/test/test_solver.py +343 -0
- engforge/test/test_tabulation.py +233 -0
- engforge/typing.py +92 -0
- engforge-0.3.0.dist-info/METADATA +392 -0
- engforge-0.3.0.dist-info/RECORD +69 -0
- engforge-0.3.0.dist-info/WHEEL +5 -0
- engforge-0.3.0.dist-info/licenses/LICENSE +7 -0
- engforge-0.3.0.dist-info/top_level.txt +1 -0
engforge/solveable.py
ADDED
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
import attrs, attr
|
|
2
|
+
import uuid
|
|
3
|
+
import numpy
|
|
4
|
+
import numpy as np
|
|
5
|
+
import scipy.optimize as sciopt
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
import copy
|
|
8
|
+
import datetime
|
|
9
|
+
import typing
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# from engforge.dynamics import DynamicsMixin
|
|
13
|
+
from engforge.attributes import AttributeInstance
|
|
14
|
+
from engforge.engforge_attributes import AttributedBaseMixin
|
|
15
|
+
from engforge.configuration import Configuration, forge
|
|
16
|
+
from engforge.properties import *
|
|
17
|
+
from engforge.system_reference import *
|
|
18
|
+
from engforge.system_reference import Ref
|
|
19
|
+
from engforge.solver_utils import *
|
|
20
|
+
from engforge.attr_dynamics import IntegratorInstance
|
|
21
|
+
|
|
22
|
+
import collections
|
|
23
|
+
import itertools
|
|
24
|
+
|
|
25
|
+
SOLVER_OPTIONS = ["root", "minimize"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SolvableLog(LoggingMixin):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
log = SolvableLog()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Anonymous Update Funcs (solve scoping issue)
|
|
36
|
+
def _update_func(comp, eval_kw):
|
|
37
|
+
def updt(*args, **kw):
|
|
38
|
+
eval_kw.update(kw)
|
|
39
|
+
if log.log_level <= 12:
|
|
40
|
+
log.info(f"update| {comp.name} ") # =5)
|
|
41
|
+
return comp.update(comp.parent, *args, **eval_kw)
|
|
42
|
+
|
|
43
|
+
if log.log_level <= 12:
|
|
44
|
+
log.info(f"create method| {comp.name}| {eval_kw}")
|
|
45
|
+
updt.__name__ = f"{comp.name}_update"
|
|
46
|
+
return updt
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _post_update_func(comp, eval_kw):
|
|
50
|
+
def updt(*args, **kw):
|
|
51
|
+
eval_kw.update(kw)
|
|
52
|
+
return comp.post_update(comp.parent, *args, **eval_kw)
|
|
53
|
+
|
|
54
|
+
if log.log_level <= 12:
|
|
55
|
+
log.info(f"create post method| {comp.name}| {eval_kw}")
|
|
56
|
+
updt.__name__ = f"{comp.name}_post_update"
|
|
57
|
+
return updt
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _cost_update(comp):
|
|
61
|
+
from engforge.eng.costs import Economics, CostModel
|
|
62
|
+
|
|
63
|
+
if isinstance(comp, Economics):
|
|
64
|
+
|
|
65
|
+
def updt(*args, **kw):
|
|
66
|
+
if log.log_level <= 12:
|
|
67
|
+
log.info(f"update economics {comp.name} | {comp.term_length} ")
|
|
68
|
+
comp.system_properties_classdef(True)
|
|
69
|
+
comp.update(comp.parent, *args, **kw)
|
|
70
|
+
|
|
71
|
+
if log.log_level <= 12:
|
|
72
|
+
log.info(f"economics update cb {comp.name} | {comp.term_length} ")
|
|
73
|
+
updt.__name__ = f"{comp.name}_econ_update"
|
|
74
|
+
else:
|
|
75
|
+
|
|
76
|
+
def updt(*args, **kw):
|
|
77
|
+
if log.log_level <= 12:
|
|
78
|
+
log.info(f"update costs {comp.name} ") # =5)
|
|
79
|
+
comp.system_properties_classdef(True)
|
|
80
|
+
# comp.update(comp.parent, *args, **kw) #called as update without cm
|
|
81
|
+
return comp.update_dflt_costs()
|
|
82
|
+
|
|
83
|
+
if log.log_level <= 12:
|
|
84
|
+
log.info(f"cost update cb {comp.name} ")
|
|
85
|
+
updt.__name__ = f"{comp.name}_cost_update"
|
|
86
|
+
|
|
87
|
+
return updt
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
dflt_dynamics = {
|
|
91
|
+
"dynamics.state": {},
|
|
92
|
+
"dynamics.input": {},
|
|
93
|
+
"dynamics.rate": {},
|
|
94
|
+
"dynamics.output": {},
|
|
95
|
+
"dynamic_comps": {},
|
|
96
|
+
}
|
|
97
|
+
skipa_attr_names = (
|
|
98
|
+
"index",
|
|
99
|
+
"parent",
|
|
100
|
+
"dynamic_input_vars",
|
|
101
|
+
"dynamic_state_vars",
|
|
102
|
+
"dynamic_output_vars",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class SolveableMixin(AttributedBaseMixin): #'Configuration'
|
|
107
|
+
"""commonality for components,systems that identifies subsystems and states for solving.
|
|
108
|
+
|
|
109
|
+
This class defines the update structure of components and systems, and the storage of internal references to the system and its components. It also provides a method to iterate over the internal components and their references.
|
|
110
|
+
|
|
111
|
+
Importantly it defines the references to the system and its components, and the ability to set the system state from a dictionary of values across multiple objects. It also provides a method to iterate over the internal components and their references. There are several helper functions to these ends.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
# TODO: add parent state storage
|
|
115
|
+
parent: "Configuration"
|
|
116
|
+
|
|
117
|
+
_prv_internal_references: dict
|
|
118
|
+
_prv_internal_components: dict
|
|
119
|
+
_prv_internal_systems: dict
|
|
120
|
+
_prv_internal_tabs: dict
|
|
121
|
+
_prv_system_references: dict
|
|
122
|
+
|
|
123
|
+
# Update Flow
|
|
124
|
+
# TODO: pass the problem vs the parent component, then locate this component in the problem and update any references
|
|
125
|
+
def update(self, parent, *args, **kwargs):
|
|
126
|
+
"""Kwargs comes from eval_kw in solver"""
|
|
127
|
+
if log.log_level <= 12:
|
|
128
|
+
log.info(f"void updating {self.__class__.__name__}.{self}")
|
|
129
|
+
|
|
130
|
+
def post_update(self, parent, *args, **kwargs):
|
|
131
|
+
"""Kwargs comes from eval_kw in solver"""
|
|
132
|
+
if log.log_level <= 12:
|
|
133
|
+
log.info(f"void post-updating {self.__class__.__name__}.{self}")
|
|
134
|
+
|
|
135
|
+
def collect_update_refs(self, eval_kw=None, ignore=None):
|
|
136
|
+
"""checks all methods and creates ref's to execute them later"""
|
|
137
|
+
from engforge.eng.costs import CostModel, Economics
|
|
138
|
+
|
|
139
|
+
updt_refs = {}
|
|
140
|
+
from engforge.components import Component
|
|
141
|
+
from engforge.component_collections import ComponentIter
|
|
142
|
+
|
|
143
|
+
# Ignore
|
|
144
|
+
if ignore is None:
|
|
145
|
+
ignore = set()
|
|
146
|
+
elif self in ignore:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
key = "top"
|
|
150
|
+
if self.__class__.update != SolveableMixin.update:
|
|
151
|
+
ref = Ref(self, _update_func(self, eval_kw if eval_kw else {}))
|
|
152
|
+
updt_refs[key] = ref
|
|
153
|
+
|
|
154
|
+
if isinstance(self, (CostModel, Economics)):
|
|
155
|
+
ref = Ref(self, _cost_update(self))
|
|
156
|
+
updt_refs[key + "._cost_model_"] = ref
|
|
157
|
+
|
|
158
|
+
for key, comp in self.internal_configurations(False).items():
|
|
159
|
+
# for key,lvl,comp in self.go_through_configurations(check_config=False):
|
|
160
|
+
if ignore is not None and comp in ignore:
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
if not isinstance(comp, SolveableMixin):
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
# provide add eval_kw
|
|
167
|
+
if eval_kw and key in eval_kw:
|
|
168
|
+
eval_kw_comp = eval_kw[key]
|
|
169
|
+
else:
|
|
170
|
+
eval_kw_comp = {}
|
|
171
|
+
ekw = eval_kw_comp
|
|
172
|
+
|
|
173
|
+
# Add if its a unique update method (not the passthrough)
|
|
174
|
+
if isinstance(comp, (CostModel, Economics)):
|
|
175
|
+
ref = Ref(comp, _cost_update(comp))
|
|
176
|
+
updt_refs[key + "._cost_model_"] = ref
|
|
177
|
+
|
|
178
|
+
if comp.__class__.update != SolveableMixin.update:
|
|
179
|
+
ref = Ref(comp, _update_func(comp, ekw))
|
|
180
|
+
updt_refs[key] = ref
|
|
181
|
+
|
|
182
|
+
# Cost Models
|
|
183
|
+
|
|
184
|
+
ignore.add(self)
|
|
185
|
+
return updt_refs
|
|
186
|
+
|
|
187
|
+
def collect_post_update_refs(self, eval_kw=None, ignore=None):
|
|
188
|
+
"""checks all methods and creates ref's to execute them later"""
|
|
189
|
+
updt_refs = {}
|
|
190
|
+
from engforge.components import Component
|
|
191
|
+
from engforge.component_collections import ComponentIter
|
|
192
|
+
|
|
193
|
+
# Ignore
|
|
194
|
+
if ignore is None:
|
|
195
|
+
ignore = set()
|
|
196
|
+
elif self in ignore:
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
if self.__class__.post_update != SolveableMixin.post_update:
|
|
200
|
+
ref = Ref(self, _post_update_func(self, eval_kw if eval_kw else {}))
|
|
201
|
+
updt_refs["top"] = ref
|
|
202
|
+
|
|
203
|
+
for key, comp in self.internal_configurations(False).items():
|
|
204
|
+
# for key,lvl,comp in self.go_through_configurations(check_config=False):
|
|
205
|
+
if ignore is not None and comp in ignore:
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
if not isinstance(comp, SolveableMixin):
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
# provide add eval_kw
|
|
212
|
+
if eval_kw and key in eval_kw:
|
|
213
|
+
eval_kw_comp = eval_kw[key]
|
|
214
|
+
else:
|
|
215
|
+
eval_kw_comp = {}
|
|
216
|
+
|
|
217
|
+
ekw = eval_kw_comp
|
|
218
|
+
|
|
219
|
+
# Add if its a unique update method (not the passthrough)
|
|
220
|
+
if comp.__class__.post_update != SolveableMixin.post_update:
|
|
221
|
+
ref = Ref(comp, _post_update_func(comp, ekw))
|
|
222
|
+
updt_refs[key] = ref
|
|
223
|
+
|
|
224
|
+
ignore.add(self)
|
|
225
|
+
return updt_refs
|
|
226
|
+
|
|
227
|
+
# internals caching
|
|
228
|
+
# instance attributes
|
|
229
|
+
|
|
230
|
+
# TODO: move all system / property & identification to the problem_context or new system_identificaiton class (problem/base)
|
|
231
|
+
@instance_cached
|
|
232
|
+
def signals(self):
|
|
233
|
+
"""this is just a record of signals from this solvable. dont use this to get the signals, use high level signals strategy #TODO: add signals strategy"""
|
|
234
|
+
return {k: getattr(self, k) for k in self.signals_attributes()}
|
|
235
|
+
|
|
236
|
+
@instance_cached
|
|
237
|
+
def solvers(self):
|
|
238
|
+
"""this is just a record of any solvable attribute. dont use this to get the attribute, use another strategy"""
|
|
239
|
+
return {k: getattr(self, k) for k in self.solvers_attributes()}
|
|
240
|
+
|
|
241
|
+
@instance_cached(allow_set=True)
|
|
242
|
+
def transients(self):
|
|
243
|
+
"""this is just a record of any transient attribute. dont use this to get the transient, use another strategy"""
|
|
244
|
+
return {k: getattr(self, k) for k in self.transients_attributes()}
|
|
245
|
+
|
|
246
|
+
def internal_components(self, recache=False) -> dict:
|
|
247
|
+
"""get all the internal components"""
|
|
248
|
+
if recache == False and hasattr(self, "_prv_internal_components"):
|
|
249
|
+
return self._prv_internal_components
|
|
250
|
+
from engforge.components import Component
|
|
251
|
+
|
|
252
|
+
o = {k: getattr(self, k) for k in self.slots_attributes()}
|
|
253
|
+
o = {k: v for k, v in o.items() if isinstance(v, Component)}
|
|
254
|
+
self._prv_internal_components = o
|
|
255
|
+
return o
|
|
256
|
+
|
|
257
|
+
def internal_systems(self, recache=False) -> dict:
|
|
258
|
+
"""get all the internal components"""
|
|
259
|
+
if recache == False and hasattr(self, "_prv_internal_systems"):
|
|
260
|
+
return self._prv_internal_systems
|
|
261
|
+
from engforge.system import System
|
|
262
|
+
|
|
263
|
+
o = {k: getattr(self, k) for k in self.slots_attributes()}
|
|
264
|
+
o = {k: v for k, v in o.items() if isinstance(v, System)}
|
|
265
|
+
self._prv_internal_systems = o
|
|
266
|
+
return o
|
|
267
|
+
|
|
268
|
+
def internal_tabulations(self, recache=False) -> dict:
|
|
269
|
+
"""get all the internal tabulations"""
|
|
270
|
+
from engforge.tabulation import TabulationMixin
|
|
271
|
+
|
|
272
|
+
if recache == False and hasattr(self, "_prv_internal_tabs"):
|
|
273
|
+
return self._prv_internal_tabs
|
|
274
|
+
|
|
275
|
+
o = {k: getattr(self, k) for k in self.slots_attributes()}
|
|
276
|
+
o = {k: v for k, v in o.items() if isinstance(v, TabulationMixin)}
|
|
277
|
+
self._prv_internal_tabs = o
|
|
278
|
+
return o
|
|
279
|
+
|
|
280
|
+
# recursive references
|
|
281
|
+
@instance_cached
|
|
282
|
+
def iterable_components(self) -> dict:
|
|
283
|
+
"""Finds ComponentIter internal_components that are not 'wide'"""
|
|
284
|
+
from engforge.component_collections import ComponentIter
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
k: v
|
|
288
|
+
for k, v in self.internal_components().items()
|
|
289
|
+
if isinstance(v, ComponentIter) and not v.wide
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
def internal_references(self, recache=False, numeric_only=False) -> dict:
|
|
293
|
+
"""get references to all internal attributes and values, only saving when complete cache info is requested (vs numeric only)"""
|
|
294
|
+
if not numeric_only and (
|
|
295
|
+
recache == False and hasattr(self, "_prv_internal_references")
|
|
296
|
+
):
|
|
297
|
+
return self._prv_internal_references
|
|
298
|
+
|
|
299
|
+
out = self._gather_references(numeric_only=numeric_only)
|
|
300
|
+
if not numeric_only:
|
|
301
|
+
self._prv_internal_references = out
|
|
302
|
+
return out
|
|
303
|
+
|
|
304
|
+
def _gather_references(self, numeric_only=False) -> dict:
|
|
305
|
+
out = {}
|
|
306
|
+
out["attributes"] = at = {}
|
|
307
|
+
out["properties"] = pr = {}
|
|
308
|
+
|
|
309
|
+
for key in self.system_properties_classdef():
|
|
310
|
+
pr[key] = Ref(self, key, True, False)
|
|
311
|
+
|
|
312
|
+
if numeric_only:
|
|
313
|
+
for key in self.numeric_fields():
|
|
314
|
+
at[key] = Ref(self, key, False, True)
|
|
315
|
+
else:
|
|
316
|
+
for key in self.input_fields((list, tuple, dict)):
|
|
317
|
+
at[key] = Ref(self, key, False, True)
|
|
318
|
+
|
|
319
|
+
return out
|
|
320
|
+
|
|
321
|
+
def _iterate_components(self):
|
|
322
|
+
"""sets the current component for each product combination of iterable_components"""
|
|
323
|
+
|
|
324
|
+
components = self.iterable_components
|
|
325
|
+
|
|
326
|
+
if not components:
|
|
327
|
+
yield # enter once
|
|
328
|
+
else:
|
|
329
|
+
|
|
330
|
+
def _gen(gen, compkey):
|
|
331
|
+
for itemkey, item in gen:
|
|
332
|
+
yield compkey, itemkey
|
|
333
|
+
|
|
334
|
+
iter_vals = {
|
|
335
|
+
cn: _gen(comp._item_gen(), cn) for cn, comp in components.items()
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
for out in itertools.product(*list(iter_vals.values())):
|
|
339
|
+
for ck, ikey in out:
|
|
340
|
+
# TODO: progress bar or print location
|
|
341
|
+
components[ck].current_item = ikey
|
|
342
|
+
yield out
|
|
343
|
+
|
|
344
|
+
# finally reset the data!
|
|
345
|
+
for ck, comp in components.items():
|
|
346
|
+
comp.reset()
|
|
347
|
+
|
|
348
|
+
def comp_references(self, ignore_none_comp=True, **kw):
|
|
349
|
+
"""A cached set of recursive references to any slot component
|
|
350
|
+
#FIXME: by instance recache on iterative component change or other signals
|
|
351
|
+
"""
|
|
352
|
+
out = {}
|
|
353
|
+
for key, lvl, comp in self.go_through_configurations(parent_level=1, **kw):
|
|
354
|
+
if ignore_none_comp and not isinstance(comp, SolveableMixin):
|
|
355
|
+
self.warning(f"ignoring {key} {lvl}|{comp}")
|
|
356
|
+
continue
|
|
357
|
+
out[key] = comp
|
|
358
|
+
return out
|
|
359
|
+
|
|
360
|
+
# Run & Input
|
|
361
|
+
def _iterate_input_matrix(
|
|
362
|
+
self,
|
|
363
|
+
method,
|
|
364
|
+
cb=None,
|
|
365
|
+
sequence: list = None,
|
|
366
|
+
eval_kw: dict = None,
|
|
367
|
+
sys_kw: dict = None,
|
|
368
|
+
force_solve=False,
|
|
369
|
+
return_results=False,
|
|
370
|
+
method_kw: dict = None,
|
|
371
|
+
print_kw: dict = None,
|
|
372
|
+
**kwargs,
|
|
373
|
+
):
|
|
374
|
+
"""applies a permutation of input vars for vars. runs the system instance by applying input to the system and its slot-components, ensuring that the targeted attributes actualy exist.
|
|
375
|
+
|
|
376
|
+
:param revert: will reset the values of X that were recorded at the beginning of the run.
|
|
377
|
+
:param cb: a callback function that takes the system as an argument cb(system)
|
|
378
|
+
:param sequence: a list of dictionaries that should be run in order per the outer-product of kwargs
|
|
379
|
+
:param eval_kw: a dictionary of keyword arguments to pass to the eval function of each component by their name and a set of keyword args. Use this to set values in the component that are not inputs to the system. No iteration occurs upon these values, they are static and irrevertable
|
|
380
|
+
:param sys_kw: a dictionary of keyword arguments to pass to the eval function of each system by their name and a set of keyword args. Use this to set values in the component that are not inputs to the system. No iteration occurs upon these values, they are static and irrevertable
|
|
381
|
+
:param print_kw: a dictionary of keyword arguments to pass to the print_all_info function of the current context
|
|
382
|
+
|
|
383
|
+
:param kwargs: inputs are run on a product basis asusming they correspond to actual scoped vars (system.var or system.slot.var)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
:returns: system or list of systems. If transient a set of systems that have been run with permutations of the input, otherwise a single system with all permutations input run
|
|
387
|
+
"""
|
|
388
|
+
from engforge.system import System
|
|
389
|
+
from engforge.problem_context import ProblemExec
|
|
390
|
+
|
|
391
|
+
self.debug(f"running [Solver].{method} {self.identity} with input {kwargs}")
|
|
392
|
+
assert hasattr(ProblemExec.class_cache, "session"), "must be active context!"
|
|
393
|
+
# create iterable null for sequence
|
|
394
|
+
if sequence is None or not sequence:
|
|
395
|
+
sequence = [{}]
|
|
396
|
+
|
|
397
|
+
if method_kw is None:
|
|
398
|
+
method_kw = {}
|
|
399
|
+
|
|
400
|
+
# Create Keys List
|
|
401
|
+
sequence_keys = set()
|
|
402
|
+
for seq in sequence:
|
|
403
|
+
sequence_keys = sequence_keys.union(set(seq.keys()))
|
|
404
|
+
|
|
405
|
+
# RUN when not solved, or anything changed, or arguments or if forced
|
|
406
|
+
if force_solve or not self.solved or self.anything_changed or kwargs:
|
|
407
|
+
_input = self.parse_run_kwargs(**kwargs)
|
|
408
|
+
|
|
409
|
+
output = {}
|
|
410
|
+
inputs = {}
|
|
411
|
+
result = {"output": output, "input_sets": inputs}
|
|
412
|
+
|
|
413
|
+
# Pre Run Callback
|
|
414
|
+
self.pre_run_callback(eval_kw=eval_kw, sys_kw=sys_kw, **kwargs)
|
|
415
|
+
|
|
416
|
+
# Premute the input as per SS or Transient Logic
|
|
417
|
+
ingrp = list(_input.values())
|
|
418
|
+
keys = list(_input.keys())
|
|
419
|
+
|
|
420
|
+
# Iterate over components (they are assigned to the system by call)
|
|
421
|
+
for itercomp in self._iterate_components():
|
|
422
|
+
# Iterate over inputs
|
|
423
|
+
for vars in itertools.product(*ingrp):
|
|
424
|
+
# Set the reference aliases
|
|
425
|
+
cur = {k: v for k, v in zip(keys, vars)}
|
|
426
|
+
|
|
427
|
+
# Iterate over Sequence (or run once)
|
|
428
|
+
for seq in sequence:
|
|
429
|
+
# apply sequence values
|
|
430
|
+
icur = cur.copy()
|
|
431
|
+
# apply sequence values
|
|
432
|
+
if seq:
|
|
433
|
+
icur.update(**seq)
|
|
434
|
+
|
|
435
|
+
# Run The Method with inputs provisioned
|
|
436
|
+
out = method(icur, eval_kw, sys_kw, cb=cb, **method_kw)
|
|
437
|
+
|
|
438
|
+
if (
|
|
439
|
+
print_kw
|
|
440
|
+
and hasattr(self, "last_context")
|
|
441
|
+
and self.last_context
|
|
442
|
+
):
|
|
443
|
+
self.last_context.print_all_info(**print_kw)
|
|
444
|
+
|
|
445
|
+
if return_results:
|
|
446
|
+
# store the output
|
|
447
|
+
output[max(output) + 1 if output else 0] = out
|
|
448
|
+
# store the input
|
|
449
|
+
inputs[max(inputs) + 1 if inputs else 0] = icur
|
|
450
|
+
|
|
451
|
+
# nice
|
|
452
|
+
# TODO: wrap this with context manager
|
|
453
|
+
self._solved = True
|
|
454
|
+
|
|
455
|
+
# Pre Run Callback with current state
|
|
456
|
+
self.post_run_callback(eval_kw=eval_kw, sys_kw=sys_kw, **kwargs)
|
|
457
|
+
|
|
458
|
+
if return_results:
|
|
459
|
+
return result
|
|
460
|
+
|
|
461
|
+
elif not self.anything_changed:
|
|
462
|
+
self.warning(f"nothing changed, not running {self.identity}")
|
|
463
|
+
|
|
464
|
+
return
|
|
465
|
+
elif self.solved:
|
|
466
|
+
raise Exception("Analysis Already Solved")
|
|
467
|
+
|
|
468
|
+
# IO Functions
|
|
469
|
+
def parse_simulation_input(self, **kwargs):
|
|
470
|
+
"""parses the simulation input
|
|
471
|
+
|
|
472
|
+
:param dt: timestep in s, required for transients
|
|
473
|
+
:param endtime: when to end the simulation
|
|
474
|
+
"""
|
|
475
|
+
# timestep
|
|
476
|
+
if "dt" not in kwargs:
|
|
477
|
+
raise Exception("transients require `dt` to run")
|
|
478
|
+
# f"transients require timestep input `dt`"
|
|
479
|
+
dt = float(kwargs.pop("dt"))
|
|
480
|
+
|
|
481
|
+
# endtime
|
|
482
|
+
if "endtime" not in kwargs:
|
|
483
|
+
raise Exception("transients require `endtime` to run")
|
|
484
|
+
|
|
485
|
+
# add data
|
|
486
|
+
_trans_opts = {"dt": None, "endtime": None}
|
|
487
|
+
_trans_opts["dt"] = dt
|
|
488
|
+
|
|
489
|
+
# f"transients require `endtime` to specify "
|
|
490
|
+
_trans_opts["endtime"] = endtime = float(kwargs.pop("endtime"))
|
|
491
|
+
_trans_opts["Nrun"] = max(int(endtime / dt) + 1, 1)
|
|
492
|
+
|
|
493
|
+
# TODO: expose integrator choices
|
|
494
|
+
# TODO: add delay and signal & feedback options
|
|
495
|
+
|
|
496
|
+
return _trans_opts
|
|
497
|
+
|
|
498
|
+
def parse_run_kwargs(self, **kwargs):
|
|
499
|
+
"""ensures correct input for simulation.
|
|
500
|
+
:returns: first set of input for initalization, and all input dictionaries as tuple.
|
|
501
|
+
|
|
502
|
+
"""
|
|
503
|
+
# Validate OTher Arguments By Parameter Or Comp-Recursive
|
|
504
|
+
var_args = {k: v for k, v in kwargs.items() if "." not in k}
|
|
505
|
+
comp_args = {k: v for k, v in kwargs.items() if "." in k}
|
|
506
|
+
|
|
507
|
+
# check vars
|
|
508
|
+
inpossible = set.union(set(self.input_fields()), set(self.slots_attributes()))
|
|
509
|
+
argdiff = set(var_args).difference(inpossible)
|
|
510
|
+
assert not argdiff, f"bad input {argdiff}"
|
|
511
|
+
|
|
512
|
+
# check components
|
|
513
|
+
comps = set([k.split(".")[0] for k in comp_args.keys()])
|
|
514
|
+
compdiff = comps.difference(set(self.slots_attributes()))
|
|
515
|
+
assert not compdiff, f"bad slot references {compdiff}"
|
|
516
|
+
|
|
517
|
+
_input = {}
|
|
518
|
+
test = lambda v, add: isinstance(v, (int, float, str, *add)) or v is None
|
|
519
|
+
|
|
520
|
+
# vars input
|
|
521
|
+
for k, v in kwargs.items():
|
|
522
|
+
# If a slot check the type is applicable
|
|
523
|
+
subslot = self.check_ref_slot_type(k)
|
|
524
|
+
if subslot is not None:
|
|
525
|
+
# log.debug(f'found subslot {k}: {subslot}')
|
|
526
|
+
addty = subslot
|
|
527
|
+
else:
|
|
528
|
+
addty = []
|
|
529
|
+
|
|
530
|
+
# Ensure Its a List
|
|
531
|
+
if isinstance(v, numpy.ndarray):
|
|
532
|
+
v = v.tolist()
|
|
533
|
+
|
|
534
|
+
if not isinstance(v, list):
|
|
535
|
+
assert test(v, addty), f"bad values {k}:{v}"
|
|
536
|
+
v = [v]
|
|
537
|
+
else:
|
|
538
|
+
assert all([test(vi, addty) for vi in v]), f"bad values: {k}:{v}"
|
|
539
|
+
|
|
540
|
+
if k not in _input:
|
|
541
|
+
_input[k] = v
|
|
542
|
+
else:
|
|
543
|
+
_input[k].extend(v)
|
|
544
|
+
|
|
545
|
+
return _input
|
|
546
|
+
|
|
547
|
+
# REFERENCE FUNCTIONS
|
|
548
|
+
# Location Funcitons
|
|
549
|
+
@classmethod
|
|
550
|
+
def locate(cls, key, fail=True) -> type:
|
|
551
|
+
""":returns: the class or attribute by key if its in this system class or a subcomponent. If nothing is found raise an error"""
|
|
552
|
+
# Nested
|
|
553
|
+
log.msg(f"locating {cls.__name__} | key: {key}")
|
|
554
|
+
val = None
|
|
555
|
+
|
|
556
|
+
if "." in key:
|
|
557
|
+
args = key.split(".")
|
|
558
|
+
comp, sub = args[0], ".".join(args[1:])
|
|
559
|
+
assert comp in cls.slots_attributes(), f"invalid {comp} in {key}"
|
|
560
|
+
comp_cls = cls.slots_attributes(attr_type=True)[comp].accepted[0]
|
|
561
|
+
val = comp_cls.locate(sub, fail=True)
|
|
562
|
+
|
|
563
|
+
elif key in cls.input_fields():
|
|
564
|
+
val = cls.input_fields()[key]
|
|
565
|
+
|
|
566
|
+
elif key in cls.system_properties_classdef():
|
|
567
|
+
val = cls.system_properties_classdef()[key]
|
|
568
|
+
|
|
569
|
+
# Fail on comand but otherwise return val
|
|
570
|
+
if val is None:
|
|
571
|
+
if fail:
|
|
572
|
+
raise Exception(f"key {key} not found")
|
|
573
|
+
return None
|
|
574
|
+
return val
|
|
575
|
+
|
|
576
|
+
def locate_ref(self, key, fail=True, **kw):
|
|
577
|
+
"""Pass a string of a relative var or property on this system or pass a callable to get a reference to a function. If the key has a `.` in it the comp the lowest level component will be returned, unless a callable is passed in which case this component will be used or the `comp` passed in the kw will be used.
|
|
578
|
+
:param key: the key to locate, or a callable to be used as a reference
|
|
579
|
+
:param comp: the component to use if a callable is passed
|
|
580
|
+
:returns: the instance assigned to this system. If the key has a `.` in it the comp the lowest level component will be returned
|
|
581
|
+
"""
|
|
582
|
+
|
|
583
|
+
log.msg(f"locating {self.identity} | key: {key}")
|
|
584
|
+
val = None
|
|
585
|
+
|
|
586
|
+
# Handle callable key override
|
|
587
|
+
if callable(key):
|
|
588
|
+
comp = kw.pop("comp", self)
|
|
589
|
+
func = copy.copy(key)
|
|
590
|
+
return Ref(comp, func, **kw)
|
|
591
|
+
else:
|
|
592
|
+
assert "comp" not in kw, f"comp kwarg not allowed with string key {key}"
|
|
593
|
+
|
|
594
|
+
if "." in key:
|
|
595
|
+
args = key.split(".")
|
|
596
|
+
comp, sub = args[0], ".".join(args[1:])
|
|
597
|
+
assert comp in self.slots_attributes(), f"invalid {comp} in {key}"
|
|
598
|
+
# comp_cls = cls.slots_attributes()[comp].type.accepted[0]
|
|
599
|
+
comp = getattr(self, comp)
|
|
600
|
+
if "." not in key:
|
|
601
|
+
return Ref(comp, sub, **kw)
|
|
602
|
+
if comp:
|
|
603
|
+
return comp.locate_ref(sub, fail=fail, **kw)
|
|
604
|
+
else:
|
|
605
|
+
return None
|
|
606
|
+
|
|
607
|
+
elif key in self.input_fields():
|
|
608
|
+
# val= cls.input_fields()[key]
|
|
609
|
+
return Ref(self, key, **kw)
|
|
610
|
+
|
|
611
|
+
elif key in self.system_properties_classdef():
|
|
612
|
+
# val= cls.system_properties_classdef()[key]
|
|
613
|
+
return Ref(self, key, **kw)
|
|
614
|
+
|
|
615
|
+
elif key in self.internal_configurations() or key in self.slots_attributes():
|
|
616
|
+
return Ref(self, key, **kw)
|
|
617
|
+
|
|
618
|
+
# Fail on comand but otherwise return val
|
|
619
|
+
if val is None:
|
|
620
|
+
if fail:
|
|
621
|
+
raise Exception(f"key {key} not found")
|
|
622
|
+
return None
|
|
623
|
+
return val
|
|
624
|
+
|
|
625
|
+
# Reference Caching
|
|
626
|
+
# TODO: move to problem context
|
|
627
|
+
def system_references(self, recache=False, numeric_only=False, **kw):
|
|
628
|
+
"""gather a list of references to attributes and"""
|
|
629
|
+
if (
|
|
630
|
+
not numeric_only
|
|
631
|
+
and not kw
|
|
632
|
+
and (recache == False and hasattr(self, "_prv_system_references"))
|
|
633
|
+
):
|
|
634
|
+
return self._prv_system_references
|
|
635
|
+
|
|
636
|
+
# TODO: system references are really important and nesting them together complicates the refresh process. Each component should be able to refresh itself and its children on set_state, as well as alert parents to change. Ideally the `Ref` objects could stay the same and no `recache` would need to occur. This would be a huge performance boost and fix a lot of the issues with the current system.
|
|
637
|
+
|
|
638
|
+
out = self.internal_references(recache, numeric_only=numeric_only)
|
|
639
|
+
tatr = out["attributes"]
|
|
640
|
+
tprp = out["properties"]
|
|
641
|
+
comp_dict = {"": self}
|
|
642
|
+
out["components"] = comp_set_ref = {}
|
|
643
|
+
|
|
644
|
+
# component iternals
|
|
645
|
+
for key, comp in self.comp_references(**kw).items():
|
|
646
|
+
if comp is None:
|
|
647
|
+
continue
|
|
648
|
+
sout = comp.internal_references(recache, numeric_only=numeric_only)
|
|
649
|
+
satr = sout["attributes"]
|
|
650
|
+
sprp = sout["properties"]
|
|
651
|
+
|
|
652
|
+
# find the parent component
|
|
653
|
+
key_segs = key.split(".")
|
|
654
|
+
if len(key_segs) == 1:
|
|
655
|
+
parent = ""
|
|
656
|
+
else:
|
|
657
|
+
parent = ".".join(key_segs[:-1])
|
|
658
|
+
|
|
659
|
+
# parent refs for assigning a component
|
|
660
|
+
comp_dict[key] = comp
|
|
661
|
+
if parent in comp_dict:
|
|
662
|
+
attr_name = key.split(".")[-1]
|
|
663
|
+
comp_set_ref[key] = Ref(comp_dict[parent], attr_name, False, True)
|
|
664
|
+
|
|
665
|
+
# Fill in
|
|
666
|
+
for k, v in satr.items():
|
|
667
|
+
if k in skipa_attr_names:
|
|
668
|
+
continue
|
|
669
|
+
tatr[f"{key}.{k}"] = v
|
|
670
|
+
|
|
671
|
+
for k, v in sprp.items():
|
|
672
|
+
tprp[f"{key}.{k}"] = v
|
|
673
|
+
|
|
674
|
+
if not numeric_only and not kw:
|
|
675
|
+
self._prv_system_references = out
|
|
676
|
+
return out
|
|
677
|
+
|
|
678
|
+
def collect_comp_refs(self, conf: "Configuration" = None, **kw):
|
|
679
|
+
"""collects all the references for the system grouped by component"""
|
|
680
|
+
if conf is None:
|
|
681
|
+
conf = self
|
|
682
|
+
comp_dict = {}
|
|
683
|
+
attr_dict = {}
|
|
684
|
+
cls_dict = {}
|
|
685
|
+
out = {"comps": comp_dict, "attrs": attr_dict, "type": cls_dict}
|
|
686
|
+
for key, lvl, conf in self.go_through_configurations(**kw):
|
|
687
|
+
comp_dict[key] = conf
|
|
688
|
+
attr_dict[key] = conf.collect_inst_attributes()
|
|
689
|
+
|
|
690
|
+
return out
|
|
691
|
+
|
|
692
|
+
def collect_solver_refs(
|
|
693
|
+
self,
|
|
694
|
+
conf: "Configuration" = None,
|
|
695
|
+
check_atr_f=None,
|
|
696
|
+
check_kw=None,
|
|
697
|
+
check_dynamics=True,
|
|
698
|
+
**kw,
|
|
699
|
+
):
|
|
700
|
+
"""collects all the references for the system grouped by function and prepended with the system key"""
|
|
701
|
+
from engforge.attributes import ATTR_BASE
|
|
702
|
+
from engforge.engforge_attributes import AttributedBaseMixin
|
|
703
|
+
|
|
704
|
+
confobj = conf
|
|
705
|
+
if confobj is None:
|
|
706
|
+
confobj = self
|
|
707
|
+
|
|
708
|
+
comp_dict = {}
|
|
709
|
+
attr_dict = {}
|
|
710
|
+
cls_dict = {}
|
|
711
|
+
skipped = {}
|
|
712
|
+
|
|
713
|
+
out = {
|
|
714
|
+
"comps": comp_dict,
|
|
715
|
+
"attrs": attr_dict,
|
|
716
|
+
"type": cls_dict,
|
|
717
|
+
"skipped": skipped,
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
# Go through all components
|
|
721
|
+
for key, lvl, conf in confobj.go_through_configurations(**kw):
|
|
722
|
+
if conf is None:
|
|
723
|
+
continue
|
|
724
|
+
|
|
725
|
+
if hasattr(conf, "_solver_override") and conf._solver_override:
|
|
726
|
+
continue
|
|
727
|
+
|
|
728
|
+
# Get attributes & attribute instances
|
|
729
|
+
atrs = conf.collect_inst_attributes()
|
|
730
|
+
rawattr = conf.collect_inst_attributes(handle_inst=False)
|
|
731
|
+
key = f"{key}." if key else "" # you need a dot if there's a key
|
|
732
|
+
comp_dict[key] = conf
|
|
733
|
+
# Gather attribute heirarchy and make key.var the dictionary entry
|
|
734
|
+
for atype, aval in atrs.items():
|
|
735
|
+
ck_type = rawattr[atype]
|
|
736
|
+
if atype not in cls_dict:
|
|
737
|
+
cls_dict[atype] = {}
|
|
738
|
+
|
|
739
|
+
if isinstance(aval, dict) and aval:
|
|
740
|
+
for k, pre, val in ATTR_BASE.unpack_atrs(aval, atype):
|
|
741
|
+
# No Room For Components (SLOTS feature)
|
|
742
|
+
if isinstance(val, (AttributedBaseMixin, ATTR_BASE)):
|
|
743
|
+
if conf.log_level <= 5:
|
|
744
|
+
conf.debug(f"skipping comp attr {val}")
|
|
745
|
+
continue
|
|
746
|
+
|
|
747
|
+
if val is None:
|
|
748
|
+
continue
|
|
749
|
+
|
|
750
|
+
if conf.log_level <= 5:
|
|
751
|
+
conf.msg(f"")
|
|
752
|
+
conf.msg(f"got val: {k} {pre} {val}")
|
|
753
|
+
|
|
754
|
+
slv_type = None
|
|
755
|
+
pre_var = pre.split(".")[-1]
|
|
756
|
+
if hasattr(conf, pre_var):
|
|
757
|
+
_var = getattr(conf, pre_var)
|
|
758
|
+
if isinstance(_var, AttributeInstance):
|
|
759
|
+
slv_type = _var
|
|
760
|
+
conf.msg(f"slv type: {conf.classname}.{pre_var} -> {_var}")
|
|
761
|
+
|
|
762
|
+
val_type = ck_type[pre_var]
|
|
763
|
+
|
|
764
|
+
# Otherwise assign the data from last var and the compoenent name
|
|
765
|
+
var_name = pre_var # prer alias
|
|
766
|
+
if slv_type:
|
|
767
|
+
var_name = slv_type.get_alias(pre)
|
|
768
|
+
|
|
769
|
+
# Keep reference to the original type and name
|
|
770
|
+
scope_name = f"{key}{var_name}"
|
|
771
|
+
cls_dict[atype][scope_name] = val_type
|
|
772
|
+
|
|
773
|
+
if conf.log_level <= 5:
|
|
774
|
+
conf.msg(f"rec: {var_name} {k} {pre} {val} {slv_type}")
|
|
775
|
+
|
|
776
|
+
# Check to skip this item
|
|
777
|
+
# keep references even if null
|
|
778
|
+
pre = f"{atype}.{k}" # pre switch
|
|
779
|
+
if pre not in attr_dict:
|
|
780
|
+
attr_dict[pre] = {}
|
|
781
|
+
|
|
782
|
+
if isinstance(val, Ref) and val.allow_set:
|
|
783
|
+
# its a var, skip it if it's already been skipped
|
|
784
|
+
current_skipped = [set(v) for v in skipped.values()]
|
|
785
|
+
if current_skipped and val.key in set.union(
|
|
786
|
+
*current_skipped
|
|
787
|
+
):
|
|
788
|
+
continue
|
|
789
|
+
|
|
790
|
+
# Perform the check
|
|
791
|
+
if check_atr_f and not check_atr_f(
|
|
792
|
+
pre, scope_name, val_type, check_kw
|
|
793
|
+
):
|
|
794
|
+
if conf.log_level <= 5:
|
|
795
|
+
conf.msg(f"chk skip {scope_name} {k} {pre} {val}")
|
|
796
|
+
if pre not in skipped:
|
|
797
|
+
skipped[pre] = []
|
|
798
|
+
|
|
799
|
+
if isinstance(val, Ref) and val.allow_set:
|
|
800
|
+
# its a var
|
|
801
|
+
skipped[pre].append(f"{key}{val.key}")
|
|
802
|
+
else:
|
|
803
|
+
# not objective or settable, must be a obj/cond
|
|
804
|
+
skipped[pre].append(scope_name)
|
|
805
|
+
continue
|
|
806
|
+
|
|
807
|
+
# if the value is a dictionary, unpack it with comp key
|
|
808
|
+
if val:
|
|
809
|
+
attr_dict[pre].update({scope_name: val})
|
|
810
|
+
else:
|
|
811
|
+
if attr_dict[pre]:
|
|
812
|
+
continue # keep it!
|
|
813
|
+
else:
|
|
814
|
+
attr_dict[pre] = {} # reset it
|
|
815
|
+
|
|
816
|
+
elif atype not in attr_dict or not attr_dict[atype]:
|
|
817
|
+
# print(f'unpacking {atype} {aval}')
|
|
818
|
+
attr_dict[atype] = {}
|
|
819
|
+
|
|
820
|
+
# Dynamic Variables Add, following the skipped items
|
|
821
|
+
if check_dynamics:
|
|
822
|
+
dyn_refs = self.collect_dynamic_refs(confobj).copy()
|
|
823
|
+
# house keeping to organize special returns
|
|
824
|
+
|
|
825
|
+
# TODO: generalize this with a function to update the attr dict serach result when changing components
|
|
826
|
+
out["dynamic_comps"] = dyn_comp = dyn_refs.pop("dynamic_comps", {})
|
|
827
|
+
|
|
828
|
+
# Check the dynamics for the system
|
|
829
|
+
if check_atr_f or any([v for v in skipped.values()]):
|
|
830
|
+
skipd = set()
|
|
831
|
+
# check each group of dynamics
|
|
832
|
+
for pre, refs in dyn_refs.items():
|
|
833
|
+
if pre not in skipped:
|
|
834
|
+
skipped[pre] = []
|
|
835
|
+
|
|
836
|
+
if pre not in attr_dict:
|
|
837
|
+
attr_dict[pre] = {} # initalize dynamic group
|
|
838
|
+
|
|
839
|
+
# eval each ref for inclusion
|
|
840
|
+
for var, ref in refs.items():
|
|
841
|
+
key_segs = var.split(".")
|
|
842
|
+
key = "" if len(key_segs) == 1 else ".".join(key_segs[:-1])
|
|
843
|
+
scoped_name = f"{var}"
|
|
844
|
+
conf = dyn_comp.get(key)
|
|
845
|
+
|
|
846
|
+
is_ref = isinstance(ref, Ref)
|
|
847
|
+
if not is_ref:
|
|
848
|
+
conf.info(f"not ref {scoped_name}")
|
|
849
|
+
continue
|
|
850
|
+
|
|
851
|
+
if is_ref and not ref.allow_set:
|
|
852
|
+
# adding unsettables (likely rates)
|
|
853
|
+
attr_dict[pre].update(**{var: ref})
|
|
854
|
+
self.debug(f"set escape: {scoped_name} {ref}")
|
|
855
|
+
continue
|
|
856
|
+
|
|
857
|
+
val_type = ref.comp
|
|
858
|
+
if (
|
|
859
|
+
check_atr_f
|
|
860
|
+
and isinstance(var, str)
|
|
861
|
+
and check_atr_f(pre, var, val_type, check_kw)
|
|
862
|
+
):
|
|
863
|
+
conf.msg(f"dynvar add {pre,var,val_type,skipd}")
|
|
864
|
+
attr_dict[pre].update(**{var: ref})
|
|
865
|
+
else:
|
|
866
|
+
conf.msg(f"dynvar endskip {pre,var,val_type,skipd}")
|
|
867
|
+
if check_atr_f: # then it didn't checkout
|
|
868
|
+
skipped[pre].append(scope_name)
|
|
869
|
+
|
|
870
|
+
else:
|
|
871
|
+
# There's no checks to be done, just add these
|
|
872
|
+
attr_dict.update(**dyn_refs)
|
|
873
|
+
else:
|
|
874
|
+
cpy = dflt_dynamics.copy()
|
|
875
|
+
cpy.pop("dynamic_comps", {})
|
|
876
|
+
attr_dict.update(cpy)
|
|
877
|
+
|
|
878
|
+
return out
|
|
879
|
+
|
|
880
|
+
# Dynamics info refs
|
|
881
|
+
def collect_dynamic_refs(self, conf: "Configuration" = None, **kw) -> dict:
|
|
882
|
+
"""collects the dynamics of the systems
|
|
883
|
+
1. Time.integrate
|
|
884
|
+
2. Dynamic Instances
|
|
885
|
+
"""
|
|
886
|
+
from engforge.dynamics import DynamicsMixin
|
|
887
|
+
|
|
888
|
+
if conf is None:
|
|
889
|
+
conf = self
|
|
890
|
+
dynamics = {k: {} for k in dflt_dynamics}
|
|
891
|
+
|
|
892
|
+
for key, lvl, conf in conf.go_through_configurations(**kw):
|
|
893
|
+
# FIXME: add a check for the dynamics mixin, that isn't hacky
|
|
894
|
+
# BUG: importing dynamicsmixin resolves as different class in different modules, weird
|
|
895
|
+
# if "dynamicsmixin" not in str(conf.__class__.mro()).lower():
|
|
896
|
+
# continue
|
|
897
|
+
if not isinstance(conf, DynamicsMixin) or not conf.is_dynamic:
|
|
898
|
+
continue
|
|
899
|
+
|
|
900
|
+
sval = f"{key}." if key else ""
|
|
901
|
+
scope = lambda d: {f"{sval}{k}": v for k, v in d.items()}
|
|
902
|
+
dynamics["dynamics.state"].update(scope(conf.Xt_ref))
|
|
903
|
+
dynamics["dynamics.input"].update(scope(conf.Ut_ref))
|
|
904
|
+
dynamics["dynamics.output"].update(scope(conf.Yt_ref))
|
|
905
|
+
dynamics["dynamics.rate"].update(scope(conf.dXtdt_ref))
|
|
906
|
+
dynamics["dynamic_comps"][key] = conf
|
|
907
|
+
|
|
908
|
+
return dynamics
|
|
909
|
+
|
|
910
|
+
def get_system_input_refs(
|
|
911
|
+
self,
|
|
912
|
+
strings=False,
|
|
913
|
+
numeric=True,
|
|
914
|
+
misc=False,
|
|
915
|
+
all=False,
|
|
916
|
+
boolean=False,
|
|
917
|
+
**kw,
|
|
918
|
+
) -> dict:
|
|
919
|
+
"""
|
|
920
|
+
Get the references to system input based on the specified criteria.
|
|
921
|
+
|
|
922
|
+
:param strings: Include system properties of string type.
|
|
923
|
+
:param numeric: Include system properties of numeric type (float, int).
|
|
924
|
+
:param misc: Include system properties of miscellaneous type.
|
|
925
|
+
:param all: Include all system properties regardless of type.
|
|
926
|
+
:param boolean: Include system properties of boolean type.
|
|
927
|
+
:param kw: Additional keyword arguments passed to recursive config loop
|
|
928
|
+
:return: A dictionary of system property references.
|
|
929
|
+
:rtype: dict
|
|
930
|
+
"""
|
|
931
|
+
from engforge.tabulation import SKIP_REF
|
|
932
|
+
|
|
933
|
+
refs = {}
|
|
934
|
+
for ckey, lvl, comp in self.go_through_configurations(**kw):
|
|
935
|
+
if comp is None:
|
|
936
|
+
continue
|
|
937
|
+
for p, atr in comp.input_fields().items():
|
|
938
|
+
if p in SKIP_REF and not all:
|
|
939
|
+
continue
|
|
940
|
+
if all:
|
|
941
|
+
refs[(f"{ckey}." if ckey else "") + p] = Ref(comp, p, False, True)
|
|
942
|
+
continue
|
|
943
|
+
elif atr.type:
|
|
944
|
+
ty = atr.type
|
|
945
|
+
if issubclass(ty, (bool)):
|
|
946
|
+
if not boolean:
|
|
947
|
+
continue # prevent catch at int type
|
|
948
|
+
refs[(f"{ckey}." if ckey else "") + p] = Ref(
|
|
949
|
+
comp, p, True, False
|
|
950
|
+
)
|
|
951
|
+
elif issubclass(ty, (float, int)) and numeric:
|
|
952
|
+
refs[(f"{ckey}." if ckey else "") + p] = Ref(
|
|
953
|
+
comp, p, False, True
|
|
954
|
+
)
|
|
955
|
+
elif issubclass(ty, (str)) and strings:
|
|
956
|
+
refs[(f"{ckey}." if ckey else "") + p] = Ref(
|
|
957
|
+
comp, p, False, True
|
|
958
|
+
)
|
|
959
|
+
elif misc:
|
|
960
|
+
refs[(f"{ckey}." if ckey else "") + p] = Ref(
|
|
961
|
+
comp, p, False, True
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
return refs
|