API for the abiotic_tools module#

The models.abiotic.abiotic_tools module contains a set of general functions that are shared across submodules in the abiotic_model model.

TODO cross-check with pyrealm for duplication/ different implementation TODO change temperatures to Kelvin

Functions:

build_indices(data, layer_structure)

Build indices for different layers and variables for easier access.

calculate_actual_vapour_pressure(...)

Calculate actual vapour pressure, [kPa].

calculate_air_density(air_temperature, ...)

Calculate the density of air using the ideal gas law.

calculate_atmospheric_layer_geometry(data, ...)

Calculate heights, layer thickness, and midpoints for atmospheric layers.

calculate_latent_heat_vapourisation(...)

Calculate latent heat of vapourisation.

calculate_molar_density_air(temperature, ...)

Calculate temperature-dependent molar density of air.

calculate_slope_of_saturated_pressure_curve(...)

Calculate slope of the saturated pressure curve.

calculate_specific_humidity(air_temperature, ...)

Calculate specific humidity.

compute_aboveground_layer_thickness(heights)

Calculate layer thickness for above ground layers only.

compute_layer_thickness_for_varying_canopy(heights)

Calculate layer thickness for varying canopy layers, true layers only.

compute_weights_from_absorbed_radiation(...)

Convert a 2D radiation array into normalized weights that sum to 1.

fill_layer_template(layer_structure, assignments)

Fill layer template with index values.

find_last_valid_row(array)

Find last valid value in array for each column.

finite_and_within(arr, low, high, name)

Test that values are finite and within bounds.

generate_diurnal_cycle_from_monthly_data(...)

Generate synthetic hourly forcing for one day from monthly averages.

initialize_data_record(variables, time_dim, ...)

Create a data_record dict with a new leading time dimension.

mean_to_layers(var, index, data_record, ...)

Return mean value over time for given variable and fill into layer structure.

record_hourly_output(hour, data_record, ...)

Record hourly data.

set_unintended_nan_to_zero(input_array, ...)

Clean up outputs: set unintended NaNs to 0, preserve intended NaNs.

update_profile_from_reference(...)

Update a layer-based profile for a given time index using a reference variable.

validate_variables(names, values[, exclude])

Validate all variables are in update dictionary.

virtual_ecosystem.models.abiotic.abiotic_tools.build_indices(data: Data, layer_structure: LayerStructure) SimpleNamespace[source]#

Build indices for different layers and variables for easier access.

Parameters:
  • data – Data object

  • layer_structure – Layer structure object

Returns:

SimpleNamespace with indices for different layers and variables

virtual_ecosystem.models.abiotic.abiotic_tools.calculate_actual_vapour_pressure(air_temperature: DataArray, relative_humidity: DataArray, pyrealm_core_constants: CoreConst) DataArray[source]#

Calculate actual vapour pressure, [kPa].

Parameters:
  • air_temperature – Air temperature, [C]

  • relative_humidity – Relative humidity, [-]

  • pyrealm_core_constants – Set of constants from pyrealm

Returns:

actual vapour pressure, [kPa]

virtual_ecosystem.models.abiotic.abiotic_tools.calculate_air_density(air_temperature: ndarray[tuple[Any, ...], dtype[floating]], atmospheric_pressure: ndarray[tuple[Any, ...], dtype[floating]], specific_gas_constant_dry_air: float, celsius_to_kelvin: float)[source]#

Calculate the density of air using the ideal gas law.

Parameters:
  • air_temperature – Air temperature, [C]

  • atmospheric_pressure – Atmospheric pressure, [kPa]

  • specific_gas_constant_dry_air – Specific gas constant for dry air, [J kg-1 K-1]

  • celsius_to_kelvin – Factor to convert temperature in Celsius to absolute temperature in Kelvin

Returns:

density of air, [kg m-3].

