lca-algebraic 1.4.1.dev4__tar.gz → 1.4.1.dev6__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 (44) hide show
  1. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/PKG-INFO +1 -1
  2. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/activity.py +38 -11
  3. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/base_utils.py +1 -0
  4. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/bw_wrapper/__init__.py +5 -0
  5. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/bw_wrapper/bw2.py +10 -0
  6. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/bw_wrapper/bw25.py +10 -0
  7. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/bw_wrapper/protocol.py +3 -0
  8. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/cache.py +10 -2
  9. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/lambda_expression.py +9 -3
  10. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/lca.py +172 -67
  11. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/log.py +8 -0
  12. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/methods.py +15 -8
  13. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/params.py +48 -10
  14. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/settings.py +4 -0
  15. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/stats.py +2 -2
  16. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic.egg-info/PKG-INFO +1 -1
  17. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic.egg-info/SOURCES.txt +5 -0
  18. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/pyproject.toml +1 -1
  19. lca_algebraic-1.4.1.dev6/test/test_expand_params.py +26 -0
  20. lca_algebraic-1.4.1.dev6/test/test_methods.py +35 -0
  21. lca_algebraic-1.4.1.dev6/test/test_overwrite.py +73 -0
  22. lca_algebraic-1.4.1.dev6/test/test_params.py +90 -0
  23. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/test/test_premise.py +35 -1
  24. lca_algebraic-1.4.1.dev6/test/test_proxy_cache.py +47 -0
  25. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/test/test_stats.py +13 -0
  26. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/test/test_units.py +4 -10
  27. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/LICENSE +0 -0
  28. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/README.md +0 -0
  29. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/__init__.py +0 -0
  30. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/axis_dict.py +0 -0
  31. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/database.py +0 -0
  32. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/interpolation.py +0 -0
  33. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/io.py +0 -0
  34. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/matrix.py +0 -0
  35. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/sympy_utils.py +0 -0
  36. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic/units.py +0 -0
  37. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic.egg-info/dependency_links.txt +0 -0
  38. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic.egg-info/requires.txt +0 -0
  39. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/lca_algebraic.egg-info/top_level.txt +0 -0
  40. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/setup.cfg +0 -0
  41. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/test/test_axis_dict.py +0 -0
  42. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/test/test_axis_matrices_cache.py +0 -0
  43. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/test/test_cache.py +0 -0
  44. {lca_algebraic-1.4.1.dev4 → lca_algebraic-1.4.1.dev6}/test/test_sympy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lca_algebraic
3
- Version: 1.4.1.dev4
3
+ Version: 1.4.1.dev6
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
@@ -13,12 +13,14 @@ from lca_algebraic.bw_wrapper import (
13
13
  Activity,
14
14
  Exchange,
15
15
  ExchangeDataset,
16
+ activity_not_found,
16
17
  copy_activity_skip_keys,
17
18
  dict_as_exchangedataset,
18
19
  exchange_type_for_sub_act,
19
20
  get_activity,
20
21
  process_node_default,
21
22
  production_edge_default,
23
+ purge_activity_content,
22
24
  )
