// Software License Agreement (BSD-3-Clause)
//
// Copyright 2018 The University of North Carolina at Chapel Hill
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above
//    copyright notice, this list of conditions and the following
//    disclaimer in the documentation and/or other materials provided
//    with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

//! @author Jeff Ichnowski

#pragma once
#ifndef NIGH_IMPL_REGION_LP_HPP
#define NIGH_IMPL_REGION_LP_HPP

#include "region.hpp"
#include "box_distance.hpp"
#include "lp_sum.hpp"
#include <Eigen/Dense>
#include <array>
#include <type_traits>

namespace unc::robotics::nigh::impl {

    template <typename Key, int dim, int p, typename Concurrency>
    class LPRegion {
        static constexpr int kDim = (dim == -1 ? Eigen::Dynamic : dim);
        using Space = metric::Space<Key, metric::LP<p>>;
        using Distance = typename Space::Distance;

        Eigen::Matrix<Distance, kDim, 1> min_;
        Eigen::Matrix<Distance, kDim, 1> max_;

    public:
        LPRegion() = default;

        LPRegion(const LPRegion& region)
            : min_(region.min_)
            , max_(region.max_)
        {
        }

        template <typename Traversal>
        LPRegion(const Space&, const Traversal&, const Key& q)
            : min_(q), max_(q)
        {
        }
        // LPRegion(const LPMetric<p>&, const T& q) : min_(q), max_(q) {}

        template <typename D>
        void init(const Space&, const Eigen::MatrixBase<D>& q) {
            min_ = q;
            max_ = q;
        }

        template <typename D>
        void grow(const Space&, const Eigen::MatrixBase<D>& q) {
            min_ = min_.cwiseMin(q);
            max_ = max_.cwiseMax(q);
        }

        template <typename D>
        void init(const Space& space, const Eigen::ArrayBase<D>& q) {
            init(space, q.matrix());
        }

        template <typename D>
        void grow(const Space& space, const Eigen::ArrayBase<D>& q) {
            grow(space, q.matrix());
        }

        template <std::size_t N>
        std::enable_if_t<N == dim>
        init(const Space& space, const std::array<Distance, N>& q) {
            init(space, Eigen::Map<const Eigen::Matrix<Distance, dim, 1>>(q.data()));
        }

        template <std::size_t N>
        std::enable_if_t<N == dim>
        grow(const Space& space, const std::array<Distance, N>& q) {
            grow(space, Eigen::Map<const Eigen::Matrix<Distance, dim, 1>>(q.data()));
        }

        template <typename Alloc>
        void init(const Space& space, const std::vector<Distance, Alloc>& q) {
            init(space, Eigen::Map<const Eigen::Matrix<Distance, dim, 1>>(q.data(), q.size()));
        }

        template <typename Alloc>
        void grow(const Space& space, const std::vector<Distance, Alloc>& q) {
            grow(space, Eigen::Map<const Eigen::Matrix<Distance, dim, 1>>(q.data(), q.size()));
        }

        constexpr unsigned dimensions() const {
            return min_.size();
        }

        Distance selectAxis(unsigned *axis) const {
            return (max_ - min_).maxCoeff(axis);
        }

        Distance distTo(const Key& key) const {
            return boxDistance<p>(min_, max_, key);
        }

        Distance min(int index) const { return min_[index]; }
        Distance max(int index) const { return max_[index]; }
    };

    template <typename Key, int dim, int p>
    class LPRegion<Key, dim, p, Concurrent> {
        static constexpr int kDim = dim;
        using Space = metric::Space<Key, metric::LP<p>>;
        using Distance = typename Space::Distance;

        std::array<Atom<Distance, true>, kDim> min_;
        std::array<Atom<Distance, true>, kDim> max_;

    public:
        LPRegion() = default;

        LPRegion(const LPRegion& other) {
            for (int i=0 ; i<kDim ; ++i) {
                min_[i].store(other.min_[i].load(std::memory_order_relaxed), std::memory_order_relaxed);
                max_[i].store(other.max_[i].load(std::memory_order_relaxed), std::memory_order_relaxed);
            }
        }

        template <typename Traversal>
        LPRegion(const Space&, const Traversal&, const Key& q) {
            // LPRegion(const metric::LP<p>&, const Q& q) {
            for (int i=0 ; i<kDim ; ++i) {
                min_[i].store(Space::coeff(q, i), std::memory_order_relaxed);
                max_[i].store(Space::coeff(q, i), std::memory_order_relaxed);
            }
        }

        constexpr unsigned dimensions() const {
            return kDim;
        }

        Distance min(int index) const { return min_[index].load(std::memory_order_relaxed); }
        Distance max(int index) const { return max_[index].load(std::memory_order_relaxed); }

        template <typename State>
        void grow(const Space&, const State& q) {
            for (int i=0 ; i<kDim ; ++i) {
                Distance v = Space::coeff(q, i);
                for (Distance e = min(i) ; v < e && !min_[i].compare_exchange_weak(e, v, std::memory_order_relaxed); );
                for (Distance e = max(i) ; v > e && !max_[i].compare_exchange_weak(e, v, std::memory_order_relaxed); );
                // std::cout << "region " << i << ": " << min_[i].load() << " to " << max_[i].load() << std::endl;
            }
        }

        Distance selectAxis(unsigned *axis) const {
            Distance dBest = max(0) - min(0);
            *axis = 0;

            for (int i=1 ; i<kDim ; ++i) {
                Distance dx = max(i) - min(i);
                if (dx > dBest) {
                    dBest = dx;
                    *axis = i;
                }
            }

            return dBest;
        }

        Distance distTo(const Key& key) const {
            Eigen::Matrix<Distance, kDim, 1> d;
            for (int i=0 ; i<kDim ; ++i)
                d[i] = std::max({
                        min_[i].load(std::memory_order_relaxed) - Space::coeff(key, i),
                        Space::coeff(key, i) - max_[i].load(std::memory_order_relaxed),
                        static_cast<Distance>(0)
                    });;
            return d.template lpNorm<p>();
        }
    };

    template <typename Key, int p>
    class LPRegion<Key, -1, p, Concurrent> {
        using Space = metric::Space<Key, metric::LP<p>>;
        using Distance = typename Space::Distance;

        // In order to put std::atomic into a vector, we need to
        // provide a move constructor.  Since we only expect the move
        // constructor to be used during initialization of the region,
        // we can implement it with relaxed memory order.
        struct Coeff : Atom<Distance, true> {
            using Base = Atom<Distance, true>;
            using Base::Base;
            Coeff(const Coeff& other) : Base(other.load(std::memory_order_relaxed)) {}
            Coeff(Coeff&& other) : Base(other.load(std::memory_order_relaxed)) {}
        };

        std::vector<Coeff> min_;
        std::vector<Coeff> max_;

    public:
        LPRegion() = default;

        LPRegion(const LPRegion& other)
            : min_(other.min_)
            , max_(other.max_)
        {
        }

        LPRegion(const Space&, LPRegion&& other)
            : min_(std::move(other.min_))
            , max_(std::move(other.max_))
        {
        }

        template <typename Traversal>
        LPRegion(const Space& space, const Traversal&, const Key& q) {
            unsigned dim = space.dimensions();
            min_.reserve(dim);
            max_.reserve(dim);
            for (unsigned i=0 ; i<dim ; ++i) {
                min_.emplace_back(Space::coeff(q, i));
                max_.emplace_back(Space::coeff(q, i));
            }
        }

        unsigned dimensions() const {
            return min_.size();
        }

        Distance min(int index) const { return min_[index].load(std::memory_order_relaxed); }
        Distance max(int index) const { return max_[index].load(std::memory_order_relaxed); }

        template <typename State>
        void grow(const Space& space, const State& q) {
            unsigned dim = space.dimensions();
            assert(dim == min_.size());
            for (unsigned i=0 ; i<dim ; ++i) {
                Distance v = Space::coeff(q, i);
                for (Distance e = min(i) ; v < e && !min_[i].compare_exchange_weak(e, v, std::memory_order_relaxed); );
                for (Distance e = max(i) ; v > e && !max_[i].compare_exchange_weak(e, v, std::memory_order_relaxed); );
                // std::cout << "region " << i << ": " << min(i) << " to " << max(i) << std::endl;
            }
        }

        Distance selectAxis(unsigned *axis) const {
            int dimensions = min_.size();
            Distance dBest = max(0) - min(0);
            *axis = 0;

            for (int i=1 ; i<dimensions ; ++i) {
                Distance dx = max(i) - min(i);
                if (dx > dBest) {
                    dBest = dx;
                    *axis = i;
                }
            }

            return dBest;
        }

        Distance distTo(const Key& key) const {
            std::size_t dimensions = min_.size();
            impl::LPSum<p, Distance> sum(std::max({Distance(0), min(0) - key[0], key[0] - max(0)}));
            for (std::size_t i=1 ; i<dimensions ; ++i)
                sum += std::max({Distance(0), min(i) - key[i], key[i] - max(i)});
            return sum;
        }
    };

    template <typename Key, int p, typename Concurrency>
    class Region<Key, metric::LP<p>, Concurrency>
        : public LPRegion<Key, metric::Space<Key, metric::LP<p>>::kDimensions, p, Concurrency>
    {
    public:
        using LPRegion<Key, metric::Space<Key, metric::LP<p>>::kDimensions, p, Concurrency>::LPRegion;
    };

}

#endif


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

#include <iostream>
#include <fstream>

#include <boost/filesystem.hpp>
#include "Problems/applicationOutput.h"
#include "Problems/getAlgorithm.h"
#include "Problems/saveOptimizationResults.h"
#include "tudat/simulation/simulation.h"
#include "tudat/astro/LowThrustTrajectories/lowThrustOptimisationSetup.h"
#include "tudat/astro/LowThrustTrajectories/ShapeBasedMethods/hodographicShaping.h"
#include "tudat/astro/LowThrustTrajectories/ShapeBasedMethods/sphericalShaping.h"
#include "tudat/astro/LowThrustTrajectories/ShapeBasedMethods/createBaseFunctionHodographicShaping.h"
#include "tudat/astro/LowThrustTrajectories/lowThrustLegSettings.h"
#include "tudat/astro/LowThrustTrajectories/lowThrustLeg.h"
#include "tudat/astro/ephemerides/approximatePlanetPositions.h"
#include "tudat/astro/LowThrustTrajectories/ShapeBasedMethods/hodographicShapingOptimisationSetup.h"
#include "tudat/astro/LowThrustTrajectories/ShapeBasedMethods/getRecommendedBaseFunctionsHodographicShaping.h"

using namespace tudat;

