FieldSpacecraftState.java

  1. /* Copyright 2002-2020 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.propagation;


  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.stream.Stream;

  24. import org.hipparchus.Field;
  25. import org.hipparchus.RealFieldElement;
  26. import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
  27. import org.hipparchus.exception.LocalizedCoreFormats;
  28. import org.hipparchus.exception.MathIllegalArgumentException;
  29. import org.hipparchus.exception.MathIllegalStateException;
  30. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  31. import org.hipparchus.util.FastMath;
  32. import org.hipparchus.util.MathArrays;
  33. import org.orekit.attitudes.FieldAttitude;
  34. import org.orekit.attitudes.LofOffset;
  35. import org.orekit.errors.OrekitException;
  36. import org.orekit.errors.OrekitIllegalArgumentException;
  37. import org.orekit.errors.OrekitIllegalStateException;
  38. import org.orekit.errors.OrekitMessages;
  39. import org.orekit.frames.FieldTransform;
  40. import org.orekit.frames.Frame;
  41. import org.orekit.frames.LOFType;
  42. import org.orekit.orbits.FieldOrbit;
  43. import org.orekit.orbits.PositionAngle;
  44. import org.orekit.time.FieldAbsoluteDate;
  45. import org.orekit.time.FieldTimeInterpolable;
  46. import org.orekit.time.FieldTimeShiftable;
  47. import org.orekit.time.FieldTimeStamped;
  48. import org.orekit.utils.FieldAbsolutePVCoordinates;
  49. import org.orekit.utils.FieldPVCoordinates;
  50. import org.orekit.utils.TimeStampedFieldPVCoordinates;


  51. /** This class is the representation of a complete state holding orbit, attitude
  52.  * and mass information at a given date.
  53.  *
  54.  * <p>It contains an {@link FieldOrbit orbital state} at a current
  55.  * {@link FieldAbsoluteDate} both handled by an {@link FieldOrbit}, plus the current
  56.  * mass and attitude. FieldOrbitand state are guaranteed to be consistent in terms
  57.  * of date and reference frame. The spacecraft state may also contain additional
  58.  * states, which are simply named double arrays which can hold any user-defined
  59.  * data.
  60.  * </p>
  61.  * <p>
  62.  * The state can be slightly shifted to close dates. This shift is based on
  63.  * a simple Keplerian model for orbit, a linear extrapolation for attitude
  64.  * taking the spin rate into account and no mass change. It is <em>not</em>
  65.  * intended as a replacement for proper orbit and attitude propagation but
  66.  * should be sufficient for either small time shifts or coarse accuracy.
  67.  * </p>
  68.  * <p>
  69.  * The instance {@code FieldSpacecraftState} is guaranteed to be immutable.
  70.  * </p>
  71.  * @see org.orekit.propagation.numerical.NumericalPropagator
  72.  * @author Fabien Maussion
  73.  * @author V&eacute;ronique Pommier-Maurussane
  74.  * @author Luc Maisonobe
  75.  * @author Vincent Mouraux
  76.  */
  77. public class FieldSpacecraftState <T extends RealFieldElement<T>>
  78.     implements FieldTimeStamped<T>, FieldTimeShiftable<FieldSpacecraftState<T>, T>, FieldTimeInterpolable<FieldSpacecraftState<T>, T> {

  79.     /** Default mass. */
  80.     private static final double DEFAULT_MASS = 1000.0;

  81.     /**
  82.      * tolerance on date comparison in {@link #checkConsistency(FieldOrbit<T>, FieldAttitude<T>)}. 100 ns
  83.      * corresponds to sub-mm accuracy at LEO orbital velocities.
  84.      */
  85.     private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;

  86.     /** Orbital state. */
  87.     private final FieldOrbit<T> orbit;

  88.     /** Trajectory state, when it is not an orbit. */
  89.     private final FieldAbsolutePVCoordinates<T> absPva;

  90.     /** FieldAttitude<T>. */
  91.     private final FieldAttitude<T> attitude;

  92.     /** Current mass (kg). */
  93.     private final T mass;

  94.     /** Additional states. */
  95.     private final Map<String, T[]> additional;

  96.     /** Build a spacecraft state from orbit only.
  97.      * <p>FieldAttitude and mass are set to unspecified non-null arbitrary values.</p>
  98.      * @param orbit the orbit
  99.      */
  100.     public FieldSpacecraftState(final FieldOrbit<T> orbit) {
  101.         this(orbit,
  102.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  103.              orbit.getA().getField().getZero().add(DEFAULT_MASS), null);
  104.     }

  105.     /** Build a spacecraft state from orbit and attitude provider.
  106.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  107.      * @param orbit the orbit
  108.      * @param attitude attitude
  109.      * @exception IllegalArgumentException if orbit and attitude dates
  110.      * or frames are not equal
  111.      */
  112.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude)
  113.         throws IllegalArgumentException {
  114.         this(orbit, attitude, orbit.getA().getField().getZero().add(DEFAULT_MASS), null);
  115.     }

  116.     /** Create a new instance from orbit and mass.
  117.      * <p>FieldAttitude law is set to an unspecified default attitude.</p>
  118.      * @param orbit the orbit
  119.      * @param mass the mass (kg)
  120.      */
  121.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final T mass) {
  122.         this(orbit,
  123.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  124.              mass, null);
  125.     }

  126.     /** Build a spacecraft state from orbit, attitude provider and mass.
  127.      * @param orbit the orbit
  128.      * @param attitude attitude
  129.      * @param mass the mass (kg)
  130.      * @exception IllegalArgumentException if orbit and attitude dates
  131.      * or frames are not equal
  132.      */
  133.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final T mass)
  134.         throws IllegalArgumentException {
  135.         this(orbit, attitude, mass, null);
  136.     }

  137.     /** Build a spacecraft state from orbit only.
  138.      * <p>FieldAttitude and mass are set to unspecified non-null arbitrary values.</p>
  139.      * @param orbit the orbit
  140.      * @param additional additional states
  141.      */
  142.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final Map<String, T[]> additional) {
  143.         this(orbit,
  144.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  145.              orbit.getA().getField().getZero().add(DEFAULT_MASS), additional);
  146.     }

  147.     /** Build a spacecraft state from orbit and attitude provider.
  148.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  149.      * @param orbit the orbit
  150.      * @param attitude attitude
  151.      * @param additional additional states
  152.      * @exception IllegalArgumentException if orbit and attitude dates
  153.      * or frames are not equal
  154.      */
  155.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final Map<String, T[]> additional)
  156.         throws IllegalArgumentException {
  157.         this(orbit, attitude, orbit.getA().getField().getZero().add(DEFAULT_MASS), additional);
  158.     }

  159.     /** Create a new instance from orbit and mass.
  160.      * <p>FieldAttitude law is set to an unspecified default attitude.</p>
  161.      * @param orbit the orbit
  162.      * @param mass the mass (kg)
  163.      * @param additional additional states
  164.      */
  165.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final T mass, final Map<String, T[]> additional) {
  166.         this(orbit,
  167.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  168.              mass, additional);
  169.     }

  170.     /** Build a spacecraft state from orbit, attitude provider and mass.
  171.      * @param orbit the orbit
  172.      * @param attitude attitude
  173.      * @param mass the mass (kg)
  174.      * @param additional additional states (may be null if no additional states are available)
  175.      * @exception IllegalArgumentException if orbit and attitude dates
  176.      * or frames are not equal
  177.      */
  178.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude,
  179.                                 final T mass, final Map<String, T[]> additional)
  180.         throws IllegalArgumentException {
  181.         checkConsistency(orbit, attitude);
  182.         this.orbit      = orbit;
  183.         this.attitude   = attitude;
  184.         this.mass       = mass;
  185.         this.absPva     = null;

  186.         if (additional == null) {
  187.             this.additional = Collections.emptyMap();
  188.         } else {

  189.             this.additional = new HashMap<String, T[]>(additional.size());
  190.             for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
  191.                 this.additional.put(entry.getKey(), entry.getValue().clone());
  192.             }
  193.         }
  194.     }

  195.     /** Convert a {@link FieldSpacecraftState}.
  196.      * @param field field to which the elements belong
  197.      * @param state state to convert
  198.      */
  199.     public FieldSpacecraftState(final Field<T> field, final SpacecraftState state) {

  200.         if (state.isOrbitDefined()) {
  201.             final double[] stateD    = new double[6];
  202.             final double[] stateDotD = state.getOrbit().hasDerivatives() ? new double[6] : null;
  203.             state.getOrbit().getType().mapOrbitToArray(state.getOrbit(), PositionAngle.TRUE, stateD, stateDotD);
  204.             final T[] stateF    = MathArrays.buildArray(field, 6);
  205.             for (int i = 0; i < stateD.length; ++i) {
  206.                 stateF[i]    = field.getZero().add(stateD[i]);
  207.             }
  208.             final T[] stateDotF;
  209.             if (stateDotD == null) {
  210.                 stateDotF = null;
  211.             } else {
  212.                 stateDotF = MathArrays.buildArray(field, 6);
  213.                 for (int i = 0; i < stateDotD.length; ++i) {
  214.                     stateDotF[i] = field.getZero().add(stateDotD[i]);
  215.                 }
  216.             }

  217.             final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, state.getDate());

  218.             this.orbit    = state.getOrbit().getType().mapArrayToOrbit(stateF, stateDotF,
  219.                                                                        PositionAngle.TRUE,
  220.                                                                        dateF, field.getZero().add(state.getMu()), state.getFrame());
  221.             this.attitude = new FieldAttitude<>(field, state.getAttitude());
  222.             this.mass     = field.getZero().add(state.getMass());
  223.             this.absPva     = null;
  224.             final Map<String, double[]> additionalD = state.getAdditionalStates();
  225.             if (additionalD.isEmpty()) {
  226.                 this.additional = Collections.emptyMap();
  227.             } else {
  228.                 this.additional = new HashMap<String, T[]>(additionalD.size());
  229.                 for (final Map.Entry<String, double[]> entry : additionalD.entrySet()) {
  230.                     final T[] array = MathArrays.buildArray(field, entry.getValue().length);
  231.                     for (int k = 0; k < array.length; ++k) {
  232.                         array[k] = field.getZero().add(entry.getValue()[k]);
  233.                     }
  234.                     this.additional.put(entry.getKey(), array);
  235.                 }
  236.             }

  237.         } else {
  238.             final T zero = field.getZero();
  239.             final T x = zero.add(state.getPVCoordinates().getPosition().getX());
  240.             final T y = zero.add(state.getPVCoordinates().getPosition().getY());
  241.             final T z = zero.add(state.getPVCoordinates().getPosition().getZ());
  242.             final T vx = zero.add(state.getPVCoordinates().getVelocity().getX());
  243.             final T vy = zero.add(state.getPVCoordinates().getVelocity().getY());
  244.             final T vz = zero.add(state.getPVCoordinates().getVelocity().getZ());
  245.             final T ax = zero.add(state.getPVCoordinates().getAcceleration().getX());
  246.             final T ay = zero.add(state.getPVCoordinates().getAcceleration().getY());
  247.             final T az = zero.add(state.getPVCoordinates().getAcceleration().getZ());
  248.             final FieldPVCoordinates<T> pva_f = new FieldPVCoordinates<>(new FieldVector3D<>(x, y, z), new FieldVector3D<>(vx, vy, vz), new FieldVector3D<>(ax, ay, az));
  249.             final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, state.getDate());
  250.             this.orbit = null;
  251.             this.attitude = new FieldAttitude<>(field, state.getAttitude());
  252.             this.mass     = field.getZero().add(state.getMass());
  253.             this.absPva   = new FieldAbsolutePVCoordinates<>(state.getFrame(), dateF, pva_f);
  254.             final Map<String, double[]> additionalD = state.getAdditionalStates();
  255.             if (additionalD.isEmpty()) {
  256.                 this.additional = Collections.emptyMap();
  257.             } else {
  258.                 this.additional = new HashMap<String, T[]>(additionalD.size());
  259.                 for (final Map.Entry<String, double[]> entry : additionalD.entrySet()) {
  260.                     final T[] array = MathArrays.buildArray(field, entry.getValue().length);
  261.                     for (int k = 0; k < array.length; ++k) {
  262.                         array[k] = field.getZero().add(entry.getValue()[k]);
  263.                     }
  264.                     this.additional.put(entry.getKey(), array);
  265.                 }
  266.             }
  267.         }
  268.     }

  269.     /** Build a spacecraft state from orbit only.
  270.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  271.      * @param absPva position-velocity-acceleration
  272.      */
  273.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva) {
  274.         this(absPva,
  275.              new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  276.              absPva.getDate().getField().getZero().add(DEFAULT_MASS), null);
  277.     }

  278.     /** Build a spacecraft state from orbit and attitude provider.
  279.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  280.      * @param absPva position-velocity-acceleration
  281.      * @param attitude attitude
  282.      * @exception IllegalArgumentException if orbit and attitude dates
  283.      * or frames are not equal
  284.      */
  285.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude)
  286.         throws IllegalArgumentException {
  287.         this(absPva, attitude, absPva.getDate().getField().getZero().add(DEFAULT_MASS), null);
  288.     }

  289.     /** Create a new instance from orbit and mass.
  290.      * <p>Attitude law is set to an unspecified default attitude.</p>
  291.      * @param absPva position-velocity-acceleration
  292.      * @param mass the mass (kg)
  293.      */
  294.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final T mass) {
  295.         this(absPva,
  296.              new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  297.              mass, null);
  298.     }

  299.     /** Build a spacecraft state from orbit, attitude provider and mass.
  300.      * @param absPva position-velocity-acceleration
  301.      * @param attitude attitude
  302.      * @param mass the mass (kg)
  303.      * @exception IllegalArgumentException if orbit and attitude dates
  304.      * or frames are not equal
  305.      */
  306.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude, final T mass)
  307.         throws IllegalArgumentException {
  308.         this(absPva, attitude, mass, null);
  309.     }

  310.     /** Build a spacecraft state from orbit only.
  311.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  312.      * @param absPva position-velocity-acceleration
  313.      * @param additional additional states
  314.      */
  315.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final Map<String, T[]> additional) {
  316.         this(absPva,
  317.              new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  318.              absPva.getDate().getField().getZero().add(DEFAULT_MASS), additional);
  319.     }

  320.     /** Build a spacecraft state from orbit and attitude provider.
  321.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  322.      * @param absPva position-velocity-acceleration
  323.      * @param attitude attitude
  324.      * @param additional additional states
  325.      * @exception IllegalArgumentException if orbit and attitude dates
  326.      * or frames are not equal
  327.      */
  328.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude, final Map<String, T[]> additional)
  329.         throws IllegalArgumentException {
  330.         this(absPva, attitude, absPva.getDate().getField().getZero().add(DEFAULT_MASS), additional);
  331.     }

  332.     /** Create a new instance from orbit and mass.
  333.      * <p>Attitude law is set to an unspecified default attitude.</p>
  334.      * @param absPva position-velocity-acceleration
  335.      * @param mass the mass (kg)
  336.      * @param additional additional states
  337.      */
  338.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final T mass, final Map<String, T[]> additional) {
  339.         this(absPva,
  340.              new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  341.              mass, additional);
  342.     }

  343.     /** Build a spacecraft state from orbit, attitude provider and mass.
  344.      * @param absPva position-velocity-acceleration
  345.      * @param attitude attitude
  346.      * @param mass the mass (kg)
  347.      * @param additional additional states (may be null if no additional states are available)
  348.      * @exception IllegalArgumentException if orbit and attitude dates
  349.      * or frames are not equal
  350.      */
  351.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude,
  352.                            final T mass, final Map<String, T[]> additional)
  353.         throws IllegalArgumentException {
  354.         checkConsistency(absPva, attitude);
  355.         this.orbit      = null;
  356.         this.absPva     = absPva;
  357.         this.attitude   = attitude;
  358.         this.mass       = mass;
  359.         if (additional == null) {
  360.             this.additional = Collections.emptyMap();
  361.         } else {
  362.             this.additional = new HashMap<String, T[]>(additional.size());
  363.             for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
  364.                 this.additional.put(entry.getKey(), entry.getValue().clone());
  365.             }
  366.         }
  367.     }

  368.     /** Add an additional state.
  369.      * <p>
  370.      * {@link FieldSpacecraftState SpacecraftState} instances are immutable,
  371.      * so this method does <em>not</em> change the instance, but rather
  372.      * creates a new instance, which has the same orbit, attitude, mass
  373.      * and additional states as the original instance, except it also
  374.      * has the specified state. If the original instance already had an
  375.      * additional state with the same name, it will be overridden. If it
  376.      * did not have any additional state with that name, the new instance
  377.      * will have one more additional state than the original instance.
  378.      * </p>
  379.      * @param name name of the additional state
  380.      * @param value value of the additional state
  381.      * @return a new instance, with the additional state added
  382.      * @see #hasAdditionalState(String)
  383.      * @see #getAdditionalState(String)
  384.      * @see #getAdditionalStates()
  385.      */
  386.     @SafeVarargs
  387.     public final FieldSpacecraftState<T> addAdditionalState(final String name, final T... value) {
  388.         final Map<String, T[]> newMap = new HashMap<String, T[]>(additional.size() + 1);
  389.         newMap.putAll(additional);
  390.         newMap.put(name, value.clone());
  391.         if (absPva == null) {
  392.             return new FieldSpacecraftState<>(orbit, attitude, mass, newMap);
  393.         } else {
  394.             return new FieldSpacecraftState<>(absPva, attitude, mass, newMap);
  395.         }
  396.     }

  397.     /** Check orbit and attitude dates are equal.
  398.      * @param orbitN the orbit
  399.      * @param attitudeN attitude
  400.      * @exception IllegalArgumentException if orbit and attitude dates
  401.      * are not equal
  402.      */
  403.     private void checkConsistency(final FieldOrbit<T> orbitN, final FieldAttitude<T> attitudeN)
  404.         throws IllegalArgumentException {
  405.         if (orbitN.getDate().durationFrom(attitudeN.getDate()).abs().getReal() >
  406.             DATE_INCONSISTENCY_THRESHOLD) {

  407.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  408.                                                      orbitN.getDate(), attitudeN.getDate());
  409.         }

  410.         if (orbitN.getFrame() != attitudeN.getReferenceFrame()) {
  411.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  412.                                                      orbitN.getFrame().getName(),
  413.                                                      attitudeN.getReferenceFrame().getName());
  414.         }
  415.     }

  416.     /** Check if the state contains an orbit part.
  417.      * <p>
  418.      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
  419.      * position-velocity-acceleration} or an {@link FieldOrbit orbit}.
  420.      * </p>
  421.      * @return true if state contains an orbit (in which case {@link #getOrbit()}
  422.      * will not throw an exception), or false if the state contains an
  423.      * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
  424.      * will not throw an exception)
  425.      */
  426.     public boolean isOrbitDefined() {
  427.         return orbit != null;
  428.     }

  429.     /**
  430.      * Check FieldAbsolutePVCoordinates and attitude dates are equal.
  431.      * @param absPva   position-velocity-acceleration
  432.      * @param attitude attitude
  433.      * @param <T>      the type of the field elements
  434.      * @exception IllegalArgumentException if orbit and attitude dates are not equal
  435.      */
  436.     private static <T extends RealFieldElement<T>> void checkConsistency(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude)
  437.         throws IllegalArgumentException {
  438.         if (FastMath.abs(absPva.getDate().durationFrom(attitude.getDate())).getReal() >
  439.             DATE_INCONSISTENCY_THRESHOLD) {
  440.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  441.                                                      absPva.getDate(), attitude.getDate());
  442.         }
  443.         if (absPva.getFrame() != attitude.getReferenceFrame()) {
  444.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  445.                                                      absPva.getFrame().getName(),
  446.                                                      attitude.getReferenceFrame().getName());
  447.         }
  448.     }

  449.     /** Get a time-shifted state.
  450.      * <p>
  451.      * The state can be slightly shifted to close dates. This shift is based on
  452.      * a simple Keplerian model for orbit, a linear extrapolation for attitude
  453.      * taking the spin rate into account and neither mass nor additional states
  454.      * changes. It is <em>not</em> intended as a replacement for proper orbit
  455.      * and attitude propagation but should be sufficient for small time shifts
  456.      * or coarse accuracy.
  457.      * </p>
  458.      * <p>
  459.      * As a rough order of magnitude, the following table shows the extrapolation
  460.      * errors obtained between this simple shift method and an {@link
  461.      * org.orekit.propagation.numerical.FieldNumericalPropagator numerical
  462.      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
  463.      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
  464.      * Beware that these results will be different for other orbits.
  465.      * </p>
  466.      * <table border="1">
  467.      * <caption>Extrapolation Error</caption>
  468.      * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
  469.      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
  470.      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
  471.      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
  472.      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
  473.      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
  474.      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
  475.      * </table>
  476.      * @param dt time shift in seconds
  477.      * @return a new state, shifted with respect to the instance (which is immutable)
  478.      * except for the mass which stay unchanged
  479.      */
  480.     public FieldSpacecraftState<T> shiftedBy(final double dt) {
  481.         if (absPva == null) {
  482.             return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  483.                     mass, additional);
  484.         } else {
  485.             return new FieldSpacecraftState<>(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
  486.                     mass, additional);
  487.         }
  488.     }

  489.     /** Get a time-shifted state.
  490.      * <p>
  491.      * The state can be slightly shifted to close dates. This shift is based on
  492.      * a simple Keplerian model for orbit, a linear extrapolation for attitude
  493.      * taking the spin rate into account and neither mass nor additional states
  494.      * changes. It is <em>not</em> intended as a replacement for proper orbit
  495.      * and attitude propagation but should be sufficient for small time shifts
  496.      * or coarse accuracy.
  497.      * </p>
  498.      * <p>
  499.      * As a rough order of magnitude, the following table shows the extrapolation
  500.      * errors obtained between this simple shift method and an {@link
  501.      * org.orekit.propagation.numerical.FieldNumericalPropagator numerical
  502.      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
  503.      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
  504.      * Beware that these results will be different for other orbits.
  505.      * </p>
  506.      * <table border="1">
  507.      * <caption>Extrapolation Error</caption>
  508.      * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
  509.      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
  510.      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
  511.      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
  512.      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
  513.      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
  514.      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
  515.      * </table>
  516.      * @param dt time shift in seconds
  517.      * @return a new state, shifted with respect to the instance (which is immutable)
  518.      * except for the mass which stay unchanged
  519.      */
  520.     public FieldSpacecraftState<T> shiftedBy(final T dt) {
  521.         if (absPva == null) {
  522.             return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  523.                     mass, additional);
  524.         } else {
  525.             return new FieldSpacecraftState<>(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
  526.                     mass, additional);
  527.         }
  528.     }

  529.     /** {@inheritDoc}
  530.      * <p>
  531.      * The additional states that are interpolated are the ones already present
  532.      * in the instance. The sample instances must therefore have at least the same
  533.      * additional states has the instance. They may have more additional states,
  534.      * but the extra ones will be ignored.
  535.      * </p>
  536.      * <p>
  537.      * The instance and all the sample instances <em>must</em> be based on similar
  538.      * trajectory data, i.e. they must either all be based on orbits or all be based
  539.      * on absolute position-velocity-acceleration. Any inconsistency will trigger
  540.      * an {@link OrekitIllegalStateException}.
  541.      * </p>
  542.      * <p>
  543.      * As this implementation of interpolation is polynomial, it should be used only
  544.      * with small samples (about 10-20 points) in order to avoid <a
  545.      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
  546.      * and numerical problems (including NaN appearing).
  547.      * </p>
  548.      * @exception OrekitIllegalStateException if some instances are not based on
  549.      * similar trajectory data
  550.      */
  551.     public FieldSpacecraftState<T> interpolate(final FieldAbsoluteDate<T> date,
  552.                                                final Stream<FieldSpacecraftState<T>> sample) {

  553.         // prepare interpolators
  554.         final List<FieldOrbit<T>> orbits;
  555.         final List<FieldAbsolutePVCoordinates<T>> absPvas;
  556.         if (isOrbitDefined()) {
  557.             orbits  = new ArrayList<FieldOrbit<T>>();
  558.             absPvas = null;
  559.         } else {
  560.             orbits  = null;
  561.             absPvas = new ArrayList<FieldAbsolutePVCoordinates<T>>();
  562.         }
  563.         final List<FieldAttitude<T>> attitudes = new ArrayList<>();
  564.         final FieldHermiteInterpolator<T> massInterpolator = new FieldHermiteInterpolator<>();
  565.         final Map<String, FieldHermiteInterpolator<T>> additionalInterpolators =
  566.                 new HashMap<String, FieldHermiteInterpolator<T>>(additional.size());
  567.         for (final String name : additional.keySet()) {
  568.             additionalInterpolators.put(name, new FieldHermiteInterpolator<>());
  569.         }

  570.         // extract sample data
  571.         sample.forEach(state -> {
  572.             final T deltaT = state.getDate().durationFrom(date);
  573.             if (isOrbitDefined()) {
  574.                 orbits.add(state.getOrbit());
  575.             } else {
  576.                 absPvas.add(state.getAbsPVA());
  577.             }
  578.             attitudes.add(state.getAttitude());
  579.             final T[] mm = MathArrays.buildArray(orbit.getA().getField(), 1);
  580.             mm[0] = state.getMass();
  581.             massInterpolator.addSamplePoint(deltaT,
  582.                                             mm);
  583.             for (final Map.Entry<String, FieldHermiteInterpolator<T>> entry : additionalInterpolators.entrySet()) {
  584.                 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
  585.             }
  586.         });

  587.         // perform interpolations
  588.         final FieldOrbit<T> interpolatedOrbit;
  589.         final FieldAbsolutePVCoordinates<T> interpolatedAbsPva;
  590.         if (isOrbitDefined()) {
  591.             interpolatedOrbit  = orbit.interpolate(date, orbits);
  592.             interpolatedAbsPva = null;
  593.         } else {
  594.             interpolatedOrbit  = null;
  595.             interpolatedAbsPva = absPva.interpolate(date, absPvas);
  596.         }
  597.         final FieldAttitude<T> interpolatedAttitude = attitude.interpolate(date, attitudes);
  598.         final T interpolatedMass       = massInterpolator.value(orbit.getA().getField().getZero())[0];
  599.         final Map<String, T[]> interpolatedAdditional;
  600.         if (additional.isEmpty()) {
  601.             interpolatedAdditional = null;
  602.         } else {
  603.             interpolatedAdditional = new HashMap<String, T[]>(additional.size());
  604.             for (final Map.Entry<String, FieldHermiteInterpolator<T>> entry : additionalInterpolators.entrySet()) {
  605.                 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(orbit.getA().getField().getZero()));
  606.             }
  607.         }

  608.         // create the complete interpolated state
  609.         if (isOrbitDefined()) {
  610.             return new FieldSpacecraftState<>(interpolatedOrbit, interpolatedAttitude,
  611.                                        interpolatedMass, interpolatedAdditional);
  612.         } else {
  613.             return new FieldSpacecraftState<>(interpolatedAbsPva, interpolatedAttitude,
  614.                                        interpolatedMass, interpolatedAdditional);
  615.         }

  616.     }

  617.     /** Get the absolute position-velocity-acceleration.
  618.      * <p>
  619.      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
  620.      * position-velocity-acceleration} or an {@link FieldOrbit orbit}. Which
  621.      * one is present can be checked using {@link #isOrbitDefined()}.
  622.      * </p>
  623.      * @return absolute position-velocity-acceleration
  624.      * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
  625.      * which mean the state rather contains an {@link FieldOrbit}
  626.      * @see #isOrbitDefined()
  627.      * @see #getOrbit()
  628.      */
  629.     public FieldAbsolutePVCoordinates<T> getAbsPVA() throws OrekitIllegalStateException {
  630.         if (absPva == null) {
  631.             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
  632.         }
  633.         return absPva;
  634.     }

  635.     /** Get the current orbit.
  636.      * <p>
  637.      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
  638.      * position-velocity-acceleration} or an {@link FieldOrbit orbit}. Which
  639.      * one is present can be checked using {@link #isOrbitDefined()}.
  640.      * </p>
  641.      * @return the orbit
  642.      * @exception OrekitIllegalStateException if orbit is null,
  643.      * which means the state rather contains an {@link FieldAbsolutePVCoordinates absolute
  644.      * position-velocity-acceleration}
  645.      * @see #isOrbitDefined()
  646.      * @see #getAbsPVA()
  647.      */
  648.     public FieldOrbit<T> getOrbit() throws OrekitIllegalStateException {
  649.         if (orbit == null) {
  650.             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
  651.         }
  652.         return orbit;
  653.     }

  654.     /** Get the date.
  655.      * @return date
  656.      */
  657.     public FieldAbsoluteDate<T> getDate() {
  658.         return (absPva == null) ? orbit.getDate() : absPva.getDate();
  659.     }

  660.     /** Get the defining frame.
  661.      * @return the frame in which state is defined
  662.      */
  663.     public Frame getFrame() {
  664.         return (absPva == null) ? orbit.getFrame() : absPva.getFrame();
  665.     }


  666.     /** Check if an additional state is available.
  667.      * @param name name of the additional state
  668.      * @return true if the additional state is available
  669.      * @see #addAdditionalState(String, RealFieldElement...)
  670.      * @see #getAdditionalState(String)
  671.      * @see #getAdditionalStates()
  672.      */
  673.     public boolean hasAdditionalState(final String name) {
  674.         return additional.containsKey(name);
  675.     }

  676.     /** Check if two instances have the same set of additional states available.
  677.      * <p>
  678.      * Only the names and dimensions of the additional states are compared,
  679.      * not their values.
  680.      * </p>
  681.      * @param state state to compare to instance
  682.      * @exception MathIllegalArgumentException if an additional state does not have
  683.      * the same dimension in both states
  684.      */
  685.     public void ensureCompatibleAdditionalStates(final FieldSpacecraftState<T> state)
  686.         throws MathIllegalArgumentException {

  687.         // check instance additional states is a subset of the other one
  688.         for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
  689.             final T[] other = state.additional.get(entry.getKey());
  690.             if (other == null) {
  691.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  692.                                           entry.getKey());
  693.             }
  694.             if (other.length != entry.getValue().length) {
  695.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  696.                                                     other.length, entry.getValue().length);
  697.             }
  698.         }

  699.         if (state.additional.size() > additional.size()) {
  700.             // the other state has more additional states
  701.             for (final String name : state.additional.keySet()) {
  702.                 if (!additional.containsKey(name)) {
  703.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  704.                                               name);
  705.                 }
  706.             }
  707.         }

  708.     }

  709.     /** Get an additional state.
  710.      * @param name name of the additional state
  711.      * @return value of the additional state
  712.           * @see #addAdditionalState(String, RealFieldElement...)
  713.      * @see #hasAdditionalState(String)
  714.      * @see #getAdditionalStates()
  715.      */
  716.     public T[] getAdditionalState(final String name) {
  717.         if (!additional.containsKey(name)) {
  718.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
  719.         }
  720.         return additional.get(name).clone();
  721.     }

  722.     /** Get an unmodifiable map of additional states.
  723.      * @return unmodifiable map of additional states
  724.      * @see #addAdditionalState(String, RealFieldElement...)
  725.      * @see #hasAdditionalState(String)
  726.      * @see #getAdditionalState(String)
  727.      */
  728.     public Map<String, T[]> getAdditionalStates() {
  729.         return Collections.unmodifiableMap(additional);
  730.     }

  731.     /** Compute the transform from state defining frame to spacecraft frame.
  732.      * <p>The spacecraft frame origin is at the point defined by the orbit,
  733.      * and its orientation is defined by the attitude.</p>
  734.      * @return transform from specified frame to current spacecraft frame
  735.      */
  736.     public FieldTransform<T> toTransform() {
  737.         final TimeStampedFieldPVCoordinates<T> pv = getPVCoordinates();
  738.         return new FieldTransform<>(pv.getDate(),
  739.                                     new FieldTransform<>(pv.getDate(), pv.negate()),
  740.                                     new FieldTransform<>(pv.getDate(), attitude.getOrientation()));
  741.     }

  742.     /** Get the central attraction coefficient.
  743.      * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the
  744.      * state is contains an absolute position-velocity-acceleration rather than an orbit
  745.      */
  746.     public T getMu() {
  747.         return (absPva == null) ? orbit.getMu() : absPva.getDate().getField().getZero().add(Double.NaN);
  748.     }

  749.     /** Get the Keplerian period.
  750.      * <p>The Keplerian period is computed directly from semi major axis
  751.      * and central acceleration constant.</p>
  752.      * @return keplerian period in seconds, or {code Double.NaN} if the
  753.      * state is contains an absolute position-velocity-acceleration rather
  754.      * than an orbit
  755.      */
  756.     public T getKeplerianPeriod() {
  757.         return (absPva == null) ? orbit.getKeplerianPeriod() : absPva.getDate().getField().getZero().add(Double.NaN);
  758.     }

  759.     /** Get the Keplerian mean motion.
  760.      * <p>The Keplerian mean motion is computed directly from semi major axis
  761.      * and central acceleration constant.</p>
  762.      * @return keplerian mean motion in radians per second, or {code Double.NaN} if the
  763.      * state is contains an absolute position-velocity-acceleration rather
  764.      * than an orbit
  765.      */
  766.     public T getKeplerianMeanMotion() {
  767.         return (absPva == null) ? orbit.getKeplerianMeanMotion() : absPva.getDate().getField().getZero().add(Double.NaN);
  768.     }

  769.     /** Get the semi-major axis.
  770.      * @return semi-major axis (m), or {code Double.NaN} if the
  771.      * state is contains an absolute position-velocity-acceleration rather
  772.      * than an orbit
  773.      */
  774.     public T getA() {
  775.         return (absPva == null) ? orbit.getA() : absPva.getDate().getField().getZero().add(Double.NaN);
  776.     }

  777.     /** Get the first component of the eccentricity vector (as per equinoctial parameters).
  778.      * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the
  779.      * state is contains an absolute position-velocity-acceleration rather
  780.      * than an orbit
  781.      * @see #getE()
  782.      */
  783.     public T getEquinoctialEx() {
  784.         return (absPva == null) ? orbit.getEquinoctialEx() : absPva.getDate().getField().getZero().add(Double.NaN);
  785.     }

  786.     /** Get the second component of the eccentricity vector (as per equinoctial parameters).
  787.      * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the
  788.      * state is contains an absolute position-velocity-acceleration rather
  789.      * than an orbit
  790.      * @see #getE()
  791.      */
  792.     public T getEquinoctialEy() {
  793.         return (absPva == null) ? orbit.getEquinoctialEy() : absPva.getDate().getField().getZero().add(Double.NaN);
  794.     }

  795.     /** Get the first component of the inclination vector (as per equinoctial parameters).
  796.      * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the
  797.      * state is contains an absolute position-velocity-acceleration rather
  798.      * than an orbit
  799.      * @see #getI()
  800.      */
  801.     public T getHx() {
  802.         return (absPva == null) ? orbit.getHx() : absPva.getDate().getField().getZero().add(Double.NaN);
  803.     }

  804.     /** Get the second component of the inclination vector (as per equinoctial parameters).
  805.      * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the
  806.      * state is contains an absolute position-velocity-acceleration rather
  807.      * than an orbit
  808.      * @see #getI()
  809.      */
  810.     public T getHy() {
  811.         return (absPva == null) ? orbit.getHy() : absPva.getDate().getField().getZero().add(Double.NaN);
  812.     }

  813.     /** Get the true latitude argument (as per equinoctial parameters).
  814.      * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the
  815.      * state is contains an absolute position-velocity-acceleration rather
  816.      * than an orbit
  817.      * @see #getLE()
  818.      * @see #getLM()
  819.      */
  820.     public T getLv() {
  821.         return (absPva == null) ? orbit.getLv() : absPva.getDate().getField().getZero().add(Double.NaN);
  822.     }

  823.     /** Get the eccentric latitude argument (as per equinoctial parameters).
  824.      * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the
  825.      * state is contains an absolute position-velocity-acceleration rather
  826.      * than an orbit
  827.      * @see #getLv()
  828.      * @see #getLM()
  829.      */
  830.     public T getLE() {
  831.         return (absPva == null) ? orbit.getLE() : absPva.getDate().getField().getZero().add(Double.NaN);
  832.     }

  833.     /** Get the mean longitude argument (as per equinoctial parameters).
  834.      * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the
  835.      * state is contains an absolute position-velocity-acceleration rather
  836.      * than an orbit
  837.      * @see #getLv()
  838.      * @see #getLE()
  839.      */
  840.     public T getLM() {
  841.         return (absPva == null) ? orbit.getLM() : absPva.getDate().getField().getZero().add(Double.NaN);
  842.     }

  843.     // Additional orbital elements

  844.     /** Get the eccentricity.
  845.      * @return eccentricity, or {code Double.NaN} if the
  846.      * state is contains an absolute position-velocity-acceleration rather
  847.      * than an orbit
  848.      * @see #getEquinoctialEx()
  849.      * @see #getEquinoctialEy()
  850.      */
  851.     public T getE() {
  852.         return (absPva == null) ? orbit.getE() : absPva.getDate().getField().getZero().add(Double.NaN);
  853.     }

  854.     /** Get the inclination.
  855.      * @return inclination (rad)
  856.      * @see #getHx()
  857.      * @see #getHy()
  858.      */
  859.     public T getI() {
  860.         return (absPva == null) ? orbit.getI() : absPva.getDate().getField().getZero().add(Double.NaN);
  861.     }

  862.     /** Get the {@link TimeStampedFieldPVCoordinates} in orbit definition frame.
  863.      * <p>
  864.      * Compute the position and velocity of the satellite. This method caches its
  865.      * results, and recompute them only when the method is called with a new value
  866.      * for mu. The result is provided as a reference to the internally cached
  867.      * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
  868.      * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
  869.      * </p>
  870.      * @return pvCoordinates in orbit definition frame
  871.      */
  872.     public TimeStampedFieldPVCoordinates<T> getPVCoordinates() {
  873.         return (absPva == null) ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
  874.     }

  875.     /** Get the {@link TimeStampedFieldPVCoordinates} in given output frame.
  876.      * <p>
  877.      * Compute the position and velocity of the satellite. This method caches its
  878.      * results, and recompute them only when the method is called with a new value
  879.      * for mu. The result is provided as a reference to the internally cached
  880.      * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
  881.      * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
  882.      * </p>
  883.      * @param outputFrame frame in which coordinates should be defined
  884.      * @return pvCoordinates in orbit definition frame
  885.      */
  886.     public TimeStampedFieldPVCoordinates<T>getPVCoordinates(final Frame outputFrame) {
  887.         return (absPva == null) ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
  888.     }

  889.     /** Get the attitude.
  890.      * @return the attitude.
  891.      */
  892.     public FieldAttitude<T> getAttitude() {
  893.         return attitude;
  894.     }

  895.     /** Gets the current mass.
  896.      * @return the mass (kg)
  897.      */
  898.     public T getMass() {
  899.         return mass;
  900.     }

  901.     /**To convert a FieldSpacecraftState instance into a SpacecraftState instance.
  902.      *
  903.      * @return SpacecraftState instance with the same properties
  904.      */
  905.     public SpacecraftState toSpacecraftState() {
  906.         final Map<String, double[]> map;
  907.         if (additional.isEmpty()) {
  908.             map = Collections.emptyMap();
  909.         } else {
  910.             map = new HashMap<>(additional.size());
  911.             for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
  912.                 final double[] array = new double[entry.getValue().length];
  913.                 for (int k = 0; k < array.length; ++k) {
  914.                     array[k] = entry.getValue()[k].getReal();
  915.                 }
  916.                 map.put(entry.getKey(), array);
  917.             }
  918.         }
  919.         if (isOrbitDefined()) {
  920.             return new SpacecraftState(orbit.toOrbit(), attitude.toAttitude(),
  921.                                        mass.getReal(), map);
  922.         } else {
  923.             return new SpacecraftState(absPva.toAbsolutePVCoordinates(),
  924.                                        attitude.toAttitude(), mass.getReal(),
  925.                                        map);
  926.         }
  927.     }

  928.     @Override
  929.     public String toString() {
  930.         return "FieldSpacecraftState{" +
  931.                 "orbit=" + orbit +
  932.                 ", attitude=" + attitude +
  933.                 ", mass=" + mass +
  934.                 ", additional=" + additional +
  935.                 '}';
  936.     }

  937. }