Passed
Push — master ( a924a4...15102c )
by Stefan
01:16
created

eppaurora.models.ssusiq2023.ssusiq2023()   C

Complexity

Conditions 8

Size

Total Lines 110
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 47
nop 6
dl 0
loc 110
rs 6.8678
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 auroral ionization rates
10
11
Implements the empirical model for auroral ionization,
12
derived from SSUSI UV observations [1]_.
13
14
.. [1] Bender et al., in prep., 2023
15
"""
16
from logging import warning as warn
17
from os import path
18
from pkg_resources import resource_filename
19
20
import numpy as np
21
import xarray as xr
22
23
__all__ = [
24
	"ssusiq2023",
25
]
26
27
COEFF_FILE = "SSUSI_IRgrid_coeffs_f17f18.nc"
28
COEFF_PATH = resource_filename(__name__, path.join("data", COEFF_FILE))
29
30
31
def ssusiq2023(gmlat, mlt, alt, sw_coeffs, coeff_ds=None, return_var=False):
32
	u"""
33
	Parameters
34
	----------
35
	gmlat: float
36
		Geomagnetic latitude in [degrees].
37
	mlt: float
38
		Magnetic local time in [hours].
39
	alt: float
40
		Altitude in [km]
41
	sw_coeffs: array_like or `xarray.DataArray`
42
		The space weather index values to use (for the requested time(s)),
43
		should be of shape (N, M) with N = number of proxies, currently 5:
44
		[Kp, PC, Ap, log(f10.7_81ctr_obs), log(v_plasma)].
45
		The `xarray.DataArray` should have a dimension named "proxy" with
46
		matching coordinates:
47
		["Kp", "PC", "Ap", "log_f107_81ctr_obs", "log_v_plasma"]
48
		All the other dimensions will be broadcasted.
49
	coeff_ds: `xarray.Dataset`, optional (default: None)
50
		Dataset with the model coefficients, `None` uses the packaged version.
51
	return_var: bool, optional (default: False)
52
		If `True`, returns the predicted variance in addition to the values,
53
		otherwise only the mean prediction is returned.
54
55
	Returns
56
	-------
57
	q: `xarray.DataArray`
58
		log(q), where q is the ionization rate in [cm⁻³ s⁻¹]
59
		if `return_var` is False.
60
	q, var(q): tuple of `xarray.DataArray`s
61
		log(q) and var(log(q)) where q is the ionization rate in [cm⁻³ s⁻¹]
62
		if `return_var` is True.
63
	"""
64
	coeff_ds = coeff_ds or xr.open_dataset(
65
		COEFF_PATH, decode_times=False, engine="h5netcdf"
66
	)
67
	coeff_sel = coeff_ds.sel(
68
		altitude=alt, latitude=gmlat, mlt=mlt, method="nearest",
69
	)
70
71
	# Determine if `xarray` read bytes or strings to
72
	# match the correct name in the proxy names.
73
	# Default is plain strings.
74
	offset = "offset"
75
	if isinstance(coeff_ds.proxy.values[0], bytes):
76
		offset = offset.encode()
77
	have_offset = offset in coeff_ds.proxy.values
78
79
	# prepare the coefficients (array) as a `xarray.DataArray`
80
	if isinstance(sw_coeffs, xr.DataArray):
81
		if have_offset:
82
			ones = xr.ones_like(sw_coeffs.isel(proxy=0))
83
			ones = ones.assign_coords(proxy="offset")
84
			sw_coeffs = xr.concat([sw_coeffs, ones], dim="proxy")
85
	else:
86
		sw_coeffs = np.atleast_2d(sw_coeffs)
87
		if have_offset:
88
			aix = sw_coeffs.shape.index(len(coeff_ds.proxy.values) - 1)
89
			if aix != 0:
90
				warn(
91
					"Automatically changing axis. "
92
					"This is ambiguous, to remove the ambiguity, "
93
					"make sure that the different indexes (proxies) "
94
					"are ordered along the zero-th axis in multi-"
95
					"dimensional settings. I.e. each row corresponds "
96
					"to a different index, Kp, PC, Ap, etc."
97
				)
98
				sw_coeffs = sw_coeffs.swapaxes(aix, 0)
99
			sw_coeffs = np.vstack([sw_coeffs, np.ones(sw_coeffs.shape[1])])
100
		else:
101
			aix = sw_coeffs.shape.index(len(coeff_ds.proxy.values))
102
			if aix != 0:
103
				warn(
104
					"Automatically changing axis. "
105
					"This is ambiguous, to remove the ambiguity, "
106
					"make sure that the different indexes (proxies) "
107
					"are ordered along the zero-th axis in multi-"
108
					"dimensional settings. I.e. each row corresponds "
109
					"to a different index, Kp, PC, Ap, etc."
110
				)
111
				sw_coeffs = sw_coeffs.swapaxes(aix, 0)
112
		extra_dims = ["dim_{0}".format(_d) for _d in range(sw_coeffs.ndim - 1)]
113
		sw_coeffs = xr.DataArray(
114
			sw_coeffs,
115
			dims=["proxy"] + extra_dims,
116
			coords={"proxy": coeff_ds.proxy.values},
117
		)
118
119
	# Calculate model (mean) values from `beta`
120
	# fill NaNs with zero for `.dot()`
121
	coeffs = coeff_sel.beta.fillna(0.)
122
	q = coeffs.dot(sw_coeffs)
123
	q = q.rename("log_q")
124
	q.attrs = {
125
		"long_name": "natural logarithm of ionization rate",
126
		"units": "log(cm-3 s-1)",
127
	}
128
	if not return_var:
129
		return q
130
131
	# Calculate variance of the model from `beta_std`
132
	# fill NaNs with zero for `.dot()`
133
	coeffv = coeff_sel.beta_std.fillna(0.)**2
134
	q_var = coeffv.dot(sw_coeffs**2)
135
	q_var = q_var.rename("var_log_q")
136
	q_var.attrs = {
137
		"long_name": "variance of the natural logarithm of ionization rate",
138
		"units": "1",
139
	}
140
	return q, q_var
141