Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bulkfraction engine for pyaeroval #1458

Merged
merged 40 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4e1a15e
Start of bulk fraction engine
dulte Nov 26, 2024
2fcd87d
First MVP
dulte Nov 28, 2024
c4326e3
Second MVP
dulte Dec 3, 2024
2f8c5af
Adds web info for pmfraction
dulte Dec 3, 2024
b418467
Adds conc of rdn in precip
dulte Dec 9, 2024
50169b6
Some tests for bulk
dulte Dec 9, 2024
c3352e7
Adds test for _run_var for bulk
dulte Dec 13, 2024
5027172
Adds an f from an f string...
dulte Dec 13, 2024
b9834ed
Fixes variable tests
dulte Dec 13, 2024
d452862
Adds multiple EC variables
dulte Dec 13, 2024
8d0ef1d
Fix test
dulte Dec 13, 2024
7928a62
Adds more var defs
dulte Dec 20, 2024
ad0f17a
Changes how ebc is read in emep
dulte Dec 20, 2024
2c962e4
Adds min and max to ebc
dulte Jan 8, 2025
083ab63
linting
dulte Jan 8, 2025
c27bac6
merge
dulte Jan 8, 2025
8861cfb
Fixes one test
dulte Jan 8, 2025
99f5402
Changes the resturn type of get_colocators, and added tests
dulte Jan 14, 2025
55d27a3
Small changes to var names
dulte Jan 14, 2025
8dbf19d
merge
dulte Jan 14, 2025
a77cf78
Formatting
dulte Jan 14, 2025
af7e87f
Comments out some test variables
dulte Jan 14, 2025
646e6f1
changes var web info
dulte Jan 14, 2025
2409685
Merge branch 'bulkfraction' of github.com:metno/pyaerocom into bulkfr…
dulte Jan 14, 2025
4e2a182
Fixes tests
dulte Jan 14, 2025
f8e5ed5
Merge branch 'main-dev' into bulkfraction
dulte Jan 17, 2025
362da24
Changes how bulfractionengine works with model with model_use_vars
dulte Jan 28, 2025
bd36cd3
Adds vars having to do with emissions
dulte Jan 28, 2025
39b1145
Makes the last tests pass
dulte Jan 28, 2025
42f0ae2
Review changes to bulkfraction_engine
dulte Jan 31, 2025
5dc6780
Variable aux function added to emep reader
dulte Jan 31, 2025
080e96e
Adds new EM variable
dulte Jan 31, 2025
13618a6
Adds more test to bulkfraction_engine
dulte Jan 31, 2025
12bcd50
Ruff
dulte Jan 31, 2025
05626fb
Small changes to tests
dulte Jan 31, 2025
1f1fbc0
more ruff
dulte Jan 31, 2025
098f42c
Ruff once more
dulte Jan 31, 2025
66ff3fa
Removes dead code
dulte Jan 31, 2025
ca3f894
Fixes pydantic error
dulte Jan 31, 2025
e478075
Tries to add some doc strings
dulte Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions pyaerocom/aeroval/bulkfraction_engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import logging

from pyaerocom import ColocatedData
from pyaerocom.aeroval._processing_base import HasColocator, ProcessingEngine
from pyaerocom.aeroval.obsentry import ObsEntry
from pyaerocom.aeroval.modelentry import ModelEntry
from pyaerocom.aeroval.coldatatojson_engine import ColdataToJsonEngine


from pyaerocom.colocation.colocator import Colocator
from pyaerocom.colocation.colocation_setup import ColocationSetup


logger = logging.getLogger(__name__)


class BulkFractionEngine(ProcessingEngine, HasColocator):
def run(self, var_list: list[str] | str | None, model_name: str, obs_name: str):
self.sobs_cfg = self.cfg.obs_cfg.get_entry(obs_name)
self.smodel_cfg = self.cfg.model_cfg.get_entry(model_name)

if var_list is None:
var_list = self.sobs_cfg.obs_vars
elif isinstance(var_list, str):
var_list = [var_list]
elif not isinstance(var_list, list):
raise ValueError(f"invalid input for var_list: {var_list}.")

Check warning on line 27 in pyaerocom/aeroval/bulkfraction_engine.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/aeroval/bulkfraction_engine.py#L26-L27

Added lines #L26 - L27 were not covered by tests

files_to_convert = []
for var_name in var_list:
bulk_vars = self._get_bulk_vars(var_name, self.sobs_cfg)

