/*
 [auto_generated]
 boost/numeric/odeint/stepper/symplectic_euler.hpp

 [begin_description]
 Implementation of the symplectic Euler for separable Hamiltonian systems.
 [end_description]

 Copyright 2009-2011 Karsten Ahnert
 Copyright 2009-2011 Mario Mulansky

 Distributed under the Boost Software License, Version 1.0.
 (See accompanying file LICENSE_1_0.txt or
 copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_NUMERIC_ODEINT_STEPPER_SYMPLECTIC_EULER_HPP_INCLUDED
#define BOOST_NUMERIC_ODEINT_STEPPER_SYMPLECTIC_EULER_HPP_INCLUDED

#include <boost/numeric/odeint/stepper/base/symplectic_rkn_stepper_base.hpp>

#include <boost/numeric/odeint/algebra/range_algebra.hpp>
#include <boost/numeric/odeint/algebra/default_operations.hpp>

#include <boost/array.hpp>

namespace boost {
namespace numeric {
namespace odeint {

#ifndef DOXYGEN_SKIP
namespace detail {
namespace symplectic_euler_coef {

template< class Value >
struct coef_a_type : public boost::array< Value , 1 >
{
    coef_a_type( void )
    {
        (*this)[0] = static_cast< Value >( 1 );
    }
};

template< class Value >
struct coef_b_type : public boost::array< Value , 1 >
{
    coef_b_type( void )
    {
        (*this)[0] = static_cast< Value >( 1 );
    }
};

} // namespace symplectic_euler_coef
} // namespace detail
#endif

template<
class Coor ,
class Momentum = Coor ,
class Value = double ,
class CoorDeriv = Coor ,
class MomentumDeriv = Coor ,
class Time = Value ,
class Algebra = range_algebra ,
class Operations = default_operations ,
class Resizer = initially_resizer
>
#ifndef DOXYGEN_SKIP
class symplectic_euler :
public symplectic_nystroem_stepper_base
<
1 , 1 ,
Coor , Momentum , Value , CoorDeriv , MomentumDeriv , Time , Algebra , Operations , Resizer
>
#else
class symplectic_euler : public symplectic_nystroem_stepper_base
#endif
{
public:

#ifndef DOXYGEN_SKIP
    typedef symplectic_nystroem_stepper_base<
    1 , 1 , Coor , Momentum , Value , CoorDeriv , MomentumDeriv , Time , Algebra , Operations , Resizer > stepper_base_type;
#endif
    typedef typename stepper_base_type::algebra_type algebra_type;
    typedef typename stepper_base_type::value_type value_type;

    symplectic_euler( const algebra_type &algebra = algebra_type() )
    : stepper_base_type( detail::symplectic_euler_coef::coef_a_type< value_type >() ,
            detail::symplectic_euler_coef::coef_b_type< value_type >() ,
            algebra )
    { }
};

/*************** DOXYGEN ***************/

/**
 * \class symplectic_euler
 * \brief Implementation of the symplectic Euler method.
 *
 * The method is of first order and has one stage. It is described HERE.
 *
 * \tparam Order The order of the stepper.
 * \tparam Coor The type representing the coordinates q.
 * \tparam Momentum The type representing the coordinates p.
 * \tparam Value The basic value type. Should be somethink like float, double or a high-precision type.
 * \tparam CoorDeriv The type representing the time derivative of the coordinate dq/dt.
 * \tparam MomemtnumDeriv The type representing the time derivative of the momentum dp/dt.
 * \tparam Time The type representing the time t.
 * \tparam Algebra The algebra.
 * \tparam Operations The operations.
 * \tparam Resizer The resizer policy.
 */

    /**
     * \fn symplectic_euler::symplectic_euler( const algebra_type &algebra )
     * \brief Constructs the symplectic_euler. This constructor can be used as a default
     * constructor if the algebra has a default constructor.
     * \param algebra A copy of algebra is made and stored inside explicit_stepper_base.
     */

} // namespace odeint
} // namespace numeric
} // namespace boost

#endif // BOOST_NUMERIC_ODEINT_STEPPER_SYMPLECTIC_EULER_HPP_INCLUDED


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#include <iostream>
#include <cmath>
#include <boost/numeric/mtl/concept/collection.hpp>
#include <boost/numeric/mtl/vector/dense_vector.hpp>
#include <boost/numeric/mtl/vector/parameter.hpp>
#include <boost/numeric/mtl/operation/operators.hpp>
#include <boost/numeric/mtl/operation/min.hpp>
#include <boost/numeric/mtl/operation/max.hpp>

using namespace std;

template <typename Vector>
void test(Vector& v, const char* name)
{
    typedef typename mtl::Collection<Vector>::value_type value_type;
    using mtl::min; using mtl::max;

    for (unsigned i= 0; i < size(v); i++)
	v[i]= value_type(double(i+1) * pow(-1.0, int(i))); // Amb. in MSVC

    std::cout << "\n" << name << "  --- v = " << v; std::cout.flush();

    std::cout << "min(v) = " << min(v) << "\n"; std::cout.flush();
    MTL_THROW_IF(min(v) != -4.0, mtl::runtime_error("min wrong"));

    std::cout << "min<4>(v) = " << min<4>(v) << "\n"; std::cout.flush();
    MTL_THROW_IF(min<4>(v) != -4.0, mtl::runtime_error("min<4> wrong"));

    std::cout << "max(v) = " << max(v) << "\n"; std::cout.flush();
    MTL_THROW_IF(max(v) != 5.0, mtl::runtime_error("max wrong"));
}

int main(int, char**)
{
    mtl::dense_vector<float>   u(5);
    mtl::dense_vector<double>  x(5);

    std::cout << "Testing vector operations\n";

    test(u, "test float");
    test(x, "test double");

    mtl::dense_vector<float, mtl::vec::parameters<mtl::row_major> >   ur(5);
    test(ur, "test float in row vector");

    return 0;
}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// QuickBook Example

// Copyright (c) 2014, Oracle and/or its affiliates

// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

//[is_simple
//` Checks whether a geometry is simple

#include <iostream>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/geometries/multi_linestring.hpp>
/*<-*/ #include "create_svg_one.hpp" /*->*/

int main()
{
    typedef boost::geometry::model::d2::point_xy<double> point_type;
    typedef boost::geometry::model::linestring<point_type> linestring_type;
    typedef boost::geometry::model::multi_linestring<linestring_type> multi_linestring_type;

    multi_linestring_type multi_linestring;
    boost::geometry::read_wkt("MULTILINESTRING((0 0,0 10,10 10,10 0,0 0),(10 10,20 20))", multi_linestring);

    std::cout << "is simple? "
              << (boost::geometry::is_simple(multi_linestring) ? "yes" : "no")
              << std::endl;
    /*<-*/ create_svg("is_simple_example.svg", multi_linestring); /*->*/
    return 0;
}

//]

//[is_simple_output
/*`
Output:
[pre
is simple? no

[$img/algorithms/is_simple_example.png]

]

*/
//]


#pragma once
#include <Eigen/Core>
#include <Eigen/Sparse>

namespace mtao::eigen {

    template <typename T>
        constexpr bool is_sparse() {
            return std::is_base_of_v<
                Eigen::SparseMatrixBase<T>,T
                >;
        }
    template <typename T>
        constexpr bool is_matrix() {
            return std::is_base_of_v<
                Eigen::MatrixBase<T>,T
                >;
        }
    template <typename T>
        constexpr bool is_array() {
            return std::is_base_of_v<
                Eigen::ArrayBase<T>,T
                >;
        }
    template <typename T>
        constexpr bool is_row_major() {
            return T::Options & Eigen::RowMajor;
        }
    template <typename T>
        constexpr bool is_col_major() {
            return T::Options & Eigen::ColMajor;
        }
    template <typename T>
        constexpr bool is_sparse(const T& A) {
            return is_sparse<T>();
        }
    template <typename T>
        constexpr bool is_matrix(const T& A) {
            return is_matrix<T>();
        }
    template <typename T>
        constexpr bool is_array(const T& A) {
            return is_array<T>();
        }
    template <typename T>
        constexpr bool is_row_major(const T& A) {
            return is_row_major<T>();
        }
    template <typename T>
        constexpr bool is_col_major(const T& A) {
            return is_col_major<T>();
        }
}


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          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
//==============================================================================

#include <nt2/hyperbolic/include/functions/sinhcosh.hpp>
#include <nt2/exponential/constants.hpp>
#include <nt2/sdk/functor/meta/call.hpp>
#include <nt2/sdk/unit/tests/ulp.hpp>
#include <nt2/sdk/unit/tests/type_expr.hpp>
#include <nt2/sdk/unit/module.hpp>

#include <nt2/include/functions/sinh.hpp>
#include <nt2/include/functions/cosh.hpp>
#include <nt2/include/functions/tie.hpp>
#include <nt2/include/constants/zero.hpp>
#include <nt2/include/constants/one.hpp>
#include <nt2/include/constants/inf.hpp>
#include <nt2/include/constants/minf.hpp>
#include <nt2/include/constants/nan.hpp>
#include <boost/fusion/include/vector_tie.hpp>

