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

System to allow constants to be altered based on configuration #258

Merged
merged 30 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ea84407
Altered abiotic_simple schema to allow for a constants object
jacobcook1995 Jul 13, 2023
7201272
Made seperate constants file for abiotic_simple module
jacobcook1995 Jul 13, 2023
5b43b14
Updated docs for abiotic constants move
jacobcook1995 Jul 13, 2023
6ad7726
Made the microclimate constants into a single dataclass
jacobcook1995 Jul 14, 2023
6191cd5
Made the parameters an attribute of the SimpleAbioticModel
jacobcook1995 Jul 14, 2023
c5e7af0
Started supply constants directly to run_microclimate
jacobcook1995 Jul 14, 2023
b81ff15
Started supplying parameters in setup step as well
jacobcook1995 Jul 14, 2023
033ac10
Added tests that constants in (abiotic_simple) config are handled cor…
jacobcook1995 Jul 14, 2023
003ff17
Added utility function to warn user of all incorrectly named constants
jacobcook1995 Jul 14, 2023
b10396d
Added test for check_constants utility function
jacobcook1995 Jul 17, 2023
6daf2f1
Added extra test of from_config for incorrectly named constants
jacobcook1995 Jul 17, 2023
6a9922f
Updated config docs with details of the new constants system
jacobcook1995 Jul 17, 2023
34f6104
Added details of constants system to adding new models docs
jacobcook1995 Jul 17, 2023
86509c5
Removed outdated TODO
jacobcook1995 Jul 17, 2023
7af00a4
Renamed AbioticSimpleParams to AbioticSimpleConsts
jacobcook1995 Jul 17, 2023
6d877c6
Renamed parameters to constants within code
jacobcook1995 Jul 17, 2023
a8ef343
Changed defining new models docs to only talk about constants rather …
jacobcook1995 Jul 17, 2023
3622d0f
Changed parameters to constants
jacobcook1995 Jul 19, 2023
38bc95f
Removed constants section from config docs
jacobcook1995 Jul 19, 2023
65e1170
Added docs page on constants
jacobcook1995 Jul 19, 2023
cc99938
Added link to parameterisation docs
jacobcook1995 Jul 19, 2023
1713509
Fixed grammer errors Vivienne noticed
jacobcook1995 Jul 19, 2023
443ac42
Added link to the configuration docs
jacobcook1995 Jul 19, 2023
93f9af7
Update virtual_rainforest/core/utils.py
jacobcook1995 Jul 19, 2023
dc61043
Fixed broken tests
jacobcook1995 Jul 19, 2023
04541b9
Renamed check_constants to something more descriptive
jacobcook1995 Jul 19, 2023
ab70c90
Simplified and renamed parameterisation docs
jacobcook1995 Jul 19, 2023
bad0c37
Removed brackets from LOGGER output
jacobcook1995 Jul 19, 2023
90062da
Updated index with new name of parameterisation doc
jacobcook1995 Jul 19, 2023
a5831fc
Moved constants definition description to defining_new_models, and ad…
jacobcook1995 Jul 19, 2023
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
24 changes: 24 additions & 0 deletions docs/source/api/abiotic_simple/abiotic_constants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
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 for the {mod}`~virtual_rainforest.models.abiotic_simple.constants` module

```{eval-rst}
.. automodule:: virtual_rainforest.models.abiotic_simple.constants
:autosummary:
:members:
:special-members: __init__
```
69 changes: 64 additions & 5 deletions docs/source/development/defining_new_models.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ directory.
mkdir virtual_rainforest/models/freshwater
```

You will need to create at least three files within this folder, although you may choose
You will need to create at least four files within this folder, although you may choose
to add other python modules containing different parts of the module functionality.

* An `__init__.py` file, which tells Python that the folder is a submodule within the
Expand All @@ -43,13 +43,49 @@ to add other python modules containing different parts of the module functionali
object.
* A JSON Schema file defining the model configuration, called
`{model_name}_schema.json`.
* A python module `constants.py` that will contain the constants relevant to the model.

For example:

```bash
touch virtual_rainforest/models/freshwater/__init__.py
touch virtual_rainforest/models/freshwater/freshwater_model.py
touch virtual_rainforest/models/freshwater/freshwater_schema.json
touch virtual_rainforest/models/freshwater/constants.py
```

## Defining constants and their default values

Each model should define a `constants.py` module. Constants and their default values
should be defined in this module using {func}`dataclasses.dataclass`. These constants
can be stored in a single data class or spread over multiple data classes. However,
having a large number of data classes is likely to make the downstream code messier, so
constants should only be split across multiple classes when there's a strong reason to
do so.

It's also important that every constant is given an explicit type hint. If a type hint
is not provided then `dataclass` treats the constant as a class attribute rather than an
instance attribute. This means that its value cannot be changed when a new instance is
created.

An example `constants.py` file is shown below:

```python
from dataclasses import dataclass

