lca-algebraic 1.4.1.dev1__tar.gz → 1.4.1.dev3__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.dev1 → lca_algebraic-1.4.1.dev3}/PKG-INFO +1 -1
  2. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/lambda_expression.py +9 -3
  3. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/lca.py +81 -45
  4. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/methods.py +6 -1
  5. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/settings.py +4 -0
  6. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic.egg-info/PKG-INFO +1 -1
  7. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/pyproject.toml +1 -1
  8. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/test/test_premise.py +35 -1
  9. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/LICENSE +0 -0
  10. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/README.md +0 -0
  11. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/__init__.py +0 -0
  12. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/activity.py +0 -0
  13. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/axis_dict.py +0 -0
  14. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/base_utils.py +0 -0
  15. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/bw_wrapper/__init__.py +0 -0
  16. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/bw_wrapper/bw2.py +0 -0
  17. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/bw_wrapper/bw25.py +0 -0
  18. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/bw_wrapper/protocol.py +0 -0
  19. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/cache.py +0 -0
  20. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/database.py +0 -0
  21. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/interpolation.py +0 -0
  22. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/io.py +0 -0
  23. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/log.py +0 -0
  24. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/matrix.py +0 -0
  25. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/params.py +0 -0
  26. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/stats.py +0 -0
  27. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/sympy_utils.py +0 -0
  28. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic/units.py +0 -0
  29. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic.egg-info/SOURCES.txt +0 -0
  30. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic.egg-info/dependency_links.txt +0 -0
  31. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic.egg-info/requires.txt +0 -0
  32. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/lca_algebraic.egg-info/top_level.txt +0 -0
  33. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/setup.cfg +0 -0
  34. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/test/test_axis_dict.py +0 -0
  35. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/test/test_axis_matrices_cache.py +0 -0
  36. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/test/test_cache.py +0 -0
  37. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/test/test_sympy.py +0 -0
  38. {lca_algebraic-1.4.1.dev1 → lca_algebraic-1.4.1.dev3}/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.dev1
3
+ Version: 1.4.1.dev3
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
@@ -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}
@@ -34,7 +34,7 @@ from lca_algebraic.bw_wrapper import (
34
34
  multi_lca,
35
35
  )
36
36
  from lca_algebraic.lambda_expression import LambdaExpr
37
- from lca_algebraic.settings import Settings
37
+ from lca_algebraic.settings import UNITY_METHOD, Settings
38
38
 
39
39
  from . import atomic, copyActivity, getActByCode, resetDb