//! Execute  main
int main( )
{
    //Set seed for reproducible results
    pagmo::random_device::set_seed( 123 );

    tudat::spice_interface::loadStandardSpiceKernels( );

    // Ephemeris departure body.
    ephemerides::EphemerisPointer pointerToDepartureBodyEphemeris = std::make_shared< ephemerides::ApproximatePlanetPositions>(
                ephemerides::ApproximatePlanetPositionsBase::BodiesWithEphemerisData::earthMoonBarycenter );

    // Ephemeris arrival body.
    ephemerides::EphemerisPointer pointerToArrivalBodyEphemeris = std::make_shared< ephemerides::ApproximatePlanetPositions >(
                ephemerides::ApproximatePlanetPositionsBase::BodiesWithEphemerisData::mars );

    std::function< Eigen::Vector6d( const double ) > departureStateFunction = [ = ]( const double currentTime )
    { return pointerToDepartureBodyEphemeris->getCartesianState( currentTime ); };

    std::function< Eigen::Vector6d( const double ) > arrivalStateFunction = [ = ]( const double currentTime )
    { return pointerToArrivalBodyEphemeris->getCartesianState( currentTime ); };

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////        GRID SEARCH FOR HODOGRAPHIC SHAPING LOWEST-ORDER SOLUTION            /////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Define bounds for departure date and time-of-flight.
    std::pair< double, double > departureTimeBounds =
            std::make_pair( 7304.5 * physical_constants::JULIAN_DAY, 10225.5 * physical_constants::JULIAN_DAY  );
    std::pair< double, double > timeOfFlightBounds =
            std::make_pair( 500.0 * physical_constants::JULIAN_DAY, 2000.0 * physical_constants::JULIAN_DAY );

    // Initialize free coefficients vectors
    Eigen::VectorXd freeCoefficientsRadialVelocityFunction = Eigen::VectorXd::Zero( 0 );
    Eigen::VectorXd freeCoefficientsNormalVelocityFunction = Eigen::VectorXd::Zero( 0 );
    Eigen::VectorXd freeCoefficientsAxialVelocityFunction = Eigen::VectorXd::Zero( 0 );

    std::map< int, Eigen::Vector4d > hodographicShapingResultsLowOrder;

    int numberCases = 0;

    // for-loop parsing the time-of-flight values, ranging from 500 to 2000 days, with a time-step of 5 days.
    for ( int i = 0 ; i <= ( timeOfFlightBounds.second - timeOfFlightBounds.first ) / ( 5.0 * physical_constants::JULIAN_DAY ) ; i++  )
    {
        double currentTOF = timeOfFlightBounds.first + i * 5.0 * physical_constants::JULIAN_DAY;

        // Get recommended base functions for the radial velocity composite function.
        std::vector< std::shared_ptr< shape_based_methods::BaseFunctionHodographicShaping > > radialVelocityFunctionComponents;
        shape_based_methods::getRecommendedRadialVelocityBaseFunctions(
                    radialVelocityFunctionComponents, freeCoefficientsRadialVelocityFunction, currentTOF );

        // Get recommended base functions for the normal velocity composite function.
        std::vector< std::shared_ptr< shape_based_methods::BaseFunctionHodographicShaping > > normalVelocityFunctionComponents;
        shape_based_methods::getRecommendedNormalAxialBaseFunctions(
                    normalVelocityFunctionComponents, freeCoefficientsNormalVelocityFunction, currentTOF );

        // for-loop parsing the departure date values, ranging from 7304 MJD to 10225 MJD (with 401 steps)
        for ( int j = 0 ; j <= 400; j++ )
        {
            double currentDepartureDate = departureTimeBounds.first + j * ( departureTimeBounds.second - departureTimeBounds.first ) / 400.0;

            // Compute states at departure and arrival.
            Eigen::Vector6d cartesianStateAtDeparture = pointerToDepartureBodyEphemeris->getCartesianState( currentDepartureDate );
            Eigen::Vector6d cartesianStateAtArrival = pointerToArrivalBodyEphemeris->getCartesianState( currentDepartureDate + currentTOF );

            int bestNumberOfRevolutions;
            double currentBestDeltaV;

            // Parse shaped trajectories with numbers of revolutions between 0 and 5.
            for ( int currentNumberOfRevolutions = 0 ; currentNumberOfRevolutions <= 5 ; currentNumberOfRevolutions++ )
            {
                // Get recommended base functions for the axial velocity composite function.
                std::vector< std::shared_ptr< shape_based_methods::BaseFunctionHodographicShaping > > axialVelocityFunctionComponents;
                shape_based_methods::getRecommendedAxialVelocityBaseFunctions(
                            axialVelocityFunctionComponents, freeCoefficientsAxialVelocityFunction, currentTOF,
                            currentNumberOfRevolutions );

                // Create hodographically shaped trajectory.
                tudat::shape_based_methods::HodographicShaping hodographicShaping = shape_based_methods::HodographicShaping(
                            cartesianStateAtDeparture, cartesianStateAtArrival, currentTOF,
                            spice_interface::getBodyGravitationalParameter( "Sun" ), currentNumberOfRevolutions,
                            radialVelocityFunctionComponents, normalVelocityFunctionComponents, axialVelocityFunctionComponents,
                            freeCoefficientsRadialVelocityFunction, freeCoefficientsNormalVelocityFunction,
                            freeCoefficientsAxialVelocityFunction );

                // Save trajectory with the lowest deltaV.
                if ( currentNumberOfRevolutions == 0 )
                {
                    bestNumberOfRevolutions = 0;
                    currentBestDeltaV = hodographicShaping.computeDeltaV( );
                }
                else
                {
                    if ( hodographicShaping.computeDeltaV( ) < currentBestDeltaV )
                    {
                        currentBestDeltaV = hodographicShaping.computeDeltaV( );
                        bestNumberOfRevolutions = currentNumberOfRevolutions;
                    }
                }
            }

            // Save results.
            Eigen::Vector4d outputVector =
                    ( Eigen::Vector4d( ) << currentTOF / physical_constants::JULIAN_DAY,
                      currentDepartureDate / physical_constants::JULIAN_DAY, currentBestDeltaV, bestNumberOfRevolutions ).finished( );
            numberCases++;
            hodographicShapingResultsLowOrder[ numberCases ] = outputVector;

        }
    }

    input_output::writeDataMapToTextFile( hodographicShapingResultsLowOrder,
                                          "hodographicShapingLowOrder.dat",
                                          tudat_pagmo_applications::getOutputPath( ),
                                          "",
                                          std::numeric_limits< double >::digits10,
                                          std::numeric_limits< double >::digits10,
                                          "," );

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////        RESTRICTED GRID SEARCH FOR HODOGRAPHIC SHAPING HIGH-ORDER SOLUTION             ///////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    numberCases = 0;

    // Define lower and upper bounds for the radial velocity free coefficients.
    std::vector< std::vector< double > > bounds( 2, std::vector< double >( 2, 0.0 ) );
    bounds[ 0 ][ 0 ] = - 600.0;
    bounds[ 1 ][ 0 ] = 800.0;
    bounds[ 0 ][ 1 ] = 0.0;
    bounds[ 1 ][ 1 ] = 1500.0;

    // Set fixed number of revolutions.
    int numberOfRevolutions = 1;

    std::map< int, Eigen::Vector4d > hodographicShapingResultsHigherOrder;
    std::map< int, Eigen::Vector4d > hodographicShapingResultsLowOrderOneRevolution;

    // for-loop parsing the time-of-flight values, ranging from 500 to 900 days, with a time-step of 20 days.
    for ( int i = 0 ; i <= ( 900.0 * physical_constants::JULIAN_DAY - timeOfFlightBounds.first ) / ( 20 * physical_constants::JULIAN_DAY ) ; i++  )
    {
        double currentTOF = timeOfFlightBounds.first + i * 20.0 * physical_constants::JULIAN_DAY;

        double frequency = 2.0 * mathematical_constants::PI / currentTOF;
        double scaleFactor = 1.0 / currentTOF;

        // Define settings for the two additional base functions for the radial velocity composite function.
        std::shared_ptr< shape_based_methods::BaseFunctionHodographicShapingSettings > fourthRadialVelocityBaseFunctionSettings =
                std::make_shared< shape_based_methods::PowerTimesTrigonometricFunctionHodographicShapingSettings >(
                    1.0, 0.5 * frequency, scaleFactor );
        std::shared_ptr< shape_based_methods::BaseFunctionHodographicShapingSettings > fifthRadialVelocityBaseFunctionSettings =
                std::make_shared< shape_based_methods::PowerTimesTrigonometricFunctionHodographicShapingSettings >(
                    1.0, 0.5 * frequency, scaleFactor );

        // Get recommended base functions for the radial velocity composite function, and add two additional base functions
        // (introducing two degrees of freedom in the trajectory design problem).
        std::vector< std::shared_ptr< shape_based_methods::BaseFunctionHodographicShaping > > radialVelocityFunctionComponents;
        shape_based_methods::getRecommendedRadialVelocityBaseFunctions( radialVelocityFunctionComponents, freeCoefficientsRadialVelocityFunction, currentTOF );
        radialVelocityFunctionComponents.push_back(
                    createBaseFunctionHodographicShaping( shape_based_methods::scaledPowerSine, fourthRadialVelocityBaseFunctionSettings ) );
        radialVelocityFunctionComponents.push_back(
                    createBaseFunctionHodographicShaping( shape_based_methods::scaledPowerCosine, fifthRadialVelocityBaseFunctionSettings ) );

        // Get recommended base functions for the normal velocity composite function.
        std::vector< std::shared_ptr< shape_based_methods::BaseFunctionHodographicShaping > > normalVelocityFunctionComponents;
        shape_based_methods::getRecommendedNormalAxialBaseFunctions( normalVelocityFunctionComponents, freeCoefficientsNormalVelocityFunction, currentTOF );

        // for-loop parsing departure dates ranging from 7304 MJD to 7379 MJD (with a time-step of 15 days).
        for ( int j = 0 ; j <= ( 7379.5 * physical_constants::JULIAN_DAY - departureTimeBounds.first ) / ( 15 * physical_constants::JULIAN_DAY ); j++ )
        {
            double currentDepartureDate = departureTimeBounds.first +
                    j * 15.0 * physical_constants::JULIAN_DAY;

            // Compute states at departure and arrival.
            Eigen::Vector6d cartesianStateAtDeparture = pointerToDepartureBodyEphemeris->getCartesianState( currentDepartureDate );
            Eigen::Vector6d cartesianStateAtArrival = pointerToArrivalBodyEphemeris->getCartesianState( currentDepartureDate + currentTOF );

            // Get recommended base functions for the axial velocity composite function.
            std::vector< std::shared_ptr< shape_based_methods::BaseFunctionHodographicShaping > > axialVelocityFunctionComponents;
            shape_based_methods::getRecommendedAxialVelocityBaseFunctions( axialVelocityFunctionComponents, freeCoefficientsAxialVelocityFunction,
                                                                           currentTOF, numberOfRevolutions );

            // Create hodographic shaping optimisation problem.
            problem prob{ shape_based_methods::FixedTimeHodographicShapingOptimisationProblem(
                            cartesianStateAtDeparture, cartesianStateAtArrival, currentTOF,
                            spice_interface::getBodyGravitationalParameter( "Sun" ), numberOfRevolutions,
                            radialVelocityFunctionComponents,
                            normalVelocityFunctionComponents, axialVelocityFunctionComponents, bounds ) };

            // Perform optimisation.
            algorithm algo{ pagmo::sga( ) };

            // Create an island with 1024 individuals
            island isl{ algo, prob, 25 };

            // Evolve for 100 generations
            for( int i = 0 ; i < 10; i++ )
            {
                isl.evolve( );
                while( isl.status( ) != pagmo::evolve_status::idle &&
                       isl.status( ) != pagmo::evolve_status::idle_error )
                {
                    isl.wait( );
                }
                isl.wait_check( ); // Raises errors

            }

            // Save high-order shaping solution.
            double currentBestDeltaV = isl.get_population( ).champion_f( )[ 0 ];

            Eigen::Vector4d outputVector = ( Eigen::Vector4d( ) << currentTOF / physical_constants::JULIAN_DAY,
                                             currentDepartureDate / physical_constants::JULIAN_DAY, currentBestDeltaV, 1 ).finished( );

            hodographicShapingResultsHigherOrder[ numberCases ] = outputVector;

            // Compute corresponding low-order hodographic shaping solution.
            Eigen::VectorXd freeCoefficientsRadialVelocityFunction = Eigen::VectorXd::Zero( 2 );
            Eigen::VectorXd freeCoefficientsNormalVelocityFunction = Eigen::VectorXd::Zero( 0 );
            Eigen::VectorXd freeCoefficientsAxialVelocityFunction = Eigen::VectorXd::Zero( 0 );

            // Compute low-order hodographically shaped trajectory (number of revolutions set to 1).
            tudat::shape_based_methods::HodographicShaping hodographicShapingLowOrderOneRevolution = shape_based_methods::HodographicShaping(
                        cartesianStateAtDeparture, cartesianStateAtArrival, currentTOF,
                        spice_interface::getBodyGravitationalParameter( "Sun" ), numberOfRevolutions,
                        radialVelocityFunctionComponents, normalVelocityFunctionComponents, axialVelocityFunctionComponents,
                        freeCoefficientsRadialVelocityFunction, freeCoefficientsNormalVelocityFunction, freeCoefficientsAxialVelocityFunction );

            // Save low-order shaping solution.
            outputVector = ( Eigen::Vector4d( ) << currentTOF / physical_constants::JULIAN_DAY,
                             currentDepartureDate / physical_constants::JULIAN_DAY, hodographicShapingLowOrderOneRevolution.computeDeltaV( ), 1 ).finished( );
            hodographicShapingResultsLowOrderOneRevolution[ numberCases ] = outputVector;

            numberCases++;

        }
    }

    input_output::writeDataMapToTextFile( hodographicShapingResultsLowOrderOneRevolution,
                                          "hodographicShapingLowOrderOneRevolution.dat",
                                          tudat_pagmo_applications::getOutputPath( ),
                                          "",
                                          std::numeric_limits< double >::digits10,
                                          std::numeric_limits< double >::digits10,
                                          "," );

    input_output::writeDataMapToTextFile( hodographicShapingResultsHigherOrder,
                                          "hodographicShapingHigherOrder.dat",
                                          tudat_pagmo_applications::getOutputPath( ),
                                          "",
                                          std::numeric_limits< double >::digits10,
                                          std::numeric_limits< double >::digits10,
                                          "," );

    return 0;

}


