Source code for pycif.plugins.models.dalecbethy.run

import os
import subprocess
from logging import debug, info

from ....utils import path
from ....utils.check.errclass import CifRuntimeError
from .utils import compile as _compile


[docs] def run(self, runsubdir, mode, workdir, ddi, nbproc=1, do_simu=True, approx_transf=False, ref_fwd_dir="", overlap=False, datastore=None, **kwargs): """Compile (if needed) and run the D&B executable for one sub-period. D&B is a self-contained Fortran executable (not a Python model like ``satwetch4``): this function launches it as a subprocess in ``runsubdir``, where :func:`~.io.inputs.make_auxiliary.make_auxiliary` and :func:`~.io.native2inputs.native2inputs` are expected to have already staged everything the executable needs (``input/*.nc``, ``input/*-params.csv``, ``opt.nml``, ``obs.nml``), following the fixed, hard-wired relative paths used throughout the D&B sources (see e.g. ``src/prior.f90``, ``src/ncread_forcing.f90``: ``input/...``, ``mode==adj`` is not implemented yet. This function also triggers :func:`~.compile.compile` itself, on the first call only (i.e. once ``self.workdir/model/runmodel.x`` exists, later calls skip straight to running it). It is deliberately *not* triggered any earlier -- e.g. by the generic, input-agnostic ``run_model`` transform init (see the ``__init__.py`` note) -- because D&B's build needs ``runsubdir/input/{static,dyn}forcing.nc`` to already exist (see :func:`~.compile.compile`); by the time ``run`` is first called, pyCIF has finished initialising the model (domain, mapper, periods) and staging its input data (via :func:`~.io.native2inputs.native2inputs`), so that precondition holds. Args: self: the dalecbethy model Plugin. runsubdir (str): working directory for the current run; the D&B executable and its ``input/`` directory must be staged here. mode (str): 'fwd', 'tl' or 'adj'. workdir (str): pyCIF working directory. ddi (datetime.datetime): start date of the current sub-simulation. do_simu (bool): re-run or not an existing simulation. .. warning:: TODO / open questions for a D&B developer: * D&B ships a hand-coded adjoint (``src/cost_bw.f90``, ``runassi.x``) but it is wired to D&B's *own* L-BFGS-B minimiser (``mini/``), driving D&B's internal control vector (``src/prior.f90``) against D&B's own observation operator (``src/obsop.f90``), not to a per-timestep tangent-linear/adjoint call pyCIF could drive itself (unlike e.g. CHIMERE's ``tlchimere.e``/``achimere.e``). Whether/how a pyCIF-driven ``mode='tl'``/``'adj'`` run should be implemented here (e.g. by building and calling ``runassi.x`` or a new dedicated driver) is left as a follow-up. * D&B currently only supports full-domain runs at hourly internal time steps between ``yyyymmdd_start``/``yyyymmdd_end`` (``src/dimensions.f90``); how that maps onto pyCIF's sub-simulation windows (``ddi``/``ddf``) still needs to be decided, in particular since those dates are presently compiled into the executable rather than passed at runtime. """ if mode != "fwd": raise CifRuntimeError( "The D&B plugin does not implement a tangent-linear/adjoint " f"run yet (mode={mode!r} requested); see run.py TODOs." ) if not do_simu: return # Compile lazily, only once per model instance (see docstring above): # later sub-periods reuse the executable from the first call, even if # 'force-recompile' is set (that only forces *one* recompilation here, # not one per sub-period). built_exe = f"{workdir}/model/runmodel.x" if not getattr(self, "_dalecbethy_compiled", False): if getattr(self, "force-recompile") or not os.path.isfile(built_exe): _compile(self, runsubdir) self._dalecbethy_compiled = True exe = f"{runsubdir}/runmodel.x" path.link(built_exe, exe) info(f"Running D&B in {runsubdir} for sub-period starting {ddi}") with open(f"{runsubdir}/log.std", "w") as log: process = subprocess.Popen( "./runmodel.x", cwd=runsubdir, stdout=log, stderr=subprocess.PIPE, ) _, stderr = process.communicate() if process.returncode != 0: debug("D&B returned errors during execution:") debug(stderr.decode()) raise CifRuntimeError( f"D&B execution failed in {runsubdir}; see log.std for details" )