virtual_ecosystem.models.abiotic.abiotic_tools.calculate_atmospheric_layer_geometry(data: Data, idx: SimpleNamespace, minimum_mixing_depth: float) dict[str, ndarray[tuple[Any, ...], dtype[floating]]][source]#

Calculate heights, layer thickness, and midpoints for atmospheric layers.

The midpoint values are distances in metres above ground for each cell. For layer heights below the surface layer, a minimum mixing depth is introduced. This is to prevent unrealistically low mixing depths and therefore high temperatures in the lowest canopy layer when the layer height is very low. Note that this is an artificial inflation of the mixing depth.

Parameters:
  • data – Data object

  • idx – SimpleNamespace containing layer indices

  • minimum_mixing_depth – Minimum depth for lowest canopy layer, [m]

Returns:

dict containing heights, thickness, layer_top, layer_midpoints

virtual_ecosystem.models.abiotic.abiotic_tools.calculate_latent_heat_vapourisation(temperature: ndarray[tuple[Any, ...], dtype[floating]], celsius_to_kelvin: float, latent_heat_vap_equ_factors: tuple[float, float]) ndarray[tuple[Any, ...], dtype[floating]][source]#

Calculate latent heat of vapourisation.

Implementation after Eq. 8, Henderson-Sellers (1984).

Parameters:
  • temperature – Air temperature, [C]

  • celsius_to_kelvin – Factor to convert temperature in Celsius to absolute temperature in Kelvin

  • latent_heat_vap_equ_factors – Factors in calculation of latent heat of vapourisation

Returns:

latent heat of vapourisation, [kJ kg-1]

virtual_ecosystem.models.abiotic.abiotic_tools.calculate_molar_density_air(temperature: ndarray[tuple[Any, ...], dtype[floating]], atmospheric_pressure: ndarray[tuple[Any, ...], dtype[floating]], standard_mole: float, standard_pressure: float, celsius_to_kelvin: float) ndarray[tuple[Any, ...], dtype[floating]][source]#

Calculate temperature-dependent molar density of air.

Implementation after Maclean and Klinges (2021).

Parameters:
  • temperature – Air temperature, [C]

  • atmospheric_pressure – Atmospheric pressure, [kPa]

  • standard_mole – Moles of ideal gas in 1 m^3 air at standard atmosphere

  • standard_pressure – Standard atmospheric pressure, [kPa]

  • celsius_to_kelvin – Factor to convert temperature in Celsius to absolute temperature in Kelvin

Returns:

molar density of air, [mol m-3]

virtual_ecosystem.models.abiotic.abiotic_tools.calculate_slope_of_saturated_pressure_curve(temperature: ndarray[tuple[Any, ...], dtype[floating]], saturated_pressure_slope_parameters: tuple[float, float, float, float]) ndarray[tuple[Any, ...], dtype[floating]][source]#

Calculate slope of the saturated pressure curve.

Parameters:
  • temperature – Temperature, [C]

  • saturated_pressure_slope_parameters – List of parameters to calculate the slope of the saturated vapour pressure curve

Returns:

Slope of the saturated pressure curve, \(\Delta_{v}\)

virtual_ecosystem.models.abiotic.abiotic_tools.calculate_specific_humidity(air_temperature: ndarray[tuple[Any, ...], dtype[floating]], relative_humidity: ndarray[tuple[Any, ...], dtype[floating]], atmospheric_pressure: ndarray[tuple[Any, ...], dtype[floating]], molecular_weight_ratio_water_to_dry_air: float, pyrealm_core_constants: CoreConst) ndarray[tuple[Any, ...], dtype[floating]][source]#

Calculate specific humidity.

Parameters:
  • air_temperature – Air temperature, [C]

  • relative_humidity – Relative humidity, [%]

  • atmospheric_pressure – Atmospheric pressure, [kPa]

  • molecular_weight_ratio_water_to_dry_air – The ratio of the molar mass of water vapour to the molar mass of dry air

  • pyrealm_core_constants – Pyrealm core constants

Returns:

Specific humidity, [kg kg-1]

