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