1   /* Copyright 2002-2025 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.frames;
18  
19  import java.util.Map;
20  import java.util.concurrent.ConcurrentHashMap;
21  import java.util.function.BiFunction;
22  import java.util.function.Function;
23  
24  import org.hipparchus.CalculusFieldElement;
25  import org.hipparchus.Field;
26  import org.hipparchus.FieldElement;
27  import org.orekit.errors.OrekitIllegalArgumentException;
28  import org.orekit.errors.OrekitMessages;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.time.FieldAbsoluteDate;
31  
32  
33  /** Tridimensional references frames class.
34   *
35   * <h2> Frame Presentation </h2>
36   * <p>This class is the base class for all frames in OREKIT. The frames are
37   * linked together in a tree with some specific frame chosen as the root of the tree.
38   * Each frame is defined by {@link Transform transforms} combining any number
39   * of translations and rotations from a reference frame which is its
40   * parent frame in the tree structure.</p>
41   * <p>When we say a {@link Transform transform} t is <em>from frame<sub>A</sub>
42   * to frame<sub>B</sub></em>, we mean that if the coordinates of some absolute
43   * vector (say the direction of a distant star for example) has coordinates
44   * u<sub>A</sub> in frame<sub>A</sub> and u<sub>B</sub> in frame<sub>B</sub>,
45   * then u<sub>B</sub>={@link
46   * Transform#transformVector(org.hipparchus.geometry.euclidean.threed.Vector3D)
47   * t.transformVector(u<sub>A</sub>)}.
48   * <p>The transforms may be constant or varying, depending on the implementation of
49   * the {@link TransformProvider transform provider} used to define the frame. For simple
50   * fixed transforms, using {@link FixedTransformProvider} is sufficient. For varying
51   * transforms (time-dependent or telemetry-based for example), it may be useful to define
52   * specific implementations of {@link TransformProvider transform provider}.</p>
53   *
54   * @author Guylaine Prat
55   * @author Luc Maisonobe
56   * @author Pascal Parraud
57   */
58  public class Frame {
59  
60      /** Parent frame (only the root frame doesn't have a parent). */
61      private final Frame parent;
62  
63      /** Depth of the frame with respect to tree root. */
64      private final int depth;
65  
66      /** Provider for transform from parent frame to instance. */
67      private final TransformProvider transformProvider;
68  
69      /** Instance name. */
70      private final String name;
71  
72      /** Indicator for pseudo-inertial frames. */
73      private final boolean pseudoInertial;
74  
75      /** Cache for transforms with peer frame.
76       * @since 13.0.3
77       */
78      private CachedTransformProvider peerCache;
79  
80      /** Cache for transforms with peer frame.
81       * @since 13.0.3
82       */
83      private Map<Field<? extends CalculusFieldElement<?>>, FieldCachedTransformProvider<?>> peerFieldCache;
84  
85      /** Private constructor used only for the root frame.
86       * @param name name of the frame
87       * @param pseudoInertial true if frame is considered pseudo-inertial
88       * (i.e. suitable for propagating orbit)
89       */
90      private Frame(final String name, final boolean pseudoInertial) {
91          parent              = null;
92          depth               = 0;
93          transformProvider   = new FixedTransformProvider(Transform.IDENTITY);
94          this.name           = name;
95          this.pseudoInertial = pseudoInertial;
96          this.peerCache      = null;
97          this.peerFieldCache = null;
98      }
99  
100     /** Build a non-inertial frame from its transform with respect to its parent.
101      * <p>calling this constructor is equivalent to call
102      * <code>{link {@link #Frame(Frame, Transform, String, boolean)
103      * Frame(parent, transform, name, false)}</code>.</p>
104      * @param parent parent frame (must be non-null)
105      * @param transform transform from parent frame to instance
106      * @param name name of the frame
107      * @exception IllegalArgumentException if the parent frame is null
108      */
109     public Frame(final Frame parent, final Transform transform, final String name)
110         throws IllegalArgumentException {
111         this(parent, transform, name, false);
112     }
113 
114     /** Build a non-inertial frame from its transform with respect to its parent.
115      * <p>calling this constructor is equivalent to call
116      * <code>{link {@link #Frame(Frame, Transform, String, boolean)
117      * Frame(parent, transform, name, false)}</code>.</p>
118      * @param parent parent frame (must be non-null)
119      * @param transformProvider provider for transform from parent frame to instance
120      * @param name name of the frame
121      * @exception IllegalArgumentException if the parent frame is null
122      */
123     public Frame(final Frame parent, final TransformProvider transformProvider, final String name)
124         throws IllegalArgumentException {
125         this(parent, transformProvider, name, false);
126     }
127 
128     /** Build a frame from its transform with respect to its parent.
129      * <p>The convention for the transform is that it is from parent
130      * frame to instance. This means that the two following frames
131      * are similar:</p>
132      * <pre>
133      * Frame frame1 = new Frame(FramesFactory.getGCRF(), new Transform(t1, t2));
134      * Frame frame2 = new Frame(new Frame(FramesFactory.getGCRF(), t1), t2);
135      * </pre>
136      * @param parent parent frame (must be non-null)
137      * @param transform transform from parent frame to instance
138      * @param name name of the frame
139      * @param pseudoInertial true if frame is considered pseudo-inertial
140      * (i.e. suitable for propagating orbit)
141      * @exception IllegalArgumentException if the parent frame is null
142      */
143     public Frame(final Frame parent, final Transform transform, final String name,
144                  final boolean pseudoInertial)
145         throws IllegalArgumentException {
146         this(parent, new FixedTransformProvider(transform), name, pseudoInertial);
147     }
148 
149     /** Build a frame from its transform with respect to its parent.
150      * <p>The convention for the transform is that it is from parent
151      * frame to instance. This means that the two following frames
152      * are similar:</p>
153      * <pre>
154      * Frame frame1 = new Frame(FramesFactory.getGCRF(), new Transform(t1, t2));
155      * Frame frame2 = new Frame(new Frame(FramesFactory.getGCRF(), t1), t2);
156      * </pre>
157      * @param parent parent frame (must be non-null)
158      * @param transformProvider provider for transform from parent frame to instance
159      * @param name name of the frame
160      * @param pseudoInertial true if frame is considered pseudo-inertial
161      * (i.e. suitable for propagating orbit)
162      * @exception IllegalArgumentException if the parent frame is null
163      */
164     public Frame(final Frame parent, final TransformProvider transformProvider, final String name,
165                  final boolean pseudoInertial)
166         throws IllegalArgumentException {
167 
168         if (parent == null) {
169             throw new OrekitIllegalArgumentException(OrekitMessages.NULL_PARENT_FOR_FRAME, name);
170         }
171         this.parent            = parent;
172         this.depth             = parent.depth + 1;
173         this.transformProvider = transformProvider;
174         this.name              = name;
175         this.pseudoInertial    = pseudoInertial;
176 
177     }
178 
179     /** Get the name.
180      * @return the name
181      */
182     public String getName() {
183         return this.name;
184     }
185 
186     /** Check if the frame is pseudo-inertial.
187      * <p>Pseudo-inertial frames are frames that do have a linear motion and
188      * either do not rotate or rotate at a very low rate resulting in
189      * neglectible inertial forces. This means they are suitable for orbit
190      * definition and propagation using Newtonian mechanics. Frames that are
191      * <em>not</em> pseudo-inertial are <em>not</em> suitable for orbit
192      * definition and propagation.</p>
193      * @return true if frame is pseudo-inertial
194      */
195     public boolean isPseudoInertial() {
196         return pseudoInertial;
197     }
198 
199     /** New definition of the java.util toString() method.
200      * @return the name
201      */
202     public String toString() {
203         return this.name;
204     }
205 
206     /** Get the parent frame.
207      * @return parent frame
208      */
209     public Frame getParent() {
210         return parent;
211     }
212 
213     /** Get the depth of the frame.
214      * <p>
215      * The depth of a frame is the number of parents frame between
216      * it and the frames tree root. It is 0 for the root frame, and
217      * the depth of a frame is the depth of its parent frame plus one.
218      * </p>
219      * @return depth of the frame
220      */
221     public int getDepth() {
222         return depth;
223     }
224 
225     /** Get the n<sup>th</sup> ancestor of the frame.
226      * @param n index of the ancestor (0 is the instance, 1 is its parent,
227      * 2 is the parent of its parent...)
228      * @return n<sup>th</sup> ancestor of the frame (must be between 0
229      * and the depth of the frame)
230      * @exception IllegalArgumentException if n is larger than the depth
231      * of the instance
232      */
233     public Frame getAncestor(final int n) throws IllegalArgumentException {
234 
235         // safety check
236         if (n > depth) {
237             throw new OrekitIllegalArgumentException(OrekitMessages.FRAME_NO_NTH_ANCESTOR,
238                                                      name, depth, n);
239         }
240 
241         // go upward to find ancestor
242         Frame current = this;
243         for (int i = 0; i < n; ++i) {
244             current = current.parent;
245         }
246 
247         return current;
248 
249     }
250 
251     /** Associate this frame with a peer, caching transforms.
252      * <p>
253      * The cache is a LRU cache (Least Recently Used), so entries remain in
254      * the cache if they are used frequently, and only older entries
255      * that have not been accessed for a while will be expunged.
256      * </p>
257      * <p>
258      * If a peer was already associated with this frame, it will be overridden.
259      * </p>
260      * <p>
261      * Peering is unidirectional, i.e. if frameA is peered with frameB,
262      * then frameB may be peered with another frameC or no frame at all.
263      * This allows several frames to be peered with a pivot one (typically
264      * Earth frame and many topocentric frames all peered with one inertial frame).
265      * </p>
266      * @param peer peer frame
267      * @param cacheSize number of transforms kept in the date-based cache
268      * @since 13.0.3
269      */
270     public void setPeerCaching(final Frame peer, final int cacheSize) {
271 
272         // caching for regular dates
273         peerCache = createCache(peer, cacheSize);
274 
275         // caching for field dates
276         peerFieldCache = new ConcurrentHashMap<>();
277 
278     }
279 
280     /** Get the peer associated to this frame.
281      * @return peer associated with this frame, null if not peered at all
282      * @since 13.0.3
283      */
284     public Frame getPeer() {
285         return peerCache == null ? null : peerCache.getDestination();
286     }
287 
288     /** Create cache.
289      * @param peer peer frame
290      * @param cacheSize number of transforms kept in the date-based cache
291      * @return built cache
292      * @since 13.0.3
293      */
294     private CachedTransformProvider createCache(final Frame peer, final int cacheSize) {
295         final Function<AbsoluteDate, Transform> fullGenerator =
296                 date -> getTransformTo(peer,
297                                        Transform.IDENTITY,
298                                        frame -> frame.getTransformProvider().getTransform(date),
299                                        (t1, t2) -> new Transform(date, t1, t2),
300                                        Transform::getInverse);
301         final Function<AbsoluteDate, KinematicTransform> kinematicGenerator =
302                 date -> getTransformTo(peer,
303                                        KinematicTransform.getIdentity(),
304                                        frame -> frame.getTransformProvider().getTransform(date),
305                                        (t1, t2) -> KinematicTransform.compose(date, t1, t2),
306                                        KinematicTransform::getInverse);
307         final Function<AbsoluteDate, StaticTransform> staticGenerator =
308                 date -> getTransformTo(peer,
309                                        StaticTransform.getIdentity(),
310                                        frame -> frame.getTransformProvider().getTransform(date),
311                                        (t1, t2) -> StaticTransform.compose(date, t1, t2),
312                                        StaticTransform::getInverse);
313         return new CachedTransformProvider(this, peer,
314                                            fullGenerator, kinematicGenerator, staticGenerator,
315                                            cacheSize);
316     }
317 
318     /** Create field cache.
319      * @param <T> type of the field elements
320      * @param peer peer frame
321      * @param field field elements belong to
322      * @return built cache
323      * @since 13.0.3
324      */
325     private <T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T>
326         createCache(final Frame peer, final Field<T> field) {
327         final Function<FieldAbsoluteDate<T>, FieldTransform<T>> fullGenerator =
328                 d -> getTransformTo(peer,
329                                     FieldTransform.getIdentity(field),
330                                     frame -> frame.getTransformProvider().getTransform(d),
331                                     (FieldTransform<T> t1, FieldTransform<T> t2) -> new FieldTransform<>(d, t1, t2),
332                                     FieldTransform::getInverse);
333         final Function<FieldAbsoluteDate<T>, FieldKinematicTransform<T>> kinematicGenerator =
334                 d -> getTransformTo(peer,
335                                     FieldKinematicTransform.getIdentity(field),
336                                     frame -> frame.getTransformProvider().getTransform(d),
337                                     (t1, t2) -> FieldKinematicTransform.compose(d, t1, t2),
338                                     FieldKinematicTransform::getInverse);
339         final Function<FieldAbsoluteDate<T>, FieldStaticTransform<T>> staticGenerator =
340                 d -> getTransformTo(peer,
341                                     FieldStaticTransform.getIdentity(field),
342                                     frame -> frame.getTransformProvider().getTransform(d),
343                                     (t1, t2) -> FieldStaticTransform.compose(d, t1, t2),
344                                     FieldStaticTransform::getInverse);
345         return new FieldCachedTransformProvider<>(this, peer,
346                                                   fullGenerator, kinematicGenerator, staticGenerator,
347                                                   peerCache.getCacheSize());
348     }
349 
350     /** Get the transform from the instance to another frame.
351      * @param destination destination frame to which we want to transform vectors
352      * @param date the date (can be null if it is certain that no date dependent frame is used)
353      * @return transform from the instance to the destination frame
354      */
355     public Transform getTransformTo(final Frame destination, final AbsoluteDate date) {
356         if (peerCache != null && peerCache.getDestination() == destination) {
357             // this is our peer, we must cache the transform
358             return peerCache.getTransform(date);
359         } else {
360             // not our peer, just compute the transform and forget about it
361             return getTransformTo(
362                     destination,
363                     Transform.IDENTITY,
364                     frame -> frame.getTransformProvider().getTransform(date),
365                     (t1, t2) -> new Transform(date, t1, t2),
366                     Transform::getInverse);
367         }
368     }
369 
370     /** Get the transform from the instance to another frame.
371      * @param destination destination frame to which we want to transform vectors
372      * @param date        the date (<em>must</em> be non-null, which is a more stringent condition
373      *                    than in {@link #getTransformTo(Frame, FieldAbsoluteDate)})
374      * @param <T> the type of the field elements
375      * @return transform from the instance to the destination frame
376      */
377     public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransformTo(final Frame destination,
378                                                                                 final FieldAbsoluteDate<T> date) {
379         if (peerCache != null && peerCache.getDestination() == destination) {
380             // this is our peer, we must cache the transform
381             @SuppressWarnings("unchedked")
382             final FieldCachedTransformProvider<T> cache =
383                     (FieldCachedTransformProvider<T>) peerFieldCache.computeIfAbsent(date.getField(),
384                                                                                      field -> createCache(destination, date.getField()));
385             return cache.getTransform(date);
386         } else {
387             // not our peer, just compute the transform and forget about it
388             if (date.hasZeroField()) {
389                 return new FieldTransform<>(date.getField(), getTransformTo(destination, date.toAbsoluteDate()));
390             }
391 
392             return getTransformTo(destination,
393                                   FieldTransform.getIdentity(date.getField()),
394                                   frame -> frame.getTransformProvider().getTransform(date),
395                                   (t1, t2) -> new FieldTransform<>(date, t1, t2),
396                                   FieldTransform::getInverse);
397         }
398     }
399 
400     /**
401      * Get the kinematic portion of the transform from the instance to another
402      * frame. The returned transform is kinematic in the sense that it includes
403      * translations and rotations, with rates, but cannot transform an acceleration vector.
404      *
405      * <p>This method is often more performant than {@link
406      * #getTransformTo(Frame, AbsoluteDate)} when accelerations are not needed.
407      *
408      * @param destination destination frame to which we want to transform
409      *                    vectors
410      * @param date        the date (can be null if it is sure than no date
411      *                    dependent frame is used)
412      * @return kinematic transform from the instance to the destination frame
413      * @since 12.1
414      */
415     public KinematicTransform getKinematicTransformTo(final Frame destination, final AbsoluteDate date) {
416         if (peerCache != null && peerCache.getDestination() == destination) {
417             // this is our peer, we must cache the transform
418             return peerCache.getKinematicTransform(date);
419         } else {
420             // not our peer, just compute the transform and forget about it
421             return getTransformTo(
422                     destination,
423                     KinematicTransform.getIdentity(),
424                     frame -> frame.getTransformProvider().getKinematicTransform(date),
425                     (t1, t2) -> KinematicTransform.compose(date, t1, t2),
426                     KinematicTransform::getInverse);
427         }
428     }
429 
430     /**
431      * Get the static portion of the transform from the instance to another
432      * frame. The returned transform is static in the sense that it includes
433      * translations and rotations, but not rates.
434      *
435      * <p>This method is often more performant than {@link
436      * #getTransformTo(Frame, AbsoluteDate)} when rates are not needed.
437      *
438      * @param destination destination frame to which we want to transform
439      *                    vectors
440      * @param date        the date (can be null if it is sure than no date
441      *                    dependent frame is used)
442      * @return static transform from the instance to the destination frame
443      * @since 11.2
444      */
445     public StaticTransform getStaticTransformTo(final Frame destination,
446                                                 final AbsoluteDate date) {
447         if (peerCache != null && peerCache.getDestination() == destination) {
448             // this is our peer, we must cache the transform
449             return peerCache.getStaticTransform(date);
450         }
451         else {
452             // not our peer, just compute the transform and forget about it
453             return getTransformTo(
454                     destination,
455                     StaticTransform.getIdentity(),
456                     frame -> frame.getTransformProvider().getStaticTransform(date),
457                     (t1, t2) -> StaticTransform.compose(date, t1, t2),
458                     StaticTransform::getInverse);
459         }
460     }
461 
462     /**
463      * Get the static portion of the transform from the instance to another
464      * frame. The returned transform is static in the sense that it includes
465      * translations and rotations, but not rates.
466      *
467      * <p>This method is often more performant than {@link
468      * #getTransformTo(Frame, FieldAbsoluteDate)} when rates are not needed.
469      *
470      * <p>A first check is made on the FieldAbsoluteDate because "fielded" transforms have low-performance.<br>
471      * The date field is checked with {@link FieldElement#isZero()}.<br>
472      * If true, the un-fielded version of the transform computation is used.
473      *
474      * @param <T>         type of the elements
475      * @param destination destination frame to which we want to transform
476      *                    vectors
477      * @param date        the date (<em>must</em> be non-null, which is a more stringent condition
478      *                    than in {@link #getStaticTransformTo(Frame, AbsoluteDate)})
479      * @return static transform from the instance to the destination frame
480      * @since 12.0
481      */
482     public <T extends CalculusFieldElement<T>> FieldStaticTransform<T> getStaticTransformTo(final Frame destination,
483                                                 final FieldAbsoluteDate<T> date) {
484         if (peerCache != null && peerCache.getDestination() == destination) {
485             // this is our peer, we must cache the transform
486             @SuppressWarnings("unchedked")
487             final FieldCachedTransformProvider<T> cache =
488                     (FieldCachedTransformProvider<T>) peerFieldCache.computeIfAbsent(date.getField(),
489                                                                                      field -> createCache(destination, date.getField()));
490             return cache.getStaticTransform(date);
491         } else {
492             // not our peer, just compute the transform and forget about it
493             if (date.hasZeroField()) {
494                 // If date field is Zero, then use the un-fielded version for performances
495                 return FieldStaticTransform.of(date, getStaticTransformTo(destination, date.toAbsoluteDate()));
496 
497             }
498             else {
499                 // Use classic fielded function
500                 return getTransformTo(destination,
501                                       FieldStaticTransform.getIdentity(date.getField()),
502                                       frame -> frame.getTransformProvider().getStaticTransform(date),
503                                       (t1, t2) -> FieldStaticTransform.compose(date, t1, t2),
504                                       FieldStaticTransform::getInverse);
505             }
506         }
507     }
508 
509     /**
510      * Get the kinematic portion of the transform from the instance to another
511      * frame. The returned transform is kinematic in the sense that it includes
512      * translations and rotations, with rates, but cannot transform an acceleration vector.
513      *
514      * <p>This method is often more performant than {@link
515      * #getTransformTo(Frame, AbsoluteDate)} when accelerations are not needed.
516      * @param <T>          Type of transform returned.
517      * @param destination destination frame to which we want to transform
518      *                    vectors
519      * @param date        the date (<em>must</em> be non-null, which is a more stringent condition
520      *      *                    than in {@link #getKinematicTransformTo(Frame, AbsoluteDate)})
521      * @return kinematic transform from the instance to the destination frame
522      * @since 12.1
523      */
524     public <T extends CalculusFieldElement<T>> FieldKinematicTransform<T> getKinematicTransformTo(final Frame destination,
525                                                                                                   final FieldAbsoluteDate<T> date) {
526         if (peerCache != null && peerCache.getDestination() == destination) {
527             // this is our peer, we must cache the transform
528             @SuppressWarnings("unchedked")
529             final FieldCachedTransformProvider<T> cache =
530                     (FieldCachedTransformProvider<T>) peerFieldCache.computeIfAbsent(date.getField(),
531                                                                                      field -> createCache(destination, date.getField()));
532             return cache.getKinematicTransform(date);
533         }
534         else {
535             // not our peer, just compute the transform and forget about it
536             if (date.hasZeroField()) {
537                 // If date field is Zero, then use the un-fielded version for performances
538                 final KinematicTransform kinematicTransform = getKinematicTransformTo(destination, date.toAbsoluteDate());
539                 return FieldKinematicTransform.of(date.getField(), kinematicTransform);
540 
541             }
542             else {
543                 // Use classic fielded function
544                 return getTransformTo(destination,
545                                       FieldKinematicTransform.getIdentity(date.getField()),
546                                       frame -> frame.getTransformProvider().getKinematicTransform(date),
547                                       (t1, t2) -> FieldKinematicTransform.compose(date, t1, t2),
548                                       FieldKinematicTransform::getInverse);
549             }
550         }
551     }
552 
553     /**
554      * Generic get transform method that builds the transform from {@code this}
555      * to {@code destination}.
556      *
557      * @param destination  destination frame to which we want to transform
558      *                     vectors
559      * @param identity     transform of the given type.
560      * @param getTransform method to get a transform from a frame.
561      * @param compose      method to combine two transforms.
562      * @param inverse      method to invert a transform.
563      * @param <T>          Type of transform returned.
564      * @return composite transform.
565      */
566     private <T> T getTransformTo(final Frame destination,
567                                  final T identity,
568                                  final Function<Frame, T> getTransform,
569                                  final BiFunction<T, T, T> compose,
570                                  final Function<T, T> inverse) {
571 
572         if (this == destination) {
573             // shortcut for special case that may be frequent
574             return identity;
575         }
576 
577         // common ancestor to both frames in the frames tree
578         final Frame common = findCommon(this, destination);
579 
580         // transform from common to instance
581         T commonToInstance = identity;
582         for (Frame frame = this; frame != common; frame = frame.parent) {
583             commonToInstance = compose.apply(getTransform.apply(frame), commonToInstance);
584         }
585 
586         // transform from destination up to common
587         T commonToDestination = identity;
588         for (Frame frame = destination; frame != common; frame = frame.parent) {
589             commonToDestination = compose.apply(getTransform.apply(frame), commonToDestination);
590         }
591 
592         // transform from instance to destination via common
593         return compose.apply(inverse.apply(commonToInstance), commonToDestination);
594 
595     }
596 
597     /** Get the provider for transform from parent frame to instance.
598      * @return provider for transform from parent frame to instance
599      */
600     public TransformProvider getTransformProvider() {
601         return transformProvider;
602     }
603 
604     /** Find the deepest common ancestor of two frames in the frames tree.
605      * @param from origin frame
606      * @param to destination frame
607      * @return an ancestor frame of both <code>from</code> and <code>to</code>
608      */
609     private static Frame findCommon(final Frame from, final Frame to) {
610 
611         // select deepest frames that could be the common ancestor
612         Frame currentF = from.depth > to.depth ? from.getAncestor(from.depth - to.depth) : from;
613         Frame currentT = from.depth > to.depth ? to : to.getAncestor(to.depth - from.depth);
614 
615         // go upward until we find a match
616         while (currentF != currentT) {
617             currentF = currentF.parent;
618             currentT = currentT.parent;
619         }
620 
621         return currentF;
622 
623     }
624 
625     /** Determine if a Frame is a child of another one.
626      * @param potentialAncestor supposed ancestor frame
627      * @return true if the potentialAncestor belongs to the
628      * path from instance to the root frame, excluding itself
629      */
630     public boolean isChildOf(final Frame potentialAncestor) {
631         if (depth <= potentialAncestor.depth) {
632             return false;
633         }
634         return getAncestor(depth - potentialAncestor.depth) == potentialAncestor;
635     }
636 
637     /** Get the unique root frame.
638      * @return the unique instance of the root frame
639      */
640     public static Frame getRoot() {
641         return LazyRootHolder.INSTANCE;
642     }
643 
644     /** Get a new version of the instance, frozen with respect to a reference frame.
645      * <p>
646      * Freezing a frame consist in computing its position and orientation with respect
647      * to another frame at some freezing date and fixing them so they do not depend
648      * on time anymore. This means the frozen frame is fixed with respect to the
649      * reference frame.
650      * </p>
651      * <p>
652      * One typical use of this method is to compute an inertial launch reference frame
653      * by freezing a {@link TopocentricFrame topocentric frame} at launch date
654      * with respect to an inertial frame. Another use is to freeze an equinox-related
655      * celestial frame at a reference epoch date.
656      * </p>
657      * <p>
658      * Only the frame returned by this method is frozen, the instance by itself
659      * is not affected by calling this method and still moves freely.
660      * </p>
661      * @param reference frame with respect to which the instance will be frozen
662      * @param freezingDate freezing date
663      * @param frozenName name of the frozen frame
664      * @return a frozen version of the instance
665      */
666     public Frame getFrozenFrame(final Frame reference, final AbsoluteDate freezingDate,
667                                 final String frozenName) {
668         return new Frame(reference, reference.getTransformTo(this, freezingDate).freeze(),
669                          frozenName, reference.isPseudoInertial());
670     }
671 
672     // We use the Initialization on demand holder idiom to store
673     // the singletons, as it is both thread-safe, efficient (no
674     // synchronization) and works with all versions of java.
675 
676     /** Holder for the root frame singleton. */
677     private static class LazyRootHolder {
678 
679         /** Unique instance. */
680         private static final Frame INSTANCE = new Frame(Predefined.GCRF.getName(), true) { };
681 
682         /** Private constructor.
683          * <p>This class is a utility class, it should neither have a public
684          * nor a default constructor. This private constructor prevents
685          * the compiler from generating one automatically.</p>
686          */
687         private LazyRootHolder() {
688         }
689 
690     }
691 
692 }