/**
 * Copyright 2018 Gizem S. Cetin <gscetin@wpi.edu>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "cufhe.h"
#include "cufhe_cpu.h"
#include <boost/python.hpp>
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/class.hpp>

using namespace cufhe;

BOOST_PYTHON_MODULE(fhepy_cpu)
{
        using namespace boost::python;

        /* DATA/STRUCTS */

	class_<Param>("Param")
		.def(init<uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, double, double>())
		.def_readwrite("lwe_n", &Param::lwe_n_)
		.def_readwrite("tlwe_n", &Param::tlwe_n_)
		.def_readwrite("tlwe_k", &Param::tlwe_k_)
		.def_readwrite("tgsw_decomp_bits", &Param::tgsw_decomp_bits_)
		.def_readwrite("tgsw_decomp_size", &Param::tgsw_decomp_size_)
		.def_readwrite("keyswitching_decomp_bits", &Param::keyswitching_decomp_bits_)
		.def_readwrite("keyswitching_decomp_size", &Param::keyswitching_decomp_size_)
		.def_readwrite("lwe_noise", &Param::lwe_noise_)
		.def_readwrite("tlwe_noise", &Param::tlwe_noise_);

        object Ptxt_class = class_<Ptxt>("Ptxt")
		.add_property("message", &Ptxt::get, &Ptxt::set);
                Ptxt_class.attr("PtxtSpace") = Ptxt::kPtxtSpace;

        class_<Ctxt>("Ctxt", init< optional<bool> >());
	class_<PubKey>("PubKey", init< optional<bool> >());
	class_<PriKey>("PriKey", init< optional<bool> >());

        /* SERVER METHODS */
	/* CPU GATES */
	def("AND", And);
	def("OR", Or);
	def("NAND", Nand);
	def("NOR", Nor);
	def("XOR", Xor);
	def("XNOR", Xnor);
	def("NOT", Not);
	def("Copy", Copy);

        /* CLIENT METHODS */

        def("SetSeed", SetSeed);
        def("PriKeyGen", PriKeyGen);
        def("PubKeyGen", PubKeyGen);
        def("KeyGen", KeyGen);
        def("Encrypt", Encrypt);
        def("Decrypt", Decrypt);

        /* I/O METHODS */

        def("WritePriKeyToFile", WritePriKeyToFile);
        def("ReadPriKeyFromFile", ReadPriKeyFromFile);
        def("WritePubKeyToFile", WritePubKeyToFile);
        def("ReadPubKeyFromFile", ReadPubKeyFromFile);
        def("WriteCtxtToFile", WriteCtxtToFile);
        def("ReadCtxtFromFile", ReadCtxtFromFile);

}


// Copyright 2018 The Simons Foundation, Inc. - All Rights Reserved.
//
// 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.

#include "rbm_multival.hpp"

#include <Eigen/Dense>
#include <iostream>
#include <map>
#include <vector>
#include "Utils/all_utils.hpp"
#include "Utils/log_cosh.hpp"
#include "abstract_machine.hpp"
#include "rbm_spin.hpp"

namespace netket {

RbmMultival::RbmMultival(std::shared_ptr<const AbstractHilbert> hilbert,
                         int nhidden, int alpha, bool usea, bool useb)
    : AbstractMachine(hilbert),
      nv_(hilbert->Size()),
      ls_(hilbert->LocalSize()),
      usea_(usea),
      useb_(useb) {
  nh_ = std::max(nhidden, alpha * nv_);
  Init();
}

void RbmMultival::Init() {
  W_.resize(nv_ * ls_, nh_);
  a_.resize(nv_ * ls_);
  b_.resize(nh_);

  thetas_.resize(nh_);
  lnthetas_.resize(nh_);
  thetasnew_.resize(nh_);
  lnthetasnew_.resize(nh_);

  npar_ = nv_ * nh_ * ls_;

  if (usea_) {
    npar_ += nv_ * ls_;
  } else {
    a_.setZero();
  }

  if (useb_) {
    npar_ += nh_;
  } else {
    b_.setZero();
  }

  auto localstates = GetHilbert().LocalStates();

  localconfs_.resize(nv_ * ls_);
  for (int i = 0; i < nv_ * ls_; i += ls_) {
    for (int j = 0; j < ls_; j++) {
      localconfs_(i + j) = localstates[j];
    }
  }

  mask_.resize(nv_ * ls_, nv_);
  mask_.setZero();

  for (int i = 0; i < nv_ * ls_; i++) {
    mask_(i, i / ls_) = 1;
  }

  for (int i = 0; i < ls_; i++) {
    confindex_[localstates[i]] = i;
  }

  vtilde_.resize(nv_ * ls_);

  InfoMessage() << "RBM Multival Initizialized with nvisible = " << nv_
                << " and nhidden = " << nh_ << std::endl;
  InfoMessage() << "Using visible bias = " << usea_ << std::endl;
  InfoMessage() << "Using hidden bias  = " << useb_ << std::endl;
  InfoMessage() << "Local size is      = " << ls_ << std::endl;
}

int RbmMultival::Nvisible() const { return nv_; }

int RbmMultival::Npar() const { return npar_; }

any RbmMultival::InitLookup(VisibleConstType v) {
  LookupType lt;
  lt.resize(b_.size());
  ComputeTheta(v, lt);
  return any{std::move(lt)};
}

void RbmMultival::UpdateLookup(VisibleConstType v,
                               const std::vector<int> &tochange,
                               const std::vector<double> &newconf,
                               any &lookup) {
  if (tochange.size() != 0) {
    auto &lt = any_cast_ref<LookupType>(lookup);
    for (std::size_t s = 0; s < tochange.size(); s++) {
      const int sf = tochange[s];
      const int oldtilde = confindex_[v[sf]];
      const int newtilde = confindex_[newconf[s]];

      lt -= W_.row(ls_ * sf + oldtilde);
      lt += W_.row(ls_ * sf + newtilde);
    }
  }
}

RbmMultival::VectorType RbmMultival::DerLogSingle(VisibleConstType v,
                                                  const any &lookup) {
  return DerLogSingleImpl(v, lookup.empty() ? InitLookup(v) : lookup);
}

RbmMultival::VectorType RbmMultival::DerLogSingleImpl(VisibleConstType v,
                                                      const any &lookup) {
  VectorType der(npar_);
  der.setZero();

  ComputeVtilde(v, vtilde_);

  int k = 0;

  if (usea_) {
    for (; k < nv_ * ls_; k++) {
      der(k) = vtilde_(k);
    }
  }

  lnthetas_ = (any_cast_ref<LookupType>(lookup)).array().tanh();

  if (useb_) {
    for (int p = 0; p < nh_; p++) {
      der(k) = lnthetas_(p);
      k++;
    }
  }

  for (int i = 0; i < nv_ * ls_; i++) {
    for (int j = 0; j < nh_; j++) {
      der(k) = lnthetas_(j) * vtilde_(i);
      k++;
    }
  }
  return der;
}

RbmMultival::VectorType RbmMultival::GetParameters() {
  VectorType pars(npar_);

  int k = 0;

  if (usea_) {
    for (; k < nv_ * ls_; k++) {
      pars(k) = a_(k);
    }
  }

  if (useb_) {
    for (int p = 0; p < nh_; p++) {
      pars(k) = b_(p);
      k++;
    }
  }

  for (int i = 0; i < nv_ * ls_; i++) {
    for (int j = 0; j < nh_; j++) {
      pars(k) = W_(i, j);
      k++;
    }
  }

  return pars;
}

void RbmMultival::SetParameters(VectorConstRefType pars) {
  int k = 0;

  if (usea_) {
    for (; k < nv_ * ls_; k++) {
      a_(k) = pars(k);
    }
  }

  if (useb_) {
    for (int p = 0; p < nh_; p++) {
      b_(p) = pars(k);
      k++;
    }
  }

  for (int i = 0; i < nv_ * ls_; i++) {
    for (int j = 0; j < nh_; j++) {
      W_(i, j) = pars(k);
      k++;
    }
  }
}

// Value of the logarithm of the wave-function
// using pre-computed look-up tables for efficiency
Complex RbmMultival::LogValSingle(VisibleConstType v, const any &lt) {
  if (lt.empty()) {
    ComputeTheta(v, thetas_);

    return (vtilde_.dot(a_) + SumLogCosh(thetas_));
  }

  ComputeVtilde(v, vtilde_);
  return (vtilde_.dot(a_) + SumLogCosh(any_cast_ref<LookupType>(lt)));
}

// Difference between logarithms of values, when one or more visible variables
// are being changed
void RbmMultival::LogValDiff(VisibleConstType v,
                             const std::vector<std::vector<int>> &tochange,
                             const std::vector<std::vector<double>> &newconf,
                             Eigen::Ref<Eigen::VectorXcd> logvaldiffs) {
  const std::size_t nconn = tochange.size();
  logvaldiffs = VectorType::Zero(nconn);

  ComputeTheta(v, thetas_);

  Complex logtsum = SumLogCosh(thetas_);

  for (std::size_t k = 0; k < nconn; k++) {
    if (tochange[k].size() != 0) {
      thetasnew_ = thetas_;

      for (std::size_t s = 0; s < tochange[k].size(); s++) {
        const int sf = tochange[k][s];
        const int oldtilde = confindex_[v[sf]];
        const int newtilde = confindex_[newconf[k][s]];

        logvaldiffs(k) -= a_(ls_ * sf + oldtilde);
        logvaldiffs(k) += a_(ls_ * sf + newtilde);

        thetasnew_ -= W_.row(ls_ * sf + oldtilde);
        thetasnew_ += W_.row(ls_ * sf + newtilde);
      }

      logvaldiffs(k) += SumLogCosh(thetasnew_) - logtsum;
    }
  }
}

#if 0
// Computhes the values of the theta pseudo-angles
inline void RbmMultival::ComputeTheta(VisibleConstType v, VectorType &theta) {
  ComputeVtilde(v, vtilde_);
  theta = (W_.transpose() * vtilde_ + b_);
}

inline void RbmMultival::ComputeVtilde(VisibleConstType v,
                                       Eigen::VectorXd &vtilde) {
  auto t = (localconfs_.array() == (mask_ * v).array());
  vtilde = t.template cast<double>();
}
#endif

void RbmMultival::Save(const std::string &filename) const {
  json state;
  state["Name"] = "RbmMultival";
  state["Nvisible"] = nv_;
  state["Nhidden"] = nh_;
  state["LocalSize"] = ls_;
  state["UseVisibleBias"] = usea_;
  state["UseHiddenBias"] = useb_;
  state["a"] = a_;
  state["b"] = b_;
  state["W"] = W_;
  WriteJsonToFile(state, filename);
}

void RbmMultival::Load(const std::string &filename) {
  auto const pars = ReadJsonFromFile(filename);
  if (pars.at("Name") != "RbmMultival") {
    throw InvalidInputError(
        "Error while constructing RbmMultival from Json input");
  }

  if (FieldExists(pars, "Nvisible")) {
    nv_ = pars["Nvisible"];
  }

  if (nv_ != GetHilbert().Size()) {
    throw InvalidInputError(
        "Loaded wave-function has incompatible Hilbert space");
  }

  if (FieldExists(pars, "LocalSize")) {
    ls_ = pars["LocalSize"];
  }
  if (ls_ != GetHilbert().LocalSize()) {
    throw InvalidInputError(
        "Loaded wave-function has incompatible Hilbert space");
  }

  if (FieldExists(pars, "Nhidden")) {
    nh_ = FieldVal(pars, "Nhidden");
  } else {
    nh_ = nv_ * double(FieldVal(pars, "Alpha"));
  }

  usea_ = FieldOrDefaultVal(pars, "UseVisibleBias", true);
  useb_ = FieldOrDefaultVal(pars, "UseHiddenBias", true);

  Init();

  // Loading parameters, if defined in the input
  if (FieldExists(pars, "a")) {
    a_ = pars["a"];
  } else {
    a_.setZero();
  }

  if (FieldExists(pars, "b")) {
    b_ = pars["b"];
  } else {
    b_.setZero();
  }
  if (FieldExists(pars, "W")) {
    W_ = pars["W"];
  }
}

bool RbmMultival::IsHolomorphic() const noexcept { return true; }

}  // namespace netket


