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.estimation.measurements;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.IdentityHashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.function.Function;
25  
26  import org.hipparchus.linear.MatrixUtils;
27  import org.hipparchus.linear.RealMatrix;
28  import org.orekit.propagation.SpacecraftState;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.utils.ParameterDriver;
31  import org.orekit.utils.ParameterDriversList;
32  import org.orekit.utils.TimeSpanMap;
33  import org.orekit.utils.TimeSpanMap.Span;
34  import org.orekit.utils.TimeStampedPVCoordinates;
35  
36  /** Class multiplexing several measurements as one.
37   * <p>
38   * Date comes from the first measurement, observed and estimated
39   * values result from gathering all underlying measurements values.
40   *
41   * @author Luc Maisonobe
42   * @since 10.1
43   */
44  public class MultiplexedMeasurement extends AbstractMeasurement<MultiplexedMeasurement> {
45  
46      /** Type of the measurement. */
47      public static final String MEASUREMENT_TYPE = "MultiplexedMeasurement";
48  
49      /** Multiplexed measurements. */
50      private final List<ObservedMeasurement<?>> observedMeasurements;
51  
52      /** Multiplexed measurements without derivatives.
53       */
54      private final List<EstimatedMeasurementBase<?>> estimatedMeasurementsWithoutDerivatives;
55  
56      /** Multiplexed measurements. */
57      private final List<EstimatedMeasurement<?>> estimatedMeasurements;
58  
59      /** Multiplexed parameters drivers. */
60      private final ParameterDriversList parametersDrivers;
61  
62      /** Total dimension. */
63      private final int dimension;
64  
65      /** Total number of satellites involved. */
66      private final int nbSat;
67  
68      /** States mapping. */
69      private final int[][] multiplexedToUnderlying;
70  
71      /** States mapping. */
72      private final int[][] underlyingToMultiplexed;
73  
74      /** Simple constructor.
75       * @param measurements measurements to multiplex
76       * @since 10.1
77       */
78      public MultiplexedMeasurement(final List<ObservedMeasurement<?>> measurements) {
79          super(measurements.getFirst().getDate(),
80                multiplex(measurements, ComparableMeasurement::getObservedValue),
81                multiplexMeasurementQuality(measurements), multiplex(measurements));
82  
83          this.observedMeasurements                    = measurements;
84          this.estimatedMeasurementsWithoutDerivatives = new ArrayList<>();
85          this.estimatedMeasurements                   = new ArrayList<>();
86          this.parametersDrivers                       = new ParameterDriversList();
87  
88          // gather parameters drivers
89          int dim = 0;
90          for (final ObservedMeasurement<?> m : measurements) {
91              for (final ParameterDriver driver : m.getParametersDrivers()) {
92                  parametersDrivers.add(driver);
93              }
94              dim += m.getDimension();
95          }
96          parametersDrivers.sort();
97          for (final ParameterDriver driver : parametersDrivers.getDrivers()) {
98              addParameterDriver(driver);
99          }
100         this.dimension = dim;
101 
102         // set up states mappings for observed satellites
103         final List<ObservableSatellite> deduplicated = getSatellites();
104         this.nbSat   = deduplicated.size();
105         this.multiplexedToUnderlying = new int[measurements.size()][];
106         this.underlyingToMultiplexed = new int[measurements.size()][deduplicated.size()];
107         for (int i = 0; i < multiplexedToUnderlying.length; ++i) {
108             final List<ObservableSatellite> satellites = measurements.get(i).getSatellites();
109             multiplexedToUnderlying[i] = new int[satellites.size()];
110             for (int j = 0; j < multiplexedToUnderlying[i].length; ++j) {
111                 final int index = satellites.get(j).getPropagatorIndex();
112                 for (int k = 0; k < nbSat; ++k) {
113                     if (deduplicated.get(k).getPropagatorIndex() == index) {
114                         multiplexedToUnderlying[i][j] = k;
115                         underlyingToMultiplexed[i][k] = j;
116                         break;
117                     }
118                 }
119             }
120         }
121 
122     }
123 
124     /** Get the underlying measurements.
125      * @return underlying measurements
126      */
127     public List<ObservedMeasurement<?>> getMeasurements() {
128         return observedMeasurements;
129     }
130 
131     /** Get the underlying estimated measurements without derivatives.
132      * @return underlying estimated measurements without derivatives
133      * @since 12.0
134      */
135     public List<EstimatedMeasurementBase<?>> getEstimatedMeasurementsWithoutDerivatives() {
136         return estimatedMeasurementsWithoutDerivatives;
137     }
138 
139     /** Get the underlying estimated measurements.
140      * @return underlying estimated measurements
141      */
142     public List<EstimatedMeasurement<?>> getEstimatedMeasurements() {
143         return estimatedMeasurements;
144     }
145 
146     /** Get the spacecraft state index in the underlying measurement.
147      * @param measurementIndex index of the underlying measurement
148      * @param multiplexedStateIndex index of the spacecraft state in the multiplexed array
149      * @return spacecraft state index in the underlying measurement
150      * @since 13.0
151      */
152     public int getUnderlyingStateIndex(final int measurementIndex, final int multiplexedStateIndex) {
153         return multiplexedToUnderlying[measurementIndex][multiplexedStateIndex];
154     }
155 
156     /** Get the spacecraft state index in the multiplexed measurement.
157      * @param measurementIndex index of the underlying measurement
158      * @param underlyingStateIndex index of the spacecraft state in the underlying array
159      * @return spacecraft state index in the multiplexed measurement
160      * @since 13.0
161      */
162     public int getMultiplexedStateIndex(final int measurementIndex, final int underlyingStateIndex) {
163         return underlyingToMultiplexed[measurementIndex][underlyingStateIndex];
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     protected EstimatedMeasurementBase<MultiplexedMeasurement> theoreticalEvaluationWithoutDerivatives(final int iteration,
169                                                                                                        final int evaluation,
170                                                                                                        final SpacecraftState[] states) {
171 
172         final SpacecraftState[]              evaluationStates = new SpacecraftState[nbSat];
173         final double[]                       value            = new double[dimension];
174 
175         // loop over all multiplexed measurements
176         estimatedMeasurementsWithoutDerivatives.clear();
177         int index = 0;
178         for (int i = 0; i < observedMeasurements.size(); ++i) {
179 
180             // filter states involved in the current measurement
181             final SpacecraftState[] filteredStates = new SpacecraftState[multiplexedToUnderlying[i].length];
182             for (int j = 0; j < multiplexedToUnderlying[i].length; ++j) {
183                 filteredStates[j] = states[getUnderlyingStateIndex(i, j)];
184             }
185 
186             // perform evaluation
187             final EstimatedMeasurementBase<?> eI = observedMeasurements.get(i).estimateWithoutDerivatives(iteration, evaluation, filteredStates);
188             estimatedMeasurementsWithoutDerivatives.add(eI);
189 
190             // extract results
191             final double[] valueI = eI.getEstimatedValue();
192             System.arraycopy(valueI, 0, value, index, valueI.length);
193             index += valueI.length;
194 
195             // extract states
196             final SpacecraftState[] statesI = eI.getStates();
197             for (int j = 0; j < multiplexedToUnderlying[i].length; ++j) {
198                 evaluationStates[multiplexedToUnderlying[i][j]] = statesI[j];
199             }
200 
201         }
202 
203         // create multiplexed estimation
204         final EstimatedMeasurementBase<MultiplexedMeasurement> multiplexed =
205                         new EstimatedMeasurementBase<>(this, iteration, evaluation,
206                                                        evaluationStates,
207                                                        new TimeStampedPVCoordinates[0]);
208 
209         // copy multiplexed value
210         multiplexed.setEstimatedValue(value);
211 
212         return multiplexed;
213 
214     }
215 
216     /** {@inheritDoc} */
217     @Override
218     protected EstimatedMeasurement<MultiplexedMeasurement> theoreticalEvaluation(final int iteration, final int evaluation,
219                                                                                  final SpacecraftState[] states) {
220 
221         final SpacecraftState[]              evaluationStates = new SpacecraftState[nbSat];
222         final double[]                       value            = new double[dimension];
223 
224         // loop over all multiplexed measurements
225         estimatedMeasurements.clear();
226         int index = 0;
227         for (int i = 0; i < observedMeasurements.size(); ++i) {
228 
229             // filter states involved in the current measurement
230             final SpacecraftState[] filteredStates = new SpacecraftState[multiplexedToUnderlying[i].length];
231             for (int j = 0; j < multiplexedToUnderlying[i].length; ++j) {
232                 filteredStates[j] = states[multiplexedToUnderlying[i][j]];
233             }
234 
235             // perform evaluation
236             final EstimatedMeasurement<?> eI = observedMeasurements.get(i).estimate(iteration, evaluation, filteredStates);
237             estimatedMeasurements.add(eI);
238 
239             // extract results
240             final double[] valueI = eI.getEstimatedValue();
241             System.arraycopy(valueI, 0, value, index, valueI.length);
242             index += valueI.length;
243 
244             // extract states
245             final SpacecraftState[] statesI = eI.getStates();
246             for (int j = 0; j < multiplexedToUnderlying[i].length; ++j) {
247                 evaluationStates[multiplexedToUnderlying[i][j]] = statesI[j];
248             }
249 
250         }
251 
252         // create multiplexed estimation
253         final EstimatedMeasurement<MultiplexedMeasurement> multiplexed =
254                         new EstimatedMeasurement<>(this, iteration, evaluation,
255                                                    evaluationStates,
256                                                    new TimeStampedPVCoordinates[0]);
257 
258         // copy multiplexed value
259         multiplexed.setEstimatedValue(value);
260 
261         // combine derivatives
262         final int                            stateSize             = estimatedMeasurements.getFirst().getStateSize();
263         final double[]                       zeroDerivative        = new double[stateSize];
264         final double[][][]                   stateDerivatives      = new double[nbSat][dimension][];
265         for (final double[][] m : stateDerivatives) {
266             Arrays.fill(m, zeroDerivative);
267         }
268 
269         final Map<ParameterDriver, TimeSpanMap<double[]>> parametersDerivatives = new IdentityHashMap<>();
270         index = 0;
271         for (int i = 0; i < observedMeasurements.size(); ++i) {
272 
273             final EstimatedMeasurement<?> eI   = estimatedMeasurements.get(i);
274             final int                     idx  = index;
275             final int                     dimI = eI.getObservedMeasurement().getDimension();
276 
277             // state derivatives
278             for (int j = 0; j < multiplexedToUnderlying[i].length; ++j) {
279                 System.arraycopy(eI.getStateDerivatives(j), 0,
280                                  stateDerivatives[multiplexedToUnderlying[i][j]], index,
281                                  dimI);
282             }
283 
284             // parameters derivatives
285             eI.getDerivativesDrivers().forEach(driver -> {
286                 final ParameterDriversList.DelegatingDriver delegating = parametersDrivers.findByName(driver.getName());
287 
288                 if (parametersDerivatives.get(delegating) == null) {
289                     final TimeSpanMap<double[]> derivativeSpanMap = new TimeSpanMap<>(new double[dimension]);
290                     parametersDerivatives.put(delegating, derivativeSpanMap);
291                 }
292 
293                 final TimeSpanMap<Double> driverNameSpan = delegating.getValueSpanMap();
294                 for (Span<Double> span = driverNameSpan.getSpan(driverNameSpan.getFirstSpan().getEnd()); span != null; span = span.next()) {
295 
296                     double[] derivatives = parametersDerivatives.get(delegating).get(span.getStart());
297                     if (derivatives == null) {
298                         derivatives = new double[dimension];
299                     }
300                     if (!parametersDerivatives.get(delegating).getSpan(span.getStart()).getStart().equals(span.getStart())) {
301                         if ((span.getStart()).equals(AbsoluteDate.PAST_INFINITY)) {
302                             parametersDerivatives.get(delegating).addValidBefore(derivatives, span.getEnd(), false);
303                         } else {
304                             parametersDerivatives.get(delegating).addValidAfter(derivatives, span.getStart(), false);
305                         }
306 
307                     }
308 
309                     System.arraycopy(eI.getParameterDerivatives(driver, span.getStart()), 0, derivatives, idx, dimI);
310 
311                 }
312 
313             });
314 
315             index += dimI;
316 
317         }
318 
319         // set states derivatives
320         for (int i = 0; i < nbSat; ++i) {
321             multiplexed.setStateDerivatives(i, stateDerivatives[i]);
322         }
323 
324         // set parameters derivatives
325         parametersDerivatives.
326             entrySet().
327             forEach(e -> multiplexed.setParameterDerivatives(e.getKey(), e.getValue()));
328 
329         return multiplexed;
330 
331     }
332 
333     /** Multiplex measurements data.
334      * @param measurements measurements to multiplex
335      * @return multiplexed data
336      */
337     private static MeasurementQuality multiplexMeasurementQuality(final List<ObservedMeasurement<?>> measurements) {
338 
339         final int totalSize = measurements.stream().mapToInt(ObservedMeasurement::getDimension).sum();
340         final double[] weights = new double[totalSize];
341         final RealMatrix covarianceMatrix = MatrixUtils.createRealMatrix(totalSize, totalSize);
342         int n = 0;
343         for (final ObservedMeasurement<?> measurement : measurements) {
344             System.arraycopy(measurement.getBaseWeight(), 0, weights, n, measurement.getDimension());
345             covarianceMatrix.setSubMatrix(measurement.getMeasurementQuality().getCovarianceMatrix().getData(), n, n);
346             n += measurement.getDimension();
347         }
348 
349         return new MeasurementQuality(covarianceMatrix.getData(), weights);
350 
351     }
352 
353     /** Multiplex measurements value.
354      * @param measurements measurements to multiplex
355      * @param extractor data extraction function
356      * @return multiplexed data
357      */
358     private static double[] multiplex(final List<ObservedMeasurement<?>> measurements,
359                                       final Function<ObservedMeasurement<?>, double[]> extractor) {
360 
361         // gather individual parts
362         final List<double[]> parts = new ArrayList<> (measurements.size());
363         int n = 0;
364         for (final ObservedMeasurement<?> measurement : measurements) {
365             final double[] p = extractor.apply(measurement);
366             parts.add(p);
367             n += p.length;
368         }
369 
370         // create multiplexed data
371         final double[] multiplexed = new double[n];
372         int index = 0;
373         for (final double[] p : parts) {
374             System.arraycopy(p, 0, multiplexed, index, p.length);
375             index += p.length;
376         }
377 
378         return multiplexed;
379 
380     }
381 
382     /** Multiplex satellites data.
383      * @param measurements measurements to multiplex
384      * @return multiplexed satellites data
385      */
386     private static List<ObservableSatellite> multiplex(final List<ObservedMeasurement<?>> measurements) {
387 
388         final List<ObservableSatellite> satellites = new ArrayList<>();
389 
390         // gather all satellites, removing duplicates
391         for (final ObservedMeasurement<?> measurement : measurements) {
392             for (final ObservableSatellite satellite : measurement.getSatellites()) {
393                 boolean searching = true;
394                 for (int i = 0; i < satellites.size() && searching; ++i) {
395                     // check if we already know this satellite
396                     searching = satellite.getPropagatorIndex() != satellites.get(i).getPropagatorIndex();
397                 }
398                 if (searching) {
399                     // this is a new satellite, add it to the global list
400                     satellites.add(satellite);
401                 }
402             }
403         }
404 
405         return satellites;
406 
407     }
408 
409 }