bw-processing 1.3__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.
- {bw_processing-1.3/src/bw_processing.egg-info → bw_processing-1.5}/PKG-INFO +155 -1
- {bw_processing-1.3 → bw_processing-1.5}/README.md +153 -0
- {bw_processing-1.3 → bw_processing-1.5}/pyproject.toml +1 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/__init__.py +15 -2
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/array_creation.py +29 -65
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/datapackage.py +415 -89
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/matrix_entry.py +69 -0
- bw_processing-1.5/src/bw_processing/param_labels.py +100 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/utils.py +5 -1
- {bw_processing-1.3 → bw_processing-1.5/src/bw_processing.egg-info}/PKG-INFO +155 -1
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing.egg-info/SOURCES.txt +2 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing.egg-info/requires.txt +1 -0
- bw_processing-1.5/tests/test_array_creation.py +97 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_datapackage.py +250 -51
- bw_processing-1.5/tests/test_matrix_entry.py +399 -0
- bw_processing-1.5/tests/test_params.py +483 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_utils.py +15 -3
- bw_processing-1.3/tests/test_array_creation.py +0 -14
- bw_processing-1.3/tests/test_matrix_entry.py +0 -178
- {bw_processing-1.3 → bw_processing-1.5}/LICENSE +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/MANIFEST.in +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/setup.cfg +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/constants.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/errors.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/__init__.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/another name.indices.parquet +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.data.parquet +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.distributions.parquet +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.flip.parquet +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/sa-data-vector-from-dict.indices.parquet +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/some name.data.parquet +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/some name.flip.parquet +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/datapackage_1/some name.indices.parquet +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/datapackage_2.zip +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/interfaces.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/parquet_files.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/examples/simple.zip +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/filesystem.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/indexing.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/io_helpers.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/io_parquet_helpers.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/io_pyarrow_helpers.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/merging.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/proxies.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing/unique_fields.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing.egg-info/dependency_links.txt +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/src/bw_processing.egg-info/top_level.txt +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_filesystem.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_filtered_datapackage.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_indexing.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_integration.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_interfaces.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_io_parquet_helpers.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_io_pyarrow_helpers.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_loading.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_merging.py +0 -0
- {bw_processing-1.3 → bw_processing-1.5}/tests/test_proxies.py +0 -0
- {bw_processing-1.3 → 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.
|
|
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>
|
|
@@ -21,6 +21,7 @@ Requires-Python: >=3.9
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: fsspec
|
|
24
|
+
Requires-Dist: jsonschema>=4.0
|
|
24
25
|
Requires-Dist: morefs
|
|
25
26
|
Requires-Dist: numpy
|
|
26
27
|
Requires-Dist: pandas
|
|
@@ -67,6 +68,13 @@ Library for storing numeric data for use in matrix-based calculations. Designed
|
|
|
67
68
|
|
|
68
69
|
- [Background](#background)
|
|
69
70
|
- [Concepts](#concepts)
|
|
71
|
+
- [Data packages](#data-packages)
|
|
72
|
+
- [Vectors versus arrays](#vectors-versus-arrays)
|
|
73
|
+
- [Persistent versus dynamic](#persistent-versus-dynamic)
|
|
74
|
+
- [Scale arrays](#scale-arrays)
|
|
75
|
+
- [Parameter arrays for sensitivity analysis](#parameter-arrays-for-sensitivity-analysis)
|
|
76
|
+
- [NaN as a sentinel value](#nan-as-a-sentinel-value)
|
|
77
|
+
- [Policies](#policies)
|
|
70
78
|
- [Install](#install)
|
|
71
79
|
- [Usage](#usage)
|
|
72
80
|
- [Contributing](#contributing)
|
|
@@ -85,6 +93,7 @@ The [Brightway LCA framework](https://brightway.dev/) has stored data used in co
|
|
|
85
93
|
* **Use [fsspec](https://filesystem-spec.readthedocs.io/en/latest/) for file IO**. The use of this library allows for data packages to be stored on your local computer, or on [many logical or virtual file systems](https://docs.pyfilesystem.org/en/latest/guide.html).
|
|
86
94
|
* **Simpler handling of numeric values whose sign should be flipped**. Sometimes it is more convenient to specify positive numbers in dataset definitions, even though such numbers should be negative when inserted into the resulting matrices. For example, in the technosphere matrix in life cycle assessment, products produced are positive and products consumed are negative, though both values are given as positive in datasets. Brightway used to use a type mapping dictionary to indicate which values in a matrix should have their sign flipped after insertion. Such mapping dictionaries are brittle and inelegant. `bw_processing` uses an optional boolean vector, called `flip`, to indicate if any values should be flipped.
|
|
87
95
|
* **Per-exchange multiplicative scaling**. An optional float vector, called `scale`, can be attached to any resource group. Each element is a multiplicative factor applied to the corresponding data value — whether static or sampled stochastically — before it is inserted into the matrix. Typical uses are allocation factors and unit conversions. A value of `1.0` leaves the data unchanged.
|
|
96
|
+
* **Recording independent variables for sensitivity analysis**. An optional `params_array` can be attached to any resource group to record the values of model parameters (independent variables) that were used to generate the data. For array resources each column of `params_array` corresponds to the same column in `data_array`, making it straightforward to correlate inputs with outputs for sensitivity analysis methods such as Morris or Sobol.
|
|
88
97
|
* **Separation of uncertainty distribution parameters from other data**. Fitting data to a [probability density function](https://en.wikipedia.org/wiki/Probability_density_function) (PDF), or an estimate of such a PDF, is only one approach to quantitative uncertainty analysis. We would like to support other approaches, including [direct sampling from real data](https://github.com/PascalLesage/presamples/). Therefore, uncertainty distribution parameters are stored separately, only loaded if needed, and are only one way to express quantitative uncertainty.
|
|
89
98
|
|
|
90
99
|
## Concepts
|
|
@@ -230,6 +239,151 @@ dp.add_persistent_vector(
|
|
|
230
239
|
|
|
231
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`.
|
|
232
241
|
|
|
242
|
+
### Parameter arrays for sensitivity analysis
|
|
243
|
+
|
|
244
|
+
Any resource group can carry an optional `params_array` that records the values of the independent variables (model parameters) used to generate the data. This is the foundation for global sensitivity analysis workflows such as [Morris screening](https://en.wikipedia.org/wiki/Morris_method) or [Sobol indices](https://en.wikipedia.org/wiki/Variance-based_sensitivity_analysis).
|
|
245
|
+
|
|
246
|
+
**Shape conventions:**
|
|
247
|
+
|
|
248
|
+
| Resource type | `data_array` shape | `params_array` shape |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| persistent / dynamic vector | `(n_exchanges,)` | `(n_params,)` |
|
|
251
|
+
| persistent array | `(n_exchanges, n_scenarios)` | `(n_params, n_scenarios)` |
|
|
252
|
+
| dynamic array | `(n_exchanges, n_scenarios)` | `(n_params, n_scenarios)` — column count not validated against interface |
|
|
253
|
+
|
|
254
|
+
Column `j` of `params_array` describes the parameter configuration that produced column `j` of `data_array`. The `params_array` must be a float dtype.
|
|
255
|
+
|
|
256
|
+
#### Basic usage
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
import numpy as np
|
|
260
|
+
from bw_processing import create_datapackage, INDICES_DTYPE
|
|
261
|
+
|
|
262
|
+
dp = create_datapackage()
|
|
263
|
+
indices = np.array([(1, 4), (2, 5)], dtype=INDICES_DTYPE)
|
|
264
|
+
|
|
265
|
+
# Store the parameter values alongside the data
|
|
266
|
+
dp.add_persistent_vector(
|
|
267
|
+
matrix="technosphere",
|
|
268
|
+
name="my-process",
|
|
269
|
+
indices_array=indices,
|
|
270
|
+
data_array=np.array([100.0, 200.0]),
|
|
271
|
+
params_array=np.array([25.0, 1.013]), # temperature (°C), pressure (atm)
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### Adding labels
|
|
276
|
+
|
|
277
|
+
`param_labels` is an optional list of label objects (strings or dicts) whose length must match `params_array.shape[0]`. When provided, a companion `name.param_labels.json` file is written inside the same resource group, containing a `"values"` list and an optional `"schema"` (a [JSON Schema](https://json-schema.org/) document). Pass `param_label_schema=StringLabelSchema()` for plain-string labels, or a `ParamLabelSchema` for structured dict labels.
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
import numpy as np
|
|
281
|
+
from bw_processing import create_datapackage, INDICES_DTYPE, StringLabelSchema
|
|
282
|
+
|
|
283
|
+
dp = create_datapackage()
|
|
284
|
+
dp.add_persistent_vector(
|
|
285
|
+
matrix="technosphere",
|
|
286
|
+
name="my-process",
|
|
287
|
+
indices_array=np.array([(1, 4)], dtype=INDICES_DTYPE),
|
|
288
|
+
data_array=np.array([100.0]),
|
|
289
|
+
params_array=np.array([25.0, 1.013]),
|
|
290
|
+
param_labels=["temperature", "pressure"],
|
|
291
|
+
param_label_schema=StringLabelSchema(),
|
|
292
|
+
)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### Structured labels with a schema
|
|
296
|
+
|
|
297
|
+
Use `ParamLabelSchema` and `ParamLabelField` when labels are structured objects, and pass a `param_label_schema` to validate each label at write time:
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
import numpy as np
|
|
301
|
+
from bw_processing import (
|
|
302
|
+
create_datapackage,
|
|
303
|
+
INDICES_DTYPE,
|
|
304
|
+
ParamLabelField,
|
|
305
|
+
ParamLabelSchema,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
dp = create_datapackage()
|
|
309
|
+
|
|
310
|
+
schema = ParamLabelSchema(
|
|
311
|
+
fields=[
|
|
312
|
+
ParamLabelField(name="name", type="string"),
|
|
313
|
+
ParamLabelField(name="database", type="string"),
|
|
314
|
+
ParamLabelField(name="year", type="integer", required=False),
|
|
315
|
+
],
|
|
316
|
+
description="Brightway activity reference",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
param_labels = [
|
|
320
|
+
{"name": "electricity", "database": "ecoinvent", "year": 2020},
|
|
321
|
+
{"name": "heat", "database": "ecoinvent"},
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
dp.add_persistent_array(
|
|
325
|
+
matrix="technosphere",
|
|
326
|
+
name="sa-run",
|
|
327
|
+
indices_array=np.array([(1, 4), (2, 5)], dtype=INDICES_DTYPE),
|
|
328
|
+
data_array=np.array([[10.0, 20.0, 30.0], # 2 exchanges × 3 scenarios
|
|
329
|
+
[40.0, 50.0, 60.0]]),
|
|
330
|
+
params_array=np.array([[25.0, 30.0, 35.0], # temperature: 2 params × 3 scenarios
|
|
331
|
+
[1.0, 1.1, 1.2]]),
|
|
332
|
+
param_labels=param_labels,
|
|
333
|
+
param_label_schema=schema, # validates every label against the JSON Schema on write
|
|
334
|
+
)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
`ParamLabelField.type` accepts the standard JSON Schema type names: `"string"`, `"integer"`, `"number"`, `"boolean"`. For plain-string labels you can also pass `param_label_schema=StringLabelSchema()` to make the schema explicit.
|
|
338
|
+
|
|
339
|
+
#### Retrieving params and labels
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
params_data, _ = dp.get_resource("sa-run.params") # numpy array
|
|
343
|
+
labels_data, _ = dp.get_resource("sa-run.param_labels") # {"schema": ..., "values": [...]}
|
|
344
|
+
|
|
345
|
+
# Reconstruct the schema dataclass from the stored JSON Schema:
|
|
346
|
+
from bw_processing import schema_from_json_schema
|
|
347
|
+
schema = schema_from_json_schema(labels_data["schema"])
|
|
348
|
+
# → ParamLabelSchema(fields=[...])
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
#### Dependency
|
|
352
|
+
|
|
353
|
+
`params_array` validation uses the [`jsonschema`](https://python-jsonschema.readthedocs.io/) library, which is a required dependency of `bw_processing`.
|
|
354
|
+
|
|
355
|
+
### NaN as a sentinel value
|
|
356
|
+
|
|
357
|
+
A `NaN` value in a data vector or array is treated by `matrix_utils` as **"no data insertion"** — that element is skipped when the matrix is built or rebuilt, leaving the corresponding matrix cell at whatever value was written by an earlier package. This convention makes it straightforward to define scenario or override packages: set an element to `NaN` to inherit the base value, or to a real number to override it.
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
import numpy as np
|
|
361
|
+
from bw_processing import create_datapackage, INDICES_DTYPE
|
|
362
|
+
|
|
363
|
+
# Base package — sets matrix[0,0]=5 and matrix[1,1]=7
|
|
364
|
+
dp_base = create_datapackage()
|
|
365
|
+
dp_base.add_persistent_vector(
|
|
366
|
+
matrix="foo",
|
|
367
|
+
name="base",
|
|
368
|
+
indices_array=np.array([(0, 0), (1, 1)], dtype=INDICES_DTYPE),
|
|
369
|
+
data_array=np.array([5.0, 7.0]),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Scenario package — overrides matrix[1,1] but leaves matrix[0,0] untouched
|
|
373
|
+
dp_scenario = create_datapackage()
|
|
374
|
+
dp_scenario.add_persistent_vector(
|
|
375
|
+
matrix="foo",
|
|
376
|
+
name="scenario",
|
|
377
|
+
indices_array=np.array([(0, 0), (1, 1)], dtype=INDICES_DTYPE),
|
|
378
|
+
data_array=np.array([np.nan, 99.0]),
|
|
379
|
+
)
|
|
380
|
+
# When both packages are passed to MappedMatrix:
|
|
381
|
+
# matrix[0,0] == 5.0 (NaN in scenario → base value preserved)
|
|
382
|
+
# matrix[1,1] == 99.0 (non-NaN in scenario → override applied)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Note that `NaN` skipping is implemented in `matrix_utils`, not in `bw_processing` itself. `bw_processing` stores and retrieves the `NaN` values faithfully; the skip logic runs at matrix construction time.
|
|
386
|
+
|
|
233
387
|
### Policies
|
|
234
388
|
|
|
235
389
|
Data package policies define how the data should be used. Policies apply to the entire data package; you may wish to adjust what is stored in which data packages to get the effect you desire.
|
|
@@ -25,6 +25,13 @@ Library for storing numeric data for use in matrix-based calculations. Designed
|
|
|
25
25
|
|
|
26
26
|
- [Background](#background)
|
|
27
27
|
- [Concepts](#concepts)
|
|
28
|
+
- [Data packages](#data-packages)
|
|
29
|
+
- [Vectors versus arrays](#vectors-versus-arrays)
|
|
30
|
+
- [Persistent versus dynamic](#persistent-versus-dynamic)
|
|
31
|
+
- [Scale arrays](#scale-arrays)
|
|
32
|
+
- [Parameter arrays for sensitivity analysis](#parameter-arrays-for-sensitivity-analysis)
|
|
33
|
+
- [NaN as a sentinel value](#nan-as-a-sentinel-value)
|
|
34
|
+
- [Policies](#policies)
|
|
28
35
|
- [Install](#install)
|
|
29
36
|
- [Usage](#usage)
|
|
30
37
|
- [Contributing](#contributing)
|
|
@@ -43,6 +50,7 @@ The [Brightway LCA framework](https://brightway.dev/) has stored data used in co
|
|
|
43
50
|
* **Use [fsspec](https://filesystem-spec.readthedocs.io/en/latest/) for file IO**. The use of this library allows for data packages to be stored on your local computer, or on [many logical or virtual file systems](https://docs.pyfilesystem.org/en/latest/guide.html).
|
|
44
51
|
* **Simpler handling of numeric values whose sign should be flipped**. Sometimes it is more convenient to specify positive numbers in dataset definitions, even though such numbers should be negative when inserted into the resulting matrices. For example, in the technosphere matrix in life cycle assessment, products produced are positive and products consumed are negative, though both values are given as positive in datasets. Brightway used to use a type mapping dictionary to indicate which values in a matrix should have their sign flipped after insertion. Such mapping dictionaries are brittle and inelegant. `bw_processing` uses an optional boolean vector, called `flip`, to indicate if any values should be flipped.
|
|
45
52
|
* **Per-exchange multiplicative scaling**. An optional float vector, called `scale`, can be attached to any resource group. Each element is a multiplicative factor applied to the corresponding data value — whether static or sampled stochastically — before it is inserted into the matrix. Typical uses are allocation factors and unit conversions. A value of `1.0` leaves the data unchanged.
|
|
53
|
+
* **Recording independent variables for sensitivity analysis**. An optional `params_array` can be attached to any resource group to record the values of model parameters (independent variables) that were used to generate the data. For array resources each column of `params_array` corresponds to the same column in `data_array`, making it straightforward to correlate inputs with outputs for sensitivity analysis methods such as Morris or Sobol.
|
|
46
54
|
* **Separation of uncertainty distribution parameters from other data**. Fitting data to a [probability density function](https://en.wikipedia.org/wiki/Probability_density_function) (PDF), or an estimate of such a PDF, is only one approach to quantitative uncertainty analysis. We would like to support other approaches, including [direct sampling from real data](https://github.com/PascalLesage/presamples/). Therefore, uncertainty distribution parameters are stored separately, only loaded if needed, and are only one way to express quantitative uncertainty.
|
|
47
55
|
|
|
48
56
|
## Concepts
|
|
@@ -188,6 +196,151 @@ dp.add_persistent_vector(
|
|
|
188
196
|
|
|
189
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`.
|
|
190
198
|
|
|
199
|
+
### Parameter arrays for sensitivity analysis
|
|
200
|
+
|
|
201
|
+
Any resource group can carry an optional `params_array` that records the values of the independent variables (model parameters) used to generate the data. This is the foundation for global sensitivity analysis workflows such as [Morris screening](https://en.wikipedia.org/wiki/Morris_method) or [Sobol indices](https://en.wikipedia.org/wiki/Variance-based_sensitivity_analysis).
|
|
202
|
+
|
|
203
|
+
**Shape conventions:**
|
|
204
|
+
|
|
205
|
+
| Resource type | `data_array` shape | `params_array` shape |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| persistent / dynamic vector | `(n_exchanges,)` | `(n_params,)` |
|
|
208
|
+
| persistent array | `(n_exchanges, n_scenarios)` | `(n_params, n_scenarios)` |
|
|
209
|
+
| dynamic array | `(n_exchanges, n_scenarios)` | `(n_params, n_scenarios)` — column count not validated against interface |
|
|
210
|
+
|
|
211
|
+
Column `j` of `params_array` describes the parameter configuration that produced column `j` of `data_array`. The `params_array` must be a float dtype.
|
|
212
|
+
|
|
213
|
+
#### Basic usage
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
import numpy as np
|
|
217
|
+
from bw_processing import create_datapackage, INDICES_DTYPE
|
|
218
|
+
|
|
219
|
+
dp = create_datapackage()
|
|
220
|
+
indices = np.array([(1, 4), (2, 5)], dtype=INDICES_DTYPE)
|
|
221
|
+
|
|
222
|
+
# Store the parameter values alongside the data
|
|
223
|
+
dp.add_persistent_vector(
|
|
224
|
+
matrix="technosphere",
|
|
225
|
+
name="my-process",
|
|
226
|
+
indices_array=indices,
|
|
227
|
+
data_array=np.array([100.0, 200.0]),
|
|
228
|
+
params_array=np.array([25.0, 1.013]), # temperature (°C), pressure (atm)
|
|
229
|
+
)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### Adding labels
|
|
233
|
+
|
|
234
|
+
`param_labels` is an optional list of label objects (strings or dicts) whose length must match `params_array.shape[0]`. When provided, a companion `name.param_labels.json` file is written inside the same resource group, containing a `"values"` list and an optional `"schema"` (a [JSON Schema](https://json-schema.org/) document). Pass `param_label_schema=StringLabelSchema()` for plain-string labels, or a `ParamLabelSchema` for structured dict labels.
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
import numpy as np
|
|
238
|
+
from bw_processing import create_datapackage, INDICES_DTYPE, StringLabelSchema
|
|
239
|
+
|
|
240
|
+
dp = create_datapackage()
|
|
241
|
+
dp.add_persistent_vector(
|
|
242
|
+
matrix="technosphere",
|
|
243
|
+
name="my-process",
|
|
244
|
+
indices_array=np.array([(1, 4)], dtype=INDICES_DTYPE),
|
|
245
|
+
data_array=np.array([100.0]),
|
|
246
|
+
params_array=np.array([25.0, 1.013]),
|
|
247
|
+
param_labels=["temperature", "pressure"],
|
|
248
|
+
param_label_schema=StringLabelSchema(),
|
|
249
|
+
)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### Structured labels with a schema
|
|
253
|
+
|
|
254
|
+
Use `ParamLabelSchema` and `ParamLabelField` when labels are structured objects, and pass a `param_label_schema` to validate each label at write time:
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
import numpy as np
|
|
258
|
+
from bw_processing import (
|
|
259
|
+
create_datapackage,
|
|
260
|
+
INDICES_DTYPE,
|
|
261
|
+
ParamLabelField,
|
|
262
|
+
ParamLabelSchema,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
dp = create_datapackage()
|
|
266
|
+
|
|
267
|
+
schema = ParamLabelSchema(
|
|
268
|
+
fields=[
|
|
269
|
+
ParamLabelField(name="name", type="string"),
|
|
270
|
+
ParamLabelField(name="database", type="string"),
|
|
271
|
+
ParamLabelField(name="year", type="integer", required=False),
|
|
272
|
+
],
|
|
273
|
+
description="Brightway activity reference",
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
param_labels = [
|
|
277
|
+
{"name": "electricity", "database": "ecoinvent", "year": 2020},
|
|
278
|
+
{"name": "heat", "database": "ecoinvent"},
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
dp.add_persistent_array(
|
|
282
|
+
matrix="technosphere",
|
|
283
|
+
name="sa-run",
|
|
284
|
+
indices_array=np.array([(1, 4), (2, 5)], dtype=INDICES_DTYPE),
|
|
285
|
+
data_array=np.array([[10.0, 20.0, 30.0], # 2 exchanges × 3 scenarios
|
|
286
|
+
[40.0, 50.0, 60.0]]),
|
|
287
|
+
params_array=np.array([[25.0, 30.0, 35.0], # temperature: 2 params × 3 scenarios
|
|
288
|
+
[1.0, 1.1, 1.2]]),
|
|
289
|
+
param_labels=param_labels,
|
|
290
|
+
param_label_schema=schema, # validates every label against the JSON Schema on write
|
|
291
|
+
)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`ParamLabelField.type` accepts the standard JSON Schema type names: `"string"`, `"integer"`, `"number"`, `"boolean"`. For plain-string labels you can also pass `param_label_schema=StringLabelSchema()` to make the schema explicit.
|
|
295
|
+
|
|
296
|
+
#### Retrieving params and labels
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
params_data, _ = dp.get_resource("sa-run.params") # numpy array
|
|
300
|
+
labels_data, _ = dp.get_resource("sa-run.param_labels") # {"schema": ..., "values": [...]}
|
|
301
|
+
|
|
302
|
+
# Reconstruct the schema dataclass from the stored JSON Schema:
|
|
303
|
+
from bw_processing import schema_from_json_schema
|
|
304
|
+
schema = schema_from_json_schema(labels_data["schema"])
|
|
305
|
+
# → ParamLabelSchema(fields=[...])
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### Dependency
|
|
309
|
+
|
|
310
|
+
`params_array` validation uses the [`jsonschema`](https://python-jsonschema.readthedocs.io/) library, which is a required dependency of `bw_processing`.
|
|
311
|
+
|
|
312
|
+
### NaN as a sentinel value
|
|
313
|
+
|
|
314
|
+
A `NaN` value in a data vector or array is treated by `matrix_utils` as **"no data insertion"** — that element is skipped when the matrix is built or rebuilt, leaving the corresponding matrix cell at whatever value was written by an earlier package. This convention makes it straightforward to define scenario or override packages: set an element to `NaN` to inherit the base value, or to a real number to override it.
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
import numpy as np
|
|
318
|
+
from bw_processing import create_datapackage, INDICES_DTYPE
|
|
319
|
+
|
|
320
|
+
# Base package — sets matrix[0,0]=5 and matrix[1,1]=7
|
|
321
|
+
dp_base = create_datapackage()
|
|
322
|
+
dp_base.add_persistent_vector(
|
|
323
|
+
matrix="foo",
|
|
324
|
+
name="base",
|
|
325
|
+
indices_array=np.array([(0, 0), (1, 1)], dtype=INDICES_DTYPE),
|
|
326
|
+
data_array=np.array([5.0, 7.0]),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Scenario package — overrides matrix[1,1] but leaves matrix[0,0] untouched
|
|
330
|
+
dp_scenario = create_datapackage()
|
|
331
|
+
dp_scenario.add_persistent_vector(
|
|
332
|
+
matrix="foo",
|
|
333
|
+
name="scenario",
|
|
334
|
+
indices_array=np.array([(0, 0), (1, 1)], dtype=INDICES_DTYPE),
|
|
335
|
+
data_array=np.array([np.nan, 99.0]),
|
|
336
|
+
)
|
|
337
|
+
# When both packages are passed to MappedMatrix:
|
|
338
|
+
# matrix[0,0] == 5.0 (NaN in scenario → base value preserved)
|
|
339
|
+
# matrix[1,1] == 99.0 (non-NaN in scenario → override applied)
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Note that `NaN` skipping is implemented in `matrix_utils`, not in `bw_processing` itself. `bw_processing` stores and retrieves the `NaN` values faithfully; the skip logic runs at matrix construction time.
|
|
343
|
+
|
|
191
344
|
### Policies
|
|
192
345
|
|
|
193
346
|
Data package policies define how the data should be used. Policies apply to the entire data package; you may wish to adjust what is stored in which data packages to get the effect you desire.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
__all__ = (
|
|
2
2
|
"__version__",
|
|
3
|
+
"AnyLabelSchema",
|
|
3
4
|
"as_unique_attributes",
|
|
4
5
|
"as_unique_attributes_dataframe",
|
|
5
6
|
"clean_datapackage_name",
|
|
@@ -16,24 +17,36 @@ __all__ = (
|
|
|
16
17
|
"generic_zipfile_filesystem",
|
|
17
18
|
"INDICES_DTYPE",
|
|
18
19
|
"load_datapackage",
|
|
20
|
+
"ArrayEntry",
|
|
19
21
|
"MatrixEntry",
|
|
20
22
|
"MatrixName",
|
|
21
23
|
"MatrixSerializeFormat",
|
|
22
24
|
"md5",
|
|
23
25
|
"merge_datapackages_with_mask",
|
|
26
|
+
"ParamLabelField",
|
|
27
|
+
"ParamLabelSchema",
|
|
24
28
|
"reindex",
|
|
25
29
|
"reset_index",
|
|
26
30
|
"safe_filename",
|
|
31
|
+
"schema_from_json_schema",
|
|
27
32
|
"simple_graph",
|
|
33
|
+
"StringLabelSchema",
|
|
28
34
|
"UNCERTAINTY_DTYPE",
|
|
29
35
|
"UndefinedInterface",
|
|
30
36
|
)
|
|
31
37
|
|
|
32
|
-
__version__ = "1.
|
|
38
|
+
__version__ = "1.5"
|
|
33
39
|
|
|
34
40
|
|
|
35
41
|
from bw_processing.array_creation import create_array, create_structured_array
|
|
36
42
|
from bw_processing.constants import DEFAULT_LICENSES, INDICES_DTYPE, UNCERTAINTY_DTYPE, MatrixSerializeFormat
|
|
43
|
+
from bw_processing.param_labels import (
|
|
44
|
+
AnyLabelSchema,
|
|
45
|
+
ParamLabelField,
|
|
46
|
+
ParamLabelSchema,
|
|
47
|
+
StringLabelSchema,
|
|
48
|
+
schema_from_json_schema,
|
|
49
|
+
)
|
|
37
50
|
from bw_processing.datapackage import (
|
|
38
51
|
Datapackage,
|
|
39
52
|
DatapackageBase,
|
|
@@ -46,7 +59,7 @@ from bw_processing.examples import examples_dir
|
|
|
46
59
|
from bw_processing.filesystem import clean_datapackage_name, md5, safe_filename
|
|
47
60
|
from bw_processing.indexing import reindex, reset_index
|
|
48
61
|
from bw_processing.io_helpers import generic_directory_filesystem, generic_zipfile_filesystem
|
|
49
|
-
from bw_processing.matrix_entry import MatrixEntry, MatrixName, create_datapackage_from_entries
|
|
62
|
+
from bw_processing.matrix_entry import ArrayEntry, MatrixEntry, MatrixName, create_datapackage_from_entries
|
|
50
63
|
from bw_processing.merging import merge_datapackages_with_mask
|
|
51
64
|
from bw_processing.proxies import UndefinedInterface
|
|
52
65
|
from bw_processing.unique_fields import as_unique_attributes, as_unique_attributes_dataframe
|
|
@@ -21,46 +21,45 @@ def chunked(iterable, chunk_size):
|
|
|
21
21
|
return iter(lambda: list(itertools.islice(iterable, chunk_size)), [])
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
"""Create a numpy
|
|
24
|
+
def create_chunked(iterable, dtype, ncols=None, bucket_size=500):
|
|
25
|
+
"""Create a numpy array from an iterable of indeterminate length.
|
|
26
|
+
|
|
27
|
+
Needed when we can't determine the length of the iterable ahead of time
|
|
28
|
+
(e.g. for a generator or a database cursor), so can't create the complete
|
|
29
|
+
array in memory in one step.
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
Creates a list of arrays with ``bucket_size`` rows until ``iterable`` is
|
|
32
|
+
exhausted, then concatenates them along axis 0.
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
Pass ``ncols`` for a plain 2D array; omit it for a 1D structured array.
|
|
30
35
|
|
|
31
36
|
Args:
|
|
32
37
|
iterable: Iterable of data used to populate the array.
|
|
33
|
-
dtype: Numpy dtype of the created array
|
|
34
|
-
|
|
38
|
+
dtype: Numpy dtype of the created array.
|
|
39
|
+
ncols: Number of columns; if None, a 1D structured array is created.
|
|
35
40
|
bucket_size: Number of rows in each intermediate array.
|
|
36
41
|
|
|
37
|
-
Returns
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
Returns:
|
|
43
|
+
The created array. Returns a zero-length array if ``iterable`` has no data.
|
|
40
44
|
"""
|
|
45
|
+
bucket_shape = (bucket_size,) if ncols is None else (bucket_size, ncols)
|
|
46
|
+
empty_shape = (0,) if ncols is None else (0, ncols)
|
|
41
47
|
arrays = []
|
|
42
|
-
array = np.zeros(
|
|
43
|
-
|
|
48
|
+
array = np.zeros(bucket_shape, dtype=dtype)
|
|
44
49
|
for chunk in chunked(iterable, bucket_size):
|
|
45
50
|
for i, row in enumerate(chunk):
|
|
46
51
|
array[i] = row
|
|
47
52
|
if i < bucket_size - 1:
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
# .copy() releases the oversized bucket buffer immediately rather
|
|
54
|
+
# than keeping it alive as a view until the final concatenation.
|
|
55
|
+
arrays.append(array[: i + 1].copy())
|
|
50
56
|
else:
|
|
51
57
|
arrays.append(array)
|
|
52
|
-
array = np.zeros(
|
|
53
|
-
|
|
54
|
-
# Empty iterable - create zero-length array
|
|
58
|
+
array = np.zeros(bucket_shape, dtype=dtype)
|
|
59
|
+
# Empty iterable - create zero-length array.
|
|
55
60
|
# Needed because we return iterators for SQL databases
|
|
56
|
-
# but don't know if e.g.
|
|
57
|
-
|
|
58
|
-
if arrays:
|
|
59
|
-
array = np.hstack(arrays)
|
|
60
|
-
else:
|
|
61
|
-
array = np.zeros(0, dtype=dtype)
|
|
62
|
-
|
|
63
|
-
return array
|
|
61
|
+
# but don't know if e.g. sometimes a database has no biosphere exchanges.
|
|
62
|
+
return np.concatenate(arrays, axis=0) if arrays else np.zeros(empty_shape, dtype=dtype)
|
|
64
63
|
|
|
65
64
|
|
|
66
65
|
def create_structured_array(iterable, dtype, nrows=None, sort=False, sort_fields=None):
|
|
@@ -80,7 +79,7 @@ def create_structured_array(iterable, dtype, nrows=None, sort=False, sort_fields
|
|
|
80
79
|
array[i] = tuple(row)
|
|
81
80
|
|
|
82
81
|
else:
|
|
83
|
-
array =
|
|
82
|
+
array = create_chunked(iterable, dtype)
|
|
84
83
|
|
|
85
84
|
if sort:
|
|
86
85
|
sort_fields = sort_fields or ()
|
|
@@ -93,44 +92,6 @@ def create_structured_array(iterable, dtype, nrows=None, sort=False, sort_fields
|
|
|
93
92
|
return array
|
|
94
93
|
|
|
95
94
|
|
|
96
|
-
def create_chunked_array(iterable, ncols, dtype=np.float32, bucket_size=500):
|
|
97
|
-
"""Create a numpy array from an iterable of indeterminate length.
|
|
98
|
-
|
|
99
|
-
Needed when we can't determine the length of the iterable ahead of time (e.g. for a generator or a database cursor), so can't create the complete array in memory in on step
|
|
100
|
-
|
|
101
|
-
Creates a list of arrays with ``bucket_size`` rows until ``iterable`` is exhausted, then concatenates them.
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
iterable: Iterable of data used to populate the array.
|
|
105
|
-
ncols: Number of columns in the created array.
|
|
106
|
-
dtype: Numpy dtype of the created array
|
|
107
|
-
bucket_size: Number of rows in each intermediate array.
|
|
108
|
-
|
|
109
|
-
Returns:.
|
|
110
|
-
Returns the created array. Will return a zero-length array if ``iterable`` has no data.
|
|
111
|
-
|
|
112
|
-
"""
|
|
113
|
-
arrays = []
|
|
114
|
-
array = np.zeros((bucket_size, ncols), dtype=dtype)
|
|
115
|
-
|
|
116
|
-
for chunk in chunked(iterable, bucket_size):
|
|
117
|
-
for i, row in enumerate(chunk):
|
|
118
|
-
array[i, :] = row
|
|
119
|
-
if i < bucket_size - 1:
|
|
120
|
-
array = array[: i + 1, :]
|
|
121
|
-
arrays.append(array)
|
|
122
|
-
else:
|
|
123
|
-
arrays.append(array)
|
|
124
|
-
array = np.zeros((bucket_size, ncols), dtype=dtype)
|
|
125
|
-
|
|
126
|
-
if arrays:
|
|
127
|
-
array = np.hstack(arrays)
|
|
128
|
-
else:
|
|
129
|
-
array = np.zeros((0, ncols), dtype=dtype)
|
|
130
|
-
|
|
131
|
-
return array
|
|
132
|
-
|
|
133
|
-
|
|
134
95
|
def create_array(iterable, nrows=None, dtype=np.float32):
|
|
135
96
|
"""Create a numpy array data ``iterable``. Returns a filepath of a created file (if ``filepath`` is provided, or the array.
|
|
136
97
|
|
|
@@ -152,7 +113,10 @@ def create_array(iterable, nrows=None, dtype=np.float32):
|
|
|
152
113
|
array[i, :] = tuple(row)
|
|
153
114
|
|
|
154
115
|
else:
|
|
155
|
-
|
|
156
|
-
|
|
116
|
+
try:
|
|
117
|
+
ncols, data = get_ncols(iterable)
|
|
118
|
+
except StopIteration:
|
|
119
|
+
return np.zeros((0, 0), dtype=dtype)
|
|
120
|
+
array = create_chunked(data, dtype, ncols=ncols)
|
|
157
121
|
|
|
158
122
|
return array
|