hestia-earth-models 0.70.1__py3-none-any.whl → 0.70.2__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/cml2001Baseline/resourceUseMineralsAndMetalsDuringCycle.py +2 -1
- hestia_earth/models/config/Cycle.json +60 -0
- hestia_earth/models/config/Site.json +8 -0
- hestia_earth/models/hestia/excretaKgMass.py +1 -1
- hestia_earth/models/hestia/management.py +4 -6
- hestia_earth/models/hestia/pToSurfaceWaterAquacultureSystems.py +148 -0
- hestia_earth/models/hestia/soilMeasurement.py +1 -1
- hestia_earth/models/ipcc2019/ch4ToAirFloodedRice.py +8 -6
- hestia_earth/models/ipcc2019/ch4ToAirOrganicSoilCultivation.py +270 -0
- hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +0 -3
- hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +0 -3
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +57 -43
- hestia_earth/models/ipcc2019/co2ToAirLimeHydrolysis.py +7 -5
- hestia_earth/models/ipcc2019/co2ToAirOrganicSoilCultivation.py +215 -0
- hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +0 -3
- hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationDirect.py +161 -0
- hestia_earth/models/ipcc2019/organicSoilCultivation_utils.py +159 -0
- hestia_earth/models/mocking/search-results.json +714 -706
- hestia_earth/models/site/grouped_measurement.py +132 -0
- hestia_earth/models/utils/__init__.py +4 -3
- hestia_earth/models/utils/blank_node.py +38 -9
- hestia_earth/models/utils/constant.py +26 -20
- hestia_earth/models/utils/product.py +39 -1
- hestia_earth/models/utils/property.py +14 -6
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.2.dist-info}/METADATA +1 -1
- {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.2.dist-info}/RECORD +42 -31
- tests/models/hestia/test_feedConversionRatio.py +2 -3
- tests/models/hestia/test_pToSurfaceWaterAquacultureSystems.py +56 -0
- tests/models/hestia/test_soilMeasurement.py +11 -19
- tests/models/ipcc2019/test_ch4ToAirEntericFermentation.py +2 -5
- tests/models/ipcc2019/test_ch4ToAirOrganicSoilCultivation.py +61 -0
- tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +11 -9
- tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +10 -8
- tests/models/ipcc2019/test_co2ToAirLimeHydrolysis.py +1 -1
- tests/models/ipcc2019/test_co2ToAirOrganicSoilCultivation.py +62 -0
- tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +11 -9
- tests/models/ipcc2019/test_n2OToAirOrganicSoilCultivationDirect.py +61 -0
- tests/models/site/test_grouped_measurement.py +20 -0
- {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.2.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.2.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.2.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,8 @@ from hestia_earth.utils.model import filter_list_term_type
|
|
6
6
|
from hestia_earth.utils.tools import list_sum
|
7
7
|
|
8
8
|
from hestia_earth.models.log import logRequirements, logShouldRun
|
9
|
-
from hestia_earth.models.utils import _filter_list_term_unit
|
9
|
+
from hestia_earth.models.utils import _filter_list_term_unit
|
10
|
+
from hestia_earth.models.utils.constant import Units
|
10
11
|
from hestia_earth.models.utils.blank_node import _sum_nodes_value
|
11
12
|
from hestia_earth.models.utils.indicator import _new_indicator
|
12
13
|
from . import MODEL
|
@@ -1759,6 +1759,51 @@
|
|
1759
1759
|
},
|
1760
1760
|
"stage": 2
|
1761
1761
|
},
|
1762
|
+
{
|
1763
|
+
"key": "emissions",
|
1764
|
+
"model": "ipcc2019",
|
1765
|
+
"value": "ch4ToAirOrganicSoilCultivation",
|
1766
|
+
"runStrategy": "add_blank_node_if_missing",
|
1767
|
+
"runArgs": {
|
1768
|
+
"runNonMeasured": true,
|
1769
|
+
"runNonAddedTerm": true
|
1770
|
+
},
|
1771
|
+
"mergeStrategy": "list",
|
1772
|
+
"mergeArgs": {
|
1773
|
+
"replaceThreshold": ["value", 0.01]
|
1774
|
+
},
|
1775
|
+
"stage": 2
|
1776
|
+
},
|
1777
|
+
{
|
1778
|
+
"key": "emissions",
|
1779
|
+
"model": "ipcc2019",
|
1780
|
+
"value": "co2ToAirOrganicSoilCultivation",
|
1781
|
+
"runStrategy": "add_blank_node_if_missing",
|
1782
|
+
"runArgs": {
|
1783
|
+
"runNonMeasured": true,
|
1784
|
+
"runNonAddedTerm": true
|
1785
|
+
},
|
1786
|
+
"mergeStrategy": "list",
|
1787
|
+
"mergeArgs": {
|
1788
|
+
"replaceThreshold": ["value", 0.01]
|
1789
|
+
},
|
1790
|
+
"stage": 2
|
1791
|
+
},
|
1792
|
+
{
|
1793
|
+
"key": "emissions",
|
1794
|
+
"model": "ipcc2019",
|
1795
|
+
"value": "n2OToAirOrganicSoilCultivationDirect",
|
1796
|
+
"runStrategy": "add_blank_node_if_missing",
|
1797
|
+
"runArgs": {
|
1798
|
+
"runNonMeasured": true,
|
1799
|
+
"runNonAddedTerm": true
|
1800
|
+
},
|
1801
|
+
"mergeStrategy": "list",
|
1802
|
+
"mergeArgs": {
|
1803
|
+
"replaceThreshold": ["value", 0.01]
|
1804
|
+
},
|
1805
|
+
"stage": 2
|
1806
|
+
},
|
1762
1807
|
{
|
1763
1808
|
"key": "emissions",
|
1764
1809
|
"model": "ipcc2019",
|
@@ -2178,6 +2223,21 @@
|
|
2178
2223
|
"replaceThreshold": ["value", 0.01]
|
2179
2224
|
},
|
2180
2225
|
"stage": 2
|
2226
|
+
},
|
2227
|
+
{
|
2228
|
+
"key": "emissions",
|
2229
|
+
"model": "hestia",
|
2230
|
+
"value": "pToSurfaceWaterAquacultureSystems",
|
2231
|
+
"runStrategy": "add_blank_node_if_missing",
|
2232
|
+
"runArgs": {
|
2233
|
+
"runNonMeasured": true,
|
2234
|
+
"runNonAddedTerm": true
|
2235
|
+
},
|
2236
|
+
"mergeStrategy": "list",
|
2237
|
+
"mergeArgs": {
|
2238
|
+
"replaceThreshold": ["value", 0.01]
|
2239
|
+
},
|
2240
|
+
"stage": 2
|
2181
2241
|
}
|
2182
2242
|
],
|
2183
2243
|
{
|
@@ -400,6 +400,14 @@
|
|
400
400
|
"mergeStrategy": "list",
|
401
401
|
"stage": 1
|
402
402
|
},
|
403
|
+
{
|
404
|
+
"key": "measurements",
|
405
|
+
"model": "site",
|
406
|
+
"value": "grouped_measurement",
|
407
|
+
"runStrategy": "always",
|
408
|
+
"mergeStrategy": "list",
|
409
|
+
"stage": 1
|
410
|
+
},
|
403
411
|
{
|
404
412
|
"key": "measurements",
|
405
413
|
"model": "hestia",
|
@@ -147,7 +147,7 @@ def _get_cycle_duration(cycle: dict, land_cover_id: str):
|
|
147
147
|
land_cover_id,
|
148
148
|
column_name('maximumCycleDuration')
|
149
149
|
), default=None)
|
150
|
-
return cycle_duration or
|
150
|
+
return cycle_duration or lookup_value
|
151
151
|
|
152
152
|
|
153
153
|
def _gap_filled_date_only_str(date_str: str, mode: str = DatestrGapfillMode.END) -> str:
|
@@ -165,11 +165,9 @@ def _gap_filled_start_date(land_cover_id: str, end_date: str, cycle: dict) -> di
|
|
165
165
|
"""If possible, gap-fill the startDate based on the endDate - cycleDuration"""
|
166
166
|
cycle_duration = _get_cycle_duration(cycle, land_cover_id)
|
167
167
|
return {
|
168
|
-
"startDate":
|
169
|
-
_gap_filled_date_obj(
|
170
|
-
|
171
|
-
_gap_filled_date_obj(cycle.get("startDate"), mode=DatestrGapfillMode.START)
|
172
|
-
if cycle.get("startDate") else datetime.fromtimestamp(0)
|
168
|
+
"startDate": (
|
169
|
+
_gap_filled_date_obj(cycle.get("startDate"), mode=DatestrGapfillMode.START) if cycle.get("startDate") else
|
170
|
+
_gap_filled_date_obj(end_date) - timedelta(days=cycle_duration - 1)
|
173
171
|
)
|
174
172
|
} if any([cycle_duration, cycle.get("startDate")]) else {}
|
175
173
|
|
@@ -0,0 +1,148 @@
|
|
1
|
+
from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition, TermTermType
|
2
|
+
from hestia_earth.utils.model import filter_list_term_type
|
3
|
+
from hestia_earth.utils.tools import list_sum, non_empty_list
|
4
|
+
|
5
|
+
from hestia_earth.models.log import logRequirements, logShouldRun, debugValues, log_as_table
|
6
|
+
from hestia_earth.models.utils import _filter_list_term_unit
|
7
|
+
from hestia_earth.models.utils.emission import _new_emission
|
8
|
+
from hestia_earth.models.utils.constant import Units, convert_to_unit, get_atomic_conversion
|
9
|
+
from hestia_earth.models.utils.product import convert_product_to_unit
|
10
|
+
from hestia_earth.models.utils.completeness import _is_term_type_complete
|
11
|
+
from . import MODEL
|
12
|
+
|
13
|
+
REQUIREMENTS = {
|
14
|
+
"Cycle": {
|
15
|
+
"completeness.animalFeed": "True",
|
16
|
+
"completeness.product": "True",
|
17
|
+
"completeness.fertiliser": "True",
|
18
|
+
"completeness.liveAnimalInput": "True",
|
19
|
+
"inputs": [
|
20
|
+
{
|
21
|
+
"@type": "Input",
|
22
|
+
"term.termType": [
|
23
|
+
"crop",
|
24
|
+
"liveAnimal",
|
25
|
+
"liveAquaticSpecies",
|
26
|
+
"animalProduct",
|
27
|
+
"processedFood",
|
28
|
+
"feedFoodAdditive",
|
29
|
+
"organicFertiliser",
|
30
|
+
"forage",
|
31
|
+
"excreta",
|
32
|
+
"waste"
|
33
|
+
],
|
34
|
+
"optional": {
|
35
|
+
"properties": [
|
36
|
+
{"@type": "Property", "value": "", "term.@id": "phosphorusContentAsP"},
|
37
|
+
{"@type": "Property", "value": "", "term.@id": "phosphateContentAsP2O5"}
|
38
|
+
]
|
39
|
+
}
|
40
|
+
},
|
41
|
+
{
|
42
|
+
"@type": "Input",
|
43
|
+
"term.termType": "inorganicFertiliser",
|
44
|
+
"term.units": "kg P2O5"
|
45
|
+
}
|
46
|
+
],
|
47
|
+
"products": [{
|
48
|
+
"@type": "Product",
|
49
|
+
"term.termType": ["liveAquaticSpecies", "liveAnimal", "animalProduct", "crop"],
|
50
|
+
"optional": {
|
51
|
+
"properties": [
|
52
|
+
{"@type": "Property", "value": "", "term.@id": "phosphorusContentAsP"},
|
53
|
+
{"@type": "Property", "value": "", "term.@id": "phosphateContentAsP2O5"}
|
54
|
+
]
|
55
|
+
}
|
56
|
+
}]
|
57
|
+
}
|
58
|
+
}
|
59
|
+
RETURNS = {
|
60
|
+
"Emission": [{
|
61
|
+
"value": "",
|
62
|
+
"min": "",
|
63
|
+
"max": "",
|
64
|
+
"methodTier": "tier 1",
|
65
|
+
"statsDefinition": "modelled"
|
66
|
+
}]
|
67
|
+
}
|
68
|
+
TERM_ID = 'pToSurfaceWaterAquacultureSystems'
|
69
|
+
TIER = EmissionMethodTier.TIER_1.value
|
70
|
+
|
71
|
+
|
72
|
+
def _emission(value: float):
|
73
|
+
emission = _new_emission(TERM_ID, MODEL)
|
74
|
+
emission['value'] = [value]
|
75
|
+
emission['statsDefinition'] = EmissionStatsDefinition.MODELLED.value
|
76
|
+
emission['methodTier'] = TIER
|
77
|
+
return emission
|
78
|
+
|
79
|
+
|
80
|
+
def _convert_to_P(blank_node: dict):
|
81
|
+
return {
|
82
|
+
'id': blank_node.get('term', {}).get('@id'),
|
83
|
+
'value': list_sum(blank_node.get('value'), default=None),
|
84
|
+
'value-converted': (
|
85
|
+
convert_product_to_unit(blank_node, Units.KG_P) or
|
86
|
+
convert_product_to_unit(blank_node, Units.KG_P2O5) / get_atomic_conversion(Units.KG_P2O5, Units.KG_P) or
|
87
|
+
convert_to_unit(blank_node, Units.KG_P)
|
88
|
+
)
|
89
|
+
}
|
90
|
+
|
91
|
+
|
92
|
+
def _run(cycle: dict):
|
93
|
+
inputs = filter_list_term_type(cycle.get('inputs', []), [
|
94
|
+
TermTermType.CROP,
|
95
|
+
TermTermType.LIVEANIMAL,
|
96
|
+
TermTermType.LIVEAQUATICSPECIES,
|
97
|
+
TermTermType.ANIMALPRODUCT,
|
98
|
+
TermTermType.PROCESSEDFOOD,
|
99
|
+
TermTermType.FEEDFOODADDITIVE,
|
100
|
+
TermTermType.ORGANICFERTILISER,
|
101
|
+
TermTermType.FORAGE,
|
102
|
+
TermTermType.EXCRETA,
|
103
|
+
TermTermType.WASTE,
|
104
|
+
]) + _filter_list_term_unit(filter_list_term_type(cycle.get('inputs', []), [
|
105
|
+
TermTermType.INORGANICFERTILISER
|
106
|
+
]), Units.KG_P2O5)
|
107
|
+
inputs_P = list(map(_convert_to_P, inputs))
|
108
|
+
total_inputs_P = list_sum(non_empty_list([i.get('value-converted', 0) for i in inputs_P]))
|
109
|
+
|
110
|
+
products = filter_list_term_type(cycle.get('products', []), [
|
111
|
+
TermTermType.CROP,
|
112
|
+
TermTermType.LIVEANIMAL,
|
113
|
+
TermTermType.LIVEAQUATICSPECIES,
|
114
|
+
TermTermType.ANIMALPRODUCT
|
115
|
+
])
|
116
|
+
products_P = list(map(_convert_to_P, products))
|
117
|
+
total_products_P = list_sum(non_empty_list([i.get('value-converted', 0) for i in products_P]))
|
118
|
+
|
119
|
+
debugValues(cycle, model=MODEL, term=TERM_ID,
|
120
|
+
inputs_P=log_as_table(inputs_P),
|
121
|
+
products_P=log_as_table(products_P))
|
122
|
+
|
123
|
+
return [_emission(total_inputs_P - total_products_P)]
|
124
|
+
|
125
|
+
|
126
|
+
def _should_run(cycle: dict):
|
127
|
+
is_animalFeed_complete = _is_term_type_complete(cycle, 'animalFeed')
|
128
|
+
is_product_complete = _is_term_type_complete(cycle, 'product')
|
129
|
+
is_fertiliser_complete = _is_term_type_complete(cycle, 'fertiliser')
|
130
|
+
is_liveAnimalInput_complete = _is_term_type_complete(cycle, 'liveAnimalInput')
|
131
|
+
|
132
|
+
logRequirements(cycle, model=MODEL, term=TERM_ID,
|
133
|
+
is_term_type_animalFeed_complete=is_animalFeed_complete,
|
134
|
+
is_term_type_product_complete=is_product_complete,
|
135
|
+
is_term_type_fertiliser_complete=is_fertiliser_complete,
|
136
|
+
is_term_type_liveAnimalInput_complete=is_liveAnimalInput_complete)
|
137
|
+
|
138
|
+
should_run = all([
|
139
|
+
is_animalFeed_complete,
|
140
|
+
is_product_complete,
|
141
|
+
is_fertiliser_complete,
|
142
|
+
is_liveAnimalInput_complete
|
143
|
+
])
|
144
|
+
logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
|
145
|
+
return should_run
|
146
|
+
|
147
|
+
|
148
|
+
def run(cycle: dict): return _run(cycle) if _should_run(cycle) else []
|
@@ -32,7 +32,7 @@ STANDARD_DEPTHS = {(0, 30), (0, 50)}
|
|
32
32
|
|
33
33
|
|
34
34
|
def _measurement(value: float, date: str, term_id: str, standard_fields: dict):
|
35
|
-
data = _new_measurement(term_id)
|
35
|
+
data = _new_measurement(term_id, MODEL)
|
36
36
|
data["value"] = [value]
|
37
37
|
data["depthUpper"] = standard_fields["depthUpper"]
|
38
38
|
data["depthLower"] = standard_fields["depthLower"]
|
@@ -16,8 +16,10 @@ REQUIREMENTS = {
|
|
16
16
|
"Cycle": {
|
17
17
|
"practices": [
|
18
18
|
{"@type": "Practice", "value": "", "term.@id": "croppingDuration"},
|
19
|
-
{"@type": "Practice", "value": "", "term.termType": ["landUseManagement", "waterRegime"]}
|
19
|
+
{"@type": "Practice", "value": "", "term.termType": ["landUseManagement", "waterRegime"]},
|
20
|
+
{"@type": "Practice", "value": "", "term.termType": "cropResidueManagement"}
|
20
21
|
],
|
22
|
+
"products": [{"@type": "Product", "value": "", "term.@id": "aboveGroundCropResidueIncorporated"}],
|
21
23
|
"site": {
|
22
24
|
"@type": "Site",
|
23
25
|
"country": {"@type": "Term", "termType": "region"}
|
@@ -31,9 +33,7 @@ REQUIREMENTS = {
|
|
31
33
|
"term.termType": "fertiliserBrandName",
|
32
34
|
"properties": [{"@type": "Property", "value": "", "key.termType": "organicFertiliser"}]
|
33
35
|
}
|
34
|
-
]
|
35
|
-
"products": [{"@type": "Product", "value": "", "term.@id": "aboveGroundCropResidueIncorporated"}],
|
36
|
-
"practices": [{"@type": "Practice", "value": "", "term.termType": "cropResidueManagement"}]
|
36
|
+
]
|
37
37
|
}
|
38
38
|
}
|
39
39
|
}
|
@@ -160,8 +160,10 @@ def _get_fertiliser_value(cycle: dict, suffix: str = 'value'):
|
|
160
160
|
def _calculate_SFo(cycle: dict, suffix: str = 'value'):
|
161
161
|
cropResidue = _get_cropResidue_value(cycle, suffix)
|
162
162
|
fertiliser = _get_fertiliser_value(cycle, suffix)
|
163
|
-
|
164
|
-
|
163
|
+
return (1 + (fertiliser/1000) + (cropResidue/1000)) ** 0.59 if all([
|
164
|
+
fertiliser is not None,
|
165
|
+
cropResidue is not None
|
166
|
+
]) else None
|
165
167
|
|
166
168
|
|
167
169
|
def _get_practice_values(practice: dict, col: str, default=None):
|
@@ -0,0 +1,270 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import numpy.typing as npt
|
3
|
+
from typing import Callable, Union
|
4
|
+
|
5
|
+
from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition
|
6
|
+
|
7
|
+
from hestia_earth.models.log import logRequirements, logShouldRun
|
8
|
+
from hestia_earth.models.utils.array_builders import (
|
9
|
+
discrete_uniform_1d, gen_seed, normal_1d, repeat_single, triangular_1d
|
10
|
+
)
|
11
|
+
from hestia_earth.models.utils.cycle import land_occupation_per_ha
|
12
|
+
from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
|
13
|
+
from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
|
14
|
+
from hestia_earth.models.utils.emission import _new_emission
|
15
|
+
from hestia_earth.models.utils.measurement import most_relevant_measurement_value
|
16
|
+
from hestia_earth.models.utils.site import valid_site_type
|
17
|
+
|
18
|
+
from .organicSoilCultivation_utils import (
|
19
|
+
assign_ditch_category, assign_organic_soil_category, calc_emission, DitchCategory, get_ditch_frac,
|
20
|
+
get_emission_factor, OrganicSoilCategory, remap_categories, valid_eco_climate_zone
|
21
|
+
)
|
22
|
+
from . import MODEL
|
23
|
+
|
24
|
+
REQUIREMENTS = {
|
25
|
+
"Cycle": {
|
26
|
+
"or": [
|
27
|
+
{
|
28
|
+
"cycleDuration": "",
|
29
|
+
"practices": [{"@type": "Practice", "value": "", "term.@id": "longFallowRatio"}]
|
30
|
+
},
|
31
|
+
{
|
32
|
+
"@doc": "for plantations, additional properties are required",
|
33
|
+
"practices": [
|
34
|
+
{"@type": "Practice", "value": "", "term.@id": "nurseryDensity"},
|
35
|
+
{"@type": "Practice", "value": "", "term.@id": "nurseryDuration"},
|
36
|
+
{"@type": "Practice", "value": "", "term.@id": "plantationProductiveLifespan"},
|
37
|
+
{"@type": "Practice", "value": "", "term.@id": "plantationDensity"},
|
38
|
+
{"@type": "Practice", "value": "", "term.@id": "plantationLifespan"},
|
39
|
+
{"@type": "Practice", "value": "", "term.@id": "rotationDuration"}
|
40
|
+
]
|
41
|
+
}
|
42
|
+
],
|
43
|
+
"site": {
|
44
|
+
"@type": "Site",
|
45
|
+
"measurements": [
|
46
|
+
{"@type": "Measurement", "value": "", "term.@id": "histosol"},
|
47
|
+
{"@type": "Measurement", "value": "", "term.@id": "ecoClimateZone"}
|
48
|
+
]
|
49
|
+
},
|
50
|
+
"optional": {
|
51
|
+
"cycleDuration": ""
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
LOOKUPS = {
|
56
|
+
"crop": [
|
57
|
+
"isPlantation",
|
58
|
+
"IPCC_2013_ORGANIC_SOIL_CULTIVATION_CATEGORY"
|
59
|
+
],
|
60
|
+
"forage": [
|
61
|
+
"isPlantation",
|
62
|
+
"IPCC_2013_ORGANIC_SOIL_CULTIVATION_CATEGORY"
|
63
|
+
],
|
64
|
+
"ecoClimateZone": [
|
65
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CH4_HECTARE_ANNUAL_CROPS",
|
66
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CH4_HECTARE_PERENNIAL_CROPS",
|
67
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CH4_HECTARE_ACACIA",
|
68
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CH4_HECTARE_OIL_PALM",
|
69
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CH4_HECTARE_SAGO_PALM",
|
70
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CH4_HECTARE_PADDY_RICE_CULTIVATION",
|
71
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CH4_HECTARE_GRASSLAND",
|
72
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CH4_HECTARE_DITCH",
|
73
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CH4_HECTARE_OTHER",
|
74
|
+
"IPCC_2013_DRAINED_ORGANIC_SOILS_DITCH_FRAC_AGRICULTURAL_LAND",
|
75
|
+
"IPCC_2013_DRAINED_ORGANIC_SOILS_DITCH_FRAC_NETHERLANDS"
|
76
|
+
]
|
77
|
+
}
|
78
|
+
RETURNS = {
|
79
|
+
"Emission": [{
|
80
|
+
"value": "",
|
81
|
+
"sd": "",
|
82
|
+
"min": "",
|
83
|
+
"max": "",
|
84
|
+
"observations": "",
|
85
|
+
"statsDefinition": "simulated",
|
86
|
+
"methodTier": "tier 1"
|
87
|
+
}]
|
88
|
+
}
|
89
|
+
TERM_ID = 'ch4ToAirOrganicSoilCultivation'
|
90
|
+
TIER = EmissionMethodTier.TIER_1.value
|
91
|
+
|
92
|
+
_STATS_DEFINITION = EmissionStatsDefinition.SIMULATED.value
|
93
|
+
_ITERATIONS = 100000
|
94
|
+
|
95
|
+
_CATEGORY_REMAPPER = {
|
96
|
+
OrganicSoilCategory.ACACIA: OrganicSoilCategory.PERENNIAL_CROPS
|
97
|
+
}
|
98
|
+
|
99
|
+
|
100
|
+
def _emission(descriptive_stats: dict):
|
101
|
+
emission = _new_emission(TERM_ID, MODEL) | descriptive_stats
|
102
|
+
emission['methodTier'] = TIER
|
103
|
+
return emission
|
104
|
+
|
105
|
+
|
106
|
+
def sample_emission_factor(
|
107
|
+
eco_climate_zone: EcoClimateZone,
|
108
|
+
organic_soil_category: OrganicSoilCategory,
|
109
|
+
seed: Union[int, np.random.Generator, None] = None,
|
110
|
+
) -> npt.NDArray:
|
111
|
+
category = remap_categories(organic_soil_category, _CATEGORY_REMAPPER) # fewer categories than CO2 model
|
112
|
+
factor_data = get_emission_factor(TERM_ID, eco_climate_zone, category)
|
113
|
+
sample_func = _get_sample_func(factor_data)
|
114
|
+
return sample_func(iterations=_ITERATIONS, seed=seed, **factor_data)
|
115
|
+
|
116
|
+
|
117
|
+
def sample_ditch_frac(
|
118
|
+
eco_climate_zone: EcoClimateZone,
|
119
|
+
ditch_category: DitchCategory,
|
120
|
+
seed: Union[int, np.random.Generator, None] = None,
|
121
|
+
) -> npt.NDArray:
|
122
|
+
factor_data = get_ditch_frac(eco_climate_zone, ditch_category, term=TERM_ID)
|
123
|
+
sample_func = _get_sample_func(factor_data)
|
124
|
+
return sample_func(iterations=_ITERATIONS, seed=seed, **factor_data)
|
125
|
+
|
126
|
+
|
127
|
+
def _sample_normal(
|
128
|
+
*, iterations: int, value: float, sd: float, seed: Union[int, np.random.Generator, None] = None, **_
|
129
|
+
) -> npt.NDArray:
|
130
|
+
return normal_1d(shape=(1, iterations), mu=value, sigma=sd, seed=seed)
|
131
|
+
|
132
|
+
|
133
|
+
def _sample_uniform(
|
134
|
+
*, iterations: int, min: float, max: float, seed: Union[int, np.random.Generator, None] = None, **_
|
135
|
+
) -> npt.NDArray:
|
136
|
+
return discrete_uniform_1d(shape=(1, iterations), low=min, high=max, seed=seed)
|
137
|
+
|
138
|
+
|
139
|
+
def _sample_triangular(
|
140
|
+
*, iterations: int, value: float, min: float, max: float, seed: Union[int, np.random.Generator, None] = None, **_
|
141
|
+
) -> npt.NDArray:
|
142
|
+
return triangular_1d(shape=(1, iterations), mode=value, low=min, high=max, seed=seed)
|
143
|
+
|
144
|
+
|
145
|
+
def _sample_constant(*, value: float, **_) -> npt.NDArray:
|
146
|
+
"""Sample a constant model parameter."""
|
147
|
+
return repeat_single(shape=(1, 1), value=value)
|
148
|
+
|
149
|
+
|
150
|
+
_KWARGS_TO_SAMPLE_FUNC = {
|
151
|
+
("value", "sd"): _sample_normal,
|
152
|
+
("value", "min", "max"): _sample_triangular,
|
153
|
+
("min", "max"): _sample_uniform,
|
154
|
+
("value",): _sample_constant
|
155
|
+
}
|
156
|
+
"""
|
157
|
+
Mapping from available distribution data to sample function.
|
158
|
+
"""
|
159
|
+
|
160
|
+
|
161
|
+
def _get_sample_func(kwargs: dict) -> Callable:
|
162
|
+
"""
|
163
|
+
Select the correct sample function for a parameter based on the distribution data available. All possible
|
164
|
+
parameters for the model should have, at a minimum, a `value`, meaning that no default function needs to be
|
165
|
+
specified.
|
166
|
+
|
167
|
+
This function has been extracted into it's own method to allow for mocking of sample function.
|
168
|
+
|
169
|
+
Keyword Args
|
170
|
+
------------
|
171
|
+
value : float
|
172
|
+
The distribution mean.
|
173
|
+
sd : float
|
174
|
+
The standard deviation of the distribution.
|
175
|
+
se : float
|
176
|
+
The standard error of the distribution.
|
177
|
+
n : float
|
178
|
+
Sample size.
|
179
|
+
|
180
|
+
Returns
|
181
|
+
-------
|
182
|
+
Callable
|
183
|
+
The sample function for the distribution.
|
184
|
+
"""
|
185
|
+
return next(
|
186
|
+
sample_func for required_kwargs, sample_func in _KWARGS_TO_SAMPLE_FUNC.items()
|
187
|
+
if all(kwarg in kwargs.keys() for kwarg in required_kwargs)
|
188
|
+
)
|
189
|
+
|
190
|
+
|
191
|
+
def _should_run(cycle: dict):
|
192
|
+
end_date = cycle.get('endDate')
|
193
|
+
site = cycle.get('site', {})
|
194
|
+
measurements = site.get('measurements', [])
|
195
|
+
|
196
|
+
seed = gen_seed(cycle, MODEL, TERM_ID)
|
197
|
+
rng = np.random.default_rng(seed)
|
198
|
+
|
199
|
+
def _get_measurement_content(term_id: str):
|
200
|
+
return most_relevant_measurement_value(measurements, term_id, end_date)
|
201
|
+
|
202
|
+
histosol = _get_measurement_content('histosol')
|
203
|
+
eco_climate_zone = get_eco_climate_zone_value(cycle, as_enum=True)
|
204
|
+
organic_soil_category = assign_organic_soil_category(cycle, log_id=TERM_ID)
|
205
|
+
ditch_category = assign_ditch_category(cycle)
|
206
|
+
|
207
|
+
emission_factor = (
|
208
|
+
sample_emission_factor(eco_climate_zone, organic_soil_category, seed=rng) if eco_climate_zone
|
209
|
+
else None
|
210
|
+
)
|
211
|
+
ditch_factor = (
|
212
|
+
sample_emission_factor(eco_climate_zone, OrganicSoilCategory.DITCH, seed=rng) if eco_climate_zone
|
213
|
+
else None
|
214
|
+
)
|
215
|
+
ditch_frac = (
|
216
|
+
sample_ditch_frac(eco_climate_zone, ditch_category, seed=rng) if eco_climate_zone
|
217
|
+
else None
|
218
|
+
)
|
219
|
+
|
220
|
+
land_occupation = land_occupation_per_ha(MODEL, TERM_ID, cycle)
|
221
|
+
|
222
|
+
logRequirements(
|
223
|
+
cycle, model=MODEL, term=TERM_ID,
|
224
|
+
eco_climate_zone=eco_climate_zone,
|
225
|
+
organic_soil_category=organic_soil_category,
|
226
|
+
ditch_category=ditch_category,
|
227
|
+
emission_factor=f"{np.mean(emission_factor):.3f}",
|
228
|
+
ditch_factor=f"{np.mean(ditch_factor):.3f}",
|
229
|
+
ditch_frac=f"{np.mean(ditch_frac):.3f}",
|
230
|
+
land_occupation=land_occupation,
|
231
|
+
histosol=histosol
|
232
|
+
)
|
233
|
+
|
234
|
+
should_run = all([
|
235
|
+
valid_site_type(site),
|
236
|
+
valid_eco_climate_zone(eco_climate_zone),
|
237
|
+
all(
|
238
|
+
var is not None for var in [
|
239
|
+
emission_factor,
|
240
|
+
ditch_factor,
|
241
|
+
ditch_frac,
|
242
|
+
land_occupation,
|
243
|
+
histosol
|
244
|
+
]
|
245
|
+
)
|
246
|
+
])
|
247
|
+
|
248
|
+
logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
|
249
|
+
|
250
|
+
return should_run, emission_factor, ditch_factor, ditch_frac, histosol, land_occupation
|
251
|
+
|
252
|
+
|
253
|
+
def _run(
|
254
|
+
emission_factor: npt.NDArray,
|
255
|
+
ditch_factor: npt.NDArray,
|
256
|
+
ditch_frac: npt.NDArray,
|
257
|
+
histosol: float,
|
258
|
+
land_occupation: float
|
259
|
+
):
|
260
|
+
land_emission = calc_emission(TERM_ID, emission_factor, histosol, land_occupation)
|
261
|
+
ditch_emission = calc_emission(TERM_ID, ditch_factor, histosol, land_occupation)
|
262
|
+
|
263
|
+
result = (ditch_emission * ditch_frac) + (land_emission * (1 - ditch_frac))
|
264
|
+
descriptive_stats = calc_descriptive_stats(result, _STATS_DEFINITION)
|
265
|
+
return [_emission(descriptive_stats)]
|
266
|
+
|
267
|
+
|
268
|
+
def run(cycle: dict):
|
269
|
+
should_run, *args = _should_run(cycle)
|
270
|
+
return _run(*args) if should_run else []
|
@@ -186,13 +186,11 @@ def _should_compile_inventory_func(
|
|
186
186
|
) for cycle in cycles
|
187
187
|
)
|
188
188
|
|
189
|
-
has_stock_measurements = len(carbon_stock_measurements) > 0
|
190
189
|
has_cycles = len(cycles) > 0
|
191
190
|
has_functional_unit_1_ha = all(cycle.get('functionalUnit') == CycleFunctionalUnit._1_HA.value for cycle in cycles)
|
192
191
|
|
193
192
|
should_run = all([
|
194
193
|
has_soil,
|
195
|
-
has_stock_measurements,
|
196
194
|
has_cycles,
|
197
195
|
has_functional_unit_1_ha
|
198
196
|
])
|
@@ -201,7 +199,6 @@ def _should_compile_inventory_func(
|
|
201
199
|
"site_type": site_type,
|
202
200
|
"has_soil": has_soil,
|
203
201
|
"carbon_stock_term": _CARBON_STOCK_TERM_ID,
|
204
|
-
"has_stock_measurements": has_stock_measurements,
|
205
202
|
"has_cycles": has_cycles,
|
206
203
|
"has_functional_unit_1_ha": has_functional_unit_1_ha,
|
207
204
|
}
|
@@ -199,13 +199,11 @@ def _should_compile_inventory_func(
|
|
199
199
|
) for cycle in cycles
|
200
200
|
)
|
201
201
|
|
202
|
-
has_stock_measurements = len(carbon_stock_measurements) > 0
|
203
202
|
has_cycles = len(cycles) > 0
|
204
203
|
has_functional_unit_1_ha = all(cycle.get('functionalUnit') == CycleFunctionalUnit._1_HA.value for cycle in cycles)
|
205
204
|
|
206
205
|
should_run = all([
|
207
206
|
has_soil,
|
208
|
-
has_stock_measurements,
|
209
207
|
has_cycles,
|
210
208
|
has_functional_unit_1_ha
|
211
209
|
])
|
@@ -214,7 +212,6 @@ def _should_compile_inventory_func(
|
|
214
212
|
"site_type": site_type,
|
215
213
|
"has_soil": has_soil,
|
216
214
|
"carbon_stock_term": _CARBON_STOCK_TERM_ID,
|
217
|
-
"has_stock_measurements": has_stock_measurements,
|
218
215
|
"has_cycles": has_cycles,
|
219
216
|
"has_functional_unit_1_ha": has_functional_unit_1_ha,
|
220
217
|
}
|