{ "cells": [ { "cell_type": "markdown", "id": "cell-001", "metadata": {}, "source": [ "# SVDSBTL backend — validation against HEOS\n", "\n", "This page compares the **SVDSBTL** surrogate backend against the **HEOS** Helmholtz-energy reference across four input pairs. For `(P, T)`, `(H, P)`, and `(P, S)` each panel shows the **density** error `log10 | (rho_SVDSBTL - rho_HEOS) / rho_HEOS |`. For `(rho, T)` density is the *input* (exact by construction), so the DT-indexed surface is checked on **pressure** instead: `log10 | (p_SVDSBTL - p_HEOS) / p_HEOS |` over a `(T, rho)` rectangle.\n", "\n", "The heatmaps are interactive — hover for the local error value, drag to pan, scroll to zoom, double-click to reset. The figures are static HTML (no kernel, no Binder, no JupyterLite); the interactivity is plotly.js embedded by nbsphinx.\n", "\n", "**Backends:** `HEOS` (reference) vs `SVDSBTL&HEOS` (rank-truncated SVD surrogate over HEOS). \n", "**Inputs:** `PT_INPUTS`, `HmassP_INPUTS`, `PSmass_INPUTS` (validate `rhomass()`) and `DmassT_INPUTS` (validates `p()`). \n", "**Fluids:** `Water`, `Argon`, `Hydrogen`, `R1234yf`, `R245fa`, `D6`, `CO2`, `Helium`.\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "cell-002", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:10:19.591202Z", "iopub.status.busy": "2026-06-27T16:10:19.591068Z", "iopub.status.idle": "2026-06-27T16:10:21.290255Z", "shell.execute_reply": "2026-06-27T16:10:21.289904Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SVDSBTL cache directory: /__w/CoolProp/CoolProp/.svdtables-cache\n" ] } ], "source": [ "import os\n", "from pathlib import Path\n", "\n", "# Route SVDSBTL surface I/O to a docs-dedicated cache directory so the\n", "# build does not collide with developer interactive work in\n", "# ~/.CoolProp/SVDTables/. Setting this *before* importing CoolProp lets\n", "# the C++ config-init read the env var on first access, and joblib's\n", "# loky workers inherit the env so they hit the same cache.\n", "_cache = os.environ.setdefault(\n", " 'COOLPROP_ALTERNATIVE_SVDTABLES_DIRECTORY',\n", " str(Path.home() / '.CoolProp' / 'SVDTables-docs'),\n", ")\n", "Path(_cache).mkdir(parents=True, exist_ok=True)\n", "\n", "import numpy as np\n", "import plotly.io as pio\n", "import plotly.graph_objects as go\n", "import CoolProp.CoolProp as CP\n", "from CoolProp.CoolProp import PT_INPUTS, HmassP_INPUTS, QT_INPUTS, DmassT_INPUTS, PSmass_INPUTS\n", "\n", "# Also set via the explicit config setter in the parent process. NOTE:\n", "# this only mutates the parent's Configuration singleton; joblib's loky\n", "# workers spawn fresh interpreters with their own singletons and rely\n", "# entirely on env-var inheritance (which is why os.environ.setdefault\n", "# above MUST stay before the CoolProp import — do not remove it).\n", "CP.set_config_string(CP.ALTERNATIVE_SVDTABLES_DIRECTORY, _cache)\n", "\n", "pio.renderers.default = 'notebook_connected'\n", "print(f'SVDSBTL cache directory: {_cache}')\n", "\n", "FLUIDS = ['Water', 'Argon', 'Hydrogen', 'R1234yf', 'R245fa', 'D6', 'CO2', 'Helium']\n", "INPUT_PAIRS = ['PT', 'HP', 'DT', 'PS']\n", "N_X, N_Y = 300, 300 # grid resolution per axis (90k cells per panel)\n", "BAND_REL = 0.02 # half-width band around saturation line to mask (PT only)\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "cell-003", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:10:21.292135Z", "iopub.status.busy": "2026-06-27T16:10:21.291772Z", "iopub.status.idle": "2026-06-27T16:10:21.300911Z", "shell.execute_reply": "2026-06-27T16:10:21.300608Z" } }, "outputs": [], "source": [ "def _hp_axes(ref, Tmin, Tmax, pmin, pmax, N_Y):\n", " \"\"\"Estimate an enthalpy range covering the single-phase region for\n", " the HP plot by sampling a coarse (T, p) grid across the rectangle.\n", "\n", " Sampling the interior — not just the four corners — matters for fluids\n", " like CO2 whose high triple-point pressure and steep melting line put\n", " most corners outside HEOS validity: 3 of CO2's 4 corners throw (p below\n", " p_triple, or T below T_melt(p)). A corners-only range would then\n", " collapse to a single enthalpy and render a degenerate sliver of a panel.\n", " \"\"\"\n", " hs = []\n", " for T_q in np.linspace(Tmin, Tmax, 8):\n", " for p_q in np.geomspace(pmin, pmax, 8):\n", " try:\n", " ref.update(PT_INPUTS, p_q, T_q)\n", " hs.append(ref.hmass())\n", " except Exception:\n", " pass\n", " if len(hs) < 2:\n", " # Almost nothing valid in the rectangle even after sampling.\n", " # Return a benign ~1 J/kg-wide axis so the panel still renders;\n", " # the surface queries there will mostly be NaN.\n", " h0 = hs[0] if hs else 0.0\n", " return np.linspace(h0 - 1.0, h0 + 1.0, N_Y)\n", " return np.linspace(min(hs), max(hs), N_Y)\n", "\n", "def _ps_axes(ref, Tmin, Tmax, pmin, pmax, N_Y):\n", " \"\"\"Estimate a specific-entropy range covering the single-phase region\n", " for the PS plot by sampling a coarse (T, p) grid across the rectangle.\n", " Entropy twin of _hp_axes — entropy has no natural reduced form, so the\n", " axis stays in absolute J/(kg.K). Interior sampling matters for the\n", " same reason as _hp_axes (CO2's corners fall outside HEOS validity).\n", " \"\"\"\n", " ss = []\n", " for T_q in np.linspace(Tmin, Tmax, 8):\n", " for p_q in np.geomspace(pmin, pmax, 8):\n", " try:\n", " ref.update(PT_INPUTS, p_q, T_q)\n", " ss.append(ref.smass())\n", " except Exception:\n", " pass\n", " if len(ss) < 2:\n", " s0 = ss[0] if ss else 0.0\n", " return np.linspace(s0 - 1.0, s0 + 1.0, N_Y)\n", " return np.linspace(min(ss), max(ss), N_Y)\n", "\n", "def _dt_axes(ref, Tmin, Tmax, pmin, pmax, N_X):\n", " \"\"\"Density axis for the DT panel. rho is the *input* for DmassT, so\n", " we need a rho range, not a p range: sample the densities reached across\n", " the (T, p) rectangle and span their min..max on a log axis. Sampling\n", " the interior (not just corners) keeps fluids like CO2 — whose corners\n", " fall below p_triple / the melting line — from collapsing it. The\n", " spanned range also covers the two-phase dome, whose densities lie\n", " between the vapor and liquid branches sampled here.\n", " \"\"\"\n", " rhos = []\n", " for T_q in np.linspace(Tmin, Tmax, 8):\n", " for p_q in np.geomspace(pmin, pmax, 8):\n", " try:\n", " ref.update(PT_INPUTS, p_q, T_q)\n", " rhos.append(ref.rhomass())\n", " except Exception:\n", " pass\n", " if len(rhos) < 2:\n", " r0 = rhos[0] if rhos else 1.0\n", " return np.geomspace(max(r0 * 0.5, 1e-6), r0 * 2.0, N_X)\n", " return np.geomspace(max(min(rhos), 1e-6), max(rhos), N_X)\n", "\n", "def compute_error_grid(fluid, input_pair, N_X=N_X, N_Y=N_Y):\n", " \"\"\"Return (x_axis, y_axis, log10_err, Tc, pc, x_label, y_label, err_msg).\n", "\n", " input_pair is 'PT', 'HP', 'DT', or 'PS'.\n", " 'PT' -> x = p (log, Pa), y = T (linear, K). Validates rho.\n", " Mask a +/- BAND_REL band around the saturation line.\n", " 'HP' -> x = p (log, Pa), y = h (linear, J/kg). Validates rho.\n", " No mask; the SVDSBTL surface is undefined inside the dome and\n", " queries there are caught and set to NaN.\n", " 'PS' -> x = p (log, Pa), y = s (linear, J/kg/K). Validates rho.\n", " No mask (same dome handling as HP — the two-phase wedge is a\n", " true 2D region in (s, p) and surface queries there are NaN).\n", " 'DT' -> x = rho (log, kg/m^3), y = T (linear, K). Validates p.\n", " rho is the input (exact), so the DT-indexed surface is checked\n", " on p(rho, T). No mask: the two-phase dome is a valid DT region\n", " (SVDSBTL routes it via the dome lever-rule, matching p_sat(T)).\n", "\n", " SVDSBTL surface build failures are caught and reported via err_msg;\n", " the returned grid is all-NaN in that case.\n", " \"\"\"\n", " ref = CP.AbstractState('HEOS', fluid)\n", " Tc = ref.T_critical()\n", " pc = ref.p_critical()\n", " Tmin = max(ref.Tmin(), 0.6 * Tc)\n", " Tmax = min(ref.Tmax(), 1.4 * Tc)\n", " pmin = max(1e3, 0.01 * pc)\n", " pmax = 3.0 * pc\n", "\n", " psat = None\n", " if input_pair == 'PT':\n", " x_axis = np.geomspace(pmin, pmax, N_X)\n", " y_axis = np.linspace(Tmin, Tmax, N_Y)\n", " x_label = 'reduced pressure p / p_c'\n", " y_label = 'reduced temperature T / T_c'\n", " # Saturation line p_sat(T) for masking.\n", " psat = np.full_like(y_axis, np.nan)\n", " for i, T in enumerate(y_axis):\n", " if T < Tc:\n", " try:\n", " ref.update(QT_INPUTS, 0.0, T)\n", " psat[i] = ref.p()\n", " except Exception:\n", " pass\n", " elif input_pair == 'HP':\n", " x_axis = np.geomspace(pmin, pmax, N_X)\n", " y_axis = _hp_axes(ref, Tmin, Tmax, pmin, pmax, N_Y)\n", " x_label = 'reduced pressure p / p_c'\n", " y_label = 'specific enthalpy h [kJ/kg]'\n", " elif input_pair == 'DT':\n", " x_axis = _dt_axes(ref, Tmin, Tmax, pmin, pmax, N_X)\n", " y_axis = np.linspace(Tmin, Tmax, N_Y)\n", " x_label = 'density rho [kg/m^3]'\n", " y_label = 'reduced temperature T / T_c'\n", " elif input_pair == 'PS':\n", " x_axis = np.geomspace(pmin, pmax, N_X)\n", " y_axis = _ps_axes(ref, Tmin, Tmax, pmin, pmax, N_Y)\n", " x_label = 'reduced pressure p / p_c'\n", " y_label = 'specific entropy s [J/(kg K)]'\n", " else:\n", " raise ValueError(f'unknown input_pair: {input_pair!r}')\n", "\n", " err = np.full((N_Y, N_X), np.nan)\n", "\n", " try:\n", " svd = CP.AbstractState('SVDSBTL&HEOS', fluid)\n", " except Exception as e:\n", " return (x_axis, y_axis, err, Tc, pc, x_label, y_label,\n", " f'SVDSBTL surface build failed: {e}')\n", "\n", " for i, y in enumerate(y_axis):\n", " for j, xv in enumerate(x_axis):\n", " if psat is not None and np.isfinite(psat[i]) and abs(xv - psat[i]) / psat[i] < BAND_REL:\n", " continue\n", " try:\n", " if input_pair == 'PT':\n", " ref.update(PT_INPUTS, xv, y)\n", " svd.update(PT_INPUTS, xv, y)\n", " q_ref, q_svd = ref.rhomass(), svd.rhomass()\n", " elif input_pair == 'HP':\n", " ref.update(HmassP_INPUTS, y, xv)\n", " svd.update(HmassP_INPUTS, y, xv)\n", " q_ref, q_svd = ref.rhomass(), svd.rhomass()\n", " elif input_pair == 'PS':\n", " # PSmass takes (p, s) = (xv, y); validate density.\n", " ref.update(PSmass_INPUTS, xv, y)\n", " svd.update(PSmass_INPUTS, xv, y)\n", " q_ref, q_svd = ref.rhomass(), svd.rhomass()\n", " else: # DT — rho is the input; validate pressure\n", " ref.update(DmassT_INPUTS, xv, y)\n", " svd.update(DmassT_INPUTS, xv, y)\n", " q_ref, q_svd = ref.p(), svd.p()\n", " if q_ref > 0:\n", " err[i, j] = abs(q_svd - q_ref) / q_ref\n", " except Exception:\n", " pass\n", " # Floor zero / near-zero errors at 1e-16 so exact-match cells render at\n", " # the colorbar floor (best-possible accuracy) instead of going to -inf\n", " # and showing as NaN/white. Inside the two-phase dome SVDSBTL returns the\n", " # same value as HEOS (shared saturation lookup), so err is ~0 there.\n", " with np.errstate(divide='ignore', invalid='ignore'):\n", " log_err = np.log10(np.maximum(err, 1e-16))\n", " return x_axis, y_axis, log_err, Tc, pc, x_label, y_label, None\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "cell-004", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:10:21.302158Z", "iopub.status.busy": "2026-06-27T16:10:21.302023Z", "iopub.status.idle": "2026-06-27T16:10:21.306063Z", "shell.execute_reply": "2026-06-27T16:10:21.305844Z" } }, "outputs": [], "source": [ "def plot_from_grid(fluid, input_pair, grid):\n", " x_axis, y_axis, log_err, Tc, pc, x_label, y_label, err_msg = grid\n", " title = f'{fluid} [{input_pair}] \\u2014 Tc={Tc:.2f} K, pc={pc/1e5:.3f} bar'\n", " if err_msg is not None:\n", " title += ' [SVDSBTL surface unavailable]'\n", " if input_pair == 'PT':\n", " # x = p/p_c (log), y = T/T_c (linear). z is stored as [y_index, x_index].\n", " heat = go.Heatmap(\n", " x=x_axis / pc, y=y_axis / Tc, z=log_err,\n", " colorscale='Viridis', zmin=-12, zmax=-2,\n", " colorbar=dict(title='log10 |\\u0394\\u03c1/\\u03c1|'),\n", " hovertemplate=('p/p_c=%{x:.3f}
'\n", " 'T/T_c=%{y:.3f}
'\n", " 'log10 err=%{z:.2f}'),\n", " )\n", " xaxis = dict(title=x_label, type='log')\n", " yaxis = dict(title=y_label)\n", " elif input_pair == 'HP': # Mollier convention: x = h (linear), y = p (log).\n", " heat = go.Heatmap(\n", " x=y_axis / 1e3, y=x_axis / pc, z=log_err.T,\n", " colorscale='Viridis', zmin=-12, zmax=-2,\n", " colorbar=dict(title='log10 |\\u0394\\u03c1/\\u03c1|'),\n", " hovertemplate=('h=%{x:.1f} kJ/kg
'\n", " 'p/p_c=%{y:.3f}
'\n", " 'log10 err=%{z:.2f}'),\n", " )\n", " xaxis = dict(title=y_label) # 'specific enthalpy h [kJ/kg]'\n", " yaxis = dict(title='reduced pressure p / p_c', type='log')\n", " elif input_pair == 'PS': # Mollier-like: x = s (linear), y = p (log).\n", " heat = go.Heatmap(\n", " x=y_axis, y=x_axis / pc, z=log_err.T,\n", " colorscale='Viridis', zmin=-12, zmax=-2,\n", " colorbar=dict(title='log10 |\\u0394\\u03c1/\\u03c1|'),\n", " hovertemplate=('s=%{x:.1f} J/(kg K)
'\n", " 'p/p_c=%{y:.3f}
'\n", " 'log10 err=%{z:.2f}'),\n", " )\n", " xaxis = dict(title=y_label) # 'specific entropy s [J/(kg K)]'\n", " yaxis = dict(title='reduced pressure p / p_c', type='log')\n", " else: # DT — x = rho (log), y = T/T_c (linear). Validates pressure.\n", " heat = go.Heatmap(\n", " x=x_axis, y=y_axis / Tc, z=log_err,\n", " colorscale='Viridis', zmin=-12, zmax=-2,\n", " colorbar=dict(title='log10 |\\u0394p/p|'),\n", " hovertemplate=('rho=%{x:.4g} kg/m^3
'\n", " 'T/T_c=%{y:.3f}
'\n", " 'log10 err=%{z:.2f}'),\n", " )\n", " xaxis = dict(title=x_label, type='log')\n", " yaxis = dict(title=y_label)\n", " fig = go.Figure(heat)\n", " fig.update_layout(\n", " title=title,\n", " xaxis=xaxis,\n", " yaxis=yaxis,\n", " width=720, height=480,\n", " margin=dict(l=70, r=20, t=50, b=60),\n", " )\n", " if err_msg is not None:\n", " print(err_msg)\n", " return fig\n" ] }, { "cell_type": "markdown", "id": "cell-005", "metadata": {}, "source": [ "### Environment preflight\n", "\n", "Before the parallel grid below, eagerly materialize **all four** input-pair surfaces for a single fluid via the SVDSBTL `prebuild` option. This fails **loudly here** if the CoolProp build / kernel environment can't produce them (e.g. a stale wheel without `PSmass` support) — rather than letting every PS / DT panel downstream render as a silent blank. `prebuild` strips itself from the cache key, so these surfaces are shared with the plain `SVDSBTL&HEOS` constructions the panels use.\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "cell-006", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:10:21.307206Z", "iopub.status.busy": "2026-06-27T16:10:21.307083Z", "iopub.status.idle": "2026-06-27T16:10:21.744782Z", "shell.execute_reply": "2026-06-27T16:10:21.744425Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "preflight OK: all SVDSBTL input-pair surfaces build for Water\n" ] } ], "source": [ "# prebuild=true builds PT + HmassP + DmassT + PSmass for Water in a single\n", "# construction; a build / env failure raises here and fails the docs build,\n", "# which is the intended loud signal (vs. a quietly blank panel).\n", "CP.AbstractState('SVDSBTL&HEOS', 'Water?{\"prebuild\": true}')\n", "print('preflight OK: all SVDSBTL input-pair surfaces build for Water')\n" ] }, { "cell_type": "markdown", "id": "cell-007", "metadata": {}, "source": [ "### Grid computation\n", "\n", "Each `(fluid, input_pair)` pair is an independent unit of work, so we compute all of them concurrently via `joblib` (process-based workers; `n_jobs=min(len(FLUIDS)*len(INPUT_PAIRS), cpu_count)`). Most of the wall-clock here is the one-time SVDSBTL surface build (a dense SVD at production resolution), not the per-point lookups; surfaces are cached and reused on subsequent builds.\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "cell-008", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:10:21.746129Z", "iopub.status.busy": "2026-06-27T16:10:21.745983Z", "iopub.status.idle": "2026-06-27T16:11:09.145300Z", "shell.execute_reply": "2026-06-27T16:11:09.144931Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "computed 32 panels (n_jobs=4, cores=4)\n" ] } ], "source": [ "import os\n", "from joblib import Parallel, delayed\n", "\n", "def _compute(fluid, input_pair):\n", " return (fluid, input_pair), compute_error_grid(fluid, input_pair)\n", "\n", "tasks = [(f, ip) for f in FLUIDS for ip in INPUT_PAIRS]\n", "n_jobs = min(len(tasks), os.cpu_count() or 1)\n", "results = Parallel(n_jobs=n_jobs, backend='loky')(\n", " delayed(_compute)(f, ip) for f, ip in tasks\n", ")\n", "GRIDS = {key: grid for key, grid in results}\n", "print(f'computed {len(GRIDS)} panels (n_jobs={n_jobs}, cores={os.cpu_count()})')\n" ] }, { "cell_type": "markdown", "id": "cell-009", "metadata": {}, "source": [ "## Water" ] }, { "cell_type": "markdown", "id": "cell-010", "metadata": {}, "source": [ "### PT" ] }, { "cell_type": "code", "execution_count": 6, "id": "cell-011", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.146619Z", "iopub.status.busy": "2026-06-27T16:11:09.146487Z", "iopub.status.idle": "2026-06-27T16:11:09.204969Z", "shell.execute_reply": "2026-06-27T16:11:09.204577Z" } }, "outputs": [ { "data": { "text/html": [ " \n", " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Water', 'PT', GRIDS[('Water', 'PT')])" ] }, { "cell_type": "markdown", "id": "cell-012", "metadata": {}, "source": [ "### HP" ] }, { "cell_type": "code", "execution_count": 7, "id": "cell-013", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.208380Z", "iopub.status.busy": "2026-06-27T16:11:09.208111Z", "iopub.status.idle": "2026-06-27T16:11:09.232204Z", "shell.execute_reply": "2026-06-27T16:11:09.231845Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Water', 'HP', GRIDS[('Water', 'HP')])" ] }, { "cell_type": "markdown", "id": "cell-014", "metadata": {}, "source": [ "### DT" ] }, { "cell_type": "code", "execution_count": 8, "id": "cell-015", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.235780Z", "iopub.status.busy": "2026-06-27T16:11:09.235508Z", "iopub.status.idle": "2026-06-27T16:11:09.259969Z", "shell.execute_reply": "2026-06-27T16:11:09.259577Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Water', 'DT', GRIDS[('Water', 'DT')])" ] }, { "cell_type": "markdown", "id": "cell-016", "metadata": {}, "source": [ "### PS" ] }, { "cell_type": "code", "execution_count": 9, "id": "cell-017", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.262533Z", "iopub.status.busy": "2026-06-27T16:11:09.262407Z", "iopub.status.idle": "2026-06-27T16:11:09.286920Z", "shell.execute_reply": "2026-06-27T16:11:09.286517Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Water', 'PS', GRIDS[('Water', 'PS')])" ] }, { "cell_type": "markdown", "id": "cell-018", "metadata": {}, "source": [ "## Argon" ] }, { "cell_type": "markdown", "id": "cell-019", "metadata": {}, "source": [ "### PT" ] }, { "cell_type": "code", "execution_count": 10, "id": "cell-020", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.290209Z", "iopub.status.busy": "2026-06-27T16:11:09.290055Z", "iopub.status.idle": "2026-06-27T16:11:09.312505Z", "shell.execute_reply": "2026-06-27T16:11:09.312219Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Argon', 'PT', GRIDS[('Argon', 'PT')])" ] }, { "cell_type": "markdown", "id": "cell-021", "metadata": {}, "source": [ "### HP" ] }, { "cell_type": "code", "execution_count": 11, "id": "cell-022", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.315540Z", "iopub.status.busy": "2026-06-27T16:11:09.315394Z", "iopub.status.idle": "2026-06-27T16:11:09.339965Z", "shell.execute_reply": "2026-06-27T16:11:09.339581Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Argon', 'HP', GRIDS[('Argon', 'HP')])" ] }, { "cell_type": "markdown", "id": "cell-023", "metadata": {}, "source": [ "### DT" ] }, { "cell_type": "code", "execution_count": 12, "id": "cell-024", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.342708Z", "iopub.status.busy": "2026-06-27T16:11:09.342551Z", "iopub.status.idle": "2026-06-27T16:11:09.367182Z", "shell.execute_reply": "2026-06-27T16:11:09.366823Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Argon', 'DT', GRIDS[('Argon', 'DT')])" ] }, { "cell_type": "markdown", "id": "cell-025", "metadata": {}, "source": [ "### PS" ] }, { "cell_type": "code", "execution_count": 13, "id": "cell-026", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.370194Z", "iopub.status.busy": "2026-06-27T16:11:09.369865Z", "iopub.status.idle": "2026-06-27T16:11:09.394606Z", "shell.execute_reply": "2026-06-27T16:11:09.394276Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Argon', 'PS', GRIDS[('Argon', 'PS')])" ] }, { "cell_type": "markdown", "id": "cell-027", "metadata": {}, "source": [ "## Hydrogen" ] }, { "cell_type": "markdown", "id": "cell-028", "metadata": {}, "source": [ "### PT" ] }, { "cell_type": "code", "execution_count": 14, "id": "cell-029", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.397722Z", "iopub.status.busy": "2026-06-27T16:11:09.397454Z", "iopub.status.idle": "2026-06-27T16:11:09.421698Z", "shell.execute_reply": "2026-06-27T16:11:09.421404Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Hydrogen', 'PT', GRIDS[('Hydrogen', 'PT')])" ] }, { "cell_type": "markdown", "id": "cell-030", "metadata": {}, "source": [ "### HP" ] }, { "cell_type": "code", "execution_count": 15, "id": "cell-031", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.424767Z", "iopub.status.busy": "2026-06-27T16:11:09.424479Z", "iopub.status.idle": "2026-06-27T16:11:09.448809Z", "shell.execute_reply": "2026-06-27T16:11:09.448434Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Hydrogen', 'HP', GRIDS[('Hydrogen', 'HP')])" ] }, { "cell_type": "markdown", "id": "cell-032", "metadata": {}, "source": [ "### DT" ] }, { "cell_type": "code", "execution_count": 16, "id": "cell-033", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.451554Z", "iopub.status.busy": "2026-06-27T16:11:09.451409Z", "iopub.status.idle": "2026-06-27T16:11:09.475822Z", "shell.execute_reply": "2026-06-27T16:11:09.475525Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Hydrogen', 'DT', GRIDS[('Hydrogen', 'DT')])" ] }, { "cell_type": "markdown", "id": "cell-034", "metadata": {}, "source": [ "### PS" ] }, { "cell_type": "code", "execution_count": 17, "id": "cell-035", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.478831Z", "iopub.status.busy": "2026-06-27T16:11:09.478553Z", "iopub.status.idle": "2026-06-27T16:11:09.502623Z", "shell.execute_reply": "2026-06-27T16:11:09.502261Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Hydrogen', 'PS', GRIDS[('Hydrogen', 'PS')])" ] }, { "cell_type": "markdown", "id": "cell-036", "metadata": {}, "source": [ "## R1234yf" ] }, { "cell_type": "markdown", "id": "cell-037", "metadata": {}, "source": [ "### PT" ] }, { "cell_type": "code", "execution_count": 18, "id": "cell-038", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.505993Z", "iopub.status.busy": "2026-06-27T16:11:09.505778Z", "iopub.status.idle": "2026-06-27T16:11:09.529867Z", "shell.execute_reply": "2026-06-27T16:11:09.529505Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('R1234yf', 'PT', GRIDS[('R1234yf', 'PT')])" ] }, { "cell_type": "markdown", "id": "cell-039", "metadata": {}, "source": [ "### HP" ] }, { "cell_type": "code", "execution_count": 19, "id": "cell-040", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.532084Z", "iopub.status.busy": "2026-06-27T16:11:09.531828Z", "iopub.status.idle": "2026-06-27T16:11:09.556126Z", "shell.execute_reply": "2026-06-27T16:11:09.555837Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('R1234yf', 'HP', GRIDS[('R1234yf', 'HP')])" ] }, { "cell_type": "markdown", "id": "cell-041", "metadata": {}, "source": [ "### DT" ] }, { "cell_type": "code", "execution_count": 20, "id": "cell-042", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.559176Z", "iopub.status.busy": "2026-06-27T16:11:09.558914Z", "iopub.status.idle": "2026-06-27T16:11:09.583026Z", "shell.execute_reply": "2026-06-27T16:11:09.582683Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('R1234yf', 'DT', GRIDS[('R1234yf', 'DT')])" ] }, { "cell_type": "markdown", "id": "cell-043", "metadata": {}, "source": [ "### PS" ] }, { "cell_type": "code", "execution_count": 21, "id": "cell-044", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.585669Z", "iopub.status.busy": "2026-06-27T16:11:09.585433Z", "iopub.status.idle": "2026-06-27T16:11:09.607872Z", "shell.execute_reply": "2026-06-27T16:11:09.607467Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('R1234yf', 'PS', GRIDS[('R1234yf', 'PS')])" ] }, { "cell_type": "markdown", "id": "cell-045", "metadata": {}, "source": [ "## R245fa" ] }, { "cell_type": "markdown", "id": "cell-046", "metadata": {}, "source": [ "### PT" ] }, { "cell_type": "code", "execution_count": 22, "id": "cell-047", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.611158Z", "iopub.status.busy": "2026-06-27T16:11:09.611018Z", "iopub.status.idle": "2026-06-27T16:11:09.635951Z", "shell.execute_reply": "2026-06-27T16:11:09.635586Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('R245fa', 'PT', GRIDS[('R245fa', 'PT')])" ] }, { "cell_type": "markdown", "id": "cell-048", "metadata": {}, "source": [ "### HP" ] }, { "cell_type": "code", "execution_count": 23, "id": "cell-049", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.639070Z", "iopub.status.busy": "2026-06-27T16:11:09.638794Z", "iopub.status.idle": "2026-06-27T16:11:09.662522Z", "shell.execute_reply": "2026-06-27T16:11:09.662152Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('R245fa', 'HP', GRIDS[('R245fa', 'HP')])" ] }, { "cell_type": "markdown", "id": "cell-050", "metadata": {}, "source": [ "### DT" ] }, { "cell_type": "code", "execution_count": 24, "id": "cell-051", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.665973Z", "iopub.status.busy": "2026-06-27T16:11:09.665840Z", "iopub.status.idle": "2026-06-27T16:11:09.696434Z", "shell.execute_reply": "2026-06-27T16:11:09.696110Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('R245fa', 'DT', GRIDS[('R245fa', 'DT')])" ] }, { "cell_type": "markdown", "id": "cell-052", "metadata": {}, "source": [ "### PS" ] }, { "cell_type": "code", "execution_count": 25, "id": "cell-053", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.698996Z", "iopub.status.busy": "2026-06-27T16:11:09.698864Z", "iopub.status.idle": "2026-06-27T16:11:09.722922Z", "shell.execute_reply": "2026-06-27T16:11:09.722530Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('R245fa', 'PS', GRIDS[('R245fa', 'PS')])" ] }, { "cell_type": "markdown", "id": "cell-054", "metadata": {}, "source": [ "## D6" ] }, { "cell_type": "markdown", "id": "cell-055", "metadata": {}, "source": [ "### PT" ] }, { "cell_type": "code", "execution_count": 26, "id": "cell-056", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.726250Z", "iopub.status.busy": "2026-06-27T16:11:09.725923Z", "iopub.status.idle": "2026-06-27T16:11:09.750395Z", "shell.execute_reply": "2026-06-27T16:11:09.750049Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('D6', 'PT', GRIDS[('D6', 'PT')])" ] }, { "cell_type": "markdown", "id": "cell-057", "metadata": {}, "source": [ "### HP" ] }, { "cell_type": "code", "execution_count": 27, "id": "cell-058", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.753830Z", "iopub.status.busy": "2026-06-27T16:11:09.753544Z", "iopub.status.idle": "2026-06-27T16:11:09.778049Z", "shell.execute_reply": "2026-06-27T16:11:09.777668Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('D6', 'HP', GRIDS[('D6', 'HP')])" ] }, { "cell_type": "markdown", "id": "cell-059", "metadata": {}, "source": [ "### DT" ] }, { "cell_type": "code", "execution_count": 28, "id": "cell-060", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.780994Z", "iopub.status.busy": "2026-06-27T16:11:09.780668Z", "iopub.status.idle": "2026-06-27T16:11:09.802807Z", "shell.execute_reply": "2026-06-27T16:11:09.802458Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('D6', 'DT', GRIDS[('D6', 'DT')])" ] }, { "cell_type": "markdown", "id": "cell-061", "metadata": {}, "source": [ "### PS" ] }, { "cell_type": "code", "execution_count": 29, "id": "cell-062", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.805415Z", "iopub.status.busy": "2026-06-27T16:11:09.805144Z", "iopub.status.idle": "2026-06-27T16:11:09.829310Z", "shell.execute_reply": "2026-06-27T16:11:09.828953Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('D6', 'PS', GRIDS[('D6', 'PS')])" ] }, { "cell_type": "markdown", "id": "cell-063", "metadata": {}, "source": [ "## CO2" ] }, { "cell_type": "markdown", "id": "cell-064", "metadata": {}, "source": [ "### PT" ] }, { "cell_type": "code", "execution_count": 30, "id": "cell-065", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.832652Z", "iopub.status.busy": "2026-06-27T16:11:09.832355Z", "iopub.status.idle": "2026-06-27T16:11:09.857110Z", "shell.execute_reply": "2026-06-27T16:11:09.856737Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('CO2', 'PT', GRIDS[('CO2', 'PT')])" ] }, { "cell_type": "markdown", "id": "cell-066", "metadata": {}, "source": [ "### HP" ] }, { "cell_type": "code", "execution_count": 31, "id": "cell-067", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.861038Z", "iopub.status.busy": "2026-06-27T16:11:09.860694Z", "iopub.status.idle": "2026-06-27T16:11:09.885230Z", "shell.execute_reply": "2026-06-27T16:11:09.884861Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('CO2', 'HP', GRIDS[('CO2', 'HP')])" ] }, { "cell_type": "markdown", "id": "cell-068", "metadata": {}, "source": [ "### DT" ] }, { "cell_type": "code", "execution_count": 32, "id": "cell-069", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.889433Z", "iopub.status.busy": "2026-06-27T16:11:09.889156Z", "iopub.status.idle": "2026-06-27T16:11:09.913263Z", "shell.execute_reply": "2026-06-27T16:11:09.912902Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('CO2', 'DT', GRIDS[('CO2', 'DT')])" ] }, { "cell_type": "markdown", "id": "cell-070", "metadata": {}, "source": [ "### PS" ] }, { "cell_type": "code", "execution_count": 33, "id": "cell-071", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.916782Z", "iopub.status.busy": "2026-06-27T16:11:09.916446Z", "iopub.status.idle": "2026-06-27T16:11:09.941341Z", "shell.execute_reply": "2026-06-27T16:11:09.940974Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('CO2', 'PS', GRIDS[('CO2', 'PS')])" ] }, { "cell_type": "markdown", "id": "cell-072", "metadata": {}, "source": [ "## Helium" ] }, { "cell_type": "markdown", "id": "cell-073", "metadata": {}, "source": [ "### PT" ] }, { "cell_type": "code", "execution_count": 34, "id": "cell-074", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.945215Z", "iopub.status.busy": "2026-06-27T16:11:09.944879Z", "iopub.status.idle": "2026-06-27T16:11:09.968261Z", "shell.execute_reply": "2026-06-27T16:11:09.967962Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Helium', 'PT', GRIDS[('Helium', 'PT')])" ] }, { "cell_type": "markdown", "id": "cell-075", "metadata": {}, "source": [ "### HP" ] }, { "cell_type": "code", "execution_count": 35, "id": "cell-076", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:09.972377Z", "iopub.status.busy": "2026-06-27T16:11:09.972228Z", "iopub.status.idle": "2026-06-27T16:11:09.996945Z", "shell.execute_reply": "2026-06-27T16:11:09.996556Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Helium', 'HP', GRIDS[('Helium', 'HP')])" ] }, { "cell_type": "markdown", "id": "cell-077", "metadata": {}, "source": [ "### DT" ] }, { "cell_type": "code", "execution_count": 36, "id": "cell-078", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:10.000703Z", "iopub.status.busy": "2026-06-27T16:11:10.000542Z", "iopub.status.idle": "2026-06-27T16:11:10.024749Z", "shell.execute_reply": "2026-06-27T16:11:10.024357Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Helium', 'DT', GRIDS[('Helium', 'DT')])" ] }, { "cell_type": "markdown", "id": "cell-079", "metadata": {}, "source": [ "### PS" ] }, { "cell_type": "code", "execution_count": 37, "id": "cell-080", "metadata": { "execution": { "iopub.execute_input": "2026-06-27T16:11:10.027898Z", "iopub.status.busy": "2026-06-27T16:11:10.027757Z", "iopub.status.idle": "2026-06-27T16:11:10.051705Z", "shell.execute_reply": "2026-06-27T16:11:10.051326Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_from_grid('Helium', 'PS', GRIDS[('Helium', 'PS')])" ] }, { "cell_type": "markdown", "id": "cell-081", "metadata": {}, "source": [ "## Notes\n", "\n", "- **PT panels** mask a 2-percent band around the saturation line, since (T, p) inside that band straddles the dome at ULP scale and SVDSBTL is single-phase only.\n", "- **HP panels** have no explicit mask: the two-phase dome is a true 2D region in (h, p), and SVDSBTL surface queries inside it throw — caught and reported as NaN. The white wedge in each HP heatmap is the dome.\n", "- **PS panels** mirror HP: the surface tabulates `(rho, T, h, u, w)` over `(p, s)` with `s` as the input axis. No mask — the two-phase dome is a 2D wedge in (s, p), shown as the white region where surface queries are NaN.\n", "- **DT panels** validate **pressure** (`rho` is the input, hence exact): `log10 |\\u0394p/p|` over `(T, rho)`. No mask — the two-phase dome is a valid DT region (SVDSBTL routes it through the dome lever-rule, matching HEOS `p_sat(T)`), so it shows near-zero error rather than a NaN wedge.\n", "- `R1234yf`, `R245fa`, and `D6` have narrow native `Tmin/Tmax` envelopes that may clip the plot domain below the nominal `0.6 Tc` lower bound.\n", "- All panels are computed concurrently via `joblib` (process workers; `n_jobs=min(panels, cpu_count)`). Most of the run time is the one-time SVDSBTL surface build, not the per-point lookups.\n", "- This notebook is re-executed on every `make html` build (`Web/conf.py` pre-executes all `.ipynb` via `jupyter nbconvert --execute`). Surfaces are cached at the directory printed in the first code cell (default `~/.CoolProp/SVDTables-docs/`, override via `COOLPROP_ALTERNATIVE_SVDTABLES_DIRECTORY`). First build of a fresh (fluid, input_pair) takes 20-80 s; subsequent builds reuse the serialized surface and run in ~1 s.\n", "- Source generator: `Web/coolprop/_gen/gen_SVDSBTLValidation.py`.\n" ] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.3" } }, "nbformat": 4, "nbformat_minor": 5 }