hestia-earth-models 0.74.14__py3-none-any.whl → 0.74.16__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.
- hestia_earth/models/cache_nodes.py +9 -6
- hestia_earth/models/config/ImpactAssessment.json +0 -22
- hestia_earth/models/config/Site.json +11 -3
- hestia_earth/models/cycle/completeness/material.py +2 -3
- hestia_earth/models/emepEea2019/fuelCombustion_utils.py +21 -21
- hestia_earth/models/hestia/landOccupationDuringCycle.py +9 -27
- hestia_earth/models/hestia/resourceUse_utils.py +49 -20
- hestia_earth/models/hestia/soilClassification.py +314 -0
- hestia_earth/models/ipcc2019/aboveGroundBiomass.py +5 -15
- hestia_earth/models/ipcc2019/belowGroundBiomass.py +5 -15
- hestia_earth/models/ipcc2019/biocharOrganicCarbonPerHa.py +5 -39
- hestia_earth/models/ipcc2019/ch4ToAirOrganicSoilCultivation.py +5 -5
- hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +10 -15
- hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +11 -16
- hestia_earth/models/ipcc2019/co2ToAirBiocharStockChange.py +7 -17
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +123 -74
- hestia_earth/models/ipcc2019/co2ToAirOrganicSoilCultivation.py +4 -5
- hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +10 -15
- hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationDirect.py +5 -5
- hestia_earth/models/ipcc2019/nonCo2EmissionsToAirNaturalVegetationBurning.py +18 -47
- hestia_earth/models/ipcc2019/organicCarbonPerHa.py +10 -10
- hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +4 -19
- hestia_earth/models/ipcc2019/organicSoilCultivation_utils.py +0 -9
- hestia_earth/models/log.py +75 -1
- hestia_earth/models/mocking/search-results.json +1 -1
- hestia_earth/models/utils/blank_node.py +12 -4
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.74.14.dist-info → hestia_earth_models-0.74.16.dist-info}/METADATA +15 -7
- {hestia_earth_models-0.74.14.dist-info → hestia_earth_models-0.74.16.dist-info}/RECORD +37 -34
- {hestia_earth_models-0.74.14.dist-info → hestia_earth_models-0.74.16.dist-info}/WHEEL +1 -1
- tests/models/ecoalimV9/test_cycle.py +2 -2
- tests/models/hestia/test_landTransformation20YearAverageDuringCycle.py +4 -8
- tests/models/hestia/test_soilClassification.py +72 -0
- tests/models/ipcc2019/test_organicCarbonPerHa_utils.py +4 -48
- tests/models/test_log.py +128 -0
- {hestia_earth_models-0.74.14.dist-info → hestia_earth_models-0.74.16.dist-info/licenses}/LICENSE +0 -0
- {hestia_earth_models-0.74.14.dist-info → hestia_earth_models-0.74.16.dist-info}/top_level.txt +0 -0
|
@@ -18,7 +18,10 @@ from hestia_earth.utils.tools import flatten, non_empty_list, safe_parse_date
|
|
|
18
18
|
from hestia_earth.utils.stats import correlated_normal_2d, gen_seed
|
|
19
19
|
from hestia_earth.utils.descriptive_stats import calc_descriptive_stats
|
|
20
20
|
|
|
21
|
-
from hestia_earth.models.log import
|
|
21
|
+
from hestia_earth.models.log import (
|
|
22
|
+
format_conditional_message, format_bool, format_float, format_int, format_nd_array, log_as_table, logRequirements,
|
|
23
|
+
logShouldRun
|
|
24
|
+
)
|
|
22
25
|
from hestia_earth.models.utils import pairwise
|
|
23
26
|
from hestia_earth.models.utils.blank_node import (
|
|
24
27
|
_gapfill_datestr, _get_datestr_format, cumulative_nodes_term_match, DatestrGapfillMode, DatestrFormat,
|
|
@@ -88,6 +91,11 @@ _SITE_TYPE_SYSTEMS_MAPPING = {
|
|
|
88
91
|
]
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
_MEASUREMENTS_REQUIRED_LOG_MESSAGE = {
|
|
95
|
+
"on_true": "True - carbon stock measurements are required for this model to run",
|
|
96
|
+
"on_false": "False - this model can run without carbon stock measurements in specific cases (see documentation)"
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
|
|
92
100
|
class _InventoryKey(Enum):
|
|
93
101
|
"""
|
|
@@ -103,6 +111,7 @@ class _InventoryKey(Enum):
|
|
|
103
111
|
LAND_USE_CHANGE_EVENT = "luc-event"
|
|
104
112
|
YEARS_SINCE_LUC_EVENT = "years-since-luc-event"
|
|
105
113
|
YEARS_SINCE_INVENTORY_START = "years-since-inventory-start"
|
|
114
|
+
YEAR_IS_RELEVANT = "year-is-relevant"
|
|
106
115
|
|
|
107
116
|
|
|
108
117
|
CarbonStock = NamedTuple("CarbonStock", [
|
|
@@ -338,15 +347,18 @@ def _add_carbon_stock_change_emissions(
|
|
|
338
347
|
|
|
339
348
|
def create_should_run_function(
|
|
340
349
|
carbon_stock_term_id: str,
|
|
350
|
+
land_use_change_emission_term_id: str,
|
|
351
|
+
management_change_emission_term_id: str,
|
|
341
352
|
*,
|
|
342
353
|
depth_upper: Optional[float] = None,
|
|
343
354
|
depth_lower: Optional[float] = None,
|
|
344
|
-
|
|
355
|
+
measurements_required: bool = True,
|
|
345
356
|
measurement_method_ranking: list[MeasurementMethodClassification] = DEFAULT_MEASUREMENT_METHOD_RANKING,
|
|
346
357
|
transition_period: float = _TRANSITION_PERIOD_DAYS,
|
|
347
358
|
get_valid_management_nodes_func: Callable[[dict], list[dict]] = lambda *_: [],
|
|
348
359
|
summarise_land_use_func: Callable[[list[dict]], Any] = lambda *_: None,
|
|
349
|
-
detect_land_use_change_func: Callable[[Any, Any], bool] = lambda *_: False
|
|
360
|
+
detect_land_use_change_func: Callable[[Any, Any], bool] = lambda *_: False,
|
|
361
|
+
exclude_from_logs: Optional[list[str]] = None
|
|
350
362
|
) -> Callable[[dict], tuple[bool, str, dict, dict]]:
|
|
351
363
|
"""
|
|
352
364
|
Create a `should_run` function for a carbon stock change model.
|
|
@@ -362,6 +374,12 @@ def create_should_run_function(
|
|
|
362
374
|
The `term.@id` of the carbon stock measurement (e.g., `aboveGroundBiomass`, `belowGroundBiomass`,
|
|
363
375
|
`organicCarbonPerHa`).
|
|
364
376
|
|
|
377
|
+
land_use_change_emission_term_id : str
|
|
378
|
+
The term id for emissions allocated to land use changes.
|
|
379
|
+
|
|
380
|
+
management_change_emission_term_id : str
|
|
381
|
+
The term id for emissions allocated to management changes.
|
|
382
|
+
|
|
365
383
|
depth_upper : float, optional
|
|
366
384
|
The upper bound of the measurement depth (e.g., 0 cm). If provided, only measurements matching this bound are
|
|
367
385
|
included.
|
|
@@ -370,7 +388,7 @@ def create_should_run_function(
|
|
|
370
388
|
The lower bound of the measurement depth (e.g., 30 cm). If provided, only measurements matching this bound are
|
|
371
389
|
included.
|
|
372
390
|
|
|
373
|
-
|
|
391
|
+
measurements_required : bool, default=True
|
|
374
392
|
If `True`, at least two valid measurement must be present for the cycle to be included in the inventory. If
|
|
375
393
|
`False`, the function may allow an inventory to be generated without direct measurements.
|
|
376
394
|
|
|
@@ -407,6 +425,10 @@ def create_should_run_function(
|
|
|
407
425
|
|
|
408
426
|
Detects whether a land use change event has occurred between two summaries.
|
|
409
427
|
|
|
428
|
+
exclude_from_logs : list[str], optional
|
|
429
|
+
|
|
430
|
+
A list of log keys to exclude from the model logs.
|
|
431
|
+
|
|
410
432
|
Returns
|
|
411
433
|
-------
|
|
412
434
|
should_run_func : Callable[[dict], tuple[bool, str, dict, dict]]
|
|
@@ -471,7 +493,7 @@ def create_should_run_function(
|
|
|
471
493
|
has_soil
|
|
472
494
|
and has_cycles
|
|
473
495
|
and has_functional_unit_1_ha
|
|
474
|
-
and (has_stock_measurements or not
|
|
496
|
+
and (has_stock_measurements or not measurements_required)
|
|
475
497
|
)
|
|
476
498
|
|
|
477
499
|
compile_inventory_func = _create_compile_inventory_function(
|
|
@@ -485,13 +507,21 @@ def create_should_run_function(
|
|
|
485
507
|
|
|
486
508
|
inventory, inventory_logs = (
|
|
487
509
|
compile_inventory_func(
|
|
510
|
+
cycle_id,
|
|
488
511
|
cycles,
|
|
489
512
|
carbon_stock_measurements,
|
|
490
513
|
land_cover_nodes
|
|
491
514
|
) if should_compile_inventory else ({}, {})
|
|
492
515
|
)
|
|
493
516
|
|
|
494
|
-
|
|
517
|
+
assigned_emissions = _assign_emissions(
|
|
518
|
+
cycle_id,
|
|
519
|
+
inventory,
|
|
520
|
+
land_use_change_emission_term_id,
|
|
521
|
+
management_change_emission_term_id
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
has_valid_inventory = bool(assigned_emissions) > 0
|
|
495
525
|
has_consecutive_years = check_consecutive(inventory.keys())
|
|
496
526
|
|
|
497
527
|
should_run_ = all([has_valid_inventory, has_consecutive_years])
|
|
@@ -506,10 +536,28 @@ def create_should_run_function(
|
|
|
506
536
|
"has_valid_inventory": has_valid_inventory,
|
|
507
537
|
"has_consecutive_years": has_consecutive_years,
|
|
508
538
|
"has_stock_measurements": has_stock_measurements,
|
|
509
|
-
"
|
|
539
|
+
"measurements_required": format_conditional_message(
|
|
540
|
+
measurements_required,
|
|
541
|
+
**_MEASUREMENTS_REQUIRED_LOG_MESSAGE
|
|
542
|
+
)
|
|
510
543
|
}
|
|
511
544
|
|
|
512
|
-
|
|
545
|
+
final_logs = _filter_logs(logs, exclude_from_logs) if isinstance(exclude_from_logs, list) else logs
|
|
546
|
+
|
|
547
|
+
for term_id in [land_use_change_emission_term_id, management_change_emission_term_id]:
|
|
548
|
+
|
|
549
|
+
assigned_emission = assigned_emissions.get(term_id)
|
|
550
|
+
tier = (
|
|
551
|
+
_get_emission_method(assigned_emission).value if assigned_emission
|
|
552
|
+
else _DEFAULT_EMISSION_METHOD_TIER.value
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
should_run_term = should_run_ and bool(assigned_emission)
|
|
556
|
+
|
|
557
|
+
logRequirements(cycle, model=MODEL, term=term_id, **final_logs)
|
|
558
|
+
logShouldRun(cycle, MODEL, term_id, should_run_term, methodTier=tier)
|
|
559
|
+
|
|
560
|
+
return should_run_, assigned_emissions
|
|
513
561
|
|
|
514
562
|
return should_run
|
|
515
563
|
|
|
@@ -589,6 +637,7 @@ def _create_compile_inventory_function(
|
|
|
589
637
|
A function that compiles an annual inventory given cycles, carbon stock measurements, and land cover nodes.
|
|
590
638
|
"""
|
|
591
639
|
def compile_inventory(
|
|
640
|
+
cycle_id: str,
|
|
592
641
|
cycles: list[dict],
|
|
593
642
|
carbon_stock_measurements: list[dict],
|
|
594
643
|
land_cover_nodes: list[dict]
|
|
@@ -612,6 +661,9 @@ def _create_compile_inventory_function(
|
|
|
612
661
|
|
|
613
662
|
Parameters
|
|
614
663
|
----------
|
|
664
|
+
cycle_id : str
|
|
665
|
+
The `@id` of the cycle the model is running on.
|
|
666
|
+
|
|
615
667
|
cycles : list[dict]
|
|
616
668
|
A list of [Cycle](https://www.hestia.earth/schema/Cycles) nodes related to the site.
|
|
617
669
|
|
|
@@ -645,7 +697,7 @@ def _create_compile_inventory_function(
|
|
|
645
697
|
logs : dict
|
|
646
698
|
Diagnostic logs describing intermediate steps and validation decisions.
|
|
647
699
|
"""
|
|
648
|
-
cycle_inventory = _compile_cycle_inventory(cycles)
|
|
700
|
+
cycle_inventory = _compile_cycle_inventory(cycle_id, cycles)
|
|
649
701
|
carbon_stock_inventory = _compile_carbon_stock_inventory(
|
|
650
702
|
carbon_stock_measurements, transition_period=transition_period, iterations=iterations, seed=seed
|
|
651
703
|
)
|
|
@@ -667,7 +719,7 @@ def _create_compile_inventory_function(
|
|
|
667
719
|
return compile_inventory
|
|
668
720
|
|
|
669
721
|
|
|
670
|
-
def _compile_cycle_inventory(cycles: list[dict]) -> dict:
|
|
722
|
+
def _compile_cycle_inventory(cycle_id: str, cycles: list[dict]) -> dict:
|
|
671
723
|
"""
|
|
672
724
|
Compile the share of emissions for each cycle, grouped by inventory year.
|
|
673
725
|
|
|
@@ -690,6 +742,9 @@ def _compile_cycle_inventory(cycles: list[dict]) -> dict:
|
|
|
690
742
|
|
|
691
743
|
Parameters
|
|
692
744
|
----------
|
|
745
|
+
cycle_id : str
|
|
746
|
+
The `@id` of the cycle the model is running on.
|
|
747
|
+
|
|
693
748
|
cycles : list[dict]
|
|
694
749
|
List of [Cycle](https://www.hestia.earth/schema/Cycle) nodes.
|
|
695
750
|
|
|
@@ -713,8 +768,10 @@ def _compile_cycle_inventory(cycles: list[dict]) -> dict:
|
|
|
713
768
|
}
|
|
714
769
|
|
|
715
770
|
return {
|
|
716
|
-
year: {
|
|
717
|
-
|
|
771
|
+
year: {
|
|
772
|
+
_InventoryKey.SHARE_OF_EMISSION: calculate_emissions(cycles_in_year),
|
|
773
|
+
_InventoryKey.YEAR_IS_RELEVANT: cycle_id in (cycle.get("@id") for cycle in cycles_in_year)
|
|
774
|
+
} for year, cycles_in_year in grouped_cycles.items()
|
|
718
775
|
}
|
|
719
776
|
|
|
720
777
|
|
|
@@ -1373,11 +1430,12 @@ def _format_cycle_inventory(cycle_inventory: dict) -> str:
|
|
|
1373
1430
|
Format the cycle inventory for logging as a table. Rows represent inventory years, columns represent the share of
|
|
1374
1431
|
emission for each cycle present in the inventory. If the inventory is invalid, return `"None"` as a string.
|
|
1375
1432
|
"""
|
|
1376
|
-
|
|
1433
|
+
RELEVANT_KEY = _InventoryKey.YEAR_IS_RELEVANT
|
|
1434
|
+
SHARE_KEY = _InventoryKey.SHARE_OF_EMISSION
|
|
1377
1435
|
|
|
1378
1436
|
unique_cycles = sorted(
|
|
1379
|
-
set(non_empty_list(flatten(list(group[
|
|
1380
|
-
key=lambda id: next((year, id) for year in cycle_inventory if id in cycle_inventory[year][
|
|
1437
|
+
set(non_empty_list(flatten(list(group[SHARE_KEY]) for group in cycle_inventory.values()))),
|
|
1438
|
+
key=lambda id: next((year, id) for year in cycle_inventory if id in cycle_inventory[year][SHARE_KEY])
|
|
1381
1439
|
)
|
|
1382
1440
|
|
|
1383
1441
|
should_run = cycle_inventory and len(unique_cycles) > 0
|
|
@@ -1385,8 +1443,9 @@ def _format_cycle_inventory(cycle_inventory: dict) -> str:
|
|
|
1385
1443
|
return log_as_table(
|
|
1386
1444
|
{
|
|
1387
1445
|
"year": year,
|
|
1446
|
+
RELEVANT_KEY.value: format_bool(group.get(RELEVANT_KEY, False)),
|
|
1388
1447
|
**{
|
|
1389
|
-
id:
|
|
1448
|
+
id: format_float(group.get(SHARE_KEY, {}).get(id, 0)) for id in unique_cycles
|
|
1390
1449
|
}
|
|
1391
1450
|
} for year, group in cycle_inventory.items()
|
|
1392
1451
|
) if should_run else "None"
|
|
@@ -1450,21 +1509,6 @@ def _format_land_use_inventory(land_use_inventory: dict) -> str:
|
|
|
1450
1509
|
) if should_run else "None"
|
|
1451
1510
|
|
|
1452
1511
|
|
|
1453
|
-
def _format_bool(value: Optional[bool]) -> str:
|
|
1454
|
-
"""Format a bool for logging in a table."""
|
|
1455
|
-
return str(value) if isinstance(value, bool) else "None"
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
def _format_int(value: Optional[float]) -> str:
|
|
1459
|
-
"""Format an int for logging in a table. If the value is invalid, return `"None"` as a string."""
|
|
1460
|
-
return f"{value:.0f}" if isinstance(value, (float, int)) else "None"
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
def _format_number(value: Optional[float]) -> str:
|
|
1464
|
-
"""Format a float for logging in a table. If the value is invalid, return `"None"` as a string."""
|
|
1465
|
-
return f"{value:.1f}" if isinstance(value, (float, int)) else "None"
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
1512
|
def _format_column_header(method: MeasurementMethodClassification, inventory_key: _InventoryKey) -> str:
|
|
1469
1513
|
"""
|
|
1470
1514
|
Format a measurement method classification and inventory key for logging in a table as a column header. Replace any
|
|
@@ -1483,16 +1527,20 @@ def _format_named_tuple(value: Optional[Union[CarbonStock, CarbonStockChange, Ca
|
|
|
1483
1527
|
Extract and format just the value and discard the other data. If the value is invalid, return `"None"` as a string.
|
|
1484
1528
|
"""
|
|
1485
1529
|
return (
|
|
1486
|
-
|
|
1530
|
+
format_nd_array(mean(value.value))
|
|
1487
1531
|
if isinstance(value, (CarbonStock, CarbonStockChange, CarbonStockChangeEmission))
|
|
1488
1532
|
else "None"
|
|
1489
1533
|
)
|
|
1490
1534
|
|
|
1491
1535
|
|
|
1536
|
+
def _filter_logs(logs: dict[str, Any], exclude_keys: list[str]):
|
|
1537
|
+
return {k: v for k, v in logs.items() if k not in exclude_keys}
|
|
1538
|
+
|
|
1539
|
+
|
|
1492
1540
|
_LAND_USE_INVENTORY_KEY_TO_FORMAT_FUNC = {
|
|
1493
|
-
_InventoryKey.LAND_USE_CHANGE_EVENT:
|
|
1494
|
-
_InventoryKey.YEARS_SINCE_LUC_EVENT:
|
|
1495
|
-
_InventoryKey.YEARS_SINCE_INVENTORY_START:
|
|
1541
|
+
_InventoryKey.LAND_USE_CHANGE_EVENT: format_bool,
|
|
1542
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT: format_int,
|
|
1543
|
+
_InventoryKey.YEARS_SINCE_INVENTORY_START: format_int
|
|
1496
1544
|
}
|
|
1497
1545
|
"""
|
|
1498
1546
|
Map inventory keys to format functions. The columns in inventory logged as a table will also be sorted in the order of
|
|
@@ -1500,32 +1548,14 @@ the `dict` keys.
|
|
|
1500
1548
|
"""
|
|
1501
1549
|
|
|
1502
1550
|
|
|
1503
|
-
def
|
|
1504
|
-
|
|
1551
|
+
def _assign_emissions(
|
|
1552
|
+
cycle_id: str,
|
|
1553
|
+
inventory,
|
|
1505
1554
|
land_use_change_emission_term_id: str,
|
|
1506
1555
|
management_change_emission_term_id: str
|
|
1507
|
-
)
|
|
1508
|
-
"""
|
|
1509
|
-
Create a run function for an emissions from carbon stock change model.
|
|
1556
|
+
):
|
|
1510
1557
|
|
|
1511
|
-
|
|
1512
|
-
model ouputs are formatted into HESTIA emission nodes.
|
|
1513
|
-
|
|
1514
|
-
Parameters
|
|
1515
|
-
----------
|
|
1516
|
-
new_emission_func : Callable[[EmissionMethodTier, tuple], dict]
|
|
1517
|
-
A function, with the signature `(method_tier: dict, **kwargs: dict) -> (emission_node: dict)`.
|
|
1518
|
-
land_use_change_emission_term_id : str
|
|
1519
|
-
The term id for emissions allocated to land use changes.
|
|
1520
|
-
management_change_emission_term_id : str
|
|
1521
|
-
The term id for emissions allocated to management changes.
|
|
1522
|
-
|
|
1523
|
-
Returns
|
|
1524
|
-
-------
|
|
1525
|
-
Callable[[str, dict], list[dict]]
|
|
1526
|
-
The customised `run` function with the signature `(cycle_id: str, inventory: dict) -> emissions: list[dict]`.
|
|
1527
|
-
"""
|
|
1528
|
-
def reduce_emissions(result: dict, year: int, cycle_id: str, inventory: dict):
|
|
1558
|
+
def assign(result: dict, year: int, cycle_id: str, inventory: dict):
|
|
1529
1559
|
"""
|
|
1530
1560
|
Assign emissions to either the land use or management change term ids and sum together.
|
|
1531
1561
|
"""
|
|
@@ -1575,7 +1605,36 @@ def create_run_function(
|
|
|
1575
1605
|
|
|
1576
1606
|
return result | emission_dict | zero_emission_dict
|
|
1577
1607
|
|
|
1578
|
-
def
|
|
1608
|
+
def should_run_year(year: int) -> bool:
|
|
1609
|
+
return cycle_id in inventory.get(year, {}).get(_InventoryKey.SHARE_OF_EMISSION, {}).keys()
|
|
1610
|
+
|
|
1611
|
+
return reduce(
|
|
1612
|
+
lambda result, year: assign(result, year, cycle_id, inventory),
|
|
1613
|
+
(year for year in inventory.keys() if should_run_year(year)),
|
|
1614
|
+
{}
|
|
1615
|
+
)
|
|
1616
|
+
|
|
1617
|
+
|
|
1618
|
+
def create_run_function(
|
|
1619
|
+
new_emission_func: Callable[[EmissionMethodTier, dict], dict]
|
|
1620
|
+
) -> Callable[[str, dict], list[dict]]:
|
|
1621
|
+
"""
|
|
1622
|
+
Create a run function for an emissions from carbon stock change model.
|
|
1623
|
+
|
|
1624
|
+
A model-specific `new_emission_func` should be passed as a parameter to this higher-order function to control how
|
|
1625
|
+
model ouputs are formatted into HESTIA emission nodes.
|
|
1626
|
+
|
|
1627
|
+
Parameters
|
|
1628
|
+
----------
|
|
1629
|
+
new_emission_func : Callable[[EmissionMethodTier, tuple], dict]
|
|
1630
|
+
A function, with the signature `(method_tier: dict, **kwargs: dict) -> (emission_node: dict)`.
|
|
1631
|
+
|
|
1632
|
+
Returns
|
|
1633
|
+
-------
|
|
1634
|
+
Callable[[str, dict], list[dict]]
|
|
1635
|
+
The customised `run` function with the signature `(cycle_id: str, inventory: dict) -> emissions: list[dict]`.
|
|
1636
|
+
"""
|
|
1637
|
+
def run(assigned_emissions: dict) -> list[dict]:
|
|
1579
1638
|
"""
|
|
1580
1639
|
Calculate emissions for a specific cycle using from a carbon stock change using pre-compiled inventory data.
|
|
1581
1640
|
|
|
@@ -1594,27 +1653,17 @@ def create_run_function(
|
|
|
1594
1653
|
list[dict]
|
|
1595
1654
|
A list of [Emission](https://www.hestia.earth/schema/Emission) nodes containing model results.
|
|
1596
1655
|
"""
|
|
1597
|
-
|
|
1598
|
-
def should_run_year(year: int) -> bool:
|
|
1599
|
-
return cycle_id in inventory.get(year, {}).get(_InventoryKey.SHARE_OF_EMISSION, {}).keys()
|
|
1600
|
-
|
|
1601
|
-
assigned_emissions = reduce(
|
|
1602
|
-
lambda result, year: reduce_emissions(result, year, cycle_id, inventory),
|
|
1603
|
-
(year for year in inventory.keys() if should_run_year(year)),
|
|
1604
|
-
{}
|
|
1605
|
-
)
|
|
1606
|
-
|
|
1607
1656
|
return [
|
|
1608
1657
|
new_emission_func(
|
|
1609
1658
|
term_id=emission_term_id,
|
|
1610
|
-
method_tier=_get_emission_method(
|
|
1659
|
+
method_tier=_get_emission_method(stock_change_emission),
|
|
1611
1660
|
**calc_descriptive_stats(
|
|
1612
|
-
|
|
1661
|
+
stock_change_emission.value,
|
|
1613
1662
|
EmissionStatsDefinition.SIMULATED,
|
|
1614
1663
|
decimals=6
|
|
1615
1664
|
)
|
|
1616
|
-
) for emission_term_id,
|
|
1617
|
-
if isinstance(
|
|
1665
|
+
) for emission_term_id, stock_change_emission in assigned_emissions.items()
|
|
1666
|
+
if isinstance(stock_change_emission, CarbonStockChangeEmission)
|
|
1618
1667
|
]
|
|
1619
1668
|
|
|
1620
1669
|
return run
|
|
@@ -1631,7 +1680,7 @@ def get_zero_emission(year):
|
|
|
1631
1680
|
|
|
1632
1681
|
def _get_emission_method(emission: CarbonStockChangeEmission):
|
|
1633
1682
|
method = emission.method
|
|
1634
|
-
return method if isinstance(method, EmissionMethodTier) else
|
|
1683
|
+
return method if isinstance(method, EmissionMethodTier) else _DEFAULT_EMISSION_METHOD_TIER
|
|
1635
1684
|
|
|
1636
1685
|
|
|
1637
1686
|
def is_soil_based_system(cycles, site_type):
|
|
@@ -5,7 +5,7 @@ from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition
|
|
|
5
5
|
from hestia_earth.utils.stats import gen_seed, repeat_single, truncated_normal_1d
|
|
6
6
|
from hestia_earth.utils.descriptive_stats import calc_descriptive_stats
|
|
7
7
|
|
|
8
|
-
from hestia_earth.models.log import logRequirements, logShouldRun
|
|
8
|
+
from hestia_earth.models.log import format_float, format_nd_array, logRequirements, logShouldRun
|
|
9
9
|
from hestia_earth.models.utils.cycle import land_occupation_per_ha
|
|
10
10
|
from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
|
|
11
11
|
from hestia_earth.models.utils.emission import _new_emission
|
|
@@ -13,8 +13,7 @@ from hestia_earth.models.utils.measurement import most_relevant_measurement_valu
|
|
|
13
13
|
from hestia_earth.models.utils.site import valid_site_type
|
|
14
14
|
|
|
15
15
|
from .organicSoilCultivation_utils import (
|
|
16
|
-
assign_organic_soil_category, calc_emission,
|
|
17
|
-
OrganicSoilCategory, valid_eco_climate_zone
|
|
16
|
+
assign_organic_soil_category, calc_emission, get_emission_factor, OrganicSoilCategory, valid_eco_climate_zone
|
|
18
17
|
)
|
|
19
18
|
from . import MODEL
|
|
20
19
|
|
|
@@ -183,8 +182,8 @@ def _should_run(cycle: dict):
|
|
|
183
182
|
eco_climate_zone=eco_climate_zone,
|
|
184
183
|
organic_soil_category=organic_soil_category,
|
|
185
184
|
emission_factor=format_nd_array(emission_factor),
|
|
186
|
-
land_occupation=
|
|
187
|
-
histosol=
|
|
185
|
+
land_occupation=format_float(land_occupation),
|
|
186
|
+
histosol=format_float(histosol)
|
|
188
187
|
)
|
|
189
188
|
|
|
190
189
|
should_run = all([
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from hestia_earth.schema import EmissionMethodTier
|
|
2
2
|
|
|
3
|
-
from hestia_earth.models.log import logRequirements, logShouldRun
|
|
4
3
|
from hestia_earth.models.utils.emission import _new_emission
|
|
5
4
|
|
|
6
5
|
from .organicCarbonPerHa_tier_1 import _assign_ipcc_land_use_category, get_valid_management_nodes
|
|
@@ -43,8 +42,10 @@ RETURNS = {
|
|
|
43
42
|
}
|
|
44
43
|
TERM_ID = 'co2ToAirSoilOrganicCarbonStockChangeLandUseChange,co2ToAirSoilOrganicCarbonStockChangeManagementChange'
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
_TERM_IDS = TERM_ID.split(",")
|
|
46
|
+
|
|
47
|
+
_LU_EMISSION_TERM_ID = _TERM_IDS[0]
|
|
48
|
+
_MG_EMISSION_TERM_ID = _TERM_IDS[1]
|
|
48
49
|
|
|
49
50
|
_DEPTH_UPPER = 0
|
|
50
51
|
_DEPTH_LOWER = 30
|
|
@@ -115,24 +116,18 @@ def run(cycle: dict) -> list[dict]:
|
|
|
115
116
|
"""
|
|
116
117
|
should_run_exec = create_should_run_function(
|
|
117
118
|
_CARBON_STOCK_TERM_ID,
|
|
119
|
+
_LU_EMISSION_TERM_ID,
|
|
120
|
+
_MG_EMISSION_TERM_ID,
|
|
118
121
|
depth_upper=_DEPTH_UPPER,
|
|
119
122
|
depth_lower=_DEPTH_LOWER,
|
|
120
|
-
|
|
123
|
+
measurements_required=False, # Model can allocate zero emissions to LUC with enough landCover data
|
|
121
124
|
get_valid_management_nodes_func=get_valid_management_nodes,
|
|
122
125
|
summarise_land_use_func=lambda nodes: _assign_ipcc_land_use_category(nodes, None),
|
|
123
126
|
detect_land_use_change_func=lambda a, b: a != b
|
|
124
127
|
)
|
|
125
128
|
|
|
126
|
-
run_exec = create_run_function(
|
|
127
|
-
new_emission_func=_emission,
|
|
128
|
-
land_use_change_emission_term_id=_LU_EMISSION_TERM_ID,
|
|
129
|
-
management_change_emission_term_id=_MG_EMISSION_TERM_ID
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
should_run, cycle_id, inventory, logs = should_run_exec(cycle)
|
|
129
|
+
run_exec = create_run_function(new_emission_func=_emission)
|
|
133
130
|
|
|
134
|
-
|
|
135
|
-
logRequirements(cycle, model=MODEL, term=term_id, **logs)
|
|
136
|
-
logShouldRun(cycle, MODEL, term_id, should_run)
|
|
131
|
+
should_run, assigned_emissions = should_run_exec(cycle)
|
|
137
132
|
|
|
138
|
-
return run_exec(
|
|
133
|
+
return run_exec(assigned_emissions) if should_run else []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from hestia_earth.schema import EmissionMethodTier
|
|
2
2
|
|
|
3
|
-
from hestia_earth.models.log import logRequirements, logShouldRun
|
|
3
|
+
from hestia_earth.models.log import format_float, logRequirements, logShouldRun
|
|
4
4
|
from hestia_earth.models.utils.cycle import land_occupation_per_ha
|
|
5
5
|
from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
|
|
6
6
|
from hestia_earth.models.utils.emission import _new_emission
|
|
@@ -8,7 +8,7 @@ from hestia_earth.models.utils.measurement import most_relevant_measurement_valu
|
|
|
8
8
|
from hestia_earth.models.utils.site import valid_site_type
|
|
9
9
|
|
|
10
10
|
from .organicSoilCultivation_utils import (
|
|
11
|
-
assign_organic_soil_category, calc_emission,
|
|
11
|
+
assign_organic_soil_category, calc_emission, get_emission_factor, OrganicSoilCategory,
|
|
12
12
|
remap_categories, valid_eco_climate_zone
|
|
13
13
|
)
|
|
14
14
|
from . import MODEL
|
|
@@ -122,9 +122,9 @@ def _should_run(cycle: dict):
|
|
|
122
122
|
cycle, model=MODEL, term=TERM_ID,
|
|
123
123
|
eco_climate_zone=eco_climate_zone,
|
|
124
124
|
organic_soil_category=organic_soil_category,
|
|
125
|
-
emission_factor=f"{
|
|
126
|
-
land_occupation=
|
|
127
|
-
histosol=
|
|
125
|
+
emission_factor=f"{format_float(emission_factor_mean)} ± {format_float(emission_factor_sd)}",
|
|
126
|
+
land_occupation=format_float(land_occupation),
|
|
127
|
+
histosol=format_float(histosol)
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
should_run = all([
|
|
@@ -10,7 +10,10 @@ from hestia_earth.utils.tools import safe_parse_float
|
|
|
10
10
|
from hestia_earth.utils.stats import gen_seed, repeat_single, truncated_normal_1d
|
|
11
11
|
from hestia_earth.utils.descriptive_stats import calc_descriptive_stats
|
|
12
12
|
|
|
13
|
-
from hestia_earth.models.log import
|
|
13
|
+
from hestia_earth.models.log import (
|
|
14
|
+
debugMissingLookup, format_bool, format_decimal_percentage, format_float, format_nd_array, format_str, log_as_table,
|
|
15
|
+
logRequirements, logShouldRun
|
|
16
|
+
)
|
|
14
17
|
from hestia_earth.models.utils.blank_node import group_nodes_by_year
|
|
15
18
|
from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
|
|
16
19
|
from hestia_earth.models.utils.emission import _new_emission
|
|
@@ -670,60 +673,28 @@ def _compile_inventory(
|
|
|
670
673
|
return should_run, inventory, logs
|
|
671
674
|
|
|
672
675
|
|
|
673
|
-
def _format_bool(value: Optional[bool]) -> str:
|
|
674
|
-
"""Format a bool for logging in a table."""
|
|
675
|
-
return str(bool(value))
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
def _format_number(value: Optional[float], unit: Optional[str] = None) -> str:
|
|
679
|
-
"""Format a float for logging in a table."""
|
|
680
|
-
return f"{value:.1f}{f' {unit}' if unit else ''}" if isinstance(value, (float, int)) else "None"
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
def _format_nd_array(value: Optional[npt.NDArray], unit: Optional[str] = None) -> str:
|
|
684
|
-
"""Format a numpy array for logging in a table."""
|
|
685
|
-
return (
|
|
686
|
-
f"{_format_number(value.mean())} ± {_format_number(value.std())}" + f"{f' {unit}' if unit else ''}"
|
|
687
|
-
if isinstance(value, np.ndarray) else "None"
|
|
688
|
-
)
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
def _format_decimal_percentage(value: Optional[float], unit: Optional[str] = "pct") -> str:
|
|
692
|
-
"""Format a decimal percentage (0-1) as a percentage (0-100%) for logging in a table."""
|
|
693
|
-
return _format_number(value * 100, unit) if isinstance(value, (float, int)) else "None"
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
_INVALID_CHARS = {"_", ":", ",", "="}
|
|
697
|
-
_REPLACEMENT_CHAR = "-"
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
def _format_str(value: str, *_) -> str:
|
|
701
|
-
"""Format a string for logging in a table. Remove all characters used to render the table on the front end."""
|
|
702
|
-
return reduce(lambda x, char: x.replace(char, _REPLACEMENT_CHAR), _INVALID_CHARS, str(value))
|
|
703
|
-
|
|
704
|
-
|
|
705
676
|
def _format_column_header(*keys: tuple[Union[Enum, str], ...]) -> str:
|
|
706
677
|
"""Format a variable number of enums and strings for logging as a table column header."""
|
|
707
|
-
return " ".join(
|
|
678
|
+
return " ".join(format_str(k.value if isinstance(k, Enum) else format_str(k)) for k in keys)
|
|
708
679
|
|
|
709
680
|
|
|
710
681
|
def _format_eco_climate_zone(value: EcoClimateZone) -> str:
|
|
711
682
|
"""Format an eco-climate zone for logging."""
|
|
712
683
|
return (
|
|
713
|
-
|
|
684
|
+
format_str(str(value.name).lower().replace("_", " ").capitalize()) if isinstance(value, EcoClimateZone)
|
|
714
685
|
else "None"
|
|
715
686
|
)
|
|
716
687
|
|
|
717
688
|
|
|
718
689
|
_LOGS_FORMAT_DATA: dict[str, Callable] = {
|
|
719
|
-
"has_valid_site_type":
|
|
690
|
+
"has_valid_site_type": format_bool,
|
|
720
691
|
"eco_climate_zone": _format_eco_climate_zone,
|
|
721
|
-
"has_valid_eco_climate_zone":
|
|
722
|
-
"has_land_cover_nodes":
|
|
723
|
-
"should_compile_inventory":
|
|
724
|
-
"percent_burned": lambda x:
|
|
692
|
+
"has_valid_eco_climate_zone": format_bool,
|
|
693
|
+
"has_land_cover_nodes": format_bool,
|
|
694
|
+
"should_compile_inventory": format_bool,
|
|
695
|
+
"percent_burned": lambda x: format_float(x, "pct"),
|
|
725
696
|
}
|
|
726
|
-
_DEFAULT_FORMAT_FUNC =
|
|
697
|
+
_DEFAULT_FORMAT_FUNC = format_str
|
|
727
698
|
|
|
728
699
|
|
|
729
700
|
def _format_logs(logs: dict) -> dict[str, str]:
|
|
@@ -735,23 +706,23 @@ def _format_logs(logs: dict) -> dict[str, str]:
|
|
|
735
706
|
|
|
736
707
|
_INVENTORY_FORMAT_DATA: dict[_InventoryKey, dict[Literal["filter_by", "format_func"], Any]] = {
|
|
737
708
|
"fuel_burnt_per_category": {
|
|
738
|
-
"format_func": lambda x:
|
|
709
|
+
"format_func": lambda x: format_nd_array(x, "kg")
|
|
739
710
|
},
|
|
740
711
|
"annual_emissions": {
|
|
741
712
|
"filter_by": ("term_id", ),
|
|
742
|
-
"format_func": lambda x:
|
|
713
|
+
"format_func": lambda x: format_nd_array(x, "kg")
|
|
743
714
|
},
|
|
744
715
|
"amortised_emissions": {
|
|
745
716
|
"filter_by": ("term_id", ),
|
|
746
|
-
"format_func": lambda x:
|
|
717
|
+
"format_func": lambda x: format_nd_array(x, "kg")
|
|
747
718
|
},
|
|
748
719
|
"share_of_emissions": {
|
|
749
720
|
"filter_by": ("cycle_id", ),
|
|
750
|
-
"format_func":
|
|
721
|
+
"format_func": format_decimal_percentage
|
|
751
722
|
},
|
|
752
723
|
"allocated_emissions": {
|
|
753
724
|
"filter_by": ("term_id", "cycle_id"),
|
|
754
|
-
"format_func": lambda x:
|
|
725
|
+
"format_func": lambda x: format_nd_array(x, "kg")
|
|
755
726
|
}
|
|
756
727
|
}
|
|
757
728
|
"""
|
|
@@ -840,7 +811,7 @@ def _log_emission_data(should_run: bool, term_id: _EmissionTermId, cycle: dict,
|
|
|
840
811
|
formatted_inventory = _format_inventory(term_id, cycle.get("@id"), inventory)
|
|
841
812
|
|
|
842
813
|
logRequirements(cycle, model=MODEL, term=term_id, **formatted_logs, inventory=formatted_inventory)
|
|
843
|
-
logShouldRun(cycle, MODEL, term_id, should_run)
|
|
814
|
+
logShouldRun(cycle, MODEL, term_id, should_run, methodTier=TIER)
|
|
844
815
|
|
|
845
816
|
|
|
846
817
|
def _should_run(cycle: dict):
|
|
@@ -2,9 +2,9 @@ from functools import reduce
|
|
|
2
2
|
from pydash.objects import merge
|
|
3
3
|
from types import ModuleType
|
|
4
4
|
|
|
5
|
-
from hestia_earth.models.log import log_as_table, logRequirements, logShouldRun
|
|
5
|
+
from hestia_earth.models.log import format_bool, format_enum, format_float, log_as_table, logRequirements, logShouldRun
|
|
6
6
|
|
|
7
|
-
from .organicCarbonPerHa_utils import
|
|
7
|
+
from .organicCarbonPerHa_utils import format_bool_list, format_float_list
|
|
8
8
|
from . import organicCarbonPerHa_tier_1 as tier_1
|
|
9
9
|
from . import organicCarbonPerHa_tier_2 as tier_2
|
|
10
10
|
from . import MODEL # noqa
|
|
@@ -204,20 +204,20 @@ def _get_unique_inventory_keys(inventory: dict) -> list:
|
|
|
204
204
|
|
|
205
205
|
_INVENTORY_KEY_TO_FORMAT_FUNC = {
|
|
206
206
|
tier_2._InventoryKey.SHOULD_RUN: format_bool,
|
|
207
|
-
tier_2._InventoryKey.TEMP_MONTHLY:
|
|
208
|
-
tier_2._InventoryKey.PRECIP_MONTHLY:
|
|
209
|
-
tier_2._InventoryKey.PET_MONTHLY:
|
|
207
|
+
tier_2._InventoryKey.TEMP_MONTHLY: format_float_list,
|
|
208
|
+
tier_2._InventoryKey.PRECIP_MONTHLY: format_float_list,
|
|
209
|
+
tier_2._InventoryKey.PET_MONTHLY: format_float_list,
|
|
210
210
|
tier_2._InventoryKey.IRRIGATED_MONTHLY: format_bool_list,
|
|
211
|
-
tier_2._InventoryKey.SAND_CONTENT:
|
|
212
|
-
tier_2._InventoryKey.CARBON_INPUT:
|
|
213
|
-
tier_2._InventoryKey.N_CONTENT:
|
|
214
|
-
tier_2._InventoryKey.LIGNIN_CONTENT:
|
|
211
|
+
tier_2._InventoryKey.SAND_CONTENT: format_float,
|
|
212
|
+
tier_2._InventoryKey.CARBON_INPUT: format_float,
|
|
213
|
+
tier_2._InventoryKey.N_CONTENT: format_float,
|
|
214
|
+
tier_2._InventoryKey.LIGNIN_CONTENT: format_float,
|
|
215
215
|
tier_2._InventoryKey.TILLAGE_CATEGORY: format_enum,
|
|
216
216
|
tier_2._InventoryKey.IS_PADDY_RICE: format_bool,
|
|
217
217
|
tier_1._InventoryKey.SHOULD_RUN: format_bool,
|
|
218
218
|
tier_1._InventoryKey.LU_CATEGORY: format_enum,
|
|
219
219
|
tier_1._InventoryKey.MG_CATEGORY: format_enum,
|
|
220
|
-
tier_1._InventoryKey.CI_CATEGORY: format_enum
|
|
220
|
+
tier_1._InventoryKey.CI_CATEGORY: format_enum
|
|
221
221
|
}
|
|
222
222
|
"""
|
|
223
223
|
Map inventory keys to format functions. The columns in inventory logged as a table will also be sorted in the order of
|