From 5e2e5427be01a74d7943bcb6af0176f0776e07f5 Mon Sep 17 00:00:00 2001
From: Luc Maisonobe <luc@orekit.org>
Date: Wed, 24 Dec 2014 16:12:19 +0100
Subject: [PATCH] Added partial derivatives for line-of-sights.

The derivatives are computed with respect to transforms parameters.
This is the first step towards los calibration.
---
 .../orekit/rugged/errors/RuggedMessages.java  |   3 +-
 .../org/orekit/rugged/los/FixedRotation.java  |  82 +++++++-
 .../org/orekit/rugged/los/LOSBuilder.java     | 176 +++++++++++++-----
 .../org/orekit/rugged/los/LOSTransform.java   |  19 +-
 .../orekit/rugged/los/PolynomialRotation.java | 111 ++++++++++-
 .../orekit/rugged/los/TimeDependentLOS.java   |  13 +-
 .../los/TimeIndependentLOSTransform.java      |  26 ++-
 .../orekit/rugged/utils/ParameterType.java    |  32 ++++
 .../orekit/rugged/utils/ParametricModel.java  |  56 ++++++
 .../org/orekit/rugged/RuggedMessages_de.utf8  |   3 +
 .../org/orekit/rugged/RuggedMessages_en.utf8  |   3 +
 .../org/orekit/rugged/RuggedMessages_es.utf8  |   3 +
 .../org/orekit/rugged/RuggedMessages_fr.utf8  |   3 +
 .../org/orekit/rugged/RuggedMessages_gl.utf8  |   3 +
 .../org/orekit/rugged/RuggedMessages_it.utf8  |   3 +
 .../org/orekit/rugged/RuggedMessages_ro.utf8  |   3 +
 src/site/xdoc/changes.xml                     |   4 +
 .../rugged/errors/RuggedMessagesTest.java     |   2 +-
 18 files changed, 479 insertions(+), 66 deletions(-)
 create mode 100644 src/main/java/org/orekit/rugged/utils/ParameterType.java
 create mode 100644 src/main/java/org/orekit/rugged/utils/ParametricModel.java

