#include "storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.h"

#include <queue>
#include <boost/container/flat_set.hpp>
#include <storm-pars/analysis/MonotonicityChecker.h>

#include "storm/adapters/RationalFunctionAdapter.h"
#include "storm/logic/FragmentSpecification.h"
#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h"
#include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h"
#include "storm/utility/vector.h"
#include "storm/models/sparse/Dtmc.h"
#include "storm/models/sparse/Mdp.h"
#include "storm/models/sparse/StandardRewardModel.h"

#include "storm/exceptions/InvalidArgumentException.h"
#include "storm/exceptions/NotSupportedException.h"

namespace storm {
    namespace modelchecker {

        template <typename SparseModelType, typename ConstantType>
        SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::SparseParameterLiftingModelChecker() {
            //Intentionally left empty
        }

        template <typename SparseModelType, typename ConstantType>
        void SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::specifyFormula(Environment const& env, storm::modelchecker::CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask) {

            currentFormula = checkTask.getFormula().asSharedPointer();
            currentCheckTask = std::make_unique<storm::modelchecker::CheckTask<storm::logic::Formula, ConstantType>>(checkTask.substituteFormula(*currentFormula).template convertValueType<ConstantType>());

            if (currentCheckTask->getFormula().isProbabilityOperatorFormula()) {
                auto const& probOpFormula = currentCheckTask->getFormula().asProbabilityOperatorFormula();
                if(probOpFormula.getSubformula().isBoundedUntilFormula()) {
                    specifyBoundedUntilFormula(env, currentCheckTask->substituteFormula(probOpFormula.getSubformula().asBoundedUntilFormula()));
                } else if(probOpFormula.getSubformula().isUntilFormula()) {
                    specifyUntilFormula(env, currentCheckTask->substituteFormula(probOpFormula.getSubformula().asUntilFormula()));
                } else if (probOpFormula.getSubformula().isEventuallyFormula()) {
                    specifyReachabilityProbabilityFormula(env, currentCheckTask->substituteFormula(probOpFormula.getSubformula().asEventuallyFormula()));
                } else {
                    STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Parameter lifting is not supported for the given property.");
                }
            } else if (currentCheckTask->getFormula().isRewardOperatorFormula()) {
                auto const& rewOpFormula = currentCheckTask->getFormula().asRewardOperatorFormula();
                if(rewOpFormula.getSubformula().isEventuallyFormula()) {
                    specifyReachabilityRewardFormula(env, currentCheckTask->substituteFormula(rewOpFormula.getSubformula().asEventuallyFormula()));
                } else if (rewOpFormula.getSubformula().isCumulativeRewardFormula()) {
                    specifyCumulativeRewardFormula(env, currentCheckTask->substituteFormula(rewOpFormula.getSubformula().asCumulativeRewardFormula()));
                }
            }
        }

        template <typename SparseModelType, typename ConstantType>
        RegionResult SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::analyzeRegion(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, RegionResultHypothesis const& hypothesis, RegionResult const& initialResult, bool sampleVerticesOfRegion, std::shared_ptr<storm::analysis::Order> reachabilityOrder, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult) {
            typedef typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType VariableType;
            typedef typename storm::analysis::MonotonicityResult<VariableType>::Monotonicity Monotonicity;
            typedef typename storm::utility::parametric::Valuation<typename SparseModelType::ValueType> Valuation;
            typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::CoefficientType CoefficientType;
            STORM_LOG_THROW(this->currentCheckTask->isOnlyInitialStatesRelevantSet(), storm::exceptions::NotSupportedException, "Analyzing regions with parameter lifting requires a property where only the value in the initial states is relevant.");
            STORM_LOG_THROW(this->currentCheckTask->isBoundSet(), storm::exceptions::NotSupportedException, "Analyzing regions with parameter lifting requires a bounded property.");
            STORM_LOG_THROW(this->parametricModel->getInitialStates().getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException, "Analyzing regions with parameter lifting requires a model with a single initial state.");

            RegionResult result = initialResult;

            // Check if we need to check the formula on one point to decide whether to show AllSat or AllViolated
            if (hypothesis == RegionResultHypothesis::Unknown && result == RegionResult::Unknown) {
                result = getInstantiationChecker().check(env, region.getCenterPoint())->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()] ? RegionResult::CenterSat : RegionResult::CenterViolated;
            }

            bool existsSat = (hypothesis == RegionResultHypothesis::AllSat || result == RegionResult::ExistsSat || result == RegionResult::CenterSat);
            bool existsViolated = (hypothesis == RegionResultHypothesis::AllViolated || result == RegionResult::ExistsViolated || result == RegionResult::CenterViolated);

