TimeTable

block TimeTable "Generate a (possibly discontinuous) signal by linear interpolation in a table"
    parameter Real table[:,2] = fill(0, 0, 2) "Table matrix (time = first column; e.g., table=[0, 0; 1, 1; 2, 4])"
        annotation (Dialog(groupImage = "modelica://Modelica/Resources/Images/Blocks/Sources/TimeTable.png"));
    parameter Modelica.SIunits.Time timeScale(min = Modelica.Constants.eps) = 1 "Time scale of first table column"
        annotation (Evaluate = true);

    extends Interfaces.SignalSource;

    parameter Modelica.SIunits.Time shiftTime = startTime "Shift time of first table column";
protected
    Real a "Interpolation coefficient a of actual interval (y=a*x+b)";
    Real b "Interpolation coefficient b of actual interval (y=a*x+b)";
    Integer last(start = 1) "Last used lower grid index";
    discrete SIunits.Time nextEvent(start = 0, fixed = true) "Next event instant";
    discrete Real nextEventScaled(start = 0, fixed = true) "Next scaled event instant";
    Real timeScaled "Scaled time";

    function getInterpolationCoefficients "Determine interpolation coefficients and next time event"
        extends Modelica.Icons.Function;

        input Real table[:,2] "Table for interpolation";
        input Real offset "y-offset";
        input Real startTimeScaled "Scaled time-offset";
        input Real timeScaled "Actual scaled time instant";
        input Integer last "Last used lower grid index";
        input Real TimeEps "Relative epsilon to check for identical time instants";
        input Real shiftTimeScaled "Time shift";
        output Real a "Interpolation coefficient a (y=a*x + b)";
        output Real b "Interpolation coefficient b (y=a*x + b)";
        output Real nextEventScaled "Next scaled event instant";
        output Integer next "New lower grid index";
    protected
        Integer columns = 2 "Column to be interpolated";
        Integer ncol = 2 "Number of columns to be interpolated";
        Integer nrow = size(table, 1) "Number of table rows";
        Integer next0;
        Real tp;
        Real dt;
    algorithm
        next := last;
        nextEventScaled := timeScaled - TimeEps * abs(timeScaled);
        tp := timeScaled + TimeEps * abs(timeScaled);
        if tp < startTimeScaled then 
            nextEventScaled := startTimeScaled;
            a := 0;
            b := offset;
        elseif nrow < 2 then 
            a := 0;
            b := offset + table[1,columns];
        else 
            tp := tp - shiftTimeScaled;
            while next < nrow and table[next,1] <= tp loop
                next := next + 1;
            end while;
            if next < nrow then 
                nextEventScaled := shiftTimeScaled + table[next,1];
            end if;
            if next == 1 then 
                next := 2;
            end if;
            next0 := next - 1;
            dt := table[next,1] - table[next0,1];
            if dt <= TimeEps * abs(table[next,1]) then 
                a := 0;
                b := offset + table[next,columns];
            else 
                a := (table[next,columns] - table[next0,columns]) / dt;
                b := offset + table[next0,columns] - a * table[next0,1];
            end if;
        end if;
        b := b - a * shiftTimeScaled;
    end getInterpolationCoefficients;
algorithm
    if noEvent(1 < size(table, 1)) then 
        assert(not (0 < table[1,1] or table[1,1] < 0), "The first point in time has to be set to 0, but is table[1,1] = " + String(table[1,1]));
    end if;
    when {pre(nextEvent) <= time, initial()} then 
        (a,b,nextEventScaled,last) := getInterpolationCoefficients(table, offset, startTime / timeScale, timeScaled, last, 100 * Modelica.Constants.eps, shiftTime / timeScale);
        nextEvent := nextEventScaled * timeScale;
    end when;
