grid-process 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,209 @@
1
+ # Copyright (c) 2026 MeteoSwiss, contributors listed in AUTHORS
2
+ #
3
+ # Distributed under the terms of the BSD 3-Clause License.
4
+ #
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
+
7
+ """
8
+ Module to control, parse and convert units
9
+ """
10
+
11
+ import logging
12
+ import re
13
+ from functools import partial
14
+ from collections.abc import Sequence, Hashable
15
+
16
+ import numpy as np
17
+ import xarray as xr
18
+ from pint import UnitRegistry
19
+
20
+ from grid_process.helpers import make_accessor
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class UnitsError(Exception):
26
+ """
27
+ Class to raise units errors
28
+ """
29
+
30
+
31
+ # Register xarray accessor so module functions can also be called as methods
32
+ # on xr.DataArray objects
33
+ @xr.register_dataarray_accessor("units_conv")
34
+ class DaUnits:
35
+ """
36
+ Xarray accessors class for units utility functions on DataArray
37
+ """
38
+
39
+ def __init__(self, da: xr.DataArray):
40
+ self._data = da
41
+
42
+
43
+ @xr.register_dataset_accessor("units_conv")
44
+ class DsUnits:
45
+ """
46
+ Xarray accessors class for units utility functions on Dataset
47
+ """
48
+
49
+ def __init__(self, ds: xr.Dataset):
50
+ self._data = ds
51
+
52
+
53
+ dataarray_accessor = partial(make_accessor, accessor_class=DaUnits)
54
+ dataset_accessor = partial(make_accessor, accessor_class=DsUnits)
55
+
56
+ ureg: UnitRegistry = UnitRegistry(
57
+ autoconvert_offset_to_baseunit=True,
58
+ preprocessors=[
59
+ partial(
60
+ re.sub,
61
+ r"(?<=[A-Za-z])(?![A-Za-z])(?<![0-9\-][eE])(?<![0-9\-])(?=[0-9\-])",
62
+ "**",
63
+ ),
64
+ partial(re.sub, r"\b[h]\b", "hour"), # avoid parsing of Plank constant for h
65
+ ],
66
+ )
67
+
68
+
69
+ @dataarray_accessor
70
+ def da_to(da: xr.DataArray, dst_units: str) -> xr.DataArray:
71
+ """
72
+ Convert units in a xarray.DataArray.
73
+
74
+ Parameters
75
+ ----------
76
+ da : xarray.DataArray
77
+ with attribute "units"
78
+ dst_units : str
79
+ destination units
80
+
81
+ Returns
82
+ -------
83
+ da_out : xarray.DataArray
84
+ contains the data array with converted units
85
+
86
+ Raises
87
+ ------
88
+ pint.errors.DimensionalityError
89
+ if the conversion is not possible
90
+ """
91
+
92
+ da = da.copy()
93
+ src_units_parsed = ureg.parse_units(da.units)
94
+ dst_units_parsed = ureg.parse_units(dst_units)
95
+
96
+ if src_units_parsed == dst_units_parsed:
97
+ pass
98
+ else:
99
+ da_quantity = da.values * src_units_parsed
100
+ da.values = da_quantity.to(dst_units_parsed).magnitude
101
+
102
+ da.attrs["units"] = dst_units
103
+ return da
104
+
105
+
106
+ @dataset_accessor
107
+ def ds_to(ds: xr.Dataset, dst_units: str | dict[Hashable, str]) -> xr.Dataset:
108
+ """
109
+ Convert units in a xarray.Dataset.
110
+
111
+ Parameters
112
+ ----------
113
+ ds : xarray.Dataset
114
+ with variable attributes "units"
115
+ dst_units : str or dict
116
+ destination units for each data variable to be converted.
117
+ Variable names not specified in ``dst_units`` are kept in the same units.
118
+ If passed as a single string, all variables in the dataset are converted to the
119
+ same units
120
+
121
+ Returns
122
+ -------
123
+ ds_out : xarray.Dataset
124
+ contains the dataset with converted units
125
+
126
+ Raises
127
+ ------
128
+ pint.errors.DimensionalityError
129
+ if the conversion is not possible
130
+ """
131
+
132
+ if isinstance(dst_units, str):
133
+ dst_units = {var: dst_units for var in ds}
134
+ ds_out = ds.copy()
135
+ for var, dst_unit in dst_units.items():
136
+ ds_out[var] = da_to(ds[var], dst_unit)
137
+ return ds_out
138
+
139
+
140
+ def consistency_check(datasets: Sequence[xr.Dataset], check_vars: bool = True) -> dict[str, dict[str, str]]:
141
+ """
142
+ Checks that the input datasets have same data variables and units. If it is
143
+ the case, returns the 'units' attributes of each data variable otherwise
144
+ raises an error.
145
+
146
+ Parameters
147
+ ----------
148
+ datasets : sequence of xarray.Dataset
149
+ each data variable in the datasets must have a 'units' attributes
150
+ check_vars : bool, optional
151
+ if False, allows datasets to have different variables, but common ones
152
+ must have same units. Default is True
153
+
154
+ Returns
155
+ -------
156
+ out : dict
157
+ contains the 'units' attribute of the data variables in the input
158
+ datasets. In case same units are specified in various format, the
159
+ dominant format is kept if there is one otherwise the format from the
160
+ first dataset in the input sequence
161
+
162
+ Raises
163
+ ------
164
+ UnitsError
165
+ if same variables across the input datasets have different units
166
+ ValueError
167
+ when ``check_vars=True`` and the input datasets do not have exact same
168
+ set of variables
169
+ """
170
+
171
+ vars_attrs = np.array([{varname: da.units for varname, da in ds.data_vars.items()} for ds in datasets])
172
+
173
+ if check_vars:
174
+ if not np.all([dic.keys() == vars_attrs[0].keys() for dic in vars_attrs]):
175
+ raise ValueError("The datasets to combine must have matching variables, which was not the case")
176
+
177
+ unique = {key: {"units": value} for elem in vars_attrs[::-1] for key, value in elem.items()}
178
+ if np.all(
179
+ [
180
+ ureg.parse_units(value) == ureg.parse_units(unique[key]["units"])
181
+ for dic in vars_attrs
182
+ for key, value in dic.items()
183
+ ]
184
+ ):
185
+ return unique
186
+
187
+ raise UnitsError("Same variables among the datasets to combine have different units. Please convert units first")
188
+
189
+
190
+ def units_check(datasets: Sequence[xr.Dataset]) -> None:
191
+ """
192
+ Check that the data variables in the datasets all have same single units.
193
+ If not, it raises an error.
194
+
195
+ Parameters
196
+ ----------
197
+ datasets : sequence of xarray.Dataset
198
+ each data variable in the datasets must have a 'units' attributes
199
+
200
+ Raises
201
+ ------
202
+ UnitsError
203
+ if any variable in any of the input datasets have different units
204
+ """
205
+
206
+ units = [ureg.parse_units(da.units) for ds in datasets for _, da in ds.data_vars.items()]
207
+
208
+ if len(set(units)) != 1:
209
+ raise UnitsError("Variables in the datasets do not all have same single units")
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.4
2
+ Name: grid-process
3
+ Version: 1.0.0
4
+ Summary: Weather forecast oriented Python library to process xarray Datasets
5
+ License-Expression: BSD-3-Clause
6
+ License-File: AUTHORS
7
+ License-File: LICENSE
8
+ Author: Mathieu Schaer
9
+ Author-email: mathieu.schaer@meteoswiss.ch
10
+ Requires-Python: >=3.11,<3.14
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Dist: bottleneck (>=1.4.0)
16
+ Requires-Dist: dask (>=2023.6.1)
17
+ Requires-Dist: geopandas (>=1.0.0)
18
+ Requires-Dist: numpy (>=1.26.0)
19
+ Requires-Dist: pandas (>=2.1.2)
20
+ Requires-Dist: pint (>=0.24.4)
21
+ Requires-Dist: pydantic (>=2.7.0)
22
+ Requires-Dist: pydantic-settings (>=2.2.0)
23
+ Requires-Dist: pyproj (>=3.6.0)
24
+ Requires-Dist: pyyaml (>=6.0)
25
+ Requires-Dist: scipy (>=1.10.1)
26
+ Requires-Dist: xarray (>=2024.6.0)
27
+ Project-URL: Changelog, https://github.com/MeteoSwiss/grid-process/blob/main/CHANGELOG.rst
28
+ Project-URL: Documentation, https://meteoswiss.github.io/grid-process/
29
+ Project-URL: Homepage, https://github.com/MeteoSwiss/grid-process
30
+ Project-URL: Repository, https://github.com/MeteoSwiss/grid-process
31
+ Description-Content-Type: text/x-rst
32
+
33
+ .. image:: https://img.shields.io/pypi/v/grid-process.svg
34
+ :target: https://pypi.org/project/grid-process/
35
+
36
+ .. image:: https://img.shields.io/pypi/pyversions/grid-process.svg
37
+ :target: https://pypi.org/project/grid-process/
38
+
39
+ .. image:: https://img.shields.io/pypi/l/grid-process.svg
40
+ :target: https://pypi.org/project/grid-process/
41
+
42
+ .. image:: https://github.com/MeteoSwiss/grid-process/actions/workflows/CI_test.yaml/badge.svg
43
+ :target: https://github.com/MeteoSwiss/grid-process/actions/workflows/CI_test.yaml
44
+
45
+ .. image:: https://github.com/MeteoSwiss/grid-process/actions/workflows/CI_publish_dev_documentation.yaml/badge.svg
46
+ :target: https://github.com/MeteoSwiss/grid-process/actions/workflows/CI_publish_dev_documentation.yaml
47
+
48
+ ===============
49
+ Getting Started
50
+ ===============
51
+
52
+ Weather forecast oriented Python library to process xarray Datasets.
53
+
54
+ .. warning::
55
+
56
+ **Project status and deprecation outlook.** ``grid-process`` mainly serves the needs of MeteoSwiss operational
57
+ processing. As equivalent functionality becomes available in the
58
+ `ECMWF earthkit ecosystem <https://earthkit.ecmwf.int/>`_, parts of this library, potentially all of it, will be
59
+ gradually deprecated in favour of earthkit. Any deprecation will be announced in the changelog. If you start a new
60
+ project, please consider earthkit first.
61
+
62
+ Features
63
+ --------
64
+
65
+ * Grid to points interpolations routines
66
+ * Temporal interpolation routines
67
+ * Spatial and temporal aggregation routines
68
+ * Ensemble and spatial statistics
69
+ * Configurable data model (dimension, coordinate and variable names) to support many data conventions
70
+ * And much more
71
+
72
+ .. note::
73
+
74
+ ``grid-process`` was originally developed internally at MeteoSwiss as ``gridefix-process`` (versions 1 to 6),
75
+ to process grids from an internal grid database. The first open-source release is ``1.0.0``.
76
+ See `Migrating from gridefix-process`_ if you are upgrading from the internal package.
77
+
78
+ Development Setup with Poetry
79
+ -----------------------------
80
+
81
+ Building the Project
82
+ ''''''''''''''''''''
83
+ .. code-block:: console
84
+
85
+ $ cd grid-process
86
+ $ poetry install
87
+
88
+ Run Tests
89
+ '''''''''
90
+
91
+ .. code-block:: console
92
+
93
+ $ poetry run pytest
94
+
95
+ Run Quality Tools
96
+ '''''''''''''''''
97
+
98
+ .. code-block:: console
99
+
100
+ $ poetry run pylint grid_process
101
+ $ poetry run mypy grid_process
102
+ $ poetry run ruff format
103
+
104
+ Generate Documentation
105
+ ''''''''''''''''''''''
106
+
107
+ .. code-block:: console
108
+
109
+ $ poetry run sphinx-build doc doc/_build
110
+
111
+ Then open the index.html file generated in *grid-process/doc/_build/*.
112
+
113
+ Build wheels
114
+ ''''''''''''
115
+
116
+ .. code-block:: console
117
+
118
+ $ poetry build
119
+
120
+ Using the Library
121
+ -----------------
122
+
123
+ To install grid-process in your project, run this command in your terminal:
124
+
125
+ .. code-block:: console
126
+
127
+ $ poetry add grid-process
128
+
129
+ You can then use the library in your project through
130
+
131
+ .. code-block:: python
132
+
133
+ import grid_process
134
+
135
+ Migrating from gridefix-process
136
+ -------------------------------
137
+
138
+ * Package and import name: ``gridefix-process`` / ``gridefix_process`` became ``grid-process`` / ``grid_process``.
139
+ * Environment variable prefix: ``GRIDEFIX_PROCESS__`` became ``GRID_PROCESS__``
140
+ (e.g. ``GRID_PROCESS__data_model__validtime``).
141
+ * Versioning restarts at ``1.0.0``; the last internal release was ``gridefix-process`` 6.0.1.
142
+
143
+ Release the Project
144
+ -------------------
145
+
146
+ The project follows the **GitOps concept**: releases are triggered whenever a Git TAG is created.
147
+
148
+ The TAG must follow the `semantic version <https://semver.org/>`__ format and `PEP 440 <https://peps.python.org/pep-0440/>`__ , otherwise the release task will fail.
149
+
150
+ Follow these steps to create a new release:
151
+
152
+ * Adapt CHANGELOG.rst with release information
153
+ * Adapt ``doc/_static/switcher_config.json`` adding the new documentation URL for the release
154
+ * Create a new Release in the Github project
155
+
@@ -0,0 +1,23 @@
1
+ grid_process/__init__.py,sha256=Jcp9bagxKMYWbJvnH6av9nez7Rz3eNn4eH8xw7nmrJs,416
2
+ grid_process/aggreg.py,sha256=if_Rw_vWn3cou-1gggNWp_ONA1_DmmLIavoNvN-OWEA,15858
3
+ grid_process/blending.py,sha256=DA-OcnnxVn_RVlezpIIiRyeqeuk7Dtomyn6f4fWqdCQ,7903
4
+ grid_process/config/__init__.py,sha256=Z9eycy3pcWnAMQEEvzU58proP8-QjFuV4C9iIhMvpLk,167
5
+ grid_process/config/grid_process.yaml,sha256=d0MViWtaNcD9Yzf8OwlKFIUghttCaNZLCGTkB-F46DQ,768
6
+ grid_process/config/library_settings.py,sha256=2v_Ac4Lb24JtP9f4hX4LulSJSo81xGLpuNk37hDbUO8,1778
7
+ grid_process/da_utils.py,sha256=Ovv6YOvrqMdJGOhsAiMQRexhIKbPMbf2s1LUr-yuABE,2870
8
+ grid_process/data_model.py,sha256=7bLSz1XqjeEC-muTlvCEvf2XJGEBQcrazts-CRblf5k,18095
9
+ grid_process/ds_utils.py,sha256=AoTk30gelptpmmIluHGiZppeZo53EeEz1AcfP8r3_3I,1690
10
+ grid_process/ens_stats.py,sha256=4K8bGY7nxb3wIZKRFciPTYbkDeKgrb1BLqaEo_nnudE,14305
11
+ grid_process/grid_interp.py,sha256=2JobEgwhkNCTBSkB1-ZJBef_slWzPScNlwN_nH9uamY,75073
12
+ grid_process/grid_interp_utils.py,sha256=taVJu83dIfQ5qmYsYrm0ddSSHdd2kKz62IqeFDKWqqs,8425
13
+ grid_process/helpers.py,sha256=Xh1c9L593n3C4Ix-l4grfXhBCvf3Z502YD8ssWc729E,5399
14
+ grid_process/optimization.py,sha256=Wx_i4OtG4DJ5DPpqBJoHeZtwbrtZIFb2mAppfOlAZfk,1471
15
+ grid_process/sun.py,sha256=ZYfop-NBtzj0_P69gYMGRQzvYF9-JbfLAdgq3-BB62M,17730
16
+ grid_process/time_utils.py,sha256=7izknDQ6jsIemSaW7l6_-BeK1ujIgko51yjPIaLN1Z4,3444
17
+ grid_process/timeseries.py,sha256=HZzVagTq4PAtRd24SlD6gNbhfiTrRLqFMqBr7TZ8ZVs,20636
18
+ grid_process/units_conv.py,sha256=LNlu3Re4ch6k9Ld2fdmepwYMwoxJ8XSTVYQ5GiRoey4,5911
19
+ grid_process-1.0.0.dist-info/METADATA,sha256=JWBygUJSGYllfdL_qECh5Zhbjw5g70LcrgxKl4fUgYA,5086
20
+ grid_process-1.0.0.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
21
+ grid_process-1.0.0.dist-info/licenses/AUTHORS,sha256=fwJyCiS9pTKM48kEGJ0jwgkBa5T6PXpdCeXz-JSpc5g,135
22
+ grid_process-1.0.0.dist-info/licenses/LICENSE,sha256=qfrMXNx_JZjckTrhyQAnw38WA0ZpMendVO7tY8XUsfQ,1544
23
+ grid_process-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.5.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,11 @@
1
+ Development Lead
2
+ ----------------
3
+
4
+ * Mathieu Schaer
5
+
6
+ Contributors
7
+ ------------
8
+
9
+ * Daniele Nerini
10
+ * Adel Imamovic
11
+ * Verena Bessenbacher
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, MeteoSwiss
4
+ Authors: Mathieu Schaer <mathieu.schaer@meteoswiss.ch>
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.