compression-safeguards
  • Home
  • Try the safeguards

API Reference

  • Overview
  • compression_safeguards
    • api
    • safeguards
      • abc
      • combinators
        • all
        • any
        • assume_safe
        • select
      • eb
      • pointwise
        • abc
        • eb
        • lossless
        • qoi
          • eb
        • same
        • sign
      • qois
      • stencil
        • abc
        • qoi
          • eb
    • utils
      • bindings
      • cast
      • error
      • intervals
      • typing
  • numcodecs_safeguards
    • checksum
    • compute
    • lossless
  • xarray_safeguards

Examples

  • Datasets
  • Special values
    • NaN values
    • Missing values
  • Regions of Interest (RoIs)
    • Latent Heat Flux
  • Quantities of Interest (QoIs)
    • Pointwise: Log-scale Specific Humidity
      • Lossless compression
      • Compressing q with lossy compressors
      • Compressing q using the safeguarded lossy compressors
      • Compressing q with QPET-SPERR
      • Compressing q with ratio-error-bounded lossy compressors
      • Compressing q with OptZConfig
      • Visual comparison of the error distributions for the logarithm diagnostic
    • Semi-Pointwise: Kinetic energy
    • Higher-order derivatives
    • Neighbourhood: Relative Vorticity
    • Bounding the dSSIM metric
  • Topology
    • Precipitation extrema
    • Isosurfaces
  • Error bound distributions
    • Absolute error bound
  • Safeguards on chunked data
    • xarray-safeguards
  • Preview
    • Incremental safeguards

Links

  • GitHub
  • PyPI
    • compression-safeguards
    • numcodecs-safeguards
    • xarray-safeguards
compression-safeguards
  • Examples
  • Quantities of Interest (QoIs)
  • Pointwise: Log-scale Specific Humidity
  • Try     View Source

Preserve a pointwise quantity of interest (QoI) with safeguards¶

In this example, we compare how three different lossy compressors (ZFP, SZ3, and SPERR) affect the log-scale visualisation of a 2D specific humidity slice. Finally we apply safeguards to guarantee an absolute error bound directly on the pointwise quantity of interest, the logarithm that is being visualised. We also compare the safeguards with the QoI-aware QPET-SPERR compressor and the compressor configuration auto-tuner OptZConfig. This example thus provides a simple starting point for preserving simple diagnostics and plotting.

Copied!
import ssl

ssl._create_default_https_context = ssl._create_stdlib_context
import ssl ssl._create_default_https_context = ssl._create_stdlib_context
Copied!
from pathlib import Path

import earthkit.plots
import humanize
import matplotlib as mpl
import numpy as np
import pandas as pd
import xarray as xr
from matplotlib import patheffects as PathEffects
from pathlib import Path import earthkit.plots import humanize import matplotlib as mpl import numpy as np import pandas as pd import xarray as xr from matplotlib import patheffects as PathEffects
Copied!
# Retrieve the data
ERA5 = xr.open_dataset(Path() / "data" / "era5-q" / "data.nc")
ERA5_Q = ERA5["q"].sel(valid_time="2024-04-02T12:00:00", pressure_level=850)
# Retrieve the data ERA5 = xr.open_dataset(Path() / "data" / "era5-q" / "data.nc") ERA5_Q = ERA5["q"].sel(valid_time="2024-04-02T12:00:00", pressure_level=850)
Copied!
def compute_corrections_percentage(
    my_ERA5_Q: xr.DataArray, orig_ERA5_Q: xr.DataArray
) -> float:
    return np.mean(my_ERA5_Q != orig_ERA5_Q)
def compute_corrections_percentage( my_ERA5_Q: xr.DataArray, orig_ERA5_Q: xr.DataArray ) -> float: return np.mean(my_ERA5_Q != orig_ERA5_Q)
Copied!
old_cmap_and_norm = earthkit.plots.styles.colors.cmap_and_norm
old_cmap_and_norm = earthkit.plots.styles.colors.cmap_and_norm
Copied!
def my_cmap_and_norm(colors, levels, normalize=True, extend=None, extend_levels=True):
    return old_cmap_and_norm(colors, levels, normalize, extend, True)


earthkit.plots.styles.colors.cmap_and_norm = my_cmap_and_norm
def my_cmap_and_norm(colors, levels, normalize=True, extend=None, extend_levels=True): return old_cmap_and_norm(colors, levels, normalize, extend, True) earthkit.plots.styles.colors.cmap_and_norm = my_cmap_and_norm
Copied!
def plot_specific_humidity_log10(
    my_ERA5_Q: xr.DataArray,
    cr,
    chart,
    title,
    span,
    qlog10_eb_abs,
    error=False,
    corr=None,
    my_ERA5_Q_ratio=None,
    cr_ratio=None,
    corr_ratio=None,
    inset=True,
):
    import copy

    with np.errstate(divide="ignore", invalid="ignore"):
        ERA5_Q_log10 = np.log10(ERA5_Q)
        my_ERA5_Q_log10 = np.log10(my_ERA5_Q)

        if my_ERA5_Q_ratio is not None:
            my_ERA5_Q_ratio_log10 = np.log10(my_ERA5_Q_ratio)

    if error:
        err_v = np.mean(~(np.abs(my_ERA5_Q_log10 - ERA5_Q_log10) <= qlog10_eb_abs))

        if my_ERA5_Q_ratio is not None:
            err_ratio_v = np.mean(
                ~(np.abs(my_ERA5_Q_ratio_log10 - ERA5_Q_log10) <= qlog10_eb_abs)
            )

        with xr.set_options(keep_attrs=True):
            da = (my_ERA5_Q_log10 - ERA5_Q_log10).compute()

        da.attrs.update(
            long_name=f"Absolute error over log10({da.long_name.lower()})",
            units=ERA5_Q.units,
        )
    else:
        # plot the decimal logarithm of specific humidity to better capture scale
        da = np.log10(my_ERA5_Q)
        da.attrs.update(
            long_name=f"log10({ERA5_Q.long_name.lower()})", units=ERA5_Q.units
        )

    # compute the default style that earthkit.maps would apply
    source = earthkit.plots.sources.XarraySource(da)
    style = copy.deepcopy(
        earthkit.plots.styles.auto.guess_style(
            source,
            units=source.units,
        )
    )

    if error:
        style._levels = earthkit.plots.styles.levels.Levels(
            np.linspace(-span, span, 22)
        )
        style._legend_kwargs["ticks"] = np.linspace(-span, span, 5)
        style._colors = "coolwarm"
    else:
        style._levels = earthkit.plots.styles.levels.Levels(np.linspace(*span, 22))
        style._legend_kwargs["ticks"] = np.linspace(*span, 5)
        style._colors = "viridis"

    extend_left = np.nanmin(da) < (-span if error else span[0])
    extend_right = np.nanmax(da) > (span if error else span[1])

    extend = {
        (False, False): "neither",
        (True, False): "min",
        (False, True): "max",
        (True, True): "both",
    }[(extend_left, extend_right)]

    chart.ax.fill_between(
        [0, 1],
        [1, 1],
        hatch="XX",
        edgecolor="magenta",
        facecolor="lavenderblush",
        transform=chart.ax.transAxes,
        zorder=-12,
    )

    if error:
        style._legend_kwargs["extend"] = extend
        chart.pcolormesh(da, style=style, zorder=-11)

        if corr is not None:
            da_hatch = my_ERA5_Q == corr
            da_corr = (~da_hatch).astype(float)

            old_process_projection_requirements = (
                chart.ax.get_figure()._process_projection_requirements
            )

            def _process_projection_requirements(
                *, axes_class=None, polar=False, projection=None, **kwargs
            ):
                if axes_class is not None and projection is not None:
                    return axes_class, dict(projection=projection, **kwargs)
                return old_process_projection_requirements(
                    axes_class=axes_class, polar=polar, projection=projection, **kwargs
                )

            chart.ax.get_figure()._process_projection_requirements = (
                _process_projection_requirements
            )

            axin = chart.ax.inset_axes(
                [0.025, 0.05, 1 / 3, 1 / 3],
                xticklabels=[],
                yticklabels=[],
                axes_class=type(chart.ax),
                projection=chart.ax.projection,
            )
            axin.pcolormesh(
                da_corr.longitude.values,
                da_corr.latitude.values,
                np.squeeze(da_corr.values),
                cmap=mpl.colors.ListedColormap(["white", "green", "lawngreen"]),
                vmin=0,
                vmax=2,
                rasterized=True,
            )
            axin.coastlines(color="#555555")
            axin.spines["geo"].set_edgecolor("black")
            axin.set_title(
                "Corrections",
                path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")],
            )
        elif inset:
            da_err = ~(np.abs(da) <= qlog10_eb_abs)

            old_process_projection_requirements = (
                chart.ax.get_figure()._process_projection_requirements
            )

            def _process_projection_requirements(
                *, axes_class=None, polar=False, projection=None, **kwargs
            ):
                if axes_class is not None and projection is not None:
                    return axes_class, dict(projection=projection, **kwargs)
                return old_process_projection_requirements(
                    axes_class=axes_class, polar=polar, projection=projection, **kwargs
                )

            chart.ax.get_figure()._process_projection_requirements = (
                _process_projection_requirements
            )

            axin = chart.ax.inset_axes(
                [0.025, 0.05, 1 / 3, 1 / 3],
                xticklabels=[],
                yticklabels=[],
                axes_class=type(chart.ax),
                projection=chart.ax.projection,
            )
            axin.pcolormesh(
                da_err.longitude.values,
                da_err.latitude.values,
                np.squeeze(da_err.values),
                cmap=mpl.colors.ListedColormap(["white", "red"]),
                vmin=0,
                vmax=1,
                rasterized=True,
            )
            axin.coastlines(color="#555555")
            axin.spines["geo"].set_edgecolor("black")
            axin.set_title(
                "Violations",
                path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")],
            )
    else:
        chart.quickplot(da, style=style, extend=extend, zorder=-11)

    chart.ax.set_rasterization_zorder(-10)

    chart.title(title)

    if error:
        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%"

        if my_ERA5_Q_ratio is not None:
            err_ratio_v = (
                0
                if err_ratio_v == 0
                else np.format_float_positional(
                    100 * err_ratio_v, precision=1, min_digits=1
                )
                + "%"
            )
            if err_ratio_v == "0.0%":
                err_ratio_v = "<0.05%"

        t = chart.ax.text(
            0.95,
            0.1,
            f"V={err_v}" + ("" if my_ERA5_Q_ratio is None else f" ({err_ratio_v})"),
            ha="right",
            va="bottom",
            transform=chart.ax.transAxes,
        )
        t.set_bbox(dict(facecolor="white", alpha=0.75, edgecolor="black"))

    t = chart.ax.text(
        0.95,
        0.9,
        rf"$\times$ {np.round(cr, 2)}"
        + ("" if cr_ratio is None else rf" ($\times$ {np.round(cr_ratio, 2)})")
        if error
        else humanize.naturalsize(ERA5_Q.nbytes, binary=True),
        ha="right",
        va="top",
        transform=chart.ax.transAxes,
    )
    t.set_bbox(dict(facecolor="white", alpha=0.75, edgecolor="black"))

    chart.contour(
        x=np.broadcast_to(
            da.longitude.values.reshape(1, -1),
            da.shape,
        ),
        y=np.broadcast_to(
            da.latitude.values.reshape(-1, 1),
            da.shape,
        ),
        z=~(np.isfinite(da).values),
        colors=["none"],
        linecolors=["magenta"],
        levels=[-0.5, 0.9, 1.5],
        legend_style=None,
        labels=False,
        zorder=-10,
    )

    for m in earthkit.plots.schemas.schema.quickmap_subplot_workflow:
        if m != "title":
            getattr(chart, m)()

    for m in earthkit.plots.schemas.schema.quickmap_figure_workflow:
        getattr(chart, m)()

    counts, bins = np.histogram(
        da.values.flatten(),
        range=(-span if error else span[0], span if error else span[1]),
        bins=21,
    )
    midpoints = bins[:-1] + np.diff(bins) / 2
    cb = chart.ax.collections[1].colorbar
    extend_width = (bins[-1] - bins[-2]) / (bins[-1] - bins[0])
    cax = cb.ax.inset_axes(
        [
            0.0 - extend_width * extend_left,
            1.25,
            1.0
            + extend_width
            * (0 + extend_left + extend_right + np.any(~np.isfinite(da)) * 2),
            1.0,
        ]
    )
    cax.bar(
        midpoints,
        height=counts,
        width=(bins[-1] - bins[0]) / len(counts),
        color=cb.cmap(cb.norm(midpoints)),
    )
    if extend_left:
        cax.bar(
            bins[0] - (bins[1] - bins[0]) / 2,
            height=np.sum(da < (-span if error else span[0])),
            width=(bins[-1] - bins[0]) / len(counts),
            color=cb.cmap(cb.norm(midpoints[0])),
        )
    if extend_right:
        cax.bar(
            bins[-1] + (bins[-1] - bins[-2]) / 2,
            height=np.sum(da > (span if error else span[1])),
            width=(bins[-1] - bins[0]) / len(counts),
            color=cb.cmap(cb.norm(midpoints[-1])),
        )
    if np.any(~np.isfinite(da)):
        cax.bar(
            bins[-1] + (bins[-1] - bins[-2]) * (extend_right * 2 + 2 + 1) / 2,
            height=np.sum(~np.isfinite(da)),
            width=(bins[-1] - bins[0]) / len(counts),
            color="lavenderblush",
            edgecolor="magenta",
            lw=0,
            hatch="XXXX",
        )
    q1, q2, q3 = da.quantile([0.25, 0.5, 0.75]).values
    cax.axvline(da.mean().item(), ls=":", ymin=0.1, ymax=0.9, c="w", lw=2)
    cax.axvline(q1, ymin=0.25, ymax=0.75, c="w", lw=2)
    cax.axvline(q2, ymin=0.1, ymax=0.9, c="w", lw=2)
    cax.axvline(q3, ymin=0.25, ymax=0.75, c="w", lw=2)
    cax.axvline(da.mean().item(), ymin=0.1, ymax=0.9, ls=":", c="k", lw=1)
    cax.axvline(q1, ymin=0.25, ymax=0.75, c="k", lw=1)
    cax.axvline(q2, ymin=0.1, ymax=0.9, c="k", lw=1)
    cax.axvline(q3, ymin=0.25, ymax=0.75, c="k", lw=1)
    cax.set_xlim(
        (-span if error else span[0]) - (bins[-1] - bins[-2]) * extend_left,
        (span if error else span[1])
        + (bins[-1] - bins[-2]) * (0 + extend_right + np.any(~np.isfinite(da)) * 2),
    )
    cax.set_xticks([])
    cax.set_yticks([])
    cax.spines[:].set_visible(False)
