lca-algebraic 1.4.1.dev0__tar.gz → 1.4.1.dev2__tar.gz

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.
Files changed (38) hide show
  1. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/PKG-INFO +2 -1
  2. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/activity.py +10 -8
  3. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/base_utils.py +17 -1
  4. lca_algebraic-1.4.1.dev2/lca_algebraic/bw_wrapper/__init__.py +150 -0
  5. lca_algebraic-1.4.1.dev2/lca_algebraic/bw_wrapper/bw2.py +98 -0
  6. lca_algebraic-1.4.1.dev2/lca_algebraic/bw_wrapper/bw25.py +115 -0
  7. lca_algebraic-1.4.1.dev2/lca_algebraic/bw_wrapper/protocol.py +81 -0
  8. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/cache.py +15 -5
  9. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/database.py +10 -0
  10. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/lambda_expression.py +9 -3
  11. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/lca.py +322 -49
  12. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/log.py +8 -0
  13. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/methods.py +6 -1
  14. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/params.py +1 -3
  15. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/settings.py +11 -0
  16. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic.egg-info/PKG-INFO +2 -1
  17. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic.egg-info/SOURCES.txt +5 -0
  18. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic.egg-info/requires.txt +1 -0
  19. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/pyproject.toml +3 -2
  20. lca_algebraic-1.4.1.dev2/test/test_premise.py +131 -0
  21. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/LICENSE +0 -0
  22. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/README.md +0 -0
  23. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/__init__.py +0 -0
  24. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/axis_dict.py +0 -0
  25. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/interpolation.py +0 -0
  26. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/io.py +0 -0
  27. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/matrix.py +0 -0
  28. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/stats.py +0 -0
  29. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/sympy_utils.py +0 -0
  30. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic/units.py +0 -0
  31. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic.egg-info/dependency_links.txt +0 -0
  32. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/lca_algebraic.egg-info/top_level.txt +0 -0
  33. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/setup.cfg +0 -0
  34. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/test/test_axis_dict.py +0 -0
  35. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/test/test_axis_matrices_cache.py +0 -0
  36. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/test/test_cache.py +0 -0
  37. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/test/test_sympy.py +0 -0
  38. {lca_algebraic-1.4.1.dev0 → lca_algebraic-1.4.1.dev2}/test/test_units.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lca_algebraic
3
- Version: 1.4.1.dev0
3
+ Version: 1.4.1.dev2
4
4
  Summary: This library provides a layer above Brightway for defining parametric models and running super fast LCA for monte carlo analysis.
5
5
  Author-email: OIE - Mines ParisTech <raphael.jolivet@mines-paristech.fr>
6
6
  License: BSD
@@ -29,6 +29,7 @@ Requires-Dist: deprecation
29
29
  Requires-Dist: SALib
30
30
  Requires-Dist: pint
31
31
  Requires-Dist: typing-extensions
32
+ Requires-Dist: xarray>=2022.3.0
32
33
  Provides-Extra: bw2
33
34
  Requires-Dist: brightway2; extra == "bw2"
34
35
  Requires-Dist: pypardiso; platform_machine == "x86_64" and extra == "bw2"
@@ -168,13 +168,11 @@ class ActivityExtended(Activity):
168
168
  or dict of attributes, for updating both at once, or any other attribute.
169
169
  The amount can reference the symbol 'old_amount' that will be replaced with the current amount of the exchange.
