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