NT2_TEST_CASE_TPL(sinhcosh, NT2_REAL_TYPES)
{
  using nt2::sinhcosh;
  using nt2::tag::sinhcosh_;
  T a[] = {nt2::Zero<T>(), nt2::One<T>(), T(5), T(-5)};
  size_t N =  sizeof(a)/sizeof(T);
  NT2_TEST_TYPE_IS( (typename boost::dispatch::meta::call<sinhcosh_(T)>::type)
                  , (std::pair<T,T>)
                  );

  {
    T s, c;
    for(size_t i=0; i < N; ++i)
    {
      sinhcosh(a[i], s, c);
      NT2_TEST_ULP_EQUAL(s, nt2::sinh(a[i]), 1);
      NT2_TEST_ULP_EQUAL(c, nt2::cosh(a[i]), 1);
    }
  }

  {
    T s, c;
    for(size_t i=0; i < N; ++i)
    {
      s = sinhcosh(a[i], c);
      NT2_TEST_ULP_EQUAL(s, nt2::sinh(a[i]), 1);
      NT2_TEST_ULP_EQUAL(c, nt2::cosh(a[i]), 1);
    }
  }

  {
    T s, c;
    for(size_t i=0; i < N; ++i)
    {
      boost::fusion::vector_tie(s, c) = sinhcosh(a[i]);
      NT2_TEST_ULP_EQUAL(s, nt2::sinh(a[i]), 1);
      NT2_TEST_ULP_EQUAL(c, nt2::cosh(a[i]), 1);
    }
  }

  {
    for(size_t i=0; i < N; ++i)
    {
      std::pair<T,T> p = sinhcosh(a[i]);
      NT2_TEST_ULP_EQUAL(p.first,  nt2::sinh(a[i]), 1);
      NT2_TEST_ULP_EQUAL(p.second, nt2::cosh(a[i]), 1);
    }
  }

  T b[] = {nt2::Inf<T>(), nt2::Minf<T>(), nt2::Nan<T>()};
  N =  sizeof(b)/sizeof(T);
#ifndef BOOST_SIMD_NO_INVALIDS

  {
    T s, c;
    for(size_t i=0; i < N; ++i)
    {
      sinhcosh(b[i], s, c);
      NT2_TEST_ULP_EQUAL(s, nt2::sinh(b[i]), 1);
      NT2_TEST_ULP_EQUAL(c, nt2::cosh(b[i]), 1);
    }
  }

  {
    T s, c;
    for(size_t i=0; i < N; ++i)
    {
      s = sinhcosh(b[i], c);
      NT2_TEST_ULP_EQUAL(s, nt2::sinh(b[i]), 1);
      NT2_TEST_ULP_EQUAL(c, nt2::cosh(b[i]), 1);
    }
  }

  {
    T s, c;
    for(size_t i=0; i < N; ++i)
    {
      boost::fusion::vector_tie(s, c) = sinhcosh(b[i]);
      NT2_TEST_ULP_EQUAL(s, nt2::sinh(b[i]), 1);
      NT2_TEST_ULP_EQUAL(c, nt2::cosh(b[i]), 1);
    }
  }

  {
    for(size_t i=0; i < N; ++i)
    {
      std::pair<T,T> p = sinhcosh(b[i]);
      NT2_TEST_ULP_EQUAL(p.first,  nt2::sinh(b[i]), 1);
      NT2_TEST_ULP_EQUAL(p.second, nt2::cosh(b[i]), 1);
    }
  }
#endif
}


//
// A demo program of reordering using Rabbit Order.
//
// Author: ARAI Junya <arai.junya@lab.ntt.co.jp> <araijn@gmail.com>
//

#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/count.hpp>

#include "../rabbit_order.hpp"
#include "edge_list.hpp"
#include <utils/yche_serialization.h>

using rabbit_order::vint;
typedef std::vector<std::vector<std::pair<vint, float> > > adjacency_list;

string g_path_dir;

vint count_unused_id(const vint n, const std::vector<edge_list::edge> &edges) {
    std::vector<char> appears(n);
    for (size_t i = 0; i < edges.size(); ++i) {
        appears[std::get<0>(edges[i])] = true;
        appears[std::get<1>(edges[i])] = true;
    }
    return static_cast<vint>(boost::count(appears, false));
}

vint count_unused_id_unweighted(const vint n, const std::vector<edge_list::aux::unweighted_edge> &edges) {
    std::vector<char> appears(n);
    for (size_t i = 0; i < edges.size(); ++i) {
        appears[std::get<0>(edges[i])] = true;
        appears[std::get<1>(edges[i])] = true;
    }
    return static_cast<vint>(boost::count(appears, false));
}

template<typename RandomAccessRange>
adjacency_list make_adj_list(const vint n, const RandomAccessRange &es) {
    using std::get;

    // Symmetrize the edge list and remove self-loops simultaneously
    std::vector<edge_list::aux::edge> ss(boost::size(es) * 2);
#pragma omp parallel for
    for (size_t i = 0; i < boost::size(es); ++i) {
        auto &e = es[i];
        if (get<0>(e) != get<1>(e)) {
            ss[i * 2] = std::make_tuple(get<0>(e), get<1>(e), 1.0f);
            ss[i * 2 + 1] = std::make_tuple(get<1>(e), get<0>(e), 1.0f);
        } else {
            // Insert zero-weight edges instead of loops; they are ignored in making
            // an adjacency list
            ss[i * 2] = std::make_tuple(0, 0, 0.0f);
            ss[i * 2 + 1] = std::make_tuple(0, 0, 0.0f);
        }
    }

    // Sort the edges
    __gnu_parallel::sort(ss.begin(), ss.end());

    // Convert to an adjacency list
    adjacency_list adj(n);
#pragma omp parallel
    {
        // Advance iterators to a boundary of a source vertex
        const auto adv = [](auto it, const auto first, const auto last) {
            while (first != it && it != last && get<0>(*(it - 1)) == get<0>(*it))
                ++it;
            return it;
        };

        // Compute an iterator range assigned to this thread
        const int p = omp_get_max_threads();
        const size_t t = static_cast<size_t>(omp_get_thread_num());
        const size_t ifirst = ss.size() / p * (t) + std::min(t, ss.size() % p);
        const size_t ilast = ss.size() / p * (t + 1) + std::min(t + 1, ss.size() % p);
        auto it = adv(ss.begin() + ifirst, ss.begin(), ss.end());
        const auto last = adv(ss.begin() + ilast, ss.begin(), ss.end());

        // Reduce edges and store them in std::vector
        while (it != last) {
            const vint s = get<0>(*it);

            // Obtain an upper bound of degree and reserve memory
            const auto maxdeg =
                    std::find_if(it, last, [s](auto &x) { return get<0>(x) != s; }) - it;
            adj[s].reserve(maxdeg);

            while (it != last && get<0>(*it) == s) {
                const vint t = get<1>(*it);
                float w = 0.0;
                while (it != last && get<0>(*it) == s && get<1>(*it) == t)
                    w += get<2>(*it++);
                if (w > 0.0)
                    adj[s].push_back({t, w});
            }

            // The actual degree can be smaller than the upper bound
            adj[s].shrink_to_fit();
        }
    }

    return adj;
}

adjacency_list read_graph(const std::string &graphpath) {
//    const auto edges = edge_list::read(graphpath);
    FILE *pFile = fopen(graphpath.c_str(), "r");
    YcheSerializer serializer;
    vector<tuple<vint, vint >> edges;
    serializer.read_array(pFile, edges);
    fclose(pFile);

    // The number of vertices = max vertex ID + 1 (assuming IDs start from zero)
    const auto n =
            boost::accumulate(edges, static_cast<vint>(0), [](vint s, auto &e) {
                return std::max(s, std::max(std::get<0>(e), std::get<1>(e)) + 1);
            });

    if (const size_t c = count_unused_id_unweighted(n, edges)) {
        std::cerr << "WARNING: " << c << "/" << n << " vertex IDs are unused"
                  << " (zero-degree vertices or noncontiguous IDs?)\n";
    }

    return make_adj_list(n, edges);
}

template<typename InputIt>
typename std::iterator_traits<InputIt>::difference_type
count_uniq(const InputIt f, const InputIt l) {
    std::vector<typename std::iterator_traits<InputIt>::value_type> ys(f, l);
    return boost::size(boost::unique(boost::sort(ys)));
}

double compute_modularity(const adjacency_list &adj, const vint *const coms) {
    const vint n = static_cast<vint>(adj.size());
    const auto ncom = count_uniq(coms, coms + n);
    double m2 = 0.0;  // total weight of the (bidirectional) edges

    std::unordered_map<vint, double[2]> degs(ncom);  // ID -> {all, loop}
    degs.reserve(ncom);

#pragma omp parallel reduction(+:m2)
    {
        std::unordered_map<vint, double[2]> mydegs(ncom);
        mydegs.reserve(ncom);

#pragma omp for
        for (vint v = 0; v < n; ++v) {
            const vint c = coms[v];
            auto *const d = &mydegs[c];
            for (const auto e : adj[v]) {
                m2 += e.second;
                (*d)[0] += e.second;
                if (coms[e.first] == c) (*d)[1] += e.second;
            }
        }

#pragma omp critical
        {
            for (auto &kv : mydegs) {
                auto *const d = &degs[kv.first];
                (*d)[0] += kv.second[0];
                (*d)[1] += kv.second[1];
            }
        }
    }
    assert(static_cast<intmax_t>(degs.size()) == ncom);

    double q = 0.0;
    for (auto &kv : degs) {
        const double all = kv.second[0];
        const double loop = kv.second[1];
        q += loop / m2 - (all / m2) * (all / m2);
    }

    return q;
}

void detect_community(adjacency_list adj) {
    auto _adj = adj;  // copy `adj` because it is used for computing modularity

    std::cerr << "Detecting communities...\n";
    const double tstart = rabbit_order::now_sec();
    //--------------------------------------------
    auto g = rabbit_order::aggregate(std::move(_adj));
    const auto c = std::make_unique<vint[]>(g.n());
#pragma omp parallel for
    for (vint v = 0; v < g.n(); ++v)
        c[v] = rabbit_order::trace_com(v, &g);
    //--------------------------------------------
    std::cerr << "Runtime for community detection [sec]: "
              << rabbit_order::now_sec() - tstart << std::endl;

    // Print the result
    std::copy(&c[0], &c[g.n()], std::ostream_iterator<vint>(std::cout, "\n"));

    std::cerr << "Computing modularity of the result...\n";
    const double q = compute_modularity(adj, c.get());
    std::cerr << "Modularity: " << q << std::endl;
}

void reorder(adjacency_list adj) {
    std::cerr << "Generating a permutation...\n";
    const double tstart = rabbit_order::now_sec();
    //--------------------------------------------
    const auto g = rabbit_order::aggregate(std::move(adj));
    const auto p = rabbit_order::compute_perm(g);
    //--------------------------------------------
    std::cerr << "Runtime for permutation generation [sec]: "
              << rabbit_order::now_sec() - tstart << std::endl;

    // Print the result
    std::copy(&p[0], &p[g.n()], std::ostream_iterator<vint>(std::cout, "\n"));

    // save the binary dictionary to file here
    // output using ycheserializer
    string my_path = g_path_dir + "/" + "rabbit_order.dict";
    cout << my_path << endl;
    FILE *pFile = fopen(my_path.c_str(), "wb");
    YcheSerializer serializer;
    serializer.write_array(pFile, p.get(), g.n());

    // flush and close the file handle
    fflush(pFile);
    fclose(pFile);
}

