Passed
Push — master ( 6b2218...55e87d )
by Stefan
04:02
created

eppaurora.models.zhangpaxton2008.zp2008()   A

Complexity

Conditions 3

Size

Total Lines 44
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 19
nop 5
dl 0
loc 44
rs 9.45
c 0
b 0
f 0
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