hestia-earth-models 0.59.7__py3-none-any.whl → 0.60.1__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.

Files changed (39) hide show
  1. hestia_earth/models/cache_sites.py +40 -16
  2. hestia_earth/models/ipcc2019/animal/__init__.py +0 -0
  3. hestia_earth/models/ipcc2019/animal/pastureGrass.py +298 -0
  4. hestia_earth/models/ipcc2019/{co2ToAirSoilCarbonStockChangeManagementChange.py → co2ToAirSoilOrganicCarbonStockChangeManagementChange.py} +2 -2
  5. hestia_earth/models/ipcc2019/pastureGrass.py +73 -447
  6. hestia_earth/models/ipcc2019/pastureGrass_utils.py +415 -0
  7. hestia_earth/models/mocking/search-results.json +215 -207
  8. hestia_earth/models/site/pre_checks/cache_geospatialDatabase.py +16 -4
  9. hestia_earth/models/utils/completeness.py +17 -14
  10. hestia_earth/models/utils/feedipedia.py +23 -23
  11. hestia_earth/models/utils/property.py +3 -1
  12. hestia_earth/models/version.py +1 -1
  13. {hestia_earth_models-0.59.7.dist-info → hestia_earth_models-0.60.1.dist-info}/LICENSE +1 -1
  14. {hestia_earth_models-0.59.7.dist-info → hestia_earth_models-0.60.1.dist-info}/METADATA +1 -1
  15. {hestia_earth_models-0.59.7.dist-info → hestia_earth_models-0.60.1.dist-info}/RECORD +39 -34
  16. tests/models/cycle/animal/input/test_properties.py +3 -1
  17. tests/models/cycle/animal/test_properties.py +4 -2
  18. tests/models/cycle/input/test_properties.py +3 -1
  19. tests/models/cycle/product/test_properties.py +2 -1
  20. tests/models/cycle/test_coldCarcassWeightPerHead.py +1 -0
  21. tests/models/cycle/test_coldDressedCarcassWeightPerHead.py +1 -0
  22. tests/models/cycle/test_energyContentLowerHeatingValue.py +1 -0
  23. tests/models/cycle/test_feedConversionRatio.py +10 -0
  24. tests/models/cycle/test_readyToCookWeightPerHead.py +1 -0
  25. tests/models/ipcc2006/test_n2OToAirCropResidueDecompositionDirect.py +4 -1
  26. tests/models/ipcc2019/animal/__init__.py +0 -0
  27. tests/models/ipcc2019/animal/test_pastureGrass.py +45 -0
  28. tests/models/ipcc2019/test_ch4ToAirEntericFermentation.py +32 -8
  29. tests/models/ipcc2019/{test_co2ToAirSoilCarbonStockChangeManagementChange.py → test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py} +1 -1
  30. tests/models/ipcc2019/test_n2OToAirCropResidueDecompositionDirect.py +6 -1
  31. tests/models/ipcc2019/test_n2OToAirInorganicFertiliserDirect.py +6 -1
  32. tests/models/ipcc2019/test_n2OToAirOrganicFertiliserDirect.py +6 -1
  33. tests/models/ipcc2019/test_pastureGrass.py +32 -8
  34. tests/models/pooreNemecek2018/test_excretaKgN.py +5 -0
  35. tests/models/pooreNemecek2018/test_excretaKgVs.py +5 -0
  36. tests/models/pooreNemecek2018/test_no3ToGroundwaterSoilFlux.py +1 -0
  37. tests/models/test_cache_sites.py +10 -7
  38. {hestia_earth_models-0.59.7.dist-info → hestia_earth_models-0.60.1.dist-info}/WHEEL +0 -0
  39. {hestia_earth_models-0.59.7.dist-info → hestia_earth_models-0.60.1.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  from functools import reduce
2
2
  from enum import Enum
3
+ from pydash.objects import merge
3
4
  from hestia_earth.utils.api import download_hestia
4
5
  from hestia_earth.utils.tools import flatten
5
6
 
@@ -64,12 +65,13 @@ def _run_values(sites: list, param_type: ParamType, rasters: list = [], vectors:
64
65
  **_cache_results(raster_results, rasters, index),
65
66
  **_cache_results(vector_results, vectors, index)
66
67
  } | ({CACHE_AREA_SIZE: area_size} if area_size is not None else {})
67
- return {
68
- **site,
69
- CACHE_KEY: (
70
- cached_value(site) | {CACHE_GEOSPATIAL_KEY: cached_data}
71
- ) | ({CACHE_YEARS_KEY: years} if years else {})
72
- }
68
+ cached_data = merge(cached_value(site, CACHE_GEOSPATIAL_KEY, {}), cached_data)
69
+ site_cache = merge(
70
+ site.get(CACHE_KEY, {}),
71
+ {CACHE_GEOSPATIAL_KEY: cached_data},
72
+ ({CACHE_YEARS_KEY: cached_value(site, CACHE_YEARS_KEY, []) + years} if years else {})
73
+ )
74
+ return merge(site, {CACHE_KEY: site_cache})
73
75
 
74
76
  return reduce(lambda prev, curr: prev + [_process_site(curr)], sites, [])
75
77
 
@@ -82,7 +84,7 @@ def _preload_regions_area_size(sites: dict):
82
84
  return {term_id: download_hestia(term_id).get('area') for term_id in region_ids}
83
85
 
84
86
 
85
- def _group_sites(sites: dict):
87
+ def _group_sites(sites: dict, check_has_cache: bool = True):
86
88
  # preload area size for all regions
87
89
  regions_area_size = _preload_regions_area_size(sites)
88
90
 
@@ -90,7 +92,7 @@ def _group_sites(sites: dict):
90
92
  return regions_area_size.get(_site_gadm_id(site)) if _should_preload_region_area_size(site) else None
91
93
 
92
94
  sites = [
93
- (n, ) + (_should_run(n, get_region_area_size(n))) for n in sites
95
+ (n, ) + (_should_run(n, area_size=get_region_area_size(n), check_has_cache=check_has_cache)) for n in sites
94
96
  ]
95
97
  # restrict sites based on should_cache result
96
98
  sites = [(site, area_size) for site, should_cache, area_size in sites if should_cache]
@@ -112,6 +114,24 @@ def _group_sites(sites: dict):
112
114
  }
