Preserving spatial derivatives with safeguards¶
In this example, which has been adopted from the ZFP evaluation at https://computing.llnl.gov/projects/zfp/zfp-and-derivatives, we compare how three different lossy compressors (ZFP, SZ3, and SPERR) affect the Laplacian of the data, and apply safeguards to guarantee that the Laplacian is preserved. We also briefly investigate the impact of tuning the error bound for a lossy error-bounded compressor to optimize the compression ratio after applying safeguards. We further compare the safeguards with the compressor configuration auto-tuner OptZConfig.
QPET supports mean error bounds over non-overlapping blocks of data, but not over overlapping windows of data, which are required to preserve an error over a finite-difference approximated spatial derivative. To be safe, QPET would need to be configured with a maximally conservative pointwise error bound, which is no different than configuring SPERR (or SZ3 or ZFP) with this error bound. Therefore, we do not compare with QPET in this example.
from pathlib import Path
import humanize
import matplotlib as mpl
import numpy as np
import pandas as pd
from matplotlib import patheffects as PathEffects
from matplotlib import pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from mpl_toolkits.axes_grid1 import make_axes_locatable
x = y = np.linspace(-1, 1, 1024)
Y, X = np.meshgrid(y, x, indexing="ij")
dx = float(x[1] - x[0])
dy = float(y[1] - y[0])
dy, dx
(0.0019550342130987275, 0.0019550342130987275)
def compute_corrections_percentage(my_U: np.ndarray, orig_U: np.ndarray) -> float:
return np.mean(my_U != orig_U)
# plot the Laplacian DU for a 2D field my_U
def plot_DU(
compute_DU,
my_U,
cr,
ax,
title,
DU_eb_abs,
transform_symbol=None,
my_DU=None,
corr=None,
my_U_it=None,
cr_it=None,
include_boundary=False,
inset=True,
inset_offset=(0.05, 0.05),
):
show_err = my_DU is None
if my_DU is None:
my_DU = compute_DU(my_U)
DU = compute_DU(U)
vmin = np.nanmin(DU)
vmax = np.nanmax(DU)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
ax.set_title(title)
if show_err:
err_v = np.mean(~(np.abs(my_DU - DU) <= DU_eb_abs))
err_v = (
0
if err_v == 0
else np.format_float_positional(100 * err_v, precision=1, min_digits=1)
+ "%"
)
if err_v == "0.0%":
err_v = "<0.05%"
t = ax.text(
0.95,
0.05,
f"V={err_v}",
ha="right",
va="bottom",
transform=ax.transAxes,
)
t.set_bbox(dict(facecolor="white", alpha=0.5, edgecolor="black"))
# create a colourmap that includes the entire finite range of my_DU but
# assigns colours based on the range of DU to ensure consistent colours
cx = np.linspace(
(
np.minimum(np.amin(np.nan_to_num(my_DU, nan=0, posinf=0, neginf=0)), vmin)
- vmin
)
/ (vmax - vmin),
(
np.maximum(vmax, np.amax(np.nan_to_num(my_DU, nan=0, posinf=0, neginf=0)))
- vmin
)
/ (vmax - vmin),
256,
)
cmap = LinearSegmentedColormap.from_list("RdBu_r_ext", plt.get_cmap("RdBu_r")(cx))
ax.fill_between(
[0, 1],
[1, 1],
hatch="XX",
edgecolor="magenta",
facecolor="lavenderblush",
transform=ax.transAxes,
zorder=-13,
)
im = ax.imshow(my_DU, cmap=cmap, origin="lower", extent=(-1, 1, -1, 1), zorder=-12)
ax.contour(
X if include_boundary else X[1:-1, 1:-1],
Y if include_boundary else Y[1:-1, 1:-1],
my_DU,
levels=10,
colors="lightgrey",
vmin=vmin,
vmax=vmax,
alpha=0.25,
zorder=-11,
)
ax.set_rasterization_zorder(-10)
ax.set_aspect("equal")
if show_err:
if corr is not None:
da_hatch = my_U == corr
if my_U_it is None:
da_corr = da_hatch.astype(float)
else:
da_hatch_it = my_U_it == corr
da_corr = (~da_hatch).astype(float) + (~da_hatch_it).astype(float)
axin = ax.inset_axes(
[*inset_offset, 1 / 3, 1 / 3],
xticks=[],
yticks=[],
)
axin.imshow(
da_corr,
cmap=mpl.colors.ListedColormap(["white", "green", "lawngreen"]),
vmin=0,
vmax=2,
origin="lower",
extent=(-1, 1, -1, 1),
zorder=-10,
)
axin.set_title(
"Corrections",
path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")],
)
elif inset:
da_err = ~(np.abs(my_DU - DU) <= DU_eb_abs)
axin = ax.inset_axes(
[*inset_offset, 1 / 3, 1 / 3],
xticks=[],
yticks=[],
)
axin.imshow(
da_err,
cmap=mpl.colors.ListedColormap(["white", "red"]),
vmin=0,
vmax=1,
origin="lower",
extent=(-1, 1, -1, 1),
zorder=-10,
)
axin.set_title(
"Violations",
path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")],
)
t = ax.text(
0.95,
0.95,
(
rf"$\times$ {np.round(cr, 2)}"
+ ("" if cr_it is None else rf" ($\times$ {np.round(cr_it, 2)})")
)
if show_err
else humanize.naturalsize(U.nbytes, binary=True),
ha="right",
va="top",
transform=ax.transAxes,
)
t.set_bbox(dict(facecolor="white", alpha=0.5, edgecolor="black"))
cb = ax.get_figure().colorbar(im, cax=cax, orientation="vertical")
if show_err:
cb.ax.fill_between(
cb.ax.get_xlim(),
cb.ax.get_ylim()[0],
np.amin(DU),
hatch="xxx",
ec="w",
fc="none",
)
cb.ax.fill_between(
cb.ax.get_xlim(),
np.amax(DU),
cb.ax.get_ylim()[1],
hatch="xxx",
ec="w",
fc="none",
)
def table_DU(
compute_DU,
my_U,
cr,
title,
DU_eb_abs,
corr,
) -> pd.DataFrame:
DU = compute_DU(U)
my_DU = compute_DU(my_U)
err_U_inf = np.amax(np.abs(my_U - U))
err_DU_inf = np.amax(np.abs(my_DU - DU))
err_DU_inf_fin = np.nanmax(
np.nan_to_num(
np.abs(my_DU - DU),
nan=np.nan,
posinf=np.nan,
neginf=np.nan,
)
)
err_DU_2 = np.sqrt(np.mean(np.square(my_DU - DU)))
err_DU_2_fin = np.sqrt(
np.nanmean(
np.nan_to_num(
np.square(my_DU - DU),
nan=np.nan,
posinf=np.nan,
neginf=np.nan,
)
)
)
err_v = np.mean(~(np.abs(my_DU - DU) <= DU_eb_abs))
err_v = (
0
if err_v == 0
else np.format_float_positional(100 * err_v, precision=1, min_digits=1) + "%"
)
if err_v == "0.0%":
err_v = "<0.05%"
corr = None if corr is None else compute_corrections_percentage(my_U, corr)
corr = (
""
if corr is None
else (
0
if corr == 0
else np.format_float_positional(100 * corr, precision=1, min_digits=1) + "%"
)
)
if corr == "0.0%":
corr = "<0.05%"
return pd.DataFrame(
{
"Compressor": [title[0]],
r"$\epsilon_{abs}$": [title[1]],
"Safeguarded": [title[2]],
"Corrections": [title[3]],
r"$L_{\infty}(\hat{u})$": [f"{err_U_inf:.02}"],
r"$L_{\infty}(\Delta \hat{u})$": [
f"{err_DU_inf:.02}".replace("nan", "NaN")
+ ("" if np.isfinite(err_DU_inf) else f" ({err_DU_inf_fin:.02})"),
],
r"$L_{2}(\Delta \hat{u})$": [
f"{err_DU_2:.02}".replace("nan", "NaN")
+ ("" if np.isfinite(err_DU_2) else f" ({err_DU_2_fin:.02})"),
],
"V": [err_v],
"C": [corr],
"CR": [rf"$\times$ {np.round(cr, 2)}"],
}
)
Example 1: $u(x, y) = {(x^2 + y^2)}^{3 \mathbin{/} 2} \mathbin{/} 9$¶
The Laplacian of $u$ is $\Delta u = \sqrt{x^2 + y^2}$.
We evaluate the Laplacian numerically on the original $u$ and the compressed $\hat{u}$ by using the second-order central difference approximation.
U = np.power(X**2 + Y**2, 3 / 2) / 9
# analytical Laplacian on uncompressed
DU = np.sqrt(X**2 + Y**2)
# absolute error bound on the Laplacian, around 1% of the range
DU_eb_abs = 0.01
from compression_safeguards import SafeguardKind
from compression_safeguards.utils.bindings import Bindings
# we use a constant grid spacing and the valid boundary condition to preserve
# an absolute error bound over the Laplacian, but not on the data boundaries
# where data points do not have a left, right, upper, and lower neighbour
qoi_eb_stencil = SafeguardKind.qoi_eb_stencil.value(
qoi="""
(
finite_difference(x, order=2, accuracy=2, type=0, axis=0, grid_spacing=c["dy"]) +
finite_difference(x, order=2, accuracy=2, type=0, axis=1, grid_spacing=c["dx"])
)
""",
neighbourhood=[
dict(axis=0, before=1, after=1, boundary="valid"),
dict(axis=1, before=1, after=1, boundary="valid"),
],
type="abs",
eb=DU_eb_abs,
)
# evaluate the Laplacian numerically by evaluating the quantity of interest
def compute_DU(U):
return qoi_eb_stencil.evaluate_qoi(U, late_bound=Bindings(dx=dx, dy=dy))
import observe
observations = []
Lossless compression¶
We first compress the data losslessly with ZStandard at level 22, which gives maximum compression, to provide a baseline.
# compressed with Zstdandard
from numcodecs_wasm_zstd import Zstd
zstd = Zstd(level=22)
with observe.observe(zstd, observations):
U_zstd_enc = zstd.encode(U)
U_zstd = zstd.decode(U_zstd_enc)
U_zstd_cr = U.nbytes / U_zstd_enc.nbytes
Compressing u with lossy compressors¶
We configure each compressor with an absolute error bound of $1 \cdot 10^{-7}$ over the u array. For each compressor, we also test two more error bounds to investigate the impact of tuning: one that produces around 1% of violations for the absolute error bound over the Laplacian, and one that produces no violations.
# absolute error bound for error-bounded lossy compression
eb_abs = 1e-7
# we test several error bounds to showcase the impact of tuning
# - eb_abs: common error bound
# - tuned to get ~1% violations
# - tuned to get 0 violations
eb_abs_zfps = [eb_abs, 5e-8, 2e-8]
# compressed with ZFP
from numcodecs_wasm_zfp import Zfp
zfp = dict()
U_zfp = dict()
U_zfp_cr = dict()
for eb_abs_zfp in eb_abs_zfps:
zfp[eb_abs_zfp] = Zfp(mode="fixed-accuracy", tolerance=eb_abs_zfp)
with observe.observe(zfp[eb_abs_zfp], observations):
U_zfp_enc = zfp[eb_abs_zfp].encode(U)
U_zfp[eb_abs_zfp] = zfp[eb_abs_zfp].decode(U_zfp_enc)
U_zfp_cr[eb_abs_zfp] = U.nbytes / U_zfp_enc.nbytes
eb_abs_sz3s = [eb_abs, 1.5e-7, 5e-9]
# compressed with SZ3
from numcodecs_wasm_sz3 import Sz3
sz3 = dict()
U_sz3 = dict()
U_sz3_cr = dict()
for eb_abs_sz3 in eb_abs_sz3s:
sz3[eb_abs_sz3] = Sz3(eb_mode="abs", eb_abs=eb_abs_sz3)
with observe.observe(sz3[eb_abs_sz3], observations):
U_sz3_enc = sz3[eb_abs_sz3].encode(U)
U_sz3[eb_abs_sz3] = sz3[eb_abs_sz3].decode(U_sz3_enc)
U_sz3_cr[eb_abs_sz3] = U.nbytes / U_sz3_enc.nbytes
eb_abs_sperrs = [eb_abs, 3.5e-8, 8e-9]
# compressed with SPERR
from numcodecs_wasm_sperr import Sperr
sperr = dict()
U_sperr = dict()
U_sperr_cr = dict()
for eb_abs_sperr in eb_abs_sperrs:
sperr[eb_abs_sperr] = Sperr(mode="pwe", pwe=eb_abs_sperr)
with observe.observe(sperr[eb_abs_sperr], observations):
U_sperr_enc = sperr[eb_abs_sperr].encode(U)
U_sperr[eb_abs_sperr] = sperr[eb_abs_sperr].decode(U_sperr_enc)
U_sperr_cr[eb_abs_sperr] = U.nbytes / U_sperr_enc.nbytes
# compressed to constant zero
from numcodecs_zero import ZeroCodec
zero = ZeroCodec()
with observe.observe(zero, observations):
U_zero_enc = zero.encode(U)
U_zero = zero.decode(U_zero_enc)
Compressing u using the safeguarded lossy compressors¶
We configure the safeguards to bound the pointwise absolute error on the derived Laplacian.
For this first example, we only evaluate the Laplacian numerically on non-boundary points, allowing us to only use second-order central finite differences.
from numcodecs_safeguards import SafeguardedCodec
U_sg_qoi = dict()
U_sg_qoi_cr = dict()
# compressed with the safeguards with an absolute error bound over the Laplacian
for codecs in [zfp, sz3, sperr, {0: zero}]:
U_sg_qoi_codec = dict()
U_sg_qoi_codec_cr = dict()
for eb_abs_codec, codec in codecs.items():
codec_sg_qoi = SafeguardedCodec(
codec=codec,
safeguards=[qoi_eb_stencil],
fixed_constants=dict(dx=dx, dy=dy),
)
with observe.observe(codec_sg_qoi, observations):
U_sg_qoi_enc = codec_sg_qoi.encode(U)
U_sg_qoi_codec[eb_abs_codec] = codec_sg_qoi.decode(U_sg_qoi_enc)
U_sg_qoi_codec_cr[eb_abs_codec] = U.nbytes / np.asarray(U_sg_qoi_enc).nbytes
U_sg_qoi[codec.codec_id] = U_sg_qoi_codec
U_sg_qoi_cr[codec.codec_id] = U_sg_qoi_codec_cr
U_sg_it_qoi = dict()
U_sg_it_qoi_cr = dict()
# compressed with the safeguards with an absolute error bound over the Laplacian
for codecs in [zfp, sz3, sperr, {0: zero}]:
U_sg_it_qoi_codec = dict()
U_sg_it_qoi_codec_cr = dict()
for eb_abs_codec, codec in codecs.items():
codec_sg_it_qoi = SafeguardedCodec(
codec=codec,
safeguards=[qoi_eb_stencil],
fixed_constants=dict(dx=dx, dy=dy),
# use iteration to refine the corrections
compute=dict(unstable_iterative=True),
)
with observe.observe(codec_sg_it_qoi, observations):
U_sg_it_qoi_enc = codec_sg_it_qoi.encode(U)
U_sg_it_qoi_codec[eb_abs_codec] = codec_sg_it_qoi.decode(U_sg_it_qoi_enc)
U_sg_it_qoi_codec_cr[eb_abs_codec] = (
U.nbytes / np.asarray(U_sg_it_qoi_enc).nbytes
)
U_sg_it_qoi[codec.codec_id] = U_sg_it_qoi_codec
U_sg_it_qoi_cr[codec.codec_id] = U_sg_it_qoi_codec_cr
U_sg_lossless_qoi = dict()
U_sg_lossless_qoi_cr = dict()
# compressed with the safeguards with an absolute error bound over the Laplacian
for codecs in [zfp, sz3, sperr, {0: zero}]:
U_sg_lossless_qoi_codec = dict()
U_sg_lossless_qoi_codec_cr = dict()
for eb_abs_codec, codec in codecs.items():
codec_sg_lossless_qoi = SafeguardedCodec(
codec=codec,
safeguards=[qoi_eb_stencil],
fixed_constants=dict(dx=dx, dy=dy),
# produce lossless corrections and refine them with iteration
compute=dict(unstable_iterative=True, unstable_lossless_corrections=True),
)
with observe.observe(codec_sg_lossless_qoi, observations):
U_sg_lossless_qoi_enc = codec_sg_lossless_qoi.encode(U)
U_sg_lossless_qoi_codec[eb_abs_codec] = codec_sg_lossless_qoi.decode(
U_sg_lossless_qoi_enc
)
U_sg_lossless_qoi_codec_cr[eb_abs_codec] = (
U.nbytes / np.asarray(U_sg_lossless_qoi_enc).nbytes
)
U_sg_lossless_qoi[codec.codec_id] = U_sg_lossless_qoi_codec
U_sg_lossless_qoi_cr[codec.codec_id] = U_sg_lossless_qoi_codec_cr
Compressing u with OptZConfig¶
We configure OptZConfig with a custom safety violations metric, implemented in Python, that computes the percentage of violations $V$. We then maximise the score
$$ \textrm{score} = \begin{cases} -\textrm{V} \quad &\text{if } \textrm{V} > 0 \\ \textrm{CR} \quad &\text{otherwise} \end{cases} $$
using the FRAZ search algorithm with 25 iterations, where CR is the achieved compression ratio. Since FRAZ seems to struggle with finding sufficient absolute error bounds spread across several orders of magnitude, we search for bounds in logarithmic space by wrapping each codec in an Exponential<CODEC> meta-codec.
import numcodecs
class SafetyViolationsMetric(numcodecs.abc.Codec):
codec_id = "safety-violations-metric"
def __init__(self):
self._data = None
def encode(self, buf):
# store the original data for later
self._data = np.array(buf, copy=True)
# return no metric
return np.empty(0, dtype=np.float64)
def decode(self, buf, out=None):
# compute the violations
data_DU = compute_DU(self._data)
buf_DU = compute_DU(buf)
violations = np.mean(~(np.abs(buf_DU - data_DU) <= DU_eb_abs))
self._data = None
# return the violations score metric
return numcodecs.compat.ndarray_copy(np.float64(violations), out)
numcodecs.registry.register_codec(SafetyViolationsMetric)
class ExponentialZfp(Zfp):
codec_id = "e-zfp.rs"
def __new__(cls, tolerance: float, **kwargs):
codec = super().__new__(cls, tolerance=np.exp(tolerance), **kwargs)
codec._tolerance = tolerance
return codec
def get_config(self):
return {
**super().get_config(),
"id": type(self).codec_id,
"tolerance": self._tolerance,
}
class ExponentialSz3(Sz3):
codec_id = "e-sz3.rs"
def __new__(cls, eb_abs: float, **kwargs):
codec = super().__new__(cls, eb_abs=np.exp(eb_abs), **kwargs)
codec._eb_abs = eb_abs
return codec
def get_config(self):
return {
**super().get_config(),
"id": type(self).codec_id,
"eb_abs": self._eb_abs,
}
class ExponentialSperr(Sperr):
codec_id = "e-sperr.rs"
def __new__(cls, pwe: float, **kwargs):
codec = super().__new__(cls, pwe=np.exp(pwe), **kwargs)
codec._pwe = pwe
return codec
def get_config(self):
return {**super().get_config(), "id": type(self).codec_id, "pwe": self._pwe}
numcodecs.registry.register_codec(ExponentialZfp)
numcodecs.registry.register_codec(ExponentialSz3)
numcodecs.registry.register_codec(ExponentialSperr)
from numcodecs_wasm_pressio import Pressio
U_optzconfig = dict()
U_optzconfig_cr = dict()
for codec, parameter, lower_bound in [
(zfp[eb_abs], "tolerance", 1e-9), # decent guess
(sz3[eb_abs], "eb_abs", 1e-9), # decent guess
(sperr[eb_abs], "pwe", 1e-9), # decent guess
]:
optzconfig = Pressio(
compressor_id="opt",
compressor_config={
"opt:output": ["composite:score"],
"opt:inputs": [f"numcodecs.rs:{parameter}"],
"opt:lower_bound": np.log(lower_bound),
"opt:upper_bound": np.log(eb_abs),
"opt:max_iterations": 25,
"opt:objective_mode_name": "max",
},
early_config={
"opt:compressor": "pressio",
"pressio:compressor": "numcodecs.rs",
**{
f"numcodecs.rs:{k}": f"e-{v}" if k == "id" else v
for k, v in codec.get_config().items()
},
"opt:search": "fraz",
"pressio:metric": "composite",
"composite:plugins": ["size", "numcodecs.rs-metric"],
"composite:scripts": [
"""
violations = metrics["numcodecs.rs-metric:decompression"]
if violations > 0 then
return "score", -violations
else
return "score", metrics["size:compression_ratio"]
end
"""
],
"numcodecs.rs-metric:id": "safety-violations-metric",
},
)
with observe.observe(optzconfig, observations):
U_optzconfig_enc = optzconfig.encode(U)
U_optzconfig[codec.codec_id] = optzconfig.decode(U_optzconfig_enc)
U_optzconfig_cr[codec.codec_id] = U.nbytes / np.asarray(U_optzconfig_enc).nbytes
rank={0,1,} iter={0} input={-18.4207,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={1} input={-19.649,} output={5.93138,} objective={5.93138}
rank={0,1,} iter={2} input={-17.2111,} output={-0.0023514,} objective={-0.0023514}
rank={0,1,} iter={3} input={-18.9025,} output={6.3745,} objective={6.3745}
rank={0,1,} iter={4} input={-20.7219,} output={5.61127,} objective={5.61127}
rank={0,1,} iter={5} input={-18.5536,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={6} input={-20.1564,} output={5.61127,} objective={5.61127}
rank={0,1,} iter={7} input={-18.4872,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={8} input={-19.2369,} output={6.3745,} objective={6.3745}
rank={0,1,} iter={9} input={-18.6931,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={10} input={-19.0697,} output={6.3745,} objective={6.3745}
rank={0,1,} iter={11} input={-19.4029,} output={6.3745,} objective={6.3745}
rank={0,1,} iter={12} input={-20.439,} output={5.61127,} objective={5.61127}
rank={0,1,} iter={13} input={-19.8743,} output={5.93138,} objective={5.93138}
rank={0,1,} iter={14} input={-18.7626,} output={6.3745,} objective={6.3745}
rank={0,1,} iter={15} input={-18.6229,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={16} input={-18.6582,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={17} input={-18.5889,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={18} input={-18.5204,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={19} input={-18.4531,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={20} input={-18.6408,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={21} input={-18.5708,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={22} input={-18.6757,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={23} input={-18.6056,} output={6.76896,} objective={6.76896}
rank={0,1,} iter={24} input={-18.4699,} output={6.76896,} objective={6.76896}
final_iter={25} inputs={-18.4207,} output={6.76896,}
rank={0,1,} iter={0} input={-18.4207,} output={-0.00486843,} objective={-0.00486843}
rank={0,1,} iter={1} input={-19.649,} output={118.396,} objective={118.396}
rank={0,1,} iter={2} input={-17.2111,} output={-0.0305634,} objective={-0.0305634}
rank={0,1,} iter={3} input={-20.7233,} output={96.9523,} objective={96.9523}
rank={0,1,} iter={4} input={-20.0749,} output={113.257,} objective={113.257}
rank={0,1,} iter={5} input={-19.3665,} output={157.208,} objective={157.208}
rank={0,1,} iter={6} input={-16.1189,} output={-0.00436483,} objective={-0.00436483}
rank={0,1,} iter={7} input={-17.0505,} output={-0.00902647,} objective={-0.00902647}
rank={0,1,} iter={8} input={-19.3903,} output={152.492,} objective={152.492}
rank={0,1,} iter={9} input={-18.2085,} output={-2.29779e-05,} objective={-2.29779e-05}
rank={0,1,} iter={10} input={-20.349,} output={102.448,} objective={102.448}
rank={0,1,} iter={11} input={-18.7875,} output={-0.000189567,} objective={-0.000189567}
rank={0,1,} iter={12} input={-19.8528,} output={115.113,} objective={115.113}
rank={0,1,} iter={13} input={-19.077,} output={187.589,} objective={187.589}
rank={0,1,} iter={14} input={-17.7089,} output={-0.000787949,} objective={-0.000787949}
rank={0,1,} iter={15} input={-19.1814,} output={154.29,} objective={154.29}
rank={0,1,} iter={16} input={-16.5854,} output={-0.00369273,} objective={-0.00369273}
rank={0,1,} iter={17} input={-18.9322,} output={-0.000220204,} objective={-0.000220204}
rank={0,1,} iter={18} input={-20.535,} output={121.474,} objective={121.474}
rank={0,1,} iter={19} input={-19.1045,} output={177.54,} objective={177.54}
rank={0,1,} iter={20} input={-17.9583,} output={-0.000146484,} objective={-0.000146484}
rank={0,1,} iter={21} input={-19.0408,} output={-1.91482e-06,} objective={-1.91482e-06}
rank={0,1,} iter={22} input={-17.46,} output={-0.0263633,} objective={-0.0263633}
rank={0,1,} iter={23} input={-19.0887,} output={183.723,} objective={183.723}
rank={0,1,} iter={24} input={-16.3531,} output={-0.00672677,} objective={-0.00672677}
final_iter={25} inputs={-19.077,} output={187.589,}
rank={0,1,} iter={0} input={-18.4207,} output={-3.82964e-06,} objective={-3.82964e-06}
rank={0,1,} iter={1} input={-19.649,} output={71.0574,} objective={71.0574}
rank={0,1,} iter={2} input={-17.2111,} output={-0.00825001,} objective={-0.00825001}
rank={0,1,} iter={3} input={-20.7233,} output={62.4901,} objective={62.4901}
rank={0,1,} iter={4} input={-20.1121,} output={67.3265,} objective={67.3265}
rank={0,1,} iter={5} input={-18.491,} output={-2.87223e-06,} objective={-2.87223e-06}
rank={0,1,} iter={6} input={-20.3787,} output={65.0592,} objective={65.0592}
rank={0,1,} iter={7} input={-19.07,} output={76.2878,} objective={76.2878}
rank={0,1,} iter={8} input={-16.1182,} output={-0.0347866,} objective={-0.0347866}
rank={0,1,} iter={9} input={-19.3224,} output={74.183,} objective={74.183}
rank={0,1,} iter={10} input={-19.8669,} output={69.2541,} objective={69.2541}
rank={0,1,} iter={11} input={-18.491,} output={-2.87223e-06,} objective={-2.87223e-06}
rank={0,1,} iter={12} input={-19.4728,} output={72.8241,} objective={72.8241}
rank={0,1,} iter={13} input={-18.7805,} output={79.2125,} objective={79.2125}
rank={0,1,} iter={14} input={-17.8163,} output={-0.000410729,} objective={-0.000410729}
rank={0,1,} iter={15} input={-18.915,} output={77.6241,} objective={77.6241}
rank={0,1,} iter={16} input={-16.6639,} output={-0.0199668,} objective={-0.0199668}
rank={0,1,} iter={17} input={-18.839,} output={78.4327,} objective={78.4327}
rank={0,1,} iter={18} input={-20.5472,} output={63.8359,} objective={63.8359}
rank={0,1,} iter={19} input={-18.6358,} output={80.7404,} objective={80.7404}
rank={0,1,} iter={20} input={-18.1186,} output={-5.93595e-05,} objective={-5.93595e-05}
rank={0,1,} iter={21} input={-18.7805,} output={79.2125,} objective={79.2125}
rank={0,1,} iter={22} input={-17.5143,} output={-0.00333849,} objective={-0.00333849}
rank={0,1,} iter={23} input={-18.7081,} output={80.0096,} objective={80.0096}
rank={0,1,} iter={24} input={-16.938,} output={-0.0139179,} objective={-0.0139179}
final_iter={25} inputs={-18.6358,} output={80.7404,}
Visual comparison of the compressed Laplacians¶
fig, axs = plt.subplots(nrows=3, ncols=4, figsize=(16, 12))
plot_DU(
compute_DU,
U,
1.0,
axs[0, 0],
"Analytical",
DU_eb_abs=DU_eb_abs,
my_DU=DU[1:-1, 1:-1],
)
plot_DU(
compute_DU,
U_zfp[eb_abs],
U_zfp_cr[eb_abs],
axs[0, 1],
r"ZFP($\epsilon_{abs}$)",
DU_eb_abs=DU_eb_abs,
)
plot_DU(
compute_DU,
U_sz3[eb_abs],
U_sz3_cr[eb_abs],
axs[0, 2],
r"SZ3($\epsilon_{abs}$)",
DU_eb_abs=DU_eb_abs,
)
plot_DU(
compute_DU,
U_sperr[eb_abs],
U_sperr_cr[eb_abs],
axs[0, 3],
r"SPERR($\epsilon_{abs}$)",
DU_eb_abs=DU_eb_abs,
)
plot_DU(
compute_DU,
U_sg_qoi["zero"][0],
U_sg_qoi_cr["zero"][0],
axs[1, 0],
r"Safeguarded(0, $\epsilon_{QoI,abs}$)",
DU_eb_abs=DU_eb_abs,
corr=U_zero,
my_U_it=U_sg_it_qoi["zero"][0],
cr_it=U_sg_it_qoi_cr["zero"][0],
)
plot_DU(
compute_DU,
U_sg_qoi["zfp.rs"][eb_abs],
U_sg_qoi_cr["zfp.rs"][eb_abs],
axs[1, 1],
r"Safeguarded(ZFP, $\epsilon_{QoI,abs}$)",
DU_eb_abs=DU_eb_abs,
corr=U_zfp[eb_abs],
my_U_it=U_sg_it_qoi["zfp.rs"][eb_abs],
cr_it=U_sg_it_qoi_cr["zfp.rs"][eb_abs],
)
plot_DU(
compute_DU,
U_sg_qoi["sz3.rs"][eb_abs],
U_sg_qoi_cr["sz3.rs"][eb_abs],
axs[1, 2],
r"Safeguarded(SZ3, $\epsilon_{QoI,abs}$)",
DU_eb_abs=DU_eb_abs,
corr=U_sz3[eb_abs],
my_U_it=U_sg_it_qoi["sz3.rs"][eb_abs],
cr_it=U_sg_it_qoi_cr["sz3.rs"][eb_abs],
)
plot_DU(
compute_DU,
U_sg_qoi["sperr.rs"][eb_abs],
U_sg_qoi_cr["sperr.rs"][eb_abs],
axs[1, 3],
r"Safeguarded(SPERR, $\epsilon_{QoI,abs}$)",
DU_eb_abs=DU_eb_abs,
corr=U_sperr[eb_abs],
my_U_it=U_sg_it_qoi["sperr.rs"][eb_abs],
cr_it=U_sg_it_qoi_cr["sperr.rs"][eb_abs],
)
axs[2, 0].set_axis_off()
plot_DU(
compute_DU,
U_optzconfig["zfp.rs"],
U_optzconfig_cr["zfp.rs"],
axs[2, 1],
r"OptZConfig(ZFP, $\epsilon_{QoI,abs}$)",
DU_eb_abs=DU_eb_abs,
inset=False,
)
plot_DU(
compute_DU,
U_optzconfig["sz3.rs"],
U_optzconfig_cr["sz3.rs"],
axs[2, 2],
r"OptZConfig(SZ3, $\epsilon_{QoI,abs}$)",
DU_eb_abs=DU_eb_abs,
inset=False,
)
plot_DU(
compute_DU,
U_optzconfig["sperr.rs"],
U_optzconfig_cr["sperr.rs"],
axs[2, 3],
r"OptZConfig(SPERR, $\epsilon_{QoI,abs}$)",
DU_eb_abs=DU_eb_abs,
inset=False,
)
plt.tight_layout()
plt.savefig(Path("plots") / "derivative-radial.pdf", dpi=300)
plt.show()
u_radial_sg_table_a = pd.concat(
[
table_DU(
compute_DU,
U_sg_lossless_qoi["zero"][0],
U_sg_lossless_qoi_cr["zero"][0],
["0", "", r"$\epsilon_{QoI,abs}$", "lossless"],
DU_eb_abs,
U_zero,
),
table_DU(
compute_DU,
U_sg_qoi["zero"][0],
U_sg_qoi_cr["zero"][0],
["0", "", r"$\epsilon_{QoI,abs}$", "one-shot"],
DU_eb_abs,
U_zero,
),
table_DU(
compute_DU,
U_sg_it_qoi["zero"][0],
U_sg_it_qoi_cr["zero"][0],
["0", "", r"$\epsilon_{QoI,abs}$", "iterative"],
DU_eb_abs,
U_zero,
),
]
+ [
x
for eb_abs_zfp in eb_abs_zfps
for x in [
table_DU(
compute_DU,
U_zfp[eb_abs_zfp],
U_zfp_cr[eb_abs_zfp],
[r"ZFP($\epsilon_{abs}$)", f"{eb_abs_zfp}", "-", ""],
DU_eb_abs,
None,
),
table_DU(
compute_DU,
U_sg_lossless_qoi["zfp.rs"][eb_abs_zfp],
U_sg_lossless_qoi_cr["zfp.rs"][eb_abs_zfp],
[
r"ZFP($\epsilon_{abs}$)",
f"{eb_abs_zfp}",
r"$\epsilon_{QoI,abs}$",
"lossless",
],
DU_eb_abs,
U_zfp[eb_abs_zfp],
),
table_DU(
compute_DU,
U_sg_qoi["zfp.rs"][eb_abs_zfp],
U_sg_qoi_cr["zfp.rs"][eb_abs_zfp],
[
r"ZFP($\epsilon_{abs}$)",
f"{eb_abs_zfp}",
r"$\epsilon_{QoI,abs}$",
"one-shot",
],
DU_eb_abs,
U_zfp[eb_abs_zfp],
),
table_DU(
compute_DU,
U_sg_it_qoi["zfp.rs"][eb_abs_zfp],
U_sg_it_qoi_cr["zfp.rs"][eb_abs_zfp],
[
r"ZFP($\epsilon_{abs}$)",
f"{eb_abs_zfp}",
r"$\epsilon_{QoI,abs}$",
"iterative",
],
DU_eb_abs,
U_zfp[eb_abs_zfp],
),
]
]
+ [
table_DU(
compute_DU,
U_optzconfig["zfp.rs"],
U_optzconfig_cr["zfp.rs"],
[
"OptZConfig(ZFP)",
"",
r"$\epsilon_{QoI,abs}$",
"",
],
DU_eb_abs,
None,
),
]
+ [
x
for eb_abs_sz3 in eb_abs_sz3s[:2]
for x in [
table_DU(
compute_DU,
U_sz3[eb_abs_sz3],
U_sz3_cr[eb_abs_sz3],
[r"SZ3($\epsilon_{abs}$)", f"{eb_abs_sz3}", "-", ""],
DU_eb_abs,
None,
),
table_DU(
compute_DU,
U_sg_lossless_qoi["sz3.rs"][eb_abs_sz3],
U_sg_lossless_qoi_cr["sz3.rs"][eb_abs_sz3],
[
r"SZ3($\epsilon_{abs}$)",
f"{eb_abs_sz3}",
r"$\epsilon_{QoI,abs}$",
"lossless",
],
DU_eb_abs,
U_sz3[eb_abs_sz3],
),
table_DU(
compute_DU,
U_sg_qoi["sz3.rs"][eb_abs_sz3],
U_sg_qoi_cr["sz3.rs"][eb_abs_sz3],
[
r"SZ3($\epsilon_{abs}$)",
f"{eb_abs_sz3}",
r"$\epsilon_{QoI,abs}$",
"one-shot",
],
DU_eb_abs,
U_sz3[eb_abs_sz3],
),
table_DU(
compute_DU,
U_sg_it_qoi["sz3.rs"][eb_abs_sz3],
U_sg_it_qoi_cr["sz3.rs"][eb_abs_sz3],
[
r"SZ3($\epsilon_{abs}$)",
f"{eb_abs_sz3}",
r"$\epsilon_{QoI,abs}$",
"iterative",
],
DU_eb_abs,
U_sz3[eb_abs_sz3],
),
]
]
)
u_radial_sg_table_b = pd.concat(
[
x
for eb_abs_sz3 in eb_abs_sz3s[2:]
for x in [
table_DU(
compute_DU,
U_sz3[eb_abs_sz3],
U_sz3_cr[eb_abs_sz3],
[r"SZ3($\epsilon_{abs}$)", f"{eb_abs_sz3}", "-", ""],
DU_eb_abs,
None,
),
table_DU(
compute_DU,
U_sg_lossless_qoi["sz3.rs"][eb_abs_sz3],
U_sg_lossless_qoi_cr["sz3.rs"][eb_abs_sz3],
[
r"SZ3($\epsilon_{abs}$)",
f"{eb_abs_sz3}",
r"$\epsilon_{QoI,abs}$",
"lossless",
],
DU_eb_abs,
U_sz3[eb_abs_sz3],
),
table_DU(
compute_DU,
U_sg_qoi["sz3.rs"][eb_abs_sz3],
U_sg_qoi_cr["sz3.rs"][eb_abs_sz3],
[
r"SZ3($\epsilon_{abs}$)",
f"{eb_abs_sz3}",
r"$\epsilon_{QoI,abs}$",
"one-shot",
],
DU_eb_abs,
U_sz3[eb_abs_sz3],
),
table_DU(
compute_DU,
U_sg_it_qoi["sz3.rs"][eb_abs_sz3],
U_sg_it_qoi_cr["sz3.rs"][eb_abs_sz3],
[
r"SZ3($\epsilon_{abs}$)",
f"{eb_abs_sz3}",
r"$\epsilon_{QoI,abs}$",
"iterative",
],
DU_eb_abs,
U_sz3[eb_abs_sz3],
),
]
]
+ [
table_DU(
compute_DU,
U_optzconfig["sz3.rs"],
U_optzconfig_cr["sz3.rs"],
[
"OptZConfig(SZ3)",
"",
r"$\epsilon_{QoI,abs}$",
"",
],
DU_eb_abs,
None,
),
]
+ [
x
for eb_abs_sperr in eb_abs_sperrs
for x in [
table_DU(
compute_DU,
U_sperr[eb_abs_sperr],
U_sperr_cr[eb_abs_sperr],
[r"SPERR($\epsilon_{abs}$)", f"{eb_abs_sperr}", "-", ""],
DU_eb_abs,
None,
),
table_DU(
compute_DU,
U_sg_lossless_qoi["sperr.rs"][eb_abs_sperr],
U_sg_lossless_qoi_cr["sperr.rs"][eb_abs_sperr],
[
r"SPERR($\epsilon_{abs}$)",
f"{eb_abs_sperr}",
r"$\epsilon_{QoI,abs}$",
"lossless",
],
DU_eb_abs,
U_sperr[eb_abs_sperr],
),
table_DU(
compute_DU,
U_sg_qoi["sperr.rs"][eb_abs_sperr],
U_sg_qoi_cr["sperr.rs"][eb_abs_sperr],
[
r"SPERR($\epsilon_{abs}$)",
f"{eb_abs_sperr}",
r"$\epsilon_{QoI,abs}$",
"one-shot",
],
DU_eb_abs,
U_sperr[eb_abs_sperr],
),
table_DU(
compute_DU,
U_sg_it_qoi["sperr.rs"][eb_abs_sperr],
U_sg_it_qoi_cr["sperr.rs"][eb_abs_sperr],
[
r"SPERR($\epsilon_{abs}$)",
f"{eb_abs_sperr}",
r"$\epsilon_{QoI,abs}$",
"iterative",
],
DU_eb_abs,
U_sperr[eb_abs_sperr],
),
]
]
+ [
table_DU(
compute_DU,
U_optzconfig["sperr.rs"],
U_optzconfig_cr["sperr.rs"],
[
"OptZConfig(SPERR)",
"",
r"$\epsilon_{QoI,abs}$",
"",
],
DU_eb_abs,
None,
),
]
+ [
table_DU(
compute_DU,
U_zstd,
U_zstd_cr,
["ZSTD(22)", "", "-", ""],
DU_eb_abs,
None,
)
]
)
u_radial_sg_table_index = [
"Compressor",
r"$\epsilon_{abs}$",
"Safeguarded",
"Corrections",
]
u_radial_sg_table = pd.concat([u_radial_sg_table_a, u_radial_sg_table_b]).set_index(
u_radial_sg_table_index
)
u_radial_sg_table_a = u_radial_sg_table_a.set_index(u_radial_sg_table_index)
u_radial_sg_table_b = u_radial_sg_table_b.set_index(u_radial_sg_table_index)
for name, table in {
"derivative-radial-a.tex": u_radial_sg_table_a,
"derivative-radial-b.tex": u_radial_sg_table_b,
"derivative-radial.tex": u_radial_sg_table,
}.items():
Path("tables").joinpath(name).write_text(
table.to_latex(escape=False)
.replace("%", r"\%")
.replace(
"\\cline{1-10} \\cline{2-10} \\cline{3-10}\n\\bottomrule", "\\bottomrule"
)
)
u_radial_sg_table
| $L_{\infty}(\hat{u})$ | $L_{\infty}(\Delta \hat{u})$ | $L_{2}(\Delta \hat{u})$ | V | C | CR | ||||
|---|---|---|---|---|---|---|---|---|---|
| Compressor | $\epsilon_{abs}$ | Safeguarded | Corrections | ||||||
| 0 | $\epsilon_{QoI,abs}$ | lossless | 0.31 | 0.0099 | 3.7e-05 | 0 | 100.0% | $\times$ 2.97 | |
| one-shot | 0.31 | 0.0092 | 0.0028 | 0 | 100.0% | $\times$ 6.9 | |||
| iterative | 0.31 | 0.0092 | 0.0028 | 0 | 100.0% | $\times$ 6.75 | |||
| ZFP($\epsilon_{abs}$) | 1e-07 | - | 2.7e-08 | 0.028 | 0.0045 | 4.5% | $\times$ 8.37 | ||
| $\epsilon_{QoI,abs}$ | lossless | 2e-08 | 0.01 | 0.0036 | 0 | 5.5% | $\times$ 7.18 | ||
| one-shot | 2e-08 | 0.0086 | 0.0024 | 0 | 40.0% | $\times$ 7.1 | |||
| iterative | 2e-08 | 0.01 | 0.0036 | 0 | 5.4% | $\times$ 8.03 | |||
| 5e-08 | - | 1.5e-08 | 0.015 | 0.0025 | 0.2% | $\times$ 7.91 | |||
| $\epsilon_{QoI,abs}$ | lossless | 1.3e-08 | 0.01 | 0.0025 | 0 | 0.2% | $\times$ 7.83 | ||
| one-shot | 4.8e-09 | 0.0073 | 0.0019 | 0 | 12.1% | $\times$ 7.45 | |||
| iterative | 1.3e-08 | 0.01 | 0.0025 | 0 | 0.2% | $\times$ 7.88 | |||
| 2e-08 | - | 7.8e-09 | 0.0077 | 0.0015 | 0 | $\times$ 7.43 | |||
| $\epsilon_{QoI,abs}$ | lossless | 7.8e-09 | 0.0077 | 0.0015 | 0 | 0 | $\times$ 7.43 | ||
| one-shot | 7.8e-09 | 0.0077 | 0.0015 | 0 | 0 | $\times$ 7.43 | |||
| iterative | 7.8e-09 | 0.0077 | 0.0015 | 0 | 0 | $\times$ 7.43 | |||
| OptZConfig(ZFP) | $\epsilon_{QoI,abs}$ | 4.1e-09 | 0.0041 | 0.00079 | 0 | $\times$ 6.77 | |||
| SZ3($\epsilon_{abs}$) | 1e-07 | - | 1e-07 | 0.086 | 0.0022 | 0.4% | $\times$ 721.97 | ||
| $\epsilon_{QoI,abs}$ | lossless | 9.4e-08 | 0.01 | 0.0017 | 0 | 25.1% | $\times$ 5.64 | ||
| one-shot | 7.6e-08 | 0.0094 | 0.0024 | 0 | 86.8% | $\times$ 24.69 | |||
| iterative | 9.4e-08 | 0.01 | 0.0021 | 0 | 25.0% | $\times$ 57.36 | |||
| 1.5e-07 | - | 1.5e-07 | 0.13 | 0.0032 | 0.9% | $\times$ 821.12 | |||
| $\epsilon_{QoI,abs}$ | lossless | 1.2e-07 | 0.01 | 0.0018 | 0 | 49.9% | $\times$ 3.0 | ||
| one-shot | 1.2e-07 | 0.0091 | 0.0026 | 0 | 91.0% | $\times$ 22.22 | |||
| iterative | 1.2e-07 | 0.01 | 0.0026 | 0 | 51.8% | $\times$ 32.51 | |||
| 5e-09 | - | 5e-09 | 0.0093 | 0.00076 | 0 | $\times$ 176.04 | |||
| $\epsilon_{QoI,abs}$ | lossless | 5e-09 | 0.0093 | 0.00076 | 0 | 0 | $\times$ 176.02 | ||
| one-shot | 5e-09 | 0.0093 | 0.00076 | 0 | 0 | $\times$ 176.02 | |||
| iterative | 5e-09 | 0.0093 | 0.00076 | 0 | 0 | $\times$ 176.02 | |||
| OptZConfig(SZ3) | $\epsilon_{QoI,abs}$ | 5.2e-09 | 0.0098 | 0.00075 | 0 | $\times$ 187.59 | |||
| SPERR($\epsilon_{abs}$) | 1e-07 | - | 9.7e-08 | 0.12 | 0.0049 | 3.5% | $\times$ 112.65 | ||
| $\epsilon_{QoI,abs}$ | lossless | 8.7e-08 | 0.01 | 0.0013 | 0 | 6.7% | $\times$ 17.35 | ||
| one-shot | 8.7e-08 | 0.0087 | 0.0014 | 0 | 41.2% | $\times$ 29.35 | |||
| iterative | 8.7e-08 | 0.01 | 0.0014 | 0 | 6.5% | $\times$ 61.85 | |||
| 3.5e-08 | - | 3.5e-08 | 0.041 | 0.0017 | 0.9% | $\times$ 98.06 | |||
| $\epsilon_{QoI,abs}$ | lossless | 3.5e-08 | 0.01 | 0.001 | 0 | 1.3% | $\times$ 46.54 | ||
| one-shot | 1.1e-08 | 0.0088 | 0.00083 | 0 | 8.7% | $\times$ 53.68 | |||
| iterative | 3.5e-08 | 0.01 | 0.0011 | 0 | 1.3% | $\times$ 83.29 | |||
| 8e-09 | - | 8e-09 | 0.0083 | 0.00041 | 0 | $\times$ 80.65 | |||
| $\epsilon_{QoI,abs}$ | lossless | 8e-09 | 0.0083 | 0.00041 | 0 | 0 | $\times$ 80.65 | ||
| one-shot | 8e-09 | 0.0083 | 0.00041 | 0 | 0 | $\times$ 80.65 | |||
| iterative | 8e-09 | 0.0083 | 0.00041 | 0 | 0 | $\times$ 80.65 | |||
| OptZConfig(SPERR) | $\epsilon_{QoI,abs}$ | 8e-09 | 0.0085 | 0.00041 | 0 | $\times$ 80.74 | |||
| ZSTD(22) | - | 0.0 | 0.0 | 0.0 | 0 | $\times$ 4.26 |
import json
with Path("observations").joinpath("derivative-radial.json").open("w") as f:
json.dump(observations, f)
Example 2: $u(x, y) = e^{4 x + 3 y}$¶
The Laplacian of $u$ is $\Delta u = 25 u$.
We plot the natural logarithm of the Laplacian.
U = np.exp(4 * X + 3 * Y)
# analytical solution on uncompressed
DU = U * 25
# absolute error bound on the natural logarithm of the Laplacian,
# around 1% of the range
ln_DU_eb_abs = 0.1
# we use the safeguard's support for arbitrary grid-based spacing and switch
# from 2nd order central finite differences to 2nd order forward/backwards
# finite differences at the data boundaries, where data points do not have a
# left, right, upper, and lower neighbour
# this enables us to compute the Laplacian at all data points, including at
# the boundaries
# we pad the data with NaNs to denote the missing values
qoi_eb_stencil = SafeguardKind.qoi_eb_stencil.value(
qoi="""
V["d2Udy2"] = where(
c["Y"] > -1,
where(
c["Y"] < 1,
# use 2nd order central finite difference where possible
finite_difference(
x, order=2, accuracy=2, type=0, axis=0, grid_centre=c["Y"]
),
# use 2nd order backwards finite difference at the upper boundary
finite_difference(
x, order=2, accuracy=1, type=-1, axis=0, grid_centre=c["Y"]
),
),
# use 2nd order forward finite difference at the lower boundary
finite_difference(
x, order=2, accuracy=1, type=1, axis=0, grid_centre=c["Y"]
),
);
V["d2Udx2"] = where(
c["X"] > -1,
where(
c["X"] < 1,
# use 2nd order central finite difference where possible
finite_difference(
x, order=2, accuracy=2, type=0, axis=1, grid_centre=c["X"]
),
# use 2nd order backwards finite difference at the right boundary
finite_difference(
x, order=2, accuracy=1, type=-1, axis=1, grid_centre=c["X"]
),
),
# use 2nd order forward finite difference at the left boundary
finite_difference(
x, order=2, accuracy=1, type=1, axis=1, grid_centre=c["X"]
),
);
# compute the natural logarithm of the Laplacian
return ln(V["d2Udy2"] + V["d2Udx2"]);
""",
neighbourhood=[
dict(axis=0, before=2, after=2, boundary="constant", constant_boundary=np.nan),
dict(axis=1, before=2, after=2, boundary="constant", constant_boundary=np.nan),
],
type="abs",
eb=ln_DU_eb_abs,
)
# evaluate the Laplacian numerically by evaluating the quantity of interest
def compute_ln_DU(U):
return qoi_eb_stencil.evaluate_qoi(U, late_bound=Bindings(X=X, Y=Y))
observations = []
Lossless compression¶
We first compress the data losslessly with ZStandard at level 22, which gives maximum compression, to provide a baseline.
# compressed with Zstdandard
from numcodecs_wasm_zstd import Zstd
zstd = Zstd(level=22)
with observe.observe(zstd, observations):
U_zstd_enc = zstd.encode(U)
U_zstd = zstd.decode(U_zstd_enc)
U_zstd_cr = U.nbytes / U_zstd_enc.nbytes
Compressing u with lossy compressors¶
We configure each compressor with an absolute error bound of $5 \cdot 10^{-6}$ over the u array.
# absolute error bound for error-bounded lossy compression
eb_abs = 5e-6
# compressed with ZFP
from numcodecs_wasm_zfp import Zfp
zfp = Zfp(mode="fixed-accuracy", tolerance=eb_abs)
with observe.observe(zfp, observations):
U_zfp_enc = zfp.encode(U)
U_zfp = zfp.decode(U_zfp_enc)
U_zfp_cr = U.nbytes / U_zfp_enc.nbytes
# compressed with SZ3
from numcodecs_wasm_sz3 import Sz3
sz3 = Sz3(eb_mode="abs", eb_abs=eb_abs)
with observe.observe(sz3, observations):
U_sz3_enc = sz3.encode(U)
U_sz3 = sz3.decode(U_sz3_enc)
U_sz3_cr = U.nbytes / U_sz3_enc.nbytes
# compressed with SPERR
from numcodecs_wasm_sperr import Sperr
sperr = Sperr(mode="pwe", pwe=eb_abs)
with observe.observe(sperr, observations):
U_sperr_enc = sperr.encode(U)
U_sperr = sperr.decode(U_sperr_enc)
U_sperr_cr = U.nbytes / U_sperr_enc.nbytes
# compressed to constant zero
from numcodecs_zero import ZeroCodec
zero = ZeroCodec()
with observe.observe(zero, observations):
U_zero_enc = zero.encode(U)
U_zero = zero.decode(U_zero_enc)
Compressing u using the safeguarded lossy compressors¶
We configure the safeguards to bound the pointwise absolute error on the derived natural logarithm of the Laplacian.
For this second example, we use second-order central finite differences on non-boundary points and second-order forward/backwards finite differences on boundary points so that we can evaluate the Laplacian everywhere.
U_sg_qoi = dict()
U_sg_qoi_cr = dict()
# compressed with the safeguards with an absolute error bound over the log of the Laplacian
for codec in [zfp, sz3, sperr, zero]:
codec_sg_qoi = SafeguardedCodec(
codec=codec,
safeguards=[qoi_eb_stencil],
fixed_constants=dict(X=X, Y=Y),
)
with observe.observe(codec_sg_qoi, observations):
U_sg_qoi_enc = codec_sg_qoi.encode(U)
U_sg_qoi[codec.codec_id] = codec_sg_qoi.decode(U_sg_qoi_enc)
U_sg_qoi_cr[codec.codec_id] = U.nbytes / np.asarray(U_sg_qoi_enc).nbytes
U_sg_it_qoi = dict()
U_sg_it_qoi_cr = dict()
# compressed with the safeguards with an absolute error bound over the log of the Laplacian
for codec in [zfp, sz3, sperr, zero]:
codec_sg_it_qoi = SafeguardedCodec(
codec=codec,
safeguards=[qoi_eb_stencil],
fixed_constants=dict(X=X, Y=Y),
# use iteration to refine the corrections
compute=dict(unstable_iterative=True),
)
with observe.observe(codec_sg_it_qoi, observations):
U_sg_it_qoi_enc = codec_sg_it_qoi.encode(U)
U_sg_it_qoi[codec.codec_id] = codec_sg_it_qoi.decode(U_sg_it_qoi_enc)
U_sg_it_qoi_cr[codec.codec_id] = U.nbytes / np.asarray(U_sg_it_qoi_enc).nbytes
U_sg_lossless_qoi = dict()
U_sg_lossless_qoi_cr = dict()
# compressed with the safeguards with an absolute error bound over the log of the Laplacian
for codec in [zfp, sz3, sperr, zero]:
codec_sg_lossless_qoi = SafeguardedCodec(
codec=codec,
safeguards=[qoi_eb_stencil],
fixed_constants=dict(X=X, Y=Y),
# produce lossless corrections and refine them with iteration
compute=dict(unstable_iterative=True, unstable_lossless_corrections=True),
)
with observe.observe(codec_sg_lossless_qoi, observations):
U_sg_lossless_qoi_enc = codec_sg_lossless_qoi.encode(U)
U_sg_lossless_qoi[codec.codec_id] = codec_sg_lossless_qoi.decode(
U_sg_lossless_qoi_enc
)
U_sg_lossless_qoi_cr[codec.codec_id] = (
U.nbytes / np.asarray(U_sg_lossless_qoi_enc).nbytes
)
Compressing u with OptZConfig¶
import numcodecs
class SafetyViolationsMetric(numcodecs.abc.Codec):
codec_id = "safety-violations-metric"
def __init__(self):
self._data = None
def encode(self, buf):
# store the original data for later
self._data = np.array(buf, copy=True)
# return no metric
return np.empty(0, dtype=np.float64)
def decode(self, buf, out=None):
# compute the violations
data_ln_DU = compute_ln_DU(self._data)
buf_ln_DU = compute_ln_DU(buf)
violations = np.mean(~(np.abs(buf_ln_DU - data_ln_DU) <= ln_DU_eb_abs))
self._data = None
# return the violations score metric
return numcodecs.compat.ndarray_copy(np.float64(violations), out)
numcodecs.registry.register_codec(SafetyViolationsMetric)
from numcodecs_wasm_pressio import Pressio
U_optzconfig = dict()
U_optzconfig_cr = dict()
for codec, parameter, lower_bound in [
(zfp, "tolerance", 1e-9), # decent guess
(sz3, "eb_abs", 1e-9), # decent guess
(sperr, "pwe", 1e-9), # decent guess
]:
optzconfig = Pressio(
compressor_id="opt",
compressor_config={
"opt:output": ["composite:score"],
"opt:inputs": [f"numcodecs.rs:{parameter}"],
"opt:lower_bound": np.log(lower_bound),
"opt:upper_bound": np.log(eb_abs),
"opt:max_iterations": 25,
"opt:objective_mode_name": "max",
},
early_config={
"opt:compressor": "pressio",
"pressio:compressor": "numcodecs.rs",
**{
f"numcodecs.rs:{k}": f"e-{v}" if k == "id" else v
for k, v in codec.get_config().items()
},
"opt:search": "fraz",
"pressio:metric": "composite",
"composite:plugins": ["size", "numcodecs.rs-metric"],
"composite:scripts": [
"""
violations = metrics["numcodecs.rs-metric:decompression"]
if violations > 0 then
return "score", -violations
else
return "score", metrics["size:compression_ratio"]
end
"""
],
"numcodecs.rs-metric:id": "safety-violations-metric",
},
)
with observe.observe(optzconfig, observations):
U_optzconfig_enc = optzconfig.encode(U)
U_optzconfig[codec.codec_id] = optzconfig.decode(U_optzconfig_enc)
U_optzconfig_cr[codec.codec_id] = U.nbytes / np.asarray(U_optzconfig_enc).nbytes
rank={0,1,} iter={0} input={-16.4647,} output={-0.00361252,} objective={-0.00361252}
rank={0,1,} iter={1} input={-18.7364,} output={4.31434,} objective={4.31434}
rank={0,1,} iter={2} input={-14.2276,} output={-0.0477619,} objective={-0.0477619}
rank={0,1,} iter={3} input={-20.7233,} output={3.87286,} objective={3.87286}
rank={0,1,} iter={4} input={-19.6138,} output={4.08427,} objective={4.08427}
rank={0,1,} iter={5} input={-16.5948,} output={-0.00361252,} objective={-0.00361252}
rank={0,1,} iter={6} input={-20.1151,} output={3.87286,} objective={3.87286}
rank={0,1,} iter={7} input={-18.0423,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={8} input={-12.2062,} output={-0.173178,} objective={-0.173178}
rank={0,1,} iter={9} input={-18.2793,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={10} input={-19.1394,} output={4.31434,} objective={4.31434}
rank={0,1,} iter={11} input={-18.1608,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={12} input={-18.468,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={13} input={-19.3399,} output={4.31434,} objective={4.31434}
rank={0,1,} iter={14} input={-18.9384,} output={4.31434,} objective={4.31434}
rank={0,1,} iter={15} input={-18.5626,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={16} input={-18.3739,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={17} input={-20.4176,} output={3.87286,} objective={3.87286}
rank={0,1,} iter={18} input={-19.8319,} output={4.08427,} objective={4.08427}
rank={0,1,} iter={19} input={-18.1011,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={20} input={-18.2199,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={21} input={-18.3265,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={22} input={-18.6101,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={23} input={-18.4202,} output={4.56507,} objective={4.56507}
rank={0,1,} iter={24} input={-18.5143,} output={4.56507,} objective={4.56507}
final_iter={25} inputs={-18.0423,} output={4.56507,}
rank={0,1,} iter={0} input={-16.4647,} output={-0.00195503,} objective={-0.00195503}
rank={0,1,} iter={1} input={-18.7364,} output={-0.000151634,} objective={-0.000151634}
rank={0,1,} iter={2} input={-14.2276,} output={-0.00961113,} objective={-0.00961113}
rank={0,1,} iter={3} input={-18.9211,} output={22.5971,} objective={22.5971}
rank={0,1,} iter={4} input={-20.7233,} output={15.7417,} objective={15.7417}
rank={0,1,} iter={5} input={-12.2076,} output={-0.0120564,} objective={-0.0120564}
rank={0,1,} iter={6} input={-19.7922,} output={-9.53674e-07,} objective={-9.53674e-07}
rank={0,1,} iter={7} input={-17.6017,} output={-0.000749588,} objective={-0.000749588}
rank={0,1,} iter={8} input={-19.2643,} output={-4.86374e-05,} objective={-4.86374e-05}
rank={0,1,} iter={9} input={-15.3432,} output={-0.00452042,} objective={-0.00452042}
rank={0,1,} iter={10} input={-19.0004,} output={22.4294,} objective={22.4294}
rank={0,1,} iter={11} input={-13.2178,} output={-0.00402737,} objective={-0.00402737}
rank={0,1,} iter={12} input={-18.9585,} output={22.6012,} objective={22.6012}
rank={0,1,} iter={13} input={-17.0343,} output={-0.00115108,} objective={-0.00115108}
rank={0,1,} iter={14} input={-18.9408,} output={22.6182,} objective={22.6182}
rank={0,1,} iter={15} input={-18.1704,} output={-0.000402451,} objective={-0.000402451}
rank={0,1,} iter={16} input={-15.9054,} output={-0.000188828,} objective={-0.000188828}
rank={0,1,} iter={17} input={-14.7849,} output={-0.00613689,} objective={-0.00613689}
rank={0,1,} iter={18} input={-20.3229,} output={17.9368,} objective={17.9368}
rank={0,1,} iter={19} input={-13.7225,} output={-0.010644,} objective={-0.010644}
rank={0,1,} iter={20} input={-12.7121,} output={-0.0191288,} objective={-0.0191288}
rank={0,1,} iter={21} input={-20.5138,} output={16.3001,} objective={16.3001}
rank={0,1,} iter={22} input={-20.133,} output={-1.90735e-06,} objective={-1.90735e-06}
rank={0,1,} iter={23} input={-17.8865,} output={28.1549,} objective={28.1549}
rank={0,1,} iter={24} input={-16.7511,} output={-0.00179005,} objective={-0.00179005}
final_iter={25} inputs={-17.8865,} output={28.1549,}
rank={0,1,} iter={0} input={-16.4647,} output={-0.00167561,} objective={-0.00167561}
rank={0,1,} iter={1} input={-18.7364,} output={-1.90735e-05,} objective={-1.90735e-05}
rank={0,1,} iter={2} input={-14.2276,} output={-0.0121365,} objective={-0.0121365}
rank={0,1,} iter={3} input={-18.9211,} output={-1.14441e-05,} objective={-1.14441e-05}
rank={0,1,} iter={4} input={-12.7876,} output={-0.0294952,} objective={-0.0294952}
rank={0,1,} iter={5} input={-20.1487,} output={19.3432,} objective={19.3432}
rank={0,1,} iter={6} input={-20.7233,} output={17.2925,} objective={17.2925}
rank={0,1,} iter={7} input={-20.3707,} output={18.5197,} objective={18.5197}
rank={0,1,} iter={8} input={-19.4779,} output={22.19,} objective={22.19}
rank={0,1,} iter={9} input={-17.6007,} output={-0.000369072,} objective={-0.000369072}
rank={0,1,} iter={10} input={-19.7542,} output={20.9662,} objective={20.9662}
rank={0,1,} iter={11} input={-15.3468,} output={-0.00506592,} objective={-0.00506592}
rank={0,1,} iter={12} input={-19.5744,} output={21.7446,} objective={21.7446}
rank={0,1,} iter={13} input={-13.5084,} output={-0.0193777,} objective={-0.0193777}
rank={0,1,} iter={14} input={-19.1425,} output={-1.90735e-06,} objective={-1.90735e-06}
rank={0,1,} iter={15} input={-12.2099,} output={-0.0399446,} objective={-0.0399446}
rank={0,1,} iter={16} input={-19.3102,} output={-9.53674e-07,} objective={-9.53674e-07}
rank={0,1,} iter={17} input={-17.0321,} output={-0.000904083,} objective={-0.000904083}
rank={0,1,} iter={18} input={-19.5217,} output={21.9796,} objective={21.9796}
rank={0,1,} iter={19} input={-18.1685,} output={-0.000138283,} objective={-0.000138283}
rank={0,1,} iter={20} input={-19.436,} output={-9.53674e-07,} objective={-9.53674e-07}
rank={0,1,} iter={21} input={-14.7871,} output={-0.00792217,} objective={-0.00792217}
rank={0,1,} iter={22} input={-19.4989,} output={22.0852,} objective={22.0852}
rank={0,1,} iter={23} input={-15.9059,} output={-0.00310135,} objective={-0.00310135}
rank={0,1,} iter={24} input={-19.4881,} output={22.1341,} objective={22.1341}
final_iter={25} inputs={-19.4779,} output={22.19,}
Visual comparison of the compressed natural logarithms of the Laplacians¶
fig, axs = plt.subplots(nrows=3, ncols=4, figsize=(16, 12))
plot_DU(
compute_ln_DU,
U,
1.0,
axs[0, 0],
"Analytical",
DU_eb_abs=ln_DU_eb_abs,
my_DU=np.log(DU),
transform_symbol="ln",
include_boundary=True,
)
plot_DU(
compute_ln_DU,
U_zfp,
U_zfp_cr,
axs[0, 1],
r"ZFP($\epsilon_{abs}$)",
DU_eb_abs=ln_DU_eb_abs,
transform_symbol="ln",
include_boundary=True,
inset_offset=(1 - 0.05 - (1 / 3), 0.5 - (1 / 6)),
)
plot_DU(
compute_ln_DU,
U_sz3,
U_sz3_cr,
axs[0, 2],
r"SZ3($\epsilon_{abs}$)",
DU_eb_abs=ln_DU_eb_abs,
transform_symbol="ln",
include_boundary=True,
inset_offset=(1 - 0.05 - (1 / 3), 0.5 - (1 / 6)),
)
plot_DU(
compute_ln_DU,
U_sperr,
U_sperr_cr,
axs[0, 3],
r"SPERR($\epsilon_{abs}$)",
DU_eb_abs=ln_DU_eb_abs,
transform_symbol="ln",
include_boundary=True,
inset_offset=(1 - 0.05 - (1 / 3), 0.5 - (1 / 6)),
)
plot_DU(
compute_ln_DU,
U_sg_qoi["zero"],
U_sg_qoi_cr["zero"],
axs[1, 0],
r"Safeguarded(0, $\epsilon_{QoI,abs}$)",
DU_eb_abs=ln_DU_eb_abs,
transform_symbol="ln",
corr=U_zero,
my_U_it=U_sg_it_qoi["zero"],
cr_it=U_sg_it_qoi_cr["zero"],
include_boundary=True,
inset_offset=(1 - 0.05 - (1 / 3), 0.5 - (1 / 6)),
)
plot_DU(
compute_ln_DU,
U_sg_qoi["zfp.rs"],
U_sg_qoi_cr["zfp.rs"],
axs[1, 1],
r"Safeguarded(ZFP, $\epsilon_{QoI,abs}$)",
DU_eb_abs=ln_DU_eb_abs,
transform_symbol="ln",
corr=U_zfp,
my_U_it=U_sg_it_qoi["zfp.rs"],
cr_it=U_sg_it_qoi_cr["zfp.rs"],
include_boundary=True,
inset_offset=(1 - 0.05 - (1 / 3), 0.5 - (1 / 6)),
)
plot_DU(
compute_ln_DU,
U_sg_qoi["sz3.rs"],
U_sg_qoi_cr["sz3.rs"],
axs[1, 2],
r"Safeguarded(SZ3, $\epsilon_{QoI,abs}$)",
DU_eb_abs=ln_DU_eb_abs,
transform_symbol="ln",
corr=U_sz3,
my_U_it=U_sg_it_qoi["sz3.rs"],
cr_it=U_sg_it_qoi_cr["sz3.rs"],
include_boundary=True,
inset_offset=(1 - 0.05 - (1 / 3), 0.5 - (1 / 6)),
)
plot_DU(
compute_ln_DU,
U_sg_qoi["sperr.rs"],
U_sg_qoi_cr["sperr.rs"],
axs[1, 3],
r"Safeguarded(SPERR, $\epsilon_{QoI,abs}$)",
DU_eb_abs=ln_DU_eb_abs,
transform_symbol="ln",
corr=U_sperr,
my_U_it=U_sg_it_qoi["sperr.rs"],
cr_it=U_sg_it_qoi_cr["sperr.rs"],
include_boundary=True,
inset_offset=(1 - 0.05 - (1 / 3), 0.5 - (1 / 6)),
)
axs[2, 0].set_axis_off()
plot_DU(
compute_ln_DU,
U_optzconfig["zfp.rs"],
U_optzconfig_cr["zfp.rs"],
axs[2, 1],
r"OptZConfig(ZFP, $\epsilon_{QoI,abs}$)",
DU_eb_abs=ln_DU_eb_abs,
transform_symbol="ln",
include_boundary=True,
inset=False,
)
plot_DU(
compute_ln_DU,
U_optzconfig["sz3.rs"],
U_optzconfig_cr["sz3.rs"],
axs[2, 2],
r"OptZConfig(SZ3, $\epsilon_{QoI,abs}$)",
DU_eb_abs=ln_DU_eb_abs,
transform_symbol="ln",
include_boundary=True,
inset=False,
)
plot_DU(
compute_ln_DU,
U_optzconfig["sperr.rs"],
U_optzconfig_cr["sperr.rs"],
axs[2, 3],
r"OptZConfig(SPERR, $\epsilon_{QoI,abs}$)",
DU_eb_abs=ln_DU_eb_abs,
transform_symbol="ln",
include_boundary=True,
inset=False,
)
plt.tight_layout()
plt.savefig(Path("plots") / "derivative-log-exp.pdf", dpi=300)
plt.show()
u_log_exp_sg_table = (
pd.concat(
[
table_DU(
compute_ln_DU,
U_sg_lossless_qoi["zero"],
U_sg_lossless_qoi_cr["zero"],
["0", "", r"$\epsilon_{QoI,abs}$", "lossless"],
ln_DU_eb_abs,
U_zero,
),
table_DU(
compute_ln_DU,
U_sg_qoi["zero"],
U_sg_qoi_cr["zero"],
["0", "", r"$\epsilon_{QoI,abs}$", "one-shot"],
ln_DU_eb_abs,
U_zero,
),
table_DU(
compute_ln_DU,
U_sg_it_qoi["zero"],
U_sg_it_qoi_cr["zero"],
["0", "", r"$\epsilon_{QoI,abs}$", "iterative"],
ln_DU_eb_abs,
U_zero,
),
table_DU(
compute_ln_DU,
U_zfp,
U_zfp_cr,
[r"ZFP($\epsilon_{abs}$)", f"{eb_abs}", "-", ""],
ln_DU_eb_abs,
None,
),
table_DU(
compute_ln_DU,
U_sg_lossless_qoi["zfp.rs"],
U_sg_lossless_qoi_cr["zfp.rs"],
[
r"ZFP($\epsilon_{abs}$)",
f"{eb_abs}",
r"$\epsilon_{QoI,abs}$",
"lossless",
],
ln_DU_eb_abs,
U_zfp,
),
table_DU(
compute_ln_DU,
U_sg_qoi["zfp.rs"],
U_sg_qoi_cr["zfp.rs"],
[
r"ZFP($\epsilon_{abs}$)",
f"{eb_abs}",
r"$\epsilon_{QoI,abs}$",
"one-shot",
],
ln_DU_eb_abs,
U_zfp,
),
table_DU(
compute_ln_DU,
U_sg_it_qoi["zfp.rs"],
U_sg_it_qoi_cr["zfp.rs"],
[
r"ZFP($\epsilon_{abs}$)",
f"{eb_abs}",
r"$\epsilon_{QoI,abs}$",
"iterative",
],
ln_DU_eb_abs,
U_zfp,
),
table_DU(
compute_ln_DU,
U_optzconfig["zfp.rs"],
U_optzconfig_cr["zfp.rs"],
[
"OptZConfig(ZFP)",
"",
r"$\epsilon_{QoI,abs}$",
"",
],
ln_DU_eb_abs,
None,
),
table_DU(
compute_ln_DU,
U_sz3,
U_sz3_cr,
[r"SZ3($\epsilon_{abs}$)", f"{eb_abs}", "-", ""],
ln_DU_eb_abs,
None,
),
table_DU(
compute_ln_DU,
U_sg_lossless_qoi["sz3.rs"],
U_sg_lossless_qoi_cr["sz3.rs"],
[
r"SZ3($\epsilon_{abs}$)",
f"{eb_abs}",
r"$\epsilon_{QoI,abs}$",
"lossless",
],
ln_DU_eb_abs,
U_sz3,
),
table_DU(
compute_ln_DU,
U_sg_qoi["sz3.rs"],
U_sg_qoi_cr["sz3.rs"],
[
r"SZ3($\epsilon_{abs}$)",
f"{eb_abs}",
r"$\epsilon_{QoI,abs}$",
"one-shot",
],
ln_DU_eb_abs,
U_sz3,
),
table_DU(
compute_ln_DU,
U_sg_it_qoi["sz3.rs"],
U_sg_it_qoi_cr["sz3.rs"],
[
r"SZ3($\epsilon_{abs}$)",
f"{eb_abs}",
r"$\epsilon_{QoI,abs}$",
"iterative",
],
ln_DU_eb_abs,
U_sz3,
),
table_DU(
compute_ln_DU,
U_optzconfig["sz3.rs"],
U_optzconfig_cr["sz3.rs"],
[
"OptZConfig(SZ3)",
"",
r"$\epsilon_{QoI,abs}$",
"",
],
ln_DU_eb_abs,
None,
),
table_DU(
compute_ln_DU,
U_sperr,
U_sperr_cr,
[r"SPERR($\epsilon_{abs}$)", f"{eb_abs}", "-", ""],
ln_DU_eb_abs,
None,
),
table_DU(
compute_ln_DU,
U_sg_lossless_qoi["sperr.rs"],
U_sg_lossless_qoi_cr["sperr.rs"],
[
r"SPERR($\epsilon_{abs}$)",
f"{eb_abs}",
r"$\epsilon_{QoI,abs}$",
"lossless",
],
ln_DU_eb_abs,
U_sperr,
),
table_DU(
compute_ln_DU,
U_sg_qoi["sperr.rs"],
U_sg_qoi_cr["sperr.rs"],
[
r"SPERR($\epsilon_{abs}$)",
f"{eb_abs}",
r"$\epsilon_{QoI,abs}$",
"one-shot",
],
ln_DU_eb_abs,
U_sperr,
),
table_DU(
compute_ln_DU,
U_sg_it_qoi["sperr.rs"],
U_sg_it_qoi_cr["sperr.rs"],
[
r"SPERR($\epsilon_{abs}$)",
f"{eb_abs}",
r"$\epsilon_{QoI,abs}$",
"iterative",
],
ln_DU_eb_abs,
U_sperr,
),
table_DU(
compute_ln_DU,
U_optzconfig["sperr.rs"],
U_optzconfig_cr["sperr.rs"],
[
"OptZConfig(SPERR)",
"",
r"$\epsilon_{QoI,abs}$",
"",
],
ln_DU_eb_abs,
None,
),
table_DU(
compute_ln_DU,
U_zstd,
U_zstd_cr,
["ZSTD(22)", "", "-", ""],
ln_DU_eb_abs,
None,
),
]
)
.drop(r"$\epsilon_{abs}$", axis="columns")
.rename(
columns={
r"$L_{\infty}(\Delta \hat{u})$": r"$L_{\infty}(\ln(\Delta \hat{u}))$",
r"$L_{2}(\Delta \hat{u})$": r"$L_{2}(\ln(\Delta \hat{u}))$",
}
)
.set_index(["Compressor", "Safeguarded", "Corrections"])
)
Path("tables").joinpath("derivative-log-exp.tex").write_text(
u_log_exp_sg_table.to_latex(escape=False)
.replace("%", r"\%")
.replace("\\cline{1-9} \\cline{2-9}\n\\bottomrule", "\\bottomrule")
)
u_log_exp_sg_table
| $L_{\infty}(\hat{u})$ | $L_{\infty}(\ln(\Delta \hat{u}))$ | $L_{2}(\ln(\Delta \hat{u}))$ | V | C | CR | |||
|---|---|---|---|---|---|---|---|---|
| Compressor | Safeguarded | Corrections | ||||||
| 0 | $\epsilon_{QoI,abs}$ | lossless | 0.0 | 0.0 | 0.0 | 0 | 100.0% | $\times$ 27.22 |
| one-shot | 0.0012 | 0.087 | 0.028 | 0 | 100.0% | $\times$ 329.22 | ||
| iterative | 0.0012 | 0.087 | 0.028 | 0 | 100.0% | $\times$ 329.22 | ||
| ZFP($\epsilon_{abs}$) | - | 2e-06 | NaN (2.7e+01) | NaN (1.3) | 17.3% | $\times$ 8.2 | ||
| $\epsilon_{QoI,abs}$ | lossless | 2e-06 | 0.1 | 0.019 | 0 | 23.4% | $\times$ 7.04 | |
| one-shot | 2e-06 | 0.087 | 0.018 | 0 | 30.1% | $\times$ 8.0 | ||
| iterative | 2e-06 | 0.1 | 0.023 | 0 | 22.6% | $\times$ 8.02 | ||
| OptZConfig(ZFP) | $\epsilon_{QoI,abs}$ | 4.2e-09 | 0.084 | 0.0024 | 0 | $\times$ 4.57 | ||
| SZ3($\epsilon_{abs}$) | - | 5e-06 | NaN (6.6) | NaN (0.052) | 1.4% | $\times$ 159.4 | ||
| $\epsilon_{QoI,abs}$ | lossless | 5e-06 | 0.1 | 0.0085 | 0 | 29.7% | $\times$ 4.11 | |
| one-shot | 5e-06 | 0.089 | 0.018 | 0 | 45.6% | $\times$ 28.95 | ||
| iterative | 5e-06 | 0.1 | 0.017 | 0 | 29.5% | $\times$ 37.5 | ||
| OptZConfig(SZ3) | $\epsilon_{QoI,abs}$ | 1.7e-08 | 0.099 | 0.002 | 0 | $\times$ 28.15 | ||
| SPERR($\epsilon_{abs}$) | - | 5e-06 | NaN (1.3e+01) | NaN (0.16) | 4.0% | $\times$ 91.59 | ||
| $\epsilon_{QoI,abs}$ | lossless | 5e-06 | 0.1 | 0.009 | 0 | 14.8% | $\times$ 7.18 | |
| one-shot | 5e-06 | 0.092 | 0.014 | 0 | 28.7% | $\times$ 31.48 | ||
| iterative | 5e-06 | 0.1 | 0.014 | 0 | 14.6% | $\times$ 41.2 | ||
| OptZConfig(SPERR) | $\epsilon_{QoI,abs}$ | 3.5e-09 | 0.079 | 0.0007 | 0 | $\times$ 22.19 | ||
| ZSTD(22) | - | 0.0 | 0.0 | 0.0 | 0 | $\times$ 73.21 |
import json
with Path("observations").joinpath("derivative-log-exp.json").open("w") as f:
json.dump(observations, f)