virtual_ecosystem.models.abiotic.abiotic_tools.compute_aboveground_layer_thickness(heights: ndarray[tuple[Any, ...], dtype[floating]]) ndarray[tuple[Any, ...], dtype[floating]][source]#

Calculate layer thickness for above ground layers only.

Calculate layer thickness by subtracting from the next valid layer below (skipping NaNs), and for the last valid layer in each column subtract from zero (ground level). Soil layers are set to NaN.

Parameters:

heights – 2D array of layer heights, [m]

Returns:

2D array of layer thickness, [m], same shape as input

virtual_ecosystem.models.abiotic.abiotic_tools.compute_layer_thickness_for_varying_canopy(heights: ndarray[tuple[Any, ...], dtype[floating]]) ndarray[tuple[Any, ...], dtype[floating]][source]#

Calculate layer thickness for varying canopy layers, true layers only.

Calculate layer thickness by subtracting from the next valid layer below (skipping NaNs), and for the last valid layer in each column subtract from zero (ground level).

Parameters:

heights – 2D array of layer heights, [m]

Returns:

2D array of layer thickness, [m], same shape as input

virtual_ecosystem.models.abiotic.abiotic_tools.compute_weights_from_absorbed_radiation(radiation: ndarray[tuple[Any, ...], dtype[floating]]) ndarray[tuple[Any, ...], dtype[floating]][source]#

Convert a 2D radiation array into normalized weights that sum to 1.

Weights sum to 1 along the layer axis (axis=0) for each cell independently, ignoring NaN values. NaN entries in the input remain NaN in the output. Cells where all valid radiation is zero return NaN weights — these are cells with no canopy and no radiation to distribute.

Parameters:

radiation – 2D array of absorbed radiation values for each layer and cell

Returns:

2D array of normalized weights corresponding to the absorbed radiation

virtual_ecosystem.models.abiotic.abiotic_tools.fill_layer_template(layer_structure: LayerStructure, assignments: list[tuple]) ndarray[tuple[Any, ...], dtype[floating]][source]#

Fill layer template with index values.

Parameters:
  • layer_structure – LayerStructure

  • assignments – list of variable names, indices and values

Returns:

array with updated indices

virtual_ecosystem.models.abiotic.abiotic_tools.find_last_valid_row(array: ndarray[tuple[Any, ...], dtype[floating]]) ndarray[tuple[Any, ...], dtype[floating]][source]#

Find last valid value in array for each column.

This function looks for the last valid value in each column of a 2-dimensional array. If the previous value is nan, it moved up the array. If all values are NaN, the value is set to NaN, too.

Parameters:

array – Two-dimesional array for which last valid values should be found

Returns:

Array that contains last valid values

virtual_ecosystem.models.abiotic.abiotic_tools.finite_and_within(arr: DataArray, low: float, high: float, name: str) None[source]#

Test that values are finite and within bounds.

Parameters:
  • arr – Output DataArray to be tested

  • low – Minimum value

  • high – maximum value

  • name – name of variable

Returns:

None.

Raises:

AssertionError – if values are not finite or outside bounds.

virtual_ecosystem.models.abiotic.abiotic_tools.generate_diurnal_cycle_from_monthly_data(monthly_air_temperature: ndarray[tuple[Any, ...], dtype[floating]], monthly_shortwave_absorption: ndarray[tuple[Any, ...], dtype[floating]], monthly_relative_humidity: ndarray[tuple[Any, ...], dtype[floating]], monthly_evapotranspiration: ndarray[tuple[Any, ...], dtype[floating]], monthly_soil_evaporation: ndarray[tuple[Any, ...], dtype[floating]], latitude_deg: float, month: int, days: int, daily_temp_amplitude: float = 5.0) dict[str, ndarray[tuple[Any, ...], dtype[floating]]][source]#

Generate synthetic hourly forcing for one day from monthly averages.

