Source code for pycif.plugins.models.lagrangian.io.inputs.inicond

import copy
import datetime
import glob
import os
from logging import debug

import numpy as np
import pandas as pd

from ......utils.parallel import thread
from ...utils.read import read_flexpart_gridinit
from ......utils.check.errclass import CifError


[docs] def inicond_contribution( self, mode, dataobs, fp_header_init, spec, ddi, batch_computation=False ): """Compute the initial-condition–concentration contribution for one species and period. Convolves the FLEXPART initial-condition sensitivity fields (``grid_initial_*``) with the stored initial-condition array to produce the background concentration increment for each observation in *dataobs*. Args: self: Lagrangian model plugin instance. mode (str): ``'fwd'`` or ``'tl'``. dataobs: CIF observation data-store for the period. fp_header_init: FLEXPART header for the initial-condition sensitivity. spec (str): species name. ddi (datetime): sub-simulation period start. batch_computation (bool): ensemble batch mode flag. Returns: dataobs updated with ``'spec'`` contributions from initial conditions. """ inicond = { spec: {"spec": self.datainicond[("inicond", spec)][ddi]["spec"]}} if mode == "tl" and "incr" in self.datainicond[("inicond", spec)][ddi]: inicond[spec]["incr"] = \ self.datainicond[("inicond", spec)][ddi]["incr"] # Initialize debug columns if needed if self.dump_debug: dataobs[spec][("flexpart", "background")] = 0. dataobs[spec][("flexpart", "background_tl")] = 0. # Specific treatment for batch computing sample_species = [spec] if batch_computation: ref_species = self.perturbed_species[spec] sample_species = [ s for s in self.chemistry.acspecies.attributes if self.perturbed_species[s] == ref_species ] inicond = { s: {"spec": self.datainicond[("inicond", s)][ddi]["spec"]} for s in sample_species } if mode == "tl" and "incr" in self.datainicond[("inicond", spec)][ddi]: for s in sample_species: inicond[s]["incr"] = self.datainicond[( "inicond", s)][ddi]["incr"] if self.dump_debug: for s in sample_species: dataobs[s][("flexpart", "background")] = 0. dataobs[s][("flexpart", "background_tl")] = 0. # Execute parallel threads nthreads = self.nthreads nobs = len(dataobs[sample_species[0]]) thread_intervals = np.linspace(0, nobs, nthreads + 1).astype(int) @thread def thread_function(ithread): for obs_i in range(thread_intervals[ithread], thread_intervals[ithread + 1]): process_obs_row( self, dataobs, ithread, fp_header_init, inicond, obs_i, sample_species, batch_computation ) thread_function(range(nthreads)) # Flush inicond for s in sample_species: self.datainicond[("inicond", s)][ddi]["spec"] = None self.datainicond[("inicond", s)][ddi]["incr"] = None
[docs] def process_obs_row(self, dataobs, ithread, fp_header_init, inicond, obs_i, sample_species, batch_computation): """Compute the initial-condition contribution for a single observation row. Reads the initial-condition sensitivity field for the observation at *obs_i* and convolves it with *inicond* to produce the background concentration contribution. Args: self: Lagrangian model plugin instance. dataobs (dict): species-keyed observation data-stores. ithread (int): thread index. fp_header_init: FLEXPART initial-condition sensitivity header. inicond (dict): initial-condition arrays keyed by species. obs_i (int): row index into the observation data-store. sample_species (list): species names to process. batch_computation (bool): ensemble batch mode flag. """ ref_spec = sample_species[0] row = dataobs[ref_spec]["metadata"].iloc[obs_i] station = row.station network = row.network subdir = row.date.strftime(self.footprint_dir_format) # Translate station name if needed if hasattr(self, "station_name_dict"): station = self.dict_station_name[station.upper()].upper() # Infer folder structure runsubdir_init = os.path.join( self.run_dir_bg, self.footprint_stat_subdir_format.format( stat=station.upper(), network=network), subdir ) release_date = row.date - pd.to_timedelta(self.release_shift) file_date = release_date.strftime('%Y%m%d%H%M%S') file_name = self.file_ini_format.format( date=file_date, stat=station.upper(), network=network) list_valid = glob.glob(os.path.join(runsubdir_init, file_name)) if list_valid == []: debug(f"WARNING: file not found: {os.path.join(runsubdir_init, file_name)}") return elif len(list_valid) > 1: raise CifError( f"Multiple files fit the specified format {self.file_ini_format}. " f"This can be related to the use of a wildcard... " f"Please check your yml" ) file_name = os.path.basename(list_valid[0]) if self.preloaded_footprints.get("loaded_file", "") == list_valid[0] \ and self.reload_footprints: debug("Using preloaded concentrations from " f"{file_name} for station {station}") for s in sample_species: sample_inicond = inicond[s] for data_id in sample_inicond: dataobs[s].iloc[ obs_i, dataobs[s].columns.get_loc(("maindata", data_id)) ] = self.preloaded_footprints[data_id][s] else: debug(f"Thread #{ithread}: Reading {file_name} for station {station}") grid_init = read_flexpart_gridinit( runsubdir_init, file_name, fp_header_init) if self.reload_footprints: self.preloaded_footprints = { "loaded_file": list_valid[0], "data": copy.deepcopy(grid_init), "spec": {}, "incr": {} } # Normalize grid_init to make sure that total sensitivity is 1 grid_init /= grid_init.sum() # Multiply 3-D sensitivity to background concentrations # WARNING: do not deal with temporal and vertical dimension yet nz = (fp_header_init.outheight != 0.).sum() ini_sensit = grid_init.T.reshape(nz, -1) for s in sample_species: sample_inicond = inicond[s] for data_id in sample_inicond: dataini = sample_inicond[data_id] ini_dates = pd.DatetimeIndex( dataini.time.values).to_pydatetime() inds_inicond = np.argmin( (np.array( [row.date - pd.to_timedelta(self.backward_trajdays)] )[:, np.newaxis] - ini_dates[np.newaxis, :] ) >= datetime.timedelta(0), axis=1) - 1 istartsensit = ( 0 if self.domain.zlon.size == self.domain.zlon_in.size else self.domain.zlon_in.size ) ini_sim = ( dataini[inds_inicond, :, 0, istartsensit:].values * ini_sensit ).sum() # Filling simulation dataobs[s].iloc[ obs_i, dataobs[s].columns.get_loc(("maindata", data_id)) ] += ini_sim # Dump debug if self.dump_debug: if data_id == "spec": sim_col = "background" else: sim_col = "background_tl" dataobs[s].iloc[ obs_i, dataobs[s].columns.get_loc( ("flexpart", sim_col)) ] = ini_sim # Save simulation for later if needed if self.reload_footprints: self.preloaded_footprints[data_id][s] = ini_sim