GroundStation.java

  1. /* Copyright 2002-2017 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.estimation.measurements;

  18. import java.util.Map;

  19. import org.hipparchus.Field;
  20. import org.hipparchus.analysis.differentiation.DSFactory;
  21. import org.hipparchus.analysis.differentiation.DerivativeStructure;
  22. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  23. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  24. import org.hipparchus.geometry.euclidean.threed.Rotation;
  25. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  26. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  27. import org.hipparchus.util.FastMath;
  28. import org.orekit.bodies.BodyShape;
  29. import org.orekit.bodies.FieldGeodeticPoint;
  30. import org.orekit.bodies.GeodeticPoint;
  31. import org.orekit.bodies.OneAxisEllipsoid;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitMessages;
  34. import org.orekit.frames.FieldTransform;
  35. import org.orekit.frames.Frame;
  36. import org.orekit.frames.TopocentricFrame;
  37. import org.orekit.frames.Transform;
  38. import org.orekit.time.AbsoluteDate;
  39. import org.orekit.time.FieldAbsoluteDate;
  40. import org.orekit.utils.ParameterDriver;

  41. /** Class modeling a ground station that can perform some measurements.
  42.  * <p>
  43.  * This class adds a position offset parameter to a base {@link TopocentricFrame
  44.  * topocentric frame}.
  45.  * </p>
  46.  * <p>
  47.  * Since 9.0, this class also adds parameters for an additional polar motion
  48.  * and an additional prime meridian orientation. Since these parameters will
  49.  * have the same name for all ground stations, they will be managed consistently
  50.  * and allow to estimate Earth orientation precisely (this is needed for precise
  51.  * orbit determination). The polar motion and prime meridian orientation will
  52.  * be applied <em>after</em> regular Earth orientation parameters, so the value
  53.  * of the estimated parameters will be correction to EOP, they will not be the
  54.  * complete EOP values by themselves. Basically, this means that for Earth, the
  55.  * following transforms are applied in order, between inertial frame and ground
  56.  * station frame (for non-Earth based ground stations, different precession nutation
  57.  * models and associated planet oritentation parameters would be applied, if available):
  58.  * </p>
  59.  * <ol>
  60.  *   <li>precession/nutation, as theoretical model plus celestial pole EOP parameters</li>
  61.  *   <li>body rotation, as theoretical model plus prime meridian EOP parameters</li>
  62.  *   <li>polar motion, which is only from EOP parameters (no theoretical models)</li>
  63.  *   <li>additional body rotation, controlled by {@link #getPrimeMeridianOffsetDriver()} and {@link #getPrimeMeridianDriftDriver()}</li>
  64.  *   <li>additional polar motion, controlled by {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  65.  *   {@link #getPolarOffsetYDriver()} and {@link #getPolarDriftYDriver()}</li>
  66.  *   <li>station position offset, controlled by {@link #getEastOffsetDriver()},
  67.  *   {@link #getNorthOffsetDriver()} and {@link #getZenithOffsetDriver()}</li>
  68.  * </ol>
  69.  * @author Luc Maisonobe
  70.  * @since 8.0
  71.  */
  72. public class GroundStation {

  73.     /** Suffix for ground station position offset parameter name. */
  74.     public static final String OFFSET_SUFFIX = "-offset";

  75.     /** Suffix for ground station intermediate frame name. */
  76.     public static final String INTERMEDIATE_SUFFIX = "-intermediate";

  77.     /** Offsets scaling factor.
  78.      * <p>
  79.      * We use a power of 2 (in fact really 1.0 here) to avoid numeric noise introduction
  80.      * in the multiplications/divisions sequences.
  81.      * </p>
  82.      */
  83.     private static final double OFFSET_SCALE = FastMath.scalb(1.0, 0);

  84.     /** Angular scaling factor.
  85.      * <p>
  86.      * We use a power of 2 to avoid numeric noise introduction
  87.      * in the multiplications/divisions sequences.
  88.      * </p>
  89.      */
  90.     private static final double ANGULAR_SCALE = FastMath.scalb(1.0, -22);

  91.     /** Base frame associated with the station. */
  92.     private final TopocentricFrame baseFrame;

  93.     /** Driver for position offset along the East axis. */
  94.     private final ParameterDriver eastOffsetDriver;

  95.     /** Driver for position offset along the North axis. */
  96.     private final ParameterDriver northOffsetDriver;

  97.     /** Driver for position offset along the zenith axis. */
  98.     private final ParameterDriver zenithOffsetDriver;

  99.     /** Driver for prime meridian offset. */
  100.     private final ParameterDriver primeMeridianOffsetDriver;

  101.     /** Driver for prime meridian drift. */
  102.     private final ParameterDriver primeMeridianDriftDriver;

  103.     /** Driver for pole offset along X. */
  104.     private final ParameterDriver polarOffsetXDriver;

  105.     /** Driver for pole drift along X. */
  106.     private final ParameterDriver polarDriftXDriver;

  107.     /** Driver for pole offset along Y. */
  108.     private final ParameterDriver polarOffsetYDriver;

  109.     /** Driver for pole drift along Y. */
  110.     private final ParameterDriver polarDriftYDriver;

  111.     /** Simple constructor.
  112.      * <p>
  113.      * The initial values for the pole and prime meridian parametric linear models
  114.      * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
  115.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  116.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
  117.      * The initial values for the station offset model ({@link #getEastOffsetDriver()},
  118.      * {@link #getNorthOffsetDriver()}, {@link #getZenithOffsetDriver()}) are set to 0.
  119.      * This implies that as long as these values are not changed, the offset frame is
  120.      * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
  121.      * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
  122.      * </p>
  123.      * @param baseFrame base frame associated with the station, without *any* parametric
  124.      * model (no station offset, no polar motion, no meridian shift)
  125.      * @exception OrekitException if some frame transforms cannot be computed
  126.      * or if the ground station is not defined on a {@link OneAxisEllipsoid ellipsoid}.
  127.      */
  128.     public GroundStation(final TopocentricFrame baseFrame)
  129.         throws OrekitException {

  130.         this.baseFrame = baseFrame;

  131.         this.eastOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-East",
  132.                                                     0.0, OFFSET_SCALE,
  133.                                                     Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  134.         this.northOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-North",
  135.                                                      0.0, OFFSET_SCALE,
  136.                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  137.         this.zenithOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-Zenith",
  138.                                                       0.0, OFFSET_SCALE,
  139.                                                       Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  140.         this.primeMeridianOffsetDriver = new ParameterDriver("prime-meridian-offset",
  141.                                                              0.0, ANGULAR_SCALE,
  142.                                                             -FastMath.PI, FastMath.PI);

  143.         this.primeMeridianDriftDriver = new ParameterDriver("prime-meridian-drift",
  144.                                                             0.0, ANGULAR_SCALE,
  145.                                                             Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  146.         this.polarOffsetXDriver = new ParameterDriver("polar-offset-X",
  147.                                                       0.0, ANGULAR_SCALE,
  148.                                                       -FastMath.PI, FastMath.PI);

  149.         this.polarDriftXDriver = new ParameterDriver("polar-drift-X",
  150.                                                      0.0, ANGULAR_SCALE,
  151.                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  152.         this.polarOffsetYDriver = new ParameterDriver("polar-offset-Y",
  153.                                                       0.0, ANGULAR_SCALE,
  154.                                                       -FastMath.PI, FastMath.PI);

  155.         this.polarDriftYDriver = new ParameterDriver("polar-drift-Y",
  156.                                                      0.0, ANGULAR_SCALE,
  157.                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  158.     }

  159.     /** Get a driver allowing to change station position along East axis.
  160.      * @return driver for station position offset along East axis
  161.      */
  162.     public ParameterDriver getEastOffsetDriver() {
  163.         return eastOffsetDriver;
  164.     }

  165.     /** Get a driver allowing to change station position along North axis.
  166.      * @return driver for station position offset along North axis
  167.      */
  168.     public ParameterDriver getNorthOffsetDriver() {
  169.         return northOffsetDriver;
  170.     }

  171.     /** Get a driver allowing to change station position along Zenith axis.
  172.      * @return driver for station position offset along Zenith axis
  173.      */
  174.     public ParameterDriver getZenithOffsetDriver() {
  175.         return zenithOffsetDriver;
  176.     }

  177.     /** Get a driver allowing to add a prime meridian rotation.
  178.      * <p>
  179.      * The parameter is an angle in radians. In order to convert this
  180.      * value to a DUT1 in seconds, the value must be divided by
  181.      * {@code ave = 7.292115146706979e-5} (which is the nominal Angular Velocity
  182.      * of Earth from the TIRF model).
  183.      * </p>
  184.      * @return driver for prime meridian rotation
  185.      */
  186.     public ParameterDriver getPrimeMeridianOffsetDriver() {
  187.         return primeMeridianOffsetDriver;
  188.     }

  189.     /** Get a driver allowing to add a prime meridian rotation rate.
  190.      * <p>
  191.      * The parameter is an angle rate in radians per second. In order to convert this
  192.      * value to a LOD in seconds, the value must be multiplied by -86400 and divided by
  193.      * {@code ave = 7.292115146706979e-5} (which is the nominal Angular Velocity
  194.      * of Earth from the TIRF model).
  195.      * </p>
  196.      * @return driver for prime meridian rotation rate
  197.      */
  198.     public ParameterDriver getPrimeMeridianDriftDriver() {
  199.         return primeMeridianDriftDriver;
  200.     }

  201.     /** Get a driver allowing to add a polar offset along X.
  202.      * <p>
  203.      * The parameter is an angle in radians
  204.      * </p>
  205.      * @return driver for polar offset along X
  206.      */
  207.     public ParameterDriver getPolarOffsetXDriver() {
  208.         return polarOffsetXDriver;
  209.     }

  210.     /** Get a driver allowing to add a polar drift along X.
  211.      * <p>
  212.      * The parameter is an angle rate in radians per second
  213.      * </p>
  214.      * @return driver for polar drift along X
  215.      */
  216.     public ParameterDriver getPolarDriftXDriver() {
  217.         return polarDriftXDriver;
  218.     }

  219.     /** Get a driver allowing to add a polar offset along Y.
  220.      * <p>
  221.      * The parameter is an angle in radians
  222.      * </p>
  223.      * @return driver for polar offset along Y
  224.      */
  225.     public ParameterDriver getPolarOffsetYDriver() {
  226.         return polarOffsetYDriver;
  227.     }

  228.     /** Get a driver allowing to add a polar drift along Y.
  229.      * <p>
  230.      * The parameter is an angle rate in radians per second
  231.      * </p>
  232.      * @return driver for polar drift along Y
  233.      */
  234.     public ParameterDriver getPolarDriftYDriver() {
  235.         return polarDriftYDriver;
  236.     }

  237.     /** Get the base frame associated with the station.
  238.      * <p>
  239.      * The base frame corresponds to a null position offset, null
  240.      * polar motion, null meridian shift
  241.      * </p>
  242.      * @return base frame associated with the station
  243.      */
  244.     public TopocentricFrame getBaseFrame() {
  245.         return baseFrame;
  246.     }

  247.     /** Get the geodetic point at the center of the offset frame.
  248.      * @return geodetic point at the center of the offset frame
  249.      * @exception OrekitException if frames transforms cannot be computed
  250.      */
  251.     public GeodeticPoint getOffsetGeodeticPoint()
  252.         throws OrekitException {

  253.         // take station offset into account
  254.         final double    x          = parametricModel(eastOffsetDriver);
  255.         final double    y          = parametricModel(northOffsetDriver);
  256.         final double    z          = parametricModel(zenithOffsetDriver);
  257.         final BodyShape baseShape  = baseFrame.getParentShape();
  258.         final Transform baseToBody = baseFrame.getTransformTo(baseShape.getBodyFrame(), (AbsoluteDate) null);
  259.         final Vector3D  origin     = baseToBody.transformPosition(new Vector3D(x, y, z));

  260.         return baseShape.transform(origin, baseShape.getBodyFrame(), null);

  261.     }

  262.     /** Get the transform between offset frame and inertial frame.
  263.      * <p>
  264.      * The offset frame takes the <em>current</em> position offset,
  265.      * polar motion and the meridian shift into account. The frame
  266.      * returned is disconnected from later changes in the parameters.
  267.      * When the {@link ParameterDriver parameters} managing these
  268.      * offsets are changed, the method must be called again to retrieve
  269.      * a new offset frame.
  270.      * </p>
  271.      * @param inertial inertial frame to transform to
  272.      * @param date date of the transform
  273.      * @return offset frame defining vectors
  274.      * @exception OrekitException if offset frame cannot be computed for current offset values
  275.      */
  276.     public Transform getOffsetToInertial(final Frame inertial, final AbsoluteDate date)
  277.         throws OrekitException {

  278.         // take parametric prime meridian shift into account
  279.         final double theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
  280.         final double thetaDot = parametricModel(primeMeridianDriftDriver);
  281.         final Transform meridianShift =
  282.                         new Transform(date,
  283.                                       new Rotation(Vector3D.PLUS_K, -theta, RotationConvention.FRAME_TRANSFORM),
  284.                                       new Vector3D(-thetaDot, Vector3D.PLUS_K));

  285.         // take parametric pole shift into account
  286.         final double xp     = linearModel(date, polarOffsetXDriver, polarDriftXDriver);
  287.         final double yp     = linearModel(date, polarOffsetYDriver, polarDriftYDriver);
  288.         final double xpDot  = parametricModel(polarDriftXDriver);
  289.         final double ypDot  = parametricModel(polarDriftYDriver);
  290.         final Transform poleShift =
  291.                         new Transform(date,
  292.                                       new Transform(date,
  293.                                                     new Rotation(Vector3D.PLUS_I, yp, RotationConvention.FRAME_TRANSFORM),
  294.                                                     new Vector3D(ypDot, 0.0, 0.0)),
  295.                                       new Transform(date,
  296.                                                     new Rotation(Vector3D.PLUS_J, xp, RotationConvention.FRAME_TRANSFORM),
  297.                                                     new Vector3D(0.0, xpDot, 0.0)));

  298.         // take station offset into account
  299.         final double    x          = parametricModel(eastOffsetDriver);
  300.         final double    y          = parametricModel(northOffsetDriver);
  301.         final double    z          = parametricModel(zenithOffsetDriver);
  302.         final BodyShape baseShape  = baseFrame.getParentShape();
  303.         final Transform baseToBody = baseFrame.getTransformTo(baseShape.getBodyFrame(), (AbsoluteDate) null);

  304.         final Vector3D      origin   = baseToBody.transformPosition(new Vector3D(x, y, z));
  305.         final GeodeticPoint originGP = baseShape.transform(origin, baseShape.getBodyFrame(), date);
  306.         final Transform offsetToIntermediate =
  307.                         new Transform(date,
  308.                                       new Transform(date,
  309.                                                     new Rotation(Vector3D.PLUS_I, Vector3D.PLUS_K,
  310.                                                                  originGP.getEast(), originGP.getZenith()),
  311.                                                     Vector3D.ZERO),
  312.                                       new Transform(date, origin));

  313.         // combine all transforms together
  314.         final Transform intermediateToBody = new Transform(date, poleShift, meridianShift);
  315.         final Transform bodyToInert        = baseFrame.getParent().getTransformTo(inertial, date);

  316.         return new Transform(date, offsetToIntermediate, new Transform(date, intermediateToBody, bodyToInert));

  317.     }

  318.     /** Get the transform between offset frame and inertial frame with derivatives.
  319.      * <p>
  320.      * As the East and North vector are not well defined at pole, the derivatives
  321.      * of these two vectors diverge to infinity as we get closer to the pole.
  322.      * So this method should not be used for stations less than 0.0001 degree from
  323.      * either poles.
  324.      * </p>
  325.      * @param inertial inertial frame to transform to
  326.      * @param date date of the transform
  327.      * @param factory factory for the derivatives
  328.      * @param indices indices of the estimated parameters in derivatives computations
  329.      * @return offset frame defining vectors with derivatives
  330.      * @exception OrekitException if some frame transforms cannot be computed
  331.      * @since 9.0
  332.      */
  333.     public FieldTransform<DerivativeStructure> getOffsetToInertial(final Frame inertial,
  334.                                                                    final FieldAbsoluteDate<DerivativeStructure> date,
  335.                                                                    final DSFactory factory,
  336.                                                                    final Map<String, Integer> indices)
  337.         throws OrekitException {

  338.         final Field<DerivativeStructure>         field = date.getField();
  339.         final FieldVector3D<DerivativeStructure> zero  = FieldVector3D.getZero(field);
  340.         final FieldVector3D<DerivativeStructure> plusI = FieldVector3D.getPlusI(field);
  341.         final FieldVector3D<DerivativeStructure> plusJ = FieldVector3D.getPlusJ(field);
  342.         final FieldVector3D<DerivativeStructure> plusK = FieldVector3D.getPlusK(field);

  343.         // take parametric prime meridian shift into account
  344.         final DerivativeStructure theta    = linearModel(factory, date,
  345.                                                          primeMeridianOffsetDriver, primeMeridianDriftDriver,
  346.                                                          indices);
  347.         final DerivativeStructure thetaDot = parametricModel(factory, primeMeridianDriftDriver, indices);
  348.         final FieldTransform<DerivativeStructure> meridianShift =
  349.                         new FieldTransform<>(date,
  350.                                              new FieldRotation<>(plusK, theta.negate(), RotationConvention.FRAME_TRANSFORM),
  351.                                              new FieldVector3D<>(thetaDot.negate(), plusK));

  352.         // take parametric pole shift into account
  353.         final DerivativeStructure xp    = linearModel(factory, date,
  354.                                                       polarOffsetXDriver, polarDriftXDriver, indices);
  355.         final DerivativeStructure yp    = linearModel(factory, date,
  356.                                                       polarOffsetYDriver, polarDriftYDriver, indices);
  357.         final DerivativeStructure xpDot = parametricModel(factory, polarDriftXDriver, indices);
  358.         final DerivativeStructure ypDot = parametricModel(factory, polarDriftYDriver, indices);
  359.         final FieldTransform<DerivativeStructure> poleShift =
  360.                         new FieldTransform<>(date,
  361.                                              new FieldTransform<>(date,
  362.                                                              new FieldRotation<>(plusI, yp, RotationConvention.FRAME_TRANSFORM),
  363.                                                              new FieldVector3D<>(ypDot, field.getZero(), field.getZero())),
  364.                                              new FieldTransform<>(date,
  365.                                                              new FieldRotation<>(plusJ, xp, RotationConvention.FRAME_TRANSFORM),
  366.                                                              new FieldVector3D<>(field.getZero(), xpDot, field.getZero())));

  367.         // take station offset into account
  368.         final DerivativeStructure  x          = parametricModel(factory, eastOffsetDriver,   indices);
  369.         final DerivativeStructure  y          = parametricModel(factory, northOffsetDriver,  indices);
  370.         final DerivativeStructure  z          = parametricModel(factory, zenithOffsetDriver, indices);
  371.         final BodyShape            baseShape  = baseFrame.getParentShape();
  372.         final Transform            baseToBody = baseFrame.getTransformTo(baseShape.getBodyFrame(), (AbsoluteDate) null);

  373.         final FieldVector3D<DerivativeStructure>      origin   = baseToBody.transformPosition(new FieldVector3D<>(x, y, z));
  374.         final FieldGeodeticPoint<DerivativeStructure> originGP = baseShape.transform(origin, baseShape.getBodyFrame(), date);
  375.         final FieldTransform<DerivativeStructure> offsetToIntermediate =
  376.                         new FieldTransform<>(date,
  377.                                              new FieldTransform<>(date,
  378.                                                                   new FieldRotation<>(plusI, plusK,
  379.                                                                                       originGP.getEast(), originGP.getZenith()),
  380.                                                                   zero),
  381.                                              new FieldTransform<>(date, origin));

  382.         // combine all transforms together
  383.         final FieldTransform<DerivativeStructure> intermediateToBody = new FieldTransform<>(date, poleShift, meridianShift);
  384.         final FieldTransform<DerivativeStructure> bodyToInert        = baseFrame.getParent().getTransformTo(inertial, date);

  385.         return new FieldTransform<>(date, offsetToIntermediate, new FieldTransform<>(date, intermediateToBody, bodyToInert));

  386.     }

  387.     /** Evaluate a parametric linear model.
  388.      * @param date current date
  389.      * @param offsetDriver driver for the offset parameter
  390.      * @param driftDriver driver for the drift parameter
  391.      * @return current value of the linear model
  392.      * @exception OrekitException if reference date has not been set for the
  393.      * offset driver
  394.      */
  395.     private double linearModel(final AbsoluteDate date,
  396.                                final ParameterDriver offsetDriver, final ParameterDriver driftDriver)
  397.         throws OrekitException {
  398.         if (offsetDriver.getReferenceDate() == null) {
  399.             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
  400.                                       offsetDriver.getName());
  401.         }
  402.         final double dt     = date.durationFrom(offsetDriver.getReferenceDate());
  403.         final double offset = parametricModel(offsetDriver);
  404.         final double drift  = parametricModel(driftDriver);
  405.         return dt * drift + offset;
  406.     }

  407.     /** Evaluate a parametric linear model.
  408.      * @param factory factory for the derivatives
  409.      * @param date current date
  410.      * @param offsetDriver driver for the offset parameter
  411.      * @param driftDriver driver for the drift parameter
  412.      * @param indices indices of the estimated parameters in derivatives computations
  413.      * @return current value of the linear model
  414.      * @exception OrekitException if reference date has not been set for the
  415.      * offset driver
  416.      */
  417.     private DerivativeStructure linearModel(final DSFactory factory, final FieldAbsoluteDate<DerivativeStructure> date,
  418.                                             final ParameterDriver offsetDriver, final ParameterDriver driftDriver,
  419.                                             final Map<String, Integer> indices)
  420.         throws OrekitException {
  421.         if (offsetDriver.getReferenceDate() == null) {
  422.             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
  423.                                       offsetDriver.getName());
  424.         }
  425.         final DerivativeStructure dt     = date.durationFrom(offsetDriver.getReferenceDate());
  426.         final DerivativeStructure offset = parametricModel(factory, offsetDriver, indices);
  427.         final DerivativeStructure drift  = parametricModel(factory, driftDriver, indices);
  428.         return dt.multiply(drift).add(offset);
  429.     }

  430.     /** Evaluate a parametric model.
  431.      * @param driver driver managing the parameter
  432.      * @return value of the parametric model
  433.      */
  434.     private double parametricModel(final ParameterDriver driver) {
  435.         return driver.getValue();
  436.     }

  437.     /** Evaluate a parametric model.
  438.      * @param factory factory for the derivatives
  439.      * @param driver driver managing the parameter
  440.      * @param indices indices of the estimated parameters in derivatives computations
  441.      * @return value of the parametric model
  442.      */
  443.     private DerivativeStructure parametricModel(final DSFactory factory, final ParameterDriver driver,
  444.                                                 final Map<String, Integer> indices) {
  445.         final Integer index = indices.get(driver.getName());
  446.         return (index == null) ?
  447.              factory.constant(driver.getValue()) :
  448.              factory.variable(index, driver.getValue());
  449.     }

  450. }