23
25
  from lca_algebraic.database import (
24
26
  _find_biosphere_db,
@@ -168,6 +170,11 @@ class ActivityExtended(Activity):
168
170
  or activity for updating only input,
169
171
  or dict of attributes, for updating both at once, or any other attribute.
170
172
  The amount can reference the symbol 'old_amount' that will be replaced with the current amount of the exchange.
173
+
174
+ Passing a dict of attributes is also the way to tag a single exchange with an axis \
175
+ (see ``compute_impacts``) : only that exchange will be ventilated under the category::
176
+
177
+ myActivity.updateExchanges({some_act: dict(amount=2, phase="transport")})
171
178
  """
172
179
  if not _isForeground(self["database"]) and not Settings.internals:
173
180
  msg = f"You are updating a background activity in {self['database']}. Use copyActivity() in a foreground db instead."
@@ -319,7 +326,6 @@ class ActivityExtended(Activity):
319
326
  def _amount_to_formula(self, amount: ValueOrExpression, exchange: ExchangeDataset):
320
327
  res = dict()
321
328
  if isinstance(amount, Basic):
322
-
323
329
  if amount.is_number:
324
330
  return {"amount": float(amount.evalf())}
325
331
 
@@ -336,7 +342,7 @@ class ActivityExtended(Activity):
336
342
  res[STORE_FORMULA_KEY] = str(amount)
337
343
  res[STORE_FORMULA_TAG] = True
338
344
  res["amount"] = 0
339
- elif isinstance(amount, float|int):
345
+ elif isinstance(amount, float | int):
340
346
  res["amount"] = amount
341
347
  else:
342
348
  raise Exception(
@@ -401,7 +407,14 @@ class ActivityExtended(Activity):
401
407
  yield exch
402
408
 
403
409
  def updateMeta(self, **kwargs):
404
- """Update any property. Useful to update axes"""
410
+ """Update any custom property of the activity.
411
+
412
+ This is typically used to tag an activity with an axis : all its outgoing
413
+ exchanges will then be ventilated under that category by ``compute_impacts``::
414
+
415
+ myActivity.updateMeta(phase="manufacturing")
416
+ compute_impacts(model, methods, axis="phase")
417
+ """
405
418
  for key, val in kwargs.items():
406
419
  self._data[key] = val
407
420
  self.save()
@@ -484,6 +497,11 @@ def findActivity(
484
497
  search = search.lower()
485
498
  search = search.replace(",", " ")
486
499
 
500
+ # bw2data >= 4 backs `search` with FTS5, which rejects blank queries
501
+ # while bw2data 3 silently returned nothing.
502
+ if not search.strip():
503
+ return []
504
+
487
505
  # Find candidates via index
488
506
  # candidates = _find_candidates(db_name, name_key)
489
507
  candidates = _getDb(db_name).search(search, limit=limit)
@@ -491,7 +509,8 @@ def findActivity(
491
509
  if len(candidates) == 0:
492
510
  # Try again removing strange caracters
493
511
  search = re.sub(r"\w*[^a-zA-Z ]+\w*", " ", search)
494
- candidates = _getDb(db_name).search(search, limit=limit)
512
+ if search.strip():
513
+ candidates = _getDb(db_name).search(search, limit=limit)
495
514
 
496
515
  # Exact match
497
516
  return list(filter(act_filter, candidates))
@@ -555,13 +574,19 @@ def _newAct(db_name, code):
555
574
  warn(msg)
556
575
 
557
576
  db = _getDb(db_name)
558
- # Already present : delete it ?
559
- for act in db:
560
- if act["code"] == code:
561
- warn("Activity '%s' was already in '%s'. Overwriting it" % (code, db_name))
562
- act.delete()
563
577
 
564
- return db.new_activity(code)
578
+ try:
579
+ act = getActByCode(db_name, code)
580
+ except activity_not_found():
581
+ return db.new_activity(code)
582
+
583
+ # Already present : overwrite it in place.
584
+ # We must not delete+recreate the activity : on brightway 2.5, deleting an
585
+ # activity also removes the incoming links (exchanges from other activities
586
+ # consuming it), which would silently break previously built models.
587
+ warn("Activity '%s' was already in '%s'. Overwriting it" % (code, db_name))
588
+ purge_activity_content(act)
589
+ return act
565
590
 
566
591
 
567
592
  def newActivity(
@@ -638,7 +663,9 @@ def newActivity(
638
663
 
639
664
 
640
665
  @atomic
641
- def copyActivity(db_name, activity: ActivityExtended, code=None, withExchanges=True, stripFormula=True, input_mapping=None, **kwargs) -> ActivityExtended:
666
+ def copyActivity(
667
+ db_name, activity: ActivityExtended, code=None, withExchanges=True, stripFormula=True, input_mapping=None, **kwargs
668
+ ) -> ActivityExtended:
642
669
  """Copy an activity and its exchanges into another database. You usually want to copy activities from your background to
643
670
  your foreground DB to update them, keeping your background DB clean.
644
671
 
@@ -52,6 +52,7 @@ def _getDb(dbname):
52
52
 
53
53
  def _MinMax(op, arg0, *args):
54
54
  from lca_algebraic.units import unit_registry as u
55
+
55
56
  if not Settings.units_enabled:
56
57
  return op(arg0, *args)
57
58
  same_unit = all(x.units == arg0.units for x in args)
@@ -111,6 +111,10 @@ def copy_activity_skip_keys() -> set:
111
111
  return _backend.copy_activity_skip_keys()
112
112
 
113
113
 
114
+ def purge_activity_content(act) -> None:
115
+ _backend.purge_activity_content(act)
116
+
117
+
114
118
  def bw2setup() -> None:
115
119
  _backend.bw2setup()
116
120
 
@@ -146,5 +150,6 @@ __all__ = [
146
150
  "process_node_default",
147
151
  "production_edge_default",
148
152
  "projects",
153
+ "purge_activity_content",
149
154
  "sqlite3_lci_db",
150
155
  ]
@@ -91,6 +91,16 @@ class BW2Backend:
91
91
  def copy_activity_skip_keys(self) -> set:
92
92
  return {"database", "code"}
93
93
 
94
+ def purge_activity_content(self, act) -> None:
95
+ """Remove an activity's outgoing exchanges and non-identity attributes.
96
+
97
+ The activity row, its code and the incoming links (exchanges of other
98
+ activities pointing to it) are preserved.
99
+ """
100
+ act.exchanges().delete()
101
+ for key in [k for k in act._data if k not in {"database", "code"}]:
102
+ del act._data[key]
103
+
94
104
  def bw2setup(self):
95
105
  bw.bw2setup()
96
106
 
@@ -108,6 +108,16 @@ class BW25Backend:
108
108
  def copy_activity_skip_keys(self) -> set:
109
109
  return {"database", "code", "id"}
110
110
 
111
+ def purge_activity_content(self, act) -> None:
112
+ """Remove an activity's outgoing exchanges and non-identity attributes.
113
+
114
+ The activity row, its code and the incoming links (exchanges of other
115
+ activities pointing to it) are preserved.
116
+ """
117
+ act.exchanges().delete(allow_in_sourced_project=True)
118
+ for key in [k for k in act._data if k not in {"database", "code", "id"}]:
119
+ del act._data[key]
120
+
111
121
  def bw2setup(self) -> None:
112
122
  raise NotImplementedError("bw2setup is only supported with brightway2")
113
123
 
@@ -74,6 +74,9 @@ class BrightwayBackend(Protocol):
74
74
  def copy_activity_skip_keys(self) -> set:
75
75
  ...
76
76
 
77
+ def purge_activity_content(self, act) -> None:
78
+ ...
79
+
77
80
  def bw2setup(self) -> None:
78
81
  ...
79
82
 
@@ -49,8 +49,16 @@ def flush_caches():
49
49
  cache.sync()
50
50
 
51
51
 
52
- def get_dependant_dbs(db_name, skip_proxy=True):
53
- """Recursively get list of dependant db names, including the current one"""
52
+ def get_dependant_dbs(db_name, skip_proxy=None):
53
+ """Recursively get list of dependant db names, including the current one
54
+
55
+ Proxy databases are skipped unless background factorization is enabled : with
56
+ ``factorize_static_bg`` the developed expressions / LCIA of a database refer to
57
+ the acts of its ``<db>-proxy`` databases, so a change of those proxies must be
58
+ visible (see the staleness check in ``_CacheDict``).
59
+ """
60
+ if skip_proxy is None:
61
+ skip_proxy = not Settings.factorize_static_bg
54
62
 
55
63
  # Only work if database is up to date (processed)
56
64
  if databases[db_name].get("dirty"):
@@ -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.core import S
@@ -9,6 +9,7 @@ from sympy.printing.numpy import NumPyPrinter
9
9
  from lca_algebraic.axis_dict import AxisDict
10
10
  from lca_algebraic.base_utils import _user_functions
11
11
  from lca_algebraic.bw_wrapper import Activity
12
+ from lca_algebraic.log import warn
12
13
  from lca_algebraic.params import (
13
14
  _complete_params,
14
15
  _expand_param_names,
@@ -66,7 +67,7 @@ class LambdaExpr:
66
67
 
67
68
  # List of background activities, indexed in the same order as impacts[i]
68
69
  self.background_activities: List[Activity] = list()
69
- self.impacts: List[float] = None
70
+ self.impacts: Optional[List[float]] = None
70
71
 
71
72
  if isinstance(expr, dict) and not isinstance(expr, AxisDict):
72
73
  # Come from JSON serialization
@@ -150,12 +151,17 @@ class LambdaExpr:
150
151
  def with_impacts(self, impacts: Dict[Activity, float]):
151
152
  """Transforms a generic LambaExpr toa one with custom impact values"""
152
153
  if self.impacts is not None:
153
- raise Exception("You are specializing a LambaExpression with impacts already set.")
154
+ warn("You are specializing a LambaExpression with impacts already set.")
155
+
154
156
  res = copy(self)
155
157
  res.impacts = [impacts[bg_act] for bg_act in self.background_activities]
156
158
 
157
159
  return res
158
160
 
161
+ def impacts_dict(self):
162
+ """Returns list of impacts as dict"""
163
+ return {bg_act: val for bg_act, val in zip(self.background_activities, self.impacts)}
164
+
159
165
 
160
166
  def _filter_param_values(params, expanded_param_names):
161
167
  return {key: val for key, val in params.items() if key in expanded_param_names}