diff --git a/src/main/java/org/orekit/rugged/errors/RuggedMessages.java b/src/main/java/org/orekit/rugged/errors/RuggedMessages.java
index 031d104b..94e97731 100644
--- a/src/main/java/org/orekit/rugged/errors/RuggedMessages.java
+++ b/src/main/java/org/orekit/rugged/errors/RuggedMessages.java
@@ -64,7 +64,8 @@ public enum RuggedMessages implements Localizable {
     LINE_OF_SIGHT_NEVER_CROSSES_ALTITUDE("line-of-sight never crosses altitude {0}"),
     DEM_ENTRY_POINT_IS_BEHIND_SPACECRAFT("line-of-sight enters the Digital Elevation Model behind spacecraft!"),
     FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP("frame {0} does not match frame {1} from interpolator dump"),
-    NOT_INTERPOLATOR_DUMP_DATA("data is not an interpolator dump");
+    NOT_INTERPOLATOR_DUMP_DATA("data is not an interpolator dump"),
+    ESTIMATED_PARAMETERS_NUMBER_MISMATCH("number of estimated parameters mismatch, expected {0} got {1}");
 
     // CHECKSTYLE: resume JavadocVariable check
 
diff --git a/src/main/java/org/orekit/rugged/los/FixedRotation.java b/src/main/java/org/orekit/rugged/los/FixedRotation.java
index 0e73277e..bfd41e9b 100644
--- a/src/main/java/org/orekit/rugged/los/FixedRotation.java
+++ b/src/main/java/org/orekit/rugged/los/FixedRotation.java
@@ -16,8 +16,14 @@
  */
 package org.orekit.rugged.los;
 
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.geometry.euclidean.threed.FieldRotation;
+import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
 import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.orekit.rugged.errors.RuggedException;
+import org.orekit.rugged.errors.RuggedMessages;
+import org.orekit.rugged.utils.ParameterType;
 
 /** {@link TimeIndependentLOSTransform LOS transform} based on a fixed rotation.
  * @author Luc Maisonobe
@@ -25,14 +31,76 @@ import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
  */
 public class FixedRotation implements TimeIndependentLOSTransform {
 
+    /** Parameters type. */
+    private final ParameterType type;
+
     /** Underlying rotation. */
-    private final Rotation rotation;
+    private Rotation rotation;
+
+    /** Underlying rotation with derivatives. */
+    private FieldRotation<DerivativeStructure> rDS;
 
     /** Simple constructor.
-     * @param rotation rotation to apply
+     * <p>
+     * The single parameter is the rotation angle.
+     * </p>
+     * @param type parameter type
+     * @param axis rotation axis
+     * @param angle rotation angle
      */
-    public FixedRotation(final Rotation rotation) {
-        this.rotation = rotation;
+    public FixedRotation(final ParameterType type, final Vector3D axis, final double angle) {
+        this.type     = type;
+        this.rotation = new Rotation(axis, angle);
+        this.rDS      = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getNbEstimatedParameters() {
+        return type == ParameterType.FIXED ? 0 : 1;
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * The single parameter is the rotation angle.
+     * </p>
+     */
+    @Override
+    public void getEstimatedParameters(final double[] parameters, final int start, final int length)
+        throws RuggedException {
+        checkSlice(length);
+        parameters[start] = rotation.getAngle();
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * The single parameter is the rotation angle.
+     * </p>
+     */
+    @Override
+    public void setEstimatedParameters(final double[] parameters, final int start, final int length)
+        throws RuggedException {
+        checkSlice(length);
+        final Vector3D axis = rotation.getAxis();
+        rotation = new Rotation(axis, parameters[start]);
+        final FieldVector3D<DerivativeStructure> axisDS =
+                new FieldVector3D<DerivativeStructure>(new DerivativeStructure(parameters.length, 1, axis.getX()),
+                                                       new DerivativeStructure(parameters.length, 1, axis.getY()),
+                                                       new DerivativeStructure(parameters.length, 1, axis.getZ()));
+        final DerivativeStructure angleDS = new DerivativeStructure(parameters.length, 1, start, parameters[start]);
+        rDS = new FieldRotation<DerivativeStructure>(axisDS, angleDS);
+    }
+
+    /** Check the number of parameters of an array slice.
+     * @param length number of elements in the array slice to consider
+     * @exception RuggedException if the size of the slice does not match
+     * the {@link #getNbEstimatedParameters() number of estimated parameters}
+     */
+    private void checkSlice(final int length) throws RuggedException {
+        if (getNbEstimatedParameters() != length) {
+            throw new RuggedException(RuggedMessages.ESTIMATED_PARAMETERS_NUMBER_MISMATCH,
+                                      getNbEstimatedParameters(), length);
+        }
     }
 
     /** {@inheritDoc} */
@@ -41,4 +109,10 @@ public class FixedRotation implements TimeIndependentLOSTransform {
         return rotation.applyTo(los);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public FieldVector3D<DerivativeStructure> transformLOS(final int i, final FieldVector3D<DerivativeStructure> los) {
+        return rDS.applyTo(los);
+    }
+
 }
diff --git a/src/main/java/org/orekit/rugged/los/LOSBuilder.java b/src/main/java/org/orekit/rugged/los/LOSBuilder.java
index fd034b44..b25fadf1 100644
--- a/src/main/java/org/orekit/rugged/los/LOSBuilder.java
+++ b/src/main/java/org/orekit/rugged/los/LOSBuilder.java
@@ -17,12 +17,15 @@
 package org.orekit.rugged.los;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
 import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
-import org.orekit.rugged.api.Rugged;
+import org.orekit.rugged.errors.RuggedException;
+import org.orekit.rugged.errors.RuggedMessages;
+import org.orekit.rugged.utils.ParametricModel;
 import org.orekit.time.AbsoluteDate;
 
 /** Builder for lines-of-sight list.
@@ -110,69 +113,43 @@ public class LOSBuilder {
             this.transform = transform;
         }
 
-        /** Get the underlying transform.
-         * @return underlying time-independent transform
-         */
-        public TimeIndependentLOSTransform getTransform() {
-            return transform;
-        }
-
         /** {@inheritDoc} */
         @Override
-        public Vector3D transformLOS(final int i, final Vector3D los, final AbsoluteDate date) {
-            return transform.transformLOS(i, los);
+        public int getNbEstimatedParameters() {
+            return transform.getNbEstimatedParameters();
         }
 
-    }
-
-    /** Implement time-independent LOS by applying all registered transforms at construction. */
-    private static class FixedLOS implements TimeDependentLOS {
-
-        /** Fixed direction for los. */
-        private final Vector3D[] los;
-
-        /** Simple constructor.
-         * @param raw raw directions
-         * @param transforms transforms to apply (must be time-independent!)
-         */
-        public FixedLOS(final List<Vector3D> raw, final List<LOSTransform> transforms) {
-
-            los = new Vector3D[raw.size()];
-
-            // apply transforms only once
-            for (int i = 0; i < raw.size(); ++i) {
-                Vector3D v = raw.get(i);
-                for (final LOSTransform transform : transforms) {
-                    v = ((TransformAdapter) transform).getTransform().transformLOS(i, v);
-                }
-                los[i] = v.normalize();
-            }
-
+        /** {@inheritDoc} */
+        @Override
+        public void getEstimatedParameters(final double[] parameters, final int start, final int length)
+            throws RuggedException {
+            transform.getEstimatedParameters(parameters, start, length);
         }
 
         /** {@inheritDoc} */
-        public int getNbPixels() {
-            return los.length;
+        @Override
+        public void setEstimatedParameters(final double[] parameters, final int start, final int length)
+            throws RuggedException {
+            transform.setEstimatedParameters(parameters, start, length);
         }
 
         /** {@inheritDoc} */
-        public Vector3D getLOS(final int index, final AbsoluteDate date) {
-            return los[index];
+        @Override
+        public Vector3D transformLOS(final int i, final Vector3D los, final AbsoluteDate date) {
+            return transform.transformLOS(i, los);
         }
 
         /** {@inheritDoc} */
-        public FieldVector3D<DerivativeStructure> getLOS(final int index, final AbsoluteDate date,
-                                                         final double[] parameters) {
-            // fixed LOS do not depend on any parameters
-            return new FieldVector3D<DerivativeStructure>(new DerivativeStructure(parameters.length, 1, los[index].getX()),
-                                                          new DerivativeStructure(parameters.length, 1, los[index].getY()),
-                                                          new DerivativeStructure(parameters.length, 1, los[index].getZ()));
+        @Override
+        public FieldVector3D<DerivativeStructure> transformLOS(final int i, final FieldVector3D<DerivativeStructure> los,
+                                                               final AbsoluteDate date) {
+            return transform.transformLOS(i, los);
         }
 
     }
 
-    /** Implement time-dependent LOS by applying all registered transforms at runtime. */
-    private static class TransformsSequenceLOS implements TimeDependentLOS {
+    /** Implement time-independent LOS by recomputing directions by applying all transforms each time. */
+    private static class TransformsSequenceLOS implements ParametricModel, TimeDependentLOS {
 
         /** Raw direction. */
         private final Vector3D[] raw;
@@ -180,6 +157,9 @@ public class LOSBuilder {
         /** Transforms to be applied. */
         private final LOSTransform[] transforms;
 
+        /** Total number of estimated parameters. */
+        private final int total;
+
         /** Simple constructor.
          * @param raw raw directions
          * @param transforms transforms to apply
@@ -189,19 +169,76 @@ public class LOSBuilder {
             // copy the lists, to ensure immutability of the built object,
             // in case addTransform is called again after build
             // or the raw LOS list is changed by caller
-
             this.raw = new Vector3D[raw.size()];
             for (int i = 0; i < raw.size(); ++i) {
                 this.raw[i] = raw.get(i);
             }
 
             this.transforms = new LOSTransform[transforms.size()];
+            int n = 0;
             for (int i = 0; i < transforms.size(); ++i) {
-                this.transforms[i] = transforms.get(i);
+                final LOSTransform transform = transforms.get(i);
+                this.transforms[i] = transform;
+                n += transform.getNbEstimatedParameters();
+            }
+            this.total = n;
+
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int getNbEstimatedParameters() {
+            return total;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void getEstimatedParameters(final double[] parameters, final int start, final int length)
+            throws RuggedException {
+
+            // global check
+            checkSlice(length);
+
+            // retrieve parameters for all transforms
+            int offset = 0;
+            for (final ParametricModel model : transforms) {
+                final int n = model.getNbEstimatedParameters();
+                model.getEstimatedParameters(parameters, offset, n);
+                offset += n;
+            }
+
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void setEstimatedParameters(final double[] parameters, final int start, final int length)
+            throws RuggedException {
+
+            // global check
+            checkSlice(length);
+
+            // set parameters for all transforms
+            int offset = 0;
+            for (final ParametricModel model : transforms) {
+                final int n = model.getNbEstimatedParameters();
+                model.setEstimatedParameters(parameters, offset, n);
+                offset += n;
             }
 
         }
 
+        /** Check the number of parameters of an array slice.
+         * @param length number of elements in the array slice to consider
+         * @exception RuggedException if the size of the slice does not match
+         * the {@link #getNbEstimatedParameters() number of estimated parameters}
+         */
+        private void checkSlice(final int length) throws RuggedException {
+            if (getNbEstimatedParameters() != length) {
+                throw new RuggedException(RuggedMessages.ESTIMATED_PARAMETERS_NUMBER_MISMATCH,
+                                          getNbEstimatedParameters(), length);
+            }
+        }
+
         /** {@inheritDoc} */
         public int getNbPixels() {
             return raw.length;
@@ -218,6 +255,7 @@ public class LOSBuilder {
         }
 
         /** {@inheritDoc} */
+        @Override
         public FieldVector3D<DerivativeStructure> getLOS(final int index, final AbsoluteDate date,
                                                          final double[] parameters) {
             // non-adjustable LOS do not depend on any parameters
@@ -229,4 +267,44 @@ public class LOSBuilder {
 
     }
 
+    /** Implement time-independent LOS by computing directions only when parameters are changed. */
+    private static class FixedLOS extends TransformsSequenceLOS {
+
+        /** transformed direction for los. */
+        private final Vector3D[] transformed;
+
+        /** Simple constructor.
+         * @param raw raw directions
+         * @param transforms transforms to apply (must be time-independent!)
+         */
+        public FixedLOS(final List<Vector3D> raw, final List<LOSTransform> transforms) {
+            super(raw, transforms);
+            transformed = new Vector3D[raw.size()];
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void setEstimatedParameters(final double[] parameters, final int start, final int length)
+            throws RuggedException {
+
+            // update the transforms
+            super.setEstimatedParameters(parameters, start, length);
+
+            // unset the directions, to ensure they get recomputed if needed
+            Arrays.fill(transformed, null);
+
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector3D getLOS(final int index, final AbsoluteDate date) {
+            if (transformed[index] == null) {
+                // recompute the transformed los direction only if needed
+                transformed[index] = super.getLOS(index, date);
+            }
+            return transformed[index];
+        }
+
+    }
+
 }
diff --git a/src/main/java/org/orekit/rugged/los/LOSTransform.java b/src/main/java/org/orekit/rugged/los/LOSTransform.java
index ff085f72..840d1a6c 100644
--- a/src/main/java/org/orekit/rugged/los/LOSTransform.java
+++ b/src/main/java/org/orekit/rugged/los/LOSTransform.java
@@ -16,14 +16,17 @@
  */
 package org.orekit.rugged.los;
 
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.orekit.rugged.utils.ParametricModel;
 import org.orekit.time.AbsoluteDate;
 
 /** Interface for lines-of-sight tranforms.
  * @author Luc Maisonobe
  * @see LOSBuilder
  */
-interface LOSTransform {
+interface LOSTransform extends ParametricModel {
 
     /** Transform a line-of-sight.
      * @param i los pixel index
@@ -33,4 +36,18 @@ interface LOSTransform {
      */
     Vector3D transformLOS(int i, Vector3D los, AbsoluteDate date);
 
+    /** Transform a line-of-sight and its partial derivatives.
+     * <p>
+     * This method is used for LOS calibration purposes. It allows to compute
+     * the Jacobian matrix of the LOS with respect to the parameters, which
+     * are typically polynomials coefficients representing rotation angles.
+     * These polynomials can be used for example to model thermo-elastic effects.
+     * </p>
+     * @param index los pixel index
+     * @param date date
+     * @param los line-of-sight to transform
+     * @return line of sight, and its first partial derivatives with respect to the parameters
+     */
+    FieldVector3D<DerivativeStructure> transformLOS(int index, FieldVector3D<DerivativeStructure> los, AbsoluteDate date);
+
 }
diff --git a/src/main/java/org/orekit/rugged/los/PolynomialRotation.java b/src/main/java/org/orekit/rugged/los/PolynomialRotation.java
index cc97a47f..8502dd92 100644
--- a/src/main/java/org/orekit/rugged/los/PolynomialRotation.java
+++ b/src/main/java/org/orekit/rugged/los/PolynomialRotation.java
@@ -16,9 +16,15 @@
  */
 package org.orekit.rugged.los;
 
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
 import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math3.geometry.euclidean.threed.FieldRotation;
+import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
 import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.orekit.rugged.errors.RuggedException;
+import org.orekit.rugged.errors.RuggedMessages;
+import org.orekit.rugged.utils.ParameterType;
 import org.orekit.time.AbsoluteDate;
 
 /** {@link LOSTransform LOS transform} based on a rotation with polynomial angle.
@@ -27,33 +33,124 @@ import org.orekit.time.AbsoluteDate;
  */
 public class PolynomialRotation implements LOSTransform {
 
+    /** Parameters type. */
+    private final ParameterType type;
+
     /** Rotation axis. */
     private final Vector3D axis;
 
     /** Rotation angle polynomial. */
-    private final PolynomialFunction angle;
+    private PolynomialFunction angle;
+
+    /** Rotation axis and derivatives. */
+    private FieldVector3D<DerivativeStructure> axisDS;
+
+    /** Rotation angle polynomial and derivatives. */
+    private DerivativeStructure[] angleDS;
 
     /** Reference date for polynomial evaluation. */
     private final AbsoluteDate referenceDate;
 
     /** Simple constructor.
+     * <p>
+     * The angle of the rotation is evaluated as a polynomial in t,
+     * where t is the duration in seconds between evaluation date and
+     * reference date. The parameters are the polynomial coefficients,
+     * with the constant term at index 0.
+     * </p>
+     * @param type parameters type
      * @param axis rotation axis
-     * @param angle rotation angle as a polynomial in t, where t
-     * is the duration in seconds between evaluation date and reference date
      * @param referenceDate reference date for the polynomial angle
+     * @param angleCoeffs polynomial coefficients of the polynomial angle,
+     * with the constant term at index 0
      */
-    public PolynomialRotation(final Vector3D axis,
-                              final PolynomialFunction angle,
-                              final AbsoluteDate referenceDate) {
+    public PolynomialRotation(final ParameterType type,
+                              final Vector3D axis,
+                              final AbsoluteDate referenceDate,
+                              final double ... angleCoeffs) {
+        this.type          = type;
         this.axis          = axis;
-        this.angle         = angle;
+        this.angle         = new PolynomialFunction(angleCoeffs);
         this.referenceDate = referenceDate;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public int getNbEstimatedParameters() {
+        return type == ParameterType.FIXED ? 0 : (angle.degree() + 1);
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * The parameters are the polynomial coefficients,
+     * with the constant term at index 0.
+     * </p>
+     */
+    @Override
+    public void getEstimatedParameters(final double[] parameters, final int start, final int length)
+        throws RuggedException {
+        checkSlice(length);
+        System.arraycopy(angle.getCoefficients(), 0, length, start, length);
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * The parameters are the polynomial coefficients,
+     * with the constant term at index 0.
+     * </p>
+     */
+    @Override
+    public void setEstimatedParameters(final double[] parameters, final int start, final int length)
+        throws RuggedException {
+
+        checkSlice(length);
+
+        // regular rotation
+        angle = new PolynomialFunction(parameters);
+
+        // prepare components to compute rotation with derivatives
+        axisDS = new FieldVector3D<DerivativeStructure>(new DerivativeStructure(parameters.length, 1, axis.getX()),
+                                                        new DerivativeStructure(parameters.length, 1, axis.getY()),
+                                                        new DerivativeStructure(parameters.length, 1, axis.getZ()));
+        angleDS = new DerivativeStructure[length];
+        for (int i = 0; i < length; ++i) {
+            angleDS[i] = new DerivativeStructure(parameters.length, 1, start + i, parameters[start + i]);
+        }
+
+    }
+
+    /** Check the number of parameters of an array slice.
+     * @param length number of elements in the array slice to consider
+     * @exception RuggedException if the size of the slice does not match
+     * the {@link #getNbEstimatedParameters() number of estimated parameters}
+     */
+    private void checkSlice(final int length) throws RuggedException {
+        if (getNbEstimatedParameters() != length) {
+            throw new RuggedException(RuggedMessages.ESTIMATED_PARAMETERS_NUMBER_MISMATCH,
+                                      getNbEstimatedParameters(), length);
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     public Vector3D transformLOS(final int i, final Vector3D los, final AbsoluteDate date) {
         return new Rotation(axis, angle.value(date.durationFrom(referenceDate))).applyTo(los);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public FieldVector3D<DerivativeStructure> transformLOS(final int i, final FieldVector3D<DerivativeStructure> los,
+                                                           final AbsoluteDate date) {
+
+        // evaluate polynomial, with all its partial derivatives
+        final double t = date.durationFrom(referenceDate);
+        DerivativeStructure alpha = axisDS.getX().getField().getZero();
+        for (int k = angleDS.length - 1; k >= 0; --k) {
+            alpha = alpha.multiply(t).add(angleDS[k]);
+        }
+
+        return new FieldRotation<DerivativeStructure>(axisDS, alpha).applyTo(los);
+
+    }
+
 }
diff --git a/src/main/java/org/orekit/rugged/los/TimeDependentLOS.java b/src/main/java/org/orekit/rugged/los/TimeDependentLOS.java
index 0479a9b1..8b7891ed 100644
--- a/src/main/java/org/orekit/rugged/los/TimeDependentLOS.java
+++ b/src/main/java/org/orekit/rugged/los/TimeDependentLOS.java
@@ -19,13 +19,14 @@ package org.orekit.rugged.los;
 import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
 import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.orekit.rugged.utils.ParametricModel;
 import org.orekit.time.AbsoluteDate;
 
 /** Interface representing a line-of-sight which depends on time.
  * @see LineSensor
  * @author Luc Maisonobe
  */
-public interface TimeDependentLOS {
+public interface TimeDependentLOS extends ParametricModel {
 
     /** Get the number of pixels.
      * @return number of pixels
@@ -44,7 +45,15 @@ public interface TimeDependentLOS {
      * This method is used for LOS calibration purposes. It allows to compute
      * the Jacobian matrix of the LOS with respect to the parameters, which
      * are typically polynomials coefficients representing rotation angles.
-     * These polynomials can be used for example to model thermo-elestic effects.
+     * These polynomials can be used for example to model thermo-elastic effects.
+     * </p>
+     * <p>
+     * Note that in order for the partial derivatives to be properly set up, the
+     * {@link #setEstimatedParameters(double[], int, int) setEstimatedParameters}
+     * <em>must</em> have been called at least once before this method and its
+     * {@code start} parameter will be used to ensure the partial derivatives are
+     * ordered in the same way in the returned vector as they were in the set
+     * parameters.
      * </p>
      * @param index los pixel index
      * @param date date
diff --git a/src/main/java/org/orekit/rugged/los/TimeIndependentLOSTransform.java b/src/main/java/org/orekit/rugged/los/TimeIndependentLOSTransform.java
index c75de032..bee09499 100644
--- a/src/main/java/org/orekit/rugged/los/TimeIndependentLOSTransform.java
+++ b/src/main/java/org/orekit/rugged/los/TimeIndependentLOSTransform.java
@@ -16,13 +16,16 @@
  */
 package org.orekit.rugged.los;
 
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.orekit.rugged.utils.ParametricModel;
 
 /** Interface for lines-of-sight tranforms that do not depend on time.
  * @author Luc Maisonobe
  * @see LOSBuilder
  */
-interface TimeIndependentLOSTransform {
+interface TimeIndependentLOSTransform extends ParametricModel {
 
     /** Transform a line-of-sight.
      * @param i los pixel index
@@ -31,4 +34,25 @@ interface TimeIndependentLOSTransform {
      */
     Vector3D transformLOS(int i, Vector3D los);
 
+    /** Transform a line-of-sight and its partial derivatives.
+     * <p>
+     * This method is used for LOS calibration purposes. It allows to compute
+     * the Jacobian matrix of the LOS with respect to the parameters, which
+     * are typically polynomials coefficients representing rotation angles.
+     * These polynomials can be used for example to model thermo-elastic effects.
+     * </p>
+     * <p>
+     * Note that in order for the partial derivatives to be properly set up, the
+     * {@link #setEstimatedParameters(double[], int, int) setEstimatedParameters}
+     * <em>must</em> have been called at least once before this method and its
+     * {@code start} parameter will be used to ensure the partial derivatives are
+     * ordered in the same way in the returned vector as they were in the set
+     * parameters.
+     * </p>
+     * @param index los pixel index
+     * @param los line-of-sight to transform
+     * @return line of sight, and its first partial derivatives with respect to the parameters
+     */
+    FieldVector3D<DerivativeStructure> transformLOS(int index, FieldVector3D<DerivativeStructure> los);
+
 }
diff --git a/src/main/java/org/orekit/rugged/utils/ParameterType.java b/src/main/java/org/orekit/rugged/utils/ParameterType.java
new file mode 100644
index 00000000..d7bfba16
--- /dev/null
+++ b/src/main/java/org/orekit/rugged/utils/ParameterType.java
@@ -0,0 +1,32 @@
+/* Copyright 2013-2014 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (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.rugged.utils;
+
+
+/** Type of parameter.
+ * @author Luc Maisonobe
+ * @see ParametricModel
+ */
+public enum ParameterType {
+
+    /** Type of parameter which cannot be changed at all. */
+    FIXED,
+
+    /** Type of parameter which can be estimated, i.e. its value can change. */
+    ESTIMATED;
+
+}
diff --git a/src/main/java/org/orekit/rugged/utils/ParametricModel.java b/src/main/java/org/orekit/rugged/utils/ParametricModel.java
new file mode 100644
index 00000000..0e3be2a7
--- /dev/null
+++ b/src/main/java/org/orekit/rugged/utils/ParametricModel.java
@@ -0,0 +1,56 @@
+/* Copyright 2013-2014 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (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.rugged.utils;
+
+import org.orekit.rugged.errors.RuggedException;
+
+
+/** Interface for models that have parameters.
+ * <p>
+ * The parameters are typically polynomial coefficients, for example
+ * to model thermo-elastic deformations of the sensor or spacecraft.
+ * </p>
+ * @author Luc Maisonobe
+ */
+public interface ParametricModel {
+
+    /** Get the number of estimated parameters.
+     * @return number of estimated parameters
+     */
+    int getNbEstimatedParameters();
+
+    /** Get the current values of the estimated parameters.
+     * @param parameters global array where to put the parameters
+     * @param start start index of the array slice to consider
+     * @param length number of elements in the array slice to consider
+     * @exception RuggedException if the size of the slice does not match
+     * the {@link #getNbEstimatedParameters() number of estimated parameters}
+     */
+    void getEstimatedParameters(double[] parameters, int start, int length)
+        throws RuggedException;
+
+    /** Set new values for the estimated parameters.
+     * @param parameters global array containing the parameters to set (among others)
+     * @param start start index of the array slice to consider
+     * @param length number of elements in the array slice to consider
+     * @exception RuggedException if the size of the slice does not match
+     * the {@link #getNbEstimatedParameters() number of estimated parameters}
+     */
+    void setEstimatedParameters(double[] parameters, int start, int length)
+        throws RuggedException;
+
+}
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
index 62efdb1d..999dbcf4 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
@@ -42,3 +42,6 @@ FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP = der Rahmen {0} passt nicht zum Rahmen {
 
 # data is not an interpolator dump
 NOT_INTERPOLATOR_DUMP_DATA = die Angabe ist nicht das Backup der Interpolation
+
+# number of estimated parameters mismatch, expected {0} got {1}
+ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
index 1c6fb33b..bcf1729e 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
@@ -42,3 +42,6 @@ FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP = frame {0} does not match frame {1} from
 
 # data is not an interpolator dump
 NOT_INTERPOLATOR_DUMP_DATA = data is not an interpolator dump
+
+# number of estimated parameters mismatch, expected {0} got {1}
+ESTIMATED_PARAMETERS_NUMBER_MISMATCH = number of estimated parameters mismatch, expected {0} got {1}
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
index 3fe2e545..3c1ff3eb 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
@@ -42,3 +42,6 @@ FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP = el sistema de referencia {0} no corresp
 
 # data is not an interpolator dump
 NOT_INTERPOLATOR_DUMP_DATA = los datos no están en el volcado de datos del interpolador
+
+# number of estimated parameters mismatch, expected {0} got {1}
+ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
index 96e63b5a..d8842ce1 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
@@ -42,3 +42,6 @@ FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP = le repère {0} ne correspond pas au rep
 
 # data is not an interpolator dump
 NOT_INTERPOLATOR_DUMP_DATA = les données ne correspondent pas à une sauvegarde d''interpolateur
+
+# number of estimated parameters mismatch, expected {0} got {1}
+ESTIMATED_PARAMETERS_NUMBER_MISMATCH = incohérence du nombre de paramètres estimés, {0} attendus, {1} renseignés
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
index f09aa9a8..a841bad3 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
@@ -42,3 +42,6 @@ FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP = o sistema de referencia {0} non corresp
 
 # data is not an interpolator dump
 NOT_INTERPOLATOR_DUMP_DATA = os datos non están no baleirado de datos do interpolador
+
+# number of estimated parameters mismatch, expected {0} got {1}
+ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
index d894f090..69a983c8 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
@@ -42,3 +42,6 @@ FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP = il riferimento {0} non corrisponde al r
 
 # data is not an interpolator dump
 NOT_INTERPOLATOR_DUMP_DATA = i dati non corrispondono a un salvatagggio d''interpolatore
+
+# number of estimated parameters mismatch, expected {0} got {1}
+ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8
index 5dc5eae4..33d24adc 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8
@@ -42,3 +42,6 @@ FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP = sistemul de coordonate {0} nu se potriv
 
 # data is not an interpolator dump
 NOT_INTERPOLATOR_DUMP_DATA = datele nu reprezintă o copie de siguranță a interpolatorului
+
+# number of estimated parameters mismatch, expected {0} got {1}
+ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index 6c35af4d..8402869d 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -22,6 +22,10 @@
   <body>
     <release version="1.0" date="TBD"
              description="TBD">
+      <action dev="luc" type="add" >
+        Added partial derivatives for line-of-sights with respect to transforms parameters.
+        This is the first step towards los calibration.
+      </action>
       <action dev="luc" type="add" >
         Added sequences of transforms for lines-of-sight.
       </action>
diff --git a/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java b/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java
index 41f8cc7f..eb225981 100644
--- a/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java
+++ b/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java
@@ -30,7 +30,7 @@ public class RuggedMessagesTest {
 
     @Test
     public void testMessageNumber() {
-        Assert.assertEquals(15, RuggedMessages.values().length);
+        Assert.assertEquals(16, RuggedMessages.values().length);
     }
 
     @Test
-- 
GitLab