DSSTSolarRadiationPressure.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.geometry.euclidean.threed.Vector3D;
  19. import org.hipparchus.util.FastMath;
  20. import org.hipparchus.util.MathUtils;
  21. import org.hipparchus.util.Precision;
  22. import org.orekit.errors.OrekitException;
  23. import org.orekit.forces.radiation.IsotropicRadiationSingleCoefficient;
  24. import org.orekit.forces.radiation.RadiationSensitive;
  25. import org.orekit.forces.radiation.SolarRadiationPressure;
  26. import org.orekit.propagation.SpacecraftState;
  27. import org.orekit.propagation.events.EventDetector;
  28. import org.orekit.utils.ExtendedPVCoordinatesProvider;
  29. import org.orekit.utils.PVCoordinatesProvider;

  30. /** Solar radiation pressure contribution to the
  31.  *  {@link org.orekit.propagation.semianalytical.dsst.DSSTPropagator DSSTPropagator}.
  32.  *  <p>
  33.  *  The solar radiation pressure acceleration is computed through the
  34.  *  acceleration model of
  35.  *  {@link org.orekit.forces.radiation.SolarRadiationPressure SolarRadiationPressure}.
  36.  *  </p>
  37.  *
  38.  *  @author Pascal Parraud
  39.  */
  40. public class DSSTSolarRadiationPressure extends AbstractGaussianContribution {

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

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

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

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

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

  51.     /** Sun model. */
  52.     private final PVCoordinatesProvider sun;

  53.     /** Central Body radius. */
  54.     private final double                ae;

  55.     /** Spacecraft model for radiation acceleration computation. */
  56.     private final RadiationSensitive spacecraft;


  57.     /**
  58.      * Simple constructor with default reference values and spherical spacecraft.
  59.      * <p>
  60.      * When this constructor is used, the reference values are:
  61.      * </p>
  62.      * <ul>
  63.      * <li>d<sub>ref</sub> = 149597870000.0 m</li>
  64.      * <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
  65.      * </ul>
  66.      * <p>
  67.      * The spacecraft has a spherical shape.
  68.      * </p>
  69.      *
  70.      * @param cr satellite radiation pressure coefficient (assuming total specular reflection)
  71.      * @param area cross sectional area of satellite
  72.      * @param sun Sun model
  73.      * @param equatorialRadius central body equatorial radius (for shadow computation)
  74.      * @deprecated as of 9.2 replaced by {@link #DSSTSolarRadiationPressure(double, double,
  75.      * ExtendedPVCoordinatesProvider, double)}
  76.      */
  77.     @Deprecated
  78.     public DSSTSolarRadiationPressure(final double cr, final double area,
  79.                                       final PVCoordinatesProvider sun,
  80.                                       final double equatorialRadius) {
  81.         this(D_REF, P_REF, cr, area, sun, equatorialRadius);
  82.     }

  83.     /**
  84.      * Simple constructor with default reference values and spherical spacecraft.
  85.      * <p>
  86.      * When this constructor is used, the reference values are:
  87.      * </p>
  88.      * <ul>
  89.      * <li>d<sub>ref</sub> = 149597870000.0 m</li>
  90.      * <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
  91.      * </ul>
  92.      * <p>
  93.      * The spacecraft has a spherical shape.
  94.      * </p>
  95.      *
  96.      * @param cr satellite radiation pressure coefficient (assuming total specular reflection)
  97.      * @param area cross sectional area of satellite
  98.      * @param sun Sun model
  99.      * @param equatorialRadius central body equatorial radius (for shadow computation)
  100.      * @since 9.2
  101.      */
  102.     public DSSTSolarRadiationPressure(final double cr, final double area,
  103.                                       final ExtendedPVCoordinatesProvider sun,
  104.                                       final double equatorialRadius) {
  105.         this(D_REF, P_REF, cr, area, sun, equatorialRadius);
  106.     }

  107.     /**
  108.      * Simple constructor with default reference values, but custom spacecraft.
  109.      * <p>
  110.      * When this constructor is used, the reference values are:
  111.      * </p>
  112.      * <ul>
  113.      * <li>d<sub>ref</sub> = 149597870000.0 m</li>
  114.      * <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
  115.      * </ul>
  116.      *
  117.      * @param sun Sun model
  118.      * @param equatorialRadius central body equatorial radius (for shadow computation)
  119.      * @param spacecraft spacecraft model
  120.      * @deprecated as of 9.2 replaced by {{@link #DSSTSolarRadiationPressure(ExtendedPVCoordinatesProvider,
  121.      * double, RadiationSensitive)}
  122.      */
  123.     public DSSTSolarRadiationPressure(final PVCoordinatesProvider sun,
  124.                                       final double equatorialRadius,
  125.                                       final RadiationSensitive spacecraft) {
  126.         this(D_REF, P_REF, sun, equatorialRadius, spacecraft);
  127.     }

  128.     /**
  129.      * Simple constructor with default reference values, but custom spacecraft.
  130.      * <p>
  131.      * When this constructor is used, the reference values are:
  132.      * </p>
  133.      * <ul>
  134.      * <li>d<sub>ref</sub> = 149597870000.0 m</li>
  135.      * <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
  136.      * </ul>
  137.      *
  138.      * @param sun Sun model
  139.      * @param equatorialRadius central body equatorial radius (for shadow computation)
  140.      * @param spacecraft spacecraft model
  141.      * @since 9.2
  142.      */
  143.     public DSSTSolarRadiationPressure(final ExtendedPVCoordinatesProvider sun,
  144.                                       final double equatorialRadius,
  145.                                       final RadiationSensitive spacecraft) {
  146.         this(D_REF, P_REF, sun, equatorialRadius, spacecraft);
  147.     }

  148.     /**
  149.      * Constructor with customizable reference values but spherical spacecraft.
  150.      * <p>
  151.      * Note that reference solar radiation pressure <code>pRef</code> in N/m² is linked
  152.      * to solar flux SF in W/m² using formula pRef = SF/c where c is the speed of light
  153.      * (299792458 m/s). So at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
  154.      * N/m² solar radiation pressure.
  155.      * </p>
  156.      *
  157.      * @param dRef reference distance for the solar radiation pressure (m)
  158.      * @param pRef reference solar radiation pressure at dRef (N/m²)
  159.      * @param cr satellite radiation pressure coefficient (assuming total specular reflection)
  160.      * @param area cross sectional area of satellite
  161.      * @param sun Sun model
  162.      * @param equatorialRadius central body equatorial radius (for shadow computation)
  163.      * @deprecated as of 9.2 replaced by {@link #DSSTSolarRadiationPressure(double, double,
  164.      * double, double, ExtendedPVCoordinatesProvider, double)
  165.      */
  166.     @Deprecated
  167.     public DSSTSolarRadiationPressure(final double dRef, final double pRef,
  168.                                       final double cr, final double area,
  169.                                       final PVCoordinatesProvider sun,
  170.                                       final double equatorialRadius) {

  171.         // cR being the DSST SRP coef and assuming a spherical spacecraft,
  172.         // the conversion is:
  173.         // cR = 1 + (1 - kA) * (1 - kR) * 4 / 9
  174.         // with kA arbitrary sets to 0
  175.         this(dRef, pRef, sun, equatorialRadius, new IsotropicRadiationSingleCoefficient(area, cr));
  176.     }

  177.     /**
  178.      * Constructor with customizable reference values but spherical spacecraft.
  179.      * <p>
  180.      * Note that reference solar radiation pressure <code>pRef</code> in N/m² is linked
  181.      * to solar flux SF in W/m² using formula pRef = SF/c where c is the speed of light
  182.      * (299792458 m/s). So at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
  183.      * N/m² solar radiation pressure.
  184.      * </p>
  185.      *
  186.      * @param dRef reference distance for the solar radiation pressure (m)
  187.      * @param pRef reference solar radiation pressure at dRef (N/m²)
  188.      * @param cr satellite radiation pressure coefficient (assuming total specular reflection)
  189.      * @param area cross sectional area of satellite
  190.      * @param sun Sun model
  191.      * @param equatorialRadius central body equatorial radius (for shadow computation)
  192.      * @since 9.2
  193.      */
  194.     public DSSTSolarRadiationPressure(final double dRef, final double pRef,
  195.                                       final double cr, final double area,
  196.                                       final ExtendedPVCoordinatesProvider sun,
  197.                                       final double equatorialRadius) {

  198.         // cR being the DSST SRP coef and assuming a spherical spacecraft,
  199.         // the conversion is:
  200.         // cR = 1 + (1 - kA) * (1 - kR) * 4 / 9
  201.         // with kA arbitrary sets to 0
  202.         this(dRef, pRef, sun, equatorialRadius, new IsotropicRadiationSingleCoefficient(area, cr));
  203.     }

  204.     /**
  205.      * Complete constructor.
  206.      * <p>
  207.      * Note that reference solar radiation pressure <code>pRef</code> in N/m² is linked
  208.      * to solar flux SF in W/m² using formula pRef = SF/c where c is the speed of light
  209.      * (299792458 m/s). So at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
  210.      * N/m² solar radiation pressure.
  211.      * </p>
  212.      *
  213.      * @param dRef reference distance for the solar radiation pressure (m)
  214.      * @param pRef reference solar radiation pressure at dRef (N/m²)
  215.      * @param sun Sun model
  216.      * @param equatorialRadius central body equatorial radius (for shadow computation)
  217.      * @param spacecraft spacecraft model
  218.      * @deprecated as of 9.2 replaced by {@link #DSSTSolarRadiationPressure(double, double,
  219.      * ExtendedPVCoordinatesProvider, double, RadiationSensitive)
  220.      */
  221.     @Deprecated
  222.     public DSSTSolarRadiationPressure(final double dRef, final double pRef,
  223.                                       final PVCoordinatesProvider sun, final double equatorialRadius,
  224.                                       final RadiationSensitive spacecraft) {

  225.         //Call to the constructor from superclass using the numerical SRP model as ForceModel
  226.         super(PREFIX, GAUSS_THRESHOLD,
  227.               new SolarRadiationPressure(dRef, pRef, sun, equatorialRadius, spacecraft));

  228.         this.sun  = sun;
  229.         this.ae   = equatorialRadius;
  230.         this.spacecraft = spacecraft;
  231.     }

  232.     /**
  233.      * Complete constructor.
  234.      * <p>
  235.      * Note that reference solar radiation pressure <code>pRef</code> in N/m² is linked
  236.      * to solar flux SF in W/m² using formula pRef = SF/c where c is the speed of light
  237.      * (299792458 m/s). So at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
  238.      * N/m² solar radiation pressure.
  239.      * </p>
  240.      *
  241.      * @param dRef reference distance for the solar radiation pressure (m)
  242.      * @param pRef reference solar radiation pressure at dRef (N/m²)
  243.      * @param sun Sun model
  244.      * @param equatorialRadius central body equatorial radius (for shadow computation)
  245.      * @param spacecraft spacecraft model
  246.      * @since 9.2
  247.      */
  248.     public DSSTSolarRadiationPressure(final double dRef, final double pRef,
  249.                                       final ExtendedPVCoordinatesProvider sun,
  250.                                       final double equatorialRadius,
  251.                                       final RadiationSensitive spacecraft) {

  252.         //Call to the constructor from superclass using the numerical SRP model as ForceModel
  253.         super(PREFIX, GAUSS_THRESHOLD,
  254.               new SolarRadiationPressure(dRef, pRef, sun, equatorialRadius, spacecraft));

  255.         this.sun  = sun;
  256.         this.ae   = equatorialRadius;
  257.         this.spacecraft = spacecraft;
  258.     }

  259.     /** Get spacecraft shape.
  260.      * @return the spacecraft shape.
  261.      */
  262.     public RadiationSensitive getSpacecraft() {
  263.         return spacecraft;
  264.     }

  265.     /** {@inheritDoc} */
  266.     public EventDetector[] getEventsDetectors() {
  267.         return null;
  268.     }

  269.     /** {@inheritDoc} */
  270.     protected double[] getLLimits(final SpacecraftState state) throws OrekitException {
  271.         // Default bounds without shadow [-PI, PI]
  272.         final double[] ll = {-FastMath.PI + MathUtils.normalizeAngle(state.getLv(), 0),
  273.                              FastMath.PI + MathUtils.normalizeAngle(state.getLv(), 0)};

  274.         // Direction cosines of the Sun in the equinoctial frame
  275.         final Vector3D sunDir = sun.getPVCoordinates(state.getDate(), state.getFrame()).getPosition().normalize();
  276.         final double alpha = sunDir.dotProduct(f);
  277.         final double beta  = sunDir.dotProduct(g);
  278.         final double gamma = sunDir.dotProduct(w);

  279.         // Compute limits only if the perigee is close enough from the central body to be in the shadow
  280.         if (FastMath.abs(gamma * a * (1. - ecc)) < ae) {

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

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

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

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

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

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

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

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

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

  425.     /**
  426.      * Compute the real roots of a cubic equation.
  427.      *
  428.      * <pre>
  429.      * a[0] * x³ + a[1] * x² + a[2] * x + a[3] = 0.
  430.      * </pre>
  431.      *
  432.      * @param a the 4 coefficients
  433.      * @param y the real roots sorted in descending order
  434.      * @return the number of real roots
  435.      */
  436.     private int realCubicRoots(final double[] a, final double[] y) {
  437.         if (Precision.equals(a[0], 0.)) {
  438.             // Treat the degenerate cubic as quadratic
  439.             final double[] aa = new double[a.length - 1];
  440.             System.arraycopy(a, 1, aa, 0, aa.length);
  441.             return realQuadraticRoots(aa, y);
  442.         }

  443.         // Transform coefficients
  444.         final double b  = -a[1] / (3. * a[0]);
  445.         final double c  =  a[2] / a[0];
  446.         final double d  =  a[3] / a[0];
  447.         final double b2 =  b * b;
  448.         final double p  =  b2 - c / 3.;
  449.         final double q  =  b * (b2 - c * 0.5) - d * 0.5;

  450.         // Compute discriminant
  451.         final double disc = p * p * p - q * q;

  452.         if (disc < 0.) {
  453.             // One real root
  454.             final double alpha  = q + FastMath.copySign(FastMath.sqrt(-disc), q);
  455.             final double cbrtAl = FastMath.cbrt(alpha);
  456.             final double cbrtBe = p / cbrtAl;

  457.             if (p < 0.) {
  458.                 y[0] = b + 2. * q / (cbrtAl * cbrtAl + cbrtBe * cbrtBe - p);
  459.             } else if (p > 0.) {
  460.                 y[0] = b + cbrtAl + cbrtBe;
  461.             } else {
  462.                 y[0] = b + cbrtAl;
  463.             }

  464.             return 1;

  465.         } else if (disc > 0.) {
  466.             // Three distinct real roots
  467.             final double phi = FastMath.atan2(FastMath.sqrt(disc), q) / 3.;
  468.             final double sqP = 2.0 * FastMath.sqrt(p);

  469.             y[0] = b + sqP * FastMath.cos(phi);
  470.             y[1] = b - sqP * FastMath.cos(FastMath.PI / 3. + phi);
  471.             y[2] = b - sqP * FastMath.cos(FastMath.PI / 3. - phi);

  472.             return 3;

  473.         } else {
  474.             // One distinct and two equals real roots
  475.             final double cbrtQ = FastMath.cbrt(q);
  476.             final double root1 = b + 2. * cbrtQ;
  477.             final double root2 = b - cbrtQ;
  478.             if (q < 0.) {
  479.                 y[0] = root2;
  480.                 y[1] = root2;
  481.                 y[2] = root1;
  482.             } else {
  483.                 y[0] = root1;
  484.                 y[1] = root2;
  485.                 y[2] = root2;
  486.             }

  487.             return 3;
  488.         }
  489.     }

  490.     /**
  491.      * Compute the real roots of a quadratic equation.
  492.      *
  493.      * <pre>
  494.      * a[0] * x² + a[1] * x + a[2] = 0.
  495.      * </pre>
  496.      *
  497.      * @param a the 3 coefficients
  498.      * @param y the real roots sorted in descending order
  499.      * @return the number of real roots
  500.      */
  501.     private int realQuadraticRoots(final double[] a, final double[] y) {
  502.         if (Precision.equals(a[0], 0.)) {
  503.             // Degenerate quadratic
  504.             if (Precision.equals(a[1], 0.)) {
  505.                 // Degenerate linear equation: no real roots
  506.                 return 0;
  507.             }
  508.             // Linear equation: one real root
  509.             y[0] = -a[2] / a[1];
  510.             return 1;
  511.         }

  512.         // Transform coefficients
  513.         final double b = -0.5 * a[1] / a[0];
  514.         final double c =  a[2] / a[0];

  515.         // Compute discriminant
  516.         final double d =  b * b - c;

  517.         if (d < 0.) {
  518.             // No real roots
  519.             return 0;
  520.         } else if (d > 0.) {
  521.             // Two distinct real roots
  522.             final double y0 = b + FastMath.copySign(FastMath.sqrt(d), b);
  523.             final double y1 = c / y0;
  524.             y[0] = FastMath.max(y0, y1);
  525.             y[1] = FastMath.min(y0, y1);
  526.             return 2;
  527.         } else {
  528.             // Discriminant is zero: two equal real roots
  529.             y[0] = b;
  530.             y[1] = b;
  531.             return 2;
  532.         }
  533.     }
  534. }