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/attributes.py
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""Defines a customizeable attrs attribute that is handled in configuration,
|
|
2
|
+
|
|
3
|
+
on init an instance of `Instance` type for any ATTR_BASE subclass is created"""
|
|
4
|
+
|
|
5
|
+
import attrs, attr, uuid
|
|
6
|
+
from engforge.logging import LoggingMixin, log
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ATTRLog(LoggingMixin):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
log = ATTRLog()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
DEFAULT_COMBO = "default"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AttributeInstance:
|
|
20
|
+
class_attr: "ATTR_BASE"
|
|
21
|
+
system: "System"
|
|
22
|
+
classname: str = "attribute" # for ref compatability
|
|
23
|
+
backref: "ATTR_BASE"
|
|
24
|
+
|
|
25
|
+
# TODO: universal slots method
|
|
26
|
+
# __slots__ = ["system", "class_attr"]
|
|
27
|
+
|
|
28
|
+
def __init__(self, class_attr: "CLASS_ATTR", system: "System", **kwargs) -> None:
|
|
29
|
+
self.class_attr = class_attr
|
|
30
|
+
self.system = system
|
|
31
|
+
self.compile(**kwargs)
|
|
32
|
+
|
|
33
|
+
def compile(self, **kwargs):
|
|
34
|
+
# raise NotImplementedError("Override Me!")
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def as_ref_dict(self) -> dict:
|
|
38
|
+
log.info(f"pass - as ref {self}")
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
def get_alias(self, path):
|
|
42
|
+
return path.split(".")[-1]
|
|
43
|
+
|
|
44
|
+
def is_active(self, value=False) -> bool:
|
|
45
|
+
mthd = "dflt"
|
|
46
|
+
if hasattr(self, "_active"):
|
|
47
|
+
mthd = "instance"
|
|
48
|
+
value = self._active
|
|
49
|
+
|
|
50
|
+
elif hasattr(self.class_attr, "active"):
|
|
51
|
+
mthd = "class"
|
|
52
|
+
value = self.class_attr.active
|
|
53
|
+
|
|
54
|
+
# print(f'{self}| {mthd} is_active: {value}')
|
|
55
|
+
# the default
|
|
56
|
+
return value
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def combos(self):
|
|
60
|
+
return self.class_attr.combos
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def active(self):
|
|
64
|
+
return self.class_attr.active
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# TODO: add a concept of queries of ATTR_BASE subclasses, where multiple types may be selected or filtered, with individual arguments.
|
|
68
|
+
class ATTR_BASE(attrs.Attribute):
|
|
69
|
+
"""A base class that handles initalization in the attrs meta class scheme by ultimately createing an Instance"""
|
|
70
|
+
|
|
71
|
+
name: str
|
|
72
|
+
config_cls: "System"
|
|
73
|
+
attr_prefix = "ATTR"
|
|
74
|
+
instance_class: AttributeInstance = AttributeInstance # Define me
|
|
75
|
+
default_options: dict
|
|
76
|
+
template_class = True
|
|
77
|
+
|
|
78
|
+
# TODO: add generic selection & activation of attributes
|
|
79
|
+
active: bool
|
|
80
|
+
combos: list
|
|
81
|
+
|
|
82
|
+
none_ok = False
|
|
83
|
+
|
|
84
|
+
# Activation & Combo Selection Functionality
|
|
85
|
+
@classmethod
|
|
86
|
+
def process_combos(cls, combos, default=None, add_combos=None):
|
|
87
|
+
if isinstance(combos, str):
|
|
88
|
+
if "*" in combos:
|
|
89
|
+
raise KeyError("wildcard (*) not allowed in combos!")
|
|
90
|
+
out = combos.split(",")
|
|
91
|
+
elif isinstance(combos, list):
|
|
92
|
+
if any(["*" in c for c in combos]):
|
|
93
|
+
raise KeyError("wildcard (*) not allowed in combos!")
|
|
94
|
+
out = combos
|
|
95
|
+
else:
|
|
96
|
+
out = []
|
|
97
|
+
|
|
98
|
+
if add_combos is not None:
|
|
99
|
+
out += cls.process_combos(add_combos, default=default)
|
|
100
|
+
|
|
101
|
+
if out:
|
|
102
|
+
return out
|
|
103
|
+
if default:
|
|
104
|
+
return default
|
|
105
|
+
return ["default"]
|
|
106
|
+
|
|
107
|
+
# Initialization
|
|
108
|
+
@classmethod
|
|
109
|
+
def configure_for_system(cls, name, config_class, cb=None, **kwargs):
|
|
110
|
+
"""add the config class, and perform checks with `class_validate)
|
|
111
|
+
:returns: [optional] a dictionary of options to be used in the make_attribute method
|
|
112
|
+
"""
|
|
113
|
+
log.debug(f"{cls.__name__} is being configured for {cls.attr_prefix}")
|
|
114
|
+
cls.name = name
|
|
115
|
+
cls.config_cls = config_class
|
|
116
|
+
|
|
117
|
+
if isinstance(cls.instance_class, ATTR_BASE) and not hasattr(
|
|
118
|
+
cls.instance_class, "backref"
|
|
119
|
+
):
|
|
120
|
+
# get parent class
|
|
121
|
+
refs = [cls, ATTR_BASE]
|
|
122
|
+
mro_group = cls.mro()
|
|
123
|
+
mro_group = mro_group[: mro_group.index(ATTR_BASE)]
|
|
124
|
+
cans = [v for v in mro_group if v not in refs]
|
|
125
|
+
last = cans[-1]
|
|
126
|
+
# if not hasattr(cls,'instance_class') or not cls.instance_class:
|
|
127
|
+
# log.info(f'no instance class! {cls}')
|
|
128
|
+
cls.instance_class.backref = last
|
|
129
|
+
else:
|
|
130
|
+
pass
|
|
131
|
+
# print(f'backref exists {cls.instance_class.backref} {cls}')
|
|
132
|
+
|
|
133
|
+
if cb is not None:
|
|
134
|
+
cb(cls, config_class, **kwargs)
|
|
135
|
+
|
|
136
|
+
return {} # OVERWRITE ME "custom_options":False
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def create_instance(cls, instance: "Configuration") -> AttributeInstance:
|
|
140
|
+
"""Create an instance of the instance_class"""
|
|
141
|
+
if cls.instance_class is None:
|
|
142
|
+
raise Exception(
|
|
143
|
+
f"Instance Class Hasnt Been Defined For `{cls}.instance_class`"
|
|
144
|
+
)
|
|
145
|
+
if not hasattr(cls, "config_cls"):
|
|
146
|
+
raise Exception(f"`config_cls` hasnt been defined for `{cls}`")
|
|
147
|
+
|
|
148
|
+
cls.class_validate(instance=instance)
|
|
149
|
+
return cls.instance_class(cls, instance)
|
|
150
|
+
|
|
151
|
+
# Override Me:
|
|
152
|
+
@classmethod
|
|
153
|
+
def configure_instance(cls, instance, attribute, value):
|
|
154
|
+
"""validates the instance given attr's init routine"""
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def class_validate(cls, instance, **kwargs):
|
|
159
|
+
"""validates onetime A method to validate the kwargs passed to the define method"""
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def define_validate(cls, **kwargs):
|
|
164
|
+
"""A method to validate the kwargs passed to the define method"""
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
# Interafce & Utility
|
|
168
|
+
@classmethod
|
|
169
|
+
def define(cls, **kwargs):
|
|
170
|
+
"""taking a component or system class as possible input valid input is later validated as an instance of that class or subclass"""
|
|
171
|
+
assert (
|
|
172
|
+
not cls.template_class
|
|
173
|
+
), f"{cls} is not template class and cannot defined anything, ie anything that has been created as a function of `define` cannot be defined"
|
|
174
|
+
|
|
175
|
+
cls.define_validate(**kwargs)
|
|
176
|
+
|
|
177
|
+
# Create A New Signals Class
|
|
178
|
+
kw_pairs = [f"{k}_{v}" for k, v in kwargs.items()]
|
|
179
|
+
new_name = f"{cls.attr_prefix}_{('_'.join(kw_pairs))}".replace(".", "_")
|
|
180
|
+
# define the class dictionary
|
|
181
|
+
new_dict = dict(name=new_name)
|
|
182
|
+
new_slot = cls._setup_cls(new_name, new_dict, **kwargs)
|
|
183
|
+
return new_slot
|
|
184
|
+
|
|
185
|
+
@classmethod
|
|
186
|
+
def _setup_cls(cls, name, new_dict, **kwargs):
|
|
187
|
+
# randomize name for specifics reasons
|
|
188
|
+
uid = str(uuid.uuid4())
|
|
189
|
+
# base info
|
|
190
|
+
name = name + "_" + uid.replace("-", "")[0:16]
|
|
191
|
+
new_dict["uuid"] = uid
|
|
192
|
+
new_dict["default_options"] = cls.default_options.copy()
|
|
193
|
+
new_dict["template_class"] = False
|
|
194
|
+
new_dict["name"] = name
|
|
195
|
+
|
|
196
|
+
# create a new type of attribute
|
|
197
|
+
new_slot = type(name, (cls,), new_dict)
|
|
198
|
+
new_slot.default_options["default"] = new_slot.make_factory()
|
|
199
|
+
new_slot.default_options["validator"] = new_slot.configure_instance
|
|
200
|
+
log.debug(
|
|
201
|
+
f"defined {name}|{new_slot} with {kwargs}| {new_slot.default_options}"
|
|
202
|
+
)
|
|
203
|
+
return new_slot
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def make_factory(cls, **kwargs):
|
|
207
|
+
# print(f"{cls} making factory with: {kwargs}")
|
|
208
|
+
return attrs.Factory(cls.create_instance, takes_self=True)
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def make_attribute(cls, name, comp_class, **kwargs):
|
|
212
|
+
"""makes an attrs.Attribute for the class"""
|
|
213
|
+
cust_options = cls.configure_for_system(name, comp_class, **kwargs)
|
|
214
|
+
# make copy for new instance
|
|
215
|
+
opts = cls.default_options.copy()
|
|
216
|
+
# update with custom kwargs
|
|
217
|
+
if isinstance(cust_options, dict):
|
|
218
|
+
opts.update(cust_options)
|
|
219
|
+
# input has the final override
|
|
220
|
+
opts.update(kwargs)
|
|
221
|
+
# The core functionality
|
|
222
|
+
opts.update(name=name, type=cls)
|
|
223
|
+
|
|
224
|
+
# Handle the default and validator, if not overridden
|
|
225
|
+
if "default" not in kwargs:
|
|
226
|
+
opts["default"] = cls.make_factory()
|
|
227
|
+
if "validator" not in kwargs:
|
|
228
|
+
opts["validator"] = cls.configure_instance
|
|
229
|
+
|
|
230
|
+
return attrs.Attribute(**opts)
|
|
231
|
+
|
|
232
|
+
# Structural Orchestration Through Subclassing
|
|
233
|
+
@classmethod
|
|
234
|
+
def collect_cls(cls, system) -> dict:
|
|
235
|
+
"""collects all the attributes for a system"""
|
|
236
|
+
if not isinstance(system, type):
|
|
237
|
+
system = system.__class__
|
|
238
|
+
return {k: at.type for k, at in system._get_init_attrs_data(cls).items()}
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def collect_attr_inst(cls, system, handle_inst=True) -> dict:
|
|
242
|
+
"""collects all the attribute instances for a system"""
|
|
243
|
+
cattr = cls.collect_cls(system)
|
|
244
|
+
out = {}
|
|
245
|
+
for k, v in cattr.items():
|
|
246
|
+
inst = getattr(system, k)
|
|
247
|
+
if (inst is None and getattr(cls.instance_class, "none_ok", False)) or (
|
|
248
|
+
cls.instance_class is not None
|
|
249
|
+
and not isinstance(inst, cls.instance_class)
|
|
250
|
+
):
|
|
251
|
+
log.warning(
|
|
252
|
+
f"Attribute {k}|{inst} is not an instance of {cls.instance_class} in {system}"
|
|
253
|
+
)
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
if inst and handle_inst:
|
|
257
|
+
inst = cls.handle_instance(inst)
|
|
258
|
+
|
|
259
|
+
if inst is not None:
|
|
260
|
+
out[k] = inst
|
|
261
|
+
return out
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
264
|
+
def unpack_atrs(d, pre="", conv=None):
|
|
265
|
+
if not conv:
|
|
266
|
+
conv = lambda v: v
|
|
267
|
+
if isinstance(d, dict):
|
|
268
|
+
for k, v in d.items():
|
|
269
|
+
if not isinstance(v, dict):
|
|
270
|
+
yield k, pre, conv(v)
|
|
271
|
+
elif v:
|
|
272
|
+
if pre and not pre.endswith("."):
|
|
273
|
+
ppre = pre + "."
|
|
274
|
+
ck = f"{ppre}{k}"
|
|
275
|
+
for kii, ki, vi in ATTR_BASE.unpack_atrs(v, ck):
|
|
276
|
+
yield kii, ki, conv(vi)
|
|
277
|
+
else:
|
|
278
|
+
if pre and not pre.endswith("."):
|
|
279
|
+
ppre = pre + "."
|
|
280
|
+
ck = f"{ppre}{k}"
|
|
281
|
+
yield "", ck, v
|
|
282
|
+
return d
|
|
283
|
+
|
|
284
|
+
@classmethod
|
|
285
|
+
def handle_instance(cls, canidate):
|
|
286
|
+
"""handles the instance, override as you wish"""
|
|
287
|
+
# print('handle_instance',cls,canidate,cls.instance_class)
|
|
288
|
+
|
|
289
|
+
if isinstance(canidate, cls.instance_class):
|
|
290
|
+
o = canidate.as_ref_dict()
|
|
291
|
+
return o
|
|
292
|
+
return canidate # live and let live
|
|
293
|
+
|
|
294
|
+
@classmethod
|
|
295
|
+
def subclasses(cls, out=None):
|
|
296
|
+
"""return all subclasses of components, including their subclasses
|
|
297
|
+
:param out: out is to pass when the middle of a recursive operation, do not use it!
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
# init the set by default
|
|
301
|
+
if out is None:
|
|
302
|
+
out = set()
|
|
303
|
+
|
|
304
|
+
for cls in cls.__subclasses__():
|
|
305
|
+
if not cls.template_class:
|
|
306
|
+
continue
|
|
307
|
+
out.add(cls)
|
|
308
|
+
cls.subclasses(out)
|
|
309
|
+
|
|
310
|
+
return out
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
ATTR_BASE.default_options = dict(
|
|
314
|
+
# validator=ATTR_BASE.configure_instance,
|
|
315
|
+
repr=False,
|
|
316
|
+
cmp=None,
|
|
317
|
+
hash=None,
|
|
318
|
+
init=False, # change this to allow for init input
|
|
319
|
+
metadata=None,
|
|
320
|
+
converter=None,
|
|
321
|
+
kw_only=True,
|
|
322
|
+
eq=None,
|
|
323
|
+
order=None,
|
|
324
|
+
on_setattr=None,
|
|
325
|
+
inherited=False,
|
|
326
|
+
)
|
engforge/common.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""A set of common values and functions that are globaly available."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import functools
|
|
5
|
+
|
|
6
|
+
# Common Modules
|
|
7
|
+
from engforge.configuration import *
|
|
8
|
+
from engforge.logging import LoggingMixin
|
|
9
|
+
from urllib.request import urlopen
|
|
10
|
+
|
|
11
|
+
import numpy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ForgeLog(LoggingMixin):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
log = ForgeLog()
|
|
19
|
+
log.info("Starting engforge Enviornment")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class inst_vectorize(numpy.vectorize):
|
|
23
|
+
def __get__(self, obj, objtype):
|
|
24
|
+
return functools.partial(self.__call__, obj)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def chunks(lst, n):
|
|
28
|
+
"""Yield successive n-sized chunks from lst."""
|
|
29
|
+
for i in range(0, len(lst), n):
|
|
30
|
+
yield lst[i : i + n]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_ec2_instance():
|
|
34
|
+
"""Check if an instance is running on AWS."""
|
|
35
|
+
result = False
|
|
36
|
+
meta = "http://169.254.169.254/latest/meta-data/public-ipv4"
|
|
37
|
+
try:
|
|
38
|
+
result = urlopen(meta, timeout=5.0).status == 200
|
|
39
|
+
return True
|
|
40
|
+
except:
|
|
41
|
+
return False
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_size(obj, seen=None):
|
|
46
|
+
"""Recursively finds size of objects"""
|
|
47
|
+
size = sys.getsizeof(obj)
|
|
48
|
+
if seen is None:
|
|
49
|
+
seen = set()
|
|
50
|
+
obj_id = id(obj)
|
|
51
|
+
if obj_id in seen:
|
|
52
|
+
return 0
|
|
53
|
+
# Important mark as seen *before* entering recursion to gracefully handle
|
|
54
|
+
# self-referential objects
|
|
55
|
+
seen.add(obj_id)
|
|
56
|
+
if isinstance(obj, dict):
|
|
57
|
+
size += sum([get_size(v, seen) for v in obj.values()])
|
|
58
|
+
size += sum([get_size(k, seen) for k in obj.keys()])
|
|
59
|
+
elif hasattr(obj, "__dict__"):
|
|
60
|
+
size += get_size(obj.__dict__, seen)
|
|
61
|
+
elif hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, bytearray)):
|
|
62
|
+
size += sum([get_size(i, seen) for i in obj])
|
|
63
|
+
return size
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Constants
|
|
67
|
+
g = gravity = 9.80665 # m/s2
|
|
68
|
+
G_grav_constant = 6.67430e-11 # m3/kgs
|
|
69
|
+
speed_of_light = 299792458 # m/s
|
|
70
|
+
u_planck = 6.62607015e-34 # Js
|
|
71
|
+
R_univ_gas = 8.314462618 # J/molkg
|
|
72
|
+
mass_electron = 9.1093837015e-31 # kg
|
|
73
|
+
mass_proton = 1.67262192369e-27
|
|
74
|
+
mass_neutron = 1.67492749804e-27
|
|
75
|
+
r_electron = 2.8179403262e-15
|
|
76
|
+
Kboltzman = 1.380649e-23 # J⋅K−1
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""define a collection of components that will propigate to its parents dataframe
|
|
2
|
+
|
|
3
|
+
When `wide` is set each component's references are reported to the system's table, otherwise only one component's references are reported, however the system will iterate over the components by calling `system.iterable_components`
|
|
4
|
+
|
|
5
|
+
Define a Iterable Component slot in a system by calling `Slot.define_iterable(...,wide=True/False)`
|
|
6
|
+
|
|
7
|
+
CostModel isonly supported in wide mode at this time.
|
|
8
|
+
|
|
9
|
+
Types:
|
|
10
|
+
1. ComponentList, ordered by index
|
|
11
|
+
2. ComponentDict, ordered by key
|
|
12
|
+
3. ComponentGraph, ?#TODO:
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from collections import UserDict, UserList
|
|
16
|
+
from engforge.components import Component
|
|
17
|
+
from engforge.attr_slots import Slot
|
|
18
|
+
from engforge.configuration import forge
|
|
19
|
+
from engforge.typing import *
|
|
20
|
+
from engforge.system_reference import Ref, system_property
|
|
21
|
+
from engforge.properties import *
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
import attrs
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check_comp_type(instance, attr, value):
|
|
28
|
+
"""ensures the input component type is a Component"""
|
|
29
|
+
from engforge.eng.costs import CostModel
|
|
30
|
+
|
|
31
|
+
if not instance.wide and isinstance(value, type) and issubclass(value, CostModel):
|
|
32
|
+
raise TypeError(f"Cost Mixin Not Supported As Iter Type! {value}")
|
|
33
|
+
|
|
34
|
+
if isinstance(value, type) and issubclass(value, Component):
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
raise TypeError(f"Not A Component Class! {value}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class iter_tkn:
|
|
41
|
+
"""ambigious type to keep track of iterable position form system reference"""
|
|
42
|
+
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@forge
|
|
47
|
+
class ComponentIter(Component):
|
|
48
|
+
"""Iterable components are designed to eval a large selection of components either one-by-one or all at once at the system level depending on if `wide` property is set."""
|
|
49
|
+
|
|
50
|
+
_ref_cache: dict = None
|
|
51
|
+
_item_refs: dict = None
|
|
52
|
+
|
|
53
|
+
wide: bool = True
|
|
54
|
+
|
|
55
|
+
# what holds the components
|
|
56
|
+
data: iter
|
|
57
|
+
|
|
58
|
+
# current item keu, non table type, this triggers `anything_changed` in `system._iterate_component()`
|
|
59
|
+
current_item: iter_tkn = attr.ib(factory=lambda: None, hash=False, eq=False)
|
|
60
|
+
_first_item_key: iter_tkn
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def current(self):
|
|
64
|
+
"""returns all data in wide format and the active key in _current_item"""
|
|
65
|
+
if self.wide:
|
|
66
|
+
return self.data
|
|
67
|
+
else:
|
|
68
|
+
if self.current_item is None:
|
|
69
|
+
if not hasattr(self, "_first_item_key"):
|
|
70
|
+
next(self._item_gen())
|
|
71
|
+
return self.data[self._first_item_key]
|
|
72
|
+
return self.data[self.current_item]
|
|
73
|
+
|
|
74
|
+
def _item_gen(self):
|
|
75
|
+
for key, item in self.data.items():
|
|
76
|
+
if not hasattr(self, "_first_item_key"):
|
|
77
|
+
self._first_item_key = key
|
|
78
|
+
yield key, item
|
|
79
|
+
|
|
80
|
+
def __setitem__(self, key, value):
|
|
81
|
+
assert isinstance(
|
|
82
|
+
value, self.component_type
|
|
83
|
+
), f"{value} is not of type: {self.component_type}"
|
|
84
|
+
super().__setitem__(key, value)
|
|
85
|
+
self.reset()
|
|
86
|
+
|
|
87
|
+
def reset(self):
|
|
88
|
+
# reset reference cache
|
|
89
|
+
self._prv_internal_references = None
|
|
90
|
+
self._item_refs = None
|
|
91
|
+
self.current_item = None
|
|
92
|
+
|
|
93
|
+
def _item_key(self, itkey, item):
|
|
94
|
+
"""override this to customize data access to self.data or other container name"""
|
|
95
|
+
return itkey
|
|
96
|
+
|
|
97
|
+
# TODO: way to update internal references in problem, do on update()?
|
|
98
|
+
@property
|
|
99
|
+
def _internal_references(self) -> dict:
|
|
100
|
+
"""considers wide format to return active references"""
|
|
101
|
+
if self.wide:
|
|
102
|
+
return self._prv_internal_references
|
|
103
|
+
else:
|
|
104
|
+
if self.current_item is None:
|
|
105
|
+
return self._item_refs[self._first_item_key]
|
|
106
|
+
return self._item_refs[self.current_item]
|
|
107
|
+
|
|
108
|
+
def comp_references(self, **kw):
|
|
109
|
+
"""Returns this components global references"""
|
|
110
|
+
out = {}
|
|
111
|
+
out["attributes"] = at = {}
|
|
112
|
+
out["properties"] = pr = {}
|
|
113
|
+
|
|
114
|
+
for key in self.system_properties_classdef():
|
|
115
|
+
pr[key] = Ref(self, key, True, False)
|
|
116
|
+
|
|
117
|
+
for key in self.input_fields():
|
|
118
|
+
at[key] = Ref(self, key, False, True)
|
|
119
|
+
|
|
120
|
+
return out
|
|
121
|
+
|
|
122
|
+
def internal_references(self, recache=False, numeric_only=False):
|
|
123
|
+
"""lists the this_name.comp_key.<attr/prop key>: Ref format to override data_dict"""
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
recache == False
|
|
127
|
+
and hasattr(self, "_prv_internal_references")
|
|
128
|
+
and self._prv_internal_references
|
|
129
|
+
):
|
|
130
|
+
return self._internal_references
|
|
131
|
+
|
|
132
|
+
keeprefcopy = lambda d: {k: {**c} for k, c in d.items()}
|
|
133
|
+
|
|
134
|
+
out = keeprefcopy(self.comp_references())
|
|
135
|
+
at = out["attributes"] # = at = {}
|
|
136
|
+
pr = out["properties"] # = pr = {}
|
|
137
|
+
_item_refs = {}
|
|
138
|
+
|
|
139
|
+
for itkey, item in self._item_gen():
|
|
140
|
+
it_base_key = self._item_key(itkey, item)
|
|
141
|
+
|
|
142
|
+
_item_refs[itkey] = ir = keeprefcopy(self.comp_references())
|
|
143
|
+
atr = ir["attributes"] # = atr = {}
|
|
144
|
+
prr = ir["properties"] # = prr = {}
|
|
145
|
+
|
|
146
|
+
# set property refs
|
|
147
|
+
for key in item.system_properties_classdef():
|
|
148
|
+
k = f"{it_base_key}.{key}"
|
|
149
|
+
rc = Ref(item, key, True, False)
|
|
150
|
+
pr[k] = rc
|
|
151
|
+
prr[key] = rc
|
|
152
|
+
|
|
153
|
+
# set attr refs
|
|
154
|
+
if numeric_only:
|
|
155
|
+
for key in item.numeric_fields():
|
|
156
|
+
k = f"{it_base_key}.{key}"
|
|
157
|
+
ri = Ref(item, key, False, True)
|
|
158
|
+
at[k] = ri
|
|
159
|
+
atr[key] = ri
|
|
160
|
+
|
|
161
|
+
else:
|
|
162
|
+
for key in item.input_fields():
|
|
163
|
+
k = f"{it_base_key}.{key}"
|
|
164
|
+
ri = Ref(item, key, False, True)
|
|
165
|
+
at[k] = ri
|
|
166
|
+
atr[key] = ri
|
|
167
|
+
|
|
168
|
+
# cache the references
|
|
169
|
+
self._prv_internal_references = out
|
|
170
|
+
self._item_refs = _item_refs
|
|
171
|
+
return self._internal_references
|
|
172
|
+
|
|
173
|
+
def __hash__(self):
|
|
174
|
+
return hash(id(self))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@forge
|
|
178
|
+
class ComponentDict(ComponentIter, UserDict):
|
|
179
|
+
"""Stores components by name, and allows tabulation of them"""
|
|
180
|
+
|
|
181
|
+
component_type: type = attrs.field(validator=check_comp_type)
|
|
182
|
+
|
|
183
|
+
# tabulate_depth:int = attrs.field(default=1) #TODO: impement this
|
|
184
|
+
|
|
185
|
+
_ref_cache: dict = None
|
|
186
|
+
|
|
187
|
+
# Dict Setup
|
|
188
|
+
def __on_init__(self):
|
|
189
|
+
UserDict.__init__(self)
|
|
190
|
+
|
|
191
|
+
def __str__(self):
|
|
192
|
+
return f"{self.__class__.__name__}[{len(self.data)}]"
|
|
193
|
+
|
|
194
|
+
def __repr__(self) -> str:
|
|
195
|
+
return str(self)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@forge
|
|
199
|
+
class ComponentIterator(ComponentIter, UserList):
|
|
200
|
+
"""Stores components by name, and allows tabulation of them"""
|
|
201
|
+
|
|
202
|
+
component_type: type = attrs.field(validator=check_comp_type)
|
|
203
|
+
|
|
204
|
+
# Dict Setup
|
|
205
|
+
def __on_init__(self):
|
|
206
|
+
UserList.__init__(self)
|
|
207
|
+
|
|
208
|
+
def _item_gen(self):
|
|
209
|
+
for i, item in enumerate(self.data):
|
|
210
|
+
if not hasattr(self, "_first_item_key"):
|
|
211
|
+
self._first_item_key = i
|
|
212
|
+
yield i, item
|
|
213
|
+
|
|
214
|
+
def _item_key(self, itkey, item):
|
|
215
|
+
return f"{self.component_type.__name__.lower()}.{itkey}"
|
|
216
|
+
|
|
217
|
+
# def __setitem__(self, key, value):
|
|
218
|
+
# assert isinstance(value,self.component_type)
|
|
219
|
+
# super().__setitem__(key, value)
|
|
220
|
+
# #reset reference cache
|
|
221
|
+
# self._ref_cache = None
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# #Tabulation Override
|
|
225
|
+
# @property
|
|
226
|
+
# def internal_references(self):
|
|
227
|
+
# """lists the this_name.comp_key.<attr/prop key>: Ref format to override data_dict"""
|
|
228
|
+
#
|
|
229
|
+
# if self._ref_cache:
|
|
230
|
+
# return self._ref_cache
|
|
231
|
+
#
|
|
232
|
+
# out = {}
|
|
233
|
+
# out["attributes"] = at = {}
|
|
234
|
+
# out["properties"] = pr = {}
|
|
235
|
+
#
|
|
236
|
+
# for itkey,item in self._item_gen():
|
|
237
|
+
# it_base_key = f'{itkey}'
|
|
238
|
+
#
|
|
239
|
+
# for key in item.system_properties_classdef():
|
|
240
|
+
# k = f'{it_base_key}.{key}'
|
|
241
|
+
# pr[k] = Ref(item, key)
|
|
242
|
+
#
|
|
243
|
+
# for key in item.input_fields():
|
|
244
|
+
# k = f'{it_base_key}.{key}'
|
|
245
|
+
# at[k] = Ref(item, key, False)
|
|
246
|
+
#
|
|
247
|
+
# #cache the references
|
|
248
|
+
# self._ref_cache = out
|
|
249
|
+
# return out
|
|
250
|
+
|
|
251
|
+
# #Tabulation Override
|
|
252
|
+
# @property
|
|
253
|
+
# def internal_references(self):
|
|
254
|
+
# """lists the this_name.comp_key.<attr/prop key>: Ref format to override data_dict"""
|
|
255
|
+
#
|
|
256
|
+
# if self._ref_cache:
|
|
257
|
+
# return self._ref_cache
|
|
258
|
+
#
|
|
259
|
+
# out = {}
|
|
260
|
+
# out["attributes"] = at = {}
|
|
261
|
+
# out["properties"] = pr = {}
|
|
262
|
+
#
|
|
263
|
+
# for it,item in self._item_gen():
|
|
264
|
+
# it_base_key = f'{self.component_type.__name__.lower()}.{it}'
|
|
265
|
+
#
|
|
266
|
+
# for key in item.system_properties_classdef():
|
|
267
|
+
# k = f'{it_base_key}.{key}'
|
|
268
|
+
# pr[k] = Ref(item, key)
|
|
269
|
+
#
|
|
270
|
+
# for key in item.input_fields():
|
|
271
|
+
# k = f'{it_base_key}.{key}'
|
|
272
|
+
# at[k] = Ref(item, key, False)
|
|
273
|
+
#
|
|
274
|
+
# #cache the references
|
|
275
|
+
# self._ref_cache = out
|
|
276
|
+
# return out
|