geopack-vectorize 2.0.2__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.
Files changed (69) hide show
  1. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/CHANGELOG.md +9 -0
  2. {geopack_vectorize-2.0.2/geopack_vectorize.egg-info → geopack_vectorize-2.0.3}/PKG-INFO +1 -1
  3. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/__init__.py +1 -1
  4. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/coordinates_complex.py +2 -0
  5. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/models/t01.py +2 -1
  6. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/models/t96.py +4 -4
  7. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3/geopack_vectorize.egg-info}/PKG-INFO +1 -1
  8. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/pyproject.toml +1 -1
  9. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/setup.py +1 -1
  10. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/tests/test_coordinate_broadcasting.py +16 -0
  11. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/tests/test_model_input_regressions.py +29 -0
  12. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/LICENSE +0 -0
  13. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/MANIFEST.in +0 -0
  14. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/README.md +0 -0
  15. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/notebooks/01_coordinate_transformations_guide.ipynb +0 -0
  16. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/notebooks/02_magnetic_field_models_guide.ipynb +0 -0
  17. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/notebooks/03_performance_comparison.ipynb +0 -0
  18. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/notebooks/04_accuracy_validation.ipynb +0 -0
  19. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/notebooks/05_field_line_tracing_guide.ipynb +0 -0
  20. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/notebooks/06_field_line_tracing_validation.ipynb +0 -0
  21. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/notebooks/07_fieldline_geometry_and_derivatives.ipynb +0 -0
  22. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/notebooks/directional_derivatives_maps/dipole_field_directional_derivatives.ipynb +0 -0
  23. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/notebooks/directional_derivatives_maps/t96_field_directional_derivatives.ipynb +0 -0
  24. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/python_code_samples/_helpers.py +0 -0
  25. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/python_code_samples/basic_usage.py +0 -0
  26. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/python_code_samples/field_line_9_derivatives_example.py +0 -0
  27. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/python_code_samples/field_line_geometry_example.py +0 -0
  28. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/python_code_samples/magnetic_field_simple_sample.py +0 -0
  29. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/examples/readme_examples.py +0 -0
  30. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/Mageometry/__init__.py +0 -0
  31. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/Mageometry/field_line_directional_derivatives.py +0 -0
  32. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/Mageometry/field_line_geometry.py +0 -0
  33. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/core.py +0 -0
  34. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/geopack.py +0 -0
  35. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf10coeffs.txt +0 -0
  36. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf11coeffs.txt +0 -0
  37. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf12coeffs.txt +0 -0
  38. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf13coeffs.txt +0 -0
  39. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf14coeffs.txt +0 -0
  40. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/igrf_coeffs/igrf9coeffs.txt +0 -0
  41. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/models/__init__.py +0 -0
  42. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/models/t01.py +0 -0
  43. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/models/t04.py +0 -0
  44. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/models/t89.py +0 -0
  45. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/models/t96.py +0 -0
  46. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/print_structure.py +0 -0
  47. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/t01.py +0 -0
  48. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/t04.py +0 -0
  49. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/t89.py +0 -0
  50. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/t96.py +0 -0
  51. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/__init__.py +0 -0
  52. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/condip1_exact.py +0 -0
  53. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/coordinates.py +0 -0
  54. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/igrf.py +0 -0
  55. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/models/__init__.py +0 -0
  56. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/models/t04.py +0 -0
  57. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/models/t89.py +0 -0
  58. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack/vectorized/trace.py +0 -0
  59. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack_vectorize.egg-info/SOURCES.txt +0 -0
  60. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack_vectorize.egg-info/dependency_links.txt +0 -0
  61. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack_vectorize.egg-info/requires.txt +0 -0
  62. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/geopack_vectorize.egg-info/top_level.txt +0 -0
  63. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/requirements.txt +0 -0
  64. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/setup.cfg +0 -0
  65. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/tests/installed_smoke.py +0 -0
  66. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/tests/test_distribution.py +0 -0
  67. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/tests/test_trace_vectorized.py +0 -0
  68. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/tests/test_trace_vectorized_with_vectorized_models.py +0 -0
  69. {geopack_vectorize-2.0.2 → geopack_vectorize-2.0.3}/tests/test_vectorized_models.py +0 -0
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
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
+
10
19
  ## [2.0.2] - 2026-11-04
11
20
 
12
21
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geopack-vectorize
3
- Version: 2.0.2
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
@@ -6,7 +6,7 @@ This module re-exports all public functions defined in geopack/geopack.py,
6
6
  so that they can be imported directly from `geopack`.
7
7
  """
8
8
 
9
- __version__ = '2.0.2'
9
+ __version__ = '2.0.3'
10
10
 
11
11
  from .geopack import (
12
12
  update_igrf,
@@ -23,6 +23,7 @@ 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
29
  scalar_input = all(np.isscalar(c) for c in (r, theta, phi))
@@ -50,6 +51,7 @@ def sphcar(r, theta, phi, j):
50
51
 
51
52
  # Handle conditional logic for phi
52
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)
53
55
 
54
56
  # Handle conditional logic for theta
55
57
  theta_out = np.where(sq != 0,
@@ -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
- bx, by, bz = np.zeros_like(x), np.zeros_like(y), np.zeros_like(z)
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)
@@ -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
- # Initialize output arrays
129
- bx = np.zeros_like(x)
130
- by = np.zeros_like(y)
131
- bz = np.zeros_like(z)
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geopack-vectorize
3
- Version: 2.0.2
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "geopack-vectorize"
7
- version = "2.0.2"
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.2',
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)',
@@ -65,6 +65,22 @@ class TestCoordinateBroadcasting(unittest.TestCase):
65
65
  result = getattr(coordinates_complex, name)(*base)
66
66
  self.assertTrue(all(isinstance(c, float) for c in result))
67
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
+
68
84
  def test_igrf_cartesian_wrappers_broadcast(self):
69
85
  for name in ('igrf_gsm', 'igrf_gsw'):
70
86
  for coords in ((5., np.array([1., 2.]), 1.),
@@ -12,6 +12,35 @@ PARMOD = [2., -20., 2., -5., 1., 1., 1., 1., 1., 1.]
12
12
 
13
13
 
14
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
+
15
44
  def test_t01_imf_interconnection_preserves_clock_angle(self):
16
45
  # With G1/G2 held fixed, the IMF dependence is exactly the prescribed
17
46
  # T01 interconnection field, including outside the magnetopause.