1"""The one rule Phase 00 exists to enforce: no ``PropsSI`` in library code.
3CoolProp offers three ways to ask the same question. Measured on the development
4host, nitrogen density at a fixed state point:
6=========================================== ========== ============
7call path per call throughput
8=========================================== ========== ============
9``PropsSI('D', 'T', t, 'P', p, 'Nitrogen')`` 184.5 us 5.4 k/s
10``AbstractState('HEOS', ...)``, reused 3.18 us 315 k/s
11``AbstractState('BICUBIC&HEOS', ...)`` 0.14 us 7.1 M/s
12=========================================== ========== ============
14That is a 1300x spread across three spellings of one line. ``PropsSI`` parses
15its fluid name and rebuilds the backend on *every call*; the low-level interface
16does that once. A stiff transient on a sixty-node network evaluates properties
17millions of times over Jacobian assembly, so the choice is the difference
18between a run that finishes in seconds and one that finishes overnight.
20The failure mode this guards against is not someone deciding ``PropsSI`` is
21fine. It is someone reaching for the convenient one-liner while debugging, in a
22branch that only runs on the cold path, and it never being noticed -- because
23nothing about it is wrong except how long it takes, and the profile of a slow
24solve rarely names the line that caused it.
26Tests are exempt. Comparing the two paths is exactly how the numbers above were
27obtained, and the property layer's regression suite (Phase 01) will want the
28reference implementation to check the tabulated one against.
31from __future__
import annotations
34from pathlib
import Path
38PACKAGE_ROOT = Path(__file__).resolve().parents[1] /
"feedtwin"
45BANNED = re.compile(
r"\b(PropsSI|Props1SI|HAPropsSI|PhaseSI)\s*\(")
48 "Use a cached feedtwin.props.Fluid (CoolProp AbstractState, BICUBIC&HEOS) "
49 "instead -- it is ~1300x faster per call. See this module's docstring."
54 return sorted(PACKAGE_ROOT.rglob(
"*.py"))
58 """A moved package would make the scan below silently vacuous."""
59 assert PACKAGE_ROOT.is_dir(), f
"expected the feedtwin package at {PACKAGE_ROOT}"
63@pytest.mark.parametrize("source", _library_sources(), ids=
lambda p: p.name)
65 """No convenience CoolProp call anywhere under ``feedtwin/``."""
68 for n, line
in enumerate(source.read_text().splitlines(), start=1)
69 if BANNED.search(line)
73 listed =
"\n".join(f
" {source.name}:{n} {text}" for n, text
in offenders)
74 pytest.fail(f
"banned CoolProp convenience call:\n{listed}\n\n{GUIDANCE}")
None test_package_root_exists()
list[Path] _library_sources()
None test_no_propssi_in_library_code(Path source)