bw-processing 1.4__tar.gz → 1.5__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.4/src/bw_processing.egg-info → bw_processing-1.5}/PKG-INFO +1 -1
  2. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/__init__.py +1 -1
  3. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/datapackage.py +91 -99
  4. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/matrix_entry.py +10 -8
  5. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/utils.py +5 -1
  6. {bw_processing-1.4 → bw_processing-1.5/src/bw_processing.egg-info}/PKG-INFO +1 -1
  7. {bw_processing-1.4 → bw_processing-1.5}/tests/test_datapackage.py +111 -51
  8. {bw_processing-1.4 → bw_processing-1.5}/tests/test_matrix_entry.py +103 -0
  9. {bw_processing-1.4 → bw_processing-1.5}/tests/test_utils.py +15 -3
  10. {bw_processing-1.4 → bw_processing-1.5}/LICENSE +0 -0
  11. {bw_processing-1.4 → bw_processing-1.5}/MANIFEST.in +0 -0
  12. {bw_processing-1.4 → bw_processing-1.5}/README.md +0 -0
  13. {bw_processing-1.4 → bw_processing-1.5}/pyproject.toml +0 -0
  14. {bw_processing-1.4 → bw_processing-1.5}/setup.cfg +0 -0
  15. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/array_creation.py +0 -0
  16. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/constants.py +0 -0
  17. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/errors.py +0 -0
  18. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/__init__.py +0 -0
  19. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/another name.indices.parquet +0 -0
  20. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.data.parquet +0 -0
  21. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.distributions.parquet +0 -0
  22. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.flip.parquet +0 -0
  23. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.indices.parquet +0 -0
  24. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/some name.data.parquet +0 -0
  25. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/some name.flip.parquet +0 -0
  26. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/some name.indices.parquet +0 -0
  27. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/datapackage_2.zip +0 -0
  28. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/interfaces.py +0 -0
  29. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/parquet_files.py +0 -0
  30. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/examples/simple.zip +0 -0
  31. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/filesystem.py +0 -0
  32. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/indexing.py +0 -0
  33. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/io_helpers.py +0 -0
  34. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/io_parquet_helpers.py +0 -0
  35. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/io_pyarrow_helpers.py +0 -0
  36. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/merging.py +0 -0
  37. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/param_labels.py +0 -0
  38. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/proxies.py +0 -0
  39. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing/unique_fields.py +0 -0
  40. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing.egg-info/SOURCES.txt +0 -0
  41. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing.egg-info/dependency_links.txt +0 -0
  42. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing.egg-info/requires.txt +0 -0
  43. {bw_processing-1.4 → bw_processing-1.5}/src/bw_processing.egg-info/top_level.txt +0 -0
  44. {bw_processing-1.4 → bw_processing-1.5}/tests/test_array_creation.py +0 -0
  45. {bw_processing-1.4 → bw_processing-1.5}/tests/test_filesystem.py +0 -0
  46. {bw_processing-1.4 → bw_processing-1.5}/tests/test_filtered_datapackage.py +0 -0
  47. {bw_processing-1.4 → bw_processing-1.5}/tests/test_indexing.py +0 -0
  48. {bw_processing-1.4 → bw_processing-1.5}/tests/test_integration.py +0 -0
  49. {bw_processing-1.4 → bw_processing-1.5}/tests/test_interfaces.py +0 -0
  50. {bw_processing-1.4 → bw_processing-1.5}/tests/test_io_parquet_helpers.py +0 -0
  51. {bw_processing-1.4 → bw_processing-1.5}/tests/test_io_pyarrow_helpers.py +0 -0
  52. {bw_processing-1.4 → bw_processing-1.5}/tests/test_loading.py +0 -0
  53. {bw_processing-1.4 → bw_processing-1.5}/tests/test_merging.py +0 -0
  54. {bw_processing-1.4 → bw_processing-1.5}/tests/test_params.py +0 -0
  55. {bw_processing-1.4 → bw_processing-1.5}/tests/test_proxies.py +0 -0
  56. {bw_processing-1.4 → bw_processing-1.5}/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.4
3
+ Version: 1.5
4
4
  Summary: Foo
5
5
  Author-email: Chris Mutel <cmutel@gmail.com>
6
6
  Maintainer-email: Chris Mutel <cmutel@gmail.com>
@@ -35,7 +35,7 @@ __all__ = (
35
35
  "UndefinedInterface",
36
36
  )
37
37
 
38
- __version__ = "1.4"
38
+ __version__ = "1.5"
39
39
 
40
40
 
41
41
  from bw_processing.array_creation import create_array, create_structured_array
@@ -500,7 +500,6 @@ class Datapackage(DatapackageBase):
500
500
  dict_iterator: Any = None,
501
501
  nrows: Optional[int] = None,
502
502
  matrix_serialize_format_type: Optional[MatrixSerializeFormat] = None,
503
- scale_array: Optional[np.ndarray] = None,
504
503
  **kwargs,
505
504
  ) -> None:
506
505
  """Create a persistant vector from an iterator. Uses the utility function ``resolve_dict_iterator``.
@@ -512,6 +511,7 @@ class Datapackage(DatapackageBase):
512
511
  indices_array,
513
512
  distributions_array,
514
513
  flip_array,
514
+ rescale_array,
515
515
  ) = resolve_dict_iterator(dict_iterator, nrows)
516
516
  self.add_persistent_vector(
517
517
  matrix=matrix,
@@ -521,7 +521,7 @@ class Datapackage(DatapackageBase):
521
521
  indices_array=indices_array,
522
522
  flip_array=flip_array,
523
523
  distributions_array=distributions_array,
524
- scale_array=scale_array,
524
+ rescale_array=rescale_array,
525
525
  matrix_serialize_format_type=matrix_serialize_format_type,
526
526
  **kwargs,
527
527
  )
@@ -536,35 +536,19 @@ class Datapackage(DatapackageBase):
536
536
  """Add matrix data from a list of :class:`MatrixEntry` objects.
537
537
 
538
538
  High-level convenience method that does not require working directly
539
- with NumPy arrays.
540
-
541
- If any entry has a ``rescale`` value other than ``1.0``, all rescale
542
- values are collected into a ``scale_array`` resource. The entries are
543
- pre-sorted by ``(row, col, amount, uncertainty_type)`` — matching
544
- the internal sort applied by :func:`resolve_dict_iterator` — so the
545
- scale values stay aligned with the stored indices and data arrays.
539
+ with NumPy arrays. If any entry has a ``rescale`` value other than
540
+ ``1.0``, the rescale values are stored as a ``rescale_array`` resource.
546
541
 
547
542
  Args:
548
543
  matrix: Name of the target matrix (e.g. ``"technosphere"``).
