Geopack-2008 2.2.1__py3-none-win_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geopack/__init__.py +322 -0
- geopack/_enums.py +30 -0
- geopack/_native.py +322 -0
- geopack/geopack.dll +0 -0
- geopack/py.typed +0 -0
- geopack_2008-2.2.1.dist-info/METADATA +166 -0
- geopack_2008-2.2.1.dist-info/RECORD +9 -0
- geopack_2008-2.2.1.dist-info/WHEEL +5 -0
- geopack_2008-2.2.1.dist-info/top_level.txt +1 -0
geopack/__init__.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""Geopack-2008 for Python.
|
|
2
|
+
|
|
3
|
+
A Pythonic wrapper around the NativeAOT-built ``geopack`` shared library
|
|
4
|
+
(magnetic field of the Earth, solar-terrestrial coordinate transforms). No .NET
|
|
5
|
+
runtime is required at runtime.
|
|
6
|
+
|
|
7
|
+
Typical usage::
|
|
8
|
+
|
|
9
|
+
import geopack
|
|
10
|
+
|
|
11
|
+
ctx = geopack.recalc(1997, 12, 16, 21, 0, 0, vx=-304, vy=13.78, vz=4)
|
|
12
|
+
b = ctx.igrf_gsw(1, 1, 1) # (bx, by, bz) nT, GSW
|
|
13
|
+
r = ctx.gsw_to_geo(1, 1, 1) # (x, y, z) Earth radii, GEO
|
|
14
|
+
ctx.close()
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from typing import NamedTuple, Optional, Sequence, Union
|
|
21
|
+
|
|
22
|
+
from . import _native
|
|
23
|
+
from ._enums import CoordinateSystem, MagnetopausePosition
|
|
24
|
+
from ._native import GeopackError
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"GeopackError",
|
|
28
|
+
"CoordinateSystem",
|
|
29
|
+
"MagnetopausePosition",
|
|
30
|
+
"Vector3",
|
|
31
|
+
"Sun",
|
|
32
|
+
"MagnetopauseResult",
|
|
33
|
+
"Context",
|
|
34
|
+
"DEFAULT_SOLAR_WIND",
|
|
35
|
+
"recalc",
|
|
36
|
+
"sun",
|
|
37
|
+
"shu_mgnp",
|
|
38
|
+
"t96_mgnp",
|
|
39
|
+
"t89",
|
|
40
|
+
"__version__",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
__version__ = "2.2.1"
|
|
44
|
+
|
|
45
|
+
#: Default solar wind velocity (GSE, km/s) used when the caller does not provide one.
|
|
46
|
+
DEFAULT_SOLAR_WIND = (-400.0, 0.0, 0.0)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Vector3(NamedTuple):
|
|
50
|
+
"""A Cartesian vector in Earth radii (positions) or nT (fields).
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
x: X component.
|
|
54
|
+
y: Y component.
|
|
55
|
+
z: Z component.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
x: float
|
|
59
|
+
y: float
|
|
60
|
+
z: float
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Sun(NamedTuple):
|
|
64
|
+
"""Position of the Sun for a given UTC date/time.
|
|
65
|
+
|
|
66
|
+
Attributes:
|
|
67
|
+
gst: Greenwich mean sidereal time in radians.
|
|
68
|
+
slong: Ecliptic longitude of the Sun in radians.
|
|
69
|
+
srasn: Right ascension of the Sun in radians.
|
|
70
|
+
sdec: Declination of the Sun in radians.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
gst: float
|
|
74
|
+
slong: float
|
|
75
|
+
srasn: float
|
|
76
|
+
sdec: float
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class MagnetopauseResult(NamedTuple):
|
|
80
|
+
"""Result of a magnetopause model evaluation.
|
|
81
|
+
|
|
82
|
+
Attributes:
|
|
83
|
+
boundary: Nearest boundary point in GSW (Earth radii).
|
|
84
|
+
dist: Distance from the input point to the boundary (Earth radii).
|
|
85
|
+
position: Relative position of the point (Inside, Outside, NotDefined).
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
boundary: Vector3
|
|
89
|
+
dist: float
|
|
90
|
+
position: MagnetopausePosition
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Context:
|
|
94
|
+
"""A frozen computation context for a single UTC date/time.
|
|
95
|
+
|
|
96
|
+
Created by :func:`recalc`. All field and transform methods take coordinates
|
|
97
|
+
in the documented system and return plain ``(x, y, z)`` tuples. The context
|
|
98
|
+
is immutable on the native side and safe to share between threads.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
__slots__ = ("_handle", "_closed")
|
|
102
|
+
|
|
103
|
+
def __init__(self, handle: int):
|
|
104
|
+
self._handle = handle
|
|
105
|
+
self._closed = False
|
|
106
|
+
|
|
107
|
+
# -- lifecycle ----------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def close(self) -> None:
|
|
110
|
+
"""Release the native computation context. Idempotent."""
|
|
111
|
+
if not self._closed:
|
|
112
|
+
_native.context_release(self._handle)
|
|
113
|
+
self._closed = True
|
|
114
|
+
|
|
115
|
+
def __del__(self) -> None: # pragma: no cover - interpreter shutdown
|
|
116
|
+
try:
|
|
117
|
+
self.close()
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
def __enter__(self) -> "Context":
|
|
122
|
+
return self
|
|
123
|
+
|
|
124
|
+
def __exit__(self, *exc) -> None:
|
|
125
|
+
self.close()
|
|
126
|
+
|
|
127
|
+
def _check_open(self) -> None:
|
|
128
|
+
if self._closed:
|
|
129
|
+
raise RuntimeError("Geopack context has been closed")
|
|
130
|
+
|
|
131
|
+
# -- field models -------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
def igrf_gsw(self, x: float, y: float, z: float) -> Vector3:
|
|
134
|
+
"""IGRF magnetic field at ``(x, y, z)`` in GSW, returned in nT."""
|
|
135
|
+
self._check_open()
|
|
136
|
+
return Vector3(*_native.igrf_gsw(self._handle, x, y, z))
|
|
137
|
+
|
|
138
|
+
def dip(self, x: float, y: float, z: float) -> Vector3:
|
|
139
|
+
"""Dipole magnetic field at ``(x, y, z)`` in GSW, returned in nT."""
|
|
140
|
+
self._check_open()
|
|
141
|
+
return Vector3(*_native.dip(self._handle, x, y, z))
|
|
142
|
+
|
|
143
|
+
def igrf_geo(self, r: float, theta: float, phi: float) -> Vector3:
|
|
144
|
+
"""IGRF field in GEO spherical coordinates at radius ``r`` (Re),
|
|
145
|
+
colatitude ``theta`` and longitude ``phi`` (radians).
|
|
146
|
+
|
|
147
|
+
Returns the spherical components ``(Br, Btheta, Bphi)`` in nT.
|
|
148
|
+
"""
|
|
149
|
+
self._check_open()
|
|
150
|
+
return Vector3(*_native.igrf_geo(self._handle, r, theta, phi))
|
|
151
|
+
|
|
152
|
+
# -- external field models ----------------------------------------------
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def psi(self) -> float:
|
|
156
|
+
"""Dipole tilt angle (radians) for this context's date/time, as computed by Recalc."""
|
|
157
|
+
self._check_open()
|
|
158
|
+
return _native.context_psi(self._handle)
|
|
159
|
+
|
|
160
|
+
def t89(self, iopt: int, x: float, y: float, z: float, psi: Optional[float] = None) -> Vector3:
|
|
161
|
+
"""Tsyganenko (1989) external field at ``(x, y, z)`` in GSW.
|
|
162
|
+
|
|
163
|
+
``iopt`` is the disturbance-level index (1..7, maps to Kp intervals).
|
|
164
|
+
``psi`` is the dipole tilt in radians and defaults to this context's tilt.
|
|
165
|
+
Returns the field vector in GSM (nT). Calls are serialized natively
|
|
166
|
+
because the model keeps static state (not thread-safe).
|
|
167
|
+
"""
|
|
168
|
+
self._check_open()
|
|
169
|
+
if psi is None:
|
|
170
|
+
psi = self.psi
|
|
171
|
+
return Vector3(*_native.t89(iopt, psi, x, y, z))
|
|
172
|
+
|
|
173
|
+
# -- coordinate transforms (in -> out, Earth radii) ---------------------
|
|
174
|
+
|
|
175
|
+
def gsw_to_gse(self, x: float, y: float, z: float) -> Vector3:
|
|
176
|
+
"""Transform Cartesian (x, y, z) from GSW to GSE."""
|
|
177
|
+
return self._transform(_native.gsw_to_gse, x, y, z)
|
|
178
|
+
|
|
179
|
+
def gse_to_gsw(self, x: float, y: float, z: float) -> Vector3:
|
|
180
|
+
"""Transform Cartesian (x, y, z) from GSE to GSW."""
|
|
181
|
+
return self._transform(_native.gse_to_gsw, x, y, z)
|
|
182
|
+
|
|
183
|
+
def geo_to_mag(self, x: float, y: float, z: float) -> Vector3:
|
|
184
|
+
"""Transform Cartesian (x, y, z) from GEO to MAG."""
|
|
185
|
+
return self._transform(_native.geo_to_mag, x, y, z)
|
|
186
|
+
|
|
187
|
+
def mag_to_geo(self, x: float, y: float, z: float) -> Vector3:
|
|
188
|
+
"""Transform Cartesian (x, y, z) from MAG to GEO."""
|
|
189
|
+
return self._transform(_native.mag_to_geo, x, y, z)
|
|
190
|
+
|
|
191
|
+
def gei_to_geo(self, x: float, y: float, z: float) -> Vector3:
|
|
192
|
+
"""Transform Cartesian (x, y, z) from GEI to GEO."""
|
|
193
|
+
return self._transform(_native.gei_to_geo, x, y, z)
|
|
194
|
+
|
|
195
|
+
def geo_to_gei(self, x: float, y: float, z: float) -> Vector3:
|
|
196
|
+
"""Transform Cartesian (x, y, z) from GEO to GEI."""
|
|
197
|
+
return self._transform(_native.geo_to_gei, x, y, z)
|
|
198
|
+
|
|
199
|
+
def mag_to_sm(self, x: float, y: float, z: float) -> Vector3:
|
|
200
|
+
"""Transform Cartesian (x, y, z) from MAG to SM."""
|
|
201
|
+
return self._transform(_native.mag_to_sm, x, y, z)
|
|
202
|
+
|
|
203
|
+
def sm_to_mag(self, x: float, y: float, z: float) -> Vector3:
|
|
204
|
+
"""Transform Cartesian (x, y, z) from SM to MAG."""
|
|
205
|
+
return self._transform(_native.sm_to_mag, x, y, z)
|
|
206
|
+
|
|
207
|
+
def sm_to_gsw(self, x: float, y: float, z: float) -> Vector3:
|
|
208
|
+
"""Transform Cartesian (x, y, z) from SM to GSW."""
|
|
209
|
+
return self._transform(_native.sm_to_gsw, x, y, z)
|
|
210
|
+
|
|
211
|
+
def gsw_to_sm(self, x: float, y: float, z: float) -> Vector3:
|
|
212
|
+
"""Transform Cartesian (x, y, z) from GSW to SM."""
|
|
213
|
+
return self._transform(_native.gsw_to_sm, x, y, z)
|
|
214
|
+
|
|
215
|
+
def geo_to_gsw(self, x: float, y: float, z: float) -> Vector3:
|
|
216
|
+
"""Transform Cartesian (x, y, z) from GEO to GSW."""
|
|
217
|
+
return self._transform(_native.geo_to_gsw, x, y, z)
|
|
218
|
+
|
|
219
|
+
def gsw_to_geo(self, x: float, y: float, z: float) -> Vector3:
|
|
220
|
+
"""Transform Cartesian (x, y, z) from GSW to GEO."""
|
|
221
|
+
return self._transform(_native.gsw_to_geo, x, y, z)
|
|
222
|
+
|
|
223
|
+
def _transform(self, fn, x: float, y: float, z: float) -> Vector3:
|
|
224
|
+
self._check_open()
|
|
225
|
+
return Vector3(*fn(self._handle, x, y, z))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def recalc(
|
|
229
|
+
date_or_year: Union[datetime, int],
|
|
230
|
+
month: Optional[int] = None,
|
|
231
|
+
day: Optional[int] = None,
|
|
232
|
+
hour: int = 0,
|
|
233
|
+
minute: int = 0,
|
|
234
|
+
second: int = 0,
|
|
235
|
+
vx: float = DEFAULT_SOLAR_WIND[0],
|
|
236
|
+
vy: float = DEFAULT_SOLAR_WIND[1],
|
|
237
|
+
vz: float = DEFAULT_SOLAR_WIND[2],
|
|
238
|
+
) -> Context:
|
|
239
|
+
"""Create a :class:`Context` for a UTC date/time and solar wind velocity.
|
|
240
|
+
|
|
241
|
+
``date_or_year`` is either a ``datetime.datetime`` (then ``month``/``day``
|
|
242
|
+
must be omitted) or a bare year. The solar wind velocity defaults to
|
|
243
|
+
``(-400, 0, 0)`` km/s (GSE) as in the original GEOPACK-2008.
|
|
244
|
+
"""
|
|
245
|
+
if isinstance(date_or_year, datetime):
|
|
246
|
+
if month is not None or day is not None:
|
|
247
|
+
raise ValueError("pass either a datetime or (year, month, day), not both")
|
|
248
|
+
dt = date_or_year
|
|
249
|
+
year, month, day = dt.year, dt.month, dt.day
|
|
250
|
+
hour, minute, second = dt.hour, dt.minute, dt.second
|
|
251
|
+
else:
|
|
252
|
+
if month is None or day is None:
|
|
253
|
+
raise ValueError("month and day are required when a year is given")
|
|
254
|
+
year = date_or_year
|
|
255
|
+
|
|
256
|
+
handle = _native.context_create(
|
|
257
|
+
year, month, day, hour, minute, second, vx, vy, vz
|
|
258
|
+
)
|
|
259
|
+
return Context(handle)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def sun(date_or_year: Union[datetime, int], month: Optional[int] = None, day: Optional[int] = None,
|
|
263
|
+
hour: int = 0, minute: int = 0, second: int = 0) -> Sun:
|
|
264
|
+
"""Position of the Sun for a UTC date/time (see :func:`recalc`)."""
|
|
265
|
+
if isinstance(date_or_year, datetime):
|
|
266
|
+
if month is not None or day is not None:
|
|
267
|
+
raise ValueError("pass either a datetime or (year, month, day), not both")
|
|
268
|
+
dt = date_or_year
|
|
269
|
+
year, month, day = dt.year, dt.month, dt.day
|
|
270
|
+
hour, minute, second = dt.hour, dt.minute, dt.second
|
|
271
|
+
else:
|
|
272
|
+
if month is None or day is None:
|
|
273
|
+
raise ValueError("month and day are required when a year is given")
|
|
274
|
+
year = date_or_year
|
|
275
|
+
|
|
276
|
+
return Sun(*_native.sun(year, month, day, hour, minute, second))
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def shu_mgnp(
|
|
280
|
+
xn_pd: float, vel: float, bz_imf: float, x: float, y: float, z: float,
|
|
281
|
+
) -> MagnetopauseResult:
|
|
282
|
+
"""Shue et al. (1998) magnetopause model. Input in GSW (Re).
|
|
283
|
+
|
|
284
|
+
``xn_pd`` is either the solar wind proton number density (per cm3) if
|
|
285
|
+
``vel > 0``, or the solar wind ram pressure in nPa if ``vel < 0``.
|
|
286
|
+
``vel`` is the velocity in km/s (a negative value signals that ``xn_pd``
|
|
287
|
+
is a pressure, not a density). ``bz_imf`` is the IMF Bz in nT.
|
|
288
|
+
``(x, y, z)`` is the probe point in GSW.
|
|
289
|
+
"""
|
|
290
|
+
mx, my, mz, dist, position = _native.shu_mgnp(xn_pd, vel, bz_imf, x, y, z)
|
|
291
|
+
return MagnetopauseResult(Vector3(mx, my, mz), dist, MagnetopausePosition(position))
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def t96_mgnp(xn_pd: float, vel: float, x: float, y: float, z: float) -> MagnetopauseResult:
|
|
295
|
+
"""Tsyganenko 1996 magnetopause model. Input in GSW (Re).
|
|
296
|
+
|
|
297
|
+
``xn_pd`` is either the solar wind proton number density (per cm3) if
|
|
298
|
+
``vel > 0``, or the solar wind ram pressure in nPa if ``vel < 0``.
|
|
299
|
+
``vel`` is the velocity in km/s (a negative value signals that ``xn_pd``
|
|
300
|
+
is a pressure, not a density). ``(x, y, z)`` is the probe point in GSW.
|
|
301
|
+
"""
|
|
302
|
+
mx, my, mz, dist, position = _native.t96_mgnp(xn_pd, vel, x, y, z)
|
|
303
|
+
return MagnetopauseResult(Vector3(mx, my, mz), dist, MagnetopausePosition(position))
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def t89(
|
|
307
|
+
iopt: int,
|
|
308
|
+
psi: float,
|
|
309
|
+
x: float,
|
|
310
|
+
y: float,
|
|
311
|
+
z: float,
|
|
312
|
+
parmod: Optional[Sequence[float]] = None,
|
|
313
|
+
) -> Vector3:
|
|
314
|
+
"""Tsyganenko (1989) external field model. Input in GSW (Re).
|
|
315
|
+
|
|
316
|
+
``iopt`` is the disturbance-level index (1..7, maps to Kp intervals).
|
|
317
|
+
``psi`` is the dipole tilt angle in radians.
|
|
318
|
+
``parmod`` is a dummy parameter array accepted for parity with the .NET
|
|
319
|
+
``IT89.Calculate`` contract; it is not used by T89 and defaults to ``None``.
|
|
320
|
+
``(x, y, z)`` is the probe point in GSW. Returns the field vector in GSM (nT).
|
|
321
|
+
"""
|
|
322
|
+
return Vector3(*_native.t89(iopt, psi, x, y, z))
|
geopack/_enums.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Enumerations shared with the native Geopack ABI.
|
|
2
|
+
|
|
3
|
+
The numeric values mirror the C# ``CoordinateSystem`` and
|
|
4
|
+
``MagnetopausePosition`` enums so the integer codes crossing the C ABI stay
|
|
5
|
+
unambiguous.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from enum import IntEnum
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CoordinateSystem(IntEnum):
|
|
14
|
+
"""Geographic coordinate system codes (GEO = 0, GSW = 1, ...)."""
|
|
15
|
+
|
|
16
|
+
GEO = 0
|
|
17
|
+
GSW = 1
|
|
18
|
+
GSE = 2
|
|
19
|
+
MAG = 3
|
|
20
|
+
SM = 4
|
|
21
|
+
GEI = 5
|
|
22
|
+
GSM = 6
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MagnetopausePosition(IntEnum):
|
|
26
|
+
"""Position of a point relative to the magnetopause boundary."""
|
|
27
|
+
|
|
28
|
+
NotDefined = 0
|
|
29
|
+
Inside = 1
|
|
30
|
+
Outside = 2
|
geopack/_native.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""Thin ctypes layer over the NativeAOT-built geopack shared library.
|
|
2
|
+
|
|
3
|
+
The library exposes a flat C ABI (see src/Geopack.Native/GeopackNative.cs):
|
|
4
|
+
|
|
5
|
+
* every function returns an ``int`` error code (0 = success, non-zero = error);
|
|
6
|
+
* results are written into caller-provided out-pointers;
|
|
7
|
+
* a ``ComputationContext`` lives behind an opaque ``long`` handle created by
|
|
8
|
+
``gp_context_create`` and released by ``gp_context_release``;
|
|
9
|
+
* the thread-local error message (UTF-8) is fetched with ``gp_last_error``.
|
|
10
|
+
|
|
11
|
+
This module only knows how to cross the FFI boundary. The object-oriented,
|
|
12
|
+
user-facing API lives in :mod:`geopack`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import ctypes
|
|
18
|
+
import ctypes.util
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from typing import NamedTuple, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
#: Filenames to try, in order, per platform. The native build produces
|
|
24
|
+
#: ``geopack.dylib`` on macOS, ``geopack.so`` on Linux and ``geopack.dll`` on
|
|
25
|
+
#: Windows (no ``lib`` prefix). A GEOPACK_LIBRARY env var overrides all of them.
|
|
26
|
+
_LIBRARY_NAMES = {
|
|
27
|
+
"darwin": ("geopack.dylib", "libgeopack.dylib"),
|
|
28
|
+
"linux": ("geopack.so", "libgeopack.so"),
|
|
29
|
+
"win32": ("geopack.dll",),
|
|
30
|
+
}
|
|
31
|
+
_FALLBACK_NAMES = ("geopack.dylib", "libgeopack.dylib", "geopack.so", "libgeopack.so", "geopack.dll")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class GeopackError(Exception):
|
|
35
|
+
"""Raised when the native library reports a non-zero error code."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _find_library_path() -> str:
|
|
39
|
+
"""Locate the native library, preferring the package directory."""
|
|
40
|
+
package_dir = os.path.dirname(os.path.abspath(__file__))
|
|
41
|
+
override = os.environ.get("GEOPACK_LIBRARY")
|
|
42
|
+
candidates = [override] if override else []
|
|
43
|
+
candidates += list(_LIBRARY_NAMES.get(sys.platform, ())) + list(_FALLBACK_NAMES)
|
|
44
|
+
|
|
45
|
+
for name in candidates:
|
|
46
|
+
if not name:
|
|
47
|
+
continue
|
|
48
|
+
path = os.path.join(package_dir, name)
|
|
49
|
+
if os.path.exists(path):
|
|
50
|
+
return path
|
|
51
|
+
if os.path.isabs(name) and os.path.exists(name):
|
|
52
|
+
return name
|
|
53
|
+
|
|
54
|
+
raise ImportError(
|
|
55
|
+
"Native geopack library not found. Build it with python/build_native.sh "
|
|
56
|
+
f"and place it next to this module. Tried: {', '.join(candidates)}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
_lib = ctypes.CDLL(_find_library_path())
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
# Prototypes
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
_c_handle = ctypes.c_int64
|
|
67
|
+
_c_double = ctypes.c_double
|
|
68
|
+
_c_int = ctypes.c_int
|
|
69
|
+
_p_handle = ctypes.POINTER(ctypes.c_int64)
|
|
70
|
+
_p_double = ctypes.POINTER(ctypes.c_double)
|
|
71
|
+
_p_byte = ctypes.POINTER(ctypes.c_byte)
|
|
72
|
+
|
|
73
|
+
_lib.gp_context_create.argtypes = [
|
|
74
|
+
_c_int, _c_int, _c_int, _c_int, _c_int, _c_int, # y mo d h mi s
|
|
75
|
+
_c_double, _c_double, _c_double, # vx vy vz
|
|
76
|
+
_p_handle, # out handle
|
|
77
|
+
]
|
|
78
|
+
_lib.gp_context_create.restype = _c_int
|
|
79
|
+
|
|
80
|
+
_lib.gp_context_release.argtypes = [_c_handle]
|
|
81
|
+
_lib.gp_context_release.restype = None
|
|
82
|
+
|
|
83
|
+
_lib.gp_last_error.argtypes = [_p_byte, _c_int]
|
|
84
|
+
_lib.gp_last_error.restype = _c_int
|
|
85
|
+
|
|
86
|
+
# Field models + transforms: (i64 ctx, double x, y, z, double* o1, o2, o3)
|
|
87
|
+
_TRANSFORM_ARGTYPES = [
|
|
88
|
+
_c_handle,
|
|
89
|
+
_c_double, _c_double, _c_double,
|
|
90
|
+
_p_double, _p_double, _p_double,
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
_lib.gp_igrf_gsw.argtypes = _TRANSFORM_ARGTYPES
|
|
94
|
+
_lib.gp_igrf_gsw.restype = _c_int
|
|
95
|
+
_lib.gp_dip.argtypes = _TRANSFORM_ARGTYPES
|
|
96
|
+
_lib.gp_dip.restype = _c_int
|
|
97
|
+
|
|
98
|
+
_lib.gp_igrf_geo.argtypes = [
|
|
99
|
+
_c_handle,
|
|
100
|
+
_c_double, _c_double, _c_double, # r theta phi
|
|
101
|
+
_p_double, _p_double, _p_double, # out br bt bp
|
|
102
|
+
]
|
|
103
|
+
_lib.gp_igrf_geo.restype = _c_int
|
|
104
|
+
|
|
105
|
+
_lib.gp_sun.argtypes = [
|
|
106
|
+
_c_int, _c_int, _c_int, _c_int, _c_int, _c_int, # y mo d h mi s
|
|
107
|
+
_p_double, _p_double, _p_double, _p_double, # out gst slong srasn sdec
|
|
108
|
+
]
|
|
109
|
+
_lib.gp_sun.restype = _c_int
|
|
110
|
+
|
|
111
|
+
for _name in (
|
|
112
|
+
"gp_gsw_to_gse",
|
|
113
|
+
"gp_gse_to_gsw",
|
|
114
|
+
"gp_geo_to_mag",
|
|
115
|
+
"gp_mag_to_geo",
|
|
116
|
+
"gp_gei_to_geo",
|
|
117
|
+
"gp_geo_to_gei",
|
|
118
|
+
"gp_mag_to_sm",
|
|
119
|
+
"gp_sm_to_mag",
|
|
120
|
+
"gp_sm_to_gsw",
|
|
121
|
+
"gp_gsw_to_sm",
|
|
122
|
+
"gp_geo_to_gsw",
|
|
123
|
+
"gp_gsw_to_geo",
|
|
124
|
+
):
|
|
125
|
+
getattr(_lib, _name).argtypes = _TRANSFORM_ARGTYPES
|
|
126
|
+
getattr(_lib, _name).restype = _c_int
|
|
127
|
+
|
|
128
|
+
_lib.gp_shu_mgnp.argtypes = [
|
|
129
|
+
_c_double, _c_double, _c_double, # xnPd vel bzImf
|
|
130
|
+
_c_double, _c_double, _c_double, # x y z
|
|
131
|
+
_p_double, _p_double, _p_double, # out mx my mz
|
|
132
|
+
_p_double, # out dist
|
|
133
|
+
_p_int := ctypes.POINTER(ctypes.c_int),
|
|
134
|
+
]
|
|
135
|
+
_lib.gp_shu_mgnp.restype = _c_int
|
|
136
|
+
|
|
137
|
+
_lib.gp_t96_mgnp.argtypes = [
|
|
138
|
+
_c_double, _c_double, # xnPd vel
|
|
139
|
+
_c_double, _c_double, _c_double, # x y z
|
|
140
|
+
_p_double, _p_double, _p_double, # out mx my mz
|
|
141
|
+
_p_double, # out dist
|
|
142
|
+
ctypes.POINTER(ctypes.c_int),
|
|
143
|
+
]
|
|
144
|
+
_lib.gp_t96_mgnp.restype = _c_int
|
|
145
|
+
|
|
146
|
+
# External field models (context-free, input GSW, output GSM)
|
|
147
|
+
_lib.gp_t89.argtypes = [
|
|
148
|
+
_c_int, _c_double, _c_double, _c_double, _c_double, # iopt psi x y z
|
|
149
|
+
_p_double, _p_double, _p_double, # out bx by bz
|
|
150
|
+
]
|
|
151
|
+
_lib.gp_t89.restype = _c_int
|
|
152
|
+
|
|
153
|
+
# Context dipole tilt (radians), computed by Recalc
|
|
154
|
+
_lib.gp_context_psi.argtypes = [_c_handle, _p_double]
|
|
155
|
+
_lib.gp_context_psi.restype = _c_int
|
|
156
|
+
|
|
157
|
+
del _name # loop variable cleanup
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
# Error plumbing
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _last_error() -> Optional[str]:
|
|
165
|
+
"""Read and clear the thread-local error message set by the native side."""
|
|
166
|
+
# Probe: null buffer + zero capacity returns the UTF-8 byte length without
|
|
167
|
+
# clearing the stored message.
|
|
168
|
+
size = _lib.gp_last_error(None, 0)
|
|
169
|
+
if size <= 0:
|
|
170
|
+
return None
|
|
171
|
+
buf = ctypes.create_string_buffer(size + 1)
|
|
172
|
+
_lib.gp_last_error(ctypes.cast(buf, _p_byte), size + 1)
|
|
173
|
+
return buf.value.decode("utf-8", errors="replace")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _check(rc: int) -> None:
|
|
177
|
+
if rc != 0:
|
|
178
|
+
raise GeopackError(_last_error() or f"native geopack error (code {rc})")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
# Raw wrappers
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def context_create(
|
|
187
|
+
year: int, month: int, day: int, hour: int, minute: int, second: int,
|
|
188
|
+
vx: float, vy: float, vz: float,
|
|
189
|
+
) -> int:
|
|
190
|
+
handle = ctypes.c_int64()
|
|
191
|
+
rc = _lib.gp_context_create(
|
|
192
|
+
year, month, day, hour, minute, second, vx, vy, vz, ctypes.byref(handle)
|
|
193
|
+
)
|
|
194
|
+
_check(rc)
|
|
195
|
+
return handle.value
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def context_release(handle: int) -> None:
|
|
199
|
+
_lib.gp_context_release(handle)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def igrf_gsw(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
203
|
+
return _call_vector(_lib.gp_igrf_gsw, handle, x, y, z)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def dip(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
207
|
+
return _call_vector(_lib.gp_dip, handle, x, y, z)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def igrf_geo(handle: int, r: float, theta: float, phi: float) -> Tuple[float, float, float]:
|
|
211
|
+
bx, bt, bp = ctypes.c_double(), ctypes.c_double(), ctypes.c_double()
|
|
212
|
+
rc = _lib.gp_igrf_geo(
|
|
213
|
+
handle, r, theta, phi, ctypes.byref(bx), ctypes.byref(bt), ctypes.byref(bp)
|
|
214
|
+
)
|
|
215
|
+
_check(rc)
|
|
216
|
+
return bx.value, bt.value, bp.value
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def sun(
|
|
220
|
+
year: int, month: int, day: int, hour: int, minute: int, second: int,
|
|
221
|
+
) -> Tuple[float, float, float, float]:
|
|
222
|
+
gst, slong, srasn, sdec = (ctypes.c_double() for _ in range(4))
|
|
223
|
+
rc = _lib.gp_sun(
|
|
224
|
+
year, month, day, hour, minute, second,
|
|
225
|
+
ctypes.byref(gst), ctypes.byref(slong), ctypes.byref(srasn), ctypes.byref(sdec),
|
|
226
|
+
)
|
|
227
|
+
_check(rc)
|
|
228
|
+
return gst.value, slong.value, srasn.value, sdec.value
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _call_vector(fn, handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
232
|
+
ox, oy, oz = ctypes.c_double(), ctypes.c_double(), ctypes.c_double()
|
|
233
|
+
rc = fn(handle, x, y, z, ctypes.byref(ox), ctypes.byref(oy), ctypes.byref(oz))
|
|
234
|
+
_check(rc)
|
|
235
|
+
return ox.value, oy.value, oz.value
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# Coordinate transforms: (long ctx, double x, y, z) -> (ox, oy, oz)
|
|
239
|
+
def gsw_to_gse(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
240
|
+
return _call_vector(_lib.gp_gsw_to_gse, handle, x, y, z)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def gse_to_gsw(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
244
|
+
return _call_vector(_lib.gp_gse_to_gsw, handle, x, y, z)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def geo_to_mag(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
248
|
+
return _call_vector(_lib.gp_geo_to_mag, handle, x, y, z)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def mag_to_geo(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
252
|
+
return _call_vector(_lib.gp_mag_to_geo, handle, x, y, z)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def gei_to_geo(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
256
|
+
return _call_vector(_lib.gp_gei_to_geo, handle, x, y, z)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def geo_to_gei(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
260
|
+
return _call_vector(_lib.gp_geo_to_gei, handle, x, y, z)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def mag_to_sm(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
264
|
+
return _call_vector(_lib.gp_mag_to_sm, handle, x, y, z)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def sm_to_mag(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
268
|
+
return _call_vector(_lib.gp_sm_to_mag, handle, x, y, z)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def sm_to_gsw(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
272
|
+
return _call_vector(_lib.gp_sm_to_gsw, handle, x, y, z)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def gsw_to_sm(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
276
|
+
return _call_vector(_lib.gp_gsw_to_sm, handle, x, y, z)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def geo_to_gsw(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
280
|
+
return _call_vector(_lib.gp_geo_to_gsw, handle, x, y, z)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def gsw_to_geo(handle: int, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
284
|
+
return _call_vector(_lib.gp_gsw_to_geo, handle, x, y, z)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def shu_mgnp(
|
|
288
|
+
xn_pd: float, vel: float, bz_imf: float, x: float, y: float, z: float,
|
|
289
|
+
) -> Tuple[float, float, float, float, int]:
|
|
290
|
+
return _call_mgnp(_lib.gp_shu_mgnp, xn_pd, vel, bz_imf, x, y, z)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def t96_mgnp(xn_pd: float, vel: float, x: float, y: float, z: float) -> Tuple[float, float, float, float, int]:
|
|
294
|
+
return _call_mgnp(_lib.gp_t96_mgnp, xn_pd, vel, x, y, z)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _call_mgnp(fn, *args: float) -> Tuple[float, float, float, float, int]:
|
|
298
|
+
mx, my, mz, dist = (ctypes.c_double() for _ in range(4))
|
|
299
|
+
position = ctypes.c_int()
|
|
300
|
+
rc = fn(
|
|
301
|
+
*args,
|
|
302
|
+
ctypes.byref(mx), ctypes.byref(my), ctypes.byref(mz),
|
|
303
|
+
ctypes.byref(dist), ctypes.byref(position),
|
|
304
|
+
)
|
|
305
|
+
_check(rc)
|
|
306
|
+
return mx.value, my.value, mz.value, dist.value, position.value
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def t89(iopt: int, psi: float, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
|
310
|
+
"""Tsyganenko (1989) external field at (x, y, z) in GSW, GSM components in nT."""
|
|
311
|
+
bx, by, bz = ctypes.c_double(), ctypes.c_double(), ctypes.c_double()
|
|
312
|
+
rc = _lib.gp_t89(iopt, psi, x, y, z, ctypes.byref(bx), ctypes.byref(by), ctypes.byref(bz))
|
|
313
|
+
_check(rc)
|
|
314
|
+
return bx.value, by.value, bz.value
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def context_psi(handle: int) -> float:
|
|
318
|
+
"""Dipole tilt (radians) for the given context, as computed by Recalc."""
|
|
319
|
+
psi = ctypes.c_double()
|
|
320
|
+
rc = _lib.gp_context_psi(handle, ctypes.byref(psi))
|
|
321
|
+
_check(rc)
|
|
322
|
+
return psi.value
|
geopack/geopack.dll
ADDED
|
Binary file
|
geopack/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Geopack-2008
|
|
3
|
+
Version: 2.2.1
|
|
4
|
+
Summary: Geopack-2008 magnetospheric field model and solar-terrestrial coordinate transforms (NativeAOT bindings)
|
|
5
|
+
Author: Aurora Science Hub
|
|
6
|
+
License: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/Aurora-Science-Hub/Geopack
|
|
8
|
+
Project-URL: Repository, https://github.com/Aurora-Science-Hub/Geopack
|
|
9
|
+
Project-URL: Issues, https://github.com/Aurora-Science-Hub/Geopack/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/Aurora-Science-Hub/Geopack/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: geopack,space physics,geophysics,magnetosphere,magnetic field,igrf,tsyganenko,t89,magnetopause,solar wind,coordinate-transforms
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
26
|
+
Requires-Python: >=3.8
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# Geopack-2008 — GEOPACK-2008 geomagnetic field model for Python
|
|
30
|
+
|
|
31
|
+
[](https://www.python.org/)
|
|
32
|
+
[](https://github.com/Aurora-Science-Hub/Geopack/actions/workflows/python-package.yml)
|
|
33
|
+
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
34
|
+
[](https://github.com/Aurora-Science-Hub/Geopack)
|
|
35
|
+
|
|
36
|
+
**Geopack-2008** (imported as `geopack`) is a self-contained, zero-dependency Python package for the
|
|
37
|
+
**GEOPACK-2008** geomagnetic field model: the **International Geomagnetic
|
|
38
|
+
Reference Field (IGRF)**, the **dipole field**, the **Tsyganenko (1989)
|
|
39
|
+
external field model (T89)**, the **Shue et al. (1998) and Tsyganenko (1996)
|
|
40
|
+
magnetopause models**, the **Sun position** computation, and the standard
|
|
41
|
+
**solar-terrestrial coordinate transforms** (GEO, GSW, GSE, SM, MAG, GEI, GSM).
|
|
42
|
+
|
|
43
|
+
It is a Python binding over a [C# port of GEOPACK-2008](https://github.com/Aurora-Science-Hub/Geopack)
|
|
44
|
+
compiled ahead-of-time with NativeAOT into a shared library (`geopack.dylib` /
|
|
45
|
+
`geopack.so` / `geopack.dll`), loaded through `ctypes` from the Python standard
|
|
46
|
+
library. No .NET runtime is required at runtime, and there are no Python
|
|
47
|
+
dependencies (no NumPy/SciPy).
|
|
48
|
+
|
|
49
|
+
- **Double precision**: accuracy matches the original Fortran code to 12–13 decimal digits
|
|
50
|
+
- **Standard library only** — no third-party runtime dependencies
|
|
51
|
+
- **Thread-safe** immutable computation contexts
|
|
52
|
+
- **Platform wheels** for Linux (x64/arm64), Windows (x64/arm64) and macOS (arm64)
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
Build the wheel for your platform (requires .NET SDK 10 + a C toolchain) and
|
|
57
|
+
`pip install` it. On macOS/Linux:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
./python/build_wheel.sh
|
|
61
|
+
pip install python/dist/geopack_2008-*.whl
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
On Windows (cmd):
|
|
65
|
+
|
|
66
|
+
```bat
|
|
67
|
+
.\python\build_wheel.bat
|
|
68
|
+
pip install python\dist\geopack_2008-*.whl
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The scripts build the native library for the host platform only; for another
|
|
72
|
+
OS/arch, build the wheel on that machine (NativeAOT does not cross-compile).
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import geopack
|
|
78
|
+
|
|
79
|
+
ctx = geopack.recalc(1997, 12, 16, 21, 0, 0, vx=-304, vy=13.78, vz=4)
|
|
80
|
+
b = ctx.igrf_gsw(1, 1, 1) # (bx, by, bz) nT in GSW
|
|
81
|
+
r = ctx.gsw_to_geo(1, 1, 1) # (x, y, z) Earth radii in GEO
|
|
82
|
+
ctx.close()
|
|
83
|
+
|
|
84
|
+
# A datetime works too, and contexts are context managers:
|
|
85
|
+
with geopack.recalc(datetime(1997, 12, 16, 21, 0), vx=-304, vy=13.78, vz=4) as ctx:
|
|
86
|
+
b = ctx.igrf_gsw(1, 1, 1)
|
|
87
|
+
|
|
88
|
+
s = geopack.sun(1997, 12, 16, 21, 0, 0) # (gst, slong, srasn, sdec), radians
|
|
89
|
+
|
|
90
|
+
mp = geopack.shu_mgnp(xn_pd=2.0, vel=400.0, bz_imf=0.0, x=10, y=0, z=0)
|
|
91
|
+
print(mp.boundary, mp.dist, mp.position) # Vector3, float, MagnetopausePosition
|
|
92
|
+
|
|
93
|
+
# Tsyganenko (1989) external field — input GSW, output GSM (nT):
|
|
94
|
+
b = geopack.t89(iopt=3, psi=ctx.psi, x=-6.6, y=0, z=0)
|
|
95
|
+
b = ctx.t89(iopt=3, x=-6.6, y=0, z=0) # psi defaults to ctx.psi
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
A runnable example with all of the above (sun position, IGRF/dipole fields,
|
|
99
|
+
coordinate transforms, magnetopause models) is in
|
|
100
|
+
[`python/example.py`](example.py):
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python3 python/example.py
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Implemented models and coordinate systems
|
|
107
|
+
|
|
108
|
+
| Category | Model | Python API |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| Internal field | IGRF (geomagnetic main field) | `Context.igrf_gsw`, `Context.igrf_geo` |
|
|
111
|
+
| Internal field | Dipole | `Context.dip` |
|
|
112
|
+
| External field | Tsyganenko (1989) — T89 | `Context.t89`, `geopack.t89` |
|
|
113
|
+
| Magnetopause | Shue et al. (1998) | `geopack.shu_mgnp` |
|
|
114
|
+
| Magnetopause | Tsyganenko (1996) | `geopack.t96_mgnp` |
|
|
115
|
+
| Sun position | Greenwich sidereal time, solar ephemeris | `geopack.sun` |
|
|
116
|
+
|
|
117
|
+
Supported coordinate systems (see `CoordinateSystem`): **GEO** (geographic),
|
|
118
|
+
**GSW** (geocentric solar wind), **GSE** (geocentric solar ecliptic),
|
|
119
|
+
**SM** (solar magnetic), **MAG** (geomagnetic), **GEI** (geocentric solar
|
|
120
|
+
equatorial inertial) and **GSM** (geocentric solar magnetospheric).
|
|
121
|
+
|
|
122
|
+
## API
|
|
123
|
+
|
|
124
|
+
All coordinates are in Earth radii; magnetic fields are in nT; angles are in
|
|
125
|
+
radians; solar wind velocity is in km/s.
|
|
126
|
+
|
|
127
|
+
* `recalc(date_or_year, month=None, day=None, hour=0, minute=0, second=0, vx=-400, vy=0, vz=0) -> Context`
|
|
128
|
+
* `Context` methods:
|
|
129
|
+
* fields: `igrf_gsw(x,y,z)`, `dip(x,y,z)`, `igrf_geo(r,theta,phi)`,
|
|
130
|
+
`t89(iopt,x,y,z, psi=None)`, property `psi` (dipole tilt, radians)
|
|
131
|
+
* transforms: `gsw_to_gse`, `gse_to_gsw`, `geo_to_mag`, `mag_to_geo`,
|
|
132
|
+
`gei_to_geo`, `geo_to_gei`, `mag_to_sm`, `sm_to_mag`, `sm_to_gsw`,
|
|
133
|
+
`gsw_to_sm`, `geo_to_gsw`, `gsw_to_geo` — each `(x,y,z) -> (x,y,z)`
|
|
134
|
+
* lifecycle: `close()`, context manager
|
|
135
|
+
* `sun(...) -> Sun(gst, slong, srasn, sdec)` — no context needed
|
|
136
|
+
* `shu_mgnp(xn_pd, vel, bz_imf, x, y, z)` and `t96_mgnp(xn_pd, vel, x, y, z)`
|
|
137
|
+
return `MagnetopauseResult(boundary, dist, position)` — no context needed
|
|
138
|
+
* `t89(iopt, psi, x, y, z, parmod=None) -> Vector3` — Tsyganenko (1989) external
|
|
139
|
+
field, input GSW, output GSM (nT); `parmod` is a dummy accepted for parity with
|
|
140
|
+
the .NET `IT89.Calculate` contract (unused) — no context needed
|
|
141
|
+
|
|
142
|
+
`GEOPACK_LIBRARY` environment variable points the loader at a specific library
|
|
143
|
+
path if the packaged one is not present.
|
|
144
|
+
|
|
145
|
+
## How to Cite
|
|
146
|
+
|
|
147
|
+
If you use this software in your research, please cite it — see the
|
|
148
|
+
[`How to Cite`](https://github.com/Aurora-Science-Hub/Geopack#how-to-cite)
|
|
149
|
+
section of the main repository README (APA and BibTeX entries with a Zenodo DOI).
|
|
150
|
+
|
|
151
|
+
## References
|
|
152
|
+
|
|
153
|
+
The implementation is based on the GEOPACK-2008 library and the following
|
|
154
|
+
scientific works:
|
|
155
|
+
|
|
156
|
+
1. **Tsyganenko, N. A.** (1989). *A magnetospheric magnetic field model with a warped tail current sheet*. Planetary and Space Science, 37(1), 5–20. https://doi.org/10.1016/0032-0633(89)90066-4
|
|
157
|
+
|
|
158
|
+
2. **Tsyganenko, N. A.** (1996). *Effects of the solar wind conditions on the global magnetospheric configuration as deduced from data-based field models*. ESA SP-389, 181–185.
|
|
159
|
+
|
|
160
|
+
3. **Shue, J.-H., et al.** (1998). *Magnetopause location under extreme solar wind conditions*. Journal of Geophysical Research, 103(A8), 17691–17700. https://doi.org/10.1029/98JA01103
|
|
161
|
+
|
|
162
|
+
4. **Hapgood, M. A.** (1992). *Space physics coordinate transformations: A user guide*. Planetary and Space Science, 40(5), 711–717. https://doi.org/10.1016/0032-0633(92)90012-D
|
|
163
|
+
|
|
164
|
+
5. **Tsyganenko, N. A.** (2002). *A model of the near magnetosphere with a dawn-dusk asymmetry 1. Mathematical structure*. Journal of Geophysical Research, 107(A8). https://doi.org/10.1029/2001JA000219
|
|
165
|
+
|
|
166
|
+
6. **Tsyganenko, N. A., & Sitnov, M. I.** (2005). *Modeling the dynamics of the inner magnetosphere during strong geomagnetic storms*. Journal of Geophysical Research: Space Physics, 110(A3), A03208. https://doi.org/10.1029/2004JA010798
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
geopack/__init__.py,sha256=1hLWM4nJnaWuQKcM7045kjHCOpbziYHlfmNyZ5vePiM,11921
|
|
2
|
+
geopack/_enums.py,sha256=o2bZ15IU2J_HMg5hipvFo7HESw8UoHCCBnBdabGE4UE,644
|
|
3
|
+
geopack/_native.py,sha256=IoKTS0-oPK-eaAGIL_mBgUQCM197NKbmRv9ftjtxM2Y,11537
|
|
4
|
+
geopack/geopack.dll,sha256=fBtBC3fma0adAqR4jxedft2opvzugGlbanBT6JZ7O1c,1486336
|
|
5
|
+
geopack/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
geopack_2008-2.2.1.dist-info/METADATA,sha256=oU2_f9zj05h_bYo2sAA1gItTtTsFRjgvg8JoBztCrbA,8472
|
|
7
|
+
geopack_2008-2.2.1.dist-info/WHEEL,sha256=QE-niYB-ZRQVWczDB301OM3v6K9Rt_4BEsv6l0aMysk,98
|
|
8
|
+
geopack_2008-2.2.1.dist-info/top_level.txt,sha256=5wOBhK4wkKyuE1EMPgWf9CTiOAWArRHC3Mk3e0nEbfk,8
|
|
9
|
+
geopack_2008-2.2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
geopack
|