113
115
 
114
116
 
117
+ def _run(sites: list, years: list, include_region: bool, years_only: bool = False):
118
+ rasters, vectors = list_collections(years, include_region, years_only)
119
+ filtered_data = _group_sites(sites, not years_only)
120
+ return flatten([
121
+ _run_values(filtered_data.get(param_type), param_type, rasters, vectors, years)
122
+ for param_type in [e for e in ParamType] if len(filtered_data.get(param_type)) > 0
123
+ ])
124
+
125
+
126
+ def _group_years(years: list, years_range: int):
127
+ batches = sorted(list(set(list(range(years[0], years[-1] + 1, years_range)) + [years[0], years[-1]])))
128
+ grouped_batches = [batches[i:i+2] for i in range(0, len(batches))]
129
+ return [
130
+ # make sure we don't overlap
131
+ [v[0] + (0 if v[0] == years[0] else 1), v[1]] for v in grouped_batches if len(v) == 2
132
+ ]
133
+
134
+
115
135
  def run(sites: list, years: list = None, include_region: bool = False):
116
136
  """
117
137
  Run all queries at once for the list of provided Sites.
@@ -127,11 +147,15 @@ def run(sites: list, years: list = None, include_region: bool = False):
127
147
  Prefecth region IDs.
128
148
  This will cache region-level data and will make the request slower. Only use if needed.
129
149
  """