int main(int argc, char *argv[]) {
    using boost::adaptors::transformed;

    // Parse command-line arguments
    if (argc != 2 && (argc != 3 || std::string("-c") != argv[1])) {
        std::cerr << "Usage: reorder [-c] GRAPH_FILE\n"
                  << "  -c    Print community IDs instead of a new ordering\n";
        exit(EXIT_FAILURE);
    }
    const std::string graphpath = argc == 3 ? argv[2] : argv[1];

    string tmp = graphpath;
    auto upper_bound = tmp.find_last_of('/');
    g_path_dir = tmp.substr(0, upper_bound);

    const bool commode = argc == 3;

    std::cerr << "Number of threads: " << omp_get_max_threads() << std::endl;

    std::cerr << "Reading an edge-list file: " << graphpath << std::endl;
    auto adj = read_graph(graphpath);
    const auto m =
            boost::accumulate(adj | transformed([](auto &es) { return es.size(); }),
                              static_cast<size_t>(0));
    std::cerr << "Number of vertices: " << adj.size() << std::endl;
    std::cerr << "Number of edges: " << m << std::endl;

    if (commode)
        detect_community(std::move(adj));
    else
        reorder(std::move(adj));

    return EXIT_SUCCESS;
}


#include <stan/math/rev/mat.hpp>
#include <gtest/gtest.h>
#include <test/unit/math/rev/mat/fun/util.hpp>
#include <test/unit/math/rev/mat/util.hpp>
#ifdef STAN_OPENCL
#include <boost/random/mersenne_twister.hpp>
#endif
// multiply operates on two matrices A X B
// A (n, m)
// B (m, k)
// If stacked col-wise like vec operator
// first n*m elements are for A
// second m*k elements starting with n*m + 1-th element (indexed by
// n*m (remember !)
template <int R_A, int C_A, int C_B>
class mult_vv {
  int i, j, N, M, K;

 public:
  mult_vv(int i_, int j_, int N_, int M_, int K_)
      : i(i_), j(j_), N(N_), M(M_), K(K_) {}
  template <typename T>
  T operator()(Eigen::Matrix<T, -1, 1> x) const {
    using stan::math::multiply;
    Eigen::Matrix<T, R_A, C_A> A_c(N, M);
    Eigen::Matrix<T, C_A, C_B> B_c(M, K);
    int pos = 0;
    // traverse col-major
    for (int m = 0; m < M; ++m)
      for (int n = 0; n < N; ++n)
        A_c(n, m) = x(pos++);

    for (int k = 0; k < K; ++k)
      for (int m = 0; m < M; ++m)
        B_c(m, k) = x(pos++);

    Eigen::Matrix<T, R_A, C_B> AB_c = multiply(A_c, B_c);
    return AB_c(i, j);
  }
};

template <>
class mult_vv<1, -1, 1> {
  int N, M, K;

 public:
  mult_vv(int N_, int M_, int K_) : N(N_), M(M_), K(K_) {}
  template <typename T>
  T operator()(Eigen::Matrix<T, -1, 1> x) const {
    using stan::math::multiply;
    Eigen::Matrix<T, 1, -1> A_c(N, M);
    Eigen::Matrix<T, -1, 1> B_c(M, K);
    int pos = 0;
    // traverse col-major
    for (int m = 0; m < M; ++m)
      for (int n = 0; n < N; ++n)
        A_c(n, m) = x(pos++);

    for (int k = 0; k < K; ++k)
      for (int m = 0; m < M; ++m)
        B_c(m, k) = x(pos++);

    T AB_c = multiply(A_c, B_c);
    return AB_c;
  }
};

template <int R_A, int C_A, int C_B>
class mult_dv {
  int i, j, M, K;
  Eigen::Matrix<double, R_A, C_A> A_c;

 public:
  mult_dv(int i_, int j_, int M_, int K_, Eigen::Matrix<double, R_A, C_A> A_c_)
      : i(i_), j(j_), M(M_), K(K_), A_c(A_c_) {}
  template <typename T>
  T operator()(Eigen::Matrix<T, -1, 1> x) const {
    using stan::math::multiply;
    Eigen::Matrix<T, C_A, C_B> B_c(M, K);
    int pos = 0;
    // traverse col-major

    for (int k = 0; k < K; ++k)
      for (int m = 0; m < M; ++m)
        B_c(m, k) = x(pos++);

    Eigen::Matrix<T, R_A, C_B> AB_c = multiply(A_c, B_c);
    return AB_c(i, j);
  }
};

template <>
class mult_dv<1, -1, 1> {
  int M, K;
  Eigen::Matrix<double, 1, -1> A_c;

 public:
  mult_dv(int M_, int K_, Eigen::Matrix<double, 1, -1> A_c_)
      : M(M_), K(K_), A_c(A_c_) {}
  template <typename T>
  T operator()(Eigen::Matrix<T, -1, 1> x) const {
    using stan::math::multiply;
    Eigen::Matrix<T, -1, 1> B_c(M, K);
    int pos = 0;
    // traverse col-major

    for (int k = 0; k < K; ++k)
      for (int m = 0; m < M; ++m)
        B_c(m, k) = x(pos++);

    T AB_c = multiply(A_c, B_c);
    return AB_c;
  }
};

template <int R_A, int C_A, int C_B>
class mult_vd {
  int i, j, N, M;
  Eigen::Matrix<double, C_A, C_B> B_c;

 public:
  mult_vd(int i_, int j_, int N_, int M_, Eigen::Matrix<double, C_A, C_B> B_c_)
      : i(i_), j(j_), N(N_), M(M_), B_c(B_c_) {}
  template <typename T>
  T operator()(Eigen::Matrix<T, -1, 1> x) const {
    using stan::math::multiply;
    Eigen::Matrix<T, R_A, C_A> A_c(N, M);
    int pos = 0;
    // traverse col-major
    for (int m = 0; m < M; ++m)
      for (int n = 0; n < N; ++n)
        A_c(n, m) = x(pos++);

    Eigen::Matrix<T, -1, -1> AB_c = multiply(A_c, B_c);
    return AB_c(i, j);
  }
};

template <>
class mult_vd<1, -1, 1> {
  int N, M;
  Eigen::Matrix<double, -1, 1> B_c;

 public:
  mult_vd(int N_, int M_, Eigen::Matrix<double, -1, 1> B_c_)
      : N(N_), M(M_), B_c(B_c_) {}
  template <typename T>
  T operator()(Eigen::Matrix<T, -1, 1> x) const {
    using stan::math::multiply;
    Eigen::Matrix<T, 1, -1> A_c(N, M);
    int pos = 0;
    // traverse col-major
    for (int m = 0; m < M; ++m)
      for (int n = 0; n < N; ++n)
        A_c(n, m) = x(pos++);

    T AB_c = multiply(A_c, B_c);
    return AB_c;
  }
};

Eigen::Matrix<double, -1, 1> generate_inp(int N, int M, int K) {
  std::srand(123);
  int size_vec = N * M + M * K;
  Eigen::Matrix<double, -1, 1> vec
      = Eigen::Matrix<double, -1, 1>::Random(size_vec);
  return vec;
}

template <int R_A, int C_A, int C_B>
void pull_vals(int N, int M, int K, const Eigen::Matrix<double, -1, 1>& x,
               Eigen::Matrix<double, R_A, C_A>& A,
               Eigen::Matrix<double, C_A, C_B>& B) {
  A.resize(N, M);
  B.resize(M, K);
  int pos = 0;
  for (int m = 0; m < M; ++m)
    for (int n = 0; n < N; ++n)
      A(n, m) = x(pos++);

  for (int k = 0; k < K; ++k)
    for (int m = 0; m < M; ++m)
      B(m, k) = x(pos++);
}

