1   /* Copyright 2025-2026 Hawkeye 360 (HE360)
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.estimation.measurements;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  
24  import org.hipparchus.Field;
25  import org.hipparchus.analysis.differentiation.Gradient;
26  import org.hipparchus.util.FastMath;
27  import org.orekit.orbits.CartesianOrbit;
28  import org.orekit.orbits.FieldCartesianOrbit;
29  import org.orekit.propagation.SpacecraftState;
30  import org.orekit.time.TimeInterval;
31  import org.orekit.time.clocks.ClockModel;
32  import org.orekit.time.clocks.PolynomialClockModel;
33  import org.orekit.utils.AbsolutePVCoordinates;
34  import org.orekit.utils.FieldAbsolutePVCoordinates;
35  import org.orekit.utils.FieldPVCoordinatesProvider;
36  import org.orekit.utils.PVCoordinatesProvider;
37  import org.orekit.utils.drivers.ParameterDriver;
38  import org.orekit.utils.TimeStampedFieldPVCoordinates;
39  import org.orekit.utils.TimeStampedPVCoordinates;
40  import org.orekit.utils.drivers.ParameterDriversProvider;
41  
42  /** Abstract class underlying both observed and observing measurement
43   * objects.  Contains the ClockModel and the ability to store a
44   * master list of all parameter drivers associated with the object.
45   *
46   * @author Brianna Aubin
47   * @since 14.0
48   */
49  public abstract class AbstractParticipant implements MeasurementParticipant {
50  
51      /** Checkstyle is annoying sometimes. */
52      private static final String CLOCK_STRING = "-clock";
53  
54      /** Clock offset scaling factor.
55       * <p>
56       * We use a power of 2 to avoid numeric noise introduction
57       * in the multiplications/divisions sequences.
58       * </p>
59       */
60      private static final double CLOCK_OFFSET_SCALE = FastMath.scalb(1.0, -10);
61  
62      /** Stores clock model. */
63      private final ClockModel clockModel;
64  
65      /** Stores list of all ParameterDriver values. */
66      private final List<ParameterDriver> parameterDrivers = new ArrayList<>();
67  
68      /** Name of the satellite. */
69      private final String name;
70  
71      /** Simple constructor.
72       * @param name name of MeasurementObject
73       */
74      protected AbstractParticipant(final String name) {
75          this(name, createEmptyPolynomialClock(name));
76      }
77  
78      /** Simple constructor.
79       * @param name name of MeasurementObject
80       * @param clock clock belonging to MeasurementObject
81       */
82      protected AbstractParticipant(final String name, final ClockModel clock) {
83  
84          // Initialize member variables
85          this.name = name;
86          this.clockModel = clock;
87  
88          parameterDrivers.addAll(clock.getParametersDrivers());
89  
90      }
91  
92      /** Get the MeasurementObject name.
93       * @return name for the object
94       */
95      @Override
96      public final String getName() {
97          return name;
98      }
99  
100     /** Creates a polynomial clock with zero displacement.
101      * @param name name of object that is holding the clock
102      * @return new polynomial clock model
103      */
104     protected static PolynomialClockModel createEmptyPolynomialClock(final String name) {
105         return new PolynomialClockModel(new ParameterDriver(name + CLOCK_STRING + BIAS_SUFFIX,
106                                                             0.0, CLOCK_OFFSET_SCALE,
107                                                             Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
108                                                             TimeInterval.UNLIMITED),
109                                            new ParameterDriver(name + CLOCK_STRING + DRIFT_SUFFIX,
110                                                                0.0, CLOCK_OFFSET_SCALE,
111                                                                Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
112                                                                TimeInterval.UNLIMITED),
113                                            new ParameterDriver(name + CLOCK_STRING + ACCELERATION_SUFFIX,
114                                                                0.0, CLOCK_OFFSET_SCALE,
115                                                                Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
116                                                                TimeInterval.UNLIMITED));
117     }
118 
119     /** Get the clock model valid at some date.
120      * @return clock model
121      */
122     @Override
123     public final ClockModel getClockModel() {
124         return clockModel;
125     }
126 
127     /** Get model parameters.
128      * @return model parameters, will throw an
129      * exception if one PDriver has several values driven. If
130      * it's the case (if at least 1 PDriver of the model has several values
131      * driven) the method {@link ParameterDriversProvider#getParameters()} must be used.
132      */
133     @Override
134     public List<ParameterDriver> getParametersDrivers() {
135         return Collections.unmodifiableList(parameterDrivers);
136     }
137 
138     /**
139      * Add a single parameter.
140      * @param parameterDriver parameter being added to the MeasurementObject
141      */
142     protected final void addParameterDriver(final ParameterDriver parameterDriver) {
143         parameterDrivers.add(parameterDriver);
144     }
145 
146     /**
147      * Create PV provider from position-velocity-acceleration vector and template state.
148      * @param templateState template state
149      * @param pvCoordinates position-velocity-acceleration
150      * @return position-velocity-acceleration provider
151      */
152     public static PVCoordinatesProvider extractPVCoordinatesProvider(final SpacecraftState templateState,
153                                                                      final TimeStampedPVCoordinates pvCoordinates) {
154         if (templateState.isOrbitDefined()) {
155             final CartesianOrbit cartesianOrbit = new CartesianOrbit(pvCoordinates, templateState.getFrame(),
156                     templateState.getOrbit().getMu());
157             return templateState.getOrbit().getType().convertType(cartesianOrbit);
158         } else {
159             return new AbsolutePVCoordinates(templateState.getFrame(), pvCoordinates);
160         }
161     }
162 
163     /**
164      * Create PV provider from position-velocity-acceleration vector and template state.
165      *
166      * @param templateState template state
167      * @param pvCoordinates position-velocity-acceleration
168      * @return position-velocity-acceleration provider
169      */
170     public static FieldPVCoordinatesProvider<Gradient> extractFieldPVCoordinatesProvider(final SpacecraftState templateState,
171                                                                                          final TimeStampedFieldPVCoordinates<Gradient> pvCoordinates) {
172         final Field<Gradient> field = pvCoordinates.getDate().getField();
173         if (templateState.isOrbitDefined()) {
174             final FieldCartesianOrbit<Gradient> cartesianOrbit = new FieldCartesianOrbit<>(pvCoordinates, templateState.getFrame(),
175                     field.getZero().newInstance(templateState.getOrbit().getMu()));
176             return templateState.getOrbit().getType().convertType(cartesianOrbit);
177         } else {
178             return new FieldAbsolutePVCoordinates<>(templateState.getFrame(), pvCoordinates);
179         }
180     }
181 }