CombiTimeTable

block CombiTimeTable "Table look-up with respect to time and linear/periodic extrapolation methods (data from matrix/file)"
    import Modelica.Blocks.Tables.Internal;

    extends Modelica.Blocks.Interfaces.MO(final nout = max([size(columns, 1); size(offset, 1)]));

    parameter Boolean tableOnFile = false "= true, if table is defined on file or in function usertab"
        annotation (Dialog(group = "Table data definition"));
    parameter Real table[:,:] = fill(0, 0, 2) "Table matrix (time = first column; e.g., table=[0, 0; 1, 1; 2, 4])"
        annotation (Dialog(
            group = "Table data definition",
            enable = not tableOnFile));
    parameter String tableName = "NoName" "Table name on file or in function usertab (see docu)"
        annotation (Dialog(
            group = "Table data definition",
            enable = tableOnFile));
    parameter String fileName = "NoName" "File where matrix is stored"
        annotation (Dialog(
            group = "Table data definition",
            enable = tableOnFile,
            loadSelector(
                filter = "Text files (*.txt);;MATLAB MAT-files (*.mat)",
                caption = "Open file in which table is present")));
    parameter Boolean verboseRead = true "= true, if info message that file is loading is to be printed"
        annotation (Dialog(
            group = "Table data definition",
            enable = tableOnFile));
    parameter Integer columns[:] = 2:size(table, 2) "Columns of table to be interpolated"
        annotation (Dialog(
            group = "Table data interpretation",
            groupImage = "modelica://Modelica/Resources/Images/Blocks/Sources/CombiTimeTable.png"));
    parameter Modelica.Blocks.Types.Smoothness smoothness = Modelica.Blocks.Types.Smoothness.LinearSegments "Smoothness of table interpolation"
        annotation (Dialog(group = "Table data interpretation"));
    parameter Modelica.Blocks.Types.Extrapolation extrapolation = Modelica.Blocks.Types.Extrapolation.LastTwoPoints "Extrapolation of data outside the definition range"
        annotation (Dialog(group = "Table data interpretation"));
    parameter Modelica.SIunits.Time timeScale(min = Modelica.Constants.eps) = 1 "Time scale of first table column"
        annotation (
            Dialog(group = "Table data interpretation"),
            Evaluate = true);
    parameter Real offset[:] = {0} "Offsets of output signals"
        annotation (Dialog(group = "Table data interpretation"));
    parameter Modelica.SIunits.Time startTime = 0 "Output = offset for time < startTime"
        annotation (Dialog(group = "Table data interpretation"));
    parameter Modelica.SIunits.Time shiftTime = startTime "Shift time of first table column"
        annotation (Dialog(group = "Table data interpretation"));
    parameter Modelica.Blocks.Types.TimeEvents timeEvents = Modelica.Blocks.Types.TimeEvents.Always "Time event handling of table interpolation"
        annotation (Dialog(
            group = "Table data interpretation",
            enable = smoothness == Modelica.Blocks.Types.Smoothness.LinearSegments));
    parameter Boolean verboseExtrapolation = false "= true, if warning messages are to be printed if time is outside the table definition range"
        annotation (Dialog(
            group = "Table data interpretation",
            enable = extrapolation == Modelica.Blocks.Types.Extrapolation.LastTwoPoints or extrapolation == Modelica.Blocks.Types.Extrapolation.HoldLastPoint));
    final parameter Modelica.SIunits.Time t_min = t_minScaled * timeScale "Minimum abscissa value defined in table";
    final parameter Modelica.SIunits.Time t_max = t_maxScaled * timeScale "Maximum abscissa value defined in table";
    final parameter Real t_minScaled = Internal.getTimeTableTmin(tableID) "Minimum (scaled) abscissa value defined in table";
    final parameter Real t_maxScaled = Internal.getTimeTableTmax(tableID) "Maximum (scaled) abscissa value defined in table";
protected
    final parameter Real p_offset[nout] = if size(offset, 1) == 1 then ones(nout) * offset[1] else offset "Offsets of output signals";
    parameter Modelica.Blocks.Types.ExternalCombiTimeTable tableID = Modelica.Blocks.Types.ExternalCombiTimeTable(if tableOnFile then tableName else "NoName", if tableOnFile and fileName <> "NoName" and not Modelica.Utilities.Strings.isEmpty(fileName) then fileName else "NoName", table, startTime / timeScale, columns, smoothness, extrapolation, shiftTime / timeScale, if smoothness == Modelica.Blocks.Types.Smoothness.LinearSegments then timeEvents else if smoothness == Modelica.Blocks.Types.Smoothness.ConstantSegments then Modelica.Blocks.Types.TimeEvents.Always else Modelica.Blocks.Types.TimeEvents.NoTimeEvents, if tableOnFile then verboseRead else false) "External table object";
    discrete Modelica.SIunits.Time nextTimeEvent(start = 0, fixed = true) "Next time event instant";
    discrete Real nextTimeEventScaled(start = 0, fixed = true) "Next scaled time event instant";
    Real timeScaled "Scaled time";

    function readTableData = Modelica.Blocks.Tables.Internal.readTimeTableData "Read table data from text or MATLAB MAT-file";
