Source code for ogstools.logparser.plots

# Copyright (c) 2012-2025, OpenGeoSys Community (http://www.opengeosys.org)
#            Distributed under a Modified BSD License.
#            See accompanying file LICENSE.txt or
#            http://www.opengeosys.org/project/license
#


from typing import Any, Literal

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

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, x_ticks: np.ndarray, y_ticks: np.ndarray, x_label: str
) -> plt.Figure:
    fig.axes[0].set_xticks(x_ticks, minor=True)
    fig.axes[0].set_yticks(y_ticks, minor=True)
    fig.axes[0].set_xlabel(x_label.replace("_", " "))
    fig.axes[0].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])
    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: :py: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" Keyword Arguments: - see: :py: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 = kwargs.get("fig", res) return _format_fig(fig, 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_scale` 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" Keyword Arguments: - see: :py: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_scale", True) res = heatmap(errors, err_var, x_vals=x_vals, **kwargs) fig = kwargs.get("fig", res) return _format_fig(fig, x_ticks, y_ticks, x_label=x_metric)