time_invariant.py
¶
Visualization of time-invariant solutions.
- Requires Python packages:
- class gme.plot.time_invariant.TimeInvariant(dpi: int = 100, font_size: int = 11)¶
Visualization of time-invariant solutions.
Extends
gme.plot.base.Graphing
.- profile_aniso(gmes: gme.ode.time_invariant.TimeInvariantSolution, gmeq: gme.core.equations.Equations, sub: Dict, name: str, fig_size: Optional[Tuple[float, float]] = None, dpi: Optional[int] = None, n_points: int = 51, xf_stop: float = 0.995, sf: Optional[Tuple[float, float]] = None, n_arrows: int = 26, y_limits: Optional[Tuple[float, float]] = None, v_scale: float = 0.4, v_exponent: float = 1.0, do_pub_label: bool = False, pub_label: str = '', eta_label_xy: Optional[Tuple[float, float]] = None) → None¶
Plot anisotropy-annotate topo profile.
Plot time-invariant profile annotated with ray vectors, normal-slowness covector herringbones, and colorized for anisotropy (defined as the difference \((\alpha-\beta)\)).
- Parameters
gmes – instance of time invariant solution class defined in
gme.ode.time_invariant
gmeq – GME model equations class instance defined in
gme.core.equations
sub – dictionary of model parameter values to be used for equation substitutions
name – name of figure (key in figure dictionary)
fig_size – optional figure width and height in inches
dpi – optional rasterization resolution
n_points – optional sample rate along each curve
xf_stop – optional x-fraction at which to stop plotting
sf – optional scale factor(s) for vertical axes (bottom and top)
n_arrows – optional number of \(\mathbf{v}\) vector arrows and \(\mathbf{\widetilde{p}}\) covector herringbones to plot
y_limits – optional [z_min, z_max] vertical plot range
v_scale – optional velocity arrow prefactor
v_exponent – optional velocity arrow exaggeration power function exponent
eta_label_xy – optional where to plot \(\eta\) annotation text
do_pub_label – optionally do ‘publication’ annotation?
pub_label – optional ‘publication’ annotation text
- profile_beta(gmes: gme.ode.time_invariant.TimeInvariantSolution, gmeq: gme.core.equations.Equations, sub: Dict, name: str, fig_size: Optional[Tuple[float, float]] = None, dpi: Optional[int] = None, n_points: int = 26, xf_stop: float = 1, legend_loc: str = 'upper left', eta_label_xy: Tuple[float, float] = (0.6, 0.8), pub_label_xy: Tuple[float, float] = (0.88, 0.7), do_etaxi_label: bool = True, do_pub_label: bool = False, pub_label: str = '') → None¶
Plot covector tilt along a profile.
For a time-invariant (steady-state) topographic profile, plot the surface-normal covector angle \(\beta\) from vertical, aka the surface tilt angle from horizontal, as a function of dimensionless horizontal distance \(x/L_{\mathrm{c}}\).
This angle is named and calculated in three ways: (1) \(\beta_p\): directly from the series of \(\mathbf{\widetilde{p}}\) values generated by ray ODE integration, since \(\tan(\beta_p)=-p_x/p_z\); (2) \(\beta_{ts}\): by differentiating the computed time-invariant topographic profile (itself constructed from the terminations of the ensemble of progressively truncated, identical rays); (3) \(\beta_{vt}\): from the velocity triangle \(\tan(\beta_{vt}) = \dfrac{v^z+\xi^{\perp}}{v^x}\): generated by the geometric constraint of balancing the ray velocities with surface-normal velocities.
- Parameters
gmes – instance of time invariant solution class defined in
gme.ode.time_invariant
gmeq – GME model equations class instance defined in
gme.core.equations
sub – dictionary of model parameter values to be used for equation substitutions
name – name of figure (key in figure dictionary)
fig_size – optional figure width and height in inches
dpi – optional rasterization resolution
n_points – optional sample rate along each curve
xf_stop – optional x-fraction at which to stop plotting
legend_loc – optional position of legend
eta_label_xy – optional where to plot \(\eta\) annotation text
var_label_xy – optional where to plot ‘var’ annotation text
do_eta_xi – optionally do \(\eta\), \(\xi\) annotation?
pub_label – optional ‘publication’ annotation text
- profile_beta_error(gmes: gme.ode.time_invariant.TimeInvariantSolution, gmeq: gme.core.equations.Equations, sub: Dict, name: str, fig_size: Optional[Tuple[float, float]] = None, dpi: Optional[int] = None, n_points: int = 101, eta_label_xy: Tuple[float, float] = (0.5, 0.8), xf_stop: float = 0.995) → None¶
Plot error in covector tilt calculation along profile.
For a time-invariant (steady-state) topographic profile, plot the error in the estimated surface-normal covector angle \(\beta\) as a function of dimensionless horizontal distance \(x/L_{\mathrm{c}}\).
This error, expressed as a percentage, is defined as one of the following normalized differences: (1) \(100(\beta_{ts}-\beta_{p})/\beta_{p}\), or (2) \(100(\beta_{vt}-\beta_{p})/\beta_{p}\). The error in \(\beta_{vt}\) can be non-trivial for \(x/L_{\mathrm{c}} \rightarrow 0\) and \(\eta < 1\) if the number of rays used to construct the topographic profile is insufficient.
- Parameters
gmes – instance of time invariant solution class defined in
gme.ode.time_invariant
gmeq – GME model equations class instance defined in
gme.core.equations
sub –
- dictionary of model parameter values to be used for
equation substitutions
name – name of figure (key in figure dictionary)
fig_size – optional figure width and height in inches
dpi – optional rasterization resolution
n_points – optional sample rate along each curve
eta_label_xy – optional where to plot \(\eta\) annotation text
xf_stop – optional x-fraction at which to stop plotting
- profile_ensemble(gmes: gme.ode.time_invariant.TimeInvariantSolution, pr_choices: Dict, name: str, fig_size: Optional[Tuple[float, float]] = None, dpi: Optional[int] = None, aspect: Optional[float] = None, do_direct: bool = False) → None¶
Plot ‘steady-state’ profile bundle.
Plot set of time-invariant profiles for a selection of values of \(\mathsf{Ci}\) and \(\eta\).
- Parameters
gmes – instance of time invariant solution class defined in
time_invariant
pr_choices – dictionary of model parameter values to be used for equation substitutions
name – name of figure (key in figure dictionary)
fig_size – optional figure width and height in inches
dpi – optional rasterization resolution
- profile_xi(gmes: gme.ode.time_invariant.TimeInvariantSolution, gmeq: gme.core.equations.Equations, sub: Dict, name: str, fig_size: Optional[Tuple[float, float]] = None, dpi: Optional[int] = None, xf_stop: float = 1, n_points: int = 201, pub_label_xy: Tuple[float, float] = (0.5, 0.2), eta_label_xy: Tuple[float, float] = (0.5, 0.5), var_label_xy: Tuple[float, float] = (0.8, 0.5), do_etaxi_label=True, do_pub_label=False, pub_label: str = '(a)', xi_norm: Optional[float] = None) → None¶
Plot erosion ‘rate’ along profile.
Plot surface-normal erosion speed \(\xi^{\perp}\) along a time-invariant profile.
- Parameters
gmes – instance of time invariant solution class defined in
gme.ode.time_invariant
gmeq – GME model equations class instance defined in
gme.core.equations
sub – dictionary of model parameter values to be used for equation substitutions
name – name of figure (key in figure dictionary)
fig_size – optional figure width and height in inches
dpi – optional rasterization resolution
xf_stop – optional x-fraction at which to stop plotting
n_points – optional sample rate along each curve
pub_label_xy – optional where to plot ‘publication’ annotation text
eta_label_xy – optional where to plot \(\eta\) annotation text
var_label_xy – optional where to plot ‘var’ annotation text
do_eta_xi – optionally do \(\eta\), \(\xi\) annotation?
pub_label – optional ‘publication’ annotation text
xi_norm – optional normalization factor \(\xi^{\rightarrow_{0}}\) for \(\xi\)
- profile_xihorizontal(gmes: gme.ode.time_invariant.TimeInvariantSolution, gmeq: gme.core.equations.Equations, sub: Dict, name: str, fig_size: Optional[Tuple[float, float]] = None, dpi: Optional[int] = None, xf_stop: float = 1, n_points: int = 201, pub_label_xy: Tuple[float, float] = (0.55, 0.81), eta_label_xy: Tuple[float, float] = (0.5, 0.2), var_label_xy: Tuple[float, float] = (0.85, 0.81), do_etaxi_label: bool = True, do_pub_label: bool = False, pub_label: str = '(d)', xi_norm: Optional[float] = None) → None¶
Plot horizontal erosion ‘rate’ along profile.
Plot horizontal erosion speed \(\xi^{\rightarrow}\) along a time-invariant profile.
- Parameters
gmes – instance of time invariant solution class defined in
gme.ode.time_invariant
gmeq – GME model equations class instance defined in
gme.core.equations
sub – dictionary of model parameter values to be used for equation substitutions
name – name of figure (key in figure dictionary)
fig_size – optional figure width and height in inches
dpi – optional rasterization resolution
xf_stop – optional x-fraction at which to stop plotting
n_points – optional sample rate along each curve
pub_label_xy – optional where to plot ‘publication’ annotation text
eta_label_xy – optional where to plot \(\eta\) annotation text
var_label_xy – optional where to plot ‘var’ annotation text
do_eta_xi – optionally do \(\eta\), \(\xi\) annotation?
pub_label – optional ‘publication’ annotation text
xi_norm – optional normalization factor \(\xi^{\rightarrow_{0}}\) for \(\xi\)
- profile_xivertical(gmes: gme.ode.time_invariant.TimeInvariantSolution, gmeq: gme.core.equations.Equations, sub: Dict, name: str, fig_size: Optional[Tuple[float, float]] = None, dpi: Optional[int] = None, xf_stop: float = 1, n_points: int = 201, y_limits: Optional[Tuple[float, float]] = None, pub_label_xy: Tuple[float, float] = (0.5, 0.81), eta_label_xy: Tuple[float, float] = (0.5, 0.2), var_label_xy: Tuple[float, float] = (0.85, 0.81), do_etaxi_label: bool = True, do_pub_label: bool = False, pub_label: str = '(e)', xi_norm: Optional[float] = None) → None¶
Plot vertical erosion ‘rate’ along profile.
Plot vertical erosion speed \(\xi^{\downarrow}\) along a time-invariant profile.
- Parameters
gmes – instance of time invariant solution class defined in
gme.ode.time_invariant
gmeq – GME model equations class instance defined in
gme.core.equations
sub – dictionary of model parameter values to be used for equation substitutions
name – name of figure (key in figure dictionary)
fig_size – optional figure width and height in inches
dpi – optional rasterization resolution
xf_stop – optional x-fraction at which to stop plotting
n_points – optional sample rate along each curve
pub_label_xy – optional where to plot ‘publication’ annotation text
eta_label_xy – optional where to plot \(\eta\) annotation text
var_label_xy – optional where to plot ‘var’ annotation text
do_eta_xi – optionally do \(\eta\), \(\xi\) annotation?
pub_label – optional ‘publication’ annotation text
xi_norm – optional: normalization factor \(\xi^{\rightarrow_{0}}\) for \(\xi\)
Code¶
"""
Visualization of time-invariant solutions.
---------------------------------------------------------------------
Requires Python packages:
- :mod:`NumPy <numpy>`
- :mod:`SymPy <sympy>`
- :mod:`MatPlotLib <matplotlib>`
- `GME`_
.. _GMPLib: https://github.com/geomorphysics/GMPLib
.. _GME: https://github.com/geomorphysics/GME
.. _Matrix:
https://docs.sympy.org/latest/modules/matrices/immutablematrices.html
---------------------------------------------------------------------
"""
# pylint: disable = too-few-public-methods, no-self-use
# Library
import warnings
from typing import Dict, Tuple, Optional
# NumPy
import numpy as np
# SymPy
from sympy import N, Rational, deg
# MatPlotLib
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib import cm
from mpl_toolkits.axes_grid1 import make_axes_locatable
# GME
from gme.core.symbols import Ci, mu
from gme.core.equations import Equations
from gme.ode.time_invariant import TimeInvariantSolution
from gme.plot.base import Graphing
warnings.filterwarnings("ignore")
__all__ = ["TimeInvariant"]
class TimeInvariant(Graphing):
"""
Visualization of time-invariant solutions.
Extends :class:`gme.plot.base.Graphing`.
"""
# Definitions
fc: str
def profile_aniso(
self,
gmes: TimeInvariantSolution,
gmeq: Equations,
sub: Dict,
name: str,
fig_size: Optional[Tuple[float, float]] = None,
dpi: Optional[int] = None,
n_points: int = 51,
xf_stop: float = 0.995,
sf: Optional[Tuple[float, float]] = None,
n_arrows: int = 26,
y_limits: Optional[Tuple[float, float]] = None,
v_scale: float = 0.4,
v_exponent: float = 1.0,
do_pub_label: bool = False,
pub_label: str = "",
eta_label_xy: Optional[Tuple[float, float]] = None,
) -> None:
r"""
Plot anisotropy-annotate topo profile.
Plot time-invariant profile annotated with ray vectors,
normal-slowness covector herringbones,
and colorized for anisotropy (defined as the difference
:math:`(\alpha-\beta)`).
Args:
gmes:
instance of time invariant solution class
defined in :mod:`gme.ode.time_invariant`
gmeq:
GME model equations class instance defined in
:mod:`gme.core.equations`
sub:
dictionary of model parameter values to be used for
equation substitutions
name:
name of figure (key in figure dictionary)
fig_size:
optional figure width and height in inches
dpi:
optional rasterization resolution
n_points:
optional sample rate along each curve
xf_stop:
optional x-fraction at which to stop plotting
sf:
optional scale factor(s) for vertical axes (bottom and top)
n_arrows:
optional number of :math:`\mathbf{v}` vector arrows and
:math:`\mathbf{\widetilde{p}}` covector herringbones to plot
y_limits:
optional [z_min, z_max] vertical plot range
v_scale:
optional velocity arrow prefactor
v_exponent:
optional velocity arrow exaggeration power function exponent
eta_label_xy:
optional where to plot :math:`\eta` annotation text
do_pub_label:
optionally do 'publication' annotation?
pub_label:
optional 'publication' annotation text
"""
_ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
eta_label_xy = (0.5, 0.8) if eta_label_xy is None else eta_label_xy
x_array = np.linspace(0, 1 * xf_stop, n_points)
h_array = gmes.h_interp(x_array)
profile_lw = 1
# Solid line = topo profile from direct integration of gradient array
plt.plot(
gmes.h_x_array,
(gmes.h_z_array - gmes.h_z_array[0]),
"k",
lw=profile_lw,
label=rf"$h(x)\quad\eta={gmeq.eta_}$",
)
plt.plot(
x_array,
h_array,
"o",
mec="k",
mfc="gray",
ms=3,
fillstyle="full",
markeredgewidth=0.5,
)
plt.xlabel(r"Distance, $x/L_{\mathrm{c}}$ [-]", fontsize=16)
plt.ylabel(r"Elevation, $z/L_{\mathrm{c}}$ [-]", fontsize=16)
plt.grid(True, ls=":")
axes = plt.gca()
ylim = axes.get_ylim()
cmap_choice = "viridis_r"
# cmap_choice = 'plasma_r'
# cmap_choice = 'magma_r'
# cmap_choice = 'inferno_r'
# cmap_choice = 'cividis_r'
shape = "full"
rx_array = gmes.rx_array
x_min, x_max = rx_array[0], rx_array[-1]
x_array = np.linspace(x_min, x_max * xf_stop, n_arrows)
h_array = gmes.h_interp(x_array)
beta_array = gmes.beta_p_interp(x_array)
alpha_array = gmes.alpha_interp(x_array)
rdot_array = gmes.rdot_interp(x_array)
p_array = gmes.p_interp(x_array)
aniso_array = np.rad2deg(alpha_array - beta_array)
aniso_span = np.array((10 * np.floor(min(aniso_array) / 10), 90))
color_map = cm.get_cmap(cmap_choice)
# stretch_aniso_array = (((aniso_array-min(aniso_array))
# /(max(aniso_array)-min(aniso_array)))**30) \
# *(max(aniso_array)-min(aniso_array))+min(aniso_array)
aniso_colors = [
color_map(
(aniso_ - aniso_span[0]) / (aniso_span[1] - aniso_span[0])
)
for aniso_ in aniso_array
]
for rp_idx in (0, 1):
# Fix the range of p values to be represented: smaller values
# will be clipped
p_range_max = 20
p_max = max(p_array)
p_min = min(p_array)
p_min = (
p_max / p_range_max if p_max / p_min > p_range_max else p_min
)
p_range = p_max - p_min
np_scale = 15
# Fix the range of rdot values to be represented:
# smaller values will be clipped
rdot_range_max = 20
rdot_max, rdot_min = max(rdot_array), min(rdot_array)
rdot_min = (
rdot_max / rdot_range_max
if rdot_max / rdot_min > rdot_range_max
else rdot_min
)
rdot_range = rdot_max - rdot_min
nrdot_scale = 9
for (x_, z_, aniso_color, alpha_, beta_, rdot_, p_) in zip(
x_array,
h_array,
aniso_colors,
alpha_array,
beta_array,
rdot_array,
p_array,
):
if rp_idx == 0:
# Ray vector
hw = 0.01
hl = 0.02
oh = 0.1
len_arrow = (
v_scale
* ((rdot_ - rdot_min) ** v_exponent / rdot_range)
if rdot_ >= rdot_min
else 0
) + v_scale / nrdot_scale
dx, dz = (
len_arrow * np.cos(alpha_ - np.pi / 2),
len_arrow * np.sin(alpha_ - np.pi / 2),
)
plt.arrow(
x_,
z_,
dx,
dz,
head_width=hw,
head_length=hl,
lw=1,
shape=shape,
overhang=oh,
length_includes_head=True,
ec=aniso_color,
fc=aniso_color,
)
else:
# Slowness covector
len_stick = 0.08
hw = 0.015
hl = 0.0
oh = 0.0
np_ = (
1 + int(0.5 + np_scale * ((p_ - p_min) / p_range))
if p_ >= p_min
else 1
)
dx, dz = len_stick * np.sin(beta_), -len_stick * np.cos(
beta_
)
plt.arrow(
x_,
z_,
-dx,
-dz,
head_width=hw,
head_length=-0.01,
lw=1,
shape=shape,
overhang=1,
length_includes_head=True,
head_starts_at_zero=True,
ec=aniso_color,
)
for i_head in list(range(1, np_)):
len_head = i_head / (np_)
plt.arrow(
x_,
z_,
-dx * len_head,
-dz * len_head,
head_width=hw,
head_length=hl,
lw=1,
shape=shape,
overhang=oh,
length_includes_head=True,
ec=aniso_color,
)
colorbar_im = axes.imshow(
aniso_span[0]
+ (aniso_span[1] - aniso_span[0]) * np.arange(9).reshape(3, 3) / 8,
cmap=color_map,
extent=(0, 0, 0, 0),
)
plt.text(
*eta_label_xy,
rf"$\eta={gmeq.eta_}$"
+ r"$\quad\mathsf{Ci}=$"
+ rf"${round(float(deg(Ci.subs(sub))))}\degree$",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=14,
color="k",
)
axes.set_aspect(1)
plt.xlim(-0.05, 1.08)
if sf is None:
if gmeq.eta_ <= Rational(1, 2):
sf = (3, 6.0)
elif gmeq.eta_ < Rational(3, 4):
sf = (1.5, 4.3)
elif gmeq.eta_ >= Rational(3, 2):
sfy = float(N((9 - 4.3) * (gmeq.eta_ - 0.5) + 4.3)) * 0.3
sf = (sfy, sfy)
else:
sfy = float(N((9 - 1.0) * (gmeq.eta_ - 0.5) + 1.0)) * 0.5
sf = (sfy, sfy)
plt.xlim(-0.03, 1.05)
if y_limits is None:
plt.ylim(ylim[0] * sf[0], ylim[1] - ylim[0] * sf[1])
else:
plt.ylim(*y_limits)
# Hand-made legend
class _ArrivalTime:
"""Private class."""
# pass
class _HandlerArrivalTime:
def legend_artist(
self, legend, orig_handle, fontsize, handlebox
) -> mpatches.Arrow:
"""How to plot this legend element."""
del legend, orig_handle, fontsize
# x0, y0 = handlebox.xdescent, handlebox.ydescent
# width, height = handlebox.width, handlebox.height
patch = mpatches.Arrow(
4, 4, 20, 0, width=0, lw=profile_lw, ec="k", fc="k"
)
handlebox.add_artist(patch)
return patch
class _RayPoint:
"""Private class."""
# pass
class _HandlerRayPoint:
"""Private class."""
def __init__(self, fc: str = "gray"):
"""Initialize: constructor method."""
super().__init__()
self.fc = fc
def legend_artist(
self, legend, orig_handle, fontsize, handlebox
) -> mpatches.Circle:
"""How to plot this legend element."""
del legend, orig_handle, fontsize
# x0, y0 = handlebox.xdescent, handlebox.ydescent
# width, height = handlebox.width, handlebox.height
patch = mpatches.Circle((15, 4), radius=2.5, ec="k", fc=self.fc)
handlebox.add_artist(patch)
return patch
class _RayArrow:
"""Private class."""
# pass
class _HandlerRayArrow:
"""Private class."""
def legend_artist(
self, legend, orig_handle, fontsize, handlebox
) -> mpatches.FancyArrow:
"""How to plot this legend element."""
del legend, orig_handle, fontsize
# x0, y0 = handlebox.xdescent, handlebox.ydescent
width, height = handlebox.width, handlebox.height
color_ = aniso_colors[3]
patch = mpatches.FancyArrow(
0,
0.5 * height,
width,
0,
length_includes_head=True,
head_width=0.75 * height,
overhang=0.1,
fc=color_,
ec=color_,
)
handlebox.add_artist(patch)
return patch
class _NormalStick:
"""Private class."""
# pass
class _HandlerNormalStick:
"""Private class."""
def legend_artist(
self, legend, orig_handle, fontsize, handlebox
) -> mpatches.FancyArrow:
# legend, orig_handle, fontsize,
"""How to plot this legend element."""
del legend, orig_handle, fontsize
# x0, y0 = handlebox.xdescent, handlebox.ydescent
width, height = handlebox.width, handlebox.height
color_ = aniso_colors[5]
patch = mpatches.FancyArrow(
0.8 * height,
0.5 * height,
0.5,
0,
length_includes_head=True,
head_width=0.65 * height,
head_length=0.7 * height,
overhang=1,
fc=color_,
ec=color_,
)
handlebox.add_artist(patch)
for w in [width * 0.25, width * 0.6, width * 0.8]:
patch = mpatches.FancyArrow(
0.8 * height + 0.5,
0.5 * height,
w,
0,
length_includes_head=True,
head_width=height if w < width * 0.8 else 0,
head_length=0,
overhang=0,
lw=1.5,
fc=color_,
ec=color_,
)
handlebox.add_artist(patch)
return patch
legend_fns1 = [_ArrivalTime(), _RayPoint(), _RayArrow(), _NormalStick()]
legend_labels1 = [
r"$T(\mathbf{r})$",
r"$\mathbf{r}$",
r"$\mathbf{v}$",
r"$\mathbf{\widetilde{p}}$",
]
legend_handlers1 = {
_ArrivalTime: _HandlerArrivalTime(),
_RayPoint: _HandlerRayPoint(),
_RayArrow: _HandlerRayArrow(),
_NormalStick: _HandlerNormalStick(),
}
legend1 = plt.legend(
legend_fns1,
legend_labels1,
handler_map=legend_handlers1,
loc="upper left",
)
axes.add_artist(legend1)
divider = make_axes_locatable(axes)
colorbar_axes = divider.append_axes("right", size="5%", pad=0.2)
colorbar_axes.set_aspect(0.2)
colorbar = plt.colorbar(colorbar_im, cax=colorbar_axes)
colorbar.set_label(
r"Anisotropy $\psi = \alpha-\beta+90$ [${\degree}$]",
rotation=270,
labelpad=20,
)
if do_pub_label:
plt.text(
0.93,
0.15,
pub_label,
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=16,
color="k",
)
def profile_beta(
self,
gmes: TimeInvariantSolution,
gmeq: Equations,
sub: Dict,
name: str,
fig_size: Optional[Tuple[float, float]] = None,
dpi: Optional[int] = None,
n_points: int = 26,
xf_stop: float = 1,
legend_loc: str = "upper left",
eta_label_xy: Tuple[float, float] = (0.6, 0.8),
pub_label_xy: Tuple[float, float] = (0.88, 0.7),
do_etaxi_label: bool = True,
do_pub_label: bool = False,
pub_label: str = "",
) -> None:
r"""
Plot covector tilt along a profile.
For a time-invariant (steady-state) topographic profile,
plot the surface-normal covector angle :math:`\beta` from vertical,
aka the surface tilt angle from horizontal, as a function of
dimensionless horizontal distance :math:`x/L_{\mathrm{c}}`.
This angle is named and calculated in three ways:
(1) :math:`\beta_p`: directly from the series of
:math:`\mathbf{\widetilde{p}}` values
generated by ray ODE integration, since :math:`\tan(\beta_p)=-p_x/p_z`;
(2) :math:`\beta_{ts}`: by differentiating the computed time-invariant
topographic profile (itself constructed from the terminations of the
ensemble of progressively truncated, identical rays);
(3) :math:`\beta_{vt}`: from the velocity triangle
:math:`\tan(\beta_{vt}) = \dfrac{v^z+\xi^{\perp}}{v^x}`: generated
by the geometric constraint of balancing the ray velocities with
surface-normal velocities.
Args:
gmes:
instance of time invariant solution class
defined in :mod:`gme.ode.time_invariant`
gmeq:
GME model equations class instance defined in
:mod:`gme.core.equations`
sub:
dictionary of model parameter values to be used for
equation substitutions
name:
name of figure (key in figure dictionary)
fig_size:
optional figure width and height in inches
dpi:
optional rasterization resolution
n_points:
optional sample rate along each curve
xf_stop:
optional x-fraction at which to stop plotting
legend_loc:
optional position of legend
eta_label_xy:
optional where to plot :math:`\eta` annotation text
var_label_xy:
optional where to plot 'var' annotation text
do_eta_xi:
optionally do :math:`\eta`, :math:`\xi` annotation?
pub_label:
optional 'publication' annotation text
"""
_ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
# eta_label_xy = [0.6,0.8] if eta_label_xy is None else eta_label_xy
# pub_label_xy = [0.88,0.7] if pub_label_xy is None else pub_label_xy
rx_array = gmes.rx_array
x_min, x_max = rx_array[0], rx_array[-1]
x_array = np.linspace(x_min, x_max * xf_stop, n_points)
x_dbl_array = np.linspace(x_min, x_max * xf_stop, n_points * 2 - 1)
plt.plot(
x_dbl_array,
np.rad2deg(gmes.beta_vt_interp(x_dbl_array)),
"bs",
ls="-",
ms=3,
label=r"$\beta_{vt}$ from $(v^z+\xi^{\!\downarrow\!})/v^x$",
)
plt.plot(
x_array,
np.rad2deg(gmes.beta_ts_interp(x_array)),
"go",
ls="-",
ms=4,
label=r"$\beta_{ts}$ from topo gradient",
)
plt.plot(
x_dbl_array,
np.rad2deg(gmes.beta_p_interp(x_dbl_array)),
"r",
ls="-",
ms=3,
label=r"$\beta_p$ from $p_x/p_z$",
)
#
# plt.plot(gmes.rx_array,np.rad2deg(gmes.beta_array),
# 'ks', ls='-', ms=3, label=r'$\beta_p$ from $p_x/p_z$')
# plt.plot(gmes.rx_array,np.rad2deg(gmes.beta_ts_array),
# 'bo', ls='-', ms=4,
# label=r'$\beta_{ts}$ from topo gradient')
# plt.plot(gmes.rx_array,np.rad2deg(gmes.beta_vt_array),
# 'r', ls='-', label=r'$\beta_{vt}$
# from $(v^z+\xi^{\!\downarrow\!})/v^x$')
plt.xlabel(r"Distance, $x/L_{\mathrm{c}}$ [-]", fontsize=13)
plt.ylabel(r"Angle $\beta$ [$\degree$]", fontsize=13)
plt.grid(True, ls=":")
plt.ylim(
1e-9,
)
axes = plt.gca()
plt.legend(loc=legend_loc, fontsize=11, framealpha=0.95)
if do_etaxi_label:
plt.text(
*eta_label_xy,
rf"$\eta={gmeq.eta_}$"
+ r"$\quad\mathsf{Ci}=$"
+ rf"${round(float(deg(Ci.subs(sub))))}\degree$",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=14,
color="k",
)
if do_pub_label:
plt.text(
*pub_label_xy,
pub_label,
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=16,
color="k",
)
def profile_beta_error(
self,
gmes: TimeInvariantSolution,
gmeq: Equations,
sub: Dict,
name: str,
fig_size: Optional[Tuple[float, float]] = None,
dpi: Optional[int] = None,
n_points: int = 101,
eta_label_xy: Tuple[float, float] = (0.5, 0.8),
xf_stop: float = 0.995,
) -> None:
r"""
Plot error in covector tilt calculation along profile.
For a time-invariant (steady-state) topographic profile,
plot the error in the estimated surface-normal covector
angle :math:`\beta` as a function of dimensionless horizontal
distance :math:`x/L_{\mathrm{c}}`.
This error, expressed as a percentage, is defined as
one of the following normalized differences:
(1) :math:`100(\beta_{ts}-\beta_{p})/\beta_{p}`, or
(2) :math:`100(\beta_{vt}-\beta_{p})/\beta_{p}`.
The error in :math:`\beta_{vt}` can be non-trivial for
:math:`x/L_{\mathrm{c}} \rightarrow 0` and :math:`\eta < 1`
if the number of rays used to construct the topographic profile is
insufficient.
Args:
gmes:
instance of time invariant solution class
defined in :mod:`gme.ode.time_invariant`
gmeq:
GME model equations class instance defined in
:mod:`gme.core.equations`
sub:
dictionary of model parameter values to be used for
equation substitutions
name:
name of figure (key in figure dictionary)
fig_size:
optional figure width and height in inches
dpi:
optional rasterization resolution
n_points:
optional sample rate along each curve
eta_label_xy:
optional where to plot :math:`\eta` annotation text
xf_stop:
optional x-fraction at which to stop plotting
"""
_ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
# eta_label_xy = [0.5,0.8] if eta_label_xy is None else eta_label_xy
# pub_label_xy = [0.5,0.2] if pub_label_xy is None else pub_label_xy
rx_array = gmes.rx_array
x_min, x_max = rx_array[0], rx_array[-1]
x_array = np.linspace(x_min, x_max * xf_stop, n_points)
plt.plot(
x_array,
gmes.beta_vt_error_interp(x_array),
"b",
ls="-",
label=r"$\dfrac{\beta_{vt}-\beta_{p}}{\beta_{p}}$",
)
plt.plot(
x_array,
gmes.beta_ts_error_interp(x_array),
"g",
ls="-",
label=r"$\dfrac{\beta_{ts}-\beta_{p}}{\beta_{p}}$",
)
plt.xlabel(r"Distance, $x/L_{\mathrm{c}}$ [-]", fontsize=13)
plt.ylabel(r"Error [%]", fontsize=13)
plt.grid(True, ls=":")
axes = plt.gca()
ylim = axes.get_ylim()
plt.ylim(ylim[0] * 1.0, ylim[1] * 1.3)
plt.legend(loc="upper left", fontsize=9, framealpha=0.95)
plt.text(
*eta_label_xy,
rf"$\eta={gmeq.eta_}$"
+ r"$\quad\mathsf{Ci}=$"
+ rf"${round(float(deg(Ci.subs(sub))))}\degree$",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=14,
color="k",
)
def profile_xi(
self,
gmes: TimeInvariantSolution,
gmeq: Equations,
sub: Dict,
name: str,
fig_size: Optional[Tuple[float, float]] = None,
dpi: Optional[int] = None,
xf_stop: float = 1,
n_points: int = 201,
pub_label_xy: Tuple[float, float] = (0.5, 0.2),
eta_label_xy: Tuple[float, float] = (0.5, 0.5),
var_label_xy: Tuple[float, float] = (0.8, 0.5),
do_etaxi_label=True,
do_pub_label=False,
pub_label: str = "(a)",
xi_norm: Optional[float] = None,
) -> None:
r"""
Plot erosion 'rate' along profile.
Plot surface-normal erosion speed :math:`\xi^{\perp}`
along a time-invariant profile.
Args:
gmes:
instance of time invariant solution class
defined in :mod:`gme.ode.time_invariant`
gmeq:
GME model equations class instance defined in
:mod:`gme.core.equations`
sub:
dictionary of model parameter values to be used for
equation substitutions
name:
name of figure (key in figure dictionary)
fig_size:
optional figure width and height in inches
dpi:
optional rasterization resolution
xf_stop:
optional x-fraction at which to stop plotting
n_points:
optional sample rate along each curve
pub_label_xy:
optional where to plot 'publication' annotation text
eta_label_xy:
optional where to plot :math:`\eta` annotation text
var_label_xy:
optional where to plot 'var' annotation text
do_eta_xi:
optionally do :math:`\eta`, :math:`\xi` annotation?
pub_label:
optional 'publication' annotation text
xi_norm:
optional normalization factor
:math:`\xi^{\rightarrow_{0}}` for :math:`\xi`
"""
_ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
# eta_label_xy = [0.5,0.5] if eta_label_xy is None else eta_label_xy
# pub_label_xy = [0.5,0.2] if pub_label_xy is None else pub_label_xy
# var_label_xy = [0.8,0.5] if var_label_xy is None else var_label_xy
if xi_norm is None:
xi_norm = 1
rate_label = r"$\xi^{\!\perp\!}$"
else:
xi_norm = float(N(xi_norm))
rate_label = r"$\xi^{\!\perp\!}/\xi^{\!\rightarrow_{\!\!0}}$ [-]"
rx_array = gmes.rx_array
x_min, x_max = rx_array[0], rx_array[-1]
x_array = np.linspace(x_min, x_max * xf_stop, n_points)
u_array = gmes.u_interp(x_array)
u_from_rdot_array = gmes.u_from_rdot_interp(x_array)
dashes = [1, 2.0]
if not do_pub_label:
plt.plot(
x_array,
u_from_rdot_array / xi_norm,
"g",
dashes=dashes,
lw=3,
label=r"$\xi^{\!\perp\!}(x)$ from $v$",
)
plt.plot(
x_array,
u_array / xi_norm,
"b",
ls="-",
lw=1.5,
label=r"$\xi^{\!\perp\!}(x)$ from $1/p$",
)
else:
plt.plot(
x_array,
u_from_rdot_array / xi_norm,
"g",
dashes=dashes,
lw=3,
label=r"from $v$",
)
plt.plot(
x_array,
u_array / xi_norm,
"b",
ls="-",
lw=1.5,
label=r"from $1/p$",
)
plt.xlabel(r"Distance, $x/L_{\mathrm{c}}$ [-]", fontsize=13)
plt.ylabel(r"Normal erosion rate " + rate_label, fontsize=12)
plt.grid(True, ls=":")
axes = plt.gca()
ylim = plt.ylim()
axes.set_ylim(-(ylim[1] - ylim[0]) / 20, ylim[1] * 1.05)
plt.legend(loc="lower left", fontsize=11, framealpha=0.95)
plt.text(
*eta_label_xy,
rf"$\eta={gmeq.eta_}$"
+ r"$\quad\mathsf{Ci}=$"
+ rf"${round(float(deg(Ci.subs(sub))))}\degree$"
if do_etaxi_label
else "",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=16,
color="k",
)
plt.text(
*var_label_xy,
r"$\xi^{\perp}(x)$",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=18,
color="k",
)
plt.text(
*pub_label_xy,
pub_label if do_pub_label else "",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=16,
color="k",
)
def profile_xihorizontal(
self,
gmes: TimeInvariantSolution,
gmeq: Equations,
sub: Dict,
name: str,
fig_size: Optional[Tuple[float, float]] = None,
dpi: Optional[int] = None,
xf_stop: float = 1,
n_points: int = 201,
pub_label_xy: Tuple[float, float] = (0.55, 0.81),
eta_label_xy: Tuple[float, float] = (0.5, 0.2),
var_label_xy: Tuple[float, float] = (0.85, 0.81),
do_etaxi_label: bool = True,
do_pub_label: bool = False,
pub_label: str = "(d)",
xi_norm: Optional[float] = None,
) -> None:
r"""
Plot horizontal erosion 'rate' along profile.
Plot horizontal erosion speed :math:`\xi^{\rightarrow}`
along a time-invariant profile.
Args:
gmes:
instance of time invariant solution class
defined in :mod:`gme.ode.time_invariant`
gmeq:
GME model equations class instance defined in
:mod:`gme.core.equations`
sub:
dictionary of model parameter values to be used for
equation substitutions
name:
name of figure (key in figure dictionary)
fig_size:
optional figure width and height in inches
dpi:
optional rasterization resolution
xf_stop:
optional x-fraction at which to stop plotting
n_points:
optional sample rate along each curve
pub_label_xy:
optional where to plot 'publication' annotation text
eta_label_xy:
optional where to plot :math:`\eta` annotation text
var_label_xy:
optional where to plot 'var' annotation text
do_eta_xi:
optionally do :math:`\eta`, :math:`\xi` annotation?
pub_label:
optional 'publication' annotation text
xi_norm:
optional normalization factor
:math:`\xi^{\rightarrow_{0}}` for :math:`\xi`
"""
_ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
# eta_label_xy = [0.5,0.2] if eta_label_xy is None else eta_label_xy
# pub_label_xy = [0.55,0.81] if pub_label_xy is None else pub_label_xy
# var_label_xy = [0.85,0.81] if var_label_xy is None else var_label_xy
if xi_norm is None:
xi_norm = 1
rate_label = r"$\xi^{\!\rightarrow}$"
else:
xi_norm = float(N(xi_norm))
rate_label = (
r"$\xi^{\!\rightarrow\!\!}/\xi^{\!\rightarrow_{\!\!0}}$ [-]"
)
rx_array = gmes.rx_array
x_min, x_max = rx_array[0], rx_array[-1]
x_array = np.linspace(x_min, x_max * xf_stop, n_points)
uhorizontal_p_array = gmes.uhorizontal_p_interp(x_array) / xi_norm
uhorizontal_v_array = gmes.uhorizontal_v_interp(x_array) / xi_norm
dashes = [1, 2.0]
if not do_pub_label:
plt.plot(
x_array,
uhorizontal_v_array,
"g",
dashes=dashes,
lw=3,
label=r"$\xi^{\!\rightarrow\!}(x)$ from $v$",
)
plt.plot(
x_array,
uhorizontal_p_array,
"b",
ls="-",
lw=1.5,
label=r"$\xi^{\!\rightarrow\!}(x)$ from $1/p$",
)
else:
plt.plot(
x_array,
uhorizontal_p_array,
"g",
dashes=dashes,
lw=3,
label=r"from $v$",
)
plt.plot(
x_array,
uhorizontal_v_array,
"b",
ls="-",
lw=1.5,
label=r"from $1/p$",
)
axes = plt.gca()
ylim = plt.ylim()
axes.set_ylim(-(ylim[1] - ylim[0]) / 20, ylim[1])
plt.grid(True, ls=":")
plt.xlabel(r"Distance, $x/L_{\mathrm{c}}$ [-]", fontsize=13)
plt.ylabel(r"Horiz erosion rate " + rate_label, fontsize=12)
# axes.set_ylim(ylim[0]*1.1,-0)
plt.legend(loc="lower left", fontsize=11, framealpha=0.95)
plt.text(
*eta_label_xy,
rf"$\eta={gmeq.eta_}$"
+ r"$\quad\mathsf{Ci}=$"
+ rf"${round(float(deg(Ci.subs(sub))))}\degree$"
if do_etaxi_label
else "",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=16,
color="k",
)
plt.text(
*var_label_xy,
r"$\xi^{\rightarrow}(x)$",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=18,
color="k",
)
plt.text(
*pub_label_xy,
pub_label if do_pub_label else "",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=16,
color="k",
)
def profile_xivertical(
self,
gmes: TimeInvariantSolution,
gmeq: Equations,
sub: Dict,
name: str,
fig_size: Optional[Tuple[float, float]] = None,
dpi: Optional[int] = None,
xf_stop: float = 1,
n_points: int = 201,
y_limits: Optional[Tuple[float, float]] = None,
pub_label_xy: Tuple[float, float] = (0.5, 0.81),
eta_label_xy: Tuple[float, float] = (0.5, 0.2),
var_label_xy: Tuple[float, float] = (0.85, 0.81),
do_etaxi_label: bool = True,
do_pub_label: bool = False,
pub_label: str = "(e)",
xi_norm: Optional[float] = None,
) -> None:
r"""
Plot vertical erosion 'rate' along profile.
Plot vertical erosion speed
:math:`\xi^{\downarrow}` along a time-invariant profile.
Args:
gmes:
instance of time invariant solution class
defined in :mod:`gme.ode.time_invariant`
gmeq:
GME model equations class instance defined in
:mod:`gme.core.equations`
sub:
dictionary of model parameter values to be used for
equation substitutions
name:
name of figure (key in figure dictionary)
fig_size:
optional figure width and height in inches
dpi:
optional rasterization resolution
xf_stop:
optional x-fraction at which to stop plotting
n_points:
optional sample rate along each curve
pub_label_xy:
optional where to plot 'publication' annotation text
eta_label_xy:
optional where to plot :math:`\eta` annotation text
var_label_xy:
optional where to plot 'var' annotation text
do_eta_xi:
optionally do :math:`\eta`, :math:`\xi` annotation?
pub_label:
optional 'publication' annotation text
xi_norm: optional: normalization factor
:math:`\xi^{\rightarrow_{0}}` for :math:`\xi`
"""
_ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
# eta_label_xy = [0.5,0.2] if eta_label_xy is None else eta_label_xy
# pub_label_xy = [0.5,0.81] if pub_label_xy is None else pub_label_xy
# var_label_xy = [0.85,0.81] if var_label_xy is None else var_label_xy
if xi_norm is None:
xi_norm = 1
rate_label = r"$\xi^{\!\downarrow}$"
else:
xi_norm = float(N(xi_norm))
rate_label = (
r"$\xi^{\!\downarrow}\!\!/\xi^{\!\rightarrow_{\!\!0}}$ [-]"
)
rx_array = gmes.rx_array
x_min, x_max = rx_array[0], rx_array[-1]
x_array = np.linspace(x_min, x_max * xf_stop, n_points)
xiv_p_array = gmes.xiv_p_interp(x_array) / xi_norm
xiv_v_array = gmes.xiv_v_interp(x_array) / xi_norm
if not do_pub_label:
plt.plot(
x_array,
xiv_v_array,
"g",
ls="-",
label=r"$\xi^{\!\downarrow\!}(x)$ from $v$",
)
plt.plot(
x_array,
xiv_p_array,
"b",
ls="-",
label=r"$\xi^{\!\downarrow\!}(x)$ from $1/p$",
)
else:
plt.plot(x_array, xiv_v_array, "g", ls=":", lw=3, label=r"from $v$")
plt.plot(x_array, xiv_p_array, "b", ls="-", label=r"from $1/p$")
axes = plt.gca()
# ylim = plt.ylim()
plt.grid(True, ls=":")
plt.xlabel(r"Distance, $x/L_{\mathrm{c}}$ [-]", fontsize=13)
plt.ylabel(r"Vertical erosion rate " + rate_label, fontsize=12)
if y_limits is not None:
axes.set_ylim(*y_limits)
else:
xiv_mean = np.mean(xiv_p_array)
xiv_deviation = (
np.max(
[
xiv_mean - np.min(xiv_p_array),
np.max(xiv_p_array) - xiv_mean,
]
)
* 1.1
)
axes.set_ylim([xiv_mean - xiv_deviation, xiv_mean + xiv_deviation])
plt.legend(loc="upper left", fontsize=11, framealpha=0.95)
plt.text(
*eta_label_xy,
rf"$\eta={gmeq.eta_}$"
+ r"$\quad\mathsf{Ci}=$"
+ rf"${round(float(deg(Ci.subs(sub))))}\degree$"
if do_etaxi_label
else "",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=16,
color="k",
)
plt.text(
*var_label_xy,
r"$\xi^{\downarrow}(x)$",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=18,
color="k",
)
plt.text(
*pub_label_xy,
pub_label if do_pub_label else "",
transform=axes.transAxes,
horizontalalignment="center",
verticalalignment="center",
fontsize=16,
color="k",
)
def profile_ensemble(
self,
gmes: TimeInvariantSolution,
pr_choices: Dict,
name: str,
fig_size: Optional[Tuple[float, float]] = None,
dpi: Optional[int] = None,
aspect: Optional[float] = None,
do_direct: bool = False,
) -> None:
r"""
Plot 'steady-state' profile bundle.
Plot set of time-invariant profiles for a selection of values of
:math:`\mathsf{Ci}` and :math:`\eta`.
Args:
gmes:
instance of time invariant solution class
defined in :mod:`~.ode.time_invariant`
pr_choices:
dictionary of model parameter values to be used for
equation substitutions
name:
name of figure (key in figure dictionary)
fig_size:
optional figure width and height in inches
dpi:
optional rasterization resolution
"""
_ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
axes = plt.gca()
def plot_h_profile(eta_, Ci_, idx_, n_, lw=2, dashing="-"):
sub_ = pr_choices[(eta_, Ci_)]
mu_ = sub_[mu]
gmes_ = gmes[(eta_, Ci_)]
h_x_array = gmes_.h_x_direct_array if do_direct else gmes_.h_x_array
h_z_array = gmes_.h_z_direct_array if do_direct else gmes_.h_z_array
Ci_label = (
rf"{deg(Ci_)}" if deg(Ci_) >= 1 else rf"{deg(Ci_).n():0.1}"
)
color_ = self.mycolors(
idx_, 1, n_, do_smooth=False, cmap_choice="brg"
)
plt.plot(
h_x_array,
(h_z_array - h_z_array[0]),
dashing,
lw=lw,
color=color_,
label=rf"$\eta=${eta_}, "
+ rf"$\mu=${mu_}, "
+ r"$\mathsf{Ci}=$"
+ Ci_label
+ r"$\degree$",
)
def make_eta_Ci_list(eta_choice):
eta_Ci_list = [
(eta_, Ci_) for (eta_, Ci_) in gmes if eta_ == eta_choice
]
return sorted(
eta_Ci_list, key=lambda eta_Ci_: eta_Ci_[1], reverse=True
)
eta_Ci_list_1p5 = make_eta_Ci_list(Rational(3, 2))
eta_Ci_list_0p5 = make_eta_Ci_list(Rational(1, 2))
eta_Ci_list_0p25 = make_eta_Ci_list(Rational(1, 4))
for (eta_Ci_list_, dashing_, lw_) in [
(eta_Ci_list_1p5, "-", 2),
(eta_Ci_list_0p5, ":", 3),
(eta_Ci_list_0p25, "-", 2),
]:
n_ = len(eta_Ci_list_)
for i_, (eta_, Ci_) in enumerate(eta_Ci_list_):
plot_h_profile(eta_, Ci_, i_, n_, lw=lw_, dashing=dashing_)
axes.set_aspect(1 if aspect is None else aspect)
plt.grid(True, ls=":")
plt.xlim(-0.02, 1.02)
plt.xlabel(r"Distance, $x/L_{\mathrm{c}}$ [-]", fontsize=16)
plt.ylabel(r"Elevation, $z/L_{\mathrm{c}}$ [-]", fontsize=16)
plt.legend(loc="upper left", fontsize=11, framealpha=0.95)
#