ray_profiles.py

Visualization of ray properties along a profile.


Requires Python packages/modules:

class gme.plot.ray_profiles.RayProfiles(dpi: int = 100, font_size: int = 11)

Plot rays along a profile.

Visualization of ray properties as a function of distance along the profile.

Extends gme.plot.base.Graphing.

profile_h(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, y_limits: Optional[Tuple[Optional[float], Optional[float]]] = None, do_legend: bool = True, do_profile_points: bool = True, profile_subsetting: int = 5, do_pub_label: bool = False, pub_label: str = '', pub_label_xy: Tuple[float, float] = (0.93, 0.33), do_etaxi_label=True, eta_label_xy: Optional[Tuple[float, float]] = None)None

Plot profile.

Plot a time-invariant topographic profile solution of Hamilton’s equations.

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

profile_h_rays(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, x_limits: Optional[Tuple[Optional[float], Optional[float]]] = None, y_limits: Optional[Tuple[Optional[float], Optional[float]]] = None, n_points: int = 101, do_direct: bool = True, n_rays: int = 4, do_schematic: bool = False, do_legend: bool = True, do_fault_bdry: bool = False, do_compute_xivh_ratio: bool = False, do_one_ray: bool = False, do_t_sampling: bool = True, do_pub_label: bool = False, pub_label: str = '', pub_label_xy: Tuple[float, float] = (0.93, 0.33), do_etaxi_label: bool = True, eta_label_xy: Tuple[float, float] = (0.5, 0.8))None

Plot rays and profile.

Plot a set of erosion rays for a time-invariant topographic profile solution of Hamilton’s equations.

Hamilton’s equations are integrated once (from the left boundary to the divide) and a time-invariant profile is constructed by repeating the ray trajectory, with a suitable truncation and vertical initial offset, multiple times at the left boundary: the set of end of truncated rays constitutes the ‘steady-state’ topographic profile. The point of truncation of each trajectory corresponds to the effective time lag imposed by the choice of vertical initial offset (which is controlled by the vertical slip rate).

Visualization of this profile includes: (i) plotting a subsampling of the terminated points of the ray truncations; (ii) plotting a continuous curve generated by integrating the surface gradient implied by the erosion-front normal covector values \(\mathbf{\widetilde{p}}\) values generated by solving Hamilton’s equations.

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

  • do_direct – plot directly integrated ray trajectory (from \(\mathbf{\widetilde{p}}\) values) as a solid curve

  • ray_subsetting – optional ray subsampling rate (typically far more rays are computed than should be plotted)

  • do_schematic – optionally plot in more schematic form for expository purposes?

  • do_simple – optionally simplify?

profile_ray(gmes: gme.ode.single_ray.SingleRaySolution, gmeq: gme.core.equations.Equations, sub: Dict, name: str, fig_size: Optional[Tuple[float, float]] = None, dpi: Optional[int] = None, y_limits: Optional[Tuple[Optional[float], Optional[float]]] = None, n_points: int = 101, aspect: Optional[float] = None, do_schematic: bool = False, do_ndim: bool = True, do_simple: bool = False, do_t_sampling: bool = True, do_pub_label: bool = False, pub_label: str = '', pub_label_xy: Tuple[float, float] = (0.15, 0.5), do_etaxi_label: bool = True, eta_label_xy=None)None

Plot an erosion ray solution of Hamilton’s equations.

Parameters
  • gmes – instance of single-ray solution class defined in gme.ode.single_ray

  • 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

  • do_direct – plot directly integrated ray trajectory (from \(\mathbf{\widetilde{p}}\) values) as a solid curve

  • do_schematic – optionally plot in more schematic form for expository purposes?

  • do_simple – optionally simplify?

Code

