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

Defining the CoreComponents, LayerStructure and ModelTiming classes #374

Merged
merged 40 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
685e42b
Draft implementation of CoreComponents
davidorme Jan 24, 2024
f5f6a20
Doc fixes
davidorme Jan 24, 2024
4a70211
Forgot to add new tests file
davidorme Jan 24, 2024
01fc628
Extending and fixing tests
davidorme Jan 24, 2024
9513801
Updating callable signatures to use CoreComponents - not unpacked yet.
davidorme Jan 25, 2024
45ac49a
Updating main to use CoreComponents
davidorme Jan 25, 2024
9a76bba
Unpacking core components into new BaseModel attributes
davidorme Jan 25, 2024
548c849
Revising docstrings
davidorme Jan 25, 2024
adaf95b
MoarRevising docstrings
davidorme Jan 25, 2024
b96e68e
Updating Plants Model
davidorme Jan 25, 2024
be73d57
Updating soils model
davidorme Jan 25, 2024
9f2c892
Updating litter model
davidorme Jan 25, 2024
42d8bbc
Updated hydrology_model
davidorme Jan 25, 2024
53153fd
Updated abiotic_simple_model
davidorme Jan 25, 2024
a5cd8bd
Added n_layers to LayerStructure
davidorme Jan 25, 2024
4359c6e
Use n_layers in models
davidorme Jan 25, 2024
03b5a81
Updated animal model
davidorme Jan 25, 2024
e17e368
Converted layer_roles from tuple to list - incompatible with xarray c…
davidorme Jan 25, 2024
f862264
AnimalModel loads wrong constants
davidorme Jan 25, 2024
b02d14a
Added Config and CoreComponents fixtures, updated fixtures using prev…
davidorme Jan 25, 2024
242fe2f
Removing test for deprecated extract_timing_details
davidorme Jan 25, 2024
e3c33a7
Updating plants tests to new BaseModel structure
davidorme Jan 25, 2024
086e53f
Potential solution to BaseModel._repr issue
davidorme Jan 25, 2024
a705be0
Updating soil tests
davidorme Jan 25, 2024
5d55541
Update litter model tests
davidorme Jan 25, 2024
388fc9f
Updating hydrology model tests
davidorme Jan 26, 2024
86c4c93
Updated abiotic_simple model tests
davidorme Jan 26, 2024
cb048e8
Restored BaseModel._check_update_speed functionality
davidorme Jan 26, 2024
e3181e9
Fixing core tests, log message tweaks
davidorme Jan 26, 2024
663d3c4
Deprecated input file location in tests
davidorme Jan 26, 2024
ce3396b
Updating animal model tests
davidorme Jan 26, 2024
127423e
Last test fixes
davidorme Jan 26, 2024
b1c9339
Cleaner start_time setting
davidorme Jan 26, 2024
53d820f
add _validate_canopy_layer function
davidorme Jan 26, 2024
849e651
add _validate_soil_layers function
davidorme Jan 26, 2024
162daa4
Updating core_component.py validators and adding unit tests for valid…
davidorme Jan 26, 2024
20fe796
Merge pull request #375 from ImperialCollegeLondon/369-adopting-new-core
davidorme Feb 2, 2024
42bd787
Indenting issue in RST
davidorme Feb 2, 2024
7443127
Fixing docstring issues
davidorme Feb 5, 2024
b4fb178
Merge branch 'develop' into 369-simplifying-the-model-creation-setup
davidorme Feb 5, 2024
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
23 changes: 23 additions & 0 deletions docs/source/api/core/core_components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
jupytext:
cell_metadata_filter: -all
formats: md:myst
main_language: python
text_representation:
extension: .md
format_name: myst
format_version: 0.13
jupytext_version: 1.13.8
kernelspec:
display_name: vr_python3
language: python
name: vr_python3
---

# API documentation for the {mod}`~virtual_rainforest.core.core_components` module

```{eval-rst}
.. automodule:: virtual_rainforest.core.core_components
:autosummary:
:members:
```
1 change: 1 addition & 0 deletions docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ team.
File readers <api/core/readers.md>
Core axes <api/core/axes.md>
Base Model <api/core/base_model.md>
Core Components <api/core/core_components.md>
Core Constants <api/core/constants.md>
Constants Classes <api/core/constants_class.md>
Constants Loader <api/core/constants_loader.md>
Expand Down
340 changes: 340 additions & 0 deletions tests/core/test_core_components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
"""Check that the core components system works as expected."""

from contextlib import nullcontext as does_not_raise
from logging import ERROR, INFO

import numpy as np
import pytest
from pint import Quantity

from tests.conftest import log_check
from virtual_rainforest.core.exceptions import ConfigurationError

