FieldKinematicTransform.java
/* Copyright 2022-2024 Romain Serra
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.frames;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.geometry.euclidean.threed.FieldRotation;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.util.MathArrays;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.utils.FieldPVCoordinates;
import org.orekit.utils.PVCoordinates;
import org.orekit.utils.TimeStampedFieldPVCoordinates;
import org.orekit.utils.TimeStampedPVCoordinates;
import java.util.Arrays;
/**
* A transform that only includes translation and rotation as well as their respective rates.
* It is kinematic in the sense that it cannot transform an acceleration vector.
*
* @author Romain Serra
* @see FieldStaticTransform
* @see FieldTransform
* @see KinematicTransform
* @since 12.1
*/
public interface FieldKinematicTransform<T extends CalculusFieldElement<T>> extends FieldStaticTransform<T> {
/**
* Get the identity kinematic transform.
*
* @param <T> type of the elements
* @param field field used by default
* @return identity transform.
*/
static <T extends CalculusFieldElement<T>> FieldKinematicTransform<T> getIdentity(final Field<T> field) {
return FieldTransform.getIdentity(field);
}
/** Compute a composite velocity.
* @param first first applied transform
* @param second second applied transform
* @param <T> the type of the field elements
* @return velocity part of the composite transform
*/
static <T extends CalculusFieldElement<T>> FieldVector3D<T> compositeVelocity(final FieldKinematicTransform<T> first,
final FieldKinematicTransform<T> second) {
final FieldVector3D<T> v1 = first.getVelocity();
final FieldRotation<T> r1 = first.getRotation();
final FieldVector3D<T> o1 = first.getRotationRate();
final FieldVector3D<T> p2 = second.getTranslation();
final FieldVector3D<T> v2 = second.getVelocity();
final FieldVector3D<T> crossP = FieldVector3D.crossProduct(o1, p2);
return v1.add(r1.applyInverseTo(v2.add(crossP)));
}
/** Compute a composite rotation rate.
* @param <T> type of the elements
* @param first first applied transform
* @param second second applied transform
* @return rotation rate part of the composite transform
*/
static <T extends CalculusFieldElement<T>> FieldVector3D<T> compositeRotationRate(final FieldKinematicTransform<T> first,
final FieldKinematicTransform<T> second) {
final FieldVector3D<T> o1 = first.getRotationRate();
final FieldRotation<T> r2 = second.getRotation();
final FieldVector3D<T> o2 = second.getRotationRate();
return o2.add(r2.applyTo(o1));
}
/** Transform {@link PVCoordinates}, without the acceleration vector.
* @param pv the position-velocity couple to transform.
* @return transformed position-velocity
*/
default FieldPVCoordinates<T> transformOnlyPV(final FieldPVCoordinates<T> pv) {
final FieldVector3D<T> transformedP = transformPosition(pv.getPosition());
final FieldVector3D<T> crossP = FieldVector3D.crossProduct(getRotationRate(), transformedP);
final FieldVector3D<T> transformedV = getRotation().applyTo(pv.getVelocity().add(getVelocity())).subtract(crossP);
return new FieldPVCoordinates<>(transformedP, transformedV);
}
/** Transform {@link TimeStampedPVCoordinates}, without the acceleration vector.
* <p>
* In order to allow the user more flexibility, this method does <em>not</em> check for
* consistency between the transform {@link #getDate() date} and the time-stamped
* position-velocity {@link TimeStampedPVCoordinates#getDate() date}. The returned
* value will always have the same {@link TimeStampedPVCoordinates#getDate() date} as
* the input argument, regardless of the instance {@link #getDate() date}.
* </p>
* @param pv the position-velocity couple to transform.
* @return transformed position-velocity
*/
default TimeStampedFieldPVCoordinates<T> transformOnlyPV(final TimeStampedFieldPVCoordinates<T> pv) {
final FieldVector3D<T> transformedP = transformPosition(pv.getPosition());
final FieldVector3D<T> crossP = FieldVector3D.crossProduct(getRotationRate(), transformedP);
final FieldVector3D<T> transformedV = getRotation().applyTo(pv.getVelocity().add(getVelocity())).subtract(crossP);
return new TimeStampedFieldPVCoordinates<>(pv.getDate(), transformedP, transformedV,
FieldVector3D.getZero(pv.getDate().getField()));
}
/** Compute the Jacobian of the {@link #transformOnlyPV(FieldPVCoordinates)} (FieldPVCoordinates)}
* method of the transform.
* <p>
* Element {@code jacobian[i][j]} is the derivative of Cartesian coordinate i
* of the transformed {@link FieldPVCoordinates} with respect to Cartesian coordinate j
* of the input {@link FieldPVCoordinates} in method {@link #transformOnlyPV(FieldPVCoordinates)}.
* </p>
* <p>
* This definition implies that if we define position-velocity coordinates
* <pre>
* PV₁ = transform.transformPVCoordinates(PV₀), then
* </pre>
* <p> their differentials dPV₁ and dPV₀ will obey the following relation
* where J is the matrix computed by this method:
* <pre>
* dPV₁ = J × dPV₀
* </pre>
*
* @return Jacobian matrix
*/
default T[][] getPVJacobian() {
final Field<T> field = getFieldDate().getField();
final T zero = field.getZero();
final T[][] jacobian = MathArrays.buildArray(field, 6, 6);
// elementary matrix for rotation
final T[][] mData = getRotation().getMatrix();
// dP1/dP0
System.arraycopy(mData[0], 0, jacobian[0], 0, 3);
System.arraycopy(mData[1], 0, jacobian[1], 0, 3);
System.arraycopy(mData[2], 0, jacobian[2], 0, 3);
// dP1/dV0
Arrays.fill(jacobian[0], 3, 6, zero);
Arrays.fill(jacobian[1], 3, 6, zero);
Arrays.fill(jacobian[2], 3, 6, zero);
// dV1/dP0
final FieldVector3D<T> o = getRotationRate();
final T ox = o.getX();
final T oy = o.getY();
final T oz = o.getZ();
for (int i = 0; i < 3; ++i) {
jacobian[3][i] = oz.multiply(mData[1][i]).subtract(oy.multiply(mData[2][i]));
jacobian[4][i] = ox.multiply(mData[2][i]).subtract(oz.multiply(mData[0][i]));
jacobian[5][i] = oy.multiply(mData[0][i]).subtract(ox.multiply(mData[1][i]));
}
// dV1/dV0
System.arraycopy(mData[0], 0, jacobian[3], 3, 3);
System.arraycopy(mData[1], 0, jacobian[4], 3, 3);
System.arraycopy(mData[2], 0, jacobian[5], 3, 3);
return jacobian;
}
/** Get the first time derivative of the translation.
* @return first time derivative of the translation
* @see #getTranslation()
*/
FieldVector3D<T> getVelocity();
/** Get the first time derivative of the rotation.
* <p>The norm represents the angular rate.</p>
* @return First time derivative of the rotation
* @see #getRotation()
*/
FieldVector3D<T> getRotationRate();
/**
* Get the inverse transform of the instance.
*
* @return inverse transform of the instance
*/
FieldKinematicTransform<T> getInverse();
/**
* Build a transform by combining two existing ones.
* <p>
* Note that the dates of the two existing transformed are <em>ignored</em>,
* and the combined transform date is set to the date supplied in this
* constructor without any attempt to shift the raw transforms. This is a
* design choice allowing user full control of the combination.
* </p>
*
* @param <T> type of the elements
* @param date date of the transform
* @param first first transform applied
* @param second second transform applied
* @return the newly created kinematic transform that has the same effect as
* applying {@code first}, then {@code second}.
* @see #of(FieldAbsoluteDate, FieldPVCoordinates, FieldRotation, FieldVector3D)
*/
static <T extends CalculusFieldElement<T>> FieldKinematicTransform<T> compose(final FieldAbsoluteDate<T> date,
final FieldKinematicTransform<T> first,
final FieldKinematicTransform<T> second) {
final FieldVector3D<T> composedTranslation = FieldStaticTransform.compositeTranslation(first, second);
final FieldVector3D<T> composedTranslationRate = FieldKinematicTransform.compositeVelocity(first, second);
return of(date, new FieldPVCoordinates<>(composedTranslation, composedTranslationRate),
FieldStaticTransform.compositeRotation(first, second),
FieldKinematicTransform.compositeRotationRate(first, second));
}
/**
* Create a new kinematic transform from a rotation and zero, constant translation.
*
* @param <T> type of the elements
* @param date of translation.
* @param rotation to apply after the translation. That is after translating
* applying this rotation produces positions expressed in
* the new frame.
* @param rotationRate rate of rotation
* @return the newly created kinematic transform.
* @see #of(FieldAbsoluteDate, FieldPVCoordinates, FieldRotation, FieldVector3D)
*/
static <T extends CalculusFieldElement<T>> FieldKinematicTransform<T> of(final FieldAbsoluteDate<T> date,
final FieldRotation<T> rotation,
final FieldVector3D<T> rotationRate) {
return of(date, FieldPVCoordinates.getZero(date.getField()), rotation, rotationRate);
}
/**
* Create a new kinematic transform from a translation and its rate.
*
* @param <T> type of the elements
* @param date of translation.
* @param pvCoordinates translation (with rate) to apply, expressed in the old frame. That is, the
* opposite of the coordinates of the new origin in the
* old frame.
* @return the newly created kinematic transform.
* @see #of(FieldAbsoluteDate, FieldPVCoordinates, FieldRotation, FieldVector3D)
*/
static <T extends CalculusFieldElement<T>> FieldKinematicTransform<T> of(final FieldAbsoluteDate<T> date,
final FieldPVCoordinates<T> pvCoordinates) {
final Field<T> field = date.getField();
return of(date, pvCoordinates, FieldRotation.getIdentity(field), FieldVector3D.getZero(field));
}
/**
* Create a new kinematic transform from a non-Field version.
*
* @param <T> type of the elements
* @param field field.
* @param kinematicTransform non-Field kinematic transform
* @return the newly created kinematic transform.
* @see #of(FieldAbsoluteDate, FieldPVCoordinates, FieldRotation, FieldVector3D)
*/
static <T extends CalculusFieldElement<T>> FieldKinematicTransform<T> of(final Field<T> field,
final KinematicTransform kinematicTransform) {
final FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field, kinematicTransform.getDate());
final FieldPVCoordinates<T> pvCoordinates = new FieldPVCoordinates<>(field,
new PVCoordinates(kinematicTransform.getTranslation(), kinematicTransform.getVelocity()));
final FieldRotation<T> rotation = new FieldRotation<>(field, kinematicTransform.getRotation());
final FieldVector3D<T> rotationRate = new FieldVector3D<>(field, kinematicTransform.getRotationRate());
return of(date, pvCoordinates, rotation, rotationRate);
}
/**
* Create a new kinematic transform from a translation and rotation.
*
* @param <T> type of the elements
* @param date of translation.
* @param pvCoordinates translation (with rate) to apply, expressed in the old frame. That is, the
* opposite of the coordinates of the new origin in the
* old frame.
* @param rotation to apply after the translation. That is after
* translating applying this rotation produces positions
* expressed in the new frame.
* @param rotationRate rate of rotation
* @return the newly created kinematic transform.
* @see #compose(FieldAbsoluteDate, FieldKinematicTransform, FieldKinematicTransform)
* @see #of(FieldAbsoluteDate, FieldPVCoordinates, FieldRotation, FieldVector3D)
* @see #of(FieldAbsoluteDate, FieldPVCoordinates, FieldRotation, FieldVector3D)
*/
static <T extends CalculusFieldElement<T>> FieldKinematicTransform<T> of(final FieldAbsoluteDate<T> date,
final FieldPVCoordinates<T> pvCoordinates,
final FieldRotation<T> rotation,
final FieldVector3D<T> rotationRate) {
return new FieldKinematicTransform<T>() {
@Override
public FieldKinematicTransform<T> getInverse() {
final FieldRotation<T> r = getRotation();
final FieldVector3D<T> rp = r.applyTo(getTranslation());
final FieldVector3D<T> pInv = rp.negate();
final FieldVector3D<T> crossP = FieldVector3D.crossProduct(getRotationRate(), rp);
final FieldVector3D<T> vInv = crossP.subtract(getRotation().applyTo(getVelocity()));
final FieldRotation<T> rInv = r.revert();
return FieldKinematicTransform.of(date, new FieldPVCoordinates<>(pInv, vInv),
rInv, rInv.applyTo(getRotationRate()).negate());
}
@Override
public AbsoluteDate getDate() {
return date.toAbsoluteDate();
}
@Override
public FieldAbsoluteDate<T> getFieldDate() {
return date;
}
@Override
public FieldVector3D<T> getTranslation() {
return pvCoordinates.getPosition();
}
@Override
public FieldRotation<T> getRotation() {
return rotation;
}
@Override
public FieldVector3D<T> getVelocity() {
return pvCoordinates.getVelocity();
}
@Override
public FieldVector3D<T> getRotationRate() {
return rotationRate;
}
};
}
}