time_dependent.py
¶
Visualization of solutions with time-varying boundary conditions.
- Requires Python packages:
- class gme.plot.time_dependent.TimeDependent(dpi: int = 100, font_size: int = 11)¶
Visualization of solutions with time-varying boundary conditions.
Extends
gme.plot.base.Graphing
.- profile_isochrones(gmes: gme.ode.velocity_boundary.VelocityBoundarySolution, gmeq: gme.core.equations.Equations, sub: Dict, name: str, fig_size: Optional[Tuple[float, float]] = None, dpi: Optional[int] = None, do_zero_isochrone: bool = True, do_rays: bool = True, ray_subsetting: int = 5, ray_lw: float = 0.5, ray_ls: str = '-', ray_label: str = 'ray', do_isochrones: bool = True, isochrone_subsetting: int = 1, isochrone_lw: float = 0.5, isochrone_ls: str = '-', do_annotate_rays: bool = False, n_arrows: int = 10, arrow_sf: float = 0.7, arrow_offset: int = 4, do_annotate_cusps: bool = False, cusp_lw: float = 1.5, do_smooth_colors: bool = False, x_limits: Tuple[float, float] = (- 0.001, 1.001), y_limits: Tuple[float, float] = (- 0.025, 0.525), aspect: Optional[float] = None, do_legend: bool = True, do_alt_legend: bool = False, do_grid: bool = True, do_infer_initiation: bool = True, do_pub_label: bool = False, do_etaxi_label: bool = True, pub_label: Optional[str] = None, eta_label_xy: Tuple[float, float] = (0.65, 0.85), pub_label_xy: Tuple[float, float] = (0.5, 0.92)) → None¶
Plot isochrones and rays for a time-dependent b.c. solution.
- Parameters
gmes – instance of velocity boundary solution class defined in
gme.ode.velocity_boundary
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 plot in figures dictionary
fig_size – optional figure width and height in inches
dpi – optional rasterization resolution
do_zero_isochrone – optionally plot initial surface?
do_rays – optionally plot rays?
ray_subsetting – optional rate of ray subsetting
ray_lw – optional ray line width
ray_ls – optional ray line style
ray_label – optional ray line label
do_isochrones – optionally plot isochrones?
isochrone_subsetting – optional rate of isochrone subsetting
do_isochrone_p – optionally plot isochrone herringbones?
isochrone_lw – optional isochrone line width
isochrone_ls – optional isochrone line style
do_annotate_rays – optionally plot arrowheads along rays?
n_arrows – optional number of arrowheads to annotate along rays or cusp-line
arrow_sf – optional scale factor for arrowhead sizes
arrow_offset – optional offset to start annotating arrowheads
do_annotate_cusps – optionally plot line to visualize cusp initiation and propagation
cusp_lw – optional cusp propagation curve line width
x_limits – optional [x_min, x_max] horizontal plot range
y_limits – optional [z_min, z_max] vertical plot range
aspect – optional figure aspect ratio
do_legend – optionally plot legend
do_alt_legend – optionally plot slightly different legend
do_grid – optionally plot dotted grey gridlines
do_infer_initiation – optionally draw dotted line inferring cusp initiation at the left boundary
Code¶
"""
Visualization of solutions with time-varying boundary conditions.
---------------------------------------------------------------------
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
---------------------------------------------------------------------
"""
# Library
import warnings
from typing import Dict, Tuple, Optional
# NumPy
import numpy as np
# SymPy
from sympy import deg
# MatPlotLib
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
# GME
from gme.core.symbols import Ci
from gme.core.equations import Equations
from gme.ode.base import rpt_tuple
from gme.ode.velocity_boundary import VelocityBoundarySolution
from gme.plot.base import Graphing
warnings.filterwarnings("ignore")
__all__ = ["TimeDependent"]
class TimeDependent(Graphing):
"""
Visualization of solutions with time-varying boundary conditions.
Extends :class:`gme.plot.base.Graphing`.
"""
def profile_isochrones(
self,
gmes: VelocityBoundarySolution,
gmeq: Equations,
sub: Dict,
name: str,
fig_size: Optional[Tuple[float, float]] = None,
dpi: Optional[int] = None,
do_zero_isochrone: bool = True,
do_rays: bool = True,
ray_subsetting: int = 5,
ray_lw: float = 0.5,
ray_ls: str = "-",
ray_label: str = "ray",
do_isochrones: bool = True,
isochrone_subsetting: int = 1,
isochrone_lw: float = 0.5,
isochrone_ls: str = "-",
do_annotate_rays: bool = False,
n_arrows: int = 10,
arrow_sf: float = 0.7,
arrow_offset: int = 4,
do_annotate_cusps: bool = False,
cusp_lw: float = 1.5,
do_smooth_colors: bool = False,
x_limits: Tuple[float, float] = (-0.001, 1.001),
y_limits: Tuple[float, float] = (-0.025, 0.525),
aspect: float = None,
do_legend: bool = True,
do_alt_legend: bool = False,
do_grid: bool = True,
do_infer_initiation: bool = True,
do_pub_label: bool = False,
do_etaxi_label: bool = True,
pub_label: Optional[str] = None,
eta_label_xy: Tuple[float, float] = (0.65, 0.85),
pub_label_xy: Tuple[float, float] = (0.5, 0.92),
) -> None:
"""
Plot isochrones and rays for a time-dependent b.c. solution.
Args:
gmes:
instance of velocity boundary solution class defined in
:mod:`gme.ode.velocity_boundary`
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 plot in figures dictionary
fig_size:
optional figure width and height in inches
dpi:
optional rasterization resolution
do_zero_isochrone:
optionally plot initial surface?
do_rays:
optionally plot rays?
ray_subsetting:
optional rate of ray subsetting
ray_lw:
optional ray line width
ray_ls:
optional ray line style
ray_label:
optional ray line label
do_isochrones:
optionally plot isochrones?
isochrone_subsetting:
optional rate of isochrone subsetting
do_isochrone_p:
optionally plot isochrone herringbones?
isochrone_lw:
optional isochrone line width
isochrone_ls:
optional isochrone line style
do_annotate_rays:
optionally plot arrowheads along rays?
n_arrows:
optional number of arrowheads to annotate along rays or
cusp-line
arrow_sf:
optional scale factor for arrowhead sizes
arrow_offset:
optional offset to start annotating arrowheads
do_annotate_cusps:
optionally plot line to visualize cusp initiation and
propagation
cusp_lw:
optional cusp propagation curve line width
x_limits:
optional [x_min, x_max] horizontal plot range
y_limits:
optional [z_min, z_max] vertical plot range
aspect:
optional figure aspect ratio
do_legend:
optionally plot legend
do_alt_legend:
optionally plot slightly different legend
do_grid:
optionally plot dotted grey gridlines
do_infer_initiation:
optionally draw dotted line inferring cusp initiation at the
left boundary
"""
_ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
# Unpack for brevity
if hasattr(gmes, "rpt_isochrones"):
rx_isochrones, rz_isochrones, _, _, t_isochrones = [
gmes.rpt_isochrones[rpt_] for rpt_ in rpt_tuple
]
# Initial boundary
if hasattr(gmes, "rpt_isochrones") and do_zero_isochrone:
n_isochrones: int = len(rx_isochrones)
plt.plot(
rx_isochrones[0],
rz_isochrones[0],
"-",
color=self.gray_color(0, n_isochrones),
lw=2,
label=("zero isochrone" if do_legend else None),
)
# Rays
axes = plt.gca()
if do_rays:
n_rays = len(gmes.rpt_arrays["rx"])
for i_ray, (rx_array, rz_array, _) in enumerate(
zip(
reversed(gmes.rpt_arrays["rx"]),
reversed(gmes.rpt_arrays["rz"]),
reversed(gmes.rpt_arrays["t"]),
)
):
if (i_ray // ray_subsetting - i_ray / ray_subsetting) == 0:
this_ray_label = (
ray_label + r" ($t_{\mathrm{oldest}}$)"
if i_ray == 0
else ray_label + r" ($t_{\mathrm{newest}}$)"
if i_ray == n_rays - 1
else ""
)
if do_annotate_rays:
self.arrow_annotate_ray_custom(
rx_array,
rz_array,
axes,
i_ray,
ray_subsetting,
n_rays,
n_arrows,
arrow_sf,
arrow_offset,
x_limits=x_limits,
y_limits=y_limits,
line_style=ray_ls,
line_width=ray_lw,
ray_label=this_ray_label,
do_smooth_colors=do_smooth_colors,
)
else:
color_ = self.mycolors(
i_ray,
ray_subsetting,
n_rays,
do_smooth=do_smooth_colors,
)
plt.plot(
rx_array,
rz_array,
lw=ray_lw,
color=color_,
linestyle=ray_ls,
label=this_ray_label,
)
# Time slices or isochrones of erosion front
if hasattr(gmes, "rpt_isochrones") and do_isochrones:
n_isochrones = len(rx_isochrones)
delta_t: float = t_isochrones[1]
i_isochrone, rx_isochrone, rz_isochrone = None, None, None
# suppresses annoying pylint warning
for i_isochrone, (rx_isochrone, rz_isochrone, _) in enumerate(
zip(rx_isochrones, rz_isochrones, t_isochrones)
):
i_subsetted: float = float(
(
i_isochrone // isochrone_subsetting
- i_isochrone / isochrone_subsetting
)
)
i_subsubsetted: float = float(
(
i_isochrone // (isochrone_subsetting * 10)
- i_isochrone / (isochrone_subsetting * 10)
)
)
if (
i_isochrone > 0
and i_subsetted == 0.0
and rx_isochrone is not None
):
lw_: float = (
1.3 * isochrone_lw
if i_subsubsetted == 0.0
else 0.5 * isochrone_lw
)
plt.plot(
rx_isochrone,
rz_isochrone,
self.gray_color(i_isochrone, n_isochrones),
linestyle=isochrone_ls,
lw=lw_,
)
# Hack legend items
if rx_isochrone is not None:
label_part = r"isochrone $\Delta{\hat{t}}=$"
label_ = rf"{label_part}${int(10*delta_t)}$"
plt.plot(
rx_isochrone,
rz_isochrone,
self.gray_color(i_isochrone, n_isochrones),
linestyle=isochrone_ls,
lw=1.3 * isochrone_lw,
label=label_,
)
label_ = rf"{label_part}${round(delta_t,1)}$"
plt.plot(
rx_isochrone,
rz_isochrone,
self.gray_color(i_isochrone, n_isochrones),
linestyle=isochrone_ls,
lw=0.5 * isochrone_lw,
label=label_,
)
# Knickpoint aka cusp propagation
if do_annotate_cusps:
rxz_array = np.array(
[
rxz
for (t, rxz), _, _ in gmes.trxz_cusps
if rxz != [] and rxz[0] <= 1.01
]
)
if (n_cusps := len(rxz_array)) > 0:
# Plot locations of cusps as a propagation curve
plt.plot(
rxz_array.T[0][:-1],
rxz_array.T[1][:-1],
lw=cusp_lw,
color="r",
alpha=0.4,
label="cusp propagation",
)
# Plot inferred initiation of cusp propagation
# from LHS boundary
if do_infer_initiation:
((x1_, y1_), (x2_, y2_)) = rxz_array[0:2]
dx_, dy_ = (x2_ - x1_) / 100, (y2_ - y1_) / 100
x0_ = 0
y0_ = y1_ + (dy_ / dx_) * (x0_ - x1_)
plt.plot(
(x0_, x1_),
(y0_, y1_),
":",
lw=cusp_lw,
color="r",
alpha=0.4,
label="inferred initiation",
)
# Plot arrow annotations using subset of cusps
cusp_subsetting = max(1, n_cusps // n_arrows)
rxz_array_subset = rxz_array[::cusp_subsetting].T
sf: float = 0.7
my_arrow_style = mpatches.ArrowStyle.Fancy(
head_length=0.99 * sf,
head_width=0.6 * sf,
tail_width=0.0001,
)
for ((x1_, y1_), (x0_, y0_)) in zip(
rxz_array_subset.T[:-1], rxz_array_subset.T[1:]
):
dx_, dy_ = (x1_ - x0_) / 100, (y1_ - y0_) / 100
if (
x_limits is None
or (
x0_ >= x_limits[0]
if x_limits[0] is not None
else True
)
and (
x0_ <= x_limits[1]
if x_limits[1] is not None
else True
)
):
axes.annotate(
"",
xy=(x0_, y0_),
xytext=(x0_ + dx_, y0_ + dy_),
arrowprops=dict(
arrowstyle=my_arrow_style, color="r", alpha=0.4
),
)
# Label axes
plt.xlabel(r"Distance, $x/L_{\mathrm{c}}$ [-]", fontsize=16)
plt.ylabel(r"Elevation, $z/L_{\mathrm{c}}$ [-]", fontsize=16)
if do_legend or do_alt_legend:
plt.legend(loc="upper left", fontsize=10, framealpha=0.95)
# Tidy axes etc
plt.xlim(*x_limits)
plt.ylim(*y_limits)
if do_grid:
plt.grid(True, ls=":")
axes.set_aspect(1 if aspect is None else aspect)
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=14,
color="k",
)