1
|
|
|
# coding: utf-8 |
2
|
|
|
# Copyright (c) 2023 Stefan Bender |
3
|
|
|
# |
4
|
|
|
# This file is part of pyeppaurora. |
5
|
|
|
# pyeppaurora is free software: you can redistribute it or modify |
6
|
|
|
# it under the terms of the GNU General Public License as published |
7
|
|
|
# by the Free Software Foundation, version 2. |
8
|
|
|
# See accompanying LICENSE file or http://www.gnu.org/licenses/gpl-2.0.html. |
9
|
|
|
"""Empirical model for electron energy and flux |
10
|
|
|
|
11
|
|
|
Implements the empirical Kp-driven model for auroral electrons, |
12
|
|
|
providing mean energy and energy flux as described in [1]_. |
13
|
|
|
|
14
|
|
|
.. [1] Zhang and Paxton, JASTP, 70, 1231--1242, 2008 |
15
|
|
|
""" |
16
|
|
|
from os import path |
17
|
|
|
from pkg_resources import resource_filename |
18
|
|
|
|
19
|
|
|
import numpy as np |
20
|
|
|
|
21
|
|
|
__all__ = [ |
22
|
|
|
"epstein_coeffs", |
23
|
|
|
"epstein_eval", |
24
|
|
|
"hemispheric_power", |
25
|
|
|
"read_zp2008_coeffs", |
26
|
|
|
"zp2008", |
27
|
|
|
] |
28
|
|
|
|
29
|
|
|
COEFF_FILE = "Zhang2008.txt" |
30
|
|
|
COEFF_PATH = resource_filename(__name__, path.join("data", COEFF_FILE)) |
31
|
|
|
COEFF_NAMES = ["A", "B", "C", "D"] |
32
|
|
|
|
33
|
|
|
# Kp_model bin edges and centres as in Zhang and Paxton, 2008 |
34
|
|
|
KP_BINE = [ |
35
|
|
|
(0.0, 1.5), (1.5, 3.0), (3.0, 4.5), (4.5, 6.0), (6.0, 8.0), (8.0, 10.0) |
36
|
|
|
] |
37
|
|
|
KP_BINC = np.asarray(KP_BINE).mean(axis=1) |
38
|
|
|
|
39
|
|
|
|
40
|
|
|
def hemispheric_power(Kp): |
41
|
|
|
"""Hemispheric Power in GW from Kp |
42
|
|
|
|
43
|
|
|
Zhang and Paxton, 2008, Eqs. (1) and (2) |
44
|
|
|
|
45
|
|
|
Parameters |
46
|
|
|
----------- |
47
|
|
|
Kp: float or array_like |
48
|
|
|
Geomagnetic Kp index value(s). |
49
|
|
|
|
50
|
|
|
Returns |
51
|
|
|
------- |
52
|
|
|
HP: float or array_like |
53
|
|
|
Hemispheric power in [GW], same shape as `Kp`. |
54
|
|
|
""" |
55
|
|
|
Kp = np.asarray(Kp) |
56
|
|
|
return np.where( |
57
|
|
|
Kp <= 5.0, |
58
|
|
|
38.66 * np.exp(0.1967 * Kp) - 33.99, |
59
|
|
|
4.592 * np.exp(0.4731 * Kp) + 20.47, |
60
|
|
|
) |
61
|
|
|
|
62
|
|
|
|
63
|
|
|
def read_zp2008_coeffs(file=None, nf=6, nKp=len(KP_BINC)): |
64
|
|
|
fdata = np.genfromtxt( |
65
|
|
|
file or COEFF_PATH, |
66
|
|
|
delimiter=" ", |
67
|
|
|
dtype=None, |
68
|
|
|
names=["name"] + COEFF_NAMES, |
69
|
|
|
) |
70
|
|
|
# number of coefficients per Kp bin |
71
|
|
|
nc = 2 * nf + 1 |
72
|
|
|
# Energy fluxes are in Table 1. |
73
|
|
|
Q0tab = [fdata[n * nc:(n + 1) * nc] for n in range(nKp)] |
74
|
|
|
# Mean energies are in Table 2. |
75
|
|
|
Emtab = [fdata[n * nc:(n + 1) * nc] for n in range(nKp, 2 * nKp)] |
76
|
|
|
return Q0tab, Emtab |
77
|
|
|
|
78
|
|
|
|
79
|
|
|
def find_Kp_idx(Kp): |
80
|
|
|
# maximum avoids negative indices |
81
|
|
|
return np.maximum(0, np.searchsorted(KP_BINC, Kp, side="left") - 1) |
82
|
|
|
|
83
|
|
|
|
84
|
|
|
def epstein_coeffs(angle, table): |
85
|
|
|
r""" |
86
|
|
|
:math:`angle = MLT * 2\pi / 24` |
87
|
|
|
""" |
88
|
|
|
coeffs = np.array(table[COEFF_NAMES].tolist()) |
89
|
|
|
nf = (len(coeffs) - 1) // 2 |
90
|
|
|
if (2 * nf + 1) != len(coeffs): |
91
|
|
|
raise ValueError("Number of coefficients is inconsistent.") |
92
|
|
|
fs = np.arange(1, nf + 1) |
93
|
|
|
cos = np.cos(fs * angle) @ coeffs[1:nf + 1] |
94
|
|
|
sin = np.sin(fs * angle) @ coeffs[nf + 1:2 * nf + 1] |
95
|
|
|
return coeffs[0] + cos + sin |
96
|
|
|
|
97
|
|
|
|
98
|
|
|
def epstein_eval(x, coeffs): |
99
|
|
|
"""Epstein function evaluated at x |
100
|
|
|
|
101
|
|
|
x = 90 - |Mlat| |
102
|
|
|
coeffs = Epstein coefficients, e.g. from `epstein_coeffs()` |
103
|
|
|
""" |
104
|
|
|
a, b, c, d = coeffs |
105
|
|
|
loc = x - b |
106
|
|
|
return a * np.exp(loc / c) / (1 + np.exp(loc / d))**2 |
107
|
|
|
|
108
|
|
|
|
109
|
|
|
def zp2008(mlat, mlt, Kp, Q0table=None, Emtable=None): |
110
|
|
|
u""" |
111
|
|
|
Parameters |
112
|
|
|
---------- |
113
|
|
|
mlat: float |
114
|
|
|
(Geo)Magnetic latitude in [degrees]. |
115
|
|
|
mlt: float |
116
|
|
|
Magnetic local time in [hours]. |
117
|
|
|
Kp: float |
118
|
|
|
Geomagnetic Kp index value(s). |
119
|
|
|
Q0table: np.recarray, optional |
120
|
|
|
Fourier coefficient table for the Epstein coefficients |
121
|
|
|
for the energy flux. E.g. as returned by `read_zp2008_coeffs()`. |
122
|
|
|
Emtable: np.recarray, optional |
123
|
|
|
Fourier coefficient table for the Epstein coefficients |
124
|
|
|
for the mean energy. E.g. as returned by `read_zp2008_coeffs()`. |
125
|
|
|
|
126
|
|
|
Returns |
127
|
|
|
------- |
128
|
|
|
(Q0, Em): tuple |
129
|
|
|
Electron energy flux Q0 in [mW m⁻²] (or [erg s⁻¹ cm⁻²]), |
130
|
|
|
and electron mean energy in [keV]. |
131
|
|
|
""" |
132
|
|
|
if (Q0table is None) or (Emtable is None): |
133
|
|
|
Q0t, Emt = read_zp2008_coeffs() |
134
|
|
|
Q0table = Q0table or Q0t |
135
|
|
|
Emtable = Emtable or Emt |
136
|
|
|
|
137
|
|
|
angle = mlt * np.pi / 12.0 |
138
|
|
|
x = 90.0 - np.abs(mlat) |
139
|
|
|
ix = find_Kp_idx(Kp) |
140
|
|
|
ixs = [ix, ix + 1] |
141
|
|
|
Kps = KP_BINC[ixs] |
142
|
|
|
Q0_epst = [ |
143
|
|
|
epstein_eval(x, epstein_coeffs(angle, Q0table[ix])) |
144
|
|
|
for ix in ixs |
145
|
|
|
] |
146
|
|
|
Em_epst = [ |
147
|
|
|
epstein_eval(x, epstein_coeffs(angle, Emtable[ix])) |
148
|
|
|
for ix in ixs |
149
|
|
|
] |
150
|
|
|
Q0 = np.interp(hemispheric_power(Kp), hemispheric_power(Kps), Q0_epst) |
151
|
|
|
Em = np.interp(Kp, Kps, Em_epst) |
152
|
|
|
return Q0, Em |
153
|
|
|
|