/**

\file
\author Datta Ramadasan
//==============================================================================
//         Copyright 2015 INSTITUT PASCAL UMR 6602 CNRS/Univ. Clermont II
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================

*/

#ifndef __OPTIMISATION2_ALGO_LM_MANY_CLASSES_SCHUR_COMPLEMENT_IMPLICIT_SCHUR2_HPP__
#define __OPTIMISATION2_ALGO_LM_MANY_CLASSES_SCHUR_COMPLEMENT_IMPLICIT_SCHUR2_HPP__

#include "../levmar.hpp"
#include <libv/lma/lm/ba/initialize.hpp>
#include "schur.hpp"

#include <boost/mpl/remove_if.hpp>

namespace lma
{
  template<class TypeS, class AP, class H, class P, class Ys> struct PRODAPPHS
  {
    AP& ap;
    const H& h;
    const P& p;
    const Ys& ys;

    PRODAPPHS(AP& ap_, const H& h_, const P& p_, const Ys& ys_):ap(ap_),h(h_),p(p_),ys(ys_){}

    template<class V> void operator()(ttt::wrap<V>)
    {
      mpl::for_each<TypeS,ttt::wrap<mpl::_1>>(prod_ap_ph<V>(ap,h,p,ys));
    }
  };

  template<class BA, class SCHUR> struct BABY2
  {
    typedef typename SCHUR::KeyUs OptimizeKeys;
    typedef typename ListS<typename BA::ListeHessien,typename SCHUR::KeyUs>::type TypeS;// TODO en fait, on a besoin que des clés, et pas de la map complète avec les containers

    typedef typename SCHUR::TupleResiduUs TupleResiduUs;
    typedef typename SCHUR::TupleResiduVs TupleResiduVs;
    const BA& ba_;
    const SCHUR& schur_;

    const typename BA::Hessian& A() const { return ba_.h; }
    const TupleResiduUs& B() const { return schur_.bs; }

    BABY2(const BA& ba__, const SCHUR& schur__):ba_(ba__),schur_(schur__){}

    template<class AP, class P> void prodAP(AP& ap, const P& p) const
    {
      for_each<MetaProd<typename AddTranspose<TypeS>::type,TupleResiduUs>>(std::tie(ap,ba_.h,p),AABB());

      mpl::for_each<typename SCHUR::KeyVs,ttt::wrap<mpl::_1>>(PRODAPPHS<TypeS,AP,typename BA::Hessian,P,typename SCHUR::TupleWs>(ap,ba_.h,p,schur_.ys)); // S -= YWT * p
    }
  };

// ######################################################################################
// ######################################################################################

  template<class BA,class K> void set_zero(ImplicitSchurContainer<BA,K>& schur)
  {
    bf::for_each(schur.ys,detail::SetZero());
    bf::for_each(schur.bs,detail::SetZero());
  }

  template<class BDL, class BA, class K> void initialize(const BDL& bundle, ImplicitSchurContainer<BA,K>& schur)
  {
    bf::for_each(schur.ys,detail::ResizeInter<BDL>(bundle));
  }

  template<class Bdl, class NormEq, class MatrixTag_, class K> struct ImplicitSchur
  {
    typedef MatrixTag_ MatrixTag;
    typedef Bdl Bundle;
    typedef utils::Tic<false> Tic;
    typedef Bas<Bundle,MatrixTag> Ba;
    typedef typename Ba::Hessian Hessian;

    typedef ImplicitSchurContainer<Ba,K> SchurCont;
    SchurCont schur_;

    typedef typename SchurCont::TypeVs TypeVs;
    typedef typename SchurCont::TypeWs TypeWs;
    typedef typename SchurCont::ListResidu ResiduUs;
    typedef typename SchurCont::ListResiduVs ResiduVs;
    NormEq norm_eq;

    template<class Config>
    ImplicitSchur(Config config):norm_eq(config) {}

	  const typename SchurCont::TupleVs& get_vs() const { return schur_.save_vs;}

    void init(Bundle& bundle_, Ba&)
    {
      initialize(bundle_,schur_);
    }

    void init_zero()
    {
      set_zero(schur_);
    }

    void save_h(const Hessian& h)
    {
      bf::for_each(schur_.save_vs,AssignSame<Hessian>(h));
    }

    void reload_h(Hessian& h, double prev_lambda)
    {
      for_each<MetaUnary<typename SchurCont::DiagUs>>(std::tie(h),LambdaDiag(prev_lambda));
      for_each<MetaBinary<typename SchurCont::DiagVs>>(std::tie(h,schur_.save_vs),OpAssignSame());
    }

    void solve(Ba& ba_, const Bundle&)
    {
      inv_v(ba_.h);
      compute_y(ba_);
      compute_b(ba_);
      compute_delta_a(ba_);
      compute_delta_b(ba_);
    }

    void inv_v(Hessian& h)
    {
      Tic tic("Inv V");
      for_each<MetaUnary<typename SchurCont::DiagVs>>(std::tie(h),Inv());
      tic.disp();
    }

    void compute_y(const Ba& ba_)
    {
      Tic tic("Compute Y");
      // Y = W * V
      for_each<MetaProd<TypeWs,TypeVs>>(std::tie(schur_.ys,ba_.h,ba_.h),A__B_prod_C());
      tic.disp();
    }

    void compute_b(Ba& ba_)
    {
      Tic tic("Compute B");
      //b = ea - y * eb;
      schur_.bs = ba_.jte;
      for_each<MetaProd<TypeWs,ResiduVs>>(std::tie(schur_.bs,schur_.ys,ba_.jte),A__minus_B_prod_C());
      tic.disp();
    }

    void compute_delta_a(Ba& ba_)
    {
      Tic tic("Compute DA");
      norm_eq(BABY2<Ba,SchurCont>(ba_,schur_),ba_.delta,MatrixTag());
      tic.disp();
    }

    void compute_delta_b(Ba& ba_)
    {
      Tic tic("Compute DB");
// -------------------------------------------- //
//       delta_b = V * (eb - Wt * delta_a)      //
// -------------------------------------------- //
//    delta_b = Wt * delta_a
      for_each<MetaProd<Transpose<TypeWs>,ResiduUs>>(std::tie(ba_.delta,ba_.h,ba_.delta),A__B_prod_C());

//    delta_b = V * (eb - delta_b)
      for_each<MetaProd<TypeVs,ResiduVs>>(std::tie(ba_.delta,ba_.h,ba_.jte),A__B_prod_C_minus_A());
      tic.disp();
    }
  };

}// eon

namespace ttt
{
  template<class A, class B, class C, int I> struct Name<lma::ImplicitSchur<A,B,C,v::numeric_tag<I>>> { static std::string name(){ return "ImplicitSchur<" + ttt::name<B>() + "," + ttt::name<C>() + "," + lma::to_string(I) + ">"; } };
}

#endif


/* Copyright © 2018 Apple Inc. All rights reserved.
 *
 * Use of this source code is governed by a BSD-3-clause license that can
 * be found in the LICENSE.txt file or at https://opensource.org/licenses/BSD-3-Clause
 */

#define BOOST_TEST_MODULE test_od_yolo

#include <unity/toolkits/object_detection/od_yolo.hpp>

#include <algorithm>

#include <boost/test/unit_test.hpp>
#include <util/test_macros.hpp>
#include <unity/toolkits/coreml_export/mlmodel_include.hpp>
#include <unity/toolkits/neural_net/model_spec.hpp>