130
- rasters, vectors = list_collections(years, include_region)
131
-
132
- filtered_data = _group_sites(sites)
133
-
134
- return flatten([
135
- _run_values(filtered_data.get(param_type), param_type, rasters, vectors, years)
136
- for param_type in [e for e in ParamType] if len(filtered_data.get(param_type)) > 0
137
- ])
150
+ try:
151
+ return _run(sites, years, include_region)
152
+ except Exception as e:
153
+ # when querying with multiple years, we can reach a compute memory limit, so run the years separately
154
+ if str(e) == 'User memory limit exceeded.' and years:
155
+ sites = _run(sites, [], include_region)
156
+ # query for subranges
157
+ for sub_years in _group_years(years, years_range=5):
158
+ sites = _run(sites, sub_years, include_region, years_only=True)
159
+ return sites
160
+
161
+ return []
File without changes
@@ -0,0 +1,298 @@
1
+ """
2
+ Full Grass Consumption
3
+
4
+ This model estimates the energetic requirements of ruminants and can be used to estimate the amount of grass they graze.
5
+ Source:
6
+ [IPCC 2019, Vol.4, Chapter 10](https://www.ipcc-nggip.iges.or.jp/public/2019rf/pdf/4_Volume4/19R_V4_Ch10_Livestock.pdf).
7
+ """
8
+ from hestia_earth.schema import TermTermType
9
+ from hestia_earth.utils.model import filter_list_term_type
10
+ from hestia_earth.utils.tools import list_sum
11
+
12
+ from hestia_earth.models.log import logRequirements, logShouldRun, debugValues
13
+ from hestia_earth.models.utils.input import _new_input
14
+ from hestia_earth.models.utils.term import get_wool_terms, get_lookup_value
15
+ from hestia_earth.models.utils.completeness import _is_term_type_complete, _is_term_type_incomplete
16
+ from hestia_earth.models.utils.property import get_node_property
17
+ from .. import MODEL
18
+ from ..pastureGrass_utils import (
19
+ practice_input_id,
20
+ should_run_practice,
21
+ calculate_meanDE,
22
+ calculate_meanECHHV,
23
+ calculate_REM,
24
+ calculate_REG,
25
+ calculate_NEfeed,
26
+ product_wool_energy,
27
+ get_animals,
28
+ get_animal_values
29
+ )
30
+
31
+ REQUIREMENTS = {
32
+ "Cycle": {
33
+ "completeness.animalFeed": "True",
34
+ "completeness.animalPopulation": "True",
35
+ "completeness.freshForage": "False",
36
+ "site": {
37
+ "@type": "Site",
38
+ "siteType": "permanent pasture"
39
+ },
40
+ "practices": [{
41
+ "@type": "Practice",
42
+ "value": "",
43
+ "term.@id": "pastureGrass",
44
+ "key": {
45
+ "@type": "Term",
46
+ "term.termType": "landCover"
47
+ }
48
+ }],
49
+ "animals": [{
50
+ "@type": "Animal",
51
+ "value": "> 0",
52
+ "term.termType": "liveAnimal",
53
+ "referencePeriod": "average",
54
+ "properties": [{
55
+ "@type": "Property",
56
+ "value": "",
57
+ "term.@id": [
58
+ "liveweightPerHead",
59
+ "weightAtMaturity"
60
+ ]
61
+ }],
62
+ "optional": {
63
+ "properties": [{
64
+ "@type": "Property",
65
+ "value": "",
66
+ "term.@id": [
67
+ "hoursWorkedPerDay",
68
+ "pregnancyRateTotal",
69
+ "animalsPerBirth"
70
+ ]
71
+ }],
72
+ "inputs": [{
73
+ "@type": "Input",
74
+ "term.units": "kg",
75
+ "value": "> 0",
76
+ "optional": {
77
+ "properties": [{
78
+ "@type": "Property",
79
+ "value": "",
80
+ "term.@id": ["neutralDetergentFibreContent", "energyContentHigherHeatingValue"]
81
+ }]
82
+ }
83
+ }],
84
+ "practices": [{
85
+ "@type": "Practice",
86
+ "value": "",
87
+ "term.termType": "animalManagement",
88
+ "properties": [{
89
+ "@type": "Property",
90
+ "value": "",
91
+ "term.@id": "fatContent"
92
+ }]
93
+ }]
94
+ }
95
+ }],
96
+ "none": {
97
+ "inputs": [{
98
+ "@type": "Input",
99
+ "term.units": "kg",
100
+ "value": "> 0",
101
+ "isAnimalFeed": "True"
102
+ }]
103
+ },
104
+ "optional": {
105
+ "products": [{
106
+ "@type": "Product",
107
+ "value": "",
108
+ "term.@id": "animalProduct"
109
+ }]
110
+ }
111
+ }
112
+ }
113
+ LOOKUPS = {
114
+ "animalManagement": [
115
+ "mjKgEvMilkIpcc2019"
116
+ ],
117
+ "animalProduct": ["mjKgEvWoolNetEnergyWoolIpcc2019", "allowedLiveAnimalTermIds"],
118
+ "liveAnimal": [
119
+ "ipcc2019AnimalTypeGrouping",
120
+ "mjDayKgCfiNetEnergyMaintenanceIpcc2019",
121
+ "ratioCPregnancyNetEnergyPregnancyIpcc2019",
122
+ "ratioCNetEnergyGrowthCattleBuffaloIpcc2019",
123
+ "mjKgABNetEnergyGrowthSheepGoatsIpcc2019",
124
+ "isWoolProducingAnimal"
125
+ ],
126
+ "system-liveAnimal-activityCoefficient-ipcc2019": "using animal term @id",
127
+ "crop-property": ["energyDigestibilityRuminants", "energyContentHigherHeatingValue"],
128
+ "crop": "grazedPastureGrassInputId",
129
+ "forage-property": ["energyDigestibilityRuminants", "energyContentHigherHeatingValue"],
130
+ "landCover": "grazedPastureGrassInputId"
131
+ }
132
+ RETURNS = {
133
+ "Animal": [{
134
+ "Input": [{
135
+ "@type": "Input",
136
+ "term.termType": ["crop", "forage"],
137
+ "value": ""
138
+ }]
139
+ }]
140
+ }
141
+ MODEL_KEY = 'animal/pastureGrass'
142
+
143
+
144
+ def _input(term_id: str, value: float):
145
+ node = _new_input(term_id, MODEL)
146
+ node['value'] = [value]
147
+ return node
148
+
149
+
150
+ def _sum_liveWeightPerHead(animals: list):
151
+ return list_sum([
152
+ animal.get('value', 0) * get_node_property(animal, 'liveweightPerHead', False).get('value', 0)
153
+ for animal in animals
154
+ ])
155
+
156
+
157
+ def _isNEwool_animal(animal: dict):
158
+ value = get_lookup_value(animal.get('term', {}), 'isWoolProducingAnimal', model=MODEL, model_key=MODEL_KEY)
159
+ return not (not value)
160
+
161
+
162
+ def _is_NEwool_product(product: dict, animal: dict):
163
+ animal_term_ids = get_lookup_value(product, 'allowedLiveAnimalTermIds', model=MODEL, model_key=MODEL_KEY).split(';')
164
+ return animal.get('term', {}).get('@id') in animal_term_ids
165
+
166
+
167
+ def calculate_NEwool(cycle: dict, animal: dict, products: list, total_weight: float) -> float:
168
+ term_id = animal.get('term', {}).get('@id')
169
+
170
+ wool_products = [p for p in products if _is_NEwool_product(p.get('term', {}), animal)]
171
+ total_energy = list_sum([
172
+ list_sum(product.get('value', [])) * product_wool_energy(product) for product in wool_products
173
+ ])
174
+ animal_weight = _sum_liveWeightPerHead([animal])
175
+
176
+ debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
177
+ total_energy=total_energy,
178
+ animal_liveWeightPerHead=animal_weight,
179
+ total_liveWeightPerHead=total_weight)
180
+
181
+ return total_energy * animal_weight/total_weight
182
+
183
+
184
+ def _calculate_GE(
185
+ cycle: dict, animal: dict, REM: float, REG: float, NEwool: float, system: dict
186
+ ) -> float:
187
+ term_id = animal.get('term', {}).get('@id')
188
+
189
+ NEm, NEa, NEl, NEwork, NEp, NEg = get_animal_values(cycle, animal, system)
190
+
191
+ NEm_feed, NEg_feed = calculate_NEfeed(animal)
192
+ debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
193
+ NEm_feed=NEm_feed,
194
+ NEg_feed=NEg_feed)
195
+
196
+ return (NEm + NEa + NEl + NEwork + NEp - NEm_feed)/REM + (NEg + NEwool - NEg_feed)/REG
197
+
198
+
199
+ def _run_practice(cycle: dict, GE: float, meanECHHV: float):
200
+ def run(practice: dict):
201
+ key = practice.get('key', {})
202
+ key_id = key.get('@id')
203
+ input_term_id = practice_input_id(practice)
204
+ value = (GE / meanECHHV) * (list_sum(practice.get('value', [0])) / 100)
205
+
206
+ logRequirements(cycle, model=MODEL, term=input_term_id, model_key=MODEL_KEY,
207
+ GE=GE,
208
+ meanECHHV=meanECHHV,
209
+ key_id=key_id)
210
+
211
+ logShouldRun(cycle, MODEL, input_term_id, True, model_key=MODEL_KEY)
212
+
213
+ return _input(input_term_id, value)
214
+
215
+ return run
216
+
217
+
218
+ def _run_animal(cycle: dict, meanDE: float, meanECHHV: float, system: dict, practices: list):
219
+ REM = calculate_REM(meanDE)
220
+ REG = calculate_REG(meanDE)
221
+
222
+ wool_term_ids = get_wool_terms()
223
+ # list of animal product
224
+ wool_products = [p for p in cycle.get('products', []) if p.get('term', {}).get('@id') in wool_term_ids]
225
+ animals = list(filter(_isNEwool_animal, cycle.get('animals', [])))
226
+ total_liveWeightPerHead = _sum_liveWeightPerHead(animals)
227
+
228
+ def run(animal: dict):
229
+ term_id = animal.get('term', {}).get('@id')
230
+
231
+ NEwool = calculate_NEwool(cycle, animal, wool_products, total_liveWeightPerHead) if (
232
+ total_liveWeightPerHead > 0
233
+ ) else 0
234
+ GE = (_calculate_GE(cycle, animal, REM, REG, NEwool, system) / (meanDE/100)) if all([REM, REG]) else 0
235
+
236
+ debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
237
+ REM=REM,
238
+ REG=REG,
239
+ NEwool=NEwool,
240
+ GE=GE,
241
+ meanDE=meanDE)
242
+
243
+ inputs = list(map(_run_practice(cycle, GE, meanECHHV), practices))
244
+ return animal | {
245
+ 'inputs': animal.get('inputs', []) + inputs
246
+ }
247
+
248
+ return run
249
+
250
+
251
+ def _run(cycle: dict, meanDE: float, meanECHHV: float, system: dict, practices: list):
252
+ animals = get_animals(cycle)
253
+ return list(map(_run_animal(cycle, meanDE, meanECHHV, system, practices), animals))
254
+
255
+
256
+ def _should_run(cycle: dict, practices: dict):
257
+ systems = filter_list_term_type(cycle.get('practices', []), TermTermType.SYSTEM)
258
+ animalFeed_complete = _is_term_type_complete(cycle, 'animalFeed')
259
+ animalPopulation_complete = _is_term_type_complete(cycle, 'animalPopulation')
260
+ freshForage_incomplete = _is_term_type_incomplete(cycle, 'freshForage')
261
+ all_animals_have_value = all([a.get('value', 0) > 0 for a in cycle.get('animals', [])])
262
+
263
+ no_cycle_inputs_feed = all([not input.get('isAnimalFeed', False) for input in cycle.get('inputs', [])])
264
+
265
+ meanDE = calculate_meanDE(practices)
266
+ meanECHHV = calculate_meanECHHV(practices)
267
+
268
+ should_run = all([
269
+ animalFeed_complete,
270
+ animalPopulation_complete,
271
+ freshForage_incomplete,
272
+ no_cycle_inputs_feed,
273
+ all_animals_have_value,
274
+ len(systems) > 0,
275
+ len(practices) > 0,
276
+ meanDE > 0,
277
+ meanECHHV > 0
278
+ ])
279
+
280
+ for term_id in [MODEL_KEY] + [practice_input_id(p) for p in practices]:
281
+ logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
282
+ term_type_animalFeed_complete=animalFeed_complete,
283
+ term_type_animalPopulation_complete=animalPopulation_complete,
284
+ term_type_freshForage_incomplete=freshForage_incomplete,
285
+ no_cycle_inputs_feed=no_cycle_inputs_feed,
286
+ all_animals_have_value=all_animals_have_value,
287
+ meanDE=meanDE,
288
+ meanECHHV=meanECHHV)
289
+
290
+ logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
291
+
292
+ return should_run, meanDE, meanECHHV, systems[0] if systems else None
293
+
294
+
295
+ def run(cycle: dict):
296
+ practices = list(filter(should_run_practice(cycle), cycle.get('practices', [])))
297
+ should_run, meanDE, meanECHHV, system = _should_run(cycle, practices)
298
+ return _run(cycle, meanDE, meanECHHV, system, practices) if should_run else []
@@ -50,7 +50,7 @@ RETURNS = {
50
50
  "depth": "30"
51
51
  }]
52
52
  }
53
- TERM_ID = 'co2ToAirSoilCarbonStockChangeManagementChange'
53
+ TERM_ID = 'co2ToAirSoilOrganicCarbonStockChangeManagementChange'
54
54
 
55
55
  DEPTH_UPPER = 0
56
56
  DEPTH_LOWER = 30
@@ -101,7 +101,7 @@ MEASUREMENT_METHOD_RANKING = [
101
101
  ]
102
102
  """
103
103
  A ranking of `MeasurementMethodClassification`s from weakest to strongest used to determine the `EmissionMethodTier` of
104
- the `co2ToAirSoilCarbonStockChangeManagementChange` output.
104
+ the `co2ToAirSoilOrganicCarbonStockChangeManagementChange` output.
105
105
 
106
106
  The `EmissionMethodTier` should be based on the weakest `MeasurementMethodClassification` between the current SOC and
107
107
  previous SOC.