Click or drag to resize

Evaluators And Evaluator Groups

DME Component Libraries makes extensive use of the "evaluator pattern" throughout the library. Almost any computation in DME Component Libraries makes use of the evaluator pattern. Evaluators generally represent a function of time, that is, a function that takes as input time, and produces as output a specific type of value.

Using Evaluators

Evaluators are created from definitional objects. A definitional object does not actually do any computations itself. Instead, it allows you to configure an object in the modeled world and then obtain an evaluator with which to do computations relating to that object.

As an example, AxesLinearRate is a definitional object that represents a set of axes that rotate with a linear (or constant) rate. We can construct an instance of this type that rotates with a constant rate relative to the J2000 axes:

Java
AxesLinearRate axes = new AxesLinearRate();
axes.setReferenceAxes(CentralBodiesFacet.getFromContext().getEarth().getJ2000Frame().getAxes());
axes.setReferenceEpoch(TimeConstants.J2000);
axes.setInitialRotation(UnitQuaternion.getIdentity());
axes.setInitialRotationalVelocity(0.1);
axes.setRotationalAcceleration(0.0);
axes.setSpinAxis(new UnitCartesian(1.0, 0.0, 0.0));

The axes object we have just created has a different orientation relative to J2000 at different times. In order to evaluate the rotation from J2000 to our axes at a given time, we first obtain an evaluator:

Java
AxesEvaluator evaluator = axes.getEvaluator();

We can then evaluate the evaluator at different times:

Java
JulianDate date = new GregorianDate(2007, 11, 20, 12, 0, 0.0).toJulianDate();
UnitQuaternion rotationFromJ2000 = evaluator.evaluate(date);

As in this example, evaluators are always created by calling a method on a definitional object. For simple objects with only one thing to evaluate, as in the example above, this method is generally called getEvaluator. More complicated objects, such as CentralBody, might have multiple methods that return different evaluators.

Once an evaluator has been created from a definitional object, changes to the definitional object will not affect the results computed by the evaluator. To obtain new results after changing properties of a definitional object, obtain a new evaluator. For example:

Java
AxesLinearRate axes = new AxesLinearRate();
axes.setReferenceAxes(CentralBodiesFacet.getFromContext().getEarth().getJ2000Frame().getAxes());
axes.setReferenceEpoch(TimeConstants.J2000);
axes.setInitialRotation(UnitQuaternion.getIdentity());
axes.setInitialRotationalVelocity(0.1);
axes.setRotationalAcceleration(0.0);
axes.setSpinAxis(new UnitCartesian(1.0, 0.0, 0.0));

AxesEvaluator evaluator = axes.getEvaluator();

// Evaluate the rotation at a specific time.
JulianDate date = new GregorianDate(2007, 11, 20, 12, 0, 0.0).toJulianDate();
UnitQuaternion rotationFromJ2000 = evaluator.evaluate(date);

// Change the axes to spin around the Y-axis instead of the X-axis.
axes.setSpinAxis(new UnitCartesian(0.0, 1.0, 0.0));

// Evaluate the rotation at the same time.
UnitQuaternion sameRotationFromJ2000 = evaluator.evaluate(date);

// rotationFromJ2000 and sameRotationFromJ2000 are identical, even though
// the spin axis of the definition object has changed.  The evaluator has
// ignored the change.

// Reobtain the evaluator, and evaluate using the new evaluator.
evaluator = axes.getEvaluator();
UnitQuaternion differentRotationFromJ2000 = evaluator.evaluate(date);

// rotationFromJ2000 and differentRotationFromJ200 are different, reflecting
// the change in the spin axis.

All evaluators implement IThreadAware. In short, this means that an evaluator may or may not be safe to evaluate from multiple threads simultaneously. Check the IsThreadSafe (get) property to determine if a particular evaluator is thread-safe. If it is not, then use CopyForAnotherThread.copy to make a copy of the evaluator for each thread in which it is to be used. See the Multithreading topic for more information on IThreadAware.

Using Evaluator Groups

EvaluatorGroups enable more efficient evaluation of evaluators by eliminating redundant computations.

Frequently, an evaluator uses one or more other evaluators to do its computation. For example, CentralBody.observeCartographicPoint returns an evaluator that evaluates the longitude, latitude, and height of a given Point relative to the central body at a given time. In order to do so, it must find the position of the point in the central body's FixedFrame (get / set), then transform that Cartesian position to a Cartographic position. This requires evaluating a PointEvaluator, then, if the Point is not expressed in the FixedFrame (get / set) already, it must calculate the transformation between the frames at the given time.