TEST(AgradRevMatrix, multiply_scalar_scalar) {
  using stan::math::multiply;
  double d1, d2;
  AVAR v1, v2;

  d1 = 10;
  v1 = 10;
  d2 = -2;
  v2 = -2;

  EXPECT_FLOAT_EQ(-20.0, multiply(d1, d2));
  EXPECT_FLOAT_EQ(-20.0, multiply(d1, v2).val());
  EXPECT_FLOAT_EQ(-20.0, multiply(v1, d2).val());
  EXPECT_FLOAT_EQ(-20.0, multiply(v1, v2).val());

  EXPECT_FLOAT_EQ(6.0, multiply(AVAR(3), AVAR(2)).val());
  EXPECT_FLOAT_EQ(6.0, multiply(3.0, AVAR(2)).val());
  EXPECT_FLOAT_EQ(6.0, multiply(AVAR(3), 2.0).val());
}
TEST(AgradRevMatrix, multiply_vector_scalar) {
  using stan::math::vector_d;
  using stan::math::vector_v;

  vector_d d1(3);
  vector_v v1(3);
  double d2;
  AVAR v2;

  d1 << 100, 0, -3;
  v1 << 100, 0, -3;
  d2 = -2;
  v2 = -2;

  vector_v output;
  output = multiply(d1, v2);
  EXPECT_FLOAT_EQ(-200, output(0).val());
  EXPECT_FLOAT_EQ(0, output(1).val());
  EXPECT_FLOAT_EQ(6, output(2).val());

  output = multiply(v1, d2);
  EXPECT_FLOAT_EQ(-200, output(0).val());
  EXPECT_FLOAT_EQ(0, output(1).val());
  EXPECT_FLOAT_EQ(6, output(2).val());

  output = multiply(v1, v2);
  EXPECT_FLOAT_EQ(-200, output(0).val());
  EXPECT_FLOAT_EQ(0, output(1).val());
  EXPECT_FLOAT_EQ(6, output(2).val());
}
TEST(AgradRevMatrix, multiply_rowvector_scalar) {
  using stan::math::row_vector_d;
  using stan::math::row_vector_v;

  row_vector_d d1(3);
  row_vector_v v1(3);
  double d2;
  AVAR v2;

  d1 << 100, 0, -3;
  v1 << 100, 0, -3;
  d2 = -2;
  v2 = -2;

  row_vector_v output;
  output = multiply(d1, v2);
  EXPECT_FLOAT_EQ(-200, output(0).val());
  EXPECT_FLOAT_EQ(0, output(1).val());
  EXPECT_FLOAT_EQ(6, output(2).val());

  output = multiply(v1, d2);
  EXPECT_FLOAT_EQ(-200, output(0).val());
  EXPECT_FLOAT_EQ(0, output(1).val());
  EXPECT_FLOAT_EQ(6, output(2).val());

  output = multiply(v1, v2);
  EXPECT_FLOAT_EQ(-200, output(0).val());
  EXPECT_FLOAT_EQ(0, output(1).val());
  EXPECT_FLOAT_EQ(6, output(2).val());
}
TEST(AgradRevMatrix, multiply_matrix_scalar) {
  using stan::math::matrix_d;
  using stan::math::matrix_v;

  matrix_d d1(2, 2);
  matrix_v v1(2, 2);
  double d2;
  AVAR v2;

  d1 << 100, 0, -3, 4;
  v1 << 100, 0, -3, 4;
  d2 = -2;
  v2 = -2;

  matrix_v output;
  output = multiply(d1, v2);
  EXPECT_FLOAT_EQ(-200, output(0, 0).val());
  EXPECT_FLOAT_EQ(0, output(0, 1).val());
  EXPECT_FLOAT_EQ(6, output(1, 0).val());
  EXPECT_FLOAT_EQ(-8, output(1, 1).val());

  output = multiply(v1, d2);
  EXPECT_FLOAT_EQ(-200, output(0, 0).val());
  EXPECT_FLOAT_EQ(0, output(0, 1).val());
  EXPECT_FLOAT_EQ(6, output(1, 0).val());
  EXPECT_FLOAT_EQ(-8, output(1, 1).val());

  output = multiply(v1, v2);
  EXPECT_FLOAT_EQ(-200, output(0, 0).val());
  EXPECT_FLOAT_EQ(0, output(0, 1).val());
  EXPECT_FLOAT_EQ(6, output(1, 0).val());
  EXPECT_FLOAT_EQ(-8, output(1, 1).val());
}
TEST(AgradRevMatrix, multiply_rowvector_vector) {
  using stan::math::row_vector_d;
  using stan::math::row_vector_v;
  using stan::math::vector_d;
  using stan::math::vector_v;

  row_vector_d d1(3);
  row_vector_v v1(3);
  vector_d d2(3);
  vector_v v2(3);

  d1 << 1, 3, -5;
  v1 << 1, 3, -5;
  d2 << 4, -2, -1;
  v2 << 4, -2, -1;

  EXPECT_FLOAT_EQ(3, multiply(v1, v2).val());
  EXPECT_FLOAT_EQ(3, multiply(v1, d2).val());
  EXPECT_FLOAT_EQ(3, multiply(d1, v2).val());

  d1.resize(1);
  v1.resize(1);
  EXPECT_THROW(multiply(v1, v2), std::invalid_argument);
  EXPECT_THROW(multiply(v1, d2), std::invalid_argument);
  EXPECT_THROW(multiply(d1, v2), std::invalid_argument);
}
TEST(AgradRevMatrix, multiply_vector_rowvector) {
  using stan::math::matrix_v;
  using stan::math::row_vector_d;
  using stan::math::row_vector_v;
  using stan::math::vector_d;
  using stan::math::vector_v;

  vector_d d1(3);
  vector_v v1(3);
  row_vector_d d2(3);
  row_vector_v v2(3);

  d1 << 1, 3, -5;
  v1 << 1, 3, -5;
  d2 << 4, -2, -1;
  v2 << 4, -2, -1;

  matrix_v output = multiply(v1, v2);
  EXPECT_EQ(3, output.rows());
  EXPECT_EQ(3, output.cols());
  EXPECT_FLOAT_EQ(4, output(0, 0).val());
  EXPECT_FLOAT_EQ(-2, output(0, 1).val());
  EXPECT_FLOAT_EQ(-1, output(0, 2).val());
  EXPECT_FLOAT_EQ(12, output(1, 0).val());
  EXPECT_FLOAT_EQ(-6, output(1, 1).val());
  EXPECT_FLOAT_EQ(-3, output(1, 2).val());
  EXPECT_FLOAT_EQ(-20, output(2, 0).val());
  EXPECT_FLOAT_EQ(10, output(2, 1).val());
  EXPECT_FLOAT_EQ(5, output(2, 2).val());

  output = multiply(v1, d2);
  EXPECT_EQ(3, output.rows());
  EXPECT_EQ(3, output.cols());
  EXPECT_FLOAT_EQ(4, output(0, 0).val());
  EXPECT_FLOAT_EQ(-2, output(0, 1).val());
  EXPECT_FLOAT_EQ(-1, output(0, 2).val());
  EXPECT_FLOAT_EQ(12, output(1, 0).val());
  EXPECT_FLOAT_EQ(-6, output(1, 1).val());
  EXPECT_FLOAT_EQ(-3, output(1, 2).val());
  EXPECT_FLOAT_EQ(-20, output(2, 0).val());
  EXPECT_FLOAT_EQ(10, output(2, 1).val());
  EXPECT_FLOAT_EQ(5, output(2, 2).val());

  output = multiply(d1, v2);
  EXPECT_EQ(3, output.rows());
  EXPECT_EQ(3, output.cols());
  EXPECT_FLOAT_EQ(4, output(0, 0).val());
  EXPECT_FLOAT_EQ(-2, output(0, 1).val());
  EXPECT_FLOAT_EQ(-1, output(0, 2).val());
  EXPECT_FLOAT_EQ(12, output(1, 0).val());
  EXPECT_FLOAT_EQ(-6, output(1, 1).val());
  EXPECT_FLOAT_EQ(-3, output(1, 2).val());
  EXPECT_FLOAT_EQ(-20, output(2, 0).val());
  EXPECT_FLOAT_EQ(10, output(2, 1).val());
  EXPECT_FLOAT_EQ(5, output(2, 2).val());
}
TEST(AgradRevMatrix, multiply_matrix_vector) {
  using stan::math::matrix_d;
  using stan::math::matrix_v;
  using stan::math::vector_d;
  using stan::math::vector_v;

  matrix_d d1(3, 2);
  matrix_v v1(3, 2);
  vector_d d2(2);
  vector_v v2(2);

  d1 << 1, 3, -5, 4, -2, -1;
  v1 << 1, 3, -5, 4, -2, -1;
  d2 << -2, 4;
  v2 << -2, 4;

  vector_v output = multiply(v1, v2);
  EXPECT_EQ(3, output.size());
  EXPECT_FLOAT_EQ(10, output(0).val());
  EXPECT_FLOAT_EQ(26, output(1).val());
  EXPECT_FLOAT_EQ(0, output(2).val());

  output = multiply(v1, d2);
  EXPECT_EQ(3, output.size());
  EXPECT_FLOAT_EQ(10, output(0).val());
  EXPECT_FLOAT_EQ(26, output(1).val());
  EXPECT_FLOAT_EQ(0, output(2).val());

  output = multiply(d1, v2);
  EXPECT_EQ(3, output.size());
  EXPECT_FLOAT_EQ(10, output(0).val());
  EXPECT_FLOAT_EQ(26, output(1).val());
  EXPECT_FLOAT_EQ(0, output(2).val());
}
TEST(AgradRevMatrix, multiply_matrix_vector_exception) {
  using stan::math::matrix_d;
  using stan::math::matrix_v;
  using stan::math::vector_d;
  using stan::math::vector_v;

  matrix_d d1(3, 2);
  matrix_v v1(3, 2);
  vector_d d2(4);
  vector_v v2(4);
  EXPECT_THROW(multiply(v1, v2), std::invalid_argument);
  EXPECT_THROW(multiply(v1, d2), std::invalid_argument);
  EXPECT_THROW(multiply(d1, v2), std::invalid_argument);
}
TEST(AgradRevMatrix, multiply_rowvector_matrix) {
  using stan::math::matrix_d;
  using stan::math::matrix_v;
  using stan::math::row_vector_d;
  using stan::math::row_vector_v;
  using stan::math::vector_v;

  row_vector_d d1(3);
  row_vector_v v1(3);
  matrix_d d2(3, 2);
  matrix_v v2(3, 2);

  d1 << -2, 4, 1;
  v1 << -2, 4, 1;
  d2 << 1, 3, -5, 4, -2, -1;
  v2 << 1, 3, -5, 4, -2, -1;

  vector_v output = multiply(v1, v2);
  EXPECT_EQ(2, output.size());
  EXPECT_FLOAT_EQ(-24, output(0).val());
  EXPECT_FLOAT_EQ(9, output(1).val());

  output = multiply(v1, d2);
  EXPECT_EQ(2, output.size());
  EXPECT_FLOAT_EQ(-24, output(0).val());
  EXPECT_FLOAT_EQ(9, output(1).val());

  output = multiply(d1, v2);
  EXPECT_EQ(2, output.size());
  EXPECT_FLOAT_EQ(-24, output(0).val());
  EXPECT_FLOAT_EQ(9, output(1).val());
}
TEST(AgradRevMatrix, multiply_rowvector_matrix_exception) {
  using stan::math::matrix_d;
  using stan::math::matrix_v;
  using stan::math::row_vector_d;
  using stan::math::row_vector_v;

  row_vector_d d1(4);
  row_vector_v v1(4);
  matrix_d d2(3, 2);
  matrix_v v2(3, 2);
  EXPECT_THROW(multiply(v1, v2), std::invalid_argument);
  EXPECT_THROW(multiply(v1, d2), std::invalid_argument);
  EXPECT_THROW(multiply(d1, v2), std::invalid_argument);
}
TEST(AgradRevMatrix, multiply_matrix_matrix) {
  using stan::math::matrix_d;
  using stan::math::matrix_v;

  matrix_d d1(2, 3);
  matrix_v v1(2, 3);
  matrix_d d2(3, 2);
  matrix_v v2(3, 2);

  d1 << 9, 24, 3, 46, -9, -33;
  v1 << 9, 24, 3, 46, -9, -33;
  d2 << 1, 3, -5, 4, -2, -1;
  v2 << 1, 3, -5, 4, -2, -1;

  matrix_v output = multiply(v1, v2);
  EXPECT_EQ(2, output.rows());
  EXPECT_EQ(2, output.cols());
  EXPECT_FLOAT_EQ(-117, output(0, 0).val());
  EXPECT_FLOAT_EQ(120, output(0, 1).val());
  EXPECT_FLOAT_EQ(157, output(1, 0).val());
  EXPECT_FLOAT_EQ(135, output(1, 1).val());

  output = multiply(v1, d2);
  EXPECT_EQ(2, output.rows());
  EXPECT_EQ(2, output.cols());
  EXPECT_FLOAT_EQ(-117, output(0, 0).val());
  EXPECT_FLOAT_EQ(120, output(0, 1).val());
  EXPECT_FLOAT_EQ(157, output(1, 0).val());
  EXPECT_FLOAT_EQ(135, output(1, 1).val());

  output = multiply(d1, v2);
  EXPECT_EQ(2, output.rows());
  EXPECT_EQ(2, output.cols());
  EXPECT_FLOAT_EQ(-117, output(0, 0).val());
  EXPECT_FLOAT_EQ(120, output(0, 1).val());
  EXPECT_FLOAT_EQ(157, output(1, 0).val());
  EXPECT_FLOAT_EQ(135, output(1, 1).val());
}
TEST(AgradRevMatrix, multiply_matrix_matrix_exception) {
  using stan::math::matrix_d;
  using stan::math::matrix_v;

  matrix_d d1(2, 2);
  matrix_v v1(2, 2);
  matrix_d d2(3, 2);
  matrix_v v2(3, 2);

  EXPECT_THROW(multiply(v1, v2), std::invalid_argument);
  EXPECT_THROW(multiply(v1, d2), std::invalid_argument);
  EXPECT_THROW(multiply(d1, v2), std::invalid_argument);
}
TEST(AgradRevMatrix, multiply_scalar_vector_cv) {
  using stan::math::multiply;
  using stan::math::vector_v;

  vector_v x(3);
  x << 1, 2, 3;
  AVEC x_ind = createAVEC(x(0), x(1), x(2));
  vector_v y = multiply(2.0, x);
  EXPECT_FLOAT_EQ(2.0, y(0).val());
  EXPECT_FLOAT_EQ(4.0, y(1).val());
  EXPECT_FLOAT_EQ(6.0, y(2).val());

  VEC g = cgradvec(y(0), x_ind);
  EXPECT_FLOAT_EQ(2.0, g[0]);
  EXPECT_FLOAT_EQ(0.0, g[1]);
  EXPECT_FLOAT_EQ(0.0, g[2]);
}
TEST(AgradRevMatrix, multiply_scalar_vector_vv) {
  using stan::math::multiply;
  using stan::math::vector_v;

  vector_v x(3);
  x << 1, 4, 9;
  AVAR two = 2.0;
  AVEC x_ind = createAVEC(x(0), x(1), x(2), two);
  vector_v y = multiply(two, x);
  EXPECT_FLOAT_EQ(2.0, y(0).val());
  EXPECT_FLOAT_EQ(8.0, y(1).val());
  EXPECT_FLOAT_EQ(18.0, y(2).val());

  VEC g = cgradvec(y(1), x_ind);
  EXPECT_FLOAT_EQ(0.0, g[0]);
  EXPECT_FLOAT_EQ(2.0, g[1]);
  EXPECT_FLOAT_EQ(0.0, g[2]);
  EXPECT_FLOAT_EQ(4.0, g[3]);
}
TEST(AgradRevMatrix, multiply_scalar_vector_vc) {
  using stan::math::multiply;
  using stan::math::vector_v;

  vector_v x(3);
  x << 1, 2, 3;
  AVAR two = 2.0;
  AVEC x_ind = createAVEC(two);
  vector_v y = multiply(two, x);
  EXPECT_FLOAT_EQ(2.0, y(0).val());
  EXPECT_FLOAT_EQ(4.0, y(1).val());
  EXPECT_FLOAT_EQ(6.0, y(2).val());

  VEC g = cgradvec(y(2), x_ind);
  EXPECT_FLOAT_EQ(3.0, g[0]);
}

