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.Map;
20  
21  import org.hipparchus.CalculusFieldElement;
22  import org.hipparchus.analysis.differentiation.Gradient;
23  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
24  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
25  import org.hipparchus.geometry.euclidean.threed.Rotation;
26  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
27  import org.hipparchus.geometry.euclidean.threed.Vector3D;
28  import org.hipparchus.util.FastMath;
29  import org.orekit.errors.OrekitException;
30  import org.orekit.errors.OrekitMessages;
31  import org.orekit.frames.FieldStaticTransform;
32  import org.orekit.frames.FieldTransform;
33  import org.orekit.frames.StaticTransform;
34  import org.orekit.frames.Transform;
35  import org.orekit.frames.TransformProvider;
36  import org.orekit.time.AbsoluteDate;
37  import org.orekit.time.FieldAbsoluteDate;
38  import org.orekit.time.TimeInterval;
39  import org.orekit.time.TimeOffset;
40  import org.orekit.time.UT1Scale;
41  import org.orekit.utils.IERSConventions;
42  import org.orekit.utils.drivers.ParameterDriver;
43  
44  /** Class modeling an Earth frame whose Earth Orientation Parameters can be estimated.
45   * <p>
46   * This class adds parameters for an additional polar motion
47   * and an additional prime meridian orientation on top of an underlying regular Earth
48   * frame like {@link org.orekit.frames.FramesFactory#getITRF(IERSConventions, boolean) ITRF}.
49   * The polar motion and prime meridian orientation are applied <em>after</em> regular Earth
50   * orientation parameters, so the value of the estimated parameters will be correction to EOP,
51   * they will not be the complete EOP values by themselves. Basically, this means that for
52   * Earth, the following transforms are applied in order, between inertial frame and this frame:
53   * </p>
54   * <ol>
55   *   <li>precession/nutation, as theoretical model plus celestial pole EOP parameters</li>
56   *   <li>body rotation, as theoretical model plus prime meridian EOP parameters</li>
57   *   <li>polar motion, which is only from EOP parameters (no theoretical models)</li>
58   *   <li>additional body rotation, controlled by {@link #getPrimeMeridianOffsetDriver()} and {@link #getPrimeMeridianDriftDriver()}</li>
59   *   <li>additional polar motion, controlled by {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
60   *   {@link #getPolarOffsetYDriver()} and {@link #getPolarDriftYDriver()}</li>
61   * </ol>
62   * @author Luc Maisonobe
63   * @since 9.1
64   */
65  public class EstimatedEarthFrameProvider implements TransformProvider {
66  
67      /** Earth Angular Velocity, in rad/s, from TIRF model. */
68      public static final double EARTH_ANGULAR_VELOCITY = 7.292115146706979e-5;
69  
70      /** Angular scaling factor.
71       * <p>
72       * We use a power of 2 to avoid numeric noise introduction
73       * in the multiplications/divisions sequences.
74       * </p>
75       */
76      private static final double ANGULAR_SCALE = FastMath.scalb(1.0, -22);
77  
78      /** Underlying raw UT1. */
79      private final UT1Scale baseUT1;
80  
81      /** Estimated UT1. */
82      private final UT1Scale estimatedUT1;
83  
84      /** Driver for prime meridian offset. */
85      private final ParameterDriver primeMeridianOffsetDriver;
86  
87      /** Driver for prime meridian drift. */
88      private final ParameterDriver primeMeridianDriftDriver;
89  
90      /** Driver for pole offset along X. */
91      private final ParameterDriver polarOffsetXDriver;
92  
93      /** Driver for pole drift along X. */
94      private final ParameterDriver polarDriftXDriver;
95  
96      /** Driver for pole offset along Y. */
97      private final ParameterDriver polarOffsetYDriver;
98  
99      /** Driver for pole drift along Y. */
100     private final ParameterDriver polarDriftYDriver;
101 
102     /** Build an estimated Earth frame.
103      * <p>
104      * The initial values for the pole and prime meridian parametric linear models
105      * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
106      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
107      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
108      * </p>
109      * @param baseUT1 underlying base UT1
110      * @since 9.1
111      */
112     public EstimatedEarthFrameProvider(final UT1Scale baseUT1) {
113 
114         this.primeMeridianOffsetDriver = new ParameterDriver("prime-meridian-offset",
115                                                              0.0, ANGULAR_SCALE,
116                                                              -FastMath.PI, FastMath.PI,
117                                                              TimeInterval.UNLIMITED);
118 
119         this.primeMeridianDriftDriver = new ParameterDriver("prime-meridian-drift",
120                                                             0.0, ANGULAR_SCALE,
121                                                             Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
122                                                             TimeInterval.UNLIMITED);
123 
124         this.polarOffsetXDriver = new ParameterDriver("polar-offset-X",
125                                                       0.0, ANGULAR_SCALE,
126                                                       -FastMath.PI, FastMath.PI,
127                                                       TimeInterval.UNLIMITED);
128 
129         this.polarDriftXDriver = new ParameterDriver("polar-drift-X",
130                                                      0.0, ANGULAR_SCALE,
131                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
132                                                      TimeInterval.UNLIMITED);
133 
134         this.polarOffsetYDriver = new ParameterDriver("polar-offset-Y",
135                                                       0.0, ANGULAR_SCALE,
136                                                       -FastMath.PI, FastMath.PI,
137                                                       TimeInterval.UNLIMITED);
138 
139         this.polarDriftYDriver = new ParameterDriver("polar-drift-Y",
140                                                      0.0, ANGULAR_SCALE,
141                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
142                                                      TimeInterval.UNLIMITED);
143 
144         this.baseUT1      = baseUT1;
145         this.estimatedUT1 = new EstimatedUT1Scale();
146 
147     }
148 
149     /** Get a driver allowing to add a prime meridian rotation.
150      * <p>
151      * The parameter is an angle in radians. In order to convert this
152      * value to a DUT1 in seconds, the value must be divided by
153      * {@link #EARTH_ANGULAR_VELOCITY} (nominal Angular Velocity of Earth).
154      * </p>
155      * @return driver for prime meridian rotation
156      */
157     public ParameterDriver getPrimeMeridianOffsetDriver() {
158         return primeMeridianOffsetDriver;
159     }
160 
161     /** Get a driver allowing to add a prime meridian rotation rate.
162      * <p>
163      * The parameter is an angle rate in radians per second. In order to convert this
164      * value to a LOD in seconds, the value must be multiplied by -86400 and divided by
165      * {@link #EARTH_ANGULAR_VELOCITY} (nominal Angular Velocity of Earth).
166      * </p>
167      * @return driver for prime meridian rotation rate
168      */
169     public ParameterDriver getPrimeMeridianDriftDriver() {
170         return primeMeridianDriftDriver;
171     }
172 
173     /** Get a driver allowing to add a polar offset along X.
174      * <p>
175      * The parameter is an angle in radians
176      * </p>
177      * @return driver for polar offset along X
178      */
179     public ParameterDriver getPolarOffsetXDriver() {
180         return polarOffsetXDriver;
181     }
182 
183     /** Get a driver allowing to add a polar drift along X.
184      * <p>
185      * The parameter is an angle rate in radians per second
186      * </p>
187      * @return driver for polar drift along X
188      */
189     public ParameterDriver getPolarDriftXDriver() {
190         return polarDriftXDriver;
191     }
192 
193     /** Get a driver allowing to add a polar offset along Y.
194      * <p>
195      * The parameter is an angle in radians
196      * </p>
197      * @return driver for polar offset along Y
198      */
199     public ParameterDriver getPolarOffsetYDriver() {
200         return polarOffsetYDriver;
201     }
202 
203     /** Get a driver allowing to add a polar drift along Y.
204      * <p>
205      * The parameter is an angle rate in radians per second
206      * </p>
207      * @return driver for polar drift along Y
208      */
209     public ParameterDriver getPolarDriftYDriver() {
210         return polarDriftYDriver;
211     }
212 
213     /** Get the estimated UT1 time scale.
214      * @return estimated UT1 time scale
215      */
216     public UT1Scale getEstimatedUT1() {
217         return estimatedUT1;
218     }
219 
220     /** {@inheritDoc} */
221     @Override
222     public Transform getTransform(final AbsoluteDate date) {
223 
224         // take parametric prime meridian shift into account
225         final double theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
226         final double thetaDot = primeMeridianDriftDriver.getValue();
227         final Transform meridianShift =
228                         new Transform(date,
229                                       new Rotation(Vector3D.PLUS_K, theta, RotationConvention.FRAME_TRANSFORM),
230                                       new Vector3D(0, 0, thetaDot));
231 
232         // take parametric pole shift into account
233         final double xpNeg     = -linearModel(date, polarOffsetXDriver, polarDriftXDriver);
234         final double ypNeg     = -linearModel(date, polarOffsetYDriver, polarDriftYDriver);
235         final double xpNegDot  = -polarDriftXDriver.getValue();
236         final double ypNegDot  = -polarDriftYDriver.getValue();
237         final Transform poleShift =
238                         new Transform(date,
239                                       new Transform(date,
240                                                     new Rotation(Vector3D.PLUS_J, xpNeg, RotationConvention.FRAME_TRANSFORM),
241                                                     new Vector3D(0.0, xpNegDot, 0.0)),
242                                       new Transform(date,
243                                                     new Rotation(Vector3D.PLUS_I, ypNeg, RotationConvention.FRAME_TRANSFORM),
244                                                     new Vector3D(ypNegDot, 0.0, 0.0)));
245 
246         return new Transform(date, meridianShift, poleShift);
247 
248     }
249 
250     /** {@inheritDoc} */
251     @Override
252     public StaticTransform getStaticTransform(final AbsoluteDate date) {
253 
254         // take parametric prime meridian shift into account
255         final double theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
256         final StaticTransform meridianShift = StaticTransform.of(
257                 date,
258                 new Rotation(Vector3D.PLUS_K, theta, RotationConvention.FRAME_TRANSFORM)
259         );
260 
261         // take parametric pole shift into account
262         final double xpNeg     = -linearModel(date, polarOffsetXDriver, polarDriftXDriver);
263         final double ypNeg     = -linearModel(date, polarOffsetYDriver, polarDriftYDriver);
264         final StaticTransform poleShift = StaticTransform.compose(
265                 date,
266                 StaticTransform.of(
267                         date,
268                         new Rotation(Vector3D.PLUS_J, xpNeg, RotationConvention.FRAME_TRANSFORM)),
269                 StaticTransform.of(
270                         date,
271                         new Rotation(Vector3D.PLUS_I, ypNeg, RotationConvention.FRAME_TRANSFORM)));
272 
273         return StaticTransform.compose(date, meridianShift, poleShift);
274 
275     }
276 
277     /** {@inheritDoc} */
278     @Override
279     public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
280 
281         final T zero = date.getField().getZero();
282 
283         // prime meridian shift parameters
284         final T theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
285         final T thetaDot = zero.newInstance(primeMeridianDriftDriver.getValue());
286 
287         // pole shift parameters
288         final T xpNeg    = linearModel(date, polarOffsetXDriver, polarDriftXDriver).negate();
289         final T ypNeg    = linearModel(date, polarOffsetYDriver, polarDriftYDriver).negate();
290         final T xpNegDot = zero.subtract(polarDriftXDriver.getValue());
291         final T ypNegDot = zero.subtract(polarDriftYDriver.getValue());
292 
293         return getTransform(date, theta, thetaDot, xpNeg, xpNegDot, ypNeg, ypNegDot);
294 
295     }
296 
297     /** {@inheritDoc} */
298     @Override
299     public <T extends CalculusFieldElement<T>> FieldStaticTransform<T> getStaticTransform(final FieldAbsoluteDate<T> date) {
300 
301         // take parametric prime meridian shift into account
302         final T theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
303         final FieldStaticTransform<T> meridianShift = FieldStaticTransform.of(
304                 date,
305                 new FieldRotation<>(FieldVector3D.getPlusK(date.getField()), theta, RotationConvention.FRAME_TRANSFORM)
306         );
307 
308         // take parametric pole shift into account
309         final T xpNeg     = linearModel(date, polarOffsetXDriver, polarDriftXDriver).negate();
310         final T ypNeg     = linearModel(date, polarOffsetYDriver, polarDriftYDriver).negate();
311         final FieldStaticTransform<T> poleShift = FieldStaticTransform.compose(
312                 date,
313                 FieldStaticTransform.of(
314                         date,
315                         new FieldRotation<>(FieldVector3D.getPlusJ(date.getField()), xpNeg, RotationConvention.FRAME_TRANSFORM)),
316                 FieldStaticTransform.of(
317                         date,
318                         new FieldRotation<>(FieldVector3D.getPlusI(date.getField()), ypNeg, RotationConvention.FRAME_TRANSFORM)));
319 
320         return FieldStaticTransform.compose(date, meridianShift, poleShift);
321 
322     }
323 
324     /** Get the transform with derivatives.
325      * @param date date of the transform
326      * @param freeParameters total number of free parameters in the gradient
327      * @param indices indices of the estimated parameters in derivatives computations
328      * @return computed transform with derivatives
329      * @since 10.2
330      */
331     public FieldTransform<Gradient> getTransform(final FieldAbsoluteDate<Gradient> date,
332                                                  final int freeParameters,
333                                                  final Map<String, Integer> indices) {
334 
335         // prime meridian shift parameters
336         final Gradient theta    = linearModel(freeParameters, date,
337                                               primeMeridianOffsetDriver, primeMeridianDriftDriver,
338                                               indices);
339         final Gradient thetaDot = primeMeridianDriftDriver.getValue(freeParameters, indices);
340 
341         // pole shift parameters
342         final Gradient xpNeg    = linearModel(freeParameters, date,
343                                                          polarOffsetXDriver, polarDriftXDriver, indices).negate();
344         final Gradient ypNeg    = linearModel(freeParameters, date,
345                                                          polarOffsetYDriver, polarDriftYDriver, indices).negate();
346         final Gradient xpNegDot = polarDriftXDriver.getValue(freeParameters, indices).negate();
347         final Gradient ypNegDot = polarDriftYDriver.getValue(freeParameters, indices).negate();
348 
349         return getTransform(date, theta, thetaDot, xpNeg, xpNegDot, ypNeg, ypNegDot);
350 
351     }
352 
353     /** Get the static transform with derivatives.
354      * @param date date of the transform
355      * @param freeParameters total number of free parameters in the gradient
356      * @param indices indices of the estimated parameters in derivatives computations
357      * @return computed transform with derivatives
358      * @since 14.0
359      */
360     public FieldStaticTransform<Gradient> getStaticTransform(final FieldAbsoluteDate<Gradient> date,
361                                                  final int freeParameters,
362                                                  final Map<String, Integer> indices) {
363 
364         // prime meridian shift parameters
365         final Gradient theta    = linearModel(freeParameters, date, primeMeridianOffsetDriver, primeMeridianDriftDriver,
366                 indices);
367         final Gradient thetaDot = primeMeridianDriftDriver.getValue(freeParameters, indices);
368 
369         // pole shift parameters
370         final Gradient xpNeg    = linearModel(freeParameters, date,
371                 polarOffsetXDriver, polarDriftXDriver, indices).negate();
372         final Gradient ypNeg    = linearModel(freeParameters, date,
373                 polarOffsetYDriver, polarDriftYDriver, indices).negate();
374         final Gradient xpNegDot = polarDriftXDriver.getValue(freeParameters, indices).negate();
375         final Gradient ypNegDot = polarDriftYDriver.getValue(freeParameters, indices).negate();
376 
377         final Gradient                zero  = date.getField().getZero();
378         final FieldVector3D<Gradient> plusI = FieldVector3D.getPlusI(date.getField());
379         final FieldVector3D<Gradient> plusJ = FieldVector3D.getPlusJ(date.getField());
380         final FieldVector3D<Gradient> plusK = FieldVector3D.getPlusK(date.getField());
381 
382         // take parametric prime meridian shift into account
383         final FieldStaticTransform<Gradient> meridianShift =
384                 FieldStaticTransform.of(date, new FieldVector3D<>(zero, zero, thetaDot),
385                         new FieldRotation<>(plusK, theta, RotationConvention.FRAME_TRANSFORM));
386 
387         // take parametric pole shift into account
388         final FieldStaticTransform<Gradient> poleShift =
389                 FieldStaticTransform.compose(date,
390                         FieldStaticTransform.of(date, new FieldVector3D<>(zero, xpNegDot, zero),
391                                 new FieldRotation<>(plusJ, xpNeg, RotationConvention.FRAME_TRANSFORM)),
392                         FieldStaticTransform.of(date, new FieldVector3D<>(ypNegDot, zero, zero),
393                                 new FieldRotation<>(plusI, ypNeg, RotationConvention.FRAME_TRANSFORM)));
394 
395         return FieldStaticTransform.compose(date, meridianShift, poleShift);
396 
397     }
398 
399     /** Get the transform with derivatives.
400      * @param date date of the transform
401      * @param theta angle of the prime meridian
402      * @param thetaDot angular rate of the prime meridian
403      * @param xpNeg opposite of the angle of the pole motion along X
404      * @param xpNegDot opposite of the angular rate of the pole motion along X
405      * @param ypNeg opposite of the angle of the pole motion along Y
406      * @param ypNegDot opposite of the angular rate of the pole motion along Y
407      * @param <T> type of the field elements
408      * @return computed transform with derivatives
409      */
410     private <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date,
411                                                                            final T theta, final T thetaDot,
412                                                                            final T xpNeg, final T xpNegDot,
413                                                                            final T ypNeg, final T ypNegDot) {
414 
415         final T                zero  = date.getField().getZero();
416         final FieldVector3D<T> plusI = FieldVector3D.getPlusI(date.getField());
417         final FieldVector3D<T> plusJ = FieldVector3D.getPlusJ(date.getField());
418         final FieldVector3D<T> plusK = FieldVector3D.getPlusK(date.getField());
419 
420         // take parametric prime meridian shift into account
421         final FieldTransform<T> meridianShift =
422                         new FieldTransform<>(date,
423                                              new FieldRotation<>(plusK, theta, RotationConvention.FRAME_TRANSFORM),
424                                              new FieldVector3D<>(zero, zero, thetaDot));
425 
426         // take parametric pole shift into account
427         final FieldTransform<T> poleShift =
428                         new FieldTransform<>(date,
429                                       new FieldTransform<>(date,
430                                                            new FieldRotation<>(plusJ, xpNeg, RotationConvention.FRAME_TRANSFORM),
431                                                            new FieldVector3D<>(zero, xpNegDot, zero)),
432                                       new FieldTransform<>(date,
433                                                            new FieldRotation<>(plusI, ypNeg, RotationConvention.FRAME_TRANSFORM),
434                                                            new FieldVector3D<>(ypNegDot, zero, zero)));
435 
436         return new FieldTransform<>(date, meridianShift, poleShift);
437 
438     }
439 
440     /** Evaluate a parametric linear model.
441      * @param date current date
442      * @param offsetDriver driver for the offset parameter
443      * @param driftDriver driver for the drift parameter
444      * @return current value of the linear model
445      */
446     private double linearModel(final AbsoluteDate date,
447                                final ParameterDriver offsetDriver, final ParameterDriver driftDriver) {
448         if (offsetDriver.getReferenceDate() == null) {
449             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
450                                       offsetDriver.getName());
451         }
452         final double dt     = date.durationFrom(offsetDriver.getReferenceDate());
453         final double offset = offsetDriver.getValue();
454         final double drift  = driftDriver.getValue();
455         return dt * drift + offset;
456     }
457 
458     /** Evaluate a parametric linear model.
459      * @param date current date
460      * @param offsetDriver driver for the offset parameter
461      * @param driftDriver driver for the drift parameter
462      * @return current value of the linear model
463      * @param <T> type of the filed elements
464      */
465     private <T extends CalculusFieldElement<T>> T linearModel(final FieldAbsoluteDate<T> date,
466                                                           final ParameterDriver offsetDriver,
467                                                           final ParameterDriver driftDriver) {
468         if (offsetDriver.getReferenceDate() == null) {
469             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
470                                       offsetDriver.getName());
471         }
472         final T dt          = date.durationFrom(offsetDriver.getReferenceDate());
473         final double offset = offsetDriver.getValue();
474         final double drift  = driftDriver.getValue();
475         return dt.multiply(drift).add(offset);
476     }
477 
478     /** Evaluate a parametric linear model.
479      * @param freeParameters total number of free parameters in the gradient
480      * @param date current date
481      * @param offsetDriver driver for the offset parameter
482      * @param driftDriver driver for the drift parameter
483      * @param indices indices of the estimated parameters in derivatives computations
484      * @return current value of the linear model
485      * @since 10.2
486      */
487     private Gradient linearModel(final int freeParameters, final FieldAbsoluteDate<Gradient> date,
488                                  final ParameterDriver offsetDriver, final ParameterDriver driftDriver,
489                                  final Map<String, Integer> indices) {
490         if (offsetDriver.getReferenceDate() == null) {
491             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
492                                       offsetDriver.getName());
493         }
494         final Gradient dt     = date.durationFrom(offsetDriver.getReferenceDate());
495         final Gradient offset = offsetDriver.getValue(freeParameters, indices);
496         final Gradient drift  = driftDriver.getValue(freeParameters, indices);
497         return dt.multiply(drift).add(offset);
498     }
499 
500     /** Local time scale for estimated UT1. */
501     private class EstimatedUT1Scale extends UT1Scale {
502 
503         /** Simple constructor.
504          */
505         EstimatedUT1Scale() {
506             super(baseUT1.getEOPHistory(), baseUT1.getUTCScale());
507         }
508 
509         /** {@inheritDoc} */
510         @Override
511         public <T extends CalculusFieldElement<T>> T offsetFromTAI(final FieldAbsoluteDate<T> date) {
512             final T dut1 = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver).divide(EARTH_ANGULAR_VELOCITY);
513             return baseUT1.offsetFromTAI(date).add(dut1);
514         }
515 
516         /** {@inheritDoc} */
517         @Override
518         public TimeOffset offsetFromTAI(final AbsoluteDate date) {
519             final double dut1 = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver) / EARTH_ANGULAR_VELOCITY;
520             return baseUT1.offsetFromTAI(date).add(new TimeOffset(dut1));
521         }
522 
523         /** {@inheritDoc} */
524         @Override
525         public String getName() {
526             return baseUT1.getName() + "/estimated";
527         }
528 
529     }
530 }