namespace turi {
namespace object_detection {
namespace {

using turi::neural_net::model_spec;

BOOST_AUTO_TEST_CASE(test_add_tiny_darknet_yolo) {

  static constexpr char COORDINATES_NAME[] = "test_coordinates";
  static constexpr char CONFIDENCE_NAME[] = "test_confidence";
  static constexpr char INPUT_NAME[] = "test_input";
  static constexpr size_t OUTPUT_GRID_SIZE = 13;
  static constexpr size_t OUTPUT_GRID_AREA = OUTPUT_GRID_SIZE*OUTPUT_GRID_SIZE;
  static constexpr size_t NUM_CLASSES = 6;
  static constexpr size_t NUM_PREDS = NUM_CLASSES + 5;  // 4 for bbox, 1 conf

  const std::string prefix = "__test__";
  const std::vector<std::pair<float, float>> anchor_boxes = {
      {1.f, 2.f}, {1.f, 1.f}, {2.f, 1.f},
      {2.f, 4.f}, {2.f, 2.f}, {4.f, 2.f},
      {4.f, 8.f}, {4.f, 4.f}, {8.f, 4.f},
      {8.f, 16.f}, {8.f, 8.f}, {16.f, 8.f},
      {16.f, 32.f}, {16.f, 16.f}, {32.f, 16.f},
  };

  model_spec nn_spec;
  add_yolo(&nn_spec, COORDINATES_NAME, CONFIDENCE_NAME, INPUT_NAME,
           anchor_boxes, NUM_CLASSES, OUTPUT_GRID_SIZE, OUTPUT_GRID_SIZE,
           prefix);

  // The add_yolo function simply appends a mostly fixed sequence of 24 layers
  // to an existing model_spec. Assert that the resulting proto is what we want.
  // In theory, some of the layers could be reordered or have different names,
  // but it's much easier to test for exact equality.

  const CoreML::Specification::NeuralNetwork& nn = nn_spec.get_coreml_spec();
  TS_ASSERT_EQUALS(nn.layers_size(), 24);

  const auto& ymap_sp_pre = nn.layers(0);
  TS_ASSERT_EQUALS(ymap_sp_pre.input_size(), 1);
  TS_ASSERT_EQUALS(ymap_sp_pre.input(0), INPUT_NAME);
  TS_ASSERT_EQUALS(ymap_sp_pre.output_size(), 1);
  TS_ASSERT_EQUALS(ymap_sp_pre.output(0), prefix + "ymap_sp_pre");
  TS_ASSERT_EQUALS(ymap_sp_pre.reshape().targetshape_size(), 4);
  TS_ASSERT_EQUALS(ymap_sp_pre.reshape().targetshape(0), 1);
  TS_ASSERT_EQUALS(ymap_sp_pre.reshape().targetshape(1), anchor_boxes.size());
  TS_ASSERT_EQUALS(ymap_sp_pre.reshape().targetshape(2), NUM_PREDS);
  TS_ASSERT_EQUALS(ymap_sp_pre.reshape().targetshape(3), OUTPUT_GRID_AREA);

  const auto& ymap_sp = nn.layers(1);
  TS_ASSERT_EQUALS(ymap_sp.input_size(), 1);
  TS_ASSERT_EQUALS(ymap_sp.input(0), prefix + "ymap_sp_pre");
  TS_ASSERT_EQUALS(ymap_sp.output_size(), 1);
  TS_ASSERT_EQUALS(ymap_sp.output(0), prefix + "ymap_sp");
  TS_ASSERT_EQUALS(ymap_sp.permute().axis_size(), 4);
  TS_ASSERT_EQUALS(ymap_sp.permute().axis(0), 0);
  TS_ASSERT_EQUALS(ymap_sp.permute().axis(1), 2);
  TS_ASSERT_EQUALS(ymap_sp.permute().axis(2), 1);
  TS_ASSERT_EQUALS(ymap_sp.permute().axis(3), 3);

  const auto& raw_rel_xy_sp = nn.layers(2);
  TS_ASSERT_EQUALS(raw_rel_xy_sp.input_size(), 1);
  TS_ASSERT_EQUALS(raw_rel_xy_sp.input(0), prefix + "ymap_sp");
  TS_ASSERT_EQUALS(raw_rel_xy_sp.output_size(), 1);
  TS_ASSERT_EQUALS(raw_rel_xy_sp.output(0), prefix + "raw_rel_xy_sp");
  TS_ASSERT_EQUALS(raw_rel_xy_sp.slice().startindex(), 0);
  TS_ASSERT_EQUALS(raw_rel_xy_sp.slice().endindex(), 2);
  TS_ASSERT_EQUALS(raw_rel_xy_sp.slice().stride(), 1);
  TS_ASSERT_EQUALS(raw_rel_xy_sp.slice().axis(),
                   CoreML::Specification::SliceLayerParams::CHANNEL_AXIS);

  const auto& rel_xy_sp = nn.layers(3);
  TS_ASSERT_EQUALS(rel_xy_sp.input_size(), 1);
  TS_ASSERT_EQUALS(rel_xy_sp.input(0), prefix + "raw_rel_xy_sp");
  TS_ASSERT_EQUALS(rel_xy_sp.output_size(), 1);
  TS_ASSERT_EQUALS(rel_xy_sp.output(0), prefix + "rel_xy_sp");
  TS_ASSERT(rel_xy_sp.activation().has_sigmoid());

  const auto& rel_xy = nn.layers(4);
  TS_ASSERT_EQUALS(rel_xy.input_size(), 1);
  TS_ASSERT_EQUALS(rel_xy.input(0), prefix + "rel_xy_sp");
  TS_ASSERT_EQUALS(rel_xy.output_size(), 1);
  TS_ASSERT_EQUALS(rel_xy.output(0), prefix + "rel_xy");
  TS_ASSERT_EQUALS(rel_xy.reshape().targetshape_size(), 4);
  TS_ASSERT_EQUALS(rel_xy.reshape().targetshape(0), 1);
  TS_ASSERT_EQUALS(rel_xy.reshape().targetshape(1), 2);
  TS_ASSERT_EQUALS(rel_xy.reshape().targetshape(2),
                   OUTPUT_GRID_AREA * anchor_boxes.size());
  TS_ASSERT_EQUALS(rel_xy.reshape().targetshape(3), 1);

  const auto& constant_xy = nn.layers(5);
  TS_ASSERT_EQUALS(constant_xy.input_size(), 0);
  TS_ASSERT_EQUALS(constant_xy.output_size(), 1);
  TS_ASSERT_EQUALS(constant_xy.output(0), prefix + "constant_xy");
  TS_ASSERT_EQUALS(constant_xy.loadconstant().shape_size(), 3);
  TS_ASSERT_EQUALS(constant_xy.loadconstant().shape(0), 2);
  TS_ASSERT_EQUALS(constant_xy.loadconstant().shape(1),
                   OUTPUT_GRID_AREA * anchor_boxes.size());
  TS_ASSERT_EQUALS(constant_xy.loadconstant().shape(2), 1);
  TS_ASSERT_EQUALS(constant_xy.loadconstant().data().floatvalue_size(),
                   2 * 2535 * 1);
  for (size_t b = 0; b < anchor_boxes.size(); ++b) {
    for (size_t y = 0; y < OUTPUT_GRID_SIZE; ++y) {
      for (size_t x = 0; x < OUTPUT_GRID_SIZE; ++x) {
        int idx = static_cast<int>(  b * OUTPUT_GRID_AREA
                                   + y * OUTPUT_GRID_SIZE
                                   + x);
        TS_ASSERT_EQUALS(constant_xy.loadconstant().data().floatvalue(idx), x);
        idx += anchor_boxes.size() * OUTPUT_GRID_AREA;
        TS_ASSERT_EQUALS(constant_xy.loadconstant().data().floatvalue(idx), y);
      }
    }
  }

  const auto& xy = nn.layers(6);
  TS_ASSERT_EQUALS(xy.input_size(), 2);
  TS_ASSERT_EQUALS(xy.input(0), prefix + "constant_xy");
  TS_ASSERT_EQUALS(xy.input(1), prefix + "rel_xy");
  TS_ASSERT_EQUALS(xy.output_size(), 1);
  TS_ASSERT_EQUALS(xy.output(0), prefix + "xy");
  TS_ASSERT(xy.has_add());

  const auto& raw_rel_wh_sp = nn.layers(7);
  TS_ASSERT_EQUALS(raw_rel_wh_sp.input_size(), 1);
  TS_ASSERT_EQUALS(raw_rel_wh_sp.input(0), prefix + "ymap_sp");
  TS_ASSERT_EQUALS(raw_rel_wh_sp.output_size(), 1);
  TS_ASSERT_EQUALS(raw_rel_wh_sp.output(0), prefix + "raw_rel_wh_sp");
  TS_ASSERT_EQUALS(raw_rel_wh_sp.slice().startindex(), 2);
  TS_ASSERT_EQUALS(raw_rel_wh_sp.slice().endindex(), 4);
  TS_ASSERT_EQUALS(raw_rel_wh_sp.slice().stride(), 1);
  TS_ASSERT_EQUALS(raw_rel_wh_sp.slice().axis(),
                   CoreML::Specification::SliceLayerParams::CHANNEL_AXIS);

  const auto& rel_wh_sp = nn.layers(8);
  TS_ASSERT_EQUALS(rel_wh_sp.input_size(), 1);
  TS_ASSERT_EQUALS(rel_wh_sp.input(0), prefix + "raw_rel_wh_sp");
  TS_ASSERT_EQUALS(rel_wh_sp.output_size(), 1);
  TS_ASSERT_EQUALS(rel_wh_sp.output(0), prefix + "rel_wh_sp");
  TS_ASSERT_EQUALS(rel_wh_sp.unary().type(),
                   CoreML::Specification::UnaryFunctionLayerParams::EXP);

  const auto& rel_wh = nn.layers(9);
  TS_ASSERT_EQUALS(rel_wh.input_size(), 1);
  TS_ASSERT_EQUALS(rel_wh.input(0), prefix + "rel_wh_sp");
  TS_ASSERT_EQUALS(rel_wh.output_size(), 1);
  TS_ASSERT_EQUALS(rel_wh.output(0), prefix + "rel_wh");
  TS_ASSERT_EQUALS(rel_wh.reshape().targetshape_size(), 4);
  TS_ASSERT_EQUALS(rel_wh.reshape().targetshape(0), 1);
  TS_ASSERT_EQUALS(rel_wh.reshape().targetshape(1), 2 * anchor_boxes.size());
  TS_ASSERT_EQUALS(rel_wh.reshape().targetshape(2), OUTPUT_GRID_SIZE);
  TS_ASSERT_EQUALS(rel_wh.reshape().targetshape(3), OUTPUT_GRID_SIZE);

  const auto& c_anchors = nn.layers(10);
  TS_ASSERT_EQUALS(c_anchors.input_size(), 0);
  TS_ASSERT_EQUALS(c_anchors.output_size(), 1);
  TS_ASSERT_EQUALS(c_anchors.output(0), prefix + "c_anchors");
  TS_ASSERT_EQUALS(c_anchors.loadconstant().shape_size(), 3);
  TS_ASSERT_EQUALS(c_anchors.loadconstant().shape(0), 2 * anchor_boxes.size());
  TS_ASSERT_EQUALS(c_anchors.loadconstant().shape(1), OUTPUT_GRID_SIZE);
  TS_ASSERT_EQUALS(c_anchors.loadconstant().shape(2), OUTPUT_GRID_SIZE);
  TS_ASSERT_EQUALS(c_anchors.loadconstant().data().floatvalue_size(),
                   2 * anchor_boxes.size() * OUTPUT_GRID_AREA);
  for (size_t i = 0; i < anchor_boxes.size(); ++i) {
    for (size_t j = 0; j < OUTPUT_GRID_AREA; ++j) {
      int idx = static_cast<int>(i * OUTPUT_GRID_AREA + j);
      TS_ASSERT_EQUALS(c_anchors.loadconstant().data().floatvalue(idx),
                       anchor_boxes[i].first);
      idx += static_cast<int>(anchor_boxes.size() * OUTPUT_GRID_AREA);
      TS_ASSERT_EQUALS(c_anchors.loadconstant().data().floatvalue(idx),
                       anchor_boxes[i].second);
    }
  }

  const auto& wh_pre = nn.layers(11);
  TS_ASSERT_EQUALS(wh_pre.input_size(), 2);
  TS_ASSERT_EQUALS(wh_pre.input(0), prefix + "c_anchors");
  TS_ASSERT_EQUALS(wh_pre.input(1), prefix + "rel_wh");
  TS_ASSERT_EQUALS(wh_pre.output_size(), 1);
  TS_ASSERT_EQUALS(wh_pre.output(0), prefix + "wh_pre");
  TS_ASSERT(wh_pre.has_multiply());

  const auto& wh = nn.layers(12);
  TS_ASSERT_EQUALS(wh.input_size(), 1);
  TS_ASSERT_EQUALS(wh.input(0), prefix + "wh_pre");
  TS_ASSERT_EQUALS(wh.output_size(), 1);
  TS_ASSERT_EQUALS(wh.output(0), prefix + "wh");
  TS_ASSERT_EQUALS(wh.reshape().targetshape_size(), 4);
  TS_ASSERT_EQUALS(wh.reshape().targetshape(0), 1);
  TS_ASSERT_EQUALS(wh.reshape().targetshape(1), 2);
  TS_ASSERT_EQUALS(wh.reshape().targetshape(2),
                   OUTPUT_GRID_AREA * anchor_boxes.size());
  TS_ASSERT_EQUALS(wh.reshape().targetshape(3), 1);

  const auto& boxes_out_transposed = nn.layers(13);
  TS_ASSERT_EQUALS(boxes_out_transposed.input_size(), 2);
  TS_ASSERT_EQUALS(boxes_out_transposed.input(0), prefix + "xy");
  TS_ASSERT_EQUALS(boxes_out_transposed.input(1), prefix + "wh");
  TS_ASSERT_EQUALS(boxes_out_transposed.output_size(), 1);
  TS_ASSERT_EQUALS(boxes_out_transposed.output(0),
                   prefix + "boxes_out_transposed");
  TS_ASSERT(boxes_out_transposed.has_concat());
  TS_ASSERT(!boxes_out_transposed.concat().sequenceconcat());

  const auto& boxes_out = nn.layers(14);
  TS_ASSERT_EQUALS(boxes_out.input_size(), 1);
  TS_ASSERT_EQUALS(boxes_out.input(0), prefix + "boxes_out_transposed");
  TS_ASSERT_EQUALS(boxes_out.output_size(), 1);
  TS_ASSERT_EQUALS(boxes_out.output(0), prefix + "boxes_out");
  TS_ASSERT_EQUALS(boxes_out.permute().axis_size(), 4);
  TS_ASSERT_EQUALS(boxes_out.permute().axis(0), 0);
  TS_ASSERT_EQUALS(boxes_out.permute().axis(1), 2);
  TS_ASSERT_EQUALS(boxes_out.permute().axis(2), 1);
  TS_ASSERT_EQUALS(boxes_out.permute().axis(3), 3);

  const auto& coordinates = nn.layers(15);
  TS_ASSERT_EQUALS(coordinates.input_size(), 1);
  TS_ASSERT_EQUALS(coordinates.input(0), prefix + "boxes_out");
  TS_ASSERT_EQUALS(coordinates.output_size(), 1);
  TS_ASSERT_EQUALS(coordinates.output(0), COORDINATES_NAME);
  TS_ASSERT_EQUALS(coordinates.scale().shapescale_size(), 3);
  TS_ASSERT_EQUALS(coordinates.scale().shapescale(0),
                   OUTPUT_GRID_AREA * anchor_boxes.size());
  TS_ASSERT_EQUALS(coordinates.scale().shapescale(1), 4);
  TS_ASSERT_EQUALS(coordinates.scale().shapescale(2), 1);
  TS_ASSERT_EQUALS(coordinates.scale().scale().floatvalue_size(),
                   OUTPUT_GRID_AREA * anchor_boxes.size() * 4 * 1);
  for (int i = 0; i < coordinates.scale().scale().floatvalue_size(); ++i) {
    TS_ASSERT_EQUALS(coordinates.scale().scale().floatvalue(i),
                     1.f / OUTPUT_GRID_SIZE);
  }

  const auto& scores_sp = nn.layers(16);
  TS_ASSERT_EQUALS(scores_sp.input_size(), 1);
  TS_ASSERT_EQUALS(scores_sp.input(0), prefix + "ymap_sp");
  TS_ASSERT_EQUALS(scores_sp.output_size(), 1);
  TS_ASSERT_EQUALS(scores_sp.output(0), prefix + "scores_sp");
  TS_ASSERT_EQUALS(scores_sp.slice().startindex(), 5);
  TS_ASSERT_EQUALS(scores_sp.slice().endindex(), NUM_PREDS);
  TS_ASSERT_EQUALS(scores_sp.slice().stride(), 1);
  TS_ASSERT_EQUALS(scores_sp.slice().axis(),
                   CoreML::Specification::SliceLayerParams::CHANNEL_AXIS);

  const auto& probs_sp = nn.layers(17);
  TS_ASSERT_EQUALS(probs_sp.input_size(), 1);
  TS_ASSERT_EQUALS(probs_sp.input(0), prefix + "scores_sp");
  TS_ASSERT_EQUALS(probs_sp.output_size(), 1);
  TS_ASSERT_EQUALS(probs_sp.output(0), prefix + "probs_sp");
  TS_ASSERT(probs_sp.has_softmax());

  const auto& logit_conf_sp = nn.layers(18);
  TS_ASSERT_EQUALS(logit_conf_sp.input_size(), 1);
  TS_ASSERT_EQUALS(logit_conf_sp.input(0), prefix + "ymap_sp");
  TS_ASSERT_EQUALS(logit_conf_sp.output_size(), 1);
  TS_ASSERT_EQUALS(logit_conf_sp.output(0), prefix + "logit_conf_sp");
  TS_ASSERT_EQUALS(logit_conf_sp.slice().startindex(), 4);
  TS_ASSERT_EQUALS(logit_conf_sp.slice().endindex(), 5);
  TS_ASSERT_EQUALS(logit_conf_sp.slice().stride(), 1);
  TS_ASSERT_EQUALS(logit_conf_sp.slice().axis(),
                   CoreML::Specification::SliceLayerParams::CHANNEL_AXIS);

  const auto& conf_sp = nn.layers(19);
  TS_ASSERT_EQUALS(conf_sp.input_size(), 1);
  TS_ASSERT_EQUALS(conf_sp.input(0), prefix + "logit_conf_sp");
  TS_ASSERT_EQUALS(conf_sp.output_size(), 1);
  TS_ASSERT_EQUALS(conf_sp.output(0), prefix + "conf_sp");
  TS_ASSERT(conf_sp.activation().has_sigmoid());

  const auto& conf_tiled_sp = nn.layers(20);
  TS_ASSERT_EQUALS(conf_tiled_sp.input_size(), NUM_CLASSES);
  for (int i = 0; i < static_cast<int>(NUM_CLASSES); ++i) {
    TS_ASSERT_EQUALS(conf_tiled_sp.input(i), prefix + "conf_sp");
  }
  TS_ASSERT_EQUALS(conf_tiled_sp.output_size(), 1);
  TS_ASSERT_EQUALS(conf_tiled_sp.output(0), prefix + "conf_tiled_sp");
  TS_ASSERT(conf_tiled_sp.has_concat());
  TS_ASSERT(!conf_tiled_sp.concat().sequenceconcat());

  const auto& confprobs_sp = nn.layers(21);
  TS_ASSERT_EQUALS(confprobs_sp.input_size(), 2);
  TS_ASSERT_EQUALS(confprobs_sp.input(0), prefix + "conf_tiled_sp");
  TS_ASSERT_EQUALS(confprobs_sp.input(1), prefix + "probs_sp");
  TS_ASSERT_EQUALS(confprobs_sp.output_size(), 1);
  TS_ASSERT_EQUALS(confprobs_sp.output(0), prefix + "confprobs_sp");
  TS_ASSERT(confprobs_sp.has_multiply());

  const auto& confprobs_transposed = nn.layers(22);
  TS_ASSERT_EQUALS(confprobs_transposed.input_size(), 1);
  TS_ASSERT_EQUALS(confprobs_transposed.input(0), prefix + "confprobs_sp");
  TS_ASSERT_EQUALS(confprobs_transposed.output_size(), 1);
  TS_ASSERT_EQUALS(confprobs_transposed.output(0),
                   prefix + "confprobs_transposed");
  TS_ASSERT_EQUALS(confprobs_transposed.reshape().targetshape_size(), 4);
  TS_ASSERT_EQUALS(confprobs_transposed.reshape().targetshape(0), 1);
  TS_ASSERT_EQUALS(confprobs_transposed.reshape().targetshape(1), NUM_CLASSES);
  TS_ASSERT_EQUALS(confprobs_transposed.reshape().targetshape(2),
                   OUTPUT_GRID_AREA * anchor_boxes.size());
  TS_ASSERT_EQUALS(confprobs_transposed.reshape().targetshape(3), 1);

  const auto& confidence = nn.layers(23);
  TS_ASSERT_EQUALS(confidence.input_size(), 1);
  TS_ASSERT_EQUALS(confidence.input(0), prefix + "confprobs_transposed");
  TS_ASSERT_EQUALS(confidence.output_size(), 1);
  TS_ASSERT_EQUALS(confidence.output(0), CONFIDENCE_NAME);
  TS_ASSERT_EQUALS(confidence.permute().axis_size(), 4);
  TS_ASSERT_EQUALS(confidence.permute().axis(0), 0);
  TS_ASSERT_EQUALS(confidence.permute().axis(1), 2);
  TS_ASSERT_EQUALS(confidence.permute().axis(2), 1);
  TS_ASSERT_EQUALS(confidence.permute().axis(3), 3);
}

}  // namespace
}  // namespace object_detection
}  // namespace turi