TEST(AgradRevMatrix, multiply_scalar_row_vector_cv) {
  using stan::math::multiply;
  using stan::math::row_vector_v;

  row_vector_v x(3);
  x << 1, 2, 3;
  AVEC x_ind = createAVEC(x(0), x(1), x(2));
  row_vector_v y = multiply(2.0, x);
  EXPECT_FLOAT_EQ(2.0, y(0).val());
  EXPECT_FLOAT_EQ(4.0, y(1).val());
  EXPECT_FLOAT_EQ(6.0, y(2).val());

  VEC g = cgradvec(y(0), x_ind);
  EXPECT_FLOAT_EQ(2.0, g[0]);
  EXPECT_FLOAT_EQ(0.0, g[1]);
  EXPECT_FLOAT_EQ(0.0, g[2]);
}
TEST(AgradRevMatrix, multiply_scalar_row_vector_vv) {
  using stan::math::multiply;
  using stan::math::row_vector_v;

  row_vector_v x(3);
  x << 1, 4, 9;
  AVAR two = 2.0;
  AVEC x_ind = createAVEC(x(0), x(1), x(2), two);
  row_vector_v y = multiply(two, x);
  EXPECT_FLOAT_EQ(2.0, y(0).val());
  EXPECT_FLOAT_EQ(8.0, y(1).val());
  EXPECT_FLOAT_EQ(18.0, y(2).val());

  VEC g = cgradvec(y(1), x_ind);
  EXPECT_FLOAT_EQ(0.0, g[0]);
  EXPECT_FLOAT_EQ(2.0, g[1]);
  EXPECT_FLOAT_EQ(0.0, g[2]);
  EXPECT_FLOAT_EQ(4.0, g[3]);
}
TEST(AgradRevMatrix, multiply_scalar_row_vector_vc) {
  using stan::math::multiply;
  using stan::math::row_vector_v;

  row_vector_v x(3);
  x << 1, 2, 3;
  AVAR two = 2.0;
  AVEC x_ind = createAVEC(two);
  row_vector_v y = multiply(two, x);
  EXPECT_FLOAT_EQ(2.0, y(0).val());
  EXPECT_FLOAT_EQ(4.0, y(1).val());
  EXPECT_FLOAT_EQ(6.0, y(2).val());

  VEC g = cgradvec(y(2), x_ind);
  EXPECT_FLOAT_EQ(3.0, g[0]);
}

TEST(AgradRevMatrix, multiply_scalar_matrix_cv) {
  using stan::math::matrix_v;
  using stan::math::multiply;

  matrix_v x(2, 3);
  x << 1, 2, 3, 4, 5, 6;
  AVEC x_ind = createAVEC(x(0, 0), x(0, 1), x(0, 2), x(1, 0));
  matrix_v y = multiply(2.0, x);
  EXPECT_FLOAT_EQ(2.0, y(0, 0).val());
  EXPECT_FLOAT_EQ(4.0, y(0, 1).val());
  EXPECT_FLOAT_EQ(6.0, y(0, 2).val());

  VEC g = cgradvec(y(0, 0), x_ind);
  EXPECT_FLOAT_EQ(2.0, g[0]);
  EXPECT_FLOAT_EQ(0.0, g[1]);
  EXPECT_FLOAT_EQ(0.0, g[2]);
  EXPECT_FLOAT_EQ(0.0, g[3]);
}

TEST(AgradRevMatrix, multiply_scalar_matrix_vc) {
  using stan::math::matrix_d;
  using stan::math::matrix_v;
  using stan::math::multiply;

  matrix_d x(2, 3);
  x << 1, 2, 3, 4, 5, 6;
  AVAR two = 2.0;
  AVEC x_ind = createAVEC(two);

  matrix_v y = multiply(two, x);
  EXPECT_FLOAT_EQ(2.0, y(0, 0).val());
  EXPECT_FLOAT_EQ(4.0, y(0, 1).val());
  EXPECT_FLOAT_EQ(6.0, y(0, 2).val());

  VEC g = cgradvec(y(1, 0), x_ind);
  EXPECT_FLOAT_EQ(4.0, g[0]);
}

TEST(AgradRevMatrix, multiply_vector_int) {
  // test namespace resolution
  using stan::math::multiply;
  using stan::math::vector_d;
  using stan::math::vector_v;

  vector_d dvec(3);
  dvec << 1, 2, 3;
  int a = 2;
  vector_d prod_vec = multiply(dvec, a);
  EXPECT_EQ(3, prod_vec.size());
  EXPECT_EQ(2.0, prod_vec[0]);
  EXPECT_EQ(4.0, prod_vec[1]);
  EXPECT_EQ(6.0, prod_vec[2]);
}