def plot_specific_humidity_log10( my_ERA5_Q: xr.DataArray, cr, chart, title, span, qlog10_eb_abs, error=False, corr=None, my_ERA5_Q_ratio=None, cr_ratio=None, corr_ratio=None, inset=True, ): import copy with np.errstate(divide="ignore", invalid="ignore"): ERA5_Q_log10 = np.log10(ERA5_Q) my_ERA5_Q_log10 = np.log10(my_ERA5_Q) if my_ERA5_Q_ratio is not None: my_ERA5_Q_ratio_log10 = np.log10(my_ERA5_Q_ratio) if error: err_v = np.mean(~(np.abs(my_ERA5_Q_log10 - ERA5_Q_log10) <= qlog10_eb_abs)) if my_ERA5_Q_ratio is not None: err_ratio_v = np.mean( ~(np.abs(my_ERA5_Q_ratio_log10 - ERA5_Q_log10) <= qlog10_eb_abs) ) with xr.set_options(keep_attrs=True): da = (my_ERA5_Q_log10 - ERA5_Q_log10).compute() da.attrs.update( long_name=f"Absolute error over log10({da.long_name.lower()})", units=ERA5_Q.units, ) else: # plot the decimal logarithm of specific humidity to better capture scale da = np.log10(my_ERA5_Q) da.attrs.update( long_name=f"log10({ERA5_Q.long_name.lower()})", units=ERA5_Q.units ) # compute the default style that earthkit.maps would apply source = earthkit.plots.sources.XarraySource(da) style = copy.deepcopy( earthkit.plots.styles.auto.guess_style( source, units=source.units, ) ) if error: style._levels = earthkit.plots.styles.levels.Levels( np.linspace(-span, span, 22) ) style._legend_kwargs["ticks"] = np.linspace(-span, span, 5) style._colors = "coolwarm" else: style._levels = earthkit.plots.styles.levels.Levels(np.linspace(*span, 22)) style._legend_kwargs["ticks"] = np.linspace(*span, 5) style._colors = "viridis" extend_left = np.nanmin(da) < (-span if error else span[0]) extend_right = np.nanmax(da) > (span if error else span[1]) extend = { (False, False): "neither", (True, False): "min", (False, True): "max", (True, True): "both", }[(extend_left, extend_right)] chart.ax.fill_between( [0, 1], [1, 1], hatch="XX", edgecolor="magenta", facecolor="lavenderblush", transform=chart.ax.transAxes, zorder=-12, ) if error: style._legend_kwargs["extend"] = extend chart.pcolormesh(da, style=style, zorder=-11) if corr is not None: da_hatch = my_ERA5_Q == corr da_corr = (~da_hatch).astype(float) old_process_projection_requirements = ( chart.ax.get_figure()._process_projection_requirements ) def _process_projection_requirements( *, axes_class=None, polar=False, projection=None, **kwargs ): if axes_class is not None and projection is not None: return axes_class, dict(projection=projection, **kwargs) return old_process_projection_requirements( axes_class=axes_class, polar=polar, projection=projection, **kwargs ) chart.ax.get_figure()._process_projection_requirements = ( _process_projection_requirements ) axin = chart.ax.inset_axes( [0.025, 0.05, 1 / 3, 1 / 3], xticklabels=[], yticklabels=[], axes_class=type(chart.ax), projection=chart.ax.projection, ) axin.pcolormesh( da_corr.longitude.values, da_corr.latitude.values, np.squeeze(da_corr.values), cmap=mpl.colors.ListedColormap(["white", "green", "lawngreen"]), vmin=0, vmax=2, rasterized=True, ) axin.coastlines(color="#555555") axin.spines["geo"].set_edgecolor("black") axin.set_title( "Corrections", path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")], ) elif inset: da_err = ~(np.abs(da) <= qlog10_eb_abs) old_process_projection_requirements = ( chart.ax.get_figure()._process_projection_requirements ) def _process_projection_requirements( *, axes_class=None, polar=False, projection=None, **kwargs ): if axes_class is not None and projection is not None: return axes_class, dict(projection=projection, **kwargs) return old_process_projection_requirements( axes_class=axes_class, polar=polar, projection=projection, **kwargs ) chart.ax.get_figure()._process_projection_requirements = ( _process_projection_requirements ) axin = chart.ax.inset_axes( [0.025, 0.05, 1 / 3, 1 / 3], xticklabels=[], yticklabels=[], axes_class=type(chart.ax), projection=chart.ax.projection, ) axin.pcolormesh( da_err.longitude.values, da_err.latitude.values, np.squeeze(da_err.values), cmap=mpl.colors.ListedColormap(["white", "red"]), vmin=0, vmax=1, rasterized=True, ) axin.coastlines(color="#555555") axin.spines["geo"].set_edgecolor("black") axin.set_title( "Violations", path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")], ) else: chart.quickplot(da, style=style, extend=extend, zorder=-11) chart.ax.set_rasterization_zorder(-10) chart.title(title) if error: 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%" if my_ERA5_Q_ratio is not None: err_ratio_v = ( 0 if err_ratio_v == 0 else np.format_float_positional( 100 * err_ratio_v, precision=1, min_digits=1 ) + "%" ) if err_ratio_v == "0.0%": err_ratio_v = "<0.05%" t = chart.ax.text( 0.95, 0.1, f"V={err_v}" + ("" if my_ERA5_Q_ratio is None else f" ({err_ratio_v})"), ha="right", va="bottom", transform=chart.ax.transAxes, ) t.set_bbox(dict(facecolor="white", alpha=0.75, edgecolor="black")) t = chart.ax.text( 0.95, 0.9, rf"$\times$ {np.round(cr, 2)}" + ("" if cr_ratio is None else rf" ($\times$ {np.round(cr_ratio, 2)})") if error else humanize.naturalsize(ERA5_Q.nbytes, binary=True), ha="right", va="top", transform=chart.ax.transAxes, ) t.set_bbox(dict(facecolor="white", alpha=0.75, edgecolor="black")) chart.contour( x=np.broadcast_to( da.longitude.values.reshape(1, -1), da.shape, ), y=np.broadcast_to( da.latitude.values.reshape(-1, 1), da.shape, ), z=~(np.isfinite(da).values), colors=["none"], linecolors=["magenta"], levels=[-0.5, 0.9, 1.5], legend_style=None, labels=False, zorder=-10, ) for m in earthkit.plots.schemas.schema.quickmap_subplot_workflow: if m != "title": getattr(chart, m)() for m in earthkit.plots.schemas.schema.quickmap_figure_workflow: getattr(chart, m)() counts, bins = np.histogram( da.values.flatten(), range=(-span if error else span[0], span if error else span[1]), bins=21, ) midpoints = bins[:-1] + np.diff(bins) / 2 cb = chart.ax.collections[1].colorbar extend_width = (bins[-1] - bins[-2]) / (bins[-1] - bins[0]) cax = cb.ax.inset_axes( [ 0.0 - extend_width * extend_left, 1.25, 1.0 + extend_width * (0 + extend_left + extend_right + np.any(~np.isfinite(da)) * 2), 1.0, ] ) cax.bar( midpoints, height=counts, width=(bins[-1] - bins[0]) / len(counts), color=cb.cmap(cb.norm(midpoints)), ) if extend_left: cax.bar( bins[0] - (bins[1] - bins[0]) / 2, height=np.sum(da < (-span if error else span[0])), width=(bins[-1] - bins[0]) / len(counts), color=cb.cmap(cb.norm(midpoints[0])), ) if extend_right: cax.bar( bins[-1] + (bins[-1] - bins[-2]) / 2, height=np.sum(da > (span if error else span[1])), width=(bins[-1] - bins[0]) / len(counts), color=cb.cmap(cb.norm(midpoints[-1])), ) if np.any(~np.isfinite(da)): cax.bar( bins[-1] + (bins[-1] - bins[-2]) * (extend_right * 2 + 2 + 1) / 2, height=np.sum(~np.isfinite(da)), width=(bins[-1] - bins[0]) / len(counts), color="lavenderblush", edgecolor="magenta", lw=0, hatch="XXXX", ) q1, q2, q3 = da.quantile([0.25, 0.5, 0.75]).values cax.axvline(da.mean().item(), ls=":", ymin=0.1, ymax=0.9, c="w", lw=2) cax.axvline(q1, ymin=0.25, ymax=0.75, c="w", lw=2) cax.axvline(q2, ymin=0.1, ymax=0.9, c="w", lw=2) cax.axvline(q3, ymin=0.25, ymax=0.75, c="w", lw=2) cax.axvline(da.mean().item(), ymin=0.1, ymax=0.9, ls=":", c="k", lw=1) cax.axvline(q1, ymin=0.25, ymax=0.75, c="k", lw=1) cax.axvline(q2, ymin=0.1, ymax=0.9, c="k", lw=1) cax.axvline(q3, ymin=0.25, ymax=0.75, c="k", lw=1) cax.set_xlim( (-span if error else span[0]) - (bins[-1] - bins[-2]) * extend_left, (span if error else span[1]) + (bins[-1] - bins[-2]) * (0 + extend_right + np.any(~np.isfinite(da)) * 2), ) cax.set_xticks([]) cax.set_yticks([]) cax.spines[:].set_visible(False)
Copied!
def table_specific_humidity_log10(
    my_ERA5_Q: xr.DataArray,
    cr,
    title,
    qlog10_eb_abs,
    corr=None,
):
    with np.errstate(divide="ignore", invalid="ignore"):
        ERA5_Q_log10 = np.log10(ERA5_Q)
        my_ERA5_Q_log10 = np.log10(my_ERA5_Q)

    err_Q_inf = np.amax(np.abs(my_ERA5_Q - ERA5_Q).values)
    err_Q_log10_inf = np.amax(np.abs(my_ERA5_Q_log10 - ERA5_Q_log10).values)
    err_Q_log10_fin_inf = np.nanmax(
        np.nan_to_num(
            np.abs(my_ERA5_Q_log10 - ERA5_Q_log10),
            nan=np.nan,
            posinf=np.nan,
            neginf=np.nan,
        )
    )
    err_Q_log10_2 = np.sqrt(np.mean(np.square(my_ERA5_Q_log10 - ERA5_Q_log10).values))
    err_Q_log10_fin_2 = np.sqrt(
        np.nanmean(
            np.nan_to_num(
                np.square(my_ERA5_Q_log10 - ERA5_Q_log10),
                nan=np.nan,
                posinf=np.nan,
                neginf=np.nan,
            )
        )
    )

    err_v = np.mean(~(np.abs(my_ERA5_Q_log10 - ERA5_Q_log10) <= qlog10_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_ERA5_Q, 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]],
            "(Bound)": [title[1]],
            "Safeguarded": [title[2]],
            "Corrections": [title[3]],
            r"$L_{\infty}(\hat{q})$": [f"{err_Q_inf:.02}"],
            r"$L_{\infty}(\log_{10}(\hat{q}))$": [
                f"{err_Q_log10_inf:.03}".replace("nan", "NaN")
                + (
                    ""
                    if np.isfinite(err_Q_log10_inf)
                    else f" [{err_Q_log10_fin_inf:.03}]"
                )
            ],
            r"$L_{2}(\log_{10}(\hat{q}))$": [
                f"{err_Q_log10_2:.03}".replace("nan", "NaN")
                + ("" if np.isfinite(err_Q_log10_2) else f" [{err_Q_log10_fin_2:.03}]")
            ],
            "V": [err_v],
            "C": [corr],
            "CR": [rf"$\times$ {np.round(cr, 2)}"],
        }
    )
