axsdb 0.0.2__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.
axsdb/error.py ADDED
@@ -0,0 +1,200 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import warnings
5
+ from collections.abc import Mapping
6
+
7
+ import attrs
8
+
9
+ # ------------------------------------------------------------------------------
10
+ # Exceptions
11
+ # ------------------------------------------------------------------------------
12
+
13
+
14
+ class DataError(Exception):
15
+ """Raised when encountering issues with data."""
16
+
17
+ pass
18
+
19
+
20
+ class InterpolationError(Exception):
21
+ """Raised when encountering errors during interpolation."""
22
+
23
+ pass
24
+
25
+
26
+ # ------------------------------------------------------------------------------
27
+ # Error handling components
28
+ # ------------------------------------------------------------------------------
29
+
30
+
31
+ class ErrorHandlingAction(enum.Enum):
32
+ """
33
+ Error handling action descriptors.
34
+ """
35
+
36
+ IGNORE = "ignore" #: Ignore the error.
37
+ RAISE = "raise" #: Raise the error.
38
+ WARN = "warn" #: Emit a warning.
39
+
40
+
41
+ @attrs.define
42
+ class ErrorHandlingPolicy:
43
+ """
44
+ Error handling policy.
45
+
46
+ Parameters
47
+ ----------
48
+ missing : ErrorHandlingAction
49
+ Action to perform when a variable is missing.
50
+
51
+ scalar : ErrorHandlingAction
52
+ Action to perform when a dimension is scalar.
53
+
54
+ bounds : ErrorHandlingAction
55
+ Action to perform when an off-bounds query is made.
56
+ """
57
+
58
+ missing: ErrorHandlingAction
59
+ scalar: ErrorHandlingAction
60
+ bounds: ErrorHandlingAction
61
+
62
+ @classmethod
63
+ def convert(cls, value):
64
+ """
65
+ Convert a value to an :class:`.ErrorHandlingPolicy`.
66
+
67
+ Parameters
68
+ ----------
69
+ value
70
+ Value to convert. Dictionaries values are tentatively converted to
71
+ :class:`.ErrorHandlingAction`, then passed as keyword arguments to
72
+ the constructor.
73
+
74
+ Returns
75
+ -------
76
+ ErrorHandlingPolicy
77
+ """
78
+ if isinstance(value, Mapping):
79
+ kwargs = {k: ErrorHandlingAction(v) for k, v in value.items()}
80
+ return cls(**kwargs)
81
+ else:
82
+ return value
83
+
84
+
85
+ @attrs.define
86
+ class ErrorHandlingConfiguration:
87
+ """
88
+ Error handling configuration.
89
+
90
+ Parameters
91
+ ----------
92
+ x : ErrorHandlingPolicy
93
+ Error handling policy for species concentrations.
94
+
95
+ p : ErrorHandlingPolicy
96
+ Error handling policy for pressure.
97
+
98
+ t : ErrorHandlingPolicy
99
+ Error handling policy for temperature.
100
+ """
101
+
102
+ x: ErrorHandlingPolicy = attrs.field(converter=ErrorHandlingPolicy.convert)
103
+ p: ErrorHandlingPolicy = attrs.field(converter=ErrorHandlingPolicy.convert)
104
+ t: ErrorHandlingPolicy = attrs.field(converter=ErrorHandlingPolicy.convert)
105
+
106
+ @classmethod
107
+ def convert(cls, value):
108
+ """
109
+ Convert a value to an :class:`.ErrorHandlingConfiguration`.
110
+
111
+ Parameters
112
+ ----------
113
+ value
114
+ Value to convert. Dictionaries values are passed as keyword arguments
115
+ to the constructor.
116
+
117
+ Returns
118
+ -------
119
+ ErrorHandlingConfiguration
120
+ """
121
+ if isinstance(value, Mapping):
122
+ return cls(**value)
123
+ else:
124
+ return value
125
+
126
+
127
+ def handle_error(error: InterpolationError, action: ErrorHandlingAction):
128
+ """
129
+ Apply an error handling policy.
130
+
131
+ Parameters
132
+ ----------
133
+ error : .InterpolationError
134
+ The error that is handled.
135
+
136
+ action : ErrorHandlingAction
137
+ If ``IGNORE``, do nothing; if ``WARN``, emit a warning; if ``RAISE``,
138
+ raise the error.
139
+ """
140
+ if action is ErrorHandlingAction.IGNORE:
141
+ return
142
+
143
+ if action is ErrorHandlingAction.WARN:
144
+ warnings.warn(str(error), UserWarning)
145
+ return
146
+
147
+ if action is ErrorHandlingAction.RAISE:
148
+ raise error
149
+
150
+ raise NotImplementedError
151
+
152
+
153
+ #: Global default error handling configuration
154
+ _DEFAULT_ERROR_HANDLING_CONFIG: ErrorHandlingConfiguration | None = None
155
+
156
+
157
+ def set_error_handling_config(value: Mapping | ErrorHandlingConfiguration) -> None:
158
+ """
159
+ Set the global default error handling configuration.
160
+
161
+ Parameters
162
+ ----------
163
+ value : Mapping | ErrorHandlingConfiguration
164
+ Error handling configuration.
165
+
166
+ Raises
167
+ ------
168
+ ValueError
169
+ If ``value`` cannot be converted to an :class:`.ErrorHandlingConfiguration`.
170
+ """
171
+ global _DEFAULT_ERROR_HANDLING_CONFIG
172
+ value = ErrorHandlingConfiguration.convert(value)
173
+ if not isinstance(value, ErrorHandlingConfiguration):
174
+ raise ValueError("could not convert value to ErrorHandlingConfiguration")
175
+ _DEFAULT_ERROR_HANDLING_CONFIG = value
176
+
177
+
178
+ def get_error_handling_config() -> ErrorHandlingConfiguration:
179
+ """
180
+ Retrieve the current global default error handling configuration.
181
+
182
+ Returns
183
+ -------
184
+ ErrorHandlingConfiguration
185
+ """
186
+ global _DEFAULT_ERROR_HANDLING_CONFIG
187
+ if _DEFAULT_ERROR_HANDLING_CONFIG is None: # No config yet: assign a default
188
+ set_error_handling_config(
189
+ {
190
+ # This default configuration ignores bound errors on pressure and temperature
191
+ # variables because this usually occurs at high altitude, where the absorption
192
+ # coefficient is very low and can be safely forced to 0.
193
+ "p": {"missing": "raise", "scalar": "raise", "bounds": "ignore"},
194
+ "t": {"missing": "raise", "scalar": "raise", "bounds": "ignore"},
195
+ # Ignore missing molecule coordinates, raise on bound error.
196
+ "x": {"missing": "ignore", "scalar": "ignore", "bounds": "raise"},
197
+ }
198
+ )
199
+
200
+ return _DEFAULT_ERROR_HANDLING_CONFIG
axsdb/factory.py ADDED
@@ -0,0 +1,135 @@
1
+ from __future__ import annotations
2
+ from pathlib import Path
3
+
4
+ import attrs
5
+ from typing import Callable, Type, TYPE_CHECKING, Any
6
+
7
+ if TYPE_CHECKING:
8
+ from axsdb import AbsorptionDatabase
9
+
10
+ AbsorptionDatabaseT = Type[AbsorptionDatabase]
11
+
12
+
13
+ @attrs.define
14
+ class RegistryEntry:
15
+ name: str = attrs.field()
16
+ cls: AbsorptionDatabaseT = attrs.field(repr=False)
17
+ _path: Path | Callable = attrs.field(repr=False)
18
+ kwargs: dict[str, Any] = attrs.field(repr=False, factory=dict)
19
+
20
+ def path(self):
21
+ return self._path() if callable(self._path) else self._path
22
+
23
+
24
+ @attrs.define
25
+ class AbsorptionDatabaseFactory:
26
+ """
27
+ This factory instantiates :class:`.AbsorptionDatabase` subclasses given a
28
+ name. Internally, a registry maps registered names to a matching database
29
+ type, a path where its data is located, and default loading options.
30
+
31
+ Examples
32
+ --------
33
+ Initialize a factory instance:
34
+
35
+ >>> factory = AbsorptionDatabaseFactory()
36
+
37
+ Register a new database:
38
+
39
+ >>> factory.register(
40
+ ... name="nanockd", cls=CKDAbsorptionDatabase, path="~/Downloads/nanockd"
41
+ ... )
42
+
43
+ Instantiate a database:
44
+
45
+ >>> factory.create("nanockd")
46
+ """
47
+
48
+ _registry: dict[str, RegistryEntry] = attrs.field(factory=dict, init=False)
49
+
50
+ def register(
51
+ self,
52
+ name: str,
53
+ cls: AbsorptionDatabaseT,
54
+ path: Path | Callable,
55
+ kwargs: dict[str, Any] | None = None,
56
+ ) -> None:
57
+ """
58
+ Register a new database to the factory.
59
+
60
+ Parameters
61
+ ----------
62
+ name : str
63
+ A unique identifier for this entry.
64
+
65
+ cls : type
66
+ The matching database type.
67
+
68
+ path : path-like or callable
69
+ The path to the directory where database data are located. If the
70
+ name is dynamic (*e.g.* resolved by Eradiate's file resolver), a
71
+ callable with signature ``f() -> Path | str`` can be passed.
72
+
73
+ kwargs : dict, optional
74
+ Default loading options passed to the constructor when instantiating
75
+ this database.
76
+
77
+ Examples
78
+ --------
79
+ Simplest registration pattern:
80
+
81
+ >>> factory.register(
82
+ ... name="nanomono", cls=MonoAbsorptionDatabase, path="~/Data/nanomono"
83
+ ... )
84
+
85
+ Get path dynamically from a callable:
86
+
87
+ >>> def path():
88
+ ... return "~/Data/nanomono"
89
+ >>> factory.register(
90
+ ... name="nanomono",
91
+ ... cls=MonoAbsorptionDatabase,
92
+ ... path=path,
93
+ ... )
94
+
95
+ Add default keyword arguments:
96
+
97
+ >>> factory.register(
98
+ ... name="nanomono",
99
+ ... cls=MonoAbsorptionDatabase,
100
+ ... path="~/Data/nanomono",
101
+ ... kwargs={"lazy": True},
102
+ ... )
103
+
104
+ """
105
+ if kwargs is None:
106
+ kwargs = {}
107
+ self._registry[name] = RegistryEntry(
108
+ name=name, cls=cls, path=path, kwargs=kwargs
109
+ )
110
+
111
+ def create(self, name: str, **kwargs) -> AbsorptionDatabase:
112
+ """
113
+ Instantiate a database given its name using the
114
+ :meth:`~.AbsorptionDatabase.from_directory` constructor.
115
+
116
+ Parameters
117
+ ----------
118
+ name : str
119
+ Name of a registered databased.
120
+
121
+ kwargs
122
+ Optional keyword arguments passed to the database constructor. These
123
+ settings will override registered defaults.
124
+
125
+ Returns
126
+ -------
127
+ AbsorptionDatabase
128
+ Created database instance.
129
+ """
130
+ entry = self._registry[name]
131
+ cls = entry.cls
132
+ path = entry.path()
133
+ kwargs = {**entry.kwargs, **kwargs}
134
+
135
+ return cls.from_directory(path, **kwargs)
axsdb/py.typed ADDED
File without changes
axsdb/typing.py ADDED
@@ -0,0 +1,4 @@
1
+ import os
2
+ from typing import TypeVar
3
+
4
+ PathLike = TypeVar("PathLike", str, bytes, os.PathLike)
axsdb/units.py ADDED
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+ import xarray as xr
5
+ import pint
6
+
7
+ #: An alias to Pint's application registry.
8
+ ureg = pint.get_application_registry()
9
+
10
+
11
+ def ensure_units(
12
+ value: Any, default_units: pint.Unit, convert: bool = False
13
+ ) -> pint.Quantity:
14
+ """
15
+ Ensure that a value is wrapped in a Pint quantity container.
16
+
17
+ Parameters
18
+ ----------
19
+ value
20
+ Checked value.
21
+
22
+ default_units : pint.Unit
23
+ Units to use to initialize the :class:`pint.Quantity` if ``value`` is
24
+ not a :class:`pint.Quantity`.
25
+
26
+ convert : bool, default: False
27
+ If ``True``, ``value`` will also be converted to ``default_units`` if it is a
28
+ :class:`pint.Quantity`.
29
+
30
+ Returns
31
+ -------
32
+ Converted ``value``.
33
+ """
34
+ if isinstance(value, pint.Quantity):
35
+ if convert:
36
+ return value.to(default_units)
37
+ else:
38
+ return value
39
+ else:
40
+ return value * default_units
41
+
42
+
43
+ def xarray_to_quantity(da: xr.DataArray) -> pint.Quantity:
44
+ """
45
+ Converts a :class:`~xarray.DataArray` to a :class:`~pint.Quantity`.
46
+ The array's ``attrs`` metadata mapping must contain a ``units`` field.
47
+
48
+ Parameters
49
+ ----------
50
+ da : DataArray
51
+ :class:`~xarray.DataArray` instance which will be converted.
52
+
53
+ Returns
54
+ -------
55
+ quantity
56
+ The corresponding Pint quantity.
57
+
58
+ Raises
59
+ ------
60
+ ValueError
61
+ If array attributes do not contain a ``units`` field.
62
+
63
+ Notes
64
+ -----
65
+ This function can also be used on coordinate variables.
66
+ """
67
+ try:
68
+ units = da.attrs["units"]
69
+ except KeyError as e:
70
+ raise ValueError("this DataArray has no 'units' attribute field") from e
71
+
72
+ return ureg.Quantity(da.values, units)
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: axsdb
3
+ Version: 0.0.2
4
+ Summary: An absorption database reader for the Eradiate radiative transfer model.
5
+ Author-email: Vincent Leroy <vincent.leroy@rayference.eu>
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: attrs
8
+ Requires-Dist: cachetools
9
+ Requires-Dist: netcdf4
10
+ Requires-Dist: pint
11
+ Requires-Dist: scipy
12
+ Requires-Dist: typer
13
+ Requires-Dist: xarray
14
+ Description-Content-Type: text/markdown
15
+
16
+ # AxsDB — The Eradiate Absorption Cross-section Database Interface
17
+
18
+ [![PyPI version](https://img.shields.io/pypi/v/axsdb?color=blue)](https://pypi.org/project/axsdb)
19
+ [![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/eradiate/axsdb/ci.yml?branch=main)](https://github.com/eradiate/axsdb/actions/workflows/ci.yml)
20
+ [![Documentation Status](https://img.shields.io/readthedocs/axsdb)](https://axsdb.readthedocs.io)
21
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
22
+ [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
23
+
24
+ This library provides an interface to read and
25
+ query [Eradiate](https://eradiate.eu)'s absorption databases.
26
+
27
+ ## License
28
+
29
+ The Eradiate Absorption Database Reader is distributed under the terms of the
30
+ [GNU Lesser General Public License v3.0](https://choosealicense.com/licenses/lgpl-3.0/).
@@ -0,0 +1,13 @@
1
+ axsdb/__init__.py,sha256=9-EMYK0zJ6dTDZxRvqxpoAwZYER6xHjhNRROhCDPqEs,666
2
+ axsdb/_version.py,sha256=zpfzURzIlh9ajjpvYv4uvkfd404RwJGzy_drTalmMm0,163
3
+ axsdb/cli.py,sha256=UUtmlrS1Y0y6TrXDZtjDY2xBeqPlH_RRSDdRQjv9_b4,1689
4
+ axsdb/core.py,sha256=_FXP-gio8yTGvYEuiOjWCizi6OV8r9WdI0UNo9Kqk3U,31343
5
+ axsdb/error.py,sha256=dmdurgFPkZxyutyWnYb85ddt7TV3jFw1etLln14boPQ,5621
6
+ axsdb/factory.py,sha256=Pr8kxQ51hgQY8wBCuMy7sarHT5ORmaRYhFWiu5styhk,3710
7
+ axsdb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ axsdb/typing.py,sha256=Aol4ouIrfrUs-HBBgblW6Iv-xJDSpFe1RxWCQj8QDFs,94
9
+ axsdb/units.py,sha256=XxbcC-dXiXd8ZkB3cBtd3NTT8zSEd1g8FGQUr5-nrhQ,1762
10
+ axsdb-0.0.2.dist-info/METADATA,sha256=CN4Ek0lbULDF88DFdZnXDni-oquWTsGdAkU6L_TQ-JQ,1457
11
+ axsdb-0.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
+ axsdb-0.0.2.dist-info/entry_points.txt,sha256=4OnXeexRQs2Bl3T4F_jwijW6hPb6vOsIcQAECTsnHUE,41
13
+ axsdb-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ axsdb = axsdb.cli:main