TEST(AgradRevMatrix, multiply_matrix_matrix_grad_fd) {
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 5;
  MatrixXd A;
  MatrixXd B;
  MatrixXd AB(N, K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vv<-1, -1, -1> func(n, k, N, M, K);
      VectorXd grad_ad(N * M + M * K);
      VectorXd grad_fd(N * M + M * K);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test, val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test, val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_matrix_grad_ex) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 5;
  MatrixXd A;
  MatrixXd B;
  MatrixXd AB(N, K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vv<-1, -1, -1> func(n, k, N, M, K);
      VectorXd grad_ad(N * M + M * K);
      MatrixXd grad_A;
      MatrixXd grad_B;
      MatrixXd grad_A_ex;
      MatrixXd grad_B_ex;
      grad_A_ex.resize(N, M);
      grad_A_ex.setZero();
      grad_B_ex.resize(M, K);
      grad_B_ex.setZero();
      grad_A_ex.row(n) = B.col(k);
      grad_B_ex.col(k) = A.row(n);
      double val_ad;
      stan::math::gradient(func, test, val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      pull_vals(N, M, K, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_A - grad_A_ex).lpNorm<Infinity>(), 0);
      EXPECT_FLOAT_EQ((grad_B - grad_B_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_vector_grad_fd) {
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 1;
  MatrixXd A;
  VectorXd B;
  VectorXd AB(N);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vv<-1, -1, 1> func(n, k, N, M, K);
      VectorXd grad_ad(N * M + M * K);
      VectorXd grad_fd(N * M + M * K);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test, val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test, val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(n), val_ad);
      EXPECT_FLOAT_EQ(AB(n), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_vector_grad_ex) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 1;
  MatrixXd A;
  VectorXd B;
  VectorXd AB(N);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vv<-1, -1, 1> func(n, k, N, M, K);
      VectorXd grad_ad(N * M + M * K);
      MatrixXd grad_A;
      MatrixXd grad_B;
      MatrixXd grad_A_ex;
      MatrixXd grad_B_ex;
      grad_A_ex.resize(N, M);
      grad_A_ex.setZero();
      grad_B_ex.resize(M, K);
      grad_B_ex.setZero();
      grad_A_ex.row(n) = B.col(k);
      grad_B_ex.col(k) = A.row(n);
      double val_ad;
      stan::math::gradient(func, test, val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(n), val_ad);
      pull_vals(N, M, K, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_A - grad_A_ex).lpNorm<Infinity>(), 0);
      EXPECT_FLOAT_EQ((grad_B - grad_B_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_matrix_grad_fd) {
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 5;
  RowVectorXd A;
  MatrixXd B;
  RowVectorXd AB(K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vv<1, -1, -1> func(n, k, N, M, K);
      VectorXd grad_ad(N * M + M * K);
      VectorXd grad_fd(N * M + M * K);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test, val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test, val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(k), val_ad);
      EXPECT_FLOAT_EQ(AB(k), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_matrix_grad_ex) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 5;
  RowVectorXd A;
  MatrixXd B;
  RowVectorXd AB(K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vv<1, -1, -1> func(n, k, N, M, K);
      VectorXd grad_ad(N * M + M * K);
      RowVectorXd grad_A;
      MatrixXd grad_B;
      RowVectorXd grad_A_ex;
      MatrixXd grad_B_ex;
      grad_A_ex.resize(M);
      grad_A_ex.setZero();
      grad_B_ex.resize(M, K);
      grad_B_ex.setZero();
      grad_A_ex.row(n) = B.col(k);
      grad_B_ex.col(k) = A.row(n);
      double val_ad;
      stan::math::gradient(func, test, val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(k), val_ad);
      pull_vals(N, M, K, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_A - grad_A_ex).lpNorm<Infinity>(), 0);
      EXPECT_FLOAT_EQ((grad_B - grad_B_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_vector_grad_fd) {
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 1;
  RowVectorXd A;
  VectorXd B;
  double AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vv<1, -1, 1> func(N, M, K);
      VectorXd grad_ad(N * M + M * K);
      VectorXd grad_fd(N * M + M * K);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test, val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test, val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB, val_ad);
      EXPECT_FLOAT_EQ(AB, val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_vector_grad_ex) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 1;
  RowVectorXd A;
  VectorXd B;
  double AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vv<1, -1, 1> func(N, M, K);
      VectorXd grad_ad(N * M + M * K);
      RowVectorXd grad_A;
      VectorXd grad_B;
      RowVectorXd grad_A_ex;
      VectorXd grad_B_ex;
      grad_A_ex.resize(M);
      grad_A_ex.setZero();
      grad_B_ex.resize(M);
      grad_B_ex.setZero();
      grad_A_ex = B.transpose();
      grad_B_ex = A.transpose();
      double val_ad;
      stan::math::gradient(func, test, val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB, val_ad);
      pull_vals(N, M, K, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_A - grad_A_ex).lpNorm<Infinity>(), 0);
      EXPECT_FLOAT_EQ((grad_B - grad_B_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_vector_row_vector_grad_fd) {
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 1;
  int K = 5;
  VectorXd A;
  RowVectorXd B;
  MatrixXd AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vv<-1, 1, -1> func(n, k, N, M, K);
      VectorXd grad_ad(N * M + M * K);
      VectorXd grad_fd(N * M + M * K);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test, val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test, val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_vector_row_vector_grad_ex) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 1;
  int K = 5;
  VectorXd A;
  RowVectorXd B;
  MatrixXd AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vv<-1, 1, -1> func(n, k, N, M, K);
      VectorXd grad_ad(N * M + M * K);
      VectorXd grad_A;
      VectorXd grad_A_ex;
      RowVectorXd grad_B;
      RowVectorXd grad_B_ex;
      grad_A_ex.resize(N);
      grad_A_ex.setZero();
      grad_B_ex.resize(K);
      grad_B_ex.setZero();
      grad_A_ex.row(n) = B.col(k);
      grad_B_ex.col(k) = A.row(n);
      double val_ad;
      stan::math::gradient(func, test, val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      pull_vals(N, M, K, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_A - grad_A_ex).lpNorm<Infinity>(), 0);
      EXPECT_FLOAT_EQ((grad_B - grad_B_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_matrix_grad_fd_dv) {
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 5;
  MatrixXd A;
  MatrixXd B;
  MatrixXd AB(N, K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_dv<-1, -1, -1> func(n, k, M, K, A);
      VectorXd grad_ad(M * K);
      VectorXd grad_fd(M * K);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test.tail(M * K), val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test.tail(M * K), val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_matrix_grad_ex_dv) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 5;
  MatrixXd A;
  MatrixXd B;
  MatrixXd AB(N, K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_dv<-1, -1, -1> func(n, k, M, K, A);
      VectorXd grad_ad(M * K);
      MatrixXd grad_B;
      MatrixXd grad_A;
      MatrixXd grad_B_ex;
      grad_B_ex.resize(M, K);
      grad_B_ex.setZero();
      grad_B_ex.col(k) = A.row(n);
      double val_ad;
      stan::math::gradient(func, test.tail(M * K), val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      pull_vals(0, M, K, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_B - grad_B_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_vector_grad_fd_dv) {
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 1;
  MatrixXd A;
  VectorXd B;
  VectorXd AB(N);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_dv<-1, -1, 1> func(n, k, M, K, A);
      VectorXd grad_ad(M * K);
      VectorXd grad_fd(M * K);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test.tail(M * K), val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test.tail(M * K), val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(n), val_ad);
      EXPECT_FLOAT_EQ(AB(n), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_vector_grad_ex_dv) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 1;
  MatrixXd A;
  VectorXd B;
  VectorXd AB(N);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_dv<-1, -1, 1> func(n, k, M, K, A);
      VectorXd grad_ad(M * K);
      VectorXd grad_B;
      MatrixXd grad_A;
      VectorXd grad_B_ex;
      grad_B_ex.resize(M);
      grad_B_ex.setZero();
      grad_B_ex = A.row(n);
      double val_ad;
      stan::math::gradient(func, test.tail(M * K), val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(n), val_ad);
      pull_vals(0, M, K, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_B - grad_B_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_matrix_grad_fd_dv) {
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 5;
  RowVectorXd A;
  MatrixXd B;
  RowVectorXd AB(K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_dv<1, -1, -1> func(n, k, M, K, A);
      VectorXd grad_ad(M * K);
      VectorXd grad_fd(M * K);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test.tail(M * K), val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test.tail(M * K), val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(k), val_ad);
      EXPECT_FLOAT_EQ(AB(k), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_matrix_grad_ex_dv) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 5;
  RowVectorXd A;
  MatrixXd B;
  RowVectorXd AB(N, K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_dv<1, -1, -1> func(n, k, M, K, A);
      VectorXd grad_ad(M * K);
      MatrixXd grad_B;
      MatrixXd grad_A;
      MatrixXd grad_B_ex;
      grad_B_ex.resize(M, K);
      grad_B_ex.setZero();
      grad_B_ex.col(k) = A.row(n);
      double val_ad;
      stan::math::gradient(func, test.tail(M * K), val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(k), val_ad);
      pull_vals(0, M, K, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_B - grad_B_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_vector_grad_fd_dv) {
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 1;
  RowVectorXd A;
  VectorXd B;
  double AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_dv<1, -1, 1> func(M, K, A);
      VectorXd grad_ad(M * K);
      VectorXd grad_fd(M * K);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test.tail(M * K), val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test.tail(M * K), val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB, val_ad);
      EXPECT_FLOAT_EQ(AB, val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_vector_grad_ex_dv) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 1;
  RowVectorXd A;
  VectorXd B;
  double AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_dv<1, -1, 1> func(M, K, A);
      VectorXd grad_ad(M * K);
      VectorXd grad_B;
      MatrixXd grad_A;
      VectorXd grad_B_ex;
      grad_B_ex.resize(M, K);
      grad_B_ex.setZero();
      grad_B_ex = A;
      double val_ad;
      stan::math::gradient(func, test.tail(M * K), val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB, val_ad);
      pull_vals(0, M, K, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_B - grad_B_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_vector_row_vector_grad_fd_dv) {
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 1;
  int K = 5;
  VectorXd A;
  RowVectorXd B;
  MatrixXd AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_dv<-1, 1, -1> func(n, k, M, K, A);
      VectorXd grad_ad(M * K);
      VectorXd grad_fd(M * K);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test.tail(M * K), val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test.tail(M * K), val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_vector_row_vector_grad_ex_dv) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 1;
  int K = 5;
  VectorXd A;
  RowVectorXd B;
  MatrixXd AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_dv<-1, 1, -1> func(n, k, M, K, A);
      VectorXd grad_ad(M * K);
      VectorXd grad_A;
      RowVectorXd grad_B;
      RowVectorXd grad_B_ex;
      grad_B_ex.resize(K);
      grad_B_ex.setZero();
      grad_B_ex.col(k) = A.row(n);
      double val_ad;
      stan::math::gradient(func, test.tail(M * K), val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      pull_vals(0, M, K, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_B - grad_B_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_matrix_grad_fd_vd) {
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 5;
  MatrixXd A;
  MatrixXd B;
  MatrixXd AB(N, K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vd<-1, -1, -1> func(n, k, N, M, B);
      VectorXd grad_ad(N * M);
      VectorXd grad_fd(N * M);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test.head(N * M), val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test.head(N * M), val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_matrix_grad_ex_vd) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 5;
  MatrixXd A;
  MatrixXd B;
  MatrixXd AB(N, K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vd<-1, -1, -1> func(n, k, N, M, B);
      VectorXd grad_ad(N * M);
      MatrixXd grad_B;
      MatrixXd grad_A;
      MatrixXd grad_A_ex;
      grad_A_ex.resize(N, M);
      grad_A_ex.setZero();
      grad_A_ex.row(n) = B.col(k);
      double val_ad;
      stan::math::gradient(func, test.head(N * M), val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      pull_vals(N, M, 0, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_A - grad_A_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_vector_grad_fd_vd) {
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 1;
  MatrixXd A;
  VectorXd B;
  VectorXd AB(N);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vd<-1, -1, 1> func(n, k, N, M, B);
      VectorXd grad_ad(N * M);
      VectorXd grad_fd(N * M);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test.head(M * N), val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test.head(N * M), val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(n), val_ad);
      EXPECT_FLOAT_EQ(AB(n), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_matrix_vector_grad_ex_vd) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 4;
  int K = 1;
  MatrixXd A;
  MatrixXd B;
  VectorXd AB(N);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vd<-1, -1, 1> func(n, k, N, M, B);
      VectorXd grad_ad(N * M);
      MatrixXd grad_B;
      MatrixXd grad_A;
      MatrixXd grad_A_ex;
      grad_A_ex.resize(N, M);
      grad_A_ex.setZero();
      grad_A_ex.row(n) = B.col(k);
      double val_ad;
      stan::math::gradient(func, test.head(N * M), val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(n), val_ad);
      pull_vals(N, M, 0, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_A - grad_A_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_matrix_grad_fd_vd) {
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 5;
  RowVectorXd A;
  MatrixXd B;
  RowVectorXd AB(K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vd<1, -1, -1> func(n, k, N, M, B);
      VectorXd grad_ad(N * M);
      VectorXd grad_fd(N * M);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test.head(N * M), val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test.head(N * M), val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(k), val_ad);
      EXPECT_FLOAT_EQ(AB(k), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_matrix_grad_ex_vd) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 5;
  RowVectorXd A;
  MatrixXd B;
  RowVectorXd AB(K);
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vd<1, -1, -1> func(n, k, N, M, B);
      VectorXd grad_ad(N * M);
      MatrixXd grad_B;
      MatrixXd grad_A;
      RowVectorXd grad_A_ex;
      grad_A_ex.resize(M);
      grad_A_ex.setZero();
      grad_A_ex = B.col(k);
      double val_ad;
      stan::math::gradient(func, test.head(N * M), val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(k), val_ad);
      pull_vals(N, M, 0, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_A - grad_A_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_vector_grad_fd_vd) {
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 1;
  RowVectorXd A;
  VectorXd B;
  double AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vd<1, -1, 1> func(N, M, B);
      VectorXd grad_ad(N * M);
      VectorXd grad_fd(N * M);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test.head(N * M), val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test.head(N * M), val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB, val_ad);
      EXPECT_FLOAT_EQ(AB, val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_row_vector_vector_grad_ex_vd) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 1;
  int M = 4;
  int K = 1;
  RowVectorXd A;
  VectorXd B;
  double AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vd<1, -1, 1> func(N, M, B);
      VectorXd grad_ad(N * M);
      MatrixXd grad_B;
      RowVectorXd grad_A;
      RowVectorXd grad_A_ex;
      grad_A_ex.resize(M);
      grad_A_ex.setZero();
      grad_A_ex = B;
      double val_ad;
      stan::math::gradient(func, test.head(N * M), val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB, val_ad);
      pull_vals(N, M, 0, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_A - grad_A_ex).lpNorm<Infinity>(), 0);
    }
  }
}

TEST(AgradRevMatrix, multiply_vector_row_vector_grad_fd_vd) {
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 1;
  int K = 5;
  VectorXd A;
  RowVectorXd B;
  MatrixXd AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vd<-1, 1, -1> func(n, k, N, M, B);
      VectorXd grad_ad(N * M);
      VectorXd grad_fd(N * M);
      double val_ad;
      double val_fd;
      stan::math::gradient(func, test.head(M * N), val_ad, grad_ad);
      stan::math::finite_diff_gradient(func, test.head(N * M), val_fd, grad_fd);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_fd);
      for (int i = 0; i < grad_ad.size(); ++i)
        EXPECT_NEAR(grad_ad(i), grad_fd(i), 1e-10);
    }
  }
}

TEST(AgradRevMatrix, multiply_vector_row_vector_grad_ex_vd) {
  using Eigen::Infinity;
  using Eigen::MatrixXd;
  using Eigen::RowVectorXd;
  using Eigen::VectorXd;

  int N = 3;
  int M = 1;
  int K = 5;
  VectorXd A;
  RowVectorXd B;
  MatrixXd AB;
  VectorXd test = generate_inp(N, M, K);
  pull_vals(N, M, K, test, A, B);
  AB = A * B;
  for (int n = 0; n < N; ++n) {
    for (int k = 0; k < K; ++k) {
      mult_vd<-1, 1, -1> func(n, k, N, M, B);
      VectorXd grad_ad(N * M);
      RowVectorXd grad_B;
      VectorXd grad_A;
      VectorXd grad_A_ex;
      grad_A_ex.resize(N);
      grad_A_ex.setZero();
      grad_A_ex.row(n) = B.col(k);
      double val_ad;
      stan::math::gradient(func, test.head(N * M), val_ad, grad_ad);
      EXPECT_FLOAT_EQ(AB(n, k), val_ad);
      pull_vals(N, M, 0, grad_ad, grad_A, grad_B);
      EXPECT_FLOAT_EQ((grad_A - grad_A_ex).lpNorm<Infinity>(), 0);
    }
  }
}
TEST(AgradRevMatrix, check_varis_on_stack) {
  using stan::math::value_of;
  stan::math::matrix_v m(3, 3);
  m << 1, 2, 3, 4, 5, 6, 7, 8, 9;
  stan::math::vector_v v(3);
  v << 10, 20, 30;
  stan::math::row_vector_v rv(3);
  rv << 100, 200, 300;
  stan::math::var s = 1;

  test::check_varis_on_stack(stan::math::multiply(m, m));
  test::check_varis_on_stack(stan::math::multiply(m, value_of(m)));
  test::check_varis_on_stack(stan::math::multiply(value_of(m), m));

  test::check_varis_on_stack(stan::math::multiply(m, v));
  test::check_varis_on_stack(stan::math::multiply(m, value_of(v)));
  test::check_varis_on_stack(stan::math::multiply(value_of(m), v));

  test::check_varis_on_stack(stan::math::multiply(rv, m));
  test::check_varis_on_stack(stan::math::multiply(rv, value_of(m)));
  test::check_varis_on_stack(stan::math::multiply(value_of(rv), m));

  test::check_varis_on_stack(stan::math::multiply(rv, v));
  test::check_varis_on_stack(stan::math::multiply(rv, value_of(v)));
  test::check_varis_on_stack(stan::math::multiply(value_of(rv), v));

  test::check_varis_on_stack(stan::math::multiply(s, m));
  test::check_varis_on_stack(stan::math::multiply(s, value_of(m)));
  test::check_varis_on_stack(stan::math::multiply(value_of(s), m));

  test::check_varis_on_stack(stan::math::multiply(s, rv));
  test::check_varis_on_stack(stan::math::multiply(s, value_of(rv)));
  test::check_varis_on_stack(stan::math::multiply(value_of(s), rv));

  test::check_varis_on_stack(stan::math::multiply(s, v));
  test::check_varis_on_stack(stan::math::multiply(s, value_of(v)));
  test::check_varis_on_stack(stan::math::multiply(value_of(s), v));

  test::check_varis_on_stack(stan::math::multiply(m, s));
  test::check_varis_on_stack(stan::math::multiply(m, value_of(s)));
  test::check_varis_on_stack(stan::math::multiply(value_of(m), s));

  test::check_varis_on_stack(stan::math::multiply(rv, s));
  test::check_varis_on_stack(stan::math::multiply(rv, value_of(s)));
  test::check_varis_on_stack(stan::math::multiply(value_of(rv), s));

  test::check_varis_on_stack(stan::math::multiply(v, s));
  test::check_varis_on_stack(stan::math::multiply(v, value_of(s)));
  test::check_varis_on_stack(stan::math::multiply(value_of(v), s));

  test::check_varis_on_stack(stan::math::multiply(s, s));
  test::check_varis_on_stack(stan::math::multiply(s, value_of(s)));
  test::check_varis_on_stack(stan::math::multiply(value_of(s), s));
}

#ifdef STAN_OPENCL
#define EXPECT_MATRIX_NEAR(A, B, DELTA) \
  for (int i = 0; i < A.size(); i++)    \
    EXPECT_NEAR(stan::math::value_of(A(i)), stan::math::value_of(B(i)), DELTA);

boost::random::mt19937 rng;
#define MULTIPLY_OPENCL_OVERRIDE 0
#define MULTIPLY_CPU_OVERRIDE INT_MAX
TEST(AgradRevMatrix, multiply_val_vv_cl) {
  int temp = stan::math::opencl_context.tuning_opts()
                 .multiply_dim_prod_worth_transfer;
  using stan::math::matrix_d;
  using stan::math::matrix_v;
  using stan::math::multiply;
  int size = 234;
  matrix_v Av(size, size);
  matrix_v Bv(size, size);
  matrix_v C, C_cl;
  for (int i = 0; i < size; i++) {
    for (int j = 0; j < size; j++) {
      Av(i, j) = stan::math::uniform_rng(-5, 5, rng);
      Bv(i, j) = stan::math::uniform_rng(-5, 5, rng);
    }
  }
  stan::math::opencl_context.tuning_opts().multiply_dim_prod_worth_transfer
      = MULTIPLY_OPENCL_OVERRIDE;
  C_cl = multiply(Av, Bv);
  C_cl(0, 0).grad();
  stan::math::opencl_context.tuning_opts().multiply_dim_prod_worth_transfer
      = MULTIPLY_CPU_OVERRIDE;
  C = multiply(Av, Bv);
  C(0, 0).grad();
  EXPECT_MATRIX_NEAR(C, C_cl, 1.0E-12);
  EXPECT_MATRIX_NEAR(C.adj(), C_cl.adj(), 1.0E-12);
  stan::math::recover_memory();
  stan::math::opencl_context.tuning_opts().multiply_dim_prod_worth_transfer
      = temp;
}

TEST(AgradRevMatrix, multiply_val_vd_cl) {
  int temp = stan::math::opencl_context.tuning_opts()
                 .multiply_dim_prod_worth_transfer;
  using stan::math::matrix_d;
  using stan::math::matrix_v;
  using stan::math::multiply;
  int size = 256;
  matrix_v Av(size, size);
  matrix_v Bd(size, size);
  matrix_v C, C_cl;
  for (int i = 0; i < size; i++) {
    for (int j = 0; j < size; j++) {
      Av(i, j) = stan::math::uniform_rng(-5, 5, rng);
      Bd(i, j) = stan::math::uniform_rng(-5, 5, rng);
    }
  }
  stan::math::opencl_context.tuning_opts().multiply_dim_prod_worth_transfer
      = MULTIPLY_OPENCL_OVERRIDE;
  C_cl = multiply(Av, Bd);
  C_cl(0, 0).grad();
  stan::math::opencl_context.tuning_opts().multiply_dim_prod_worth_transfer
      = MULTIPLY_CPU_OVERRIDE;
  C = multiply(Av, Bd);
  C(0, 0).grad();
  EXPECT_MATRIX_NEAR(C, C_cl, 1.0E-12);
  EXPECT_MATRIX_NEAR(C.adj(), C_cl.adj(), 1.0E-12);
  stan::math::recover_memory();
  stan::math::opencl_context.tuning_opts().multiply_dim_prod_worth_transfer
      = temp;
}

TEST(AgradRevMatrix, multiply_val_dv_cl) {
  int temp = stan::math::opencl_context.tuning_opts()
                 .multiply_dim_prod_worth_transfer;
  using stan::math::matrix_d;
  using stan::math::matrix_v;
  using stan::math::multiply;
  int size = 321;
  matrix_v Ad(size, size);
  matrix_v Bv(size, size);
  matrix_v C, C_cl;
  for (int i = 0; i < size; i++) {
    for (int j = 0; j < size; j++) {
      Ad(i, j) = stan::math::uniform_rng(-5, 5, rng);
      Bv(i, j) = stan::math::uniform_rng(-5, 5, rng);
    }
  }
  stan::math::opencl_context.tuning_opts().multiply_dim_prod_worth_transfer
      = MULTIPLY_OPENCL_OVERRIDE;
  C_cl = multiply(Ad, Bv);
  C_cl(0, 0).grad();
  stan::math::opencl_context.tuning_opts().multiply_dim_prod_worth_transfer
      = MULTIPLY_CPU_OVERRIDE;
  C = multiply(Ad, Bv);
  C(0, 0).grad();
  EXPECT_MATRIX_NEAR(C, C_cl, 1.0E-12);
  EXPECT_MATRIX_NEAR(C.adj(), C_cl.adj(), 1.0E-12);
  stan::math::recover_memory();
  stan::math::opencl_context.tuning_opts().multiply_dim_prod_worth_transfer
      = temp;
}

#endif


// smooth: Lie Theory for Robotics
// https://github.com/pettni/smooth
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
//
// Copyright (c) 2021 Petter Nilsson
//
// 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.

#ifndef SMOOTH__C1_HPP_
#define SMOOTH__C1_HPP_

#include <Eigen/Core>

#include <complex>

#include "internal/c1.hpp"
#include "internal/lie_group_base.hpp"
#include "internal/macro.hpp"
#include "lie_group.hpp"
#include "map.hpp"
#include "so2.hpp"

namespace smooth {

// \cond
template<typename Scalar>
class SO2;
// \endcond

/**
 * @brief Base class for C1 Lie group types.
 *
 * Memory layout
 * -------------
 *
 * - Group:    \f$ \mathbf{x} = [a, b] \f$
 * - Tangent:  \f$ \mathbf{a} = [s, \omega_z] \f$
 *
 * Constraints
 * -----------
 *
 * - Group:   \f$ a^2 + b^2 > 0 \f$
 * - Tangent: \f$ -\pi < \omega_z \leq \pi \f$
 *
 * Lie group matrix form
 * ---------------------
 *
 * \f[
 * \mathbf{X} =
 * \begin{bmatrix}
 *  b & -a \\
 *  a &  b
 * \end{bmatrix} \in \mathbb{R}^{2 \times 2}
 * \f]
 *
 *
 * Lie algebra matrix form
 * -----------------------
 *
 * \f[
 * \mathbf{a}^\wedge =
 * \begin{bmatrix}
 *   s & -\omega_z \\
 *  \omega_z &   s \\
 * \end{bmatrix} \in \mathbb{R}^{2 \times 2}
 * \f]
 */
template<typename _Derived>
class C1Base : public LieGroupBase<_Derived>
{
  using Base = LieGroupBase<_Derived>;

protected:
  C1Base() = default;

public:
  SMOOTH_INHERIT_TYPEDEFS;

  /**
   * @brief Rotation angle.
   */
  Scalar angle() const
  {
    using std::atan2;

    return atan2(
      static_cast<const _Derived &>(*this).coeffs().x(),
      static_cast<const _Derived &>(*this).coeffs().y());
  }

  /**
   * @brief Scaling.
   */
  Scalar scaling() const
  {
    using std::sqrt;

    return sqrt(
      static_cast<const _Derived &>(*this).coeffs().x()
        * static_cast<const _Derived &>(*this).coeffs().x()
      + static_cast<const _Derived &>(*this).coeffs().y()
          * static_cast<const _Derived &>(*this).coeffs().y());
  }

  /**
   * @brief Rotation.
   */
  SO2<Scalar> so2() const
  {
    return SO2<Scalar>(c1());  // it's normalized inside SO2
  }

  /**
   * @brief Complex number representation.
   */
  std::complex<Scalar> c1() const
  {
    return std::complex<Scalar>(
      static_cast<const _Derived &>(*this).coeffs().y(),
      static_cast<const _Derived &>(*this).coeffs().x());
  }

  /**
   * @brief Rotation and scaling action on 2D vector.
   */
  template<typename EigenDerived>
  Eigen::Matrix<Scalar, 2, 1> operator*(const Eigen::MatrixBase<EigenDerived> & v) const
  {
    return Base::matrix() * v;
  }
};

// \cond
template<typename _Scalar>
class C1;
// \endcond

// \cond
template<typename _Scalar>
struct liebase_info<C1<_Scalar>>
{
  static constexpr bool is_mutable = true;

  using Impl   = C1Impl<_Scalar>;
  using Scalar = _Scalar;

  template<typename NewScalar>
  using PlainObject = C1<NewScalar>;
};
// \endcond

/**
 * @brief Storage implementation of C1 Lie group.
 *
 * @see C1Base for memory layout.
 */
template<typename _Scalar>
class C1 : public C1Base<C1<_Scalar>>
{
  using Base = C1Base<C1<_Scalar>>;
  SMOOTH_GROUP_API(C1);

public:
  /**
   * @brief Construct from scaling and angle.
   *
   * @param scaling strictly greater than zero.
   * @param angle angle of rotation (radians).
   */
  C1(const Scalar & scaling, const Scalar & angle)
  {
    using std::cos, std::sin;

    coeffs_.x() = scaling * sin(angle);
    coeffs_.y() = scaling * cos(angle);
  }

  /**
   * @brief Construct from complex number.
   *
   * @param c complex number.
   */
  C1(const std::complex<Scalar> & c)
  {
    coeffs_.x() = c.imag();
    coeffs_.y() = c.real();
  }
};

// \cond
template<typename _Scalar>
struct liebase_info<Map<C1<_Scalar>>> : public liebase_info<C1<_Scalar>>
{};
// \endcond

/**
 * @brief Memory mapping of C1 Lie group.
 *
 * @see C1Base for memory layout.
 */
template<typename _Scalar>
class Map<C1<_Scalar>> : public C1Base<Map<C1<_Scalar>>>
{
  using Base = C1Base<Map<C1<_Scalar>>>;

  SMOOTH_MAP_API();
};

// \cond
template<typename _Scalar>
struct liebase_info<Map<const C1<_Scalar>>> : public liebase_info<C1<_Scalar>>
{
  static constexpr bool is_mutable = false;
};
// \endcond

/**
 * @brief Const memory mapping of C1 Lie group.
 *
 * @see C1Base for memory layout.
 */
template<typename _Scalar>
class Map<const C1<_Scalar>> : public C1Base<Map<const C1<_Scalar>>>
{
  using Base = C1Base<Map<const C1<_Scalar>>>;

  SMOOTH_CONST_MAP_API();
};

using C1f = C1<float>;   ///< C1 with float scalar representation
using C1d = C1<double>;  ///< C1 with double scalar representation

}  // namespace smooth

#endif  // SMOOTH__C1_HPP_


#define BOOST_TEST_MODULE snark_logic_test
#include <boost/test/included/unit_test.hpp>

#include "../bin/comparerLogic.h"

using namespace nil::crypto3;
using namespace nil::crypto3::zk;

template<typename FieldType>
bool test_bp_input(int minYear, int maxYear, int year) {
    blueprint<FieldType> bp;
    ComparerLogic<FieldType> comparerLogic(bp);
    comparerLogic.generate_r1cs_constraints(bp);
    comparerLogic.generate_r1cs_witness(bp, minYear, maxYear, year);
    return bp.is_satisfied();
}

void test_comparerLogic_range(bool isSatisfied, int minYear, int maxYear, int year) {
    std::cout << "Testing " << year <<" in range (" << minYear << ", " << maxYear << ")" << std::endl;
    BOOST_CHECK(test_bp_input<field_type>(minYear, maxYear, year) == isSatisfied);
}

BOOST_AUTO_TEST_SUITE(comparerLogic_test_suite)

int minYear = 2,
    maxYear = 100,
    yearInside = 5,
    yearOutsideLess = 1,
    yearOutsideMore = 101;

BOOST_AUTO_TEST_CASE(comparerLogic_test_valid_ranges) {
    test_comparerLogic_range(true, minYear, maxYear, yearInside);
}

BOOST_AUTO_TEST_CASE(comparerLogic_test_invalid_ranges) {
    test_comparerLogic_range(false, minYear, maxYear, yearOutsideLess);
    test_comparerLogic_range(false, minYear, maxYear, yearOutsideMore);
}

BOOST_AUTO_TEST_SUITE_END()


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2009 Klaus Spanderen

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

/*! \file ranluxuniformrng.hpp
    \brief "Luxury" random number generator.
*/

#ifndef quantlib_ranlux_uniform_rng_h
#define quantlib_ranlux_uniform_rng_h

#include <ql/methods/montecarlo/sample.hpp>
#include <boost/random/ranlux.hpp>

namespace QuantLib {

    //! Uniform random number generator
    /*! M. Luescher's "luxury" random number generator

        Implementation is a proxy for the corresponding boost random
        number generator. For more detail see the boost documentation and:
          M.Luescher, A portable high-quality random number generator for
          lattice field theory simulations, Comp. Phys. Comm. 79 (1994) 100

        Available luxury levels:
        Ranlux3: Any theoretically possible correlations have very small change
                 of being observed.
        Ranlux4: highest possible luxury.
    */
    class Ranlux3UniformRng {
      public:
        typedef Sample<Real> sample_type;

        explicit Ranlux3UniformRng(Size seed = 19780503U)
        : ranlux3_(boost::random::ranlux64_base_01(seed)) {}

        sample_type next() const { return {ranlux3_(), 1.0}; }

      private:
        mutable boost::ranlux64_3_01 ranlux3_;
    };

    class Ranlux4UniformRng {
      public:
        typedef Sample<Real> sample_type;

        explicit Ranlux4UniformRng(Size seed = 19780503U)
        : ranlux4_(boost::random::ranlux64_base_01(seed)) {}

        sample_type next() const { return {ranlux4_(), 1.0}; }

      private:
        mutable boost::ranlux64_4_01 ranlux4_;
    };
}

#endif