def table_specific_humidity_log10( my_ERA5_Q: xr.DataArray, cr, title, qlog10_eb_abs, corr=None, ): with np.errstate(divide="ignore", invalid="ignore"): ERA5_Q_log10 = np.log10(ERA5_Q) my_ERA5_Q_log10 = np.log10(my_ERA5_Q) err_Q_inf = np.amax(np.abs(my_ERA5_Q - ERA5_Q).values) err_Q_log10_inf = np.amax(np.abs(my_ERA5_Q_log10 - ERA5_Q_log10).values) err_Q_log10_fin_inf = np.nanmax( np.nan_to_num( np.abs(my_ERA5_Q_log10 - ERA5_Q_log10), nan=np.nan, posinf=np.nan, neginf=np.nan, ) ) err_Q_log10_2 = np.sqrt(np.mean(np.square(my_ERA5_Q_log10 - ERA5_Q_log10).values)) err_Q_log10_fin_2 = np.sqrt( np.nanmean( np.nan_to_num( np.square(my_ERA5_Q_log10 - ERA5_Q_log10), nan=np.nan, posinf=np.nan, neginf=np.nan, ) ) ) err_v = np.mean(~(np.abs(my_ERA5_Q_log10 - ERA5_Q_log10) <= qlog10_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_ERA5_Q, 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]], "(Bound)": [title[1]], "Safeguarded": [title[2]], "Corrections": [title[3]], r"$L_{\infty}(\hat{q})$": [f"{err_Q_inf:.02}"], r"$L_{\infty}(\log_{10}(\hat{q}))$": [ f"{err_Q_log10_inf:.03}".replace("nan", "NaN") + ( "" if np.isfinite(err_Q_log10_inf) else f" [{err_Q_log10_fin_inf:.03}]" ) ], r"$L_{2}(\log_{10}(\hat{q}))$": [ f"{err_Q_log10_2:.03}".replace("nan", "NaN") + ("" if np.isfinite(err_Q_log10_2) else f" [{err_Q_log10_fin_2:.03}]") ], "V": [err_v], "C": [corr], "CR": [rf"$\times$ {np.round(cr, 2)}"], } )
Copied!
import observe

observations = []
import observe observations = []

Lossless compression¶

We first compress the data losslessly with ZStandard at level 22, which gives maximum compression, to provide a baseline.

Copied!
from numcodecs_wasm_zstd import Zstd

zstd = Zstd(level=22)

with observe.observe(zstd, observations):
    ERA5_Q_zstd_enc = zstd.encode(ERA5_Q.values)
    ERA5_Q_zstd = ERA5_Q.copy(data=zstd.decode(ERA5_Q_zstd_enc))

ERA5_Q_zstd_cr = ERA5_Q.nbytes / ERA5_Q_zstd_enc.nbytes
from numcodecs_wasm_zstd import Zstd zstd = Zstd(level=22) with observe.observe(zstd, observations): ERA5_Q_zstd_enc = zstd.encode(ERA5_Q.values) ERA5_Q_zstd = ERA5_Q.copy(data=zstd.decode(ERA5_Q_zstd_enc)) ERA5_Q_zstd_cr = ERA5_Q.nbytes / ERA5_Q_zstd_enc.nbytes

Compressing q with lossy compressors¶

We configure each compressor with an absolute error bound of $5 \cdot 10^{-4}$ kg/kg, which produces decent compression ratios and shows that ZFP, SZ3 and ZFP can produce negative values whose logarithm is undefined.

Copied!
eb_abs = 0.0005
eb_abs = 0.0005
Copied!
from numcodecs_wasm_zfp import Zfp

zfp = Zfp(mode="fixed-accuracy", tolerance=eb_abs)

with observe.observe(zfp, observations):
    ERA5_Q_zfp_enc = zfp.encode(ERA5_Q.values)
    ERA5_Q_zfp = ERA5_Q.copy(data=zfp.decode(ERA5_Q_zfp_enc))

ERA5_Q_zfp_cr = ERA5_Q.nbytes / ERA5_Q_zfp_enc.nbytes
from numcodecs_wasm_zfp import Zfp zfp = Zfp(mode="fixed-accuracy", tolerance=eb_abs) with observe.observe(zfp, observations): ERA5_Q_zfp_enc = zfp.encode(ERA5_Q.values) ERA5_Q_zfp = ERA5_Q.copy(data=zfp.decode(ERA5_Q_zfp_enc)) ERA5_Q_zfp_cr = ERA5_Q.nbytes / ERA5_Q_zfp_enc.nbytes
Copied!
from numcodecs_wasm_sz3 import Sz3

sz3 = Sz3(eb_mode="abs", eb_abs=eb_abs)

with observe.observe(sz3, observations):
    ERA5_Q_sz3_enc = sz3.encode(ERA5_Q.values)
    ERA5_Q_sz3 = ERA5_Q.copy(data=sz3.decode(ERA5_Q_sz3_enc))

ERA5_Q_sz3_cr = ERA5_Q.nbytes / ERA5_Q_sz3_enc.nbytes
from numcodecs_wasm_sz3 import Sz3 sz3 = Sz3(eb_mode="abs", eb_abs=eb_abs) with observe.observe(sz3, observations): ERA5_Q_sz3_enc = sz3.encode(ERA5_Q.values) ERA5_Q_sz3 = ERA5_Q.copy(data=sz3.decode(ERA5_Q_sz3_enc)) ERA5_Q_sz3_cr = ERA5_Q.nbytes / ERA5_Q_sz3_enc.nbytes
Copied!
from numcodecs_wasm_sperr import Sperr

sperr = Sperr(mode="pwe", pwe=eb_abs)

with observe.observe(sperr, observations):
    ERA5_Q_sperr_enc = sperr.encode(ERA5_Q.values)
    ERA5_Q_sperr = ERA5_Q.copy(data=sperr.decode(ERA5_Q_sperr_enc))

ERA5_Q_sperr_cr = ERA5_Q.nbytes / ERA5_Q_sperr_enc.nbytes
from numcodecs_wasm_sperr import Sperr sperr = Sperr(mode="pwe", pwe=eb_abs) with observe.observe(sperr, observations): ERA5_Q_sperr_enc = sperr.encode(ERA5_Q.values) ERA5_Q_sperr = ERA5_Q.copy(data=sperr.decode(ERA5_Q_sperr_enc)) ERA5_Q_sperr_cr = ERA5_Q.nbytes / ERA5_Q_sperr_enc.nbytes
Copied!
from numcodecs_zero import ZeroCodec

zero = ZeroCodec()

with observe.observe(zero, observations):
    ERA5_Q_zero_enc = zero.encode(ERA5_Q.values)
    ERA5_Q_zero = ERA5_Q.copy(data=zero.decode(ERA5_Q_zero_enc))
from numcodecs_zero import ZeroCodec zero = ZeroCodec() with observe.observe(zero, observations): ERA5_Q_zero_enc = zero.encode(ERA5_Q.values) ERA5_Q_zero = ERA5_Q.copy(data=zero.decode(ERA5_Q_zero_enc))

Compressing q using the safeguarded lossy compressors¶

We configure the safeguards to bound the pointwise absolute error on the decimal logarithm diagnostic, choosing an error bound of 0.25 (in logarithmic space).

Copied!
qlog10_eb_abs = 0.25
qlog10_eb_abs = 0.25
Copied!
from numcodecs_safeguards import SafeguardedCodec

ERA5_Q_sg = dict()
ERA5_Q_sg_cr = dict()

for codec_id, codec in {
    "zero": zero,
    "zfp.rs": zfp,
    "sz3.rs": sz3,
    "sperr.rs": sperr,
}.items():
    sg = SafeguardedCodec(
        codec=codec,
        safeguards=[
            dict(
                kind="qoi_eb_pw",
                qoi="log10(x)",
                type="abs",
                eb=qlog10_eb_abs,
            )
        ],
    )

    with observe.observe(sg, observations):
        ERA5_Q_sg_enc = sg.encode(ERA5_Q.values)
        ERA5_Q_sg[codec_id] = ERA5_Q.copy(data=sg.decode(ERA5_Q_sg_enc))

    ERA5_Q_sg_cr[codec_id] = ERA5_Q.nbytes / np.asarray(ERA5_Q_sg_enc).nbytes
from numcodecs_safeguards import SafeguardedCodec ERA5_Q_sg = dict() ERA5_Q_sg_cr = dict() for codec_id, codec in { "zero": zero, "zfp.rs": zfp, "sz3.rs": sz3, "sperr.rs": sperr, }.items(): sg = SafeguardedCodec( codec=codec, safeguards=[ dict( kind="qoi_eb_pw", qoi="log10(x)", type="abs", eb=qlog10_eb_abs, ) ], ) with observe.observe(sg, observations): ERA5_Q_sg_enc = sg.encode(ERA5_Q.values) ERA5_Q_sg[codec_id] = ERA5_Q.copy(data=sg.decode(ERA5_Q_sg_enc)) ERA5_Q_sg_cr[codec_id] = ERA5_Q.nbytes / np.asarray(ERA5_Q_sg_enc).nbytes
Copied!
from numcodecs_safeguards import SafeguardedCodec

ERA5_Q_sg_lossless = dict()
ERA5_Q_sg_lossless_cr = dict()