Often, these nested evaluators are shared between multiple top-level evaluators. For example, if you are observing the cartographic position of two SGP4-propagated satellites, the transformation from the SGP4 propagator's reference frame to the fixed frame must be done for both satellites. If both evaluators are evaluated at the same time, it would be inefficient to compute the reference frame transformation twice.

EvaluatorGroup provides an automatic mechanism for eliminating such redundant computations. By passing the same EvaluatorGroup to the CentralBody.observeCartographicPoint method when requesting the evaluator for each satellite, the resulting evaluators will perform any common computations between the two evaluators just once for a given time.

Java
Point point1 = createSatellite1Point();
Point point2 = createSatellite2Point();

EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth();

EvaluatorGroup group = new EvaluatorGroup();
MotionEvaluator1<Cartographic> evaluator1 = earth.observeCartographicPoint(point1, group);
MotionEvaluator1<Cartographic> evaluator2 = earth.observeCartographicPoint(point2, group);

// Update the reference to each evaluator using the evaluator group.
// This will optimize the evaluators by removing any redundancy.
evaluator1 = group.updateReference(evaluator1);
evaluator2 = group.updateReference(evaluator2);

// evaluator1 and evaluator2 will share redundant computations.

Note that this is only helpful if the two evaluators are frequently evaluated at the same JulianDate. Otherwise, putting both evaluators in the same EvaluatorGroup will offer no benefit and in fact may have a small performance cost.

Implementing Custom Evaluators

The following discussion details the various pieces and parts involved in implementing the Evaluator pattern successfully. It discusses the various abstract and virtual methods which you should implement, what they do, and what sort of things to watch out for in order to ensure good performance and thread safety. Code samples are provided which should allow you a complete picture of a typical evaluator. At the end, you should have all the code necessary to create custom implementations which can seamlessly integrate your algorithms into the rest of DME Component Libraries.

Evaluator Construction

Most definitional object classes in the library (e.g. Point, AccessConstraint, etc.) have an abstract method which is responsible for producing an Evaluator for the object. Along with acting as a 'factory method' for the corresponding Evaluator, the method is also responsible for handling EvaluatorGroups. EvaluatorGroups serve to eliminate redundant calculations when creating Evaluators within Evaluators, and can optimize performance by determining which Evaluators should cache their previous value.

In the example below, the getEvaluator1 method calls createEvaluator on the EvaluatorGroup. If an Evaluator from 'this' object instance with the given parameters was already created in the group, the preexisting instance is returned and the invoke method of GetEvaluator1Callback is never called. Otherwise, it calls the invoke method to create the Evaluator. Note that the "inner evaluators" are all constructed inside the "CreateEvaluator" method. This is to avoid extra calls in the event that the evaluator already exists, since, by necessity, the inner evaluators already exist as well.

Java
public final Evaluator<Double> getEvaluator1(EvaluatorGroup group, Point point, List<Scalar> scalars) {
    return group.createEvaluator(getEvaluator1Callback, point, scalars, m_propertyOne, m_propertyList);
}

// Create all the inner evaluators in this method so that they are only called once.
// Make sure the parameters along with the surrounding class instance specify a unique key
// to identify the evaluator in the EvaluatorGroup.
private Evaluator<Double> createEvaluator1(EvaluatorGroup group, Point point, List<Scalar> scalars, Double argumentOne, List<Integer> argumentList) {
    PointEvaluator pointEvaluator = point.getEvaluator(group);
    ArrayList<MotionEvaluator1<Double>> scalarEvaluators = new ArrayList<>(scalars.size());
    for (Scalar scalar : scalars) {
        scalarEvaluators.add(scalar.getEvaluator(group));
    }
    Ellipsoid immutableEarth = CentralBodiesFacet.getFromContext().getEarth().getShape();
    // Make sure to copy any lists to ensure that they cannot be modified outside the evaluator
    argumentList = new ArrayList<>(argumentList);
    return new SampleEvaluator(group, pointEvaluator, scalarEvaluators, argumentOne, immutableEarth, argumentList);
}

private final EvaluatorGroup.Callback4<Evaluator<Double>, Point, List<Scalar>, Double, List<Integer>> getEvaluator1Callback =
        EvaluatorGroup.Callback4.of(this::createEvaluator1);

