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.
Files changed (69) hide show
  1. engforge/__init__.py +26 -0
  2. engforge/_testing_components.py +864 -0
  3. engforge/analysis.py +257 -0
  4. engforge/attr_dynamics.py +222 -0
  5. engforge/attr_plotting.py +811 -0
  6. engforge/attr_signals.py +116 -0
  7. engforge/attr_slots.py +211 -0
  8. engforge/attr_solver.py +443 -0
  9. engforge/attributes.py +326 -0
  10. engforge/common.py +76 -0
  11. engforge/component_collections.py +276 -0
  12. engforge/components.py +55 -0
  13. engforge/configuration.py +673 -0
  14. engforge/dataframe.py +185 -0
  15. engforge/datastores/__init__.py +116 -0
  16. engforge/datastores/data.py +519 -0
  17. engforge/datastores/gdocs.py +2650 -0
  18. engforge/datastores/reporting.py +935 -0
  19. engforge/datastores/secrets.py +549 -0
  20. engforge/dynamics.py +662 -0
  21. engforge/eng/__init__.py +0 -0
  22. engforge/eng/costs.py +1459 -0
  23. engforge/eng/fluid_material.py +279 -0
  24. engforge/eng/geometry.py +1060 -0
  25. engforge/eng/pipes.py +1211 -0
  26. engforge/eng/prediction.py +366 -0
  27. engforge/eng/solid_materials.py +445 -0
  28. engforge/eng/structure.py +2228 -0
  29. engforge/eng/structure_beams.py +821 -0
  30. engforge/eng/thermodynamics.py +271 -0
  31. engforge/engforge_attributes.py +380 -0
  32. engforge/env_var.py +214 -0
  33. engforge/locations.py +18 -0
  34. engforge/logging.py +290 -0
  35. engforge/patterns.py +449 -0
  36. engforge/problem_context.py +2251 -0
  37. engforge/properties.py +501 -0
  38. engforge/reporting.py +595 -0
  39. engforge/solveable.py +964 -0
  40. engforge/solver.py +316 -0
  41. engforge/solver_utils.py +726 -0
  42. engforge/system.py +143 -0
  43. engforge/system_reference.py +299 -0
  44. engforge/tabulation.py +288 -0
  45. engforge/test/__init__.py +2 -0
  46. engforge/test/_pre_test_structures.py +221 -0
  47. engforge/test/report_testing.py +158 -0
  48. engforge/test/test_airfilter.py +91 -0
  49. engforge/test/test_analysis.py +3 -0
  50. engforge/test/test_comp_iter.py +196 -0
  51. engforge/test/test_composition.py +139 -0
  52. engforge/test/test_costs.py +338 -0
  53. engforge/test/test_dynamics.py +109 -0
  54. engforge/test/test_dynamics_spaces.py +39 -0
  55. engforge/test/test_four_bar.py +97 -0
  56. engforge/test/test_modules.py +72 -0
  57. engforge/test/test_performance.py +93 -0
  58. engforge/test/test_pipes.py +49 -0
  59. engforge/test/test_problem.py +411 -0
  60. engforge/test/test_problem_deepscoping.py +68 -0
  61. engforge/test/test_slider_crank.py +51 -0
  62. engforge/test/test_solver.py +343 -0
  63. engforge/test/test_tabulation.py +233 -0
  64. engforge/typing.py +92 -0
  65. engforge-0.3.0.dist-info/METADATA +392 -0
  66. engforge-0.3.0.dist-info/RECORD +69 -0
  67. engforge-0.3.0.dist-info/WHEEL +5 -0
  68. engforge-0.3.0.dist-info/licenses/LICENSE +7 -0
  69. engforge-0.3.0.dist-info/top_level.txt +1 -0
