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

feat: Add default scaler #9

Merged
merged 2 commits into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add `default_scaler` to the configuration for the scaler to be used if no specific scaler configuration is provided [#9](https://github.com/climate-resource/spaemis/pull/9)
- Move test configuration to `test-data` directory [#8](https://github.com/climate-resource/spaemis/pull/8)
- Add functionality to write out an xarray dataset as a set of CSVs that are formatted the same as the input emissions inventory data [#7](https://github.com/climate-resource/spaemis/pull/7)
- Add functionality to write out a xarray dataset as a set of CSVs that are formatted the same as the input emissions inventory data [#7](https://github.com/climate-resource/spaemis/pull/7)
- Add relative_change scaler and reading of Input4MIPs data [#3](https://github.com/climate-resource/spaemis/pull/3)
- Added CLI command `project` and a framework for scalers [#2](https://github.com/climate-resource/spaemis/pull/2)
- Initial commit and repository setup
- Initial commit and repository setup
35 changes: 31 additions & 4 deletions src/spaemis/commands/project_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@

import logging
import os.path
from itertools import product
from typing import Dict, Tuple

import click
import xarray as xr

from spaemis.commands.base import cli
from spaemis.config import DownscalingScenarioConfig, VariableConfig, load_config
from spaemis.config import DownscalingScenarioConfig, VariableScalerConfig, load_config
from spaemis.inventory import EmissionsInventory, load_inventory, write_inventory_csvs
from spaemis.scaling import get_scaler_by_config

logger = logging.getLogger(__name__)


def scale_inventory(
cfg: VariableConfig, inventory: EmissionsInventory, target_year: int
cfg: VariableScalerConfig, inventory: EmissionsInventory, target_year: int
) -> xr.Dataset:
"""
Scale a given variable/sector
Expand Down Expand Up @@ -68,10 +70,35 @@ def calculate_projections(

The dimensionality of the output variables is (sector, year, lat, lon)
"""
scaling_configs: Dict[Tuple[str, str], VariableScalerConfig] = {
(cfg.variable, cfg.sector): cfg for cfg in config.scalers
}

if config.default_scaler:
# Add in additional scalers for each missing variable/sector
variables = inventory.data.data_vars.keys()
sectors = inventory.data["sector"].values

for variable, sector in product(variables, sectors):
scaling_configs.setdefault(
(variable, sector),
VariableScalerConfig(
variable=variable,
sector=sector,
method=config.default_scaler,
),
)

projections = []
for variable_config in config.variables:

for variable_config in scaling_configs.values():
for slice_year in config.timeslices:
logger.info(f"Processing year={slice_year}")
logger.info(
"Processing variable=%s sector=%s year=%i",
variable_config.variable,
variable_config.sector,
slice_year,
)
res = scale_inventory(variable_config, inventory, slice_year)
projections.append(res)

Expand Down
7 changes: 4 additions & 3 deletions src/spaemis/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Description of the configuration
"""

from typing import Any, ClassVar, Literal, Type, Union, get_args
from typing import Any, ClassVar, Literal, Optional, Type, Union, get_args

from attrs import define
from cattrs.preconf.pyyaml import make_converter
Expand Down Expand Up @@ -42,7 +42,7 @@ def _discriminate_scaler(value: Any, _klass: Type) -> ScalerMethod:


@define
class VariableConfig:
class VariableScalerConfig:
variable: str
sector: str
method: ScalerMethod
Expand All @@ -57,7 +57,8 @@ class DownscalingScenarioConfig:
inventory_name: str
inventory_year: int
timeslices: list[int]
variables: list[VariableConfig]
scalers: list[VariableScalerConfig]
default_scaler: Optional[ScalerMethod] = None


def load_config(config_file: str) -> DownscalingScenarioConfig:
Expand Down
12 changes: 8 additions & 4 deletions src/spaemis/config/scenarios/ssp245.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,30 @@ timeslices:
- 2020
- 2040
- 2060
- 2080

variables:
default_scaler:
name: constant

scalers:
- variable: NOx
sector: industry
method:
name: relative_change
source_id: IAMC-MESSAGE-GLOBIOM-ssp245-1-1
source_id: &source IAMC-MESSAGE-GLOBIOM-ssp245-1-1
variable_id: NOx-em-anthro
sector: Industrial Sector
- variable: NOx
sector: motor_vehicles
method:
name: relative_change
source_id: IAMC-MESSAGE-GLOBIOM-ssp245-1-1
source_id: *source
variable_id: NOx-em-anthro
sector: Transportation Sector
- variable: CO
sector: industry
method:
name: relative_change
source_id: IAMC-MESSAGE-GLOBIOM-ssp245-1-1
source_id: *source
variable_id: CO-em-anthro
sector: Industrial Sector
2 changes: 1 addition & 1 deletion tests/test-data/config/test-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ timeslices:
- 2040
- 2060

variables:
scalers:
- variable: NOx
sector: industry
method:
Expand Down
37 changes: 32 additions & 5 deletions tests/unit/commands/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

from spaemis.commands import cli
from spaemis.commands.project_command import calculate_projections, scale_inventory
from spaemis.config import VariableConfig, converter, load_config
from spaemis.config import (
ConstantScaleMethod,
VariableScalerConfig,
converter,
load_config,
)


def test_cli_project(runner, config_file, tmpdir, mocker, inventory):
Expand Down Expand Up @@ -59,7 +64,7 @@ def test_scale_inventory_missing_variable(inventory):
"sector": "Industrial",
"method": {"name": "constant"},
},
VariableConfig,
VariableScalerConfig,
)
with pytest.raises(ValueError, match="Variable missing not available in inventory"):
scale_inventory(config, inventory, 2040)
Expand All @@ -72,7 +77,7 @@ def test_scale_inventory_missing_sector(inventory):
"sector": "unknown",
"method": {"name": "constant"},
},
VariableConfig,
VariableScalerConfig,
)
with pytest.raises(ValueError, match="Sector unknown not available in inventory"):
scale_inventory(config, inventory, 2040)
Expand All @@ -85,7 +90,7 @@ def test_scale_inventory_constant(inventory):
"sector": "rail",
"method": {"name": "constant"},
},
VariableConfig,
VariableScalerConfig,
)
res = scale_inventory(config, inventory, 2040)
assert isinstance(res, xr.Dataset)
Expand All @@ -111,7 +116,7 @@ def test_scale_inventory_relative(inventory):
"sector": "Transportation Sector",
},
},
VariableConfig,
VariableScalerConfig,
)
res = scale_inventory(config, inventory, 2040)
assert isinstance(res, xr.Dataset)
Expand Down Expand Up @@ -143,3 +148,25 @@ def test_calculate_projections(config, inventory):
assert res["CO"].sel(sector="motor_vehicles").isnull().all()
# but CO|industry should have data
assert not res["CO"].sel(sector="industry").isnull().all()


def test_calculate_projections_with_default(config, inventory):
config.default_scaler = ConstantScaleMethod()

res = calculate_projections(config, inventory)

assert (res["sector"] == inventory.data["sector"]).all()

# CO|architect_coating should be held constant
xr.testing.assert_allclose(
res["CO"]
.sel(sector="architect_coating", year=2040)
.reset_coords("year", drop=True),
inventory.data["CO"].sel(sector="architect_coating"),
)
# CO|industry should be scaled
with pytest.raises(AssertionError):
xr.testing.assert_allclose(
res["CO"].sel(sector="industry", year=2040).reset_coords("year", drop=True),
inventory.data["CO"].sel(sector="industry"),
)