bw-processing 1.5__tar.gz → 1.6__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 (56) hide show
  1. {bw_processing-1.5/src/bw_processing.egg-info → bw_processing-1.6}/PKG-INFO +34 -7
  2. {bw_processing-1.5 → bw_processing-1.6}/README.md +33 -6
  3. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/__init__.py +1 -1
  4. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/datapackage.py +101 -3
  5. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/matrix_entry.py +18 -0
  6. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/merging.py +1 -1
  7. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/utils.py +8 -1
  8. {bw_processing-1.5 → bw_processing-1.6/src/bw_processing.egg-info}/PKG-INFO +34 -7
  9. {bw_processing-1.5 → bw_processing-1.6}/tests/test_datapackage.py +98 -0
  10. {bw_processing-1.5 → bw_processing-1.6}/tests/test_matrix_entry.py +95 -1
  11. {bw_processing-1.5 → bw_processing-1.6}/tests/test_utils.py +17 -5
  12. {bw_processing-1.5 → bw_processing-1.6}/LICENSE +0 -0
  13. {bw_processing-1.5 → bw_processing-1.6}/MANIFEST.in +0 -0
  14. {bw_processing-1.5 → bw_processing-1.6}/pyproject.toml +0 -0
  15. {bw_processing-1.5 → bw_processing-1.6}/setup.cfg +0 -0
  16. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/array_creation.py +0 -0
  17. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/constants.py +0 -0
  18. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/errors.py +0 -0
  19. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/__init__.py +0 -0
  20. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/datapackage_1/another name.indices.parquet +0 -0
  21. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.data.parquet +0 -0
  22. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.distributions.parquet +0 -0
  23. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.flip.parquet +0 -0
  24. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.indices.parquet +0 -0
  25. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/datapackage_1/some name.data.parquet +0 -0
  26. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/datapackage_1/some name.flip.parquet +0 -0
  27. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/datapackage_1/some name.indices.parquet +0 -0
  28. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/datapackage_2.zip +0 -0
  29. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/interfaces.py +0 -0
  30. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/parquet_files.py +0 -0
  31. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/examples/simple.zip +0 -0
  32. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/filesystem.py +0 -0
  33. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/indexing.py +0 -0
  34. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/io_helpers.py +0 -0
  35. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/io_parquet_helpers.py +0 -0
  36. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/io_pyarrow_helpers.py +0 -0
  37. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/param_labels.py +0 -0
  38. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/proxies.py +0 -0
  39. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing/unique_fields.py +0 -0
  40. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing.egg-info/SOURCES.txt +0 -0
  41. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing.egg-info/dependency_links.txt +0 -0
  42. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing.egg-info/requires.txt +0 -0
  43. {bw_processing-1.5 → bw_processing-1.6}/src/bw_processing.egg-info/top_level.txt +0 -0
  44. {bw_processing-1.5 → bw_processing-1.6}/tests/test_array_creation.py +0 -0
  45. {bw_processing-1.5 → bw_processing-1.6}/tests/test_filesystem.py +0 -0
  46. {bw_processing-1.5 → bw_processing-1.6}/tests/test_filtered_datapackage.py +0 -0
  47. {bw_processing-1.5 → bw_processing-1.6}/tests/test_indexing.py +0 -0
  48. {bw_processing-1.5 → bw_processing-1.6}/tests/test_integration.py +0 -0
  49. {bw_processing-1.5 → bw_processing-1.6}/tests/test_interfaces.py +0 -0
  50. {bw_processing-1.5 → bw_processing-1.6}/tests/test_io_parquet_helpers.py +0 -0
  51. {bw_processing-1.5 → bw_processing-1.6}/tests/test_io_pyarrow_helpers.py +0 -0
  52. {bw_processing-1.5 → bw_processing-1.6}/tests/test_loading.py +0 -0
  53. {bw_processing-1.5 → bw_processing-1.6}/tests/test_merging.py +0 -0
  54. {bw_processing-1.5 → bw_processing-1.6}/tests/test_params.py +0 -0
  55. {bw_processing-1.5 → bw_processing-1.6}/tests/test_proxies.py +0 -0
  56. {bw_processing-1.5 → bw_processing-1.6}/tests/test_unique_fields.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bw_processing
3
- Version: 1.5
3
+ Version: 1.6
4
4
  Summary: Foo
5
5
  Author-email: Chris Mutel <cmutel@gmail.com>
6
6
  Maintainer-email: Chris Mutel <cmutel@gmail.com>
@@ -211,12 +211,12 @@ print(data_obj.url)
211
211
 
212
212
  ### Scale arrays
213
213
 
214
- Any resource group (persistent or dynamic, vector or array) can carry an optional `scale_array`: a one-dimensional float array of the same length as `indices_array`. Each element is a multiplicative factor applied to the corresponding data value before it is inserted into the matrix. The factor is applied to both static and stochastically-sampled values. A value of `1.0` leaves the data unchanged.
214
+ Any resource group (persistent or dynamic, vector or array) can carry an optional `rescale_array`: a one-dimensional float array of the same length as `indices_array`. Each element is a multiplicative factor applied to the corresponding data value before it is inserted into the matrix. The factor is applied to both static and stochastically-sampled values. A value of `1.0` leaves the data unchanged.
215
215
 
216
216
  Typical use cases:
217
217
 
218
- * **Allocation factors** — when a process produces multiple products, the exchange amounts must be partitioned between them. Storing the allocation coefficients as a `scale_array` keeps them alongside the data they modify without requiring a separate processing step.
219
- * **Unit conversions** — when source data is expressed in a unit that differs from the matrix convention, a constant conversion factor can be stored as a `scale_array` rather than baked into every data value.
218
+ * **Allocation factors** — when a process produces multiple products, the exchange amounts must be partitioned between them. Storing the allocation coefficients as a `rescale_array` keeps them alongside the data they modify without requiring a separate processing step.
219
+ * **Unit conversions** — when source data is expressed in a unit that differs from the matrix convention, a constant conversion factor can be stored as a `rescale_array` rather than baked into every data value.
220
220
 
