Tutorial 71: Time Curves

This example demonstrates how to …

SIR 3S Installation

[1]:
SIR3S_SIRGRAF_DIR = r"C:\3S\SIR 3S\SirGraf-90-15-00-19_Quebec"

Imports

[2]:
from sir3stoolkit.core import wrapper
[3]:
from sir3stoolkit.mantle import mantle

The wrapper package has to be initialized with reference to a SIR 3S (SirGraf) installation.

[4]:
wrapper.Initialize_Toolkit(SIR3S_SIRGRAF_DIR)

Additional imports

[5]:
import os

Initialization

[6]:
s3s = mantle.Mantle_SIR3S_Model()
Initialization complete

Open Model

[7]:
dbFilePath=r"C:\Users\aUsername\3S\PT3S\PT3S\Examples\Example2.db3"
[8]:
s3s.OpenModel(dbName=dbFilePath,
              providerType=s3s.ProviderTypes.SQLite,
              Mid="M-1-0-1",
              saveCurrentlyOpenModel=False,
              namedInstance="",
              userID="",
              password="")
Model is open for further operation

Edit

[9]:
node_tks = ["5471942051669438823", "5166327497394449628", "5667818255300782920", "5262999985134416180"]
[10]:
df_pipes_results = s3s.generate_element_results_dataframe(element_type=s3s.ObjectTypes.Node
                                                          ,tks=node_tks
                                                          ,properties=["QM", "PH"]
                                                          ,timestamps=None # all available simulation timestamps will be used
                                                          )
[2025-11-21 15:11:52,562] INFO in sir3stoolkit.mantle.dataframes: [results] Generating results dataframe for element type: ObjectTypes.Node
[2025-11-21 15:11:52,562] INFO in sir3stoolkit.mantle.dataframes: [Resolving Timestamps] No timestamps were given. Checking available simulation timestamps (SIR3S_Model.GetTimeStamps()).
[2025-11-21 15:11:52,621] INFO in sir3stoolkit.mantle.dataframes: [Resolving Timestamps] 601 simulation timestamp(s) are available.
[2025-11-21 15:11:52,693] INFO in sir3stoolkit.mantle.dataframes: [Resolving Timestamps] 601 valid timestamp(s) will be used.
[2025-11-21 15:11:52,697] INFO in sir3stoolkit.mantle.dataframes: [Resolving tks] Retrieved 635 element(s) of element type ObjectTypes.Node.
[2025-11-21 15:11:52,698] INFO in sir3stoolkit.mantle.dataframes: [Resolving tks] 4 tks remain after filering for given tks.
[2025-11-21 15:11:52,699] INFO in sir3stoolkit.mantle.dataframes: [results] Using 2 result properties.
[2025-11-21 15:11:52,802] INFO in sir3stoolkit.mantle.dataframes: [results] Retrieving result properties...
[2025-11-21 15:11:55,670] INFO in sir3stoolkit.mantle.dataframes: [results] Done. Shape: (601, 8)
[11]:
df_pipes_results.head(3)
[11]:
tk 5471942051669438823 5166327497394449628 5667818255300782920 5262999985134416180
name K0011 5143521_i 791393_3_k 1629686_0_i
end_nodes No end nodes on element type No end nodes on element type No end nodes on element type No end nodes on element type
property QM PH QM PH QM PH QM PH
timestamp
2020-01-28 15:45:01.000 +01:00 0 10.59964 -5.7E-06 8.320659 -5.7E-06 6.477164 -3.380222E-06 0.8816414
2020-01-28 15:45:01.100 +01:00 0 10.59981 -5.7E-06 8.320658 -5.7E-06 6.477123 -3.380221E-06 0.8816413
2020-01-28 15:45:01.200 +01:00 0 10.59993 -5.7E-06 8.320656 -5.7E-06 6.47708 -3.380217E-06 0.8816391
[25]:
import pandas as pd
import matplotlib.pyplot as plt