freq = self.sobs_cfg.ts_type
cd, fp = self._run_var(
model_name, obs_name, var_name, bulk_vars, freq, self.sobs_cfg, self.smodel_cfg
)

files_to_convert.append(fp)

engine = ColdataToJsonEngine(self.cfg)
engine.run(files_to_convert)

def _get_bulk_vars(self, var_name: str, cfg: ObsEntry) -> list:
bulk_vars = cfg.bulk_options
if var_name not in bulk_vars:
raise KeyError(f"Could not find bulk vars entry for {var_name}")

if len(bulk_vars[var_name].vars) != 2:
raise ValueError(

Check warning on line 49 in pyaerocom/aeroval/bulkfraction_engine.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/aeroval/bulkfraction_engine.py#L49

Added line #L49 was not covered by tests
f"(Only) 2 entries must be present for bulk vars to calculate fraction for {var_name}. Found {bulk_vars[var_name]}"
)

return bulk_vars[var_name].vars

def _run_var(
self,
model_name: str,
obs_name: str,
var_name: str,
bulk_vars: list,
freq: str,
obs_entry: ObsEntry,
model_entry: ModelEntry,
) -> tuple[ColocatedData, str]:
model_exists = obs_entry.bulk_options[var_name].model_exists

cols = self.get_colocators(bulk_vars, var_name, freq, model_name, obs_name, model_exists)

coldatas = []
for col in cols:
if len(list(col.keys())) != 1:
raise ValueError(

Check warning on line 72 in pyaerocom/aeroval/bulkfraction_engine.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/aeroval/bulkfraction_engine.py#L72

Added line #L72 was not covered by tests
"Found more than one colocated object when trying to run bulk variable"
)
bv = list(col.keys())[0]
coldatas.append(col[bv].run(bv))

num_name, denum_name = bulk_vars[0], bulk_vars[1]
num_col = cols[0][num_name].run(num_name)
denum_col = cols[1][denum_name].run(denum_name)

model_num_name, model_denum_name = self._get_model_var_names(
var_name, bulk_vars, model_exists, model_entry
)

cd = self._combine_coldatas(
num_col[model_num_name][num_name],
denum_col[model_denum_name][denum_name],
var_name,
obs_entry,
)

num_colocator = cols[0][num_name]

fp = cd.to_netcdf(
out_dir=num_colocator.output_dir,
savename=cd._aerocom_savename(
var_name,
obs_name,
var_name,
model_name,
num_colocator.get_start_str(),
num_colocator.get_stop_str(),
freq,
num_colocator.colocation_setup.filter_name,
None, # cd.data.attrs["vert_code"],
),
)
return cd, fp

def _combine_coldatas(
self,
num_coldata: ColocatedData,
denum_coldata: ColocatedData,
var_name: str,
obs_entry: ObsEntry,
) -> ColocatedData:
mode = obs_entry.bulk_options[var_name].mode
model_exists = obs_entry.bulk_options[var_name].model_exists
units = obs_entry.bulk_options[var_name].units

if mode == "fraction":
new_data = num_coldata.data / denum_coldata.data

elif mode == "product":
new_data = num_coldata.data * denum_coldata.data
else:
raise ValueError(f"Mode must be either fraction of product, and not {mode}")

Check warning on line 128 in pyaerocom/aeroval/bulkfraction_engine.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/aeroval/bulkfraction_engine.py#L128

Added line #L128 was not covered by tests
if model_exists:
# TODO: Unsure if this works!!!
new_data[1] = num_coldata.data[1].where(new_data[1])

cd = ColocatedData(new_data)

cd.data.attrs = num_coldata.data.attrs
cd.data.attrs["var_name"] = [var_name, var_name]
cd.data.attrs["var_units"] = [units, units]
cd.metadata["var_name_input"] = [var_name, var_name]
return cd

def _get_model_var_names(
self, var_name: str, bulk_vars: list[str], model_exists: bool, model_entry: ModelEntry
) -> tuple[str]:
num_name, denum_name = bulk_vars[0], bulk_vars[1]
if model_exists:
num_name, denum_name = var_name, var_name

model_use_vars = model_entry.model_use_vars
if model_use_vars != {}:
num_name, denum_name = model_use_vars[num_name], model_use_vars[denum_name]

Check warning on line 150 in pyaerocom/aeroval/bulkfraction_engine.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/aeroval/bulkfraction_engine.py#L150

Added line #L150 was not covered by tests

return num_name, denum_name

def get_colocators(
self,
bulk_vars: list,
var_name: str,
freq: str,
model_name: str = None,
obs_name: str = None,
model_exists: bool = False,
) -> list[dict[str | Colocator]]:
"""
Instantiate colocation engine

Parameters
----------
model_name : str, optional
name of model. The default is None.
obs_name : str, optional
name of obs. The default is None.

Returns
-------
Colocator

"""
col_cfg = {**self.cfg.colocation_opts.model_dump()}
outdir = self.cfg.path_manager.get_coldata_dir()
col_cfg["basedir_coldata"] = outdir

if model_name:
mod_cfg = self.cfg.get_model_entry(model_name)
col_cfg["model_cfg"] = mod_cfg

# Hack and at what lowlevel_helpers's import_from was doing
for key, val in mod_cfg.items():
if key in ColocationSetup.model_fields:
col_cfg[key] = val
if obs_name:
obs_cfg = self.cfg.get_obs_entry(obs_name)
pyaro_config = obs_cfg["obs_config"] if "obs_config" in obs_cfg else None
col_cfg["obs_config"] = pyaro_config

# Hack and at what lowlevel_helpers's import_from was doing
for key, val in obs_cfg.model_dump().items():
if key in ColocationSetup.model_fields:
col_cfg[key] = val

col_cfg["add_meta"].update(diurnal_only=self.cfg.get_obs_entry(obs_name).diurnal_only)
cols = []
col_cfg["ts_type"] = freq
for bulk_var in bulk_vars:
col_cfg["obs_vars"] = bulk_var
if model_exists:
col_cfg["model_use_vars"] = {
bulk_var: var_name,
}
col_stp = ColocationSetup(**col_cfg)
col = Colocator(col_stp)

cols.append({bulk_var: col})

return cols
25 changes: 25 additions & 0 deletions pyaerocom/aeroval/data/var_scale_colmap.ini
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,29 @@ colmap = coolwarm

[vcdno2]
scale = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]
colmap = coolwarm


[fractioneBCbb]
scale = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
colmap = coolwarm

[fractioneBCff]
scale = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
colmap = coolwarm

[concebc]
scale = [0.0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.5, 1.0, 2.0]
colmap = coolwarm

[concebcem]
scale = [0.0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.5, 1.0, 2.0]
colmap = coolwarm

[fractioneBCbbem]
scale = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
colmap = coolwarm

[fractioneBCffem]
scale = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
colmap = coolwarm
49 changes: 49 additions & 0 deletions pyaerocom/aeroval/data/var_web_info.ini
Original file line number Diff line number Diff line change
Expand Up @@ -847,3 +847,52 @@ category = Vertical column density
menu_name = VCD CO
vertical_type = 3D
category = Vertical column density


; # Fraction tests
; [pmfraction]
; menu_name = PM Fraction
; vertical_type = 3D
; category = Particle ratio

; [pmfraction2]
; menu_name = PM Fraction 2
; vertical_type = 3D
; category = Particle ratio

[concwetrdn]
menu_name = RDN in Precip
vertical_type = 3D
category = Particle concentrations


# EC
; [fractionECFine]
; menu_name = EC Fine Fraction
; vertical_type = 3D
; category = Particle ratio

; [fractionECCoarse]
; menu_name = EC Fine Fraction
; vertical_type = 3D
; category = Particle ratio

; [fractionEC]
; menu_name = EC Fraction
; vertical_type = 3D
; category = Particle ratio

[fractioneBCbb]
menu_name = eBC Residential Fraction
vertical_type = 3D
category = Particle ratio

[fractioneBCff]
menu_name = eBC Non-Residential Fraction
vertical_type = 3D
category = Particle ratio

[concebc]
menu_name = Equivalent BC
vertical_type = 3D
category = Particle concentrations
5 changes: 5 additions & 0 deletions pyaerocom/aeroval/experiment_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pyaerocom.aeroval.helpers import delete_dummy_model, make_dummy_model
from pyaerocom.aeroval.modelmaps_engine import ModelMapsEngine
from pyaerocom.aeroval.superobs_engine import SuperObsEngine
from pyaerocom.aeroval.bulkfraction_engine import BulkFractionEngine

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -65,6 +66,10 @@ def _run_single_entry(self, model_name, obs_name, var_list):
engine = ColdataToJsonEngine(self.cfg)
engine.run(files_to_convert)

elif ocfg.is_bulk:
engine = BulkFractionEngine(self.cfg)
engine.run(var_list, model_name, obs_name)

else:
# If a var_list is given, only run on the obs networks which contain that variable
if var_list:
Expand Down
Loading
Loading