CoolProp
8.0.0
An open-source fluid property and humid air property database
include
CoolProp
region
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
8
namespace
CoolProp
{
9
namespace
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.
32
enum class
AxisScale
: std::uint8_t
33
{
34
LINEAR
,
35
LOG
,
36
POWER
,
37
POWER_LO
38
};
39
40
struct
AxisTransform
41
{
42
AxisScale
scale
;
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
}
77
if
(
scale
==
AxisScale::POWER_LO
) {
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
}
90
if
(
scale
==
AxisScale::POWER_LO
) {
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
Generated by
1.9.4