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