Skip to content

Performance issue and/or endless loop in periodic extrapolation #6

@rruusu

Description

@rruusu

Description

The while loops used to wrap the input u for periodic extrapolation in several functions like ModelicaTableAdditions_CombiTable1D_getValue() can cause severe performance issues and, in extreme cases, infinite loops.

Code Reference

In ModelicaTableAdditions_CombiTable1D_getValue():

/* Periodic extrapolation */
if (tableID->extrapolation == PERIODIC) {
    const double T = uMax - uMin;

    if (u < uMin) {
        do {
            u += T;
        } while (u < uMin);
    }
    else if (u > uMax) {
        do {
            u -= T;
        } while (u > uMax);
    }
    last = findRowIndex(table, nRow, nCol, tableID->last, u);
    tableID->last = last;
}

Impact & Root Cause

  1. Performance Bottleneck: With a large value for u, the loop can take a significant amount of time. This can happen during step-size adjustments even in well-behaved models.
  2. Infinite Loop: As u and T are double-precision floats, they are limited by a 53-bit mantissa. If the absolute value of u is larger than T * 2^53, adding or subtracting T has no effect on the value of u, resulting in an endless loop that will freeze the whole simulation.

Proposed Solution

This O(n) logic can be replaced with an O(1) modulo operation using fmod. This eliminates both the performance penalty and the floating-point infinite loop risk.

For example:

/* Periodic extrapolation */
if (tableID->extrapolation == PERIODIC) {
    const double T = uMax - uMin;
    
    if (u < uMin || u > uMax) {
        double rem = fmod(u - uMin, T);
        /* fmod can return negative values, so adjust to strictly positive modulo */
        if (rem < 0.0) {
            rem += T;
        }
        u = uMin + rem;
    }
    
    last = findRowIndex(table, nRow, nCol, tableID->last, u);
    tableID->last = last;
}

Mitigation

This loop can be avoided in Modelica by adding the modulus operation to the assignment of the table input u directly.

From:

table.u = <equation for u>;

To:;

table.u = table.u_min + mod(<equation_for_u>, table.u_max - table.u_min);

If the table is continuously periodic, a noEvent(...) can be added around the modulus to avoid triggering of unnecessary events.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions