matrix-utils 0.6.2__tar.gz → 0.7.0__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 (70) hide show
  1. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/CHANGELOG.md +8 -0
  2. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/PKG-INFO +1 -1
  3. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils/__init__.py +1 -1
  4. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils/array_mapper.py +15 -3
  5. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils/mapped_matrix.py +29 -13
  6. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils/resource_group.py +23 -3
  7. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils.egg-info/PKG-INFO +1 -1
  8. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/array_mapper.py +25 -0
  9. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/mapped_matrix.py +26 -0
  10. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/.gitignore +0 -0
  11. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/.pre-commit-config.yaml +0 -0
  12. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/CODE_OF_CONDUCT.md +0 -0
  13. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/LICENSE +0 -0
  14. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/MANIFEST.in +0 -0
  15. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/README.md +0 -0
  16. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/binder/Brightway 2.5 demonstration.ipynb +0 -0
  17. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/binder/environment.yml +0 -0
  18. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/Brightway 2.5 - Linking with IAMs.ipynb +0 -0
  19. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/Brightway 2.5 demonstration.ipynb +0 -0
  20. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/Graph traversal with modification.ipynb +0 -0
  21. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/Indexing speed tests.ipynb +0 -0
  22. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/Interfaces which produce results for multiple matrices.ipynb +0 -0
  23. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/Mapping speed tests.ipynb +0 -0
  24. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/Multifunctionality with Brightway 2.5.ipynb +0 -0
  25. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/Real-time modification of hybrid databases.ipynb +0 -0
  26. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/Supply chain modification.ipynb +0 -0
  27. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/Untitled.ipynb +0 -0
  28. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/profiling/bw2.prof +0 -0
  29. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/profiling/bw2.svg +0 -0
  30. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/profiling/bw25.prof +0 -0
  31. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/profiling/bw25.svg +0 -0
  32. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/profiling/bw25_profile.txt +0 -0
  33. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/profiling/bw25_profiling.py +0 -0
  34. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/profiling/bw2_profile.txt +0 -0
  35. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/profiling/bw2_profiling.py +0 -0
  36. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/profiling/profiling-versus-bw2.py +0 -0
  37. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/speed_test_unique_indices.py +0 -0
  38. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/dev/speed_tests.py +0 -0
  39. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/docs/Makefile +0 -0
  40. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/docs/conf.py +0 -0
  41. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/docs/index.rst +0 -0
  42. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/docs/loading.rst +0 -0
  43. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/docs/make.bat +0 -0
  44. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/docs/saving.rst +0 -0
  45. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/docs/utilities.rst +0 -0
  46. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils/aggregation.py +0 -0
  47. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils/errors.py +0 -0
  48. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils/indexers.py +0 -0
  49. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils/mapped_matrix_dict.py +0 -0
  50. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils/utils.py +0 -0
  51. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils.egg-info/SOURCES.txt +0 -0
  52. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils.egg-info/dependency_links.txt +0 -0
  53. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils.egg-info/requires.txt +0 -0
  54. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/matrix_utils.egg-info/top_level.txt +0 -0
  55. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/pyproject.toml +0 -0
  56. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/setup.cfg +0 -0
  57. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/fixtures/__init__.py +0 -0
  58. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/fixtures/a-first.zip +0 -0
  59. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/fixtures/b-second.zip +0 -0
  60. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/fixtures/basic.py +0 -0
  61. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/fixtures/create_static_fixtures.py +0 -0
  62. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/fixtures/sa-1.zip +0 -0
  63. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/fixtures/sa-2.zip +0 -0
  64. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/indexers.py +0 -0
  65. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/integration.py +0 -0
  66. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/interfaces.py +0 -0
  67. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/masking.py +0 -0
  68. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/monte_carlo.py +0 -0
  69. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/ordering.py +0 -0
  70. {matrix_utils-0.6.2 → matrix_utils-0.7.0}/tests/utils.py +0 -0
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ### [0.7.0] - 2026-02-27
4
+
5
+ * Merge [#33](https://github.com/brightway-lca/matrix_utils/pull/33): Speed up large-index mapping by avoiding repeated unique and using fast integer dedupicator. Thanks @romainsacchi!
6
+
7
+ ### [0.6.3] - 2026-02-23
8
+
9
+ * Fix [#32](https://github.com/brightway-lca/matrix_utils/issues/32): Replace deprecated `.A1` property on scipy sparse matrices
10
+
3
11
  ### [0.6.2] - 2025-08-06
4
12
 
5
13
  * Fix [#27](https://github.com/brightway-lca/matrix_utils/issues/27): Release `0.6.1` broke Python `3.9` compatibility
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrix_utils
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Tools to create matrices from data packages
5
5
  Author-email: Chris Mutel <cmutel@gmail.com>
6
6
  Maintainer-email: Chris Mutel <cmutel@gmail.com>
@@ -11,7 +11,7 @@ __all__ = (
11
11
  "SparseMatrixDict",
12
12
  )
13
13
 
14
- __version__ = "0.6.2"
14
+ __version__ = "0.7.0"
15
15
 
16
16
  from matrix_utils.array_mapper import ArrayMapper
17
17
  from matrix_utils.indexers import CombinatorialIndexer, Proxy, RandomIndexer, SequentialIndexer
@@ -1,4 +1,5 @@
1
1
  import numpy as np
2
+ import pandas as pd
2
3
  from scipy import sparse
3
4
 
4
5
  from matrix_utils.errors import EmptyArray
@@ -25,10 +26,21 @@ class ArrayMapper:
25
26
  """ # NOQA: E501
26
27
 
27
28
  def __init__(self, *, array: np.ndarray, sparse_cutoff: int = 50000, empty_ok: bool = False):
29
+
30
+
28
31
  self._check_input_array(array)
29
32
 
30
- # Even if already unique, this only adds ~2ms for 100.000 elements
31
- self.array = np.unique(array)
33
+ # `np.unique` can be very slow on large, unsorted integer arrays.
34
+ # Use hash-based `pandas.unique` first, then sort only the unique values.
35
+ # Fall back to NumPy if anything unexpected occurs.
36
+ if array.dtype.kind in {"i", "u"} and array.shape[0] > 100_000:
37
+ try:
38
+ self.array = np.sort(pd.unique(array))
39
+ except (TypeError, ValueError):
40
+ self.array = np.unique(array)
41
+ else:
42
+ self.array = np.unique(array)
43
+
32
44
  self.empty_ok = empty_ok
33
45
 
34
46
  if self.array.shape == (0,):
@@ -75,7 +87,7 @@ class ArrayMapper:
75
87
  result = np.zeros_like(array) - 1
76
88
  mask = array <= self.max_value
77
89
  # https://numpy.org/doc/stable/reference/generated/numpy.matrix.A1.html
78
- result[mask] = (self.matrix[array[mask], np.zeros_like(array[mask])]).A1 - 1
90
+ result[mask] = np.asarray(self.matrix[array[mask], np.zeros_like(array[mask])]).ravel() - 1
79
91
  return result
80
92
 
81
93
  def to_dict(self) -> dict:
@@ -115,20 +115,36 @@ class MappedMatrix:
115
115
  self.add_indexers(indexer_override, seed_override)
116
116
 
117
117
  try:
118
- self.row_mapper = row_mapper or ArrayMapper(
119
- array=safe_concatenate_indices(
120
- [obj.unique_row_indices_for_mapping() for obj in self.groups], empty_ok
121
- ),
122
- empty_ok=empty_ok,
123
- )
124
- if not diagonal:
125
- self.col_mapper = col_mapper or ArrayMapper(
126
- array=safe_concatenate_indices(
127
- [obj.unique_col_indices_for_mapping() for obj in self.groups],
128
- empty_ok,
129
- ),
118
+ if row_mapper is None:
119
+ row_arrays = []
120
+ for obj in self.groups:
121
+ arr = obj.row_indices_for_mapping()
122
+ row_arrays.append(arr)
123
+
124
+ row_values = safe_concatenate_indices(row_arrays, empty_ok)
125
+
126
+ self.row_mapper = ArrayMapper(
127
+ array=row_values,
130
128
  empty_ok=empty_ok,
131
129
  )
130
+ else:
131
+ self.row_mapper = row_mapper
132
+
133
+ if not diagonal:
134
+ if col_mapper is None:
135
+ col_arrays = []
136
+ for obj in self.groups:
137
+ arr = obj.col_indices_for_mapping()
138
+ col_arrays.append(arr)
139
+
140
+ col_values = safe_concatenate_indices(col_arrays, empty_ok)
141
+
142
+ self.col_mapper = ArrayMapper(
143
+ array=col_values,
144
+ empty_ok=empty_ok,
145
+ )
146
+ else:
147
+ self.col_mapper = col_mapper
132
148
  except AllArraysEmpty:
133
149
  handle_all_arrays_empty(
134
150
  packages=self.packages, matrix_label=self.matrix_label, identifier=identifier
@@ -234,7 +250,7 @@ class MappedMatrix:
234
250
  obj.add_indexer(indexer=package.indexer)
235
251
  else:
236
252
  package.indexer = RandomIndexer(
237
- seed=seed_override or package.metadata["seed"],
253
+ seed=seed_override if seed_override is not None else package.metadata["seed"],
238
254
  max_value=package.get_max_index_value(),
239
255
  )
240
256
  for obj in resources:
@@ -69,7 +69,9 @@ class ResourceGroup:
69
69
  self.custom_filter = custom_filter
70
70
  self.transpose = transpose
71
71
  self.vector = self.is_vector()
72
- self.seed = seed_override or self.package.metadata.get("seed")
72
+ self.seed = (
73
+ seed_override if seed_override is not None else self.package.metadata.get("seed")
74
+ )
73
75
 
74
76
  if custom_filter is not None:
75
77
  self.custom_filter_mask = custom_filter(self.get_indices_data())
@@ -218,11 +220,29 @@ class ResourceGroup:
218
220
 
219
221
  def unique_row_indices_for_mapping(self):
220
222
  """Return array of unique indices that respect aggregation policy"""
221
- return np.unique(mask_array(self.get_indices_data()["row"], self.custom_filter_mask))
223
+ data = mask_array(self.get_indices_data()["row"], self.custom_filter_mask)
224
+ # Structured-array field views can be non-contiguous; materialize contiguous memory first
225
+ # to avoid pathological performance in `np.unique`.
226
+ return np.unique(np.ascontiguousarray(data))
222
227
 
223
228
  def unique_col_indices_for_mapping(self):
224
229
  """Return array of unique indices that respect aggregation policy"""
225
- return np.unique(mask_array(self.get_indices_data()["col"], self.custom_filter_mask))
230
+ data = mask_array(self.get_indices_data()["col"], self.custom_filter_mask)
231
+ # Structured-array field views can be non-contiguous; materialize contiguous memory first
232
+ # to avoid pathological performance in `np.unique`.
233
+ return np.unique(np.ascontiguousarray(data))
234
+
235
+ def row_indices_for_mapping(self):
236
+ """Return raw row indices for mapping, with custom filter applied if present."""
237
+ data = mask_array(self.get_indices_data()["row"], self.custom_filter_mask)
238
+ # Force contiguous memory to improve downstream sorting/uniquing performance.
239
+ return np.ascontiguousarray(data)
240
+
241
+ def col_indices_for_mapping(self):
242
+ """Return raw column indices for mapping, with custom filter applied if present."""
243
+ data = mask_array(self.get_indices_data()["col"], self.custom_filter_mask)
244
+ # Force contiguous memory to improve downstream sorting/uniquing performance.
245
+ return np.ascontiguousarray(data)
226
246
 
227
247
  def add_indexer(self, indexer: Indexer):
228
248
  self.indexer = indexer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrix_utils
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Tools to create matrices from data packages
5
5
  Author-email: Chris Mutel <cmutel@gmail.com>
6
6
  Maintainer-email: Chris Mutel <cmutel@gmail.com>
@@ -1,6 +1,7 @@
1
1
  import numpy as np
2
2
  import pytest
3
3
 
4
+ import matrix_utils.array_mapper as array_mapper_module
4
5
  from matrix_utils.array_mapper import ArrayMapper
5
6
  from matrix_utils.errors import EmptyArray
6
7
 
@@ -78,3 +79,27 @@ def test_empty_array_ok():
78
79
  am = ArrayMapper(array=np.array([], dtype=int), empty_ok=True)
79
80
  assert am.array.shape == (0,)
80
81
  assert am.empty_input
82
+
83
+
84
+ def test_empty_mapper_maps_to_missing_values():
85
+ am = ArrayMapper(array=np.array([], dtype=int), empty_ok=True)
86
+ result = am.map_array(np.array([0, 42], dtype=int))
87
+ assert np.array_equal(result, np.array([-1, -1]))
88
+
89
+
90
+ def test_reverse_dict():
91
+ am = ArrayMapper(array=np.array([10, 20, 10, 30]))
92
+ assert am.reverse_dict() == {0: 10, 1: 20, 2: 30}
93
+
94
+
95
+ def test_large_integer_array_pandas_fallback(monkeypatch):
96
+ # Trigger the large integer optimization branch and force fallback
97
+ inpt = np.tile(np.array([5, 2, 7, 2, 5], dtype=np.int64), 25_000)
98
+
99
+ def raise_type_error(_):
100
+ raise TypeError("boom")
101
+
102
+ monkeypatch.setattr(array_mapper_module.pd, "unique", raise_type_error)
103
+
104
+ am = ArrayMapper(array=inpt)
105
+ assert np.array_equal(am.array, np.array([2, 5, 7]))
@@ -842,3 +842,29 @@ def test_empty_combinatorial_datapackage():
842
842
  )
843
843
  for _ in range(10):
844
844
  next(mm)
845
+
846
+
847
+ def test_input_indexer_vector_raises_on_unsupported_type():
848
+ class BadIndexer:
849
+ index = "not-an-index"
850
+
851
+ def __next__(self):
852
+ return self
853
+
854
+ s = bwp.create_datapackage(sequential=True)
855
+ s.add_persistent_vector(
856
+ matrix="foo",
857
+ data_array=np.arange(3),
858
+ indices_array=np.array([(0, 0), (1, 1), (0, 1)], dtype=bwp.INDICES_DTYPE),
859
+ )
860
+
861
+ mm = MappedMatrix(
862
+ packages=[s],
863
+ matrix="foo",
864
+ use_arrays=False,
865
+ use_distributions=False,
866
+ indexer_override=BadIndexer(),
867
+ )
868
+
869
+ with pytest.raises(ValueError, match="Can't understand indexer value"):
870
+ mm.input_indexer_vector()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes