geopack-vectorize 2.0.0__tar.gz → 2.0.1__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.
Files changed (75) hide show
  1. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/CHANGELOG.md +14 -1
  2. geopack_vectorize-2.0.1/PKG-INFO +459 -0
  3. geopack_vectorize-2.0.1/README.md +411 -0
  4. geopack_vectorize-2.0.1/examples/notebooks/03_performance_comparison.ipynb +1861 -0
  5. geopack_vectorize-2.0.1/examples/notebooks/07_fieldline_geometry_and_derivatives.ipynb +1182 -0
  6. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/examples/notebooks/directional_derivatives_maps/dipole_field_directional_derivatives.ipynb +35 -13
  7. geopack_vectorize-2.0.1/examples/notebooks/directional_derivatives_maps/t96_field_directional_derivatives.ipynb +1782 -0
  8. geopack_vectorize-2.0.1/examples/python_code_samples/_helpers.py +84 -0
  9. {geopack_vectorize-2.0.0/examples → geopack_vectorize-2.0.1/examples/python_code_samples}/basic_usage.py +45 -81
  10. {geopack_vectorize-2.0.0/examples → geopack_vectorize-2.0.1/examples/python_code_samples}/field_line_9_derivatives_example.py +10 -62
  11. {geopack_vectorize-2.0.0/examples → geopack_vectorize-2.0.1/examples/python_code_samples}/field_line_geometry_example.py +51 -71
  12. geopack_vectorize-2.0.1/examples/readme_examples.py +145 -0
  13. geopack_vectorize-2.0.1/geopack/Mageometry/__init__.py +52 -0
  14. {geopack_vectorize-2.0.0/geopack/vectorized → geopack_vectorize-2.0.1/geopack/Mageometry}/field_line_geometry.py +0 -2
  15. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/__init__.py +66 -28
  16. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/geopack.py +21 -13
  17. geopack_vectorize-2.0.1/geopack/t01.py +5 -0
  18. geopack_vectorize-2.0.1/geopack/t04.py +5 -0
  19. geopack_vectorize-2.0.1/geopack/t89.py +5 -0
  20. geopack_vectorize-2.0.1/geopack/t96.py +5 -0
  21. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/vectorized/__init__.py +10 -38
  22. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/vectorized/igrf.py +0 -2
  23. geopack_vectorize-2.0.1/geopack/vectorized/models/__init__.py +8 -0
  24. geopack_vectorize-2.0.1/geopack/vectorized/models/t01.py +1969 -0
  25. geopack_vectorize-2.0.1/geopack/vectorized/models/t04.py +1985 -0
  26. geopack_vectorize-2.0.1/geopack/vectorized/models/t89.py +358 -0
  27. geopack_vectorize-2.0.1/geopack/vectorized/models/t96.py +2446 -0
  28. geopack_vectorize-2.0.1/geopack_vectorize.egg-info/PKG-INFO +459 -0
  29. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack_vectorize.egg-info/SOURCES.txt +20 -8
  30. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack_vectorize.egg-info/requires.txt +4 -0
  31. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/pyproject.toml +11 -4
  32. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/setup.py +2 -2
  33. geopack_vectorize-2.0.1/tests/installed_smoke.py +107 -0
  34. geopack_vectorize-2.0.1/tests/test_distribution.py +73 -0
  35. geopack_vectorize-2.0.0/PKG-INFO +0 -346
  36. geopack_vectorize-2.0.0/README.md +0 -302
  37. geopack_vectorize-2.0.0/examples/notebooks/03_performance_comparison.ipynb +0 -1749
  38. geopack_vectorize-2.0.0/examples/notebooks/07_fieldline_geometry_and_derivatives.ipynb +0 -1152
  39. geopack_vectorize-2.0.0/examples/notebooks/directional_derivatives_maps/t96_field_directional_derivatives.ipynb +0 -4
  40. geopack_vectorize-2.0.0/examples/test_9_formulas.py +0 -134
  41. geopack_vectorize-2.0.0/examples/test_unit_vectors.py +0 -162
  42. geopack_vectorize-2.0.0/geopack_vectorize.egg-info/PKG-INFO +0 -346
  43. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/LICENSE +0 -0
  44. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/MANIFEST.in +0 -0
  45. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/examples/notebooks/01_coordinate_transformations_guide.ipynb +0 -0
  46. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/examples/notebooks/02_magnetic_field_models_guide.ipynb +0 -0
  47. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/examples/notebooks/04_accuracy_validation.ipynb +0 -0
  48. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/examples/notebooks/05_field_line_tracing_guide.ipynb +0 -0
  49. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/examples/notebooks/06_field_line_tracing_validation.ipynb +0 -0
  50. {geopack_vectorize-2.0.0/examples/notebooks → geopack_vectorize-2.0.1/examples/python_code_samples}/magnetic_field_simple_sample.py +0 -0
  51. {geopack_vectorize-2.0.0/geopack/vectorized → geopack_vectorize-2.0.1/geopack/Mageometry}/field_line_directional_derivatives.py +0 -0
  52. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/core.py +0 -0
  53. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/igrf_coeffs/igrf10coeffs.txt +0 -0
  54. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/igrf_coeffs/igrf11coeffs.txt +0 -0
  55. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/igrf_coeffs/igrf12coeffs.txt +0 -0
  56. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/igrf_coeffs/igrf13coeffs.txt +0 -0
  57. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/igrf_coeffs/igrf14coeffs.txt +0 -0
  58. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/igrf_coeffs/igrf9coeffs.txt +0 -0
  59. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/models/__init__.py +0 -0
  60. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/models/t01.py +0 -0
  61. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/models/t04.py +0 -0
  62. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/models/t89.py +0 -0
  63. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/models/t96.py +0 -0
  64. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/print_structure.py +0 -0
  65. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/vectorized/condip1_exact.py +0 -0
  66. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/vectorized/coordinates.py +0 -0
  67. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/vectorized/coordinates_complex.py +0 -0
  68. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack/vectorized/trace.py +0 -0
  69. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack_vectorize.egg-info/dependency_links.txt +0 -0
  70. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/geopack_vectorize.egg-info/top_level.txt +0 -0
  71. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/requirements.txt +0 -0
  72. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/setup.cfg +0 -0
  73. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/tests/test_trace_vectorized.py +0 -0
  74. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/tests/test_trace_vectorized_with_vectorized_models.py +0 -0
  75. {geopack_vectorize-2.0.0 → geopack_vectorize-2.0.1}/tests/test_vectorized_models.py +0 -0