/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.
 */

#include <stdint.h>

#include <algorithm>
#include <initializer_list>
#include <ostream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include <glog/logging.h>

#include <boost/lexical_cast.hpp>

#include <mesos/v1/resources.hpp>
#include <mesos/v1/values.hpp>

#include <stout/error.hpp>
#include <stout/foreach.hpp>
#include <stout/strings.hpp>

using std::max;
using std::min;
using std::ostream;
using std::string;
using std::vector;

namespace mesos {
namespace v1 {

ostream& operator<<(ostream& stream, const Value::Scalar& scalar)
{
  return stream << scalar.value();
}

bool operator==(const Value::Scalar& left, const Value::Scalar& right)
{
  return left.value() == right.value();
}

bool operator<=(const Value::Scalar& left, const Value::Scalar& right)
{
  return left.value() <= right.value();
}

Value::Scalar operator+(const Value::Scalar& left, const Value::Scalar& right)
{
  Value::Scalar result;
  result.set_value(left.value() + right.value());
  return result;
}

Value::Scalar operator-(const Value::Scalar& left, const Value::Scalar& right)
{
  Value::Scalar result;
  result.set_value(left.value() - right.value());
  return result;
}

Value::Scalar& operator+=(Value::Scalar& left, const Value::Scalar& right)
{
  left.set_value(left.value() + right.value());
  return left;
}

Value::Scalar& operator-=(Value::Scalar& left, const Value::Scalar& right)
{
  left.set_value(left.value() - right.value());
  return left;
}

namespace internal {

struct Range
{
  uint64_t start;
  uint64_t end;
};

// Coalesces the vector of ranges provided and modifies `result` to contain the
// solution.
// The algorithm first sorts all the individual intervals so that we can iterate
// over them sequentially.
// The algorithm does a single pass, after the sort, and builds up the solution
// in place. It then modifies the `result` with as few steps as possible. The
// expensive part of this operation is modification of the protobuf, which is
// why we prefer to build up the solution in a temporary vector.
void coalesce(Value::Ranges* result, vector<Range> ranges)
{
  // Exit early if empty.
  if (ranges.empty()) {
    result->clear_range();
    return;
  }

  std::sort(
      ranges.begin(),
      ranges.end(),
      [](const Range& left, const Range& right) {
        return std::tie(left.start, left.end) <
               std::tie(right.start, right.end);
      });

  // We build up initial state of the current range.
  CHECK(!ranges.empty());
  int count = 1;
  Range current = ranges.front();

  // In a single pass, we compute the size of the end result, as well as modify
  // in place the intermediate data structure to build up result as we
  // solve it.
  foreach (const Range& range, ranges) {
    // Skip if this range is equivalent to the current range.
    if (range.start == current.start && range.end == current.end) {
      continue;
    }

    // If the current range just needs to be extended on the right.
    if (range.start == current.start && range.end > current.end) {
      current.end = range.end;
    } else if (range.start > current.start) {
      // If we are starting farther ahead, then there are 2 cases:
      if (range.start <= current.end + 1) {
        // 1. Ranges are overlapping and we can merge them.
        current.end = max(current.end, range.end);
      } else {
        // 2. No overlap and we are adding a new range.
        ranges[count - 1] = current;
        ++count;
        current = range;
      }
    }
  }

  // Record the state of the last range into of ranges vector.
  ranges[count - 1] = current;

  CHECK(count <= static_cast<int>(ranges.size()));

  // Shrink result if it is too large by deleting trailing subrange.
  if (count < result->range_size()) {
    result->mutable_range()->DeleteSubrange(
        count, result->range_size() - count);
  }

  // Resize enough space so we allocate the pointer array just once.
  result->mutable_range()->Reserve(count);

  // Copy the solution from ranges vector into result.
  for (int i = 0; i < count; ++i) {
    // result might be small and needs to be extended.
    if (i >= result->range_size()) {
      result->add_range();
    }

    CHECK(i < result->range_size());
    result->mutable_range(i)->set_begin(ranges[i].start);
    result->mutable_range(i)->set_end(ranges[i].end);
  }

  CHECK_EQ(result->range_size(), count);
}

} // namespace internal {

// Coalesce the given addedRanges  into 'result' ranges.
void coalesce(
    Value::Ranges* result,
    std::initializer_list<Value::Ranges> addedRanges)
{
  size_t rangesSum = result->range_size();
  foreach (const Value::Ranges& range, addedRanges) {
    rangesSum += range.range_size();
  }

  vector<internal::Range> ranges;
  ranges.reserve(rangesSum);

  // Merges ranges into a vector.
  auto fill = [&ranges](const Value::Ranges& inputs) {
    foreach (const Value::Range& range, inputs.range()) {
      ranges.push_back({range.begin(), range.end()});
    }
  };

  // Merge both ranges into the vector;
  fill(*result);
  foreach (const Value::Ranges& range, addedRanges) {
    fill(range);
  }

  internal::coalesce(result, std::move(ranges));
}

// Coalesce the given Value::Ranges 'ranges'.
void coalesce(Value::Ranges* result)
{
  coalesce(result, {Value::Ranges()});
}

// Coalesce the given range 'addedRange' into 'result' ranges.
void coalesce(Value::Ranges* result, const Value::Range& addedRange)
{
  Value::Ranges ranges;
  Value::Range* range = ranges.add_range();
  range->CopyFrom(addedRange);
  coalesce(result, {ranges});
}

// Removes a range from already coalesced ranges.
// The algorithms constructs a new vector of ranges which is then
// coalesced into a Value::Ranges instance.
static void remove(Value::Ranges* _ranges, const Value::Range& removal)
{
  vector<internal::Range> ranges;
  ranges.reserve(_ranges->range_size());

  foreach (const Value::Range& range, _ranges->range()) {
    // Skip if the entire range is subsumed by `removal`.
    if (range.begin() >= removal.begin() && range.end() <= removal.end()) {
      continue;
    }

    // Divide if the range subsumes the `removal`.
    if (range.begin() < removal.begin() && range.end() > removal.end()) {
      // Front.
      ranges.emplace_back(internal::Range{range.begin(), removal.begin() - 1});
      // Back.
      ranges.emplace_back(internal::Range{removal.end() + 1, range.end()});
    }

    // Fully Emplace if the range doesn't intersect.
    if (range.end() < removal.begin() || range.begin() > removal.end()) {
      ranges.emplace_back(internal::Range{range.begin(), range.end()});
    } else {
      // Trim if the range does intersect.
      if (range.end() > removal.end()) {
        // Trim front.
        ranges.emplace_back(internal::Range{removal.end() + 1, range.end()});
      } else {
        // Trim back.
        CHECK(range.begin() < removal.begin());
        ranges.emplace_back(
            internal::Range{range.begin(), removal.begin() - 1});
      }
    }
  }

  internal::coalesce(_ranges, std::move(ranges));
}

ostream& operator<<(ostream& stream, const Value::Ranges& ranges)
{
  stream << "[";
  for (int i = 0; i < ranges.range_size(); i++) {
    stream << ranges.range(i).begin() << "-" << ranges.range(i).end();
    if (i + 1 < ranges.range_size()) {
      stream << ", ";
    }
  }
  stream << "]";
  return stream;
}

bool operator==(const Value::Ranges& _left, const Value::Ranges& _right)
{
  Value::Ranges left;
  coalesce(&left, {_left});

  Value::Ranges right;
  coalesce(&right, {_right});

  if (left.range_size() == right.range_size()) {
    for (int i = 0; i < left.range_size(); i++) {
      // Make sure this range is equal to a range in the right.
      bool found = false;
      for (int j = 0; j < right.range_size(); j++) {
        if (left.range(i).begin() == right.range(j).begin() &&
            left.range(i).end() == right.range(j).end()) {
          found = true;
          break;
        }
      }

      if (!found) {
        return false;
      }
    }

    return true;
  }

  return false;
}

bool operator<=(const Value::Ranges& _left, const Value::Ranges& _right)
{
  Value::Ranges left;
  coalesce(&left, {_left});

  Value::Ranges right;
  coalesce(&right, {_right});

  for (int i = 0; i < left.range_size(); i++) {
    // Make sure this range is a subset of a range in right.
    bool matched = false;
    for (int j = 0; j < right.range_size(); j++) {
      if (left.range(i).begin() >= right.range(j).begin() &&
          left.range(i).end() <= right.range(j).end()) {
        matched = true;
        break;
      }
    }
    if (!matched) {
      return false;
    }
  }

  return true;
}

Value::Ranges operator+(const Value::Ranges& left, const Value::Ranges& right)
{
  Value::Ranges result;
  coalesce(&result, {left, right});
  return result;
}

Value::Ranges operator-(const Value::Ranges& left, const Value::Ranges& right)
{
  Value::Ranges result;
  coalesce(&result, {left});
  return result -= right;
}

Value::Ranges& operator+=(Value::Ranges& left, const Value::Ranges& right)
{
  coalesce(&left, {right});
  return left;
}

Value::Ranges& operator-=(Value::Ranges& left, const Value::Ranges& right)
{
  coalesce(&left);
  for (int i = 0; i < right.range_size(); ++i) {
    remove(&left, right.range(i));
  }
  return left;
}

ostream& operator<<(ostream& stream, const Value::Set& set)
{
  stream << "{";
  for (int i = 0; i < set.item_size(); i++) {
    stream << set.item(i);
    if (i + 1 < set.item_size()) {
      stream << ", ";
    }
  }
  stream << "}";
  return stream;
}

bool operator==(const Value::Set& left, const Value::Set& right)
{
  if (left.item_size() == right.item_size()) {
    for (int i = 0; i < left.item_size(); i++) {
      // Make sure this item is equal to an item in the right.
      bool found = false;
      for (int j = 0; j < right.item_size(); j++) {
        if (left.item(i) == right.item(i)) {
          found = true;
          break;
        }
      }

      if (!found) {
        return false;
      }
    }

    return true;
  }

  return false;
}

bool operator<=(const Value::Set& left, const Value::Set& right)
{
  if (left.item_size() <= right.item_size()) {
    for (int i = 0; i < left.item_size(); i++) {
      // Make sure this item is equal to an item in the right.
      bool found = false;
      for (int j = 0; j < right.item_size(); j++) {
        if (left.item(i) == right.item(j)) {
          found = true;
          break;
        }
      }

      if (!found) {
        return false;
      }
    }

    return true;
  }

  return false;
}

Value::Set operator+(const Value::Set& left, const Value::Set& right)
{
  Value::Set result;

  for (int i = 0; i < left.item_size(); i++) {
    result.add_item(left.item(i));
  }

  // A little bit of extra logic to avoid adding duplicates from right.
  for (int i = 0; i < right.item_size(); i++) {
    bool found = false;
    for (int j = 0; j < result.item_size(); j++) {
      if (right.item(i) == result.item(j)) {
        found = true;
        break;
      }
    }

    if (!found) {
      result.add_item(right.item(i));
    }
  }

  return result;
}

Value::Set operator-(const Value::Set& left, const Value::Set& right)
{
  Value::Set result;

  // Look for the same item in right as we add left to result.
  for (int i = 0; i < left.item_size(); i++) {
    bool found = false;
    for (int j = 0; j < right.item_size(); j++) {
      if (left.item(i) == right.item(j)) {
        found = true;
        break;
      }
    }

    if (!found) {
      result.add_item(left.item(i));
    }
  }

  return result;
}

Value::Set& operator+=(Value::Set& left, const Value::Set& right)
{
  // A little bit of extra logic to avoid adding duplicates from right.
  for (int i = 0; i < right.item_size(); i++) {
    bool found = false;
    for (int j = 0; j < left.item_size(); j++) {
      if (right.item(i) == left.item(j)) {
        found = true;
        break;
      }
    }

    if (!found) {
      left.add_item(right.item(i));
    }
  }

  return left;
}

Value::Set& operator-=(Value::Set& left, const Value::Set& right)
{
  // For each item in right, remove it if it's in left.
  for (int i = 0; i < right.item_size(); i++) {
    for (int j = 0; j < left.item_size(); j++) {
      if (right.item(i) == left.item(j)) {
        left.mutable_item()->DeleteSubrange(j, 1);
        break;
      }
    }
  }

  return left;
}

ostream& operator<<(ostream& stream, const Value::Text& value)
{
  return stream << value.value();
}

bool operator==(const Value::Text& left, const Value::Text& right)
{
  return left.value() == right.value();
}

namespace internal {
namespace values {

Try<Value> parse(const string& text)
{
  Value value;

  // Remove any spaces from the text.
  string temp;
  foreach (const char c, text) {
    if (c != ' ') {
      temp += c;
    }
  }

  if (temp.length() == 0) {
    return Error("Expecting non-empty string");
  }

  // TODO(ynie): Find a better way to check brackets.
  if (!strings::checkBracketsMatching(temp, '{', '}') ||
      !strings::checkBracketsMatching(temp, '[', ']') ||
      !strings::checkBracketsMatching(temp, '(', ')')) {
    return Error("Mismatched brackets");
  }

  size_t index = temp.find('[');
  if (index == 0) {
    // This is a Value::Ranges.
    value.set_type(Value::RANGES);
    Value::Ranges* ranges = value.mutable_ranges();
    const vector<string>& tokens = strings::tokenize(temp, "[]-,\n");
    if (tokens.size() % 2 != 0) {
      return Error("Expecting one or more \"ranges\"");
    } else {
      for (size_t i = 0; i < tokens.size(); i += 2) {
        Value::Range* range = ranges->add_range();

        int j = i;
        try {
          range->set_begin(boost::lexical_cast<uint64_t>((tokens[j++])));
          range->set_end(boost::lexical_cast<uint64_t>(tokens[j++]));
        } catch (const boost::bad_lexical_cast&) {
          return Error(
              "Expecting non-negative integers in '" + tokens[j - 1] + "'");
        }
      }

      coalesce(ranges);

      return value;
    }
  } else if (index == string::npos) {
    size_t index = temp.find('{');
    if (index == 0) {
      // This is a set.
      value.set_type(Value::SET);
      Value::Set* set = value.mutable_set();
      const vector<string>& tokens = strings::tokenize(temp, "{},\n");
      for (size_t i = 0; i < tokens.size(); i++) {
        set->add_item(tokens[i]);
      }
      return value;
    } else if (index == string::npos) {
      try {
        // This is a scalar.
        value.set_type(Value::SCALAR);
        Value::Scalar* scalar = value.mutable_scalar();
        scalar->set_value(boost::lexical_cast<double>(temp));
        return value;
      } catch (const boost::bad_lexical_cast&) {
        // This is a text.
        value.set_type(Value::TEXT);
        Value::Text* text = value.mutable_text();
        text->set_value(temp);
        return value;
      }
    } else {
      return Error("Unexpected '{' found");
    }
  }

  return Error("Unexpected '[' found");
}

} // namespace values {
} // namespace internal {

} // namespace v1 {
} // namespace mesos {


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Nikita Kaskov <nbering@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//
// @file This module implements two binding commitment schemes used in the Groth16
// aggregation.
// The first one is a commitment scheme that commits to a single vector $a$ of
// length n in the second base group $G_1$ (for example):
// * it requires a structured SRS $v_1$ of the form $(h,h^u,h^{u^2}, ...
// ,g^{h^{n-1}})$ with $h \in G_2$ being a random generator of $G_2$ and $u$ a
// random scalar (coming from a power of tau ceremony for example)
// * it requires a second structured SRS $v_2$ of the form $(h,h^v,h^{v^2},
// ...$ with $v$ being a random scalar different than u (coming from another
// power of tau ceremony for example)
// The Commitment is a tuple $(\prod_{i=0}^{n-1} e(a_i,v_{1,i}),
// \prod_{i=0}^{n-1} e(a_i,v_{2,i}))$
//
// The second one takes two vectors $a \in G_1^n$ and $b \in G_2^n$ and commits
// to them using a similar approach as above. It requires an additional SRS
// though:
// * $v_1$ and $v_2$ stay the same
// * An additional tuple $w_1 = (g^{u^n},g^{u^{n+1}},...g^{u^{2n-1}})$ and $w_2 =
// (g^{v^n},g^{v^{n+1},...,g^{v^{2n-1}})$ where $g$ is a random generator of
// $G_1$
// The commitment scheme returns a tuple:
// * $\prod_{i=0}^{n-1} e(a_i,v_{1,i})e(w_{1,i},b_i)$
// * $\prod_{i=0}^{n-1} e(a_i,v_{2,i})e(w_{2,i},b_i)$
//
// The second commitment scheme enables to save some KZG verification in the
// verifier of the Groth16 verification protocol since we pack two vectors in
// one commitment.

#ifndef CRYPTO3_R1CS_GG_PPZKSNARK_IPP2_COMMITMENT_HPP
#define CRYPTO3_R1CS_GG_PPZKSNARK_IPP2_COMMITMENT_HPP

#include <tuple>
#include <vector>

#include <boost/assert.hpp>
#include <boost/iterator/zip_iterator.hpp>

#include <nil/crypto3/algebra/type_traits.hpp>

#include <nil/crypto3/algebra/algorithms/pair.hpp>

namespace nil {
    namespace crypto3 {
        namespace zk {
            namespace snark {
                /// Both commitment outputs a pair of $F_q^k$ element.
                template<typename CurveType>
                using r1cs_gg_ppzksnark_ipp2_commitment_output =
                    std::pair<typename CurveType::scalar_field_type::value_type,
                              typename CurveType::scalar_field_type::value_type>;

                /// Key is a generic commitment key that is instanciated with g and h as basis,
                /// and a and b as powers.
                template<typename FieldType>
                struct r1cs_gg_ppzksnark_ipp2_commitment_key {
                    typedef FieldType field_type;
                    typedef typename field_type::value_type value_type;

                    typedef typename std::vector<value_type>::const_iterator const_iterator;
                    typedef typename std::vector<value_type>::iterator iterator;

