within Modelica;
package Math "Library of mathematical functions (e.g., sin, cos) and of functions operating on vectors and matrices"

  extends Modelica.Icons.Package;

package Vectors "Library of functions operating on vectors"
  extends Modelica.Icons.Package;

  function toString "Convert a real vector in to a string representation"
    extends Modelica.Icons.Function;
    import Modelica.Utilities.Strings;

    input Real v[:] "Real vector";
    input String name="" "Independent variable name used for printing";
    input Integer significantDigits=6
      "Number of significant digits that are shown";
    output String s="";
  protected
    String blanks=Strings.repeat(significantDigits);
    String space=Strings.repeat(8);
    Integer r=size(v, 1);

  algorithm
    if r == 0 then
      s := if name == "" then "[]" else name + " = []";
    else
      s := if name == "" then "\n" else "\n" + name + " = \n";
      for i in 1:r loop
        s := s + space;

        if v[i] >= 0 then
          s := s + " ";
        end if;
        s := s + String(v[i], significantDigits=significantDigits) +
          Strings.repeat(significantDigits + 8 - Strings.length(String(abs(v[i]))));

        s := s + "\n";
      end for;

    end if;
Vectors.toString(v,name=\"\",significantDigits=6);

Description

The function call \"Vectors.toString(v)\" returns the string representation of vector v.
With the optional arguments \"name\" and \"significantDigits\" a name and the number of the digits are defined.
The default values of \"name\" and \"significantDigits\" are \"\" and 6 respectively. If name==\"\" (empty string) then the prefix \"<name> =\" is left out at the output-string.

Example

v = {2.12, -4.34, -2.56, -1.67};
toString(v);
                       // = \"
                       //           2.12
                       //          -4.34
                       //          -2.56
                       //          -1.67\"
toString(v,\"vv\",1);
                       // = \"vv =
                       //           2
                       //          -4
                       //          -3
                       //          -2\"

See also

Matrices.toString,

", revisions="

"));
  end toString;

  function isEqual "Determine if two Real vectors are numerically identical"
    extends Modelica.Icons.Function;
    input Real v1[:] "First vector";
    input Real v2[:] "Second vector (may have different length as v1)";
    input Real eps(min=0) = 0
      "Two elements e1 and e2 of the two vectors are identical if abs(e1-e2) <= eps";
    output Boolean result
      "= true, if vectors have the same length and the same elements";

  protected
    Integer n=size(v1, 1) "Dimension of vector v1";
    Integer i=1;
  algorithm
    result := false;
    if size(v2, 1) == n then
      result := true;
      while i <= n loop
        if abs(v1[i] - v2[i]) > eps then
          result := false;
          i := n;
        end if;
        i := i + 1;
      end while;
    end if;
Vectors.isEqual(v1, v2, eps=0);

Description

The function call \"Vectors.isEqual(v1, v2)\" returns true,
if the two Real vectors v1 and v2 have the same dimensions and
the same elements. Otherwise the function
returns false. Two elements e1 and e2 of the two vectors
are checked on equality by the test \"abs(e1-e2) ≤ eps\", where \"eps\"
can be provided as third argument of the function. Default is \"eps = 0\".

Example

  Real v1[3] = {1, 2, 3};
  Real v2[4] = {1, 2, 3, 4};
  Real v3[3] = {1, 2, 3.0001};
  Boolean result;
algorithm
  result := Vectors.isEqual(v1,v2);     // = false
  result := Vectors.isEqual(v1,v3);     // = false
  result := Vectors.isEqual(v1,v1);     // = true
  result := Vectors.isEqual(v1,v3,0.1); // = true

See also

Vectors.find,
Matrices.isEqual,
Strings.isEqual

"));
  end isEqual;

  function norm "Return the p-norm of a vector"
    extends Modelica.Icons.Function;
    input Real v[:] "Real vector";
    input Real p(min=1) = 2
      "Type of p-norm (often used: 1, 2, or Modelica.Constants.inf)";
    output Real result=0.0 "p-norm of vector v";
  protected
    Real eps = 10*Modelica.Constants.eps;
  algorithm
   if size(v,1) > 0 then
    if p >= 2-eps and p <= 2+eps then
      result := sqrt(v*v);
    elseif p >= Modelica.Constants.inf then
      result := max(abs(v));
    elseif p >= 1-eps and p <= 1+eps then
      result := sum(abs(v));
    elseif p >= 1 then
      result := (sum(abs(v[i])^p for i in 1:size(v, 1)))^(1/p);
    else
      assert(false, "Optional argument \"p\" (= " + String(p) + ") of function \"norm\" >= 1 required");
    end if;
   end if;
Vectors.norm(v,p=2);   // 1 ≤ p ≤ ∞

Description

The function call \"Vectors.norm(v)\" returns the
Euclidean norm \"sqrt(v*v)\" of vector v.
With the optional
second argument \"p\", any other p-norm can be computed:

Besides the Euclidean norm (p=2), also the 1-norm and the
infinity-norm are sometimes used:

1-norm
      = sum(abs(v))
      norm(v,1)

2-norm
      = sqrt(v*v)
      norm(v) or norm(v,2)

infinity-norm
      = max(abs(v))
      norm(v,Modelica.Constants.inf)

Note, for any vector norm the following inequality holds:

norm(v1+v2,p) ≤ norm(v1,p) + norm(v2,p)

Example

v = {2, -4, -2, -1};
norm(v,1);    // = 9
norm(v,2);    // = 5
norm(v);      // = 5
norm(v,10.5); // = 4.00052597412635
norm(v,Modelica.Constants.inf);  // = 4

See also

Matrices.norm

"));
  end norm;

  function length
    "Return length of a vector (better as norm(), if further symbolic processing is performed)"
    extends Modelica.Icons.Function;
    input Real v[:] "Real vector";
    output Real result "Length of vector v";
  algorithm
    result := sqrt(v*v);

Description

The function call \"Vectors.length(v)\" returns the
Euclidean length \"sqrt(v*v)\" of vector v.
The function call is equivalent to Vectors.norm(v). The advantage of
length(v) over norm(v) is that function length(..) is implemented
in one statement and therefore the function is usually automatically
inlined. Further symbolic processing is therefore possible, which is
not the case with function norm(..).

Example

v = {2, -4, -2, -1};
length(v);  // = 5

See also

Vectors.norm

"));
  end length;

  function normalize
    "Return normalized vector such that length = 1 and prevent zero-division for zero vector"
    extends Modelica.Icons.Function;
    input Real v[:] "Real vector";
    input Real eps(min=0.0)=100*Modelica.Constants.eps
      "if |v| < eps then result = v/eps";
    output Real result[size(v, 1)](each final unit="1") "Input vector v normalized to length=1";

  algorithm
    /* This function has the inline annotation. If the function is inlined:
     - "smooth(..)" defines how often the expression can be differentiated
       (if symbolic processing is performed).
  */
    result := smooth(0, if length(v) >= eps then v/length(v) else v/eps);
Vectors.normalize(v,eps=100*Modelica.Constants.eps);

Description

The function call \"Vectors.normalize(v)\" returns the
unit vector \"v/length(v)\" of vector v.
If length(v) is close to zero (more precisely, if length(v) < eps),
v/eps is returned in order to avoid
a division by zero. For many applications this is useful, because
often the unit vector e = v/length(v) is used to compute
a vector x*e, where the scalar x is in the order of length(v),
i.e., x*e is small, when length(v) is small and then
it is fine to replace e by v to avoid a division by zero.

Since the function has the \"Inline\" annotation, it
is usually inlined and symbolic processing is applied.

Example

normalize({1,2,3});  // = {0.267, 0.534, 0.802}
normalize({0,0,0});  // = {0,0,0}

See also

Vectors.length,
Vectors.normalizeWithAssert

"));
  end normalize;

function normalizeWithAssert
    "Return normalized vector such that length = 1 (trigger an assert for zero vector)"
  import Modelica.Math.Vectors.length;
  extends Modelica.Icons.Function;
  input Real v[:] "Real vector";
  output Real result[size(v, 1)] "Input vector v normalized to length=1";

algorithm
  assert(length(v) > 0.0, "Vector v={0,0,0} shall be normalized (= v/sqrt(v*v)), but this results in a division by zero.\nProvide a non-zero vector!");
  result := v/length(v);

Description

The function call \"Vectors.normalizeWithAssert(v)\" returns the
unit vector \"v/sqrt(v*v)\" of vector v.
If vector v is a zero vector, an assert is triggered.

Since the function has the \"Inline\" annotation, it
is usually inlined and symbolic processing is applied.

Example

normalizeWithAssert({1,2,3});  // = {0.267, 0.534, 0.802}
normalizeWithAssert({0,0,0});  // error (an assert is triggered)

See also

Vectors.length,
Vectors.normalize

"));
end normalizeWithAssert;

  function reverse "Reverse vector elements (e.g., v[1] becomes last element)"
    extends Modelica.Icons.Function;
    input Real v[:] "Real vector";
    output Real result[size(v, 1)] "Elements of vector v in reversed order";

  algorithm
    result := {v[end - i + 1] for i in 1:size(v, 1)};

Description

The function call \"Vectors.reverse(v)\" returns the
vector elements in reverse order.

Example

reverse({1,2,3,4});  // = {4,3,2,1}

"));
  end reverse;

  function sort "Sort elements of vector in ascending or descending order"
    extends Modelica.Icons.Function;
    input Real v[:] "Real vector to be sorted";
    input Boolean ascending=true
      "= true, if ascending order, otherwise descending order";
    output Real sorted_v[size(v, 1)]=v "Sorted vector";
    output Integer indices[size(v, 1)]=1:size(v, 1) "sorted_v = v[indices]";

    /* shellsort algorithm; should be improved later */
  protected
    Integer gap;
    Integer i;
    Integer j;
    Real wv;
    Integer wi;
    Integer nv=size(v, 1);
    Boolean swap;
  algorithm
    gap := div(nv, 2);

    while gap > 0 loop
      i := gap;
      while i < nv loop
        j := i - gap;
        if j >= 0 then
          if ascending then
            swap := sorted_v[j + 1] > sorted_v[j + gap + 1];
          else
            swap := sorted_v[j + 1] < sorted_v[j + gap + 1];
          end if;
        else
          swap := false;
        end if;

        while swap loop
          wv := sorted_v[j + 1];
          wi := indices[j + 1];
          sorted_v[j + 1] := sorted_v[j + gap + 1];
          sorted_v[j + gap + 1] := wv;
          indices[j + 1] := indices[j + gap + 1];
          indices[j + gap + 1] := wi;
          j := j - gap;
          if j >= 0 then
            if ascending then
              swap := sorted_v[j + 1] > sorted_v[j + gap + 1];
            else
              swap := sorted_v[j + 1] < sorted_v[j + gap + 1];
            end if;
          else
            swap := false;
          end if;
        end while;
        i := i + 1;
      end while;
      gap := div(gap, 2);
    end while;
(sorted_v, indices) = Vectors.sort(v, ascending=true);

Description

Function sort(..) sorts a Real vector v
in ascending order and returns the result in sorted_v.
If the optional argument \"ascending\" is false, the vector
is sorted in descending order. In the optional second
output argument the indices of the sorted vector with respect
to the original vector are given, such that sorted_v = v[indices].

Example

(v2, i2) := Vectors.sort({-1, 8, 3, 6, 2});
     -> v2 = {-1, 2, 3, 6, 8}
        i2 = {1, 5, 3, 4, 2}

"));
  end sort;

  function find "Find element in a vector"
    extends Modelica.Icons.Function;
    input Real e "Search for e";
    input Real v[:] "Real vector";
    input Real eps(min=0) = 0
      "Element e is equal to a element v[i] of vector v if abs(e-v[i]) <= eps";
    output Integer result
      "v[result] = e (first occurrence of e); result=0, if not found";
  protected
    Integer i;
  algorithm
    result := 0;
    i := 1;
    while i <= size(v, 1) loop
      if abs(v[i] - e) <= eps then
        result := i;
        i := size(v, 1) + 1;
      else
        i := i + 1;
      end if;
    end while;
Vectors.find(e, v, eps=0);

Description

The function call \"Vectors.find(e, v)\" returns the index of the first occurrence of input e in vector v.
The test of equality is performed by \"abs(e-v[i]) ≤ eps\", where \"eps\"
can be provided as third argument of the function. Default is \"eps = 0\".

Example

  Real v[3] = {1, 2, 3};
  Real e1 = 2;
  Real e2 = 3.01;
  Boolean result;
algorithm
  result := Vectors.find(e1,v);          // = 2
  result := Vectors.find(e2,v);          // = 0
  result := Vectors.find(e2,v,eps=0.1);  // = 3

See also

Vectors.isEqual

"));
  end find;

  function interpolate "Interpolate linearly in a vector"
    extends Modelica.Icons.Function;
    input Real x[:]
      "Abscissa table vector (strict monotonically increasing values required)";
    input Real y[size(x, 1)] "Ordinate table vector";
    input Real xi "Desired abscissa value";
    input Integer iLast=1 "Index used in last search";
    output Real yi "Ordinate value corresponding to xi";
    output Integer iNew=1 "xi is in the interval x[iNew] <= xi < x[iNew+1]";
  protected
    Integer i;
    Integer nx=size(x, 1);
    Real x1;
    Real x2;
    Real y1;
    Real y2;
  algorithm
    assert(nx > 0, "The table vectors must have at least 1 entry.");
    if nx == 1 then
      yi := y[1];
    else
      // Search interval
      i := min(max(iLast, 1), nx - 1);
      if xi >= x[i] then
        // search forward
        while i < nx and xi >= x[i] loop
          i := i + 1;
        end while;
        i := i - 1;
      else
        // search backward
        while i > 1 and xi < x[i] loop
          i := i - 1;
        end while;
      end if;

      // Get interpolation data
      x1 := x[i];
      x2 := x[i + 1];
      y1 := y[i];
      y2 := y[i + 1];

      assert(x2 > x1, "Abscissa table vector values must be increasing");
      // Interpolate
      yi := y1 + (y2 - y1)*(xi - x1)/(x2 - x1);
      iNew := i;
    end if;
(yi, iNew) = Vectors.interpolate(x,y,xi,iLast=1);

Description

The function call \"Vectors.interpolate(x,y,xi)\" interpolates
linearly in vectors
(x,y) and returns the value yi that corresponds to xi. Vector x[:] must consist
of monotonically increasing values. If xi < x[1] or > x[end], then
extrapolation takes places through the first or last two x[:] values, respectively.
If the x and y vectors have length 1, then always y[1] is returned.
The search for the interval x[iNew] ≤ xi < x[iNew+1] starts at the optional
input argument \"iLast\". The index \"iNew\" is returned as output argument.
The usage of \"iLast\" and \"iNew\" is useful to increase the efficiency of the call,
if many interpolations take place.
If x has two or more identical values then interpolation utilizes the x-value
with the largest index.

Example

  Real x1[:] = { 0,  2,  4,  6,  8, 10};
  Real x2[:] = { 1,  2,  3,  3,  4,  5};
  Real y[:]  = {10, 20, 30, 40, 50, 60};
algorithm
  (yi, iNew) := Vectors.interpolate(x1,y,5);  // yi = 35, iNew=3
  (yi, iNew) := Vectors.interpolate(x2,y,4);  // yi = 50, iNew=5
  (yi, iNew) := Vectors.interpolate(x2,y,3);  // yi = 40, iNew=4

"));
  end interpolate;

  function relNodePositions "Return vector of relative node positions (0..1)"
    extends Modelica.Icons.Function;
    input Integer nNodes
      "Number of nodes (including node at left and right position)";
    output Real xsi[nNodes] "Relative node positions";
  protected
    Real delta;
  algorithm
    if nNodes >= 1 then
      xsi[1] := 0;
    end if;

    if nNodes >= 2 then
      xsi[nNodes] := 1;
    end if;

    if nNodes == 3 then
      xsi[2] := 0.5;
    elseif nNodes > 3 then
      delta := 1/(nNodes - 2);
      for i in 2:nNodes - 1 loop
        xsi[i] := (i - 1.5)*delta;
      end for;
    end if;

Description

The function call \"relNodePositions(nNodes)\" returns a vector
with the relative positions of the nodes of a discretized pipe with nNodes nodes (including the node
at the left and at the right side of the pipe), see next figure:

Example

  Real xsi[7];
algorithm
  xsi = relNodePositions(7);  // xsi = {0, 0.1, 0.3, 0.5, 0.7, 0.9, 1}

See also

MultiBody.Visualizers.PipeWithScalarField

"));
  end relNodePositions;
end Vectors;

package Matrices "Library of functions operating on matrices"
  package Examples
    "Examples demonstrating the usage of the Math.Matrices functions"
    extends Modelica.Icons.ExamplesPackage;
    function solveLinearEquations
      "Demonstrate the solution of linear equation systems"
      extends Modelica.Icons.Function;
      import Modelica.Utilities.Streams.print;
      // solve and solve2
    protected
      Real A0[0, 0];
      Real A1[2, 2]=[1, 2; 3, 4];
      Real x1_ref[2]={-2,3};
      Real b1[2]=A1*x1_ref;
      Real x1[2];
      Real B2[2, 3]=[b1, 2*b1, -3*b1];
      Real X2[2, 3];

      // leastSquares and leastSquares2
      Integer rank;
      Real a[3]={2,3,-1};
      Real A3[3, 3]=transpose([{2,3,-4}, a, 3*a]);
      Real x3_ref[3]={-2,3,5};
      Real b3[3]=A3*x3_ref;
      Real x3[3];
      Real B3[3, 2]=[b3, -3*b3];
      Real X3[3, 2];

      Real diff;
    algorithm
      print("\nDemonstrate how to solve linear equation systems:\n");

      // Solve regular linear equation with a right hand side vector
      x1 := Math.Matrices.solve(A1, b1);
      diff := Vectors.norm(x1 - x1_ref);
      print("diff1 = " + String(diff));
      assert(abs(diff)<1e-10, "Solution should be close to desired");

      // Solve regular linear equation with a right hand side matrix
      X2 := Math.Matrices.solve2(A1, B2);
      diff := Matrices.norm(X2 - [x1_ref, 2*x1_ref, -3*x1_ref]);
      print("diff2 = " + String(diff));
      assert(abs(diff)<1e-10, "Solution should be close to desired");

      // Solve singular linear equation with a right hand side vector
      (x3,rank) := Math.Matrices.leastSquares(A3, b3);
      diff := Vectors.norm(A3*x3 - b3);
      print("diff3 = " + String(diff) + ", n = " + String(
        size(A3, 1)) + ", rank = " + String(rank));
      assert(abs(diff)<1e-10, "Solution should be close to desired");

      // Solve singular linear equation with a right hand side matrix
      (X3,rank) := Math.Matrices.leastSquares2(A3, B3);
      diff := Matrices.norm(A3*X3 - B3);
      print("diff4 = " + String(diff) + ", n = " + String(
        size(A3, 1)) + ", rank = " + String(rank));
      assert(abs(diff)<1e-10, "Solution should be close to desired");
    end solveLinearEquations;
  end Examples;

  function toString "Convert a matrix into its string representation"
    extends Modelica.Icons.Function;
    import Modelica.Utilities.Strings;

    input Real M[:, :] "Real matrix";
    input String name="" "Independent variable name used for printing";
    input Integer significantDigits=6
      "Number of significant digits that are shown";
    output String s="" "String expression of matrix M";
  protected
    String blanks=Strings.repeat(significantDigits);
    String space=Strings.repeat(8);
    String space2=Strings.repeat(3);
    Integer r=size(M, 1);
    Integer c=size(M, 2);

  algorithm
    if r == 0 or c == 0 then
      s := name + " = []";
    else
      s := if name == "" then "\n" else "\n" + name + " = \n";
      for i in 1:r loop
        s := s + space;
        for j in 1:c loop
          if M[i, j] >= 0 then
            s := s + " ";
          end if;
          s := s + String(M[i, j], significantDigits=significantDigits) +
            Strings.repeat(significantDigits + 8 - Strings.length(String(abs(M[
            i, j]))));
        end for;
        s := s + "\n";
      end for;

    end if;
Matrices.toString(A, name=\"\", significantDigits=6);

Description

The function call \"Matrices.toString(A)\" returns the
string representation of matrix A.
With the optional arguments \"name\" and \"significantDigits\", a name and the number of the digits are defined.
The default values of name and significantDigits are \"\" and 6 respectively. If name==\"\" then the
prefix \"<name> =\" is left out.

Example

A = [2.12, -4.34; -2.56, -1.67];

toString(A);
// = \"
//      2.12   -4.34
//     -2.56   -1.67\";

toString(A,\"A\",1);
// = \"A =
//         2     -4
//        -3     -2\"

See also

Vectors.toString

", revisions="
"));
  end toString;

  extends Modelica.Icons.Package;

  function isEqual "Compare whether two Real matrices are identical"
    extends Modelica.Icons.Function;
    input Real M1[:, :] "First matrix";
    input Real M2[:, :] "Second matrix (may have different size as M1)";
    input Real eps(min=0) = 0
      "Two elements e1 and e2 of the two matrices are identical if abs(e1-e2) <= eps";
    output Boolean result
      "= true, if matrices have the same size and the same elements";

  protected
    Integer nrow=size(M1, 1) "Number of rows of matrix M1";
    Integer ncol=size(M1, 2) "Number of columns of matrix M1";
    Integer i=1;
    Integer j;
  algorithm
    result := false;
    if size(M2, 1) == nrow and size(M2, 2) == ncol then
      result := true;
      while i <= nrow loop
        j := 1;
        while j <= ncol loop
          if abs(M1[i, j] - M2[i, j]) > eps then
            result := false;
            i := nrow;
            j := ncol;
          end if;
          j := j + 1;
        end while;
        i := i + 1;
      end while;
    end if;
Matrices.isEqual(M1, M2, eps=0);

Description

The function call \"Matrices.isEqual(M1, M2)\" returns true,
if the two Real matrices M1 and M2 have the same dimensions and
the same elements. Otherwise the function
returns false. Two elements e1 and e2 of the two matrices
are checked on equality by the test \"abs(e1-e2) ≤ eps\", where \"eps\"
can be provided as third argument of the function. Default is \"eps = 0\".

Example

  Real A1[2,2] = [1,2; 3,4];
  Real A2[3,2] = [1,2; 3,4; 5,6];
  Real A3[2,2] = [1,2, 3,4.0001];
  Boolean result;
algorithm
  result := Matrices.isEqual(M1,M2);     // = false
  result := Matrices.isEqual(M1,M3);     // = false
  result := Matrices.isEqual(M1,M1);     // = true
  result := Matrices.isEqual(M1,M3,0.1); // = true

See also

Vectors.isEqual,
Strings.isEqual
"));
  end isEqual;

  function solve
    "Solve real system of linear equations A*x=b with a b vector (Gaussian elimination with partial pivoting)"

    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)] "Matrix A of A*x = b";
    input Real b[size(A, 1)] "Vector b of A*x = b";
    output Real x[size(b, 1)] "Vector x such that A*x = b";

  protected
    Integer info;
  algorithm
    (x,info) := LAPACK.dgesv_vec(A, b);
    assert(info == 0, "Solving a linear system of equations with function
\"Matrices.solve\" is not possible, because the system has either
no or infinitely many solutions (A is singular).");

Description

This function call returns the
solution x of the linear system of equations

A*x = b

If a unique solution x does not exist (since A is singular),
an assertion is triggered. If this is not desired, use instead
Matrices.leastSquares
and inquire the singularity of the solution with the return argument rank
(a unique solution is computed if rank = size(A,1)).

Note, the solution is computed with the LAPACK function \"dgesv\",
i.e., by Gaussian elimination with partial pivoting.

Example

  Real A[3,3] = [1,2,3;
                 3,4,5;
                 2,1,4];
  Real b[3] = {10,22,12};
  Real x[3];
algorithm
  x := Matrices.solve(A,b);  // x = {3,2,1}

See also

Matrices.LU,
Matrices.LU_solve,
Matrices.leastSquares.
"));
  end solve;

  function solve2
    "Solve real system of linear equations A*X=B with a B matrix (Gaussian elimination with partial pivoting)"

    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)] "Matrix A of A*X = B";
    input Real B[size(A, 1), :] "Matrix B of A*X = B";
    output Real X[size(B, 1), size(B, 2)] "Matrix X such that A*X = B";

  protected
    Integer info;
  algorithm
    (X,info) := LAPACK.dgesv(A, B);
    assert(info == 0, "Solving a linear system of equations with function
\"Matrices.solve2\" is not possible, because the system has either
no or infinitely many solutions (A is singular).");

Description

This function call returns the
solution X of the linear system of equations

A*X = B

If a unique solution X does not exist (since A is singular),
an assertion is triggered. If this is not desired, use instead
Matrices.leastSquares2
and inquire the singularity of the solution with the return argument rank
(a unique solution is computed if rank = size(A,1)).

Note, the solution is computed with the LAPACK function \"dgesv\",
i.e., by Gaussian elimination with partial pivoting.

Example

  Real A[3,3] = [1,2,3;
                 3,4,5;
                 2,1,4];
  Real B[3,2] = [10, 20;
                 22, 44;
                 12, 24];
  Real X[3,2];
algorithm
  X := Matrices.solve2(A, B);  /* X = [3, 6;
                                       2, 4;
                                       1, 2] */

See also

Matrices.LU,
Matrices.LU_solve2,
Matrices.leastSquares2.

"));
  end solve2;

  function leastSquares
    "Solve linear equation A*x = b (exactly if possible, or otherwise in a least square sense; A may be non-square and may be rank deficient)"
    extends Modelica.Icons.Function;
    input Real A[:, :] "Matrix A";
    input Real b[size(A, 1)] "Vector b";
    input Real rcond=100*Modelica.Constants.eps
      "Reciprocal condition number to estimate the rank of A";
    output Real x[size(A, 2)]
      "Vector x such that min|A*x-b|^2 if size(A,1) >= size(A,2) or min|x|^2 and A*x=b, if size(A,1) < size(A,2)";
    output Integer rank "Rank of A";
  protected
    Integer info;
    Real xx[max(size(A, 1), size(A, 2))];
  algorithm
    if min(size(A)) > 0 then
      (xx,info,rank) := LAPACK.dgelsy_vec(
            A,
            b,
            rcond);
      x := xx[1:size(A, 2)];
      assert(info == 0,
        "Solving an overdetermined or underdetermined linear system\n" +
        "of equations with function \"Matrices.leastSquares\" failed.");
    else
      x := fill(0.0, size(A, 2));
    end if;

Description

Returns a solution of equation A*x = b in a least
square sense (A may be rank deficient):

minimize | A*x - b |

Several different cases can be distinguished (note, rank is an
output argument of this function):

size(A,1) = size(A,2)

 A solution is returned for a regular, as well as a singular matrix A:

 rank = size(A,1):

     A is regular and the returned solution x fulfills the equation
     A*x = b uniquely.

 rank < size(A,1):

     A is singular and no unique solution for equation A*x = b exists.

  If an infinite number of solutions exists, the one is selected that fulfills
           the equation and at the same time has the minimum norm |x| for all solution
           vectors that fulfill the equation.

  If no solution exists, x is selected such that |A*x - b| is as small as
           possible (but A*x - b is not zero).

size(A,1) > size(A,2):

The equation A*x = b has no unique solution. The solution x is selected such that
|A*x - b| is as small as possible. If rank = size(A,2), this minimum norm solution is
unique. If rank < size(A,2), there are an infinite number of solutions leading to the
same minimum value of |A*x - b|. From these infinite number of solutions, the one with the
minimum norm |x| is selected. This gives a unique solution that minimizes both
|A*x - b| and |x|.

size(A,1) < size(A,2):

 rank = size(A,1):

     There are an infinite number of solutions that fulfill the equation A*x = b.
     From this infinite number, the unique solution is selected that minimizes |x|.

 rank < size(A,1):

     There is either no solution of equation A*x = b, or there are again an infinite
     number of solutions. The unique solution x is returned that minimizes
      both |A*x - b| and |x|.

Note, the solution is computed with the LAPACK function \"dgelsy\",
i.e., QR or LQ factorization of A with column pivoting.

Algorithmic details

The function first computes a QR factorization with column pivoting:

A * P = Q * [ R11 R12 ]
            [  0  R22 ]

with R11 defined as the largest leading submatrix whose estimated
condition number is less than 1/rcond.  The order of R11, rank,
is the effective rank of A.

Then, R22 is considered to be negligible, and R12 is annihilated
by orthogonal transformations from the right, arriving at the
complete orthogonal factorization:

A * P = Q * [ T11 0 ] * Z
            [  0  0 ]

The minimum-norm solution is then

x = P * Z' [ inv(T11)*Q1'*b ]
           [        0       ]

where Q1 consists of the first \"rank\" columns of Q.

See also

Matrices.leastSquares2
(same as leastSquares, but with a right hand side matrix),

Matrices.solve
(for square, regular matrices A)

"));
  end leastSquares;

  function leastSquares2
    "Solve linear equation A*X = B (exactly if possible, or otherwise in a least square sense; A may be non-square and may be rank deficient)"
    extends Modelica.Icons.Function;
    input Real A[:, :] "Matrix A";
    input Real B[size(A, 1), :] "Matrix B";
    input Real rcond=100*Modelica.Constants.eps
      "Reciprocal condition number to estimate rank of A";
    output Real X[size(A, 2), size(B, 2)]
      "Matrix X such that min|A*X-B|^2 if size(A,1) >= size(A,2) or min|X|^2 and A*X=B, if size(A,1) < size(A,2)";
    output Integer rank "Rank of A";
  protected
    Integer info;
    Real XX[max(size(A, 1), size(A, 2)), size(B, 2)];
  algorithm
    (XX,info,rank) := LAPACK.dgelsy(
          A,
          B,
          rcond);
    X := XX[1:size(A, 2), :];
    assert(info == 0, "Solving an overdetermined or underdetermined linear system of
equations with function \"Matrices.leastSquares2\" failed.");

Description

Returns a solution of equation A*X = B in a least
square sense (A may be rank deficient):

minimize | A*X - B |

Several different cases can be distinguished (note, rank is an
output argument of this function):

size(A,1) = size(A,2)

 A solution is returned for a regular, as well as a singular matrix A:

 rank = size(A,1):

     A is regular and the returned solution X fulfills the equation
     A*X = B uniquely.

 rank < size(A,1):

     A is singular and no unique solution for equation A*X = B exists.

  If an infinite number of solutions exists, the one is selected that fulfills
           the equation and at the same time has the minimum norm |x| for all solution
           vectors that fulfill the equation.

  If no solution exists, X is selected such that |A*X - B| is as small as
           possible (but A*X - B is not zero).

size(A,1) > size(A,2):

The equation A*X = B has no unique solution. The solution X is selected such that
|A*X - B| is as small as possible. If rank = size(A,2), this minimum norm solution is
unique. If rank < size(A,2), there are an infinite number of solutions leading to the
same minimum value of |A*X - B|. From these infinite number of solutions, the one with the
minimum norm |X| is selected. This gives a unique solution that minimizes both
|A*X - B| and |X|.

size(A,1) < size(A,2):

 rank = size(A,1):

     There are an infinite number of solutions that fulfill the equation A*X = B.
     From this infinite number, the unique solution is selected that minimizes |X|.

 rank < size(A,1):

     There is either no solution of equation A*X = B, or there are again an infinite
     number of solutions. The unique solution X is returned that minimizes
      both |A*X - B| and |X|.

Note, the solution is computed with the LAPACK function \"dgelsy\",
i.e., QR or LQ factorization of A with column pivoting.

Algorithmic details

The function first computes a QR factorization with column pivoting:

A * P = Q * [ R11 R12 ]
            [  0  R22 ]

with R11 defined as the largest leading submatrix whose estimated
condition number is less than 1/rcond.  The order of R11, rank,
is the effective rank of A.

Then, R22 is considered to be negligible, and R12 is annihilated
by orthogonal transformations from the right, arriving at the
complete orthogonal factorization:

A * P = Q * [ T11 0 ] * Z
            [  0  0 ]

The minimum-norm solution is then

X = P * Z' [ inv(T11)*Q1'*B ]
           [        0       ]