def plot_two_common_lowest(df: pd.DataFrame, start=None, end=None, labels=None, figsize=(10, 4)):
    """
    Plot two minimal-common lowest-level column labels in a single figure
    using left/right y-axes (twin axes). Each axis shows one label; within
    each, one line per level-0 group is plotted.

    Parameters
    ----------
    df : pd.DataFrame
        DataFrame with MultiIndex columns: (level-0, ..., lowest-level).
    start, end : str or None
        Optional start and end timestamps (ISO format or parseable by pandas).
        Example: '2020-01-28 15:45:01.000 +01:00'
    labels : tuple[str, str] or None
        Optional pair of lowest-level labels (as strings) to plot.
        If None, the function will compute the intersection across level-0
        groups and take the first two labels.
    figsize : tuple
        Figure size.

    Returns
    -------
    fig, ax_left, ax_right, used_labels : (Figure, Axes, Axes, tuple[str, str])
    """
    if not isinstance(df.columns, pd.MultiIndex):
        raise ValueError("DataFrame must have MultiIndex columns.")

    # Convert index to datetime if possible
    df = df.copy()
    try:
        df.index = pd.to_datetime(df.index)
    except Exception:
        pass

    # Apply time filtering if start/end provided
    if start:
        start_ts = pd.to_datetime(start)
        df = df[df.index >= start_ts]
    if end:
        end_ts = pd.to_datetime(end)
        df = df[df.index <= end_ts]

    lvl0 = df.columns.get_level_values(0)
    lvl_last = df.columns.get_level_values(-1)

    # Map level-0 -> set of lowest-level labels
    mapping = (
        pd.Series(lvl_last.astype(str), index=lvl0)
        .groupby(level=0)
        .unique()
    )
    sets = [set(vals) for vals in mapping]
    common = sorted(set.intersection(*sets) if sets else set())

    if labels is None:
        if len(common) < 2:
            raise ValueError("Need at least two common lowest-level labels to use twin axes.")
        used_labels = (common[0], common[1])
    else:
        if len(labels) != 2:
            raise ValueError("labels must be a tuple/list of two strings.")
        labels_str = tuple(str(x) for x in labels)
        missing = [l for l in labels_str if l not in common]
        if missing:
            raise ValueError(f"Provided labels not common across all groups: {missing}")
        used_labels = labels_str

    top_groups = list(mapping.index)

    fig, ax_left = plt.subplots(figsize=figsize)
    ax_right = ax_left.twinx()

    def plot_label_on_axis(label_str, axis, linestyle="-"):
        lines = []
        for g in top_groups:
            mask = (df.columns.get_level_values(0).astype(str) == str(g)) & \
                   (df.columns.get_level_values(-1).astype(str) == label_str)
            cols = df.columns[mask]
            if len(cols) == 0:
                continue
            series = df[cols].sum(axis=1) if len(cols) > 1 else df[cols[0]]
            ln, = axis.plot(df.index, series, linestyle=linestyle, label=f"{g}")
            lines.append(ln)
        return lines

    left_label, right_label = used_labels
    lines_left = plot_label_on_axis(left_label, ax_left, linestyle="-")
    lines_right = plot_label_on_axis(right_label, ax_right, linestyle="--")

    ax_left.set_ylabel(str(left_label))
    ax_right.set_ylabel(str(right_label))
    ax_left.set_xlabel("Time")
    ax_left.grid(True, linestyle="--", alpha=0.3)

    lines_all = lines_left + lines_right
    labels_all = [l.get_label() for l in lines_all]
    if lines_all:
        ax_left.legend(lines_all, labels_all, loc="best", fontsize=8)

    fig.tight_layout()
    return fig, ax_left, ax_right, used_labels
[27]:
fig, ax_l, ax_r, used = plot_two_common_lowest(
    df_pipes_results,
    start="2020-01-28 14:45:00.000 +01:00",
    end="2020-01-28 15:46:00.000 +01:00"
)
plt.show()
../../_images/tutorials_SIR3S_Model_Mantle_ToolkitTutorial071_22_0.png
[12]:
#df_pipes_results.xs(key='1629686_0_i', axis=1, level=1)