                    /// Exponent is a
                    std::vector<value_type> a;
                    /// Exponent is b
                    std::vector<value_type> b;

                    /// Returns true if commitment keys have the exact required length.
                    /// It is necessary for the IPP scheme to work that commitment
                    /// key have the exact same number of arguments as the number of proofs to
                    /// aggregate.
                    inline bool valid(std::size_t n) {
                        return a.size() == n && n == b.size();
                    }

                    /// Returns both vectors scaled by the given vector entrywise.
                    /// In other words, it returns $\{v_i^{s_i}\}$
                    template<typename InputIterator>
                    r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType> scale(InputIterator sfirst, InputIterator slast) {
                        BOOST_ASSERT(std::distance(sfirst, slast) == a.size() &&
                                     std::distance(sfirst, slast) == b.size());
                        r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType> result;
                        const_iterator afirst = a.begin(), bfirst = b.begin();

                        while (sfirst != slast && afirst != a.end() && bfirst != b.end()) {
                            result.a.emplace_back(afirst->to_projective() * sfirst->to_affine());
                            result.b.emplace_back(bfirst->to_projective() * sfirst->to_affine());
                        }

                        return result;
                    }

                    /// Takes a left and right commitment key and returns a commitment
                    /// key $left \circ right^{scale} = (left_i*right_i^{scale} ...)$. This is
                    /// required step during GIPA recursion.
                    r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType>
                        compress(const r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType> &other,
                                 const typename FieldType::number_type &scale) {
                        BOOST_ASSERT(a.size() == other.a.size() && other.valid());

                        r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType> result;
                        const_iterator afirst = a.begin(), bfirst = b.begin();
                        const_iterator oafirst = other.a.begin(), obfirst = other.b.begin();

                        while (afirst != a.end() && bfirst != b.end() && oafirst != other.a.begin() &&
                               obfirst != other.b.begin()) {
                            auto ra = oafirst->to_projective() * scale;
                            auto rb = obfirst->to_projective() * scale;

                            ra.add_assign_mixed(*afirst);
                            rb.add_assign_mixed(*bfirst);

                            result.a.emplace_back(ra.to_affine());
                            result.b.emplace_back(rb.to_affine());

                            ++afirst;
                            ++bfirst;
                            ++oafirst;
                            ++obfirst;
                        }

                        return result;
                    }
                };

                /*!
                 * Returns both vectors scaled by the given vector entrywise.
                 * In other words, it returns $\{v_i^{s_i}\}$
                 */
                template<typename FieldType, typename InputIterator>
                r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType>
                    scale(const r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType> &key,
                          InputIterator first,
                          InputIterator last) {
                    BOOST_ASSERT(std::distance(first, last) == key.a.size() &&
                                 std::distance(first, last) == key.b.size());
                    r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType> result;

                    std::for_each(
                        boost::make_zip_iterator(std::make_tuple(first, key.a.begin(), key.b.begin())),
                        boost::make_zip_iterator(std::make_tuple(last, key.a.end(), key.b.end())),
                        [&](const std::tuple<const typename FieldType::value_type &,
                                             const typename FieldType::value_type &,
                                             const typename FieldType::value_type &> &t) {
                            result.a.emplace_back(std::get<1>(t).to_projective() * std::get<0>(t).to_affine());
                            result.b.emplace_back(std::get<2>(t).to_projective() * std::get<0>(t).to_affine());
                        });

                    return result;
                }

                /// Takes a left and right commitment key and returns a commitment
                /// key $left \circ right^{scale} = (left_i*right_i^{scale} ...)$. This is
                /// required step during GIPA recursion.
                template<typename FieldType>
                r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType>
                    compress(const r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType> &left,
                             const r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType> &right,
                             const typename FieldType::number_type &scale) {
                    BOOST_ASSERT(left.a.size() == right.a.size());

                    r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType> result;
                    typename r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType>::const_iterator lafirst = left.a.begin(),
                                                                                lbfirst = left.b.begin();
                    typename r1cs_gg_ppzksnark_ipp2_commitment_key<FieldType>::const_iterator rafirst = right.a.begin(),
                                                                                rbfirst = right.b.begin();

                    std::for_each(
                        boost::make_zip_iterator(
                            std::make_tuple(left.a.begin(), left.b.begin(), right.a.begin(), right.b.begin())),
                        boost::make_zip_iterator(
                            std::make_tuple(left.a.end(), left.b.end(), right.a.end(), right.b.end())),
                        [&](const std::tuple<
                            const typename FieldType::value_type &, const typename FieldType::value_type &,
                            const typename FieldType::value_type &, const typename FieldType::value_type &> &t) {
                            auto ra = std::get<2>(t).to_projective() * scale;
                            auto rb = std::get<3>(t).to_projective() * scale;

                            ra += std::get<0>(t);
                            rb += std::get<1>(t);

                            result.a.emplace_back(ra.to_affine());
                            result.b.emplace_back(rb.to_affine());
                        });

                    return result;
                }

                /// Commitment key used by the "single" commitment on G1 values as
                /// well as in the "pair" commtitment.
                /// It contains $\{h^a^i\}_{i=1}^n$ and $\{h^b^i\}_{i=1}^n$
                template<typename CurveType>
                using r1cs_gg_ppzksnark_ipp2_vkey = r1cs_gg_ppzksnark_ipp2_commitment_key<typename CurveType::g2_type>;

                /// Commitment key used by the "pair" commitment. Note the sequence of
                /// powers starts at $n$ already.
                /// It contains $\{g^{a^{n+i}}\}_{i=1}^n$ and $\{g^{b^{n+i}}\}_{i=1}^n$
                template<typename CurveType>
                using r1cs_gg_ppzksnark_ipp2_wkey = r1cs_gg_ppzksnark_ipp2_commitment_key<typename CurveType::g1_type>;

                template<typename CurveType>
                struct r1cs_gg_ppzksnark_ipp2_commitment {
                    typedef CurveType curve_type;

                    typedef r1cs_gg_ppzksnark_ipp2_wkey<CurveType> wkey_type;
                    typedef r1cs_gg_ppzksnark_ipp2_vkey<CurveType> vkey_type;

                    typedef r1cs_gg_ppzksnark_ipp2_commitment_output<CurveType> output_type;

                    /// Commits to a tuple of G1 vector and G2 vector in the following way:
                    /// $T = \prod_{i=0}^n e(A_i, v_{1,i})e(B_i,w_{1,i})$
                    /// $U = \prod_{i=0}^n e(A_i, v_{2,i})e(B_i,w_{2,i})$
                    /// Output is $(T,U)$
                    template<typename InputG1Iterator, typename InputG2Iterator>
                    static output_type pair(const vkey_type &vkey, const wkey_type &wkey, InputG1Iterator afirst,
                                            InputG1Iterator alast, InputG2Iterator bfirst, InputG2Iterator blast) {
                        // (A * v)
                        auto t1 = algebra::pair(afirst, alast, vkey.a);
                        auto t2 = algebra::pair(wkey.a, bfirst, blast);

                        // (B * v)
                        auto u1 = algebra::pair(afirst, alast, vkey.b);
                        auto u2 = algebra::pair(wkey.b, bfirst, blast);

                        // (A * v)(w * B)
                        return {t1 *= t2, u1 *= u2};
                    }

                    /// Commits to a single vector of G1 elements in the following way:
                    /// $T = \prod_{i=0}^n e(A_i, v_{1,i})$
                    /// $U = \prod_{i=0}^n e(A_i, v_{2,i})$
                    /// Output is $(T,U)$
                    template<typename InputG1Iterator>
                    output_type pair(const vkey_type &vkey, InputG1Iterator afirst, InputG1Iterator alast) {
                        return {algebra::pair(afirst, alast, vkey.a), algebra::pair(afirst, alast, vkey.b)};
                    }
                };

                /// Commits to a tuple of G1 vector and G2 vector in the following way:
                /// $T = \prod_{i=0}^n e(A_i, v_{1,i})e(B_i,w_{1,i})$
                /// $U = \prod_{i=0}^n e(A_i, v_{2,i})e(B_i,w_{2,i})$
                /// Output is $(T,U)$
                template<typename ProofSchemeCommitmentType, typename InputG1Iterator, typename InputG2Iterator>
                typename ProofSchemeCommitmentType::output_type
                    pair(const typename ProofSchemeCommitmentType::vkey_type &vkey,
                         const typename ProofSchemeCommitmentType::wkey_type &wkey, InputG1Iterator afirst,
                         InputG1Iterator alast, InputG2Iterator bfirst, InputG2Iterator blast) {
                    // (A * v)
                    auto t1 = algebra::pair(afirst, alast, vkey.a);
                    auto t2 = algebra::pair(wkey.a, bfirst, blast);

                    // (B * v)
                    auto u1 = algebra::pair(afirst, alast, vkey.b);
                    auto u2 = algebra::pair(wkey.b, bfirst, blast);

                    // (A * v)(w * B)
                    return {t1 *= t2, u1 *= u2};
                }

                /// Commits to a single vector of G1 elements in the following way:
                /// $T = \prod_{i=0}^n e(A_i, v_{1,i})$
                /// $U = \prod_{i=0}^n e(A_i, v_{2,i})$
                /// Output is $(T,U)$
                template<typename ProofSchemeCommitmentType, typename InputG1Iterator>
                typename ProofSchemeCommitmentType::output_type
                    pair(const typename ProofSchemeCommitmentType::vkey_type &vkey, InputG1Iterator afirst,
                         InputG1Iterator alast) {
                    return {algebra::pair(afirst, alast, vkey.a), algebra::pair(afirst, alast, vkey.b)};
                }
            }    // namespace snark
        }        // namespace zk
    }            // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_R1CS_GG_PPZKSNARK_TYPES_POLICY_HPP