DSSTSolarRadiationPressure.java
- /* Copyright 2002-2023 CS GROUP
- * Licensed to CS GROUP (CS) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * CS 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
- *
- * http://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.orekit.propagation.semianalytical.dsst.forces;
- import java.util.List;
- import org.hipparchus.CalculusFieldElement;
- import org.hipparchus.Field;
- import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
- import org.hipparchus.geometry.euclidean.threed.Vector3D;
- import org.hipparchus.util.FastMath;
- import org.hipparchus.util.MathArrays;
- import org.hipparchus.util.MathUtils;
- import org.hipparchus.util.Precision;
- import org.orekit.bodies.OneAxisEllipsoid;
- import org.orekit.forces.radiation.IsotropicRadiationSingleCoefficient;
- import org.orekit.forces.radiation.RadiationSensitive;
- import org.orekit.forces.radiation.SolarRadiationPressure;
- import org.orekit.propagation.FieldSpacecraftState;
- import org.orekit.propagation.SpacecraftState;
- import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
- import org.orekit.propagation.semianalytical.dsst.utilities.FieldAuxiliaryElements;
- import org.orekit.utils.ExtendedPVCoordinatesProvider;
- import org.orekit.utils.ParameterDriver;
- /** Solar radiation pressure contribution to the
- * {@link org.orekit.propagation.semianalytical.dsst.DSSTPropagator DSSTPropagator}.
- * <p>
- * The solar radiation pressure acceleration is computed through the
- * acceleration model of
- * {@link org.orekit.forces.radiation.SolarRadiationPressure SolarRadiationPressure}.
- * </p>
- *
- * @author Pascal Parraud
- */
- public class DSSTSolarRadiationPressure extends AbstractGaussianContribution {
- /** Reference distance for the solar radiation pressure (m). */
- private static final double D_REF = 149597870000.0;
- /** Reference solar radiation pressure at D_REF (N/m²). */
- private static final double P_REF = 4.56e-6;
- /** Threshold for the choice of the Gauss quadrature order. */
- private static final double GAUSS_THRESHOLD = 1.0e-15;
- /** Threshold for shadow equation. */
- private static final double S_ZERO = 1.0e-6;
- /** Prefix for the coefficient keys. */
- private static final String PREFIX = "DSST-SRP-";
- /** Sun model. */
- private final ExtendedPVCoordinatesProvider sun;
- /** Central Body radius. */
- private final double ae;
- /** Spacecraft model for radiation acceleration computation. */
- private final RadiationSensitive spacecraft;
- /**
- * Simple constructor with default reference values and spherical spacecraft.
- * <p>
- * When this constructor is used, the reference values are:
- * </p>
- * <ul>
- * <li>d<sub>ref</sub> = 149597870000.0 m</li>
- * <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
- * </ul>
- * <p>
- * The spacecraft has a spherical shape.
- * </p>
- *
- * @param cr satellite radiation pressure coefficient (assuming total specular reflection)
- * @param area cross sectional area of satellite
- * @param sun Sun model
- * @param centralBody central body (for shadow computation)
- * @param mu central attraction coefficient
- * @since 12.0
- */
- public DSSTSolarRadiationPressure(final double cr, final double area,
- final ExtendedPVCoordinatesProvider sun,
- final OneAxisEllipsoid centralBody,
- final double mu) {
- this(D_REF, P_REF, cr, area, sun, centralBody, mu);
- }
- /**
- * Simple constructor with default reference values, but custom spacecraft.
- * <p>
- * When this constructor is used, the reference values are:
- * </p>
- * <ul>
- * <li>d<sub>ref</sub> = 149597870000.0 m</li>
- * <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
- * </ul>
- *
- * @param sun Sun model
- * @param centralBody central body (for shadow computation)
- * @param spacecraft spacecraft model
- * @param mu central attraction coefficient
- * @since 12.0
- */
- public DSSTSolarRadiationPressure(final ExtendedPVCoordinatesProvider sun,
- final OneAxisEllipsoid centralBody,
- final RadiationSensitive spacecraft,
- final double mu) {
- this(D_REF, P_REF, sun, centralBody, spacecraft, mu);
- }
- /**
- * Constructor with customizable reference values but spherical spacecraft.
- * <p>
- * Note that reference solar radiation pressure <code>pRef</code> in N/m² is linked
- * to solar flux SF in W/m² using formula pRef = SF/c where c is the speed of light
- * (299792458 m/s). So at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
- * N/m² solar radiation pressure.
- * </p>
- *
- * @param dRef reference distance for the solar radiation pressure (m)
- * @param pRef reference solar radiation pressure at dRef (N/m²)
- * @param cr satellite radiation pressure coefficient (assuming total specular reflection)
- * @param area cross sectional area of satellite
- * @param sun Sun model
- * @param centralBody central body (for shadow computation)
- * @param mu central attraction coefficient
- * @since 12.0
- */
- public DSSTSolarRadiationPressure(final double dRef, final double pRef,
- final double cr, final double area,
- final ExtendedPVCoordinatesProvider sun,
- final OneAxisEllipsoid centralBody,
- final double mu) {
- // cR being the DSST SRP coef and assuming a spherical spacecraft,
- // the conversion is:
- // cR = 1 + (1 - kA) * (1 - kR) * 4 / 9
- // with kA arbitrary sets to 0
- this(dRef, pRef, sun, centralBody, new IsotropicRadiationSingleCoefficient(area, cr), mu);
- }
- /**
- * Complete constructor.
- * <p>
- * Note that reference solar radiation pressure <code>pRef</code> in N/m² is linked
- * to solar flux SF in W/m² using formula pRef = SF/c where c is the speed of light
- * (299792458 m/s). So at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
- * N/m² solar radiation pressure.
- * </p>
- *
- * @param dRef reference distance for the solar radiation pressure (m)
- * @param pRef reference solar radiation pressure at dRef (N/m²)
- * @param sun Sun model
- * @param centralBody central body shape model (for umbra/penumbra computation)
- * @param spacecraft spacecraft model
- * @param mu central attraction coefficient
- * @since 12.0
- */
- public DSSTSolarRadiationPressure(final double dRef, final double pRef,
- final ExtendedPVCoordinatesProvider sun,
- final OneAxisEllipsoid centralBody,
- final RadiationSensitive spacecraft,
- final double mu) {
- //Call to the constructor from superclass using the numerical SRP model as ForceModel
- super(PREFIX, GAUSS_THRESHOLD,
- new SolarRadiationPressure(dRef, pRef, sun, centralBody, spacecraft), mu);
- this.sun = sun;
- this.ae = centralBody.getEquatorialRadius();
- this.spacecraft = spacecraft;
- }
- /** Get spacecraft shape.
- * @return the spacecraft shape.
- */
- public RadiationSensitive getSpacecraft() {
- return spacecraft;
- }
- /** {@inheritDoc} */
- protected List<ParameterDriver> getParametersDriversWithoutMu() {
- return spacecraft.getRadiationParametersDrivers();
- }
- /** {@inheritDoc} */
- protected double[] getLLimits(final SpacecraftState state, final AuxiliaryElements auxiliaryElements) {
- // Default bounds without shadow [-PI, PI]
- final double[] ll = {-FastMath.PI + MathUtils.normalizeAngle(state.getLv(), 0),
- FastMath.PI + MathUtils.normalizeAngle(state.getLv(), 0)};
- // Direction cosines of the Sun in the equinoctial frame
- final Vector3D sunDir = sun.getPosition(state.getDate(), state.getFrame()).normalize();
- final double alpha = sunDir.dotProduct(auxiliaryElements.getVectorF());
- final double beta = sunDir.dotProduct(auxiliaryElements.getVectorG());
- final double gamma = sunDir.dotProduct(auxiliaryElements.getVectorW());
- // Compute limits only if the perigee is close enough from the central body to be in the shadow
- if (FastMath.abs(gamma * auxiliaryElements.getSma() * (1. - auxiliaryElements.getEcc())) < ae) {
- // Compute the coefficients of the quartic equation in cos(L) 3.5-(2)
- final double bet2 = beta * beta;
- final double h2 = auxiliaryElements.getH() * auxiliaryElements.getH();
- final double k2 = auxiliaryElements.getK() * auxiliaryElements.getK();
- final double m = ae / (auxiliaryElements.getSma() * auxiliaryElements.getB());
- final double m2 = m * m;
- final double m4 = m2 * m2;
- final double bb = alpha * beta + m2 * auxiliaryElements.getH() * auxiliaryElements.getK();
- final double b2 = bb * bb;
- final double cc = alpha * alpha - bet2 + m2 * (k2 - h2);
- final double dd = 1. - bet2 - m2 * (1. + h2);
- final double[] a = new double[5];
- a[0] = 4. * b2 + cc * cc;
- a[1] = 8. * bb * m2 * auxiliaryElements.getH() + 4. * cc * m2 * auxiliaryElements.getK();
- a[2] = -4. * b2 + 4. * m4 * h2 - 2. * cc * dd + 4. * m4 * k2;
- a[3] = -8. * bb * m2 * auxiliaryElements.getH() - 4. * dd * m2 * auxiliaryElements.getK();
- a[4] = -4. * m4 * h2 + dd * dd;
- // Compute the real roots of the quartic equation 3.5-2
- final double[] roots = new double[4];
- final int nbRoots = realQuarticRoots(a, roots);
- if (nbRoots > 0) {
- // Check for consistency
- boolean entryFound = false;
- boolean exitFound = false;
- // Eliminate spurious roots
- for (int i = 0; i < nbRoots; i++) {
- final double cosL = roots[i];
- final double sL = FastMath.sqrt((1. - cosL) * (1. + cosL));
- // Check both angles: L and -L
- for (int j = -1; j <= 1; j += 2) {
- final double sinL = j * sL;
- final double cPhi = alpha * cosL + beta * sinL;
- // Is the angle on the shadow side of the central body (eq. 3.5-3) ?
- if (cPhi < 0.) {
- final double range = 1. + auxiliaryElements.getK() * cosL + auxiliaryElements.getH() * sinL;
- final double S = 1. - m2 * range * range - cPhi * cPhi;
- // Is the shadow equation 3.5-1 satisfied ?
- if (FastMath.abs(S) < S_ZERO) {
- // Is this the entry or exit angle ?
- final double dSdL = m2 * range * (auxiliaryElements.getK() * sinL - auxiliaryElements.getH() * cosL) + cPhi * (alpha * sinL - beta * cosL);
- if (dSdL > 0.) {
- // Exit from shadow: 3.5-4
- exitFound = true;
- ll[0] = FastMath.atan2(sinL, cosL);
- } else {
- // Entry into shadow: 3.5-5
- entryFound = true;
- ll[1] = FastMath.atan2(sinL, cosL);
- }
- }
- }
- }
- }
- // Must be one entry and one exit or none
- if (!(entryFound == exitFound)) {
- // entry or exit found but not both ! In this case, consider there is no eclipse...
- ll[0] = -FastMath.PI;
- ll[1] = FastMath.PI;
- }
- // Quadrature between L at exit and L at entry so Lexit must be lower than Lentry
- if (ll[0] > ll[1]) {
- // Keep the angles between [-2PI, 2PI]
- if (ll[1] < 0.) {
- ll[1] += 2. * FastMath.PI;
- } else {
- ll[0] -= 2. * FastMath.PI;
- }
- }
- }
- }
- return ll;
- }
- /** {@inheritDoc} */
- protected <T extends CalculusFieldElement<T>> T[] getLLimits(final FieldSpacecraftState<T> state,
- final FieldAuxiliaryElements<T> auxiliaryElements) {
- final Field<T> field = state.getDate().getField();
- final T zero = field.getZero();
- final T one = field.getOne();
- final T pi = one.getPi();
- // Default bounds without shadow [-PI, PI]
- final T[] ll = MathArrays.buildArray(field, 2);
- ll[0] = MathUtils.normalizeAngle(state.getLv(), zero).subtract(pi);
- ll[1] = MathUtils.normalizeAngle(state.getLv(), zero).add(pi);
- // Direction cosines of the Sun in the equinoctial frame
- final FieldVector3D<T> sunDir = sun.getPosition(state.getDate(), state.getFrame()).normalize();
- final T alpha = sunDir.dotProduct(auxiliaryElements.getVectorF());
- final T beta = sunDir.dotProduct(auxiliaryElements.getVectorG());
- final T gamma = sunDir.dotProduct(auxiliaryElements.getVectorW());
- // Compute limits only if the perigee is close enough from the central body to be in the shadow
- if (FastMath.abs(gamma.multiply(auxiliaryElements.getSma()).multiply(auxiliaryElements.getEcc().negate().add(one))).getReal() < ae) {
- // Compute the coefficients of the quartic equation in cos(L) 3.5-(2)
- final T bet2 = beta.multiply(beta);
- final T h2 = auxiliaryElements.getH().multiply(auxiliaryElements.getH());
- final T k2 = auxiliaryElements.getK().multiply(auxiliaryElements.getK());
- final T m = (auxiliaryElements.getSma().multiply(auxiliaryElements.getB())).divide(ae).reciprocal();
- final T m2 = m.multiply(m);
- final T m4 = m2.multiply(m2);
- final T bb = alpha.multiply(beta).add(m2.multiply(auxiliaryElements.getH()).multiply(auxiliaryElements.getK()));
- final T b2 = bb.multiply(bb);
- final T cc = alpha.multiply(alpha).subtract(bet2).add(m2.multiply(k2.subtract(h2)));
- final T dd = bet2.add(m2.multiply(h2.add(1.))).negate().add(one);
- final T[] a = MathArrays.buildArray(field, 5);
- a[0] = b2.multiply(4.).add(cc.multiply(cc));
- a[1] = bb.multiply(8.).multiply(m2).multiply(auxiliaryElements.getH()).add(cc.multiply(4.).multiply(m2).multiply(auxiliaryElements.getK()));
- a[2] = m4.multiply(h2).multiply(4.).subtract(cc.multiply(dd).multiply(2.)).add(m4.multiply(k2).multiply(4.)).subtract(b2.multiply(4.));
- a[3] = auxiliaryElements.getH().multiply(m2).multiply(bb).multiply(8.).add(auxiliaryElements.getK().multiply(m2).multiply(dd).multiply(4.)).negate();
- a[4] = dd.multiply(dd).subtract(m4.multiply(h2).multiply(4.));
- // Compute the real roots of the quartic equation 3.5-2
- final T[] roots = MathArrays.buildArray(field, 4);
- final int nbRoots = realQuarticRoots(a, roots, field);
- if (nbRoots > 0) {
- // Check for consistency
- boolean entryFound = false;
- boolean exitFound = false;
- // Eliminate spurious roots
- for (int i = 0; i < nbRoots; i++) {
- final T cosL = roots[i];
- final T sL = FastMath.sqrt((cosL.negate().add(one)).multiply(cosL.add(one)));
- // Check both angles: L and -L
- for (int j = -1; j <= 1; j += 2) {
- final T sinL = sL.multiply(j);
- final T cPhi = cosL.multiply(alpha).add(sinL.multiply(beta));
- // Is the angle on the shadow side of the central body (eq. 3.5-3) ?
- if (cPhi.getReal() < 0.) {
- final T range = cosL.multiply(auxiliaryElements.getK()).add(sinL.multiply(auxiliaryElements.getH())).add(one);
- final T S = (range.multiply(range).multiply(m2).add(cPhi.multiply(cPhi))).negate().add(1.);
- // Is the shadow equation 3.5-1 satisfied ?
- if (FastMath.abs(S).getReal() < S_ZERO) {
- // Is this the entry or exit angle ?
- final T dSdL = m2.multiply(range).multiply(auxiliaryElements.getK().multiply(sinL).subtract(auxiliaryElements.getH().multiply(cosL))).add(cPhi.multiply(alpha.multiply(sinL).subtract(beta.multiply(cosL))));
- if (dSdL.getReal() > 0.) {
- // Exit from shadow: 3.5-4
- exitFound = true;
- ll[0] = FastMath.atan2(sinL, cosL);
- } else {
- // Entry into shadow: 3.5-5
- entryFound = true;
- ll[1] = FastMath.atan2(sinL, cosL);
- }
- }
- }
- }
- }
- // Must be one entry and one exit or none
- if (!(entryFound == exitFound)) {
- // entry or exit found but not both ! In this case, consider there is no eclipse...
- ll[0] = pi.negate();
- ll[1] = pi;
- }
- // Quadrature between L at exit and L at entry so Lexit must be lower than Lentry
- if (ll[0].getReal() > ll[1].getReal()) {
- // Keep the angles between [-2PI, 2PI]
- if (ll[1].getReal() < 0.) {
- ll[1] = ll[1].add(pi.multiply(2.0));
- } else {
- ll[0] = ll[0].subtract(pi.multiply(2.0));
- }
- }
- }
- }
- return ll;
- }
- /** Get the central body equatorial radius.
- * @return central body equatorial radius (m)
- */
- public double getEquatorialRadius() {
- return ae;
- }
- /**
- * Compute the real roots of a quartic equation.
- *
- * <pre>
- * a[0] * x⁴ + a[1] * x³ + a[2] * x² + a[3] * x + a[4] = 0.
- * </pre>
- *
- * @param a the 5 coefficients
- * @param y the real roots
- * @return the number of real roots
- */
- private int realQuarticRoots(final double[] a, final double[] y) {
- /* Treat the degenerate quartic as cubic */
- if (Precision.equals(a[0], 0.)) {
- final double[] aa = new double[a.length - 1];
- System.arraycopy(a, 1, aa, 0, aa.length);
- return realCubicRoots(aa, y);
- }
- // Transform coefficients
- final double b = a[1] / a[0];
- final double c = a[2] / a[0];
- final double d = a[3] / a[0];
- final double e = a[4] / a[0];
- final double bh = b * 0.5;
- // Solve resolvant cubic
- final double[] z3 = new double[3];
- final int i3 = realCubicRoots(new double[] {1.0, -c, b * d - 4. * e, e * (4. * c - b * b) - d * d}, z3);
- if (i3 == 0) {
- return 0;
- }
- // Largest real root of resolvant cubic
- final double z = z3[0];
- // Compute auxiliary quantities
- final double zh = z * 0.5;
- final double p = FastMath.max(z + bh * bh - c, 0.);
- final double q = FastMath.max(zh * zh - e, 0.);
- final double r = bh * z - d;
- final double pp = FastMath.sqrt(p);
- final double qq = FastMath.copySign(FastMath.sqrt(q), r);
- // Solve quadratic factors of quartic equation
- final double[] y1 = new double[2];
- final int n1 = realQuadraticRoots(new double[] {1.0, bh - pp, zh - qq}, y1);
- final double[] y2 = new double[2];
- final int n2 = realQuadraticRoots(new double[] {1.0, bh + pp, zh + qq}, y2);
- if (n1 == 2) {
- if (n2 == 2) {
- y[0] = y1[0];
- y[1] = y1[1];
- y[2] = y2[0];
- y[3] = y2[1];
- return 4;
- } else {
- y[0] = y1[0];
- y[1] = y1[1];
- return 2;
- }
- } else {
- if (n2 == 2) {
- y[0] = y2[0];
- y[1] = y2[1];
- return 2;
- } else {
- return 0;
- }
- }
- }
- /**
- * Compute the real roots of a quartic equation.
- *
- * <pre>
- * a[0] * x⁴ + a[1] * x³ + a[2] * x² + a[3] * x + a[4] = 0.
- * </pre>
- * @param <T> the type of the field elements
- *
- * @param a the 5 coefficients
- * @param y the real roots
- * @param field field of elements
- * @return the number of real roots
- */
- private <T extends CalculusFieldElement<T>> int realQuarticRoots(final T[] a, final T[] y,
- final Field<T> field) {
- final T zero = field.getZero();
- // Treat the degenerate quartic as cubic
- if (Precision.equals(a[0].getReal(), 0.)) {
- final T[] aa = MathArrays.buildArray(field, a.length - 1);
- System.arraycopy(a, 1, aa, 0, aa.length);
- return realCubicRoots(aa, y, field);
- }
- // Transform coefficients
- final T b = a[1].divide(a[0]);
- final T c = a[2].divide(a[0]);
- final T d = a[3].divide(a[0]);
- final T e = a[4].divide(a[0]);
- final T bh = b.multiply(0.5);
- // Solve resolvant cubic
- final T[] z3 = MathArrays.buildArray(field, 3);
- final T[] i = MathArrays.buildArray(field, 4);
- i[0] = zero.add(1.0);
- i[1] = c.negate();
- i[2] = b.multiply(d).subtract(e.multiply(4.0));
- i[3] = e.multiply(c.multiply(4.).subtract(b.multiply(b))).subtract(d.multiply(d));
- final int i3 = realCubicRoots(i, z3, field);
- if (i3 == 0) {
- return 0;
- }
- // Largest real root of resolvant cubic
- final T z = z3[0];
- // Compute auxiliary quantities
- final T zh = z.multiply(0.5);
- final T p = FastMath.max(z.add(bh.multiply(bh)).subtract(c), zero);
- final T q = FastMath.max(zh.multiply(zh).subtract(e), zero);
- final T r = bh.multiply(z).subtract(d);
- final T pp = FastMath.sqrt(p);
- final T qq = FastMath.copySign(FastMath.sqrt(q), r);
- // Solve quadratic factors of quartic equation
- final T[] y1 = MathArrays.buildArray(field, 2);
- final T[] n = MathArrays.buildArray(field, 3);
- n[0] = zero.add(1.0);
- n[1] = bh.subtract(pp);
- n[2] = zh.subtract(qq);
- final int n1 = realQuadraticRoots(n, y1);
- final T[] y2 = MathArrays.buildArray(field, 2);
- final T[] nn = MathArrays.buildArray(field, 3);
- nn[0] = zero.add(1.0);
- nn[1] = bh.add(pp);
- nn[2] = zh.add(qq);
- final int n2 = realQuadraticRoots(nn, y2);
- if (n1 == 2) {
- if (n2 == 2) {
- y[0] = y1[0];
- y[1] = y1[1];
- y[2] = y2[0];
- y[3] = y2[1];
- return 4;
- } else {
- y[0] = y1[0];
- y[1] = y1[1];
- return 2;
- }
- } else {
- if (n2 == 2) {
- y[0] = y2[0];
- y[1] = y2[1];
- return 2;
- } else {
- return 0;
- }
- }
- }
- /**
- * Compute the real roots of a cubic equation.
- *
- * <pre>
- * a[0] * x³ + a[1] * x² + a[2] * x + a[3] = 0.
- * </pre>
- *
- * @param a the 4 coefficients
- * @param y the real roots sorted in descending order
- * @return the number of real roots
- */
- private int realCubicRoots(final double[] a, final double[] y) {
- if (Precision.equals(a[0], 0.)) {
- // Treat the degenerate cubic as quadratic
- final double[] aa = new double[a.length - 1];
- System.arraycopy(a, 1, aa, 0, aa.length);
- return realQuadraticRoots(aa, y);
- }
- // Transform coefficients
- final double b = -a[1] / (3. * a[0]);
- final double c = a[2] / a[0];
- final double d = a[3] / a[0];
- final double b2 = b * b;
- final double p = b2 - c / 3.;
- final double q = b * (b2 - c * 0.5) - d * 0.5;
- // Compute discriminant
- final double disc = p * p * p - q * q;
- if (disc < 0.) {
- // One real root
- final double alpha = q + FastMath.copySign(FastMath.sqrt(-disc), q);
- final double cbrtAl = FastMath.cbrt(alpha);
- final double cbrtBe = p / cbrtAl;
- if (p < 0.) {
- y[0] = b + 2. * q / (cbrtAl * cbrtAl + cbrtBe * cbrtBe - p);
- } else if (p > 0.) {
- y[0] = b + cbrtAl + cbrtBe;
- } else {
- y[0] = b + cbrtAl;
- }
- return 1;
- } else if (disc > 0.) {
- // Three distinct real roots
- final double phi = FastMath.atan2(FastMath.sqrt(disc), q) / 3.;
- final double sqP = 2.0 * FastMath.sqrt(p);
- y[0] = b + sqP * FastMath.cos(phi);
- y[1] = b - sqP * FastMath.cos(FastMath.PI / 3. + phi);
- y[2] = b - sqP * FastMath.cos(FastMath.PI / 3. - phi);
- return 3;
- } else {
- // One distinct and two equals real roots
- final double cbrtQ = FastMath.cbrt(q);
- final double root1 = b + 2. * cbrtQ;
- final double root2 = b - cbrtQ;
- if (q < 0.) {
- y[0] = root2;
- y[1] = root2;
- y[2] = root1;
- } else {
- y[0] = root1;
- y[1] = root2;
- y[2] = root2;
- }
- return 3;
- }
- }
- /**
- * Compute the real roots of a cubic equation.
- *
- * <pre>
- * a[0] * x³ + a[1] * x² + a[2] * x + a[3] = 0.
- * </pre>
- *
- * @param <T> the type of the field elements
- * @param a the 4 coefficients
- * @param y the real roots sorted in descending order
- * @param field field of elements
- * @return the number of real roots
- */
- private <T extends CalculusFieldElement<T>> int realCubicRoots(final T[] a, final T[] y,
- final Field<T> field) {
- if (Precision.equals(a[0].getReal(), 0.)) {
- // Treat the degenerate cubic as quadratic
- final T[] aa = MathArrays.buildArray(field, a.length - 1);
- System.arraycopy(a, 1, aa, 0, aa.length);
- return realQuadraticRoots(aa, y);
- }
- // Transform coefficients
- final T b = a[1].divide(a[0].multiply(3.)).negate();
- final T c = a[2].divide(a[0]);
- final T d = a[3].divide(a[0]);
- final T b2 = b.multiply(b);
- final T p = b2.subtract(c.divide(3.));
- final T q = b.multiply(b2.subtract(c.multiply(0.5))).subtract(d.multiply(0.5));
- // Compute discriminant
- final T disc = p.multiply(p).multiply(p).subtract(q.multiply(q));
- if (disc.getReal() < 0.) {
- // One real root
- final T alpha = FastMath.copySign(FastMath.sqrt(disc.negate()), q).add(q);
- final T cbrtAl = FastMath.cbrt(alpha);
- final T cbrtBe = p.divide(cbrtAl);
- if (p .getReal() < 0.) {
- y[0] = q.divide(cbrtAl.multiply(cbrtAl).add(cbrtBe.multiply(cbrtBe)).subtract(p)).multiply(2.).add(b);
- } else if (p.getReal() > 0.) {
- y[0] = b.add(cbrtAl).add(cbrtBe);
- } else {
- y[0] = b.add(cbrtAl);
- }
- return 1;
- } else if (disc.getReal() > 0.) {
- // Three distinct real roots
- final T phi = FastMath.atan2(FastMath.sqrt(disc), q).divide(3.);
- final T sqP = FastMath.sqrt(p).multiply(2.);
- y[0] = b.add(sqP.multiply(FastMath.cos(phi)));
- y[1] = b.subtract(sqP.multiply(FastMath.cos(phi.add(b.getPi().divide(3.)))));
- y[2] = b.subtract(sqP.multiply(FastMath.cos(phi.negate().add(b.getPi().divide(3.)))));
- return 3;
- } else {
- // One distinct and two equals real roots
- final T cbrtQ = FastMath.cbrt(q);
- final T root1 = b.add(cbrtQ.multiply(2.0));
- final T root2 = b.subtract(cbrtQ);
- if (q.getReal() < 0.) {
- y[0] = root2;
- y[1] = root2;
- y[2] = root1;
- } else {
- y[0] = root1;
- y[1] = root2;
- y[2] = root2;
- }
- return 3;
- }
- }
- /**
- * Compute the real roots of a quadratic equation.
- *
- * <pre>
- * a[0] * x² + a[1] * x + a[2] = 0.
- * </pre>
- *
- * @param a the 3 coefficients
- * @param y the real roots sorted in descending order
- * @return the number of real roots
- */
- private int realQuadraticRoots(final double[] a, final double[] y) {
- if (Precision.equals(a[0], 0.)) {
- // Degenerate quadratic
- if (Precision.equals(a[1], 0.)) {
- // Degenerate linear equation: no real roots
- return 0;
- }
- // Linear equation: one real root
- y[0] = -a[2] / a[1];
- return 1;
- }
- // Transform coefficients
- final double b = -0.5 * a[1] / a[0];
- final double c = a[2] / a[0];
- // Compute discriminant
- final double d = b * b - c;
- if (d < 0.) {
- // No real roots
- return 0;
- } else if (d > 0.) {
- // Two distinct real roots
- final double y0 = b + FastMath.copySign(FastMath.sqrt(d), b);
- final double y1 = c / y0;
- y[0] = FastMath.max(y0, y1);
- y[1] = FastMath.min(y0, y1);
- return 2;
- } else {
- // Discriminant is zero: two equal real roots
- y[0] = b;
- y[1] = b;
- return 2;
- }
- }
- /**
- * Compute the real roots of a quadratic equation.
- *
- * <pre>
- * a[0] * x² + a[1] * x + a[2] = 0.
- * </pre>
- *
- * @param <T> the type of the field elements
- * @param a the 3 coefficients
- * @param y the real roots sorted in descending order
- * @return the number of real roots
- */
- private <T extends CalculusFieldElement<T>> int realQuadraticRoots(final T[] a, final T[] y) {
- if (Precision.equals(a[0].getReal(), 0.)) {
- // Degenerate quadratic
- if (Precision.equals(a[1].getReal(), 0.)) {
- // Degenerate linear equation: no real roots
- return 0;
- }
- // Linear equation: one real root
- y[0] = a[2].divide(a[1]).negate();
- return 1;
- }
- // Transform coefficients
- final T b = a[1].divide(a[0]).multiply(0.5).negate();
- final T c = a[2].divide(a[0]);
- // Compute discriminant
- final T d = b.multiply(b).subtract(c);
- if (d.getReal() < 0.) {
- // No real roots
- return 0;
- } else if (d.getReal() > 0.) {
- // Two distinct real roots
- final T y0 = b.add(FastMath.copySign(FastMath.sqrt(d), b));
- final T y1 = c.divide(y0);
- y[0] = FastMath.max(y0, y1);
- y[1] = FastMath.min(y0, y1);
- return 2;
- } else {
- // Discriminant is zero: two equal real roots
- y[0] = b;
- y[1] = b;
- return 2;
- }
- }
- }