170
170
  """
171
-
172
- if not _isForeground(self["database"]):
171
+ if not _isForeground(self["database"]) and not Settings.internals:
173
172
  msg = f"You are updating a background activity in {self['database']}. Use copyActivity() in a foreground db instead."
174
173
  if Settings.strict_mode:
175
174
  raise Exception(msg)
176
- else:
177
- warn(msg)
175
+ warn(msg)
178
176
 
179
177
  # Update exchanges
180
178
  for ex_target_or_name, updates in updates.items():
@@ -541,12 +539,11 @@ def _equals(val1: ValueOrExpression, val2: ValueOrExpression):
541
539
 
542
540
 
543
541
  def _newAct(db_name, code):
544
- if not _isForeground(db_name):
542
+ if not _isForeground(db_name) and not Settings.internals:
545
543
  msg = f"You are creating activity in background DB {db_name}. Use copyActivity() in a foreground db instead."
546
544
  if Settings.strict_mode:
547
545
  raise Exception(msg)
548
- else:
549
- warn(msg)
546
+ warn(msg)
550
547
 
551
548
  db = _getDb(db_name)
552
549
  # Already present : delete it ?
@@ -632,7 +629,9 @@ def newActivity(
632
629
 
633
630
 
634
631
  @atomic
635
- def copyActivity(db_name, activity: ActivityExtended, code=None, withExchanges=True, **kwargs) -> ActivityExtended:
632
+ def copyActivity(
633
+ db_name, activity: ActivityExtended, code=None, withExchanges=True, input_mapping=None, **kwargs
634
+ ) -> ActivityExtended:
636
635
  """Copy an activity and its exchanges into another database. You usually want to copy activities from your background to
637
636
  your foreground DB to update them, keeping your background DB clean.
638
637
 