equation
    if verboseExtrapolation and (extrapolation == Modelica.Blocks.Types.Extrapolation.LastTwoPoints or extrapolation == Modelica.Blocks.Types.Extrapolation.HoldLastPoint) then 
        assert(noEvent(t_min <= time), "\nExtrapolation warning: Time (=" + String(time) + ") must be greater or equal\nthan the minimum abscissa value t_min (=" + String(t_min) + ") defined in the table.\n", level = AssertionLevel.warning);
        assert(noEvent(time <= t_max), "\nExtrapolation warning: Time (=" + String(time) + ") must be less or equal\nthan the maximum abscissa value t_max (=" + String(t_max) + ") defined in the table.\n", level = AssertionLevel.warning);
    end if;
    if tableOnFile then 
        assert(tableName <> "NoName", "tableOnFile = true and no table name given");
    else 
        assert(0 < size(table, 1) and 0 < size(table, 2), "tableOnFile = false and parameter table is an empty matrix");
    end if;
    if smoothness == Modelica.Blocks.Types.Smoothness.ConstantSegments then 
        for i in 1:nout loop
            y[i] = p_offset[i] + Internal.getTimeTableValueNoDer(tableID, i, timeScaled, nextTimeEventScaled, pre(nextTimeEventScaled));
        end for;
    else 
        for i in 1:nout loop
            y[i] = p_offset[i] + Internal.getTimeTableValue(tableID, i, timeScaled, nextTimeEventScaled, pre(nextTimeEventScaled));
        end for;
    end if;
    when {pre(nextTimeEvent) <= time, initial()} then 
        nextTimeEventScaled = Internal.getNextTimeEvent(tableID, timeScaled);
        nextTimeEvent = if nextTimeEventScaled < Modelica.Constants.inf then nextTimeEventScaled * timeScale else Modelica.Constants.inf;
    end when;
    timeScaled = time / timeScale;

    annotation (
        Documentation(
            info = "<html>\n<p>\nThis block generates an output signal y[:] by <strong>constant</strong>,\n<strong>linear</strong> or <strong>cubic Hermite spline interpolation</strong>\nin a 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 other columns contain the data to be interpolated.\n</p>\n\n<p>\n<img src=\"modelica://Modelica/Resources/Images/Blocks/Sources/CombiTimeTable.png\"\n     alt=\"CombiTimeTable.png\">\n</p>\n\n<p>\nVia parameter <strong>columns</strong> it can be defined which columns of the\ntable are interpolated. If, e.g., columns={2,4}, it is assumed that\n2 output signals are present and that the first output is computed\nby interpolation of column 2 and the second output is computed\nby interpolation of column 4 of the table matrix.\nThe table interpolation has the following properties:\n</p>\n<ul>\n<li>The interpolation interval is found by a binary 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>strictly increasing</strong> for cubic Hermite\n    spline interpolation, otherwise <strong>monotonically increasing</strong>.</li>\n<li><strong>Discontinuities</strong> are allowed for (constant or) linear interpolation,\n    by providing the same time point twice in the table.</li>\n<li>Via parameter <strong>smoothness</strong> it is defined how the data is interpolated:\n<pre>\n  smoothness = 1: Linear interpolation\n             = 2: Akima interpolation: Smooth interpolation by cubic Hermite\n                  splines such that der(y) is continuous, also if extrapolated.\n             = 3: Constant segments\n             = 4: Fritsch-Butland interpolation: Smooth interpolation by cubic\n                  Hermite splines such that y preserves the monotonicity and\n                  der(y) is continuous, also if extrapolated.\n             = 5: Steffen interpolation: Smooth interpolation by cubic Hermite\n                  splines such that y preserves the monotonicity and der(y)\n                  is continuous, also if extrapolated.\n</pre></li>\n<li>Values <strong>outside</strong> of the table range, are computed by\n    extrapolation according to the setting of parameter <strong>extrapolation</strong>:\n<pre>\n  extrapolation = 1: Hold the first or last value of the table,\n                     if outside of the table scope.\n                = 2: Extrapolate by using the derivative at the first/last table\n                     points if outside of the table scope.\n                     (If smoothness is LinearSegments or ConstantSegments\n                     this means to extrapolate linearly through the first/last\n                     two table points.).\n                = 3: Periodically repeat the table data (periodical function).\n                = 4: No extrapolation, i.e. extrapolation triggers an error\n</pre></li>\n<li>If the table has only <strong>one row</strong>, no interpolation is performed and\n    the table values of this row are just returned.</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 all outputs.</li>\n<li>The table is implemented in a numerically sound way by\n    generating <strong>time events</strong> at interval boundaries, in case of\n    interpolation by linear segments.\n    This generates continuously differentiable values for the integrator.\n    Via parameter <strong>timeEvents</strong> it is defined how the time events are generated:\n<pre>\n  timeEvents = 1: Always generate time events at interval boundaries\n             = 2: Generate time events at discontinuities (defined by duplicated sample points)\n             = 3: No time events at interval boundaries\n</pre>\n    For interpolation by constant segments time events are always generated at interval boundaries.\n    For smooth interpolation by cubic Hermite splines no time events are generated at interval boundaries.</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<li>For special applications it is sometimes needed to know the minimum\n    and maximum time instant defined in the table as a parameter. For this\n    reason parameters <strong>t_min</strong>/<strong>t_minScaled</strong> and\n    <strong>t_max</strong>/<strong>t_maxScaled</strong> are provided and can be\n    accessed from the outside of the table object. Whereas <strong>t_min</strong> and\n    <strong>t_max</strong> define the scaled abscissa values (using parameter\n    <strong>timeScale</strong>) in SIunits.Time, <strong>t_minScaled</strong> and\n    <strong>t_maxScaled</strong> define the unitless original abscissa values of\n    the table.</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];\n   extrapolation = 2 (default), timeEvents = 2\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 via last 2 points).\n</pre>\n<p>\nThe table matrix can be defined in the following ways:\n</p>\n<ol>\n<li>Explicitly supplied as <strong>parameter matrix</strong> \"table\",\n    and the other parameters have the following values:\n<pre>\n   tableName is \"NoName\" or has only blanks,\n   fileName  is \"NoName\" or has only blanks.\n</pre></li>\n<li><strong>Read</strong> from a <strong>file</strong> \"fileName\" where the matrix is stored as\n    \"tableName\". Both text and MATLAB MAT-file format is possible.\n    (The text format is described below).\n    The MAT-file format comes in four different versions: v4, v6, v7 and v7.3.\n    The library supports at least v4, v6 and v7 whereas v7.3 is optional.\n    It is most convenient to generate the MAT-file from FreeMat or MATLAB&reg;\n    by command\n<pre>\n   save tables.mat tab1 tab2 tab3\n</pre>\n    or Scilab by command\n<pre>\n   savematfile tables.mat tab1 tab2 tab3\n</pre>\n    when the three tables tab1, tab2, tab3 should be used from the model.<br>\n    Note, a fileName can be defined as URI by using the helper function\n    <a href=\"modelica://Modelica.Utilities.Files.loadResource\">loadResource</a>.</li>\n<li>Statically stored in function \"usertab\" in file \"usertab.c\".\n    The matrix is identified by \"tableName\". Parameter\n    fileName = \"NoName\" or has only blanks. Row-wise storage is always to be\n    preferred as otherwise the table is reallocated and transposed.</li>\n</ol>\n<p>\nWhen the constant \"NO_FILE_SYSTEM\" is defined, all file I/O related parts of the\nsource code are removed by the C-preprocessor, such that no access to files takes place.\n</p>\n<p>\nIf tables are read from a text file, the file needs to have the\nfollowing structure (\"-----\" is not part of the file content):\n</p>\n<pre>\n-----------------------------------------------------\n#1\ndouble tab1(6,2)   # comment line\n  0   0\n  1   0\n  1   1\n  2   4\n  3   9\n  4  16\ndouble tab2(6,2)   # another comment line\n  0   0\n  2   0\n  2   2\n  4   8\n  6  18\n  8  32\n-----------------------------------------------------\n</pre>\n<p>\nNote, that the first two characters in the file need to be\n\"#1\" (a line comment defining the version number of the file format).\nAfterwards, the corresponding matrix has to be declared\nwith type (= \"double\" or \"float\"), name and actual dimensions.\nFinally, in successive rows of the file, the elements of the matrix\nhave to be given. The elements have to be provided as a sequence of\nnumbers in row-wise order (therefore a matrix row can span several\nlines in the file and need not start at the beginning of a line).\nNumbers have to be given according to C syntax (such as 2.3, -2, +2.e4).\nNumber separators are spaces, tab (\\t), comma (,), or semicolon (;).\nSeveral matrices may be defined one after another. Line comments start\nwith the hash symbol (#) and can appear everywhere.\nText files should either be ASCII or UTF-8 encoded, where UTF-8 encoded strings are only allowed in line comments and an optional UTF-8 BOM at the start of the text file is ignored.\nOther characters, like trailing non comments, are not allowed in the file.\n</p>\n<p>\nMATLAB is a registered trademark of The MathWorks, Inc.\n</p>\n</html>",
            revisions = "<html>\n<p><strong>Release Notes:</strong></p>\n<ul>\n<li><em>April 09, 2013</em>\n       by Thomas Beutlich:<br>\n       Implemented as external object.</li>\n<li><em>March 31, 2001</em>\n       by <a href=\"http://www.robotic.dlr.de/Martin.Otter/\">Martin Otter</a>:<br>\n       Used CombiTableTime as a basis and added the\n       arguments <strong>extrapolation, columns, startTime</strong>.\n       This allows periodic function definitions.</li>\n</ul>\n</html>"),
        Icon(
            coordinateSystem(
                preserveAspectRatio = true,
                extent = {
                    {-100, -100}, 
                    {100, 100}}),
            graphics = {
                Polygon(
                    lineColor = {192, 192, 192},
                    fillColor = {192, 192, 192},
                    fillPattern = FillPattern.Solid,
                    points = {
                        {-80, 90}, 
                        {-88, 68}, 
                        {-72, 68}, 
                        {-80, 90}}), 
                Line(
                    points = {
                        {-80, 68}, 
                        {-80, -80}},
                    color = {192, 192, 192}), 
                Line(
                    points = {
                        {-90, -70}, 
                        {82, -70}},
                    color = {192, 192, 192}), 
                Polygon(
                    lineColor = {192, 192, 192},
                    fillColor = {192, 192, 192},
                    fillPattern = FillPattern.Solid,
                    points = {
                        {90, -70}, 
                        {68, -62}, 
                        {68, -78}, 
                        {90, -70}}), 
                Rectangle(
                    lineColor = {255, 255, 255},
                    fillColor = {255, 215, 136},
                    fillPattern = FillPattern.Solid,
                    extent = {
                        {-48, -50}, 
                        {2, 70}}), 
                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}})}),
        Diagram(
            coordinateSystem(
                preserveAspectRatio = true,
                extent = {
                    {-100, -100}, 
                    {100, 100}}),
            graphics = {
                Polygon(
                    points = {
                        {-80, 90}, 
                        {-88, 68}, 
                        {-72, 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 = {
                        {90, -70}, 
                        {68, -62}, 
                        {68, -78}, 
                        {90, -70}},
                    lineColor = {95, 95, 95},
                    fillColor = {95, 95, 95},
                    fillPattern = FillPattern.Solid), 
                Rectangle(
                    extent = {
                        {-20, 90}, 
                        {20, -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}, 
                    {20, 90}, 
                    {20, -30}}), 
                Text(
                    extent = {
                        {-71, -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, -31}, 
                        {-31, -70}},
                    color = {95, 95, 95}), 
                Line(
                    points = {
                        {-20, -30}, 
                        {-20, -70}},
                    color = {95, 95, 95}), 
                Text(
                    extent = {
                        {-42, -74}, 
                        {6, -84}},
                    textString = "startTime"), 
                Line(
                    points = {
                        {-20, -30}, 
                        {-80, -30}},
                    color = {95, 95, 95}), 
                Text(
                    extent = {
                        {-73, 93}, 
                        {-44, 74}},
                    textString = "y"), 
                Text(
                    extent = {
                        {66, -81}, 
                        {92, -92}},
                    textString = "time"), 
                Text(
                    extent = {
                        {-19, 83}, 
                        {20, 68}},
                    textString = "time"), 
                Text(
                    extent = {
                        {21, 82}, 
                        {50, 68}},
                    textString = "y[1]"), 
                Line(points = {
                    {50, 90}, 
                    {50, -30}}), 
                Line(
                    points = {
                        {80, 0}, 
                        {100, 0}},
                    color = {0, 0, 255}), 
                Text(
                    extent = {
                        {34, -30}, 
                        {71, -42}},
                    textString = "columns",
                    lineColor = {0, 0, 255}), 
                Text(
                    extent = {
                        {51, 82}, 
                        {80, 68}},
                    textString = "y[2]")}));
end CombiTimeTable;