Source code for pycif.plugins.models.dalecbethy.io.inputs.make_forcing

import os
from logging import info

import pandas as pd
import xarray as xr

from ......utils import path
from ......utils.hdf5 import _hdf5_lock
from ...ini_mapper import meteo_components, static_components


[docs] def make_meteo(self, datastore, runsubdir, mode, datei, datef): """Build D&B's ``input/dynforcing.nc`` from the ``meteo`` data-store. See :func:`_make_forcing` for the shared logic (also used by :func:`make_static`): for each of D&B's mandatory hourly meteo components (see :data:`pycif.plugins.models.dalecbethy.ini_mapper.meteo_components`), either the raw forcing file is linked as-is, or the component is (re)written via ``self.meteo.write``. The mandatory ``yyyymmddhh`` calendar variable -- not itself a per-component mapper input -- is then copied as-is from the original raw file, for the hours covered by ``[datei, datef]``. Args: self: the dalecbethy model Plugin. datastore (dict): the pyCIF data-store, keyed by ``("meteo", <component>)``. runsubdir (str): sub-directory for the current simulation. mode (str): running mode ('fwd', 'tl', 'adj'); only 'fwd' is supported so far (D&B has no adjoint/tangent-linear wiring for meteo yet). datei, datef: date interval of the sub-simulation. Raises: NotImplementedError: if ``mode != "fwd"``. """ _make_forcing( self, datastore, runsubdir, mode, datei, datef, components=meteo_components, trid_type="meteo", subplug=self.meteo, target_name="dynforcing.nc", needs_calendar=True, )
[docs] def make_static(self, datastore, runsubdir, mode, datei, datef): """Build D&B's ``input/staticforcing.nc`` from the ``dalecbethy_static`` data-store. See :func:`_make_forcing` for the shared logic (also used by :func:`make_meteo`): for each of D&B's time-invariant land-surface/PFT components (see :data:`pycif.plugins.models.dalecbethy.ini_mapper.static_components`), either the raw forcing file is linked as-is, or the component is (re)written via ``self.static.write``. Unlike ``dynforcing.nc``, there is no ``yyyymmddhh`` calendar variable to copy. Args: self: the dalecbethy model Plugin. datastore (dict): the pyCIF data-store, keyed by ``("dalecbethy_static", <component>)``. runsubdir (str): sub-directory for the current simulation. mode (str): running mode ('fwd', 'tl', 'adj'); only 'fwd' is supported so far (D&B has no adjoint/tangent-linear wiring for static forcing yet). datei, datef: date interval of the sub-simulation. Raises: NotImplementedError: if ``mode != "fwd"``. """ _make_forcing( self, datastore, runsubdir, mode, datei, datef, components=static_components, trid_type="dalecbethy_static", subplug=self.static, target_name="staticforcing.nc", needs_calendar=False, )
def _make_forcing(self, datastore, runsubdir, mode, datei, datef, *, components, trid_type, subplug, target_name, needs_calendar): """Shared logic behind :func:`make_meteo` and :func:`make_static`. Mirrors ``pycif.plugins.models.chimere.io.inputs.make_meteo.make_meteo``: for each of ``components``, if it was not materialized in memory (no ``"spec"`` key in the data-store, i.e. ``force_loadin`` was not needed -- see :func:`pycif.plugins.models.dalecbethy.ini_mapper.ini_mapper`), the *whole* original raw forcing file is simply linked to ``runsubdir/input/<target_name>``. Unlike CHIMERE (one file per species), a single D&B raw file already carries every component of a given kind (dynamic or static) natively, so one link covers every component -- no need to read/rewrite anything. Otherwise (a transform perturbed/materialized the data), it is written via ``subplug.write`` (see :func:`pycif.plugins.datastreams.fluxes.dalecbethy.write.write`). Args: self: the dalecbethy model Plugin. datastore (dict): the pyCIF data-store. runsubdir (str): sub-directory for the current simulation. mode (str): running mode ('fwd', 'tl', 'adj'); only 'fwd' is supported so far. datei, datef: date interval of the sub-simulation. components (list[str]): component names to look for in the data-store, keyed as ``(trid_type, component)``. trid_type (str): first element of the data-store key, e.g. ``"meteo"`` or ``"dalecbethy_static"``. subplug: the shared fluxes sub-plugin instance to write components with (``self.meteo`` or ``self.static``). target_name (str): name of the native file to build, within ``runsubdir/input`` (``"dynforcing.nc"`` or ``"staticforcing.nc"``). needs_calendar (bool): whether the mandatory ``yyyymmddhh`` calendar variable must be copied in, once any component was written (dynamic forcing only). Raises: NotImplementedError: if ``mode != "fwd"``. """ if mode != "fwd": raise NotImplementedError( f"dalecbethy make_forcing ({trid_type}): mode={mode!r} not " f"implemented yet" ) ddi = min(datei, datef) ddf = max(datei, datef) input_dir = os.path.join(runsubdir, "input") os.makedirs(input_dir, exist_ok=True) target_file = os.path.join(input_dir, target_name) wrote_any = False linked = False ref_file = None for comp in components: trid = (trid_type, comp) if trid not in datastore: continue tracer = datastore[trid] tracer_data = tracer["data"][ddi] if ref_file is None: # The original (un-materialized) raw file, kept around so that # _write_yyyymmddhh can copy the calendar variable from it even # when every component ends up (re)written below. ref_file = ddi.strftime( os.path.join(tracer["dirorig"], tracer["fileorig"])) if "spec" not in tracer_data: # Not perturbed/materialized: the raw file already carries # every component natively, so a single link suffices. if not os.path.isfile(target_file): path.link(ref_file, target_file) linked = True else: # Replace an existing link by a real copy before appending to # it, so we never write through the link into the raw file. if linked: path.copyfromlink(target_file) linked = False xr_mode = "a" if os.path.isfile(target_file) else "w" subplug.write( comp, target_file, tracer_data["spec"], mode=xr_mode) wrote_any = True if wrote_any and needs_calendar: _write_yyyymmddhh(ref_file, target_file, ddi, ddf) if wrote_any or linked: info(f"D&B forcing written to '{target_file}'") def _write_yyyymmddhh(ref_file, dynforcing_file, ddi, ddf): """Copy the ``yyyymmddhh`` calendar variable from the raw forcing file. D&B's own ``time`` records mark the *end* of each hourly period (see ``pycif.plugins.datastreams.fluxes.dalecbethy.fetch.fetch``), hence the ``(times > ddi) & (times <= ddf)`` mask below, matching that same convention. Args: ref_file (str): the original, un-materialized dynamic forcing file to copy the calendar variable from (same file any unperturbed component in this sub-simulation would have been linked from, see :func:`_make_forcing`). dynforcing_file (str): the ``dynforcing.nc`` file to append to. ddi, ddf: date interval of the sub-simulation. """ with _hdf5_lock: with xr.open_dataset(ref_file) as ds: times = pd.DatetimeIndex(ds["time"].values) mask = (times > ddi) & (times <= ddf) yyyymmddhh = ds["yyyymmddhh"].isel(time=mask).values with _hdf5_lock: xr.Dataset( {"yyyymmddhh": (("ntc", "time"), yyyymmddhh)} ).to_netcdf(dynforcing_file, mode="a")