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/attr_signals.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""This module defines the slot attrs attribute to define the update behavior of a component or between components in an analysis"""
|
|
2
|
+
|
|
3
|
+
"""Signals define data flow in the solver system. These updates can happen before or after a solver execution as defined in SolverMixin
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import attrs, uuid
|
|
9
|
+
from engforge.attributes import ATTR_BASE, AttributeInstance, DEFAULT_COMBO
|
|
10
|
+
from engforge.attr_slots import SLOT_TYPES
|
|
11
|
+
|
|
12
|
+
VALID_MODES = ["pre", "post", "both"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SignalInstance(AttributeInstance):
|
|
16
|
+
"""A decoupled signal instance to perform operations on a system instance"""
|
|
17
|
+
|
|
18
|
+
system: "System"
|
|
19
|
+
signal: "Signal"
|
|
20
|
+
|
|
21
|
+
# compiled info
|
|
22
|
+
target: "Ref"
|
|
23
|
+
source: "Ref"
|
|
24
|
+
class_attr: "Signal"
|
|
25
|
+
|
|
26
|
+
def __init__(self, signal, system) -> None:
|
|
27
|
+
self.class_attr = self.signal = signal
|
|
28
|
+
self.system = system
|
|
29
|
+
self.compile()
|
|
30
|
+
|
|
31
|
+
def compile(self, **kwargs):
|
|
32
|
+
self.source = self.system.locate_ref(self.signal.source)
|
|
33
|
+
self.target = self.system.locate_ref(self.signal.target)
|
|
34
|
+
self.system.debug(f"SIGNAL| setting {self.target} with {self.source}")
|
|
35
|
+
|
|
36
|
+
def apply(self):
|
|
37
|
+
"""sets `target` from `source`"""
|
|
38
|
+
val = self.source.value()
|
|
39
|
+
if self.system.log_level < 10:
|
|
40
|
+
self.system.msg(f"Signal| applying {self.source}|{val} to {self.target}")
|
|
41
|
+
self.target.set_value(val)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def mode(self) -> str:
|
|
45
|
+
return self.signal.mode
|
|
46
|
+
|
|
47
|
+
def as_ref_dict(self) -> dict:
|
|
48
|
+
return dict(
|
|
49
|
+
target=self.target,
|
|
50
|
+
source=self.source,
|
|
51
|
+
signal=self,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def get_alias(self, path):
|
|
55
|
+
return path.split(".")[-1]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Signal(ATTR_BASE):
|
|
59
|
+
"""A base class that handles initalization in the attrs meta class scheme by ultimately createing a SignalInstance"""
|
|
60
|
+
|
|
61
|
+
name: str
|
|
62
|
+
mode: str
|
|
63
|
+
target: str
|
|
64
|
+
source: str
|
|
65
|
+
config_cls: "System"
|
|
66
|
+
attr_prefix = "SIGNAL"
|
|
67
|
+
instance_class = SignalInstance
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def define(cls, target: str, source: str, mode="pre", **kw):
|
|
71
|
+
"""taking a component or system class as possible input valid input is later validated as an instance of that class or subclass"""
|
|
72
|
+
assert mode in VALID_MODES, f"invalid mode: {mode}"
|
|
73
|
+
|
|
74
|
+
active = kw.get("active", True)
|
|
75
|
+
combo_dflt = "default,signals"
|
|
76
|
+
combos = kw.get("combos", None)
|
|
77
|
+
|
|
78
|
+
# Create A New Signals Class
|
|
79
|
+
new_name = f"Signal_{mode}_{source}_to_{target}".replace(".", "_")
|
|
80
|
+
new_name = new_name + "_" + str(uuid.uuid4()).replace("-", "")[0:16]
|
|
81
|
+
new_dict = dict(
|
|
82
|
+
name=new_name,
|
|
83
|
+
mode=mode,
|
|
84
|
+
target=target,
|
|
85
|
+
source=source,
|
|
86
|
+
active=active,
|
|
87
|
+
combos=cls.process_combos(combos, combo_dflt, combo_dflt),
|
|
88
|
+
default_options=cls.default_options.copy(),
|
|
89
|
+
)
|
|
90
|
+
new_slot = type(new_name, (Signal,), new_dict)
|
|
91
|
+
new_slot.default_options["default"] = new_slot.make_factory()
|
|
92
|
+
new_slot.default_options["validator"] = new_slot.configure_instance
|
|
93
|
+
new_sig = cls._setup_cls(new_name, new_dict)
|
|
94
|
+
return new_sig
|
|
95
|
+
|
|
96
|
+
# FIXME: move to
|
|
97
|
+
@classmethod
|
|
98
|
+
def class_validate(cls, instance, **kwargs):
|
|
99
|
+
from engforge.properties import system_property
|
|
100
|
+
|
|
101
|
+
system = cls.config_cls
|
|
102
|
+
|
|
103
|
+
var_type = system.locate(cls.target)
|
|
104
|
+
if var_type is None:
|
|
105
|
+
raise Exception(f"target not found: {cls.target}")
|
|
106
|
+
assert isinstance(
|
|
107
|
+
var_type, attrs.Attribute
|
|
108
|
+
), f"bad var {cls.target} not attribute: {var_type}"
|
|
109
|
+
|
|
110
|
+
driv_type = system.locate(cls.source)
|
|
111
|
+
if driv_type is None:
|
|
112
|
+
raise Exception(f"source not found: {cls.source}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Support Previous API
|
|
116
|
+
Signal = Signal
|
engforge/attr_slots.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""This module defines the slot attrs attribute to ensure the type of component added is correct and to define behavior,defaults and argument passing behavio"""
|
|
2
|
+
|
|
3
|
+
import attrs
|
|
4
|
+
import uuid
|
|
5
|
+
from engforge.attributes import ATTR_BASE, AttributeInstance
|
|
6
|
+
import typing as typ
|
|
7
|
+
from engforge.logging import LoggingMixin
|
|
8
|
+
|
|
9
|
+
SLOT_TYPES = typ.Union["Component", "System"]
|
|
10
|
+
ITERATOR = typ.Union["ComponentIterator"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SlotLog(LoggingMixin):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
log = SlotLog()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Slot(ATTR_BASE):
|
|
21
|
+
"""Slot defines a way to accept different components or systems in a system"""
|
|
22
|
+
|
|
23
|
+
# These are added on System signals_slots_handler aka attrs field_transformer
|
|
24
|
+
name: str
|
|
25
|
+
accepted: SLOT_TYPES
|
|
26
|
+
config_cls: "System"
|
|
27
|
+
attr_prefix = "Slot"
|
|
28
|
+
none_ok: bool
|
|
29
|
+
instance_class = None # add the component or system on define
|
|
30
|
+
|
|
31
|
+
dflt_kw: dict = None # a dictionary of input in factory for custom inits
|
|
32
|
+
default_ok = True # otherwise accept class with defaults
|
|
33
|
+
|
|
34
|
+
is_iter: bool
|
|
35
|
+
wide: bool # only for component iterator
|
|
36
|
+
# default_options = ATTR_BASE.default_options.copy()
|
|
37
|
+
default_options = dict(
|
|
38
|
+
repr=True,
|
|
39
|
+
validator=None,
|
|
40
|
+
cmp=None,
|
|
41
|
+
hash=None,
|
|
42
|
+
init=True,
|
|
43
|
+
metadata=None,
|
|
44
|
+
converter=None,
|
|
45
|
+
kw_only=True,
|
|
46
|
+
eq=None,
|
|
47
|
+
order=None,
|
|
48
|
+
on_setattr=None,
|
|
49
|
+
inherited=False,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def define(
|
|
54
|
+
cls,
|
|
55
|
+
*component_or_systems: SLOT_TYPES,
|
|
56
|
+
none_ok=False,
|
|
57
|
+
default_ok=True,
|
|
58
|
+
dflt_kw: dict = None,
|
|
59
|
+
):
|
|
60
|
+
"""taking a component or system class as possible input valid input is later validated as an instance of that class or subclass
|
|
61
|
+
|
|
62
|
+
:param none_ok: will allow no component on that item, oterwise will fail
|
|
63
|
+
:param default_ok: will create the slot class with no input if true
|
|
64
|
+
:param dflt_kw: a dictionary of input in factory for custom inits overrides defaults_ok
|
|
65
|
+
#TODO: add default_args,default_kwargs
|
|
66
|
+
"""
|
|
67
|
+
from engforge.components import Component
|
|
68
|
+
from engforge.component_collections import ComponentIter
|
|
69
|
+
from engforge.system import System
|
|
70
|
+
from engforge.eng.costs import CostModel
|
|
71
|
+
|
|
72
|
+
# Format THe Accepted Component Types
|
|
73
|
+
assert (
|
|
74
|
+
len(component_or_systems) == 1
|
|
75
|
+
), "only one slot allowed, try making a subclass"
|
|
76
|
+
assert not any(
|
|
77
|
+
[issubclass(c, ComponentIter) for c in component_or_systems]
|
|
78
|
+
), f"`ComponentIter` slot should be defined with `define_iterator` "
|
|
79
|
+
assert all(
|
|
80
|
+
[
|
|
81
|
+
issubclass(c, Component) or issubclass(c, System)
|
|
82
|
+
for c in component_or_systems
|
|
83
|
+
]
|
|
84
|
+
), "Not System Or Component Input"
|
|
85
|
+
|
|
86
|
+
### Cost models should always default to None
|
|
87
|
+
# FIXME: is this nessicary?
|
|
88
|
+
if any([issubclass(c, CostModel) for c in component_or_systems]):
|
|
89
|
+
default_ok = False
|
|
90
|
+
none_ok = True
|
|
91
|
+
|
|
92
|
+
new_dict = dict(
|
|
93
|
+
accepted=component_or_systems,
|
|
94
|
+
none_ok=none_ok,
|
|
95
|
+
default_ok=default_ok,
|
|
96
|
+
dflt_kw=dflt_kw,
|
|
97
|
+
instance_class=component_or_systems[0],
|
|
98
|
+
default_options=cls.default_options.copy(),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# slot
|
|
102
|
+
new_name = cls.attr_prefix + "_"
|
|
103
|
+
new_sig = cls._setup_cls(new_name, new_dict)
|
|
104
|
+
return new_sig
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def define_iterator(
|
|
108
|
+
cls,
|
|
109
|
+
*component_or_systems: ITERATOR,
|
|
110
|
+
none_ok: bool = False,
|
|
111
|
+
default_ok: bool = True,
|
|
112
|
+
wide: bool = True,
|
|
113
|
+
dflt_kw: dict = None,
|
|
114
|
+
):
|
|
115
|
+
"""taking a type of component iterator, defines an interface that can be 'wide' where all items are executed in the same row on `System.run()`.
|
|
116
|
+
|
|
117
|
+
Conversely if `wide` is false the system will loop over each item as if it was included in System.run(). Multiple ComponentIterators with wide=False will result in a `outer join` of the items.
|
|
118
|
+
|
|
119
|
+
:param none_ok: will allow no component on that item, otherwise will fail
|
|
120
|
+
:param default_ok: will create the slot class with no input if true
|
|
121
|
+
:param dflt_kw: a dictionary of input in factory for custom inits
|
|
122
|
+
:param wide: default is true, will determine if wide dataframe format, or outerproduct format when `System.run()` is called
|
|
123
|
+
"""
|
|
124
|
+
from engforge.components import Component
|
|
125
|
+
from engforge.component_collections import ComponentIter
|
|
126
|
+
from engforge.system import System
|
|
127
|
+
|
|
128
|
+
# Format THe Accepted Component Types
|
|
129
|
+
assert (
|
|
130
|
+
len(component_or_systems) == 1
|
|
131
|
+
), "only one slot allowed, try making a subclass"
|
|
132
|
+
assert all(
|
|
133
|
+
[issubclass(c, ComponentIter) for c in component_or_systems]
|
|
134
|
+
), "Not System Or Component Input"
|
|
135
|
+
|
|
136
|
+
# FIXME: come up with a better name :)
|
|
137
|
+
new_name = f"SlotITER_{str(uuid.uuid4()).replace('-','')[0:16]}"
|
|
138
|
+
new_slot = type(
|
|
139
|
+
new_name,
|
|
140
|
+
(Slot,),
|
|
141
|
+
dict(
|
|
142
|
+
# default=cls.make_factory(),
|
|
143
|
+
name=new_name,
|
|
144
|
+
accepted=component_or_systems,
|
|
145
|
+
none_ok=none_ok,
|
|
146
|
+
default_ok=default_ok,
|
|
147
|
+
dflt_kw=dflt_kw,
|
|
148
|
+
is_iter=True,
|
|
149
|
+
wide=wide,
|
|
150
|
+
instance_class=component_or_systems[0],
|
|
151
|
+
default_options=cls.default_options.copy(),
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
new_slot.default_options["validator"] = new_slot.configure_instance
|
|
155
|
+
new_slot.default_options["default"] = new_slot.make_factory()
|
|
156
|
+
return new_slot
|
|
157
|
+
|
|
158
|
+
# Create a validator function
|
|
159
|
+
@classmethod
|
|
160
|
+
def configure_instance(cls, instance, attribute, value):
|
|
161
|
+
from engforge.component_collections import ComponentIter
|
|
162
|
+
|
|
163
|
+
comp_cls = cls.config_cls
|
|
164
|
+
|
|
165
|
+
# apply wide behavior to componentiter instance
|
|
166
|
+
if isinstance(value, ComponentIter) and attribute.type.wide == False:
|
|
167
|
+
# print(f'validate {instance} {attribute} {value}')
|
|
168
|
+
value.wide = False
|
|
169
|
+
|
|
170
|
+
if value in cls.accepted or all([value is None, cls.none_ok]):
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
if any([isinstance(value, a) for a in cls.accepted]):
|
|
174
|
+
return True
|
|
175
|
+
|
|
176
|
+
if cls.default_ok and value is None:
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
raise ValueError(
|
|
180
|
+
f"{instance} value {value} is not an accepted type for slot: {comp_cls.__name__}.{cls.name}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def make_factory(cls, **kwargs):
|
|
185
|
+
accepted = cls.accepted
|
|
186
|
+
log.debug(f"slot instance factory: {cls} {accepted}, {kwargs}")
|
|
187
|
+
|
|
188
|
+
if isinstance(accepted, (tuple, list)) and len(accepted) > 0:
|
|
189
|
+
accepted = accepted[0]
|
|
190
|
+
|
|
191
|
+
log.debug(f"slot factory: {accepted},{cls.dflt_kw},{cls.default_ok}")
|
|
192
|
+
if cls.dflt_kw:
|
|
193
|
+
return attrs.Factory(cls.make_accepted(accepted, **cls.dflt_kw), False)
|
|
194
|
+
elif cls.default_ok:
|
|
195
|
+
return attrs.Factory(accepted, False)
|
|
196
|
+
else:
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def make_accepted(cls, accepted, **kw):
|
|
201
|
+
log.debug(f"making accepted: {cls.instance_class} {kw}")
|
|
202
|
+
return lambda: accepted(**kw)
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def handle_instance(cls, canidate):
|
|
206
|
+
"""a passthrough"""
|
|
207
|
+
return canidate
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# Support Previous SnakeCase
|
|
211
|
+
Slot = Slot
|