From f1d6905032d846dc9a699063515143018aa65e77 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Thu, 15 Jun 2023 00:38:07 -0700 Subject: [PATCH 01/53] WIP: draft support for float16 + float64 --- xbitinfo/graphics.py | 40 ++++++++++++++++++++++++---------------- xbitinfo/xbitinfo.py | 3 ++- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/xbitinfo/graphics.py b/xbitinfo/graphics.py index 257b7445..c5556e5c 100644 --- a/xbitinfo/graphics.py +++ b/xbitinfo/graphics.py @@ -217,10 +217,15 @@ def plot_bitinformation(bitinfo, cmap="turku"): 1, ), "Only bitinfo along one dimension is supported at the moment. Please select dimension before plotting." - assert ( - "bit32" in bitinfo.dims - ), "currently only works properly for float32 data, looking forward to your PR closing https://github.com/observingClouds/xbitinfo/issues/168" - + # assert ( + # "bit32" in bitinfo.dims + # ), "currently only works properly for float32 data, looking forward to your PR closing https://github.com/observingClouds/xbitinfo/issues/168" + if "bit32" in bitinfo.dims: + bits = 32 + elif "bit16" in bitinfo.dims: + bits = 16 + elif "bit64" in bitinfo.dims: + bits = 64 nvars = len(bitinfo) varnames = bitinfo.keys() @@ -247,14 +252,14 @@ def plot_bitinformation(bitinfo, cmap="turku"): fig_height = np.max([4, 4 + (nvars - 10) * 0.2]) # auto adjust to nvars fig, ax1 = plt.subplots(1, 1, figsize=(12, fig_height), sharey=True) ax1.invert_yaxis() - ax1.set_box_aspect(1 / 32 * nvars) + ax1.set_box_aspect(1 / bits * nvars) plt.tight_layout(rect=[0.06, 0.18, 0.8, 0.98]) pos = ax1.get_position() cax = fig.add_axes([pos.x0, 0.12, pos.x1 - pos.x0, 0.02]) ax1right = ax1.twinx() ax1right.invert_yaxis() - ax1right.set_box_aspect(1 / 32 * nvars) + ax1right.set_box_aspect(1 / bits * nvars) if cmap == "turku": import cmcrameri.cm as cmc @@ -276,15 +281,15 @@ def plot_bitinformation(bitinfo, cmap="turku"): # grey shading ax1.fill_betweenx( - infbitsy, infbitsx, np.ones(len(infbitsx)) * 32, alpha=0.4, color="grey" + infbitsy, infbitsx, np.ones(len(infbitsx)) * bits, alpha=0.4, color="grey" ) ax1.fill_betweenx( - infbitsy, infbitsx100, np.ones(len(infbitsx)) * 32, alpha=0.1, color="c" + infbitsy, infbitsx100, np.ones(len(infbitsx)) * bits, alpha=0.1, color="c" ) ax1.fill_betweenx( infbitsy, infbitsx100, - np.ones(len(infbitsx)) * 32, + np.ones(len(infbitsx)) * bits, alpha=0.3, facecolor="none", edgecolor="c", @@ -311,7 +316,7 @@ def plot_bitinformation(bitinfo, cmap="turku"): ax1.fill_betweenx([-1, -1], [-1, -1], [-1, -1], color="w", label="unused bits") ax1.axvline(1, color="k", lw=1, zorder=3) - ax1.axvline(9, color="k", lw=1, zorder=3) + ax1.axvline(NMBITS[bits], color="k", lw=1, zorder=3) fig.suptitle( "Real bitwise information content", @@ -321,7 +326,7 @@ def plot_bitinformation(bitinfo, cmap="turku"): horizontalalignment="left", ) - ax1.set_xlim(0, 32) + ax1.set_xlim(0, bits) ax1.set_ylim(nvars, 0) ax1right.set_ylim(nvars, 0) @@ -334,7 +339,7 @@ def plot_bitinformation(bitinfo, cmap="turku"): ax1.text( infbits[0] + 0.1, 0.8, - f"{int(infbits[0]-9)} mantissa bits", + f"{int(infbits[0]-NMBITS[bits])} mantissa bits", fontsize=8, color="saddlebrown", ) @@ -348,19 +353,22 @@ def plot_bitinformation(bitinfo, cmap="turku"): ) ax1.set_xticks([1, 9]) - ax1.set_xticks(np.hstack([np.arange(1, 8), np.arange(9, 32)]), minor=True) + ax1.set_xticks( + np.hstack([np.arange(1, NMBITS[bits] - 1), np.arange(NMBITS[bits], bits)]), + minor=True, + ) ax1.set_xticklabels([]) ax1.text(0, nvars + 1.2, "sign", rotation=90) ax1.text(2, nvars + 1.2, "exponent bits", color="darkslategrey") ax1.text(10, nvars + 1.2, "mantissa bits") - for i in range(1, 9): + for i in range(1, NMBITS[bits]): ax1.text( i + 0.5, nvars + 0.5, i, ha="center", fontsize=7, color="darkslategrey" ) - for i in range(1, 24): - ax1.text(8 + i + 0.5, nvars + 0.5, i, ha="center", fontsize=7) + for i in range(1, bits - NMBITS[bits] + 1): + ax1.text(NMBITS[bits] - 1 + i + 0.5, nvars + 0.5, i, ha="center", fontsize=7) ax1.legend(bbox_to_anchor=(1.08, 0.5), loc="center left", framealpha=0.6) diff --git a/xbitinfo/xbitinfo.py b/xbitinfo/xbitinfo.py index bff0070f..e53092ff 100644 --- a/xbitinfo/xbitinfo.py +++ b/xbitinfo/xbitinfo.py @@ -293,7 +293,8 @@ def _py_get_bitinformation(ds, var, axis, dim, kwargs={}): itemsize = ds[var].dtype.itemsize astype = f"u{itemsize}" X = da.array(ds[var]) - X = pb.signed_exponent(X) + if X.dtype in (np.float16, np.float32, np.float64): + X = pb.signed_exponent(X) X = X.astype(astype) if axis is not None: dim = ds[var].dims[axis] From 5f75a321b760e71b1704b891f7ba316e4e011ac5 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Fri, 23 Jun 2023 20:29:00 -0700 Subject: [PATCH 02/53] correct coords for f/i/u types --- xbitinfo/xbitinfo.py | 56 +++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/xbitinfo/xbitinfo.py b/xbitinfo/xbitinfo.py index e53092ff..37c5abaf 100644 --- a/xbitinfo/xbitinfo.py +++ b/xbitinfo/xbitinfo.py @@ -36,28 +36,34 @@ NMBITS = {64: 12, 32: 9, 16: 6} # number of non mantissa bits for given dtype -def get_bit_coords(dtype_size): - """Get coordinates for bits assuming float dtypes.""" - if dtype_size == 16: - coords = ( - ["±"] - + [f"e{int(i)}" for i in range(1, 6)] - + [f"m{int(i-5)}" for i in range(6, 16)] - ) - elif dtype_size == 32: - coords = ( - ["±"] - + [f"e{int(i)}" for i in range(1, 9)] - + [f"m{int(i-8)}" for i in range(9, 32)] - ) - elif dtype_size == 64: - coords = ( - ["±"] - + [f"e{int(i)}" for i in range(1, 12)] - + [f"m{int(i-11)}" for i in range(12, 64)] - ) +def get_bit_coords(dtype): + """Get coordinates for bits based on dtype.""" + if dtype.kind == "f": + n_bits = np.finfo(dtype).bits + n_sign = 1 + n_exponent = np.finfo(dtype).nexp + n_mantissa = np.finfo(dtype).nmant + elif dtype.kind == "i": + n_bits = np.iinfo(dtype).bits + n_sign = 1 + n_exponent = 0 + n_mantissa = n_bits - n_sign + elif dtype.kind == "u": + n_bits = np.iinfo(dtype).bits + n_sign = 0 + n_exponent = 0 + n_mantissa = n_bits - n_sign else: - raise ValueError(f"dtype of size {dtype_size} neither known nor implemented.") + raise ValueError(f"dtype {dtype} neither known nor implemented.") + + assert ( + n_sign + n_exponent + n_mantissa == n_bits + ), "The components of the datatype could not be safely inferred." + coords = ( + n_sign * ["±"] + + [f"e{int(i)}" for i in range(1, n_exponent + 1)] + + [f"m{int(i)}" for i in range(1, n_mantissa + 1)] + ) return coords @@ -65,13 +71,13 @@ def dict_to_dataset(info_per_bit): """Convert keepbits dictionary to :py:class:`xarray.Dataset`.""" dsb = xr.Dataset() for v in info_per_bit.keys(): - dtype_size = len(info_per_bit[v]["bitinfo"]) + dtype = info_per_bit[v]["dtype"] dim = info_per_bit[v]["dim"] - dim_name = f"bit{dtype_size}" + dim_name = f"bit{dtype}" dsb[v] = xr.DataArray( info_per_bit[v]["bitinfo"], dims=[dim_name], - coords={dim_name: get_bit_coords(dtype_size), "dim": dim}, + coords={dim_name: get_bit_coords(dtype), "dim": dim}, name=v, attrs={ "long_name": f"{v} bitwise information", @@ -277,6 +283,7 @@ def _jl_get_bitinformation(ds, var, axis, dim, kwargs={}): ) info_per_bit["dim"] = dim info_per_bit["axis"] = axis_jl - 1 + info_per_bit["dtype"] = ds[var].dtype return info_per_bit @@ -309,6 +316,7 @@ def _py_get_bitinformation(ds, var, axis, dim, kwargs={}): info_per_bit["bitinfo"] = pb.bitinformation(X, axis=axis).compute() info_per_bit["dim"] = dim info_per_bit["axis"] = axis + info_per_bit["dtype"] = ds[var].dtype return info_per_bit From 40c4d51c0d3fecd4cdb17ffc4e80664d6bbbe99b Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Fri, 23 Jun 2023 22:19:37 -0700 Subject: [PATCH 03/53] add bit_partitioning func --- xbitinfo/xbitinfo.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/xbitinfo/xbitinfo.py b/xbitinfo/xbitinfo.py index 37c5abaf..23b0ae84 100644 --- a/xbitinfo/xbitinfo.py +++ b/xbitinfo/xbitinfo.py @@ -33,11 +33,7 @@ jl.eval("include(Main.path)") -NMBITS = {64: 12, 32: 9, 16: 6} # number of non mantissa bits for given dtype - - -def get_bit_coords(dtype): - """Get coordinates for bits based on dtype.""" +def bit_partitioning(dtype): if dtype.kind == "f": n_bits = np.finfo(dtype).bits n_sign = 1 @@ -55,10 +51,15 @@ def get_bit_coords(dtype): n_mantissa = n_bits - n_sign else: raise ValueError(f"dtype {dtype} neither known nor implemented.") - assert ( n_sign + n_exponent + n_mantissa == n_bits ), "The components of the datatype could not be safely inferred." + return n_bits, n_sign, n_exponent, n_mantissa + + +def get_bit_coords(dtype): + """Get coordinates for bits based on dtype.""" + n_bits, n_sign, n_exponent, n_mantissa = bit_partitioning(dtype) coords = ( n_sign * ["±"] + [f"e{int(i)}" for i in range(1, n_exponent + 1)] @@ -445,7 +446,10 @@ def get_keepbits(info_per_bit, inflevel=0.99): bit_vars = [v for v in info_per_bit.data_vars if bitdim in info_per_bit[v].dims] if bit_vars != []: cdf = _cdf_from_info_per_bit(info_per_bit[bit_vars], bitdim) - bitdim_non_mantissa_bits = NMBITS[int(bitdim[3:])] + data_type = np.dtype(bitdim.replace("bit", "")) + n_bits, _, _, n_mant = bit_partitioning(data_type) + bitdim_non_mantissa_bits = n_bits - n_mant + keepmantissabits_bitdim = ( (cdf > inflevel).argmax(bitdim) + 1 - bitdim_non_mantissa_bits ) From 8f4c644491df55e6c23269db24df8783db48d66b Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Fri, 23 Jun 2023 22:20:14 -0700 Subject: [PATCH 04/53] allow further datatypes --- xbitinfo/xbitinfo.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/xbitinfo/xbitinfo.py b/xbitinfo/xbitinfo.py index 23b0ae84..460ea03c 100644 --- a/xbitinfo/xbitinfo.py +++ b/xbitinfo/xbitinfo.py @@ -441,7 +441,17 @@ def get_keepbits(info_per_bit, inflevel=0.99): inflevel = xr.DataArray(inflevel, dims="inflevel", coords={"inflevel": inflevel}) if (inflevel < 0).any() or (inflevel > 1.0).any(): raise ValueError("Please provide `inflevel` from interval [0.,1.]") - for bitdim in ["bit16", "bit32", "bit64"]: + for bitdim in [ + "bitfloat16", + "bitfloat32", + "bitfloat64", + "bitint16", + "bitint32", + "bitint64", + "bituint16", + "bituint32", + "bituint64", + ]: # get only variables of bitdim bit_vars = [v for v in info_per_bit.data_vars if bitdim in info_per_bit[v].dims] if bit_vars != []: From 8ba0727566eb448926b04a7e24beba5895713bae Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Fri, 23 Jun 2023 22:22:06 -0700 Subject: [PATCH 05/53] create subplots per data type --- xbitinfo/graphics.py | 344 ++++++++++++++++++++++++------------------- 1 file changed, 195 insertions(+), 149 deletions(-) diff --git a/xbitinfo/graphics.py b/xbitinfo/graphics.py index c5556e5c..9afe1013 100644 --- a/xbitinfo/graphics.py +++ b/xbitinfo/graphics.py @@ -2,7 +2,7 @@ import numpy as np import xarray as xr -from .xbitinfo import NMBITS, _cdf_from_info_per_bit, get_keepbits +from .xbitinfo import _cdf_from_info_per_bit, bit_partitioning, get_keepbits def add_bitinfo_labels( @@ -185,6 +185,28 @@ def add_bitinfo_labels( t_keepbits.set_bbox(dict(facecolor="white", alpha=0.9, edgecolor="white")) +def split_dataset_by_dims(info_per_bit): + """Split dataset by its dimensions. + + Parameters + ---------- + info_per_bit : dict + Information content of each bit for each variable in ``da``. This is the output from :py:func:`xbitinfo.xbitinfo.get_bitinformation`. + + Returns + ------- + var_by_dim : dict + Dictionary containing the dimensions of the datasets as keys and the dataset using the dimension as value. + """ + var_by_dim = {d: [] for d in info_per_bit.dims} + for var in info_per_bit.data_vars: + assert ( + len(info_per_bit[var].dims) == 1 + ), f"Variable {var} has more than one dimension." + var_by_dim[info_per_bit[var].dims[0]].append(var) + return var_by_dim + + def plot_bitinformation(bitinfo, cmap="turku"): """Plot bitwise information content as in Klöwer et al. 2021 Figure 2. @@ -213,166 +235,190 @@ def plot_bitinformation(bitinfo, cmap="turku"): """ import matplotlib.pyplot as plt - assert bitinfo.coords["dim"].shape <= ( - 1, - ), "Only bitinfo along one dimension is supported at the moment. Please select dimension before plotting." - - # assert ( - # "bit32" in bitinfo.dims - # ), "currently only works properly for float32 data, looking forward to your PR closing https://github.com/observingClouds/xbitinfo/issues/168" - if "bit32" in bitinfo.dims: - bits = 32 - elif "bit16" in bitinfo.dims: - bits = 16 - elif "bit64" in bitinfo.dims: - bits = 64 - nvars = len(bitinfo) - varnames = bitinfo.keys() - - infbits_dict = get_keepbits(bitinfo, 0.99) - infbits100_dict = get_keepbits(bitinfo, 0.999999999) - - ICnan = np.zeros((nvars, 64)) - infbits = np.zeros(nvars) - infbits100 = np.zeros(nvars) - ICnan[:, :] = np.nan - for v, var in enumerate(varnames): - ic = bitinfo[var].squeeze(drop=True) - ICnan[v, : len(ic)] = ic - # infbits are all bits, infbits_dict were mantissa bits - infbits[v] = infbits_dict[var] + NMBITS[len(ic)] - infbits100[v] = infbits100_dict[var] + NMBITS[len(ic)] - ICnan = np.where(ICnan == 0, np.nan, ICnan) - ICcsum = np.nancumsum(ICnan, axis=1) - - infbitsy = np.hstack([0, np.repeat(np.arange(1, nvars), 2), nvars]) - infbitsx = np.repeat(infbits, 2) - infbitsx100 = np.repeat(infbits100, 2) - - fig_height = np.max([4, 4 + (nvars - 10) * 0.2]) # auto adjust to nvars - fig, ax1 = plt.subplots(1, 1, figsize=(12, fig_height), sharey=True) - ax1.invert_yaxis() - ax1.set_box_aspect(1 / bits * nvars) - plt.tight_layout(rect=[0.06, 0.18, 0.8, 0.98]) - pos = ax1.get_position() - cax = fig.add_axes([pos.x0, 0.12, pos.x1 - pos.x0, 0.02]) - - ax1right = ax1.twinx() - ax1right.invert_yaxis() - ax1right.set_box_aspect(1 / bits * nvars) - - if cmap == "turku": - import cmcrameri.cm as cmc - - cmap = cmc.turku_r - pcm = ax1.pcolormesh(ICnan, vmin=0, vmax=1, cmap=cmap) - cbar = plt.colorbar(pcm, cax=cax, orientation="horizontal") - cbar.set_label("information content [bit]") - - # 99% of real information enclosed - ax1.plot( - np.hstack([infbits, infbits[-1]]), - np.arange(nvars + 1), - "C1", - ds="steps-pre", - zorder=10, - label="99% of\ninformation", - ) + vars_by_dim = split_dataset_by_dims(bitinfo) + bitinfo_all = bitinfo + for dim, vars in vars_by_dim.items(): + bitinfo = bitinfo_all[vars] + data_type = np.dtype(dim.replace("bit", "")) + n_bits, n_sign, n_exp, n_mant = bit_partitioning(data_type) + nonmantissa_bits = n_bits - n_mant + nvars = len(bitinfo) + varnames = bitinfo.keys() + + infbits_dict = get_keepbits(bitinfo, 0.99) + infbits100_dict = get_keepbits(bitinfo, 0.999999999) + + ICnan = np.zeros((nvars, 64)) + infbits = np.zeros(nvars) + infbits100 = np.zeros(nvars) + ICnan[:, :] = np.nan + for v, var in enumerate(varnames): + ic = bitinfo[var].squeeze(drop=True) + ICnan[v, : len(ic)] = ic + # infbits are all bits, infbits_dict were mantissa bits + infbits[v] = infbits_dict[var] + nonmantissa_bits + infbits100[v] = infbits100_dict[var] + nonmantissa_bits + ICnan = np.where(ICnan == 0, np.nan, ICnan) + ICcsum = np.nancumsum(ICnan, axis=1) + + infbitsy = np.hstack([0, np.repeat(np.arange(1, nvars), 2), nvars]) + infbitsx = np.repeat(infbits, 2) + infbitsx100 = np.repeat(infbits100, 2) + + fig_height = np.max([4, 4 + (nvars - 10) * 0.2]) # auto adjust to nvars + fig, ax1 = plt.subplots(1, 1, figsize=(12, fig_height), sharey=True) + ax1.invert_yaxis() + ax1.set_box_aspect(1 / n_bits * nvars) + plt.tight_layout(rect=[0.06, 0.18, 0.8, 0.98]) + pos = ax1.get_position() + cax = fig.add_axes([pos.x0, 0.12, pos.x1 - pos.x0, 0.02]) + + ax1right = ax1.twinx() + ax1right.invert_yaxis() + ax1right.set_box_aspect(1 / n_bits * nvars) + + if cmap == "turku": + import cmcrameri.cm as cmc + + cmap = cmc.turku_r + pcm = ax1.pcolormesh(ICnan, vmin=0, vmax=1, cmap=cmap) + cbar = plt.colorbar(pcm, cax=cax, orientation="horizontal") + cbar.set_label("information content [bit]") + + # 99% of real information enclosed + ax1.plot( + np.hstack([infbits, infbits[-1]]), + np.arange(nvars + 1), + "C1", + ds="steps-pre", + zorder=10, + label="99% of\ninformation", + ) - # grey shading - ax1.fill_betweenx( - infbitsy, infbitsx, np.ones(len(infbitsx)) * bits, alpha=0.4, color="grey" - ) - ax1.fill_betweenx( - infbitsy, infbitsx100, np.ones(len(infbitsx)) * bits, alpha=0.1, color="c" - ) - ax1.fill_betweenx( - infbitsy, - infbitsx100, - np.ones(len(infbitsx)) * bits, - alpha=0.3, - facecolor="none", - edgecolor="c", - ) + # grey shading + ax1.fill_betweenx( + infbitsy, infbitsx, np.ones(len(infbitsx)) * n_bits, alpha=0.4, color="grey" + ) + ax1.fill_betweenx( + infbitsy, infbitsx100, np.ones(len(infbitsx)) * n_bits, alpha=0.1, color="c" + ) + ax1.fill_betweenx( + infbitsy, + infbitsx100, + np.ones(len(infbitsx)) * n_bits, + alpha=0.3, + facecolor="none", + edgecolor="c", + ) - # for legend only - ax1.fill_betweenx( - [-1, -1], - [-1, -1], - [-1, -1], - color="burlywood", - label="last 1% of\ninformation", - alpha=0.5, - ) - ax1.fill_betweenx( - [-1, -1], - [-1, -1], - [-1, -1], - facecolor="teal", - edgecolor="c", - label="false information", - alpha=0.3, - ) - ax1.fill_betweenx([-1, -1], [-1, -1], [-1, -1], color="w", label="unused bits") + # for legend only + ax1.fill_betweenx( + [-1, -1], + [-1, -1], + [-1, -1], + color="burlywood", + label="last 1% of\ninformation", + alpha=0.5, + ) + ax1.fill_betweenx( + [-1, -1], + [-1, -1], + [-1, -1], + facecolor="teal", + edgecolor="c", + label="false information", + alpha=0.3, + ) + ax1.fill_betweenx([-1, -1], [-1, -1], [-1, -1], color="w", label="unused bits") + + if n_sign > 0: + ax1.axvline(n_sign, color="k", lw=1, zorder=3) + ax1.axvline(nonmantissa_bits, color="k", lw=1, zorder=3) + + fig.suptitle( + "Real bitwise information content", + x=0.05, + y=0.98, + fontweight="bold", + horizontalalignment="left", + ) - ax1.axvline(1, color="k", lw=1, zorder=3) - ax1.axvline(NMBITS[bits], color="k", lw=1, zorder=3) + ax1.set_xlim(0, n_bits) + ax1.set_ylim(nvars, 0) + ax1right.set_ylim(nvars, 0) - fig.suptitle( - "Real bitwise information content", - x=0.05, - y=0.98, - fontweight="bold", - horizontalalignment="left", - ) + ax1.set_yticks(np.arange(nvars) + 0.5) + ax1right.set_yticks(np.arange(nvars) + 0.5) + ax1.set_yticklabels(varnames) + ax1right.set_yticklabels([f"{i:4.1f}" for i in ICcsum[:, -1]]) + ax1right.set_ylabel("total information per value [bit]") - ax1.set_xlim(0, bits) - ax1.set_ylim(nvars, 0) - ax1right.set_ylim(nvars, 0) - - ax1.set_yticks(np.arange(nvars) + 0.5) - ax1right.set_yticks(np.arange(nvars) + 0.5) - ax1.set_yticklabels(varnames) - ax1right.set_yticklabels([f"{i:4.1f}" for i in ICcsum[:, -1]]) - ax1right.set_ylabel("total information per value [bit]") - - ax1.text( - infbits[0] + 0.1, - 0.8, - f"{int(infbits[0]-NMBITS[bits])} mantissa bits", - fontsize=8, - color="saddlebrown", - ) - for i in range(1, nvars): ax1.text( - infbits[i] + 0.1, - (i) + 0.8, - f"{int(infbits[i]-9)}", + infbits[0] + 0.1, + 0.8, + f"{int(infbits[0]-nonmantissa_bits)} mantissa bits", fontsize=8, color="saddlebrown", ) - - ax1.set_xticks([1, 9]) - ax1.set_xticks( - np.hstack([np.arange(1, NMBITS[bits] - 1), np.arange(NMBITS[bits], bits)]), - minor=True, - ) - ax1.set_xticklabels([]) - ax1.text(0, nvars + 1.2, "sign", rotation=90) - ax1.text(2, nvars + 1.2, "exponent bits", color="darkslategrey") - ax1.text(10, nvars + 1.2, "mantissa bits") - - for i in range(1, NMBITS[bits]): + for i in range(1, nvars): + ax1.text( + infbits[i] + 0.1, + (i) + 0.8, + f"{int(infbits[i]-9)}", + fontsize=8, + color="saddlebrown", + ) + + ax1.set_xticks([n_sign, n_sign + n_exp, n_bits]) + ax1.set_xticks( + np.hstack( + [ + np.arange(n_sign, nonmantissa_bits - 1), + np.arange(nonmantissa_bits, n_bits - 1), + ] + ), + minor=True, + ) + ax1.set_xticklabels([]) + if n_sign > 0: + ax1.text(0, nvars + 1.2, "sign", rotation=90) + if n_exp > 0: + ax1.text( + n_sign + n_exp / 2, + nvars + 1.2, + "exponent bits", + color="darkslategrey", + horizontalalignment="center", + verticalalignment="center", + ) ax1.text( - i + 0.5, nvars + 0.5, i, ha="center", fontsize=7, color="darkslategrey" + n_sign + n_exp + n_mant / 2, + nvars + 1.2, + "mantissa bits", + horizontalalignment="center", + verticalalignment="center", ) - for i in range(1, bits - NMBITS[bits] + 1): - ax1.text(NMBITS[bits] - 1 + i + 0.5, nvars + 0.5, i, ha="center", fontsize=7) - - ax1.legend(bbox_to_anchor=(1.08, 0.5), loc="center left", framealpha=0.6) - - fig.show() + # Set xticklabels + ## Set exponent labels + for e, i in enumerate(range(n_sign, n_sign + n_exp)): + ax1.text( + i + 0.5, + nvars + 0.5, + e + 1, + ha="center", + fontsize=7, + color="darkslategrey", + ) + ## Set mantissa labels + for m in range(1, n_mant + 1): + ax1.text( + nonmantissa_bits - 1 + m + 0.5, nvars + 0.5, m, ha="center", fontsize=7 + ) + + ax1.legend(bbox_to_anchor=(1.08, 0.5), loc="center left", framealpha=0.6) + + fig.show() return fig From ec7d99c5b9a83ecb7d8c6aac05f91000b266cd5b Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Fri, 23 Jun 2023 23:01:31 -0700 Subject: [PATCH 06/53] add crop argument --- xbitinfo/graphics.py | 54 ++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/xbitinfo/graphics.py b/xbitinfo/graphics.py index 9afe1013..3a68350a 100644 --- a/xbitinfo/graphics.py +++ b/xbitinfo/graphics.py @@ -207,7 +207,7 @@ def split_dataset_by_dims(info_per_bit): return var_by_dim -def plot_bitinformation(bitinfo, cmap="turku"): +def plot_bitinformation(bitinfo, cmap="turku", crop=None): """Plot bitwise information content as in Klöwer et al. 2021 Figure 2. Klöwer, M., Razinger, M., Dominguez, J. J., Düben, P. D., & Palmer, T. N. (2021). @@ -220,6 +220,8 @@ def plot_bitinformation(bitinfo, cmap="turku"): Containing the bitwise information content for each variable cmap : str or plt.cm Colormap. Defaults to ``"turku"``. + crop : int + Maximum bits to show in figure. Returns ------- @@ -242,6 +244,10 @@ def plot_bitinformation(bitinfo, cmap="turku"): data_type = np.dtype(dim.replace("bit", "")) n_bits, n_sign, n_exp, n_mant = bit_partitioning(data_type) nonmantissa_bits = n_bits - n_mant + if crop is None: + bits_to_show = n_bits + else: + bits_to_show = int(np.min([crop, n_bits])) nvars = len(bitinfo) varnames = bitinfo.keys() @@ -268,14 +274,14 @@ def plot_bitinformation(bitinfo, cmap="turku"): fig_height = np.max([4, 4 + (nvars - 10) * 0.2]) # auto adjust to nvars fig, ax1 = plt.subplots(1, 1, figsize=(12, fig_height), sharey=True) ax1.invert_yaxis() - ax1.set_box_aspect(1 / n_bits * nvars) + ax1.set_box_aspect(1 / bits_to_show * nvars) plt.tight_layout(rect=[0.06, 0.18, 0.8, 0.98]) pos = ax1.get_position() cax = fig.add_axes([pos.x0, 0.12, pos.x1 - pos.x0, 0.02]) ax1right = ax1.twinx() ax1right.invert_yaxis() - ax1right.set_box_aspect(1 / n_bits * nvars) + ax1right.set_box_aspect(1 / bits_to_show * nvars) if cmap == "turku": import cmcrameri.cm as cmc @@ -297,15 +303,23 @@ def plot_bitinformation(bitinfo, cmap="turku"): # grey shading ax1.fill_betweenx( - infbitsy, infbitsx, np.ones(len(infbitsx)) * n_bits, alpha=0.4, color="grey" + infbitsy, + infbitsx, + np.ones(len(infbitsx)) * bits_to_show, + alpha=0.4, + color="grey", ) ax1.fill_betweenx( - infbitsy, infbitsx100, np.ones(len(infbitsx)) * n_bits, alpha=0.1, color="c" + infbitsy, + infbitsx100, + np.ones(len(infbitsx)) * bits_to_show, + alpha=0.1, + color="c", ) ax1.fill_betweenx( infbitsy, infbitsx100, - np.ones(len(infbitsx)) * n_bits, + np.ones(len(infbitsx)) * bits_to_show, alpha=0.3, facecolor="none", edgecolor="c", @@ -343,7 +357,6 @@ def plot_bitinformation(bitinfo, cmap="turku"): horizontalalignment="left", ) - ax1.set_xlim(0, n_bits) ax1.set_ylim(nvars, 0) ax1right.set_ylim(nvars, 0) @@ -369,14 +382,16 @@ def plot_bitinformation(bitinfo, cmap="turku"): color="saddlebrown", ) - ax1.set_xticks([n_sign, n_sign + n_exp, n_bits]) + major_xticks = np.array([n_sign, n_sign + n_exp, n_bits], dtype="int") + ax1.set_xticks(major_xticks[major_xticks <= bits_to_show]) + minor_xticks = np.hstack( + [ + np.arange(n_sign, nonmantissa_bits - 1), + np.arange(nonmantissa_bits, n_bits - 1), + ] + ) ax1.set_xticks( - np.hstack( - [ - np.arange(n_sign, nonmantissa_bits - 1), - np.arange(nonmantissa_bits, n_bits - 1), - ] - ), + minor_xticks[minor_xticks <= bits_to_show], minor=True, ) ax1.set_xticklabels([]) @@ -401,7 +416,7 @@ def plot_bitinformation(bitinfo, cmap="turku"): # Set xticklabels ## Set exponent labels - for e, i in enumerate(range(n_sign, n_sign + n_exp)): + for e, i in enumerate(range(n_sign, np.min([n_sign + n_exp, bits_to_show]))): ax1.text( i + 0.5, nvars + 0.5, @@ -411,12 +426,13 @@ def plot_bitinformation(bitinfo, cmap="turku"): color="darkslategrey", ) ## Set mantissa labels - for m in range(1, n_mant + 1): - ax1.text( - nonmantissa_bits - 1 + m + 0.5, nvars + 0.5, m, ha="center", fontsize=7 - ) + for m, i in enumerate( + range(n_sign + n_exp, np.min([n_sign + n_exp + n_mant, bits_to_show])) + ): + ax1.text(i + 0.5, nvars + 0.5, m + 1, ha="center", fontsize=7) ax1.legend(bbox_to_anchor=(1.08, 0.5), loc="center left", framealpha=0.6) + ax1.set_xlim(0, bits_to_show) fig.show() From bc4b709e28e89a647f33fbb016bcc7ec61c3dd15 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Sat, 24 Jun 2023 00:00:51 -0700 Subject: [PATCH 07/53] check dataset is reduced --- xbitinfo/graphics.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xbitinfo/graphics.py b/xbitinfo/graphics.py index 3a68350a..957335e2 100644 --- a/xbitinfo/graphics.py +++ b/xbitinfo/graphics.py @@ -237,6 +237,9 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): """ import matplotlib.pyplot as plt + assert ( + "dim" not in bitinfo.dims + ), "Found dependence of bitinformation on dimension. Please reduce data first by e.g. `bitinfo.max(dim='dim')`" vars_by_dim = split_dataset_by_dims(bitinfo) bitinfo_all = bitinfo for dim, vars in vars_by_dim.items(): From 7417708aaee88239284ce653cf112b9842941ea2 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Sat, 24 Jun 2023 00:10:00 -0700 Subject: [PATCH 08/53] refactor for data_type dimension --- xbitinfo/graphics.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/xbitinfo/graphics.py b/xbitinfo/graphics.py index 957335e2..32a4d6ad 100644 --- a/xbitinfo/graphics.py +++ b/xbitinfo/graphics.py @@ -117,16 +117,12 @@ def add_bitinfo_labels( CDF = _cdf_from_info_per_bit(info_per_bit, dimension) CDF_DataArray = CDF[da.name] + data_type = np.dtype(dimension.replace("bit", "")) + _, _, n_exp, _ = bit_partitioning(data_type) if inflevels is None: inflevels = [] for i, keep in enumerate(keepbits): - if dimension == "bit16": - mantissa_index = keep + 5 - if dimension == "bit32": - mantissa_index = keep + 8 - if dimension == "bit64": - mantissa_index = keep + 11 - + mantissa_index = keep + n_exp inflevels.append(CDF_DataArray[mantissa_index].values) if keepbits is None: From 8aad1aeda5bb371b9e8719d7c82f411b01357af1 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Sat, 24 Jun 2023 00:52:33 -0700 Subject: [PATCH 09/53] adjust test for new dimension names --- tests/test_get_bitinformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_get_bitinformation.py b/tests/test_get_bitinformation.py index 0b370d07..ad711495 100644 --- a/tests/test_get_bitinformation.py +++ b/tests/test_get_bitinformation.py @@ -167,7 +167,7 @@ def test_get_bitinformation_dtype(rasm, dtype, implementation): ds = rasm.astype(dtype) v = list(ds.data_vars)[0] dtype_bits = dtype.replace("float", "") - assert len(xb.get_bitinformation(ds, dim="x")[v].coords["bit" + dtype_bits]) == int( + assert len(xb.get_bitinformation(ds, dim="x")[v].coords["bit" + dtype]) == int( dtype_bits ) From d4657fb33c2f4d023e21df1103197e19477629a6 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Sat, 24 Jun 2023 00:57:04 -0700 Subject: [PATCH 10/53] remove deprecated np.complex --- xbitinfo/xbitinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbitinfo/xbitinfo.py b/xbitinfo/xbitinfo.py index 460ea03c..d323e5ac 100644 --- a/xbitinfo/xbitinfo.py +++ b/xbitinfo/xbitinfo.py @@ -698,7 +698,7 @@ class JsonCustomEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, (np.ndarray, np.number)): return obj.tolist() - elif isinstance(obj, (complex, np.complex)): + elif isinstance(obj, complex): return [obj.real, obj.imag] elif isinstance(obj, set): return list(obj) From c42cf5e27ca23ed0f0b69c81bd02dcbab55f3751 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Sat, 24 Jun 2023 01:03:22 -0700 Subject: [PATCH 11/53] adjust for new dimension names --- xbitinfo/xbitinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbitinfo/xbitinfo.py b/xbitinfo/xbitinfo.py index d323e5ac..ab467248 100644 --- a/xbitinfo/xbitinfo.py +++ b/xbitinfo/xbitinfo.py @@ -465,7 +465,7 @@ def get_keepbits(info_per_bit, inflevel=0.99): ) # keep all mantissa bits for 100% information if 1.0 in inflevel: - bitdim_all_mantissa_bits = int(bitdim[3:]) - bitdim_non_mantissa_bits + bitdim_all_mantissa_bits = n_bits - bitdim_non_mantissa_bits keepall = xr.ones_like(keepmantissabits_bitdim.sel(inflevel=1.0)) * ( bitdim_all_mantissa_bits ) From 65971667bb278ff1f15b24d929252b9d6b776033 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Sat, 24 Jun 2023 09:20:38 -0700 Subject: [PATCH 12/53] convert dtype to str for saving to json --- xbitinfo/xbitinfo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xbitinfo/xbitinfo.py b/xbitinfo/xbitinfo.py index 5c930ff2..8fdb899b 100644 --- a/xbitinfo/xbitinfo.py +++ b/xbitinfo/xbitinfo.py @@ -72,7 +72,7 @@ def dict_to_dataset(info_per_bit): """Convert keepbits dictionary to :py:class:`xarray.Dataset`.""" dsb = xr.Dataset() for v in info_per_bit.keys(): - dtype = info_per_bit[v]["dtype"] + dtype = np.dtype(info_per_bit[v]["dtype"]) dim = info_per_bit[v]["dim"] dim_name = f"bit{dtype}" dsb[v] = xr.DataArray( @@ -284,7 +284,7 @@ def _jl_get_bitinformation(ds, var, axis, dim, kwargs={}): ) info_per_bit["dim"] = dim info_per_bit["axis"] = axis_jl - 1 - info_per_bit["dtype"] = ds[var].dtype + info_per_bit["dtype"] = str(ds[var].dtype) return info_per_bit @@ -320,7 +320,7 @@ def _py_get_bitinformation(ds, var, axis, dim, kwargs={}): info_per_bit["bitinfo"] = pb.bitinformation(X, axis=axis).compute() info_per_bit["dim"] = dim info_per_bit["axis"] = axis - info_per_bit["dtype"] = ds[var].dtype + info_per_bit["dtype"] = str(ds[var].dtype) return info_per_bit From 63f1809ee09b8d79a1a22bf43e196e0f805c6956 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Sat, 24 Jun 2023 09:22:12 -0700 Subject: [PATCH 13/53] fix for len(dim)==1 --- xbitinfo/graphics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xbitinfo/graphics.py b/xbitinfo/graphics.py index 32a4d6ad..2b9272c5 100644 --- a/xbitinfo/graphics.py +++ b/xbitinfo/graphics.py @@ -233,6 +233,7 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): """ import matplotlib.pyplot as plt + bitinfo = bitinfo.squeeze() assert ( "dim" not in bitinfo.dims ), "Found dependence of bitinformation on dimension. Please reduce data first by e.g. `bitinfo.max(dim='dim')`" From 9d1ba15b0209377b9a40a86f645f1ec2146d2b7a Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Sat, 24 Jun 2023 09:22:50 -0700 Subject: [PATCH 14/53] adjust test for new dimension names --- tests/test_get_bitinformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_get_bitinformation.py b/tests/test_get_bitinformation.py index ad711495..cbd22d6b 100644 --- a/tests/test_get_bitinformation.py +++ b/tests/test_get_bitinformation.py @@ -206,7 +206,7 @@ def test_get_bitinformation_different_dtypes(rasm, implementation): ds["Tair32"] = ds.Tair.astype("float32") ds["Tair16"] = ds.Tair.astype("float16") bi = xb.get_bitinformation(ds, implementation=implementation) - for bitdim in ["bit16", "bit32", "bit64"]: + for bitdim in ["bitfloat16", "bitfloat32", "bitfloat64"]: assert bitdim in bi.dims assert bitdim in bi.coords From da343f014ae21382d760668d3cee7c62ddc7beca Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Sat, 24 Jun 2023 09:28:49 -0700 Subject: [PATCH 15/53] adjust doctest to new coords --- xbitinfo/xbitinfo.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/xbitinfo/xbitinfo.py b/xbitinfo/xbitinfo.py index 8fdb899b..0415147d 100644 --- a/xbitinfo/xbitinfo.py +++ b/xbitinfo/xbitinfo.py @@ -153,12 +153,12 @@ def get_bitinformation( # noqa: C901 >>> ds = xr.tutorial.load_dataset("air_temperature") >>> xb.get_bitinformation(ds, dim="lon") # doctest: +ELLIPSIS - Dimensions: (bit32: 32) + Dimensions: (bitfloat32: 32) Coordinates: - * bit32 (bit32) >> xb.get_bitinformation(ds) - Dimensions: (bit32: 32, dim: 3) + Dimensions: (bitfloat32: 32, dim: 3) Coordinates: - * bit32 (bit32) Date: Sat, 14 Oct 2023 21:14:10 +0200 Subject: [PATCH 16/53] add notebook for chunking methods --- docs/chunking.ipynb | 891 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 891 insertions(+) create mode 100644 docs/chunking.ipynb diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb new file mode 100644 index 00000000..c171dea6 --- /dev/null +++ b/docs/chunking.ipynb @@ -0,0 +1,891 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e515b4bd-a302-45a9-8464-56b67a73a46c", + "metadata": {}, + "source": [ + "## Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "96e9149e-fc6d-4048-8e45-a29966e5c6b8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from itertools import product\n", + "import numpy as np\n", + "\n", + "import xarray as xr\n", + "import xbitinfo as xb" + ] + }, + { + "cell_type": "markdown", + "id": "b64e0873-0a27-4757-947a-4a559a102288", + "metadata": {}, + "source": [ + "## Data loading" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "320224c9-06e2-428a-8614-8ed0d15eee82", + "metadata": {}, + "outputs": [], + "source": [ + "# load data\n", + "ds = xr.tutorial.load_dataset(\"air_temperature\") \n", + "chunks = {'lat':5,'lon':10} # Defining chunks that will be used for the reading/bitrounding/writing\n", + "ds = ds.chunk(chunks) # Apply chunking" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f3120040-79f1-4a7f-a61f-afec9fb3ca5b", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset>\n",
+       "Dimensions:  (lat: 25, time: 2920, lon: 53)\n",
+       "Coordinates:\n",
+       "  * lat      (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n",
+       "  * lon      (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n",
+       "  * time     (time) datetime64[ns] 2013-01-01 ... 2014-12-31T18:00:00\n",
+       "Data variables:\n",
+       "    air      (time, lat, lon) float32 dask.array<chunksize=(2920, 5, 10), meta=np.ndarray>\n",
+       "Attributes:\n",
+       "    Conventions:  COARDS\n",
+       "    title:        4x daily NMC reanalysis (1948)\n",
+       "    description:  Data is from NMC initialized reanalysis\\n(4x/day).  These a...\n",
+       "    platform:     Model\n",
+       "    references:   http://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanaly...
" + ], + "text/plain": [ + "\n", + "Dimensions: (lat: 25, time: 2920, lon: 53)\n", + "Coordinates:\n", + " * lat (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n", + " * lon (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n", + " * time (time) datetime64[ns] 2013-01-01 ... 2014-12-31T18:00:00\n", + "Data variables:\n", + " air (time, lat, lon) float32 dask.array\n", + "Attributes:\n", + " Conventions: COARDS\n", + " title: 4x daily NMC reanalysis (1948)\n", + " description: Data is from NMC initialized reanalysis\\n(4x/day). These a...\n", + " platform: Model\n", + " references: http://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanaly..." + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ds" + ] + }, + { + "cell_type": "markdown", + "id": "b9e8fe5a-2e4e-4dfd-8026-0991e9988668", + "metadata": {}, + "source": [ + "## Saving to file" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e011a900-5da2-40be-a292-d81a0cafcd6d", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_11221/350902741.py:1: SerializationWarning: saving variable air with floating point data as an integer dtype without any _FillValue to use for NaNs\n", + " ds.to_netcdf(\"0.air_original.nc\")\n" + ] + } + ], + "source": [ + "ds.to_netcdf(\"0.air_original.nc\")" + ] + }, + { + "cell_type": "markdown", + "id": "2b98628e-cbcb-4018-8565-4c0324cf2d61", + "metadata": {}, + "source": [ + "## Compress with `to_compressed_netcdf`" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "99d02f35-85fc-4a8d-94a0-880ac2ffbb72", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/ayoubf/Projects/xbitinfo/xbitinfo/save_compressed.py:121: SerializationWarning: saving variable air with floating point data as an integer dtype without any _FillValue to use for NaNs\n", + " self._obj.to_netcdf(\n" + ] + } + ], + "source": [ + "ds.to_compressed_netcdf(\"1.air_compressed_all.nc\")" + ] + }, + { + "cell_type": "markdown", + "id": "5f5aae30-4a0a-401c-9018-9e34626c3d2c", + "metadata": {}, + "source": [ + "## Compress with bitrounding" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "fdf077dd-6494-4c38-9461-5ea7ac370a01", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "eedef4b12a25419fba0f9d7348e619a2", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/1 [00:00>> slices_from_chunks(((2, 2), (3, 3, 3))) # doctest: +NORMALIZE_WHITESPACE\n", + " [(slice(0, 2, None), slice(0, 3, None)),\n", + " (slice(0, 2, None), slice(3, 6, None)),\n", + " (slice(0, 2, None), slice(6, 9, None)),\n", + " (slice(2, 4, None), slice(0, 3, None)),\n", + " (slice(2, 4, None), slice(3, 6, None)),\n", + " (slice(2, 4, None), slice(6, 9, None))]\n", + " \"\"\"\n", + " cumdims = []\n", + " for bds in chunks:\n", + " out = np.empty(len(bds)+1, dtype=int)\n", + " out[0] = 0\n", + " np.cumsum(bds, out=out[1:])\n", + " cumdims.append(out)\n", + " slices = [\n", + " [slice(s, s + dim) for s, dim in zip(starts, shapes)]\n", + " for starts, shapes in zip(cumdims, chunks)\n", + " ]\n", + " return list(product(*slices))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "19032fcd-93bc-48b8-ba1f-beba9673491b", + "metadata": {}, + "outputs": [], + "source": [ + "fn = 'air.zarr' # Output filename\n", + "ds.to_compressed_zarr(fn, compute=False, mode='w') # Creates empty file structure" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7221b47f-b8f4-4ebf-bc2b-cb61d12989be", + "metadata": {}, + "outputs": [], + "source": [ + "dims = ds.air.dims\n", + "len_dims = len(dims)\n", + "slices = slices_from_chunks(ds.air.chunks)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7c2ed18f-4dc8-4f5c-88ed-ae5ad41d1647", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "for b, block in enumerate(ds.air.data.to_delayed().ravel()): # Loop over each chunk\n", + " #slices = {d:s for (d,s) in zip(dims, block.key[1:1+len_dims])}\n", + " ds_block = xr.Dataset({'air':(dims, block.compute())}) # Conversion of dask.delayed array to Dataset (as xbitinfo wants type xr.Dataset)\n", + " rounded_ds = bitrounding(ds_block) # Apply bitrounding\n", + " rounded_ds.to_zarr(fn, region={dims[d]:s for (d,s) in enumerate(slices[b])}) # Write individual chunk to disk" + ] + }, + { + "cell_type": "markdown", + "id": "9ae3603f-291d-4c7f-92a8-95d9935daf35", + "metadata": {}, + "source": [ + "## Creating smaller datasets as chunks and compressing" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "1d53f86f-fa72-4161-a364-8c1f78dba6d6", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "%%capture\n", + "\n", + "at_least_zero = lambda x: max(x, 0)\n", + "\n", + "chunk_long, chunk_lat = [10, 5] # for int division\n", + "var = 'lat'\n", + "\n", + "dss = []\n", + "dss_bitrounded = []\n", + "dss_kbits = []\n", + "\n", + "long_c = int(ds.lon.size / chunk_long)\n", + "lat_c = int(ds.lat.size / chunk_lat)\n", + "\n", + "for i in range(long_c):\n", + " for j in range(lat_c):\n", + " temp_ds = ds.isel(lon=slice(i*chunk_long, (i+1)*chunk_long),\n", + " lat=slice(j*chunk_lat, (j+1)*chunk_lat))\n", + " dss.append(temp_ds)\n", + " temp_info_pbit = xb.get_bitinformation(temp_ds, dim=var, implementation=\"python\")\n", + " temp_keepbits = xb.get_keepbits(temp_info_pbit, 0.99)\n", + " # temp_keepbits = temp_keepbits.map(at_least_zero)\n", + " dss_kbits.append(temp_keepbits)\n", + " temp_ds_bitrounded = xb.xr_bitround(temp_ds, temp_keepbits)\n", + " dss_bitrounded.append(temp_ds_bitrounded)\n", + "\n", + " if i == 0 and j == 0 : \n", + " MERGED_ds_bitr = temp_ds_bitrounded\n", + " else:\n", + " MERGED_ds_bitr = xr.merge([MERGED_ds_bitr, temp_ds_bitrounded])" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7e1c4b35-4767-47e7-9ddf-357250b631b8", + "metadata": {}, + "outputs": [], + "source": [ + "MERGED_ds_bitr.to_compressed_netcdf(\"3.air_chunked_bitr_compressed.nc\")" + ] + }, + { + "cell_type": "markdown", + "id": "d3b60c66-252d-48a6-af93-a00c9ca8f0ba", + "metadata": {}, + "source": [ + "## ALL" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "998581b5-6ad9-4f6f-9c61-d0bf1486ec7f", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7.5M\t0.air_original.nc\n", + "1.7M\t1.air_compressed_all.nc\n", + "1.3M\t2.air_bitrounded_compressed.nc\n", + "776K\t3.air_chunked_bitr_compressed.nc\n", + "1.1M\tair.zarr\n" + ] + } + ], + "source": [ + "!du -hs *.nc *.zarr" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:bitinfo] *", + "language": "python", + "name": "conda-env-bitinfo-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 69598b06fd1361c9bb65200c2507dbda132b27c7 Mon Sep 17 00:00:00 2001 From: ayoubft <63267601+ayoubft@users.noreply.github.com> Date: Sat, 14 Oct 2023 21:14:39 +0200 Subject: [PATCH 17/53] add chunking entry in docs --- docs/index.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 5c476353..068b79e7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -96,6 +96,17 @@ Credits quick-start.ipynb +**Chunking** + +* :doc:`chunking` + +.. toctree:: + :maxdepth: 1 + :hidden: + :caption: Chunking + + chunking.ipynb + **Help & Reference** * :doc:`api` From 428a1b679446ca88f53833eb6018b45778fce6a6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 19:15:59 +0000 Subject: [PATCH 18/53] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/chunking.ipynb | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index c171dea6..0e1476a5 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -40,8 +40,11 @@ "outputs": [], "source": [ "# load data\n", - "ds = xr.tutorial.load_dataset(\"air_temperature\") \n", - "chunks = {'lat':5,'lon':10} # Defining chunks that will be used for the reading/bitrounding/writing\n", + "ds = xr.tutorial.load_dataset(\"air_temperature\")\n", + "chunks = {\n", + " \"lat\": 5,\n", + " \"lon\": 10,\n", + "} # Defining chunks that will be used for the reading/bitrounding/writing\n", "ds = ds.chunk(chunks) # Apply chunking" ] }, @@ -703,7 +706,7 @@ "metadata": {}, "outputs": [], "source": [ - "def bitrounding(chunk, var='lat'):\n", + "def bitrounding(chunk, var=\"lat\"):\n", " \"\"\"\n", " Just a function that handles all the xbitinfo calls\n", " \"\"\"\n", @@ -712,8 +715,9 @@ " bitround = xb.xr_bitround(chunk, keepbits)\n", " return bitround\n", "\n", + "\n", "def slices_from_chunks(chunks):\n", - " \"\"\" Translate chunks tuple to a set of slices in product order\n", + " \"\"\"Translate chunks tuple to a set of slices in product order\n", "\n", " >>> slices_from_chunks(((2, 2), (3, 3, 3))) # doctest: +NORMALIZE_WHITESPACE\n", " [(slice(0, 2, None), slice(0, 3, None)),\n", @@ -725,7 +729,7 @@ " \"\"\"\n", " cumdims = []\n", " for bds in chunks:\n", - " out = np.empty(len(bds)+1, dtype=int)\n", + " out = np.empty(len(bds) + 1, dtype=int)\n", " out[0] = 0\n", " np.cumsum(bds, out=out[1:])\n", " cumdims.append(out)\n", @@ -743,8 +747,8 @@ "metadata": {}, "outputs": [], "source": [ - "fn = 'air.zarr' # Output filename\n", - "ds.to_compressed_zarr(fn, compute=False, mode='w') # Creates empty file structure" + "fn = \"air.zarr\" # Output filename\n", + "ds.to_compressed_zarr(fn, compute=False, mode=\"w\") # Creates empty file structure" ] }, { @@ -768,10 +772,14 @@ "source": [ "%%capture\n", "for b, block in enumerate(ds.air.data.to_delayed().ravel()): # Loop over each chunk\n", - " #slices = {d:s for (d,s) in zip(dims, block.key[1:1+len_dims])}\n", - " ds_block = xr.Dataset({'air':(dims, block.compute())}) # Conversion of dask.delayed array to Dataset (as xbitinfo wants type xr.Dataset)\n", + " # slices = {d:s for (d,s) in zip(dims, block.key[1:1+len_dims])}\n", + " ds_block = xr.Dataset(\n", + " {\"air\": (dims, block.compute())}\n", + " ) # Conversion of dask.delayed array to Dataset (as xbitinfo wants type xr.Dataset)\n", " rounded_ds = bitrounding(ds_block) # Apply bitrounding\n", - " rounded_ds.to_zarr(fn, region={dims[d]:s for (d,s) in enumerate(slices[b])}) # Write individual chunk to disk" + " rounded_ds.to_zarr(\n", + " fn, region={dims[d]: s for (d, s) in enumerate(slices[b])}\n", + " ) # Write individual chunk to disk" ] }, { @@ -796,8 +804,8 @@ "\n", "at_least_zero = lambda x: max(x, 0)\n", "\n", - "chunk_long, chunk_lat = [10, 5] # for int division\n", - "var = 'lat'\n", + "chunk_long, chunk_lat = [10, 5] # for int division\n", + "var = \"lat\"\n", "\n", "dss = []\n", "dss_bitrounded = []\n", @@ -808,17 +816,21 @@ "\n", "for i in range(long_c):\n", " for j in range(lat_c):\n", - " temp_ds = ds.isel(lon=slice(i*chunk_long, (i+1)*chunk_long),\n", - " lat=slice(j*chunk_lat, (j+1)*chunk_lat))\n", + " temp_ds = ds.isel(\n", + " lon=slice(i * chunk_long, (i + 1) * chunk_long),\n", + " lat=slice(j * chunk_lat, (j + 1) * chunk_lat),\n", + " )\n", " dss.append(temp_ds)\n", - " temp_info_pbit = xb.get_bitinformation(temp_ds, dim=var, implementation=\"python\")\n", + " temp_info_pbit = xb.get_bitinformation(\n", + " temp_ds, dim=var, implementation=\"python\"\n", + " )\n", " temp_keepbits = xb.get_keepbits(temp_info_pbit, 0.99)\n", " # temp_keepbits = temp_keepbits.map(at_least_zero)\n", " dss_kbits.append(temp_keepbits)\n", " temp_ds_bitrounded = xb.xr_bitround(temp_ds, temp_keepbits)\n", " dss_bitrounded.append(temp_ds_bitrounded)\n", "\n", - " if i == 0 and j == 0 : \n", + " if i == 0 and j == 0:\n", " MERGED_ds_bitr = temp_ds_bitrounded\n", " else:\n", " MERGED_ds_bitr = xr.merge([MERGED_ds_bitr, temp_ds_bitrounded])" From 1a1100739892333ea7e9c6fab2a04515d7d8b8e0 Mon Sep 17 00:00:00 2001 From: ayoubft <63267601+ayoubft@users.noreply.github.com> Date: Sat, 14 Oct 2023 22:59:47 +0200 Subject: [PATCH 19/53] change nb metadata to avoid CI failure --- docs/chunking.ipynb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index c171dea6..1fa31d4d 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -869,9 +869,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python [conda env:bitinfo] *", + "display_name": "Python 3", "language": "python", - "name": "conda-env-bitinfo-py" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -883,8 +883,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.3" - } + "version": "3.10.4" + }, + "toc-autonumbering": true }, "nbformat": 4, "nbformat_minor": 5 From f5c56a53d32a1b622ac4227b048842b3164b21b9 Mon Sep 17 00:00:00 2001 From: ayoubft <63267601+ayoubft@users.noreply.github.com> Date: Sat, 14 Oct 2023 23:15:43 +0200 Subject: [PATCH 20/53] add title to nb --- docs/chunking.ipynb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index e81a4a3e..166031b5 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -1,5 +1,13 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "a1f40619", + "metadata": {}, + "source": [ + "# Chunking" + ] + }, { "cell_type": "markdown", "id": "e515b4bd-a302-45a9-8464-56b67a73a46c", From e9eee1cfa9b593ee848aa70acea0660fc311d0e5 Mon Sep 17 00:00:00 2001 From: ayoubft <63267601+ayoubft@users.noreply.github.com> Date: Sat, 14 Oct 2023 21:14:10 +0200 Subject: [PATCH 21/53] add notebook for chunking methods --- docs/chunking.ipynb | 891 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 891 insertions(+) create mode 100644 docs/chunking.ipynb diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb new file mode 100644 index 00000000..c171dea6 --- /dev/null +++ b/docs/chunking.ipynb @@ -0,0 +1,891 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e515b4bd-a302-45a9-8464-56b67a73a46c", + "metadata": {}, + "source": [ + "## Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "96e9149e-fc6d-4048-8e45-a29966e5c6b8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from itertools import product\n", + "import numpy as np\n", + "\n", + "import xarray as xr\n", + "import xbitinfo as xb" + ] + }, + { + "cell_type": "markdown", + "id": "b64e0873-0a27-4757-947a-4a559a102288", + "metadata": {}, + "source": [ + "## Data loading" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "320224c9-06e2-428a-8614-8ed0d15eee82", + "metadata": {}, + "outputs": [], + "source": [ + "# load data\n", + "ds = xr.tutorial.load_dataset(\"air_temperature\") \n", + "chunks = {'lat':5,'lon':10} # Defining chunks that will be used for the reading/bitrounding/writing\n", + "ds = ds.chunk(chunks) # Apply chunking" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f3120040-79f1-4a7f-a61f-afec9fb3ca5b", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset>\n",
+       "Dimensions:  (lat: 25, time: 2920, lon: 53)\n",
+       "Coordinates:\n",
+       "  * lat      (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n",
+       "  * lon      (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n",
+       "  * time     (time) datetime64[ns] 2013-01-01 ... 2014-12-31T18:00:00\n",
+       "Data variables:\n",
+       "    air      (time, lat, lon) float32 dask.array<chunksize=(2920, 5, 10), meta=np.ndarray>\n",
+       "Attributes:\n",
+       "    Conventions:  COARDS\n",
+       "    title:        4x daily NMC reanalysis (1948)\n",
+       "    description:  Data is from NMC initialized reanalysis\\n(4x/day).  These a...\n",
+       "    platform:     Model\n",
+       "    references:   http://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanaly...
" + ], + "text/plain": [ + "\n", + "Dimensions: (lat: 25, time: 2920, lon: 53)\n", + "Coordinates:\n", + " * lat (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n", + " * lon (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n", + " * time (time) datetime64[ns] 2013-01-01 ... 2014-12-31T18:00:00\n", + "Data variables:\n", + " air (time, lat, lon) float32 dask.array\n", + "Attributes:\n", + " Conventions: COARDS\n", + " title: 4x daily NMC reanalysis (1948)\n", + " description: Data is from NMC initialized reanalysis\\n(4x/day). These a...\n", + " platform: Model\n", + " references: http://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanaly..." + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ds" + ] + }, + { + "cell_type": "markdown", + "id": "b9e8fe5a-2e4e-4dfd-8026-0991e9988668", + "metadata": {}, + "source": [ + "## Saving to file" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e011a900-5da2-40be-a292-d81a0cafcd6d", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_11221/350902741.py:1: SerializationWarning: saving variable air with floating point data as an integer dtype without any _FillValue to use for NaNs\n", + " ds.to_netcdf(\"0.air_original.nc\")\n" + ] + } + ], + "source": [ + "ds.to_netcdf(\"0.air_original.nc\")" + ] + }, + { + "cell_type": "markdown", + "id": "2b98628e-cbcb-4018-8565-4c0324cf2d61", + "metadata": {}, + "source": [ + "## Compress with `to_compressed_netcdf`" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "99d02f35-85fc-4a8d-94a0-880ac2ffbb72", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/ayoubf/Projects/xbitinfo/xbitinfo/save_compressed.py:121: SerializationWarning: saving variable air with floating point data as an integer dtype without any _FillValue to use for NaNs\n", + " self._obj.to_netcdf(\n" + ] + } + ], + "source": [ + "ds.to_compressed_netcdf(\"1.air_compressed_all.nc\")" + ] + }, + { + "cell_type": "markdown", + "id": "5f5aae30-4a0a-401c-9018-9e34626c3d2c", + "metadata": {}, + "source": [ + "## Compress with bitrounding" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "fdf077dd-6494-4c38-9461-5ea7ac370a01", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "eedef4b12a25419fba0f9d7348e619a2", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/1 [00:00>> slices_from_chunks(((2, 2), (3, 3, 3))) # doctest: +NORMALIZE_WHITESPACE\n", + " [(slice(0, 2, None), slice(0, 3, None)),\n", + " (slice(0, 2, None), slice(3, 6, None)),\n", + " (slice(0, 2, None), slice(6, 9, None)),\n", + " (slice(2, 4, None), slice(0, 3, None)),\n", + " (slice(2, 4, None), slice(3, 6, None)),\n", + " (slice(2, 4, None), slice(6, 9, None))]\n", + " \"\"\"\n", + " cumdims = []\n", + " for bds in chunks:\n", + " out = np.empty(len(bds)+1, dtype=int)\n", + " out[0] = 0\n", + " np.cumsum(bds, out=out[1:])\n", + " cumdims.append(out)\n", + " slices = [\n", + " [slice(s, s + dim) for s, dim in zip(starts, shapes)]\n", + " for starts, shapes in zip(cumdims, chunks)\n", + " ]\n", + " return list(product(*slices))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "19032fcd-93bc-48b8-ba1f-beba9673491b", + "metadata": {}, + "outputs": [], + "source": [ + "fn = 'air.zarr' # Output filename\n", + "ds.to_compressed_zarr(fn, compute=False, mode='w') # Creates empty file structure" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7221b47f-b8f4-4ebf-bc2b-cb61d12989be", + "metadata": {}, + "outputs": [], + "source": [ + "dims = ds.air.dims\n", + "len_dims = len(dims)\n", + "slices = slices_from_chunks(ds.air.chunks)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7c2ed18f-4dc8-4f5c-88ed-ae5ad41d1647", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "for b, block in enumerate(ds.air.data.to_delayed().ravel()): # Loop over each chunk\n", + " #slices = {d:s for (d,s) in zip(dims, block.key[1:1+len_dims])}\n", + " ds_block = xr.Dataset({'air':(dims, block.compute())}) # Conversion of dask.delayed array to Dataset (as xbitinfo wants type xr.Dataset)\n", + " rounded_ds = bitrounding(ds_block) # Apply bitrounding\n", + " rounded_ds.to_zarr(fn, region={dims[d]:s for (d,s) in enumerate(slices[b])}) # Write individual chunk to disk" + ] + }, + { + "cell_type": "markdown", + "id": "9ae3603f-291d-4c7f-92a8-95d9935daf35", + "metadata": {}, + "source": [ + "## Creating smaller datasets as chunks and compressing" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "1d53f86f-fa72-4161-a364-8c1f78dba6d6", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "%%capture\n", + "\n", + "at_least_zero = lambda x: max(x, 0)\n", + "\n", + "chunk_long, chunk_lat = [10, 5] # for int division\n", + "var = 'lat'\n", + "\n", + "dss = []\n", + "dss_bitrounded = []\n", + "dss_kbits = []\n", + "\n", + "long_c = int(ds.lon.size / chunk_long)\n", + "lat_c = int(ds.lat.size / chunk_lat)\n", + "\n", + "for i in range(long_c):\n", + " for j in range(lat_c):\n", + " temp_ds = ds.isel(lon=slice(i*chunk_long, (i+1)*chunk_long),\n", + " lat=slice(j*chunk_lat, (j+1)*chunk_lat))\n", + " dss.append(temp_ds)\n", + " temp_info_pbit = xb.get_bitinformation(temp_ds, dim=var, implementation=\"python\")\n", + " temp_keepbits = xb.get_keepbits(temp_info_pbit, 0.99)\n", + " # temp_keepbits = temp_keepbits.map(at_least_zero)\n", + " dss_kbits.append(temp_keepbits)\n", + " temp_ds_bitrounded = xb.xr_bitround(temp_ds, temp_keepbits)\n", + " dss_bitrounded.append(temp_ds_bitrounded)\n", + "\n", + " if i == 0 and j == 0 : \n", + " MERGED_ds_bitr = temp_ds_bitrounded\n", + " else:\n", + " MERGED_ds_bitr = xr.merge([MERGED_ds_bitr, temp_ds_bitrounded])" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7e1c4b35-4767-47e7-9ddf-357250b631b8", + "metadata": {}, + "outputs": [], + "source": [ + "MERGED_ds_bitr.to_compressed_netcdf(\"3.air_chunked_bitr_compressed.nc\")" + ] + }, + { + "cell_type": "markdown", + "id": "d3b60c66-252d-48a6-af93-a00c9ca8f0ba", + "metadata": {}, + "source": [ + "## ALL" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "998581b5-6ad9-4f6f-9c61-d0bf1486ec7f", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7.5M\t0.air_original.nc\n", + "1.7M\t1.air_compressed_all.nc\n", + "1.3M\t2.air_bitrounded_compressed.nc\n", + "776K\t3.air_chunked_bitr_compressed.nc\n", + "1.1M\tair.zarr\n" + ] + } + ], + "source": [ + "!du -hs *.nc *.zarr" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:bitinfo] *", + "language": "python", + "name": "conda-env-bitinfo-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From face55ab8d36ae176b6c6bb364d700b17b3cc141 Mon Sep 17 00:00:00 2001 From: ayoubft <63267601+ayoubft@users.noreply.github.com> Date: Sat, 14 Oct 2023 21:14:39 +0200 Subject: [PATCH 22/53] add chunking entry in docs --- docs/index.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 5c476353..068b79e7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -96,6 +96,17 @@ Credits quick-start.ipynb +**Chunking** + +* :doc:`chunking` + +.. toctree:: + :maxdepth: 1 + :hidden: + :caption: Chunking + + chunking.ipynb + **Help & Reference** * :doc:`api` From 88e1b74e5485aa95d68900c83ec1e740a3509b77 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 19:15:59 +0000 Subject: [PATCH 23/53] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/chunking.ipynb | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index c171dea6..0e1476a5 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -40,8 +40,11 @@ "outputs": [], "source": [ "# load data\n", - "ds = xr.tutorial.load_dataset(\"air_temperature\") \n", - "chunks = {'lat':5,'lon':10} # Defining chunks that will be used for the reading/bitrounding/writing\n", + "ds = xr.tutorial.load_dataset(\"air_temperature\")\n", + "chunks = {\n", + " \"lat\": 5,\n", + " \"lon\": 10,\n", + "} # Defining chunks that will be used for the reading/bitrounding/writing\n", "ds = ds.chunk(chunks) # Apply chunking" ] }, @@ -703,7 +706,7 @@ "metadata": {}, "outputs": [], "source": [ - "def bitrounding(chunk, var='lat'):\n", + "def bitrounding(chunk, var=\"lat\"):\n", " \"\"\"\n", " Just a function that handles all the xbitinfo calls\n", " \"\"\"\n", @@ -712,8 +715,9 @@ " bitround = xb.xr_bitround(chunk, keepbits)\n", " return bitround\n", "\n", + "\n", "def slices_from_chunks(chunks):\n", - " \"\"\" Translate chunks tuple to a set of slices in product order\n", + " \"\"\"Translate chunks tuple to a set of slices in product order\n", "\n", " >>> slices_from_chunks(((2, 2), (3, 3, 3))) # doctest: +NORMALIZE_WHITESPACE\n", " [(slice(0, 2, None), slice(0, 3, None)),\n", @@ -725,7 +729,7 @@ " \"\"\"\n", " cumdims = []\n", " for bds in chunks:\n", - " out = np.empty(len(bds)+1, dtype=int)\n", + " out = np.empty(len(bds) + 1, dtype=int)\n", " out[0] = 0\n", " np.cumsum(bds, out=out[1:])\n", " cumdims.append(out)\n", @@ -743,8 +747,8 @@ "metadata": {}, "outputs": [], "source": [ - "fn = 'air.zarr' # Output filename\n", - "ds.to_compressed_zarr(fn, compute=False, mode='w') # Creates empty file structure" + "fn = \"air.zarr\" # Output filename\n", + "ds.to_compressed_zarr(fn, compute=False, mode=\"w\") # Creates empty file structure" ] }, { @@ -768,10 +772,14 @@ "source": [ "%%capture\n", "for b, block in enumerate(ds.air.data.to_delayed().ravel()): # Loop over each chunk\n", - " #slices = {d:s for (d,s) in zip(dims, block.key[1:1+len_dims])}\n", - " ds_block = xr.Dataset({'air':(dims, block.compute())}) # Conversion of dask.delayed array to Dataset (as xbitinfo wants type xr.Dataset)\n", + " # slices = {d:s for (d,s) in zip(dims, block.key[1:1+len_dims])}\n", + " ds_block = xr.Dataset(\n", + " {\"air\": (dims, block.compute())}\n", + " ) # Conversion of dask.delayed array to Dataset (as xbitinfo wants type xr.Dataset)\n", " rounded_ds = bitrounding(ds_block) # Apply bitrounding\n", - " rounded_ds.to_zarr(fn, region={dims[d]:s for (d,s) in enumerate(slices[b])}) # Write individual chunk to disk" + " rounded_ds.to_zarr(\n", + " fn, region={dims[d]: s for (d, s) in enumerate(slices[b])}\n", + " ) # Write individual chunk to disk" ] }, { @@ -796,8 +804,8 @@ "\n", "at_least_zero = lambda x: max(x, 0)\n", "\n", - "chunk_long, chunk_lat = [10, 5] # for int division\n", - "var = 'lat'\n", + "chunk_long, chunk_lat = [10, 5] # for int division\n", + "var = \"lat\"\n", "\n", "dss = []\n", "dss_bitrounded = []\n", @@ -808,17 +816,21 @@ "\n", "for i in range(long_c):\n", " for j in range(lat_c):\n", - " temp_ds = ds.isel(lon=slice(i*chunk_long, (i+1)*chunk_long),\n", - " lat=slice(j*chunk_lat, (j+1)*chunk_lat))\n", + " temp_ds = ds.isel(\n", + " lon=slice(i * chunk_long, (i + 1) * chunk_long),\n", + " lat=slice(j * chunk_lat, (j + 1) * chunk_lat),\n", + " )\n", " dss.append(temp_ds)\n", - " temp_info_pbit = xb.get_bitinformation(temp_ds, dim=var, implementation=\"python\")\n", + " temp_info_pbit = xb.get_bitinformation(\n", + " temp_ds, dim=var, implementation=\"python\"\n", + " )\n", " temp_keepbits = xb.get_keepbits(temp_info_pbit, 0.99)\n", " # temp_keepbits = temp_keepbits.map(at_least_zero)\n", " dss_kbits.append(temp_keepbits)\n", " temp_ds_bitrounded = xb.xr_bitround(temp_ds, temp_keepbits)\n", " dss_bitrounded.append(temp_ds_bitrounded)\n", "\n", - " if i == 0 and j == 0 : \n", + " if i == 0 and j == 0:\n", " MERGED_ds_bitr = temp_ds_bitrounded\n", " else:\n", " MERGED_ds_bitr = xr.merge([MERGED_ds_bitr, temp_ds_bitrounded])" From f17fad9fe3edd181d5d371859d516c8d04e5e460 Mon Sep 17 00:00:00 2001 From: ayoubft <63267601+ayoubft@users.noreply.github.com> Date: Sat, 14 Oct 2023 22:59:47 +0200 Subject: [PATCH 24/53] change nb metadata to avoid CI failure --- docs/chunking.ipynb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index 0e1476a5..e81a4a3e 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -881,9 +881,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python [conda env:bitinfo] *", + "display_name": "Python 3", "language": "python", - "name": "conda-env-bitinfo-py" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -895,8 +895,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.3" - } + "version": "3.10.4" + }, + "toc-autonumbering": true }, "nbformat": 4, "nbformat_minor": 5 From 6656d7b13fb64749a0e48947a6f1e3160487c5b2 Mon Sep 17 00:00:00 2001 From: ayoubft <63267601+ayoubft@users.noreply.github.com> Date: Sat, 14 Oct 2023 23:15:43 +0200 Subject: [PATCH 25/53] add title to nb --- docs/chunking.ipynb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index e81a4a3e..166031b5 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -1,5 +1,13 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "a1f40619", + "metadata": {}, + "source": [ + "# Chunking" + ] + }, { "cell_type": "markdown", "id": "e515b4bd-a302-45a9-8464-56b67a73a46c", From ffca4dd7c3b1711bfce2e24525c11833b86ff8ed Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Thu, 14 Dec 2023 11:27:36 -0800 Subject: [PATCH 26/53] support sphinx 6.0 ext.extlinks --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7907a2ce..de102f46 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -152,8 +152,8 @@ copybutton_remove_prompts = True extlinks = { - "issue": ("https://github.com/observingClouds/xbitinfo/issues/%s", "GH#"), - "pr": ("https://github.com/observingClouds/xbitinfo/pull/%s", "GH#"), + "issue": ("https://github.com/observingClouds/xbitinfo/issues/%s", "GH#%s"), + "pr": ("https://github.com/observingClouds/xbitinfo/pull/%s", "GH#%s"), } From 85201e5ae56fbec85e313c586a1f88bdbd53e688 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 10 Dec 2023 01:01:55 +0000 Subject: [PATCH 27/53] Update GitHub Action Versions --- .github/workflows/benchmarks.yml | 2 +- .github/workflows/ci.yaml | 10 +++++----- .github/workflows/pypi.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index c50178ca..ce5a9cf6 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 0 - name: Setup Miniconda - uses: conda-incubator/setup-miniconda@v2.2.0 + uses: conda-incubator/setup-miniconda@v3.0.1 with: # installer-url: https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh installer-url: https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 14e52719..341e1a7b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,7 +33,7 @@ jobs: with: fetch-depth: 0 - name: Set up conda - uses: conda-incubator/setup-miniconda@v2.2.0 + uses: conda-incubator/setup-miniconda@v3.0.1 with: auto-update-conda: false channels: conda-forge @@ -60,7 +60,7 @@ jobs: shell: 'bash -l {0}' steps: - uses: actions/checkout@v4.1.1 - - uses: conda-incubator/setup-miniconda@v2.2.0 + - uses: conda-incubator/setup-miniconda@v3.0.1 with: channels: conda-forge miniforge-variant: Mambaforge @@ -91,7 +91,7 @@ jobs: steps: - uses: actions/checkout@v4.1.1 - name: Set up conda - uses: conda-incubator/setup-miniconda@v2.2.0 + uses: conda-incubator/setup-miniconda@v3.0.1 with: auto-update-conda: false channels: conda-forge @@ -134,11 +134,11 @@ jobs: with: fetch-depth: 0 - name: Setup python - uses: actions/setup-python@v4.7.1 + uses: actions/setup-python@v5.0.0 with: python-version: '3.11' - name: Set up Julia - uses: julia-actions/setup-julia@v1.9.2 + uses: julia-actions/setup-julia@v1.9.4 with: version: 1.7.1 - name: Install dependencies diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 21c614f9..34ae4030 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: Set up Python - uses: actions/setup-python@v4.7.1 + uses: actions/setup-python@v5.0.0 with: python-version: "3.10" @@ -45,7 +45,7 @@ jobs: - name: Publish a Python distribution to PyPI if: success() && github.event_name == 'release' - uses: pypa/gh-action-pypi-publish@v1.8.10 + uses: pypa/gh-action-pypi-publish@v1.8.11 with: user: __token__ password: ${{ secrets.PYPI_PASSWORD }} From 0a8811a930c35bbff314f08553ab776ddf69f019 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:52:01 -0800 Subject: [PATCH 28/53] Rename menu subsection --- docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 068b79e7..f333a444 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -96,14 +96,14 @@ Credits quick-start.ipynb -**Chunking** +**User Guide** * :doc:`chunking` .. toctree:: :maxdepth: 1 :hidden: - :caption: Chunking + :caption: User Guide chunking.ipynb From b35fed7e09bdcc6c5edae255fb4e4001ceca423a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 23:02:50 -0800 Subject: [PATCH 29/53] [pre-commit.ci] pre-commit autoupdate (#238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/PyCQA/autoflake: v2.0.2 → v2.2.1](https://github.com/PyCQA/autoflake/compare/v2.0.2...v2.2.1) - [github.com/asottile/pyupgrade: v3.3.1 → v3.13.0](https://github.com/asottile/pyupgrade/compare/v3.3.1...v3.13.0) - [github.com/psf/black: 23.3.0 → 23.9.1](https://github.com/psf/black/compare/23.3.0...23.9.1) - [github.com/PyCQA/flake8: 6.0.0 → 6.1.0](https://github.com/PyCQA/flake8/compare/6.0.0...6.1.0) - [github.com/pre-commit/mirrors-mypy: v1.1.1 → v1.5.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.1.1...v1.5.1) * Remove typing extension version pin * fix linter error E721 --- .pre-commit-config.yaml | 12 ++++++------ tests/test_get_bitinformation.py | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f9e8dce..7b5c7f24 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: mixed-line-ending # This wants to go before isort & flake8 - repo: https://github.com/PyCQA/autoflake - rev: "v2.0.2" + rev: "v2.2.1" hooks: - id: autoflake # isort should run before black as black sometimes tweaks the isort output args: ["--in-place", "--ignore-init-module-imports"] @@ -24,14 +24,14 @@ repos: - id: isort args: ["--profile", "black"] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.13.0 hooks: - id: pyupgrade args: - "--py38-plus" # https://github.com/python/black#version-control-integration - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.9.1 hooks: - id: black - id: black-jupyter @@ -41,11 +41,11 @@ repos: - id: blackdoc exclude: docs/index.rst - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.1.1 + rev: v1.5.1 hooks: - id: mypy exclude: "properties|asv_bench" @@ -58,6 +58,6 @@ repos: types-pkg_resources, types-PyYAML, types-pytz, - typing-extensions==3.10.0.0, + typing-extensions, numpy, ] diff --git a/tests/test_get_bitinformation.py b/tests/test_get_bitinformation.py index 0b370d07..5eafe2cb 100644 --- a/tests/test_get_bitinformation.py +++ b/tests/test_get_bitinformation.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """Tests for `xbitinfo` package.""" import os @@ -33,7 +31,7 @@ def assert_different(a, b): numpy.testing.assert_array_equal """ __tracebackhide__ = True - assert type(a) == type(b) + assert isinstance(a, type(b)) if isinstance(a, (Variable, DataArray)): assert not a.equals(b), formatting.diff_array_repr(a, b, "equals") elif isinstance(a, Dataset): From 11567328b9cc0a5c689c1a6a0b49fbe77e41af05 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Fri, 15 Dec 2023 00:03:54 -0800 Subject: [PATCH 30/53] WIP: plot testing for all float types --- tests/test_visualisation.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_visualisation.py b/tests/test_visualisation.py index 6a397b6c..aac6ddfa 100644 --- a/tests/test_visualisation.py +++ b/tests/test_visualisation.py @@ -3,7 +3,7 @@ import xarray as xr import xbitinfo as xb -from xbitinfo.graphics import add_bitinfo_labels +from xbitinfo.graphics import add_bitinfo_labels, plot_bitinformation def test_add_bitinfo_labels(): @@ -50,3 +50,9 @@ def test_add_bitinfo_labels(): assert ax.texts[i + 5].get_text() == keepbits_text # Cleanup the plot plt.close() + +@pytest.mark.parametrize("dtype", ["float64", "float32", "float16"]) +def test_plot_bitinformation(rasm, dtype): + ds = rasm.astype(dtype) + info_per_bit = xb.get_bitinformation(ds, dim="lon") + plot_bitinformation(info_per_bit) From 521fb4b1d889653ad7559b287f1086e9263d21ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 08:07:26 +0000 Subject: [PATCH 31/53] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_visualisation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_visualisation.py b/tests/test_visualisation.py index aac6ddfa..7d8104e7 100644 --- a/tests/test_visualisation.py +++ b/tests/test_visualisation.py @@ -51,6 +51,7 @@ def test_add_bitinfo_labels(): # Cleanup the plot plt.close() + @pytest.mark.parametrize("dtype", ["float64", "float32", "float16"]) def test_plot_bitinformation(rasm, dtype): ds = rasm.astype(dtype) From c1b8481f99fc45f72522dbc49913fdfb46f00625 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Fri, 15 Dec 2023 00:03:54 -0800 Subject: [PATCH 32/53] WIP: plot testing for all float types --- tests/test_visualisation.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_visualisation.py b/tests/test_visualisation.py index 6a397b6c..cbadc8c8 100644 --- a/tests/test_visualisation.py +++ b/tests/test_visualisation.py @@ -3,7 +3,7 @@ import xarray as xr import xbitinfo as xb -from xbitinfo.graphics import add_bitinfo_labels +from xbitinfo.graphics import add_bitinfo_labels, plot_bitinformation def test_add_bitinfo_labels(): @@ -50,3 +50,10 @@ def test_add_bitinfo_labels(): assert ax.texts[i + 5].get_text() == keepbits_text # Cleanup the plot plt.close() + +@pytest.mark.parametrize("dtype", ["float64", "float32", "float16"]) +def test_plot_bitinformation(dtype): + rasm = xr.tutorial.load_dataset("air_temperature") + ds = rasm.astype(dtype) + info_per_bit = xb.get_bitinformation(ds, dim="lon") + plot_bitinformation(info_per_bit) From fd8b06e18b22c7835bcfee802f8b3e9b293a6cf6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 08:30:43 +0000 Subject: [PATCH 33/53] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_visualisation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_visualisation.py b/tests/test_visualisation.py index cbadc8c8..d488c9fe 100644 --- a/tests/test_visualisation.py +++ b/tests/test_visualisation.py @@ -51,6 +51,7 @@ def test_add_bitinfo_labels(): # Cleanup the plot plt.close() + @pytest.mark.parametrize("dtype", ["float64", "float32", "float16"]) def test_plot_bitinformation(dtype): rasm = xr.tutorial.load_dataset("air_temperature") From 5f4bda7d396be9745fdf07967c8c036bdd76680f Mon Sep 17 00:00:00 2001 From: ayoubft <63267601+ayoubft@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:49:33 +0100 Subject: [PATCH 34/53] add chunks plot + comments --- docs/chunking.ipynb | 276 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 222 insertions(+), 54 deletions(-) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index 166031b5..0feb60a1 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -8,6 +8,16 @@ "# Chunking" ] }, + { + "cell_type": "markdown", + "id": "b8e2d4f5-c444-404a-8444-3648cb0a94bf", + "metadata": {}, + "source": [ + "Geospatial data can vary in its information density from one part of the world to another. A dataset containing streets will be very dense in cities but contains little information in remote places like the Alps or even the ocean. The same is also true for datasets about the ocean or the atmosphere.\n", + "\n", + "Currently in the bitinformation framework, to preserve all real information, the maximum information content calculated by `xbitinfo` needs to be used for the entire dataset. However, bitinformation can also be calculated on subsets, such that the ‘boring’ parts can therefore be more efficiently compressed. This notebook portrays how to do it." + ] + }, { "cell_type": "markdown", "id": "e515b4bd-a302-45a9-8464-56b67a73a46c", @@ -44,16 +54,22 @@ "cell_type": "code", "execution_count": 2, "id": "320224c9-06e2-428a-8614-8ed0d15eee82", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "# load data\n", "ds = xr.tutorial.load_dataset(\"air_temperature\")\n", + "\n", + "# Defining chunks that will be used for the reading/bitrounding/writing\n", "chunks = {\n", " \"lat\": 5,\n", " \"lon\": 10,\n", - "} # Defining chunks that will be used for the reading/bitrounding/writing\n", - "ds = ds.chunk(chunks) # Apply chunking" + "}\n", + "\n", + "# Apply chunking\n", + "ds = ds.chunk(chunks) " ] }, { @@ -443,17 +459,17 @@ " title: 4x daily NMC reanalysis (1948)\n", " description: Data is from NMC initialized reanalysis\\n(4x/day). These a...\n", " platform: Model\n", - " references: http://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanaly..." ], "text/plain": [ @@ -603,25 +619,28 @@ "id": "b9e8fe5a-2e4e-4dfd-8026-0991e9988668", "metadata": {}, "source": [ - "## Saving to file" + "## Saving to `NetCDF` file" ] }, { "cell_type": "code", "execution_count": 4, "id": "e011a900-5da2-40be-a292-d81a0cafcd6d", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/tmp/ipykernel_11221/350902741.py:1: SerializationWarning: saving variable air with floating point data as an integer dtype without any _FillValue to use for NaNs\n", + "/tmp/ipykernel_24883/1840452313.py:2: SerializationWarning: saving variable air with floating point data as an integer dtype without any _FillValue to use for NaNs\n", " ds.to_netcdf(\"0.air_original.nc\")\n" ] } ], "source": [ + "# Saving the dataset as NetCDF file\n", "ds.to_netcdf(\"0.air_original.nc\")" ] }, @@ -637,7 +656,9 @@ "cell_type": "code", "execution_count": 5, "id": "99d02f35-85fc-4a8d-94a0-880ac2ffbb72", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "name": "stderr", @@ -649,6 +670,7 @@ } ], "source": [ + "# Compress and save the dataset as NetCDF file\n", "ds.to_compressed_netcdf(\"1.air_compressed_all.nc\")" ] }, @@ -664,12 +686,14 @@ "cell_type": "code", "execution_count": 6, "id": "fdf077dd-6494-4c38-9461-5ea7ac370a01", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "eedef4b12a25419fba0f9d7348e619a2", + "model_id": "d7a346d6cbd14460890ae3be8ec11ff0", "version_major": 2, "version_minor": 0 }, @@ -682,8 +706,13 @@ } ], "source": [ + "# Get bitinformation of the dataset along the 'longitude' dimension\n", "info_per_bit = xb.get_bitinformation(ds, dim=\"lon\", implementation=\"python\")\n", + "\n", + "# Get the number of bits necessary to keep 99% of information in our dataset\n", "keepbits = xb.get_keepbits(info_per_bit, 0.99)\n", + "\n", + "# Round the dataset using the keepbits number\n", "ds_bitrounded = xb.xr_bitround(ds, keepbits)" ] }, @@ -696,6 +725,7 @@ }, "outputs": [], "source": [ + "# Compress and save the bitrounded dataset as NetCDF file\n", "ds_bitrounded.to_compressed_netcdf(\"2.air_bitrounded_compressed.nc\")" ] }, @@ -707,11 +737,19 @@ "## Zarr chunking and compressing" ] }, + { + "cell_type": "markdown", + "id": "e837c725-b7de-4418-a530-113583411884", + "metadata": {}, + "source": [] + }, { "cell_type": "code", "execution_count": 8, "id": "91343d2a-63ec-4d61-a369-cc99139297e4", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "def bitrounding(chunk, var=\"lat\"):\n", @@ -752,7 +790,9 @@ "cell_type": "code", "execution_count": 9, "id": "19032fcd-93bc-48b8-ba1f-beba9673491b", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "fn = \"air.zarr\" # Output filename\n", @@ -763,11 +803,14 @@ "cell_type": "code", "execution_count": 10, "id": "7221b47f-b8f4-4ebf-bc2b-cb61d12989be", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "dims = ds.air.dims\n", "len_dims = len(dims)\n", + "\n", "slices = slices_from_chunks(ds.air.chunks)" ] }, @@ -775,19 +818,28 @@ "cell_type": "code", "execution_count": 11, "id": "7c2ed18f-4dc8-4f5c-88ed-ae5ad41d1647", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "%%capture\n", - "for b, block in enumerate(ds.air.data.to_delayed().ravel()): # Loop over each chunk\n", - " # slices = {d:s for (d,s) in zip(dims, block.key[1:1+len_dims])}\n", + "\n", + "# Loop over each chunk\n", + "for b, block in enumerate(ds.air.data.to_delayed().ravel()): \n", + "\n", + " # Conversion of dask.delayed array to Dataset (as xbitinfo wants type xr.Dataset)\n", " ds_block = xr.Dataset(\n", " {\"air\": (dims, block.compute())}\n", - " ) # Conversion of dask.delayed array to Dataset (as xbitinfo wants type xr.Dataset)\n", - " rounded_ds = bitrounding(ds_block) # Apply bitrounding\n", + " ) \n", + " \n", + " # Apply bitrounding\n", + " rounded_ds = bitrounding(ds_block)\n", + " \n", + " # Write individual chunk to disk\n", " rounded_ds.to_zarr(\n", " fn, region={dims[d]: s for (d, s) in enumerate(slices[b])}\n", - " ) # Write individual chunk to disk" + " )" ] }, { @@ -798,6 +850,12 @@ "## Creating smaller datasets as chunks and compressing" ] }, + { + "cell_type": "markdown", + "id": "4265a4fa-b397-4552-ac3c-a1a358fffcd0", + "metadata": {}, + "source": [] + }, { "cell_type": "code", "execution_count": 12, @@ -810,45 +868,52 @@ "source": [ "%%capture\n", "\n", + "# Define a lambda function to ensure that the value is at least zero\n", + "# negative keepbits not yet supported\n", "at_least_zero = lambda x: max(x, 0)\n", "\n", - "chunk_long, chunk_lat = [10, 5] # for int division\n", - "var = \"lat\"\n", - "\n", + "# Create empty intermediate holders for plotting later\n", "dss = []\n", "dss_bitrounded = []\n", "dss_kbits = []\n", "\n", - "long_c = int(ds.lon.size / chunk_long)\n", - "lat_c = int(ds.lat.size / chunk_lat)\n", + "# How many chunks there are\n", + "long_c = int(ds.lon.size / chunks['lon'])\n", + "lat_c = int(ds.lat.size / chunks['lat'])\n", "\n", - "for i in range(long_c):\n", - " for j in range(lat_c):\n", - " temp_ds = ds.isel(\n", - " lon=slice(i * chunk_long, (i + 1) * chunk_long),\n", - " lat=slice(j * chunk_lat, (j + 1) * chunk_lat),\n", - " )\n", - " dss.append(temp_ds)\n", - " temp_info_pbit = xb.get_bitinformation(\n", - " temp_ds, dim=var, implementation=\"python\"\n", - " )\n", - " temp_keepbits = xb.get_keepbits(temp_info_pbit, 0.99)\n", - " # temp_keepbits = temp_keepbits.map(at_least_zero)\n", - " dss_kbits.append(temp_keepbits)\n", - " temp_ds_bitrounded = xb.xr_bitround(temp_ds, temp_keepbits)\n", - " dss_bitrounded.append(temp_ds_bitrounded)\n", + "for i, j in product(range(long_c), range(lat_c)):\n", + " \n", + " # Extract a chunk of the dataset\n", + " temp_ds = ds.isel(\n", + " lon=slice(i * chunks['lon'], (i + 1) * chunks['lon']),\n", + " lat=slice(j * chunks['lat'], (j + 1) * chunks['lat']),\n", + " )\n", + " dss.append(temp_ds)\n", + " \n", + " # Compress with bitrounding (See details above)\n", + " temp_info_pbit = xb.get_bitinformation(\n", + " temp_ds, dim='lat', implementation=\"python\"\n", + " )\n", + " temp_keepbits = xb.get_keepbits(temp_info_pbit, 0.99)\n", + " temp_keepbits = temp_keepbits.where(temp_keepbits['air'] > 0, 0)\n", + " dss_kbits.append(temp_keepbits)\n", + " temp_ds_bitrounded = xb.xr_bitround(temp_ds, temp_keepbits)\n", + " dss_bitrounded.append(temp_ds_bitrounded)\n", "\n", - " if i == 0 and j == 0:\n", - " MERGED_ds_bitr = temp_ds_bitrounded\n", - " else:\n", - " MERGED_ds_bitr = xr.merge([MERGED_ds_bitr, temp_ds_bitrounded])" + " # Merge the bitrounded datasets\n", + " if i == 0 and j == 0:\n", + " MERGED_ds_bitr = temp_ds_bitrounded\n", + " else:\n", + " MERGED_ds_bitr = xr.merge([MERGED_ds_bitr, temp_ds_bitrounded])" ] }, { "cell_type": "code", "execution_count": 13, "id": "7e1c4b35-4767-47e7-9ddf-357250b631b8", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "MERGED_ds_bitr.to_compressed_netcdf(\"3.air_chunked_bitr_compressed.nc\")" @@ -856,15 +921,100 @@ }, { "cell_type": "markdown", - "id": "d3b60c66-252d-48a6-af93-a00c9ca8f0ba", + "id": "5d628121-d5ec-4544-a47f-f47c86524b09", "metadata": {}, "source": [ - "## ALL" + "### Plot" ] }, { "cell_type": "code", "execution_count": 14, + "id": "d8835a3c-8af0-4423-baf4-84aa9a386f67", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import matplotlib as mpl\n", + "import matplotlib.patheffects as pe" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "c3b5f657-cddd-4476-82a3-c3c2c1a6e7b6", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA7gAAAJDCAYAAAAhG7g1AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd5gT1foH8O9MerbvsoWlLFWKgFRRROlgQQRUQOUqgqLSREB/AldBREBU6hUQREARQRCwcEFAAaUJAkqV3mEp20t65vfHXqLZbEkO2c2W7+d59oFM5p05SU5m5uTMeY+kKIoCIiIiIiIiolJODnQBiIiIiIiIiPyBDVwiIiIiIiIqE9jAJSIiIiIiojKBDVwiIiIiIiIqE9jAJSIiIiIiojKBDVwiIiIiIiIqE9jAJSIiIiIiojKBDVwiIiIiIiIqE9jAJSIiIiIiojKBDVwiIipztm7dCkmSXH/9+vXzKX7x4sVu8ePHjy+SchIREZF/sYFLRFSG/bORJkkSzp0757HOihUroFar3dZ76623ir+wpcjWrVsxfvx4198ff/wR6CIRERERAHWgC0BERIGzZs0a9O3bFw6Hw7Vs1KhRePfddwNYqsAzGAyIjY11PQ4ODnZ7fuvWrXjnnXdcj6tVq4bGjRsXV/GIiIgoH2zgEhGVUz/88AN69+4Nu93uWjZkyBB88MEHASxVydC7d2/07t070MUgIiIiH/EWZSKicmjjxo144oknYLPZXMtefPFFzJo1K4ClIiIiIro9bOASEZUzW7ZsQffu3WGxWFzLnn32WXzyySeQJCnPmNTUVEyZMgWtW7dGVFQUtFot4uLi8Oijj+Lbb78tcH8mkwlz5sxBx44dERMTA61WiwoVKqBjx45YsmQJnE6nR8y5c+fcxgS3bdsWDocDM2bMQKNGjWAwGBAdHY0+ffrg5MmTXr1um82GyZMno169etDr9YiPj8fAgQORmJjosW5+SaZuLf/n7ckA8Pzzz+eblOrs2bMYNmwYGjZsiJCQEGg0GkRHR6NevXp46qmnMHv2bNy4ccOr10BEREQF4y3KRETlyPbt2/Hyyy/DZDK5lvXu3RufffZZvo3bnTt34vHHH/doCF67dg0//PCD61bnzz//HFqt1m2dv/76C4899hhOnDjhtjwpKQk//fQTfvrpJyxevBhr1qxBeHh4vuW22Wzo1q0b/vvf/7qWmc1mrFixAv/973+xefNm3H333fnGm81mdOzYEb/88otr2dWrV7FgwQKsW7cOv/76K2rUqJFvvKiDBw/i/vvvR3p6utvymzdv4ubNm/jrr7+wfPlyVK9eHV27dvX7/omIiMob9uASEZUj/fv3R1ZWlutx9+7dsXTpUqhUqjzXP336NB555BG3xq0kSQgNDXVbb8WKFRgxYoTbsuTkZDz44IMejdvcsVu3bkXfvn0LLPfOnTtdjVuj0ej2XEZGBvr06ePWaM9t5cqV+OWXXyBJEvR6vdtzV65cwTPPPANFUQosA/B38qmgoCCP1xQbG+v6u5WUasKECW6NW1mWERERke/7TURERLeHDVwionLkn2NuH374YdcUQfl56623kJqa6nrcv39/JCUlIS0tDceOHUOdOnVcz82dOxfHjx93Pf7www9x/vx51+NHHnkEly5dQlpaGi5duoT77rvP9dy6deuwadOmAsvepk0bXLlyBZmZmdi8ebNbj+/Zs2exdOnSfGOdTid69eqF5ORkZGZm4quvvnLrbd69ezc2b95c4P6BnN7uxMREjBo1ym35zJkzkZiY6Pq79fyhQ4dc67Rv3x5JSUlITk6GxWLBhQsXsGzZMjz11FMeWZqJiIhIDBu4RETlVIUKFaDRaPJ93mKxYO3ata7H8fHxWLBgASIiIgAAdevWxbhx41zPO51OrFixwvV4+fLlrv/rdDp8+eWXqFSpEgCgUqVKHtmav/rqq3zLIssyFi1ahIoVK0KSJHTo0MGjx3jdunX5xoeGhuLTTz9FeHg4VCoV+vTp49FrXFC8qH82XFUqlWu8sUqlQpUqVfDUU09h2bJlaNu2rd/3TUREVB6xgUtEVE59/vnnGDRoUL7Pnzx50u223ytXrkClUrklU3r66afdYn7//XcAQGZmJs6ePetabrFYEB4e7hbbqlWrPGPzUqNGDVSvXt1tWfv27d0eHz16NN/4u+++GyEhIcLxoh599FHX/zdt2oSoqChUrVoVXbp0wciRI/H999+79aoTERHR7WEDl4ioHMndqJw3b55HT+gtaWlpPm//5s2btx2bl+joaI9lFSpUcHuckZFRZPGixowZg379+kGW/z7dXrx4ERs3bsS0adPQrVs31KpVC/v37/f7vomIiMojZlEmIipHFi1ahNdee80tG/H06dNhNBoxceJEt3XDwsLcHut0ugIzHQN/J5DKHatWqxEVFVVg7K1bn/OSV+M397Lcyav8GS9Kq9Vi0aJFePfdd/Hjjz/i4MGDOHXqFH7//Xdcv34dAHDhwgX0798ff/zxh9/3T0REVN6wgUtEVI5otVp888036Nq1K3766SfX8vfeew9GoxFjxoxxLatduzYMBoPrNuW4uDicOXPGrTcyt1tjTIODg1G9enXXbcoajQYnT570uE04r9i8nDlzBufPn0dCQoJr2c8//+y2Tr169fKN37NnDzIzM93GxPoSn1vu98DhcBS4fuXKlTFgwAC39du2bYvt27cDAP7880+kpKQU2MgnIiKiwvEWZSKickav1+O7777D/fff77Z87NixmDlzpuuxTqdDt27dXI/Pnz+PZ555BmfOnHEts1qtOHjwIKZPn46WLVvi119/dT3Xq1cv1/9NJhN69uzpllXY4XDg+PHjmDdvHjp27FhgFmSHw4H+/fsjMTERiqLgp59+wrRp09zWKWge2bS0NAwcOBBpaWlwOBxYsWKFx/58mYc2dw/19u3b82ygDxs2DOPGjcPu3buRnZ3tWn7+/HlcuXLFbV2OxSUiIrp9kuLNxH9ERFQqSZLk9vjs2bOoVq0agJwxp506dcJvv/3mts78+fPx4osvAgBOnTqF5s2be4ypNRqN0Ol0SE9Pd+u93LJliysjcFJSEpo2bYoLFy64xep0OgQHByMtLQ12u921fNGiRejXrx8A4Ny5c25JpWRZdjUgjUajW2MRAKpXr44jR47AYDAAyJlbt127dnm+H3q93mPO3HvuuQc7d+50vV+LFy/G888/73p+3LhxGD9+vNvrzJ2kymAwuG5z3r59O2rVqoXu3bvj22+/de371vO538/q1au7/XBAREREYtiDS0RUToWEhGDDhg1o0qSJ2/KXX34ZX3zxBQCgVq1a+O9//4v4+Hi3dbKzs5GSkuLWuFWpVK4GJgBERUXhxx9/RN26dd1iLRYLkpKS3Bq3AAqcC/a+++7Dk08+6dp37texfPlyt33n9thjj6Fx48ZQFMWjcRsfH4+lS5d6/BhQkAceeAANGjRwW2YymXDt2jVcu3bN47UBgKIoSEtL82jc6vV6zJ071+t9ExERUf7YwCUiKsfCw8OxceNGt8aa0+nE888/j1WrVgHIybx87NgxTJs2De3atUN0dDTUajUMBgNq1KiB7t27Y/bs2bhw4QJatmzptv26deviwIEDWLBgAR566CHExcVBq9VCr9ejatWqeOihhzB16lScOnUKTzzxRL7llGUZy5cvx+zZs9GoUSPo9XpERUWhd+/e2LdvH+6+++5CX+eOHTswZswY1KpVCzqdDnFxcXjxxRexb98+1KxZ06f3TaVSYdOmTRgwYAAqV64MtTrvlBZTpkzBtGnT8Nhjj6FOnTqIjIyESqVCcHAwGjRogCFDhuDgwYPo0qWLT/snIiKivPEWZSIiKnFy36Lcpk0bbN26NXAFIiIiolKBPbhERERERERUJrCBS0RERERERGUCG7hERERERERUJrCBS0RERERERGUCk0wRERERERFRmcAeXCIiIiIiIioT2MAlIiIiIiKiMoENXCIiIiIiIioT2MAlIiIiIiKiMoENXCIiIiIiIioT2MAlIiIiIiKiMoENXCIiIiIiIioT2MAlIiIiIiKiMoENXCIiIiIiIioT2MAlIiIiIiKiMoENXCKiYrZz506MHz8eqampHs+1bdsWbdu2LfYyFYcTJ05g1KhRaNasGcLDwxEZGYn77rsPq1atynP969evo1+/fqhQoQKMRiPuvfde/PTTTx7r/fDDD3j22WfRsGFDaDQaSJKU5/YuXryIHj16oEaNGggKCkJYWBiaNGmC//znP7Db7V6/Dn+XqzCzZ89G3bp1odPpUL16dbzzzjuw2Wxu61y6dAnDhw9HmzZtEB4eDkmSsHjxYqH9ERERlWZs4BIRFbOdO3finXfeybOBO2fOHMyZM6f4C1UMNm7ciHXr1uHxxx/HypUr8eWXX6J27dp48sknMWHCBLd1LRYLOnTogJ9++gkzZ87Et99+i9jYWDz44IPYtm2b27pr1qzB7t27Ub9+fdx111357j8rKwuhoaF466238N1332H58uVo3bo1hg4dipdfftmr11AU5SrIe++9h1dffRU9e/bEjz/+iEGDBmHSpEkYPHiw23qnTp3Cl19+Ca1Wi4cfflhoX0RERGWCQkRExeqDDz5QAChnz54NdFGK1Y0bNxSn0+mx/JFHHlGMRqNiNptdyz7++GMFgLJz507XMpvNptSvX1+5++673eIdDofr/4MHD1Z8PbX16tVLUavVbvvPT3GW6+bNm4per1cGDhzotvy9995TJElSjhw5kue+9u7dqwBQFi1a5NP+iIiIygL24BIRFaPx48fj9ddfBwBUr14dkiRBkiRs3boVgOctyufOnYMkSfjggw/w/vvvo1q1ajAYDGjbti1OnDgBm82GN998E/Hx8QgLC0OPHj1w/fp1j/2uWLEC9957L4KCghAcHIwuXbrgwIEDxfGSXSpUqJDnbbp33303srOzkZyc7Fq2Zs0a1KlTB/fee69rmVqtRt++fbFnzx5cvnzZtVyWb+9UFh0dDVmWoVKpCl23OMu1YcMGmM1mPP/8827Ln3/+eSiKgrVr1/ptX0RERGUFz4hERMXohRdewNChQwEAq1evxq5du7Br1y40bdq0wLiPP/4YO3bswMcff4xPP/0Uf/31Fx599FEMGDAAN27cwGeffYapU6di8+bNeOGFF9xiJ02ahKeeegr169fH119/jS+++AIZGRm4//77cfTo0ULLbLfbvfpTFEXoPdmyZQuio6MRExPjWnb48GE0atTIY91by44cOSK0LwBQFAV2ux0pKSlYsWIFFi9ejJEjR0KtVhcaW5TlymtfANCwYUO35RUrVkSFChVczxMREdHfCj+bExGR31SuXBlVq1YFADRp0gTVqlXzKi48PBxr16519dTdvHkTw4cPR926dfHtt9+61vvrr78wY8YMpKenIzQ0FBcvXsS4ceMwZMgQzJo1y7Vep06dULt2bbzzzjtYsWJFvvs9d+4cqlev7lUZt2zZ4nOCrE8//RRbt27FzJkz3XpQk5KSEBkZ6bH+rWVJSUk+7eef3n//fYwePRoAIEkSxowZg4kTJ3oVW5TlymtfOp0OQUFBee7Pn/siIiIqK9jAJSIqBR5++GG321Dr1asHAHjkkUfc1ru1/MKFC2jQoAF+/PFH2O12PPvss26ZgvV6Pdq0aYMtW7YUuN/4+Hjs3bvXqzLWqVPHq/VuWb9+PQYPHownnnjC1av9TwVlHRbNSAwA/fr1Q8eOHZGcnIyff/4ZH3zwAdLS0jB79mwAOT28DofDLeafvbv+LlfuDM4qlcq1naJ6D4iIiMoqNnCJiEqB3L2GWq22wOVmsxkAcO3aNQBAixYt8txuYWM3tVotGjdu7FUZvRnDesuPP/6Inj17olOnTvjyyy89GmtRUVF59lDeGqebVy+qt+Li4hAXFwcA6Ny5MyIiIvDmm2+if//+aNKkCZYsWeIx7vXW7ddFUS6NRuP2eNGiRejXrx+ioqJgNpuRnZ0No9Hosb9mzZr5vC8iIqKyjg1cIqIyrEKFCgCAVatWISEhwef4orhF+ccff0T37t3Rpk0bfPPNN65G+T81bNgQhw4d8lh+a1mDBg28KpM37r77bgA58/Q2adIEjz76aL691kVRrtz7uvV+3xp7e+jQIbRs2dL1fGJiIm7evOnX94CIiKisYAOXiKiY6XQ6AIDJZCryfXXp0gVqtRqnT5/G448/7nO8v29R3rhxI7p3747WrVtj7dq1rvcitx49emDQoEH47bffXI07u92OpUuXomXLloiPj/f+RRTi1m3atWrVApDTSxsVFVVs5WrevHmeyx988EHo9XosXrzYrYG7ePFiSJKE7t27+7wvIiKiso4NXCKiYnarZ27mzJl47rnnoNFoUKdOHYSEhPh9X9WqVcOECRMwduxYnDlzBg8++CAiIiJw7do17NmzB0FBQXjnnXfyjddqtfk2wHy1fft2dO/eHXFxcRgzZgz++OMPt+fr16+P0NBQAED//v3x8ccf48knn8SUKVMQExODOXPm4Pjx49i8ebNb3Pnz512N8NOnTwPI6bG+9fpvlX/cuHG4du0aHnjgAVSqVAmpqanYsGEDFixYgCeffNKrW36Lolz5iYyMxL///W+89dZbiIyMROfOnbF3716MHz8eL7zwAurXr++2/q1tnzlzBgDw+++/Izg4GADwxBNPFPraiIiIyoSAzsJLRFROjR49WomPj1dkWVYAKFu2bFEURVHatGmjtGnTxrXe2bNnFQDKBx984Ba/ZcsWBYCycuVKt+WLFi1SACh79+51W7527VqlXbt2SmhoqKLT6ZSEhATliSeeUDZv3lwkry8v48aNUwDk+3frPbglMTFRefbZZ5XIyEhFr9cr99xzj7Jp0yaP7d56zXn9Pffcc671vvvuO6Vjx45KbGysolarleDgYOXuu+9WZs2apdhsNq9fh7/LVZiZM2cqd9xxh6LVapWqVasq48aNU6xWq8d6Bb23RERE5YWkKIITFxIRERERERGVIAWnzyQiIiIiIiIqJdjAJSIiIiIiojKBDVwiIiIiIiIqE9jAJSIiIiIiojKBDVwiIiIiIiIqE9jAJSIiIiIiojJBHegCFDWn04krV64gJCQEkiQFujhEREREROWaoijIyMhAfHw8ZLn09beZzWZYrdYi2bZW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J4oPEXs/swWnUzHbxbLUquTiHRspWk5NMZfToXf/PJ2yGrkvxYN0asha91Nt7vfTKauRezRhXnFGjdgxWzRONKtxmkWsIZvXcdZbonU0RCd2I5veKvbeqPXBbo8dDs9jsUMXBIfO6LZM5ch1jsjrO6LRQtEUPFemtxRN4evkRdIJXo9oxI6FSBXLD6FkpgrFOU1ijW1FcLywI9e1iD2P74g96Srsua5FHGaxawrROV1Vet+ngnFkc2hCWTF69GiMHDkSEyZMQEhICL755hvExMTgmWeewYMPPhiQMnl9ZJ81a5bXDdZVq1YJF4iIiIiIiKhEC3AW5ZLi2LFj+OqrrwAAarUaJpMJwcHBmDBhAh577DG88sorxV4mr36q2bJlCyIjI73e6Pr161GpUiXhQhEREREREZVYsvx3I9dvf35OWlUMgoKCYLFYAADx8fE4ffq067mbN28GpExe9eC2adPGp422bt1aqDBERERERERUOtxzzz3YsWMH6tevj0ceeQQjR47EoUOHsHr1atxzzz0BKZPQ4BOn04lTp07h+vXrcDrdx4E88MADfikYERERERFRSSTJsvC45YK2WdpMmzYNmZmZAIDx48cjMzMTK1asQK1atTB9+vSAlMnnBu7u3bvx9NNP4/z58x7zL0mSBIeD8x8SERERERGVdTVq1HD932g0Ys6cOQEsTQ6fG7gvv/wymjdvjnXr1qFixYqQpOLNWklERERERBRQUhEkmZJKX5IpEb7kdgJyOlH379+PhIQEr9b3uYF78uRJrFq1CrVq1fI1lIiIiIiIiMqx1NRUzJgxA2FhYYWuqygKBg0a5NNdwj43cFu2bIlTp06xgUtEREREROUTpwm6LX369PF6CtqhQ4f6tG2vGrgHDx5028HIkSORmJiIhg0bQqNxnw28UaNGPhWAiIiIiIiIyofcSYoLk5GR4dP6XjVwGzduDEmS3JJK9e/f3/X/W88xyRQREREREZV1zKLszmq14uzZs6hZsybU6sKbmJcvX0alSpUKXOfLL7/EM88843NZvGrgnj171ucNExERERERlUm8RRkAkJ2djaFDh2LJkiUAgBMnTqBGjRoYNmwY4uPj8eabb+YZ16lTJ+zYsQMRERF5Pr9s2TI8//zzQg1cr34mSEhIcP2dP38elSpVcluWkJCASpUq4fz58z4XgIiIiIiIiEqf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48ePIzo6Ot+4+fPnw2LJ+VFOURRMmjQJERERiIuLQ3h4OEaMGCE8nZDPDdyNGzfi/fff97jHu3bt2j5PE0RERERERFTqlIBblPP669evn9t6AwcORHZ2NsLCwvLcjl6vx+zZs5GUlITs7Gx8//33HlmcC/LYY49hwoQJsP3vDg5JknDhwgW8+eabePzxx/ONe+WVV1xTG82fPx+TJk3CW2+9hV9//RXvv/8+PvvsM8yZM8frcvyTz7coZ2VlufXc3nLz5s1CM3wRERERERFR2fDhhx/i4YcfRkxMDEwmE9q0aYPExETce++9eO+99/KNU5S/h88sXLgQ7777rmv62VatWrka3kOGDPG5TD43cB944AF8/vnnePfddwHktNKdTic++OADtGvXzucCEBERERERlSqSlPPn722WMqGhodi+fTt+/vln7N+/H06nE02bNvVqDO+tqYjOnj2LDh06uD3Xvn17V4PXVz43cD/44AO0bdsWv//+O6xWK9544w0cOXIEycnJ2LFjh1AhiIiIiIiIqHRq37492rdv71PMhg0bXEmxbs3Mc4vJZIIsmIDX56j69evj4MGDaNGiBTp16oSsrCz07NkTBw4cQM2aNYUKQUREREREVGoEeAxuSfLTTz+ha9euqFmzJmrVqoWuXbti8+bNhcY999xz6N69Oy5duoSffvrJ7bldu3YJty197sEFcubBnTBhgtAOiYiIiIiIqPT7z3/+g9deew1PPPEEXn31VQDA7t278fDDD2PatGn5jqEtLENyXFwcJk+eLFQmoQbur7/+ik8++QRnzpzBypUrUalSJXzxxReoXr06WrduLVQQIiIiIiKi0uDW1D7+3mZpM3nyZEyfPt2tITts2DDcd999eO+994SSRAFA165dhcvk87v4zTffoEuXLjAYDNi/f79r/qKMjAxMmjRJuCBERERERERUeqSnp+PBBx/0WN65c+c858fN7cyZM/j888/x/vvv48MPP8Q333zjVVxBfG7gTpw4EfPmzcOCBQug0Whcy1u1aoX9+/ffVmGIiIiIiIhKPEkGZD//lcIe3G7dumHNmjUey7/99ls8+uij+cZlZWXhySefRK1atdCvXz+MGTMGH330EXr37o1KlSrh448/Fi6Tz7coHz9+HA888IDH8tDQUKSmpgoXhIiIiIiIqFQoiqRQpbCBW69ePbz3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d3Y4JoeTor700kuYMGECfvzxRwgh8Pfff+Odd97Bq6++yqGBiIiIiIiIKog333wT2dnZqF27Nv7zn/9g6NChqFu3LnJycvDmm28WW/6XX35B37594eXlhQMHDkCvz01SmZGRgXfffVcqJoev4D755JMwmUyYPn06srOzMWzYMFSrVg2ffPIJHn/8cakgSkPOjdtQe3rk/i0UAALzvJ8CKGx/NjFLPkem0kglp4bKU27AciF5NUWfkilXLlUuG7NsnAql/OA0CpXK5n+DQgn42s5jSM+CMs8t3KY8daiHEvC0bTP62xlQIE85ndyYfMIst21kt6mQHY5D7SG3Po233PoUcnWvUZbuT6BZRtt60Bvz10u20QKz2na6l0GuHgKUcvsYhU7uu+uplas/rZdcOS+13ED3Qm5gAPio5NqLKj3R5n9lTv6xopUZSVCZPG2nZdveYqnU5c84rcxKhtKc5/uml3sMSOhzpMrBw0uqmOweW5FxS7IkoEgrOFNncVTFz+JU5hzbOrQUsA+wnD0Is9Y2MpPBtm0ZjfnLGS+dhEGT5xNJjhMrDHLjnguj3C3nFsn1ZVy+IVUuU7KcLjVbqpwpR+580qizLadXa/LNk5Oqh8Vku92VSrl9qEor940wSxzLykOuBiGE0xMqlsUEjXPmzMH48ePh7W17zM/OzsacOXPwxhtvFFn+7bffxhdffIFRo0YhJibGOr1jx452dZALInU28NRTT+Gpp57CrVu3YLFYEBISIrVyIiIiIiKisoa3KOe6k+04r8OHDyMoKKjY8qdOnULXrl3zTff390dqaqpUTHI/d/+rcuXK91KciIiIiIiIypjAwEAoFAooFApERUXZdHLNZjMyMzMxfvz4YpcTFhaGs2fPIjIy0mb6zp07Ubt2banY7OrgtmjRotixiu44cOCAVCBERERERERlRRm84Oo0H3/8MYQQGDt2LObMmYOAgADre1qtFpGRkejQoUOxy3nmmWfwwgsv4Ntvv4VCocD169exe/duTJs2rdjbmwtjVwd30KBB1r91Oh0WLVqEhg0bWoPes2cP/vnnHzz33HNSQRAREREREVHZMHr0aAC5I+x06tQJarXcjcHTp09HWloaevToAZ1Oh65du8LDwwPTpk3DxIkTpZZpVySzZs2y/v1///d/eP755/HWW2/lm+fKlStSQRAREREREZUV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yb9+Q5YBMNo+HiAkrwaV9vmI/L5C7jshW38WybuaVGq5tq3S2maxLShuYRb5ppvh/le6zeXgajyv4LqvCtPBJSIiIiIicgZLCXRwLezgOgWzKBMREREREVG5wCu4REREREREDjCLErhFmck9nYJXcImIiIiIiKhccGkH9/PPP0fTpk3h7+8Pf39/dOjQAevWrbO+L4TA7NmzER4eDi8vL3Tv3h3//POPCyMmIiIiIqKK7k6SKWe/6N65tINbvXp1zJs3D/v378f+/ftx//33Y+DAgdZO7Pz58/HRRx/hs88+w759+xAaGorevXsjI4Nj5xEREREREZEtl3ZwH374YTz00EOIiopCVFQU3nnnHfj6+mLPnj0QQuDjjz/GzJkzMWTIEDRu3BhLly5FdnY2li9f7sqwiYiIiIioAuMVXPflNs/gms1mxMTEICsrCx06dMCFCxeQmJiIPn36WOfx8PBAt27dsGvXrkKXo9frkZ6ebvMiIiIiIiKi8s/lHdyjR4/C19cXHh4eGD9+PFatWoWGDRsiMTERAFC1alWb+atWrWp9ryBz585FQECA9RUREVGi8RMRERERUcVisogSedG9c3kHt169ejh06BD27NmDZ599FqNHj8bx48et7ysUCpv5hRD5pt1txowZSEtLs76uXLlSYrETEREREVHFw1uU3ZfLx8HVarWoU6cOAKB169bYt28fPvnkE7z88ssAgMTERISFhVnnT0pKyndV924eHh7w8PAo2aCJiIiIiIjI7bj8Cm5eQgjo9XrUqlULoaGh2LRpk/U9g8GAuLg4dOzY0YUREhERERFRRWYpgau3Fl7BdQqXXsF99dVX8eCDDyIiIgIZGRmIiYnBtm3bsH79eigUCkyePBnvvvsu6tati7p16+Ldd9+Ft7c3hg0b5sqwiYiIiIiIyA25tIN748YNjBw5EgkJCQgICEDTpk2xfv169O7dGwAwffp05OTk4LnnnkNKSgratWuHjRs3ws/Pz5VhExERERFRBWYWAmbh3Cuuzl5eReXSDu7ixYuLfF+hUGD27NmYPXt26QREREREREREZZbLk0wRERERERGVJSWR9ZhZlJ3D7ZJMEREREREREcngFVwiIiIiIiIH8Aqu+6owHVyT3gSTMrfRmET+C9cmnQkKvclmmjBbpNZlMcs1TrNJbn2yD6RrlHIX8JUqhVQ5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gMLBuwiEEA6XcSe8gktEREREROQAYREl8gIAf39/m1dRHdxJkyZhzZo12Lp1q03m4x07diApKQk1atSAWq2GWq3GpUuXMHXqVERGRgIAQkNDYTAYkJKSYrPMpKQkVK1a1fkbrZSwg0tERERERFSGCCEwceJExMbGYsuWLahVq5bN+yNHjsSRI0dw6NAh6ys8PBwvvfQSNmzYAABo1aoVNBoNNm3aZC2XkJCAY8eOoWPHjqX6eZyJtygTERERERE54q4rrs5cpr0mTJiA5cuXY/Xq1fDz87M+MxsQEAAvLy8EBwcjODjYpoxGo0FoaKh1rNyAgACMGzcOU6dORXBwMIKCgjBt2jQ0adLEmlW5LGIHl4iIiIiIqAz5/PPPAQDdu3e3mR4dHY0xY8bYvZwFCxZArVZj6NChyMnJQc+ePbFkyZIyOwYuUIE6uClGM3T/PittVuVP759iNEOVJ+2/bFp3r1LO6y47dIRKK1dOWYby1pvzDK5nLmA4HrPJkm++fPMUMOyM2WDONxaiUnLMArPkkBOyw3+U5FAVBSntOGXLWfKMVWHQ5F+OIcsA5BnuJysp2+Z/o4c2X7mM65nQ6G2H8LhxKlkqzmrNs6TK+YX5SpXzr+YnVc5wJb34mQqQJjsopiTZscTzjl1uNuWPO8tkgSrP9LzN06zOXy7dZIEq7/5LyO0nZD+fr+RwTbK7F3O23Oe7F7LjvcrKV/fK/CeQtw1mqPRFD+llVuVvM6lGS77zmLxt1F7SdShdTjZOubYtW++yn0/ydCvfd7eg9ZtF/mEtVU4em7VYBsf32WbJMZPdiUUI6SE+i1qmvWTG4L148WK+aZ6enli4cCEWLlzo8PLcVYXp4BIRERERETmDEM6/RVmm00r5MckUERERERERlQu8gktEREREROQAUQJJppyetKqC4hVcIiIiIiIiKhd4BZeIiIiIiMgBFgugcPIV13KQe8st8AouERERERERlQu8gktEREREROQAIYTTsx4zi7Jz8AouERERERERlQu8gktEREREROQAYcl9OXuZdO/YwSUiIiIiInKAxSJKIMkUb1F2Bt6iTEREREREROUCr+ASERERERE5QFgEhJOvuDp7eRUVr+ASERERERFRucAruERERERERA7gFVz3xSu4REREREREVC7wCi4REREREZEDLEJAIZycRdnJy6uo2MEt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mkWu257PVadOHesV2hMnTuCZZ55BYmKidRl79+7Fu+++i/379+Py5cuIjY3FzZs3rR3gyMhIHDlyBKdOncKtW7cq9NBIRERUjrno2V8ionJr9OjRAkC+1+jRo4UQQmzbtk20adNGaLVaERoaKl5++WVhNBqt5bt162ZNqnTHwIEDreWFEOLEiROiU6dOQqvVivr164u1a9cKAGL9+vVCiPxJpoQQ4s033xShoaFCoVBYl5WWliaGDh0q/P39RUREhFiyZIlNkikhhDh16pTo3Lmz0Gq1IioqSqxfv94myZQQQiQkJIhRo0aJypUrCw8PD1G7dm3x1FNPibS0tAK3UWFJrzIyMsSIESOEt7e3qFq1qpg/f36+7WEwGMQbb7whIiMjhUajEaGhoWLw4MHiyJEj1nm++uorUa1aNeHl5SUGDRok3n77bREaGmp9f9asWaJZs2b54kpPTxeTJk0S4eHhQqPRiIiICDF8+HBx+fJl6zzr168XHTt2FF5eXsLf31+0bdtWfPXVV0IIIVatWiXatWsn/P39hY+Pj2jfvr3YvHlzgdsgr+I+V3Jyshg4cKDw9fUVISEh4rXXXhOjRo2yJo46fvy46Nu3r6hSpYrw8PAQUVFRYuHChdblJyUlid69ewtfX18BQGzdutX6HpNMERFReaEQwo6HnoiIyK399ddf6Ny5M86ePYv77rvP1eEUa9u2bejRowdSUlJsxqctKU899RROnjyJHTt2lPi6yqIlS5Zg8uTJSE1NdXUoRERE94TP4BIRlUGrVq2Cr68v6tati7Nnz+KFF15Ap06dykTn9m7Vq1fHww8/jBUrVjh1uR988AF69+4NHx8frFu3DkuXLsWiRYucuo7ywtfXFyaTCZ6enq4OhYiI6J6xg0tEVAZlZGRg+vTpuHLlCipXroxevXrhww8/dHVYdmvXrh3OnDkD4H9Zgp3p77//xvz585GRkYHatWvj008/xf/93/85fT322rFjBx588MFC37/zTLQrHDp0CADyZW8mIiIqi3iLMhERUQnLycnBtWvXCn2/qKzMREREZD92cImIiIiIiKhc4DBBREREREREVC6wg0tERERERETlAju4REREREREVC6wg0tERERERETlAju4REREREREVC6wg0tERERERETlAju4REREREREVC6wg0tERERERETlwv8DK3rp53zGuLYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# # Create a figure and axis and plot the air temperature\n", + "fig, ax = plt.subplots(figsize=(12, 6))\n", + "ds['air'].isel(time=0).plot(ax=ax, cmap='RdBu_r')\n", + "\n", + "for i in range(len(dss_bitrounded)):\n", + " \n", + " # Get chunk limits\n", + " lats = dss[i].lat\n", + " longs = dss[i].lon \n", + " x = float(min(longs[0], longs[-1]))\n", + " y = float(min(lats[0], lats[-1]))\n", + " w = float(abs(longs[0] - longs[-1]))\n", + " h = float(abs(lats[0] - lats[-1]))\n", + " \n", + " # Draw rectangle\n", + " rect = mpl.patches.Rectangle((x, y), width = w, height = h,\n", + " facecolor = \"none\", edgecolor = \"#E5E4E2\",\n", + " path_effects=[pe.withStroke(linewidth=3, foreground=\"gray\")])\n", + " ax.add_patch(rect)\n", + " \n", + " # Annotate number of keepbits\n", + " rx, ry = rect.get_xy()\n", + " cx = rx + rect.get_width()/2.0\n", + " cy = ry + rect.get_height()/2.0\n", + " ax.annotate(f\"{int(dss_kbits[i].air):2}\",\n", + " (cx, cy), color='k', weight='normal', fontsize=14, ha='right', \n", + " va='center', path_effects=[pe.withStroke(linewidth=2, foreground='w')])\n", + "\n", + "fig.text(.39, .94, f'Keepbits ', weight='bold', fontsize=16)\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "d3b60c66-252d-48a6-af93-a00c9ca8f0ba", + "metadata": { + "tags": [] + }, + "source": [ + "## Summary" + ] + }, + { + "cell_type": "markdown", + "id": "b28089ea-22f9-45c6-abc9-b65bd946ac66", + "metadata": {}, + "source": [ + "Below are the file sizes resulting from the various compression techniques outlined above." + ] + }, + { + "cell_type": "code", + "execution_count": 16, "id": "998581b5-6ad9-4f6f-9c61-d0bf1486ec7f", "metadata": { "tags": [] @@ -885,11 +1035,29 @@ "source": [ "!du -hs *.nc *.zarr" ] + }, + { + "cell_type": "markdown", + "id": "15c6975d-6909-4e2c-9395-0a64d39ed44f", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "fed34f3f-2bee-45d3-9bdf-1237b77cf1b8", + "metadata": {}, + "source": [ + "In this experiment, the sizes are minimized when applying bitrounding and compression to the dataset chunks. \n", + "\n", + "However, it's important to note that this outcome may not be universally applicable, check this for your dataset." + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, From 70b01af957fe5baf249db797de1ed0e0c3fe953e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:51:04 +0000 Subject: [PATCH 35/53] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/chunking.ipynb | 73 +++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index 0feb60a1..794b4f2b 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -69,7 +69,7 @@ "}\n", "\n", "# Apply chunking\n", - "ds = ds.chunk(chunks) " + "ds = ds.chunk(chunks)" ] }, { @@ -826,20 +826,15 @@ "%%capture\n", "\n", "# Loop over each chunk\n", - "for b, block in enumerate(ds.air.data.to_delayed().ravel()): \n", - "\n", + "for b, block in enumerate(ds.air.data.to_delayed().ravel()):\n", " # Conversion of dask.delayed array to Dataset (as xbitinfo wants type xr.Dataset)\n", - " ds_block = xr.Dataset(\n", - " {\"air\": (dims, block.compute())}\n", - " ) \n", - " \n", + " ds_block = xr.Dataset({\"air\": (dims, block.compute())})\n", + "\n", " # Apply bitrounding\n", " rounded_ds = bitrounding(ds_block)\n", - " \n", + "\n", " # Write individual chunk to disk\n", - " rounded_ds.to_zarr(\n", - " fn, region={dims[d]: s for (d, s) in enumerate(slices[b])}\n", - " )" + " rounded_ds.to_zarr(fn, region={dims[d]: s for (d, s) in enumerate(slices[b])})" ] }, { @@ -878,24 +873,21 @@ "dss_kbits = []\n", "\n", "# How many chunks there are\n", - "long_c = int(ds.lon.size / chunks['lon'])\n", - "lat_c = int(ds.lat.size / chunks['lat'])\n", + "long_c = int(ds.lon.size / chunks[\"lon\"])\n", + "lat_c = int(ds.lat.size / chunks[\"lat\"])\n", "\n", "for i, j in product(range(long_c), range(lat_c)):\n", - " \n", " # Extract a chunk of the dataset\n", " temp_ds = ds.isel(\n", - " lon=slice(i * chunks['lon'], (i + 1) * chunks['lon']),\n", - " lat=slice(j * chunks['lat'], (j + 1) * chunks['lat']),\n", + " lon=slice(i * chunks[\"lon\"], (i + 1) * chunks[\"lon\"]),\n", + " lat=slice(j * chunks[\"lat\"], (j + 1) * chunks[\"lat\"]),\n", " )\n", " dss.append(temp_ds)\n", - " \n", + "\n", " # Compress with bitrounding (See details above)\n", - " temp_info_pbit = xb.get_bitinformation(\n", - " temp_ds, dim='lat', implementation=\"python\"\n", - " )\n", + " temp_info_pbit = xb.get_bitinformation(temp_ds, dim=\"lat\", implementation=\"python\")\n", " temp_keepbits = xb.get_keepbits(temp_info_pbit, 0.99)\n", - " temp_keepbits = temp_keepbits.where(temp_keepbits['air'] > 0, 0)\n", + " temp_keepbits = temp_keepbits.where(temp_keepbits[\"air\"] > 0, 0)\n", " dss_kbits.append(temp_keepbits)\n", " temp_ds_bitrounded = xb.xr_bitround(temp_ds, temp_keepbits)\n", " dss_bitrounded.append(temp_ds_bitrounded)\n", @@ -963,33 +955,44 @@ "source": [ "# # Create a figure and axis and plot the air temperature\n", "fig, ax = plt.subplots(figsize=(12, 6))\n", - "ds['air'].isel(time=0).plot(ax=ax, cmap='RdBu_r')\n", + "ds[\"air\"].isel(time=0).plot(ax=ax, cmap=\"RdBu_r\")\n", "\n", "for i in range(len(dss_bitrounded)):\n", - " \n", " # Get chunk limits\n", " lats = dss[i].lat\n", - " longs = dss[i].lon \n", + " longs = dss[i].lon\n", " x = float(min(longs[0], longs[-1]))\n", " y = float(min(lats[0], lats[-1]))\n", " w = float(abs(longs[0] - longs[-1]))\n", " h = float(abs(lats[0] - lats[-1]))\n", - " \n", + "\n", " # Draw rectangle\n", - " rect = mpl.patches.Rectangle((x, y), width = w, height = h,\n", - " facecolor = \"none\", edgecolor = \"#E5E4E2\",\n", - " path_effects=[pe.withStroke(linewidth=3, foreground=\"gray\")])\n", + " rect = mpl.patches.Rectangle(\n", + " (x, y),\n", + " width=w,\n", + " height=h,\n", + " facecolor=\"none\",\n", + " edgecolor=\"#E5E4E2\",\n", + " path_effects=[pe.withStroke(linewidth=3, foreground=\"gray\")],\n", + " )\n", " ax.add_patch(rect)\n", - " \n", + "\n", " # Annotate number of keepbits\n", " rx, ry = rect.get_xy()\n", - " cx = rx + rect.get_width()/2.0\n", - " cy = ry + rect.get_height()/2.0\n", - " ax.annotate(f\"{int(dss_kbits[i].air):2}\",\n", - " (cx, cy), color='k', weight='normal', fontsize=14, ha='right', \n", - " va='center', path_effects=[pe.withStroke(linewidth=2, foreground='w')])\n", + " cx = rx + rect.get_width() / 2.0\n", + " cy = ry + rect.get_height() / 2.0\n", + " ax.annotate(\n", + " f\"{int(dss_kbits[i].air):2}\",\n", + " (cx, cy),\n", + " color=\"k\",\n", + " weight=\"normal\",\n", + " fontsize=14,\n", + " ha=\"right\",\n", + " va=\"center\",\n", + " path_effects=[pe.withStroke(linewidth=2, foreground=\"w\")],\n", + " )\n", "\n", - "fig.text(.39, .94, f'Keepbits ', weight='bold', fontsize=16)\n", + "fig.text(0.39, 0.94, f\"Keepbits \", weight=\"bold\", fontsize=16)\n", "\n", "plt.show()" ] From 654f6ad08f0fc4d386e7e8beadf9d33dde4eb5ce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 17 Dec 2023 01:01:46 +0000 Subject: [PATCH 36/53] Update GitHub Action Versions --- .github/workflows/benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index ce5a9cf6..0db7840a 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -74,7 +74,7 @@ jobs: cp benchmarks.log .asv/results/ working-directory: ${{ env.ASV_DIR }} - - uses: actions/upload-artifact@v3.1.3 + - uses: actions/upload-artifact@v4.0.0 if: always() with: name: asv-benchmark-results-${{ runner.os }} From c06fcfb8b0cd2bf025d28ae5214fa3987825de1c Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:52:01 -0800 Subject: [PATCH 37/53] reordering of cells; add more description --- docs/chunking.ipynb | 236 ++++++++++++++++++++++---------------------- 1 file changed, 120 insertions(+), 116 deletions(-) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index 794b4f2b..56ad538e 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -15,7 +15,9 @@ "source": [ "Geospatial data can vary in its information density from one part of the world to another. A dataset containing streets will be very dense in cities but contains little information in remote places like the Alps or even the ocean. The same is also true for datasets about the ocean or the atmosphere.\n", "\n", - "Currently in the bitinformation framework, to preserve all real information, the maximum information content calculated by `xbitinfo` needs to be used for the entire dataset. However, bitinformation can also be calculated on subsets, such that the ‘boring’ parts can therefore be more efficiently compressed. This notebook portrays how to do it." + "By default the number of bits that need to be kept (`keepbits`) to preserve the requested amount of information is determined based on the entire dataset. This approach doesn't always result in the best compression rates as it preserves too many keepbits in regions with anomalously low information density. The following steps show how the `keepbits` can be retrieved and applied on subsets. In this case, subsets are defined as dataset chunks.\n", + "\n", + "This work is a result of the ECMWF Code4Earth 2023. Please have a look at the [presentation of this project](https://youtu.be/IOi4XvECpsQ?si=hwZkppNRa-J2XVZ9) for additional details." ] }, { @@ -614,121 +616,6 @@ "ds" ] }, - { - "cell_type": "markdown", - "id": "b9e8fe5a-2e4e-4dfd-8026-0991e9988668", - "metadata": {}, - "source": [ - "## Saving to `NetCDF` file" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "e011a900-5da2-40be-a292-d81a0cafcd6d", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_24883/1840452313.py:2: SerializationWarning: saving variable air with floating point data as an integer dtype without any _FillValue to use for NaNs\n", - " ds.to_netcdf(\"0.air_original.nc\")\n" - ] - } - ], - "source": [ - "# Saving the dataset as NetCDF file\n", - "ds.to_netcdf(\"0.air_original.nc\")" - ] - }, - { - "cell_type": "markdown", - "id": "2b98628e-cbcb-4018-8565-4c0324cf2d61", - "metadata": {}, - "source": [ - "## Compress with `to_compressed_netcdf`" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "99d02f35-85fc-4a8d-94a0-880ac2ffbb72", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/ayoubf/Projects/xbitinfo/xbitinfo/save_compressed.py:121: SerializationWarning: saving variable air with floating point data as an integer dtype without any _FillValue to use for NaNs\n", - " self._obj.to_netcdf(\n" - ] - } - ], - "source": [ - "# Compress and save the dataset as NetCDF file\n", - "ds.to_compressed_netcdf(\"1.air_compressed_all.nc\")" - ] - }, - { - "cell_type": "markdown", - "id": "5f5aae30-4a0a-401c-9018-9e34626c3d2c", - "metadata": {}, - "source": [ - "## Compress with bitrounding" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "fdf077dd-6494-4c38-9461-5ea7ac370a01", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d7a346d6cbd14460890ae3be8ec11ff0", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - " 0%| | 0/1 [00:00 Date: Mon, 1 Jan 2024 17:40:43 +0000 Subject: [PATCH 38/53] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/PyCQA/isort: 5.12.0 → 5.13.2](https://github.com/PyCQA/isort/compare/5.12.0...5.13.2) - [github.com/asottile/pyupgrade: v3.13.0 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.13.0...v3.15.0) - [github.com/psf/black: 23.9.1 → 23.12.1](https://github.com/psf/black/compare/23.9.1...23.12.1) - [github.com/keewis/blackdoc: v0.3.8 → v0.3.9](https://github.com/keewis/blackdoc/compare/v0.3.8...v0.3.9) - [github.com/pre-commit/mirrors-mypy: v1.5.1 → v1.8.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.1...v1.8.0) --- .pre-commit-config.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7b5c7f24..b71c00c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ ci: # https://pre-commit.com/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -19,24 +19,24 @@ repos: - id: autoflake # isort should run before black as black sometimes tweaks the isort output args: ["--in-place", "--ignore-init-module-imports"] - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/asottile/pyupgrade - rev: v3.13.0 + rev: v3.15.0 hooks: - id: pyupgrade args: - "--py38-plus" # https://github.com/python/black#version-control-integration - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.12.1 hooks: - id: black - id: black-jupyter - repo: https://github.com/keewis/blackdoc - rev: v0.3.8 + rev: v0.3.9 hooks: - id: blackdoc exclude: docs/index.rst @@ -45,7 +45,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.8.0 hooks: - id: mypy exclude: "properties|asv_bench" From 26e92fbd13d8cb3a54d90d03d47d3e365459057b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 7 Jan 2024 01:02:52 +0000 Subject: [PATCH 39/53] Update GitHub Action Versions --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 341e1a7b..06a86c95 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -138,7 +138,7 @@ jobs: with: python-version: '3.11' - name: Set up Julia - uses: julia-actions/setup-julia@v1.9.4 + uses: julia-actions/setup-julia@v1.9.5 with: version: 1.7.1 - name: Install dependencies From d4ba05439f27c7f8424fa8a4bb526d93d4f7ae34 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jan 2024 01:03:06 +0000 Subject: [PATCH 40/53] Update GitHub Action Versions --- .github/workflows/benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 0db7840a..abb3c123 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -74,7 +74,7 @@ jobs: cp benchmarks.log .asv/results/ working-directory: ${{ env.ASV_DIR }} - - uses: actions/upload-artifact@v4.0.0 + - uses: actions/upload-artifact@v4.1.0 if: always() with: name: asv-benchmark-results-${{ runner.os }} From bff15682dc09eb0ac77a62cddaf0904c14e4d0fb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 21 Jan 2024 01:03:19 +0000 Subject: [PATCH 41/53] Update GitHub Action Versions --- .github/workflows/benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index abb3c123..1d861518 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -74,7 +74,7 @@ jobs: cp benchmarks.log .asv/results/ working-directory: ${{ env.ASV_DIR }} - - uses: actions/upload-artifact@v4.1.0 + - uses: actions/upload-artifact@v4.2.0 if: always() with: name: asv-benchmark-results-${{ runner.os }} From 716665d7b41de3383ce606e4d2bff8384ce69dd2 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:16:44 -0800 Subject: [PATCH 42/53] Force push gh-actions-updates to specific branch Fix the creation of several pull requests in case branches do not get merged before next update. --- .github/workflows/updater.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/updater.yaml b/.github/workflows/updater.yaml index 66674e97..223b3f23 100644 --- a/.github/workflows/updater.yaml +++ b/.github/workflows/updater.yaml @@ -21,3 +21,4 @@ jobs: with: # [Required] Access token with `workflow` scope. token: ${{ secrets.WORKFLOW_SECRET }} + pull_request_branch: gh-actions-update From 897345b750f34ace4677cd669e607a8d92bb299d Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:42:10 -0800 Subject: [PATCH 43/53] remove pytest_lazy_fixture dep --- environment.yml | 1 - setup.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/environment.yml b/environment.yml index fe242e9e..a75c755e 100644 --- a/environment.yml +++ b/environment.yml @@ -28,7 +28,6 @@ dependencies: - sphinx-book-theme - myst-nb - numcodecs>=0.10.0 - - pytest-lazy-fixture - pip - pip: - -e . diff --git a/setup.py b/setup.py index dfd973c0..fe091f16 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ with open("requirements.txt") as f: requirements = f.read().strip().split("\n") -test_requirements = ["pytest", "pytest-lazy-fixture", "pooch", "netcdf4", "dask"] +test_requirements = ["pytest", "pooch", "netcdf4", "dask"] extras_require = { "viz": ["matplotlib", "cmcrameri"], From 5e698f217982b0b690f69d6638b0f1f107e41a9c Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:54:28 -0800 Subject: [PATCH 44/53] replace lazy_fixture following https://github.com/TvoroG/pytest-lazy-fixture/issues/65#issuecomment-1914527162 --- tests/test_bitinformation_pipeline.py | 17 +++++++++-------- tests/test_get_bitinformation.py | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/test_bitinformation_pipeline.py b/tests/test_bitinformation_pipeline.py index 777d7dd9..d25a7e89 100644 --- a/tests/test_bitinformation_pipeline.py +++ b/tests/test_bitinformation_pipeline.py @@ -9,17 +9,18 @@ @pytest.mark.parametrize( "ds,dim,axis", [ - (pytest.lazy_fixture("ugrid_demo"), None, -1), - (pytest.lazy_fixture("icon_grid_demo"), "ncells", None), - (pytest.lazy_fixture("air_temperature"), "lon", None), - (pytest.lazy_fixture("rasm"), "x", None), - (pytest.lazy_fixture("ROMS_example"), "eta_rho", None), - (pytest.lazy_fixture("era52mt"), "time", None), - (pytest.lazy_fixture("eraint_uvz"), "longitude", None), + ("ugrid_demo", None, -1), + ("icon_grid_demo", "ncells", None), + ("air_temperature", "lon", None), + ("rasm", "x", None), + ("ROMS_example", "eta_rho", None), + ("era52mt", "time", None), + ("eraint_uvz", "longitude", None), ], ) -def test_full(ds, dim, axis): +def test_full(ds, dim, axis, request): """Test xbitinfo end to end.""" + ds = request.getfixturevalue(ds) # xbitinfo bitinfo = xb.get_bitinformation(ds, dim=dim, axis=axis) keepbits = xb.get_keepbits(bitinfo) diff --git a/tests/test_get_bitinformation.py b/tests/test_get_bitinformation.py index 5eafe2cb..8f16cea8 100644 --- a/tests/test_get_bitinformation.py +++ b/tests/test_get_bitinformation.py @@ -226,17 +226,18 @@ def test_get_bitinformation_keep_attrs(rasm): @pytest.mark.parametrize( "ds,dim,axis", [ - (pytest.lazy_fixture("ugrid_demo"), None, -1), - (pytest.lazy_fixture("icon_grid_demo"), "ncells", None), - (pytest.lazy_fixture("air_temperature"), "lon", None), - (pytest.lazy_fixture("rasm"), "x", None), - (pytest.lazy_fixture("ROMS_example"), "eta_rho", None), - (pytest.lazy_fixture("era52mt"), "time", None), - (pytest.lazy_fixture("eraint_uvz"), "longitude", None), + ("ugrid_demo", None, -1), + ("icon_grid_demo", "ncells", None), + ("air_temperature", "lon", None), + ("rasm", "x", None), + ("ROMS_example", "eta_rho", None), + ("era52mt", "time", None), + ("eraint_uvz", "longitude", None), ], ) -def test_implementations_agree(ds, dim, axis): +def test_implementations_agree(ds, dim, axis, request): """Test whether the python and julia implementation retrieve the same results""" + ds = request.getfixturevalue(ds) bi_python = xb.get_bitinformation( ds, dim=dim, From 86d2f697fef96daa1a620d05a4ae2f1c7d742053 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 02:56:55 +0000 Subject: [PATCH 45/53] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_bitinformation_pipeline.py | 2 +- tests/test_get_bitinformation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_bitinformation_pipeline.py b/tests/test_bitinformation_pipeline.py index d25a7e89..05388b46 100644 --- a/tests/test_bitinformation_pipeline.py +++ b/tests/test_bitinformation_pipeline.py @@ -20,7 +20,7 @@ ) def test_full(ds, dim, axis, request): """Test xbitinfo end to end.""" - ds = request.getfixturevalue(ds) + ds = request.getfixturevalue(ds) # xbitinfo bitinfo = xb.get_bitinformation(ds, dim=dim, axis=axis) keepbits = xb.get_keepbits(bitinfo) diff --git a/tests/test_get_bitinformation.py b/tests/test_get_bitinformation.py index 8f16cea8..fc3d362a 100644 --- a/tests/test_get_bitinformation.py +++ b/tests/test_get_bitinformation.py @@ -237,7 +237,7 @@ def test_get_bitinformation_keep_attrs(rasm): ) def test_implementations_agree(ds, dim, axis, request): """Test whether the python and julia implementation retrieve the same results""" - ds = request.getfixturevalue(ds) + ds = request.getfixturevalue(ds) bi_python = xb.get_bitinformation( ds, dim=dim, From 5a73addd965137dc332d3f0af853e6bb38ce8511 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 4 Feb 2024 00:58:31 +0000 Subject: [PATCH 46/53] Update GitHub Action Versions --- .github/workflows/benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 1d861518..c7ef0c1a 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -74,7 +74,7 @@ jobs: cp benchmarks.log .asv/results/ working-directory: ${{ env.ASV_DIR }} - - uses: actions/upload-artifact@v4.2.0 + - uses: actions/upload-artifact@v4.3.0 if: always() with: name: asv-benchmark-results-${{ runner.os }} From 993addab6a06974250d1b70ffa05039433d93c5a Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Tue, 6 Feb 2024 02:07:36 -0800 Subject: [PATCH 47/53] plot bitinfo of diff. dtypes in subplots --- CHANGELOG.rst | 1 + xbitinfo/graphics.py | 157 ++++++++++++++++++++++++++++--------------- 2 files changed, 105 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 55433717..471528b7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ CHANGELOG X.X.X (unreleased) ------------------ +* Add support for additional datatypes in :py:func:`xbitinfo.xbitinfo.plot_bitinformation` (:pr:`218`, :issue:`168`) `Hauke Schulz`_. * Drop python 3.8 support and add python 3.11 (:pr:`175`) `Hauke Schulz`_. * Implement basic retrieval of bitinformation in python as alternative to julia implementation (:pr:`156`, :issue:`155`, :pr:`126`, :issue:`125`) `Hauke Schulz`_ with helpful comments from `Milan Klöwer`_. * Make julia binding to BitInformation.jl optional (:pr:`153`, :issue:`151`) `Aaron Spring`_. diff --git a/xbitinfo/graphics.py b/xbitinfo/graphics.py index 2b9272c5..da89aacd 100644 --- a/xbitinfo/graphics.py +++ b/xbitinfo/graphics.py @@ -239,7 +239,8 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): ), "Found dependence of bitinformation on dimension. Please reduce data first by e.g. `bitinfo.max(dim='dim')`" vars_by_dim = split_dataset_by_dims(bitinfo) bitinfo_all = bitinfo - for dim, vars in vars_by_dim.items(): + subfigure_data = [None] * len(vars_by_dim) + for d, (dim, vars) in enumerate(vars_by_dim.items()): bitinfo = bitinfo_all[vars] data_type = np.dtype(dim.replace("bit", "")) n_bits, n_sign, n_exp, n_mant = bit_partitioning(data_type) @@ -249,7 +250,7 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): else: bits_to_show = int(np.min([crop, n_bits])) nvars = len(bitinfo) - varnames = bitinfo.keys() + varnames = list(bitinfo.keys()) infbits_dict = get_keepbits(bitinfo, 0.99) infbits100_dict = get_keepbits(bitinfo, 0.999999999) @@ -272,27 +273,74 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): infbitsx100 = np.repeat(infbits100, 2) fig_height = np.max([4, 4 + (nvars - 10) * 0.2]) # auto adjust to nvars - fig, ax1 = plt.subplots(1, 1, figsize=(12, fig_height), sharey=True) - ax1.invert_yaxis() - ax1.set_box_aspect(1 / bits_to_show * nvars) - plt.tight_layout(rect=[0.06, 0.18, 0.8, 0.98]) - pos = ax1.get_position() - cax = fig.add_axes([pos.x0, 0.12, pos.x1 - pos.x0, 0.02]) - - ax1right = ax1.twinx() + + subfigure_data[d] = {} + subfigure_data[d]["fig_height"] = fig_height + subfigure_data[d]["nvars"] = nvars + subfigure_data[d]["varnames"] = varnames + subfigure_data[d]["ICnan"] = ICnan + subfigure_data[d]["ICcsum"] = ICcsum + subfigure_data[d]["infbits"] = infbits + subfigure_data[d]["infbitsx"] = infbitsx + subfigure_data[d]["infbitsy"] = infbitsy + subfigure_data[d]["infbitsx100"] = infbitsx100 + subfigure_data[d]["nbits"] = (n_sign, n_exp, n_bits, n_mant, nonmantissa_bits) + subfigure_data[d]["bits_to_show"] = bits_to_show + + total_fig_height = np.sum([d["fig_height"] for d in subfigure_data]) + fig, axs = plt.subplots(len(subfigure_data), 1, figsize=(12, total_fig_height)) + + if isinstance(axs, plt.Axes): + axs = [axs] + + fig.suptitle( + "Real bitwise information content", + x=0.05, + y=0.98, + fontweight="bold", + horizontalalignment="left", + ) + + if cmap == "turku": + import cmcrameri.cm as cmc + + cmap = cmc.turku_r + + max_bits_to_show = np.max([d["bits_to_show"] for d in subfigure_data]) + + for d, subfig in enumerate(subfigure_data): + infbits = subfig["infbits"] + nvars = subfig["nvars"] + n_sign, n_exp, n_bits, n_mant, nonmantissa_bits = subfig["nbits"] + ICcsum = subfig["ICcsum"] + ICnan = subfig["ICnan"] + infbitsy = subfig["infbitsy"] + infbitsx = subfig["infbitsx"] + infbitsx100 = subfig["infbitsx100"] + varnames = subfig["varnames"] + bits_to_show = subfig["bits_to_show"] + + mbits_to_show = bits_to_show - nonmantissa_bits + + axs[d].invert_yaxis() + axs[d].set_box_aspect(1 / max_bits_to_show * nvars) + + ax1right = axs[d].twinx() ax1right.invert_yaxis() - ax1right.set_box_aspect(1 / bits_to_show * nvars) + ax1right.set_box_aspect(1 / max_bits_to_show * nvars) - if cmap == "turku": - import cmcrameri.cm as cmc + pcm = axs[d].pcolormesh(ICnan, vmin=0, vmax=1, cmap=cmap) - cmap = cmc.turku_r - pcm = ax1.pcolormesh(ICnan, vmin=0, vmax=1, cmap=cmap) - cbar = plt.colorbar(pcm, cax=cax, orientation="horizontal") - cbar.set_label("information content [bit]") + if d == len(subfigure_data) - 1: + pos = axs[d].get_position() + cax = fig.add_axes([pos.x0, 0.12, pos.x1 - pos.x0, 0.05]) + lax = fig.add_axes([pos.x0, 0.07, pos.x1 - pos.x0, 0.07]) + lax.axis("off") + cbar = plt.colorbar(pcm, cax=cax, orientation="horizontal") + cbar.set_label("information content [bit]") # 99% of real information enclosed - ax1.plot( + l0 = axs[d].plot( np.hstack([infbits, infbits[-1]]), np.arange(nvars + 1), "C1", @@ -302,21 +350,21 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): ) # grey shading - ax1.fill_betweenx( + axs[d].fill_betweenx( infbitsy, infbitsx, np.ones(len(infbitsx)) * bits_to_show, alpha=0.4, color="grey", ) - ax1.fill_betweenx( + axs[d].fill_betweenx( infbitsy, infbitsx100, np.ones(len(infbitsx)) * bits_to_show, alpha=0.1, color="c", ) - ax1.fill_betweenx( + axs[d].fill_betweenx( infbitsy, infbitsx100, np.ones(len(infbitsx)) * bits_to_show, @@ -326,7 +374,7 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): ) # for legend only - ax1.fill_betweenx( + l1 = axs[d].fill_betweenx( [-1, -1], [-1, -1], [-1, -1], @@ -334,7 +382,7 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): label="last 1% of\ninformation", alpha=0.5, ) - ax1.fill_betweenx( + l2 = axs[d].fill_betweenx( [-1, -1], [-1, -1], [-1, -1], @@ -343,30 +391,25 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): label="false information", alpha=0.3, ) - ax1.fill_betweenx([-1, -1], [-1, -1], [-1, -1], color="w", label="unused bits") + axs[d].fill_betweenx( + [-1, -1], [-1, -1], [-1, -1], color="w", label="unused bits" + ) if n_sign > 0: - ax1.axvline(n_sign, color="k", lw=1, zorder=3) - ax1.axvline(nonmantissa_bits, color="k", lw=1, zorder=3) - - fig.suptitle( - "Real bitwise information content", - x=0.05, - y=0.98, - fontweight="bold", - horizontalalignment="left", - ) + axs[d].axvline(n_sign, color="k", lw=1, zorder=3) + axs[d].axvline(nonmantissa_bits, color="k", lw=1, zorder=3) - ax1.set_ylim(nvars, 0) + axs[d].set_ylim(nvars, 0) ax1right.set_ylim(nvars, 0) - ax1.set_yticks(np.arange(nvars) + 0.5) + axs[d].set_yticks(np.arange(nvars) + 0.5) ax1right.set_yticks(np.arange(nvars) + 0.5) - ax1.set_yticklabels(varnames) + axs[d].set_yticklabels(varnames) ax1right.set_yticklabels([f"{i:4.1f}" for i in ICcsum[:, -1]]) - ax1right.set_ylabel("total information per value [bit]") + if d == len(subfigure_data) // 2: + ax1right.set_ylabel("total information\nper value [bit]") - ax1.text( + axs[d].text( infbits[0] + 0.1, 0.8, f"{int(infbits[0]-nonmantissa_bits)} mantissa bits", @@ -374,7 +417,7 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): color="saddlebrown", ) for i in range(1, nvars): - ax1.text( + axs[d].text( infbits[i] + 0.1, (i) + 0.8, f"{int(infbits[i]-9)}", @@ -383,22 +426,22 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): ) major_xticks = np.array([n_sign, n_sign + n_exp, n_bits], dtype="int") - ax1.set_xticks(major_xticks[major_xticks <= bits_to_show]) + axs[d].set_xticks(major_xticks[major_xticks <= bits_to_show]) minor_xticks = np.hstack( [ np.arange(n_sign, nonmantissa_bits - 1), np.arange(nonmantissa_bits, n_bits - 1), ] ) - ax1.set_xticks( + axs[d].set_xticks( minor_xticks[minor_xticks <= bits_to_show], minor=True, ) - ax1.set_xticklabels([]) + axs[d].set_xticklabels([]) if n_sign > 0: - ax1.text(0, nvars + 1.2, "sign", rotation=90) + axs[d].text(0, nvars + 1.2, "sign", rotation=90) if n_exp > 0: - ax1.text( + axs[d].text( n_sign + n_exp / 2, nvars + 1.2, "exponent bits", @@ -406,8 +449,8 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): horizontalalignment="center", verticalalignment="center", ) - ax1.text( - n_sign + n_exp + n_mant / 2, + axs[d].text( + n_sign + n_exp + mbits_to_show / 2, nvars + 1.2, "mantissa bits", horizontalalignment="center", @@ -417,7 +460,7 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): # Set xticklabels ## Set exponent labels for e, i in enumerate(range(n_sign, np.min([n_sign + n_exp, bits_to_show]))): - ax1.text( + axs[d].text( i + 0.5, nvars + 0.5, e + 1, @@ -429,12 +472,20 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): for m, i in enumerate( range(n_sign + n_exp, np.min([n_sign + n_exp + n_mant, bits_to_show])) ): - ax1.text(i + 0.5, nvars + 0.5, m + 1, ha="center", fontsize=7) - - ax1.legend(bbox_to_anchor=(1.08, 0.5), loc="center left", framealpha=0.6) - ax1.set_xlim(0, bits_to_show) + axs[d].text(i + 0.5, nvars + 0.5, m + 1, ha="center", fontsize=7) + + if d == len(subfigure_data) - 1: + lax.legend( + bbox_to_anchor=(0.5, 0), + loc="center", + framealpha=0.6, + ncol=3, + handles=[l1, l2, l0[0]], + ) + axs[d].set_xlim(0, bits_to_show) - fig.show() + plt.tight_layout() + fig.show() return fig From 9136d523fbef847a6beeecb6b0fc7873432bef92 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:18:34 -0800 Subject: [PATCH 48/53] fix legend overlap with colorbar --- xbitinfo/graphics.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/xbitinfo/graphics.py b/xbitinfo/graphics.py index da89aacd..49e3cd18 100644 --- a/xbitinfo/graphics.py +++ b/xbitinfo/graphics.py @@ -287,8 +287,17 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): subfigure_data[d]["nbits"] = (n_sign, n_exp, n_bits, n_mant, nonmantissa_bits) subfigure_data[d]["bits_to_show"] = bits_to_show - total_fig_height = np.sum([d["fig_height"] for d in subfigure_data]) - fig, axs = plt.subplots(len(subfigure_data), 1, figsize=(12, total_fig_height)) + fig_heights = [subfig["fig_height"] for subfig in subfigure_data] + fig = plt.figure(figsize=(12, sum(fig_heights) + 2 * 2)) + fig_heights_incl_cax = fig_heights + [2 / (sum(fig_heights) + 2)] * 2 + grid = fig.add_gridspec( + len(subfigure_data) + 2, 1, height_ratios=fig_heights_incl_cax + ) + + axs = [] + for i in range(len(subfigure_data) + 2): + ax = fig.add_subplot(grid[i, 0]) + axs.append(ax) if isinstance(axs, plt.Axes): axs = [axs] @@ -332,9 +341,8 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): pcm = axs[d].pcolormesh(ICnan, vmin=0, vmax=1, cmap=cmap) if d == len(subfigure_data) - 1: - pos = axs[d].get_position() - cax = fig.add_axes([pos.x0, 0.12, pos.x1 - pos.x0, 0.05]) - lax = fig.add_axes([pos.x0, 0.07, pos.x1 - pos.x0, 0.07]) + cax = axs[len(subfigure_data)] + lax = axs[len(subfigure_data) + 1] lax.axis("off") cbar = plt.colorbar(pcm, cax=cax, orientation="horizontal") cbar.set_label("information content [bit]") @@ -391,8 +399,8 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): label="false information", alpha=0.3, ) - axs[d].fill_betweenx( - [-1, -1], [-1, -1], [-1, -1], color="w", label="unused bits" + l3 = axs[d].fill_betweenx( + [-1, -1], [-1, -1], [-1, -1], color="w", label="unused bits", edgecolor="k" ) if n_sign > 0: @@ -479,12 +487,11 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): bbox_to_anchor=(0.5, 0), loc="center", framealpha=0.6, - ncol=3, - handles=[l1, l2, l0[0]], + ncol=4, + handles=[l0[0], l1, l2, l3], ) axs[d].set_xlim(0, bits_to_show) - plt.tight_layout() fig.show() return fig From 10153a6b62fd748fa083be546f5b4647b4ead982 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:59:41 -0800 Subject: [PATCH 49/53] update doctest --- xbitinfo/graphics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbitinfo/graphics.py b/xbitinfo/graphics.py index 49e3cd18..bef4037a 100644 --- a/xbitinfo/graphics.py +++ b/xbitinfo/graphics.py @@ -228,7 +228,7 @@ def plot_bitinformation(bitinfo, cmap="turku", crop=None): >>> ds = xr.tutorial.load_dataset("air_temperature") >>> info_per_bit = xb.get_bitinformation(ds, dim="lon") >>> xb.plot_bitinformation(info_per_bit) -
+
""" import matplotlib.pyplot as plt From 04efd3a6cd39c0cb0c7b5c5af7e06a450eb81e31 Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:04:09 -0800 Subject: [PATCH 50/53] focus on zarr files; fix chunk visualization --- docs/chunking.ipynb | 203 ++++++++++---------------------------------- 1 file changed, 44 insertions(+), 159 deletions(-) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index 56ad538e..1c516c65 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -624,12 +624,6 @@ "## Zarr chunking and compressing" ] }, - { - "cell_type": "markdown", - "id": "e837c725-b7de-4418-a530-113583411884", - "metadata": {}, - "source": [] - }, { "cell_type": "code", "execution_count": 8, @@ -646,7 +640,7 @@ " bitinfo = xb.get_bitinformation(chunk, dim=var, implementation=\"python\")\n", " keepbits = xb.get_keepbits(bitinfo, 0.99)\n", " bitround = xb.xr_bitround(chunk, keepbits)\n", - " return bitround\n", + " return bitround, keepbits\n", "\n", "\n", "def slices_from_chunks(chunks):\n", @@ -674,31 +668,13 @@ ] }, { - "cell_type": "code", - "execution_count": 9, - "id": "19032fcd-93bc-48b8-ba1f-beba9673491b", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "fn = \"air.zarr\" # Output filename\n", - "ds.to_compressed_zarr(fn, compute=False, mode=\"w\") # Creates empty file structure" - ] - }, - { - "cell_type": "code", - "execution_count": 10, + "cell_type": "markdown", "id": "7221b47f-b8f4-4ebf-bc2b-cb61d12989be", "metadata": { "tags": [] }, - "outputs": [], "source": [ - "dims = ds.air.dims\n", - "len_dims = len(dims)\n", - "\n", - "slices = slices_from_chunks(ds.air.chunks)" + "### Save dataset as compressed zarr after compressing individual chunks" ] }, { @@ -710,94 +686,28 @@ }, "outputs": [], "source": [ - "%%capture\n", + "fn = \"air_bitrounded_by_chunks.zarr\" # Output filename\n", + "ds.to_compressed_zarr(fn, compute=False, mode=\"w\") # Creates empty file structure\n", + "\n", + "dims = ds.air.dims\n", + "len_dims = len(dims)\n", + "\n", + "slices = slices_from_chunks(ds.air.chunks)\n", "\n", "# Loop over each chunk\n", + "keepbits = []\n", "for b, block in enumerate(ds.air.data.to_delayed().ravel()):\n", " # Conversion of dask.delayed array to Dataset (as xbitinfo wants type xr.Dataset)\n", " ds_block = xr.Dataset({\"air\": (dims, block.compute())})\n", "\n", " # Apply bitrounding\n", - " rounded_ds = bitrounding(ds_block)\n", + " rounded_ds, keepbit = bitrounding(ds_block)\n", + " keepbits.append(keepbit)\n", "\n", " # Write individual chunk to disk\n", " rounded_ds.to_zarr(fn, region={dims[d]: s for (d, s) in enumerate(slices[b])})" ] }, - { - "cell_type": "markdown", - "id": "9ae3603f-291d-4c7f-92a8-95d9935daf35", - "metadata": {}, - "source": [ - "## Creating smaller datasets as chunks and compressing" - ] - }, - { - "cell_type": "markdown", - "id": "4265a4fa-b397-4552-ac3c-a1a358fffcd0", - "metadata": {}, - "source": [] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "1d53f86f-fa72-4161-a364-8c1f78dba6d6", - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [], - "source": [ - "%%capture\n", - "\n", - "# Define a lambda function to ensure that the value is at least zero\n", - "# negative keepbits not yet supported\n", - "at_least_zero = lambda x: max(x, 0)\n", - "\n", - "# Create empty intermediate holders for plotting later\n", - "dss = []\n", - "dss_bitrounded = []\n", - "dss_kbits = []\n", - "\n", - "# How many chunks there are\n", - "long_c = int(ds.lon.size / chunks[\"lon\"])\n", - "lat_c = int(ds.lat.size / chunks[\"lat\"])\n", - "\n", - "for i, j in product(range(long_c), range(lat_c)):\n", - " # Extract a chunk of the dataset\n", - " temp_ds = ds.isel(\n", - " lon=slice(i * chunks[\"lon\"], (i + 1) * chunks[\"lon\"]),\n", - " lat=slice(j * chunks[\"lat\"], (j + 1) * chunks[\"lat\"]),\n", - " )\n", - " dss.append(temp_ds)\n", - "\n", - " # Compress with bitrounding (See details above)\n", - " temp_info_pbit = xb.get_bitinformation(temp_ds, dim=\"lat\", implementation=\"python\")\n", - " temp_keepbits = xb.get_keepbits(temp_info_pbit, 0.99)\n", - " temp_keepbits = temp_keepbits.where(temp_keepbits[\"air\"] > 0, 0)\n", - " dss_kbits.append(temp_keepbits)\n", - " temp_ds_bitrounded = xb.xr_bitround(temp_ds, temp_keepbits)\n", - " dss_bitrounded.append(temp_ds_bitrounded)\n", - "\n", - " # Merge the bitrounded datasets\n", - " if i == 0 and j == 0:\n", - " MERGED_ds_bitr = temp_ds_bitrounded\n", - " else:\n", - " MERGED_ds_bitr = xr.merge([MERGED_ds_bitr, temp_ds_bitrounded])" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "7e1c4b35-4767-47e7-9ddf-357250b631b8", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "MERGED_ds_bitr.to_compressed_netcdf(\"3.air_chunked_bitr_compressed.nc\")" - ] - }, { "cell_type": "markdown", "id": "5d628121-d5ec-4544-a47f-f47c86524b09", @@ -842,12 +752,17 @@ "source": [ "# # Create a figure and axis and plot the air temperature\n", "fig, ax = plt.subplots(figsize=(12, 6))\n", - "ds[\"air\"].isel(time=0).plot(ax=ax, cmap=\"RdBu_r\")\n", + "rounded_ds = xr.open_zarr(fn).isel(time=0)\n", + "rounded_ds[\"air\"].plot(ax=ax, cmap=\"RdBu_r\")\n", + "\n", + "slices = slices_from_chunks(rounded_ds.air.chunks)\n", "\n", - "for i in range(len(dss_bitrounded)):\n", + "for i in range(len(slices)):\n", " # Get chunk limits\n", - " lats = dss[i].lat\n", - " longs = dss[i].lon\n", + " dss = rounded_ds.isel(lat=slices[i][0], lon=slices[i][1])\n", + " lats = dss.lat\n", + " longs = dss.lon\n", + "\n", " x = float(min(longs[0], longs[-1]))\n", " y = float(min(lats[0], lats[-1]))\n", " w = float(abs(longs[0] - longs[-1]))\n", @@ -869,7 +784,7 @@ " cx = rx + rect.get_width() / 2.0\n", " cy = ry + rect.get_height() / 2.0\n", " ax.annotate(\n", - " f\"{int(dss_kbits[i].air):2}\",\n", + " f\"{int(keepbits[i].air):2}\",\n", " (cx, cy),\n", " color=\"k\",\n", " weight=\"normal\",\n", @@ -892,8 +807,8 @@ "## Reference compression\n", "For comparision with other compression approaches the dataset is also saved as:\n", "- uncompressed netCDF\n", - "- lossless compressed netCDF\n", - "- lossy compressed netCDF while preserving 99% of bitinformation" + "- lossless compressed zarr\n", + "- lossy compressed zarr while preserving 99% of bitinformation" ] }, { @@ -906,7 +821,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "e011a900-5da2-40be-a292-d81a0cafcd6d", "metadata": { "tags": [] @@ -928,77 +843,47 @@ }, { "cell_type": "markdown", - "id": "2b98628e-cbcb-4018-8565-4c0324cf2d61", + "id": "1cc93427", "metadata": {}, "source": [ - "### Saving as compressed NetCDF with `to_compressed_netcdf`" + "### Save dataset as compressed zarr (without bitrounding)" ] }, { "cell_type": "code", - "execution_count": 5, - "id": "99d02f35-85fc-4a8d-94a0-880ac2ffbb72", + "execution_count": 9, + "id": "19032fcd-93bc-48b8-ba1f-beba9673491b", "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/ayoubf/Projects/xbitinfo/xbitinfo/save_compressed.py:121: SerializationWarning: saving variable air with floating point data as an integer dtype without any _FillValue to use for NaNs\n", - " self._obj.to_netcdf(\n" - ] - } - ], + "outputs": [], "source": [ - "# Compress and save the dataset as NetCDF file\n", - "ds.to_compressed_netcdf(\"1.air_compressed_all.nc\")" + "fn = \"air_compressed.zarr\" # Output filename\n", + "ds.to_compressed_zarr(fn, mode=\"w\") # Creates empty file structure" ] }, { "cell_type": "markdown", - "id": "5f5aae30-4a0a-401c-9018-9e34626c3d2c", + "id": "648f759c", "metadata": {}, "source": [ - "### Saving while preserving 99% of information based on bitrounding algorithm" + "### Save dataset as compressed zarr after applying bitrounding" ] }, { "cell_type": "code", - "execution_count": 6, - "id": "fdf077dd-6494-4c38-9461-5ea7ac370a01", + "execution_count": null, + "id": "93eb4cd6", "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d7a346d6cbd14460890ae3be8ec11ff0", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - " 0%| | 0/1 [00:00 Date: Tue, 6 Feb 2024 14:23:38 -0800 Subject: [PATCH 51/53] suppress progress bar; remove output of cells --- docs/chunking.ipynb | 590 ++------------------------------------------ 1 file changed, 15 insertions(+), 575 deletions(-) diff --git a/docs/chunking.ipynb b/docs/chunking.ipynb index 1c516c65..cf58b158 100644 --- a/docs/chunking.ipynb +++ b/docs/chunking.ipynb @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "96e9149e-fc6d-4048-8e45-a29966e5c6b8", "metadata": { "tags": [] @@ -54,7 +54,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "320224c9-06e2-428a-8614-8ed0d15eee82", "metadata": { "tags": [] @@ -76,542 +76,12 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "f3120040-79f1-4a7f-a61f-afec9fb3ca5b", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.Dataset>\n",
-       "Dimensions:  (lat: 25, time: 2920, lon: 53)\n",
-       "Coordinates:\n",
-       "  * lat      (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n",
-       "  * lon      (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n",
-       "  * time     (time) datetime64[ns] 2013-01-01 ... 2014-12-31T18:00:00\n",
-       "Data variables:\n",
-       "    air      (time, lat, lon) float32 dask.array<chunksize=(2920, 5, 10), meta=np.ndarray>\n",
-       "Attributes:\n",
-       "    Conventions:  COARDS\n",
-       "    title:        4x daily NMC reanalysis (1948)\n",
-       "    description:  Data is from NMC initialized reanalysis\\n(4x/day).  These a...\n",
-       "    platform:     Model\n",
-       "    references:   http://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanaly...
" - ], - "text/plain": [ - "\n", - "Dimensions: (lat: 25, time: 2920, lon: 53)\n", - "Coordinates:\n", - " * lat (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n", - " * lon (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n", - " * time (time) datetime64[ns] 2013-01-01 ... 2014-12-31T18:00:00\n", - "Data variables:\n", - " air (time, lat, lon) float32 dask.array\n", - "Attributes:\n", - " Conventions: COARDS\n", - " title: 4x daily NMC reanalysis (1948)\n", - " description: Data is from NMC initialized reanalysis\\n(4x/day). These a...\n", - " platform: Model\n", - " references: http://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanaly..." - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "ds" ] @@ -626,7 +96,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "91343d2a-63ec-4d61-a369-cc99139297e4", "metadata": { "tags": [] @@ -679,13 +149,14 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "7c2ed18f-4dc8-4f5c-88ed-ae5ad41d1647", "metadata": { "tags": [] }, "outputs": [], "source": [ + "%%capture\n", "fn = \"air_bitrounded_by_chunks.zarr\" # Output filename\n", "ds.to_compressed_zarr(fn, compute=False, mode=\"w\") # Creates empty file structure\n", "\n", @@ -718,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "d8835a3c-8af0-4423-baf4-84aa9a386f67", "metadata": { "tags": [] @@ -732,23 +203,12 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "c3b5f657-cddd-4476-82a3-c3c2c1a6e7b6", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA7gAAAJDCAYAAAAhG7g1AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd5gT1foH8O9MerbvsoWlLFWKgFRRROlgQQRUQOUqgqLSREB/AldBREBU6hUQREARQRCwcEFAAaUJAkqV3mEp20t65vfHXqLZbEkO2c2W7+d59oFM5p05SU5m5uTMeY+kKIoCIiIiIiIiolJODnQBiIiIiIiIiPyBDVwiIiIiIiIqE9jAJSIiIiIiojKBDVwiIiIiIiIqE9jAJSIiIiIiojKBDVwiIiIiIiIqE9jAJSIiIiIiojKBDVwiIiIiIiIqE9jAJSIiIiIiojKBDVwiIipztm7dCkmSXH/9+vXzKX7x4sVu8ePHjy+SchIREZF/sYFLRFSG/bORJkkSzp0757HOihUroFar3dZ76623ir+wpcjWrVsxfvx4198ff/wR6CIRERERAHWgC0BERIGzZs0a9O3bFw6Hw7Vs1KhRePfddwNYqsAzGAyIjY11PQ4ODnZ7fuvWrXjnnXdcj6tVq4bGjRsXV/GIiIgoH2zgEhGVUz/88AN69+4Nu93uWjZkyBB88MEHASxVydC7d2/07t070MUgIiIiH/EWZSKicmjjxo144oknYLPZXMtefPFFzJo1K4ClIiIiIro9bOASEZUzW7ZsQffu3WGxWFzLnn32WXzyySeQJCnPmNTUVEyZMgWtW7dGVFQUtFot4uLi8Oijj+Lbb78tcH8mkwlz5sxBx44dERMTA61WiwoVKqBjx45YsmQJnE6nR8y5c+fcxgS3bdsWDocDM2bMQKNGjWAwGBAdHY0+ffrg5MmTXr1um82GyZMno169etDr9YiPj8fAgQORmJjosW5+SaZuLf/n7ckA8Pzzz+eblOrs2bMYNmwYGjZsiJCQEGg0GkRHR6NevXp46qmnMHv2bNy4ccOr10BEREQF4y3KRETlyPbt2/Hyyy/DZDK5lvXu3RufffZZvo3bnTt34vHHH/doCF67dg0//PCD61bnzz//HFqt1m2dv/76C4899hhOnDjhtjwpKQk//fQTfvrpJyxevBhr1qxBeHh4vuW22Wzo1q0b/vvf/7qWmc1mrFixAv/973+xefNm3H333fnGm81mdOzYEb/88otr2dWrV7FgwQKsW7cOv/76K2rUqJFvvKiDBw/i/vvvR3p6utvymzdv4ubNm/jrr7+wfPlyVK9eHV27dvX7/omIiMob9uASEZUj/fv3R1ZWlutx9+7dsXTpUqhUqjzXP336NB555BG3xq0kSQgNDXVbb8WKFRgxYoTbsuTkZDz44IMejdvcsVu3bkXfvn0LLPfOnTtdjVuj0ej2XEZGBvr06ePWaM9t5cqV+OWXXyBJEvR6vdtzV65cwTPPPANFUQosA/B38qmgoCCP1xQbG+v6u5WUasKECW6NW1mWERERke/7TURERLeHDVwionLkn2NuH374YdcUQfl56623kJqa6nrcv39/JCUlIS0tDceOHUOdOnVcz82dOxfHjx93Pf7www9x/vx51+NHHnkEly5dQlpaGi5duoT77rvP9dy6deuwadOmAsvepk0bXLlyBZmZmdi8ebNbj+/Zs2exdOnSfGOdTid69eqF5ORkZGZm4quvvnLrbd69ezc2b95c4P6BnN7uxMREjBo1ym35zJkzkZiY6Pq79fyhQ4dc67Rv3x5JSUlITk6GxWLBhQsXsGzZMjz11FMeWZqJiIhIDBu4RETlVIUKFaDRaPJ93mKxYO3ata7H8fHxWLBgASIiIgAAdevWxbhx41zPO51OrFixwvV4+fLlrv/rdDp8+eWXqFSpEgCgUqVKHtmav/rqq3zLIssyFi1ahIoVK0KSJHTo0MGjx3jdunX5xoeGhuLTTz9FeHg4VCoV+vTp49FrXFC8qH82XFUqlWu8sUqlQpUqVfDUU09h2bJlaNu2rd/3TUREVB6xgUtEVE59/vnnGDRoUL7Pnzx50u223ytXrkClUrklU3r66afdYn7//XcAQGZmJs6ePetabrFYEB4e7hbbqlWrPGPzUqNGDVSvXt1tWfv27d0eHz16NN/4u+++GyEhIcLxoh599FHX/zdt2oSoqChUrVoVXbp0wciRI/H999+79aoTERHR7WEDl4ioHMndqJw3b55HT+gtaWlpPm//5s2btx2bl+joaI9lFSpUcHuckZFRZPGixowZg379+kGW/z7dXrx4ERs3bsS0adPQrVs31KpVC/v37/f7vomIiMojZlEmIipHFi1ahNdee80tG/H06dNhNBoxceJEt3XDwsLcHut0ugIzHQN/J5DKHatWqxEVFVVg7K1bn/OSV+M397Lcyav8GS9Kq9Vi0aJFePfdd/Hjjz/i4MGDOHXqFH7//Xdcv34dAHDhwgX0798ff/zxh9/3T0REVN6wgUtEVI5otVp888036Nq1K3766SfX8vfeew9GoxFjxoxxLatduzYMBoPrNuW4uDicOXPGrTcyt1tjTIODg1G9enXXbcoajQYnT570uE04r9i8nDlzBufPn0dCQoJr2c8//+y2Tr169fKN37NnDzIzM93GxPoSn1vu98DhcBS4fuXKlTFgwAC39du2bYvt27cDAP7880+kpKQU2MgnIiKiwvEWZSKickav1+O7777D/fff77Z87NixmDlzpuuxTqdDt27dXI/Pnz+PZ555BmfOnHEts1qtOHjwIKZPn46WLVvi119/dT3Xq1cv1/9NJhN69uzpllXY4XDg+PHjmDdvHjp27FhgFmSHw4H+/fsjMTERiqLgp59+wrRp09zWKWge2bS0NAwcOBBpaWlwOBxYsWKFx/58mYc2dw/19u3b82ygDxs2DOPGjcPu3buRnZ3tWn7+/HlcuXLFbV2OxSUiIrp9kuLNxH9ERFQqSZLk9vjs2bOoVq0agJwxp506dcJvv/3mts78+fPx4osvAgBOnTqF5s2be4ypNRqN0Ol0SE9Pd+u93LJliysjcFJSEpo2bYoLFy64xep0OgQHByMtLQ12u921fNGiRejXrx8A4Ny5c25JpWRZdjUgjUajW2MRAKpXr44jR47AYDAAyJlbt127dnm+H3q93mPO3HvuuQc7d+50vV+LFy/G888/73p+3LhxGD9+vNvrzJ2kymAwuG5z3r59O2rVqoXu3bvj22+/de371vO538/q1au7/XBAREREYtiDS0RUToWEhGDDhg1o0qSJ2/KXX34ZX3zxBQCgVq1a+O9//4v4+Hi3dbKzs5GSkuLWuFWpVK4GJgBERUXhxx9/RN26dd1iLRYLkpKS3Bq3AAqcC/a+++7Dk08+6dp37texfPlyt33n9thjj6Fx48ZQFMWjcRsfH4+lS5d6/BhQkAceeAANGjRwW2YymXDt2jVcu3bN47UBgKIoSEtL82jc6vV6zJ071+t9ExERUf7YwCUiKsfCw8OxceNGt8aa0+nE888/j1WrVgHIybx87NgxTJs2De3atUN0dDTUajUMBgNq1KiB7t27Y/bs2bhw4QJatmzptv26deviwIEDWLBgAR566CHExcVBq9VCr9ejatWqeOihhzB16lScOnUKTzzxRL7llGUZy5cvx+zZs9GoUSPo9XpERUWhd+/e2LdvH+6+++5CX+eOHTswZswY1KpVCzqdDnFxcXjxxRexb98+1KxZ06f3TaVSYdOmTRgwYAAqV64MtTrvlBZTpkzBtGnT8Nhjj6FOnTqIjIyESqVCcHAwGjRogCFDhuDgwYPo0qWLT/snIiKivPEWZSIiKnFy36Lcpk0bbN26NXAFIiIiolKBPbhERERERERUJrCBS0RERERERGUCG7hERERERERUJrCBS0RERERERGUCk0wRERERERFRmcAeXCIiIiIiIioT2MAlIiIiIiKiMoENXCIiIiIiIioT2MAlIiIiIiKiMoENXCIiIiIiIioT2MAlIiIiIiKiMoENXCIiIiIiIioT2MAlIiIiIiKiMoENXCIiIiIiIioT2MAlIiIiIiKiMoENXCKiYrZz506MHz8eqampHs+1bdsWbdu2LfYyFYcTJ05g1KhRaNasGcLDwxEZGYn77rsPq1atynP969evo1+/fqhQoQKMRiPuvfde/PTTTx7r/fDDD3j22WfRsGFDaDQaSJKU5/YuXryIHj16oEaNGggKCkJYWBiaNGmC//znP7Db7V6/Dn+XqzCzZ89G3bp1odPpUL16dbzzzjuw2Wxu61y6dAnDhw9HmzZtEB4eDkmSsHjxYqH9ERERlWZs4BIRFbOdO3finXfeybOBO2fOHMyZM6f4C1UMNm7ciHXr1uHxxx/HypUr8eWXX6J27dp48sknMWHCBLd1LRYLOnTogJ9++gkzZ87Et99+i9jYWDz44IPYtm2b27pr1qzB7t27Ub9+fdx111357j8rKwuhoaF466238N1332H58uVo3bo1hg4dipdfftmr11AU5SrIe++9h1dffRU9e/bEjz/+iEGDBmHSpEkYPHiw23qnTp3Cl19+Ca1Wi4cfflhoX0RERGWCQkRExeqDDz5QAChnz54NdFGK1Y0bNxSn0+mx/JFHHlGMRqNiNptdyz7++GMFgLJz507XMpvNptSvX1+5++673eIdDofr/4MHD1Z8PbX16tVLUavVbvvPT3GW6+bNm4per1cGDhzotvy9995TJElSjhw5kue+9u7dqwBQFi1a5NP+iIiIygL24BIRFaPx48fj9ddfBwBUr14dkiRBkiRs3boVgOctyufOnYMkSfjggw/w/vvvo1q1ajAYDGjbti1OnDgBm82GN998E/Hx8QgLC0OPHj1w/fp1j/2uWLEC9957L4KCghAcHIwuXbrgwIEDxfGSXSpUqJDnbbp33303srOzkZyc7Fq2Zs0a1KlTB/fee69rmVqtRt++fbFnzx5cvnzZtVyWb+9UFh0dDVmWoVKpCl23OMu1YcMGmM1mPP/8827Ln3/+eSiKgrVr1/ptX0RERGUFz4hERMXohRdewNChQwEAq1evxq5du7Br1y40bdq0wLiPP/4YO3bswMcff4xPP/0Uf/31Fx599FEMGDAAN27cwGeffYapU6di8+bNeOGFF9xiJ02ahKeeegr169fH119/jS+++AIZGRm4//77cfTo0ULLbLfbvfpTFEXoPdmyZQuio6MRExPjWnb48GE0atTIY91by44cOSK0LwBQFAV2ux0pKSlYsWIFFi9ejJEjR0KtVhcaW5TlymtfANCwYUO35RUrVkSFChVczxMREdHfCj+bExGR31SuXBlVq1YFADRp0gTVqlXzKi48PBxr16519dTdvHkTw4cPR926dfHtt9+61vvrr78wY8YMpKenIzQ0FBcvXsS4ceMwZMgQzJo1y7Vep06dULt2bbzzzjtYsWJFvvs9d+4cqlev7lUZt2zZ4nOCrE8//RRbt27FzJkz3XpQk5KSEBkZ6bH+rWVJSUk+7eef3n//fYwePRoAIEkSxowZg4kTJ3oVW5TlymtfOp0OQUFBee7Pn/siIiIqK9jAJSIqBR5++GG321Dr1asHAHjkkUfc1ru1/MKFC2jQoAF+/PFH2O12PPvss26ZgvV6Pdq0aYMtW7YUuN/4+Hjs3bvXqzLWqVPHq/VuWb9+PQYPHownnnjC1av9TwVlHRbNSAwA/fr1Q8eOHZGcnIyff/4ZH3zwAdLS0jB79mwAOT28DofDLeafvbv+LlfuDM4qlcq1naJ6D4iIiMoqNnCJiEqB3L2GWq22wOVmsxkAcO3aNQBAixYt8txuYWM3tVotGjdu7FUZvRnDesuPP/6Inj17olOnTvjyyy89GmtRUVF59lDeGqebVy+qt+Li4hAXFwcA6Ny5MyIiIvDmm2+if//+aNKkCZYsWeIx7vXW7ddFUS6NRuP2eNGiRejXrx+ioqJgNpuRnZ0No9Hosb9mzZr5vC8iIqKyjg1cIqIyrEKFCgCAVatWISEhwef4orhF+ccff0T37t3Rpk0bfPPNN65G+T81bNgQhw4d8lh+a1mDBg28KpM37r77bgA58/Q2adIEjz76aL691kVRrtz7uvV+3xp7e+jQIbRs2dL1fGJiIm7evOnX94CIiKisYAOXiKiY6XQ6AIDJZCryfXXp0gVqtRqnT5/G448/7nO8v29R3rhxI7p3747WrVtj7dq1rvcitx49emDQoEH47bffXI07u92OpUuXomXLloiPj/f+RRTi1m3atWrVApDTSxsVFVVs5WrevHmeyx988EHo9XosXrzYrYG7ePFiSJKE7t27+7wvIiKiso4NXCKiYnarZ27mzJl47rnnoNFoUKdOHYSEhPh9X9WqVcOECRMwduxYnDlzBg8++CAiIiJw7do17NmzB0FBQXjnnXfyjddqtfk2wHy1fft2dO/eHXFxcRgzZgz++OMPt+fr16+P0NBQAED//v3x8ccf48knn8SUKVMQExODOXPm4Pjx49i8ebNb3Pnz512N8NOnTwPI6bG+9fpvlX/cuHG4du0aHnjgAVSqVAmpqanYsGEDFixYgCeffNKrW36Lolz5iYyMxL///W+89dZbiIyMROfOnbF3716MHz8eL7zwAurXr++2/q1tnzlzBgDw+++/Izg4GADwxBNPFPraiIiIyoSAzsJLRFROjR49WomPj1dkWVYAKFu2bFEURVHatGmjtGnTxrXe2bNnFQDKBx984Ba/ZcsWBYCycuVKt+WLFi1SACh79+51W7527VqlXbt2SmhoqKLT6ZSEhATliSeeUDZv3lwkry8v48aNUwDk+3frPbglMTFRefbZZ5XIyEhFr9cr99xzj7Jp0yaP7d56zXn9Pffcc671vvvuO6Vjx45KbGysolarleDgYOXuu+9WZs2apdhsNq9fh7/LVZiZM2cqd9xxh6LVapWqVasq48aNU6xWq8d6Bb23RERE5YWkKIITFxIRERERERGVIAWnzyQiIiIiIiIqJdjAJSIiIiIiojKBDVwiIiIiIiIqE9jAJSIiIiIiojKBDVwiIiIiIiIqE9jAJSIiIiIiojJBHegCFDWn04krV64gJCQEkiQFujhEREREROWaoijIyMhAfHw8ZLn09beZzWZYrdYi2bZW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J4oPEXs/swWnUzHbxbLUquTiHRspWk5NMZfToXf/PJ2yGrkvxYN0asha91Nt7vfTKauRezRhXnFGjdgxWzRONKtxmkWsIZvXcdZbonU0RCd2I5veKvbeqPXBbo8dDs9jsUMXBIfO6LZM5ch1jsjrO6LRQtEUPFemtxRN4evkRdIJXo9oxI6FSBXLD6FkpgrFOU1ijW1FcLywI9e1iD2P74g96Srsua5FHGaxawrROV1Vet+ngnFkc2hCWTF69GiMHDkSEyZMQEhICL755hvExMTgmWeewYMPPhiQMnl9ZJ81a5bXDdZVq1YJF4iIiIiIiKhEC3AW5ZLi2LFj+OqrrwAAarUaJpMJwcHBmDBhAh577DG88sorxV4mr36q2bJlCyIjI73e6Pr161GpUiXhQhEREREREZVYsvx3I9dvf35OWlUMgoKCYLFYAADx8fE4ffq067mbN28GpExe9eC2adPGp422bt1aqDBERERERERUOtxzzz3YsWMH6tevj0ceeQQjR47EoUOHsHr1atxzzz0BKZPQ4BOn04lTp07h+vXrcDrdx4E88MADfikYERERERFRSSTJsvC45YK2WdpMmzYNmZmZAIDx48cjMzMTK1asQK1atTB9+vSAlMnnBu7u3bvx9NNP4/z58x7zL0mSBIeD8x8SERERERGVdTVq1HD932g0Ys6cOQEsTQ6fG7gvv/wymjdvjnXr1qFixYqQpOLNWklERERERBRQUhEkmZJKX5IpEb7kdgJyOlH379+PhIQEr9b3uYF78uRJrFq1CrVq1fI1lIiIiIiIiMqx1NRUzJgxA2FhYYWuqygKBg0a5NNdwj43cFu2bIlTp06xgUtEREREROUTpwm6LX369PF6CtqhQ4f6tG2vGrgHDx5028HIkSORmJiIhg0bQqNxnw28UaNGPhWAiIiIiIiIyofcSYoLk5GR4dP6XjVwGzduDEmS3JJK9e/f3/X/W88xyRQREREREZV1zKLszmq14uzZs6hZsybU6sKbmJcvX0alSpUKXOfLL7/EM88843NZvGrgnj171ucNExERERERlUm8RRkAkJ2djaFDh2LJkiUAgBMnTqBGjRoYNmwY4uPj8eabb+YZ16lTJ+zYsQMRERF5Pr9s2TI8//zzQg1cr34mSEhIcP2dP38elSpVcluWkJCASpUq4fz58z4XgIiIiIiIiEqf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48ePIzo6Ot+4+fPnw2LJ+VFOURRMmjQJERERiIuLQ3h4OEaMGCE8nZDPDdyNGzfi/fff97jHu3bt2j5PE0RERERERFTqlIBblPP669evn9t6AwcORHZ2NsLCwvLcjl6vx+zZs5GUlITs7Gx8//33HlmcC/LYY49hwoQJsP3vDg5JknDhwgW8+eabePzxx/ONe+WVV1xTG82fPx+TJk3CW2+9hV9//RXvv/8+PvvsM8yZM8frcvyTz7coZ2VlufXc3nLz5s1CM3wRERERERFR2fDhhx/i4YcfRkxMDEwmE9q0aYPExETce++9eO+99/KNU5S/h88sXLgQ7777rmv62VatWrka3kOGDPG5TD43cB944AF8/vnnePfddwHktNKdTic++OADtGvXzucCEBERERERlSqSlPPn722WMqGhodi+fTt+/vln7N+/H06nE02bNvVqDO+tqYjOnj2LDh06uD3Xvn17V4PXVz43cD/44AO0bdsWv//+O6xWK9544w0cOXIEycnJ2LFjh1AhiIiIiIiIqHRq37492rdv71PMhg0bXEmxbs3Mc4vJZIIsmIDX56j69evj4MGDaNGiBTp16oSsrCz07NkTBw4cQM2aNYUKQUREREREVGoEeAxuSfLTTz+ha9euqFmzJmrVqoWuXbti8+bNhcY999xz6N69Oy5duoSffvrJ7bldu3YJty197sEFcubBnTBhgtAOiYiIiIiIqPT7z3/+g9deew1PPPEEXn31VQDA7t278fDDD2PatGn5jqEtLENyXFwcJk+eLFQmoQbur7/+ik8++QRnzpzBypUrUalSJXzxxReoXr06WrduLVQQIiIiIiKi0uDW1D7+3mZpM3nyZEyfPt2tITts2DDcd999eO+994SSRAFA165dhcvk87v4zTffoEuXLjAYDNi/f79r/qKMjAxMmjRJuCBERERERERUeqSnp+PBBx/0WN65c+c858fN7cyZM/j888/x/vvv48MPP8Q333zjVVxBfG7gTpw4EfPmzcOCBQug0Whcy1u1aoX9+/ffVmGIiIiIiIhKPEkGZD//lcIe3G7dumHNmjUey7/99ls8+uij+cZlZWXhySefRK1atdCvXz+MGTMGH330EXr37o1KlSrh448/Fi6Tz7coHz9+HA888IDH8tDQUKSmpgoXhIiIiIiIqFQoiqRQpbCBW69ePbz3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d3Y4JoeTor700kuYMGECfvzxRwgh8Pfff+Odd97Bq6++yqGBiIiIiIiIKog333wT2dnZqF27Nv7zn/9g6NChqFu3LnJycvDmm28WW/6XX35B37594eXlhQMHDkCvz01SmZGRgXfffVcqJoev4D755JMwmUyYPn06srOzMWzYMFSrVg2ffPIJHn/8cakgSkPOjdtQe3rk/i0UAALzvJ8CKGx/NjFLPkem0kglp4bKU27AciF5NUWfkilXLlUuG7NsnAql/OA0CpXK5n+DQgn42s5jSM+CMs8t3KY8daiHEvC0bTP62xlQIE85ndyYfMIst21kt6mQHY5D7SG3Po233PoUcnWvUZbuT6BZRtt60Bvz10u20QKz2na6l0GuHgKUcvsYhU7uu+uplas/rZdcOS+13ED3Qm5gAPio5NqLKj3R5n9lTv6xopUZSVCZPG2nZdveYqnU5c84rcxKhtKc5/uml3sMSOhzpMrBw0uqmOweW5FxS7IkoEgrOFNncVTFz+JU5hzbOrQUsA+wnD0Is9Y2MpPBtm0ZjfnLGS+dhEGT5xNJjhMrDHLjnguj3C3nFsn1ZVy+IVUuU7KcLjVbqpwpR+580qizLadXa/LNk5Oqh8Vku92VSrl9qEor940wSxzLykOuBiGE0xMqlsUEjXPmzMH48ePh7W17zM/OzsacOXPwxhtvFFn+7bffxhdffIFRo0YhJibGOr1jx452dZALInU28NRTT+Gpp57CrVu3YLFYEBISIrVyIiIiIiKisoa3KOe6k+04r8OHDyMoKKjY8qdOnULXrl3zTff390dqaqpUTHI/d/+rcuXK91KciIiIiIiIypjAwEAoFAooFApERUXZdHLNZjMyMzMxfvz4YpcTFhaGs2fPIjIy0mb6zp07Ubt2banY7OrgtmjRotixiu44cOCAVCBERERERERlRRm84Oo0H3/8MYQQGDt2LObMmYOAgADre1qtFpGRkejQoUOxy3nmmWfwwgsv4Ntvv4VCocD169exe/duTJs2rdjbmwtjVwd30KBB1r91Oh0WLVqEhg0bWoPes2cP/vnnHzz33HNSQRAREREREVHZMHr0aAC5I+x06tQJarXcjcHTp09HWloaevToAZ1Oh65du8LDwwPTpk3DxIkTpZZpVySzZs2y/v1///d/eP755/HWW2/lm+fKlStSQRAREREREZUV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yb9+Q5YBMNo+HiAkrwaV9vmI/L5C7jshW38WybuaVGq5tq3S2maxLShuYRb5ppvh/le6zeXgajyv4LqvCtPBJSIiIiIicgZLCXRwLezgOgWzKBMREREREVG5wCu4REREREREDjCLErhFmck9nYJXcImIiIiIiKhccGkH9/PPP0fTpk3h7+8Pf39/dOjQAevWrbO+L4TA7NmzER4eDi8vL3Tv3h3//POPCyMmIiIiIqKK7k6SKWe/6N65tINbvXp1zJs3D/v378f+/ftx//33Y+DAgdZO7Pz58/HRRx/hs88+w759+xAaGorevXsjI4Nj5xEREREREZEtl3ZwH374YTz00EOIiopCVFQU3nnnHfj6+mLPnj0QQuDjjz/GzJkzMWTIEDRu3BhLly5FdnY2li9f7sqwiYiIiIioAuMVXPflNs/gms1mxMTEICsrCx06dMCFCxeQmJiIPn36WOfx8PBAt27dsGvXrkKXo9frkZ6ebvMiIiIiIiKi8s/lHdyjR4/C19cXHh4eGD9+PFatWoWGDRsiMTERAFC1alWb+atWrWp9ryBz585FQECA9RUREVGi8RMRERERUcVisogSedG9c3kHt169ejh06BD27NmDZ599FqNHj8bx48et7ysUCpv5hRD5pt1txowZSEtLs76uXLlSYrETEREREVHFw1uU3ZfLx8HVarWoU6cOAKB169bYt28fPvnkE7z88ssAgMTERISFhVnnT0pKyndV924eHh7w8PAo2aCJiIiIiIjI7bj8Cm5eQgjo9XrUqlULoaGh2LRpk/U9g8GAuLg4dOzY0YUREhERERFRRWYpgau3Fl7BdQqXXsF99dVX8eCDDyIiIgIZGRmIiYnBtm3bsH79eigUCkyePBnvvvsu6tati7p16+Ldd9+Ft7c3hg0b5sqwiYiIiIiIyA25tIN748YNjBw5EgkJCQgICEDTpk2xfv169O7dGwAwffp05OTk4LnnnkNKSgratWuHjRs3ws/Pz5VhExERERFRBWYWAmbh3Cuuzl5eReXSDu7ixYuLfF+hUGD27NmYPXt26QREREREREREZZbLk0wRERERERGVJSWR9ZhZlJ3D7ZJMEREREREREcngFVwiIiIiIiIH8Aqu+6owHVyT3gSTMrfRmET+C9cmnQkKvclmmjBbpNZlMcs1TrNJbn2yD6RrlHIX8JUqhVQ5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gMLBuwiEEA6XcSe8gktEREREROQAYREl8gIAf39/m1dRHdxJkyZhzZo12Lp1q03m4x07diApKQk1atSAWq2GWq3GpUuXMHXqVERGRgIAQkNDYTAYkJKSYrPMpKQkVK1a1fkbrZSwg0tERERERFSGCCEwceJExMbGYsuWLahVq5bN+yNHjsSRI0dw6NAh6ys8PBwvvfQSNmzYAABo1aoVNBoNNm3aZC2XkJCAY8eOoWPHjqX6eZyJtygTERERERE54q4rrs5cpr0mTJiA5cuXY/Xq1fDz87M+MxsQEAAvLy8EBwcjODjYpoxGo0FoaKh1rNyAgACMGzcOU6dORXBwMIKCgjBt2jQ0adLEmlW5LGIHl4iIiIiIqAz5/PPPAQDdu3e3mR4dHY0xY8bYvZwFCxZArVZj6NChyMnJQc+ePbFkyZIyOwYuUIE6uClGM3T/PittVuVP759iNEOVJ+2/bFp3r1LO6y47dIRKK1dOWYby1pvzDK5nLmA4HrPJkm++fPMUMOyM2WDONxaiUnLMArPkkBOyw3+U5FAVBSntOGXLWfKMVWHQ5F+OIcsA5BnuJysp2+Z/o4c2X7mM65nQ6G2H8LhxKlkqzmrNs6TK+YX5SpXzr+YnVc5wJb34mQqQJjsopiTZscTzjl1uNuWPO8tkgSrP9LzN06zOXy7dZIEq7/5LyO0nZD+fr+RwTbK7F3O23Oe7F7LjvcrKV/fK/CeQtw1mqPRFD+llVuVvM6lGS77zmLxt1F7SdShdTjZOubYtW++yn0/ydCvfd7eg9ZtF/mEtVU4em7VYBsf32WbJMZPdiUUI6SE+i1qmvWTG4L148WK+aZ6enli4cCEWLlzo8PLcVYXp4BIRERERETmDEM6/RVmm00r5MckUERERERERlQu8gktEREREROQAUQJJppyetKqC4hVcIiIiIiIiKhd4BZeIiIiIiMgBFgugcPIV13KQe8st8AouERERERERlQu8gktEREREROQAIYTTsx4zi7Jz8AouERERERERlQu8gktEREREROQAYcl9OXuZdO/YwSUiIiIiInKAxSJKIMkUb1F2Bt6iTEREREREROUCr+ASERERERE5QFgEhJOvuDp7eRUVr+ASERERERFRucAruERERERERA7gFVz3xSu4REREREREVC7wCi4REREREZEDLEJAIZycRdnJy6uo2MEt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mkWu257PVadOHesV2hMnTuCZZ55BYmKidRl79+7Fu+++i/379+Py5cuIjY3FzZs3rR3gyMhIHDlyBKdOncKtW7cq9NBIRERUjrno2V8ionJr9OjRAkC+1+jRo4UQQmzbtk20adNGaLVaERoaKl5++WVhNBqt5bt162ZNqnTHwIEDreWFEOLEiROiU6dOQqvVivr164u1a9cKAGL9+vVCiPxJpoQQ4s033xShoaFCoVBYl5WWliaGDh0q/P39RUREhFiyZIlNkikhhDh16pTo3Lmz0Gq1IioqSqxfv94myZQQQiQkJIhRo0aJypUrCw8PD1G7dm3x1FNPibS0tAK3UWFJrzIyMsSIESOEt7e3qFq1qpg/f36+7WEwGMQbb7whIiMjhUajEaGhoWLw4MHiyJEj1nm++uorUa1aNeHl5SUGDRok3n77bREaGmp9f9asWaJZs2b54kpPTxeTJk0S4eHhQqPRiIiICDF8+HBx+fJl6zzr168XHTt2FF5eXsLf31+0bdtWfPXVV0IIIVatWiXatWsn/P39hY+Pj2jfvr3YvHlzgdsgr+I+V3Jyshg4cKDw9fUVISEh4rXXXhOjRo2yJo46fvy46Nu3r6hSpYrw8PAQUVFRYuHChdblJyUlid69ewtfX18BQGzdutX6HpNMERFReaEQwo6HnoiIyK399ddf6Ny5M86ePYv77rvP1eEUa9u2bejRowdSUlJsxqctKU899RROnjyJHTt2lPi6yqIlS5Zg8uTJSE1NdXUoRERE94TP4BIRlUGrVq2Cr68v6tati7Nnz+KFF15Ap06dykTn9m7Vq1fHww8/jBUrVjh1uR988AF69+4NHx8frFu3DkuXLsWiRYucuo7ywtfXFyaTCZ6enq4OhYiI6J6xg0tEVAZlZGRg+vTpuHLlCipXroxevXrhww8/dHVYdmvXrh3OnDkD4H9Zgp3p77//xvz585GRkYHatWvj008/xf/93/85fT322rFjBx588MFC37/zTLQrHDp0CADyZW8mIiIqi3iLMhERUQnLycnBtWvXCn2/qKzMREREZD92cImIiIiIiKhc4DBBREREREREVC6wg0tERERERETlAju4REREREREVC6wg0tERERERETlAju4REREREREVC6wg0tERERERETlAju4REREREREVC6wg0tERERERETlwv8DK3rp53zGuLYAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# # Create a figure and axis and plot the air temperature\n", "fig, ax = plt.subplots(figsize=(12, 6))\n", @@ -826,16 +286,7 @@ "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_24883/1840452313.py:2: SerializationWarning: saving variable air with floating point data as an integer dtype without any _FillValue to use for NaNs\n", - " ds.to_netcdf(\"0.air_original.nc\")\n" - ] - } - ], + "outputs": [], "source": [ "# Saving the dataset as NetCDF file\n", "ds.to_netcdf(\"0.air_original.nc\")" @@ -851,7 +302,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "19032fcd-93bc-48b8-ba1f-beba9673491b", "metadata": { "tags": [] @@ -881,6 +332,7 @@ }, "outputs": [], "source": [ + "%%capture\n", "fn = \"air_bitrounded.zarr\" # Output filename\n", "rounded_ds, keepbits = bitrounding(ds)\n", "rounded_ds.to_compressed_zarr(fn, mode=\"w\")" @@ -906,24 +358,12 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "998581b5-6ad9-4f6f-9c61-d0bf1486ec7f", "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "7.5M\t0.air_original.nc\n", - "1.7M\t1.air_compressed_all.nc\n", - "1.3M\t2.air_bitrounded_compressed.nc\n", - "776K\t3.air_chunked_bitr_compressed.nc\n", - "1.1M\tair.zarr\n" - ] - } - ], + "outputs": [], "source": [ "!du -hs *.nc *.zarr" ] From 5f38d1a750ff5ec73f4e77be7cb684547ca7e43d Mon Sep 17 00:00:00 2001 From: Hauke Schulz <43613877+observingClouds@users.noreply.github.com> Date: Sun, 25 Feb 2024 11:35:46 -0800 Subject: [PATCH 52/53] update doctest with xarray nbytes in __repr__ (v2024.02.0) --- xbitinfo/xbitinfo.py | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/xbitinfo/xbitinfo.py b/xbitinfo/xbitinfo.py index 0415147d..4450a98f 100644 --- a/xbitinfo/xbitinfo.py +++ b/xbitinfo/xbitinfo.py @@ -152,13 +152,13 @@ def get_bitinformation( # noqa: C901 ------- >>> ds = xr.tutorial.load_dataset("air_temperature") >>> xb.get_bitinformation(ds, dim="lon") # doctest: +ELLIPSIS - + Size: 652B Dimensions: (bitfloat32: 32) Coordinates: - * bitfloat32 (bitfloat32) >> xb.get_bitinformation(ds) - + Size: 1kB Dimensions: (bitfloat32: 32, dim: 3) Coordinates: - * bitfloat32 (bitfloat32) >> ds = xr.tutorial.load_dataset("air_temperature") >>> info_per_bit = xb.get_bitinformation(ds, dim="lon") >>> xb.get_keepbits(info_per_bit) - + Size: 28B Dimensions: (inflevel: 1) Coordinates: - dim >> xb.get_keepbits(info_per_bit, inflevel=0.99999999) - + Size: 28B Dimensions: (inflevel: 1) Coordinates: - dim >> xb.get_keepbits(info_per_bit, inflevel=1.0) - + Size: 28B Dimensions: (inflevel: 1) Coordinates: - dim >> info_per_bit = xb.get_bitinformation(ds) >>> xb.get_keepbits(info_per_bit) - + Size: 80B Dimensions: (dim: 3, inflevel: 1) Coordinates: - * dim (dim) Date: Sun, 25 Feb 2024 00:57:58 +0000 Subject: [PATCH 53/53] Update GitHub Action Versions --- .github/workflows/benchmarks.yml | 4 ++-- .github/workflows/ci.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index c7ef0c1a..6162f8f7 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 0 - name: Setup Miniconda - uses: conda-incubator/setup-miniconda@v3.0.1 + uses: conda-incubator/setup-miniconda@v3.0.2 with: # installer-url: https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh installer-url: https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh @@ -74,7 +74,7 @@ jobs: cp benchmarks.log .asv/results/ working-directory: ${{ env.ASV_DIR }} - - uses: actions/upload-artifact@v4.3.0 + - uses: actions/upload-artifact@v4.3.1 if: always() with: name: asv-benchmark-results-${{ runner.os }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 06a86c95..2dd80b37 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,7 +33,7 @@ jobs: with: fetch-depth: 0 - name: Set up conda - uses: conda-incubator/setup-miniconda@v3.0.1 + uses: conda-incubator/setup-miniconda@v3.0.2 with: auto-update-conda: false channels: conda-forge @@ -60,7 +60,7 @@ jobs: shell: 'bash -l {0}' steps: - uses: actions/checkout@v4.1.1 - - uses: conda-incubator/setup-miniconda@v3.0.1 + - uses: conda-incubator/setup-miniconda@v3.0.2 with: channels: conda-forge miniforge-variant: Mambaforge @@ -91,7 +91,7 @@ jobs: steps: - uses: actions/checkout@v4.1.1 - name: Set up conda - uses: conda-incubator/setup-miniconda@v3.0.1 + uses: conda-incubator/setup-miniconda@v3.0.2 with: auto-update-conda: false channels: conda-forge @@ -138,7 +138,7 @@ jobs: with: python-version: '3.11' - name: Set up Julia - uses: julia-actions/setup-julia@v1.9.5 + uses: julia-actions/setup-julia@v1.9.6 with: version: 1.7.1 - name: Install dependencies