            // Here we check on global monotonicity
            if (localMonotonicityResult != nullptr && localMonotonicityResult->isDone()) {
                // Try to check it with a global monotonicity result
                auto monRes = localMonotonicityResult->getGlobalMonotonicityResult();
                bool lowerBound = isLowerBound(this->currentCheckTask->getBound().comparisonType);

                if (monRes->isDone() && monRes->isAllMonotonicity()) {
                    // Build valuations
                    auto monMap = monRes->getMonotonicityResult();
                    Valuation valuationToCheckSat;
                    Valuation valuationToCheckViolated;
                    for (auto var : region.getVariables()) {
                        auto monVar = monMap[var];
                        if (monVar == Monotonicity::Constant) {
                            valuationToCheckSat.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var)));
                            valuationToCheckViolated.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var)));
                        } else if (monVar == Monotonicity::Decr) {
                            if (lowerBound) {
                                valuationToCheckSat.insert(std::pair<VariableType, CoefficientType>(var, region.getUpperBoundary(var)));
                                valuationToCheckViolated.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var)));
                            } else {
                                valuationToCheckSat.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var)));
                                valuationToCheckViolated.insert(std::pair<VariableType, CoefficientType>(var, region.getUpperBoundary(var)));
                            }
                        } else if (monVar == Monotonicity::Incr) {
                            if (lowerBound) {
                                valuationToCheckSat.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var)));
                                valuationToCheckViolated.insert(std::pair<VariableType, CoefficientType>(var, region.getUpperBoundary(var)));
                            } else {
                                valuationToCheckSat.insert(std::pair<VariableType, CoefficientType>(var, region.getUpperBoundary(var)));
                                valuationToCheckViolated.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var)));
                            }
                        }
                    }

                    // Check for result
                    if (existsSat && getInstantiationCheckerSAT().check(env, valuationToCheckSat)->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) {
                        STORM_LOG_INFO("Region " << region << " is AllSat, discovered with instantiation checker on " << valuationToCheckSat << " and help of monotonicity" << std::endl);
                        RegionModelChecker<typename SparseModelType::ValueType>::numberOfRegionsKnownThroughMonotonicity++;
                        return RegionResult::AllSat;
                    }

                    if (existsViolated && !getInstantiationCheckerVIO().check(env, valuationToCheckViolated)->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) {
                        STORM_LOG_INFO("Region " << region << " is AllViolated, discovered with instantiation checker on " << valuationToCheckViolated << " and help of monotonicity" << std::endl);
                        RegionModelChecker<typename SparseModelType::ValueType>::numberOfRegionsKnownThroughMonotonicity++;
                        return RegionResult::AllViolated;
                    }

                    return RegionResult::ExistsBoth;
                }
            }

            // Try to prove AllSat or AllViolated, depending on the hypothesis or the current result
            if (existsSat) {
                // show AllSat:
                storm::solver::OptimizationDirection parameterOptimizationDirection = isLowerBound(this->currentCheckTask->getBound().comparisonType) ? storm::solver::OptimizationDirection::Minimize : storm::solver::OptimizationDirection::Maximize;
                auto checkResult = this->check(env, region, parameterOptimizationDirection, reachabilityOrder, localMonotonicityResult);
                if (checkResult->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) {
                    result = RegionResult::AllSat;
                } else if (sampleVerticesOfRegion) {
                    result = sampleVertices(env, region, result);
                }
            } else if (existsViolated) {
                // show AllViolated:
                storm::solver::OptimizationDirection parameterOptimizationDirection = isLowerBound(this->currentCheckTask->getBound().comparisonType) ? storm::solver::OptimizationDirection::Maximize : storm::solver::OptimizationDirection::Minimize;
                auto checkResult = this->check(env, region, parameterOptimizationDirection, reachabilityOrder, localMonotonicityResult);
                if (!checkResult->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) {
                    result = RegionResult::AllViolated;
                } else if (sampleVerticesOfRegion) {
                    result = sampleVertices(env, region, result);
                }
            } else {
                STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "When analyzing a region, an invalid initial result was given: " << initialResult);
            }
            return result;
        }

        template <typename SparseModelType, typename ConstantType>
        RegionResult SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::sampleVertices(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, RegionResult const& initialResult) {
            RegionResult result = initialResult;

            if (result == RegionResult::AllSat || result == RegionResult::AllViolated) {
                return result;
            }

            bool hasSatPoint = result == RegionResult::ExistsSat || result == RegionResult::CenterSat;
            bool hasViolatedPoint = result == RegionResult::ExistsViolated || result == RegionResult::CenterViolated;

            // Check if there is a point in the region for which the property is satisfied
            auto vertices = region.getVerticesOfRegion(region.getVariables());
            auto vertexIt = vertices.begin();
            while (vertexIt != vertices.end() && !(hasSatPoint && hasViolatedPoint)) {
                if (getInstantiationChecker().check(env, *vertexIt)->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) {
                    hasSatPoint = true;
                } else {
                    hasViolatedPoint = true;
                }
                ++vertexIt;
            }

            if (hasSatPoint) {
                if (hasViolatedPoint) {
                    result = RegionResult::ExistsBoth;
                } else if (result != RegionResult::CenterSat) {
                    result = RegionResult::ExistsSat;
                }
            } else if (hasViolatedPoint && result != RegionResult::CenterViolated) {
                result = RegionResult::ExistsViolated;
            }

            return result;
        }

        template <typename SparseModelType, typename ConstantType>
        std::unique_ptr<CheckResult> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::check(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::Order> reachabilityOrder, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult) {
            auto quantitativeResult = computeQuantitativeValues(env, region, dirForParameters, localMonotonicityResult);
            lastValue = quantitativeResult->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()];
            if(currentCheckTask->getFormula().hasQuantitativeResult()) {
                return quantitativeResult;
            } else {
                return quantitativeResult->template asExplicitQuantitativeCheckResult<ConstantType>().compareAgainstBound(this->currentCheckTask->getFormula().asOperatorFormula().getComparisonType(), this->currentCheckTask->getFormula().asOperatorFormula().template getThresholdAs<ConstantType>());
            }
        }

        template <typename SparseModelType, typename ConstantType>
        std::unique_ptr<QuantitativeCheckResult<ConstantType>> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getBound(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult) {
            STORM_LOG_WARN_COND(this->currentCheckTask->getFormula().hasQuantitativeResult(), "Computing quantitative bounds for a qualitative formula...");
            return std::make_unique<ExplicitQuantitativeCheckResult<ConstantType>>(std::move(computeQuantitativeValues(env, region, dirForParameters, localMonotonicityResult)->template asExplicitQuantitativeCheckResult<ConstantType>()));
        }

        template <typename SparseModelType, typename ConstantType>
        typename SparseModelType::ValueType SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getBoundAtInitState(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) {
            STORM_LOG_THROW(this->parametricModel->getInitialStates().getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException, "Getting a bound at the initial state requires a model with a single initial state.");
            return storm::utility::convertNumber<typename SparseModelType::ValueType>(getBound(env, region, dirForParameters)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()]);
        }

        template <typename SparseModelType, typename ConstantType>
        storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getInstantiationCheckerSAT() {
            return getInstantiationChecker();
        }

        template <typename SparseModelType, typename ConstantType>
        storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getInstantiationCheckerVIO() {
            return getInstantiationChecker();
        }

        template <typename SparseModelType, typename ConstantType>
        struct RegionBound {
            typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::VariableType VariableType;

            RegionBound(RegionBound<SparseModelType, ConstantType> const& other) = default;
            RegionBound(storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& r, std::shared_ptr<storm::analysis::Order> o, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> l, ConstantType const& b) : region(r), order(o), localMonRes(l), bound(b) {}
            storm::storage::ParameterRegion<typename SparseModelType::ValueType> region;
            std::shared_ptr<storm::analysis::Order> order;
            std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonRes;
            ConstantType bound;
        };

        template <typename SparseModelType, typename ConstantType>
        std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::computeExtremalValue(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dir, typename SparseModelType::ValueType const& precision) {
            return computeExtremalValue(env, region, dir, precision, boost::none);
        }

        // TODO: this is not efficient
        template <typename SparseModelType, typename ConstantType>
        bool SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::checkExtremalValue(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dir, typename SparseModelType::ValueType const& precision, typename SparseModelType::ValueType const& valueToCheck) {
            auto res = computeExtremalValue(env, region, dir, precision, storm::utility::convertNumber<ConstantType>(valueToCheck)).first;
            auto diff = storm::utility::convertNumber<ConstantType>(valueToCheck) - storm::utility::convertNumber<ConstantType>(res);
            return (diff >= (-storm::utility::convertNumber<ConstantType>(precision)) && diff <= storm::utility::convertNumber<ConstantType>(precision));
        }

        template <typename SparseModelType, typename ConstantType>
        SparseModelType const& SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getConsideredParametricModel() const {
            return *parametricModel;
        }

        template <typename SparseModelType, typename ConstantType>
        CheckTask<storm::logic::Formula, ConstantType> const& SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getCurrentCheckTask() const {
            return *currentCheckTask;
        }

        template <typename SparseModelType, typename ConstantType>
        void SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::specifyBoundedUntilFormula(Environment const& env, CheckTask<logic::BoundedUntilFormula, ConstantType> const& checkTask) {
            STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Parameter lifting is not supported for the given property.");
        }

        template <typename SparseModelType, typename ConstantType>
        void SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::specifyUntilFormula(Environment const& env, CheckTask<logic::UntilFormula, ConstantType> const& checkTask) {
            STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Parameter lifting is not supported for the given property.");
        }

        template <typename SparseModelType, typename ConstantType>
        void SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::specifyReachabilityProbabilityFormula(Environment const& env, CheckTask<logic::EventuallyFormula, ConstantType> const& checkTask) {
            // transform to until formula
            auto untilFormula = std::make_shared<storm::logic::UntilFormula const>(storm::logic::Formula::getTrueFormula(), checkTask.getFormula().getSubformula().asSharedPointer());
            specifyUntilFormula(env, currentCheckTask->substituteFormula(*untilFormula));
        }

        template <typename SparseModelType, typename ConstantType>
        void SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::specifyReachabilityRewardFormula(Environment const& env, CheckTask<logic::EventuallyFormula, ConstantType> const& checkTask) {
            STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Parameter lifting is not supported for the given property.");
        }

        template <typename SparseModelType, typename ConstantType>
        void SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::specifyCumulativeRewardFormula(Environment const& env, CheckTask<logic::CumulativeRewardFormula, ConstantType> const& checkTask) {
            STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Parameter lifting is not supported for the given property.");
        }

        template<typename SparseModelType, typename ConstantType>
        std::shared_ptr<storm::analysis::Order> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::copyOrder(std::shared_ptr<storm::analysis::Order> order) {
            auto res = order->copy();
            if (orderExtender) {
                orderExtender->setUnknownStates(order, res);
                orderExtender->copyMinMax(order, res);
            }
            return res;
        }

        template<typename SparseModelType, typename ConstantType>
        std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::checkForPossibleMonotonicity(Environment const& env,
                                                                                const storage::ParameterRegion<typename SparseModelType::ValueType> &region,
                                                                                std::set<VariableType>& possibleMonotoneIncrParameters,
                                                                                std::set<VariableType>& possibleMonotoneDecrParameters,
                                                                                std::set<VariableType>& possibleNotMonotoneParameters,
                                                                                std::set<VariableType>const& consideredVariables,
                                                                                storm::solver::OptimizationDirection const& dir) {
            bool minimize = storm::solver::minimize(dir);
            typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation Valuation;
            typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::CoefficientType CoefficientType;
            ConstantType value = storm::solver::minimize(dir) ? 1 : 0;
            Valuation valuation;
            for (auto& var : consideredVariables) {
                ConstantType previousCenter = -1;
//                ConstantType previousUpper = -1;
//                ConstantType previousLower = -1;
                bool monDecr = true;
                bool monIncr = true;
                auto valuationCenter = region.getCenterPoint();
//                auto valuationLower = region.getLowerBoundaries();
//                auto valuationUpper = region.getUpperBoundaries();

                valuationCenter[var] = region.getLowerBoundary(var);
//                valuationLower[var] = region.getLowerBoundary(var);
//                valuationUpper[var] = region.getLowerBoundary(var);
                // TODO: make cmdline argument or 1/precision
                int numberOfSamples = 50;
                auto stepSize = (region.getUpperBoundary(var) - region.getLowerBoundary(var)) / (numberOfSamples - 1);

                while (valuationCenter[var] <= region.getUpperBoundary(var)) {
                    // Create valuation
//                    assert (valuationLower[var] <= region.getUpperBoundary(var));
//                    assert (valuationUpper[var] <= region.getUpperBoundary(var));
//
//                    ConstantType valueLower = getInstantiationChecker().check(env, valuationLower)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()];
                    ConstantType valueCenter = getInstantiationChecker().check(env, valuationCenter)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()];
//                    ConstantType valueUpper = getInstantiationChecker().check(env, valuationUpper)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()];
//                    if (storm::solver::minimize(dir) ? valueLower <= value : valueLower >= value) {
//                        value = valueLower;
//                        valuation = valuationLower;
//                    }
                    if (storm::solver::minimize(dir) ? valueCenter <= value : valueCenter >= value) {
                        value = valueCenter;
                        valuation = valuationCenter;
                    }
//                    if (storm::solver::minimize(dir) ? valueUpper <= value : valueUpper >= value) {
//                        value = valueUpper;
//                        valuation = valuationUpper;
//                    }
                    // Calculate difference with result for previous valuation
                    ConstantType diffCenter = previousCenter - valueCenter;
//                    ConstantType diffLower = previousLower - valueLower;
//                    ConstantType diffUpper = previousUpper - valueUpper;
                    assert (previousCenter == -1 || (diffCenter >= -1 && diffCenter <= 1));
//                    assert (previousUpper == -1 || (diffUpper >= -1 && diffUpper <= 1));
//                    assert (previousLower == -1 || (diffLower >= -1 && diffLower <= 1));
//
                    if (previousCenter != -1) {
                        assert (previousCenter != -1 && previousCenter != -1);
                        monDecr &= diffCenter > 0 && diffCenter > 0 && diffCenter > 0; // then previous value is larger than the current value from the initial states
                        monIncr &= diffCenter < 0 && diffCenter < 0 && diffCenter < 0;
//                    } else {
//                        assert (previousUpper == -1 && previousLower == -1);
                    }
                    previousCenter = valueCenter;
//                    previousLower = valueLower;
//                    previousUpper = valueUpper;
                    if (!monDecr && ! monIncr) {
                        break;
                    }
                    valuationCenter[var] += stepSize;
//                    valuationLower[var] += stepSize;
//                    valuationUpper[var] += stepSize;
                }
                if (monIncr) {
                    possibleMonotoneParameters.insert(var);
                    possibleMonotoneIncrParameters.insert(var);
                } else if (monDecr) {
                    possibleMonotoneParameters.insert(var);
                    possibleMonotoneDecrParameters.insert(var);
                } else {
                    possibleNotMonotoneParameters.insert(var);
                }
            }
            return std::make_pair(storm::utility::convertNumber<typename SparseModelType::ValueType>(value), std::move(valuation));

        }

        template<typename SparseModelType, typename ConstantType>
        std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::computeExtremalValue(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dir, typename SparseModelType::ValueType const& precision, boost::optional<ConstantType> const& initialValue) {
            typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::CoefficientType CoefficientType;
            STORM_LOG_THROW(this->parametricModel->getInitialStates().getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException, "Getting extremal values at the initial state requires a model with a single initial state.");
            bool const useMonotonicity = this->isUseMonotonicitySet();
            bool const minimize = storm::solver::minimize(dir);

            // Comparator for the region queue
            auto cmp = storm::solver::minimize(dir) ?
                       [](RegionBound<SparseModelType, ConstantType> const& lhs, RegionBound<SparseModelType, ConstantType> const& rhs) { return lhs.bound > rhs.bound; } :
                       [](RegionBound<SparseModelType, ConstantType> const& lhs, RegionBound<SparseModelType, ConstantType> const& rhs) { return lhs.bound < rhs.bound; };
            std::priority_queue<RegionBound<SparseModelType, ConstantType>, std::vector<RegionBound<SparseModelType, ConstantType>>, decltype(cmp)> regionQueue(cmp);
            storm::utility::Stopwatch initialWatch(true);

            storm::utility::Stopwatch boundsWatch(false);
            auto numberOfPLACallsBounds = 0;
            if (useMonotonicity) {
                if (this->isUseBoundsSet()) {
                    numberOfPLACallsBounds++;
                    numberOfPLACallsBounds++;
                    orderExtender->setMinValuesOnce(getBound(env, region, storm::solver::OptimizationDirection::Minimize, nullptr)->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector());
                    orderExtender->setMaxValuesOnce(getBound(env, region, storm::solver::OptimizationDirection::Maximize, nullptr)->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector());
                }
                auto order = this->extendOrder(env, nullptr, region);
                auto monRes = std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>>(new storm::analysis::LocalMonotonicityResult<VariableType>(order->getNumberOfStates()));
                storm::utility::Stopwatch monotonicityWatch(true);
                this->extendLocalMonotonicityResult(region, order, monRes);
                monotonicityWatch.stop();
                STORM_PRINT(std::endl << "Total time for monotonicity checking: " << monotonicityWatch << "." << std::endl << std::endl);

                if (minimize) {
                    regionQueue.emplace(region, order, monRes, storm::utility::zero<ConstantType>());
                } else {
                    regionQueue.emplace(region, order, monRes, storm::utility::one<ConstantType>());
                }
            } else {
                if (minimize) {
                    regionQueue.emplace(region, nullptr, nullptr, storm::utility::zero<ConstantType>());
                } else {
                    regionQueue.emplace(region, nullptr, nullptr, storm::utility::one<ConstantType>());
                }
            }

            // The results
            boost::optional<ConstantType> value;
            typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation valuation;
            if (!initialValue) {
                auto init = getGoodInitialPoint(env, region, dir, regionQueue.top().localMonRes);
                value = storm::utility::convertNumber<ConstantType>(init.first);
                valuation = std::move(init.second);
            } else {
                value = initialValue;
            }

            initialWatch.stop();
            STORM_PRINT(std::endl << "Total time for initial points: " << initialWatch << "." << std::endl << std::endl);
            if (!initialValue) {
                STORM_LOG_INFO("Initial value: " << value.get() << " at " << valuation);
            }

            auto numberOfSplits = 0;
//            auto count = 0;
            auto numberOfPLACalls = 0;
            auto numberOfOrderCopies = 0;
            auto numberOfMonResCopies = 0;
            storm::utility::Stopwatch loopWatch(true);

            if (!(useMonotonicity && regionQueue.top().localMonRes->getGlobalMonotonicityResult()->isDone() && regionQueue.top().localMonRes->getGlobalMonotonicityResult()->isAllMonotonicity())) {
                // Doing the extremal computation
                bool changed = false;

                auto totalArea = storm::utility::convertNumber<ConstantType>(region.area());
                auto coveredArea = storm::utility::zero<ConstantType>();
                while (!regionQueue.empty()) {
                    auto currRegion = regionQueue.top().region;
                    auto order = regionQueue.top().order;
                    auto localMonotonicityResult = regionQueue.top().localMonRes;
                    auto currBound = regionQueue.top().bound;
                    STORM_LOG_INFO("Currently looking at region: " << currRegion);

                    std::vector<storm::storage::ParameterRegion<typename SparseModelType::ValueType>> newRegions;

                    // Check whether this region needs further investigation based on the bound of the parent region
                    bool investigateBounds = !value
                                             || (minimize && currBound < value.get() - storm::utility::convertNumber<ConstantType>(precision))
                                             || (!minimize && currBound > value.get() + storm::utility::convertNumber<ConstantType>(precision));

                    if (investigateBounds) {
                        numberOfPLACalls++;
                        auto bounds = getBound(env, currRegion, dir, localMonotonicityResult)->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector();
                        currBound = bounds[*this->parametricModel->getInitialStates().begin()];
                        // Check whether this region needs further investigation based on the bound of this region
                        bool lookAtRegion = !value
                                            || (minimize && currBound < value.get() - storm::utility::convertNumber<ConstantType>(precision))
                                            || (!minimize && currBound > value.get() + storm::utility::convertNumber<ConstantType>(precision));
                        if (lookAtRegion) {
                            if (useMonotonicity) {
                                // Continue extending order/monotonicity result
                                bool changedOrder = false;
                                if (!order->getDoneBuilding() && orderExtender->isHope(order, currRegion)) {
                                    if (numberOfCopiesOrder[order] != 1) {
                                        numberOfCopiesOrder[order]--;
                                        order = copyOrder(order);
                                        numberOfOrderCopies++;
                                    } else {
                                        assert (numberOfCopiesOrder[order] == 1);
                                    }
                                    this->extendOrder(env, order, currRegion);
                                    changedOrder = true;
                                }
                                if (changedOrder) {
                                    assert(!localMonotonicityResult->isDone());

                                    if (numberOfCopiesMonRes[localMonotonicityResult] != 1) {
                                        numberOfCopiesMonRes[localMonotonicityResult]--;
                                        localMonotonicityResult = localMonotonicityResult->copy();
                                        numberOfMonResCopies++;
                                    } else {
                                        assert (numberOfCopiesMonRes[localMonotonicityResult] == 1);
                                    }
                                    this->extendLocalMonotonicityResult(currRegion, order, localMonotonicityResult);
                                }
                            }

                            // Check whether this region contains a new 'good' value and set this value
                            auto point = useMonotonicity ? currRegion.getPoint(dir, *(localMonotonicityResult->getGlobalMonotonicityResult())) : currRegion.getCenterPoint();
                            auto currValue = getInstantiationChecker().check(env, point)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()];
                            if (!value || (minimize ? currValue <= value.get() : currValue >= value.get())) {
                                changed = true;
                                value = currValue;
                                valuation = point;
                            }

                            if ((minimize && currBound < value.get() - storm::utility::convertNumber<ConstantType>(precision))
                                || (!minimize && currBound > value.get() + storm::utility::convertNumber<ConstantType>(precision))) {
                                // We will split the region in this case, but first try to extend the order
                                if (useMonotonicity && this->isUseBoundsSet() && !order->getDoneBuilding()) {
                                    boundsWatch.start();
                                    numberOfPLACallsBounds++;
                                    if (minimize) {
                                        orderExtender->setMinValues(order, bounds);
                                        orderExtender->setMaxValues(order, getBound(env, currRegion, storm::solver::OptimizationDirection::Maximize, localMonotonicityResult)->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector());
                                    } else {
                                        orderExtender->setMaxValues(order, bounds);
                                        orderExtender->setMinValues(order, getBound(env, currRegion, storm::solver::OptimizationDirection::Minimize, localMonotonicityResult)->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector());
                                    }
                                    boundsWatch.stop();
                                    // We try to extend the order again based on minMaxValues
                                    auto i = order->getNumberOfDoneStates();
                                    this->extendOrder(env, order, currRegion);
                                    if (i < order->getNumberOfDoneStates()) {
                                        this->extendLocalMonotonicityResult(currRegion, order, localMonotonicityResult);
                                    }
                                }
                                // Now split the region
                                if (useMonotonicity) {
                                    this->splitSmart(currRegion, newRegions, order, *(localMonotonicityResult->getGlobalMonotonicityResult()), true);
                                } else if (this->isRegionSplitEstimateSupported()) {
                                    auto empty = storm::analysis::MonotonicityResult<VariableType>();
                                    this->splitSmart(currRegion, newRegions, order, empty, true);
                                } else {
                                    currRegion.split(currRegion.getCenterPoint(), newRegions);
                                }
                            }
                        }
                    }

                    // When the newRegions is empty we are done with the current region
                    if (newRegions.empty()) {
                        coveredArea += storm::utility::convertNumber<ConstantType>(currRegion.area());
                        if (order != nullptr) {
                            numberOfCopiesOrder[order]--;
                            numberOfCopiesMonRes[localMonotonicityResult]--;
                        }
                    } else {
                        STORM_LOG_INFO("Splitting region " << currRegion << " into " << newRegions.size());
                        numberOfSplits++;
                    }
                    regionQueue.pop();

                    // Add the new regions to the queue
                    if (useMonotonicity) {
                        for (auto &r : newRegions) {
                            r.setBoundParent(storm::utility::convertNumber<CoefficientType>(currBound));
                            regionQueue.emplace(r, order, localMonotonicityResult, currBound);
                        }
                        if (numberOfCopiesOrder.find(order) != numberOfCopiesOrder.end()) {
                            numberOfCopiesOrder[order] += newRegions.size();
                            numberOfCopiesMonRes[localMonotonicityResult] += newRegions.size();
                        } else {
                            numberOfCopiesOrder[order] = newRegions.size();
                            numberOfCopiesMonRes[localMonotonicityResult] = newRegions.size();
                        }
                    } else {
                        for (auto &r : newRegions) {
                            r.setBoundParent(storm::utility::convertNumber<CoefficientType>(currBound));
                            regionQueue.emplace(r, nullptr, nullptr, currBound);
                        }
                    }

                    STORM_LOG_INFO("Current value : " << value.get() << ", current bound: " << currBound << ".");
                    STORM_LOG_INFO("Covered " << (coveredArea * storm::utility::convertNumber<ConstantType>(100.0) / totalArea) << "% of the region." << std::endl);
                }
                loopWatch.stop();
            }

            STORM_PRINT("Total number of splits: " << numberOfSplits << std::endl);
            STORM_PRINT("Total number of plaCalls: " << numberOfPLACalls << std::endl);
            if (useMonotonicity) {
                STORM_PRINT("Total number of plaCalls for bounds for monotonicity checking: " << numberOfPLACallsBounds << std::endl);
                STORM_PRINT("Total number of copies of the order: " << numberOfOrderCopies << std::endl);
                STORM_PRINT("Total number of copies of the local monotonicity result: " << numberOfMonResCopies
                                                                                << std::endl);
            }
            STORM_PRINT(std::endl << "Total time for region refinement: " << loopWatch << "." << std::endl << std::endl);
            STORM_PRINT(std::endl << "Total time for additional bounds: " << boundsWatch << "." << std::endl << std::endl);

            return std::make_pair(storm::utility::convertNumber<typename SparseModelType::ValueType>(value.get()), valuation);
        }

        template<typename SparseModelType, typename ConstantType>
        std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getGoodInitialPoint(const Environment &env, const storage::ParameterRegion<typename SparseModelType::ValueType> &region, const OptimizationDirection &dir, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonRes) {
            typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation Valuation;
            typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::CoefficientType CoefficientType;
            ConstantType value = storm::solver::minimize(dir) ? 1 : 0;
            Valuation valuation;
            std::set<VariableType> monIncr, monDecr, notMon, notMonFirst;

            if (localMonRes != nullptr) {
                localMonRes->getGlobalMonotonicityResult()->splitBasedOnMonotonicity(region.getVariables(), monIncr, monDecr, notMonFirst);

                auto numMon = monIncr.size() + monDecr.size();
                STORM_PRINT("Number of monotone parameters: " << numMon << std::endl;);

                if (numMon < region.getVariables().size()) {
                    checkForPossibleMonotonicity(env, region, monIncr, monDecr, notMon, notMonFirst, dir);
                    STORM_PRINT("Number of possible monotone parameters: " << (monIncr.size() + monDecr.size() - numMon) << std::endl;);
                    STORM_PRINT("Number of definitely not monotone parameters: " << notMon.size() << std::endl;);
                }

                valuation = region.getPoint(dir, monIncr, monDecr);
//                std::size_t const numOfSamples = 50;
//                std::vector<Valuation> resultingVector(numOfSamples);
//                std::map<VariableType, CoefficientType> stepSize;
//                for (auto variable: notMon) {
//                    stepSize[variable] = (region.getUpperBoundary(variable) - region.getLowerBoundary(variable)) / (numOfSamples - 1);
//                }
//
//                for (uint_fast64_t i = 0; i < numOfSamples; ++i) {
//                    for (auto variable : notMon) {
//                        resultingVector[i].insert(std::pair<VariableType, CoefficientType>(variable, (region.getLowerBoundary(variable) + stepSize[variable] * i)));
//                    }
//                    for (auto variable: monIncr) {
//                        if (storm::solver::minimize(dir)) {
//                            resultingVector[i].insert(std::pair<VariableType, CoefficientType>(variable, region.getLowerBoundary(variable)));
//                        } else {
//                            resultingVector[i].insert(std::pair<VariableType, CoefficientType>(variable, region.getUpperBoundary(variable)));
//                        }
//                    }
//                    for (auto variable : monDecr) {
//                        if (!storm::solver::minimize(dir)) {
//                            resultingVector[i].insert(std::pair<VariableType, CoefficientType>(variable, region.getLowerBoundary(variable)));
//                        } else {
//                            resultingVector[i].insert(std::pair<VariableType, CoefficientType>(variable, region.getUpperBoundary(variable)));
//                        }
//                    }
//                }
//
//                for (auto &point : resultingVector) {
//                    auto currValue = getInstantiationChecker().check(env, point)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()];
//                    if (storm::solver::minimize(dir) ? currValue <= value : currValue >= value) {
//                        value = currValue;
//                        valuation = point;
//                    }
//                }
            } else {
                valuation = region.getCenterPoint();
            }
            value = getInstantiationChecker().check(env, valuation)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()];

            return std::make_pair(storm::utility::convertNumber<typename SparseModelType::ValueType>(value), std::move(valuation));
        }

        template class SparseParameterLiftingModelChecker<storm::models::sparse::Dtmc<storm::RationalFunction>, double>;
        template class SparseParameterLiftingModelChecker<storm::models::sparse::Mdp<storm::RationalFunction>, double>;
        template class SparseParameterLiftingModelChecker<storm::models::sparse::Dtmc<storm::RationalFunction>, storm::RationalNumber>;
        template class SparseParameterLiftingModelChecker<storm::models::sparse::Mdp<storm::RationalFunction>, storm::RationalNumber>;
    }
}