221
221
  ```python
222
222
  import numpy as np
@@ -226,18 +226,45 @@ from bw_processing.constants import INDICES_DTYPE
226
226
  dp = create_datapackage()
227
227
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
228
228
  data_array = np.array([100.0, 200.0, 300.0])
229
- scale_array = np.array([0.6, 1.0, 0.4]) # e.g. allocation factors
229
+ rescale_array = np.array([0.6, 1.0, 0.4]) # e.g. allocation factors
230
230
 
231
231
  dp.add_persistent_vector(
232
232
  matrix="technosphere",
233
233
  name="my-process",
234
234
  indices_array=indices_array,
235
235
  data_array=data_array,
236
- scale_array=scale_array,
236
+ rescale_array=rescale_array,
237
237
  )
238
238
  ```
239
239
 
240
- The stored resource has `kind="scale"` and can be retrieved via `dp.get_resource("my-process.scale")`. The `scale_array` must be a float dtype (`float32` or `float64`); passing an integer array raises `WrongDatatype`.
240
+ The stored resource has `kind="rescale"` and can be retrieved via `dp.get_resource("my-process.rescale")`. The `rescale_array` must be a float dtype (`float32` or `float64`); passing an integer array raises `WrongDatatype`.
241
+
242
+ ### Reference (production) exchanges
243
+
244
+ Any resource group can also carry an optional `reference_array`: a one-dimensional boolean array of the same length as `indices_array`. Where an element is `True`, that exchange is the **reference (production) exchange** for its activity/column.
245
+
246
+ The five structural heuristics in `bw_graph_tools` (matching ids, single non-flipped entry, single positive, single negative, unique product) cannot always identify the reference exchange — whenever an activity has more than one same-sign exchange and the products also appear in other columns, the choice is genuinely ambiguous. Only the modeller knows the answer. `reference_array` records it directly so downstream tools can read it instead of guessing.
247
+
248
+ ```python
249
+ import numpy as np
250
+ from bw_processing import create_datapackage
251
+ from bw_processing.constants import INDICES_DTYPE
252
+
253
+ dp = create_datapackage()
254
+ indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
255
+ data_array = np.array([1.0, 0.5, 2.0])
256
+ reference_array = np.array([True, False, False]) # first exchange is the reference
257
+
258
+ dp.add_persistent_vector(
259
+ matrix="technosphere",
260
+ name="my-process",
261
+ indices_array=indices_array,
262
+ data_array=data_array,
263
+ reference_array=reference_array,
264
+ )
265
+ ```
266
+
267
+ The stored resource has `kind="reference"` and can be retrieved via `dp.get_resource("my-process.reference")`. It must be a boolean array; passing a non-boolean array raises `WrongDatatype`. To keep the common case cheap, the resource is written only when at least one entry is `True` — a group with no reference flags carries no `reference` resource. Using the high-level `MatrixEntry`/`ArrayEntry` API, set `reference=True` (or a boolean `reference` array) on the entries you want flagged.
241
268
 
242
269
  ### Parameter arrays for sensitivity analysis
243
270
 
@@ -168,12 +168,12 @@ print(data_obj.url)
168
168
 
169
169
  ### Scale arrays
170
170
 
171
- Any resource group (persistent or dynamic, vector or array) can carry an optional `scale_array`: a one-dimensional float array of the same length as `indices_array`. Each element is a multiplicative factor applied to the corresponding data value before it is inserted into the matrix. The factor is applied to both static and stochastically-sampled values. A value of `1.0` leaves the data unchanged.
171
+ Any resource group (persistent or dynamic, vector or array) can carry an optional `rescale_array`: a one-dimensional float array of the same length as `indices_array`. Each element is a multiplicative factor applied to the corresponding data value before it is inserted into the matrix. The factor is applied to both static and stochastically-sampled values. A value of `1.0` leaves the data unchanged.
172
172
 
173
173
  Typical use cases:
174
174
 
175
- * **Allocation factors** — when a process produces multiple products, the exchange amounts must be partitioned between them. Storing the allocation coefficients as a `scale_array` keeps them alongside the data they modify without requiring a separate processing step.
176
- * **Unit conversions** — when source data is expressed in a unit that differs from the matrix convention, a constant conversion factor can be stored as a `scale_array` rather than baked into every data value.
175
+ * **Allocation factors** — when a process produces multiple products, the exchange amounts must be partitioned between them. Storing the allocation coefficients as a `rescale_array` keeps them alongside the data they modify without requiring a separate processing step.
176
+ * **Unit conversions** — when source data is expressed in a unit that differs from the matrix convention, a constant conversion factor can be stored as a `rescale_array` rather than baked into every data value.
177
177
 
178
178
  ```python
179
179
  import numpy as np
@@ -183,18 +183,45 @@ from bw_processing.constants import INDICES_DTYPE
183
183
  dp = create_datapackage()
184
184
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
185
185
  data_array = np.array([100.0, 200.0, 300.0])
186
- scale_array = np.array([0.6, 1.0, 0.4]) # e.g. allocation factors
186
+ rescale_array = np.array([0.6, 1.0, 0.4]) # e.g. allocation factors
187
187
 
188
188
  dp.add_persistent_vector(
189
189
  matrix="technosphere",
190
190
  name="my-process",
191
191
  indices_array=indices_array,
192
192
  data_array=data_array,
193
- scale_array=scale_array,
193
+ rescale_array=rescale_array,
194
194
  )
195
195
  ```
196
196
 
197
- The stored resource has `kind="scale"` and can be retrieved via `dp.get_resource("my-process.scale")`. The `scale_array` must be a float dtype (`float32` or `float64`); passing an integer array raises `WrongDatatype`.
197
+ The stored resource has `kind="rescale"` and can be retrieved via `dp.get_resource("my-process.rescale")`. The `rescale_array` must be a float dtype (`float32` or `float64`); passing an integer array raises `WrongDatatype`.
198
+
199
+ ### Reference (production) exchanges
200
+
201
+ Any resource group can also carry an optional `reference_array`: a one-dimensional boolean array of the same length as `indices_array`. Where an element is `True`, that exchange is the **reference (production) exchange** for its activity/column.
202
+
203
+ The five structural heuristics in `bw_graph_tools` (matching ids, single non-flipped entry, single positive, single negative, unique product) cannot always identify the reference exchange — whenever an activity has more than one same-sign exchange and the products also appear in other columns, the choice is genuinely ambiguous. Only the modeller knows the answer. `reference_array` records it directly so downstream tools can read it instead of guessing.
204
+
205
+ ```python
206
+ import numpy as np
207
+ from bw_processing import create_datapackage
208
+ from bw_processing.constants import INDICES_DTYPE
209
+
210
+ dp = create_datapackage()
211
+ indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
212
+ data_array = np.array([1.0, 0.5, 2.0])
213
+ reference_array = np.array([True, False, False]) # first exchange is the reference
214
+
215
+ dp.add_persistent_vector(
216
+ matrix="technosphere",
217
+ name="my-process",
218
+ indices_array=indices_array,
219
+ data_array=data_array,
220
+ reference_array=reference_array,
221
+ )
222
+ ```
223
+
224
+ The stored resource has `kind="reference"` and can be retrieved via `dp.get_resource("my-process.reference")`. It must be a boolean array; passing a non-boolean array raises `WrongDatatype`. To keep the common case cheap, the resource is written only when at least one entry is `True` — a group with no reference flags carries no `reference` resource. Using the high-level `MatrixEntry`/`ArrayEntry` API, set `reference=True` (or a boolean `reference` array) on the entries you want flagged.
198
225
 
