1   /* Copyright 2022-2025 Luc Maisonobe
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  
18  package org.orekit.forces.maneuvers.propulsion;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.stream.Stream;
24  
25  import org.hipparchus.CalculusFieldElement;
26  import org.hipparchus.Field;
27  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.orekit.forces.maneuvers.Control3DVectorCostType;
30  import org.orekit.propagation.FieldSpacecraftState;
31  import org.orekit.propagation.SpacecraftState;
32  import org.orekit.propagation.events.DateDetector;
33  import org.orekit.propagation.events.EventDetector;
34  import org.orekit.propagation.events.FieldDateDetector;
35  import org.orekit.propagation.events.FieldEventDetector;
36  import org.orekit.time.AbsoluteDate;
37  import org.orekit.time.TimeStamped;
38  import org.orekit.utils.ParameterDriver;
39  import org.orekit.utils.TimeSpanMap;
40  
41  /** Thrust propulsion model based on segmented profile.
42   * @author Luc Maisonobe
43   * @since 12.0
44   */
45  public class ProfileThrustPropulsionModel implements ThrustPropulsionModel {
46  
47      /** Thrust profile. */
48      private final TimeSpanMap<ThrustVectorProvider> profile;
49  
50      /** Specific impulse. */
51      private final double isp;
52  
53      /** Name of the maneuver. */
54      private final String name;
55  
56      /** Type of norm linking thrust vector to mass flow rate. */
57      private final Control3DVectorCostType control3DVectorCostType;
58  
59      /** Constructor with default cost type.
60       * @param profile thrust profile (N)
61       * @param isp specific impulse (s)
62       * @param name name of the maneuver
63       * @since 13.0
64       */
65      public ProfileThrustPropulsionModel(final TimeSpanMap<ThrustVectorProvider> profile, final double isp,
66                                          final String name) {
67          this(profile, isp, Control3DVectorCostType.TWO_NORM, name);
68      }
69  
70      /** Generic constructor.
71       * @param profile thrust profile (N)
72       * @param isp specific impulse (s)
73       * @param control3DVectorCostType control vector's cost type
74       * @param name name of the maneuver
75       * @since 13.0
76       */
77      public ProfileThrustPropulsionModel(final TimeSpanMap<ThrustVectorProvider> profile, final double isp,
78                                          final Control3DVectorCostType control3DVectorCostType, final String name) {
79          this.name    = name;
80          this.isp     = isp;
81          this.profile = profile;
82          this.control3DVectorCostType = control3DVectorCostType;
83      }
84  
85      /** Build with customized profile.
86       * @param profile thrust profile (N)
87       * @param isp specific impulse (s)
88       * @param name name of the maneuver
89       * @param control3DVectorCostType control vector's cost type
90       * @param <T> segment type
91       * @return propulsion model
92       * @since 13.0
93       */
94      public static <T extends ThrustVectorProvider> ProfileThrustPropulsionModel of(final TimeSpanMap<T> profile,
95                                                                                     final double isp,
96                                                                                     final Control3DVectorCostType control3DVectorCostType,
97                                                                                     final String name) {
98          return new ProfileThrustPropulsionModel(buildSegments(profile), isp, control3DVectorCostType, name);
99      }
100 
101     /**
102      * Method building a map of generic segments from customized ones.
103      * @param timeSpanMap input profile
104      * @param <T> segment type
105      * @return generic profile
106      * @since 13.0
107      */
108     private static <T extends ThrustVectorProvider> TimeSpanMap<ThrustVectorProvider> buildSegments(final TimeSpanMap<T> timeSpanMap) {
109         TimeSpanMap.Span<T> span = timeSpanMap.getFirstSpan();
110         final TimeSpanMap<ThrustVectorProvider> segments = new TimeSpanMap<>(null);
111         while (span != null) {
112             segments.addValidBetween(span.getData(), span.getStart(), span.getEnd());
113             span = span.next();
114         }
115         return segments;
116     }
117 
118     /** {@inheritDoc} */
119     @Override
120     public String getName() {
121         return name;
122     }
123 
124     /** {@inheritDoc} */
125     @Override
126     public Control3DVectorCostType getControl3DVectorCostType() {
127         return control3DVectorCostType;
128     }
129 
130     /**
131      * Getter for active provider at input date.
132      * @param date date
133      * @return active segment
134      * @since 13.0
135      */
136     public ThrustVectorProvider getActiveProvider(final AbsoluteDate date) {
137         return profile.get(date);
138     }
139 
140     /** {@inheritDoc} */
141     @Override
142     public Vector3D getThrustVector(final SpacecraftState s) {
143         return getThrustVector(s, getParameters());
144     }
145 
146     /** {@inheritDoc} */
147     @Override
148     public double getFlowRate(final SpacecraftState s) {
149         return getFlowRate(s, getParameters());
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public double getFlowRate(final SpacecraftState s, final double[] parameters) {
155         return -control3DVectorCostType.evaluate(getThrustVector(s, parameters)) / ThrustPropulsionModel.getExhaustVelocity(isp);
156     }
157 
158     /** {@inheritDoc} */
159     @Override
160     public Vector3D getThrustVector(final SpacecraftState s, final double[] parameters) {
161         final ThrustVectorProvider active = getActiveProvider(s.getDate());
162         return active == null ? Vector3D.ZERO : active.getThrustVector(s.getDate(), s.getMass());
163     }
164 
165     /** {@inheritDoc} */
166     public <T extends CalculusFieldElement<T>> FieldVector3D<T> getThrustVector(final FieldSpacecraftState<T> s,
167                                                                                 final T[] parameters) {
168         final ThrustVectorProvider active = getActiveProvider(s.getDate().toAbsoluteDate());
169         return active == null ? FieldVector3D.getZero(s.getDate().getField()) :
170                 active.getThrustVector(s.getDate(), s.getMass());
171     }
172 
173     /** {@inheritDoc} */
174     public <T extends CalculusFieldElement<T>> T getFlowRate(final FieldSpacecraftState<T> s, final T[] parameters) {
175         return control3DVectorCostType.evaluate(getThrustVector(s, parameters)).divide(-ThrustPropulsionModel.getExhaustVelocity(isp));
176     }
177 
178     /** {@inheritDoc}.
179      * <p>
180      * The single detector returned triggers {@link org.hipparchus.ode.events.Action#RESET_DERIVATIVES} events
181      * at every {@link ThrustVectorProvider} boundaries.
182      * </p>
183      */
184     @Override
185     public Stream<EventDetector> getEventDetectors() {
186 
187         final List<AbsoluteDate> transitionDates = new ArrayList<>();
188         for (TimeSpanMap.Transition<ThrustVectorProvider> transition = profile.getFirstTransition();
189              transition != null;
190              transition = transition.next()) {
191             transitionDates.add(transition.getDate());
192         }
193         final DateDetector detector = getDateDetector(transitionDates.toArray(new TimeStamped[0]));
194         return Stream.of(detector);
195     }
196 
197     /** {@inheritDoc}.
198      * <p>
199      * The single detector returned triggers {@link org.hipparchus.ode.events.Action#RESET_DERIVATIVES} events
200      * at every {@link ThrustVectorProvider} boundaries.
201      * </p>
202      */
203     @Override
204     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
205         final List<AbsoluteDate> transitionDates = new ArrayList<>();
206         for (TimeSpanMap.Transition<ThrustVectorProvider> transition = profile.getFirstTransition();
207              transition != null;
208              transition = transition.next()) {
209             transitionDates.add(transition.getDate());
210         }
211         final FieldDateDetector<T> detector = getFieldDateDetector(field, transitionDates.toArray(new AbsoluteDate[0]));
212         return Stream.of(detector);
213     }
214 
215     /** {@inheritDoc} */
216     @Override
217     public List<ParameterDriver> getParametersDrivers() {
218         return Collections.emptyList();
219     }
220 }