BaseParameterDriversSequenceBuilder.java

/* Copyright 2022-2026 Luc Maisonobe
 * Licensed to CS GROUP (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.utils.drivers;

import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeInterval;
import org.orekit.utils.TimeSpanMap;

/** Builder for {@link ParameterDriversSequence}.
 * @param <T> type of the elements
 * @param <P> type of the parameter driver
 * @param <O> type of the parameter observer
 * @param <S> type of the parameters drivers sequence
 * @param <B> type of the parameters drivers sequence builder
 * @author Luc Maisonobe
 * @since 14.0
 */
public abstract class BaseParameterDriversSequenceBuilder<T,
                                                          P extends BaseParameterDriver<P, O>,
                                                          O extends BaseParameterObserver<P, O>,
                                                          S extends BaseParameterDriversSequence<P, O>,
                                                          B extends BaseParameterDriversSequenceBuilder<T, P, O, S, B>> {

    /** Prefix for drivers limited to one time span. */
    public static final String SPAN_PREFIX = "span-";

    /** Base name of the parameters. */
    private final String baseName;

    /** Parameters scaling factor. */
    private final double scale;

    /** Minimum value. */
    private final double minValue;

    /** Maximum value. */
    private final double maxValue;

    /** Reference values. */
    private final TimeSpanMap<T> spans;

    /** Constructor for an initially empty builder.
     * <p>
     * The builder {@link #build() build()} method cannot be called right after construction.
     * Some reference values must be set before, either by calling the
     * {@link #addReferenceValue(Object)  addReferenceValue(referenceValue)} method once, or by
     * calling the {@link #addReferenceValue(Object, AbsoluteDate, AbsoluteDate)
     * addReferenceValue(referenceValue, earliestValidityDate, latestValidityDate)} method as many times
     * as needed to cover the usage range before the {@link #build() build()} method can be called.
     * </p>
     * @param baseName     base name of the parameters
     * @param scale        scaling factor to convert the parameters value to
     *                     non-dimensional (typically set to the expected standard deviation
     *                     of the parameter), it must be non-zero
     * @param minValue     minimum value allowed
     * @param maxValue     maximum value allowed
     * @param defaultValue default value valid throughout timeline
     */
    protected BaseParameterDriversSequenceBuilder(final String baseName, final double scale,
                                                  final double minValue, final double maxValue,
                                                  final T defaultValue) {
        this.baseName = baseName;
        this.scale    = scale;
        this.minValue = minValue;
        this.maxValue = maxValue;
        this.spans    = new TimeSpanMap<>(defaultValue);
    }

    /** Add a reference value throughout timeline.
     * <p>
     * Calling this method is equivalent to call
     * {@code addReferenceValue(referenceValue, AbsoluteDate.PAST_INFINITY, AbsoluteDate.FUTURE_INFINITY)}.
     * </p>
     * @param referenceValue reference value
     * @return the instance itself, allowing use of the fluent interface pattern
     */
    public B addReferenceValue(final T referenceValue) {
        return addReferenceValue(referenceValue, AbsoluteDate.PAST_INFINITY, AbsoluteDate.FUTURE_INFINITY);
    }

    /** Add a reference value valid for a time span.
     * @param referenceValue       reference value
     * @param earliestValidityDate date after which the coefficient is valid
     * @param latestValidityDate   date before which the coefficient is valid
     * @return the instance itself, allowing use of the fluent interface pattern
     */
    public B addReferenceValue(final T referenceValue,
                               final AbsoluteDate earliestValidityDate,
                               final AbsoluteDate latestValidityDate) {
        spans.addValidBetween(referenceValue, earliestValidityDate, latestValidityDate);
        @SuppressWarnings("unchecked")
        final B self = (B) this;
        return self;
    }

    /** Build a {@link BaseParameterDriversSequence}.
     * <p>
     * If only one reference value has been set, its name will be the base name set at construction.
     * If several reference values have been set, their names will be built by concatenating together
     * {@link #SPAN_PREFIX}, followed by the base name set at construction, followed by a single "-",
     * and finally appending the index of the parameter driver, counting from 0.
     * </p>
     * @return built sequence
     */
    public S build() {

        TimeSpanMap.Span<T> current;
        try {
            current = spans.getFirstNonNullSpan();
        } catch (OrekitException oe) {
            // user did not call addReferenceValue
            throw new OrekitException(oe, OrekitMessages.NO_REFERENCE_VALUES_SET);
        }

        // check if there is one or several reference values
        final boolean oneValueOnly = current == spans.getLastNonNullSpan();

        final TimeSpanMap<P> drivers = new TimeSpanMap<>(null);

        // build the drivers
        int index = 0;
        while (current != null) {

            // safety check
            if (current.getData() == null) {
                if (current.next() == null) {
                    // null data after the sequence is OK
                    break;
                } else {
                    // null date in the middle of the sequence
                    throw new OrekitException(OrekitMessages.MISSING_REFERENCE_VALUE,
                                              current.getStart(), current.getEnd());
                }
            }

            // build the name of the driver, using a chronological index if needed
            final String name = oneValueOnly ? baseName : SPAN_PREFIX + baseName + "-" + index++;

            // create the driver
            final P driver = buildDriver(name, current.getData(), scale, minValue, maxValue,
                                         TimeInterval.of(current.getStart(), current.getEnd(), false));

            // add it to the map
            drivers.addValidBetween(driver, current.getStart(), current.getEnd());

            // prepare handling of next reference value
            current = current.next();

        }

        return buildSequence(drivers);

    }

    /** Build a parameter driver.
     * @param name           name of the parameter
     * @param referenceValue reference value of the parameter
     * @param scaleFactor    scaling factor to convert the parameters value to non-dimensional (typically set to the
     *                       expected standard deviation of the parameter), it must be non-zero
     * @param min            minimum value allowed
     * @param max            maximum value allowed
     * @param validity       validity interval
     * @return               built parameter driver
     */
    protected abstract P buildDriver(String name, T referenceValue, double scaleFactor,
                                     double min, double max, TimeInterval validity);

    /** Build a sequence.
     * @param drivers drivers sequence
     * @return built sequence
     */
    protected abstract S buildSequence(TimeSpanMap<P> drivers);

}