From 5cb0947df1495ada749dc13b8387dd48f8c66285 Mon Sep 17 00:00:00 2001 From: Chris Mutel Date: Mon, 17 Jun 2024 10:28:33 +0200 Subject: [PATCH] Fix SimaPro allocation - Can allocate functional inputs - Don't allocate nonfunctional production --- bw2io/strategies/simapro.py | 21 ++- tests/simapro.py | 15 --- tests/strategies/simapro_allocation.py | 171 +++++++++++++++++++++++++ 3 files changed, 189 insertions(+), 18 deletions(-) create mode 100644 tests/strategies/simapro_allocation.py diff --git a/bw2io/strategies/simapro.py b/bw2io/strategies/simapro.py index dc8f287..fbdc7f5 100644 --- a/bw2io/strategies/simapro.py +++ b/bw2io/strategies/simapro.py @@ -17,6 +17,16 @@ detoxify_re = re.compile(detoxify_pattern) +def functional(exc: dict) -> bool: + """Determine if an exchange is functional by looking at `type` and `functional` attributes.""" + if exc.get("functional"): + return True + # Backwards compatibility, but makes me uncomfortable. Much better to label explicitly. + elif "functional" not in exc and exc["type"] == "production": + return True + return False + + def sp_allocate_products(db): """ Allocate products in a SimaPro dataset by creating a separate dataset for each product. @@ -86,7 +96,9 @@ def sp_allocate_products(db): if not ds["type"] == "multifunctional": continue products = [ - exc for exc in ds.get("exchanges", []) if exc["type"] == "production" + exc + for exc in ds.get("exchanges", []) + if functional(exc) ] for product in products: if not isinstance(product.get("allocation"), Number): @@ -106,15 +118,18 @@ def sp_allocate_products(db): continue new = copy.deepcopy(ds) - new["exchanges"] = [copy.deepcopy(product).pop("allocation")] + [ + production_exc = copy.deepcopy(product) + del production_exc['allocation'] + new["exchanges"] = [production_exc] + [ rescale_exchange(exc, allocation) for exc in new["exchanges"] - if exc["type"] != "production" + if not functional(exc) ] # Just how SimaPro rolls... new["name"] = new["reference product"] = product["name"] new["unit"] = product["unit"] new["production amount"] = product["amount"] + new["type"] = "process" new_data.append(new) return db + new_data diff --git a/tests/simapro.py b/tests/simapro.py index c1cd86d..bbc4283 100644 --- a/tests/simapro.py +++ b/tests/simapro.py @@ -22,21 +22,6 @@ SP_FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixtures", "simapro") -@bw2test -def test_sp_import_allocation(): - Migration("default-units").write( - get_default_units_migration_data(), "Convert to default units" - ) - - sp = SimaProCSVImporter( - os.path.join(SP_FIXTURES_DIR, "allocation.csv"), normalize_biosphere=False - ) - sp.apply_strategies() - assert sp.all_linked - assert sp.statistics() == (3, 5, 0) - sp.write_database() - - @bw2test def test_sp_wrong_field_ordering(): sp = SimaProCSVImporter(os.path.join(SP_FIXTURES_DIR, "new-order.csv")) diff --git a/tests/strategies/simapro_allocation.py b/tests/strategies/simapro_allocation.py new file mode 100644 index 0000000..f7607a0 --- /dev/null +++ b/tests/strategies/simapro_allocation.py @@ -0,0 +1,171 @@ +from bw2io.strategies.simapro import sp_allocate_products + + +def test_sp_allocate_products(): + given = [ + { + "type": "multifunctional", + "exchanges": [ + { + "name": "Biowaste", + "unit": "kg", + "comment": "Manure", + "amount": 10, + "uncertainty type": 0, + "loc": 10, + "type": "production", + "functional": False, + }, + { + "name": "Quack", + "unit": "p", + "amount": 5, + "uncertainty type": 0, + "loc": 5, + "type": "technosphere", + }, + { + "name": "Wool", + "unit": "kg", + "amount": 1000.0, + "allocation": 3, + "type": "production", + "functional": True, + }, + { + "name": "Strips", + "unit": "kg", + "amount": 2000.0, + "allocation": 2, + "type": "technosphere", + "functional": True, + }, + { + "name": "Viscera", + "unit": "kg", + "amount": 3000.0, + "allocation": 0, + "type": "production", + "functional": True, + }, + ], + } + ] + expected = [ + { + "type": "multifunctional", + "exchanges": [ + { + "name": "Biowaste", + "unit": "kg", + "comment": "Manure", + "amount": 10, + "uncertainty type": 0, + "loc": 10, + "type": "production", + "functional": False, + }, + { + "name": "Quack", + "unit": "p", + "amount": 5, + "uncertainty type": 0, + "loc": 5, + "type": "technosphere", + }, + { + "name": "Wool", + "unit": "kg", + "amount": 1000.0, + "allocation": 3, + "type": "production", + "functional": True, + }, + { + "name": "Strips", + "unit": "kg", + "amount": 2000.0, + "allocation": 2, + "type": "technosphere", + "functional": True, + }, + { + "name": "Viscera", + "unit": "kg", + "amount": 3000.0, + "allocation": 0, + "type": "production", + "functional": True, + }, + ], + }, + { + "type": "process", + "exchanges": [ + { + "name": "Wool", + "unit": "kg", + "amount": 1000.0, + "type": "production", + "functional": True, + }, + { + "name": "Biowaste", + "unit": "kg", + "comment": "Manure", + "amount": 6., + "uncertainty type": 0, + "loc": 6., + "type": "production", + "functional": False, + }, + { + "name": "Quack", + "unit": "p", + "amount": 3., + "uncertainty type": 0, + "loc": 3., + "type": "technosphere", + }, + ], + "name": "Wool", + "reference product": "Wool", + "unit": "kg", + "production amount": 1000.0, + }, + { + "type": "process", + "exchanges": [ + { + "name": "Strips", + "unit": "kg", + "amount": 2000.0, + "type": "technosphere", + "functional": True, + }, + { + "name": "Biowaste", + "unit": "kg", + "comment": "Manure", + "amount": 4., + "uncertainty type": 0, + "loc": 4., + "type": "production", + "functional": False, + }, + { + "name": "Quack", + "unit": "p", + "amount": 2., + "uncertainty type": 0, + "loc": 2., + "type": "technosphere", + }, + ], + "name": "Strips", + "reference product": "Strips", + "unit": "kg", + "production amount": 2000.0, + }, + ] + assert sp_allocate_products(given) == expected