199
226
  ### Parameter arrays for sensitivity analysis
200
227
 
@@ -35,7 +35,7 @@ __all__ = (
35
35
  "UndefinedInterface",
36
36
  )
37
37
 
38
- __version__ = "1.5"
38
+ __version__ = "1.6"
39
39
 
40
40
 
41
41
  from bw_processing.array_creation import create_array, create_structured_array
@@ -512,6 +512,7 @@ class Datapackage(DatapackageBase):
512
512
  distributions_array,
513
513
  flip_array,
514
514
  rescale_array,
515
+ reference_array,
515
516
  ) = resolve_dict_iterator(dict_iterator, nrows)
516
517
  self.add_persistent_vector(
517
518
  matrix=matrix,
@@ -522,6 +523,7 @@ class Datapackage(DatapackageBase):
522
523
  flip_array=flip_array,
523
524
  distributions_array=distributions_array,
524
525
  rescale_array=rescale_array,
526
+ reference_array=reference_array,
525
527
  matrix_serialize_format_type=matrix_serialize_format_type,
526
528
  **kwargs,
527
529
  )
@@ -538,6 +540,8 @@ class Datapackage(DatapackageBase):
538
540
  High-level convenience method that does not require working directly
539
541
  with NumPy arrays. If any entry has a ``rescale`` value other than
540
542
  ``1.0``, the rescale values are stored as a ``rescale_array`` resource.
543
+ If any entry has ``reference=True``, the reference flags are stored as
544
+ a ``reference_array`` resource (``kind="reference"``).
541
545
 
542
546
  Args:
543
547
  matrix: Name of the target matrix (e.g. ``"technosphere"``).
@@ -562,6 +566,8 @@ class Datapackage(DatapackageBase):
562
566
  Each :class:`.ArrayEntry` becomes one persistent-array resource group.
563
567
  Resource group names are auto-generated. If an entry has a ``rescale``
564
568
  array it is stored as a ``rescale_array`` resource (``kind="rescale"``).
569
+ If an entry has a ``reference`` array with any ``True`` value it is
570
+ stored as a ``reference_array`` resource (``kind="reference"``).
565
571
 
566
572
  Args:
567
573
  matrix: Name of the target matrix (e.g. ``"technosphere"``).
@@ -577,6 +583,7 @@ class Datapackage(DatapackageBase):
577
583
  data_array=entry.data,
578
584
  flip_array=entry.flip,
579
585
  rescale_array=entry.rescale,
586
+ reference_array=entry.reference,
580
587
  )
581
588
 
582
589
  def add_persistent_vector(
@@ -589,6 +596,7 @@ class Datapackage(DatapackageBase):
589
596
  flip_array: Optional[np.ndarray] = None,
590
597
  distributions_array: Optional[np.ndarray] = None,
591
598
  rescale_array: Optional[np.ndarray] = None,
599
+ reference_array: Optional[np.ndarray] = None,
592
600
  params_array: Optional[np.ndarray] = None,
593
601
  param_labels: Optional[list] = None,
594
602
  param_label_schema: Optional[AnyLabelSchema] = None,
@@ -605,6 +613,12 @@ class Datapackage(DatapackageBase):
605
613
  factors and unit conversions. A value of ``1.0`` leaves the data
606
614
  unchanged.
607
615
 
616
+ ``reference_array`` is an optional 1-D boolean array of the same length
617
+ as ``indices_array``. Where ``True``, that entry is the reference
618
+ (production) exchange for its column. It is stored as a
619
+ ``reference_array`` resource (``kind="reference"``) only when at least
620
+ one entry is flagged.
621
+
608
622
  ``params_array`` is an optional 1-D float array recording the values of
609
623
  independent variables (e.g. model parameters) used to generate this
610
624
  resource group. ``param_labels`` is an optional list of label objects
@@ -713,6 +727,15 @@ class Datapackage(DatapackageBase):
713
727
  matrix_serialize_format_type=matrix_serialize_format_type,
714
728
  **kwargs,
715
729
  )
730
+ if reference_array is not None:
731
+ self._add_reference_array_resource(
732
+ reference_array=reference_array,
733
+ indices_array=indices_array,
734
+ name=name,
735
+ keep_proxy=keep_proxy,
736
+ matrix_serialize_format_type=matrix_serialize_format_type,
737
+ **kwargs,
738
+ )
716
739
  if params_array is not None:
717
740
  params_array = load_bytes(params_array)
718
741
  if params_array.ndim != 1:
@@ -751,6 +774,7 @@ class Datapackage(DatapackageBase):
751
774
  name: Optional[str] = None,
752
775
  flip_array: Optional[np.ndarray] = None,
753
776
  rescale_array: Optional[np.ndarray] = None,
777
+ reference_array: Optional[np.ndarray] = None,
754
778
  params_array: Optional[np.ndarray] = None,
755
779
  param_labels: Optional[list] = None,
756
780
  param_label_schema: Optional[AnyLabelSchema] = None,
@@ -767,6 +791,12 @@ class Datapackage(DatapackageBase):
767
791
  factors and unit conversions. A value of ``1.0`` leaves the data
768
792
  unchanged.
769
793
 
