Source code for pyrokinetics.diagnostics.convergence
import numpy as np
from ..pyro import Pyro
[docs]
class ConvergenceTestLinear:
r"""
Contains convergence info
Need a PyroScan object to apply the rule to
"""
[docs]
def __init__(self, pyro: Pyro, tolerance_range: float = 0.8):
self.pyro = pyro
self.tolerance_range = tolerance_range
if not hasattr(self.pyro, "gk_output"):
self.pyro.load_gk_output()
self.gk_output = self.pyro.gk_output
self.get_eigenvalue_tolerances()
self.get_field_end_ratios()
self.get_grid_scale()
self.get_kperp_fields()
[docs]
def to_dict(self):
return {
"mode_frequency_tolerance": self.mode_frequency_tolerance,
"growth_rate_tolerance": self.growth_rate_tolerance,
"field_end_ratios": self.field_end_ratios,
"grid_scale": self.grid_scale,
"kperp_fields": self.kperp_fields,
"kpar_fields": self.kpar_fields,
}
[docs]
def get_eigenvalue_tolerances(self):
# Check accuracy of frequency, starting at time_range*final_time
time = self.gk_output.data["time"]
final_time = time.isel(time=-1)
mode_frequency = self.gk_output.data["mode_frequency"]
mode_frequency = mode_frequency.where(
mode_frequency["time"] > self.tolerance_range * final_time, drop=True
)
final_mode_frequency = mode_frequency.isel(time=-1)
# Calculate growth rate tolerance
growth_rate = self.gk_output.data["growth_rate"]
growth_rate = growth_rate.where(
growth_rate["time"] > self.tolerance_range * final_time, drop=True
)
final_growth_rate = growth_rate.isel(time=-1)
growth_difference = ((growth_rate - final_growth_rate) / final_growth_rate) ** 2
freq_difference = (
(mode_frequency - final_mode_frequency) / final_mode_frequency
) ** 2
# Average over the end of the simulation, starting at time_range*final_time
growth_tolerance = np.sqrt(
growth_difference.integrate("time")
/ (
growth_difference["time"].isel(time=-1)
- growth_difference["time"].isel(time=0)
)
)
freq_tolerance = np.sqrt(
freq_difference.integrate("time")
/ (
freq_difference["time"].isel(time=-1)
- freq_difference["time"].isel(time=0)
)
)
self.growth_rate_tolerance = float(growth_tolerance.data.m.flatten()[0])
self.mode_frequency_tolerance = float(freq_tolerance.data.m.flatten()[0])
[docs]
def get_field_end_ratios(self):
# Check ratio of field ends and max field to ensure proper decay
fields = self.gk_output.data["field"].data
field_end_ratios = {}
for field_name in fields:
field = abs(self.gk_output.data[field_name].isel(kx=0, ky=0, time=-1))
ratio = (field.isel(theta=0) + field.isel(theta=-1)) / (2 * max(field))
field_end_ratios[field_name] = float(ratio.data.m)
self.field_end_ratios = field_end_ratios
[docs]
def get_grid_scale(self):
# Check amount of grid via second derivative
fields = self.gk_output.data["field"].data
grid_scale_ratios = {}
for field_name in fields:
field = self.gk_output.data[field_name].isel(kx=0, ky=0, time=-1).real
turning_points = (
np.abs(np.sign(field.differentiate("theta")).diff(dim="theta")).sum(
dim="theta"
)
/ 2
)
turning_ratio = turning_points / len(field["theta"])
grid_scale_ratios[field_name] = float(turning_ratio.data)
self.grid_scale = grid_scale_ratios
[docs]
def get_kperp_fields(self):
# Check accuracy of frequency, starting at time_range*final_time
fields = self.gk_output.data["field"].data
theta = self.gk_output.data["theta"].data
jacobian = self.gk_output.data["jacobian"]
numerics = self.pyro.numerics
self.pyro.load_metric_terms(ntheta=numerics.ntheta * 4)
metric = self.pyro.metric_terms
theta_metric = metric.regulartheta
g_tt = metric.field_aligned_covariant_metric("theta", "theta")
b_dot_grad = 1 / np.sqrt(g_tt)
theta_mod = np.mod(theta, 2 * np.pi)
b_dot_grad = np.interp(theta_mod, theta_metric, b_dot_grad, period=2 * np.pi)
theta_long, k_perp_long = self.pyro.metric_terms.k_perp(
ky=1.0, theta0=numerics.theta0, nperiod=numerics.nperiod
)
k_perp = np.interp(theta, theta_long, k_perp_long)
kperp_fields = {}
kpar_fields = {}
for field_name in fields:
field = np.abs(self.gk_output.data[field_name].isel(kx=0, ky=0, time=-1))
field_sq = (
np.abs(self.gk_output.data[field_name].isel(kx=0, ky=0, time=-1)) ** 2
* jacobian
)
b_dot_grad_field = b_dot_grad * field.differentiate("theta")
k_par = b_dot_grad_field / field
kpar_fields[field_name] = float(
np.sqrt(
(
(k_par**2 * field_sq).integrate("theta")
/ field_sq.integrate("theta")
).data.m
)
)
kperp_fields[field_name] = float(
np.sqrt(
(
(k_perp**2 * field_sq).integrate("theta")
/ field_sq.integrate("theta")
).data.m
)
)
self.kperp_fields = kperp_fields
self.kpar_fields = kpar_fields