# The dataclass must be frozen to prevent constants from being accidentally altered
# during runtime
@dataclass(frozen=True)
class ExampleConsts:
"""Dataclass to store all constants for the `example_model` model."""

# Each constant must be given a type hint, otherwise its default value cannot be
# changed
example_constant_1: float = -1.27
"""Details of source of constant and its units."""

example_constant_2: float = 5.4
"""Details of source of constant and its units."""
```

## Defining the new model class
Expand Down Expand Up @@ -177,6 +213,7 @@ def __init__(
data: Data,
update_interval: pint.Quantity,
no_of_ponds: int,
constants: FreshwaterConsts,
**kwargs: Any,
):

Expand All @@ -193,6 +230,9 @@ def __init__(

# Store model specific details as attributes.
self.no_of_ponds = int(no_of_ponds)

# Store the constants relevant to the freshwater model
self.constants = constants

# Save attribute names to be used by the __repr__
self._repr.append("no_of_ponds")
Expand Down Expand Up @@ -300,6 +340,14 @@ The method then uses those parsed arguments to actually call the `__init__` meth
return an initialised instance of the model using the settings. The `from_config`
method should raise an `InitialisationError` if the configuration fails.

The `from_config` method should also check if any constants have been provided as part
of the configuration. If they haven't a default set of constants is generated. If they
have been supplied they are used to generate a custom set of constants. The
{func}`~virtual_rainforest.core.utils.check_valid_constant_names` utility function is
used to check that no constant has been supplied with an incorrect name. At least one
constants class should be created, but it's fine to split constants across more classes
if that makes for clearer code.

As an example:

```python
Expand All @@ -322,11 +370,22 @@ def from_config(
# Non-timing details now extracted
no_of_pools = config["freshwater"]["no_of_pools"]

LOGGER.info(
"Information required to initialise the soil model successfully "
"extracted."
# Check if any constants have been supplied
if "freshwater" in config and "constants" in config["freshwater"]:
# Checks that constants is config are as expected
check_valid_constant_names(config, "freshwater", "FreshwaterConsts")
# If an error isn't raised then generate the dataclass
constants = FreshwaterConsts(
**config["freshwater"]["constants"]["FreshwaterConsts"]
)
return cls(data, update_interval, no_pools)
else:
# If no constants are supplied then the defaults should be used
constants = FreshwaterConsts()

LOGGER.info(
"Information required to initialise the soil model successfully extracted."
)
return cls(data, update_interval, no_pools, constants)

```

Expand Down
2 changes: 2 additions & 0 deletions docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ team.
virtual_rainforest/module_overview.md
virtual_rainforest/usage.md
virtual_rainforest/main_simulation.md
virtual_rainforest/constants.md
virtual_rainforest/soil/soil_details.md
virtual_rainforest/core/grid.md
virtual_rainforest/core/data.md
Expand Down Expand Up @@ -96,6 +97,7 @@ team.
Abiotic Simple Overview <api/abiotic_simple.md>
Abiotic Simple Model <api/abiotic_simple/abiotic_simple_model.md>
Abiotic Simple Microclimate <api/abiotic_simple/microclimate.md>
Abiotic Simple Constants <api/abiotic_simple/abiotic_constants.md>
Abiotic Hydrology Overview <api/hydrology.md>
Abiotic Hydrology Model <api/hydrology/hydrology_model.md>
Abiotic Hydrology Constants <api/hydrology/hydrology_constants.md>
Expand Down
32 changes: 32 additions & 0 deletions docs/source/virtual_rainforest/constants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Virtual Rainforest parameterisation

The Virtual Rainforest contains a very large number of constants. These constants are
assigned default values based on either site specific data or best estimates from the
literature. However, in many cases this still leads to significant uncertainty about
true values of constants. Because of this, the Virtual Rainforest is set up to allow all
constants to be changed. This allows end users to change constant values if they have
access to better estimates, and also allows for sensitivity analysis to be performed.

As a quick note on terminology, we have chosen to call all our parameters "constants",
despite many of them not being truly universal constants. This is to make it clear that
none of them should be changed within a given Virtual Rainforest simulation. Though it
is fine to use different values for them across different simulations.

## Using non-default values for constants

If you want to use a non-default value for a constant this can be accomplished using the
[configuration system](core/config.md). The configuration for each specific model
contains a `constants` section. Within this section constants are grouped based on the
name of the data class they belong to. An example of this can be seen below:

```toml
[example_model.constants.ExampleConsts]
example_constant_1 = 23.0
example_constant_2 = -7.7
```

Any values supplied in this way will be used to override the default values for the data
class in question. Only constants for which non-default values are supplied will be
replaced. Anything that is not included within the configuration will just take the
default value, which is set in the data class (see
[here](../development/defining_new_models.md) for further details).
40 changes: 40 additions & 0 deletions tests/core/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,46 @@ def test_Config_build_schema(
),
id="unexpected_property",
),
pytest.param(
{
"core": {"modules": ["abiotic_simple"]},
},
does_not_raise(),
((INFO, "Configuration validated"),),
id="no constants",
),
pytest.param(
{
"core": {"modules": ["abiotic_simple"]},
"abiotic_simple": {
"constants": {"AbioticSimpleConsts": {"constant1": 1.0}}
},
},
does_not_raise(),
((INFO, "Configuration validated"),),
id="correct constant",
),
pytest.param(
{
"core": {"modules": ["abiotic_simple"]},
"abiotic_simple": {"constants": {"constant1": 1.0}},
},
pytest.raises(ConfigurationError),
(
(
ERROR,
"Configuration error in ['abiotic_simple', 'constants']: "
"'AbioticSimpleConsts' is a required property",
),
(
ERROR,
"Configuration error in ['abiotic_simple', 'constants']: Additional"
" properties are not allowed ('constant1' was unexpected)",
),
(CRITICAL, "Configuration contains schema violations: check log"),
),
id="missing AbioticSimpleConsts",
),
],
)
def test_Config_validate_config(
Expand Down
57 changes: 56 additions & 1 deletion tests/core/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Testing the utility functions."""

from contextlib import nullcontext as does_not_raise
from logging import CRITICAL, ERROR
from logging import CRITICAL, ERROR, INFO
from pathlib import Path

import pytest
Expand Down Expand Up @@ -131,3 +131,58 @@ def test_set_layer_roles(soil_layers, canopy_layers, raises, caplog, exp_log):

# Final check that expected logging entries are produced
log_check(caplog, exp_log)


@pytest.mark.parametrize(
"config,raises,exp_log",
[
pytest.param(
{
"abiotic_simple": {
"constants": {
"AbioticSimpleConsts": {"air_temperature_gradient": -1.0}
}
}
},
does_not_raise(),
(),
id="expected_constants",
),
pytest.param(
{
"abiotic_simple": {
"constants": {
"AbioticSimpleConsts": {
"air_temperature_gradient": -1.0,
"invented_constant": 37.9,
"saturation_vapour_pressure_factor4": 0.07,
}
}
}
},
pytest.raises(ConfigurationError),
(
(
ERROR,
"Unknown names supplied for AbioticSimpleConsts: ",
),
(
INFO,
"Valid names are as follows: ",
),
),
id="unexpected_constants",
),
],
)
def test_check_valid_constant_names(caplog, config, raises, exp_log):
"""Check that function to check constants behaves as expected."""
from virtual_rainforest.core.utils import check_valid_constant_names

with raises:
check_valid_constant_names(
config, model_name="abiotic_simple", class_name="AbioticSimpleConsts"
)

# Final check that expected logging entries are produced
log_check(caplog, exp_log)
Loading