geopack-vectorize 2.0.0__py3-none-any.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 +207 -0
- geopack/core.py +0 -0
- geopack/geopack.py +1449 -0
- geopack/igrf_coeffs/igrf10coeffs.txt +196 -0
- geopack/igrf_coeffs/igrf11coeffs.txt +199 -0
- geopack/igrf_coeffs/igrf12coeffs.txt +199 -0
- geopack/igrf_coeffs/igrf13coeffs.txt +199 -0
- geopack/igrf_coeffs/igrf14coeffs.txt +199 -0
- geopack/igrf_coeffs/igrf9coeffs.txt +196 -0
- geopack/models/__init__.py +10 -0
- geopack/models/t01.py +2283 -0
- geopack/models/t04.py +2215 -0
- geopack/models/t89.py +375 -0
- geopack/models/t96.py +1895 -0
- geopack/print_structure.py +23 -0
- geopack/vectorized/__init__.py +137 -0
- geopack/vectorized/condip1_exact.py +256 -0
- geopack/vectorized/coordinates.py +381 -0
- geopack/vectorized/coordinates_complex.py +170 -0
- geopack/vectorized/field_line_directional_derivatives.py +325 -0
- geopack/vectorized/field_line_geometry.py +412 -0
- geopack/vectorized/igrf.py +294 -0
- geopack/vectorized/trace.py +596 -0
- geopack_vectorize-2.0.0.dist-info/METADATA +346 -0
- geopack_vectorize-2.0.0.dist-info/RECORD +28 -0
- geopack_vectorize-2.0.0.dist-info/WHEEL +5 -0
- geopack_vectorize-2.0.0.dist-info/licenses/LICENSE +22 -0
- geopack_vectorize-2.0.0.dist-info/top_level.txt +1 -0
geopack/geopack.py
ADDED
|
@@ -0,0 +1,1449 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import os.path
|
|
3
|
+
import datetime
|
|
4
|
+
from urllib.request import urlopen, Request
|
|
5
|
+
from urllib.parse import urljoin
|
|
6
|
+
from fnmatch import fnmatch
|
|
7
|
+
|
|
8
|
+
# Import models
|
|
9
|
+
from .models import t89, t96, t01, t04
|
|
10
|
+
|
|
11
|
+
igrf_pattern = 'igrf*coeffs.txt'
|
|
12
|
+
|
|
13
|
+
def update_igrf(local_dir):
|
|
14
|
+
"""
|
|
15
|
+
Update to the latest IGRF coefficients.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Where to find the files.
|
|
19
|
+
url = 'http://www.ngdc.noaa.gov/IAGA/vmod/coeffs/'
|
|
20
|
+
|
|
21
|
+
# Find the coeff files.
|
|
22
|
+
coef_files = []
|
|
23
|
+
try:
|
|
24
|
+
request = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
|
|
25
|
+
with urlopen(request) as response:
|
|
26
|
+
if response.status != 200:
|
|
27
|
+
print('Failed to get the list of IGRF coefficients. Status code: {response.status}')
|
|
28
|
+
else:
|
|
29
|
+
html = response.read().decode('utf-8')
|
|
30
|
+
for line in html.splitlines():
|
|
31
|
+
if 'href' in line and '.txt' in line:
|
|
32
|
+
start = line.find('href="')+6
|
|
33
|
+
end = line.find('"', start)
|
|
34
|
+
base_name = line[start:end]
|
|
35
|
+
if fnmatch(base_name, igrf_pattern):
|
|
36
|
+
coef_files.append(base_name)
|
|
37
|
+
except:
|
|
38
|
+
print('Failed to get the list of IGRF coefficients.')
|
|
39
|
+
|
|
40
|
+
# Download the files.
|
|
41
|
+
for coef_file in coef_files:
|
|
42
|
+
local_file = os.path.join(local_dir, coef_file)
|
|
43
|
+
if os.path.exists(local_file): continue
|
|
44
|
+
|
|
45
|
+
remote_file = urljoin(url, coef_file)
|
|
46
|
+
with urlopen(remote_file) as response:
|
|
47
|
+
if response.status != 200: continue
|
|
48
|
+
with open(local_file, 'wb') as file:
|
|
49
|
+
file.write(response.read())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def init_igrf(version=None):
|
|
56
|
+
"""
|
|
57
|
+
Initialize the IGRF coefficients and related coefs.
|
|
58
|
+
Should be called once and only once when importing the geopack module.
|
|
59
|
+
|
|
60
|
+
:param version: The version of IGRF coefficients to load, e.g., '13'. If None, the latest version will be loaded.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
global igrf, nmn,mns, nyear,years,yruts
|
|
64
|
+
|
|
65
|
+
print('Load IGRF coefficients ...')
|
|
66
|
+
|
|
67
|
+
# Load all available IGRF coefficients.
|
|
68
|
+
local_dir = os.path.join(os.path.dirname(__file__), 'igrf_coeffs')
|
|
69
|
+
if not os.path.exists(local_dir): os.mkdir(local_dir)
|
|
70
|
+
update_igrf(local_dir)
|
|
71
|
+
|
|
72
|
+
if version is None:
|
|
73
|
+
# Load the latest version.
|
|
74
|
+
coef_files = os.listdir(local_dir)
|
|
75
|
+
versions = []
|
|
76
|
+
prefix, suffix = igrf_pattern.split('*')
|
|
77
|
+
for f in coef_files:
|
|
78
|
+
if f.startswith(prefix) and f.endswith(suffix):
|
|
79
|
+
versions.append(f[len(prefix):-len(suffix)])
|
|
80
|
+
version = max(versions, key=int)
|
|
81
|
+
|
|
82
|
+
base_name = 'igrf'+version+'coeffs.txt'
|
|
83
|
+
coef_file = os.path.join(local_dir, base_name)
|
|
84
|
+
|
|
85
|
+
nheader = 3
|
|
86
|
+
with open(coef_file, 'r') as file:
|
|
87
|
+
for i in range(nheader):
|
|
88
|
+
next(file)
|
|
89
|
+
header = file.readline().rstrip()
|
|
90
|
+
cols = header.split()
|
|
91
|
+
# first 3 columns are g/h flag, n, m.
|
|
92
|
+
years = np.array([np.int32(j[0:4]) for j in cols[3:]])
|
|
93
|
+
nyear = len(years)
|
|
94
|
+
lines = file.read().splitlines()
|
|
95
|
+
|
|
96
|
+
cols = lines[-1].split()
|
|
97
|
+
k = np.int32(cols[1]) + 1
|
|
98
|
+
nmn = np.int32((k + 1) * k * 0.5)
|
|
99
|
+
igrf = np.zeros((nmn, nyear, 2), dtype=float)
|
|
100
|
+
mns = np.empty((nmn, 2), dtype=np.int32)
|
|
101
|
+
l = 0
|
|
102
|
+
for i in range(k):
|
|
103
|
+
for j in range(i + 1):
|
|
104
|
+
mns[l, :] = [i, j]
|
|
105
|
+
l += 1
|
|
106
|
+
|
|
107
|
+
for line in lines:
|
|
108
|
+
cols = line.split()
|
|
109
|
+
if cols[0] == 'g':
|
|
110
|
+
i = 0
|
|
111
|
+
else:
|
|
112
|
+
i = 1
|
|
113
|
+
n, m = np.int32(cols[1:3])
|
|
114
|
+
mn = np.int32(n * (n + 1) * 0.5 + m)
|
|
115
|
+
igrf[mn, :, i] = [np.float32(j) for j in cols[3:]]
|
|
116
|
+
|
|
117
|
+
# treat the last column
|
|
118
|
+
years[-1] += 5
|
|
119
|
+
igrf[:, -1, :] = igrf[:, -2, :] + igrf[:, -1, :] * 5
|
|
120
|
+
yruts = np.empty(nyear, dtype=float)
|
|
121
|
+
t0_datetime = datetime.datetime(1970,1,1)
|
|
122
|
+
for i in range(nyear):
|
|
123
|
+
yruts[i] = (datetime.datetime(years[i],1,1)-t0_datetime).total_seconds()
|
|
124
|
+
# separate g/h
|
|
125
|
+
igrf = {'g': igrf[:,:,0], 'h': igrf[:,:,1]}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def load_igrf(ut):
|
|
129
|
+
"""
|
|
130
|
+
Load the IGRF coefficients for the given time.
|
|
131
|
+
:param ut: ut sec, a float.
|
|
132
|
+
:return: g,h. The IGRF coef at given time.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
# igrf should be initilized already.
|
|
136
|
+
global igrf, nmn,nyear, mns, years, yruts
|
|
137
|
+
try: isinstance(igrf, dict)
|
|
138
|
+
except: init_igrf()
|
|
139
|
+
|
|
140
|
+
# locate the two years of interest.
|
|
141
|
+
if ut <= yruts[0]: yridx = 0
|
|
142
|
+
elif ut >= yruts[-1]: yridx = -2
|
|
143
|
+
else:
|
|
144
|
+
yridx = np.argwhere(yruts <= ut)[-1]
|
|
145
|
+
|
|
146
|
+
g0 = np.squeeze(igrf['g'][:,yridx])
|
|
147
|
+
h0 = np.squeeze(igrf['h'][:,yridx])
|
|
148
|
+
g1 = np.squeeze(igrf['g'][:,yridx+1])
|
|
149
|
+
h1 = np.squeeze(igrf['h'][:,yridx+1])
|
|
150
|
+
|
|
151
|
+
ut0 = yruts[yridx]
|
|
152
|
+
ut1 = yruts[yridx+1]
|
|
153
|
+
f1 = (ut-ut0)/(ut1-ut0)
|
|
154
|
+
f0 = 1-f1
|
|
155
|
+
|
|
156
|
+
return g0*f0+g1*f1, h0*f0+h1*f1
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def igrf_gsw(xgsw,ygsw,zgsw):
|
|
160
|
+
"""
|
|
161
|
+
Calculates components of the main (internal) geomagnetic field in the geocentric solar
|
|
162
|
+
magnetospheric coordinate system, using IAGA international geomagnetic reference model
|
|
163
|
+
coefficients (e.g., http://www.ngdc.noaa.gov/iaga/vmod/igrf.html revised: 22 march, 2005)
|
|
164
|
+
|
|
165
|
+
Before the first call of this subroutine, or if the date/time
|
|
166
|
+
was changed, the model coefficients and GEO-GSW rotation matrix elements should be updated
|
|
167
|
+
by calling the subroutine recalc
|
|
168
|
+
|
|
169
|
+
Python version by Sheng Tian
|
|
170
|
+
|
|
171
|
+
:param xgsw,ygsw,zgsw: cartesian GSW coordinates (in units Re=6371.2 km)
|
|
172
|
+
:return: hxgsw,hygsw,hzgsw. Cartesian GSW components of the main geomagnetic field in nanotesla
|
|
173
|
+
"""
|
|
174
|
+
xgsm,ygsm,zgsm = gswgsm(xgsw,ygsw,zgsw, 1)
|
|
175
|
+
bxgsm,bygsm,bzgsm = igrf_gsm(xgsm,ygsm,zgsm)
|
|
176
|
+
return gswgsm(bxgsm,bygsm,bzgsm, -1)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def igrf_gsm(xgsm,ygsm,zgsm):
|
|
180
|
+
"""
|
|
181
|
+
Calculates components of the main (internal) geomagnetic field in the geocentric solar
|
|
182
|
+
magnetospheric coordinate system, using IAGA international geomagnetic reference model
|
|
183
|
+
coefficients (e.g., http://www.ngdc.noaa.gov/iaga/vmod/igrf.html revised: 22 march, 2005)
|
|
184
|
+
|
|
185
|
+
Before the first call of this subroutine, or if the date/time
|
|
186
|
+
was changed, the model coefficients and GEO-GSM rotation matrix elements should be updated
|
|
187
|
+
by calling the subroutine recalc
|
|
188
|
+
|
|
189
|
+
Python version by Sheng Tian
|
|
190
|
+
|
|
191
|
+
:param xgsm,ygsm,zgsm: cartesian GSM coordinates (in units Re=6371.2 km)
|
|
192
|
+
:return: hxgsm,hygsm,hzgsm. Cartesian GSM components of the main geomagnetic field in nanotesla
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
xgeo,ygeo,zgeo = geogsm(xgsm,ygsm,zgsm, -1)
|
|
196
|
+
r,theta,phi = sphcar(xgeo,ygeo,zgeo, -1)
|
|
197
|
+
br,btheta,bphi = igrf_geo(r,theta,phi)
|
|
198
|
+
bxgeo,bygeo,bzgeo = bspcar(theta,phi,br,btheta,bphi)
|
|
199
|
+
return geogsm(bxgeo,bygeo,bzgeo, 1)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def igrf_geo(r,theta,phi):
|
|
203
|
+
"""
|
|
204
|
+
Calculates components of the main (internal) geomagnetic field in the spherical geographic
|
|
205
|
+
(geocentric) coordinate system, using IAGA international geomagnetic reference model
|
|
206
|
+
coefficients (e.g., http://www.ngdc.noaa.gov/iaga/vmod/igrf.html, revised: 22 march, 2005)
|
|
207
|
+
|
|
208
|
+
Before the first call of this subroutine, or if the time was changed,
|
|
209
|
+
the model coefficients should be updated by calling the subroutine recalc
|
|
210
|
+
|
|
211
|
+
Python version by Sheng Tian
|
|
212
|
+
|
|
213
|
+
:param r: spherical geographic (geocentric) coordinates: radial distance r in units Re=6371.2 km
|
|
214
|
+
:param theta: colatitude theta in radians
|
|
215
|
+
:param phi: longitude phi in radians
|
|
216
|
+
:return: br, btheta, bphi. Spherical components of the main geomagnetic field in nanotesla
|
|
217
|
+
(positive br outward, btheta southward, bphi eastward)
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# common /geopack2/ g(105),h(105),rec(105)
|
|
222
|
+
global g, h, rec
|
|
223
|
+
|
|
224
|
+
ct = np.cos(theta)
|
|
225
|
+
st = np.sin(theta)
|
|
226
|
+
minst = 1e-5
|
|
227
|
+
if np.abs(st) < minst: smlst = True
|
|
228
|
+
else: smlst = False
|
|
229
|
+
|
|
230
|
+
# In this new version, the optimal value of the parameter nm (maximal order of the spherical
|
|
231
|
+
# harmonic expansion) is not user-prescribed, but calculated inside the subroutine, based
|
|
232
|
+
# on the value of the radial distance r:
|
|
233
|
+
irp3 = np.int64(r+2)
|
|
234
|
+
nm = np.int64(3+30/irp3)
|
|
235
|
+
if nm > 13: nm = 13
|
|
236
|
+
k = nm+1
|
|
237
|
+
|
|
238
|
+
# r dependence is encapsulated here.
|
|
239
|
+
a = np.empty(k)
|
|
240
|
+
b = np.empty(k)
|
|
241
|
+
ar = 1/r # a/r
|
|
242
|
+
a[0] = ar*ar # a[n] = (a/r)^(n+2).
|
|
243
|
+
b[0] = a[0] # b[n] = (n+1)(a/r)^(n+2)
|
|
244
|
+
for n in range(1,k):
|
|
245
|
+
a[n] = a[n-1]*ar
|
|
246
|
+
b[n] = a[n]*(n+1)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# t - short for theta, f - short for phi.
|
|
250
|
+
br,bt,bf = [0.]*3
|
|
251
|
+
d,p = [0.,1]
|
|
252
|
+
|
|
253
|
+
# m = 0. P^n,0
|
|
254
|
+
m = 0
|
|
255
|
+
smf,cmf = [0.,1]
|
|
256
|
+
p1,d1,p2,d2 = [p,d,0.,0]
|
|
257
|
+
l0 = 0
|
|
258
|
+
mn = l0
|
|
259
|
+
for n in range(m,k):
|
|
260
|
+
w = g[mn]*cmf+h[mn]*smf
|
|
261
|
+
br += b[n]*w*p1 # p1 is P^n,m.
|
|
262
|
+
bt -= a[n]*w*d1 # d1 is dP^n,m/dt.
|
|
263
|
+
xk = rec[mn]
|
|
264
|
+
# Eq 16c and its derivative on theta.
|
|
265
|
+
d0 = ct*d1-st*p1-xk*d2 # dP^n,m/dt = ct*dP^n-1,m/dt - st*P_n-1,m - K^n,m*dP^n-2,m/dt
|
|
266
|
+
p0 = ct*p1-xk*p2 # P^n,m = ct*P^n-1,m - K^n,m*P^n-2,m
|
|
267
|
+
d2,p2,d1 = [d1,p1,d0]
|
|
268
|
+
p1 = p0
|
|
269
|
+
mn += n+1
|
|
270
|
+
|
|
271
|
+
# Eq 16b and its derivative on theta.
|
|
272
|
+
d = st*d+ct*p # dP^m,m/dt = st*dP^m-1,m-1/dt + ct*P^m-1,m-1
|
|
273
|
+
p = st*p # P^m,m = st*P^m-1,m-1
|
|
274
|
+
|
|
275
|
+
# Similarly for P^n,m
|
|
276
|
+
l0 = 0
|
|
277
|
+
for m in range(1,k): # sum over m
|
|
278
|
+
smf = np.sin(m*phi) # sin(m*phi)
|
|
279
|
+
cmf = np.cos(m*phi) # cos(m*phi)
|
|
280
|
+
p1,d1,p2,d2 = [p,d,0.,0]
|
|
281
|
+
tbf = 0.
|
|
282
|
+
l0 += m+1
|
|
283
|
+
mn = l0
|
|
284
|
+
for n in range(m,k): # sum over n
|
|
285
|
+
w=g[mn]*cmf+h[mn]*smf # [g^n,m*cos(m*phi)+h^n,m*sin(m*phi)]
|
|
286
|
+
br += b[n]*w*p1
|
|
287
|
+
bt -= a[n]*w*d1
|
|
288
|
+
tp = p1
|
|
289
|
+
if smlst: tp = d1
|
|
290
|
+
tbf += a[n]*(g[mn]*smf-h[mn]*cmf)*tp
|
|
291
|
+
xk = rec[mn]
|
|
292
|
+
d0 = ct*d1-st*p1-xk*d2 # dP^n,m/dt = ct*dP^n-1,m/dt - st*P_n-1,m - K^n,m*dP^n-2,m/dt
|
|
293
|
+
p0 = ct*p1-xk*p2 # P^n,m = ct*P^n-1,m - K^n,m*P^n-2,m
|
|
294
|
+
d2,p2,d1 = [d1,p1,d0]
|
|
295
|
+
p1=p0
|
|
296
|
+
mn += n+1
|
|
297
|
+
|
|
298
|
+
d = st*d+ct*p
|
|
299
|
+
p = st*p
|
|
300
|
+
|
|
301
|
+
# update B_phi.
|
|
302
|
+
tbf *= m
|
|
303
|
+
bf += tbf
|
|
304
|
+
|
|
305
|
+
if smlst:
|
|
306
|
+
if ct < 0.: bf = -bf
|
|
307
|
+
else: bf /= st
|
|
308
|
+
|
|
309
|
+
return br,bt,bf
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def dip(xgsm,ygsm,zgsm):
|
|
315
|
+
"""
|
|
316
|
+
Calculates gsm components of a geodipole field with the dipole moment
|
|
317
|
+
corresponding to the epoch, specified by calling subroutine recalc (should be
|
|
318
|
+
invoked before the first use of this one and in case the date/time was changed).
|
|
319
|
+
|
|
320
|
+
:param xgsm,ygsm,zgsm: GSM coordinates in Re (1 Re = 6371.2 km)
|
|
321
|
+
:return: bxgsm,bygsm,gzgsm. Field components in gsm system, in nanotesla.
|
|
322
|
+
|
|
323
|
+
Last modification: May 4, 2005.
|
|
324
|
+
Author: N. A. Tsyganenko
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
# common /geopack1/ aaa(10),sps,cps,bbb(23)
|
|
328
|
+
# common /geopack2/ g(105),h(105),rec(105)
|
|
329
|
+
global aaa, sps,cps, bbb, g, h, rec
|
|
330
|
+
|
|
331
|
+
dipmom = np.sqrt(g[1]**2+g[2]**2+h[2]**2)
|
|
332
|
+
|
|
333
|
+
p = xgsm**2
|
|
334
|
+
u = zgsm**2
|
|
335
|
+
v = 3*zgsm*xgsm
|
|
336
|
+
t = ygsm**2
|
|
337
|
+
q = dipmom/np.sqrt(p+t+u)**5
|
|
338
|
+
|
|
339
|
+
bxgsm = q*((t+u-2.*p)*sps-v*cps)
|
|
340
|
+
bygsm = -3.*ygsm*q*(xgsm*sps+zgsm*cps)
|
|
341
|
+
bzgsm = q*((p+t-2.*u)*cps-v*sps)
|
|
342
|
+
|
|
343
|
+
return bxgsm,bygsm,bzgsm
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def dip_gsw(xgsw,ygsw,zgsw):
|
|
347
|
+
"""
|
|
348
|
+
Calculates gsm components of a geodipole field with the dipole moment
|
|
349
|
+
corresponding to the epoch, specified by calling subroutine recalc (should be
|
|
350
|
+
invoked before the first use of this one and in case the date/time was changed).
|
|
351
|
+
|
|
352
|
+
:param xgsw,ygsw,zgsw: GSW coordinates in Re (1 Re = 6371.2 km)
|
|
353
|
+
:return: bxgsm,bygsm,gzgsm. Field components in gsm system, in nanotesla.
|
|
354
|
+
|
|
355
|
+
Author: Sheng Tian
|
|
356
|
+
"""
|
|
357
|
+
xgsm,ygsm,zgsm = gswgsm(xgsw,ygsw,zgsw, 1)
|
|
358
|
+
bxgsm,bygsm,bzgsm = dip(xgsm,ygsm,zgsm)
|
|
359
|
+
return gswgsm(bxgsm,bygsm,bzgsm, -1)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def recalc(ut, vxgse=-400,vygse=0,vzgse=0):
|
|
363
|
+
"""
|
|
364
|
+
1. Prepares elements of rotation matrices for transformations of vectors between
|
|
365
|
+
several coordinate systems, most frequently used in space physics.
|
|
366
|
+
2. Prepares coefficients used in the calculation of the main geomagnetic field (igrf model)
|
|
367
|
+
|
|
368
|
+
This subroutine should be invoked before using the following subroutines:
|
|
369
|
+
igrf_geo, igrf_gsm, dip, geomag, geogsm, magsm, smgsm, gsmgse, geigeo.
|
|
370
|
+
There is no need to repeatedly invoke recalc, if multiple calculations are made for the same date and time.
|
|
371
|
+
|
|
372
|
+
:param ut: Universal time in second.
|
|
373
|
+
:param v[xyz]gse: The solar wind velocity expressed in GSE.
|
|
374
|
+
:return: psi. Dipole tilt angle in radian.
|
|
375
|
+
|
|
376
|
+
Python version by Sheng Tian
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
# The common block /geopack1/ contains elements of the rotation matrices and other
|
|
381
|
+
# parameters related to the coordinate transformations performed by this package
|
|
382
|
+
# common /geopack1/ st0,ct0,sl0,cl0,ctcl,stcl,ctsl,stsl,sfi,cfi,sps,
|
|
383
|
+
# cps,shi,chi,hi,psi,xmut,a11,a21,a31,a12,a22,a32,a13,a23,a33,ds3,
|
|
384
|
+
# cgst,sgst,ba(6)
|
|
385
|
+
|
|
386
|
+
# st0/ct0 - sin/cos of teta0 (colat in geo?). sl0/cl0 - sin/cos of lambda0 (longitude in geo?).
|
|
387
|
+
# ctcl/stcl - . ctsl/stsl - . geo x and z?
|
|
388
|
+
# sfi/cfi/xmut - rotate angle between mag and sm and its sin/cos.
|
|
389
|
+
# sps/cps/psi - tilt angle and its sin/cos.
|
|
390
|
+
# shi/chi/hi - rotate angle between gse to gsm and its sin/cos.
|
|
391
|
+
# a[11,...,33] - matrix converts geo to gsm.
|
|
392
|
+
# cgst/sgst - cos/sin of gst.
|
|
393
|
+
# ds3.
|
|
394
|
+
# ba(6).
|
|
395
|
+
global st0,ct0,sl0,cl0,ctcl,stcl,ctsl,stsl,sfi,cfi,sps,cps, \
|
|
396
|
+
shi,chi,hi,psi,xmut,ds3,cgst,sgst,ba, \
|
|
397
|
+
a11,a21,a31,a12,a22,a32,a13,a23,a33, \
|
|
398
|
+
e11,e21,e31,e12,e22,e32,e13,e23,e33
|
|
399
|
+
|
|
400
|
+
# The common block /geopack2/ contains coefficients of the IGRF field model, calculated
|
|
401
|
+
# for a given year and day from their standard epoch values. the array rec contains
|
|
402
|
+
# coefficients used in the recursion relations for legendre associate polynomials.
|
|
403
|
+
# common /geopack2/ g(105),h(105),rec(105)
|
|
404
|
+
global g,h,rec
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
# Compute the m,n related coefficients (following the notation in Davis 2004):
|
|
408
|
+
# 1. The Schmidt quasi-normalization: S_n,m, which normalizes the associated Legendre polynomials P_n^m
|
|
409
|
+
# to the Guassian normalized associated Legendre polynomials P^n,m.
|
|
410
|
+
#
|
|
411
|
+
# Since g_n^m * P_n^m should be constant, mutiplying S_n,m to g_n^m is equivalently converting P_n^m to P^n,m.
|
|
412
|
+
# The benefit of doing so is that P^n,m follows simple recursive form, c.f. Eq (16 a-c) and Eq(17 a-b).
|
|
413
|
+
# 2. rec[mn], which used in the recursion relation.
|
|
414
|
+
k = 14
|
|
415
|
+
nmn = np.int32((k+1)*k/2)
|
|
416
|
+
|
|
417
|
+
# rec[mn].
|
|
418
|
+
rec = np.empty(nmn,dtype=float)
|
|
419
|
+
mn = 0
|
|
420
|
+
for n in range(k): # K^1,m = 0, Eq (17a), automatically done.
|
|
421
|
+
n2 = 2*n+1
|
|
422
|
+
n2 = n2*(n2-2)
|
|
423
|
+
for m in range(n+1):
|
|
424
|
+
rec[mn] = (n-m)*(n+m)/n2 # K^n,m = (n-m)(n+m)/(2n+1)(2n-1), Eq (17b)
|
|
425
|
+
mn += 1
|
|
426
|
+
|
|
427
|
+
# coefficients for a given time, g_n^m(t), h_n^m(t)
|
|
428
|
+
g,h = load_igrf(ut)
|
|
429
|
+
|
|
430
|
+
# now multiply them by schmidt normalization factors:
|
|
431
|
+
s = 1. # S_0,0 = 1, Eq (18a)
|
|
432
|
+
mn = 0
|
|
433
|
+
for n in range(1,k):
|
|
434
|
+
mn += 1 # skip m=0.
|
|
435
|
+
s *= (2*n-1)/n
|
|
436
|
+
g[mn] *= s # S_n,0 = S_n-1,0 * (2n-1)/n, Eq (18b)
|
|
437
|
+
h[mn] *= s
|
|
438
|
+
p = s
|
|
439
|
+
for m in range(1,n+1):
|
|
440
|
+
if m == 1: aa = 2 # aa = delta_m,1
|
|
441
|
+
else: aa = 1
|
|
442
|
+
p *= np.sqrt(aa*(n-m+1)/(n+m))
|
|
443
|
+
mn += 1
|
|
444
|
+
g[mn] *= p # S_n,m = S_n,m-1 * sqrt(aa(n-m+1)/(n+m)), Eq (18c)
|
|
445
|
+
h[mn] *= p # now g/h are actually g^n,m, Eq (14 a-b)
|
|
446
|
+
|
|
447
|
+
g10=-g[1]
|
|
448
|
+
g11=-g[2]
|
|
449
|
+
h11=-h[2]
|
|
450
|
+
|
|
451
|
+
# Now calculate the components of the unit vector ezmag in geo coord.system:
|
|
452
|
+
# sin(teta0)*cos(lambda0), sin(teta0)*sin(lambda0), and cos(teta0)
|
|
453
|
+
# st0 * cl0 st0 * sl0 ct0
|
|
454
|
+
sq=g11**2+h11**2
|
|
455
|
+
sqq=np.sqrt(sq)
|
|
456
|
+
sqr=np.sqrt(g10**2+sq)
|
|
457
|
+
sl0= h11/sqq
|
|
458
|
+
cl0= g11/sqq
|
|
459
|
+
st0= sqq/sqr
|
|
460
|
+
ct0= g10/sqr
|
|
461
|
+
stcl=st0*cl0
|
|
462
|
+
stsl=st0*sl0
|
|
463
|
+
ctsl=ct0*sl0
|
|
464
|
+
ctcl=ct0*cl0
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
gst,slong,srasn,sdec,obliq = sun(ut)
|
|
468
|
+
|
|
469
|
+
# All vectors are expressed in GEI.
|
|
470
|
+
|
|
471
|
+
# xgse_[xyz] (s[123]) are the components of the unit vector exgsm=exgse in GEI,
|
|
472
|
+
# pointing from the earth's center to the sun:
|
|
473
|
+
xgse_x=np.cos(srasn)*np.cos(sdec)
|
|
474
|
+
xgse_y=np.sin(srasn)*np.cos(sdec)
|
|
475
|
+
xgse_z=np.sin(sdec)
|
|
476
|
+
|
|
477
|
+
# zgse_[xyz] (dz[123]) in GEI has the components (0,-sin(delta),cos(delta)) = (0.,-0.397823,0.917462);
|
|
478
|
+
# Here delta = 23.44214 deg for the epoch 1978 (see the book by gurevich or other astronomical handbooks).
|
|
479
|
+
# Here the most accurate time-dependent formula is used:
|
|
480
|
+
zgse_x=0.
|
|
481
|
+
zgse_y=-np.sin(obliq)
|
|
482
|
+
zgse_z= np.cos(obliq)
|
|
483
|
+
|
|
484
|
+
# ygse_[xyz] (dy[123]) = zgse_[xyz] x xgsm_[xyz] in GEI:
|
|
485
|
+
ygse_x=zgse_y*xgse_z-zgse_z*xgse_y
|
|
486
|
+
ygse_y=zgse_z*xgse_x-zgse_x*xgse_z
|
|
487
|
+
ygse_z=zgse_x*xgse_y-zgse_y*xgse_x
|
|
488
|
+
|
|
489
|
+
# zsm_[xyz] (dip[123]) are the components of the unit vector zsm=zmag in GEI:
|
|
490
|
+
cgst=np.cos(gst)
|
|
491
|
+
sgst=np.sin(gst)
|
|
492
|
+
zsm_x=stcl*cgst-stsl*sgst
|
|
493
|
+
zsm_y=stcl*sgst+stsl*cgst
|
|
494
|
+
zsm_z=ct0
|
|
495
|
+
|
|
496
|
+
# xgsw_[xyz] (x[123]) in GEI.
|
|
497
|
+
v1 = -1/np.sqrt(vxgse*vxgse+vygse*vygse+vzgse*vzgse)
|
|
498
|
+
xgsw_x = (vxgse*xgse_x + vygse*ygse_x + vzgse*zgse_x)*v1
|
|
499
|
+
xgsw_y = (vxgse*xgse_y + vygse*ygse_y + vzgse*zgse_y)*v1
|
|
500
|
+
xgsw_z = (vxgse*xgse_z + vygse*ygse_z + vzgse*zgse_z)*v1
|
|
501
|
+
|
|
502
|
+
# ygsw (y[123]) = zsm x xgsw in GEI.
|
|
503
|
+
ygsw_x=zsm_y*xgsw_z-zsm_z*xgsw_y
|
|
504
|
+
ygsw_y=zsm_z*xgsw_x-zsm_x*xgsw_z
|
|
505
|
+
ygsw_z=zsm_x*xgsw_y-zsm_y*xgsw_x
|
|
506
|
+
y=np.sqrt(ygsw_x*ygsw_x+ygsw_y*ygsw_y+ygsw_z*ygsw_z)
|
|
507
|
+
ygsw_x=ygsw_x/y
|
|
508
|
+
ygsw_y=ygsw_y/y
|
|
509
|
+
ygsw_z=ygsw_z/y
|
|
510
|
+
|
|
511
|
+
# zgsw (z[123]) = xgsw x ygsw in GEI.
|
|
512
|
+
zgsw_x = xgsw_y*ygsw_z-xgsw_z*ygsw_y
|
|
513
|
+
zgsw_y = xgsw_z*ygsw_x-xgsw_x*ygsw_z
|
|
514
|
+
zgsw_z = xgsw_x*ygsw_y-xgsw_y*ygsw_x
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# xgsm = xgse in GEI.
|
|
518
|
+
xgsm_x,xgsm_y,xgsm_z = xgse_x,xgse_y,xgse_z
|
|
519
|
+
|
|
520
|
+
# ygsm = zsm x xgsm in GEI.
|
|
521
|
+
ygsm_x = zsm_y*xgsm_z - zsm_z*xgsm_y
|
|
522
|
+
ygsm_y = zsm_z*xgsm_x - zsm_x*xgsm_z
|
|
523
|
+
ygsm_z = zsm_x*xgsm_y - zsm_y*xgsm_x
|
|
524
|
+
y=np.sqrt(ygsm_x*ygsm_x+ygsm_y*ygsm_y+ygsm_z*ygsm_z)
|
|
525
|
+
ygsm_x = ygsm_x/y
|
|
526
|
+
ygsm_y = ygsm_y/y
|
|
527
|
+
ygsm_z = ygsm_z/y
|
|
528
|
+
|
|
529
|
+
# ezgsm = exgsm x eygsm in GEI.
|
|
530
|
+
zgsm_x = xgse_y*ygsm_z-xgse_z*ygsm_y
|
|
531
|
+
zgsm_y = xgse_z*ygsm_x-xgse_x*ygsm_z
|
|
532
|
+
zgsm_z = xgse_x*ygsm_y-xgse_y*ygsm_x
|
|
533
|
+
|
|
534
|
+
# The elements of the matrix gse to gsm are the scalar products:
|
|
535
|
+
# chi=em22=(eygsm,eygse), shi=em23=(eygsm,ezgse), em32=(ezgsm,eygse)=-em23, and em33=(ezgsm,ezgse)=em22
|
|
536
|
+
chi = ygsm_x*ygse_x + ygsm_y*ygse_y + ygsm_z*ygse_z
|
|
537
|
+
shi = ygsm_x*zgse_x + ygsm_y*zgse_y + ygsm_z*zgse_z
|
|
538
|
+
hi = np.arcsin(shi)
|
|
539
|
+
|
|
540
|
+
# elements of the matrix gsm to gsw are the scalar products:
|
|
541
|
+
# e11 = (exgsm, exgsw) e12 = (exgsm, eygsw) e13 = (exgsm, ezgsw)
|
|
542
|
+
# e21 = (eygsm, exgsw) e22 = (eygsm, eygsw) e23 = (eygsm, ezgsw)
|
|
543
|
+
# e31 = (ezgsm, exgsw) e32 = (ezgsm, eygsw) e33 = (ezgsm, ezgsw)
|
|
544
|
+
e11 = xgsm_x*xgsw_x + xgsm_y*xgsw_y + xgsm_z*xgsw_z
|
|
545
|
+
e12 = xgsm_x*ygsw_x + xgsm_y*ygsw_y + xgsm_z*ygsw_z
|
|
546
|
+
e13 = xgsm_x*zgsw_x + xgsm_y*zgsw_y + xgsm_z*zgsw_z
|
|
547
|
+
e21 = ygsm_x*xgsw_x + ygsm_y*xgsw_y + ygsm_z*xgsw_z
|
|
548
|
+
e22 = ygsm_x*ygsw_x + ygsm_y*ygsw_y + ygsm_z*ygsw_z
|
|
549
|
+
e23 = ygsm_x*zgsw_x + ygsm_y*zgsw_y + ygsm_z*zgsw_z
|
|
550
|
+
e31 = zgsm_x*xgsw_x + zgsm_y*xgsw_y + zgsm_z*xgsw_z
|
|
551
|
+
e32 = zgsm_x*ygsw_x + zgsm_y*ygsw_y + zgsm_z*ygsw_z
|
|
552
|
+
e33 = zgsm_x*zgsw_x + zgsm_y*zgsw_y + zgsm_z*zgsw_z
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
# Tilt angle: psi=arcsin(ezsm dot exgsm)
|
|
556
|
+
sps=zsm_x*xgse_x+zsm_y*xgse_y+zsm_z*xgse_z
|
|
557
|
+
cps=np.sqrt(1.-sps**2)
|
|
558
|
+
psi=np.arcsin(sps)
|
|
559
|
+
|
|
560
|
+
# The elements of the matrix mag to sm are the scalar products:
|
|
561
|
+
# cfi=gm22=(eysm,eymag), sfi=gm23=(eysm,exmag); They can be derived as follows:
|
|
562
|
+
# In geo the vectors exmag and eymag have the components (ct0*cl0,ct0*sl0,-st0) and (-sl0,cl0,0), respectively.
|
|
563
|
+
|
|
564
|
+
# Hence, in gei the components are:
|
|
565
|
+
# exmag: ct0*cl0*cos(gst)-ct0*sl0*sin(gst)
|
|
566
|
+
# ct0*cl0*sin(gst)+ct0*sl0*cos(gst)
|
|
567
|
+
# -st0
|
|
568
|
+
# eymag: -sl0*cos(gst)-cl0*sin(gst)
|
|
569
|
+
# -sl0*sin(gst)+cl0*cos(gst)
|
|
570
|
+
# 0
|
|
571
|
+
# The components of eysm in gei were found above as ysm_in_geix, ysm_in_geiy, and ysm_in_geiz;
|
|
572
|
+
# Now we only have to combine the quantities into scalar products:
|
|
573
|
+
xmag_x= ct0*(cl0*cgst-sl0*sgst)
|
|
574
|
+
xmag_y= ct0*(cl0*sgst+sl0*cgst)
|
|
575
|
+
xmag_z=-st0
|
|
576
|
+
ymag_x=-(sl0*cgst+cl0*sgst)
|
|
577
|
+
ymag_y=-(sl0*sgst-cl0*cgst)
|
|
578
|
+
cfi=ygsm_x*ymag_x+ygsm_y*ymag_y
|
|
579
|
+
sfi=ygsm_x*xmag_x+ygsm_y*xmag_y+ygsm_z*xmag_z
|
|
580
|
+
|
|
581
|
+
xmut=(np.arctan2(sfi,cfi)+3.1415926536)*3.8197186342
|
|
582
|
+
|
|
583
|
+
# The elements of the matrix geo to gsm are the scalar products:
|
|
584
|
+
# a11=(exgeo,exgsm), a12=(eygeo,exgsm), a13=(ezgeo,exgsm),
|
|
585
|
+
# a21=(exgeo,eygsm), a22=(eygeo,eygsm), a23=(ezgeo,eygsm),
|
|
586
|
+
# a31=(exgeo,ezgsm), a32=(eygeo,ezgsm), a33=(ezgeo,ezgsm),
|
|
587
|
+
# All the unit vectors in brackets are already defined in gei:
|
|
588
|
+
# xgeo=(cgst,sgst,0), ygeo=(-sgst,cgst,0), zgeo=(0,0,1)
|
|
589
|
+
# and therefore:
|
|
590
|
+
a11= xgsm_x*cgst+xgse_y*sgst
|
|
591
|
+
a12=-xgsm_x*sgst+xgse_y*cgst
|
|
592
|
+
a13= xgsm_z
|
|
593
|
+
a21= ygsm_x *cgst+ygsm_y *sgst
|
|
594
|
+
a22=-ygsm_x *sgst+ygsm_y *cgst
|
|
595
|
+
a23= ygsm_z
|
|
596
|
+
a31= zgsm_x*cgst+zgsm_y*sgst
|
|
597
|
+
a32=-zgsm_x*sgst+zgsm_y*cgst
|
|
598
|
+
a33= zgsm_z
|
|
599
|
+
|
|
600
|
+
return psi
|
|
601
|
+
|
|
602
|
+
def sun(ut):
|
|
603
|
+
"""
|
|
604
|
+
Calculates four quantities necessary for coordinate transformations
|
|
605
|
+
which depend on sun position (and, hence, on universal time and season)
|
|
606
|
+
Based on http://aa.usno.navy.mil/faq/docs/SunApprox.php and http://aa.usno.navy.mil/faq/docs/GAST.php
|
|
607
|
+
|
|
608
|
+
:param ut: ut sec, can be array.
|
|
609
|
+
:return: gst,slong,srasn,sdec. gst - greenwich mean sidereal time, slong - longitude along ecliptic
|
|
610
|
+
srasn - right ascension, sdec - declination of the sun (radians)
|
|
611
|
+
obliq - mean oblique of the ecliptic (radian)
|
|
612
|
+
|
|
613
|
+
Python version by Sheng Tian
|
|
614
|
+
"""
|
|
615
|
+
|
|
616
|
+
twopi = 2*np.pi
|
|
617
|
+
jd2000 = 2451545.0
|
|
618
|
+
# convert to Julian date.
|
|
619
|
+
t0_jd = 2440587.5 # in day, 0 of Julian day.
|
|
620
|
+
secofdaygsw_x = 1./86400 # 1/sec of day.
|
|
621
|
+
t_jd = ut*secofdaygsw_x+t0_jd
|
|
622
|
+
|
|
623
|
+
# d = mjd - mj2000.
|
|
624
|
+
d = t_jd-jd2000
|
|
625
|
+
d = np.squeeze(d)
|
|
626
|
+
|
|
627
|
+
# mean obliquity of the ecliptic, e.
|
|
628
|
+
# e = 23.439 - 0.00000036*d ; in degree.
|
|
629
|
+
e = 0.4090877233749509 - 6.2831853e-9*d
|
|
630
|
+
|
|
631
|
+
# mean anomaly of the Sun, g.
|
|
632
|
+
# g = 357.529 + 0.98560028*d ; in degree.
|
|
633
|
+
g = 6.2400582213628066 + 0.0172019699945780*d
|
|
634
|
+
g = np.mod(g, twopi)
|
|
635
|
+
|
|
636
|
+
# mean longitude of the Sun, q.
|
|
637
|
+
# q = 280.459 + 0.98564736*d ; in degree.
|
|
638
|
+
q = 4.8949329668507771 + 0.0172027916955899*d
|
|
639
|
+
q = np.mod(q, twopi)
|
|
640
|
+
|
|
641
|
+
# geocentric apparent ecliptic longitude, l.
|
|
642
|
+
# l = q + 1.915 sin g + 0.020 sin 2g ; in degree.
|
|
643
|
+
l = q + 0.0334230551756914*np.sin(g) + 0.0003490658503989*np.sin(2*g)
|
|
644
|
+
|
|
645
|
+
# vl - q, mean longitude of the sun.
|
|
646
|
+
# vl = np.mod(279.696678+0.9856473354*dj,360.)/rad
|
|
647
|
+
# q = np.mod(4.881627937990388+0.01720279126623886*dj, twopi)
|
|
648
|
+
|
|
649
|
+
# g, mean anomaly of the sun.
|
|
650
|
+
# g = np.mod(358.475845+0.985600267*dj,360.)/rad
|
|
651
|
+
# g = np.mod(6.256583784118852+0.017201969767685215*dj, twopi)
|
|
652
|
+
|
|
653
|
+
# slong - l, geocentric apparent ecliptic longitude.
|
|
654
|
+
# slong = (vl + (1.91946-0.004789*t)*sin(g) + 0.020094*sin(2*g))/rad
|
|
655
|
+
# l = q+(0.03350089686033036-2.2884002156881157e-09*dj)*np.sin(g)+0.0003507064598957406*np.sin(2*g)
|
|
656
|
+
# l = np.mod(l, twopi)
|
|
657
|
+
|
|
658
|
+
# obliq - e, mean obliquity of the ecliptic.
|
|
659
|
+
# obliq = (23.45229-0.0130125*t)/rad
|
|
660
|
+
# e = 0.40931967763254096-6.217959450123535e-09*dj
|
|
661
|
+
|
|
662
|
+
# sin(d) = sin(e) * sin(L)
|
|
663
|
+
sind = np.sin(e)*np.sin(l)
|
|
664
|
+
sdec = np.arcsin(sind)
|
|
665
|
+
|
|
666
|
+
# tan(RA) = cos(e)*sin(L)/cos(L)
|
|
667
|
+
srasn = np.arctan2(np.cos(e)*np.sin(l), np.cos(l))
|
|
668
|
+
srasn = np.mod(srasn, twopi)
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
# http://aa.usno.navy.mil/faq/docs/GAST.php
|
|
672
|
+
# gst - gmst, greenwich mean sidereal time.
|
|
673
|
+
# gst = np.mod(279.690983+.9856473354*dj+360.*fday+180.,360.)/rad
|
|
674
|
+
# gst = np.mod(4.881528541489487+0.01720279126623886*dj+twopi*fday+np.pi, twopi)
|
|
675
|
+
# gmst = 18.697374558 + 24.06570982441908*d # in hour
|
|
676
|
+
gmst = 4.894961212735792 + 6.30038809898489*d # in rad
|
|
677
|
+
gmst = np.mod(gmst, twopi)
|
|
678
|
+
|
|
679
|
+
return gmst,l,srasn,sdec,e
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def gswgsm(p1,p2,p3, j):
|
|
683
|
+
"""
|
|
684
|
+
Converts gsm to gsw coordinates or vice versa.
|
|
685
|
+
j>0 j<0
|
|
686
|
+
input: j,xgsm,ygsm,zgsm j,xgsw,ygsw,zgsw
|
|
687
|
+
output: xgsw,ygsw,zgsw xgsm,ygsm,zgsm
|
|
688
|
+
|
|
689
|
+
:param p1,p2,p3: input position
|
|
690
|
+
:param j: flag
|
|
691
|
+
:return: output position
|
|
692
|
+
"""
|
|
693
|
+
global e11, e21, e31, e12, e22, e32, e13, e23, e33
|
|
694
|
+
|
|
695
|
+
if j > 0:
|
|
696
|
+
xgsw,ygsw,zgsw = [p1,p2,p3]
|
|
697
|
+
xgsm = xgsw*e11 + ygsw*e12 + zgsw*e13
|
|
698
|
+
ygsm = xgsw*e21 + ygsw*e22 + zgsw*e23
|
|
699
|
+
zgsm = xgsw*e31 + ygsw*e32 + zgsw*e33
|
|
700
|
+
return xgsm,ygsm,zgsm
|
|
701
|
+
else:
|
|
702
|
+
xgsm,ygsm,zgsm = [p1,p2,p3]
|
|
703
|
+
xgsw = xgsm*e11 + ygsm*e21 + zgsm*e31
|
|
704
|
+
ygsw = xgsm*e12 + ygsm*e22 + zgsm*e32
|
|
705
|
+
zgsw = xgsm*e13 + ygsm*e23 + zgsm*e33
|
|
706
|
+
return xgsw,ygsw,zgsw
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def geomag(p1,p2,p3, j):
|
|
710
|
+
"""
|
|
711
|
+
Converts geographic (geo) to dipole (mag) coordinates or vice versa.
|
|
712
|
+
j>0 j<0
|
|
713
|
+
input: j,xgeo,ygeo,zgeo j,xmag,ymag,zmag
|
|
714
|
+
output: xmag,ymag,zmag xgeo,ygeo,zgeo
|
|
715
|
+
|
|
716
|
+
:param p1,p2,p3: input position
|
|
717
|
+
:param j: flag
|
|
718
|
+
:return: output position
|
|
719
|
+
"""
|
|
720
|
+
|
|
721
|
+
# Attention: subroutine recalc must be invoked before geomag in two cases:
|
|
722
|
+
# /a/ before the first transformation of coordinates
|
|
723
|
+
# /b/ if the values of time have been changed
|
|
724
|
+
|
|
725
|
+
# common /geopack1/ st0,ct0,sl0,cl0,ctcl,stcl,ctsl,stsl,ab(19),bb(8)
|
|
726
|
+
global st0,ct0, sl0,sl0, ctcl,stcl, ctsl,stsl
|
|
727
|
+
|
|
728
|
+
if j > 0:
|
|
729
|
+
xgeo,ygeo,zgeo = [p1,p2,p3]
|
|
730
|
+
xmag = xgeo*ctcl+ygeo*ctsl-zgeo*st0
|
|
731
|
+
ymag = ygeo*cl0-xgeo*sl0
|
|
732
|
+
zmag = xgeo*stcl+ygeo*stsl+zgeo*ct0
|
|
733
|
+
return xmag,ymag,zmag
|
|
734
|
+
else:
|
|
735
|
+
xmag,ymag,zmag = [p1,p2,p3]
|
|
736
|
+
xgeo = xmag*ctcl-ymag*sl0+zmag*stcl
|
|
737
|
+
ygeo = xmag*ctsl+ymag*cl0+zmag*stsl
|
|
738
|
+
zgeo = zmag*ct0-xmag*st0
|
|
739
|
+
return xgeo,ygeo,zgeo
|
|
740
|
+
|
|
741
|
+
def geigeo(p1,p2,p3, j):
|
|
742
|
+
"""
|
|
743
|
+
Converts equatorial inertial (gei) to geographical (geo) coords or vice versa.
|
|
744
|
+
j>0 j<0
|
|
745
|
+
input: j,xgei,ygei,zgei j,xgeo,ygeo,zgeo
|
|
746
|
+
output: xgeo,ygeo,zgeo xgei,ygei,zgei
|
|
747
|
+
|
|
748
|
+
:param p1,p2,p3: input position
|
|
749
|
+
:param j: flag
|
|
750
|
+
:return: output position
|
|
751
|
+
"""
|
|
752
|
+
|
|
753
|
+
# Attention: subroutine recalc must be invoked before geomag in two cases:
|
|
754
|
+
# /a/ before the first transformation of coordinates
|
|
755
|
+
# /b/ if the values of time have been changed
|
|
756
|
+
|
|
757
|
+
# common /geopack1/ a(27),cgst,sgst,b(6)
|
|
758
|
+
global cgst,sgst
|
|
759
|
+
|
|
760
|
+
if j > 0:
|
|
761
|
+
xgei,ygei,zgei = [p1,p2,p3]
|
|
762
|
+
xgeo = xgei*cgst+ygei*sgst
|
|
763
|
+
ygeo = ygei*cgst-xgei*sgst
|
|
764
|
+
zgeo = zgei
|
|
765
|
+
return xgeo,ygeo,zgeo
|
|
766
|
+
else:
|
|
767
|
+
xgeo,ygeo,zgeo = [p1,p2,p3]
|
|
768
|
+
xgei = xgeo*cgst-ygeo*sgst
|
|
769
|
+
ygei = ygeo*cgst+xgeo*sgst
|
|
770
|
+
zgei = zgeo
|
|
771
|
+
return xgei,ygei,zgei
|
|
772
|
+
|
|
773
|
+
def magsm(p1,p2,p3, j):
|
|
774
|
+
"""
|
|
775
|
+
Converts dipole (mag) to solar magnetic (sm) coordinates or vice versa
|
|
776
|
+
j>0 j<0
|
|
777
|
+
input: j,xmag,ymag,zmag j,xsm, ysm, zsm
|
|
778
|
+
output: xsm, ysm, zsm xmag,ymag,zmag
|
|
779
|
+
|
|
780
|
+
:param p1,p2,p3: input position
|
|
781
|
+
:param j: flag
|
|
782
|
+
:return: output position
|
|
783
|
+
"""
|
|
784
|
+
|
|
785
|
+
# Attention: subroutine recalc must be invoked before geomag in two cases:
|
|
786
|
+
# /a/ before the first transformation of coordinates
|
|
787
|
+
# /b/ if the values of time have been changed
|
|
788
|
+
|
|
789
|
+
# common /geopack1/ a(8),sfi,cfi,b(7),ab(10),ba(8)
|
|
790
|
+
global sfi,cfi
|
|
791
|
+
|
|
792
|
+
if j > 0:
|
|
793
|
+
xmag,ymag,zmag = [p1,p2,p3]
|
|
794
|
+
xsm = xmag*cfi-ymag*sfi
|
|
795
|
+
ysm = xmag*sfi+ymag*cfi
|
|
796
|
+
zsm = zmag
|
|
797
|
+
return xsm,ysm,zsm
|
|
798
|
+
else:
|
|
799
|
+
xsm,ysm,zsm = [p1,p2,p3]
|
|
800
|
+
xmag = xsm*cfi+ysm*sfi
|
|
801
|
+
ymag = ysm*cfi-xsm*sfi
|
|
802
|
+
zmag = zsm
|
|
803
|
+
return xmag,ymag,zmag
|
|
804
|
+
|
|
805
|
+
def gsmgse(p1,p2,p3, j):
|
|
806
|
+
"""
|
|
807
|
+
converts geocentric solar magnetospheric (gsm) coords to solar ecliptic (gse) ones or vice versa.
|
|
808
|
+
j>0 j<0
|
|
809
|
+
input: j,xgsm,ygsm,zgsm j,xgse,ygse,zgse
|
|
810
|
+
output: xgse,ygse,zgse xgsm,ygsm,zgsm
|
|
811
|
+
|
|
812
|
+
:param p1,p2,p3: input position
|
|
813
|
+
:param j: flag
|
|
814
|
+
:return: output position
|
|
815
|
+
"""
|
|
816
|
+
|
|
817
|
+
# common /geopack1/ a(12),shi,chi,ab(13),ba(8)
|
|
818
|
+
global shi,chi
|
|
819
|
+
|
|
820
|
+
if j > 0:
|
|
821
|
+
xgsm,ygsm,zgsm = [p1,p2,p3]
|
|
822
|
+
xgse = xgsm
|
|
823
|
+
ygse = ygsm*chi-zgsm*shi
|
|
824
|
+
zgse = ygsm*shi+zgsm*chi
|
|
825
|
+
return xgse,ygse,zgse
|
|
826
|
+
else:
|
|
827
|
+
xgse,ygse,zgse = [p1,p2,p3]
|
|
828
|
+
xgsm = xgse
|
|
829
|
+
ygsm = ygse*chi+zgse*shi
|
|
830
|
+
zgsm = zgse*chi-ygse*shi
|
|
831
|
+
return xgsm,ygsm,zgsm
|
|
832
|
+
|
|
833
|
+
def smgsm(p1,p2,p3, j):
|
|
834
|
+
"""
|
|
835
|
+
Converts solar magnetic (sm) to geocentric solar magnetospheric (gsm) coordinates or vice versa.
|
|
836
|
+
j>0 j<0
|
|
837
|
+
input: j,xsm, ysm, zsm j,xgsm,ygsm,zgsm
|
|
838
|
+
output: xgsm,ygsm,zgsm xsm, ysm, zsm
|
|
839
|
+
|
|
840
|
+
:param p1,p2,p3: input position
|
|
841
|
+
:param j: flag
|
|
842
|
+
:return: output position
|
|
843
|
+
"""
|
|
844
|
+
|
|
845
|
+
# Attention: subroutine recalc must be invoked before geomag in two cases:
|
|
846
|
+
# /a/ before the first transformation of coordinates
|
|
847
|
+
# /b/ if the values of time have been changed
|
|
848
|
+
|
|
849
|
+
# common /geopack1/ a(10),sps,cps,b(15),ab(8)
|
|
850
|
+
global sps,cps
|
|
851
|
+
|
|
852
|
+
if j > 0:
|
|
853
|
+
xsm,ysm,zsm = [p1,p2,p3]
|
|
854
|
+
xgsm = xsm*cps+zsm*sps
|
|
855
|
+
ygsm = ysm
|
|
856
|
+
zgsm = zsm*cps-xsm*sps
|
|
857
|
+
return xgsm,ygsm,zgsm
|
|
858
|
+
else:
|
|
859
|
+
xgsm,ygsm,zgsm = [p1,p2,p3]
|
|
860
|
+
xsm = xgsm*cps-zgsm*sps
|
|
861
|
+
ysm = ygsm
|
|
862
|
+
zsm = xgsm*sps+zgsm*cps
|
|
863
|
+
return xsm,ysm,zsm
|
|
864
|
+
|
|
865
|
+
def geogsm(p1,p2,p3, j):
|
|
866
|
+
"""
|
|
867
|
+
Converts geographic (geo) to geocentric solar magnetospheric (gsm) coordinates or vice versa.
|
|
868
|
+
j>0 j<0
|
|
869
|
+
input: j,xgeo,ygeo,zgeo j,xgsm,ygsm,zgsm
|
|
870
|
+
output: xgsm,ygsm,zgsm xgeo,ygeo,zgeo
|
|
871
|
+
|
|
872
|
+
:param p1,p2,p3: input position
|
|
873
|
+
:param j: flag
|
|
874
|
+
:return: output position
|
|
875
|
+
"""
|
|
876
|
+
|
|
877
|
+
# Attention: subroutine recalc must be invoked before geomag in two cases:
|
|
878
|
+
# /a/ before the first transformation of coordinates
|
|
879
|
+
# /b/ if the values of time have been changed
|
|
880
|
+
|
|
881
|
+
# common /geopack1/aa(17),a11,a21,a31,a12,a22,a32,a13,a23,a33,d,b(8)
|
|
882
|
+
global a11,a21,a31,a12,a22,a32,a13,a23,a33
|
|
883
|
+
|
|
884
|
+
if j > 0:
|
|
885
|
+
xgeo,ygeo,zgeo = [p1,p2,p3]
|
|
886
|
+
xgsm = a11*xgeo+a12*ygeo+a13*zgeo
|
|
887
|
+
ygsm = a21*xgeo+a22*ygeo+a23*zgeo
|
|
888
|
+
zgsm = a31*xgeo+a32*ygeo+a33*zgeo
|
|
889
|
+
return xgsm,ygsm,zgsm
|
|
890
|
+
else:
|
|
891
|
+
xgsm,ygsm,zgsm = [p1,p2,p3]
|
|
892
|
+
xgeo = a11*xgsm+a21*ygsm+a31*zgsm
|
|
893
|
+
ygeo = a12*xgsm+a22*ygsm+a32*zgsm
|
|
894
|
+
zgeo = a13*xgsm+a23*ygsm+a33*zgsm
|
|
895
|
+
return xgeo,ygeo,zgeo
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def geodgeo(p1,p2, j):
|
|
899
|
+
"""
|
|
900
|
+
This subroutine (1) converts vertical local height (altitude) h and geodetic
|
|
901
|
+
latitude xmu into geocentric coordinates r and theta (geocentric radial
|
|
902
|
+
distance and colatitude, respectively; also known as ecef coordinates),
|
|
903
|
+
as well as (2) performs the inverse transformation from {r,theta} to {h,xmu}.
|
|
904
|
+
|
|
905
|
+
The subroutine uses world geodetic system wgs84 parameters for the earth's
|
|
906
|
+
ellipsoid. the angular quantities (geo colatitude theta and geodetic latitude
|
|
907
|
+
xmu) are in radians, and the distances (geocentric radius r and altitude h
|
|
908
|
+
above the earth's ellipsoid) are in kilometers.
|
|
909
|
+
|
|
910
|
+
if j>0, the transformation is made from geodetic to geocentric coordinates using simple direct equations.
|
|
911
|
+
if j<0, the inverse transformation from geocentric to geodetic coordinates is made by means of a fast iterative algorithm.
|
|
912
|
+
|
|
913
|
+
j>0 j<0
|
|
914
|
+
input: j, h,xmu j, r,theta
|
|
915
|
+
output: j, r,theta j, h,xmu
|
|
916
|
+
|
|
917
|
+
Author: N.A. Tsyganenko
|
|
918
|
+
Date: Dec 5, 2007
|
|
919
|
+
|
|
920
|
+
:param h: Altitude in km.
|
|
921
|
+
:param xmu: Geodetic latitude in radian.
|
|
922
|
+
:param r: Geocentric distance in km.
|
|
923
|
+
:param theta: Spherical co-latitude in radian.
|
|
924
|
+
"""
|
|
925
|
+
|
|
926
|
+
# r_eq is the semi-major axis of the earth's ellipsoid,
|
|
927
|
+
# and beta is its second eccentricity squared
|
|
928
|
+
r_eq, beta = 6378.137, 6.73949674228e-3
|
|
929
|
+
|
|
930
|
+
if j>0: # Direct transformation(GEOD->GEO):
|
|
931
|
+
h,xmu = [p1,p2]
|
|
932
|
+
cosxmu = np.cos(xmu)
|
|
933
|
+
sinxmu = np.sin(xmu)
|
|
934
|
+
den = np.sqrt(cosxmu**2+(sinxmu/(1+beta))**2)
|
|
935
|
+
coslam = cosxmu/den
|
|
936
|
+
sinlam = sinxmu/(den*(1+beta))
|
|
937
|
+
rs = r_eq/np.sqrt(1+beta*sinlam**2)
|
|
938
|
+
x = rs*coslam+h*cosxmu
|
|
939
|
+
z = rs*sinlam+h*sinxmu
|
|
940
|
+
r = np.sqrt(x**2+z**2)
|
|
941
|
+
theta = np.arccos(z/r)
|
|
942
|
+
return r,theta
|
|
943
|
+
else: # Inverse transformation(GEO->GEOD):
|
|
944
|
+
r,theta = [p1,p2]
|
|
945
|
+
phi = np.pi*0.5-theta
|
|
946
|
+
phi1,dphi,h,xmu,tol = phi,0,0,0,1e-6
|
|
947
|
+
for n in range(100):
|
|
948
|
+
sp = np.sin(phi1)
|
|
949
|
+
arg = sp*(1+beta)/np.sqrt(1+beta*(2+beta)*sp**2)
|
|
950
|
+
xmu = np.arcsin(arg)
|
|
951
|
+
rs = r_eq/np.sqrt(1+beta*np.sin(phi1)**2)
|
|
952
|
+
cosfims = np.cos(phi1-xmu)
|
|
953
|
+
h = np.sqrt((rs*cosfims)**2+r**2-rs**2)-rs*cosfims
|
|
954
|
+
z = rs*np.sin(phi1)+h*np.sin(xmu)
|
|
955
|
+
x = rs*np.cos(phi1)+h*np.cos(xmu)
|
|
956
|
+
rr = np.sqrt(x**2+z**2)
|
|
957
|
+
dphi = np.arcsin(z/rr)-phi
|
|
958
|
+
phi1 -= dphi
|
|
959
|
+
if np.abs(dphi) <= tol: break
|
|
960
|
+
return h,xmu
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def sphcar(p1,p2,p3, j):
|
|
964
|
+
"""
|
|
965
|
+
Converts spherical coords into cartesian ones and vice versa (theta and phi in radians).
|
|
966
|
+
j>0 j<0
|
|
967
|
+
input: j,r,theta,phi j,x,y,z
|
|
968
|
+
output: x,y,z r,theta,phi
|
|
969
|
+
|
|
970
|
+
:param r,theta,phi:
|
|
971
|
+
:param x,y,z:
|
|
972
|
+
:param j:
|
|
973
|
+
:return:
|
|
974
|
+
|
|
975
|
+
Note: at the poles (x=0 and y=0) we assume phi=0 (when converting from cartesian to spherical coords, i.e., for j<0)
|
|
976
|
+
Last mofification: April 1, 2003 (only some notation changes and more comments added)
|
|
977
|
+
Author: N.A. Tsyganenko
|
|
978
|
+
"""
|
|
979
|
+
|
|
980
|
+
if j > 0:
|
|
981
|
+
r,theta,phi = [p1,p2,p3]
|
|
982
|
+
sq=r*np.sin(theta)
|
|
983
|
+
x=sq*np.cos(phi)
|
|
984
|
+
y=sq*np.sin(phi)
|
|
985
|
+
z= r*np.cos(theta)
|
|
986
|
+
return x,y,z
|
|
987
|
+
else:
|
|
988
|
+
x,y,z = [p1,p2,p3]
|
|
989
|
+
sq=x**2+y**2
|
|
990
|
+
r=np.sqrt(sq+z**2)
|
|
991
|
+
if sq != 0:
|
|
992
|
+
sq=np.sqrt(sq)
|
|
993
|
+
phi=np.arctan2(y,x)
|
|
994
|
+
theta=np.arctan2(sq,z)
|
|
995
|
+
if phi < 0: phi += 2*np.pi
|
|
996
|
+
else:
|
|
997
|
+
phi=0.
|
|
998
|
+
if z < 0: theta = np.pi
|
|
999
|
+
else: theta = 0
|
|
1000
|
+
return r,theta,phi
|
|
1001
|
+
|
|
1002
|
+
def bspcar(theta,phi,br,btheta,bphi):
|
|
1003
|
+
"""
|
|
1004
|
+
Calculates cartesian field components from spherical ones.
|
|
1005
|
+
|
|
1006
|
+
:param theta,phi: spherical angles of the point in radians
|
|
1007
|
+
:param br,btheta,bphi: spherical components of the field
|
|
1008
|
+
:return: bx,by,bz. cartesian components of the field
|
|
1009
|
+
|
|
1010
|
+
# Last mofification: April 1, 2003 (only some notation changes and more comments added)
|
|
1011
|
+
# Author: N.A. Tsyganenko
|
|
1012
|
+
"""
|
|
1013
|
+
|
|
1014
|
+
s= np.sin(theta)
|
|
1015
|
+
c= np.cos(theta)
|
|
1016
|
+
sf=np.sin(phi)
|
|
1017
|
+
cf=np.cos(phi)
|
|
1018
|
+
be=br*s+btheta*c
|
|
1019
|
+
|
|
1020
|
+
bx=be*cf-bphi*sf
|
|
1021
|
+
by=be*sf+bphi*cf
|
|
1022
|
+
bz=br*c-btheta*s
|
|
1023
|
+
|
|
1024
|
+
return bx,by,bz
|
|
1025
|
+
|
|
1026
|
+
def bcarsp(x,y,z,bx,by,bz):
|
|
1027
|
+
"""
|
|
1028
|
+
Calculates spherical field components from those in cartesian system
|
|
1029
|
+
|
|
1030
|
+
:param x,y,z: cartesian components of the position vector
|
|
1031
|
+
:param bx,by,bz: cartesian components of the field vector
|
|
1032
|
+
:return: br,btheta,bphi. spherical components of the field vector
|
|
1033
|
+
|
|
1034
|
+
Last mofification: April 1, 2003 (only some notation changes and more comments added)
|
|
1035
|
+
Author: N.A. Tsyganenko
|
|
1036
|
+
"""
|
|
1037
|
+
|
|
1038
|
+
rho2=x**2+y**2
|
|
1039
|
+
r=np.sqrt(rho2+z**2)
|
|
1040
|
+
rho=np.sqrt(rho2)
|
|
1041
|
+
|
|
1042
|
+
if rho != 0:
|
|
1043
|
+
cphi=x/rho
|
|
1044
|
+
sphi=y/rho
|
|
1045
|
+
else:
|
|
1046
|
+
cphi=1.
|
|
1047
|
+
sphi=0.
|
|
1048
|
+
|
|
1049
|
+
ct=z/r
|
|
1050
|
+
st=rho/r
|
|
1051
|
+
|
|
1052
|
+
br=(x*bx+y*by+z*bz)/r
|
|
1053
|
+
btheta=(bx*cphi+by*sphi)*ct-bz*st
|
|
1054
|
+
bphi=by*cphi-bx*sphi
|
|
1055
|
+
|
|
1056
|
+
return br,btheta,bphi
|
|
1057
|
+
|
|
1058
|
+
|
|
1059
|
+
def call_external_model(exname, par, ps, x,y,z):
|
|
1060
|
+
if exname == 't89':
|
|
1061
|
+
return t89(par, ps, x, y, z)
|
|
1062
|
+
elif exname == 't96':
|
|
1063
|
+
return t96(par, ps, x, y, z)
|
|
1064
|
+
elif exname == 't01':
|
|
1065
|
+
return t01(par, ps, x, y, z)
|
|
1066
|
+
elif exname == 't04':
|
|
1067
|
+
return t04(par, ps, x, y, z)
|
|
1068
|
+
else:
|
|
1069
|
+
raise ValueError
|
|
1070
|
+
|
|
1071
|
+
def call_internal_model(inname, x,y,z):
|
|
1072
|
+
if inname == 'dipole':
|
|
1073
|
+
return dip(x,y,z)
|
|
1074
|
+
elif inname == 'igrf':
|
|
1075
|
+
return igrf_gsm(x,y,z)
|
|
1076
|
+
else:
|
|
1077
|
+
raise ValueError
|
|
1078
|
+
|
|
1079
|
+
def rhand(x,y,z,parmod,exname,inname):
|
|
1080
|
+
"""
|
|
1081
|
+
Calculates the components of the right hand side vector in the geomagnetic field
|
|
1082
|
+
line equation (a subsidiary subroutine for the subroutine step)
|
|
1083
|
+
|
|
1084
|
+
:param x,y,z:
|
|
1085
|
+
:param parmod:
|
|
1086
|
+
:param exname: name of the subroutine for the external field.
|
|
1087
|
+
:param inname: name of the subroutine for the internal field.
|
|
1088
|
+
|
|
1089
|
+
Last mofification: March 31, 2003
|
|
1090
|
+
Author: N.A. Tsyganenko
|
|
1091
|
+
:return: r1,r2,r3.
|
|
1092
|
+
"""
|
|
1093
|
+
# common /geopack1/ a(15),psi,aa(10),ds3,bb(8)
|
|
1094
|
+
global a, psi, aa, ds3, bb
|
|
1095
|
+
|
|
1096
|
+
bxgsm,bygsm,bzgsm = call_external_model(exname, parmod, psi, x,y,z)
|
|
1097
|
+
hxgsm,hygsm,hzgsm = call_internal_model(inname, x,y,z)
|
|
1098
|
+
|
|
1099
|
+
bx=bxgsm+hxgsm
|
|
1100
|
+
by=bygsm+hygsm
|
|
1101
|
+
bz=bzgsm+hzgsm
|
|
1102
|
+
b=ds3/np.sqrt(bx**2+by**2+bz**2)
|
|
1103
|
+
|
|
1104
|
+
r1=bx*b
|
|
1105
|
+
r2=by*b
|
|
1106
|
+
r3=bz*b
|
|
1107
|
+
|
|
1108
|
+
return r1,r2,r3
|
|
1109
|
+
|
|
1110
|
+
def step(x,y,z, ds,errin,parmod,exname,inname):
|
|
1111
|
+
"""
|
|
1112
|
+
Re-calculates {x,y,z}, making a step along a field line.
|
|
1113
|
+
model version, the array parmod contains input parameters for that model
|
|
1114
|
+
|
|
1115
|
+
:param x,y,z: the input position
|
|
1116
|
+
:param ds: the step size
|
|
1117
|
+
:param errin: the permissible error value
|
|
1118
|
+
:param parmod: contains input parameters for that model
|
|
1119
|
+
:param exname: name of the subroutine for the external field.
|
|
1120
|
+
:param inname: name of the subroutine for the internal field.
|
|
1121
|
+
:return: x,y,z. The output position
|
|
1122
|
+
|
|
1123
|
+
Last mofification: March 31, 2003
|
|
1124
|
+
Author: N.A. Tsyganenko
|
|
1125
|
+
"""
|
|
1126
|
+
|
|
1127
|
+
# common /geopack1/ a(26),ds3,b(8)
|
|
1128
|
+
global a, ds3, b
|
|
1129
|
+
|
|
1130
|
+
if errin <=0: raise ValueError
|
|
1131
|
+
errcur = errin
|
|
1132
|
+
i = 0
|
|
1133
|
+
maxloop = 100
|
|
1134
|
+
|
|
1135
|
+
while (errcur >= errin) & (i < maxloop):
|
|
1136
|
+
ds3=-ds/3.
|
|
1137
|
+
r11,r12,r13 = rhand(x,y,z,parmod,exname,inname)
|
|
1138
|
+
r21,r22,r23 = rhand(x+r11,y+r12,z+r13,parmod,exname,inname)
|
|
1139
|
+
r31,r32,r33 = rhand(x+.5*(r11+r21),y+.5*(r12+r22),z+.5*(r13+r23),parmod,exname,inname)
|
|
1140
|
+
r41,r42,r43 = rhand(x+.375*(r11+3.*r31),y+.375*(r12+3.*r32),z+.375*(r13+3.*r33),parmod,exname,inname)
|
|
1141
|
+
r51,r52,r53 = rhand(x+1.5*(r11-3.*r31+4.*r41),y+1.5*(r12-3.*r32+4.*r42),z+1.5*(r13-3.*r33+4.*r43),parmod,exname,inname)
|
|
1142
|
+
errcur=np.abs(r11-4.5*r31+4.*r41-.5*r51)+np.abs(r12-4.5*r32+4.*r42-.5*r52)+np.abs(r13-4.5*r33+4.*r43-.5*r53)
|
|
1143
|
+
if errcur < errin: break
|
|
1144
|
+
|
|
1145
|
+
ds *= 0.5
|
|
1146
|
+
i += 1
|
|
1147
|
+
else:
|
|
1148
|
+
print('reached maximum loop ...')
|
|
1149
|
+
return
|
|
1150
|
+
|
|
1151
|
+
x += 0.5*(r11+4.*r41+r51)
|
|
1152
|
+
y += 0.5*(r12+4.*r42+r52)
|
|
1153
|
+
z += 0.5*(r13+4.*r43+r53)
|
|
1154
|
+
if (errcur < (errin*0.04)) & (np.abs(ds) < 1.33):
|
|
1155
|
+
ds *= 1.5
|
|
1156
|
+
|
|
1157
|
+
return x,y,z
|
|
1158
|
+
|
|
1159
|
+
def trace(xi,yi,zi,dir,rlim=10,r0=1,parmod=2,exname='t89',inname='igrf',maxloop=1000):
|
|
1160
|
+
"""
|
|
1161
|
+
Traces a field line from an arbitrary point of space to the earth's surface or
|
|
1162
|
+
to a model limiting boundary.
|
|
1163
|
+
|
|
1164
|
+
The highest order of spherical harmonics in the main field expansion used
|
|
1165
|
+
in the mapping is calculated automatically. if inname=igrf_gsm, then an IGRF model
|
|
1166
|
+
field will be used, and if inname=dip, a pure dipole field will be used.
|
|
1167
|
+
|
|
1168
|
+
In any case, before calling trace, one should invoke recalc, to calculate correct
|
|
1169
|
+
values of the IGRF coefficients and all quantities needed for transformations
|
|
1170
|
+
between coordinate systems involved in this calculations.
|
|
1171
|
+
|
|
1172
|
+
Alternatively, the subroutine recalc can be invoked with the desired values of
|
|
1173
|
+
ut sec (to specify the dipole moment), while the values of the dipole
|
|
1174
|
+
tilt angle psi (in radians) and its sine (sps) and cosine (cps) can be explicitly
|
|
1175
|
+
specified and forwarded to the common block geopack1 (11th, 12th, and 16th elements, resp.)
|
|
1176
|
+
|
|
1177
|
+
:param xi,yi,zi: gsm coords of initial point (in earth radii, 1 re = 6371.2 km). Must be outside the sphere of r0,
|
|
1178
|
+
otherwise tracing will not start.
|
|
1179
|
+
:param dir: sign of the tracing direction: if
|
|
1180
|
+
dir=1.0 then we move antiparallel to the field vector (e.g. from northern to southern conjugate point), and if
|
|
1181
|
+
dir=-1.0 then the tracing goes in the opposite direction.
|
|
1182
|
+
:param rlim: upper limit of the geocentric distance, where the tracing is terminated.
|
|
1183
|
+
:param r0: radius of a sphere (in re) for which the field line endpoint coordinates xf,yf,zf should be calculated.
|
|
1184
|
+
:param parmod: the iopt for T89, or for the other Txx models it is
|
|
1185
|
+
a 10-element array containing model parameters, needed for a unique specification of the external field.
|
|
1186
|
+
The concrete meaning of the components of parmod depends on a specific version of the external field model.
|
|
1187
|
+
:param exname: name of the subroutine for the external field.
|
|
1188
|
+
:param inname: name of the subroutine for the internal field.
|
|
1189
|
+
:return:
|
|
1190
|
+
xf,yf,zf. GSM coords of the last calculated point of a field line
|
|
1191
|
+
xx,yy,zz. Arrays containing coords of field line points. Here their maximal length was assumed equal to 999.
|
|
1192
|
+
l. Actual number of the calculated field line points. if l exceeds 999, tracing terminates, and a warning is displayed.
|
|
1193
|
+
|
|
1194
|
+
Last mofification: March 31, 2003
|
|
1195
|
+
Author: N.A. Tsyganenko
|
|
1196
|
+
"""
|
|
1197
|
+
|
|
1198
|
+
# common /geopack1/ aa(26),dd,bb(8)
|
|
1199
|
+
global aa, dd, bb, ds3
|
|
1200
|
+
|
|
1201
|
+
err, l, ds, x,y,z, dd, al = 0.001, 0, 0.5*dir, xi,yi,zi, dir, 0.
|
|
1202
|
+
xx = np.array([x])
|
|
1203
|
+
yy = np.array([y])
|
|
1204
|
+
zz = np.array([z])
|
|
1205
|
+
|
|
1206
|
+
# Here we call RHAND just to find out the sign of the radial component of the field
|
|
1207
|
+
# vector, and to determine the initial direction of the tracing (i.e., either away
|
|
1208
|
+
# or towards Earth):
|
|
1209
|
+
ds3 = -ds/3.
|
|
1210
|
+
r1,r2,r3 = rhand(x,y,z,parmod,exname,inname)
|
|
1211
|
+
|
|
1212
|
+
# |ad|=0.01 and its sign follows the rule:
|
|
1213
|
+
# (1) if dir=1 (tracing antiparallel to B vector) then the sign of ad is the same as of Br
|
|
1214
|
+
# (2) if dir=-1 (tracing parallel to B vector) then the sign of ad is opposite to that of Br
|
|
1215
|
+
# ad is defined in order to initialize the value of rr (radial distance at previous step):
|
|
1216
|
+
ad=0.01
|
|
1217
|
+
if (x*r1+y*r2+z*r3) < 0: ad=-0.01
|
|
1218
|
+
|
|
1219
|
+
rr=np.sqrt(x**2+y**2+z**2)+ad
|
|
1220
|
+
|
|
1221
|
+
while l < maxloop:
|
|
1222
|
+
ryz=y**2+z**2
|
|
1223
|
+
r2=x**2+ryz
|
|
1224
|
+
r=np.sqrt(r2)
|
|
1225
|
+
xr,yr,zr = [x,y,z]
|
|
1226
|
+
|
|
1227
|
+
# check if the line hit the outer tracing boundary; if yes, then terminate the tracing
|
|
1228
|
+
if (r >= rlim) | (ryz >= 1600) | (x>=20): break
|
|
1229
|
+
|
|
1230
|
+
# check whether or not the inner tracing boundary was crossed from outside,
|
|
1231
|
+
# if yes, then calculate the footpoint position by interpolation
|
|
1232
|
+
if (r < r0) & (rr > r): # this is crossing the inner boundary from inside!!!??? looks like it will trace into the boundary and then bounce back out.
|
|
1233
|
+
# find the footpoint position by interpolating between the current and previous field line points:
|
|
1234
|
+
r1=(r0-r)/(rr-r)
|
|
1235
|
+
x=x-(x-xr)*r1
|
|
1236
|
+
y=y-(y-yr)*r1
|
|
1237
|
+
z=z-(z-zr)*r1
|
|
1238
|
+
xr,yr,zr = [x,y,z]
|
|
1239
|
+
break
|
|
1240
|
+
|
|
1241
|
+
# check if (i) we are moving outward, or (ii) we are still sufficiently
|
|
1242
|
+
# far from Earth (beyond R=5Re); if yes, proceed further:
|
|
1243
|
+
if (r >= rr) | (r > 5):
|
|
1244
|
+
pass
|
|
1245
|
+
# now we moved closer inward (between R=3 and R=5); go to 3 and begin logging
|
|
1246
|
+
# previous values of X,Y,Z, to be used in the interpolation (after having
|
|
1247
|
+
# crossed the inner tracing boundary):
|
|
1248
|
+
else:
|
|
1249
|
+
if r >= 3:
|
|
1250
|
+
# We entered inside the sphere R=3: to avoid too large steps (and hence inaccurate
|
|
1251
|
+
# interpolated position of the footpoint), enforce the progressively smaller
|
|
1252
|
+
# stepsize values as we approach the inner boundary R=R0:
|
|
1253
|
+
ds = dir
|
|
1254
|
+
else:
|
|
1255
|
+
fc = 0.2
|
|
1256
|
+
if (r-r0) < 0.05: fc = 0.05
|
|
1257
|
+
al = fc*(r-r0+0.2)
|
|
1258
|
+
ds = dir*al
|
|
1259
|
+
rr=r
|
|
1260
|
+
x,y,z = step(x,y,z,ds,err,parmod,exname,inname)
|
|
1261
|
+
xx = np.append(xx,x)
|
|
1262
|
+
yy = np.append(yy,y)
|
|
1263
|
+
zz = np.append(zz,z)
|
|
1264
|
+
l += 1
|
|
1265
|
+
|
|
1266
|
+
return x,y,z, xx,yy,zz
|
|
1267
|
+
|
|
1268
|
+
def shuetal_mgnp(xn_pd,vel,bzimf,xgsm,ygsm,zgsm):
|
|
1269
|
+
"""
|
|
1270
|
+
For any point of space with coordinates (xgsm,ygsm,zgsm) and specified conditions
|
|
1271
|
+
in the incoming solar wind, this subroutine:
|
|
1272
|
+
(1) determines if the point (xgsm,ygsm,zgsm) lies inside or outside the
|
|
1273
|
+
model magnetopause of Shue et al. (jgr-a, v.103, p. 17691, 1998).
|
|
1274
|
+
(2) calculates the gsm position of a point {xmgnp,ymgnp,zmgnp}, lying at the model
|
|
1275
|
+
magnetopause and asymptotically tending to the nearest boundary point with
|
|
1276
|
+
respect to the observation point {xgsm,ygsm,zgsm}, as it approaches the magnetopause.
|
|
1277
|
+
|
|
1278
|
+
:param xn_pd: either solar wind proton number density (per c.c.) (if vel>0)
|
|
1279
|
+
or the solar wind ram pressure in nanopascals (if vel<0)
|
|
1280
|
+
:param vel: either solar wind velocity (km/sec)
|
|
1281
|
+
or any negative number, which indicates that xn_pd stands
|
|
1282
|
+
for the solar wind pressure, rather than for the density
|
|
1283
|
+
:param bzimf: imf bz in nanoteslas
|
|
1284
|
+
:param xgsm,ygsm,zgsm: gsm position of the observation point in earth radii
|
|
1285
|
+
:return: xmgnp,ymgnp,zmgnp. GSM position of the boundary point.
|
|
1286
|
+
dist. Distance (in re) between the observation point (xgsm,ygsm,zgsm) and the model magnetopause
|
|
1287
|
+
id. position flag: id=+1 (-1) means that the observation point lies inside (outside) of the model magnetopause, respectively.
|
|
1288
|
+
|
|
1289
|
+
Last mofification: April 4, 2003
|
|
1290
|
+
Author: N.A. Tsyganenko
|
|
1291
|
+
"""
|
|
1292
|
+
|
|
1293
|
+
# pd is the solar wind dynamic pressure (in npa)
|
|
1294
|
+
if vel < 0: pd = xn_pd
|
|
1295
|
+
else: pd = 1.94e-6*xn_pd*vel**2
|
|
1296
|
+
|
|
1297
|
+
# Define the angle phi, measured duskward from the noon-midnight meridian plane;
|
|
1298
|
+
# if the observation point lies on the x axis, the angle phi cannot be uniquely
|
|
1299
|
+
# defined, and we set it at zero:
|
|
1300
|
+
if (ygsm != 0) | (zgsm != 0): phi = np.arctan2(ygsm,zgsm)
|
|
1301
|
+
else: phi = 0
|
|
1302
|
+
|
|
1303
|
+
# First, find out if the observation point lies inside the Shue et al bdry
|
|
1304
|
+
# and set the value of the id flag:
|
|
1305
|
+
id = -1
|
|
1306
|
+
r0 = (10.22+1.29*np.tanh(0.184*(bzimf+8.14)))*pd**(-0.15151515)
|
|
1307
|
+
alpha = (0.58-0.007*bzimf)*(1.+0.024*np.log(pd))
|
|
1308
|
+
r = np.sqrt(xgsm**2+ygsm**2+zgsm**2)
|
|
1309
|
+
rm = r0*(2./(1.+xgsm/r))**alpha
|
|
1310
|
+
if r < rm: id = 1
|
|
1311
|
+
|
|
1312
|
+
# Now, find the corresponding t96 magnetopause position, to be used as
|
|
1313
|
+
# a starting approximation in the search of a corresponding Shue et al.
|
|
1314
|
+
# boundary point:
|
|
1315
|
+
xmt96,ymt96,zmt96,dist,id96 = t96_mgnp(pd,-1.,xgsm,ygsm,zgsm)
|
|
1316
|
+
|
|
1317
|
+
rho2 = ymt96**2+zmt96**2
|
|
1318
|
+
r = np.sqrt(rho2+xmt96**2)
|
|
1319
|
+
st = np.sqrt(rho2)/r
|
|
1320
|
+
ct = xmt96/r
|
|
1321
|
+
|
|
1322
|
+
# Now, use newton's iterative method to find the nearest point at the Shue et al.'s boundary:
|
|
1323
|
+
nit = 0
|
|
1324
|
+
while nit < 1000:
|
|
1325
|
+
nit += 1
|
|
1326
|
+
|
|
1327
|
+
t = np.arctan2(st,ct)
|
|
1328
|
+
rm = r0*(2./(1.+ct))**alpha
|
|
1329
|
+
|
|
1330
|
+
f = r-rm
|
|
1331
|
+
gradf_r = 1.
|
|
1332
|
+
gradf_t = -alpha/r*rm*st/(1.+ct)
|
|
1333
|
+
gradf = np.sqrt(gradf_r**2+gradf_t**2)
|
|
1334
|
+
|
|
1335
|
+
dr = -f/gradf**2
|
|
1336
|
+
dt = dr/r*gradf_t
|
|
1337
|
+
|
|
1338
|
+
r = r+dr
|
|
1339
|
+
t = t+dt
|
|
1340
|
+
st = np.sin(t)
|
|
1341
|
+
ct = np.cos(t)
|
|
1342
|
+
|
|
1343
|
+
ds = np.sqrt(dr**2+(r*dt)**2)
|
|
1344
|
+
if ds <= 1.e-4: break
|
|
1345
|
+
else: print(' boundary point could not be found; iterations do not converge')
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
xmgnp = r*np.cos(t)
|
|
1349
|
+
rho = r*np.sin(t)
|
|
1350
|
+
ymgnp = rho*np.sin(phi)
|
|
1351
|
+
zmgnp = rho*np.cos(phi)
|
|
1352
|
+
|
|
1353
|
+
dist = np.sqrt((xgsm-xmgnp)**2+(ygsm-ymgnp)**2+(zgsm-zmgnp)**2)
|
|
1354
|
+
|
|
1355
|
+
return xmgnp,ymgnp,zmgnp, dist, id
|
|
1356
|
+
|
|
1357
|
+
def t96_mgnp(xn_pd,vel,xgsm,ygsm,zgsm):
|
|
1358
|
+
"""
|
|
1359
|
+
For any point of space with given coordinates (xgsm,ygsm,zgsm), this subroutine defines
|
|
1360
|
+
the position of a point (xmgnp,ymgnp,zmgnp) at the T96 model magnetopause, having the
|
|
1361
|
+
same value of the ellipsoidal tau-coordinate, and the distance between them. This is
|
|
1362
|
+
not the shortest distance d_min to the boundary, but dist asymptotically tends to d_min,
|
|
1363
|
+
as the observation point gets closer to the magnetopause.
|
|
1364
|
+
|
|
1365
|
+
The pressure-dependent magnetopause is that used in the t96_01 model
|
|
1366
|
+
(Tsyganenko, jgr, v.100, p.5599, 1995; esa sp-389, p.181, oct. 1996)
|
|
1367
|
+
|
|
1368
|
+
:param xn_pd: either solar wind proton number density (per c.c.) (if vel>0)
|
|
1369
|
+
or the solar wind ram pressure in nanopascals (if vel<0)
|
|
1370
|
+
:param vel: either solar wind velocity (km/sec)
|
|
1371
|
+
or any negative number, which indicates that xn_pd stands
|
|
1372
|
+
for the solar wind pressure, rather than for the density
|
|
1373
|
+
:param xgsm,ygsm,zgsm: gsm position of the observation point in earth radii
|
|
1374
|
+
:return: xmgnp,ymgnp,zmgnp. GSM position of the boundary point.
|
|
1375
|
+
dist. Distance (in re) between the observation point (xgsm,ygsm,zgsm) and the model magnetopause
|
|
1376
|
+
id. position flag: id=+1 (-1) means that the observation point lies inside (outside) of the model magnetopause, respectively.
|
|
1377
|
+
|
|
1378
|
+
Last mofification: April 3, 2003
|
|
1379
|
+
Author: N.A. Tsyganenko
|
|
1380
|
+
"""
|
|
1381
|
+
# Define solar wind dynamic pressure (nanopascals, assuming 4% of alpha-particles),
|
|
1382
|
+
# if not explicitly specified in the input:
|
|
1383
|
+
if vel < 0: pd = xn_pd
|
|
1384
|
+
else: pd = 1.94e-6*xn_pd*vel**2
|
|
1385
|
+
|
|
1386
|
+
# ratio of pd to the average pressure, assumed equal to 2 npa:
|
|
1387
|
+
# The power index 0.14 in the scaling factor is the best-fit value
|
|
1388
|
+
# obtained from data and used in the t96_01 version
|
|
1389
|
+
# Values of the magnetopause parameters for pd = 2 npa:
|
|
1390
|
+
rat = pd/2.0
|
|
1391
|
+
rat16 = rat**0.14
|
|
1392
|
+
|
|
1393
|
+
a0, s00, x00 = [70.,1.08,5.48]
|
|
1394
|
+
|
|
1395
|
+
# Values of the magnetopause parameters, scaled by the actual pressure:
|
|
1396
|
+
# xm is the x-coordinate of the "seam" between the ellipsoid and the cylinder
|
|
1397
|
+
# For details on the ellipsoidal coordinates, see the paper:
|
|
1398
|
+
# N.A. Tsyganenko, Solution of chapman-ferraro problem for an ellipsoidal magnetopause, planet.space sci., v.37, p.1037, 1989).
|
|
1399
|
+
a = a0/rat16
|
|
1400
|
+
s0 = s00
|
|
1401
|
+
x0 = x00/rat16
|
|
1402
|
+
xm = x0-a
|
|
1403
|
+
|
|
1404
|
+
|
|
1405
|
+
if (ygsm != 0) | (zgsm != 0): phi = np.arctan2(ygsm,zgsm)
|
|
1406
|
+
else: phi = 0
|
|
1407
|
+
|
|
1408
|
+
rho = np.sqrt(ygsm**2+zgsm**2)
|
|
1409
|
+
if xgsm < xm:
|
|
1410
|
+
xmgnp = xgsm
|
|
1411
|
+
rhomgnp = a*np.sqrt(s0**2-1)
|
|
1412
|
+
ymgnp = rhomgnp*np.sin(phi)
|
|
1413
|
+
zmgnp = rhomgnp*np.cos(phi)
|
|
1414
|
+
dist = np.sqrt((xgsm-xmgnp)**2+(ygsm-ymgnp)**2+(zgsm-zmgnp)**2)
|
|
1415
|
+
if rhomgnp > rho: id = 1
|
|
1416
|
+
else: id = -1
|
|
1417
|
+
return xmgnp,ymgnp,zmgnp, dist, id
|
|
1418
|
+
|
|
1419
|
+
xksi = (xgsm-x0)/a+1.
|
|
1420
|
+
xdzt = rho/a
|
|
1421
|
+
sq1 = np.sqrt((1.+xksi)**2+xdzt**2)
|
|
1422
|
+
sq2 = np.sqrt((1.-xksi)**2+xdzt**2)
|
|
1423
|
+
sigma = 0.5*(sq1+sq2)
|
|
1424
|
+
tau = 0.5*(sq1-sq2)
|
|
1425
|
+
|
|
1426
|
+
# Now calculate (x,y,z) for the closest point at the magnetopause
|
|
1427
|
+
xmgnp = x0-a*(1.-s0*tau)
|
|
1428
|
+
arg = (s0**2-1.)*(1.-tau**2)
|
|
1429
|
+
if arg < 0: arg = 0
|
|
1430
|
+
rhomgnp = a*np.sqrt(arg)
|
|
1431
|
+
ymgnp = rhomgnp*np.sin(phi)
|
|
1432
|
+
zmgnp = rhomgnp*np.cos(phi)
|
|
1433
|
+
|
|
1434
|
+
# Now calculate the distance between the points {xgsm,ygsm,zgsm} and {xmgnp,ymgnp,zmgnp}:
|
|
1435
|
+
# (in general, this is not the shortest distance d_min, but dist asymptotically tends
|
|
1436
|
+
# to d_min, as we are getting closer to the magnetopause):
|
|
1437
|
+
dist = np.sqrt((xgsm-xmgnp)**2+(ygsm-ymgnp)**2+(zgsm-zmgnp)**2)
|
|
1438
|
+
if sigma > s0: id = -1 # id = -1 means that the point lies outside
|
|
1439
|
+
else: id = 1 # id = 1 means that the point lies inside
|
|
1440
|
+
|
|
1441
|
+
return xmgnp,ymgnp,zmgnp, dist, id
|
|
1442
|
+
|
|
1443
|
+
|
|
1444
|
+
init_igrf()
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
if __name__ == '__main__':
|
|
1448
|
+
update_igrf()
|
|
1449
|
+
init_igrf()
|