Source code for pycif.plugins.models.chimere.ini_periods

import datetime
import os
from logging import debug, info

import numpy as np
import pandas as pd
import xarray as xr
from netCDF4 import Dataset

from ....utils.dates import date_range
from ....utils.netcdf import readnc


[docs] def ini_periods(self, **kwargs): """Initialise sub-simulation periods and time-step grids for CHIMERE. Partitions the full simulation window into sub-periods of length ``self.periods`` (typically 1 day) and determines the number of physical time steps per hour (``nphour``) for each sub-period. When a METEO.nc file is present, ``nphour`` is read from the NetCDF ``nphourm`` variable, which may vary through the simulation window (CFL-adaptive time stepping). Otherwise it defaults to ``self.nphour_ref``. Sets the following attributes on *self*: * ``subsimu_dates`` — boundary dates of all sub-periods. * ``tstep_dates`` — dict mapping each sub-period start to its time-step array. * ``input_dates`` — same structure, used to schedule data reads. * ``tstep_all`` — flat array of all time steps. * ``nhour`` — dict of ``nphour`` per sub-period. * ``subtstep`` — dict of sub-time-step counts per sub-period. * ``iniobs`` — whether to dump observations for each sub-period. * ``reset_obs`` — whether to reset the observation accumulator. * ``runsimu`` — whether to actually run each sub-period (False for stop-or-more periods already computed). * ``chain`` — whether each sub-period has a successor. Args: self (Plugin): CHIMERE model plugin instance (carries ``datei``, ``datef``, ``periods``, ``nphour_ref``, ``nho``, ``nhours``, ``workdir``, and ``stopORmore``). **kwargs: unused; accepted for interface consistency. Raises: Exception: if sub-periods are not all of the same length. """ datei = self.datei datef = self.datef nho = self.nho nhours = self.nhours # List of sub-simulation windows self.subsimu_dates = date_range(datei, datef, period=self.periods) # Check that subperiods are all of the same length if np.unique(np.diff(self.subsimu_dates)).size > 1: raise Exception( f"Trying to run CHIMERE on a simulation window not fitting the sub-simulation length. \nPlease check the compatibility between the duration of your sub-simulations ({self.periods}) and the simulation window ({datei} to {datef}): \nGuessed sub-periods: \n" + "\n".join( [ f" - {d0}: {d1 - d0}" for d0, d1 in zip(self.subsimu_dates[:-1], self.subsimu_dates[1:]) ] ) ) # Time steps defined by the user nphour_ref = self.nphour_ref # Numbers of time steps per hour really used in the simulation self.tstep_dates = {} self.tstep_all = [] self.nhour = {} self.subtstep = {} self.input_dates = {} # Stop-or-more function list_to_stop = self.stopORmore list_spec_stop = [] list_thld_stop = [] for l in list_to_stop: list_spec_stop.append(l[0]) list_thld_stop.append(l[1]) if list_thld_stop != [] and list_spec_stop != []: info("Looking at concentrations small enough to stop simulations") self.list_spec_stop = list_spec_stop self.list_thld_stop = list_thld_stop # Loop over sub simulations for dd in self.subsimu_dates[:-1]: # time-steps in METEO.nc, computed by diagmet try: met = dd.strftime(f"{self.meteo.dir}/{self.meteo.file}") debug(f"Reading {met} to initialize nphourm") ds_met = xr.open_dataset(met) if "nphourm2" in ds_met.variables: nbstep = ds_met.variables["nphourm2"].values.astype(int) elif "nphourm" in ds_met.variables: nbstep = ds_met.variables["nphourm"].values.astype(int) else: raise Exception( f"The file {met} is available, but no information is " f"available about nphourm. Please check your file" ) except IOError: debug(f"{met} is not available. Using nphourref (={nphour_ref}).") nbstep = nhours * [nphour_ref] except AttributeError: debug( "No meteo plugin was defined in the Yaml. " f"Using default plugin with nphourref (={nphour_ref})." ) nbstep = nhours * [nphour_ref] # Loop on hours and check CFL self.tstep_dates[dd] = [] self.nhour[dd] = [] self.subtstep[dd] = [] for nh in range(nhours): nphour = nbstep[nh] if nphour_ref < nbstep[nh] else nphour_ref # Frequency in seconds # TODO: Check with FORTRAN: nphour rounding? freq = f"{int(3600 // nphour)}s" # List of time steps # TODO: what about chemical time steps? # the time step to really use is nphour*ichemstep ddhi = dd + datetime.timedelta(hours=nh) ddhe = dd + datetime.timedelta(hours=nh + 1) drange = list(pd.date_range(ddhi, ddhe, freq=freq).to_pydatetime()) self.tstep_dates[dd].extend(drange[:-1]) self.tstep_all.extend(drange[:-1]) # Saving substep indexes for matching with observation nphour_int = len(drange) - 1 self.subtstep[dd].extend(list(range(1, nphour_int + 1))) self.nhour[dd].extend(nphour_int * [nh + 1]) # List of dates for which inputs are needed self.input_dates[dd] = pd.date_range( dd, periods=nhours + 1, freq="1h" ).to_pydatetime() # Include last time step self.tstep_dates[dd].append(ddhe) self.tstep_dates[dd] = np.array(self.tstep_dates[dd]) # Include very last time step self.tstep_all.append(ddhe) self.tstep_all = np.array(self.tstep_all) # Initializes dictionary to keep in memory whether observations were # already dumped for a given period self.iniobs = {ddi: False for ddi in self.subsimu_dates} self.nbobs_prior = {ddi: 0 for ddi in self.subsimu_dates} self.nbdatatot_prior = {ddi: 0 for ddi in self.subsimu_dates} self.reset_obs = {ddi: True for ddi in self.subsimu_dates} # Keep track for stop-or-more function (initialize run = True) self.runsimu = {ddi: True for ddi in self.subsimu_dates} # Keep in memory whether a given period has a successor period # The info is used by the adjoint to fetch or not the aend file self.chain = {ddi: True for ddi in self.subsimu_dates[:-1]} self.chain[self.subsimu_dates[-2]] = False