1 /* 2 * Copyright (C) 2023 Alberto Irurueta Carro (alberto@irurueta.com) 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.irurueta.numerical; 17 18 import com.irurueta.algebra.AlgebraException; 19 import com.irurueta.algebra.LUDecomposer; 20 import com.irurueta.algebra.Matrix; 21 22 /** 23 * Estimates the Padé approximant rational function by using a number of coefficients 24 * of a Taylor series. 25 * Padé approximants can yield more accurate solutions than Taylor series in certain situations. 26 */ 27 public class PadeApproximantEstimator { 28 29 /** 30 * Number of times to iteratively improve LU decomposition solution by default. 31 */ 32 private static final int DEFAULT_IMPROVEMENT_TIMES = 4; 33 34 /** 35 * Number of times to iteratively improve LU decomposition. 36 */ 37 private final int improveTimes; 38 39 /** 40 * Computes LU decomposition to find denominator coefficients. 41 */ 42 private final LUDecomposer luDecomposer = new LUDecomposer(); 43 44 /** 45 * Number of coefficients being processed based on provided Taylor power series ones. 46 */ 47 private int n; 48 49 /** 50 * Contains matrix to solve Padé coefficients. 51 */ 52 private Matrix q; 53 54 /** 55 * Contains intermediate solution for denominator coefficients. 56 */ 57 private Matrix x; 58 59 /** 60 * Contains intermediate solution for numerator coefficients. 61 */ 62 private Matrix y; 63 64 /** 65 * Contains product of "a" and "x" matrices to iteratively improve LU solution. 66 */ 67 private Matrix ax; 68 69 /** 70 * Contains residual to iteratively improve LU solution. 71 */ 72 private Matrix residual; 73 74 /** 75 * Improved LU solution in one iteration. 76 */ 77 private Matrix improvedX; 78 79 /** 80 * Default constructor. 81 * Uses default number of times to iteratively improve solution. 82 */ 83 public PadeApproximantEstimator() { 84 this(DEFAULT_IMPROVEMENT_TIMES); 85 } 86 87 /** 88 * Constructor. 89 * @param improveTimes number of times to iteratively improve solution. 90 * @throws IllegalArgumentException if provided number of times is negative. 91 */ 92 public PadeApproximantEstimator(final int improveTimes) { 93 if (improveTimes < 0) { 94 throw new IllegalArgumentException("Times must be zero or greater"); 95 } 96 97 this.improveTimes = improveTimes; 98 } 99 100 /** 101 * Estimates Padé coefficients for provided Taylor power series ones. 102 * 103 * @param taylorCoefficients Taylor series coefficients. 104 * @return Result containing Padé approximant numerator and denominator coefficients. 105 * @throws NumericalException if a numerical error occurs. 106 * @throws IllegalArgumentException if provided number of Taylor series coefficients is less 107 * than 3. 108 */ 109 public Result estimatePadeCoefficients(final double[] taylorCoefficients) throws NumericalException { 110 final var coefN = (taylorCoefficients.length - 1) / 2; 111 final var num = new double[coefN + 1]; 112 final var denom = new double[coefN + 1]; 113 estimatePadeCoefficients(taylorCoefficients, coefN, num, denom); 114 return new Result(num, denom); 115 } 116 117 /** 118 * Estimates Padé coefficients for provided Taylor power series ones. 119 * 120 * @param taylorCoefficients Taylor series coefficients. 121 * @param numeratorResult numerator coefficients of Padé approximant 122 * (must be (taylorCoefficients.length - 1) / 2). 123 * @param denominatorResult denominator coefficients of Padé approximant 124 * (must be (taylorCoefficients.length - 1) / 2).. 125 * @throws NumericalException if a numerical error occurs. 126 * @throws IllegalArgumentException if provided number of Taylor series coefficients is less 127 * than 3 or if provided numerator or denominator result coefficients have an invalid size. 128 */ 129 public void estimatePadeCoefficients( 130 final double[] taylorCoefficients, final double[] numeratorResult, final double[] denominatorResult) 131 throws NumericalException { 132 final var coefN = (taylorCoefficients.length - 1) / 2; 133 estimatePadeCoefficients(taylorCoefficients, coefN, numeratorResult, denominatorResult); 134 } 135 136 /** 137 * Estimates Padé coefficients for provided Taylor power series ones. 138 * 139 * @param taylorCoefficients Taylor series coefficients. 140 * @param n Number of padé coefficients to generate. 141 * @param numeratorResult numerator coefficients of Padé approximant 142 * (must be (taylorCoefficients.length - 1) / 2). 143 * @param denominatorResult denominator coefficients of Padé approximant 144 * (must be (taylorCoefficients.length - 1) / 2).. 145 * @throws NumericalException if a numerical error occurs. 146 * @throws IllegalArgumentException if provided number of Taylor series coefficients is less 147 * than 3 or if provided numerator or denominator result coefficients have an invalid size. 148 */ 149 private void estimatePadeCoefficients( 150 final double[] taylorCoefficients, final int n, final double[] numeratorResult, 151 final double[] denominatorResult) throws NumericalException { 152 if (taylorCoefficients.length < 3) { 153 throw new IllegalArgumentException("Length of Taylor series coefficients must be at least 3"); 154 } 155 156 try { 157 // Based on Numerical Recipes section 5.12 Padé Approximants page 245. 158 final var nPlusOne = n +1; 159 if (numeratorResult.length != nPlusOne || denominatorResult.length != nPlusOne) { 160 throw new IllegalArgumentException("Wrong numerator or denominator array length"); 161 } 162 163 if (this.n != n) { 164 initialize(n); 165 } 166 int j; 167 int k; 168 double sum; 169 170 for (j = 0; j < n; j++) { 171 // set up matrix for solving 172 y.setElementAtIndex(j, taylorCoefficients[n + j + 1]); 173 for (k = 0; k < n; k++) { 174 q.setElementAt(j, k, taylorCoefficients[j - k + n]); 175 } 176 } 177 178 luDecomposer.setInputMatrix(q); 179 luDecomposer.decompose(); 180 luDecomposer.solve(y, x); 181 182 for (j = 0; j < improveTimes; j++) { 183 improveLuSolve(q, y, x, improvedX); 184 x.copyFrom(improvedX); 185 } 186 187 for (k = 0; k < n; k++) { 188 for (sum = taylorCoefficients[k + 1], j = 0; j <= k; j++) { 189 sum -= x.getElementAtIndex(j) * taylorCoefficients[k - j]; 190 } 191 y.setElementAtIndex(k, sum); 192 } 193 numeratorResult[0] = taylorCoefficients[0]; 194 denominatorResult[0] = 1.0; 195 for (j = 0; j < n; j++) { 196 numeratorResult[j + 1] = y.getElementAtIndex(j); 197 denominatorResult[j + 1] = -x.getElementAtIndex(j); 198 } 199 200 } catch (final AlgebraException ex) { 201 throw new NumericalException(ex); 202 } 203 } 204 205 /** 206 * One step to iteratively improve LU solve solution. 207 * 208 * @param a a matrix of a linear system of equations to be solved. 209 * @param b b matrix of a linear system of equations to be solved. 210 * @param x x matrix containing initial solution of linear system of equations to be improved. 211 * @param result matrix where result will be stored. 212 * @throws AlgebraException if a numerical error occurs. 213 */ 214 private void improveLuSolve(final Matrix a, final Matrix b, final Matrix x, final Matrix result) 215 throws AlgebraException { 216 // Based on Numerical Recipes page 62 217 // We need to solve iteratively: A * deltaX = A * (x + deltaX) - b 218 // deltaX is the residual error between initially estimated x and the true x 219 // Hence: 220 // result = x - deltaX 221 // Where result will be closer to the true x 222 223 a.multiply(x, ax); 224 ax.subtract(b, residual); 225 226 luDecomposer.solve(residual, result); 227 228 result.multiplyByScalar(-1.0); 229 result.add(x); 230 } 231 232 /** 233 * Initializes required matrices. 234 * 235 * @param n length of required number of Padé coefficients for provided Taylor series ones. 236 * @throws AlgebraException if a numerical error occurs. 237 */ 238 private void initialize(final int n) throws AlgebraException { 239 q = new Matrix(n, n); 240 x = new Matrix(n, 1); 241 y = new Matrix(n, 1); 242 243 if (improveTimes > 0) { 244 ax = new Matrix(n, 1); 245 residual = new Matrix(n, 1); 246 improvedX = new Matrix(n, 1); 247 } 248 249 this.n = n; 250 } 251 252 /** 253 * Contains result of Padé approximant. 254 */ 255 public static class Result { 256 257 /** 258 * Numerator coefficients. 259 */ 260 private final double[] numerators; 261 262 /** 263 * Denominator coefficients. 264 */ 265 private final double[] denominators; 266 267 /** 268 * Constructor. 269 * 270 * @param numerators numerator coefficients. 271 * @param denominators denominator coefficients. 272 */ 273 public Result(final double[] numerators, final double[] denominators) { 274 this.numerators = numerators; 275 this.denominators = denominators; 276 } 277 278 /** 279 * Gets numerator coefficients. 280 * 281 * @return numerator coefficients. 282 */ 283 public double[] getNumerators() { 284 return numerators; 285 } 286 287 /** 288 * Gets denominator coefficients. 289 * 290 * @return denominator coefficients. 291 */ 292 public double[] getDenominators() { 293 return denominators; 294 } 295 } 296 }