pyBADA 0.1.5__py3-none-any.whl → 0.1.6__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.
pyBADA/utils.py ADDED
@@ -0,0 +1,204 @@
1
+ import numpy as np
2
+ import xarray as xr
3
+ import pandas as pd
4
+ import numbers
5
+ from decimal import Decimal, ROUND_HALF_UP
6
+
7
+
8
+ def _round_scalar(x, dec):
9
+ """Round a single scalar value using half-up rounding."""
10
+ quant = Decimal('1.' + '0'*dec)
11
+ d = Decimal(str(x))
12
+ return float(d.quantize(quant, rounding=ROUND_HALF_UP))
13
+
14
+
15
+ def proper_round(num, dec=0):
16
+ """Forced half-up rounding, vectorized over numpy arrays, pandas, and xarray.
17
+
18
+ :param num: Input scalar, array-like, pandas Series/DataFrame, or xarray DataArray
19
+ :param dec: Number of decimal places
20
+ :returns: Rounded values with half-up rule; preserves infinities.
21
+ """
22
+ # Scalar helper handling infinities
23
+ def _f(v):
24
+ # Preserve infinities
25
+ try:
26
+ if np.isinf(v):
27
+ return v
28
+ except Exception:
29
+ pass
30
+ return _round_scalar(v, dec)
31
+
32
+ # xarray DataArray
33
+ if isinstance(num, xr.DataArray):
34
+ arr = num.data
35
+ rounded = np.vectorize(_f)(arr)
36
+ return xr.DataArray(rounded, coords=num.coords, dims=num.dims)
37
+
38
+ # pandas Series
39
+ if isinstance(num, pd.Series):
40
+ return num.map(_f)
41
+
42
+ # pandas DataFrame
43
+ if isinstance(num, pd.DataFrame):
44
+ return num.applymap(_f)
45
+
46
+ # numpy array
47
+ if isinstance(num, np.ndarray):
48
+ return np.vectorize(_f)(num)
49
+
50
+ # scalar fallback
51
+ return _f(num)
52
+
53
+
54
+ def to_numpy(x):
55
+ """
56
+ Convert xarray.DataArray, pandas.Series/DataFrame, or any array-like
57
+ to a NumPy array of floats.
58
+ """
59
+ if isinstance(x, xr.DataArray):
60
+ return x.values
61
+ if isinstance(x, (pd.Series, pd.DataFrame)):
62
+ return x.to_numpy(copy=False)
63
+ return np.asarray(x, dtype=float)
64
+
65
+
66
+ def _extract(x):
67
+ if isinstance(x, numbers.Real) and not isinstance(x, (np.generic, np.ndarray)):
68
+ return float(x)
69
+ if isinstance(x, xr.DataArray):
70
+ return x.data
71
+ if isinstance(x, (pd.Series, pd.DataFrame)):
72
+ return x.values
73
+ return np.asarray(x, dtype=float)
74
+
75
+ def _broadcast(*arrays):
76
+ """
77
+ Broadcast any number of array-like inputs to a common shape.
78
+ - If *all* inputs are Python scalars, just return them as a tuple.
79
+ - If exactly two inputs, and one is 1-D while the other is N-D (N>1),
80
+ then align the 1-D array to whichever axis of the N-D array has the same length.
81
+ - Otherwise prepend leading singleton dims to match trailing-dims broadcasting.
82
+ """
83
+ # 1) Scalar passthrough
84
+ if all(isinstance(a, numbers.Real) and not isinstance(a, (np.generic, np.ndarray))
85
+ for a in arrays):
86
+ return tuple(arrays)
87
+
88
+ # Convert everything up front
89
+ arrs = [np.asarray(a) for a in arrays]
90
+
91
+ # Special‐case exactly two inputs, one 1-D and one N-D
92
+ if len(arrs) == 2:
93
+ a0, a1 = arrs
94
+ # identify which is 1-D and which is higher-D
95
+ if a0.ndim == 1 and a1.ndim > 1:
96
+ arrs[0] = _align_1d_to_nd(a0, a1)
97
+ return np.broadcast_arrays(arrs[0], a1)
98
+ if a1.ndim == 1 and a0.ndim > 1:
99
+ arrs[1] = _align_1d_to_nd(a1, a0)
100
+ return np.broadcast_arrays(a0, arrs[1])
101
+
102
+ # Fallback: pad all inputs with leading singleton dims
103
+ max_ndim = max(a.ndim for a in arrs)
104
+ padded = [
105
+ a.reshape((1,) * (max_ndim - a.ndim) + a.shape)
106
+ for a in arrs
107
+ ]
108
+ try:
109
+ return np.broadcast_arrays(*padded)
110
+ except ValueError:
111
+ shapes = [a.shape for a in arrs]
112
+ raise ValueError(f"Cannot broadcast input shapes {shapes}")
113
+
114
+ def _align_1d_to_nd(one_d, nd):
115
+ """
116
+ Take one_d of shape (N,) and an array nd of shape (...),
117
+ find axis i where nd.shape[i] == N, and reshape one_d to
118
+ (1,1,...,N,...,1) so it lines up on that axis.
119
+ """
120
+ N = one_d.shape[0]
121
+ for i, dim in enumerate(nd.shape):
122
+ if dim == N:
123
+ # build a shape of length nd.ndim: all 1s except axis i holds N
124
+ new_shape = tuple(1 if j != i else N for j in range(nd.ndim))
125
+ return one_d.reshape(new_shape)
126
+ # no matching dimension found
127
+ raise ValueError(f"Cannot align 1D array of length {N} with ND shape {nd.shape}")
128
+
129
+ # def _broadcast(*arrays):
130
+ # """
131
+ # Broadcast any number of array-like inputs to a common shape.
132
+ # Accepts Python scalars, numpy arrays, pandas Series/DataFrame, xarray DataArray (via utils._extract), and returns numpy arrays broadcasted or scalars.
133
+ # """
134
+
135
+ # If all inputs are Python real scalars, return them unchanged
136
+ # if all(isinstance(a, numbers.Real) and not isinstance(a, (np.generic, np.ndarray)) for a in arrays):
137
+ # return tuple(arrays)
138
+
139
+ # Convert inputs to numpy arrays and broadcast
140
+ # arrs = [np.asarray(a) for a in arrays]
141
+ # try:
142
+ # return np.broadcast_arrays(*arrs)
143
+ # except ValueError:
144
+ # shapes = [a.shape for a in arrs]
145
+ # raise ValueError(f"Cannot broadcast input shapes {shapes}")
146
+
147
+ def _wrap(core, original):
148
+ # 1) Plain Python floats
149
+ if isinstance(original, numbers.Real) and not isinstance(original, (np.generic, np.ndarray)):
150
+ # core might be a 0-d array or numpy scalar
151
+ return float(np.asarray(core).item())
152
+
153
+ # xarray
154
+ if isinstance(original, xr.DataArray):
155
+ return xr.DataArray(
156
+ core,
157
+ coords=original.coords,
158
+ dims=original.dims,
159
+ name=original.name,
160
+ attrs=original.attrs
161
+ )
162
+
163
+ # pandas Series
164
+ if isinstance(original, pd.Series):
165
+ return pd.Series(core, index=original.index, name=original.name)
166
+
167
+ # pandas DataFrame
168
+ if isinstance(original, pd.DataFrame):
169
+ return pd.DataFrame(core, index=original.index, columns=original.columns)
170
+
171
+ # fallback: NumPy arrays/scalars
172
+ return core
173
+
174
+ def _vectorized_wrapper(core_func, *args):
175
+ """Generic vectorized wrapper for functions with N inputs."""
176
+ # Extract raw arrays or scalars
177
+ arrs = [_extract(a) for a in args]
178
+ core = core_func(*arrs)
179
+
180
+ # If *all* inputs were plain Python real numbers, return a Python float
181
+ first = args[0]
182
+ if isinstance(first, numbers.Real) and not isinstance(first, (np.generic, np.ndarray)):
183
+ return float(np.asarray(core).item())
184
+
185
+ # xarray: if every arg was a DataArray, wrap back to DataArray
186
+ if isinstance(first, xr.DataArray) and all(isinstance(a, xr.DataArray) for a in args):
187
+ return xr.DataArray(core, coords=first.coords, dims=first.dims)
188
+
189
+ # pandas Series
190
+ if isinstance(first, pd.Series) and all(isinstance(a, pd.Series) for a in args):
191
+ return pd.Series(core, index=first.index, name=first.name)
192
+
193
+ # pandas DataFrame
194
+ if isinstance(first, pd.DataFrame) and all(isinstance(a, pd.DataFrame) for a in args):
195
+ return pd.DataFrame(core, index=first.index, columns=first.columns)
196
+
197
+ # fallback: NumPy array or scalar (leave as-is)
198
+ return core
199
+
200
+ def checkArgument(argument, **kwargs):
201
+ if kwargs.get(argument) is not None:
202
+ return kwargs.get(argument)
203
+ else:
204
+ raise TypeError("Missing " + argument + " argument")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyBADA
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Aircraft performance modelling, trajectory prediction and optimisation, and visualisation with EUROCONTROL's BADA.
5
5
  Project-URL: Homepage, https://github.com/eurocontrol-bada/pybada
6
6
  Author-email: Henrich Glaser-Opitz <henrich.glaser-opitz@eurocontrol.int>, Antonio Vivace <antonio.vivace@eurocontrol.int>
@@ -10,17 +10,18 @@ License-File: LICENCE.txt
10
10
  Classifier: License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: >=3.12
13
- Requires-Dist: numpy>=2.2.3
14
- Requires-Dist: pandas>=2.2.3
15
- Requires-Dist: scipy>=1.15.2
13
+ Requires-Dist: numpy>=2.3.2
14
+ Requires-Dist: pandas>=2.3.1
15
+ Requires-Dist: scipy>=1.16.1
16
16
  Requires-Dist: simplekml>=1.3.6
17
- Requires-Dist: xlsxwriter>=3.2.2
17
+ Requires-Dist: xarray>=2025.7.1
18
+ Requires-Dist: xlsxwriter>=3.2.5
18
19
  Provides-Extra: dev
19
20
  Requires-Dist: build; extra == 'dev'
20
- Requires-Dist: folium==0.19.5; extra == 'dev'
21
- Requires-Dist: matplotlib==3.10.1; extra == 'dev'
22
- Requires-Dist: pre-commit==4.1.0; extra == 'dev'
23
- Requires-Dist: pytest==8.3.5; extra == 'dev'
21
+ Requires-Dist: folium==0.20.0; extra == 'dev'
22
+ Requires-Dist: matplotlib==3.10.5; extra == 'dev'
23
+ Requires-Dist: pre-commit==4.2.0; extra == 'dev'
24
+ Requires-Dist: pytest==8.4.1; extra == 'dev'
24
25
  Requires-Dist: readthedocs-sphinx-search>=0.3.2; extra == 'dev'
