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 20 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__
```
36 changes: 31 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,16 @@ 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
(in one or more dataclasses).

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 the new model class
Expand Down Expand Up @@ -177,6 +180,7 @@ def __init__(
data: Data,
update_interval: pint.Quantity,
no_of_ponds: int,
constants: FreshwaterConsts,
**kwargs: Any,
):

Expand All @@ -193,6 +197,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 +307,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_constants` 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 +337,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_constants(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/parameterisation.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
59 changes: 59 additions & 0 deletions docs/source/virtual_rainforest/parameterisation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Virtual Rainforest parameterisation

The Virtual Rainforest contains a very large number of constants. These constants are
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file mixes user facing information and model creation information. The user-facing stuff is fairly short, but this is far more detailed than is needed here. I suspect we may need to break up the creating a model section into multiple pages, but we can park that.

If the structure of the constants classes etc moves into new model creation, we can then be more specific about details: add a type, because otherwise dataclass treats it as a class attribute, rather than a instance attribute.

We also should think about what other user facing help we want here. I can imagine that we might add an export_model_constants class method to the BaseModel which just spits out a TOML document from any model of the constants. I don't know if that is something we need to put in place for @dalonsoa?
That could then be exposed via a command line endpoint: vr export_model_constants plants plant_constants.toml.

What users need to know in this document is how constants are used, how to find what they can change and how to change it in TOML. I'm also wondering if calling it constants.md might be better 😄

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.

## Defining constants and their default values

Each model should define a `constants.py` submodule. Constants and their default values
should be defined in this submodule 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, otherwise
the default value cannot be overwritten. 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."""
```

## 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. The configuration for each specific model contains a `constants`
section. Within this section each 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 use the
default value.
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
59 changes: 58 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,60 @@ 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,
"Incorrect constant names supplied for AbioticSimpleConsts "
"dataclass: ['invented_constant', "
"'saturation_vapour_pressure_factor4']",
),
(
INFO,
"Valid names are as follows: [",
),
),
id="unexpected_constants",
),
],
)
def test_check_constants(caplog, config, raises, exp_log):
"""Check that function to check constants behaves as expected."""
from virtual_rainforest.core.utils import check_constants

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

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