DEFAULT_CANOPY = (
"above",
"canopy",
"canopy",
"canopy",
"canopy",
"canopy",
"canopy",
"canopy",
"canopy",
"canopy",
"canopy",
"subcanopy",
"surface",
"soil",
"soil",
)

ALTERNATE_CANOPY = (
"above",
"canopy",
"canopy",
"canopy",
"subcanopy",
"surface",
"soil",
"soil",
"soil",
)


@pytest.mark.parametrize(
argnames="config, expected_layers, expected_timing, expected_constants",
argvalues=[
pytest.param(
"[core]",
{
"canopy_layers": 10,
"soil_layers": [-0.25, -1.0],
"above_canopy_height_offset": 2.0,
"surface_layer_height": 0.1,
"subcanopy_layer_height": 1.5,
"layer_roles": DEFAULT_CANOPY,
},
{
"start_time": np.datetime64("2013-01-01"),
"run_length": np.timedelta64(63115200, "s"),
"run_length_quantity": Quantity(63115200.0, "second"),
"update_interval": np.timedelta64(2629800, "s"),
"update_interval_quantity": Quantity(2629800.0, "second"),
"end_time": np.datetime64("2015-01-01T12:00:00"),
"reconciled_run_length": np.timedelta64(63115200, "s"),
},
{"depth_of_active_soil_layer": 0.25},
id="defaults",
),
pytest.param(
"""[core.layers]
soil_layers=[-0.1, -0.5, -0.9]
canopy_layers=3
above_canopy_height_offset=1.5
surface_layer_height=0.2
subcanopy_layer_height=1.2
[core.timing]
start_date = "2020-01-01"
update_interval = "10 minutes"
run_length = "30 years"
[core.constants.CoreConsts]
depth_of_active_soil_layer = 2
""",
{
"canopy_layers": 3,
"soil_layers": [-0.1, -0.5, -0.9],
"above_canopy_height_offset": 1.5,
"surface_layer_height": 0.2,
"subcanopy_layer_height": 1.2,
"layer_roles": ALTERNATE_CANOPY,
},
{
"start_time": np.datetime64("2020-01-01"),
"run_length": np.timedelta64(946728000, "s"),
"run_length_quantity": Quantity(946728000.0, "second"),
"update_interval": np.timedelta64(10 * 60, "s"),
"update_interval_quantity": Quantity(10 * 60, "second"),
"end_time": np.datetime64("2049-12-31T12:00:00"),
"reconciled_run_length": np.timedelta64(946728000, "s"),
},
{"depth_of_active_soil_layer": 2},
id="defaults",
),
],
)
def test_CoreComponents(config, expected_layers, expected_timing, expected_constants):
"""Simple test of core component generation."""
from virtual_rainforest.core.config import Config
from virtual_rainforest.core.core_components import CoreComponents

cfg = Config(cfg_strings=config)
core_components = CoreComponents(cfg)

assert core_components.layer_structure.__dict__ == expected_layers
assert core_components.model_timing.__dict__ == expected_timing
assert core_components.core_constants.__dict__ == expected_constants


@pytest.mark.parametrize(
argnames="config_string, raises, expected_values, expected_log",
argvalues=[
pytest.param(
"[core]",
does_not_raise(),
(
10,
[-0.25, -1.0],
2.0,
0.1,
1.5,
DEFAULT_CANOPY,
),
((INFO, "Layer structure built from model configuration"),),
id="defaults",
),
pytest.param(
"""[core.layers]
soil_layers=[-0.1, -0.5, -0.9]
canopy_layers=3
above_canopy_height_offset=1.5
surface_layer_height=0.2
subcanopy_layer_height=1.2
""",
does_not_raise(),
(
3,
[-0.1, -0.5, -0.9],
1.5,
0.2,
1.2,
ALTERNATE_CANOPY,
),
((INFO, "Layer structure built from model configuration"),),
id="alternative",
),
pytest.param(
"""[core.layers]
soil_layers=[0.1, -0.5, -0.9]
canopy_layers=9
above_canopy_height_offset=1.5
surface_layer_height=0.2
subcanopy_layer_height=1.2
""",
pytest.raises(ConfigurationError),
None,
((ERROR, "Soil layer depths must be strictly decreasing and negative."),),
id="bad_soil",
),
],
)
def test_LayerStructure(caplog, config_string, raises, expected_values, expected_log):
"""Test the creation and error handling of LayerStructure."""
from virtual_rainforest.core.config import Config
from virtual_rainforest.core.core_components import LayerStructure

cfg = Config(cfg_strings=config_string)

with raises:
layer_structure = LayerStructure(cfg)

log_check(caplog=caplog, expected_log=expected_log, subset=slice(-1, None, None))

