RiccatiEquationSolverImpl.java

  1. /*
  2.  * Licensed to the Hipparchus project under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The Hipparchus project licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.hipparchus.linear;

  18. import org.hipparchus.complex.Complex;
  19. import org.hipparchus.exception.LocalizedCoreFormats;
  20. import org.hipparchus.exception.MathIllegalArgumentException;
  21. import org.hipparchus.exception.MathRuntimeException;
  22. import org.hipparchus.util.FastMath;

  23. /**
  24.  *
  25.  * This solver computes the solution using the following approach:
  26.  *
  27.  * 1. Compute the Hamiltonian matrix 2. Extract its complex eigen vectors (not
  28.  * the best solution, a better solution would be ordered Schur transformation)
  29.  * 3. Approximate the initial solution given by 2 using the Kleinman algorithm
  30.  * (an iterative method)
  31.  */
  32. public class RiccatiEquationSolverImpl implements RiccatiEquationSolver {

  33.     /** Internally used maximum iterations. */
  34.     private static final int MAX_ITERATIONS = 100;

  35.     /** Internally used epsilon criteria. */
  36.     private static final double EPSILON = 1e-8;

  37.     /** The solution of the algebraic Riccati equation. */
  38.     private final RealMatrix P;

  39.     /** The computed K. */
  40.     private final RealMatrix K;

  41.     /**
  42.      * Constructor of the solver. A and B should be compatible. B and R must be
  43.      * multiplicative compatible. A and Q must be multiplicative compatible. R
  44.      * must be invertible.
  45.      *
  46.      * @param A state transition matrix
  47.      * @param B control multipliers matrix
  48.      * @param Q state cost matrix
  49.      * @param R control cost matrix
  50.      */
  51.     public RiccatiEquationSolverImpl(final RealMatrix A, final RealMatrix B,
  52.                                      final RealMatrix Q, final RealMatrix R) {

  53.         // checking A
  54.         if (!A.isSquare()) {
  55.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NON_SQUARE_MATRIX,
  56.                                                    A.getRowDimension(), A.getColumnDimension());
  57.         }
  58.         if (A.getColumnDimension() != B.getRowDimension()) {
  59.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  60.                                                    A.getRowDimension(), B.getRowDimension());
  61.         }
  62.         MatrixUtils.checkMultiplicationCompatible(B, R);
  63.         MatrixUtils.checkMultiplicationCompatible(A, Q);

  64.         // checking R
  65.         final SingularValueDecomposition svd = new SingularValueDecomposition(R);
  66.         if (!svd.getSolver().isNonSingular()) {
  67.             throw new MathIllegalArgumentException(LocalizedCoreFormats.SINGULAR_MATRIX);
  68.         }

  69.         final RealMatrix R_inv = svd.getSolver().getInverse();

  70.         P = computeP(A, B, Q, R, R_inv, MAX_ITERATIONS, EPSILON);

  71.         K = R_inv.multiplyTransposed(B).multiply(P);
  72.     }

  73.     /**
  74.      * Compute an initial stable solution and then applies the Kleinman
  75.      * algorithm to approximate it using an EPSILON.
  76.      *
  77.      * @param A state transition matrix
  78.      * @param B control multipliers matrix
  79.      * @param Q state cost matrix
  80.      * @param R control cost matrix
  81.      * @param R_inv inverse of matrix R
  82.      * @param maxIterations maximum number of iterations
  83.      * @param epsilon epsilon to be used
  84.      * @return matrix P, solution of the algebraic Riccati equation
  85.      */
  86.     private RealMatrix computeP(final RealMatrix A, final RealMatrix B,
  87.                                 final RealMatrix Q, final RealMatrix R, final RealMatrix R_inv,
  88.                                 final int maxIterations, final double epsilon) {
  89.         final RealMatrix P_ = computeInitialP(A, B, Q, R_inv);
  90.         return approximateP(A, B, Q, R, R_inv, P_, maxIterations, epsilon);
  91.     }

  92.     /**
  93.      * Compute initial P using the Hamiltonian and the ordered eigen values
  94.      * decomposition.
  95.      *
  96.      * @param A state transition matrix
  97.      * @param B control multipliers matrix
  98.      * @param Q state cost matrix
  99.      * @param R_inv inverse of matrix R
  100.      * @return initial solution
  101.      */
  102.     private RealMatrix computeInitialP(final RealMatrix A, final RealMatrix B,
  103.                                        final RealMatrix Q, final RealMatrix R_inv) {
  104.         final RealMatrix B_tran = B.transpose();

  105.         // computing the Hamiltonian Matrix
  106.         final RealMatrix m11 = A;
  107.         final RealMatrix m12 = B.multiply(R_inv).multiply(B_tran).scalarMultiply(-1).scalarAdd(0);
  108.         final RealMatrix m21 = Q.scalarMultiply(-1).scalarAdd(0);
  109.         final RealMatrix m22 = A.transpose().scalarMultiply(-1).scalarAdd(0);
  110.         if (m11.getRowDimension() != m12.getRowDimension()) {
  111.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  112.                                                    m11.getRowDimension(), m12.getRowDimension());
  113.         }
  114.         if (m21.getRowDimension() != m22.getRowDimension()) {
  115.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  116.                                                    m21.getRowDimension(), m22.getRowDimension());
  117.         }
  118.         if (m11.getColumnDimension() != m21.getColumnDimension()) {
  119.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  120.                                                    m11.getColumnDimension(), m21.getColumnDimension());
  121.         }
  122.         if (m21.getColumnDimension() != m22.getColumnDimension()) {
  123.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  124.                                                    m21.getColumnDimension(), m22.getColumnDimension());
  125.         }

  126.         // defining M
  127.         final RealMatrix m = MatrixUtils.createRealMatrix(m11.getRowDimension() + m21.getRowDimension(),
  128.                                                           m11.getColumnDimension() + m12.getColumnDimension());
  129.         // defining submatrixes
  130.         m.setSubMatrix(m11.getData(), 0, 0);
  131.         m.setSubMatrix(m12.getData(), 0, m11.getColumnDimension());
  132.         m.setSubMatrix(m21.getData(), m11.getRowDimension(), 0);
  133.         m.setSubMatrix(m22.getData(), m11.getRowDimension(), m11.getColumnDimension());

  134.         // eigen decomposition
  135.         // numerically bad, but it is used for the initial stable solution for
  136.         // the
  137.         // Kleinman Algorithm
  138.         // it must be ordered in order to work with submatrices
  139.         final OrderedComplexEigenDecomposition eigenDecomposition =
  140.                         new OrderedComplexEigenDecomposition(m,
  141.                                                              EPSILON,
  142.                                                              ComplexEigenDecomposition.DEFAULT_EPSILON,
  143.                                                              ComplexEigenDecomposition.DEFAULT_EPSILON_AV_VD_CHECK);
  144.         final FieldMatrix<Complex> u = eigenDecomposition.getV();

  145.         // solving linear system
  146.         // P = U_21*U_11^-1
  147.         final FieldMatrix<Complex> u11 = u.getSubMatrix(0,
  148.                                                         m11.getRowDimension() - 1, 0, m11.getColumnDimension() - 1);
  149.         final FieldMatrix<Complex> u21 = u.getSubMatrix(m11.getRowDimension(),
  150.                                                         2 * m11.getRowDimension() - 1, 0, m11.getColumnDimension() - 1);

  151.         final FieldDecompositionSolver<Complex> solver = new FieldLUDecomposition<>(u11).getSolver();

  152.         if (!solver.isNonSingular()) {
  153.             throw new MathRuntimeException(LocalizedCoreFormats.SINGULAR_MATRIX);
  154.         }

  155.         // solving U_11^{-1}
  156.         FieldMatrix<Complex> u11_inv = solver.getInverse();

  157.         // P = U_21*U_11^-1
  158.         FieldMatrix<Complex> p = u21.multiply(u11_inv);

  159.         // converting to realmatrix - ignoring precision errors in imaginary
  160.         // components
  161.         return convertToRealMatrix(p, Double.MAX_VALUE);

  162.     }

  163.     /**
  164.      * Applies the Kleinman's algorithm.
  165.      *
  166.      * @param A state transition matrix
  167.      * @param B control multipliers matrix
  168.      * @param Q state cost matrix
  169.      * @param R control cost matrix
  170.      * @param R_inv inverse of matrix R
  171.      * @param initialP initial solution
  172.      * @param maxIterations maximum number of iterations allowed
  173.      * @param epsilon convergence threshold
  174.      * @return improved solution
  175.      */
  176.     private RealMatrix approximateP(final RealMatrix A, final RealMatrix B,
  177.                                     final RealMatrix Q, final RealMatrix R, final RealMatrix R_inv,
  178.                                     final RealMatrix initialP, final int maxIterations, final double epsilon) {
  179.         RealMatrix P_ = initialP;

  180.         double error = 1;
  181.         int i = 1;
  182.         while (error > epsilon) {
  183.             final RealMatrix K_ = P_.multiply(B).multiply(R_inv).scalarMultiply(-1);

  184.             // X = AA+BB*K1';
  185.             final RealMatrix X = A.add(B.multiplyTransposed(K_));
  186.             // Y = -K1*RR*K1' - QQ;
  187.             final RealMatrix Y = K_.multiply(R).multiplyTransposed(K_).scalarMultiply(-1).subtract(Q);

  188.             final Array2DRowRealMatrix X_ = (Array2DRowRealMatrix) X.transpose();
  189.             final Array2DRowRealMatrix Y_ = (Array2DRowRealMatrix) Y;
  190.             final Array2DRowRealMatrix eyeX =
  191.                             (Array2DRowRealMatrix) MatrixUtils.createRealIdentityMatrix(X_.getRowDimension());

  192.             // X1=kron(X',eye(size(X))) + kron(eye(size(X)),X');
  193.             final RealMatrix X__ = X_.kroneckerProduct(eyeX).add(eyeX.kroneckerProduct(X_));
  194.             // Y1=reshape(Y,prod(size(Y)),1); %%stack
  195.             final RealMatrix Y__ = Y_.stack();

  196.             // PX = inv(X1)*Y1;
  197.             // sensitive to numerical erros
  198.             // final RealMatrix PX = MatrixUtils.inverse(X__).multiply(Y__);
  199.             DecompositionSolver solver = new LUDecomposition(X__).getSolver();
  200.             if (!solver.isNonSingular()) {
  201.                 throw new MathRuntimeException(LocalizedCoreFormats.SINGULAR_MATRIX);
  202.             }
  203.             final RealMatrix PX = solver.solve(Y__);

  204.             // P = reshape(PX,sqrt(length(PX)),sqrt(length(PX))); %%unstack
  205.             final RealMatrix P__ = ((Array2DRowRealMatrix) PX).unstackSquare();

  206.             // aerror = norm(P - P1);
  207.             final RealMatrix diff = P__.subtract(P_);
  208.             // calculationg l2 norm
  209.             final SingularValueDecomposition svd = new SingularValueDecomposition(diff);
  210.             error = svd.getNorm();

  211.             P_ = P__;
  212.             i++;
  213.             if (i > maxIterations) {
  214.                 throw new MathRuntimeException(LocalizedCoreFormats.CONVERGENCE_FAILED);
  215.             }
  216.         }

  217.         return P_;
  218.     }

  219.     /** {inheritDoc} */
  220.     @Override
  221.     public RealMatrix getP() {
  222.         return P;
  223.     }

  224.     /** {inheritDoc} */
  225.     @Override
  226.     public RealMatrix getK() {
  227.         return K;
  228.     }

  229.     /**
  230.      * Converts a given complex matrix into a real matrix taking into account a
  231.      * precision for the imaginary components.
  232.      *
  233.      * @param matrix complex field matrix
  234.      * @param tolerance tolerance on the imaginary part
  235.      * @return real matrix.
  236.      */
  237.     private RealMatrix convertToRealMatrix(FieldMatrix<Complex> matrix, double tolerance) {
  238.         final RealMatrix toRet = MatrixUtils.createRealMatrix(matrix.getRowDimension(), matrix.getRowDimension());
  239.         for (int i = 0; i < toRet.getRowDimension(); i++) {
  240.             for (int j = 0; j < toRet.getColumnDimension(); j++) {
  241.                 Complex c = matrix.getEntry(i, j);
  242.                 if (c.getImaginary() != 0 && FastMath.abs(c.getImaginary()) > tolerance) {
  243.                     throw new MathRuntimeException(LocalizedCoreFormats.COMPLEX_CANNOT_BE_CONSIDERED_A_REAL_NUMBER,
  244.                                                    c.getReal(), c.getImaginary());
  245.                 }
  246.                 toRet.setEntry(i, j, c.getReal());
  247.             }
  248.         }
  249.         return toRet;
  250.     }

  251. }