40
40
  from .cache import (
@@ -162,15 +162,29 @@ def _multiLCAWithProxies(acts: list[ActivityExtended], methods: list[MethodKey])
162
162
  return _multiLCA([{act: 1} for act in proxy_acts.values()], methods)
163
163
 
164
164
 
165
- def _multiLCAWithCache(all_acts, methods) -> Dict[Tuple[ActivityExtended, MethodKey], float]:
165
+ def _multiLCAWithCache(all_acts, methods, scenario: str = None) -> Dict[Tuple[ActivityExtended, MethodKey], float]:
166
166
  res = dict()
167
167
 
168
+ # Possibly map bg activities to another target scenario db
169
+ # key = initial cat, value = target scenario act
170
+ scenario_mapping = _compute_scenario_mapping(all_acts, scenario=scenario)
171
+
172
+ all_acts = list(scenario_mapping.values())
173
+ reverse_scenario_mapping = {val: key for key, val in scenario_mapping.items()}
174
+
168
175
  # Split activities by db_name
169
176
  for db_name, acts in _group_acts_by_db(all_acts).items():
170
177
  with LCIACache(db_name) as cache:
171
178
  # List activities with at least one missing value
172
179
  remaining_acts = list(act for act in all_acts if any(method for method in methods if (act, method) not in cache.data))
173
180
 
181
+ # Handle custom "unity" method separately
182
+ if UNITY_METHOD in methods:
183
+ for act in acts:
184
+ for method in methods:
185
+ if not (act, method) in cache.data:
186
+ cache.data[(act, method)] = 1.0
187
+
174
188
  # list methods with at least one missing value
175
189
  remaining_methods = list(
176
190
  method for method in methods if any(act for act in all_acts if (act, method) not in cache.data)
@@ -190,7 +204,7 @@ def _multiLCAWithCache(all_acts, methods) -> Dict[Tuple[ActivityExtended, Method
190
204
  cache.data[(act, method)] = lca.iloc[imethod, iact]
191
205
 
192
206
  # Update res with a copy of the cache for selected impacts and activities
193
- res.update({(act, method): cache.data[(act, method)] for act in acts for method in methods})
207
+ res.update({(reverse_scenario_mapping[act], method): cache.data[(act, method)] for act in acts for method in methods})
194
208
 
195
209
  return res
196
210
 
@@ -253,7 +267,7 @@ def lambdify_expr(expr):
253
267
 
254
268
 
255
269
  def _preMultiLCAAlgebric(
256
- model: ActivityExtended, methods: List[MethodKey], alpha: ValueOrExpression = 1, axis: str = None, scenario: list[str] = None
270
+ model: ActivityExtended, methods: List[MethodKey], alpha: ValueOrExpression = 1, axis: str = None, scenarios: list[str] = None
257
271
  ) -> list[LambdaExpr]:
258
272
  """
259
273
  This method transforms an activity into a set of functions ready to compute LCA very fast on a set on methods.
@@ -262,26 +276,34 @@ def _preMultiLCAAlgebric(
262
276
  This method is used by multiLCAAlgebric
263
277
  """
264
278
 
265
- with DbContext(model):
266
- if isinstance(alpha, Quantity):
267
- alpha = alpha.magnitude
279
+ if not isinstance(scenarios, list):
280
+ scenarios = [scenarios]
281
+
282
+ res: list[list[LambdaExpr]] = []
268
283
 
269
- def _key(method):
270
- key = (model, axis, method, alpha)
271
- if scenario:
272
- key += (scenario,)
273
- return key
284
+ for scenario in scenarios:
285
+ with DbContext(model):
286
+ if isinstance(alpha, Quantity):
287
+ alpha = alpha.magnitude
274
288
 
275
- with ExprCache(model["database"]) as cache:
276
- missing_methods = [method for method in methods if not _key(method) in cache.data]
277
- if len(missing_methods) > 0:
278
- exprs = _modelToExpr(model, methods=missing_methods, axis=axis, alpha=alpha, scenario=scenario)
279
- for method, expr in zip(missing_methods, exprs):
280
- cache.data[_key(method)] = expr
289
+ def _key(method):
290
+ key = (model, axis, method, alpha)
291
+ if scenario:
292
+ key += (scenario,)
293
+ return key
281
294
 
282
- # At this point, everything is in cache
283
- # REturn the list in order
284
- return list(cache.data[_key(method)] for method in methods)
295
+ with ExprCache(model["database"]) as cache:
296
+ missing_methods = [method for method in methods if not _key(method) in cache.data]
297
+ if len(missing_methods) > 0:
298
+ exprs = _modelToExpr(model, methods=missing_methods, axis=axis, alpha=alpha, scenario=scenario)
299
+ for method, expr in zip(missing_methods, exprs):
300
+ cache.data[_key(method)] = expr
301
+
302
+ # At this point, everything is in cache
303
+ # REturn the list in order
304
+ res.append(list(cache.data[_key(method)] for method in methods))
305
+
306
+ return _merge_lambda_scenarios(res)
285
307
 
286
308
 
287
309
  def _build_mapping_key(act: Activity):
@@ -444,17 +466,13 @@ def _modelToExpr(
444
466
  zero.impacts = []
445
467
  return [zero] * len(methods)
446
468
 
447
- # Possibly map bg activities to another target scenario db
448
- act_mapping = _compute_scenario_mapping(generic_lambda_expr.background_activities, scenario=scenario)
449
-
450
469
  # Compute LCA for background activities
451
- all_impacts = _multiLCAWithCache(act_mapping.values(), methods)
470
+ all_impacts = _multiLCAWithCache(all_acts=generic_lambda_expr.background_activities, methods=methods, scenario=scenario)
452
471
 
453
472
  res = list()
454
473
 
455
474
  for method in methods:
456
- impacts = {bg_act: all_impacts[target_act, method] for bg_act, target_act in act_mapping.items()}
457
-
475
+ impacts = {bg_act: all_impacts[bg_act, method] for bg_act in generic_lambda_expr.background_activities}
458
476
  res.append(generic_lambda_expr.with_impacts(impacts))
459
477
 
460
478
  return res
@@ -487,8 +505,12 @@ def _postMultiLCAAlgebric(methods, lambdas: List[LambdaExpr], with_params=False,
487
505
 
488
506
  if len(lambdas[0].impacts) > 0:
489
507
  first_impact = lambdas[0].impacts[0]
508
+
509
+ # Vector impacts means several scenarios : should be same number as scenarios
490
510
  if isinstance(first_impact, (list, np.ndarray)):
491
511
  nb_scenarios = len(first_impact)
512
+ if nb_scenarios > 1 and param_length > 1 and nb_scenarios != param_length:
513
+ raise Exception("Number of scenarios and number of params should be the same")
492
514
  param_length = max(param_length, nb_scenarios)
493
515
 
494
516
  # lambda are SymDict ?
@@ -635,6 +657,8 @@ def compute_inventory(
635
657
  as_dict=False,
636
658
  impact_method=None,
637
659
  fields=["database", "name", "location", "unit"],
660
+ scenario: Union[str, list[str], None] = None,
661
+ filter_zeros=True,
638
662
  **params,
639
663
  ):
640
664
  """
@@ -667,41 +691,55 @@ def compute_inventory(
667
691
  Dataframe or Dict of act => value
668
692
 
669
693
  """
694
+ if not isinstance(scenario, list):
695
+ scenario = [scenario]
696
+
697
+ if impact_method is None:
698
+ impact_method = UNITY_METHOD
699
+
700
+ zeros = np.full(len(scenario), 0.0)
670
701
 
671
702
  with temp_settings(factorize_static_bg=False):
672
- lambda_expr = _actToLambdaExpr(model, alpha=1 / functional_unit)
703
+ # Compute once the generic lambda expression
704
+ lambda_exprs = _preMultiLCAAlgebric(model=model, methods=[impact_method], alpha=1 / functional_unit, scenarios=scenario)
705
+
706
+ lambda_expr = lambda_exprs[0]
673
707
 
674
708
  # Transform to dict of act => value
675
- val_by_act = dict()
709
+ val_by_act: Dict[Activity, DataFrame] = dict()
676
710
  for bg_act in lambda_expr.background_activities:
677
- # Dummy impact set to 1 only for current bg act, zero for others
678
- impacts = {act: 1.0 if act == bg_act else 0.0 for act in lambda_expr.background_activities}
711
+ impacts = lambda_expr.impacts_dict()
712
+
713
+ # Build temp lambda with only a single act activated, the other impacts being set to Zero
714
+ filtered_impacts = {act: impacts[act] if act == bg_act else zeros for act in lambda_expr.background_activities}
679
715
 
680
- bg_expr = lambda_expr.with_impacts(impacts)
681
- val = bg_expr.compute(**params).value
682
- if isinstance(val, dict):
683
- val = val["_all_"]
684
- val_by_act[bg_act] = val
716
+ single_bg_expr = lambda_expr.with_impacts(filtered_impacts)
685
717
 
686
- if impact_method is not None:
687
- # Compute LCA of background activities
688
- impact_by_act = _multiLCAWithCache(val_by_act.keys(), [impact_method])
718
+ res = _postMultiLCAAlgebric(methods=[impact_method], lambdas=[single_bg_expr], **params)
689
719
 
690
- val_by_act: {act: value * impact_by_act[(act, impact_method)] for act, value in val_by_act.items()}
720
+ val_by_act[bg_act] = res.values.flatten()
691
721
 
692
722
  if as_dict:
693
723
  return val_by_act
694
724
 
695
725
  # Transform to dataframe
696
726
  items = []
697
- for act, value in val_by_act.items():
727
+ for act, values in val_by_act.items():
728
+ if filter_zeros and np.all(values == 0.0):
729
+ continue
730
+
698
731
  item = dict()
699
732
 
700
733
  for field in fields:
701
734
  if field in act:
702
735
  item[field] = act[field]
703
736
 
704
- item["value"] = value
737
+ # Flatten multiple values
738
+ if len(values) == 1:
739
+ item["value"] = values[0]
740
+ else:
741
+ for i in range(0, len(values)):
742
+ item[scenario[i]] = values[i]
705
743
  items.append(item)
706
744
 
707
745
  return DataFrame(items)
@@ -857,9 +895,7 @@ def compute_impacts(
857
895
 
858
896
  scenarios = scenario if isinstance(scenario, list) else [scenario]
859
897
 
860
- lambdas = _merge_lambda_scenarios(
861
- _preMultiLCAAlgebric(model, methods, scenario=scenario, alpha=alpha, axis=axis) for scenario in scenarios
862
- )
898
+ lambdas = _preMultiLCAAlgebric(model, methods, scenarios=scenarios, alpha=alpha, axis=axis)
863
899
 
864
900
  unit: Optional[Unit] = functional_unit.units if isinstance(functional_unit, Quantity) else None
865
901
 
@@ -4,6 +4,7 @@ from typing import Dict, Tuple
4
4
  from pint import Unit
5
5
 
6
6
  from lca_algebraic.bw_wrapper import Method, methods
7
+ from lca_algebraic.settings import UNITY_METHOD
7
8
 
8
9
 
9
10
  def _impact_labels():
@@ -56,7 +57,11 @@ def findMethods(search=None, mainCat=None):
56
57
  def method_unit(method: Tuple, fu_unit: Unit = None):
57
58
  """Get the unit of an impact method"""
58
59
 
59
- res = Method(method).metadata["unit"]
60
+ if method == UNITY_METHOD:
61
+ res = "unit"
62
+ else:
63
+ res = Method(method).metadata["unit"]
64
+
60
65
  if fu_unit is not None:
61
66
  res += f" / {fu_unit}"
62
67
 
@@ -11,6 +11,10 @@ except ImportError:
11
11
  pass
12
12
 
13
13
 
14
+ # Custom method that returns "1.0" used for inventories
15
+ UNITY_METHOD = ("UNITY", "UNITY", "UNITY")
16
+
17
+
14
18
  class CSE(StrEnum):
15
19
  NONE = "none"
16
20
  DEFAULT = "default" # Default sympy one
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lca_algebraic
3
- Version: 1.4.1.dev1
3
+ Version: 1.4.1.dev3
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lca_algebraic"
7
- version = "1.4.1.dev1"
7
+ version = "1.4.1.dev3"
8
8
 
9
9
  description = "This library provides a layer above Brightway for defining parametric models and running super fast LCA for monte carlo analysis."
10
10
  readme = "README.md"
@@ -1,7 +1,9 @@
1
1
  from numpy.ma.testutils import assert_array_equal
2
+ from pandas import DataFrame
2
3
 
3
- from lca_algebraic import resetDb, newActivity, newFloatParam, compute_impacts, temp_settings
4
+ from lca_algebraic import resetDb, newActivity, newFloatParam, compute_impacts, temp_settings, compute_inventory
4
5
  from test.conftest import DataFixture, USER_DB, assert_impacts
6
+ from pandas.testing import assert_frame_equal
5
7
 
6
8
 
7
9
  def test_compute_impact_with_scenario(data: DataFixture):
@@ -73,6 +75,38 @@ def test_compute_impact_with_several_scenarios(data: DataFixture):
73
75
  print(res)
74
76
 
75
77
 
78
+ def test_compute_inventories_with_several_scenarios(data: DataFixture):
79
+ # Scnerio database are separated with '#'
80
+ BG_DB1 = "bg#scen1"
81
+ BG_DB2 = "bg#scen2"
82
+
83
+ # Build two databases
84
+ resetDb(BG_DB1, foreground=False)
85
+ resetDb(BG_DB2, foreground=False)
86
+
87
+ # Add twin BG activities with two different values
88
+ bga = newActivity(BG_DB1, name="bg_a", unit="kg", exchanges={data.bio1: 1.0})
89
+ bga_ = newActivity(BG_DB2, name="bg_a", unit="kg", exchanges={data.bio1: 2.0})
90
+
91
+ # Add twin BG activities with two different values ibio2
92
+ bgb = newActivity(BG_DB1, name="bg_b", unit="kg", exchanges={data.bio2: 1.0})
93
+ bgb_ = newActivity(BG_DB2, name="bg_b", unit="kg", exchanges={data.bio2: 2.0})
94
+
95
+ # Build parametrized model on BG1
96
+ p1 = newFloatParam("p1", default=1, min=0, max=1)
97
+
98
+ fg_act = newActivity(USER_DB, "fg", "kg", {bga: p1, bgb: 1.0})
99
+
100
+ # Raw impacts without scenario : targeting scen1
101
+ res = compute_inventory(model=fg_act, impact_method=data.ibio1, p1=[1.0, 2.0], scenario=["scen1", "scen2"])
102
+
103
+ assert_frame_equal(
104
+ rtol=1e-03,
105
+ left=res,
106
+ right=DataFrame([{"database": "bg#scen1", "name": "bg_a", "location": "GLO", "unit": "kg", "scen1": 1.0, "scen2": 4.0}]),
107
+ )
108
+
109
+
76
110
  def test_compute_impact_with_background_proxy(data: DataFixture):
77
111
  """Test the same with background proxy factorization activated"""
78
112