1   /* Copyright 2002-2026 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.utils.drivers;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.hipparchus.analysis.differentiation.Gradient;
26  import org.hipparchus.exception.LocalizedCoreFormats;
27  import org.hipparchus.util.FastMath;
28  import org.hipparchus.util.Precision;
29  import org.orekit.errors.OrekitException;
30  import org.orekit.errors.OrekitMessages;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.time.TimeInterval;
33  
34  /** Class allowing to drive the value of a parameter.
35   * <p>
36   * This class is typically used as a bridge between an estimation algorithm
37   * (typically orbit determination or optimizer) and an internal parameter in
38   * a physical model that needs to be tuned. The physical model will expose to
39   * the algorithm a set of instances of this class so the algorithm can call the
40   * {@link #setValue(double)} method to update the parameter value.
41   * </p>
42   * <p>
43   * Any object can be notified when any of value, name, selection status… are changed.
44   * This is done by {@link #addObserver(ParameterObserver)}  registering} a
45   * {@link ParameterObserver ParameterObserver} to the parameter driver.
46   * <p>
47   * This design has two major goals. First, it allows an external algorithm to drive
48   * internal parameters blindly, as it only needs to get a list of instances of this
49   * class, without knowing what they really drive. Second, it allows the physical
50   * model to not expose directly setters methods for its parameters. In order to be
51   * able to modify the parameter value, the algorithm <em>must</em> retrieve a
52   * parameter driver.
53   * </p>
54   * <p>
55   * As of versions 12.X and 13.X, it was possible to set up time-dependent values
56   * within a single {@code ParameterDriver}. This feature has been replaced by
57   * {@link ParameterDriversSequence} as of 14.0, which is simpler and also allows
58   * finer selection, making it possible to select only a subset of the parameters
59   * along a timeline. Starting with version 14.0, {@code ParameterDriver} instances
60   * only hold one value, which can have a restricted validity range.
61   * </p>
62   * @see ParameterObserver
63   * @author Luc Maisonobe
64   * @author Melina Vanel
65   * @since 8.0
66   */
67  public class ParameterDriver {
68  
69      /** Name of the parameter. */
70      private String name;
71  
72      /** Reference value. */
73      private double referenceValue;
74  
75      /** Scaling factor. */
76      private double scale;
77  
78      /** Current value.
79       * @since 14.0
80       */
81      private double value;
82  
83      /** Minimum value. */
84      private double minValue;
85  
86      /** Maximum value. */
87      private double maxValue;
88  
89      /** Reference date.
90       * @since 9.0
91       */
92      private AbsoluteDate referenceDate;
93  
94      /** Validity interval.
95       * @since 14.0
96       */
97      private TimeInterval validity;
98  
99      /** Selection status.
100      * <p>
101      * Selection is used for estimated parameters in orbit determination,
102      * or to compute the Jacobian matrix in partial derivatives computation.
103      * </p>
104      */
105     private boolean selected;
106 
107     /** Observers observing this driver. */
108     private final List<ParameterObserver> observers;
109 
110     /**
111      * Simple constructor.
112      * <p>
113      * At construction, the parameter is configured as <em>not</em> selected, the reference date is set to {@code null},
114      * the value is set to the {@code referenceValue}.
115      * </p>
116      * @param name           name of the parameter
117      * @param referenceValue reference value of the parameter
118      * @param scale          scaling factor to convert the parameters value to non-dimensional (typically set to the
119      *                       expected standard deviation of the parameter), it must be non-zero
120      * @param minValue       minimum value allowed
121      * @param maxValue       maximum value allowed
122      * @param validity       validity interval
123      */
124     public ParameterDriver(final String name,
125                            final double referenceValue, final double scale,
126                            final double minValue, final double maxValue,
127                            final TimeInterval validity) {
128 
129         if (FastMath.abs(scale) <= Precision.SAFE_MIN) {
130             throw new OrekitException(OrekitMessages.TOO_SMALL_SCALE_FOR_PARAMETER, name, scale);
131         }
132 
133         this.name           = name;
134         this.referenceValue = referenceValue;
135         this.scale          = scale;
136         this.value          = referenceValue;
137         this.minValue       = minValue;
138         this.maxValue       = maxValue;
139         this.validity       = validity;
140         this.referenceDate  = null;
141         this.selected       = false;
142         this.observers      = new ArrayList<>();
143 
144     }
145 
146     /** Add an observer for this driver.
147      * <p>
148      * The observer {@link ParameterObserver#valueChanged(double, ParameterDriver)
149      * valueChanged} method is called once automatically when the
150      * observer is added, and then called at each value change.
151      * </p>
152      * @param observer observer to add
153      */
154     public void addObserver(final ParameterObserver observer) {
155         observers.add(observer);
156     }
157 
158     /** Remove an observer.
159      * @param observer observer to remove
160      * @since 9.1
161      */
162     public void removeObserver(final ParameterObserver observer) {
163         for (final Iterator<ParameterObserver> iterator = observers.iterator(); iterator.hasNext();) {
164             if (iterator.next() == observer) {
165                 iterator.remove();
166                 return;
167             }
168         }
169     }
170 
171     /** Replace an observer.
172      * @param oldObserver observer to replace
173      * @param newObserver new observer to use
174      * @since 10.1
175      */
176     public void replaceObserver(final ParameterObserver oldObserver, final ParameterObserver newObserver) {
177         for (int i = 0; i < observers.size(); ++i) {
178             if (observers.get(i) == oldObserver) {
179                 observers.set(i, newObserver);
180             }
181         }
182     }
183 
184     /** Get the observers for this driver.
185      * @return an unmodifiable view of the observers for this driver
186      * @since 9.1
187      */
188     public List<ParameterObserver> getObservers() {
189         return Collections.unmodifiableList(observers);
190     }
191 
192     /** Get parameter driver general name.
193      * @return name
194      */
195     public String getName() {
196         return name;
197     }
198 
199     /** Change the general name of this parameter driver.
200      * @param name new name
201      */
202     public void setName(final String name) {
203         final String previousName = this.name;
204         this.name = name;
205         for (final ParameterObserver observer : observers) {
206             observer.nameChanged(previousName, this);
207         }
208     }
209 
210     /** Get reference parameter value.
211      * @return reference parameter value
212      */
213     public double getReferenceValue() {
214         return referenceValue;
215     }
216 
217     /** Set reference parameter value.
218      * @since 9.3
219      * @param referenceValue the reference value to set.
220      */
221     public void setReferenceValue(final double referenceValue) {
222         final double previousReferenceValue = this.referenceValue;
223         this.referenceValue = referenceValue;
224         for (final ParameterObserver observer : observers) {
225             observer.referenceValueChanged(previousReferenceValue, this);
226         }
227     }
228 
229     /** Get minimum parameter value.
230      * @return minimum parameter value
231      */
232     public double getMinValue() {
233         return minValue;
234     }
235 
236     /** Set minimum parameter value.
237      * @since 9.3
238      * @param minValue the minimum value to set.
239      */
240     public void setMinValue(final double minValue) {
241 
242         // safety check
243         if (minValue > maxValue) {
244             throw new OrekitException(LocalizedCoreFormats.NUMBER_TOO_LARGE, minValue, maxValue);
245         }
246 
247         if (value < minValue) {
248             // clip value to minimum
249             setValue(minValue);
250         }
251 
252         final double previousMinValue = this.minValue;
253         this.minValue = minValue;
254         for (final ParameterObserver observer : observers) {
255             observer.minValueChanged(previousMinValue, this);
256         }
257 
258     }
259 
260     /** Get maximum parameter value.
261      * @return maximum parameter value
262      */
263     public double getMaxValue() {
264         return maxValue;
265     }
266 
267     /** Set maximum parameter value.
268      * @since 9.3
269      * @param maxValue the maximum value to set.
270      */
271     public void setMaxValue(final double maxValue) {
272 
273         // safety check
274         if (maxValue < minValue) {
275             throw new OrekitException(LocalizedCoreFormats.NUMBER_TOO_SMALL, maxValue, minValue);
276         }
277 
278         if (value > maxValue) {
279             // clip value to maximum
280             setValue(maxValue);
281         }
282 
283         final double previousMaxValue = this.maxValue;
284         this.maxValue = maxValue;
285         for (final ParameterObserver observer : observers) {
286             observer.maxValueChanged(previousMaxValue, this);
287         }
288 
289     }
290 
291     /** Get scale.
292      * @return scale
293      */
294     public double getScale() {
295         return scale;
296     }
297 
298     /** Set scale.
299      * @since 9.3
300      * @param scale the scale to set.
301      */
302     public void setScale(final double scale) {
303         final double previousScale = this.scale;
304         this.scale = scale;
305         for (final ParameterObserver observer : observers) {
306             observer.scaleChanged(previousScale, this);
307         }
308     }
309 
310     /** Get normalized value.
311      * <p>
312      * The normalized value is a non-dimensional value
313      * suitable for use as part of a vector in an optimization
314      * process. It is computed as {@code (current - reference)/scale}.
315      * </p>
316      * @return normalized value
317      */
318     public double getNormalizedValue() {
319         return (value - referenceValue) / scale;
320     }
321 
322     /** Set normalized value.
323      * <p>
324      * The normalized value is a non-dimensional value
325      * suitable for use as part of a vector in an optimization
326      * process. It is computed as {@code (current - reference)/scale}.
327      * </p>
328      * @param normalized value
329      */
330     public void setNormalizedValue(final double normalized) {
331         setValue(referenceValue + scale * normalized);
332     }
333 
334     /** Get current reference date.
335      * @return current reference date (null if it was never set)
336      * @since 9.0
337      */
338     public AbsoluteDate getReferenceDate() {
339         return referenceDate;
340     }
341 
342     /** Set reference date.
343      * @param newReferenceDate new reference date
344      * @since 9.0
345      */
346     public void setReferenceDate(final AbsoluteDate newReferenceDate) {
347         final AbsoluteDate previousReferenceDate = getReferenceDate();
348         referenceDate = newReferenceDate;
349         for (final ParameterObserver observer : observers) {
350             observer.referenceDateChanged(previousReferenceDate, this);
351         }
352     }
353 
354     /** Get current parameter value.
355      * @return current parameter value
356      */
357     public double getValue() {
358         return value;
359     }
360 
361     /** Get the value as a gradient.
362      * @param freeParameters total number of free parameters in the gradient
363      * @param indices indices of the differentiation parameters in derivatives computations
364      * @return value with derivatives
365      * @since 10.2
366      */
367     public Gradient getValue(final int freeParameters, final Map<String, Integer> indices) {
368         final Integer index = indices.get(getName());
369         return (index == null) ?
370                Gradient.constant(freeParameters, getValue()) :
371                Gradient.variable(freeParameters, index, getValue());
372     }
373 
374     /** Set parameter value.
375      * <p>
376      * If {@code newValue} is below {@link #getMinValue()}, it will
377      * be silently set to {@link #getMinValue()}. If {@code newValue} is
378      * above {@link #getMaxValue()}, it will be silently set to {@link
379      * #getMaxValue()}.
380      * </p>
381      * @param newValue new value to set
382      */
383     public void setValue(final double newValue) {
384         final double previousValue = value;
385         value = FastMath.max(minValue, FastMath.min(maxValue, newValue));
386         for (final ParameterObserver observer : observers) {
387             observer.valueChanged(previousValue, this);
388         }
389     }
390 
391     /** Get the validity interval.
392      * @return validity interval
393      * @since 14.0
394      */
395     public TimeInterval getValidity() {
396         return validity;
397     }
398 
399     /** Set the validity interval.
400      * @param validity validity interval
401      * @since 14.0
402      */
403     public void setValidity(final TimeInterval validity) {
404         final TimeInterval previousValidity = getValidity();
405         this.validity = validity;
406         for (final ParameterObserver observer : observers) {
407             observer.validityChanged(previousValidity, this);
408         }
409     }
410 
411     /** Configure a parameter selection status.
412      * <p>
413      * Selection is used for estimated parameters in orbit determination,
414      * or to compute the Jacobian matrix in partial derivatives computation.
415      * </p>
416      * @param selected if true the parameter is selected,
417      * otherwise it will be fixed
418      */
419     public void setSelected(final boolean selected) {
420         final boolean previousSelection = isSelected();
421         this.selected = selected;
422         for (final ParameterObserver observer : observers) {
423             observer.selectionChanged(previousSelection, this);
424         }
425     }
426 
427     /** Check if parameter is selected.
428      * <p>
429      * Selection is used for estimated parameters in orbit determination,
430      * or to compute the Jacobian matrix in partial derivatives computation.
431      * </p>
432      * @return true if parameter is selected, false if it is not
433      */
434     public boolean isSelected() {
435         return selected;
436     }
437 
438     /** Get a text representation of the parameter.
439      * @return text representation of the parameter, in the form name = value.
440      */
441     public String toString() {
442         return name + " = " + value;
443     }
444 
445 }