Source code for wntr.extensions.design_verification.constraints

"""Hydraulic-constraint calculations for design verification."""

from __future__ import annotations

import math

import numpy as np
import pandas as pd
from wntr.utils.check_values import (
    _check_float,
    _check_positive_non_zero_float,
    _check_positive_or_zero_float,
)

from .exceptions import InvalidConstraintError
from .models import ConstraintResult


def _validate_compliance_percentage(
    required_compliance_pct: float,
) -> float:
    """Validate a required compliance percentage."""
    required = _check_float(
        required_compliance_pct,
        "required_compliance_pct",
    )

    if not math.isfinite(required):
        raise InvalidConstraintError(
            "required_compliance_pct must be finite."
        )

    if not 0.0 <= required <= 100.0:
        raise InvalidConstraintError(
            "required_compliance_pct must be between 0 and 100."
        )

    return required


def _validate_tolerance(
    value: object,
    *,
    name: str,
) -> float:
    """Return a validated non-negative finite tolerance."""
    tolerance = _check_positive_or_zero_float(
        value,
        name,
    )

    if not math.isfinite(tolerance):
        raise InvalidConstraintError(
            f"{name} must be finite."
        )

    return tolerance


[docs] def evaluate_minimum_pressure( pressure: pd.DataFrame, minimum_pressure_m: float, required_compliance_pct: float = 100.0, pressure_tolerance_m: float = 0.0, ) -> ConstraintResult: """Evaluate minimum-pressure compliance. Parameters ---------- pressure : pd.DataFrame Junction-pressure results. Rows represent simulation times and columns represent junction names. minimum_pressure_m : float Minimum allowable pressure head in metres. required_compliance_pct : float, optional Percentage of junction-time values required to satisfy the pressure limit. pressure_tolerance_m : float, optional Non-negative numerical tolerance applied below the minimum pressure limit. The default of zero preserves strict comparison behaviour. Returns ------- ConstraintResult Pressure-compliance result and critical location. """ values = pressure.to_numpy(dtype=float) minimum_pressure = _check_float( minimum_pressure_m, "minimum_pressure_m", ) if not math.isfinite(minimum_pressure): raise InvalidConstraintError( "minimum_pressure_m must be finite." ) required = _validate_compliance_percentage( required_compliance_pct ) pressure_tolerance = _validate_tolerance( pressure_tolerance_m, name="pressure_tolerance_m", ) compliant = ( values >= minimum_pressure - pressure_tolerance ) compliance_pct = float( compliant.mean() * 100.0 ) flat_position = int(np.argmin(values)) row_position, column_position = np.unravel_index( flat_position, values.shape, ) return ConstraintResult( critical_value=float( values[row_position, column_position] ), compliance_pct=compliance_pct, feasible=bool(compliance_pct >= required), critical_component=str( pressure.columns[column_position] ), critical_time=pressure.index[row_position], )
[docs] def evaluate_maximum_velocity( velocity: pd.DataFrame, maximum_velocity_mps: float, required_compliance_pct: float = 100.0, velocity_tolerance_mps: float = 0.0, ) -> ConstraintResult: """Evaluate maximum absolute pipe-velocity compliance. Parameters ---------- velocity : pd.DataFrame Pipe-velocity results. Rows represent simulation times and columns represent pipe names. maximum_velocity_mps : float Maximum allowable absolute velocity in metres per second. required_compliance_pct : float, optional Percentage of pipe-time values required to satisfy the velocity limit. velocity_tolerance_mps : float, optional Non-negative numerical tolerance applied above the maximum absolute velocity limit. The default of zero preserves strict comparison behaviour. Returns ------- ConstraintResult Velocity-compliance result and critical location. """ values = velocity.to_numpy(dtype=float) maximum_velocity = _check_positive_non_zero_float( maximum_velocity_mps, "maximum_velocity_mps", ) if not math.isfinite(maximum_velocity): raise InvalidConstraintError( "maximum_velocity_mps must be finite." ) required = _validate_compliance_percentage( required_compliance_pct ) velocity_tolerance = _validate_tolerance( velocity_tolerance_mps, name="velocity_tolerance_mps", ) # Velocity signs indicate flow direction. Compliance is based on # the magnitude of velocity. absolute_values = np.abs(values) compliant = ( absolute_values <= maximum_velocity + velocity_tolerance ) compliance_pct = float( compliant.mean() * 100.0 ) flat_position = int(np.argmax(absolute_values)) row_position, column_position = np.unravel_index( flat_position, absolute_values.shape, ) return ConstraintResult( critical_value=float( absolute_values[row_position, column_position] ), compliance_pct=compliance_pct, feasible=bool(compliance_pct >= required), critical_component=str( velocity.columns[column_position] ), critical_time=velocity.index[row_position], )