geopack-vectorize 2.0.1__tar.gz → 2.0.3__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.
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/CHANGELOG.md +22 -0
- {geopack_vectorize-2.0.1/geopack_vectorize.egg-info → geopack_vectorize-2.0.3}/PKG-INFO +7 -1
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/README.md +6 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/notebooks/directional_derivatives_maps/t96_field_directional_derivatives.ipynb +6 -6
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/__init__.py +1 -1
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/models/t01.py +1 -1
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/coordinates.py +15 -8
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/coordinates_complex.py +9 -4
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/igrf.py +3 -3
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/models/t01.py +10 -9
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/models/t96.py +8 -8
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3/geopack_vectorize.egg-info}/PKG-INFO +7 -1
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack_vectorize.egg-info/SOURCES.txt +2 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/pyproject.toml +1 -1
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/setup.py +1 -1
- geopack_vectorize-2.0.3/tests/test_coordinate_broadcasting.py +95 -0
- geopack_vectorize-2.0.3/tests/test_model_input_regressions.py +96 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/LICENSE +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/MANIFEST.in +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/notebooks/01_coordinate_transformations_guide.ipynb +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/notebooks/02_magnetic_field_models_guide.ipynb +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/notebooks/03_performance_comparison.ipynb +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/notebooks/04_accuracy_validation.ipynb +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/notebooks/05_field_line_tracing_guide.ipynb +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/notebooks/06_field_line_tracing_validation.ipynb +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/notebooks/07_fieldline_geometry_and_derivatives.ipynb +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/notebooks/directional_derivatives_maps/dipole_field_directional_derivatives.ipynb +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/python_code_samples/_helpers.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/python_code_samples/basic_usage.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/python_code_samples/field_line_9_derivatives_example.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/python_code_samples/field_line_geometry_example.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/python_code_samples/magnetic_field_simple_sample.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/readme_examples.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/Mageometry/__init__.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/Mageometry/field_line_directional_derivatives.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/Mageometry/field_line_geometry.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/core.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/geopack.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf10coeffs.txt +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf11coeffs.txt +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf12coeffs.txt +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf13coeffs.txt +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf14coeffs.txt +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf9coeffs.txt +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/models/__init__.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/models/t04.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/models/t89.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/models/t96.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/print_structure.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/t01.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/t04.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/t89.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/t96.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/__init__.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/condip1_exact.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/models/__init__.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/models/t04.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/models/t89.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/trace.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack_vectorize.egg-info/dependency_links.txt +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack_vectorize.egg-info/requires.txt +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack_vectorize.egg-info/top_level.txt +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/requirements.txt +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/setup.cfg +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/tests/installed_smoke.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/tests/test_distribution.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/tests/test_trace_vectorized.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/tests/test_trace_vectorized_with_vectorized_models.py +0 -0
- {geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/tests/test_vectorized_models.py +0 -0
|
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [2.0.3] - 2026-11-07
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Return NaN fields for NaN coordinates in vectorized T01/T96 instead of
|
|
14
|
+
leaving zero outputs when no magnetopause region matches. Valid points
|
|
15
|
+
in the same batch retain their scalar-reference values.
|
|
16
|
+
- Normalize Cartesian-to-spherical longitude in vectorized `sphcar` to
|
|
17
|
+
match the scalar convention of [0, 2*pi), retaining phi=0 at the poles.
|
|
18
|
+
|
|
19
|
+
## [2.0.2] - 2026-11-04
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Preserve the T01 IMF clock angle for negative By instead of replacing it
|
|
23
|
+
with 2*pi, correcting both scalar and vectorized magnetic fields.
|
|
24
|
+
- Accept integer coordinates without truncation in vectorized T01/T96,
|
|
25
|
+
and preserve NumPy broadcasting and multidimensional inputs in T01.
|
|
26
|
+
- Broadcast all coordinate/vector components before transforms and determine
|
|
27
|
+
scalar outputs from every input, including spherical transforms and the
|
|
28
|
+
IGRF GSM/GSW wrappers.
|
|
29
|
+
|
|
8
30
|
## [2.0.1] - 2026-10-28
|
|
9
31
|
|
|
10
32
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: geopack-vectorize
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Python implementation of geopack and Tsyganenko models with optimized vectorization
|
|
5
5
|
Home-page: https://github.com/Butadiene/geopack-vectorize
|
|
6
6
|
Author: geopack-vectorize contributors
|
|
@@ -64,6 +64,12 @@ geopack-vectorize extends the original geopack by adding vectorized implementati
|
|
|
64
64
|
- **Full Backward Compatibility**: All original geopack functions remain unchanged and available
|
|
65
65
|
- **Comprehensive Validation**: Extensive test suite ensuring < 10⁻¹¹ relative error vs original implementations
|
|
66
66
|
|
|
67
|
+
## Publication and Citation
|
|
68
|
+
|
|
69
|
+
geopack-vectorize, including its Mageometry component for local-frame analysis of magnetic field geometry, is described in the following paper published in *Earth, Planets and Space*. If you use this work in your research, please cite:
|
|
70
|
+
|
|
71
|
+
> Isono, K., Yoshikawa, A. & Katoh, Y. (2026). A local-frame approach to three-dimensional analysis of magnetospheric magnetic fields. *Earth, Planets and Space*, **78**, Article 201. [doi:10.1186/s40623-026-02528-2](https://doi.org/10.1186/s40623-026-02528-2).
|
|
72
|
+
|
|
67
73
|
## Upgrading to v2.0.0
|
|
68
74
|
|
|
69
75
|
As of v2.0.0, the PyPI package name has changed from `geopack-vectorized` to `geopack-vectorize`. The old `geopack-vectorized` package will remain available for a transitional period but will eventually be removed. Please update your installation:
|
|
@@ -16,6 +16,12 @@ geopack-vectorize extends the original geopack by adding vectorized implementati
|
|
|
16
16
|
- **Full Backward Compatibility**: All original geopack functions remain unchanged and available
|
|
17
17
|
- **Comprehensive Validation**: Extensive test suite ensuring < 10⁻¹¹ relative error vs original implementations
|
|
18
18
|
|
|
19
|
+
## Publication and Citation
|
|
20
|
+
|
|
21
|
+
geopack-vectorize, including its Mageometry component for local-frame analysis of magnetic field geometry, is described in the following paper published in *Earth, Planets and Space*. If you use this work in your research, please cite:
|
|
22
|
+
|
|
23
|
+
> Isono, K., Yoshikawa, A. & Katoh, Y. (2026). A local-frame approach to three-dimensional analysis of magnetospheric magnetic fields. *Earth, Planets and Space*, **78**, Article 201. [doi:10.1186/s40623-026-02528-2](https://doi.org/10.1186/s40623-026-02528-2).
|
|
24
|
+
|
|
19
25
|
## Upgrading to v2.0.0
|
|
20
26
|
|
|
21
27
|
As of v2.0.0, the PyPI package name has changed from `geopack-vectorized` to `geopack-vectorize`. The old `geopack-vectorized` package will remain available for a transitional period but will eventually be removed. Please update your installation:
|
|
@@ -339,7 +339,7 @@
|
|
|
339
339
|
" unit:\n",
|
|
340
340
|
" - None -> uses UNIT_STR (for Gamma, etc.)\n",
|
|
341
341
|
" - \"\" -> no unit display (for ratio, etc.)\n",
|
|
342
|
-
" - \"A
|
|
342
|
+
" - \"A/m$^2$\" -> [A/m$^2$] (for FAC current density, etc.)\n",
|
|
343
343
|
" \"\"\"\n",
|
|
344
344
|
" if unit is None:\n",
|
|
345
345
|
" unit = UNIT_STR\n",
|
|
@@ -850,7 +850,7 @@
|
|
|
850
850
|
"\n",
|
|
851
851
|
"def compute_fac_proxy_and_ratio(Bmag_nT, dn_db_T, db_dn_T):\n",
|
|
852
852
|
" mu0 = 1.25663706127e-6\n",
|
|
853
|
-
" FAC = Bmag_nT * (dn_db_T - db_dn_T) / mu0 * (1e-9) / (6371 *
|
|
853
|
+
" FAC = Bmag_nT * (dn_db_T - db_dn_T) / mu0 * (1e-9) / (6371 * 1e3) # A/m^2 (1 Re = 6371 km = 6.371e6 m)\n",
|
|
854
854
|
" den = np.abs(dn_db_T) + np.abs(db_dn_T)\n",
|
|
855
855
|
" ratio = np.abs(dn_db_T) / np.where(den == 0, np.nan, den)\n",
|
|
856
856
|
" return FAC, ratio\n",
|
|
@@ -918,7 +918,7 @@
|
|
|
918
918
|
" r\"Field-Aligned Current $\\frac{B}{\\mu_0}$((∂n/∂b)·T − (∂b/∂n)·T)\"\n",
|
|
919
919
|
" ),\n",
|
|
920
920
|
" cbar_label=\"FAC\",\n",
|
|
921
|
-
" unit=\"A
|
|
921
|
+
" unit=\"A/m$^2$\",\n",
|
|
922
922
|
" force_levels=None,\n",
|
|
923
923
|
" ),\n",
|
|
924
924
|
" dict(\n",
|
|
@@ -1166,7 +1166,7 @@
|
|
|
1166
1166
|
" r\"Field-Aligned Current $\\frac{B}{\\mu_0}$((∂n/∂b)·T − (∂b/∂n)·T)\"\n",
|
|
1167
1167
|
" ),\n",
|
|
1168
1168
|
" cbar_label=\"FAC\",\n",
|
|
1169
|
-
" unit=\"A
|
|
1169
|
+
" unit=\"A/m$^2$\",\n",
|
|
1170
1170
|
" force_levels=None,\n",
|
|
1171
1171
|
" ),\n",
|
|
1172
1172
|
" dict(\n",
|
|
@@ -1464,7 +1464,7 @@
|
|
|
1464
1464
|
" r\"Field-Aligned Current $\\frac{B}{\\mu_0}$((∂n/∂b)·T − (∂b/∂n)·T)\"\n",
|
|
1465
1465
|
" ),\n",
|
|
1466
1466
|
" cbar_label=\"FAC\",\n",
|
|
1467
|
-
" unit=\"A
|
|
1467
|
+
" unit=\"A/m$^2$\",\n",
|
|
1468
1468
|
" force_levels=None,\n",
|
|
1469
1469
|
" ),\n",
|
|
1470
1470
|
" dict(\n",
|
|
@@ -1581,7 +1581,7 @@
|
|
|
1581
1581
|
" r\"Field-Aligned Current $\\frac{B}{\\mu_0}$((∂n/∂b)·T − (∂b/∂n)·T)\"\n",
|
|
1582
1582
|
" ),\n",
|
|
1583
1583
|
" cbar_label=\"FAC\",\n",
|
|
1584
|
-
" unit=\"A
|
|
1584
|
+
" unit=\"A/m$^2$\",\n",
|
|
1585
1585
|
" force_levels=None,\n",
|
|
1586
1586
|
" ),\n",
|
|
1587
1587
|
" dict(\n",
|
|
@@ -147,7 +147,7 @@ def extall(iopgen,iopt,iopb,iopr,a,ntot,pdyn,dst,byimf,bzimf,vbimf1,vbimf2,ps,x,
|
|
|
147
147
|
theta = 0.
|
|
148
148
|
else:
|
|
149
149
|
theta=np.arctan2(byimf,bzimf)
|
|
150
|
-
if theta <= 0: theta
|
|
150
|
+
if theta <= 0: theta += 2*np.pi
|
|
151
151
|
|
|
152
152
|
ct=np.cos(theta)
|
|
153
153
|
st=np.sin(theta)
|
|
@@ -34,12 +34,13 @@ def gsmgse(xgsm, ygsm, zgsm, j):
|
|
|
34
34
|
Transformed coordinates (GSE if j>0, GSM if j<0)
|
|
35
35
|
"""
|
|
36
36
|
# Check if input is scalar
|
|
37
|
-
scalar_input = np.isscalar(xgsm)
|
|
37
|
+
scalar_input = all(np.isscalar(c) for c in (xgsm, ygsm, zgsm))
|
|
38
38
|
|
|
39
39
|
# Ensure arrays
|
|
40
40
|
xgsm = np.atleast_1d(xgsm)
|
|
41
41
|
ygsm = np.atleast_1d(ygsm)
|
|
42
42
|
zgsm = np.atleast_1d(zgsm)
|
|
43
|
+
xgsm, ygsm, zgsm = np.broadcast_arrays(xgsm, ygsm, zgsm)
|
|
43
44
|
|
|
44
45
|
# Get transformation angles from geopack globals
|
|
45
46
|
chi = gp.chi
|
|
@@ -83,12 +84,13 @@ def geigeo(xgei, ygei, zgei, j):
|
|
|
83
84
|
Transformed coordinates (GEO if j>0, GEI if j<0)
|
|
84
85
|
"""
|
|
85
86
|
# Check if input is scalar
|
|
86
|
-
scalar_input = np.isscalar(xgei)
|
|
87
|
+
scalar_input = all(np.isscalar(c) for c in (xgei, ygei, zgei))
|
|
87
88
|
|
|
88
89
|
# Ensure arrays
|
|
89
90
|
xgei = np.atleast_1d(xgei)
|
|
90
91
|
ygei = np.atleast_1d(ygei)
|
|
91
92
|
zgei = np.atleast_1d(zgei)
|
|
93
|
+
xgei, ygei, zgei = np.broadcast_arrays(xgei, ygei, zgei)
|
|
92
94
|
|
|
93
95
|
# Get transformation angles from geopack globals
|
|
94
96
|
cgst = gp.cgst
|
|
@@ -132,12 +134,13 @@ def magsm(xmag, ymag, zmag, j):
|
|
|
132
134
|
Transformed coordinates (SM if j>0, MAG if j<0)
|
|
133
135
|
"""
|
|
134
136
|
# Check if input is scalar
|
|
135
|
-
scalar_input = np.isscalar(xmag)
|
|
137
|
+
scalar_input = all(np.isscalar(c) for c in (xmag, ymag, zmag))
|
|
136
138
|
|
|
137
139
|
# Ensure arrays
|
|
138
140
|
xmag = np.atleast_1d(xmag)
|
|
139
141
|
ymag = np.atleast_1d(ymag)
|
|
140
142
|
zmag = np.atleast_1d(zmag)
|
|
143
|
+
xmag, ymag, zmag = np.broadcast_arrays(xmag, ymag, zmag)
|
|
141
144
|
|
|
142
145
|
# Get transformation angles from geopack globals
|
|
143
146
|
sfi = gp.sfi
|
|
@@ -181,12 +184,13 @@ def smgsm(xsm, ysm, zsm, j):
|
|
|
181
184
|
Transformed coordinates (GSM if j>0, SM if j<0)
|
|
182
185
|
"""
|
|
183
186
|
# Check if input is scalar
|
|
184
|
-
scalar_input = np.isscalar(xsm)
|
|
187
|
+
scalar_input = all(np.isscalar(c) for c in (xsm, ysm, zsm))
|
|
185
188
|
|
|
186
189
|
# Ensure arrays
|
|
187
190
|
xsm = np.atleast_1d(xsm)
|
|
188
191
|
ysm = np.atleast_1d(ysm)
|
|
189
192
|
zsm = np.atleast_1d(zsm)
|
|
193
|
+
xsm, ysm, zsm = np.broadcast_arrays(xsm, ysm, zsm)
|
|
190
194
|
|
|
191
195
|
# Get transformation angles from geopack globals
|
|
192
196
|
sps = gp.sps
|
|
@@ -230,12 +234,13 @@ def geomag(xgeo, ygeo, zgeo, j):
|
|
|
230
234
|
Transformed coordinates (MAG if j>0, GEO if j<0)
|
|
231
235
|
"""
|
|
232
236
|
# Check if input is scalar
|
|
233
|
-
scalar_input = np.isscalar(xgeo)
|
|
237
|
+
scalar_input = all(np.isscalar(c) for c in (xgeo, ygeo, zgeo))
|
|
234
238
|
|
|
235
239
|
# Ensure arrays
|
|
236
240
|
xgeo = np.atleast_1d(xgeo)
|
|
237
241
|
ygeo = np.atleast_1d(ygeo)
|
|
238
242
|
zgeo = np.atleast_1d(zgeo)
|
|
243
|
+
xgeo, ygeo, zgeo = np.broadcast_arrays(xgeo, ygeo, zgeo)
|
|
239
244
|
|
|
240
245
|
# Get transformation matrix elements from geopack globals
|
|
241
246
|
# These are set by recalc() based on IGRF coefficients
|
|
@@ -286,12 +291,13 @@ def geogsm(xgeo, ygeo, zgeo, j):
|
|
|
286
291
|
Transformed coordinates (GSM if j>0, GEO if j<0)
|
|
287
292
|
"""
|
|
288
293
|
# Check if input is scalar
|
|
289
|
-
scalar_input = np.isscalar(xgeo)
|
|
294
|
+
scalar_input = all(np.isscalar(c) for c in (xgeo, ygeo, zgeo))
|
|
290
295
|
|
|
291
296
|
# Ensure arrays
|
|
292
297
|
xgeo = np.atleast_1d(xgeo)
|
|
293
298
|
ygeo = np.atleast_1d(ygeo)
|
|
294
299
|
zgeo = np.atleast_1d(zgeo)
|
|
300
|
+
xgeo, ygeo, zgeo = np.broadcast_arrays(xgeo, ygeo, zgeo)
|
|
295
301
|
|
|
296
302
|
# Get transformation matrix elements from geopack globals
|
|
297
303
|
a11 = gp.a11
|
|
@@ -342,12 +348,13 @@ def gswgsm(xgsw, ygsw, zgsw, j):
|
|
|
342
348
|
Transformed coordinates (GSM if j>0, GSW if j<0)
|
|
343
349
|
"""
|
|
344
350
|
# Check if input is scalar
|
|
345
|
-
scalar_input = np.isscalar(xgsw)
|
|
351
|
+
scalar_input = all(np.isscalar(c) for c in (xgsw, ygsw, zgsw))
|
|
346
352
|
|
|
347
353
|
# Ensure arrays
|
|
348
354
|
xgsw = np.atleast_1d(xgsw)
|
|
349
355
|
ygsw = np.atleast_1d(ygsw)
|
|
350
356
|
zgsw = np.atleast_1d(zgsw)
|
|
357
|
+
xgsw, ygsw, zgsw = np.broadcast_arrays(xgsw, ygsw, zgsw)
|
|
351
358
|
|
|
352
359
|
# Get transformation matrix elements from geopack globals
|
|
353
360
|
e11 = gp.e11
|
|
@@ -378,4 +385,4 @@ def gswgsm(xgsw, ygsw, zgsw, j):
|
|
|
378
385
|
if scalar_input:
|
|
379
386
|
return x_out.item(), y_out.item(), z_out.item()
|
|
380
387
|
else:
|
|
381
|
-
return x_out, y_out, z_out
|
|
388
|
+
return x_out, y_out, z_out
|
{geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/vectorized/coordinates_complex.py
RENAMED
|
@@ -23,14 +23,16 @@ def sphcar(r, theta, phi, j):
|
|
|
23
23
|
-------
|
|
24
24
|
tuple of (float or ndarray)
|
|
25
25
|
Transformed coordinates (x,y,z if j>0, r,theta,phi if j<0)
|
|
26
|
+
For j<0, phi is in [0, 2*pi), with phi=0 at the poles.
|
|
26
27
|
"""
|
|
27
28
|
# Check if input is scalar
|
|
28
|
-
scalar_input = np.isscalar(r)
|
|
29
|
+
scalar_input = all(np.isscalar(c) for c in (r, theta, phi))
|
|
29
30
|
|
|
30
31
|
# Ensure arrays
|
|
31
32
|
r = np.atleast_1d(r)
|
|
32
33
|
theta = np.atleast_1d(theta)
|
|
33
34
|
phi = np.atleast_1d(phi)
|
|
35
|
+
r, theta, phi = np.broadcast_arrays(r, theta, phi)
|
|
34
36
|
|
|
35
37
|
if j > 0: # Spherical to Cartesian
|
|
36
38
|
sq = r * np.sin(theta)
|
|
@@ -49,6 +51,7 @@ def sphcar(r, theta, phi, j):
|
|
|
49
51
|
|
|
50
52
|
# Handle conditional logic for phi
|
|
51
53
|
phi_out = np.where(sq != 0, np.arctan2(y, x), 0.0)
|
|
54
|
+
phi_out = np.where(phi_out < 0, phi_out + 2 * np.pi, phi_out)
|
|
52
55
|
|
|
53
56
|
# Handle conditional logic for theta
|
|
54
57
|
theta_out = np.where(sq != 0,
|
|
@@ -81,7 +84,7 @@ def bspcar(theta, phi, br, btheta, bphi):
|
|
|
81
84
|
Magnetic field components in Cartesian coordinates (bx, by, bz)
|
|
82
85
|
"""
|
|
83
86
|
# Check if input is scalar
|
|
84
|
-
scalar_input = np.isscalar(theta)
|
|
87
|
+
scalar_input = all(np.isscalar(c) for c in (theta, phi, br, btheta, bphi))
|
|
85
88
|
|
|
86
89
|
# Ensure arrays
|
|
87
90
|
theta = np.atleast_1d(theta)
|
|
@@ -89,6 +92,7 @@ def bspcar(theta, phi, br, btheta, bphi):
|
|
|
89
92
|
br = np.atleast_1d(br)
|
|
90
93
|
btheta = np.atleast_1d(btheta)
|
|
91
94
|
bphi = np.atleast_1d(bphi)
|
|
95
|
+
theta, phi, br, btheta, bphi = np.broadcast_arrays(theta, phi, br, btheta, bphi)
|
|
92
96
|
|
|
93
97
|
# Calculate trigonometric values
|
|
94
98
|
s = np.sin(theta)
|
|
@@ -126,7 +130,7 @@ def bcarsp(x, y, z, bx, by, bz):
|
|
|
126
130
|
Magnetic field components in spherical coordinates (br, btheta, bphi)
|
|
127
131
|
"""
|
|
128
132
|
# Check if input is scalar
|
|
129
|
-
scalar_input = np.isscalar(x)
|
|
133
|
+
scalar_input = all(np.isscalar(c) for c in (x, y, z, bx, by, bz))
|
|
130
134
|
|
|
131
135
|
# Ensure arrays
|
|
132
136
|
x = np.atleast_1d(x)
|
|
@@ -135,6 +139,7 @@ def bcarsp(x, y, z, bx, by, bz):
|
|
|
135
139
|
bx = np.atleast_1d(bx)
|
|
136
140
|
by = np.atleast_1d(by)
|
|
137
141
|
bz = np.atleast_1d(bz)
|
|
142
|
+
x, y, z, bx, by, bz = np.broadcast_arrays(x, y, z, bx, by, bz)
|
|
138
143
|
|
|
139
144
|
# Calculate distances
|
|
140
145
|
rho2 = x*x + y*y
|
|
@@ -167,4 +172,4 @@ def bcarsp(x, y, z, bx, by, bz):
|
|
|
167
172
|
if scalar_input:
|
|
168
173
|
return br.item(), btheta.item(), bphi.item()
|
|
169
174
|
else:
|
|
170
|
-
return br, btheta, bphi
|
|
175
|
+
return br, btheta, bphi
|
|
@@ -225,7 +225,7 @@ def igrf_gsm(xgsm, ygsm, zgsm):
|
|
|
225
225
|
Magnetic field components in GSM coordinates (nT)
|
|
226
226
|
"""
|
|
227
227
|
# Handle scalar inputs
|
|
228
|
-
scalar_input = np.isscalar(xgsm)
|
|
228
|
+
scalar_input = all(np.isscalar(c) for c in (xgsm, ygsm, zgsm))
|
|
229
229
|
xgsm = np.atleast_1d(xgsm)
|
|
230
230
|
ygsm = np.atleast_1d(ygsm)
|
|
231
231
|
zgsm = np.atleast_1d(zgsm)
|
|
@@ -270,7 +270,7 @@ def igrf_gsw(xgsw, ygsw, zgsw):
|
|
|
270
270
|
Magnetic field components in GSW coordinates (nT)
|
|
271
271
|
"""
|
|
272
272
|
# Handle scalar inputs
|
|
273
|
-
scalar_input = np.isscalar(xgsw)
|
|
273
|
+
scalar_input = all(np.isscalar(c) for c in (xgsw, ygsw, zgsw))
|
|
274
274
|
xgsw = np.atleast_1d(xgsw)
|
|
275
275
|
ygsw = np.atleast_1d(ygsw)
|
|
276
276
|
zgsw = np.atleast_1d(zgsw)
|
|
@@ -289,4 +289,4 @@ def igrf_gsw(xgsw, ygsw, zgsw):
|
|
|
289
289
|
if scalar_input:
|
|
290
290
|
return bxgsw.item(), bygsw.item(), bzgsw.item()
|
|
291
291
|
else:
|
|
292
|
-
return bxgsw, bygsw, bzgsw
|
|
292
|
+
return bxgsw, bygsw, bzgsw
|
|
@@ -60,10 +60,11 @@ def t01(parmod, ps, x, y, z):
|
|
|
60
60
|
# Track if all inputs were scalar
|
|
61
61
|
scalar_input = np.isscalar(x) and np.isscalar(y) and np.isscalar(z)
|
|
62
62
|
|
|
63
|
-
#
|
|
64
|
-
x = np.
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
# Normalize dtype and shape before masked coordinate iterations.
|
|
64
|
+
x, y, z = np.broadcast_arrays(
|
|
65
|
+
np.atleast_1d(np.asarray(x, dtype=float)),
|
|
66
|
+
np.atleast_1d(np.asarray(y, dtype=float)),
|
|
67
|
+
np.atleast_1d(np.asarray(z, dtype=float)))
|
|
67
68
|
|
|
68
69
|
a = np.array([
|
|
69
70
|
1.00000, 2.47341, 0.40791, 0.30429, -0.10637, -0.89108, 3.29350,
|
|
@@ -133,7 +134,7 @@ def extall(iopgen,iopt,iopb,iopr,a,ntot,pdyn,dst,byimf,bzimf,vbimf1,vbimf2,ps,x,
|
|
|
133
134
|
theta = 0.
|
|
134
135
|
else:
|
|
135
136
|
theta=np.arctan2(byimf,bzimf)
|
|
136
|
-
if theta <= 0: theta
|
|
137
|
+
if theta <= 0: theta += 2*np.pi
|
|
137
138
|
|
|
138
139
|
ct=np.cos(theta)
|
|
139
140
|
st=np.sin(theta)
|
|
@@ -172,8 +173,7 @@ def extall(iopgen,iopt,iopb,iopr,a,ntot,pdyn,dst,byimf,bzimf,vbimf1,vbimf2,ps,x,
|
|
|
172
173
|
zss[active] = znew
|
|
173
174
|
|
|
174
175
|
# Skip already-converged points in subsequent iterations
|
|
175
|
-
|
|
176
|
-
active[active_idx] = dd > 1e-6
|
|
176
|
+
active[active] = dd > 1e-6
|
|
177
177
|
|
|
178
178
|
rho2=y**2+zss**2
|
|
179
179
|
asq=am**2
|
|
@@ -249,7 +249,8 @@ def extall(iopgen,iopt,iopb,iopr,a,ntot,pdyn,dst,byimf,bzimf,vbimf1,vbimf2,ps,x,
|
|
|
249
249
|
mask_interp = (sigma >= (s0 - dsig)) & (sigma < (s0 + dsig))
|
|
250
250
|
mask_out = sigma >= (s0 + dsig)
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
# NaN sigma matches no region, so invalid coordinates must stay undefined.
|
|
253
|
+
bx, by, bz = np.full_like(x, np.nan), np.full_like(y, np.nan), np.full_like(z, np.nan)
|
|
253
254
|
|
|
254
255
|
# Case 1: Inside the magnetosphere
|
|
255
256
|
bx = np.where(mask_in, bbx, bx)
|
|
@@ -1966,4 +1967,4 @@ def dipole(ps, x, y, z):
|
|
|
1966
1967
|
by = -3 * y * q * (x * sps + z * cps)
|
|
1967
1968
|
bz = q * ((x2 + y2 - 2 * z2) * cps - xz3 * sps)
|
|
1968
1969
|
|
|
1969
|
-
return bx, by, bz
|
|
1970
|
+
return bx, by, bz
|
|
@@ -46,9 +46,9 @@ def t96(parmod, ps, x, y, z):
|
|
|
46
46
|
scalar_input = np.isscalar(x) and np.isscalar(y) and np.isscalar(z)
|
|
47
47
|
|
|
48
48
|
# Convert inputs to numpy arrays
|
|
49
|
-
x = np.atleast_1d(x)
|
|
50
|
-
y = np.atleast_1d(y)
|
|
51
|
-
z = np.atleast_1d(z)
|
|
49
|
+
x = np.atleast_1d(np.asarray(x, dtype=float))
|
|
50
|
+
y = np.atleast_1d(np.asarray(y, dtype=float))
|
|
51
|
+
z = np.atleast_1d(np.asarray(z, dtype=float))
|
|
52
52
|
|
|
53
53
|
# Broadcast arrays to same shape
|
|
54
54
|
x, y, z = np.broadcast_arrays(x, y, z)
|
|
@@ -125,10 +125,10 @@ def t96(parmod, ps, x, y, z):
|
|
|
125
125
|
sqrt_arg = np.maximum(sqrt_arg, 0) # Ensure non-negative
|
|
126
126
|
sigma = np.sqrt((aro + axx0 + np.sqrt(sqrt_arg)) / (2.0 * asq))
|
|
127
127
|
|
|
128
|
-
#
|
|
129
|
-
bx = np.
|
|
130
|
-
by = np.
|
|
131
|
-
bz = np.
|
|
128
|
+
# NaN sigma matches no region, so invalid coordinates must stay undefined.
|
|
129
|
+
bx = np.full_like(x, np.nan)
|
|
130
|
+
by = np.full_like(y, np.nan)
|
|
131
|
+
bz = np.full_like(z, np.nan)
|
|
132
132
|
|
|
133
133
|
# Define masks for three regions
|
|
134
134
|
mask_inside = sigma < (s0 - dsig)
|
|
@@ -2443,4 +2443,4 @@ if __name__ == '__main__':
|
|
|
2443
2443
|
print("\nArray input:")
|
|
2444
2444
|
for i in range(len(x_arr)):
|
|
2445
2445
|
print(f" Point ({x_arr[i]}, {y_arr[i]}, {z_arr[i]}): "
|
|
2446
|
-
f"B = ({bx_arr[i]:.3f}, {by_arr[i]:.3f}, {bz_arr[i]:.3f}) nT")
|
|
2446
|
+
f"B = ({bx_arr[i]:.3f}, {by_arr[i]:.3f}, {bz_arr[i]:.3f}) nT")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: geopack-vectorize
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Python implementation of geopack and Tsyganenko models with optimized vectorization
|
|
5
5
|
Home-page: https://github.com/Butadiene/geopack-vectorize
|
|
6
6
|
Author: geopack-vectorize contributors
|
|
@@ -64,6 +64,12 @@ geopack-vectorize extends the original geopack by adding vectorized implementati
|
|
|
64
64
|
- **Full Backward Compatibility**: All original geopack functions remain unchanged and available
|
|
65
65
|
- **Comprehensive Validation**: Extensive test suite ensuring < 10⁻¹¹ relative error vs original implementations
|
|
66
66
|
|
|
67
|
+
## Publication and Citation
|
|
68
|
+
|
|
69
|
+
geopack-vectorize, including its Mageometry component for local-frame analysis of magnetic field geometry, is described in the following paper published in *Earth, Planets and Space*. If you use this work in your research, please cite:
|
|
70
|
+
|
|
71
|
+
> Isono, K., Yoshikawa, A. & Katoh, Y. (2026). A local-frame approach to three-dimensional analysis of magnetospheric magnetic fields. *Earth, Planets and Space*, **78**, Article 201. [doi:10.1186/s40623-026-02528-2](https://doi.org/10.1186/s40623-026-02528-2).
|
|
72
|
+
|
|
67
73
|
## Upgrading to v2.0.0
|
|
68
74
|
|
|
69
75
|
As of v2.0.0, the PyPI package name has changed from `geopack-vectorized` to `geopack-vectorize`. The old `geopack-vectorized` package will remain available for a transitional period but will eventually be removed. Please update your installation:
|
|
@@ -60,7 +60,9 @@ geopack_vectorize.egg-info/dependency_links.txt
|
|
|
60
60
|
geopack_vectorize.egg-info/requires.txt
|
|
61
61
|
geopack_vectorize.egg-info/top_level.txt
|
|
62
62
|
tests/installed_smoke.py
|
|
63
|
+
tests/test_coordinate_broadcasting.py
|
|
63
64
|
tests/test_distribution.py
|
|
65
|
+
tests/test_model_input_regressions.py
|
|
64
66
|
tests/test_trace_vectorized.py
|
|
65
67
|
tests/test_trace_vectorized_with_vectorized_models.py
|
|
66
68
|
tests/test_vectorized_models.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "geopack-vectorize"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.3"
|
|
8
8
|
description = "Python implementation of geopack and Tsyganenko models with optimized vectorization"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Sheng Tian", email = "ts0110@atmos.ucla.edu"}
|
|
@@ -6,7 +6,7 @@ with open('README.md', 'r', encoding='utf-8') as fh:
|
|
|
6
6
|
|
|
7
7
|
setuptools.setup(
|
|
8
8
|
name='geopack-vectorize',
|
|
9
|
-
version='2.0.
|
|
9
|
+
version='2.0.3',
|
|
10
10
|
author='geopack-vectorize contributors',
|
|
11
11
|
author_email='',
|
|
12
12
|
description='Vectorized Python implementation of geopack and Tsyganenko models (fork of tsssss/geopack)',
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Scalar and NumPy broadcasting contracts for transforms and IGRF wrappers."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from geopack import geopack as scalar
|
|
8
|
+
from geopack.vectorized import coordinates, coordinates_complex, igrf
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestCoordinateBroadcasting(unittest.TestCase):
|
|
12
|
+
@classmethod
|
|
13
|
+
def setUpClass(cls):
|
|
14
|
+
scalar.recalc(100., vxgse=-400., vygse=15., vzgse=20.)
|
|
15
|
+
|
|
16
|
+
def assert_pointwise(self, vector_function, scalar_function, arguments, extra=()):
|
|
17
|
+
broadcast = np.broadcast_arrays(*arguments)
|
|
18
|
+
expected = np.array([scalar_function(*point, *extra)
|
|
19
|
+
for point in zip(*(c.ravel() for c in broadcast))])
|
|
20
|
+
expected = expected.T.reshape((3,) + broadcast[0].shape)
|
|
21
|
+
actual = vector_function(*arguments, *extra)
|
|
22
|
+
for component in actual:
|
|
23
|
+
self.assertEqual(component.shape, broadcast[0].shape)
|
|
24
|
+
np.testing.assert_allclose(actual, expected, rtol=1e-11, atol=1e-8)
|
|
25
|
+
|
|
26
|
+
def test_cartesian_transforms_broadcast_each_component_in_both_directions(self):
|
|
27
|
+
names = ('gsmgse', 'geigeo', 'magsm', 'smgsm', 'geomag', 'geogsm', 'gswgsm')
|
|
28
|
+
cases = [(np.array([1., 2.]), 3., 4.),
|
|
29
|
+
(1., np.array([2., 3.]), 4.),
|
|
30
|
+
(1., 2., np.array([3., 4.])),
|
|
31
|
+
(np.array([1., 2.])[:, None], np.array([2., 3., 4.]), 4.)]
|
|
32
|
+
for name in names:
|
|
33
|
+
for direction in (-1, 1):
|
|
34
|
+
for coords in cases:
|
|
35
|
+
with self.subTest(transform=name, direction=direction,
|
|
36
|
+
shapes=tuple(np.shape(c) for c in coords)):
|
|
37
|
+
self.assert_pointwise(getattr(coordinates, name), getattr(scalar, name),
|
|
38
|
+
coords, (direction,))
|
|
39
|
+
with self.subTest(transform=name, direction=direction, scalar=True):
|
|
40
|
+
result = getattr(coordinates, name)(1., 2., 3., direction)
|
|
41
|
+
self.assertTrue(all(isinstance(c, float) for c in result))
|
|
42
|
+
np.testing.assert_allclose(result, getattr(scalar, name)(1., 2., 3., direction))
|
|
43
|
+
|
|
44
|
+
def test_spherical_coordinate_transforms_broadcast(self):
|
|
45
|
+
cases = (
|
|
46
|
+
((5., np.array([.5, 1.]), .3), 1),
|
|
47
|
+
((np.array([4., 5.]), .5, .3), 1),
|
|
48
|
+
((5., .5, np.array([.3, .6])), 1),
|
|
49
|
+
((1., np.array([2., 3.]), 4.), -1),
|
|
50
|
+
((np.array([1., 2.])[:, None], 2., np.array([3., 4., 5.])), -1),
|
|
51
|
+
)
|
|
52
|
+
for coords, direction in cases:
|
|
53
|
+
with self.subTest(direction=direction, shapes=tuple(np.shape(c) for c in coords)):
|
|
54
|
+
self.assert_pointwise(coordinates_complex.sphcar, scalar.sphcar, coords, (direction,))
|
|
55
|
+
|
|
56
|
+
def test_magnetic_component_transforms_broadcast_every_input(self):
|
|
57
|
+
for name, base in (('bspcar', [.7, .3, 4., 2., 1.]),
|
|
58
|
+
('bcarsp', [4., 2., 1., 3., 2., 1.])):
|
|
59
|
+
for index in range(len(base)):
|
|
60
|
+
arguments = list(base)
|
|
61
|
+
arguments[index] = base[index] + np.array([0., .1])
|
|
62
|
+
with self.subTest(transform=name, vector_input=index):
|
|
63
|
+
self.assert_pointwise(getattr(coordinates_complex, name), getattr(scalar, name),
|
|
64
|
+
arguments)
|
|
65
|
+
result = getattr(coordinates_complex, name)(*base)
|
|
66
|
+
self.assertTrue(all(isinstance(c, float) for c in result))
|
|
67
|
+
|
|
68
|
+
def test_cartesian_to_spherical_longitude_matches_scalar_in_all_quadrants(self):
|
|
69
|
+
# Include the negative x axis, signed zero, both poles, and the origin.
|
|
70
|
+
points = np.array([[1., 1., 0.], [-1., 1., 0.], [-1., -1., 0.], [1., -1., 0.],
|
|
71
|
+
[-1., 0., 0.], [-1., -0., 0.], [1., -0., 0.],
|
|
72
|
+
[0., 0., 1.], [0., 0., -1.], [0., 0., 0.]])
|
|
73
|
+
for point in points:
|
|
74
|
+
with self.subTest(point=point):
|
|
75
|
+
actual = coordinates_complex.sphcar(*point, -1)
|
|
76
|
+
self.assertTrue(all(isinstance(c, float) for c in actual))
|
|
77
|
+
np.testing.assert_allclose(actual, scalar.sphcar(*point, -1), atol=1e-14)
|
|
78
|
+
self.assertGreaterEqual(actual[2], 0.)
|
|
79
|
+
self.assertLess(actual[2], 2 * np.pi)
|
|
80
|
+
self.assert_pointwise(coordinates_complex.sphcar, scalar.sphcar, points.T, (-1,))
|
|
81
|
+
self.assert_pointwise(coordinates_complex.sphcar, scalar.sphcar,
|
|
82
|
+
(np.array([-1., 1.])[:, None], np.array([-1., 1.]), 0.), (-1,))
|
|
83
|
+
|
|
84
|
+
def test_igrf_cartesian_wrappers_broadcast(self):
|
|
85
|
+
for name in ('igrf_gsm', 'igrf_gsw'):
|
|
86
|
+
for coords in ((5., np.array([1., 2.]), 1.),
|
|
87
|
+
(np.array([4., 5.])[:, None], np.array([1., 2., 3.]), 1.)):
|
|
88
|
+
with self.subTest(wrapper=name, shapes=tuple(np.shape(c) for c in coords)):
|
|
89
|
+
self.assert_pointwise(getattr(igrf, name), getattr(scalar, name), coords)
|
|
90
|
+
result = getattr(igrf, name)(5., 1., 1.)
|
|
91
|
+
self.assertTrue(all(isinstance(c, float) for c in result))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == '__main__':
|
|
95
|
+
unittest.main()
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Regressions independent of scalar/vectorized implementation agreement."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from geopack import models
|
|
8
|
+
from geopack.vectorized import models as vectorized
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
PARMOD = [2., -20., 2., -5., 1., 1., 1., 1., 1., 1.]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestModelInputRegressions(unittest.TestCase):
|
|
15
|
+
def test_nan_coordinates_return_nan_for_scalar_inputs(self):
|
|
16
|
+
for name in ('t01', 't96'):
|
|
17
|
+
for axis in range(3):
|
|
18
|
+
point = [-6., 1., 2.]
|
|
19
|
+
point[axis] = np.nan
|
|
20
|
+
with self.subTest(model=name, nan_axis=axis), np.errstate(invalid='ignore'):
|
|
21
|
+
expected = getattr(models, name)(PARMOD, .2, *point)
|
|
22
|
+
actual = getattr(vectorized, name)(PARMOD, .2, *point)
|
|
23
|
+
self.assertTrue(np.all(np.isnan(expected)))
|
|
24
|
+
self.assertTrue(np.all(np.isnan(actual)))
|
|
25
|
+
self.assertTrue(all(isinstance(c, float) for c in actual))
|
|
26
|
+
|
|
27
|
+
def test_nan_coordinates_preserve_valid_points_in_multidimensional_batches(self):
|
|
28
|
+
valid_points = np.array([[-6., 1., 2.], [10.0655881232758, 2., 1.], [15., 3., 1.]])
|
|
29
|
+
for name in ('t01', 't96'):
|
|
30
|
+
expected = np.full((3, len(valid_points), 2), np.nan)
|
|
31
|
+
expected[:, :, 0] = np.array([
|
|
32
|
+
getattr(models, name)(PARMOD, .2, *point) for point in valid_points
|
|
33
|
+
]).T
|
|
34
|
+
self.assertTrue(np.all(np.isfinite(expected[:, :, 0])))
|
|
35
|
+
for axis in range(3):
|
|
36
|
+
points = np.repeat(valid_points[:, None, :], 2, axis=1)
|
|
37
|
+
points[:, 1, axis] = np.nan
|
|
38
|
+
with self.subTest(model=name, nan_axis=axis), np.errstate(invalid='ignore'):
|
|
39
|
+
actual = getattr(vectorized, name)(PARMOD, .2, *np.moveaxis(points, -1, 0))
|
|
40
|
+
self.assertEqual(np.shape(actual), expected.shape)
|
|
41
|
+
np.testing.assert_allclose(actual, expected, rtol=1e-10, atol=1e-6,
|
|
42
|
+
equal_nan=True)
|
|
43
|
+
|
|
44
|
+
def test_t01_imf_interconnection_preserves_clock_angle(self):
|
|
45
|
+
# With G1/G2 held fixed, the IMF dependence is exactly the prescribed
|
|
46
|
+
# T01 interconnection field, including outside the magnetopause.
|
|
47
|
+
for function in (models.t01, vectorized.t01):
|
|
48
|
+
for point in ((-6., 2., 1.), (15., 3., 1.)):
|
|
49
|
+
base = list(PARMOD)
|
|
50
|
+
base[2:4] = [0., 0.]
|
|
51
|
+
baseline = np.array(function(base, .2, *point))
|
|
52
|
+
for by in (-5., -1e-8, -0., 0., 1e-8, 5.):
|
|
53
|
+
for bz in (-5., 5.):
|
|
54
|
+
with self.subTest(api=function.__module__, point=point, by=by, bz=bz):
|
|
55
|
+
parameters = list(PARMOD)
|
|
56
|
+
parameters[2:4] = [by, bz]
|
|
57
|
+
theta = np.arctan2(by, bz)
|
|
58
|
+
factor = .05887 + .57691 * np.sin(theta / 2.)**2
|
|
59
|
+
expected = baseline + factor * np.array([0., by, bz])
|
|
60
|
+
actual = function(parameters, .2, *point)
|
|
61
|
+
np.testing.assert_allclose(actual, expected, rtol=1e-12, atol=1e-9)
|
|
62
|
+
|
|
63
|
+
def test_integer_coordinates_match_scalar_fields_in_all_regions(self):
|
|
64
|
+
points = np.array([[5, 1, 1], [10, 0, -5], [15, 1, 1]])
|
|
65
|
+
for name in ('t01', 't96'):
|
|
66
|
+
scalar, batched = getattr(models, name), getattr(vectorized, name)
|
|
67
|
+
expected = np.array([scalar(PARMOD, .4, *point.astype(float)) for point in points]).T
|
|
68
|
+
with self.subTest(model=name, mode='batch'):
|
|
69
|
+
np.testing.assert_allclose(batched(PARMOD, .4, *points.T), expected,
|
|
70
|
+
rtol=1e-10, atol=1e-6)
|
|
71
|
+
for point, field in zip(points, expected.T):
|
|
72
|
+
with self.subTest(model=name, point=point):
|
|
73
|
+
actual = batched(PARMOD, .4, *(int(c) for c in point))
|
|
74
|
+
np.testing.assert_allclose(actual, field, rtol=1e-10, atol=1e-6)
|
|
75
|
+
self.assertTrue(all(isinstance(c, float) for c in actual))
|
|
76
|
+
|
|
77
|
+
def test_t01_broadcast_and_mesh_inputs_match_pointwise_fields(self):
|
|
78
|
+
points = (
|
|
79
|
+
(5., np.array([1., 2.]), 1.),
|
|
80
|
+
(np.array([5., 10.])[:, None], np.array([1., 2., 3.]), -5.),
|
|
81
|
+
np.meshgrid([5., 10.], [1., 2.], [-5., 1.], indexing='ij'),
|
|
82
|
+
)
|
|
83
|
+
for coords in points:
|
|
84
|
+
broadcast = np.broadcast_arrays(*coords)
|
|
85
|
+
expected = np.array([models.t01(PARMOD, .4, *point)
|
|
86
|
+
for point in zip(*(c.ravel() for c in broadcast))])
|
|
87
|
+
expected = expected.T.reshape((3,) + broadcast[0].shape)
|
|
88
|
+
with self.subTest(shape=broadcast[0].shape):
|
|
89
|
+
actual = vectorized.t01(PARMOD, .4, *coords)
|
|
90
|
+
for component in actual:
|
|
91
|
+
self.assertEqual(component.shape, broadcast[0].shape)
|
|
92
|
+
np.testing.assert_allclose(actual, expected, rtol=1e-10, atol=1e-6)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == '__main__':
|
|
96
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/notebooks/04_accuracy_validation.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/python_code_samples/_helpers.py
RENAMED
|
File without changes
|
{geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/examples/python_code_samples/basic_usage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack/Mageometry/field_line_geometry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack_vectorize.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{geopack_vectorize-2.0.1 → geopack_vectorize-2.0.3}/geopack_vectorize.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|