794
+ ``reference_array`` is an optional 1-D boolean array of the same length
795
+ as ``indices_array``. Where ``True``, that entry is the reference
796
+ (production) exchange for its column. It is stored as a
797
+ ``reference_array`` resource (``kind="reference"``) only when at least
798
+ one entry is flagged.
799
+
770
800
  ``params_array`` is an optional 2-D float array of shape
771
801
  ``(n_params, n_scenarios)`` where ``n_scenarios`` must equal
772
802
  ``data_array.shape[1]``. It records the independent variable values
@@ -850,6 +880,15 @@ class Datapackage(DatapackageBase):
850
880
  matrix_serialize_format_type=matrix_serialize_format_type,
851
881
  **kwargs,
852
882
  )
883
+ if reference_array is not None:
884
+ self._add_reference_array_resource(
885
+ reference_array=reference_array,
886
+ indices_array=indices_array,
887
+ name=name,
888
+ keep_proxy=keep_proxy,
889
+ matrix_serialize_format_type=matrix_serialize_format_type,
890
+ **kwargs,
891
+ )
853
892
  if params_array is not None:
854
893
  params_array = load_bytes(params_array)
855
894
  if params_array.ndim != 2:
@@ -912,7 +951,7 @@ class Datapackage(DatapackageBase):
912
951
  if kind == "indices":
913
952
  meta_object = "vector"
914
953
  meta_type = "indices"
915
- elif kind in ("flip", "rescale", "params"):
954
+ elif kind in ("flip", "rescale", "reference", "params"):
916
955
  meta_object = "vector"
917
956
  meta_type = "generic"
918
957
  elif kind == "distributions":
@@ -986,6 +1025,41 @@ class Datapackage(DatapackageBase):
986
1025
  **kwargs,
987
1026
  )
988
1027
 
1028
+ def _add_reference_array_resource(
1029
+ self,
1030
+ *,
1031
+ reference_array: np.ndarray,
1032
+ indices_array: np.ndarray,
1033
+ name: str,
1034
+ keep_proxy: bool,
1035
+ matrix_serialize_format_type: Optional[MatrixSerializeFormat],
1036
+ **kwargs,
1037
+ ) -> None:
1038
+ reference_array = load_bytes(reference_array)
1039
+ if reference_array.dtype != bool:
1040
+ raise WrongDatatype(
1041
+ "`reference_array` dtype is {}, but must be `bool`".format(reference_array.dtype)
1042
+ )
1043
+ if reference_array.shape != indices_array.shape:
1044
+ raise ShapeMismatch(
1045
+ "`reference_array` shape ({}) doesn't match `indices_array` ({}).".format(
1046
+ reference_array.shape, indices_array.shape
1047
+ )
1048
+ )
1049
+ # If no references flagged, don't need to store it
1050
+ if reference_array.sum():
1051
+ self._add_numpy_array_resource(
1052
+ array=reference_array,
1053
+ group=name,
1054
+ name=name + ".reference",
1055
+ kind="reference",
1056
+ keep_proxy=keep_proxy,
1057
+ matrix_serialize_format_type=matrix_serialize_format_type,
1058
+ meta_object="vector",
1059
+ meta_type="generic",
1060
+ **kwargs,
1061
+ )
1062
+
989
1063
  @staticmethod
990
1064
  def _check_params_args(
991
1065
  params_array: Optional[np.ndarray],
@@ -1133,6 +1207,7 @@ class Datapackage(DatapackageBase):
1133
1207
  name: Optional[str] = None,
1134
1208
  flip_array: Optional[np.ndarray] = None, # Not interface
1135
1209
  rescale_array: Optional[np.ndarray] = None, # Not interface
1210
+ reference_array: Optional[np.ndarray] = None, # Not interface
1136
1211
  params_array: Optional[np.ndarray] = None, # Not interface
1137
1212
  param_labels: Optional[list] = None,
1138
1213
  param_label_schema: Optional[AnyLabelSchema] = None,
@@ -1147,7 +1222,7 @@ class Datapackage(DatapackageBase):
1147
1222
  1-D numpy array of length ``len(indices_array)`` each time it is called.
1148
1223
 
1149
1224
  The ``indices_array``, optional ``flip_array``, optional ``rescale_array``,
1150
- and optional ``params_array`` are static and are stored as normal numpy
1225
+ optional ``reference_array``, and optional ``params_array`` are static and are stored as normal numpy
1151
1226
  resources. See ``add_persistent_vector`` for documentation of the
1152
1227
  ``params_array``, ``param_labels``, and ``param_label_schema`` arguments.
1153
1228
 
@@ -1162,6 +1237,8 @@ class Datapackage(DatapackageBase):
1162
1237
  multiplied by ``-1`` before insertion.
1163
1238
  rescale_array: Optional 1-D float array of multiplicative factors
1164
1239
  applied before matrix insertion.
1240
+ reference_array: Optional 1-D boolean array; where ``True`` the
1241
+ entry is the reference (production) exchange for its column.
1165
1242
  keep_proxy: If ``True``, store a proxy rather than the raw array
1166
1243
  for on-disk resources.
1167
1244
  matrix_serialize_format_type: Override the instance-level
@@ -1219,6 +1296,15 @@ class Datapackage(DatapackageBase):
1219
1296
  matrix_serialize_format_type=matrix_serialize_format_type,
1220
1297
  **kwargs,
1221
1298
  )
1299
+ if reference_array is not None:
1300
+ self._add_reference_array_resource(
1301
+ reference_array=reference_array,
1302
+ indices_array=indices_array,
1303
+ name=name,
1304
+ keep_proxy=keep_proxy,
1305
+ matrix_serialize_format_type=matrix_serialize_format_type,
1306
+ **kwargs,
1307
+ )
1222
1308
  if params_array is not None:
1223
1309
  params_array = load_bytes(params_array)
1224
1310
  if params_array.ndim != 1:
@@ -1267,6 +1353,7 @@ class Datapackage(DatapackageBase):
1267
1353
  name: Optional[str] = None,
1268
1354
  flip_array: Optional[np.ndarray] = None,
1269
1355
  rescale_array: Optional[np.ndarray] = None, # Not interface
1356
+ reference_array: Optional[np.ndarray] = None, # Not interface
1270
1357
  params_array: Optional[np.ndarray] = None, # Not interface
1271
1358
  param_labels: Optional[list] = None,
1272
1359
  param_label_schema: Optional[AnyLabelSchema] = None,
