DSSTSolarRadiationPressure.java

  1. /* Copyright 2002-2023 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.ExtendedPVCoordinatesProvider;
  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 ExtendedPVCoordinatesProvider 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 ExtendedPVCoordinatesProvider 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 ExtendedPVCoordinatesProvider 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 ExtendedPVCoordinatesProvider 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 ExtendedPVCoordinatesProvider 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.     protected List<ParameterDriver> getParametersDriversWithoutMu() {
  178.         return spacecraft.getRadiationParametersDrivers();
  179.     }

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

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

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

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

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

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

  267.         final Field<T> field = state.getDate().getField();
  268.         final T zero = field.getZero();
  269.         final T one  = field.getOne();
  270.         final T pi   = one.getPi();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  534.         // Compute discriminant
  535.         final double disc = p * p * p - q * q;

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

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

  548.             return 1;

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

  553.             y[0] = b + sqP * FastMath.cos(phi);
  554.             y[1] = b - sqP * FastMath.cos(FastMath.PI / 3. + phi);
  555.             y[2] = b - sqP * FastMath.cos(FastMath.PI / 3. - phi);

  556.             return 3;

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

  571.             return 3;
  572.         }
  573.     }

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

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

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

  602.         // Compute discriminant
  603.         final T disc = p.multiply(p).multiply(p).subtract(q.multiply(q));

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

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

  616.             return 1;

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

  621.             y[0] = b.add(sqP.multiply(FastMath.cos(phi)));
  622.             y[1] = b.subtract(sqP.multiply(FastMath.cos(phi.add(b.getPi().divide(3.)))));
  623.             y[2] = b.subtract(sqP.multiply(FastMath.cos(phi.negate().add(b.getPi().divide(3.)))));

  624.             return 3;

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

  639.             return 3;
  640.         }
  641.     }

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

  664.         // Transform coefficients
  665.         final double b = -0.5 * a[1] / a[0];
  666.         final double c =  a[2] / a[0];

  667.         // Compute discriminant
  668.         final double d =  b * b - c;

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

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

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

  709.         // Transform coefficients
  710.         final T b = a[1].divide(a[0]).multiply(0.5).negate();
  711.         final T c =  a[2].divide(a[0]);

  712.         // Compute discriminant
  713.         final T d =  b.multiply(b).subtract(c);

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

  731. }