hestia-earth-models 0.64.9__py3-none-any.whl → 0.64.10__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/cml2001Baseline/abioticResourceDepletionFossilFuels.py +175 -0
- hestia_earth/models/cml2001Baseline/abioticResourceDepletionMineralsAndMetals.py +136 -0
- hestia_earth/models/environmentalFootprintV3/soilQualityIndexLandTransformation.py +2 -2
- hestia_earth/models/ipcc2019/aboveGroundBiomass.py +31 -243
- hestia_earth/models/ipcc2019/belowGroundBiomass.py +529 -0
- hestia_earth/models/ipcc2019/biomass_utils.py +406 -0
- hestia_earth/models/ipcc2019/{co2ToAirAboveGroundBiomassStockChangeLandUseChange.py → co2ToAirAboveGroundBiomassStockChange.py} +19 -7
- hestia_earth/models/ipcc2019/{co2ToAirBelowGroundBiomassStockChangeLandUseChange.py → co2ToAirBelowGroundBiomassStockChange.py} +19 -7
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +402 -73
- hestia_earth/models/ipcc2019/{co2ToAirSoilOrganicCarbonStockChangeManagementChange.py → co2ToAirSoilOrganicCarbonStockChange.py} +20 -8
- hestia_earth/models/ipcc2019/organicCarbonPerHa.py +3 -1
- hestia_earth/models/ipcc2019/pastureGrass_utils.py +6 -7
- hestia_earth/models/lcImpactAllEffects100Years/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToFreshwaterEcosystemsWaterStress.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthParticulateMatterFormation.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthWaterStress.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToMarineEcosystemsMarineEutrophication.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToFreshwaterEcosystemsWaterStress.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthParticulateMatterFormation.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthWaterStress.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToMarineEcosystemsMarineEutrophication.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToFreshwaterEcosystemsWaterStress.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthParticulateMatterFormation.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthWaterStress.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToMarineEcosystemsMarineEutrophication.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToFreshwaterEcosystemsWaterStress.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthParticulateMatterFormation.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthWaterStress.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToMarineEcosystemsMarineEutrophication.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
- hestia_earth/models/mocking/build_mock_search.py +44 -0
- hestia_earth/models/mocking/mock_search.py +8 -49
- hestia_earth/models/mocking/search-results.json +3075 -578
- hestia_earth/models/poschEtAl2008/terrestrialAcidificationPotentialAccumulatedExceedance.py +3 -3
- hestia_earth/models/poschEtAl2008/terrestrialEutrophicationPotentialAccumulatedExceedance.py +3 -3
- hestia_earth/models/preload_requests.py +1 -1
- hestia_earth/models/schmidt2007/utils.py +13 -4
- hestia_earth/models/utils/blank_node.py +73 -3
- hestia_earth/models/utils/constant.py +8 -1
- hestia_earth/models/utils/cycle.py +10 -13
- hestia_earth/models/utils/fuel.py +1 -1
- hestia_earth/models/utils/impact_assessment.py +31 -16
- hestia_earth/models/utils/lookup.py +36 -7
- hestia_earth/models/utils/pesticideAI.py +1 -1
- hestia_earth/models/utils/property.py +11 -4
- hestia_earth/models/utils/term.py +15 -8
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.64.9.dist-info → hestia_earth_models-0.64.10.dist-info}/METADATA +2 -2
- {hestia_earth_models-0.64.9.dist-info → hestia_earth_models-0.64.10.dist-info}/RECORD +78 -71
- {hestia_earth_models-0.64.9.dist-info → hestia_earth_models-0.64.10.dist-info}/WHEEL +1 -1
- tests/models/cml2001Baseline/test_abioticResourceDepletionFossilFuels.py +196 -0
- tests/models/cml2001Baseline/test_abioticResourceDepletionMineralsAndMetals.py +124 -0
- tests/models/edip2003/test_ozoneDepletionPotential.py +1 -13
- tests/models/environmentalFootprintV3/test_soilQualityIndexLandTransformation.py +1 -2
- tests/models/impact_assessment/test_emissions.py +1 -0
- tests/models/ipcc2019/test_aboveGroundBiomass.py +27 -63
- tests/models/ipcc2019/test_belowGroundBiomass.py +146 -0
- tests/models/ipcc2019/test_biomass_utils.py +115 -0
- tests/models/ipcc2019/{test_co2ToAirAboveGroundBiomassStockChangeLandUseChange.py → test_co2ToAirAboveGroundBiomassStockChange.py} +5 -5
- tests/models/ipcc2019/{test_co2ToAirBelowGroundBiomassStockChangeLandUseChange.py → test_co2ToAirBelowGroundBiomassStockChange.py} +5 -5
- tests/models/ipcc2019/{test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py → test_co2ToAirSoilOrganicCarbonStockChange.py} +5 -5
- tests/models/ipcc2021/test_gwp100.py +2 -2
- hestia_earth/models/ipcc2019/aboveGroundBiomass_utils.py +0 -180
- tests/models/ipcc2019/test_aboveGroundBiomass_utils.py +0 -92
- {hestia_earth_models-0.64.9.dist-info → hestia_earth_models-0.64.10.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.64.9.dist-info → hestia_earth_models-0.64.10.dist-info}/top_level.txt +0 -0
|
@@ -10,12 +10,13 @@ from itertools import product
|
|
|
10
10
|
from numpy import array, random, mean
|
|
11
11
|
from numpy.typing import NDArray
|
|
12
12
|
from pydash.objects import merge
|
|
13
|
-
from typing import Callable, NamedTuple, Optional, Union
|
|
13
|
+
from typing import Any, Callable, NamedTuple, Optional, Union
|
|
14
14
|
|
|
15
15
|
from hestia_earth.schema import (
|
|
16
|
-
EmissionMethodTier, EmissionStatsDefinition, MeasurementMethodClassification
|
|
16
|
+
EmissionMethodTier, EmissionStatsDefinition, MeasurementMethodClassification, TermTermType
|
|
17
17
|
)
|
|
18
18
|
from hestia_earth.utils.date import diff_in_days, YEAR
|
|
19
|
+
from hestia_earth.utils.model import filter_list_term_type
|
|
19
20
|
from hestia_earth.utils.tools import flatten, non_empty_list, safe_parse_date
|
|
20
21
|
|
|
21
22
|
from hestia_earth.models.log import log_as_table
|
|
@@ -44,10 +45,12 @@ _MAX_CORRELATION = 1
|
|
|
44
45
|
_MIN_CORRELATION = 0.5
|
|
45
46
|
_NOMINAL_ERROR = 75
|
|
46
47
|
"""
|
|
47
|
-
|
|
48
|
+
Carbon stock measurements without an associated `sd` should be assigned a nominal error of 75% (2*sd as a percentage of
|
|
48
49
|
the mean).
|
|
49
50
|
"""
|
|
50
|
-
|
|
51
|
+
_DEFAULT_YEARS_SINCE_LUC_EVENT = 999
|
|
52
|
+
_TRANSITION_PERIOD_YEARS = 20
|
|
53
|
+
_TRANSITION_PERIOD_DAYS = 20 * YEAR # 20 years in days
|
|
51
54
|
_VALID_DATE_FORMATS = {
|
|
52
55
|
DatestrFormat.YEAR,
|
|
53
56
|
DatestrFormat.YEAR_MONTH,
|
|
@@ -91,6 +94,9 @@ class _InventoryKey(Enum):
|
|
|
91
94
|
CARBON_STOCK_CHANGE = "carbon-stock-change"
|
|
92
95
|
CO2_EMISSION = "carbon-emission"
|
|
93
96
|
SHARE_OF_EMISSION = "share-of-emissions"
|
|
97
|
+
LAND_USE_SUMMARY = "land-use-summary"
|
|
98
|
+
LAND_USE_CHANGE_EVENT = "luc-event"
|
|
99
|
+
YEARS_SINCE_LUC_EVENT = "years-since-luc-event"
|
|
94
100
|
|
|
95
101
|
|
|
96
102
|
CarbonStock = NamedTuple("CarbonStock", [
|
|
@@ -319,9 +325,12 @@ def _add_carbon_stock_change_emissions(
|
|
|
319
325
|
|
|
320
326
|
|
|
321
327
|
def create_should_run_function(
|
|
328
|
+
*,
|
|
322
329
|
carbon_stock_term_id: str,
|
|
323
330
|
should_compile_inventory_func: Callable[[dict, list[dict], list[dict]], tuple[bool, dict]],
|
|
324
|
-
|
|
331
|
+
summarise_land_use_func: Callable[[list[dict]], Any],
|
|
332
|
+
detect_land_use_change_func: Callable[[Any, Any], bool],
|
|
333
|
+
should_run_measurement_func: Callable[[dict], bool] = lambda *_: True,
|
|
325
334
|
measurement_method_ranking: list[MeasurementMethodClassification] = DEFAULT_MEASUREMENT_METHOD_RANKING
|
|
326
335
|
) -> Callable[[dict], tuple[bool, str, dict, dict]]:
|
|
327
336
|
"""
|
|
@@ -342,6 +351,15 @@ def create_should_run_function(
|
|
|
342
351
|
`(site: dict, cycles: list[dict], carbon_stock_measurements: list[dict]) -> (should_run: bool, logs: dict)`, to
|
|
343
352
|
determine whether there is enough site and cycles data available to compile the carbon stock change inventory.
|
|
344
353
|
|
|
354
|
+
summarise_land_use_func: Callable[[list[dict]], Any]
|
|
355
|
+
A function with the signature `(nodes: list[dict]) -> Any`, to reduce a list of `landCover`
|
|
356
|
+
[Management](https://www.hestia.earth/schema/Management) nodes into a land use summary that can be compared
|
|
357
|
+
with other summaries to determine whether land use change events have occured.
|
|
358
|
+
|
|
359
|
+
detect_land_use_change_func: Callable[[Any, Any], bool]
|
|
360
|
+
A function with the signature `(summary_a: Any, summary_b: Any) -> bool`, to determine whether a land use
|
|
361
|
+
change event has occured.
|
|
362
|
+
|
|
345
363
|
should_run_measurement_func : Callable[[dict], bool], optional.
|
|
346
364
|
An optional measurement validation function, with the signature `(measurement: dict) -> bool`, that can be used
|
|
347
365
|
to add in additional criteria (`depthUpper`, `depthLower`, etc.) for the inclusion of a measurement in the
|
|
@@ -396,6 +414,8 @@ def create_should_run_function(
|
|
|
396
414
|
])
|
|
397
415
|
]
|
|
398
416
|
|
|
417
|
+
land_cover_nodes = filter_list_term_type(site.get("management", []), TermTermType.LANDCOVER)
|
|
418
|
+
|
|
399
419
|
seed = gen_seed(site) # All cycles linked to the same site should be consistent
|
|
400
420
|
rng = random.default_rng(seed)
|
|
401
421
|
|
|
@@ -403,14 +423,20 @@ def create_should_run_function(
|
|
|
403
423
|
site, cycles, carbon_stock_measurements
|
|
404
424
|
)
|
|
405
425
|
|
|
426
|
+
compile_inventory_func = _create_compile_inventory_function(
|
|
427
|
+
summarise_land_use_func=summarise_land_use_func,
|
|
428
|
+
detect_land_use_change_func=detect_land_use_change_func,
|
|
429
|
+
iterations=_ITERATIONS,
|
|
430
|
+
seed=rng,
|
|
431
|
+
measurement_method_ranking=measurement_method_ranking
|
|
432
|
+
)
|
|
433
|
+
|
|
406
434
|
inventory, inventory_logs = (
|
|
407
|
-
|
|
435
|
+
compile_inventory_func(
|
|
408
436
|
cycle_id,
|
|
409
437
|
cycles,
|
|
410
438
|
carbon_stock_measurements,
|
|
411
|
-
|
|
412
|
-
seed=rng,
|
|
413
|
-
measurement_method_ranking=measurement_method_ranking
|
|
439
|
+
land_cover_nodes
|
|
414
440
|
) if should_compile_inventory else ({}, {})
|
|
415
441
|
)
|
|
416
442
|
|
|
@@ -462,69 +488,126 @@ def _get_site(cycle: dict) -> dict:
|
|
|
462
488
|
return cycle.get("site", {})
|
|
463
489
|
|
|
464
490
|
|
|
465
|
-
def
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
491
|
+
def _create_compile_inventory_function(
|
|
492
|
+
*,
|
|
493
|
+
summarise_land_use_func: Callable[[list[dict]], Any],
|
|
494
|
+
detect_land_use_change_func: Callable[[Any, Any], bool],
|
|
469
495
|
iterations: int = 10000,
|
|
470
496
|
seed: Union[int, random.Generator, None] = None,
|
|
471
497
|
measurement_method_ranking: list[MeasurementMethodClassification] = DEFAULT_MEASUREMENT_METHOD_RANKING
|
|
472
|
-
) ->
|
|
498
|
+
) -> Callable:
|
|
473
499
|
"""
|
|
474
|
-
|
|
475
|
-
of emissions of cycles based on the provided cycles and measurement data.
|
|
500
|
+
Create a compile inventory function for an emissions from carbon stock change model.
|
|
476
501
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
The final inventory structure is:
|
|
482
|
-
```
|
|
483
|
-
{
|
|
484
|
-
year (int): {
|
|
485
|
-
_InventoryKey.CARBON_STOCK: value (CarbonStock),
|
|
486
|
-
_InventoryKey.CARBON_STOCK_CHANGE: value (CarbonStockChange),
|
|
487
|
-
_InventoryKey.CO2_EMISSION: value (CarbonStockChangeEmission),
|
|
488
|
-
_InventoryKey.SHARE_OF_EMISSION: {
|
|
489
|
-
cycle_id (str): value (float),
|
|
490
|
-
...cycle_ids
|
|
491
|
-
}
|
|
492
|
-
},
|
|
493
|
-
...years
|
|
494
|
-
}
|
|
495
|
-
```
|
|
502
|
+
Model-specific validation functions should be passed as parameters to this higher order function to determine how
|
|
503
|
+
land cover data is reduced down to a land use summary and how those land use summaries should be compared to
|
|
504
|
+
determine when land use change events have occured.
|
|
496
505
|
|
|
497
506
|
Parameters
|
|
498
507
|
----------
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
508
|
+
summarise_land_use_func: Callable[[list[dict]], Any]
|
|
509
|
+
A function with the signature `(nodes: list[dict]) -> Any`, to reduce a list of `landCover`
|
|
510
|
+
[Management](https://www.hestia.earth/schema/Management) nodes into a land use summary that can be compared
|
|
511
|
+
with other summaries to determine whether land use change events have occured.
|
|
512
|
+
|
|
513
|
+
detect_land_use_change_func: Callable[[Any, Any], bool]
|
|
514
|
+
A function with the signature `(summary_a: Any, summary_b: Any) -> bool`, to determine whether a land use
|
|
515
|
+
change event has occured.
|
|
516
|
+
|
|
505
517
|
iterations : int, optional
|
|
506
518
|
The number of iterations for stochastic processing (default is 10,000).
|
|
519
|
+
|
|
507
520
|
seed : int, random.Generator, or None, optional
|
|
508
521
|
Seed for random number generation to ensure reproducibility. Default is None.
|
|
509
522
|
|
|
523
|
+
measurement_method_ranking : list[MeasurementMethodClassification], optional
|
|
524
|
+
The order in which to prioritise `MeasurementMethodClassification`s when reducing the inventory down to a
|
|
525
|
+
single method per year. Defaults to:
|
|
526
|
+
```
|
|
527
|
+
MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT,
|
|
528
|
+
MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS,
|
|
529
|
+
MeasurementMethodClassification.TIER_3_MODEL,
|
|
530
|
+
MeasurementMethodClassification.TIER_2_MODEL,
|
|
531
|
+
MeasurementMethodClassification.TIER_1_MODEL
|
|
532
|
+
```
|
|
533
|
+
n.b., measurements with methods not included in the ranking will not be included in the inventory.
|
|
510
534
|
|
|
511
535
|
Returns
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
`
|
|
536
|
+
----------
|
|
537
|
+
Callable
|
|
538
|
+
The `compile_inventory` function.
|
|
515
539
|
"""
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
540
|
+
def compile_inventory(
|
|
541
|
+
cycle_id: str,
|
|
542
|
+
cycles: list[dict],
|
|
543
|
+
carbon_stock_measurements: list[dict],
|
|
544
|
+
land_cover_nodes: list[dict]
|
|
545
|
+
) -> tuple[dict, dict]:
|
|
546
|
+
"""
|
|
547
|
+
Compile an annual inventory of carbon stocks, changes in carbon stocks, carbon stock change emissions, and the
|
|
548
|
+
share of emissions of cycles based on the provided cycles and measurement data.
|
|
520
549
|
|
|
521
|
-
|
|
550
|
+
A separate inventory is compiled for each valid `MeasurementMethodClassification` present in the data, and the
|
|
551
|
+
strongest available method is chosen for each relevant inventory year. These inventories are then merged into
|
|
552
|
+
one final result.
|
|
553
|
+
|
|
554
|
+
The final inventory structure is:
|
|
555
|
+
```
|
|
556
|
+
{
|
|
557
|
+
year (int): {
|
|
558
|
+
_InventoryKey.CARBON_STOCK: value (CarbonStock),
|
|
559
|
+
_InventoryKey.CARBON_STOCK_CHANGE: value (CarbonStockChange),
|
|
560
|
+
_InventoryKey.CO2_EMISSION: value (CarbonStockChangeEmission),
|
|
561
|
+
_InventoryKey.SHARE_OF_EMISSION: {
|
|
562
|
+
cycle_id (str): value (float),
|
|
563
|
+
...cycle_ids
|
|
564
|
+
},
|
|
565
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT: value (int)
|
|
566
|
+
},
|
|
567
|
+
...years
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Parameters
|
|
572
|
+
----------
|
|
573
|
+
cycle_id : str
|
|
574
|
+
The unique identifier of the cycle being processed.
|
|
575
|
+
cycles : list[dict]
|
|
576
|
+
A list of [Cycle](https://www.hestia.earth/schema/Cycles) nodes related to the site.
|
|
577
|
+
carbon_stock_measurements : list[dict]
|
|
578
|
+
A list of [Measurement](https://www.hestia.earth/schema/Measurement) nodes, representing carbon stock
|
|
579
|
+
measurements across time and methods.
|
|
580
|
+
land_cover_nodes : list[dict]
|
|
581
|
+
A list of `landCover `[Management](https://www.hestia.earth/schema/Management) nodes, representing the
|
|
582
|
+
site's land cover over time.
|
|
522
583
|
|
|
523
|
-
inventory = _squash_inventory(
|
|
524
|
-
cycle_id, cycle_inventory, carbon_stock_inventory, measurement_method_ranking=measurement_method_ranking
|
|
525
|
-
)
|
|
526
584
|
|
|
527
|
-
|
|
585
|
+
Returns
|
|
586
|
+
-------
|
|
587
|
+
tuple[dict, dict]
|
|
588
|
+
`(inventory, logs)`
|
|
589
|
+
"""
|
|
590
|
+
cycle_inventory = _compile_cycle_inventory(cycles)
|
|
591
|
+
carbon_stock_inventory = _compile_carbon_stock_inventory(
|
|
592
|
+
carbon_stock_measurements, iterations=iterations, seed=seed
|
|
593
|
+
)
|
|
594
|
+
land_use_inventory = _compile_land_use_inventory(
|
|
595
|
+
land_cover_nodes, summarise_land_use_func, detect_land_use_change_func
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
inventory = _squash_inventory(
|
|
599
|
+
cycle_id,
|
|
600
|
+
cycle_inventory,
|
|
601
|
+
carbon_stock_inventory,
|
|
602
|
+
land_use_inventory,
|
|
603
|
+
measurement_method_ranking=measurement_method_ranking
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
logs = _generate_logs(cycle_inventory, carbon_stock_inventory, land_use_inventory)
|
|
607
|
+
|
|
608
|
+
return inventory, logs
|
|
609
|
+
|
|
610
|
+
return compile_inventory
|
|
528
611
|
|
|
529
612
|
|
|
530
613
|
def _compile_cycle_inventory(cycles: list[dict]) -> dict:
|
|
@@ -724,7 +807,7 @@ def _preprocess_carbon_stocks(
|
|
|
724
807
|
dates,
|
|
725
808
|
decay_fn=lambda dt: exponential_decay(
|
|
726
809
|
dt,
|
|
727
|
-
tau=calc_tau(
|
|
810
|
+
tau=calc_tau(_TRANSITION_PERIOD_DAYS),
|
|
728
811
|
initial_value=_MAX_CORRELATION,
|
|
729
812
|
final_value=_MIN_CORRELATION
|
|
730
813
|
)
|
|
@@ -846,6 +929,100 @@ def _calculate_co2_emissions(carbon_stock_changes_by_year: dict) -> dict:
|
|
|
846
929
|
}
|
|
847
930
|
|
|
848
931
|
|
|
932
|
+
def _compile_land_use_inventory(
|
|
933
|
+
land_cover_nodes: list[dict],
|
|
934
|
+
summarise_land_use_func: Callable[[list[dict]], Any],
|
|
935
|
+
detect_land_use_change_func: Callable[[Any, Any], bool]
|
|
936
|
+
) -> dict:
|
|
937
|
+
"""
|
|
938
|
+
Compile an annual inventory of land use data.
|
|
939
|
+
|
|
940
|
+
The returned inventory has the shape:
|
|
941
|
+
```
|
|
942
|
+
{
|
|
943
|
+
year (int): {
|
|
944
|
+
_InventoryKey.LAND_USE_SUMMARY: value (Any),
|
|
945
|
+
_InventoryKey.LAND_USE_CHANGE_EVENT: value (bool),
|
|
946
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT: value (int)
|
|
947
|
+
},
|
|
948
|
+
...years
|
|
949
|
+
}
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
Parameters
|
|
953
|
+
----------
|
|
954
|
+
land_cover_nodes : list[dict]
|
|
955
|
+
A list of `landCover` Management nodes, representing the site's land cover over time.
|
|
956
|
+
summarise_land_use_func: Callable[[list[dict]], Any]
|
|
957
|
+
A function with the signature `(nodes: list[dict]) -> Any`, to reduce a list of `landCover` Management nodes
|
|
958
|
+
into a land use summary that can be compared with other summaries to determine whether land use change events
|
|
959
|
+
have occured.
|
|
960
|
+
detect_land_use_change_func: Callable[[Any, Any], bool]
|
|
961
|
+
A function with the signature `(summary_a: Any, summary_b: Any) -> bool`, to determine whether a land use
|
|
962
|
+
change event has occured.
|
|
963
|
+
|
|
964
|
+
Returns
|
|
965
|
+
-------
|
|
966
|
+
dict
|
|
967
|
+
The land use inventory.
|
|
968
|
+
"""
|
|
969
|
+
land_cover_nodes_by_year = group_nodes_by_year(land_cover_nodes)
|
|
970
|
+
|
|
971
|
+
def build_inventory_year(result: dict, year_pair: tuple[int, int]) -> dict:
|
|
972
|
+
"""
|
|
973
|
+
Build a year of the inventory using the data from `land_cover_nodes_by_year`.
|
|
974
|
+
|
|
975
|
+
Parameters
|
|
976
|
+
----------
|
|
977
|
+
inventory: dict
|
|
978
|
+
The land cover change portion of the inventory. Must have the same shape as the returned dict.
|
|
979
|
+
year_pair : tuple[int, int]
|
|
980
|
+
A tuple with the shape `(prev_year, current_year)`.
|
|
981
|
+
Returns
|
|
982
|
+
-------
|
|
983
|
+
dict
|
|
984
|
+
The land use inventory.
|
|
985
|
+
"""
|
|
986
|
+
|
|
987
|
+
prev_year, current_year = year_pair
|
|
988
|
+
land_cover_nodes = land_cover_nodes_by_year.get(current_year, {})
|
|
989
|
+
|
|
990
|
+
land_use_summary = summarise_land_use_func(land_cover_nodes)
|
|
991
|
+
prev_land_use_summary = result.get(prev_year, {}).get(_InventoryKey.LAND_USE_SUMMARY, {})
|
|
992
|
+
|
|
993
|
+
is_luc_event = detect_land_use_change_func(land_use_summary, prev_land_use_summary)
|
|
994
|
+
|
|
995
|
+
time_delta = current_year - prev_year
|
|
996
|
+
prev_years_since_luc_event = result.get(prev_year, {}).get(_InventoryKey.YEARS_SINCE_LUC_EVENT, 0)
|
|
997
|
+
years_since_luc_event = time_delta if is_luc_event else prev_years_since_luc_event + time_delta
|
|
998
|
+
|
|
999
|
+
update_dict = {
|
|
1000
|
+
current_year: {
|
|
1001
|
+
_InventoryKey.LAND_USE_SUMMARY: land_use_summary,
|
|
1002
|
+
_InventoryKey.LAND_USE_CHANGE_EVENT: is_luc_event,
|
|
1003
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT: years_since_luc_event,
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return result | update_dict
|
|
1007
|
+
|
|
1008
|
+
should_run = len(land_cover_nodes_by_year) > 0
|
|
1009
|
+
start_year = min(land_cover_nodes_by_year.keys(), default=None)
|
|
1010
|
+
|
|
1011
|
+
return reduce(
|
|
1012
|
+
build_inventory_year,
|
|
1013
|
+
pairwise(land_cover_nodes_by_year.keys()), # Inventory years need data from previous year to be compiled.
|
|
1014
|
+
{
|
|
1015
|
+
start_year: {
|
|
1016
|
+
_InventoryKey.LAND_USE_SUMMARY: summarise_land_use_func(
|
|
1017
|
+
land_cover_nodes_by_year.get(start_year, [])
|
|
1018
|
+
),
|
|
1019
|
+
_InventoryKey.LAND_USE_CHANGE_EVENT: False,
|
|
1020
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT: _DEFAULT_YEARS_SINCE_LUC_EVENT
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
) if should_run else {}
|
|
1024
|
+
|
|
1025
|
+
|
|
849
1026
|
def _sorted_merge(*sources: Union[dict, list[dict]]) -> dict:
|
|
850
1027
|
"""
|
|
851
1028
|
Merge one or more dictionaries into a single dictionary, ensuring that the keys are sorted in temporal order.
|
|
@@ -873,6 +1050,7 @@ def _squash_inventory(
|
|
|
873
1050
|
cycle_id: str,
|
|
874
1051
|
cycle_inventory: dict,
|
|
875
1052
|
carbon_stock_inventory: dict,
|
|
1053
|
+
land_use_inventory: dict,
|
|
876
1054
|
measurement_method_ranking: list[MeasurementMethodClassification] = DEFAULT_MEASUREMENT_METHOD_RANKING
|
|
877
1055
|
) -> dict:
|
|
878
1056
|
"""
|
|
@@ -884,6 +1062,7 @@ def _squash_inventory(
|
|
|
884
1062
|
----------
|
|
885
1063
|
cycle_id : str
|
|
886
1064
|
The unique identifier of the cycle being processed.
|
|
1065
|
+
|
|
887
1066
|
cycle_inventory : dict
|
|
888
1067
|
A dictionary representing the share of emissions for each cycle, grouped by year.
|
|
889
1068
|
Format:
|
|
@@ -898,6 +1077,7 @@ def _squash_inventory(
|
|
|
898
1077
|
...more years
|
|
899
1078
|
}
|
|
900
1079
|
```
|
|
1080
|
+
|
|
901
1081
|
carbon_stock_inventory : dict
|
|
902
1082
|
A dictionary representing carbon stock and emissions data grouped by measurement method and year.
|
|
903
1083
|
Format:
|
|
@@ -915,6 +1095,32 @@ def _squash_inventory(
|
|
|
915
1095
|
}
|
|
916
1096
|
```
|
|
917
1097
|
|
|
1098
|
+
land_use_inventory : dict
|
|
1099
|
+
A dictionary representing land use and land use change data grouped by year.
|
|
1100
|
+
Format:
|
|
1101
|
+
```
|
|
1102
|
+
{
|
|
1103
|
+
year (int): {
|
|
1104
|
+
_InventoryKey.LAND_USE_SUMMARY: value (Any),
|
|
1105
|
+
_InventoryKey.LAND_USE_CHANGE_EVENT: value (bool),
|
|
1106
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT: value (int)
|
|
1107
|
+
},
|
|
1108
|
+
...years
|
|
1109
|
+
}
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
measurement_method_ranking : list[MeasurementMethodClassification], optional
|
|
1113
|
+
The order in which to prioritise `MeasurementMethodClassification`s when reducing the inventory down to a
|
|
1114
|
+
single method per year. Defaults to:
|
|
1115
|
+
```
|
|
1116
|
+
MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT,
|
|
1117
|
+
MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS,
|
|
1118
|
+
MeasurementMethodClassification.TIER_3_MODEL,
|
|
1119
|
+
MeasurementMethodClassification.TIER_2_MODEL,
|
|
1120
|
+
MeasurementMethodClassification.TIER_1_MODEL
|
|
1121
|
+
```
|
|
1122
|
+
n.b., measurements with methods not included in the ranking will not be included in the inventory.
|
|
1123
|
+
|
|
918
1124
|
Returns
|
|
919
1125
|
-------
|
|
920
1126
|
dict
|
|
@@ -951,8 +1157,12 @@ def _squash_inventory(
|
|
|
951
1157
|
def squash(result: dict, year: int) -> dict:
|
|
952
1158
|
update_dict = next(
|
|
953
1159
|
(
|
|
954
|
-
{
|
|
955
|
-
|
|
1160
|
+
{
|
|
1161
|
+
year: {
|
|
1162
|
+
**_get_land_use_change_data(year, land_use_inventory),
|
|
1163
|
+
**reduce(merge, [carbon_stock_inventory[method][year], cycle_inventory[year]], dict())
|
|
1164
|
+
}
|
|
1165
|
+
} for method in measurement_method_ranking if should_run_group(method, year)
|
|
956
1166
|
),
|
|
957
1167
|
{}
|
|
958
1168
|
)
|
|
@@ -961,9 +1171,44 @@ def _squash_inventory(
|
|
|
961
1171
|
return reduce(squash, inventory_years, dict())
|
|
962
1172
|
|
|
963
1173
|
|
|
964
|
-
def
|
|
1174
|
+
def _get_land_use_change_data(
|
|
1175
|
+
year: int,
|
|
1176
|
+
land_use_inventory: dict
|
|
1177
|
+
) -> dict:
|
|
1178
|
+
"""
|
|
1179
|
+
Retrieve a value for `_InventoryKey.YEARS_SINCE_LUC_EVENT` for a specific inventory year, or gapfill it from
|
|
1180
|
+
available data.
|
|
1181
|
+
|
|
1182
|
+
If no land use data is available in the inventory, the site is assumed to have a stable land use and all emissions
|
|
1183
|
+
will be allocated to management changes.
|
|
965
1184
|
"""
|
|
966
|
-
|
|
1185
|
+
closest_inventory_year = next(
|
|
1186
|
+
(key for key in land_use_inventory.keys() if key >= year), # get the next inventory year
|
|
1187
|
+
min(
|
|
1188
|
+
land_use_inventory.keys(), key=lambda x: abs(x - year), # else the previous
|
|
1189
|
+
default=None # else return `None`
|
|
1190
|
+
)
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
delta_time = closest_inventory_year - year if closest_inventory_year else 0
|
|
1194
|
+
inventory_data = land_use_inventory.get(closest_inventory_year, {})
|
|
1195
|
+
|
|
1196
|
+
years_since_luc_event = (
|
|
1197
|
+
inventory_data.get(_InventoryKey.YEARS_SINCE_LUC_EVENT, _DEFAULT_YEARS_SINCE_LUC_EVENT) - delta_time
|
|
1198
|
+
)
|
|
1199
|
+
|
|
1200
|
+
return {
|
|
1201
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT: years_since_luc_event
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
def _generate_logs(
|
|
1206
|
+
cycle_inventory: dict,
|
|
1207
|
+
carbon_stock_inventory: dict,
|
|
1208
|
+
land_use_inventory: dict
|
|
1209
|
+
) -> dict:
|
|
1210
|
+
"""
|
|
1211
|
+
Generate logs for the compiled inventory, providing details about cycle, carbon and land use inventories.
|
|
967
1212
|
|
|
968
1213
|
Parameters
|
|
969
1214
|
----------
|
|
@@ -971,6 +1216,8 @@ def _generate_logs(cycle_inventory: dict, carbon_stock_inventory: dict) -> dict:
|
|
|
971
1216
|
The compiled cycle inventory.
|
|
972
1217
|
carbon_stock_inventory : dict
|
|
973
1218
|
The compiled carbon stock inventory.
|
|
1219
|
+
land_use_inventory : dict
|
|
1220
|
+
The compiled carbon stock inventory.
|
|
974
1221
|
|
|
975
1222
|
Returns
|
|
976
1223
|
-------
|
|
@@ -980,6 +1227,7 @@ def _generate_logs(cycle_inventory: dict, carbon_stock_inventory: dict) -> dict:
|
|
|
980
1227
|
logs = {
|
|
981
1228
|
"cycle_inventory": _format_cycle_inventory(cycle_inventory),
|
|
982
1229
|
"carbon_stock_inventory": _format_carbon_stock_inventory(carbon_stock_inventory),
|
|
1230
|
+
"land_use_inventory": _format_land_use_inventory(land_use_inventory)
|
|
983
1231
|
}
|
|
984
1232
|
return logs
|
|
985
1233
|
|
|
@@ -1038,6 +1286,43 @@ def _format_carbon_stock_inventory(carbon_stock_inventory: dict) -> str:
|
|
|
1038
1286
|
) if should_run else "None"
|
|
1039
1287
|
|
|
1040
1288
|
|
|
1289
|
+
def _format_land_use_inventory(land_use_inventory: dict) -> str:
|
|
1290
|
+
"""
|
|
1291
|
+
Format the carbon stock inventory for logging as a table. Rows represent inventory years, columns represent land
|
|
1292
|
+
use change data. If the inventory is invalid, return `"None"` as a string.
|
|
1293
|
+
|
|
1294
|
+
TODO: Implement logging of land use summary.
|
|
1295
|
+
"""
|
|
1296
|
+
KEYS = [
|
|
1297
|
+
_InventoryKey.LAND_USE_CHANGE_EVENT,
|
|
1298
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT
|
|
1299
|
+
]
|
|
1300
|
+
|
|
1301
|
+
inventory_years = sorted(set(non_empty_list(years for years in land_use_inventory.keys())))
|
|
1302
|
+
should_run = land_use_inventory and len(inventory_years) > 0
|
|
1303
|
+
|
|
1304
|
+
return log_as_table(
|
|
1305
|
+
{
|
|
1306
|
+
"year": year,
|
|
1307
|
+
**{
|
|
1308
|
+
key.value: _LAND_USE_INVENTORY_KEY_TO_FORMAT_FUNC[key](
|
|
1309
|
+
land_use_inventory.get(year, {}).get(key)
|
|
1310
|
+
) for key in KEYS
|
|
1311
|
+
}
|
|
1312
|
+
} for year in inventory_years
|
|
1313
|
+
) if should_run else "None"
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
def _format_bool(value: Optional[bool]) -> str:
|
|
1317
|
+
"""Format a bool for logging in a table."""
|
|
1318
|
+
return str(value) if isinstance(value, bool) else "None"
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
def _format_int(value: Optional[float]) -> str:
|
|
1322
|
+
"""Format an int for logging in a table. If the value is invalid, return `"None"` as a string."""
|
|
1323
|
+
return f"{value:.0f}" if isinstance(value, (float, int)) else "None"
|
|
1324
|
+
|
|
1325
|
+
|
|
1041
1326
|
def _format_number(value: Optional[float]) -> str:
|
|
1042
1327
|
"""Format a float for logging in a table. If the value is invalid, return `"None"` as a string."""
|
|
1043
1328
|
return f"{value:.1f}" if isinstance(value, (float, int)) else "None"
|
|
@@ -1067,8 +1352,20 @@ def _format_named_tuple(value: Optional[Union[CarbonStock, CarbonStockChange, Ca
|
|
|
1067
1352
|
)
|
|
1068
1353
|
|
|
1069
1354
|
|
|
1355
|
+
_LAND_USE_INVENTORY_KEY_TO_FORMAT_FUNC = {
|
|
1356
|
+
_InventoryKey.LAND_USE_CHANGE_EVENT: _format_bool,
|
|
1357
|
+
_InventoryKey.YEARS_SINCE_LUC_EVENT: _format_int
|
|
1358
|
+
}
|
|
1359
|
+
"""
|
|
1360
|
+
Map inventory keys to format functions. The columns in inventory logged as a table will also be sorted in the order of
|
|
1361
|
+
the `dict` keys.
|
|
1362
|
+
"""
|
|
1363
|
+
|
|
1364
|
+
|
|
1070
1365
|
def create_run_function(
|
|
1071
|
-
new_emission_func: Callable[[EmissionMethodTier, dict], dict]
|
|
1366
|
+
new_emission_func: Callable[[EmissionMethodTier, dict], dict],
|
|
1367
|
+
land_use_change_emission_term_id: str,
|
|
1368
|
+
management_change_emission_term_id: str
|
|
1072
1369
|
) -> Callable[[str, dict], list[dict]]:
|
|
1073
1370
|
"""
|
|
1074
1371
|
Create a run function for an emissions from carbon stock change model.
|
|
@@ -1080,12 +1377,42 @@ def create_run_function(
|
|
|
1080
1377
|
----------
|
|
1081
1378
|
new_emission_func : Callable[[EmissionMethodTier, tuple], dict]
|
|
1082
1379
|
A function, with the signature `(method_tier: dict, **kwargs: dict) -> (emission_node: dict)`.
|
|
1380
|
+
land_use_change_emission_term_id : str
|
|
1381
|
+
The term id for emissions allocated to land use changes.
|
|
1382
|
+
management_change_emission_term_id : str
|
|
1383
|
+
The term id for emissions allocated to management changes.
|
|
1083
1384
|
|
|
1084
1385
|
Returns
|
|
1085
1386
|
-------
|
|
1086
1387
|
Callable[[str, dict], list[dict]]
|
|
1087
1388
|
The customised `run` function with the signature `(cycle_id: str, inventory: dict) -> emissions: list[dict]`.
|
|
1088
1389
|
"""
|
|
1390
|
+
def reduce_emissions(result: dict, year: int, cycle_id: str, inventory: dict):
|
|
1391
|
+
"""
|
|
1392
|
+
Assign emissions to either the land use or management change term ids and sum together.
|
|
1393
|
+
"""
|
|
1394
|
+
data = inventory[year]
|
|
1395
|
+
years_since_luc_event = data[_InventoryKey.YEARS_SINCE_LUC_EVENT]
|
|
1396
|
+
emission_term_id = (
|
|
1397
|
+
land_use_change_emission_term_id if years_since_luc_event <= _TRANSITION_PERIOD_YEARS
|
|
1398
|
+
else management_change_emission_term_id
|
|
1399
|
+
)
|
|
1400
|
+
|
|
1401
|
+
rescaled_emission = _rescale_carbon_stock_change_emission(
|
|
1402
|
+
data[_InventoryKey.CO2_EMISSION], data[_InventoryKey.SHARE_OF_EMISSION][cycle_id]
|
|
1403
|
+
)
|
|
1404
|
+
|
|
1405
|
+
previous_emission = result.get(emission_term_id)
|
|
1406
|
+
|
|
1407
|
+
update_dict = {
|
|
1408
|
+
emission_term_id: (
|
|
1409
|
+
_add_carbon_stock_change_emissions(previous_emission, rescaled_emission) if previous_emission
|
|
1410
|
+
else rescaled_emission
|
|
1411
|
+
)
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
return result | update_dict
|
|
1415
|
+
|
|
1089
1416
|
def run(cycle_id: str, inventory: dict) -> list[dict]:
|
|
1090
1417
|
"""
|
|
1091
1418
|
Calculate emissions for a specific cycle using from a carbon stock change using pre-compiled inventory data.
|
|
@@ -1103,22 +1430,24 @@ def create_run_function(
|
|
|
1103
1430
|
Returns
|
|
1104
1431
|
-------
|
|
1105
1432
|
list[dict]
|
|
1106
|
-
A list of [Emission
|
|
1433
|
+
A list of [Emission](https://www.hestia.earth/schema/Emission) nodes containing model results.
|
|
1107
1434
|
"""
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
]
|
|
1113
|
-
total_emission = reduce(_add_carbon_stock_change_emissions, rescaled_emissions)
|
|
1114
|
-
|
|
1115
|
-
descriptive_stats = calc_descriptive_stats(
|
|
1116
|
-
total_emission.value,
|
|
1117
|
-
EmissionStatsDefinition.SIMULATED,
|
|
1118
|
-
decimals=6
|
|
1435
|
+
assigned_emissions = reduce(
|
|
1436
|
+
lambda result, year: reduce_emissions(result, year, cycle_id, inventory),
|
|
1437
|
+
inventory.keys(),
|
|
1438
|
+
{}
|
|
1119
1439
|
)
|
|
1120
1440
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1441
|
+
return [
|
|
1442
|
+
new_emission_func(
|
|
1443
|
+
term_id=emission_term_id,
|
|
1444
|
+
method_tier=total_emission.method,
|
|
1445
|
+
**calc_descriptive_stats(
|
|
1446
|
+
total_emission.value,
|
|
1447
|
+
EmissionStatsDefinition.SIMULATED,
|
|
1448
|
+
decimals=6
|
|
1449
|
+
)
|
|
1450
|
+
) for emission_term_id, total_emission in assigned_emissions.items()
|
|
1451
|
+
]
|
|
1123
1452
|
|
|
1124
1453
|
return run
|