Skip to content

Commit fe0ce2b

Browse files
committed
Compatibility with simapro multifunctionality
1 parent 0d583be commit fe0ce2b

File tree

11 files changed

+337
-117
lines changed

11 files changed

+337
-117
lines changed

bw2io/__init__.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,12 @@
5050

5151
__version__ = "0.9.DEV28"
5252

53-
from .chemidplus import ChemIDPlus
54-
from .package import BW2Package
55-
from .export import (
56-
DatabaseToGEXF,
57-
DatabaseSelectionToGEXF,
58-
keyword_to_gephi_graph,
59-
lci_matrices_to_excel,
60-
lci_matrices_to_matlab,
61-
)
6253
from .backup import (
6354
backup_data_directory,
6455
backup_project_directory,
6556
restore_project_directory,
6657
)
58+
from .chemidplus import ChemIDPlus
6759
from .data import (
6860
add_ecoinvent_33_biosphere_flows,
6961
add_ecoinvent_34_biosphere_flows,
@@ -76,7 +68,13 @@
7668
get_csv_example_filepath,
7769
get_xlsx_example_filepath,
7870
)
79-
from .migrations import migrations, Migration, create_core_migrations
71+
from .export import (
72+
DatabaseSelectionToGEXF,
73+
DatabaseToGEXF,
74+
keyword_to_gephi_graph,
75+
lci_matrices_to_excel,
76+
lci_matrices_to_matlab,
77+
)
8078
from .importers import (
8179
CSVImporter,
8280
CSVLCIAImporter,
@@ -92,10 +90,12 @@
9290
SingleOutputEcospold1Importer,
9391
SingleOutputEcospold2Importer,
9492
)
93+
from .migrations import Migration, create_core_migrations, migrations
94+
from .package import BW2Package
95+
from .remote import install_project
9596
from .units import normalize_units
96-
from .unlinked_data import unlinked_data, UnlinkedData
97+
from .unlinked_data import UnlinkedData, unlinked_data
9798
from .utils import activity_hash, es2_activity_hash, load_json_data_file
98-
from .remote import install_project
9999

100100
try:
101101
from .ecoinvent import import_ecoinvent_release
@@ -128,9 +128,10 @@ def create_default_lcia_methods(
128128
overwrite=False, rationalize_method_names=False, shortcut=True
129129
):
130130
if shortcut:
131-
import zipfile
132131
import json
132+
import zipfile
133133
from pathlib import Path
134+
134135
from .importers.base_lcia import LCIAImporter
135136

136137
fp = Path(__file__).parent.resolve() / "data" / "lcia" / "lcia_39_ecoinvent.zip"
@@ -174,13 +175,14 @@ def useeio20(name="USEEIO-2.0", collapse_products=False, prune=False):
174175
print(f"{name} already present")
175176
return
176177

177-
from .importers.json_ld import JSONLDImporter
178-
from .importers.json_ld_lcia import JSONLDLCIAImporter
179-
from .strategies import remove_useeio_products, remove_random_exchanges
180-
from .download_utils import download_with_progressbar
181-
from pathlib import Path
182178
import tempfile
183179
import zipfile
180+
from pathlib import Path
181+
182+
from .download_utils import download_with_progressbar
183+
from .importers.json_ld import JSONLDImporter
184+
from .importers.json_ld_lcia import JSONLDLCIAImporter
185+
from .strategies import remove_random_exchanges, remove_useeio_products
184186

185187
with tempfile.TemporaryDirectory() as td:
186188
dp = Path(td)
@@ -218,10 +220,11 @@ def exiobase_monetary(
218220
name=None,
219221
ignore_small_balancing_corrections=True,
220222
):
221-
from .download_utils import download_with_progressbar
222223
import tempfile
223224
from pathlib import Path
224225