@@ -1283,7 +1370,7 @@ class Datapackage(DatapackageBase):
1283
1370
  interface.
1284
1371
 
1285
1372
  The ``indices_array``, optional ``flip_array``, optional ``rescale_array``,
1286
- and optional ``params_array`` are static and are stored as normal numpy
1373
+ optional ``reference_array``, and optional ``params_array`` are static and are stored as normal numpy
1287
1374
  resources. For dynamic arrays the column count of ``params_array`` is
1288
1375
  not validated against the interface (whose column count may be unknown at
1289
1376
  write time). See ``add_persistent_vector`` for documentation of the
@@ -1300,6 +1387,8 @@ class Datapackage(DatapackageBase):
1300
1387
  multiplied by ``-1`` before insertion.
1301
1388
  rescale_array: Optional 1-D float array of multiplicative factors
1302
1389
  applied before matrix insertion.
1390
+ reference_array: Optional 1-D boolean array; where ``True`` the
1391
+ entry is the reference (production) exchange for its column.
1303
1392
  keep_proxy: If ``True``, store a proxy rather than the raw array
1304
1393
  for on-disk resources.
1305
1394
  matrix_serialize_format_type: Override the instance-level
@@ -1365,6 +1454,15 @@ class Datapackage(DatapackageBase):
1365
1454
  matrix_serialize_format_type=matrix_serialize_format_type,
1366
1455
  **kwargs,
1367
1456
  )
1457
+ if reference_array is not None:
1458
+ self._add_reference_array_resource(
1459
+ reference_array=reference_array,
1460
+ indices_array=indices_array,
1461
+ name=name,
1462
+ keep_proxy=keep_proxy,
1463
+ matrix_serialize_format_type=matrix_serialize_format_type,
1464
+ **kwargs,
1465
+ )
1368
1466
  if params_array is not None:
1369
1467
  params_array = load_bytes(params_array)
1370
1468
  if params_array.ndim != 2:
@@ -63,6 +63,12 @@ class MatrixEntry:
63
63
  Stored as a ``rescale_array`` resource (``kind="rescale"``). Note
64
64
  that the Python ``float`` value is downcast to ``numpy.float32``
65
65
  when written to the structured array.
66
+ reference: If True, this exchange is the reference (production) exchange
67
+ for its activity/column. Consumers such as bw_graph_tools use this
68
+ to identify production exchanges directly instead of guessing from
69
+ matrix structure. Stored as a ``reference_array`` resource
70
+ (``kind="reference"``) only when at least one entry is flagged;
71
+ defaults to False.
66
72
  """
67
73
 
68
74
  row: int
@@ -77,6 +83,7 @@ class MatrixEntry:
77
83
  maximum: float = math.nan
78
84
  negative: bool = False
79
85
  rescale: float = 1.0
86
+ reference: bool = False
80
87
 
81
88
  def __post_init__(self):
82
89
  if self.uncertainty_type in _NO_UNCERTAINTY_IDS:
@@ -109,6 +116,10 @@ class ArrayEntry:
109
116
  rescale: Optional 1-D float array of per-entry multiplicative factors
110
117
  (one per row). ``1.0`` leaves the value unchanged. Stored as a
111
118
  ``rescale_array`` resource (``kind="rescale"``).
119
+ reference: Optional 1-D boolean sequence of length ``n_entries``.
120
+ Where True, that entry is the reference (production) exchange for
121
+ its column. Stored as a ``reference_array`` resource
122
+ (``kind="reference"``) only when at least one entry is flagged.
112
123
  """
113
124
 
114
125
  rows: np.ndarray
@@ -116,6 +127,7 @@ class ArrayEntry:
116
127
  data: np.ndarray
117
128
  flip: Optional[np.ndarray] = None
118
129
  rescale: Optional[np.ndarray] = None
130
+ reference: Optional[np.ndarray] = None
119
131
 
120
132
  def __post_init__(self):
121
133
  self.rows = np.asarray(self.rows)
@@ -150,6 +162,12 @@ class ArrayEntry:
150
162
  raise ValueError(
151
163
  f"`rescale` shape {self.rescale.shape} doesn't match `rows` shape {self.rows.shape}"
152
164
  )
165
+ if self.reference is not None:
166
+ self.reference = np.asarray(self.reference, dtype=bool)
167
+ if self.reference.shape != self.rows.shape:
168
+ raise ValueError(
169
+ f"`reference` shape {self.reference.shape} doesn't match `rows` shape {self.rows.shape}"
170
+ )
153
171
 
154
172
 
155
173
  def create_datapackage_from_entries(
@@ -53,7 +53,7 @@ def add_resource_suffix(metadata: dict, suffix: str) -> dict:
53
53
  last = metadata["name"].split(".")[-1]
54
54
  rest = metadata["name"][: -len(last) - 1]
55
55
 
56
- if last not in {"indices", "data", "distributions", "flip"}:
56
+ if last not in {"indices", "data", "distributions", "flip", "rescale", "reference"}:
57
57
  raise ValueError("Can't understand resource name suffix")
58
58
 
59
59
  rest = metadata["name"][: -len(last) - 1]
@@ -65,6 +65,7 @@ def dictionary_formatter(row: dict) -> tuple:
65
65
  row.get("negative", False),
66
66
  row.get("flip", False),
67
67
  row.get("rescale", 1.0),
68
+ row.get("reference", False),
68
69
  )
69
70
 
70
71
 
@@ -74,13 +75,18 @@ def resolve_dict_iterator(iterator: Any, nrows: int = None) -> tuple:
74
75
  data = (dictionary_formatter(row) for row in iterator)
75
76
  array = create_structured_array(
76
77
  data,
77
- INDICES_DTYPE + [("amount", np.float32)] + UNCERTAINTY_DTYPE + [("flip", bool), ("rescale", np.float32)],
78
+ INDICES_DTYPE
79
+ + [("amount", np.float32)]
80
+ + UNCERTAINTY_DTYPE
81
+ + [("flip", bool), ("rescale", np.float32), ("reference", bool)],
78
82
  nrows=nrows,
79
83
  sort=True,
80
84
  sort_fields=sort_fields,
81
85
  )
82
86
  rescale = array["rescale"]
83
87
  rescale_array = rescale if (rescale != 1.0).any() else None
