CoolProp 8.0.0
An open-source fluid property and humid air property database
SatBoundaryFactory.h
Go to the documentation of this file.
1#ifndef COOLPROP_SBTL_SAT_BOUNDARY_FACTORY_H
2#define COOLPROP_SBTL_SAT_BOUNDARY_FACTORY_H
3
4#include <memory>
5#include <optional>
6#include <vector>
7
11
12namespace CoolProp {
13namespace sbtl {
14
15// Thermodynamics-aware factories that turn HEOS state queries into
16// reusable BoundaryCurve objects. These are convenience wrappers over
17// the Phase 1 generic curves — Region/Atlas know nothing about HEOS,
18// but every SBTL preset needs HEOS-sampled sat curves, so the wrapper
19// lives here so the Phase 1 components stay fluid-agnostic.
20//
21// All factories return CubicSplineCurve (C^2 continuous) sampled at
22// `n_knots` points log-uniform in pressure. C^2 is the right default
23// for SBTL: smooth across knots so the η normalization doesn't
24// introduce derivative kinks (which would show up as bands of
25// elevated SVD error — Phase 2a verified this empirically for Water).
26//
27// Default knot count (64) is plenty for the smooth sat curves of
28// pure fluids; raise it if a fluid's curve has unusually fast log-p
29// variation.
30//
31// TODO (Phase 2c): the *true* sat boundaries can be a no-refit view
32// onto the existing fluid superancillary (h_sat(T) chained with
33// T_sat(p) — both already machine-precision Chebyshev expansions on
34// the SuperAncillary class). Doing this needs (a) a public accessor
35// on HelmholtzEOSMixtureBackend (currently get_superanc() is
36// protected, see HelmholtzEOSMixtureBackend.h:65), and (b) a new
37// `SuperancillaryBoundaryCurve` BoundaryCurve subclass that composes
38// the two expansions and carries them through eval / eval_da.
39// Same factory entry points; same return type; same SVDSurface
40// consumer. Defer to Phase 2c when the backend itself will need to
41// pull the superancillary out anyway.
42
44{
45 std::size_t n_knots = 64; // CubicSplineCurve knots per curve
46
47 // T-floor walk-up: number of 0.5 K steps to try when T_min falls
48 // below T_melt(p) at high p. Default 1000 = 500 K of slack, which
49 // covers water's melting line all the way to its EOS p_max ~1 GPa
50 // (melting curve reaches ~370 K vs T_triple 273 K). Subcritical
51 // fluids typically clear in a handful of steps; this is just the
52 // safety net for SUPER-region builds.
53 std::size_t t_floor_walk_steps = 1000;
54};
55
56// h_sat,L(p) — mass enthalpy on the saturated-liquid side of the dome.
57//
58// When the source backend exposes a SuperAncillary (HEOS pure
59// fluids), returns a SuperancillaryBoundaryCurve composing
60// eval_sat('H', 0) with get_T_from_p — machine-precision residual
61// against the source HEOS, no refit (CoolProp-8vg). Otherwise
62// (REFPROP / IF97 / pseudo-pure fluids), falls back to a 64-knot
63// CubicSplineCurve sampled via PQ_INPUTS with Q = 0.
64std::unique_ptr<region::BoundaryCurve> build_h_sat_L(::CoolProp::AbstractState& heos, double p_min, double p_max,
65 const SatBoundaryBuildOptions& opts = {});
66
67// h_sat,V(p) — mass enthalpy on the saturated-vapor side of the dome.
68// Same SuperAncillary-or-spline dispatch as build_h_sat_L; PQ_INPUTS
69// with Q = 1 in the spline fallback.
70std::unique_ptr<region::BoundaryCurve> build_h_sat_V(::CoolProp::AbstractState& heos, double p_min, double p_max,
71 const SatBoundaryBuildOptions& opts = {});
72
73// s_sat,L(p) — mass entropy on the saturated-liquid side of the dome.
74// Entropy analog of build_h_sat_L: SuperAncillary prop_key 'S', Q = 0,
75// output_scale = 1/M (J/mol/K -> J/kg/K). Spline fallback samples via
76// PQ_INPUTS with Q = 0 and reads smass().
77std::unique_ptr<region::BoundaryCurve> build_s_sat_L(::CoolProp::AbstractState& heos, double p_min, double p_max,
78 const SatBoundaryBuildOptions& opts = {});
79
80// s_sat,V(p) — mass entropy on the saturated-vapor side of the dome.
81// Same dispatch as build_s_sat_L; PQ_INPUTS with Q = 1 in the fallback.
82std::unique_ptr<region::BoundaryCurve> build_s_sat_V(::CoolProp::AbstractState& heos, double p_min, double p_max,
83 const SatBoundaryBuildOptions& opts = {});
84
85// T_sat(p) — saturation temperature. Single-valued for pure fluids
86// (LIQUID and VAPOR sat curves coincide on T). Same dispatch — the
87// SuperAncillary path uses its own inverse-p Chebyshev expansion.
88std::unique_ptr<region::BoundaryCurve> build_T_sat(::CoolProp::AbstractState& heos, double p_min, double p_max,
89 const SatBoundaryBuildOptions& opts = {});
90
91// rho_sat,L(T) — mass density on the saturated-liquid side of the
92// dome, parameterized on temperature. Used by the DT-indexed preset
93// where the secondary axis (D) is bounded below by rho_sat,V(T) and
94// above by rho_sat,L(T) at fixed T.
95//
96// When the source backend exposes a SuperAncillary, returns a
97// SuperancillaryTemperatureBoundaryCurve calling eval_sat('D', 0, T)
98// directly (no inversion, machine-precision residual). Otherwise
99// falls back to a CubicSplineCurve sampled via QT_INPUTS with Q=0
100// at `n_knots` linear-uniform T values across [T_min, T_max].
101//
102// NOTE: for fluids with a density anomaly (water at T_anom ≈ 277 K,
103// heavy water at ≈ 284 K) the caller MUST split the LIQUID region so
104// each sub-region's [T_min, T_max] stays inside one monotonic piece of
105// rho_sat,L(T). Querying piece boundaries on the SuperAncillary via
106// `get_approx1d('D', 0).get_x_at_extrema()` is the supported way.
107std::unique_ptr<region::BoundaryCurve> build_rho_sat_L(::CoolProp::AbstractState& heos, double T_min, double T_max,
108 const SatBoundaryBuildOptions& opts = {});
109
110// rho_sat,V(T) — mass density on the saturated-vapor side of the
111// dome, parameterized on temperature. Same dispatch as
112// build_rho_sat_L; QT_INPUTS with Q=1 in the spline fallback. No
113// anomaly handling needed — rho_sat,V is monotone in T for every
114// known fluid.
115std::unique_ptr<region::BoundaryCurve> build_rho_sat_V(::CoolProp::AbstractState& heos, double T_min, double T_max,
116 const SatBoundaryBuildOptions& opts = {});
117
118// Isobar h-floor: h on the cold-isotherm boundary. For fluids with
119// steep melting curves (Methane / Propane / CO2 LIQUID at high p),
120// T_min may fall below T_melt(p) at part of the pressure range;
121// the lambda walks T up in 0.5 K increments until HEOS accepts the
122// state. Resulting floor is non-isothermal but still monotone in p.
123// Matches Phase 2a's `h_lo_liq` lambda.
124std::unique_ptr<region::CubicSplineCurve> build_h_isotherm_floor(::CoolProp::AbstractState& heos, double p_min, double p_max, double T_min,
125 const SatBoundaryBuildOptions& opts = {});
126
127// Isobar h-ceiling: h on the hot-isotherm boundary (T = T_max - margin
128// so we stay strictly inside the HEOS validity envelope).
129std::unique_ptr<region::CubicSplineCurve> build_h_isotherm_ceiling(::CoolProp::AbstractState& heos, double p_min, double p_max, double T_max,
130 const SatBoundaryBuildOptions& opts = {});
131
132// Isobar s-floor / s-ceiling: entropy analogs of build_h_isotherm_floor
133// / build_h_isotherm_ceiling. Same cold-isotherm melting-line T-walk
134// in the floor variant; both read smass() instead of hmass().
135std::unique_ptr<region::CubicSplineCurve> build_s_isotherm_floor(::CoolProp::AbstractState& heos, double p_min, double p_max, double T_min,
136 const SatBoundaryBuildOptions& opts = {});
137
138std::unique_ptr<region::CubicSplineCurve> build_s_isotherm_ceiling(::CoolProp::AbstractState& heos, double p_min, double p_max, double T_max,
139 const SatBoundaryBuildOptions& opts = {});
140
141// Locate the interior extrema of rho_sat,L(T) inside [T_min, T_max] —
142// i.e., the T values where drho_sat,L/dT = 0. For most fluids the
143// returned vector is empty (rho_sat,L is monotone decreasing in T from
144// triple to critical). Water and heavy water each have one extremum
145// (the density anomaly at ~277 K and ~284 K respectively); any future
146// fluid with N extrema returns N entries.
147//
148// Used by the DT-indexed SVDSBTL preset (CoolProp-i7j) to split the
149// LIQUID region into monotonic sub-regions — required because a
150// non-monotone rho_sat,L(T) creates a non-simply-connected LIQUID
151// region in (D, T) space (cells straddling the anomaly contain a
152// discontinuity the SVD can't represent).
153//
154// Dispatch:
155// 1. Source has SuperAncillary: query
156// `get_approx1d('D', 0).get_x_at_extrema()` — exact, zero compute.
157// 2. Else: walk QT_INPUTS on a coarse T grid (~64 points), find any
158// sign change in dρ_sat,L/dT via central difference, bisect each
159// bracket with TOMS748 to locate the extremum.
160//
161// Returned T values are sorted ascending and lie strictly inside
162// (T_min, T_max).
163std::vector<double> find_rho_satL_extrema_T(::CoolProp::AbstractState& heos, double T_min, double T_max);
164
165// Convenience: subcritical pressure range for `fluid`. Returns
166// (p_min, p_max) = (p_triple, p_crit * 0.999), where p_triple is the
167// fluid's true triple-point pressure (heos.p_triple(), with a
168// QT-at-Ttriple fallback when that is not finite/positive). The lowest
169// tabulated isobar therefore sits exactly at p_triple so a PT query at
170// p == p_triple() resolves (RegionAtlas containment is inclusive);
171// PQ(p_triple, 0) converges, so build_T_sat can sample the bottom knot.
172// Driven by the fluid's HEOS AbstractState — needs an instance already
173// constructed by the caller.
174//
175// `p_min_override` (absolute Pa, from the backend's `pmin` option)
176// replaces the p_triple floor when supplied. It MUST be >= p_triple:
177// below the triple line the liquid-vapour saturation boundary that
178// bounds the subcritical regions does not exist, so a sub-triple floor
179// is rejected with ValueError rather than failing obscurely deep in the
180// boundary-curve sampling.
181std::pair<double, double> subcritical_pressure_range(::CoolProp::AbstractState& heos, std::optional<double> p_min_override = std::nullopt);
182
183// Convenience: supercritical pressure range for `fluid`. Returns
184// (p_min, p_max) ≈ (p_crit * 1.001, pmax_eos * 0.99) — a thin margin
185// above the critical point so the SUPER region doesn't share its
186// lower boundary with the subcritical regions' upper bound (the SVD
187// gets ill-conditioned right at p_crit; the gap is small enough that
188// caller-side dispatch falls back to direct HEOS in the gap, when /
189// if that fallback ships). Driven by the fluid's HEOS AbstractState.
190std::pair<double, double> supercritical_pressure_range(::CoolProp::AbstractState& heos);
191
192} // namespace sbtl
193} // namespace CoolProp
194
195#endif // COOLPROP_SBTL_SAT_BOUNDARY_FACTORY_H