STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_property_call_discipline.py
Go to the documentation of this file.
1"""The one rule Phase 00 exists to enforce: no ``PropsSI`` in library code.
2
3CoolProp offers three ways to ask the same question. Measured on the development
4host, nitrogen density at a fixed state point:
5
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=========================================== ========== ============
13
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.
19
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.
25
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.
29"""
30
31from __future__ import annotations
32
33import re
34from pathlib import Path
35
36import pytest
37
38PACKAGE_ROOT = Path(__file__).resolve().parents[1] / "feedtwin"
39
40#: The convenience wrappers. Each rebuilds its backend per call.
41#:
42#: Matched on a word boundary so ``_PropsSI_is_banned`` or a mention inside a
43#: sentence in a docstring does not trip the check -- only something that reads
44#: as a call does.
45BANNED = re.compile(r"\b(PropsSI|Props1SI|HAPropsSI|PhaseSI)\s*\‍(")
46
47GUIDANCE = (
48 "Use a cached feedtwin.props.Fluid (CoolProp AbstractState, BICUBIC&HEOS) "
49 "instead -- it is ~1300x faster per call. See this module's docstring."
50)
51
52
53def _library_sources() -> list[Path]:
54 return sorted(PACKAGE_ROOT.rglob("*.py"))
55
56
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}"
60 assert _library_sources(), f"no Python sources found under {PACKAGE_ROOT}"
61
62
63@pytest.mark.parametrize("source", _library_sources(), ids=lambda p: p.name)
64def test_no_propssi_in_library_code(source: Path) -> None:
65 """No convenience CoolProp call anywhere under ``feedtwin/``."""
66 offenders = [
67 (n, line.strip())
68 for n, line in enumerate(source.read_text().splitlines(), start=1)
69 if BANNED.search(line)
70 ]
71
72 if offenders:
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}")