STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_measured_off_curve.py
Go to the documentation of this file.
1"""A measured component whose flow-bench sweep does not start at zero.
2
3Flow benches rarely measure at zero flow, and a Newton solve starts every
4branch at 1 g/s and probes past the top of any sweep. A measurement that
5brackets the operating point must solve, the excursions off its ends are
6continued (``dp ~ mdot^2``, the law the curve measures) and flagged, and a solve
7asked not to raise never raises.
8"""
9
10from __future__ import annotations
11
12import pytest
13
14from feedtwin.comps import build_component
15from feedtwin.model import ComponentInstance, Param, Provenance
16from feedtwin.model.curve import Curve
17from feedtwin.solve.network import Network
18from feedtwin.solve.steady import solve_steady
19
20PSI = 6894.757293168361
21BENCH = Curve(
22 x=(0.05, 0.1, 0.2),
23 y=(2.0, 8.0, 32.0),
24 x_unit="kg/s",
25 y_unit="psi",
26 source=Provenance.MEASURED,
27)
28
29
30def _measured(tag: str) -> object:
31 return build_component(
32 ComponentInstance.build(
33 tag, "valve", {}, curves={"dp_mdot": BENCH}, model="measured"
34 )
35 )
36
37
38def _ethanol(net: Network, *nodes: tuple[str, float | None]) -> None:
39 for node_id, psia in nodes:
40 net.add_node(
41 node_id, "ethanol", 293.0, pressure=None if psia is None else psia * PSI
42 )
43 net.nodes[node_id].phase = "liquid"
44
45
47 """20 psi across a sweep of 2-32 psi over 0.05-0.2 kg/s: inside the curve
48 (8 psi at 0.1, 32 at 0.2, so about 0.158 kg/s). The first iterate at
49 1 g/s used to raise off the bottom of the curve and fail the solve."""
50 net = Network()
51 _ethanol(net, ("A", 520.0), ("B", 500.0))
52 net.add_branch("M", _measured("M"), "A", "B") # type: ignore[arg-type]
53 result = solve_steady(net)
54 assert result.converged
55 flow = result.flows["M"]
56 assert 0.1 < flow < 0.2
57 assert BENCH(flow) / PSI == pytest.approx(20.0, rel=1e-6)
58 assert result.diagnostics["M"]["extrapolated"] == 0.0
59
60
62 """A capped stub is back-filled at exactly zero flow, off the curve."""
63 net = Network()
64 _ethanol(net, ("A", 520.0), ("D", 500.0), ("S", None))
65 pipe = build_component(
66 ComponentInstance.build(
67 "L",
68 "pipe",
69 {
70 "length": Param(1.0, "m", Provenance.MEASURED, ""),
71 "bore": Param(9.5, "mm", Provenance.MEASURED, ""),
72 },
73 )
74 )
75 net.add_branch("L", pipe, "A", "D") # type: ignore[arg-type]
76 net.add_branch("M", _measured("M"), "A", "S") # type: ignore[arg-type]
77 result = solve_steady(net, raise_on_failure=False)
78 assert result.converged
79 assert result.flows["M"] == 0.0
80 assert result.pressures["S"] == pytest.approx(result.pressures["A"])
81
82
84 """Above the sweep the loss keeps its measured ``mdot^2`` shape, and the
85 diagnostics say the point was not measured."""
86 element = _measured("M")
87 net = Network()
88 _ethanol(net, ("A", 600.0))
89 flow = net.conditions("A", 600.0 * PSI)
90 top = BENCH(0.2)
91 assert element.pressure_drop(0.4, flow) == pytest.approx(4.0 * top) # type: ignore[attr-defined]
92 assert element.pressure_drop(0.025, flow) == pytest.approx(BENCH(0.05) / 4.0) # type: ignore[attr-defined]
93 assert element.pressure_drop(0.0, flow) == 0.0 # type: ignore[attr-defined]
94 assert element.diagnostics(0.4, flow)["extrapolated"] == 1.0 # type: ignore[attr-defined]
95 assert element.diagnostics(0.1, flow)["extrapolated"] == 0.0 # type: ignore[attr-defined]
None _ethanol(Network net, *tuple[str, float|None] nodes)
None test_a_capped_measured_stub_back_fills_without_raising()
None test_off_the_ends_is_continued_quadratically_and_flagged()