549
544
  entries: List of :class:`.MatrixEntry` instances.
550
545
  name: Optional resource group name; auto-generated if omitted.
551
546
  """
552
- has_rescale = any(e.rescale != 1.0 for e in entries)
553
- if has_rescale:
554
- entries = sorted(
555
- entries,
556
- key=lambda e: (e.row, e.col, float(np.float32(e.amount)), e.uncertainty_type),
557
- )
558
- scale_array = np.array([e.rescale for e in entries], dtype=np.float32)
559
- else:
560
- scale_array = None
561
-
562
547
  self.add_persistent_vector_from_iterator(
563
548
  matrix=matrix,
564
549
  name=name,
565
550
  dict_iterator=(e.as_dict() for e in entries),
566
551
  nrows=len(entries),
567
- scale_array=scale_array,
568
552
  )
569
553
 
570
554
  def add_array_entries(
@@ -576,8 +560,8 @@ class Datapackage(DatapackageBase):
576
560
  """Add matrix data from a list of :class:`.ArrayEntry` objects.
577
561
 
578
562
  Each :class:`.ArrayEntry` becomes one persistent-array resource group.
579
- Resource group names are auto-generated. If an entry has a ``scale``
580
- array it is stored as a ``scale_array`` resource (``kind="scale"``).
563
+ Resource group names are auto-generated. If an entry has a ``rescale``
564
+ array it is stored as a ``rescale_array`` resource (``kind="rescale"``).
581
565
 
582
566
  Args:
583
567
  matrix: Name of the target matrix (e.g. ``"technosphere"``).
@@ -592,7 +576,7 @@ class Datapackage(DatapackageBase):
592
576
  indices_array=indices,
593
577
  data_array=entry.data,
594
578
  flip_array=entry.flip,
595
- scale_array=entry.scale,
579
+ rescale_array=entry.rescale,
596
580
  )
597
581
 
598
582
  def add_persistent_vector(
@@ -604,7 +588,7 @@ class Datapackage(DatapackageBase):
604
588
  data_array: Optional[np.ndarray] = None,
605
589
  flip_array: Optional[np.ndarray] = None,
606
590
  distributions_array: Optional[np.ndarray] = None,
607
- scale_array: Optional[np.ndarray] = None,
591
+ rescale_array: Optional[np.ndarray] = None,
608
592
  params_array: Optional[np.ndarray] = None,
609
593
  param_labels: Optional[list] = None,
610
594
  param_label_schema: Optional[AnyLabelSchema] = None,
@@ -614,7 +598,7 @@ class Datapackage(DatapackageBase):
614
598
  ) -> None:
615
599
  """Add a persistent vector resource group to the datapackage.
616
600
 
617
- ``scale_array`` is an optional 1-D float array of the same length as
601
+ ``rescale_array`` is an optional 1-D float array of the same length as
618
602
  ``indices_array``. Each element is a multiplicative factor applied to
619
603
  the corresponding data value — whether static or stochastic — before
620
604
  the value is inserted into the matrix. Typical uses are allocation
@@ -697,18 +681,18 @@ class Datapackage(DatapackageBase):
697
681
  )
698
682
  if flip_array is not None:
699
683
  flip_array = load_bytes(flip_array)
684
+ if flip_array.dtype != bool:
685
+ raise WrongDatatype(
686
+ "`flip_array` dtype is {}, but must be `bool`".format(flip_array.dtype)
687
+ )
688
+ if flip_array.shape != indices_array.shape:
689
+ raise ShapeMismatch(
690
+ "`flip_array` shape ({}) doesn't match `indices_array` ({}).".format(
691
+ flip_array.shape, indices_array.shape
692
+ )
693
+ )
700
694
  # If no flips, don't need to store it
701
695
  if flip_array.sum():
702
- if flip_array.dtype != bool:
703
- raise WrongDatatype(
704
- "`flip_array` dtype is {}, but must be `bool`".format(flip_array.dtype)
705
- )
706
- elif flip_array.shape != indices_array.shape:
707
- raise ShapeMismatch(
708
- "`flip_array` shape ({}) doesn't match `indices_array` ({}).".format(
709
- flip_array.shape, indices_array.shape
710
- )
711
- )
712
696
  self._add_numpy_array_resource(
713
697
  array=flip_array,
714
698
  group=name,
@@ -720,9 +704,9 @@ class Datapackage(DatapackageBase):
720
704
  meta_type="generic",
721
705
  **kwargs,
722
706
  )
723
- if scale_array is not None:
724
- self._add_scale_array_resource(
725
- scale_array=scale_array,
707
+ if rescale_array is not None:
708
+ self._add_rescale_array_resource(
709
+ rescale_array=rescale_array,
726
710
  indices_array=indices_array,
727
711
  name=name,
728
712
  keep_proxy=keep_proxy,
@@ -766,7 +750,7 @@ class Datapackage(DatapackageBase):
766
750
  indices_array: np.ndarray,
767
751
  name: Optional[str] = None,
768
752
  flip_array: Optional[np.ndarray] = None,
769
- scale_array: Optional[np.ndarray] = None,
753
+ rescale_array: Optional[np.ndarray] = None,
770
754
  params_array: Optional[np.ndarray] = None,
771
755
  param_labels: Optional[list] = None,
772
756
  param_label_schema: Optional[AnyLabelSchema] = None,
@@ -776,7 +760,7 @@ class Datapackage(DatapackageBase):
776
760
  ) -> None:
777
761
  """Add a persistent array resource group to the datapackage.
778
762
 
779
- ``scale_array`` is an optional 1-D float array of the same length as
763
+ ``rescale_array`` is an optional 1-D float array of the same length as
780
764
  ``indices_array``. Each element is a multiplicative factor applied to
781
765
  the corresponding data value — whether static or stochastic — before
782
766
  the value is inserted into the matrix. Typical uses are allocation
@@ -834,17 +818,18 @@ class Datapackage(DatapackageBase):
834
818
  )
835
819
  if flip_array is not None:
836
820
  flip_array = load_bytes(flip_array)