engforge/tabulation.py ADDED
@@ -0,0 +1,288 @@
1
+ """Tabulation Module:
2
+
3
+ Incrementally records attrs input values and system_properties per save_data() call.
4
+
5
+ save_data() is called after item.eval() is called.
6
+ """
7
+
8
+ from contextlib import contextmanager
9
+ import attr
10
+
11
+ from engforge.common import inst_vectorize, chunks
12
+
13
+ # from engforge.configuration import Configuration, forge
14
+ from engforge.solveable import SolveableMixin
15
+ from engforge.logging import LoggingMixin
16
+ from engforge.dataframe import DataframeMixin
17
+ from engforge.typing import *
18
+ from engforge.properties import *
19
+ from typing import Callable
20
+
21
+ import numpy
22
+ import pandas
23
+ import os
24
+ import collections
25
+ import uuid
26
+
27
+
28
+ class TableLog(LoggingMixin):
29
+ pass
30
+
31
+
32
+ log = TableLog()
33
+
34
+ # SKIP_REF = ["run_id", "converged", "name", "index"]
35
+ SKIP_REF = ["name", "index"]
36
+
37
+
38
+ class TabulationMixin(SolveableMixin, DataframeMixin):
39
+ """In which we define a class that can enable tabulation"""
40
+
41
+ # Super Special Tabulating Index
42
+ # index = 0 # Not an attr on purpose, we want pandas to provide the index
43
+
44
+ # override per class:
45
+ _skip_table_vars: list = None
46
+ _skip_plot_vars: list
47
+
48
+ # Cached and private
49
+ # _table: dict = None
50
+ _anything_changed: bool = True
51
+ _always_save_data = False
52
+
53
+ def __getstate__(self):
54
+ """remove references and storage of properties, be sure to call super if overwriting this function in your subclass"""
55
+ vs = super().__getstate__()
56
+ pir = vs.pop("_prv_internal_references", None)
57
+ pir = vs.pop("_system_properties_def", None)
58
+ pir = vs.pop("parent", None)
59
+
60
+ return vs
61
+
62
+ @property
63
+ def anything_changed(self):
64
+ """use the on_setattr method to determine if anything changed,
65
+ also assume that stat_tab could change without input changes"""
66
+ if not hasattr(self, "_anything_changed"):
67
+ self._anything_changed = True
68
+
69
+ if self._anything_changed or self.always_save_data:
70
+ if self.log_level <= 5:
71
+ self.msg(
72
+ f"change: {self._anything_changed}| always: {self.always_save_data}"
73
+ )
74
+ return True
75
+ return False
76
+
77
+ @property
78
+ def last_context(self):
79
+ """Returns the last context"""
80
+ raise NotImplemented("this should be implemented in the solvable class")
81
+
82
+ # TODO: create an intelligent graph informed anything_changed alerting system (pyee?) and trigger solver_cache expirations appropriately
83
+ # @solver_cached #FIXME: not caching correctly
84
+ @property # FIXME: this is slow
85
+ def dataframe(self):
86
+ """
87
+ Returns a pandas DataFrame based on the current context.
88
+
89
+ This method checks for the presence of `last_context` and its `dataframe` attribute.
90
+ If they exist, it returns the `dataframe` from `last_context`.
91
+ If not, it checks for the `_patch_dataframe` attribute and returns it if it exists.
92
+ If neither condition is met, it returns an empty DataFrame.
93
+
94
+ :return: A pandas DataFrame based on the current context or an empty DataFrame if no context is available.
95
+ :rtype: pandas.DataFrame
96
+ """
97
+ """"""
98
+ if hasattr(self, "last_context") and hasattr(self.last_context, "dataframe"):
99
+ return self.last_context.dataframe
100
+ if hasattr(self, "_patch_dataframe") and self._patch_dataframe is not None:
101
+ return self._patch_dataframe
102
+ return pandas.DataFrame([])
103
+
104
+ @dataframe.setter
105
+ def dataframe(self, input_dataframe):
106
+ if hasattr(self, "last_context") and hasattr(self.last_context, "dataframe"):
107
+ raise Exception(f"may not set dataframe on run component")
108
+ self._patch_dataframe = input_dataframe
109
+
110
+ @property
111
+ def plotable_variables(self):
112
+ """Checks columns for ones that only contain numeric types or haven't been explicitly skipped"""
113
+ if self.dataframe is not None:
114
+ check_type = lambda key: all(
115
+ [isinstance(v, NUMERIC_TYPES) for v in self.dataframe[key]]
116
+ )
117
+ check_non_mono = lambda key: len(set(self.dataframe[key])) > 1
118
+
119
+ return [
120
+ var
121
+ for var in self.dataframe.columns
122
+ if var.lower() not in self.skip_plot_vars
123
+ and check_type(var)
124
+ and check_non_mono(var)
125
+ ]
126
+ return []
127
+
128
+ # Properties & Attribues
129
+ def print_info(self):
130
+ print(f"INFO: {self.name} | {self.identity}")
131
+ print("#" * 80)
132
+ for key, value in sorted(self.data_dict.items(), key=lambda kv: kv[0]):
133
+ print(f"{key:>40} | {value}")
134
+
135
+ @property
136
+ def data_dict(self):
137
+ """this is what is captured and used in each row of the dataframe / table"""
138
+ # NOTE: Solver class overrides this with full system references
139
+ out = collections.OrderedDict()
140
+ sref = self.internal_references()
141
+ for k, v in sref["attributes"].items():
142
+ if k in self.attr_raw_keys:
143
+ out[k] = v.value()
144
+ for k, v in sref["properties"].items():
145
+ out[k] = v.value()
146
+ return out
147
+
148
+ @instance_cached
149
+ def attr_raw_keys(self) -> list:
150
+ good = set(self.table_fields())
151
+ return [k for k in attr.fields_dict(self.__class__).keys() if k in good]
152
+
153
+ def set_attr(self, **kwargs):
154
+ assert set(kwargs).issubset(set(self.attr_raw_keys))
155
+ # TODO: support subcomponents via slots lookup
156
+ for k, v in kwargs.items():
157
+ setattr(self, k, v)
158
+
159
+ @instance_cached
160
+ def always_save_data(self):
161
+ """Checks if any properties are stochastic (random)"""
162
+ return self._always_save_data
163
+
164
+ @solver_cached
165
+ def table_dict(self):
166
+ # We use __get__ to emulate the property, we could call regularly from self but this is more straightforward
167
+ return {
168
+ k.lower(): obj.__get__(self)
169
+ for k, obj in self.system_properties_def.items()
170
+ }
171
+
172
+ @solver_cached
173
+ def system_properties(self):
174
+ # We use __get__ to emulate the property, we could call regularly from self but this is more straightforward
175
+ tabulated_properties = [
176
+ obj.__get__(self) for k, obj in self.system_properties_def.items()
177
+ ]
178
+ return tabulated_properties
179
+
180
+ @instance_cached
181
+ def system_properties_labels(self) -> list:
182
+ """Returns the labels from table properties"""
183
+ class_dict = self.__class__.__dict__
184
+ tabulated_properties = [
185
+ obj.label.lower() for k, obj in self.system_properties_def.items()
186
+ ]
187
+ return tabulated_properties
188
+
189
+ @instance_cached
190
+ def system_properties_types(self) -> list:
191
+ """Returns the types from table properties"""
192
+ class_dict = self.__class__.__dict__
193
+ tabulated_properties = [
194
+ obj.return_type for k, obj in self.system_properties_def.items()
195
+ ]
196
+ return tabulated_properties
197
+
198
+ @instance_cached
199
+ def system_properties_keys(self) -> list:
200
+ """Returns the table property keys"""
201
+ tabulated_properties = [k for k, obj in self.system_properties_def.items()]
202
+ return tabulated_properties
203
+
204
+ @instance_cached
205
+ def system_properties_description(self) -> list:
206
+ """returns system_property descriptions if they exist"""
207
+ class_dict = self.__class__.__dict__
208
+ tabulated_properties = [
209
+ obj.desc for k, obj in self.system_properties_def.items()
210
+ ]
211
+ return tabulated_properties
212
+
213
+ @classmethod
214
+ def cls_all_property_labels(cls):
215
+ return [obj.label for k, obj in cls.system_properties_classdef().items()]
216
+
217
+ @classmethod
218
+ def cls_all_property_keys(cls):
219
+ return [k for k, obj in cls.system_properties_classdef().items()]
220
+
221
+ @classmethod
222
+ def cls_all_attrs_fields(cls):
223
+ return attr.fields_dict(cls)
224
+
225
+ @solver_cached
226
+ def system_properties_def(self):
227
+ """Combine other classes table properties into this one, in the case of subclassed system_properties as a property that is cached"""
228
+ return self.__class__.system_properties_classdef()
229
+
230
+ @classmethod
231
+ def system_properties_classdef(cls, recache=False):
232
+ """Combine other parent-classes table properties into this one, in the case of subclassed system_properties"""
233
+ from engforge.tabulation import TabulationMixin
234
+
235
+ cls_key = f"_{cls.__name__}_system_properties"
236
+ # Use a cache for deep recursion
237
+ if not recache and hasattr(cls, cls_key):
238
+ res = getattr(cls, cls_key)
239
+ if res is not None:
240
+ return res
241
+
242
+ # otherwise make the cache
243
+ __system_properties = {}
244
+ for k, obj in cls.__dict__.items():
245
+ if isinstance(obj, system_property):
246
+ __system_properties[k] = obj
247
+
248
+ #
249
+ mrl = cls.mro()
250
+ inx_comp = mrl.index(TabulationMixin)
251
+
252
+ # Ensures everything is includes Tabulation Functionality
253
+ mrvs = mrl[1:inx_comp]
254
+
255
+ for mrv in mrvs:
256
+ # Remove anything not in the user code
257
+ log.msg(f"adding system properties from {mrv.__name__}")
258
+ if (
259
+ issubclass(mrv, TabulationMixin)
260
+ # and "engforge" not in mrv.__module__
261
+ ):
262
+ for k, obj in mrv.__dict__.items():
263
+ if k not in __system_properties and isinstance(
264
+ obj, system_property
265
+ ): # Precedent
266
+ # Assumes our instance has assumed this table property
267
+ prop = getattr(cls, k, None)
268
+ if prop and isinstance(prop, system_property):
269
+ __system_properties[k] = prop
270
+ if log.log_level <= 3:
271
+ log.msg(f"adding system property {mrv.__name__}.{k}")
272
+
273
+ setattr(cls, cls_key, __system_properties)
274
+
275
+ return __system_properties
276
+
277
+ @classmethod
278
+ def pre_compile(cls):
279
+ cls._anything_changed = True # set default on class
280
+ if any([v.stochastic for k, v in cls.system_properties_classdef(True).items()]):
281
+ log.info(f"setting always save on {cls.__name__}")
282
+ cls._always_save_data = True
283
+
284
+ @property
285
+ def system_id(self) -> str:
286
+ """returns an instance unique id based on id(self)"""
287
+ idd = id(self)
288
+ return f"{self.classname}.{idd}"
@@ -0,0 +1,2 @@
1
+ import sys
2
+ import os, pathlib
@@ -0,0 +1,221 @@
1
+ import unittest
2
+ from matplotlib import pylab
3
+ from engforge.eng.structure import *
4
+ from engforge.eng.geometry import ShapelySection
5
+ from sectionproperties.pre.library.steel_sections import i_section
6
+
7
+ from numpy import *
8
+
9
+
10
+ class test_cantilever(unittest.TestCase):
11
+ # tests the first example here
12
+ # https://www.engineeringtoolbox.com/cantilever-beams-d_1848.html
13
+
14
+ def setUp(self):
15
+ self.st = Structure(name="cantilever_beam", add_gravity_force=False)
16
+ self.st.add_node("wall", 0, 0, 0)
17
+ self.st.add_node("free", 0, 5, 0)
18
+
19
+ self.ibeam = i_section(0.3072, 0.1243, 0.0121, 0.008, 0.0089, 4)
20
+ beam = ShapelySection(shape=self.ibeam, material=ANSI_4130())
21
+ self.bm = self.st.add_member("mem", "wall", "free", section=beam)
22
+
23
+ self.st.def_support(
24
+ "wall",
25
+ support_DX=True,
26
+ support_DY=True,
27
+ support_DZ=True,
28
+ support_RY=True,
29
+ support_RX=True,
30
+ support_RZ=True,
31
+ )
32
+ self.st.frame.add_node_load("free", "FX", 3000, case="gravity")
33
+
34
+ self.st.run(check_statics=True)
35
+
36
+ def test_beam(self):
37
+ self.subtest_assert_near(self.bm.A, 53.4 / (100**2))
38
+ self.subtest_assert_near(self.bm.Ix, 8196 / (100**4))
39
+ self.subtest_assert_near(self.bm.Iy, 388.8 / (100**4))
40
+ self.subtest_assert_near(self.bm.section_mass, 41.9)
41
+
42
+ self.subtest_assert_near(self.bm.max_von_mises(), 27.4e6)
43
+ self.subtest_assert_near(float(self.bm.data_dict["min_deflection_y"]), -0.0076)
44
+ # self.subtest_assert_near(float(self.bm.data_dict["max_shear_y"]), 3000)
45
+ self.subtest_assert_near(float(self.bm.data_dict["max_shear_y"]), 3000)
46
+
47
+ df = self.st.node_dataframe.loc["gravity"]
48
+
49
+ dfw = df.loc["wall"]
50
+ dff = df.loc["free"]
51
+
52
+ self.subtest_assert_near(float(dfw["rxfx"]), -3000, msg="wall rxfx")
53
+ self.subtest_assert_near(float(dfw["rxmz"]), 15000, msg="wall rxmz")
54
+ self.subtest_assert_near(float(dfw["dx"]), 0, msg="wall dx")
55
+
56
+ self.subtest_assert_near(float(dff["dx"]), 0.0076, msg="dx")
57
+ self.subtest_assert_near(float(dff["rxfx"]), 0, msg="rxfx")
58
+ self.subtest_assert_near(float(dff["rxmz"]), 0, msg="rxmz")
59
+
60
+ stress_obj = self.bm.get_stress_at(0, "gravity")
61
+ # stress_obj.plot_stress_vm()
62
+
63
+ def subtest_assert_near(self, value, truth, pct=0.025, **kw):
64
+ with self.subTest(**kw):
65
+ self.assertAlmostEqual(value, truth, delta=max(abs(truth * pct), abs(pct)))
66
+
67
+
68
+ class test_truss(unittest.TestCase):
69
+ # Match this example, no beam stresses
70
+ # https://engineeringlibrary.org/reference/trusses-air-force-stress-manual
71
+
72
+ def setUp(self):
73
+ self.st = Structure(name="truss", add_gravity_force=False)
74
+ self.st.add_node("A", 0, 0, 0)
75
+ self.st.add_node("B", 15, 30 * sqrt(3) / 2, 0)
76
+ self.st.add_node("C", 45, 30 * sqrt(3) / 2, 0)
77
+ self.st.add_node("D", 75, 30 * sqrt(3) / 2, 0)
78
+ self.st.add_node("E", 90, 0, 0)
79
+ self.st.add_node("F", 60, 0, 0)
80
+ self.st.add_node("G", 30, 0, 0)
81
+
82
+ pairs = set()
83
+ Lmin = 30 * sqrt(3) / 2
84
+ Lmax = 30.1
85
+
86
+ for n1 in self.st.nodes.values():
87
+ for n2 in self.st.nodes.values():
88
+ L = numpy.sqrt(
89
+ (n1.X - n2.X) ** 2.0 + (n1.Y - n2.Y) ** 2.0 + (n1.Z - n2.Z) ** 2.0
90
+ )
91
+
92
+ if (
93
+ L >= Lmin
94
+ and L <= Lmax
95
+ and (n1.name, n2.name) not in pairs
96
+ and (n2.name, n1.name) not in pairs
97
+ ):
98
+ pairs.add((n1.name, n2.name))
99
+
100
+ elif (n1.name, n2.name) in pairs or (
101
+ n2.name,
102
+ n1.name,
103
+ ) in pairs:
104
+ pass
105
+
106
+ self.beam = sections.rectangular_section(1.5, 1.5)
107
+ material = ANSI_4130()
108
+ self.section = ShapelySection(shape=self.beam, material=material)
109
+
110
+ constrained = ("A", "E")
111
+ for n1, n2 in pairs:
112
+ bkey = f"{n1}_{n2}"
113
+ self.bm = self.st.add_member(
114
+ bkey,
115
+ n1,
116
+ n2,
117
+ # material=ANSI_4130(),
118
+ section=self.section,
119
+ # min_mesh_size=0.01,
120
+ )
121
+
122
+ # if n1 not in constrained:
123
+ # print(f'releasing {bkey}')
124
+ # self.st.frame.DefineReleases(bkey, Rzi=True)
125
+
126
+ # if n2 not in constrained:
127
+ # print(f'releasing {bkey} J')
128
+ # self.st.frame.DefineReleases(bkey, Rzj=True)
129
+
130
+ self.st.def_support(
131
+ "A",
132
+ support_DX=True,
133
+ support_DY=True,
134
+ support_DZ=True,
135
+ support_RY=True,
136
+ support_RX=True,
137
+ support_RZ=True,
138
+ )
139
+ self.st.def_support(
140
+ "E",
141
+ support_DX=False,
142
+ support_DY=True,
143
+ support_DZ=True,
144
+ support_RY=False,
145
+ support_RX=False,
146
+ support_RZ=True,
147
+ )
148
+ # for node in self.st.nodes:
149
+ # self.st.frame.Definesupport_(node,support_DZ=True,support_RZ=True)
150
+
151
+ self.st.add_node_load("F", "FY", -1000, case="gravity")
152
+ self.st.add_node_load("G", "FY", -2000, case="gravity")
153
+
154
+ self.st.run(check_statics=True)
155
+ # self.st.visulize()
156
+
157
+ def test_reactions(self):
158
+ df = self.st.node_dataframe.loc["gravity"]
159
+ # print(df)
160
+
161
+ dfa = df.loc["A"]
162
+ dfe = df.loc["E"]
163
+
164
+ # print(dfa)
165
+ # print(dfe)
166
+
167
+ self.subtest_assert_near(float(dfa["rxfy"]), 1667)
168
+ self.subtest_assert_near(float(dfe["rxfy"]), 1333)
169
+
170
+ self.subtest_member("A", "B", "max_axial", 1925)
171
+ self.subtest_member("A", "G", "max_axial", -949) # -926 textbook
172
+ self.subtest_member("B", "C", "max_axial", 1925)
173
+ self.subtest_member("B", "G", "max_axial", -1925)
174
+ self.subtest_member("C", "D", "max_axial", 1541)
175
+ self.subtest_member("F", "G", "max_axial", -1734)
176
+ self.subtest_member("C", "F", "max_axial", 382)
177
+ self.subtest_member("C", "G", "max_axial", -382)
178
+ self.subtest_member("C", "G", "max_axial", -378) # 1541 textbook
179
+ self.subtest_member("D", "F", "max_axial", -1541)
180
+ self.subtest_member("E", "F", "max_axial", -770)
181
+
182
+ # Visualization.RenderModel( self.st.frame )
183
+
184
+ def subtest_member(self, nodea, nodeb, result_key, truth, pct=0.025):
185
+ with self.subTest(msg=f"test {nodea}->{nodeb}"):
186
+ key_1 = f"{nodea}_{nodeb}"
187
+ key_2 = f"{nodeb}_{nodea}"
188
+
189
+ if key_1 in self.st.beams:
190
+ key = key_1
191
+ elif key_2 in self.st.beams:
192
+ key = key_2
193
+ else:
194
+ raise
195
+
196
+ value = self.get_member_result(nodea, nodeb, result_key)
197
+ dopasst = abs(value - truth) <= abs(truth) * pct
198
+
199
+ if not dopasst:
200
+ print(f"fails {key} {result_key}| {value:3.5f} == {truth:3.5f}?")
201
+ self.subtest_assert_near(value, truth, pct=pct)
202
+
203
+ def subtest_assert_near(self, value, truth, pct=0.025):
204
+ with self.subTest():
205
+ self.assertAlmostEqual(value, truth, delta=abs(truth * pct))
206
+
207
+ def get_member_result(self, nodea, nodeb, result_key):
208
+ key_1 = f"{nodea}_{nodeb}"
209
+ key_2 = f"{nodeb}_{nodea}"
210
+
211
+ if key_1 in self.st.beams:
212
+ mem = self.st.beams[key_1]
213
+ if result_key in mem.data_dict:
214
+ return float(mem.data_dict[result_key])
215
+
216
+ elif key_2 in self.st.beams:
217
+ mem = self.st.beams[key_2]
218
+ if result_key in mem.data_dict:
219
+ return float(mem.data_dict[result_key])
220
+
221
+ return numpy.nan # shouod fail, nan is not comparable
@@ -0,0 +1,158 @@
1
+ from engforge.datastores.reporting import *
2
+ from engforge.configuration import forge
3
+ from engforge.tabulation import system_property
4
+ from engforge.components import Component
5
+ from engforge.analysis import Analysis
6
+ from logging import *
7
+
8
+ import numpy
9
+
10
+ # tests scripts
11
+
12
+ # suprise the db and add more columns to test dynamic creation
13
+ suprise = False
14
+
15
+
16
+ @forge
17
+ class TestComponent(Component):
18
+ blank = attr.ib(default=None, validator=None) # no validator
19
+
20
+ name = attr.ib(default="test", validator=STR_VALIDATOR())
21
+ word = attr.ib(default="bird", validator=STR_VALIDATOR())
22
+ val = attr.ib(default=10)
23
+
24
+ always_save_data = True
25
+
26
+ @system_property
27
+ def synthetic_val(self):
28
+ if self.word == "schfiftyfive":
29
+ return 55
30
+ return random.random() * self.val
31
+
32
+ @system_property
33
+ def nan_val(self):
34
+ return numpy.nan
35
+
36
+ @system_property
37
+ def inf_val(self):
38
+ return numpy.inf
39
+
40
+ if suprise:
41
+
42
+ @system_property
43
+ def surpise_val(self):
44
+ return 13
45
+
46
+
47
+ @forge
48
+ class OtherComponent(Component):
49
+ arg1 = attr.ib(default="mmmm", validator=STR_VALIDATOR())
50
+ arg2 = attr.ib(default="word", validator=STR_VALIDATOR())
51
+ argnum = attr.ib(default=10)
52
+
53
+ if suprise:
54
+ ones = attr.ib(default=1111)
55
+ wacky = attr.ib(default="one hundred and 111", validator=STR_VALIDATOR())
56
+ more = attr.ib(default="other stuff", validator=STR_VALIDATOR())
57
+
58
+ always_save_data = True
59
+
60
+ @system_property
61
+ def random_val(self):
62
+ return self.argnum * random.random() / random.random()
63
+
64
+ if suprise:
65
+
66
+ @system_property
67
+ def new_val(self):
68
+ return self.argnum * random.random()
69
+
70
+
71
+ if suprise:
72
+
73
+ @forge
74
+ class SupriseComponent(Component):
75
+ arg3 = attr.ib(default="mmmm", validator=STR_VALIDATOR())
76
+ arg4 = attr.ib(default="word", validator=STR_VALIDATOR())
77
+ argque = attr.ib(default=-999)
78
+ # ones = attr.ib(default=1111)
79
+ # wacky = attr.ib(default='one hundred and 111', validator=STR_VALIDATOR())
80
+ # more = attr.ib(default='other stuff', validator=STR_VALIDATOR())
81
+
82
+ always_save_data = True
83
+
84
+ @system_property
85
+ def negative_random_val(self):
86
+ return self.argque * random.random() / random.random()
87
+
88
+
89
+ @forge
90
+ class TestAnalysis(Analysis):
91
+ if suprise:
92
+ surprise = attr.ib(factory=SupriseComponent)
93
+
94
+ other_cocomponent = attr.ib(factory=OtherComponent)
95
+ internal_component = attr.ib(factory=TestComponent)
96
+ some_random_value = attr.ib(default=10)
97
+ other_rand_val = attr.ib(default=1e6)
98
+
99
+ mode = "iterator"
100
+ iterator = attr.ib(factory=lambda: list(range(10)))
101
+ always_save_data = True
102
+
103
+ @system_property
104
+ def internal_val(self):
105
+ return self.internal_component.val
106
+
107
+ @system_property
108
+ def rand_val(self):
109
+ return random.random() * self.internal_component.val
110
+
111
+ def eval(self, item):
112
+ self.some_random_value = item
113
+
114
+ @system_property
115
+ def random_none(self):
116
+ val = random.random()
117
+ if val > 0.6:
118
+ return None
119
+ return val
120
+
121
+ if suprise:
122
+
123
+ @system_property
124
+ def suprise(self):
125
+ return random.random() * self.internal_component.val
126
+
127
+
128
+ if __name__ == "__main__":
129
+ log = logging.getLogger()
130
+ DEBUG = True
131
+ if DEBUG:
132
+ log.setLevel(logging.DEBUG)
133
+ logging.basicConfig(level=logging.DEBUG)
134
+ set_all_loggers_to(logging.DEBUG)
135
+
136
+ analysis = TestAnalysis()
137
+ tc = analysis.internal_component
138
+
139
+ # 1) Ensure exists Reflect The Database
140
+ db = DBConnection("reports", host="localhost", user="postgres", passd="dumbpass")
141
+ db.ensure_database_exists()
142
+ # db.engine.echo = True
143
+
144
+ rr = ResultsRegistry(db=db)
145
+ # rr.ensure_all_tables()
146
+ # rr.base.metadata.drop_all()
147
+ # rr.initalize()
148
+
149
+ # 2) Use ComponentRegistry, and AnalysisRegistry to gather subclasses of each. Analysis will also have results tables since they are components, these will be referenced in the analysis table.
150
+ analysis.run()
151
+ rr.ensure_analysis(analysis)
152
+ thread = rr.upload_analysis(analysis, use_thread=True)
153
+ thread.join()
154
+
155
+ # 3) For each component and analysis map create tables or map them if they dont exist.
156
+ # - use a registry approach (singleton?) to map the {component: db_class} pairs
157
+
158
+ # 4) When an an an analysis is solved, if configured for reporting, on post processing the results will be added to the database