ParameterDriversSequenceBuilder.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}.
 * @author Luc Maisonobe
 * @since 14.0
 */
public class ParameterDriversSequenceBuilder {

    /** 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<Double> 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(double) addReferenceValue(referenceValue)} method once, or by
     * calling the {@link #addReferenceValue(double, 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
     */
    public ParameterDriversSequenceBuilder(final String baseName, final double scale,
                                           final double minValue, final double maxValue) {
        this.baseName = baseName;
        this.scale    = scale;
        this.minValue = minValue;
        this.maxValue = maxValue;
        this.spans    = new TimeSpanMap<>(null);
    }

    /** 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 ParameterDriversSequenceBuilder addReferenceValue(final double 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 ParameterDriversSequenceBuilder addReferenceValue(final double referenceValue,
                                                             final AbsoluteDate earliestValidityDate,
                                                             final AbsoluteDate latestValidityDate) {
        spans.addValidBetween(referenceValue, earliestValidityDate, latestValidityDate);
        return this;
    }

    /** Build a {@link ParameterDriversSequence}.
     * <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 ParameterDriversSequence build() {

        TimeSpanMap.Span<Double> 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<ParameterDriver> 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 ParameterDriver driver = new ParameterDriver(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 new ParameterDriversSequence(drivers);

    }

}