837
- if flip_array.sum():
838
- if flip_array.dtype != bool:
839
- raise WrongDatatype(
840
- "`flip_array` dtype is {}, but must be `bool`".format(flip_array.dtype)
841
- )
842
- elif flip_array.shape != indices_array.shape:
843
- raise ShapeMismatch(
844
- "`flip_array` shape ({}) doesn't match `indices_array` ({}).".format(
845
- flip_array.shape, indices_array.shape
846
- )
821
+ if flip_array.dtype != bool:
822
+ raise WrongDatatype(
823
+ "`flip_array` dtype is {}, but must be `bool`".format(flip_array.dtype)
824
+ )
825
+ if flip_array.shape != indices_array.shape:
826
+ raise ShapeMismatch(
827
+ "`flip_array` shape ({}) doesn't match `indices_array` ({}).".format(
828
+ flip_array.shape, indices_array.shape
847
829
  )
830
+ )
831
+ # If no flips, don't need to store it
832
+ if flip_array.sum():
848
833
  self._add_numpy_array_resource(
849
834
  array=flip_array,
850
835
  group=name,
@@ -856,9 +841,9 @@ class Datapackage(DatapackageBase):
856
841
  meta_type="generic",
857
842
  **kwargs,
858
843
  )
859
- if scale_array is not None:
860
- self._add_scale_array_resource(
861
- scale_array=scale_array,
844
+ if rescale_array is not None:
845
+ self._add_rescale_array_resource(
846
+ rescale_array=rescale_array,
862
847
  indices_array=indices_array,
863
848
  name=name,
864
849
  keep_proxy=keep_proxy,
@@ -927,7 +912,7 @@ class Datapackage(DatapackageBase):
927
912
  if kind == "indices":
928
913
  meta_object = "vector"
929
914
  meta_type = "indices"
930
- elif kind in ("flip", "scale", "params"):
915
+ elif kind in ("flip", "rescale", "params"):
931
916
  meta_object = "vector"
932
917
  meta_type = "generic"
933
918
  elif kind == "distributions":
@@ -968,32 +953,32 @@ class Datapackage(DatapackageBase):
968
953
 
969
954
  self._modified = set()
970
955
 
971
- def _add_scale_array_resource(
956
+ def _add_rescale_array_resource(
972
957
  self,
973
958
  *,
974
- scale_array: np.ndarray,
959
+ rescale_array: np.ndarray,
975
960
  indices_array: np.ndarray,
976
961
  name: str,
977
962
  keep_proxy: bool,
978
963
  matrix_serialize_format_type: Optional[MatrixSerializeFormat],
979
964
  **kwargs,
980
965
  ) -> None:
981
- scale_array = load_bytes(scale_array)
982
- if not np.issubdtype(scale_array.dtype, np.floating):
966
+ rescale_array = load_bytes(rescale_array)
967
+ if not np.issubdtype(rescale_array.dtype, np.floating):
983
968
  raise WrongDatatype(
984
- "`scale_array` dtype is {}, but must be a float dtype".format(scale_array.dtype)
969
+ "`rescale_array` dtype is {}, but must be a float dtype".format(rescale_array.dtype)
985
970
  )
986
- elif scale_array.shape != indices_array.shape:
971
+ elif rescale_array.shape != indices_array.shape:
987
972
  raise ShapeMismatch(
988
- "`scale_array` shape ({}) doesn't match `indices_array` ({}).".format(
989
- scale_array.shape, indices_array.shape
973
+ "`rescale_array` shape ({}) doesn't match `indices_array` ({}).".format(
974
+ rescale_array.shape, indices_array.shape
990
975
  )
991
976
  )
992
977
  self._add_numpy_array_resource(
993
- array=scale_array,
978
+ array=rescale_array,
994
979
  group=name,
995
- name=name + ".scale",
996
- kind="scale",
980
+ name=name + ".rescale",
981
+ kind="rescale",
997
982
  keep_proxy=keep_proxy,
998
983
  matrix_serialize_format_type=matrix_serialize_format_type,
999
984
  meta_object="vector",
@@ -1147,7 +1132,7 @@ class Datapackage(DatapackageBase):
1147
1132
  indices_array: np.ndarray, # Not interface
1148
1133
  name: Optional[str] = None,
1149
1134
  flip_array: Optional[np.ndarray] = None, # Not interface
1150
- scale_array: Optional[np.ndarray] = None, # Not interface
1135
+ rescale_array: Optional[np.ndarray] = None, # Not interface
1151
1136
  params_array: Optional[np.ndarray] = None, # Not interface
1152
1137
  param_labels: Optional[list] = None,
1153
1138
  param_label_schema: Optional[AnyLabelSchema] = None,
@@ -1161,7 +1146,7 @@ class Datapackage(DatapackageBase):
1161
1146
  stored on disk. ``interface`` must implement ``__next__()`` and return a
1162
1147
  1-D numpy array of length ``len(indices_array)`` each time it is called.
1163
1148
 
1164
- The ``indices_array``, optional ``flip_array``, optional ``scale_array``,
1149
+ The ``indices_array``, optional ``flip_array``, optional ``rescale_array``,
1165
1150
  and optional ``params_array`` are static and are stored as normal numpy
1166
1151
  resources. See ``add_persistent_vector`` for documentation of the
1167
1152
  ``params_array``, ``param_labels``, and ``param_label_schema`` arguments.
@@ -1175,7 +1160,7 @@ class Datapackage(DatapackageBase):
1175
1160
  name: Optional resource group name; auto-generated if omitted.
1176
1161
  flip_array: Optional boolean array; where ``True`` the value is
1177
1162
  multiplied by ``-1`` before insertion.
1178
- scale_array: Optional 1-D float array of multiplicative factors
1163
+ rescale_array: Optional 1-D float array of multiplicative factors
1179
1164
  applied before matrix insertion.
1180
1165
  keep_proxy: If ``True``, store a proxy rather than the raw array
1181
1166
  for on-disk resources.
@@ -1202,17 +1187,18 @@ class Datapackage(DatapackageBase):
1202
1187
  )
1203
1188
  if flip_array is not None:
1204
1189
  flip_array = load_bytes(flip_array)
1205
- if flip_array.sum():
1206
- if flip_array.dtype != bool:
1207
- raise WrongDatatype(
1208
- "`flip_array` dtype is {}, but must be `bool`".format(flip_array.dtype)
1209
- )
1210
- elif flip_array.shape != indices_array.shape:
1211
- raise ShapeMismatch(
1212
- "`flip_array` shape ({}) doesn't match `indices_array` ({}).".format(
1213
- flip_array.shape, indices_array.shape
1214
- )
1190
+ if flip_array.dtype != bool:
1191
+ raise WrongDatatype(
1192
+ "`flip_array` dtype is {}, but must be `bool`".format(flip_array.dtype)
1193
+ )
1194
+ if flip_array.shape != indices_array.shape:
1195
+ raise ShapeMismatch(
1196
+ "`flip_array` shape ({}) doesn't match `indices_array` ({}).".format(
1197
+ flip_array.shape, indices_array.shape
1215
1198
  )
1199
+ )
1200
+ # If no flips, don't need to store it
1201
+ if flip_array.sum():
1216
1202
  self._add_numpy_array_resource(
1217
1203
  array=flip_array,
1218
1204
  group=name,
@@ -1224,9 +1210,9 @@ class Datapackage(DatapackageBase):
1224
1210
  meta_type="generic",
1225
1211
  **kwargs,
1226
1212
  )
1227
- if scale_array is not None:
1228
- self._add_scale_array_resource(
1229
- scale_array=scale_array,
1213
+ if rescale_array is not None:
1214
+ self._add_rescale_array_resource(
1215
+ rescale_array=rescale_array,
1230
1216
  indices_array=indices_array,
1231
1217
  name=name,
1232
1218
  keep_proxy=keep_proxy,
@@ -1280,7 +1266,7 @@ class Datapackage(DatapackageBase):
1280
1266
  indices_array: np.ndarray, # Not interface
1281
1267
  name: Optional[str] = None,
1282
1268
  flip_array: Optional[np.ndarray] = None,
1283
- scale_array: Optional[np.ndarray] = None, # Not interface
1269
+ rescale_array: Optional[np.ndarray] = None, # Not interface
1284
1270
  params_array: Optional[np.ndarray] = None, # Not interface
1285
1271
  param_labels: Optional[list] = None,
1286
1272
  param_label_schema: Optional[AnyLabelSchema] = None,
@@ -1296,7 +1282,7 @@ class Datapackage(DatapackageBase):
1296
1282
  array for ``args[1]``. ``ncols`` may be ``None`` for an infinite
1297
1283
  interface.
1298
1284
 
1299
- The ``indices_array``, optional ``flip_array``, optional ``scale_array``,
1285
+ The ``indices_array``, optional ``flip_array``, optional ``rescale_array``,
1300
1286
  and optional ``params_array`` are static and are stored as normal numpy
1301
1287
  resources. For dynamic arrays the column count of ``params_array`` is
1302
1288
  not validated against the interface (whose column count may be unknown at
@@ -1312,7 +1298,7 @@ class Datapackage(DatapackageBase):
1312
1298
  name: Optional resource group name; auto-generated if omitted.
1313
1299
  flip_array: Optional boolean array; where ``True`` the value is
1314
1300
  multiplied by ``-1`` before insertion.
1315
- scale_array: Optional 1-D float array of multiplicative factors
1301
+ rescale_array: Optional 1-D float array of multiplicative factors
1316
1302
  applied before matrix insertion.
1317
1303
  keep_proxy: If ``True``, store a proxy rather than the raw array
1318
1304
  for on-disk resources.
@@ -1322,8 +1308,13 @@ class Datapackage(DatapackageBase):
1322
1308
  self._check_params_args(params_array, param_labels, param_label_schema)
1323
1309
  self._prepare_modifications()
1324
1310
 
1325
- if isinstance(flip_array, np.ndarray) and not flip_array.sum():
1326
- flip_array = None
1311
+ if isinstance(flip_array, np.ndarray):
1312
+ if flip_array.dtype != bool:
1313
+ raise WrongDatatype(
1314
+ "`flip_array` dtype is {}, but must be `bool`".format(flip_array.dtype)
1315
+ )
1316
+ if not flip_array.sum():
1317
+ flip_array = None
1327
1318
 
1328
1319
  kwargs.update({"matrix": matrix, "category": "array", "nrows": len(indices_array)})
1329
1320
  name = self._prepare_name(name)
@@ -1342,17 +1333,18 @@ class Datapackage(DatapackageBase):
1342
1333
  )
1343
1334
  if flip_array is not None:
1344
1335
  flip_array = load_bytes(flip_array)
1345
- if flip_array.sum():
1346
- if flip_array.dtype != bool:
1347
- raise WrongDatatype(
1348
- "`flip_array` dtype is {}, but must be `bool`".format(flip_array.dtype)
1349
- )
1350
- elif flip_array.shape != indices_array.shape:
1351
- raise ShapeMismatch(
1352
- "`flip_array` shape ({}) doesn't match `indices_array` ({}).".format(
1353
- flip_array.shape, indices_array.shape
1354
- )
1336
+ if flip_array.dtype != bool:
1337
+ raise WrongDatatype(
1338
+ "`flip_array` dtype is {}, but must be `bool`".format(flip_array.dtype)
1339
+ )
1340
+ if flip_array.shape != indices_array.shape:
1341
+ raise ShapeMismatch(
1342
+ "`flip_array` shape ({}) doesn't match `indices_array` ({}).".format(
1343
+ flip_array.shape, indices_array.shape
1355
1344
  )
1345
+ )
1346
+ # If no flips, don't need to store it
1347
+ if flip_array.sum():
1356
1348
  self._add_numpy_array_resource(
1357
1349
  array=flip_array,
1358
1350
  group=name,
@@ -1364,9 +1356,9 @@ class Datapackage(DatapackageBase):
1364
1356
  meta_type="generic",
1365
1357
  **kwargs,
1366
1358
  )
1367
- if scale_array is not None:
1368
- self._add_scale_array_resource(
1369
- scale_array=scale_array,
1359
+ if rescale_array is not None:
1360
+ self._add_rescale_array_resource(
1361
+ rescale_array=rescale_array,
1370
1362
  indices_array=indices_array,
1371
1363
  name=name,
1372
1364
  keep_proxy=keep_proxy,
@@ -60,7 +60,9 @@ class MatrixEntry:
60
60
  negative: Whether the underlying value is negative.
61
61
  rescale: Per-exchange multiplicative factor applied before matrix
62
62
  insertion. ``1.0`` (the default) leaves the value unchanged.
63
- Stored as a ``scale_array`` resource (``kind="scale"``).
63
+ Stored as a ``rescale_array`` resource (``kind="rescale"``). Note
64
+ that the Python ``float`` value is downcast to ``numpy.float32``
65
+ when written to the structured array.
64
66
  """
65
67
 
66
68
  row: int
@@ -104,16 +106,16 @@ class ArrayEntry:
104
106
  cols: 1-D sequence of integer column indices, one per matrix entry.
105
107
  data: 2-D array of shape ``(n_entries, n_scenarios)``.
106
108
  flip: Optional 1-D boolean sequence of length ``n_entries``.
107
- scale: Optional 1-D float array of per-entry multiplicative factors
109
+ rescale: Optional 1-D float array of per-entry multiplicative factors
108
110
  (one per row). ``1.0`` leaves the value unchanged. Stored as a
109
- ``scale_array`` resource (``kind="scale"``).
111
+ ``rescale_array`` resource (``kind="rescale"``).
110
112
  """
111
113
 
112
114
  rows: np.ndarray
113
115
  cols: np.ndarray
114
116
  data: np.ndarray
115
117
  flip: Optional[np.ndarray] = None
116
- scale: Optional[np.ndarray] = None
118
+ rescale: Optional[np.ndarray] = None
117
119
 
118
120
  def __post_init__(self):
119
121
  self.rows = np.asarray(self.rows)
@@ -142,11 +144,11 @@ class ArrayEntry:
142
144
  raise ValueError(
143
145
  f"`flip` shape {self.flip.shape} doesn't match `rows` shape {self.rows.shape}"
144
146
  )
145
- if self.scale is not None:
146
- self.scale = np.asarray(self.scale, dtype=np.float32)
147
- if self.scale.shape != self.rows.shape:
147
+ if self.rescale is not None:
148
+ self.rescale = np.asarray(self.rescale, dtype=np.float32)
149
+ if self.rescale.shape != self.rows.shape:
148
150
  raise ValueError(
149
- f"`scale` shape {self.scale.shape} doesn't match `rows` shape {self.rows.shape}"
151
+ f"`rescale` shape {self.rescale.shape} doesn't match `rows` shape {self.rows.shape}"
150
152
  )
151
153
 
152
154
 
@@ -64,6 +64,7 @@ def dictionary_formatter(row: dict) -> tuple:
64
64
  row.get("maximum", np.nan),
65
65
  row.get("negative", False),
66
66
  row.get("flip", False),
67
+ row.get("rescale", 1.0),
67
68
  )
68
69
 
69
70
 
@@ -73,11 +74,13 @@ def resolve_dict_iterator(iterator: Any, nrows: int = None) -> tuple:
73
74
  data = (dictionary_formatter(row) for row in iterator)
74
75
  array = create_structured_array(
75
76
  data,
76
- INDICES_DTYPE + [("amount", np.float32)] + UNCERTAINTY_DTYPE + [("flip", bool)],
77
+ INDICES_DTYPE + [("amount", np.float32)] + UNCERTAINTY_DTYPE + [("flip", bool), ("rescale", np.float32)],
77
78
  nrows=nrows,
78
79
  sort=True,
79
80
  sort_fields=sort_fields,
80
81
  )
82
+ rescale = array["rescale"]
83
+ rescale_array = rescale if (rescale != 1.0).any() else None
81
84
  return (
82
85
  array["amount"],
83
86
  # Not repacking fields would cause this multi-field index to return a view
@@ -98,6 +101,7 @@ def resolve_dict_iterator(iterator: Any, nrows: int = None) -> tuple:
98
101
  ]
99
102
  ),
100
103
  array["flip"],
104
+ rescale_array,
101
105
  )
102
106
 
103
107
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bw_processing
3
- Version: 1.4
3
+ Version: 1.5
4
4
  Summary: Foo
5
5
  Author-email: Chris Mutel <cmutel@gmail.com>
6
6
  Maintainer-email: Chris Mutel <cmutel@gmail.com>
@@ -330,6 +330,23 @@ def test_add_persistent_vector_flip_dtype():
330
330
  )
331
331
 
332
332
 
333
+ def test_add_persistent_vector_flip_dtype_all_zeros():
334
+ # A non-bool flip that is all-zeros must still raise WrongDatatype;
335
+ # previously the dtype check was skipped when sum() == 0.
336
+ dp = create_datapackage()
337
+ data_array = np.array([2, 7, 12])
338
+ flip_array = np.array([0, 0, 0]) # all falsy, but wrong dtype
339
+ indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
340
+ with pytest.raises(WrongDatatype):
341
+ dp.add_persistent_vector(
342
+ matrix="sa_matrix",
343
+ data_array=data_array,
344
+ name="sa-data-vector",
345
+ flip_array=flip_array,
346
+ indices_array=indices_array,
347
+ )
348
+
349
+
333
350
  def test_add_persistent_vector_flip_shapemistmatch():
334
351
  dp = create_datapackage()
335
352
  data_array = np.array([2, 7, 12])
@@ -386,6 +403,21 @@ def test_add_persistent_array_flip_dtype():
386
403
  )
387
404
 
388
405
 
406
+ def test_add_persistent_array_flip_dtype_all_zeros():
407
+ dp = create_datapackage()
408
+ data_array = np.arange(12).reshape(3, 4)
409
+ flip_array = np.array([0, 0, 0]) # all falsy, but wrong dtype
410
+ indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
411
+ with pytest.raises(WrongDatatype):
412
+ dp.add_persistent_array(
413
+ matrix="sa_matrix",
414
+ data_array=data_array,
415
+ name="sa-data-vector",
416
+ flip_array=flip_array,
417
+ indices_array=indices_array,
418
+ )
419
+
420
+
389
421
  def test_add_persistent_array_flip_shapemistmatch():
390
422
  dp = create_datapackage()
391
423
  data_array = np.arange(12).reshape(3, 4)
@@ -415,6 +447,20 @@ def test_add_dynamic_array_flip_dtype():
415
447
  )
416
448
 
417
449
 
450
+ def test_add_dynamic_array_flip_dtype_all_zeros():
451
+ dp = create_datapackage()
452
+ flip_array = np.array([0, 0, 0]) # all falsy, but wrong dtype
453
+ indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
454
+ with pytest.raises(WrongDatatype):
455
+ dp.add_dynamic_array(
456
+ matrix="sa_matrix",
457
+ interface=Dummy(),
458
+ name="sa-data-vector",
459
+ flip_array=flip_array,
460
+ indices_array=indices_array,
461
+ )
462
+
463
+
418
464
  def test_add_dynamic_array_flip_shapemistmatch():
419
465
  dp = create_datapackage()
420
466
  flip_array = np.array([0, 1, 0, 1], dtype=bool)
@@ -443,6 +489,20 @@ def test_add_dynamic_vector_flip_dtype():
443
489
  )
444
490
 
445
491
 
492
+ def test_add_dynamic_vector_flip_dtype_all_zeros():
493
+ dp = create_datapackage()
494
+ flip_array = np.array([0, 0, 0]) # all falsy, but wrong dtype
495
+ indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
496
+ with pytest.raises(WrongDatatype):
497
+ dp.add_dynamic_vector(
498
+ matrix="sa_matrix",
499
+ interface=Dummy(),
500
+ name="sa-data-vector",
501
+ flip_array=flip_array,
502
+ indices_array=indices_array,
503
+ )
504
+
505
+
446
506
  def test_add_dynamic_vector_flip_shapemistmatch():
447
507
  dp = create_datapackage()
448
508
  flip_array = np.array([0, 1, 0, 1], dtype=bool)
@@ -457,28 +517,28 @@ def test_add_dynamic_vector_flip_shapemistmatch():
457
517
  )
458
518
 
459
519
 
460
- def test_add_persistent_vector_scale_array():
520
+ def test_add_persistent_vector_rescale_array():
461
521
  dp = create_datapackage()
462
522
  data_array = np.array([2.0, 7.0, 12.0])
463
- scale_array = np.array([0.5, 1.0, 2.0])
523
+ rescale_array = np.array([0.5, 1.0, 2.0])
464
524
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
465
525
  dp.add_persistent_vector(
466
526
  matrix="sa_matrix",
467
527
  data_array=data_array,
468
528
  name="sa-data-vector",
469
529
  indices_array=indices_array,
470
- scale_array=scale_array,
530
+ rescale_array=rescale_array,
471
531
  )
472
- assert "sa-data-vector.scale" in [o["name"] for o in dp.resources]
473
- data, meta = dp.get_resource("sa-data-vector.scale")
474
- assert meta["kind"] == "scale"
475
- assert np.allclose(data, scale_array)
532
+ assert "sa-data-vector.rescale" in [o["name"] for o in dp.resources]
533
+ data, meta = dp.get_resource("sa-data-vector.rescale")
534
+ assert meta["kind"] == "rescale"
535
+ assert np.allclose(data, rescale_array)
476
536
 
477
537
 
478
538
  def test_add_persistent_vector_scale_dtype():
479
539
  dp = create_datapackage()
480
540
  data_array = np.array([2.0, 7.0, 12.0])
481
- scale_array = np.array([1, 2, 3]) # integer dtype
541
+ rescale_array = np.array([1, 2, 3]) # integer dtype
482
542
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
483
543
  with pytest.raises(WrongDatatype):
484
544
  dp.add_persistent_vector(
@@ -486,14 +546,14 @@ def test_add_persistent_vector_scale_dtype():
486
546
  data_array=data_array,
487
547
  name="sa-data-vector",
488
548
  indices_array=indices_array,
489
- scale_array=scale_array,
549
+ rescale_array=rescale_array,
490
550
  )
491
551
 
492
552
 
493
553
  def test_add_persistent_vector_scale_shapemismatch():
494
554
  dp = create_datapackage()
495
555
  data_array = np.array([2.0, 7.0, 12.0])
496
- scale_array = np.array([0.5, 1.0, 2.0, 3.0])
556
+ rescale_array = np.array([0.5, 1.0, 2.0, 3.0])
497
557
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
498
558
  with pytest.raises(ShapeMismatch):
499
559
  dp.add_persistent_vector(
@@ -501,32 +561,32 @@ def test_add_persistent_vector_scale_shapemismatch():
501
561
  data_array=data_array,
502
562
  name="sa-data-vector",
503
563
  indices_array=indices_array,
504
- scale_array=scale_array,
564
+ rescale_array=rescale_array,
505
565
  )
506
566
 
507
567
 
508
- def test_add_persistent_array_scale_array():
568
+ def test_add_persistent_array_rescale_array():
509
569
  dp = create_datapackage()
510
570
  data_array = np.arange(12, dtype=float).reshape(3, 4)
511
- scale_array = np.array([0.5, 1.0, 2.0])
571
+ rescale_array = np.array([0.5, 1.0, 2.0])
512
572
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
513
573
  dp.add_persistent_array(
514
574
  matrix="sa_matrix",
515
575
  data_array=data_array,
516
576
  name="sa-data-vector",
517
577
  indices_array=indices_array,
518
- scale_array=scale_array,
578
+ rescale_array=rescale_array,
519
579
  )
520
- assert "sa-data-vector.scale" in [o["name"] for o in dp.resources]
521
- data, meta = dp.get_resource("sa-data-vector.scale")
522
- assert meta["kind"] == "scale"
523
- assert np.allclose(data, scale_array)
580
+ assert "sa-data-vector.rescale" in [o["name"] for o in dp.resources]
581
+ data, meta = dp.get_resource("sa-data-vector.rescale")
582
+ assert meta["kind"] == "rescale"
583
+ assert np.allclose(data, rescale_array)
524
584
 
525
585
 
526
586
  def test_add_persistent_array_scale_dtype():
527
587
  dp = create_datapackage()
528
588
  data_array = np.arange(12, dtype=float).reshape(3, 4)
529
- scale_array = np.array([1, 2, 3])
589
+ rescale_array = np.array([1, 2, 3])
530
590
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
531
591
  with pytest.raises(WrongDatatype):
532
592
  dp.add_persistent_array(
@@ -534,14 +594,14 @@ def test_add_persistent_array_scale_dtype():
534
594
  data_array=data_array,
535
595
  name="sa-data-vector",
536
596
  indices_array=indices_array,
537
- scale_array=scale_array,
597
+ rescale_array=rescale_array,
538
598
  )
539
599
 
540
600
 
541
601
  def test_add_persistent_array_scale_shapemismatch():
542
602
  dp = create_datapackage()
543
603
  data_array = np.arange(12, dtype=float).reshape(3, 4)
544
- scale_array = np.array([0.5, 1.0, 2.0, 3.0])
604
+ rescale_array = np.array([0.5, 1.0, 2.0, 3.0])
545
605
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
546
606
  with pytest.raises(ShapeMismatch):
547
607
  dp.add_persistent_array(
@@ -549,30 +609,30 @@ def test_add_persistent_array_scale_shapemismatch():
549
609
  data_array=data_array,
550
610
  name="sa-data-vector",
551
611
  indices_array=indices_array,
552
- scale_array=scale_array,
612
+ rescale_array=rescale_array,
553
613
  )
554
614
 
555
615
 
556
- def test_add_dynamic_vector_scale_array():
616
+ def test_add_dynamic_vector_rescale_array():
557
617
  dp = create_datapackage()
558
- scale_array = np.array([0.5, 1.0, 2.0])
618
+ rescale_array = np.array([0.5, 1.0, 2.0])
559
619
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
560
620
  dp.add_dynamic_vector(
561
621
  matrix="sa_matrix",
562
622
  interface=Dummy(),
563
623
  name="sa-data-vector",
564
624
  indices_array=indices_array,
565
- scale_array=scale_array,
625
+ rescale_array=rescale_array,
566
626
  )
567
- assert "sa-data-vector.scale" in [o["name"] for o in dp.resources]
568
- data, meta = dp.get_resource("sa-data-vector.scale")
569
- assert meta["kind"] == "scale"
570
- assert np.allclose(data, scale_array)
627
+ assert "sa-data-vector.rescale" in [o["name"] for o in dp.resources]
628
+ data, meta = dp.get_resource("sa-data-vector.rescale")
629
+ assert meta["kind"] == "rescale"
630
+ assert np.allclose(data, rescale_array)
571
631
 
572
632
 
573
633
  def test_add_dynamic_vector_scale_dtype():
574
634
  dp = create_datapackage()
575
- scale_array = np.array([1, 2, 3])
635
+ rescale_array = np.array([1, 2, 3])
576
636
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
577
637
  with pytest.raises(WrongDatatype):
578
638
  dp.add_dynamic_vector(
@@ -580,13 +640,13 @@ def test_add_dynamic_vector_scale_dtype():
580
640
  interface=Dummy(),
581
641
  name="sa-data-vector",
582
642
  indices_array=indices_array,
583
- scale_array=scale_array,
643
+ rescale_array=rescale_array,
584
644
  )
585
645
 
586
646
 
587
647
  def test_add_dynamic_vector_scale_shapemismatch():
588
648
  dp = create_datapackage()
589
- scale_array = np.array([0.5, 1.0, 2.0, 3.0])
649
+ rescale_array = np.array([0.5, 1.0, 2.0, 3.0])
590
650
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
591
651
  with pytest.raises(ShapeMismatch):
592
652
  dp.add_dynamic_vector(
@@ -594,30 +654,30 @@ def test_add_dynamic_vector_scale_shapemismatch():
594
654
  interface=Dummy(),
595
655
  name="sa-data-vector",
596
656
  indices_array=indices_array,
597
- scale_array=scale_array,
657
+ rescale_array=rescale_array,
598
658
  )
599
659
 
600
660
 
601
- def test_add_dynamic_array_scale_array():
661
+ def test_add_dynamic_array_rescale_array():
602
662
  dp = create_datapackage()
603
- scale_array = np.array([0.5, 1.0, 2.0])
663
+ rescale_array = np.array([0.5, 1.0, 2.0])
604
664
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
605
665
  dp.add_dynamic_array(
606
666
  matrix="sa_matrix",
607
667
  interface=Dummy(),
608
668
  name="sa-data-vector",
609
669
  indices_array=indices_array,
610
- scale_array=scale_array,
670
+ rescale_array=rescale_array,
611
671
  )
612
- assert "sa-data-vector.scale" in [o["name"] for o in dp.resources]
613
- data, meta = dp.get_resource("sa-data-vector.scale")
614
- assert meta["kind"] == "scale"
615
- assert np.allclose(data, scale_array)
672
+ assert "sa-data-vector.rescale" in [o["name"] for o in dp.resources]
673
+ data, meta = dp.get_resource("sa-data-vector.rescale")
674
+ assert meta["kind"] == "rescale"
675
+ assert np.allclose(data, rescale_array)
616
676
 
617
677
 
618
678
  def test_add_dynamic_array_scale_dtype():
619
679
  dp = create_datapackage()
620
- scale_array = np.array([1, 2, 3])
680
+ rescale_array = np.array([1, 2, 3])
621
681
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
622
682
  with pytest.raises(WrongDatatype):
623
683
  dp.add_dynamic_array(
@@ -625,13 +685,13 @@ def test_add_dynamic_array_scale_dtype():
625
685
  interface=Dummy(),
626
686
  name="sa-data-vector",
627
687
  indices_array=indices_array,
628
- scale_array=scale_array,
688
+ rescale_array=rescale_array,
629
689
  )
630
690
 
631
691
 
632
692
  def test_add_dynamic_array_scale_shapemismatch():
633
693
  dp = create_datapackage()
634
- scale_array = np.array([0.5, 1.0, 2.0, 3.0])
694
+ rescale_array = np.array([0.5, 1.0, 2.0, 3.0])
635
695
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
636
696
  with pytest.raises(ShapeMismatch):
637
697
  dp.add_dynamic_array(
@@ -639,7 +699,7 @@ def test_add_dynamic_array_scale_shapemismatch():
639
699
  interface=Dummy(),
640
700
  name="sa-data-vector",
641
701
  indices_array=indices_array,
642
- scale_array=scale_array,
702
+ rescale_array=rescale_array,
643
703
  )
644
704
 
645
705
 
@@ -782,8 +842,8 @@ def test_finalize_mixed_groups_prunes_only_trivial(tmp_path):
782
842
  assert dist_resources[0]["group"] == "real"
783
843
 
784
844
 
785
- def test_scale_array_parquet_roundtrip(tmp_path):
786
- scale_array = np.array([0.5, 1.0, 2.0])
845
+ def test_rescale_array_parquet_roundtrip(tmp_path):
846
+ rescale_array = np.array([0.5, 1.0, 2.0])
787
847
  indices_array = np.array([(1, 4), (2, 5), (3, 6)], dtype=INDICES_DTYPE)
788
848
  data_array = np.array([100.0, 200.0, 300.0])
789
849
 
@@ -796,16 +856,16 @@ def test_scale_array_parquet_roundtrip(tmp_path):
796
856
  data_array=data_array,
797
857
  name="sa-data-vector",
798
858
  indices_array=indices_array,
799
- scale_array=scale_array,
859
+ rescale_array=rescale_array,
800
860
  matrix_serialize_format_type=MatrixSerializeFormat.PARQUET,
801
861
  )
802
862
  dp.finalize_serialization()
803
863
 
804
864
  dp2 = load_datapackage(generic_directory_filesystem(dirpath=tmp_path))
805
- assert "sa-data-vector.scale" in [o["name"] for o in dp2.resources]
806
- loaded, meta = dp2.get_resource("sa-data-vector.scale")
807
- assert meta["kind"] == "scale"
808
- assert np.allclose(loaded, scale_array)
865
+ assert "sa-data-vector.rescale" in [o["name"] for o in dp2.resources]
866
+ loaded, meta = dp2.get_resource("sa-data-vector.rescale")
867
+ assert meta["kind"] == "rescale"
868
+ assert np.allclose(loaded, rescale_array)
809
869
 
810
870
 
811
871
  def test_simple_graph():
@@ -49,6 +49,15 @@ class TestMatrixEntry:
49
49
  assert math.isnan(e.shape)
50
50
  assert math.isnan(e.minimum)
51
51
  assert math.isnan(e.maximum)
52
+ assert e.rescale == pytest.approx(1.0)
53
+
54
+ def test_rescale_custom_value(self):
55
+ e = MatrixEntry(row=1, col=2, amount=3.0, rescale=0.5)
56
+ assert e.rescale == pytest.approx(0.5)
57
+
58
+ def test_rescale_in_as_dict(self):
59
+ e = MatrixEntry(row=1, col=2, amount=3.0, rescale=2.0)
60
+ assert e.as_dict()["rescale"] == pytest.approx(2.0)
52
61
 
53
62
  def test_loc_set_to_amount_for_no_uncertainty(self):
54
63
  e = MatrixEntry(row=1, col=2, amount=5.0)
@@ -81,6 +90,7 @@ class TestMatrixEntry:
81
90
  assert set(d.keys()) == {
82
91
  "row", "col", "amount", "flip", "uncertainty_type",
83
92
  "loc", "scale", "shape", "minimum", "maximum", "negative",
93
+ "rescale",
84
94
  }
85
95
 
86
96
  def test_as_dict_values(self):
@@ -228,6 +238,19 @@ class TestArrayEntry:
228
238
  with pytest.raises(ValueError, match="flip.*rows"):
229
239
  ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 4)), flip=[True, False, True])