When constructing an Evaluator, make sure that all of the members stored in the Evaluator are either other Evaluator types, value types, or immutable reference types. An Evaluator should never be constructed with a reference type which could be altered externally to the Evaluator, affecting the calculation. Lists of immutable types are safe so long as there is no reference to the List outside of the Evaluator and the lists are handled in a thread-safe manner during evaluation.

Java
// Initialize a new instance based on the given inner evaluator.
// Note that the arguments must be guaranteed not to change behind the
// evaluator's back.  Otherwise, this evaluator will not behave correctly
// when used in EvaluatorGroups.
public SampleEvaluator(EvaluatorGroup group, 
        MotionEvaluator1<Cartesian> innerEvaluatorOne, 
        List<MotionEvaluator1<Double>> innerEvaluatorList, 
        double argumentOne,
        Ellipsoid argumentTwo, 
        List<Integer> argumentList) {
    super(group);
    m_innerEvaluatorOne = innerEvaluatorOne;
    m_innerEvaluatorList = innerEvaluatorList;
    m_argumentList = argumentList;
    m_argumentOne = argumentOne;
    m_immutableShape = argumentTwo;
}

private double m_argumentOne;
private Ellipsoid m_immutableShape;
private List<Integer> m_argumentList;
private MotionEvaluator1<Cartesian> m_innerEvaluatorOne;
private List<MotionEvaluator1<Double>> m_innerEvaluatorList;
private boolean m_isDisposed;
Reporting Thread Safety

Every Evaluator implements the IThreadAware interface. When spawning new computation threads, this is used to make sure Evaluators (and their data) are updated or copied correctly to ensure thread safety. If IsThreadSafe (get) returns false, the Evaluator is copied, usually with an instance of CopyForAnotherThread. If IsThreadSafe (get) is true, the existing instance is shared across the computation threads. Note that when implementing IsThreadSafe (get), you should check to make sure that any member objects which implement IThreadAware are thread-safe. Also, the updateEvaluatorReferences method will be used by the EvaluatorGroup when optimizing performance after all the Evaluators have been constructed.

In the copy constructor, the CopyContext is used to update the members of the evaluator. The context then can control whether each object will be copied or not, and maintain a mapping between the old and new object to ensure that objects are replaced consistently across an object graph. For example, CopyForAnotherThread will copy objects which are not thread-safe. The context is also used when optimizing Evaluators, to replace evaluators with caching versions. If you have a custom data structure being used as data in an Evaluator, you can implement ICloneWithContext for that data structure and follow the pattern explained in the documentation for that interface.

Java
// Copy constructor which updates the object references based on the given context
// This is important for copying evaluators when using multiple threads
private SampleEvaluator(SampleEvaluator existingInstance, CopyContext context) {
    super(existingInstance, context);

    m_argumentOne = existingInstance.m_argumentOne;
    m_immutableShape = context.updateReference(existingInstance.m_immutableShape);
    // If the evaluator modifies the list, copy it.
    // Otherwise, simply update the reference.
    m_argumentList = context.updateReference(existingInstance.m_argumentList);

    m_innerEvaluatorOne = existingInstance.m_innerEvaluatorOne;
    m_innerEvaluatorList = existingInstance.m_innerEvaluatorList;
    updateEvaluatorReferences(context);
}

// This method is used to update the references between evaluators when optimizing
// their dependencies with an EvaluatorGroup.  This makes sure that the each unique
// calculation is only performed once at a given time.
@Override
public void updateEvaluatorReferences(CopyContext context) {
    m_innerEvaluatorOne = context.updateReference(m_innerEvaluatorOne);
    EvaluatorHelper.updateCollectionReferences(m_innerEvaluatorList, context);
}

// Clone this evaluator
@Override
public Object clone(CopyContext context) {
    return new SampleEvaluator(this, context);
}

// This property indicates whether this evaluator includes any non-thread-safe operations.
// If there is any File I/O, shared collections, or if any of the inner evaluators are
// not thread-safe this evaluator needs to be copied before being used in another thread.
@Override
public boolean getIsThreadSafe() {
    return m_innerEvaluatorOne.getIsThreadSafe() && EvaluatorHelper.allEvaluatorsAreThreadSafe(m_innerEvaluatorList);
}
Availability of Data

Since most Evaluators are comprised of inner evaluators, it is important to understand over which time intervals an evaluator is valid. If an inner evaluator has an inner evaluator (etc., etc.) which has a limited set of data (e.g. an Evaluator created from an StkEphemerisFile), the top evaluator needs a way of reporting this to calling code. The IAvailability interface handles this.

