diff --git a/pyaerocom/aeroval/experiment_output.py b/pyaerocom/aeroval/experiment_output.py index 7c975b46f..cffdab848 100644 --- a/pyaerocom/aeroval/experiment_output.py +++ b/pyaerocom/aeroval/experiment_output.py @@ -21,6 +21,7 @@ from pyaerocom.aeroval.setupclasses import EvalSetup from pyaerocom.aeroval.varinfo_web import VarinfoWeb from pyaerocom.exceptions import EntryNotAvailable, VariableDefinitionError +from pyaerocom.stats.mda8.const import MDA8_INPUT_VARS, MDA8_OUTPUT_VARS from pyaerocom.stats.stats import _init_stats_dummy from pyaerocom.variable_helpers import get_aliases @@ -698,6 +699,12 @@ def _is_part_of_experiment(self, obs_name, obs_var, mod_name, mod_var) -> bool: True if this combination is valid, else False. """ + + # MDA8 is computed on-the-fly ONLY if a MDA8_INPUT_VAR at hourly freq is detected. + # Consequently, it is not specified in a config but should be included as part of the experiment. + if obs_var in MDA8_OUTPUT_VARS and mod_var in MDA8_OUTPUT_VARS: + return True + # get model entry for model name try: mcfg = self.cfg.model_cfg.get_entry(mod_name) diff --git a/pyaerocom/aeroval/glob_defaults.py b/pyaerocom/aeroval/glob_defaults.py index ff3aca270..28c1d87d5 100644 --- a/pyaerocom/aeroval/glob_defaults.py +++ b/pyaerocom/aeroval/glob_defaults.py @@ -629,6 +629,7 @@ vmrso2=["SO2", "3D", "Gas volume mixing ratio"], concso4=["SO4", "3D", "Particle concentrations"], vmro3=["O3", "3D", "Volume mixing ratios"], + vmro3mda8=["O3 (MDA8)", "3D", "Valume mixing ratios"], vmro3max=["O3Max", "3D", "Volume mixing ratios"], vmrox=["OX", "3D", "Gas volume mixing ratio"], concco=["CO", "3D", "Particle concentration"], diff --git a/pyaerocom/colocation/colocator.py b/pyaerocom/colocation/colocator.py index c4ac05db9..fcf5e10ec 100644 --- a/pyaerocom/colocation/colocator.py +++ b/pyaerocom/colocation/colocator.py @@ -31,7 +31,7 @@ from pyaerocom.io import ReadCAMS2_83, ReadGridded, ReadUngridded from pyaerocom.io.helpers import get_all_supported_ids_ungridded from pyaerocom.io.mscw_ctm.reader import ReadMscwCtm -from pyaerocom.stats.mda8.const import MDA_VARS +from pyaerocom.stats.mda8.const import MDA8_INPUT_VARS from pyaerocom.stats.mda8.mda8 import mda8_colocated_data from .colocated_data import ColocatedData @@ -383,17 +383,16 @@ def run(self, var_list: list = None): ) # note this can be ColocatedData or ColocatedDataLists data_out[mod_var][obs_var] = coldata - if obs_var in MDA_VARS: - mda8 = None + if obs_var in MDA8_INPUT_VARS: try: mda8 = mda8_colocated_data( coldata, obs_var=f"{obs_var}mda8", mod_var=f"{mod_var}mda8" ) - except ValueError: - logger.warning( - "Tried calculating mda8 for [%s, %s], but failed.", obs_var, mod_var - ) - finally: + except ValueError as e: + logger.debug(e) + else: + self._save_coldata(mda8) + logger.info("Successfully calculated mda8 for [%s, %s].", obs_var, mod_var) data_out[f"{mod_var}mda8"][f"{obs_var}mda8"] = mda8 self._processing_status.append([mod_var, obs_var, 1]) diff --git a/pyaerocom/data/variables.ini b/pyaerocom/data/variables.ini index 4d1477281..23dd82cb5 100644 --- a/pyaerocom/data/variables.ini +++ b/pyaerocom/data/variables.ini @@ -3105,6 +3105,10 @@ unit = ug m-3 description=Mass concentration of ozone unit = ug m-3 +[vmro3mda8] +description=MDA8 of O3 VMR. +unit = nmol mol-1 + [concco] description=Mass concentration of organic carbon unit = ug m-3 diff --git a/pyaerocom/stats/mda8/const.py b/pyaerocom/stats/mda8/const.py index 8c32ac409..e0e6dc2fd 100644 --- a/pyaerocom/stats/mda8/const.py +++ b/pyaerocom/stats/mda8/const.py @@ -1,2 +1,3 @@ # Variable names for which mda8 values are to be calculated. -MDA_VARS = ("conco3", "vmro3") +MDA8_INPUT_VARS = ("conco3", "vmro3") +MDA8_OUTPUT_VARS = ("vmro3mda8",) diff --git a/pyaerocom/stats/mda8/mda8.py b/pyaerocom/stats/mda8/mda8.py index 471708fae..c7a63fd9b 100644 --- a/pyaerocom/stats/mda8/mda8.py +++ b/pyaerocom/stats/mda8/mda8.py @@ -1,8 +1,12 @@ +import logging + import numpy as np import xarray as xr from pyaerocom.colocation.colocated_data import ColocatedData +logger = logging.getLogger(__name__) + def min_periods_max(x: np.ndarray, /, min_periods=1) -> float: """Calculates the max of a 1-dimensional array, returning @@ -44,6 +48,7 @@ def mda8_colocated_data(coldat: ColocatedData, /, obs_var: str, mod_var: str) -> cd = ColocatedData(_calc_mda8(coldat.data)) cd.data.attrs["var_name"] = [obs_var, mod_var] + cd.metadata["var_name_input"] = [obs_var, mod_var] return cd