equation
    assert(0 < size(table, 1), "No table values defined.");
    y = a * timeScaled + b;
    timeScaled = time / timeScale;

    annotation (
        Icon(
            coordinateSystem(
                preserveAspectRatio = true,
                extent = {
                    {-100, -100}, 
                    {100, 100}}),
            graphics = {
                Line(
                    points = {
                        {-80, 68}, 
                        {-80, -80}},
                    color = {192, 192, 192}), 
                Polygon(
                    points = {
                        {-80, 90}, 
                        {-88, 68}, 
                        {-72, 68}, 
                        {-80, 90}},
                    lineColor = {192, 192, 192},
                    fillColor = {192, 192, 192},
                    fillPattern = FillPattern.Solid), 
                Line(
                    points = {
                        {-90, -70}, 
                        {82, -70}},
                    color = {192, 192, 192}), 
                Polygon(
                    points = {
                        {90, -70}, 
                        {68, -62}, 
                        {68, -78}, 
                        {90, -70}},
                    lineColor = {192, 192, 192},
                    fillColor = {192, 192, 192},
                    fillPattern = FillPattern.Solid), 
                Rectangle(
                    extent = {
                        {-48, 70}, 
                        {2, -50}},
                    lineColor = {255, 255, 255},
                    fillColor = {192, 192, 192},
                    fillPattern = FillPattern.Solid), 
                Line(points = {
                    {-48, -50}, 
                    {-48, 70}, 
                    {52, 70}, 
                    {52, -50}, 
                    {-48, -50}, 
                    {-48, -20}, 
                    {52, -20}, 
                    {52, 10}, 
                    {-48, 10}, 
                    {-48, 40}, 
                    {52, 40}, 
                    {52, 70}, 
                    {2, 70}, 
                    {2, -51}}), 
                Text(
                    extent = {
                        {-150, -150}, 
                        {150, -110}},
                    textString = "offset=%offset")}),
        Diagram(
            coordinateSystem(
                preserveAspectRatio = true,
                extent = {
                    {-100, -100}, 
                    {100, 100}}),
            graphics = {
                Polygon(
                    points = {
                        {-80, 90}, 
                        {-85, 68}, 
                        {-74, 68}, 
                        {-80, 90}},
                    lineColor = {95, 95, 95},
                    fillColor = {95, 95, 95},
                    fillPattern = FillPattern.Solid), 
                Line(
                    points = {
                        {-80, 68}, 
                        {-80, -80}},
                    color = {95, 95, 95}), 
                Line(
                    points = {
                        {-90, -70}, 
                        {82, -70}},
                    color = {95, 95, 95}), 
                Polygon(
                    points = {
                        {88, -70}, 
                        {68, -65}, 
                        {68, -74}, 
                        {88, -70}},
                    lineColor = {95, 95, 95},
                    fillColor = {95, 95, 95},
                    fillPattern = FillPattern.Solid), 
                Rectangle(
                    extent = {
                        {-20, 90}, 
                        {30, -30}},
                    lineColor = {255, 255, 255},
                    fillColor = {192, 192, 192},
                    fillPattern = FillPattern.Solid), 
                Line(points = {
                    {-20, -30}, 
                    {-20, 90}, 
                    {80, 90}, 
                    {80, -30}, 
                    {-20, -30}, 
                    {-20, 0}, 
                    {80, 0}, 
                    {80, 30}, 
                    {-20, 30}, 
                    {-20, 60}, 
                    {80, 60}, 
                    {80, 90}, 
                    {30, 90}, 
                    {30, -31}}), 
                Text(
                    extent = {
                        {-70, -42}, 
                        {-32, -54}},
                    textString = "offset"), 
                Polygon(
                    points = {
                        {-31, -30}, 
                        {-33, -40}, 
                        {-28, -40}, 
                        {-31, -30}},
                    lineColor = {95, 95, 95},
                    fillColor = {95, 95, 95},
                    fillPattern = FillPattern.Solid), 
                Polygon(
                    points = {
                        {-31, -70}, 
                        {-34, -60}, 
                        {-29, -60}, 
                        {-31, -70}, 
                        {-31, -70}},
                    lineColor = {95, 95, 95},
                    fillColor = {95, 95, 95},
                    fillPattern = FillPattern.Solid), 
                Line(
                    points = {
                        {-31, -32}, 
                        {-31, -70}},
                    color = {95, 95, 95}), 
                Line(
                    points = {
                        {-20, -30}, 
                        {-20, -70}},
                    color = {95, 95, 95}), 
                Text(
                    extent = {
                        {-38, -73}, 
                        {8, -83}},
                    textString = "startTime"), 
                Line(
                    points = {
                        {-20, -30}, 
                        {-80, -30}},
                    color = {95, 95, 95}), 
                Text(
                    extent = {
                        {-76, 93}, 
                        {-44, 75}},
                    textString = "y"), 
                Text(
                    extent = {
                        {66, -78}, 
                        {90, -88}},
                    textString = "time"), 
                Text(
                    extent = {
                        {-15, 83}, 
                        {24, 68}},
                    textString = "time"), 
                Text(
                    extent = {
                        {33, 83}, 
                        {76, 67}},
                    textString = "y")}),
        Documentation(
            info = "<html>\n<p>\nThis block generates an output signal by <strong>linear interpolation</strong> in\na table. The time points and function values are stored in a matrix\n<strong>table[i,j]</strong>, where the first column table[:,1] contains the\ntime points and the second column contains the data to be interpolated.\nThe table interpolation has the following properties:\n</p>\n<ul>\n<li>The interpolation interval is found by a linear search where the interval used in the\n    last call is used as start interval.</li>\n<li>The time points need to be <strong>monotonically increasing</strong>.</li>\n<li><strong>Discontinuities</strong> are allowed, by providing the same\n    time point twice in the table.</li>\n<li>Values <strong>outside</strong> of the table range, are computed by\n    <strong>extrapolation</strong> through the last or first two points of the\n    table.</li>\n<li>If the table has only <strong>one row</strong>, no interpolation is performed and\n    the function value is just returned independently of the actual time instant.</li>\n<li>Via parameters <strong>shiftTime</strong> and <strong>offset</strong> the curve defined\n    by the table can be shifted both in time and in the ordinate value.\n    The time instants stored in the table are therefore <strong>relative</strong>\n    to <strong>shiftTime</strong>.</li>\n<li>If time &lt; startTime, no interpolation is performed and the offset\n    is used as ordinate value for the output.</li>\n<li>If the table has more than one row, the first point in time <strong>always</strong> has to be set to <strong>0</strong>, e.g.,\n    <strong>table=[1,1;2,2]</strong> is <strong>illegal</strong>. If you want to\n    shift the time table in time use the <strong>shiftTime</strong> parameter instead.</li>\n<li>The table is implemented in a numerically sound way by\n    generating <strong>time events</strong> at interval boundaries.\n    This generates continuously differentiable values for the integrator.</li>\n<li>Via parameter <strong>timeScale</strong> the first column of the table array can\n    be scaled, e.g., if the table array is given in hours (instead of seconds)\n    <strong>timeScale</strong> shall be set to 3600.</li>\n</ul>\n<p>\nExample:\n</p>\n<pre>\n   table = [0, 0;\n            1, 0;\n            1, 1;\n            2, 4;\n            3, 9;\n            4, 16];\nIf, e.g., time = 1.0, the output y =  0.0 (before event), 1.0 (after event)\n    e.g., time = 1.5, the output y =  2.5,\n    e.g., time = 2.0, the output y =  4.0,\n    e.g., time = 5.0, the output y = 23.0 (i.e., extrapolation).\n</pre>\n\n<p>\n<img src=\"modelica://Modelica/Resources/Images/Blocks/Sources/TimeTable.png\"\n     alt=\"TimeTable.png\">\n</p>\n\n</html>",
            revisions = "<html>\n<h4>Release Notes</h4>\n<ul>\n<li><em>Oct. 21, 2002</em>\n       by Christian Schweiger:<br>\n       Corrected interface from\n<pre>\n    parameter Real table[:, :]=[0, 0; 1, 1; 2, 4];\n</pre>\n       to\n<pre>\n    parameter Real table[:, <strong>2</strong>]=[0, 0; 1, 1; 2, 4];\n</pre>\n       </li>\n<li><em>Nov. 7, 1999</em>\n       by <a href=\"http://www.robotic.dlr.de/Martin.Otter/\">Martin Otter</a>:<br>\n       Realized.</li>\n</ul>\n</html>"));
end TimeTable;