Java
// This method checks whether this calculation can produce a valid value at the given
// time.  For example, if an evaluator has ephemeris data which spans over certain times,
// the evaluator is not "available" at times where there is no data.
@Override
public boolean isAvailable(JulianDate date) {
    return EvaluatorHelper.allEvaluatorsAreAvailable(date, m_innerEvaluatorOne) && 
           EvaluatorHelper.allEvaluatorsAreAvailable(date, m_innerEvaluatorList);
}

// This method produces the time intervals when this evaluator can produce a valid value.
@Override
public TimeIntervalCollection getAvailabilityIntervals(TimeIntervalCollection consideredIntervals) {
    consideredIntervals = EvaluatorHelper.getAvailabilityIntervals(consideredIntervals, m_innerEvaluatorOne);
    consideredIntervals = EvaluatorHelper.getAvailabilityIntervals(consideredIntervals, m_innerEvaluatorList);
    return consideredIntervals;
}
What if the Evaluator Represents a Constant?

Since some Evaluators always return a single value, the IIsTimeVarying interface checks to see whether it is possible to optimize performance by eliminating extra calls. Care should be taken when setting this value to false. Even if all the inner evaluators return false, if the current evaluation uses time at all, IsTimeVarying (get) should return true. The only time that an evaluator can explicitly return false is if it does not use the time parameter at all, including feeding the time to inner evaluators. If unsure, it's always safe to return true, since you are guaranteed correct results at the cost of performance.

Java
// This property indicates whether the value produced by this evaluator is
// independent of time, in which case a constant value is recorded rather
// than continuously reevaluating
@Override
public boolean getIsTimeVarying() {
    // If the "evaluate" method does not use the "date" in its
    // calculation, except to pass it to an inner evaluator,
    // then simply return: m_innerEvaluatorOne.getIsTimeVarying()
    // Otherwise return: true
    return true;
}
Dispose Pattern

Most Evaluators include the 'dispose pattern' in order to make sure that any references to system resources are disposed in a timely manner. Make sure to dispose of any local resources and call dispose on any inner evaluators.

Java
// When this evaluator is no longer used, it calls dispose on any inner evaluators
// to make sure that any resources are released in a timely manner.
@Override
protected void dispose(boolean disposing) {
    if (m_isDisposed || !disposing) {
        return;
    }
    m_isDisposed = true;
    m_innerEvaluatorOne.dispose();
    EvaluatorHelper.dispose(m_innerEvaluatorList);
}
Performance Enhancement

In order to allow Evaluators to be created in a flexible manner, Evaluators are typically composed together into hierarchies of computation. In many cases, computations can be shared (e.g. Earth Fixed to Earth Inertial transformations). Evaluator caching allows these complex computations to remain efficient despite their complexity. If an Evaluator is shared between two or more other Evaluators, an EvaluatorGroup will call the following method to create a caching wrapper, then replace the existing Evaluator with the caching evaluator in all other Evaluators which use it. In most cases, the base class provides an implementation, but if other evaluators require a derived evaluator type (e.g. SampleEvaluator) instead of an instance of the base class, you will need to implement a caching wrapper of the correct type to prevent a casting exception. Generally, this is only necessary if the public "GetEvaluator" methods return instances of a derived evaluator type.

Java
// When an EvaluatorGroup determines that this evaluator is used by two or more
// evaluators, the result of the 'evaluate' method is cached so that the calculation
// is only performed once for each time.
// If you don't want any other evaluators to cache this evaluator's value,
// simply return this instance instead of using the caching wrapper.
@Override
public IEvaluator getCachingWrapper() {
    return new CachingEvaluator<Double>(this);
}

If you want to ensure that no caching occurs for a particular Evaluator, you can overload this method to simply return the existing instance.

The Algorithm Itself

Lastly, the core of the evaluator implementation is the evaluate call which contains the algorithm to compute. Provided that the rest of the Evaluator is implemented carefully and IsThreadSafe (get) returns the correct value, it is not necessary to perform any locking to ensure thread safety. However, because of performance improvements involving caching, the value returned for a given JulianDate should never change. Otherwise, the caching will not reflect changes in the value.

Java
// This is where the evaluator performs its calculation.
// There are a few things to note for the implementation:
// + The result should always be the same when fed in the same 'date'.
// + Avoid any calls to non-immutable objects.
// + Thread safety is handled for the user unless the user adds any
//   specifically un-thread-safe operations.  In which case, the user
//   should modify the "getIsThreadSafe" method to return 'false'.
@Override
public Double evaluate(JulianDate date) {
    double maximum = 0.0;
    for (int i = 0; i < m_innerEvaluatorList.size(); i++) {
        // MotionEvaluators allow you to obtain derivatives...
        Motion1<Double> scalarResult = m_innerEvaluatorList.get(i).evaluate(date, 1);
        // ...but it is not guaranteed to return the requested order, so make sure to check
        if (scalarResult.getOrder() > 0 && scalarResult.getFirstDerivative() > maximum) {
            maximum = scalarResult.getValue();
        }
    }
    Cartographic position = m_immutableShape.cartesianToCartographic(m_innerEvaluatorOne.evaluate(date));
    return position.getHeight() * Math.min(maximum, m_argumentOne);
}
Presenting a Sample Evaluator

Here is what the complete code looks like:

Java
private static final class SampleEvaluator extends Evaluator<Double> {
    // Initialize a new instance based on the given inner evaluator.
    // Note that the arguments must be guaranteed not to change behind the
    // evaluator's back.  Otherwise, this evaluator will not behave correctly
    // when used in EvaluatorGroups.
    public SampleEvaluator(EvaluatorGroup group, 
            MotionEvaluator1<Cartesian> innerEvaluatorOne, 
            List<MotionEvaluator1<Double>> innerEvaluatorList, 
            double argumentOne,
            Ellipsoid argumentTwo, 
            List<Integer> argumentList) {
        super(group);
        m_innerEvaluatorOne = innerEvaluatorOne;
        m_innerEvaluatorList = innerEvaluatorList;
        m_argumentList = argumentList;
        m_argumentOne = argumentOne;
        m_immutableShape = argumentTwo;
    }

    private double m_argumentOne;
    private Ellipsoid m_immutableShape;
    private List<Integer> m_argumentList;
    private MotionEvaluator1<Cartesian> m_innerEvaluatorOne;
    private List<MotionEvaluator1<Double>> m_innerEvaluatorList;
    private boolean m_isDisposed;

    // Copy constructor which updates the object references based on the given context
    // This is important for copying evaluators when using multiple threads
    private SampleEvaluator(SampleEvaluator existingInstance, CopyContext context) {
        super(existingInstance, context);

        m_argumentOne = existingInstance.m_argumentOne;
        m_immutableShape = context.updateReference(existingInstance.m_immutableShape);
        // If the evaluator modifies the list, copy it.
        // Otherwise, simply update the reference.
        m_argumentList = context.updateReference(existingInstance.m_argumentList);

        m_innerEvaluatorOne = existingInstance.m_innerEvaluatorOne;
        m_innerEvaluatorList = existingInstance.m_innerEvaluatorList;
        updateEvaluatorReferences(context);
    }

    // This method is used to update the references between evaluators when optimizing
    // their dependencies with an EvaluatorGroup.  This makes sure that the each unique
    // calculation is only performed once at a given time.
    @Override
    public void updateEvaluatorReferences(CopyContext context) {
        m_innerEvaluatorOne = context.updateReference(m_innerEvaluatorOne);
        EvaluatorHelper.updateCollectionReferences(m_innerEvaluatorList, context);
    }

    // Clone this evaluator
    @Override
    public Object clone(CopyContext context) {
        return new SampleEvaluator(this, context);
    }

    // This property indicates whether this evaluator includes any non-thread-safe operations.
    // If there is any File I/O, shared collections, or if any of the inner evaluators are
    // not thread-safe this evaluator needs to be copied before being used in another thread.
    @Override
    public boolean getIsThreadSafe() {
        return m_innerEvaluatorOne.getIsThreadSafe() && EvaluatorHelper.allEvaluatorsAreThreadSafe(m_innerEvaluatorList);
    }

    // This is where the evaluator performs its calculation.
    // There are a few things to note for the implementation:
    // + The result should always be the same when fed in the same 'date'.
    // + Avoid any calls to non-immutable objects.
    // + Thread safety is handled for the user unless the user adds any
    //   specifically un-thread-safe operations.  In which case, the user
    //   should modify the "getIsThreadSafe" method to return 'false'.
    @Override
    public Double evaluate(JulianDate date) {
        double maximum = 0.0;
        for (int i = 0; i < m_innerEvaluatorList.size(); i++) {
            // MotionEvaluators allow you to obtain derivatives...
            Motion1<Double> scalarResult = m_innerEvaluatorList.get(i).evaluate(date, 1);
            // ...but it is not guaranteed to return the requested order, so make sure to check
            if (scalarResult.getOrder() > 0 && scalarResult.getFirstDerivative() > maximum) {
                maximum = scalarResult.getValue();
            }
        }
        Cartographic position = m_immutableShape.cartesianToCartographic(m_innerEvaluatorOne.evaluate(date));
        return position.getHeight() * Math.min(maximum, m_argumentOne);
    }

    // This method checks whether this calculation can produce a valid value at the given
    // time.  For example, if an evaluator has ephemeris data which spans over certain times,
    // the evaluator is not "available" at times where there is no data.
    @Override
    public boolean isAvailable(JulianDate date) {
        return EvaluatorHelper.allEvaluatorsAreAvailable(date, m_innerEvaluatorOne) && 
               EvaluatorHelper.allEvaluatorsAreAvailable(date, m_innerEvaluatorList);
    }

    // This method produces the time intervals when this evaluator can produce a valid value.
    @Override
    public TimeIntervalCollection getAvailabilityIntervals(TimeIntervalCollection consideredIntervals) {
        consideredIntervals = EvaluatorHelper.getAvailabilityIntervals(consideredIntervals, m_innerEvaluatorOne);
        consideredIntervals = EvaluatorHelper.getAvailabilityIntervals(consideredIntervals, m_innerEvaluatorList);
        return consideredIntervals;
    }

    // This property indicates whether the value produced by this evaluator is
    // independent of time, in which case a constant value is recorded rather
    // than continuously reevaluating
    @Override
    public boolean getIsTimeVarying() {
        // If the "evaluate" method does not use the "date" in its
        // calculation, except to pass it to an inner evaluator,
        // then simply return: m_innerEvaluatorOne.getIsTimeVarying()
        // Otherwise return: true
        return true;
    }

    // When this evaluator is no longer used, it calls dispose on any inner evaluators
    // to make sure that any resources are released in a timely manner.
    @Override
    protected void dispose(boolean disposing) {
        if (m_isDisposed || !disposing) {
            return;
        }
        m_isDisposed = true;
        m_innerEvaluatorOne.dispose();
        EvaluatorHelper.dispose(m_innerEvaluatorList);
    }

    // When an EvaluatorGroup determines that this evaluator is used by two or more
    // evaluators, the result of the 'evaluate' method is cached so that the calculation
    // is only performed once for each time.
    // If you don't want any other evaluators to cache this evaluator's value,
    // simply return this instance instead of using the caching wrapper.
    @Override
    public IEvaluator getCachingWrapper() {
        return new CachingEvaluator<Double>(this);
    }
}
Variations on the Evaluator Theme

There are a few Evaluators which provide slightly different functionality in DME Component Libraries.

MotionEvaluator1<T> adds a way to optionally change the sampling strategy used in Access and other calculations. It also takes an "order" parameter to its MotionEvaluator`1.evaluate method. This serves as an optional recommendation for the number of derivatives to calculate. Though the Evaluator is not required to compute results up to the requested order, specifying an order less than the available implementation lets the Evaluator know not to perform extraneous computation. An AccessConstraintEvaluator is a good example of a type-specific implementation of a MotionEvaluator1<T>.

Java
// Optional override
@Override
public JulianDate getNextSampleSuggestion(JulianDate date) {
    return date.addSeconds(m_stepsize);
}

// Optional override
@Override
public Double evaluate(JulianDate date) {
    return evaluate(date, 0).getValue();
}

// Required override
@Override
public Motion1<Double> evaluate(JulianDate date, int order) {
    Motion1<Cartesian> state = m_innerEvaluator.evaluate(date, order + 1);
    Double[] result = new Double[state.getOrder() - 1];
    for (int i = 0; i < state.getOrder() - 1; i++) {
        result[i] = state.get(i).getMagnitude() * m_argumentOne;
    }
    return new Motion1<>(result);
}

The geometry types add an additional property which specifies how the raw data is defined. For instance, PointEvaluator adds a property to specify which ReferenceFrame the point is defined in over which times. Thus, it is possible to change the definition of a point from one frame to another without having to recreate the point. This can be useful for things like cis-lunar navigation, formation flying, or for representing a trajectory comprised of multiple different ephemeris sources.

Java
// Required override
@Override
public TimeIntervalCollection1<ReferenceFrame> getDefinedInIntervals() {
    return createConstantDefinedIn(m_definedInFrame);
}

Similarly, AxesEvaluator has a similar property which specifies which Axes the given axes are defined in at different times. Note that any angular rates returned from an AxesEvaluator should be expressed in the "defined-in" Axes.