25
26
  Requires-Dist: ruff==0.11.5; extra == 'dev'
26
27
  Requires-Dist: sphinx-gallery==0.19.0; extra == 'dev'
@@ -33,10 +34,10 @@ Description-Content-Type: text/markdown
33
34
 
34
35
  [![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)
35
36
  <a href="https://github.com/eurocontrol-bada/pybada/blob/main/LICENCE.txt"><img alt="License: EUPL" src="https://img.shields.io/badge/license-EUPL-3785D1.svg"></a>
36
- <a href="https://pypi.org/project/pyBADA"><img alt="Released on PyPi" src="https://img.shields.io/pypi/v/pyBADA.svg"></a>
37
- ![Python 3.12](https://img.shields.io/badge/Python-3.12-3776AB.svg?logo=python&logoColor=white)
37
+ <a href="https://pypi.org/project/pyBADA"><img alt="Released on PyPi" src="https://img.shields.io/pypi/v/pyBADA.svg"></a> <img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dw/pybada"> ![Python 3.12](https://img.shields.io/badge/Python-3.12-3776AB.svg?logo=python&logoColor=white)
38
38
  <a href="https://github.com/eurocontrol-bada/pybada"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
39
39
  [![Run unit tests](https://github.com/eurocontrol-bada/pybada/actions/workflows/pytest.yml/badge.svg)](https://github.com/eurocontrol-bada/pybada/actions/workflows/pytest.yml)
40
+ ![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/pyBADA_dev?style=social&label=Follow%20%40pyBADA_dev)
40
41
 
41
42
  This package provides aircraft performance modelling, trajectory prediction and optimisation, and visualisation with [BADA](https://www.eurocontrol.int/model/bada) in Python.
42
43
 
@@ -89,4 +90,5 @@ You won't receive support for it, but you can pass the flag `--ignore-requires-p
89
90
 
90
91
  BADA and pyBADA are developed and maintained by [EUROCONTROL](https://www.eurocontrol.int/model/bada).
91
92
 
92
- This project is released under the European Union Public License v1.2 - see the [LICENSE](https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12) file for details.
93
+ This project is released under the European Union Public License v1.2 - see the [LICENCE](https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12) file for details.
94
+ See the [Amendment to the EUPL](./AMENDMENT_TO_EUPL_license.md) for additional terms.
@@ -1,17 +1,18 @@
1
- pyBADA/TCL.py,sha256=K6L_E0BchttNDWrAfDfuHWk3-ux7mvgeONpr2OkQNgw,373689
1
+ pyBADA/TCL.py,sha256=0X6giDdZi-T5pr1l0x5zBoVkS5MjrxYVWZq-O6UAxsA,373536
2
2
  pyBADA/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- pyBADA/aircraft.py,sha256=VTXMyTm9Gw-EQpNTL9Q8-Mzp2wXE9S2mjJLHvSF-AZs,13009
4
- pyBADA/atmosphere.py,sha256=audmiVJR1fdraeorWBpYqQTlFn_uZVRWcC9ex-Nr9Jw,11348
5
- pyBADA/bada3.py,sha256=FYeZb2uX6NqHJVjJhT5wMhcOObosRr2A8QZXn-uOY6s,186277
6
- pyBADA/bada4.py,sha256=PjP26gEn45Ebajm4TR9FhKMUDQuIlBXOANSCjeS7NsY,222266
7
- pyBADA/badaH.py,sha256=rF7gE5-tsemGDKwkUpiPazw5vdLpMkBNuXpI_22z0S8,153349
8
- pyBADA/configuration.py,sha256=66OCBBYJhXF4quCHDK47YA9yOftuPdrbvbn88P-531E,6505
3
+ pyBADA/aircraft.py,sha256=VBgavT6CO05W941ts3Rm93SC-wUZcx8QbEey1QPxiZ0,13009
4
+ pyBADA/atmosphere.py,sha256=6aYd6bwXcN_HcoHk77-dMMFkoNTT7XUPiCVpRiurUtY,19589
5
+ pyBADA/bada3.py,sha256=r342J8qcoO7Otp2CzYdItRABVcjVWyl142XJ1-inu9I,186825
6
+ pyBADA/bada4.py,sha256=O2ruf7I2oIFp3NeNcabLriFci48TFTJ5RKNfhyXEpPg,223355
7
+ pyBADA/badaH.py,sha256=9PQWky2tE876rXrK9VlPKnQOJlCvi8bpqquOVZQeDDY,153049
8
+ pyBADA/configuration.py,sha256=j4PxLEByx5hP-JDV7FegqgzS0Tk_6rVBrs8DJC42o-Y,6506
9
9
  pyBADA/constants.py,sha256=zJhiLcG49Ab2i-c_mHNCmfLpecmQozvIOjX71aVB5KE,952
10
- pyBADA/conversions.py,sha256=grIokt50KOTaINsmMDWyCJvMpt-_U3FgC9ZNg_aqC1g,3252
11
- pyBADA/flightTrajectory.py,sha256=XEbohC3NGLaDymSLyqyzM9h3D6Tzns2iHLx9uGAMs-U,38483
12
- pyBADA/geodesic.py,sha256=jTEphU_H9D10lgi1Pm77OSsT7icApniaNtSSVHTjZXo,30721
13
- pyBADA/magnetic.py,sha256=O5Ki4ViSqvznl08gZRM90mBbkkpAMR6PR0hKc-FY8yg,4996
14
- pyBADA/trajectoryPrediction.py,sha256=jC-FxEhD3C_hP5nRutZndYtrat14NIKMVG30Aiq0XJ4,6945
10
+ pyBADA/conversions.py,sha256=pfOPAgpQmHPOLUPnOnUTAo2lxj5XE0dZ_dWTzYbsy7M,4358
11
+ pyBADA/flightTrajectory.py,sha256=g8J18o17lUmcF7cd4bHvBUn9el6ywn1628biDF2LAsQ,38484
12
+ pyBADA/geodesic.py,sha256=sC1GTqDkrdFNhzTr8xn4tdEqbdoIijvRmKSsL1ObyOw,34626
13
+ pyBADA/magnetic.py,sha256=svmwNEwfuiDIQChQxmfqNd9tyNHjnAK1lYsTYy_RYio,4997
14
+ pyBADA/trajectoryPrediction.py,sha256=8mkwRoXcmUyw1UlRbg-gRLE5BmSlubI3h_c8aPpKS6A,6946
15
+ pyBADA/utils.py,sha256=ULW1n_Rm30kGrBGG97-yd8V1SJQw8Y6NdEVIEq_xVhc,7029
15
16
  pyBADA/data/magneticDeclinationGridData.json,sha256=ffmLcxdDwFwJqV4WPyMM5SWt60tMj8xsLh5IHYkpO3U,5390918
16
17
  pyBADA/aircraft/BADA3/DUMMY/BADA.GPF,sha256=1Am9yU8v9nnIpvJt-XhJRz4APOkRzfNPujKAy3UCYg0,9831
17
18
  pyBADA/aircraft/BADA3/DUMMY/BZJT__.APF,sha256=wGyXK-ro1S6YhC4wp2nMQKi0JGYLvTunNjO6x_YbhW4,2523
@@ -90,8 +91,8 @@ pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA.PTF,sha256=aEthZF1xztp6QSHYEwU5tH4HF8x
90
91
  pyBADA/aircraft/BADAH/DUMMY/DUMH/LRC.OPT,sha256=7WecIu4kfw5nM_ADih06Hb8pCoxLVsEdHHJTqQrx4hg,10123
91
92
  pyBADA/aircraft/BADAH/DUMMY/DUMH/MEC.OPT,sha256=yKczjH6lZqTplmcV79tZLvwXmHM2F9bYoB2gIM8hBpg,10123
92
93
  pyBADA/aircraft/BADAH/DUMMY/DUMH/MRC.OPT,sha256=fTGqt0P9xgt9Q4sKPlL0CZi9aj73prAPlXj1dpWHSOk,10123
93
- pybada-0.1.5.dist-info/METADATA,sha256=68gtFyLFR_5FgbJNOZ5S3TndHeD5PGv4ALBcX6iXjFw,3539
94
- pybada-0.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
95
- pybada-0.1.5.dist-info/licenses/AUTHORS,sha256=iCKpU7CHp2sB4u5hkS2WyMJHLzL0gfMm-bobd7QDmzE,108
96
- pybada-0.1.5.dist-info/licenses/LICENCE.txt,sha256=RpvAZSjULHvoTR_esTlucJ08-zdQydnoqQLbqOh9Ub8,13826
97
- pybada-0.1.5.dist-info/RECORD,,
94
+ pybada-0.1.6.dist-info/METADATA,sha256=aG80WMHd3JzEGGPQNBXGeNZnkXhCmcRAIIFQyACxn60,3853
95
+ pybada-0.1.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
+ pybada-0.1.6.dist-info/licenses/AUTHORS,sha256=iCKpU7CHp2sB4u5hkS2WyMJHLzL0gfMm-bobd7QDmzE,108
97
+ pybada-0.1.6.dist-info/licenses/LICENCE.txt,sha256=RpvAZSjULHvoTR_esTlucJ08-zdQydnoqQLbqOh9Ub8,13826
98
+ pybada-0.1.6.dist-info/RECORD,,
File without changes