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/eng/pipes.py ADDED
@@ -0,0 +1,1211 @@
1
+ """We'll use the QP formulation to develop a fluid analysis system for fluids
2
+ start with single phase and move to others
3
+
4
+
5
+ 1) Pumps Power = C x Q x P
6
+ 2) Compressor = C x (QP) x (PR^C2 - 1)
7
+ 3) Pipes = dP = C x fXL/D x V^2 / 2g
8
+ 4) Pipe Fittings / Valves = dP = C x V^2 (fXL/D +K) | K is a constant, for valves it can be interpolated between closed and opened
9
+ 5) Splitters & Joins: Handle fluid mixing
10
+ 6) Phase Separation (This one is gonna be hard)
11
+ 7) Heat Exchanger dP = f(V,T) - phase change issues
12
+ 8) Filtration dP = k x Vxc x (Afrontal/Asurface) "linear"
13
+ """
14
+
15
+ from engforge.components import Component
16
+ from engforge.configuration import forge
17
+ from engforge.system_reference import Ref
18
+ from engforge.tabulation import (
19
+ system_property,
20
+ NUMERIC_VALIDATOR,
21
+ STR_VALIDATOR,
22
+ )
23
+ from engforge.eng.fluid_material import FluidMaterial
24
+ from engforge.common import G_grav_constant
25
+ from engforge.attr_slots import *
26
+ from engforge.attr_signals import *
27
+ from engforge.logging import LoggingMixin
28
+ from engforge.system import System
29
+ from engforge.properties import *
30
+
31
+
32
+ import networkx as nx
33
+
34
+ import attr, attrs
35
+
36
+ import numpy
37
+ import fluids
38
+
39
+ import attrs
40
+
41
+
42
+ class PipeLog(LoggingMixin):
43
+ pass
44
+
45
+
46
+ log = PipeLog()
47
+
48
+
49
+ # TODO: add compressibility effects
50
+ @forge
51
+ class PipeNode(Component):
52
+ x: float = attrs.field()
53
+ y: float = attrs.field()
54
+ z: float = attrs.field()
55
+
56
+ _segments: list # created on init
57
+
58
+ def __on_init__(self):
59
+ self._segments = []
60
+
61
+ def add_segment(self, pipe: "PipeFlow"):
62
+ if pipe not in self.segments:
63
+ self._segments.append(pipe)
64
+ else:
65
+ self.warning(f"pipe already added: {pipe}")
66
+
67
+ @property
68
+ def segments(self):
69
+ return self._segments
70
+
71
+ @system_property
72
+ def sum_of_flows(self) -> float:
73
+ out = 0
74
+ for pipe_seg in self._segments:
75
+ if self is pipe_seg.node_s:
76
+ out -= pipe_seg.Q
77
+ elif self is pipe_seg.node_e:
78
+ out += pipe_seg.Q
79
+ return out
80
+
81
+
82
+ @forge
83
+ class PipeFlow(Component):
84
+ D: float = attrs.field()
85
+ v: float = attrs.field(default=0)
86
+ material = Slot.define(FluidMaterial)
87
+
88
+ def set_flow(self, flow):
89
+ v = flow / self.A
90
+ self.v = v
91
+
92
+ # Geometric Propreties
93
+ @system_property
94
+ def A(self) -> float:
95
+ """:returns: the cross sectional area of pipe in [m2]"""
96
+ return 3.1415 * (self.D / 2.0) ** 2.0
97
+
98
+ @system_property
99
+ def C(self) -> float:
100
+ """:returns: the sectional circmerence of pipe"""
101
+ return 3.1415 * self.D
102
+
103
+ # Fluid Propreties
104
+ @system_property
105
+ def Q(self) -> float:
106
+ """:returns: the volumetric flow through this pipe in [m3/s]"""
107
+ return self.A * self.v
108
+
109
+ @system_property
110
+ def Mf(self) -> float:
111
+ """:returns: the massflow through this pipe in [kg/s]"""
112
+ return self.density * self.Q
113
+
114
+ @system_property
115
+ def reynoldsNumber(self) -> float:
116
+ """:returns: the flow reynolds number"""
117
+ o = abs(self.density * self.v * self.D / self.viscosity)
118
+ return max(o, 1)
119
+
120
+ # Material Properties Exposure
121
+ @system_property
122
+ def density(self) -> float:
123
+ return self.material.density
124
+
125
+ @system_property
126
+ def viscosity(self) -> float:
127
+ return self.material.viscosity
128
+
129
+ @system_property
130
+ def enthalpy(self) -> float:
131
+ return self.material.enthalpy
132
+
133
+ @system_property
134
+ def T(self) -> float:
135
+ return self.material.T
136
+
137
+ @T.setter
138
+ def T(self, new_T):
139
+ self.material.T = new_T
140
+
141
+ @system_property
142
+ def P(self) -> float:
143
+ return self.material.P
144
+
145
+ @P.setter
146
+ def P(self, new_P):
147
+ self.material.P = new_P
148
+
149
+ @property
150
+ def dP_f(self):
151
+ """The loss of pressure in the pipe due to pressure"""
152
+ raise NotImplemented()
153
+
154
+ @property
155
+ def dP_p(self):
156
+ """The loss of pressure in the pipe due to potential"""
157
+ raise NotImplemented()
158
+
159
+ @property
160
+ def dP_tot(self):
161
+ raise NotImplemented()
162
+
163
+ @property
164
+ def Fvec(self):
165
+ """returns the fluidized vector for 1D CFD for continuity:0, and momentum:1"""
166
+ # TODO: add energy eq
167
+ # TODO: 1D CFD Pipe Network Solver :)
168
+ return [
169
+ self.density * self.v,
170
+ self.density * self.v**2.0 + self.P,
171
+ ] # TODO: include Txx
172
+
173
+
174
+ @forge
175
+ class Pipe(PipeFlow, Component):
176
+ node_s = Slot.define(PipeNode, default_ok=False)
177
+ node_e = Slot.define(PipeNode, default_ok=False)
178
+ roughness: float = attrs.field(default=0.0)
179
+ bend_radius: float = attrs.field(default=None)
180
+
181
+ straight_method = "Clamond"
182
+ laminar_method = "Schmidt laminar"
183
+ turbulent_method = "Schmidt turbulent"
184
+
185
+ def __on_init__(self):
186
+ self.node_s.add_segment(self)
187
+ self.node_e.add_segment(self)
188
+
189
+ @system_property
190
+ def Lx(self) -> float:
191
+ return self.node_e.x - self.node_s.x
192
+
193
+ @system_property
194
+ def Ly(self) -> float:
195
+ return self.node_e.y - self.node_s.y
196
+
197
+ @system_property
198
+ def Lz(self) -> float:
199
+ return self.node_e.z - self.node_s.z
200
+
201
+ @system_property
202
+ def Lhz(self) -> float:
203
+ """:returns: The length of pipe element in the XY plane"""
204
+ return numpy.sqrt(self.Lx**2.0 + self.Ly**2.0)
205
+
206
+ @system_property
207
+ def L(self) -> float:
208
+ """:returns: The absolute length of pipe element"""
209
+ return numpy.sqrt(self.Lx**2.0 + self.Ly**2.0 + self.Lz**2.0)
210
+
211
+ @system_property
212
+ def inclination(self) -> float:
213
+ """:returns: the inclination angle in degrees"""
214
+ return numpy.rad2deg(numpy.arctan2(self.Lz, self.Lhz))
215
+
216
+ @system_property
217
+ def friction_factor(self) -> float:
218
+ """The friction factor considering bend radius"""
219
+ if self.bend_radius is None:
220
+ re = self.reynoldsNumber
221
+ return fluids.friction.friction_factor(
222
+ re, self.roughness, Method=self.straight_method
223
+ )
224
+ else:
225
+ re = self.reynoldsNumber
226
+ dc = self.D * self.bend_radius
227
+ return fluids.friction.friction_factor_curved(
228
+ re,
229
+ self.D,
230
+ dc,
231
+ self.roughness,
232
+ laminar_method=self.laminar_method,
233
+ turbulent_method=self.turbulent_method,
234
+ )
235
+
236
+ @system_property
237
+ def Kpipe(self) -> float:
238
+ """The loss coeffient of this pipe section"""
239
+ return self.friction_factor * self.L / self.D
240
+
241
+ @system_property
242
+ def dP_f(self) -> float:
243
+ """The loss of pressure in the pipe due to pressure"""
244
+ return self.sign * self.density * self.v**2.0 * self.Kpipe / 2.0
245
+
246
+ @system_property
247
+ def dP_p(self) -> float:
248
+ """The loss of pressure in the pipe due to potential"""
249
+ return self.density * self.Lz * 9.81
250
+
251
+ @system_property
252
+ def dP_tot(self) -> float:
253
+ return self.dP_f + self.dP_p
254
+
255
+ @system_property
256
+ def sign(self) -> int:
257
+ return numpy.sign(self.v)
258
+
259
+
260
+ # Specalized Nodes
261
+ class FlowNode(PipeNode):
262
+ """Base For Boundary Condition Nodes of"""
263
+
264
+ @system_property
265
+ def dP_f(self) -> float:
266
+ return 0.0
267
+
268
+ @system_property
269
+ def dP_p(self) -> float:
270
+ return 0.0
271
+
272
+ @system_property
273
+ def dP_tot(self) -> float:
274
+ return 0.0
275
+
276
+
277
+ @forge
278
+ class PipeFitting(FlowNode, PipeFlow):
279
+ Kfitting = attr.ib(default=0.1, type=float)
280
+
281
+ @system_property
282
+ def dP_f(self) -> float:
283
+ """The loss of pressure in the pipe due to pressure"""
284
+ return self.density * self.v**2.0 * self.Kfitting / 2.0
285
+
286
+ @system_property
287
+ def dP_p(self) -> float:
288
+ """The loss of pressure in the pipe due to potential"""
289
+ return 0.0
290
+
291
+ @system_property
292
+ def dP_tot(self) -> float:
293
+ return self.dP_f + self.dP_p
294
+
295
+
296
+ # TODO: Add in fitting numbers:
297
+ # https://neutrium.net/fluid-flow/pressure-loss-from-fittings-excess-head-k-method/
298
+ # https://neutrium.net/fluid-flow/discharge-coefficient-for-nozzles-and-orifices/
299
+ """ Fitting Types K
300
+ 0 45° Elbow Standard (R/D = 1) 0.35
301
+ 1 45° Elbow Long Radius (R/D = 1.5) 0.20
302
+ 2 90° Elbow Curved Standard (R/D = 1) 0.75
303
+ 3 90° Elbow Curved Long Radius (R/D = 1.5) 0.45
304
+ 4 90° Elbow Square or Mitred NaN 1.30
305
+ 5 180° Bend Close Return 1.50
306
+ 6 Tee, Run Through Branch Blanked 0.40
307
+ 7 Tee, as Elbow Entering in run 1.00
308
+ 8 Tee, as Elbow Entering in branch 1.00
309
+ 9 Tee, Branching Flow NaN 1.00
310
+ 10 Coupling NaN 0.04
311
+ 11 Union NaN 0.04
312
+ 12 Gate valve Fully Open 0.17
313
+ 13 Gate valve 3/4 Open 0.90
314
+ 14 Gate valve 1/2 Open 4.50
315
+ 15 Gate valve 1/4 Open 24.00
316
+ 16 Diaphragm valve Fully Open 2.30
317
+ 17 Diaphragm valve 3/4 Open 2.60
318
+ 18 Diaphragm valve 1/2 Open 4.30
319
+ 19 Diaphragm valve 1/4 Open 21.00
320
+ 20 Globe valve, Bevel Seat Fully Open 6.00
321
+ 21 Globe valve, Bevel Seat 1/2 Open 9.50
322
+ 22 Globe Valve, Composition seat Fully Open 6.00
323
+ 23 Globe Valve, Composition seat 1/2 Open 8.50
324
+ 24 Plug disk Fully Open 9.00
325
+ 25 Plug disk 3/4 Open 13.00
326
+ 26 Plug disk 1/2 Open 36.00
327
+ 27 Plug disk 1/4 Open 112.00
328
+ 28 Angle valve Fully Open 2.00
329
+ 29 Y valve or blowoff valve Fully Open 3.00
330
+ 30 Plug cock \theta = 5° 0.05
331
+ 31 Plug cock \theta = 10° 0.29
332
+ 32 Plug cock \theta = 20° 1.56
333
+ 33 Plug cock \theta = 40° 17.30
334
+ 34 Plug cock \theta = 60° 206.00
335
+ 35 Butterfly valve \theta = 5° 0.24
336
+ 36 Butterfly valve \theta = 10° 0.52
337
+ 37 Butterfly valve \theta = 20° 1.54
338
+ 38 Butterfly valve \theta = 40° 10.80
339
+ 39 Butterfly valve \theta = 60° 118.00
340
+ 40 Check valve Swing 2.00
341
+ 41 Check valve Disk 10.00
342
+ 42 Check valve Ball 70.00
343
+ 43 Foot valve NaN 15.00
344
+ 44 Water meter Disk 7.00
345
+ 45 Water meter Piston 15.00
346
+ 46 Water meter Rotary (star-shaped disk) 10.00
347
+ 47 Water meter Turbine-wheel 6.00"""
348
+
349
+
350
+ #
351
+ @forge
352
+ class FlowInput(FlowNode):
353
+ flow_in: float = attrs.field(default=0.0)
354
+
355
+ @system_property
356
+ def sum_of_flows(self) -> float:
357
+ out = self.flow_in
358
+ for pipe_seg in self._segments:
359
+ if self is pipe_seg.node_s:
360
+ out -= pipe_seg.Q
361
+ elif self is pipe_seg.node_e:
362
+ out += pipe_seg.Q
363
+ return out
364
+
365
+
366
+ #
367
+ # class PressureInput(FlowNode):
368
+ # pressure_in: float = attrs.field()
369
+ #
370
+ # class PressureOut(FlowNode):
371
+ # pressure_out: float = attrs.field()
372
+
373
+
374
+ @forge
375
+ class Pump(Component):
376
+ """Simulates a pump with power input, max flow, and max pressure by assuming a flow characteristic"""
377
+
378
+ max_flow: float = attrs.field()
379
+ max_pressure: float = attrs.field()
380
+ # throttle: float
381
+
382
+ @property
383
+ def design_flow_curve(self):
384
+ """:returns: a tuple output of flow vector, and pressure vector"""
385
+ flow = numpy.linspace(0, self.max_flow)
386
+ return flow, self.max_pressure * (1 - (flow / self.max_flow) ** 2.0)
387
+
388
+ def dPressure(self, current_flow):
389
+ """The pressure the pump generates"""
390
+ flow, dP = self.design_flow_curve
391
+ assert current_flow >= 0, "Flow must be positive"
392
+ assert current_flow <= self.max_flow, "Flow must be less than max flow"
393
+ return numpy.interp(current_flow, flow, dP)
394
+
395
+ def power(self, current_flow):
396
+ """The power used considering in watts"""
397
+ return self.dPressure(current_flow) * current_flow
398
+
399
+
400
+ @forge
401
+ class PipeSystem(System):
402
+ in_node = Slot.define(PipeNode)
403
+ graph: nx.Graph
404
+ items: dict
405
+
406
+ def __on_init__(self):
407
+ self.items = {}
408
+ self.flow_solvers = {}
409
+ self.pipe_flow = {}
410
+ self.create_graph_from_pipe_or_node(self.in_node)
411
+ self.assemble_solvers()
412
+
413
+ def assemble_solvers(self):
414
+ for i, cycle in enumerate(nx.cycle_basis(self.graph)):
415
+ cycle_attr_name = f"_cycle_redisual_{i}"
416
+ pipes = []
417
+ self.info(f"found cycle: {cycle}")
418
+ for cs, cl in zip(cycle, cycle[1:] + [cycle[0]]):
419
+ pipe = self.graph.get_edge_data(cs, cl)["pipe"]
420
+ mult = 1
421
+ if cs == pipe.node_e.system_id:
422
+ # reverse
423
+ mult = -1
424
+ pipes.append((pipe, mult))
425
+
426
+ def res_func():
427
+ out = 0
428
+ for pipe, mult in pipes:
429
+ out += mult * pipe.dP_tot
430
+ return out / 1000.0
431
+
432
+ setattr(self, cycle_attr_name, res_func)
433
+ self.flow_solvers[cycle_attr_name] = Ref(self, cycle_attr_name)
434
+
435
+ bf = lambda kv: len(kv[1].segments)
436
+ for nid, node in sorted(self.nodes.items(), key=bf):
437
+ if len(node.segments) > 1:
438
+ self.flow_solvers[nid] = Ref(node, "sum_of_flows")
439
+ elif not isinstance(node, FlowInput):
440
+ self.info(f"deadend: {node.identity}")
441
+ # self.flow_solvers[nid] = Ref(node,'sum_of_flows')
442
+
443
+ for nid, node in self.nodes.items():
444
+ if isinstance(node, FlowInput):
445
+ self.flow_solvers[nid] = Ref(node, "sum_of_flows")
446
+
447
+ for pid, pipe in self.pipes.items():
448
+ self.pipe_flow[pid] = Ref(pipe, "v")
449
+
450
+ @property
451
+ def _X(self):
452
+ return {k: v for k, v in self.pipe_flow.items()}
453
+
454
+ @property
455
+ def _F(self):
456
+ return {k: v for k, v in self.flow_solvers.items()}
457
+
458
+ @instance_cached
459
+ def F_keyword_order(self):
460
+ """defines the order of inputs in ordered mode for calcF"""
461
+ return {i: k for i, k in enumerate(self.flow_solvers)}
462
+
463
+ @property
464
+ def nodes(self):
465
+ return {k: v for k, v in self.items.items() if isinstance(v, PipeNode)}
466
+
467
+ @property
468
+ def pipes(self):
469
+ return {k: v for k, v in self.items.items() if isinstance(v, Pipe)}
470
+
471
+ # Pipe System Composition
472
+ def add_to_graph(self, graph, node_or_pipe) -> str:
473
+ """recursively add node or pipe elements"""
474
+
475
+ idd = node_or_pipe.system_id
476
+
477
+ # Early Exit
478
+ if idd in self.items:
479
+ return idd
480
+
481
+ elif graph.has_node(idd):
482
+ return idd
483
+
484
+ log.info(f"adding {idd}")
485
+
486
+ if isinstance(node_or_pipe, PipeNode):
487
+ if not graph.has_node(idd):
488
+ graph.add_node(idd, sys_id=idd, node=node_or_pipe)
489
+ self.items[idd] = node_or_pipe
490
+
491
+ for seg in node_or_pipe.segments:
492
+ self.add_to_graph(graph, seg)
493
+
494
+ elif isinstance(node_or_pipe, Pipe):
495
+ nodes = node_or_pipe.node_s
496
+ nodee = node_or_pipe.node_e
497
+
498
+ if not graph.has_node(nodes.system_id):
499
+ nsid = self.add_to_graph(graph, nodes)
500
+ else:
501
+ nsid = nodes.system_id
502
+
503
+ if not graph.has_node(nodee.system_id):
504
+ neid = self.add_to_graph(graph, nodee)
505
+ else:
506
+ neid = nodee.system_id
507
+
508
+ if not graph.has_edge(nsid, neid):
509
+ graph.add_edge(nsid, neid, sys_id=idd, pipe=node_or_pipe)
510
+ self.items[idd] = node_or_pipe
511
+
512
+ else:
513
+ raise ValueError(f"not a node or pipe: {node_or_pipe}")
514
+
515
+ return idd
516
+
517
+ def create_graph_from_pipe_or_node(self, node_or_pipe) -> nx.Graph:
518
+ """Creates a networkx graph from a pipe or node"""
519
+
520
+ self.graph = nx.Graph()
521
+
522
+ if isinstance(node_or_pipe, PipeNode):
523
+ self.add_to_graph(self.graph, node_or_pipe)
524
+
525
+ elif isinstance(node_or_pipe, Pipe):
526
+ self.add_to_graph(self.graph, node_or_pipe)
527
+
528
+ else:
529
+ raise ValueError(f"not a node or pipe: {node_or_pipe}")
530
+
531
+ return self.graph
532
+
533
+ def draw(self):
534
+ try:
535
+ from pyvis.network import Network
536
+
537
+ net = Network(directed=False)
538
+
539
+ net.from_nx(self.graph)
540
+ net.show("process_graph.html")
541
+
542
+ except:
543
+ pos = pos = nx.spring_layout(self.graph)
544
+ nx.draw(self.graph, pos=pos)
545
+ labels = nx.draw_networkx_labels(self.graph, pos=pos)
546
+
547
+
548
+ # TODO: update compressibility:
549
+ #
550
+ # import control as ct
551
+ # import numpy as np
552
+ # from engforge.components import Component, forge
553
+ # from matplotlib.pylab import *
554
+ #
555
+ # DT_DFLT = 1E-3
556
+ # @forge(auto_attribs=True)
557
+ # class Accumulator(Component):
558
+ #
559
+ # Rt: float = 5000
560
+ # Pref: float = 1E6
561
+ # Lref: float = 5
562
+ # Aact: float = 5
563
+ # Cact: float = 0.1
564
+ # Mact: float = 0.1
565
+ # Pmax: float = 7E8
566
+ # tlast: float = 0
567
+ # Kwall: float = 1
568
+ #
569
+ # lim_marg: float = 0.01
570
+ # min_dt: float = DT_DFLT
571
+ #
572
+ # model: 'InputOutputSystem' = None
573
+ #
574
+ # discrete: bool = True
575
+ # pressure_mode: bool = False #false is flow based
576
+ #
577
+ # def __on_init__(self):
578
+ # #handle defined pressure mode
579
+ # if self.pressure_mode:
580
+ # if not self.discrete:
581
+ # self.model = ct.NonlinearIOSystem(self.pr_update_accumulator,self.pr_accumulator_output,inputs=('Pi',),outputs=('Pc','Qc','Pout'),states=('x','v'),name= self.name if self.name else 'accumul ator')
582
+ # else:
583
+ # self.model = ct.NonlinearIOSystem(self.pr_discrete_update_accumulator,self.pr_accumulator_output,inputs=('Pi',),outputs=('Pc','Qc','Pout'),states=('x','v'),name= self.name if self.name else 'accumulator',dt=True)
584
+ # else:
585
+ # if not self.discrete:
586
+ # self.model = ct.NonlinearIOSystem(self.w_update_accumulator,self.w_output,inputs=('Qc',),outputs=('Pc','Pout'),states=('x'),name= self.name if self.name else 'accumulator')
587
+ # else:
588
+ # self.model = ct.NonlinearIOSystem(self.w_discrete_update_accumulator, self.w_output,inputs=('Qc',),outputs=('Pc','Pout'),states=('x'),name= self.name if self.name else 'accumulator',dt=True)
589
+ #
590
+ # def w_update_accumulator(self,t,x,u,params):
591
+ # Rt = params.get('Rt',self.Rt) #Pa/(m3/s)
592
+ # Pref = params.get('Pref',self.Pref)
593
+ # Lref = params.get('Lref',self.Lref)
594
+ # Aact = params.get('Aact',self.Aact)
595
+ # Cact = params.get('Cact',self.Cact)
596
+ # Mact = params.get('Mact',self.Mact)
597
+ # Kwall = params.get('Kwall',self.Kwall)
598
+ # Pmax = params.get('Pmax',self.Pmax)
599
+ #
600
+ # qin = u[0]
601
+ #
602
+ # xact = x[0]
603
+ # #Pi = x[0]
604
+ #
605
+ # if self.discrete and isinstance(self.model.dt,float):
606
+ # self.dt = dt = self.model.dt
607
+ #
608
+ # else: #infer dt
609
+ # if t >= self.tlast+self.min_dt:
610
+ # self.dt = dt = max(abs(t - self.tlast),self.min_dt)
611
+ # self.tlast = t
612
+ # else:
613
+ # self.dt = dt = getattr(self,'dt',self.min_dt)
614
+ #
615
+ # #determine rate of x change (positive compresses accumulator (x->0))
616
+ # xdot = -qin/Aact
617
+ #
618
+ # xmin = Pref*Lref/Pmax
619
+ #
620
+ # #limit xdot to prevent over/under shoot
621
+ # if xact + dt * xdot > Lref*(1-self.lim_marg):
622
+ # xdot = (Lref-xact)/dt
623
+ # elif xact + dt * xdot < xmin:
624
+ # xdot = (xmin-xact)/dt
625
+ #
626
+ # return xdot
627
+ #
628
+ # def w_discrete_update_accumulator(self,t,x,u,params):
629
+ # v = self.w_update_accumulator(t,x,u,params)
630
+ # dt = self.model.dt
631
+ #
632
+ # if self.discrete and isinstance(self.model.dt,float):
633
+ # dt = self.model.dt
634
+ # else:
635
+ # dt = self.dt
636
+ #
637
+ # xnew= x[0] + v*dt
638
+ #
639
+ # return xnew
640
+ #
641
+ # def w_output(self,t,x,u,params):
642
+ # Rt = params.get('Rt',self.Rt) #Pa/(m3/s)
643
+ # Pref = params.get('Pref',self.Pref)
644
+ # Lref = params.get('Lref',self.Lref)
645
+ # Aact = params.get('Aact',self.Aact)
646
+ # Cact = params.get('Cact',self.Cact)
647
+ # Mact = params.get('Mact',self.Mact)
648
+ # Kwall = params.get('Kwall',self.Kwall)
649
+ # Pmax = params.get('Pmax',self.Pmax)
650
+ #
651
+ # qin = u[0]
652
+ #
653
+ # xact = x[0]
654
+ #
655
+ # xmin = Pref*Lref/Pmax
656
+ #
657
+ # Pcomp = Pref*Lref / max(xmin,xact)
658
+ #
659
+ # #junction pressure is higher than pcomp when accumulator is compressed
660
+ # Pi = Pcomp + qin*Rt
661
+ #
662
+ # return [Pcomp,Pi]
663
+ #
664
+ #
665
+ #
666
+ #
667
+ # def pr_update_accumulator(self,t,x,u,params):
668
+ # """takes the current accumulator position, external pressure, and accumulator constants to determine the rate of change of accumulator position"""
669
+ # Rt = params.get('Rt',self.Rt) #Pa/(m3/s)
670
+ # Pref = params.get('Pref',self.Pref)
671
+ # Lref = params.get('Lref',self.Lref)
672
+ # Aact = params.get('Aact',self.Aact)
673
+ # Cact = params.get('Cact',self.Cact)
674
+ # Mact = params.get('Mact',self.Mact)
675
+ # Kwall = params.get('Kwall',self.Kwall)
676
+ # Pmax = params.get('Pmax',self.Pmax)
677
+ #
678
+ # Pi = u[0]
679
+ # #print(x,u)
680
+ # xact = x[0]
681
+ # v = x[1]
682
+ #
683
+ # if self.discrete and isinstance(self.model.dt,float):
684
+ # self.dt = dt = self.model.dt
685
+ #
686
+ # else: #infer dt
687
+ # if t >= self.tlast+self.min_dt:
688
+ # self.dt = dt = max(abs(t - self.tlast),self.min_dt)
689
+ # self.tlast = t
690
+ # else:
691
+ # self.dt = dt = getattr(self,'dt',self.min_dt)
692
+ #
693
+ # xmin = Pref*Lref/Pmax
694
+ #
695
+ # Pcomp = Pref*Lref / max(max(xmin,xact),Lref)
696
+ # dP = Pcomp-Pi
697
+ # sgn = np.sign(dP)
698
+ #
699
+ # #FIXME: remove since dP is based on equillibritum
700
+ # #xeq = max(min(Pref*Lref/Pi,Lref),xmin)
701
+ # #K = 0*self.Kwall*(xeq-xact)/Lref
702
+ # K = 0
703
+ #
704
+ # xdot = sgn * ( abs(dP) / ( Rt * Aact**2.))**0.5/Lref
705
+ # dvdt = ((xdot-v) - K)/Mact - Cact*Aact*v*abs(v)/Mact
706
+ #
707
+ # x_proj = v * dt + xact
708
+ #
709
+ # #Consider projected limits
710
+ # if x_proj >= Lref*(1-self.lim_marg):
711
+ # vin = v
712
+ # v = (Lref-xact)/dt#,-1*Lref/10/dt)
713
+ # dvdt = (v-vin)/dt
714
+ #
715
+ # elif x_proj <= xmin:
716
+ # vin = v
717
+ # v = (0 - xact)/dt
718
+ # dvdt = (v-vin)/dt
719
+ #
720
+ # return v,dvdt
721
+ #
722
+ # def pr_discrete_update_accumulator(self,t,x,u,params):
723
+ # v,dvdt = self.pr_update_accumulator(t,x,u,params)
724
+ # dt = self.model.dt
725
+ #
726
+ # if self.discrete and isinstance(self.model.dt,float):
727
+ # dt = self.model.dt
728
+ # else:
729
+ # dt = self.dt
730
+ #
731
+ # xnew= x[0] + v*dt
732
+ # vnew = x[1] + dvdt*dt
733
+ #
734
+ # return xnew,vnew
735
+ #
736
+ # def pr_accumulator_output(self,t,x,u,params):
737
+ # """returns accumulator pressure and flow (in positive)"""
738
+ # Rt = params.get('Rt',self.Rt) #Pa/(m3/s)
739
+ # Pref = params.get('Pref',self.Pref)
740
+ # Lref = params.get('Lref',self.Lref)
741
+ # Aact = params.get('Aact',self.Aact)
742
+ # Cact = params.get('Cact',self.Cact)
743
+ # Mact = params.get('Mact',self.Mact)
744
+ # Pmax = params.get('Pmax',self.Pmax)
745
+ # #dt = params.get('dt',self.dt)
746
+ #
747
+ # Pi = u[0]
748
+ # xact = x[0]
749
+ # v = x[1]
750
+ #
751
+ # xmin = Pref*Lref/Pmax
752
+ # Pcomp = Pref*Lref / max(xmin,xact)
753
+ # Q = v*Aact
754
+ # return [Pcomp,Q,Pi]
755
+ #
756
+ # @forge(auto_attribs=True)
757
+ # class Motor(Component):
758
+ # """Nonlinear motor with parasitics with fixed displacement"""
759
+ #
760
+ # I: float = 1
761
+ # D: float = 1E-6
762
+ # Cd: float = 0.0
763
+ # Ckloss: float = 0
764
+ # f: float = 0.0
765
+ # N: float = 1000
766
+ #
767
+ # min_dt: float = DT_DFLT
768
+ # model: 'InputOutputSystem' = None
769
+ # discrete: bool = True
770
+ # tlast = 0
771
+ #
772
+ # def __on_init__(self):
773
+ # #print(self.name)
774
+ # if not self.discrete:
775
+ # self.model = ct.NonlinearIOSystem(self.update_motor, self.motor_output, inputs=('P1','P2'), outputs=('Trq','Q','Pwr','Pin','Pout'), state=('w',), name=self.name if self.name else 'motor')
776
+ # else:
777
+ # self.model = ct.NonlinearIOSystem(self.discrete_update_motor, self.motor_output, inputs=('P1','P2'), outputs=('Trq','Q','Pwr','Pin','Pout'), state=('w',), name=self.name if self.name else 'motor',dt=True)
778
+ # self.tlast = 0
779
+ #
780
+ # def update_motor(self,t,x,u,params):
781
+ # """updates the motor acceleration based on pressure differential and nonlinear parasitics"""
782
+ # I = params.get('I',self.I)
783
+ # D = params.get('D',self.D)
784
+ # Cd = params.get('Cd',self.Cd)
785
+ # Ckloss = params.get('Ckloss',self.Ckloss)
786
+ # f = params.get('f',self.f)
787
+ # N = params.get('N',self.N)
788
+ #
789
+ # P1 = u[0]
790
+ # P2 = u[1]
791
+ # w = x[0]
792
+ #
793
+ # Q = D*(w/(2*3.14159))
794
+ # dPq = np.sign(Q)*Ckloss*Q**2
795
+ #
796
+ # dw_torque = (D/(2*np.pi))* ((P1 - P2)-dPq)
797
+ # dw_friction = w*f*N
798
+ # dw_drag = w*abs(w)*Cd
799
+ #
800
+ # return (dw_torque - dw_friction - dw_drag)/I
801
+ #
802
+ # def discrete_update_motor(self,t,x,u,params):
803
+ # dw = self.update_motor(t,x,u,params)
804
+ #
805
+ #
806
+ # if self.discrete and isinstance(self.model.dt,float):
807
+ # self.dt = dt = self.model.dt
808
+ #
809
+ # else: #infer dt
810
+ # if t >= self.tlast+self.min_dt:
811
+ # self.dt = dt = max(abs(t - self.tlast),self.min_dt)
812
+ # self.tlast = t
813
+ # else:
814
+ # self.dt = dt = getattr(self,'dt',self.min_dt)
815
+ #
816
+ # return x[0] + dw*self.dt
817
+ #
818
+ # def motor_output(self,t,x,u,params):
819
+ # """calculates torque, flow and power output"""
820
+ # I = params.get('I',self.I)
821
+ # D = params.get('D',self.D)
822
+ # Cd = params.get('Cd',self.Cd)
823
+ # Ckloss = params.get('Ckloss',self.Ckloss)
824
+ # f = params.get('f',self.f)
825
+ # N = params.get('N',self.N)
826
+ #
827
+ # P1 = u[0]
828
+ # P2 = u[1]
829
+ # w = x[0]
830
+ #
831
+ # Q = (w/2*3.14159)*D
832
+ # dPq = np.sign(Q)*Ckloss*Q**2
833
+ #
834
+ # dP = (P1 - P2) - dPq
835
+ #
836
+ # Trq = D * dP / (2*3.14159)
837
+ # Pwr = dP * Q
838
+ # return [Trq, Q, Pwr,P1,P2]
839
+ #
840
+ #
841
+ # @forge(auto_attribs=True)
842
+ # class Actuator(Component):
843
+ # Ap: float = 1
844
+ # mp: float = 0.5
845
+ # bp: float = 0.1
846
+ # Kwall: float = 1000
847
+ # Lstroke: float = 1
848
+ #
849
+ # min_dt = DT_DFLT
850
+ # lim_marg = 0.01
851
+ #
852
+ # lim = None # true if high, false if low and none otherwise
853
+ #
854
+ # model: 'InterConnectedSystem' = None
855
+ #
856
+ # discrete: bool = True
857
+ #
858
+ # tlast = 0
859
+ #
860
+ # def __on_init__(self):
861
+ # if self.discrete:
862
+ # self.model = ct.NonlinearIOSystem(self.discrete_update_fnc,self.output_fnc,state=('x','v'),outputs=('Q1','Q2','P1','P2'),inputs=('F','P1','P2'),dt=True,name=self.name if self.name else 'actuator')
863
+ #
864
+ # else:
865
+ # self.model = ct.NonlinearIOSystem(self.update_fnc,self.output_fnc,state=('x','v'),outputs=('Q1','Q2','P1','P2'),inputs=('F','P1','P2'),name=self.name if self.name else 'actuator')
866
+ #
867
+ #
868
+ #
869
+ # def update_fnc(self,t,x,u,params):
870
+ # Ap = params.get('Ap',self.Ap)
871
+ # mp = params.get('mp',self.mp)
872
+ # bp = params.get('bp',self.bp)
873
+ # Kwall = params.get('Kwall',self.Kwall)
874
+ # Lstroke = params.get('Lstroke',self.Lstroke)
875
+ #
876
+ # xpos = x[0]
877
+ # v = x[1]
878
+ #
879
+ # f = u[0]
880
+ # p1 = u[1]
881
+ # p2 = u[2]
882
+ #
883
+ # if self.discrete and isinstance(self.model.dt,float):
884
+ # dt = self.model.dt
885
+ # self.dt = dt
886
+ #
887
+ # else: #infer dt
888
+ # if t >= self.tlast+self.min_dt:
889
+ # self.dt = dt = max(abs(t - self.tlast),self.min_dt)
890
+ # self.tlast = t
891
+ # else:
892
+ # self.dt = dt = getattr(self,'dt',self.min_dt)
893
+ #
894
+ # if xpos > Lstroke:
895
+ # #print('kpos')
896
+ # K = self.Kwall*(xpos-Lstroke)
897
+ # elif xpos < 0:
898
+ # #print('kneg')
899
+ # K = self.Kwall*(xpos - 0)
900
+ # else:
901
+ # K = 0
902
+ #
903
+ # daf = f - (p1-p2)*Ap
904
+ # dad = bp*v*abs(v)
905
+ # dvdt = (daf - dad - K*daf)/mp
906
+ #
907
+ # x_proj = v * dt + xpos
908
+ #
909
+ # if x_proj >= Lstroke*(1-self.lim_marg):
910
+ # vin = v
911
+ # v = (Lstroke-xpos)/dt
912
+ # dvdt = min((v-vin)/dt,dvdt)
913
+ #
914
+ # elif x_proj <= self.lim_marg:
915
+ # vin = v
916
+ # v = (0 - xpos)/dt
917
+ # dvdt = max((v-vin)/dt,dvdt)
918
+ #
919
+ #
920
+ # return [v,dvdt]
921
+ #
922
+ # def discrete_update_fnc(self,t,x,u,params):
923
+ # v,dvdt = self.update_fnc(t,x,u,params)
924
+ # if self.discrete and isinstance(self.model.dt,float):
925
+ # dt = self.model.dt
926
+ # else:
927
+ # dt = self.dt
928
+ #
929
+ # if hasattr(self,'last_a'):
930
+ # dvdt = (self.last_a + dvdt)/2
931
+ # v = (self.last_v + v)/2
932
+ #
933
+ # vnew = v + dvdt*dt
934
+ # newx = x[0] + v*dt
935
+ #
936
+ # if newx < 0:
937
+ # newx = 0
938
+ # if vnew < 0:
939
+ # vnew = 0
940
+ #
941
+ # elif newx > self.Lstroke:
942
+ # newx = self.Lstroke
943
+ # if vnew > 0:
944
+ # vnew = 0
945
+ #
946
+ # self.last_x = newx
947
+ # self.last_v = vnew
948
+ # self.last_a = dvdt
949
+ #
950
+ # return newx, vnew
951
+ #
952
+ # def output_fnc(self,t,x,u,params):
953
+ # Ap = params.get('Ap',self.Ap)
954
+ #
955
+ # v = x[1]
956
+ #
957
+ # Q1 = -1*v * Ap
958
+ # Q2 = v * Ap
959
+ #
960
+ # return [Q1,Q2,u[1],u[2]]
961
+ #
962
+ #
963
+ # @forge(auto_attribs=True)
964
+ # class Pipe(Component):
965
+ # '''models pressure change and flow Q from nodes with pressure P1->P2'''
966
+ # Kp:float = 1 #TODO: better friction model
967
+ #
968
+ # flow_input: bool = False
969
+ # model: 'InputOutputSystem' = None
970
+ #
971
+ # def __on_init__(self):
972
+ # if not self.flow_input:
973
+ # self.model = ct.NonlinearIOSystem(self.pipe_flow,self.pipe_flow_output,state=('Q'),inputs=('P1','P2'),outputs=('P1o','P2o','Q'),dt=True,name=self.name if self.name else 'pipe')
974
+ # else:
975
+ # self.model = ct.NonlinearIOSystem(self.pipe_pressure,self.pipe_pressure_output,state=('P2'),inputs=('P1','Q'),outputs=('P1o','P2','Q'),dt=True,name=self.name if self.name else 'pipe')
976
+ #
977
+ # def pipe_flow(self,t,x,u,params):
978
+ # Kp = params.get('Kp',self.Kp)
979
+ # p1 = u[0]
980
+ # p2 = u[1]
981
+ # dP = (p2-p1)
982
+ # return -1*np.sign(dP)*(abs(dP)/Kp)**0.5
983
+ #
984
+ # def pipe_pressure(self,t,x,u,params):
985
+ # Kp = params.get('Kp',self.Kp)
986
+ # p1 = u[0]
987
+ # q = u[1]
988
+ # return -1*np.sign(q)*Kp*q**2 + p1
989
+ #
990
+ # def pipe_flow_output(self,t,x,u,params):
991
+ # return [u[0],u[1],x[0]]
992
+ #
993
+ # def pipe_pressure_output(self,t,x,u,params):
994
+ # return [u[0],x[0],u[1]]
995
+ #
996
+ #
997
+ # @forge(auto_attribs=True)
998
+ # class Valve(Component):
999
+ # Ao: float = 0.1 #area
1000
+ # ts: float = 30/1000. #seconds
1001
+ # rho: float = 1000
1002
+ # Cd: float = 0.1
1003
+ # discrete_control: bool = True # contorl signal is zero or one, vs a goal
1004
+ #
1005
+ # tlast = 0
1006
+ # min_dt:float = 1E-3
1007
+ #
1008
+ # def update_alpha(self,t,x,u,params):
1009
+ # ts = params.get('ts',self.ts)
1010
+ # uctl = u[0]
1011
+ # alpha = x[0]
1012
+ #
1013
+ # if t >= self.tlast+self.min_dt:
1014
+ # self.dt = dt = max(abs(t - self.tlast),self.min_dt)
1015
+ # self.tlast = t
1016
+ # else:
1017
+ # self.dt = dt = getattr(self,'dt',self.min_dt)
1018
+ #
1019
+ # dA = 1.0 / ts
1020
+ #
1021
+ # if self.discrete_control:
1022
+ # if uctl==1:
1023
+ # if alpha < 1-dA*dt:
1024
+ # return dA
1025
+ # else:
1026
+ # return 1-alpha
1027
+ # elif uctl==0:
1028
+ # if alpha > dA*dt:
1029
+ # return dA
1030
+ # else:
1031
+ # return 0-alpha
1032
+ # else:
1033
+ # vctl = min(max(uctl,0),1)
1034
+ # d = (uctl-alpha)/dt
1035
+ # dv = max(min(d,dA),-dA)
1036
+ # return dv
1037
+ #
1038
+ #
1039
+ # def get_flow(self,t,x,u,params):
1040
+ # rho = params.get('rho',self.rho)
1041
+ # ts = params.get('ts',self.ts)
1042
+ # Cd = params.get('Cd',self.Cd)
1043
+ # Ao = params.get('Ao',self.Ao)
1044
+ #
1045
+ # alpha = x[0]
1046
+ # if alpha < 0.05:
1047
+ # return 0.0 #avoid divide by zero in area
1048
+ #
1049
+ # dP = u[1]
1050
+ # Av = Ao*min(max(alpha,0),1)
1051
+ # return np.sign(dP)*Cd*Av*((2/rho)*abs(dP))**0.5
1052
+ #
1053
+ # @forge(auto_attribs=True)
1054
+ # class PipeJunction(Component):
1055
+ #
1056
+ # n_pipes: int = 2
1057
+ # Vi: float = 1
1058
+ # B: float = 300E3
1059
+ #
1060
+ # min_dt = DT_DFLT
1061
+ # model: 'InputOutputSystem' = None
1062
+ # discrete:bool = True
1063
+ #
1064
+ # def __on_init__(self):
1065
+ # state = ('P',)
1066
+ # inputs = tuple(f'Q{i+1}' for i in range(self.n_pipes))
1067
+ # outins = tuple(f'Qout{i+1}' for i in range(self.n_pipes))
1068
+ # outputs = tuple(list(state)+list(outins)+['dQ'])
1069
+ # if self.discrete:
1070
+ # self.model = ct.NonlinearIOSystem(self.discrete_pressure_update,self.output,inputs=inputs,outputs=outputs,state=state,dt=True,name=self.name if self.name else 'junction')
1071
+ # self.tlast = 0
1072
+ # else:
1073
+ # self.model = ct.NonlinearIOSystem(self.pressure_update,self.output,inputs=inputs,outputs=outputs,state=state,name=self.name if self.name else 'junction')
1074
+ #
1075
+ # def pressure_update(self,t,x,u,params):
1076
+ # #TODO: update compressibility from temp / pressure ect.
1077
+ # B = params.get('B',self.B)
1078
+ # Vi = params.get('Vi',self.Vi)
1079
+ # sumQ = sum(u)
1080
+ # dPdt = (B/Vi)*sumQ
1081
+ # return dPdt
1082
+ #
1083
+ # def discrete_pressure_update(self,t,x,u,params):
1084
+ # dPdt = self.pressure_update(t,x,u,params)
1085
+ # if self.discrete and isinstance(self.model.dt,float):
1086
+ # dt = self.model.dt
1087
+ # self.dt = dt
1088
+ #
1089
+ # else: #infer dt
1090
+ # if t >= self.tlast+self.min_dt:
1091
+ # self.dt = dt = max(abs(t - self.tlast),self.min_dt)
1092
+ # self.tlast = t
1093
+ # else:
1094
+ # self.dt = dt = getattr(self,'dt',self.min_dt)
1095
+ # return self.dt*dPdt + x[0]
1096
+ #
1097
+ # def output(self,t,x,u,parms):
1098
+ # return tuple([x[0]]+list(u)+[sum(u)])
1099
+ #
1100
+
1101
+
1102
+ if __name__ == "__main__":
1103
+ N = 10
1104
+
1105
+ rho = 1000.0
1106
+ f = 0.015
1107
+ L = 1.0
1108
+ Po = 1e5
1109
+
1110
+ A = 1.0 * ones(N)
1111
+ u = 0.0 * ones(N)
1112
+ p = Po * ones(N)
1113
+
1114
+ Pin = 1.1e5
1115
+ Uin = lambda Ps: sqrt((Pin - Ps) / (rho * 2.0))
1116
+
1117
+ def F(i):
1118
+ ui = u[i]
1119
+ ps = p[i]
1120
+ F1 = rho * ui
1121
+ F2 = F1 * ui + ps
1122
+ return F1, F2
1123
+
1124
+ def J(i):
1125
+ return 0.0, -rho * f
1126
+
1127
+ def decodeF(F1, F2):
1128
+ up = F1 / rho
1129
+ pp = F2 - rho * up**2.0
1130
+ return up, pp
1131
+
1132
+ plast = p.copy()
1133
+ ulast = u.copy()
1134
+ it = 0
1135
+ started = False
1136
+ while not started or (plast - p).mean() > 1e-3 or (ulast - u).mean() > 1e-3:
1137
+ started = True
1138
+
1139
+ plast = p.copy()
1140
+ ulast = u.copy()
1141
+
1142
+ for i in range(N):
1143
+ if i == 0: # inlet
1144
+ ps = p[i]
1145
+ ui = Uin(ps)
1146
+ u[i] = ui
1147
+
1148
+ # STATUS
1149
+ F1, F2 = F(i)
1150
+ J1, J2 = J(i)
1151
+
1152
+ # PREDICT i+1
1153
+ dF1, dF2 = J1, J2
1154
+ Fp1, Fp2 = F1 + dF1 * L, F2 + dF2 * L
1155
+
1156
+ up1, pp1 = decodeF(Fp1, Fp2)
1157
+
1158
+ # CORRECTOR i+1
1159
+ Jp1, Jp2 = 0.0, -rho * f
1160
+ dpF1, dpF2 = Jp1, Jp2
1161
+
1162
+ # avgGradients
1163
+ dF1n, dF2n = 0.5 * (dpF1 + dF1), 0.5 * (dpF2 + dF2)
1164
+
1165
+ # Calculate i+1 actual
1166
+ Fn1, Fn2 = F1 + dF1n * L, F2 + dF2n * L
1167
+
1168
+ un, pn = decodeF(Fn1, Fn2)
1169
+ if N - 1 > i:
1170
+ u[i + 1] = un
1171
+ p[i + 1] = pn
1172
+
1173
+ for i in reversed(range(N)):
1174
+ # if i == 0: #inlet
1175
+ # ps = p[i]
1176
+ # ui = Uin(ps)
1177
+ # u[i] = ui
1178
+
1179
+ if i == N - 1:
1180
+ p[i] = Po
1181
+
1182
+ # STATUS
1183
+ F1, F2 = F(i)
1184
+ J1, J2 = J(i)
1185
+
1186
+ # PREDICT i+1
1187
+ dF1, dF2 = J1, J2
1188
+ Fp1, Fp2 = F1 + dF1 * L, F2 + dF2 * L
1189
+
1190
+ up1, pp1 = decodeF(Fp1, Fp2)
1191
+
1192
+ # CORRECTOR i+1
1193
+ Jp1, Jp2 = 0.0, -rho * f
1194
+ dpF1, dpF2 = Jp1, Jp2
1195
+
1196
+ # avgGradients
1197
+ dF1n, dF2n = 0.5 * (dpF1 + dF1), 0.5 * (dpF2 + dF2)
1198
+
1199
+ # Calculate i+1 actual
1200
+ Fn1, Fn2 = F1 + dF1n * L, F2 + dF2n * L
1201
+
1202
+ un, pn = decodeF(Fn1, Fn2)
1203
+ if N - 1 > i:
1204
+ u[i - 1] = un
1205
+ p[i - 1] = pn
1206
+
1207
+ print(u)
1208
+ print(p)
1209
+ it += 1
1210
+ if it >= 2:
1211
+ break