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
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import attr
|
|
2
|
+
|
|
3
|
+
# from engforge.configuration import Configuration, forge
|
|
4
|
+
from engforge.components import Component, system_property, forge
|
|
5
|
+
|
|
6
|
+
from engforge.eng.fluid_material import Water, Air, Steam
|
|
7
|
+
from CoolProp.CoolProp import PropsSI
|
|
8
|
+
import CoolProp.CoolProp as CP
|
|
9
|
+
import numpy
|
|
10
|
+
from numpy import vectorize
|
|
11
|
+
|
|
12
|
+
STD_TEMP = 273 + 20
|
|
13
|
+
STD_PRESSURE = 1.01325e5
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Thermodynamics
|
|
17
|
+
@vectorize
|
|
18
|
+
def heat_of_vaporization(P, material="Water", measure="Hmolar"):
|
|
19
|
+
"""Returns heat of vaporization for material
|
|
20
|
+
:param P: pressure in P
|
|
21
|
+
"""
|
|
22
|
+
assert measure.startswith("H") # Must be an enthalpy
|
|
23
|
+
if P > 2.2063e07: # Critical Pressure
|
|
24
|
+
return 0.0
|
|
25
|
+
H_L = PropsSI(measure, "P", P, "Q", 0, material)
|
|
26
|
+
H_V = PropsSI(measure, "P", P, "Q", 1, material)
|
|
27
|
+
return H_V - H_L
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@vectorize
|
|
31
|
+
def boiling_point(P, material="Water"):
|
|
32
|
+
"""Returns the boiling point in `K` for the material"""
|
|
33
|
+
return PropsSI("T", "P", P, "Q", 1, material)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Heat Exchanger
|
|
37
|
+
def dp_he_entrance(sigma, G, rho):
|
|
38
|
+
"""Heat Exchanger Entrance Pressure Loss
|
|
39
|
+
:param sigma: contraction-ratio - ratio of minimum flow area to frontal area
|
|
40
|
+
:param G: mass flux of fluid
|
|
41
|
+
:param rho: density of fluid
|
|
42
|
+
"""
|
|
43
|
+
Kc = 0.42 * (1.0 - sigma**2.0) ** 2.0
|
|
44
|
+
return (1.0 - sigma**2.0 + Kc) * (G**2.0 / rho) / 2.0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def dp_he_exit(sigma, G, rho):
|
|
48
|
+
"""Heat Exchanger Exit Pressure Loss
|
|
49
|
+
:param sigma: contraction-ratio - ratio of minimum flow area to frontal area
|
|
50
|
+
:param G: mass flux of fluid
|
|
51
|
+
:param rho: density of fluid
|
|
52
|
+
"""
|
|
53
|
+
Ke = (1.0 - sigma) ** 2.0
|
|
54
|
+
return (1.0 - sigma**2.0 + Ke) * (G**2.0 / rho) / 2.0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def dp_he_core(G, f, L, rho, Dh):
|
|
58
|
+
"""Losses due to friction
|
|
59
|
+
:param f: fanning friction factor
|
|
60
|
+
:param G: mass flux (massflow / Area)
|
|
61
|
+
:param L: length of heat exchanger
|
|
62
|
+
:param rho: intermediate density
|
|
63
|
+
:param Dh: diameter of heat exchanger
|
|
64
|
+
"""
|
|
65
|
+
top = 4 * f * L * G**2.0
|
|
66
|
+
btm = Dh * 2 * rho
|
|
67
|
+
dp_friciton = top / btm
|
|
68
|
+
return dp_friciton
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def dp_he_gas_losses(G, rhoe, rhoi):
|
|
72
|
+
"""Measures the pressure loss or gain due to density changes in the HE
|
|
73
|
+
:param G: mass flux
|
|
74
|
+
:param rhoe: exit density
|
|
75
|
+
:param rhoi: entrance density
|
|
76
|
+
"""
|
|
77
|
+
dp_pressure = G**2.0 * ((1.0 / rhoe) - (1.0 / rhoi))
|
|
78
|
+
return dp_pressure
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def fanning_friction_factor(Re, method="turbulent"):
|
|
82
|
+
if method == "turbulent":
|
|
83
|
+
if Re <= 5e4:
|
|
84
|
+
return 0.0791 / (Re**0.25)
|
|
85
|
+
return 0.0014 + 0.125 / (Re**0.32)
|
|
86
|
+
elif method == "laminar":
|
|
87
|
+
return 16.0 / Re
|
|
88
|
+
else: # Default to turbulent
|
|
89
|
+
return fanning_friction_factor(Re, method="turbulent")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Simple Elements
|
|
93
|
+
@forge
|
|
94
|
+
class SimpleHeatExchanger(Component):
|
|
95
|
+
Thi = attr.ib()
|
|
96
|
+
mdot_h = attr.ib()
|
|
97
|
+
Cp_h = attr.ib()
|
|
98
|
+
|
|
99
|
+
Tci = attr.ib()
|
|
100
|
+
mdot_c = attr.ib()
|
|
101
|
+
Cp_c = attr.ib()
|
|
102
|
+
|
|
103
|
+
efficiency = attr.ib(default=0.8)
|
|
104
|
+
name = attr.ib(default="HeatExchanger")
|
|
105
|
+
|
|
106
|
+
@system_property
|
|
107
|
+
def CmatH(self) -> float:
|
|
108
|
+
return self.Cp_h * self.mdot_h
|
|
109
|
+
|
|
110
|
+
@system_property
|
|
111
|
+
def CmatC(self) -> float:
|
|
112
|
+
return self.Cp_c * self.mdot_c
|
|
113
|
+
|
|
114
|
+
@system_property
|
|
115
|
+
def Tout_ideal(self) -> float:
|
|
116
|
+
numerator = self.Thi * self.CmatH + self.Tci * self.CmatC
|
|
117
|
+
denominator = self.CmatC + self.CmatH
|
|
118
|
+
return numerator / denominator
|
|
119
|
+
|
|
120
|
+
@system_property
|
|
121
|
+
def Qdot_ideal(self) -> float:
|
|
122
|
+
"""Use Tout ideal to determine the heat flow should be the same for both"""
|
|
123
|
+
v1 = self.CmatH * (self.Thi - self.Tout_ideal)
|
|
124
|
+
v2 = self.CmatC * (self.Tout_ideal - self.Tci)
|
|
125
|
+
if abs((v2 - v1) / float(v1)) >= 0.1:
|
|
126
|
+
self.warning("Qdot_ideal not matching")
|
|
127
|
+
return (v1 + v2) / 2.0
|
|
128
|
+
|
|
129
|
+
@system_property
|
|
130
|
+
def Qdot(self) -> float:
|
|
131
|
+
return self.Qdot_ideal * self.efficiency
|
|
132
|
+
|
|
133
|
+
@system_property
|
|
134
|
+
def Th_out(self) -> float:
|
|
135
|
+
return self.Thi - self.Qdot / self.CmatH
|
|
136
|
+
|
|
137
|
+
@system_property
|
|
138
|
+
def Tc_out(self) -> float:
|
|
139
|
+
return self.Tci + self.Qdot / self.CmatC
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Compression
|
|
143
|
+
@forge
|
|
144
|
+
class SimpleCompressor(Component):
|
|
145
|
+
pressure_ratio = attr.ib()
|
|
146
|
+
|
|
147
|
+
Tin = attr.ib()
|
|
148
|
+
mdot = attr.ib()
|
|
149
|
+
|
|
150
|
+
Cp = attr.ib()
|
|
151
|
+
gamma = attr.ib(default=1.4)
|
|
152
|
+
|
|
153
|
+
efficiency = attr.ib(default=0.75)
|
|
154
|
+
name = attr.ib(default="Compressor")
|
|
155
|
+
|
|
156
|
+
@system_property
|
|
157
|
+
def temperature_ratio(self) -> float:
|
|
158
|
+
return (
|
|
159
|
+
self.pressure_ratio ** ((self.gamma - 1.0) / self.gamma) - 1.0
|
|
160
|
+
) / self.efficiency
|
|
161
|
+
|
|
162
|
+
@system_property
|
|
163
|
+
def Tout(self) -> float:
|
|
164
|
+
return self.temperature_ratio * self.Tin
|
|
165
|
+
|
|
166
|
+
@system_property
|
|
167
|
+
def power_input(self) -> float:
|
|
168
|
+
return self.Cp * self.mdot * (self.Tout - self.Tin)
|
|
169
|
+
|
|
170
|
+
def pressure_out(self, pressure_in):
|
|
171
|
+
return self.pressure_ratio * pressure_in
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@forge
|
|
175
|
+
class SimpleTurbine(Component):
|
|
176
|
+
Pout = attr.ib()
|
|
177
|
+
|
|
178
|
+
Pin = attr.ib()
|
|
179
|
+
Tin = attr.ib()
|
|
180
|
+
|
|
181
|
+
mdot = attr.ib()
|
|
182
|
+
|
|
183
|
+
Cp = attr.ib()
|
|
184
|
+
gamma = attr.ib(default=1.4)
|
|
185
|
+
|
|
186
|
+
efficiency = attr.ib(default=0.80)
|
|
187
|
+
name = attr.ib(default="Turbine")
|
|
188
|
+
|
|
189
|
+
@system_property
|
|
190
|
+
def pressure_ratio(self) -> float:
|
|
191
|
+
return self.Pin / self.Pout
|
|
192
|
+
|
|
193
|
+
@system_property
|
|
194
|
+
def Tout(self) -> float:
|
|
195
|
+
return self.Tin * (
|
|
196
|
+
1
|
|
197
|
+
- self.efficiency
|
|
198
|
+
* (1.0 - (1 / self.pressure_ratio) ** ((self.gamma - 1.0) / self.gamma))
|
|
199
|
+
)
|
|
200
|
+
# return self.Tin * self.pressure_ratio**((self.gamma-1.0)/self.gamma)/ self.efficiency
|
|
201
|
+
|
|
202
|
+
@system_property
|
|
203
|
+
def power_output(self) -> float:
|
|
204
|
+
"""calculates power output base on temp diff (where eff applied)"""
|
|
205
|
+
return self.Cp * self.mdot * (self.Tin - self.Tout)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# Compression
|
|
209
|
+
@forge
|
|
210
|
+
class SimplePump(Component):
|
|
211
|
+
MFin = attr.field() # kg/s
|
|
212
|
+
pressure_ratio = attr.field() # nd
|
|
213
|
+
Tin = attr.field(default=STD_TEMP) # k
|
|
214
|
+
Pin = attr.field(default=STD_PRESSURE) # c
|
|
215
|
+
|
|
216
|
+
efficiency = attr.field(default=0.75)
|
|
217
|
+
fluid = attr.field(factory=Water)
|
|
218
|
+
name = attr.field(default="pump")
|
|
219
|
+
|
|
220
|
+
def __on_init__(self):
|
|
221
|
+
self.eval()
|
|
222
|
+
|
|
223
|
+
def eval(self):
|
|
224
|
+
self.fluid.T = self.Tin
|
|
225
|
+
self.fluid.P = self.Pin
|
|
226
|
+
|
|
227
|
+
Tsat = self.fluid.Tsat
|
|
228
|
+
if self.Tin / Tsat >= 1:
|
|
229
|
+
self.warning("infeasible: pumping vapor!")
|
|
230
|
+
|
|
231
|
+
elif (self.Tin / self.fluid.Tsat) > 0.8:
|
|
232
|
+
self.warning("pump near saturated temp")
|
|
233
|
+
|
|
234
|
+
@system_property
|
|
235
|
+
def volumetric_flow(self) -> float:
|
|
236
|
+
return self.MFin / self.fluid.density
|
|
237
|
+
|
|
238
|
+
@system_property
|
|
239
|
+
def temperature_delta(self) -> float:
|
|
240
|
+
rho = self.fluid.density
|
|
241
|
+
v = self.MFin / rho
|
|
242
|
+
p = self.Pin
|
|
243
|
+
w = v * p / self.efficiency # work done
|
|
244
|
+
cp = self.fluid.specific_heat
|
|
245
|
+
dt = w * (1 - self.efficiency) / (rho * cp * v)
|
|
246
|
+
return dt
|
|
247
|
+
|
|
248
|
+
@system_property
|
|
249
|
+
def Pout(self) -> float:
|
|
250
|
+
return self.pressure_ratio * self.Pin
|
|
251
|
+
|
|
252
|
+
@system_property
|
|
253
|
+
def Tout(self) -> float:
|
|
254
|
+
return self.temperature_delta + self.Tin
|
|
255
|
+
|
|
256
|
+
@system_property
|
|
257
|
+
def pressure_delta(self) -> float:
|
|
258
|
+
return (self.pressure_ratio - 1) * self.Pin
|
|
259
|
+
|
|
260
|
+
@system_property
|
|
261
|
+
def power_input(self) -> float:
|
|
262
|
+
return self.volumetric_flow * self.pressure_delta / self.efficiency
|
|
263
|
+
|
|
264
|
+
@system_property
|
|
265
|
+
def cost(self) -> float:
|
|
266
|
+
pwr = self.power_input
|
|
267
|
+
c1 = numpy.log(pwr)
|
|
268
|
+
c2 = -0.03195 * pwr**2.0
|
|
269
|
+
c3 = 467.2 * pwr
|
|
270
|
+
c4 = 2.048e4
|
|
271
|
+
return c1 + c2 + c3 + c4
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
from engforge.attributes import ATTR_BASE, AttributeInstance
|
|
2
|
+
from engforge.attr_dynamics import Time
|
|
3
|
+
from engforge.attr_solver import Solver
|
|
4
|
+
from engforge.attr_signals import Signal
|
|
5
|
+
from engforge.attr_slots import Slot
|
|
6
|
+
from engforge.attr_plotting import Plot, Trace
|
|
7
|
+
from engforge.logging import LoggingMixin, log
|
|
8
|
+
from engforge.typing import *
|
|
9
|
+
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
import deepdiff
|
|
12
|
+
import typing
|
|
13
|
+
import datetime
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
import attr, attrs
|
|
17
|
+
|
|
18
|
+
from attrs import Attribute
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EngAttr(LoggingMixin):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
log = EngAttr()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_attributes_of(cls, subclass_of: type = None, exclude=False):
|
|
29
|
+
choose = issubclass
|
|
30
|
+
if exclude:
|
|
31
|
+
choose = lambda ty, type_set: not issubclass(ty, type_set)
|
|
32
|
+
|
|
33
|
+
if subclass_of is None:
|
|
34
|
+
subclass_of = ATTR_BASE
|
|
35
|
+
|
|
36
|
+
# This handles the attrs class before or after compilation
|
|
37
|
+
attrval = {}
|
|
38
|
+
if "__attrs_attrs__" in cls.__dict__: # Handle Attrs Class
|
|
39
|
+
for k, v in attrs.fields_dict(cls).items():
|
|
40
|
+
if isinstance(v.type, type) and choose(v.type, subclass_of):
|
|
41
|
+
attrval[k] = v
|
|
42
|
+
|
|
43
|
+
# else: # Handle Pre-Attrs Class
|
|
44
|
+
# FIXME: should this run first?
|
|
45
|
+
for k, v in cls.__dict__.items():
|
|
46
|
+
if isinstance(v, type) and choose(v, subclass_of):
|
|
47
|
+
attrval[k] = v
|
|
48
|
+
|
|
49
|
+
return attrval
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AttributedBaseMixin(LoggingMixin):
|
|
53
|
+
"""A mixin that adds the ability to configure all engforge.core attributes of a class"""
|
|
54
|
+
|
|
55
|
+
# Auto Configuration Methods
|
|
56
|
+
@classmethod
|
|
57
|
+
def collect_all_attributes(cls):
|
|
58
|
+
"""collects all the attributes for a system"""
|
|
59
|
+
out = {}
|
|
60
|
+
for base_class in ATTR_BASE.subclasses():
|
|
61
|
+
nm = base_class.__name__.lower()
|
|
62
|
+
out[nm] = base_class.collect_cls(cls)
|
|
63
|
+
return out
|
|
64
|
+
|
|
65
|
+
def collect_inst_attributes(self, **kw):
|
|
66
|
+
"""collects all the attributes for a system"""
|
|
67
|
+
out = {}
|
|
68
|
+
for base_class in ATTR_BASE.subclasses():
|
|
69
|
+
nm = base_class.__name__.lower()
|
|
70
|
+
out[nm] = base_class.collect_attr_inst(self, **kw)
|
|
71
|
+
return out
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def _get_init_attrs_data(cls, subclass_of: type, exclude=False, attr_type=False):
|
|
75
|
+
choose = issubclass
|
|
76
|
+
if exclude:
|
|
77
|
+
choose = lambda ty, type_set: not issubclass(ty, type_set)
|
|
78
|
+
|
|
79
|
+
attrval = {}
|
|
80
|
+
if "__attrs_attrs__" in cls.__dict__: # Handle Attrs Class
|
|
81
|
+
for k, v in attrs.fields_dict(cls).items():
|
|
82
|
+
if isinstance(v.type, type) and choose(v.type, subclass_of):
|
|
83
|
+
attrval[k] = v.type if attr_type else v
|
|
84
|
+
|
|
85
|
+
# else: # Handle Pre-Attrs Class
|
|
86
|
+
# FIXME: should this run first?
|
|
87
|
+
for k, v in cls.__dict__.items():
|
|
88
|
+
if isinstance(v, type) and choose(v, subclass_of):
|
|
89
|
+
attrval[k] = v
|
|
90
|
+
|
|
91
|
+
return attrval
|
|
92
|
+
|
|
93
|
+
# Attribute Methods
|
|
94
|
+
@property
|
|
95
|
+
def attrs_fields(self) -> set:
|
|
96
|
+
return set(attr.fields(self.__class__))
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def _extract_type(cls, typ):
|
|
100
|
+
"""gathers valid types for an attribute.type"""
|
|
101
|
+
from engforge.attr_slots import Slot
|
|
102
|
+
from engforge.configuration import Configuration
|
|
103
|
+
|
|
104
|
+
if not isinstance(typ, type) or typ is None:
|
|
105
|
+
return list()
|
|
106
|
+
|
|
107
|
+
if issubclass(typ, Slot):
|
|
108
|
+
accept = typ.accepted
|
|
109
|
+
if isinstance(accept, (tuple, list)):
|
|
110
|
+
return list(accept)
|
|
111
|
+
return [accept]
|
|
112
|
+
|
|
113
|
+
elif issubclass(typ, Configuration):
|
|
114
|
+
return [typ]
|
|
115
|
+
|
|
116
|
+
elif issubclass(typ, TABLE_TYPES):
|
|
117
|
+
return [typ]
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def check_ref_slot_type(cls, sys_key: str) -> list:
|
|
121
|
+
"""recursively checks class slots for the key, and returns the slot type"""
|
|
122
|
+
|
|
123
|
+
from engforge.configuration import Configuration
|
|
124
|
+
|
|
125
|
+
slot_refs = cls.slot_refs()
|
|
126
|
+
if sys_key in slot_refs:
|
|
127
|
+
return slot_refs[sys_key]
|
|
128
|
+
|
|
129
|
+
slts = cls.input_attrs()
|
|
130
|
+
key_segs = sys_key.split(".")
|
|
131
|
+
out = []
|
|
132
|
+
# print(slts.keys(),sys_key)
|
|
133
|
+
if "." not in sys_key and sys_key not in slts:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
elif sys_key in slts:
|
|
137
|
+
# print(f'slt find {sys_key}')
|
|
138
|
+
return cls._extract_type(slts[sys_key].type)
|
|
139
|
+
else:
|
|
140
|
+
fst = key_segs[0]
|
|
141
|
+
rem = key_segs[1:]
|
|
142
|
+
if fst in slts:
|
|
143
|
+
sub_clss = cls._extract_type(slts[fst].type)
|
|
144
|
+
out = []
|
|
145
|
+
for acpt in sub_clss:
|
|
146
|
+
if isinstance(acpt, type) and issubclass(acpt, Configuration):
|
|
147
|
+
vals = acpt.check_ref_slot_type(".".join(rem))
|
|
148
|
+
# print(f'recursive find {acpt}.{rem} = {vals}')
|
|
149
|
+
if vals:
|
|
150
|
+
out.extend(vals)
|
|
151
|
+
|
|
152
|
+
elif isinstance(acpt, type):
|
|
153
|
+
out.append(acpt)
|
|
154
|
+
|
|
155
|
+
slot_refs[sys_key] = out
|
|
156
|
+
|
|
157
|
+
return out
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def slot_refs(cls, recache=False):
|
|
161
|
+
"""returns all slot references in this configuration"""
|
|
162
|
+
key = f"{cls.__name__}_prv_slot_sys_refs"
|
|
163
|
+
if recache == False and hasattr(cls, key):
|
|
164
|
+
return getattr(cls, key)
|
|
165
|
+
o = {}
|
|
166
|
+
setattr(cls, key, o)
|
|
167
|
+
return o
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def slots_attributes(cls, attr_type=False) -> typing.Dict[str, "Attribute"]:
|
|
171
|
+
"""Lists all slots attributes for class"""
|
|
172
|
+
return cls._get_init_attrs_data(Slot, attr_type=attr_type)
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def signals_attributes(cls) -> typing.Dict[str, "Attribute"]:
|
|
176
|
+
"""Lists all signals attributes for class"""
|
|
177
|
+
return cls._get_init_attrs_data(Signal)
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def solvers_attributes(cls) -> typing.Dict[str, "Attribute"]:
|
|
181
|
+
"""Lists all signals attributes for class"""
|
|
182
|
+
return cls._get_init_attrs_data(Solver)
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def transients_attributes(cls) -> typing.Dict[str, "Attribute"]:
|
|
186
|
+
"""Lists all signals attributes for class"""
|
|
187
|
+
return cls._get_init_attrs_data(Time)
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def trace_attributes(cls) -> typing.Dict[str, "Attribute"]:
|
|
191
|
+
"""Lists all trace attributes for class"""
|
|
192
|
+
return cls._get_init_attrs_data(Trace)
|
|
193
|
+
|
|
194
|
+
@classmethod
|
|
195
|
+
def plot_attributes(cls) -> typing.Dict[str, "Attribute"]:
|
|
196
|
+
"""Lists all plot attributes for class"""
|
|
197
|
+
return cls._get_init_attrs_data(Plot)
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def input_attrs(cls):
|
|
201
|
+
"""Lists all input attributes for class"""
|
|
202
|
+
return attr.fields_dict(cls)
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def input_fields(cls, add_ign_types: list = None):
|
|
206
|
+
"""no attr base types, no tuples, no lists, no dicts"""
|
|
207
|
+
ignore_types = [
|
|
208
|
+
ATTR_BASE,
|
|
209
|
+
# tuple,
|
|
210
|
+
# list,
|
|
211
|
+
# dict,
|
|
212
|
+
]
|
|
213
|
+
if add_ign_types:
|
|
214
|
+
ignore_types.extend(add_ign_types)
|
|
215
|
+
return cls._get_init_attrs_data(tuple(ignore_types), exclude=True)
|
|
216
|
+
|
|
217
|
+
@classmethod
|
|
218
|
+
def numeric_fields(cls):
|
|
219
|
+
"""no tuples,lists, dicts, strings, or attr base types"""
|
|
220
|
+
ignore_types = (
|
|
221
|
+
ATTR_BASE,
|
|
222
|
+
str,
|
|
223
|
+
tuple,
|
|
224
|
+
list,
|
|
225
|
+
dict,
|
|
226
|
+
)
|
|
227
|
+
typ = cls._get_init_attrs_data(ignore_types, exclude=True)
|
|
228
|
+
return {k: v for k, v in typ.items() if v.type in (int, float)}
|
|
229
|
+
|
|
230
|
+
@classmethod
|
|
231
|
+
def table_fields(cls):
|
|
232
|
+
"""the table attributes corresponding to"""
|
|
233
|
+
# TODO: add list/numpy fields with vector stats
|
|
234
|
+
keeps = (str, float, int)
|
|
235
|
+
typ = cls._get_init_attrs_data(keeps)
|
|
236
|
+
return {k: v for k, v in typ.items()}
|
|
237
|
+
|
|
238
|
+
# Dictonaries
|
|
239
|
+
@property
|
|
240
|
+
def as_dict(self):
|
|
241
|
+
"""returns values as they are in the class instance"""
|
|
242
|
+
from engforge.configuration import Configuration
|
|
243
|
+
|
|
244
|
+
inputs = self.input_attrs()
|
|
245
|
+
# TODO: add signals?
|
|
246
|
+
properties = getattr(self, "system_properties_classdef", None)
|
|
247
|
+
if properties:
|
|
248
|
+
inputs.update(properties())
|
|
249
|
+
|
|
250
|
+
o = {k: getattr(self, k, None) for k, v in inputs.items()}
|
|
251
|
+
return o
|
|
252
|
+
|
|
253
|
+
# TODO: refactor this, allowing a nesting return option for sub components, by default True (later to be reverted to False, as a breaking change). this messes up hashing and we can just the other object hash
|
|
254
|
+
@property
|
|
255
|
+
def input_as_dict(self):
|
|
256
|
+
"""returns values as they are in the class instance, but converts classes inputs to their input_as_dict"""
|
|
257
|
+
from engforge.configuration import Configuration
|
|
258
|
+
|
|
259
|
+
o = {k: getattr(self, k, None) for k in self.input_fields()}
|
|
260
|
+
o = {
|
|
261
|
+
k: v if not isinstance(v, Configuration) else v.input_as_dict
|
|
262
|
+
for k, v in o.items()
|
|
263
|
+
}
|
|
264
|
+
return o
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def table_row_dict(self):
|
|
268
|
+
"""returns values as they would be put in a table row from this instance ignoring any sub components"""
|
|
269
|
+
from engforge.configuration import Configuration
|
|
270
|
+
|
|
271
|
+
o = {k: getattr(self, k, None) for k in self.table_fields()}
|
|
272
|
+
return o
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def numeric_as_dict(self):
|
|
276
|
+
"""recursively gets internal components numeric_as_dict as well as its own numeric values"""
|
|
277
|
+
from engforge.configuration import Configuration
|
|
278
|
+
|
|
279
|
+
o = {k: getattr(self, k, None) for k in self.numeric_fields()}
|
|
280
|
+
o = {
|
|
281
|
+
k: v if not isinstance(v, Configuration) else v.numeric_as_dict
|
|
282
|
+
for k, v in o.items()
|
|
283
|
+
}
|
|
284
|
+
return o
|
|
285
|
+
|
|
286
|
+
# Hashes
|
|
287
|
+
# TODO: issue with logging sub-items
|
|
288
|
+
def hash(self, *args, **input_kw):
|
|
289
|
+
"""hash by parm or by input_kw, only input can be hashed by lookup as system properties can create a recursive loop and should be deterministic from input"""
|
|
290
|
+
d = {k: v for k, v in self.input_as_dict.items() if k in args}
|
|
291
|
+
d.update(input_kw) # override with input_kw
|
|
292
|
+
return d, deepdiff.DeepHash(
|
|
293
|
+
d, ignore_encoding_errors=True, significant_digits=6
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
def hash_with(self, **input_kw):
|
|
297
|
+
"""
|
|
298
|
+
Generates a hash for the object's dictionary representation, updated with additional keyword arguments.
|
|
299
|
+
Args:
|
|
300
|
+
**input_kw: Arbitrary keyword arguments to update the object's dictionary representation.
|
|
301
|
+
Returns:
|
|
302
|
+
The hash value of the updated dictionary.
|
|
303
|
+
Raises:
|
|
304
|
+
Any exceptions raised by deepdiff.DeepHash if hashing fails.
|
|
305
|
+
"""
|
|
306
|
+
d = self.as_dict
|
|
307
|
+
d.update(input_kw)
|
|
308
|
+
return deepdiff.DeepHash(d, ignore_encoding_errors=True)[d]
|
|
309
|
+
|
|
310
|
+
def hash_numeric_with(self, **input_kw):
|
|
311
|
+
d = self.numeric_as_dict
|
|
312
|
+
d.update(input_kw)
|
|
313
|
+
return deepdiff.DeepHash(d, ignore_encoding_errors=True)[d]
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def unique_hash(self):
|
|
317
|
+
d = self.as_dict
|
|
318
|
+
return deepdiff.DeepHash(d, ignore_encoding_errors=True)[d]
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def input_hash(self):
|
|
322
|
+
d = self.input_as_dict
|
|
323
|
+
return deepdiff.DeepHash(d, ignore_encoding_errors=True)[d]
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def table_hash(self):
|
|
327
|
+
d, hsh = self.hash(**self.table_row_dict)
|
|
328
|
+
return hsh[d]
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def numeric_hash(self):
|
|
332
|
+
d = self.numeric_as_dict
|
|
333
|
+
return deepdiff.DeepHash(d, ignore_encoding_errors=True)[d]
|
|
334
|
+
|
|
335
|
+
# Configuration Push/Pop methods
|
|
336
|
+
def setattrs(self, dict):
|
|
337
|
+
"""sets attributes from a dictionary"""
|
|
338
|
+
msg = f"invalid keys {set(dict.keys()) - set(self.input_attrs())}"
|
|
339
|
+
assert set(dict.keys()).issubset(set(self.input_attrs())), msg
|
|
340
|
+
for k, v in dict.items():
|
|
341
|
+
setattr(self, k, v)
|
|
342
|
+
|
|
343
|
+
@contextmanager
|
|
344
|
+
def difference(self, **kwargs):
|
|
345
|
+
"""a context manager that will allow you to dynamically change any information, then will change it back in a fail safe way.
|
|
346
|
+
|
|
347
|
+
with self.difference(name='new_name', value = new_value) as new_config:
|
|
348
|
+
#do stuff with config, ok to fail
|
|
349
|
+
|
|
350
|
+
you may not access any "private" variable that starts with an `_` as in _whatever
|
|
351
|
+
|
|
352
|
+
difference is useful for saving slight differences in configuration in conjunction with solve
|
|
353
|
+
you might create wrappers for eval, or implement a strategy pattern.
|
|
354
|
+
|
|
355
|
+
only attributes may be changed.
|
|
356
|
+
|
|
357
|
+
#TODO: allow recursive operation with sub comps or systems.
|
|
358
|
+
#TODO: make a full system copy so the system can be reverted later
|
|
359
|
+
"""
|
|
360
|
+
_temp_vars = {}
|
|
361
|
+
|
|
362
|
+
_temp_vars.update(
|
|
363
|
+
{
|
|
364
|
+
arg: getattr(self, arg)
|
|
365
|
+
for arg in kwargs.keys()
|
|
366
|
+
if hasattr(self, arg)
|
|
367
|
+
if not arg.startswith("_")
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
bad_vars = set.difference(set(kwargs.keys()), set(_temp_vars.keys()))
|
|
372
|
+
if bad_vars:
|
|
373
|
+
self.warning("Could Not Change {}".format(",".join(list(bad_vars))))
|
|
374
|
+
|
|
375
|
+
try: # Change Variables To Input
|
|
376
|
+
self.setattrs(kwargs)
|
|
377
|
+
yield self
|
|
378
|
+
finally:
|
|
379
|
+
rstdict = {k: _temp_vars[k] for k, v in kwargs.items()}
|
|
380
|
+
self.setattrs(rstdict)
|