88
+ reference = array["reference"]
89
+ reference_array = reference if reference.any() else None
84
90
  return (
85
91
  array["amount"],
86
92
  # Not repacking fields would cause this multi-field index to return a view
@@ -102,6 +108,7 @@ def resolve_dict_iterator(iterator: Any, nrows: int = None) -> tuple:
102
108
  ),
103
109
  array["flip"],
104
110
  rescale_array,
111
+ reference_array,
105
112
  )
106
113
 
107
114
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bw_processing
3
- Version: 1.5
3
+ Version: 1.6
4
4
  Summary: Foo
5
5
  Author-email: Chris Mutel <cmutel@gmail.com>
6
6
  Maintainer-email: Chris Mutel <cmutel@gmail.com>
@@ -211,12 +211,12 @@ print(data_obj.url)
211
211
 
212
212
  ### Scale arrays
213
213
 
214
- Any resource group (persistent or dynamic, vector or array) can carry an optional `scale_array`: a one-dimensional float array of the same length as `indices_array`. Each element is a multiplicative factor applied to the corresponding data value before it is inserted into the matrix. The factor is applied to both static and stochastically-sampled values. A value of `1.0` leaves the data unchanged.
214
+ Any resource group (persistent or dynamic, vector or array) can carry an optional `rescale_array`: a one-dimensional float array of the same length as `indices_array`. Each element is a multiplicative factor applied to the corresponding data value before it is inserted into the matrix. The factor is applied to both static and stochastically-sampled values. A value of `1.0` leaves the data unchanged.
215
215
 
216
216
  Typical use cases:
217
217
 
218
- * **Allocation factors** — when a process produces multiple products, the exchange amounts must be partitioned between them. Storing the allocation coefficients as a `scale_array` keeps them alongside the data they modify without requiring a separate processing step.
219
- * **Unit conversions** — when source data is expressed in a unit that differs from the matrix convention, a constant conversion factor can be stored as a `scale_array` rather than baked into every data value.
218
+ * **Allocation factors** — when a process produces multiple products, the exchange amounts must be partitioned between them. Storing the allocation coefficients as a `rescale_array` keeps them alongside the data they modify without requiring a separate processing step.
219
+ * **Unit conversions** — when source data is expressed in a unit that differs from the matrix convention, a constant conversion factor can be stored as a `rescale_array` rather than baked into every data value.
220
220
 
221
221
  ```python
222
222
  import numpy as np
@@ -226,18 +226,45 @@ from bw_processing.constants import INDICES_DTYPE
226
226
  dp = create_datapackage()
227
227
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
228
228
  data_array = np.array([100.0, 200.0, 300.0])
229
- scale_array = np.array([0.6, 1.0, 0.4]) # e.g. allocation factors
229
+ rescale_array = np.array([0.6, 1.0, 0.4]) # e.g. allocation factors
230
230
 
231
231
  dp.add_persistent_vector(
232
232
  matrix="technosphere",
233
233
  name="my-process",
234
234
  indices_array=indices_array,
235
235
  data_array=data_array,
236
- scale_array=scale_array,
236
+ rescale_array=rescale_array,
237
237
  )
238
238
  ```
239
239
 
240
- The stored resource has `kind="scale"` and can be retrieved via `dp.get_resource("my-process.scale")`. The `scale_array` must be a float dtype (`float32` or `float64`); passing an integer array raises `WrongDatatype`.
240
+ The stored resource has `kind="rescale"` and can be retrieved via `dp.get_resource("my-process.rescale")`. The `rescale_array` must be a float dtype (`float32` or `float64`); passing an integer array raises `WrongDatatype`.
241
+
242
+ ### Reference (production) exchanges
243
+
244
+ Any resource group can also carry an optional `reference_array`: a one-dimensional boolean array of the same length as `indices_array`. Where an element is `True`, that exchange is the **reference (production) exchange** for its activity/column.
245
+
246
+ The five structural heuristics in `bw_graph_tools` (matching ids, single non-flipped entry, single positive, single negative, unique product) cannot always identify the reference exchange — whenever an activity has more than one same-sign exchange and the products also appear in other columns, the choice is genuinely ambiguous. Only the modeller knows the answer. `reference_array` records it directly so downstream tools can read it instead of guessing.
247
+
248
+ ```python
249
+ import numpy as np
250
+ from bw_processing import create_datapackage
251
+ from bw_processing.constants import INDICES_DTYPE
252
+
253
+ dp = create_datapackage()
254
+ indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
255
+ data_array = np.array([1.0, 0.5, 2.0])
256
+ reference_array = np.array([True, False, False]) # first exchange is the reference
257
+
258
+ dp.add_persistent_vector(
259
+ matrix="technosphere",
260
+ name="my-process",
261
+ indices_array=indices_array,
262
+ data_array=data_array,
263
+ reference_array=reference_array,
264
+ )
265
+ ```
266
+
267
+ The stored resource has `kind="reference"` and can be retrieved via `dp.get_resource("my-process.reference")`. It must be a boolean array; passing a non-boolean array raises `WrongDatatype`. To keep the common case cheap, the resource is written only when at least one entry is `True` — a group with no reference flags carries no `reference` resource. Using the high-level `MatrixEntry`/`ArrayEntry` API, set `reference=True` (or a boolean `reference` array) on the entries you want flagged.
241
268
 
242
269
  ### Parameter arrays for sensitivity analysis
243
270
 
@@ -947,3 +947,101 @@ def test_zipfile_compressed_roundtrip(tmp_path):
947
947
  dp = load_datapackage(ZipFileSystem(str(zip_path)))
948
948
  data, _ = dp.get_resource("sa-data-vector.data")
949
949
  assert np.allclose(data, [2, 7, 12])
