hestia-earth-models 0.65.11__py3-none-any.whl → 0.67.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.
- hestia_earth/models/cache_sites.py +7 -9
- hestia_earth/models/cml2001Baseline/abioticResourceDepletionFossilFuels.py +23 -54
- hestia_earth/models/cml2001Baseline/resourceUseEnergyDepletionDuringCycle.py +152 -0
- hestia_earth/models/cml2001Baseline/resourceUseEnergyDepletionInputsProduction.py +40 -0
- hestia_earth/models/cml2001Baseline/resourceUseMineralsAndMetalsDuringCycle.py +80 -0
- hestia_earth/models/cml2001Baseline/resourceUseMineralsAndMetalsInputsProduction.py +40 -0
- hestia_earth/models/config/Cycle.json +34 -16
- hestia_earth/models/config/ImpactAssessment.json +1867 -1832
- hestia_earth/models/config/Site.json +4 -1
- hestia_earth/models/cycle/completeness/freshForage.py +10 -2
- hestia_earth/models/cycle/cropResidueManagement.py +3 -1
- hestia_earth/models/cycle/input/hestiaAggregatedData.py +13 -10
- hestia_earth/models/ecoinventV3/__init__.py +2 -1
- hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/__init__.py +4 -3
- hestia_earth/models/environmentalFootprintV3_1/environmentalFootprintSingleOverallScore.py +135 -0
- hestia_earth/models/environmentalFootprintV3_1/marineEutrophicationPotential.py +36 -0
- hestia_earth/models/environmentalFootprintV3_1/scarcityWeightedWaterUse.py +40 -0
- hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/soilQualityIndexLandTransformation.py +17 -6
- hestia_earth/models/geospatialDatabase/{aware.py → awareWaterBasinId.py} +1 -1
- hestia_earth/models/hestia/landCover.py +42 -34
- hestia_earth/models/hestia/residueRemoved.py +80 -0
- hestia_earth/models/hestia/resourceUse_utils.py +43 -29
- hestia_earth/models/impact_assessment/product/value.py +1 -1
- hestia_earth/models/ipcc2019/aboveGroundBiomass.py +34 -13
- hestia_earth/models/ipcc2019/belowGroundBiomass.py +33 -12
- hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +17 -8
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +7 -4
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +2 -1
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +29 -18
- hestia_earth/models/ipcc2019/pastureGrass_utils.py +8 -1
- hestia_earth/models/log.py +1 -1
- hestia_earth/models/mocking/search-results.json +872 -872
- hestia_earth/models/site/defaultMethodClassification.py +9 -2
- hestia_earth/models/site/defaultMethodClassificationDescription.py +4 -2
- hestia_earth/models/site/management.py +48 -30
- hestia_earth/models/site/pre_checks/cache_geospatialDatabase.py +19 -14
- hestia_earth/models/utils/__init__.py +6 -0
- hestia_earth/models/utils/aggregated.py +13 -10
- hestia_earth/models/utils/array_builders.py +4 -3
- hestia_earth/models/utils/blank_node.py +23 -13
- hestia_earth/models/utils/lookup.py +4 -2
- hestia_earth/models/utils/property.py +5 -2
- hestia_earth/models/version.py +1 -1
- hestia_earth/orchestrator/log.py +11 -0
- hestia_earth/orchestrator/models/__init__.py +8 -3
- hestia_earth/orchestrator/strategies/merge/merge_list.py +17 -6
- {hestia_earth_models-0.65.11.dist-info → hestia_earth_models-0.67.0.dist-info}/METADATA +1 -1
- {hestia_earth_models-0.65.11.dist-info → hestia_earth_models-0.67.0.dist-info}/RECORD +86 -69
- tests/models/cml2001Baseline/test_abioticResourceDepletionFossilFuels.py +51 -87
- tests/models/cml2001Baseline/test_resourceUseEnergyDepletionDuringCycle.py +103 -0
- tests/models/cml2001Baseline/test_resourceUseEnergyDepletionInputsProduction.py +23 -0
- tests/models/cml2001Baseline/test_resourceUseMineralsAndMetalsDuringCycle.py +58 -0
- tests/models/cml2001Baseline/test_resourceUseMineralsAndMetalsInputsProduction.py +23 -0
- tests/models/environmentalFootprintV3_1/test_environmentalFootprintSingleOverallScore.py +93 -0
- tests/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/test_freshwaterEcotoxicityPotentialCtue.py +6 -5
- tests/models/environmentalFootprintV3_1/test_marineEutrophicationPotential.py +27 -0
- tests/models/environmentalFootprintV3_1/test_scarcityWeightedWaterUse.py +32 -0
- tests/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/test_soilQualityIndexLandOccupation.py +4 -3
- tests/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/test_soilQualityIndexLandTransformation.py +8 -22
- tests/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/test_soilQualityIndexTotalLandUseEffects.py +4 -4
- tests/models/faostat2018/product/test_price.py +1 -1
- tests/models/geospatialDatabase/{test_aware.py → test_awareWaterBasinId.py} +1 -1
- tests/models/hestia/test_landCover.py +2 -1
- tests/models/hestia/test_landTransformation20YearAverageDuringCycle.py +2 -1
- tests/models/hestia/test_residueRemoved.py +20 -0
- tests/models/impact_assessment/test_emissions.py +0 -1
- tests/models/ipcc2019/test_aboveGroundBiomass.py +3 -1
- tests/models/ipcc2019/test_belowGroundBiomass.py +4 -2
- tests/models/ipcc2019/test_organicCarbonPerHa.py +94 -1
- tests/models/site/pre_checks/test_cache_geospatialDatabase.py +22 -0
- tests/models/site/test_defaultMethodClassification.py +6 -0
- tests/models/site/test_defaultMethodClassificationDescription.py +6 -0
- tests/models/site/test_management.py +4 -4
- tests/models/test_cache_sites.py +2 -2
- tests/models/test_config.py +3 -3
- tests/models/test_ecoinventV3.py +0 -1
- tests/models/utils/test_array_builders.py +2 -2
- tests/orchestrator/strategies/merge/test_merge_list.py +11 -1
- /hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/freshwaterEcotoxicityPotentialCtue.py +0 -0
- /hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/soilQualityIndexLandOccupation.py +0 -0
- /hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/soilQualityIndexTotalLandUseEffects.py +0 -0
- /hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/utils.py +0 -0
- {hestia_earth_models-0.65.11.dist-info → hestia_earth_models-0.67.0.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.65.11.dist-info → hestia_earth_models-0.67.0.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.65.11.dist-info → hestia_earth_models-0.67.0.dist-info}/top_level.txt +0 -0
- /tests/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/__init__.py +0 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
"""
|
2
|
+
This model will gap-fill the value for `residueRemoved` when the only provided data is the incorporated residue.
|
3
|
+
We are assuming that anything that was not incorporated must have been removed.
|
4
|
+
"""
|
5
|
+
from hestia_earth.schema import TermTermType
|
6
|
+
from hestia_earth.utils.model import filter_list_term_type
|
7
|
+
from hestia_earth.utils.tools import list_sum
|
8
|
+
|
9
|
+
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
|
10
|
+
from hestia_earth.models.utils.completeness import _is_term_type_incomplete
|
11
|
+
from hestia_earth.models.utils.practice import _new_practice
|
12
|
+
from hestia_earth.models.utils import is_from_model
|
13
|
+
from . import MODEL
|
14
|
+
|
15
|
+
REQUIREMENTS = {
|
16
|
+
"Cycle": {
|
17
|
+
"completeness.cropResidue": "False",
|
18
|
+
"practices": [{
|
19
|
+
"@type": "Practice",
|
20
|
+
"term.@id": [
|
21
|
+
"residueIncorporated",
|
22
|
+
"residueIncorporatedLessThan30DaysBeforeCultivation",
|
23
|
+
"residueIncorporatedMoreThan30DaysBeforeCultivation"
|
24
|
+
]
|
25
|
+
}],
|
26
|
+
"none": {
|
27
|
+
"practices": [{
|
28
|
+
"@type": "Practice",
|
29
|
+
"term.@id": [
|
30
|
+
"residueRemoved",
|
31
|
+
"residueBurnt",
|
32
|
+
"residueLeftOnField"
|
33
|
+
]
|
34
|
+
}]
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
RETURNS = {
|
39
|
+
"Practice": [{
|
40
|
+
"value": ""
|
41
|
+
}]
|
42
|
+
}
|
43
|
+
TERM_ID = 'residueRemoved'
|
44
|
+
|
45
|
+
|
46
|
+
def _practice(value: float):
|
47
|
+
practice = _new_practice(TERM_ID, MODEL)
|
48
|
+
practice['value'] = [value]
|
49
|
+
return practice
|
50
|
+
|
51
|
+
|
52
|
+
def _should_run(cycle: dict):
|
53
|
+
crop_residue_incomplete = _is_term_type_incomplete(cycle, TermTermType.CROPRESIDUE)
|
54
|
+
|
55
|
+
practices = filter_list_term_type(cycle.get('practices', []), TermTermType.CROPRESIDUEMANAGEMENT)
|
56
|
+
incorporated_practices = [
|
57
|
+
{'id': p.get('term', {}).get('@id'), 'value': list_sum(p.get('value'), None)}
|
58
|
+
for p in practices
|
59
|
+
if p.get('term', {}).get('@id').startswith('residueIncorporated') and not is_from_model(p)
|
60
|
+
]
|
61
|
+
has_other_practices = any([
|
62
|
+
not p.get('term', {}).get('@id').startswith('residueIncorporated')
|
63
|
+
for p in practices
|
64
|
+
])
|
65
|
+
incorporated_value = list_sum([p.get('value') for p in incorporated_practices], None)
|
66
|
+
|
67
|
+
logRequirements(cycle, model=MODEL, term=TERM_ID,
|
68
|
+
term_type_cropResidue_incomplete=crop_residue_incomplete,
|
69
|
+
incorporated_practices=log_as_table(incorporated_practices),
|
70
|
+
incorporated_value=incorporated_value,
|
71
|
+
has_other_practices=has_other_practices)
|
72
|
+
|
73
|
+
should_run = all([crop_residue_incomplete, incorporated_value, not has_other_practices])
|
74
|
+
logShouldRun(cycle, MODEL, TERM_ID, should_run)
|
75
|
+
return should_run, 100 - (incorporated_value or 0)
|
76
|
+
|
77
|
+
|
78
|
+
def run(cycle: dict):
|
79
|
+
should_run, value = _should_run(cycle)
|
80
|
+
return [_practice(value)] if should_run else []
|
@@ -46,26 +46,25 @@ def _gap_filled_date_obj(date_str: str) -> datetime:
|
|
46
46
|
)
|
47
47
|
|
48
48
|
|
49
|
-
def
|
50
|
-
|
49
|
+
def _find_closest_node(
|
50
|
+
ia_date_str: str,
|
51
51
|
management_nodes: list,
|
52
|
-
historic_date_offset: int
|
53
|
-
|
52
|
+
historic_date_offset: int,
|
53
|
+
node_date_field: str
|
54
|
+
) -> str:
|
54
55
|
historic_ia_date_obj = (
|
55
|
-
_gap_filled_date_obj(
|
56
|
-
if
|
56
|
+
_gap_filled_date_obj(ia_date_str) - relativedelta(years=historic_date_offset)
|
57
|
+
if ia_date_str else None
|
57
58
|
)
|
58
59
|
# Calculate all distances in days which are less than MAXIMUM_OFFSET_DAYS from historic date
|
59
|
-
# Assumption: if there are two dates are equidistant from the target
|
60
|
+
# Assumption: if there are two dates are equidistant from the target, choose the second.
|
60
61
|
filtered_dates = {
|
61
|
-
abs((_gap_filled_date_obj(node.get(
|
62
|
+
abs((_gap_filled_date_obj(node.get(node_date_field)) - historic_ia_date_obj).days): node.get(node_date_field)
|
62
63
|
for node in management_nodes
|
63
64
|
if node.get("term", {}).get("termType", "") == TermTermType.LANDCOVER.value and
|
64
|
-
abs((_gap_filled_date_obj(node.get(
|
65
|
+
abs((_gap_filled_date_obj(node.get(node_date_field)) - historic_ia_date_obj).days) <= _MAXIMUM_OFFSET_DAYS
|
65
66
|
}
|
66
|
-
|
67
|
-
|
68
|
-
return nearest_date != "", nearest_date
|
67
|
+
return filtered_dates[min(filtered_dates.keys())] if filtered_dates else ""
|
69
68
|
|
70
69
|
|
71
70
|
def should_run(
|
@@ -73,7 +72,7 @@ def should_run(
|
|
73
72
|
site: dict,
|
74
73
|
term_id: str,
|
75
74
|
historic_date_offset: int
|
76
|
-
) -> tuple[bool, dict, str]:
|
75
|
+
) -> tuple[bool, dict, str, str]:
|
77
76
|
relevant_emission_resource_use = [
|
78
77
|
node for node in impact_assessment.get("emissionsResourceUse", [])
|
79
78
|
if node.get("term", {}).get("@id", "") == _RESOURCE_USE_TERM_ID and node.get("value", -1) >= 0
|
@@ -87,33 +86,45 @@ def should_run(
|
|
87
86
|
{node.get("landCover", {}).get("@id") for node in relevant_emission_resource_use}
|
88
87
|
for node in filtered_management_nodes
|
89
88
|
)
|
89
|
+
match_mode = (
|
90
|
+
DatestrGapfillMode.START if impact_assessment.get("cycle", {}).get("aggregated") is True
|
91
|
+
else DatestrGapfillMode.END
|
92
|
+
)
|
93
|
+
match_date = "startDate" if match_mode == DatestrGapfillMode.START else "endDate"
|
94
|
+
|
95
|
+
closest_date = _find_closest_node(
|
96
|
+
ia_date_str=impact_assessment.get(match_date, ""),
|
97
|
+
management_nodes=filtered_management_nodes,
|
98
|
+
historic_date_offset=historic_date_offset,
|
99
|
+
node_date_field=match_date
|
100
|
+
)
|
101
|
+
closest_start_date, closest_end_date = (closest_date, None) if match_date == "startDate" else (None, closest_date)
|
90
102
|
current_node_index = next(
|
91
103
|
(i for i, node in enumerate(filtered_management_nodes)
|
92
|
-
if _str_dates_match(
|
104
|
+
if _str_dates_match(
|
105
|
+
date_str_one=node.get(match_date, ""),
|
106
|
+
date_str_two=impact_assessment.get(match_date, ""),
|
107
|
+
mode=match_mode
|
108
|
+
)),
|
93
109
|
None
|
94
110
|
)
|
95
111
|
current_node = filtered_management_nodes.pop(current_node_index) if current_node_index is not None else None
|
96
112
|
|
97
|
-
close_date_found, closest_date_str = _should_run_close_date_found(
|
98
|
-
ia_end_date_str=impact_assessment.get("endDate", ""),
|
99
|
-
management_nodes=filtered_management_nodes,
|
100
|
-
historic_date_offset=historic_date_offset
|
101
|
-
)
|
102
|
-
|
103
113
|
logRequirements(impact_assessment, model=MODEL, term=term_id,
|
104
|
-
|
105
|
-
|
106
|
-
|
114
|
+
closest_end_date=closest_end_date,
|
115
|
+
closest_start_date=closest_start_date,
|
116
|
+
has_landOccupationDuringCycle=land_occupation_during_cycle_found,
|
117
|
+
landCover_term_id=(current_node or {}).get('term', {}).get('@id'))
|
107
118
|
|
108
119
|
should_run_result = all([
|
109
120
|
relevant_emission_resource_use,
|
110
121
|
land_occupation_during_cycle_found,
|
111
122
|
current_node,
|
112
|
-
|
123
|
+
closest_end_date or closest_start_date
|
113
124
|
])
|
114
125
|
logShouldRun(impact_assessment, MODEL, term=term_id, should_run=should_run_result)
|
115
126
|
|
116
|
-
return should_run_result, current_node,
|
127
|
+
return should_run_result, current_node, closest_end_date, closest_start_date
|
117
128
|
|
118
129
|
|
119
130
|
def _get_land_occupation_for_land_use_type(impact_assessment: dict, ipcc_land_use_category: str) -> float:
|
@@ -160,7 +171,8 @@ def _calculate_indicator_value(
|
|
160
171
|
def _run_calculate_transformation(
|
161
172
|
term_id: str,
|
162
173
|
current_node: dict,
|
163
|
-
|
174
|
+
closest_end_date: str,
|
175
|
+
closest_start_date: str,
|
164
176
|
impact_assessment: dict,
|
165
177
|
site: dict,
|
166
178
|
historic_date_offset: int
|
@@ -178,7 +190,8 @@ def _run_calculate_transformation(
|
|
178
190
|
term_id=term_id,
|
179
191
|
management_nodes=[
|
180
192
|
node for node in site.get("management", [])
|
181
|
-
if _str_dates_match(node.get("endDate", ""),
|
193
|
+
if _str_dates_match(node.get("endDate", ""), closest_end_date) or
|
194
|
+
_str_dates_match(node.get("startDate", ""), closest_start_date)
|
182
195
|
],
|
183
196
|
ipcc_land_use_category=crop_ipcc_land_use_category(current_node.get("term", {}).get("@id", "")),
|
184
197
|
previous_land_cover_id=previous_land_cover_id,
|
@@ -196,7 +209,7 @@ def run_resource_use(
|
|
196
209
|
term_id: str
|
197
210
|
) -> list:
|
198
211
|
site = get_site(impact_assessment)
|
199
|
-
_should_run, current_node,
|
212
|
+
_should_run, current_node, closest_end_date, closest_start_date = should_run(
|
200
213
|
impact_assessment=impact_assessment,
|
201
214
|
site=site,
|
202
215
|
term_id=term_id,
|
@@ -207,6 +220,7 @@ def run_resource_use(
|
|
207
220
|
site=site,
|
208
221
|
term_id=term_id,
|
209
222
|
current_node=current_node,
|
210
|
-
|
223
|
+
closest_end_date=closest_end_date,
|
224
|
+
closest_start_date=closest_start_date,
|
211
225
|
historic_date_offset=historic_date_offset
|
212
226
|
) if _should_run else []
|
@@ -170,7 +170,7 @@ def _should_run(site: dict) -> tuple[bool, dict, dict]:
|
|
170
170
|
inventory = _compile_inventory(land_cover) if should_compile_inventory else {}
|
171
171
|
kwargs = {
|
172
172
|
"eco_climate_zone": eco_climate_zone,
|
173
|
-
"seed": gen_seed(site)
|
173
|
+
"seed": gen_seed(site, MODEL, TERM_ID)
|
174
174
|
}
|
175
175
|
|
176
176
|
logRequirements(
|
@@ -229,6 +229,7 @@ def _compile_inventory(land_cover_nodes: list[dict]) -> dict:
|
|
229
229
|
The inventory of data.
|
230
230
|
"""
|
231
231
|
land_cover_grouped = group_nodes_by_year(land_cover_nodes)
|
232
|
+
min_year, max_year = min(land_cover_grouped.keys()), max(land_cover_grouped.keys())
|
232
233
|
|
233
234
|
def build_inventory_year(inventory: dict, year_pair: tuple[int, int]) -> dict:
|
234
235
|
"""
|
@@ -262,15 +263,33 @@ def _compile_inventory(land_cover_nodes: list[dict]) -> dict:
|
|
262
263
|
years_since_lcc_event = time_delta if is_lcc_event else prev_years_since_lcc_event + time_delta
|
263
264
|
regime_start_year = current_year - years_since_lcc_event
|
264
265
|
|
266
|
+
equilibrium_year = regime_start_year + _EQUILIBRIUM_TRANSITION_PERIOD
|
267
|
+
inventory_years = set(list(inventory.keys()) + list(land_cover_grouped.keys()))
|
268
|
+
|
269
|
+
should_add_equilibrium_year = (
|
270
|
+
min_year < equilibrium_year < max_year # Is the year relevant?
|
271
|
+
and equilibrium_year not in inventory_years # Is the year missing?
|
272
|
+
and equilibrium_year < current_year # Is it the first inventory year after the equilibrium?
|
273
|
+
)
|
274
|
+
|
275
|
+
current_data = {
|
276
|
+
_InventoryKey.BIOMASS_CATEGORY_SUMMARY: biomass_category_summary,
|
277
|
+
_InventoryKey.LAND_COVER_SUMMARY: land_cover_summary,
|
278
|
+
_InventoryKey.LAND_COVER_CHANGE_EVENT: is_lcc_event,
|
279
|
+
_InventoryKey.YEARS_SINCE_LCC_EVENT: years_since_lcc_event,
|
280
|
+
_InventoryKey.REGIME_START_YEAR: regime_start_year
|
281
|
+
}
|
282
|
+
|
283
|
+
equilibrium_data = {
|
284
|
+
**current_data,
|
285
|
+
_InventoryKey.YEARS_SINCE_LCC_EVENT: _EQUILIBRIUM_TRANSITION_PERIOD
|
286
|
+
}
|
287
|
+
|
265
288
|
update_dict = {
|
266
|
-
current_year:
|
267
|
-
|
268
|
-
_InventoryKey.LAND_COVER_SUMMARY: land_cover_summary,
|
269
|
-
_InventoryKey.LAND_COVER_CHANGE_EVENT: is_lcc_event,
|
270
|
-
_InventoryKey.YEARS_SINCE_LCC_EVENT: years_since_lcc_event,
|
271
|
-
_InventoryKey.REGIME_START_YEAR: regime_start_year
|
272
|
-
}
|
289
|
+
current_year: current_data,
|
290
|
+
**({equilibrium_year: equilibrium_data} if should_add_equilibrium_year else {})
|
273
291
|
}
|
292
|
+
|
274
293
|
return inventory | update_dict
|
275
294
|
|
276
295
|
start_year = list(land_cover_grouped)[0]
|
@@ -290,11 +309,13 @@ def _compile_inventory(land_cover_nodes: list[dict]) -> dict:
|
|
290
309
|
}
|
291
310
|
}
|
292
311
|
|
293
|
-
return
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
312
|
+
return dict(sorted(
|
313
|
+
reduce(
|
314
|
+
build_inventory_year,
|
315
|
+
pairwise(land_cover_grouped.keys()), # Inventory years need data from previous year to be compiled.
|
316
|
+
initial
|
317
|
+
).items()
|
318
|
+
))
|
298
319
|
|
299
320
|
|
300
321
|
def _format_inventory(inventory: dict) -> str:
|
@@ -167,7 +167,7 @@ def _should_run(site: dict) -> tuple[bool, dict, dict]:
|
|
167
167
|
inventory = _compile_inventory(land_cover) if should_compile_inventory else {}
|
168
168
|
kwargs = {
|
169
169
|
"eco_climate_zone": eco_climate_zone,
|
170
|
-
"seed": gen_seed(site)
|
170
|
+
"seed": gen_seed(site, MODEL, TERM_ID)
|
171
171
|
}
|
172
172
|
|
173
173
|
logRequirements(
|
@@ -222,6 +222,7 @@ def _compile_inventory(land_cover_nodes: list[dict]) -> dict:
|
|
222
222
|
The inventory of data.
|
223
223
|
"""
|
224
224
|
land_cover_grouped = group_nodes_by_year(land_cover_nodes)
|
225
|
+
min_year, max_year = min(land_cover_grouped.keys()), max(land_cover_grouped.keys())
|
225
226
|
|
226
227
|
def build_inventory_year(inventory: dict, year_pair: tuple[int, int]) -> dict:
|
227
228
|
"""
|
@@ -253,14 +254,32 @@ def _compile_inventory(land_cover_nodes: list[dict]) -> dict:
|
|
253
254
|
years_since_lcc_event = time_delta if is_lcc_event else prev_years_since_lcc_event + time_delta
|
254
255
|
regime_start_year = current_year - years_since_lcc_event
|
255
256
|
|
257
|
+
equilibrium_year = regime_start_year + _EQUILIBRIUM_TRANSITION_PERIOD
|
258
|
+
inventory_years = set(list(inventory.keys()) + list(land_cover_grouped.keys()))
|
259
|
+
|
260
|
+
should_add_equilibrium_year = (
|
261
|
+
min_year < equilibrium_year < max_year # Is the year relevant?
|
262
|
+
and equilibrium_year not in inventory_years # Is the year missing?
|
263
|
+
and equilibrium_year < current_year # Is it the first inventory year after the equilibrium?
|
264
|
+
)
|
265
|
+
|
266
|
+
current_data = {
|
267
|
+
_InventoryKey.BIOMASS_CATEGORY_SUMMARY: biomass_category_summary,
|
268
|
+
_InventoryKey.LAND_COVER_CHANGE_EVENT: is_lcc_event,
|
269
|
+
_InventoryKey.YEARS_SINCE_LCC_EVENT: years_since_lcc_event,
|
270
|
+
_InventoryKey.REGIME_START_YEAR: regime_start_year
|
271
|
+
}
|
272
|
+
|
273
|
+
equilibrium_data = {
|
274
|
+
**current_data,
|
275
|
+
_InventoryKey.YEARS_SINCE_LCC_EVENT: _EQUILIBRIUM_TRANSITION_PERIOD
|
276
|
+
}
|
277
|
+
|
256
278
|
update_dict = {
|
257
|
-
current_year:
|
258
|
-
|
259
|
-
_InventoryKey.LAND_COVER_CHANGE_EVENT: is_lcc_event,
|
260
|
-
_InventoryKey.YEARS_SINCE_LCC_EVENT: years_since_lcc_event,
|
261
|
-
_InventoryKey.REGIME_START_YEAR: regime_start_year
|
262
|
-
}
|
279
|
+
current_year: current_data,
|
280
|
+
**({equilibrium_year: equilibrium_data} if should_add_equilibrium_year else {})
|
263
281
|
}
|
282
|
+
|
264
283
|
return inventory | update_dict
|
265
284
|
|
266
285
|
start_year = list(land_cover_grouped)[0]
|
@@ -277,11 +296,13 @@ def _compile_inventory(land_cover_nodes: list[dict]) -> dict:
|
|
277
296
|
}
|
278
297
|
}
|
279
298
|
|
280
|
-
return
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
299
|
+
return dict(sorted(
|
300
|
+
reduce(
|
301
|
+
build_inventory_year,
|
302
|
+
pairwise(land_cover_grouped.keys()), # Inventory years need data from previous year to be compiled.
|
303
|
+
initial
|
304
|
+
).items()
|
305
|
+
))
|
285
306
|
|
286
307
|
|
287
308
|
def _format_inventory(inventory: dict) -> str:
|
@@ -237,24 +237,33 @@ def _should_run(cycle: dict):
|
|
237
237
|
|
238
238
|
# only keep inputs that have a positive value
|
239
239
|
inputs = list(filter(lambda i: list_sum(i.get('value', [])) > 0, feed_inputs))
|
240
|
-
DE = (
|
241
|
-
|
242
|
-
)
|
243
|
-
|
240
|
+
DE = get_total_value_converted_with_min_ratio(
|
241
|
+
MODEL, TERM_ID, cycle, inputs, prop_id=DE_type, is_sum=False
|
242
|
+
) if DE_type else None
|
243
|
+
# set as a percentage in the properties
|
244
|
+
DE = DE * 100 if DE else DE
|
245
|
+
DE_default = get_default_digestibility(MODEL, TERM_ID, cycle)
|
246
|
+
|
247
|
+
# set as a percentage in the properties
|
248
|
+
NDF = get_total_value_converted_with_min_ratio(
|
249
|
+
MODEL, TERM_ID, cycle, inputs, prop_id='neutralDetergentFibreContent', is_sum=False
|
250
|
+
)
|
251
|
+
NDF = NDF * 100 if NDF else NDF
|
244
252
|
|
245
253
|
enteric_factor = safe_parse_float(_get_lookup_value(
|
246
|
-
lookup, term, LOOKUPS['liveAnimal'][1], DE, NDF, ionophore, milk_yield
|
254
|
+
lookup, term, LOOKUPS['liveAnimal'][1], DE or DE_default, NDF, ionophore, milk_yield
|
247
255
|
), None)
|
248
256
|
enteric_sd = safe_parse_float(_get_lookup_value(
|
249
|
-
lookup, term, LOOKUPS['liveAnimal'][2], DE, NDF, ionophore, milk_yield
|
257
|
+
lookup, term, LOOKUPS['liveAnimal'][2], DE or DE_default, NDF, ionophore, milk_yield
|
250
258
|
), None)
|
251
259
|
|
252
260
|
default_values = _get_default_values(lookup, term)
|
253
261
|
|
254
262
|
debugValues(cycle, model=MODEL, term=TERM_ID,
|
255
263
|
DE_type=DE_type,
|
256
|
-
|
257
|
-
|
264
|
+
DE=DE,
|
265
|
+
**({'DE_default_lookup': DE_default} if not DE else {}),
|
266
|
+
NDF=NDF,
|
258
267
|
ionophore=ionophore,
|
259
268
|
milk_yield=milk_yield,
|
260
269
|
enteric_factor=enteric_factor,
|
@@ -38,6 +38,7 @@ from hestia_earth.models.utils.time_series import (
|
|
38
38
|
)
|
39
39
|
|
40
40
|
from .utils import check_consecutive
|
41
|
+
from . import MODEL
|
41
42
|
|
42
43
|
_ITERATIONS = 10000
|
43
44
|
_MAX_CORRELATION = 1
|
@@ -424,7 +425,7 @@ def create_should_run_function(
|
|
424
425
|
|
425
426
|
land_cover_nodes = get_valid_management_nodes_func(site)
|
426
427
|
|
427
|
-
seed = gen_seed(site) # All cycles linked to the same site should be consistent
|
428
|
+
seed = gen_seed(site, MODEL, carbon_stock_term_id) # All cycles linked to the same site should be consistent
|
428
429
|
rng = random.default_rng(seed)
|
429
430
|
|
430
431
|
should_compile_inventory, should_compile_logs = should_compile_inventory_func(
|
@@ -1315,7 +1316,8 @@ def _format_land_use_inventory(land_use_inventory: dict) -> str:
|
|
1315
1316
|
"""
|
1316
1317
|
KEYS = [
|
1317
1318
|
_InventoryKey.LAND_USE_CHANGE_EVENT,
|
1318
|
-
_InventoryKey.YEARS_SINCE_LUC_EVENT
|
1319
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT,
|
1320
|
+
_InventoryKey.YEARS_SINCE_INVENTORY_START
|
1319
1321
|
]
|
1320
1322
|
|
1321
1323
|
inventory_years = sorted(set(non_empty_list(years for years in land_use_inventory.keys())))
|
@@ -1374,7 +1376,8 @@ def _format_named_tuple(value: Optional[Union[CarbonStock, CarbonStockChange, Ca
|
|
1374
1376
|
|
1375
1377
|
_LAND_USE_INVENTORY_KEY_TO_FORMAT_FUNC = {
|
1376
1378
|
_InventoryKey.LAND_USE_CHANGE_EVENT: _format_bool,
|
1377
|
-
_InventoryKey.YEARS_SINCE_LUC_EVENT: _format_int
|
1379
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT: _format_int,
|
1380
|
+
_InventoryKey.YEARS_SINCE_INVENTORY_START: _format_int
|
1378
1381
|
}
|
1379
1382
|
"""
|
1380
1383
|
Map inventory keys to format functions. The columns in inventory logged as a table will also be sorted in the order of
|
@@ -1416,7 +1419,7 @@ def create_run_function(
|
|
1416
1419
|
years_since_inventory_start = data[_InventoryKey.YEARS_SINCE_INVENTORY_START]
|
1417
1420
|
|
1418
1421
|
is_luc_emission = bool(years_since_luc_event) and years_since_luc_event <= _TRANSITION_PERIOD_YEARS
|
1419
|
-
is_data_complete = bool(years_since_inventory_start) and years_since_inventory_start
|
1422
|
+
is_data_complete = bool(years_since_inventory_start) and years_since_inventory_start >= _TRANSITION_PERIOD_YEARS
|
1420
1423
|
|
1421
1424
|
if is_luc_emission:
|
1422
1425
|
# If LUC emission allocate emissions to land use change AND add corresponding zero emission to management
|
@@ -41,6 +41,7 @@ from .organicCarbonPerHa_utils import (
|
|
41
41
|
sample_plus_minus_error, sample_plus_minus_uncertainty, SITE_TYPE_TO_IPCC_LAND_USE_CATEGORY,
|
42
42
|
SUPER_MAJORITY_AREA_THRESHOLD, STATS_DEFINITION
|
43
43
|
)
|
44
|
+
from . import MODEL
|
44
45
|
|
45
46
|
_LOOKUPS = {
|
46
47
|
"crop": "IPCC_LAND_USE_CATEGORY",
|
@@ -555,7 +556,7 @@ def should_run(site: dict) -> tuple[bool, dict, dict]:
|
|
555
556
|
)
|
556
557
|
|
557
558
|
kwargs = {
|
558
|
-
"seed": gen_seed(site),
|
559
|
+
"seed": gen_seed(site, MODEL, _TERM_ID),
|
559
560
|
"eco_climate_zone": eco_climate_zone,
|
560
561
|
"ipcc_soil_category": ipcc_soil_category,
|
561
562
|
}
|
@@ -41,6 +41,7 @@ from .organicCarbonPerHa_utils import (
|
|
41
41
|
IpccLandUseCategory, IpccManagementCategory, is_cover_crop, MIN_AREA_THRESHOLD, MIN_YIELD_THRESHOLD,
|
42
42
|
sample_constant, sample_plus_minus_uncertainty, sample_truncated_normal, STATS_DEFINITION
|
43
43
|
)
|
44
|
+
from . import MODEL
|
44
45
|
|
45
46
|
_LOOKUPS = {
|
46
47
|
"crop": "IPCC_LAND_USE_CATEGORY",
|
@@ -62,6 +63,7 @@ _ABOVE_GROUND_CROP_RESIDUE_TOTAL_TERM_ID = "aboveGroundCropResidueTotal"
|
|
62
63
|
_CARBON_CONTENT_TERM_ID = "carbonContent"
|
63
64
|
_NITROGEN_CONTENT_TERM_ID = "nitrogenContent"
|
64
65
|
_LIGNIN_CONTENT_TERM_ID = "ligninContent"
|
66
|
+
_DRY_MATTER_TERM_ID = "dryMatter"
|
65
67
|
|
66
68
|
_CROP_RESIDUE_MANAGEMENT_TERM_IDS = [
|
67
69
|
"residueIncorporated",
|
@@ -82,7 +84,8 @@ _DEFAULT_COVER_CROP_BIOMASS = 4000 # TODO: Confirm assumption, Source PAS 2050-
|
|
82
84
|
_CARBON_INPUT_PROPERTY_TERM_IDS = [
|
83
85
|
_CARBON_CONTENT_TERM_ID,
|
84
86
|
_NITROGEN_CONTENT_TERM_ID,
|
85
|
-
_LIGNIN_CONTENT_TERM_ID
|
87
|
+
_LIGNIN_CONTENT_TERM_ID,
|
88
|
+
_DRY_MATTER_TERM_ID
|
86
89
|
]
|
87
90
|
|
88
91
|
_CARBON_SOURCE_TERM_TYPES = [
|
@@ -410,7 +413,7 @@ def should_run(site: dict) -> tuple[bool, dict, dict]:
|
|
410
413
|
_compile_inventory(cycles, measurement_nodes)
|
411
414
|
if should_compile_inventory else ({}, {})
|
412
415
|
)
|
413
|
-
kwargs["seed"] = gen_seed(site)
|
416
|
+
kwargs["seed"] = gen_seed(site, MODEL, _TERM_ID)
|
414
417
|
|
415
418
|
valid_years = [year for year, group in inventory.items() if group.get(_InventoryKey.SHOULD_RUN)]
|
416
419
|
|
@@ -1380,9 +1383,13 @@ def _calc_carbon_source_ag_crop_residue(node: dict, cycle: dict) -> Union[Carbon
|
|
1380
1383
|
])
|
1381
1384
|
mass = value * max(residue_left_on_field, _MIN_RESIDUE_LEFT_ON_FIELD) / 100
|
1382
1385
|
|
1386
|
+
carbon_content, nitrogen_content, lignin_content, dry_matter = _retrieve_carbon_source_properties(node)
|
1387
|
+
|
1383
1388
|
carbon_source = CarbonSource(
|
1384
|
-
mass,
|
1385
|
-
|
1389
|
+
mass * dry_matter if dry_matter else mass,
|
1390
|
+
carbon_content / dry_matter if dry_matter else carbon_content,
|
1391
|
+
nitrogen_content / dry_matter if dry_matter else nitrogen_content,
|
1392
|
+
lignin_content / dry_matter if dry_matter else lignin_content
|
1386
1393
|
)
|
1387
1394
|
|
1388
1395
|
return carbon_source if _validate_carbon_source(carbon_source) else None
|
@@ -1390,7 +1397,7 @@ def _calc_carbon_source_ag_crop_residue(node: dict, cycle: dict) -> Union[Carbon
|
|
1390
1397
|
|
1391
1398
|
def _should_run_carbon_source_cover_crop(node: dict) -> bool:
|
1392
1399
|
"""
|
1393
|
-
Determine whether a product is a valid
|
1400
|
+
Determine whether a product is a valid cover crop carbon source.
|
1394
1401
|
|
1395
1402
|
Parameters
|
1396
1403
|
----------
|
@@ -1404,13 +1411,13 @@ def _should_run_carbon_source_cover_crop(node: dict) -> bool:
|
|
1404
1411
|
Whether the node satisfies the critera.
|
1405
1412
|
"""
|
1406
1413
|
LOOKUP = _LOOKUPS["landCover"]
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
)
|
1412
|
-
|
1413
|
-
|
1414
|
+
TARGET_LOOKUP_VALUES = IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE[IpccLandUseCategory.ANNUAL_CROPS]
|
1415
|
+
|
1416
|
+
return (
|
1417
|
+
node.get("term", {}).get("termType") in [TermTermType.LANDCOVER.value]
|
1418
|
+
and is_cover_crop(node)
|
1419
|
+
and node_lookup_match(node, LOOKUP, TARGET_LOOKUP_VALUES)
|
1420
|
+
)
|
1414
1421
|
|
1415
1422
|
|
1416
1423
|
def _calc_carbon_source_cover_crop(node: dict, *_) -> Union[CarbonSource, None]:
|
@@ -1475,15 +1482,20 @@ def _calc_carbon_source(node: dict, *_) -> Union[CarbonSource, None]:
|
|
1475
1482
|
CarbonSource | None
|
1476
1483
|
The carbon source data of the cover crop, or `None` if carbon source data incomplete.
|
1477
1484
|
"""
|
1485
|
+
mass = get_node_value(node)
|
1486
|
+
carbon_content, nitrogen_content, lignin_content, dry_matter = _retrieve_carbon_source_properties(node)
|
1487
|
+
|
1478
1488
|
carbon_source = CarbonSource(
|
1479
|
-
|
1480
|
-
|
1489
|
+
mass * dry_matter if dry_matter else mass,
|
1490
|
+
carbon_content / dry_matter if dry_matter else carbon_content,
|
1491
|
+
nitrogen_content / dry_matter if dry_matter else nitrogen_content,
|
1492
|
+
lignin_content / dry_matter if dry_matter else lignin_content
|
1481
1493
|
)
|
1482
1494
|
|
1483
1495
|
return carbon_source if _validate_carbon_source(carbon_source) else None
|
1484
1496
|
|
1485
1497
|
|
1486
|
-
def _retrieve_carbon_source_properties(node: dict) -> tuple[float, float, float]:
|
1498
|
+
def _retrieve_carbon_source_properties(node: dict) -> tuple[float, float, float, float]:
|
1487
1499
|
"""
|
1488
1500
|
Extract the carbon source properties from an input or product node or, if required, retrieve them from default
|
1489
1501
|
properties.
|
@@ -1497,12 +1509,11 @@ def _retrieve_carbon_source_properties(node: dict) -> tuple[float, float, float]
|
|
1497
1509
|
Returns
|
1498
1510
|
-------
|
1499
1511
|
tuple[float, float, float]
|
1500
|
-
`(carbon_content, nitrogen_content, lignin_content)`
|
1512
|
+
`(carbon_content, nitrogen_content, lignin_content, dry_matter)`
|
1501
1513
|
"""
|
1502
|
-
|
1514
|
+
return (
|
1503
1515
|
get_node_property(node, term_id).get("value", 0)/100 for term_id in _CARBON_INPUT_PROPERTY_TERM_IDS
|
1504
1516
|
)
|
1505
|
-
return carbon_content, nitrogen_content, lignin_content
|
1506
1517
|
|
1507
1518
|
|
1508
1519
|
def _validate_carbon_source(carbon_source: CarbonSource) -> bool:
|
@@ -323,7 +323,14 @@ def calculate_GE(values: list, REM: float, REG: float, NEwool: float, NEm_feed:
|
|
323
323
|
NEp = _sum_values(values, 'NEp')
|
324
324
|
NEg = _sum_values(values, 'NEg')
|
325
325
|
|
326
|
-
|
326
|
+
REM_factor = NEm + NEa + NEl + NEwork + NEp
|
327
|
+
REG_factor = NEg + NEwool
|
328
|
+
|
329
|
+
correction_factor = REM_factor + REG_factor
|
330
|
+
NEm_feed_corrected = NEm_feed * REM_factor/correction_factor if correction_factor != 0 else NEm_feed
|
331
|
+
NEg_feed_corrected = NEg_feed * REG_factor/correction_factor if correction_factor != 0 else NEg_feed
|
332
|
+
|
333
|
+
return ((REM_factor - NEm_feed_corrected)/REM + (REG_factor - NEg_feed_corrected)/REG) if all([REM, REG]) else 0
|
327
334
|
|
328
335
|
|
329
336
|
def calculate_meanECHHV(practices: list, **log_args) -> float:
|
hestia_earth/models/log.py
CHANGED
@@ -65,7 +65,7 @@ def logShouldRun(log_node: dict, model: str, term: Union[str, None], should_run:
|
|
65
65
|
def debugMissingLookup(lookup_name: str, row: str, row_value: str, col: str, value, **kwargs):
|
66
66
|
if value is None or value == '':
|
67
67
|
extra = (', ' + _join_args(**kwargs)) if len(kwargs.keys()) > 0 else ''
|
68
|
-
logger.
|
68
|
+
logger.warning('Missing lookup=%s, %s=%s, column=%s' + extra, lookup_name, row, row_value, col)
|
69
69
|
|
70
70
|
|
71
71
|
def logErrorRun(model: str, term: str, error: str):
|