You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
615 KiB
615 KiB
Analysis of simulated IP and temperature grouped by latitude¶
Import libraries¶
In [2]:
import datetime as dt
import numpy as np
import scipy.stats as st
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
from matplotlib import cm, colormaps, colors, transforms
Helper functions, variables and classes¶
In [3]:
month_name = ["J", "F", "M", "A", "M", "J",
"J", "A", "S", "O", "N", "D"]
month_name_3 = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
In [4]:
# area factors for different latitudes
area_factor = (
np.cos(np.arange(180) * np.pi / 180) -
np.cos(np.arange(1, 181) * np.pi / 180)
) / 2
In [5]:
def std_error(avg_val, avg_sqr, counter):
"""
Estimate the standard error from the average value
and the average value of the square.
:param avg_val: the average value
:param avg_sqr: the average square value
:param counter: the size of the sample
:return: the standard error
"""
return np.sqrt((avg_sqr - avg_val**2) / (counter - 1))
In [6]:
class LatitudeBand():
"""
Represents a latitude band object with specific
latitude range and model information
Attributes:
slice (slice): slice for the latitude range
indices (tuple): indices within the latitude range
model (str): model associated with the latitude band
color (str): color associated with the latitude band.
"""
def __init__(self, expression, model=None):
"""
Initializes a LatitudeBand object with the given latitude
expression and model.
Args:
expression (str): latitude range expr. like '0-8S', '8N-8S'
model (str, optional): 'WRF' or 'INMCM', defaults to None
"""
# split the expression into two parts
# and replace "0" with "0S" if necessary
s1, s2 = [s if s != "0" else "0S" for s in expression.split("-")]
assert all(s[-1] in "NS" and s[:-1].isdigit() for s in [s1, s2])
# calculate values for the latitude bands
# taking into account direction
N1 = int(s1[:-1]) * (-1 if s1[-1] == "S" else 1)
N2 = int(s2[:-1]) * (-1 if s2[-1] == "S" else 1)
# format latitude values for display using thin spaces
p1 = f"0°" if N1 == 0 else f"{abs(N1):d}° {'S' if N1 < 0 else 'N'}"
p2 = f"0°" if N2 == 0 else f"{abs(N2):d}° {'S' if N2 < 0 else 'N'}"
# set color for each latitude band
Z = (N1 + N2) / 2
cmap = colormaps.get_cmap('tab20')
if 0 < Z < 9:
self.color = cmap(7 / 40)
elif -9 < Z < 0:
self.color = cmap(5 / 40)
elif -18 < Z < -9:
self.color = cmap(39 / 40)
elif -90 < Z < -18:
self.color = cmap(37 / 40)
elif 9 < Z < 18:
self.color = cmap(35 / 40)
elif 18 < Z < 90:
self.color = cmap(33 / 40)
# ensure the model is valid and set degrees
# and central index based on the model
assert model in ["WRF", "INMCM"]
degrees = 1 if model == "WRF" else 1.5
central_idx = 90 if model == "WRF" else 60
# check that latitude values are divisible
# by the appropriate degrees
assert all(N % degrees == 0 for N in [N1, N2])
# convert latitude values to indices and sort them
N1, N2 = sorted([int(N // degrees + central_idx) for N in [N1, N2]])
# create a slice for the latitude range
# and generate indices within that range
self.slice = slice(N1, N2, 1)
self.indicies = tuple(np.arange(*self.slice.indices(1000000)).tolist())
# store latitude bands model and prettify title
self.model = model
self.title = f"{p1}–{p2}" # like 9° S–18° S
self.label = self.title.replace(" ", "")
def __repr__(self):
return self.label
Loading precalculated arrays¶
In [7]:
# numbers of simulated days for analysis
wrf_N_days = 4992
inm_N_days = 3650
In [8]:
# dates corresponding to the indices (0 axis) of the data arrays
# note: for WRF dates correspond to real dates
wrf_dt_indicies = np.array(
[dt.date(1980, 1, 1) + dt.timedelta(i * 3) for i in range(wrf_N_days)]
)
inm_dt_indicies = np.array(
[dt.date(2022, 1, 1) + dt.timedelta(i % 365) for i in range(inm_N_days)]
)
In [9]:
# dictionaries where processed data is saved. The dictionary keys represent the
# threshold value of CAPE - integer numbers from the list 500, 800, 1000, 1200,
# where the key 500 relates only to temperature-based modeling and is present
# only in dictionaries wrf_<...>, while keys 800, 1000, 1200 correspond to
# classical modeling using the WRF or INMCM model.
# averaged air temperature at the height of 2 meter with the shape (180, 12)
wrf_LATxMON_t2 = np.load("./data/WRF/WRF_T2_LATxMON.npy")
# summed WRF IP along longitudes and averaged over months with shape (180, 12)
wrf_LATxMON_ip = {
key: np.load(f"./data/WRF/WRF_IP_{parameters}_LATxMON.npy")
for key, parameters in zip([500, 800, 1000, 1200],
["500_T2_25", "800", "1000", "1200"])
}
# the same for INMCM
inm_LATxMON_ip = {
key: np.load(f"./data/INMCM/INMCM_IP_{parameters}_LATxMON.npy")
for key, parameters in zip([800, 1000, 1200],
["800", "1000", "1200"])
}
# dict contains arrays with dimensions (4992, 24)
# where axis 0 contains the day index (see `wrf_dt_indicies`)
wrf_hourly_total_ip = {
key: np.load(f"./data/WRF/WRF_HOURLY_TOTAL_IP_{parameters}.npy")[:wrf_N_days]
for key, parameters in zip([500, 800, 1000, 1200],
["500_T2_25", "800", "1000", "1200"])
}
Auxiliary calculations of values to mention in the text¶
In [10]:
# for averaging over months we use
# `(wrf_data_LATxMON[:, :].sum(axis=0)*days).sum()/days.sum()`
# unstead
# `wrf_data_LATxMON[:, :].sum(axis=0).mean()`
# because
# ((a1+a2+a3)/3 + (b1+b2)/2)/2 != (a1+a2+a3+b1+b2)/5
wrf_numdays = np.load("./data/WRF/WRF_NUMDAYS_MON.npy")
inm_numdays = np.load("./data/INMCM/INMCM_NUMDAYS_MON.npy")
In [11]:
# calculate the average contribution of the regions to
# the ionospheric potential (as % of total IP)
# for specific latitudinal bands and parameterizations
# select the latitudinal bands for analysis
band_names = ["9S-9N", "18S-18N", "30S-30N"]
wrf_bands = [LatitudeBand(t, model="WRF") for t in band_names]
inmcm_bands = [LatitudeBand(t, model="INMCM") for t in band_names]
print("Model | CAPE | T2,°C | Band | % ")
# iterate over the latitude bands selected above
for i, (wrf_band, inm_band) in enumerate(zip(wrf_bands, inmcm_bands)):
print("-"*41)
# we will analyze all parameterizations only in the 9N-9S band; in the
# other bands, we will consider only the parameterization with CAPE = 1000
capes = [1000]
if i == 0:
capes = [500, 800, 1000, 1200]
# iterate over different parameterizations
for cape in capes:
# calculation of the seasonal variation of contribution to IP
# from i-nth band -> array (12,)
wrf_ip_MON = wrf_LATxMON_ip[cape][wrf_band.slice].sum(axis=0)
# calculation of the contribution of the n-th band
# to the total IP in percentage -> single value
wrf_share = (wrf_ip_MON*wrf_numdays).sum() / wrf_numdays.sum() / 240e3 * 100
# note: The averaging by months takes into account the number of
# days taken from the original series for each month, as direct
# averaging may lead to a slight error
# the same for INMCM
if cape != 500:
inm_ip_MIN = inm_LATxMON_ip[cape][inm_band.slice].sum(axis=0)
inm_share = (inm_ip_MIN*inm_numdays).sum() / inm_numdays.sum() / 240e3 * 100
print(f"INMCM | {cape:4d} | - |{inm_band.label:>11} | {inm_share:>4.2f}%")
p = "25 " if cape == 500 else "- "
print(f"WRF | {cape:4d} | {p} |{wrf_band.label:>11} | {wrf_share:>4.2f}%")
In [12]:
# calculation of:
# (i) the positions of the extrema of the seasonal
# variation of the contribution to IP from
# individual latitudinal bands,
# (ii) the amplitudes of the peak-to-peak amplitudes
# of the seasonal variation <-//->,
# for different parameterizations
# select the latitudinal bands for analysis
band_names = ["90S-9S", "9S-9N", "9N-90N"]
wrf_bands = [LatitudeBand(t, model="WRF") for t in band_names]
inmcm_bands = [LatitudeBand(t, model="INMCM") for t in band_names]
print("Model | CAPE | T2,°C | Band | Min | Max | Peak-to-peak, %")
# iterate over the latitude bands selected above
for i, (wrf_band, inm_band) in enumerate(zip(wrf_bands, inmcm_bands)):
print("-"*63)
for cape in [500, 800, 1000, 1200]:
# calculation of the seasonal variation of contribution to IP
# from i-nth band -> array (12,)
seas_var = wrf_LATxMON_ip[cape][wrf_band.slice].sum(axis=0)
# calculation of the positions of the extrema
month_min = month_name_3[np.argmin(seas_var)]
month_max = month_name_3[np.argmax(seas_var)]
# calculation of the peak-to-peak amplitude of the seasonal variation
pk_pk_ampl = (seas_var.max() - seas_var.min())/seas_var.mean() * 100
# the same for INMCM
if cape != 500:
seas_var = inm_LATxMON_ip[cape][inm_band.slice].sum(axis=0)
month_min = month_name_3[np.argmin(seas_var)]
month_max = month_name_3[np.argmax(seas_var)]
pk_pk_ampl = (seas_var.max() - seas_var.min())/seas_var.mean() * 100
print(f"INMCM | {cape:4} | - |{inm_band.label:>11} | {month_min} | {month_max} | {pk_pk_ampl:>6.2f}%")
p = "25 " if cape == 500 else "- "
print(f"WRF | {cape:4} | {p:<5} |{inm_band.label:>11} | {month_min} | {month_max} | {pk_pk_ampl:>6.2f}%")
In [13]:
# calculation of the maximum values of the contribution
# to IP from latitudinal bands, calculation is needed for
# manual refinement of the figure boundaries.
# select the latitudinal bands for analysis
band_names = ["18N-90N", "9N-18N", "0-9N", "0-9S", "9S-18S", "18S-90S"]
wrf_bands = [LatitudeBand(t, model="WRF") for t in band_names]
inmcm_bands = [LatitudeBand(t, model="INMCM") for t in band_names]
print("CAPE = 1000 J/kg\n")
print("Model | Band | Max IP contrib.")
# iterate over the latitude bands selected above
for i, (wrf_band, inm_band) in enumerate(zip(wrf_bands, inmcm_bands)):
print("-"*31)
# calculation of the maximum (over months axis) values, V
wrf_max = wrf_LATxMON_ip[1000][wrf_band.slice].sum(axis=0).max()
inm_max = inm_LATxMON_ip[1000][inm_band.slice].sum(axis=0).max()
print(f"WRF | {wrf_band.label:>9} | {wrf_max:>9.2f} V")
print(f"INMCM | {inm_band.label:>9} | {inm_max:>9.2f} V")
In [14]:
# calculation of the maximum and minimum values of the temperature at the
# 2-meter level in each latitude band. The temperature is averaged over
# spatial cells within each band. Calculation is needed for manual
# refinement of the figure boundaries.
# select the latitudinal bands for analysis
band_names = ["18N-30N", "9N-18N", "0-9N", "0-9S", "9S-18S", "18S-30S"]
wrf_bands = [LatitudeBand(t, model="WRF") for t in band_names]
print("Model | Band | Min T2 | Max T2")
print("-" * 37)
# iterate over the latitude bands selected above
for wrf_band in wrf_bands:
# averaging is performed with consideration of the area factor
tmp_t2 = (
wrf_LATxMON_t2[wrf_band.slice] * area_factor[wrf_band.slice, np.newaxis]
).sum(axis=0) / area_factor[wrf_band.slice, np.newaxis].sum()
print(f"WRF | {wrf_band.label:>9} | {tmp_t2.min():>3.2f}°C | {tmp_t2.max():>3.2f}°C")
Figure 2.4¶
In [15]:
# select the latitudinal bands for analysis
band_names = ["0-9N", "0-9S", "9S-18S", "18S-90S", "9N-18N", "18N-90N"]
wrf_bands = [LatitudeBand(t, model="WRF") for t in band_names]
inmcm_bands = [LatitudeBand(t, model="INMCM") for t in band_names]
bar_data = np.zeros((6, len(band_names), 12))
# (axes, strips, months)
for j, cape in enumerate([800, 1000, 1200]):
ax_idx = j * 2
for i, band in enumerate(wrf_bands):
bar_data[ax_idx, i] = wrf_LATxMON_ip[cape][band.slice].sum(axis=0)
ax_idx = j * 2 + 1
for i, band in enumerate(inmcm_bands):
bar_data[ax_idx, i] = inm_LATxMON_ip[cape][band.slice].sum(axis=0)
In [16]:
fig = plt.figure(figsize=(10, 14), constrained_layout=False)
ax = [None for _ in range(6)]
for n in range(6):
ax[n] = fig.add_subplot(4, 4, (2*n + 1, 2*n + 2))
low = [0e3] * 6
high = [300e3] * 6
step = [50e3] * 6
coeff = [1e3] * 6
caption = ['WRF, 1980–2020, $\\varepsilon_0 = 0.8$ kJ/kg',
'INMCM, 10 years, $\\varepsilon_0 = 0.8$ kJ/kg',
'WRF, 1980–2020, $\\varepsilon_0 = 1$ kJ/kg',
'INMCM, 10 years, $\\varepsilon_0 = 1$ kJ/kg',
'WRF, 1980–2020, $\\varepsilon_0 = 1.2$ kJ/kg',
'INMCM, 10 years, $\\varepsilon_0 = 1.2$ kJ/kg']
lab = ['18° S–90° S',
'9° S–18° S',
'0°–9° S',
'0°–9° N',
'9° N–18° N',
'18° N–90° N']
# thin spaces (' ') are used between ‘°’ signs and letters
for n in range(6):
for axis in ['top', 'bottom', 'left', 'right']:
ax[n].spines[axis].set_linewidth(0.5)
ax[n].tick_params(length=6, width=0.5, axis='y')
ax[n].tick_params(length=0, width=0.5, axis='x')
ax[n].grid(color='0.', linewidth=0.5, axis='y')
ax[n].set_xlim((-0.5, 11.5))
ax[n].set_xticks(np.arange(12))
ax[n].set_xticklabels(month_name, fontsize='large', va='top')
ax[n].set_ylim((low[n], high[n]))
ax[n].set_yticks(np.arange(low[n], high[n] + step[n] / 2, step[n]))
ax[n].set_yticklabels((np.arange(low[n], high[n] + step[n] / 2,
step[n]) / coeff[n]).astype(int),
fontsize='large')
ax[n].set_ylabel('Monthly mean\nionospheric potential, kV',
fontsize='large')
ax[n].set_title(caption[n], fontsize='large')
fig.align_ylabels([ax[0], ax[2], ax[4]])
fig.align_ylabels([ax[1], ax[3], ax[5]])
for n in range(6):
bottom_data = np.zeros(12)
for k in range(len(band_names)):
data = bar_data[n, k]
ax[n].bar(
np.arange(12),
data,
0.8,
color=wrf_bands[k].color,
bottom=bottom_data,
label=wrf_bands[k].title,
)
bottom_data += data
for n in range(6):
ax[n].text(-0.28, 1.05, chr(ord('a') + 3 * (n % 2) + n // 2),
fontsize='x-large',
fontweight='semibold', ha='left', va='bottom',
transform=ax[n].transAxes)
fig.subplots_adjust(hspace=0.3, wspace=1.)
leg_width_rel = (ax[5].get_position().x1 - ax[4].get_position().x0) \
/ (ax[4].get_position().x1 - ax[4].get_position().x0)
leg = ax[4].legend(bbox_to_anchor=(0., -1./3, leg_width_rel, 1./6), ncols=6,
loc='lower center', borderaxespad=0.,
mode='expand', fontsize='large',
framealpha=1, edgecolor='0.',
handlelength=1., handletextpad=0.5)
leg.get_frame().set_linewidth(0.5)
fig.savefig('figures_two_parts/ip_decomposed.eps', bbox_inches='tight')
Figure 2.2¶
In [17]:
# select the latitudinal bands for analysis
band_names = ["18N-90N", "9N-18N", "0-9N", "0-9S", "9S-18S", "18S-90S"]
wrf_bands = [LatitudeBand(t, model="WRF") for t in band_names]
inm_bands = [LatitudeBand(t, model="INMCM") for t in band_names]
wrf_t2_bands = [LatitudeBand(t, model="WRF") for t in ["18N-30N"]+band_names[1:-1]+["18S-30S"]]
In [18]:
wrf_slice = LatitudeBand("30S-30N", model="WRF").slice
inm_slice = LatitudeBand("30S-30N", model="INMCM").slice
WRF_IP_LxM = wrf_LATxMON_ip[1000][wrf_slice] / 360
INM_IP_LxM = inm_LATxMON_ip[1000][inm_slice] / 180
T2_LxM = wrf_LATxMON_t2[wrf_slice]
In [19]:
strip_map = np.full((180, 360, 4), 1.)
for band in wrf_t2_bands:
for i in band.indicies:
for j in range(360):
strip_map[i, j] = band.color
In [20]:
wrf_ip_sum_over_bands = np.zeros((6, 12))
inm_ip_sum_over_bands = np.zeros((6, 12))
wrf_t2_avg_over_bands = np.zeros((6, 12))
for j in range(6):
wrf_ip_sum_over_bands[j] = wrf_LATxMON_ip[1000][wrf_bands[j].slice].sum(axis=0)
inm_ip_sum_over_bands[j] = inm_LATxMON_ip[1000][inm_bands[j].slice].sum(axis=0)
wrf_t2_avg_over_bands[j] = np.sum(
wrf_LATxMON_t2[wrf_t2_bands[j].slice]
* area_factor[wrf_t2_bands[j].slice, np.newaxis],
axis=0,
) / np.sum(area_factor[wrf_t2_bands[j].slice, np.newaxis])
In [21]:
fig_width = 10
fig_height = 12
width = 3.
wsp = 1.
tot_width = 3 * width + 4 * wsp
widthM = 0.6 * tot_width
height = np.array([2., 2., 3., 3., 2., 1.])
hsp = 0.8
hsep1 = 2.
hsep2 = 3.5
assert len(height) == 6
heightD = 4.
heightM = widthM/tot_width * 60/360 * fig_width/fig_height
tot_height = (sum(height) + heightD + 8 * hsp + hsep1 + hsep2) / (1 - heightM)
heightM *= tot_height
sizeT = np.array([6., 3., 2., 2., 3., 4.])
hspT = hsp
assert len(sizeT) == 6
heightT = sizeT * (sum(height) + 5 * hsp - 5 * hspT) / sum(sizeT)
minT = np.array([16., 24., 24., 24., 22., 18.])
fig = plt.figure(figsize=(fig_width, fig_height), constrained_layout=False)
ax = [None for _ in range(12)]
axT = [None for _ in range(6)]
for n in range(6):
ax[2*n] = fig.add_axes(
(
(width + 2 * wsp) / tot_width,
(sum(height[n+1:]) + heightM + (6 - n) * hsp + hsep1) / tot_height,
width / tot_width,
height[n] / tot_height
)
)
ax[2*n + 1] = fig.add_axes(
(
(2 * width + 3 * wsp) / tot_width,
(sum(height[n+1:]) + heightM + (6 - n) * hsp + hsep1) / tot_height,
width / tot_width,
height[n] / tot_height
)
)
axT[n] = fig.add_axes(
(
wsp / tot_width,
(sum(heightT[n+1:]) + (5 - n) * hspT
+ heightM + hsp + hsep1) / tot_height,
width / tot_width,
heightT[n] / tot_height
)
)
axD = [None for _ in range(3)]
for n in range(3):
axD[n] = fig.add_axes(
(
(n * width + (n + 1) * wsp) / tot_width,
(sum(height) + heightM + 7 * hsp + hsep1 + hsep2) / tot_height,
width / tot_width,
heightD / tot_height
)
)
axM = fig.add_axes(
(
(1 - widthM / tot_width) / 2,
hsp / tot_height,
widthM / tot_width,
heightM / tot_height
),
projection=ccrs.PlateCarree(central_longitude=0)
)
axM.coastlines('110m', linewidth=0.5)
caption = ['WRF, 1980–2020, $\\varepsilon_0 = 1$ kJ/kg',
'INMCM, 10 years, $\\varepsilon_0 = 1$ kJ/kg']
captionT = 'WRF, 1980–2020'
captionD = ['WRF, 1980–2020',
'WRF, 1980–2020, $\\varepsilon_0 = 1$ kJ/kg',
'INMCM, 10 years, $\\varepsilon_0 = 1$ kJ/kg']
for n in range(12):
for axis in ['top', 'bottom', 'left', 'right']:
ax[n].spines[axis].set_linewidth(0.5)
ax[n].tick_params(length=6, width=0.5, axis='y')
ax[n].tick_params(length=0, width=0.5, axis='x')
ax[n].grid(color='0.', linewidth=0.5, axis='y')
ax[n].set_xlim((-0.5, 11.5))
ax[n].set_xticks(np.arange(12))
ax[n].set_xticklabels(month_name, fontsize='large', va='top')
low = 0
high = 50e3 * height[n // 2]
step = 50e3
coeff = 1e3
ax[n].set_ylim((low, high))
ax[n].set_yticks(np.arange(low, high + step / 2, step))
ax[n].set_yticklabels((np.arange(low, high + step / 1,
step) / coeff).astype(int),
fontsize='large')
if n < 2:
ax[n].set_title(caption[n], fontsize='large')
for n in range(2):
lbl = ax[n].set_ylabel('Monthly mean contributions '
'to the ionospheric potential, kV',
fontsize='large', labelpad=0.)
lbl.set_position((0., 1 - (sum(height) + 5 * hsp) / 2 / height[0]))
for n in range(6):
for axis in ['top', 'bottom', 'left', 'right']:
axT[n].spines[axis].set_linewidth(0.5)
axT[n].tick_params(length=6, width=0.5, axis='y')
axT[n].tick_params(length=0, width=0.5, axis='x')
axT[n].grid(color='0.', linewidth=0.5, axis='y')
axT[n].set_xlim((-0.5, 11.5))
axT[n].set_xticks(np.arange(12))
axT[n].set_xticklabels(month_name, fontsize='large', va='top')
low = minT[n]
step = 2
high = minT[n] + step * sizeT[n]
coeff = 1
axT[n].set_ylim((low, high))
axT[n].set_yticks(np.arange(low, high + step / 2, step))
axT[n].set_yticklabels((np.arange(low, high + step / 2,
step) / coeff).astype(int),
fontsize='large')
if n == 0:
axT[n].set_title(captionT, fontsize='large')
lbl = axT[0].set_ylabel('Monthly mean surface air temperature, °C',
fontsize='large')
lbl.set_position((0., 1 - (sum(heightT) + 5 * hspT) / 2 / heightT[0]))
for n in range(3):
for axis in ['top', 'bottom', 'left', 'right']:
axD[n].spines[axis].set_linewidth(0.5)
axD[n].tick_params(length=6, width=0.5)
axD[n].tick_params(length=0, width=0.5, which='minor')
axD[n].grid(color='0.', linewidth=0.5, axis='y')
axD[n].set_xlim((0, 12))
axD[n].set_xticks(np.arange(0, 12.5, 1))
axD[n].set_xticks(np.arange(0.5, 12, 1), minor=True)
axD[n].set_xticklabels([])
axD[n].set_xticklabels(month_name, fontsize='large', va='top',
minor=True)
axD[n].set_ylim((-30, 30))
axD[n].set_yticks(np.arange(-30, 31, 10))
axD[n].set_yticklabels(['0' if v == 0 else f'{-v}S' if v < 0 else f'{v}N'
for v in range(-30, 31, 10)],
fontsize='large')
axD[n].set_title(captionD[n], fontsize='large')
cax = [None for _ in range(3)]
for n in range(3):
cax[n] = fig.add_axes(
[axD[n].get_position().x0,
axD[n].get_position().y0 - 0.035,
axD[n].get_position().x1 - axD[n].get_position().x0,
0.01])
min_val = [10, 0, 0]
max_val = [30, 50, 120]
step_val = [5, 10, 20]
cbar_cmap = ['inferno', 'plasma_r', 'plasma_r']
cbar_norm = [None] * 3
for n in range(3):
cbar_norm[n] = colors.Normalize(vmin=min_val[n], vmax=max_val[n])
cbar = fig.colorbar(cm.ScalarMappable(norm=cbar_norm[n],
cmap=cbar_cmap[n]),
cax=cax[n], orientation='horizontal')
cbar.outline.set_linewidth(0.5)
cbar.ax.tick_params(length=6, width=0.5)
cbar.set_ticks(np.arange(min_val[n], max_val[n] + 1, step_val[n]))
cbar.ax.set_xticklabels(['0' if v == 0 else f'−{-v}' if v < 0 else f'{v}'
for v in range(min_val[n], max_val[n] + 1,
step_val[n])],
fontsize='large')
if n == 0:
cbar.set_label('Mean surface air temperature, °C',
fontsize='large')
else:
cbar.set_label('Mean grid cell contributions\n'
'to the ionospheric potential, V',
fontsize='large')
for axis in ['geo']:
axM.spines[axis].set_linewidth(0.5)
axM.tick_params(top=True, right=True, which='both')
axM.tick_params(length=6, width=0.5)
axM.tick_params(length=3, width=0.5, which='minor')
axM.grid(False)
axM.set_xticks(np.arange(-180, 181, 30))
axM.set_xticks(np.arange(-180, 181, 10), minor=True)
axM.set_xticklabels(['180°', '', '120° W', '', '60° W', '', '0°',
'', '60° E', '', '120° E', '', '180°'],
fontsize='large', va='top')
# thin spaces (' ') are used between ‘°’ signs and letters
axM.set_yticks(np.arange(-90, 91, 30))
axM.set_yticks(np.arange(-90, 91, 10), minor=True)
axM.set_yticklabels(['', '', '30° S', '0°', '30° N', '', ''],
fontsize='large')
# thin spaces (' ') are used between ‘°’ signs and letters
axM.set_title('Partition of the Earth’s surface into regions',
fontsize='large', pad=10)
for j in range(6):
ax[2*j].bar(range(12), wrf_ip_sum_over_bands[j],
0.8, color=wrf_bands[j].color)
ax[2*j+1].bar(range(12), inm_ip_sum_over_bands[j],
0.8, color=inm_bands[j].color)
axT[j].bar(range(12), wrf_t2_avg_over_bands[j],
0.8, color=wrf_t2_bands[j].color)
offset = lambda p: transforms.ScaledTranslation(0, p/72, fig.dpi_scale_trans)
for AX in [ax[2*j], ax[2*j+1]]:
AX.text(0.01 if j<2 else 0.99, 1,
wrf_bands[j].title,
fontsize='large',
ha='left' if j<2 else 'right',
va='top',
transform=AX.transAxes + offset(-2))
axT[j].text(0.01 if j<2 else 0.99, 1,
wrf_t2_bands[j].title,
fontsize='large',
ha='left' if j<2 else 'right',
va='top',
transform=axT[j].transAxes + offset(-2))
axD[0].imshow(T2_LxM,
cmap=cbar_cmap[0], norm=cbar_norm[0],
extent=[0, 12, 30, -30], aspect='auto', interpolation='none',
rasterized=True)
axD[1].imshow(WRF_IP_LxM,
cmap=cbar_cmap[1], norm=cbar_norm[1],
extent=[0, 12, 30, -30], aspect='auto', interpolation='none',
rasterized=True)
axD[2].imshow(INM_IP_LxM,
cmap=cbar_cmap[2], norm=cbar_norm[2],
extent=[0, 12, 30, -30], aspect='auto', interpolation='none',
rasterized=True)
axM.imshow(np.flip(strip_map[60:-60], axis=0), extent=[-180, 180, -30, 30])
for n in range(3):
axD[n].text(-0.28 * width/width, 1 + 0.1 * heightD/heightD,
chr(ord('a') + 2 * n), fontsize='x-large',
fontweight='semibold', ha='left', va='bottom',
transform=axD[n].transAxes)
axT[0].text(-0.28 * width/width, 1 + 0.1 * heightD/heightT[0],
'b', fontsize='x-large',
fontweight='semibold', ha='left', va='bottom',
transform=axT[0].transAxes)
for n in range(2):
ax[n].text(-0.28 * width/width, 1 + 0.1 * heightD/height[0],
chr(ord('d') + 2 * n), fontsize='x-large',
fontweight='semibold', ha='left', va='bottom',
transform=ax[n].transAxes)
axM.text(-0.28 * width/widthM, 1 + 0.1 * heightD/heightM,
'g', fontsize='x-large',
fontweight='semibold', ha='left', va='bottom',
transform=axM.transAxes)
fig.savefig('figures_two_parts/ip_separate.eps', bbox_inches='tight')
Figure 2.6¶
In [44]:
vostok_diurnal = np.load('./data/Vostok/vostok_diurnal_2006_2020.npy')
vostok_seasonal = np.load('./data/Vostok/vostok_2006_2020_results.npz')['mean'][0]
vostok_seasonal_counter = np.load('./data/Vostok/vostok_2006_2020_results.npz')['counter'][0]
In [45]:
wrf_diurnal = wrf_hourly_total_ip[1000].mean(axis=0) / 240e3
wrf_new_diurnal = wrf_hourly_total_ip[500].mean(axis=0) / 240e3
vostok_diurnal /= vostok_diurnal.mean()
In [46]:
# ring
vostok_diurnal = [*vostok_diurnal, vostok_diurnal[0]]
wrf_new_diurnal = [*wrf_new_diurnal, wrf_new_diurnal[0]]
wrf_diurnal = [*wrf_diurnal, wrf_diurnal[0]]
In [47]:
# select the latitudinal bands for analysis
band_names = ["0-9N", "0-9S", "9S-18S", "18S-90S", "9N-18N", "18N-90N"]
wrf_bands = [LatitudeBand(t, model="WRF") for t in band_names]
inmcm_bands = [LatitudeBand(t, model="INMCM") for t in band_names]
bar_data = np.zeros((2, len(band_names), 12))
# (axes, bands, months)
for i, band in enumerate(wrf_bands):
bar_data[0, i] = wrf_LATxMON_ip[1000][band.slice].sum(axis=0)
bar_data[1, i] = wrf_LATxMON_ip[500][band.slice].sum(axis=0)
In [68]:
# helper function for reuse code of indicate peak-to-peak amplitude
def external_arrows(ax=None, miny=0, maxy=1, arrow_pos=12, text_pos=12.2, ampl=2):
ax.annotate(
"",
xy=(arrow_pos, miny),
xycoords="data",
xytext=(arrow_pos, maxy),
textcoords="data",
annotation_clip=False,
arrowprops=dict(
arrowstyle="-",
patchA=None,
patchB=None,
shrinkA=0.0,
shrinkB=0.0,
connectionstyle="arc3,rad=0.",
fc="black",
linewidth=0.5,
),
)
ax.annotate(
"",
xy=(arrow_pos, maxy + 30e3),
xycoords="data",
xytext=(arrow_pos, maxy),
textcoords="data",
annotation_clip=False,
arrowprops=dict(
arrowstyle="<|-,head_length=0.8,head_width=0.3",
patchA=None,
patchB=None,
shrinkA=0.0,
shrinkB=0.0,
connectionstyle="arc3,rad=0.",
fc="black",
linewidth=0.5,
),
)
ax.annotate(
"",
xy=(arrow_pos, miny - 30e3),
xycoords="data",
xytext=(arrow_pos, miny),
textcoords="data",
annotation_clip=False,
arrowprops=dict(
arrowstyle="<|-,head_length=0.8,head_width=0.3",
patchA=None,
patchB=None,
shrinkA=0.0,
shrinkB=0.0,
connectionstyle="arc3,rad=0.",
fc="black",
linewidth=0.5,
),
)
ax.text(
text_pos,
(miny + maxy) / 2,
f"{ampl * 100:.0f}%",
fontsize="large",
ha="left",
va="center",
rotation=270,
)
In [124]:
fig = plt.figure(figsize=(10, 15), constrained_layout=False)
ax = [None for _ in range(6)]
for n in range(6):
ax[n] = fig.add_subplot(4, 4, (2*n + 1, 2*n + 2))
low = [0.8, 0] + [0.8, 0e3] * 2
high = [1.2, 180] + [1.2, 300e3] * 2
step = [0.1, 30] + [0.1, 50e3] * 2
coeff = [1] * 2 + [1, 1e3] * 2
caption = ['Vostok station, 2006–2020',
'Vostok station, 2006–2020',
'WRF, 1980–2020, $\\varepsilon_0 = 1$ kJ/kg',
'WRF, 1980–2020, $\\varepsilon_0 = 1$ kJ/kg',
'WRF, 1980–2020, $\\varepsilon_0 = 0.5$ kJ/kg, $T_0 = 25$ °C',
'WRF, 1980–2020, $\\varepsilon_0 = 0.5$ kJ/kg, $T_0 = 25$ °C']
lab = ['18° S–90° S',
'9° S–18° S',
'0°–9° S',
'0°–9° N',
'9° N–18° N',
'18° N–90° N']
# thin spaces (' ') are used between ‘°’ signs and letters
for n in range(0, 5, 2):
for axis in ['top', 'bottom', 'left', 'right']:
ax[n].spines[axis].set_linewidth(0.5)
ax[n].tick_params(length=6, width=0.5)
ax[n].grid(color='0.', linewidth=0.5)
ax[n].set_xlim((0, 24))
ax[n].set_xticks(np.arange(0, 25, 2))
ax[n].set_xticklabels(np.arange(0, 25, 2), fontsize='large')
ax[n].set_xlabel('UTC, hours', fontsize='large')
ax[n].set_ylim((low[n], high[n]))
ax[n].set_yticks(np.arange(low[n], high[n] + step[n] / 2, step[n]))
ax[n].set_yticklabels([f'{x:.1f}' for x in
np.arange(low[n], high[n] + step[n] / 2,
step[n]) / coeff[n]],
fontsize='large')
if n == 0:
ax[n].set_ylabel('Fair-weather pot. gradient\n'
'as a fraction of the mean',
fontsize='large')
else:
ax[n].set_ylabel('Ionospheric potential\nas a fraction of the mean',
fontsize='large')
ax[n].set_title(caption[n], fontsize='large')
for n in range(1, 6, 2):
for axis in ['top', 'bottom', 'left', 'right']:
ax[n].spines[axis].set_linewidth(0.5)
ax[n].tick_params(length=6, width=0.5, axis='y')
ax[n].tick_params(length=0, width=0.5, axis='x')
ax[n].grid(color='0.', linewidth=0.5, axis='y')
ax[n].set_xlim((-0.5, 11.5))
ax[n].set_xticks(np.arange(12))
ax[n].set_xticklabels(month_name, fontsize='large', va='top')
ax[n].set_ylim((low[n], high[n]))
ax[n].set_yticks(np.arange(low[n], high[n] + step[n] / 2, step[n]))
ax[n].set_yticklabels((np.arange(low[n], high[n] + step[n] / 2,
step[n]) / coeff[n]).astype(int),
fontsize='large')
if n == 1:
ax[n].set_ylabel('Monthly mean fair-weather\npotential gradient, V/m',
fontsize='large')
else:
ax[n].set_ylabel('Monthly mean\nionospheric potential, kV',
fontsize='large')
ax[n].set_title(caption[n], fontsize='large')
fig.align_ylabels([ax[0], ax[2], ax[4]])
fig.align_ylabels([ax[1], ax[3], ax[5]])
# diurnal variations
ax[0].plot(np.arange(0, 25),
vostok_diurnal,
linewidth=1.5, color='purple', clip_on=False,
zorder=4)
ax[2].plot(np.arange(0, 25),
wrf_diurnal,
linewidth=1.5, color='purple', clip_on=False,
zorder=4)
ax[4].plot(np.arange(0, 25),
wrf_new_diurnal,
linewidth=1.5, color='purple', clip_on=False,
zorder=4)
# arrows and amplitudes
for ax_number, data_diurnal in zip([0, 2, 4], [vostok_diurnal, wrf_diurnal, wrf_new_diurnal]):
ax[ax_number].annotate('', xy=(25, np.min(data_diurnal)), xycoords='data',
xytext=(25, np.max(data_diurnal)), textcoords='data',
annotation_clip=False,
arrowprops=dict(
arrowstyle='<|-|>,head_length=0.8,head_width=0.3',
patchA=None, patchB=None, shrinkA=0., shrinkB=0.,
connectionstyle='arc3,rad=0.', fc='black',
linewidth=0.5
))
ampl = np.max(data_diurnal) - np.min(data_diurnal)
ax[ax_number].text(25.2, (np.min(data_diurnal) +
np.max(data_diurnal)) / 2,
f'{ampl * 100:.0f}%',
fontsize='large', ha='left', va='center', rotation=270)
# seasonal variation of PG
ax[1].bar(np.arange(12), vostok_seasonal, 0.8, color='orangered')
# arrows and percent label for seasonal variation of PG
vostok_ampl = (vostok_seasonal.max() - vostok_seasonal.min())
vostok_ampl /= (vostok_seasonal*vostok_seasonal_counter).sum()/vostok_seasonal_counter.sum()
ax[1].annotate('', xy=(12, np.min(vostok_seasonal)), xycoords='data',
xytext=(12, np.max(vostok_seasonal)), textcoords='data',
annotation_clip=False,
arrowprops=dict(
arrowstyle='<|-|>,head_length=0.8,head_width=0.3',
patchA=None, patchB=None, shrinkA=0., shrinkB=0.,
connectionstyle='arc3,rad=0.', fc='black',
linewidth=0.5
))
ax[1].text(12.2, (np.min(vostok_seasonal) +
np.max(vostok_seasonal)) / 2,
f'{vostok_ampl * 100:.0f}%',
fontsize='large', ha='left', va='center', rotation=270)
# seasonal variations of simulated IP (separated by latitude bands)
for n in range(2):
bottom_data = np.zeros(12)
for k in range(len(band_names)):
data = bar_data[n, k]
ax[2*n+3].bar(
np.arange(12),
data,
0.8,
color=wrf_bands[k].color,
bottom=bottom_data,
label=wrf_bands[k].title,
)
bottom_data += data
# arrows and percent label for seasonal variations of simulated IP
variation_1000 = wrf_LATxMON_ip[1000].sum(axis=0)
variation_500 = wrf_LATxMON_ip[500].sum(axis=0)
for ax_number, seasonal_variation in zip([3, 5], [variation_1000, variation_500]):
seasonal_ampl = (seasonal_variation.max() - seasonal_variation.min())
seasonal_ampl /= (seasonal_variation*wrf_numdays).sum()/wrf_numdays.sum()
external_arrows(ax=ax[ax_number],
miny=seasonal_variation.min(),
maxy=seasonal_variation.max(),
ampl=seasonal_ampl)
for n in range(0, 5, 2):
ax[n].text(-0.27, 1.05, chr(ord('a') + n),
fontsize='x-large',
fontweight='semibold', ha='left', va='bottom',
transform=ax[n].transAxes)
for n in range(1, 6, 2):
ax[n].text(-0.28, 1.05, chr(ord('a') + n),
fontsize='x-large',
fontweight='semibold', ha='left', va='bottom',
transform=ax[n].transAxes)
fig.subplots_adjust(hspace=0.45, wspace=1.6)
leg_width_rel = (ax[5].get_position().x1 - ax[4].get_position().x0) \
/ (ax[5].get_position().x1 - ax[5].get_position().x0)
leg = ax[5].legend(bbox_to_anchor=(1. - leg_width_rel, -1./2,
leg_width_rel, 1./6), ncols=6,
loc='lower center', borderaxespad=0.,
mode='expand', fontsize='large',
framealpha=1, edgecolor='0.',
handlelength=1., handletextpad=0.5)
leg.get_frame().set_linewidth(0.5)
fig.savefig('figures_two_parts/new_parameterisation.eps', bbox_inches='tight')