"""
Visualization of ray properties along a profile.

---------------------------------------------------------------------

Requires Python packages/modules:
  -  :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 Tuple, Dict, Optional

# NumPy
import numpy as np

# SymPy
from sympy import deg, tan

# MatPlotLib
import matplotlib.pyplot as plt
from matplotlib.pyplot import Axes

# GME
from gme.core.symbols import Ci, xiv_0, xih_0
from gme.core.equations import Equations
from gme.ode.single_ray import SingleRaySolution
from gme.ode.time_invariant import TimeInvariantSolution
from gme.plot.base import Graphing

warnings.filterwarnings("ignore")

__all__ = ["RayProfiles"]


class RayProfiles(Graphing):
    """
    Plot rays along a profile.

    Visualization of ray properties as a function of distance along
    the profile.

    Extends :class:`gme.plot.base.Graphing`.
    """

    def profile_ray(
        self,
        gmes: SingleRaySolution,
        gmeq: Equations,
        sub: Dict,
        name: str,
        fig_size: Optional[Tuple[float, float]] = None,
        dpi: Optional[int] = None,
        y_limits: Optional[Tuple[Optional[float], Optional[float]]] = None,
        n_points: int = 101,
        aspect: Optional[float] = None,
        do_schematic: bool = False,
        do_ndim: bool = True,
        do_simple: bool = False,
        do_t_sampling: bool = True,
        do_pub_label: bool = False,
        pub_label: str = "",
        pub_label_xy: Tuple[float, float] = (0.15, 0.50),
        do_etaxi_label: bool = True,
        eta_label_xy=None,
    ) -> None:
        r"""
        Plot an erosion ray solution of Hamilton's equations.

        Args:
            gmes:
                instance of single-ray solution class
                defined in :mod:`gme.ode.single_ray`
            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
            do_direct:
                plot directly integrated ray trajectory
                (from :math:`\mathbf{\widetilde{p}}` values) as a solid curve
            do_schematic:
                optionally plot in more schematic form for expository purposes?
            do_simple:
                optionally simplify?

        """
        _ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
        axes: Axes = plt.gca()
        # pub_label_xy = [0.15,0.50] if pub_label_xy is None else pub_label_xy

        t_array = gmes.t_array
        rx_array = gmes.rx_array
        # rz_array = gmes.rz_array
        # v_array = n.sqrt(gmes.rdotx_array**2 + gmes.rdotz_array**2)

        if do_t_sampling:
            t_begin, t_end = t_array[0], t_array[-1]
            t_rsmpld_array = np.linspace(t_begin, t_end, n_points)
        else:
            x_rsmpld_array = np.linspace(rx_array[0], rx_array[-1], n_points)
            t_rsmpld_array = gmes.t_interp_x(x_rsmpld_array)
        rx_rsmpld_array = gmes.rx_interp_t(t_rsmpld_array)
        rz_rsmpld_array = gmes.rz_interp_t(t_rsmpld_array)
        v_rsmpld_array = np.sqrt(
            gmes.rdotx_interp_t(t_rsmpld_array) ** 2
            + gmes.rdotz_interp_t(t_rsmpld_array) ** 2
        )

        # Plot arrow-annotated rays
        xi_vh_ratio = (
            float(-tan(Ci).subs(sub))
            if do_ndim
            else float(-(xiv_0 / xih_0).subs(sub))
        )
        self.draw_rays_with_arrows_simple(
            axes,
            sub,
            xi_vh_ratio,
            t_rsmpld_array,
            rx_rsmpld_array,
            rz_rsmpld_array,
            v_rsmpld_array,
            n_rays=1,
            n_t=None,
            ls="-",
            sf=1,
            do_one_ray=True,
            color="0.5",
        )

        axes.set_aspect(aspect if aspect is not None else 1)
        plt.grid(True, ls=":")
        plt.xlabel(r"Distance, $x/L_{\mathrm{c}}$  [-]", fontsize=13)
        plt.ylabel(r"Elevation, $z/L_{\mathrm{c}}$  [-]", fontsize=13)
        if eta_label_xy is None:
            eta_label_xy = (0.92, 0.15)
        if not do_schematic and not do_simple:
            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=13,
                    color="k",
                )
            if do_pub_label:
                plt.text(
                    *pub_label_xy,
                    pub_label,
                    transform=axes.transAxes,
                    horizontalalignment="center",
                    verticalalignment="center",
                    fontsize=16,
                    color="k",
                )
        if y_limits is not None:
            plt.ylim(*y_limits)

    def profile_h_rays(
        self,
        gmes: TimeInvariantSolution,
        gmeq: Equations,
        sub: Dict,
        name: str,
        fig_size: Optional[Tuple[float, float]] = None,
        dpi: Optional[int] = None,
        x_limits: Optional[Tuple[Optional[float], Optional[float]]] = None,
        y_limits: Optional[Tuple[Optional[float], Optional[float]]] = None,
        n_points: int = 101,
        do_direct: bool = True,
        n_rays: int = 4,
        do_schematic: bool = False,
        do_legend: bool = True,
        do_fault_bdry: bool = False,
        do_compute_xivh_ratio: bool = False,
        do_one_ray: bool = False,
        do_t_sampling: bool = True,
        do_pub_label: bool = False,
        pub_label: str = "",
        pub_label_xy: Tuple[float, float] = (0.93, 0.33),
        do_etaxi_label: bool = True,
        eta_label_xy: Tuple[float, float] = (0.5, 0.8),
    ) -> None:
        r"""
        Plot rays and profile.

        Plot a set of erosion rays for a time-invariant
        topographic profile solution of Hamilton's equations.

        Hamilton's equations are integrated once
        (from the left boundary to the divide)
        and a time-invariant profile is constructed by repeating
        the ray trajectory, with a suitable truncation and vertical
        initial offset, multiple times at the left boundary:
        the set of end of truncated rays constitutes the 'steady-state'
        topographic profile.
        The point of truncation of each trajectory corresponds to the
        effective time lag imposed by the choice of vertical initial
        offset (which is controlled by the vertical slip rate).

        Visualization of this profile includes: (i) plotting a subsampling
        of the terminated points of the ray truncations;
        (ii) plotting a continuous curve generated by integrating the surface
        gradient implied by the erosion-front normal
        covector values :math:`\mathbf{\widetilde{p}}` values generated by
        solving Hamilton's equations.

        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
            do_direct:
                plot directly integrated ray trajectory
                (from :math:`\mathbf{\widetilde{p}}` values) as a solid curve
            ray_subsetting:
                optional ray subsampling rate
                (typically far more rays are computed than should be plotted)
            do_schematic:
                optionally plot in more schematic form for expository purposes?
            do_simple:
                optionally simplify?

        """
        _ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
        # pub_label_xy = [0.93,0.33] if pub_label_xy is None else pub_label_xy
        # eta_label_xy = [0.5,0.8] if eta_label_xy is None else eta_label_xy
        axes: Axes = plt.gca()

        t_array = gmes.t_array  # [::ray_subsetting]
        rx_array = gmes.rx_array  # [::ray_subsetting]
        # rz_array = gmes.rz_array #[::ray_subsetting]

        if do_t_sampling:
            (t_begin, t_end) = t_array[0], t_array[-1]
            t_rsmpld_array = np.linspace(t_begin, t_end, n_points)
        else:
            x_rsmpld_array = np.linspace(rx_array[0], rx_array[-1], n_points)
            t_rsmpld_array = gmes.t_interp_x(x_rsmpld_array)
        rx_rsmpld_array = gmes.rx_interp_t(t_rsmpld_array)
        rz_rsmpld_array = gmes.rz_interp_t(t_rsmpld_array)

        # Plot arrow-annotated rays
        xi_vh_ratio = (
            float(-tan(Ci).subs(sub))
            if do_compute_xivh_ratio
            else float(-(xiv_0 / xih_0).subs(sub))
        )
        self.draw_rays_with_arrows_simple(
            axes,
            sub,
            xi_vh_ratio,
            t_rsmpld_array,
            rx_rsmpld_array,
            rz_rsmpld_array,
            n_rays=n_rays,
            n_t=None,
            ls="-" if do_schematic else "-",
            sf=0.5 if do_schematic else 1,
            do_one_ray=do_one_ray,
        )

        if do_schematic:
            # For schematic fig, also plot mirror-image topo profile
            #                     on opposite side of drainage divide
            self.draw_rays_with_arrows_simple(
                axes,
                sub,
                xi_vh_ratio,
                t_rsmpld_array,
                2 - rx_rsmpld_array,
                rz_rsmpld_array,
                n_rays=n_rays,
                n_t=None,
                ls="-" if do_schematic else "-",
                sf=0.5 if do_schematic else 1,
                do_labels=False,
            )

        # # Markers = topo profile from ray terminations
        # if not do_schematic and not do_one_ray:
        #     plt.plot( gmes.x_array[::profile_subsetting],
        #                             gmes.h_array[::profile_subsetting],
        #                 'k'+('s' if do_profile_points else '-'),
        #            ms=3, label=r'$T(\mathbf{r})$ from rays $\mathbf{r}(t)$' )

        # Solid line = topo profile from direct integration of gradient array
        if (do_direct or do_schematic) and not do_one_ray:
            plt.plot(
                gmes.h_x_array,
                gmes.h_z_array - gmes.h_z_array[0],
                "k",
                label=r"$T(\mathbf{r})$"
                if do_schematic
                else r"$T(\mathbf{r})$",
            )
            if do_schematic:
                plt.plot(
                    gmes.h_x_array,
                    gmes.h_z_array - gmes.h_z_array[0] + 0.26,
                    "0.75",
                    lw=1,
                    ls="--",
                )
                plt.plot(
                    gmes.h_x_array,
                    gmes.h_z_array - gmes.h_z_array[0] + 0.13,
                    "0.5",
                    lw=1,
                    ls="--",
                )
                plt.plot(
                    2 - gmes.h_x_array, gmes.h_z_array - gmes.h_z_array[0], "k"
                )
                plt.plot(
                    2 - gmes.h_x_array,
                    gmes.h_z_array - gmes.h_z_array[0] + 0.13,
                    "0.5",
                    lw=1,
                    ls="--",
                )
                plt.plot(
                    2 - gmes.h_x_array,
                    gmes.h_z_array - gmes.h_z_array[0] + 0.26,
                    "0.75",
                    lw=1,
                    ls="--",
                )

        axes.set_aspect(1)
        plt.grid(True, ls=":")
        plt.xlabel(
            r"Distance, $x/L_{\mathrm{c}}$  [-]",
            fontsize=13 if do_schematic else 16,
        )
        plt.ylabel(
            r"Elevation, $z/L_{\mathrm{c}}$  [-]",
            fontsize=13 if do_schematic else 16,
        )
        if not do_schematic and not do_one_ray and do_legend:
            plt.legend(
                loc="upper right" if do_schematic else "upper left",
                fontsize=9 if do_schematic else 11,
                framealpha=0.95,
            )
        if not do_schematic:
            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=16,
                    color="k",
                )
            if do_pub_label:
                plt.text(
                    *pub_label_xy,
                    pub_label,
                    transform=axes.transAxes,
                    horizontalalignment="center",
                    verticalalignment="center",
                    fontsize=16,
                    color="k",
                )
        if x_limits is not None:
            plt.xlim(*x_limits)
        if y_limits is not None:
            plt.ylim(*y_limits)

        if do_schematic:
            for x_, align_ in ((0.03, "center"), (1.97, "center")):
                plt.text(
                    x_,
                    0.45,
                    "rays initiated",
                    rotation=0,
                    horizontalalignment=align_,
                    verticalalignment="center",
                    fontsize=12,
                    color="r",
                )
            plt.text(
                1,
                0.53,
                "rays annihilated",
                rotation=0,
                horizontalalignment="center",
                verticalalignment="center",
                fontsize=12,
                color="0.25",
            )
            for x_, align_ in ((-0.03, "right"), (2.03, "left")):
                plt.text(
                    x_,
                    0.17,
                    "fault slip b.c."
                    if do_fault_bdry
                    else "const. erosion rate",
                    rotation=90,
                    horizontalalignment=align_,
                    verticalalignment="center",
                    fontsize=12,
                    color="r",
                    alpha=0.7,
                )
            plt.text(
                0.46,
                0.38,
                r"surface isochrone $T(\mathbf{r})=\mathrm{past}$",
                rotation=12,
                horizontalalignment="center",
                verticalalignment="center",
                fontsize=10,
                color="0.2",
            )
            plt.text(
                0.52,
                0.05,
                r"surface isochrone $T(\mathbf{r})=\mathrm{now}$",
                rotation=12,
                horizontalalignment="center",
                verticalalignment="center",
                fontsize=10,
                color="k",
            )
            for (x_, y_, dx_, dy_, shape_) in (
                (0, 0.4, 0, -0.15, "left"),
                (0, 0.25, 0, -0.15, "left"),
                (0, 0.1, 0, -0.15, "left"),
                (2, 0.4, 0, -0.15, "right"),
                (2, 0.25, 0, -0.15, "right"),
                (2, 0.1, 0, -0.15, "right"),
            ):
                plt.arrow(
                    x_,
                    y_,
                    dx_,
                    dy_,
                    head_length=0.04,
                    head_width=0.03,
                    length_includes_head=True,
                    shape=shape_,
                    facecolor="r",
                    edgecolor="r",
                )

    def profile_h(
        self,
        gmes: TimeInvariantSolution,
        gmeq: Equations,
        sub: Dict,
        name: str,
        fig_size: Optional[Tuple[float, float]] = None,
        dpi: Optional[int] = None,
        y_limits: Optional[Tuple[Optional[float], Optional[float]]] = None,
        do_legend: bool = True,
        do_profile_points: bool = True,
        profile_subsetting: int = 5,
        do_pub_label: bool = False,
        pub_label: str = "",
        pub_label_xy: Tuple[float, float] = (0.93, 0.33),
        do_etaxi_label=True,
        eta_label_xy: Tuple[float, float] = None,
    ) -> None:
        r"""
        Plot profile.

        Plot a time-invariant topographic profile solution of
        Hamilton's equations.

        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
        """
        _ = self.create_figure(name, fig_size=fig_size, dpi=dpi)
        # pub_label_xy = [0.93,0.33] if pub_label_xy is None else pub_label_xy
        axes: Axes = plt.gca()

        # t_array  = gmes.t_array #[::ray_subsetting]
        # rx_array = gmes.rx_array #[::ray_subsetting]
        # rz_array = gmes.rz_array #[::ray_subsetting]
        # print()

        # if do_t_sampling:
        #     t_begin, t_end = t_array[0], t_array[-1]
        #     # t_rsmpld_array = np.linspace(t_begin, t_end, n_points)
        # else:
        #     x_rsmpld_array = np.linspace(rx_array[0], rx_array[-1], n_points)
        #     t_rsmpld_array = gmes.t_interp_x(x_rsmpld_array)
        # # rx_rsmpld_array = gmes.rx_interp_t(t_rsmpld_array)
        # # rz_rsmpld_array = gmes.rz_interp_t(t_rsmpld_array)

        # Markers = topo profile from ray terminations
        plt.plot(
            gmes.x_array[::profile_subsetting],
            gmes.h_array[::profile_subsetting],
            "k" + ("s" if do_profile_points else "-"),
            ms=3,
            label=r"$T(\mathbf{r})$ from rays $\mathbf{r}(t)$",
        )

        # 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",
            label=r"$T(\mathbf{r})$ by integration",
        )
        axes.set_aspect(1)
        plt.grid(True, ls=":")
        plt.xlabel(r"Distance, $x/L_{\mathrm{c}}$  [-]", fontsize=15)
        plt.ylabel(r"Elevation, $z/L_{\mathrm{c}}$  [-]", fontsize=15)
        if do_legend:
            plt.legend(loc=(0.38, 0.75), fontsize=11, framealpha=0.95)
        if eta_label_xy is None:
            eta_label_xy = (0.92, 0.15)
        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=16,
                color="k",
            )
        if do_pub_label:
            plt.text(
                *pub_label_xy,
                pub_label,
                transform=axes.transAxes,
                horizontalalignment="center",
                verticalalignment="center",
                fontsize=16,
                color="k",
            )
        if y_limits is not None:
            plt.ylim(*y_limits)