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,338 @@
|
|
|
1
|
+
"""Testing of costs reporting module
|
|
2
|
+
- emphasis on recursive testing w/o double accounting on instance basis (id!)
|
|
3
|
+
- emphasis on default / subclass adjustments
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import unittest
|
|
7
|
+
from engforge.components import Component
|
|
8
|
+
from engforge._testing_components import *
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestEconDefaults(unittest.TestCase):
|
|
12
|
+
def tearDown(self) -> None:
|
|
13
|
+
Comp1.reset_cls_costs()
|
|
14
|
+
EconDefault.reset_cls_costs()
|
|
15
|
+
|
|
16
|
+
def test_custom_costs(self):
|
|
17
|
+
ed = EconDefault()
|
|
18
|
+
self.assertNotIn("comp1", ed.internal_components())
|
|
19
|
+
|
|
20
|
+
ed.custom_cost("comp1", Comp1())
|
|
21
|
+
self.assertIn("comp1", ed.internal_components())
|
|
22
|
+
|
|
23
|
+
def test_non_costmodel_default(self):
|
|
24
|
+
EconDefault.default_cost("comp", Comp1(cost_per_item=10))
|
|
25
|
+
ed = EconDefault(comp=Component())
|
|
26
|
+
ed.run()
|
|
27
|
+
self.assertEqual(ed.combine_cost, 10)
|
|
28
|
+
self.assertEqual(ed.econ.combine_cost, 10)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestCategoriesAndTerms(unittest.TestCase):
|
|
32
|
+
def tearDown(self) -> None:
|
|
33
|
+
Comp1.reset_cls_costs()
|
|
34
|
+
Comp2.reset_cls_costs()
|
|
35
|
+
EconRecursive.reset_cls_costs()
|
|
36
|
+
TermCosts.reset_cls_costs()
|
|
37
|
+
|
|
38
|
+
def test_itemized_costs(self):
|
|
39
|
+
tc = TermCosts()
|
|
40
|
+
|
|
41
|
+
ct = tc.costs_at_term(0)
|
|
42
|
+
self.assertEqual(ct["cost_init"], 100)
|
|
43
|
+
self.assertEqual(ct["cost_maintenance"], 0)
|
|
44
|
+
self.assertEqual(ct["cost_tax"], 1)
|
|
45
|
+
self.assertEqual(ct["cost_wage_tax"], 0)
|
|
46
|
+
|
|
47
|
+
ct = tc.costs_at_term(1)
|
|
48
|
+
self.assertEqual(ct["cost_init"], 00)
|
|
49
|
+
self.assertEqual(ct["cost_maintenance"], 10)
|
|
50
|
+
self.assertEqual(ct["cost_tax"], 1)
|
|
51
|
+
self.assertEqual(ct["cost_wage_tax"], 0)
|
|
52
|
+
|
|
53
|
+
ct = tc.costs_at_term(2)
|
|
54
|
+
self.assertEqual(ct["cost_init"], 00)
|
|
55
|
+
self.assertEqual(ct["cost_maintenance"], 10)
|
|
56
|
+
self.assertEqual(ct["cost_tax"], 1)
|
|
57
|
+
self.assertEqual(ct["cost_wage_tax"], 15)
|
|
58
|
+
|
|
59
|
+
def test_category_costs(self):
|
|
60
|
+
tc = TermCosts()
|
|
61
|
+
cc = tc.cost_categories_at_term(0)
|
|
62
|
+
self.assertEqual(cc["capex"], 100)
|
|
63
|
+
self.assertEqual(cc["opex"], 0)
|
|
64
|
+
self.assertEqual(cc["tax"], 1)
|
|
65
|
+
|
|
66
|
+
tc = TermCosts()
|
|
67
|
+
cc = tc.cost_categories_at_term(2)
|
|
68
|
+
self.assertEqual(cc["capex"], 0)
|
|
69
|
+
self.assertEqual(cc["opex"], 25)
|
|
70
|
+
self.assertEqual(cc["tax"], 16)
|
|
71
|
+
|
|
72
|
+
def test_econ(self):
|
|
73
|
+
tc = TermCosts()
|
|
74
|
+
Comp2.default_cost("comp1", 50)
|
|
75
|
+
c2 = Comp2(cost_per_item=10)
|
|
76
|
+
er = EconRecursive(comp1=tc, comp2=c2)
|
|
77
|
+
# inkw = {'econ_term_length':[1,5,10,50],'econ_discount_rate':[0,0.01,0.05,0.1]}
|
|
78
|
+
inkw = {}
|
|
79
|
+
er.run(revert_last=False, **inkw)
|
|
80
|
+
|
|
81
|
+
d = er.data_dict
|
|
82
|
+
|
|
83
|
+
self.assertEqual(d["econ.lifecycle.term_cost"], 161)
|
|
84
|
+
self.assertEqual(d["econ.summary.total_cost"], 161)
|
|
85
|
+
self.assertEqual(d["combine_cost"], 161)
|
|
86
|
+
self.assertEqual(d["sub_items_cost"], 161)
|
|
87
|
+
|
|
88
|
+
def test_econ_array(self):
|
|
89
|
+
tc = TermCosts()
|
|
90
|
+
Comp2.default_cost("comp1", 50)
|
|
91
|
+
c2 = Comp2(cost_per_item=10)
|
|
92
|
+
er = EconRecursive(comp1=tc, comp2=c2)
|
|
93
|
+
inkw = {"econ.term_length": [0, 5, 10, 15], "econ.fixed_output": 1}
|
|
94
|
+
# inkw = {}
|
|
95
|
+
er.run(revert_last=False, revert_every=False, **inkw)
|
|
96
|
+
|
|
97
|
+
df = er.dataframe
|
|
98
|
+
tc = (
|
|
99
|
+
df["econ.summary.total_cost"] == np.array([161.0, 220.0, 305.0, 390.0])
|
|
100
|
+
).all()
|
|
101
|
+
self.assertTrue(tc)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TestEconomicsAccounting(unittest.TestCase):
|
|
105
|
+
def setUp(self) -> None:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
def tearDown(self) -> None:
|
|
109
|
+
Comp1.reset_cls_costs()
|
|
110
|
+
Comp2.reset_cls_costs()
|
|
111
|
+
EconRecursive.reset_cls_costs()
|
|
112
|
+
|
|
113
|
+
def test_econ_defaults(self):
|
|
114
|
+
Comp1.default_cost("norm", Norm(cost_per_item=10))
|
|
115
|
+
Comp2.default_cost("comp1", Comp1(cost_per_item=5))
|
|
116
|
+
EconRecursive.default_cost("comp2", Comp2(cost_per_item=3))
|
|
117
|
+
er = EconRecursive(cost_per_item=50)
|
|
118
|
+
# c2 = er.comp2
|
|
119
|
+
self.assertEqual(er.combine_cost, 78)
|
|
120
|
+
self.assertEqual(er.sub_items_cost, 28)
|
|
121
|
+
|
|
122
|
+
er.run()
|
|
123
|
+
d = er.data_dict
|
|
124
|
+
self.assertEqual(78, d["econ.summary.total_cost"])
|
|
125
|
+
self.assertEqual(78, d["econ.lifecycle.annualized.term_cost"])
|
|
126
|
+
self.assertEqual(78, d["econ.lifecycle.annualized.levelized_cost"])
|
|
127
|
+
self.assertEqual(78, d["econ.lifecycle.term_cost"])
|
|
128
|
+
|
|
129
|
+
def test_recursive_null(self, ANS=75):
|
|
130
|
+
Comp1.default_cost("norm", 5)
|
|
131
|
+
Comp2.default_cost("comp1", 10)
|
|
132
|
+
EconRecursive.default_cost("comp1", 3)
|
|
133
|
+
EconRecursive.default_cost("comp2", 7)
|
|
134
|
+
er = EconRecursive(cost_per_item=50, comp1=None, comp2=None)
|
|
135
|
+
er.run()
|
|
136
|
+
self.assertEqual(er.combine_cost, ANS)
|
|
137
|
+
self.assertEqual(er.econ.combine_cost, ANS)
|
|
138
|
+
self.assertEqual(er.comp1, None)
|
|
139
|
+
self.assertEqual(er.comp2, None)
|
|
140
|
+
|
|
141
|
+
d = er.data_dict
|
|
142
|
+
self.assertEqual(ANS, d["econ.combine_cost"])
|
|
143
|
+
self.assertEqual(ANS, d["combine_cost"])
|
|
144
|
+
self.assertEqual(3, d["econ.comp1.cost.item_cost"])
|
|
145
|
+
self.assertEqual(7, d["econ.comp2.cost.item_cost"])
|
|
146
|
+
|
|
147
|
+
def test_recursive_comp2(self, ANS=80):
|
|
148
|
+
Comp1.default_cost("norm", 5)
|
|
149
|
+
Comp2.default_cost("comp1", 10)
|
|
150
|
+
EconRecursive.default_cost("comp1", Comp1(cost_per_item=3))
|
|
151
|
+
EconRecursive.default_cost("comp2", Comp2(cost_per_item=7))
|
|
152
|
+
er = EconRecursive(cost_per_item=50, comp1=None, comp2=None)
|
|
153
|
+
er.run()
|
|
154
|
+
self.assertEqual(er.combine_cost, ANS)
|
|
155
|
+
self.assertEqual(er.econ.combine_cost, ANS)
|
|
156
|
+
|
|
157
|
+
d = er.data_dict
|
|
158
|
+
self.assertEqual(ANS, d["econ.combine_cost"])
|
|
159
|
+
self.assertEqual(ANS, d["combine_cost"])
|
|
160
|
+
self.assertEqual(3, d["econ.comp1.cost.item_cost"])
|
|
161
|
+
# self.assertEqual(10,d['econ_comp2.combine_cost'])
|
|
162
|
+
|
|
163
|
+
def test_recursive_all(self, ANS=80):
|
|
164
|
+
# FIXME: missing norm cost of -5
|
|
165
|
+
|
|
166
|
+
Comp1.default_cost("norm", 5)
|
|
167
|
+
Comp2.default_cost("comp1", 10)
|
|
168
|
+
EconRecursive.default_cost("comp1", Comp1(cost_per_item=3))
|
|
169
|
+
EconRecursive.default_cost("comp2", Comp2(cost_per_item=7))
|
|
170
|
+
er = EconRecursive(cost_per_item=50)
|
|
171
|
+
er.run()
|
|
172
|
+
self.assertEqual(er.combine_cost, ANS)
|
|
173
|
+
self.assertEqual(er.econ.combine_cost, ANS)
|
|
174
|
+
|
|
175
|
+
d = er.data_dict
|
|
176
|
+
self.assertEqual(ANS, d["econ.combine_cost"])
|
|
177
|
+
self.assertEqual(ANS, d["combine_cost"])
|
|
178
|
+
# self.assertEqual(5,d['econ_comp1.combine_cost'])
|
|
179
|
+
# self.assertEqual(10,d['econ_comp2.combine_cost'])
|
|
180
|
+
self.assertEqual(10, d["econ.comp2.comp1.cost.item_cost"])
|
|
181
|
+
self.assertEqual(5, d["econ.comp1.norm.cost.item_cost"])
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class TestCostModel(unittest.TestCase):
|
|
185
|
+
def setUp(self):
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
def tearDown(self) -> None:
|
|
189
|
+
Comp1.reset_cls_costs()
|
|
190
|
+
Comp2.reset_cls_costs()
|
|
191
|
+
|
|
192
|
+
def test_comp1(self):
|
|
193
|
+
c0 = Comp1(cost_per_item=0)
|
|
194
|
+
self.assertEqual(c0.combine_cost, 0)
|
|
195
|
+
|
|
196
|
+
c1 = Comp1(cost_per_item=5)
|
|
197
|
+
self.assertEqual(c1.combine_cost, 5)
|
|
198
|
+
|
|
199
|
+
c2 = Comp1(cost_per_item=10)
|
|
200
|
+
self.assertEqual(c2.combine_cost, 10)
|
|
201
|
+
|
|
202
|
+
def test_comp2(self):
|
|
203
|
+
c1 = Comp1(cost_per_item=0)
|
|
204
|
+
c2 = Comp2(cost_per_item=0, comp1=c1)
|
|
205
|
+
self.assertEqual(c2.combine_cost, 0)
|
|
206
|
+
|
|
207
|
+
c1 = Comp1(cost_per_item=5)
|
|
208
|
+
c2 = Comp2(cost_per_item=5, comp1=c1)
|
|
209
|
+
self.assertEqual(c2.combine_cost, 10)
|
|
210
|
+
|
|
211
|
+
c1 = Comp1(cost_per_item=10)
|
|
212
|
+
c2 = Comp2(cost_per_item=10, comp1=c1)
|
|
213
|
+
self.assertEqual(c2.combine_cost, 20)
|
|
214
|
+
|
|
215
|
+
def test_default(self):
|
|
216
|
+
c1 = Comp1(cost_per_item=0)
|
|
217
|
+
self.assertEqual(c1.combine_cost, 0)
|
|
218
|
+
|
|
219
|
+
Comp1.default_cost("norm", 10)
|
|
220
|
+
c1 = Comp1(cost_per_item=0)
|
|
221
|
+
self.assertEqual(c1.combine_cost, 10)
|
|
222
|
+
|
|
223
|
+
c1 = Comp1(cost_per_item=0)
|
|
224
|
+
c1.custom_cost("norm", 20)
|
|
225
|
+
self.assertEqual(c1.combine_cost, 20)
|
|
226
|
+
self.assertEqual(Comp1._slot_costs["norm"], 10)
|
|
227
|
+
|
|
228
|
+
def test_ref_loop(self):
|
|
229
|
+
# Comp1.default_cost('norm',10)
|
|
230
|
+
c2 = Comp2(cost_per_item=5)
|
|
231
|
+
c1 = Comp1(cost_per_item=5, norm=c2)
|
|
232
|
+
c2.comp1 = c1
|
|
233
|
+
self.assertEqual(c1.combine_cost, 10)
|
|
234
|
+
self.assertEqual(c1.sub_items_cost, 10)
|
|
235
|
+
|
|
236
|
+
def test_override(self):
|
|
237
|
+
Comp1.default_cost("norm", 10)
|
|
238
|
+
# c2 = Comp2(cost_per_item=5)
|
|
239
|
+
c1 = Comp1(cost_per_item=5) # ,norm=c2)
|
|
240
|
+
self.assertEqual(c1.combine_cost, 15)
|
|
241
|
+
self.assertEqual(c1.sub_items_cost, 10)
|
|
242
|
+
|
|
243
|
+
# Comp1.reset_cls_costs()
|
|
244
|
+
c2 = Comp2(cost_per_item=15)
|
|
245
|
+
c1 = Comp1(cost_per_item=15, norm=c2)
|
|
246
|
+
self.assertEqual(c1.combine_cost, 40)
|
|
247
|
+
self.assertEqual(c1.sub_items_cost, 25)
|
|
248
|
+
|
|
249
|
+
def test_defaults(self):
|
|
250
|
+
# Comp1.reset_cls_costs()
|
|
251
|
+
Comp1.default_cost("norm", Norm(cost_per_item=10))
|
|
252
|
+
Comp2.default_cost("comp1", Comp1(cost_per_item=5))
|
|
253
|
+
c2 = Comp2(cost_per_item=3)
|
|
254
|
+
self.assertEqual(c2.combine_cost, 18)
|
|
255
|
+
self.assertEqual(c2.sub_items_cost, 15)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class TestFanSystemDataFrame(unittest.TestCase):
|
|
259
|
+
def test_dataframe(self):
|
|
260
|
+
# Create the FanSystem instance
|
|
261
|
+
fs = FanSystem(fan=Fan(), motor=Motor(), base=MetalBase())
|
|
262
|
+
fs.run(
|
|
263
|
+
**{
|
|
264
|
+
"econ.term_length": [1, 10, 20, 50],
|
|
265
|
+
"fan.V": [1, 5, 10],
|
|
266
|
+
"fan.area": [1, 5, 10],
|
|
267
|
+
},
|
|
268
|
+
base=[None, MetalBase()],
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Get the dataframe
|
|
272
|
+
df_complete = fs.last_context.dataframe
|
|
273
|
+
|
|
274
|
+
dfc = df_complete
|
|
275
|
+
match = (
|
|
276
|
+
dfc["fan.blade_cost"] + dfc["motor.motor_cost"]
|
|
277
|
+
== dfc["econ.lifecycle.category.capex"]
|
|
278
|
+
).all()
|
|
279
|
+
self.assertTrue(match)
|
|
280
|
+
|
|
281
|
+
match = (
|
|
282
|
+
df_complete["fan.area"] * df_complete["fan.v"]
|
|
283
|
+
== df_complete["fan.volumetric_flow"]
|
|
284
|
+
).all()
|
|
285
|
+
self.assertTrue(match)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# TODO: get cost accounting working for ComponentIter components
|
|
289
|
+
# @forge
|
|
290
|
+
# class CompGroup(ComponentIterator):
|
|
291
|
+
# pass
|
|
292
|
+
# # @forge
|
|
293
|
+
# # class CostGroup(CompGroup,CostModel):
|
|
294
|
+
# # pass
|
|
295
|
+
|
|
296
|
+
# @forge
|
|
297
|
+
# class EconWide(System):
|
|
298
|
+
# econ = Slot.define(Economics)
|
|
299
|
+
# comp_set = Slot.define_iterator(CompGroup,wide=True)
|
|
300
|
+
#
|
|
301
|
+
# @forge
|
|
302
|
+
# class EconNarrow(System):
|
|
303
|
+
# econ = Slot.define(Economics)
|
|
304
|
+
# comp_set = Slot.define_iterator(CompGroup,wide=False)
|
|
305
|
+
|
|
306
|
+
# class TestEconomicsIter(unittest.TestCase):
|
|
307
|
+
#
|
|
308
|
+
# def tearDown(self):
|
|
309
|
+
# Comp1.reset_cls_costs()
|
|
310
|
+
# Comp2.reset_cls_costs()
|
|
311
|
+
# EconWide.reset_cls_costs()
|
|
312
|
+
# EconNarrow.reset_cls_costs()
|
|
313
|
+
# CostGroup.reset_cls_costs()
|
|
314
|
+
#
|
|
315
|
+
# def test_comp_wide(self):
|
|
316
|
+
#
|
|
317
|
+
# def test_comp_narrow(self):
|
|
318
|
+
#
|
|
319
|
+
# def test_cost_wide(self):
|
|
320
|
+
#
|
|
321
|
+
# def test_comp_narrow(self):
|
|
322
|
+
# cg = CompGroup(component_type=Comp1)
|
|
323
|
+
# total = 0
|
|
324
|
+
# for i in range(11):
|
|
325
|
+
# cg.data.append(Comp1(cost_per_item=i))
|
|
326
|
+
# total += i
|
|
327
|
+
#
|
|
328
|
+
# ew = EconNarrow(comp_set = cg)
|
|
329
|
+
# ew.run()
|
|
330
|
+
#
|
|
331
|
+
# d = ew.data_dict
|
|
332
|
+
# self.assertEqual(d['econ_comp_set.item_cost'], ew.comp_set.current.item_cost)
|
|
333
|
+
# self.assertEqual(ew.econ.total_cost,total)
|
|
334
|
+
# self.assertEqual(ew.econ.total_cost,total)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
if __name__ == "__main__":
|
|
338
|
+
unittest.main()
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""test transient cases and match against real world results"""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from engforge import *
|
|
6
|
+
from engforge.attr_plotting import *
|
|
7
|
+
from engforge._testing_components import SpringMass
|
|
8
|
+
|
|
9
|
+
from scipy.optimize import curve_fit, least_squares
|
|
10
|
+
import numpy as np
|
|
11
|
+
from matplotlib.pyplot import *
|
|
12
|
+
|
|
13
|
+
import attrs
|
|
14
|
+
|
|
15
|
+
fit = lambda t, a, b, w: a * np.sin((2 * np.pi / w) * t) + b
|
|
16
|
+
jac = lambda t, a, b, w: np.array(
|
|
17
|
+
[
|
|
18
|
+
np.sin(w * t),
|
|
19
|
+
1 * np.ones(t.shape),
|
|
20
|
+
a * w * np.cos(w * t),
|
|
21
|
+
0 * np.ones(t.shape),
|
|
22
|
+
]
|
|
23
|
+
).T
|
|
24
|
+
ls = lambda x, t, y: (x[0] * np.sin(x[1] * t + x[3]) + x[2]) - y
|
|
25
|
+
f = lambda x, t, y: (x[0] * np.sin(x[1] * t + x[3]) + x[2])
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestDynamics(unittest.TestCase):
|
|
29
|
+
def test_sim(self):
|
|
30
|
+
"""tests free undamped motion"""
|
|
31
|
+
|
|
32
|
+
sm = SpringMass(Fa=0, u=0)
|
|
33
|
+
|
|
34
|
+
# the analytical answer
|
|
35
|
+
self.w_ans = np.sqrt(sm.k / sm.m)
|
|
36
|
+
|
|
37
|
+
dt = 0.001
|
|
38
|
+
|
|
39
|
+
df = sm.simulate(dt=dt, endtime=10)
|
|
40
|
+
# TODO: add passing flag to context
|
|
41
|
+
# self.assertTrue(sm.solved)
|
|
42
|
+
# self.assertTrue(sm.converged)
|
|
43
|
+
|
|
44
|
+
X = df.x
|
|
45
|
+
T = df.time
|
|
46
|
+
t = T[T < 2]
|
|
47
|
+
x = X[T < 2]
|
|
48
|
+
|
|
49
|
+
p0 = (x.max() - x.min(), self.w_ans + 0.01, x.mean(), -3.14)
|
|
50
|
+
# ans,cov = curve_fit(fit,t,x,p0=p0)
|
|
51
|
+
ans = least_squares(
|
|
52
|
+
ls,
|
|
53
|
+
p0,
|
|
54
|
+
args=(t, x),
|
|
55
|
+
ftol=1e-14,
|
|
56
|
+
jac="cs",
|
|
57
|
+
xtol=1e-14,
|
|
58
|
+
gtol=1e-14,
|
|
59
|
+
x_scale=2,
|
|
60
|
+
)
|
|
61
|
+
if not ans.success:
|
|
62
|
+
raise Exception("failure")
|
|
63
|
+
|
|
64
|
+
self.assertAlmostEqual(ans.x[1], self.w_ans, delta=0.01)
|
|
65
|
+
|
|
66
|
+
def test_damping(self):
|
|
67
|
+
"""test that the ss answer is equal to the result with damping"""
|
|
68
|
+
sm = SpringMass(Fa=0, u=5)
|
|
69
|
+
sm.run(dxdt=0, combos="time") # time captures integral via combos
|
|
70
|
+
|
|
71
|
+
dfss = sm.last_context.dataframe
|
|
72
|
+
trsm, df = sm.simulate(dt=0.001, endtime=10, return_all=True)
|
|
73
|
+
|
|
74
|
+
self.assertAlmostEqual(df.iloc[-1].x, dfss.iloc[0].x, delta=0.01)
|
|
75
|
+
|
|
76
|
+
def test_forcing(self):
|
|
77
|
+
"""test that the ss answer is equal to the result with damping"""
|
|
78
|
+
sm = SpringMass(Fa=10, u=0, wo_f=1)
|
|
79
|
+
sm.wo_f = w_ans = np.sqrt(sm.k / sm.m) * 0.1 # very different frequency
|
|
80
|
+
|
|
81
|
+
sm.run(dxdt=0, combos="time")
|
|
82
|
+
dfss = sm.dataframe
|
|
83
|
+
sm.x = dfss.iloc[0].x # no residual input
|
|
84
|
+
|
|
85
|
+
trsm, df = sm.simulate(
|
|
86
|
+
dt=0.001, endtime=10, run_solver=False, combos="*", return_all=True
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
X = df.x
|
|
90
|
+
T = df.time
|
|
91
|
+
t = T[T < 2]
|
|
92
|
+
x = X[T < 2]
|
|
93
|
+
|
|
94
|
+
p0 = (x.max() - x.min(), w_ans + 0.01, x.mean(), -3.14)
|
|
95
|
+
# ans,cov = curve_fit(fit,t,x,p0=p0)
|
|
96
|
+
ans = least_squares(
|
|
97
|
+
ls,
|
|
98
|
+
p0,
|
|
99
|
+
args=(t, x),
|
|
100
|
+
ftol=1e-14,
|
|
101
|
+
jac="cs",
|
|
102
|
+
xtol=1e-14,
|
|
103
|
+
gtol=1e-14,
|
|
104
|
+
x_scale=2,
|
|
105
|
+
)
|
|
106
|
+
if not ans.success:
|
|
107
|
+
raise Exception("failure")
|
|
108
|
+
|
|
109
|
+
self.assertAlmostEqual(ans.x[1], w_ans, delta=0.01)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from engforge._testing_components import *
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestDynamics(unittest.TestCase):
|
|
8
|
+
def test_dynamics(self, endtime=10, dt=0.01):
|
|
9
|
+
dc = DynamicComponent()
|
|
10
|
+
ds = DynamicSystem(comp=dc)
|
|
11
|
+
|
|
12
|
+
min_kw = {"normalize": np.array([1 / 1000])}
|
|
13
|
+
sim, df = ds.simulate(dt, endtime, run_solver=False, return_all=True)
|
|
14
|
+
|
|
15
|
+
# ax = df.plot("time", ["x", "comp_x", "trns_x", "comp_x0"])
|
|
16
|
+
# # ax.set_ylim(-1,5)
|
|
17
|
+
# ax2 = ax.twinx()
|
|
18
|
+
# ax2.plot(df.time, df.a, "r--", label="acl")
|
|
19
|
+
# ax2.plot(df.time, df["trns_a"], "b--", label="trans_acl")
|
|
20
|
+
# ax2.plot(df.time, df["spring_accel"], "c--", label="spring_acl")
|
|
21
|
+
# ax2.legend()
|
|
22
|
+
|
|
23
|
+
self.assertGreaterEqual(df["time"].max(), endtime - dt)
|
|
24
|
+
|
|
25
|
+
def test_steady_state(self):
|
|
26
|
+
dc = DynamicComponent()
|
|
27
|
+
ds = DynamicSystem(comp=dc)
|
|
28
|
+
|
|
29
|
+
# TODO: check dxdt=0 combo results (dynamics/rates==states)
|
|
30
|
+
|
|
31
|
+
ans = ds.run(dxdt=0, combos="time", revert_last=False, revert_every=False)
|
|
32
|
+
output = ans["output"][0]
|
|
33
|
+
self.assertTrue(output["success"])
|
|
34
|
+
|
|
35
|
+
all_rate_res = output["Ycon"]
|
|
36
|
+
for rkey, rate_res in all_rate_res.items():
|
|
37
|
+
self.assertAlmostEqual(
|
|
38
|
+
rate_res, 0, places=3, msg=f"Rate {rkey} is not zero"
|
|
39
|
+
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from engforge import *
|
|
2
|
+
import attrs
|
|
3
|
+
import numpy
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@forge(auto_attribs=True)
|
|
7
|
+
class FourBar(System):
|
|
8
|
+
# r1
|
|
9
|
+
r1: float = 0.02
|
|
10
|
+
gamma_zero: float = 10 * 3.1415 / 180 # radians
|
|
11
|
+
gamma: float = 0
|
|
12
|
+
r1_slv = Solver.declare_var("r1", combos="indep,r1")
|
|
13
|
+
r1_slv.add_var_constraint(0.0, kind="min", combos="lim")
|
|
14
|
+
r1_slv.add_var_constraint(0.05, kind="max", combos="lim")
|
|
15
|
+
|
|
16
|
+
gz_slv = Solver.declare_var("gamma_zero", combos="indep,zero")
|
|
17
|
+
gz_slv.add_var_constraint(0.0, kind="min", combos="lim")
|
|
18
|
+
gz_slv.add_var_constraint(15 * 3.1415 / 180, kind="max", combos="lim")
|
|
19
|
+
|
|
20
|
+
gam_slv = Solver.declare_var("gamma", combos="indep,gamma")
|
|
21
|
+
gam_slv.add_var_constraint(0.0, kind="min", combos="lim")
|
|
22
|
+
gam_sig = Signal.define("gamma", "gamma_zero", combos="gam_sig")
|
|
23
|
+
|
|
24
|
+
# arm
|
|
25
|
+
theta: float = 0 # radians
|
|
26
|
+
ra: float = 0.03
|
|
27
|
+
theta_slv = Solver.declare_var("theta", combos="indep,theta")
|
|
28
|
+
theta_slv.add_var_constraint(0.0, kind="min", combos="lim")
|
|
29
|
+
theta_slv.add_var_constraint(3.14159, kind="max", combos="lim")
|
|
30
|
+
|
|
31
|
+
# r2
|
|
32
|
+
x2: float = 0.05
|
|
33
|
+
h2: float = 0.0
|
|
34
|
+
x2_slv = Solver.declare_var("x2", combos="indep,x2")
|
|
35
|
+
x2_slv.add_var_constraint(0.025, kind="min", combos="lim")
|
|
36
|
+
x2_slv.add_var_constraint(0.05, kind="max", combos="lim")
|
|
37
|
+
|
|
38
|
+
h2_slv = Solver.declare_var("h2", combos="indep,h2")
|
|
39
|
+
h2_slv.add_var_constraint(-0.01, kind="min", combos="lim")
|
|
40
|
+
h2_slv.add_var_constraint(0.01, kind="max", combos="lim")
|
|
41
|
+
|
|
42
|
+
len_slv = Solver.con_eq("l3_zero", "l3_gamma", combos="goal,leneq")
|
|
43
|
+
goal_slv = Solver.objective("gamma_goal", kind="max", combos="goal,gamma")
|
|
44
|
+
l3_max = Solver.objective("l3_zero", kind="max", combos="goal,l3max")
|
|
45
|
+
|
|
46
|
+
@system_prop
|
|
47
|
+
def gamma_goal(self) -> float:
|
|
48
|
+
return self.gamma
|
|
49
|
+
|
|
50
|
+
@system_prop
|
|
51
|
+
def r3_x_zero(self) -> float:
|
|
52
|
+
"""length defined by closed condition"""
|
|
53
|
+
return self.x2 + self.ra - self.r1 * numpy.cos(self.gamma_zero)
|
|
54
|
+
|
|
55
|
+
@system_prop
|
|
56
|
+
def r3_x(self) -> float:
|
|
57
|
+
"""length defined by gamma/theta"""
|
|
58
|
+
return (
|
|
59
|
+
self.x2 + self.ra * numpy.cos(self.theta) - self.r1 * numpy.cos(self.gamma)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@system_prop
|
|
63
|
+
def y_zero(self) -> float:
|
|
64
|
+
"""length defined by closed condition"""
|
|
65
|
+
return self.h2 - self.r1 * numpy.sin(self.gamma_zero)
|
|
66
|
+
|
|
67
|
+
@system_prop
|
|
68
|
+
def y(self) -> float:
|
|
69
|
+
"""length defined by gamma/theta"""
|
|
70
|
+
return (
|
|
71
|
+
self.x2 + self.ra * numpy.sin(self.theta) - self.r1 * numpy.sin(self.gamma)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@system_prop
|
|
75
|
+
def l3_zero(self) -> float:
|
|
76
|
+
return self.r3_x_zero**2 + self.y_zero**2
|
|
77
|
+
|
|
78
|
+
@system_prop
|
|
79
|
+
def l3_gamma(self) -> float:
|
|
80
|
+
return self.r3_x**2 + self.y**2
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
# %run -i ~/engforge/engforge/test/test_four_bar.py
|
|
85
|
+
import numpy as np
|
|
86
|
+
|
|
87
|
+
fb = FourBar()
|
|
88
|
+
fb.run(combos="*", revert_last=False, revert_every=False)
|
|
89
|
+
|
|
90
|
+
df = fb.last_context.dataframe
|
|
91
|
+
|
|
92
|
+
fb.run(
|
|
93
|
+
combos="goal,gamma,theta,lim",
|
|
94
|
+
theta=np.linspace(0, 3.14159),
|
|
95
|
+
revert_last=False,
|
|
96
|
+
revert_every=False,
|
|
97
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
|
|
3
|
+
testpath = pathlib.Path(__file__).parent.absolute()
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger("neptunya-test")
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from matplotlib import pylab
|
|
13
|
+
import numpy as n
|
|
14
|
+
import unittest
|
|
15
|
+
|
|
16
|
+
import engforge
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import copy, traceback
|
|
20
|
+
import random, json
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ImportTest(unittest.TestCase):
|
|
25
|
+
"""We test the compilation of all included modules"""
|
|
26
|
+
|
|
27
|
+
def test_import_analysis(self):
|
|
28
|
+
import engforge.analysis
|
|
29
|
+
|
|
30
|
+
def test_import_common(self):
|
|
31
|
+
import engforge.common
|
|
32
|
+
|
|
33
|
+
def test_import_components(self):
|
|
34
|
+
import engforge.components
|
|
35
|
+
|
|
36
|
+
def test_import_configuration(self):
|
|
37
|
+
import engforge.configuration
|
|
38
|
+
|
|
39
|
+
def test_import_data(self):
|
|
40
|
+
import engforge.datastores.data
|
|
41
|
+
|
|
42
|
+
# def test_import_gdocs(self):
|
|
43
|
+
# import engforge.datastores.gdocs
|
|
44
|
+
|
|
45
|
+
def test_import_locations(self):
|
|
46
|
+
import engforge.locations
|
|
47
|
+
|
|
48
|
+
def test_import_logging(self):
|
|
49
|
+
import engforge.logging
|
|
50
|
+
|
|
51
|
+
def test_import_plotting(self):
|
|
52
|
+
import engforge.attr_plotting
|
|
53
|
+
|
|
54
|
+
# def test_import_process(self):
|
|
55
|
+
# import engforge.process
|
|
56
|
+
|
|
57
|
+
def test_import_patterns(self):
|
|
58
|
+
import engforge.patterns
|
|
59
|
+
|
|
60
|
+
def test_import_solid_materials(self):
|
|
61
|
+
import engforge.eng.solid_materials
|
|
62
|
+
|
|
63
|
+
def test_import_tabulation(self):
|
|
64
|
+
import engforge.tabulation
|
|
65
|
+
|
|
66
|
+
def test_import_thermodynamics(self):
|
|
67
|
+
import engforge.eng.thermodynamics
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
print(("\n" * 10))
|
|
72
|
+
unittest.main()
|