bw-graph-tools 0.3__tar.gz → 0.4__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.
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/LICENSE +1 -1
- bw_graph_tools-0.4/MANIFEST.in +0 -0
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/PKG-INFO +24 -30
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/README.md +4 -11
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/bw_graph_tools/__init__.py +4 -5
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/bw_graph_tools/graph_traversal.py +50 -38
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/bw_graph_tools/graph_traversal_utils.py +1 -4
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/bw_graph_tools/matrix_tools.py +21 -11
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/bw_graph_tools/shortest_path.py +40 -14
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/bw_graph_tools/testing.py +4 -2
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/bw_graph_tools.egg-info/PKG-INFO +25 -31
- bw_graph_tools-0.4/bw_graph_tools.egg-info/SOURCES.txt +21 -0
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/bw_graph_tools.egg-info/requires.txt +10 -9
- bw_graph_tools-0.4/bw_graph_tools.egg-info/top_level.txt +1 -0
- bw_graph_tools-0.4/pyproject.toml +105 -0
- bw_graph_tools-0.4/setup.cfg +4 -0
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/tests/test_first_heuristic.py +56 -4
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/tests/test_get_path_from_matrix.py +16 -9
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/tests/test_second_heuristic.py +6 -18
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/tests/test_third_heuristic.py +5 -15
- bw_graph_tools-0.3/MANIFEST.in +0 -1
- bw_graph_tools-0.3/bw_graph_tools/VERSION +0 -1
- bw_graph_tools-0.3/bw_graph_tools/utils.py +0 -15
- bw_graph_tools-0.3/bw_graph_tools.egg-info/SOURCES.txt +0 -31
- bw_graph_tools-0.3/bw_graph_tools.egg-info/not-zip-safe +0 -1
- bw_graph_tools-0.3/bw_graph_tools.egg-info/top_level.txt +0 -5
- bw_graph_tools-0.3/docs/conf.py +0 -93
- bw_graph_tools-0.3/pyproject.toml +0 -3
- bw_graph_tools-0.3/setup.cfg +0 -91
- bw_graph_tools-0.3/tests/traversal/test_marc_vd_meide.py +0 -160
- bw_graph_tools-0.3/tests/traversal/test_negative_production.py +0 -239
- bw_graph_tools-0.3/tests/traversal/test_nonunitary_production_with_recursion.py +0 -239
- bw_graph_tools-0.3/tests/traversal/test_only_one_layer.py +0 -161
- bw_graph_tools-0.3/tests/traversal/test_separate_production.py +0 -241
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/bw_graph_tools/errors.py +0 -0
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/bw_graph_tools.egg-info/dependency_links.txt +0 -0
- {bw_graph_tools-0.3 → bw_graph_tools-0.4}/tests/test_matrix_utils.py +0 -0
|
File without changes
|
|
@@ -1,43 +1,44 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bw_graph_tools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: Graph traversal class and utilities
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Author-email: <cmutel@gmail.com>
|
|
8
|
-
Maintainer: Chris Mutel
|
|
9
|
-
Maintainer-email: <cmutel@gmail.com>
|
|
10
|
-
License: BSD-3-Clause
|
|
5
|
+
Author-email: Chris Mutel <cmutel@gmail.com>
|
|
6
|
+
Maintainer-email: Chris Mutel <cmutel@gmail.com>
|
|
11
7
|
Project-URL: source, https://github.com/brightway-lca/bw_graph_tools
|
|
12
8
|
Project-URL: homepage, https://github.com/brightway-lca/bw_graph_tools
|
|
13
9
|
Project-URL: tracker, https://github.com/brightway-lca/bw_graph_tools/issues
|
|
14
|
-
|
|
15
|
-
Platform: any
|
|
16
|
-
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
17
11
|
Classifier: Programming Language :: Python
|
|
18
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
23
22
|
License-File: LICENSE
|
|
24
23
|
Requires-Dist: bw2calc>=2.0.dev13
|
|
24
|
+
Requires-Dist: bw_processing
|
|
25
25
|
Requires-Dist: matrix_utils
|
|
26
|
-
Requires-Dist: numpy
|
|
26
|
+
Requires-Dist: numpy<2
|
|
27
27
|
Requires-Dist: scipy
|
|
28
|
-
Requires-Dist: bw_processing
|
|
29
28
|
Provides-Extra: testing
|
|
30
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: bw_graph_tools; extra == "testing"
|
|
30
|
+
Requires-Dist: bw2data; extra == "testing"
|
|
31
31
|
Requires-Dist: pytest; extra == "testing"
|
|
32
32
|
Requires-Dist: pytest-cov; extra == "testing"
|
|
33
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: python-coveralls; extra == "testing"
|
|
34
34
|
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: build; extra == "dev"
|
|
35
36
|
Requires-Dist: pre-commit; extra == "dev"
|
|
36
37
|
Requires-Dist: pylint; extra == "dev"
|
|
37
|
-
|
|
38
|
-
Requires-Dist:
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
Requires-Dist:
|
|
38
|
+
Requires-Dist: pytest; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-randomly; extra == "dev"
|
|
41
|
+
Requires-Dist: setuptools; extra == "dev"
|
|
41
42
|
|
|
42
43
|
# bw_graph_tools
|
|
43
44
|
|
|
@@ -85,7 +86,6 @@ Normally we construct matrices and solve the resulting set of linear equations t
|
|
|
85
86
|
If we add temporal information using `bw_temporalis`, then the same node can occur at different times depending on how the temporal dynamics its preceding path. For example:
|
|
86
87
|
|
|
87
88
|
|
|
88
|
-
|
|
89
89
|
## Contributing
|
|
90
90
|
|
|
91
91
|
Contributions are very welcome.
|
|
@@ -103,15 +103,9 @@ please [file an issue] along with a detailed description.
|
|
|
103
103
|
|
|
104
104
|
## Documentation
|
|
105
105
|
|
|
106
|
-
1. Install the
|
|
107
|
-
2. Build the documentation locally by running
|
|
106
|
+
1. Install the conda environment from the file `.docs/environment.yml`
|
|
107
|
+
2. Build the documentation locally by running:
|
|
108
108
|
|
|
109
|
-
```
|
|
109
|
+
```bash
|
|
110
110
|
sphinx-autobuild docs _build/html -a -j auto --open-browser
|
|
111
111
|
```
|
|
112
|
-
|
|
113
|
-
<!-- github-only -->
|
|
114
|
-
|
|
115
|
-
[command-line reference]: https://bw_graph_tools.readthedocs.io/en/latest/usage.html
|
|
116
|
-
[license]: https://github.com/brightway-lca/bw_graph_tools/blob/main/LICENSE
|
|
117
|
-
[contributor guide]: https://github.com/brightway-lca/bw_graph_tools/blob/main/CONTRIBUTING.md
|
|
@@ -44,7 +44,6 @@ Normally we construct matrices and solve the resulting set of linear equations t
|
|
|
44
44
|
If we add temporal information using `bw_temporalis`, then the same node can occur at different times depending on how the temporal dynamics its preceding path. For example:
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
|
|
48
47
|
## Contributing
|
|
49
48
|
|
|
50
49
|
Contributions are very welcome.
|
|
@@ -62,15 +61,9 @@ please [file an issue] along with a detailed description.
|
|
|
62
61
|
|
|
63
62
|
## Documentation
|
|
64
63
|
|
|
65
|
-
1. Install the
|
|
66
|
-
2. Build the documentation locally by running
|
|
64
|
+
1. Install the conda environment from the file `.docs/environment.yml`
|
|
65
|
+
2. Build the documentation locally by running:
|
|
67
66
|
|
|
68
|
-
```
|
|
67
|
+
```bash
|
|
69
68
|
sphinx-autobuild docs _build/html -a -j auto --open-browser
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
<!-- github-only -->
|
|
73
|
-
|
|
74
|
-
[command-line reference]: https://bw_graph_tools.readthedocs.io/en/latest/usage.html
|
|
75
|
-
[license]: https://github.com/brightway-lca/bw_graph_tools/blob/main/LICENSE
|
|
76
|
-
[contributor guide]: https://github.com/brightway-lca/bw_graph_tools/blob/main/CONTRIBUTING.md
|
|
69
|
+
```
|
|
@@ -11,9 +11,8 @@ __all__ = (
|
|
|
11
11
|
"to_normalized_adjacency_matrix",
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
from .utils import get_version_tuple
|
|
14
|
+
__version__ = "0.4"
|
|
15
|
+
|
|
17
16
|
from .graph_traversal import (
|
|
18
17
|
AssumedDiagonalGraphTraversal,
|
|
19
18
|
Edge,
|
|
@@ -21,5 +20,5 @@ from .graph_traversal import (
|
|
|
21
20
|
NewNodeEachVisitGraphTraversal,
|
|
22
21
|
Node,
|
|
23
22
|
)
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
from .graph_traversal_utils import get_path_from_matrix, path_as_brightway_objects
|
|
24
|
+
from .matrix_tools import guess_production_exchanges, to_normalized_adjacency_matrix
|
|
@@ -2,11 +2,12 @@ import warnings
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from functools import lru_cache
|
|
4
4
|
from heapq import heappop, heappush
|
|
5
|
+
from typing import Optional
|
|
5
6
|
|
|
6
|
-
from bw2calc import LCA
|
|
7
|
-
from scipy.sparse import spmatrix
|
|
8
7
|
import matrix_utils as mu
|
|
9
8
|
import numpy as np
|
|
9
|
+
from bw2calc import LCA
|
|
10
|
+
from scipy.sparse import spmatrix
|
|
10
11
|
|
|
11
12
|
try:
|
|
12
13
|
from bw2data import databases
|
|
@@ -70,9 +71,15 @@ class Node:
|
|
|
70
71
|
supply_amount : float
|
|
71
72
|
The amount of the *activity* (not reference product!) needed to supply the demand from the requesting supply chain edge.
|
|
72
73
|
cumulative_score : float
|
|
73
|
-
Total LCIA score attributed to `supply_amount` of this activity
|
|
74
|
+
Total LCIA score attributed to `supply_amount` of this activity, including impacts from direct emissions.
|
|
74
75
|
direct_emissions_score : float
|
|
75
76
|
Total LCIA score attributed only to the direct characterized biosphere flows of `supply_amount` of this activity.
|
|
77
|
+
direct_emissions_score_outside_specific_flows : float
|
|
78
|
+
The score attributable to *direct emissions* of this node which isn't broken out into separate `Flow` objects.
|
|
79
|
+
remaining_cumulative_score_outside_specific_flows : float
|
|
80
|
+
The *cumulative* score of this node, including direct emissions, which isn't broken out into separate `Flow` objects.
|
|
81
|
+
terminal : bool
|
|
82
|
+
Boolean flag indicating whether graph traversal was cutoff at this node
|
|
76
83
|
|
|
77
84
|
"""
|
|
78
85
|
|
|
@@ -85,6 +92,9 @@ class Node:
|
|
|
85
92
|
supply_amount: float
|
|
86
93
|
cumulative_score: float
|
|
87
94
|
direct_emissions_score: float
|
|
95
|
+
direct_emissions_score_outside_specific_flows: float = 0.0
|
|
96
|
+
remaining_cumulative_score_outside_specific_flows: float = 0.0
|
|
97
|
+
terminal: bool = False
|
|
88
98
|
|
|
89
99
|
def __lt__(self, other):
|
|
90
100
|
# Needed for sorting
|
|
@@ -176,13 +186,13 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
176
186
|
def calculate(
|
|
177
187
|
cls,
|
|
178
188
|
lca_object: LCA,
|
|
179
|
-
cutoff: float
|
|
180
|
-
biosphere_cutoff: float
|
|
181
|
-
max_calc: int
|
|
182
|
-
skip_coproducts: bool
|
|
183
|
-
separate_biosphere_flows: bool
|
|
184
|
-
static_activity_indices: set[int]
|
|
185
|
-
functional_unit_unique_id: int
|
|
189
|
+
cutoff: Optional[float] = 5e-3,
|
|
190
|
+
biosphere_cutoff: Optional[float] = 1e-4,
|
|
191
|
+
max_calc: Optional[int] = 10000,
|
|
192
|
+
skip_coproducts: Optional[bool] = False,
|
|
193
|
+
separate_biosphere_flows: Optional[bool] = True,
|
|
194
|
+
static_activity_indices: Optional[set[int]] = set(),
|
|
195
|
+
functional_unit_unique_id: Optional[int] = -1,
|
|
186
196
|
) -> dict:
|
|
187
197
|
"""
|
|
188
198
|
Priority-first traversal (i.e. follow the past of highest score) of the supply chain graph. This class unrolls
|
|
@@ -240,8 +250,8 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
240
250
|
traverse
|
|
241
251
|
functional_unit_unique_id : int
|
|
242
252
|
An integer id we can use for the functional unit virtual activity.
|
|
243
|
-
Shouldn't overlap any other activity ids. Don't change unless you
|
|
244
|
-
|
|
253
|
+
Shouldn't overlap any other activity ids. Don't change unless you
|
|
254
|
+
really know what you are doing.
|
|
245
255
|
|
|
246
256
|
Returns
|
|
247
257
|
-------
|
|
@@ -257,8 +267,7 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
257
267
|
biosphere_cutoff_score = abs(total_score * biosphere_cutoff)
|
|
258
268
|
technosphere_matrix = lca_object.technosphere_matrix
|
|
259
269
|
production_exchange_mapping = {
|
|
260
|
-
x: y
|
|
261
|
-
for x, y in zip(*cls.get_production_exchanges(lca_object.technosphere_mm))
|
|
270
|
+
x: y for x, y in zip(*cls.get_production_exchanges(lca_object.technosphere_mm))
|
|
262
271
|
}
|
|
263
272
|
heap, edges, flows = [], [], []
|
|
264
273
|
calculation_count = Counter()
|
|
@@ -282,9 +291,7 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
282
291
|
cls.traverse_edges(
|
|
283
292
|
consumer_index=functional_unit_unique_id,
|
|
284
293
|
consumer_unique_id=functional_unit_unique_id,
|
|
285
|
-
product_indices=[
|
|
286
|
-
lca_object.dicts.product[key] for key in lca_object.demand
|
|
287
|
-
],
|
|
294
|
+
product_indices=[lca_object.dicts.product[key] for key in lca_object.demand],
|
|
288
295
|
product_amounts=lca_object.demand.values(),
|
|
289
296
|
lca=lca_object,
|
|
290
297
|
calculation_count=calculation_count,
|
|
@@ -323,6 +330,11 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
323
330
|
|
|
324
331
|
flows.sort(reverse=True)
|
|
325
332
|
|
|
333
|
+
non_terminal_nodes = {edge.consumer_unique_id for edge in edges}
|
|
334
|
+
for node_id_key in nodes:
|
|
335
|
+
if node_id_key not in non_terminal_nodes:
|
|
336
|
+
nodes[node_id_key].terminal = True
|
|
337
|
+
|
|
326
338
|
return {
|
|
327
339
|
"nodes": nodes,
|
|
328
340
|
"edges": edges,
|
|
@@ -461,9 +473,7 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
461
473
|
|
|
462
474
|
supply = caching_solver(product_index, product_amount)
|
|
463
475
|
cumulative_score = float((characterized_biosphere * supply).sum())
|
|
464
|
-
reference_product_net_production_amount = matrix[
|
|
465
|
-
product_index, producer_index
|
|
466
|
-
]
|
|
476
|
+
reference_product_net_production_amount = matrix[product_index, producer_index]
|
|
467
477
|
scale = product_amount / reference_product_net_production_amount
|
|
468
478
|
|
|
469
479
|
if abs(cumulative_score) < cutoff_score:
|
|
@@ -473,16 +483,12 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
473
483
|
unique_id=next(calculation_count),
|
|
474
484
|
activity_datapackage_id=lca.dicts.activity.reversed[producer_index],
|
|
475
485
|
activity_index=producer_index,
|
|
476
|
-
reference_product_datapackage_id=lca.dicts.product.reversed[
|
|
477
|
-
product_index
|
|
478
|
-
],
|
|
486
|
+
reference_product_datapackage_id=lca.dicts.product.reversed[product_index],
|
|
479
487
|
reference_product_index=product_index,
|
|
480
488
|
reference_product_production_amount=reference_product_net_production_amount,
|
|
481
489
|
supply_amount=scale,
|
|
482
490
|
cumulative_score=cumulative_score,
|
|
483
|
-
direct_emissions_score=(
|
|
484
|
-
scale * characterized_biosphere[:, producer_index]
|
|
485
|
-
).sum(),
|
|
491
|
+
direct_emissions_score=(scale * characterized_biosphere[:, producer_index]).sum(),
|
|
486
492
|
)
|
|
487
493
|
edges.append(
|
|
488
494
|
Edge(
|
|
@@ -496,13 +502,22 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
496
502
|
)
|
|
497
503
|
|
|
498
504
|
if separate_biosphere_flows:
|
|
499
|
-
cls.add_biosphere_flows(
|
|
505
|
+
flow_score = cls.add_biosphere_flows(
|
|
500
506
|
flows=flows,
|
|
501
507
|
matrix=(scale * characterized_biosphere[:, producer_index]).tocoo(),
|
|
502
508
|
lca=lca,
|
|
503
509
|
node=producing_node,
|
|
504
510
|
biosphere_cutoff_score=biosphere_cutoff_score,
|
|
505
511
|
)
|
|
512
|
+
else:
|
|
513
|
+
flow_score = 0
|
|
514
|
+
|
|
515
|
+
producing_node.direct_emissions_score_outside_specific_flows = (
|
|
516
|
+
producing_node.direct_emissions_score - flow_score
|
|
517
|
+
)
|
|
518
|
+
producing_node.remaining_cumulative_score_outside_specific_flows = (
|
|
519
|
+
producing_node.cumulative_score - flow_score
|
|
520
|
+
)
|
|
506
521
|
|
|
507
522
|
heappush(heap, (abs(1 / cumulative_score), producing_node))
|
|
508
523
|
nodes[producing_node.unique_id] = producing_node
|
|
@@ -529,9 +544,7 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
529
544
|
return lca.characterization_matrix * lca.biosphere_matrix
|
|
530
545
|
|
|
531
546
|
@classmethod
|
|
532
|
-
def get_production_exchanges(
|
|
533
|
-
cls, mapped_matrix: mu.MappedMatrix
|
|
534
|
-
) -> (np.array, np.array):
|
|
547
|
+
def get_production_exchanges(cls, mapped_matrix: mu.MappedMatrix) -> (np.array, np.array):
|
|
535
548
|
"""
|
|
536
549
|
Get matrix row and column indices of productions exchanges by trying a
|
|
537
550
|
series of heuristics. See documentation for
|
|
@@ -563,7 +576,7 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
563
576
|
lca: LCA,
|
|
564
577
|
node: Node,
|
|
565
578
|
biosphere_cutoff_score: float,
|
|
566
|
-
) ->
|
|
579
|
+
) -> float:
|
|
567
580
|
"""
|
|
568
581
|
Add individual biosphere flows as `Flow` instances to `flow` if their score is above `biosphere_cutoff_score`.
|
|
569
582
|
|
|
@@ -582,12 +595,13 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
582
595
|
|
|
583
596
|
Returns
|
|
584
597
|
-------
|
|
585
|
-
`
|
|
586
|
-
Modifies `flows` by adding new `Flow` instances
|
|
598
|
+
The total LCIA score broken out to separate `Flow` instances
|
|
587
599
|
|
|
588
600
|
"""
|
|
601
|
+
added_score = 0.0
|
|
589
602
|
for index, score in zip(matrix.row, matrix.data):
|
|
590
603
|
if abs(score) > biosphere_cutoff_score:
|
|
604
|
+
added_score += score
|
|
591
605
|
flows.append(
|
|
592
606
|
Flow(
|
|
593
607
|
flow_datapackage_id=lca.dicts.biosphere.reversed[index],
|
|
@@ -596,12 +610,12 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
596
610
|
activity_id=node.activity_datapackage_id,
|
|
597
611
|
activity_index=node.activity_index,
|
|
598
612
|
amount=(
|
|
599
|
-
lca.biosphere_matrix[index, node.activity_index]
|
|
600
|
-
* node.supply_amount
|
|
613
|
+
lca.biosphere_matrix[index, node.activity_index] * node.supply_amount
|
|
601
614
|
),
|
|
602
615
|
score=score,
|
|
603
616
|
)
|
|
604
617
|
)
|
|
618
|
+
return added_score
|
|
605
619
|
|
|
606
620
|
@classmethod
|
|
607
621
|
def get_demand_vector_for_activity(
|
|
@@ -648,9 +662,7 @@ class NewNodeEachVisitGraphTraversal:
|
|
|
648
662
|
|
|
649
663
|
class AssumedDiagonalGraphTraversal(NewNodeEachVisitGraphTraversal):
|
|
650
664
|
@classmethod
|
|
651
|
-
def get_production_exchanges(
|
|
652
|
-
cls, mapped_matrix: mu.MappedMatrix
|
|
653
|
-
) -> (np.ndarray, np.ndarray):
|
|
665
|
+
def get_production_exchanges(cls, mapped_matrix: mu.MappedMatrix) -> (np.ndarray, np.ndarray):
|
|
654
666
|
"""
|
|
655
667
|
Assume production exchanges are always on the diagonal instead of
|
|
656
668
|
examining matrix structure and input data.
|
|
@@ -60,10 +60,7 @@ def path_as_brightway_objects(
|
|
|
60
60
|
(
|
|
61
61
|
bd.get_node(id=lca.dicts.product.reversed[x]),
|
|
62
62
|
bd.get_node(id=lca.dicts.activity.reversed[y]),
|
|
63
|
-
-1
|
|
64
|
-
* lca.technosphere_matrix[
|
|
65
|
-
y, x
|
|
66
|
-
], # Flip x and y because y is input to activity x
|
|
63
|
+
-1 * lca.technosphere_matrix[y, x], # Flip x and y because y is input to activity x
|
|
67
64
|
)
|
|
68
65
|
for x, y in zip(path[:-1], path[1:])
|
|
69
66
|
]
|
|
@@ -67,8 +67,24 @@ def gpe_first_heuristic(mm: mu.MappedMatrix) -> Tuple[np.ndarray, np.ndarray]:
|
|
|
67
67
|
# Need to check the original input values, not after mapping when they are
|
|
68
68
|
# normalized to [0, X] range.
|
|
69
69
|
ident_mask = indices["row"] == indices["col"]
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
|
|
71
|
+
# In theory these values should be on the diagonal, as the input row and col
|
|
72
|
+
# values are the same. However, we don't have a strong guarantee that this is
|
|
73
|
+
# true. So to eliminate duplicate values we combine to a (X, 2) array, and get
|
|
74
|
+
# unique rows.
|
|
75
|
+
# Note that `unique` also does sorting, see
|
|
76
|
+
# https://stackoverflow.com/questions/16970982/find-unique-rows-in-numpy-array
|
|
77
|
+
combined = np.unique(
|
|
78
|
+
np.vstack(
|
|
79
|
+
(
|
|
80
|
+
group.row_mapper.map_array(indices["row"][ident_mask]),
|
|
81
|
+
group.col_mapper.map_array(indices["col"][ident_mask]),
|
|
82
|
+
)
|
|
83
|
+
),
|
|
84
|
+
axis=1,
|
|
85
|
+
)
|
|
86
|
+
row_mapped = combined[0, :]
|
|
87
|
+
col_mapped = combined[1, :]
|
|
72
88
|
|
|
73
89
|
if (row_mapped == -1).sum() or (col_mapped == -1).sum():
|
|
74
90
|
ERROR = """
|
|
@@ -116,9 +132,7 @@ def gpe_second_heuristic(
|
|
|
116
132
|
existing_mask = np.in1d(h2_col_indices, col_existing)
|
|
117
133
|
|
|
118
134
|
# Need mask with correct shape for both row and col indices
|
|
119
|
-
mask = np.in1d(
|
|
120
|
-
group.col_masked[~group.flip], h2_col_indices[~existing_mask]
|
|
121
|
-
)
|
|
135
|
+
mask = np.in1d(group.col_masked[~group.flip], h2_col_indices[~existing_mask])
|
|
122
136
|
|
|
123
137
|
not_flipped.append(
|
|
124
138
|
(
|
|
@@ -184,9 +198,7 @@ def guess_production_exchanges(mm: mu.MappedMatrix) -> Tuple[np.ndarray, np.ndar
|
|
|
184
198
|
# Every column must have an activity with some reference product or the system
|
|
185
199
|
# is not solvable. Therefore we can look across all columns. We will do
|
|
186
200
|
# all the work in matrix indices.
|
|
187
|
-
missing = np.setdiff1d(
|
|
188
|
-
np.arange(mm.matrix.shape[0]), col_indices, assume_unique=True
|
|
189
|
-
)
|
|
201
|
+
missing = np.setdiff1d(np.arange(mm.matrix.shape[0]), col_indices, assume_unique=True)
|
|
190
202
|
|
|
191
203
|
# Short circuit other steps if possible; assumption is that this step will
|
|
192
204
|
# be taken for most matrices
|
|
@@ -200,9 +212,7 @@ def guess_production_exchanges(mm: mu.MappedMatrix) -> Tuple[np.ndarray, np.ndar
|
|
|
200
212
|
if row_indices.shape != col_indices.shape:
|
|
201
213
|
raise ValueError("Guessed row indices do not match guessed column indices.")
|
|
202
214
|
|
|
203
|
-
missing = np.setdiff1d(
|
|
204
|
-
np.arange(mm.matrix.shape[0]), col_indices, assume_unique=True
|
|
205
|
-
)
|
|
215
|
+
missing = np.setdiff1d(np.arange(mm.matrix.shape[0]), col_indices, assume_unique=True)
|
|
206
216
|
if missing.size:
|
|
207
217
|
raise UnclearProductionExchange(
|
|
208
218
|
"Can't find production exchanges for columns: {}".format(list(missing))
|
|
@@ -36,16 +36,23 @@ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISE
|
|
|
36
36
|
OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
37
37
|
|
|
38
38
|
"""
|
|
39
|
+
|
|
39
40
|
from functools import partial
|
|
40
41
|
from multiprocessing import Pool
|
|
41
|
-
from typing import Optional, Union
|
|
42
|
+
from typing import Iterable, Optional, Union
|
|
42
43
|
|
|
43
44
|
import numpy as np
|
|
44
45
|
from scipy import sparse
|
|
45
46
|
|
|
46
47
|
|
|
47
|
-
def get_distances(
|
|
48
|
-
|
|
48
|
+
def get_distances(
|
|
49
|
+
adjacency: sparse.csr_matrix,
|
|
50
|
+
sources: Optional[Union[int, Iterable]] = None,
|
|
51
|
+
method: str = "D",
|
|
52
|
+
return_predecessors: bool = False,
|
|
53
|
+
unweighted: bool = False,
|
|
54
|
+
n_jobs: Optional[int] = None,
|
|
55
|
+
):
|
|
49
56
|
"""Compute distances between nodes.
|
|
50
57
|
|
|
51
58
|
* Graphs
|
|
@@ -88,28 +95,40 @@ def get_distances(adjacency: sparse.csr_matrix, sources: Optional[Union[int, Ite
|
|
|
88
95
|
|
|
89
96
|
"""
|
|
90
97
|
n_jobs, directed = 1, True
|
|
91
|
-
if method ==
|
|
92
|
-
raise ValueError(
|
|
98
|
+
if method == "FW" and n_jobs != 1:
|
|
99
|
+
raise ValueError("The Floyd-Warshall algorithm cannot be used with parallel computations.")
|
|
93
100
|
if sources is None:
|
|
94
101
|
sources = np.arange(adjacency.shape[0])
|
|
95
102
|
elif np.issubdtype(type(sources), np.integer):
|
|
96
103
|
sources = np.array([sources])
|
|
97
104
|
n = len(sources)
|
|
98
|
-
local_function = partial(
|
|
99
|
-
|
|
105
|
+
local_function = partial(
|
|
106
|
+
sparse.csgraph.shortest_path,
|
|
107
|
+
adjacency,
|
|
108
|
+
method,
|
|
109
|
+
directed,
|
|
110
|
+
return_predecessors,
|
|
111
|
+
unweighted,
|
|
112
|
+
False,
|
|
113
|
+
)
|
|
100
114
|
if n_jobs == 1 or n == 1:
|
|
101
115
|
try:
|
|
102
|
-
res = sparse.csgraph.shortest_path(
|
|
103
|
-
|
|
116
|
+
res = sparse.csgraph.shortest_path(
|
|
117
|
+
adjacency, method, directed, return_predecessors, unweighted, False, sources
|
|
118
|
+
)
|
|
104
119
|
except sparse.csgraph.NegativeCycleError:
|
|
105
|
-
raise ValueError(
|
|
120
|
+
raise ValueError(
|
|
121
|
+
"The shortest path computation could not be completed because a negative cycle is present."
|
|
122
|
+
)
|
|
106
123
|
else:
|
|
107
124
|
try:
|
|
108
125
|
with Pool(n_jobs) as pool:
|
|
109
126
|
res = np.array(pool.map(local_function, sources))
|
|
110
127
|
except sparse.csgraph.NegativeCycleError:
|
|
111
128
|
pool.terminate()
|
|
112
|
-
raise ValueError(
|
|
129
|
+
raise ValueError(
|
|
130
|
+
"The shortest path computation could not be completed because a negative cycle is present."
|
|
131
|
+
)
|
|
113
132
|
if return_predecessors:
|
|
114
133
|
res[1][res[1] < 0] = -1
|
|
115
134
|
if n == 1:
|
|
@@ -123,8 +142,14 @@ def get_distances(adjacency: sparse.csr_matrix, sources: Optional[Union[int, Ite
|
|
|
123
142
|
return res
|
|
124
143
|
|
|
125
144
|
|
|
126
|
-
def get_shortest_path(
|
|
127
|
-
|
|
145
|
+
def get_shortest_path(
|
|
146
|
+
adjacency: sparse.csr_matrix,
|
|
147
|
+
sources: Union[int, Iterable],
|
|
148
|
+
targets: Union[int, Iterable],
|
|
149
|
+
method: str = "D",
|
|
150
|
+
unweighted: bool = False,
|
|
151
|
+
n_jobs: Optional[int] = None,
|
|
152
|
+
):
|
|
128
153
|
"""Compute the shortest paths in the graph.
|
|
129
154
|
|
|
130
155
|
Parameters
|
|
@@ -181,7 +206,8 @@ def get_shortest_path(adjacency: sparse.csr_matrix, sources: Union[int, Iterable
|
|
|
181
206
|
targets = sources
|
|
182
207
|
else:
|
|
183
208
|
raise ValueError(
|
|
184
|
-
|
|
209
|
+
"This request is ambiguous. Either use one source and multiple targets or multiple sources and one target."
|
|
210
|
+
)
|
|
185
211
|
|
|
186
212
|
if source2target:
|
|
187
213
|
dists, preds = get_distances(adjacency, source, method, True, unweighted, n_jobs)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from numbers import Number
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
2
4
|
import numpy as np
|
|
3
5
|
|
|
4
|
-
from . import
|
|
6
|
+
from . import Edge, Flow, Node
|
|
5
7
|
|
|
6
8
|
|
|
7
|
-
def equal_dict(a: Node
|
|
9
|
+
def equal_dict(a: Union[Node, Edge, Flow], b: dict, fields: list[str]):
|
|
8
10
|
for field in fields:
|
|
9
11
|
if field in b:
|
|
10
12
|
if isinstance(b[field], Number):
|
|
@@ -1,43 +1,44 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
|
-
Name:
|
|
3
|
-
Version: 0.
|
|
2
|
+
Name: bw_graph_tools
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: Graph traversal class and utilities
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Author-email: <cmutel@gmail.com>
|
|
8
|
-
Maintainer: Chris Mutel
|
|
9
|
-
Maintainer-email: <cmutel@gmail.com>
|
|
10
|
-
License: BSD-3-Clause
|
|
5
|
+
Author-email: Chris Mutel <cmutel@gmail.com>
|
|
6
|
+
Maintainer-email: Chris Mutel <cmutel@gmail.com>
|
|
11
7
|
Project-URL: source, https://github.com/brightway-lca/bw_graph_tools
|
|
12
8
|
Project-URL: homepage, https://github.com/brightway-lca/bw_graph_tools
|
|
13
9
|
Project-URL: tracker, https://github.com/brightway-lca/bw_graph_tools/issues
|
|
14
|
-
|
|
15
|
-
Platform: any
|
|
16
|
-
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
17
11
|
Classifier: Programming Language :: Python
|
|
18
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
23
22
|
License-File: LICENSE
|
|
24
23
|
Requires-Dist: bw2calc>=2.0.dev13
|
|
24
|
+
Requires-Dist: bw_processing
|
|
25
25
|
Requires-Dist: matrix_utils
|
|
26
|
-
Requires-Dist: numpy
|
|
26
|
+
Requires-Dist: numpy<2
|
|
27
27
|
Requires-Dist: scipy
|
|
28
|
-
Requires-Dist: bw_processing
|
|
29
28
|
Provides-Extra: testing
|
|
30
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: bw_graph_tools; extra == "testing"
|
|
30
|
+
Requires-Dist: bw2data; extra == "testing"
|
|
31
31
|
Requires-Dist: pytest; extra == "testing"
|
|
32
32
|
Requires-Dist: pytest-cov; extra == "testing"
|
|
33
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: python-coveralls; extra == "testing"
|
|
34
34
|
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: build; extra == "dev"
|
|
35
36
|
Requires-Dist: pre-commit; extra == "dev"
|
|
36
37
|
Requires-Dist: pylint; extra == "dev"
|
|
37
|
-
|
|
38
|
-
Requires-Dist:
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
Requires-Dist:
|
|
38
|
+
Requires-Dist: pytest; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-randomly; extra == "dev"
|
|
41
|
+
Requires-Dist: setuptools; extra == "dev"
|
|
41
42
|
|
|
42
43
|
# bw_graph_tools
|
|
43
44
|
|
|
@@ -85,7 +86,6 @@ Normally we construct matrices and solve the resulting set of linear equations t
|
|
|
85
86
|
If we add temporal information using `bw_temporalis`, then the same node can occur at different times depending on how the temporal dynamics its preceding path. For example:
|
|
86
87
|
|
|
87
88
|
|
|
88
|
-
|
|
89
89
|
## Contributing
|
|
90
90
|
|
|
91
91
|
Contributions are very welcome.
|
|
@@ -103,15 +103,9 @@ please [file an issue] along with a detailed description.
|
|
|
103
103
|
|
|
104
104
|
## Documentation
|
|
105
105
|
|
|
106
|
-
1. Install the
|
|
107
|
-
2. Build the documentation locally by running
|
|
106
|
+
1. Install the conda environment from the file `.docs/environment.yml`
|
|
107
|
+
2. Build the documentation locally by running:
|
|
108
108
|
|
|
109
|
-
```
|
|
109
|
+
```bash
|
|
110
110
|
sphinx-autobuild docs _build/html -a -j auto --open-browser
|
|
111
111
|
```
|
|
112
|
-
|
|
113
|
-
<!-- github-only -->
|
|
114
|
-
|
|
115
|
-
[command-line reference]: https://bw_graph_tools.readthedocs.io/en/latest/usage.html
|
|
116
|
-
[license]: https://github.com/brightway-lca/bw_graph_tools/blob/main/LICENSE
|
|
117
|
-
[contributor guide]: https://github.com/brightway-lca/bw_graph_tools/blob/main/CONTRIBUTING.md
|