Skip to content

Performance Metrics

OpenSMC includes 12 built-in metrics to evaluate the performance of sliding mode controllers, from error-based integrals (ISE, IAE, ITAE) to controller-specific measures (chattering index, control effort).

Usage Example

from opensmc import metrics

# After running a simulation and getting a result dict
# result has keys: 't', 'x', 'u', 's', 'ref'

# Calculate a single metric
ise = metrics.ise(result)

# Calculate all metrics at once
all_metrics = metrics.compute_all(result)
print(f"Overshoot: {all_metrics['overshoot']}%")
print(f"Settling Time: {all_metrics['settling_time']}s")

Available Metrics

metrics

OpenSMC Metrics — 12 standardized performance metrics for SMC benchmarking.

Functions

chattering_index(result, axis=0)

Chattering index: total variation of control signal TV(u).

Source code in opensmc/metrics.py
def chattering_index(result, axis=0):
    """Chattering index: total variation of control signal TV(u)."""
    u = result['u'][:, axis] if result['u'].ndim > 1 else result['u'].ravel()
    return float(np.sum(np.abs(np.diff(u))))

compute_all(result, axis=0)

Compute all performance metrics and return as dict.

Source code in opensmc/metrics.py
def compute_all(result, axis=0):
    """Compute all performance metrics and return as dict."""
    return {
        'rmse': rmse(result, axis),
        'mae': mae(result, axis),
        'ise': ise(result, axis),
        'iae': iae(result, axis),
        'itae': itae(result, axis),
        'settling_time': settling_time(result, axis),
        'overshoot': overshoot(result, axis),
        'control_effort': control_effort(result, axis if result['u'].shape[1] > 1 else 0),
        'chattering_index': chattering_index(result, axis if result['u'].shape[1] > 1 else 0),
        'reaching_time': reaching_time(result, axis if result['s'].shape[1] > 1 else 0),
        'ss_error': ss_error(result, axis),
        'steady_state_error': ss_error(result, axis),
        'max_swing': max_swing(result, min(axis, result['x'].shape[1] - 1)),
        'residual_swing': residual_swing(result, min(axis, result['x'].shape[1] - 1)),
    }

control_effort(result, axis=None)

Total control effort: integral of |u|^2.

Source code in opensmc/metrics.py
def control_effort(result, axis=None):
    """Total control effort: integral of |u|^2."""
    u = result['u']
    t = result['t']
    dt = t[1] - t[0] if len(t) > 1 else 1.0
    if axis is not None:
        u = u[:, axis]
    return float(np.sum(u ** 2) * dt)

iae(result, axis=None)

Integral of absolute error.

Source code in opensmc/metrics.py
def iae(result, axis=None):
    """Integral of absolute error."""
    e = result['e']
    t = result['t']
    dt = t[1] - t[0] if len(t) > 1 else 1.0
    if axis is not None:
        e = e[:, axis]
    return float(np.sum(np.abs(e)) * dt)

ise(result, axis=None)

Integral of squared error.

Source code in opensmc/metrics.py
def ise(result, axis=None):
    """Integral of squared error."""
    e = result['e']
    t = result['t']
    dt = t[1] - t[0] if len(t) > 1 else 1.0
    if axis is not None:
        e = e[:, axis]
    return float(np.sum(e ** 2) * dt)

itae(result, axis=None)

Integral of time-weighted absolute error.

Source code in opensmc/metrics.py
def itae(result, axis=None):
    """Integral of time-weighted absolute error."""
    e = result['e']
    t = result['t']
    dt = t[1] - t[0] if len(t) > 1 else 1.0
    if axis is not None:
        e = e[:, axis]
    return float(np.sum(t * np.abs(e)) * dt)

mae(result, axis=None)

Mean absolute error.

Source code in opensmc/metrics.py
def mae(result, axis=None):
    """Mean absolute error."""
    e = result['e']
    if axis is not None:
        e = e[:, axis]
    return float(np.mean(np.abs(e)))

max_swing(result, axis=2)

Maximum swing angle (for crane/pendulum systems). Default: state index 2.

Source code in opensmc/metrics.py
def max_swing(result, axis=2):
    """Maximum swing angle (for crane/pendulum systems). Default: state index 2."""
    x = result['x'][:, axis]
    return float(np.max(np.abs(x)))

overshoot(result, axis=0)

Percent overshoot relative to reference.

Source code in opensmc/metrics.py
def overshoot(result, axis=0):
    """Percent overshoot relative to reference."""
    x = result['x'][:, axis]
    xref = result['xref'][:, axis]
    ref_final = xref[-1]
    x0 = x[0]
    if abs(ref_final - x0) < 1e-15:
        return 0.0
    peak = np.max(x) if ref_final > x0 else np.min(x)
    os = (peak - ref_final) / (ref_final - x0) * 100
    return float(max(0.0, os))

reaching_time(result, axis=0, tol=0.01)

Time for |s| to first enter the band |s| < tol.

Source code in opensmc/metrics.py
def reaching_time(result, axis=0, tol=0.01):
    """Time for |s| to first enter the band |s| < tol."""
    s = result['s'][:, axis] if result['s'].ndim > 1 else result['s'].ravel()
    t = result['t']
    for i in range(len(s)):
        if np.abs(s[i]) < tol:
            return float(t[i])
    return float(t[-1])

residual_swing(result, axis=2, tail_frac=0.2)

Residual swing in steady state.

Source code in opensmc/metrics.py
def residual_swing(result, axis=2, tail_frac=0.2):
    """Residual swing in steady state."""
    x = result['x'][:, axis]
    idx = int(len(x) * (1 - tail_frac))
    return float(np.max(np.abs(x[idx:])))

rmse(result, axis=None)

Root mean square error.

Source code in opensmc/metrics.py
def rmse(result, axis=None):
    """Root mean square error."""
    e = result['e']
    if axis is not None:
        e = e[:, axis]
    return float(np.sqrt(np.mean(e ** 2)))

settling_time(result, axis=0, tol_frac=0.02, threshold=None)

Settling time (2% criterion by default).

Time after which |e| stays within tol_frac * |e(0)| permanently.

Source code in opensmc/metrics.py
def settling_time(result, axis=0, tol_frac=0.02, threshold=None):
    """Settling time (2% criterion by default).

    Time after which |e| stays within tol_frac * |e(0)| permanently.
    """
    if threshold is not None:
        tol_frac = threshold
    e = result['e'][:, axis] if result['e'].ndim > 1 else result['e']
    t = result['t']
    tol = tol_frac * np.max(np.abs(e[:min(10, len(e))]))
    if tol < 1e-15:
        tol = 1e-6
    ts = t[-1]
    for j in range(len(t) - 1, -1, -1):
        if np.abs(e[j]) > tol:
            ts = t[min(j + 1, len(t) - 1)]
            break
    else:
        ts = t[0]
    return float(ts)

ss_error(result, axis=0, tail_frac=0.1)

Steady-state error: mean |e| over last tail_frac of simulation.

Source code in opensmc/metrics.py
def ss_error(result, axis=0, tail_frac=0.1):
    """Steady-state error: mean |e| over last tail_frac of simulation."""
    e = result['e'][:, axis] if result['e'].ndim > 1 else result['e'].ravel()
    idx = int(len(e) * (1 - tail_frac))
    return float(np.mean(np.abs(e[idx:])))

steady_state_error(result, axis=0, tail_frac=0.1)

Alias for ss_error.

Source code in opensmc/metrics.py
def steady_state_error(result, axis=0, tail_frac=0.1):
    """Alias for ss_error."""
    return ss_error(result, axis, tail_frac)