hestia-earth-models 0.61.6__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/cycle/input/hestiaAggregatedData.py +1 -1
- 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 +360 -260
- hestia_earth/models/schererPfister2015/pToDrainageWaterSoilFlux.py +1 -1
- hestia_earth/models/schererPfister2015/pToGroundwaterSoilFlux.py +1 -1
- hestia_earth/models/site/organicCarbonPerHa.py +58 -44
- hestia_earth/models/site/soilMeasurement.py +25 -38
- hestia_earth/models/utils/__init__.py +28 -0
- hestia_earth/models/utils/aquacultureManagement.py +2 -2
- hestia_earth/models/utils/array_builders.py +578 -0
- hestia_earth/models/utils/blank_node.py +2 -3
- hestia_earth/models/utils/crop.py +24 -1
- hestia_earth/models/utils/cycle.py +0 -23
- 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/lookup.py +6 -3
- hestia_earth/models/utils/measurement.py +118 -4
- hestia_earth/models/utils/site.py +25 -13
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.61.6.dist-info → hestia_earth_models-0.61.8.dist-info}/METADATA +1 -1
- {hestia_earth_models-0.61.6.dist-info → hestia_earth_models-0.61.8.dist-info}/RECORD +52 -40
- 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 +5 -19
- tests/models/utils/test_array_builders.py +253 -0
- tests/models/utils/{test_cycle.py → test_crop.py} +2 -2
- 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.6.dist-info → hestia_earth_models-0.61.8.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.61.6.dist-info → hestia_earth_models-0.61.8.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.61.6.dist-info → hestia_earth_models-0.61.8.dist-info}/top_level.txt +0 -0
|
@@ -1,70 +1,39 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The IPCC model for estimating soil organic carbon stock changes in the 0 - 30cm depth interval due to management
|
|
3
|
-
changes. This model
|
|
4
|
-
|
|
5
|
-
grasslands remaining grasslands).
|
|
3
|
+
changes. This model will attempt to run the Tier 2 methodology and return results. If the Tier 2 methodology cannot be
|
|
4
|
+
run, it will attempt to run the Tier 1 methodology and return the results.
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[Hestia SOC wiki](https://gitlab.com/hestia-earth/hestia-engine-models/-/wikis/Soil-organic-carbon-modelling).
|
|
6
|
+
Both tier methodologies are run as Monte Carlo simulations with 10000 iterations, allowing the model to calculate a
|
|
7
|
+
`value` (mean), `sd`, `min`, `max` for each year of the model result.
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
The requirements in this file are for the Tier 1 methodology only, as it has simpler requirements. The requirements for
|
|
10
|
+
the Tier 2 methodology can be found in the
|
|
11
|
+
[Hestia SOC wiki](https://gitlab.com/hestia-earth/hestia-engine-models/-/wikis/Soil-organic-carbon-modelling)
|
|
12
|
+
alongside data recommendations, examples and explanations for both tiers.
|
|
13
13
|
"""
|
|
14
|
-
from
|
|
15
|
-
from numpy import exp
|
|
14
|
+
from functools import reduce
|
|
16
15
|
from pydash.objects import merge
|
|
17
|
-
from
|
|
18
|
-
from typing import (
|
|
19
|
-
Any,
|
|
20
|
-
NamedTuple,
|
|
21
|
-
Optional,
|
|
22
|
-
Union
|
|
23
|
-
)
|
|
24
|
-
from hestia_earth.schema import (
|
|
25
|
-
CycleFunctionalUnit,
|
|
26
|
-
MeasurementMethodClassification,
|
|
27
|
-
SiteSiteType,
|
|
28
|
-
TermTermType,
|
|
29
|
-
)
|
|
30
|
-
from hestia_earth.utils.model import find_term_match, filter_list_term_type
|
|
31
|
-
from hestia_earth.utils.tools import flatten, list_sum, non_empty_list
|
|
16
|
+
from types import ModuleType
|
|
32
17
|
|
|
33
18
|
from hestia_earth.models.log import log_as_table, logRequirements, logShouldRun
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
group_nodes_by_year_and_month,
|
|
40
|
-
group_nodes_by_year,
|
|
41
|
-
GroupNodesByYearMode,
|
|
42
|
-
node_lookup_match,
|
|
43
|
-
node_term_match
|
|
44
|
-
)
|
|
45
|
-
from hestia_earth.models.utils.cycle import check_cycle_site_ids_identical
|
|
46
|
-
from hestia_earth.models.utils.ecoClimateZone import get_ecoClimateZone_lookup_value
|
|
47
|
-
from hestia_earth.models.utils.measurement import (
|
|
48
|
-
_new_measurement,
|
|
49
|
-
)
|
|
50
|
-
from hestia_earth.models.utils.property import get_node_property
|
|
51
|
-
from hestia_earth.models.utils.term import (
|
|
52
|
-
get_cover_crop_property_terms,
|
|
53
|
-
get_crop_residue_incorporated_or_left_on_field_terms,
|
|
54
|
-
get_irrigated_terms,
|
|
55
|
-
get_residue_removed_or_burnt_terms,
|
|
56
|
-
get_upland_rice_crop_terms,
|
|
57
|
-
get_upland_rice_land_cover_terms
|
|
58
|
-
)
|
|
59
|
-
from hestia_earth.models.utils.site import related_cycles
|
|
60
|
-
from .utils import check_consecutive
|
|
61
|
-
from . import MODEL
|
|
19
|
+
|
|
20
|
+
from .organicCarbonPerHa_utils import format_bool, format_bool_list, format_enum, format_number, format_number_list
|
|
21
|
+
from . import organicCarbonPerHa_tier_1_utils as tier_1
|
|
22
|
+
from . import organicCarbonPerHa_tier_2_utils as tier_2
|
|
23
|
+
from . import MODEL # noqa
|
|
62
24
|
|
|
63
25
|
REQUIREMENTS = {
|
|
64
26
|
"Site": {
|
|
65
27
|
"siteType": ["cropland", "permanent pasture", "forest", "other natural vegetation"],
|
|
28
|
+
"management": [
|
|
29
|
+
{"@type": "Management", "value": "", "term.termType": "landCover"}
|
|
30
|
+
],
|
|
66
31
|
"measurements": [
|
|
67
|
-
{
|
|
32
|
+
{
|
|
33
|
+
"@type": "Measurement",
|
|
34
|
+
"value": ["1", "2", "3", "4", "7", "8", "9", "10", "11", "12"],
|
|
35
|
+
"term.@id": "ecoClimateZone"
|
|
36
|
+
}
|
|
68
37
|
],
|
|
69
38
|
"optional": {
|
|
70
39
|
"measurements": [
|
|
@@ -79,7 +48,6 @@ REQUIREMENTS = {
|
|
|
79
48
|
"term.termType": "cropResidueManagement",
|
|
80
49
|
"name": ["burnt", "removed"]
|
|
81
50
|
},
|
|
82
|
-
{"@type": "Management", "value": "", "startDate": "", "endDate": "", "term.termType": "landCover"},
|
|
83
51
|
{
|
|
84
52
|
"@type": "Management",
|
|
85
53
|
"value": "",
|
|
@@ -94,29 +62,29 @@ REQUIREMENTS = {
|
|
|
94
62
|
"startDate": "",
|
|
95
63
|
"endDate": "",
|
|
96
64
|
"term.termType": "waterRegime",
|
|
97
|
-
"name": ["
|
|
65
|
+
"name": ["deep water", "irrigated"]
|
|
98
66
|
},
|
|
99
|
-
{"@type": "Management", "value": "", "startDate": "", "endDate": "", "term.@id": "animalManureUsed"},
|
|
100
67
|
{
|
|
101
68
|
"@type": "Management",
|
|
102
69
|
"value": "",
|
|
103
70
|
"startDate": "",
|
|
104
71
|
"endDate": "",
|
|
105
|
-
"term.@id": "
|
|
72
|
+
"term.@id": "amendmentIncreasingSoilCarbonUsed"
|
|
106
73
|
},
|
|
74
|
+
{"@type": "Management", "value": "", "startDate": "", "endDate": "", "term.@id": "animalManureUsed"},
|
|
107
75
|
{
|
|
108
76
|
"@type": "Management",
|
|
109
77
|
"value": "",
|
|
110
78
|
"startDate": "",
|
|
111
79
|
"endDate": "",
|
|
112
|
-
"term.@id": "
|
|
80
|
+
"term.@id": "inorganicNitrogenFertiliserUsed"
|
|
113
81
|
},
|
|
114
82
|
{
|
|
115
83
|
"@type": "Management",
|
|
116
84
|
"value": "",
|
|
117
85
|
"startDate": "",
|
|
118
86
|
"endDate": "",
|
|
119
|
-
"term.@id": "
|
|
87
|
+
"term.@id": "organicFertiliserUsed"
|
|
120
88
|
},
|
|
121
89
|
{"@type": "Management", "value": "", "startDate": "", "endDate": "", "term.@id": "shortBareFallow"}
|
|
122
90
|
]
|
|
@@ -125,33 +93,6 @@ REQUIREMENTS = {
|
|
|
125
93
|
}
|
|
126
94
|
LOOKUPS = {
|
|
127
95
|
"crop": "IPCC_LAND_USE_CATEGORY",
|
|
128
|
-
"ecoClimateZone": [
|
|
129
|
-
"IPCC_2019_SOC_REF_KG_C_HECTARE_SAN",
|
|
130
|
-
"IPCC_2019_SOC_REF_KG_C_HECTARE_WET",
|
|
131
|
-
"IPCC_2019_SOC_REF_KG_C_HECTARE_VOL",
|
|
132
|
-
"IPCC_2019_SOC_REF_KG_C_HECTARE_POD",
|
|
133
|
-
"IPCC_2019_SOC_REF_KG_C_HECTARE_HAC",
|
|
134
|
-
"IPCC_2019_SOC_REF_KG_C_HECTARE_LAC",
|
|
135
|
-
"IPCC_2019_LANDUSE_FACTOR_GRASSLAND",
|
|
136
|
-
"IPCC_2019_LANDUSE_FACTOR_PERENNIAL_CROPS",
|
|
137
|
-
"IPCC_2019_LANDUSE_FACTOR_PADDY_RICE_CULTIVATION",
|
|
138
|
-
"IPCC_2019_LANDUSE_FACTOR_ANNUAL_CROPS_WET",
|
|
139
|
-
"IPCC_2019_LANDUSE_FACTOR_ANNUAL_CROPS",
|
|
140
|
-
"IPCC_2019_LANDUSE_FACTOR_SET_ASIDE",
|
|
141
|
-
"IPCC_2019_GRASSLAND_MANAGEMENT_FACTOR_SEVERELY_DEGRADED",
|
|
142
|
-
"IPCC_2019_GRASSLAND_MANAGEMENT_FACTOR_IMPROVED_GRASSLAND",
|
|
143
|
-
"IPCC_2019_GRASSLAND_MANAGEMENT_FACTOR_HIGH_INTENSITY_GRAZING",
|
|
144
|
-
"IPCC_2019_GRASSLAND_MANAGEMENT_FACTOR_NOMINALLY_MANAGED",
|
|
145
|
-
"IPCC_2019_TILLAGE_MANAGEMENT_FACTOR_FULL_TILLAGE",
|
|
146
|
-
"IPCC_2019_TILLAGE_MANAGEMENT_FACTOR_REDUCED_TILLAGE",
|
|
147
|
-
"IPCC_2019_TILLAGE_MANAGEMENT_FACTOR_NO_TILLAGE",
|
|
148
|
-
"IPCC_2019_GRASSLAND_CARBON_INPUT_FACTOR_HIGH",
|
|
149
|
-
"IPCC_2019_GRASSLAND_CARBON_INPUT_FACTOR_MEDIUM",
|
|
150
|
-
"IPCC_2019_CROPLAND_CARBON_INPUT_FACTOR_HIGH_WITH_MANURE",
|
|
151
|
-
"IPCC_2019_CROPLAND_CARBON_INPUT_FACTOR_HIGH_WITHOUT_MANURE",
|
|
152
|
-
"IPCC_2019_CROPLAND_CARBON_INPUT_FACTOR_MEDIUM",
|
|
153
|
-
"IPCC_2019_CROPLAND_CARBON_INPUT_FACTOR_LOW"
|
|
154
|
-
],
|
|
155
96
|
"landCover": [
|
|
156
97
|
"IPCC_LAND_USE_CATEGORY",
|
|
157
98
|
"LOW_RESIDUE_PRODUCING_CROP",
|
|
@@ -165,3838 +106,133 @@ LOOKUPS = {
|
|
|
165
106
|
RETURNS = {
|
|
166
107
|
"Measurement": [{
|
|
167
108
|
"value": "",
|
|
109
|
+
"sd": "",
|
|
110
|
+
"min": "",
|
|
111
|
+
"max": "",
|
|
112
|
+
"statsDefinition": "simulated",
|
|
113
|
+
"observations": "",
|
|
168
114
|
"dates": "",
|
|
169
115
|
"depthUpper": "0",
|
|
170
116
|
"depthLower": "30",
|
|
171
117
|
"methodClassification": ""
|
|
172
118
|
}]
|
|
173
119
|
}
|
|
174
|
-
|
|
175
120
|
TERM_ID = 'organicCarbonPerHa'
|
|
121
|
+
ITERATIONS = 10000 # TODO: Refine number of iterations to balance performance and precision.
|
|
176
122
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
MIN_AREA_THRESHOLD = 30 # 30% as per IPCC guidelines
|
|
180
|
-
SUPER_MAJORITY_AREA_THRESHOLD = 100 - MIN_AREA_THRESHOLD
|
|
181
|
-
MIN_YIELD_THRESHOLD = 1
|
|
182
|
-
DEPTH_UPPER = 0
|
|
183
|
-
DEPTH_LOWER = 30
|
|
184
|
-
|
|
185
|
-
# --- TIER 2 CONSTANTS ---
|
|
186
|
-
|
|
187
|
-
NUMBER_OF_TILLAGES_TERM_ID = "numberOfTillages"
|
|
188
|
-
TEMPERATURE_MONTHLY_TERM_ID = "temperatureMonthly"
|
|
189
|
-
PRECIPITATION_MONTHLY_TERM_ID = "precipitationMonthly"
|
|
190
|
-
PET_MONTHLY_TERM_ID = "potentialEvapotranspirationMonthly"
|
|
191
|
-
SAND_CONTENT_TERM_ID = "sandContent"
|
|
192
|
-
CARBON_CONTENT_TERM_ID = "carbonContent"
|
|
193
|
-
NITROGEN_CONTENT_TERM_ID = "nitrogenContent"
|
|
194
|
-
LIGNIN_CONTENT_TERM_ID = "ligninContent"
|
|
195
|
-
|
|
196
|
-
CARBON_INPUT_PROPERTY_TERM_IDS = [
|
|
197
|
-
CARBON_CONTENT_TERM_ID,
|
|
198
|
-
NITROGEN_CONTENT_TERM_ID,
|
|
199
|
-
LIGNIN_CONTENT_TERM_ID
|
|
200
|
-
]
|
|
201
|
-
|
|
202
|
-
CARBON_SOURCE_TERM_TYPES = [
|
|
203
|
-
TermTermType.ORGANICFERTILISER.value,
|
|
204
|
-
TermTermType.SOILAMENDMENT.value
|
|
205
|
-
]
|
|
206
|
-
|
|
207
|
-
MIN_RUN_IN_PERIOD = 5
|
|
208
|
-
|
|
209
|
-
DEFAULT_PARAMS = {
|
|
210
|
-
"active_decay_factor": 7.4,
|
|
211
|
-
"slow_decay_factor": 0.209,
|
|
212
|
-
"passive_decay_factor": 0.00689,
|
|
213
|
-
"f_1": 0.378,
|
|
214
|
-
"f_2_full_tillage": 0.455,
|
|
215
|
-
"f_2_reduced_tillage": 0.477,
|
|
216
|
-
"f_2_no_tillage": 0.5,
|
|
217
|
-
"f_2_unknown_tillage": 0.368,
|
|
218
|
-
"f_3": 0.455,
|
|
219
|
-
"f_5": 0.0855,
|
|
220
|
-
"f_6": 0.0504,
|
|
221
|
-
"f_7": 0.42,
|
|
222
|
-
"f_8": 0.45,
|
|
223
|
-
"tillage_factor_full_tillage": 3.036,
|
|
224
|
-
"tillage_factor_reduced_tillage": 2.075,
|
|
225
|
-
"tillage_factor_no_tillage": 1,
|
|
226
|
-
"maximum_temperature": 45,
|
|
227
|
-
"optimum_temperature": 33.69,
|
|
228
|
-
"water_factor_slope": 1.331,
|
|
229
|
-
"default_carbon_content": 0.42,
|
|
230
|
-
"default_nitrogen_content": 0.0085,
|
|
231
|
-
"default_lignin_content": 0.073
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
VALID_SITE_TYPES_TIER_2 = [
|
|
235
|
-
SiteSiteType.CROPLAND.value
|
|
236
|
-
]
|
|
237
|
-
|
|
238
|
-
VALID_FUNCTIONAL_UNITS_TIER_2 = [
|
|
239
|
-
CycleFunctionalUnit._1_HA.value
|
|
240
|
-
]
|
|
241
|
-
|
|
242
|
-
# --- TIER 1 CONSTANTS ---
|
|
243
|
-
|
|
244
|
-
CLAY_CONTENT_TERM_ID = "clayContent"
|
|
245
|
-
LONG_FALLOW_CROP_TERM_ID = "longFallowCrop"
|
|
246
|
-
IMPROVED_PASTURE_TERM_ID = "improvedPasture"
|
|
247
|
-
SHORT_BARE_FALLOW_TERM_ID = "shortBareFallow"
|
|
248
|
-
ANIMAL_MANURE_USED_TERM_ID = "animalManureUsed"
|
|
249
|
-
INORGANIC_NITROGEN_FERTILISER_USED_TERM_ID = "inorganicNitrogenFertiliserUsed"
|
|
250
|
-
ORGANIC_FERTILISER_USED_TERM_ID = "organicFertiliserUsed"
|
|
251
|
-
SOIL_AMENDMENT_USED_TERM_ID = "amendmentIncreasingSoilCarbonUsed"
|
|
252
|
-
|
|
253
|
-
CLAY_CONTENT_MAX = 8
|
|
254
|
-
SAND_CONTENT_MIN = 70
|
|
255
|
-
|
|
256
|
-
EQUILIBRIUM_TRANSITION_PERIOD = 20
|
|
257
|
-
"""
|
|
258
|
-
The number of years required for soil organic carbon to reach equilibrium after
|
|
259
|
-
a change in land use, management regime or carbon input regime.
|
|
260
|
-
"""
|
|
261
|
-
|
|
262
|
-
EXCLUDED_ECO_CLIMATE_ZONES_TIER_1 = {
|
|
263
|
-
5, # Polar Moist
|
|
264
|
-
6 # Polar Dry
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
VALID_SITE_TYPES_TIER_1 = [
|
|
268
|
-
SiteSiteType.CROPLAND.value,
|
|
269
|
-
SiteSiteType.FOREST.value,
|
|
270
|
-
SiteSiteType.OTHER_NATURAL_VEGETATION.value,
|
|
271
|
-
SiteSiteType.PERMANENT_PASTURE.value,
|
|
272
|
-
]
|
|
273
|
-
|
|
274
|
-
# --- SHARED TIER 1 & TIER 2 FORMAT MEASUREMENT OUTPUT ---
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def _measurement(year: int, value: float, method_classification: str) -> dict:
|
|
278
|
-
"""
|
|
279
|
-
Build a Hestia `Measurement` node to contain a value calculated by the models.
|
|
280
|
-
|
|
281
|
-
Parameters
|
|
282
|
-
----------
|
|
283
|
-
year : int
|
|
284
|
-
The year that the value is associated with.
|
|
285
|
-
value : float
|
|
286
|
-
The value calculated by either the Tier 1 or Tier 2 model.
|
|
287
|
-
method_classification :str
|
|
288
|
-
The method tier used to calculate the value, either `tier 1 model` or `tier 2 model`.
|
|
289
|
-
|
|
290
|
-
Returns
|
|
291
|
-
-------
|
|
292
|
-
dict
|
|
293
|
-
A valid Hestia `Measurement` node, see: https://www.hestia.earth/schema/Measurement.
|
|
294
|
-
"""
|
|
295
|
-
measurement = _new_measurement(TERM_ID)
|
|
296
|
-
measurement["value"] = [value]
|
|
297
|
-
measurement["dates"] = [f"{year}-12-31"]
|
|
298
|
-
measurement["depthUpper"] = DEPTH_UPPER
|
|
299
|
-
measurement["depthLower"] = DEPTH_LOWER
|
|
300
|
-
measurement["methodClassification"] = method_classification
|
|
301
|
-
return measurement
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
# --- SHARED TIER 1 & TIER 2 ENUMS ---
|
|
305
|
-
|
|
306
|
-
class IpccManagementCategory(Enum):
|
|
307
|
-
"""
|
|
308
|
-
Enum representing IPCC Management Categories for grasslands and annual croplands.
|
|
309
|
-
|
|
310
|
-
See [IPCC (2019) Vol. 4, Ch. 5 and 6](https://www.ipcc-nggip.iges.or.jp/public/2019rf/vol4.html) for more
|
|
311
|
-
information.
|
|
312
|
-
"""
|
|
313
|
-
SEVERELY_DEGRADED = "severely degraded"
|
|
314
|
-
IMPROVED_GRASSLAND = "improved grassland"
|
|
315
|
-
HIGH_INTENSITY_GRAZING = "high-intensity grazing"
|
|
316
|
-
NOMINALLY_MANAGED = "nominally managed"
|
|
317
|
-
FULL_TILLAGE = "full tillage"
|
|
318
|
-
REDUCED_TILLAGE = "reduced tillage"
|
|
319
|
-
NO_TILLAGE = "no tillage"
|
|
320
|
-
OTHER = "other"
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
class _InventoryKey(Enum):
|
|
324
|
-
"""
|
|
325
|
-
Enum representing the inner keys of the annual inventory is constructed from site and cycle data.
|
|
326
|
-
"""
|
|
327
|
-
# Tier 1
|
|
328
|
-
LU_CATEGORY = 'ipcc land use category'
|
|
329
|
-
MG_CATEGORY = 'ipcc management category'
|
|
330
|
-
CI_CATEGORY = 'ipcc carbon input category'
|
|
331
|
-
SHOULD_RUN_TIER_1 = 'should run tier 1'
|
|
332
|
-
# Tier 2
|
|
333
|
-
TEMP_MONTHLY = 'temperature monthly'
|
|
334
|
-
PRECIP_MONTHLY = 'precipitation monthly'
|
|
335
|
-
PET_MONTHLY = 'PET monthly'
|
|
336
|
-
IRRIGATED_MONTHLY = 'irrigated monthly'
|
|
337
|
-
CARBON_INPUT = 'carbon input'
|
|
338
|
-
N_CONTENT = 'nitrogen content'
|
|
339
|
-
LIGNIN_CONTENT = 'lignin content'
|
|
340
|
-
TILLAGE_CATEGORY = 'ipcc tillage category'
|
|
341
|
-
SAND_CONTENT = 'sand content'
|
|
342
|
-
IS_PADDY_RICE = 'is paddy rice'
|
|
343
|
-
SHOULD_RUN_TIER_2 = 'should run tier 2'
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
REQUIRED_KEYS_TIER_1 = [
|
|
347
|
-
_InventoryKey.LU_CATEGORY,
|
|
348
|
-
_InventoryKey.MG_CATEGORY,
|
|
349
|
-
_InventoryKey.CI_CATEGORY
|
|
350
|
-
]
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
REQUIRED_KEYS_TIER_2 = [
|
|
354
|
-
_InventoryKey.TEMP_MONTHLY,
|
|
355
|
-
_InventoryKey.PRECIP_MONTHLY,
|
|
356
|
-
_InventoryKey.PET_MONTHLY,
|
|
357
|
-
_InventoryKey.CARBON_INPUT,
|
|
358
|
-
_InventoryKey.N_CONTENT,
|
|
359
|
-
_InventoryKey.LIGNIN_CONTENT,
|
|
360
|
-
_InventoryKey.TILLAGE_CATEGORY,
|
|
361
|
-
_InventoryKey.IS_PADDY_RICE
|
|
362
|
-
]
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
# --- TIER 1 ENUMS ---
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
class IpccSoilCategory(Enum):
|
|
369
|
-
"""
|
|
370
|
-
Enum representing IPCC Soil Categories.
|
|
371
|
-
|
|
372
|
-
See [IPCC (2019) Vol 4, Ch. 2 and 3](https://www.ipcc-nggip.iges.or.jp/public/2019rf/vol4.html) for more
|
|
373
|
-
information.
|
|
374
|
-
"""
|
|
375
|
-
ORGANIC_SOILS = "organic soils"
|
|
376
|
-
SANDY_SOILS = "sandy soils"
|
|
377
|
-
WETLAND_SOILS = "wetland soils"
|
|
378
|
-
VOLCANIC_SOILS = "volcanic soils"
|
|
379
|
-
SPODIC_SOILS = "spodic soils"
|
|
380
|
-
HIGH_ACTIVITY_CLAY_SOILS = "high-activity clay soils"
|
|
381
|
-
LOW_ACTIVITY_CLAY_SOILS = "low-activity clay soils"
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
class IpccLandUseCategory(Enum):
|
|
385
|
-
"""
|
|
386
|
-
Enum representing IPCC Land Use Categories.
|
|
387
|
-
|
|
388
|
-
See [IPCC (2019) Vol 4](https://www.ipcc-nggip.iges.or.jp/public/2019rf/vol4.html) for more information.
|
|
389
|
-
"""
|
|
390
|
-
GRASSLAND = "grassland"
|
|
391
|
-
PERENNIAL_CROPS = "perennial crops"
|
|
392
|
-
PADDY_RICE_CULTIVATION = "paddy rice cultivation"
|
|
393
|
-
ANNUAL_CROPS_WET = "annual crops (wet)"
|
|
394
|
-
ANNUAL_CROPS = "annual crops"
|
|
395
|
-
SET_ASIDE = "set aside"
|
|
396
|
-
FOREST = "forest"
|
|
397
|
-
NATIVE = "native"
|
|
398
|
-
OTHER = "other"
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
class IpccCarbonInputCategory(Enum):
|
|
402
|
-
"""
|
|
403
|
-
Enum representing IPCC Carbon Input Categories for improved grasslands and annual croplands.
|
|
404
|
-
|
|
405
|
-
See [IPCC (2019) Vol. 4, Ch. 4, 5 and 6](https://www.ipcc-nggip.iges.or.jp/public/2019rf/vol4.html) for more
|
|
406
|
-
information.
|
|
407
|
-
"""
|
|
408
|
-
GRASSLAND_HIGH = "grassland high"
|
|
409
|
-
GRASSLAND_MEDIUM = "grassland medium"
|
|
410
|
-
CROPLAND_HIGH_WITH_MANURE = "cropland high (with manure)"
|
|
411
|
-
CROPLAND_HIGH_WITHOUT_MANURE = "cropland high (without manure)"
|
|
412
|
-
CROPLAND_MEDIUM = "cropland medium"
|
|
413
|
-
CROPLAND_LOW = "cropland low"
|
|
414
|
-
OTHER = "other"
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
# --- TIER 2 NAMED TUPLES FOR CARBON SOURCES AND MODEL RESULTS ---
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
CarbonSource = NamedTuple(
|
|
421
|
-
"CarbonSource",
|
|
422
|
-
[
|
|
423
|
-
("mass", float),
|
|
424
|
-
("carbon_content", float),
|
|
425
|
-
("nitrogen_content", float),
|
|
426
|
-
("lignin_content", float),
|
|
427
|
-
]
|
|
428
|
-
)
|
|
429
|
-
"""
|
|
430
|
-
A single carbon source (e.g. crop residues or organic amendment).
|
|
431
|
-
|
|
432
|
-
Attributes
|
|
433
|
-
-----------
|
|
434
|
-
mass : float
|
|
435
|
-
The dry-matter mass of the carbon source, kg ha-1
|
|
436
|
-
carbon_content : float
|
|
437
|
-
The carbon content of the carbon source, decimal proportion, kg C (kg d.m.)-1.
|
|
438
|
-
nitrogen_content : float
|
|
439
|
-
The nitrogen content of the carbon source, decimal_proportion, kg N (kg d.m.)-1.
|
|
440
|
-
lignin_content : float
|
|
441
|
-
The lignin content of the carbon source, decimal_proportion, kg lignin (kg d.m.)-1.
|
|
442
|
-
"""
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
TemperatureFactorResult = NamedTuple(
|
|
446
|
-
"TemperatureFactorResult",
|
|
447
|
-
[
|
|
448
|
-
("timestamps", list[float]),
|
|
449
|
-
("annual_temperature_factors", list[float])
|
|
450
|
-
]
|
|
451
|
-
)
|
|
452
|
-
"""
|
|
453
|
-
A named tuple to hold the result of `_run_annual_temperature_factors`.
|
|
454
|
-
|
|
455
|
-
Attributes
|
|
456
|
-
----------
|
|
457
|
-
timestamps : list[int]
|
|
458
|
-
A list of integer timestamps (e.g. `[1995, 1996]`) for each year in the inventory.
|
|
459
|
-
annual_temperature_factors : list[float]
|
|
460
|
-
A list of annual temperature factors for each year in the inventory, dimensionless, between `0` and `1`.
|
|
461
|
-
"""
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
WaterFactorResult = NamedTuple(
|
|
465
|
-
"WaterFactorResult",
|
|
466
|
-
[
|
|
467
|
-
("timestamps", list[float]),
|
|
468
|
-
("annual_water_factors", list[float])
|
|
469
|
-
]
|
|
470
|
-
)
|
|
471
|
-
"""
|
|
472
|
-
A named tuple to hold the result of `_run_annual_water_factors`.
|
|
473
|
-
|
|
474
|
-
Attributes
|
|
475
|
-
----------
|
|
476
|
-
timestamps : list[int]
|
|
477
|
-
A list of integer timestamps (e.g. `[1995, 1996]`) for each year in the inventory.
|
|
478
|
-
annual_water_factors : list[float]
|
|
479
|
-
A list of annual water factors for each year in the inventory, dimensionless, between `0.31935` and `2.25`.
|
|
480
|
-
"""
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
Tier2SocResult = NamedTuple(
|
|
484
|
-
"Tier2SocResult",
|
|
485
|
-
[
|
|
486
|
-
("timestamps", list[float]),
|
|
487
|
-
("active_pool_soc_stocks", list[float]),
|
|
488
|
-
("slow_pool_soc_stocks", list[float]),
|
|
489
|
-
("passive_pool_soc_stocks", list[float]),
|
|
490
|
-
]
|
|
491
|
-
)
|
|
492
|
-
"""
|
|
493
|
-
A named tuple to hold the result of `_run_soc_stocks`.
|
|
494
|
-
|
|
495
|
-
Attributes
|
|
496
|
-
----------
|
|
497
|
-
timestamps : list[int]
|
|
498
|
-
A list of integer timestamps (e.g. `[1995, 1996]`) for each year in the inventory.
|
|
499
|
-
active_pool_soc_stocks : list[float]
|
|
500
|
-
The active sub-pool SOC stock for each year in the inventory, kg C ha-1.
|
|
501
|
-
slow_pool_soc_stocks : list[float]
|
|
502
|
-
The slow sub-pool SOC stock for each year in the inventory, kg C ha-1.
|
|
503
|
-
passive_pool_soc_stocks : list[float]
|
|
504
|
-
The passive sub-pool SOC stock for each year in the inventory, kg C ha-1.
|
|
505
|
-
"""
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
# --- TIER 1 NAMED TUPLES FOR STOCK CHANGE FACTORS AND MODEL RESULTS ---
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
StockChangeFactors = NamedTuple("StockChangeFactors", [
|
|
512
|
-
("land_use_factor", float),
|
|
513
|
-
("management_factor", float),
|
|
514
|
-
("carbon_input_factor", float)
|
|
515
|
-
])
|
|
516
|
-
"""
|
|
517
|
-
A named tuple to hold the 3 stock change factors retrieved by the model for each year in the inventory.
|
|
518
|
-
|
|
519
|
-
Attributes
|
|
520
|
-
----------
|
|
521
|
-
land_use_factor : float
|
|
522
|
-
The stock change factor for mineral soil organic C land-use systems or sub-systems for a particular land-use,
|
|
523
|
-
dimensionless.
|
|
524
|
-
management_factor : float
|
|
525
|
-
The stock change factor for mineral soil organic C for management regime, dimensionless.
|
|
526
|
-
carbon_input_factor : float
|
|
527
|
-
The stock change factor for mineral soil organic C for the input of organic amendments, dimensionless.
|
|
528
|
-
"""
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
# --- SHARED TIER 1 & TIER 2 MAPPING DICTS ---
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
IPCC_MANAGEMENT_CATEGORY_TO_TILLAGE_MANAGEMENT_LOOKUP_VALUE = {
|
|
535
|
-
IpccManagementCategory.FULL_TILLAGE: "Full tillage",
|
|
536
|
-
IpccManagementCategory.REDUCED_TILLAGE: "Reduced tillage",
|
|
537
|
-
IpccManagementCategory.NO_TILLAGE: "No tillage"
|
|
538
|
-
}
|
|
539
|
-
"""
|
|
540
|
-
A dictionary mapping IPCC management categories to corresponding tillage lookup values in the
|
|
541
|
-
`"IPCC_TILLAGE_MANAGEMENT_CATEGORY" column`.
|
|
542
|
-
"""
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
# --- TIER 1 MAPPING DICTS ---
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
IPCC_CATEGORY_TO_ECO_CLIMATE_ZONE_LOOKUP_COLUMN = {
|
|
549
|
-
# IpccSoilCategory
|
|
550
|
-
IpccSoilCategory.SANDY_SOILS: LOOKUPS["ecoClimateZone"][0],
|
|
551
|
-
IpccSoilCategory.WETLAND_SOILS: LOOKUPS["ecoClimateZone"][1],
|
|
552
|
-
IpccSoilCategory.VOLCANIC_SOILS: LOOKUPS["ecoClimateZone"][2],
|
|
553
|
-
IpccSoilCategory.SPODIC_SOILS: LOOKUPS["ecoClimateZone"][3],
|
|
554
|
-
IpccSoilCategory.HIGH_ACTIVITY_CLAY_SOILS: LOOKUPS["ecoClimateZone"][4],
|
|
555
|
-
IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS: LOOKUPS["ecoClimateZone"][5],
|
|
556
|
-
# IpccLandUseCategory
|
|
557
|
-
IpccLandUseCategory.GRASSLAND: LOOKUPS["ecoClimateZone"][6],
|
|
558
|
-
IpccLandUseCategory.PERENNIAL_CROPS: LOOKUPS["ecoClimateZone"][7],
|
|
559
|
-
IpccLandUseCategory.PADDY_RICE_CULTIVATION: LOOKUPS["ecoClimateZone"][8],
|
|
560
|
-
IpccLandUseCategory.ANNUAL_CROPS_WET: LOOKUPS["ecoClimateZone"][9],
|
|
561
|
-
IpccLandUseCategory.ANNUAL_CROPS: LOOKUPS["ecoClimateZone"][10],
|
|
562
|
-
IpccLandUseCategory.SET_ASIDE: LOOKUPS["ecoClimateZone"][11],
|
|
563
|
-
# IpccManagementCategory
|
|
564
|
-
IpccManagementCategory.SEVERELY_DEGRADED: LOOKUPS["ecoClimateZone"][12],
|
|
565
|
-
IpccManagementCategory.IMPROVED_GRASSLAND: LOOKUPS["ecoClimateZone"][13],
|
|
566
|
-
IpccManagementCategory.HIGH_INTENSITY_GRAZING: LOOKUPS["ecoClimateZone"][14],
|
|
567
|
-
IpccManagementCategory.NOMINALLY_MANAGED: LOOKUPS["ecoClimateZone"][15],
|
|
568
|
-
IpccManagementCategory.FULL_TILLAGE: LOOKUPS["ecoClimateZone"][16],
|
|
569
|
-
IpccManagementCategory.REDUCED_TILLAGE: LOOKUPS["ecoClimateZone"][17],
|
|
570
|
-
IpccManagementCategory.NO_TILLAGE: LOOKUPS["ecoClimateZone"][18],
|
|
571
|
-
# IpccCarbonInputCategory
|
|
572
|
-
IpccCarbonInputCategory.GRASSLAND_HIGH: LOOKUPS["ecoClimateZone"][19],
|
|
573
|
-
IpccCarbonInputCategory.GRASSLAND_MEDIUM: LOOKUPS["ecoClimateZone"][20],
|
|
574
|
-
IpccCarbonInputCategory.CROPLAND_HIGH_WITH_MANURE: LOOKUPS["ecoClimateZone"][21],
|
|
575
|
-
IpccCarbonInputCategory.CROPLAND_HIGH_WITHOUT_MANURE: LOOKUPS["ecoClimateZone"][22],
|
|
576
|
-
IpccCarbonInputCategory.CROPLAND_MEDIUM: LOOKUPS["ecoClimateZone"][23],
|
|
577
|
-
IpccCarbonInputCategory.CROPLAND_LOW: LOOKUPS["ecoClimateZone"][24]
|
|
578
|
-
}
|
|
579
|
-
"""
|
|
580
|
-
A dictionary mapping IPCC category enums to their corresponding eco-climate zone lookup columns.
|
|
581
|
-
"""
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
def _get_eco_climate_zone_lookup_column(
|
|
585
|
-
ipcc_category: Union[
|
|
586
|
-
IpccSoilCategory,
|
|
587
|
-
IpccLandUseCategory,
|
|
588
|
-
IpccManagementCategory,
|
|
589
|
-
IpccCarbonInputCategory
|
|
590
|
-
]
|
|
591
|
-
) -> Optional[str]:
|
|
592
|
-
"""
|
|
593
|
-
Retrieve the corresponding eco-climate zone lookup column for the given IPCC category.
|
|
594
|
-
|
|
595
|
-
Parameters
|
|
596
|
-
----------
|
|
597
|
-
ipcc_category : IpccSoilCategory | IpccLandUseCategory | IpccManagementCategory | IpccCarbonInputCategory
|
|
598
|
-
The IPCC category for which the eco-climate zone lookup column is needed.
|
|
599
|
-
|
|
600
|
-
Returns
|
|
601
|
-
-------
|
|
602
|
-
str | None
|
|
603
|
-
The eco-climate zone lookup column associated with the provided
|
|
604
|
-
IPCC category, or None if no mapping is found.
|
|
605
|
-
"""
|
|
606
|
-
return IPCC_CATEGORY_TO_ECO_CLIMATE_ZONE_LOOKUP_COLUMN.get(ipcc_category, None)
|
|
607
|
-
|
|
123
|
+
_METHOD_TIERS = [tier_2, tier_1]
|
|
608
124
|
|
|
609
|
-
IPCC_SOIL_CATEGORY_TO_SOIL_TYPE_LOOKUP_VALUE = {
|
|
610
|
-
IpccSoilCategory.ORGANIC_SOILS: "Organic soils",
|
|
611
|
-
IpccSoilCategory.SANDY_SOILS: "Sandy soils",
|
|
612
|
-
IpccSoilCategory.WETLAND_SOILS: "Wetland soils",
|
|
613
|
-
IpccSoilCategory.VOLCANIC_SOILS: "Volcanic soils",
|
|
614
|
-
IpccSoilCategory.SPODIC_SOILS: "Spodic soils",
|
|
615
|
-
IpccSoilCategory.HIGH_ACTIVITY_CLAY_SOILS: "High-activity clay soils",
|
|
616
|
-
IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS: "Low-activity clay soils",
|
|
617
|
-
}
|
|
618
|
-
"""
|
|
619
|
-
A dictionary mapping IPCC soil categories to corresponding soil type and USDA soil type lookup values in the
|
|
620
|
-
`"IPCC_SOIL_CATEGORY"` column.
|
|
621
|
-
"""
|
|
622
|
-
|
|
623
|
-
SITE_TYPE_TO_IPCC_LAND_USE_CATEGORY = {
|
|
624
|
-
SiteSiteType.PERMANENT_PASTURE.value: IpccLandUseCategory.GRASSLAND,
|
|
625
|
-
SiteSiteType.FOREST.value: IpccLandUseCategory.FOREST,
|
|
626
|
-
SiteSiteType.OTHER_NATURAL_VEGETATION.value: IpccLandUseCategory.NATIVE
|
|
627
|
-
}
|
|
628
|
-
"""
|
|
629
|
-
A dictionary mapping site types to corresponding IPCC land use categories.
|
|
630
|
-
"""
|
|
631
|
-
|
|
632
|
-
IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE = {
|
|
633
|
-
IpccLandUseCategory.GRASSLAND: "Grassland",
|
|
634
|
-
IpccLandUseCategory.PERENNIAL_CROPS: "Perennial crops",
|
|
635
|
-
IpccLandUseCategory.PADDY_RICE_CULTIVATION: "Paddy rice cultivation",
|
|
636
|
-
IpccLandUseCategory.ANNUAL_CROPS_WET: "Annual crops",
|
|
637
|
-
IpccLandUseCategory.ANNUAL_CROPS: "Annual crops",
|
|
638
|
-
IpccLandUseCategory.SET_ASIDE: [
|
|
639
|
-
"Annual crops", "Paddy rice cultivation", "Perennial crops", "Set aside"
|
|
640
|
-
],
|
|
641
|
-
IpccLandUseCategory.FOREST: "Forest",
|
|
642
|
-
IpccLandUseCategory.NATIVE: "Native"
|
|
643
|
-
}
|
|
644
|
-
"""
|
|
645
|
-
A dictionary mapping IPCC land use categories to corresponding land cover lookup values in the
|
|
646
|
-
`"IPCC_LAND_USE_CATEGORY"` column.
|
|
647
|
-
"""
|
|
648
|
-
|
|
649
|
-
IPCC_MANAGEMENT_CATEGORY_TO_GRASSLAND_MANAGEMENT_TERM_ID = {
|
|
650
|
-
IpccManagementCategory.SEVERELY_DEGRADED: "severelyDegradedPasture",
|
|
651
|
-
IpccManagementCategory.IMPROVED_GRASSLAND: "improvedPasture",
|
|
652
|
-
IpccManagementCategory.HIGH_INTENSITY_GRAZING: "highIntensityGrazingPasture",
|
|
653
|
-
IpccManagementCategory.NOMINALLY_MANAGED: "nominallyManagedPasture",
|
|
654
|
-
IpccManagementCategory.OTHER: "nativePasture"
|
|
655
|
-
}
|
|
656
|
-
"""
|
|
657
|
-
A dictionary mapping IPCC management categories to corresponding grassland management term IDs from the land cover
|
|
658
|
-
glossary.
|
|
659
|
-
"""
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
# --- TIER 2 FUNCTIONS: ASSIGN TILLAGE CATEGORY TO CYCLES ---
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
def _check_zero_tillages(practices: list[dict]) -> bool:
|
|
666
|
-
"""
|
|
667
|
-
Checks whether a list of `Practice`s nodes describe 0 total tillages, or not.
|
|
668
|
-
|
|
669
|
-
Parameters
|
|
670
|
-
----------
|
|
671
|
-
practices : list[dict]
|
|
672
|
-
A list of Hestia `Practice` nodes, see: https://www.hestia.earth/schema/Practice.
|
|
673
|
-
|
|
674
|
-
Returns
|
|
675
|
-
-------
|
|
676
|
-
bool
|
|
677
|
-
Whether or not 0 tillages counted.
|
|
678
|
-
"""
|
|
679
|
-
practice = find_term_match(practices, NUMBER_OF_TILLAGES_TERM_ID)
|
|
680
|
-
nTillages = list_sum(practice.get("value", []))
|
|
681
|
-
return nTillages <= 0
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
def _check_cycle_tillage_management_category(
|
|
685
|
-
cycle: dict,
|
|
686
|
-
key: IpccManagementCategory
|
|
687
|
-
) -> bool:
|
|
688
|
-
"""
|
|
689
|
-
Checks whether a Hesita `Cycle` node meets the requirements of a specific tillage `IpccManagementCategory`.
|
|
690
|
-
|
|
691
|
-
Parameters
|
|
692
|
-
----------
|
|
693
|
-
cycle : dict
|
|
694
|
-
A Hestia `Cycle` node, see: https://www.hestia.earth/schema/Cycle.
|
|
695
|
-
key : IpccManagementCategory
|
|
696
|
-
The `IpccManagementCategory` to match.
|
|
697
|
-
|
|
698
|
-
Returns
|
|
699
|
-
-------
|
|
700
|
-
bool
|
|
701
|
-
Whether or not the cycle meets the requirements for the category.
|
|
702
|
-
"""
|
|
703
|
-
LOOKUP = LOOKUPS["tillage"]
|
|
704
|
-
target_lookup_values = IPCC_MANAGEMENT_CATEGORY_TO_TILLAGE_MANAGEMENT_LOOKUP_VALUE.get(key, None)
|
|
705
|
-
|
|
706
|
-
practices = cycle.get("practices", [])
|
|
707
|
-
tillage_nodes = filter_list_term_type(
|
|
708
|
-
practices, [TermTermType.TILLAGE]
|
|
709
|
-
)
|
|
710
125
|
|
|
711
|
-
|
|
712
|
-
tillage_nodes,
|
|
713
|
-
lookup=LOOKUP,
|
|
714
|
-
target_lookup_values=target_lookup_values,
|
|
715
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
716
|
-
) and (
|
|
717
|
-
key is not IpccManagementCategory.NO_TILLAGE
|
|
718
|
-
or _check_zero_tillages(tillage_nodes)
|
|
719
|
-
)
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
TIER_2_TILLAGE_MANAGEMENT_CATEGORY_DECISION_TREE = {
|
|
723
|
-
IpccManagementCategory.FULL_TILLAGE: (
|
|
724
|
-
lambda cycles, key: any(
|
|
725
|
-
_check_cycle_tillage_management_category(cycle, key) for cycle in cycles
|
|
726
|
-
)
|
|
727
|
-
),
|
|
728
|
-
IpccManagementCategory.REDUCED_TILLAGE: (
|
|
729
|
-
lambda cycles, key: any(
|
|
730
|
-
_check_cycle_tillage_management_category(cycle, key) for cycle in cycles
|
|
731
|
-
)
|
|
732
|
-
),
|
|
733
|
-
IpccManagementCategory.NO_TILLAGE: (
|
|
734
|
-
lambda cycles, key: any(
|
|
735
|
-
_check_cycle_tillage_management_category(cycle, key) for cycle in cycles
|
|
736
|
-
)
|
|
737
|
-
)
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
def _assign_tier_2_ipcc_tillage_management_category(
|
|
742
|
-
cycles: list[dict],
|
|
743
|
-
default: IpccManagementCategory = IpccManagementCategory.OTHER
|
|
744
|
-
) -> IpccManagementCategory:
|
|
126
|
+
def run(site: dict) -> list[dict]:
|
|
745
127
|
"""
|
|
746
|
-
|
|
128
|
+
Run both tiers of the IPCC (2019) SOC model.
|
|
747
129
|
|
|
748
130
|
Parameters
|
|
749
131
|
----------
|
|
750
|
-
|
|
751
|
-
A
|
|
132
|
+
site : dict
|
|
133
|
+
A Hestia `Site` node, see: https://www.hestia.earth/schema/Site.
|
|
752
134
|
|
|
753
135
|
Returns
|
|
754
136
|
-------
|
|
755
|
-
|
|
137
|
+
list[dict]
|
|
138
|
+
A list of Hestia `Measurement` nodes containing the calculated SOC stocks and additional relevant data.
|
|
756
139
|
"""
|
|
140
|
+
should_run, run_data = _should_run(site)
|
|
141
|
+
_log_data(site, should_run, run_data)
|
|
757
142
|
return next(
|
|
758
143
|
(
|
|
759
|
-
|
|
760
|
-
if
|
|
144
|
+
_run_method(method, **run_data[method]) for method in run_data.keys()
|
|
145
|
+
if run_data[method]["should_run"]
|
|
761
146
|
),
|
|
762
|
-
|
|
763
|
-
) if
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
# --- TIER 2 FUNCTIONS: ANNUAL TEMPERATURE FACTOR FROM MONTHLY TEMPERATURE DATA ---
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
def _calc_temperature_factor(
|
|
770
|
-
average_temperature: float,
|
|
771
|
-
maximum_temperature: float = 45.0,
|
|
772
|
-
optimum_temperature: float = 33.69,
|
|
773
|
-
) -> float:
|
|
774
|
-
"""
|
|
775
|
-
Equation 5.0E, part 2. Calculate the temperature effect on decomposition in mineral soils for a single month using
|
|
776
|
-
the Steady-State Method.
|
|
777
|
-
|
|
778
|
-
If `average_temperature >= maximum_temperature` the function should always return 0.
|
|
779
|
-
|
|
780
|
-
Parameters
|
|
781
|
-
----------
|
|
782
|
-
average_temperature : float
|
|
783
|
-
The average air temperature of a given month, degrees C.
|
|
784
|
-
maximum_temperature : float
|
|
785
|
-
The maximum air temperature for decomposition, degrees C, default value: `45.0`.
|
|
786
|
-
optimum_temperature : float
|
|
787
|
-
The optimum air temperature for decomposition, degrees C, default value: `33.69`.
|
|
788
|
-
|
|
789
|
-
Returns
|
|
790
|
-
-------
|
|
791
|
-
float
|
|
792
|
-
The air temperature effect on decomposition for a given month, dimensionless, between `0` and `1`.
|
|
793
|
-
"""
|
|
794
|
-
prelim = (maximum_temperature - average_temperature) / (
|
|
795
|
-
maximum_temperature - optimum_temperature
|
|
796
|
-
)
|
|
797
|
-
return 0 if average_temperature >= maximum_temperature else (
|
|
798
|
-
pow(prelim, 0.2) * exp((0.2 / 2.63) * (1 - pow(prelim, 2.63)))
|
|
799
|
-
)
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
def _calc_annual_temperature_factor(
|
|
803
|
-
average_temperature_monthly: list[float],
|
|
804
|
-
maximum_temperature: float = 45.0,
|
|
805
|
-
optimum_temperature: float = 33.69,
|
|
806
|
-
) -> Union[float, None]:
|
|
807
|
-
"""
|
|
808
|
-
Equation 5.0E, part 1. Calculate the average annual temperature effect on decomposition in mineral soils using the
|
|
809
|
-
Steady-State Method.
|
|
810
|
-
|
|
811
|
-
Parameters
|
|
812
|
-
----------
|
|
813
|
-
average_temperature_monthly : list[float]
|
|
814
|
-
A list of monthly average air temperatures in degrees C, must have a length of 12.
|
|
815
|
-
|
|
816
|
-
Returns
|
|
817
|
-
-------
|
|
818
|
-
float | None
|
|
819
|
-
Average annual temperature factor, dimensionless, between `0` and `1`, or `None` if the input list is empty.
|
|
820
|
-
"""
|
|
821
|
-
return mean(
|
|
822
|
-
list(
|
|
823
|
-
_calc_temperature_factor(t, maximum_temperature, optimum_temperature)
|
|
824
|
-
for t in average_temperature_monthly
|
|
825
|
-
)
|
|
826
|
-
) if average_temperature_monthly else None
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
# --- TIER 2 FUNCTIONS: ANNUAL WATER FACTOR FROM MONTHLY PRECIPITATION, PET AND IRRIGATION DATA ---
|
|
830
|
-
|
|
147
|
+
list()
|
|
148
|
+
) if should_run else []
|
|
831
149
|
|
|
832
|
-
def _calc_water_factor(
|
|
833
|
-
precipitation: float,
|
|
834
|
-
pet: float,
|
|
835
|
-
is_irrigated: bool = False,
|
|
836
|
-
water_factor_slope: float = 1.331,
|
|
837
|
-
) -> float:
|
|
838
|
-
"""
|
|
839
|
-
Equation 5.0F, part 2. Calculate the water effect on decomposition in mineral soils for a single month using the
|
|
840
|
-
Steady-State Method.
|
|
841
150
|
|
|
842
|
-
|
|
151
|
+
def _should_run(site: dict) -> tuple[bool, dict[ModuleType, dict]]:
|
|
152
|
+
# List of tuples `(should_run, inventory, kwargs, logs)` for each method tier.
|
|
153
|
+
INNER_KEYS = ("should_run", "inventory", "kwargs", "logs")
|
|
154
|
+
run_data = {
|
|
155
|
+
method: {
|
|
156
|
+
key: value for key, value in zip(INNER_KEYS, method.should_run(site))
|
|
157
|
+
} for method in _METHOD_TIERS
|
|
158
|
+
}
|
|
159
|
+
should_run = any(data["should_run"] for data in run_data.values())
|
|
160
|
+
return should_run, run_data
|
|
843
161
|
|
|
844
|
-
Parameters
|
|
845
|
-
----------
|
|
846
|
-
precipitation : float
|
|
847
|
-
The sum total precipitation of a given month, mm.
|
|
848
|
-
pet : float
|
|
849
|
-
The sum total potential evapotranspiration in a given month, mm.
|
|
850
|
-
is_irrigated : bool
|
|
851
|
-
Whether or not irrigation has been used in a given month.
|
|
852
|
-
water_factor_slope : float
|
|
853
|
-
The slope for mappet term to estimate water factor, dimensionless, default value: `1.331`.
|
|
854
162
|
|
|
855
|
-
|
|
856
|
-
-------
|
|
857
|
-
float
|
|
858
|
-
The water effect on decomposition for a given month, dimensionless, between `0.2129` and `1.5`.
|
|
163
|
+
def _log_data(site: dict, should_run: bool, run_data: dict[ModuleType, dict]) -> None:
|
|
859
164
|
"""
|
|
860
|
-
|
|
861
|
-
return 0.775 if is_irrigated else 0.2129 + (water_factor_slope * (mappet)) - (0.2413 * pow(mappet, 2))
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
def _calc_annual_water_factor(
|
|
865
|
-
precipitation_monthly: list[float],
|
|
866
|
-
pet_monthly: list[float],
|
|
867
|
-
is_irrigated_monthly: Union[list[bool], None] = None,
|
|
868
|
-
water_factor_slope: float = 1.331,
|
|
869
|
-
) -> Union[float, None]:
|
|
165
|
+
Format and log the inventory, kwargs and any other requirement data for all tier methodologies of the model.
|
|
870
166
|
"""
|
|
871
|
-
|
|
872
|
-
|
|
167
|
+
inventory = reduce(merge, [data["inventory"] for data in run_data.values()], dict())
|
|
168
|
+
kwargs = reduce(merge, [data["kwargs"] for data in run_data.values()], dict())
|
|
169
|
+
logs = reduce(merge, [data["logs"] for data in run_data.values()], dict())
|
|
873
170
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
A list of true/false values that describe whether irrigation has been used in each calendar month, must have a
|
|
882
|
-
length of 12. If `None` is provided, a list of 12 `False` values is used.
|
|
883
|
-
water_factor_slope : float
|
|
884
|
-
The slope for mappet term to estimate water factor, dimensionless, default value: `1.331`.
|
|
885
|
-
|
|
886
|
-
Returns
|
|
887
|
-
-------
|
|
888
|
-
float | None
|
|
889
|
-
Average annual water factor multiplied by `1.5`, dimensionless, between `0.31935` and `2.25`,
|
|
890
|
-
or `None` if any of the input lists are empty.
|
|
891
|
-
"""
|
|
892
|
-
is_irrigated_monthly = (
|
|
893
|
-
[False] * 12 if is_irrigated_monthly is None else is_irrigated_monthly
|
|
171
|
+
logRequirements(
|
|
172
|
+
site,
|
|
173
|
+
model=MODEL,
|
|
174
|
+
term=TERM_ID,
|
|
175
|
+
**logs,
|
|
176
|
+
**kwargs,
|
|
177
|
+
inventory=_format_inventory(inventory)
|
|
894
178
|
)
|
|
895
|
-
|
|
896
|
-
return 1.5 * mean(list(
|
|
897
|
-
_calc_water_factor(precipitation, pet, is_irrigated, water_factor_slope)
|
|
898
|
-
for precipitation, pet, is_irrigated in zipped
|
|
899
|
-
)) if all([precipitation_monthly, pet_monthly]) else None
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
# --- TIER 2 FUNCTIONS: ANNUAL TOTAL ORGANIC C INPUT TO SOIL, N CONTENT AND LIGNIN CONTENT FROM CARBON SOURCES ---
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
def _calc_total_organic_carbon_input(
|
|
906
|
-
carbon_sources: list[CarbonSource], default_carbon_content=0.42
|
|
907
|
-
) -> float:
|
|
908
|
-
"""
|
|
909
|
-
Equation 5.0H part 1. Calculate the total organic carbon to a site from all carbon sources (above-ground and
|
|
910
|
-
below-ground crop residues, organic amendments, etc.).
|
|
179
|
+
logShouldRun(site, MODEL, TERM_ID, should_run)
|
|
911
180
|
|
|
912
|
-
Parameters
|
|
913
|
-
----------
|
|
914
|
-
carbon_sources : list[CarbonSource])
|
|
915
|
-
A list of carbon sources as named tuples with the format
|
|
916
|
-
`(mass: float, carbon_content: float, nitrogen_content: float, lignin_content: float)`.
|
|
917
|
-
default_carbon_content : float
|
|
918
|
-
The default carbon content of a carbon source, decimal proportion, kg C (kg d.m.)-1.
|
|
919
181
|
|
|
920
|
-
|
|
921
|
-
-------
|
|
922
|
-
float
|
|
923
|
-
The total mass of organic carbon inputted into the site, kg C ha-1.
|
|
182
|
+
def _format_inventory(inventory: dict) -> str:
|
|
924
183
|
"""
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
def _calc_average_nitrogen_content_of_organic_carbon_sources(
|
|
929
|
-
carbon_sources: list[CarbonSource], default_nitrogen_content=0.0085
|
|
930
|
-
) -> float:
|
|
184
|
+
Format the inventory as a table.
|
|
931
185
|
"""
|
|
932
|
-
|
|
186
|
+
inventory_keys = _get_unique_inventory_keys(inventory)
|
|
187
|
+
return log_as_table(
|
|
188
|
+
{
|
|
189
|
+
"year": year,
|
|
190
|
+
**{
|
|
191
|
+
key.value: _INVENTORY_KEY_TO_FORMAT_FUNC[key](group.get(key))
|
|
192
|
+
for key in inventory_keys
|
|
193
|
+
}
|
|
194
|
+
} for year, group in inventory.items()
|
|
195
|
+
) if inventory else "None"
|
|
933
196
|
|
|
934
|
-
Parameters
|
|
935
|
-
----------
|
|
936
|
-
carbon_sources : list[CarbonSource]
|
|
937
|
-
A list of carbon sources as named tuples with the format
|
|
938
|
-
`(mass: float, carbon_content: float, nitrogen_content: float, lignin_content: float)`
|
|
939
|
-
default_nitrogen_content : float
|
|
940
|
-
The default nitrogen content of a carbon source, decimal proportion, kg N (kg d.m.)-1.
|
|
941
197
|
|
|
942
|
-
|
|
943
|
-
-------
|
|
944
|
-
float
|
|
945
|
-
The average nitrogen content of the carbon sources, decimal_proportion, kg N (kg d.m.)-1.
|
|
198
|
+
def _get_unique_inventory_keys(inventory: dict) -> list:
|
|
946
199
|
"""
|
|
947
|
-
|
|
948
|
-
weighted_values = [
|
|
949
|
-
c.mass * (c.nitrogen_content if c.nitrogen_content else default_nitrogen_content) for c in carbon_sources
|
|
950
|
-
]
|
|
951
|
-
should_run = total_weight > 0
|
|
952
|
-
return sum(weighted_values) / total_weight if should_run else 0
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
def _calc_average_lignin_content_of_organic_carbon_sources(
|
|
956
|
-
carbon_sources: list[dict[str, float]], default_lignin_content=0.073
|
|
957
|
-
) -> float:
|
|
200
|
+
Return a list of unique inventory keys in a fixed order.
|
|
958
201
|
"""
|
|
959
|
-
|
|
202
|
+
unique_keys = reduce(
|
|
203
|
+
lambda result, keys: result | set(keys),
|
|
204
|
+
(
|
|
205
|
+
(key for key in group.keys() if key in _INVENTORY_KEY_TO_FORMAT_FUNC)
|
|
206
|
+
for group in inventory.values()
|
|
207
|
+
),
|
|
208
|
+
set()
|
|
209
|
+
)
|
|
210
|
+
key_order = {key: i for i, key in enumerate(_INVENTORY_KEY_TO_FORMAT_FUNC.keys())}
|
|
211
|
+
return sorted(unique_keys, key=lambda key_: key_order[key_])
|
|
960
212
|
|
|
961
|
-
Parameters
|
|
962
|
-
----------
|
|
963
|
-
carbon_sources : list[CarbonSource]
|
|
964
|
-
A list of carbon sources as named tuples with the format
|
|
965
|
-
`(mass: float, carbon_content: float, nitrogen_content: float, lignin_content: float)`
|
|
966
|
-
default_lignin_content : float
|
|
967
|
-
The default lignin content of a carbon source, decimal proportion, kg lignin (kg d.m.)-1.
|
|
968
213
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
214
|
+
_INVENTORY_KEY_TO_FORMAT_FUNC = {
|
|
215
|
+
tier_2._InventoryKey.SHOULD_RUN: format_bool,
|
|
216
|
+
tier_2._InventoryKey.TEMP_MONTHLY: format_number_list,
|
|
217
|
+
tier_2._InventoryKey.PRECIP_MONTHLY: format_number_list,
|
|
218
|
+
tier_2._InventoryKey.PET_MONTHLY: format_number_list,
|
|
219
|
+
tier_2._InventoryKey.IRRIGATED_MONTHLY: format_bool_list,
|
|
220
|
+
tier_2._InventoryKey.SAND_CONTENT: format_number,
|
|
221
|
+
tier_2._InventoryKey.CARBON_INPUT: format_number,
|
|
222
|
+
tier_2._InventoryKey.N_CONTENT: format_number,
|
|
223
|
+
tier_2._InventoryKey.LIGNIN_CONTENT: format_number,
|
|
224
|
+
tier_2._InventoryKey.TILLAGE_CATEGORY: format_enum,
|
|
225
|
+
tier_2._InventoryKey.IS_PADDY_RICE: format_bool,
|
|
226
|
+
tier_1._InventoryKey.SHOULD_RUN: format_bool,
|
|
227
|
+
tier_1._InventoryKey.LU_CATEGORY: format_enum,
|
|
228
|
+
tier_1._InventoryKey.MG_CATEGORY: format_enum,
|
|
229
|
+
tier_1._InventoryKey.CI_CATEGORY: format_enum,
|
|
230
|
+
}
|
|
231
|
+
"""
|
|
232
|
+
Map inventory keys to format functions. The columns in inventory logged as a table will also be sorted in the order of
|
|
233
|
+
the `dict` keys.
|
|
234
|
+
"""
|
|
980
235
|
|
|
981
236
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
def _calc_beta(
|
|
986
|
-
carbon_input: float,
|
|
987
|
-
lignin_content: float = 0.073,
|
|
988
|
-
nitrogen_content: float = 0.0083,
|
|
989
|
-
) -> float:
|
|
990
|
-
"""
|
|
991
|
-
Equation 5.0G, part 2. Calculate the C input to the metabolic dead organic matter C component, kg C ha-1.
|
|
992
|
-
|
|
993
|
-
See table 5.5b for default values for lignin content and nitrogen content.
|
|
994
|
-
|
|
995
|
-
Parameters
|
|
996
|
-
----------
|
|
997
|
-
carbon_input : float
|
|
998
|
-
Total carbon input to the soil during an inventory year, kg C ha-1.
|
|
999
|
-
lignin_content : float
|
|
1000
|
-
The average lignin content of carbon input sources, decimal proportion, default value: `0.073`.
|
|
1001
|
-
nitrogen_content : float
|
|
1002
|
-
The average nitrogen content of carbon sources, decimal proportion, default value: `0.0083`.
|
|
1003
|
-
|
|
1004
|
-
Returns
|
|
1005
|
-
-------
|
|
1006
|
-
float
|
|
1007
|
-
The C input to the metabolic dead organic matter C component, kg C ha-1.
|
|
1008
|
-
"""
|
|
1009
|
-
return carbon_input * (0.85 - 0.018 * (lignin_content / nitrogen_content))
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
def _get_f_2(
|
|
1013
|
-
tillage_management_category: IpccManagementCategory = IpccManagementCategory.OTHER,
|
|
1014
|
-
f_2_full_tillage: float = 0.455,
|
|
1015
|
-
f_2_reduced_tillage: float = 0.477,
|
|
1016
|
-
f_2_no_tillage: float = 0.5,
|
|
1017
|
-
f_2_unknown_tillage: float = 0.368,
|
|
1018
|
-
) -> float:
|
|
1019
|
-
"""
|
|
1020
|
-
Get the value of `f_2` (the stabilisation efficiencies for structural decay products entering the active pool)
|
|
1021
|
-
based on the tillage `IpccManagementCategory`.
|
|
1022
|
-
|
|
1023
|
-
If tillage regime is unknown, `IpccManagementCategory.OTHER` should be assumed.
|
|
1024
|
-
|
|
1025
|
-
Parameters
|
|
1026
|
-
----------
|
|
1027
|
-
tillage_management_category : (IpccManagementCategory)
|
|
1028
|
-
The tillage category of the inventory year, default value: `IpccManagementCategory.OTHER`.
|
|
1029
|
-
f_2_full_tillage : float
|
|
1030
|
-
The stabilisation efficiencies for structural decay products entering the active pool under full tillage,
|
|
1031
|
-
decimal proportion, default value: `0.455`.
|
|
1032
|
-
f_2_reduced_tillage : float
|
|
1033
|
-
The stabilisation efficiencies for structural decay products entering the active pool under reduced tillage,
|
|
1034
|
-
decimal proportion, default value: `0.477`.
|
|
1035
|
-
f_2_no_tillage : float
|
|
1036
|
-
The stabilisation efficiencies for structural decay products entering the active pool under no tillage,
|
|
1037
|
-
decimal proportion, default value: `0.5`.
|
|
1038
|
-
f_2_unknown_tillage : float
|
|
1039
|
-
The stabilisation efficiencies for structural decay products entering the active pool if tillage is not known,
|
|
1040
|
-
decimal proportion, default value: `0.368`.
|
|
1041
|
-
|
|
1042
|
-
Returns
|
|
1043
|
-
-------
|
|
1044
|
-
float: The stabilisation efficiencies for structural decay products entering the active pool,
|
|
1045
|
-
decimal proportion.
|
|
1046
|
-
"""
|
|
1047
|
-
ipcc_tillage_management_category_to_f_2s = {
|
|
1048
|
-
IpccManagementCategory.FULL_TILLAGE: f_2_full_tillage,
|
|
1049
|
-
IpccManagementCategory.REDUCED_TILLAGE: f_2_reduced_tillage,
|
|
1050
|
-
IpccManagementCategory.NO_TILLAGE: f_2_no_tillage,
|
|
1051
|
-
IpccManagementCategory.OTHER: f_2_unknown_tillage
|
|
1052
|
-
}
|
|
1053
|
-
default = f_2_unknown_tillage
|
|
1054
|
-
|
|
1055
|
-
return ipcc_tillage_management_category_to_f_2s.get(tillage_management_category, default)
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
def _calc_f_4(sand_content: float = 0.33, f_5: float = 0.0855) -> float:
|
|
1059
|
-
"""
|
|
1060
|
-
Equation 5.0C, part 4. Calculate the value of the stabilisation efficiencies for active pool decay products
|
|
1061
|
-
entering the slow pool based on the sand content of the soil.
|
|
1062
|
-
|
|
1063
|
-
Parameters
|
|
1064
|
-
----------
|
|
1065
|
-
sand_content : float)
|
|
1066
|
-
The sand content of the soil, decimal proportion, default value: `0.33`.
|
|
1067
|
-
f_5 : float
|
|
1068
|
-
The stabilisation efficiencies for active pool decay products entering the passive pool, decimal_proportion,
|
|
1069
|
-
default value: `0.0855`.
|
|
1070
|
-
|
|
1071
|
-
Returns
|
|
1072
|
-
-------
|
|
1073
|
-
float
|
|
1074
|
-
The stabilisation efficiencies for active pool decay products entering the slow pool, decimal proportion.
|
|
1075
|
-
"""
|
|
1076
|
-
return 1 - f_5 - (0.17 + 0.68 * sand_content)
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
def _calc_alpha(
|
|
1080
|
-
carbon_input: float,
|
|
1081
|
-
f_2: float,
|
|
1082
|
-
f_4: float,
|
|
1083
|
-
lignin_content: float = 0.073,
|
|
1084
|
-
nitrogen_content: float = 0.0083,
|
|
1085
|
-
f_1: float = 0.378,
|
|
1086
|
-
f_3: float = 0.455,
|
|
1087
|
-
f_5: float = 0.0855,
|
|
1088
|
-
f_6: float = 0.0504,
|
|
1089
|
-
f_7: float = 0.42,
|
|
1090
|
-
f_8: float = 0.45,
|
|
1091
|
-
) -> float:
|
|
1092
|
-
"""
|
|
1093
|
-
Equation 5.0G, part 1. Calculate the C input to the active soil carbon sub-pool, kg C ha-1.
|
|
1094
|
-
|
|
1095
|
-
See table 5.5b for default values for lignin content and nitrogen content.
|
|
1096
|
-
|
|
1097
|
-
Parameters
|
|
1098
|
-
----------
|
|
1099
|
-
carbon_input : float
|
|
1100
|
-
Total carbon input to the soil during an inventory year, kg C ha-1.
|
|
1101
|
-
f_2 : float
|
|
1102
|
-
The stabilisation efficiencies for structural decay products entering the active pool, decimal proportion.
|
|
1103
|
-
f_4 : float
|
|
1104
|
-
The stabilisation efficiencies for active pool decay products entering the slow pool, decimal proportion.
|
|
1105
|
-
lignin_content : float
|
|
1106
|
-
The average lignin content of carbon input sources, decimal proportion, default value: `0.073`.
|
|
1107
|
-
nitrogen_content : float
|
|
1108
|
-
The average nitrogen content of carbon input sources, decimal proportion, default value: `0.0083`.
|
|
1109
|
-
sand_content : float
|
|
1110
|
-
The sand content of the soil, decimal proportion, default value: `0.33`.
|
|
1111
|
-
f_1 : float
|
|
1112
|
-
The stabilisation efficiencies for metabolic decay products entering the active pool, decimal proportion,
|
|
1113
|
-
default value: `0.378`.
|
|
1114
|
-
f_3 : float
|
|
1115
|
-
The stabilisation efficiencies for structural decay products entering the slow pool, decimal proportion,
|
|
1116
|
-
default value: `0.455`.
|
|
1117
|
-
f_5 : float
|
|
1118
|
-
The stabilisation efficiencies for active pool decay products entering the passive pool, decimal proportion,
|
|
1119
|
-
default value: `0.0855`.
|
|
1120
|
-
f_6 : float
|
|
1121
|
-
The stabilisation efficiencies for slow pool decay products entering the passive pool, decimal proportion,
|
|
1122
|
-
default value: `0.0504`.
|
|
1123
|
-
f_7 : float
|
|
1124
|
-
The stabilisation efficiencies for slow pool decay products entering the active pool, decimal proportion,
|
|
1125
|
-
default value: `0.42`.
|
|
1126
|
-
f_8 : float
|
|
1127
|
-
The stabilisation efficiencies for passive pool decay products entering the active pool, decimal proportion,
|
|
1128
|
-
default value: `0.45`.
|
|
1129
|
-
|
|
1130
|
-
Returns
|
|
1131
|
-
-------
|
|
1132
|
-
float
|
|
1133
|
-
The C input to the active soil carbon sub-pool, kg C ha-1.
|
|
1134
|
-
"""
|
|
1135
|
-
beta = _calc_beta(
|
|
1136
|
-
carbon_input, lignin_content=lignin_content, nitrogen_content=nitrogen_content
|
|
1137
|
-
)
|
|
1138
|
-
|
|
1139
|
-
x = beta * f_1
|
|
1140
|
-
y = (carbon_input * (1 - lignin_content) - beta) * f_2
|
|
1141
|
-
z = (carbon_input * lignin_content) * f_3 * (f_7 + (f_6 * f_8))
|
|
1142
|
-
d = 1 - (f_4 * f_7) - (f_5 * f_8) - (f_4 * f_6 * f_8)
|
|
1143
|
-
return (x + y + z) / d
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
def _get_tillage_factor(
|
|
1147
|
-
tillage_management_category: IpccManagementCategory = IpccManagementCategory.FULL_TILLAGE,
|
|
1148
|
-
tillage_factor_full_tillage: float = 3.036,
|
|
1149
|
-
tillage_factor_reduced_tillage: float = 2.075,
|
|
1150
|
-
tillage_factor_no_tillage: float = 1,
|
|
1151
|
-
) -> float:
|
|
1152
|
-
"""
|
|
1153
|
-
Calculate the tillage disturbance modifier on decay rate for active and slow sub-pools based on the tillage
|
|
1154
|
-
`IpccManagementCategory`.
|
|
1155
|
-
|
|
1156
|
-
If tillage regime is unknown, `FULL_TILLAGE` should be assumed.
|
|
1157
|
-
|
|
1158
|
-
Parameters
|
|
1159
|
-
----------
|
|
1160
|
-
tillage_factor_full_tillage : float)
|
|
1161
|
-
The tillage disturbance modifier for decay rates under full tillage, dimensionless, default value: `3.036`.
|
|
1162
|
-
tillage_factor_reduced_tillage : float
|
|
1163
|
-
Tillage disturbance modifier for decay rates under reduced tillage, dimensionless, default value: `2.075`.
|
|
1164
|
-
tillage_factor_no_tillage : float
|
|
1165
|
-
Tillage disturbance modifier for decay rates under no tillage, dimensionless, default value: `1`.
|
|
1166
|
-
|
|
1167
|
-
Returns
|
|
1168
|
-
-------
|
|
1169
|
-
float
|
|
1170
|
-
The tillage disturbance modifier on decay rate for active and slow sub-pools, dimensionless.
|
|
1171
|
-
"""
|
|
1172
|
-
ipcc_tillage_management_category_to_tillage_factors = {
|
|
1173
|
-
IpccManagementCategory.FULL_TILLAGE: tillage_factor_full_tillage,
|
|
1174
|
-
IpccManagementCategory.REDUCED_TILLAGE: tillage_factor_reduced_tillage,
|
|
1175
|
-
IpccManagementCategory.NO_TILLAGE: tillage_factor_no_tillage,
|
|
1176
|
-
}
|
|
1177
|
-
default = tillage_factor_full_tillage
|
|
1178
|
-
|
|
1179
|
-
return ipcc_tillage_management_category_to_tillage_factors.get(
|
|
1180
|
-
tillage_management_category, default
|
|
1181
|
-
)
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
def _calc_active_pool_decay_rate(
|
|
1185
|
-
annual_temperature_factor: float,
|
|
1186
|
-
annual_water_factor: float,
|
|
1187
|
-
tillage_factor: float,
|
|
1188
|
-
sand_content: float = 0.33,
|
|
1189
|
-
active_decay_factor: float = 7.4,
|
|
1190
|
-
) -> float:
|
|
1191
|
-
"""
|
|
1192
|
-
Equation 5.0B, part 3. Calculate the decay rate for the active SOC sub-pool given conditions in an inventory year.
|
|
1193
|
-
|
|
1194
|
-
Parameters
|
|
1195
|
-
----------
|
|
1196
|
-
annual_temperature_factor : float
|
|
1197
|
-
Average annual temperature factor, dimensionless, between `0` and `1`.
|
|
1198
|
-
annual_water_factor : float
|
|
1199
|
-
Average annual water factor, dimensionless, between `0.31935` and `2.25`.
|
|
1200
|
-
tillage_factor : float
|
|
1201
|
-
The tillage disturbance modifier on decay rate for active and slow sub-pools, dimensionless.
|
|
1202
|
-
sand_content : float
|
|
1203
|
-
sand_content (float): The sand content of the soil, decimal proportion, default value: `0.33`.
|
|
1204
|
-
active_decay_factor : float
|
|
1205
|
-
decay rate constant under optimal conditions for decomposition of the active SOC subpool, year-1, default value:
|
|
1206
|
-
`7.4`.
|
|
1207
|
-
|
|
1208
|
-
Returns
|
|
1209
|
-
-------
|
|
1210
|
-
float
|
|
1211
|
-
The decay rate for active SOC sub-pool, year-1.
|
|
1212
|
-
"""
|
|
1213
|
-
sand_factor = 0.25 + (0.75 * sand_content)
|
|
1214
|
-
return (
|
|
1215
|
-
annual_temperature_factor
|
|
1216
|
-
* annual_water_factor
|
|
1217
|
-
* tillage_factor
|
|
1218
|
-
* sand_factor
|
|
1219
|
-
* active_decay_factor
|
|
1220
|
-
)
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
def _calc_active_pool_steady_state(
|
|
1224
|
-
alpha: float, active_pool_decay_rate: float
|
|
1225
|
-
) -> float:
|
|
1226
|
-
"""
|
|
1227
|
-
Equation 5.0B part 2. Calculate the steady state active sub-pool SOC stock given conditions in an inventory year.
|
|
1228
|
-
|
|
1229
|
-
Parameters
|
|
1230
|
-
----------
|
|
1231
|
-
alpha : float
|
|
1232
|
-
The C input to the active soil carbon sub-pool, kg C ha-1.
|
|
1233
|
-
active_pool_decay_rate : float
|
|
1234
|
-
Decay rate for active SOC sub-pool, year-1.
|
|
1235
|
-
|
|
1236
|
-
Returns
|
|
1237
|
-
-------
|
|
1238
|
-
float
|
|
1239
|
-
The steady state active sub-pool SOC stock given conditions in year y, kg C ha-1
|
|
1240
|
-
"""
|
|
1241
|
-
return alpha / active_pool_decay_rate
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
# --- TIER 2 FUNCTIONS: SLOW SUB-POOL SOC STOCK ---
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
def _calc_slow_pool_decay_rate(
|
|
1248
|
-
annual_temperature_factor: float,
|
|
1249
|
-
annual_water_factor: float,
|
|
1250
|
-
tillage_factor: float,
|
|
1251
|
-
slow_decay_factor: float = 0.209,
|
|
1252
|
-
) -> float:
|
|
1253
|
-
"""
|
|
1254
|
-
Equation 5.0C, part 3. Calculate the decay rate for the slow SOC sub-pool given conditions in an inventory year.
|
|
1255
|
-
|
|
1256
|
-
Parameters
|
|
1257
|
-
----------
|
|
1258
|
-
annual_temperature_factor : float
|
|
1259
|
-
Average annual temperature factor, dimensionless, between `0` and `1`.
|
|
1260
|
-
annual_water_factor : float
|
|
1261
|
-
Average annual water factor, dimensionless, between `0.31935` and `2.25`.
|
|
1262
|
-
tillage_factor : float
|
|
1263
|
-
The tillage disturbance modifier on decay rate for active and slow sub-pools, dimensionless.
|
|
1264
|
-
slow_decay_factor : float)
|
|
1265
|
-
The decay rate constant under optimal conditions for decomposition of the slow SOC subpool, year-1,
|
|
1266
|
-
default value: `0.209`.
|
|
1267
|
-
|
|
1268
|
-
Returns
|
|
1269
|
-
-------
|
|
1270
|
-
float
|
|
1271
|
-
The decay rate for slow SOC sub-pool, year-1.
|
|
1272
|
-
"""
|
|
1273
|
-
return (
|
|
1274
|
-
annual_temperature_factor
|
|
1275
|
-
* annual_water_factor
|
|
1276
|
-
* tillage_factor
|
|
1277
|
-
* slow_decay_factor
|
|
1278
|
-
)
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
def _calc_slow_pool_steady_state(
|
|
1282
|
-
carbon_input: float,
|
|
1283
|
-
f_4: float,
|
|
1284
|
-
active_pool_steady_state: float,
|
|
1285
|
-
active_pool_decay_rate: float,
|
|
1286
|
-
slow_pool_decay_rate: float,
|
|
1287
|
-
lignin_content: float = 0.073,
|
|
1288
|
-
f_3: float = 0.455,
|
|
1289
|
-
) -> float:
|
|
1290
|
-
"""
|
|
1291
|
-
Equation 5.0C, part 2. Calculate the steady state slow sub-pool SOC stock given conditions in an inventory year.
|
|
1292
|
-
|
|
1293
|
-
Parameters
|
|
1294
|
-
----------
|
|
1295
|
-
carbon_input : float
|
|
1296
|
-
Total carbon input to the soil during an inventory year, kg C ha-1.
|
|
1297
|
-
f_4 : float
|
|
1298
|
-
The stabilisation efficiencies for active pool decay products entering the slow pool, decimal proportion.
|
|
1299
|
-
active_pool_steady_state : float
|
|
1300
|
-
The steady state active sub-pool SOC stock given conditions in year y, kg C ha-1
|
|
1301
|
-
active_pool_decay_rate : float
|
|
1302
|
-
Decay rate for active SOC sub-pool, year-1.
|
|
1303
|
-
slow_pool_decay_rate : float
|
|
1304
|
-
Decay rate for slow SOC sub-pool, year-1.
|
|
1305
|
-
lignin_content : float
|
|
1306
|
-
The average lignin content of carbon input sources, decimal proportion, default value: `0.073`.
|
|
1307
|
-
f_3 : float
|
|
1308
|
-
The stabilisation efficiencies for structural decay products entering the slow pool, decimal proportion,
|
|
1309
|
-
default value: `0.455`.
|
|
1310
|
-
|
|
1311
|
-
Returns
|
|
1312
|
-
-------
|
|
1313
|
-
float
|
|
1314
|
-
The steady state slow sub-pool SOC stock given conditions in year y, kg C ha-1
|
|
1315
|
-
"""
|
|
1316
|
-
x = carbon_input * lignin_content * f_3
|
|
1317
|
-
y = active_pool_steady_state * active_pool_decay_rate * f_4
|
|
1318
|
-
return (x + y) / slow_pool_decay_rate
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
# --- TIER 2 FUNCTIONS: PASSIVE SUB-POOL SOC STOCK ---
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
def _calc_passive_pool_decay_rate(
|
|
1325
|
-
annual_temperature_factor: float,
|
|
1326
|
-
annual_water_factor: float,
|
|
1327
|
-
passive_decay_factor: float = 0.00689,
|
|
1328
|
-
) -> float:
|
|
1329
|
-
"""
|
|
1330
|
-
Equation 5.0D, part 3. Calculate the decay rate for the passive SOC sub-pool given conditions in an inventory year.
|
|
1331
|
-
|
|
1332
|
-
Parameters
|
|
1333
|
-
----------
|
|
1334
|
-
annual_temperature_factor : float
|
|
1335
|
-
Average annual temperature factor, dimensionless, between `0` and `1`.
|
|
1336
|
-
annual_water_factor : float
|
|
1337
|
-
Average annual water factor, dimensionless, between `0.31935` and `2.25`.
|
|
1338
|
-
passive_decay_factor : float
|
|
1339
|
-
decay rate constant under optimal conditions for decomposition of the passive SOC subpool, year-1,
|
|
1340
|
-
default value: `0.00689`.
|
|
1341
|
-
|
|
1342
|
-
Returns
|
|
1343
|
-
-------
|
|
1344
|
-
float
|
|
1345
|
-
The decay rate for passive SOC sub-pool, year-1.
|
|
1346
|
-
"""
|
|
1347
|
-
return annual_temperature_factor * annual_water_factor * passive_decay_factor
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
def _calc_passive_pool_steady_state(
|
|
1351
|
-
active_pool_steady_state: float,
|
|
1352
|
-
slow_pool_steady_state: float,
|
|
1353
|
-
active_pool_decay_rate: float,
|
|
1354
|
-
slow_pool_decay_rate: float,
|
|
1355
|
-
passive_pool_decay_rate: float,
|
|
1356
|
-
f_5: float = 0.0855,
|
|
1357
|
-
f_6: float = 0.0504,
|
|
1358
|
-
) -> float:
|
|
1359
|
-
"""
|
|
1360
|
-
Equation 5.0D, part 2. Calculate the steady state passive sub-pool SOC stock given conditions in an inventory year.
|
|
1361
|
-
|
|
1362
|
-
Parameters
|
|
1363
|
-
----------
|
|
1364
|
-
active_pool_steady_state : float
|
|
1365
|
-
The steady state active sub-pool SOC stock given conditions in year y, kg C ha-1.
|
|
1366
|
-
slow_pool_steady_state : float
|
|
1367
|
-
The steady state slow sub-pool SOC stock given conditions in year y, kg C ha-1.
|
|
1368
|
-
active_pool_decay_rate : float
|
|
1369
|
-
Decay rate for active SOC sub-pool, year-1.
|
|
1370
|
-
slow_pool_decay_rate : float
|
|
1371
|
-
Decay rate for slow SOC sub-pool, year-1.
|
|
1372
|
-
passive_pool_decay_rate : float
|
|
1373
|
-
Decay rate for passive SOC sub-pool, year-1.
|
|
1374
|
-
f_5 : float
|
|
1375
|
-
The stabilisation efficiencies for active pool decay products entering the passive pool, decimal proportion,
|
|
1376
|
-
default value: `0.0855`.
|
|
1377
|
-
f_6 : float
|
|
1378
|
-
The stabilisation efficiencies for slow pool decay products entering the passive pool, decimal proportion,
|
|
1379
|
-
default value: `0.0504`.
|
|
1380
|
-
|
|
1381
|
-
Returns
|
|
1382
|
-
-------
|
|
1383
|
-
float
|
|
1384
|
-
The steady state passive sub-pool SOC stock given conditions in year y, kg C ha-1.
|
|
1385
|
-
"""
|
|
1386
|
-
x = active_pool_steady_state * active_pool_decay_rate * f_5
|
|
1387
|
-
y = slow_pool_steady_state * slow_pool_decay_rate * f_6
|
|
1388
|
-
return (x + y) / passive_pool_decay_rate
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
# --- TIER 2 FUNCTIONS: GENERIC SUB-POOL SOC STOCK ---
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
def _calc_sub_pool_soc_stock(
|
|
1395
|
-
sub_pool_steady_state: (float),
|
|
1396
|
-
previous_sub_pool_soc_stock: (float),
|
|
1397
|
-
sub_pool_decay_rate: (float),
|
|
1398
|
-
timestep: int = 1,
|
|
1399
|
-
) -> float:
|
|
1400
|
-
"""
|
|
1401
|
-
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.
|
|
1402
|
-
|
|
1403
|
-
If `sub_pool_decay_rate > 1` then set its value to `1` for this calculation.
|
|
1404
|
-
|
|
1405
|
-
Parameters
|
|
1406
|
-
----------
|
|
1407
|
-
sub_pool_steady_state : float
|
|
1408
|
-
The steady state sub-pool SOC stock given conditions in year y, kg C ha-1.
|
|
1409
|
-
previous_sub_pool_soc_stock : float
|
|
1410
|
-
The sub-pool SOC stock in year y-timestep (by default one year ago), kg C ha-1.
|
|
1411
|
-
sub_pool_decay_rate : float
|
|
1412
|
-
Decay rate for active SOC sub-pool, year-1.
|
|
1413
|
-
timestep : int
|
|
1414
|
-
The number of years between current and previous inventory year.
|
|
1415
|
-
|
|
1416
|
-
Returns
|
|
1417
|
-
-------
|
|
1418
|
-
float
|
|
1419
|
-
The sub-pool SOC stock in year y, kg C ha-1.
|
|
1420
|
-
"""
|
|
1421
|
-
sub_pool_decay_rate = min(1, sub_pool_decay_rate)
|
|
1422
|
-
return (
|
|
1423
|
-
previous_sub_pool_soc_stock
|
|
1424
|
-
+ (sub_pool_steady_state - previous_sub_pool_soc_stock)
|
|
1425
|
-
* timestep
|
|
1426
|
-
* sub_pool_decay_rate
|
|
1427
|
-
)
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
# --- TIER 2 FUNCTIONS: SOC STOCK CHANGE ---
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
def _calc_tier_2_soc_stock(
|
|
1434
|
-
active_pool_soc_stock: float,
|
|
1435
|
-
slow_pool_soc_stock: float,
|
|
1436
|
-
passive_pool_soc_stock: float,
|
|
1437
|
-
) -> float:
|
|
1438
|
-
"""
|
|
1439
|
-
Equation 5.0A, part 3. Calculate the total SOC stock for a site by summing its active, slow and passive SOC stock
|
|
1440
|
-
sub-pools. This is the value we need for our `organicCarbonPerHa` measurement.
|
|
1441
|
-
|
|
1442
|
-
Parameters
|
|
1443
|
-
----------
|
|
1444
|
-
actve_pool_soc_stock : float
|
|
1445
|
-
The active sub-pool SOC stock in year y, kg C ha-1.
|
|
1446
|
-
slow_pool_soc_stock : float
|
|
1447
|
-
The slow sub-pool SOC stock in year y, kg C ha-1.
|
|
1448
|
-
passive_pool_soc_stock : float
|
|
1449
|
-
The passive sub-pool SOC stock in year y, kg C ha-1.
|
|
1450
|
-
|
|
1451
|
-
Returns
|
|
1452
|
-
-------
|
|
1453
|
-
float
|
|
1454
|
-
The SOC stock of a site in year y, kg C ha-1.
|
|
1455
|
-
"""
|
|
1456
|
-
return active_pool_soc_stock + slow_pool_soc_stock + passive_pool_soc_stock
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
# --- TIER 2 SUB-MODEL: RUN ACTIVE, SLOW AND PASSIVE SOC STOCKS ---
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
def timeseries_to_inventory(timeseries_data: list[float], run_in_period: int):
|
|
1463
|
-
"""
|
|
1464
|
-
Convert annual data to inventory data by averaging the values for the run-in period.
|
|
1465
|
-
|
|
1466
|
-
Parameters
|
|
1467
|
-
----------
|
|
1468
|
-
timeseries_data : list[float]
|
|
1469
|
-
The timeseries data to be reformatted.
|
|
1470
|
-
run_in_period : int
|
|
1471
|
-
The length of the run-in in years.
|
|
1472
|
-
|
|
1473
|
-
Returns
|
|
1474
|
-
-------
|
|
1475
|
-
list[float]
|
|
1476
|
-
The inventory formatted data, where value 0 is the average of the run-in values.
|
|
1477
|
-
"""
|
|
1478
|
-
return [mean(timeseries_data[0:run_in_period])] + timeseries_data[run_in_period:]
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
def _run_soc_stocks(
|
|
1482
|
-
timestamps: list[int],
|
|
1483
|
-
annual_temperature_factors: list[float],
|
|
1484
|
-
annual_water_factors: list[float],
|
|
1485
|
-
annual_organic_carbon_inputs: list[float],
|
|
1486
|
-
annual_n_contents: list[float],
|
|
1487
|
-
annual_lignin_contents: list[float],
|
|
1488
|
-
annual_tillage_categories: Union[list[IpccManagementCategory], None] = None,
|
|
1489
|
-
sand_content: float = 0.33,
|
|
1490
|
-
run_in_period: int = 5,
|
|
1491
|
-
params: Union[dict[str, float], None] = None,
|
|
1492
|
-
) -> Tier2SocResult:
|
|
1493
|
-
"""
|
|
1494
|
-
Run the IPCC Tier 2 SOC model with precomputed `annual_temperature_factors`, `annual_water_factors`,
|
|
1495
|
-
`annual_organic_carbon_inputs`, `annual_n_contents`, `annual_lignin_contents`.
|
|
1496
|
-
|
|
1497
|
-
Parameters
|
|
1498
|
-
----------
|
|
1499
|
-
timestamps : list[int]
|
|
1500
|
-
A list of integer timestamps (e.g. `[1995, 1996]`) for each year in the inventory.
|
|
1501
|
-
annual_temperature_factors : list[float]
|
|
1502
|
-
A list of temperature factors for each year in the inventory, dimensionless (see Equation 5.0E).
|
|
1503
|
-
annual_water_factors : list[float]
|
|
1504
|
-
A list of water factors for each year in the inventory, dimensionless (see Equation 5.0F).
|
|
1505
|
-
annual_organic_carbon_inputs : list[float]
|
|
1506
|
-
A list of organic carbon inputs to the soil for each year in the inventory, kg C ha-1 year-1 (see Equation
|
|
1507
|
-
5.0H).
|
|
1508
|
-
annual_n_contents : list[float]
|
|
1509
|
-
A list of the average nitrogen contents of the organic carbon sources for each year in the inventory, decimal
|
|
1510
|
-
proportion.
|
|
1511
|
-
annual_lignin_contents : list[float]
|
|
1512
|
-
A list of the average lignin contents of the organic carbon sources for each year in the inventory, decimal
|
|
1513
|
-
proportion.
|
|
1514
|
-
annual_tillage_categories : list[IpccManagementCategory] | None
|
|
1515
|
-
A list of the site"s `IpccManagementCategory`s for each year in the inventory.
|
|
1516
|
-
sand_content : float
|
|
1517
|
-
The sand content of the site, decimal proportion, default value: `0.33`.
|
|
1518
|
-
run_in_period : int
|
|
1519
|
-
The length of the run-in period in years, must be greater than or equal to 1, default value: `5`.
|
|
1520
|
-
params : dict[str: float] | None
|
|
1521
|
-
Overrides for the model parameters. If `None` only default parameters will be used.
|
|
1522
|
-
|
|
1523
|
-
Returns
|
|
1524
|
-
-------
|
|
1525
|
-
Tier2SocResult
|
|
1526
|
-
Returns an annual inventory of organicCarbonPerHa data in the format
|
|
1527
|
-
`(timestamps: list[int], organicCarbonPerHa_values: list[float], active_pool_soc_stocks: list[float],
|
|
1528
|
-
slow_pool_soc_stocks: list[float], passive_pool_soc_stocks: list[float])`
|
|
1529
|
-
"""
|
|
1530
|
-
|
|
1531
|
-
# --- MERGE ANY USER-SET PARAMETERS WITH THE IPCC DEFAULTS ---
|
|
1532
|
-
|
|
1533
|
-
params = DEFAULT_PARAMS | (params or {})
|
|
1534
|
-
|
|
1535
|
-
# --- GET F4 ---
|
|
1536
|
-
|
|
1537
|
-
f_4 = _calc_f_4(sand_content, f_5=params.get("f_5"))
|
|
1538
|
-
|
|
1539
|
-
# --- GET ANNUAL DATA ---
|
|
1540
|
-
|
|
1541
|
-
annual_f_2s = [
|
|
1542
|
-
_get_f_2(
|
|
1543
|
-
till,
|
|
1544
|
-
f_2_full_tillage=params.get("f_2_full_tillage"),
|
|
1545
|
-
f_2_reduced_tillage=params.get("f_2_reduced_tillage"),
|
|
1546
|
-
f_2_no_tillage=params.get("f_2_no_tillage"),
|
|
1547
|
-
f_2_unknown_tillage=params.get("f_2_unknown_tillage"),
|
|
1548
|
-
)
|
|
1549
|
-
for till in annual_tillage_categories
|
|
1550
|
-
]
|
|
1551
|
-
|
|
1552
|
-
annual_tillage_factors = [
|
|
1553
|
-
_get_tillage_factor(
|
|
1554
|
-
till,
|
|
1555
|
-
tillage_factor_full_tillage=params.get("tillage_factor_full_tillage"),
|
|
1556
|
-
tillage_factor_reduced_tillage=params.get("tillage_factor_reduced_tillage"),
|
|
1557
|
-
tillage_factor_no_tillage=params.get("tillage_factor_no_tillage"),
|
|
1558
|
-
)
|
|
1559
|
-
for till in annual_tillage_categories
|
|
1560
|
-
]
|
|
1561
|
-
|
|
1562
|
-
# --- SPLIT ANNUAL DATA INTO RUN-IN AND INVENTORY PERIODS ---
|
|
1563
|
-
|
|
1564
|
-
inventory_temperature_factors = timeseries_to_inventory(annual_temperature_factors, run_in_period)
|
|
1565
|
-
inventory_water_factors = timeseries_to_inventory(annual_water_factors, run_in_period)
|
|
1566
|
-
inventory_carbon_inputs = timeseries_to_inventory(annual_organic_carbon_inputs, run_in_period)
|
|
1567
|
-
inventory_n_contents = timeseries_to_inventory(annual_n_contents, run_in_period)
|
|
1568
|
-
inventory_lignin_contents = timeseries_to_inventory(annual_lignin_contents, run_in_period)
|
|
1569
|
-
inventory_f_2s = timeseries_to_inventory(annual_f_2s, run_in_period)
|
|
1570
|
-
inventory_tillage_factors = timeseries_to_inventory(annual_tillage_factors, run_in_period)
|
|
1571
|
-
|
|
1572
|
-
# The last year of the run-in should be the first year of the inventory
|
|
1573
|
-
inventory_timestamps = timestamps[run_in_period - 1:]
|
|
1574
|
-
|
|
1575
|
-
# --- CALCULATE THE ACTIVE ACTIVE POOL STEADY STATES ---
|
|
1576
|
-
|
|
1577
|
-
inventory_alphas = [
|
|
1578
|
-
_calc_alpha(
|
|
1579
|
-
carbon_input,
|
|
1580
|
-
f_2,
|
|
1581
|
-
f_4,
|
|
1582
|
-
lignin_content,
|
|
1583
|
-
nitrogen_content,
|
|
1584
|
-
f_1=params.get("f_1"),
|
|
1585
|
-
f_3=params.get("f_3"),
|
|
1586
|
-
f_5=params.get("f_5"),
|
|
1587
|
-
f_6=params.get("f_6"),
|
|
1588
|
-
f_7=params.get("f_7"),
|
|
1589
|
-
f_8=params.get("f_8"),
|
|
1590
|
-
)
|
|
1591
|
-
for carbon_input, f_2, lignin_content, nitrogen_content in zip(
|
|
1592
|
-
inventory_carbon_inputs,
|
|
1593
|
-
inventory_f_2s,
|
|
1594
|
-
inventory_lignin_contents,
|
|
1595
|
-
inventory_n_contents,
|
|
1596
|
-
)
|
|
1597
|
-
]
|
|
1598
|
-
|
|
1599
|
-
inventory_active_pool_decay_rates = [
|
|
1600
|
-
_calc_active_pool_decay_rate(
|
|
1601
|
-
temp_fac,
|
|
1602
|
-
water_fac,
|
|
1603
|
-
till_fac,
|
|
1604
|
-
sand_content,
|
|
1605
|
-
active_decay_factor=params.get("active_decay_factor"),
|
|
1606
|
-
)
|
|
1607
|
-
for temp_fac, water_fac, till_fac in zip(
|
|
1608
|
-
inventory_temperature_factors,
|
|
1609
|
-
inventory_water_factors,
|
|
1610
|
-
inventory_tillage_factors,
|
|
1611
|
-
)
|
|
1612
|
-
]
|
|
1613
|
-
|
|
1614
|
-
inventory_active_pool_steady_states = [
|
|
1615
|
-
_calc_active_pool_steady_state(alpha, active_decay_rate)
|
|
1616
|
-
for alpha, active_decay_rate in zip(
|
|
1617
|
-
inventory_alphas, inventory_active_pool_decay_rates
|
|
1618
|
-
)
|
|
1619
|
-
]
|
|
1620
|
-
|
|
1621
|
-
# --- CALCULATE THE SLOW POOL STEADY STATES ---
|
|
1622
|
-
|
|
1623
|
-
inventory_slow_pool_decay_rates = [
|
|
1624
|
-
_calc_slow_pool_decay_rate(
|
|
1625
|
-
temp_fac, water_fac, till_fac, slow_decay_factor=params.get("slow_decay_factor")
|
|
1626
|
-
)
|
|
1627
|
-
for temp_fac, water_fac, till_fac in zip(
|
|
1628
|
-
inventory_temperature_factors,
|
|
1629
|
-
inventory_water_factors,
|
|
1630
|
-
inventory_tillage_factors,
|
|
1631
|
-
)
|
|
1632
|
-
]
|
|
1633
|
-
|
|
1634
|
-
inventory_slow_pool_steady_states = [
|
|
1635
|
-
_calc_slow_pool_steady_state(
|
|
1636
|
-
carbon_input,
|
|
1637
|
-
f_4,
|
|
1638
|
-
active_steady_state,
|
|
1639
|
-
active_decay_rate,
|
|
1640
|
-
slow_decay_rate,
|
|
1641
|
-
lignin_content,
|
|
1642
|
-
f_3=params.get("f_3"),
|
|
1643
|
-
)
|
|
1644
|
-
for carbon_input, active_steady_state, active_decay_rate, slow_decay_rate, lignin_content in zip(
|
|
1645
|
-
inventory_carbon_inputs,
|
|
1646
|
-
inventory_active_pool_steady_states,
|
|
1647
|
-
inventory_active_pool_decay_rates,
|
|
1648
|
-
inventory_slow_pool_decay_rates,
|
|
1649
|
-
inventory_lignin_contents,
|
|
1650
|
-
)
|
|
1651
|
-
]
|
|
1652
|
-
|
|
1653
|
-
# --- CALCULATE THE PASSIVE POOL STEADY STATES ---
|
|
1654
|
-
|
|
1655
|
-
inventory_passive_pool_decay_rates = [
|
|
1656
|
-
_calc_passive_pool_decay_rate(
|
|
1657
|
-
temp_fac, water_fac, passive_decay_factor=params.get("passive_decay_factor")
|
|
1658
|
-
)
|
|
1659
|
-
for temp_fac, water_fac in zip(
|
|
1660
|
-
inventory_temperature_factors, inventory_water_factors
|
|
1661
|
-
)
|
|
1662
|
-
]
|
|
1663
|
-
|
|
1664
|
-
inventory_passive_pool_steady_states = [
|
|
1665
|
-
_calc_passive_pool_steady_state(
|
|
1666
|
-
active_steady_state,
|
|
1667
|
-
slow_steady_state,
|
|
1668
|
-
active_decay_rate,
|
|
1669
|
-
slow_decay_rate,
|
|
1670
|
-
passive_decay_rate,
|
|
1671
|
-
f_5=params.get("f_5"),
|
|
1672
|
-
f_6=params.get("f_6"),
|
|
1673
|
-
)
|
|
1674
|
-
for active_steady_state, slow_steady_state, active_decay_rate, slow_decay_rate, passive_decay_rate in zip(
|
|
1675
|
-
inventory_active_pool_steady_states,
|
|
1676
|
-
inventory_slow_pool_steady_states,
|
|
1677
|
-
inventory_active_pool_decay_rates,
|
|
1678
|
-
inventory_slow_pool_decay_rates,
|
|
1679
|
-
inventory_passive_pool_decay_rates,
|
|
1680
|
-
)
|
|
1681
|
-
]
|
|
1682
|
-
|
|
1683
|
-
# --- CALCULATE THE ACTIVE, SLOW AND PASSIVE SOC STOCKS ---
|
|
1684
|
-
|
|
1685
|
-
inventory_active_pool_soc_stocks = inventory_active_pool_steady_states[:1]
|
|
1686
|
-
inventory_slow_pool_soc_stocks = inventory_slow_pool_steady_states[:1]
|
|
1687
|
-
inventory_passive_pool_soc_stocks = inventory_passive_pool_steady_states[:1]
|
|
1688
|
-
|
|
1689
|
-
for index in range(1, len(inventory_timestamps), 1):
|
|
1690
|
-
inventory_active_pool_soc_stocks.insert(
|
|
1691
|
-
index,
|
|
1692
|
-
_calc_sub_pool_soc_stock(
|
|
1693
|
-
inventory_active_pool_steady_states[index],
|
|
1694
|
-
inventory_active_pool_soc_stocks[index - 1],
|
|
1695
|
-
inventory_active_pool_decay_rates[index],
|
|
1696
|
-
),
|
|
1697
|
-
)
|
|
1698
|
-
inventory_slow_pool_soc_stocks.insert(
|
|
1699
|
-
index,
|
|
1700
|
-
_calc_sub_pool_soc_stock(
|
|
1701
|
-
inventory_slow_pool_steady_states[index],
|
|
1702
|
-
inventory_slow_pool_soc_stocks[index - 1],
|
|
1703
|
-
inventory_slow_pool_decay_rates[index],
|
|
1704
|
-
),
|
|
1705
|
-
)
|
|
1706
|
-
inventory_passive_pool_soc_stocks.insert(
|
|
1707
|
-
index,
|
|
1708
|
-
_calc_sub_pool_soc_stock(
|
|
1709
|
-
inventory_passive_pool_steady_states[index],
|
|
1710
|
-
inventory_passive_pool_soc_stocks[index - 1],
|
|
1711
|
-
inventory_passive_pool_decay_rates[index],
|
|
1712
|
-
),
|
|
1713
|
-
)
|
|
1714
|
-
|
|
1715
|
-
# --- RETURN THE RESULT ---
|
|
1716
|
-
|
|
1717
|
-
return Tier2SocResult(
|
|
1718
|
-
timestamps=inventory_timestamps,
|
|
1719
|
-
active_pool_soc_stocks=inventory_active_pool_soc_stocks,
|
|
1720
|
-
slow_pool_soc_stocks=inventory_slow_pool_soc_stocks,
|
|
1721
|
-
passive_pool_soc_stocks=inventory_passive_pool_soc_stocks,
|
|
1722
|
-
)
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
# --- TIER 2 SUB-MODEL: ANNUAL TEMPERATURE FACTORS ---
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
def _check_12_months(inner_dict: dict, keys: set[Any]):
|
|
1729
|
-
"""
|
|
1730
|
-
Checks whether an inner dict has 12 months of data for each of the required inner keys.
|
|
1731
|
-
|
|
1732
|
-
Parameters
|
|
1733
|
-
----------
|
|
1734
|
-
inner_dict : dict
|
|
1735
|
-
A dictionary representing one year in a timeseries for the Tier 2 model.
|
|
1736
|
-
keys : set[Any]
|
|
1737
|
-
The required inner keys.
|
|
1738
|
-
|
|
1739
|
-
Returns
|
|
1740
|
-
-------
|
|
1741
|
-
bool
|
|
1742
|
-
Whether or not the inner dict satisfies the conditions.
|
|
1743
|
-
"""
|
|
1744
|
-
return all(
|
|
1745
|
-
len(inner_dict.get(key, [])) == 12 for key in keys
|
|
1746
|
-
)
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
# --- SUB-MODEL ANNUAL TEMPERATURE FACTORS ---
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
def _run_annual_temperature_factors(
|
|
1753
|
-
timestamps: list[int],
|
|
1754
|
-
temperatures: list[list[float]],
|
|
1755
|
-
maximum_temperature: float = 45.0,
|
|
1756
|
-
optimum_temperature: float = 33.69,
|
|
1757
|
-
):
|
|
1758
|
-
"""
|
|
1759
|
-
Parameters
|
|
1760
|
-
----------
|
|
1761
|
-
timestamps : list[int]
|
|
1762
|
-
A list of integer timestamps (e.g. `[1995, 1996]`) for each year in the inventory.
|
|
1763
|
-
temperatures : list[list[float]])
|
|
1764
|
-
A list of monthly average temperatures for each year in the inventory
|
|
1765
|
-
(e.g. `[[10,10,10,20,25,15,15,10,10,10,5,5]]`).
|
|
1766
|
-
maximum_temperature : float
|
|
1767
|
-
The maximum air temperature for decomposition, degrees C, default value: `45.0`.
|
|
1768
|
-
optimum_temperature : float
|
|
1769
|
-
The optimum air temperature for decomposition, degrees C, default value: `33.69`.
|
|
1770
|
-
|
|
1771
|
-
Returns
|
|
1772
|
-
-------
|
|
1773
|
-
TemperatureFactorResult
|
|
1774
|
-
An inventory of annual temperature factor data as a named tuple with the format
|
|
1775
|
-
`(timestamps: list[int], annual_temperature_factors: list[float])`.
|
|
1776
|
-
"""
|
|
1777
|
-
return TemperatureFactorResult(
|
|
1778
|
-
timestamps=timestamps,
|
|
1779
|
-
annual_temperature_factors=[
|
|
1780
|
-
_calc_annual_temperature_factor(
|
|
1781
|
-
monthly_temperatures, maximum_temperature, optimum_temperature
|
|
1782
|
-
)
|
|
1783
|
-
for monthly_temperatures in temperatures
|
|
1784
|
-
],
|
|
1785
|
-
)
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
# --- TIER 2 SUB-MODEL: ANNUAL WATER FACTORS ---
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
def _run_annual_water_factors(
|
|
1792
|
-
timestamps: list[int],
|
|
1793
|
-
precipitations: list[list[float]],
|
|
1794
|
-
pets: list[list[float]],
|
|
1795
|
-
is_irrigateds: Union[list[list[bool]], None] = None,
|
|
1796
|
-
water_factor_slope: float = 1.331,
|
|
1797
|
-
):
|
|
1798
|
-
"""
|
|
1799
|
-
Parameters
|
|
1800
|
-
----------
|
|
1801
|
-
timestamps : list[int]
|
|
1802
|
-
A list of integer timestamps (e.g. `[1995, 1996...]`) for each year in the inventory.
|
|
1803
|
-
precipitations : list[list[float]]
|
|
1804
|
-
A list of monthly sum precipitations for each year in the inventory
|
|
1805
|
-
(e.g. `[[10,10,10,20,25,15,15,10,10,10,5,5]]`).
|
|
1806
|
-
pets list[list[float]]
|
|
1807
|
-
A list of monthly sum potential evapotransiprations for each year in the inventory.
|
|
1808
|
-
is_irrigateds list[list[bool]] | None
|
|
1809
|
-
A list of monthly booleans that describe whether irrigation is used in a particular calendar month for each
|
|
1810
|
-
year in the inventory.
|
|
1811
|
-
water_factor_slope : float
|
|
1812
|
-
The slope for mappet term to estimate water factor, dimensionless, default value: `1.331`.
|
|
1813
|
-
|
|
1814
|
-
Returns
|
|
1815
|
-
-------
|
|
1816
|
-
WaterFactorResult
|
|
1817
|
-
An inventory of annual water factor data as a named tuple with the format
|
|
1818
|
-
`(timestamps: list[int], annual_water_factors: list[float])`.
|
|
1819
|
-
"""
|
|
1820
|
-
is_irrigateds = [None] * len(timestamps) if is_irrigateds is None else is_irrigateds
|
|
1821
|
-
return WaterFactorResult(
|
|
1822
|
-
timestamps=timestamps,
|
|
1823
|
-
annual_water_factors=[
|
|
1824
|
-
_calc_annual_water_factor(
|
|
1825
|
-
monthly_precipitations,
|
|
1826
|
-
monthly_pets,
|
|
1827
|
-
monthly_is_irrigateds,
|
|
1828
|
-
water_factor_slope,
|
|
1829
|
-
)
|
|
1830
|
-
for monthly_precipitations, monthly_pets, monthly_is_irrigateds in zip(
|
|
1831
|
-
precipitations, pets, is_irrigateds
|
|
1832
|
-
)
|
|
1833
|
-
],
|
|
1834
|
-
)
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
# --- TIER 2 SUB-MODEL: ANNUAL ORGANIC CARBON INPUTS ---
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
def _iterate_carbon_source(node: dict) -> Union[CarbonSource, None]:
|
|
1841
|
-
"""
|
|
1842
|
-
Validates whether a node is a valid carbon source and returns
|
|
1843
|
-
a `CarbonSource` named tuple if yes.
|
|
1844
|
-
|
|
1845
|
-
Parameters
|
|
1846
|
-
----------
|
|
1847
|
-
node : dict
|
|
1848
|
-
A Hestia `Product` or `Input` node, see: https://www.hestia.earth/schema/Product
|
|
1849
|
-
or https://www.hestia.earth/schema/Input.
|
|
1850
|
-
|
|
1851
|
-
Returns
|
|
1852
|
-
-------
|
|
1853
|
-
CarbonSource | None
|
|
1854
|
-
A `CarbonSource` named tuple if the node is a carbon source with the required properties, else `None`.
|
|
1855
|
-
"""
|
|
1856
|
-
mass = list_sum(node.get("value", []))
|
|
1857
|
-
carbon_content, nitrogen_content, lignin_content = (
|
|
1858
|
-
get_node_property(node, term_id).get("value", 0)/100 for term_id in CARBON_INPUT_PROPERTY_TERM_IDS
|
|
1859
|
-
)
|
|
1860
|
-
|
|
1861
|
-
should_run = all([
|
|
1862
|
-
mass > 0,
|
|
1863
|
-
0 < carbon_content <= 1,
|
|
1864
|
-
0 < nitrogen_content <= 1,
|
|
1865
|
-
0 < lignin_content <= 1
|
|
1866
|
-
])
|
|
1867
|
-
|
|
1868
|
-
return (
|
|
1869
|
-
CarbonSource(
|
|
1870
|
-
mass, carbon_content, nitrogen_content, lignin_content
|
|
1871
|
-
) if should_run else None
|
|
1872
|
-
)
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
def _get_carbon_sources_from_cycles(cycles: dict) -> list[CarbonSource]:
|
|
1876
|
-
"""
|
|
1877
|
-
Retrieves and formats all of the valid carbon sources from a list of cycles.
|
|
1878
|
-
|
|
1879
|
-
Carbon sources can be either a Hestia `Product` node (e.g. crop residue) or `Input` node (e.g. organic amendment).
|
|
1880
|
-
|
|
1881
|
-
Parameters
|
|
1882
|
-
----------
|
|
1883
|
-
cycles : list[dict]
|
|
1884
|
-
A list of Hestia `Cycle` nodes, see: https://www.hestia.earth/schema/Cycle.
|
|
1885
|
-
|
|
1886
|
-
Returns
|
|
1887
|
-
-------
|
|
1888
|
-
list[CarbonSource]
|
|
1889
|
-
A formatted list of `CarbonSource`s for the inputted `Cycle`s.
|
|
1890
|
-
"""
|
|
1891
|
-
inputs_and_products = non_empty_list(flatten(
|
|
1892
|
-
[cycle.get("inputs", []) + cycle.get("products", []) for cycle in cycles]
|
|
1893
|
-
))
|
|
1894
|
-
crop_residue_terms = get_crop_residue_incorporated_or_left_on_field_terms()
|
|
1895
|
-
|
|
1896
|
-
return non_empty_list([
|
|
1897
|
-
_iterate_carbon_source(node) for node in inputs_and_products
|
|
1898
|
-
if any([
|
|
1899
|
-
node.get("term", {}).get("@id") in crop_residue_terms,
|
|
1900
|
-
node.get("term", {}).get("termType") in CARBON_SOURCE_TERM_TYPES
|
|
1901
|
-
])
|
|
1902
|
-
])
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
# --- TIER 2 SOC MODEL ---
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
def _run_tier_2(
|
|
1909
|
-
inventory: dict[int: dict[_InventoryKey: any]],
|
|
1910
|
-
*,
|
|
1911
|
-
run_in_period: int = 5,
|
|
1912
|
-
run_with_irrigation: bool = True,
|
|
1913
|
-
sand_content: float = 0.33,
|
|
1914
|
-
params: Union[dict[str, float], None] = None,
|
|
1915
|
-
**_
|
|
1916
|
-
) -> list[dict]:
|
|
1917
|
-
"""
|
|
1918
|
-
Run the IPCC Tier 2 SOC model on a time series of annual data about a site and the mangagement activities taking
|
|
1919
|
-
place on it. To avoid any errors, the `inventory` parameter must be pre-validated by the `should_run` function.
|
|
1920
|
-
|
|
1921
|
-
The inventory should be in the following shape:
|
|
1922
|
-
```
|
|
1923
|
-
{
|
|
1924
|
-
year (int): {
|
|
1925
|
-
_InventoryKey.SHOULD_RUN_TIER_2: bool,
|
|
1926
|
-
_InventoryKey.TEMP_MONTHLY: list[float],
|
|
1927
|
-
_InventoryKey.PRECIP_MONTHLY: list[float],
|
|
1928
|
-
_InventoryKey.PET_MONTHLY: list[float],
|
|
1929
|
-
_InventoryKey.IRRIGATED_MONTHLY: list[bool]
|
|
1930
|
-
_InventoryKey.CARBON_INPUT: float,
|
|
1931
|
-
_InventoryKey.N_CONTENT: float,
|
|
1932
|
-
_InventoryKey.TILLAGE_CATEGORY: IpccManagementCategory,
|
|
1933
|
-
_InventoryKey.SAND_CONTENT: float
|
|
1934
|
-
},
|
|
1935
|
-
...
|
|
1936
|
-
}
|
|
1937
|
-
```
|
|
1938
|
-
|
|
1939
|
-
TODO: interpolate between `sandContent` measurements for different years of the inventory
|
|
1940
|
-
|
|
1941
|
-
Parameters
|
|
1942
|
-
----------
|
|
1943
|
-
inventory : dict
|
|
1944
|
-
The inventory built by the `_should_run` function.
|
|
1945
|
-
run_in_period : int, optional
|
|
1946
|
-
The length of the run-in period in years, must be greater than or equal to 1, default value: `5`.
|
|
1947
|
-
run_with_irrigation : bool, optional
|
|
1948
|
-
`True` if the model should run while taking into account irrigation, `False` if not.
|
|
1949
|
-
sand_content : float, optional
|
|
1950
|
-
A back-up sand content for if none are found in the inventory, decimal proportion, default value: `0.33`.
|
|
1951
|
-
params : dict | None, optional
|
|
1952
|
-
Overrides for the model parameters. If `None` only default parameters will be used.
|
|
1953
|
-
|
|
1954
|
-
Returns
|
|
1955
|
-
-------
|
|
1956
|
-
list[dict]
|
|
1957
|
-
A list of Hestia `Measurement` nodes containing the calculated SOC stocks and additional relevant data.
|
|
1958
|
-
"""
|
|
1959
|
-
valid_inventory = {
|
|
1960
|
-
year: group for year, group in inventory.items() if group.get(_InventoryKey.SHOULD_RUN_TIER_2)
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
timestamps = [year for year in valid_inventory.keys()]
|
|
1964
|
-
|
|
1965
|
-
annual_temperature_monthlys = [group[_InventoryKey.TEMP_MONTHLY] for group in valid_inventory.values()]
|
|
1966
|
-
annual_precipitation_monthlys = [group[_InventoryKey.PRECIP_MONTHLY] for group in valid_inventory.values()]
|
|
1967
|
-
annual_pet_monthlys = [group[_InventoryKey.PET_MONTHLY] for group in valid_inventory.values()]
|
|
1968
|
-
|
|
1969
|
-
annual_carbon_inputs = [group[_InventoryKey.CARBON_INPUT] for group in valid_inventory.values()]
|
|
1970
|
-
annual_n_contents = [group[_InventoryKey.N_CONTENT] for group in valid_inventory.values()]
|
|
1971
|
-
annual_lignin_contents = [group[_InventoryKey.LIGNIN_CONTENT] for group in valid_inventory.values()]
|
|
1972
|
-
annual_tillage_categories = [group[_InventoryKey.TILLAGE_CATEGORY] for group in valid_inventory.values()]
|
|
1973
|
-
annual_irrigated_monthly = (
|
|
1974
|
-
[group[_InventoryKey.IRRIGATED_MONTHLY] for group in valid_inventory.values()] if run_with_irrigation else None
|
|
1975
|
-
)
|
|
1976
|
-
|
|
1977
|
-
sand_content = next(
|
|
1978
|
-
(
|
|
1979
|
-
group[_InventoryKey.SAND_CONTENT] for group in valid_inventory.values()
|
|
1980
|
-
if _InventoryKey.SAND_CONTENT in group
|
|
1981
|
-
),
|
|
1982
|
-
sand_content
|
|
1983
|
-
)
|
|
1984
|
-
|
|
1985
|
-
# --- MERGE ANY USER-SET PARAMETERS WITH THE IPCC DEFAULTS ---
|
|
1986
|
-
|
|
1987
|
-
params = DEFAULT_PARAMS | (params or {})
|
|
1988
|
-
|
|
1989
|
-
# --- COMPUTE FACTORS AND CARBON INPUTS ---
|
|
1990
|
-
|
|
1991
|
-
_, annual_temperature_factors = _run_annual_temperature_factors(
|
|
1992
|
-
timestamps,
|
|
1993
|
-
annual_temperature_monthlys,
|
|
1994
|
-
maximum_temperature=params.get("maximum_temperature"),
|
|
1995
|
-
optimum_temperature=params.get("optimum_temperature")
|
|
1996
|
-
)
|
|
1997
|
-
|
|
1998
|
-
_, annual_water_factors = _run_annual_water_factors(
|
|
1999
|
-
timestamps,
|
|
2000
|
-
annual_precipitation_monthlys,
|
|
2001
|
-
annual_pet_monthlys,
|
|
2002
|
-
annual_irrigated_monthly,
|
|
2003
|
-
water_factor_slope=params.get("water_factor_slope")
|
|
2004
|
-
)
|
|
2005
|
-
|
|
2006
|
-
# --- RUN THE MODEL ---
|
|
2007
|
-
|
|
2008
|
-
result = _run_soc_stocks(
|
|
2009
|
-
timestamps=timestamps,
|
|
2010
|
-
annual_temperature_factors=annual_temperature_factors,
|
|
2011
|
-
annual_water_factors=annual_water_factors,
|
|
2012
|
-
annual_organic_carbon_inputs=annual_carbon_inputs,
|
|
2013
|
-
annual_n_contents=annual_n_contents,
|
|
2014
|
-
annual_lignin_contents=annual_lignin_contents,
|
|
2015
|
-
annual_tillage_categories=annual_tillage_categories,
|
|
2016
|
-
sand_content=sand_content,
|
|
2017
|
-
run_in_period=run_in_period,
|
|
2018
|
-
params=params
|
|
2019
|
-
)
|
|
2020
|
-
|
|
2021
|
-
values = [
|
|
2022
|
-
_calc_tier_2_soc_stock(
|
|
2023
|
-
active,
|
|
2024
|
-
slow,
|
|
2025
|
-
passive
|
|
2026
|
-
) for active, slow, passive in zip(
|
|
2027
|
-
result.active_pool_soc_stocks,
|
|
2028
|
-
result.slow_pool_soc_stocks,
|
|
2029
|
-
result.passive_pool_soc_stocks
|
|
2030
|
-
)
|
|
2031
|
-
]
|
|
2032
|
-
|
|
2033
|
-
# --- RETURN MEASUREMENT NODES ---
|
|
2034
|
-
|
|
2035
|
-
return [
|
|
2036
|
-
_measurement(
|
|
2037
|
-
year,
|
|
2038
|
-
value,
|
|
2039
|
-
MeasurementMethodClassification.TIER_2_MODEL.value
|
|
2040
|
-
) for year, value in zip(
|
|
2041
|
-
result.timestamps,
|
|
2042
|
-
values
|
|
2043
|
-
)
|
|
2044
|
-
]
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
# --- TIER 1 FUNCTIONS ---
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
def _retrieve_soc_ref(
|
|
2051
|
-
eco_climate_zone: int,
|
|
2052
|
-
ipcc_soil_category: IpccSoilCategory
|
|
2053
|
-
) -> float:
|
|
2054
|
-
"""
|
|
2055
|
-
Retrieve the soil organic carbon (SOC) reference value for a given combination of eco-climate zone
|
|
2056
|
-
and IPCC soil category.
|
|
2057
|
-
|
|
2058
|
-
See [IPCC (2019) Vol. 4, Ch. 2, Table 2.3](https://www.ipcc-nggip.iges.or.jp/public/2019rf/vol4.html)
|
|
2059
|
-
for more information.
|
|
2060
|
-
|
|
2061
|
-
Parameters
|
|
2062
|
-
----------
|
|
2063
|
-
eco_climate_zone : int
|
|
2064
|
-
The eco-climate zone identifier for the site corresponding to a row in the
|
|
2065
|
-
[ecoClimateZone](https://gitlab.com/hestia-earth/hestia-glossary/-/blob/develop/Measurements/ecoClimateZone-lookup.csv)
|
|
2066
|
-
lookup table.
|
|
2067
|
-
ipcc_soil_category : IpccSoilCategory
|
|
2068
|
-
The IPCC soil category of the site.
|
|
2069
|
-
|
|
2070
|
-
Returns
|
|
2071
|
-
-------
|
|
2072
|
-
float
|
|
2073
|
-
The reference condition soil organic carbon (SOC) stock in the 0-30cm depth interval, kg C ha-1.
|
|
2074
|
-
"""
|
|
2075
|
-
col_name = _get_eco_climate_zone_lookup_column(ipcc_soil_category)
|
|
2076
|
-
return get_ecoClimateZone_lookup_value(eco_climate_zone, col_name)
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
def _retrieve_soc_stock_factors(
|
|
2080
|
-
eco_climate_zone: int,
|
|
2081
|
-
ipcc_land_use_category: IpccLandUseCategory,
|
|
2082
|
-
ipcc_management_category: IpccManagementCategory,
|
|
2083
|
-
ipcc_carbon_input_category: IpccCarbonInputCategory
|
|
2084
|
-
) -> StockChangeFactors:
|
|
2085
|
-
"""
|
|
2086
|
-
Retrieve the stock change factors for soil organic carbon (SOC) based on a given combination of land use,
|
|
2087
|
-
management and carbon input.
|
|
2088
|
-
|
|
2089
|
-
Parameters
|
|
2090
|
-
----------
|
|
2091
|
-
eco_climate_zone : int
|
|
2092
|
-
The eco-climate zone identifier for the site corresponding to a row in the
|
|
2093
|
-
[ecoClimateZone](https://gitlab.com/hestia-earth/hestia-glossary/-/blob/develop/Measurements/ecoClimateZone-lookup.csv)
|
|
2094
|
-
lookup table.
|
|
2095
|
-
ipcc_land_use_category : IpccLandUseCategory
|
|
2096
|
-
The IPCC land use category for the inventory year.
|
|
2097
|
-
ipcc_management_category : IpccManagementCategory
|
|
2098
|
-
The IPCC land use category for the inventory year.
|
|
2099
|
-
ipcc_carbon_input_category : IpccCarbonInputCategory
|
|
2100
|
-
The IPCC land use category for the inventory year.
|
|
2101
|
-
|
|
2102
|
-
Returns
|
|
2103
|
-
-------
|
|
2104
|
-
StockChangeFactors
|
|
2105
|
-
A named tuple containing the retrieved stock change factors for SOC.
|
|
2106
|
-
"""
|
|
2107
|
-
DEFAULT_FACTOR = 1
|
|
2108
|
-
|
|
2109
|
-
EXCLUDED_LAND_USE_CATEGORIES = {
|
|
2110
|
-
IpccLandUseCategory.FOREST,
|
|
2111
|
-
IpccLandUseCategory.NATIVE,
|
|
2112
|
-
IpccLandUseCategory.OTHER
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
EXCLUDED_MANAGEMENT_CATEGORIES = {
|
|
2116
|
-
IpccManagementCategory.OTHER
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
EXCLUDED_CARBON_INPUT_CATEGORIES = {
|
|
2120
|
-
IpccCarbonInputCategory.OTHER
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
def get_factor(category, exclude_set):
|
|
2124
|
-
return (
|
|
2125
|
-
DEFAULT_FACTOR if category in exclude_set
|
|
2126
|
-
else get_ecoClimateZone_lookup_value(
|
|
2127
|
-
eco_climate_zone, _get_eco_climate_zone_lookup_column(category)
|
|
2128
|
-
)
|
|
2129
|
-
)
|
|
2130
|
-
|
|
2131
|
-
land_use_factor = get_factor(ipcc_land_use_category, EXCLUDED_LAND_USE_CATEGORIES)
|
|
2132
|
-
management_factor = get_factor(ipcc_management_category, EXCLUDED_MANAGEMENT_CATEGORIES)
|
|
2133
|
-
carbon_input_factor = get_factor(ipcc_carbon_input_category, EXCLUDED_CARBON_INPUT_CATEGORIES)
|
|
2134
|
-
|
|
2135
|
-
return StockChangeFactors(land_use_factor, management_factor, carbon_input_factor)
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
def _calc_soc_equilibrium(
|
|
2139
|
-
soc_ref: float,
|
|
2140
|
-
land_use_factor: float,
|
|
2141
|
-
management_factor: float,
|
|
2142
|
-
carbon_input_factor: float
|
|
2143
|
-
) -> float:
|
|
2144
|
-
"""
|
|
2145
|
-
Calculate the soil organic carbon (SOC) equilibrium based on reference SOC and factors.
|
|
2146
|
-
|
|
2147
|
-
In the tier 1 model, SOC equilibriums are considered to be reached after 20 years of consistant land use,
|
|
2148
|
-
management and carbon input.
|
|
2149
|
-
|
|
2150
|
-
Parameters
|
|
2151
|
-
----------
|
|
2152
|
-
soc_ref : float
|
|
2153
|
-
The reference condition SOC stock in the 0-30cm depth interval, kg C ha-1.
|
|
2154
|
-
land_use_factor : float
|
|
2155
|
-
The stock change factor for mineral soil organic C land-use systems or sub-systems
|
|
2156
|
-
for a particular land-use, dimensionless.
|
|
2157
|
-
management_factor : float
|
|
2158
|
-
The stock change factor for mineral soil organic C for management regime, dimensionless.
|
|
2159
|
-
carbon_input_factor : float
|
|
2160
|
-
The stock change factor for mineral soil organic C for the input of organic amendments, dimensionless.
|
|
2161
|
-
|
|
2162
|
-
Returns
|
|
2163
|
-
-------
|
|
2164
|
-
float
|
|
2165
|
-
The calculated SOC equilibrium, kg C ha-1.
|
|
2166
|
-
"""
|
|
2167
|
-
return soc_ref * land_use_factor * management_factor * carbon_input_factor
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
def _calc_regime_start_index(
|
|
2171
|
-
current_index: int, soc_equilibriums: list[float], default: Optional[int] = None
|
|
2172
|
-
) -> Optional[int]:
|
|
2173
|
-
"""
|
|
2174
|
-
Calculate the start index of the SOC regime based on the current index and equilibriums.
|
|
2175
|
-
|
|
2176
|
-
Parameters
|
|
2177
|
-
----------
|
|
2178
|
-
current_index : int
|
|
2179
|
-
The current index in the SOC equilibriums list.
|
|
2180
|
-
soc_equilibriums : list[float]
|
|
2181
|
-
List of SOC equilibriums.
|
|
2182
|
-
default : Any | None
|
|
2183
|
-
Default value to return if no suitable start index is found, by default `None`.
|
|
2184
|
-
|
|
2185
|
-
Returns
|
|
2186
|
-
-------
|
|
2187
|
-
int | None
|
|
2188
|
-
The calculated start index for the SOC regime.
|
|
2189
|
-
"""
|
|
2190
|
-
|
|
2191
|
-
def calc_forward_index(sliced_reverse_index: int) -> int:
|
|
2192
|
-
"""
|
|
2193
|
-
Calculate the forward index based on a sliced reverse index.
|
|
2194
|
-
"""
|
|
2195
|
-
return current_index - sliced_reverse_index - 1
|
|
2196
|
-
|
|
2197
|
-
current_soc_equilibrium = soc_equilibriums[current_index]
|
|
2198
|
-
sliced_reversed_soc_equilibriums = reversed(soc_equilibriums[0:current_index])
|
|
2199
|
-
|
|
2200
|
-
return next(
|
|
2201
|
-
(
|
|
2202
|
-
calc_forward_index(sliced_reverse_index) for sliced_reverse_index, prev_equilibrium
|
|
2203
|
-
in enumerate(sliced_reversed_soc_equilibriums)
|
|
2204
|
-
if not prev_equilibrium == current_soc_equilibrium
|
|
2205
|
-
),
|
|
2206
|
-
default
|
|
2207
|
-
)
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
def _iterate_soc_equilibriums(
|
|
2211
|
-
timestamps: list[int], soc_equilibriums: list[float]
|
|
2212
|
-
) -> tuple[list[int], list[float]]:
|
|
2213
|
-
"""
|
|
2214
|
-
Iterate over SOC equilibriums, inserting timestamps and soc_equilibriums for any missing years where SOC would have
|
|
2215
|
-
reached equilibrium.
|
|
2216
|
-
|
|
2217
|
-
Parameters
|
|
2218
|
-
----------
|
|
2219
|
-
timestamps : list[int]
|
|
2220
|
-
List of timestamps for each year in the inventory.
|
|
2221
|
-
soc_equilibriums : list[float]
|
|
2222
|
-
List of SOC equilibriums for each year in the inventory.
|
|
2223
|
-
|
|
2224
|
-
Returns
|
|
2225
|
-
-------
|
|
2226
|
-
tuple[list[int], list[float]]
|
|
2227
|
-
Updated `timestamps` and `soc_equilibriums`.
|
|
2228
|
-
"""
|
|
2229
|
-
iterated_timestamps = list(timestamps)
|
|
2230
|
-
iterated_soc_equilibriums = list(soc_equilibriums)
|
|
2231
|
-
|
|
2232
|
-
def calc_equilibrium_reached_timestamp(index: int) -> int:
|
|
2233
|
-
"""
|
|
2234
|
-
Calculate the timestamp when SOC equilibrium is reached based on the current index.
|
|
2235
|
-
"""
|
|
2236
|
-
regime_start_index = _calc_regime_start_index(index, soc_equilibriums)
|
|
2237
|
-
regime_start_timestamp = (
|
|
2238
|
-
timestamps[regime_start_index] if regime_start_index is not None
|
|
2239
|
-
else timestamps[0] - EQUILIBRIUM_TRANSITION_PERIOD
|
|
2240
|
-
)
|
|
2241
|
-
return regime_start_timestamp + EQUILIBRIUM_TRANSITION_PERIOD
|
|
2242
|
-
|
|
2243
|
-
def is_missing_equilibrium_year(
|
|
2244
|
-
timestamp: int, equilibrium_reached_timestamp: int
|
|
2245
|
-
) -> bool:
|
|
2246
|
-
"""
|
|
2247
|
-
Check if the given timestamp is after equilibrium and the equilibrium year is missing.
|
|
2248
|
-
"""
|
|
2249
|
-
return (
|
|
2250
|
-
timestamp > equilibrium_reached_timestamp
|
|
2251
|
-
and equilibrium_reached_timestamp not in iterated_timestamps
|
|
2252
|
-
)
|
|
2253
|
-
|
|
2254
|
-
for index, (timestamp, soc_equilibrium) in enumerate(zip(timestamps, soc_equilibriums)):
|
|
2255
|
-
equilibrium_reached_timestamp = calc_equilibrium_reached_timestamp(index)
|
|
2256
|
-
|
|
2257
|
-
if is_missing_equilibrium_year(timestamp, equilibrium_reached_timestamp):
|
|
2258
|
-
iterated_timestamps.insert(index, equilibrium_reached_timestamp)
|
|
2259
|
-
iterated_soc_equilibriums.insert(index, soc_equilibrium)
|
|
2260
|
-
|
|
2261
|
-
return iterated_timestamps, iterated_soc_equilibriums
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
def _run_soc_equilibriums(
|
|
2265
|
-
timestamps: list[int],
|
|
2266
|
-
ipcc_land_use_categories: list[IpccLandUseCategory],
|
|
2267
|
-
ipcc_management_categories: list[IpccManagementCategory],
|
|
2268
|
-
ipcc_carbon_input_categories: list[IpccCarbonInputCategory],
|
|
2269
|
-
eco_climate_zone: int,
|
|
2270
|
-
soc_ref: float
|
|
2271
|
-
) -> tuple[list[int], list[float]]:
|
|
2272
|
-
"""
|
|
2273
|
-
Run the soil organic carbon (SOC) equilibriums calculation for each year in the inventory.
|
|
2274
|
-
|
|
2275
|
-
Missing years where SOC equilibrium would be reached are inserted to allow for annual SOC change to be calculated
|
|
2276
|
-
correctly.
|
|
2277
|
-
|
|
2278
|
-
Parameters
|
|
2279
|
-
----------
|
|
2280
|
-
timestamps : list[int]
|
|
2281
|
-
A list of timestamps for each year in the inventory.
|
|
2282
|
-
ipcc_land_use_categories : list[IpccLandUseCategory]
|
|
2283
|
-
A list of IPCC land use categories for each year in the inventory.
|
|
2284
|
-
ipcc_management_categories : list[IpccManagementCategory]
|
|
2285
|
-
A list of IPCC management categories for each year in the inventory.
|
|
2286
|
-
ipcc_carbon_input_categories : list[IpccCarbonInputCategory]
|
|
2287
|
-
A list of IPCC carbon input categories for each year in the inventory.
|
|
2288
|
-
eco_climate_zone : int
|
|
2289
|
-
The eco-climate zone identifier for the site corresponding to a row in the
|
|
2290
|
-
[ecoClimateZone](https://gitlab.com/hestia-earth/hestia-glossary/-/blob/develop/Measurements/ecoClimateZone-lookup.csv)
|
|
2291
|
-
lookup table.
|
|
2292
|
-
soc_ref : float
|
|
2293
|
-
The reference condition SOC stock in the 0-30cm depth interval, kg C ha-1.
|
|
2294
|
-
|
|
2295
|
-
Returns
|
|
2296
|
-
-------
|
|
2297
|
-
tuple[list[int], list[float]]
|
|
2298
|
-
`timestamps` and `soc_equilibriums` for each year in the inventory, including any missing years where SOC
|
|
2299
|
-
equilibrium would have been reached.
|
|
2300
|
-
"""
|
|
2301
|
-
|
|
2302
|
-
# Calculate SOC equilibriums for each year
|
|
2303
|
-
soc_equilibriums = [
|
|
2304
|
-
_calc_soc_equilibrium(
|
|
2305
|
-
soc_ref,
|
|
2306
|
-
*_retrieve_soc_stock_factors(
|
|
2307
|
-
eco_climate_zone,
|
|
2308
|
-
land_use_category,
|
|
2309
|
-
management_category,
|
|
2310
|
-
carbon_input_category
|
|
2311
|
-
)
|
|
2312
|
-
) for land_use_category, management_category, carbon_input_category in zip(
|
|
2313
|
-
ipcc_land_use_categories,
|
|
2314
|
-
ipcc_management_categories,
|
|
2315
|
-
ipcc_carbon_input_categories
|
|
2316
|
-
)
|
|
2317
|
-
]
|
|
2318
|
-
|
|
2319
|
-
# Insert missing years where SOC equilibrium would have been reached
|
|
2320
|
-
iterated_timestamps, iterated_soc_equilibriums = (
|
|
2321
|
-
_iterate_soc_equilibriums(timestamps, soc_equilibriums)
|
|
2322
|
-
)
|
|
2323
|
-
|
|
2324
|
-
return iterated_timestamps, iterated_soc_equilibriums
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
def _calc_tier_1_soc_stocks(
|
|
2328
|
-
timestamps: list[int],
|
|
2329
|
-
soc_equilibriums: list[float],
|
|
2330
|
-
) -> list[float]:
|
|
2331
|
-
"""
|
|
2332
|
-
Calculate soil organic carbon (SOC) stocks (kg C ha-1) in the 0-30cm depth interval for each year in the inventory.
|
|
2333
|
-
|
|
2334
|
-
Parameters
|
|
2335
|
-
----------
|
|
2336
|
-
timestamps : list[int]
|
|
2337
|
-
A list of timestamps for each year in the inventory.
|
|
2338
|
-
soc_equilibriums : list[float]
|
|
2339
|
-
A list of SOC equilibriums for each year in the inventory.
|
|
2340
|
-
|
|
2341
|
-
Returns
|
|
2342
|
-
-------
|
|
2343
|
-
list[float]
|
|
2344
|
-
SOC stocks for each year in the inventory.
|
|
2345
|
-
"""
|
|
2346
|
-
soc_stocks = [soc_equilibriums[0]]
|
|
2347
|
-
|
|
2348
|
-
for index in range(1, len(soc_equilibriums)):
|
|
2349
|
-
|
|
2350
|
-
timestamp = timestamps[index]
|
|
2351
|
-
soc_equilibrium = soc_equilibriums[index]
|
|
2352
|
-
|
|
2353
|
-
regime_start_index = _calc_regime_start_index(index, soc_equilibriums)
|
|
2354
|
-
|
|
2355
|
-
regime_start_timestamp = (
|
|
2356
|
-
timestamps[regime_start_index]
|
|
2357
|
-
if regime_start_index is not None
|
|
2358
|
-
else timestamps[0] - EQUILIBRIUM_TRANSITION_PERIOD
|
|
2359
|
-
)
|
|
2360
|
-
|
|
2361
|
-
regime_start_soc_stock = soc_stocks[regime_start_index or 0]
|
|
2362
|
-
|
|
2363
|
-
regime_duration = timestamp - regime_start_timestamp
|
|
2364
|
-
|
|
2365
|
-
time_ratio = min(regime_duration / EQUILIBRIUM_TRANSITION_PERIOD, 1)
|
|
2366
|
-
soc_delta = (soc_equilibrium - regime_start_soc_stock) * time_ratio
|
|
2367
|
-
|
|
2368
|
-
soc_stocks.append(regime_start_soc_stock + soc_delta)
|
|
2369
|
-
|
|
2370
|
-
return soc_stocks
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
# --- GET THE ECO-CLIMATE ZONE FROM THE MEASUREMENTS ---
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
def _get_eco_climate_zone(measurements: list[dict]) -> Optional[int]:
|
|
2377
|
-
"""
|
|
2378
|
-
Get the eco-climate zone value from a list of measurements.
|
|
2379
|
-
|
|
2380
|
-
Parameters
|
|
2381
|
-
----------
|
|
2382
|
-
measurements : list[dict]
|
|
2383
|
-
A list of measurement nodes.
|
|
2384
|
-
|
|
2385
|
-
Returns
|
|
2386
|
-
-------
|
|
2387
|
-
int | None
|
|
2388
|
-
The eco-climate zone value if found, otherwise None.
|
|
2389
|
-
"""
|
|
2390
|
-
eco_climate_zone = find_term_match(measurements, "ecoClimateZone")
|
|
2391
|
-
return get_node_value(eco_climate_zone) or None
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
# --- ASSIGN IPCC SOIL CATEGORY TO SITE ---
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
def _check_soil_category(
|
|
2398
|
-
*,
|
|
2399
|
-
key: IpccSoilCategory,
|
|
2400
|
-
soil_types: list[dict],
|
|
2401
|
-
usda_soil_types: list[dict],
|
|
2402
|
-
**_
|
|
2403
|
-
) -> bool:
|
|
2404
|
-
"""
|
|
2405
|
-
Check if the soil category matches the given key.
|
|
2406
|
-
|
|
2407
|
-
Parameters
|
|
2408
|
-
----------
|
|
2409
|
-
key : IpccSoilCategory
|
|
2410
|
-
The IPCC soil category to check.
|
|
2411
|
-
soil_types : list[dict]
|
|
2412
|
-
List of soil type measurement nodes.
|
|
2413
|
-
usda_soil_types : list[dict]
|
|
2414
|
-
List of USDA soil type measurement nodes
|
|
2415
|
-
|
|
2416
|
-
Returns
|
|
2417
|
-
-------
|
|
2418
|
-
bool
|
|
2419
|
-
`True` if the soil category matches, `False` otherwise.
|
|
2420
|
-
"""
|
|
2421
|
-
SOIL_TYPE_LOOKUP = LOOKUPS["soilType"]
|
|
2422
|
-
USDA_SOIL_TYPE_LOOKUP = LOOKUPS["usdaSoilType"]
|
|
2423
|
-
|
|
2424
|
-
target_lookup_values = IPCC_SOIL_CATEGORY_TO_SOIL_TYPE_LOOKUP_VALUE.get(key, None)
|
|
2425
|
-
|
|
2426
|
-
is_soil_type_match = cumulative_nodes_lookup_match(
|
|
2427
|
-
soil_types,
|
|
2428
|
-
lookup=SOIL_TYPE_LOOKUP,
|
|
2429
|
-
target_lookup_values=target_lookup_values,
|
|
2430
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
2431
|
-
)
|
|
2432
|
-
|
|
2433
|
-
is_usda_soil_type_match = cumulative_nodes_lookup_match(
|
|
2434
|
-
usda_soil_types,
|
|
2435
|
-
lookup=USDA_SOIL_TYPE_LOOKUP,
|
|
2436
|
-
target_lookup_values=target_lookup_values,
|
|
2437
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
2438
|
-
)
|
|
2439
|
-
|
|
2440
|
-
return is_soil_type_match or is_usda_soil_type_match
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
def _check_sandy_soil_category(
|
|
2444
|
-
*,
|
|
2445
|
-
key: IpccSoilCategory,
|
|
2446
|
-
soil_types: list[dict],
|
|
2447
|
-
usda_soil_types: list[dict],
|
|
2448
|
-
has_sandy_soil: bool,
|
|
2449
|
-
**_
|
|
2450
|
-
) -> bool:
|
|
2451
|
-
"""
|
|
2452
|
-
Check if the soils are sandy.
|
|
2453
|
-
|
|
2454
|
-
This function is special case of `_check_soil_category`.
|
|
2455
|
-
|
|
2456
|
-
Parameters
|
|
2457
|
-
----------
|
|
2458
|
-
key : IpccSoilCategory
|
|
2459
|
-
The IPCC soil category to check.
|
|
2460
|
-
soil_types : list[dict]
|
|
2461
|
-
List of soil type measurement nodes.
|
|
2462
|
-
usda_soil_types : list[dict]
|
|
2463
|
-
List of USDA soil type measurement nodes
|
|
2464
|
-
has_sandy_soil : bool
|
|
2465
|
-
True if the soils are sandy, False otherwise.
|
|
2466
|
-
|
|
2467
|
-
Returns
|
|
2468
|
-
-------
|
|
2469
|
-
bool
|
|
2470
|
-
`True` if the soil category matches, `False` otherwise.
|
|
2471
|
-
"""
|
|
2472
|
-
return _check_soil_category(key=key, soil_types=soil_types, usda_soil_types=usda_soil_types) or has_sandy_soil
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
SOIL_CATEGORY_DECISION_TREE = {
|
|
2476
|
-
IpccSoilCategory.ORGANIC_SOILS: _check_soil_category,
|
|
2477
|
-
IpccSoilCategory.SANDY_SOILS: _check_sandy_soil_category,
|
|
2478
|
-
IpccSoilCategory.WETLAND_SOILS: _check_soil_category,
|
|
2479
|
-
IpccSoilCategory.VOLCANIC_SOILS: _check_soil_category,
|
|
2480
|
-
IpccSoilCategory.SPODIC_SOILS: _check_soil_category,
|
|
2481
|
-
IpccSoilCategory.HIGH_ACTIVITY_CLAY_SOILS: _check_soil_category,
|
|
2482
|
-
IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS: _check_soil_category
|
|
2483
|
-
}
|
|
2484
|
-
"""
|
|
2485
|
-
A decision tree mapping IPCC soil categories to corresponding check functions.
|
|
2486
|
-
|
|
2487
|
-
Key: IpccSoilCategory
|
|
2488
|
-
Value: Corresponding function for checking the match of the given soil category based on soil types.
|
|
2489
|
-
"""
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
def _assign_ipcc_soil_category(
|
|
2493
|
-
measurements: list[dict],
|
|
2494
|
-
default: IpccSoilCategory = IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS
|
|
2495
|
-
) -> IpccSoilCategory:
|
|
2496
|
-
"""
|
|
2497
|
-
Assign an IPCC soil category based on a site"s measurement nodes.
|
|
2498
|
-
|
|
2499
|
-
Parameters
|
|
2500
|
-
----------
|
|
2501
|
-
measurements : list[dict]
|
|
2502
|
-
List of measurement nodes.
|
|
2503
|
-
default : IpccSoilCategory, optional
|
|
2504
|
-
The default soil category if none matches, by default IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS.
|
|
2505
|
-
|
|
2506
|
-
Returns
|
|
2507
|
-
-------
|
|
2508
|
-
IpccSoilCategory
|
|
2509
|
-
The assigned IPCC soil category.
|
|
2510
|
-
"""
|
|
2511
|
-
soil_types = filter_list_term_type(measurements, TermTermType.SOILTYPE)
|
|
2512
|
-
usda_soil_types = filter_list_term_type(measurements, TermTermType.USDASOILTYPE)
|
|
2513
|
-
|
|
2514
|
-
clay_content = get_node_value(find_term_match(measurements, CLAY_CONTENT_TERM_ID))
|
|
2515
|
-
sand_content = get_node_value(find_term_match(measurements, SAND_CONTENT_TERM_ID))
|
|
2516
|
-
|
|
2517
|
-
has_sandy_soil = clay_content < CLAY_CONTENT_MAX and sand_content > SAND_CONTENT_MIN
|
|
2518
|
-
|
|
2519
|
-
return next(
|
|
2520
|
-
(
|
|
2521
|
-
key for key in SOIL_CATEGORY_DECISION_TREE
|
|
2522
|
-
if SOIL_CATEGORY_DECISION_TREE[key](
|
|
2523
|
-
key=key,
|
|
2524
|
-
soil_types=soil_types,
|
|
2525
|
-
usda_soil_types=usda_soil_types,
|
|
2526
|
-
has_sandy_soil=has_sandy_soil
|
|
2527
|
-
)
|
|
2528
|
-
),
|
|
2529
|
-
default
|
|
2530
|
-
) if len(soil_types) > 0 or len(usda_soil_types) > 0 else default
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
# --- ASSIGN IPCC LAND USE CATEGORY ---
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
def _has_irrigation(water_regime_nodes: list[dict]) -> bool:
|
|
2537
|
-
"""
|
|
2538
|
-
Check if irrigation is present in the water regime nodes.
|
|
2539
|
-
|
|
2540
|
-
Parameters
|
|
2541
|
-
----------
|
|
2542
|
-
water_regime_nodes : list[dict]
|
|
2543
|
-
List of water regime nodes to be checked.
|
|
2544
|
-
|
|
2545
|
-
Returns
|
|
2546
|
-
-------
|
|
2547
|
-
bool
|
|
2548
|
-
`True` if irrigation is present, `False` otherwise.
|
|
2549
|
-
"""
|
|
2550
|
-
return cumulative_nodes_term_match(
|
|
2551
|
-
water_regime_nodes,
|
|
2552
|
-
target_term_ids=get_irrigated_terms(),
|
|
2553
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
2554
|
-
)
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
def _has_long_fallow(land_cover_nodes: list[dict]) -> bool:
|
|
2558
|
-
"""
|
|
2559
|
-
Check if long fallow terms are present in the land cover nodes.
|
|
2560
|
-
|
|
2561
|
-
n.b., a super majority of the site area must be under long fallow for it to be classified as set aside.
|
|
2562
|
-
|
|
2563
|
-
Parameters
|
|
2564
|
-
----------
|
|
2565
|
-
land_cover_nodes : list[dict]
|
|
2566
|
-
List of land cover nodes to be checked.
|
|
2567
|
-
|
|
2568
|
-
Returns
|
|
2569
|
-
-------
|
|
2570
|
-
bool
|
|
2571
|
-
`True` if long fallow is present, `False` otherwise.
|
|
2572
|
-
"""
|
|
2573
|
-
LOOKUP = LOOKUPS["landCover"][0]
|
|
2574
|
-
TARGET_LOOKUP_VALUE = "Set aside"
|
|
2575
|
-
return cumulative_nodes_lookup_match(
|
|
2576
|
-
land_cover_nodes,
|
|
2577
|
-
lookup=LOOKUP,
|
|
2578
|
-
target_lookup_values=TARGET_LOOKUP_VALUE,
|
|
2579
|
-
cumulative_threshold=SUPER_MAJORITY_AREA_THRESHOLD
|
|
2580
|
-
) or cumulative_nodes_match(
|
|
2581
|
-
lambda node: get_node_property(node, LONG_FALLOW_CROP_TERM_ID, False).get("value", 0),
|
|
2582
|
-
land_cover_nodes,
|
|
2583
|
-
cumulative_threshold=SUPER_MAJORITY_AREA_THRESHOLD
|
|
2584
|
-
)
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
def _has_upland_rice(land_cover_nodes: list[dict]) -> bool:
|
|
2588
|
-
"""
|
|
2589
|
-
Check if upland rice is present in the land cover nodes.
|
|
2590
|
-
|
|
2591
|
-
Parameters
|
|
2592
|
-
----------
|
|
2593
|
-
land_cover_nodes : list[dict]
|
|
2594
|
-
List of land cover nodes to be checked.
|
|
2595
|
-
|
|
2596
|
-
Returns
|
|
2597
|
-
-------
|
|
2598
|
-
bool
|
|
2599
|
-
`True` if upland rice is present, `False` otherwise.
|
|
2600
|
-
"""
|
|
2601
|
-
return cumulative_nodes_term_match(
|
|
2602
|
-
land_cover_nodes,
|
|
2603
|
-
target_term_ids=get_upland_rice_land_cover_terms(),
|
|
2604
|
-
cumulative_threshold=SUPER_MAJORITY_AREA_THRESHOLD
|
|
2605
|
-
)
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
IPCC_LAND_USE_CATEGORY_TO_VALIDATION_KWARGS = {
|
|
2609
|
-
IpccLandUseCategory.ANNUAL_CROPS_WET: {"has_wetland_soils"},
|
|
2610
|
-
IpccLandUseCategory.SET_ASIDE: {"has_long_fallow"},
|
|
2611
|
-
}
|
|
2612
|
-
"""
|
|
2613
|
-
Keyword arguments that need to be validated in addition to the `landCover` lookup match for specific
|
|
2614
|
-
`IpccLandUseCategory`s.
|
|
2615
|
-
"""
|
|
2616
|
-
|
|
2617
|
-
IPCC_LAND_USE_CATEGORY_TO_OVERRIDE_KWARGS = {
|
|
2618
|
-
IpccLandUseCategory.PADDY_RICE_CULTIVATION: {"has_irrigated_upland_rice"}
|
|
2619
|
-
}
|
|
2620
|
-
"""
|
|
2621
|
-
Keyword arguments that can override the `landCover` lookup match for specific `IpccLandUseCategory`s.
|
|
2622
|
-
"""
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
def _check_ipcc_land_use_category(*, key: IpccLandUseCategory, land_cover_nodes: list[dict], **kwargs) -> bool:
|
|
2626
|
-
"""
|
|
2627
|
-
Check if the land cover nodes and keyword args satisfy the requirements for the given key.
|
|
2628
|
-
|
|
2629
|
-
Parameters
|
|
2630
|
-
----------
|
|
2631
|
-
key : IpccLandUseCategory
|
|
2632
|
-
The IPCC land use category to check.
|
|
2633
|
-
land_cover_nodes : list[dict]
|
|
2634
|
-
List of land cover nodes to be checked.
|
|
2635
|
-
|
|
2636
|
-
Keyword Args
|
|
2637
|
-
------------
|
|
2638
|
-
has_irrigated_upland_rice : bool
|
|
2639
|
-
Indicates whether irrigated upland rice is present on more than 30% of the site.
|
|
2640
|
-
has_long_fallow : bool
|
|
2641
|
-
Indicates whether long fallow is present on more than 70% of the site.
|
|
2642
|
-
has_wetland_soils : bool
|
|
2643
|
-
Indicates whether wetland soils are present to more than 30% of the site.
|
|
2644
|
-
|
|
2645
|
-
Returns
|
|
2646
|
-
-------
|
|
2647
|
-
bool
|
|
2648
|
-
`True` if the conditions match the specified land use category, `False` otherwise.
|
|
2649
|
-
"""
|
|
2650
|
-
LOOKUP = LOOKUPS["landCover"][0]
|
|
2651
|
-
target_lookup_values = IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE.get(key, None)
|
|
2652
|
-
valid_lookup = cumulative_nodes_lookup_match(
|
|
2653
|
-
land_cover_nodes,
|
|
2654
|
-
lookup=LOOKUP,
|
|
2655
|
-
target_lookup_values=target_lookup_values,
|
|
2656
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
2657
|
-
)
|
|
2658
|
-
|
|
2659
|
-
validation_kwargs = IPCC_LAND_USE_CATEGORY_TO_VALIDATION_KWARGS.get(key, set())
|
|
2660
|
-
valid_kwargs = all(v for k, v in kwargs.items() if k in validation_kwargs)
|
|
2661
|
-
|
|
2662
|
-
override_kwargs = IPCC_LAND_USE_CATEGORY_TO_OVERRIDE_KWARGS.get(key, set())
|
|
2663
|
-
valid_override = any(v for k, v in kwargs.items() if k in override_kwargs)
|
|
2664
|
-
|
|
2665
|
-
return (valid_lookup and valid_kwargs) or valid_override
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
LAND_USE_CATEGORY_DECISION_TREE = {
|
|
2669
|
-
IpccLandUseCategory.GRASSLAND: _check_ipcc_land_use_category,
|
|
2670
|
-
IpccLandUseCategory.SET_ASIDE: _check_ipcc_land_use_category,
|
|
2671
|
-
IpccLandUseCategory.PERENNIAL_CROPS: _check_ipcc_land_use_category,
|
|
2672
|
-
IpccLandUseCategory.PADDY_RICE_CULTIVATION: _check_ipcc_land_use_category,
|
|
2673
|
-
IpccLandUseCategory.ANNUAL_CROPS_WET: _check_ipcc_land_use_category,
|
|
2674
|
-
IpccLandUseCategory.ANNUAL_CROPS: _check_ipcc_land_use_category,
|
|
2675
|
-
IpccLandUseCategory.FOREST: _check_ipcc_land_use_category,
|
|
2676
|
-
IpccLandUseCategory.NATIVE: _check_ipcc_land_use_category,
|
|
2677
|
-
IpccLandUseCategory.OTHER: _check_ipcc_land_use_category
|
|
2678
|
-
}
|
|
2679
|
-
"""
|
|
2680
|
-
A decision tree mapping IPCC soil categories to corresponding check functions.
|
|
2681
|
-
|
|
2682
|
-
Key: IpccLandUseCategory
|
|
2683
|
-
Value: Corresponding function for checking the match of the given land use category based on land cover nodes
|
|
2684
|
-
and additional kwargs.
|
|
2685
|
-
"""
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
def _assign_ipcc_land_use_category(
|
|
2689
|
-
management_nodes: list[dict], ipcc_soil_category: IpccSoilCategory,
|
|
2690
|
-
) -> IpccLandUseCategory:
|
|
2691
|
-
"""
|
|
2692
|
-
Assigns IPCC land use category based on management nodes and soil category.
|
|
2693
|
-
|
|
2694
|
-
Parameters
|
|
2695
|
-
----------
|
|
2696
|
-
management_nodes : list[dict]
|
|
2697
|
-
List of management nodes.
|
|
2698
|
-
ipcc_soil_category : IpccSoilCategory
|
|
2699
|
-
The site"s assigned IPCC soil category.
|
|
2700
|
-
|
|
2701
|
-
Returns
|
|
2702
|
-
-------
|
|
2703
|
-
IpccLandUseCategory
|
|
2704
|
-
Assigned IPCC land use category.
|
|
2705
|
-
"""
|
|
2706
|
-
DECISION_TREE = LAND_USE_CATEGORY_DECISION_TREE
|
|
2707
|
-
DEFAULT = IpccLandUseCategory.OTHER
|
|
2708
|
-
|
|
2709
|
-
land_cover_nodes = filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])
|
|
2710
|
-
water_regime_nodes = filter_list_term_type(management_nodes, [TermTermType.WATERREGIME])
|
|
2711
|
-
|
|
2712
|
-
has_irrigation = _has_irrigation(water_regime_nodes)
|
|
2713
|
-
has_upland_rice = _has_upland_rice(land_cover_nodes)
|
|
2714
|
-
has_irrigated_upland_rice = has_upland_rice and has_irrigation
|
|
2715
|
-
has_long_fallow = _has_long_fallow(land_cover_nodes)
|
|
2716
|
-
has_wetland_soils = ipcc_soil_category is IpccSoilCategory.WETLAND_SOILS
|
|
2717
|
-
|
|
2718
|
-
should_run = bool(land_cover_nodes)
|
|
2719
|
-
|
|
2720
|
-
return next(
|
|
2721
|
-
(
|
|
2722
|
-
key for key in DECISION_TREE
|
|
2723
|
-
if DECISION_TREE[key](
|
|
2724
|
-
key=key,
|
|
2725
|
-
land_cover_nodes=land_cover_nodes,
|
|
2726
|
-
has_long_fallow=has_long_fallow,
|
|
2727
|
-
has_irrigated_upland_rice=has_irrigated_upland_rice,
|
|
2728
|
-
has_wetland_soils=has_wetland_soils
|
|
2729
|
-
)
|
|
2730
|
-
),
|
|
2731
|
-
DEFAULT
|
|
2732
|
-
) if should_run else DEFAULT
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
# --- ASSIGN IPCC MANAGEMENT CATEGORY ---
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
def _check_grassland_ipcc_management_category(
|
|
2739
|
-
*, key: IpccManagementCategory, land_cover_nodes: list[dict], **_
|
|
2740
|
-
) -> bool:
|
|
2741
|
-
"""
|
|
2742
|
-
Check if the land cover nodes match the target conditions for a grassland IpccManagementCategory.
|
|
2743
|
-
|
|
2744
|
-
Parameters
|
|
2745
|
-
----------
|
|
2746
|
-
key : IpccManagementCategory
|
|
2747
|
-
The IPCC management category to check.
|
|
2748
|
-
land_cover_nodes : list[dict]
|
|
2749
|
-
List of land cover nodes to be checked.
|
|
2750
|
-
|
|
2751
|
-
Returns
|
|
2752
|
-
-------
|
|
2753
|
-
bool
|
|
2754
|
-
`True` if the conditions match the specified management category, `False` otherwise.
|
|
2755
|
-
"""
|
|
2756
|
-
target_term_id = IPCC_MANAGEMENT_CATEGORY_TO_GRASSLAND_MANAGEMENT_TERM_ID.get(key, None)
|
|
2757
|
-
return cumulative_nodes_term_match(
|
|
2758
|
-
land_cover_nodes,
|
|
2759
|
-
target_term_ids=target_term_id,
|
|
2760
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
2761
|
-
)
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
def _check_tillage_ipcc_management_category(
|
|
2765
|
-
*, key: IpccManagementCategory, tillage_nodes: list[dict], **_
|
|
2766
|
-
) -> bool:
|
|
2767
|
-
"""
|
|
2768
|
-
Check if the tillage nodes match the target conditions for a tillage IpccManagementCategory.
|
|
2769
|
-
|
|
2770
|
-
Parameters
|
|
2771
|
-
----------
|
|
2772
|
-
key : IpccManagementCategory
|
|
2773
|
-
The IPCC management category to check.
|
|
2774
|
-
tillage_nodes : list[dict]
|
|
2775
|
-
List of tillage nodes to be checked.
|
|
2776
|
-
|
|
2777
|
-
Returns
|
|
2778
|
-
-------
|
|
2779
|
-
bool
|
|
2780
|
-
`True` if the conditions match the specified management category, `False` otherwise.
|
|
2781
|
-
"""
|
|
2782
|
-
LOOKUP = LOOKUPS["tillage"]
|
|
2783
|
-
target_lookup_values = IPCC_MANAGEMENT_CATEGORY_TO_TILLAGE_MANAGEMENT_LOOKUP_VALUE.get(key, None)
|
|
2784
|
-
return cumulative_nodes_lookup_match(
|
|
2785
|
-
tillage_nodes,
|
|
2786
|
-
lookup=LOOKUP,
|
|
2787
|
-
target_lookup_values=target_lookup_values,
|
|
2788
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
2789
|
-
)
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
GRASSLAND_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE = {
|
|
2793
|
-
IpccManagementCategory.SEVERELY_DEGRADED: _check_grassland_ipcc_management_category,
|
|
2794
|
-
IpccManagementCategory.IMPROVED_GRASSLAND: _check_grassland_ipcc_management_category,
|
|
2795
|
-
IpccManagementCategory.HIGH_INTENSITY_GRAZING: _check_grassland_ipcc_management_category,
|
|
2796
|
-
IpccManagementCategory.NOMINALLY_MANAGED: _check_grassland_ipcc_management_category,
|
|
2797
|
-
IpccManagementCategory.OTHER: _check_grassland_ipcc_management_category
|
|
2798
|
-
}
|
|
2799
|
-
"""
|
|
2800
|
-
Decision tree mapping IPCC management categories to corresponding check functions for grassland.
|
|
2801
|
-
|
|
2802
|
-
Key: IpccManagementCategory
|
|
2803
|
-
Value: Corresponding function for checking the match of the given management category based on land cover nodes.
|
|
2804
|
-
"""
|
|
2805
|
-
|
|
2806
|
-
TILLAGE_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE = {
|
|
2807
|
-
IpccManagementCategory.FULL_TILLAGE: _check_tillage_ipcc_management_category,
|
|
2808
|
-
IpccManagementCategory.REDUCED_TILLAGE: _check_tillage_ipcc_management_category,
|
|
2809
|
-
IpccManagementCategory.NO_TILLAGE: _check_tillage_ipcc_management_category
|
|
2810
|
-
}
|
|
2811
|
-
"""
|
|
2812
|
-
Decision tree mapping IPCC management categories to corresponding check functions for tillage.
|
|
2813
|
-
|
|
2814
|
-
Key: IpccManagementCategory
|
|
2815
|
-
Value: Corresponding function for checking the match of the given management category based on tillage nodes.
|
|
2816
|
-
"""
|
|
2817
|
-
|
|
2818
|
-
IPCC_LAND_USE_CATEGORY_TO_DECISION_TREE = {
|
|
2819
|
-
IpccLandUseCategory.GRASSLAND: GRASSLAND_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE,
|
|
2820
|
-
IpccLandUseCategory.ANNUAL_CROPS_WET: TILLAGE_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE,
|
|
2821
|
-
IpccLandUseCategory.ANNUAL_CROPS: TILLAGE_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE
|
|
2822
|
-
}
|
|
2823
|
-
"""
|
|
2824
|
-
Decision tree mapping IPCC land use categories to corresponding decision trees for management categories.
|
|
2825
|
-
|
|
2826
|
-
Key: IpccLandUseCategory
|
|
2827
|
-
Value: Corresponding decision tree for IPCC management categories based on land use categories.
|
|
2828
|
-
"""
|
|
2829
|
-
|
|
2830
|
-
IPCC_LAND_USE_CATEGORY_TO_DEFAULT_IPCC_MANAGEMENT_CATEGORY = {
|
|
2831
|
-
IpccLandUseCategory.GRASSLAND: IpccManagementCategory.NOMINALLY_MANAGED,
|
|
2832
|
-
IpccLandUseCategory.ANNUAL_CROPS_WET: IpccManagementCategory.FULL_TILLAGE,
|
|
2833
|
-
IpccLandUseCategory.ANNUAL_CROPS: IpccManagementCategory.FULL_TILLAGE
|
|
2834
|
-
}
|
|
2835
|
-
"""
|
|
2836
|
-
Mapping of default IPCC management categories for each IPCC land use category.
|
|
2837
|
-
|
|
2838
|
-
Key: IpccLandUseCategory
|
|
2839
|
-
Value: Default IPCC management category for the given land use category.
|
|
2840
|
-
"""
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
def _assign_ipcc_management_category(
|
|
2844
|
-
management_nodes: list[dict], ipcc_land_use_category: IpccLandUseCategory
|
|
2845
|
-
) -> IpccManagementCategory:
|
|
2846
|
-
"""
|
|
2847
|
-
Assign an IPCC Management Category based on the given management nodes and IPCC Land Use Category.
|
|
2848
|
-
|
|
2849
|
-
Parameters
|
|
2850
|
-
----------
|
|
2851
|
-
management_nodes : list[dict]
|
|
2852
|
-
List of management nodes.
|
|
2853
|
-
ipcc_land_use_category : IpccLandUseCategory
|
|
2854
|
-
The IPCC Land Use Category.
|
|
2855
|
-
|
|
2856
|
-
Returns
|
|
2857
|
-
-------
|
|
2858
|
-
IpccManagementCategory
|
|
2859
|
-
The assigned IPCC Management Category.
|
|
2860
|
-
"""
|
|
2861
|
-
decision_tree = IPCC_LAND_USE_CATEGORY_TO_DECISION_TREE.get(ipcc_land_use_category, {})
|
|
2862
|
-
default = IPCC_LAND_USE_CATEGORY_TO_DEFAULT_IPCC_MANAGEMENT_CATEGORY.get(
|
|
2863
|
-
ipcc_land_use_category, IpccManagementCategory.OTHER
|
|
2864
|
-
)
|
|
2865
|
-
|
|
2866
|
-
land_cover_nodes = filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])
|
|
2867
|
-
tillage_nodes = filter_list_term_type(management_nodes, [TermTermType.TILLAGE])
|
|
2868
|
-
|
|
2869
|
-
should_run = any([
|
|
2870
|
-
decision_tree == GRASSLAND_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE and len(land_cover_nodes) > 0,
|
|
2871
|
-
decision_tree == TILLAGE_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE and len(tillage_nodes) > 0
|
|
2872
|
-
])
|
|
2873
|
-
|
|
2874
|
-
return next(
|
|
2875
|
-
(
|
|
2876
|
-
key for key in decision_tree
|
|
2877
|
-
if decision_tree[key](
|
|
2878
|
-
key=key,
|
|
2879
|
-
land_cover_nodes=land_cover_nodes,
|
|
2880
|
-
tillage_nodes=tillage_nodes,
|
|
2881
|
-
)
|
|
2882
|
-
),
|
|
2883
|
-
default
|
|
2884
|
-
) if should_run else default
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
# --- ASSIGN IPCC CARBON INPUT CATEGORY ---
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
GRASSLAND_IPCC_CARBON_INPUT_CATEGORY_TO_MIN_NUM_IMPROVEMENTS = {
|
|
2891
|
-
IpccCarbonInputCategory.GRASSLAND_HIGH: 2,
|
|
2892
|
-
IpccCarbonInputCategory.GRASSLAND_MEDIUM: 1
|
|
2893
|
-
}
|
|
2894
|
-
"""
|
|
2895
|
-
A mapping from IPCC Grassland Carbon Input Categories to the minimum number of improvements required.
|
|
2896
|
-
|
|
2897
|
-
Key: IpccCarbonInputCategory
|
|
2898
|
-
Value: Minimum number of improvements required for the corresponding Grassland Carbon Input Category.
|
|
2899
|
-
"""
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
def _check_grassland_ipcc_carbon_input_category(
|
|
2903
|
-
*, key: IpccCarbonInputCategory, num_grassland_improvements: int, **_,
|
|
2904
|
-
) -> bool:
|
|
2905
|
-
"""
|
|
2906
|
-
Checks if the given carbon input arguments satisfy the conditions for a specific
|
|
2907
|
-
Grassland IPCC Carbon Input Category.
|
|
2908
|
-
|
|
2909
|
-
Parameters
|
|
2910
|
-
----------
|
|
2911
|
-
key : IpccCarbonInputCategory
|
|
2912
|
-
The grassland IPCC Carbon Input Category to check.
|
|
2913
|
-
num_grassland_improvements : int
|
|
2914
|
-
The number of grassland improvements.
|
|
2915
|
-
|
|
2916
|
-
Returns
|
|
2917
|
-
-------
|
|
2918
|
-
bool
|
|
2919
|
-
`True` if the conditions for the specified category are met; otherwise, `False`.
|
|
2920
|
-
"""
|
|
2921
|
-
return num_grassland_improvements >= GRASSLAND_IPCC_CARBON_INPUT_CATEGORY_TO_MIN_NUM_IMPROVEMENTS[key]
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
def _check_cropland_high_with_manure_category(
|
|
2925
|
-
*,
|
|
2926
|
-
has_animal_manure_used: bool,
|
|
2927
|
-
has_bare_fallow: bool,
|
|
2928
|
-
has_low_residue_producing_crops: bool,
|
|
2929
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used: bool,
|
|
2930
|
-
has_residue_removed_or_burnt: bool,
|
|
2931
|
-
**_
|
|
2932
|
-
) -> Optional[int]:
|
|
2933
|
-
"""
|
|
2934
|
-
Checks the Cropland High with Manure IPCC Carbon Input Category based on the given carbon input arguments.
|
|
2935
|
-
|
|
2936
|
-
Parameters
|
|
2937
|
-
----------
|
|
2938
|
-
has_animal_manure_used : bool
|
|
2939
|
-
Indicates whether animal manure is used on more than 30% of the site.
|
|
2940
|
-
has_bare_fallow : bool
|
|
2941
|
-
Indicates whether bare fallow is present on more than 30% of the site.
|
|
2942
|
-
has_low_residue_producing_crops : bool
|
|
2943
|
-
Indicates whether low residue-producing crops are present on more than 70% of the site.
|
|
2944
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used : bool
|
|
2945
|
-
Indicates whether a nitrogen-fixing crop or inorganic nitrogen fertiliser is used on more than 30% of the site.
|
|
2946
|
-
has_residue_removed_or_burnt : bool
|
|
2947
|
-
Indicates whether residues are removed or burnt on more than 30% of the site.
|
|
2948
|
-
|
|
2949
|
-
Returns
|
|
2950
|
-
-------
|
|
2951
|
-
int | none
|
|
2952
|
-
The category key if conditions are met; otherwise, `None`.
|
|
2953
|
-
"""
|
|
2954
|
-
conditions = {
|
|
2955
|
-
1: all([
|
|
2956
|
-
not has_residue_removed_or_burnt,
|
|
2957
|
-
not has_low_residue_producing_crops,
|
|
2958
|
-
not has_bare_fallow,
|
|
2959
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
2960
|
-
has_animal_manure_used
|
|
2961
|
-
])
|
|
2962
|
-
}
|
|
2963
|
-
|
|
2964
|
-
return next(
|
|
2965
|
-
(key for key, condition in conditions.items() if condition), None
|
|
2966
|
-
)
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
def _check_cropland_high_without_manure_category(
|
|
2970
|
-
*,
|
|
2971
|
-
has_animal_manure_used: bool,
|
|
2972
|
-
has_bare_fallow: bool,
|
|
2973
|
-
has_cover_crop: bool,
|
|
2974
|
-
has_irrigation: bool,
|
|
2975
|
-
has_low_residue_producing_crops: bool,
|
|
2976
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used: bool,
|
|
2977
|
-
has_organic_fertiliser_or_soil_amendment_used: bool,
|
|
2978
|
-
has_practice_increasing_c_input: bool,
|
|
2979
|
-
has_residue_removed_or_burnt: bool,
|
|
2980
|
-
**_
|
|
2981
|
-
) -> Optional[int]:
|
|
2982
|
-
"""
|
|
2983
|
-
Checks the Cropland High without Manure IPCC Carbon Input Category based on the given carbon input arguments.
|
|
2984
|
-
|
|
2985
|
-
Parameters
|
|
2986
|
-
----------
|
|
2987
|
-
has_animal_manure_used : bool
|
|
2988
|
-
Indicates whether animal manure is used on more than 30% of the site.
|
|
2989
|
-
has_bare_fallow : bool
|
|
2990
|
-
Indicates whether bare fallow is present on more than 30% of the site.
|
|
2991
|
-
has_cover_crop : bool
|
|
2992
|
-
Indicates whether cover crops are present on more than 30% of the site.
|
|
2993
|
-
has_irrigation : bool
|
|
2994
|
-
Indicates whether irrigation is applied to more than 30% of the site.
|
|
2995
|
-
has_low_residue_producing_crops : bool
|
|
2996
|
-
Indicates whether low residue-producing crops are present on more than 70% of the site.
|
|
2997
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used : bool
|
|
2998
|
-
Indicates whether a nitrogen-fixing crop or inorganic nitrogen fertiliser is used on more than 30% of the site.
|
|
2999
|
-
has_organic_fertiliser_or_soil_amendment_used : bool
|
|
3000
|
-
Indicates whether organic fertiliser or soil amendments are used on more than 30% of the site.
|
|
3001
|
-
has_practice_increasing_c_input : bool
|
|
3002
|
-
Indicates whether practices increasing carbon input are present on more than 30% of the site.
|
|
3003
|
-
has_residue_removed_or_burnt : bool
|
|
3004
|
-
Indicates whether residues are removed or burnt on more than 30% of the site.
|
|
3005
|
-
|
|
3006
|
-
Returns
|
|
3007
|
-
-------
|
|
3008
|
-
int | None
|
|
3009
|
-
The category key if conditions are met; otherwise, `None`.
|
|
3010
|
-
"""
|
|
3011
|
-
conditions = {
|
|
3012
|
-
1: all([
|
|
3013
|
-
not has_residue_removed_or_burnt,
|
|
3014
|
-
not has_low_residue_producing_crops,
|
|
3015
|
-
not has_bare_fallow,
|
|
3016
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
3017
|
-
any([
|
|
3018
|
-
has_irrigation,
|
|
3019
|
-
has_practice_increasing_c_input,
|
|
3020
|
-
has_cover_crop,
|
|
3021
|
-
has_organic_fertiliser_or_soil_amendment_used
|
|
3022
|
-
]),
|
|
3023
|
-
not has_animal_manure_used
|
|
3024
|
-
])
|
|
3025
|
-
}
|
|
3026
|
-
|
|
3027
|
-
return next(
|
|
3028
|
-
(key for key, condition in conditions.items() if condition), None
|
|
3029
|
-
)
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
def _check_cropland_medium_category(
|
|
3033
|
-
*,
|
|
3034
|
-
has_animal_manure_used: bool,
|
|
3035
|
-
has_bare_fallow: bool,
|
|
3036
|
-
has_cover_crop: bool,
|
|
3037
|
-
has_irrigation: bool,
|
|
3038
|
-
has_low_residue_producing_crops: bool,
|
|
3039
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used: bool,
|
|
3040
|
-
has_organic_fertiliser_or_soil_amendment_used: bool,
|
|
3041
|
-
has_practice_increasing_c_input: bool,
|
|
3042
|
-
has_residue_removed_or_burnt: bool,
|
|
3043
|
-
**_
|
|
3044
|
-
) -> Optional[int]:
|
|
3045
|
-
"""
|
|
3046
|
-
Checks the Cropland Medium IPCC Carbon Input Category based on the given carbon input arguments.
|
|
3047
|
-
|
|
3048
|
-
Parameters
|
|
3049
|
-
----------
|
|
3050
|
-
has_animal_manure_used : bool
|
|
3051
|
-
Indicates whether animal manure is used on more than 30% of the site.
|
|
3052
|
-
has_bare_fallow : bool
|
|
3053
|
-
Indicates whether bare fallow is present on more than 30% of the site.
|
|
3054
|
-
has_cover_crop : bool
|
|
3055
|
-
Indicates whether cover crops are present on more than 30% of the site.
|
|
3056
|
-
has_irrigation : bool
|
|
3057
|
-
Indicates whether irrigation is applied to more than 30% of the site.
|
|
3058
|
-
has_low_residue_producing_crops : bool
|
|
3059
|
-
Indicates whether low residue-producing crops are present on more than 70% of the site.
|
|
3060
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used : bool
|
|
3061
|
-
Indicates whether a nitrogen-fixing crop or inorganic nitrogen fertiliser is used on more than 30% of the site.
|
|
3062
|
-
has_organic_fertiliser_or_soil_amendment_used : bool
|
|
3063
|
-
Indicates whether organic fertiliser or soil amendments are used on more than 30% of the site.
|
|
3064
|
-
has_practice_increasing_c_input : bool
|
|
3065
|
-
Indicates whether practices increasing carbon input are present on more than 30% of the site.
|
|
3066
|
-
has_residue_removed_or_burnt : bool
|
|
3067
|
-
Indicates whether residues are removed or burnt on more than 30% of the site.
|
|
3068
|
-
|
|
3069
|
-
Returns
|
|
3070
|
-
-------
|
|
3071
|
-
int | None
|
|
3072
|
-
The category key if conditions are met; otherwise, `None`.
|
|
3073
|
-
"""
|
|
3074
|
-
conditions = {
|
|
3075
|
-
1: all([
|
|
3076
|
-
has_residue_removed_or_burnt,
|
|
3077
|
-
has_animal_manure_used
|
|
3078
|
-
]),
|
|
3079
|
-
2: all([
|
|
3080
|
-
not has_residue_removed_or_burnt,
|
|
3081
|
-
any([
|
|
3082
|
-
has_low_residue_producing_crops,
|
|
3083
|
-
has_bare_fallow
|
|
3084
|
-
]),
|
|
3085
|
-
any([
|
|
3086
|
-
has_irrigation,
|
|
3087
|
-
has_practice_increasing_c_input,
|
|
3088
|
-
has_cover_crop,
|
|
3089
|
-
has_organic_fertiliser_or_soil_amendment_used,
|
|
3090
|
-
])
|
|
3091
|
-
]),
|
|
3092
|
-
3: all([
|
|
3093
|
-
not has_residue_removed_or_burnt,
|
|
3094
|
-
not has_low_residue_producing_crops,
|
|
3095
|
-
not has_bare_fallow,
|
|
3096
|
-
not has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
3097
|
-
any([
|
|
3098
|
-
has_irrigation,
|
|
3099
|
-
has_practice_increasing_c_input,
|
|
3100
|
-
has_cover_crop,
|
|
3101
|
-
has_organic_fertiliser_or_soil_amendment_used
|
|
3102
|
-
])
|
|
3103
|
-
]),
|
|
3104
|
-
4: all([
|
|
3105
|
-
not has_residue_removed_or_burnt,
|
|
3106
|
-
not has_low_residue_producing_crops,
|
|
3107
|
-
not has_bare_fallow,
|
|
3108
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
3109
|
-
not has_irrigation,
|
|
3110
|
-
not has_organic_fertiliser_or_soil_amendment_used,
|
|
3111
|
-
not has_practice_increasing_c_input,
|
|
3112
|
-
not has_cover_crop
|
|
3113
|
-
])
|
|
3114
|
-
}
|
|
3115
|
-
|
|
3116
|
-
return next(
|
|
3117
|
-
(key for key, condition in conditions.items() if condition), None
|
|
3118
|
-
)
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
def _check_cropland_low_category(
|
|
3122
|
-
*,
|
|
3123
|
-
has_animal_manure_used: bool,
|
|
3124
|
-
has_bare_fallow: bool,
|
|
3125
|
-
has_cover_crop: bool,
|
|
3126
|
-
has_irrigation: bool,
|
|
3127
|
-
has_low_residue_producing_crops: bool,
|
|
3128
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used: bool,
|
|
3129
|
-
has_organic_fertiliser_or_soil_amendment_used: bool,
|
|
3130
|
-
has_practice_increasing_c_input: bool,
|
|
3131
|
-
has_residue_removed_or_burnt: bool,
|
|
3132
|
-
**_
|
|
3133
|
-
) -> Optional[int]:
|
|
3134
|
-
"""
|
|
3135
|
-
Checks the Cropland Low IPCC Carbon Input Category based on the given carbon input arguments.
|
|
3136
|
-
|
|
3137
|
-
Parameters
|
|
3138
|
-
----------
|
|
3139
|
-
has_animal_manure_used : bool
|
|
3140
|
-
Indicates whether animal manure is used on more than 30% of the site.
|
|
3141
|
-
has_bare_fallow : bool
|
|
3142
|
-
Indicates whether bare fallow is present on more than 30% of the site.
|
|
3143
|
-
has_cover_crop : bool
|
|
3144
|
-
Indicates whether cover crops are present on more than 30% of the site.
|
|
3145
|
-
has_irrigation : bool
|
|
3146
|
-
Indicates whether irrigation is applied to more than 30% of the site.
|
|
3147
|
-
has_low_residue_producing_crops : bool
|
|
3148
|
-
Indicates whether low residue-producing crops are present on more than 70% of the site.
|
|
3149
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used : bool
|
|
3150
|
-
Indicates whether a nitrogen-fixing crop or inorganic nitrogen fertiliser is used on more than 30% of the site.
|
|
3151
|
-
has_organic_fertiliser_or_soil_amendment_used : bool
|
|
3152
|
-
Indicates whether organic fertiliser or soil amendments are used on more than 30% of the site.
|
|
3153
|
-
has_practice_increasing_c_input : bool
|
|
3154
|
-
Indicates whether practices increasing carbon input are present on more than 30% of the site.
|
|
3155
|
-
has_residue_removed_or_burnt : bool
|
|
3156
|
-
Indicates whether residues are removed or burnt on more than 30% of the site.
|
|
3157
|
-
|
|
3158
|
-
Returns
|
|
3159
|
-
-------
|
|
3160
|
-
int | None
|
|
3161
|
-
The category key if conditions are met; otherwise, `None`.
|
|
3162
|
-
"""
|
|
3163
|
-
conditions = {
|
|
3164
|
-
1: all([
|
|
3165
|
-
has_residue_removed_or_burnt,
|
|
3166
|
-
not has_animal_manure_used
|
|
3167
|
-
]),
|
|
3168
|
-
2: all([
|
|
3169
|
-
not has_residue_removed_or_burnt,
|
|
3170
|
-
any([
|
|
3171
|
-
has_low_residue_producing_crops,
|
|
3172
|
-
has_bare_fallow
|
|
3173
|
-
]),
|
|
3174
|
-
not has_irrigation,
|
|
3175
|
-
not has_practice_increasing_c_input,
|
|
3176
|
-
not has_cover_crop,
|
|
3177
|
-
not has_organic_fertiliser_or_soil_amendment_used
|
|
3178
|
-
]),
|
|
3179
|
-
3: all([
|
|
3180
|
-
not has_residue_removed_or_burnt,
|
|
3181
|
-
not has_low_residue_producing_crops,
|
|
3182
|
-
not has_bare_fallow,
|
|
3183
|
-
not has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
3184
|
-
not has_irrigation,
|
|
3185
|
-
not has_organic_fertiliser_or_soil_amendment_used,
|
|
3186
|
-
not has_practice_increasing_c_input,
|
|
3187
|
-
not has_cover_crop
|
|
3188
|
-
])
|
|
3189
|
-
}
|
|
3190
|
-
|
|
3191
|
-
return next(
|
|
3192
|
-
(key for key, condition in conditions.items() if condition), None
|
|
3193
|
-
)
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
def _get_carbon_input_kwargs(
|
|
3197
|
-
management_nodes: list[dict]
|
|
3198
|
-
) -> dict:
|
|
3199
|
-
"""
|
|
3200
|
-
Creates CarbonInputArgs based on the provided list of management nodes.
|
|
3201
|
-
|
|
3202
|
-
Parameters
|
|
3203
|
-
----------
|
|
3204
|
-
management_nodes : list[dict]
|
|
3205
|
-
The list of management nodes.
|
|
3206
|
-
|
|
3207
|
-
Returns
|
|
3208
|
-
-------
|
|
3209
|
-
dict
|
|
3210
|
-
The carbon input keyword arguments.
|
|
3211
|
-
"""
|
|
3212
|
-
|
|
3213
|
-
PRACTICE_INCREASING_C_INPUT_LOOKUP = LOOKUPS["landUseManagement"]
|
|
3214
|
-
LOW_RESIDUE_PRODUCING_CROP_LOOKUP = LOOKUPS["landCover"][1]
|
|
3215
|
-
N_FIXING_CROP_LOOKUP = LOOKUPS["landCover"][2]
|
|
3216
|
-
|
|
3217
|
-
# To prevent double counting already explicitly checked practices.
|
|
3218
|
-
EXCLUDED_PRACTICE_TERM_IDS = {
|
|
3219
|
-
IMPROVED_PASTURE_TERM_ID,
|
|
3220
|
-
ANIMAL_MANURE_USED_TERM_ID,
|
|
3221
|
-
INORGANIC_NITROGEN_FERTILISER_USED_TERM_ID,
|
|
3222
|
-
ORGANIC_FERTILISER_USED_TERM_ID
|
|
3223
|
-
}
|
|
3224
|
-
|
|
3225
|
-
crop_residue_management_nodes = filter_list_term_type(management_nodes, [TermTermType.CROPRESIDUEMANAGEMENT])
|
|
3226
|
-
land_cover_nodes = filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])
|
|
3227
|
-
land_use_management_nodes = filter_list_term_type(management_nodes, [TermTermType.LANDUSEMANAGEMENT])
|
|
3228
|
-
water_regime_nodes = filter_list_term_type(management_nodes, [TermTermType.WATERREGIME])
|
|
3229
|
-
|
|
3230
|
-
has_animal_manure_used = any(
|
|
3231
|
-
get_node_value(node) for node in land_use_management_nodes if node_term_match(node, ANIMAL_MANURE_USED_TERM_ID)
|
|
3232
|
-
)
|
|
3233
|
-
|
|
3234
|
-
has_bare_fallow = cumulative_nodes_term_match(
|
|
3235
|
-
land_cover_nodes,
|
|
3236
|
-
target_term_ids=SHORT_BARE_FALLOW_TERM_ID,
|
|
3237
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
3238
|
-
)
|
|
3239
|
-
|
|
3240
|
-
cover_crop_property_terms = get_cover_crop_property_terms()
|
|
3241
|
-
has_cover_crop = cumulative_nodes_match(
|
|
3242
|
-
lambda node: any(
|
|
3243
|
-
get_node_property(node, term_id, False).get("value", False) for term_id in cover_crop_property_terms
|
|
3244
|
-
),
|
|
3245
|
-
land_cover_nodes,
|
|
3246
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
3247
|
-
)
|
|
3248
|
-
|
|
3249
|
-
has_inorganic_n_fertiliser_used = any(
|
|
3250
|
-
get_node_value(node) for node in land_use_management_nodes
|
|
3251
|
-
if node_term_match(node, INORGANIC_NITROGEN_FERTILISER_USED_TERM_ID)
|
|
3252
|
-
)
|
|
3253
|
-
|
|
3254
|
-
has_irrigation = _has_irrigation(water_regime_nodes)
|
|
3255
|
-
|
|
3256
|
-
# SUPER_MAJORITY_AREA_THRESHOLD
|
|
3257
|
-
has_low_residue_producing_crops = cumulative_nodes_lookup_match(
|
|
3258
|
-
land_cover_nodes,
|
|
3259
|
-
lookup=LOW_RESIDUE_PRODUCING_CROP_LOOKUP,
|
|
3260
|
-
target_lookup_values=True,
|
|
3261
|
-
cumulative_threshold=SUPER_MAJORITY_AREA_THRESHOLD
|
|
3262
|
-
)
|
|
3263
|
-
|
|
3264
|
-
has_n_fixing_crop = cumulative_nodes_lookup_match(
|
|
3265
|
-
land_cover_nodes,
|
|
3266
|
-
lookup=N_FIXING_CROP_LOOKUP,
|
|
3267
|
-
target_lookup_values=True,
|
|
3268
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
3269
|
-
)
|
|
3270
|
-
|
|
3271
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used = has_n_fixing_crop or has_inorganic_n_fertiliser_used
|
|
3272
|
-
|
|
3273
|
-
has_organic_fertiliser_or_soil_amendment_used = any(
|
|
3274
|
-
get_node_value(node) for node in land_use_management_nodes
|
|
3275
|
-
if node_term_match(node, [ORGANIC_FERTILISER_USED_TERM_ID, SOIL_AMENDMENT_USED_TERM_ID])
|
|
3276
|
-
)
|
|
3277
|
-
|
|
3278
|
-
has_practice_increasing_c_input = cumulative_nodes_match(
|
|
3279
|
-
lambda node: (
|
|
3280
|
-
node_lookup_match(node, PRACTICE_INCREASING_C_INPUT_LOOKUP, True)
|
|
3281
|
-
and not node_term_match(node, EXCLUDED_PRACTICE_TERM_IDS)
|
|
3282
|
-
),
|
|
3283
|
-
land_use_management_nodes,
|
|
3284
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
3285
|
-
)
|
|
3286
|
-
|
|
3287
|
-
has_residue_removed_or_burnt = cumulative_nodes_term_match(
|
|
3288
|
-
crop_residue_management_nodes,
|
|
3289
|
-
target_term_ids=get_residue_removed_or_burnt_terms(),
|
|
3290
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
3291
|
-
)
|
|
3292
|
-
|
|
3293
|
-
num_grassland_improvements = [
|
|
3294
|
-
has_irrigation,
|
|
3295
|
-
has_practice_increasing_c_input,
|
|
3296
|
-
has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
3297
|
-
has_organic_fertiliser_or_soil_amendment_used
|
|
3298
|
-
].count(True)
|
|
3299
|
-
|
|
3300
|
-
return {
|
|
3301
|
-
"has_animal_manure_used": has_animal_manure_used,
|
|
3302
|
-
"has_bare_fallow": has_bare_fallow,
|
|
3303
|
-
"has_cover_crop": has_cover_crop,
|
|
3304
|
-
"has_irrigation": has_irrigation,
|
|
3305
|
-
"has_low_residue_producing_crops": has_low_residue_producing_crops,
|
|
3306
|
-
"has_n_fixing_crop_or_inorganic_n_fertiliser_used": has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
3307
|
-
"has_organic_fertiliser_or_soil_amendment_used": has_organic_fertiliser_or_soil_amendment_used,
|
|
3308
|
-
"has_practice_increasing_c_input": has_practice_increasing_c_input,
|
|
3309
|
-
"has_residue_removed_or_burnt": has_residue_removed_or_burnt,
|
|
3310
|
-
"num_grassland_improvements": num_grassland_improvements
|
|
3311
|
-
}
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
GRASSLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE = {
|
|
3315
|
-
IpccCarbonInputCategory.GRASSLAND_HIGH: _check_grassland_ipcc_carbon_input_category,
|
|
3316
|
-
IpccCarbonInputCategory.GRASSLAND_MEDIUM: _check_grassland_ipcc_carbon_input_category
|
|
3317
|
-
}
|
|
3318
|
-
"""
|
|
3319
|
-
A decision tree for assigning IPCC Carbon Input Categories to Grassland based on the number of improvements.
|
|
3320
|
-
|
|
3321
|
-
Key: IpccCarbonInputCategory
|
|
3322
|
-
Value: Corresponding function to check if the given conditions are met for the category.
|
|
3323
|
-
"""
|
|
3324
|
-
|
|
3325
|
-
CROPLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE = {
|
|
3326
|
-
IpccCarbonInputCategory.CROPLAND_HIGH_WITH_MANURE: _check_cropland_high_with_manure_category,
|
|
3327
|
-
IpccCarbonInputCategory.CROPLAND_HIGH_WITHOUT_MANURE: _check_cropland_high_without_manure_category,
|
|
3328
|
-
IpccCarbonInputCategory.CROPLAND_MEDIUM: _check_cropland_medium_category,
|
|
3329
|
-
IpccCarbonInputCategory.CROPLAND_LOW: _check_cropland_low_category
|
|
3330
|
-
}
|
|
3331
|
-
"""
|
|
3332
|
-
A decision tree for assigning IPCC Carbon Input Categories to Cropland based on specific conditions.
|
|
3333
|
-
|
|
3334
|
-
Key: IpccCarbonInputCategory
|
|
3335
|
-
Value: Corresponding function to check if the given conditions are met for the category.
|
|
3336
|
-
"""
|
|
3337
|
-
|
|
3338
|
-
DECISION_TREE_FROM_IPCC_MANAGEMENT_CATEGORY = {
|
|
3339
|
-
IpccManagementCategory.IMPROVED_GRASSLAND: GRASSLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE,
|
|
3340
|
-
IpccManagementCategory.FULL_TILLAGE: CROPLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE,
|
|
3341
|
-
IpccManagementCategory.REDUCED_TILLAGE: CROPLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE,
|
|
3342
|
-
IpccManagementCategory.NO_TILLAGE: CROPLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE
|
|
3343
|
-
}
|
|
3344
|
-
"""
|
|
3345
|
-
A decision tree mapping IPCC Management Categories to respective Carbon Input Category decision trees.
|
|
3346
|
-
|
|
3347
|
-
Key: IpccManagementCategory
|
|
3348
|
-
Value: Decision tree for Carbon Input Categories corresponding to the management category.
|
|
3349
|
-
"""
|
|
3350
|
-
|
|
3351
|
-
DEFAULT_CARBON_INPUT_CATEGORY = {
|
|
3352
|
-
IpccManagementCategory.IMPROVED_GRASSLAND: IpccCarbonInputCategory.GRASSLAND_MEDIUM,
|
|
3353
|
-
IpccManagementCategory.FULL_TILLAGE: IpccCarbonInputCategory.CROPLAND_LOW,
|
|
3354
|
-
IpccManagementCategory.REDUCED_TILLAGE: IpccCarbonInputCategory.CROPLAND_LOW,
|
|
3355
|
-
IpccManagementCategory.NO_TILLAGE: IpccCarbonInputCategory.CROPLAND_LOW
|
|
3356
|
-
}
|
|
3357
|
-
"""
|
|
3358
|
-
A mapping from IPCC Management Categories to default Carbon Input Categories.
|
|
3359
|
-
|
|
3360
|
-
Key: IpccManagementCategory
|
|
3361
|
-
Value: Default Carbon Input Category for the corresponding Management Category.
|
|
3362
|
-
"""
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
def _assign_ipcc_carbon_input_category(
|
|
3366
|
-
management_nodes: list[dict],
|
|
3367
|
-
ipcc_management_category: IpccManagementCategory
|
|
3368
|
-
) -> IpccCarbonInputCategory:
|
|
3369
|
-
"""
|
|
3370
|
-
Assigns an IPCC Carbon Input Category based on the provided management nodes and IPCC Management Category.
|
|
3371
|
-
|
|
3372
|
-
Parameters
|
|
3373
|
-
----------
|
|
3374
|
-
management_nodes : list[dict]
|
|
3375
|
-
List of management nodes containing information about land management practices.
|
|
3376
|
-
ipcc_management_category : IpccManagementCategory
|
|
3377
|
-
IPCC Management Category for which the Carbon Input Category needs to be assigned.
|
|
3378
|
-
|
|
3379
|
-
Returns
|
|
3380
|
-
-------
|
|
3381
|
-
IpccCarbonInputCategory
|
|
3382
|
-
Assigned IPCC Carbon Input Category.
|
|
3383
|
-
"""
|
|
3384
|
-
decision_tree = DECISION_TREE_FROM_IPCC_MANAGEMENT_CATEGORY.get(ipcc_management_category, {})
|
|
3385
|
-
default = DEFAULT_CARBON_INPUT_CATEGORY.get(ipcc_management_category, IpccCarbonInputCategory.OTHER)
|
|
3386
|
-
|
|
3387
|
-
should_run = len(management_nodes) > 0
|
|
3388
|
-
|
|
3389
|
-
return next(
|
|
3390
|
-
(key for key in decision_tree if decision_tree[key](
|
|
3391
|
-
key=key,
|
|
3392
|
-
**_get_carbon_input_kwargs(management_nodes)
|
|
3393
|
-
)),
|
|
3394
|
-
default
|
|
3395
|
-
) if should_run else default
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
# --- TIER 1 SOC MODEL ---
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
def _run_tier_1(
|
|
3402
|
-
inventory: dict,
|
|
3403
|
-
*,
|
|
3404
|
-
eco_climate_zone: int,
|
|
3405
|
-
soc_ref: float,
|
|
3406
|
-
**_
|
|
3407
|
-
) -> list[dict]:
|
|
3408
|
-
"""
|
|
3409
|
-
Run the IPCC (2019) Tier 1 methodology for calculating SOC stocks (in kg C ha-1) for each year in the inventory
|
|
3410
|
-
and wrap each of the calculated values in Hestia measurement nodes. To avoid any errors, the `inventory` parameter
|
|
3411
|
-
must be pre-validated by the `should_run` function.
|
|
3412
|
-
|
|
3413
|
-
See [IPCC (2019) Vol. 4, Ch. 2](https://www.ipcc-nggip.iges.or.jp/public/2019rf/vol4.html) for more information.
|
|
3414
|
-
|
|
3415
|
-
The inventory should be in the following shape:
|
|
3416
|
-
```
|
|
3417
|
-
{
|
|
3418
|
-
year (int): {
|
|
3419
|
-
_InventoryKey.SHOULD_RUN_TIER_1: bool,
|
|
3420
|
-
_InventoryKey.LU_CATEGORY: IpccLandUseCategory,
|
|
3421
|
-
_InventoryKey.MG_CATEGORY: IpccManagementCategory,
|
|
3422
|
-
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory
|
|
3423
|
-
},
|
|
3424
|
-
...
|
|
3425
|
-
}
|
|
3426
|
-
```
|
|
3427
|
-
|
|
3428
|
-
Parameters
|
|
3429
|
-
----------
|
|
3430
|
-
inventory : dict
|
|
3431
|
-
The inventory built by the `_should_run` function.
|
|
3432
|
-
eco_climate_zone : int
|
|
3433
|
-
The eco-climate zone identifier for the site corresponding to a row in the
|
|
3434
|
-
[ecoClimateZone](https://gitlab.com/hestia-earth/hestia-glossary/-/blob/develop/Measurements/ecoClimateZone-lookup.csv)
|
|
3435
|
-
lookup table.
|
|
3436
|
-
ipcc_soil_category : IpccSoilCategory
|
|
3437
|
-
The reference condition SOC stock in the 0-30cm depth interval, kg C ha-1.
|
|
3438
|
-
|
|
3439
|
-
Returns
|
|
3440
|
-
-------
|
|
3441
|
-
list[dict]
|
|
3442
|
-
A list of Hestia `Measurement` nodes containing the calculated SOC stocks and additional relevant data.
|
|
3443
|
-
"""
|
|
3444
|
-
|
|
3445
|
-
valid_inventory = {
|
|
3446
|
-
year: group for year, group in inventory.items() if group.get(_InventoryKey.SHOULD_RUN_TIER_1)
|
|
3447
|
-
}
|
|
3448
|
-
|
|
3449
|
-
timestamps = [year for year in valid_inventory.keys()]
|
|
3450
|
-
ipcc_land_use_categories = [group[_InventoryKey.LU_CATEGORY] for group in valid_inventory.values()]
|
|
3451
|
-
ipcc_management_categories = [group[_InventoryKey.MG_CATEGORY] for group in valid_inventory.values()]
|
|
3452
|
-
ipcc_carbon_input_categories = [group[_InventoryKey.CI_CATEGORY] for group in valid_inventory.values()]
|
|
3453
|
-
|
|
3454
|
-
iterated_timestamps, iterated_soc_equilibriums = _run_soc_equilibriums(
|
|
3455
|
-
timestamps,
|
|
3456
|
-
ipcc_land_use_categories,
|
|
3457
|
-
ipcc_management_categories,
|
|
3458
|
-
ipcc_carbon_input_categories,
|
|
3459
|
-
eco_climate_zone,
|
|
3460
|
-
soc_ref
|
|
3461
|
-
)
|
|
3462
|
-
|
|
3463
|
-
soc_stocks = _calc_tier_1_soc_stocks(iterated_timestamps, iterated_soc_equilibriums)
|
|
3464
|
-
|
|
3465
|
-
return [
|
|
3466
|
-
_measurement(
|
|
3467
|
-
year,
|
|
3468
|
-
soc_stock,
|
|
3469
|
-
MeasurementMethodClassification.TIER_1_MODEL.value
|
|
3470
|
-
) for year, soc_stock in zip(
|
|
3471
|
-
iterated_timestamps,
|
|
3472
|
-
soc_stocks
|
|
3473
|
-
)
|
|
3474
|
-
]
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
# --- SHOULD RUN ---
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
def _should_run(site: dict) -> tuple[bool, dict]:
|
|
3481
|
-
"""
|
|
3482
|
-
Extract data from site & related cycles, pre-process data and determine whether there is sufficient data to run the
|
|
3483
|
-
tier 1 and/or tier 2 model.
|
|
3484
|
-
|
|
3485
|
-
The inventory dict should be in the following shape:
|
|
3486
|
-
```
|
|
3487
|
-
{
|
|
3488
|
-
year (int): {
|
|
3489
|
-
_InventoryKey.SHOULD_RUN_TIER_2: bool,
|
|
3490
|
-
_InventoryKey.TEMP_MONTHLY: list[float],
|
|
3491
|
-
_InventoryKey.PRECIP_MONTHLY: list[float],
|
|
3492
|
-
_InventoryKey.PET_MONTHLY: list[float],
|
|
3493
|
-
_InventoryKey.IRRIGATED_MONTHLY: list[bool]
|
|
3494
|
-
_InventoryKey.CARBON_INPUT: float,
|
|
3495
|
-
_InventoryKey.N_CONTENT: float,
|
|
3496
|
-
_InventoryKey.TILLAGE_CATEGORY: IpccManagementCategory,
|
|
3497
|
-
_InventoryKey.SAND_CONTENT: float,
|
|
3498
|
-
_InventoryKey.SHOULD_RUN_TIER_1: bool,
|
|
3499
|
-
_InventoryKey.LU_CATEGORY: IpccLandUseCategory,
|
|
3500
|
-
_InventoryKey.MG_CATEGORY: IpccManagementCategory,
|
|
3501
|
-
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory
|
|
3502
|
-
},
|
|
3503
|
-
...
|
|
3504
|
-
}
|
|
3505
|
-
```
|
|
3506
|
-
|
|
3507
|
-
The kwargs dict should be in the following shape:
|
|
3508
|
-
```
|
|
3509
|
-
{
|
|
3510
|
-
"run_with_irrigation": bool,
|
|
3511
|
-
"eco_climate_zone": int,
|
|
3512
|
-
"ipcc_soil_category": IpccSoilCategory,
|
|
3513
|
-
"soc_ref": float
|
|
3514
|
-
}
|
|
3515
|
-
```
|
|
3516
|
-
"""
|
|
3517
|
-
site_type = site.get("siteType", "")
|
|
3518
|
-
management_nodes = site.get("management", [])
|
|
3519
|
-
measurement_nodes = site.get("measurements", [])
|
|
3520
|
-
cycles = related_cycles(site)
|
|
3521
|
-
|
|
3522
|
-
has_management = len(management_nodes) > 0
|
|
3523
|
-
has_measurements = len(measurement_nodes) > 0
|
|
3524
|
-
has_related_cycles = len(cycles) > 0
|
|
3525
|
-
has_functional_unit_1_ha = all(cycle.get("functionalUnit") in VALID_FUNCTIONAL_UNITS_TIER_2 for cycle in cycles)
|
|
3526
|
-
|
|
3527
|
-
should_build_inventory_tier_1 = all([
|
|
3528
|
-
site_type in VALID_SITE_TYPES_TIER_1,
|
|
3529
|
-
has_management,
|
|
3530
|
-
has_measurements
|
|
3531
|
-
])
|
|
3532
|
-
|
|
3533
|
-
should_build_inventory_tier_2 = all([
|
|
3534
|
-
site_type in VALID_SITE_TYPES_TIER_2,
|
|
3535
|
-
has_related_cycles,
|
|
3536
|
-
check_cycle_site_ids_identical(cycles),
|
|
3537
|
-
has_functional_unit_1_ha
|
|
3538
|
-
])
|
|
3539
|
-
|
|
3540
|
-
inventory_tier_1, kwargs_tier_1 = (
|
|
3541
|
-
_build_inventory_tier_1(site_type, management_nodes, measurement_nodes)
|
|
3542
|
-
if should_build_inventory_tier_1 else ({}, {})
|
|
3543
|
-
)
|
|
3544
|
-
|
|
3545
|
-
inventory_tier_2, kwargs_tier_2 = (
|
|
3546
|
-
_build_inventory_tier_2(cycles, measurement_nodes)
|
|
3547
|
-
if should_build_inventory_tier_2 else ({}, {})
|
|
3548
|
-
)
|
|
3549
|
-
|
|
3550
|
-
inventory = dict(sorted(merge(inventory_tier_1, inventory_tier_2).items()))
|
|
3551
|
-
kwargs = kwargs_tier_1 | kwargs_tier_2
|
|
3552
|
-
|
|
3553
|
-
should_run_tier_1 = _should_run_tier_1(inventory, **kwargs) if should_build_inventory_tier_1 else False
|
|
3554
|
-
should_run_tier_2 = _should_run_tier_2(inventory, **kwargs) if should_build_inventory_tier_2 else False
|
|
3555
|
-
|
|
3556
|
-
logRequirements(
|
|
3557
|
-
site, model=MODEL, term=TERM_ID,
|
|
3558
|
-
should_build_inventory_tier_1=should_build_inventory_tier_1,
|
|
3559
|
-
should_build_inventory_tier_2=should_build_inventory_tier_2,
|
|
3560
|
-
should_run_tier_1=should_run_tier_1,
|
|
3561
|
-
should_run_tier_2=should_run_tier_2,
|
|
3562
|
-
site_type=site_type,
|
|
3563
|
-
has_management=has_management,
|
|
3564
|
-
has_measurements=has_measurements,
|
|
3565
|
-
has_related_cycles=has_related_cycles,
|
|
3566
|
-
is_unit_hectare=has_functional_unit_1_ha,
|
|
3567
|
-
**kwargs,
|
|
3568
|
-
inventory=_log_inventory(inventory)
|
|
3569
|
-
)
|
|
3570
|
-
|
|
3571
|
-
should_run = should_run_tier_1 or should_run_tier_2
|
|
3572
|
-
logShouldRun(site, MODEL, TERM_ID, should_run)
|
|
3573
|
-
|
|
3574
|
-
return should_run_tier_1, should_run_tier_2, inventory, kwargs
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
def _should_run_tier_1(
|
|
3578
|
-
inventory: dict,
|
|
3579
|
-
*,
|
|
3580
|
-
eco_climate_zone: int = None,
|
|
3581
|
-
soc_ref: float = None,
|
|
3582
|
-
**_
|
|
3583
|
-
) -> bool:
|
|
3584
|
-
"""
|
|
3585
|
-
Determines whether there is sufficient data in the inventory and keyword args to run the tier 1 model.
|
|
3586
|
-
"""
|
|
3587
|
-
return all([
|
|
3588
|
-
eco_climate_zone and eco_climate_zone not in EXCLUDED_ECO_CLIMATE_ZONES_TIER_1,
|
|
3589
|
-
soc_ref and soc_ref > 0,
|
|
3590
|
-
any(year for year, group in inventory.items() if group.get(_InventoryKey.SHOULD_RUN_TIER_1))
|
|
3591
|
-
])
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
def _should_run_tier_2(
|
|
3595
|
-
inventory: dict,
|
|
3596
|
-
*,
|
|
3597
|
-
sand_content: float = None,
|
|
3598
|
-
**_
|
|
3599
|
-
) -> bool:
|
|
3600
|
-
"""
|
|
3601
|
-
Determines whether there is sufficient data in the inventory and keyword args to run the tier 2 model.
|
|
3602
|
-
"""
|
|
3603
|
-
valid_years = [year for year, group in inventory.items() if group.get(_InventoryKey.SHOULD_RUN_TIER_2)]
|
|
3604
|
-
return all([
|
|
3605
|
-
len(valid_years) >= MIN_RUN_IN_PERIOD,
|
|
3606
|
-
check_consecutive(valid_years),
|
|
3607
|
-
any(inventory.get(year).get(_InventoryKey.SAND_CONTENT) for year in valid_years) or sand_content
|
|
3608
|
-
])
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
# --- LOGGING ---
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
def _log_inventory(inventory: dict) -> str:
|
|
3615
|
-
"""
|
|
3616
|
-
Format the inventory data as a table for logging.
|
|
3617
|
-
"""
|
|
3618
|
-
log_table = log_as_table(
|
|
3619
|
-
{
|
|
3620
|
-
"year": year,
|
|
3621
|
-
"should-run-tier-1": group.get(_InventoryKey.SHOULD_RUN_TIER_1, False),
|
|
3622
|
-
"should-run-tier-2": group.get(_InventoryKey.SHOULD_RUN_TIER_2, False),
|
|
3623
|
-
"ipcc-land-use-category": (
|
|
3624
|
-
group.get(_InventoryKey.LU_CATEGORY).value if group.get(_InventoryKey.LU_CATEGORY) else None
|
|
3625
|
-
),
|
|
3626
|
-
"ipcc-management-category": (
|
|
3627
|
-
group.get(_InventoryKey.MG_CATEGORY).value if group.get(_InventoryKey.MG_CATEGORY) else None
|
|
3628
|
-
),
|
|
3629
|
-
"ipcc-carbon-input-category": (
|
|
3630
|
-
group.get(_InventoryKey.CI_CATEGORY).value if group.get(_InventoryKey.CI_CATEGORY) else None
|
|
3631
|
-
),
|
|
3632
|
-
"temperature-monthly": (
|
|
3633
|
-
" ".join(f"{val:.1f}" for val in group.get(_InventoryKey.TEMP_MONTHLY, []))
|
|
3634
|
-
if group.get(_InventoryKey.TEMP_MONTHLY) else None
|
|
3635
|
-
),
|
|
3636
|
-
"precipitation-monthly": (
|
|
3637
|
-
" ".join(f"{val:.1f}" for val in group.get(_InventoryKey.PRECIP_MONTHLY, []))
|
|
3638
|
-
if group.get(_InventoryKey.PRECIP_MONTHLY) else None
|
|
3639
|
-
),
|
|
3640
|
-
"pet-monthly": (
|
|
3641
|
-
" ".join(f"{val:.1f}" for val in group.get(_InventoryKey.PET_MONTHLY, []))
|
|
3642
|
-
if group.get(_InventoryKey.PET_MONTHLY) else None
|
|
3643
|
-
),
|
|
3644
|
-
"irrigated-monthly": (
|
|
3645
|
-
" ".join(str(val) for val in group.get(_InventoryKey.IRRIGATED_MONTHLY, []))
|
|
3646
|
-
if group.get(_InventoryKey.IRRIGATED_MONTHLY) else None
|
|
3647
|
-
),
|
|
3648
|
-
"sand-content": group.get(_InventoryKey.SAND_CONTENT, None),
|
|
3649
|
-
"carbon-input": group.get(_InventoryKey.CARBON_INPUT, None),
|
|
3650
|
-
"n-content": group.get(_InventoryKey.N_CONTENT, None),
|
|
3651
|
-
"lignin-content": group.get(_InventoryKey.LIGNIN_CONTENT, None),
|
|
3652
|
-
"ipcc-tillage-category": (
|
|
3653
|
-
group.get(_InventoryKey.TILLAGE_CATEGORY).value if group.get(_InventoryKey.TILLAGE_CATEGORY) else None
|
|
3654
|
-
),
|
|
3655
|
-
"is-paddy-rice": group.get(_InventoryKey.IS_PADDY_RICE, None),
|
|
3656
|
-
} for year, group in inventory.items()
|
|
3657
|
-
)
|
|
3658
|
-
|
|
3659
|
-
return log_table or None
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
# --- TIER 2 BUILD INVENTORY ---
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
def _build_inventory_tier_2(
|
|
3666
|
-
cycles: list[dict], measurement_nodes: list[dict]
|
|
3667
|
-
) -> tuple[dict, dict]:
|
|
3668
|
-
"""
|
|
3669
|
-
Builds an annual inventory of data and a dictionary of keyword arguments for the tier 2 model.
|
|
3670
|
-
|
|
3671
|
-
TODO: implement long-term average climate data and annual climate data as back ups for monthly data
|
|
3672
|
-
"""
|
|
3673
|
-
grouped_cycles = group_nodes_by_year(cycles)
|
|
3674
|
-
grouped_measurements = group_nodes_by_year(measurement_nodes, mode=GroupNodesByYearMode.DATES)
|
|
3675
|
-
|
|
3676
|
-
grouped_climate_data = _get_grouped_climate_measurements(grouped_measurements)
|
|
3677
|
-
grouped_irrigated_monthly = _get_grouped_irrigated_monthly(grouped_cycles)
|
|
3678
|
-
grouped_sand_content_measurements = _get_grouped_sand_content_measurements(grouped_measurements)
|
|
3679
|
-
grouped_carbon_input_data = _get_grouped_carbon_input_data(grouped_cycles)
|
|
3680
|
-
grouped_tillage_categories = _get_grouped_tillage_categories(grouped_cycles)
|
|
3681
|
-
grouped_is_paddy_rice = _get_grouped_is_paddy_rice(grouped_cycles)
|
|
3682
|
-
|
|
3683
|
-
grouped_data = merge(
|
|
3684
|
-
grouped_climate_data,
|
|
3685
|
-
grouped_irrigated_monthly,
|
|
3686
|
-
grouped_sand_content_measurements,
|
|
3687
|
-
grouped_carbon_input_data,
|
|
3688
|
-
grouped_tillage_categories,
|
|
3689
|
-
grouped_is_paddy_rice
|
|
3690
|
-
)
|
|
3691
|
-
|
|
3692
|
-
grouped_should_run = {
|
|
3693
|
-
year: {_InventoryKey.SHOULD_RUN_TIER_2: _should_run_inventory_year_tier_2(group)}
|
|
3694
|
-
for year, group in grouped_data.items()
|
|
3695
|
-
}
|
|
3696
|
-
|
|
3697
|
-
inventory = merge(grouped_data, grouped_should_run)
|
|
3698
|
-
|
|
3699
|
-
# get a back-up value for sand content if no dated ones are available
|
|
3700
|
-
sand_content = get_node_value(find_term_match(
|
|
3701
|
-
[m for m in measurement_nodes if m.get("depthUpper") == DEPTH_UPPER and m.get("depthLower") == DEPTH_LOWER],
|
|
3702
|
-
SAND_CONTENT_TERM_ID,
|
|
3703
|
-
{}
|
|
3704
|
-
)) / 100
|
|
3705
|
-
|
|
3706
|
-
kwargs = {
|
|
3707
|
-
"run_with_irrigation": True,
|
|
3708
|
-
"sand_content": sand_content
|
|
3709
|
-
}
|
|
3710
|
-
|
|
3711
|
-
return inventory, kwargs
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
def _should_run_inventory_year_tier_2(group: dict) -> bool:
|
|
3715
|
-
"""
|
|
3716
|
-
Determines whether there is sufficient data in an inventory year to run the tier 2 model.
|
|
3717
|
-
|
|
3718
|
-
1. Check that the cycle is not for paddy rice.
|
|
3719
|
-
2. Check if monthly data has a value for each calendar month.
|
|
3720
|
-
3. Check if all required keys are present.
|
|
3721
|
-
|
|
3722
|
-
Parameters
|
|
3723
|
-
----------
|
|
3724
|
-
group : dict
|
|
3725
|
-
Dictionary containing information for a specific inventory year.
|
|
3726
|
-
|
|
3727
|
-
Returns
|
|
3728
|
-
-------
|
|
3729
|
-
bool
|
|
3730
|
-
True if the inventory year is valid, False otherwise.
|
|
3731
|
-
"""
|
|
3732
|
-
monthly_data_complete = _check_12_months(
|
|
3733
|
-
group,
|
|
3734
|
-
{
|
|
3735
|
-
_InventoryKey.TEMP_MONTHLY,
|
|
3736
|
-
_InventoryKey.PRECIP_MONTHLY,
|
|
3737
|
-
_InventoryKey.PET_MONTHLY,
|
|
3738
|
-
_InventoryKey.IRRIGATED_MONTHLY
|
|
3739
|
-
}
|
|
3740
|
-
)
|
|
3741
|
-
|
|
3742
|
-
carbon_input_data_complete = all([
|
|
3743
|
-
group.get(_InventoryKey.CARBON_INPUT, 0) > 0,
|
|
3744
|
-
group.get(_InventoryKey.N_CONTENT, 0) > 0,
|
|
3745
|
-
group.get(_InventoryKey.LIGNIN_CONTENT, 0) > 0,
|
|
3746
|
-
])
|
|
3747
|
-
|
|
3748
|
-
return all([
|
|
3749
|
-
not group.get(_InventoryKey.IS_PADDY_RICE),
|
|
3750
|
-
monthly_data_complete,
|
|
3751
|
-
carbon_input_data_complete,
|
|
3752
|
-
all(key in group.keys() for key in REQUIRED_KEYS_TIER_2),
|
|
3753
|
-
])
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
def _get_grouped_climate_measurements(grouped_measurements: dict) -> dict:
|
|
3757
|
-
return {
|
|
3758
|
-
year: {
|
|
3759
|
-
_InventoryKey.TEMP_MONTHLY: non_empty_list(
|
|
3760
|
-
find_term_match(measurements, TEMPERATURE_MONTHLY_TERM_ID, {}).get("value", [])
|
|
3761
|
-
),
|
|
3762
|
-
_InventoryKey.PRECIP_MONTHLY: non_empty_list(
|
|
3763
|
-
find_term_match(measurements, PRECIPITATION_MONTHLY_TERM_ID, {}).get("value", [])
|
|
3764
|
-
),
|
|
3765
|
-
_InventoryKey.PET_MONTHLY: non_empty_list(
|
|
3766
|
-
find_term_match(measurements, PET_MONTHLY_TERM_ID, {}).get("value", [])
|
|
3767
|
-
)
|
|
3768
|
-
} for year, measurements in grouped_measurements.items()
|
|
3769
|
-
}
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
def _get_grouped_irrigated_monthly(grouped_cycles: dict) -> dict:
|
|
3773
|
-
irrigated_terms = get_irrigated_terms()
|
|
3774
|
-
|
|
3775
|
-
return {
|
|
3776
|
-
year: {
|
|
3777
|
-
_InventoryKey.IRRIGATED_MONTHLY: _get_irrigated_monthly(year, cycles, irrigated_terms)
|
|
3778
|
-
} for year, cycles in grouped_cycles.items()
|
|
3779
|
-
}
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
def _get_irrigated_monthly(year: int, cycles: list[dict], irrigated_terms: list[str]) -> list[bool]:
|
|
3783
|
-
# Get practice nodes and add "startDate" and "endDate" from cycle if missing.
|
|
3784
|
-
irrigation_nodes = non_empty_list(flatten([
|
|
3785
|
-
[
|
|
3786
|
-
{
|
|
3787
|
-
"startDate": cycle.get("startDate"),
|
|
3788
|
-
"endDate": cycle.get("endDate"),
|
|
3789
|
-
**node
|
|
3790
|
-
} for node in cycle.get("practices", [])
|
|
3791
|
-
] for cycle in cycles
|
|
3792
|
-
]))
|
|
3793
|
-
|
|
3794
|
-
grouped_nodes = group_nodes_by_year_and_month(irrigation_nodes)
|
|
3795
|
-
|
|
3796
|
-
# For each month (1 - 12) check if irrigation is present.
|
|
3797
|
-
return [
|
|
3798
|
-
cumulative_nodes_term_match(
|
|
3799
|
-
grouped_nodes.get(year, {}).get(month, []),
|
|
3800
|
-
target_term_ids=irrigated_terms,
|
|
3801
|
-
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
3802
|
-
) for month in range(1, 13)
|
|
3803
|
-
]
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
def _get_grouped_sand_content_measurements(grouped_measurements: dict) -> dict:
|
|
3807
|
-
grouped_sand_content_measurements = {
|
|
3808
|
-
year: find_term_match(
|
|
3809
|
-
[m for m in measurements if m.get("depthUpper") == DEPTH_UPPER and m.get("depthLower") == DEPTH_LOWER],
|
|
3810
|
-
SAND_CONTENT_TERM_ID,
|
|
3811
|
-
{}
|
|
3812
|
-
) for year, measurements in grouped_measurements.items()
|
|
3813
|
-
}
|
|
3814
|
-
|
|
3815
|
-
return {
|
|
3816
|
-
year: {_InventoryKey.SAND_CONTENT: get_node_value(measurement)/100}
|
|
3817
|
-
for year, measurement in grouped_sand_content_measurements.items() if measurement
|
|
3818
|
-
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
def _get_grouped_carbon_input_data(grouped_cycles: dict) -> dict:
|
|
3822
|
-
grouped_carbon_sources = {
|
|
3823
|
-
year: _get_carbon_sources_from_cycles(cycle)
|
|
3824
|
-
for year, cycle in grouped_cycles.items()
|
|
3825
|
-
}
|
|
3826
|
-
|
|
3827
|
-
return {
|
|
3828
|
-
year: {
|
|
3829
|
-
_InventoryKey.CARBON_INPUT: _calc_total_organic_carbon_input(carbon_sources),
|
|
3830
|
-
_InventoryKey.N_CONTENT: _calc_average_nitrogen_content_of_organic_carbon_sources(carbon_sources),
|
|
3831
|
-
_InventoryKey.LIGNIN_CONTENT: _calc_average_lignin_content_of_organic_carbon_sources(carbon_sources)
|
|
3832
|
-
} for year, carbon_sources in grouped_carbon_sources.items()
|
|
3833
|
-
}
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
def _get_grouped_tillage_categories(grouped_cycles):
|
|
3837
|
-
return {
|
|
3838
|
-
year: {
|
|
3839
|
-
_InventoryKey.TILLAGE_CATEGORY: _assign_tier_2_ipcc_tillage_management_category(cycles)
|
|
3840
|
-
} for year, cycles in grouped_cycles.items()
|
|
3841
|
-
}
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
def _get_grouped_is_paddy_rice(grouped_cycles: dict) -> dict:
|
|
3845
|
-
return {
|
|
3846
|
-
year: {
|
|
3847
|
-
_InventoryKey.IS_PADDY_RICE: _check_is_paddy_rice(cycles)
|
|
3848
|
-
} for year, cycles in grouped_cycles.items()
|
|
3849
|
-
}
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
def _check_is_paddy_rice(cycles: list[dict]) -> bool:
|
|
3853
|
-
LOOKUP = LOOKUPS["crop"]
|
|
3854
|
-
TARGET_LOOKUP_VALUES = IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE.get(
|
|
3855
|
-
IpccLandUseCategory.PADDY_RICE_CULTIVATION, None
|
|
3856
|
-
)
|
|
3857
|
-
|
|
3858
|
-
has_paddy_rice_products = any(cumulative_nodes_lookup_match(
|
|
3859
|
-
filter_list_term_type(
|
|
3860
|
-
cycle.get("products", []) + cycle.get("practices", []),
|
|
3861
|
-
[TermTermType.CROP, TermTermType.FORAGE, TermTermType.LANDCOVER]
|
|
3862
|
-
),
|
|
3863
|
-
lookup=LOOKUP,
|
|
3864
|
-
target_lookup_values=TARGET_LOOKUP_VALUES,
|
|
3865
|
-
cumulative_threshold=MIN_YIELD_THRESHOLD,
|
|
3866
|
-
default_node_value=MIN_YIELD_THRESHOLD
|
|
3867
|
-
) for cycle in cycles)
|
|
3868
|
-
|
|
3869
|
-
reice_terms = get_upland_rice_crop_terms() + get_upland_rice_land_cover_terms()
|
|
3870
|
-
has_upland_rice_products = any(cumulative_nodes_term_match(
|
|
3871
|
-
filter_list_term_type(
|
|
3872
|
-
cycle.get("products", []) + cycle.get("practices", []),
|
|
3873
|
-
[TermTermType.CROP, TermTermType.FORAGE, TermTermType.LANDCOVER]
|
|
3874
|
-
),
|
|
3875
|
-
target_term_ids=reice_terms,
|
|
3876
|
-
cumulative_threshold=MIN_YIELD_THRESHOLD,
|
|
3877
|
-
default_node_value=MIN_YIELD_THRESHOLD
|
|
3878
|
-
) for cycle in cycles)
|
|
3879
|
-
|
|
3880
|
-
has_irrigation = any(
|
|
3881
|
-
_has_irrigation(filter_list_term_type(cycle.get("practices", []), [TermTermType.WATERREGIME]))
|
|
3882
|
-
for cycle in cycles
|
|
3883
|
-
)
|
|
3884
|
-
|
|
3885
|
-
return has_paddy_rice_products or (has_upland_rice_products and has_irrigation)
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
# --- TIER 1 BUILD INVENTORY ---
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
def _build_inventory_tier_1(
|
|
3892
|
-
site_type: str, management_nodes: list[dict], measurement_nodes: list[dict]
|
|
3893
|
-
) -> tuple[dict, dict]:
|
|
3894
|
-
"""
|
|
3895
|
-
Builds an annual inventory of data and a dictionary of keyword arguments for the tier 2 model.
|
|
3896
|
-
"""
|
|
3897
|
-
eco_climate_zone = _get_eco_climate_zone(measurement_nodes)
|
|
3898
|
-
ipcc_soil_category = _assign_ipcc_soil_category(measurement_nodes)
|
|
3899
|
-
soc_ref = _retrieve_soc_ref(eco_climate_zone, ipcc_soil_category)
|
|
3900
|
-
grouped_management = group_nodes_by_year(management_nodes)
|
|
3901
|
-
|
|
3902
|
-
# If no `landCover` nodes in `site.management` use `site.siteType` to assign static `IpccLandUseCategory`
|
|
3903
|
-
run_with_site_type = len(filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])) == 0
|
|
3904
|
-
site_type_ipcc_land_use_category = SITE_TYPE_TO_IPCC_LAND_USE_CATEGORY.get(site_type, IpccLandUseCategory.OTHER)
|
|
3905
|
-
|
|
3906
|
-
grouped_management = group_nodes_by_year(management_nodes)
|
|
3907
|
-
|
|
3908
|
-
grouped_land_use_categories = {
|
|
3909
|
-
year: {
|
|
3910
|
-
_InventoryKey.LU_CATEGORY: (
|
|
3911
|
-
site_type_ipcc_land_use_category if run_with_site_type
|
|
3912
|
-
else _assign_ipcc_land_use_category(nodes, ipcc_soil_category)
|
|
3913
|
-
)
|
|
3914
|
-
} for year, nodes in grouped_management.items()
|
|
3915
|
-
}
|
|
3916
|
-
|
|
3917
|
-
grouped_management_categories = {
|
|
3918
|
-
year: {
|
|
3919
|
-
_InventoryKey.MG_CATEGORY: _assign_ipcc_management_category(
|
|
3920
|
-
nodes,
|
|
3921
|
-
grouped_land_use_categories[year][_InventoryKey.LU_CATEGORY]
|
|
3922
|
-
)
|
|
3923
|
-
} for year, nodes in grouped_management.items()
|
|
3924
|
-
}
|
|
3925
|
-
|
|
3926
|
-
grouped_carbon_input_categories = {
|
|
3927
|
-
year: {
|
|
3928
|
-
_InventoryKey.CI_CATEGORY: _assign_ipcc_carbon_input_category(
|
|
3929
|
-
nodes,
|
|
3930
|
-
grouped_management_categories[year][_InventoryKey.MG_CATEGORY]
|
|
3931
|
-
)
|
|
3932
|
-
} for year, nodes in grouped_management.items()
|
|
3933
|
-
}
|
|
3934
|
-
|
|
3935
|
-
grouped_data = merge(
|
|
3936
|
-
grouped_land_use_categories,
|
|
3937
|
-
grouped_management_categories,
|
|
3938
|
-
grouped_carbon_input_categories
|
|
3939
|
-
)
|
|
3940
|
-
|
|
3941
|
-
grouped_should_run = {
|
|
3942
|
-
year: {_InventoryKey.SHOULD_RUN_TIER_1: _should_run_inventory_year_tier_1(group)}
|
|
3943
|
-
for year, group in grouped_data.items()
|
|
3944
|
-
}
|
|
3945
|
-
|
|
3946
|
-
inventory = merge(grouped_data, grouped_should_run)
|
|
3947
|
-
kwargs = {
|
|
3948
|
-
"eco_climate_zone": eco_climate_zone,
|
|
3949
|
-
"ipcc_soil_category": ipcc_soil_category,
|
|
3950
|
-
"run_with_site_type": run_with_site_type,
|
|
3951
|
-
"soc_ref": soc_ref
|
|
3952
|
-
}
|
|
3953
|
-
|
|
3954
|
-
return inventory, kwargs
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
def _should_run_inventory_year_tier_1(group: dict) -> bool:
|
|
3958
|
-
"""
|
|
3959
|
-
Determines whether there is sufficient data in an inventory year to run the tier 1 model.
|
|
3960
|
-
|
|
3961
|
-
1. Check if the land use category is not "OTHER"
|
|
3962
|
-
2. Check if all required keys are present.
|
|
3963
|
-
|
|
3964
|
-
Parameters
|
|
3965
|
-
----------
|
|
3966
|
-
group : dict
|
|
3967
|
-
Dictionary containing information for a specific inventory year.
|
|
3968
|
-
|
|
3969
|
-
Returns
|
|
3970
|
-
-------
|
|
3971
|
-
bool
|
|
3972
|
-
True if the inventory year is valid, False otherwise.
|
|
3973
|
-
"""
|
|
3974
|
-
return all([
|
|
3975
|
-
group.get(_InventoryKey.LU_CATEGORY) != IpccLandUseCategory.OTHER,
|
|
3976
|
-
all(key in group.keys() for key in REQUIRED_KEYS_TIER_1),
|
|
3977
|
-
])
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
# --- RUN ---
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
def run(site: dict) -> list[dict]:
|
|
3984
|
-
"""
|
|
3985
|
-
Check which Tiers of IPCC SOC model to run, run it and return the formatted output.
|
|
3986
|
-
|
|
3987
|
-
Parameters
|
|
3988
|
-
----------
|
|
3989
|
-
site : dict
|
|
3990
|
-
A Hestia `Site` node, see: https://www.hestia.earth/schema/Site.
|
|
3991
|
-
|
|
3992
|
-
Returns
|
|
3993
|
-
-------
|
|
3994
|
-
list[dict]
|
|
3995
|
-
A list of Hestia `Measurement` nodes containing the calculated SOC stocks and additional relevant data.
|
|
3996
|
-
"""
|
|
3997
|
-
should_run_tier_1, should_run_tier_2, inventory, kwargs = _should_run(site)
|
|
3998
|
-
return (
|
|
3999
|
-
_run_tier_2(inventory, **kwargs) if should_run_tier_2
|
|
4000
|
-
else _run_tier_1(inventory, **kwargs) if should_run_tier_1
|
|
4001
|
-
else []
|
|
4002
|
-
)
|
|
237
|
+
def _run_method(method: ModuleType, should_run: bool, inventory: dict, kwargs: dict, **_) -> list[dict]:
|
|
238
|
+
return method.run(inventory, **kwargs, iterations=ITERATIONS) if should_run else []
|