Skip to content

Commit

Permalink
Fix #108
Browse files Browse the repository at this point in the history
  • Loading branch information
cmutel committed Sep 13, 2024
1 parent c11a70b commit 1213fc7
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 20 deletions.
11 changes: 7 additions & 4 deletions bw2calc/multi_lca.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .errors import OutsideTechnosphere
from .lca import LCABase
from .method_config import MethodConfig
from .restricted_sparse_matrix_dict import RestrictedSparseMatrixDict
from .single_value_diagonal_matrix import SingleValueDiagonalMatrix
from .utils import consistent_global_index, get_datapackage, utc_now

Expand Down Expand Up @@ -300,8 +301,9 @@ def load_normalization_data(
if len(value.matrix.data) == 0:
warnings.warn(f"All values in normalization matrix for {key} are zero")

self.normalization_matrices = mu.SparseMatrixDict(
[(key, value.matrix) for key, value in self.normalization_mm_dict.items()]
self.normalization_matrices = RestrictedSparseMatrixDict(
self.config["normalizations"],
[(key, value.matrix) for key, value in self.normalization_mm_dict.items()],
)

def load_weighting_data(
Expand Down Expand Up @@ -330,8 +332,9 @@ def load_weighting_data(
if len(value.matrix.data) == 0:
warnings.warn(f"All values in weighting matrix for {key} are zero")

self.weighting_matrices = mu.SparseMatrixDict(
[(key, value.matrix) for key, value in self.weighting_mm_dict.items()]
self.weighting_matrices = RestrictedSparseMatrixDict(
self.config["weightings"],
[(key, value.matrix) for key, value in self.weighting_mm_dict.items()],
)

################
Expand Down
35 changes: 35 additions & 0 deletions bw2calc/restricted_sparse_matrix_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import Any

from matrix_utils import SparseMatrixDict
from pydantic import BaseModel


class RestrictionsValidator(BaseModel):
restrictions: dict[tuple[str, ...], list[tuple[str, ...]]]


class RestrictedSparseMatrixDict(SparseMatrixDict):
def __init__(self, restrictions: dict, *args, **kwargs):
"""Like SparseMatrixDict, but follows `restrictions` on what can be multiplied.
Only for use with normalization and weighting."""
super().__init__(*args, **kwargs)
RestrictionsValidator(restrictions=restrictions)
self._restrictions = restrictions

def __matmul__(self, other: Any) -> SparseMatrixDict:
"""Define logic for `@` matrix multiplication operator.
Note that the sparse matrix dict must come first, i.e. `self @ other`.
"""
if isinstance(other, (SparseMatrixDict, RestrictedSparseMatrixDict)):
return SparseMatrixDict(
{
(a, *b): c @ d
for a, c in self.items()
for b, d in other.items()
if b[0] in self._restrictions[a]
}
)
else:
return super().__matmul__(other)
17 changes: 17 additions & 0 deletions tests/fixtures/create_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,15 @@ def create_multilca_simple():
distributions_array=distributions_array,
global_index=0,
)
dp6.add_persistent_vector(
matrix="normalization_matrix",
data_array=np.ones(2).astype(float),
name="normalization-2",
identifier=("n", "2"),
indices_array=indices_array,
distributions_array=distributions_array,
global_index=0,
)

dp7 = create_datapackage(
fs=ZipFileSystem(fixture_dir / "multi_lca_simple_weighting.zip", mode="w"),
Expand All @@ -536,6 +545,14 @@ def create_multilca_simple():
indices_array=indices_array,
distributions_array=distributions_array,
)
dp7.add_persistent_vector(
matrix="weighting_matrix",
data_array=np.array([84]),
name="weighting-2",
identifier=("w", "2"),
indices_array=indices_array,
distributions_array=distributions_array,
)

dp1.finalize_serialization()
dp2.finalize_serialization()
Expand Down
Binary file modified tests/fixtures/multi_lca_simple_1.zip
Binary file not shown.
Binary file modified tests/fixtures/multi_lca_simple_2.zip
Binary file not shown.
Binary file modified tests/fixtures/multi_lca_simple_3.zip
Binary file not shown.
Binary file modified tests/fixtures/multi_lca_simple_4.zip
Binary file not shown.
Binary file modified tests/fixtures/multi_lca_simple_5.zip
Binary file not shown.
Binary file modified tests/fixtures/multi_lca_simple_normalization.zip
Binary file not shown.
Binary file modified tests/fixtures/multi_lca_simple_weighting.zip
Binary file not shown.
64 changes: 48 additions & 16 deletions tests/multi_lca.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,6 @@ def test_inventory_matrix_construction(dps, config, func_units):
mlca.lci()
mlca.lcia()

print(mlca.scores)
print(mlca.technosphere_matrix.todense())
print(mlca.biosphere_matrix.todense())

for name, mat in mlca.characterization_matrices.items():
print(name)
print(mat.todense())

for name, arr in mlca.supply_arrays.items():
print(name)
print(arr)

tm = [
(100, 1, -0.2),
(100, 2, -0.5),
Expand Down Expand Up @@ -208,10 +196,6 @@ def test_normalization_with_weighting(dps, func_units):
rows = np.zeros(mlca.biosphere_matrix.shape[0])
rows = np.array([mlca.dicts.biosphere[201], mlca.dicts.biosphere[203]])

for k, v in mlca.weighting_matrices.items():
print(k)
print(v.todense())

for key, mat in mlca.weighted_inventories.items():
expected = mlca.characterized_inventories[key[2:]][rows, :].sum()
assert np.allclose(mat.sum(), expected * 42)
Expand Down Expand Up @@ -542,3 +526,51 @@ def test_monte_carlo_multiple_iterations_selective_use_in_list_comprehension(dps

for key, lst in aggregated.items():
assert np.unique(lst).shape == (10,)


def test_bug_108(dps, config, func_units):
# https://github.com/brightway-lca/brightway2-calc/issues/108
config = {
"impact_categories": [
("first", "category"),
("second", "category"),
],
"normalizations": {
("n", "1"): [("first", "category")],
("n", "2"): [("second", "category")],
},
"weightings": {
("w", "1"): [("n", "1")],
("w", "2"): [("n", "2")],
},
}

dps.append(
get_datapackage(fixture_dir / "multi_lca_simple_normalization.zip"),
)
dps.append(
get_datapackage(fixture_dir / "multi_lca_simple_weighting.zip"),
)

mlca = MultiLCA(demands=func_units, method_config=config, data_objs=dps)
mlca.lci()
mlca.lcia()
mlca.normalize()
mlca.weight()

assert len(mlca.scores) == len(func_units) * 2
assert sorted(mlca.scores) == sorted(
[
(("w", "1"), ("n", "1"), ("first", "category"), "γ"),
(("w", "1"), ("n", "1"), ("first", "category"), "ε"),
(("w", "1"), ("n", "1"), ("first", "category"), "ζ"),
(("w", "2"), ("n", "2"), ("second", "category"), "γ"),
(("w", "2"), ("n", "2"), ("second", "category"), "ε"),
(("w", "2"), ("n", "2"), ("second", "category"), "ζ"),
]
)
assert (
mlca.scores[(("w", "2"), ("n", "2"), ("second", "category"), "ζ")]
== 3 * (3 * 10 + 1 * 10) * 84
)
assert mlca.scores[(("w", "1"), ("n", "1"), ("first", "category"), "γ")] == 3 * 42
33 changes: 33 additions & 0 deletions tests/test_restricted_sparse_matrix_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest
from matrix_utils import SparseMatrixDict
from pydantic import ValidationError

from bw2calc.restricted_sparse_matrix_dict import RestrictedSparseMatrixDict, RestrictionsValidator


class Dummy:
def __init__(self, a):
self.a = a

def __matmul__(self, other):
return self.a + other


def test_restricted_sparse_matrix_dict():
smd = SparseMatrixDict({(("one",), "foo"): 1, (("two",), "bar"): 2})
rsmd = RestrictedSparseMatrixDict(
{("seven",): [("one",)], ("eight",): [("two",)]},
{("seven",): Dummy(7), ("eight",): Dummy(8)},
)

result = rsmd @ smd
assert isinstance(result, SparseMatrixDict)
assert len(result) == 2
assert result[(("seven",), ("one",), "foo")] == 8
assert result[(("eight",), ("two",), "bar")] == 10


def test_restrictions_validator():
assert RestrictionsValidator(restrictions={("seven",): [("one",)], ("eight",): [("two",)]})
with pytest.raises(ValidationError):
RestrictionsValidator(restrictions={"seven": [("one",)], ("eight",): [("two",)]})

0 comments on commit 1213fc7

Please sign in to comment.