hestia-earth-models 0.61.7__py3-none-any.whl → 0.61.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hestia-earth-models might be problematic. Click here for more details.
- hestia_earth/models/cycle/completeness/electricityFuel.py +56 -0
- hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +44 -59
- hestia_earth/models/geospatialDatabase/histosol.py +4 -0
- hestia_earth/models/ipcc2006/co2ToAirOrganicSoilCultivation.py +4 -2
- hestia_earth/models/ipcc2006/n2OToAirOrganicSoilCultivationDirect.py +1 -1
- hestia_earth/models/ipcc2019/aboveGroundCropResidueTotal.py +1 -1
- hestia_earth/models/ipcc2019/belowGroundCropResidue.py +1 -1
- hestia_earth/models/ipcc2019/ch4ToAirExcreta.py +1 -1
- hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +511 -458
- hestia_earth/models/ipcc2019/co2ToAirUreaHydrolysis.py +5 -1
- hestia_earth/models/ipcc2019/organicCarbonPerHa.py +117 -3881
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +2060 -0
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +1630 -0
- hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +324 -0
- hestia_earth/models/mocking/search-results.json +252 -252
- hestia_earth/models/site/organicCarbonPerHa.py +58 -44
- hestia_earth/models/site/soilMeasurement.py +18 -13
- hestia_earth/models/utils/__init__.py +28 -0
- hestia_earth/models/utils/array_builders.py +578 -0
- hestia_earth/models/utils/blank_node.py +2 -3
- hestia_earth/models/utils/descriptive_stats.py +285 -0
- hestia_earth/models/utils/emission.py +73 -2
- hestia_earth/models/utils/inorganicFertiliser.py +2 -2
- hestia_earth/models/utils/measurement.py +118 -4
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.61.8.dist-info}/METADATA +1 -1
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.61.8.dist-info}/RECORD +43 -31
- tests/models/cycle/completeness/test_electricityFuel.py +21 -0
- tests/models/emepEea2019/test_nh3ToAirInorganicFertiliser.py +2 -2
- tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +54 -165
- tests/models/ipcc2019/test_organicCarbonPerHa.py +219 -460
- tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +471 -0
- tests/models/ipcc2019/test_organicCarbonPerHa_tier_2_utils.py +208 -0
- tests/models/ipcc2019/test_organicCarbonPerHa_utils.py +75 -0
- tests/models/site/test_organicCarbonPerHa.py +3 -12
- tests/models/site/test_soilMeasurement.py +3 -18
- tests/models/utils/test_array_builders.py +253 -0
- tests/models/utils/test_descriptive_stats.py +134 -0
- tests/models/utils/test_emission.py +51 -1
- tests/models/utils/test_measurement.py +54 -2
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.61.8.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.61.8.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.61.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
from itertools import product
|
|
2
|
+
import json
|
|
3
|
+
from numpy import array
|
|
4
|
+
from numpy.testing import assert_array_almost_equal
|
|
5
|
+
from pytest import mark
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
|
|
8
|
+
from hestia_earth.models.ipcc2019.organicCarbonPerHa import MODEL, TERM_ID
|
|
9
|
+
from hestia_earth.models.ipcc2019.organicCarbonPerHa_utils import (
|
|
10
|
+
EcoClimateZone, IpccCarbonInputCategory, IpccLandUseCategory, IpccManagementCategory,
|
|
11
|
+
IpccSoilCategory, sample_constant, sample_plus_minus_error, sample_plus_minus_uncertainty
|
|
12
|
+
)
|
|
13
|
+
from hestia_earth.models.ipcc2019.organicCarbonPerHa_tier_1_utils import (
|
|
14
|
+
_assign_ipcc_carbon_input_category, _assign_ipcc_land_use_category, _assign_ipcc_management_category,
|
|
15
|
+
_assign_ipcc_soil_category, _calc_missing_equilibrium_years, _calc_regime_start_years, _calc_soc_stocks,
|
|
16
|
+
_check_cropland_low_category, _check_cropland_medium_category, _get_carbon_input_kwargs, _get_sample_func,
|
|
17
|
+
_get_soc_ref_preview, _InventoryKey, _sample_parameter, _EXCLUDED_ECO_CLIMATE_ZONES
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from tests.utils import fixtures_path
|
|
21
|
+
|
|
22
|
+
class_path = f"hestia_earth.models.{MODEL}.{TERM_ID}_tier_1_utils"
|
|
23
|
+
utils_path = f"hestia_earth.models.{MODEL}.{TERM_ID}_utils"
|
|
24
|
+
term_path = "hestia_earth.models.utils.term"
|
|
25
|
+
property_path = "hestia_earth.models.utils.property"
|
|
26
|
+
|
|
27
|
+
fixtures_folder = f"{fixtures_path}/{MODEL}/{TERM_ID}"
|
|
28
|
+
|
|
29
|
+
ITERATIONS = 1000
|
|
30
|
+
|
|
31
|
+
COVER_CROP_PROPERTY_TERM_IDS = [
|
|
32
|
+
"catchCrop",
|
|
33
|
+
"coverCrop",
|
|
34
|
+
"groundCover",
|
|
35
|
+
"longFallowCrop",
|
|
36
|
+
"shortFallowCrop"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
IRRIGATED_TERM_IDS = [
|
|
40
|
+
"rainfedDeepWater",
|
|
41
|
+
"rainfedDeepWaterWaterDepth100Cm",
|
|
42
|
+
"rainfedDeepWaterWaterDepth50100Cm",
|
|
43
|
+
"irrigatedTypeUnspecified",
|
|
44
|
+
"irrigatedCenterPivotIrrigation",
|
|
45
|
+
"irrigatedContinuouslyFlooded",
|
|
46
|
+
"irrigatedDripIrrigation",
|
|
47
|
+
"irrigatedFurrowIrrigation",
|
|
48
|
+
"irrigatedLateralMoveIrrigation",
|
|
49
|
+
"irrigatedLocalizedIrrigation",
|
|
50
|
+
"irrigatedManualIrrigation",
|
|
51
|
+
"irrigatedSurfaceIrrigationMultipleDrainagePeriods",
|
|
52
|
+
"irrigatedSurfaceIrrigationSingleDrainagePeriod",
|
|
53
|
+
"irrigatedSprinklerIrrigation",
|
|
54
|
+
"irrigatedSubIrrigation",
|
|
55
|
+
"irrigatedSurfaceIrrigationDrainageRegimeUnspecified"
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
RESIDUE_REMOVED_OR_BURNT_TERM_IDS = [
|
|
59
|
+
"residueBurnt",
|
|
60
|
+
"residueRemoved"
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
UPLAND_RICE_LAND_COVER_TERM_IDS = [
|
|
64
|
+
"ricePlantUpland"
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# kwargs, sample_func, expected_shape
|
|
69
|
+
PARAMS_GET_SAMPLE_FUNC = [
|
|
70
|
+
({"value": 1}, sample_constant),
|
|
71
|
+
({"value": 1, "error": 10}, sample_plus_minus_error),
|
|
72
|
+
({"value": 1, "uncertainty": 10}, sample_plus_minus_uncertainty)
|
|
73
|
+
]
|
|
74
|
+
IDS_GET_SAMPLE_FUNC = ["constant", "+/- error", "+/- uncertainty"]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@mark.parametrize("kwargs, sample_func", PARAMS_GET_SAMPLE_FUNC, ids=IDS_GET_SAMPLE_FUNC)
|
|
78
|
+
def test_get_sample_func(kwargs, sample_func):
|
|
79
|
+
result = _get_sample_func(kwargs)
|
|
80
|
+
assert result == sample_func
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
SOC_REF_PARAMS = [p for p in product(IpccSoilCategory, EcoClimateZone) if _get_soc_ref_preview(*p)]
|
|
84
|
+
FACTOR_PARAMS = list(product(
|
|
85
|
+
[c for c in IpccLandUseCategory] + [c for c in IpccManagementCategory] + [c for c in IpccCarbonInputCategory],
|
|
86
|
+
[e for e in EcoClimateZone if e not in _EXCLUDED_ECO_CLIMATE_ZONES]
|
|
87
|
+
))
|
|
88
|
+
|
|
89
|
+
# ipcc_category, eco_climate_zone
|
|
90
|
+
PARAMS_SAMPLE_PARAMETER = SOC_REF_PARAMS + FACTOR_PARAMS
|
|
91
|
+
IDS_SAMPLE_PARAMETER = [f"{p[0]} + {p[1].name}" for p in PARAMS_SAMPLE_PARAMETER]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@mark.parametrize("ipcc_category, eco_climate_zone", PARAMS_SAMPLE_PARAMETER, ids=IDS_SAMPLE_PARAMETER)
|
|
95
|
+
def test_sample_parameter(ipcc_category, eco_climate_zone):
|
|
96
|
+
"""
|
|
97
|
+
Check that every combination of parameter and eco_climate_zone can be sampled without raising an error.
|
|
98
|
+
"""
|
|
99
|
+
EXPECTED_SHAPE = (1, ITERATIONS)
|
|
100
|
+
result = _sample_parameter(ITERATIONS, ipcc_category, eco_climate_zone)
|
|
101
|
+
assert result.shape == EXPECTED_SHAPE
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# subfolder, expected
|
|
105
|
+
SOIL_CATEGORY_PARAMS = [
|
|
106
|
+
("fractional", IpccSoilCategory.WETLAND_SOILS),
|
|
107
|
+
("no-measurements", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS),
|
|
108
|
+
("sandy-override", IpccSoilCategory.SANDY_SOILS),
|
|
109
|
+
("soilType/hac", IpccSoilCategory.HIGH_ACTIVITY_CLAY_SOILS),
|
|
110
|
+
("soilType/lac", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS),
|
|
111
|
+
("soilType/org", IpccSoilCategory.ORGANIC_SOILS),
|
|
112
|
+
("soilType/pod", IpccSoilCategory.SPODIC_SOILS),
|
|
113
|
+
("soilType/san", IpccSoilCategory.SANDY_SOILS),
|
|
114
|
+
("soilType/vol", IpccSoilCategory.VOLCANIC_SOILS),
|
|
115
|
+
("soilType/wet", IpccSoilCategory.WETLAND_SOILS),
|
|
116
|
+
("usdaSoilType/hac", IpccSoilCategory.HIGH_ACTIVITY_CLAY_SOILS),
|
|
117
|
+
("usdaSoilType/lac", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS),
|
|
118
|
+
("usdaSoilType/org", IpccSoilCategory.ORGANIC_SOILS),
|
|
119
|
+
("usdaSoilType/pod", IpccSoilCategory.SPODIC_SOILS),
|
|
120
|
+
("usdaSoilType/san", IpccSoilCategory.SANDY_SOILS),
|
|
121
|
+
("usdaSoilType/vol", IpccSoilCategory.VOLCANIC_SOILS),
|
|
122
|
+
("usdaSoilType/wet", IpccSoilCategory.WETLAND_SOILS)
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@mark.parametrize(
|
|
127
|
+
"subfolder, expected",
|
|
128
|
+
SOIL_CATEGORY_PARAMS,
|
|
129
|
+
ids=[params[0] for params in SOIL_CATEGORY_PARAMS]
|
|
130
|
+
)
|
|
131
|
+
def test_assign_ipcc_soil_category(subfolder: str, expected: IpccSoilCategory):
|
|
132
|
+
folder = f"{fixtures_folder}/IpccSoilCategory/{subfolder}"
|
|
133
|
+
|
|
134
|
+
with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
|
|
135
|
+
site = json.load(f)
|
|
136
|
+
|
|
137
|
+
result = _assign_ipcc_soil_category(site.get("measurements", []))
|
|
138
|
+
assert result == expected
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# subfolder, soil_category, expected
|
|
142
|
+
LAND_USE_CATEGORY_PARAMS = [
|
|
143
|
+
("annual-crops", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.ANNUAL_CROPS),
|
|
144
|
+
("annual-crops-wet", IpccSoilCategory.WETLAND_SOILS, IpccLandUseCategory.ANNUAL_CROPS_WET),
|
|
145
|
+
("forest", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.FOREST),
|
|
146
|
+
("fractional", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.PERENNIAL_CROPS),
|
|
147
|
+
("grassland", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.GRASSLAND),
|
|
148
|
+
("irrigated-upland-rice", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.PADDY_RICE_CULTIVATION),
|
|
149
|
+
("native", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.NATIVE),
|
|
150
|
+
("other", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.OTHER),
|
|
151
|
+
("paddy-rice-cultivation", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.PADDY_RICE_CULTIVATION),
|
|
152
|
+
("perennial-crops", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.PERENNIAL_CROPS),
|
|
153
|
+
("set-aside", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.SET_ASIDE),
|
|
154
|
+
("set-aside-override", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.SET_ASIDE),
|
|
155
|
+
("upland-rice", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.ANNUAL_CROPS),
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@mark.parametrize(
|
|
160
|
+
"subfolder, soil_category, expected",
|
|
161
|
+
LAND_USE_CATEGORY_PARAMS,
|
|
162
|
+
ids=[params[0] for params in LAND_USE_CATEGORY_PARAMS]
|
|
163
|
+
)
|
|
164
|
+
@patch(f"{term_path}.search")
|
|
165
|
+
@patch(f"{utils_path}.get_upland_rice_land_cover_terms", return_value=UPLAND_RICE_LAND_COVER_TERM_IDS)
|
|
166
|
+
@patch(f"{utils_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
|
|
167
|
+
def test_assign_ipcc_land_use_category(
|
|
168
|
+
mock_get_irrigated_terms: MagicMock,
|
|
169
|
+
mock_get_upland_rice_land_cover_terms: MagicMock,
|
|
170
|
+
mock_search: MagicMock,
|
|
171
|
+
subfolder: str,
|
|
172
|
+
soil_category: IpccSoilCategory,
|
|
173
|
+
expected: IpccLandUseCategory
|
|
174
|
+
):
|
|
175
|
+
folder = f"{fixtures_folder}/IpccLandUseCategory/{subfolder}"
|
|
176
|
+
|
|
177
|
+
with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
|
|
178
|
+
site = json.load(f)
|
|
179
|
+
|
|
180
|
+
result = _assign_ipcc_land_use_category(site.get("management", []), soil_category)
|
|
181
|
+
|
|
182
|
+
# Ensure that API calls to retrieve term IDs are properly cached.
|
|
183
|
+
mock_get_irrigated_terms.call_count <= 1
|
|
184
|
+
mock_get_upland_rice_land_cover_terms.call_count <= 1
|
|
185
|
+
|
|
186
|
+
mock_search.assert_not_called() # Ensure that the term utils are properly mocked.
|
|
187
|
+
assert result == expected
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# subfolder, land_use_category, expected
|
|
191
|
+
MANAGEMENT_CATEGORY_PARAMS = [
|
|
192
|
+
("fractional-annual-crops", IpccLandUseCategory.ANNUAL_CROPS, IpccManagementCategory.REDUCED_TILLAGE),
|
|
193
|
+
("fractional-annual-crops-wet", IpccLandUseCategory.ANNUAL_CROPS_WET, IpccManagementCategory.REDUCED_TILLAGE),
|
|
194
|
+
("fractional-grassland", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.IMPROVED_GRASSLAND),
|
|
195
|
+
("full-tillage", IpccLandUseCategory.ANNUAL_CROPS, IpccManagementCategory.FULL_TILLAGE),
|
|
196
|
+
("high-intensity-grazing", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.HIGH_INTENSITY_GRAZING),
|
|
197
|
+
("improved-grassland", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.IMPROVED_GRASSLAND),
|
|
198
|
+
("no-management/annual-crops", IpccLandUseCategory.ANNUAL_CROPS, IpccManagementCategory.FULL_TILLAGE),
|
|
199
|
+
("no-management/annual-crops-wet", IpccLandUseCategory.ANNUAL_CROPS_WET, IpccManagementCategory.FULL_TILLAGE),
|
|
200
|
+
("no-management/grassland", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.NOMINALLY_MANAGED),
|
|
201
|
+
("no-tillage", IpccLandUseCategory.ANNUAL_CROPS, IpccManagementCategory.NO_TILLAGE),
|
|
202
|
+
("nominally-managed", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.NOMINALLY_MANAGED),
|
|
203
|
+
("other", IpccLandUseCategory.OTHER, IpccManagementCategory.OTHER),
|
|
204
|
+
("reduced-tillage", IpccLandUseCategory.ANNUAL_CROPS, IpccManagementCategory.REDUCED_TILLAGE),
|
|
205
|
+
("severely-degraded", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.SEVERELY_DEGRADED),
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@mark.parametrize(
|
|
210
|
+
"subfolder, land_use_category, expected",
|
|
211
|
+
MANAGEMENT_CATEGORY_PARAMS,
|
|
212
|
+
ids=[params[0] for params in MANAGEMENT_CATEGORY_PARAMS]
|
|
213
|
+
)
|
|
214
|
+
def test_assign_ipcc_management_category(
|
|
215
|
+
subfolder: str, land_use_category: IpccLandUseCategory, expected: IpccManagementCategory
|
|
216
|
+
):
|
|
217
|
+
folder = f"{fixtures_folder}/IpccManagementCategory/{subfolder}"
|
|
218
|
+
|
|
219
|
+
with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
|
|
220
|
+
site = json.load(f)
|
|
221
|
+
|
|
222
|
+
result = _assign_ipcc_management_category(site.get("management", []), land_use_category)
|
|
223
|
+
assert result == expected
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@mark.parametrize("key", [1, 2, 3, 4], ids=lambda key: f"scenario-{key}")
|
|
227
|
+
@patch(f"{term_path}.search")
|
|
228
|
+
@patch(f"{utils_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
|
|
229
|
+
@patch(f"{utils_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
|
|
230
|
+
@patch(f"{utils_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
|
|
231
|
+
def test_check_cropland_medium_category(
|
|
232
|
+
mock_get_cover_crop_property_terms: MagicMock,
|
|
233
|
+
mock_get_irrigated_terms: MagicMock,
|
|
234
|
+
mock_get_residue_removed_or_burnt_terms: MagicMock,
|
|
235
|
+
mock_search: MagicMock,
|
|
236
|
+
key: int
|
|
237
|
+
):
|
|
238
|
+
"""
|
|
239
|
+
Tests each set of cropland medium conditions against a list of nodes that such satisfy it. The function returns the
|
|
240
|
+
key of the matching condition set, which should match the suffix of the fixtures subfolder.
|
|
241
|
+
"""
|
|
242
|
+
folder = f"{fixtures_folder}/IpccCarbonInputCategory/cropland-medium/scenario-{key}"
|
|
243
|
+
|
|
244
|
+
with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
|
|
245
|
+
site = json.load(f)
|
|
246
|
+
|
|
247
|
+
result = _check_cropland_medium_category(**_get_carbon_input_kwargs(site.get("management", [])))
|
|
248
|
+
|
|
249
|
+
# Ensure that API calls to retrieve term IDs are properly cached.
|
|
250
|
+
mock_get_cover_crop_property_terms.call_count <= 1
|
|
251
|
+
mock_get_irrigated_terms.call_count <= 1
|
|
252
|
+
mock_get_residue_removed_or_burnt_terms.call_count <= 1
|
|
253
|
+
|
|
254
|
+
mock_search.assert_not_called() # Ensure that the term utils are properly mocked.
|
|
255
|
+
assert result == key
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@mark.parametrize("key", [1, 2, 3], ids=lambda key: f"scenario-{key}")
|
|
259
|
+
@patch(f"{term_path}.search")
|
|
260
|
+
@patch(f"{utils_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
|
|
261
|
+
@patch(f"{utils_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
|
|
262
|
+
@patch(f"{utils_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
|
|
263
|
+
def test_check_cropland_low_category(
|
|
264
|
+
mock_get_cover_crop_property_terms: MagicMock,
|
|
265
|
+
mock_get_irrigated_terms: MagicMock,
|
|
266
|
+
mock_get_residue_removed_or_burnt_terms: MagicMock,
|
|
267
|
+
mock_search: MagicMock,
|
|
268
|
+
key: int
|
|
269
|
+
):
|
|
270
|
+
"""
|
|
271
|
+
Tests each set of cropland low conditions against a list of nodes that such satisfy it. The function returns the
|
|
272
|
+
key of the matching condition set, which should match the suffix of the fixtures subfolder.
|
|
273
|
+
"""
|
|
274
|
+
folder = f"{fixtures_folder}/IpccCarbonInputCategory/cropland-low/scenario-{key}"
|
|
275
|
+
|
|
276
|
+
with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
|
|
277
|
+
site = json.load(f)
|
|
278
|
+
|
|
279
|
+
result = _check_cropland_low_category(**_get_carbon_input_kwargs(site.get("management", [])))
|
|
280
|
+
|
|
281
|
+
# Ensure that API calls to retrieve term IDs are properly cached.
|
|
282
|
+
mock_get_cover_crop_property_terms.call_count <= 1
|
|
283
|
+
mock_get_irrigated_terms.call_count <= 1
|
|
284
|
+
mock_get_residue_removed_or_burnt_terms.call_count <= 1
|
|
285
|
+
|
|
286
|
+
mock_search.assert_not_called() # Ensure that the term utils are properly mocked.
|
|
287
|
+
assert result == key
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# subfolder, management_category, expected
|
|
291
|
+
CARBON_INPUT_CATEGORY_PARAMS = [
|
|
292
|
+
(
|
|
293
|
+
"cropland-high-with-manure",
|
|
294
|
+
IpccManagementCategory.FULL_TILLAGE,
|
|
295
|
+
IpccCarbonInputCategory.CROPLAND_HIGH_WITH_MANURE
|
|
296
|
+
),
|
|
297
|
+
(
|
|
298
|
+
"cropland-high-without-manure/organic-fertiliser", # Closes issue 743
|
|
299
|
+
IpccManagementCategory.FULL_TILLAGE,
|
|
300
|
+
IpccCarbonInputCategory.CROPLAND_HIGH_WITHOUT_MANURE
|
|
301
|
+
),
|
|
302
|
+
(
|
|
303
|
+
"cropland-high-without-manure/soil-amendment", # Closes issue 743
|
|
304
|
+
IpccManagementCategory.FULL_TILLAGE,
|
|
305
|
+
IpccCarbonInputCategory.CROPLAND_HIGH_WITHOUT_MANURE
|
|
306
|
+
),
|
|
307
|
+
("cropland-low/scenario-1", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_LOW),
|
|
308
|
+
("cropland-low/scenario-2", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_LOW),
|
|
309
|
+
("cropland-low/scenario-3", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_LOW),
|
|
310
|
+
("cropland-medium/scenario-1", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_MEDIUM),
|
|
311
|
+
("cropland-medium/scenario-2", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_MEDIUM),
|
|
312
|
+
("cropland-medium/scenario-3", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_MEDIUM),
|
|
313
|
+
("cropland-medium/scenario-4", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_MEDIUM),
|
|
314
|
+
("grassland-high", IpccManagementCategory.IMPROVED_GRASSLAND, IpccCarbonInputCategory.GRASSLAND_HIGH),
|
|
315
|
+
(
|
|
316
|
+
"grassland-medium/0-improvements",
|
|
317
|
+
IpccManagementCategory.IMPROVED_GRASSLAND,
|
|
318
|
+
IpccCarbonInputCategory.GRASSLAND_MEDIUM
|
|
319
|
+
),
|
|
320
|
+
(
|
|
321
|
+
"grassland-medium/1-improvements",
|
|
322
|
+
IpccManagementCategory.IMPROVED_GRASSLAND,
|
|
323
|
+
IpccCarbonInputCategory.GRASSLAND_MEDIUM
|
|
324
|
+
)
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@mark.parametrize(
|
|
329
|
+
"subfolder, management_category, expected",
|
|
330
|
+
CARBON_INPUT_CATEGORY_PARAMS,
|
|
331
|
+
ids=[params[0] for params in CARBON_INPUT_CATEGORY_PARAMS]
|
|
332
|
+
)
|
|
333
|
+
@patch(f"{term_path}.search")
|
|
334
|
+
@patch(f"{utils_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
|
|
335
|
+
@patch(f"{utils_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
|
|
336
|
+
@patch(f"{utils_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
|
|
337
|
+
def test_assign_ipcc_carbon_input_category(
|
|
338
|
+
mock_get_cover_crop_property_terms: MagicMock,
|
|
339
|
+
mock_get_irrigated_terms: MagicMock,
|
|
340
|
+
mock_get_residue_removed_or_burnt_terms: MagicMock,
|
|
341
|
+
mock_search: MagicMock,
|
|
342
|
+
subfolder: str,
|
|
343
|
+
management_category: IpccManagementCategory,
|
|
344
|
+
expected: IpccCarbonInputCategory
|
|
345
|
+
):
|
|
346
|
+
folder = f"{fixtures_folder}/IpccCarbonInputCategory/{subfolder}"
|
|
347
|
+
|
|
348
|
+
with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
|
|
349
|
+
site = json.load(f)
|
|
350
|
+
|
|
351
|
+
result = _assign_ipcc_carbon_input_category(site.get("management", []), management_category)
|
|
352
|
+
|
|
353
|
+
# Ensure that API calls to retrieve term IDs are properly cached.
|
|
354
|
+
mock_get_cover_crop_property_terms.call_count <= 1
|
|
355
|
+
mock_get_irrigated_terms.call_count <= 1
|
|
356
|
+
mock_get_residue_removed_or_burnt_terms.call_count <= 1
|
|
357
|
+
|
|
358
|
+
mock_search.assert_not_called() # Ensure that the term utils are properly mocked.
|
|
359
|
+
assert result == expected
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
TIMESTAMPS_CALC_SOC_STOCK = [1990, 1995, 2000, 2005, 2010, 2015, 2020]
|
|
363
|
+
|
|
364
|
+
# regime_start_years, soc_equilibriums, expected
|
|
365
|
+
PARAMS_CALC_SOC_STOCK = [
|
|
366
|
+
(
|
|
367
|
+
[1970, 1995, 1995, 1995, 1995, 1995, 1995],
|
|
368
|
+
array([[77.000], [70.840], [70.840], [70.840], [70.840], [70.840], [70.840]]),
|
|
369
|
+
array([[77.000], [75.460], [73.920], [72.380], [70.840], [70.840], [70.840]])
|
|
370
|
+
),
|
|
371
|
+
(
|
|
372
|
+
[1970, 1995, 1995, 1995, 2010, 2010, 2010],
|
|
373
|
+
array([[77.000], [70.840], [70.840], [70.840], [80.850], [80.850], [80.850]]),
|
|
374
|
+
array([[77.000], [75.460], [73.920], [72.380], [74.498], [76.615], [78.733]])
|
|
375
|
+
),
|
|
376
|
+
(
|
|
377
|
+
[1970, 1995, 1995, 1995, 1995, 2015, 2015],
|
|
378
|
+
array([[80.850], [70.840], [70.840], [70.840], [70.840], [80.850], [80.850]]),
|
|
379
|
+
array([[80.850], [78.348], [75.845], [73.343], [70.840], [73.343], [75.845]])
|
|
380
|
+
),
|
|
381
|
+
(
|
|
382
|
+
[1970, 1970, 2000, 2000, 2000, 2000, 2000],
|
|
383
|
+
array([[80.850], [80.850], [77.000], [77.000], [77.000], [77.000], [77.000]]),
|
|
384
|
+
array([[80.850], [80.850], [79.888], [78.925], [77.963], [77.000], [77.000]])
|
|
385
|
+
),
|
|
386
|
+
(
|
|
387
|
+
[1970, 1970, 1970, 1970, 2010, 2010, 2010],
|
|
388
|
+
array([[70.840], [70.840], [70.840], [70.840], [80.850], [80.850], [80.850]]),
|
|
389
|
+
array([[70.840], [70.840], [70.840], [70.840], [73.343], [75.845], [78.348]])
|
|
390
|
+
),
|
|
391
|
+
(
|
|
392
|
+
[1970, 1970, 2000, 2000, 2000, 2015, 2020],
|
|
393
|
+
array([[70.840], [70.840], [80.850], [80.850], [80.850], [70.840], [80.850]]),
|
|
394
|
+
array([[70.840], [70.840], [73.343], [75.845], [78.348], [76.471], [77.565]])
|
|
395
|
+
)
|
|
396
|
+
]
|
|
397
|
+
IDS_CALC_SOC_STOCK = [f"land-unit-{i+1}" for i in range(len(PARAMS_CALC_SOC_STOCK))]
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@mark.parametrize("regime_start_years, soc_equilibriums, expected", PARAMS_CALC_SOC_STOCK, ids=IDS_CALC_SOC_STOCK)
|
|
401
|
+
def test_calc_soc_stocks(regime_start_years, soc_equilibriums, expected):
|
|
402
|
+
"""
|
|
403
|
+
Test the interpolation between SOC equilibriums using test data provided in IPCC (2019).
|
|
404
|
+
"""
|
|
405
|
+
result = _calc_soc_stocks(
|
|
406
|
+
TIMESTAMPS_CALC_SOC_STOCK, regime_start_years, soc_equilibriums
|
|
407
|
+
)
|
|
408
|
+
assert_array_almost_equal(result, expected, decimal=3)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
TEST_INVENTORY = {
|
|
412
|
+
1960: {
|
|
413
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory.FOREST,
|
|
414
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory.OTHER,
|
|
415
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory.OTHER
|
|
416
|
+
},
|
|
417
|
+
1965: {
|
|
418
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory.FOREST,
|
|
419
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory.OTHER,
|
|
420
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory.OTHER
|
|
421
|
+
},
|
|
422
|
+
1970: {
|
|
423
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory.GRASSLAND,
|
|
424
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory.NOMINALLY_MANAGED,
|
|
425
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory.OTHER
|
|
426
|
+
},
|
|
427
|
+
1995: {
|
|
428
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory.ANNUAL_CROPS,
|
|
429
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory.FULL_TILLAGE,
|
|
430
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory.CROPLAND_LOW
|
|
431
|
+
},
|
|
432
|
+
2003: {
|
|
433
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory.ANNUAL_CROPS,
|
|
434
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory.FULL_TILLAGE,
|
|
435
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory.CROPLAND_MEDIUM
|
|
436
|
+
},
|
|
437
|
+
2025: {
|
|
438
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory.ANNUAL_CROPS,
|
|
439
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory.FULL_TILLAGE,
|
|
440
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory.CROPLAND_MEDIUM
|
|
441
|
+
},
|
|
442
|
+
2026: {
|
|
443
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory.ANNUAL_CROPS,
|
|
444
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory.REDUCED_TILLAGE,
|
|
445
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory.CROPLAND_MEDIUM
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
EXPECTED_MISSING_YEARS = {
|
|
450
|
+
1990: {
|
|
451
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory.GRASSLAND,
|
|
452
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory.NOMINALLY_MANAGED,
|
|
453
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory.OTHER
|
|
454
|
+
},
|
|
455
|
+
2023: {
|
|
456
|
+
_InventoryKey.LU_CATEGORY: IpccLandUseCategory.ANNUAL_CROPS,
|
|
457
|
+
_InventoryKey.MG_CATEGORY: IpccManagementCategory.FULL_TILLAGE,
|
|
458
|
+
_InventoryKey.CI_CATEGORY: IpccCarbonInputCategory.CROPLAND_MEDIUM
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def test_calc_missing_equilibrium_years():
|
|
464
|
+
result = _calc_missing_equilibrium_years(TEST_INVENTORY)
|
|
465
|
+
assert result == EXPECTED_MISSING_YEARS
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def test_calc_regime_start_years():
|
|
469
|
+
EXPECTED = [1940, 1940, 1970, 1995, 2003, 2003, 2026]
|
|
470
|
+
result = _calc_regime_start_years(TEST_INVENTORY)
|
|
471
|
+
assert result == EXPECTED
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
from numpy import array
|
|
2
|
+
from numpy.testing import assert_array_almost_equal
|
|
3
|
+
from numpy.typing import NDArray
|
|
4
|
+
from pytest import mark
|
|
5
|
+
|
|
6
|
+
from hestia_earth.models.utils.array_builders import discrete_uniform_2d, repeat_single
|
|
7
|
+
|
|
8
|
+
from hestia_earth.models.ipcc2019.organicCarbonPerHa import MODEL, TERM_ID
|
|
9
|
+
from hestia_earth.models.ipcc2019.organicCarbonPerHa_tier_2_utils import (
|
|
10
|
+
_calc_temperature_factor_annual, _calc_water_factor_annual, _Parameter, _sample_parameter
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from tests.utils import fixtures_path
|
|
14
|
+
|
|
15
|
+
class_path = f"hestia_earth.models.{MODEL}.{TERM_ID}_tier_2_utils"
|
|
16
|
+
utils_path = f"hestia_earth.models.{MODEL}.{TERM_ID}_utils"
|
|
17
|
+
term_path = "hestia_earth.models.utils.term"
|
|
18
|
+
property_path = "hestia_earth.models.utils.property"
|
|
19
|
+
|
|
20
|
+
fixtures_folder = f"{fixtures_path}/{MODEL}/{TERM_ID}"
|
|
21
|
+
|
|
22
|
+
ITERATIONS = 1000
|
|
23
|
+
SEED = 0
|
|
24
|
+
YEARS = 100
|
|
25
|
+
MONTHS = 12
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
CROP_RESIDUE_INCORP_TERM_IDS = [
|
|
29
|
+
"aboveGroundCropResidueIncorporated",
|
|
30
|
+
"aboveGroundCropResidueLeftOnField",
|
|
31
|
+
"belowGroundCropResidue",
|
|
32
|
+
"discardedCropIncorporated",
|
|
33
|
+
"discardedCropLeftOnField"
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
IRRIGATED_TERM_IDS = [
|
|
37
|
+
"rainfedDeepWater",
|
|
38
|
+
"rainfedDeepWaterWaterDepth100Cm",
|
|
39
|
+
"rainfedDeepWaterWaterDepth50100Cm",
|
|
40
|
+
"irrigatedTypeUnspecified",
|
|
41
|
+
"irrigatedCenterPivotIrrigation",
|
|
42
|
+
"irrigatedContinuouslyFlooded",
|
|
43
|
+
"irrigatedDripIrrigation",
|
|
44
|
+
"irrigatedFurrowIrrigation",
|
|
45
|
+
"irrigatedLateralMoveIrrigation",
|
|
46
|
+
"irrigatedLocalizedIrrigation",
|
|
47
|
+
"irrigatedManualIrrigation",
|
|
48
|
+
"irrigatedSurfaceIrrigationMultipleDrainagePeriods",
|
|
49
|
+
"irrigatedSurfaceIrrigationSingleDrainagePeriod",
|
|
50
|
+
"irrigatedSprinklerIrrigation",
|
|
51
|
+
"irrigatedSubIrrigation",
|
|
52
|
+
"irrigatedSurfaceIrrigationDrainageRegimeUnspecified"
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
UPLAND_RICE_LAND_COVER_TERM_IDS = [
|
|
57
|
+
"ricePlantUpland"
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
UPLAND_RICE_CROP_TERM_IDS = [
|
|
61
|
+
"riceGrainInHuskUpland"
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
DEFAULT_PROPERTIES = {
|
|
65
|
+
"manureDryKgMass": {
|
|
66
|
+
"carbonContent": {
|
|
67
|
+
"value": 38.4
|
|
68
|
+
},
|
|
69
|
+
"nitrogenContent": {
|
|
70
|
+
"value": 2.65
|
|
71
|
+
},
|
|
72
|
+
"ligninContent": {
|
|
73
|
+
"value": 9.67
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def fake_find_term_property(term: dict, property: str, *_):
|
|
80
|
+
term_id = term.get('@id', None)
|
|
81
|
+
return DEFAULT_PROPERTIES.get(term_id, {}).get(property, {})
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def fake_calc_descriptive_stats(arr: NDArray, *_args, **_kwargs):
|
|
85
|
+
return {"value": [row[0] for row in arr]}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def assert_elements_between(arr: NDArray, min: float, max: float):
|
|
89
|
+
assert ((min <= arr) & (arr <= max)).all()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def assert_rows_unique(arr: NDArray):
|
|
93
|
+
"""
|
|
94
|
+
Covert array to a set to remove repeated rows and check that number remaining rows is the same as the number of
|
|
95
|
+
original rows.
|
|
96
|
+
"""
|
|
97
|
+
assert len(set(map(tuple, arr))) == len(arr)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
PARAMS_SAMPLE_PARAMETER = [p for p in _Parameter]
|
|
101
|
+
IDS_SAMPLE_PARAMETER = [p.name for p in _Parameter]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@mark.parametrize("parameter", PARAMS_SAMPLE_PARAMETER, ids=IDS_SAMPLE_PARAMETER)
|
|
105
|
+
def test_sample_parameter(parameter):
|
|
106
|
+
"""
|
|
107
|
+
Check that every parameter can be sampled without raising an error.
|
|
108
|
+
"""
|
|
109
|
+
EXPECTED_SHAPE = (1, ITERATIONS)
|
|
110
|
+
result = _sample_parameter(ITERATIONS, parameter)
|
|
111
|
+
assert result.shape == EXPECTED_SHAPE
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# temperature_monthly, expected
|
|
115
|
+
PARAMS_TEMPERATURE_FACTOR = [
|
|
116
|
+
(array([[-100] for _ in range(12)]), array([[1.4946486e-27]])),
|
|
117
|
+
(array([[0] for _ in range(12)]), array([[0.0803555]])),
|
|
118
|
+
(array([[33.69] for _ in range(12)]), array([[1]])),
|
|
119
|
+
(array([[45] for _ in range(12)]), array([[0]])),
|
|
120
|
+
(array([[50] for _ in range(12)]), array([[0]])),
|
|
121
|
+
(
|
|
122
|
+
array([
|
|
123
|
+
[22.71129032258065], [20.310714285714287], [19.479032258064514],
|
|
124
|
+
[14.993333333333334], [11.206451612903225], [9.055],
|
|
125
|
+
[8.008064516129034], [11.254838709677419], [11.276666666666666],
|
|
126
|
+
[14.148387096774192], [19.980000000000004], [16.372580645161293]
|
|
127
|
+
]),
|
|
128
|
+
array([[0.4904241436936742]])
|
|
129
|
+
)
|
|
130
|
+
]
|
|
131
|
+
IDS_TEMPERATURE_FACTOR = ["-100", "0", "33.69", "45", "50", "ipcc"]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@mark.parametrize("temperature_monthly, expected", PARAMS_TEMPERATURE_FACTOR, ids=IDS_TEMPERATURE_FACTOR)
|
|
135
|
+
def test_calc_annual_temperature_factors(temperature_monthly, expected):
|
|
136
|
+
result = _calc_temperature_factor_annual(temperature_monthly)
|
|
137
|
+
assert_array_almost_equal(result, expected)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_calc_annual_temperature_factors_random():
|
|
141
|
+
SHAPE = (YEARS * MONTHS, ITERATIONS)
|
|
142
|
+
MIN, MAX = 0, 1
|
|
143
|
+
|
|
144
|
+
TEMPERATURE_MONTHLY = discrete_uniform_2d(SHAPE, -60, 60, seed=SEED)
|
|
145
|
+
|
|
146
|
+
result = _calc_temperature_factor_annual(TEMPERATURE_MONTHLY)
|
|
147
|
+
|
|
148
|
+
assert_elements_between(result, MIN, MAX)
|
|
149
|
+
assert_rows_unique(result)
|
|
150
|
+
assert result.shape == (YEARS, ITERATIONS)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# precipitation_monthly, pet_monthly, expected
|
|
154
|
+
PARAMS_WATER_FACTOR = [
|
|
155
|
+
(array([[0] for _ in range(12)]), array([[0] for _ in range(12)]), array([[2.24942813]])), # Closes issue 771
|
|
156
|
+
(array([[1] for _ in range(12)]), array([[10000] for _ in range(12)]), array([[0.3195496]])),
|
|
157
|
+
(array([[10000] for _ in range(12)]), array([[1] for _ in range(12)]), array([[2.24942813]])),
|
|
158
|
+
(
|
|
159
|
+
array([
|
|
160
|
+
[4.8], [23.900000000000002], [24.7],
|
|
161
|
+
[3.5999999999999996], [11.3], [11.200000000000001],
|
|
162
|
+
[27.400000000000006], [53], [30.7],
|
|
163
|
+
[39.3], [9.399999999999999], [41.8]
|
|
164
|
+
]),
|
|
165
|
+
array([
|
|
166
|
+
[253.80000000000007], [214.4], [176.59999999999994],
|
|
167
|
+
[104.19999999999997], [62.79999999999997], [41.59999999999999],
|
|
168
|
+
[45.60000000000001], [64.80000000000001], [91.60000000000001],
|
|
169
|
+
[140.00000000000006], [189.99999999999994], [233.00000000000006]
|
|
170
|
+
]),
|
|
171
|
+
array([[0.7793321739983536]])
|
|
172
|
+
)
|
|
173
|
+
]
|
|
174
|
+
IDS_WATER_FACTOR = ["0/0", "1/10000", "10000/1", "ipcc"]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@mark.parametrize("precipitation_monthly, pet_monthly, expected", PARAMS_WATER_FACTOR, ids=IDS_WATER_FACTOR)
|
|
178
|
+
def test_calc_calc_annual_water_factors(precipitation_monthly, pet_monthly, expected):
|
|
179
|
+
result = _calc_water_factor_annual(precipitation_monthly, pet_monthly)
|
|
180
|
+
assert_array_almost_equal(result, expected)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def test_calc_calc_annual_water_factors_random():
|
|
184
|
+
SHAPE = (YEARS * MONTHS, ITERATIONS)
|
|
185
|
+
MIN, MAX = 0.31935, 2.24942813
|
|
186
|
+
|
|
187
|
+
PRECIPITATION_MONTHLY = discrete_uniform_2d(SHAPE, 0, 1000, seed=SEED)
|
|
188
|
+
PET_MONTHLY = discrete_uniform_2d(SHAPE, 0, 2500, seed=SEED+1)
|
|
189
|
+
|
|
190
|
+
result = _calc_water_factor_annual(PRECIPITATION_MONTHLY, PET_MONTHLY)
|
|
191
|
+
|
|
192
|
+
assert_elements_between(result, MIN, MAX)
|
|
193
|
+
assert_rows_unique(result)
|
|
194
|
+
assert result.shape == (YEARS, ITERATIONS)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_calc_calc_annual_water_factors_irrigated():
|
|
198
|
+
EXPECTED = 0.775 * 1.5
|
|
199
|
+
|
|
200
|
+
SHAPE = (YEARS * MONTHS, ITERATIONS)
|
|
201
|
+
PRECIPITATION_MONTHLY = discrete_uniform_2d(SHAPE, 0, 1000, seed=SEED)
|
|
202
|
+
PET_MONTHLY = discrete_uniform_2d(SHAPE, 0, 2500, seed=SEED+1)
|
|
203
|
+
IRRIGATED_MONTHLY = repeat_single(SHAPE, True)
|
|
204
|
+
|
|
205
|
+
result = _calc_water_factor_annual(PRECIPITATION_MONTHLY, PET_MONTHLY, IRRIGATED_MONTHLY)
|
|
206
|
+
|
|
207
|
+
(result == EXPECTED).all() # assert all elements in result are the expected value
|
|
208
|
+
assert result.shape == (YEARS, ITERATIONS)
|