226+
from .download_utils import download_with_progressbar
227+
225228
mapping = {
226229
(3, 8, 2): {
227230
"url": "https://zenodo.org/record/5589597/files/IOT_{year}_{system}.zip?download=1",

bw2io/ecoinvent.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,12 @@ def import_ecoinvent_release(
313313

314314
for row in cfs:
315315
if lcia_prefix:
316-
impact_category = (lcia_prefix, row["method"], row["category"], row["indicator"])
316+
impact_category = (
317+
lcia_prefix,
318+
row["method"],
319+
row["category"],
320+
row["indicator"],
321+
)
317322
else:
318323
impact_category = (row["method"], row["category"], row["indicator"])
319324
if row[cf_col_label] is None:

bw2io/extractors/ecospold1.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
from io import StringIO
55
from pathlib import Path
6-
from typing import Any, Union, Optional
6+
from typing import Any, Optional, Union
77

88
import numpy as np
99
import pyecospold

bw2io/importers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
from .excel_lcia import CSVLCIAImporter, ExcelLCIAImporter
1212
from .exiobase3_hybrid import Exiobase3HybridImporter
1313
from .exiobase3_monetary import Exiobase3MonetaryImporter
14+
from .simapro_block_csv import SimaProBlockCSVImporter
1415
from .simapro_csv import SimaProCSVImporter
1516
from .simapro_lcia_csv import SimaProLCIACSVImporter
16-
from .simapro_block_csv import SimaProBlockCSVImporter
1717

1818
"""
1919
This module provides classes for importing Life Cycle Impact Assessment (LCIA) data

bw2io/importers/base_lci.py

Lines changed: 73 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import functools
33
import itertools
44
import warnings
5-
from typing import Optional
5+
from typing import Optional, Tuple
66

7-
from bw2data import Database, config, databases, parameters, labels
7+
from bw2data import Database, config, databases, labels, parameters
8+
from bw2data.data_store import ProcessedDataStore
89
from bw2data.parameters import (
910
ActivityParameter,
1011
DatabaseParameter,
@@ -56,7 +57,11 @@ def __init__(self, db_name):
5657
def all_linked(self):
5758
return self.statistics()[2] == 0
5859

59-
def statistics(self, print_stats=True):
60+
@property
61+
def needs_multifunctional_database(self):
62+
return any(ds.get("type") == "multifunctional" for ds in self.data)
63+
64+
def statistics(self, print_stats: bool = True) -> Tuple[int, int, int, int]:
6065
num_datasets = len(self.data)
6166
num_exchanges = sum([len(ds.get("exchanges", [])) for ds in self.data])
6267
num_unlinked = len(
@@ -65,8 +70,12 @@ def statistics(self, print_stats=True):
6570
for ds in self.data
6671
for exc in ds.get("exchanges", [])
6772
if not exc.get("input")
73+
and not (ds.get("type") == "multifunctional" and exc.get("functional"))
6874
]
6975
)
76+
num_multifunctional = sum(
77+
1 for ds in self.data if ds.get("type") == "multifunctional"
78+
)
7079
if print_stats:
7180
unique_unlinked = collections.defaultdict(set)
7281
for ds in self.data:
@@ -75,19 +84,28 @@ def statistics(self, print_stats=True):
7584
unique_unlinked = sorted(
7685
[(k, len(v)) for k, v in list(unique_unlinked.items())]
7786
)
78-
79-
print(
80-
(
81-
"{} datasets\n{} exchanges\n{} unlinked exchanges\n "
82-
+ "\n ".join(
83-
[
84-
"Type {}: {} unique unlinked exchanges".format(*o)
85-
for o in unique_unlinked
86-
]
87-
)
88-
).format(num_datasets, num_exchanges, num_unlinked)
87+
uu = "\n\t".join(
88+
[
89+
"Type {}: {} unique unlinked exchanges".format(*o)
90+
for o in unique_unlinked
91+
]
8992
)
90-
return num_datasets, num_exchanges, num_unlinked
93+
94+
if num_multifunctional:
95+
print(
96+
f"""{num_datasets} datasets, including {num_multifunctional} multifunctional datasets
97+
\t{num_exchanges} exchanges
98+
\t{num_unlinked} unlinked exchanges ({len(unique_unlinked)} unique)
99+
\t{uu}"""
100+
)
101+
else:
102+
print(
103+
f"""{num_datasets} datasets
104+
\t{num_exchanges} exchanges
105+
\t{num_unlinked} unlinked exchanges ({len(unique_unlinked)} unique)
106+
\t{uu}"""
107+
)
108+
return num_datasets, num_exchanges, num_unlinked, num_multifunctional
91109

92110
def write_project_parameters(self, data=None, delete_existing=True):
93111
"""Write global parameters to ``ProjectParameter`` database table.
@@ -201,6 +219,16 @@ def _write_activity_parameters(self, activity_parameters):
201219
for key in keys:
202220
parameters.add_exchanges_to_group(group, key)
203221

222+
def database_class(
223+
self, db_name: str, requested_backend: str = "sqlite"
224+
) -> ProcessedDataStore:
225+
from multifunctional import MultifunctionalDatabase
226+
227+
if self.needs_multifunctional_database:
228+
return MultifunctionalDatabase(db_name)
229+
else:
230+
return Database(db_name, backend=requested_backend)
231+
204232
def write_database(
205233
self,
206234
data: Optional[dict] = None,
@@ -260,7 +288,7 @@ def write_database(
260288

261289
if db_name in databases:
262290
# TODO: Raise error if unlinked exchanges?
263-
db = Database(db_name)
291+
db = self.database_class(db_name)
264292
if delete_existing:
265293
existing = {}
266294
else:
@@ -269,9 +297,14 @@ def write_database(
269297
existing = {}
270298
if "format" not in self.metadata:
271299
self.metadata["format"] = self.format
300+
if (
301+
self.needs_multifunctional_database
302+
and "default_allocation" not in self.metadata
303+
):
304+
self.metadata["default_allocation"] = "manual_allocation"
272305
with warnings.catch_warnings():
273306
warnings.simplefilter("ignore")
274-
db = Database(db_name, backend=backend)
307+
db = self.database_class(db_name)
275308
db.register(**self.metadata)
276309

277310
self.write_database_parameters(activate_parameters, delete_existing)
@@ -346,27 +379,39 @@ def create_new_biosphere(self, biosphere_name: str):
346379
raise ValueError(f"{biosphere_name} database already exists")
347380

348381
def reformat(exc):
349-
return exc | {"type": labels.biosphere_node_default, "exchanges": [], "database": biosphere_name, "code": activity_hash(exc)}
382+
return exc | {
383+
"type": labels.biosphere_node_default,
384+
"exchanges": [],
385+
"database": biosphere_name,
386+
"code": activity_hash(exc),
387+
}
350388

351-
bio_data = {(flow["database"], flow["code"]): flow for flow in [
352-
reformat(exc)
353-
for ds in self.data
354-
for exc in ds.get("exchanges", [])
355-
if exc["type"] in labels.biosphere_edge_types
356-
and not exc.get("input")
357-
]}
389+
bio_data = {
390+
(flow["database"], flow["code"]): flow
391+
for flow in [
392+
reformat(exc)
393+
for ds in self.data
394+
for exc in ds.get("exchanges", [])
395+
if exc["type"] in labels.biosphere_edge_types and not exc.get("input")
396+
]
397+
}
358398

359399
if not bio_data:
360-
print("Skipping biosphere database creation as all biosphere flows are linked")
400+
print(
401+
"Skipping biosphere database creation as all biosphere flows are linked"
402+
)
361403
return
362404

363-
print(f"Creating new biosphere database {biosphere_name} with {len(bio_data)} flows")
405+
print(
406+
f"Creating new biosphere database {biosphere_name} with {len(bio_data)} flows"
407+
)
364408

365409
with warnings.catch_warnings():
366410
warnings.simplefilter("ignore")
367411
new_bio = Database(biosphere_name)
368412
new_bio.register(
369-
format=self.format, comment=f"Database for unlinked biosphere flows from {self.db_name}"
413+
format=self.format,
414+
comment=f"Database for unlinked biosphere flows from {self.db_name}",
370415
)
371416

372417
new_bio.write(bio_data)

0 commit comments

Comments
 (0)