-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathgrid.py
More file actions
439 lines (338 loc) · 12.5 KB
/
Copy pathgrid.py
File metadata and controls
439 lines (338 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
"""
create simulation grids
"""
import typing as T
import math
import numpy as np
from .readdata import readgrid
pi = math.pi
def makegrid_cart3d(p: T.Dict[str, T.Any]) -> T.Dict[str, T.Any]:
""" make 3D grid and save to disk
Parameters
-----------
p: dict
simulation parameters
Returns
-------
xg: dict
simulation grid
"""
# %%create altitude grid
# original Matlab params
# p.alt_min = 80e3;
# p.alt_max = 1000e3;
# p.alt_scale = [10e3, 8e3, 500e3, 150e3];
if set(("alt_min", "alt_max", "alt_scale", "Bincl")) <= p.keys():
z = altitude_grid(p["alt_min"], p["alt_max"], p["Bincl"], p["alt_scale"])
elif "eqdir" in p and p["eqdir"].is_file():
print("makegrid_cart_3D: reusing grid from", p["eqdir"])
xeq = readgrid(p["eqdir"])
z = xeq["x1"]
del xeq
else:
raise ValueError("must specify altitude grid parameters or grid file to reuse")
# %% TRANSVERSE GRID (BASED ON SIZE OF CURRENT REGION SPECIFIED ABOVE)
# EAST
x = grid1d(p["xdist"], p["lxp"])
# NORTH
y = grid1d(p["ydist"], p["lyp"])
# %% COMPUTE CELL WALL LOCATIONS
lx2 = x.size
xi = np.empty(lx2 + 1)
xi[1:-1] = 1 / 2 * (x[1:] + x[:-1])
xi[0] = x[0] - 1 / 2 * (x[1] - x[0])
xi[-1] = x[-1] + 1 / 2 * (x[-1] - x[-2])
lx3 = y.size
yi = np.empty(lx3 + 1)
yi[1:-1] = 1 / 2 * (y[1:] + y[:-1])
yi[0] = y[0] - 1 / 2 * (y[1] - y[0])
yi[-1] = y[-1] + 1 / 2 * (y[-1] - y[-2])
lx1 = z.size
zi = np.empty(lx1 + 1)
zi[1:-1] = 1 / 2 * (z[1:] + z[:-1])
zi[0] = z[0] - 1 / 2 * (z[1] - z[0])
zi[-1] = z[-1] + 1 / 2 * (z[-1] - z[-2])
# %% GRAVITATIONAL FIELD COMPONENTS IN DIPOLE SYSTEM
Re = 6370e3
G = 6.67428e-11
Me = 5.9722e24
r = z + Re
g = G * Me / r ** 2
gz = np.tile(-g[:, None, None], (1, lx2, lx3))
assert gz.shape == (lx1, lx2, lx3)
# DISTANCE EW AND NS (FROM ENU (or UEN in our case - cyclic permuted) COORD. SYSTEM)
# #NEED TO BE CONVERTED TO DIPOLE SPHERICAL AND THEN
# GLAT/GLONG - BASICALLY HERE WE ARE MAPPING THE CARTESIAN GRID ONTO THE
# SURFACE OF A SPHERE THEN CONVERTING TO GEOGRAPHIC.
# get the magnetic coordinates of the grid center, based on user input
thetactr, phictr = geog2geomag(p["glat"], p["glon"])
# %% Center of earth distance
r = Re + z
r = np.tile(r[:, None, None], (1, lx2, lx3))
assert r.shape == (lx1, lx2, lx3)
# %% Northward angular distance
gamma2 = y / Re
# must retain the sign of x3
theta = thetactr - gamma2
# minus because distance north is against theta's direction
theta = np.tile(theta[None, None, :], (lx1, lx2, 1))
assert theta.shape == (lx1, lx2, lx3)
# %% Eastward angular distance
# gamma1=x/Re; %must retain the sign of x2
gamma1 = x / Re / np.sin(thetactr)
# must retain the sign of x2, just use theta of center of grid
phi = phictr + gamma1
phi = np.tile(phi[None, :, None], (lx1, 1, lx3))
assert phi.shape == (lx1, lx2, lx3)
# %% COMPUTE THE GEOGRAPHIC COORDINATES OF EACH GRID POINT
glatgrid, glongrid = geomag2geog(theta, phi)
# %% COMPUTE ECEF CARTESIAN IN CASE THEY ARE NEEDED
xECEF = r * np.sin(theta) * np.cos(phi)
yECEF = r * np.sin(theta) * np.sin(phi)
zECEF = r * np.cos(theta)
# %% COMPUTE SPHERICAL ECEF UNIT VECTORS - CARTESIAN-ECEF COMPONENTS
er = np.empty((lx1, lx2, lx3, 3))
etheta = np.empty_like(er)
ephi = np.empty_like(er)
er[:, :, :, 0] = np.sin(theta) * np.cos(phi)
# xECEF-component of er
er[:, :, :, 1] = np.sin(theta) * np.sin(phi)
# yECEF
er[:, :, :, 2] = np.cos(theta)
# zECEF
etheta[:, :, :, 0] = np.cos(theta) * np.cos(phi)
etheta[:, :, :, 1] = np.cos(theta) * np.sin(phi)
etheta[:, :, :, 2] = -np.sin(theta)
ephi[:, :, :, 0] = -np.sin(phi)
ephi[:, :, :, 1] = np.cos(phi)
ephi[:, :, :, 2] = 0
# %% UEN UNIT VECTORS IN ECEF COMPONENTS
e1 = er
# up is the same direction as from ctr of earth
e2 = ephi
# e2 is same as ephi
e3 = -etheta
# etheta is positive south, e3 is pos. north
# %% STORE RESULTS IN GRID DATA STRUCTURE
xg = {
"x1": z,
"x2": x,
"x3": y,
"x1i": zi,
"x2i": xi,
"x3i": yi,
}
lx = np.array((xg["x1"].size, xg["x2"].size, xg["x3"].size))
xg["lx"] = lx
xg["dx1f"] = np.append(xg["x1"][1:] - xg["x1"][:-1], xg["x1"][-1] - xg["x1"][-2])
# FWD DIFF
xg["dx1b"] = np.insert(xg["x1"][1:] - xg["x1"][:-1], 0, xg["x1"][1] - xg["x1"][0])
# BACK DIFF
xg["dx1h"] = xg["x1i"][1:-1] - xg["x1i"][:-2]
# MIDPOINT DIFFS
xg["dx2f"] = np.append(xg["x2"][1:] - xg["x2"][:-1], xg["x2"][-1] - xg["x2"][-2])
# FWD DIFF
xg["dx2b"] = np.insert(xg["x2"][1:] - xg["x2"][:-1], 0, xg["x2"][1] - xg["x2"][0])
# BACK DIFF
xg["dx2h"] = xg["x2i"][1:-1] - xg["x2i"][:-2]
# MIDPOINT DIFFS
xg["dx3f"] = np.append(xg["x3"][1:] - xg["x3"][:-1], xg["x3"][-1] - xg["x3"][-2])
# FWD DIFF
xg["dx3b"] = np.insert(xg["x3"][1:] - xg["x3"][:-1], 0, xg["x3"][1] - xg["x3"][0])
# BACK DIFF
xg["dx3h"] = xg["x3i"][1:-1] - xg["x3i"][:-2]
# MIDPOINT DIFFS
xg["h1"] = np.ones(xg["lx"])
xg["h2"] = np.ones(xg["lx"])
xg["h3"] = np.ones(xg["lx"])
xg["h1x1i"] = np.ones((lx[0] + 1, lx[1], lx[2]))
xg["h2x1i"] = np.ones((lx[0] + 1, lx[1], lx[2]))
xg["h3x1i"] = np.ones((lx[0] + 1, lx[1], lx[2]))
xg["h1x2i"] = np.ones((lx[0], lx[1] + 1, lx[2]))
xg["h2x2i"] = np.ones((lx[0], lx[1] + 1, lx[2]))
xg["h3x2i"] = np.ones((lx[0], lx[1] + 1, lx[2]))
xg["h1x3i"] = np.ones((lx[0], lx[1], lx[2] + 1))
xg["h2x3i"] = np.ones((lx[0], lx[1], lx[2] + 1))
xg["h3x3i"] = np.ones((lx[0], lx[1], lx[2] + 1))
# %% Cartesian, ECEF representation of curvilinar coordinates
xg["e1"] = e1
xg["e2"] = e2
xg["e3"] = e3
# %% ECEF spherical coordinates
xg["r"] = r
xg["theta"] = theta
xg["phi"] = phi
# xg.rx1i=[]; xg.thetax1i=[];
# xg.rx2i=[]; xg.thetax2i=[];
# %% These are cartesian representations of the ECEF, spherical unit vectors
xg["er"] = er
xg["etheta"] = etheta
xg["ephi"] = ephi
xg["I"] = np.broadcast_to(p["Bincl"], (lx2, lx3))
# %% Cartesian ECEF coordinates
xg["x"] = xECEF
xg["z"] = zECEF
xg["y"] = yECEF
xg["alt"] = xg["r"] - Re
# since we need a 3D array use xg.r here...
xg["gx1"] = gz
xg["gx2"] = np.zeros(xg["lx"])
xg["gx3"] = np.zeros(xg["lx"])
xg["Bmag"] = np.broadcast_to(-50000e-9, xg["lx"])
# minus for northern hemisphere...
xg["glat"] = glatgrid
xg["glon"] = glongrid
# xg['xp']=x; xg['zp']=z;
# xg['inull']=[];
xg["nullpts"] = np.zeros(xg["lx"])
# %% TRIM DATA STRUCTRE TO BE THE SIZE FORTRAN EXPECTS
# note: xgf is xg == True
xgf = xg
# indices corresponding to non-ghost cells for 1 dimension
i1 = slice(2, lx[0] - 2)
i2 = slice(2, lx[1] - 2)
i3 = slice(2, lx[2] - 2)
# any dx variable will not need to first element (backward diff of two ghost cells)
idx1 = slice(1, lx[0])
idx2 = slice(1, lx[1])
idx3 = slice(1, lx[2])
# x1-interface variables need only non-ghost cell values (left interface) plus one
ix1i = slice(2, lx[0] - 1)
ix2i = slice(2, lx[1] - 1)
ix3i = slice(2, lx[2] - 1)
# remove ghost cells
# now that indices have been define we can go ahead and make this change
xgf["lx"] = xgf["lx"] - 4
xgf["dx1b"] = xgf["dx1b"][idx1]
xgf["dx2b"] = xgf["dx2b"][idx2]
xgf["dx3b"] = xgf["dx3b"][idx3]
xgf["x1i"] = xgf["x1i"][ix1i]
xgf["x2i"] = xgf["x2i"][ix2i]
xgf["x3i"] = xgf["x3i"][ix3i]
xgf["dx1h"] = xgf["dx1h"][i1]
xgf["dx2h"] = xgf["dx2h"][i2]
xgf["dx3h"] = xgf["dx3h"][i3]
xgf["h1x1i"] = xgf["h1x1i"][ix1i, i2, i3]
xgf["h2x1i"] = xgf["h2x1i"][ix1i, i2, i3]
xgf["h3x1i"] = xgf["h3x1i"][ix1i, i2, i3]
xgf["h1x2i"] = xgf["h1x2i"][i1, ix2i, i3]
xgf["h2x2i"] = xgf["h2x2i"][i1, ix2i, i3]
xgf["h3x2i"] = xgf["h3x2i"][i1, ix2i, i3]
xgf["h1x3i"] = xgf["h1x3i"][i1, i2, ix3i]
xgf["h2x3i"] = xgf["h2x3i"][i1, i2, ix3i]
xgf["h3x3i"] = xgf["h3x3i"][i1, i2, ix3i]
xgf["gx1"] = xgf["gx1"][i1, i2, i3]
xgf["gx2"] = xgf["gx2"][i1, i2, i3]
xgf["gx3"] = xgf["gx3"][i1, i2, i3]
xgf["glat"] = xgf["glat"][i1, i2, i3]
xgf["glon"] = xgf["glon"][i1, i2, i3]
xgf["alt"] = xgf["alt"][i1, i2, i3]
xgf["Bmag"] = xgf["Bmag"][i1, i2, i3]
xgf["I"] = xgf["I"][i2, i3]
xgf["nullpts"] = xgf["nullpts"][i1, i2, i3]
xgf["e1"] = xgf["e1"][i1, i2, i3, :]
xgf["e2"] = xgf["e2"][i1, i2, i3, :]
xgf["e3"] = xgf["e3"][i1, i2, i3, :]
xgf["er"] = xgf["er"][i1, i2, i3, :]
xgf["etheta"] = xgf["etheta"][i1, i2, i3, :]
xgf["ephi"] = xgf["ephi"][i1, i2, i3, :]
xgf["r"] = xgf["r"][i1, i2, i3]
xgf["theta"] = xgf["theta"][i1, i2, i3]
xgf["phi"] = xgf["phi"][i1, i2, i3]
xgf["x"] = xgf["x"][i1, i2, i3]
xgf["y"] = xgf["y"][i1, i2, i3]
xgf["z"] = xgf["z"][i1, i2, i3]
return xgf
def geomag2geog(thetat: np.ndarray, phit: np.ndarray) -> T.Tuple[np.ndarray, np.ndarray]:
""" geomagnetic to geographic """
# TODO: is OK to be hardcoded?
thetan = math.radians(11)
phin = math.radians(289)
# enforce phit = [0,2pi]
i = phit > 2 * pi
phitcorrected = phit
phitcorrected[i] = phit[i] - 2 * pi
i = phit < 0
phitcorrected[i] = phit[i] + 2 * pi
# thetag2p=acos(cos(thetat).*cos(thetan)-sin(thetat).*sin(thetan).*cos(phit));
thetag2p = np.arccos(np.cos(thetat) * np.cos(thetan) - np.sin(thetat) * np.sin(thetan) * np.cos(phitcorrected))
beta = np.arccos((np.cos(thetat) - np.cos(thetag2p) * np.cos(thetan)) / (np.sin(thetag2p) * np.sin(thetan)))
phig2 = np.zeros_like(phitcorrected)
i = phitcorrected > pi
phig2[i] = phin - beta[i]
i = phitcorrected <= pi
phig2[i] = phin + beta[i]
i = phig2 < 0
phig2[i] = phig2[i] + 2 * pi
i = phig2 >= 2 * pi
phig2[i] = phig2[i] - 2 * pi
thetag2 = pi / 2 - thetag2p
lat = np.degrees(thetag2)
lon = np.degrees(phig2)
return lat, lon
def geog2geomag(lat: np.ndarray, lon: np.ndarray) -> T.Tuple[np.ndarray, np.ndarray]:
""" geographic to geomagnetic """
# TODO: is this arbitrary or hardcoded OK?
thetan = math.radians(11)
phin = math.radians(289)
# enforce [0,360] longitude
lon = lon % 360
thetagp = pi / 2 - np.radians(lat)
phig = np.radians(lon)
thetat = np.arccos(np.cos(thetagp) * np.cos(thetan) + np.sin(thetagp) * np.sin(thetan) * np.cos(phig - phin))
argtmp = (np.cos(thetagp) - np.cos(thetat) * np.cos(thetan)) / (np.sin(thetat) * np.sin(thetan))
alpha = np.arccos(max(min(argtmp, 1), -1))
phit = np.empty_like(lat)
i = ((phin > phig) & ((phin - phig) > pi)) | ((phin < phig) & ((phig - phin) < pi))
phit[i] = pi - alpha[i]
phit[~i] = alpha[~i] + pi
return thetat, phit
def grid1d(dist: float, L: int) -> np.ndarray:
"""
generate 1D grid
Parameters
----------
dist: float
one-way distance from origin (meters)
L: int
number of cells
Returns
-------
x: np.ndarray
1D vector grid
"""
xmin = -dist / 2
xmax = dist / 2
if L == 1: # degenerate dimension
# add 2 ghost cells on each side
x = np.linspace(xmin, xmax, L + 4)
else:
# exclude the ghost cells when setting extents
x = np.linspace(xmin, xmax, L)
d0 = x[1] - x[0]
d1 = x[-1] - x[-2]
# now tack on ghost cells so they are outside user-specified region
x = np.insert(x, 0, [x[0] - 2 * d0, x[0] - d0])
x = np.append(x, [x[-1] + d1, x[-1] + 2 * d1])
return x
def altitude_grid(alt_min: float, alt_max: float, incl_deg: float, d: T.Tuple[float, float, float, float]) -> np.ndarray:
if alt_min < 0 or alt_max < 0:
raise ValueError("grid values must be positive")
if alt_max <= alt_min:
raise ValueError("grid_max must be greater than grid_min")
alt = [alt_min]
while alt[-1] < alt_max:
# dalt=10+9.5*tanh((alt(i-1)-500)/150)
dalt = d[0] + d[1] * math.tanh((alt[-1] - d[2]) / d[3])
alt.append(alt[-1] + dalt)
alt = np.asarray(alt)
if alt.size < 10:
raise ValueError("grid too small")
# %% tilt for magnetic inclination
z = alt / math.sin(math.radians(incl_deg))
# %% add two ghost cells each to top and bottom
dz1 = z[1] - z[0]
dzn = z[-1] - z[-2]
z = np.insert(z, 0, [z[0] - 2 * dz1, z[0] - dz1])
z = np.append(z, [z[-1] + dzn, z[-1] + 2 * dzn])
return z