where Q1 consists of the first \"rank\" columns of Q.

See also

Matrices.leastSquares
(same as leastSquares2, but with a right hand side vector),

Matrices.solve2
(for square, regular matrices A)

"));
  end leastSquares2;

  function equalityLeastSquares
    "Solve a linear equality constrained least squares problem"
    extends Modelica.Icons.Function;
    input Real A[:, :] "Minimize |A*x - a|^2";
    input Real a[size(A, 1)];
    input Real B[:, size(A, 2)] "Subject to B*x=b";
    input Real b[size(B, 1)];
    output Real x[size(A, 2)] "Solution vector";

  protected
    Integer info;
  algorithm
    assert(size(A, 2) >= size(B, 1) and size(A, 2) <= size(A, 1) + size(B, 1),
      "It is required that size(B,1) <= size(A,2) <= size(A,1) + size(B,1)\n"
       +
      "This relationship is not fulfilled, since the matrices are declared as:\n"
       + "  A[" + String(size(A, 1)) + "," + String(size(A, 2)) + "], B[" +
      String(size(B, 1)) + "," + String(size(B, 2)) + "]\n");

    (x,info) := LAPACK.dgglse_vec(
          A,
          a,
          B,
          b);

    assert(info == 0, "Solving a linear equality-constrained least squares problem
with function \"Matrices.equalityLeastSquares\" failed.");

Description

This function returns the
solution x of the linear equality-constrained least squares problem:

min|A*x - a|^2 over x, subject to B*x = b

It is required that the dimensions of A and B fulfill the following
relationship:

size(B,1) ≤ size(A,2) ≤ size(A,1) + size(B,1)

Note, the solution is computed with the LAPACK function \"dgglse\"
using the generalized RQ factorization under the assumptions that
B has full row rank (= size(B,1)) and the matrix [A;B] has
full column rank (= size(A,2)). In this case, the problem
has a unique solution.

"));
  end equalityLeastSquares;

  pure function LU "LU decomposition of square or rectangular matrix"
    extends Modelica.Icons.Function;
    input Real A[:, :] "Square or rectangular matrix";
    output Real LU[size(A, 1), size(A, 2)]=A
      "L,U factors (used with LU_solve(..))";
    output Integer pivots[min(size(A, 1), size(A, 2))]
      "Pivot indices (used with LU_solve(..))";
    output Integer info "Information";
  protected
    Integer m=size(A, 1);
    Integer n=size(A, 2);
    Integer lda=max(1, size(A, 1));
  external"FORTRAN 77" dgetrf(
          m,
          n,
          LU,
          lda,
          pivots,
          info)
(LU, pivots, info) = Matrices.LU(A);

Description

This function call returns the
LU decomposition of a \"Real[m,n]\" matrix A, i.e.,

P*L*U = A

where P is a permutation matrix (implicitly
defined by vector pivots),
L is a lower triangular matrix with unit
diagonal elements (lower trapezoidal if m > n), and
U is an upper triangular matrix (upper trapezoidal if m < n).
Matrices L and U are stored in the returned
matrix LU (the diagonal of L is not stored).
With the companion function
Matrices.LU_solve,
this decomposition can be used to solve
linear systems (P*L*U)*x = b with different right
hand side vectors b. If a linear system of equations with
just one right hand side vector b shall be solved, it is
more convenient to just use the function
Matrices.solve.

The optional third (Integer) output argument has the following meaning:

info = 0:
      successful exit

info > 0:
      if info = i, U[i,i] is exactly zero. The factorization
          has been completed,

          but the factor U is exactly
          singular, and division by zero will occur
 if it is used
          to solve a system of equations.

The LU factorization is computed
with the LAPACK function \"dgetrf\",
i.e., by Gaussian elimination using partial pivoting
with row interchanges. Vector \"pivots\" are the
pivot indices, i.e., for 1 ≤ i ≤ min(m,n), row i of
matrix A was interchanged with row pivots[i].

Example

  Real A[3,3] = [1,2,3;
                 3,4,5;
                 2,1,4];
  Real b1[3] = {10,22,12};
  Real b2[3] = { 7,13,10};
  Real    LU[3,3];
  Integer pivots[3];
  Real    x1[3];
  Real    x2[3];
algorithm
  (LU, pivots) := Matrices.LU(A);
  x1 := Matrices.LU_solve(LU, pivots, b1);  // x1 = {3,2,1}
  x2 := Matrices.LU_solve(LU, pivots, b2);  // x2 = {1,0,2}

See also

Matrices.LU_solve,
Matrices.solve,

"));
  end LU;

  function LU_solve
    "Solve real system of linear equations P*L*U*x=b with a b vector and an LU decomposition (from LU(..))"

    extends Modelica.Icons.Function;
    input Real LU[:, size(LU, 1)]
      "L,U factors of Matrices.LU(..) for a square matrix";
    input Integer pivots[size(LU, 1)] "Pivots indices of Matrices.LU(..)";
    input Real b[size(LU, 1)] "Right hand side vector of P*L*U*x=b";
    output Real x[size(b, 1)] "Solution vector such that P*L*U*x = b";

  algorithm
    for i in 1:size(LU, 1) loop
      assert(LU[i, i] <> 0, "Solving a linear system of equations with function
\"Matrices.LU_solve\" is not possible, since the LU decomposition
is singular, i.e., no unique solution exists.");
    end for;
    x := LAPACK.dgetrs_vec(
          LU,
          pivots,
          b);

Description

This function call returns the
solution x of the linear systems of equations

P*L*U*x = b;

where P is a permutation matrix (implicitly
defined by vector pivots),
L is a lower triangular matrix with unit
diagonal elements (lower trapezoidal if m > n), and
U is an upper triangular matrix (upper trapezoidal if m < n).
The matrices of this decomposition are computed with function
Matrices.LU that
returns arguments LU and pivots
used as input arguments of Matrices.LU_solve.
With Matrices.LU and Matrices.LU_solve
it is possible to efficiently solve linear systems
with different right hand side vectors. If a linear system of equations with
just one right hand side vector shall be solved, it is
more convenient to just use the function
Matrices.solve.

If a unique solution x does not exist (since the
LU decomposition is singular), an exception is raised.

The LU factorization is computed
with the LAPACK function \"dgetrf\",
i.e., by Gaussian elimination using partial pivoting
with row interchanges. Vector \"pivots\" are the
pivot indices, i.e., for 1 ≤ i ≤ min(m,n), row i of
matrix A was interchanged with row pivots[i].

Example

  Real A[3,3] = [1,2,3;
                 3,4,5;
                 2,1,4];
  Real b1[3] = {10,22,12};
  Real b2[3] = { 7,13,10};
  Real    LU[3,3];
  Integer pivots[3];
  Real    x1[3];
  Real    x2[3];
algorithm
  (LU, pivots) := Matrices.LU(A);
  x1 := Matrices.LU_solve(LU, pivots, b1);  // x1 = {3,2,1}
  x2 := Matrices.LU_solve(LU, pivots, b2);  // x2 = {1,0,2}

See also

Matrices.LU,
Matrices.solve,
"));
  end LU_solve;

  function LU_solve2
    "Solve real system of linear equations P*L*U*X=B with a B matrix and an LU decomposition (from LU(..))"

    extends Modelica.Icons.Function;
    input Real LU[:, size(LU, 1)]
      "L,U factors of Matrices.LU(..) for a square matrix";
    input Integer pivots[size(LU, 1)] "Pivots indices of Matrices.LU(..)";
    input Real B[size(LU, 1), :] "Right hand side matrix of P*L*U*X=B";
    output Real X[size(B, 1), size(B, 2)]
      "Solution matrix such that P*L*U*X = B";

  algorithm
    for i in 1:size(LU, 1) loop
      assert(LU[i, i] <> 0, "Solving a linear system of equations with function
\"Matrices.LU_solve2\" is not possible, since the LU decomposition
is singular, i.e., no unique solution exists.");
    end for;
    X := Modelica.Math.Matrices.LAPACK.dgetrs(
          LU,
          pivots,
          B);

Description

This function call returns the
solution X of the linear systems of equations

P*L*U*X = B;

where P is a permutation matrix (implicitly
defined by vector pivots),
L is a lower triangular matrix with unit
diagonal elements (lower trapezoidal if m > n), and
U is an upper triangular matrix (upper trapezoidal if m < n).
The matrices of this decomposition are computed with function
Matrices.LU that
returns arguments LU and pivots
used as input arguments of Matrices.LU_solve2.
With Matrices.LU and Matrices.LU_solve2
it is possible to efficiently solve linear systems
with different right hand side matrices. If a linear system of equations with
just one right hand side matrix shall be solved, it is
more convenient to just use the function
Matrices.solve2.

If a unique solution X does not exist (since the
LU decomposition is singular), an exception is raised.

The LU factorization is computed
with the LAPACK function \"dgetrf\",
i.e., by Gaussian elimination using partial pivoting
with row interchanges. Vector \"pivots\" are the
pivot indices, i.e., for 1 ≤ i ≤ min(m,n), row i of
matrix A was interchanged with row pivots[i].

Example

  Real A[3,3] = [1,2,3;
                 3,4,5;
                 2,1,4];
  Real B1[3] = [10, 20;
                22, 44;
                12, 24];
  Real B2[3] = [ 7, 14;
                13, 26;
                10, 20];
  Real    LU[3,3];
  Integer pivots[3];
  Real    X1[3,2];
  Real    X2[3,2];
algorithm
  (LU, pivots) := Matrices.LU(A);
  X1 := Matrices.LU_solve2(LU, pivots, B1);  /* X1 = [3, 6;
                                                      2, 4;
                                                      1, 2] */
  X2 := Matrices.LU_solve2(LU, pivots, B2);  /* X2 = [1, 2;
                                                      0, 0;
                                                      2, 4] */

See also

Matrices.LU,
Matrices.solve2,
"));
  end LU_solve2;

  function eigenValues
    "Return eigenvalues and eigenvectors for a real, nonsymmetric matrix in a Real representation"

    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)] "Matrix";
    output Real eigenvalues[size(A, 1), 2]
      "Eigenvalues of matrix A (Re: first column, Im: second column)";
    output Real eigenvectors[size(A, 1), size(A, 2)]
      "Real-valued eigenvector matrix";

  protected
    Integer info;
    Boolean onlyEigenvalues=false;
  algorithm
    if size(A, 1) > 0 then
      if onlyEigenvalues then
        (eigenvalues[:, 1],eigenvalues[:, 2],info) := LAPACK.dgeev_eigenValues(A);
        eigenvectors := zeros(size(A, 1), size(A, 1));
      else
        (eigenvalues[:, 1],eigenvalues[:, 2],eigenvectors,info) := LAPACK.dgeev(A);
      end if;
      assert(info == 0, "Calculating the eigen values with function
\"Matrices.eigenvalues\" is not possible, since the
numerical algorithm does not converge.");
    end if;
(eigenvalues, eigenvectors) = Matrices.eigenValues(A);

Description

This function call returns the eigenvalues and
optionally the (right) eigenvectors of a square matrix
A. The first column of \"eigenvalues\" contains the real and the
second column contains the imaginary part of the eigenvalues.
If the i-th eigenvalue has no imaginary part, then eigenvectors[:,i] is
the corresponding real eigenvector. If the i-th eigenvalue
has an imaginary part, then eigenvalues[i+1,:] is the conjugate complex
eigenvalue and eigenvectors[:,i] is the real and eigenvectors[:,i+1] is the
imaginary part of the eigenvector of the i-th eigenvalue.
With function
Matrices.eigenValueMatrix,
a real block diagonal matrix is constructed from the eigenvalues
such that

A = eigenvectors * eigenValueMatrix(eigenvalues) * inv(eigenvectors)

provided the eigenvector matrix \"eigenvectors\" can be inverted
(an inversion is possible, if all eigenvalues are different;
in some cases, an inversion is also possible if some eigenvalues are
the same).

Example

  Real A[3,3] = [1,2,3;
                 3,4,5;
                 2,1,4];
  Real eval[3,2];
algorithm
  eval := Matrices.eigenValues(A);  // eval = [-0.618, 0;
                                    //          8.0  , 0;
                                    //          1.618, 0];

i.e., matrix A has the 3 real eigenvalues -0.618, 8, 1.618.

See also

Matrices.eigenValueMatrix,
Matrices.singularValues
"));
  end eigenValues;

  function eigenValueMatrix
    "Return real valued block diagonal matrix J of eigenvalues of matrix A (A=V*J*Vinv)"

    extends Modelica.Icons.Function;
    input Real eigenValues[:, 2]
      "Eigen values from function eigenValues(..) (Re: first column, Im: second column)";
    output Real J[size(eigenValues, 1), size(eigenValues, 1)]
      "Real valued block diagonal matrix with eigen values (Re: 1x1 block, Im: 2x2 block)";

  protected
    Integer n=size(eigenValues, 1);
    Integer i;
  algorithm
    J := zeros(n, n);
    i := 1;
    while i <= n loop
      if eigenValues[i, 2] == 0 then
        J[i, i] := eigenValues[i, 1];
        i := i + 1;
      else
        J[i, i] := eigenValues[i, 1];
        J[i, i + 1] := eigenValues[i, 2];
        J[i + 1, i] := eigenValues[i + 1, 2];
        J[i + 1, i + 1] := eigenValues[i + 1, 1];
        i := i + 2;
      end if;
    end while;

Description

The function call returns a block diagonal matrix J
from the two-column matrix eigenvalues
(computed by function
Matrices.eigenValues).
Matrix eigenvalues must have the real part of the
eigenvalues in the first column and the imaginary part in the
second column. If an eigenvalue i has a vanishing imaginary
part, then J[i,i] = eigenvalues[i,1], i.e., the diagonal
element of J is the real eigenvalue.
Otherwise, eigenvalue i and conjugate complex eigenvalue i+1
are used to construct a 2 by 2 diagonal block of J:

J[i  , i]   := eigenvalues[i,1];
J[i  , i+1] := eigenvalues[i,2];
J[i+1, i]   := eigenvalues[i+1,2];
J[i+1, i+1] := eigenvalues[i+1,1];

See also

Matrices.eigenValues
"));
  end eigenValueMatrix;

  function singularValues
    "Return singular values and left and right singular vectors"
    extends Modelica.Icons.Function;
    input Real A[:, :] "Matrix";
    output Real sigma[min(size(A, 1), size(A, 2))] "Singular values";
    output Real U[size(A, 1), size(A, 1)]=identity(size(A, 1))
      "Left orthogonal matrix";
    output Real VT[size(A, 2), size(A, 2)]=identity(size(A, 2))
      "Transposed right orthogonal matrix";

  protected
    Integer info;
    Integer n=min(size(A, 1), size(A, 2)) "Number of singular values";
  algorithm
    if n > 0 then
      (sigma,U,VT,info) := Modelica.Math.Matrices.LAPACK.dgesvd(A);
      assert(info == 0, "The numerical algorithm to compute the
singular value decomposition did not converge");
    end if;
(sigma, U, VT) = Matrices.singularValues(A);

Description

This function computes the singular values and optionally the
singular vectors of matrix A. Basically the singular
value decomposition of A is computed, i.e.,

A = U Σ VT
  = U*Sigma*VT

where U and V are orthogonal matrices (UUT=I,
VVT=I).
Σ = [diagonal(σi), zeros(n,m-n)], if n=size(A,1) ≤
m=size(A,2)) or [diagonal(σi); zeros(n-m,m)], if n >
m=size(A,2)). Σ has the same size as matrix A with
nonnegative diagonal elements in decreasing order and with all other elements zero
(σ1 is the largest element). The function
returns the singular values σi
in vector sigma and the orthogonal matrices in
matrices U and VT.

Example

A = [1, 2,  3,  4;
     3, 4,  5, -2;
    -1, 2, -3,  5];
(sigma, U, VT) = singularValues(A);
results in:
   sigma = {8.33, 6.94, 2.31};
i.e.
   Sigma = [8.33,    0,    0, 0;
               0, 6.94,    0, 0;
               0,    0, 2.31, 0]

See also

Matrices.eigenValues
"));
  end singularValues;

  function QR
    "Return the QR decomposition of a square matrix with optional column pivoting (A(:,p) = Q*R)"

    extends Modelica.Icons.Function;
    input Real A[:, :] "Rectangular matrix with size(A,1) >= size(A,2)";
    input Boolean pivoting=true
      "= true, if column pivoting is performed. True is default";
    output Real Q[size(A, 1), size(A, 2)]
      "Rectangular matrix with orthonormal columns such that Q*R=A[:,p]";
    output Real R[size(A, 2), size(A, 2)] "Square upper triangular matrix";
    output Integer p[size(A, 2)] "Column permutation vector";

  protected
    Integer nrow=size(A, 1);
    Integer ncol=size(A, 2);
    Real tau[size(A, 2)];
  algorithm
    assert(nrow >= ncol, "\nInput matrix A[" + String(nrow) + "," + String(ncol)
       + "] has more columns than rows.
This is not allowed when calling Modelica.Math.Matrices.QR(A).");
    if pivoting then
      (Q,tau,p) := LAPACK.dgeqp3(A);
    else
      (Q,tau) := LAPACK.dgeqrf(A);
      p := 1:ncol;
    end if;

    // determine R
    R := zeros(ncol, ncol);
    for i in 1:ncol loop
      for j in i:ncol loop
        R[i, j] := Q[i, j];
      end for;
    end for;

    Q := LAPACK.dorgqr(Q, tau);

Description

This function returns the QR decomposition of
a rectangular matrix A (the number of columns of A
must be less than or equal to the number of rows):

Q*R = A[:,p]

where Q is a rectangular matrix that has orthonormal columns and
has the same size as A (QTQ=I),
R is a square, upper triangular matrix and p is a permutation
vector. Matrix R has the following important properties:

 The absolute value of a diagonal element of R is the largest
     value in this row, i.e.,
     abs(R[i,i]) ≥ abs(R[i,j]).

 The diagonal elements of R are sorted according to size, such that
     the largest absolute value is abs(R[1,1]) and
     abs(R[i,i]) ≥ abs(R[j,j]) with i < j.

This means that if abs(R[i,i]) ≤ ε then abs(R[j,k]) ≤ ε
for j ≥ i, i.e., the i-th row up to the last row of R have
small elements and can be treated as being zero.
This allows to, e.g., estimate the row-rank
of R (which is the same row-rank as A). Furthermore,
R can be partitioned in two parts

A[:,p] = Q * [R1, R2;
              0,  0]

where R1 is a regular, upper triangular matrix.

Note, the solution is computed with the LAPACK functions \"dgeqp3\"
and \"dorgqr\", i.e., by Householder transformations with
column pivoting. If Q is not needed, the function may be
called as: (,R,p) = QR(A).

Example

  Real A[3,3] = [1,2,3;
                 3,4,5;
                 2,1,4];
  Real R[3,3];
algorithm
  (,R) := Matrices.QR(A);  // R = [-7.07.., -4.24.., -3.67..;
                                    0     , -1.73.., -0.23..;
                                    0     ,  0     ,  0.65..];

"));
  end QR;

  function hessenberg "Return upper Hessenberg form of a matrix"
    extends Modelica.Icons.Function;
    import Modelica.Math.Matrices;

    input Real A[:, size(A, 1)] "Square matrix A";

    output Real H[size(A, 1), size(A, 2)] "Hessenberg form of A";
    output Real U[size(A, 1), size(A, 2)] "Transformation matrix";

  protected
    Real V[size(A, 1), size(A, 2)]
      "V=[v1,v2,..vn-1,0] with vi are vectors which define the elementary reflectors";
    Real tau[max(0, size(A, 1) - 1)]
      "Scalar factors of the elementary reflectors";

  algorithm
    (H,V,tau) := Matrices.Utilities.toUpperHessenberg(
          A,
          1,
          size(A, 1));
    U := Matrices.LAPACK.dorghr(
          V,
          1,
          size(A, 1),
          tau);
(H, U) = Matrices.hessenberg(A);

Description

Function hessenberg computes the Hessenberg matrix H of matrix A as well as the orthogonal transformation matrix U that holds H = U'*A*U.
The Hessenberg form of a matrix is computed by repeated Householder similarity transformation. The elementary reflectors and the corresponding scalar factors are provided
by function \"Utilities.toUpperHessenberg()\". The transformation matrix U is then computed by
LAPACK.dorghr.

Example

A  = [1, 2,  3;
      6, 5,  4;
      1, 0,  0];

(H, U) = hessenberg(A);

results in:

H = [1.0,  -2.466,  2.630;
    -6.083, 5.514, -3.081;
     0.0,   0.919, -0.514]

U = [1.0,    0.0,      0.0;
     0.0,   -0.9864,  -0.1644;
     0.0,   -0.1644,   0.9864]

and therefore,

U*H*transpose(U) = [1.0, 2.0, 3.0;
                    6.0, 5.0, 4.0;
                    1.0, 0.0, 0.0]

See also

Matrices.Utilities.toUpperHessenberg

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end hessenberg;

  function realSchur
    "Return the real Schur form (rsf) S of a square matrix A, A=QZ*S*QZ'"
    extends Modelica.Icons.Function;
    import Modelica.Math.Matrices;

    input Real A[:, size(A, 1)] "Square matrix";

  public
    output Real S[size(A, 1), size(A, 2)] "Real Schur form of A";
    output Real QZ[size(A, 1), size(A, 2)] "Schur vector Matrix";
    output Real alphaReal[size(A, 1)]
      "Real part of eigenvalue=alphaReal+i*alphaImag";
    output Real alphaImag[size(A, 1)]
      "Imaginary part of eigenvalue=alphaReal+i*alphaImag";

  protected
    Integer info;

  algorithm
    if size(A, 1) > 1 then
      (S,QZ,alphaReal,alphaImag,info) := Matrices.LAPACK.dgees(A);
      assert(info == 0, "The output info of LAPACK.dgees should be zero, else if\n
     info < 0:  if info = -i, the i-th argument of dgees had an illegal value\n
     info > 0:  if INFO = i, and i is
               <= N: the QR algorithm failed to compute all the
                     eigenvalues; elements 1:ILO-1 and i+1:N of WR and WI
                     contain those eigenvalues which have converged; if
                     JOBVS = 'V', VS contains the matrix which reduces A
                     to its partially converged Schur form.\n");
    else
      S := A;
      QZ := fill(1, size(A, 1), size(A, 2));
      alphaReal := fill(1, size(A, 1));
      alphaImag := fill(0, size(A, 1));
    end if;
(S, QZ, alphaReal, alphaImag) = Matrices.realSchur(A);

Description

Function realSchur calculates the real Schur form of a real square matrix A, i.e.

A = QZ*S*transpose(QZ)

with the real nxn matrices S and QZ. S is a block upper triangular matrix with 1x1 and 2x2 blocks in the diagonal. QZ is an orthogonal matrix.
The 1x1 blocks contains the real eigenvalues of A. The 2x2 blocks [s11, s12; s21, s11] represents the conjugated complex pairs of eigenvalues, whereas the real parts of the eigenvalues
are the elements of the diagonal (s11). The imaginary parts are the positive and negative square roots of the product of the two elements s12 and s21 (imag = +-sqrt(s12*s21)).

The calculation in lapack.dgees is performed stepwise, i.e., using the internal methods of balancing and scaling of dgees.

Example

   Real A[3,3] = [1, 2, 3; 4, 5, 6; 7, 8, 9];
   Real T[3,3];
   Real Z[3,3];
   Real alphaReal[3];
   Real alphaImag[3];

algorithm
  (T, Z, alphaReal, alphaImag):=Modelica.Math.Matrices.realSchur(A);
//   T = [16.12, 4.9,   1.59E-015;
//        0,    -1.12, -1.12E-015;
//        0,     0,    -1.30E-015]
//   Z = [-0.23,  -0.88,   0.41;
//        -0.52,  -0.24,  -0.82;
//        -0.82,   0.4,    0.41]
//alphaReal = {16.12, -1.12, -1.32E-015}
//alphaImag = {0, 0, 0}

See also

Math.Matrices.Utilities.reorderRSF

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end realSchur;

  function cholesky
    "Return the Cholesky factorization of a symmetric positive definite matrix"
    extends Modelica.Icons.Function;
    import Modelica.Math.Matrices.LAPACK;
    input Real A[:, size(A, 1)] "Symmetric positive definite matrix";
    input Boolean upper=true
      "= true, if the right Cholesky factor (upper triangle) should be returned";

    output Real H[size(A, 1), size(A, 2)]
      "Cholesky factor U (upper=true) or L (upper=false) for A = U'*U or A = L*L'";

  protected
    Integer n=size(A, 1);
    Integer info;

  algorithm
    if size(A, 1) > 0 then
      (H,info) := LAPACK.dpotrf(A, upper);
    else
      H := fill(
            0,
            0,
            0);
      info := 0;
    end if;
    if info < 0 then
      assert(info == 0,
        "Cholesky factorization failed in function \"Matrices.cholesky\" due to illegal value of input "
         + String(info) + " for LAPACK routine DPOTRF");
    else
      assert(info == 0,
        "Cholesky factorization failed in function \"Matrices.cholesky\" since matrix A is not positive definite");
    end if;

    if upper then
      for i in 2:n loop
        for j in 1:i - 1 loop
          H[i, j] := 0.0;
        end for;
      end for;
    else
      for i in 1:n - 1 loop
        for j in i + 1:n loop
          H[i, j] := 0.0;
        end for;
      end for;
    end if;
H = Matrices.cholesky(A, upper=true);

Description

Function cholesky computes the Cholesky factorization of a real symmetric positive definite matrix A.
The optional Boolean input \"upper\" specifies whether the upper or the lower triangular matrix is returned, i.e.

A = H'*H   if upper is true (H is upper triangular)
A = H*H'   if upper is false (H is lower triangular)

The computation is performed by LAPACK.dpotrf.

Example

A  = [1, 0,  0;
      6, 5,  0;
      1, -2,  2];
S = A*transpose(A);

H = Matrices.cholesky(S);

results in:

H = [1.0,  6.0,  1.0;
     0.0,  5.0, -2.0;
     0.0,  0.0,  2.0]

with

transpose(H)*H = [1.0,  6.0,   1;
                  6.0, 61.0,  -4.0;
                  1.0, -4.0,   9.0] //=S

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end cholesky;

  function balance
    "Return a balanced form of matrix A to improve the condition of A"
    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)];
    output Real D[size(A, 1)] "diagonal(D)=T is transformation matrix, such that
          B = inv(T)*A*T has smaller condition as A";
    output Real B[size(A, 1), size(A, 1)]
      "Balanced matrix (= inv(diagonal(D))*A*diagonal(D) )";
  protected
    Integer na=size(A, 1);
    Integer radix=2 "Radix of exponent representation must be 'radix'
          or a multiple of 'radix'";
    Integer radix2=radix*radix;
    Boolean noconv=true;
    Integer i=1;
    Integer j=1;
    Real CO;
    Real RO;
    Real G;
    Real F;
    Real S;
    /*auxiliary variables*/

  algorithm
    // B = inv(D)*A*D, so that cond(B)<=cond(A)
    D := ones(na);
    B := A;
    while noconv loop
      noconv := false;
      for i in 1:na loop
        CO := sum(abs(B[:, i])) - abs(B[i, i]);
        RO := sum(abs(B[i, :])) - abs(B[i, i]);
        G := RO/radix;
        F := 1;
        S := CO + RO;
        while not (CO >= G or CO == 0) loop
          F := F*radix;
          CO := CO*radix2;
        end while;
        G := RO*radix;
        while not (CO < G or RO == 0) loop
          F := F/radix;
          CO := CO/radix2;
        end while;
        if not ((CO + RO)/F >= 0.95*S) then
          G := 1/F;
          D[i] := D[i]*F;
          B[i, :] := B[i, :]*G;
          B[:, i] := B[:, i]*F;
          noconv := true;
        end if;
      end for;
    end while;

