DSSTSolarRadiationPressure.java

  1. /* Copyright 2002-2020 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 org.hipparchus.Field;
  19. import org.hipparchus.RealFieldElement;
  20. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  21. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  22. import org.hipparchus.util.FastMath;
  23. import org.hipparchus.util.MathArrays;
  24. import org.hipparchus.util.MathUtils;
  25. import org.hipparchus.util.Precision;
  26. import org.orekit.forces.radiation.IsotropicRadiationSingleCoefficient;
  27. import org.orekit.forces.radiation.RadiationSensitive;
  28. import org.orekit.forces.radiation.SolarRadiationPressure;
  29. import org.orekit.propagation.FieldSpacecraftState;
  30. import org.orekit.propagation.SpacecraftState;
  31. import org.orekit.propagation.events.EventDetector;
  32. import org.orekit.propagation.events.FieldEventDetector;
  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 equatorialRadius central body equatorial radius (for shadow computation)
  81.      * @param mu central attraction coefficient
  82.      * @since 9.2
  83.      */
  84.     public DSSTSolarRadiationPressure(final double cr, final double area,
  85.                                       final ExtendedPVCoordinatesProvider sun,
  86.                                       final double equatorialRadius,
  87.                                       final double mu) {
  88.         this(D_REF, P_REF, cr, area, sun, equatorialRadius, 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 equatorialRadius central body equatorial radius (for shadow computation)
  102.      * @param spacecraft spacecraft model
  103.      * @param mu central attraction coefficient
  104.      * @since 9.2
  105.      */
  106.     public DSSTSolarRadiationPressure(final ExtendedPVCoordinatesProvider sun,
  107.                                       final double equatorialRadius,
  108.                                       final RadiationSensitive spacecraft,
  109.                                       final double mu) {
  110.         this(D_REF, P_REF, sun, equatorialRadius, 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 equatorialRadius central body equatorial radius (for shadow computation)
  127.      * @param mu central attraction coefficient
  128.      * @since 9.2
  129.      */
  130.     public DSSTSolarRadiationPressure(final double dRef, final double pRef,
  131.                                       final double cr, final double area,
  132.                                       final ExtendedPVCoordinatesProvider sun,
  133.                                       final double equatorialRadius,
  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, equatorialRadius, 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 equatorialRadius central body equatorial radius (for shadow computation)
  154.      * @param spacecraft spacecraft model
  155.      * @param mu central attraction coefficient
  156.      * @since 9.2
  157.      */
  158.     public DSSTSolarRadiationPressure(final double dRef, final double pRef,
  159.                                       final ExtendedPVCoordinatesProvider sun,
  160.                                       final double equatorialRadius,
  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, equatorialRadius, spacecraft), mu);

  166.         this.sun  = sun;
  167.         this.ae   = equatorialRadius;
  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.     public EventDetector[] getEventsDetectors() {
  178.         return null;
  179.     }

  180.     /** {@inheritDoc} */
  181.     @Override
  182.     public <T extends RealFieldElement<T>> FieldEventDetector<T>[] getFieldEventsDetectors(final Field<T> field) {
  183.         return null;
  184.     }

  185.     /** {@inheritDoc} */
  186.     protected ParameterDriver[] getParametersDriversWithoutMu() {
  187.         return spacecraft.getRadiationParametersDrivers();
  188.     }

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

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

  194.         // Direction cosines of the Sun in the equinoctial frame
  195.         final Vector3D sunDir = sun.getPVCoordinates(state.getDate(), state.getFrame()).getPosition().normalize();
  196.         final double alpha = sunDir.dotProduct(auxiliaryElements.getVectorF());
  197.         final double beta  = sunDir.dotProduct(auxiliaryElements.getVectorG());
  198.         final double gamma = sunDir.dotProduct(auxiliaryElements.getVectorW());

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

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

  273.     /** {@inheritDoc} */
  274.     protected <T extends RealFieldElement<T>> T[] getLLimits(final FieldSpacecraftState<T> state,
  275.                                                              final FieldAuxiliaryElements<T> auxiliaryElements) {

  276.         final Field<T> field = state.getDate().getField();
  277.         final T zero = field.getZero();
  278.         final T one  = field.getOne();

  279.         // Default bounds without shadow [-PI, PI]
  280.         final T[] ll = MathArrays.buildArray(field, 2);
  281.         ll[0] = MathUtils.normalizeAngle(state.getLv(), zero).subtract(FastMath.PI);
  282.         ll[1] = MathUtils.normalizeAngle(state.getLv(), zero).add(FastMath.PI);

  283.         // Direction cosines of the Sun in the equinoctial frame
  284.         final FieldVector3D<T> sunDir = sun.getPVCoordinates(state.getDate(), state.getFrame()).getPosition().normalize();
  285.         final T alpha = sunDir.dotProduct(auxiliaryElements.getVectorF());
  286.         final T beta  = sunDir.dotProduct(auxiliaryElements.getVectorG());
  287.         final T gamma = sunDir.dotProduct(auxiliaryElements.getVectorW());

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

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

  362.     /** Get the central body equatorial radius.
  363.      *  @return central body equatorial radius (m)
  364.      */
  365.     public double getEquatorialRadius() {
  366.         return ae;
  367.     }

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

  386.         // Transform coefficients
  387.         final double b  = a[1] / a[0];
  388.         final double c  = a[2] / a[0];
  389.         final double d  = a[3] / a[0];
  390.         final double e  = a[4] / a[0];
  391.         final double bh = b * 0.5;

  392.         // Solve resolvant cubic
  393.         final double[] z3 = new double[3];
  394.         final int i3 = realCubicRoots(new double[] {1.0, -c, b * d - 4. * e, e * (4. * c - b * b) - d * d}, z3);
  395.         if (i3 == 0) {
  396.             return 0;
  397.         }

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

  400.         // Compute auxiliary quantities
  401.         final double zh = z * 0.5;
  402.         final double p  = FastMath.max(z + bh * bh - c, 0.);
  403.         final double q  = FastMath.max(zh * zh - e, 0.);
  404.         final double r  = bh * z - d;
  405.         final double pp = FastMath.sqrt(p);
  406.         final double qq = FastMath.copySign(FastMath.sqrt(q), r);

  407.         // Solve quadratic factors of quartic equation
  408.         final double[] y1 = new double[2];
  409.         final int n1 = realQuadraticRoots(new double[] {1.0, bh - pp, zh - qq}, y1);
  410.         final double[] y2 = new double[2];
  411.         final int n2 = realQuadraticRoots(new double[] {1.0, bh + pp, zh + qq}, y2);

  412.         if (n1 == 2) {
  413.             if (n2 == 2) {
  414.                 y[0] = y1[0];
  415.                 y[1] = y1[1];
  416.                 y[2] = y2[0];
  417.                 y[3] = y2[1];
  418.                 return 4;
  419.             } else {
  420.                 y[0] = y1[0];
  421.                 y[1] = y1[1];
  422.                 return 2;
  423.             }
  424.         } else {
  425.             if (n2 == 2) {
  426.                 y[0] = y2[0];
  427.                 y[1] = y2[1];
  428.                 return 2;
  429.             } else {
  430.                 return 0;
  431.             }
  432.         }
  433.     }

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

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

  450.         // Treat the degenerate quartic as cubic
  451.         if (Precision.equals(a[0].getReal(), 0.)) {
  452.             final T[] aa = MathArrays.buildArray(field, a.length - 1);
  453.             System.arraycopy(a, 1, aa, 0, aa.length);
  454.             return realCubicRoots(aa, y, field);
  455.         }

  456.         // Transform coefficients
  457.         final T b  = a[1].divide(a[0]);
  458.         final T c  = a[2].divide(a[0]);
  459.         final T d  = a[3].divide(a[0]);
  460.         final T e  = a[4].divide(a[0]);
  461.         final T bh = b.multiply(0.5);

  462.         // Solve resolvant cubic
  463.         final T[] z3 = MathArrays.buildArray(field, 3);
  464.         final T[] i = MathArrays.buildArray(field, 4);
  465.         i[0] = zero.add(1.0);
  466.         i[1] = c.negate();
  467.         i[2] = b.multiply(d).subtract(e.multiply(4.0));
  468.         i[3] = e.multiply(c.multiply(4.).subtract(b.multiply(b))).subtract(d.multiply(d));
  469.         final int i3 = realCubicRoots(i, z3, field);
  470.         if (i3 == 0) {
  471.             return 0;
  472.         }

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

  475.         // Compute auxiliary quantities
  476.         final T zh = z.multiply(0.5);
  477.         final T p  = FastMath.max(z.add(bh.multiply(bh)).subtract(c), zero);
  478.         final T q  = FastMath.max(zh.multiply(zh).subtract(e), zero);
  479.         final T r  = bh.multiply(z).subtract(d);
  480.         final T pp = FastMath.sqrt(p);
  481.         final T qq = FastMath.copySign(FastMath.sqrt(q), r);

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

  495.         if (n1 == 2) {
  496.             if (n2 == 2) {
  497.                 y[0] = y1[0];
  498.                 y[1] = y1[1];
  499.                 y[2] = y2[0];
  500.                 y[3] = y2[1];
  501.                 return 4;
  502.             } else {
  503.                 y[0] = y1[0];
  504.                 y[1] = y1[1];
  505.                 return 2;
  506.             }
  507.         } else {
  508.             if (n2 == 2) {
  509.                 y[0] = y2[0];
  510.                 y[1] = y2[1];
  511.                 return 2;
  512.             } else {
  513.                 return 0;
  514.             }
  515.         }
  516.     }

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

  535.         // Transform coefficients
  536.         final double b  = -a[1] / (3. * a[0]);
  537.         final double c  =  a[2] / a[0];
  538.         final double d  =  a[3] / a[0];
  539.         final double b2 =  b * b;
  540.         final double p  =  b2 - c / 3.;
  541.         final double q  =  b * (b2 - c * 0.5) - d * 0.5;

  542.         // Compute discriminant
  543.         final double disc = p * p * p - q * q;

  544.         if (disc < 0.) {
  545.             // One real root
  546.             final double alpha  = q + FastMath.copySign(FastMath.sqrt(-disc), q);
  547.             final double cbrtAl = FastMath.cbrt(alpha);
  548.             final double cbrtBe = p / cbrtAl;

  549.             if (p < 0.) {
  550.                 y[0] = b + 2. * q / (cbrtAl * cbrtAl + cbrtBe * cbrtBe - p);
  551.             } else if (p > 0.) {
  552.                 y[0] = b + cbrtAl + cbrtBe;
  553.             } else {
  554.                 y[0] = b + cbrtAl;
  555.             }

  556.             return 1;

  557.         } else if (disc > 0.) {
  558.             // Three distinct real roots
  559.             final double phi = FastMath.atan2(FastMath.sqrt(disc), q) / 3.;
  560.             final double sqP = 2.0 * FastMath.sqrt(p);

  561.             y[0] = b + sqP * FastMath.cos(phi);
  562.             y[1] = b - sqP * FastMath.cos(FastMath.PI / 3. + phi);
  563.             y[2] = b - sqP * FastMath.cos(FastMath.PI / 3. - phi);

  564.             return 3;

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

  579.             return 3;
  580.         }
  581.     }

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

  597.         if (Precision.equals(a[0].getReal(), 0.)) {
  598.             // Treat the degenerate cubic as quadratic
  599.             final T[] aa = MathArrays.buildArray(field, a.length - 1);
  600.             System.arraycopy(a, 1, aa, 0, aa.length);
  601.             return realQuadraticRoots(aa, y);
  602.         }

  603.         // Transform coefficients
  604.         final T b  =  a[1].divide(a[0].multiply(3.)).negate();
  605.         final T c  =  a[2].divide(a[0]);
  606.         final T d  =  a[3].divide(a[0]);
  607.         final T b2 =  b.multiply(b);
  608.         final T p  =  b2.subtract(c.divide(3.));
  609.         final T q  =  b.multiply(b2.subtract(c.multiply(0.5))).subtract(d.multiply(0.5));

  610.         // Compute discriminant
  611.         final T disc = p.multiply(p).multiply(p).subtract(q.multiply(q));

  612.         if (disc.getReal() < 0.) {
  613.             // One real root
  614.             final T alpha  = FastMath.copySign(FastMath.sqrt(disc.negate()), q).add(q);
  615.             final T cbrtAl = FastMath.cbrt(alpha);
  616.             final T cbrtBe = p.divide(cbrtAl);

  617.             if (p .getReal() < 0.) {
  618.                 y[0] = q.divide(cbrtAl.multiply(cbrtAl).add(cbrtBe.multiply(cbrtBe)).subtract(p)).multiply(2.).add(b);
  619.             } else if (p.getReal() > 0.) {
  620.                 y[0] = b.add(cbrtAl).add(cbrtBe);
  621.             } else {
  622.                 y[0] = b.add(cbrtAl);
  623.             }

  624.             return 1;

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

  629.             y[0] = b.add(sqP.multiply(FastMath.cos(phi)));
  630.             y[1] = b.subtract(sqP.multiply(FastMath.cos(phi.add(FastMath.PI / 3.))));
  631.             y[2] = b.subtract(sqP.multiply(FastMath.cos(phi.negate().add(FastMath.PI / 3.))));

  632.             return 3;

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

  647.             return 3;
  648.         }
  649.     }

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

  672.         // Transform coefficients
  673.         final double b = -0.5 * a[1] / a[0];
  674.         final double c =  a[2] / a[0];

  675.         // Compute discriminant
  676.         final double d =  b * b - c;

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

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

  707.         if (Precision.equals(a[0].getReal(), 0.)) {
  708.             // Degenerate quadratic
  709.             if (Precision.equals(a[1].getReal(), 0.)) {
  710.                 // Degenerate linear equation: no real roots
  711.                 return 0;
  712.             }
  713.             // Linear equation: one real root
  714.             y[0] = a[2].divide(a[1]).negate();
  715.             return 1;
  716.         }

  717.         // Transform coefficients
  718.         final T b = a[1].divide(a[0]).multiply(0.5).negate();
  719.         final T c =  a[2].divide(a[0]);

  720.         // Compute discriminant
  721.         final T d =  b.multiply(b).subtract(c);

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

  739. }