CoolProp 8.0.0
An open-source fluid property and humid air property database
AxisTransform.h
Go to the documentation of this file.
1#ifndef COOLPROP_REGION_AXIS_TRANSFORM_H
2#define COOLPROP_REGION_AXIS_TRANSFORM_H
3
4#include <cmath>
5#include <cstdint>
6#include <stdexcept>
7
8namespace CoolProp {
9namespace region {
10
11// Map a physical variable a in [a_lo, a_hi] to xi in [0, 1]. The map is
12// linear, log(a), or critical-scaling-aware cube-root (CoolProp-4u9).
13// All three are monotone in (a_lo, a_hi); other monotone choices can be
14// added later if needed.
15//
16// POWER: forward xi = 1 - cbrt((a_hi - a) / (a_hi - a_lo)); inverse
17// a = a_hi - (a_hi - a_lo) * (1 - xi)^3. Grid lines uniform in xi
18// crowd toward a_hi (intended use: NC sub-critical sub-region in p
19// with a_hi = (1 - eps)·pc). The cube-root absorbs the (pc - p)^(1/δ)
20// scaling singularity at the critical point — Ising β ≈ 0.326 ≈ 1/3 —
21// so the Hermite-cubic SVD interpolant stays well-conditioned all the
22// way to within ~1 ppm of pc. Diagnostic measurements on R245fa /
23// Water / CO2 LIQUID + VAPOR at NT=200 NR=800 Cheb rank=20 show max
24// off-grid error 1e-9 to 1e-7 for POWER vs 1e-4 to 1e-3 for LOG over
25// the same band.
26//
27// POWER_LO: mirror of POWER that crowds toward a_lo instead of a_hi.
28// Forward xi = cbrt((a - a_lo) / (a_hi - a_lo)); inverse a = a_lo +
29// (a_hi - a_lo) * xi^3. Intended use: NC super-critical sub-region
30// with a_lo = (1 + eps)·pc (pc is the LOW end of the band there).
31// Same accuracy story as POWER, opposite anchor.
32enum class AxisScale : std::uint8_t
33{
34 LINEAR,
35 LOG,
36 POWER,
38};
39
41{
43 double a_lo; // physical lower bound
44 double a_hi; // physical upper bound
45 double a_lo_t; // transformed lower bound (a_lo or log(a_lo)); unused for POWER
46 double a_hi_t; // transformed upper bound (a_hi or log(a_hi)); unused for POWER
47 double inv_span_t; // 1 / (a_hi_t - a_lo_t); precomputed for hot path.
48 // For POWER, this stores 1 / (a_hi - a_lo) — the linear
49 // span — so chain-rule derivatives have a single scale
50 // factor to multiply.
51
52 // Factory. Throws on invalid inputs (a_hi <= a_lo, or LOG with non-
53 // positive bound).
54 static AxisTransform make(AxisScale s, double a_lo, double a_hi) {
55 if (!(a_hi > a_lo)) {
56 throw std::invalid_argument("AxisTransform: a_hi must exceed a_lo");
57 }
58 if (s == AxisScale::LOG && !(a_lo > 0.0)) {
59 throw std::invalid_argument("AxisTransform: LOG requires a_lo > 0");
60 }
61 if (s == AxisScale::POWER || s == AxisScale::POWER_LO) {
62 // a_lo_t / a_hi_t intentionally unused; inv_span_t stores
63 // 1 / (a_hi - a_lo) for the chain-rule derivative path.
64 return AxisTransform{s, a_lo, a_hi, 0.0, 0.0, 1.0 / (a_hi - a_lo)};
65 }
66 const double lo_t = (s == AxisScale::LOG) ? std::log(a_lo) : a_lo;
67 const double hi_t = (s == AxisScale::LOG) ? std::log(a_hi) : a_hi;
68 return AxisTransform{s, a_lo, a_hi, lo_t, hi_t, 1.0 / (hi_t - lo_t)};
69 }
70
71 // a -> xi in [0, 1]. Out-of-range inputs map outside [0,1] (no
72 // clamp); callers can check before / after.
73 [[nodiscard]] inline double forward(double a) const noexcept {
74 if (scale == AxisScale::POWER) {
75 return 1.0 - std::cbrt((a_hi - a) * inv_span_t);
76 }
78 return std::cbrt((a - a_lo) * inv_span_t);
79 }
80 const double a_t = (scale == AxisScale::LOG) ? std::log(a) : a;
81 return (a_t - a_lo_t) * inv_span_t;
82 }
83
84 // xi in [0, 1] -> a.
85 [[nodiscard]] inline double inverse(double xi) const noexcept {
86 if (scale == AxisScale::POWER) {
87 const double t = 1.0 - xi;
88 return a_hi - (a_hi - a_lo) * t * t * t;
89 }
91 return a_lo + (a_hi - a_lo) * xi * xi * xi;
92 }
93 const double a_t = a_lo_t + xi * (a_hi_t - a_lo_t);
94 return (scale == AxisScale::LOG) ? std::exp(a_t) : a_t;
95 }
96
97 // dxi/da at the physical point a (analytic).
98 //
99 // LINEAR : dxi/da = 1 / (a_hi - a_lo) = inv_span_t
100 // LOG : dxi/da = 1 / (a * (log a_hi - log a_lo))
101 // POWER : xi = 1 - ((a_hi - a)/(a_hi - a_lo))^(1/3)
102 // dxi/da = (1/3) * inv_span_t * ((a_hi - a) * inv_span_t)^(-2/3)
103 // Diverges as a -> a_hi (anchor side — inverse has zero slope there).
104 // POWER_LO : xi = ((a - a_lo)/(a_hi - a_lo))^(1/3)
105 // dxi/da = (1/3) * inv_span_t * ((a - a_lo) * inv_span_t)^(-2/3)
106 // Diverges as a -> a_lo (anchor side).
107 [[nodiscard]] inline double dxi_da(double a) const noexcept {
108 if (scale == AxisScale::LOG) {
109 return inv_span_t / a;
110 }
111 if (scale == AxisScale::POWER) {
112 const double u = (a_hi - a) * inv_span_t;
113 return (1.0 / 3.0) * inv_span_t / std::cbrt(u * u);
114 }
115 if (scale == AxisScale::POWER_LO) {
116 const double u = (a - a_lo) * inv_span_t;
117 return (1.0 / 3.0) * inv_span_t / std::cbrt(u * u);
118 }
119 return inv_span_t;
120 }
121};
122
123} // namespace region
124} // namespace CoolProp
125
126#endif // COOLPROP_REGION_AXIS_TRANSFORM_H