Click or drag to resize

Evaluators And Evaluator Groups

STK Components makes extensive use of the "evaluator pattern" throughout the library. Almost any computation in STK Components 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 dateToEvaluate = new JulianDate(new GregorianDate(2007, 11, 20, 12, 0, 0D));
UnitQuaternion rotationFromJ2000 = evaluator.evaluate(dateToEvaluate);

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 dateToEvaluate = new JulianDate(new GregorianDate(2007, 11, 20, 12, 0, 0D));
UnitQuaternion rotationFromJ2000 = evaluator.evaluate(dateToEvaluate);

// 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(dateToEvaluate);

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

// Re-obtain the evaluator, and evaluate using the new evaluator.
evaluator = axes.getEvaluator();
UnitQuaternion differentRotationFromJ2000 = evaluator.evaluate(dateToEvaluate);

// 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 use from multiple threads simultaneously. Check the IsThreadSafe (get) property to determine if a particular evaluator is safe. If an evaluator is not thread safe, 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 for the same times, 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 users 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 users a complete picture of a typical evaluator. At the end, the user should have all the code necessary to create custom implementations which can seamlessly integrate the user's algorithms into the rest of STK Components.

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 pre-existing 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 s : scalars) {
        scalarEvaluators.add(s.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 used in any additional threads. Note that IsThreadSafe (get) should check to make sure that any member objects which implement IThreadAware are thread safe before returning its value. 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 values. Certain contexts, such as CopyForAnotherThread will perform a "deep copy" on all members which themselves implement ICloneWithContext. The context is also used when optimizing Evaluators by replacing certain instances with optimized versions of those instances. So, if the user has a custom data structure being used as data in an Evaluator, they should at least implement ICloneWithContext. When in doubt, the safest thing to do is to explicitly call clone instead of simply updating the member data. However, it is not necessary for user types to implement ICloneWithContext so long as the evaluator handles copying the type if it cannot be shared between threads.

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 times 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 the user. 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, many Evaluators are strung together and composed into hierarchies of computation. Much of that computation is 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 to replace the existing Evaluator in all other Evaluators which use it. This is an optional method because most Evaluators include a default caching wrapper in their base class. However, if the user requires a specific type of evaluator (e.g. SampleEvaluator) instead of an instance of the base class, the user needs to implement a caching wrapper of the correct type. Otherwise, it will throw an exception. Usually this isn't an issue because Definitional Object types define "GetEvaluator" methods which return instances of the base evaluator.

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);
}

Of course, if the user wants to ensure that no caching occurs for a particular Evaluator, it is possible to 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 STK Components.

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.