if isinstance(raises, does_not_raise):
assert layer_structure.canopy_layers == expected_values[0]
assert layer_structure.soil_layers == expected_values[1]
assert layer_structure.above_canopy_height_offset == expected_values[2]
assert layer_structure.surface_layer_height == expected_values[3]
assert layer_structure.subcanopy_layer_height == expected_values[4]
assert layer_structure.layer_roles == expected_values[5]


@pytest.mark.parametrize(
"config,output,raises,expected_log_entries",
[
pytest.param(
"""[core.timing]
start_date = "2020-01-01"
update_interval = "10 minutes"
run_length = "30 years"
""",
{
"start_time": np.datetime64("2020-01-01"),
"update_interval": np.timedelta64(10, "m"),
"update_interval_as_quantity": Quantity("10 minutes"),
"end_time": np.datetime64("2049-12-31T12:00"),
},
does_not_raise(),
(
(
INFO,
"Timing details built from model configuration: "
"start - 2020-01-01, end - 2049-12-31T12:00:00, "
"run length - 946728000 seconds",
),
),
id="timing correct",
),
pytest.param(
"""[core.timing]
start_date = "2020-01-01"
update_interval = "10 metres"
run_length = "30 years"
""",
None,
pytest.raises(ConfigurationError),
((ERROR, "Invalid units for core.timing.update_interval: "),),
id="bad update dimension",
),
pytest.param(
"""[core.timing]
start_date = "2020-01-01"
update_interval = "10 epochs"
run_length = "30 years"
""",
None,
pytest.raises(ConfigurationError),
((ERROR, "Invalid units for core.timing.update_interval: "),),
id="unknown update unit",
),
pytest.param(
"""[core.timing]
start_date = "2020-01-01"
update_interval = "10 minutes"
run_length = "1 minute"
""",
{}, # Fails so no output to check
pytest.raises(ConfigurationError),
(
(
ERROR,
"Model run length (1 minute) expires before first "
"update (10 minutes)",
),
),
id="run length too short",
),
],
)
def test_ModelTiming(caplog, config, output, raises, expected_log_entries):
"""Test that function to extract main loop timing works as intended."""
from virtual_rainforest.core.config import Config
from virtual_rainforest.core.core_components import ModelTiming

config_obj = Config(cfg_strings=config)
caplog.clear()

with raises:
model_timing = ModelTiming(config=config_obj)

assert model_timing.end_time == output["end_time"]
assert model_timing.update_interval == output["update_interval"]
assert model_timing.start_time == output["start_time"]
assert (
model_timing.update_interval_quantity
== output["update_interval_as_quantity"]
)

log_check(caplog=caplog, expected_log=expected_log_entries)


@pytest.mark.parametrize(
argnames="value, raises",
argvalues=[
(1, does_not_raise()),
(1.23, does_not_raise()),
(np.infty, pytest.raises(ConfigurationError)),
(np.nan, pytest.raises(ConfigurationError)),
(-9, pytest.raises(ConfigurationError)),
(-9.5, pytest.raises(ConfigurationError)),
("h", pytest.raises(ConfigurationError)),
([1], pytest.raises(ConfigurationError)),
],
)
def test__validate_positive_finite_numeric(value, raises):
"""Testing private validation function."""
from virtual_rainforest.core.core_components import (
_validate_positive_finite_numeric,
)

with raises:
_validate_positive_finite_numeric(value, "label")


@pytest.mark.parametrize(
argnames="value, raises",
argvalues=[
(10, does_not_raise()),
(1.23, pytest.raises(ConfigurationError)),
(np.infty, pytest.raises(ConfigurationError)),
(np.nan, pytest.raises(ConfigurationError)),
(-9, pytest.raises(ConfigurationError)),
(-9.5, pytest.raises(ConfigurationError)),
("h", pytest.raises(ConfigurationError)),
([1], pytest.raises(ConfigurationError)),
],
)
def test__validate_positive_integer(value, raises):
"""Testing private validation function."""
from virtual_rainforest.core.core_components import _validate_positive_integer

with raises:
_validate_positive_integer(value)


@pytest.mark.parametrize(
argnames="value, raises",
argvalues=[
(1, pytest.raises(ConfigurationError)),
("h", pytest.raises(ConfigurationError)),
([1], pytest.raises(ConfigurationError)),
([-1], does_not_raise()),
([-1, -0.5], pytest.raises(ConfigurationError)),
([-0.5, -1.5], does_not_raise()),
],
)
def test__validate_soil_layers(value, raises):
"""Testing private validation function."""
from virtual_rainforest.core.core_components import _validate_soil_layers

with raises:
_validate_soil_layers(value)
Loading
Loading