950
+
951
+
952
+ REFERENCE_INDICES = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
953
+
954
+
955
+ def test_reference_array_stored():
956
+ dp = create_datapackage()
957
+ dp.add_persistent_vector(
958
+ matrix="sa_matrix",
959
+ data_array=np.array([2, 7, 12]),
960
+ name="sa-ref",
961
+ indices_array=REFERENCE_INDICES,
962
+ reference_array=np.array([True, False, False], dtype=bool),
963
+ )
964
+ data, _ = dp.get_resource("sa-ref.reference")
965
+ assert data.dtype == bool
966
+ assert list(data) == [True, False, False]
967
+
968
+
969
+ def test_reference_array_not_stored_when_all_false():
970
+ dp = create_datapackage()
971
+ dp.add_persistent_vector(
972
+ matrix="sa_matrix",
973
+ data_array=np.array([2, 7, 12]),
974
+ name="sa-ref",
975
+ indices_array=REFERENCE_INDICES,
976
+ reference_array=np.array([False, False, False], dtype=bool),
977
+ )
978
+ assert "sa-ref.reference" not in [r["name"] for r in dp.resources]
979
+
980
+
981
+ def test_reference_array_wrong_dtype():
982
+ dp = create_datapackage()
983
+ with pytest.raises(WrongDatatype):
984
+ dp.add_persistent_vector(
985
+ matrix="sa_matrix",
986
+ data_array=np.array([2, 7, 12]),
987
+ name="sa-ref",
988
+ indices_array=REFERENCE_INDICES,
989
+ reference_array=np.array([1, 0, 0], dtype=int),
990
+ )
991
+
992
+
993
+ def test_reference_array_shape_mismatch():
994
+ dp = create_datapackage()
995
+ with pytest.raises(ShapeMismatch):
996
+ dp.add_persistent_vector(
997
+ matrix="sa_matrix",
998
+ data_array=np.array([2, 7, 12]),
999
+ name="sa-ref",
1000
+ indices_array=REFERENCE_INDICES,
1001
+ reference_array=np.array([True, False], dtype=bool),
1002
+ )
1003
+
1004
+
1005
+ def test_reference_array_persistent_array():
1006
+ dp = create_datapackage()
1007
+ dp.add_persistent_array(
1008
+ matrix="sa_matrix",
1009
+ data_array=np.arange(12).reshape((3, 4)),
1010
+ name="sa-ref-arr",
1011
+ indices_array=REFERENCE_INDICES,
1012
+ reference_array=np.array([False, True, False], dtype=bool),
1013
+ )
1014
+ data, _ = dp.get_resource("sa-ref-arr.reference")
1015
+ assert list(data) == [False, True, False]
1016
+
1017
+
1018
+ def test_reference_array_dynamic_vector():
1019
+ dp = create_datapackage()
1020
+ dp.add_dynamic_vector(
1021
+ interface=Dummy(),
1022
+ matrix="sa_matrix",
1023
+ name="sa-ref-dyn",
1024
+ indices_array=REFERENCE_INDICES,
1025
+ reference_array=np.array([False, False, True], dtype=bool),
1026
+ )
1027
+ data, _ = dp.get_resource("sa-ref-dyn.reference")
1028
+ assert list(data) == [False, False, True]
1029
+
1030
+
1031
+ def test_reference_array_roundtrip(tmp_path):
1032
+ fs = generic_zipfile_filesystem(dirpath=tmp_path, filename="ref.zip")
1033
+ dp = create_datapackage(fs=fs, name="test-reference")
1034
+ dp.add_persistent_vector(
1035
+ matrix="sa_matrix",
1036
+ data_array=np.array([2, 7, 12]),
1037
+ name="sa-ref",
1038
+ indices_array=REFERENCE_INDICES,
1039
+ reference_array=np.array([True, False, True], dtype=bool),
1040
+ )
1041
+ dp.finalize_serialization()
1042
+
1043
+ loaded = load_datapackage(ZipFileSystem(str(tmp_path / "ref.zip")))
1044
+ data, resource = loaded.get_resource("sa-ref.reference")
1045
+ assert resource["kind"] == "reference"
1046
+ assert data.dtype == bool
1047
+ assert list(data) == [True, False, True]
@@ -59,6 +59,18 @@ class TestMatrixEntry:
59
59
  e = MatrixEntry(row=1, col=2, amount=3.0, rescale=2.0)
60
60
  assert e.as_dict()["rescale"] == pytest.approx(2.0)
61
61
 
62
+ def test_reference_default_is_false(self):
63
+ e = MatrixEntry(row=1, col=2, amount=3.0)
64
+ assert e.reference is False
65
+
66
+ def test_reference_custom_value(self):
67
+ e = MatrixEntry(row=1, col=2, amount=3.0, reference=True)
68
+ assert e.reference is True
69
+
70
+ def test_reference_in_as_dict(self):
71
+ e = MatrixEntry(row=1, col=2, amount=3.0, reference=True)
72
+ assert e.as_dict()["reference"] is True
73
+
62
74
  def test_loc_set_to_amount_for_no_uncertainty(self):
63
75
  e = MatrixEntry(row=1, col=2, amount=5.0)
64
76
  assert e.loc == pytest.approx(5.0)
@@ -90,7 +102,7 @@ class TestMatrixEntry:
90
102
  assert set(d.keys()) == {
91
103
  "row", "col", "amount", "flip", "uncertainty_type",
92
104
  "loc", "scale", "shape", "minimum", "maximum", "negative",
93
- "rescale",
105
+ "rescale", "reference",
94
106
  }
95
107
 
96
108
  def test_as_dict_values(self):
@@ -251,6 +263,19 @@ class TestArrayEntry:
251
263
  with pytest.raises(ValueError, match="rescale.*rows"):
252
264
  ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 4)), rescale=[1.0, 2.0, 3.0])
253
265
 
266
+ def test_reference_default_is_none(self):
267
+ e = ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 4)))
268
+ assert e.reference is None
269
+
270
+ def test_reference_coerced_to_bool(self):
271
+ e = ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 4)), reference=[1, 0])
272
+ assert e.reference.dtype == bool
273
+ assert list(e.reference) == [True, False]
274
+
275
+ def test_reference_shape_mismatch(self):
276
+ with pytest.raises(ValueError, match="reference.*rows"):
277
+ ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 4)), reference=[True, False, True])
278
+
254
279
 
255
280
  class TestAddArrayEntries:
256
281
  def test_single_entry(self):
@@ -325,6 +350,32 @@ class TestAddArrayEntries:
325
350
  stored = dp.data[dp.resources.index(rescale_resource)]
326
351
  np.testing.assert_array_almost_equal(stored, [2.0, 0.5])
327
352
 