for codec_id, codec in {
    "zero": zero,
    "zfp.rs": zfp,
    "sz3.rs": sz3,
    "sperr.rs": sperr,
}.items():
    sg = SafeguardedCodec(
        codec=codec,
        safeguards=[
            dict(
                kind="qoi_eb_pw",
                qoi="log10(x)",
                type="abs",
                eb=qlog10_eb_abs,
            )
        ],
        # produce lossless corrections and refine them with iteration
        compute=dict(unstable_iterative=True, unstable_lossless_corrections=True),
    )

    with observe.observe(sg, observations):
        ERA5_Q_sg_lossless_enc = sg.encode(ERA5_Q.values)
        ERA5_Q_sg_lossless[codec_id] = ERA5_Q.copy(
            data=sg.decode(ERA5_Q_sg_lossless_enc)
        )

    ERA5_Q_sg_lossless_cr[codec_id] = (
        ERA5_Q.nbytes / np.asarray(ERA5_Q_sg_lossless_enc).nbytes
    )
from numcodecs_safeguards import SafeguardedCodec ERA5_Q_sg_lossless = dict() ERA5_Q_sg_lossless_cr = dict() for codec_id, codec in { "zero": zero, "zfp.rs": zfp, "sz3.rs": sz3, "sperr.rs": sperr, }.items(): sg = SafeguardedCodec( codec=codec, safeguards=[ dict( kind="qoi_eb_pw", qoi="log10(x)", type="abs", eb=qlog10_eb_abs, ) ], # produce lossless corrections and refine them with iteration compute=dict(unstable_iterative=True, unstable_lossless_corrections=True), ) with observe.observe(sg, observations): ERA5_Q_sg_lossless_enc = sg.encode(ERA5_Q.values) ERA5_Q_sg_lossless[codec_id] = ERA5_Q.copy( data=sg.decode(ERA5_Q_sg_lossless_enc) ) ERA5_Q_sg_lossless_cr[codec_id] = ( ERA5_Q.nbytes / np.asarray(ERA5_Q_sg_lossless_enc).nbytes )

Compressing q with QPET-SPERR¶

We similarly configure QPET-SPERR to bound the pointwise absolute error on the decimal logarithm diagnostic, choosing an error bound of 0.25 (in logarithmic space).

Copied!
from numcodecs_wasm_qpet_sperr import QpetSperr

qpet = QpetSperr(
    mode="qoi-symbolic",
    qoi="log(x, 10)",
    qoi_pwe=qlog10_eb_abs,
    qoi_block_size=(1, 1, 1),
    high_prec=True,
)

with observe.observe(qpet, observations):
    ERA5_Q_qpet_enc = qpet.encode(ERA5_Q.values)
    ERA5_Q_qpet = ERA5_Q.copy(data=qpet.decode(ERA5_Q_qpet_enc))

ERA5_Q_qpet_cr = ERA5_Q.nbytes / ERA5_Q_qpet_enc.nbytes
from numcodecs_wasm_qpet_sperr import QpetSperr qpet = QpetSperr( mode="qoi-symbolic", qoi="log(x, 10)", qoi_pwe=qlog10_eb_abs, qoi_block_size=(1, 1, 1), high_prec=True, ) with observe.observe(qpet, observations): ERA5_Q_qpet_enc = qpet.encode(ERA5_Q.values) ERA5_Q_qpet = ERA5_Q.copy(data=qpet.decode(ERA5_Q_qpet_enc)) ERA5_Q_qpet_cr = ERA5_Q.nbytes / ERA5_Q_qpet_enc.nbytes
Tuning eb with qoi
current_eb = 0.00026811, current_br = 0.429688
current_eb = 0.000177975, current_br = 0.484375
current_eb = 0.00013569, current_br = 0.515625
current_eb = 8.80638e-05, current_br = 0.65625
current_eb = 7.67135e-05, current_br = 0.601562
current_eb = 7.00369e-05, current_br = 0.625
current_eb = 6.53633e-05, current_br = 0.625
Selected quantile: 0.200012
Best abs eb:  0.00026811
Tuning eb with qoi
current_eb = 0.000230275, current_br = 0.46875
current_eb = 0.00016796, current_br = 0.492188
current_eb = 0.000127011, current_br = 0.523438
current_eb = 9.69659e-05, current_br = 0.570312
current_eb = 8.98442e-05, current_br = 0.601562
current_eb = 8.47255e-05, current_br = 0.625
current_eb = 6.98144e-05, current_br = 0.648438
Selected quantile: 0.200012
Best abs eb:  0.000230275
Tuning eb with qoi
current_eb = 0.000205349, current_br = 0.382812
current_eb = 0.000152382, current_br = 0.453125
current_eb = 0.000129459, current_br = 0.5
current_eb = 0.000106536, current_br = 0.515625
current_eb = 8.07195e-05, current_br = 0.554688
current_eb = 6.53633e-05, current_br = 0.640625
current_eb = 5.04522e-05, current_br = 0.679688
Selected quantile: 0.200012
Best abs eb:  0.000205349
Tuning eb with qoi
current_eb = 0.000205349, current_br = 0.5
current_eb = 0.000205349, current_br = 0.5
current_eb = 0.00018532, current_br = 0.507812
current_eb = 0.00013569, current_br = 0.523438
current_eb = 0.000108984, current_br = 0.570312
current_eb = 9.14021e-05, current_br = 0.609375
current_eb = 7.09271e-05, current_br = 0.648438
Selected quantile: 0.0500031
Best abs eb:  0.00018532
Tuning eb with qoi
current_eb = 0.00018532, current_br = 0.554688
current_eb = 0.00018532, current_br = 0.554688
current_eb = 0.00018532, current_br = 0.554688
current_eb = 0.000161061, current_br = 0.609375
current_eb = 0.000139696, current_br = 0.648438
current_eb = 0.000121447, current_br = 0.671875
current_eb = 8.24999e-05, current_br = 0.8125
Selected quantile: 0.0500031
Best abs eb:  0.00018532
Tuning eb with qoi
current_eb = 0.00018532, current_br = 0.601562
current_eb = 0.00018532, current_br = 0.601562
current_eb = 0.000157055, current_br = 0.609375
current_eb = 7.91616e-05, current_br = 0.820312
current_eb = 6.80339e-05, current_br = 0.882812
current_eb = 5.93544e-05, current_br = 0.9375
current_eb = 5.3568e-05, current_br = 1.11719
Selected quantile: 0.05
Best abs eb:  0.000157055
Tuning eb with qoi
current_eb = 0.000157055, current_br = 1.27344
current_eb = 0.000157055, current_br = 1.27344
current_eb = 0.000157055, current_br = 1.27344
current_eb = 0.000157055, current_br = 1.27344
current_eb = 0.000157055, current_br = 1.27344
current_eb = 0.000157055, current_br = 1.27344
current_eb = 0.000157055, current_br = 1.27344
Selected quantile: 0.0019989
Best abs eb:  0.000157055
Tuning eb with qoi
current_eb = 0.000157055, current_br = 2.17188
current_eb = 0.000157055, current_br = 2.17188
current_eb = 0.000157055, current_br = 2.17188
current_eb = 0.000157055, current_br = 2.17188
current_eb = 0.000157055, current_br = 2.17188
current_eb = 0.000157055, current_br = 2.17188
current_eb = 0.000135468, current_br = 2.36719
Selected quantile: 0.00498962
Best abs eb:  0.000157055
Tuning eb with qoi
current_eb = 0.000157055, current_br = 2.125
current_eb = 0.000157055, current_br = 2.125
current_eb = 0.000157055, current_br = 2.125
current_eb = 0.000157055, current_br = 2.125
current_eb = 0.000157055, current_br = 2.125
current_eb = 0.000157055, current_br = 2.125
current_eb = 0.000157055, current_br = 2.125
Selected quantile: 0.0019989
Best abs eb:  0.000157055
Tuning eb with qoi
current_eb = 0.000157055, current_br = 1.73438
current_eb = 0.000157055, current_br = 1.73438
current_eb = 0.000157055, current_br = 1.73438
current_eb = 0.000157055, current_br = 1.73438
current_eb = 0.000157055, current_br = 1.73438
current_eb = 0.000119444, current_br = 2.01562
current_eb = 8.24999e-05, current_br = 2.5
Selected quantile: 0.00999451
Best abs eb:  0.000157055
Tuning eb with qoi
current_eb = 0.000157055, current_br = 3.35156
current_eb = 0.000157055, current_br = 3.35156
current_eb = 0.000157055, current_br = 3.35156
current_eb = 0.000157055, current_br = 3.35156
current_eb = 0.000157055, current_br = 3.35156
current_eb = 0.000157055, current_br = 3.35156
current_eb = 0.000157055, current_br = 3.35156
Selected quantile: 0.0019989
Best abs eb:  0.000157055
Tuning eb with qoi
current_eb = 0.000157055, current_br = 2.23438
current_eb = 0.000157055, current_br = 2.23438
current_eb = 0.000157055, current_br = 2.23438
current_eb = 0.000157055, current_br = 2.23438
current_eb = 0.000157055, current_br = 2.23438
current_eb = 0.000157055, current_br = 2.23438
current_eb = 0.000157055, current_br = 2.23438
Selected quantile: 0.00197754
Best abs eb:  0.000157055
Tuning eb with qoi
current_eb = 2.30781e-05, current_br = 4.375
current_eb = 1.6179e-05, current_br = 4.89844
current_eb = 1.017e-05, current_br = 5.60156
current_eb = 6.83173e-06, current_br = 6.15625
current_eb = 6.16407e-06, current_br = 6.34375
current_eb = 5.71896e-06, current_br = 6.35156
current_eb = 5.27386e-06, current_br = 6.54688
Selected quantile: 0.200004
Best abs eb:  2.30781e-05
Tuning eb with qoi
current_eb = 2.08526e-05, current_br = 3.73438
current_eb = 1.06151e-05, current_br = 4.69531
current_eb = 6.83173e-06, current_br = 5.26562
current_eb = 5.0513e-06, current_br = 5.80469
current_eb = 4.38364e-06, current_br = 5.92188
current_eb = 4.16109e-06, current_br = 6.01562
current_eb = 3.93854e-06, current_br = 6.0625
Selected quantile: 0.200004
Best abs eb:  2.08526e-05
Tuning eb with qoi
current_eb = 2.08526e-05, current_br = 3.26562
current_eb = 2.08526e-05, current_br = 3.26562
current_eb = 2.08526e-05, current_br = 3.26562
current_eb = 1.15054e-05, current_br = 4.24219
current_eb = 9.05727e-06, current_br = 4.4375
current_eb = 7.27684e-06, current_br = 4.82031
current_eb = 6.60918e-06, current_br = 4.94531
Selected quantile: 0.0499963
Best abs eb:  2.08526e-05
Tuning eb with qoi
current_eb = 2.08526e-05, current_br = 3.76562
current_eb = 2.08526e-05, current_br = 3.76562
current_eb = 2.08526e-05, current_br = 3.76562
current_eb = 2.08526e-05, current_br = 3.76562
current_eb = 2.08526e-05, current_br = 3.76562
current_eb = 2.08526e-05, current_br = 3.76562
current_eb = 2.08526e-05, current_br = 3.76562
Selected quantile: 0.00199985
Best abs eb:  2.08526e-05
Tuning eb with qoi
current_eb = 2.08526e-05, current_br = 3.41406
current_eb = 2.08526e-05, current_br = 3.41406
current_eb = 2.08526e-05, current_br = 3.41406
current_eb = 2.08526e-05, current_br = 3.41406
current_eb = 2.08526e-05, current_br = 3.41406
current_eb = 2.08526e-05, current_br = 3.41406
current_eb = 2.08526e-05, current_br = 3.41406
Selected quantile: 0.00199985
Best abs eb:  2.08526e-05
Tuning eb with qoi
current_eb = 2.08526e-05, current_br = 4.8125
current_eb = 2.08526e-05, current_br = 4.8125
current_eb = 2.08526e-05, current_br = 4.8125
current_eb = 2.08526e-05, current_br = 4.8125
current_eb = 2.08526e-05, current_br = 4.8125
current_eb = 2.04075e-05, current_br = 4.85938
current_eb = 2.01849e-05, current_br = 4.88281
Selected quantile: 0.00197368
Best abs eb:  2.01849e-05

Compressing q with ratio-error-bounded lossy compressors¶

In this simple case of a $\text{QoI} = \log_{10}(x)$ diagnostic with $\epsilon_{abs}(\text{QoI}) \leq 0.25$, we could have also used a ratio error bound with $\epsilon_{ratio} = 10^{0.25}$ to preserve the quantity of interest. While ZFP, SZ3, and SPERR do not natively support ratio error bounds, we can use a meta-compressor that transforms the ratio error bound into an absolute error bound. This meta-compressor was proposed by Liang et al. [^1] and has been implemented in libpressio as the pw_rel_compressor_plugin and in the numcodecs-pw-ratio package.

[^1]: Liang, X., Di, S., Tao, D., Chen, Z., & Cappello, F. (2018). An Efficient Transformation Scheme for Lossy Data Compression with Point-Wise Relative Error Bound. 2018 IEEE International Conference on Cluster Computing (CLUSTER), 179–189. Available from: doi:10.1109/cluster.2018.00036.

Copied!
from numcodecs_pw_ratio import PointwiseRatioErrorBoundedCodec
from numcodecs_pw_ratio import PointwiseRatioErrorBoundedCodec
Copied!
zfp_ratio = PointwiseRatioErrorBoundedCodec(
    eb_ratio=np.power(10, 0.25),
    eb_abs_marker="$eb_abs",
    log_codec={**zfp.get_config(), "tolerance": "$eb_abs"},
    sign_codec=dict(id="zlib", level=9),
)

with observe.observe(zfp_ratio, observations):
    ERA5_Q_zfp_ratio_enc = zfp_ratio.encode(ERA5_Q.values)
    ERA5_Q_zfp_ratio = ERA5_Q.copy(data=zfp_ratio.decode(ERA5_Q_zfp_ratio_enc))

ERA5_Q_zfp_ratio_cr = ERA5_Q.nbytes / np.asarray(ERA5_Q_zfp_ratio_enc).nbytes
zfp_ratio = PointwiseRatioErrorBoundedCodec( eb_ratio=np.power(10, 0.25), eb_abs_marker="$eb_abs", log_codec={**zfp.get_config(), "tolerance": "$eb_abs"}, sign_codec=dict(id="zlib", level=9), ) with observe.observe(zfp_ratio, observations): ERA5_Q_zfp_ratio_enc = zfp_ratio.encode(ERA5_Q.values) ERA5_Q_zfp_ratio = ERA5_Q.copy(data=zfp_ratio.decode(ERA5_Q_zfp_ratio_enc)) ERA5_Q_zfp_ratio_cr = ERA5_Q.nbytes / np.asarray(ERA5_Q_zfp_ratio_enc).nbytes
Copied!
sz3_ratio = PointwiseRatioErrorBoundedCodec(
    eb_ratio=np.power(10, 0.25),
    eb_abs_marker="$eb_abs",
    log_codec={**sz3.get_config(), "eb_abs": "$eb_abs"},
    sign_codec=dict(id="zlib", level=9),
)

with observe.observe(sz3_ratio, observations):
    ERA5_Q_sz3_ratio_enc = sz3_ratio.encode(ERA5_Q.values)
    ERA5_Q_sz3_ratio = ERA5_Q.copy(data=sz3_ratio.decode(ERA5_Q_sz3_ratio_enc))

ERA5_Q_sz3_ratio_cr = ERA5_Q.nbytes / np.asarray(ERA5_Q_sz3_ratio_enc).nbytes
sz3_ratio = PointwiseRatioErrorBoundedCodec( eb_ratio=np.power(10, 0.25), eb_abs_marker="$eb_abs", log_codec={**sz3.get_config(), "eb_abs": "$eb_abs"}, sign_codec=dict(id="zlib", level=9), ) with observe.observe(sz3_ratio, observations): ERA5_Q_sz3_ratio_enc = sz3_ratio.encode(ERA5_Q.values) ERA5_Q_sz3_ratio = ERA5_Q.copy(data=sz3_ratio.decode(ERA5_Q_sz3_ratio_enc)) ERA5_Q_sz3_ratio_cr = ERA5_Q.nbytes / np.asarray(ERA5_Q_sz3_ratio_enc).nbytes
Copied!
sperr_ratio = PointwiseRatioErrorBoundedCodec(
    eb_ratio=np.power(10, 0.25),
    eb_abs_marker="$eb_abs",
    log_codec={**sperr.get_config(), "pwe": "$eb_abs"},
    sign_codec=dict(id="zlib", level=9),
)

with observe.observe(sperr_ratio, observations):
    ERA5_Q_sperr_ratio_enc = sperr_ratio.encode(ERA5_Q.values)
    ERA5_Q_sperr_ratio = ERA5_Q.copy(data=sperr_ratio.decode(ERA5_Q_sperr_ratio_enc))

ERA5_Q_sperr_ratio_cr = ERA5_Q.nbytes / np.asarray(ERA5_Q_sperr_ratio_enc).nbytes
sperr_ratio = PointwiseRatioErrorBoundedCodec( eb_ratio=np.power(10, 0.25), eb_abs_marker="$eb_abs", log_codec={**sperr.get_config(), "pwe": "$eb_abs"}, sign_codec=dict(id="zlib", level=9), ) with observe.observe(sperr_ratio, observations): ERA5_Q_sperr_ratio_enc = sperr_ratio.encode(ERA5_Q.values) ERA5_Q_sperr_ratio = ERA5_Q.copy(data=sperr_ratio.decode(ERA5_Q_sperr_ratio_enc)) ERA5_Q_sperr_ratio_cr = ERA5_Q.nbytes / np.asarray(ERA5_Q_sperr_ratio_enc).nbytes
Copied!
ERA5_Q_ratio_sg = dict()
ERA5_Q_ratio_sg_cr = dict()

for codec_id, codec in {
    "zfp.rs": zfp_ratio,
    "sz3.rs": sz3_ratio,
    "sperr.rs": sperr_ratio,
}.items():
    sg = SafeguardedCodec(
        codec=codec,
        safeguards=[
            dict(
                kind="qoi_eb_pw",
                qoi="log10(x)",
                type="abs",
                eb=qlog10_eb_abs,
            )
        ],
    )

    with observe.observe(sg, observations):
        ERA5_Q_ratio_sg_enc = sg.encode(ERA5_Q.values)
        ERA5_Q_ratio_sg[codec_id] = ERA5_Q.copy(data=sg.decode(ERA5_Q_ratio_sg_enc))

    ERA5_Q_ratio_sg_cr[codec_id] = (
        ERA5_Q.nbytes / np.asarray(ERA5_Q_ratio_sg_enc).nbytes
    )
ERA5_Q_ratio_sg = dict() ERA5_Q_ratio_sg_cr = dict() for codec_id, codec in { "zfp.rs": zfp_ratio, "sz3.rs": sz3_ratio, "sperr.rs": sperr_ratio, }.items(): sg = SafeguardedCodec( codec=codec, safeguards=[ dict( kind="qoi_eb_pw", qoi="log10(x)", type="abs", eb=qlog10_eb_abs, ) ], ) with observe.observe(sg, observations): ERA5_Q_ratio_sg_enc = sg.encode(ERA5_Q.values) ERA5_Q_ratio_sg[codec_id] = ERA5_Q.copy(data=sg.decode(ERA5_Q_ratio_sg_enc)) ERA5_Q_ratio_sg_cr[codec_id] = ( ERA5_Q.nbytes / np.asarray(ERA5_Q_ratio_sg_enc).nbytes )

We can similarly compare the safeguards with a ratio-error bound instead of the $\log_{10}(x)$ quantity of interest. Using an equivalent but simpler safeguard can sometimes result in higher compression ratios (e.g. using qoi="log(x, base=10)" results in a lower compression ratio):

Copied!
sg_ratio = SafeguardedCodec(
    codec=ZeroCodec(),
    safeguards=[
        dict(
            kind="eb",
            type="ratio",
            eb=np.power(10.0, qlog10_eb_abs),
        )
    ],
)

with observe.observe(sg_ratio, observations):
    ERA5_Q_sg_ratio_enc = sg_ratio.encode(ERA5_Q.values)
    ERA5_Q_sg_ratio = ERA5_Q.copy(data=sg_ratio.decode(ERA5_Q_sg_ratio_enc))

ERA5_Q_sg_ratio_cr = ERA5_Q.nbytes / np.asarray(ERA5_Q_sg_ratio_enc).nbytes
sg_ratio = SafeguardedCodec( codec=ZeroCodec(), safeguards=[ dict( kind="eb", type="ratio", eb=np.power(10.0, qlog10_eb_abs), ) ], ) with observe.observe(sg_ratio, observations): ERA5_Q_sg_ratio_enc = sg_ratio.encode(ERA5_Q.values) ERA5_Q_sg_ratio = ERA5_Q.copy(data=sg_ratio.decode(ERA5_Q_sg_ratio_enc)) ERA5_Q_sg_ratio_cr = ERA5_Q.nbytes / np.asarray(ERA5_Q_sg_ratio_enc).nbytes
Copied!
sg_ratio_lossless = SafeguardedCodec(
    codec=ZeroCodec(),
    safeguards=[
        dict(
            kind="eb",
            type="ratio",
            eb=np.power(10.0, qlog10_eb_abs),
        )
    ],
    # produce lossless corrections and refine them with iteration
    compute=dict(unstable_iterative=True, unstable_lossless_corrections=True),
)

with observe.observe(sg_ratio_lossless, observations):
    ERA5_Q_sg_ratio_lossless_enc = sg_ratio_lossless.encode(ERA5_Q.values)
    ERA5_Q_sg_ratio_lossless = ERA5_Q.copy(
        data=sg_ratio_lossless.decode(ERA5_Q_sg_ratio_lossless_enc)
    )

ERA5_Q_sg_ratio_lossless_cr = (
    ERA5_Q.nbytes / np.asarray(ERA5_Q_sg_ratio_lossless_enc).nbytes
)
sg_ratio_lossless = SafeguardedCodec( codec=ZeroCodec(), safeguards=[ dict( kind="eb", type="ratio", eb=np.power(10.0, qlog10_eb_abs), ) ], # produce lossless corrections and refine them with iteration compute=dict(unstable_iterative=True, unstable_lossless_corrections=True), ) with observe.observe(sg_ratio_lossless, observations): ERA5_Q_sg_ratio_lossless_enc = sg_ratio_lossless.encode(ERA5_Q.values) ERA5_Q_sg_ratio_lossless = ERA5_Q.copy( data=sg_ratio_lossless.decode(ERA5_Q_sg_ratio_lossless_enc) ) ERA5_Q_sg_ratio_lossless_cr = ( ERA5_Q.nbytes / np.asarray(ERA5_Q_sg_ratio_lossless_enc).nbytes )

Compressing q 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.

Copied!
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
        with np.errstate(divide="ignore", invalid="ignore"):
            data_log10 = np.log10(self._data)
            buf_log10 = np.log10(buf)
        violations = np.mean(~(np.abs(buf_log10 - data_log10) <= qlog10_eb_abs))
        self._data = None
        # return the violations score metric
        return numcodecs.compat.ndarray_copy(np.float64(violations), out)


numcodecs.registry.register_codec(SafetyViolationsMetric)
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 with np.errstate(divide="ignore", invalid="ignore"): data_log10 = np.log10(self._data) buf_log10 = np.log10(buf) violations = np.mean(~(np.abs(buf_log10 - data_log10) <= qlog10_eb_abs)) self._data = None # return the violations score metric return numcodecs.compat.ndarray_copy(np.float64(violations), out) numcodecs.registry.register_codec(SafetyViolationsMetric)
Copied!
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)
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)
Copied!
from numcodecs_wasm_pressio import Pressio

