1   /* Copyright 2002-2015 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.forces.radiation;
18  
19  import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
20  import org.apache.commons.math3.geometry.euclidean.threed.FieldRotation;
21  import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
22  import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
23  import org.apache.commons.math3.ode.AbstractParameterizable;
24  import org.apache.commons.math3.util.FastMath;
25  import org.orekit.errors.OrekitException;
26  import org.orekit.errors.OrekitMessages;
27  import org.orekit.forces.ForceModel;
28  import org.orekit.frames.Frame;
29  import org.orekit.propagation.SpacecraftState;
30  import org.orekit.propagation.events.AbstractDetector;
31  import org.orekit.propagation.events.EventDetector;
32  import org.orekit.propagation.events.handlers.EventHandler;
33  import org.orekit.propagation.numerical.TimeDerivativesEquations;
34  import org.orekit.time.AbsoluteDate;
35  import org.orekit.utils.Constants;
36  import org.orekit.utils.PVCoordinatesProvider;
37  
38  /** Solar radiation pressure force model.
39   *
40   * @author Fabien Maussion
41   * @author Édouard Delente
42   * @author Véronique Pommier-Maurussane
43   * @author Pascal Parraud
44   */
45  public class SolarRadiationPressure extends AbstractParameterizable implements ForceModel {
46  
47      /** Reference distance for the solar radiation pressure (m). */
48      private static final double D_REF = 149597870000.0;
49  
50      /** Reference solar radiation pressure at D_REF (N/m²). */
51      private static final double P_REF = 4.56e-6;
52  
53      /** Reference flux normalized for a 1m distance (N). */
54      private final double kRef;
55  
56      /** Sun model. */
57      private final PVCoordinatesProvider sun;
58  
59      /** Earth model. */
60      private final double equatorialRadius;
61  
62      /** Spacecraft. */
63      private final RadiationSensitive spacecraft;
64  
65      /** Simple constructor with default reference values.
66       * <p>When this constructor is used, the reference values are:</p>
67       * <ul>
68       *   <li>d<sub>ref</sub> = 149597870000.0 m</li>
69       *   <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
70       * </ul>
71       * @param sun Sun model
72       * @param equatorialRadius spherical shape model (for umbra/penumbra computation)
73       * @param spacecraft the object physical and geometrical information
74       */
75      public SolarRadiationPressure(final PVCoordinatesProvider sun, final double equatorialRadius,
76                                    final RadiationSensitive spacecraft) {
77          this(D_REF, P_REF, sun, equatorialRadius, spacecraft);
78      }
79  
80      /** Complete constructor.
81       * <p>Note that reference solar radiation pressure <code>pRef</code> in
82       * N/m² is linked to solar flux SF in W/m² using
83       * formula pRef = SF/c where c is the speed of light (299792458 m/s). So
84       * at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
85       * N/m² solar radiation pressure.</p>
86       * @param dRef reference distance for the solar radiation pressure (m)
87       * @param pRef reference solar radiation pressure at dRef (N/m²)
88       * @param sun Sun model
89       * @param equatorialRadius spherical shape model (for umbra/penumbra computation)
90       * @param spacecraft the object physical and geometrical information
91       */
92      public SolarRadiationPressure(final double dRef, final double pRef,
93                                    final PVCoordinatesProvider sun,
94                                    final double equatorialRadius,
95                                    final RadiationSensitive spacecraft) {
96          super(RadiationSensitive.ABSORPTION_COEFFICIENT, RadiationSensitive.REFLECTION_COEFFICIENT);
97          this.kRef = pRef * dRef * dRef;
98          this.sun  = sun;
99          this.equatorialRadius = equatorialRadius;
100         this.spacecraft = spacecraft;
101     }
102 
103     /** {@inheritDoc} */
104     public void addContribution(final SpacecraftState s, final TimeDerivativesEquations adder)
105         throws OrekitException {
106 
107         final AbsoluteDate date         = s.getDate();
108         final Frame        frame        = s.getFrame();
109         final Vector3D     position     = s.getPVCoordinates().getPosition();
110         final Vector3D     sunSatVector = position.subtract(sun.getPVCoordinates(date, frame).getPosition());
111         final double       r2           = sunSatVector.getNormSq();
112 
113         // compute flux
114         final double   rawP = kRef * getLightningRatio(position, frame, date) / r2;
115         final Vector3D flux = new Vector3D(rawP / FastMath.sqrt(r2), sunSatVector);
116 
117         final Vector3D acceleration = spacecraft.radiationPressureAcceleration(date, frame, position, s.getAttitude().getRotation(),
118                                                                                s.getMass(), flux);
119 
120         // provide the perturbing acceleration to the derivatives adder
121         adder.addAcceleration(acceleration, s.getFrame());
122 
123     }
124 
125     /** Get the lightning ratio ([0-1]).
126      * @param position the satellite's position in the selected frame.
127      * @param frame in which is defined the position
128      * @param date the date
129      * @return lightning ratio
130      * @exception OrekitException if the trajectory is inside the Earth
131      */
132     public double getLightningRatio(final Vector3D position, final Frame frame, final AbsoluteDate date)
133         throws OrekitException {
134 
135         // Compute useful angles
136         final double[] angle = getEclipseAngles(position, frame, date);
137 
138         // Sat-Sun / Sat-CentralBody angle
139         final double sunEarthAngle = angle[0];
140 
141         // Central Body apparent radius
142         final double alphaCentral = angle[1];
143 
144         // Sun apparent radius
145         final double alphaSun = angle[2];
146 
147         double result = 1.0;
148 
149         // Is the satellite in complete umbra ?
150         if (sunEarthAngle - alphaCentral + alphaSun <= 0.0) {
151             result = 0.0;
152         } else if (sunEarthAngle - alphaCentral - alphaSun < 0.0) {
153             // Compute a lightning ratio in penumbra
154             final double sEA2    = sunEarthAngle * sunEarthAngle;
155             final double oo2sEA  = 1.0 / (2. * sunEarthAngle);
156             final double aS2     = alphaSun * alphaSun;
157             final double aE2     = alphaCentral * alphaCentral;
158             final double aE2maS2 = aE2 - aS2;
159 
160             final double alpha1  = (sEA2 - aE2maS2) * oo2sEA;
161             final double alpha2  = (sEA2 + aE2maS2) * oo2sEA;
162 
163             // Protection against numerical inaccuracy at boundaries
164             final double a1oaS   = FastMath.min(1.0, FastMath.max(-1.0, alpha1 / alphaSun));
165             final double aS2ma12 = FastMath.max(0.0, aS2 - alpha1 * alpha1);
166             final double a2oaE   = FastMath.min(1.0, FastMath.max(-1.0, alpha2 / alphaCentral));
167             final double aE2ma22 = FastMath.max(0.0, aE2 - alpha2 * alpha2);
168 
169             final double P1 = aS2 * FastMath.acos(a1oaS) - alpha1 * FastMath.sqrt(aS2ma12);
170             final double P2 = aE2 * FastMath.acos(a2oaE) - alpha2 * FastMath.sqrt(aE2ma22);
171 
172             result = 1. - (P1 + P2) / (FastMath.PI * aS2);
173         }
174 
175         return result;
176     }
177 
178     /** {@inheritDoc} */
179     public EventDetector[] getEventsDetectors() {
180         return new EventDetector[] {
181             new UmbraDetector(), new PenumbraDetector()
182         };
183     }
184 
185     /** {@inheritDoc} */
186     public FieldVector3D<DerivativeStructure> accelerationDerivatives(final AbsoluteDate date, final Frame frame,
187                                               final FieldVector3D<DerivativeStructure> position, final FieldVector3D<DerivativeStructure> velocity,
188                                               final FieldRotation<DerivativeStructure> rotation, final DerivativeStructure mass)
189         throws OrekitException {
190 
191         final FieldVector3D<DerivativeStructure> sunSatVector = position.subtract(sun.getPVCoordinates(date, frame).getPosition());
192         final DerivativeStructure r2  = sunSatVector.getNormSq();
193 
194         // compute flux
195         final double ratio = getLightningRatio(position.toVector3D(), frame, date);
196         final DerivativeStructure rawP = r2.reciprocal().multiply(kRef * ratio);
197         final FieldVector3D<DerivativeStructure> flux = new FieldVector3D<DerivativeStructure>(rawP.divide(r2.sqrt()), sunSatVector);
198 
199         // compute acceleration with all its partial derivatives
200         return spacecraft.radiationPressureAcceleration(date, frame, position, rotation, mass, flux);
201 
202     }
203 
204     /** {@inheritDoc} */
205     public FieldVector3D<DerivativeStructure> accelerationDerivatives(final SpacecraftState s, final String paramName)
206         throws OrekitException {
207 
208         complainIfNotSupported(paramName);
209         final AbsoluteDate date         = s.getDate();
210         final Frame        frame        = s.getFrame();
211         final Vector3D     position     = s.getPVCoordinates().getPosition();
212         final Vector3D     sunSatVector = position.subtract(sun.getPVCoordinates(date, frame).getPosition());
213         final double       r2           = sunSatVector.getNormSq();
214 
215         // compute flux
216         final double   rawP = kRef * getLightningRatio(position, frame, date) / r2;
217         final Vector3D flux = new Vector3D(rawP / FastMath.sqrt(r2), sunSatVector);
218 
219         return spacecraft.radiationPressureAcceleration(date, frame, position, s.getAttitude().getRotation(),
220                                                         s.getMass(), flux, paramName);
221 
222     }
223 
224     /** {@inheritDoc} */
225     public double getParameter(final String name)
226         throws IllegalArgumentException {
227         complainIfNotSupported(name);
228         if (name.equals(RadiationSensitive.ABSORPTION_COEFFICIENT)) {
229             return spacecraft.getAbsorptionCoefficient();
230         }
231         return spacecraft.getReflectionCoefficient();
232     }
233 
234     /** {@inheritDoc} */
235     public void setParameter(final String name, final double value)
236         throws IllegalArgumentException {
237         complainIfNotSupported(name);
238         if (name.equals(RadiationSensitive.ABSORPTION_COEFFICIENT)) {
239             spacecraft.setAbsorptionCoefficient(value);
240         } else {
241             spacecraft.setReflectionCoefficient(value);
242         }
243     }
244 
245     /** Get the useful angles for eclipse computation.
246      * @param position the satellite's position in the selected frame.
247      * @param frame in which is defined the position
248      * @param date the date
249      * @return the 3 angles {(satCentral, satSun), Central body apparent radius, Sun apparent radius}
250      * @exception OrekitException if the trajectory is inside the Earth
251      */
252     private double[] getEclipseAngles(final Vector3D position,
253                                       final Frame frame,
254                                       final AbsoluteDate date)
255         throws OrekitException {
256         final double[] angle = new double[3];
257 
258         final Vector3D satSunVector = sun.getPVCoordinates(date, frame).getPosition().subtract(position);
259 
260         // Sat-Sun / Sat-CentralBody angle
261         angle[0] = Vector3D.angle(satSunVector, position.negate());
262 
263         // Central body apparent radius
264         final double r = position.getNorm();
265         if (r <= equatorialRadius) {
266             throw new OrekitException(OrekitMessages.TRAJECTORY_INSIDE_BRILLOUIN_SPHERE, r);
267         }
268         angle[1] = FastMath.asin(equatorialRadius / r);
269 
270         // Sun apparent radius
271         angle[2] = FastMath.asin(Constants.SUN_RADIUS / satSunVector.getNorm());
272 
273         return angle;
274     }
275 
276     /** This class defines the umbra entry/exit detector. */
277     private class UmbraDetector extends AbstractDetector<UmbraDetector> {
278 
279         /** Serializable UID. */
280         private static final long serialVersionUID = 20141228L;
281 
282         /** Build a new instance. */
283         public UmbraDetector() {
284             super(60.0, 1.0e-3, DEFAULT_MAX_ITER, new EventHandler<UmbraDetector>() {
285 
286                 /** {@inheritDoc} */
287                 public Action eventOccurred(final SpacecraftState s, final UmbraDetector detector,
288                                             final boolean increasing) {
289                     return Action.RESET_DERIVATIVES;
290                 }
291 
292                 /** {@inheritDoc} */
293                 @Override
294                 public SpacecraftState resetState(final UmbraDetector detector, final SpacecraftState oldState) {
295                     return oldState;
296                 }
297 
298             });
299         }
300 
301         /** Private constructor with full parameters.
302          * <p>
303          * This constructor is private as users are expected to use the builder
304          * API with the various {@code withXxx()} methods to set up the instance
305          * in a readable manner without using a huge amount of parameters.
306          * </p>
307          * @param maxCheck maximum checking interval (s)
308          * @param threshold convergence threshold (s)
309          * @param maxIter maximum number of iterations in the event time search
310          * @param handler event handler to call at event occurrences
311          * @since 6.1
312          */
313         private UmbraDetector(final double maxCheck, final double threshold,
314                               final int maxIter, final EventHandler<UmbraDetector> handler) {
315             super(maxCheck, threshold, maxIter, handler);
316         }
317 
318         /** {@inheritDoc} */
319         @Override
320         protected UmbraDetector create(final double newMaxCheck, final double newThreshold,
321                                        final int newMaxIter, final EventHandler<UmbraDetector> newHandler) {
322             return new UmbraDetector(newMaxCheck, newThreshold, newMaxIter, newHandler);
323         }
324 
325         /** The G-function is the difference between the Sat-Sun-Sat-Earth angle and
326          * the Earth's apparent radius.
327          * @param s the current state information : date, kinematics, attitude
328          * @return value of the g function
329          * @exception OrekitException if sun or spacecraft position cannot be computed
330          */
331         public double g(final SpacecraftState s) throws OrekitException {
332             final double[] angle = getEclipseAngles(s.getPVCoordinates().getPosition(),
333                                                     s.getFrame(), s.getDate());
334             return angle[0] - angle[1] + angle[2];
335         }
336 
337     }
338 
339     /** This class defines the penumbra entry/exit detector. */
340     private class PenumbraDetector extends AbstractDetector<PenumbraDetector> {
341 
342         /** Serializable UID. */
343         private static final long serialVersionUID = 20141228L;
344 
345         /** Build a new instance. */
346         public PenumbraDetector() {
347             super(60.0, 1.0e-3, DEFAULT_MAX_ITER, new EventHandler<PenumbraDetector>() {
348 
349                 /** {@inheritDoc} */
350                 public Action eventOccurred(final SpacecraftState s, final PenumbraDetector detector,
351                                             final boolean increasing) {
352                     return Action.RESET_DERIVATIVES;
353                 }
354 
355                 /** {@inheritDoc} */
356                 @Override
357                 public SpacecraftState resetState(final PenumbraDetector detector, final SpacecraftState oldState) {
358                     return oldState;
359                 }
360 
361             });
362         }
363 
364         /** Private constructor with full parameters.
365          * <p>
366          * This constructor is private as users are expected to use the builder
367          * API with the various {@code withXxx()} methods to set up the instance
368          * in a readable manner without using a huge amount of parameters.
369          * </p>
370          * @param maxCheck maximum checking interval (s)
371          * @param threshold convergence threshold (s)
372          * @param maxIter maximum number of iterations in the event time search
373          * @param handler event handler to call at event occurrences
374          * @since 6.1
375          */
376         private PenumbraDetector(final double maxCheck, final double threshold,
377                                  final int maxIter, final EventHandler<PenumbraDetector> handler) {
378             super(maxCheck, threshold, maxIter, handler);
379         }
380 
381         /** {@inheritDoc} */
382         @Override
383         protected PenumbraDetector create(final double newMaxCheck, final double newThreshold,
384                                           final int newMaxIter, final EventHandler<PenumbraDetector> newHandler) {
385             return new PenumbraDetector(newMaxCheck, newThreshold, newMaxIter, newHandler);
386         }
387 
388         /** The G-function is the difference between the Sat-Sun-Sat-Earth angle and
389          * the sum of the Earth's and Sun's apparent radius.
390          * @param s the current state information : date, kinematics, attitude
391          * @return value of the g function
392          * @exception OrekitException if sun or spacecraft position cannot be computed
393          */
394         public double g(final SpacecraftState s) throws OrekitException {
395             final double[] angle = getEclipseAngles(s.getPVCoordinates().getPosition(),
396                                                     s.getFrame(), s.getDate());
397             return angle[0] - angle[1] - angle[2];
398         }
399 
400     }
401 
402 }