Source code for ogstools.logparser.plots

# SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
# SPDX-License-Identifier: BSD-3-Clause


from typing import Any, Literal

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib.ticker import MaxNLocator

from ogstools.plot import heatmap
from ogstools.variables import Scalar

from .common_ogs_analyses import (
    convergence_order_per_ts_iteration,
    errors_per_ts_iteration,
    model_and_clock_time,
)


def _format_fig(
    fig: plt.Figure,
    ax: plt.Axes,
    x_ticks: np.ndarray,
    y_ticks: np.ndarray,
    x_label: str,
) -> plt.Figure:
    x_axis_labels = {
        "time_step": "time step",
        "model_time": "model time [s]",
    }
    ax.set_xticks(x_ticks, minor=True)
    ax.set_yticks(y_ticks, minor=True)
    ax.yaxis.set_major_locator(MaxNLocator(integer=True))
    ax.set_xlabel(x_axis_labels.get(x_label, x_label.replace("_", " ")))
    ax.set_ylabel("iteration")
    fig.tight_layout()
    return fig


def _x_data(
    df: pd.DataFrame,
    x_metric: Literal["time_step", "model_time"],
    default_len_x: int,
) -> tuple[np.ndarray | None, range]:
    if x_metric == "time_step":
        return None, range(default_len_x)
    df_time = model_and_clock_time(df).reset_index()
    df_time = df_time.pivot_table(
        "model_time", "time_step", aggfunc="last"
    ).reset_index()
    x_vals = df_time[x_metric].to_numpy()
    x_ticks = 0.5 * (x_vals[1:] + x_vals[:-1])
    if len(x_vals) == default_len_x:
        # TODO: Not sure why, but sometimes the required length of the x_vals
        # is off by 1 compared to the required length to plot a heatmap with
        # errors or orders. The following fixes this but the underlying problem
        # is not yet understood.
        x_vals = np.append(0.0, x_vals)
    return x_vals, x_ticks


[docs] def plot_convergence_order( df: pd.DataFrame, n: Literal[3, 4] = 3, x_metric: Literal["time_step", "model_time"] = "time_step", **kwargs: Any, ) -> plt.Figure: """ Create a heatmap of the nonlinear solver convergence order. see: :func:`~ogstools.logparser.common_ogs_analyses.convergence_order_per_ts_iteration` In order to estimate the convergence order we need to take into account multiple values and thus cannot assign each iteration a convergence order. Only for iterations `i` of `i >= n` an order is calculated and plotted. Per default the scale is limited to a range of 0 to 2 to limit the view to meaningful data. Set the keyword arguments `vmin` and `vmax`to `None` to see the entire scale. :param df: Dataframe of a simulation log. :param n: Number of error values to use to estimate the convergence order. :param x_metric: x_axis can represent either "time_step" or "model_time" (not computation time) Keyword Arguments: - see: :func:`~ogstools.plot.heatmaps.heatmap`) :returns: A figure with a heatmap of the nonlinear solver convergence order. """ orders = convergence_order_per_ts_iteration(df, n=n) x_vals, x_ticks = _x_data(df, x_metric, orders.shape[1]) y_ticks = range(orders.shape[0]) order_var = Scalar("convergence_order", cmap="RdBu", symbol="q") kwargs.setdefault("vmin", 0) kwargs.setdefault("vmax", 2) res = heatmap(orders, order_var, x_vals=x_vals, **kwargs) fig: plt.Figure = kwargs.get("fig", res) ax = kwargs.get("ax", fig.axes[0]) return _format_fig(fig, ax, x_ticks, y_ticks, x_label=x_metric)
[docs] def plot_convergence( df: pd.DataFrame, metric: Literal["dx", "dx_x", "x"] = "dx", x_metric: Literal["time_step", "model_time"] = "time_step", **kwargs: Any, ) -> plt.Figure: """ Create a heatmap of the nonlinear solver convergence data. The individual values in the heatmap correspond to the top right indices on the x- and y-axis. E.g. the very first entry which fills the space between timesteps 0-1 and iteration 0-1 belongs to the first iteration of the first timestep. Thus we immediately read on which iteration a timestep converged and on which timestep the simulation ended. Per default logarithmic scaling is used. Set `log_scaled` to `False` to use linear scaling. :param df: Dataframe of a simulation log. :param metric: Which metric / column of the Dataframe to plot. dx (absolute error), dx_x (relative error), x (residual) :param x_metric: x_axis can represent either "time_step" or "model_time" (not computation time) Keyword Arguments: - see: :func:`~ogstools.plot.heatmaps.heatmap`) :returns: A figure with a heatmap of the nonlinear solver convergence data. """ errors = errors_per_ts_iteration(df, metric) x_vals, x_ticks = _x_data(df, x_metric, errors.shape[1]) y_ticks = range(errors.shape[0]) names = {"dx": "absolute error", "dx_x": "relative error", "x": "residual"} symbol = str(metric).replace("_", " / ") err_var = Scalar(names[metric], cmap="viridis", symbol=symbol) kwargs.setdefault("log_scaled", True) res = heatmap(errors, err_var, x_vals=x_vals, **kwargs) fig: plt.Figure = kwargs.get("fig", res) ax = kwargs.get("ax", fig.axes[0]) return _format_fig(fig, ax, x_ticks, y_ticks, x_label=x_metric)