/*
Copyright (c) Members of the EGEE Collaboration. 2004.
See http://www.eu-egee.org/partners/ for details on the
copyright holders.

Licensed 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.
*/

/*
 * File: maxRankSelector.cpp
 * Author: Monforte Salvatore <Salvatore.Monforte@ct.infn.it>
 */

// $Id: maxRankSelector.cpp,v 1.1.2.1 2012/09/11 10:19:42 mcecchi Exp $

#include "maxRankSelector.h"
#include <vector>
#include <boost/random/linear_congruential.hpp>
#include <boost/random/uniform_smallint.hpp>
#include <boost/random/variate_generator.hpp>
#include <ctime>

namespace glite {
namespace wms {
namespace broker {

namespace {

boost::minstd_rand f_rnd;
boost::uniform_smallint<size_t> distrib(0, 666);
boost::variate_generator<
  boost::minstd_rand,
  boost::uniform_smallint<size_t>
> unirand(f_rnd, distrib);

struct clustered_rank_less_than_comparator :
    public std::unary_function<maxRankSelector::rank_to_match_container_map_type::value_type&, bool>
{
  bool operator()(
    maxRankSelector::rank_to_match_container_map_type::value_type const& a,
    maxRankSelector::rank_to_match_container_map_type::value_type const& b
  )
  {
    return a.first < b.first;
  }
};

struct clustered_rank_greater_than_comparator :
    public std::unary_function<maxRankSelector::rank_to_match_container_map_type::value_type&, bool>
{
  bool operator()(
    maxRankSelector::rank_to_match_container_map_type::value_type const& a,
    maxRankSelector::rank_to_match_container_map_type::value_type const& b
  )
  {
    return a.first > b.first;
  }
};

}

maxRankSelector::maxRankSelector()
{
  f_rnd.seed(std::time(0));
}

maxRankSelector::~maxRankSelector()
{
}

mm::matchtable::const_iterator
maxRankSelector::selectBestCE(mm::matchtable const& match_table)
{
  rank_to_match_container_map_type clustered_rank_match_table;

  mm::matchtable::const_iterator it(match_table.begin());
  mm::matchtable::const_iterator const e(match_table.end());
  for ( ; it != e; ++it) {
    double r = mm::getRank(it->second);
    clustered_rank_match_table[r].push_back(it);
  }
  rank_to_match_container_map_type::const_iterator max_cluster =
    std::max_element(
      clustered_rank_match_table.begin(),
      clustered_rank_match_table.end(),
      clustered_rank_less_than_comparator()
    );
  // The range is invalid
  if (max_cluster == clustered_rank_match_table.end()) {
    return e;
  }

  size_t n = max_cluster->second.size();

  if (n == 1) {
    return max_cluster->second.front();
  }
  return max_cluster->second[unirand()%n];
}

}}} // glite::wms::broker


