1   /* Copyright 2022-2026 Thales Alenia Space
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 org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.orekit.time.AbsoluteDate;
22  import org.orekit.time.FieldAbsoluteDate;
23  
24  import java.util.Map;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.locks.StampedLock;
27  import java.util.function.Function;
28  import java.util.function.Supplier;
29  
30  /** Cache for frame transforms.
31   * <p>
32   * This class is thread-safe.
33   * </p>
34   * @author Luc Maisonobe
35   * @since 13.1
36   */
37  class PeerCache {
38  
39      /** Origin frame. */
40      private final Frame origin;
41  
42      /** Cache for transforms with peer frame. */
43      private volatile CachedTransformProvider cache;
44  
45      /** Lock for peer frame cache. */
46      private final StampedLock lock;
47  
48      /** Cache for transforms with peer frame. */
49      private volatile Map<Field<? extends CalculusFieldElement<?>>, FieldCachedTransformProvider<?>> fieldCaches;
50  
51      /** create an instance not associated with any peer.
52       * @param origin origin frame
53       */
54      PeerCache(final Frame origin) {
55          this.origin      = origin;
56          this.cache       = null;
57          this.fieldCaches = null;
58          this.lock        = new StampedLock();
59      }
60  
61      /** Associate a cache with a peer frame, caching transforms.
62       * <p>
63       * The cache is a LRU cache (Least Recently Used), so entries remain in
64       * the cache if they are used frequently, and only older entries
65       * that have not been accessed for a while will be expunged.
66       * </p>
67       * <p>
68       * If a peer was already associated with this frame, it will be overridden.
69       * </p>
70       * <p>
71       * Peering is unidirectional, i.e. if frameA is peered with frameB,
72       * then frameB may be peered with another frameC or no frame at all.
73       * This allows several frames to be peered with a pivot one (typically
74       * Earth frame and many topocentric frames all peered with one inertial frame).
75       * </p>
76       * @param peer peer frame (if null, cache is cleared)
77       * @param cacheSize number of transforms kept in the date-based cache
78       */
79      public void setPeerCaching(final Frame peer, final int cacheSize) {
80          final long lockId = lock.writeLock();
81          try {
82              if (peer == null) {
83                  // clear peering
84                  cache       = null;
85                  fieldCaches = null;
86              } else {
87                  // caching for regular dates
88                  cache       = createCache(peer, cacheSize);
89                  // caching for field dates
90                  fieldCaches = new ConcurrentHashMap<>();
91              }
92          } finally {
93              lock.unlockWrite(lockId);
94          }
95      }
96  
97      /** Get the peer associated to this frame.
98       * @return peer associated with this frame, null if not peered at all
99       */
100     Frame getPeer() {
101         if (cache == null) {
102             return null;
103         } else {
104             return optimisticRead(this::getPeerDestination);
105         }
106     }
107 
108     private Frame getPeerDestination() {
109         return cache == null ? null : cache.getDestination();
110     }
111 
112     /** Get the cached transform provider associated with this destination.
113      * @param destination destination frame to which we want to transform vectors
114      * @return cached transform provider, or null if destination is not the instance peer
115      */
116     CachedTransformProvider getCachedTransformProvider(final Frame destination) {
117         if (cache == null)  {
118             return null;
119         } else {
120             return optimisticRead(() -> getProvider(destination));
121         }
122     }
123 
124     private CachedTransformProvider getProvider(final Frame destination) {
125         final Frame peerDestination = getPeerDestination();
126         if (peerDestination == null || peerDestination != destination) {
127             return null;
128         } else {
129             return cache;
130         }
131     }
132 
133     /** Get the cached transform provider associated with this destination.
134      * @param <T> the type of the field elements
135      * @param destination destination frame to which we want to transform vectors
136      * @param field field elements belong to
137      * @return cached transform provider, or null if destination is not the instance peer
138      */
139     <T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T> getCachedTransformProvider(final Frame destination,
140                                                                                                    final Field<T> field) {
141         if (cache == null) {
142             return null;
143         } else {
144             return optimisticRead(() -> getFieldProvider(destination, field));
145         }
146     }
147 
148     private <T> T optimisticRead(final Supplier<T> readFunction) {
149         final long optimisticLockId = lock.tryOptimisticRead();
150         final T optimisticResult = readFunction.get();
151         if (lock.validate(optimisticLockId)) {
152             return optimisticResult;
153         } else {
154             final long heavyLockId = lock.readLock();
155             try {
156                 return readFunction.get();
157             } finally {
158                 lock.unlockRead(heavyLockId);
159             }
160         }
161     }
162 
163     @SuppressWarnings("unchecked")
164     private <T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T> getFieldProvider(final Frame destination, final Field<T> field) {
165         final CachedTransformProvider provider = getProvider(destination);
166         if (provider == null) {
167             return null;
168         } else {
169             return (FieldCachedTransformProvider<T>) fieldCaches.computeIfAbsent(field, f -> createCache(destination,
170                                                                                   provider.getCacheSize(),
171                                                                                   field));
172         }
173     }
174 
175     /** Create cache.
176      * @param peer peer frame
177      * @param cacheSize number of transforms kept in the date-based cache
178      * @return built cache
179      * @since 13.0.3
180      */
181     private CachedTransformProvider createCache(final Frame peer, final int cacheSize) {
182         final Function<AbsoluteDate, Transform> fullGenerator =
183                 date -> origin.getTransformTo(peer,
184                                               Transform.IDENTITY,
185                                               frame -> frame.getTransformProvider().getTransform(date),
186                                               (t1, t2) -> new Transform(date, t1, t2),
187                                               Transform::getInverse);
188         final Function<AbsoluteDate, KinematicTransform> kinematicGenerator =
189                 date -> origin.getTransformTo(peer,
190                                               KinematicTransform.getIdentity(),
191                                               frame -> frame.getTransformProvider().getTransform(date),
192                                               (t1, t2) -> KinematicTransform.compose(date, t1, t2),
193                                               KinematicTransform::getInverse);
194         final Function<AbsoluteDate, StaticTransform> staticGenerator =
195                 date -> origin.getTransformTo(peer,
196                                               StaticTransform.getIdentity(),
197                                               frame -> frame.getTransformProvider().getTransform(date),
198                                               (t1, t2) -> StaticTransform.compose(date, t1, t2),
199                                               StaticTransform::getInverse);
200         return new CachedTransformProvider(origin, peer,
201                                            fullGenerator, kinematicGenerator, staticGenerator,
202                                            cacheSize);
203     }
204 
205     /** Create field cache.
206      * @param <T> type of the field elements
207      * @param peer peer frame
208      * @param cacheSize number of transforms kept in the date-based cache
209      * @param field field elements belong to
210      * @return built cache
211      * @since 13.0.3
212      */
213     private <T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T>
214         createCache(final Frame peer, final int cacheSize, final Field<T> field) {
215         final Function<FieldAbsoluteDate<T>, FieldTransform<T>> fullGenerator =
216                 d -> origin.getTransformTo(peer,
217                                            FieldTransform.getIdentity(field),
218                                            frame -> frame.getTransformProvider().getTransform(d),
219                                            (FieldTransform<T> t1, FieldTransform<T> t2) -> new FieldTransform<>(d, t1, t2),
220                                            FieldTransform::getInverse);
221         final Function<FieldAbsoluteDate<T>, FieldKinematicTransform<T>> kinematicGenerator =
222                 d -> origin.getTransformTo(peer,
223                                            FieldKinematicTransform.getIdentity(field),
224                                            frame -> frame.getTransformProvider().getTransform(d),
225                                            (t1, t2) -> FieldKinematicTransform.compose(d, t1, t2),
226                                            FieldKinematicTransform::getInverse);
227         final Function<FieldAbsoluteDate<T>, FieldStaticTransform<T>> staticGenerator =
228                 d -> origin.getTransformTo(peer,
229                                            FieldStaticTransform.getIdentity(field),
230                                            frame -> frame.getTransformProvider().getTransform(d),
231                                            (t1, t2) -> FieldStaticTransform.compose(d, t1, t2),
232                                            FieldStaticTransform::getInverse);
233         return new FieldCachedTransformProvider<>(origin, peer,
234                                                   fullGenerator, kinematicGenerator, staticGenerator,
235                                                   cacheSize);
236     }
237 
238 }