ImperialCollegeLondon / ImperialCollegeLondon/virtual_ecosystem

The disturbance implementation

Open
#1,365 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
20
Forks
5
Avg merge
2d 1h
Merged PRs (30d)
34

Description

### Discussed in https://github.com/ImperialCollegeLondon/virtual_ecosystem/discussions/1059

Originally posted by **davidorme** September 29, 2025
This discussion is intended to provide a more concrete explanation of the features of the implementation of the `disturbance` module. The first draft is from discussion with @davidorme, @jacobcook1995, @TaranRallings, @vgro and @sallymatson.

## Big picture

We need a module that:
* Defines a `BaseDisturbance` abstract base class. We want a bunch of different actual Disturbance subclasses that share a common framework.
* Stands separately from the `BaseModel`. There might have some feature overlap with the BaseModel but until we know quite well what the overlap is, we don't want to retool `BaseModel` prematurely, even if the initial code for `BaseDisturbance` is less DRY as a result. The `BaseDisturbance` will also be experimental for some time, so we don't want to tie the two things together too early.

We could in theory implement disturbances by adding processes in the BaseModel subclasses themselves but:

* We want to keep the existing models as non-anthropogenic
* We don't want to expand the existing complexity of those models and disturbances can themselves be complex.
* We want disturbances to be able to access other science models - the `BaseModel` subclasses are isolated from each other.

### Implementation sketch

* The configuration should have a `disturbance` section that then includes the configuration of named disturbances:

```toml
[disturbance.logging]
timing = "" # details to come
priority = 1
config = "" # details to come
```
It is likely that there may be other elements of the `config` section that can be generalised, but we don't have a great idea of the details yet.

* The `disturbance` module does need a `DisturbanceRegistry`, so that the simulation setup can check for the existence of the "logging" disturbance before configuring it and we can provide a route in for user provided Disturbances (As an aside, we don't yet have a good example of how users could provide their own `BaseModelABC` instances - have a config `user_models` and `user_disturbances` entries that give paths to code?)

* It also needs a `DisturbanceTiming` class - we don't know exactly what this might look like but at the moment it is basically either an interval (every N updates) or specific time indices to run at. It needs the `core_components.ModelTiming` to check the
values are sensible (basically just inside the known number of updates).

```python
DisturbanceRegistry: dict[str, BaseDisturbance]
"""A registry of disturbance implementations."""

class DisturbanceTiming:

def __init__(
self,
model_timing: ModelTiming,
run_first: int: None,
then_run_every: int | None= None,
run_at: list[int] | None = None,
) -> None:
"Create an instance"
# Checks the values map onto the model timing and probably coerces
# the run_first/run_every form into a `run_at` list

def check_run(self, time_index) -> bool:
return True if time_index in self.run_at else False

class BaseDisturbance(ABC)

disturbance_name: str
"""A name for the disturbance (eg. 'logging') that is used to key the disturbance in the DisturbanceRegistry and config."""
disturbed_models: list[str]
"""A list of model names that this disturbance will affect."""
data_variables_disturbed: list[str]
"""A list of data variables that will be updated."""

def __init__(self, data: Data, models: dict[str: BaseModel], timing: DisturbanceTiming, **kwargs):
"""Creates an instance."""
self.data = data
self.models = models
self.timing = timing

# check that the self.models contains cls.disturbed_models. This is checked
# when the class is registered (does the BaseModel exist _at all_) but also needs
# to be checked at runtime (is the BaseModel in _this simulation__).

def __init_subclass__(self, disturbance_name: str, disturbed_models: list[str]):
"""Checks the disturbed models and variables are all known and adds the disturbance to the registry."""

@classmethod
def from_config(cls, config: Config) -> BaseDisturbance:
"""Factory method to create instance from a Config instance."""

@abstractmethod
def disturb(self, time_index):
if not self.timing.check_run(time_index):
return
# Otherwise, do stuff to self.data and self.models

```

### Execution

* The disturbance instances should be created after the models are created.
* The disturbances should execute their `disturb()` methods after the model `update()` methods have been run - we can't run disturbances _before_ the updates because not all data variables are defined until after all models have run their first `update()`.
* The execution order of disturbances is configured from the `priority` configuration. The disturbances are executed in decreasing priority, with random execution order for equal priority values.

### Examples

**Simple example: fertilizer**

* Edit data object to increase ammonium concentration in the soil
* The user would just have to define the cells, concentration of nitrogen to add and/or rate, and frequency

**Another simple example: Manuring**

* This is more complex because it's a new litter pool - so it would require the model have a Manure pool that is empty unless the Disturbance is running.
* The disturbance itself would then populate the pool variables according to a schedule.

**Forestry example**

* Identify cells that we want forestry to occur in
* Identify which size class & PFTs we want taken out
* Go into community, identify which cohorts we want to manipulate, and then reset the number of individuals in those cohorts
* Manipulate the data as needed

**Animals**

* Basically need to add animals or take animals out.
* Taking individuals out of cohorts out with some filters (hunting) is easier
* Reintroductions is harder - need to provide cohort data for new cohorts to be added.


Contributor guide

Open the contributing guide

Research direction

Start by reading core_components.ModelTiming and the existing BaseModel, Data, and Config interfaces referenced in the proposal. Map how simulation models are created and updated before defining the disturbance module, registry, timing, configuration, and execution order. Done means the open design questions are resolved and the proposed disturbance lifecycle is specified well enough to implement.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.