diff --git a/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/ChebyshevSecondKindRuleFactory.java b/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/ChebyshevSecondKindRuleFactory.java new file mode 100644 index 000000000..23dff1c06 --- /dev/null +++ b/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/ChebyshevSecondKindRuleFactory.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Hipparchus project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The Hipparchus project licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hipparchus.analysis.integration.gauss; + +import org.hipparchus.util.FastMath; +import org.hipparchus.util.Pair; + +/** + * Factory that creates Gauss-Chebyshev quadrature rules of the second kind. + * + *

The generated rules approximate integrals of the form:

+ * + *
+ *     integral from -1 to 1 of f(x) sqrt(1 - x^2) dx
+ * 
+ * + *

using:

+ * + *
+ *     sum from i = 1 to n of w_i f(x_i)
+ * 
+ * + *

where:

+ * + *
+ *     x_i = cos(i pi / (n + 1)), i = 1, ..., n
+ *     w_i = pi / (n + 1) sin^2(i pi / (n + 1))
+ * 
+ */ +public class ChebyshevSecondKindRuleFactory extends AbstractRuleFactory { + + /** + * Computes the Gauss-Chebyshev quadrature rule of the second kind. + * + * @param numberOfPoints order of the rule to be computed + * @return nodes and weights of the quadrature rule + */ + @Override + protected Pair computeRule(final int numberOfPoints) { + final double[] points = new double[numberOfPoints]; + final double[] weights = new double[numberOfPoints]; + + final double scale = FastMath.PI / (numberOfPoints + 1.0); + + for (int i = 0; i < numberOfPoints; i++) { + final double angle = (i + 1.0) * scale; + final double sin = FastMath.sin(angle); + final int index = numberOfPoints - 1 - i; + + points[index] = FastMath.cos(angle); + weights[index] = scale * sin * sin; + } + + return new Pair<>(points, weights); + } +} diff --git a/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/FieldChebyshevSecondKindRuleFactory.java b/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/FieldChebyshevSecondKindRuleFactory.java new file mode 100644 index 000000000..7046ed7e3 --- /dev/null +++ b/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/FieldChebyshevSecondKindRuleFactory.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Hipparchus project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The Hipparchus project licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hipparchus.analysis.integration.gauss; + +import org.hipparchus.CalculusFieldElement; +import org.hipparchus.Field; +import org.hipparchus.util.FastMath; +import org.hipparchus.util.MathArrays; +import org.hipparchus.util.Pair; + +/** + * Factory that creates field-based Gauss-Chebyshev quadrature rules of the + * second kind. + * + *

The generated rules approximate integrals of the form:

+ * + *
+ *     integral from -1 to 1 of f(x) sqrt(1 - x^2) dx
+ * 
+ * + *

using:

+ * + *
+ *     sum from i = 1 to n of w_i f(x_i)
+ * 
+ * + *

where:

+ * + *
+ *     x_i = cos(i pi / (n + 1)), i = 1, ..., n
+ *     w_i = pi / (n + 1) sin^2(i pi / (n + 1))
+ * 
+ * + * @param type of the field elements + */ +public class FieldChebyshevSecondKindRuleFactory> + extends FieldAbstractRuleFactory { + + /** + * Constructor. + * + * @param field field to which rule coefficients belong + */ + public FieldChebyshevSecondKindRuleFactory(final Field field) { + super(field); + } + + /** + * Computes the Gauss-Chebyshev quadrature rule of the second kind. + * + * @param numberOfPoints order of the rule to be computed + * @return nodes and weights of the quadrature rule + */ + @Override + protected Pair computeRule(final int numberOfPoints) { + final T[] points = MathArrays.buildArray(getField(), numberOfPoints); + final T[] weights = MathArrays.buildArray(getField(), numberOfPoints); + + final T scale = getField().getZero().newInstance(FastMath.PI / (numberOfPoints + 1.0)); + + for (int i = 0; i < numberOfPoints; i++) { + final T angle = scale.multiply(i + 1.0); + final T sin = FastMath.sin(angle); + final int index = numberOfPoints - 1 - i; + + points[index] = FastMath.cos(angle); + weights[index] = scale.multiply(sin.square()); + } + + return new Pair<>(points, weights); + } +} diff --git a/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/FieldGaussIntegratorFactory.java b/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/FieldGaussIntegratorFactory.java index 9f4d83a57..7fcf021de 100644 --- a/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/FieldGaussIntegratorFactory.java +++ b/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/FieldGaussIntegratorFactory.java @@ -40,6 +40,8 @@ public class FieldGaussIntegratorFactory> { private final FieldRuleFactory hermite; /** Generator of Gauss-Laguerre integrators. */ private final FieldRuleFactory laguerre; + /** Generator of Gauss-Chebyshev integrators. */ + private final FieldRuleFactory chebyshevSecondKind; /** Simple constructor. * @param field field to which function argument and value belong @@ -48,6 +50,7 @@ public FieldGaussIntegratorFactory(final Field field) { legendre = new FieldLegendreRuleFactory<>(field); hermite = new FieldHermiteRuleFactory<>(field); laguerre = new FieldLaguerreRuleFactory<>(field); + chebyshevSecondKind = new FieldChebyshevSecondKindRuleFactory<>(field); } /** @@ -120,6 +123,20 @@ public SymmetricFieldGaussIntegrator hermite(int numberOfPoints) { return new SymmetricFieldGaussIntegrator<>(hermite.getRule(numberOfPoints)); } + /** + * Creates a Gauss-Chebyshev integrator of the second kind of the given order. + * The call to the + * {@link FieldGaussIntegrator#integrate(org.hipparchus.analysis.CalculusFieldUnivariateFunction) + * integrate} method will perform an integration on the natural interval + * {@code [-1 , 1]}. + * + * @param numberOfPoints Order of the integration rule. + * @return a Gauss-Chebyshev integrator. + */ + public FieldGaussIntegrator chebyshevSecondKind(int numberOfPoints) { + return new FieldGaussIntegrator<>(chebyshevSecondKind.getRule(numberOfPoints)); + } + /** * Performs a change of variable so that the integration can be performed * on an arbitrary interval {@code [a, b]}. diff --git a/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/GaussIntegratorFactory.java b/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/GaussIntegratorFactory.java index 4a8fa29b1..1a1008afd 100644 --- a/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/GaussIntegratorFactory.java +++ b/hipparchus-core/src/main/java/org/hipparchus/analysis/integration/gauss/GaussIntegratorFactory.java @@ -42,6 +42,8 @@ public class GaussIntegratorFactory { private final RuleFactory hermite; /** Generator of Gauss-Laguerre integrators. */ private final RuleFactory laguerre; + /** Generator of Gauss-Chebyshev integrators. */ + private final RuleFactory chebyshevSecondKind; /** Simple constructor. */ @@ -57,6 +59,7 @@ public GaussIntegratorFactory(final int decimalDigits) { legendreHighPrecision = new ConvertingRuleFactory<>(new FieldLegendreRuleFactory<>(new DfpField(decimalDigits))); hermite = new HermiteRuleFactory(); laguerre = new LaguerreRuleFactory(); + chebyshevSecondKind = new ChebyshevSecondKindRuleFactory(); } /** @@ -146,6 +149,21 @@ public GaussIntegrator legendreHighPrecision(int numberOfPoints, lowerBound, upperBound)); } + /** + * Creates a Gauss-Chebyshev integrator of the second kind of the given order. + * The call to the + * {@link GaussIntegrator#integrate(org.hipparchus.analysis.UnivariateFunction) + * integrate} method will perform an integration on the natural interval + * {@code [-1 , 1]}. + * + * @param numberOfPoints Order of the integration rule. + * @return a GaussChebyshev integrator. + */ + public GaussIntegrator chebyshevSecondKind(int numberOfPoints) + throws MathIllegalArgumentException { + return new GaussIntegrator(chebyshevSecondKind.getRule(numberOfPoints)); + } + /** * Creates a Gauss-Hermite integrator of the given order. * The call to the diff --git a/hipparchus-core/src/test/java/org/hipparchus/analysis/integration/gauss/ChebyshevSecondKindTest.java b/hipparchus-core/src/test/java/org/hipparchus/analysis/integration/gauss/ChebyshevSecondKindTest.java new file mode 100644 index 000000000..3328ab38f --- /dev/null +++ b/hipparchus-core/src/test/java/org/hipparchus/analysis/integration/gauss/ChebyshevSecondKindTest.java @@ -0,0 +1,329 @@ +/* + * Licensed to the Hipparchus project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The Hipparchus project licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hipparchus.analysis.integration.gauss; + +import org.hipparchus.special.Erf; +import org.hipparchus.util.FastMath; +import org.hipparchus.util.Pair; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Tests Gauss-Chebyshev quadrature rules of the second kind against reference + * data generated by the {@code GenGCQuad} MATLAB function from NASA's CARA + * Analysis Tools. + * + * @see NASA CARA Analysis Tools + */ +class ChebyshevSecondKindTest { + + private static final double TOLERANCE = 1.0e-15; + + static final double[][] GEN_GC_QUAD_16 = { + {-9.829730996839018e-01, 1.837495178165702e-01, 6.773407894247465e-03}, + {-9.324722294043558e-01, 3.612416661871530e-01, 1.331615550646369e-02}, + {-8.502171357296142e-01, 5.264321628773557e-01, 1.940543741387547e-02}, + {-7.390089172206590e-01, 6.736956436465573e-01, 2.483389042441458e-02}, + {-6.026346363792563e-01, 7.980172272802396e-01, 2.941665508151885e-02}, + {-4.457383557765382e-01, 8.951632913550623e-01, 3.299767083121100e-02}, + {-2.736629900720829e-01, 9.618256431728190e-01, 3.545499047709023e-02}, + {-9.226835946330189e-02, 9.957341762950346e-01, 3.670493294584622e-02}, + {9.226835946330202e-02, 9.957341762950345e-01, 3.670493294584622e-02}, + {2.736629900720828e-01, 9.618256431728190e-01, 3.545499047709023e-02}, + {4.457383557765383e-01, 8.951632913550623e-01, 3.299767083121100e-02}, + {6.026346363792564e-01, 7.980172272802395e-01, 2.941665508151885e-02}, + {7.390089172206591e-01, 6.736956436465573e-01, 2.483389042441458e-02}, + {8.502171357296142e-01, 5.264321628773557e-01, 1.940543741387547e-02}, + {9.324722294043558e-01, 3.612416661871530e-01, 1.331615550646369e-02}, + {9.829730996839018e-01, 1.837495178165702e-01, 6.773407894247465e-03} + }; + + static final double[][] GEN_GC_QUAD_64 = { + {-9.988322268323265e-01, 4.831337952550822e-02, 4.657833967754515e-04}, + {-9.953316347176486e-01, 9.651392091451513e-02, 9.304789348454766e-04}, + {-9.895063994510511e-01, 1.444890495692213e-01, 1.393001296249116e-03}, + {-9.813701261394134e-01, 1.921267173537084e-01, 1.852270238580170e-03}, + {-9.709418174260519e-01, 2.393156642875583e-01, 2.307213117943439e-03}, + {-9.582458291091662e-01, 2.859456783986892e-01, 2.756767394164220e-03}, + {-9.433118132577431e-01, 3.319078531285286e-01, 3.199883112400166e-03}, + {-9.261746489577764e-01, 3.770948416883209e-01, 3.635525355359408e-03}, + {-9.068743608505453e-01, 4.214011077725294e-01, 4.062676660397876e-03}, + {-8.854560256532098e-01, 4.647231720437687e-01, 4.480339395850453e-03}, + {-8.619696668800491e-01, 5.069598538135907e-01, 4.887538091045943e-03}, + {-8.364701380102265e-01, 5.480125073546703e-01, 5.283321714564022e-03}, + {-8.090169943749473e-01, 5.877852522924733e-01, 5.666765895413191e-03}, + {-7.796743540632223e-01, 6.261851975383138e-01, 6.036975081942061e-03}, + {-7.485107481711009e-01, 6.631226582407955e-01, 6.393084633441722e-03}, + {-7.155989607441211e-01, 6.985113652489370e-01, 6.734262839554183e-03}, + {-6.810158587867969e-01, 7.322686665977737e-01, 7.059712862770465e-03}, + {-6.448422127361704e-01, 7.643157205458485e-01, 7.368674599481501e-03}, + {-6.071625078187112e-01, 7.945776797137543e-01, 7.660426455235353e-03}, + {-5.680647467311557e-01, 8.229838658936565e-01, 7.934287030054488e-03}, + {-5.276402441061325e-01, 8.494679351215212e-01, 8.189616709876989e-03}, + {-4.859834132426061e-01, 8.739680326265179e-01, 8.425819160404840e-03}, + {-4.431915455992412e-01, 8.964269372957038e-01, 8.642342719870315e-03}, + {-3.993645835656953e-01, 9.167921953165825e-01, 8.838681687467587e-03}, + {-3.546048870425355e-01, 9.350162426854148e-01, 9.014377504440393e-03}, + {-3.090169943749473e-01, 9.510565162951535e-01, 9.169019825067272e-03}, + {-2.627073781985868e-01, 9.648755533435515e-01, 9.302247475042994e-03}, + {-2.157841967678060e-01, 9.764410788292721e-01, 9.413749295017889e-03}, + {-1.683570413470385e-01, 9.857260809316509e-01, 9.503264867324932e-03}, + {-1.205366802553229e-01, 9.927088740980540e-01, 9.570585124197263e-03}, + {-7.243480016176228e-02, 9.973731496914912e-01, 9.615552836055650e-03}, + {-2.416374523613207e-02, 9.997080140801929e-01, 9.638062978725454e-03}, + {2.416374523613242e-02, 9.997080140801929e-01, 9.638062978725454e-03}, + {7.243480016176240e-02, 9.973731496914912e-01, 9.615552836055650e-03}, + {1.205366802553232e-01, 9.927088740980540e-01, 9.570585124197263e-03}, + {1.683570413470386e-01, 9.857260809316508e-01, 9.503264867324930e-03}, + {2.157841967678064e-01, 9.764410788292720e-01, 9.413749295017888e-03}, + {2.627073781985870e-01, 9.648755533435515e-01, 9.302247475042994e-03}, + {3.090169943749475e-01, 9.510565162951535e-01, 9.169019825067272e-03}, + {3.546048870425358e-01, 9.350162426854147e-01, 9.014377504440393e-03}, + {3.993645835656957e-01, 9.167921953165823e-01, 8.838681687467584e-03}, + {4.431915455992413e-01, 8.964269372957038e-01, 8.642342719870315e-03}, + {4.859834132426062e-01, 8.739680326265179e-01, 8.425819160404840e-03}, + {5.276402441061328e-01, 8.494679351215211e-01, 8.189616709876989e-03}, + {5.680647467311559e-01, 8.229838658936564e-01, 7.934287030054487e-03}, + {6.071625078187113e-01, 7.945776797137542e-01, 7.660426455235352e-03}, + {6.448422127361706e-01, 7.643157205458483e-01, 7.368674599481499e-03}, + {6.810158587867972e-01, 7.322686665977735e-01, 7.059712862770463e-03}, + {7.155989607441211e-01, 6.985113652489370e-01, 6.734262839554183e-03}, + {7.485107481711012e-01, 6.631226582407951e-01, 6.393084633441719e-03}, + {7.796743540632225e-01, 6.261851975383136e-01, 6.036975081942058e-03}, + {8.090169943749475e-01, 5.877852522924731e-01, 5.666765895413190e-03}, + {8.364701380102267e-01, 5.480125073546700e-01, 5.283321714564020e-03}, + {8.619696668800493e-01, 5.069598538135903e-01, 4.887538091045939e-03}, + {8.854560256532099e-01, 4.647231720437685e-01, 4.480339395850450e-03}, + {9.068743608505454e-01, 4.214011077725293e-01, 4.062676660397874e-03}, + {9.261746489577766e-01, 3.770948416883203e-01, 3.635525355359402e-03}, + {9.433118132577432e-01, 3.319078531285284e-01, 3.199883112400164e-03}, + {9.582458291091662e-01, 2.859456783986892e-01, 2.756767394164220e-03}, + {9.709418174260520e-01, 2.393156642875579e-01, 2.307213117943434e-03}, + {9.813701261394134e-01, 1.921267173537084e-01, 1.852270238580170e-03}, + {9.895063994510511e-01, 1.444890495692213e-01, 1.393001296249116e-03}, + {9.953316347176486e-01, 9.651392091451513e-02, 9.304789348454766e-04}, + {9.988322268323266e-01, 4.831337952550593e-02, 4.657833967754294e-04} + }; + + @Test + void matchesGenGCQuadSixteenPointTableAfterPcCircleWeightConversion() { + assertMatchesGenGCQuadTable(GEN_GC_QUAD_16); + } + + @Test + void matchesGenGCQuadSixtyFourPointTableAfterPcCircleWeightConversion() { + assertMatchesGenGCQuadTable(GEN_GC_QUAD_64); + } + + /** + * Verifies that the generated quadrature rule matches one of the reference + * tables in the {@code GenGCQuad} MATLAB function after converting the + * standard second-kind weights to the table's PcCircle weight convention. + * + * @param genGcQuadTable reference table from {@code GenGCQuad} + */ + private static void assertMatchesGenGCQuadTable(final double[][] genGcQuadTable) { + final int order = genGcQuadTable.length; + + final ChebyshevSecondKindRuleFactory factory = new ChebyshevSecondKindRuleFactory(); + final Pair rule = factory.getRule(order); + + final double[] secondKindPoints = rule.getFirst(); + final double[] secondKindWeights = rule.getSecond(); + + Assertions.assertEquals(order, secondKindPoints.length); + Assertions.assertEquals(order, secondKindWeights.length); + + for (int tableIndex = 0; tableIndex < order; tableIndex++) { + + final double expectedX = genGcQuadTable[tableIndex][0]; + final double expectedY = genGcQuadTable[tableIndex][1]; + final double expectedPcCircleWeight = genGcQuadTable[tableIndex][2]; + + final double x = secondKindPoints[tableIndex]; + final double y = FastMath.sqrt(1.0 - x * x); + + /* + * Standard second-kind weight: + * + * W = pi / (n + 1) * sin^2(theta) + * + * PcCircle-customized table weight: + * + * wGC = pi / (n + 1) * sin(theta) / sqrt(8*pi) + * + * Since y = sin(theta): + * + * wGC = W / y / sqrt(8*pi) + */ + final double pcCircleWeight = secondKindWeights[tableIndex] / y / FastMath.sqrt(8.0 * FastMath.PI); + + Assertions.assertEquals(expectedX, x, TOLERANCE, "xGC mismatch at table index " + tableIndex); + Assertions.assertEquals(expectedY, y, TOLERANCE, "yGC mismatch at table index " + tableIndex); + Assertions.assertEquals(expectedPcCircleWeight, pcCircleWeight, TOLERANCE, + "wGC mismatch at table index " + tableIndex); + } + } + + + @Test + void matchesMatlabPcCircleIntegrandForModerateOffsetWithOrder16() { + assertMatchesMatlabReference( + 0.35, + 0.20, + 1.10, + 0.75, + 0.45, + 16, + 1.06055364621781625e-01 + ); + } + + @Test + void matchesMatlabPcCircleIntegrandForModerateOffsetWithOrder64() { + assertMatchesMatlabReference( + 0.35, + 0.20, + 1.10, + 0.75, + 0.45, + 64, + 1.06055364621781625e-01 + ); + } + + @Test + void matchesMatlabPcCircleIntegrandForNearCenterWithOrder16() { + assertMatchesMatlabReference( + 0.05, + 0.08, + 0.80, + 1.25, + 0.30, + 16, + 4.37336618192281021e-02 + ); + } + + @Test + void matchesMatlabPcCircleIntegrandForNearCenterWithOrder64() { + assertMatchesMatlabReference( + 0.05, + 0.08, + 0.80, + 1.25, + 0.30, + 64, + 4.37336618192281021e-02 + ); + } + + @Test + void matchesMatlabPcCircleIntegrandForLargerRadiusWithOrder16() { + assertMatchesMatlabReference( + 0.90, + 0.40, + 1.50, + 0.65, + 0.70, + 16, + 1.56273946267817959e-01 + ); + } + + @Test + void matchesMatlabPcCircleIntegrandForLargerRadiusWithOrder64() { + assertMatchesMatlabReference( + 0.90, + 0.40, + 1.50, + 0.65, + 0.70, + 64, + 1.56273946267818015e-01 + ); + } + + private static void assertMatchesMatlabReference(final double xm, + final double zm, + final double sx, + final double sz, + final double hbr, + final int order, + final double expected) { + final double actual = integratePcCircleStyle(xm, zm, sx, sz, hbr, order); + + Assertions.assertEquals(expected, actual, TOLERANCE, + "PcCircle-style second-kind quadrature mismatch for order " + order); + } + + /** + * Uses the quadrature rule in the PcCircle convention to test probability of + * collision calculations against MATLAB results for simple reference cases. + * + * @param xm projected miss distance along the x-axis + * @param zm projected miss distance along the z-axis + * @param sx standard deviation along the x-axis + * @param sz standard deviation along the z-axis + * @param hbr hard-body radius + * @param order quadrature rule order + * @return probability of collision estimate + */ + private static double integratePcCircleStyle(final double xm, + final double zm, + final double sx, + final double sz, + final double hbr, + final int order) { + final GaussIntegratorFactory factory = new GaussIntegratorFactory(); + final GaussIntegrator integrator = factory.chebyshevSecondKind(order); + + final double sqrt2 = FastMath.sqrt(2.0); + final double dx = sqrt2 * sx; + final double dz = sqrt2 * sz; + + final double integral = integrator.integrate(u -> { + final double y = FastMath.sqrt(1.0 - u * u); + final double x = xm + hbr * u; + final double hx = hbr * y; + + final double exponential = FastMath.exp(-FastMath.pow(x / dx, 2.0)); + final double errorFunctionDifference = Erf.erf((zm - hx) / dz, (zm + hx) / dz); + + /* + * PcCircle's GenGCQuad convention uses: + * + * wGC = pi / (n + 1) * y / sqrt(8*pi) + * + * while the standard second-kind rule uses: + * + * W = pi / (n + 1) * y^2 + * + * Therefore the integrand passed to GaussIntegrator must include: + * + * 1 / (y * sqrt(8*pi)) + * + * so that: + * + * W * F(u) / (y * sqrt(8*pi)) == wGC * F(u) + */ + return exponential * errorFunctionDifference / (y * FastMath.sqrt(8.0 * FastMath.PI)); + }); + + return hbr / sx * integral; + } +} \ No newline at end of file diff --git a/hipparchus-core/src/test/java/org/hipparchus/analysis/integration/gauss/FieldChebyshevSecondKindTest.java b/hipparchus-core/src/test/java/org/hipparchus/analysis/integration/gauss/FieldChebyshevSecondKindTest.java new file mode 100644 index 000000000..e86c73f87 --- /dev/null +++ b/hipparchus-core/src/test/java/org/hipparchus/analysis/integration/gauss/FieldChebyshevSecondKindTest.java @@ -0,0 +1,255 @@ +/* + * Licensed to the Hipparchus project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The Hipparchus project licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hipparchus.analysis.integration.gauss; + +import org.hipparchus.special.Erf; +import org.hipparchus.util.Binary64; +import org.hipparchus.util.Binary64Field; +import org.hipparchus.util.FastMath; +import org.hipparchus.util.Pair; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Tests field-based Gauss-Chebyshev quadrature rules of the second kind against + * reference data generated by the {@code GenGCQuad} MATLAB function from NASA's + * CARA Analysis Tools. + * + * @see NASA CARA Analysis Tools + */ +class FieldChebyshevSecondKindTest { + + private static final double TOLERANCE = 1.0e-15; + + @Test + void matchesGenGCQuadSixteenPointTableAfterPcCircleWeightConversion() { + assertMatchesGenGCQuadTable(ChebyshevSecondKindTest.GEN_GC_QUAD_16); + } + + @Test + void matchesGenGCQuadSixtyFourPointTableAfterPcCircleWeightConversion() { + assertMatchesGenGCQuadTable(ChebyshevSecondKindTest.GEN_GC_QUAD_64); + } + + /** + * Verifies that the generated quadrature rule matches one of the reference + * tables in the {@code GenGCQuad} MATLAB function after converting the + * standard second-kind weights to the table's PcCircle weight convention. + * + * @param genGcQuadTable reference table from {@code GenGCQuad} + */ + private static void assertMatchesGenGCQuadTable(final double[][] genGcQuadTable) { + final int order = genGcQuadTable.length; + + final FieldChebyshevSecondKindRuleFactory factory = + new FieldChebyshevSecondKindRuleFactory<>(Binary64Field.getInstance()); + final Pair rule = factory.getRule(order); + + final Binary64[] secondKindPoints = rule.getFirst(); + final Binary64[] secondKindWeights = rule.getSecond(); + + Assertions.assertEquals(order, secondKindPoints.length); + Assertions.assertEquals(order, secondKindWeights.length); + + for (int tableIndex = 0; tableIndex < order; tableIndex++) { + final double expectedX = genGcQuadTable[tableIndex][0]; + final double expectedY = genGcQuadTable[tableIndex][1]; + final double expectedPcCircleWeight = genGcQuadTable[tableIndex][2]; + + final Binary64 x = secondKindPoints[tableIndex]; + final Binary64 y = FastMath.sqrt(Binary64.ONE.subtract(x.square())); + + /* + * Standard second-kind weight: + * + * W = pi / (n + 1) * sin^2(theta) + * + * PcCircle-customized table weight: + * + * wGC = pi / (n + 1) * sin(theta) / sqrt(8*pi) + * + * Since y = sin(theta): + * + * wGC = W / y / sqrt(8*pi) + */ + final Binary64 pcCircleWeight = + secondKindWeights[tableIndex].divide(y).divide(FastMath.sqrt(8.0 * FastMath.PI)); + + Assertions.assertEquals(expectedX, x.getReal(), TOLERANCE, "xGC mismatch at table index " + tableIndex); + Assertions.assertEquals(expectedY, y.getReal(), TOLERANCE, "yGC mismatch at table index " + tableIndex); + Assertions.assertEquals(expectedPcCircleWeight, pcCircleWeight.getReal(), TOLERANCE, + "wGC mismatch at table index " + tableIndex); + } + } + + @Test + void matchesMatlabPcCircleIntegrandForModerateOffsetWithOrder16() { + assertMatchesMatlabReference( + 0.35, + 0.20, + 1.10, + 0.75, + 0.45, + 16, + 1.06055364621781625e-01 + ); + } + + @Test + void matchesMatlabPcCircleIntegrandForModerateOffsetWithOrder64() { + assertMatchesMatlabReference( + 0.35, + 0.20, + 1.10, + 0.75, + 0.45, + 64, + 1.06055364621781625e-01 + ); + } + + @Test + void matchesMatlabPcCircleIntegrandForNearCenterWithOrder16() { + assertMatchesMatlabReference( + 0.05, + 0.08, + 0.80, + 1.25, + 0.30, + 16, + 4.37336618192281021e-02 + ); + } + + @Test + void matchesMatlabPcCircleIntegrandForNearCenterWithOrder64() { + assertMatchesMatlabReference( + 0.05, + 0.08, + 0.80, + 1.25, + 0.30, + 64, + 4.37336618192281021e-02 + ); + } + + @Test + void matchesMatlabPcCircleIntegrandForLargerRadiusWithOrder16() { + assertMatchesMatlabReference( + 0.90, + 0.40, + 1.50, + 0.65, + 0.70, + 16, + 1.56273946267817959e-01 + ); + } + + @Test + void matchesMatlabPcCircleIntegrandForLargerRadiusWithOrder64() { + assertMatchesMatlabReference( + 0.90, + 0.40, + 1.50, + 0.65, + 0.70, + 64, + 1.56273946267818015e-01 + ); + } + + private static void assertMatchesMatlabReference(final double xm, + final double zm, + final double sx, + final double sz, + final double hbr, + final int order, + final double expected) { + final Binary64 actual = integratePcCircleStyle( + new Binary64(xm), + new Binary64(zm), + new Binary64(sx), + new Binary64(sz), + new Binary64(hbr), + order + ); + + Assertions.assertEquals(expected, actual.getReal(), TOLERANCE, + "PcCircle-style field second-kind quadrature mismatch for order " + order); + } + + /** + * Uses the quadrature rule in the PcCircle convention to test probability of + * collision calculations against MATLAB results for simple reference cases. + * + * @param xm projected miss distance along the x-axis + * @param zm projected miss distance along the z-axis + * @param sx standard deviation along the x-axis + * @param sz standard deviation along the z-axis + * @param hbr hard-body radius + * @param order quadrature rule order + * @return probability of collision estimate + */ + private static Binary64 integratePcCircleStyle(final Binary64 xm, + final Binary64 zm, + final Binary64 sx, + final Binary64 sz, + final Binary64 hbr, + final int order) { + final FieldGaussIntegratorFactory factory = + new FieldGaussIntegratorFactory<>(Binary64Field.getInstance()); + final FieldGaussIntegrator integrator = factory.chebyshevSecondKind(order); + + final Binary64 sqrt2 = FastMath.sqrt(new Binary64(2.0)); + final Binary64 dx = sx.multiply(sqrt2); + final Binary64 dz = sz.multiply(sqrt2); + + final Binary64 integral = integrator.integrate(u -> { + final Binary64 y = FastMath.sqrt(Binary64.ONE.subtract(u.square())); + final Binary64 x = xm.add(hbr.multiply(u)); + final Binary64 hx = hbr.multiply(y); + + final Binary64 exponential = FastMath.exp(x.divide(dx).square().negate()); + final Binary64 errorFunctionDifference = Erf.erf(zm.subtract(hx).divide(dz), + zm.add(hx).divide(dz)); + + /* + * PcCircle's GenGCQuad convention uses: + * + * wGC = pi / (n + 1) * y / sqrt(8*pi) + * + * while the standard second-kind rule uses: + * + * W = pi / (n + 1) * y^2 + * + * Therefore the integrand passed to FieldGaussIntegrator must include: + * + * 1 / (y * sqrt(8*pi)) + * + * so that: + * + * W * F(u) / (y * sqrt(8*pi)) == wGC * F(u) + */ + return exponential.multiply(errorFunctionDifference) + .divide(y.multiply(FastMath.sqrt(8.0 * FastMath.PI))); + }); + + return hbr.divide(sx).multiply(integral); + } +} \ No newline at end of file