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
@@ -319,7 +319,13 @@ def _add_carbon_stock_change_emissions(
|
|
319
319
|
value = emission_1.value + emission_2.value
|
320
320
|
start_date = min(emission_1.start_date, emission_2.start_date)
|
321
321
|
end_date = max(emission_1.end_date, emission_2.end_date)
|
322
|
-
|
322
|
+
|
323
|
+
methods = [
|
324
|
+
method for emission in (emission_1, emission_2)
|
325
|
+
if isinstance((method := emission.method), EmissionMethodTier)
|
326
|
+
]
|
327
|
+
|
328
|
+
method = min_emission_method_tier(*methods) if methods else None
|
323
329
|
|
324
330
|
return CarbonStockChangeEmission(value, start_date, end_date, method)
|
325
331
|
|
@@ -442,7 +448,6 @@ def create_should_run_function(
|
|
442
448
|
|
443
449
|
inventory, inventory_logs = (
|
444
450
|
compile_inventory_func(
|
445
|
-
cycle_id,
|
446
451
|
cycles,
|
447
452
|
carbon_stock_measurements,
|
448
453
|
land_cover_nodes
|
@@ -464,7 +469,8 @@ def create_should_run_function(
|
|
464
469
|
logs = should_compile_logs | inventory_logs | {
|
465
470
|
"seed": seed,
|
466
471
|
"has_valid_inventory": has_valid_inventory,
|
467
|
-
"has_consecutive_years": has_consecutive_years
|
472
|
+
"has_consecutive_years": has_consecutive_years,
|
473
|
+
"has_stock_measurements": bool(carbon_stock_measurements)
|
468
474
|
}
|
469
475
|
|
470
476
|
return should_run_, kwargs, logs
|
@@ -554,7 +560,6 @@ def _create_compile_inventory_function(
|
|
554
560
|
The `compile_inventory` function.
|
555
561
|
"""
|
556
562
|
def compile_inventory(
|
557
|
-
cycle_id: str,
|
558
563
|
cycles: list[dict],
|
559
564
|
carbon_stock_measurements: list[dict],
|
560
565
|
land_cover_nodes: list[dict]
|
@@ -1177,20 +1182,19 @@ def _squash_inventory(
|
|
1177
1182
|
return _InventoryKey.CO2_EMISSION in carbon_stock_inventory.get(method, {}).get(year, {}).keys()
|
1178
1183
|
|
1179
1184
|
def squash(result: dict, year: int) -> dict:
|
1180
|
-
|
1181
|
-
(
|
1182
|
-
|
1183
|
-
year: {
|
1184
|
-
**_get_land_use_change_data(year, land_use_inventory),
|
1185
|
-
**reduce(merge, [
|
1186
|
-
carbon_stock_inventory.get(method, {}).get(year, {}),
|
1187
|
-
cycle_inventory.get(year, {})
|
1188
|
-
], dict())
|
1189
|
-
}
|
1190
|
-
} for method in measurement_method_ranking if should_run_group(method, year)
|
1191
|
-
),
|
1192
|
-
{}
|
1185
|
+
method = next(
|
1186
|
+
(method for method in measurement_method_ranking if should_run_group(method, year)),
|
1187
|
+
None
|
1193
1188
|
)
|
1189
|
+
update_dict = {
|
1190
|
+
year: {
|
1191
|
+
**_get_land_use_change_data(year, land_use_inventory),
|
1192
|
+
**reduce(merge, [
|
1193
|
+
carbon_stock_inventory.get(method, {}).get(year, {}),
|
1194
|
+
cycle_inventory.get(year, {})
|
1195
|
+
], dict())
|
1196
|
+
}
|
1197
|
+
}
|
1194
1198
|
return result | update_dict
|
1195
1199
|
|
1196
1200
|
return reduce(squash, inventory_years, dict())
|
@@ -1426,36 +1430,31 @@ def create_run_function(
|
|
1426
1430
|
Assign emissions to either the land use or management change term ids and sum together.
|
1427
1431
|
"""
|
1428
1432
|
data = inventory[year]
|
1433
|
+
|
1429
1434
|
years_since_luc_event = data[_InventoryKey.YEARS_SINCE_LUC_EVENT]
|
1430
1435
|
years_since_inventory_start = data[_InventoryKey.YEARS_SINCE_INVENTORY_START]
|
1436
|
+
share_of_emission = data[_InventoryKey.SHARE_OF_EMISSION][cycle_id]
|
1437
|
+
|
1438
|
+
co2_emission = data.get(_InventoryKey.CO2_EMISSION)
|
1431
1439
|
|
1440
|
+
has_co2_emission = bool(co2_emission)
|
1432
1441
|
is_luc_emission = bool(years_since_luc_event) and years_since_luc_event <= _TRANSITION_PERIOD_YEARS
|
1433
1442
|
is_data_complete = bool(years_since_inventory_start) and years_since_inventory_start >= _TRANSITION_PERIOD_YEARS
|
1434
1443
|
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
zero_emission_term_id = management_change_emission_term_id
|
1439
|
-
elif is_data_complete:
|
1440
|
-
# If management emission && data complete allocate emissions to management change AND add corresponding
|
1441
|
-
# zero emission to management
|
1442
|
-
emission_term_id = management_change_emission_term_id
|
1443
|
-
zero_emission_term_id = land_use_change_emission_term_id
|
1444
|
-
else:
|
1445
|
-
# If management emission, but not data complete allocate emissions to management change only
|
1446
|
-
emission_term_id = management_change_emission_term_id
|
1447
|
-
zero_emission_term_id = None
|
1444
|
+
emission_term_id = (
|
1445
|
+
land_use_change_emission_term_id if is_luc_emission else management_change_emission_term_id
|
1446
|
+
) if has_co2_emission else None
|
1448
1447
|
|
1449
|
-
|
1450
|
-
|
1448
|
+
zero_emission_term_id = (
|
1449
|
+
management_change_emission_term_id if is_luc_emission else
|
1450
|
+
(land_use_change_emission_term_id if is_data_complete else None)
|
1451
1451
|
)
|
1452
1452
|
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1458
|
-
) if zero_emission_term_id else None
|
1453
|
+
rescaled_emission = _rescale_carbon_stock_change_emission(
|
1454
|
+
co2_emission, share_of_emission
|
1455
|
+
) if emission_term_id else None
|
1456
|
+
|
1457
|
+
zero_emission = get_zero_emission(year) if zero_emission_term_id else None
|
1459
1458
|
|
1460
1459
|
previous_emission = result.get(emission_term_id)
|
1461
1460
|
previous_zero_emission = result.get(zero_emission_term_id)
|
@@ -1465,7 +1464,7 @@ def create_run_function(
|
|
1465
1464
|
_add_carbon_stock_change_emissions(previous_emission, rescaled_emission) if previous_emission
|
1466
1465
|
else rescaled_emission
|
1467
1466
|
)
|
1468
|
-
}
|
1467
|
+
} if emission_term_id else {}
|
1469
1468
|
|
1470
1469
|
zero_emission_dict = {
|
1471
1470
|
zero_emission_term_id: (
|
@@ -1508,7 +1507,7 @@ def create_run_function(
|
|
1508
1507
|
return [
|
1509
1508
|
new_emission_func(
|
1510
1509
|
term_id=emission_term_id,
|
1511
|
-
method_tier=total_emission
|
1510
|
+
method_tier=_get_emission_method(total_emission),
|
1512
1511
|
start_date=_get_emission_start_date(total_emission, cycle_start_date),
|
1513
1512
|
end_date=_get_emission_end_date(total_emission, cycle_end_date),
|
1514
1513
|
**calc_descriptive_stats(
|
@@ -1517,18 +1516,33 @@ def create_run_function(
|
|
1517
1516
|
decimals=6
|
1518
1517
|
)
|
1519
1518
|
) for emission_term_id, total_emission in assigned_emissions.items()
|
1519
|
+
if isinstance(total_emission, CarbonStockChangeEmission)
|
1520
1520
|
]
|
1521
1521
|
|
1522
1522
|
return run
|
1523
1523
|
|
1524
1524
|
|
1525
|
+
def get_zero_emission(year):
|
1526
|
+
return CarbonStockChangeEmission(
|
1527
|
+
value=array(0),
|
1528
|
+
start_date=_gapfill_datestr(year),
|
1529
|
+
end_date=_gapfill_datestr(year, DatestrGapfillMode.END),
|
1530
|
+
method=None
|
1531
|
+
)
|
1532
|
+
|
1533
|
+
|
1534
|
+
def _get_emission_method(emission: CarbonStockChangeEmission):
|
1535
|
+
method = emission.method
|
1536
|
+
return method if isinstance(method, EmissionMethodTier) else EmissionMethodTier.TIER_1
|
1537
|
+
|
1538
|
+
|
1525
1539
|
def _get_emission_start_date(emission: CarbonStockChangeEmission, cycle_start_date: str) -> str:
|
1526
1540
|
cycle_datetime = safe_parse_date(_gapfill_datestr(cycle_start_date))
|
1527
1541
|
emission_datetime = safe_parse_date(emission.start_date)
|
1528
1542
|
|
1529
1543
|
should_run = (
|
1530
1544
|
cycle_datetime and emission_datetime
|
1531
|
-
and cycle_datetime
|
1545
|
+
and cycle_datetime < emission_datetime # If the cycle starts before the emission, add a `startDate`
|
1532
1546
|
)
|
1533
1547
|
|
1534
1548
|
return (
|
@@ -1538,12 +1552,12 @@ def _get_emission_start_date(emission: CarbonStockChangeEmission, cycle_start_da
|
|
1538
1552
|
|
1539
1553
|
|
1540
1554
|
def _get_emission_end_date(emission: CarbonStockChangeEmission, cycle_end_date: str) -> str:
|
1541
|
-
cycle_datetime = safe_parse_date(_gapfill_datestr(cycle_end_date))
|
1555
|
+
cycle_datetime = safe_parse_date(_gapfill_datestr(cycle_end_date, DatestrGapfillMode.END))
|
1542
1556
|
emission_datetime = safe_parse_date(emission.end_date)
|
1543
1557
|
|
1544
1558
|
should_run = (
|
1545
1559
|
cycle_datetime and emission_datetime
|
1546
|
-
and cycle_datetime
|
1560
|
+
and cycle_datetime > emission_datetime # If the cycle ends after the emission, add an `endDate`
|
1547
1561
|
)
|
1548
1562
|
|
1549
1563
|
return (
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from hestia_earth.schema import EmissionMethodTier, TermTermType
|
2
|
-
from hestia_earth.utils.tools import list_sum
|
2
|
+
from hestia_earth.utils.tools import list_sum, non_empty_list
|
3
3
|
from hestia_earth.utils.model import filter_list_term_type
|
4
4
|
|
5
5
|
from hestia_earth.models.log import logRequirements, logShouldRun
|
@@ -16,7 +16,7 @@ REQUIREMENTS = {
|
|
16
16
|
"@type": "Input",
|
17
17
|
"value": "",
|
18
18
|
"term.termType": "soilAmendment",
|
19
|
-
"term.units": ["kg CaCO3", "kg
|
19
|
+
"term.units": ["kg CaCO3", "kg CaMg(CO3)2"]
|
20
20
|
}]
|
21
21
|
}
|
22
22
|
}
|
@@ -38,8 +38,10 @@ def _emission(value: float):
|
|
38
38
|
|
39
39
|
|
40
40
|
def _get_lime_values(cycle: dict, inputs: list):
|
41
|
-
|
42
|
-
|
41
|
+
values = non_empty_list([
|
42
|
+
convert_to_unit(i, Units.KG_CO2)
|
43
|
+
for i in inputs if len(i.get('value', [])) > 0
|
44
|
+
])
|
43
45
|
return [0] if (
|
44
46
|
len(values) == 0 and _is_term_type_complete(cycle, TermTermType.SOILAMENDMENT)
|
45
47
|
) else values
|
@@ -56,7 +58,7 @@ def _run(CaCO3_values: list, MgCO3_values: list):
|
|
56
58
|
def _should_run(cycle: dict):
|
57
59
|
inputs = filter_list_term_type(cycle.get('inputs', []), TermTermType.SOILAMENDMENT)
|
58
60
|
CaCO3_values = _get_lime_values(cycle, _filter_list_term_unit(inputs, Units.KG_CACO3))
|
59
|
-
MgCO3_values = _get_lime_values(cycle, _filter_list_term_unit(inputs, Units.
|
61
|
+
MgCO3_values = _get_lime_values(cycle, _filter_list_term_unit(inputs, Units.KG_CAMGCO32))
|
60
62
|
|
61
63
|
logRequirements(cycle, model=MODEL, term=TERM_ID,
|
62
64
|
CaCO3_values=len(CaCO3_values),
|
@@ -0,0 +1,215 @@
|
|
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 gen_seed, repeat_single, truncated_normal_1d
|
9
|
+
from hestia_earth.models.utils.cycle import land_occupation_per_ha
|
10
|
+
from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
|
11
|
+
from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
|
12
|
+
from hestia_earth.models.utils.emission import _new_emission
|
13
|
+
from hestia_earth.models.utils.measurement import most_relevant_measurement_value
|
14
|
+
from hestia_earth.models.utils.site import valid_site_type
|
15
|
+
|
16
|
+
from .organicSoilCultivation_utils import (
|
17
|
+
assign_organic_soil_category, calc_emission, get_emission_factor, OrganicSoilCategory, valid_eco_climate_zone
|
18
|
+
)
|
19
|
+
from . import MODEL
|
20
|
+
|
21
|
+
REQUIREMENTS = {
|
22
|
+
"Cycle": {
|
23
|
+
"or": [
|
24
|
+
{
|
25
|
+
"cycleDuration": "",
|
26
|
+
"practices": [{"@type": "Practice", "value": "", "term.@id": "longFallowRatio"}]
|
27
|
+
},
|
28
|
+
{
|
29
|
+
"@doc": "for plantations, additional properties are required",
|
30
|
+
"practices": [
|
31
|
+
{"@type": "Practice", "value": "", "term.@id": "nurseryDensity"},
|
32
|
+
{"@type": "Practice", "value": "", "term.@id": "nurseryDuration"},
|
33
|
+
{"@type": "Practice", "value": "", "term.@id": "plantationProductiveLifespan"},
|
34
|
+
{"@type": "Practice", "value": "", "term.@id": "plantationDensity"},
|
35
|
+
{"@type": "Practice", "value": "", "term.@id": "plantationLifespan"},
|
36
|
+
{"@type": "Practice", "value": "", "term.@id": "rotationDuration"}
|
37
|
+
]
|
38
|
+
}
|
39
|
+
],
|
40
|
+
"site": {
|
41
|
+
"@type": "Site",
|
42
|
+
"measurements": [
|
43
|
+
{"@type": "Measurement", "value": "", "term.@id": "histosol"},
|
44
|
+
{"@type": "Measurement", "value": "", "term.@id": "ecoClimateZone"}
|
45
|
+
]
|
46
|
+
},
|
47
|
+
"optional": {
|
48
|
+
"cycleDuration": ""
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
LOOKUPS = {
|
53
|
+
"crop": [
|
54
|
+
"isPlantation",
|
55
|
+
"IPCC_2013_ORGANIC_SOIL_CULTIVATION_CATEGORY"
|
56
|
+
],
|
57
|
+
"forage": [
|
58
|
+
"isPlantation",
|
59
|
+
"IPCC_2013_ORGANIC_SOIL_CULTIVATION_CATEGORY"
|
60
|
+
],
|
61
|
+
"ecoClimateZone": [
|
62
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_ANNUAL_CROPS",
|
63
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_PERENNIAL_CROPS",
|
64
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_ACACIA",
|
65
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_OIL_PALM",
|
66
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_SAGO_PALM",
|
67
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_PADDY_RICE_CULTIVATION",
|
68
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_GRASSLAND",
|
69
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_OTHER"
|
70
|
+
]
|
71
|
+
}
|
72
|
+
RETURNS = {
|
73
|
+
"Emission": [{
|
74
|
+
"value": "",
|
75
|
+
"sd": "",
|
76
|
+
"min": "",
|
77
|
+
"max": "",
|
78
|
+
"observations": "",
|
79
|
+
"statsDefinition": "simulated",
|
80
|
+
"methodTier": "tier 1"
|
81
|
+
}]
|
82
|
+
}
|
83
|
+
TERM_ID = 'co2ToAirOrganicSoilCultivation'
|
84
|
+
TIER = EmissionMethodTier.TIER_1.value
|
85
|
+
|
86
|
+
_STATS_DEFINITION = EmissionStatsDefinition.SIMULATED.value
|
87
|
+
_ITERATIONS = 100000
|
88
|
+
|
89
|
+
|
90
|
+
def _emission(descriptive_stats: dict):
|
91
|
+
emission = _new_emission(TERM_ID, MODEL) | descriptive_stats
|
92
|
+
emission['methodTier'] = TIER
|
93
|
+
return emission
|
94
|
+
|
95
|
+
|
96
|
+
def sample_emission_factor(
|
97
|
+
eco_climate_zone: EcoClimateZone,
|
98
|
+
organic_soil_category: OrganicSoilCategory,
|
99
|
+
seed: Union[int, np.random.Generator, None] = None,
|
100
|
+
) -> npt.NDArray:
|
101
|
+
factor_data = get_emission_factor(TERM_ID, eco_climate_zone, organic_soil_category)
|
102
|
+
sample_func = _get_sample_func(factor_data)
|
103
|
+
return sample_func(iterations=_ITERATIONS, seed=seed, **factor_data)
|
104
|
+
|
105
|
+
|
106
|
+
def _sample_truncated_normal(
|
107
|
+
*, iterations: int, value: float, sd: float, seed: Union[int, np.random.Generator, None] = None, **_
|
108
|
+
) -> npt.NDArray:
|
109
|
+
"""
|
110
|
+
Randomly sample a model parameter with a truncated normal distribution. Emission factors annot be below 0, so
|
111
|
+
truncated normal sampling used.
|
112
|
+
"""
|
113
|
+
return truncated_normal_1d(shape=(1, iterations), mu=value, sigma=sd, low=0, high=np.inf, seed=seed)
|
114
|
+
|
115
|
+
|
116
|
+
def _sample_constant(*, value: float, **_) -> npt.NDArray:
|
117
|
+
"""Sample a constant model parameter."""
|
118
|
+
return repeat_single(shape=(1, 1), value=value)
|
119
|
+
|
120
|
+
|
121
|
+
_KWARGS_TO_SAMPLE_FUNC = {
|
122
|
+
("value", "sd"): _sample_truncated_normal,
|
123
|
+
("value",): _sample_constant
|
124
|
+
}
|
125
|
+
"""
|
126
|
+
Mapping from available distribution data to sample function.
|
127
|
+
"""
|
128
|
+
|
129
|
+
|
130
|
+
def _get_sample_func(kwargs: dict) -> Callable:
|
131
|
+
"""
|
132
|
+
Select the correct sample function for a parameter based on the distribution data available. All possible
|
133
|
+
parameters for the model should have, at a minimum, a `value`, meaning that no default function needs to be
|
134
|
+
specified.
|
135
|
+
|
136
|
+
This function has been extracted into it's own method to allow for mocking of sample function.
|
137
|
+
|
138
|
+
Keyword Args
|
139
|
+
------------
|
140
|
+
value : float
|
141
|
+
The distribution mean.
|
142
|
+
sd : float
|
143
|
+
The standard deviation of the distribution.
|
144
|
+
se : float
|
145
|
+
The standard error of the distribution.
|
146
|
+
n : float
|
147
|
+
Sample size.
|
148
|
+
|
149
|
+
Returns
|
150
|
+
-------
|
151
|
+
Callable
|
152
|
+
The sample function for the distribution.
|
153
|
+
"""
|
154
|
+
return next(
|
155
|
+
sample_func for required_kwargs, sample_func in _KWARGS_TO_SAMPLE_FUNC.items()
|
156
|
+
if all(kwarg in kwargs.keys() for kwarg in required_kwargs)
|
157
|
+
)
|
158
|
+
|
159
|
+
|
160
|
+
def _should_run(cycle: dict):
|
161
|
+
end_date = cycle.get('endDate')
|
162
|
+
site = cycle.get('site', {})
|
163
|
+
measurements = site.get('measurements', [])
|
164
|
+
|
165
|
+
seed = gen_seed(cycle, MODEL, TERM_ID)
|
166
|
+
rng = np.random.default_rng(seed)
|
167
|
+
|
168
|
+
def _get_measurement_content(term_id: str):
|
169
|
+
return most_relevant_measurement_value(measurements, term_id, end_date)
|
170
|
+
|
171
|
+
histosol = _get_measurement_content('histosol')
|
172
|
+
eco_climate_zone = get_eco_climate_zone_value(cycle, as_enum=True)
|
173
|
+
organic_soil_category = assign_organic_soil_category(cycle, log_id=TERM_ID)
|
174
|
+
|
175
|
+
emission_factor = (
|
176
|
+
sample_emission_factor(eco_climate_zone, organic_soil_category, seed=rng) if eco_climate_zone
|
177
|
+
else None
|
178
|
+
)
|
179
|
+
land_occupation = land_occupation_per_ha(MODEL, TERM_ID, cycle)
|
180
|
+
|
181
|
+
logRequirements(
|
182
|
+
cycle, model=MODEL, term=TERM_ID,
|
183
|
+
eco_climate_zone=eco_climate_zone,
|
184
|
+
organic_soil_category=organic_soil_category,
|
185
|
+
emission_factor=f"{np.mean(emission_factor):.3f}",
|
186
|
+
land_occupation=land_occupation,
|
187
|
+
histosol=histosol
|
188
|
+
)
|
189
|
+
|
190
|
+
should_run = all([
|
191
|
+
valid_site_type(site),
|
192
|
+
valid_eco_climate_zone(eco_climate_zone),
|
193
|
+
all(
|
194
|
+
var is not None for var in [
|
195
|
+
emission_factor,
|
196
|
+
land_occupation,
|
197
|
+
histosol
|
198
|
+
]
|
199
|
+
)
|
200
|
+
])
|
201
|
+
|
202
|
+
logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
|
203
|
+
|
204
|
+
return should_run, emission_factor, histosol, land_occupation
|
205
|
+
|
206
|
+
|
207
|
+
def _run(emission_factor: npt.NDArray, histosol: float, land_occupation: float):
|
208
|
+
result = calc_emission(TERM_ID, emission_factor, histosol, land_occupation)
|
209
|
+
descriptive_stats = calc_descriptive_stats(result, _STATS_DEFINITION)
|
210
|
+
return [_emission(descriptive_stats)]
|
211
|
+
|
212
|
+
|
213
|
+
def run(cycle: dict):
|
214
|
+
should_run, emission_factor, histosol, land_occupation = _should_run(cycle)
|
215
|
+
return _run(emission_factor, histosol, land_occupation) if should_run else []
|
@@ -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
|
}
|
@@ -0,0 +1,161 @@
|
|
1
|
+
from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition
|
2
|
+
|
3
|
+
from hestia_earth.models.log import logRequirements, logShouldRun
|
4
|
+
from hestia_earth.models.utils.cycle import land_occupation_per_ha
|
5
|
+
from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
|
6
|
+
from hestia_earth.models.utils.emission import _new_emission
|
7
|
+
from hestia_earth.models.utils.measurement import most_relevant_measurement_value
|
8
|
+
from hestia_earth.models.utils.site import valid_site_type
|
9
|
+
|
10
|
+
from .organicSoilCultivation_utils import (
|
11
|
+
assign_organic_soil_category, calc_emission, get_emission_factor, OrganicSoilCategory, remap_categories,
|
12
|
+
valid_eco_climate_zone
|
13
|
+
)
|
14
|
+
from . import MODEL
|
15
|
+
|
16
|
+
|
17
|
+
REQUIREMENTS = {
|
18
|
+
"Cycle": {
|
19
|
+
"or": [
|
20
|
+
{
|
21
|
+
"cycleDuration": "",
|
22
|
+
"practices": [{"@type": "Practice", "value": "", "term.@id": "longFallowRatio"}]
|
23
|
+
},
|
24
|
+
{
|
25
|
+
"@doc": "for plantations, additional properties are required",
|
26
|
+
"practices": [
|
27
|
+
{"@type": "Practice", "value": "", "term.@id": "nurseryDensity"},
|
28
|
+
{"@type": "Practice", "value": "", "term.@id": "nurseryDuration"},
|
29
|
+
{"@type": "Practice", "value": "", "term.@id": "plantationProductiveLifespan"},
|
30
|
+
{"@type": "Practice", "value": "", "term.@id": "plantationDensity"},
|
31
|
+
{"@type": "Practice", "value": "", "term.@id": "plantationLifespan"},
|
32
|
+
{"@type": "Practice", "value": "", "term.@id": "rotationDuration"}
|
33
|
+
]
|
34
|
+
}
|
35
|
+
],
|
36
|
+
"site": {
|
37
|
+
"@type": "Site",
|
38
|
+
"measurements": [
|
39
|
+
{"@type": "Measurement", "value": "", "term.@id": "histosol"},
|
40
|
+
{"@type": "Measurement", "value": "", "term.@id": "ecoClimateZone"}
|
41
|
+
]
|
42
|
+
},
|
43
|
+
"optional": {
|
44
|
+
"cycleDuration": ""
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
LOOKUPS = {
|
49
|
+
"crop": [
|
50
|
+
"isPlantation",
|
51
|
+
"IPCC_2013_ORGANIC_SOIL_CULTIVATION_CATEGORY"
|
52
|
+
],
|
53
|
+
"forage": [
|
54
|
+
"isPlantation",
|
55
|
+
"IPCC_2013_ORGANIC_SOIL_CULTIVATION_CATEGORY"
|
56
|
+
],
|
57
|
+
"ecoClimateZone": [
|
58
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_N2O-N_HECTARE_ANNUAL_CROPS",
|
59
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_N2O-N_HECTARE_PERENNIAL_CROPS",
|
60
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_N2O-N_HECTARE_ACACIA",
|
61
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_N2O-N_HECTARE_OIL_PALM",
|
62
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_N2O-N_HECTARE_SAGO_PALM",
|
63
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_N2O-N_HECTARE_PADDY_RICE_CULTIVATION",
|
64
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_N2O-N_HECTARE_GRASSLAND",
|
65
|
+
"IPCC_2013_ORGANIC_SOILS_TONNES_N2O-N_HECTARE_OTHER"
|
66
|
+
]
|
67
|
+
}
|
68
|
+
RETURNS = {
|
69
|
+
"Emission": [{
|
70
|
+
"value": "",
|
71
|
+
"sd": "",
|
72
|
+
"statsDefinition": "modelled",
|
73
|
+
"methodTier": "tier 1"
|
74
|
+
}]
|
75
|
+
}
|
76
|
+
TERM_ID = 'n2OToAirOrganicSoilCultivationDirect'
|
77
|
+
TIER = EmissionMethodTier.TIER_1.value
|
78
|
+
|
79
|
+
_STATS_DEFINITION = EmissionStatsDefinition.MODELLED.value
|
80
|
+
|
81
|
+
_CATEGORY_REMAPPER = {
|
82
|
+
OrganicSoilCategory.ACACIA: OrganicSoilCategory.PERENNIAL_CROPS
|
83
|
+
}
|
84
|
+
|
85
|
+
|
86
|
+
def _emission(value: float, sd: float):
|
87
|
+
emission = _new_emission(TERM_ID, MODEL)
|
88
|
+
emission['value'] = [value]
|
89
|
+
emission['sd'] = [sd]
|
90
|
+
emission['statsDefinition'] = _STATS_DEFINITION
|
91
|
+
emission['methodTier'] = TIER
|
92
|
+
return emission
|
93
|
+
|
94
|
+
|
95
|
+
def sample_emission_factor(
|
96
|
+
eco_climate_zone: EcoClimateZone,
|
97
|
+
organic_soil_category: OrganicSoilCategory,
|
98
|
+
) -> tuple[float, float]:
|
99
|
+
category = remap_categories(organic_soil_category, _CATEGORY_REMAPPER) # fewer categories than CO2 model
|
100
|
+
factor_data = get_emission_factor(TERM_ID, eco_climate_zone, category)
|
101
|
+
mean = factor_data.get("value", 0)
|
102
|
+
sd = factor_data.get("sd", 0)
|
103
|
+
return mean, sd
|
104
|
+
|
105
|
+
|
106
|
+
def _should_run(cycle: dict):
|
107
|
+
end_date = cycle.get('endDate')
|
108
|
+
site = cycle.get('site', {})
|
109
|
+
measurements = site.get('measurements', [])
|
110
|
+
|
111
|
+
def _get_measurement_content(term_id: str):
|
112
|
+
return most_relevant_measurement_value(measurements, term_id, end_date)
|
113
|
+
|
114
|
+
histosol = _get_measurement_content('histosol')
|
115
|
+
eco_climate_zone = get_eco_climate_zone_value(cycle, as_enum=True)
|
116
|
+
organic_soil_category = assign_organic_soil_category(cycle, log_id=TERM_ID)
|
117
|
+
|
118
|
+
emission_factor_mean, emission_factor_sd = (
|
119
|
+
sample_emission_factor(eco_climate_zone, organic_soil_category)
|
120
|
+
if eco_climate_zone
|
121
|
+
else (None, None)
|
122
|
+
)
|
123
|
+
|
124
|
+
land_occupation = land_occupation_per_ha(MODEL, TERM_ID, cycle)
|
125
|
+
|
126
|
+
logRequirements(
|
127
|
+
cycle, model=MODEL, term=TERM_ID,
|
128
|
+
eco_climate_zone=eco_climate_zone,
|
129
|
+
organic_soil_category=organic_soil_category,
|
130
|
+
emission_factor=f"{emission_factor_mean} ± {emission_factor_sd}",
|
131
|
+
land_occupation=land_occupation,
|
132
|
+
histosol=histosol
|
133
|
+
)
|
134
|
+
|
135
|
+
should_run = all([
|
136
|
+
valid_site_type(site),
|
137
|
+
valid_eco_climate_zone(eco_climate_zone),
|
138
|
+
all(
|
139
|
+
var is not None for var in [
|
140
|
+
emission_factor_mean,
|
141
|
+
emission_factor_sd,
|
142
|
+
land_occupation,
|
143
|
+
histosol
|
144
|
+
]
|
145
|
+
)
|
146
|
+
])
|
147
|
+
|
148
|
+
logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
|
149
|
+
|
150
|
+
return should_run, emission_factor_mean, emission_factor_sd, histosol, land_occupation
|
151
|
+
|
152
|
+
|
153
|
+
def _run(emission_factor_mean: float, emission_factor_sd: float, histosol: float, land_occupation: float):
|
154
|
+
value = calc_emission(TERM_ID, emission_factor_mean, histosol, land_occupation)
|
155
|
+
sd = calc_emission(TERM_ID, emission_factor_sd, histosol, land_occupation)
|
156
|
+
return [_emission(value, sd)]
|
157
|
+
|
158
|
+
|
159
|
+
def run(cycle: dict):
|
160
|
+
should_run, emission_factor_mean, emission_factor_sd, histosol, land_occupation = _should_run(cycle)
|
161
|
+
return _run(emission_factor_mean, emission_factor_sd, histosol, land_occupation) if should_run else []
|