353
+ def test_reference_stored_correctly(self):
354
+ dp = create_datapackage()
355
+ entry = ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 3)), reference=[True, False])
356
+ dp.add_array_entries(matrix="technosphere_matrix", entries=[entry])
357
+ group = next(iter(dp.groups.values()))
358
+ reference_resource = next(r for r in group.resources if r["kind"] == "reference")
359
+ stored = dp.data[dp.resources.index(reference_resource)]
360
+ assert stored.dtype == bool
361
+ assert list(stored) == [True, False]
362
+
363
+ def test_no_reference_resource_when_reference_is_none(self):
364
+ dp = create_datapackage()
365
+ entry = ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 3)))
366
+ dp.add_array_entries(matrix="technosphere_matrix", entries=[entry])
367
+ group = next(iter(dp.groups.values()))
368
+ kinds = [r["kind"] for r in group.resources]
369
+ assert "reference" not in kinds
370
+
371
+ def test_no_reference_resource_when_all_false(self):
372
+ dp = create_datapackage()
373
+ entry = ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 3)), reference=[False, False])
374
+ dp.add_array_entries(matrix="technosphere_matrix", entries=[entry])
375
+ group = next(iter(dp.groups.values()))
376
+ kinds = [r["kind"] for r in group.resources]
377
+ assert "reference" not in kinds
378
+
328
379
 
329
380
  class TestAddEntries:
330
381
  def test_no_rescale_resource_when_all_rescale_one(self):
@@ -388,6 +439,49 @@ class TestAddEntries:
388
439
  else:
389
440
  assert rescales[i] == pytest.approx(2.0)
390
441
 
442
+ def test_no_reference_resource_when_no_references(self):
443
+ dp = create_datapackage()
444
+ entries = [
445
+ MatrixEntry(row=1, col=2, amount=1.0),
446
+ MatrixEntry(row=3, col=4, amount=2.0),
447
+ ]
448
+ dp.add_entries(matrix="technosphere_matrix", entries=entries)
449
+ group = next(iter(dp.groups.values()))
450
+ kinds = [r["kind"] for r in group.resources]
451
+ assert "reference" not in kinds
452
+
453
+ def test_reference_resource_stored_when_set(self):
454
+ dp = create_datapackage()
455
+ entries = [
456
+ MatrixEntry(row=1, col=2, amount=1.0, reference=True),
457
+ MatrixEntry(row=3, col=4, amount=2.0, reference=True),
458
+ ]
459
+ dp.add_entries(matrix="technosphere_matrix", entries=entries)
460
+ group = next(iter(dp.groups.values()))
461
+ reference_resource = next(r for r in group.resources if r["kind"] == "reference")
462
+ stored = dp.data[dp.resources.index(reference_resource)]
463
+ assert stored.dtype == bool
464
+ assert list(stored) == [True, True]
465
+
466
+ def test_reference_aligned_with_data_when_sorted(self):
467
+ # add_entries sorts by (row, col); reference flags must follow the same order
468
+ dp = create_datapackage()
469
+ entries = [
470
+ MatrixEntry(row=3, col=4, amount=2.0, reference=False),
471
+ MatrixEntry(row=1, col=2, amount=1.0, reference=True),
472
+ ]
473
+ dp.add_entries(matrix="technosphere_matrix", entries=entries)
474
+ group = next(iter(dp.groups.values()))
475
+ idx_resource = next(r for r in group.resources if r["kind"] == "indices")
476
+ reference_resource = next(r for r in group.resources if r["kind"] == "reference")
477
+ indices = dp.data[dp.resources.index(idx_resource)]
478
+ references = dp.data[dp.resources.index(reference_resource)]
479
+ for i, idx in enumerate(indices):
480
+ if idx["row"] == 1:
481
+ assert references[i] == np.bool_(True)
482
+ else:
483
+ assert references[i] == np.bool_(False)
484
+
391
485
 
392
486
  class TestSimpleGraphDeprecation:
393
487
  def test_deprecation_warning(self):
@@ -37,7 +37,7 @@ def test_dictionary_formatter_sparse():
37
37
  result = dictionary_formatter(given)
38
38
  assert result[:5] == (1, 1, 4, 0, 4)
39
39
  assert all(np.isnan(x) for x in result[5:9])
40
- assert result[9:] == (False, False, 1.0)
40
+ assert result[9:] == (False, False, 1.0, False)
41
41
 
42
42
 
43
43
  def test_dictionary_formatter_uncertainty_type():
@@ -68,7 +68,7 @@ def test_dictionary_formatter_complete():
68
68
  "negative": True,
69
69
  "flip": False,
70
70
  }
71
- expected = (1, 2, 3, 4, 5, 6, 7, 8, 9, True, False, 1.0)
71
+ expected = (1, 2, 3, 4, 5, 6, 7, 8, 9, True, False, 1.0, False)
72
72
  assert dictionary_formatter(given) == expected
73
73
 
74
74
 
@@ -85,20 +85,32 @@ def test_dictionary_formatter_one_dimensional():
85
85
  "negative": True,
86
86
  "flip": False,
87
87
  }
88
- expected = (1, 1, 3, 4, 5, 6, 7, 8, 9, True, False, 1.0)
88
+ expected = (1, 1, 3, 4, 5, 6, 7, 8, 9, True, False, 1.0, False)
89
89
  assert dictionary_formatter(given) == expected
90
90
 
91
91
 
92
92
  def test_dictionary_formatter_rescale():
93
93
  given = {"row": 1, "col": 2, "amount": 3.0, "rescale": 0.5}
94
94
  result = dictionary_formatter(given)
95
- assert result[-1] == 0.5
95
+ assert result[-2] == 0.5
96
96
 
97
97
 
98
98
  def test_dictionary_formatter_rescale_default():
99
99
  given = {"row": 1, "amount": 4}
100
100
  result = dictionary_formatter(given)
101
- assert result[-1] == 1.0
101
+ assert result[-2] == 1.0
102
+
103
+
104
+ def test_dictionary_formatter_reference():
105
+ given = {"row": 1, "col": 2, "amount": 3.0, "reference": True}
106
+ result = dictionary_formatter(given)
107
+ assert result[-1] is True
108
+
109
+
110
+ def test_dictionary_formatter_reference_default():
111
+ given = {"row": 1, "amount": 4}
112
+ result = dictionary_formatter(given)
113
+ assert result[-1] is False
102
114
 
103
115
 
104
116
  def test_check_suffix():
File without changes
File without changes
File without changes
File without changes