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