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.orbits;
18  
19  
20  import java.util.Arrays;
21  
22  import org.hipparchus.CalculusFieldElement;
23  import org.hipparchus.Field;
24  import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative2;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.geometry.euclidean.threed.Vector3D;
27  import org.hipparchus.util.MathArrays;
28  import org.orekit.frames.FieldKinematicTransform;
29  import org.orekit.frames.Frame;
30  import org.orekit.time.AbsoluteDate;
31  import org.orekit.time.FieldAbsoluteDate;
32  import org.orekit.time.TimeOffset;
33  import org.orekit.utils.FieldPVCoordinates;
34  import org.orekit.utils.PVCoordinates;
35  import org.orekit.utils.TimeStampedFieldPVCoordinates;
36  
37  
38  /** This class holds Cartesian orbital parameters.
39  
40   * <p>
41   * The parameters used internally are the Cartesian coordinates:
42   *   <ul>
43   *     <li>x</li>
44   *     <li>y</li>
45   *     <li>z</li>
46   *     <li>xDot</li>
47   *     <li>yDot</li>
48   *     <li>zDot</li>
49   *   </ul>
50   * contained in {@link PVCoordinates}.
51  
52   * <p>
53   * Note that the implementation of this class delegates all non-Cartesian related
54   * computations ({@link #getA()}, {@link #getEquinoctialEx()}, ...) to an underlying
55   * instance of the {@link EquinoctialOrbit} class. This implies that using this class
56   * only for analytical computations which are always based on non-Cartesian
57   * parameters is perfectly possible but somewhat sub-optimal.
58   * </p>
59   * <p>
60   * The instance <code>CartesianOrbit</code> is guaranteed to be immutable.
61   * </p>
62   * @see    Orbit
63   * @see    KeplerianOrbit
64   * @see    CircularOrbit
65   * @see    EquinoctialOrbit
66   * @author Luc Maisonobe
67   * @author Guylaine Prat
68   * @author Fabien Maussion
69   * @author V&eacute;ronique Pommier-Maurussane
70   * @author Andrew Goetz
71   * @since 9.0
72   * @param <T> type of the field elements
73   */
74  public class FieldCartesianOrbit<T extends CalculusFieldElement<T>> extends FieldOrbit<T> {
75  
76      /** Indicator for non-Keplerian acceleration. */
77      private final boolean hasNonKeplerianAcceleration;
78  
79      /** Underlying equinoctial orbit to which high-level methods are delegated. */
80      private FieldEquinoctialOrbit<T> equinoctial;
81  
82      /** Constructor from Cartesian parameters.
83       *
84       * <p> The acceleration provided in {@code pvCoordinates} is accessible using
85       * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
86       * use {@code mu} and the position to compute the acceleration, including
87       * {@link #shiftedBy(CalculusFieldElement)} and {@link #getPVCoordinates(FieldAbsoluteDate, Frame)}.
88       *
89       * @param pvaCoordinates the position, velocity and acceleration of the satellite.
90       * @param frame the frame in which the {@link PVCoordinates} are defined
91       * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
92       * @param mu central attraction coefficient (m³/s²)
93       * @exception IllegalArgumentException if frame is not a {@link
94       * Frame#isPseudoInertial pseudo-inertial frame}
95       */
96      public FieldCartesianOrbit(final TimeStampedFieldPVCoordinates<T> pvaCoordinates,
97                                 final Frame frame, final T mu)
98          throws IllegalArgumentException {
99          super(pvaCoordinates, frame, mu);
100         hasNonKeplerianAcceleration = hasNonKeplerianAcceleration(pvaCoordinates, mu);
101         equinoctial = null;
102     }
103 
104     /** Constructor from Cartesian parameters.
105      *
106      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
107      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
108      * use {@code mu} and the position to compute the acceleration, including
109      * {@link #shiftedBy(CalculusFieldElement)} and {@link #getPVCoordinates(FieldAbsoluteDate, Frame)}.
110      *
111      * @param pvaCoordinates the position and velocity of the satellite.
112      * @param frame the frame in which the {@link PVCoordinates} are defined
113      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
114      * @param date date of the orbital parameters
115      * @param mu central attraction coefficient (m³/s²)
116      * @exception IllegalArgumentException if frame is not a {@link
117      * Frame#isPseudoInertial pseudo-inertial frame}
118      */
119     public FieldCartesianOrbit(final FieldPVCoordinates<T> pvaCoordinates, final Frame frame,
120                                final FieldAbsoluteDate<T> date, final T mu)
121         throws IllegalArgumentException {
122         this(new TimeStampedFieldPVCoordinates<>(date, pvaCoordinates), frame, mu);
123     }
124 
125     /** Constructor from any kind of orbital parameters.
126      * @param op orbital parameters to copy
127      */
128     public FieldCartesianOrbit(final FieldOrbit<T> op) {
129         super(op.getPVCoordinates(), op.getFrame(), op.getMu());
130         hasNonKeplerianAcceleration = op.hasNonKeplerianAcceleration();
131         if (op instanceof FieldEquinoctialOrbit) {
132             equinoctial = (FieldEquinoctialOrbit<T>) op;
133         } else if (op instanceof FieldCartesianOrbit) {
134             equinoctial = ((FieldCartesianOrbit<T>) op).equinoctial;
135         } else {
136             equinoctial = null;
137         }
138     }
139 
140     /** Constructor from Field and CartesianOrbit.
141      * <p>Build a FieldCartesianOrbit from non-Field CartesianOrbit.</p>
142      * @param field CalculusField to base object on
143      * @param op non-field orbit with only "constant" terms
144      * @since 12.0
145      */
146     public FieldCartesianOrbit(final Field<T> field, final CartesianOrbit op) {
147         super(new TimeStampedFieldPVCoordinates<>(field, op.getPVCoordinates()), op.getFrame(),
148                 field.getZero().newInstance(op.getMu()));
149         hasNonKeplerianAcceleration = op.hasNonKeplerianAcceleration();
150         if (op.isElliptical()) {
151             equinoctial = new FieldEquinoctialOrbit<>(field, new EquinoctialOrbit(op));
152         } else {
153             equinoctial = null;
154         }
155     }
156 
157     /** Constructor from Field and Orbit.
158      * <p>Build a FieldCartesianOrbit from any non-Field Orbit.</p>
159      * @param field CalculusField to base object on
160      * @param op non-field orbit with only "constant" terms
161      * @since 12.0
162      */
163     public FieldCartesianOrbit(final Field<T> field, final Orbit op) {
164         this(field, new CartesianOrbit(op));
165     }
166 
167     /** {@inheritDoc} */
168     public OrbitType getType() {
169         return OrbitType.CARTESIAN;
170     }
171 
172     /** Lazy evaluation of equinoctial parameters. */
173     private void initEquinoctial() {
174         if (equinoctial == null) {
175             if (hasNonKeplerianAcceleration()) {
176                 // getPVCoordinates includes accelerations that will be interpreted as derivatives
177                 equinoctial = new FieldEquinoctialOrbit<>(getPVCoordinates(), getFrame(), getDate(), getMu());
178             } else {
179                 // get rid of Keplerian acceleration so we don't assume
180                 // we have derivatives when in fact we don't have them
181                 final FieldPVCoordinates<T> pva = getPVCoordinates();
182                 equinoctial = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(pva.getPosition(), pva.getVelocity()),
183                                                           getFrame(), getDate(), getMu());
184             }
185         }
186     }
187 
188     /** Get the position/velocity with derivatives.
189      * @return position/velocity with derivatives
190      * @since 10.2
191      */
192     private FieldPVCoordinates<FieldUnivariateDerivative2<T>> getPVDerivatives() {
193         // PVA coordinates
194         final FieldPVCoordinates<T> pva = getPVCoordinates();
195         final FieldVector3D<T>      p   = pva.getPosition();
196         final FieldVector3D<T>      v   = pva.getVelocity();
197         final FieldVector3D<T>      a   = pva.getAcceleration();
198         // Field coordinates
199         final FieldVector3D<FieldUnivariateDerivative2<T>> pG = new FieldVector3D<>(new FieldUnivariateDerivative2<>(p.getX(), v.getX(), a.getX()),
200                                                              new FieldUnivariateDerivative2<>(p.getY(), v.getY(), a.getY()),
201                                                              new FieldUnivariateDerivative2<>(p.getZ(), v.getZ(), a.getZ()));
202         final FieldVector3D<FieldUnivariateDerivative2<T>> vG = new FieldVector3D<>(new FieldUnivariateDerivative2<>(v.getX(), a.getX(), getZero()),
203                                                              new FieldUnivariateDerivative2<>(v.getY(), a.getY(), getZero()),
204                                                              new FieldUnivariateDerivative2<>(v.getZ(), a.getZ(), getZero()));
205         return new FieldPVCoordinates<>(pG, vG);
206     }
207 
208     /** {@inheritDoc} */
209     public T getA() {
210         // lazy evaluation of semi-major axis
211         final FieldPVCoordinates<T> pva = getPVCoordinates();
212         final T r  = pva.getPosition().getNorm();
213         final T V2 = pva.getVelocity().getNorm2Sq();
214         return r.divide(r.negate().multiply(V2).divide(getMu()).add(2));
215     }
216 
217     /** {@inheritDoc} */
218     public T getADot() {
219         if (hasNonKeplerianAcceleration()) {
220             final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
221             final FieldUnivariateDerivative2<T> r  = pv.getPosition().getNorm();
222             final FieldUnivariateDerivative2<T> V2 = pv.getVelocity().getNorm2Sq();
223             final FieldUnivariateDerivative2<T> a  = r.divide(r.multiply(V2).divide(getMu()).subtract(2).negate());
224             return a.getDerivative(1);
225         } else {
226             return getZero();
227         }
228     }
229 
230     /** {@inheritDoc} */
231     public T getE() {
232         final T muA = getA().multiply(getMu());
233         if (isElliptical()) {
234             // elliptic or circular orbit
235             final FieldPVCoordinates<T> pva = getPVCoordinates();
236             final FieldVector3D<T> pvP      = pva.getPosition();
237             final FieldVector3D<T> pvV      = pva.getVelocity();
238             final T rV2OnMu = pvP.getNorm().multiply(pvV.getNorm2Sq()).divide(getMu());
239             final T eSE     = FieldVector3D.dotProduct(pvP, pvV).divide(muA.sqrt());
240             final T eCE     = rV2OnMu.subtract(1);
241             return (eCE.square().add(eSE.square())).sqrt();
242         } else {
243             // hyperbolic orbit
244             final FieldVector3D<T> pvM = getPVCoordinates().getMomentum();
245             return pvM.getNorm2Sq().divide(muA).negate().add(1).sqrt();
246         }
247     }
248 
249     /** {@inheritDoc} */
250     public T getEDot() {
251         if (hasNonKeplerianAcceleration()) {
252             final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
253             final FieldUnivariateDerivative2<T> r       = pv.getPosition().getNorm();
254             final FieldUnivariateDerivative2<T> V2      = pv.getVelocity().getNorm2Sq();
255             final FieldUnivariateDerivative2<T> rV2OnMu = r.multiply(V2).divide(getMu());
256             final FieldUnivariateDerivative2<T> a       = r.divide(rV2OnMu.negate().add(2));
257             final FieldUnivariateDerivative2<T> eSE     = FieldVector3D.dotProduct(pv.getPosition(), pv.getVelocity()).divide(a.multiply(getMu()).sqrt());
258             final FieldUnivariateDerivative2<T> eCE     = rV2OnMu.subtract(1);
259             final FieldUnivariateDerivative2<T> e       = eCE.square().add(eSE.square()).sqrt();
260             return e.getDerivative(1);
261         } else {
262             return getZero();
263         }
264     }
265 
266     /** {@inheritDoc} */
267     public T getI() {
268         return FieldVector3D.angle(new FieldVector3D<>(getZero(), getZero(), getOne()), getPVCoordinates().getMomentum());
269     }
270 
271     /** {@inheritDoc} */
272     public T getIDot() {
273         if (hasNonKeplerianAcceleration()) {
274             final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
275             final FieldVector3D<FieldUnivariateDerivative2<T>> momentum =
276                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity());
277             final FieldUnivariateDerivative2<T> i = FieldVector3D.angle(Vector3D.PLUS_K, momentum);
278             return i.getDerivative(1);
279         } else {
280             return getZero();
281         }
282     }
283 
284     /** {@inheritDoc} */
285     public T getEquinoctialEx() {
286         initEquinoctial();
287         return equinoctial.getEquinoctialEx();
288     }
289 
290     /** {@inheritDoc} */
291     public T getEquinoctialExDot() {
292         initEquinoctial();
293         return equinoctial.getEquinoctialExDot();
294     }
295 
296     /** {@inheritDoc} */
297     public T getEquinoctialEy() {
298         initEquinoctial();
299         return equinoctial.getEquinoctialEy();
300     }
301 
302     /** {@inheritDoc} */
303     public T getEquinoctialEyDot() {
304         initEquinoctial();
305         return equinoctial.getEquinoctialEyDot();
306     }
307 
308     /** {@inheritDoc} */
309     public T getHx() {
310         final FieldVector3D<T> w = getPVCoordinates().getMomentum().normalize();
311         // Check for equatorial retrograde orbit
312         final double x = w.getX().getReal();
313         final double y = w.getY().getReal();
314         final double z = w.getZ().getReal();
315         if ((x * x + y * y) == 0 && z < 0) {
316             return getZero().add(Double.NaN);
317         }
318         return w.getY().negate().divide(w.getZ().add(1));
319     }
320 
321     /** {@inheritDoc} */
322     public T getHxDot() {
323         if (hasNonKeplerianAcceleration()) {
324             final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
325             final FieldVector3D<FieldUnivariateDerivative2<T>> w =
326                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
327             // Check for equatorial retrograde orbit
328             final double x = w.getX().getValue().getReal();
329             final double y = w.getY().getValue().getReal();
330             final double z = w.getZ().getValue().getReal();
331             if ((x * x + y * y) == 0 && z < 0) {
332                 return getZero().add(Double.NaN);
333             }
334             final FieldUnivariateDerivative2<T> hx = w.getY().negate().divide(w.getZ().add(1));
335             return hx.getDerivative(1);
336         } else {
337             return getZero();
338         }
339     }
340 
341     /** {@inheritDoc} */
342     public T getHy() {
343         final FieldVector3D<T> w = getPVCoordinates().getMomentum().normalize();
344         // Check for equatorial retrograde orbit
345         final double x = w.getX().getReal();
346         final double y = w.getY().getReal();
347         final double z = w.getZ().getReal();
348         if ((x * x + y * y) == 0 && z < 0) {
349             return getZero().add(Double.NaN);
350         }
351         return  w.getX().divide(w.getZ().add(1));
352     }
353 
354     /** {@inheritDoc} */
355     public T getHyDot() {
356         if (hasNonKeplerianAcceleration()) {
357             final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
358             final FieldVector3D<FieldUnivariateDerivative2<T>> w =
359                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
360             // Check for equatorial retrograde orbit
361             final double x = w.getX().getValue().getReal();
362             final double y = w.getY().getValue().getReal();
363             final double z = w.getZ().getValue().getReal();
364             if ((x * x + y * y) == 0 && z < 0) {
365                 return getZero().add(Double.NaN);
366             }
367             final FieldUnivariateDerivative2<T> hy = w.getX().divide(w.getZ().add(1));
368             return hy.getDerivative(1);
369         } else {
370             return getZero();
371         }
372     }
373 
374     /** {@inheritDoc} */
375     public T getLv() {
376         initEquinoctial();
377         return equinoctial.getLv();
378     }
379 
380     /** {@inheritDoc} */
381     public T getLvDot() {
382         initEquinoctial();
383         return equinoctial.getLvDot();
384     }
385 
386     /** {@inheritDoc} */
387     public T getLE() {
388         initEquinoctial();
389         return equinoctial.getLE();
390     }
391 
392     /** {@inheritDoc} */
393     public T getLEDot() {
394         initEquinoctial();
395         return equinoctial.getLEDot();
396     }
397 
398     /** {@inheritDoc} */
399     public T getLM() {
400         initEquinoctial();
401         return equinoctial.getLM();
402     }
403 
404     /** {@inheritDoc} */
405     public T getLMDot() {
406         initEquinoctial();
407         return equinoctial.getLMDot();
408     }
409 
410     /** {@inheritDoc} */
411     @Override
412     public boolean hasNonKeplerianAcceleration() {
413         return hasNonKeplerianAcceleration;
414     }
415 
416     /** {@inheritDoc} */
417     @Override
418     protected FieldVector3D<T> nonKeplerianAcceleration() {
419         final T norm = getPosition().getNorm();
420         final T factor = getMu().divide(norm.square().multiply(norm));
421         return getPVCoordinates().getAcceleration().add(new FieldVector3D<>(factor, getPosition()));
422     }
423 
424     /** {@inheritDoc} */
425     protected FieldVector3D<T> initPosition() {
426         // nothing to do here, as the canonical elements are already the Cartesian ones
427         return getPVCoordinates().getPosition();
428     }
429 
430     /** {@inheritDoc} */
431     protected TimeStampedFieldPVCoordinates<T> initPVCoordinates() {
432         // nothing to do here, as the canonical elements are already the Cartesian ones
433         return getPVCoordinates();
434     }
435 
436     /** {@inheritDoc} */
437     @Override
438     public FieldCartesianOrbit<T> inFrame(final Frame inertialFrame) {
439         if (hasNonKeplerianAcceleration()) {
440             return new FieldCartesianOrbit<>(getPVCoordinates(inertialFrame), inertialFrame, getMu());
441         } else {
442             final FieldKinematicTransform<T> transform = getFrame().getKinematicTransformTo(inertialFrame, getDate());
443             return new FieldCartesianOrbit<>(transform.transformOnlyPV(getPVCoordinates()), inertialFrame, getDate(), getMu());
444         }
445     }
446 
447     /** {@inheritDoc} */
448     @Override
449     public FieldCartesianOrbit<T> shiftedBy(final TimeOffset dt) {
450         final FieldPVCoordinates<T> shiftedPV  = shiftPV(getField().getOne().newInstance(dt.toDouble()));
451         return new FieldCartesianOrbit<>(shiftedPV, getFrame(), getDate().shiftedBy(dt), getMu());
452     }
453 
454     /** {@inheritDoc} */
455     public FieldCartesianOrbit<T> shiftedBy(final double dt) {
456         return shiftedBy(getZero().newInstance(dt));
457     }
458 
459     /** {@inheritDoc} */
460     public FieldCartesianOrbit<T> shiftedBy(final T dt) {
461         final FieldPVCoordinates<T> shiftedPV = shiftPV(dt);
462         return new FieldCartesianOrbit<>(shiftedPV, getFrame(), getDate().shiftedBy(dt), getMu());
463     }
464 
465     /** Compute shifted position and velocity.
466      * @param dt time shift
467      * @return shifted position and velocity
468      */
469     private FieldPVCoordinates<T> shiftPV(final T dt) {
470         final FieldPVCoordinates<T> pvCoordinates = getPVCoordinates();
471         final FieldPVCoordinates<T> shiftedPV = KeplerianMotionCartesianUtility.predictPositionVelocity(dt,
472                 pvCoordinates.getPosition(), pvCoordinates.getVelocity(), getMu());
473 
474         if (!dt.isZero() && hasNonKeplerianAcceleration) {
475 
476             return shiftNonKeplerian(shiftedPV, dt);
477 
478         } else {
479             // don't include acceleration,
480             // so the shifted orbit is not considered to have derivatives
481             return shiftedPV;
482         }
483     }
484 
485     /** Create a 6x6 identity matrix.
486      * @return 6x6 identity matrix
487      */
488     private T[][] create6x6Identity() {
489         // this is the fastest way to set the 6x6 identity matrix
490         final T[][] identity = MathArrays.buildArray(getField(), 6, 6);
491         for (int i = 0; i < 6; i++) {
492             Arrays.fill(identity[i], getZero());
493             identity[i][i] = getOne();
494         }
495         return identity;
496     }
497 
498     @Override
499     protected T[][] computeJacobianMeanWrtCartesian() {
500         return create6x6Identity();
501     }
502 
503     @Override
504     protected T[][] computeJacobianEccentricWrtCartesian() {
505         return create6x6Identity();
506     }
507 
508     @Override
509     protected T[][] computeJacobianTrueWrtCartesian() {
510         return create6x6Identity();
511     }
512 
513     /** {@inheritDoc} */
514     public void addKeplerContribution(final PositionAngleType type, final T gm,
515                                       final T[] pDot) {
516 
517         final FieldPVCoordinates<T> pv = getPVCoordinates();
518 
519         // position derivative is velocity
520         final FieldVector3D<T> velocity = pv.getVelocity();
521         pDot[0] = pDot[0].add(velocity.getX());
522         pDot[1] = pDot[1].add(velocity.getY());
523         pDot[2] = pDot[2].add(velocity.getZ());
524 
525         // velocity derivative is Newtonian acceleration
526         final FieldVector3D<T> position = pv.getPosition();
527         final T r2         = position.getNorm2Sq();
528         final T coeff      = r2.multiply(r2.sqrt()).reciprocal().negate().multiply(gm);
529         pDot[3] = pDot[3].add(coeff.multiply(position.getX()));
530         pDot[4] = pDot[4].add(coeff.multiply(position.getY()));
531         pDot[5] = pDot[5].add(coeff.multiply(position.getZ()));
532 
533     }
534 
535     /**  Returns a string representation of this Orbit object.
536      * @return a string representation of this object
537      */
538     public String toString() {
539         // use only the six defining elements, like the other Orbit.toString() methods
540         final String comma = ", ";
541         final PVCoordinates pv = getPVCoordinates().toPVCoordinates();
542         final Vector3D p = pv.getPosition();
543         final Vector3D v = pv.getVelocity();
544         return "Cartesian parameters: {P(" +
545                 p.getX() + comma +
546                 p.getY() + comma +
547                 p.getZ() + "), V(" +
548                 v.getX() + comma +
549                 v.getY() + comma +
550                 v.getZ() + ")}";
551     }
552 
553     @Override
554     public CartesianOrbit toOrbit() {
555         final PVCoordinates pv = getPVCoordinates().toPVCoordinates();
556         final AbsoluteDate date = getPVCoordinates().getDate().toAbsoluteDate();
557         if (hasNonKeplerianAcceleration()) {
558             // getPVCoordinates includes accelerations that will be interpreted as derivatives
559             return new CartesianOrbit(pv, getFrame(), date, getMu().getReal());
560         } else {
561             // get rid of Keplerian acceleration so we don't assume
562             // we have derivatives when in fact we don't have them
563             return new CartesianOrbit(new PVCoordinates(pv.getPosition(), pv.getVelocity()),
564                                       getFrame(), date, getMu().getReal());
565         }
566     }
567 
568 }