DSSTSolarRadiationPressure.java

  1. /* Copyright 2002-2025 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS 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.  *   http://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.orekit.propagation.semianalytical.dsst.forces;

  18. import java.util.List;

  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.Field;
  21. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  22. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  23. import org.hipparchus.util.FastMath;
  24. import org.hipparchus.util.MathArrays;
  25. import org.hipparchus.util.MathUtils;
  26. import org.hipparchus.util.Precision;
  27. import org.orekit.bodies.OneAxisEllipsoid;
  28. import org.orekit.forces.radiation.IsotropicRadiationSingleCoefficient;
  29. import org.orekit.forces.radiation.RadiationSensitive;
  30. import org.orekit.forces.radiation.SolarRadiationPressure;
  31. import org.orekit.propagation.FieldSpacecraftState;
  32. import org.orekit.propagation.SpacecraftState;
  33. import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
  34. import org.orekit.propagation.semianalytical.dsst.utilities.FieldAuxiliaryElements;
  35. import org.orekit.utils.ExtendedPositionProvider;
  36. import org.orekit.utils.ParameterDriver;

  37. /** Solar radiation pressure contribution to the
  38.  *  {@link org.orekit.propagation.semianalytical.dsst.DSSTPropagator DSSTPropagator}.
  39.  *  <p>
  40.  *  The solar radiation pressure acceleration is computed through the
  41.  *  acceleration model of
  42.  *  {@link org.orekit.forces.radiation.SolarRadiationPressure SolarRadiationPressure}.
  43.  *  </p>
  44.  *
  45.  *  @author Pascal Parraud
  46.  */
  47. public class DSSTSolarRadiationPressure extends AbstractGaussianContribution {

  48.     /** Reference distance for the solar radiation pressure (m). */
  49.     private static final double D_REF = 149597870000.0;

  50.     /** Reference solar radiation pressure at D_REF (N/m²). */
  51.     private static final double P_REF = 4.56e-6;

  52.     /** Threshold for the choice of the Gauss quadrature order. */
  53.     private static final double GAUSS_THRESHOLD = 1.0e-15;

  54.     /** Threshold for shadow equation. */
  55.     private static final double S_ZERO = 1.0e-6;

  56.     /** Prefix for the coefficient keys. */
  57.     private static final String PREFIX = "DSST-SRP-";

  58.     /** Sun model. */
  59.     private final ExtendedPositionProvider sun;

  60.     /** Central Body radius. */
  61.     private final double                ae;

  62.     /** Spacecraft model for radiation acceleration computation. */
  63.     private final RadiationSensitive spacecraft;

  64.     /**
  65.      * Simple constructor with default reference values and spherical spacecraft.
  66.      * <p>
  67.      * When this constructor is used, the reference values are:
  68.      * </p>
  69.      * <ul>
  70.      * <li>d<sub>ref</sub> = 149597870000.0 m</li>
  71.      * <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
  72.      * </ul>
  73.      * <p>
  74.      * The spacecraft has a spherical shape.
  75.      * </p>
  76.      *
  77.      * @param cr satellite radiation pressure coefficient (assuming total specular reflection)
  78.      * @param area cross sectional area of satellite
  79.      * @param sun Sun model
  80.      * @param centralBody central body (for shadow computation)
  81.      * @param mu central attraction coefficient
  82.      * @since 12.0
  83.      */
  84.     public DSSTSolarRadiationPressure(final double cr, final double area,
  85.                                       final ExtendedPositionProvider sun,
  86.                                       final OneAxisEllipsoid centralBody,
  87.                                       final double mu) {
  88.         this(D_REF, P_REF, cr, area, sun, centralBody, mu);
  89.     }

  90.     /**
  91.      * Simple constructor with default reference values, but custom spacecraft.
  92.      * <p>
  93.      * When this constructor is used, the reference values are:
  94.      * </p>
  95.      * <ul>
  96.      * <li>d<sub>ref</sub> = 149597870000.0 m</li>
  97.      * <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
  98.      * </ul>
  99.      *
  100.      * @param sun Sun model
  101.      * @param centralBody central body (for shadow computation)
  102.      * @param spacecraft spacecraft model
  103.      * @param mu central attraction coefficient
  104.      * @since 12.0
  105.      */
  106.     public DSSTSolarRadiationPressure(final ExtendedPositionProvider sun,
  107.                                       final OneAxisEllipsoid centralBody,
  108.                                       final RadiationSensitive spacecraft,
  109.                                       final double mu) {
  110.         this(D_REF, P_REF, sun, centralBody, spacecraft, mu);
  111.     }

  112.     /**
  113.      * Constructor with customizable reference values but spherical spacecraft.
  114.      * <p>
  115.      * Note that reference solar radiation pressure <code>pRef</code> in N/m² is linked
  116.      * to solar flux SF in W/m² using formula pRef = SF/c where c is the speed of light
  117.      * (299792458 m/s). So at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
  118.      * N/m² solar radiation pressure.
  119.      * </p>
  120.      *
  121.      * @param dRef reference distance for the solar radiation pressure (m)
  122.      * @param pRef reference solar radiation pressure at dRef (N/m²)
  123.      * @param cr satellite radiation pressure coefficient (assuming total specular reflection)
  124.      * @param area cross sectional area of satellite
  125.      * @param sun Sun model
  126.      * @param centralBody central body (for shadow computation)
  127.      * @param mu central attraction coefficient
  128.      * @since 12.0
  129.      */
  130.     public DSSTSolarRadiationPressure(final double dRef, final double pRef,
  131.                                       final double cr, final double area,
  132.                                       final ExtendedPositionProvider sun,
  133.                                       final OneAxisEllipsoid centralBody,
  134.                                       final double mu) {

  135.         // cR being the DSST SRP coef and assuming a spherical spacecraft,
  136.         // the conversion is:
  137.         // cR = 1 + (1 - kA) * (1 - kR) * 4 / 9
  138.         // with kA arbitrary sets to 0
  139.         this(dRef, pRef, sun, centralBody, new IsotropicRadiationSingleCoefficient(area, cr), mu);
  140.     }

  141.     /**
  142.      * Complete constructor.
  143.      * <p>
  144.      * Note that reference solar radiation pressure <code>pRef</code> in N/m² is linked
  145.      * to solar flux SF in W/m² using formula pRef = SF/c where c is the speed of light
  146.      * (299792458 m/s). So at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
  147.      * N/m² solar radiation pressure.
  148.      * </p>
  149.      *
  150.      * @param dRef reference distance for the solar radiation pressure (m)
  151.      * @param pRef reference solar radiation pressure at dRef (N/m²)
  152.      * @param sun Sun model
  153.      * @param centralBody central body shape model (for umbra/penumbra computation)
  154.      * @param spacecraft spacecraft model
  155.      * @param mu central attraction coefficient
  156.      * @since 12.0
  157.      */
  158.     public DSSTSolarRadiationPressure(final double dRef, final double pRef,
  159.                                       final ExtendedPositionProvider sun,
  160.                                       final OneAxisEllipsoid centralBody,
  161.                                       final RadiationSensitive spacecraft,
  162.                                       final double mu) {

  163.         //Call to the constructor from superclass using the numerical SRP model as ForceModel
  164.         super(PREFIX, GAUSS_THRESHOLD,
  165.               new SolarRadiationPressure(dRef, pRef, sun, centralBody, spacecraft), mu);

  166.         this.sun  = sun;
  167.         this.ae   = centralBody.getEquatorialRadius();
  168.         this.spacecraft = spacecraft;
  169.     }

  170.     /** Get spacecraft shape.
  171.      * @return the spacecraft shape.
  172.      */
  173.     public RadiationSensitive getSpacecraft() {
  174.         return spacecraft;
  175.     }

  176.     /** {@inheritDoc} */
  177.     @Override
  178.     protected List<ParameterDriver> getParametersDriversWithoutMu() {
  179.         return spacecraft.getRadiationParametersDrivers();
  180.     }

  181.     /** {@inheritDoc} */
  182.     @Override
  183.     protected double[] getLLimits(final SpacecraftState state, final AuxiliaryElements auxiliaryElements) {

  184.         // Default bounds without shadow [-PI, PI]
  185.         final double[] ll = {-FastMath.PI + MathUtils.normalizeAngle(state.getOrbit().getLv(), 0),
  186.                              FastMath.PI + MathUtils.normalizeAngle(state.getOrbit().getLv(), 0)};

  187.         // Direction cosines of the Sun in the equinoctial frame
  188.         final Vector3D sunDir = sun.getPosition(state.getDate(), state.getFrame()).normalize();
  189.         final double alpha = sunDir.dotProduct(auxiliaryElements.getVectorF());
  190.         final double beta  = sunDir.dotProduct(auxiliaryElements.getVectorG());
  191.         final double gamma = sunDir.dotProduct(auxiliaryElements.getVectorW());

  192.         // Compute limits only if the perigee is close enough from the central body to be in the shadow
  193.         if (FastMath.abs(gamma * auxiliaryElements.getSma() * (1. - auxiliaryElements.getEcc())) < ae) {

  194.             // Compute the coefficients of the quartic equation in cos(L) 3.5-(2)
  195.             final double bet2 = beta * beta;
  196.             final double h2 = auxiliaryElements.getH() * auxiliaryElements.getH();
  197.             final double k2 = auxiliaryElements.getK() * auxiliaryElements.getK();
  198.             final double m  = ae / (auxiliaryElements.getSma() * auxiliaryElements.getB());
  199.             final double m2 = m * m;
  200.             final double m4 = m2 * m2;
  201.             final double bb = alpha * beta + m2 * auxiliaryElements.getH() * auxiliaryElements.getK();
  202.             final double b2 = bb * bb;
  203.             final double cc = alpha * alpha - bet2 + m2 * (k2 - h2);
  204.             final double dd = 1. - bet2 - m2 * (1. + h2);
  205.             final double[] a = new double[5];
  206.             a[0] = 4. * b2 + cc * cc;
  207.             a[1] = 8. * bb * m2 * auxiliaryElements.getH() + 4. * cc * m2 * auxiliaryElements.getK();
  208.             a[2] = -4. * b2 + 4. * m4 * h2 - 2. * cc * dd + 4. * m4 * k2;
  209.             a[3] = -8. * bb * m2 * auxiliaryElements.getH() - 4. * dd * m2 * auxiliaryElements.getK();
  210.             a[4] = -4. * m4 * h2 + dd * dd;
  211.             // Compute the real roots of the quartic equation 3.5-2
  212.             final double[] roots = new double[4];
  213.             final int nbRoots = realQuarticRoots(a, roots);
  214.             if (nbRoots > 0) {
  215.                 // Check for consistency
  216.                 boolean entryFound = false;
  217.                 boolean exitFound  = false;
  218.                 // Eliminate spurious roots
  219.                 for (int i = 0; i < nbRoots; i++) {
  220.                     final double cosL = roots[i];
  221.                     final double sL = FastMath.sqrt((1. - cosL) * (1. + cosL));
  222.                     // Check both angles: L and -L
  223.                     for (int j = -1; j <= 1; j += 2) {
  224.                         final double sinL = j * sL;
  225.                         final double cPhi = alpha * cosL + beta * sinL;
  226.                         // Is the angle on the shadow side of the central body (eq. 3.5-3) ?
  227.                         if (cPhi < 0.) {
  228.                             final double range = 1. + auxiliaryElements.getK() * cosL + auxiliaryElements.getH() * sinL;
  229.                             final double S  = 1. - m2 * range * range - cPhi * cPhi;
  230.                             // Is the shadow equation 3.5-1 satisfied ?
  231.                             if (FastMath.abs(S) < S_ZERO) {
  232.                                 // Is this the entry or exit angle ?
  233.                                 final double dSdL = m2 * range * (auxiliaryElements.getK() * sinL - auxiliaryElements.getH() * cosL) + cPhi * (alpha * sinL - beta * cosL);
  234.                                 if (dSdL > 0.) {
  235.                                     // Exit from shadow: 3.5-4
  236.                                     exitFound = true;
  237.                                     ll[0] = FastMath.atan2(sinL, cosL);
  238.                                 } else {
  239.                                     // Entry into shadow: 3.5-5
  240.                                     entryFound = true;
  241.                                     ll[1] = FastMath.atan2(sinL, cosL);
  242.                                 }
  243.                             }
  244.                         }
  245.                     }
  246.                 }
  247.                 // Must be one entry and one exit or none
  248.                 if (!(entryFound == exitFound)) {
  249.                     // entry or exit found but not both ! In this case, consider there is no eclipse...
  250.                     ll[0] = -FastMath.PI;
  251.                     ll[1] = FastMath.PI;
  252.                 }
  253.                 // Quadrature between L at exit and L at entry so Lexit must be lower than Lentry
  254.                 if (ll[0] > ll[1]) {
  255.                     // Keep the angles between [-2PI, 2PI]
  256.                     if (ll[1] < 0.) {
  257.                         ll[1] += 2. * FastMath.PI;
  258.                     } else {
  259.                         ll[0] -= 2. * FastMath.PI;
  260.                     }
  261.                 }
  262.             }
  263.         }
  264.         return ll;
  265.     }

  266.     /** {@inheritDoc} */
  267.     @Override
  268.     protected <T extends CalculusFieldElement<T>> T[] getLLimits(final FieldSpacecraftState<T> state,
  269.                                                                  final FieldAuxiliaryElements<T> auxiliaryElements) {

  270.         final Field<T> field = state.getDate().getField();
  271.         final T zero = field.getZero();
  272.         final T one  = field.getOne();
  273.         final T pi   = one.getPi();

  274.         // Default bounds without shadow [-PI, PI]
  275.         final T[] ll = MathArrays.buildArray(field, 2);
  276.         ll[0] = MathUtils.normalizeAngle(state.getOrbit().getLv(), zero).subtract(pi);
  277.         ll[1] = MathUtils.normalizeAngle(state.getOrbit().getLv(), zero).add(pi);

  278.         // Direction cosines of the Sun in the equinoctial frame
  279.         final FieldVector3D<T> sunDir = sun.getPosition(state.getDate(), state.getFrame()).normalize();
  280.         final T alpha = sunDir.dotProduct(auxiliaryElements.getVectorF());
  281.         final T beta  = sunDir.dotProduct(auxiliaryElements.getVectorG());
  282.         final T gamma = sunDir.dotProduct(auxiliaryElements.getVectorW());

  283.         // Compute limits only if the perigee is close enough from the central body to be in the shadow
  284.         if (FastMath.abs(gamma.multiply(auxiliaryElements.getSma()).multiply(auxiliaryElements.getEcc().negate().add(one))).getReal() < ae) {

  285.             // Compute the coefficients of the quartic equation in cos(L) 3.5-(2)
  286.             final T bet2 = beta.multiply(beta);
  287.             final T h2 = auxiliaryElements.getH().multiply(auxiliaryElements.getH());
  288.             final T k2 = auxiliaryElements.getK().multiply(auxiliaryElements.getK());
  289.             final T m  = (auxiliaryElements.getSma().multiply(auxiliaryElements.getB())).divide(ae).reciprocal();
  290.             final T m2 = m.square();
  291.             final T m4 = m2.square();
  292.             final T bb = alpha.multiply(beta).add(m2.multiply(auxiliaryElements.getH()).multiply(auxiliaryElements.getK()));
  293.             final T b2 = bb.multiply(bb);
  294.             final T cc = alpha.multiply(alpha).subtract(bet2).add(m2.multiply(k2.subtract(h2)));
  295.             final T dd = bet2.add(m2.multiply(h2.add(1.))).negate().add(one);
  296.             final T[] a = MathArrays.buildArray(field, 5);
  297.             a[0] = b2.multiply(4.).add(cc.multiply(cc));
  298.             a[1] = bb.multiply(8.).multiply(m2).multiply(auxiliaryElements.getH()).add(cc.multiply(4.).multiply(m2).multiply(auxiliaryElements.getK()));
  299.             a[2] = m4.multiply(h2).multiply(4.).subtract(cc.multiply(dd).multiply(2.)).add(m4.multiply(k2).multiply(4.)).subtract(b2.multiply(4.));
  300.             a[3] = auxiliaryElements.getH().multiply(m2).multiply(bb).multiply(8.).add(auxiliaryElements.getK().multiply(m2).multiply(dd).multiply(4.)).negate();
  301.             a[4] = dd.multiply(dd).subtract(m4.multiply(h2).multiply(4.));
  302.             // Compute the real roots of the quartic equation 3.5-2
  303.             final T[] roots = MathArrays.buildArray(field, 4);
  304.             final int nbRoots = realQuarticRoots(a, roots, field);
  305.             if (nbRoots > 0) {
  306.                 // Check for consistency
  307.                 boolean entryFound = false;
  308.                 boolean exitFound  = false;
  309.                 // Eliminate spurious roots
  310.                 for (int i = 0; i < nbRoots; i++) {
  311.                     final T cosL = roots[i];
  312.                     final T sL = FastMath.sqrt((cosL.negate().add(one)).multiply(cosL.add(one)));
  313.                     // Check both angles: L and -L
  314.                     for (int j = -1; j <= 1; j += 2) {
  315.                         final T sinL = sL.multiply(j);
  316.                         final T cPhi = cosL.multiply(alpha).add(sinL.multiply(beta));
  317.                         // Is the angle on the shadow side of the central body (eq. 3.5-3) ?
  318.                         if (cPhi.getReal() < 0.) {
  319.                             final T range = cosL.multiply(auxiliaryElements.getK()).add(sinL.multiply(auxiliaryElements.getH())).add(one);
  320.                             final T S  = (range.multiply(range).multiply(m2).add(cPhi.multiply(cPhi))).negate().add(1.);
  321.                             // Is the shadow equation 3.5-1 satisfied ?
  322.                             if (FastMath.abs(S).getReal() < S_ZERO) {
  323.                                 // Is this the entry or exit angle ?
  324.                                 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))));
  325.                                 if (dSdL.getReal() > 0.) {
  326.                                     // Exit from shadow: 3.5-4
  327.                                     exitFound = true;
  328.                                     ll[0] = FastMath.atan2(sinL, cosL);
  329.                                 } else {
  330.                                     // Entry into shadow: 3.5-5
  331.                                     entryFound = true;
  332.                                     ll[1] = FastMath.atan2(sinL, cosL);
  333.                                 }
  334.                             }
  335.                         }
  336.                     }
  337.                 }
  338.                 // Must be one entry and one exit or none
  339.                 if (!(entryFound == exitFound)) {
  340.                     // entry or exit found but not both ! In this case, consider there is no eclipse...
  341.                     ll[0] = pi.negate();
  342.                     ll[1] = pi;
  343.                 }
  344.                 // Quadrature between L at exit and L at entry so Lexit must be lower than Lentry
  345.                 if (ll[0].getReal() > ll[1].getReal()) {
  346.                     // Keep the angles between [-2PI, 2PI]
  347.                     if (ll[1].getReal() < 0.) {
  348.                         ll[1] = ll[1].add(pi.multiply(2.0));
  349.                     } else {
  350.                         ll[0] = ll[0].subtract(pi.multiply(2.0));
  351.                     }
  352.                 }
  353.             }
  354.         }
  355.         return ll;
  356.     }

  357.     /** Get the central body equatorial radius.
  358.      *  @return central body equatorial radius (m)
  359.      */
  360.     public double getEquatorialRadius() {
  361.         return ae;
  362.     }

  363.     /**
  364.      * Compute the real roots of a quartic equation.
  365.      *
  366.      * <pre>
  367.      * a[0] * x⁴ + a[1] * x³ + a[2] * x² + a[3] * x + a[4] = 0.
  368.      * </pre>
  369.      *
  370.      * @param a the 5 coefficients
  371.      * @param y the real roots
  372.      * @return the number of real roots
  373.      */
  374.     private int realQuarticRoots(final double[] a, final double[] y) {
  375.         /* Treat the degenerate quartic as cubic */
  376.         if (Precision.equals(a[0], 0.)) {
  377.             final double[] aa = new double[a.length - 1];
  378.             System.arraycopy(a, 1, aa, 0, aa.length);
  379.             return realCubicRoots(aa, y);
  380.         }

  381.         // Transform coefficients
  382.         final double b  = a[1] / a[0];
  383.         final double c  = a[2] / a[0];
  384.         final double d  = a[3] / a[0];
  385.         final double e  = a[4] / a[0];
  386.         final double bh = b * 0.5;

  387.         // Solve resolvant cubic
  388.         final double[] z3 = new double[3];
  389.         final int i3 = realCubicRoots(new double[] {1.0, -c, b * d - 4. * e, e * (4. * c - b * b) - d * d}, z3);
  390.         if (i3 == 0) {
  391.             return 0;
  392.         }

  393.         // Largest real root of resolvant cubic
  394.         final double z = z3[0];

  395.         // Compute auxiliary quantities
  396.         final double zh = z * 0.5;
  397.         final double p  = FastMath.max(z + bh * bh - c, 0.);
  398.         final double q  = FastMath.max(zh * zh - e, 0.);
  399.         final double r  = bh * z - d;
  400.         final double pp = FastMath.sqrt(p);
  401.         final double qq = FastMath.copySign(FastMath.sqrt(q), r);

  402.         // Solve quadratic factors of quartic equation
  403.         final double[] y1 = new double[2];
  404.         final int n1 = realQuadraticRoots(new double[] {1.0, bh - pp, zh - qq}, y1);
  405.         final double[] y2 = new double[2];
  406.         final int n2 = realQuadraticRoots(new double[] {1.0, bh + pp, zh + qq}, y2);

  407.         if (n1 == 2) {
  408.             if (n2 == 2) {
  409.                 y[0] = y1[0];
  410.                 y[1] = y1[1];
  411.                 y[2] = y2[0];
  412.                 y[3] = y2[1];
  413.                 return 4;
  414.             } else {
  415.                 y[0] = y1[0];
  416.                 y[1] = y1[1];
  417.                 return 2;
  418.             }
  419.         } else {
  420.             if (n2 == 2) {
  421.                 y[0] = y2[0];
  422.                 y[1] = y2[1];
  423.                 return 2;
  424.             } else {
  425.                 return 0;
  426.             }
  427.         }
  428.     }

  429.     /**
  430.      * Compute the real roots of a quartic equation.
  431.      *
  432.      * <pre>
  433.      * a[0] * x⁴ + a[1] * x³ + a[2] * x² + a[3] * x + a[4] = 0.
  434.      * </pre>
  435.      * @param <T> the type of the field elements
  436.      *
  437.      * @param a the 5 coefficients
  438.      * @param y the real roots
  439.      * @param field field of elements
  440.      * @return the number of real roots
  441.      */
  442.     private <T extends CalculusFieldElement<T>> int realQuarticRoots(final T[] a, final T[] y,
  443.                                                                  final Field<T> field) {

  444.         final T zero = field.getZero();

  445.         // Treat the degenerate quartic as cubic
  446.         if (Precision.equals(a[0].getReal(), 0.)) {
  447.             final T[] aa = MathArrays.buildArray(field, a.length - 1);
  448.             System.arraycopy(a, 1, aa, 0, aa.length);
  449.             return realCubicRoots(aa, y, field);
  450.         }

  451.         // Transform coefficients
  452.         final T b  = a[1].divide(a[0]);
  453.         final T c  = a[2].divide(a[0]);
  454.         final T d  = a[3].divide(a[0]);
  455.         final T e  = a[4].divide(a[0]);
  456.         final T bh = b.multiply(0.5);

  457.         // Solve resolvant cubic
  458.         final T[] z3 = MathArrays.buildArray(field, 3);
  459.         final T[] i = MathArrays.buildArray(field, 4);
  460.         i[0] = zero.newInstance(1.0);
  461.         i[1] = c.negate();
  462.         i[2] = b.multiply(d).subtract(e.multiply(4.0));
  463.         i[3] = e.multiply(c.multiply(4.).subtract(b.multiply(b))).subtract(d.multiply(d));
  464.         final int i3 = realCubicRoots(i, z3, field);
  465.         if (i3 == 0) {
  466.             return 0;
  467.         }

  468.         // Largest real root of resolvant cubic
  469.         final T z = z3[0];

  470.         // Compute auxiliary quantities
  471.         final T zh = z.multiply(0.5);
  472.         final T p  = FastMath.max(z.add(bh.multiply(bh)).subtract(c), zero);
  473.         final T q  = FastMath.max(zh.multiply(zh).subtract(e), zero);
  474.         final T r  = bh.multiply(z).subtract(d);
  475.         final T pp = FastMath.sqrt(p);
  476.         final T qq = FastMath.copySign(FastMath.sqrt(q), r);

  477.         // Solve quadratic factors of quartic equation
  478.         final T[] y1 = MathArrays.buildArray(field, 2);
  479.         final T[] n = MathArrays.buildArray(field, 3);
  480.         n[0] = zero.newInstance(1.0);
  481.         n[1] = bh.subtract(pp);
  482.         n[2] = zh.subtract(qq);
  483.         final int n1 = realQuadraticRoots(n, y1);
  484.         final T[] y2 = MathArrays.buildArray(field, 2);
  485.         final T[] nn = MathArrays.buildArray(field, 3);
  486.         nn[0] = zero.newInstance(1.0);
  487.         nn[1] = bh.add(pp);
  488.         nn[2] = zh.add(qq);
  489.         final int n2 = realQuadraticRoots(nn, y2);

  490.         if (n1 == 2) {
  491.             if (n2 == 2) {
  492.                 y[0] = y1[0];
  493.                 y[1] = y1[1];
  494.                 y[2] = y2[0];
  495.                 y[3] = y2[1];
  496.                 return 4;
  497.             } else {
  498.                 y[0] = y1[0];
  499.                 y[1] = y1[1];
  500.                 return 2;
  501.             }
  502.         } else {
  503.             if (n2 == 2) {
  504.                 y[0] = y2[0];
  505.                 y[1] = y2[1];
  506.                 return 2;
  507.             } else {
  508.                 return 0;
  509.             }
  510.         }
  511.     }

  512.     /**
  513.      * Compute the real roots of a cubic equation.
  514.      *
  515.      * <pre>
  516.      * a[0] * x³ + a[1] * x² + a[2] * x + a[3] = 0.
  517.      * </pre>
  518.      *
  519.      * @param a the 4 coefficients
  520.      * @param y the real roots sorted in descending order
  521.      * @return the number of real roots
  522.      */
  523.     private int realCubicRoots(final double[] a, final double[] y) {
  524.         if (Precision.equals(a[0], 0.)) {
  525.             // Treat the degenerate cubic as quadratic
  526.             final double[] aa = new double[a.length - 1];
  527.             System.arraycopy(a, 1, aa, 0, aa.length);
  528.             return realQuadraticRoots(aa, y);
  529.         }

  530.         // Transform coefficients
  531.         final double b  = -a[1] / (3. * a[0]);
  532.         final double c  =  a[2] / a[0];
  533.         final double d  =  a[3] / a[0];
  534.         final double b2 =  b * b;
  535.         final double p  =  b2 - c / 3.;
  536.         final double q  =  b * (b2 - c * 0.5) - d * 0.5;

  537.         // Compute discriminant
  538.         final double disc = p * p * p - q * q;

  539.         if (disc < 0.) {
  540.             // One real root
  541.             final double alpha  = q + FastMath.copySign(FastMath.sqrt(-disc), q);
  542.             final double cbrtAl = FastMath.cbrt(alpha);
  543.             final double cbrtBe = p / cbrtAl;

  544.             if (p < 0.) {
  545.                 y[0] = b + 2. * q / (cbrtAl * cbrtAl + cbrtBe * cbrtBe - p);
  546.             } else if (p > 0.) {
  547.                 y[0] = b + cbrtAl + cbrtBe;
  548.             } else {
  549.                 y[0] = b + cbrtAl;
  550.             }

  551.             return 1;

  552.         } else if (disc > 0.) {
  553.             // Three distinct real roots
  554.             final double phi = FastMath.atan2(FastMath.sqrt(disc), q) / 3.;
  555.             final double sqP = 2.0 * FastMath.sqrt(p);

  556.             y[0] = b + sqP * FastMath.cos(phi);
  557.             y[1] = b - sqP * FastMath.cos(FastMath.PI / 3. + phi);
  558.             y[2] = b - sqP * FastMath.cos(FastMath.PI / 3. - phi);

  559.             return 3;

  560.         } else {
  561.             // One distinct and two equals real roots
  562.             final double cbrtQ = FastMath.cbrt(q);
  563.             final double root1 = b + 2. * cbrtQ;
  564.             final double root2 = b - cbrtQ;
  565.             if (q < 0.) {
  566.                 y[0] = root2;
  567.                 y[1] = root2;
  568.                 y[2] = root1;
  569.             } else {
  570.                 y[0] = root1;
  571.                 y[1] = root2;
  572.                 y[2] = root2;
  573.             }

  574.             return 3;
  575.         }
  576.     }

  577.     /**
  578.      * Compute the real roots of a cubic equation.
  579.      *
  580.      * <pre>
  581.      * a[0] * x³ + a[1] * x² + a[2] * x + a[3] = 0.
  582.      * </pre>
  583.      *
  584.      * @param <T> the type of the field elements
  585.      * @param a the 4 coefficients
  586.      * @param y the real roots sorted in descending order
  587.      * @param field field of elements
  588.      * @return the number of real roots
  589.      */
  590.     private <T extends CalculusFieldElement<T>> int realCubicRoots(final T[] a, final T[] y,
  591.                                                                final Field<T> field) {

  592.         if (Precision.equals(a[0].getReal(), 0.)) {
  593.             // Treat the degenerate cubic as quadratic
  594.             final T[] aa = MathArrays.buildArray(field, a.length - 1);
  595.             System.arraycopy(a, 1, aa, 0, aa.length);
  596.             return realQuadraticRoots(aa, y);
  597.         }

  598.         // Transform coefficients
  599.         final T b  =  a[1].divide(a[0].multiply(3.)).negate();
  600.         final T c  =  a[2].divide(a[0]);
  601.         final T d  =  a[3].divide(a[0]);
  602.         final T b2 =  b.square();
  603.         final T p  =  b2.subtract(c.divide(3.));
  604.         final T q  =  b.multiply(b2.subtract(c.multiply(0.5))).subtract(d.multiply(0.5));

  605.         // Compute discriminant
  606.         final T disc = p.multiply(p).multiply(p).subtract(q.multiply(q));

  607.         if (disc.getReal() < 0.) {
  608.             // One real root
  609.             final T alpha  = FastMath.copySign(FastMath.sqrt(disc.negate()), q).add(q);
  610.             final T cbrtAl = FastMath.cbrt(alpha);
  611.             final T cbrtBe = p.divide(cbrtAl);

  612.             if (p .getReal() < 0.) {
  613.                 y[0] = q.divide(cbrtAl.multiply(cbrtAl).add(cbrtBe.multiply(cbrtBe)).subtract(p)).multiply(2.).add(b);
  614.             } else if (p.getReal() > 0.) {
  615.                 y[0] = b.add(cbrtAl).add(cbrtBe);
  616.             } else {
  617.                 y[0] = b.add(cbrtAl);
  618.             }

  619.             return 1;

  620.         } else if (disc.getReal() > 0.) {
  621.             // Three distinct real roots
  622.             final T phi = FastMath.atan2(FastMath.sqrt(disc), q).divide(3.);
  623.             final T sqP = FastMath.sqrt(p).multiply(2.);

  624.             y[0] = b.add(sqP.multiply(FastMath.cos(phi)));
  625.             y[1] = b.subtract(sqP.multiply(FastMath.cos(phi.add(b.getPi().divide(3.)))));
  626.             y[2] = b.subtract(sqP.multiply(FastMath.cos(phi.negate().add(b.getPi().divide(3.)))));

  627.             return 3;

  628.         } else {
  629.             // One distinct and two equals real roots
  630.             final T cbrtQ = FastMath.cbrt(q);
  631.             final T root1 = b.add(cbrtQ.multiply(2.0));
  632.             final T root2 = b.subtract(cbrtQ);
  633.             if (q.getReal() < 0.) {
  634.                 y[0] = root2;
  635.                 y[1] = root2;
  636.                 y[2] = root1;
  637.             } else {
  638.                 y[0] = root1;
  639.                 y[1] = root2;
  640.                 y[2] = root2;
  641.             }

  642.             return 3;
  643.         }
  644.     }

  645.     /**
  646.      * Compute the real roots of a quadratic equation.
  647.      *
  648.      * <pre>
  649.      * a[0] * x² + a[1] * x + a[2] = 0.
  650.      * </pre>
  651.      *
  652.      * @param a the 3 coefficients
  653.      * @param y the real roots sorted in descending order
  654.      * @return the number of real roots
  655.      */
  656.     private int realQuadraticRoots(final double[] a, final double[] y) {
  657.         if (Precision.equals(a[0], 0.)) {
  658.             // Degenerate quadratic
  659.             if (Precision.equals(a[1], 0.)) {
  660.                 // Degenerate linear equation: no real roots
  661.                 return 0;
  662.             }
  663.             // Linear equation: one real root
  664.             y[0] = -a[2] / a[1];
  665.             return 1;
  666.         }

  667.         // Transform coefficients
  668.         final double b = -0.5 * a[1] / a[0];
  669.         final double c =  a[2] / a[0];

  670.         // Compute discriminant
  671.         final double d =  b * b - c;

  672.         if (d < 0.) {
  673.             // No real roots
  674.             return 0;
  675.         } else if (d > 0.) {
  676.             // Two distinct real roots
  677.             final double y0 = b + FastMath.copySign(FastMath.sqrt(d), b);
  678.             final double y1 = c / y0;
  679.             y[0] = FastMath.max(y0, y1);
  680.             y[1] = FastMath.min(y0, y1);
  681.             return 2;
  682.         } else {
  683.             // Discriminant is zero: two equal real roots
  684.             y[0] = b;
  685.             y[1] = b;
  686.             return 2;
  687.         }
  688.     }

  689.     /**
  690.      * Compute the real roots of a quadratic equation.
  691.      *
  692.      * <pre>
  693.      * a[0] * x² + a[1] * x + a[2] = 0.
  694.      * </pre>
  695.      *
  696.      * @param <T> the type of the field elements
  697.      * @param a the 3 coefficients
  698.      * @param y the real roots sorted in descending order
  699.      * @return the number of real roots
  700.      */
  701.     private <T extends CalculusFieldElement<T>> int realQuadraticRoots(final T[] a, final T[] y) {

  702.         if (Precision.equals(a[0].getReal(), 0.)) {
  703.             // Degenerate quadratic
  704.             if (Precision.equals(a[1].getReal(), 0.)) {
  705.                 // Degenerate linear equation: no real roots
  706.                 return 0;
  707.             }
  708.             // Linear equation: one real root
  709.             y[0] = a[2].divide(a[1]).negate();
  710.             return 1;
  711.         }

  712.         // Transform coefficients
  713.         final T b = a[1].divide(a[0]).multiply(0.5).negate();
  714.         final T c =  a[2].divide(a[0]);

  715.         // Compute discriminant
  716.         final T d =  b.square().subtract(c);

  717.         if (d.getReal() < 0.) {
  718.             // No real roots
  719.             return 0;
  720.         } else if (d.getReal() > 0.) {
  721.             // Two distinct real roots
  722.             final T y0 = b.add(FastMath.copySign(FastMath.sqrt(d), b));
  723.             final T y1 = c.divide(y0);
  724.             y[0] = FastMath.max(y0, y1);
  725.             y[1] = FastMath.min(y0, y1);
  726.             return 2;
  727.         } else {
  728.             // Discriminant is zero: two equal real roots
  729.             y[0] = b;
  730.             y[1] = b;
  731.             return 2;
  732.         }
  733.     }

  734. }