OptimizationProblemBuilder.java

/* Copyright 2013-2022 CS GROUP
 * 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.rugged.adjustment;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hipparchus.Field;
import org.hipparchus.analysis.differentiation.Gradient;
import org.hipparchus.analysis.differentiation.GradientField;
import org.hipparchus.optim.ConvergenceChecker;
import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem;
import org.hipparchus.optim.nonlinear.vector.leastsquares.MultivariateJacobianFunction;
import org.hipparchus.optim.nonlinear.vector.leastsquares.ParameterValidator;
import org.orekit.rugged.adjustment.measurements.Observables;
import org.orekit.rugged.errors.RuggedException;
import org.orekit.rugged.errors.RuggedMessages;
import org.orekit.rugged.linesensor.LineSensor;
import org.orekit.rugged.utils.DerivativeGenerator;
import org.orekit.utils.ParameterDriver;

/**
 * Builder for optimization problem.
 * <p>
 * Constructs the optimization problem defined by a set of measurement and sensors.
 * </p>
 * @author Jonathan Guinet
 * @author Guylaine Prat
 * @since 2.0
 */
abstract class OptimizationProblemBuilder {

    /** Margin used in parameters estimation for the inverse location lines range. */
    protected static final int ESTIMATION_LINE_RANGE_MARGIN = 100;

    /** Gradient generator.*/
    private final DerivativeGenerator<Gradient> generator;

    /** Parameter drivers list. */
    private final List<ParameterDriver> drivers;

    /** Number of parameters to refine. */
    private final int nbParams;

    /** Measurements. */
    private Observables measurements;

    /** Sensors list. */
    private final List<LineSensor> sensors;

    /** Constructor.
     * @param sensors list of sensors to refine
     * @param measurements set of observables
     */
    OptimizationProblemBuilder(final List<LineSensor> sensors, final Observables measurements) {

        this.generator = this.createGenerator(sensors);
        this.drivers = this.generator.getSelected();
        this.nbParams = this.drivers.size();
        if (this.nbParams == 0) {
            throw new RuggedException(RuggedMessages.NO_PARAMETERS_SELECTED);
        }
        this.measurements = measurements;
        this.sensors = sensors;
    }

    /** Least squares problem builder.
     * @param maxEvaluations maximum number of evaluations
     * @param convergenceThreshold convergence threshold
     * @return the least squares problem
     */

    public abstract LeastSquaresProblem build(int maxEvaluations, double convergenceThreshold);

    /** Create the convergence check.
     * <p>
     * check LInf distance of parameters variation between previous and current iteration
     * </p>
     * @param parametersConvergenceThreshold convergence threshold
     * @return the checker
     */
    final ConvergenceChecker<LeastSquaresProblem.Evaluation>
                            createChecker(final double parametersConvergenceThreshold) {

        final ConvergenceChecker<LeastSquaresProblem.Evaluation> checker = (iteration, previous, current)
            -> current.getPoint().getLInfDistance(previous.getPoint()) <= parametersConvergenceThreshold;

        return checker;
    }

    /** Create start points for optimization algorithm.
     * @return start parameters values (normalized)
     */
    final double[] createStartTab() {

        // Get start points (as a normalized value)
        final double[] start = new double[this.nbParams];
        int iStart = 0;
        for (final ParameterDriver driver : this.drivers) {
            start[iStart++] = driver.getNormalizedValue();
        }
        return start;
    }

    /** Create targets and weights of optimization problem. */
    protected abstract void createTargetAndWeight();

    /** Create the model function value and its Jacobian.
     * @return the model function value and its Jacobian
     */
    protected abstract MultivariateJacobianFunction createFunction();

    /** Parse the observables to select mapping .*/
    protected abstract void initMapping();

    /** Create parameter validator.
     * @return parameter validator
     */
    final ParameterValidator createParameterValidator() {

        // Prevent parameters to exceed their prescribed bounds
        final ParameterValidator validator = params -> {
            int i = 0;
            for (final ParameterDriver driver : this.drivers) {

                // let the parameter handle min/max clipping
                driver.setNormalizedValue(params.getEntry(i));
                params.setEntry(i++, driver.getNormalizedValue());
            }
            return params;
        };

        return validator;
    }

    /** Create the generator for {@link Gradient} instances.
     * @param selectedSensors list of sensors referencing the parameters drivers
     * @return a new generator
     */
    private DerivativeGenerator<Gradient> createGenerator(final List<LineSensor> selectedSensors) {

        // Initialize set of drivers name
        final Set<String> names = new HashSet<>();

        // Get the drivers name
        for (final LineSensor sensor : selectedSensors) {

            // Get the drivers name for the sensor
            sensor.getParametersDrivers().forEach(driver -> {

                // Add the name of the driver to the set of drivers name
                if (names.contains(driver.getName()) == false) {
                    names.add(driver.getName());
                }
            });
        }

        // Set up generator list and map
        final List<ParameterDriver> selected = new ArrayList<>();
        final Map<String, Integer> map = new HashMap<>();

        // Get the list of selected drivers
        for (final LineSensor sensor : selectedSensors) {

            sensor.getParametersDrivers().filter(driver -> driver.isSelected()).forEach(driver -> {
                if (map.get(driver.getName()) == null) {
                    map.put(driver.getName(), map.size());
                    selected.add(driver);
                }
            });
        }

        // gradient Generator
        final GradientField field = GradientField.getField(map.size());
        return new DerivativeGenerator<Gradient>() {

            /** {@inheritDoc} */
            @Override
            public List<ParameterDriver> getSelected() {
                return selected;
            }

            /** {@inheritDoc} */
            @Override
            public Gradient constant(final double value) {
                return Gradient.constant(map.size(), value);
            }

            /** {@inheritDoc} */
            @Override
            public Gradient variable(final ParameterDriver driver) {
                final Integer index = map.get(driver.getName());
                if (index == null) {
                    return constant(driver.getValue());
                } else {
                    return Gradient.variable(map.size(), index.intValue(), driver.getValue());
                }
            }

            /** {@inheritDoc} */
            @Override
            public Field<Gradient> getField() {
                return field;
            }

        };
    }

    /** Get the sensors list.
     * @return the sensors list
     */
    protected List<LineSensor> getSensors() {
        return sensors;
    }

    /** Get the number of parameters to refine.
     * @return the number of parameters to refine
     */
    protected final int getNbParams() {
        return this.nbParams;
    }

    /**
     * Get the parameters drivers list.
     * @return the selected list of parameters driver
     */
    protected final List<ParameterDriver> getDrivers() {
        return this.drivers;
    }

    /**
     * Get the derivative structure generator.
     * @return the derivative structure generator.
     */
    protected final DerivativeGenerator<Gradient> getGenerator() {
        return this.generator;
    }

    /** Get the measurements.
     * @return the measurements
     */
    protected Observables getMeasurements() {
        return measurements;
    }
}