@@ -675,6 +674,9 @@ def copyActivity(db_name, activity: ActivityExtended, code=None, withExchanges=T
675
674
  data["output"] = res.key
676
675
  if data["input"] == activity.key:
677
676
  data["input"] = res.key
677
+ if input_mapping and data["input"] in input_mapping:
678
+ # Input mapping, used to migrate proxy db linked to premise scenarios
679
+ data["input"] = input_mapping[data["input"]]
678
680
 
679
681
  # Chemical formulas might be in ecoinvent technosphere
680
682
  # We don't want them
@@ -1,4 +1,5 @@
1
1
  from contextlib import AbstractContextManager
2
+ from functools import cache
2
3
  from inspect import isfunction
3
4
  from typing import Dict, Iterable, Tuple, Union
4
5
 
@@ -26,6 +27,20 @@ def _isnumber(value):
26
27
  dbs = dict()
27
28
 
28
29
 
30
+ def invalidate_db(dbname=None):
31
+ """Drop the pooled Database instances so a fresh one is built on next access.
32
+
33
+ bw25 keeps an internal (session/ORM) view inside a Database object that is not
34
+ invalidated when a database is deleted or recreated. Keep the pool in sync so that
35
+ activities are not resolved to stale (deleted) node ids.
36
+ """
37
+ global dbs
38
+ if dbname is None:
39
+ dbs = dict()
40
+ else:
41
+ dbs.pop(dbname, None)
42
+
43
+
29
44
  def _getDb(dbname):
30
45
  """Pool of Database instances"""
31
46
  if dbname not in dbs:
@@ -174,6 +189,7 @@ def one(it: Iterable):
174
189
  return it[0]
175
190
 
176
191
 
192
+ @cache
177
193
  def getActByCode(db_name, code):
178
194
  """Get activity by code"""
179
195
  return _getDb(db_name).get(code)
@@ -181,4 +197,4 @@ def getActByCode(db_name, code):
181
197
 
182
198
  # Types
183
199
  ValueOrExpression = Union[float, Basic, Quantity]
184
- MethodKey = Tuple[str]
200
+ MethodKey = Tuple[str, ...]
@@ -0,0 +1,150 @@
1
+ from typing import Callable, Iterable, List, Tuple, Type
2
+
3
+ import pandas as pd
4
+
5
+ from .protocol import BrightwayBackend
6
+
7
+ _INSTALL_MSG = (
8
+ "Brightway is required. Install one of:\n" " pip install 'lca_algebraic[bw2]'\n" " pip install 'lca_algebraic[bw25]'"
9
+ )
10
+
11
+
12
+ def _bw2data_major_version(bw2data) -> int:
13
+ version = bw2data.__version__
14
+ if isinstance(version, tuple):
15
+ return int(version[0])
16
+ return int(str(version).split(".")[0])
17
+
18
+
19
+ def _load_backend() -> BrightwayBackend:
20
+ try:
21
+ import bw2data
22
+ except ImportError as exc:
23
+ raise ImportError(_INSTALL_MSG) from exc
24
+
25
+ if _bw2data_major_version(bw2data) >= 4:
26
+ from .bw25 import BW25Backend
27
+
28
+ return BW25Backend()
29
+
30
+ from .bw2 import BW2Backend
31
+
32
+ return BW2Backend()
33
+
34
+
35
+ _backend = _load_backend()
36
+
37
+ Activity = _backend.Activity
38
+ ActivityDataset = _backend.ActivityDataset
39
+ Exchange = _backend.Exchange
40
+ ExchangeDataset = _backend.ExchangeDataset
41
+ ActivityProxyBase = _backend.ActivityProxyBase
42
+ LCIBackend = _backend.LCIBackend
43
+ sqlite3_lci_db = _backend.sqlite3_lci_db
44
+ dict_as_exchangedataset = _backend.dict_as_exchangedataset
45
+
46
+ name = _backend.name
47
+
48
+ _LAZY_ATTRS = frozenset({"databases", "projects", "methods"})
49
+
50
+
51
+ def __getattr__(attr: str):
52
+ if attr in _LAZY_ATTRS:
53
+ return getattr(_backend, attr)
54
+ raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
55
+
56
+
57
+ def Database(db_name: str):
58
+ return _backend.Database(db_name)
59
+
60
+
61
+ def Method(key: Tuple):
62
+ return _backend.Method(key)
63
+
64
+
65
+ def get_activity(key):
66
+ return _backend.get_activity(key)
67
+
68
+
69
+ def iter_database(db_name: str) -> Iterable:
70
+ return _backend.iter_database(db_name)
71
+
72
+
73
+ def multi_lca(
74
+ activities: List[dict],
75
+ methods: List[Tuple],
76
+ act_name_fn: Callable,
77
+ method_name_fn: Callable,
78
+ ) -> pd.DataFrame:
79
+ return _backend.multi_lca(activities, methods, act_name_fn, method_name_fn)
80
+
81
+
82
+ def is_output_exchange(exc) -> bool:
83
+ return _backend.is_output_exchange(exc)
84
+
85
+
86
+ def exchange_type_for_sub_act(sub_act) -> str:
87
+ return _backend.exchange_type_for_sub_act(sub_act)
88
+
89
+
90
+ def process_node_default() -> str:
91
+ return _backend.process_node_default()
92
+
93
+
94
+ def production_edge_default() -> str:
95
+ return _backend.production_edge_default()
96
+
97
+
98
+ def activity_not_found() -> Type[Exception]:
99
+ return _backend.activity_not_found()
100
+
101
+
102
+ def new_database_parameters(params, db_name: str) -> None:
103
+ _backend.new_database_parameters(params, db_name)
104
+
105
+
106
+ def new_project_parameters(params) -> None:
107
+ _backend.new_project_parameters(params)
108
+
109
+
110
+ def copy_activity_skip_keys() -> set:
111
+ return _backend.copy_activity_skip_keys()
112
+
113
+
114
+ def bw2setup() -> None:
115
+ _backend.bw2setup()
116
+
117
+
118
+ def SingleOutputEcospold2Importer(path, dbname, use_mp=False):
119
+ return _backend.SingleOutputEcospold2Importer(path, dbname, use_mp=use_mp)
120
+
121
+
122
+ __all__ = [
123
+ "Activity",
124
+ "ActivityProxyBase",
125
+ "BrightwayBackend",
126
+ "Database",
127
+ "Exchange",
128
+ "ExchangeDataset",
129
+ "LCIBackend",
130
+ "Method",
131
+ "SingleOutputEcospold2Importer",
132
+ "activity_not_found",
133
+ "bw2setup",
134
+ "copy_activity_skip_keys",
135
+ "databases",
136
+ "dict_as_exchangedataset",
137
+ "exchange_type_for_sub_act",
138
+ "get_activity",
139
+ "is_output_exchange",
140
+ "iter_database",
141
+ "methods",
142
+ "multi_lca",
143
+ "name",
144
+ "new_database_parameters",
145
+ "new_project_parameters",
146
+ "process_node_default",
147
+ "production_edge_default",
148
+ "projects",
149
+ "sqlite3_lci_db",
150
+ ]
@@ -0,0 +1,98 @@
1
+ from typing import Callable, List, Tuple
2
+
3
+ import brightway2 as bw
4
+ import pandas as pd
5
+ from bw2data.backends import LCIBackend
6
+ from bw2data.backends.peewee import (
7
+ Activity,
8
+ ActivityDataset,
9
+ Exchange,
10
+ ExchangeDataset,
11
+ sqlite3_lci_db,
12
+ )
13
+ from bw2data.backends.peewee.utils import dict_as_exchangedataset
14
+ from bw2data.proxies import ActivityProxyBase
15
+ from peewee import DoesNotExist
16
+
17
+ _TECHNO_TYPES = [None, "process", "product", "processwithreferenceproduct", "multifunctional"]
18
+
19
+
20
+ class BW2Backend:
21
+ name = "bw2"
22
+
23
+ Activity = Activity
24
+ ActivityDataset = ActivityDataset
25
+ Exchange = Exchange
26
+ ExchangeDataset = ExchangeDataset
27
+ ActivityProxyBase = ActivityProxyBase
28
+ LCIBackend = LCIBackend
29
+ sqlite3_lci_db = sqlite3_lci_db
30
+ dict_as_exchangedataset = staticmethod(dict_as_exchangedataset)
31
+
32
+ @property
33
+ def databases(self):
34
+ return bw.databases
35
+
36
+ @property
37
+ def projects(self):
38
+ return bw.projects
39
+
40
+ @property
41
+ def methods(self):
42
+ return bw.methods
43
+
44
+ def Database(self, name: str):
45
+ return bw.Database(name)
46
+
47
+ def Method(self, key: Tuple):
48
+ return bw.Method(key)
49
+
50
+ def get_activity(self, key):
51
+ return bw.get_activity(key)
52
+
53
+ def iter_database(self, db_name: str):
54
+ return iter(bw.Database(db_name))
55
+
56
+ def multi_lca(
57
+ self,
58
+ activities: List[dict],
59
+ methods: List[Tuple],
60
+ act_name_fn: Callable,
61
+ method_name_fn: Callable,
62
+ ) -> pd.DataFrame:
63
+ bw.calculation_setups["process"] = {"inv": activities, "ia": methods}
64
+ lca = bw.MultiLCA("process")
65
+ cols = [act_name_fn(act) for act_amount in activities for act in act_amount]
66
+ return pd.DataFrame(lca.results.T, index=[method_name_fn(method) for method in methods], columns=cols)
67
+
68
+ def is_output_exchange(self, exc) -> bool:
69
+ return exc.get("type") == "production"
70
+
71
+ def exchange_type_for_sub_act(self, sub_act) -> str:
72
+ if sub_act.get("type") in _TECHNO_TYPES:
73
+ return "technosphere"
74
+ return "biosphere"
75
+
76
+ def process_node_default(self) -> str:
77
+ return "process"
78
+
79
+ def production_edge_default(self) -> str:
80
+ return "production"
81
+
82
+ def activity_not_found(self):
83
+ return DoesNotExist
84
+
85
+ def new_database_parameters(self, params, db_name: str) -> None:
86
+ bw.parameters.new_database_parameters(params, db_name)
87
+
88
+ def new_project_parameters(self, params) -> None:
89
+ bw.parameters.new_project_parameters(params)
90
+
91
+ def copy_activity_skip_keys(self) -> set:
92
+ return {"database", "code"}
93
+
94
+ def bw2setup(self):
95
+ bw.bw2setup()
96
+
97
+ def SingleOutputEcospold2Importer(self, path, dbname, use_mp=False):
98
+ return bw.SingleOutputEcospold2Importer(path, dbname, use_mp=use_mp)
@@ -0,0 +1,115 @@
1
+ from typing import Callable, List, Tuple
2
+
3
+ import bw2calc
4
+ import bw2data
5
+ import numpy as np
6
+ import pandas as pd
7
+ from bw2data import Database
8
+ from bw2data.backends import (
9
+ Activity,
10
+ ActivityDataset,
11
+ Exchange,
12
+ ExchangeDataset,
13
+ SQLiteBackend,
14
+ sqlite3_lci_db,
15
+ )
16
+ from bw2data.backends.utils import dict_as_exchangedataset
17
+ from bw2data.configuration import labels
18
+ from bw2data.errors import UnknownObject
19
+ from bw2data.parameters import ParameterManager
20
+ from bw2data.proxies import ActivityProxyBase
21
+
22
+
23
+ class BW25Backend:
24
+ name = "bw25"
25
+
26
+ Activity = Activity
27
+ ActivityDataset = ActivityDataset
28
+ Exchange = Exchange
29
+ ExchangeDataset = ExchangeDataset
30
+ ActivityProxyBase = ActivityProxyBase
31
+ LCIBackend = SQLiteBackend
32
+ sqlite3_lci_db = sqlite3_lci_db
33
+ dict_as_exchangedataset = staticmethod(dict_as_exchangedataset)
34
+
35
+ @property
36
+ def databases(self):
37
+ return bw2data.databases
38
+
39
+ @property
40
+ def projects(self):
41
+ return bw2data.projects
42
+
43
+ @property
44
+ def methods(self):
45
+ return bw2data.methods
46
+
47
+ def Database(self, name: str):
48
+ return Database(name)
49
+
50
+ def Method(self, key: Tuple):
51
+ return bw2data.Method(key)
52
+
53
+ def get_activity(self, key):
54
+ return bw2data.get_activity(key)
55
+
56
+ def iter_database(self, db_name: str):
57
+ return iter(Database(db_name))
58
+
59
+ def multi_lca(
60
+ self,
61
+ activities: List[dict],
62
+ methods: List[Tuple],
63
+ act_name_fn: Callable,
64
+ method_name_fn: Callable,
65
+ ) -> pd.DataFrame:
66
+ meth_cfg = {"impact_categories": methods}
67
+ fu = {act_name_fn(act): {act.id: 1} for act_amount in activities for act in act_amount}
68
+
69
+ bw2data.databases.clean()
70
+ data_objs = bw2data.get_multilca_data_objs(fu, meth_cfg)
71
+ lca = bw2calc.MultiLCA(demands=fu, method_config=meth_cfg, data_objs=data_objs)
72
+ lca.lci()
73
+ lca.lcia()
74
+
75
+ rows = [method_name_fn(method) for method in methods]
76
+ cols = [act_name_fn(act) for act_amount in activities for act in act_amount]
77
+ results = lca.scores
78
+
79
+ return pd.DataFrame(
80
+ np.array([[results[m, a] for a in cols] for m in methods]),
81
+ index=rows,
82
+ columns=cols,
83
+ )
84
+
85
+ def is_output_exchange(self, exc) -> bool:
86
+ return exc.get("type") == labels.production_edge_default
87
+
88
+ def exchange_type_for_sub_act(self, sub_act) -> str:
89
+ if sub_act.get("type") in labels.lci_node_types:
90
+ return labels.consumption_edge_default
91
+ return labels.biosphere_edge_default
92
+
93
+ def process_node_default(self) -> str:
94
+ return labels.process_node_default
95
+
96
+ def production_edge_default(self) -> str:
97
+ return labels.production_edge_default
98
+
99
+ def activity_not_found(self):
100
+ return UnknownObject
101
+
102
+ def new_database_parameters(self, params, db_name: str) -> None:
103
+ ParameterManager().new_database_parameters(params, db_name)
104
+
105
+ def new_project_parameters(self, params) -> None:
106
+ ParameterManager().new_project_parameters(params)
107
+
108
+ def copy_activity_skip_keys(self) -> set:
109
+ return {"database", "code", "id"}
110
+
111
+ def bw2setup(self) -> None:
112
+ raise NotImplementedError("bw2setup is only supported with brightway2")
113
+
114
+ def SingleOutputEcospold2Importer(self, path, dbname, use_mp=False):
115
+ raise NotImplementedError("SingleOutputEcospold2Importer is only supported with brightway2")
@@ -0,0 +1,81 @@
1
+ from typing import Any, Callable, Iterable, List, Protocol, Tuple, Type
2
+
3
+ import pandas as pd
4
+
5
+
6
+ class BrightwayBackend(Protocol):
7
+ """Brightway 2 / 2.5 compatibility surface used by lca_algebraic."""
8
+
9
+ Activity: Any
10
+ Exchange: Any
11
+ ActivityDataset: Any
12
+ ExchangeDataset: Any
13
+ ActivityProxyBase: Any
14
+ LCIBackend: Any
15
+ sqlite3_lci_db: Any
16
+ dict_as_exchangedataset: Callable[..., Any]
17
+
18
+ name: str
19
+
20
+ @property
21
+ def databases(self) -> Any:
22
+ ...
23
+
24
+ @property
25
+ def projects(self) -> Any:
26
+ ...
27
+
28
+ @property
29
+ def methods(self) -> Iterable:
30
+ ...
31
+
32
+ def Database(self, name: str) -> Any:
33
+ ...
34
+
35
+ def Method(self, key: Tuple) -> Any:
36
+ ...
37
+
38
+ def get_activity(self, key) -> Any:
39
+ ...
40
+
41
+ def iter_database(self, db_name: str) -> Iterable:
42
+ ...
43
+
44
+ def multi_lca(
45
+ self,
46
+ activities: List[dict],
47
+ methods: List[Tuple],
48
+ act_name_fn: Callable,
49
+ method_name_fn: Callable,
50
+ ) -> pd.DataFrame:
51
+ ...
52
+
53
+ def is_output_exchange(self, exc) -> bool:
54
+ ...
55
+
56
+ def exchange_type_for_sub_act(self, sub_act) -> str:
57
+ ...
58
+
59
+ def process_node_default(self) -> str:
60
+ ...
61
+
62
+ def production_edge_default(self) -> str:
63
+ ...
64
+
65
+ def activity_not_found(self) -> Type[Exception]:
66
+ ...
67
+
68
+ def new_database_parameters(self, params, db_name: str) -> None:
69
+ ...
70
+
71
+ def new_project_parameters(self, params) -> None:
72
+ ...
73
+
74
+ def copy_activity_skip_keys(self) -> set:
75
+ ...
76
+
77
+ def bw2setup(self) -> None:
78
+ ...
79
+
80
+ def SingleOutputEcospold2Importer(self, path, dbname, use_mp=False) -> Any:
81
+ ...
@@ -8,14 +8,15 @@ from typing import Dict, Tuple
8
8
  from dill import Pickler, load
9
9
  from sympy.core.function import UndefinedFunction
10
10
 
11
- from lca_algebraic.bw_wrapper import databases, projects
11
+ from lca_algebraic.bw_wrapper import Database, databases, projects
12
12
 
13
13
  from .database import _getMeta
14
- from .log import info, logger
14
+ from .log import logger
15
15
  from .settings import PROXY_DB_FLAG, Settings
16
16
 
17
17
  LCIA_CACHE = "lcia"
18
18
  EXPR_CACHE = "expr"
19
+ MAPPING_CACHE = "mapping"
19
20
 
20
21
 
21
22
  # Overide the behaviour for pickling sympy.UndefineFunction
@@ -43,11 +44,15 @@ def flush_caches():
43
44
  cache.sync()
44
45
 
45
46
 
46
- def get_dependant_dbs(db_name):
47
+ def get_dependant_dbs(db_name, skip_proxy=True):
47
48
  """Recursively get list of dependant db names, including the current one"""
48
49
 
50
+ # Only work if database is up to date (processed)
51
+ if databases[db_name].get("dirty"):
52
+ Database(db_name).process()
53
+
49
54
  # Skip proxy databases
50
- if _getMeta(db_name, PROXY_DB_FLAG):
55
+ if skip_proxy and _getMeta(db_name, PROXY_DB_FLAG):
51
56
  res = set()
52
57
  else:
53
58
  res = set([db_name])
@@ -150,7 +155,7 @@ class SyncDict(MutableMapping):
150
155
  if self.last_update <= file_mtime:
151
156
  return
152
157
 
153
- info(f"Flushing cache {self.name} / {self.db_name}")
158
+ # info(f"Flushing cache {self.name} / {self.db_name}")
154
159
 
155
160
  tmp = self._filename() + ".tmp"
156
161
  with open(tmp, "wb") as f:
@@ -208,6 +213,11 @@ class ExprCache(_CacheDict):
208
213
  super().__init__(EXPR_CACHE, db_name)
209
214
 
210
215
 
216
+ class MappingCache(_CacheDict):
217
+ def __init__(self, db_name):
218
+ super().__init__(MAPPING_CACHE, db_name)
219
+
220
+
211
221
  def clear_caches(local=True, disk=True):
212
222
  for cache_name in [LCIA_CACHE, EXPR_CACHE]:
213
223
  for db_name in databases:
@@ -20,6 +20,7 @@ from lca_algebraic.bw_wrapper import (
20
20
  sqlite3_lci_db,
21
21
  )
22
22
 
23
+ from .base_utils import getActByCode, invalidate_db
23
24
  from .log import logger
24
25
  from .settings import PROXY_DB_FLAG
25
26
 
@@ -87,6 +88,15 @@ def resetDb(db_name, foreground=True):
87
88
  logger.warning("Db %s was here. Reseting it" % db_name)
88
89
  del dbmeta[db_name]
89
90
 
91
+ # Drop the pooled Database instance : it may hold a stale view (e.g. bw25 node ids)
92
+ # of a previously deleted/recreated database with the same name
93
+ invalidate_db(db_name)
94
+
95
+ # getActByCode() memoizes activity objects by (db_name, code). Activities are
96
+ # recreated with the same code on reset, so drop the memo to avoid returning
97
+ # stale (deleted) node ids.
98
+ getActByCode.cache_clear()
99
+
90
100
  db = Database(db_name)
91
101
  db.write(dict())
92
102
  if foreground:
@@ -1,6 +1,6 @@
1
1
  from copy import copy
2
2
  from dataclasses import dataclass
3
- from typing import Dict, List
3
+ from typing import Dict, List, Optional
4
4
 
5
5
  from sympy import Basic, Expr, lambdify, parse_expr
6
6
  from sympy.printing.numpy import NumPyPrinter
@@ -8,6 +8,7 @@ from sympy.printing.numpy import NumPyPrinter
8
8
  from lca_algebraic.axis_dict import AxisDict
9
9
  from lca_algebraic.base_utils import _user_functions
10
10
  from lca_algebraic.bw_wrapper import Activity
11
+ from lca_algebraic.log import warn
11
12
  from lca_algebraic.params import (
12
13
  _complete_params,
13
14
  _expand_param_names,
@@ -39,7 +40,7 @@ class LambdaExpr:
39
40
 
40
41
  # List of background activities, indexed in the same order as impacts[i]
41
42
  self.background_activities: List[Activity] = list()
42
- self.impacts: List[float] = None
43
+ self.impacts: Optional[List[float]] = None
43
44
 
44
45
  if isinstance(expr, dict) and not isinstance(expr, AxisDict):
45
46
  # Come from JSON serialization
@@ -123,12 +124,17 @@ class LambdaExpr:
123
124
  def with_impacts(self, impacts: Dict[Activity, float]):
124
125
  """Transforms a generic LambaExpr toa one with custom impact values"""
125
126
  if self.impacts is not None:
126
- raise Exception("You are specializing a LambaExpression with impacts already set.")
127
+ warn("You are specializing a LambaExpression with impacts already set.")
128
+
127
129
  res = copy(self)
128
130
  res.impacts = [impacts[bg_act] for bg_act in self.background_activities]
129
131
 
130
132
  return res
131
133
 
134
+ def impacts_dict(self):
135
+ """Returns list of impacts as dict"""
136
+ return {bg_act: val for bg_act, val in zip(self.background_activities, self.impacts)}
137
+
132
138
 
133
139
  def _filter_param_values(params, expanded_param_names):
134
140
  return {key: val for key, val in params.items() if key in expanded_param_names}