import os
from logging import debug, info
from ......utils import path
from .utils import find_unique_match
# Reference obs.nml defaults, as shipped with the D&B sources (see
# model_sources/dalecbethy/obs.nml); 'synthetic=True' makes D&B compute
# and write out its own pseudo-observations instead of expecting real
# observation files, which is what a pyCIF forward run needs.
# TODO: expose these as proper 'input_arguments' once the intended use of
# the SIF/L-VOD/soil-moisture/slope/FAPAR assimilation streams within CIF
# is decided (presently only NEE is wired as an output, see __init__.py).
_OBS_NML_DEFAULTS = {
"asssif": ".false.",
"asslvod": ".false.",
"asssm": ".false.",
"assslope": ".false.",
"assfapar": ".false.",
"assnee": ".false.",
"synthetic": ".true.",
"sifsrc": "'gu'",
"conv743": "721.056688",
}
[docs]
def make_auxiliary(self, ddi, runsubdir,
do_simu=True, mode="fwd",
**kwargs):
"""Stage every file D&B's ``runmodel.x`` expects, in ``runsubdir``.
D&B hard-wires relative input paths in its Fortran sources (e.g.
``src/prior.f90``: ``input/core-params.csv``;
``src/ncread_forcing.f90``: ``input/staticforcing.nc``,
``input/dynforcing.nc``; ``src/obs.f90``: ``obs.nml``), so this
function must reproduce, inside ``runsubdir``, the same layout as the
reference D&B checkout (see the ``Jobs``/``Makefile`` "model_input"
target for the equivalent shell-level logic):
* neither ``runsubdir/input/staticforcing.nc`` nor
``runsubdir/input/dynforcing.nc`` are linked here: both are
(re)written per sub-simulation, from the ``("dalecbethy_static",
<component>)``/``("meteo", <component>)`` data-store entries, by
:func:`~.native2inputs.native2inputs`/
:func:`~.make_forcing.make_static`/:func:`~.make_forcing.make_meteo`
(see :func:`pycif.plugins.models.dalecbethy.ini_mapper.ini_mapper`);
* link the four prior-parameter CSV files for ``self.domain_name``
(from ``self.parameters_dir``) to
``runsubdir/input/{core,sif,lvod,slope}-params.csv``;
* write ``runsubdir/obs.nml`` (see :data:`_OBS_NML_DEFAULTS`);
* create the (empty) ``runsubdir/diagout`` directory D&B writes its
NetCDF diagnostics to.
The compiled executable itself is staged into ``runsubdir`` by
:func:`~.run.run`, not here -- ``run.run`` is also what lazily triggers
:func:`~.compile.compile` on first use (see the package ``__init__.py``
note on why compilation cannot happen any earlier).
Args:
self: the dalecbethy model plugin.
ddi (datetime.datetime): start date of the present simulation
period.
runsubdir (str): path to the current sub-simulation work directory.
do_simu (bool): if False, the simulation does not need to be run.
mode (str): the running mode ('fwd', 'tl', 'adj').
"""
if not do_simu:
debug("Doing nothing in make_auxiliary as do_simu is False.")
return
if mode != "fwd":
raise NotImplementedError(
f"dalecbethy make_auxiliary: mode={mode!r} not implemented yet"
)
domain_name = self.domain_name
input_dir = os.path.join(runsubdir, "input")
os.makedirs(input_dir, exist_ok=True)
os.makedirs(os.path.join(runsubdir, "diagout"), exist_ok=True)
# --- prior parameter files (D&B's own control vector, src/prior.f90)
for kind in ("core", "sif", "lvod", "slope"):
_link_unique_match(
self.parameters_dir, f"{domain_name}-{kind}-params.csv",
os.path.join(input_dir, f"{kind}-params.csv"))
# --- namelists (D&B reads these from the run directory root)
with open(os.path.join(runsubdir, "obs.nml"), "w") as f:
f.write("&obs\n")
for key, val in _OBS_NML_DEFAULTS.items():
f.write(f" {key}={val}\n")
f.write("/\n")
info(f"D&B inputs staged in {runsubdir} for domain '{domain_name}'")
def _link_unique_match(directory, pattern, dest):
"""Link the single file matching ``pattern`` in ``directory`` to ``dest``."""
path.link(find_unique_match(directory, pattern), dest)