hestia-earth-models 0.61.7__py3-none-any.whl → 0.61.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hestia-earth-models might be problematic. Click here for more details.
- hestia_earth/models/cycle/completeness/electricityFuel.py +56 -0
- hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +44 -59
- hestia_earth/models/geospatialDatabase/histosol.py +4 -0
- hestia_earth/models/ipcc2006/co2ToAirOrganicSoilCultivation.py +4 -2
- hestia_earth/models/ipcc2006/n2OToAirOrganicSoilCultivationDirect.py +1 -1
- hestia_earth/models/ipcc2019/aboveGroundCropResidueTotal.py +1 -1
- hestia_earth/models/ipcc2019/belowGroundCropResidue.py +1 -1
- hestia_earth/models/ipcc2019/ch4ToAirExcreta.py +1 -1
- hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +511 -458
- hestia_earth/models/ipcc2019/co2ToAirUreaHydrolysis.py +5 -1
- hestia_earth/models/ipcc2019/organicCarbonPerHa.py +117 -3881
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +2060 -0
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +1630 -0
- hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +324 -0
- hestia_earth/models/mocking/search-results.json +252 -252
- hestia_earth/models/site/organicCarbonPerHa.py +58 -44
- hestia_earth/models/site/soilMeasurement.py +18 -13
- hestia_earth/models/utils/__init__.py +28 -0
- hestia_earth/models/utils/array_builders.py +578 -0
- hestia_earth/models/utils/blank_node.py +2 -3
- hestia_earth/models/utils/descriptive_stats.py +285 -0
- hestia_earth/models/utils/emission.py +73 -2
- hestia_earth/models/utils/inorganicFertiliser.py +2 -2
- hestia_earth/models/utils/measurement.py +118 -4
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.61.8.dist-info}/METADATA +1 -1
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.61.8.dist-info}/RECORD +43 -31
- tests/models/cycle/completeness/test_electricityFuel.py +21 -0
- tests/models/emepEea2019/test_nh3ToAirInorganicFertiliser.py +2 -2
- tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +54 -165
- tests/models/ipcc2019/test_organicCarbonPerHa.py +219 -460
- tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +471 -0
- tests/models/ipcc2019/test_organicCarbonPerHa_tier_2_utils.py +208 -0
- tests/models/ipcc2019/test_organicCarbonPerHa_utils.py +75 -0
- tests/models/site/test_organicCarbonPerHa.py +3 -12
- tests/models/site/test_soilMeasurement.py +3 -18
- tests/models/utils/test_array_builders.py +253 -0
- tests/models/utils/test_descriptive_stats.py +134 -0
- tests/models/utils/test_emission.py +51 -1
- tests/models/utils/test_measurement.py +54 -2
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.61.8.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.61.8.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.61.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1630 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The IPCC Tier 2 methodology for estimating soil organic carbon stock changes in the 0 - 30cm depth interval due to
|
|
3
|
+
management changes.
|
|
4
|
+
|
|
5
|
+
More information on this model, including data requirements **and** recommendations, and examples can be found in the
|
|
6
|
+
[Hestia SOC wiki](https://gitlab.com/hestia-earth/hestia-engine-models/-/wikis/Soil-organic-carbon-modelling).
|
|
7
|
+
|
|
8
|
+
Source: [IPCC 2019, Vol. 4, Chapter 5](https://www.ipcc-nggip.iges.or.jp/public/2019rf/vol4.html).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from numpy import array, empty, exp, minimum, random, where, vstack
|
|
13
|
+
from numpy.typing import NDArray
|
|
14
|
+
from pydash.objects import merge
|
|
15
|
+
from typing import Any, Callable, Union
|
|
16
|
+
|
|
17
|
+
from hestia_earth.schema import (
|
|
18
|
+
CycleFunctionalUnit, MeasurementMethodClassification, SiteSiteType, TermTermType
|
|
19
|
+
)
|
|
20
|
+
from hestia_earth.utils.model import find_term_match, filter_list_term_type
|
|
21
|
+
from hestia_earth.utils.tools import flatten, list_sum, non_empty_list
|
|
22
|
+
|
|
23
|
+
from hestia_earth.models.utils.array_builders import (
|
|
24
|
+
avg_run_in_columnwise, gen_seed, grouped_avg, repeat_1d_array_as_columns
|
|
25
|
+
)
|
|
26
|
+
from hestia_earth.models.utils.blank_node import (
|
|
27
|
+
cumulative_nodes_lookup_match, cumulative_nodes_term_match, get_node_value, group_nodes_by_year,
|
|
28
|
+
group_nodes_by_year_and_month, GroupNodesByYearMode
|
|
29
|
+
)
|
|
30
|
+
from hestia_earth.models.utils.cycle import check_cycle_site_ids_identical
|
|
31
|
+
from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
|
|
32
|
+
from hestia_earth.models.utils.measurement import _new_measurement
|
|
33
|
+
from hestia_earth.models.utils.property import get_node_property
|
|
34
|
+
from hestia_earth.models.utils.site import related_cycles
|
|
35
|
+
|
|
36
|
+
from .organicCarbonPerHa_utils import (
|
|
37
|
+
CarbonSource, check_consecutive, DEPTH_LOWER, DEPTH_UPPER, check_irrigation,
|
|
38
|
+
get_crop_residue_inc_or_left_terms_with_cache,
|
|
39
|
+
get_upland_rice_crop_terms_with_cache,
|
|
40
|
+
get_upland_rice_land_cover_terms_with_cache,
|
|
41
|
+
IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE, IPCC_MANAGEMENT_CATEGORY_TO_TILLAGE_MANAGEMENT_LOOKUP_VALUE,
|
|
42
|
+
IpccLandUseCategory, IpccManagementCategory, MIN_AREA_THRESHOLD, MIN_YIELD_THRESHOLD, sample_constant,
|
|
43
|
+
sample_plus_minus_uncertainty, sample_truncated_normal, STATS_DEFINITION
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
_LOOKUPS = {
|
|
47
|
+
"crop": "IPCC_LAND_USE_CATEGORY",
|
|
48
|
+
"tillage": "IPCC_TILLAGE_MANAGEMENT_CATEGORY"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_TERM_ID = 'organicCarbonPerHa'
|
|
52
|
+
_METHOD_CLASSIFICATION = MeasurementMethodClassification.TIER_2_MODEL.value
|
|
53
|
+
|
|
54
|
+
_RUN_IN_PERIOD = 5
|
|
55
|
+
|
|
56
|
+
_SAND_CONTENT_TERM_ID = "sandContent"
|
|
57
|
+
_NUMBER_OF_TILLAGES_TERM_ID = "numberOfTillages"
|
|
58
|
+
_TEMPERATURE_MONTHLY_TERM_ID = "temperatureMonthly"
|
|
59
|
+
_PRECIPITATION_MONTHLY_TERM_ID = "precipitationMonthly"
|
|
60
|
+
_PET_MONTHLY_TERM_ID = "potentialEvapotranspirationMonthly"
|
|
61
|
+
_CARBON_CONTENT_TERM_ID = "carbonContent"
|
|
62
|
+
_NITROGEN_CONTENT_TERM_ID = "nitrogenContent"
|
|
63
|
+
_LIGNIN_CONTENT_TERM_ID = "ligninContent"
|
|
64
|
+
|
|
65
|
+
_CARBON_INPUT_PROPERTY_TERM_IDS = [
|
|
66
|
+
_CARBON_CONTENT_TERM_ID,
|
|
67
|
+
_NITROGEN_CONTENT_TERM_ID,
|
|
68
|
+
_LIGNIN_CONTENT_TERM_ID
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
_CARBON_SOURCE_TERM_TYPES = [
|
|
72
|
+
TermTermType.ORGANICFERTILISER.value,
|
|
73
|
+
TermTermType.SOILAMENDMENT.value
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
_VALID_SITE_TYPES = [
|
|
77
|
+
SiteSiteType.CROPLAND.value
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
_VALID_FUNCTIONAL_UNITS = [
|
|
81
|
+
CycleFunctionalUnit._1_HA.value
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _measurement(
|
|
86
|
+
timestamps: list[int],
|
|
87
|
+
descriptive_stats_dict: dict
|
|
88
|
+
) -> dict:
|
|
89
|
+
"""
|
|
90
|
+
Build a Hestia `Measurement` node to contain a value and descriptive statistics calculated by the models.
|
|
91
|
+
|
|
92
|
+
The `descriptive_stats_dict` parameter should include the following keys and values from the
|
|
93
|
+
[Measurement](https://www-staging.hestia.earth/schema/Measurement) schema:
|
|
94
|
+
```
|
|
95
|
+
{
|
|
96
|
+
"value": list[float],
|
|
97
|
+
"sd": list[float],
|
|
98
|
+
"min": list[float],
|
|
99
|
+
"max": list[float],
|
|
100
|
+
"statsDefinition": str,
|
|
101
|
+
"observations": list[int]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
timestamps : list[int]
|
|
108
|
+
A list of calendar years associated to the calculated SOC stocks.
|
|
109
|
+
descriptive_stats_dict : dict
|
|
110
|
+
A dict containing the descriptive statistics data that should be added to the node.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
dict
|
|
115
|
+
A valid Hestia `Measurement` node, see: https://www.hestia.earth/schema/Measurement.
|
|
116
|
+
"""
|
|
117
|
+
measurement = _new_measurement(_TERM_ID) | descriptive_stats_dict
|
|
118
|
+
measurement["dates"] = [f"{year}-12-31" for year in timestamps]
|
|
119
|
+
measurement["depthUpper"] = DEPTH_UPPER
|
|
120
|
+
measurement["depthLower"] = DEPTH_LOWER
|
|
121
|
+
measurement["methodClassification"] = _METHOD_CLASSIFICATION
|
|
122
|
+
return measurement
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class _InventoryKey(Enum):
|
|
126
|
+
"""
|
|
127
|
+
Enum representing the inner keys of the annual inventory is constructed from site and cycle data.
|
|
128
|
+
"""
|
|
129
|
+
TEMP_MONTHLY = 'temperature-monthly'
|
|
130
|
+
PRECIP_MONTHLY = 'precipitation-monthly'
|
|
131
|
+
PET_MONTHLY = 'pet-monthly'
|
|
132
|
+
IRRIGATED_MONTHLY = 'irrigated-monthly'
|
|
133
|
+
CARBON_INPUT = 'carbon-input'
|
|
134
|
+
N_CONTENT = 'nitrogen-content'
|
|
135
|
+
LIGNIN_CONTENT = 'lignin-content'
|
|
136
|
+
TILLAGE_CATEGORY = 'ipcc-tillage-category'
|
|
137
|
+
SAND_CONTENT = 'sand-content'
|
|
138
|
+
IS_PADDY_RICE = 'is-paddy-rice'
|
|
139
|
+
SHOULD_RUN = 'should-run-tier-2'
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
_REQUIRED_KEYS = {
|
|
143
|
+
_InventoryKey.TEMP_MONTHLY,
|
|
144
|
+
_InventoryKey.PRECIP_MONTHLY,
|
|
145
|
+
_InventoryKey.PET_MONTHLY,
|
|
146
|
+
_InventoryKey.CARBON_INPUT,
|
|
147
|
+
_InventoryKey.N_CONTENT,
|
|
148
|
+
_InventoryKey.LIGNIN_CONTENT,
|
|
149
|
+
_InventoryKey.TILLAGE_CATEGORY,
|
|
150
|
+
_InventoryKey.IS_PADDY_RICE
|
|
151
|
+
}
|
|
152
|
+
"""
|
|
153
|
+
The `_InventoryKey`s that must have valid values for an inventory year to be included in the model.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class _Parameter(Enum):
|
|
158
|
+
"""
|
|
159
|
+
The default Tier 2 model parameters provided in the IPCC (2019) report.
|
|
160
|
+
"""
|
|
161
|
+
ACTIVE_DECAY_FACTOR = {
|
|
162
|
+
"value": 7.4,
|
|
163
|
+
"min": 7.4,
|
|
164
|
+
"max": 7.4,
|
|
165
|
+
"sd": 0
|
|
166
|
+
}
|
|
167
|
+
SLOW_DECAY_FACTOR = {
|
|
168
|
+
"value": 0.209,
|
|
169
|
+
"min": 0.058,
|
|
170
|
+
"max": 0.3,
|
|
171
|
+
"sd": 0.566
|
|
172
|
+
}
|
|
173
|
+
PASSIVE_DECAY_FACTOR = {
|
|
174
|
+
"value": 0.00689,
|
|
175
|
+
"min": 0.005,
|
|
176
|
+
"max": 0.01,
|
|
177
|
+
"sd": 0.00125
|
|
178
|
+
}
|
|
179
|
+
F_1 = {
|
|
180
|
+
"value": 0.378,
|
|
181
|
+
"min": 0.01,
|
|
182
|
+
"max": 0.8,
|
|
183
|
+
"sd": 0.0719
|
|
184
|
+
}
|
|
185
|
+
F_2_FULL_TILLAGE = {
|
|
186
|
+
"value": 0.455,
|
|
187
|
+
# No stats available in IPCC excel document.
|
|
188
|
+
}
|
|
189
|
+
F_2_REDUCED_TILLAGE = {
|
|
190
|
+
"value": 0.477
|
|
191
|
+
# No stats available in IPCC excel document.
|
|
192
|
+
}
|
|
193
|
+
F_2_NO_TILLAGE = {
|
|
194
|
+
"value": 0.5
|
|
195
|
+
# No stats available in IPCC excel document.
|
|
196
|
+
}
|
|
197
|
+
F_2_UNKNOWN_TILLAGE = {
|
|
198
|
+
"value": 0.368,
|
|
199
|
+
"min": 0.007,
|
|
200
|
+
"max": 0.5,
|
|
201
|
+
"sd": 0.0998
|
|
202
|
+
}
|
|
203
|
+
F_3 = {
|
|
204
|
+
"value": 0.455,
|
|
205
|
+
"min": 0.1,
|
|
206
|
+
"max": 0.8,
|
|
207
|
+
"sd": 0.201
|
|
208
|
+
}
|
|
209
|
+
F_5 = {
|
|
210
|
+
"value": 0.0855,
|
|
211
|
+
"min": 0.037,
|
|
212
|
+
"max": 0.1,
|
|
213
|
+
"sd": 0.0122
|
|
214
|
+
}
|
|
215
|
+
F_6 = {
|
|
216
|
+
"value": 0.0504,
|
|
217
|
+
"min": 0.02,
|
|
218
|
+
"max": 0.19,
|
|
219
|
+
"sd": 0.0280
|
|
220
|
+
}
|
|
221
|
+
F_7 = {
|
|
222
|
+
"value": 0.42,
|
|
223
|
+
"min": 0.42,
|
|
224
|
+
"max": 0.42,
|
|
225
|
+
"sd": 0
|
|
226
|
+
}
|
|
227
|
+
F_8 = {
|
|
228
|
+
"value": 0.45,
|
|
229
|
+
"min": 0.45,
|
|
230
|
+
"max": 0.45,
|
|
231
|
+
"sd": 0
|
|
232
|
+
}
|
|
233
|
+
TILLAGE_FACTOR_FULL_TILLAGE = {
|
|
234
|
+
"value": 3.036,
|
|
235
|
+
"min": 1.4,
|
|
236
|
+
"max": 4.0,
|
|
237
|
+
"sd": 0.579
|
|
238
|
+
}
|
|
239
|
+
TILLAGE_FACTOR_REDUCED_TILLAGE = {
|
|
240
|
+
"value": 2.075,
|
|
241
|
+
"min": 1.0,
|
|
242
|
+
"max": 3.0,
|
|
243
|
+
"sd": 0.569
|
|
244
|
+
}
|
|
245
|
+
TILLAGE_FACTOR_NO_TILLAGE = {
|
|
246
|
+
"value": 1,
|
|
247
|
+
"min": 1,
|
|
248
|
+
"max": 1,
|
|
249
|
+
"sd": 0
|
|
250
|
+
}
|
|
251
|
+
MAXIMUM_TEMPERATURE = {
|
|
252
|
+
"value": 45,
|
|
253
|
+
"min": 45,
|
|
254
|
+
"max": 45,
|
|
255
|
+
"sd": 0
|
|
256
|
+
}
|
|
257
|
+
OPTIMUM_TEMPERATURE = {
|
|
258
|
+
"value": 33.69,
|
|
259
|
+
"min": 30.7,
|
|
260
|
+
"max": 35.34,
|
|
261
|
+
"sd": 0.66
|
|
262
|
+
}
|
|
263
|
+
WATER_FACTOR_SLOPE = {
|
|
264
|
+
"value": 1.331,
|
|
265
|
+
"min": 0.8,
|
|
266
|
+
"max": 2.0,
|
|
267
|
+
"sd": 0.386
|
|
268
|
+
}
|
|
269
|
+
DEFAULT_CARBON_CONTENT = {
|
|
270
|
+
"value": 0.42
|
|
271
|
+
# No stats provided in IPCC report.
|
|
272
|
+
}
|
|
273
|
+
DEFAULT_NITROGEN_CONTENT = {
|
|
274
|
+
"value": 0.0083,
|
|
275
|
+
"uncertainty": 75
|
|
276
|
+
}
|
|
277
|
+
DEFAULT_LIGNIN_CONTENT = {
|
|
278
|
+
"value": 0.073,
|
|
279
|
+
"uncertainty": 50
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
_PARAMETER_TO_SAMPLE_FUNCTION = {
|
|
284
|
+
_Parameter.ACTIVE_DECAY_FACTOR: sample_constant,
|
|
285
|
+
_Parameter.F_2_FULL_TILLAGE: sample_constant,
|
|
286
|
+
_Parameter.F_2_REDUCED_TILLAGE: sample_constant,
|
|
287
|
+
_Parameter.F_2_NO_TILLAGE: sample_constant,
|
|
288
|
+
_Parameter.F_7: sample_constant,
|
|
289
|
+
_Parameter.F_8: sample_constant,
|
|
290
|
+
_Parameter.TILLAGE_FACTOR_NO_TILLAGE: sample_constant,
|
|
291
|
+
_Parameter.MAXIMUM_TEMPERATURE: sample_constant,
|
|
292
|
+
_Parameter.DEFAULT_CARBON_CONTENT: sample_constant,
|
|
293
|
+
_Parameter.DEFAULT_NITROGEN_CONTENT: sample_plus_minus_uncertainty,
|
|
294
|
+
_Parameter.DEFAULT_LIGNIN_CONTENT: sample_plus_minus_uncertainty
|
|
295
|
+
}
|
|
296
|
+
_DEFAULT_SAMPLE_FUNCTION = sample_truncated_normal
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _sample_parameter(
|
|
300
|
+
iterations: int,
|
|
301
|
+
parameter: _Parameter,
|
|
302
|
+
seed: Union[int, random.Generator, None] = None
|
|
303
|
+
) -> NDArray:
|
|
304
|
+
"""
|
|
305
|
+
Sample a model `_Parameter` using the function specified in `_PARAMETER_TO_SAMPLE_FUNCTION` or
|
|
306
|
+
`_DEFAULT_SAMPLE_FUNCTION`.
|
|
307
|
+
|
|
308
|
+
Parameters
|
|
309
|
+
----------
|
|
310
|
+
iterations : int
|
|
311
|
+
The number of samples to be taken.
|
|
312
|
+
parameter : _Parameter
|
|
313
|
+
The model parameter to be sampled.
|
|
314
|
+
seed : int | Generator | None, optional
|
|
315
|
+
A seed to initialize the BitGenerator. If passed a Generator, it will be returned unaltered. If `None`, then
|
|
316
|
+
fresh, unpredictable entropy will be pulled from the OS.
|
|
317
|
+
|
|
318
|
+
Returns
|
|
319
|
+
-------
|
|
320
|
+
NDArray
|
|
321
|
+
A numpy array with shape `(1, iterations)`. All columns contain different sample values.
|
|
322
|
+
"""
|
|
323
|
+
kwargs = parameter.value
|
|
324
|
+
func = _get_sample_func(parameter)
|
|
325
|
+
return func(iterations=iterations, seed=seed, **kwargs)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _get_sample_func(parameter: _Parameter) -> Callable:
|
|
329
|
+
"""Extracted into method to allow for mocking of sample function."""
|
|
330
|
+
return _PARAMETER_TO_SAMPLE_FUNCTION.get(parameter, _DEFAULT_SAMPLE_FUNCTION)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# --- TIER 2 MODEL ---
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def should_run(site: dict) -> tuple[bool, dict, dict]:
|
|
337
|
+
"""
|
|
338
|
+
Extract data from site & related cycles, pre-process data and determine whether there is sufficient data to run the
|
|
339
|
+
Tier 2 model.
|
|
340
|
+
|
|
341
|
+
The returned `inventory` should be a dict with the shape:
|
|
342
|
+
```
|
|
343
|
+
{
|
|
344
|
+
year (int): {
|
|
345
|
+
_InventoryKey.SHOULD_RUN: bool,
|
|
346
|
+
_InventoryKey.TEMP_MONTHLY: list[float],
|
|
347
|
+
_InventoryKey.PRECIP_MONTHLY: list[float],
|
|
348
|
+
_InventoryKey.PET_MONTHLY: list[float],
|
|
349
|
+
_InventoryKey.IRRIGATED_MONTHLY: list[bool]
|
|
350
|
+
_InventoryKey.CARBON_INPUT: float,
|
|
351
|
+
_InventoryKey.N_CONTENT: float,
|
|
352
|
+
_InventoryKey.TILLAGE_CATEGORY: IpccManagementCategory,
|
|
353
|
+
_InventoryKey.SAND_CONTENT: float
|
|
354
|
+
},
|
|
355
|
+
...
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
The returned `kwargs` should be a dict with the shape:
|
|
360
|
+
```
|
|
361
|
+
{
|
|
362
|
+
"sand_content": float
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Parameters
|
|
367
|
+
----------
|
|
368
|
+
site : dict
|
|
369
|
+
A Hestia `Site` node, see: https://www.hestia.earth/schema/Site.
|
|
370
|
+
|
|
371
|
+
Returns
|
|
372
|
+
-------
|
|
373
|
+
tuple[bool, dict, dict]
|
|
374
|
+
A tuple containing `(should_run_, inventory, kwargs)`.
|
|
375
|
+
"""
|
|
376
|
+
site_type = site.get("siteType", "")
|
|
377
|
+
measurement_nodes = site.get("measurements", [])
|
|
378
|
+
cycles = related_cycles(site)
|
|
379
|
+
|
|
380
|
+
has_measurements = len(measurement_nodes) > 0
|
|
381
|
+
has_related_cycles = len(cycles) > 0
|
|
382
|
+
has_functional_unit_1_ha = all(cycle.get("functionalUnit") in _VALID_FUNCTIONAL_UNITS for cycle in cycles)
|
|
383
|
+
|
|
384
|
+
should_compile_inventory = all([
|
|
385
|
+
site_type in _VALID_SITE_TYPES,
|
|
386
|
+
has_measurements,
|
|
387
|
+
has_related_cycles,
|
|
388
|
+
check_cycle_site_ids_identical(cycles),
|
|
389
|
+
has_functional_unit_1_ha
|
|
390
|
+
])
|
|
391
|
+
|
|
392
|
+
inventory, kwargs = (
|
|
393
|
+
_compile_inventory(cycles, measurement_nodes)
|
|
394
|
+
if should_compile_inventory else ({}, {})
|
|
395
|
+
)
|
|
396
|
+
kwargs["seed"] = gen_seed(site)
|
|
397
|
+
|
|
398
|
+
valid_years = [year for year, group in inventory.items() if group.get(_InventoryKey.SHOULD_RUN)]
|
|
399
|
+
|
|
400
|
+
should_run_ = all([
|
|
401
|
+
len(valid_years) >= _RUN_IN_PERIOD,
|
|
402
|
+
check_consecutive(valid_years),
|
|
403
|
+
any(inventory.get(year).get(_InventoryKey.SAND_CONTENT) for year in valid_years) or kwargs.get("sand_content")
|
|
404
|
+
])
|
|
405
|
+
|
|
406
|
+
logs = {
|
|
407
|
+
"site_type": site_type,
|
|
408
|
+
"has_measurements": has_measurements,
|
|
409
|
+
"has_related_cycles": has_related_cycles,
|
|
410
|
+
"has_functional_unit_1_ha": has_functional_unit_1_ha,
|
|
411
|
+
"should_compile_inventory_tier_2": should_compile_inventory,
|
|
412
|
+
"should_run_tier_2": should_run_
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return should_run_, inventory, kwargs, logs
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def run(
|
|
419
|
+
inventory: dict[int: dict[_InventoryKey: Any]],
|
|
420
|
+
*,
|
|
421
|
+
iterations: int,
|
|
422
|
+
run_in_period: int = 5,
|
|
423
|
+
sand_content: float = 0.33,
|
|
424
|
+
seed: Union[int, random.Generator, None] = None,
|
|
425
|
+
**_
|
|
426
|
+
) -> tuple[list[int], NDArray, NDArray, NDArray]:
|
|
427
|
+
"""
|
|
428
|
+
Run the IPCC Tier 2 SOC model on a time series of annual data about a site and the mangagement activities taking
|
|
429
|
+
place on it. To avoid any errors, the `inventory` parameter must be pre-validated by the `should_run` function.
|
|
430
|
+
|
|
431
|
+
The inventory should be in the following shape:
|
|
432
|
+
```
|
|
433
|
+
{
|
|
434
|
+
year (int): {
|
|
435
|
+
_InventoryKey.SHOULD_RUN: bool,
|
|
436
|
+
_InventoryKey.TEMP_MONTHLY: list[float],
|
|
437
|
+
_InventoryKey.PRECIP_MONTHLY: list[float],
|
|
438
|
+
_InventoryKey.PET_MONTHLY: list[float],
|
|
439
|
+
_InventoryKey.IRRIGATED_MONTHLY: list[bool]
|
|
440
|
+
_InventoryKey.CARBON_INPUT: float,
|
|
441
|
+
_InventoryKey.N_CONTENT: float,
|
|
442
|
+
_InventoryKey.TILLAGE_CATEGORY: IpccManagementCategory,
|
|
443
|
+
_InventoryKey.SAND_CONTENT: float
|
|
444
|
+
},
|
|
445
|
+
...
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
TODO: interpolate between `sandContent` measurements for different years of the inventory
|
|
450
|
+
|
|
451
|
+
Parameters
|
|
452
|
+
----------
|
|
453
|
+
inventory : dict
|
|
454
|
+
The inventory built by the `should_run` function.
|
|
455
|
+
iterations : int
|
|
456
|
+
Number of iterations to run the model for.
|
|
457
|
+
run_in_period : int, optional
|
|
458
|
+
The length of the run-in period in years, must be greater than or equal to 1. Default value: `5`.
|
|
459
|
+
sand_content : float, optional
|
|
460
|
+
A back-up sand content for if none are found in the inventory, decimal proportion. Default value: `0.33`.
|
|
461
|
+
seed : int | Generator | None, optional
|
|
462
|
+
A seed to initialize the BitGenerator. If passed a Generator, it will be returned unaltered. If `None`, then
|
|
463
|
+
fresh, unpredictable entropy will be pulled from the OS.
|
|
464
|
+
|
|
465
|
+
Returns
|
|
466
|
+
-------
|
|
467
|
+
list[dict]
|
|
468
|
+
A list of HESTIA nodes containing model output results.
|
|
469
|
+
"""
|
|
470
|
+
valid_inventory = {
|
|
471
|
+
year: group for year, group in inventory.items() if group.get(_InventoryKey.SHOULD_RUN)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
def _unpack_inventory(inventory_key: _InventoryKey, monthly: bool = False) -> NDArray:
|
|
475
|
+
"""
|
|
476
|
+
Unpack the inventory dict into numpy arrays with the correct shape.
|
|
477
|
+
"""
|
|
478
|
+
unpacked = [group[inventory_key] for group in valid_inventory.values()]
|
|
479
|
+
arr = array(flatten(unpacked) if monthly else unpacked)
|
|
480
|
+
return repeat_1d_array_as_columns(iterations, arr)
|
|
481
|
+
|
|
482
|
+
timestamps = [year for year in valid_inventory.keys()]
|
|
483
|
+
|
|
484
|
+
temperature_monthly = _unpack_inventory(_InventoryKey.TEMP_MONTHLY, monthly=True)
|
|
485
|
+
precipitation_monthly = _unpack_inventory(_InventoryKey.PRECIP_MONTHLY, monthly=True)
|
|
486
|
+
pet_monthly = _unpack_inventory(_InventoryKey.PET_MONTHLY, monthly=True)
|
|
487
|
+
irrigated_monthly = _unpack_inventory(_InventoryKey.IRRIGATED_MONTHLY, monthly=True)
|
|
488
|
+
|
|
489
|
+
carbon_input_annual = _unpack_inventory(_InventoryKey.CARBON_INPUT)
|
|
490
|
+
n_content_annual = _unpack_inventory(_InventoryKey.N_CONTENT)
|
|
491
|
+
lignin_content_annual = _unpack_inventory(_InventoryKey.LIGNIN_CONTENT)
|
|
492
|
+
|
|
493
|
+
tillage_category_annual = [group[_InventoryKey.TILLAGE_CATEGORY] for group in valid_inventory.values()]
|
|
494
|
+
|
|
495
|
+
sand_content = next(
|
|
496
|
+
(
|
|
497
|
+
group[_InventoryKey.SAND_CONTENT] for group in valid_inventory.values()
|
|
498
|
+
if _InventoryKey.SAND_CONTENT in group
|
|
499
|
+
),
|
|
500
|
+
sand_content
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# --- SAMPLE PARAMETERS ---
|
|
504
|
+
|
|
505
|
+
rng = random.default_rng(seed)
|
|
506
|
+
|
|
507
|
+
active_decay_factor = _sample_parameter(iterations, _Parameter.ACTIVE_DECAY_FACTOR, seed=rng)
|
|
508
|
+
slow_decay_factor = _sample_parameter(iterations, _Parameter.SLOW_DECAY_FACTOR, seed=rng)
|
|
509
|
+
passive_decay_factor = _sample_parameter(iterations, _Parameter.PASSIVE_DECAY_FACTOR, seed=rng)
|
|
510
|
+
f_1 = _sample_parameter(iterations, _Parameter.F_1, seed=rng)
|
|
511
|
+
f_2_full_tillage = _sample_parameter(iterations, _Parameter.F_2_FULL_TILLAGE, seed=rng)
|
|
512
|
+
f_2_reduced_tillage = _sample_parameter(iterations, _Parameter.F_2_REDUCED_TILLAGE, seed=rng)
|
|
513
|
+
f_2_no_tillage = _sample_parameter(iterations, _Parameter.F_2_NO_TILLAGE, seed=rng)
|
|
514
|
+
f_2_unknown_tillage = _sample_parameter(iterations, _Parameter.F_2_UNKNOWN_TILLAGE, seed=rng)
|
|
515
|
+
f_3 = _sample_parameter(iterations, _Parameter.F_3, seed=rng)
|
|
516
|
+
f_5 = _sample_parameter(iterations, _Parameter.F_5, seed=rng)
|
|
517
|
+
f_6 = _sample_parameter(iterations, _Parameter.F_6, seed=rng)
|
|
518
|
+
f_7 = _sample_parameter(iterations, _Parameter.F_7, seed=rng)
|
|
519
|
+
f_8 = _sample_parameter(iterations, _Parameter.F_8, seed=rng)
|
|
520
|
+
tillage_factor_full_tillage = _sample_parameter(iterations, _Parameter.TILLAGE_FACTOR_FULL_TILLAGE, seed=rng)
|
|
521
|
+
tillage_factor_reduced_tillage = _sample_parameter(iterations, _Parameter.TILLAGE_FACTOR_REDUCED_TILLAGE, seed=rng)
|
|
522
|
+
tillage_factor_no_tillage = _sample_parameter(iterations, _Parameter.TILLAGE_FACTOR_NO_TILLAGE, seed=rng)
|
|
523
|
+
maximum_temperature = _sample_parameter(iterations, _Parameter.MAXIMUM_TEMPERATURE, seed=rng)
|
|
524
|
+
optimum_temperature = _sample_parameter(iterations, _Parameter.OPTIMUM_TEMPERATURE, seed=rng)
|
|
525
|
+
water_factor_slope = _sample_parameter(iterations, _Parameter.WATER_FACTOR_SLOPE, seed=rng)
|
|
526
|
+
|
|
527
|
+
f_4 = _calc_f_4(sand_content, f_5)
|
|
528
|
+
|
|
529
|
+
# --- CALCULATE TILLAGE AND CLIMATE FACTORS ---
|
|
530
|
+
|
|
531
|
+
f_2_annual = _get_f_2_annual(
|
|
532
|
+
tillage_category_annual, f_2_full_tillage, f_2_reduced_tillage, f_2_no_tillage, f_2_unknown_tillage
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
tillage_factor_annual = _get_tillage_factor_annual(
|
|
536
|
+
tillage_category_annual, tillage_factor_full_tillage, tillage_factor_reduced_tillage, tillage_factor_no_tillage
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
temperature_factor_annual = _calc_temperature_factor_annual(
|
|
540
|
+
temperature_monthly, maximum_temperature, optimum_temperature
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
water_factor_annual = _calc_water_factor_annual(
|
|
544
|
+
precipitation_monthly, pet_monthly, irrigated_monthly, water_factor_slope
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# --- AVERAGE RUN-IN YEARS TO STABILISE INITIAL SOC STOCK ---
|
|
548
|
+
|
|
549
|
+
timestamps_ = timestamps[run_in_period - 1:] # Last year of run in becomes first year of results.
|
|
550
|
+
|
|
551
|
+
temperature_factors = avg_run_in_columnwise(temperature_factor_annual, run_in_period)
|
|
552
|
+
water_factors = avg_run_in_columnwise(water_factor_annual, run_in_period)
|
|
553
|
+
carbon_inputs = avg_run_in_columnwise(carbon_input_annual, run_in_period)
|
|
554
|
+
n_contents = avg_run_in_columnwise(n_content_annual, run_in_period)
|
|
555
|
+
lignin_contents = avg_run_in_columnwise(lignin_content_annual, run_in_period)
|
|
556
|
+
f_2s = avg_run_in_columnwise(f_2_annual, run_in_period)
|
|
557
|
+
tillage_factors = avg_run_in_columnwise(tillage_factor_annual, run_in_period)
|
|
558
|
+
|
|
559
|
+
shape = temperature_factors.shape
|
|
560
|
+
|
|
561
|
+
# --- CALCULATE THE ACTIVE ACTIVE POOL STEADY STATES ---
|
|
562
|
+
|
|
563
|
+
alphas = _calc_alpha(
|
|
564
|
+
carbon_inputs, f_2s, f_4, lignin_contents, n_contents, f_1, f_3, f_5, f_6, f_7, f_8
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
active_pool_decay_rates = _calc_active_pool_decay_rate(
|
|
568
|
+
temperature_factors, water_factors, tillage_factors, sand_content, active_decay_factor
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
active_pool_steady_states = _calc_active_pool_steady_state(
|
|
572
|
+
alphas, active_pool_decay_rates
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
# --- CALCULATE THE SLOW POOL STEADY STATES ---
|
|
576
|
+
|
|
577
|
+
slow_pool_decay_rates = _calc_slow_pool_decay_rate(
|
|
578
|
+
temperature_factors, water_factors, tillage_factors, slow_decay_factor
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
slow_pool_steady_states = _calc_slow_pool_steady_state(
|
|
582
|
+
carbon_inputs, f_4, active_pool_steady_states, active_pool_decay_rates, slow_pool_decay_rates,
|
|
583
|
+
lignin_contents, f_3
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
# --- CALCULATE THE PASSIVE POOL STEADY STATES ---
|
|
587
|
+
|
|
588
|
+
passive_pool_decay_rates = _calc_passive_pool_decay_rate(
|
|
589
|
+
temperature_factors, water_factors, passive_decay_factor
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
passive_pool_steady_states = _calc_passive_pool_steady_state(
|
|
593
|
+
active_pool_steady_states, slow_pool_steady_states, active_pool_decay_rates, slow_pool_decay_rates,
|
|
594
|
+
passive_pool_decay_rates, f_5, f_6
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
# --- CALCULATE THE ACTIVE, SLOW AND PASSIVE POOL SOC STOCKS ---
|
|
598
|
+
|
|
599
|
+
active_pool_soc_stocks = empty(shape)
|
|
600
|
+
slow_pool_soc_stocks = empty(shape)
|
|
601
|
+
passive_pool_soc_stocks = empty(shape)
|
|
602
|
+
|
|
603
|
+
active_pool_soc_stocks[0] = active_pool_steady_states[0]
|
|
604
|
+
slow_pool_soc_stocks[0] = slow_pool_steady_states[0]
|
|
605
|
+
passive_pool_soc_stocks[0] = passive_pool_steady_states[0]
|
|
606
|
+
|
|
607
|
+
for index in range(1, len(timestamps_)):
|
|
608
|
+
active_pool_soc_stocks[index] = _calc_sub_pool_soc_stock(
|
|
609
|
+
active_pool_steady_states[index],
|
|
610
|
+
active_pool_soc_stocks[index - 1],
|
|
611
|
+
active_pool_decay_rates[index]
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
slow_pool_soc_stocks[index] = _calc_sub_pool_soc_stock(
|
|
615
|
+
slow_pool_steady_states[index],
|
|
616
|
+
slow_pool_soc_stocks[index - 1],
|
|
617
|
+
slow_pool_decay_rates[index]
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
passive_pool_soc_stocks[index] = _calc_sub_pool_soc_stock(
|
|
621
|
+
passive_pool_steady_states[index],
|
|
622
|
+
passive_pool_soc_stocks[index - 1],
|
|
623
|
+
passive_pool_decay_rates[index]
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
# --- ADD THE POOLS AND RETURN THE RESULT ---
|
|
627
|
+
|
|
628
|
+
soc_stocks = active_pool_soc_stocks + slow_pool_soc_stocks + passive_pool_soc_stocks
|
|
629
|
+
|
|
630
|
+
descriptive_stats = calc_descriptive_stats(
|
|
631
|
+
soc_stocks,
|
|
632
|
+
STATS_DEFINITION,
|
|
633
|
+
axis=1, # Calculate stats rowwise.
|
|
634
|
+
decimals=6 # Round values to the nearest milligram.
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
return [_measurement(timestamps_, descriptive_stats)]
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def _calc_temperature_factor_annual(
|
|
641
|
+
temperature_monthly: NDArray,
|
|
642
|
+
maximum_temperature: NDArray = array(45),
|
|
643
|
+
optimum_temperature: NDArray = array(33.69)
|
|
644
|
+
) -> NDArray:
|
|
645
|
+
"""
|
|
646
|
+
Equation 5.0E part 2, Temperature effect on decomposition for mineral soils using the steady-state method, Page
|
|
647
|
+
5.22, Tier 2 Steady State Method for Mineral Soils, Chapter 5 Cropland, 2019 Refinement to the 2006 IPCC Guidelines
|
|
648
|
+
for National Greenhouse Gas Inventories.
|
|
649
|
+
|
|
650
|
+
Parameters
|
|
651
|
+
----------
|
|
652
|
+
monthly_temperature : NDArray
|
|
653
|
+
Monthly average air temprature, degrees C.
|
|
654
|
+
maximum_temperature : NDArray
|
|
655
|
+
Maximum monthly air temperature for decomposition, degrees C. Default value: `[45]`.
|
|
656
|
+
optimum_temperature : NDArray
|
|
657
|
+
Optimum air temperature for decomposition, degrees C. Default value: `[33.69]`.
|
|
658
|
+
|
|
659
|
+
Returns
|
|
660
|
+
-------
|
|
661
|
+
NDArray
|
|
662
|
+
Annual average air temperature effect on decomposition, dimensionless.
|
|
663
|
+
"""
|
|
664
|
+
mask = temperature_monthly <= maximum_temperature
|
|
665
|
+
prelim: NDArray = (maximum_temperature - temperature_monthly) / (maximum_temperature - optimum_temperature)
|
|
666
|
+
|
|
667
|
+
temperature_factor_monthly = empty(temperature_monthly.shape)
|
|
668
|
+
temperature_factor_monthly[mask] = pow(prelim[mask], 0.2) * exp((0.2 / 2.63) * (1 - pow(prelim[mask], 2.63)))
|
|
669
|
+
temperature_factor_monthly[~mask] = 0
|
|
670
|
+
|
|
671
|
+
return grouped_avg(temperature_factor_monthly, n=12)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def _calc_water_factor_annual(
|
|
675
|
+
precipitation_monthly: NDArray,
|
|
676
|
+
pet_monthly: NDArray,
|
|
677
|
+
irrigated_monthly: NDArray = array(False),
|
|
678
|
+
water_factor_slope: NDArray = array(1.331),
|
|
679
|
+
) -> NDArray:
|
|
680
|
+
"""
|
|
681
|
+
Equation 5.0F, part 1. Calculate the average annual water effect on decomposition in mineral soils using the
|
|
682
|
+
Steady-State Method multiplied by a coefficient of `1.5`.
|
|
683
|
+
|
|
684
|
+
Parameters
|
|
685
|
+
----------
|
|
686
|
+
precipitation_monthly : NDArray
|
|
687
|
+
Monthly sum total precipitation, mm.
|
|
688
|
+
pet_monthly : NDArray
|
|
689
|
+
Monthly sum total potential evapotranspiration, mm.
|
|
690
|
+
is_irrigated_monthly : NDArray
|
|
691
|
+
Monthly true/false value that describe whether or not irrigation was used.
|
|
692
|
+
water_factor_slope : NDArray
|
|
693
|
+
The slope for mappet term to estimate water factor, dimensionless. Default value: `[1.331]`.
|
|
694
|
+
|
|
695
|
+
Returns
|
|
696
|
+
-------
|
|
697
|
+
NDArray
|
|
698
|
+
Annual water effect on decomposition, dimensionless.
|
|
699
|
+
"""
|
|
700
|
+
MAX_MAPPET = 1.25
|
|
701
|
+
WATER_FACTOR_IRRIGATED = 0.775
|
|
702
|
+
|
|
703
|
+
shape = pet_monthly.shape
|
|
704
|
+
mask = pet_monthly != 0
|
|
705
|
+
|
|
706
|
+
mappet_monthly = empty(shape)
|
|
707
|
+
mappet_monthly[mask] = minimum(precipitation_monthly[mask] / pet_monthly[mask], MAX_MAPPET)
|
|
708
|
+
mappet_monthly[~mask] = MAX_MAPPET
|
|
709
|
+
|
|
710
|
+
water_factor_monthly = where(
|
|
711
|
+
irrigated_monthly,
|
|
712
|
+
WATER_FACTOR_IRRIGATED,
|
|
713
|
+
0.2129 + (water_factor_slope * mappet_monthly) - (0.2413 * mappet_monthly**2)
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
return 1.5 * grouped_avg(water_factor_monthly, n=12)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def _calc_f_4(sand_content: NDArray = array(0.33), f_5: NDArray = array(0.0855)) -> NDArray:
|
|
720
|
+
"""
|
|
721
|
+
Equation 5.0C, part 4. Calculate the value of the stabilisation efficiencies for active pool decay products
|
|
722
|
+
entering the slow pool based on the sand content of the soil.
|
|
723
|
+
|
|
724
|
+
Parameters
|
|
725
|
+
----------
|
|
726
|
+
sand_content : NDArray
|
|
727
|
+
The sand content of the soil, decimal proportion. Default value: `[0.33]`.
|
|
728
|
+
f_5 : NDArray
|
|
729
|
+
The stabilisation efficiencies for active pool decay products entering the passive pool, decimal_proportion.
|
|
730
|
+
Default value: `[0.0855]`.
|
|
731
|
+
|
|
732
|
+
Returns
|
|
733
|
+
-------
|
|
734
|
+
NDArray
|
|
735
|
+
The stabilisation efficiencies for active pool decay products entering the slow pool, decimal proportion.
|
|
736
|
+
"""
|
|
737
|
+
return 1 - f_5 - (0.17 + 0.68 * sand_content)
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
def _get_f_2_annual(
|
|
741
|
+
tillage_category_annual: list[IpccManagementCategory],
|
|
742
|
+
f_2_full_tillage: NDArray = array(0.455),
|
|
743
|
+
f_2_reduced_tillage: NDArray = array(0.477),
|
|
744
|
+
f_2_no_tillage: NDArray = array(0.5),
|
|
745
|
+
f_2_unknown_tillage: NDArray = array(0.368),
|
|
746
|
+
) -> NDArray:
|
|
747
|
+
"""
|
|
748
|
+
Get the value of `f_2` (the stabilisation efficiencies for structural decay products entering the active pool)
|
|
749
|
+
based on the tillage `IpccManagementCategory`.
|
|
750
|
+
|
|
751
|
+
If tillage regime is unknown, `IpccManagementCategory.OTHER` should be assumed.
|
|
752
|
+
|
|
753
|
+
Parameters
|
|
754
|
+
----------
|
|
755
|
+
tillage_category_annual : list[IpccManagementCategory]
|
|
756
|
+
The tillage category for each year in the inventory.
|
|
757
|
+
f_2_full_tillage : NDArray
|
|
758
|
+
The stabilisation efficiencies for structural decay products entering the active pool under full tillage,
|
|
759
|
+
decimal proportion. Default value: `[0.455]`.
|
|
760
|
+
f_2_reduced_tillage : NDArray
|
|
761
|
+
The stabilisation efficiencies for structural decay products entering the active pool under reduced tillage,
|
|
762
|
+
decimal proportion. Default value: `[0.477]`.
|
|
763
|
+
f_2_no_tillage : NDArray
|
|
764
|
+
The stabilisation efficiencies for structural decay products entering the active pool under no tillage,
|
|
765
|
+
decimal proportion. Default value: `[0.5]`.
|
|
766
|
+
f_2_unknown_tillage : NDArray
|
|
767
|
+
The stabilisation efficiencies for structural decay products entering the active pool if tillage is not known,
|
|
768
|
+
decimal proportion. Default value: `[0.368]`.
|
|
769
|
+
|
|
770
|
+
Returns
|
|
771
|
+
-------
|
|
772
|
+
NDArray
|
|
773
|
+
The stabilisation efficiencies for structural decay products entering the active pool, decimal proportion.
|
|
774
|
+
"""
|
|
775
|
+
ipcc_tillage_management_category_to_f_2s = {
|
|
776
|
+
IpccManagementCategory.FULL_TILLAGE: f_2_full_tillage,
|
|
777
|
+
IpccManagementCategory.REDUCED_TILLAGE: f_2_reduced_tillage,
|
|
778
|
+
IpccManagementCategory.NO_TILLAGE: f_2_no_tillage,
|
|
779
|
+
IpccManagementCategory.OTHER: f_2_unknown_tillage
|
|
780
|
+
}
|
|
781
|
+
default = f_2_unknown_tillage
|
|
782
|
+
return vstack([ipcc_tillage_management_category_to_f_2s.get(till, default) for till in tillage_category_annual])
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _get_tillage_factor_annual(
|
|
786
|
+
tillage_category_annual: list[IpccManagementCategory],
|
|
787
|
+
tillage_factor_full_tillage: NDArray = array(3.036),
|
|
788
|
+
tillage_factor_reduced_tillage: NDArray = array(2.075),
|
|
789
|
+
tillage_factor_no_tillage: NDArray = array(1)
|
|
790
|
+
) -> NDArray:
|
|
791
|
+
"""
|
|
792
|
+
Calculate the tillage disturbance modifier on decay rate for active and slow sub-pools based on the tillage
|
|
793
|
+
`IpccManagementCategory`.
|
|
794
|
+
|
|
795
|
+
If tillage regime is unknown, `FULL_TILLAGE` should be assumed.
|
|
796
|
+
|
|
797
|
+
Parameters
|
|
798
|
+
----------
|
|
799
|
+
tillage_category_annual : list[IpccManagementCategory]
|
|
800
|
+
The tillage category for each year in the inventory.
|
|
801
|
+
tillage_factor_full_tillage : NDArray
|
|
802
|
+
The tillage disturbance modifier for decay rates under full tillage, dimensionless. Default value: `[3.036]`.
|
|
803
|
+
tillage_factor_reduced_tillage : NDArray
|
|
804
|
+
Tillage disturbance modifier for decay rates under reduced tillage, dimensionless. Default value: `[2.075]`.
|
|
805
|
+
tillage_factor_no_tillage : NDArray
|
|
806
|
+
Tillage disturbance modifier for decay rates under no tillage, dimensionless. Default value: `[1]`.
|
|
807
|
+
|
|
808
|
+
Returns
|
|
809
|
+
-------
|
|
810
|
+
NDArray
|
|
811
|
+
The tillage disturbance modifier on decay rate for active and slow sub-pools, dimensionless.
|
|
812
|
+
"""
|
|
813
|
+
ipcc_tillage_management_category_to_tillage_factors = {
|
|
814
|
+
IpccManagementCategory.FULL_TILLAGE: tillage_factor_full_tillage,
|
|
815
|
+
IpccManagementCategory.REDUCED_TILLAGE: tillage_factor_reduced_tillage,
|
|
816
|
+
IpccManagementCategory.NO_TILLAGE: tillage_factor_no_tillage,
|
|
817
|
+
}
|
|
818
|
+
default = tillage_factor_full_tillage
|
|
819
|
+
return vstack([
|
|
820
|
+
ipcc_tillage_management_category_to_tillage_factors.get(till, default)
|
|
821
|
+
for till in tillage_category_annual
|
|
822
|
+
])
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def _calc_alpha(
|
|
826
|
+
carbon_input: NDArray,
|
|
827
|
+
f_2: NDArray,
|
|
828
|
+
f_4: NDArray,
|
|
829
|
+
lignin_content: NDArray = array(0.073),
|
|
830
|
+
nitrogen_content: NDArray = array(0.0083),
|
|
831
|
+
f_1: NDArray = array(0.378),
|
|
832
|
+
f_3: NDArray = array(0.455),
|
|
833
|
+
f_5: NDArray = array(0.0855),
|
|
834
|
+
f_6: NDArray = array(0.0504),
|
|
835
|
+
f_7: NDArray = array(0.42),
|
|
836
|
+
f_8: NDArray = array(0.45)
|
|
837
|
+
) -> NDArray:
|
|
838
|
+
"""
|
|
839
|
+
Equation 5.0G, part 1. Calculate the C input to the active soil carbon sub-pool, kg C ha-1.
|
|
840
|
+
|
|
841
|
+
See table 5.5b for default values for lignin content and nitrogen content.
|
|
842
|
+
|
|
843
|
+
Parameters
|
|
844
|
+
----------
|
|
845
|
+
carbon_input : NDArray
|
|
846
|
+
Total carbon input to the soil, kg C ha-1.
|
|
847
|
+
f_2 : NDArray
|
|
848
|
+
The stabilisation efficiencies for structural decay products entering the active pool, decimal proportion.
|
|
849
|
+
f_4 : NDArray
|
|
850
|
+
The stabilisation efficiencies for active pool decay products entering the slow pool, decimal proportion.
|
|
851
|
+
lignin_content : NDArray
|
|
852
|
+
The average lignin content of carbon input sources, decimal proportion. Default value: `[0.073]`.
|
|
853
|
+
nitrogen_content : NDArray
|
|
854
|
+
The average nitrogen content of carbon input sources, decimal proportion. Default value: `[0.0083]`.
|
|
855
|
+
f_1 : NDArray
|
|
856
|
+
The stabilisation efficiencies for metabolic decay products entering the active pool, decimal proportion.
|
|
857
|
+
Default value: `[0.378]`.
|
|
858
|
+
f_3 : NDArray
|
|
859
|
+
The stabilisation efficiencies for structural decay products entering the slow pool, decimal proportion.
|
|
860
|
+
Default value: `[0.455]`.
|
|
861
|
+
f_5 : NDArray
|
|
862
|
+
The stabilisation efficiencies for active pool decay products entering the passive pool, decimal proportion.
|
|
863
|
+
Default value: `[0.0855]`.
|
|
864
|
+
f_6 : NDArray
|
|
865
|
+
The stabilisation efficiencies for slow pool decay products entering the passive pool, decimal proportion.
|
|
866
|
+
Default value: `[0.0504]`.
|
|
867
|
+
f_7 : NDArray
|
|
868
|
+
The stabilisation efficiencies for slow pool decay products entering the active pool, decimal proportion.
|
|
869
|
+
Default value: `[0.42]`.
|
|
870
|
+
f_8 : NDArray
|
|
871
|
+
The stabilisation efficiencies for passive pool decay products entering the active pool, decimal proportion.
|
|
872
|
+
Default value: `[0.45]`.
|
|
873
|
+
|
|
874
|
+
Returns
|
|
875
|
+
-------
|
|
876
|
+
NDArray
|
|
877
|
+
The C input to the active soil carbon sub-pool, kg C ha-1.
|
|
878
|
+
"""
|
|
879
|
+
beta = _calc_beta(
|
|
880
|
+
carbon_input, lignin_content=lignin_content, nitrogen_content=nitrogen_content
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
x = beta * f_1
|
|
884
|
+
y = (carbon_input * (1 - lignin_content) - beta) * f_2
|
|
885
|
+
z = (carbon_input * lignin_content) * f_3 * (f_7 + (f_6 * f_8))
|
|
886
|
+
d = 1 - (f_4 * f_7) - (f_5 * f_8) - (f_4 * f_6 * f_8)
|
|
887
|
+
return (x + y + z) / d
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def _calc_beta(
|
|
891
|
+
carbon_input: NDArray,
|
|
892
|
+
lignin_content: NDArray = array(0.073),
|
|
893
|
+
nitrogen_content: NDArray = array(0.0083),
|
|
894
|
+
) -> NDArray:
|
|
895
|
+
"""
|
|
896
|
+
Equation 5.0G, part 2. Calculate the C input to the metabolic dead organic matter C component, kg C ha-1.
|
|
897
|
+
|
|
898
|
+
See table 5.5b for default values for lignin content and nitrogen content.
|
|
899
|
+
|
|
900
|
+
Parameters
|
|
901
|
+
----------
|
|
902
|
+
carbon_input : NDArray
|
|
903
|
+
Total carbon input to the soil, kg C ha-1.
|
|
904
|
+
lignin_content : NDArray
|
|
905
|
+
The average lignin content of carbon input sources, decimal proportion. Default value: `[0.073]`.
|
|
906
|
+
nitrogen_content : NDArray
|
|
907
|
+
The average nitrogen content of carbon sources, decimal proportion. Default value: `[0.0083]`.
|
|
908
|
+
|
|
909
|
+
Returns
|
|
910
|
+
-------
|
|
911
|
+
NDArray
|
|
912
|
+
The C input to the metabolic dead organic matter C component, kg C ha-1.
|
|
913
|
+
"""
|
|
914
|
+
return carbon_input * (0.85 - 0.018 * (lignin_content / nitrogen_content))
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def _calc_active_pool_decay_rate(
|
|
918
|
+
temperature_factor_annual: NDArray,
|
|
919
|
+
water_factor_annual: NDArray,
|
|
920
|
+
tillage_factor: NDArray,
|
|
921
|
+
sand_content: NDArray = array(0.33),
|
|
922
|
+
active_decay_factor: NDArray = array(7.4),
|
|
923
|
+
) -> NDArray:
|
|
924
|
+
"""
|
|
925
|
+
Equation 5.0B, part 3. Calculate the decay rate for the active SOC sub-pool given conditions in an inventory year.
|
|
926
|
+
|
|
927
|
+
Parameters
|
|
928
|
+
----------
|
|
929
|
+
temperature_factor_annual : NDArray
|
|
930
|
+
Average annual temperature factor, dimensionless. All elements between `0` and `1`.
|
|
931
|
+
water_factor_annual : NDArray
|
|
932
|
+
Average annual water factor, dimensionless. All elements between `0.31935` and `2.25`.
|
|
933
|
+
tillage_factor : NDArray
|
|
934
|
+
The tillage disturbance modifier on decay rate for active and slow sub-pools, dimensionless.
|
|
935
|
+
sand_content : NDArray
|
|
936
|
+
The sand content of the soil, decimal proportion. Default value: `[0.33]`.
|
|
937
|
+
active_decay_factor : NDArray
|
|
938
|
+
decay rate constant under optimal conditions for decomposition of the active SOC subpool, year-1. Default value:
|
|
939
|
+
`[7.4]`.
|
|
940
|
+
|
|
941
|
+
Returns
|
|
942
|
+
-------
|
|
943
|
+
NDArray
|
|
944
|
+
The decay rate for active SOC sub-pool, year-1.
|
|
945
|
+
"""
|
|
946
|
+
sand_factor = 0.25 + (0.75 * sand_content)
|
|
947
|
+
return (
|
|
948
|
+
temperature_factor_annual
|
|
949
|
+
* water_factor_annual
|
|
950
|
+
* tillage_factor
|
|
951
|
+
* sand_factor
|
|
952
|
+
* active_decay_factor
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def _calc_active_pool_steady_state(
|
|
957
|
+
alpha: NDArray, active_pool_decay_rate: NDArray
|
|
958
|
+
) -> NDArray:
|
|
959
|
+
"""
|
|
960
|
+
Equation 5.0B part 2. Calculate the steady state active sub-pool SOC stock given conditions in an inventory year.
|
|
961
|
+
|
|
962
|
+
Parameters
|
|
963
|
+
----------
|
|
964
|
+
alpha : NDArray
|
|
965
|
+
The C input to the active soil carbon sub-pool, kg C ha-1.
|
|
966
|
+
active_pool_decay_rate : NDArray
|
|
967
|
+
Decay rate for active SOC sub-pool, year-1.
|
|
968
|
+
|
|
969
|
+
Returns
|
|
970
|
+
-------
|
|
971
|
+
NDArray
|
|
972
|
+
The steady state active sub-pool SOC stock given conditions in year y, kg C ha-1
|
|
973
|
+
"""
|
|
974
|
+
return alpha / active_pool_decay_rate
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
def _calc_slow_pool_decay_rate(
|
|
978
|
+
temperature_factor_annual: NDArray,
|
|
979
|
+
water_factor_annual: NDArray,
|
|
980
|
+
tillage_factor: NDArray,
|
|
981
|
+
slow_decay_factor: NDArray = array(0.209),
|
|
982
|
+
) -> NDArray:
|
|
983
|
+
"""
|
|
984
|
+
Equation 5.0C, part 3. Calculate the decay rate for the slow SOC sub-pool given conditions in an inventory year.
|
|
985
|
+
|
|
986
|
+
Parameters
|
|
987
|
+
----------
|
|
988
|
+
temperature_factor_annual : NDArray
|
|
989
|
+
Average annual temperature factor, dimensionless. All elements between `0` and `1`.
|
|
990
|
+
water_factor_annual : NDArray
|
|
991
|
+
Average annual water factor, dimensionless. All elements between `0.31935` and `2.25`.
|
|
992
|
+
tillage_factor : NDArray
|
|
993
|
+
The tillage disturbance modifier on decay rate for active and slow sub-pools, dimensionless.
|
|
994
|
+
slow_decay_factor : NDArray
|
|
995
|
+
The decay rate constant under optimal conditions for decomposition of the slow SOC subpool, year-1.
|
|
996
|
+
Default value: `0.209`.
|
|
997
|
+
|
|
998
|
+
Returns
|
|
999
|
+
-------
|
|
1000
|
+
NDArray
|
|
1001
|
+
The decay rate for slow SOC sub-pool, year-1.
|
|
1002
|
+
"""
|
|
1003
|
+
return (
|
|
1004
|
+
temperature_factor_annual
|
|
1005
|
+
* water_factor_annual
|
|
1006
|
+
* tillage_factor
|
|
1007
|
+
* slow_decay_factor
|
|
1008
|
+
)
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
def _calc_slow_pool_steady_state(
|
|
1012
|
+
carbon_input: NDArray,
|
|
1013
|
+
f_4: NDArray,
|
|
1014
|
+
active_pool_steady_state: NDArray,
|
|
1015
|
+
active_pool_decay_rate: NDArray,
|
|
1016
|
+
slow_pool_decay_rate: NDArray,
|
|
1017
|
+
lignin_content: NDArray = array(0.073),
|
|
1018
|
+
f_3: NDArray = array(0.455),
|
|
1019
|
+
) -> NDArray:
|
|
1020
|
+
"""
|
|
1021
|
+
Equation 5.0C, part 2. Calculate the steady state slow sub-pool SOC stock given conditions in an inventory year.
|
|
1022
|
+
|
|
1023
|
+
Parameters
|
|
1024
|
+
----------
|
|
1025
|
+
carbon_input : NDArray
|
|
1026
|
+
Total carbon input to the soil, kg C ha-1.
|
|
1027
|
+
f_4 : NDArray
|
|
1028
|
+
The stabilisation efficiencies for active pool decay products entering the slow pool, decimal proportion.
|
|
1029
|
+
active_pool_steady_state : NDArray
|
|
1030
|
+
The steady state active sub-pool SOC stock given conditions in year y, kg C ha-1
|
|
1031
|
+
active_pool_decay_rate : NDArray
|
|
1032
|
+
Decay rate for active SOC sub-pool, year-1.
|
|
1033
|
+
slow_pool_decay_rate : NDArray
|
|
1034
|
+
Decay rate for slow SOC sub-pool, year-1.
|
|
1035
|
+
lignin_content : NDArray
|
|
1036
|
+
The average lignin content of carbon input sources, decimal proportion. Default value: `[0.073]`.
|
|
1037
|
+
f_3 : NDArray
|
|
1038
|
+
The stabilisation efficiencies for structural decay products entering the slow pool, decimal proportion.
|
|
1039
|
+
Default value: `[0.455]`.
|
|
1040
|
+
|
|
1041
|
+
Returns
|
|
1042
|
+
-------
|
|
1043
|
+
NDArray
|
|
1044
|
+
The steady state slow sub-pool SOC stock given conditions in year y, kg C ha-1.
|
|
1045
|
+
"""
|
|
1046
|
+
x = carbon_input * lignin_content * f_3
|
|
1047
|
+
y = active_pool_steady_state * active_pool_decay_rate * f_4
|
|
1048
|
+
return (x + y) / slow_pool_decay_rate
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
def _calc_passive_pool_decay_rate(
|
|
1052
|
+
temperature_factor_annual: NDArray,
|
|
1053
|
+
water_factor_annual: NDArray,
|
|
1054
|
+
passive_decay_factor: NDArray = array(0.00689),
|
|
1055
|
+
) -> NDArray:
|
|
1056
|
+
"""
|
|
1057
|
+
Equation 5.0D, part 3. Calculate the decay rate for the passive SOC sub-pool given conditions in an inventory year.
|
|
1058
|
+
|
|
1059
|
+
Parameters
|
|
1060
|
+
----------
|
|
1061
|
+
temperature_factor_annual : NDArray
|
|
1062
|
+
Average annual temperature factor, dimensionless. All elements between `0` and `1`.
|
|
1063
|
+
water_factor_annual : NDArray
|
|
1064
|
+
Average annual water factor, dimensionless. All elements between `0.31935` and `2.25`.
|
|
1065
|
+
passive_decay_factor : NDArray
|
|
1066
|
+
decay rate constant under optimal conditions for decomposition of the passive SOC subpool, year-1.
|
|
1067
|
+
Default value: `[0.00689]`.
|
|
1068
|
+
|
|
1069
|
+
Returns
|
|
1070
|
+
-------
|
|
1071
|
+
NDArray
|
|
1072
|
+
The decay rate for passive SOC sub-pool, year-1.
|
|
1073
|
+
"""
|
|
1074
|
+
return temperature_factor_annual * water_factor_annual * passive_decay_factor
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def _calc_passive_pool_steady_state(
|
|
1078
|
+
active_pool_steady_state: NDArray,
|
|
1079
|
+
slow_pool_steady_state: NDArray,
|
|
1080
|
+
active_pool_decay_rate: NDArray,
|
|
1081
|
+
slow_pool_decay_rate: NDArray,
|
|
1082
|
+
passive_pool_decay_rate: NDArray,
|
|
1083
|
+
f_5: NDArray = array(0.0855),
|
|
1084
|
+
f_6: NDArray = array(0.0504),
|
|
1085
|
+
) -> NDArray:
|
|
1086
|
+
"""
|
|
1087
|
+
Equation 5.0D, part 2. Calculate the steady state passive sub-pool SOC stock given conditions in an inventory year.
|
|
1088
|
+
|
|
1089
|
+
Parameters
|
|
1090
|
+
----------
|
|
1091
|
+
active_pool_steady_state : NDArray
|
|
1092
|
+
The steady state active sub-pool SOC stock given conditions in year y, kg C ha-1.
|
|
1093
|
+
slow_pool_steady_state : NDArray
|
|
1094
|
+
The steady state slow sub-pool SOC stock given conditions in year y, kg C ha-1.
|
|
1095
|
+
active_pool_decay_rate : NDArray
|
|
1096
|
+
Decay rate for active SOC sub-pool, year-1.
|
|
1097
|
+
slow_pool_decay_rate : NDArray
|
|
1098
|
+
Decay rate for slow SOC sub-pool, year-1.
|
|
1099
|
+
passive_pool_decay_rate : NDArray
|
|
1100
|
+
Decay rate for passive SOC sub-pool, year-1.
|
|
1101
|
+
f_5 : NDArray
|
|
1102
|
+
The stabilisation efficiencies for active pool decay products entering the passive pool, decimal proportion.
|
|
1103
|
+
Default value: `[0.0855]`.
|
|
1104
|
+
f_6 : NDArray
|
|
1105
|
+
The stabilisation efficiencies for slow pool decay products entering the passive pool, decimal proportion.
|
|
1106
|
+
Default value: `[0.0504]`.
|
|
1107
|
+
|
|
1108
|
+
Returns
|
|
1109
|
+
-------
|
|
1110
|
+
NDArray
|
|
1111
|
+
The steady state passive sub-pool SOC stock given conditions in year y, kg C ha-1.
|
|
1112
|
+
"""
|
|
1113
|
+
x = active_pool_steady_state * active_pool_decay_rate * f_5
|
|
1114
|
+
y = slow_pool_steady_state * slow_pool_decay_rate * f_6
|
|
1115
|
+
return (x + y) / passive_pool_decay_rate
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
def _calc_sub_pool_soc_stock(
|
|
1119
|
+
sub_pool_steady_state: NDArray,
|
|
1120
|
+
previous_sub_pool_soc_stock: NDArray,
|
|
1121
|
+
sub_pool_decay_rate: NDArray,
|
|
1122
|
+
timestep: int = 1,
|
|
1123
|
+
) -> NDArray:
|
|
1124
|
+
"""
|
|
1125
|
+
Generalised from equations 5.0B, 5.0C and 5.0D, part 1. Calculate the sub-pool SOC stock in year y, kg C ha-1.
|
|
1126
|
+
|
|
1127
|
+
If `sub_pool_decay_rate > 1` then set its value to `1` for this calculation.
|
|
1128
|
+
|
|
1129
|
+
Parameters
|
|
1130
|
+
----------
|
|
1131
|
+
sub_pool_steady_state : NDArray
|
|
1132
|
+
The steady state sub-pool SOC stock given conditions in year y, kg C ha-1.
|
|
1133
|
+
previous_sub_pool_soc_stock : NDArray
|
|
1134
|
+
The sub-pool SOC stock in year y-timestep (by default one year ago), kg C ha-1.
|
|
1135
|
+
sub_pool_decay_rate : NDArray
|
|
1136
|
+
Decay rate for active SOC sub-pool, year-1.
|
|
1137
|
+
timestep : int
|
|
1138
|
+
The number of years between current and previous inventory year. Default value = `1`.
|
|
1139
|
+
|
|
1140
|
+
Returns
|
|
1141
|
+
-------
|
|
1142
|
+
NDArray
|
|
1143
|
+
The sub-pool SOC stock in year y, kg C ha-1.
|
|
1144
|
+
"""
|
|
1145
|
+
sub_pool_decay_rate = minimum(1, sub_pool_decay_rate)
|
|
1146
|
+
return (
|
|
1147
|
+
previous_sub_pool_soc_stock
|
|
1148
|
+
+ (sub_pool_steady_state - previous_sub_pool_soc_stock)
|
|
1149
|
+
* timestep
|
|
1150
|
+
* sub_pool_decay_rate
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
# --- COMPILE TIER 2 INVENTORY ---
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
def _compile_inventory(
|
|
1158
|
+
cycles: list[dict], measurement_nodes: list[dict]
|
|
1159
|
+
) -> tuple[dict, dict]:
|
|
1160
|
+
"""
|
|
1161
|
+
Builds an annual inventory of data and a dictionary of keyword arguments for the tier 2 model.
|
|
1162
|
+
|
|
1163
|
+
TODO: implement long-term average climate data and annual climate data as back ups for monthly data
|
|
1164
|
+
TODO: implement randomisation for `irrigationMonthly` if `startDate` and `endDate` are not provided
|
|
1165
|
+
"""
|
|
1166
|
+
grouped_cycles = group_nodes_by_year(cycles)
|
|
1167
|
+
grouped_measurements = group_nodes_by_year(measurement_nodes, mode=GroupNodesByYearMode.DATES)
|
|
1168
|
+
|
|
1169
|
+
grouped_climate_data = _get_grouped_climate_measurements(grouped_measurements)
|
|
1170
|
+
grouped_irrigated_monthly = _get_grouped_irrigated_monthly(grouped_cycles)
|
|
1171
|
+
grouped_sand_content_measurements = _get_grouped_sand_content_measurements(grouped_measurements)
|
|
1172
|
+
grouped_carbon_input_data = _get_grouped_carbon_input_data(grouped_cycles)
|
|
1173
|
+
grouped_tillage_categories = _get_grouped_tillage_categories(grouped_cycles)
|
|
1174
|
+
grouped_is_paddy_rice = _get_grouped_is_paddy_rice(grouped_cycles)
|
|
1175
|
+
|
|
1176
|
+
grouped_data = merge(
|
|
1177
|
+
grouped_climate_data,
|
|
1178
|
+
grouped_irrigated_monthly,
|
|
1179
|
+
grouped_sand_content_measurements,
|
|
1180
|
+
grouped_carbon_input_data,
|
|
1181
|
+
grouped_tillage_categories,
|
|
1182
|
+
grouped_is_paddy_rice
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1185
|
+
grouped_should_run = {
|
|
1186
|
+
year: {_InventoryKey.SHOULD_RUN: _should_run_inventory(group)}
|
|
1187
|
+
for year, group in grouped_data.items()
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
inventory = merge(grouped_data, grouped_should_run)
|
|
1191
|
+
|
|
1192
|
+
# Get a back-up value for sand content if no dated ones are available.
|
|
1193
|
+
sand_content = get_node_value(find_term_match(
|
|
1194
|
+
[m for m in measurement_nodes if m.get("depthUpper") == DEPTH_UPPER and m.get("depthLower") == DEPTH_LOWER],
|
|
1195
|
+
_SAND_CONTENT_TERM_ID,
|
|
1196
|
+
{}
|
|
1197
|
+
)) / 100
|
|
1198
|
+
|
|
1199
|
+
kwargs = {
|
|
1200
|
+
"sand_content": sand_content
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
return inventory, kwargs
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
def _check_12_months(inner_dict: dict, keys: set[Any]):
|
|
1207
|
+
"""
|
|
1208
|
+
Checks whether an inner dict has 12 months of data for each of the required inner keys.
|
|
1209
|
+
|
|
1210
|
+
Parameters
|
|
1211
|
+
----------
|
|
1212
|
+
inner_dict : dict
|
|
1213
|
+
A dictionary representing one year in a timeseries for the Tier 2 model.
|
|
1214
|
+
keys : set[Any]
|
|
1215
|
+
The required inner keys.
|
|
1216
|
+
|
|
1217
|
+
Returns
|
|
1218
|
+
-------
|
|
1219
|
+
bool
|
|
1220
|
+
Whether or not the inner dict satisfies the conditions.
|
|
1221
|
+
"""
|
|
1222
|
+
return all(
|
|
1223
|
+
len(inner_dict.get(key, [])) == 12 for key in keys
|
|
1224
|
+
)
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
def _get_grouped_climate_measurements(grouped_measurements: dict) -> dict:
|
|
1228
|
+
return {
|
|
1229
|
+
year: {
|
|
1230
|
+
_InventoryKey.TEMP_MONTHLY: non_empty_list(
|
|
1231
|
+
find_term_match(measurements, _TEMPERATURE_MONTHLY_TERM_ID, {}).get("value", [])
|
|
1232
|
+
),
|
|
1233
|
+
_InventoryKey.PRECIP_MONTHLY: non_empty_list(
|
|
1234
|
+
find_term_match(measurements, _PRECIPITATION_MONTHLY_TERM_ID, {}).get("value", [])
|
|
1235
|
+
),
|
|
1236
|
+
_InventoryKey.PET_MONTHLY: non_empty_list(
|
|
1237
|
+
find_term_match(measurements, _PET_MONTHLY_TERM_ID, {}).get("value", [])
|
|
1238
|
+
)
|
|
1239
|
+
} for year, measurements in grouped_measurements.items()
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
def _get_grouped_irrigated_monthly(grouped_cycles: dict) -> dict:
|
|
1244
|
+
return {
|
|
1245
|
+
year: {
|
|
1246
|
+
_InventoryKey.IRRIGATED_MONTHLY: _get_irrigated_monthly(year, cycles)
|
|
1247
|
+
} for year, cycles in grouped_cycles.items()
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
def _get_irrigated_monthly(year: int, cycles: list[dict]) -> list[bool]:
|
|
1252
|
+
# Get practice nodes and add "startDate" and "endDate" from cycle if missing.
|
|
1253
|
+
irrigation_nodes = non_empty_list(flatten([
|
|
1254
|
+
[
|
|
1255
|
+
{
|
|
1256
|
+
"startDate": cycle.get("startDate"),
|
|
1257
|
+
"endDate": cycle.get("endDate"),
|
|
1258
|
+
**node
|
|
1259
|
+
} for node in cycle.get("practices", [])
|
|
1260
|
+
] for cycle in cycles
|
|
1261
|
+
]))
|
|
1262
|
+
|
|
1263
|
+
grouped_nodes = group_nodes_by_year_and_month(irrigation_nodes)
|
|
1264
|
+
|
|
1265
|
+
# For each month (1 - 12) check if irrigation is present.
|
|
1266
|
+
return [check_irrigation(grouped_nodes.get(year, {}).get(month, [])) for month in range(1, 13)]
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
def _get_grouped_sand_content_measurements(grouped_measurements: dict) -> dict:
|
|
1270
|
+
grouped_sand_content_measurements = {
|
|
1271
|
+
year: find_term_match(
|
|
1272
|
+
[m for m in measurements if m.get("depthUpper") == DEPTH_UPPER and m.get("depthLower") == DEPTH_LOWER],
|
|
1273
|
+
_SAND_CONTENT_TERM_ID,
|
|
1274
|
+
{}
|
|
1275
|
+
) for year, measurements in grouped_measurements.items()
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
return {
|
|
1279
|
+
year: {_InventoryKey.SAND_CONTENT: get_node_value(measurement)/100}
|
|
1280
|
+
for year, measurement in grouped_sand_content_measurements.items() if measurement
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
def _get_grouped_carbon_input_data(grouped_cycles: dict) -> dict:
|
|
1285
|
+
grouped_carbon_sources = {
|
|
1286
|
+
year: _get_carbon_sources_from_cycles(cycle)
|
|
1287
|
+
for year, cycle in grouped_cycles.items()
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
return {
|
|
1291
|
+
year: {
|
|
1292
|
+
_InventoryKey.CARBON_INPUT: _calc_total_organic_carbon_input(carbon_sources),
|
|
1293
|
+
_InventoryKey.N_CONTENT: _calc_average_nitrogen_content_of_organic_carbon_sources(carbon_sources),
|
|
1294
|
+
_InventoryKey.LIGNIN_CONTENT: _calc_average_lignin_content_of_organic_carbon_sources(carbon_sources)
|
|
1295
|
+
} for year, carbon_sources in grouped_carbon_sources.items()
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
def _get_carbon_sources_from_cycles(cycles: dict) -> list[CarbonSource]:
|
|
1300
|
+
"""
|
|
1301
|
+
Retrieves and formats all of the valid carbon sources from a list of cycles.
|
|
1302
|
+
|
|
1303
|
+
Carbon sources can be either a Hestia `Product` node (e.g. crop residue) or `Input` node (e.g. organic amendment).
|
|
1304
|
+
|
|
1305
|
+
Parameters
|
|
1306
|
+
----------
|
|
1307
|
+
cycles : list[dict]
|
|
1308
|
+
A list of Hestia `Cycle` nodes, see: https://www.hestia.earth/schema/Cycle.
|
|
1309
|
+
|
|
1310
|
+
Returns
|
|
1311
|
+
-------
|
|
1312
|
+
list[CarbonSource]
|
|
1313
|
+
A formatted list of `CarbonSource`s for the inputted `Cycle`s.
|
|
1314
|
+
"""
|
|
1315
|
+
inputs_and_products = non_empty_list(flatten(
|
|
1316
|
+
[cycle.get("inputs", []) + cycle.get("products", []) for cycle in cycles]
|
|
1317
|
+
))
|
|
1318
|
+
|
|
1319
|
+
return non_empty_list([
|
|
1320
|
+
_iterate_carbon_source(node) for node in inputs_and_products
|
|
1321
|
+
if any([
|
|
1322
|
+
node.get("term", {}).get("@id") in get_crop_residue_inc_or_left_terms_with_cache(),
|
|
1323
|
+
node.get("term", {}).get("termType") in _CARBON_SOURCE_TERM_TYPES
|
|
1324
|
+
])
|
|
1325
|
+
])
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
def _iterate_carbon_source(node: dict) -> Union[CarbonSource, None]:
|
|
1329
|
+
"""
|
|
1330
|
+
Validates whether a node is a valid carbon source and returns a `CarbonSource` named tuple if yes.
|
|
1331
|
+
|
|
1332
|
+
Parameters
|
|
1333
|
+
----------
|
|
1334
|
+
node : dict
|
|
1335
|
+
A Hestia `Product` or `Input` node, see: https://www.hestia.earth/schema/Product
|
|
1336
|
+
or https://www.hestia.earth/schema/Input.
|
|
1337
|
+
|
|
1338
|
+
Returns
|
|
1339
|
+
-------
|
|
1340
|
+
CarbonSource | None
|
|
1341
|
+
A `CarbonSource` named tuple if the node is a carbon source with the required properties, else `None`.
|
|
1342
|
+
"""
|
|
1343
|
+
mass = list_sum(node.get("value", []))
|
|
1344
|
+
carbon_content, nitrogen_content, lignin_content = (
|
|
1345
|
+
get_node_property(node, term_id).get("value", 0)/100 for term_id in _CARBON_INPUT_PROPERTY_TERM_IDS
|
|
1346
|
+
)
|
|
1347
|
+
|
|
1348
|
+
should_run_ = all([
|
|
1349
|
+
mass > 0,
|
|
1350
|
+
0 < carbon_content <= 1,
|
|
1351
|
+
0 < nitrogen_content <= 1,
|
|
1352
|
+
0 < lignin_content <= 1
|
|
1353
|
+
])
|
|
1354
|
+
|
|
1355
|
+
return (
|
|
1356
|
+
CarbonSource(
|
|
1357
|
+
mass, carbon_content, nitrogen_content, lignin_content
|
|
1358
|
+
) if should_run_ else None
|
|
1359
|
+
)
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
def _calc_total_organic_carbon_input(
|
|
1363
|
+
carbon_sources: list[CarbonSource], default_carbon_content=0.42
|
|
1364
|
+
) -> float:
|
|
1365
|
+
"""
|
|
1366
|
+
Equation 5.0H part 1. Calculate the total organic carbon to a site from all carbon sources (above-ground and
|
|
1367
|
+
below-ground crop residues, organic amendments, etc.).
|
|
1368
|
+
|
|
1369
|
+
Parameters
|
|
1370
|
+
----------
|
|
1371
|
+
carbon_sources : list[CarbonSource])
|
|
1372
|
+
A list of carbon sources as named tuples with the format
|
|
1373
|
+
`(mass: float, carbon_content: float, nitrogen_content: float, lignin_content: float)`.
|
|
1374
|
+
default_carbon_content : float
|
|
1375
|
+
The default carbon content of a carbon source, decimal proportion, kg C (kg d.m.)-1.
|
|
1376
|
+
|
|
1377
|
+
Returns
|
|
1378
|
+
-------
|
|
1379
|
+
float
|
|
1380
|
+
The total mass of organic carbon inputted into the site, kg C ha-1.
|
|
1381
|
+
"""
|
|
1382
|
+
return sum(c.mass * (c.carbon_content if c.carbon_content else default_carbon_content) for c in carbon_sources)
|
|
1383
|
+
|
|
1384
|
+
|
|
1385
|
+
def _calc_average_nitrogen_content_of_organic_carbon_sources(
|
|
1386
|
+
carbon_sources: list[CarbonSource], default_nitrogen_content=0.0085
|
|
1387
|
+
) -> float:
|
|
1388
|
+
"""
|
|
1389
|
+
Calculate the average nitrogen content of the carbon inputs through a weighted mean.
|
|
1390
|
+
|
|
1391
|
+
Parameters
|
|
1392
|
+
----------
|
|
1393
|
+
carbon_sources : list[CarbonSource]
|
|
1394
|
+
A list of carbon sources as named tuples with the format
|
|
1395
|
+
`(mass: float, carbon_content: float, nitrogen_content: float, lignin_content: float)`.
|
|
1396
|
+
default_nitrogen_content : float
|
|
1397
|
+
The default nitrogen content of a carbon source, decimal proportion, kg N (kg d.m.)-1.
|
|
1398
|
+
|
|
1399
|
+
Returns
|
|
1400
|
+
-------
|
|
1401
|
+
float
|
|
1402
|
+
The average nitrogen content of the carbon sources, decimal_proportion, kg N (kg d.m.)-1.
|
|
1403
|
+
"""
|
|
1404
|
+
total_weight = sum(c.mass for c in carbon_sources)
|
|
1405
|
+
weighted_values = [
|
|
1406
|
+
c.mass * (c.nitrogen_content if c.nitrogen_content else default_nitrogen_content) for c in carbon_sources
|
|
1407
|
+
]
|
|
1408
|
+
should_run_ = total_weight > 0
|
|
1409
|
+
return sum(weighted_values) / total_weight if should_run_ else 0
|
|
1410
|
+
|
|
1411
|
+
|
|
1412
|
+
def _calc_average_lignin_content_of_organic_carbon_sources(
|
|
1413
|
+
carbon_sources: list[CarbonSource], default_lignin_content=0.073
|
|
1414
|
+
) -> float:
|
|
1415
|
+
"""
|
|
1416
|
+
Calculate the average lignin content of the carbon inputs through a weighted mean.
|
|
1417
|
+
|
|
1418
|
+
Parameters
|
|
1419
|
+
----------
|
|
1420
|
+
carbon_sources : list[CarbonSource]
|
|
1421
|
+
A list of carbon sources as named tuples with the format
|
|
1422
|
+
`(mass: float, carbon_content: float, nitrogen_content: float, lignin_content: float)`.
|
|
1423
|
+
default_lignin_content : float
|
|
1424
|
+
The default lignin content of a carbon source, decimal proportion, kg lignin (kg d.m.)-1.
|
|
1425
|
+
|
|
1426
|
+
Returns
|
|
1427
|
+
-------
|
|
1428
|
+
float
|
|
1429
|
+
The average lignin content of the carbon sources, decimal_proportion, kg lignin (kg d.m.)-1.
|
|
1430
|
+
"""
|
|
1431
|
+
total_weight = sum(c.mass for c in carbon_sources)
|
|
1432
|
+
weighted_values = [
|
|
1433
|
+
c.mass * (c.lignin_content if c.lignin_content else default_lignin_content) for c in carbon_sources
|
|
1434
|
+
]
|
|
1435
|
+
should_run_ = total_weight > 0
|
|
1436
|
+
return sum(weighted_values) / total_weight if should_run_ else 0
|
|
1437
|
+
|
|
1438
|
+
|
|
1439
|
+
def _get_grouped_tillage_categories(grouped_cycles):
|
|
1440
|
+
return {
|
|
1441
|
+
year: {
|
|
1442
|
+
_InventoryKey.TILLAGE_CATEGORY: _assign_tier_2_ipcc_tillage_management_category(cycles)
|
|
1443
|
+
} for year, cycles in grouped_cycles.items()
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
def _assign_tier_2_ipcc_tillage_management_category(
|
|
1448
|
+
cycles: list[dict],
|
|
1449
|
+
default: IpccManagementCategory = IpccManagementCategory.OTHER
|
|
1450
|
+
) -> IpccManagementCategory:
|
|
1451
|
+
"""
|
|
1452
|
+
Assigns a tillage `IpccManagementCategory` to a list of Hestia `Cycle`s.
|
|
1453
|
+
|
|
1454
|
+
Parameters
|
|
1455
|
+
----------
|
|
1456
|
+
cycles : list[dict])
|
|
1457
|
+
A list of Hestia `Cycle` nodes, see: https://www.hestia.earth/schema/Cycle.
|
|
1458
|
+
|
|
1459
|
+
Returns
|
|
1460
|
+
-------
|
|
1461
|
+
IpccManagementCategory: The assigned tillage `IpccManagementCategory`.
|
|
1462
|
+
"""
|
|
1463
|
+
return next(
|
|
1464
|
+
(
|
|
1465
|
+
key for key in _TIER_2_TILLAGE_MANAGEMENT_CATEGORY_DECISION_TREE
|
|
1466
|
+
if _TIER_2_TILLAGE_MANAGEMENT_CATEGORY_DECISION_TREE[key](cycles, key)
|
|
1467
|
+
),
|
|
1468
|
+
default
|
|
1469
|
+
) if len(cycles) > 0 else default
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
_TIER_2_TILLAGE_MANAGEMENT_CATEGORY_DECISION_TREE = {
|
|
1473
|
+
IpccManagementCategory.FULL_TILLAGE: (
|
|
1474
|
+
lambda cycles, key: any(
|
|
1475
|
+
_check_cycle_tillage_management_category(cycle, key) for cycle in cycles
|
|
1476
|
+
)
|
|
1477
|
+
),
|
|
1478
|
+
IpccManagementCategory.REDUCED_TILLAGE: (
|
|
1479
|
+
lambda cycles, key: any(
|
|
1480
|
+
_check_cycle_tillage_management_category(cycle, key) for cycle in cycles
|
|
1481
|
+
)
|
|
1482
|
+
),
|
|
1483
|
+
IpccManagementCategory.NO_TILLAGE: (
|
|
1484
|
+
lambda cycles, key: any(
|
|
1485
|
+
_check_cycle_tillage_management_category(cycle, key) for cycle in cycles
|
|
1486
|
+
)
|
|
1487
|
+
)
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
def _check_cycle_tillage_management_category(
|
|
1492
|
+
cycle: dict,
|
|
1493
|
+
key: IpccManagementCategory
|
|
1494
|
+
) -> bool:
|
|
1495
|
+
"""
|
|
1496
|
+
Checks whether a Hesita `Cycle` node meets the requirements of a specific tillage `IpccManagementCategory`.
|
|
1497
|
+
|
|
1498
|
+
Parameters
|
|
1499
|
+
----------
|
|
1500
|
+
cycle : dict
|
|
1501
|
+
A Hestia `Cycle` node, see: https://www.hestia.earth/schema/Cycle.
|
|
1502
|
+
key : IpccManagementCategory
|
|
1503
|
+
The `IpccManagementCategory` to match.
|
|
1504
|
+
|
|
1505
|
+
Returns
|
|
1506
|
+
-------
|
|
1507
|
+
bool
|
|
1508
|
+
Whether or not the cycle meets the requirements for the category.
|
|
1509
|
+
"""
|
|
1510
|
+
LOOKUP = _LOOKUPS["tillage"]
|
|
1511
|
+
target_lookup_values = IPCC_MANAGEMENT_CATEGORY_TO_TILLAGE_MANAGEMENT_LOOKUP_VALUE.get(key, None)
|
|
1512
|
+
|
|
1513
|
+
practices = cycle.get("practices", [])
|
|
1514
|
+
tillage_nodes = filter_list_term_type(
|
|
1515
|
+
practices, [TermTermType.TILLAGE]
|
|
1516
|
+
)
|
|
1517
|
+
|
|
1518
|
+
return cumulative_nodes_lookup_match(
|
|
1519
|
+
tillage_nodes,
|
|
1520
|
+
lookup=LOOKUP,
|
|
1521
|
+
target_lookup_values=target_lookup_values,
|
|
1522
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
1523
|
+
) and (
|
|
1524
|
+
key is not IpccManagementCategory.NO_TILLAGE
|
|
1525
|
+
or _check_zero_tillages(tillage_nodes)
|
|
1526
|
+
)
|
|
1527
|
+
|
|
1528
|
+
|
|
1529
|
+
def _check_zero_tillages(practices: list[dict]) -> bool:
|
|
1530
|
+
"""
|
|
1531
|
+
Checks whether a list of `Practice`s nodes describe 0 total tillages, or not.
|
|
1532
|
+
|
|
1533
|
+
Parameters
|
|
1534
|
+
----------
|
|
1535
|
+
practices : list[dict]
|
|
1536
|
+
A list of Hestia `Practice` nodes, see: https://www.hestia.earth/schema/Practice.
|
|
1537
|
+
|
|
1538
|
+
Returns
|
|
1539
|
+
-------
|
|
1540
|
+
bool
|
|
1541
|
+
Whether or not 0 tillages counted.
|
|
1542
|
+
"""
|
|
1543
|
+
practice = find_term_match(practices, _NUMBER_OF_TILLAGES_TERM_ID)
|
|
1544
|
+
nTillages = list_sum(practice.get("value", []))
|
|
1545
|
+
return nTillages <= 0
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
def _get_grouped_is_paddy_rice(grouped_cycles: dict) -> dict:
|
|
1549
|
+
return {
|
|
1550
|
+
year: {
|
|
1551
|
+
_InventoryKey.IS_PADDY_RICE: _check_is_paddy_rice(cycles)
|
|
1552
|
+
} for year, cycles in grouped_cycles.items()
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
def _check_is_paddy_rice(cycles: list[dict]) -> bool:
|
|
1557
|
+
LOOKUP = _LOOKUPS["crop"]
|
|
1558
|
+
TARGET_LOOKUP_VALUES = IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE.get(
|
|
1559
|
+
IpccLandUseCategory.PADDY_RICE_CULTIVATION, None
|
|
1560
|
+
)
|
|
1561
|
+
|
|
1562
|
+
has_paddy_rice_products = any(cumulative_nodes_lookup_match(
|
|
1563
|
+
filter_list_term_type(
|
|
1564
|
+
cycle.get("products", []) + cycle.get("practices", []),
|
|
1565
|
+
[TermTermType.CROP, TermTermType.FORAGE, TermTermType.LANDCOVER]
|
|
1566
|
+
),
|
|
1567
|
+
lookup=LOOKUP,
|
|
1568
|
+
target_lookup_values=TARGET_LOOKUP_VALUES,
|
|
1569
|
+
cumulative_threshold=MIN_YIELD_THRESHOLD,
|
|
1570
|
+
default_node_value=MIN_YIELD_THRESHOLD
|
|
1571
|
+
) for cycle in cycles)
|
|
1572
|
+
|
|
1573
|
+
has_upland_rice_products = any(cumulative_nodes_term_match(
|
|
1574
|
+
filter_list_term_type(
|
|
1575
|
+
cycle.get("products", []) + cycle.get("practices", []),
|
|
1576
|
+
[TermTermType.CROP, TermTermType.FORAGE, TermTermType.LANDCOVER]
|
|
1577
|
+
),
|
|
1578
|
+
target_term_ids=get_upland_rice_crop_terms_with_cache() + get_upland_rice_land_cover_terms_with_cache(),
|
|
1579
|
+
cumulative_threshold=MIN_YIELD_THRESHOLD,
|
|
1580
|
+
default_node_value=MIN_YIELD_THRESHOLD
|
|
1581
|
+
) for cycle in cycles)
|
|
1582
|
+
|
|
1583
|
+
has_irrigation = any(
|
|
1584
|
+
check_irrigation(filter_list_term_type(cycle.get("practices", []), [TermTermType.WATERREGIME]))
|
|
1585
|
+
for cycle in cycles
|
|
1586
|
+
)
|
|
1587
|
+
|
|
1588
|
+
return has_paddy_rice_products or (has_upland_rice_products and has_irrigation)
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
def _should_run_inventory(group: dict) -> bool:
|
|
1592
|
+
"""
|
|
1593
|
+
Determines whether there is sufficient data in an inventory year to run the tier 2 model.
|
|
1594
|
+
|
|
1595
|
+
1. Check that the cycle is not for paddy rice.
|
|
1596
|
+
2. Check if monthly data has a value for each calendar month.
|
|
1597
|
+
3. Check if all required keys are present.
|
|
1598
|
+
|
|
1599
|
+
Parameters
|
|
1600
|
+
----------
|
|
1601
|
+
group : dict
|
|
1602
|
+
Dictionary containing information for a specific inventory year.
|
|
1603
|
+
|
|
1604
|
+
Returns
|
|
1605
|
+
-------
|
|
1606
|
+
bool
|
|
1607
|
+
True if the inventory year is valid, False otherwise.
|
|
1608
|
+
"""
|
|
1609
|
+
monthly_data_complete = _check_12_months(
|
|
1610
|
+
group,
|
|
1611
|
+
{
|
|
1612
|
+
_InventoryKey.TEMP_MONTHLY,
|
|
1613
|
+
_InventoryKey.PRECIP_MONTHLY,
|
|
1614
|
+
_InventoryKey.PET_MONTHLY,
|
|
1615
|
+
_InventoryKey.IRRIGATED_MONTHLY
|
|
1616
|
+
}
|
|
1617
|
+
)
|
|
1618
|
+
|
|
1619
|
+
carbon_input_data_complete = all([
|
|
1620
|
+
group.get(_InventoryKey.CARBON_INPUT, 0) > 0,
|
|
1621
|
+
group.get(_InventoryKey.N_CONTENT, 0) > 0,
|
|
1622
|
+
group.get(_InventoryKey.LIGNIN_CONTENT, 0) > 0,
|
|
1623
|
+
])
|
|
1624
|
+
|
|
1625
|
+
return all([
|
|
1626
|
+
not group.get(_InventoryKey.IS_PADDY_RICE),
|
|
1627
|
+
monthly_data_complete,
|
|
1628
|
+
carbon_input_data_complete,
|
|
1629
|
+
all(key in group.keys() for key in _REQUIRED_KEYS),
|
|
1630
|
+
])
|