/*    Copyright (c) 2010-2019, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 *
 *    References
 *      Eigen. http://eigen.tuxfamily.org/dox/TopicUnalignedArrayAssert.html, last accessed: 18th
 *          March, 2013.
 *
 */

#include <stdexcept>

#include <boost/make_shared.hpp>

#include "Tudat/Astrodynamics/BasicAstrodynamics/stateVectorIndices.h"
#include "Tudat/Astrodynamics/Ephemerides/cartesianStateExtractor.h"

namespace tudat
{
namespace ephemerides
{

std::shared_ptr< Eigen::Vector6d > CartesianStateExtractor::extract(
        ParsedDataLineMapPtr dataLineMap )
{
    // Short-hand notation.
    namespace parsed_data_vector_utilities = input_output::parsed_data_vector_utilities;
    using Eigen::Vector6d;

    // Create a new CartesianElements object.
    std::shared_ptr< Vector6d > cartesianElements
            = std::allocate_shared< Vector6d >( Eigen::aligned_allocator< Vector6d >( ) );

    // Find and set Cartesian x coordinate.
    if ( checkOptionalFieldType( dataLineMap, 1,
                                 input_output::field_types::state::cartesianXCoordinate ) )
    {
        ( *cartesianElements )( orbital_element_conversions::xCartesianPositionIndex )
                = parsed_data_vector_utilities::getField< double >(
                    dataLineMap, input_output::field_types::state::cartesianXCoordinate );
    }

    else
    {
        throw std::runtime_error( "No Cartesian x coordinate entry found." );
    }

    // Find and set Cartesian y coordinate.
    if ( checkOptionalFieldType( dataLineMap, 1,
                                 input_output::field_types::state::cartesianYCoordinate ) )
    {
        ( *cartesianElements )( orbital_element_conversions::yCartesianPositionIndex )
                = parsed_data_vector_utilities::getField< double >(
                    dataLineMap, input_output::field_types::state::cartesianYCoordinate );
    }

    else
    {
        throw std::runtime_error( "No Cartesian y coordinate entry found." );
    }

    // Find and set Cartesian z coordinate.
    if ( checkOptionalFieldType( dataLineMap, 1,
                                 input_output::field_types::state::cartesianZCoordinate ) )
    {
        ( *cartesianElements )( orbital_element_conversions::zCartesianPositionIndex )
                = parsed_data_vector_utilities::getField< double >(
                    dataLineMap, input_output::field_types::state::cartesianZCoordinate );
    }

    else
    {
        throw std::runtime_error( "No Cartesian z coordinate entry found." );
    }

    // Find and set Cartesian x velocity.
    if ( checkOptionalFieldType( dataLineMap, 1,
                                 input_output::field_types::state::cartesianXVelocity ) )
    {
        ( *cartesianElements )( orbital_element_conversions::xCartesianVelocityIndex )
                = parsed_data_vector_utilities::getField< double >(
                    dataLineMap, input_output::field_types::state::cartesianXVelocity );
    }
    else
    {
        throw std::runtime_error( "No Cartesian x velocity entry found." );
    }

    // Find and set Cartesian y velocity.
    if ( checkOptionalFieldType( dataLineMap, 1,
                                 input_output::field_types::state::cartesianYVelocity ) )
    {
        ( *cartesianElements )( orbital_element_conversions::yCartesianVelocityIndex )
                = parsed_data_vector_utilities::getField< double >(
                    dataLineMap, input_output::field_types::state::cartesianYVelocity );
    }

    else
    {
        throw std::runtime_error( "No Cartesian y velocity entry found." );
    }

    // Find and set Cartesian z velocity.
    if ( checkOptionalFieldType( dataLineMap, 1,
                                 input_output::field_types::state::cartesianZVelocity ) )
    {
        ( *cartesianElements )( orbital_element_conversions::zCartesianVelocityIndex )
                = parsed_data_vector_utilities::getField< double >(
                    dataLineMap, input_output::field_types::state::cartesianZVelocity );
    }

    else
    {
        throw std::runtime_error( "No Cartesian z velocity entry found." );
    }

    return cartesianElements;
}

} // namespace ephemerides
} // namespace tudat