1 /* Copyright 2022-2025 Luc Maisonobe 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.files.ccsds.ndm.adm; 18 19 import org.hipparchus.analysis.differentiation.UnivariateDerivative2; 20 import org.hipparchus.geometry.euclidean.threed.FieldVector3D; 21 import org.hipparchus.geometry.euclidean.threed.Vector3D; 22 import org.hipparchus.util.FastMath; 23 import org.hipparchus.util.SinCos; 24 import org.orekit.errors.OrekitException; 25 import org.orekit.errors.OrekitMessages; 26 27 /** Utility to extract precession from the motion of a spin axis. 28 * <p> 29 * The precession is used in {@link AttitudeType#SPIN_NUTATION} and 30 * {@link AttitudeType#SPIN_NUTATION_MOMENTUM} attitude types. CCSDS 31 * calls it nutation, but it is really precession. 32 * </p> 33 * @author Luc Maisonobe 34 * @since 12.0 35 */ 36 class PrecessionFinder { 37 38 /** Precession axis (axis of the cone described by spin). */ 39 private final Vector3D axis; 40 41 /** Precession angle (fixed cone angle between precession axis and spin axis). */ 42 private final double precessionAngle; 43 44 /** Angular velocity around the precession axis (rad/s). */ 45 private final double angularVelocity; 46 47 /** Build from spin axis motion. 48 * <p> 49 * Note that the derivatives up to second order are really needed 50 * in order to retrieve the precession motion. 51 * </p> 52 * @param spin spin axis, including value, first and second derivative 53 */ 54 PrecessionFinder(final FieldVector3D<UnivariateDerivative2> spin) { 55 56 // using a suitable coordinates frame, the spin axis can be considered to describe 57 // a cone of half aperture angle 0 ≤ η ≤ π around k axis, at angular rate ω ≥ 0 58 // with an initial position in the (+i,±k) half-plane. In this frame, the normalized 59 // direction of spin s = spin/||spin|| and its first and second time derivatives 60 // have coordinates: 61 // s(t): (sin η cos ω(t-t₀), sin η sin ω(t-t₀), cos η) 62 // ds/dt(t): (-ω sin η sin ω(t-t₀), ω sin η cos ω(t-t₀), 0) 63 // d²s/dt²(t): (-ω² sin η cos ω(t-t₀), -ω² sin η sin ω(t-t₀), 0) 64 // at initial time t = t₀ this leads to: 65 // s₀ = s(t₀): (sin η, 0, cos η) 66 // s₁ = ds/dt(t₀): (0, ω sin η, 0) 67 // s₂ = d²s/dt²(t₀): (-ω² sin η, 0, 0) 68 // however, only the spin vector itself is provided, we don't initially know the unit 69 // vectors (i, j, k) of this "suitable coordinates frame". We can however easily 70 // build another frame (u, v, w) from the normalized spin vector s as follows: 71 // u = s₀, v = s₁/||s₁||, w = u ⨯ v 72 // the coordinates of vectors u, v and w in the "suitable coordinates frame" are: 73 // u: ( sin η, 0, cos η) 74 // v: ( 0, 1, 0) 75 // w: (-cos η, 0, sin η) 76 // we can then deduce the following relations, which can be computed regardless 77 // of frames used to express the various vectors: 78 // s₁ · v = ω sin η = ||s₁|| 79 // s₂ · w = ω² sin η cos η 80 // these relations can be solved for η and ω (we know that 0 ≤ η ≤ π and ω ≥ 0): 81 // η = atan2(||s₁||², s₂ · w) 82 // ω = ||s₁|| / sin η 83 // then the k axis, which is the precession axis, can be deduced as: 84 // k = cos η u + sin η w 85 86 final UnivariateDerivative2 nS = spin.getNorm(); 87 if (nS.getValue() == 0) { 88 // special case, no motion at all, set up arbitrary values 89 axis = Vector3D.PLUS_K; 90 precessionAngle = 0.0; 91 angularVelocity = 0.0; 92 } else { 93 94 // build the derivatives vectors 95 final FieldVector3D<UnivariateDerivative2> s = spin.scalarMultiply(nS.reciprocal()); 96 final Vector3D s0 = new Vector3D(s.getX().getValue(), 97 s.getY().getValue(), 98 s.getZ().getValue()); 99 final Vector3D s1 = new Vector3D(s.getX().getFirstDerivative(), 100 s.getY().getFirstDerivative(), 101 s.getZ().getFirstDerivative()); 102 final Vector3D s2 = new Vector3D(s.getX().getSecondDerivative(), 103 s.getY().getSecondDerivative(), 104 s.getZ().getSecondDerivative()); 105 106 final double nV2 = s1.getNormSq(); 107 if (nV2 == 0.0) { 108 // special case: we have a fixed spin vector 109 axis = s0; 110 precessionAngle = 0.0; 111 angularVelocity = 0.0; 112 } else { 113 114 // check second derivatives were provided ; we do it on spin rather than s2 115 // and use test against exact 0.0 because the normalization process changes 116 // the derivatives and what we really want to check are missing derivatives 117 if (new Vector3D(spin.getX().getSecondDerivative(), 118 spin.getY().getSecondDerivative(), 119 spin.getZ().getSecondDerivative()).getNormSq() == 0) { 120 throw new OrekitException(OrekitMessages.CANNOT_ESTIMATE_PRECESSION_WITHOUT_PROPER_DERIVATIVES); 121 } 122 123 // build the unit vectors 124 final double nV = FastMath.sqrt(nV2); 125 final Vector3D v = s1.scalarMultiply(1.0 / nV); 126 final Vector3D w = Vector3D.crossProduct(s0, v); 127 128 // compute precession model 129 precessionAngle = FastMath.atan2(nV2, Vector3D.dotProduct(s2, w)); 130 final SinCos sc = FastMath.sinCos(precessionAngle); 131 angularVelocity = nV / sc.sin(); 132 axis = new Vector3D(sc.cos(), s0, sc.sin(), w); 133 134 } 135 } 136 137 } 138 139 /** Get the precession axis. 140 * @return precession axis 141 */ 142 public Vector3D getAxis() { 143 return axis; 144 } 145 146 /** Get the precession angle. 147 * @return fixed cone angle between precession axis an spin axis (rad) 148 */ 149 public double getPrecessionAngle() { 150 return precessionAngle; 151 } 152 153 /** Get the angular velocity around precession axis. 154 * @return angular velocity around precession axis (rad/s) 155 */ 156 public double getAngularVelocity() { 157 return angularVelocity; 158 } 159 160 }