hestia-earth-models 0.61.7__py3-none-any.whl → 0.62.0__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 +60 -0
- hestia_earth/models/cycle/product/economicValueShare.py +47 -31
- 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/animal/pastureGrass.py +30 -24
- 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 +116 -3882
- 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/ipcc2019/pastureGrass.py +37 -19
- hestia_earth/models/ipcc2019/pastureGrass_utils.py +4 -21
- hestia_earth/models/mocking/search-results.json +293 -289
- hestia_earth/models/site/organicCarbonPerHa.py +58 -44
- hestia_earth/models/site/soilMeasurement.py +18 -13
- hestia_earth/models/utils/__init__.py +28 -0
- hestia_earth/models/utils/array_builders.py +578 -0
- hestia_earth/models/utils/blank_node.py +55 -39
- hestia_earth/models/utils/descriptive_stats.py +285 -0
- hestia_earth/models/utils/emission.py +73 -2
- hestia_earth/models/utils/inorganicFertiliser.py +2 -2
- hestia_earth/models/utils/measurement.py +118 -4
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/METADATA +2 -2
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/RECORD +51 -39
- tests/models/cycle/completeness/test_electricityFuel.py +21 -0
- tests/models/cycle/product/test_economicValueShare.py +8 -0
- tests/models/emepEea2019/test_nh3ToAirInorganicFertiliser.py +2 -2
- tests/models/ipcc2019/animal/test_pastureGrass.py +2 -2
- tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +55 -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/ipcc2019/test_pastureGrass.py +0 -16
- tests/models/site/test_organicCarbonPerHa.py +3 -12
- tests/models/site/test_soilMeasurement.py +3 -18
- tests/models/utils/test_array_builders.py +253 -0
- tests/models/utils/test_blank_node.py +154 -15
- tests/models/utils/test_descriptive_stats.py +134 -0
- tests/models/utils/test_emission.py +51 -1
- tests/models/utils/test_measurement.py +54 -2
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,2060 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The IPCC Tier 1 methodology for estimating soil organic carbon stock changes in the 0 - 30cm depth interval due to
|
|
3
|
+
management changes.
|
|
4
|
+
|
|
5
|
+
The model cannot not run on Sites with polar moist (ecoClimateZone 5) or polar dry (ecoClimateZone 6).
|
|
6
|
+
|
|
7
|
+
More information on this model, including data requirements **and** recommendations, and examples can be found in the
|
|
8
|
+
[Hestia SOC wiki](https://gitlab.com/hestia-earth/hestia-engine-models/-/wikis/Soil-organic-carbon-modelling).
|
|
9
|
+
|
|
10
|
+
Source: [IPCC 2019, Vol. 4, Chapter 2](https://www.ipcc-nggip.iges.or.jp/public/2019rf/vol4.html).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from functools import reduce
|
|
15
|
+
from numpy import empty_like, random, vstack
|
|
16
|
+
from numpy.typing import NDArray
|
|
17
|
+
from pydash.objects import merge
|
|
18
|
+
from typing import Callable, Optional, Union
|
|
19
|
+
|
|
20
|
+
from hestia_earth.schema import MeasurementMethodClassification, SiteSiteType, TermTermType
|
|
21
|
+
from hestia_earth.utils.model import find_term_match, filter_list_term_type
|
|
22
|
+
|
|
23
|
+
from hestia_earth.models.utils.array_builders import gen_seed
|
|
24
|
+
from hestia_earth.models.utils.blank_node import (
|
|
25
|
+
cumulative_nodes_match, cumulative_nodes_lookup_match, cumulative_nodes_term_match, get_node_value,
|
|
26
|
+
node_lookup_match, node_term_match, group_nodes_by_year
|
|
27
|
+
)
|
|
28
|
+
from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
|
|
29
|
+
from hestia_earth.models.utils.measurement import _new_measurement
|
|
30
|
+
from hestia_earth.models.utils.property import get_node_property
|
|
31
|
+
|
|
32
|
+
from .organicCarbonPerHa_utils import (
|
|
33
|
+
check_irrigation, DEPTH_LOWER, DEPTH_UPPER, EcoClimateZone,
|
|
34
|
+
get_cover_crop_property_terms_with_cache,
|
|
35
|
+
get_residue_removed_or_burnt_terms_with_cache,
|
|
36
|
+
get_upland_rice_land_cover_terms_with_cache,
|
|
37
|
+
IPCC_SOIL_CATEGORY_TO_SOIL_TYPE_LOOKUP_VALUE, IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE,
|
|
38
|
+
IPCC_MANAGEMENT_CATEGORY_TO_GRASSLAND_MANAGEMENT_TERM_ID,
|
|
39
|
+
IPCC_MANAGEMENT_CATEGORY_TO_TILLAGE_MANAGEMENT_LOOKUP_VALUE, IpccSoilCategory, IpccCarbonInputCategory,
|
|
40
|
+
IpccLandUseCategory, IpccManagementCategory, MIN_AREA_THRESHOLD, sample_constant, sample_plus_minus_error,
|
|
41
|
+
sample_plus_minus_uncertainty, SITE_TYPE_TO_IPCC_LAND_USE_CATEGORY, SUPER_MAJORITY_AREA_THRESHOLD, STATS_DEFINITION
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
_LOOKUPS = {
|
|
45
|
+
"crop": "IPCC_LAND_USE_CATEGORY",
|
|
46
|
+
"landCover": [
|
|
47
|
+
"IPCC_LAND_USE_CATEGORY",
|
|
48
|
+
"LOW_RESIDUE_PRODUCING_CROP",
|
|
49
|
+
"N_FIXING_CROP"
|
|
50
|
+
],
|
|
51
|
+
"landUseManagement": "PRACTICE_INCREASING_C_INPUT",
|
|
52
|
+
"soilType": "IPCC_SOIL_CATEGORY",
|
|
53
|
+
"tillage": "IPCC_TILLAGE_MANAGEMENT_CATEGORY",
|
|
54
|
+
"usdaSoilType": "IPCC_SOIL_CATEGORY"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_TERM_ID = 'organicCarbonPerHa'
|
|
58
|
+
_METHOD_CLASSIFICATION = MeasurementMethodClassification.TIER_1_MODEL.value
|
|
59
|
+
|
|
60
|
+
_CLAY_CONTENT_TERM_ID = "clayContent"
|
|
61
|
+
_SAND_CONTENT_TERM_ID = "sandContent"
|
|
62
|
+
_LONG_FALLOW_CROP_TERM_ID = "longFallowCrop"
|
|
63
|
+
_IMPROVED_PASTURE_TERM_ID = "improvedPasture"
|
|
64
|
+
_SHORT_BARE_FALLOW_TERM_ID = "shortBareFallow"
|
|
65
|
+
_ANIMAL_MANURE_USED_TERM_ID = "animalManureUsed"
|
|
66
|
+
_INORGANIC_NITROGEN_FERTILISER_USED_TERM_ID = "inorganicNitrogenFertiliserUsed"
|
|
67
|
+
_ORGANIC_FERTILISER_USED_TERM_ID = "organicFertiliserUsed"
|
|
68
|
+
_SOIL_AMENDMENT_USED_TERM_ID = "amendmentIncreasingSoilCarbonUsed"
|
|
69
|
+
|
|
70
|
+
_CLAY_CONTENT_MAX = 8
|
|
71
|
+
_SAND_CONTENT_MIN = 70
|
|
72
|
+
|
|
73
|
+
_EQUILIBRIUM_TRANSITION_PERIOD = 20
|
|
74
|
+
"""
|
|
75
|
+
The number of years required for soil organic carbon to reach equilibrium after
|
|
76
|
+
a change in land use, management regime or carbon input regime.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
_EXCLUDED_ECO_CLIMATE_ZONES = {EcoClimateZone.POLAR_MOIST, EcoClimateZone.POLAR_DRY}
|
|
80
|
+
|
|
81
|
+
_VALID_SITE_TYPES = {
|
|
82
|
+
SiteSiteType.CROPLAND.value,
|
|
83
|
+
SiteSiteType.FOREST.value,
|
|
84
|
+
SiteSiteType.OTHER_NATURAL_VEGETATION.value,
|
|
85
|
+
SiteSiteType.PERMANENT_PASTURE.value
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _measurement(
|
|
90
|
+
timestamps: list[int],
|
|
91
|
+
descriptive_stats_dict: dict
|
|
92
|
+
) -> dict:
|
|
93
|
+
"""
|
|
94
|
+
Build a Hestia `Measurement` node to contain a value and descriptive statistics calculated by the models.
|
|
95
|
+
|
|
96
|
+
The `descriptive_stats_dict` parameter should include the following keys and values from the
|
|
97
|
+
[Measurement](https://www-staging.hestia.earth/schema/Measurement) schema:
|
|
98
|
+
```
|
|
99
|
+
{
|
|
100
|
+
"value": list[float],
|
|
101
|
+
"sd": list[float],
|
|
102
|
+
"min": list[float],
|
|
103
|
+
"max": list[float],
|
|
104
|
+
"statsDefinition": str,
|
|
105
|
+
"observations": list[int]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
timestamps : list[int]
|
|
112
|
+
A list of calendar years associated to the calculated SOC stocks.
|
|
113
|
+
descriptive_stats_dict : dict
|
|
114
|
+
A dict containing the descriptive statistics data that should be added to the node.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
dict
|
|
119
|
+
A valid Hestia `Measurement` node, see: https://www.hestia.earth/schema/Measurement.
|
|
120
|
+
"""
|
|
121
|
+
measurement = _new_measurement(_TERM_ID) | descriptive_stats_dict
|
|
122
|
+
measurement["dates"] = [f"{year}-12-31" for year in timestamps]
|
|
123
|
+
measurement["depthUpper"] = DEPTH_UPPER
|
|
124
|
+
measurement["depthLower"] = DEPTH_LOWER
|
|
125
|
+
measurement["methodClassification"] = _METHOD_CLASSIFICATION
|
|
126
|
+
return measurement
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class _InventoryKey(Enum):
|
|
130
|
+
"""
|
|
131
|
+
Enum representing the inner keys of the annual inventory is constructed from `Site` data.
|
|
132
|
+
"""
|
|
133
|
+
LU_CATEGORY = "ipcc-land-use-category"
|
|
134
|
+
MG_CATEGORY = "ipcc-management-category"
|
|
135
|
+
CI_CATEGORY = "ipcc-carbon-input-category"
|
|
136
|
+
SHOULD_RUN = "should-run-tier-1"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
_REQUIRED_KEYS = {
|
|
140
|
+
_InventoryKey.LU_CATEGORY,
|
|
141
|
+
_InventoryKey.MG_CATEGORY,
|
|
142
|
+
_InventoryKey.CI_CATEGORY
|
|
143
|
+
}
|
|
144
|
+
"""
|
|
145
|
+
The `_InventoryKey`s that must have valid values for an inventory year to be included in the model.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
_SOC_REFS = {
|
|
150
|
+
IpccSoilCategory.HIGH_ACTIVITY_CLAY_SOILS: {
|
|
151
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 64000, "uncertainty": 5, "observations": 489},
|
|
152
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 24000, "uncertainty": 5, "observations": 781},
|
|
153
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 81000, "uncertainty": 5, "observations": 334},
|
|
154
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 43000, "uncertainty": 8, "observations": 177},
|
|
155
|
+
EcoClimateZone.POLAR_MOIST: {"value": 59000, "uncertainty": 41, "observations": 24},
|
|
156
|
+
EcoClimateZone.POLAR_DRY: {"value": 59000, "uncertainty": 41, "observations": 24},
|
|
157
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 63000, "uncertainty": 18, "observations": 35},
|
|
158
|
+
EcoClimateZone.BOREAL_DRY: {"value": 63000, "uncertainty": 18, "observations": 35},
|
|
159
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 51000, "uncertainty": 10, "observations": 114},
|
|
160
|
+
EcoClimateZone.TROPICAL_WET: {"value": 60000, "uncertainty": 8, "observations": 137},
|
|
161
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 40000, "uncertainty": 7, "observations": 226},
|
|
162
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 21000, "uncertainty": 5, "observations": 554}
|
|
163
|
+
},
|
|
164
|
+
IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS: {
|
|
165
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 55000, "uncertainty": 8, "observations": 183},
|
|
166
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 19000, "uncertainty": 16, "observations": 41},
|
|
167
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 76000, "uncertainty": 51, "observations": 6},
|
|
168
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 33000, "uncertainty": 90},
|
|
169
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 44000, "uncertainty": 11, "observations": 84},
|
|
170
|
+
EcoClimateZone.TROPICAL_WET: {"value": 52000, "uncertainty": 6, "observations": 271},
|
|
171
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 38000, "uncertainty": 5, "observations": 326},
|
|
172
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 19000, "uncertainty": 10, "observations": 135}
|
|
173
|
+
},
|
|
174
|
+
IpccSoilCategory.SANDY_SOILS: {
|
|
175
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 36000, "uncertainty": 23, "observations": 39},
|
|
176
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 10000, "uncertainty": 5, "observations": 338},
|
|
177
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 81000, "uncertainty": 5, "observations": 334},
|
|
178
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 51000, "uncertainty": 13, "observations": 126},
|
|
179
|
+
EcoClimateZone.POLAR_MOIST: {"value": 27000, "uncertainty": 67, "observations": 18},
|
|
180
|
+
EcoClimateZone.POLAR_DRY: {"value": 27000, "uncertainty": 67, "observations": 18},
|
|
181
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 10000, "uncertainty": 90},
|
|
182
|
+
EcoClimateZone.BOREAL_DRY: {"value": 10000, "uncertainty": 90},
|
|
183
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 52000, "uncertainty": 34, "observations": 11},
|
|
184
|
+
EcoClimateZone.TROPICAL_WET: {"value": 46000, "uncertainty": 20, "observations": 43},
|
|
185
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 27, "uncertainty": 12, "observations": 76},
|
|
186
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 9000, "uncertainty": 9, "observations": 164}
|
|
187
|
+
},
|
|
188
|
+
IpccSoilCategory.SPODIC_SOILS: {
|
|
189
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 143000, "uncertainty": 30, "observations": 9},
|
|
190
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 128000, "uncertainty": 14, "observations": 45},
|
|
191
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 117000, "uncertainty": 90},
|
|
192
|
+
EcoClimateZone.BOREAL_DRY: {"value": 117000, "uncertainty": 90}
|
|
193
|
+
},
|
|
194
|
+
IpccSoilCategory.VOLCANIC_SOILS: {
|
|
195
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 138000, "uncertainty": 12, "observations": 42},
|
|
196
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 84000, "uncertainty": 65, "observations": 10},
|
|
197
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 136000, "uncertainty": 14, "observations": 28},
|
|
198
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 20000, "uncertainty": 90},
|
|
199
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 20000, "uncertainty": 90},
|
|
200
|
+
EcoClimateZone.BOREAL_DRY: {"value": 20000, "uncertainty": 90},
|
|
201
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 96000, "uncertainty": 31, "observations": 10},
|
|
202
|
+
EcoClimateZone.TROPICAL_WET: {"value": 77000, "uncertainty": 27, "observations": 14},
|
|
203
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 70000, "uncertainty": 90},
|
|
204
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 50000, "uncertainty": 90}
|
|
205
|
+
},
|
|
206
|
+
IpccSoilCategory.WETLAND_SOILS: {
|
|
207
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 135000, "uncertainty": 28, "observations": 28},
|
|
208
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 74000, "uncertainty": 17, "observations": 49},
|
|
209
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 128000, "uncertainty": 13, "observations": 42},
|
|
210
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 87000, "uncertainty": 90},
|
|
211
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 116000, "uncertainty": 65, "observations": 6},
|
|
212
|
+
EcoClimateZone.BOREAL_DRY: {"value": 116000, "uncertainty": 65, "observations": 6},
|
|
213
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 82000, "uncertainty": 50, "observations": 12},
|
|
214
|
+
EcoClimateZone.TROPICAL_WET: {"value": 49000, "uncertainty": 19, "observations": 33},
|
|
215
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 68000, "uncertainty": 17, "observations": 55},
|
|
216
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 22000, "uncertainty": 17, "observations": 32}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
_LAND_USE_FACTORS = {
|
|
221
|
+
IpccLandUseCategory.GRASSLAND: {
|
|
222
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1},
|
|
223
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1},
|
|
224
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1},
|
|
225
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1},
|
|
226
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1},
|
|
227
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1},
|
|
228
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1},
|
|
229
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1},
|
|
230
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1},
|
|
231
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1}
|
|
232
|
+
},
|
|
233
|
+
IpccLandUseCategory.PERENNIAL_CROPS: {
|
|
234
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 0.72, "error": 22},
|
|
235
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 0.72, "error": 22},
|
|
236
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 0.72, "error": 22},
|
|
237
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 0.72, "error": 22},
|
|
238
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 0.72, "error": 22},
|
|
239
|
+
EcoClimateZone.BOREAL_DRY: {"value": 0.72, "error": 22},
|
|
240
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1.1, "error": 50},
|
|
241
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1.1, "error": 25},
|
|
242
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1.1, "error": 25},
|
|
243
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1.1, "error": 25}
|
|
244
|
+
},
|
|
245
|
+
IpccLandUseCategory.PADDY_RICE_CULTIVATION: {
|
|
246
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1.35, "error": 4},
|
|
247
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1.35, "error": 4},
|
|
248
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1.35, "error": 4},
|
|
249
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1.35, "error": 4},
|
|
250
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1.35, "error": 4},
|
|
251
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1.35, "error": 4},
|
|
252
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1.35, "error": 4},
|
|
253
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1.35, "error": 4},
|
|
254
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1.35, "error": 4},
|
|
255
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1.35, "error": 4}
|
|
256
|
+
},
|
|
257
|
+
IpccLandUseCategory.ANNUAL_CROPS_WET: {
|
|
258
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 0.71, "error": 41},
|
|
259
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 0.71, "error": 41},
|
|
260
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 0.71, "error": 41},
|
|
261
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 0.71, "error": 41},
|
|
262
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 0.71, "error": 41},
|
|
263
|
+
EcoClimateZone.BOREAL_DRY: {"value": 0.71, "error": 41},
|
|
264
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 0.86, "error": 50},
|
|
265
|
+
EcoClimateZone.TROPICAL_WET: {"value": 0.83, "error": 11},
|
|
266
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 0.83, "error": 11},
|
|
267
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 0.92, "error": 13}
|
|
268
|
+
},
|
|
269
|
+
IpccLandUseCategory.ANNUAL_CROPS: {
|
|
270
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 0.69, "error": 16},
|
|
271
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 0.76, "error": 12},
|
|
272
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 0.70, "error": 12},
|
|
273
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 0.77, "error": 14},
|
|
274
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 0.70, "error": 12},
|
|
275
|
+
EcoClimateZone.BOREAL_DRY: {"value": 0.77, "error": 14},
|
|
276
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 0.86, "error": 50},
|
|
277
|
+
EcoClimateZone.TROPICAL_WET: {"value": 0.83, "error": 11},
|
|
278
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 0.83, "error": 11},
|
|
279
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 0.92, "error": 13}
|
|
280
|
+
},
|
|
281
|
+
IpccLandUseCategory.SET_ASIDE: {
|
|
282
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 0.82, "error": 17},
|
|
283
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 0.93, "error": 11},
|
|
284
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 0.82, "error": 17},
|
|
285
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 0.93, "error": 11},
|
|
286
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 0.82, "error": 17},
|
|
287
|
+
EcoClimateZone.BOREAL_DRY: {"value": 0.93, "error": 11},
|
|
288
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 0.88, "error": 50},
|
|
289
|
+
EcoClimateZone.TROPICAL_WET: {"value": 0.82, "error": 17},
|
|
290
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 0.82, "error": 17},
|
|
291
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 0.93, "error": 11}
|
|
292
|
+
},
|
|
293
|
+
IpccLandUseCategory.FOREST: {
|
|
294
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1},
|
|
295
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1},
|
|
296
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1},
|
|
297
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1},
|
|
298
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1},
|
|
299
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1},
|
|
300
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1},
|
|
301
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1},
|
|
302
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1},
|
|
303
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1}
|
|
304
|
+
},
|
|
305
|
+
IpccLandUseCategory.NATIVE: {
|
|
306
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1},
|
|
307
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1},
|
|
308
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1},
|
|
309
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1},
|
|
310
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1},
|
|
311
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1},
|
|
312
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1},
|
|
313
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1},
|
|
314
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1},
|
|
315
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1}
|
|
316
|
+
},
|
|
317
|
+
IpccLandUseCategory.OTHER: {
|
|
318
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1},
|
|
319
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1},
|
|
320
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1},
|
|
321
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1},
|
|
322
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1},
|
|
323
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1},
|
|
324
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1},
|
|
325
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1},
|
|
326
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1},
|
|
327
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
_MANAGEMENT_FACTORS = {
|
|
332
|
+
IpccManagementCategory.SEVERELY_DEGRADED: {
|
|
333
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 0.7, "error": 40},
|
|
334
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 0.7, "error": 40},
|
|
335
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 0.7, "error": 40},
|
|
336
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 0.7, "error": 40},
|
|
337
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 0.7, "error": 40},
|
|
338
|
+
EcoClimateZone.BOREAL_DRY: {"value": 0.7, "error": 40},
|
|
339
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 0.7, "error": 40},
|
|
340
|
+
EcoClimateZone.TROPICAL_WET: {"value": 0.7, "error": 40},
|
|
341
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 0.7, "error": 40},
|
|
342
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 0.7, "error": 40}
|
|
343
|
+
},
|
|
344
|
+
IpccManagementCategory.IMPROVED_GRASSLAND: {
|
|
345
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1.14, "error": 11},
|
|
346
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1.14, "error": 11},
|
|
347
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1.14, "error": 11},
|
|
348
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1.14, "error": 11},
|
|
349
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1.14, "error": 11},
|
|
350
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1.14, "error": 11},
|
|
351
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1.16, "error": 40},
|
|
352
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1.17, "error": 9},
|
|
353
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1.17, "error": 9},
|
|
354
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1.17, "error": 9}
|
|
355
|
+
},
|
|
356
|
+
IpccManagementCategory.HIGH_INTENSITY_GRAZING: {
|
|
357
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 0.9, "error": 8},
|
|
358
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 0.9, "error": 8},
|
|
359
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 0.9, "error": 8},
|
|
360
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 0.9, "error": 8},
|
|
361
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 0.9, "error": 8},
|
|
362
|
+
EcoClimateZone.BOREAL_DRY: {"value": 0.9, "error": 8},
|
|
363
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 0.9, "error": 8},
|
|
364
|
+
EcoClimateZone.TROPICAL_WET: {"value": 0.9, "error": 8},
|
|
365
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 0.9, "error": 8},
|
|
366
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 0.9, "error": 8}
|
|
367
|
+
},
|
|
368
|
+
IpccManagementCategory.NOMINALLY_MANAGED: {
|
|
369
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1},
|
|
370
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1},
|
|
371
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1},
|
|
372
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1},
|
|
373
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1},
|
|
374
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1},
|
|
375
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1},
|
|
376
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1},
|
|
377
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1},
|
|
378
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1}
|
|
379
|
+
},
|
|
380
|
+
IpccManagementCategory.FULL_TILLAGE: {
|
|
381
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1},
|
|
382
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1},
|
|
383
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1},
|
|
384
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1},
|
|
385
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1},
|
|
386
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1},
|
|
387
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1},
|
|
388
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1},
|
|
389
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1},
|
|
390
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1}
|
|
391
|
+
},
|
|
392
|
+
IpccManagementCategory.REDUCED_TILLAGE: {
|
|
393
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1.05, "error": 4},
|
|
394
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 0.99, "error": 3},
|
|
395
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1.04, "error": 4},
|
|
396
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 0.98, "error": 5},
|
|
397
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1.04, "error": 4},
|
|
398
|
+
EcoClimateZone.BOREAL_DRY: {"value": 0.98, "error": 5},
|
|
399
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1.02, "error": 50},
|
|
400
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1.04, "error": 7},
|
|
401
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1.04, "error": 7},
|
|
402
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 0.99, "error": 7}
|
|
403
|
+
},
|
|
404
|
+
IpccManagementCategory.NO_TILLAGE: {
|
|
405
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1.1, "error": 4},
|
|
406
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1.04, "error": 3},
|
|
407
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1.09, "error": 4},
|
|
408
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1.03, "error": 4},
|
|
409
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1.09, "error": 4},
|
|
410
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1.03, "error": 4},
|
|
411
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1.08, "error": 50},
|
|
412
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1.1, "error": 5},
|
|
413
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1.1, "error": 5},
|
|
414
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1.04, "error": 7}
|
|
415
|
+
},
|
|
416
|
+
IpccManagementCategory.OTHER: {
|
|
417
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1},
|
|
418
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1},
|
|
419
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1},
|
|
420
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1},
|
|
421
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1},
|
|
422
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1},
|
|
423
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1},
|
|
424
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1},
|
|
425
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1},
|
|
426
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
_CARBON_INPUT_FACTORS = {
|
|
431
|
+
IpccCarbonInputCategory.GRASSLAND_HIGH: {
|
|
432
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1.11, "error": 7},
|
|
433
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1.11, "error": 7},
|
|
434
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1.11, "error": 7},
|
|
435
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1.11, "error": 7},
|
|
436
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1.11, "error": 7},
|
|
437
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1.11, "error": 7},
|
|
438
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1.11, "error": 7},
|
|
439
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1.11, "error": 7},
|
|
440
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1.11, "error": 7},
|
|
441
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1.11, "error": 7}
|
|
442
|
+
},
|
|
443
|
+
IpccCarbonInputCategory.GRASSLAND_MEDIUM: {
|
|
444
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1},
|
|
445
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1},
|
|
446
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1},
|
|
447
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1},
|
|
448
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1},
|
|
449
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1},
|
|
450
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1},
|
|
451
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1},
|
|
452
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1},
|
|
453
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1}
|
|
454
|
+
},
|
|
455
|
+
IpccCarbonInputCategory.CROPLAND_HIGH_WITH_MANURE: {
|
|
456
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1.44, "error": 13},
|
|
457
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1.37, "error": 12},
|
|
458
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1.44, "error": 13},
|
|
459
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1.37, "error": 12},
|
|
460
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1.44, "error": 13},
|
|
461
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1.37, "error": 12},
|
|
462
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1.41, "error": 50},
|
|
463
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1.44, "error": 13},
|
|
464
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1.44, "error": 13},
|
|
465
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1.37, "error": 12}
|
|
466
|
+
},
|
|
467
|
+
IpccCarbonInputCategory.CROPLAND_HIGH_WITHOUT_MANURE: {
|
|
468
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1.11, "error": 10},
|
|
469
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1.04, "error": 13},
|
|
470
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1.11, "error": 10},
|
|
471
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1.04, "error": 13},
|
|
472
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1.11, "error": 10},
|
|
473
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1.04, "error": 13},
|
|
474
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1.08, "error": 50},
|
|
475
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1.11, "error": 10},
|
|
476
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1.11, "error": 10},
|
|
477
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1.04, "error": 13}
|
|
478
|
+
},
|
|
479
|
+
IpccCarbonInputCategory.CROPLAND_MEDIUM: {
|
|
480
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1},
|
|
481
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1},
|
|
482
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1},
|
|
483
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1},
|
|
484
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1},
|
|
485
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1},
|
|
486
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1},
|
|
487
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1},
|
|
488
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1},
|
|
489
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1}
|
|
490
|
+
},
|
|
491
|
+
IpccCarbonInputCategory.CROPLAND_LOW: {
|
|
492
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 0.92, "error": 14},
|
|
493
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 0.95, "error": 13},
|
|
494
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 0.92, "error": 14},
|
|
495
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 0.95, "error": 13},
|
|
496
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 0.92, "error": 14},
|
|
497
|
+
EcoClimateZone.BOREAL_DRY: {"value": 0.95, "error": 13},
|
|
498
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 0.94, "error": 50},
|
|
499
|
+
EcoClimateZone.TROPICAL_WET: {"value": 0.92, "error": 14},
|
|
500
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 0.92, "error": 14},
|
|
501
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 0.95, "error": 13}
|
|
502
|
+
},
|
|
503
|
+
IpccCarbonInputCategory.OTHER: {
|
|
504
|
+
EcoClimateZone.WARM_TEMPERATE_MOIST: {"value": 1},
|
|
505
|
+
EcoClimateZone.WARM_TEMPERATE_DRY: {"value": 1},
|
|
506
|
+
EcoClimateZone.COOL_TEMPERATE_MOIST: {"value": 1},
|
|
507
|
+
EcoClimateZone.COOL_TEMPERATE_DRY: {"value": 1},
|
|
508
|
+
EcoClimateZone.BOREAL_MOIST: {"value": 1},
|
|
509
|
+
EcoClimateZone.BOREAL_DRY: {"value": 1},
|
|
510
|
+
EcoClimateZone.TROPICAL_MONTANE: {"value": 1},
|
|
511
|
+
EcoClimateZone.TROPICAL_WET: {"value": 1},
|
|
512
|
+
EcoClimateZone.TROPICAL_MOIST: {"value": 1},
|
|
513
|
+
EcoClimateZone.TROPICAL_DRY: {"value": 1}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
_KWARGS_TO_SAMPLE_FUNC = {
|
|
518
|
+
("value", "uncertainty"): sample_plus_minus_uncertainty,
|
|
519
|
+
("value", "error"): sample_plus_minus_error,
|
|
520
|
+
("value",): sample_constant
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
_IPCC_CATEGORY_TO_FACTOR_DICT = {
|
|
524
|
+
IpccSoilCategory: _SOC_REFS,
|
|
525
|
+
IpccLandUseCategory: _LAND_USE_FACTORS,
|
|
526
|
+
IpccManagementCategory: _MANAGEMENT_FACTORS,
|
|
527
|
+
IpccCarbonInputCategory: _CARBON_INPUT_FACTORS
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _sample_parameter(
|
|
532
|
+
iterations: int,
|
|
533
|
+
parameter: Union[IpccSoilCategory, IpccLandUseCategory, IpccManagementCategory, IpccCarbonInputCategory],
|
|
534
|
+
eco_climate_zone: EcoClimateZone,
|
|
535
|
+
seed: Union[int, random.Generator, None] = None
|
|
536
|
+
) -> NDArray:
|
|
537
|
+
"""
|
|
538
|
+
Sample a model parameter (SOC ref or stock change factor) using the function specified in `KWARGS_TO_SAMPLE_FUNC`.
|
|
539
|
+
|
|
540
|
+
Parameters
|
|
541
|
+
----------
|
|
542
|
+
iterations : int
|
|
543
|
+
The number of samples to take.
|
|
544
|
+
parameter : IpccSoilCategory | IpccLandUseCategory | IpccManagementCategory | IpccCarbonInputCategory
|
|
545
|
+
The model parameter to sample.
|
|
546
|
+
eco_climate_zone : EcoClimateZone
|
|
547
|
+
The eco-climate zone of the site.
|
|
548
|
+
seed : int | Generator | None, optional
|
|
549
|
+
A seed to initialize the BitGenerator. If passed a Generator, it will be returned unaltered. If `None`, then
|
|
550
|
+
fresh, unpredictable entropy will be pulled from the OS.
|
|
551
|
+
|
|
552
|
+
Returns
|
|
553
|
+
-------
|
|
554
|
+
NDArray
|
|
555
|
+
The sampled parameter as a numpy array with shape `(1, iterations)`.
|
|
556
|
+
"""
|
|
557
|
+
parameter_dict = _IPCC_CATEGORY_TO_FACTOR_DICT.get(type(parameter))
|
|
558
|
+
kwargs = parameter_dict.get(parameter, {}).get(eco_climate_zone, {})
|
|
559
|
+
func = _get_sample_func(kwargs)
|
|
560
|
+
return func(iterations=iterations, seed=seed, **kwargs)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _get_sample_func(kwargs: dict) -> Callable:
|
|
564
|
+
"""
|
|
565
|
+
Select the correct sample function for a parameter based on the distribution data available. All possible
|
|
566
|
+
parameters for the model should have, at a minimum, a `value`, meaning that no default function needs to be
|
|
567
|
+
specified.
|
|
568
|
+
|
|
569
|
+
This function has been extracted into it's own method to allow for mocking of sample function.
|
|
570
|
+
|
|
571
|
+
Keyword Args
|
|
572
|
+
------------
|
|
573
|
+
value : float
|
|
574
|
+
The distribution mean.
|
|
575
|
+
uncertainty : float
|
|
576
|
+
The +/- uncertainty of the 95% confidence interval expressed as a percentge of the mean.
|
|
577
|
+
error : float
|
|
578
|
+
Two standard deviations expressed as a percentage of the mean.
|
|
579
|
+
|
|
580
|
+
Returns
|
|
581
|
+
-------
|
|
582
|
+
Callable
|
|
583
|
+
The sample function for the distribution.
|
|
584
|
+
"""
|
|
585
|
+
return next(
|
|
586
|
+
sample_func for required_kwargs, sample_func in _KWARGS_TO_SAMPLE_FUNC.items()
|
|
587
|
+
if all(kwarg in kwargs.keys() for kwarg in required_kwargs)
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def _get_soc_ref_preview(ipcc_soil_category: IpccSoilCategory, eco_climate_zone: EcoClimateZone) -> Union[float, None]:
|
|
592
|
+
"""
|
|
593
|
+
Retrieve the mean value of the SOC ref for a specific combination of `IpccSoilCategory` and `EcoClimateZone`. This
|
|
594
|
+
is primarily for logging purposes.
|
|
595
|
+
|
|
596
|
+
Parameters
|
|
597
|
+
----------
|
|
598
|
+
ipcc_soil_category : IpccSoilCategory
|
|
599
|
+
eco_climate_zone: EcoClimateZone
|
|
600
|
+
|
|
601
|
+
Returns
|
|
602
|
+
-------
|
|
603
|
+
float | None
|
|
604
|
+
The mean value SOC ref or `None` if no reference value is available.
|
|
605
|
+
"""
|
|
606
|
+
return _SOC_REFS.get(ipcc_soil_category, {}).get(eco_climate_zone, {}).get("value", None)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
# --- TIER 1 MODEL ---
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def should_run(site: dict) -> tuple[bool, dict, dict]:
|
|
613
|
+
"""
|
|
614
|
+
Extract data from site & related cycles, pre-process data and determine whether there is sufficient data to run the
|
|
615
|
+
Tier 1 model.
|
|
616
|
+
|
|
617
|
+
The returned `inventory` should be a dict with the shape:
|
|
618
|
+
```
|
|
619
|
+
{
|
|
620
|
+
year (int): {
|
|
621
|
+
_InventoryKey.SHOULD_RUN: bool,
|
|
622
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory,
|
|
623
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory,
|
|
624
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory
|
|
625
|
+
},
|
|
626
|
+
...
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
The returned `kwargs` should be a dict with the shape:
|
|
631
|
+
```
|
|
632
|
+
{
|
|
633
|
+
"eco_climate_zone": int,
|
|
634
|
+
"ipcc_soil_category": IpccSoilCategory,
|
|
635
|
+
"soc_ref": float
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Parameters
|
|
640
|
+
----------
|
|
641
|
+
site : dict
|
|
642
|
+
A Hestia `Site` node, see: https://www.hestia.earth/schema/Site.
|
|
643
|
+
|
|
644
|
+
Returns
|
|
645
|
+
-------
|
|
646
|
+
tuple[bool, dict, dict]
|
|
647
|
+
A tuple containing `(should_run_, inventory, kwargs)`.
|
|
648
|
+
"""
|
|
649
|
+
site_type = site.get("siteType", "")
|
|
650
|
+
management_nodes = site.get("management", [])
|
|
651
|
+
measurement_nodes = site.get("measurements", [])
|
|
652
|
+
|
|
653
|
+
has_management = len(management_nodes) > 0
|
|
654
|
+
has_measurements = len(measurement_nodes) > 0
|
|
655
|
+
|
|
656
|
+
should_compile_inventory = all([
|
|
657
|
+
site_type in _VALID_SITE_TYPES,
|
|
658
|
+
has_management,
|
|
659
|
+
has_measurements
|
|
660
|
+
])
|
|
661
|
+
|
|
662
|
+
inventory, kwargs = (
|
|
663
|
+
_compile_inventory(site_type, management_nodes, measurement_nodes)
|
|
664
|
+
if should_compile_inventory else ({}, {})
|
|
665
|
+
)
|
|
666
|
+
kwargs["seed"] = gen_seed(site)
|
|
667
|
+
|
|
668
|
+
should_run_ = all([
|
|
669
|
+
kwargs.get("eco_climate_zone") not in _EXCLUDED_ECO_CLIMATE_ZONES,
|
|
670
|
+
(kwargs.get("soc_ref") or -9999) > 0,
|
|
671
|
+
any(year for year, group in inventory.items() if group.get(_InventoryKey.SHOULD_RUN))
|
|
672
|
+
])
|
|
673
|
+
|
|
674
|
+
logs = {
|
|
675
|
+
"site_type": site_type,
|
|
676
|
+
"has_management": has_management,
|
|
677
|
+
"has_measurements": has_measurements,
|
|
678
|
+
"should_compile_inventory_tier_1": should_compile_inventory,
|
|
679
|
+
"should_run_tier_1": should_run_
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return should_run_, inventory, kwargs, logs
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def run(
|
|
686
|
+
inventory: dict,
|
|
687
|
+
*,
|
|
688
|
+
eco_climate_zone: EcoClimateZone,
|
|
689
|
+
ipcc_soil_category: IpccSoilCategory,
|
|
690
|
+
iterations: int,
|
|
691
|
+
seed: Union[int, random.Generator, None] = None,
|
|
692
|
+
**_
|
|
693
|
+
) -> list[dict]:
|
|
694
|
+
"""
|
|
695
|
+
Run the IPCC (2019) Tier 1 methodology for calculating SOC stocks (in kg C ha-1) for each year in the inventory
|
|
696
|
+
and wrap each of the calculated values in Hestia measurement nodes. To avoid any errors, the `inventory` parameter
|
|
697
|
+
must be pre-validated by the `should_run` function.
|
|
698
|
+
|
|
699
|
+
See [IPCC (2019) Vol. 4, Ch. 2](https://www.ipcc-nggip.iges.or.jp/public/2019rf/vol4.html) for more information.
|
|
700
|
+
|
|
701
|
+
The inventory should be in the following shape:
|
|
702
|
+
```
|
|
703
|
+
{
|
|
704
|
+
year (int): {
|
|
705
|
+
_InventoryKey.SHOULD_RUN: bool,
|
|
706
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory,
|
|
707
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory,
|
|
708
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory
|
|
709
|
+
},
|
|
710
|
+
...
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
Parameters
|
|
715
|
+
----------
|
|
716
|
+
inventory : dict
|
|
717
|
+
The inventory built by the `_should_run` function.
|
|
718
|
+
eco_climate_zone : EcoClimateZone
|
|
719
|
+
The eco-climate zone of the site.
|
|
720
|
+
ipcc_soil_category : IpccSoilCategory
|
|
721
|
+
The IPCC soil category of the site.
|
|
722
|
+
iterations : int
|
|
723
|
+
Number of iterations to run the model for.
|
|
724
|
+
seed : int | Generator | None, optional
|
|
725
|
+
A seed to initialize the BitGenerator. If passed a Generator, it will be returned unaltered. If `None`, then
|
|
726
|
+
fresh, unpredictable entropy will be pulled from the OS.
|
|
727
|
+
|
|
728
|
+
Returns
|
|
729
|
+
-------
|
|
730
|
+
list[dict]
|
|
731
|
+
A list of HESTIA nodes containing model output results.
|
|
732
|
+
"""
|
|
733
|
+
|
|
734
|
+
valid_inventory = {
|
|
735
|
+
year: group for year, group in inventory.items() if group.get(_InventoryKey.SHOULD_RUN)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
complete_inventory = dict(sorted(
|
|
739
|
+
merge(valid_inventory, _calc_missing_equilibrium_years(valid_inventory)).items()
|
|
740
|
+
))
|
|
741
|
+
|
|
742
|
+
timestamps = [year for year in complete_inventory.keys()]
|
|
743
|
+
land_use_categories = [group[_InventoryKey.LU_CATEGORY] for group in complete_inventory.values()]
|
|
744
|
+
management_categories = [group[_InventoryKey.MG_CATEGORY] for group in complete_inventory.values()]
|
|
745
|
+
carbon_input_categories = [group[_InventoryKey.CI_CATEGORY] for group in complete_inventory.values()]
|
|
746
|
+
|
|
747
|
+
regime_start_years = _calc_regime_start_years(complete_inventory)
|
|
748
|
+
|
|
749
|
+
rng = random.default_rng(seed)
|
|
750
|
+
|
|
751
|
+
soc_ref = _sample_parameter(iterations, ipcc_soil_category, eco_climate_zone, seed=rng)
|
|
752
|
+
land_use_factors = _get_factor_annual(iterations, land_use_categories, eco_climate_zone, seed=rng)
|
|
753
|
+
management_factors = _get_factor_annual(iterations, management_categories, eco_climate_zone, seed=rng)
|
|
754
|
+
carbon_input_factors = _get_factor_annual(iterations, carbon_input_categories, eco_climate_zone, seed=rng)
|
|
755
|
+
|
|
756
|
+
soc_equilibriums = _calc_soc_equilibrium(soc_ref, land_use_factors, management_factors, carbon_input_factors)
|
|
757
|
+
soc_stocks = _calc_soc_stocks(timestamps, regime_start_years, soc_equilibriums)
|
|
758
|
+
|
|
759
|
+
descriptive_stats = calc_descriptive_stats(
|
|
760
|
+
soc_stocks,
|
|
761
|
+
STATS_DEFINITION,
|
|
762
|
+
axis=1, # Calculate stats rowwise.
|
|
763
|
+
decimals=6 # Round values to the nearest milligram.
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
return [_measurement(timestamps, descriptive_stats)]
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def _calc_missing_equilibrium_years(inventory: dict) -> dict:
|
|
770
|
+
"""
|
|
771
|
+
Calculate any missing inventory years where SOC would have reached equilibrium and return them as a dict.
|
|
772
|
+
|
|
773
|
+
Parameters
|
|
774
|
+
----------
|
|
775
|
+
inventory : dict
|
|
776
|
+
|
|
777
|
+
Returns
|
|
778
|
+
-------
|
|
779
|
+
dict
|
|
780
|
+
A dictionary of missing equilibrium years with the same structure as `inventory`.
|
|
781
|
+
"""
|
|
782
|
+
|
|
783
|
+
min_year, max_year = min(inventory.keys()), max(inventory.keys())
|
|
784
|
+
|
|
785
|
+
def add_missing_equilibrium_year(missing_years: dict, year: int):
|
|
786
|
+
group = inventory[year]
|
|
787
|
+
existing_years = set(list(inventory.keys()) + list(missing_years.keys()))
|
|
788
|
+
|
|
789
|
+
regime_start_year = _calc_regime_start_year(year, inventory)
|
|
790
|
+
equilibrium_year = regime_start_year + _EQUILIBRIUM_TRANSITION_PERIOD
|
|
791
|
+
|
|
792
|
+
should_add_equilibrium = (
|
|
793
|
+
min_year < equilibrium_year < max_year # Is the year relevant?
|
|
794
|
+
and equilibrium_year not in existing_years # Is the year missing?
|
|
795
|
+
and not any(year_ in existing_years for year_ in range(year+1, equilibrium_year)) # Is the year superseded?
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
if should_add_equilibrium:
|
|
799
|
+
missing_years[equilibrium_year] = group
|
|
800
|
+
|
|
801
|
+
return missing_years
|
|
802
|
+
|
|
803
|
+
missing_years = reduce(add_missing_equilibrium_year, inventory.keys(), dict())
|
|
804
|
+
|
|
805
|
+
return missing_years
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def _calc_regime_start_years(inventory: dict):
|
|
809
|
+
"""
|
|
810
|
+
Calculate when the land-use and land-management regime of all inventory years began.
|
|
811
|
+
|
|
812
|
+
Parameters
|
|
813
|
+
----------
|
|
814
|
+
inventory : dict
|
|
815
|
+
|
|
816
|
+
Returns
|
|
817
|
+
-------
|
|
818
|
+
list[int]
|
|
819
|
+
"""
|
|
820
|
+
return [_calc_regime_start_year(year, inventory) for year in inventory.keys()]
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def _calc_regime_start_year(current_year: int, inventory: dict) -> int:
|
|
824
|
+
"""
|
|
825
|
+
Calculate when the land-use and land-management regime of a specific inventory year began.
|
|
826
|
+
|
|
827
|
+
Parameters
|
|
828
|
+
----------
|
|
829
|
+
current_year : int
|
|
830
|
+
inventory : dict
|
|
831
|
+
|
|
832
|
+
Returns
|
|
833
|
+
-------
|
|
834
|
+
int
|
|
835
|
+
"""
|
|
836
|
+
MATCH_KEYS = {_InventoryKey.LU_CATEGORY, _InventoryKey.MG_CATEGORY, _InventoryKey.CI_CATEGORY}
|
|
837
|
+
previous_years = list(reversed([year for year in inventory.keys() if year <= current_year]))
|
|
838
|
+
return next(
|
|
839
|
+
(
|
|
840
|
+
previous_years[i-1] for i, previous_year in enumerate(previous_years)
|
|
841
|
+
if not all([
|
|
842
|
+
inventory[current_year][key] == inventory[previous_year][key]
|
|
843
|
+
for key in MATCH_KEYS
|
|
844
|
+
])
|
|
845
|
+
),
|
|
846
|
+
previous_years[-1] - _EQUILIBRIUM_TRANSITION_PERIOD
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def _get_factor_annual(
|
|
851
|
+
iterations: int,
|
|
852
|
+
category_annual: list[Union[IpccLandUseCategory, IpccManagementCategory, IpccCarbonInputCategory]],
|
|
853
|
+
eco_climate_zone: EcoClimateZone,
|
|
854
|
+
seed: Optional[int] = None
|
|
855
|
+
) -> NDArray:
|
|
856
|
+
"""
|
|
857
|
+
Build an numpy array with the shape `(len(category_annual), iterations)`, where each row represents an inventory
|
|
858
|
+
year and each column contains a sampled value for that year's factor. All rows representing the same factor should
|
|
859
|
+
be identical.
|
|
860
|
+
|
|
861
|
+
Parameters
|
|
862
|
+
----------
|
|
863
|
+
iterations : int
|
|
864
|
+
The number of samples to take for each year.
|
|
865
|
+
category_annual : list[IpccLandUseCategory | IpccManagementCategory | IpccCarbonInputCategory]
|
|
866
|
+
A list of annual IPCC categories that are linked to SOC stock change factors.
|
|
867
|
+
eco_climate_zone : EcoClimateZone
|
|
868
|
+
The eco-climate zone of the site.
|
|
869
|
+
seed : int | None
|
|
870
|
+
An optional seed for the random sampling of model parameters. If `None`, then fresh, unpredictable entropy will
|
|
871
|
+
be pulled from the OS.
|
|
872
|
+
|
|
873
|
+
Returns
|
|
874
|
+
-------
|
|
875
|
+
NDArray
|
|
876
|
+
The sampled factors as a numpy array.
|
|
877
|
+
"""
|
|
878
|
+
param_cache = {
|
|
879
|
+
category: _sample_parameter(iterations, category, eco_climate_zone, seed=seed)
|
|
880
|
+
for category in sorted(set(category_annual), key=lambda category: category.value)
|
|
881
|
+
}
|
|
882
|
+
return vstack([param_cache[category] for category in category_annual])
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def _calc_soc_equilibrium(
|
|
886
|
+
soc_ref: NDArray,
|
|
887
|
+
land_use_factor: NDArray,
|
|
888
|
+
management_factor: NDArray,
|
|
889
|
+
carbon_input_factor: NDArray
|
|
890
|
+
) -> NDArray:
|
|
891
|
+
"""
|
|
892
|
+
Calculate the soil organic carbon (SOC) equilibrium based on reference SOC and factors.
|
|
893
|
+
|
|
894
|
+
In the tier 1 model, SOC equilibriums are considered to be reached after 20 years of consistant land use,
|
|
895
|
+
management and carbon input.
|
|
896
|
+
|
|
897
|
+
Parameters
|
|
898
|
+
----------
|
|
899
|
+
soc_ref : NDArray
|
|
900
|
+
The reference condition SOC stock in the 0-30cm depth interval, kg C ha-1.
|
|
901
|
+
land_use_factor : NDArray
|
|
902
|
+
The stock change factor for mineral soil organic C land-use systems or sub-systems
|
|
903
|
+
for a particular land-use, dimensionless.
|
|
904
|
+
management_factor : NDArray
|
|
905
|
+
The stock change factor for mineral soil organic C for management regime, dimensionless.
|
|
906
|
+
carbon_input_factor : NDArray
|
|
907
|
+
The stock change factor for mineral soil organic C for the input of organic amendments, dimensionless.
|
|
908
|
+
|
|
909
|
+
Returns
|
|
910
|
+
-------
|
|
911
|
+
NDArray
|
|
912
|
+
The calculated SOC equilibrium, kg C ha-1.
|
|
913
|
+
"""
|
|
914
|
+
return soc_ref * land_use_factor * management_factor * carbon_input_factor
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def _calc_soc_stocks(
|
|
918
|
+
timestamps: list[int],
|
|
919
|
+
regime_start_years: list[int],
|
|
920
|
+
soc_equilibriums: NDArray
|
|
921
|
+
) -> NDArray:
|
|
922
|
+
"""
|
|
923
|
+
Calculate soil organic carbon (SOC) stocks (kg C ha-1) in the 0-30cm depth interval for each year in the inventory.
|
|
924
|
+
|
|
925
|
+
Parameters
|
|
926
|
+
----------
|
|
927
|
+
timestamps : list[int]
|
|
928
|
+
A list of timestamps for each year in the inventory.
|
|
929
|
+
regime_start_years : list[int]
|
|
930
|
+
A pre-calculated list of the regime start year for each year in the inventory.
|
|
931
|
+
soc_equilibriums : NDArray
|
|
932
|
+
A numpy array of SOC equilibriums where each row represents a different calendar year.
|
|
933
|
+
|
|
934
|
+
Returns
|
|
935
|
+
-------
|
|
936
|
+
NDArray
|
|
937
|
+
SOC stocks for each year in the inventory.
|
|
938
|
+
"""
|
|
939
|
+
soc_stocks = empty_like(soc_equilibriums)
|
|
940
|
+
soc_stocks[0] = soc_equilibriums[0]
|
|
941
|
+
|
|
942
|
+
for index in range(1, len(timestamps)):
|
|
943
|
+
|
|
944
|
+
current_year = timestamps[index]
|
|
945
|
+
current_soc_equilibrium = soc_equilibriums[index]
|
|
946
|
+
current_regime_start_year = regime_start_years[index]
|
|
947
|
+
|
|
948
|
+
previous_index = (
|
|
949
|
+
timestamps.index(current_regime_start_year) - 1 if current_regime_start_year in timestamps else 0
|
|
950
|
+
)
|
|
951
|
+
previous_year = timestamps[previous_index]
|
|
952
|
+
previous_soc_stock = soc_stocks[previous_index]
|
|
953
|
+
|
|
954
|
+
regime_duration = current_year - previous_year
|
|
955
|
+
time_ratio = min(regime_duration / _EQUILIBRIUM_TRANSITION_PERIOD, 1)
|
|
956
|
+
soc_delta = (current_soc_equilibrium - previous_soc_stock) * time_ratio
|
|
957
|
+
|
|
958
|
+
soc_stocks[index] = previous_soc_stock + soc_delta
|
|
959
|
+
|
|
960
|
+
return soc_stocks
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
# --- COMPILE TIER 1 INVENTORY ---
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
def _compile_inventory(
|
|
967
|
+
site_type: str, management_nodes: list[dict], measurement_nodes: list[dict]
|
|
968
|
+
) -> tuple[dict, dict]:
|
|
969
|
+
"""
|
|
970
|
+
Builds an annual inventory of data and a dictionary of keyword arguments for the tier 1 model.
|
|
971
|
+
|
|
972
|
+
Parameters
|
|
973
|
+
----------
|
|
974
|
+
site_id : str
|
|
975
|
+
The `@id` of the site.
|
|
976
|
+
site_type : str
|
|
977
|
+
A valid [site type](https://www-staging.hestia.earth/schema/Site#siteType).
|
|
978
|
+
management_nodes : list[dict]
|
|
979
|
+
A list of [Management nodes](https://www-staging.hestia.earth/schema/Management).
|
|
980
|
+
measurement_nodes: list[dict]
|
|
981
|
+
A list of [Measurement nodes](https://www-staging.hestia.earth/schema/Measurement).
|
|
982
|
+
|
|
983
|
+
Returns
|
|
984
|
+
-------
|
|
985
|
+
tuple[dict, dict]
|
|
986
|
+
A tuple containing `(inventory, kwargs)`.
|
|
987
|
+
"""
|
|
988
|
+
eco_climate_zone = _get_eco_climate_zone(measurement_nodes)
|
|
989
|
+
ipcc_soil_category = _assign_ipcc_soil_category(measurement_nodes)
|
|
990
|
+
soc_ref = _get_soc_ref_preview(ipcc_soil_category, eco_climate_zone)
|
|
991
|
+
grouped_management = group_nodes_by_year(management_nodes)
|
|
992
|
+
|
|
993
|
+
# If no `landCover` nodes in `site.management` use `site.siteType` to assign static `IpccLandUseCategory`.
|
|
994
|
+
run_with_site_type = len(filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])) == 0
|
|
995
|
+
site_type_ipcc_land_use_category = SITE_TYPE_TO_IPCC_LAND_USE_CATEGORY.get(site_type, IpccLandUseCategory.OTHER)
|
|
996
|
+
|
|
997
|
+
grouped_management = group_nodes_by_year(management_nodes)
|
|
998
|
+
|
|
999
|
+
grouped_land_use_categories = {
|
|
1000
|
+
year: {
|
|
1001
|
+
_InventoryKey.LU_CATEGORY: (
|
|
1002
|
+
site_type_ipcc_land_use_category if run_with_site_type
|
|
1003
|
+
else _assign_ipcc_land_use_category(nodes, ipcc_soil_category)
|
|
1004
|
+
)
|
|
1005
|
+
} for year, nodes in grouped_management.items()
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
grouped_management_categories = {
|
|
1009
|
+
year: {
|
|
1010
|
+
_InventoryKey.MG_CATEGORY: _assign_ipcc_management_category(
|
|
1011
|
+
nodes,
|
|
1012
|
+
grouped_land_use_categories[year][_InventoryKey.LU_CATEGORY]
|
|
1013
|
+
)
|
|
1014
|
+
} for year, nodes in grouped_management.items()
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
grouped_carbon_input_categories = {
|
|
1018
|
+
year: {
|
|
1019
|
+
_InventoryKey.CI_CATEGORY: _assign_ipcc_carbon_input_category(
|
|
1020
|
+
nodes,
|
|
1021
|
+
grouped_management_categories[year][_InventoryKey.MG_CATEGORY]
|
|
1022
|
+
)
|
|
1023
|
+
} for year, nodes in grouped_management.items()
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
grouped_data = merge(
|
|
1027
|
+
grouped_land_use_categories,
|
|
1028
|
+
grouped_management_categories,
|
|
1029
|
+
grouped_carbon_input_categories
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
grouped_should_run = {
|
|
1033
|
+
year: {_InventoryKey.SHOULD_RUN: _should_run_inventory_year(group)}
|
|
1034
|
+
for year, group in grouped_data.items()
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
inventory = merge(grouped_data, grouped_should_run)
|
|
1038
|
+
kwargs = {
|
|
1039
|
+
"eco_climate_zone": eco_climate_zone,
|
|
1040
|
+
"ipcc_soil_category": ipcc_soil_category,
|
|
1041
|
+
"run_with_site_type": run_with_site_type,
|
|
1042
|
+
"soc_ref": soc_ref
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
return inventory, kwargs
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
def _get_eco_climate_zone(measurement_nodes: list[dict]) -> Union[EcoClimateZone, None]:
|
|
1049
|
+
"""
|
|
1050
|
+
Get the eco-climate zone value from a list of Measurement nodes.
|
|
1051
|
+
|
|
1052
|
+
Parameters
|
|
1053
|
+
----------
|
|
1054
|
+
measurement_nodes : list[dict]
|
|
1055
|
+
A list of [Measurement nodes](https://www-staging.hestia.earth/schema/Measurement).
|
|
1056
|
+
|
|
1057
|
+
Returns
|
|
1058
|
+
-------
|
|
1059
|
+
EcoClimateZone | None
|
|
1060
|
+
The eco-climate zone value if found, otherwise `None`.
|
|
1061
|
+
"""
|
|
1062
|
+
eco_climate_zone = find_term_match(measurement_nodes, "ecoClimateZone")
|
|
1063
|
+
value = get_node_value(eco_climate_zone)
|
|
1064
|
+
return EcoClimateZone(value) if value else None
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def _assign_ipcc_soil_category(
|
|
1068
|
+
measurement_nodes: list[dict],
|
|
1069
|
+
default: IpccSoilCategory = IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS
|
|
1070
|
+
) -> IpccSoilCategory:
|
|
1071
|
+
"""
|
|
1072
|
+
Assign an IPCC soil category based on a site's measurement nodes.
|
|
1073
|
+
|
|
1074
|
+
Parameters
|
|
1075
|
+
----------
|
|
1076
|
+
measurement_nodes : list[dict]
|
|
1077
|
+
List of A list of [Measurement nodes](https://www-staging.hestia.earth/schema/Measurement)..
|
|
1078
|
+
default : IpccSoilCategory, optional
|
|
1079
|
+
The default soil category if none matches, by default IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS.
|
|
1080
|
+
|
|
1081
|
+
Returns
|
|
1082
|
+
-------
|
|
1083
|
+
IpccSoilCategory
|
|
1084
|
+
The assigned IPCC soil category.
|
|
1085
|
+
"""
|
|
1086
|
+
soil_types = filter_list_term_type(measurement_nodes, TermTermType.SOILTYPE)
|
|
1087
|
+
usda_soil_types = filter_list_term_type(measurement_nodes, TermTermType.USDASOILTYPE)
|
|
1088
|
+
|
|
1089
|
+
clay_content = get_node_value(find_term_match(measurement_nodes, _CLAY_CONTENT_TERM_ID))
|
|
1090
|
+
sand_content = get_node_value(find_term_match(measurement_nodes, _SAND_CONTENT_TERM_ID))
|
|
1091
|
+
|
|
1092
|
+
has_sandy_soil = clay_content < _CLAY_CONTENT_MAX and sand_content > _SAND_CONTENT_MIN
|
|
1093
|
+
|
|
1094
|
+
return next(
|
|
1095
|
+
(
|
|
1096
|
+
key for key in _SOIL_CATEGORY_DECISION_TREE
|
|
1097
|
+
if _SOIL_CATEGORY_DECISION_TREE[key](
|
|
1098
|
+
key=key,
|
|
1099
|
+
soil_types=soil_types,
|
|
1100
|
+
usda_soil_types=usda_soil_types,
|
|
1101
|
+
has_sandy_soil=has_sandy_soil
|
|
1102
|
+
)
|
|
1103
|
+
),
|
|
1104
|
+
default
|
|
1105
|
+
) if len(soil_types) > 0 or len(usda_soil_types) > 0 else default
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def _check_soil_category(
|
|
1109
|
+
*,
|
|
1110
|
+
key: IpccSoilCategory,
|
|
1111
|
+
soil_types: list[dict],
|
|
1112
|
+
usda_soil_types: list[dict],
|
|
1113
|
+
**_
|
|
1114
|
+
) -> bool:
|
|
1115
|
+
"""
|
|
1116
|
+
Check if the soil category matches the given key.
|
|
1117
|
+
|
|
1118
|
+
Parameters
|
|
1119
|
+
----------
|
|
1120
|
+
key : IpccSoilCategory
|
|
1121
|
+
The IPCC soil category to check.
|
|
1122
|
+
soil_types : list[dict]
|
|
1123
|
+
List of soil type measurement nodes.
|
|
1124
|
+
usda_soil_types : list[dict]
|
|
1125
|
+
List of USDA soil type measurement nodes
|
|
1126
|
+
|
|
1127
|
+
Returns
|
|
1128
|
+
-------
|
|
1129
|
+
bool
|
|
1130
|
+
`True` if the soil category matches, `False` otherwise.
|
|
1131
|
+
"""
|
|
1132
|
+
SOIL_TYPE_LOOKUP = _LOOKUPS["soilType"]
|
|
1133
|
+
USDA_SOIL_TYPE_LOOKUP = _LOOKUPS["usdaSoilType"]
|
|
1134
|
+
|
|
1135
|
+
target_lookup_values = IPCC_SOIL_CATEGORY_TO_SOIL_TYPE_LOOKUP_VALUE.get(key, None)
|
|
1136
|
+
|
|
1137
|
+
is_soil_type_match = cumulative_nodes_lookup_match(
|
|
1138
|
+
soil_types,
|
|
1139
|
+
lookup=SOIL_TYPE_LOOKUP,
|
|
1140
|
+
target_lookup_values=target_lookup_values,
|
|
1141
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
1142
|
+
)
|
|
1143
|
+
|
|
1144
|
+
is_usda_soil_type_match = cumulative_nodes_lookup_match(
|
|
1145
|
+
usda_soil_types,
|
|
1146
|
+
lookup=USDA_SOIL_TYPE_LOOKUP,
|
|
1147
|
+
target_lookup_values=target_lookup_values,
|
|
1148
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
return is_soil_type_match or is_usda_soil_type_match
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
def _check_sandy_soil_category(
|
|
1155
|
+
*,
|
|
1156
|
+
key: IpccSoilCategory,
|
|
1157
|
+
soil_types: list[dict],
|
|
1158
|
+
usda_soil_types: list[dict],
|
|
1159
|
+
has_sandy_soil: bool,
|
|
1160
|
+
**_
|
|
1161
|
+
) -> bool:
|
|
1162
|
+
"""
|
|
1163
|
+
Check if the soils are sandy.
|
|
1164
|
+
|
|
1165
|
+
This function is special case of `_check_soil_category`.
|
|
1166
|
+
|
|
1167
|
+
Parameters
|
|
1168
|
+
----------
|
|
1169
|
+
key : IpccSoilCategory
|
|
1170
|
+
The IPCC soil category to check.
|
|
1171
|
+
soil_types : list[dict]
|
|
1172
|
+
List of soil type measurement nodes.
|
|
1173
|
+
usda_soil_types : list[dict]
|
|
1174
|
+
List of USDA soil type measurement nodes
|
|
1175
|
+
has_sandy_soil : bool
|
|
1176
|
+
True if the soils are sandy, False otherwise.
|
|
1177
|
+
|
|
1178
|
+
Returns
|
|
1179
|
+
-------
|
|
1180
|
+
bool
|
|
1181
|
+
`True` if the soil category matches, `False` otherwise.
|
|
1182
|
+
"""
|
|
1183
|
+
return _check_soil_category(key=key, soil_types=soil_types, usda_soil_types=usda_soil_types) or has_sandy_soil
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
_SOIL_CATEGORY_DECISION_TREE = {
|
|
1187
|
+
IpccSoilCategory.ORGANIC_SOILS: _check_soil_category,
|
|
1188
|
+
IpccSoilCategory.SANDY_SOILS: _check_sandy_soil_category,
|
|
1189
|
+
IpccSoilCategory.WETLAND_SOILS: _check_soil_category,
|
|
1190
|
+
IpccSoilCategory.VOLCANIC_SOILS: _check_soil_category,
|
|
1191
|
+
IpccSoilCategory.SPODIC_SOILS: _check_soil_category,
|
|
1192
|
+
IpccSoilCategory.HIGH_ACTIVITY_CLAY_SOILS: _check_soil_category,
|
|
1193
|
+
IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS: _check_soil_category
|
|
1194
|
+
}
|
|
1195
|
+
"""
|
|
1196
|
+
A decision tree mapping IPCC soil categories to corresponding check functions.
|
|
1197
|
+
|
|
1198
|
+
Key: IpccSoilCategory
|
|
1199
|
+
Value: Corresponding function for checking the match of the given soil category based on soil types.
|
|
1200
|
+
"""
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
def _assign_ipcc_land_use_category(
|
|
1204
|
+
management_nodes: list[dict], ipcc_soil_category: IpccSoilCategory,
|
|
1205
|
+
) -> IpccLandUseCategory:
|
|
1206
|
+
"""
|
|
1207
|
+
Assigns IPCC land use category based on management nodes and soil category.
|
|
1208
|
+
|
|
1209
|
+
Parameters
|
|
1210
|
+
----------
|
|
1211
|
+
management_nodes : list[dict]
|
|
1212
|
+
List of management nodes.
|
|
1213
|
+
ipcc_soil_category : IpccSoilCategory
|
|
1214
|
+
The site"s assigned IPCC soil category.
|
|
1215
|
+
|
|
1216
|
+
Returns
|
|
1217
|
+
-------
|
|
1218
|
+
IpccLandUseCategory
|
|
1219
|
+
Assigned IPCC land use category.
|
|
1220
|
+
"""
|
|
1221
|
+
DECISION_TREE = _LAND_USE_CATEGORY_DECISION_TREE
|
|
1222
|
+
DEFAULT = IpccLandUseCategory.OTHER
|
|
1223
|
+
|
|
1224
|
+
land_cover_nodes = filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])
|
|
1225
|
+
water_regime_nodes = filter_list_term_type(management_nodes, [TermTermType.WATERREGIME])
|
|
1226
|
+
|
|
1227
|
+
has_irrigation = check_irrigation(water_regime_nodes)
|
|
1228
|
+
has_upland_rice = _has_upland_rice(land_cover_nodes)
|
|
1229
|
+
has_irrigated_upland_rice = has_upland_rice and has_irrigation
|
|
1230
|
+
has_long_fallow = _has_long_fallow(land_cover_nodes)
|
|
1231
|
+
has_wetland_soils = ipcc_soil_category is IpccSoilCategory.WETLAND_SOILS
|
|
1232
|
+
|
|
1233
|
+
should_run_ = bool(land_cover_nodes)
|
|
1234
|
+
|
|
1235
|
+
return next(
|
|
1236
|
+
(
|
|
1237
|
+
key for key in DECISION_TREE
|
|
1238
|
+
if DECISION_TREE[key](
|
|
1239
|
+
key=key,
|
|
1240
|
+
land_cover_nodes=land_cover_nodes,
|
|
1241
|
+
has_long_fallow=has_long_fallow,
|
|
1242
|
+
has_irrigated_upland_rice=has_irrigated_upland_rice,
|
|
1243
|
+
has_wetland_soils=has_wetland_soils
|
|
1244
|
+
)
|
|
1245
|
+
),
|
|
1246
|
+
DEFAULT
|
|
1247
|
+
) if should_run_ else DEFAULT
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
def _has_upland_rice(land_cover_nodes: list[dict]) -> bool:
|
|
1251
|
+
"""
|
|
1252
|
+
Check if upland rice is present in the land cover nodes.
|
|
1253
|
+
|
|
1254
|
+
Parameters
|
|
1255
|
+
----------
|
|
1256
|
+
land_cover_nodes : list[dict]
|
|
1257
|
+
List of land cover nodes to be checked.
|
|
1258
|
+
|
|
1259
|
+
Returns
|
|
1260
|
+
-------
|
|
1261
|
+
bool
|
|
1262
|
+
`True` if upland rice is present, `False` otherwise.
|
|
1263
|
+
"""
|
|
1264
|
+
return cumulative_nodes_term_match(
|
|
1265
|
+
land_cover_nodes,
|
|
1266
|
+
target_term_ids=get_upland_rice_land_cover_terms_with_cache(),
|
|
1267
|
+
cumulative_threshold=SUPER_MAJORITY_AREA_THRESHOLD
|
|
1268
|
+
)
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
def _has_long_fallow(land_cover_nodes: list[dict]) -> bool:
|
|
1272
|
+
"""
|
|
1273
|
+
Check if long fallow terms are present in the land cover nodes.
|
|
1274
|
+
|
|
1275
|
+
n.b., a super majority of the site area must be under long fallow for it to be classified as set aside.
|
|
1276
|
+
|
|
1277
|
+
Parameters
|
|
1278
|
+
----------
|
|
1279
|
+
land_cover_nodes : list[dict]
|
|
1280
|
+
List of land cover nodes to be checked.
|
|
1281
|
+
|
|
1282
|
+
Returns
|
|
1283
|
+
-------
|
|
1284
|
+
bool
|
|
1285
|
+
`True` if long fallow is present, `False` otherwise.
|
|
1286
|
+
"""
|
|
1287
|
+
LOOKUP = _LOOKUPS["landCover"][0]
|
|
1288
|
+
TARGET_LOOKUP_VALUE = "Set aside"
|
|
1289
|
+
return cumulative_nodes_lookup_match(
|
|
1290
|
+
land_cover_nodes,
|
|
1291
|
+
lookup=LOOKUP,
|
|
1292
|
+
target_lookup_values=TARGET_LOOKUP_VALUE,
|
|
1293
|
+
cumulative_threshold=SUPER_MAJORITY_AREA_THRESHOLD
|
|
1294
|
+
) or cumulative_nodes_match(
|
|
1295
|
+
lambda node: get_node_property(node, _LONG_FALLOW_CROP_TERM_ID, False).get("value", 0),
|
|
1296
|
+
land_cover_nodes,
|
|
1297
|
+
cumulative_threshold=SUPER_MAJORITY_AREA_THRESHOLD
|
|
1298
|
+
)
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
def _check_ipcc_land_use_category(*, key: IpccLandUseCategory, land_cover_nodes: list[dict], **kwargs) -> bool:
|
|
1302
|
+
"""
|
|
1303
|
+
Check if the land cover nodes and keyword args satisfy the requirements for the given key.
|
|
1304
|
+
|
|
1305
|
+
Parameters
|
|
1306
|
+
----------
|
|
1307
|
+
key : IpccLandUseCategory
|
|
1308
|
+
The IPCC land use category to check.
|
|
1309
|
+
land_cover_nodes : list[dict]
|
|
1310
|
+
List of land cover nodes to be checked.
|
|
1311
|
+
|
|
1312
|
+
Keyword Args
|
|
1313
|
+
------------
|
|
1314
|
+
has_irrigated_upland_rice : bool
|
|
1315
|
+
Indicates whether irrigated upland rice is present on more than 30% of the site.
|
|
1316
|
+
has_long_fallow : bool
|
|
1317
|
+
Indicates whether long fallow is present on more than 70% of the site.
|
|
1318
|
+
has_wetland_soils : bool
|
|
1319
|
+
Indicates whether wetland soils are present to more than 30% of the site.
|
|
1320
|
+
|
|
1321
|
+
Returns
|
|
1322
|
+
-------
|
|
1323
|
+
bool
|
|
1324
|
+
`True` if the conditions match the specified land use category, `False` otherwise.
|
|
1325
|
+
"""
|
|
1326
|
+
LOOKUP = _LOOKUPS["landCover"][0]
|
|
1327
|
+
target_lookup_values = IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE.get(key, None)
|
|
1328
|
+
valid_lookup = cumulative_nodes_lookup_match(
|
|
1329
|
+
land_cover_nodes,
|
|
1330
|
+
lookup=LOOKUP,
|
|
1331
|
+
target_lookup_values=target_lookup_values,
|
|
1332
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
1333
|
+
)
|
|
1334
|
+
|
|
1335
|
+
validation_kwargs = _IPCC_LAND_USE_CATEGORY_TO_VALIDATION_KWARGS.get(key, set())
|
|
1336
|
+
valid_kwargs = all(v for k, v in kwargs.items() if k in validation_kwargs)
|
|
1337
|
+
|
|
1338
|
+
override_kwargs = _IPCC_LAND_USE_CATEGORY_TO_OVERRIDE_KWARGS.get(key, set())
|
|
1339
|
+
valid_override = any(v for k, v in kwargs.items() if k in override_kwargs)
|
|
1340
|
+
|
|
1341
|
+
return (valid_lookup and valid_kwargs) or valid_override
|
|
1342
|
+
|
|
1343
|
+
|
|
1344
|
+
_IPCC_LAND_USE_CATEGORY_TO_VALIDATION_KWARGS = {
|
|
1345
|
+
IpccLandUseCategory.ANNUAL_CROPS_WET: {"has_wetland_soils"},
|
|
1346
|
+
IpccLandUseCategory.SET_ASIDE: {"has_long_fallow"},
|
|
1347
|
+
}
|
|
1348
|
+
"""
|
|
1349
|
+
Keyword arguments that need to be validated in addition to the `landCover` lookup match for specific
|
|
1350
|
+
`IpccLandUseCategory`s.
|
|
1351
|
+
"""
|
|
1352
|
+
|
|
1353
|
+
_IPCC_LAND_USE_CATEGORY_TO_OVERRIDE_KWARGS = {
|
|
1354
|
+
IpccLandUseCategory.PADDY_RICE_CULTIVATION: {"has_irrigated_upland_rice"}
|
|
1355
|
+
}
|
|
1356
|
+
"""
|
|
1357
|
+
Keyword arguments that can override the `landCover` lookup match for specific `IpccLandUseCategory`s.
|
|
1358
|
+
"""
|
|
1359
|
+
|
|
1360
|
+
|
|
1361
|
+
_LAND_USE_CATEGORY_DECISION_TREE = {
|
|
1362
|
+
IpccLandUseCategory.GRASSLAND: _check_ipcc_land_use_category,
|
|
1363
|
+
IpccLandUseCategory.SET_ASIDE: _check_ipcc_land_use_category,
|
|
1364
|
+
IpccLandUseCategory.PERENNIAL_CROPS: _check_ipcc_land_use_category,
|
|
1365
|
+
IpccLandUseCategory.PADDY_RICE_CULTIVATION: _check_ipcc_land_use_category,
|
|
1366
|
+
IpccLandUseCategory.ANNUAL_CROPS_WET: _check_ipcc_land_use_category,
|
|
1367
|
+
IpccLandUseCategory.ANNUAL_CROPS: _check_ipcc_land_use_category,
|
|
1368
|
+
IpccLandUseCategory.FOREST: _check_ipcc_land_use_category,
|
|
1369
|
+
IpccLandUseCategory.NATIVE: _check_ipcc_land_use_category,
|
|
1370
|
+
IpccLandUseCategory.OTHER: _check_ipcc_land_use_category
|
|
1371
|
+
}
|
|
1372
|
+
"""
|
|
1373
|
+
A decision tree mapping IPCC soil categories to corresponding check functions.
|
|
1374
|
+
|
|
1375
|
+
Key: IpccLandUseCategory
|
|
1376
|
+
Value: Corresponding function for checking the match of the given land use category based on land cover nodes
|
|
1377
|
+
and additional kwargs.
|
|
1378
|
+
"""
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
def _assign_ipcc_management_category(
|
|
1382
|
+
management_nodes: list[dict], ipcc_land_use_category: IpccLandUseCategory
|
|
1383
|
+
) -> IpccManagementCategory:
|
|
1384
|
+
"""
|
|
1385
|
+
Assign an IPCC Management Category based on the given management nodes and IPCC Land Use Category.
|
|
1386
|
+
|
|
1387
|
+
Parameters
|
|
1388
|
+
----------
|
|
1389
|
+
management_nodes : list[dict]
|
|
1390
|
+
List of management nodes.
|
|
1391
|
+
ipcc_land_use_category : IpccLandUseCategory
|
|
1392
|
+
The IPCC Land Use Category.
|
|
1393
|
+
|
|
1394
|
+
Returns
|
|
1395
|
+
-------
|
|
1396
|
+
IpccManagementCategory
|
|
1397
|
+
The assigned IPCC Management Category.
|
|
1398
|
+
"""
|
|
1399
|
+
decision_tree = _IPCC_LAND_USE_CATEGORY_TO_DECISION_TREE.get(ipcc_land_use_category, {})
|
|
1400
|
+
default = _IPCC_LAND_USE_CATEGORY_TO_DEFAULT_IPCC_MANAGEMENT_CATEGORY.get(
|
|
1401
|
+
ipcc_land_use_category, IpccManagementCategory.OTHER
|
|
1402
|
+
)
|
|
1403
|
+
|
|
1404
|
+
land_cover_nodes = filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])
|
|
1405
|
+
tillage_nodes = filter_list_term_type(management_nodes, [TermTermType.TILLAGE])
|
|
1406
|
+
|
|
1407
|
+
should_run_ = any([
|
|
1408
|
+
decision_tree == _GRASSLAND_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE and len(land_cover_nodes) > 0,
|
|
1409
|
+
decision_tree == _TILLAGE_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE and len(tillage_nodes) > 0
|
|
1410
|
+
])
|
|
1411
|
+
|
|
1412
|
+
return next(
|
|
1413
|
+
(
|
|
1414
|
+
key for key in decision_tree
|
|
1415
|
+
if decision_tree[key](
|
|
1416
|
+
key=key,
|
|
1417
|
+
land_cover_nodes=land_cover_nodes,
|
|
1418
|
+
tillage_nodes=tillage_nodes,
|
|
1419
|
+
)
|
|
1420
|
+
),
|
|
1421
|
+
default
|
|
1422
|
+
) if should_run_ else default
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
def _check_grassland_ipcc_management_category(
|
|
1426
|
+
*, key: IpccManagementCategory, land_cover_nodes: list[dict], **_
|
|
1427
|
+
) -> bool:
|
|
1428
|
+
"""
|
|
1429
|
+
Check if the land cover nodes match the target conditions for a grassland IpccManagementCategory.
|
|
1430
|
+
|
|
1431
|
+
Parameters
|
|
1432
|
+
----------
|
|
1433
|
+
key : IpccManagementCategory
|
|
1434
|
+
The IPCC management category to check.
|
|
1435
|
+
land_cover_nodes : list[dict]
|
|
1436
|
+
List of land cover nodes to be checked.
|
|
1437
|
+
|
|
1438
|
+
Returns
|
|
1439
|
+
-------
|
|
1440
|
+
bool
|
|
1441
|
+
`True` if the conditions match the specified management category, `False` otherwise.
|
|
1442
|
+
"""
|
|
1443
|
+
target_term_id = IPCC_MANAGEMENT_CATEGORY_TO_GRASSLAND_MANAGEMENT_TERM_ID.get(key, None)
|
|
1444
|
+
return cumulative_nodes_term_match(
|
|
1445
|
+
land_cover_nodes,
|
|
1446
|
+
target_term_ids=target_term_id,
|
|
1447
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
1448
|
+
)
|
|
1449
|
+
|
|
1450
|
+
|
|
1451
|
+
_GRASSLAND_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE = {
|
|
1452
|
+
IpccManagementCategory.SEVERELY_DEGRADED: _check_grassland_ipcc_management_category,
|
|
1453
|
+
IpccManagementCategory.IMPROVED_GRASSLAND: _check_grassland_ipcc_management_category,
|
|
1454
|
+
IpccManagementCategory.HIGH_INTENSITY_GRAZING: _check_grassland_ipcc_management_category,
|
|
1455
|
+
IpccManagementCategory.NOMINALLY_MANAGED: _check_grassland_ipcc_management_category,
|
|
1456
|
+
IpccManagementCategory.OTHER: _check_grassland_ipcc_management_category
|
|
1457
|
+
}
|
|
1458
|
+
"""
|
|
1459
|
+
Decision tree mapping IPCC management categories to corresponding check functions for grassland.
|
|
1460
|
+
|
|
1461
|
+
Key: IpccManagementCategory
|
|
1462
|
+
Value: Corresponding function for checking the match of the given management category based on land cover nodes.
|
|
1463
|
+
"""
|
|
1464
|
+
|
|
1465
|
+
|
|
1466
|
+
def _check_tillage_ipcc_management_category(
|
|
1467
|
+
*, key: IpccManagementCategory, tillage_nodes: list[dict], **_
|
|
1468
|
+
) -> bool:
|
|
1469
|
+
"""
|
|
1470
|
+
Check if the tillage nodes match the target conditions for a tillage IpccManagementCategory.
|
|
1471
|
+
|
|
1472
|
+
Parameters
|
|
1473
|
+
----------
|
|
1474
|
+
key : IpccManagementCategory
|
|
1475
|
+
The IPCC management category to check.
|
|
1476
|
+
tillage_nodes : list[dict]
|
|
1477
|
+
List of tillage nodes to be checked.
|
|
1478
|
+
|
|
1479
|
+
Returns
|
|
1480
|
+
-------
|
|
1481
|
+
bool
|
|
1482
|
+
`True` if the conditions match the specified management category, `False` otherwise.
|
|
1483
|
+
"""
|
|
1484
|
+
LOOKUP = _LOOKUPS["tillage"]
|
|
1485
|
+
target_lookup_values = IPCC_MANAGEMENT_CATEGORY_TO_TILLAGE_MANAGEMENT_LOOKUP_VALUE.get(key, None)
|
|
1486
|
+
return cumulative_nodes_lookup_match(
|
|
1487
|
+
tillage_nodes,
|
|
1488
|
+
lookup=LOOKUP,
|
|
1489
|
+
target_lookup_values=target_lookup_values,
|
|
1490
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
1491
|
+
)
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
_TILLAGE_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE = {
|
|
1495
|
+
IpccManagementCategory.FULL_TILLAGE: _check_tillage_ipcc_management_category,
|
|
1496
|
+
IpccManagementCategory.REDUCED_TILLAGE: _check_tillage_ipcc_management_category,
|
|
1497
|
+
IpccManagementCategory.NO_TILLAGE: _check_tillage_ipcc_management_category
|
|
1498
|
+
}
|
|
1499
|
+
"""
|
|
1500
|
+
Decision tree mapping IPCC management categories to corresponding check functions for tillage.
|
|
1501
|
+
|
|
1502
|
+
Key: IpccManagementCategory
|
|
1503
|
+
Value: Corresponding function for checking the match of the given management category based on tillage nodes.
|
|
1504
|
+
"""
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
_IPCC_LAND_USE_CATEGORY_TO_DECISION_TREE = {
|
|
1508
|
+
IpccLandUseCategory.GRASSLAND: _GRASSLAND_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE,
|
|
1509
|
+
IpccLandUseCategory.ANNUAL_CROPS_WET: _TILLAGE_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE,
|
|
1510
|
+
IpccLandUseCategory.ANNUAL_CROPS: _TILLAGE_IPCC_MANAGEMENT_CATEGORY_DECISION_TREE
|
|
1511
|
+
}
|
|
1512
|
+
"""
|
|
1513
|
+
Decision tree mapping IPCC land use categories to corresponding decision trees for management categories.
|
|
1514
|
+
|
|
1515
|
+
Key: IpccLandUseCategory
|
|
1516
|
+
Value: Corresponding decision tree for IPCC management categories based on land use categories.
|
|
1517
|
+
"""
|
|
1518
|
+
|
|
1519
|
+
_IPCC_LAND_USE_CATEGORY_TO_DEFAULT_IPCC_MANAGEMENT_CATEGORY = {
|
|
1520
|
+
IpccLandUseCategory.GRASSLAND: IpccManagementCategory.NOMINALLY_MANAGED,
|
|
1521
|
+
IpccLandUseCategory.ANNUAL_CROPS_WET: IpccManagementCategory.FULL_TILLAGE,
|
|
1522
|
+
IpccLandUseCategory.ANNUAL_CROPS: IpccManagementCategory.FULL_TILLAGE
|
|
1523
|
+
}
|
|
1524
|
+
"""
|
|
1525
|
+
Mapping of default IPCC management categories for each IPCC land use category.
|
|
1526
|
+
|
|
1527
|
+
Key: IpccLandUseCategory
|
|
1528
|
+
Value: Default IPCC management category for the given land use category.
|
|
1529
|
+
"""
|
|
1530
|
+
|
|
1531
|
+
|
|
1532
|
+
def _assign_ipcc_carbon_input_category(
|
|
1533
|
+
management_nodes: list[dict],
|
|
1534
|
+
ipcc_management_category: IpccManagementCategory
|
|
1535
|
+
) -> IpccCarbonInputCategory:
|
|
1536
|
+
"""
|
|
1537
|
+
Assigns an IPCC Carbon Input Category based on the provided management nodes and IPCC Management Category.
|
|
1538
|
+
|
|
1539
|
+
Parameters
|
|
1540
|
+
----------
|
|
1541
|
+
management_nodes : list[dict]
|
|
1542
|
+
List of management nodes containing information about land management practices.
|
|
1543
|
+
ipcc_management_category : IpccManagementCategory
|
|
1544
|
+
IPCC Management Category for which the Carbon Input Category needs to be assigned.
|
|
1545
|
+
|
|
1546
|
+
Returns
|
|
1547
|
+
-------
|
|
1548
|
+
IpccCarbonInputCategory
|
|
1549
|
+
Assigned IPCC Carbon Input Category.
|
|
1550
|
+
"""
|
|
1551
|
+
decision_tree = _DECISION_TREE_FROM_IPCC_MANAGEMENT_CATEGORY.get(ipcc_management_category, {})
|
|
1552
|
+
default = _DEFAULT_CARBON_INPUT_CATEGORY.get(ipcc_management_category, IpccCarbonInputCategory.OTHER)
|
|
1553
|
+
|
|
1554
|
+
should_run_ = len(management_nodes) > 0
|
|
1555
|
+
|
|
1556
|
+
return next(
|
|
1557
|
+
(key for key in decision_tree if decision_tree[key](
|
|
1558
|
+
key=key,
|
|
1559
|
+
**_get_carbon_input_kwargs(management_nodes)
|
|
1560
|
+
)),
|
|
1561
|
+
default
|
|
1562
|
+
) if should_run_ else default
|
|
1563
|
+
|
|
1564
|
+
|
|
1565
|
+
def _check_grassland_ipcc_carbon_input_category(
|
|
1566
|
+
*, key: IpccCarbonInputCategory, num_grassland_improvements: int, **_,
|
|
1567
|
+
) -> bool:
|
|
1568
|
+
"""
|
|
1569
|
+
Checks if the given carbon input arguments satisfy the conditions for a specific
|
|
1570
|
+
Grassland IPCC Carbon Input Category.
|
|
1571
|
+
|
|
1572
|
+
Parameters
|
|
1573
|
+
----------
|
|
1574
|
+
key : IpccCarbonInputCategory
|
|
1575
|
+
The grassland IPCC Carbon Input Category to check.
|
|
1576
|
+
num_grassland_improvements : int
|
|
1577
|
+
The number of grassland improvements.
|
|
1578
|
+
|
|
1579
|
+
Returns
|
|
1580
|
+
-------
|
|
1581
|
+
bool
|
|
1582
|
+
`True` if the conditions for the specified category are met; otherwise, `False`.
|
|
1583
|
+
"""
|
|
1584
|
+
return num_grassland_improvements >= _GRASSLAND_IPCC_CARBON_INPUT_CATEGORY_TO_MIN_NUM_IMPROVEMENTS[key]
|
|
1585
|
+
|
|
1586
|
+
|
|
1587
|
+
_GRASSLAND_IPCC_CARBON_INPUT_CATEGORY_TO_MIN_NUM_IMPROVEMENTS = {
|
|
1588
|
+
IpccCarbonInputCategory.GRASSLAND_HIGH: 2,
|
|
1589
|
+
IpccCarbonInputCategory.GRASSLAND_MEDIUM: 1
|
|
1590
|
+
}
|
|
1591
|
+
"""
|
|
1592
|
+
A mapping from IPCC Grassland Carbon Input Categories to the minimum number of improvements required.
|
|
1593
|
+
|
|
1594
|
+
Key: IpccCarbonInputCategory
|
|
1595
|
+
Value: Minimum number of improvements required for the corresponding Grassland Carbon Input Category.
|
|
1596
|
+
"""
|
|
1597
|
+
|
|
1598
|
+
|
|
1599
|
+
_GRASSLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE = {
|
|
1600
|
+
IpccCarbonInputCategory.GRASSLAND_HIGH: _check_grassland_ipcc_carbon_input_category,
|
|
1601
|
+
IpccCarbonInputCategory.GRASSLAND_MEDIUM: _check_grassland_ipcc_carbon_input_category
|
|
1602
|
+
}
|
|
1603
|
+
"""
|
|
1604
|
+
A decision tree for assigning IPCC Carbon Input Categories to Grassland based on the number of improvements.
|
|
1605
|
+
|
|
1606
|
+
Key: IpccCarbonInputCategory
|
|
1607
|
+
Value: Corresponding function to check if the given conditions are met for the category.
|
|
1608
|
+
"""
|
|
1609
|
+
|
|
1610
|
+
|
|
1611
|
+
def _check_cropland_high_with_manure_category(
|
|
1612
|
+
*,
|
|
1613
|
+
has_animal_manure_used: bool,
|
|
1614
|
+
has_bare_fallow: bool,
|
|
1615
|
+
has_low_residue_producing_crops: bool,
|
|
1616
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used: bool,
|
|
1617
|
+
has_residue_removed_or_burnt: bool,
|
|
1618
|
+
**_
|
|
1619
|
+
) -> Union[int, None]:
|
|
1620
|
+
"""
|
|
1621
|
+
Checks the Cropland High with Manure IPCC Carbon Input Category based on the given carbon input arguments.
|
|
1622
|
+
|
|
1623
|
+
Parameters
|
|
1624
|
+
----------
|
|
1625
|
+
has_animal_manure_used : bool
|
|
1626
|
+
Indicates whether animal manure is used on more than 30% of the site.
|
|
1627
|
+
has_bare_fallow : bool
|
|
1628
|
+
Indicates whether bare fallow is present on more than 30% of the site.
|
|
1629
|
+
has_low_residue_producing_crops : bool
|
|
1630
|
+
Indicates whether low residue-producing crops are present on more than 70% of the site.
|
|
1631
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used : bool
|
|
1632
|
+
Indicates whether a nitrogen-fixing crop or inorganic nitrogen fertiliser is used on more than 30% of the site.
|
|
1633
|
+
has_residue_removed_or_burnt : bool
|
|
1634
|
+
Indicates whether residues are removed or burnt on more than 30% of the site.
|
|
1635
|
+
|
|
1636
|
+
Returns
|
|
1637
|
+
-------
|
|
1638
|
+
int | none
|
|
1639
|
+
The category key if conditions are met; otherwise, `None`.
|
|
1640
|
+
"""
|
|
1641
|
+
conditions = {
|
|
1642
|
+
1: all([
|
|
1643
|
+
not has_residue_removed_or_burnt,
|
|
1644
|
+
not has_low_residue_producing_crops,
|
|
1645
|
+
not has_bare_fallow,
|
|
1646
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
1647
|
+
has_animal_manure_used
|
|
1648
|
+
])
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
return next(
|
|
1652
|
+
(key for key, condition in conditions.items() if condition), None
|
|
1653
|
+
)
|
|
1654
|
+
|
|
1655
|
+
|
|
1656
|
+
def _check_cropland_high_without_manure_category(
|
|
1657
|
+
*,
|
|
1658
|
+
has_animal_manure_used: bool,
|
|
1659
|
+
has_bare_fallow: bool,
|
|
1660
|
+
has_cover_crop: bool,
|
|
1661
|
+
has_irrigation: bool,
|
|
1662
|
+
has_low_residue_producing_crops: bool,
|
|
1663
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used: bool,
|
|
1664
|
+
has_organic_fertiliser_or_soil_amendment_used: bool,
|
|
1665
|
+
has_practice_increasing_c_input: bool,
|
|
1666
|
+
has_residue_removed_or_burnt: bool,
|
|
1667
|
+
**_
|
|
1668
|
+
) -> Union[int, None]:
|
|
1669
|
+
"""
|
|
1670
|
+
Checks the Cropland High without Manure IPCC Carbon Input Category based on the given carbon input arguments.
|
|
1671
|
+
|
|
1672
|
+
Parameters
|
|
1673
|
+
----------
|
|
1674
|
+
has_animal_manure_used : bool
|
|
1675
|
+
Indicates whether animal manure is used on more than 30% of the site.
|
|
1676
|
+
has_bare_fallow : bool
|
|
1677
|
+
Indicates whether bare fallow is present on more than 30% of the site.
|
|
1678
|
+
has_cover_crop : bool
|
|
1679
|
+
Indicates whether cover crops are present on more than 30% of the site.
|
|
1680
|
+
has_irrigation : bool
|
|
1681
|
+
Indicates whether irrigation is applied to more than 30% of the site.
|
|
1682
|
+
has_low_residue_producing_crops : bool
|
|
1683
|
+
Indicates whether low residue-producing crops are present on more than 70% of the site.
|
|
1684
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used : bool
|
|
1685
|
+
Indicates whether a nitrogen-fixing crop or inorganic nitrogen fertiliser is used on more than 30% of the site.
|
|
1686
|
+
has_organic_fertiliser_or_soil_amendment_used : bool
|
|
1687
|
+
Indicates whether organic fertiliser or soil amendments are used on more than 30% of the site.
|
|
1688
|
+
has_practice_increasing_c_input : bool
|
|
1689
|
+
Indicates whether practices increasing carbon input are present on more than 30% of the site.
|
|
1690
|
+
has_residue_removed_or_burnt : bool
|
|
1691
|
+
Indicates whether residues are removed or burnt on more than 30% of the site.
|
|
1692
|
+
|
|
1693
|
+
Returns
|
|
1694
|
+
-------
|
|
1695
|
+
int | None
|
|
1696
|
+
The category key if conditions are met; otherwise, `None`.
|
|
1697
|
+
"""
|
|
1698
|
+
conditions = {
|
|
1699
|
+
1: all([
|
|
1700
|
+
not has_residue_removed_or_burnt,
|
|
1701
|
+
not has_low_residue_producing_crops,
|
|
1702
|
+
not has_bare_fallow,
|
|
1703
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
1704
|
+
any([
|
|
1705
|
+
has_irrigation,
|
|
1706
|
+
has_practice_increasing_c_input,
|
|
1707
|
+
has_cover_crop,
|
|
1708
|
+
has_organic_fertiliser_or_soil_amendment_used
|
|
1709
|
+
]),
|
|
1710
|
+
not has_animal_manure_used
|
|
1711
|
+
])
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
return next(
|
|
1715
|
+
(key for key, condition in conditions.items() if condition), None
|
|
1716
|
+
)
|
|
1717
|
+
|
|
1718
|
+
|
|
1719
|
+
def _check_cropland_medium_category(
|
|
1720
|
+
*,
|
|
1721
|
+
has_animal_manure_used: bool,
|
|
1722
|
+
has_bare_fallow: bool,
|
|
1723
|
+
has_cover_crop: bool,
|
|
1724
|
+
has_irrigation: bool,
|
|
1725
|
+
has_low_residue_producing_crops: bool,
|
|
1726
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used: bool,
|
|
1727
|
+
has_organic_fertiliser_or_soil_amendment_used: bool,
|
|
1728
|
+
has_practice_increasing_c_input: bool,
|
|
1729
|
+
has_residue_removed_or_burnt: bool,
|
|
1730
|
+
**_
|
|
1731
|
+
) -> Union[int, None]:
|
|
1732
|
+
"""
|
|
1733
|
+
Checks the Cropland Medium IPCC Carbon Input Category based on the given carbon input arguments.
|
|
1734
|
+
|
|
1735
|
+
Parameters
|
|
1736
|
+
----------
|
|
1737
|
+
has_animal_manure_used : bool
|
|
1738
|
+
Indicates whether animal manure is used on more than 30% of the site.
|
|
1739
|
+
has_bare_fallow : bool
|
|
1740
|
+
Indicates whether bare fallow is present on more than 30% of the site.
|
|
1741
|
+
has_cover_crop : bool
|
|
1742
|
+
Indicates whether cover crops are present on more than 30% of the site.
|
|
1743
|
+
has_irrigation : bool
|
|
1744
|
+
Indicates whether irrigation is applied to more than 30% of the site.
|
|
1745
|
+
has_low_residue_producing_crops : bool
|
|
1746
|
+
Indicates whether low residue-producing crops are present on more than 70% of the site.
|
|
1747
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used : bool
|
|
1748
|
+
Indicates whether a nitrogen-fixing crop or inorganic nitrogen fertiliser is used on more than 30% of the site.
|
|
1749
|
+
has_organic_fertiliser_or_soil_amendment_used : bool
|
|
1750
|
+
Indicates whether organic fertiliser or soil amendments are used on more than 30% of the site.
|
|
1751
|
+
has_practice_increasing_c_input : bool
|
|
1752
|
+
Indicates whether practices increasing carbon input are present on more than 30% of the site.
|
|
1753
|
+
has_residue_removed_or_burnt : bool
|
|
1754
|
+
Indicates whether residues are removed or burnt on more than 30% of the site.
|
|
1755
|
+
|
|
1756
|
+
Returns
|
|
1757
|
+
-------
|
|
1758
|
+
int | None
|
|
1759
|
+
The category key if conditions are met; otherwise, `None`.
|
|
1760
|
+
"""
|
|
1761
|
+
conditions = {
|
|
1762
|
+
1: all([
|
|
1763
|
+
has_residue_removed_or_burnt,
|
|
1764
|
+
has_animal_manure_used
|
|
1765
|
+
]),
|
|
1766
|
+
2: all([
|
|
1767
|
+
not has_residue_removed_or_burnt,
|
|
1768
|
+
any([
|
|
1769
|
+
has_low_residue_producing_crops,
|
|
1770
|
+
has_bare_fallow
|
|
1771
|
+
]),
|
|
1772
|
+
any([
|
|
1773
|
+
has_irrigation,
|
|
1774
|
+
has_practice_increasing_c_input,
|
|
1775
|
+
has_cover_crop,
|
|
1776
|
+
has_organic_fertiliser_or_soil_amendment_used,
|
|
1777
|
+
])
|
|
1778
|
+
]),
|
|
1779
|
+
3: all([
|
|
1780
|
+
not has_residue_removed_or_burnt,
|
|
1781
|
+
not has_low_residue_producing_crops,
|
|
1782
|
+
not has_bare_fallow,
|
|
1783
|
+
not has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
1784
|
+
any([
|
|
1785
|
+
has_irrigation,
|
|
1786
|
+
has_practice_increasing_c_input,
|
|
1787
|
+
has_cover_crop,
|
|
1788
|
+
has_organic_fertiliser_or_soil_amendment_used
|
|
1789
|
+
])
|
|
1790
|
+
]),
|
|
1791
|
+
4: all([
|
|
1792
|
+
not has_residue_removed_or_burnt,
|
|
1793
|
+
not has_low_residue_producing_crops,
|
|
1794
|
+
not has_bare_fallow,
|
|
1795
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
1796
|
+
not has_irrigation,
|
|
1797
|
+
not has_organic_fertiliser_or_soil_amendment_used,
|
|
1798
|
+
not has_practice_increasing_c_input,
|
|
1799
|
+
not has_cover_crop
|
|
1800
|
+
])
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
return next(
|
|
1804
|
+
(key for key, condition in conditions.items() if condition), None
|
|
1805
|
+
)
|
|
1806
|
+
|
|
1807
|
+
|
|
1808
|
+
def _check_cropland_low_category(
|
|
1809
|
+
*,
|
|
1810
|
+
has_animal_manure_used: bool,
|
|
1811
|
+
has_bare_fallow: bool,
|
|
1812
|
+
has_cover_crop: bool,
|
|
1813
|
+
has_irrigation: bool,
|
|
1814
|
+
has_low_residue_producing_crops: bool,
|
|
1815
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used: bool,
|
|
1816
|
+
has_organic_fertiliser_or_soil_amendment_used: bool,
|
|
1817
|
+
has_practice_increasing_c_input: bool,
|
|
1818
|
+
has_residue_removed_or_burnt: bool,
|
|
1819
|
+
**_
|
|
1820
|
+
) -> Union[int, None]:
|
|
1821
|
+
"""
|
|
1822
|
+
Checks the Cropland Low IPCC Carbon Input Category based on the given carbon input arguments.
|
|
1823
|
+
|
|
1824
|
+
Parameters
|
|
1825
|
+
----------
|
|
1826
|
+
has_animal_manure_used : bool
|
|
1827
|
+
Indicates whether animal manure is used on more than 30% of the site.
|
|
1828
|
+
has_bare_fallow : bool
|
|
1829
|
+
Indicates whether bare fallow is present on more than 30% of the site.
|
|
1830
|
+
has_cover_crop : bool
|
|
1831
|
+
Indicates whether cover crops are present on more than 30% of the site.
|
|
1832
|
+
has_irrigation : bool
|
|
1833
|
+
Indicates whether irrigation is applied to more than 30% of the site.
|
|
1834
|
+
has_low_residue_producing_crops : bool
|
|
1835
|
+
Indicates whether low residue-producing crops are present on more than 70% of the site.
|
|
1836
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used : bool
|
|
1837
|
+
Indicates whether a nitrogen-fixing crop or inorganic nitrogen fertiliser is used on more than 30% of the site.
|
|
1838
|
+
has_organic_fertiliser_or_soil_amendment_used : bool
|
|
1839
|
+
Indicates whether organic fertiliser or soil amendments are used on more than 30% of the site.
|
|
1840
|
+
has_practice_increasing_c_input : bool
|
|
1841
|
+
Indicates whether practices increasing carbon input are present on more than 30% of the site.
|
|
1842
|
+
has_residue_removed_or_burnt : bool
|
|
1843
|
+
Indicates whether residues are removed or burnt on more than 30% of the site.
|
|
1844
|
+
|
|
1845
|
+
Returns
|
|
1846
|
+
-------
|
|
1847
|
+
int | None
|
|
1848
|
+
The category key if conditions are met; otherwise, `None`.
|
|
1849
|
+
"""
|
|
1850
|
+
conditions = {
|
|
1851
|
+
1: all([
|
|
1852
|
+
has_residue_removed_or_burnt,
|
|
1853
|
+
not has_animal_manure_used
|
|
1854
|
+
]),
|
|
1855
|
+
2: all([
|
|
1856
|
+
not has_residue_removed_or_burnt,
|
|
1857
|
+
any([
|
|
1858
|
+
has_low_residue_producing_crops,
|
|
1859
|
+
has_bare_fallow
|
|
1860
|
+
]),
|
|
1861
|
+
not has_irrigation,
|
|
1862
|
+
not has_practice_increasing_c_input,
|
|
1863
|
+
not has_cover_crop,
|
|
1864
|
+
not has_organic_fertiliser_or_soil_amendment_used
|
|
1865
|
+
]),
|
|
1866
|
+
3: all([
|
|
1867
|
+
not has_residue_removed_or_burnt,
|
|
1868
|
+
not has_low_residue_producing_crops,
|
|
1869
|
+
not has_bare_fallow,
|
|
1870
|
+
not has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
1871
|
+
not has_irrigation,
|
|
1872
|
+
not has_organic_fertiliser_or_soil_amendment_used,
|
|
1873
|
+
not has_practice_increasing_c_input,
|
|
1874
|
+
not has_cover_crop
|
|
1875
|
+
])
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
return next(
|
|
1879
|
+
(key for key, condition in conditions.items() if condition), None
|
|
1880
|
+
)
|
|
1881
|
+
|
|
1882
|
+
|
|
1883
|
+
_CROPLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE = {
|
|
1884
|
+
IpccCarbonInputCategory.CROPLAND_HIGH_WITH_MANURE: _check_cropland_high_with_manure_category,
|
|
1885
|
+
IpccCarbonInputCategory.CROPLAND_HIGH_WITHOUT_MANURE: _check_cropland_high_without_manure_category,
|
|
1886
|
+
IpccCarbonInputCategory.CROPLAND_MEDIUM: _check_cropland_medium_category,
|
|
1887
|
+
IpccCarbonInputCategory.CROPLAND_LOW: _check_cropland_low_category
|
|
1888
|
+
}
|
|
1889
|
+
"""
|
|
1890
|
+
A decision tree for assigning IPCC Carbon Input Categories to Cropland based on specific conditions.
|
|
1891
|
+
|
|
1892
|
+
Key: IpccCarbonInputCategory
|
|
1893
|
+
Value: Corresponding function to check if the given conditions are met for the category.
|
|
1894
|
+
"""
|
|
1895
|
+
|
|
1896
|
+
_DECISION_TREE_FROM_IPCC_MANAGEMENT_CATEGORY = {
|
|
1897
|
+
IpccManagementCategory.IMPROVED_GRASSLAND: _GRASSLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE,
|
|
1898
|
+
IpccManagementCategory.FULL_TILLAGE: _CROPLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE,
|
|
1899
|
+
IpccManagementCategory.REDUCED_TILLAGE: _CROPLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE,
|
|
1900
|
+
IpccManagementCategory.NO_TILLAGE: _CROPLAND_IPCC_CARBON_INPUT_CATEGORY_DECISION_TREE
|
|
1901
|
+
}
|
|
1902
|
+
"""
|
|
1903
|
+
A decision tree mapping IPCC Management Categories to respective Carbon Input Category decision trees.
|
|
1904
|
+
|
|
1905
|
+
Key: IpccManagementCategory
|
|
1906
|
+
Value: Decision tree for Carbon Input Categories corresponding to the management category.
|
|
1907
|
+
"""
|
|
1908
|
+
|
|
1909
|
+
_DEFAULT_CARBON_INPUT_CATEGORY = {
|
|
1910
|
+
IpccManagementCategory.IMPROVED_GRASSLAND: IpccCarbonInputCategory.GRASSLAND_MEDIUM,
|
|
1911
|
+
IpccManagementCategory.FULL_TILLAGE: IpccCarbonInputCategory.CROPLAND_LOW,
|
|
1912
|
+
IpccManagementCategory.REDUCED_TILLAGE: IpccCarbonInputCategory.CROPLAND_LOW,
|
|
1913
|
+
IpccManagementCategory.NO_TILLAGE: IpccCarbonInputCategory.CROPLAND_LOW
|
|
1914
|
+
}
|
|
1915
|
+
"""
|
|
1916
|
+
A mapping from IPCC Management Categories to default Carbon Input Categories.
|
|
1917
|
+
|
|
1918
|
+
Key: IpccManagementCategory
|
|
1919
|
+
Value: Default Carbon Input Category for the corresponding Management Category.
|
|
1920
|
+
"""
|
|
1921
|
+
|
|
1922
|
+
|
|
1923
|
+
def _get_carbon_input_kwargs(
|
|
1924
|
+
management_nodes: list[dict]
|
|
1925
|
+
) -> dict:
|
|
1926
|
+
"""
|
|
1927
|
+
Creates CarbonInputArgs based on the provided list of management nodes.
|
|
1928
|
+
|
|
1929
|
+
Parameters
|
|
1930
|
+
----------
|
|
1931
|
+
management_nodes : list[dict]
|
|
1932
|
+
The list of management nodes.
|
|
1933
|
+
|
|
1934
|
+
Returns
|
|
1935
|
+
-------
|
|
1936
|
+
dict
|
|
1937
|
+
The carbon input keyword arguments.
|
|
1938
|
+
"""
|
|
1939
|
+
|
|
1940
|
+
PRACTICE_INCREASING_C_INPUT_LOOKUP = _LOOKUPS["landUseManagement"]
|
|
1941
|
+
LOW_RESIDUE_PRODUCING_CROP_LOOKUP = _LOOKUPS["landCover"][1]
|
|
1942
|
+
N_FIXING_CROP_LOOKUP = _LOOKUPS["landCover"][2]
|
|
1943
|
+
|
|
1944
|
+
# To prevent double counting already explicitly checked practices.
|
|
1945
|
+
EXCLUDED_PRACTICE_TERM_IDS = {
|
|
1946
|
+
_IMPROVED_PASTURE_TERM_ID,
|
|
1947
|
+
_ANIMAL_MANURE_USED_TERM_ID,
|
|
1948
|
+
_INORGANIC_NITROGEN_FERTILISER_USED_TERM_ID,
|
|
1949
|
+
_ORGANIC_FERTILISER_USED_TERM_ID
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
crop_residue_management_nodes = filter_list_term_type(management_nodes, [TermTermType.CROPRESIDUEMANAGEMENT])
|
|
1953
|
+
land_cover_nodes = filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])
|
|
1954
|
+
land_use_management_nodes = filter_list_term_type(management_nodes, [TermTermType.LANDUSEMANAGEMENT])
|
|
1955
|
+
water_regime_nodes = filter_list_term_type(management_nodes, [TermTermType.WATERREGIME])
|
|
1956
|
+
|
|
1957
|
+
has_animal_manure_used = any(
|
|
1958
|
+
get_node_value(node) for node in land_use_management_nodes if node_term_match(node, _ANIMAL_MANURE_USED_TERM_ID)
|
|
1959
|
+
)
|
|
1960
|
+
|
|
1961
|
+
has_bare_fallow = cumulative_nodes_term_match(
|
|
1962
|
+
land_cover_nodes,
|
|
1963
|
+
target_term_ids=_SHORT_BARE_FALLOW_TERM_ID,
|
|
1964
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
1965
|
+
)
|
|
1966
|
+
|
|
1967
|
+
has_cover_crop = cumulative_nodes_match(
|
|
1968
|
+
lambda node: any(
|
|
1969
|
+
get_node_property(node, term_id, False).get("value", False)
|
|
1970
|
+
for term_id in get_cover_crop_property_terms_with_cache()
|
|
1971
|
+
),
|
|
1972
|
+
land_cover_nodes,
|
|
1973
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
1974
|
+
)
|
|
1975
|
+
|
|
1976
|
+
has_inorganic_n_fertiliser_used = any(
|
|
1977
|
+
get_node_value(node) for node in land_use_management_nodes
|
|
1978
|
+
if node_term_match(node, _INORGANIC_NITROGEN_FERTILISER_USED_TERM_ID)
|
|
1979
|
+
)
|
|
1980
|
+
|
|
1981
|
+
has_irrigation = check_irrigation(water_regime_nodes)
|
|
1982
|
+
|
|
1983
|
+
has_low_residue_producing_crops = cumulative_nodes_lookup_match(
|
|
1984
|
+
land_cover_nodes,
|
|
1985
|
+
lookup=LOW_RESIDUE_PRODUCING_CROP_LOOKUP,
|
|
1986
|
+
target_lookup_values=True,
|
|
1987
|
+
cumulative_threshold=SUPER_MAJORITY_AREA_THRESHOLD # Requires a supermajority (>70%).
|
|
1988
|
+
)
|
|
1989
|
+
|
|
1990
|
+
has_n_fixing_crop = cumulative_nodes_lookup_match(
|
|
1991
|
+
land_cover_nodes,
|
|
1992
|
+
lookup=N_FIXING_CROP_LOOKUP,
|
|
1993
|
+
target_lookup_values=True,
|
|
1994
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
1995
|
+
)
|
|
1996
|
+
|
|
1997
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used = has_n_fixing_crop or has_inorganic_n_fertiliser_used
|
|
1998
|
+
|
|
1999
|
+
has_organic_fertiliser_or_soil_amendment_used = any(
|
|
2000
|
+
get_node_value(node) for node in land_use_management_nodes
|
|
2001
|
+
if node_term_match(node, [_ORGANIC_FERTILISER_USED_TERM_ID, _SOIL_AMENDMENT_USED_TERM_ID])
|
|
2002
|
+
)
|
|
2003
|
+
|
|
2004
|
+
has_practice_increasing_c_input = cumulative_nodes_match(
|
|
2005
|
+
lambda node: (
|
|
2006
|
+
node_lookup_match(node, PRACTICE_INCREASING_C_INPUT_LOOKUP, True)
|
|
2007
|
+
and not node_term_match(node, EXCLUDED_PRACTICE_TERM_IDS)
|
|
2008
|
+
),
|
|
2009
|
+
land_use_management_nodes,
|
|
2010
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
2011
|
+
)
|
|
2012
|
+
|
|
2013
|
+
has_residue_removed_or_burnt = cumulative_nodes_term_match(
|
|
2014
|
+
crop_residue_management_nodes,
|
|
2015
|
+
target_term_ids=get_residue_removed_or_burnt_terms_with_cache(),
|
|
2016
|
+
cumulative_threshold=MIN_AREA_THRESHOLD
|
|
2017
|
+
)
|
|
2018
|
+
|
|
2019
|
+
num_grassland_improvements = [
|
|
2020
|
+
has_irrigation,
|
|
2021
|
+
has_practice_increasing_c_input,
|
|
2022
|
+
has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
2023
|
+
has_organic_fertiliser_or_soil_amendment_used
|
|
2024
|
+
].count(True)
|
|
2025
|
+
|
|
2026
|
+
return {
|
|
2027
|
+
"has_animal_manure_used": has_animal_manure_used,
|
|
2028
|
+
"has_bare_fallow": has_bare_fallow,
|
|
2029
|
+
"has_cover_crop": has_cover_crop,
|
|
2030
|
+
"has_irrigation": has_irrigation,
|
|
2031
|
+
"has_low_residue_producing_crops": has_low_residue_producing_crops,
|
|
2032
|
+
"has_n_fixing_crop_or_inorganic_n_fertiliser_used": has_n_fixing_crop_or_inorganic_n_fertiliser_used,
|
|
2033
|
+
"has_organic_fertiliser_or_soil_amendment_used": has_organic_fertiliser_or_soil_amendment_used,
|
|
2034
|
+
"has_practice_increasing_c_input": has_practice_increasing_c_input,
|
|
2035
|
+
"has_residue_removed_or_burnt": has_residue_removed_or_burnt,
|
|
2036
|
+
"num_grassland_improvements": num_grassland_improvements
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
|
|
2040
|
+
def _should_run_inventory_year(group: dict) -> bool:
|
|
2041
|
+
"""
|
|
2042
|
+
Determines whether there is sufficient data in an inventory year to run the tier 1 model.
|
|
2043
|
+
|
|
2044
|
+
1. Check if the land use category is not "OTHER"
|
|
2045
|
+
2. Check if all required keys are present.
|
|
2046
|
+
|
|
2047
|
+
Parameters
|
|
2048
|
+
----------
|
|
2049
|
+
group : dict
|
|
2050
|
+
Dictionary containing information for a specific inventory year.
|
|
2051
|
+
|
|
2052
|
+
Returns
|
|
2053
|
+
-------
|
|
2054
|
+
bool
|
|
2055
|
+
True if the inventory year is valid, False otherwise.
|
|
2056
|
+
"""
|
|
2057
|
+
return all([
|
|
2058
|
+
group.get(_InventoryKey.LU_CATEGORY) != IpccLandUseCategory.OTHER,
|
|
2059
|
+
all(key in group.keys() for key in _REQUIRED_KEYS),
|
|
2060
|
+
])
|