230
240
 
241
+ def test_rescale_default_is_none(self):
242
+ e = ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 4)))
243
+ assert e.rescale is None
244
+
245
+ def test_rescale_coerced_to_float32(self):
246
+ e = ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 4)), rescale=[2.0, 0.5])
247
+ assert e.rescale.dtype == np.float32
248
+ np.testing.assert_array_almost_equal(e.rescale, [2.0, 0.5])
249
+
250
+ def test_rescale_shape_mismatch(self):
251
+ with pytest.raises(ValueError, match="rescale.*rows"):
252
+ ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 4)), rescale=[1.0, 2.0, 3.0])
253
+
231
254
 
232
255
  class TestAddArrayEntries:
233
256
  def test_single_entry(self):
@@ -285,6 +308,86 @@ class TestAddArrayEntries:
285
308
  kinds = [r["kind"] for r in group.resources]
286
309
  assert "flip" not in kinds
287
310
 
311
+ def test_no_rescale_resource_when_rescale_is_none(self):
312
+ dp = create_datapackage()
313
+ entry = ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 3)))
314
+ dp.add_array_entries(matrix="technosphere_matrix", entries=[entry])
315
+ group = next(iter(dp.groups.values()))
316
+ kinds = [r["kind"] for r in group.resources]
317
+ assert "rescale" not in kinds
318
+
319
+ def test_rescale_stored_correctly(self):
320
+ dp = create_datapackage()
321
+ entry = ArrayEntry(rows=[0, 1], cols=[2, 3], data=np.ones((2, 3)), rescale=[2.0, 0.5])
322
+ dp.add_array_entries(matrix="technosphere_matrix", entries=[entry])
323
+ group = next(iter(dp.groups.values()))
324
+ rescale_resource = next(r for r in group.resources if r["kind"] == "rescale")
325
+ stored = dp.data[dp.resources.index(rescale_resource)]
326
+ np.testing.assert_array_almost_equal(stored, [2.0, 0.5])
327
+
328
+
329
+ class TestAddEntries:
330
+ def test_no_rescale_resource_when_all_rescale_one(self):
331
+ dp = create_datapackage()
332
+ entries = [
333
+ MatrixEntry(row=1, col=2, amount=1.0),
334
+ MatrixEntry(row=3, col=4, amount=2.0),
335
+ ]
336
+ dp.add_entries(matrix="technosphere_matrix", entries=entries)
337
+ group = next(iter(dp.groups.values()))
338
+ kinds = [r["kind"] for r in group.resources]
339
+ assert "rescale" not in kinds
340
+
341
+ def test_rescale_resource_stored_when_rescale_set(self):
342
+ dp = create_datapackage()
343
+ entries = [
344
+ MatrixEntry(row=1, col=2, amount=1.0, rescale=0.5),
345
+ MatrixEntry(row=3, col=4, amount=2.0, rescale=2.0),
346
+ ]
347
+ dp.add_entries(matrix="technosphere_matrix", entries=entries)
348
+ group = next(iter(dp.groups.values()))
349
+ rescale_resource = next(r for r in group.resources if r["kind"] == "rescale")
350
+ stored = dp.data[dp.resources.index(rescale_resource)]
351
+ np.testing.assert_array_almost_equal(sorted(stored), [0.5, 2.0])
352
+
353
+ def test_rescale_resource_written_when_only_some_entries_rescaled(self):
354
+ dp = create_datapackage()
355
+ entries = [
356
+ MatrixEntry(row=1, col=2, amount=1.0), # rescale=1.0 (default)
357
+ MatrixEntry(row=3, col=4, amount=2.0, rescale=0.5),
358
+ ]
359
+ dp.add_entries(matrix="technosphere_matrix", entries=entries)
360
+ group = next(iter(dp.groups.values()))
361
+ idx_resource = next(r for r in group.resources if r["kind"] == "indices")
362
+ rescale_resource = next(r for r in group.resources if r["kind"] == "rescale")
363
+ indices = dp.data[dp.resources.index(idx_resource)]
364
+ rescales = dp.data[dp.resources.index(rescale_resource)]
365
+ for i, idx in enumerate(indices):
366
+ if idx["row"] == 1:
367
+ assert rescales[i] == pytest.approx(1.0)
368
+ else:
369
+ assert rescales[i] == pytest.approx(0.5)
370
+
371
+ def test_rescale_sorted_with_data(self):
372
+ dp = create_datapackage()
373
+ entries = [
374
+ MatrixEntry(row=3, col=4, amount=2.0, rescale=2.0),
375
+ MatrixEntry(row=1, col=2, amount=1.0, rescale=0.5),
376
+ ]
377
+ dp.add_entries(matrix="technosphere_matrix", entries=entries)
378
+ group = next(iter(dp.groups.values()))
379
+
380
+ idx_resource = next(r for r in group.resources if r["kind"] == "indices")
381
+ rescale_resource = next(r for r in group.resources if r["kind"] == "rescale")
382
+ indices = dp.data[dp.resources.index(idx_resource)]
383
+ rescales = dp.data[dp.resources.index(rescale_resource)]
384
+
385
+ for i, idx in enumerate(indices):
386
+ if idx["row"] == 1:
387
+ assert rescales[i] == pytest.approx(0.5)
388
+ else:
389
+ assert rescales[i] == pytest.approx(2.0)
390
+
288
391
 
