Source code for pycif.plugins.models.chimere.io.inputs.make_inicond

import filecmp
import os
import shutil
import pathlib
import numpy as np
from netCDF4 import Dataset
import xarray as xr
from logging import warning
import copy

from ......utils import path
from ......utils.hdf5 import _hdf5_lock
from .utils import replace_dates
from ......utils.check.errclass import CifError


[docs] def make_inicond( self, datastore, runsubdir, mode, datei, datef, ini_type="inicond" ): """Write or symlink the CHIMERE initial concentration file ``INI_CONCS.0.nc``. For the **first sub-simulation period** only (``datei == self.datei``). For each active species: * If no data was modified in the CIF datastore, symlinks the original file from disk. * If concentrations were modified (e.g. by a prior transform), copies the original file and overwrites the relevant species variable. * For **tangent-linear** mode, also creates ``INI_CONCS.0.increment.nc`` initialised to zero perturbations. * Handles **ensemble/perturbed** species by reading from the reference species and writing to the perturbed copy. After all species are written, calls :func:`replace_dates` to ensure the NetCDF time axis matches the CHIMERE convention. Args: self: CHIMERE model plugin instance (carries ``datei``, ``nho``, ``adj_refdir``, ``chemistry.acspecies``, and ``inicond``). datastore (dict): tracer-ID-keyed mapping of CIF data-store entries. runsubdir (str): path to the period run directory. mode (str): ``'fwd'``, ``'tl'``, or ``'adj'``. datei (datetime): period start date. datef (datetime): period end date. ini_type (str): datastore key prefix; ``'inicond'`` for normal use or ``'restart_inicond'`` in EnSRF restart mode. """ ddi = min(datei, datef) # Fixed name for INI_CONCS files fileout = f"{runsubdir}/INI_CONCS.0.nc" fileoutincr = f"{runsubdir}/INI_CONCS.0.increment.nc" nho = self.nho # # Getting the forward initial concentrations (needed even for adjoint) # # if chained period # subsimu_dates = self.subsimu_dates # if mode == "adj" and hasattr(self, "fwd_chain") \ # and datei != self.datei: # date_index = np.where(subsimu_dates == datei)[0][0] # refdir = self.adj_refdir # filein = subsimu_dates[date_index - 1].strftime( # "{}/chain/end.%Y%m%d%H.{}.nc".format(refdir, nho) # ) # path.link(filein, fileout) # Exit if not first period if datei != self.datei: return # If in datastore, take data, otherwise, link to original INI_CONCS data2dump = {} data2dump_tl = {} for spec in self.chemistry.acspecies.attributes: trid = (ini_type, spec) ref_trid = (ini_type, spec) # Update ref_trid if in ensemble mode perturbed_from_reference = False if hasattr(self, "perturbed_species"): if spec in self.perturbed_species: ref_trcr = self.perturbed_species[spec] ref_trid = (ini_type, ref_trcr) # If spec not explicitly defined in datastore, # fetch general component information if available if trid in datastore: pass # If trid is a perturbed species, needs to reformat name to fit # CHIMERE naming convention for perturbed species elif trid in [(k[0], k[1].replace("__sample#", "")) for k in datastore]: trid_ind = [ (k[0], k[1].replace("__sample#", "")) for k in datastore ].index(trid) trid = list(datastore.keys())[trid_ind] # If trid comes from an unperturbed input, but in ensemble mode # needs to fetch info from the reference species elif ref_trid in datastore: trid = ref_trid perturbed_from_reference = True elif trid not in datastore and (ini_type, "") in datastore: trid = (ini_type, "") else: continue tracer = datastore[trid] tracer_data = tracer["data"][ddi] # If no data is provided, just copy from original file if "spec" not in tracer_data: dirorig = tracer["dirorig"] fileorig = tracer["fileorig"] fileini = f"{dirorig}/{fileorig}" # If does not exist, just link linked = False if not os.path.isfile(fileout): path.link(fileini, fileout) linked = True # Simply link if restart_inicond if ini_type == "restart_inicond": continue # Otherwise, check for difference if not linked or perturbed_from_reference: if not os.path.isfile(fileini): warning(f"The initial condition file {fileini} does not exist to initialize the species {spec}. The concentrations will be initialized to zero. Please check your yaml if you expect a different behaviour") continue if not filecmp.cmp(fileini, fileout) or perturbed_from_reference: spec2read = spec if not perturbed_from_reference else ref_trid[1] with _hdf5_lock: with Dataset(fileini, "r") as ds: if spec2read not in ds.variables: warning(f"{spec} is not accounted for in the initial conditions file {fileini}. Please check your IC whether it should be") continue if "Time" in [d.name for d in ds.variables[spec2read].get_dims()]: raise CifError( f"Warning! The original file {fileini} is not in the " f"correct format. More specifically, the dimensions of " f"the variable {spec2read} include 'Time' whereas it should be" f" (bottom_top, south_north, west_east)" ) iniin = ds.variables[spec2read][:] iniin = xr.DataArray( iniin[np.newaxis, ...], coords={"time": [min(datei, datef)]}, dims=("time", "lev", "lat", "lon"), ) # If ini file is still a link, should be copied # to be able to modify it locally if os.path.islink(fileout): file_orig = pathlib.Path(fileout).resolve() os.unlink(fileout) shutil.copy(file_orig, fileout) # Now saves to buffer variable before saving data2dump[spec] = copy.deepcopy(iniin) # Repeat operations for tangent linear if mode == "tl": # If does not exist, copy if not os.path.isfile(fileoutincr): shutil.copy(fileini, fileoutincr) with _hdf5_lock: with Dataset(fileoutincr, "a") as fout: if spec in fout.variables: fout.variables[spec][:] = 0.0 continue with xr.open_dataset(fileout) as ds: if spec not in ds: continue iniin = xr.DataArray( 0. * ds[spec].values[np.newaxis, ...], coords={"time": [min(datei, datef)]}, dims=("time", "lev", "lat", "lon"), ) data2dump_tl[spec] = copy.deepcopy(iniin) else: # Replace existing link by copy # of original file to modify it path.copyfromlink(fileout) # Write initial conditions ini_fwd = tracer_data["spec"] data2dump[spec] = copy.deepcopy(ini_fwd) if mode == "tl": path.copyfromlink(fileoutincr) ini_tl = tracer_data.get("incr", 0.0 * ini_fwd) data2dump_tl[spec] = copy.deepcopy(ini_tl) # Now dump buffer data if any if len(data2dump) > 0: self.inicond.write( list(data2dump.keys()), fileout, data2dump, comp_type="inicond" ) # Now dump buffer data if any if len(data2dump_tl) > 0: self.inicond.write( list(data2dump_tl.keys()), fileoutincr, data2dump_tl, comp_type="inicond" ) # Check that the dates are consistent with what CHIMERE expects replace_dates(fileout, [min(datei, datef)], self.ignore_input_dates) if mode == "tl": replace_dates(fileoutincr, [min(datei, datef)], self.ignore_input_dates)