Parameters:
  • monthly_air_temperature – Monthly mean air temperature [C]

  • monthly_shortwave_absorption – Monthly mean daily shortwave absorption [W m-2]

  • monthly_relative_humidity – Monthly mean relative humidity [%]

  • monthly_evapotranspiration – Monthly total evapotranspiration [mm/month]

  • monthly_soil_evaporation – Monthly total soil evaporation [mm/month]

  • latitude_deg – Latitude for daylength calculation [deg]

  • month – Month number [1-12]

  • days – Number of days in month

  • daily_temp_amplitude – typical diurnal temperature swing [C]

Returns:

dict of arrays air_temperature_hourly, shortwave_absorption_hourly, relative_humidity_hourly, evapotranspiration_hourly, soil_evaporation_hourly

virtual_ecosystem.models.abiotic.abiotic_tools.initialize_data_record(variables: dict[str, DataArray], time_dim: int, layers: int, cell_ids: int) dict[str, ndarray[tuple[Any, ...], dtype[floating]]][source]#

Create a data_record dict with a new leading time dimension.

Assumptions are that 1D variables have shape (cell_ids,) and 2D variables have shape (layers, cell_ids).

Parameters:
  • variables – Dictionary of variable names to template arrays

  • time_dim – Size of the new time dimension (e.g. 24)

  • layers – Number of layers

  • cell_ids – Number of cell ids

Returns:

Dictionary with initialized arrays filled with NaNs.

Raises:

ValueError – is number of dimensions cannot be matched

virtual_ecosystem.models.abiotic.abiotic_tools.mean_to_layers(var: str, index: list[int], data_record: dict, layer_structure: LayerStructure) DataArray[source]#

Return mean value over time for given variable and fill into layer structure.

Parameters:
  • var – Variable name

  • index – List of layer indices to fill

  • data_record – Data record dict

  • layer_structure – LayerStructure object

Returns:

DataArray with mean values filled into layer structure

virtual_ecosystem.models.abiotic.abiotic_tools.record_hourly_output(hour: int, data_record: dict, hourly_values: dict)[source]#

Record hourly data.

Parameters:
  • hour – Hour of the day

  • data_record – dict that contains all hourly data

  • hourly_values – Hourly values

Returns:

updated dict with hour values

virtual_ecosystem.models.abiotic.abiotic_tools.set_unintended_nan_to_zero(input_array: ndarray[tuple[Any, ...], dtype[floating]], input_nan_mask: ndarray[tuple[Any, ...], dtype[bool]]) ndarray[tuple[Any, ...], dtype[floating]][source]#

Clean up outputs: set unintended NaNs to 0, preserve intended NaNs.

Parameters:
  • input_array – Input array that may contain NaN

  • input_nan_mask – A mask of intended NaN

Returns:

Array with unintended NaN set to zero

virtual_ecosystem.models.abiotic.abiotic_tools.update_profile_from_reference(layer_structure: LayerStructure, mask_variable: DataArray, variable_name: DataArray, time_index: int) DataArray[source]#

Update a layer-based profile for a given time index using a reference variable.

This function

  • extracts a mask from air temperature to determine valid atmosphere layers

  • reads the reference variable at the given time index

  • applies the mask to keep only valid layers

  • fills the profile template for those layers

Parameters:
  • layer_structure – LayerStructure object defining the layer setup

  • mask_variable – DataArray used to create the atmospheric mask

  • variable_name – Reference variable (e.g. data[“atmospheric_pressure_ref”])

  • time_index – Index of the current time step

Returns:

Updated layer profile as a DataArray

virtual_ecosystem.models.abiotic.abiotic_tools.validate_variables(names: tuple[str, ...], values: dict[str, object], exclude: Iterable[str] = ()) None[source]#

Validate all variables are in update dictionary.

Parameters:
  • names – variable names in output

  • values – variables in hourly update

  • exclude – variable names to ignore in the comparison

Returns:

None

Raises:

ValueError – if variable mismatch is detected.