@@ -5,6 +5,19 @@ 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
+ ## [2.0.1] - 2026-10-28
9
+
10
+ ### Fixed
11
+ - Include every `geopack` subpackage in wheels and source distributions, including
12
+ `geopack.vectorized.models`, so a normal pip installation can be imported.
13
+ - Load bundled IGRF coefficients on import without network access or writes to
14
+ the installation directory. Updates remain available through `update_igrf()`,
15
+ using HTTPS and a bounded request timeout.
16
+ - Restore `t89_vectorized`, `t96_vectorized`, `t01_vectorized`, and `t04_vectorized`
17
+ imports from `geopack.vectorized`, including the quick-start import on PyPI.
18
+ - Restore upstream scalar model imports (`from geopack.t89 import t89`, and the
19
+ corresponding T96, T01, and T04 paths) used by consumers such as pySPEDAS.
20
+
8
21
  ## [2.0.0] - 2026-02-11
9
22
 
10
23
  ### Breaking
@@ -96,4 +109,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
96
109
 
97
110
  ## [1.0.11] - Previous releases
98
111
 
99
- See git history for changes in previous versions.
112
+ See git history for changes in previous versions.
@@ -0,0 +1,459 @@
1
+ Metadata-Version: 2.4
2
+ Name: geopack-vectorize
3
+ Version: 2.0.1
4
+ Summary: Python implementation of geopack and Tsyganenko models with optimized vectorization
5
+ Home-page: https://github.com/Butadiene/geopack-vectorize
6
+ Author: geopack-vectorize contributors
7
+ Author-email: Sheng Tian <ts0110@atmos.ucla.edu>
8
+ Maintainer-email: Sheng Tian <ts0110@atmos.ucla.edu>
9
+ License: MIT
10
+ Project-URL: Homepage, https://github.com/Butadiene/geopack-vectorize
11
+ Project-URL: Documentation, https://github.com/Butadiene/geopack-vectorize#readme
12
+ Project-URL: Repository, https://github.com/Butadiene/geopack-vectorize
13
+ Project-URL: Issues, https://github.com/Butadiene/geopack-vectorize/issues
14
+ Keywords: geopack,space physics,Tsyganenko model,magnetosphere,geomagnetic field
15
+ Platform: any
16
+ Classifier: Development Status :: 4 - Beta
17
+ Classifier: Intended Audience :: Science/Research
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.7
22
+ Classifier: Programming Language :: Python :: 3.8
23
+ Classifier: Programming Language :: Python :: 3.9
24
+ Classifier: Programming Language :: Python :: 3.10
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Topic :: Scientific/Engineering :: Physics
27
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
28
+ Requires-Python: >=3.7
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: numpy>=1.16.0
32
+ Requires-Dist: scipy>=1.0.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=6.0; extra == "dev"
35
+ Requires-Dist: pytest-cov>=2.0; extra == "dev"
36
+ Requires-Dist: setuptools>=61.0; extra == "dev"
37
+ Requires-Dist: wheel; extra == "dev"
38
+ Requires-Dist: matplotlib>=3.0; extra == "dev"
39
+ Provides-Extra: examples
40
+ Requires-Dist: matplotlib>=3.0; extra == "examples"
41
+ Requires-Dist: jupyter>=1.0; extra == "examples"
42
+ Requires-Dist: pandas>=1.0; extra == "examples"
43
+ Requires-Dist: psutil>=5.0; extra == "examples"
44
+ Dynamic: author
45
+ Dynamic: home-page
46
+ Dynamic: license-file
47
+ Dynamic: platform
48
+
49
+ # GEOPACK-VECTORIZE
50
+
51
+ A vectorized implementation extending the excellent Python [geopack](https://github.com/tsssss/geopack) library by Sheng Tian, adding high-performance NumPy-based versions of Tsyganenko magnetospheric field models and field line tracing algorithms.
52
+
53
+ ## Overview
54
+
55
+ This project builds upon the solid foundation of the Python geopack library, which provides faithful implementations of the Tsyganenko magnetospheric field models (T89, T96, T01, T04) and the IGRF geomagnetic field model, originally developed in Fortran by N.A. Tsyganenko.
56
+
57
+ geopack-vectorize extends the original geopack by adding vectorized implementations that leverage NumPy for parallel computation, achieving 20-150x performance improvements while maintaining machine-precision accuracy and full backward compatibility with the original scalar functions.
58
+
59
+ ### What This Project Adds
60
+
61
+ - **Vectorized Field Models**: NumPy-based implementations of all Tsyganenko models (T89, T96, T01, T04) that process arrays of points simultaneously
62
+ - **Vectorized Field Line Tracing**: Parallel tracing of multiple field lines with improved boundary interpolation
63
+ - **Vectorized Coordinate Transforms**: Array-based transformations between all coordinate systems
64
+ - **Full Backward Compatibility**: All original geopack functions remain unchanged and available
65
+ - **Comprehensive Validation**: Extensive test suite ensuring < 10⁻¹¹ relative error vs original implementations
66
+
67
+ ## Upgrading to v2.0.0
68
+
69
+ 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:
70
+
71
+ ```bash
72
+ pip uninstall geopack-vectorized
73
+ pip install geopack-vectorize
74
+ ```
75
+
76
+ Other notable changes from v1.1.4 to v2.0.0:
77
+ - Internal module structure reorganized (all vectorized code now under `geopack/vectorized/`)
78
+ - All comments and docstrings translated to English
79
+ - Docstrings standardized to NumPy format
80
+ - Example notebooks cleaned up and reorganized
81
+ - See [RELEASE_NOTES_v2.0.0.md](docs/releases/RELEASE_NOTES_v2.0.0.md) for the full list of changes
82
+
83
+ **Top-level imports are unchanged** — existing code using `from geopack import t96_vectorized` etc. will continue to work without modification.
84
+
85
+ ## Installation
86
+
87
+ ### Requirements
88
+ - Python 3.7+
89
+ - NumPy
90
+ - SciPy
91
+
92
+ ### Install from PyPI
93
+ ```bash
94
+ pip install geopack-vectorize
95
+ ```
96
+
97
+ IGRF coefficient files are included in the package. Importing `geopack` and
98
+ running the field models work offline. To explicitly download newer coefficient
99
+ files, call `geopack.update_igrf()` followed by `geopack.init_igrf()`; downloading
100
+ requires internet access.
101
+
102
+ The upstream scalar import paths, such as `from geopack.t89 import t89`, are
103
+ also supported. Both `geopack` and `geopack-vectorize` install files into the
104
+ same `geopack` Python package. In environments that require both distributions
105
+ (for example through pySPEDAS), install `geopack-vectorize` last; subsequently
106
+ reinstalling or removing either distribution can overwrite or remove shared
107
+ files. Use a separate virtual environment when testing or changing installations.
108
+
109
+ ### Install from Source
110
+ ```bash
111
+ git clone https://github.com/Butadiene/geopack-vectorize.git
112
+ cd geopack-vectorize
113
+ pip install -e .
114
+ ```
115
+
116
+ ## Usage Examples
117
+
118
+ All vectorized functions accept both scalars and NumPy arrays. Call `geopack.recalc(ut)` once before using any model or transform. See [`examples/readme_examples.py`](examples/readme_examples.py) for a runnable version of the code below. For detailed function descriptions, please also refer to the [upstream geopack README](https://github.com/tsssss/geopack).
119
+
120
+ ```python
121
+ import geopack
122
+ import numpy as np
123
+
124
+ ut = 100 # Unix timestamp (seconds since 1970-01-01)
125
+ ps = geopack.recalc(ut)
126
+ ```
127
+
128
+ ### Coordinate Transformations
129
+ ```python
130
+ from geopack import geogsm_vectorized
131
+
132
+ # Convert multiple GEO points to GSM (j=1: GEO→GSM, j=-1: GSM→GEO)
133
+ x_geo = np.array([1.0, 2.0, 3.0])
134
+ y_geo = np.array([0.5, 1.0, 1.5])
135
+ z_geo = np.array([0.0, 0.0, 0.0])
136
+
137
+ x_gsm, y_gsm, z_gsm = geogsm_vectorized(x_geo, y_geo, z_geo, j=1)
138
+ ```
139
+
140
+ ### Internal Field (IGRF and Dipole)
141
+ ```python
142
+ from geopack import igrf_gsm_vectorized
143
+
144
+ # IGRF magnetic field at multiple GSM positions (Earth radii)
145
+ x = np.array([2.0, 3.0, 4.0, 5.0])
146
+ y = np.zeros(4)
147
+ z = np.zeros(4)
148
+
149
+ bx, by, bz = igrf_gsm_vectorized(x, y, z) # returns nT
150
+
151
+ # Dipole field at the same positions (accepts scalars or arrays)
152
+ dx, dy, dz = geopack.dip(x, y, z)
153
+ ```
154
+
155
+ ### Tsyganenko External Field Models
156
+ ```python
157
+ from geopack import t96_vectorized
158
+
159
+ # T96 parameters: [Pdyn, Dst, ByIMF, BzIMF, 0, 0, 0, 0, 0, 0]
160
+ parmod = np.array([2.0, -20.0, 0.0, -5.0, 0, 0, 0, 0, 0, 0])
161
+
162
+ x = np.array([5.0, 6.0, 7.0, 8.0, 9.0]) # GSM coordinates (Re)
163
+ y = np.zeros(5)
164
+ z = np.zeros(5)
165
+
166
+ bx, by, bz = t96_vectorized(parmod, ps, x, y, z) # GSM components (nT)
167
+
168
+ # Tsyganenko models give only the external (magnetospheric) field.
169
+ # Add an internal field to get the total magnetic field:
170
+ bx_int, by_int, bz_int = geopack.dip(x, y, z)
171
+ bx_total = bx + bx_int
172
+ by_total = by + by_int
173
+ bz_total = bz + bz_int
174
+ ```
175
+
176
+ ### Field Line Tracing
177
+ ```python
178
+ from geopack import trace_vectorized
179
+
180
+ # Trace using dipole (internal) + T96 (external)
181
+ x0 = np.array([5.0, 6.0, 7.0, 8.0])
182
+ y0 = np.zeros(4)
183
+ z0 = np.zeros(4)
184
+
185
+ xf, yf, zf, status = trace_vectorized(
186
+ x0, y0, z0, dir=-1, rlim=30, parmod=parmod, exname="t96", inname="dip"
187
+ )
188
+ # status: 0 = hit inner boundary, 1 = hit outer boundary, 2 = max steps
189
+ ```
190
+
191
+ ### Field Line Geometry (Frenet-Serret Frame)
192
+ ```python
193
+ from geopack import field_line_curvature, field_line_frenet_frame
194
+
195
+ # Combined model: dipole (internal) + T96 (external)
196
+ def dip_plus_t96(parmod, ps, x, y, z):
197
+ bx_d, by_d, bz_d = geopack.dip(x, y, z)
198
+ bx_t, by_t, bz_t = t96_vectorized(parmod, ps, x, y, z)
199
+ return bx_d + bx_t, by_d + by_t, bz_d + bz_t
200
+
201
+ # Curvature at several points along the noon meridian
202
+ x = np.array([5.0, 6.0, 7.0, 8.0])
203
+ y = np.zeros(4)
204
+ z = np.zeros(4)
205
+
206
+ kappa = field_line_curvature(dip_plus_t96, parmod, ps, x, y, z, delta=1e-3)
207
+ # kappa: field line curvature [1/Re]
208
+
209
+ # Full Frenet-Serret frame (tangent, normal, binormal) + curvature
210
+ tx, ty, tz, nx, ny, nz, bx, by, bz, curvature = \
211
+ field_line_frenet_frame(dip_plus_t96, parmod, ps, x, y, z, delta=1e-3)
212
+ # curvature [1/Re]; tangent, normal, binormal are unit vectors (dimensionless)
213
+ ```
214
+
215
+ ### Field Line Directional Derivatives
216
+ ```python
217
+ from geopack import field_line_directional_derivatives
218
+
219
+ # All 9 directional derivatives (using combined dipole + T96 model)
220
+ derivs = field_line_directional_derivatives(
221
+ dip_plus_t96, parmod, ps, x, y, z, delta=1e-3
222
+ )
223
+ # All derivative values are in units of [1/Re]
224
+
225
+ # Tangential derivatives (∂/∂T)
226
+ # derivs['dT_dT_n'] (∂T/∂T)·n = κ (curvature)
227
+ # derivs['dT_dT_b'] (∂T/∂T)·b = 0 (identity)
228
+ # derivs['dn_dT_b'] (∂n/∂T)·b = τ (torsion)
229
+
230
+ # Normal derivatives (∂/∂n)
231
+ # derivs['dT_dn_n'] (∂T/∂n)·n
232
+ # derivs['dT_dn_b'] (∂T/∂n)·b
233
+ # derivs['dn_dn_b'] (∂n/∂n)·b
234
+
235
+ # Binormal derivatives (∂/∂b)
236
+ # derivs['dn_db_b'] (∂n/∂b)·b
237
+ # derivs['dn_db_T'] (∂n/∂b)·T
238
+ # derivs['db_db_T'] (∂b/∂b)·T
239
+ ```
240
+
241
+ > **Note:** The field line geometry modules now live in `geopack.Mageometry`. Top-level imports (including the legacy `*_vectorized` aliases such as `field_line_curvature_vectorized`) keep working, but deep-path imports from the old location (e.g. `from geopack.vectorized.field_line_geometry import ...`) are no longer supported.
242
+
243
+ ## Vectorized Components
244
+
245
+ ### Coordinate Transformations
246
+
247
+ All vectorized transforms accept scalar or NumPy array inputs. Each pairwise transform uses a direction flag `j`: `j=1` for the forward direction, `j=-1` for the inverse. All coordinate values are in Earth radii (Re); angles in radians. See [SPENVIS Coordinate Transformations](https://www.spenvis.oma.be/help/background/coortran/coortran.html) for coordinate system definitions.
248
+
249
+ | Function | Arguments | Forward (j=1) | Inverse (j=-1) |
250
+ |----------|-----------|---------------|-----------------|
251
+ | `geogsm_vectorized` | `(x, y, z, j)` | GEO → GSM | GSM → GEO |
252
+ | `geomag_vectorized` | `(x, y, z, j)` | GEO → MAG | MAG → GEO |
253
+ | `geigeo_vectorized` | `(x, y, z, j)` | GEI → GEO | GEO → GEI |
254
+ | `gsmgse_vectorized` | `(x, y, z, j)` | GSM → GSE | GSE → GSM |
255
+ | `smgsm_vectorized` | `(x, y, z, j)` | SM → GSM | GSM → SM |
256
+ | `magsm_vectorized` | `(x, y, z, j)` | MAG → SM | SM → MAG |
257
+ | `gswgsm_vectorized` | `(x, y, z, j)` | GSW → GSM | GSM → GSW |
258
+
259
+ Spherical/Cartesian and field-vector transforms:
260
+ - `sphcar_vectorized(r, theta, phi, j)` — Spherical ↔ Cartesian (j=1: Sph→Cart, j=-1: Cart→Sph)
261
+ - `bspcar_vectorized(theta, phi, br, btheta, bphi)` — B-field components: Spherical → Cartesian
262
+ - `bcarsp_vectorized(x, y, z, bx, by, bz)` — B-field components: Cartesian → Spherical
263
+
264
+ ### Internal Field (IGRF and Dipole)
265
+
266
+ Vectorized IGRF and dipole field functions. All return magnetic field components in nanotesla (nT). IGRF covers years 1900–2025 with extrapolation beyond.
267
+
268
+ - `igrf_geo_vectorized(r, theta, phi)` — IGRF in spherical GEO coordinates (r in Re, angles in radians); returns `(br, btheta, bphi)`
269
+ - `igrf_gsm_vectorized(x, y, z)` — IGRF in GSM Cartesian coordinates (Re); returns `(bx, by, bz)`
270
+ - `igrf_gsw_vectorized(x, y, z)` — IGRF in GSW Cartesian coordinates (Re); returns `(bx, by, bz)`
271
+ - `dip(x, y, z)` — Dipole field in GSM coordinates (Re); natively array-compatible via NumPy operations
272
+
273
+ ### External Field Models (Tsyganenko)
274
+
275
+ Tsyganenko magnetospheric field models. All take positions in **GSM coordinates** (Re) and return `(bx, by, bz)` in nanotesla (nT, GSM). `ps` is the dipole tilt angle (radians) returned by `recalc()`.
276
+
277
+ - `t89_vectorized(iopt, ps, x, y, z)` — T89 model; `iopt` is the Kp index (1–7)
278
+ - `t96_vectorized(parmod, ps, x, y, z)` — T96 model; `parmod = [Pdyn, Dst, ByIMF, BzIMF, 0, 0, 0, 0, 0, 0]`
279
+ - `t01_vectorized(parmod, ps, x, y, z)` — T01 model; `parmod = [Pdyn, Dst, ByIMF, BzIMF, G1, G2, 0, 0, 0, 0]`
280
+ - `t04_vectorized(parmod, ps, x, y, z)` — T04 model; `parmod = [Pdyn, Dst, ByIMF, BzIMF, W1, W2, W3, W4, W5, W6]`
281
+
282
+ For detailed parameter descriptions, see the [upstream geopack README](https://github.com/tsssss/geopack).
283
+
284
+ **Note:** Tsyganenko models provide only the *external* (magnetospheric) contribution. To obtain the total magnetic field, add an internal field (IGRF or dipole):
285
+
286
+ ```python
287
+ # Total field = internal (dipole) + external (T96)
288
+ bx_int, by_int, bz_int = geopack.dip(x, y, z)
289
+ bx_ext, by_ext, bz_ext = t96_vectorized(parmod, ps, x, y, z)
290
+
291
+ bx_total = bx_int + bx_ext
292
+ by_total = by_int + by_ext
293
+ bz_total = bz_int + bz_ext
294
+ ```
295
+
296
+ ### Field Line Tracing
297
+
298
+ `trace_vectorized` traces magnetic field lines from given starting points to inner or outer boundaries.
299
+
300
+ ```python
301
+ trace_vectorized(xi, yi, zi, dir, rlim, r0, parmod, exname, inname, ...)
302
+ ```
303
+
304
+ | Parameter | Type | Default | Description |
305
+ |-----------|------|---------|-------------|
306
+ | `xi, yi, zi` | float or array | *(required)* | Starting positions in GSM coordinates (Re) |
307
+ | `dir` | float | `1.0` | Tracing direction: `+1` antiparallel to **B**, `-1` parallel to **B** |
308
+ | `rlim` | float | `10.0` | Outer boundary radius (Re); tracing stops when r >= rlim |
309
+ | `r0` | float | `1.0` | Inner boundary sphere radius (Re); tracing stops when r <= r0 |
310
+ | `parmod` | array | `2` | Model parameters (scalar Kp for T89; 10-element array for T96/T01/T04) |
311
+ | `exname` | str | `"t89"` | External field model: `"t89"`, `"t96"`, `"t01"`, or `"t04"` |
312
+ | `inname` | str | `"igrf"` | Internal field model: `"igrf"` or `"dip"` |
313
+ | `maxloop` | int | `1000` | Maximum number of integration steps per trace |
314
+ | `return_full_path` | bool | `False` | If `True`, returns full field line trajectories as masked arrays |
315
+ | `strict_scalar_models` | bool | `True` | If `True`, evaluates field models point-by-point for bitwise match with scalar `trace()` |
316
+ | `return_nsteps` | bool | `False` | If `True`, also returns the number of integration steps per trace |
317
+
318
+ **Returns:** `(xf, yf, zf, status)` — final positions (Re, GSM) and integer status codes:
319
+ - `0` — reached inner boundary (r <= r0)
320
+ - `1` — reached outer boundary (r >= rlim)
321
+ - `2` — exceeded maximum integration steps
322
+
323
+ Optional returns (appended when the corresponding flag is `True`):
324
+ - `xx, yy, zz` — full path arrays (`return_full_path`)
325
+ - `nsteps` — per-trace step counts (`return_nsteps`)
326
+
327
+ Related functions for field line analysis:
328
+ - Field line geometry (curvature, torsion, Frenet-Serret frame) — see [Usage Examples](#field-line-geometry-frenet-serret-frame)
329
+ - Directional derivatives along field lines — see [Usage Examples](#field-line-directional-derivatives)
330
+
331
+ ## Documentation and Examples
332
+
333
+ Example notebooks are available in `examples/notebooks/`. Install matplotlib and pandas to run them.
334
+
335
+ ### Tutorial Notebooks
336
+ - `01_coordinate_transformations_guide` — Coordinate system transforms
337
+ - `02_magnetic_field_models_guide` — Field model usage (T89, T96, T01, T04)
338
+ - `03_performance_comparison` — Scalar vs vectorized benchmarks
339
+ - `04_accuracy_validation` — Numerical accuracy verification
340
+ - `05_field_line_tracing_guide` — Field line tracing tutorial
341
+ - `06_field_line_tracing_validation` — Tracing accuracy validation
342
+ - `07_fieldline_geometry_and_derivatives` — Frenet-Serret frame and directional derivatives
343
+
344
+ ### Advanced Examples (`examples/notebooks/directional_derivatives_maps/`)
345
+ - `dipole_field_directional_derivatives` — Dipole field directional derivative maps
346
+ - `t96_field_directional_derivatives` — T96 model directional derivative and FAC maps
347
+
348
+ ## Performance Benchmarks
349
+
350
+ Regenerate this table with [`benchmark/readme_benchmarks.py`](benchmark/readme_benchmarks.py) (`--plain` for plain text output).
351
+
352
+ | Component | Scalar (100 pts) [s] | Vectorized [s] | Speedup |
353
+ |-----------|--------------------:|---------------------:|--------:|
354
+ | Coordinate Transforms (subset) | 0.000 | 0.000 | **6.3x** |
355
+ | IGRF (GSW) | 0.006 | 0.002 | **3.2x** |
356
+ | T89 Model | 0.005 | 0.000 | **22.1x** |
357
+ | T96 Model | 0.127 | 0.023 | **5.6x** |
358
+ | T01 Model | 0.205 | 0.027 | **7.5x** |
359
+ | T04 Model | 0.200 | 0.025 | **7.9x** |
360
+ | Field Line Tracing (vectorized field models) [scalar extrap from 50] | 2.343 | 2.225 | **1.1x** |
361
+
362
+ | Component | Scalar (1000 pts) [s] | Vectorized [s] | Speedup |
363
+ |-----------|---------------------:|---------------------:|--------:|
364
+ | Coordinate Transforms (subset) | 0.004 | 0.000 | **48.9x** |
365
+ | IGRF (GSW) | 0.068 | 0.006 | **11.3x** |
366
+ | T89 Model | 0.045 | 0.000 | **116x** |
367
+ | T96 Model | 1.046 | 0.036 | **29.2x** |
368
+ | T01 Model | 1.815 | 0.039 | **46.0x** |
369
+ | T04 Model | 1.820 | 0.042 | **43.5x** |
370
+ | Field Line Tracing (vectorized field models) [scalar extrap from 50] | 21.607 | 3.050 | **7.1x** |
371
+
372
+ ### Where Does the Speedup Come From?
373
+
374
+ The speedup is not simply a function-call-count effect. Modeling the time of one vectorized call on an n-element array as `t_vec(n) ≈ a + b·n` separates a fixed per-call overhead `a` from the marginal cost per point `b`:
375
+
376
+ | Component | Overhead a [ms/call] | Marginal b [µs/point] | Scalar [µs/point] | Per-point ratio | Break-even n* |
377
+ |-----------|---------------------:|----------------------:|------------------:|----------------:|--------------:|
378
+ | T89 | 0.26 | 0.57 | 46.9 | **82x** | 2 |
379
+ | T96 | 11 | 8.9 | 1090 | **123x** | 14 |
380
+ | T01 | 16 | 21.2 | 1828 | **86x** | 10 |
381
+ | T04 | 15 | 29.1 | 1870 | **64x** | 8 |
382
+ | IGRF (GSW) | 1.3 | 3.6 | 57.9 | **16x** | 42 |
383
+
384
+ - **A vectorized call with n = 1 is slower than a scalar call** (the fixed overhead `a` is paid on every call), so the gain does not come from merely reducing the number of function calls. Below the break-even size n\* the scalar functions are faster.
385
+ - **The overhead `a` scales with model complexity** (T89 ≪ T96/T01/T04): it is the accumulated per-array-operation cost (NumPy dispatch, temporaries, both branches of `np.where`), not a constant per-call cost.
386
+ - **The per-point cost ratio far exceeds the SIMD width** for float64 (4 lanes AVX2 / 8 lanes AVX-512), so SIMD alone cannot explain it. The dominant mechanism is amortizing the Python interpreter's per-operation overhead across array elements in NumPy's compiled loops.
387
+
388
+ Regenerate this table with [`benchmark/readme_overhead_decomposition.py`](benchmark/readme_overhead_decomposition.py) (`--plain` for plain text output). A step-by-step version with figures is in [`examples/notebooks/03_performance_comparison.ipynb`](examples/notebooks/03_performance_comparison.ipynb), Section 3c.
389
+
390
+ ## Accuracy Validation
391
+
392
+ The vectorized implementations are validated against the original scalar functions across a 100 × 100 grid in the X-Z meridian plane (Y = 0, X: 2 to −10 Re, Z: 6 to −6 Re) using the combined IGRF + T96 total field. Relative error is defined as (B_scalar − B_vector) / |B_scalar|.
393
+
394
+ Regenerate these figures with [`benchmark/readme_validation.py`](benchmark/readme_validation.py).
395
+
396
+ | Metric | Value |
397
+ |--------|------:|
398
+ | Points evaluated | 9,792 |
399
+ | Max \|relative error\| | 9.56 × 10⁻¹² |
400
+ | Mean \|relative error\| | 6.72 × 10⁻¹⁴ |
401
+ | Median \|relative error\| | 2.54 × 10⁻¹⁶ |
402
+ | Scalar computation | 23.72 s |
403
+ | Vectorized computation | 0.21 s |
404
+ | Speedup | **116x** |
405
+
406
+ The median error is near double-precision machine epsilon (~1.1 × 10⁻¹⁶), confirming that the vectorized path reproduces the scalar results to floating-point precision.
407
+
408
+ **Error distribution** — Most points cluster below 10⁻¹⁵; the tail extends to ~10⁻¹¹.
409
+
410
+ ![Relative error histogram](benchmark/readme_validation_histogram.png)
411
+
412
+ **Spatial error map** — The largest errors concentrate near the magnetopause/cusp boundary where T96 current-sheet gradients are steepest, but remain negligible everywhere.
413
+
414
+ ![Relative error colormap](benchmark/readme_validation_colormap.png)
415
+
416
+ ## Technical Details
417
+
418
+ ### Vectorization Approach
419
+ - Full NumPy broadcasting support
420
+ - Elimination of all Python loops
421
+ - Optimized conditional logic using `np.where`
422
+ - Safe numerical operations with proper edge case handling
423
+ - Memory-efficient implementations
424
+
425
+ ### Accuracy Guarantees
426
+ - Maximum relative error < 10⁻¹¹ vs scalar implementations (IGRF + T96 total field)
427
+ - Median relative error at machine epsilon (~10⁻¹⁶)
428
+ - Validated across 9,792 grid points in the X-Z meridian plane
429
+ - Comprehensive test suite with configurable tolerances
430
+ - Proper handling of boundary conditions
431
+
432
+ ### Performance Optimization
433
+ - Batch processing capabilities for millions of points
434
+ - Linear memory scaling with input size
435
+ - GPU-ready array operations
436
+ - Minimal Python overhead
437
+
438
+ ## Attribution and Acknowledgments
439
+
440
+ This project extends the excellent Python [geopack](https://github.com/tsssss/geopack) implementation by Sheng Tian, which has been invaluable to the space physics community. The original geopack provides a robust, well-tested foundation that faithfully reproduces the Fortran implementations.
441
+
442
+ The original Fortran GEOPACK code and Tsyganenko models were developed by N.A. Tsyganenko and are available at:
443
+ - https://geo.phys.spbu.ru/~tsyganenko/modeling.html
444
+ - https://ccmc.gsfc.nasa.gov/models/
445
+
446
+ We are grateful to both Sheng Tian for the Python implementation and N.A. Tsyganenko for the original models that have been fundamental to magnetospheric physics research for decades.
447
+
448
+ ## License
449
+
450
+ This project maintains the MIT License from the original geopack implementation.
451
+
452
+ ## References
453
+
454
+ - Tian, S., Frissell, N., w2ruf, Lewis, J. & Lei Cai, Ph. D. tsssss/geopack: v1.0.12. Zenodo https://doi.org/10.5281/zenodo.15110787 (2025).
455
+
456
+ - Tsyganenko, N. A. (1995), "Modeling the Earth's magnetospheric magnetic field", J. Geophys. Res.
457
+ - Tsyganenko, N. A. (2002), "A model of the near magnetosphere with a dawn-dusk asymmetry", J. Geophys. Res.
458
+ - Tsyganenko, N. A. and M. I. Sitnov (2005), "Modeling the dynamics of the inner magnetosphere during strong geomagnetic storms", J. Geophys. Res.
459
+ - International Geomagnetic Reference Field: https://www.ngdc.noaa.gov/IAGA/vmod/igrf.html