289
392
  class TestSimpleGraphDeprecation:
290
393
  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)
40
+ assert result[9:] == (False, False, 1.0)
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)
71
+ expected = (1, 2, 3, 4, 5, 6, 7, 8, 9, True, False, 1.0)
72
72
  assert dictionary_formatter(given) == expected
73
73
 
74
74
 
@@ -85,10 +85,22 @@ 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)
88
+ expected = (1, 1, 3, 4, 5, 6, 7, 8, 9, True, False, 1.0)
89
89
  assert dictionary_formatter(given) == expected
90
90
 
91
91
 
92
+ def test_dictionary_formatter_rescale():
93
+ given = {"row": 1, "col": 2, "amount": 3.0, "rescale": 0.5}
94
+ result = dictionary_formatter(given)
95
+ assert result[-1] == 0.5
96
+
97
+
98
+ def test_dictionary_formatter_rescale_default():
99
+ given = {"row": 1, "amount": 4}
100
+ result = dictionary_formatter(given)
101
+ assert result[-1] == 1.0
102
+
103
+
92
104
  def test_check_suffix():
93
105
  assert check_suffix("foo.bar.baz", "baz") == "foo.bar.baz"
94
106
  assert check_suffix("foo.bar.baz", ".baz") == "foo.bar.baz"
File without changes
File without changes
File without changes
File without changes
File without changes