Source code for pycif.plugins.models.iconart.io.inputs.fluxes
import xarray as xr
import numpy as np
import os
import shutil
from logging import info
from .tracers import change_tracers_xml_fluxes
from .tv_scalef_oem import create_oem_tv_scaling_factors
from ......utils import path
from ......utils.hdf5 import _hdf5_lock
[docs]
def make_fluxes(self, datastore, ddi, ddf, runsubdir, mode):
"""Native to input function for the oem module"""
# Inputs and outputs
trids_in = list(datastore.keys())
trids_out = [("flux", s) for s in self.chemistry.emis_species]
# Flags to detect the ensemble
is_ensemble = False
is_perturbed_comp = False
# Create the OEM dir
oem_dir = os.path.join(runsubdir, 'OEM')
path.init_dir(oem_dir)
# Create or use an existing OEM gridded emissions file
emissions_file = os.path.join(oem_dir, 'gridded_emissions.nc')
with _hdf5_lock:
ds_emi = xr.open_dataset(emissions_file) \
if os.path.exists(emissions_file) else xr.Dataset()
# Loop over the species that must be emitted
for trid_out in trids_out:
trid_in = trid_out
emspec_ref = emspec = trid_out[1]
# If ensemble, check what the datastore contains
if "__sample#" in emspec:
is_ensemble = True
is_perturbed_comp = True
emspec_ref = emspec.split("__sample#")[0]
sample_id = emspec.split("__sample#")[1]
if int(sample_id) > 0:
continue
if trid_in not in datastore:
trid_in = ("flux", emspec_ref)
is_perturbed_comp = False
if trid_in not in datastore:
continue
tracer = datastore[trid_in]
tracer_data = tracer["data"][ddi]
# --------------------------------------------------------------------------
# -- Get the prior data
# --------------------------------------------------------------------------
if "spec" not in tracer_data:
# Fluxes are always loaded to account for the temporal scaling factors
pass
else:
da_flux_prior = tracer_data['spec']
# Adjust the dimensions and remove the time dependency
da_flux_prior = da_flux_prior.rename({'lon': 'cell'})
da_flux_prior = da_flux_prior.squeeze()
# Create OEM scaling factors based on the data
create_oem_tv_scaling_factors(self, ddi, ddf, da_flux_prior, oem_dir, emspec_ref)
# TODO: careful, the inversion only works with a single ensemble scaling factor for the period
da_flux_prior = da_flux_prior.mean(dim='time')
# Get country ids
if 'country_ids' not in ds_emi:
ds_emi['country_ids'] = (
'cell', np.arange(da_flux_prior.cell.size))
da_flux_post = da_flux_prior
# --------------------------------------------------------------------------
# -- Ensemble processing
# --------------------------------------------------------------------------
if is_ensemble:
if is_perturbed_comp:
info(f"The {emspec_ref} emission category is perturbed.")
info(f"Calculating emission scaling factors for the {emspec_ref} category...")
list_da = [datastore[t]["data"][ddi]["spec"] for t in trids_in]
flux_lambdas = xr.concat(list_da, dim="ens")
flux_lambdas = flux_lambdas / flux_lambdas[0]
flux_lambdas = flux_lambdas.rename({'lon': 'reg'})
flux_lambdas = flux_lambdas.squeeze()
flux_lambdas = flux_lambdas.isel(time=slice(0, -1)).mean(dim='time')
flux_lambdas = flux_lambdas.fillna(1)
flux_lambdas = flux_lambdas.values
else:
info(f"The {emspec_ref} emission category is NOT perturbed.")
info(f"Creating fake emission scaling factors (1.0) for the {emspec_ref} category...")
nsamples = getattr(self.chemistry, "nsamples", 1)
flux_lambdas = np.ones((nsamples, self.domain.nlon))
# Convert the array to a DataArray
flux_lambdas = xr.DataArray(
flux_lambdas,
dims=("ens", "reg")
)
# Store the scaling factors
self.flux_lambdas[ddi][emspec_ref] = flux_lambdas
# Get the posterior emissions
flux_lambdas_post = flux_lambdas[2]
da_flux_post = da_flux_prior * flux_lambdas_post.values
# --------------------------------------------------------------------------
# -- Dump the gridded emission files
# --------------------------------------------------------------------------
# Just change the attributes
da_flux_prior.attrs['standard_name'] = f'{emspec_ref}'
da_flux_prior.attrs['long_name'] = f'Fluxes for {emspec_ref} generated by the CIF'
da_flux_prior.attrs['units'] = 'kg/m2/s'
# Add the current category (prior and posterior) to the emission file
ds_emi[emspec_ref] = da_flux_prior
if is_ensemble:
ds_emi[emspec_ref + '_POST'] = da_flux_post
# Dump the gridded emissions file
if os.path.exists(emissions_file):
os.remove(emissions_file)
with _hdf5_lock:
ds_emi.to_netcdf(emissions_file)
info(f"Added {emspec_ref} to {emissions_file}.")
# --------------------------------------------------------------------------
# -- Adapt tracers.xml
# --------------------------------------------------------------------------
info(f"Modifying tracers.xml for the {emspec_ref} category ({is_ensemble=})...")
for t in trids_out:
change_tracers_xml_fluxes(self, t[1], is_ensemble=is_ensemble)
return