Description

This function returns a vector D, such that B=inv(diagonal(D))*A*diagonal(D) has a
better condition as matrix A, i.e., conditionNumber(B) ≤ conditionNumber(A). The elements of D
are multiples of 2 which means that this function does not introduce round-off errors.
Balancing attempts to make the norm of each row of B equal to the
norm of the respective column.

Balancing is used to minimize roundoff errors induced
through large matrix calculations like Taylor-series approximation
or computation of eigenvalues.

Example

- A = [1, 10,  1000; 0.01,  0,  10; 0.005,  0.01,  10]
- Matrices.norm(A, 1);
  = 1020.0
- (T,B)=Matrices.balance(A)
- T
  = {256, 16, 0.5}
- B
  =  [1,     0.625,   1.953125;
      0.16,  0,       0.3125;
      2.56,  0.32,   10.0]
- Matrices.norm(B, 1);
  = 12.265625

The Algorithm is taken from

H. D. Joos, G. Grübel:

RASP'91 Regulator Analysis and Synthesis Programs

    DLR - Control Systems Group 1991

which based on the balance function from EISPACK.

", revisions="

Release Notes

July 5, 2002
       by H. D. Joos and Nico Walther

       Implemented.

"));
  end balance;

  function balanceABC
    "Return a balanced form of a system [A,B;C,0] to improve its condition by a state transformation"
    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)] "System matrix A";
    input Real B[size(A,1),:] = fill(0.0, size(A,1),0)
      "System matrix B (need not be present)";
    input Real C[:,size(A,1)] = fill(0.0, 0, size(A,1))
      "System matrix C (need not be present)";
    output Real scale[size(A, 1)]
      "diagonal(scale)=T is such that [inv(T)*A*T, inv(T)*B; C*T, 0] has smaller condition as [A,B;C,0]";
    output Real As[size(A, 1), size(A, 1)] "Balanced matrix A (= inv(T)*A*T )";
    output Real Bs[size(A, 1), size(B, 2)] "Balanced matrix B (= inv(T)*B )";
    output Real Cs[size(C, 1), size(A, 1)] "Balanced matrix C (= C*T )";

  protected
    Integer na=size(A, 1);
    Integer radix=2 "Radix of exponent representation must be 'radix'
          or a multiple of 'radix'";
    Integer radix2=radix*radix;
    Boolean noconv=true;
    Integer i=1;
    Integer j=1;
    Real CO;
    Real RO;
    Real G;
    Real F;
    Real S;
  algorithm
    scale := ones(na);
    As := A;
    Bs := B;
    Cs := C;
    while noconv loop
      noconv := false;
      for i in 1:na loop
        CO := sum(abs(As[:, i])) - abs(As[i, i]) + sum(abs(Cs[:,i]));
        RO := sum(abs(As[i, :])) - abs(As[i, i]) + sum(abs(Bs[i,:]));
        G := RO/radix;
        F := 1;
        S := CO + RO;
        while not (CO >= G or CO == 0) loop
          F := F*radix;
          CO := CO*radix2;
        end while;
        G := RO*radix;
        while not (CO < G or RO == 0) loop
          F := F/radix;
          CO := CO/radix2;
        end while;
        if not ((CO + RO)/F >= 0.95*S) then
          G := 1/F;
          scale[i] := scale[i]*F;
          As[i, :] := As[i, :]*G;
          As[:, i] := As[:, i]*F;
          Bs[i, :] := Bs[i, :]*G;
          Cs[:, i] := Cs[:, i]*F;
          noconv := true;
        end if;
      end for;
    end while;
(scale,As,Bs)    = Matrices.balanceABC(A,B);
(scale,As,,Cs)   = Matrices.balanceABC(A,C=C);

Description

This function returns a vector scale, such that with T=diagonal(scale) system matrix S_scale

          |inv(T)*A*T, inv(T)*B|
S_scale = |                    |
          |       C*T,     0   |

has a better condition as system matrix S

    |A, B|
S = |    |
    |C, 0|

that is, conditionNumber(S_scale) ≤ conditionNumber(S). The elements of vector scale
are multiples of 2 which means that this function does not introduce round-off errors.

Balancing a linear dynamic system in state space form

der(x) = A*x + B*u
    y  = C*x + D*u

means to find a state transformation x_new = T*x = diagonal(scale)*x
so that the transformed system is better suited for numerical algorithms.

Example

import Modelica.Math.Matrices;

A = [1, -10,  1000; 0.01,  0,  10; 0.005,  -0.01,  10];
B = [100, 10; 1,0; -0.003, 1];
C = [-0.5, 1, 100];

(scale, As, Bs, Cs) := Matrices.balanceABC(A,B,C);
T    = diagonal(scale);
Diff = [Matrices.inv(T)*A*T, Matrices.inv(T)*B;
        C*T, zeros(1,2)] - [As, Bs; Cs, zeros(1,2)];
err  = Matrices.norm(Diff);

-> Results in:
scale = {16, 1, 0.0625}
norm(A)  = 1000.15, norm(B)  = 100.504, norm(C)  = 100.006
norm(As) = 10.8738, norm(Bs) = 16.0136, norm(Cs) = 10.2011
err = 0

The algorithm is taken from

H. D. Joos, G. Grübel:

RASP'91 Regulator Analysis and Synthesis Programs

    DLR - Control Systems Group 1991

which is based on the balance function from EISPACK.

", revisions="

Sept. 14, 2014
       by Martin Otter: Implemented.

"));
  end balanceABC;

  function trace
    "Return the trace of matrix A, i.e., the sum of the diagonal elements"
    extends Modelica.Icons.Function;

    input Real A[:, size(A, 1)] "Square matrix A";
    output Real result "Trace of A";
  algorithm
    result := sum(A[i, i] for i in 1:size(A, 1));

Description

This function computes the trace, i.e., the sum of the elements in the diagonal of matrix A.

Example

A = [1, 3;
     2, 1];
r = trace(A);

results in:

r = 2.0

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end trace;

  function det
    "Return determinant of a matrix (computed by LU decomposition; try to avoid det(..))"

    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)];
    output Real result "Determinant of matrix A";
  protected
    Real LU[size(A, 1), size(A, 1)];
    Integer pivots[size(A, 1)];

  algorithm
    (LU,pivots) := Matrices.LU(A);
    result := product(LU[i, i] for i in 1:size(A, 1))*product(if pivots[i] == i
       then 1 else -1 for i in 1:size(pivots, 1));

Description

This function returns the determinant \"result\" of matrix A
computed by a LU decomposition with row pivoting. For details about determinants, see
http://en.wikipedia.org/wiki/Determinant.
Usually, this function should never be used, because
there are nearly always better numerical algorithms
as by computing the determinant. Examples:

 Use Matrices.rank
     to compute whether det(A) = 0 (i.e., Matrices.rank(A) < size(A,1)).

 Use Matrices.solve
     to solve the linear equation A*x = b, instead of using determinants to
     compute the solution.

See also

Matrices.rank,
Matrices.solve
"));
  end det;

  function inv "Return inverse of a matrix (try to avoid inv(..))"
    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)];
    output Real invA[size(A, 1), size(A, 2)] "Inverse of matrix A";
  protected
    Integer info;
    Integer pivots[size(A, 1)] "Pivot vector";
    Real LU[size(A, 1), size(A, 2)] "LU factors of A";
  algorithm
    (LU,pivots,info) := LAPACK.dgetrf(A);

    assert(info == 0, "Calculating an inverse matrix with function
\"Matrices.inv\" is not possible, since matrix A is singular.");

    invA := LAPACK.dgetri(LU, pivots);

Description

This function returns the inverse of matrix A, i.e., A*inv(A) = identity(size(A,1))
computed by a LU decomposition with row pivoting.
Usually, this function should not be used, because
there are nearly always better numerical algorithms
as by computing directly the inverse. Example:

Use x = Matrices.solve(A,b)
to solve the linear equation A*x = b, instead of computing the solution by
x = inv(A)*b, because this is much more efficient and much more reliable.

See also

Matrices.solve
Matrices.solve2
"));
  end inv;

  function rank
    "Return rank of a rectangular matrix (computed with singular values)"
    extends Modelica.Icons.Function;
    input Real A[:, :] "Matrix";
    input Real eps=0
      "If eps > 0, the singular values are checked against eps; otherwise eps=max(size(A))*norm(A)*Modelica.Constants.eps is used";
    output Integer result "Rank of matrix A";

  protected
    Integer n=min(size(A, 1), size(A, 2));
    Integer i=n;
    Real sigma[min(size(A, 1), size(A, 2))];
    Real eps2;
  algorithm
    result := 0;
    if n > 0 then
      sigma := Modelica.Math.Matrices.singularValues(A);
      eps2 := if eps > 0 then eps else max(size(A))*sigma[1]*Modelica.Constants.eps;
      while i > 0 loop
        if sigma[i] > eps2 then
          result := i;
          i := 0;
        end if;
        i := i - 1;
      end while;
    end if;
result = Matrices.rank(A,eps=0);

Description

This function returns the rank of a square or rectangular matrix A computed by singular value decomposition.
For details about the rank of a matrix, see
http://en.wikipedia.org/wiki/Matrix_rank.
To be more precise:

 rank(A) returns the number of singular values of A that are larger than
     max(size(A))*norm(A)*Modelica.Constants.eps.

 rank(A, eps) returns the number of singular values of A that are larger than \"eps\".

See also

Matrices.rcond.
"));
  end rank;

  function conditionNumber
    "Return the condition number norm(A)*norm(inv(A)) of a matrix A"
    extends Modelica.Icons.Function;

    input Real A[:, :] "Input matrix";
    input Real p(min=1) = 2
      "Type of p-norm (only allowed: 1, 2 or Modelica.Constants.inf)";
    output Real result=0.0 "Condition number of matrix A";

  protected
    Real eps=1e-25;
    Real eps2 = 10*Modelica.Constants.eps;
    Real s[size(A, 1)] "Singular values";

  algorithm
    if min(size(A)) > 0 then
      if p >= 2-eps2 and p <= 2+eps2 then
        s := Modelica.Math.Matrices.singularValues(A);
        if min(s) < eps then
          result := Modelica.Constants.inf;
        else
          result := max(s)/min(s);
        end if;
      else
        result := Modelica.Math.Matrices.norm(A, p)*Modelica.Math.Matrices.norm(
          Modelica.Math.Matrices.inv(A), p);
      end if;
    end if;

Description

This function calculates the condition number (norm(A) * norm(inv(A))) of a general real matrix A, in either the 1-norm, 2-norm or the infinity-norm.
In the case of 2-norm the result is the ratio of the largest to the smallest singular value of A.
For more details, see http://en.wikipedia.org/wiki/Condition_number.

Example

A = [1, 2;
     2, 1];
r = conditionNumber(A);

results in:

r = 3.0

See also

Matrices.rcond

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end conditionNumber;

  function rcond "Return the reciprocal condition number of a matrix"
    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)] "Square real matrix";
    input Boolean inf=false
      "Is true if infinity norm is used and false for 1-norm";
    output Real rcond "Reciprocal condition number of A";
    output Integer info "Information";
  protected
    Real LU[size(A, 1), size(A, 1)]
      "LU factorization of matrix A, returned by dgetrf";
    Real anorm "Norm of matrix A";
    String normspec=if inf then "I" else "1" "Specifies the norm 1 or inf";

  algorithm
    if min(size(A)) > 0 then
      (LU,,info) := Modelica.Math.Matrices.LAPACK.dgetrf(A);
      anorm := Modelica.Math.Matrices.LAPACK.dlange(A, normspec);
      (rcond,info) := Modelica.Math.Matrices.LAPACK.dgecon(
            LU,
            inf,
            anorm);
    else
      rcond := Modelica.Constants.inf;
      info := 0;
    end if;

Description

This function estimates the reciprocal of the condition number (norm(A) * norm(inv(A))) of a general real matrix A, in either the 1-norm or
the infinity-norm, using the LAPACK function DGECON.
If rcond(A) is near 1.0, A is well conditioned and A is ill conditioned if rcond(A) is near zero.

Example

A = [1, 2;
     2, 1];
r = rcond(A);

results in:

r = 0.3333

See also

Matrices.conditionNumber

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end rcond;

  function norm "Return the p-norm of a matrix"
    extends Modelica.Icons.Function;
    input Real A[:, :] "Input matrix";
    input Real p(min=1) = 2
      "Type of p-norm (only allowed: 1, 2 or Modelica.Constants.inf)";
    output Real result=0.0 "p-norm of matrix A";
  protected
    Real eps = 10*Modelica.Constants.eps;
  algorithm
   if min(size(A)) > 0 then
    if p >= 1-eps and p <= 1+eps then
      // column sum norm
      for i in 1:size(A, 2) loop
        result := max(result, sum(abs(A[:, i])));
      end for;
    elseif p >= 2-eps and p <= 2+eps then
      // largest singular value
      result := max(singularValues(A));
    elseif p >= Modelica.Constants.inf then
      // row sum norm
      for i in 1:size(A, 1) loop
        result := max(result, sum(abs(A[i, :])));
      end for;
    else
      assert(false, "Optional argument \"p\" (= " + String(p) + ") of function \"norm\" must be
1, 2 or Modelica.Constants.inf");
    end if;
   end if;
Matrices.norm(A, p=2);

Description

The function call \"Matrices.norm(A)\" returns the
2-norm of matrix A, i.e., the largest singular value of A.

The function call \"Matrices.norm(A, p)\" returns the
p-norm of matrix A. The only allowed values for p are

 \"p=1\": the largest column sum of A

 \"p=2\": the largest singular value of A

 \"p=Modelica.Constants.inf\": the largest row sum of A

Note, for any matrices A1, A2 the following inequality holds:

Matrices.norm(A1+A2,p) ≤ Matrices.norm(A1,p) + Matrices.norm(A2,p)

Note, for any matrix A and vector v the following inequality holds:

Vectors.norm(A*v,p) ≤ Matrices.norm(A,p)*Vectors.norm(A,p)

See also

Matrices.frobeniusNorm

"));
  end norm;

  function frobeniusNorm "Return the Frobenius norm of a matrix"
    extends Modelica.Icons.Function;
    input Real A[:, :] "Input matrix";
    output Real result "Frobenius norm of matrix A";

  algorithm
    result := if min(size(A)) > 0 then sqrt(sum(A .* A)) else 0.0;

Description

This function computes the Frobenius norm of a general real matrix A, i.e., the square root of the sum of the squares of all elements.

Example

A = [1, 2;
     2, 1];
r = frobeniusNorm(A);

results in:

r = 3.162;

See also

Matrices.norm

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end frobeniusNorm;

  function nullSpace "Return the orthonormal nullspace of a matrix"
    extends Modelica.Icons.Function;

    input Real A[:, :] "Input matrix";
    output Real Z[size(A, 2), :] "Orthonormal nullspace of matrix A";
    output Integer nullity "Nullity, i.e., the dimension of the nullspace";

  protected
    Real V[size(A, 2), size(A, 2)] "Right orthogonal matrix";
    Real sigma[min(size(A, 1), size(A, 2))] "Singular values";
    Integer rank "Rank of matrix A";
    Real eps "Tolerance for rank determination";
    Integer n=min(size(A, 1), size(A, 2));
    Integer i=n;

  algorithm
    (sigma,,V) := Modelica.Math.Matrices.singularValues(A);
    V := transpose(V);
    // rank computation
    rank := 0;
    if n > 0 then
      eps := max(size(A, 1), size(A, 2)) * max(sigma) * Modelica.Constants.eps;
      while i > 0 loop
        if sigma[i] > eps then
          rank := i;
          i := 0;
        end if;
        i := i - 1;
      end while;
    end if;
    Z := V[:, rank + 1:size(A, 2)];
    // nullspace computation
    nullity := size(A, 2) - rank;
    // nullity
(Z, nullity) = Matrices.nullspace(A);

Description

This function calculates an orthonormal basis Z=[z_1, z_2, ...] of the nullspace of a matrix A, i.e., A*z_i=0.

The nullspace is obtained by SVD method. That is, matrix A is decomposed into the matrices S, U, V:

A = U*S*transpose(V)

with the orthonormal matrices U and V and the matrix S with

S = [S1, 0]
S1 = [diag(s); 0]

and the singular values s={s1, s2, ..., sr} of A and r=rank(A). Note, that S has the same size as A. Since U and V are orthonormal we may write

transpose(U)*A*V = [S1, 0].

Matrix S1 obviously has full column rank and therefore, the left n-r rows (n is the number of columns of A or S) of matrix V span a nullspace of A.

The nullity of matrix A is the dimension of the nullspace of A. In view of the above, it becomes clear that nullity holds

nullity = n - r

with

n = number of columns of matrix A
r = rank(A)

Example

A = [1, 2,  3, 1;
     3, 4,  5, 2;
    -1, 2, -3, 3];
(Z, nullity) = nullspace(A);

results in:

Z=[0.1715;
  -0.686;
   0.1715;
   0.686]

nullity = 1

See also

Matrices.singularValues

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end nullSpace;

  function exp
    "Return the exponential of a matrix by adaptive Taylor series expansion with scaling and balancing"

    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)];
    input Real T=1;
    output Real phi[size(A, 1), size(A, 1)] "= exp(A*T)";

  protected
    parameter Integer nmax=21;
    /*max number of iterations*/
    parameter Integer na=size(A, 1);
    Integer j=1;
    Integer k=0;
    Boolean done=false;
    Real Anorm;
    Real Tscaled=1;
    Real Atransf[na, na];
    Real D[na, na];
    /*D: dummy variable for psi*/
    Real M[na, na];
    /*M: dummy matrix*/
    Real Diag[na];
    /*diagonal transformation matrix for balancing*/

    encapsulated function columnNorm "Returns the column norm of a matrix"
      import Modelica;
      extends Modelica.Icons.Function;
      input Real A[:, :] "Input matrix";
      output Real result=0.0 "1-norm of matrix A";
    algorithm
      for i in 1:size(A, 2) loop
        result := max(result, sum(abs(A[:, i])));
      end for;
    end columnNorm;

  algorithm
    // balancing of A
    (Diag,Atransf) := balance(A);

    // scaling of T until norm(A)*/(2^k) < 1
    Tscaled := T;
    /*Anorm: column-norm of matrix A*/
    Anorm := columnNorm(Atransf);
    Anorm := Anorm*T;
    while Anorm >= 0.5 loop
      Anorm := Anorm/2;
      Tscaled := Tscaled/2;
      k := k + 1;
    end while;

    // Computation of psi by Taylor-series approximation
    M := identity(na);
    D := M;
    while j < nmax and not done loop
      M := Atransf*M*Tscaled/j;
      //stop if the new element of the series is small
      if columnNorm((D + M) - D) == 0 then
        done := true;
      else
        D := M + D;
        j := j + 1;
      end if;
    end while;

    // re-scaling
    for i in 1:k loop
      D := D*D;
    end for;

    // re-balancing: psi := diagonal(Diag)*D*inv(diagonal(Diag));
    for j in 1:na loop
      for k in 1:na loop
        phi[j, k] := D[j, k]*Diag[j]/Diag[k];
      end for;
    end for;
phi = Matrices.exp(A,T=1);

Description

This function computes the exponential eAT of matrix A, i.e.

                       (AT)^2   (AT)^3
 Φ = e^(AT) = I + AT + ------ + ------ + ....
                         2!       3!

where e=2.71828..., A is an n x n matrix with real elements and T is a real number,
e.g., the sampling time.
A may be singular. With the exponential of a matrix it is, e.g., possible
to compute the solution of a linear system of differential equations

der(x) = A*x   ->   x(t0 + T) = e^(AT)*x(t0)

Algorithmic details

The algorithm is taken from

H. D. Joos, G. Grübel:

RASP'91 Regulator Analysis and Synthesis Programs

    DLR - Control Systems Group 1991

The following steps are performed to calculate the exponential of A:

Matrix A is balanced

  (= is transformed with a diagonal matrix D, such that inv(D)*A*D
  has a smaller condition as A).

The scalar T is divided by a multiple of 2 such that norm(
       inv(D)*A*D*T/2^k ) < 0.5. Note, that (1) and (2) are implemented such that no round-off errors
  are introduced.

The matrix from (2) is approximated by explicitly performing the Taylor
  series expansion with a variable number of terms.
  Truncation occurs if a new term does no longer contribute to the value of Φ
  from the previous iteration.

The resulting matrix is transformed back, by reverting the steps of (2)
  and (1).

In several sources it is not recommended to use Taylor series expansion to
calculate the exponential of a matrix, such as in 'C.B. Moler and C.F. Van Loan:
Nineteen dubious ways to compute the exponential of a matrix. SIAM Review 20,
pp. 801-836, 1979' or in the documentation of m-file expm2 in MATLAB version 6
(http://www.mathworks.com) where it is
stated that 'As a practical numerical method, this is often slow and inaccurate'.
These statements are valid for a direct implementation of the Taylor series
expansion, but not for the implementation variant used in this function.

", revisions="

Release Notes:

July 5, 2002
       by H. D. Joos and Nico Walther

       Implemented.

"));
  end exp;

  function integralExp
    "Return the exponential and the integral of the exponential of a matrix"

    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)];
    input Real B[size(A, 1), :];
    input Real T=1;
    output Real phi[size(A, 1), size(A, 1)] "= exp(A*T)";
    output Real gamma[size(A, 1), size(B, 2)] "= integral(phi)*B";
  protected
    parameter Integer nmax=21;
    /*max number of iterations*/
    parameter Integer na=size(A, 1);
    Integer j=2;
    Integer k=0;
    Boolean done=false;
    Real Anorm;
    Real Tscaled=1;
    Real Atransf[na, na];
    Real Psi[na, na];
    /*Psi: dummy variable for psi*/
    Real M[na, na];
    /*M: dummy matrix*/
    Real Diag[na];
    /*diagonal transformation matrix for balancing*/

    encapsulated function columnNorm "Returns the column norm of a matrix"
      import Modelica;
      extends Modelica.Icons.Function;
      input Real A[:, :] "Input matrix";
      output Real result=0.0 "1-norm of matrix A";
    algorithm
      for i in 1:size(A, 2) loop
        result := max(result, sum(abs(A[:, i])));
      end for;
    end columnNorm;
  algorithm
    // balancing of A
    (Diag,Atransf) := balance(A);

    // scaling of T until norm(A)*/(2^k) < 0.5
    Tscaled := T;
    /*Anorm: column-norm of matrix A*/
    // Anorm := norm(Atransf, 1);
    Anorm := columnNorm(Atransf);
    Anorm := Anorm*T;
    while Anorm >= 0.5 loop
      Anorm := Anorm/2;
      Tscaled := Tscaled/2;
      k := k + 1;
    end while;

    // Computation of psi by Taylor-series approximation
    M := identity(na)*Tscaled;
    Psi := M;
    while j < nmax and not done loop
      M := Atransf*M*Tscaled/j;
      //stop if the new element of the series is small
      // if norm((Psi + M) - Psi, 1) == 0 then
      if columnNorm((Psi + M) - Psi) == 0 then
        done := true;
      else
        Psi := M + Psi;
        j := j + 1;
      end if;
    end while;

    // re-scaling
    for j in 1:k loop
      Psi := Atransf*Psi*Psi + 2*Psi;
    end for;

    // re-balancing: psi := diagonal(Diag)*D*inv(diagonal(Diag));
    for j in 1:na loop
      for k in 1:na loop
        Psi[j, k] := Psi[j, k]*Diag[j]/Diag[k];
      end for;
    end for;
    gamma := Psi*B;
    phi := A*Psi + identity(na);
(phi,gamma) = Matrices.integralExp(A,B,T=1);

Description

This function computes the exponential phi = e^(AT) of matrix A
and the integral gamma = integral(phi*dt)*B.

The function uses a Taylor series expansion with Balancing and
scaling/squaring to approximate the integral Ψ of the matrix
exponential Φ=e^(AT):

                         AT^2   A^2 * T^3          A^k * T^(k+1)
Ψ = int(e^(As))ds = IT + ---- + --------- + ... + --------------
                          2!        3!                (k+1)!

Φ is calculated through Φ = I + A*Ψ, so A may be singular. Γ is
simply Ψ*B.

The algorithm runs in the following steps:

Balancing

Scaling

Taylor series expansion

Re-scaling

Re-Balancing

Balancing put the bad condition of a square matrix A into a diagonal
transformation matrix D. This reduce the effort of following calculations.
Afterwards the result have to be re-balanced by transformation D*Atransf
*inv(D).

Scaling halfen T  k-times, until the norm of A*T is less than 0.5. This
guarantees minimum rounding errors in the following series
expansion. The re-scaling based on the equation  exp(A*2T) = exp(AT)^2.
The needed re-scaling formula for psi thus becomes:

      Φ = Φ'*Φ'
I + A*Ψ = I + 2A*Ψ' + A^2*Ψ'^2
      Ψ = A*Ψ'^2 + 2*Ψ'

where psi' is the scaled result from the series expansion while psi is the
re-scaled matrix.

The function is normally used to discretize a state-space system as the
zero-order-hold equivalent:

x(k+1) = Φ*x(k) + Γ*u(k)
  y(k) = C*x(k) + D*u(k)

The zero-order-hold sampling, also known as step-invariant method, gives
exact values of the state variables, under the assumption that the control
signal u is constant between the sampling instants. Zero-order-hold sampling
is described in

K. J. Åström, B. Wittenmark:

Computer Controlled Systems - Theory and Design

    Third Edition, p. 32

Syntax:
      (phi,gamma) = Matrices.expIntegral(A,B,T)
                       A,phi: [n,n] square matrices
                     B,gamma: [n,m] input matrix
                           T: scalar, e.g., sampling time

The Algorithm to calculate psi is taken from

H. D. Joos, G. Grübel:

RASP'91 Regulator Analysis and Synthesis Programs

    DLR - Control Systems Group 1991

", revisions="

Release Notes:

July 5, 2002
       by H. D. Joos and Nico Walther

       Implemented.

"));
  end integralExp;

  function integralExpT
    "Return the exponential, the integral of the exponential, and time-weighted integral of the exponential of a matrix"

    extends Modelica.Icons.Function;
    input Real A[:, size(A, 1)];
    input Real B[size(A, 1), :];
    input Real T=1;
    output Real phi[size(A, 1), size(A, 1)] "= exp(A*T)";
    output Real gamma[size(A, 1), size(B, 2)] "= integral(phi)*B";
    output Real gamma1[size(A, 1), size(B, 2)] "= integral((T-t)*exp(A*t))*B";
  protected
    Integer nmax=200;
    /*max number of iterations*/
    parameter Integer na=size(A, 1);
    parameter Integer nb=size(B, 2);
    Integer j=1;
    Boolean done=false;
    Real F[na + 2*nb, na + 2*nb];

  algorithm
    F := [A, B, zeros(na, nb); zeros(2*nb, na), zeros(2*nb, nb), [identity(nb);
      zeros(nb, nb)]];
    F := exp(F, T);
    phi := F[1:na, 1:na];
    gamma := F[1:na, na + 1:na + nb];
    gamma1 := F[1:na, na + nb + 1:na + 2*nb];
