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.
Files changed (42) hide show
  1. hestia_earth/models/cml2001Baseline/resourceUseMineralsAndMetalsDuringCycle.py +2 -1
  2. hestia_earth/models/config/Cycle.json +60 -0
  3. hestia_earth/models/config/Site.json +8 -0
  4. hestia_earth/models/hestia/excretaKgMass.py +1 -1
  5. hestia_earth/models/hestia/management.py +4 -6
  6. hestia_earth/models/hestia/pToSurfaceWaterAquacultureSystems.py +148 -0
  7. hestia_earth/models/hestia/soilMeasurement.py +1 -1
  8. hestia_earth/models/ipcc2019/ch4ToAirFloodedRice.py +8 -6
  9. hestia_earth/models/ipcc2019/ch4ToAirOrganicSoilCultivation.py +270 -0
  10. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +0 -3
  11. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +0 -3
  12. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +57 -43
  13. hestia_earth/models/ipcc2019/co2ToAirLimeHydrolysis.py +7 -5
  14. hestia_earth/models/ipcc2019/co2ToAirOrganicSoilCultivation.py +215 -0
  15. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +0 -3
  16. hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationDirect.py +161 -0
  17. hestia_earth/models/ipcc2019/organicSoilCultivation_utils.py +159 -0
  18. hestia_earth/models/mocking/search-results.json +714 -706
  19. hestia_earth/models/site/grouped_measurement.py +132 -0
  20. hestia_earth/models/utils/__init__.py +4 -3
  21. hestia_earth/models/utils/blank_node.py +38 -9
  22. hestia_earth/models/utils/constant.py +26 -20
  23. hestia_earth/models/utils/product.py +39 -1
  24. hestia_earth/models/utils/property.py +14 -6
  25. hestia_earth/models/version.py +1 -1
  26. {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.2.dist-info}/METADATA +1 -1
  27. {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.2.dist-info}/RECORD +42 -31
  28. tests/models/hestia/test_feedConversionRatio.py +2 -3
  29. tests/models/hestia/test_pToSurfaceWaterAquacultureSystems.py +56 -0
  30. tests/models/hestia/test_soilMeasurement.py +11 -19
  31. tests/models/ipcc2019/test_ch4ToAirEntericFermentation.py +2 -5
  32. tests/models/ipcc2019/test_ch4ToAirOrganicSoilCultivation.py +61 -0
  33. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +11 -9
  34. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +10 -8
  35. tests/models/ipcc2019/test_co2ToAirLimeHydrolysis.py +1 -1
  36. tests/models/ipcc2019/test_co2ToAirOrganicSoilCultivation.py +62 -0
  37. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +11 -9
  38. tests/models/ipcc2019/test_n2OToAirOrganicSoilCultivationDirect.py +61 -0
  39. tests/models/site/test_grouped_measurement.py +20 -0
  40. {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.2.dist-info}/LICENSE +0 -0
  41. {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.2.dist-info}/WHEEL +0 -0
  42. {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, Units
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",
@@ -70,7 +70,7 @@ def _run_product(cycle: dict, product_term_id: str):
70
70
  # convert to 1kg first, then apply ratio to current value
71
71
  term = download_term(product_term_id, TermTermType.EXCRETA)
72
72
  product = {
73
- 'term': term,
73
+ 'term': term or {},
74
74
  'value': [1]
75
75
  }
76
76
 
@@ -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 (lookup_value - 1 if lookup_value else None)
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": max(
169
- _gap_filled_date_obj(end_date) - timedelta(days=cycle_duration)
170
- if cycle_duration else datetime.fromtimestamp(0),
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
- return (1 + (fertiliser/1000) + (cropResidue/1000)) ** 0.59
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
  }