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