(phi,gamma,gamma1) = Matrices.integralExpT(A,B,T=1);

Description

This function computes the exponential phi = e^(AT) of matrix A
and the integral gamma = integral(phi*dt)*B and the integral
integral((T-t)*exp(A*t)*dt)*B, where A is a square (n,n) matrix and
B, gamma, and gamma1 are (n,m) matrices.

The function calculates the matrices phi,gamma,gamma1 through the equation:

                                 [ A B 0 ]
[phi gamma gamma1] = [I 0 0]*exp([ 0 0 I ]*T)
                                 [ 0 0 0 ]

The matrices define the discretized first-order-hold equivalent of
a state-space system:

x(k+1) = phi*x(k) + gamma*u(k) + gamma1/T*(u(k+1) - u(k))

The first-order-hold sampling, also known as ramp-invariant method, gives
more smooth control signals as the ZOH equivalent. First-order-hold sampling
is, e.g., described in

K. J. Åström, B. Wittenmark:

Computer Controlled Systems - Theory and Design

    Third Edition, p. 256

", revisions="

Release Notes:

July 31, 2002
       by Nico Walther

       Realized.

"));
  end integralExpT;

  //
  // protected

  function continuousLyapunov
    "Return solution X of the continuous-time Lyapunov equation X*A + A'*X = C"
    extends Modelica.Icons.Function;
    import Modelica.Math.Matrices;

    input Real A[:, size(A, 1)] "Square matrix A in X*A + A'*X = C";
    input Real C[size(A, 1), size(A, 2)] "Square matrix C in X*A + A'*X = C";
    input Boolean ATisSchur=false
      "= true, if transpose(A) has already real Schur form";
    input Real eps=Modelica.Math.Matrices.norm(A, 1)*10*1e-15 "Tolerance eps";

  protected
    Integer n=size(A, 1);
    Real R[size(A, 1), size(A, 2)] "rsf of A', i.e., R=U'A'U";
    Real U[size(A, 1), size(A, 2)] "Transformation matrix U for R=U'A'U";
    Real D[size(A, 1), size(A, 2)] "Matrix D=U'*C*U";
    Real R11[size(A, 1), size(A, 2)];
    Real R22[size(A, 1), size(A, 2)];
    Real R12[size(A, 1), size(A, 2)];
    Real R21[size(A, 1), size(A, 2)];
    Real R2[2*size(A, 1), 2*size(A, 2)];
    Real I[size(A, 1), size(A, 1)]=identity(size(A, 1));
    Real y[2*size(A, 1)];
    Real c[2*size(A, 1)];
    Real CC[size(A, 1), 2];
    Integer k;

  public
    output Real X[size(A, 1), size(A, 2)]
      "Solution X of the Lyapunov equation X*A + A'*X = C";

  algorithm
    if n > 1 then
      if ATisSchur then
        R := transpose(A);
        U := identity(n);
        D := C;
      else
        (R,U) := Modelica.Math.Matrices.realSchur(transpose(A));
        D := transpose(U)*C*U;
      end if;

      X := zeros(n, n);

      // Calculate the last 1 or 2 columns of X
      R22 := R;
      for i1 in 1:n loop
        R22[i1, i1] := R[i1, i1] + R[n, n];
      end for;
      if abs(R[n, n - 1]) < eps then
        X[:, n] := Matrices.solve(R22, D[:, n]);
        k := n - 1;
      else
        R11 := R;
        R12 := zeros(n, n);
        R21 := zeros(n, n);
        for i1 in 1:n loop
          R11[i1, i1] := R[i1, i1] + R[n - 1, n - 1];
          R12[i1, i1] := R[n - 1, n];
          R21[i1, i1] := R[n, n - 1];
        end for;

        // solve 2nx2n equation for 2x2 Schur bump with Kronecker product and vec operator approach
        R2 := [R11, R12; R21, R22];
        c := cat(
              1,
              D[:, n - 1],
              D[:, n]);
        y := Matrices.solve(R2, c);
        X[:, n - 1] := y[1:n];
        X[:, n] := y[n + 1:2*n];
        k := n - 2;
      end if;

      // Calculate the rest of X
      while k > 1 loop
        R22 := R;
        for i1 in 1:n loop
          R22[i1, i1] := R[i1, i1] + R[k, k];
        end for;
        if abs(R[k, k - 1]) < eps then
          //real eigenvalue
          X[:, k] := Matrices.solve(R22, D[:, k] - vector(X[:, k + 1:n]*matrix(
            R[k, k + 1:n])));
          k := k - 1;
        else
          // conjugated complex eigenvalues
          R11 := R;
          R12 := zeros(n, n);
          R21 := zeros(n, n);
          for i1 in 1:n loop
            R11[i1, i1] := R[i1, i1] + R[k - 1, k - 1];
            R12[i1, i1] := R[k - 1, k];
            R21[i1, i1] := R[k, k - 1];
          end for;
          R2 := [R11, R12; R21, R22];
          CC := D[:, k - 1:k] - X[:, k + 1:n]*transpose(R[k - 1:k, k + 1:n]);
          c := cat(
                1,
                CC[:, 1],
                CC[:, 2]);
          y := Matrices.solve(R2, c);
          X[:, k - 1] := y[1:n];
          X[:, k] := y[n + 1:2*n];

          k := k - 2;
        end if;
      end while;
      // k=1 or k=0

      // if k=1 the first column (if there exist a real eigenvalue) has to be calculated separately
      if k == 1 then
        R22 := R;
        for i1 in 1:n loop
          R22[i1, i1] := R[i1, i1] + R[1, 1];
        end for;
        X[:, 1] := Matrices.solve(R22, D[:, 1] - vector(X[:, 2:n]*matrix(R[1, 2
          :n])));
      end if;

      // transform X corresponding to the original form
      if not ATisSchur then
        X := U*X*transpose(U);
      end if;

    elseif n == 1 then
      //simple scalar equation
      X[1, 1] := C[1, 1]/(2*A[1, 1]);
    else
      X := fill(
            0,
            0,
            0);
    end if;
X = Matrices.continuousLyapunov(A, C, ATisSchur, eps);

Description

This function computes the solution X of the continuous-time Lyapunov equation

X*A + A'*X = C

using the Schur method for Lyapunov equations proposed by Bartels and Stewart [1].

In a nutshell, the problem is reduced to the corresponding problem

Y*R' + R*Y = D

with R=U'*A'*U is the real Schur form of A' and D=U'*C*U and Y=U'*X*U
are the corresponding transformations of C and X. This problem is solved sequentially for the 1x1 or 2x2 Schur blocks by exploiting the block triangular form of R.
Finally the solution of the original problem is recovered as X=U*Y*U'.

The Boolean input \"ATisSchur\" indicates to omit the transformation to Schur in the case that A' has already Schur form.

References

[1] Bartels, R.H. and Stewart G.W.
    Algorithm 432: Solution of the matrix equation AX + XB = C.
    Comm. ACM., Vol. 15, pp. 820-826, 1972.

Example

A = [1, 2,  3,  4;
     3, 4,  5, -2;
    -1, 2, -3, -5;
     0, 2,  0,  6];

C =  [-2, 3, 1, 0;
      -6, 8, 0, 1;
       2, 3, 4, 5;
      0, -2, 0, 0];

X = continuousLyapunov(A, C);

results in:

X = [1.633, -0.761,  0.575, -0.656;
    -1.158,  1.216,  0.047,  0.343;
    -1.066, -0.052, -0.916,  1.61;
    -2.473,  0.717, -0.986,  1.48]

See also

