CoolProp 8.0.0
An open-source fluid property and humid air property database
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
8
9namespace CoolProp {
10namespace 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
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