CoolProp
8.0.0
An open-source fluid property and humid air property database
include
CoolProp
sbtl
SVDSurfaceSerializer.h
Go to the documentation of this file.
1
#ifndef COOLPROP_SBTL_SVD_SURFACE_SERIALIZER_H
2
#define COOLPROP_SBTL_SVD_SURFACE_SERIALIZER_H
3
4
#include <string>
5
#include <vector>
6
7
#include "
CoolProp/sbtl/SVDSurface.h
"
8
9
namespace
CoolProp
{
10
namespace
sbtl {
11
12
// Persistence layer for SVDSurface. Writes a msgpack stream wrapped
13
// in a zlib-compressed payload, matching the existing TabularBackends
14
// `.bin.z` convention.
15
//
16
// File format (compressed bytes contain a single msgpack array):
17
//
18
// [
19
// "SVDS", // magic, string
20
// 1, // revision, int
21
// <fluid_name>, // string
22
// [ // surfaces, array (length n_surfaces)
23
// <surface_0>,
24
// ...
25
// ]
26
// ]
27
//
28
// Each <surface_k> is a flat positional msgpack array:
29
//
30
// [
31
// input_pair, // int (CoolProp::input_pairs)
32
// [[prop_key], ...], // properties (length-1 arrays;
33
// // per-property OutputTransform
34
// // lives inside each decomp_blob)
35
// [region_blob, ...], // regions
36
// [decomp_blob, ...] // per (region, property) SVDs
37
// ]
38
//
39
// Region geometry + boundary curves serialize via the State POD
40
// snapshots exposed on ConstantCurve / CubicSplineCurve /
41
// PiecewiseChebyshevCurve. Each curve is tagged with a uint8 kind
42
// discriminator so the deserializer knows which from_state() to
43
// call.
44
//
45
// Backward / forward compatibility:
46
// - The magic + revision header lets the loader reject obviously
47
// non-SVD files and lets future revisions add fields.
48
// - A loader on rev N hitting a stream written for rev N+M with
49
// extra trailing fields will silently ignore them (msgpack arrays
50
// are length-prefixed). A loader on rev N+M reading rev N data
51
// will throw on the missing fields — adopters bump revision when
52
// they add anything.
53
//
54
// File extension convention: `.svd.bin.z`, parallel to BicubicBackend's
55
// `.bin.z`. Stored at
56
// `${HOME}/.CoolProp/SVDTables/<fluid>.<source>.<input_pair>.svd.bin.z`
57
// by the helper paths below — see default_cache_path() for the exact
58
// composition (source is the truth-source backend name; input_pair is
59
// the integer value of the enum).
60
61
class
SVDSurfaceSerializer
62
{
63
public
:
64
// On-wire revision. Bumped on:
65
// rev 1: initial Phase 2b release (4 properties per surface)
66
// rev 2: speed_sound added as 5th property (rev 1 caches still
67
// deserialize as 4-property surfaces but calc_speed_sound
68
// would silently fall through to NaN; bumping the rev
69
// forces a clean rebuild so the user can't trip on stale
70
// caches missing w).
71
// rev 3: explicit source-of-truth backend in the cache filename
72
// ("<fluid>.<source>.<input_pair>.svd.bin.z"). No on-wire
73
// format change, just a path change — old caches at the
74
// rev-2 path simply won't be picked up and will be
75
// rebuilt under the new path the first time they're
76
// requested.
77
// rev 4: cache filename now uses the symbolic input_pair name
78
// (e.g. "PT_INPUTS") instead of the raw enum int — closes
79
// CoolProp-b6v — and incorporates a 16-hex FNV-1a 64
80
// opthash over the canonical-JSON options blob. No
81
// on-wire format change; just a path change. Old rev-3
82
// int-keyed caches simply won't be picked up.
83
// rev 5: ph_properties / pt_properties extended with transport
84
// properties (η, λ) for IAPWS G13-15 Tables 12 & 13.
85
// Old rev-4 caches are missing two properties at the
86
// tail; bump to force rebuild rather than try to silently
87
// extend them in-place.
88
// rev 6: Chebyshev η-spacing on the secondary axis (cells now
89
// crowd toward the saturation curve) and IF97-source
90
// sampling Newton-refines against forward h(T, p) at
91
// build time. Both change the *content* of the stored
92
// U-matrix and slopes at the same hashed options key, so
93
// the rev bump is the cache-invalidation mechanism — old
94
// rev-5 caches would silently serve uniform-η surfaces
95
// and undo the IF97 conformance gains.
96
// rev 7: SUPER region split for IF97 source backend — SUPER_R3
97
// (h < h_B23(p)) and SUPER_R2 (h > h_B23(p)) — so the
98
// IF97 R2/R3 derivative kink sits on a region edge
99
// instead of inside a cell. Region count for IF97 goes
100
// from 3 (LIQUID/VAPOR/SUPER) to 4 or 5 (LIQUID/VAPOR/
101
// SUPER_R3/SUPER_R2 + optional SUPER_HIGH_P above 100 MPa).
102
// On-wire region list differs, so the rev bump forces a
103
// clean rebuild instead of attempting to deserialize a
104
// rev-6 cache that won't have the new boundary curves.
105
// HEOS source unchanged.
106
// rev 8: SUPER_R3 further split at the IF97 R1/R3 isotherm
107
// h_R1R3(p) = h_IF97(623.15 K, p), so R1 territory at
108
// p > pcrit gets its own SVD (SUPER_R1_super) separate
109
// from R3 proper (SUPER_R3_proper). Region count for
110
// IF97 goes from 4-5 (post-rev-7) to 5-6. Closes the
111
// post-rev-7 R1 conformance gap where R1 and R3 modes
112
// competed for SVD bandwidth in a single region.
113
// rev 9: IF97 sampling-side Newton replaced with TOMS748
114
// bracketed root-find (foi.9.10). R3 cells whose Newton
115
// previously failed to converge (e.g., T_target=663.7 K,
116
// p=26.6 MPa where the T=700 fallback seed put Newton in
117
// R2 territory and it oscillated across the R2/R3 boundary)
118
// now sample correctly. Stored T/s/ρ/w grid values for
119
// those cells change, so existing rev-8 caches must
120
// rebuild.
121
// rev 10: CoolProp-8vg. HEOS-source presets switched their sat
122
// boundary curves (h_sat,L, h_sat,V) to
123
// region::SuperancillaryBoundaryCurve — no-refit views
124
// onto the SuperAncillary's machine-precision Chebyshev
125
// expansions. IF97-source presets (which have no SA)
126
// keep the legacy 64-knot cubic spline path. HEOS
127
// tables built with the new boundaries have different
128
// η normalisation at each grid sample → SVD
129
// coefficients shift → existing rev-9 caches must
130
// rebuild. HEOS surfaces can't yet round-trip through
131
// the serializer (SuperancillaryBoundaryCurve has no
132
// CurveKind id) — they rebuild in memory each session;
133
// IF97 surfaces still cache normally.
134
// rev 11: CurveKind::SUPERANCILLARY tag added with the State
135
// POD (p_min, p_max, prop_key, Q, output_scale, b_min,
136
// b_max — 8-element array). The SA handle itself isn't
137
// stored; load-side re-acquires it by constructing a
138
// HEOS AbstractState for the fluid in the stream and
139
// pulling its SuperAncillary. HEOS surfaces now round-
140
// trip through disk (CoolProp-cv7); first-session cost
141
// amortises across all subsequent process invocations.
142
// Rev bump invalidates rev-10 caches, which never
143
// successfully landed any HEOS surfaces on disk anyway
144
// (save() threw on the unknown subclass).
145
// rev 12: SVDSBTL&IF97 presets gain a SUPER_R5 region covering
146
// IAPWS R7-97 Region 5 (T ∈ [1073.15, 2273.15] K, p ≤
147
// 50 MPa — CoolProp-pd6). The new region appears at
148
// the tail of the regions array (per-PH and per-PT),
149
// so the region count for IF97 caches changes from
150
// 5-6 to 6-7. Rev bump forces rebuild so the loader
151
// doesn't try to dispatch lookups for R5 cells against
152
// a rev-11 cache that has no SUPER_R5 region. HEOS
153
// caches unchanged in content but invalidated by the
154
// rev bump too (R5 is IF97-only and HEOS presets skip
155
// the new region entirely; the cache geometry is the
156
// same as rev 11 but the rev field differs).
157
// rev 13: CoolProp-4u9. HEOS / REFPROP source presets gain a
158
// pair of near-critical sub-regions (NC_LIQUID,
159
// NC_VAPOR) on p ∈ [0.9·pc, (1 − 1ppm)·pc] using a new
160
// AxisScale::POWER primary axis (β = 1/3, cube-root
161
// crowding toward pc). Parent LIQUID/VAPOR p_max
162
// clipped down to 0.9·pc to hand off cleanly. Region
163
// count for HEOS caches goes from 3 (LIQUID/VAPOR/SUPER)
164
// to 5. POWER axis serialises via the same
165
// AxisTransform pack/unpack as LINEAR/LOG (scale enum
166
// value differs; a_lo/a_hi unchanged); existing rev-12
167
// caches don't know about the NC regions and would
168
// dispatch near-pc lookups to the parent LIQUID/VAPOR
169
// SVD where off-grid max error is ~1e-3 instead of the
170
// new ~1e-7 from the POWER NC regions, so the rev bump
171
// forces a clean rebuild. IF97 caches unchanged in
172
// content (no NC for IF97 — G13-15 already passes) but
173
// invalidated by the rev bump too.
174
// rev 14: CoolProp-4u9 follow-up. Add NC_SUPER region on
175
// p ∈ [(1 + 1e-10)·pc, 1.1·pc] using AxisScale::POWER_LO
176
// (mirror of POWER that crowds toward a_lo). Closes a
177
// (T, p) coverage gap exposed by the h=350 kJ/kg R245fa
178
// sweep: cells in the thin strip just above pc with T
179
// outside the auto-cal'd patch's narrow (T) bbox fell
180
// through every region (LIQUID/VAPOR clipped to 0.9·pc,
181
// NC_LIQUID/VAPOR capped at (1−1ppm)·pc, SUPER starting
182
// at 1.001·pc) and returned NaN. NC_SUPER claims them.
183
// HEOS region count goes from 5 to 6. NC sub-side band
184
// also tightened from 1ppm to 1e-10 below pc — the NC
185
// POWER axis is well-behaved at a_hi=pc-ε for any ε
186
// large enough to keep the SuperAncillary sat-curve
187
// well-defined, and tightening reduces the patch-only
188
// sliver around pc.
189
// rev 16: CoolProp-wvtz. Region gains a selectable secondary-axis
190
// (eta) scale, packed as a 9th region-blob element. The
191
// DmassT preset's VAPOR + SUPER regions switch to
192
// AxisScale::LOG so the near-ideal-gas low-density tail
193
// (rho_hi/rho_lo ~ 1e5, p ∝ rho) gets uniform-in-decade
194
// sampling instead of collapsing below the first linear-eta
195
// grid node. The stored grid sample positions and U/slopes
196
// for those regions change, and the on-wire region array
197
// grows from 8 to 9 elements, so rev-15 caches must rebuild.
198
// HEOS DmassT low-density pressure error drops from
199
// ~40 %–2400 % to ~1e-5. PT / HmassP surfaces are
200
// byte-identical (their regions stay LINEAR).
201
// rev 17: CoolProp-4z79. The DmassT preset gains three near-critical
202
// (NC) sub-regions that close the previously-uncovered
203
// ±0.1%·Tc band: POWER-axis NC_LIQUID/NC_VAPOR carry the
204
// dome-bounded sides up to ~Tc, and a POWER_LO isobar-bounded
205
// NC_SUPER spans the critical isotherm itself. HEOS-source
206
// DmassT region count grows (3 → 6), so the on-wire region
207
// list differs and rev-16 caches must rebuild. The whole
208
// [0.99,1.01]·Tc band is now table-served (no HEOS) to
209
// ~1e-6..2e-5 for all fluids. PT / HmassP and the non-DmassT
210
// presets are unchanged.
211
// rev 18: CoolProp-jh6a. The DmassT parent SUPER region's primary-T
212
// axis switches LINEAR → LOG. For low-Tc / wide-supercritical
213
// fluids (Helium: Tc 5.2 K, Tmax 2000 K, ~380× range) LINEAR
214
// starved the near-Tc supercritical zone of grid lines, giving
215
// ~1% pressure error across the whole SUPER region; LOG drops
216
// it to ~1e-8. No region-count change (same 6 regions), but
217
// the SUPER grid sample positions and U/slopes shift, so
218
// rev-17 caches must rebuild. Modest-ratio fluids (Water ~3×,
219
// CO2 ~6×) are unchanged in practice (LOG ≈ LINEAR there).
220
// rev 19: CoolProp-naqt / issue #3189. The subcritical PT / HmassP /
221
// PSmass presets lower their default p_min from
222
// ~1.01-1.03·p_triple to the true triple-point pressure
223
// (heos.p_triple()), so a PT query at p == p_triple() now
224
// resolves instead of returning NaN. The log-p primary axis
225
// span shifts (lower bottom isobar), so the grid sample
226
// positions and SVD U/slopes change and rev-18 caches must
227
// rebuild. (A non-default `pmin` option already produces a
228
// distinct cache via the opthash; this rev covers the
229
// all-defaults "{}" case whose opthash is unchanged.)
230
static
constexpr
int
kRevision
= 19;
231
232
// Pack one surface into a zlib-compressed msgpack blob.
233
static
std::vector<char>
save
(
const
SVDSurface
& surface);
234
235
// Reverse: parse a compressed blob back into a fully-sealed
236
// SVDSurface. Throws std::runtime_error on:
237
// - zlib decompression failure
238
// - magic / revision mismatch
239
// - missing or malformed fields
240
// - invalid curve / decomp dimensions surfacing through the
241
// trusted from_state() factories
242
static
SVDSurface
load
(
const
std::vector<char>& compressed);
243
244
// File-level convenience. save_to_file overwrites; load_from_file
245
// throws if the file doesn't exist or is unreadable. save_to_file
246
// routes through ::write_bytes_atomic (CPfilepaths.h) so concurrent
247
// writers in different processes / threads never see a partial-
248
// write file (CoolProp-4no.2).
249
static
void
save_to_file
(
const
SVDSurface
& surface,
const
std::string& path);
250
static
SVDSurface
load_from_file
(
const
std::string& path);
251
252
// Returns the standard cache directory:
253
// ${HOME}/.CoolProp/SVDTables/
254
// Creates the directory if it doesn't exist. Mirrors the
255
// existing BicubicBackend pattern (see TabularBackends.h:1035).
256
static
std::string
default_cache_dir
();
257
258
// Compose default_cache_dir() with
259
// "<fluid>.<source>.<input_pair_name>.<opthash>.svd.bin.z"
260
//
261
// - input_pair_name is the symbolic enum name returned by
262
// CoolProp::get_input_pair_short_desc() (e.g. "PT_INPUTS",
263
// "HmassP_INPUTS"), so reordering the input_pairs enum in
264
// DataStructures.h can't silently misalign caches (was the
265
// raw int previously — closes CoolProp-b6v).
266
// - opthash is a 16-hex string (typically FNV-1a 64 of the
267
// canonical options blob), or "no_opts" for callers that don't
268
// care about per-options caching. Defaults to "no_opts" to
269
// keep the legacy two-arg-ish call sites compiling without
270
// touching every test.
271
//
272
// source_backend must be non-empty and free of path-separator
273
// characters; opthash is rejected if it contains anything other
274
// than [0-9a-f_].
275
static
std::string
default_cache_path
(
const
std::string& fluid_name,
const
std::string& source_backend,
::CoolProp::input_pairs
input_pair,
276
const
std::string& opthash =
"no_opts"
);
277
};
278
279
}
// namespace sbtl
280
}
// namespace CoolProp
281
282
#endif
// COOLPROP_SBTL_SVD_SURFACE_SERIALIZER_H
Generated by
1.9.4