ERA5_Q_optzconfig = dict()
ERA5_Q_optzconfig_cr = dict()

for codec, parameter, lower_bound in [
    (zfp, "tolerance", 1e-6),  # initial guess
    (sz3, "eb_abs", 1e-6),  # initial guess
    (sperr, "pwe", 1e-6),  # initial 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):
        ERA5_Q_optzconfig_enc = optzconfig.encode(ERA5_Q.values)
        ERA5_Q_optzconfig[codec.codec_id] = ERA5_Q.copy(
            data=optzconfig.decode(ERA5_Q_optzconfig_enc)
        )

    ERA5_Q_optzconfig_cr[codec.codec_id] = (
        ERA5_Q.nbytes / np.asarray(ERA5_Q_optzconfig_enc).nbytes
    )
from numcodecs_wasm_pressio import Pressio ERA5_Q_optzconfig = dict() ERA5_Q_optzconfig_cr = dict() for codec, parameter, lower_bound in [ (zfp, "tolerance", 1e-6), # initial guess (sz3, "eb_abs", 1e-6), # initial guess (sperr, "pwe", 1e-6), # initial 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): ERA5_Q_optzconfig_enc = optzconfig.encode(ERA5_Q.values) ERA5_Q_optzconfig[codec.codec_id] = ERA5_Q.copy( data=optzconfig.decode(ERA5_Q_optzconfig_enc) ) ERA5_Q_optzconfig_cr[codec.codec_id] = ( ERA5_Q.nbytes / np.asarray(ERA5_Q_optzconfig_enc).nbytes )
rank={0,1,} iter={0} input={-10.7082,} output={5.05583,} objective={5.05583}
rank={0,1,} iter={1} input={-12.3658,} output={3.90135,} objective={3.90135}
rank={0,1,} iter={2} input={-9.07594,} output={-9.63168e-06,} objective={-9.63168e-06}
rank={0,1,} iter={3} input={-11.235,} output={4.40965,} objective={4.40965}
rank={0,1,} iter={4} input={-13.8136,} output={3.14865,} objective={3.14865}
rank={0,1,} iter={5} input={-11.7182,} output={4.40965,} objective={4.40965}
rank={0,1,} iter={6} input={-9.92084,} output={-1.92634e-06,} objective={-1.92634e-06}
rank={0,1,} iter={7} input={-7.60101,} output={-0.0149503,} objective={-0.0149503}
rank={0,1,} iter={8} input={-10.8662,} output={5.05583,} objective={5.05583}
rank={0,1,} iter={9} input={-13.0324,} output={3.49067,} objective={3.49067}
rank={0,1,} iter={10} input={-10.7872,} output={5.05583,} objective={5.05583}
rank={0,1,} iter={11} input={-12.002,} output={3.90135,} objective={3.90135}
rank={0,1,} iter={12} input={-11.4765,} output={4.40965,} objective={4.40965}
rank={0,1,} iter={13} input={-10.9995,} output={5.05583,} objective={5.05583}
rank={0,1,} iter={14} input={-12.6667,} output={3.49067,} objective={3.49067}
rank={0,1,} iter={15} input={-13.3964,} output={3.14865,} objective={3.14865}
rank={0,1,} iter={16} input={-11.067,} output={5.05583,} objective={5.05583}
rank={0,1,} iter={17} input={-10.9334,} output={5.05583,} objective={5.05583}
rank={0,1,} iter={18} input={-10.7474,} output={5.05583,} objective={5.05583}
rank={0,1,} iter={19} input={-10.8268,} output={5.05583,} objective={5.05583}
rank={0,1,} iter={20} input={-11.1007,} output={4.40965,} objective={4.40965}
rank={0,1,} iter={21} input={-8.33866,} output={-0.000198413,} objective={-0.000198413}
rank={0,1,} iter={22} input={-10.6072,} output={5.05583,} objective={5.05583}
rank={0,1,} iter={23} input={-10.5515,} output={5.05583,} objective={5.05583}
rank={0,1,} iter={24} input={-12.1839,} output={3.90135,} objective={3.90135}
final_iter={25} inputs={-10.7082,} output={5.05583,}
rank={0,1,} iter={0} input={-10.7082,} output={-0.00445465,} objective={-0.00445465}
rank={0,1,} iter={1} input={-12.3658,} output={-4.81584e-06,} objective={-4.81584e-06}
rank={0,1,} iter={2} input={-9.07594,} output={-0.0337947,} objective={-0.0337947}
rank={0,1,} iter={3} input={-12.5005,} output={-2.88951e-06,} objective={-2.88951e-06}
rank={0,1,} iter={4} input={-8.0252,} output={-0.0479128,} objective={-0.0479128}
rank={0,1,} iter={5} input={-13.3963,} output={5.06888,} objective={5.06888}
rank={0,1,} iter={6} input={-13.8155,} output={4.64574,} objective={4.64574}
rank={0,1,} iter={7} input={-13.5687,} output={4.8533,} objective={4.8533}
rank={0,1,} iter={8} input={-12.9068,} output={5.76445,} objective={5.76445}
rank={0,1,} iter={9} input={-11.5371,} output={-0.000393936,} objective={-0.000393936}
rank={0,1,} iter={10} input={-13.1108,} output={5.44979,} objective={5.44979}
rank={0,1,} iter={11} input={-9.89257,} output={-0.013546,} objective={-0.013546}
rank={0,1,} iter={12} input={-12.9789,} output={5.65946,} objective={5.65946}
rank={0,1,} iter={13} input={-8.55116,} output={-0.049634,} objective={-0.049634}
rank={0,1,} iter={14} input={-12.7844,} output={5.96214,} objective={5.96214}
rank={0,1,} iter={15} input={-7.60372,} output={-0.0961473,} objective={-0.0961473}
rank={0,1,} iter={16} input={-12.5397,} output={6.4103,} objective={6.4103}
rank={0,1,} iter={17} input={-11.1223,} output={-0.00267665,} objective={-0.00267665}
rank={0,1,} iter={18} input={-13.0292,} output={5.56314,} objective={5.56314}
rank={0,1,} iter={19} input={-11.9514,} output={-9.1501e-05,} objective={-9.1501e-05}
rank={0,1,} iter={20} input={-12.7844,} output={5.96214,} objective={5.96214}
rank={0,1,} iter={21} input={-9.48417,} output={-0.0260701,} objective={-0.0260701}
rank={0,1,} iter={22} input={-12.6621,} output={6.21454,} objective={6.21454}
rank={0,1,} iter={23} input={-10.3005,} output={-0.00687895,} objective={-0.00687895}
rank={0,1,} iter={24} input={-12.6001,} output={6.30746,} objective={6.30746}
final_iter={25} inputs={-12.5397,} output={6.4103,}
rank={0,1,} iter={0} input={-10.7082,} output={-3.17846e-05,} objective={-3.17846e-05}
rank={0,1,} iter={1} input={-12.3658,} output={6.663,} objective={6.663}
rank={0,1,} iter={2} input={-9.07594,} output={-0.000872631,} objective={-0.000872631}
rank={0,1,} iter={3} input={-13.8155,} output={4.77268,} objective={4.77268}
rank={0,1,} iter={4} input={-12.8557,} output={5.89156,} objective={5.89156}
rank={0,1,} iter={5} input={-10.8031,} output={-2.88951e-06,} objective={-2.88951e-06}
rank={0,1,} iter={6} input={-13.2039,} output={5.43658,} objective={5.43658}
rank={0,1,} iter={7} input={-11.5845,} output={8.35887,} objective={8.35887}
rank={0,1,} iter={8} input={-7.60101,} output={-0.0104311,} objective={-0.0104311}
rank={0,1,} iter={9} input={-11.8434,} output={7.71863,} objective={7.71863}
rank={0,1,} iter={10} input={-12.0549,} output={7.25465,} objective={7.25465}
rank={0,1,} iter={11} input={-10.8031,} output={-2.88951e-06,} objective={-2.88951e-06}
rank={0,1,} iter={12} input={-11.6826,} output={8.10321,} objective={8.10321}
rank={0,1,} iter={13} input={-11.1938,} output={9.51676,} objective={9.51676}
rank={0,1,} iter={14} input={-9.89257,} output={-0.000140623,} objective={-0.000140623}
rank={0,1,} iter={15} input={-11.3468,} output={9.03206,} objective={9.03206}
rank={0,1,} iter={16} input={-8.33743,} output={-0.00515199,} objective={-0.00515199}
rank={0,1,} iter={17} input={-11.239,} output={9.37463,} objective={9.37463}
rank={0,1,} iter={18} input={-13.4957,} output={5.10055,} objective={5.10055}
rank={0,1,} iter={19} input={-10.9985,} output={-1.63739e-05,} objective={-1.63739e-05}
rank={0,1,} iter={20} input={-10.3006,} output={-2.50424e-05,} objective={-2.50424e-05}
rank={0,1,} iter={21} input={-11.0961,} output={-5.77901e-06,} objective={-5.77901e-06}
rank={0,1,} iter={22} input={-9.48501,} output={-0.000330367,} objective={-0.000330367}
rank={0,1,} iter={23} input={-11.2142,} output={9.44911,} objective={9.44911}
rank={0,1,} iter={24} input={-8.7074,} output={-0.00149676,} objective={-0.00149676}
final_iter={25} inputs={-11.1938,} output={9.51676,}

Visual comparison of the error distributions for the logarithm diagnostic¶

Copied!
fig = earthkit.plots.Figure(
    size=(10, 23),
    rows=6,
    columns=2,
)

plot_specific_humidity_log10(
    ERA5_Q,
    1.0,
    fig.add_map(0, 0),
    "Original",
    span=(-5.5, -1.5),
    qlog10_eb_abs=qlog10_eb_abs,
)
plot_specific_humidity_log10(
    ERA5_Q_zfp,
    ERA5_Q_zfp_cr,
    fig.add_map(1, 0),
    r"ZFP($\epsilon_{{abs}}$)",
    span=0.05,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    my_ERA5_Q_ratio=ERA5_Q_zfp_ratio,
    cr_ratio=ERA5_Q_zfp_ratio_cr,
)
plot_specific_humidity_log10(
    ERA5_Q_sz3,
    ERA5_Q_sz3_cr,
    fig.add_map(2, 0),
    r"SZ3($\epsilon_{{abs}}$)",
    span=0.5,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    my_ERA5_Q_ratio=ERA5_Q_sz3_ratio,
    cr_ratio=ERA5_Q_sz3_ratio_cr,
)
plot_specific_humidity_log10(
    ERA5_Q_sperr,
    ERA5_Q_sperr_cr,
    fig.add_map(3, 0),
    r"SPERR($\epsilon_{{abs}}$)",
    span=0.25,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    my_ERA5_Q_ratio=ERA5_Q_sperr_ratio,
    cr_ratio=ERA5_Q_sperr_ratio_cr,
)

plot_specific_humidity_log10(
    ERA5_Q_sg["zero"],
    ERA5_Q_sg_cr["zero"],
    fig.add_map(0, 1),
    r"Safeguarded(0, $\epsilon_{{QoI,abs}}$)",
    span=qlog10_eb_abs,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    corr=ERA5_Q_zero,
    my_ERA5_Q_ratio=ERA5_Q_sg_ratio,
    cr_ratio=ERA5_Q_sg_ratio_cr,
    corr_ratio=ERA5_Q_zero,
)
plot_specific_humidity_log10(
    ERA5_Q_sg["zfp.rs"],
    ERA5_Q_sg_cr["zfp.rs"],
    fig.add_map(1, 1),
    r"Safeguarded(ZFP, $\epsilon_{{QoI,abs}}$)",
    span=qlog10_eb_abs,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    corr=ERA5_Q_zfp,
    my_ERA5_Q_ratio=ERA5_Q_ratio_sg["zfp.rs"],
    cr_ratio=ERA5_Q_ratio_sg_cr["zfp.rs"],
    corr_ratio=ERA5_Q_zfp_ratio,
)
plot_specific_humidity_log10(
    ERA5_Q_sg["sz3.rs"],
    ERA5_Q_sg_cr["sz3.rs"],
    fig.add_map(2, 1),
    r"Safeguarded(SZ3, $\epsilon_{{QoI,abs}}$)",
    span=qlog10_eb_abs,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    corr=ERA5_Q_sz3,
    my_ERA5_Q_ratio=ERA5_Q_ratio_sg["sz3.rs"],
    cr_ratio=ERA5_Q_ratio_sg_cr["sz3.rs"],
    corr_ratio=ERA5_Q_sz3_ratio,
)
plot_specific_humidity_log10(
    ERA5_Q_sg["sperr.rs"],
    ERA5_Q_sg_cr["sperr.rs"],
    fig.add_map(3, 1),
    r"Safeguarded(SPERR, $\epsilon_{{QoI,abs}}$)",
    span=qlog10_eb_abs,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    corr=ERA5_Q_sperr,
    my_ERA5_Q_ratio=ERA5_Q_ratio_sg["sperr.rs"],
    cr_ratio=ERA5_Q_ratio_sg_cr["sperr.rs"],
    corr_ratio=ERA5_Q_sperr_ratio,
)

plot_specific_humidity_log10(
    ERA5_Q_optzconfig["zfp.rs"],
    ERA5_Q_optzconfig_cr["zfp.rs"],
    fig.add_map(4, 0),
    r"OptZConfig(ZFP, $\epsilon_{{QoI,abs}}$)",
    span=qlog10_eb_abs,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    inset=False,
)
plot_specific_humidity_log10(
    ERA5_Q_optzconfig["sz3.rs"],
    ERA5_Q_optzconfig_cr["sz3.rs"],
    fig.add_map(4, 1),
    r"OptZConfig(SZ3, $\epsilon_{{QoI,abs}}$)",
    span=qlog10_eb_abs,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    inset=False,
)
plot_specific_humidity_log10(
    ERA5_Q_optzconfig["sperr.rs"],
    ERA5_Q_optzconfig_cr["sperr.rs"],
    fig.add_map(5, 0),
    r"OptZConfig(SPERR, $\epsilon_{{QoI,abs}}$)",
    span=qlog10_eb_abs,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    inset=False,
)

plot_specific_humidity_log10(
    ERA5_Q_qpet,
    ERA5_Q_qpet_cr,
    fig.add_map(5, 1),
    r"QPET-SPERR($\epsilon_{{QoI,abs}}$)",
    span=qlog10_eb_abs,
    qlog10_eb_abs=qlog10_eb_abs,
    error=True,
    inset=False,
)

fig.save(Path("plots") / "specific-humidity-log10.pdf")
fig = earthkit.plots.Figure( size=(10, 23), rows=6, columns=2, ) plot_specific_humidity_log10( ERA5_Q, 1.0, fig.add_map(0, 0), "Original", span=(-5.5, -1.5), qlog10_eb_abs=qlog10_eb_abs, ) plot_specific_humidity_log10( ERA5_Q_zfp, ERA5_Q_zfp_cr, fig.add_map(1, 0), r"ZFP($\epsilon_{{abs}}$)", span=0.05, qlog10_eb_abs=qlog10_eb_abs, error=True, my_ERA5_Q_ratio=ERA5_Q_zfp_ratio, cr_ratio=ERA5_Q_zfp_ratio_cr, ) plot_specific_humidity_log10( ERA5_Q_sz3, ERA5_Q_sz3_cr, fig.add_map(2, 0), r"SZ3($\epsilon_{{abs}}$)", span=0.5, qlog10_eb_abs=qlog10_eb_abs, error=True, my_ERA5_Q_ratio=ERA5_Q_sz3_ratio, cr_ratio=ERA5_Q_sz3_ratio_cr, ) plot_specific_humidity_log10( ERA5_Q_sperr, ERA5_Q_sperr_cr, fig.add_map(3, 0), r"SPERR($\epsilon_{{abs}}$)", span=0.25, qlog10_eb_abs=qlog10_eb_abs, error=True, my_ERA5_Q_ratio=ERA5_Q_sperr_ratio, cr_ratio=ERA5_Q_sperr_ratio_cr, ) plot_specific_humidity_log10( ERA5_Q_sg["zero"], ERA5_Q_sg_cr["zero"], fig.add_map(0, 1), r"Safeguarded(0, $\epsilon_{{QoI,abs}}$)", span=qlog10_eb_abs, qlog10_eb_abs=qlog10_eb_abs, error=True, corr=ERA5_Q_zero, my_ERA5_Q_ratio=ERA5_Q_sg_ratio, cr_ratio=ERA5_Q_sg_ratio_cr, corr_ratio=ERA5_Q_zero, ) plot_specific_humidity_log10( ERA5_Q_sg["zfp.rs"], ERA5_Q_sg_cr["zfp.rs"], fig.add_map(1, 1), r"Safeguarded(ZFP, $\epsilon_{{QoI,abs}}$)", span=qlog10_eb_abs, qlog10_eb_abs=qlog10_eb_abs, error=True, corr=ERA5_Q_zfp, my_ERA5_Q_ratio=ERA5_Q_ratio_sg["zfp.rs"], cr_ratio=ERA5_Q_ratio_sg_cr["zfp.rs"], corr_ratio=ERA5_Q_zfp_ratio, ) plot_specific_humidity_log10( ERA5_Q_sg["sz3.rs"], ERA5_Q_sg_cr["sz3.rs"], fig.add_map(2, 1), r"Safeguarded(SZ3, $\epsilon_{{QoI,abs}}$)", span=qlog10_eb_abs, qlog10_eb_abs=qlog10_eb_abs, error=True, corr=ERA5_Q_sz3, my_ERA5_Q_ratio=ERA5_Q_ratio_sg["sz3.rs"], cr_ratio=ERA5_Q_ratio_sg_cr["sz3.rs"], corr_ratio=ERA5_Q_sz3_ratio, ) plot_specific_humidity_log10( ERA5_Q_sg["sperr.rs"], ERA5_Q_sg_cr["sperr.rs"], fig.add_map(3, 1), r"Safeguarded(SPERR, $\epsilon_{{QoI,abs}}$)", span=qlog10_eb_abs, qlog10_eb_abs=qlog10_eb_abs, error=True, corr=ERA5_Q_sperr, my_ERA5_Q_ratio=ERA5_Q_ratio_sg["sperr.rs"], cr_ratio=ERA5_Q_ratio_sg_cr["sperr.rs"], corr_ratio=ERA5_Q_sperr_ratio, ) plot_specific_humidity_log10( ERA5_Q_optzconfig["zfp.rs"], ERA5_Q_optzconfig_cr["zfp.rs"], fig.add_map(4, 0), r"OptZConfig(ZFP, $\epsilon_{{QoI,abs}}$)", span=qlog10_eb_abs, qlog10_eb_abs=qlog10_eb_abs, error=True, inset=False, ) plot_specific_humidity_log10( ERA5_Q_optzconfig["sz3.rs"], ERA5_Q_optzconfig_cr["sz3.rs"], fig.add_map(4, 1), r"OptZConfig(SZ3, $\epsilon_{{QoI,abs}}$)", span=qlog10_eb_abs, qlog10_eb_abs=qlog10_eb_abs, error=True, inset=False, ) plot_specific_humidity_log10( ERA5_Q_optzconfig["sperr.rs"], ERA5_Q_optzconfig_cr["sperr.rs"], fig.add_map(5, 0), r"OptZConfig(SPERR, $\epsilon_{{QoI,abs}}$)", span=qlog10_eb_abs, qlog10_eb_abs=qlog10_eb_abs, error=True, inset=False, ) plot_specific_humidity_log10( ERA5_Q_qpet, ERA5_Q_qpet_cr, fig.add_map(5, 1), r"QPET-SPERR($\epsilon_{{QoI,abs}}$)", span=qlog10_eb_abs, qlog10_eb_abs=qlog10_eb_abs, error=True, inset=False, ) fig.save(Path("plots") / "specific-humidity-log10.pdf")
No description has been provided for this image
Copied!
log10q_table = pd.concat(
    [
        table_specific_humidity_log10(
            ERA5_Q_sg_lossless["zero"],
            ERA5_Q_sg_lossless_cr["zero"],
            ["0", "", r"$\epsilon_{QoI,abs}$", "lossless"],
            qlog10_eb_abs,
            ERA5_Q_zero,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sg["zero"],
            ERA5_Q_sg_cr["zero"],
            ["0", "", r"$\epsilon_{QoI,abs}$", "one-shot"],
            qlog10_eb_abs,
            ERA5_Q_zero,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sg_ratio_lossless,
            ERA5_Q_sg_ratio_lossless_cr,
            ["0", "", r"$\epsilon_{ratio}$", "lossless"],
            qlog10_eb_abs,
            ERA5_Q_zero,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sg_ratio,
            ERA5_Q_sg_ratio_cr,
            ["0", "", r"$\epsilon_{ratio}$", "one-shot"],
            qlog10_eb_abs,
            ERA5_Q_zero,
        ),
        table_specific_humidity_log10(
            ERA5_Q_zfp,
            ERA5_Q_zfp_cr,
            ["ZFP", r"$\epsilon_{abs}$", "-", ""],
            qlog10_eb_abs,
            None,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sg_lossless["zfp.rs"],
            ERA5_Q_sg_lossless_cr["zfp.rs"],
            ["ZFP", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "lossless"],
            qlog10_eb_abs,
            ERA5_Q_zfp,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sg["zfp.rs"],
            ERA5_Q_sg_cr["zfp.rs"],
            ["ZFP", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "one-shot"],
            qlog10_eb_abs,
            ERA5_Q_zfp,
        ),
        table_specific_humidity_log10(
            ERA5_Q_zfp_ratio,
            ERA5_Q_zfp_ratio_cr,
            ["ZFP", r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$", "-", ""],
            qlog10_eb_abs,
            None,
        ),
        table_specific_humidity_log10(
            ERA5_Q_ratio_sg["zfp.rs"],
            ERA5_Q_ratio_sg_cr["zfp.rs"],
            [
                "ZFP",
                r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$",
                r"$\epsilon_{QoI,abs}$",
                "one-shot",
            ],
            qlog10_eb_abs,
            ERA5_Q_zfp_ratio,
        ),
        table_specific_humidity_log10(
            ERA5_Q_optzconfig["zfp.rs"],
            ERA5_Q_optzconfig_cr["zfp.rs"],
            [
                "OptZConfig(ZFP)",
                r"$\epsilon_{abs}$",
                r"$\epsilon_{QoI,abs}$",
                "",
            ],
            qlog10_eb_abs,
            None,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sz3,
            ERA5_Q_sz3_cr,
            ["SZ3", r"$\epsilon_{abs}$", "-", ""],
            qlog10_eb_abs,
            None,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sg_lossless["sz3.rs"],
            ERA5_Q_sg_lossless_cr["sz3.rs"],
            ["SZ3", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "lossless"],
            qlog10_eb_abs,
            ERA5_Q_sz3,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sg["sz3.rs"],
            ERA5_Q_sg_cr["sz3.rs"],
            ["SZ3", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "one-shot"],
            qlog10_eb_abs,
            ERA5_Q_sz3,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sz3_ratio,
            ERA5_Q_sz3_ratio_cr,
            ["SZ3", r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$", "-", ""],
            qlog10_eb_abs,
            None,
        ),
        table_specific_humidity_log10(
            ERA5_Q_ratio_sg["sz3.rs"],
            ERA5_Q_ratio_sg_cr["sz3.rs"],
            [
                "SZ3",
                r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$",
                r"$\epsilon_{QoI,abs}$",
                "one-shot",
            ],
            qlog10_eb_abs,
            ERA5_Q_sz3_ratio,
        ),
        table_specific_humidity_log10(
            ERA5_Q_optzconfig["sz3.rs"],
            ERA5_Q_optzconfig_cr["sz3.rs"],
            [
                "OptZConfig(SZ3)",
                r"$\epsilon_{abs}$",
                r"$\epsilon_{QoI,abs}$",
                "",
            ],
            qlog10_eb_abs,
            None,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sperr,
            ERA5_Q_sperr_cr,
            ["SPERR", r"$\epsilon_{abs}$", "-", ""],
            qlog10_eb_abs,
            None,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sg_lossless["sperr.rs"],
            ERA5_Q_sg_lossless_cr["sperr.rs"],
            ["SPERR", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "lossless"],
            qlog10_eb_abs,
            ERA5_Q_sperr,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sg["sperr.rs"],
            ERA5_Q_sg_cr["sperr.rs"],
            ["SPERR", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "one-shot"],
            qlog10_eb_abs,
            ERA5_Q_sperr,
        ),
        table_specific_humidity_log10(
            ERA5_Q_sperr_ratio,
            ERA5_Q_sperr_ratio_cr,
            ["SPERR", r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$", "-", ""],
            qlog10_eb_abs,
            None,
        ),
        table_specific_humidity_log10(
            ERA5_Q_ratio_sg["sperr.rs"],
            ERA5_Q_ratio_sg_cr["sperr.rs"],
            [
                "SPERR",
                r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$",
                r"$\epsilon_{QoI,abs}$",
                "one-shot",
            ],
            qlog10_eb_abs,
            ERA5_Q_sperr_ratio,
        ),
        table_specific_humidity_log10(
            ERA5_Q_optzconfig["sperr.rs"],
            ERA5_Q_optzconfig_cr["sperr.rs"],
            [
                "OptZConfig(SPERR)",
                r"$\epsilon_{abs}$",
                r"$\epsilon_{QoI,abs}$",
                "",
            ],
            qlog10_eb_abs,
            None,
        ),
        table_specific_humidity_log10(
            ERA5_Q_qpet,
            ERA5_Q_qpet_cr,
            ["QPET-SPERR", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", ""],
            qlog10_eb_abs,
            None,
        ),
        table_specific_humidity_log10(
            ERA5_Q_zstd,
            ERA5_Q_zstd_cr,
            ["ZSTD(22)", "", "-", ""],
            qlog10_eb_abs,
            None,
        ),
    ]
).set_index(["Compressor", "(Bound)", "Safeguarded", "Corrections"])

Path("tables").joinpath("specific-humidity-log10.tex").write_text(
    log10q_table.to_latex(escape=False)
    .replace("%", r"\%")
    .replace("\\cline{1-10} \\cline{2-10} \\cline{3-10}\n\\bottomrule", "\\bottomrule")
)

log10q_table
log10q_table = pd.concat( [ table_specific_humidity_log10( ERA5_Q_sg_lossless["zero"], ERA5_Q_sg_lossless_cr["zero"], ["0", "", r"$\epsilon_{QoI,abs}$", "lossless"], qlog10_eb_abs, ERA5_Q_zero, ), table_specific_humidity_log10( ERA5_Q_sg["zero"], ERA5_Q_sg_cr["zero"], ["0", "", r"$\epsilon_{QoI,abs}$", "one-shot"], qlog10_eb_abs, ERA5_Q_zero, ), table_specific_humidity_log10( ERA5_Q_sg_ratio_lossless, ERA5_Q_sg_ratio_lossless_cr, ["0", "", r"$\epsilon_{ratio}$", "lossless"], qlog10_eb_abs, ERA5_Q_zero, ), table_specific_humidity_log10( ERA5_Q_sg_ratio, ERA5_Q_sg_ratio_cr, ["0", "", r"$\epsilon_{ratio}$", "one-shot"], qlog10_eb_abs, ERA5_Q_zero, ), table_specific_humidity_log10( ERA5_Q_zfp, ERA5_Q_zfp_cr, ["ZFP", r"$\epsilon_{abs}$", "-", ""], qlog10_eb_abs, None, ), table_specific_humidity_log10( ERA5_Q_sg_lossless["zfp.rs"], ERA5_Q_sg_lossless_cr["zfp.rs"], ["ZFP", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "lossless"], qlog10_eb_abs, ERA5_Q_zfp, ), table_specific_humidity_log10( ERA5_Q_sg["zfp.rs"], ERA5_Q_sg_cr["zfp.rs"], ["ZFP", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "one-shot"], qlog10_eb_abs, ERA5_Q_zfp, ), table_specific_humidity_log10( ERA5_Q_zfp_ratio, ERA5_Q_zfp_ratio_cr, ["ZFP", r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$", "-", ""], qlog10_eb_abs, None, ), table_specific_humidity_log10( ERA5_Q_ratio_sg["zfp.rs"], ERA5_Q_ratio_sg_cr["zfp.rs"], [ "ZFP", r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "one-shot", ], qlog10_eb_abs, ERA5_Q_zfp_ratio, ), table_specific_humidity_log10( ERA5_Q_optzconfig["zfp.rs"], ERA5_Q_optzconfig_cr["zfp.rs"], [ "OptZConfig(ZFP)", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "", ], qlog10_eb_abs, None, ), table_specific_humidity_log10( ERA5_Q_sz3, ERA5_Q_sz3_cr, ["SZ3", r"$\epsilon_{abs}$", "-", ""], qlog10_eb_abs, None, ), table_specific_humidity_log10( ERA5_Q_sg_lossless["sz3.rs"], ERA5_Q_sg_lossless_cr["sz3.rs"], ["SZ3", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "lossless"], qlog10_eb_abs, ERA5_Q_sz3, ), table_specific_humidity_log10( ERA5_Q_sg["sz3.rs"], ERA5_Q_sg_cr["sz3.rs"], ["SZ3", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "one-shot"], qlog10_eb_abs, ERA5_Q_sz3, ), table_specific_humidity_log10( ERA5_Q_sz3_ratio, ERA5_Q_sz3_ratio_cr, ["SZ3", r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$", "-", ""], qlog10_eb_abs, None, ), table_specific_humidity_log10( ERA5_Q_ratio_sg["sz3.rs"], ERA5_Q_ratio_sg_cr["sz3.rs"], [ "SZ3", r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "one-shot", ], qlog10_eb_abs, ERA5_Q_sz3_ratio, ), table_specific_humidity_log10( ERA5_Q_optzconfig["sz3.rs"], ERA5_Q_optzconfig_cr["sz3.rs"], [ "OptZConfig(SZ3)", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "", ], qlog10_eb_abs, None, ), table_specific_humidity_log10( ERA5_Q_sperr, ERA5_Q_sperr_cr, ["SPERR", r"$\epsilon_{abs}$", "-", ""], qlog10_eb_abs, None, ), table_specific_humidity_log10( ERA5_Q_sg_lossless["sperr.rs"], ERA5_Q_sg_lossless_cr["sperr.rs"], ["SPERR", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "lossless"], qlog10_eb_abs, ERA5_Q_sperr, ), table_specific_humidity_log10( ERA5_Q_sg["sperr.rs"], ERA5_Q_sg_cr["sperr.rs"], ["SPERR", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "one-shot"], qlog10_eb_abs, ERA5_Q_sperr, ), table_specific_humidity_log10( ERA5_Q_sperr_ratio, ERA5_Q_sperr_ratio_cr, ["SPERR", r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$", "-", ""], qlog10_eb_abs, None, ), table_specific_humidity_log10( ERA5_Q_ratio_sg["sperr.rs"], ERA5_Q_ratio_sg_cr["sperr.rs"], [ "SPERR", r"$\epsilon_{ratio} \rightarrow \epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "one-shot", ], qlog10_eb_abs, ERA5_Q_sperr_ratio, ), table_specific_humidity_log10( ERA5_Q_optzconfig["sperr.rs"], ERA5_Q_optzconfig_cr["sperr.rs"], [ "OptZConfig(SPERR)", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", "", ], qlog10_eb_abs, None, ), table_specific_humidity_log10( ERA5_Q_qpet, ERA5_Q_qpet_cr, ["QPET-SPERR", r"$\epsilon_{abs}$", r"$\epsilon_{QoI,abs}$", ""], qlog10_eb_abs, None, ), table_specific_humidity_log10( ERA5_Q_zstd, ERA5_Q_zstd_cr, ["ZSTD(22)", "", "-", ""], qlog10_eb_abs, None, ), ] ).set_index(["Compressor", "(Bound)", "Safeguarded", "Corrections"]) Path("tables").joinpath("specific-humidity-log10.tex").write_text( log10q_table.to_latex(escape=False) .replace("%", r"\%") .replace("\\cline{1-10} \\cline{2-10} \\cline{3-10}\n\\bottomrule", "\\bottomrule") ) log10q_table
$L_{\infty}(\hat{q})$ $L_{\infty}(\log_{10}(\hat{q}))$ $L_{2}(\log_{10}(\hat{q}))$ V C CR
Compressor (Bound) Safeguarded Corrections
0 $\epsilon_{QoI,abs}$ lossless 0.0 0.0 0.0 0 100.0% $\times$ 2.92
one-shot 0.013 0.25 0.136 0 100.0% $\times$ 89.52
$\epsilon_{ratio}$ lossless 0.0 0.0 0.0 0 100.0% $\times$ 2.92
one-shot 0.013 0.25 0.136 0 100.0% $\times$ 89.52
ZFP $\epsilon_{abs}$ - 0.00029 NaN [1.1] NaN [0.0321] 1.5% $\times$ 13.25
$\epsilon_{QoI,abs}$ lossless 0.00029 0.25 0.0279 0 1.5% $\times$ 12.92
one-shot 0.00029 0.25 0.0328 0 1.5% $\times$ 13.15
$\epsilon_{ratio} \rightarrow \epsilon_{abs}$ - 0.0025 0.0943 0.0133 0 $\times$ 14.74
$\epsilon_{QoI,abs}$ one-shot 0.0025 0.0943 0.0133 0 0 $\times$ 14.74
OptZConfig(ZFP) $\epsilon_{abs}$ $\epsilon_{QoI,abs}$ 9.4e-06 0.137 0.00346 0 $\times$ 5.06
SZ3 $\epsilon_{abs}$ - 0.0005 NaN [4.74] NaN [0.165] 9.6% $\times$ 81.1
$\epsilon_{QoI,abs}$ lossless 0.0005 0.25 0.0601 0 9.6% $\times$ 9.4
one-shot 0.0005 0.25 0.0737 0 9.6% $\times$ 17.03
$\epsilon_{ratio} \rightarrow \epsilon_{abs}$ - 0.01 0.25 0.0684 0 $\times$ 426.43
$\epsilon_{QoI,abs}$ one-shot 0.01 0.25 0.0684 0 0 $\times$ 426.29
OptZConfig(SZ3) $\epsilon_{abs}$ $\epsilon_{QoI,abs}$ 3.6e-06 0.229 0.00606 0 $\times$ 6.41
SPERR $\epsilon_{abs}$ - 0.0005 NaN [4.28] NaN [0.0615] 1.0% $\times$ 73.43
$\epsilon_{QoI,abs}$ lossless 0.0005 0.25 0.0353 0 1.0% $\times$ 36.19
one-shot 0.0005 0.25 0.038 0 1.0% $\times$ 55.95
$\epsilon_{ratio} \rightarrow \epsilon_{abs}$ - 0.006 0.247 0.0313 0 $\times$ 266.88
$\epsilon_{QoI,abs}$ one-shot 0.006 0.247 0.0313 0 0 $\times$ 266.83
OptZConfig(SPERR) $\epsilon_{abs}$ $\epsilon_{QoI,abs}$ 1.4e-05 0.226 0.0049 0 $\times$ 9.52
QPET-SPERR $\epsilon_{abs}$ $\epsilon_{QoI,abs}$ 0.00027 0.25 0.0129 0 $\times$ 24.05
ZSTD(22) - 0.0 0.0 0.0 0 $\times$ 2.23
Copied!
import json

with Path("observations").joinpath("specific-humidity-log10.json").open("w") as f:
    json.dump(observations, f)
import json with Path("observations").joinpath("specific-humidity-log10.json").open("w") as f: json.dump(observations, f)
Copied!

Next

Built with ProperDocs using a theme provided by Read the Docs.