{ "cells": [ { "cell_type": "markdown", "id": "c51c40e7", "metadata": {}, "source": [ "# Heat-exchanger speedup with the SVDSBTL backend\n", "\n", "This page reproduces the computational-efficiency result of §3.2 of\n", "\n", "> I.H. Bell, S. Quoilin, E. Georges, J.E. Braun, E.A. Groll, W.T. Horton,\n", "> V. Lemort, \"A generalized moving-boundary algorithm to predict the heat\n", "> transfer rate of counterflow heat exchangers for any phase\n", "> configuration,\" *Applied Thermal Engineering* 79 (2015) 192–201.\n", "\n", "A moving-boundary model of a water-heated n-Propane evaporator is solved with\n", "the full Helmholtz equation of state (`HEOS`) and with the SVD-compressed\n", "tabular backend (`SVDSBTL&HEOS`). The two agree on the predicted heat-transfer\n", "rate while the table lookup runs much faster — the `p,h → T,ρ` property\n", "evaluation (Water has the library's most expensive EOS) is the bottleneck the\n", "table replaces.\n", "\n", "The solver lives in a small importable module so you can reuse it:" ] }, { "cell_type": "raw", "id": "3a9f2821", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ":doc:`SVDSBTL backend reference ` · :download:`Download the solver module (hx_moving_boundary.py) `" ] }, { "cell_type": "code", "execution_count": 1, "id": "c66abc07", "metadata": { "execution": { "iopub.execute_input": "2026-06-11T01:28:33.760821Z", "iopub.status.busy": "2026-06-11T01:28:33.760650Z", "iopub.status.idle": "2026-06-11T01:28:36.419634Z", "shell.execute_reply": "2026-06-11T01:28:36.418973Z" } }, "outputs": [], "source": [ "import os, time\n", "import numpy as np\n", "import plotly.graph_objects as go\n", "import plotly.io as pio\n", "pio.renderers.default = 'notebook_connected'\n", "from hx_moving_boundary import make_evaporator, ORACLE_Q" ] }, { "cell_type": "code", "execution_count": 2, "id": "8492a137", "metadata": { "execution": { "iopub.execute_input": "2026-06-11T01:28:36.421905Z", "iopub.status.busy": "2026-06-11T01:28:36.421644Z", "iopub.status.idle": "2026-06-11T01:29:44.521190Z", "shell.execute_reply": "2026-06-11T01:29:44.520751Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HEOS providers built in 0.005 s\n", "SVDSBTL tables ready in 68.091 s (one-time, cached)\n" ] } ], "source": [ "# Build providers for both backends. The first SVDSBTL construction builds and\n", "# caches the compressed tables (~7 s/fluid, cached under ~/.CoolProp/SVDTables);\n", "# this one-time cost is reported separately and excluded from per-run timing.\n", "t0 = time.perf_counter()\n", "hx_heos = make_evaporator('HEOS', A=4.0)\n", "t_build_heos = time.perf_counter() - t0\n", "\n", "t0 = time.perf_counter()\n", "hx_svd = make_evaporator('SVDSBTL&HEOS', A=4.0)\n", "t_build_svd = time.perf_counter() - t0\n", "\n", "print('HEOS providers built in %.3f s' % t_build_heos)\n", "print('SVDSBTL tables ready in %.3f s (one-time, cached)' % t_build_svd)" ] }, { "cell_type": "code", "execution_count": 3, "id": "1cc8e680", "metadata": { "execution": { "iopub.execute_input": "2026-06-11T01:29:44.522763Z", "iopub.status.busy": "2026-06-11T01:29:44.522599Z", "iopub.status.idle": "2026-06-11T01:29:44.540766Z", "shell.execute_reply": "2026-06-11T01:29:44.540326Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HEOS : Q = 4577.2422 W eps = 0.999070\n", "SVDSBTL&HEOS : Q = 4577.2431 W eps = 0.999070\n", "|dQ|/Q backend agreement = 2.02e-07\n", "|dQ|/Q vs reference oracle = 8.17e-11\n" ] } ], "source": [ "Q_heos = hx_heos.run()\n", "eps_heos = Q_heos / hx_heos.Qmax\n", "Q_svd = hx_svd.run()\n", "eps_svd = Q_svd / hx_svd.Qmax\n", "rel = abs(Q_heos - Q_svd) / Q_heos\n", "print('HEOS : Q = %.4f W eps = %.6f' % (Q_heos, eps_heos))\n", "print('SVDSBTL&HEOS : Q = %.4f W eps = %.6f' % (Q_svd, eps_svd))\n", "print('|dQ|/Q backend agreement = %.2e' % rel)\n", "print('|dQ|/Q vs reference oracle = %.2e' % (abs(Q_heos - ORACLE_Q) / ORACLE_Q))\n", "assert rel < 1e-5, 'backends disagree on Q'\n", "assert abs(Q_heos - ORACLE_Q) / ORACLE_Q < 1e-6, 'HEOS drifted from the HX.py oracle'" ] }, { "cell_type": "code", "execution_count": 4, "id": "b108d2d1", "metadata": { "execution": { "iopub.execute_input": "2026-06-11T01:29:44.542348Z", "iopub.status.busy": "2026-06-11T01:29:44.542189Z", "iopub.status.idle": "2026-06-11T01:29:44.914689Z", "shell.execute_reply": "2026-06-11T01:29:44.914208Z" } }, "outputs": [ { "data": { "text/html": [ " \n", " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Effectiveness vs area, both backends overlaid. Reuse the built HX objects:\n", "# only the area changes between points, so no rebuild is needed.\n", "areas = np.logspace(np.log10(0.1), np.log10(10.0), 25)\n", "\n", "def sweep(hx):\n", " out = []\n", " for A in areas:\n", " hx.A_h = hx.A_c = A\n", " out.append(hx.run() / hx.Qmax)\n", " return out\n", "\n", "eps_h = sweep(hx_heos)\n", "eps_s = sweep(hx_svd)\n", "\n", "fig = go.Figure()\n", "fig.add_trace(go.Scatter(x=areas, y=eps_h, mode='lines', name='HEOS',\n", " line=dict(width=4)))\n", "fig.add_trace(go.Scatter(x=areas, y=eps_s, mode='markers', name='SVDSBTL&HEOS',\n", " marker=dict(size=7)))\n", "fig.update_xaxes(type='log', title='Heat transfer area A [m²]')\n", "fig.update_yaxes(title='Effectiveness ε = Q / Q_max')\n", "fig.update_layout(\n", " title='Counterflow evaporator effectiveness — HEOS vs SVDSBTL',\n", " template='simple_white', legend=dict(x=0.02, y=0.98))\n", "fig" ] }, { "cell_type": "code", "execution_count": 5, "id": "0db497ec", "metadata": { "execution": { "iopub.execute_input": "2026-06-11T01:29:44.916333Z", "iopub.status.busy": "2026-06-11T01:29:44.916156Z", "iopub.status.idle": "2026-06-11T01:29:47.580989Z", "shell.execute_reply": "2026-06-11T01:29:47.580518Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "per-run timing averaged over N = 200 runs at A = 4 m^2\n", " HEOS : 12.943 ms/run\n", " SVDSBTL&HEOS : 0.290 ms/run\n", " speedup (HEOS / SVDSBTL) = 44.7x\n" ] } ], "source": [ "# Per-run timing at A = 4 m^2, the paper's ms/run unit. Both backends run the\n", "# identical Python harness, so the residual pybind dispatch tax is shared.\n", "N = max(1, int(os.environ.get('HX_REPEATS', 200)))\n", "\n", "def per_run_ms(hx, N):\n", " hx.A_h = hx.A_c = 4.0\n", " hx.run() # warmup\n", " t0 = time.perf_counter()\n", " for _ in range(N):\n", " hx.run()\n", " return (time.perf_counter() - t0) / N * 1e3\n", "\n", "ms_heos = per_run_ms(hx_heos, N)\n", "ms_svd = per_run_ms(hx_svd, N)\n", "print('per-run timing averaged over N = %d runs at A = 4 m^2' % N)\n", "print(' HEOS : %.3f ms/run' % ms_heos)\n", "print(' SVDSBTL&HEOS : %.3f ms/run' % ms_svd)\n", "print(' speedup (HEOS / SVDSBTL) = %.1fx' % (ms_heos / ms_svd))" ] }, { "cell_type": "markdown", "id": "33dc7ecf", "metadata": {}, "source": [ "## What this shows\n", "\n", "Both backends predict the same heat-transfer rate (Q agreement on the order of\n", "`1e-7` relative), yet SVDSBTL is several times faster *per full HX run* — the\n", "speedup coming entirely from replacing Water's expensive `p,h → T,ρ` inversion\n", "with a table lookup, exactly as in §3.2 of the paper.\n", "\n", "The per-run ratio above is a **lower bound** on SVDSBTL's library-level\n", "advantage: both backends pay the same Python/pybind dispatch cost per property\n", "call, which compresses the ratio. The companion pure-C++ demo\n", "(`dev/demo_hx_speedup.cpp`) removes that overhead and reports a larger ratio.\n", "Absolute ms/run also differ from the paper's 24.6 / 2.46 ms — those were\n", "32-bit Python on 2011 hardware.\n", "\n", "**A note on the test case.** This evaporator matches the recovered original\n", "script (`dev/reference/HX.py`): the hot stream (Water) carries ṁ = 0.1 kg/s\n", "and the cold stream (n-Propane) ṁ = 0.01 kg/s, with two-phase\n", "α = 1000 and single-phase α = 100 W m⁻² K⁻¹. The mass flows are transposed\n", "relative to the paper's printed Table 3; this is the assignment that produces\n", "the evaporating cell structure shown in the paper's figures." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "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 }