Matrices.continuousSylvester,
Matrices.discreteLyapunov

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end continuousLyapunov;

  function continuousSylvester
    "Return solution X of the continuous-time Sylvester equation A*X + X*B = C"
    extends Modelica.Icons.Function;
    import Modelica.Math.Matrices;

    input Real A[:, :] "Square matrix A";
    input Real B[:, :] "Square matrix B";
    input Real C[size(A, 1), size(B, 2)] "Matrix C";
    input Boolean AisSchur=false "= true, if A has already real Schur form";
    input Boolean BisSchur=false "= true, if B has already real Schur form";
    output Real X[size(A, 1), size(B, 2)]
      "Solution of the continuous Sylvester equation";

  protected
    Integer n=size(A, 1);
    Integer m=size(B, 1);
    Real S[size(A, 1), size(A, 2)];
    Real T[size(B, 1), size(B, 2)];
    Real U[size(A, 1), size(A, 1)];
    Real V[size(B, 1), size(B, 1)];
    Real Chat[size(C, 1), size(C, 2)];
    Real scale;
    Integer info;

  algorithm
    if n > 1 and m > 1 then
      if AisSchur then
        S := A;
        U := identity(n);
      else
        (S,U) := Modelica.Math.Matrices.realSchur(A);
      end if;
      if BisSchur then
        T := B;
        V := identity(m);
      else
        (T,V) := Modelica.Math.Matrices.realSchur(B);
      end if;

      Chat := if AisSchur and BisSchur then C else if AisSchur then C*V else
        if BisSchur then transpose(U)*C else transpose(U)*C*V;
      (X,scale,info) := Matrices.LAPACK.dtrsyl(
            S,
            T,
            Chat);
      assert(info == 0, "Solving of Sylvester equation with Matrices.continuousSylvester was not successful.\n
                    The value of info is " + String(info) + ", but should be zero. A value unequal to zero means:\n
            < 0: if INFO = -i, the i-th argument had an illegal value\n
            = 1: A and B have common or very close eigenvalues; perturbed
                 values were used to solve the equation (but the matrices
                 A and B are unchanged).");
      X := if AisSchur and BisSchur then scale*X else if AisSchur then scale*X*
        transpose(V) else if BisSchur then scale*U*X else scale*U*X*transpose(V);
    else
      X := fill(
            0,
            n,
            m);
    end if;
X = Matrices.continuousSylvester(A, B, C, AisSchur, BisSchur);

Description

Function continuousSylvester computes the solution X of the continuous-time Sylvester equation

A*X + X*B = C.

using the Schur method for Sylvester equations proposed by Bartels and Stewart [1].

In a nutshell, the problem is reduced to the corresponding problem

S*Y + Y*T = D.

with S=U'*A*U is the real Schur of A,  T=V'*T*V is the real Schur form of B and
D=U'*C*V and Y=U*X*V'
are the corresponding transformations of C and X. This problem is solved sequentially by exploiting the block triangular form of S and T.
Finally the solution of the original problem is recovered as X=U'*Y*V.

The Boolean inputs \"AisSchur\" and \"BisSchur\" indicate to omit one or both of the transformation to Schur in the case that A and/or B have already Schur form.

The function applies LAPACK-routine DTRSYL. See LAPACK.dtrsyl
for more information.

References

[1] Bartels, R.H. and Stewart G.W.
    Algorithm 432: Solution of the matrix equation AX + XB = C.
    Comm. ACM., Vol. 15, pp. 820-826, 1972.

Example

A = [17.0,   24.0,   1.0,   8.0,   15.0 ;
     23.0,    5.0,   7.0,  14.0,   16.0 ;
      0.0,    6.0,  13.0,  20.0,   22.0;
      0.0,    0.0,  19.0,  21.0,    3.0 ;
      0.0,    0.0,   0.0,   2.0,    9.0];

B =  [8.0, 1.0, 6.0;
      0.0, 5.0, 7.0;
      0.0, 9.0, 2.0];

C = [62.0,  -12.0, 26.0;
     59.0,  -10.0, 31.0;
     70.0,  -6.0,   9.0;
     35.0,  31.0,  -7.0;
     36.0, -15.0,   7.0];

X = continuousSylvester(A, B, C);

results in:

X = [0.0,  0.0,  1.0;
     1.0,  0.0,  0.0;
     0.0,  1.0,  0.0;
     1.0,  1.0, -1.0;
     2.0, -2.0,  1.0];

See also

Matrices.discreteSylvester,
Matrices.continuousLyapunov

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end continuousSylvester;

  function continuousRiccati
    "Return solution X of the continuous-time algebraic Riccati equation A'*X + X*A - X*B*inv(R)*B'*X + Q = 0 (care)"
    extends Modelica.Icons.Function;
    import Modelica.Math.Matrices;

    input Real A[:, size(A, 1)] "Square matrix A in CARE";
    input Real B[size(A, 1), :] "Matrix B in CARE";
    input Real R[size(B, 2), size(B, 2)]=identity(size(B, 2))
      "Matrix R in CARE";
    input Real Q[size(A, 1), size(A, 1)]=identity(size(A, 1))
      "Matrix Q in CARE";
    input Boolean refine=false "True for subsequent refinement";

  protected
    Integer n=size(A, 1);
    Real G[size(A, 1), size(A, 1)]=B*Modelica.Math.Matrices.solve2(R, transpose(
        B));
    Real H[2*size(A, 1), 2*size(A, 1)]=[A, -G; -Q, -transpose(A)];
    Real H_RSF[2*size(A, 1), 2*size(A, 1)]=H;
    Real Z[size(H, 1), size(H, 2)];
    Real Z11[size(A, 1), size(A, 2)];
    Real Z21[size(A, 1), size(A, 2)];

    Integer info;

  public
    output Real X[size(A, 1), size(A, 2)] "Stabilizing solution of CARE";
    output Real alphaReal[2*size(A, 1)]
      "Real parts of eigenvalue=alphaReal+i*alphaImag";
    output Real alphaImag[2*size(A, 1)]
      "Imaginary parts of eigenvalue=alphaReal+i*alphaImag";
  algorithm
    if n > 1 then
      (H_RSF,Z,alphaReal,alphaImag) := Modelica.Math.Matrices.realSchur(H);
      (H_RSF,Z,alphaReal,alphaImag) := Matrices.Utilities.reorderRSF(
            H_RSF,
            Z,
            alphaReal,
            alphaImag,
            true);

      Z11 := Z[1:n, 1:n];
      Z21 := Z[n + 1:2*n, 1:n];
      if size(Z11, 1) > 0 then

        (X,info) := Matrices.LAPACK.dgesvx(Z11, transpose(Z21));
        //this function does not need to transpose Z11 as solve2 does with //  X := transpose(Matrices.solve2(transpose(Z11), transpose(Z21)));
        assert(info == 0,
          "Solving a linear system of equations with function \"Matrices.LAPACK.dgesvx\" is not possible, because the system has either no or infinitely many solutions (input A is singular).");
        X := transpose(X);

        if refine then
          X := Modelica.Math.Matrices.Utilities.continuousRiccatiIterative(
                A,
                B,
                R,
                Q,
                X);
        end if;
      else
        X := fill(
              0,
              size(Z21, 1),
              size(Z11, 1));
      end if;

    elseif n == 1 then
      X := matrix((A[1, 1] - sqrt(A[1, 1]*A[1, 1] + G[1, 1]*Q[1, 1]))/G[1, 1]);
      if X[1, 1]*G[1, 1] < A[1, 1] then
        X := matrix((A[1, 1] + sqrt(A[1, 1]*A[1, 1] + G[1, 1]*Q[1, 1]))/G[1, 1]);
      end if;
    else
      X := fill(
            0,
            0,
            0);
    end if;
(X, alphaReal, alphaImag) = Matrices.continuousRiccati(A, B, R, Q, true);

Description

Function continuousRiccati computes the solution X of the continuous-time algebraic Riccati equation

A'*X + X*A - X*G*X + Q = 0

with G = B*inv(R)*B'
using the Schur vector approach proposed by Laub [1].

It is assumed that Q is symmetric and positive semidefinite and R is symmetric, nonsingular and positive definite,
(A,B) is stabilizable and (A,Q) is detectable.

These assumptions are not checked in this function !!

The assumptions guarantee that the Hamiltonian matrix

H = [A, -G; -Q, -A']

has no pure imaginary eigenvalue and can be put
to an ordered real Schur form

U'*H*U = S = [S11, S12; 0, S22]

with orthogonal similarity transformation U. S is ordered in such a way,
that S11 contains the n stable eigenvalues of the closed loop system with system matrix
A - B*inv(R)*B'*X.
If U is partitioned to

U = [U11, U12; U21, U22]

with dimensions according to S, the solution X is calculated by

X*U11 = U21.

With optional input refinement=true a subsequent iterative refinement based on Newton's method with exact line search is applied.
See continuousRiccatiIterative
for more information.

References

[1] Laub, A.J.
    A Schur Method for Solving Algebraic Riccati equations.
    IEEE Trans. Auto. Contr., AC-24, pp. 913-921, 1979.

Example

A = [0.0, 1.0;
     0.0, 0.0];

B = [0.0;
     1.0];

R = [1];

Q = [1.0, 0.0;
     0.0, 2.0];

X = continuousRiccati(A, B, R, Q);

results in:

X = [2.0, 1.0;
     1.0, 2.0];

See also

Matrices.Utilities.continuousRiccatiIterative,
Matrices.discreteRiccati

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end continuousRiccati;

  function discreteLyapunov
    "Return solution X of the discrete-time Lyapunov equation A'*X*A + sgn*X = C"
    extends Modelica.Icons.Function;
    import Modelica.Math.Matrices;

    input Real A[:, size(A, 1)] "Square matrix A in A'*X*A + sgn*X = C";
    input Real C[size(A, 1), size(A, 2)]
      "Square matrix C in A'*X*A + sgn*X = C";
    input Boolean ATisSchur=false
      "= true, if transpose(A) has already real Schur form";
    input Integer sgn=1 "Specifies the sign in A'*X*A + sgn*X = C";
    input Real eps=Matrices.norm(A, 1)*10*Modelica.Constants.eps
      "Tolerance eps";

  protected
    Integer n=size(A, 1);
    Real R[size(A, 1), size(A, 2)] "RSF of A', i.e., R=U'A'U";
    Real U[size(A, 1), size(A, 2)] "Transformation matrix U for R=U'A'U";
    Real D[size(A, 1), size(A, 2)] "Matrix D=U'*C*U";
    Real R22[size(A, 1), size(A, 2)];
    Real R11[size(A, 1), size(A, 2)];
    Integer k;

    Real g[size(A, 1)];
    Real w[size(A, 1)];
    Real y[2*size(A, 1)];
    Boolean crit;

  public
    output Real X[size(A, 1), size(A, 2)]
      "Solution X of the Lyapunov equation A'*X*A + sgn*X = C";

  algorithm
    assert(sgn == 1 or sgn == -1,
      "Input sgn in function Math.Matrices.discreteLyapunov() must be 1 or -1, however it is "
       + String(sgn));
    X := zeros(n, n);
    k := n;
    if n > 1 then
      if ATisSchur then
        R := transpose(A);
        U := identity(n);
        D := C;
      else
        (R,U) := Modelica.Math.Matrices.realSchur(transpose(A));
        D := transpose(U)*C*U;
      end if;

      while k > 0 loop
        w := D[:, k] - R*X[:, k + 1:n]*R[k, k + 1:n];
        crit := if k > 1 then abs(R[k, k - 1]) < eps else false;
        if (k == 1 or crit) then
          R22 := R[k, k]*R;
          for i in 1:n loop
            R22[i, i] := R22[i, i] + sgn;
          end for;
          X[:, k] := Matrices.solve(R22, w);
          k := k - 1;
        else
          g := D[:, k - 1] - R*X[:, k + 1:n]*R[k - 1, k + 1:n];
          R11 := R[k - 1, k - 1]*R;
          R22 := R[k, k]*R;
          for i in 1:n loop
            R11[i, i] := R11[i, i] + sgn;
            R22[i, i] := R22[i, i] + sgn;
          end for;
          y := Matrices.solve([R11, R[k - 1, k]*R; R[k, k - 1]*R, R22], cat(
                1,
                g,
                w));
          X[:, k - 1] := y[1:n];
          X[:, k] := y[n + 1:2*n];
          k := k - 2;
        end if;
      end while;

      // transform X corresponding to the original form
      if not ATisSchur then
        X := U*X*transpose(U);
      end if;

    elseif n == 1 then
      X[1, 1] := C[1, 1]/(A[1, 1]*A[1, 1] + sgn);
    else
      X := fill(
            0,
            0,
            0);
    end if;
X = Matrices.discreteLyapunov(A, C, ATisSchur, sgn, eps);

Description

This function computes the solution X of the discrete-time Lyapunov equation

A'*X*A + sgn*X = C

where sgn=1 or sgn =-1. For sgn = -1, the discrete Lyapunov equation is a special case of the Stein equation:

A*X*B - X + Q = 0.

The algorithm uses the Schur method for Lyapunov equations proposed by Bartels and Stewart [1].

In a nutshell, the problem is reduced to the corresponding problem

R*Y*R' + sgn*Y = D.

with R=U'*A'*U is the real Schur form of A' and D=U'*C*U and Y=U'*X*U
are the corresponding transformations of C and X. This problem is solved sequentially by exploiting the block triangular form of R.
Finally the solution of the original problem is recovered as X=U*Y*U'.

The Boolean input \"ATisSchur\" indicates to omit the transformation to Schur in the case that A' has already Schur form.

References

[1] Bartels, R.H. and Stewart G.W.
    Algorithm 432: Solution of the matrix equation AX + XB = C.
    Comm. ACM., Vol. 15, pp. 820-826, 1972.

Example

A = [1, 2,  3,  4;
     3, 4,  5, -2;
    -1, 2, -3, -5;
     0, 2,  0,  6];

C =  [-2,  3, 1, 0;
      -6,  8, 0, 1;
       2,  3, 4, 5;
       0, -2, 0, 0];

X = discreteLyapunov(A, C, sgn=-1);

results in:

X  = [7.5735,   -3.1426,  2.7205, -2.5958;
     -2.6105,    1.2384, -0.9232,  0.9632;
      6.6090,   -2.6775,  2.6415, -2.6928;
     -0.3572,    0.2298,  0.0533, -0.27410];

See also

Matrices.discreteSylvester,
Matrices.continuousLyapunov

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end discreteLyapunov;

  function discreteSylvester
    "Return solution of the discrete-time Sylvester equation A*X*B + sgn*X = C"
    extends Modelica.Icons.Function;
    import Modelica.Math.Matrices;

    input Real A[:, size(A, 1)] "Square matrix A in A*X*B + sgn*X = C";
    input Real B[:, size(B, 1)] "Square matrix B in A*X*B + sgn*X = C";
    input Real C[size(A, 2), size(B, 1)]
      "Rectangular matrix C in A*X*B + sgn*X = C";
    input Boolean AisHess=false "= true, if A has already Hessenberg form";
    input Boolean BTisSchur=false "= true, if B' has already real Schur form";
    input Integer sgn=1 "Specifies the sign in A*X*B + sgn*X = C";
    input Real eps=Matrices.norm(A, 1)*10*Modelica.Constants.eps "Tolerance";

  protected
    Integer n=size(A, 1);
    Integer m=size(B, 1);
    Real H[size(A, 1), size(A, 1)] "Hessenberg form of A, i.e., H=U'AU";
    Real U[size(A, 1), size(A, 1)] "Transformation matrix U for H=U'AU";
    Real S[size(B, 1), size(B, 1)] "RSF form of B, i.e., S=Z'BZ";
    Real Z[size(B, 1), size(B, 1)] "Transformation matrix Z for S=Z'BZ";
    Real F[size(A, 1), size(B, 1)]
      "Appropriate transformation of the right side C, F=U'*C*Z";

    Real R22[size(A, 1), size(A, 1)];
    Real R11[size(A, 1), size(A, 1)];
    Integer k;

    Real w[size(A, 1)];
    Real g[size(A, 1)];
    Real y[2*size(A, 1)];
    Boolean crit;

  public
    output Real X[size(A, 2), size(B, 1)]
      "solution of the discrete Sylvester equation A*X*B + sgn*X = C";

  algorithm
    assert(sgn == 1 or sgn == -1,
      "Input sgn in function Math.Matrices.discreteLyapunov() must be 1 or -1, however it is "
       + String(sgn));
    X := zeros(n, m);
    k := m;

    if n > 1 and m > 1 then
      if AisHess then
        H := A;
        U := identity(n);
        if BTisSchur then
          S := B;
          Z := identity(m);
          F := C;
        else
          (S,Z) := Matrices.realSchur(transpose(B));
          S := transpose(S);
          F := C*Z;
        end if;
      else
        (H,U) := Matrices.hessenberg(A);
        if BTisSchur then
          S := B;
          Z := identity(m);
          F := transpose(U)*C;
        else
          (S,Z) := Matrices.realSchur(transpose(B));
          S := transpose(S);
          F := transpose(U)*C*Z;
        end if;
      end if;

      while k > 0 loop

        w := F[:, k] - H*X[:, k + 1:m]*S[k + 1:m, k];
        crit := if k > 1 then abs(S[k - 1, k]) < eps else false;

        if (k == 1 or crit) then
          //real eigenvalue in Schur form
          R22 := S[k, k]*H;
          for i in 1:n loop
            R22[i, i] := R22[i, i] + sgn;
          end for;
          X[:, k] := Matrices.solve(R22, w);
          // solve one column in X for one real eigenvalue
          k := k - 1;
        else
          // pair of complex eigenvalues, i.e., 2x2 Schur bump
          g := F[:, k - 1] - H*X[:, k + 1:m]*S[k + 1:m, k - 1];
          R22 := S[k, k]*H;
          R11 := S[k - 1, k - 1]*H;
          for i in 1:n loop
            R11[i, i] := R11[i, i] + sgn;
            R22[i, i] := R22[i, i] + sgn;
          end for;
          y := Matrices.solve([R11, S[k, k - 1]*H; S[k - 1, k]*H, R22], cat(
                1,
                g,
                w));
          // solve two columns in X for one conjugated complex pole pair
          X[:, k - 1] := y[1:n];
          X[:, k] := y[n + 1:2*n];
          k := k - 2;
        end if;
      end while;

      // transform X corresponding to the original form
      if not (AisHess and BTisSchur) then
        X := if AisHess then X*transpose(Z) else if BTisSchur then U*X else U*X
          *transpose(Z);
      end if;

    elseif n == 1 and m == 1 then
      // simple scalar equation
      X[1, 1] := C[1, 1]/(A[1, 1]*B[1, 1] + sgn);
    else
      X := fill(
            0,
            0,
            0);
    end if;
X = Matrices.discreteSylvester(A, B, C, AisHess, BTisSchur, sgn, eps);

Description

Function discreteSylvester computes the solution X of the discrete-time Sylvester equation

A*X*B + sgn*X = C.

where sgn = 1 or sgn = -1. The algorithm applies the Hessenberg-Schur method proposed by Golub et al [1].
For sgn = -1, the discrete Sylvester equation is also known as Stein equation:

A*X*B - X + Q = 0.

In a nutshell, the problem is reduced to the corresponding problem

H*Y*S' + sgn*Y = F.

with H=U'*A*U is the Hessenberg form of A and S=V'*B'*V is the real Schur form of B',
F=U'*C*V and Y=U*X*V'
are appropriate transformations of C and X. This problem is solved sequentially by exploiting the specific forms of S and H.
Finally the solution of the original problem is recovered as X=U'*Y*V.

The Boolean inputs \"AisHess\" and \"BTisSchur\" indicate to omit one or both of the transformation to Hessenberg form or Schur form respectively in the case that A and/or B have already Hessenberg form or Schur respectively.

References

[1] Golub, G.H., Nash, S. and Van Loan, C.F.
    A Hessenberg-Schur method for the problem AX + XB = C.
    IEEE Transaction on Automatic Control, AC-24, no. 6, pp. 909-913, 1979.

Example

A = [1.0,   2.0,   3.0;
     6.0,   7.0,   8.0;
     9.0,   2.0,   3.0];

B = [7.0,   2.0,   3.0;
     2.0,   1.0,   2.0;
     3.0,   4.0,   1.0];

C = [271.0,   135.0,   147.0;
     923.0,   494.0,   482.0;
     578.0,   383.0,   287.0];

X = discreteSylvester(A, B, C);

results in:
X = [2.0,   3.0,   6.0;
     4.0,   7.0,   1.0;
     5.0,   3.0,   2.0];

See also

Matrices.continuousSylvester,
Matrices.discreteLyapunov

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end discreteSylvester;

  function discreteRiccati
    "Return solution of discrete-time algebraic Riccati equation A'*X*A - X - A'*X*B*inv(R + B'*X*B)*B'*X*A + Q = 0 (dare)"
    extends Modelica.Icons.Function;
    import Modelica.Math.Matrices;
    input Real A[:, size(A, 1)] "Square matrix A in DARE";
    input Real B[size(A, 1), :] "Matrix B in DARE";
    input Real R[size(B, 2), size(B, 2)]=identity(size(B, 2))
      "Matrix R in DARE";
    input Real Q[size(A, 1), size(A, 1)]=identity(size(A, 1))
      "Matrix Q in DARE";
    input Boolean refine=false "True for subsequent refinement";

  protected
    Integer n=size(A, 1);
    Real G[size(A, 1), size(A, 1)]=B*Matrices.solve2(R, transpose(B));
    Real AT[size(A, 1), size(A, 1)]=transpose(A);
    Real LU[size(A, 1), size(A, 1)];
    Integer p[size(A, 1)];
    Real H[2*size(A, 1), 2*size(A, 1)];
    Real H11[size(A, 1), size(A, 1)];
    Real H12[size(A, 1), size(A, 1)];
    Real H21[size(A, 1), size(A, 1)];
    Real H22[size(A, 1), size(A, 1)];
    Real H_RSF[2*size(A, 1), 2*size(A, 1)];
    Real Z[size(H, 1), size(H, 2)];
    Real Z11[size(A, 1), size(A, 2)];
    Real Z21[size(A, 1), size(A, 2)];

    Integer info;

  public
    output Real X[size(A, 1), size(A, 2)]
      "orthogonal matrix of the Schur vectors associated to ordered rsf";
    output Real alphaReal[2*size(A, 1)]
      "Real part of eigenvalue=alphaReal+i*alphaImag";
    output Real alphaImag[2*size(A, 1)]
      "Imaginary part of eigenvalue=alphaReal+i*alphaImag";
  algorithm
    (LU,p) := Modelica.Math.Matrices.LU(AT);
    H21 := Modelica.Math.Matrices.LU_solve2(
          LU,
          p,
          -Q);
    H22 := Modelica.Math.Matrices.LU_solve2(
          LU,
          p,
          identity(n));
    (LU,p) := Modelica.Math.Matrices.LU(A);
    H12 := Modelica.Math.Matrices.LU_solve2(
          LU,
          p,
          -G);
    H12 := transpose(H12);
    H11 := A - H12*Q;
    H := [H11, H12; H21, H22];
    (H_RSF,Z,alphaReal,alphaImag) := Modelica.Math.Matrices.realSchur(H);
    // put H to Schur form
    (H_RSF,Z,alphaReal,alphaImag) := Matrices.Utilities.reorderRSF(
          H_RSF,
          Z,
          alphaReal,
          alphaImag,
          false);
    // ordered Schur form
    Z11 := Z[1:n, 1:n];
    Z21 := Z[n + 1:2*n, 1:n];
    if size(Z11, 1) > 0 then
      //  X := transpose(Matrices.solve2(transpose(Z11), transpose(Z21)));
      (X,info) := Matrices.LAPACK.dgesvx(Z11, transpose(Z21));
      //function does not need to transpose Z11 as solve2 does
      X := transpose(X);
      assert(info == 0, "Solving a linear system of equations with function
\"Matrices.LAPACK.dgesvx\" is not possible, because the system has either
no or infinitely many solutions (input A is singular).");

      if refine then
        X := Modelica.Math.Matrices.Utilities.discreteRiccatiIterative(
              A,
              B,
              R,
              Q,
              X);
      end if;
    else
      X := fill(
            0,
            size(Z21, 1),
            size(Z11, 1));
    end if;
(X, alphaReal, alphaImag) = Matrices.discreteRiccati(A, B, R, Q, true);

Description

Function discreteRiccati computes the solution X of the discrete-time algebraic Riccati equation

A'*X*A - X - A'*X*B*inv(R + B'*X*B)*B'*X*A + Q = 0

using the Schur vector approach proposed by Laub [1].

It is assumed that Q is symmetric and positive semidefinite and R is symmetric, nonsingular and positive definite,
(A,B) is stabilizable and (A,Q) is detectable. Using this method, A has also to be invertible.

These assumptions are not checked in this function !!!

The assumptions guarantee that the Hamiltonian matrix.

H = [A + G*T*Q, -G*T; -T*Q, T]

with

     -T
T = A

and

       -1
G = B*R *B'

has no eigenvalue on the unit circle and can be put
to an ordered real Schur form

U'*H*U = S = [S11, S12; 0, S22]

with orthogonal similarity transformation U. S is ordered in such a way,
that S11 contains the n stable eigenvalues of the closed loop system with system matrix

                  -1
A - B*(R + B'*X*B)  *B'*X*A

If U is partitioned to

U = [U11, U12; U21, U22]

according to S, the solution X can be calculated by

X*U11 = U21.

References

[1] Laub, A.J.
    A Schur Method for Solving Algebraic Riccati equations.
    IEEE Trans. Auto. Contr., AC-24, pp. 913-921, 1979.

Example

A  = [4.0    3.0]
     -4.5,  -3.5];

B  = [ 1.0;
      -1.0];

R = [1.0];

Q = [9.0, 6.0;
     6.0, 4.0]

X = discreteRiccati(A, B, R, Q);

  results in:

X = [14.5623, 9.7082;
      9.7082, 6.4721];

See also

Matrices.continuousRiccati

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end discreteRiccati;

  function sort
    "Sort the rows or columns of a matrix in ascending or descending order"
    extends Modelica.Icons.Function;
    input Real M[:, :] "Matrix to be sorted";
    input Boolean sortRows=true "= true, if rows are sorted, otherwise columns";
    input Boolean ascending=true
      "= true, if ascending order, otherwise descending order";
    output Real sorted_M[size(M, 1), size(M, 2)]=M "Sorted matrix";
    output Integer indices[if sortRows then size(M, 1) else size(M, 2)]
      "sorted_M = if sortRows then M[indices,:] else M[:,indices]";

    /* shellsort algorithm; should be improved later */
  protected
    Integer gap;
    Integer i;
    Integer j;
    Real wM2[size(M, 2)];
    Integer wi;
    Integer nM1=size(M, 1);
    Boolean swap;
    Real sorted_MT[size(M, 2), size(M, 1)];

    encapsulated function greater "Compare whether vector v1 > v2"
      import Modelica;
      extends Modelica.Icons.Function;
      import Modelica.Utilities.Types.Compare;
      input Real v1[:];
      input Real v2[size(v1, 1)];
      output Boolean result;
    protected
      Integer n=size(v1, 1);
      Integer i=1;
    algorithm
      result := false;
      while i <= n loop
        if v1[i] > v2[i] then
          result := true;
          i := n;
        elseif v1[i] < v2[i] then
          i := n;
        end if;
        i := i + 1;
      end while;
    end greater;

    encapsulated function less "Compare whether vector v1 < v2"
      import Modelica;
      extends Modelica.Icons.Function;
      import Modelica.Utilities.Types.Compare;
      input Real v1[:];
      input Real v2[size(v1, 1)];
      output Boolean result;
    protected
      Integer n=size(v1, 1);
      Integer i=1;
    algorithm
      result := false;
      while i <= n loop
        if v1[i] < v2[i] then
          result := true;
          i := n;
        elseif v1[i] > v2[i] then
          i := n;
        end if;
        i := i + 1;
      end while;
    end less;
  algorithm
    if not sortRows then
      (sorted_MT,indices) := sort(transpose(M), ascending=ascending);
      sorted_M := transpose(sorted_MT);
    else
      indices := 1:size(M, 1);
      gap := div(nM1, 2);
      while gap > 0 loop
        i := gap;
        while i < nM1 loop
          j := i - gap;
          if j >= 0 then
            if ascending then
              swap := greater(sorted_M[j + 1, :], sorted_M[j + gap + 1, :]);
            else
              swap := less(sorted_M[j + 1, :], sorted_M[j + gap + 1, :]);
            end if;
          else
            swap := false;
          end if;

          while swap loop
            wM2 := sorted_M[j + 1, :];
            wi := indices[j + 1];
            sorted_M[j + 1, :] := sorted_M[j + gap + 1, :];
            sorted_M[j + gap + 1, :] := wM2;
            indices[j + 1] := indices[j + gap + 1];
            indices[j + gap + 1] := wi;
            j := j - gap;
            if j >= 0 then
              if ascending then
                swap := greater(sorted_M[j + 1, :], sorted_M[j + gap + 1, :]);
              else
                swap := less(sorted_M[j + 1, :], sorted_M[j + gap + 1, :]);
              end if;
            else
              swap := false;
            end if;
          end while;
          i := i + 1;
        end while;
        gap := div(gap, 2);
      end while;
    end if;
(sorted_M, indices) = Matrices.sort(M, sortRows=true, ascending=true);

Description

Function sort(..) sorts the rows of a Real matrix M
in ascending order and returns the result in sorted_M.
If the optional argument \"sortRows\" is false, the columns
of the matrix are sorted.
If the optional argument \"ascending\" is false, the rows or
columns are sorted in descending order. In the optional second
output argument, the indices of the sorted rows or columns with respect
to the original matrix are given, such that

sorted_M = if sortedRow then M[indices,:] else M[:,indices];

Example

(M2, i2) := Matrices.sort([2, 1,  0;
                           2, 0, -1]);
     -> M2 = [2, 0, -1;
              2, 1, 0 ];
        i2 = {2,1};

"));
  end sort;

  function flipLeftRight "Flip the columns of a matrix in left/right direction"
    extends Modelica.Icons.Function;
    input Real A[:, :] "Matrix to be flipped";
    output Real Aflip[size(A, 1), size(A, 2)] "Flipped matrix";
  algorithm
    Aflip := A[:, {i for i in size(A, 2):-1:1}];

Description

Function flipLeftRight computes from matrix A a matrix A_flr with flipped columns, i.e., A_flr[:,i]=A[:,n-i+1], i=1,..., n.

Example

A = [1, 2,  3;
     3, 4,  5;
    -1, 2, -3];

A_flr = flipLeftRight(A);

results in:

A_flr = [3, 2,  1;
         5, 4,  3;
        -3, 2, -1]

See also

Matrices.flipUpDown

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end flipLeftRight;

  function flipUpDown "Flip the rows of a matrix in up/down direction"
    extends Modelica.Icons.Function;
    input Real A[:, :] "Matrix to be flipped";
    output Real Aflip[size(A, 1), size(A, 2)] "Flipped matrix";
  algorithm
    Aflip := A[{i for i in size(A, 1):-1:1}, :];

Description

Function flipUpDown computes from matrix A a matrix A_fud with flipped rows, i.e., A_fud[i,:]=A[n-i+1,:], i=1,..., n.

Example

A = [1, 2,  3;
     3, 4,  5;
    -1, 2, -3];

A_fud = flipUpDown(A);

results in:

A_fud  = [-1, 2, -3;
           3, 4,  5;
           1, 2,  3]

See also

Matrices.flipLeftRight

", revisions="

2010/05/31
       by Marcus Baur, DLR-RM

"));
  end flipUpDown;

  package LAPACK
    "Interface to LAPACK library (should usually not directly be used but only indirectly via Modelica.Math.Matrices)"
    extends Modelica.Icons.FunctionsPackage;

    pure function dgbsv
      "Solve real system of linear equations A*X=B with a B matrix"
      extends Modelica.Icons.Function;
      input Integer n "Number of equations";
      input Integer kLower "Number of lower bands";
      input Integer kUpper "Number of upper bands";
      input Real A[2*kLower + kUpper + 1, n];
      input Real B[n, :];
      output Real X[n, size(B, 2)]=B;
      output Integer info;
    protected
      Integer nrhs=size(B, 2);
      Integer ldab=size(Awork, 1);
      Real Awork[size(A, 1), size(A, 2)]=A;
      Integer ipiv[n];

    external"FORTRAN 77" dgbsv(
              n,
              kLower,
              kUpper,
              nrhs,
              Awork,
              ldab,
              ipiv,
              X,
              n,
              info)
    end dgbsv;

    pure function dgbsv_vec
      "Solve real system of linear equations A*x=b with a b vector"
      extends Modelica.Icons.Function;
      input Integer n "Number of equations";
      input Integer kLower "Number of lower bands";
      input Integer kUpper "Number of upper bands";
      input Real A[2*kLower + kUpper + 1, n];
      input Real b[n];
      output Real x[n]=b;
      output Integer info;
    protected
      Integer nrhs=1;
      Integer ldab=size(Awork, 1);
      Real Awork[size(A, 1), size(A, 2)]=A;
      Integer ipiv[n];

    external"FORTRAN 77" dgbsv(
              n,
              kLower,
              kUpper,
              nrhs,
              Awork,
              ldab,
              ipiv,
              x,
              n,
              info)
    end dgbsv_vec;

    pure function dgecon
      "Estimate the reciprocal of the condition number of a general real matrix A"
      extends Modelica.Icons.Function;

      input Real LU_of_A[:, :] "LU factorization of a real matrix A";
      input Boolean inf=false
        "Is true if infinity norm is used and false for 1-norm";
      input Real anorm "Norm of A";
      output Real rcond "Reciprocal condition number of A";
      output Integer info;
    protected
      Integer n=size(LU_of_A, 2);
      Integer lda=max(1, size(LU_of_A, 1));
      Real work[4*size(LU_of_A, 2)];
      Integer iwork[size(LU_of_A, 2)];
      String norm=if inf then "I" else "1";

    external"FORTRAN 77" dgecon(
              norm,
              n,
              LU_of_A,
              lda,
              anorm,
              rcond,
              work,
              iwork,
              info)
    end dgecon;

    pure function dgees
      "Compute real Schur form T of real nonsymmetric matrix A, and, optionally, the matrix of Schur vectors Z as well as the eigenvalues"
      extends Modelica.Icons.Function;

      input Real A[:, size(A, 1)] "Square matrix";
      output Real T[size(A, 1), size(A, 2)]=A "Real Schur form with A = Z*T*Z'";
      output Real Z[size(A, 1), size(A, 1)]
        "Orthogonal matrix Z of Schur vectors";
      output Real eval_real[size(A, 1)] "Real part of the eigenvectors of A";
      output Real eval_imag[size(A, 1)]
        "Imaginary part of the eigenvectors of A";
      output Integer info;

    protected
      constant Integer dummyFunctionPointerNotUsed[1]={0};
      Integer n=size(A, 1) "Row dimension of A";
      Integer lda=max(1, n);
      Integer sdim=0;
      Integer lwork=max(1, 10*size(A, 1));
      Real work[lwork];
      Boolean bwork[size(A, 1)];

    external"FORTRAN 77" dgees(
              "V",
              "N",
              dummyFunctionPointerNotUsed,
              n,
              T,
              lda,
              sdim,
              eval_real,
              eval_imag,
              Z,
              lda,
              work,
              lwork,
              bwork,
              info) in this
            case INFO is set to N+2 (see INFO below).

    N       (input) INTEGER
            The order of the matrix A. N >= 0.

    A       (input/output) DOUBLE PRECISION array, dimension (LDA,N)
            On entry, the N-by-N matrix A.
            On exit, A has been overwritten by its real Schur form T.

    LDA     (input) INTEGER
            The leading dimension of the array A.  LDA >= max(1,N).

    SDIM    (output) INTEGER
            If SORT = 'N', SDIM = 0.
            If SORT = 'S', SDIM = number of eigenvalues (after sorting)
                           for which SELECT is true. (Complex conjugate
                           pairs for which SELECT is true for either
                           eigenvalue count as 2.)

    WR      (output) DOUBLE PRECISION array, dimension (N)
    WI      (output) DOUBLE PRECISION array, dimension (N)
            WR and WI contain the real and imaginary parts,
            respectively, of the computed eigenvalues in the same order
            that they appear on the diagonal of the output Schur form T.
            Complex conjugate pairs of eigenvalues will appear
            consecutively with the eigenvalue having the positive
            imaginary part first.

    VS      (output) DOUBLE PRECISION array, dimension (LDVS,N)
            If JOBVS = 'V', VS contains the orthogonal matrix Z of Schur
            vectors.
            If JOBVS = 'N', VS is not referenced.

    LDVS    (input) INTEGER
            The leading dimension of the array VS.  LDVS >= 1; if
            JOBVS = 'V', LDVS >= N.

    WORK    (workspace/output) DOUBLE PRECISION array, dimension (MAX(1,LWORK))
            On exit, if INFO = 0, WORK(1) contains the optimal LWORK.

    LWORK   (input) INTEGER
            The dimension of the array WORK.  LWORK >= max(1,3*N).
            For good performance, LWORK must generally be larger.

            If LWORK = -1, then a workspace query is assumed; the routine
            only calculates the optimal size of the WORK array, returns
            this value as the first entry of the WORK array, and no error
            message related to LWORK is issued by XERBLA.

    BWORK   (workspace) LOGICAL array, dimension (N)
            Not referenced if SORT = 'N'.

    INFO    (output) INTEGER
            = 0: successful exit
            < 0: if INFO = -i, the i-th argument had an illegal value.
            > 0: if INFO = i, and i is
               <= N: the QR algorithm failed to compute all the
                     eigenvalues; elements 1:ILO-1 and i+1:N of WR and WI
                     contain those eigenvalues which have converged; if
                     JOBVS = 'V', VS contains the matrix which reduces A
                     to its partially converged Schur form.
               = N+1: the eigenvalues could not be reordered because some
                     eigenvalues were too close to separate (the problem
                     is very ill-conditioned);
               = N+2: after reordering, roundoff changed values of some
                     complex eigenvalues so that leading eigenvalues in
                     the Schur form no longer satisfy SELECT=.TRUE.  This
                     could also be caused by underflow due to scaling.
"));
    end dgees;

    pure function dgeev
      "Compute eigenvalues and (right) eigenvectors for real nonsymmetric matrix A"

      extends Modelica.Icons.Function;
      input Real A[:, size(A, 1)];
      output Real eigenReal[size(A, 1)] "Real part of eigen values";
      output Real eigenImag[size(A, 1)] "Imaginary part of eigen values";
      output Real eigenVectors[size(A, 1), size(A, 1)] "Right eigen vectors";
      output Integer info;
    protected
      Real dummy[1,1];
      Integer n=size(A, 1);
      Integer lwork=max(1, 12*n);
      Integer ldvl=1;
      Real Awork[size(A, 1), size(A, 1)]=A;
      Real work[max(1, 12*size(A, 1))];

    external"FORTRAN 77" dgeev(
              "N",
              "V",
              n,
              Awork,
              n,
              eigenReal,
              eigenImag,
              dummy,
              ldvl,
              eigenVectors,
              n,
              work,
              lwork,
              info)
    end dgeev;

    pure function dgeev_eigenValues
      "Compute eigenvalues for real nonsymmetric matrix A"

      extends Modelica.Icons.Function;
      input Real A[:, size(A, 1)];
      output Real EigenReal[size(A, 1)];
      output Real EigenImag[size(A, 1)];

      /*
      output Real Eigenvectors[size(A, 1), size(A, 1)]=zeros(size(A, 1), size(
          A, 1)); */
      output Integer info;
    protected
      Integer n=size(A, 1);
      Integer lwork=max(1, 8*n);
      Real Awork[size(A, 1), size(A, 1)]=A;
      Real work[max(1, 8*size(A, 1))];
      Real EigenvectorsL[size(A, 1), size(A, 1)]=zeros(size(A, 1), size(A, 1));

      /*
    external "FORTRAN 77" dgeev("N", "V", n, Awork, n,
        EigenReal, EigenImag, EigenvectorsL, n,
        Eigenvectors, n, work, lwork, info)
*/
    external"FORTRAN 77" dgeev(
              "N",
              "N",
              n,
              Awork,
              n,
              EigenReal,
              EigenImag,
              EigenvectorsL,
              n,
              EigenvectorsL,
              n,
              work,
              lwork,
              info)
    end dgeev_eigenValues;

    pure function dgeevx
      "Compute the eigenvalues and the (real) left and right eigenvectors of matrix A, using lapack routine dgeevx"
      extends Modelica.Icons.Function;

      input Real A[:, size(A, 1)];
      output Real alphaReal[size(A, 1)]
        "Real part of alpha (eigenvalue=(alphaReal+i*alphaImag))";
      output Real alphaImag[size(A, 1)]
        "Imaginary part of alpha (eigenvalue=(alphaReal+i*alphaImag))";
      output Real lEigenVectors[size(A, 1), size(A, 1)]
        "Left eigenvectors of matrix A";
      output Real rEigenVectors[size(A, 1), size(A, 1)]
        "Right eigenvectors of matrix A";
      output Real AS[size(A, 1), size(A, 2)]=A
        "AS is the real Schur form of the balanced version of the input matrix A";
      output Integer info;
    protected
      Integer n=size(A, 1);
      Integer ilo;
      Integer ihi;
      Real scale[size(A, 1)];
      Real abnrm;
      Real rconde[size(A, 1)];
      Real rcondv[size(A, 1)];
      Integer lwork=max(1, n*(n + 6));
      Real work[max(1, size(A, 1)*(size(A, 1) + 6))];
      Integer iwork[1];

    external"FORTRAN 77" dgeevx(
              "B",
              "V",
              "V",
              "E",
              n,
              AS,
              n,
              alphaReal,
              alphaImag,
              lEigenVectors,
              n,
              rEigenVectors,
              n,
              ilo,
              ihi,
              scale,
              abnrm,
              rconde,
              rcondv,
              work,
              lwork,
              iwork,
              info)
    end dgeevx;

    pure function dgehrd
      "Reduce a real general matrix A to upper Hessenberg form H by an orthogonal similarity transformation:  Q' * A * Q = H"
      extends Modelica.Icons.Function;

      input Real A[:, size(A, 1)];
      input Integer ilo=1
        "Lowest index where the original matrix is not in upper triangular form";
      input Integer ihi=size(A, 1)
        "Highest index where the original matrix is not in upper triangular form";
      output Real Aout[size(A, 1), size(A, 2)]=A
        "Contains the Hessenberg form in the upper triangle and the first subdiagonal and below the first subdiagonal it contains the elementary reflectors which represents (with array tau) as a product the orthogonal matrix Q";
      output Real tau[max(size(A, 1), 1) - 1]
        "Scalar factors of the elementary reflectors";
      output Integer info;
    protected
      Integer n=size(A, 1);
      Integer lda=max(1, n);
      Integer lwork=max(1, 3*n);
      Real work[max(1, 3*size(A, 1))];

    external"FORTRAN 77" dgehrd(
              n,
              ilo,
              ihi,
              Aout,
              lda,
              tau,
              work,
              lwork,
              info)
    end dgehrd;

    pure function dgels_vec
      "Solve overdetermined or underdetermined real linear equations A*x=b with a b vector"

      extends Modelica.Icons.Function;
      input Real A[:, :];
      input Real b[size(A, 1)];
      output Real x[max(size(A, 1), size(A, 2))]=cat(
                1,
                b,
                zeros(nx - nrow)) "Solution is in first size(A,2) rows";
      output Integer info;
    protected
      Integer nrow=size(A, 1);
      Integer ncol=size(A, 2);
      Integer nrhs=1;
      Integer nx=max(nrow, ncol);
      Integer lwork=max(1, min(nrow, ncol) + nx);
      Real work[max(1, size(A, 1) + size(A, 2))];
      Real Awork[size(A, 1), size(A, 2)]=A;

    external"FORTRAN 77" dgels(
              "N",
              nrow,
              ncol,
              nrhs,
              Awork,
              nrow,
              x,
              nx,
              work,
              lwork,
              info)
    end dgels_vec;

    pure function dgelsy
      "Compute the minimum-norm solution to a real linear least squares problem with rank deficient A"

      extends Modelica.Icons.Function;
      input Real A[:, :];
      input Real B[size(A, 1), :];
      input Real rcond=0.0 "Reciprocal condition number to estimate rank";
      output Real X[max(size(A, 1), size(A, 2)), size(B, 2)]=cat(
                1,
                B,
                zeros(max(nrow, ncol) - nrow, nrhs))
        "Solution is in first size(A,2) rows";
      output Integer info;
      output Integer rank "Effective rank of A";
    protected
      Integer nrow=size(A, 1);
      Integer ncol=size(A, 2);
      Integer nx=max(nrow, ncol);
      Integer nrhs=size(B, 2);
      Integer lwork=max(1, max(min(nrow, ncol) + 3*ncol + 1, 2*min(nrow, ncol) + nrhs));
      Real work[max(1, max(min(size(A, 1), size(A, 2)) + 3*size(A, 2) + 1, 2*min(size(A, 1),
        size(A, 2)) + size(B, 2)))];
      Real Awork[size(A, 1), size(A, 2)]=A;
      Integer jpvt[size(A, 2)]=zeros(ncol);

    external"FORTRAN 77" dgelsy(
              nrow,
              ncol,
              nrhs,
              Awork,
              nrow,
              X,
              nx,
              jpvt,
              rcond,
              rank,
              work,
              lwork,
              info)
    end dgelsy;

    pure function dgelsy_vec
      "Compute the minimum-norm solution to a real linear least squares problem with rank deficient A"

      extends Modelica.Icons.Function;
      input Real A[:, :];
      input Real b[size(A, 1)];
      input Real rcond=0.0 "Reciprocal condition number to estimate rank";
      output Real x[max(size(A, 1), size(A, 2))]=cat(
                1,
                b,
                zeros(max(nrow, ncol) - nrow))
        "solution is in first size(A,2) rows";
      output Integer info;
      output Integer rank "Effective rank of A";
    protected
      Integer nrow=size(A, 1);
      Integer ncol=size(A, 2);
      Integer nrhs=1;
      Integer nx=max(nrow, ncol);
      Integer lwork=max(min(nrow, ncol) + 3*ncol + 1, 2*min(nrow, ncol) + 1);
      Real work[max(min(size(A, 1), size(A, 2)) + 3*size(A, 2) + 1, 2*min(size(A, 1),
        size(A, 2)) + 1)];
      Real Awork[size(A, 1), size(A, 2)]=A;
      Integer jpvt[size(A, 2)]=zeros(ncol);

    external"FORTRAN 77" dgelsy(
              nrow,
              ncol,
              nrhs,
              Awork,
              nrow,
              x,
              nx,
              jpvt,
              rcond,
              rank,
              work,
              lwork,
              info)
    end dgelsy_vec;

    pure function dgeqp3 "Compute QR factorization with column pivoting of square or rectangular matrix A"

      extends Modelica.Icons.Function;
      input Real A[:, :] "Square or rectangular matrix";
      input Integer lwork=max(1, 3*size(A, 2) + 1) "Length of work array";
      output Real QR[size(A, 1), size(A, 2)]=A
        "QR factorization in packed format";
      output Real tau[min(size(A, 1), size(A, 2))]
        "The scalar factors of the elementary reflectors of Q";
      output Integer p[size(A, 2)]=zeros(size(A, 2)) "Pivot vector";
      output Integer info;
    protected
      Integer m=size(A, 1);
      Integer lda=max(1, size(A, 1));
      Integer ncol=size(A, 2) "Column dimension of A";
      Real work[lwork] "Work array";

    external"FORTRAN 77" dgeqp3(
              m,
              ncol,
              QR,
              lda,
              p,
              tau,
              work,
              lwork,
              info) if JPVT(J)=0,
            the J-th column of A is a free column.
            On exit, if JPVT(J)=K, then the J-th column of A*P was the
            the K-th column of A.

    TAU     (output) DOUBLE PRECISION array, dimension (min(M,N))
            The scalar factors of the elementary reflectors.

    WORK    (workspace/output) DOUBLE PRECISION array, dimension (MAX(1,LWORK))
            On exit, if INFO=0, WORK(1) returns the optimal LWORK.

    LWORK   (input) INTEGER
            The dimension of the array WORK. LWORK >= 3*N+1.
            For optimal performance LWORK >= 2*N+( N+1 )*NB, where NB
            is the optimal blocksize.

            If LWORK = -1, then a workspace query is assumed; the routine
            only calculates the optimal size of the WORK array, returns
            this value as the first entry of the WORK array, and no error
            message related to LWORK is issued by XERBLA.

    INFO    (output) INTEGER
            = 0: successful exit.
            < 0: if INFO = -i, the i-th argument had an illegal value.

    Further Details
    ===============

    The matrix Q is represented as a product of elementary reflectors

       Q = H(1) H(2) . . . H(k), where k = min(m,n).

    Each H(i) has the form

       H(i) = I - tau * v * v'

    where tau is a real/complex scalar, and v is a real/complex vector
    with v(1:i-1) = 0 and v(i) = 1; v(i+1:m) is stored on exit in
    A(i+1:m,i), and tau in TAU(i).

    Based on contributions by
      G. Quintana-Orti, Depto. de Informatica, Universidad Jaime I, Spain
      X. Sun, Computer Science Dept., Duke University, USA
"));
    end dgeqp3;

    pure function dgeqrf "Compute a QR factorization without pivoting"
      extends Modelica.Icons.Function;

      input Real A[:, :] "Square or rectangular matrix";
      output Real Aout[size(A, 1), size(A, 2)]=A
        "The upper triangle of the array contains the upper trapezoidal matrix R; the elements below the diagonal, together with the array TAU, represent the orthogonal matrix Q as a product of elementary reflectors";
      output Real tau[min(size(A, 1), size(A, 2))]
        "Scalar factors of the elementary reflectors";
      output Integer info;
      output Real work[3*max(1, size(A, 2))];
    protected
      Integer m=size(A, 1);
      Integer n=size(A, 2);
      Integer lda=max(1, m);
      Integer lwork=3*max(1, n);

    external"FORTRAN 77" dgeqrf(
              m,
              n,
              Aout,
              lda,
              tau,
              work,
              lwork,
              info) the elements below the diagonal,
            with the array TAU, represent the orthogonal matrix Q as a
            product of min(m,n) elementary reflectors (see Further
            Details).

    LDA     (input) INTEGER
            The leading dimension of the array A.  LDA >= max(1,M).

    TAU     (output) DOUBLE PRECISION array, dimension (min(M,N))
            The scalar factors of the elementary reflectors (see Further
            Details).

    WORK    (workspace/output) DOUBLE PRECISION array, dimension (MAX(1,LWORK))
            On exit, if INFO = 0, WORK(1) returns the optimal LWORK.

    LWORK   (input) INTEGER
            The dimension of the array WORK.  LWORK >= max(1,N).
            For optimum performance LWORK >= N*NB, where NB is
            the optimal blocksize.

            If LWORK = -1, then a workspace query is assumed; the routine
            only calculates the optimal size of the WORK array, returns
            this value as the first entry of the WORK array, and no error
            message related to LWORK is issued by XERBLA.

    INFO    (output) INTEGER
            = 0:  successful exit
            < 0:  if INFO = -i, the i-th argument had an illegal value

    Further Details
    ===============

    The matrix Q is represented as a product of elementary reflectors

       Q = H(1) H(2) . . . H(k), where k = min(m,n).

    Each H(i) has the form

       H(i) = I - tau * v * v'

    where tau is a real scalar, and v is a real vector with
    v(1:i-1) = 0 and v(i) = 1; v(i+1:m) is stored on exit in A(i+1:m,i),
    and tau in TAU(i).
"));
    end dgeqrf;

    pure function dgesdd "Determine singular value decomposition"
      extends Modelica.Icons.Function;
      input Real A[:, :];
      output Real sigma[min(size(A, 1), size(A, 2))];
      output Real U[size(A, 1), size(A, 1)]=zeros(size(A, 1), size(A, 1));
      output Real VT[size(A, 2), size(A, 2)]=zeros(size(A, 2), size(A, 2));
      output Integer info;
    protected
      Integer m=size(A, 1);
      Integer n=size(A, 2);
      Real Awork[size(A, 1), size(A, 2)]=A;
      Integer lda=max(1, size(A, 1));
      Integer ldu=max(1, size(A, 1));
      Integer ldvt=max(1, size(A, 2));
      Integer lwork=max(1, 3*(3*min(size(A, 1), size(A, 2))*min(size(A, 1),
          size(A, 2)) + max(max(size(A, 1), size(A, 2)), 4*min(size(A, 1), size(
          A, 2))*min(size(A, 1), size(A, 2)) + 4*min(size(A, 1), size(A, 2)))));
      Integer iwork=max(1, 8*min(size(A, 1), size(A, 2)));
      Real work[max(1, 3*(3*min(size(A, 1), size(A, 2))*min(size(A, 1), size(A,
        2)) + max(max(size(A, 1), size(A, 2)), 4*min(size(A, 1), size(A, 2))*
        min(size(A, 1), size(A, 2)) + 4*min(size(A, 1), size(A, 2)))))];

    external"FORTRAN 77" dgesdd(
              "A",
              m,
              n,
              Awork,
              lda,
              sigma,
              U,
              ldu,
              VT,
              ldvt,
              work,
              lwork,
              iwork,
              info)
            if JOBZ = 'O' and M >= N, or JOBZ = 'N', U is not referenced.

    LDU     (input) INTEGER
            The leading dimension of the array U.  LDU >= 1; if
            JOBZ = 'S' or 'A' or JOBZ = 'O' and M < N, LDU >= M.

    VT      (output) DOUBLE PRECISION array, dimension (LDVT,N)
            If JOBZ = 'A' or JOBZ = 'O' and M >= N, VT contains the
            N-by-N orthogonal matrix V**T;
            if JOBZ = 'S', VT contains the first min(M,N) rows of
            V**T (the right singular vectors, stored rowwise);
            if JOBZ = 'O' and M < N, or JOBZ = 'N', VT is not referenced.

    LDVT    (input) INTEGER
            The leading dimension of the array VT.  LDVT >= 1; if
            JOBZ = 'A' or JOBZ = 'O' and M >= N, LDVT >= N;
            if JOBZ = 'S', LDVT >= min(M,N).

    WORK    (workspace/output) DOUBLE PRECISION array, dimension (MAX(1,LWORK))
            On exit, if INFO = 0, WORK(1) returns the optimal LWORK;

    LWORK   (input) INTEGER
            The dimension of the array WORK. LWORK >= 1.
            If JOBZ = 'N',
              LWORK >= 3*min(M,N) + max(max(M,N),7*min(M,N)).
            If JOBZ = 'O',
              LWORK >= 3*min(M,N) +
                       max(max(M,N),5*min(M,N)*min(M,N)+4*min(M,N)).
            If JOBZ = 'S' or 'A'
              LWORK >= 3*min(M,N) +
                       max(max(M,N),4*min(M,N)*min(M,N)+4*min(M,N)).
            For good performance, LWORK should generally be larger.
            If LWORK = -1 but other input arguments are legal, WORK(1)
            returns the optimal LWORK.

    IWORK   (workspace) INTEGER array, dimension (8*min(M,N))

    INFO    (output) INTEGER
            = 0:  successful exit.
            < 0:  if INFO = -i, the i-th argument had an illegal value.
            > 0:  DBDSDC did not converge, updating process failed.

    Further Details
    ===============

    Based on contributions by
       Ming Gu and Huan Ren, Computer Science Division, University of
       California at Berkeley, USA
"));
    end dgesdd;

    pure function dgesv
      "Solve real system of linear equations A*X=B with a B matrix"
      extends Modelica.Icons.Function;
      input Real A[:, size(A, 1)];
      input Real B[size(A, 1), :];
      output Real X[size(A, 1), size(B, 2)]=B;
      output Integer info;
    protected
      Integer n=size(A, 1);
      Integer nrhs=size(B, 2);
      Real Awork[size(A, 1), size(A, 1)]=A;
      Integer lda=max(1, size(A, 1));
      Integer ldb=max(1, size(B, 1));
      Integer ipiv[size(A, 1)];

    external"FORTRAN 77" dgesv(
              n,
              nrhs,
              Awork,
              lda,
              ipiv,
              X,
              ldb,
              info)
    end dgesv;

    pure function dgesv_vec
      "Solve real system of linear equations A*x=b with a b vector"
      extends Modelica.Icons.Function;
      input Real A[:, size(A, 1)];
      input Real b[size(A, 1)];
      output Real x[size(A, 1)]=b;
      output Integer info;
    protected
      Integer n=size(A, 1);
      Integer nrhs=1;
      Real Awork[size(A, 1), size(A, 1)]=A;
      Integer lda=max(1, size(A, 1));
      Integer ldb=max(1, size(b, 1));
      Integer ipiv[size(A, 1)];

    external"FORTRAN 77" dgesv(
              n,
              nrhs,
              Awork,
              lda,
              ipiv,
              x,
              ldb,
              info)
    end dgesv_vec;

    pure function dgesvd "Determine singular value decomposition"
      extends Modelica.Icons.Function;
      input Real A[:, :];
      output Real sigma[min(size(A, 1), size(A, 2))];
      output Real U[size(A, 1), size(A, 1)]=zeros(size(A, 1), size(A, 1));
      output Real VT[size(A, 2), size(A, 2)]=zeros(size(A, 2), size(A, 2));
      output Integer info;
    protected
      Integer m=size(A, 1);
      Integer n=size(A, 2);
      Real Awork[size(A, 1), size(A, 2)]=A;
      Integer lwork=max(1, 5*size(A, 1) + 5*size(A, 2));
      Real work[max(1, 5*size(A, 1) + 5*size(A, 2))];

    external"FORTRAN 77" dgesvd(
              "A",
              "A",
              m,
              n,
              Awork,
              m,
              sigma,
              U,
              m,
              VT,
              n,
              work,
              lwork,
              info)
            if JOBVT = 'O', A is overwritten with the first min(m,n)
                            rows of V**T (the right singular vectors,
                            stored rowwise);
            if JOBU .ne. 'O' and JOBVT .ne. 'O', the contents of A
                            are destroyed.

    LDA     (input) INTEGER
            The leading dimension of the array A.  LDA >= max(1,M).

    S       (output) DOUBLE PRECISION array, dimension (min(M,N))
            The singular values of A, sorted so that S(i) >= S(i+1).

    U       (output) DOUBLE PRECISION array, dimension (LDU,UCOL)
            (LDU,M) if JOBU = 'A' or (LDU,min(M,N)) if JOBU = 'S'.
            If JOBU = 'A', U contains the M-by-M orthogonal matrix U;
            if JOBU = 'S', U contains the first min(m,n) columns of U
            (the left singular vectors, stored columnwise);
            if JOBU = 'N' or 'O', U is not referenced.

    LDU     (input) INTEGER
            The leading dimension of the array U.  LDU >= 1; if
            JOBU = 'S' or 'A', LDU >= M.

    VT      (output) DOUBLE PRECISION array, dimension (LDVT,N)
            If JOBVT = 'A', VT contains the N-by-N orthogonal matrix
            V**T;
            if JOBVT = 'S', VT contains the first min(m,n) rows of
            V**T (the right singular vectors, stored rowwise);
            if JOBVT = 'N' or 'O', VT is not referenced.

    LDVT    (input) INTEGER
            The leading dimension of the array VT.  LDVT >= 1; if
            JOBVT = 'A', LDVT >= N; if JOBVT = 'S', LDVT >= min(M,N).

    WORK    (workspace/output) DOUBLE PRECISION array, dimension (MAX(1,LWORK))
            On exit, if INFO = 0, WORK(1) returns the optimal LWORK;
            if INFO > 0, WORK(2:MIN(M,N)) contains the unconverged
            superdiagonal elements of an upper bidiagonal matrix B
            whose diagonal is in S (not necessarily sorted). B
            satisfies A = U * B * VT, so it has the same singular values
            as A, and singular vectors related by U and VT.

    LWORK   (input) INTEGER
            The dimension of the array WORK.
            LWORK >= MAX(1,3*MIN(M,N)+MAX(M,N),5*MIN(M,N)).
            For good performance, LWORK should generally be larger.

            If LWORK = -1, then a workspace query is assumed; the routine
            only calculates the optimal size of the WORK array, returns
            this value as the first entry of the WORK array, and no error
            message related to LWORK is issued by XERBLA.

    INFO    (output) INTEGER
            = 0:  successful exit.
            < 0:  if INFO = -i, the i-th argument had an illegal value.
            > 0:  if DBDSQR did not converge, INFO specifies how many
                  superdiagonals of an intermediate bidiagonal form B
                  did not converge to zero. See the description of WORK
                  above for details.
"));
    end dgesvd;

    pure function dgesvd_sigma "Determine singular values"
      extends Modelica.Icons.Function;
      input Real A[:, :];
      output Real sigma[min(size(A, 1), size(A, 2))];
      output Integer info;
    protected
      Integer m=size(A, 1);
      Integer n=size(A, 2);
      Real Awork[size(A, 1), size(A, 2)]=A;
      Real U[size(A, 1), size(A, 1)];
      Real VT[size(A, 2), size(A, 2)];
      Integer lwork=max(1, 5*size(A, 1) + 5*size(A, 2));
      Real work[max(1, 5*size(A, 1) + 5*size(A, 2))];

    external"FORTRAN 77" dgesvd(
              "N",
              "N",
              m,
              n,
              Awork,
              m,
              sigma,
              U,
              m,
              VT,
              n,
              work,
              lwork,
              info)
            if JOBVT = 'O', A is overwritten with the first min(m,n)
                            rows of V**T (the right singular vectors,
                            stored rowwise);
            if JOBU .ne. 'O' and JOBVT .ne. 'O', the contents of A
                            are destroyed.

    LDA     (input) INTEGER
            The leading dimension of the array A.  LDA >= max(1,M).

    S       (output) DOUBLE PRECISION array, dimension (min(M,N))
            The singular values of A, sorted so that S(i) >= S(i+1).

    U       (output) DOUBLE PRECISION array, dimension (LDU,UCOL)
            (LDU,M) if JOBU = 'A' or (LDU,min(M,N)) if JOBU = 'S'.
            If JOBU = 'A', U contains the M-by-M orthogonal matrix U;
            if JOBU = 'S', U contains the first min(m,n) columns of U
            (the left singular vectors, stored columnwise);
            if JOBU = 'N' or 'O', U is not referenced.

    LDU     (input) INTEGER
            The leading dimension of the array U.  LDU >= 1; if
            JOBU = 'S' or 'A', LDU >= M.

    VT      (output) DOUBLE PRECISION array, dimension (LDVT,N)
            If JOBVT = 'A', VT contains the N-by-N orthogonal matrix
            V**T;
            if JOBVT = 'S', VT contains the first min(m,n) rows of
            V**T (the right singular vectors, stored rowwise);
            if JOBVT = 'N' or 'O', VT is not referenced.

    LDVT    (input) INTEGER
            The leading dimension of the array VT.  LDVT >= 1; if
            JOBVT = 'A', LDVT >= N; if JOBVT = 'S', LDVT >= min(M,N).

    WORK    (workspace/output) DOUBLE PRECISION array, dimension (MAX(1,LWORK))
            On exit, if INFO = 0, WORK(1) returns the optimal LWORK;
            if INFO > 0, WORK(2:MIN(M,N)) contains the unconverged
            superdiagonal elements of an upper bidiagonal matrix B
            whose diagonal is in S (not necessarily sorted). B
            satisfies A = U * B * VT, so it has the same singular values
            as A, and singular vectors related by U and VT.

    LWORK   (input) INTEGER
            The dimension of the array WORK.
            LWORK >= MAX(1,3*MIN(M,N)+MAX(M,N),5*MIN(M,N)).
            For good performance, LWORK should generally be larger.

            If LWORK = -1, then a workspace query is assumed; the routine
            only calculates the optimal size of the WORK array, returns
            this value as the first entry of the WORK array, and no error
            message related to LWORK is issued by XERBLA.

    INFO    (output) INTEGER
            = 0:  successful exit.
            < 0:  if INFO = -i, the i-th argument had an illegal value.
            > 0:  if DBDSQR did not converge, INFO specifies how many
                  superdiagonals of an intermediate bidiagonal form B
                  did not converge to zero. See the description of WORK
                  above for details.
"));
    end dgesvd_sigma;

    pure function dgesvx
      "Solve real system of linear equations op(A)*X=B, op(A) is A or A' according to the Boolean input transposed"
      extends Modelica.Icons.Function;
      input Real A[:, size(A, 1)] "Real square matrix A";
      input Real B[size(A, 1), :] "Real matrix B";
      input Boolean transposed=true
        "= true, if the equation to be solved is A'*X=B";
      output Real X[size(A, 1), size(B, 2)] "Solution matrix";
      output Integer info;
      output Real rcond "Reciprocal condition number of the matrix A";

    protected
      Integer n=size(A, 1);
      Integer nrhs=size(B, 2);
      String transA=if transposed then "T" else "N";
      Real Awork[size(A, 1), size(A, 2)]=A;
      Real Bwork[size(B, 1), size(B, 2)]=B;
      Real AF[size(A, 1), size(A, 2)];
      Real R[size(A, 1)];
      Real C[size(A, 1)];
      Real ferr[size(B, 2)];
      Real berr[size(B, 2)];
      Integer lda=max(1, size(A, 1));
      Real work[4*size(A, 1)];
      Integer ipiv[size(A, 1)];
      Integer iwork[size(A, 1)];
      String equed = " ";

    external"FORTRAN 77" dgesvx(
              "N",
              transA,
              n,
              nrhs,
              Awork,
              lda,
              AF,
              lda,
              ipiv,
              equed,
              R,
              C,
              Bwork,
              lda,
              X,
              lda,
              rcond,
              ferr,
              berr,
              work,
              iwork,
              info) if EQUED = 'N' or 'C', R
            is not accessed.  R is an input argument if FACT = 'F';
            otherwise, R is an output argument.  If FACT = 'F' and
            EQUED = 'R' or 'B', each element of R must be positive.

    C       (input or output) DOUBLE PRECISION array, dimension (N)
            The column scale factors for A.  If EQUED = 'C' or 'B', A is
            multiplied on the right by diag(C); if EQUED = 'N' or 'R', C
            is not accessed.  C is an input argument if FACT = 'F';
            otherwise, C is an output argument.  If FACT = 'F' and
            EQUED = 'C' or 'B', each element of C must be positive.

    B       (input/output) DOUBLE PRECISION array, dimension (LDB,NRHS)
            On entry, the N-by-NRHS right hand side matrix B.
            On exit,
            if EQUED = 'N', B is not modified;
            if TRANS = 'N' and EQUED = 'R' or 'B', B is overwritten by
            diag(R)*B;
            if TRANS = 'T' or 'C' and EQUED = 'C' or 'B', B is
            overwritten by diag(C)*B.

    LDB     (input) INTEGER
            The leading dimension of the array B.  LDB >= max(1,N).

    X       (output) DOUBLE PRECISION array, dimension (LDX,NRHS)
            If INFO = 0 or INFO = N+1, the N-by-NRHS solution matrix X
            to the original system of equations.  Note that A and B are
            modified on exit if EQUED .ne. 'N', and the solution to the
            equilibrated system is inv(diag(C))*X if TRANS = 'N' and
            EQUED = 'C' or 'B', or inv(diag(R))*X if TRANS = 'T' or 'C'
            and EQUED = 'R' or 'B'.

    LDX     (input) INTEGER
            The leading dimension of the array X.  LDX >= max(1,N).

    RCOND   (output) DOUBLE PRECISION
            The estimate of the reciprocal condition number of the matrix
            A after equilibration (if done).  If RCOND is less than the
            machine precision (in particular, if RCOND = 0), the matrix
            is singular to working precision.  This condition is
            indicated by a return code of INFO > 0.

    FERR    (output) DOUBLE PRECISION array, dimension (NRHS)
            The estimated forward error bound for each solution vector
            X(j) (the j-th column of the solution matrix X).
            If XTRUE is the true solution corresponding to X(j), FERR(j)
            is an estimated upper bound for the magnitude of the largest
            element in (X(j) - XTRUE) divided by the magnitude of the
            largest element in X(j).  The estimate is as reliable as
            the estimate for RCOND, and is almost always a slight
            overestimate of the true error.

    BERR    (output) DOUBLE PRECISION array, dimension (NRHS)
            The componentwise relative backward error of each solution
            vector X(j) (i.e., the smallest relative change in
            any element of A or B that makes X(j) an exact solution).

    WORK    (workspace/output) DOUBLE PRECISION array, dimension (4*N)
            On exit, WORK(1) contains the reciprocal pivot growth
            factor norm(A)/norm(U). The \"max absolute element\" norm is
            used. If WORK(1) is much less than 1, then the stability
            of the LU factorization of the (equilibrated) matrix A
            could be poor. This also means that the solution X, condition
            estimator RCOND, and forward error bound FERR could be
            unreliable. If factorization fails with 0<INFO<=N, then
            WORK(1) contains the reciprocal pivot growth factor for the
            leading INFO columns of A.

    IWORK   (workspace) INTEGER array, dimension (N)

    INFO    (output) INTEGER
            = 0:  successful exit
            < 0:  if INFO = -i, the i-th argument had an illegal value
            > 0:  if INFO = i, and i is
                  <= N:  U(i,i) is exactly zero.  The factorization has
                         been completed, but the factor U is exactly
                         singular, so the solution and error bounds
                         could not be computed. RCOND = 0 is returned.
                  = N+1: U is nonsingular, but RCOND is less than machine
                         precision, meaning that the matrix is singular
                         to working precision.  Nevertheless, the
                         solution and error bounds are computed because
                         there are a number of situations where the
                         computed solution can be more accurate than the
                         value of RCOND would suggest.
"));
    end dgesvx;

    pure function dgetrf
      "Compute LU factorization of square or rectangular matrix A (A = P*L*U)"

      extends Modelica.Icons.Function;
      input Real A[:, :] "Square or rectangular matrix";
      output Real LU[size(A, 1), size(A, 2)]=A;
      output Integer pivots[min(size(A, 1), size(A, 2))] "Pivot vector";
      output Integer info "Information";
    protected
      Integer m=size(A, 1);
      Integer n=size(A, 2);
      Integer lda=max(1, size(A, 1));

    external"FORTRAN 77" dgetrf(
              m,
              n,
              LU,
              lda,
              pivots,
              info)
    end dgetrf;

    pure function dgetri
      "Compute the inverse of a matrix using the LU factorization from dgetrf"

      extends Modelica.Icons.Function;
      input Real LU[:, size(LU, 1)]
        "LU factorization of dgetrf of a square matrix";
      input Integer pivots[size(LU, 1)] "Pivot vector of dgetrf";
      output Real inv[size(LU, 1), size(LU, 2)]=LU "Inverse of matrix P*L*U";
      output Integer info;

    protected
      Integer n=size(LU, 1);
      Integer lda=max(1, size(LU, 1));
      Integer lwork=max(1, min(10, size(LU, 1))*size(LU, 1))
        "Length of work array";
      Real work[max(1, min(10, size(LU, 1))*size(LU, 1))];

    external"FORTRAN 77" dgetri(
              n,
              inv,
              lda,
              pivots,
              work,
              lwork,
              info)
    end dgetri;

    pure function dgetrs
      "Solve a system of linear equations with the LU decomposition from dgetrf"

      extends Modelica.Icons.Function;
      input Real LU[:, size(LU, 1)]
        "LU factorization of dgetrf of a square matrix";
      input Integer pivots[size(LU, 1)] "Pivot vector of dgetrf";
      input Real B[size(LU, 1), :] "Right hand side matrix B";
      output Real X[size(B, 1), size(B, 2)]=B "Solution matrix X";
      output Integer info;
    protected
      Integer n=size(LU, 1);
      Integer nrhs=size(B, 2);
      Real work[size(LU, 1), size(LU, 1)]=LU;
      Integer lda=max(1, size(LU, 1));
      Integer ldb=max(1, size(B, 1));

    external"FORTRAN 77" dgetrs(
              "N",
              n,
              nrhs,
              work,
              lda,
              pivots,
              X,
              ldb,
              info)
    end dgetrs;

    pure function dgetrs_vec
      "Solve a system of linear equations with the LU decomposition from dgetrf"

      extends Modelica.Icons.Function;
      input Real LU[:, size(LU, 1)]
        "LU factorization of dgetrf of a square matrix";
      input Integer pivots[size(LU, 1)] "Pivot vector of dgetrf";
      input Real b[size(LU, 1)] "Right hand side vector b";
      output Real x[size(b, 1)]=b;
      output Integer info;

    protected
      Integer n=size(LU, 1);
      Integer nrhs=1;
      Real work[size(LU, 1), size(LU, 1)]=LU;
      Integer lda=max(1, size(LU, 1));
      Integer ldb=max(1, size(b, 1));

    external"FORTRAN 77" dgetrs(
              "N",
              n,
              nrhs,
              work,
              lda,
              pivots,
              x,
              ldb,
              info)
    end dgetrs_vec;

    pure function dggev
      "Compute generalized eigenvalues, as well as the left and right eigenvectors for a (A,B) system"
      extends Modelica.Icons.Function;

      input Real A[:, size(A, 1)];
      input Real B[size(A, 1), size(A, 1)];
      input Integer nA=size(A, 1)
        "The actual dimensions of matrices A and B (the computation is performed for A[1:nA,1:nA], B[1:nA,1:nA])";
      output Real alphaReal[size(A, 1)]
        "Real part of alpha (eigenvalue=(alphaReal+i*alphaImag)/beta)";
      output Real alphaImag[size(A, 1)] "Imaginary part of alpha";
      output Real beta[size(A, 1)] "Denominator of eigenvalue";
      output Real lEigenVectors[size(A, 1), size(A, 1)]
        "Left eigenvectors of matrix A";
      output Real rEigenVectors[size(A, 1), size(A, 1)]
        "Right eigenvectors of matrix A";

      output Integer info;
    protected
      Integer n=size(A, 1);
      Integer lwork=max(1, 8*n);
      Real Awork[size(A, 1), size(A, 1)]=A;
      Real Bwork[size(A, 1), size(A, 1)]=B;
      Real work[max(1, 8*size(A, 1))];
      Integer lda=max(1, n);

    external"FORTRAN 77" dggev(
              "V",
              "V",
              nA,
              Awork,
              lda,
              Bwork,
              lda,
              alphaReal,
              alphaImag,
              beta,
              lEigenVectors,
              lda,
              rEigenVectors,
              lda,
              work,
              lwork,
              info)
    end dggev;

    pure function dggevx
      "Compute generalized eigenvalues for a (A,B) system, using lapack routine dggevx"
      extends Modelica.Icons.Function;

      input Real A[:, size(A, 1)];
      input Real B[size(A, 1), size(A, 1)];
      output Real alphaReal[size(A, 1)]
        "Real part of alpha (eigenvalue=(alphaReal+i*alphaImag)/beta)";
      output Real alphaImag[size(A, 1)] "Imaginary part of alpha";
      output Real beta[size(A, 1)] "Denominator of eigenvalue";
      output Real lEigenVectors[size(A, 1), size(A, 1)]
        "Left eigenvectors of matrix A";
      output Real rEigenVectors[size(A, 1), size(A, 1)]
        "Right eigenvectors of matrix A";

      output Integer info;
    protected
      Integer n=size(A, 1);
      Integer lda=max(1, size(A, 1));
      Integer ilo;
      Integer ihi;
      Real lscale[size(A, 1)];
      Real rscale[size(A, 1)];
      Real abnrm;
      Real bbnrm;
      Real rconde[size(A, 1)];
      Real rcondv[size(A, 1)];
      Integer lwork=2*n*n + 12*n + 16;
      Real work[2*size(A, 1)*size(A, 1) + 12*size(A, 1) + 16];
      Integer iwork[size(A, 1) + 6];
      Integer bwork[size(A, 1)];

    external"FORTRAN 77" dggevx(
              "B",
              "V",
              "V",
              "B",
              n,
              A,
              lda,
              B,
              lda,
              alphaReal,
              alphaImag,
              beta,
              lEigenVectors,
              lda,
              rEigenVectors,
              lda,
              ilo,
              ihi,
              lscale,
              rscale,
              abnrm,
              bbnrm,
              rconde,
              rcondv,
              work,
              lwork,
              iwork,
              bwork,
              info)
    end dggevx;

    pure function dgglse_vec
      "Solve a linear equality constrained least squares problem"
      extends Modelica.Icons.Function;
      input Real A[:, :] "Minimize |A*x - c|^2";
      input Real c[size(A, 1)];
      input Real B[:, size(A, 2)] "Subject to B*x=d";
      input Real d[size(B, 1)];
      output Real x[size(A, 2)] "Solution vector";
      output Integer info;
    protected
      Integer nrow_A=size(A, 1);
      Integer nrow_B=size(B, 1);
      Integer ncol_A=size(A, 2) "(min=nrow_B,max=nrow_A+nrow_B) required";
      Real Awork[size(A, 1), size(A, 2)]=A;
      Real Bwork[size(B, 1), size(A, 2)]=B;
      Real cwork[size(A, 1)]=c;
      Real dwork[size(B, 1)]=d;
      Integer lwork=max(1, ncol_A + nrow_B + max(nrow_A, max(ncol_A, nrow_B))*5);
      Real work[max(1, size(A, 2) + size(B, 1) + max(size(A, 1), max(size(A, 2), size(
        B, 1)))*5)];

    external"FORTRAN 77" dgglse(
              nrow_A,
              ncol_A,
              nrow_B,
              Awork,
              nrow_A,
              Bwork,
              nrow_B,
              cwork,
              dwork,
              x,
              work,
              lwork,
              info)
    end dgglse_vec;

    pure function dgtsv
      "Solve real system of linear equations A*X=B with B matrix and tridiagonal A"

      extends Modelica.Icons.Function;
      input Real superdiag[:];
      input Real diag[size(superdiag, 1) + 1];
      input Real subdiag[size(superdiag, 1)];
      input Real B[size(diag, 1), :];
      output Real X[size(B, 1), size(B, 2)]=B;
      output Integer info;
    protected
      Integer n=size(diag, 1);
      Integer nrhs=size(B, 2);
      Integer ldb=size(B, 1);
      Real superdiagwork[size(superdiag, 1)]=superdiag;
      Real diagwork[size(diag, 1)]=diag;
      Real subdiagwork[size(subdiag, 1)]=subdiag;

    external"FORTRAN 77" dgtsv(
              n,
              nrhs,
              subdiagwork,
              diagwork,
              superdiagwork,
              X,
              ldb,
              info)
    end dgtsv;

    pure function dgtsv_vec
      "Solve real system of linear equations A*x=b with b vector and tridiagonal A"

      extends Modelica.Icons.Function;
      input Real superdiag[:];
      input Real diag[size(superdiag, 1) + 1];
      input Real subdiag[size(superdiag, 1)];
      input Real b[size(diag, 1)];
      output Real x[size(b, 1)]=b;
      output Integer info;
    protected
      Integer n=size(diag, 1);
      Integer nrhs=1;
      Integer ldb=size(b, 1);
      Real superdiagwork[size(superdiag, 1)]=superdiag;
      Real diagwork[size(diag, 1)]=diag;
      Real subdiagwork[size(subdiag, 1)]=subdiag;

    external"FORTRAN 77" dgtsv(
              n,
              nrhs,
              subdiagwork,
              diagwork,
              superdiagwork,
              x,
              ldb,
              info)
    end dgtsv_vec;

    pure function dhgeqz "Compute generalized eigenvalues for a (A,B) system"
      extends Modelica.Icons.Function;

      input Real A[:, size(A, 1)];
      input Real B[size(A, 1), size(A, 1)];
      output Real alphaReal[size(A, 1)]
        "Real part of alpha (eigenvalue=(alphaReal+i*alphaImag)/beta)";
      output Real alphaImag[size(A, 1)] "Imaginary part of alpha";
      output Real beta[size(A, 1)] "Denominator of eigenvalue";

      output Integer info;
    protected
      Integer n=size(A, 1);
      Integer ilo=1;
      Integer ihi=n;
      Integer lwork=max(1, 3*n);
      Real work[max(1, 3*size(A, 1))];
      Real Awork[size(A, 1), size(A, 1)]=A;
      Real Bwork[size(A, 1), size(A, 1)]=B;
      Integer lda=max(1, n);
      Real Q[1, 1]=fill(
                0,
                1,
                n);

    external"FORTRAN 77" dhgeqz(
              "E",
              "N",
              "N",
              n,
              ilo,
              ihi,
              Awork,
              lda,
              Bwork,
              lda,
              alphaReal,
              alphaImag,
              beta,
              Q,
              1,
              Q,
              1,
              work,
              lwork,
              info)
    end dhgeqz;

    pure function dhseqr
      "Compute eigenvalues of a matrix H using lapack routine DHSEQR for Hessenberg form matrix"
      extends Modelica.Icons.Function;

      input Real H[:, size(H, 1)] "Matrix H with Hessenberg form";
      input Boolean eigenValuesOnly=true
        "= true, if only eigenvalues are computed, otherwise compute the Schur form too";
      input String compz="N" "Specifies the computation of the Schur vectors";
      input Real Z[:, :]=H "Matrix Z";
      output Real alphaReal[size(H, 1)]
        "Real part of alpha (eigenvalue=(alphaReal+i*alphaImag))";
      output Real alphaImag[size(H, 1)]
        "Imaginary part of alpha (eigenvalue=(alphaReal+i*alphaImag))";
      output Integer info;
      output Real Ho[:, :]=H
        "Schur decomposition (if eigenValuesOnly==false, unspecified else)";
      output Real Zo[:, :]=Z;
      output Real work[3*max(1, size(H, 1))];

    protected
      Integer n=size(H, 1);
      String job=if eigenValuesOnly then "E" else "S";
      Integer ilo=1;
      Integer ihi=n;
      Integer ldh=max(n, 1);
      Integer lwork=3*max(1, size(H, 1))
        "Dimension of the dwork array used in dhseqr";

    external"FORTRAN 77" dhseqr(
              job,
              compz,
              n,
              ilo,
              ihi,
              Ho,
              ldh,
              alphaReal,
              alphaImag,
              Zo,
              ldh,
              work,
              lwork,
              info) 2-by-2 diagonal blocks (corresponding to
          complex conjugate pairs of eigenvalues) are returned in
          standard form, with H(i,i) = H(i+1,i+1) and
          H(i+1,i)*H(i,i+1)<0. If INFO = 0 and JOB = 'E', the
          contents of H are unspecified on exit.  (The output value of
          H when INFO>0 is given under the description of INFO
          below.)

          Unlike earlier versions of DHSEQR, this subroutine may
          explicitly H(i,j) = 0 for i>j and j = 1, 2, ... ILO-1
          or j = IHI+1, IHI+2, ... N.

    LDH   (input) INTEGER
          The leading dimension of the array H. LDH >= max(1,N).

    WR    (output) DOUBLE PRECISION array, dimension (N)
    WI    (output) DOUBLE PRECISION array, dimension (N)
          The real and imaginary parts, respectively, of the computed
          eigenvalues. If two eigenvalues are computed as a complex
          conjugate pair, they are stored in consecutive elements of
          WR and WI, say the i-th and (i+1)th, with WI(i) > 0 and
          WI(i+1) < 0. If JOB = 'S', the eigenvalues are stored in
          the same order as on the diagonal of the Schur form returned
          in H, with WR(i) = H(i,i) and, if H(i:i+1,i:i+1) is a 2-by-2
          diagonal block, WI(i) = sqrt(-H(i+1,i)*H(i,i+1)) and
          WI(i+1) = -WI(i).

    Z     (input/output) DOUBLE PRECISION array, dimension (LDZ,N)
          If COMPZ = 'N', Z is not referenced.
          If COMPZ = 'I', on entry Z need not be set and on exit,
          if INFO = 0, Z contains the orthogonal matrix Z of the Schur
          vectors of H.  If COMPZ = 'V', on entry Z must contain an
          N-by-N matrix Q, which is assumed to be equal to the unit
          matrix except for the submatrix Z(ILO:IHI,ILO:IHI). On exit,
          if INFO = 0, Z contains Q*Z.
          Normally Q is the orthogonal matrix generated by DORGHR
          after the call to DGEHRD which formed the Hessenberg matrix
          H. (The output value of Z when INFO>0 is given under
          the description of INFO below.)

    LDZ   (input) INTEGER
          The leading dimension of the array Z.  if COMPZ = 'I' or
          COMPZ = 'V', then LDZ>=MAX(1,N).  Otherwise, LDZ>=1.

    WORK  (workspace/output) DOUBLE PRECISION array, dimension (LWORK)
          On exit, if INFO = 0, WORK(1) returns an estimate of
          the optimal value for LWORK.

    LWORK (input) INTEGER
          The dimension of the array WORK.  LWORK >= max(1,N)
          is sufficient and delivers very good and sometimes
          optimal performance.  However, LWORK as large as 11*N
          may be required for optimal performance.  A workspace
          query is recommended to determine the optimal workspace
          size.

          If LWORK = -1, then DHSEQR does a workspace query.
          In this case, DHSEQR checks the input parameters and
          estimates the optimal workspace size for the given
          values of N, ILO and IHI.  The estimate is returned
          in WORK(1).  No error message related to LWORK is
          issued by XERBLA.  Neither H nor Z are accessed.

    INFO  (output) INTEGER
          = 0: successful exit
          < 0: if INFO = -i, the i-th argument had an illegal
               value
          > 0: if INFO = i, DHSEQR failed to compute all of
               the eigenvalues.  Elements 1:ilo-1 and i+1:n of WR
               and WI contain those eigenvalues which have been
               successfully computed.  (Failures are rare.)

               If INFO > 0 and JOB = 'E', then on exit, the
               remaining unconverged eigenvalues are the eigen-
               values of the upper Hessenberg matrix rows and
               columns ILO through INFO of the final, output
               value of H.

               If INFO > 0 and JOB   = 'S', then on exit

          (*)  (initial value of H)*U  = U*(final value of H)

               where U is an orthogonal matrix.  The final
               value of H is upper Hessenberg and quasi-triangular
               in rows and columns INFO+1 through IHI.

               If INFO > 0 and COMPZ = 'V', then on exit

                 (final value of Z)  =  (initial value of Z)*U

               where U is the orthogonal matrix in (*) (regard-
               less of the value of JOB.)

               If INFO > 0 and COMPZ = 'I', then on exit
                     (final value of Z)  = U
               where U is the orthogonal matrix in (*) (regard-
               less of the value of JOB.)

               If INFO > 0 and COMPZ = 'N', then Z is not
               accessed.
"));
    end dhseqr;

    pure function dlange "Norm of a matrix"
      extends Modelica.Icons.Function;

      input Real A[:, :] "Real matrix A";
      input String norm="1" "Specifies the norm, i.e., 1, I, F, M";
      output Real anorm "Norm of A";
    protected
      Integer m=size(A, 1);
      Integer n=size(A, 2);
      Integer lda=max(1, size(A, 1));
      Real work[lda];

    external"FORTRAN 77" anorm = dlange(
              norm,
              m,
              n,
              A,
              lda,
              work)

    end dlange;

    pure function dorghr
      "Generate a real orthogonal matrix Q which is defined as the product of IHI-ILO elementary reflectors of order N, as returned by DGEHRD"
      extends Modelica.Icons.Function;

      input Real A[:, size(A, 1)]
        "Square matrix with the elementary reflectors";
      input Integer ilo=1
        "Lowest index where the original matrix is not in upper triangular form - ilo must have the same value as in the previous call of DGEHRD";
      input Integer ihi=size(A, 1)
        "Highest index where the original matrix is not in upper triangular form - ihi must have the same value as in the previous call of DGEHRD";
      input Real tau[max(0, size(A, 1) - 1)]
        "Scalar factors of the elementary reflectors";
      output Real Aout[size(A, 1), size(A, 2)]=A
        "Orthogonal matrix as a result of elementary reflectors";
      output Integer info;
    protected
      Integer n=size(A, 1);
      Integer lda=max(1, n);
      Integer lwork=max(1, 3*n);
      Real work[max(1, 3*size(A, 1))];

    external"FORTRAN 77" dorghr(
              n,
              ilo,
              ihi,
              Aout,
              lda,
              tau,
              work,
              lwork,
              info)
    end dorghr;

    pure function dorgqr
      "Generate a Real orthogonal matrix Q which is defined as the product of elementary reflectors as returned from dgeqrf"

      extends Modelica.Icons.Function;
      input Real QR[:, :] "QR from dgeqrf";
      input Real tau[min(size(QR, 1), size(QR, 2))]
        "The scalar factors of the elementary reflectors of Q";
      output Real Q[size(QR, 1), size(QR, 2)]=QR "Orthogonal matrix Q";
      output Integer info;

    protected
      Integer m=size(QR, 1);
      Integer n=size(QR, 2);
      Integer k=size(tau, 1);
      Integer lda=max(1, size(Q, 1));
      Integer lwork=max(1, min(10, size(QR, 2))*size(QR, 2))
        "Length of work array";
      Real work[max(1, min(10, size(QR, 2))*size(QR, 2))];

    external"FORTRAN 77" dorgqr(
              m,
              n,
              k,
              Q,
              lda,
              tau,
              work,
              lwork,
              info)
    end dorgqr;

    pure function dormhr
      "Overwrite the general real M-by-N matrix C with Q * C or C * Q or Q' * C or C * Q', where Q is an orthogonal matrix as returned by dgehrd"
      extends Modelica.Icons.Function;

      input Real C[:, :];
      input Real A[:, :];
      input Real tau[if side == "L" then size(C, 2) - 1 else size(C, 1) - 1];
      input String side="L";
      input String trans="N";
      input Integer ilo=1
        "Lowest index where the original matrix is not in upper triangular form";
      input Integer ihi=if side == "L" then size(C, 1) else size(C, 2)
        "Highest index where the original matrix is not in upper triangular form";
      output Real Cout[size(C, 1), size(C, 2)]=C
        "Contains the Hessenberg form in the upper triangle and the first subdiagonal and below the first subdiagonal it contains the elementary reflectors which represents (with array tau) as a product the orthogonal matrix Q";

      output Integer info;
    protected
      Integer m=size(C, 1);
      Integer n=size(C, 2);
      Integer lda=max(1, size(A, 2));
      Integer ldc=max(1, m);
      Integer lwork=max(1, 2*size(A, 2));
      Real work[max(1, 2*size(A, 2))];

    external"FORTRAN 77" dormhr(
              side,
              trans,
              m,
              n,
              ilo,
              ihi,
              A,
              lda,
              tau,
              Cout,
              ldc,
              work,
              lwork,
              info)
            if SIDE = 'R', LWORK >= max(1,M).
            For optimum performance LWORK >= N*NB if SIDE = 'L', and
            LWORK >= M*NB if SIDE = 'R', where NB is the optimal
            blocksize.

            If LWORK = -1, then a workspace query is assumed; the routine
            only calculates the optimal size of the WORK array, returns
            this value as the first entry of the WORK array, and no error
            message related to LWORK is issued by XERBLA.

    INFO    (output) INTEGER
            = 0:  successful exit
            < 0:  if INFO = -i, the i-th argument had an illegal value
"));
    end dormhr;

    pure function dormqr
      "Overwrite the general real M-by-N matrix C with Q * C or C * Q or Q' * C or C * Q', where Q is an orthogonal matrix of a QR factorization as returned by dgeqrf"
      extends Modelica.Icons.Function;

      input Real C[:, :];
      input Real A[:, :];
      input Real tau[:];
      input String side="L";
      input String trans="N";

      output Real Cout[size(C, 1), size(C, 2)]=C
        "Contains Q*C or Q**T*C or C*Q**T or C*Q";

      output Integer info;
    protected
      Integer m=size(C, 1);
      Integer n=size(C, 2);
      Integer k=if side == "L" then m else n;
      Integer lda=if side == "L" then max(1, m) else max(1, n);
      Integer ldc=max(1, m);
      Integer lwork=if side == "L" then max(1, n) else max(1, m);
      Real work[if side == "L" then max(1, size(C, 2)) else max(1, size(C, 1))];

    external"FORTRAN 77" dormqr(
              side,
              trans,
              m,
              n,
              k,
              A,
              lda,
              tau,
              Cout,
              ldc,
              work,
              lwork,
              info)
            if SIDE = 'R', LDA >= max(1,N).

    TAU     (input) DOUBLE PRECISION array, dimension (K)
            TAU(i) must contain the scalar factor of the elementary
            reflector H(i), as returned by DGEQRF.

    C       (input/output) DOUBLE PRECISION array, dimension (LDC,N)
            On entry, the M-by-N matrix C.
            On exit, C is overwritten by Q*C or Q**T*C or C*Q**T or C*Q.

    LDC     (input) INTEGER
            The leading dimension of the array C. LDC >= max(1,M).

    WORK    (workspace/output) DOUBLE PRECISION array, dimension (MAX(1,LWORK))
            On exit, if INFO = 0, WORK(1) returns the optimal LWORK.

    LWORK   (input) INTEGER
            The dimension of the array WORK.
            If SIDE = 'L', LWORK >= max(1,N);
            if SIDE = 'R', LWORK >= max(1,M).
            For optimum performance LWORK >= N*NB if SIDE = 'L', and
            LWORK >= M*NB if SIDE = 'R', where NB is the optimal
            blocksize.

            If LWORK = -1, then a workspace query is assumed; the routine
            only calculates the optimal size of the WORK array, returns
            this value as the first entry of the WORK array, and no error
            message related to LWORK is issued by XERBLA.

    INFO    (output) INTEGER
            = 0:  successful exit
            < 0:  if INFO = -i, the i-th argument had an illegal value
"));
    end dormqr;

    pure function dpotrf
      "Compute the Cholesky factorization of a real symmetric positive definite matrix A"
      extends Modelica.Icons.Function;

      input Real A[:, size(A, 1)] "Real symmetric positive definite matrix A";
      input Boolean upper=true "= true, if the upper triangle of A is provided";

      output Real Acholesky[size(A, 1), size(A, 1)]=A "Cholesky factor";
      output Integer info;
    protected
      String uplo=if upper then "U" else "L";
      Integer n=size(A, 1);
      Integer lda=max(1, n);
    external"FORTRAN 77" dpotrf(
              uplo,
              n,
              Acholesky,
              lda,
              info)
    end dpotrf;

    pure function dtrevc
      "Compute the right and/or left eigenvectors of a real upper quasi-triangular matrix T"
      extends Modelica.Icons.Function;

      input Real T[:, size(T, 1)] "Upper quasi triangular matrix";
      input String side="R" "Specify which eigenvectors";
      input String howmny="B" "Specify how many eigenvectors";
      input Real Q[size(T, 1), size(T, 1)]
        "Orthogonal matrix Q of Schur vectors returned by DHSEQR";

      output Real lEigenVectors[size(T, 1), size(T, 1)]=Q
        "Left eigenvectors of matrix T";
      output Real rEigenVectors[size(T, 1), size(T, 1)]=Q
        "Right eigenvectors of matrix T";
      output Integer info;

    protected
      Integer n=size(T, 1);
      Boolean select[size(T, 1)];
      Integer ldt=max(1, n);
      Integer ldvl=max(1, n);
      Integer ldvr=max(1, n);
      Real work[3*size(T, 1)];

    external"FORTRAN 77" dtrevc(
              side,
              howmny,
              select,
              n,
              T,
              ldt,
              lEigenVectors,
              ldvl,
              rEigenVectors,
              ldvr,
              n,
              n,
              work,
              info)
    end dtrevc;

    pure function dtrsen "Reorder the real Schur factorization of a real matrix"
      extends Modelica.Icons.Function;

      input String job="N" "Specifies the usage of a condition number";
      input String compq="V" "Is \"V\" if Schur vector matrix is to be updated";
      input Boolean select[:] "Specifies the eigenvalues to reorder";
      input Real T[:, :] "Real Schur form to be reordered";
      input Real Q[:, size(T, 2)] "Matrix of the Schur vectors";

    protected
      Integer n=size(T, 2);
      Integer ldt=max(1, n);
      Integer ldq=if compq == "V" then max(n, 1) else 1;
      Integer lwork=max(1, if job == "N" then n else if job == "E" then n*n
           else 2*n*n);
      Real work[max(1, if job == "N" then size(T, 2) else if job == "E" then
        size(T, 2)*size(T, 2) else 2*size(T, 2)*size(T, 2))];
      Integer liwork=if job == "N" or job == "E" then 1 else n*n;
      Integer iwork[if job == "N" or job == "E" then 1 else size(T, 2)*size(T,
        2)];

    public
      output Real To[:, :]=T "Reordered Schur form";
      output Real Qo[:, :]=Q "Reordered Schur vectors";
      output Real wr[size(T, 2)] "Reordered eigenvalues, real part";
      output Real wi[size(T, 2)] "Reordered eigenvalues, imaginary part";
      output Integer m
        "Dimension of the invariant sub space spanned bei the selected eigenvalues";
      output Real s
        "Lower bound of the reciprocal condition number. Not referenced for job==V";
      output Real sep
        "Estimated reciprocal condition number of the specified invariant subspace";
      output Integer info;

    external"FORTRAN 77" dtrsen(
              job,
              compq,
              select,
              n,
              To,
              ldt,
              Qo,
              ldq,
              wr,
              wi,
              m,
              s,
              sep,
              work,
              lwork,
              iwork,
              liwork,
              info)
            = 'V': for invariant subspace only (SEP);
            = 'B': for both eigenvalues and invariant subspace (S and
                   SEP).

    COMPQ   (input) CHARACTER*1
            = 'V': update the matrix Q of Schur vectors;
            = 'N': do not update Q.

    SELECT  (input) LOGICAL array, dimension (N)
            SELECT specifies the eigenvalues in the selected cluster. To
            select a real eigenvalue w(j), SELECT(j) must be set to
            .TRUE.. To select a complex conjugate pair of eigenvalues
            w(j) and w(j+1), corresponding to a 2-by-2 diagonal block,
            either SELECT(j) or SELECT(j+1) or both must be set to
            .TRUE.; a complex conjugate pair of eigenvalues must be
            either both included in the cluster or both excluded.

    N       (input) INTEGER
            The order of the matrix T. N >= 0.

    T       (input/output) DOUBLE PRECISION array, dimension (LDT,N)
            On entry, the upper quasi-triangular matrix T, in Schur
            canonical form.
            On exit, T is overwritten by the reordered matrix T, again in
            Schur canonical form, with the selected eigenvalues in the
            leading diagonal blocks.

    LDT     (input) INTEGER
            The leading dimension of the array T. LDT >= max(1,N).

    Q       (input/output) DOUBLE PRECISION array, dimension (LDQ,N)
            On entry, if COMPQ = 'V', the matrix Q of Schur vectors.
            On exit, if COMPQ = 'V', Q has been postmultiplied by the
            orthogonal transformation matrix which reorders T; the
            leading M columns of Q form an orthonormal basis for the
            specified invariant subspace.
            If COMPQ = 'N', Q is not referenced.

    LDQ     (input) INTEGER
            The leading dimension of the array Q.
            LDQ >= 1; and if COMPQ = 'V', LDQ >= N.

    WR      (output) DOUBLE PRECISION array, dimension (N)
    WI      (output) DOUBLE PRECISION array, dimension (N)
            The real and imaginary parts, respectively, of the reordered
            eigenvalues of T. The eigenvalues are stored in the same
            order as on the diagonal of T, with WR(i) = T(i,i) and, if
            T(i:i+1,i:i+1) is a 2-by-2 diagonal block, WI(i) > 0 and
            WI(i+1) = -WI(i). Note that if a complex eigenvalue is
            sufficiently ill-conditioned, then its value may differ
            significantly from its value before reordering.

    M       (output) INTEGER
            The dimension of the specified invariant subspace.
            0 < = M <= N.

    S       (output) DOUBLE PRECISION
            If JOB = 'E' or 'B', S is a lower bound on the reciprocal
            condition number for the selected cluster of eigenvalues.
            S cannot underestimate the true reciprocal condition number
            by more than a factor of sqrt(N). If M = 0 or N, S = 1.
            If JOB = 'N' or 'V', S is not referenced.

    SEP     (output) DOUBLE PRECISION
            If JOB = 'V' or 'B', SEP is the estimated reciprocal
            condition number of the specified invariant subspace. If
            M = 0 or N, SEP = norm(T).
            If JOB = 'N' or 'E', SEP is not referenced.

    WORK    (workspace/output) DOUBLE PRECISION array, dimension (MAX(1,LWORK))
            On exit, if INFO = 0, WORK(1) returns the optimal LWORK.

    LWORK   (input) INTEGER
            The dimension of the array WORK.
            If JOB = 'N', LWORK >= max(1,N);
            if JOB = 'E', LWORK >= max(1,M*(N-M));
            if JOB = 'V' or 'B', LWORK >= max(1,2*M*(N-M)).

            If LWORK = -1, then a workspace query is assumed; the routine
            only calculates the optimal size of the WORK array, returns
            this value as the first entry of the WORK array, and no error
            message related to LWORK is issued by XERBLA.

    IWORK   (workspace) INTEGER array, dimension (MAX(1,LIWORK))
            On exit, if INFO = 0, IWORK(1) returns the optimal LIWORK.

    LIWORK  (input) INTEGER
            The dimension of the array IWORK.
            If JOB = 'N' or 'E', LIWORK >= 1;
            if JOB = 'V' or 'B', LIWORK >= max(1,M*(N-M)).

            If LIWORK = -1, then a workspace query is assumed; the
            routine only calculates the optimal size of the IWORK array,
            returns this value as the first entry of the IWORK array, and
            no error message related to LIWORK is issued by XERBLA.

    INFO    (output) INTEGER
            = 0: successful exit
            < 0: if INFO = -i, the i-th argument had an illegal value
            = 1: reordering of T failed because some eigenvalues are too
                 close to separate (the problem is very ill-conditioned);
                 T may have been partially reordered, and WR and WI
                 contain the eigenvalues in the same order as in T; S and
                 SEP (if requested) are set to zero.

    Further Details
    ===============

    DTRSEN first collects the selected eigenvalues by computing an
    orthogonal transformation Z to move them to the top left corner of T.
    In other words, the selected eigenvalues are the eigenvalues of T11
    in:

                  Z'*T*Z = ( T11 T12 ) n1
                           (  0  T22 ) n2
                              n1  n2

    where N = n1+n2 and Z' means the transpose of Z. The first n1 columns
    of Z span the specified invariant subspace of T.

    If T has been obtained from the real Schur factorization of a matrix
    A = Q*T*Q', then the reordered real Schur factorization of A is given
    by A = (Q*Z)*(Z'*T*Z)*(Q*Z)', and the first n1 columns of Q*Z span
    the corresponding invariant subspace of A.

    The reciprocal condition number of the average of the eigenvalues of
    T11 may be returned in S. S lies between 0 (very badly conditioned)
    and 1 (very well conditioned). It is computed as follows. First we
    compute R so that

                           P = ( I  R ) n1
                               ( 0  0 ) n2
                                 n1 n2

    is the projector on the invariant subspace associated with T11.
    R is the solution of the Sylvester equation:

                          T11*R - R*T22 = T12.

    Let F-norm(M) denote the Frobenius-norm of M and 2-norm(M) denote
    the two-norm of M. Then S is computed as the lower bound

                        (1 + F-norm(R)**2)**(-1/2)

    on the reciprocal of 2-norm(P), the true reciprocal condition number.
    S cannot underestimate 1 / 2-norm(P) by more than a factor of
    sqrt(N).

    An approximate error bound for the computed average of the
    eigenvalues of T11 is

                           EPS * norm(T) / S

    where EPS is the machine precision.

    The reciprocal condition number of the right invariant subspace
    spanned by the first n1 columns of Z (or of Q*Z) is returned in SEP.
    SEP is defined as the separation of T11 and T22:

                       sep( T11, T22 ) = sigma-min( C )

    where sigma-min(C) is the smallest singular value of the
    n1*n2-by-n1*n2 matrix

       C  = kprod( I(n2), T11 ) - kprod( transpose(T22), I(n1) )

    I(m) is an m by m identity matrix, and kprod denotes the Kronecker
    product. We estimate sigma-min(C) by the reciprocal of an estimate of
    the 1-norm of inverse(C). The true reciprocal 1-norm of inverse(C)
    cannot differ from sigma-min(C) by more than a factor of sqrt(n1*n2).

    When SEP is small, small changes in T can cause large changes in
    the invariant subspace. An approximate bound on the maximum angular
    error in the computed right invariant subspace is

                        EPS * norm(T) / SEP
"));
    end dtrsen;

    pure function dtrsm
      "Solve one of the matrix equations op( A )*X = alpha*B, or X*op( A ) = alpha*B, where A is triangular matrix. BLAS routine"
      extends Modelica.Icons.Function;

      input Real A[:, :] "Input matrix A";
      input Real B[:, :] "Input matrix B";
      input Real alpha=1 "Factor alpha";
      input Boolean right=true "= true, if A is right multiplication";
      input Boolean upper=true "= true, if A is upper triangular";
      input Boolean trans=false "= true, if op(A) means transposed(A)";
      input Boolean unitTriangular=false
        "= true, if A is unit triangular, i.e., all diagonal elements of A are equal to 1";

      output Real X[size(B, 1), size(B, 2)]=B
        "Matrix Bout=alpha*op( A )*B, or B := alpha*B*op( A )";
    protected
      String side=if right then "R" else "L";
      String uplo=if upper then "U" else "L";
      String transA=if trans then "T" else "N";
      String diag=if unitTriangular then "U" else "N";
      Integer m=size(B, 1) "Number of rows of B";
      Integer n=size(B, 2) "Number of columns of B";
      Integer lda=if right then max(1, n) else max(1, m) "First dimension of A";
      Integer ldb=max(1, m) "First dimension of B";

    external"FORTRAN 77" dtrsm(
              side,
              uplo,
              transA,
              diag,
              m,
              n,
              alpha,
              A,
              lda,
              X,
              ldb)
    end dtrsm;

    pure function dtrsyl
      "Solve the real Sylvester matrix equation op(A)*X + X*op(B) = scale*C or op(A)*X - X*op(B) = scale*C"
      extends Modelica.Icons.Function;

      input Real A[:, :] "Upper quasi-triangular matrix";
      input Real B[:, :] "Upper quasi-triangular matrix";
      input Real C[if tranA then size(A, 1) else size(A, 2), if tranB then size(
        B, 1) else size(B, 2)] "Right side of the Sylvester equation";

      input Boolean tranA=false "= true, if op(A)=A'";
      input Boolean tranB=false "= true, if op(B)=B'";
      input Integer isgn=1 "Specifies the sign in the equation, +1 or -1";
      output Real X[size(C, 1), size(C, 2)]=C
        "Solution of the Sylvester equation";
      output Real scale "Scale factor";
      output Integer info;
    protected
      Integer m=if tranA then size(A, 1) else size(A, 2);
      Integer n=if tranB then size(B, 1) else size(B, 2);
      String trana=if tranA then "T" else "N";
      String tranb=if tranB then "T" else "N";
      Integer lda=max(1, m);
      Integer ldb=max(1, n);

    external"FORTRAN 77" dtrsyl(
              trana,
              tranb,
              isgn,
              m,
              n,
              A,
              lda,
              B,
              ldb,
              X,
              lda,
              scale,
              info)
    end dtrsyl;
  end LAPACK;

  package Utilities
    "Utility functions that should not be directly utilized by the user"
    extends Modelica.Icons.UtilitiesPackage;

    function continuousRiccatiIterative
      "Newton's method with exact line search for iterative solving continuous algebraic Riccati equation"
      extends Modelica.Icons.Function;

      import Modelica.Math.Matrices;

      input Real A[:, size(A, 1)]
        "Matrix A of Riccati equation X*A + A'*X -X*G*X +Q = 0";
      input Real B[size(A, 1), :] "Matrix B in G = B*inv(R)*B'";
      input Real R[size(B, 2), size(B, 2)]=identity(size(B, 2))
        "Matrix R in G = B*inv(R)*B'";
      input Real Q[size(A, 1), size(A, 2)]=identity(size(A, 1))
        "Matrix Q of Riccati equation X*A + A'*X -X*G*X +Q = 0";
      input Real X0[size(A, 1), size(A, 2)]=identity(size(A, 1))
        "Initial approximate solution for X*A + A'*X -X*G*X +Q = 0";
      input Integer maxSteps=10 "Maximal number of iteration steps";
      input Real eps=Matrices.frobeniusNorm(A)*1e-9
        "Tolerance for stop criterion";

      output Real X[size(X0, 1), size(X0, 2)]
        "Solution X of Riccati equation X*A + A'*X -X*G*X +Q = 0";
      output Real r "Norm of X*A + A'*X - X*G*X + Q, zero for exact solution";

    protected
      Integer n=size(A, 1);
      Real G[size(A, 1), size(A, 2)]=B*Matrices.solve2(R, transpose(B));
      Real Xk[size(A, 1), size(A, 2)];
      Real Ak[size(A, 1), size(A, 2)];
      Real Rk[size(A, 1), size(A, 2)];
      Real Nk[size(A, 1), size(A, 2)];
      Real Vk[size(A, 1), size(A, 2)];
      Real tk;
      Integer k;
      Boolean stop;

    algorithm
      if n > 1 then
        k := 0;
        stop := false;
        Xk := X0;
        while (not stop) loop
          k := k + 1;
          Ak := A - G*Xk;
          Rk := transpose(A)*Xk + Xk*A + Q - Xk*G*Xk;
          Nk := Matrices.continuousLyapunov(Ak, -Rk);
          Vk := Nk*G*Nk;
          tk := Matrices.Utilities.findLocal_tk(Rk, Vk);
          stop := eps > Matrices.frobeniusNorm(tk*Nk)/Matrices.frobeniusNorm(Xk)
             or k >= maxSteps;
          Xk := Xk + tk*Nk;
        end while;
        X := Xk;
        r := Matrices.frobeniusNorm(X*A + transpose(A)*X - X*G*X + Q);

      elseif n == 1 then
        // exact calculation
        X := matrix((A[1, 1] - sqrt(A[1, 1]*A[1, 1] + G[1, 1]*Q[1, 1]))/G[1, 1]);
        if X[1, 1]*G[1, 1] < A[1, 1] then
          X := matrix((A[1, 1] + sqrt(A[1, 1]*A[1, 1] + G[1, 1]*Q[1, 1]))/G[1,
            1]);
        end if;
        r := 0;
      else
        X := fill(
                0,
                0,
                0);
        r := 0;
      end if;
(X, r) = Matrices.Utilities.continuousRiccatiIterative(A, B, R, Q, X0, maxSteps, eps);

Description

This function provides a Newton-like method for solving continuous algebraic Riccati equations (care). It utilizes Exact Line Search to improve the sometimes erratic
convergence of Newton's method. Exact line search in this case means, that at each iteration i a Newton step delta_i

X_i+1 = X_i + delta_i

is taken in the direction to minimize the Frobenius norm of the residual

r = || X_i+1*A +A'*X_i+1 - X_i+1*G*X_i+1 + Q ||.

with

      -1
G = B*R *B'

The inputs \"maxSteps\" and \"eps\" specify the termination of the iteration. The iteration is terminated if either
maxSteps iteration steps have been performed or the relative change delta_i/X_i became smaller than eps.

With an appropriate initial value X0 a sufficiently accurate solution might be reach within a few iteration steps. Although a Lyapunov equation
of order n (n is the order of the Riccati equation) is to be solved at each iteration step, the algorithm might be faster
than a direct method like Matrices.continuousRiccati, since direct methods have to solve the 2*n-order Hamiltonian
system equation.

The algorithm is taken from [1] and [2].

References

[1] Benner, P., Byers, R.
    An Exact Line Search Method for Solving Generalized Continuous-Time Algebraic Riccati Equations
    IEEE Transactions On Automatic Control, Vol. 43, No. 1, pp. 101-107, 1998.
[2] Datta, B.N.
    Numerical Methods for Linear Control Systems
    Elsevier Academic Press, 2004.

Example

A=[0.0,         1.0,         0.0,         0.0;
   0.0,        -1.890,       3.900e-01,  -5.530;
   0.0,        -3.400e-02,  -2.980,       2.430;
   3.400e-02,  -1.100e-03,  -9.900e-01,  -2.100e-01];

B=[ 0.0,         0.0;
    3.600e-01,  -1.60;
   -9.500e-01,  -3.200e-02;
    3.000e-02,   0.0];

R=[1, 0; 0, 1];

Q=[2.313,       2.727,       6.880e-01,   2.300e-02;
   2.727,       4.271,       1.148,       3.230e-01;
   6.880e-01,   1.148,       3.130e-01,   1.020e-01;
   2.300e-02,   3.230e-01,   1.020e-01,   8.300e-02];

X0=identity(4);

(X,r) = Matrices.Utilities.continuousRiccatiIterative(A, B, R, Q, X0);

// X = [1.3239,  0.9015,  0.5466, -1.7672;
        0.9015,  0.9607,  0.4334, -1.1989;
        0.5466,  0.4334,  0.4605, -1.3633;
       -1.7672, -1.1989, -1.3633,  4.4612]
// r =  2.48809423389491E-015

(,r) = Matrices.Utilities.continuousRiccatiIterative(A, B, R, Q, X0,4);

// r =  0.0004;

See also

Matrices.Utilities.discreteRiccatiIterative

Matrices.continuousRiccati
", revisions="

2010/04/30
       by Marcus Baur, DLR-RM

"));
    end continuousRiccatiIterative;

    function discreteRiccatiIterative
      "Newton's method with exact line search for solving discrete algebraic Riccati equation"
      extends Modelica.Icons.Function;

      import Modelica.Math.Matrices;

      input Real A[:, size(A, 1)] "Matrix A of discrete Riccati equation";
      input Real B[size(A, 1), :] "Matrix B of discrete Riccati equation";
      input Real R[size(B, 2), size(B, 2)]=identity(size(B, 2))
        "Matrix R of discrete Riccati equation";
      input Real Q[size(A, 1), size(A, 2)]=identity(size(A, 1))
        "Matrix Q of discrete Riccati equation";
      input Real X0[size(A, 1), size(A, 2)]=identity(size(A, 1))
        "Initial approximate solution discrete Riccati equation";
      input Integer maxSteps=10 "Maximal number of iteration steps";
      input Real eps=Matrices.frobeniusNorm(A)*1e-9
        "Tolerance for stop criterion";

      output Real X[size(X0, 1), size(X0, 2)];
      output Real r;

    protected
      Integer n=size(A, 1);
      Real Xk[size(A, 1), size(A, 2)];
      Real Ak[size(A, 1), size(A, 2)];
      Real Rk[size(A, 1), size(A, 2)];
      Real Nk[size(A, 1), size(A, 2)];
      Real Hk[size(B, 2), size(B, 1)];
      Real Vk[size(A, 1), size(A, 2)];
      Real AT[size(A, 2), size(A, 2)]=transpose(A);
      Real BT[size(B, 2), size(B, 1)]=transpose(B);
      Real tk;
      Integer k;

      Boolean stop;

    algorithm
      if n > 0 then
        k := 0;
        stop := false;
        Xk := X0;
        while (not stop) loop
          k := k + 1;
          Hk := Matrices.solve2(R + BT*Xk*B, BT);
          Ak := A - B*Hk*Xk*A;
          Rk := AT*Xk*A - Xk + Q - AT*Xk*B*Hk*Xk*A;
          Nk := Modelica.Math.Matrices.discreteLyapunov(
                  A=Ak,
                  C=-Rk,
                  sgn=-1);
          Vk := transpose(Ak)*Nk*B*Hk*Nk*Ak;
          tk := Modelica.Math.Matrices.Utilities.findLocal_tk(Rk, Vk);
          stop := eps > Matrices.frobeniusNorm(tk*Nk)/Matrices.frobeniusNorm(Xk)
             or k >= maxSteps;
          Xk := Xk + tk*Nk;
        end while;
        X := Xk;
        r := Matrices.frobeniusNorm(AT*X*A - X + Q - AT*X*B*Matrices.solve2(R
           + BT*X*B, BT)*X*A);
      else
        X := fill(
                0,
                0,
                0);
        r := 0;
      end if;
(X, r) = Matrices.Utilities.discreteRiccatiIterative(A, B, R, Q, X0, maxSteps, eps);

Description

This function provides a Newton-like method for solving discrete-time algebraic Riccati equations. It uses Exact Line Search to improve the sometimes erratic
convergence of Newton's method. Exact line search in this case means, that at each iteration i a Newton step delta_i

X_i+1 = X_i + delta_i

is taken in the direction to minimize the Frobenius norm of the residual

r = || A'X_i+1*A - X_i+1 - A'X_i+1*G_i*X_i+1*A + Q ||

with

                     -1
G_i = B*(R + B'*X_i*B) *B'

Output r is the norm of the residual of the last iteration.

The inputs \"maxSteps\" and \"eps\" specify the termination of the iteration. The iteration is terminated if either
maxSteps iteration steps have been performed or the relative change delta_i/X_i became smaller than eps.

With an appropriate initial value X0 a sufficiently accurate solution might be reach with a few iteration steps. Although a Lyapunov equation of
order n (n is the order of the Riccati equation) is to be solved at each iteration step, the algorithm might be faster
than a direct method like Matrices.discreteRiccati, since direct methods have to solve the 2*n-order Hamiltonian
system equation.

The algorithm is taken from [1] and [2].

References

[1] Benner, P., Byers, R.
    An Exact Line Search Method for Solving Generalized Continuous-Time Algebraic Riccati Equations
    IEEE Transactions On Automatic Control, Vol. 43, No. 1, pp. 101-107, 1998.
[2] Datta, B.N.
    Numerical Methods for Linear Control Systems
    Elsevier Academic Press, 2004.

Example

A  = [0.9970,    0.0000,    0.0000,    0.0000;
      1.0000,    0.0000,    0.0000,    0.0000;
      0.0000,    1.0000,    0.0000,    0.0000;
      0.0000,    0.0000,    1.0000,    0.0000];

B  = [0.0150;
      0.0000;
      0.0000;
      0.0000];

R = [0.2500];

Q = [0, 0, 0, 0;
     0, 0, 0, 0;
     0, 0, 0, 0;
     0, 0, 0, 1];

X0=identity(4);

(X,r) = Matrices.Utilities.discreteRiccatiIterative(A, B, R, Q, X0);

//  X = [30.625, 0.0, 0.0, 0.0;
          0.0,   1.0, 0.0, 0.0;
          0.0,   0.0, 1.0, 0.0;
          0.0,   0.0, 0.0, 1.0];

// r =   3.10862446895044E-015

See also

Matrices.Utilities.continuousRiccatiIterative

Matrices.discreteRiccati
", revisions="

2010/04/30
       by Marcus Baur, DLR-RM

"));
    end discreteRiccatiIterative;

    function toUpperHessenberg
      "Transform a real square matrix A to upper Hessenberg form H by orthogonal similarity transformation:  Q' * A * Q = H"
      extends Modelica.Icons.Function;

      import Modelica.Math.Matrices;
      import Modelica.Math.Matrices.LAPACK;

      input Real A[:, size(A, 1)] "Square matrix A";
      input Integer ilo=1
        "Lowest index where the original matrix is not in upper triangular form";
      input Integer ihi=size(A, 1)
        "Highest index where the original matrix is not in upper triangular form";
      output Real H[size(A, 1), size(A, 2)] "Upper Hessenberg form";
      output Real V[size(A, 1), size(A, 2)]
        "V=[v1,v2,..vn-1,0] with vi are vectors which define the elementary reflectors";

      output Real tau[max(0, size(A, 1) - 1)]
        "Scalar factors of the elementary reflectors";
      output Integer info "Information of successful function call";

    protected
      Integer n=size(A, 1);
      Real Aout[size(A, 1), size(A, 2)];
      Integer i;

    algorithm
      if n > 0 then
        if n == 1 then
          H := A;
          V := {{0.0}};
          tau := zeros(0);
          info := 0;
        else
          (Aout, tau, info) := LAPACK.dgehrd(A, ilo, ihi);
          H := zeros(size(H, 1), size(H, 2));
          H[1:2, 1:n] := Aout[1:2, 1:n];
          for i in 3:n loop
            H[i, (i - 1):n] := Aout[i, (i - 1):n];
          end for;
          V := zeros(size(V, 1), size(V, 2));
          for i in ilo:min(n - 2, ihi) loop
            V[i + 1, i] := 1.0;
            V[(i + 2):n, i] := Aout[(i + 2):n, i];
          end for;
          V[n, n - 1] := 1;
        end if;
      end if;
(H, V, tau, info) = Matrices.Utilities.toUpperHessenberg(A,ilo, ihi);

Description

Function toUpperHessenberg computes a upper Hessenberg form H of a matrix A by orthogonal similarity transformation:  Q' * A * Q = H.
The optional inputs ilo and ihi improve efficiency if the matrix is already partially converted to Hessenberg form; it is assumed
that matrix A is already upper Hessenberg for rows and columns 1:(ilo-1) and (ihi+1):size(A, 1).
The function calls LAPACK.dgehrd.
See Matrices.LAPACK.dgehrd for more information about the additional outputs V, tau, info and inputs ilo, ihi.

Example

A  = [1, 2, 3;
      6, 5, 4;
      1, 0, 0];

H = toUpperHessenberg(A);

results in:

H = [1.0,  -2.466,  2.630;
    -6.083, 5.514, -3.081;
     0.0,   0.919, -0.514]

See also

Matrices.hessenberg
", revisions="

2010/04/30
       by Marcus Baur, DLR-RM

"));
    end toUpperHessenberg;

    function eigenvaluesHessenberg
      "Compute eigenvalues of an upper Hessenberg form matrix"
      extends Modelica.Icons.Function;

      import Modelica.Math.Matrices.LAPACK;
      input Real H[:, size(H, 1)] "Hessenberg matrix H";

      output Real ev[size(H, 1), 2] "Eigenvalues";
      output Integer info=0;
    protected
      Real alphaReal[size(H, 1)]
        "Real part of alpha (eigenvalue=(alphaReal+i*alphaImag))";
      Real alphaImag[size(H, 1)]
        "Imaginary part of alpha (eigenvalue=(alphaReal+i*alphaImag))";
    algorithm
      if size(H, 1) > 0 then
        (alphaReal,alphaImag,info) := LAPACK.dhseqr(H);
      else
        alphaReal := fill(0, size(H, 1));
        alphaImag := fill(0, size(H, 1));
      end if;
      ev := [alphaReal, alphaImag];
(ev, info) = Matrices.Utilities.eigenvaluesHessenberg(H);

Description

This function computes the eigenvalues of a Hessenberg form matrix. Transformation to Hessenberg form is the first step in eigenvalue computation for arbitrary matrices with QR decomposition.
This step can be skipped if the matrix has already Hessenberg form.

The function uses the LAPACK-routine dhseqr. Output info is 0 for a successful call of this
function.

See Matrices.LAPACK.dhseqr for details

Example

Real A[3,3] = [1,2,3;
               9,8,7;
               0,1,0];

Real ev[3,2];

ev := Matrices.Utilities.eigenvaluesHessenberg(A);

// ev  = [10.7538,    0.0;
          -0.8769,    1.0444;
          -0.8769,   -1.0444]
// = {10.7538,  -0.8769 +- i*1.0444}

See also

Matrices.eigenValues,
Matrices.hessenberg

", revisions="

2010/04/30
       by Marcus Baur, DLR-RM

"));
    end eigenvaluesHessenberg;

    function reorderRSF
      "Reorders a real Schur form to clusters of stable and unstable eigenvalues"
      extends Modelica.Icons.Function;

      import Modelica.Math.Matrices.LAPACK;

      input Real T[:, :] "Real Schur form";
      input Real Q[:, size(T, 2)] "Schur vector Matrix";
      input Real alphaReal[size(T, 1)]
        "Real part of eigenvalue=alphaReal+i*alphaImag";
      input Real alphaImag[size(T, 1)]
        "Imaginary part of eigenvalue=alphaReal+i*alphaImag";
      input Boolean iscontinuous=true
        "= true, if the according system is continuous. False for discrete systems";

      output Real To[size(T, 1), size(T, 2)] "Reordered Schur form";
      output Real Qo[size(T, 1), size(T, 2)] "Reordered Schur vector matrix";
      output Real wr[size(T, 2)] "Reordered eigenvalues, real part";
      output Real wi[size(T, 2)] "Reordered eigenvalues, imaginary part";

    protected
      Integer n=size(T, 2);
      Boolean select[size(T, 2)]=fill(false, size(T, 2));
      Integer i;
    algorithm
      if iscontinuous then
        for i in 1:n loop
          if alphaReal[i] < 0 then
            select[i] := true;
          end if;
        end for;
      else
        for i in 1:n loop
          if alphaReal[i]^2 + alphaImag[i]^2 < 1 then
            select[i] := true;
          end if;
        end for;
      end if;

      (To,Qo,wr,wi) := LAPACK.dtrsen(
              "E",
              "V",
              select,
              T,
              Q);
(To, Qo, wr, wi) = Matrices.Utilities.reorderRSF(T, Q, alphaReal, alphaImag, iscontinuous);

Description

Function reorderRSF() reorders a real Schur form such that the stable eigenvalues of
the system are in the 1-by-1 and 2-by-2 diagonal blocks of the block upper triangular matrix.
If the Schur form is referenced to a continuous system the staple eigenvalues are in the left complex half plane.
The stable eigenvalues of a discrete system are inside the complex unit circle.

This function is used for example to solve algebraic Riccati equations
(continuousRiccati,
discreteRiccati). In this context the Schur form
as well as the corresponding eigenvalues and the transformation matrix Q are known, why the eigenvalues and the transformation matrix are inputs to reorderRSF().

The Schur vector matrix Qo is also reordered according to To. The vectors wr and wi contains the real and imaginary parts of the
reordered eigenvalues respectively.

Example

T := [-1,2, 3,4;
       0,2, 6,5;
       0,0,-3,5;
       0,0, 0,6];
To := Matrices.Utilities.reorderRSF(T,identity(4),{-1, 2, -3, 6},{0, 0, 0, 0}, true);

// To = [-1.0, -0.384, 3.585, 4.0;
//        0.0, -3.0,   6.0,   0.64;
//        0.0,  0.0,   2.0,   7.04;
//        0.0,  0.0,   0.0,   6.0]

See also Matrices.realSchur

", revisions="

2010/04/30
       by Marcus Baur, DLR-RM

"));
    end reorderRSF;

    function findLocal_tk
      "Find a local minimizer tk to define the length of the step tk*Nk in continuousRiccatiIterative and discreteRiccatiIterative"
      extends Modelica.Icons.Function;

      import Modelica.Math.Matrices;
      import Modelica.Math.Polynomials;

      input Real Rk[:, size(Rk, 1)];
      input Real Vk[size(Rk, 1), size(Rk, 2)];

      output Real tk;

    protected
      Real alpha_k;
      Real beta_k;
      Real gamma_k;
      Real p[3, 2];
      Boolean h;

    algorithm
      alpha_k := Matrices.trace(Rk*Rk);
      beta_k := Matrices.trace(Rk*Vk);
      gamma_k := Matrices.trace(Vk*Vk);

      if gamma_k > Modelica.Constants.eps then
        p := Polynomials.roots({4*gamma_k,6*beta_k,2*(alpha_k - 2*beta_k),
          -2*alpha_k});
        h := false;
        for i1 in 1:3 loop
          if abs(p[i1, 2]) < Modelica.Constants.eps then
            if abs(p[i1, 1] - 1) <= 1 then
              tk := p[i1, 1];
              h := true;
            end if;
          end if;
        end for;
        if not h then
          tk := 1;
        end if;

      else
        tk := 1;
      end if;

Description

Function findLocal_tk() is an auxiliary function called in iterative solver for algebraic Riccati equation based on Newton's method with
exact line search like continuousRiccatiIterative

and discreteRiccatiIterative.

The function computes the local minimum of the function f_k(t_k)

f_k(t_k) = alpha_k*(1-t_k)^2 + 2*beta_k*(1-t)*t^2 + gamma_k*t^4

by calculating the zeros of the derivation d f_k/d t_k. It is known that the function f_k(t_k) has a local minimum at some value t_k_min in [0, 2].

With t_k_min the norm of the next residual of the algorithm will be minimized.

See [1] for more information

References

[1] Benner, P., Byers, R.
    An Exact Line Search Method for Solving Generalized Continuous-Time Algebraic Riccati Equations
    IEEE Transactions On Automatic Control, Vol. 43, No. 1, pp. 101-107, 1998.

See also

Matrices.Utilities.continuousRiccatiIterative

Matrices.Utilities.discreteRiccatiIterative

", revisions="

2010/04/30
       by Marcus Baur, DLR-RM

"));
    end findLocal_tk;
  end Utilities;
end Matrices;

package Icons "Icons for Math"
  extends Modelica.Icons.IconsPackage;

  partial function AxisLeft
    "Basic icon for mathematical function with y-axis on left side"
  end AxisLeft;

  partial function AxisCenter
    "Basic icon for mathematical function with y-axis in the center"
  end AxisCenter;

end Icons;

function isEqual "Determine if two Real scalars are numerically identical"
  extends Modelica.Icons.Function;
  input Real s1 "First scalar";
  input Real s2 "Second scalar";
  input Real eps(min=0) = 0
    "The two scalars are identical if abs(s1-s2) <= eps";
  output Boolean result "= true, if scalars are identical";
algorithm
  result := abs(s1 - s2) <= eps;
Math.isEqual(s1, s2, eps=0);

Description

The function call \"Math.isEqual(s1, s2)\" returns true,
if the two Real scalars s1 and s2 are identical. Otherwise the function
returns false. The equality check is performed by
\"abs(s1-s2) ≤ eps\", where \"eps\"
can be provided as third argument of the function. Default is \"eps = 0\".

Example

  Real s1 = 2.0;
  Real s2 = 2.0;
  Real s3 = 2.000001;
  Boolean result;
algorithm
  result := Math.isEqual(s1,s2);     // = true
  result := Math.isEqual(s1,s3);     // = false
  result := Math.isEqual(s1,s3,0.1); // = true

See also

Vectors.isEqual,
Matrices.isEqual,
Strings.isEqual

"));
end isEqual;

function sin "Sine"
  extends Modelica.Math.Icons.AxisLeft;
  input Modelica.Units.SI.Angle u "Independent variable";
  output Real y "Dependent variable y=sin(u)";
algorithm
  y := .sin(u);
end sin;

function cos "Cosine"
  extends Modelica.Math.Icons.AxisLeft;
  input Modelica.Units.SI.Angle u "Independent variable";
  output Real y "Dependent variable y=cos(u)";
algorithm
  y := .cos(u);
end cos;

function tan "Tangent (u shall not be -pi/2, pi/2, 3*pi/2, ...)"
  extends Modelica.Math.Icons.AxisCenter;
  input Modelica.Units.SI.Angle u "Independent variable";
  output Real y "Dependent variable y=tan(u)";
algorithm
  y := .tan(u);
end tan;

function asin "Inverse sine (-1 <= u <= 1)"
  extends Modelica.Math.Icons.AxisCenter;
  input Real u "Independent variable";
  output Modelica.Units.SI.Angle y "Dependent variable y=asin(u)";
algorithm
  y := .asin(u);
end asin;

function acos "Inverse cosine (-1 <= u <= 1)"
  extends Modelica.Math.Icons.AxisCenter;
  input Real u "Independent variable";
  output Modelica.Units.SI.Angle y "Dependent variable y=acos(u)";
algorithm
  y := .acos(u);
end acos;

function atan "Inverse tangent"
  extends Modelica.Math.Icons.AxisCenter;
  input Real u "Independent variable";
  output Modelica.Units.SI.Angle y "Dependent variable y=atan(u)";
algorithm
  y := .atan(u);
end atan;

function atan2 "Four quadrant inverse tangent"
  extends Modelica.Math.Icons.AxisCenter;
  input Real u1 "First independent variable";
  input Real u2 "Second independent variable";
  output Modelica.Units.SI.Angle y "Dependent variable y=atan2(u1, u2)=atan(u1/u2)";
algorithm
  y := .atan2(u1, u2);
end atan2;

function atan3
  "Four quadrant inverse tangent (select solution that is closest to given angle y0)"
  import Modelica.Constants.pi;
  extends Modelica.Math.Icons.AxisCenter;
  input Real u1 "First independent variable";
  input Real u2 "Second independent variable";
  input Modelica.Units.SI.Angle y0=0 "y shall be in the range: -pi < y-y0 <= pi";
  output Modelica.Units.SI.Angle y "Dependent variable y=atan3(u1, u2, y0)=atan(u1/u2)";

protected
  constant Real pi2=2*pi;
  Real w;
algorithm
  w := Math.atan2(u1, u2);
  if y0 == 0 then
    // For the default (y0 = 0), exactly the same result as with atan2(..) is returned
    y := w;
  else
    /* -pi < y - y0 <= pi
       -pi < w + 2*pi*N - y0 <= pi
       (-pi+y0-w)/(2*pi) < N <= (pi+y0-w)/(2*pi)
       -> N := integer( (pi+y0-w)/(2*pi) )
    */
    y := w + pi2*integer((pi+y0-w)/pi2);
  end if;
end atan3;

function sinh "Hyperbolic sine"
  extends Modelica.Math.Icons.AxisCenter;
  input Real u "Independent variable";
  output Real y "Dependent variable y=sinh(u)";
algorithm
  y := .sinh(u);
end sinh;

function cosh "Hyperbolic cosine"
  extends Modelica.Math.Icons.AxisCenter;
  input Real u "Independent variable";
  output Real y "Dependent variable y=cosh(u)";
algorithm
  y := .cosh(u);
end cosh;

function tanh "Hyperbolic tangent"
  extends Modelica.Math.Icons.AxisCenter;
  input Real u "Independent variable";
  output Real y "Dependent variable y=tanh(u)";
algorithm
  y := .tanh(u);
end tanh;

function asinh "Inverse of sinh (area hyperbolic sine)"
  extends Modelica.Math.Icons.AxisCenter;
  input Real u "Independent variable";
  output Real y "Dependent variable y=asinh(u)";

algorithm
  y := Modelica.Math.log(u + sqrt(u*u + 1));
end asinh;

function acosh "Inverse of cosh (area hyperbolic cosine)"
  extends Modelica.Math.Icons.AxisLeft;
  input Real u "Independent variable";
  output Real y "Dependent variable y=acosh(u)";

algorithm
  assert(u >= 1.0, "Input argument u (= " + String(u) +
    ") of acosh(u) must be >= 1.0");
  y := Modelica.Math.log(u + sqrt(u*u - 1));
end acosh;

function exp "Exponential, base e"
  extends Modelica.Math.Icons.AxisCenter;
  input Real u "Independent variable";
  output Real y "Dependent variable y=exp(u)";
algorithm
  y := .exp(u);
end exp;

function log "Natural (base e) logarithm (u shall be > 0)"
  extends Modelica.Math.Icons.AxisLeft;
  input Real u "Independent variable";
  output Real y "Dependent variable y=ln(u)";
algorithm
  y := .log(u);
end log;

function log10 "Base 10 logarithm (u shall be > 0)"
  extends Modelica.Math.Icons.AxisLeft;
  input Real u "Independent variable";
  output Real y "Dependent variable y=lg(u)";
algorithm
  y := .log10(u);
end log10;
end Math;