/*
@copyright Louis Dionne 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_TEST_LAWS_FOLDABLE_HPP
#define BOOST_HANA_TEST_LAWS_FOLDABLE_HPP

#include <boost/hana/assert.hpp>
#include <boost/hana/comparable.hpp>
#include <boost/hana/config.hpp>
#include <boost/hana/core/models.hpp>
#include <boost/hana/core/operators.hpp>
#include <boost/hana/core/when.hpp>
#include <boost/hana/foldable.hpp>
#include <boost/hana/functional/demux.hpp>
#include <boost/hana/integral_constant.hpp>
#include <boost/hana/tuple.hpp>

#include <laws/base.hpp>
#include <test/identity.hpp>

#include <vector>

namespace boost { namespace hana { namespace test {
    template <typename F, typename = when<true>>
    struct TestFoldable : TestFoldable<F, laws> {
        using TestFoldable<F, laws>::TestFoldable;
    };

    template <typename F>
    struct TestFoldable<F, laws> {
        static_assert(_models<Foldable, F>{}, "");

        template <typename Foldables>
        TestFoldable(Foldables foldables) {
            _injection<0> f{};
            _constant<0> s{};

            hana::for_each(foldables, [=](auto xs) {
                BOOST_HANA_CHECK(hana::equal(
                    hana::foldl(xs, s, f),
                    hana::foldl(hana::to<Tuple>(xs), s, f)
                ));

                BOOST_HANA_CHECK(hana::equal(
                    hana::foldr(xs, s, f),
                    hana::foldr(hana::to<Tuple>(xs), s, f)
                ));

                // equivalence of size(xs) and length(xs)
                BOOST_HANA_CHECK(hana::equal(
                    hana::length(xs),
                    hana::size(xs)
                ));

                // equivalence of count(xs, val) and count_if(xs, equal.to(val))
                struct not_there { };
                BOOST_HANA_CHECK(hana::equal(
                    hana::count(xs, not_there{}),
                    hana::count_if(xs, equal.to(not_there{}))
                ));

                hana::for_each(xs, [=](auto value) {
                    BOOST_HANA_CHECK(hana::equal(
                        hana::count(xs, value),
                        hana::count_if(xs, equal.to(value))
                    ));
                });
            });
        }
    };

    template <typename P>
    struct TestFoldable<P, when<_models<Product, P>{}>> : TestFoldable<P, laws> {
        template <typename Products>
        TestFoldable(Products products) : TestFoldable<P, laws>{products} {
            hana::for_each(products, [](auto p) {
                _injection<0> f{};

                BOOST_HANA_CHECK(hana::equal(
                    hana::unpack(p, f),
                    f(hana::first(p), hana::second(p))
                ));
            });
        }
    };

    template <typename S>
    struct TestFoldable<S, when<_models<Sequence, S>{}>> : TestFoldable<S, laws> {
        template <int i>
        using x = _constant<i>;

        template <int i>
        using ord = _constant<i>;

        struct undefined { };

        template <typename Xs>
        TestFoldable(Xs xs) : TestFoldable<S, laws>{xs} {
            _injection<0> f{};
            auto z = x<999>{};
            constexpr auto list = make<S>;

            //////////////////////////////////////////////////////////////////
            // foldl
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldl(list(), z, undefined{}),
                z
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                foldl(list(x<1>{}), z, f),
                f(z, x<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldl(list(x<1>{}, x<2>{}), z, f),
                f(f(z, x<1>{}), x<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldl(list(x<1>{}, x<2>{}, x<3>{}), z, f),
                f(f(f(z, x<1>{}), x<2>{}), x<3>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldl(list(x<1>{}, x<2>{}, x<3>{}, x<4>{}), z, f),
                f(f(f(f(z, x<1>{}), x<2>{}), x<3>{}), x<4>{})
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                foldl(list(1), z, f),
                f(z, 1)
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                foldl(list(1, '2'), z, f),
                f(f(z, 1), '2')
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                foldl(list(1, '2', 3.3), z, f),
                f(f(f(z, 1), '2'), 3.3)
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                foldl(list(1, '2', 3.3, 4.4f), z, f),
                f(f(f(f(z, 1), '2'), 3.3), 4.4f)
            ));

            //////////////////////////////////////////////////////////////////
            // foldlM
            //////////////////////////////////////////////////////////////////
            {
                using M = test::Identity;
                auto f = hana::demux(test::identity)(test::_injection<0>{});
                auto a1 = x<999>{};

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldlM<M>(list(), a1, undefined{}),
                    lift<M>(a1)
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldlM<M>(list(x<1>{}), a1, f),
                    f(a1, x<1>{})
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldlM<M>(list(x<1>{}, x<2>{}), a1, f),
                    bind(f(a1, x<1>{}), [=](auto a2) {
                        return f(a2, x<2>{});
                    })
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldlM<M>(list(x<1>{}, x<2>{}, x<3>{}), a1, f),
                    bind(f(a1, x<1>{}), [=](auto a2) {
                        return bind(f(a2, x<2>{}), [=](auto a3) {
                            return f(a3, x<3>{});
                        });
                    })
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldlM<M>(list(x<1>{}, x<2>{}, x<3>{}, x<4>{}), a1, f),
                    bind(f(a1, x<1>{}), [=](auto a2) {
                        return bind(f(a2, x<2>{}), [=](auto a3) {
                            return bind(f(a3, x<3>{}), [=](auto a4) {
                                return f(a4, x<4>{});
                            });
                        });
                    })
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldlM<M>(list(x<1>{}, x<2>{}, x<3>{}, x<4>{}, x<5>{}), a1, f),
                    bind(f(a1, x<1>{}), [=](auto a2) {
                        return bind(f(a2, x<2>{}), [=](auto a3) {
                            return bind(f(a3, x<3>{}), [=](auto a4) {
                                return bind(f(a4, x<4>{}), [=](auto a5) {
                                    return f(a5, x<5>{});
                                });
                            });
                        });
                    })
                ));
            }

            //////////////////////////////////////////////////////////////////
            // foldl1
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldl1(list(z), f),
                z
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldl1(list(z, x<2>{}), f),
                f(z, x<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldl1(list(z, x<2>{}, x<3>{}), f),
                f(f(z, x<2>{}), x<3>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldl1(list(z, x<2>{}, x<3>{}, x<4>{}), f),
                f(f(f(z, x<2>{}), x<3>{}), x<4>{})
            ));

            //////////////////////////////////////////////////////////////////
            // foldr
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldr(list(), z, undefined{}),
                z
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                foldr(list(x<0>{}), z, f),
                f(x<0>{}, z)
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldr(list(x<0>{}, x<1>{}), z, f),
                f(x<0>{}, f(x<1>{}, z))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldr(list(x<0>{}, x<1>{}, x<2>{}), z, f),
                f(x<0>{}, f(x<1>{}, f(x<2>{}, z)))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldr(list(x<0>{}, x<1>{}, x<2>{}, x<3>{}), z, f),
                f(x<0>{}, f(x<1>{}, f(x<2>{}, f(x<3>{}, z))))
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                foldr(list(1), z, f),
                f(1, z)
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                foldr(list(1, '2'), z, f),
                f(1, f('2', z))
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                foldr(list(1, '2', 3.3), z, f),
                f(1, f('2', f(3.3, z)))
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                foldr(list(1, '2', 3.3, 4.4f), z, f),
                f(1, f('2', f(3.3, f(4.4f, z))))
            ));

            //////////////////////////////////////////////////////////////////
            // foldrM
            //////////////////////////////////////////////////////////////////
            {
                using M = test::Identity;
                auto f = hana::demux(test::identity)(test::_injection<0>{});
                auto aN = x<999>{};

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldrM<M>(list(), aN, undefined{}),
                    lift<M>(aN)
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldrM<M>(list(x<1>{}), aN, f),
                    f(x<1>{}, aN)
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldrM<M>(list(x<1>{}, x<2>{}), aN, f),
                    bind(f(x<2>{}, aN), [=](auto a1) {
                        return f(x<1>{}, a1);
                    })
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldrM<M>(list(x<1>{}, x<2>{}, x<3>{}), aN, f),
                    bind(f(x<3>{}, aN), [=](auto a2) {
                        return bind(f(x<2>{}, a2), [=](auto a1) {
                            return f(x<1>{}, a1);
                        });
                    })
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldrM<M>(list(x<1>{}, x<2>{}, x<3>{}, x<4>{}), aN, f),
                    bind(f(x<4>{}, aN), [=](auto a3) {
                        return bind(f(x<3>{}, a3), [=](auto a2) {
                            return bind(f(x<2>{}, a2), [=](auto a1) {
                                return f(x<1>{}, a1);
                            });
                        });
                    })
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    foldrM<M>(list(x<1>{}, x<2>{}, x<3>{}, x<4>{}, x<5>{}), aN, f),
                    bind(f(x<5>{}, aN), [=](auto a4) {
                        return bind(f(x<4>{}, a4), [=](auto a3) {
                            return bind(f(x<3>{}, a3), [=](auto a2) {
                                return bind(f(x<2>{}, a2), [=](auto a1) {
                                    return f(x<1>{}, a1);
                                });
                            });
                        });
                    })
                ));
            }

            //////////////////////////////////////////////////////////////////
            // foldr1
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldr1(list(z), f),
                z
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldr1(list(x<1>{}, z), f),
                f(x<1>{}, z)
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldr1(list(x<1>{}, x<2>{}, z), f),
                f(x<1>{}, f(x<2>{}, z))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                foldr1(list(x<1>{}, x<2>{}, x<3>{}, z), f),
                f(x<1>{}, f(x<2>{}, f(x<3>{}, z)))
            ));

            //////////////////////////////////////////////////////////////////
            // for_each
            //////////////////////////////////////////////////////////////////
            auto check = [=](auto ...xs) {
                std::vector<int> seen{};
                for_each(list(xs...), [&](int x) {
                    seen.push_back(x);
                });
                BOOST_HANA_RUNTIME_CHECK(seen == std::vector<int>{xs...});
            };
            check();
            check(0);
            check(0, 1);
            check(0, 1, 2);
            check(0, 1, 2, 3);
            check(0, 1, 2, 3, 4);

            //////////////////////////////////////////////////////////////////
            // length
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                length(list()), size_t<0>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                length(list(undefined{})), size_t<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                length(list(undefined{}, undefined{})), size_t<2>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                length(list(undefined{}, undefined{}, undefined{})), size_t<3>
            ));

            int i = 0; // non-constexpr
            BOOST_HANA_CONSTANT_CHECK(equal(
                length(list(i, i)),
                size_t<2>
            ));

            //////////////////////////////////////////////////////////////////
            // maximum
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<0>{})),
                ord<0>{}
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<0>{}, ord<1>{})),
                ord<1>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<1>{}, ord<0>{})),
                ord<1>{}
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<0>{}, ord<1>{}, ord<2>{})),
                ord<2>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<1>{}, ord<0>{}, ord<2>{})),
                ord<2>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<1>{}, ord<2>{}, ord<0>{})),
                ord<2>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<2>{}, ord<1>{}, ord<0>{})),
                ord<2>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<2>{}, ord<0>{}, ord<1>{})),
                ord<2>{}
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<0>{}, ord<1>{}, ord<2>{}, ord<3>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<1>{}, ord<0>{}, ord<2>{}, ord<3>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<1>{}, ord<2>{}, ord<0>{}, ord<3>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<1>{}, ord<2>{}, ord<3>{}, ord<0>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<2>{}, ord<1>{}, ord<3>{}, ord<0>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<2>{}, ord<3>{}, ord<1>{}, ord<0>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<2>{}, ord<3>{}, ord<0>{}, ord<1>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<3>{}, ord<2>{}, ord<0>{}, ord<1>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<3>{}, ord<0>{}, ord<2>{}, ord<1>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<3>{}, ord<0>{}, ord<1>{}, ord<2>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<0>{}, ord<2>{}, ord<3>{}, ord<1>{})),
                ord<3>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                maximum(list(ord<0>{}, ord<3>{}, ord<1>{}, ord<2>{})),
                ord<3>{}
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{0})),
                int{0}
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{0}, long{1})),
                long{1}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{1}, long{0})),
                int{1}
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{0}, 1ll, long{2})),
                long{2}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{1}, 0ll, long{2})),
                long{2}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{1}, 2ll, long{0})),
                2ll
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{2}, 1ll, long{0})),
                int{2}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{2}, 0ll, long{1})),
                int{2}
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{0}, 1ll, long{2}, short{3})),
                short{3}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{1}, 0ll, long{2}, short{3})),
                short{3}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{1}, 2ll, long{0}, short{3})),
                short{3}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{1}, 2ll, long{3}, short{0})),
                long{3}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{2}, 1ll, long{3}, short{0})),
                long{3}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{2}, 3ll, long{1}, short{0})),
                3ll
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{2}, 3ll, long{0}, short{1})),
                3ll
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{3}, 2ll, long{0}, short{1})),
                int{3}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{3}, 0ll, long{2}, short{1})),
                int{3}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{3}, 0ll, long{1}, short{2})),
                int{3}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{0}, 2ll, long{3}, short{1})),
                long{3}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{0}, 3ll, long{1}, short{2})),
                3ll
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{0}, 1ll, long_<2>)),
                long{2}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                maximum(list(int{0}, long_<1>, 2ll)),
                2ll
            ));

            //////////////////////////////////////////////////////////////////
            // minimum
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<6>{})),
                ord<6>{}
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<6>{}, ord<7>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<7>{}, ord<6>{})),
                ord<6>{}
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<6>{}, ord<7>{}, ord<8>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<7>{}, ord<6>{}, ord<8>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<7>{}, ord<8>{}, ord<6>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<8>{}, ord<7>{}, ord<6>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<8>{}, ord<6>{}, ord<7>{})),
                ord<6>{}
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<6>{}, ord<7>{}, ord<8>{}, ord<9>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<7>{}, ord<6>{}, ord<8>{}, ord<9>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<7>{}, ord<8>{}, ord<6>{}, ord<9>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<7>{}, ord<8>{}, ord<9>{}, ord<6>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<8>{}, ord<7>{}, ord<9>{}, ord<6>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<8>{}, ord<9>{}, ord<7>{}, ord<6>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<8>{}, ord<9>{}, ord<6>{}, ord<7>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<9>{}, ord<8>{}, ord<6>{}, ord<7>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<9>{}, ord<6>{}, ord<8>{}, ord<7>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<9>{}, ord<6>{}, ord<7>{}, ord<8>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<6>{}, ord<8>{}, ord<9>{}, ord<7>{})),
                ord<6>{}
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                minimum(list(ord<6>{}, ord<9>{}, ord<7>{}, ord<8>{})),
                ord<6>{}
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{4})),
                int{4}
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{4}, short{5})),
                int{4}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{5}, short{4})),
                short{4}
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{4}, short{5}, long{6})),
                int{4}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{7}, short{6}, long{8})),
                short{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{7}, short{8}, long{6})),
                long{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{8}, short{7}, long{6})),
                long{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{8}, short{6}, long{7})),
                short{6}
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{6}, short{7}, long{8}, 9ll)),
                int{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{7}, short{6}, long{8}, 9ll)),
                short{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{7}, short{8}, long{6}, 9ll)),
                long{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{7}, short{8}, long{9}, 6ll)),
                6ll
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{8}, short{7}, long{9}, 6ll)),
                6ll
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{8}, short{9}, long{7}, 6ll)),
                6ll
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{8}, short{9}, long{6}, 7ll)),
                long{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{9}, short{8}, long{6}, 7ll)),
                long{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{9}, short{6}, long{8}, 7ll)),
                short{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{9}, short{6}, long{7}, 8ll)),
                short{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{6}, short{8}, long{9}, 7ll)),
                int{6}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{6}, short{9}, long{7}, 8ll)),
                int{6}
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(int{3}, short{4}, long_<5>)),
                int{3}
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                minimum(list(ord<33>{}, short{45}, long{46})),
                ord<33>{}
            ));

            //////////////////////////////////////////////////////////////////
            // count_if
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(), id), size_t<0>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<1>), id), size_t<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<0>), id), size_t<0>
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<1>, char_<1>), id), size_t<2>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<1>, char_<0>), id), size_t<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<0>, char_<1>), id), size_t<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<0>, char_<0>), id), size_t<0>
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<1>, char_<1>, long_<1>), id), size_t<3>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<1>, char_<1>, long_<0>), id), size_t<2>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<1>, char_<0>, long_<1>), id), size_t<2>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<1>, char_<0>, long_<0>), id), size_t<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<0>, char_<1>, long_<1>), id), size_t<2>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<0>, char_<1>, long_<0>), id), size_t<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<0>, char_<0>, long_<1>), id), size_t<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                count_if(list(int_<0>, char_<0>, long_<0>), id), size_t<0>
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{1}), id), 1u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{0}), id), 0u
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{1}, char{1}), id), 2u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{1}, char{0}), id), 1u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{0}, char{1}), id), 1u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{0}, char{0}), id), 0u
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{1}, char{1}, double{1}), id), 3u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{1}, char{1}, double{0}), id), 2u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{1}, char{0}, double{1}), id), 2u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{1}, char{0}, double{0}), id), 1u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{0}, char{1}, double{1}), id), 2u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{0}, char{1}, double{0}), id), 1u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{0}, char{0}, double{1}), id), 1u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(int{0}, char{0}, double{0}), id), 0u
            ));

            BOOST_HANA_CONSTEXPR_LAMBDA auto is_even = [](auto x) {
                return x % 2 == 0;
            };
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(), is_even), 0u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(1), is_even), 0u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(2), is_even), 1u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(1, 2), is_even), 1u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(1, 2, 3), is_even), 1u
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                count_if(list(1, 2, 3, 4), is_even), 2u
            ));

            //////////////////////////////////////////////////////////////////
            // count
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                hana::count(list(), undefined{}),
                size_t<0>
            ));

            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                hana::count(list(ct_eq<0>{}), undefined{}),
                size_t<0>
            ));
            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                hana::count(list(ct_eq<0>{}), ct_eq<0>{}),
                size_t<1>
            ));

            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                hana::count(list(ct_eq<0>{}, ct_eq<1>{}), undefined{}),
                size_t<0>
            ));
            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                hana::count(list(ct_eq<0>{}, ct_eq<1>{}), ct_eq<0>{}),
                size_t<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                hana::count(list(ct_eq<0>{}, ct_eq<1>{}), ct_eq<1>{}),
                size_t<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                hana::count(list(ct_eq<0>{}, ct_eq<0>{}), ct_eq<0>{}),
                size_t<2>
            ));

            //////////////////////////////////////////////////////////////////
            // product
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                product(list()),
                int_<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                product(list(int_<2>)),
                int_<2>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                product(list(int_<2>, int_<3>)),
                int_<2 * 3>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                product(list(int_<2>, int_<3>, int_<4>)),
                int_<2 * 3 * 4>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                product(list(int_<2>, int_<3>, int_<4>, int_<5>)),
                int_<2 * 3 * 4 * 5>
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                product(list(2)),
                2
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                product(list(2, 3)),
                2 * 3
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                product(list(2, 3, 4)),
                2 * 3 * 4
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                product(list(2, 3, 4, 5)),
                2 * 3 * 4 * 5
            ));

            //////////////////////////////////////////////////////////////////
            // sum
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                sum(list()),
                int_<0>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sum(list(int_<1>)),
                int_<1>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sum(list(int_<1>, int_<2>)),
                int_<1 + 2>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sum(list(int_<1>, int_<2>, int_<3>)),
                int_<1 + 2 + 3>
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sum(list(int_<1>, int_<2>, int_<3>, int_<4>)),
                int_<1 + 2 + 3 + 4>
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                sum(list(1)), 1
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                sum(list(1, 2)), 1 + 2
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                sum(list(1, 2, 3)), 1 + 2 + 3
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                sum(list(1, 2, 3, 4)), 1 + 2 + 3 + 4
            ));

            //////////////////////////////////////////////////////////////////
            // unpack
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                unpack(list(), f),
                f()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unpack(list(x<0>{}), f),
                f(x<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unpack(list(x<0>{}, x<1>{}), f),
                f(x<0>{}, x<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unpack(list(x<0>{}, x<1>{}, x<2>{}), f),
                f(x<0>{}, x<1>{}, x<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unpack(list(x<0>{}, x<1>{}, x<2>{}, x<3>{}), f),
                f(x<0>{}, x<1>{}, x<2>{}, x<3>{})
            ));
        }
    };
}}} // end namespace boost::hana::test

#endif // !BOOST_HANA_TEST_LAWS_FOLDABLE_HPP


//==============================================================================
//         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 <boost/simd/include/functions/interleave_odd.hpp>
#include <boost/simd/sdk/simd/native.hpp>
#include <boost/simd/sdk/simd/io.hpp>
#include <boost/simd/sdk/meta/cardinal_of.hpp>
#include <nt2/sdk/unit/tests.hpp>
#include <nt2/sdk/unit/module.hpp>

NT2_TEST_CASE_TPL(interleave_odd, BOOST_SIMD_SIMD_TYPES)
{
  using boost::simd::native;
  using boost::simd::meta::cardinal_of;

  typedef BOOST_SIMD_DEFAULT_EXTENSION      ext_t;
  typedef native<T,ext_t>                      vT;

  const std::size_t card = cardinal_of<vT>::value;
  vT a,b,c,ref;

  for(std::size_t i=0; i<card; ++i)
  {
    a[i]=T(i+1);
    b[i]=T((i+1)*10);
  }

  for(std::size_t i=0;i<card;i+= 2)
  {
    ref[i]   = a[i+1];
    ref[i+1] = b[i+1];
  }

  c = boost::simd::interleave_odd(a,b);

  NT2_TEST_EQUAL(c,ref);
}


/*
Brian Staber (brian.staber@gmail.com)
*/

#ifndef SHINOZUKAPP_2D_HPP
#define SHINOZUKAPP_2D_HPP

#include "meshpp.hpp"
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <boost/math/special_functions/erf.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/math/special_functions/gamma.hpp>
#include <boost/math/special_functions/beta.hpp>

class shinozuka_2d
{
public:

    shinozuka_2d();
    shinozuka_2d(int & nu, double & L1, double & L2);
    ~shinozuka_2d();

    template<typename typearg>
    double tau_beta(typearg & beta);
    double s_tau(double & tau);

    void generator(Epetra_Vector & v, mesh & Mesh);

    void icdf_gamma(Epetra_Vector & V, Epetra_Vector & G, double & alpha, double & beta);
    void icdf_beta(Epetra_Vector & V, Epetra_Vector & B, double & tau1, double & tau2);

    int order;
    double l1;
    double l2;
    double rotation = 0.0;
    boost::random::mt19937 rng;
    boost::random::uniform_real_distribution<> phi_;
    boost::random::uniform_real_distribution<> psi_;
};
#endif


///////////////////////////////////////////////////////////////
//  Copyright 2012 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt

#ifdef _MSC_VER
#define _SCL_SECURE_NO_WARNINGS
#endif

#include <boost/multiprecision/gmp.hpp>
#include "test_mixed.hpp"

int main()
{
#ifndef BOOST_NO_EXCEPTIONS
   try
   {
#endif
      typedef boost::multiprecision::number<boost::multiprecision::gmp_float<100>, boost::multiprecision::et_on>  big_type1;
      typedef boost::multiprecision::number<boost::multiprecision::gmp_float<50>, boost::multiprecision::et_on>   small_type1;
      typedef boost::multiprecision::number<boost::multiprecision::gmp_float<100>, boost::multiprecision::et_off> big_type2;
      typedef boost::multiprecision::number<boost::multiprecision::gmp_float<50>, boost::multiprecision::et_off>  small_type2;

      test<big_type1, small_type1>();
      test<big_type2, small_type2>();
      test<big_type1, small_type2>();
      test<big_type2, small_type1>();
#ifndef BOOST_NO_EXCEPTIONS
   }
   catch (const std::exception& e)
   {
      std::cout << "Failed with unexpected exception: " << e.what() << std::endl;
      return 1;
   }
#endif
   return boost::report_errors();
}


#include <taichi/lang.h>
#include <taichi/testing.h>
#include <numeric>
#include <taichi/visual/gui.h>
#include <taichi/system/threading.h>
#include <Eigen/Dense>
#include <taichi/math/eigen.h>
#include <Eigen/StdVector>

TLANG_NAMESPACE_BEGIN

static TC_FORCE_INLINE __m128 make_float4(float32 a,
                                          float32 b,
                                          float32 c,
                                          float32 d) {
  return _mm_set_ps(d, c, b, a);
}

TC_FORCE_INLINE __m128 make_float3(float a, float b, float c) {
  return make_float4(a, b, c, 0);
}

// clang-format off
TC_ALIGNED(64) const static __m128 grid_pos_offset_[27] = {
    make_float3(0, 0, 0),
    make_float3(0, 0, 1),
    make_float3(0, 0, 2),
    make_float3(0, 1, 0),
    make_float3(0, 1, 1),
    make_float3(0, 1, 2),
    make_float3(0, 2, 0),
    make_float3(0, 2, 1),
    make_float3(0, 2, 2),
    make_float3(1, 0, 0),
    make_float3(1, 0, 1),
    make_float3(1, 0, 2),
    make_float3(1, 1, 0),
    make_float3(1, 1, 1),
    make_float3(1, 1, 2),
    make_float3(1, 2, 0),
    make_float3(1, 2, 1),
    make_float3(1, 2, 2),
    make_float3(2, 0, 0),
    make_float3(2, 0, 1),
    make_float3(2, 0, 2),
    make_float3(2, 1, 0),
    make_float3(2, 1, 1),
    make_float3(2, 1, 2),
    make_float3(2, 2, 0),
    make_float3(2, 2, 1),
    make_float3(2, 2, 2),
};
// clang-format on

TC_TEST("cast_int") {
  __m128 f = _mm_set_ps(0.1f, 0.4f, 0.6f, 0.7f);
  union {
    __m128i i;
    int v[4];
  };
  i = _mm_cvtps_epi32(f);  // round to nearest, not floor....
  for (int j = 0; j < 4; j++) {
    TC_P(v[j]);
  }
}

struct MPMContext {
 public:
  struct Particle {
    real J;
    Vector3 pos, v;
    Matrix3 C;

    Particle() {
      pos = Vector3::rand() * 0.5_f + Vector3(0.25_f);
      v = Vector3::rand();
      C = Matrix3::rand();
      J = rand<float32>();
    }
  };

  struct ParticleEigen {
    // EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    Eigen::Matrix<real, 3, 3> C;
    Eigen::Matrix<real, 3, 1> pos, v;
    real J;
    ParticleEigen() {
    }

    ParticleEigen(Particle &p) {
      pos = to_eigen(p.pos);
      v = to_eigen(p.v);
      C = to_eigen(p.C);
      J = p.J;
    }
  };

  int n_particles;  // num particles
  static constexpr real mass = 2.0_f;
  static constexpr real vol = 3.0_f;
  static constexpr int n = 7;
  static constexpr real dx = 1.0_f / n;
  static constexpr real inv_dx = 1.0_f / dx;
  static constexpr real dt = 1e-1_f;

  using Grid = Vector4[n][n][n];
  Grid grid;
  using EigenGrid = Eigen::Matrix<real, 4, 1>[n][n][n];
  EigenGrid eigen_grid;

  void clear_grid() {
    std::memset(grid, 0, sizeof(grid));
    std::memset(eigen_grid, 0, sizeof(eigen_grid));
  }

  std::vector<Particle> particles;
  std::vector<ParticleEigen> eigen_particles;

  MPMContext(int n_particles) : n_particles(n_particles) {
    particles.resize(n_particles);
    eigen_particles.resize(n_particles);
    for (int i = 0; i < n_particles; i++) {
      particles[i] = Particle();
      eigen_particles[i] = ParticleEigen(particles[i]);
    }
    clear_grid();
  }

  void p2g() {
    constexpr int T = 3;
    TC_WARN_IF(T != 3, "T is not 3");

    for (int p_i = 0; p_i < n_particles; p_i++) {
      using Vec = Vector3;
      auto &p = particles[p_i];
      Vector3i base_coord = (p.pos * inv_dx - Vector3(0.5_f)).cast<int>();
      auto fx = p.pos * inv_dx - base_coord.cast<real>();
      // Quadratic kernels  [http://mpm.graphics   Eqn. 123, with x=fx,
      // fx-1,fx-2]
      Vec w[3]{Vec(0.5) * sqr(Vec(1.5) - fx), Vec(0.75) - sqr(fx - Vec(1.0)),
               Vec(0.5) * sqr(fx - Vec(0.5))};
      real J = p.J;
      auto stress =  // Cauchy stress times dt and inv_dx
          -4 * inv_dx * inv_dx * dt * vol * Matrix3(J - 1);
      auto affine = stress + mass * p.C;

      // Scatter to grid
      for (int i = 0; i < T; i++) {
        for (int j = 0; j < T; j++) {
          for (int k = 0; k < T; k++) {
            auto dpos = (Vec(i, j, k) - fx) * dx;
            Vector4 mv(p.v * mass, mass);  // translational momentum
            grid[base_coord.x + i][base_coord.y + j][base_coord.z + k] +=
                w[i].x * w[j].y * w[k].z * (mv + Vector4(affine * dpos, 0));
          }
        }
      }
    }
  }

  void p2g_eigen() {
    constexpr int T = 3;
    TC_WARN_IF(T != 3, "T is not 3");

    for (int p_i = 0; p_i < n_particles; p_i++) {
      using Vec = Eigen::Matrix<real, 3, 1>;
      auto sqr = [](const Vec &vec) -> Vec {
        Vec ret;
        for (int i = 0; i < 3; i++) {
          ret(i) = vec(i) * vec(i);
        }
        return ret;
      };
      auto &p = eigen_particles[p_i];
      Eigen::Matrix<int, 3, 1> base_coord =
          (p.pos * inv_dx - Vec::Ones() * 0.5_f).cast<int>();
      Vec fx = p.pos * inv_dx - base_coord.cast<real>();

      Vec w[3]{0.5f * sqr(Vec::Ones() * 1.5f - fx),
               Vec::Ones() * 0.75f - sqr(fx - Vec::Ones()),
               0.5f * sqr(fx - Vec::Ones() * 0.5f)};
      real J = p.J;
      auto stress =  // Cauchy stress times dt and inv_dx
          -4 * inv_dx * inv_dx * dt * vol * (J - 1) *
          Eigen::Matrix<real, 3, 3>::Identity();
      Eigen::Matrix<real, 3, 3> affine = stress + mass * p.C;

      // Scatter to grid
      for (int i = 0; i < T; i++) {
        for (int j = 0; j < T; j++) {
          for (int k = 0; k < T; k++) {
            Vec dpos = (Vec(i, j, k) - fx) * dx;
            Eigen::Matrix<real, 4, 1> contrib;
            for (int d = 0; d < 3; d++) {
              contrib(d) = mass * p.v(d) + (affine * dpos)(d);
            }
            contrib(3) = mass;
            eigen_grid[base_coord(0) + i][base_coord(1) + j]
                      [base_coord(2) + k] +=
                w[i](0) * w[j](1) * w[k](2) * contrib;
          }
        }
      }
    }
  }

  using Vector = Vector3f;
  using Matrix = Matrix3f;
  using Vectori = Vector3i;

  struct MLSMPMFastKernel32 {
    static constexpr int dim = 3;
    TC_ALIGNED(64) __m128 kernels[3][3];

    TC_FORCE_INLINE MLSMPMFastKernel32(const __m128 &pos, real inv_delta_x) {
      __m128 p_fract = _mm_sub_ps(pos, _mm_set1_ps(0.5f));
      TC_ALIGNED(64) __m128 w_cache[dim];
      for (int k = 0; k < dim; k++) {
        __m128 t = _mm_sub_ps(_mm_set1_ps(p_fract[k]),
                              make_float4(-0.5f, 0.5f, 1.5f, 0.0f));
        __m128 tt = _mm_mul_ps(t, t);
        w_cache[k] = _mm_fmadd_ps(
            make_float4(0.5f, -1.0f, 0.5f, 0.0f), tt,
            _mm_fmadd_ps(make_float4(-1.5f, 0.0f, 1.5f, 0.0f), t,
                         make_float4(1.125f, 0.75f, 1.125f, 0.0f)));
      }
      for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
          kernels[i][j] = _mm_mul_ps(_mm_set1_ps(w_cache[0][i] * w_cache[1][j]),
                                     w_cache[2]);
        }
      }
    }
  };

  TC_FORCE_INLINE static int get_stencil_start(real x) {
    return int(x - 0.5f);
  }

#define broadcast(s, i) _mm_shuffle_ps((s), (s), 0x55 * (i))
  void p2g_intrinsics() {
    const auto inv_delta_x = 1.0_f / dx;
    const real delta_t = dt;
    __m128 S = _mm_set1_ps(-4.0_f * inv_delta_x * delta_t);
    for (int p_i = 0; p_i < n_particles; p_i++) {
      Particle &p = particles[p_i];

      // Note, pos is magnified grid pos
      __m128 pos_ = _mm_mul_ps(p.pos.v, _mm_set1_ps(inv_delta_x));

      Vectori grid_base_pos(get_stencil_start(pos_[0]),
                            get_stencil_start(pos_[1]),
                            get_stencil_start(pos_[2]));
      auto base_offset =
          grid_base_pos[0] * n * n + grid_base_pos[1] * n + grid_base_pos[2];
      Vector grid_base_pos_f = Vector(grid_base_pos);

      MLSMPMFastKernel32 kernel(_mm_sub_ps(pos_, grid_base_pos_f), inv_delta_x);
      const __m128(&kernels)[3][3] = kernel.kernels;
      const __m128 v = p.v.v;
      __m128 mass_ = _mm_set1_ps(mass);
      // Note, apic_b has delta_x issue
      const Matrix3f apic_b_inv_d_mass = (-mass * dx) * p.C;
      const __m128 mass_v = _mm_mul_ps(_mm_set1_ps(mass), v);

      auto stress = -vol * Matrix3(p.J - 1);

      __m128 delta_t_tmp_force_[3];
      auto &delta_t_tmp_force = reinterpret_cast<Matrix &>(delta_t_tmp_force_);
      for (int i = 0; i < 3; i++) {
        delta_t_tmp_force_[i] = _mm_mul_ps(_mm_set1_ps(delta_t), stress[i]);
      }

      __m128 rela_pos = _mm_sub_ps(pos_, grid_base_pos_f);
      __m128 affine[3];

      for (int i = 0; i < 3; i++)
        affine[i] = _mm_fmadd_ps(stress[i], S, apic_b_inv_d_mass[i]);
#define LOOP(node_id)                                                       \
  {                                                                         \
    __m128 dpos = _mm_sub_ps(rela_pos, grid_pos_offset_[node_id]);          \
    __m128 weight =                                                         \
        _mm_set1_ps(kernels[node_id / 9][node_id / 3 % 3][node_id % 3]);    \
    __m128 affine_prod = _mm_fmadd_ps(                                      \
        affine[2], broadcast(dpos, 2),                                      \
        _mm_fmadd_ps(affine[1], broadcast(dpos, 1),                         \
                     _mm_fmadd_ps(affine[0], broadcast(dpos, 0), mass_v))); \
    __m128 contrib = _mm_blend_ps(mass_, affine_prod, 0x7);                 \
    __m128 delta = _mm_mul_ps(weight, contrib);                             \
    auto &g = grid[0][0][node_id / 9 * n * n + node_id / 3 % 3 * n +        \
                         node_id % 3 + base_offset];                        \
    g = _mm_add_ps(g, delta);                                               \
  }
      TC_REPEAT27(LOOP);
#undef LOOP
    }
  }
#undef broadcast
};

TC_TEST("simd_mpm_baselines") {
  {
    MPMContext context(128);
    context.p2g();

    MPMContext::Grid grid_gt;
    std::memcpy(&grid_gt[0][0][0], &context.grid[0][0][0],
                sizeof(MPMContext::Grid));
    context.clear_grid();
    context.p2g_intrinsics();
    context.p2g_eigen();

    for (int i = 0; i < context.n; i++) {
      for (int j = 0; j < context.n; j++) {
        for (int k = 0; k < context.n; k++) {
          for (int d = 0; d < 4; d++) {
            // TC_INFO("{} {} {} {} , {}", i, j, k, d, grid_gt[i][j][k][d]);
            TC_CHECK_EQUAL(grid_gt[i][j][k][d], context.grid[i][j][k][d],
                           1e-3_f);
            TC_CHECK_EQUAL(grid_gt[i][j][k][d], context.eigen_grid[i][j][k](d),
                           1e-3_f);
          }
        }
      }
    }
  }

  MPMContext context(4 * 1024 * 1024);
  int N = 4;
  for (int i = 0; i < N; i++) {
    TC_TIME(context.p2g());
  }

  context.clear_grid();
  for (int i = 0; i < N; i++) {
    TC_TIME(context.p2g_intrinsics());
  }

  for (int i = 0; i < N; i++) {
    TC_TIME(context.p2g_eigen());
  }
};

// TODO: shuffled inputs?

TC_TEST("simd_mpm") {
  initialize_benchmark();
  int n_particles = 4 * 1024 * 1024;
  MPMContext context(n_particles);
  int n_grid = context.n;
  context.p2g();

  CoreState::set_trigger_gdb_when_crash(true);
  Program prog(Arch::x86_64);
  prog.config.max_vector_width = 4;
  prog.config.gcc_version = -2;
  prog.config.serial_schedule = true;
  prog.config.force_vectorized_global_load = true;
  prog.config.force_vectorized_global_store = true;
  // prog.config.extra_flags = "-fno-slp-vectorize";

  Global(g_J, f32);
  Global(tmp, i32);
  Global(tmp1, f32);
  auto ind = Index(0);
  constexpr int dim = 3;

  Vector grid(DataType::f32, dim + 1);
  Vector g_pos(DataType::f32, dim);
  Vector g_v(DataType::f32, dim);
  Vector g_C(DataType::f32, dim, dim);

  layout([&]() {
    root.dense(ind, n_particles).place(tmp);
    root.dense(ind, n_particles).place(tmp1);
    root.dense(ind, n_particles).place(g_J);
    for (int i = 0; i < dim; i++) {
      root.dense(ind, n_particles).place(g_pos(i));
      root.dense(ind, n_particles).place(g_v(i));
      for (int j = 0; j < dim; j++) {
        root.dense(ind, n_particles).place(g_C(i, j));
      }
    }
    root.dense(ind, bit::least_pot_bound(n_grid * n_grid * n_grid))
        .place(grid(0), grid(1), grid(2), grid(3));
  });

  auto &p2g = kernel([&]() {
    Declare(p_i);
    Vectorize(4);
    For(p_i, 0, n_particles, [&]() {
      auto mass = context.mass;
      auto vol = context.vol;
      auto dx = context.dx;
      auto inv_dx = context.inv_dx;
      auto dt = context.dt;

      auto v = Eval(g_v[p_i]);
      auto pos = Eval(g_pos[p_i]);
      auto J = Eval(g_J[p_i]);

      Vector v4(4);
      for (int i = 0; i < dim; i++) {
        v4(i) = v(i);
      }
      v4(3) = real(1);

      Vector base_coord = (inv_dx * pos - 0.5_f).cast_elements<int>();
      auto base_offset = Eval(base_coord(0) * (n_grid * n_grid) +
                              base_coord(1) * (n_grid) + base_coord(2));
      Vector fx = inv_dx * pos - base_coord.cast_elements<real>();

      Vector w[3];
      w[0] = 0.5_f * sqr(1.5_f - fx);
      w[1] = 0.75_f - sqr(fx - 1.0_f);
      w[2] = 0.5_f * sqr(fx - 0.5_f);

      Vector fx4(4);
      for (int i = 0; i < dim; i++) {
        fx4(i) = fx(i);
      }
      fx4(3) = real(0);

      Matrix stress(dim, dim);
      for (int i = 0; i < dim; i++) {
        for (int j = 0; j < dim; j++) {
          if (i == j) {
            stress(i, j) = J - real(1);
          } else {
            stress(i, j) = real(0);
          }
        }
      }

      auto scaled_stress = Eval((-4 * inv_dx * inv_dx * dt * vol) * stress);

      Matrix affine_ = dx * (scaled_stress + mass * g_C[p_i]);
      Matrix affine__(4, 3);
      for (int i = 0; i < dim; i++) {
        for (int j = 0; j < dim; j++) {
          affine__(i, j) = affine_(i, j);
        }
        affine__(dim, i) = real(0);
      }
      Matrix affine(4, 3);
      int slp = 4;
      SLP(slp);
      for (int j = 0; j < dim; j++) {
        for (int i = 0; i < dim + 1; i++) {
          affine(i, j) = Eval(affine__(i, j));
        }
      }

      constexpr int T = 3;
      TC_WARN_IF(T != 3, "T is not 3");

      auto mv = Eval(mass * v4);

      auto contrib0 = Eval(mv - Eval(affine * fx));
      for (int i = 0; i < T; i++) {
        SLP(1);
        auto weight0 = Eval(w[i](0));
        SLP(slp);
        auto contrib1 = Eval(contrib0);
        for (int j = 0; j < T; j++) {
          SLP(1);
          auto weight1 = Eval(weight0 * w[j](1));
          SLP(slp);
          auto contrib2 = Eval(contrib1);
          for (int k = 0; k < T; k++) {
            SLP(1);
            auto weight2 = Eval(weight1 * w[k](2));
            SLP(slp);
            auto contrib2 = Eval(contrib1 + real(k) * affine.col(2));
            grid[base_offset + (i * n_grid * n_grid + j * n_grid + k)] +=
                weight2 * contrib2;
          }
          SLP(slp);
          contrib1.set(Eval(contrib1 + affine.col(1)));
        }
        SLP(slp);
        contrib0.set(Eval(contrib0 + affine.col(0)));
      }
    });
  });

  auto initialize_data = [&] {
    for (int p = 0; p < n_particles; p++) {
      g_J.val<float32>(p) = context.particles[p].J;
      for (int i = 0; i < dim; i++) {
        g_pos(i).val<float32>(p) = context.particles[p].pos[i];
        g_v(i).val<float32>(p) = context.particles[p].v[i];
        for (int j = 0; j < dim; j++) {
          g_C(i, j).val<float32>(p) = context.particles[p].C[j][i];
        }
      }
    }
  };
  TC_TIME(initialize_data());

  TC_P(taichi::PID::get_pid());
  TC_TIME(p2g());

  /*
  for (int i = 0; i < n_particles; i++) {
    TC_P(tmp.val<int32>(i));
  }
  for (int i = 0; i < n_particles; i++) {
    TC_P(tmp1.val<float32>(i));
  }
  return;
  */

  auto tolerance = 1e-4_f * std::sqrt((real)n_particles);
  for (int i = 0; i < context.n; i++) {
    for (int j = 0; j < context.n; j++) {
      for (int k = 0; k < context.n; k++) {
        for (int d = 0; d < 4; d++) {
          if (std::abs(
                  grid(d).val<float32>(i * n_grid * n_grid + j * n_grid + k) -
                  context.grid[i][j][k][d]) > tolerance) {
            TC_WARN("{},{},{} {}", i, j, k, d);
          }
          TC_CHECK_EQUAL(
              grid(d).val<float32>(i * n_grid * n_grid + j * n_grid + k),
              context.grid[i][j][k][d], tolerance);
        }
      }
    }
  }
  for (int i = 0; i < 10; i++)
    TC_TIME(p2g());
};

TLANG_NAMESPACE_END


#ifndef STAN_MATH_REV_MAT_FUN_MATRIX_EXP_MULTIPLY_HPP
#define STAN_MATH_REV_MAT_FUN_MATRIX_EXP_MULTIPLY_HPP

#include <stan/math/rev/mat.hpp>
#include <stan/math/rev/core.hpp>
#include <stan/math/prim/mat/fun/matrix_exp_action_handler.hpp>
#include <stan/math/prim/mat/fun/Eigen.hpp>
#include <stan/math/rev/mat/fun/to_var.hpp>
#include <stan/math/prim/mat/fun/matrix_exp.hpp>
#include <stan/math/prim/mat/err/check_multiplicable.hpp>
#include <stan/math/prim/scal/err/check_not_nan.hpp>
#include <boost/math/tools/promotion.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>
#include <vector>

namespace stan {
namespace math {

/**
 * Calculate adjoint of matrix exponential action
 * exp(At)*B when A is data and B is var, used for chaining action.
 *
 * @param Ad double array pointer to the data of matrix A
 * @param n dim of square matrix A
 * @param adjexpAB MatrixXd adjoint of exp(At)*B
 * @param t double time data
 * @return MatrixXd The adjoint of B.
 */
inline Eigen::MatrixXd exp_action_chain_dv(double* Ad, const int& n,
                                           const Eigen::MatrixXd& adjexpAB,
                                           const double t) {
  using Eigen::Map;
  matrix_exp_action_handler handle;
  return handle.action(Map<Eigen::MatrixXd>(Ad, n, n).transpose(), adjexpAB, t);
}

/**
 * Calculate adjoint of matrix exponential action
 * exp(At)*B when A is var and B is data, used for chaining action.
 *
 * @param Ad double array pointer to the data of matrix A
 * @param Bd double array pointer to the data of matrix B
 * @param n dim(nb. of rows) of square matrix A
 * @param m nb. of cols of matrix B
 * @param adjexpAB MatrixXd adjoint of exp(At)*B
 * @param t double time data
 * @return MatrixXd The adjoint of A.
 */
inline Eigen::MatrixXd exp_action_chain_vd(double* Ad, double* Bd, const int& n,
                                           const int& m,
                                           const Eigen::MatrixXd& adjexpAB,
                                           const double t) {
  using Eigen::Map;
  using Eigen::Matrix;
  using Eigen::MatrixXd;
  Eigen::MatrixXd adjexpA = Eigen::MatrixXd::Zero(n, n);
  Eigen::MatrixXd adjA = Eigen::MatrixXd::Zero(n, n);

  // TODO(yizhang): a better way such as complex step approximation
  try {
    start_nested();

    adjexpA = adjexpAB * Map<MatrixXd>(Bd, n, m).transpose();
    Eigen::Matrix<stan::math::var, Eigen::Dynamic, Eigen::Dynamic> Av(n, n);
    for (int i = 0; i < Av.size(); ++i) {
      Av(i) = to_var(Ad[i]);
    }
    std::vector<stan::math::var> Avec(Av.data(), Av.data() + Av.size());
    Eigen::Matrix<stan::math::var, Eigen::Dynamic, Eigen::Dynamic> expA
        = matrix_exp(Av);
    std::vector<double> g;
    for (size_type i = 0; i < expA.size(); ++i) {
      stan::math::set_zero_all_adjoints_nested();
      expA.coeffRef(i).grad(Avec, g);
      for (size_type j = 0; j < adjA.size(); ++j) {
        adjA(j) += adjexpA(i) * g[j];
      }
    }
  } catch (const std::exception& e) {
    recover_memory_nested();
    throw;
  }
  recover_memory_nested();
  return adjA;
}

/**
 * This is a subclass of the vari class for matrix
 * exponential action exp(At) * B where A is a double
 * NxN matrix and B is a NxCb matrix.
 *
 * The class stores the structure of each matrix,
 * the double values of A and B, and pointers to
 * the varis for A and B if A or B is a var. It
 * also instantiates and stores pointers to
 * varis for all elements of A * B.
 *
 * @tparam Ta Scalar type of matrix A
 * @tparam N rows and cols of A
 * @tparam Tb Scalar type for matrix B
 * @tparam Cb cols for matrix B
 */
template <typename Ta, int N, typename Tb, int Cb>
class matrix_exp_action_vari : public vari {
 public:
  int n_;
  int B_cols_;
  int A_size_;
  int B_size_;
  double t_;
  double* Ad_;
  double* Bd_;
  vari** variRefA_;
  vari** variRefB_;
  vari** variRefexpAB_;

  /**
   * Constructor: vari child-class of matrix_exp_action_vari.
   * @param A statically-sized matirx
   * @param B statically-sized matirx
   * @param t double scalar time.
   */
  matrix_exp_action_vari(const Eigen::Matrix<Ta, N, N>& A,
                         const Eigen::Matrix<Tb, N, Cb>& B, const double& t)
      : vari(0.0),
        n_(A.rows()),
        B_cols_(B.cols()),
        A_size_(A.size()),
        B_size_(B.size()),
        t_(t),
        Ad_(ChainableStack::instance().memalloc_.alloc_array<double>(A_size_)),
        Bd_(ChainableStack::instance().memalloc_.alloc_array<double>(B_size_)),
        variRefA_(
            ChainableStack::instance().memalloc_.alloc_array<vari*>(A_size_)),
        variRefB_(
            ChainableStack::instance().memalloc_.alloc_array<vari*>(B_size_)),
        variRefexpAB_(
            ChainableStack::instance().memalloc_.alloc_array<vari*>(B_size_)) {
    using Eigen::Map;
    using Eigen::MatrixXd;
    for (size_type i = 0; i < A.size(); ++i) {
      variRefA_[i] = A.coeffRef(i).vi_;
      Ad_[i] = A.coeffRef(i).val();
    }
    for (size_type i = 0; i < B.size(); ++i) {
      variRefB_[i] = B.coeffRef(i).vi_;
      Bd_[i] = B.coeffRef(i).val();
    }
    matrix_exp_action_handler handle;
    MatrixXd expAB = handle.action(Map<MatrixXd>(Ad_, n_, n_),
                                   Map<MatrixXd>(Bd_, n_, B_cols_), t_);
    for (size_type i = 0; i < expAB.size(); ++i)
      variRefexpAB_[i] = new vari(expAB.coeffRef(i), false);
  }

  /**
   * Chain command for the adjoint of matrix exp action.
   */
  virtual void chain() {
    using Eigen::Map;
    using Eigen::MatrixXd;
    MatrixXd adjexpAB(n_, B_cols_);

    for (size_type i = 0; i < adjexpAB.size(); ++i)
      adjexpAB(i) = variRefexpAB_[i]->adj_;

    MatrixXd adjA = exp_action_chain_vd(Ad_, Bd_, n_, B_cols_, adjexpAB, t_);
    MatrixXd adjB = exp_action_chain_dv(Ad_, n_, adjexpAB, t_);

    for (size_type i = 0; i < A_size_; ++i) {
      variRefA_[i]->adj_ += adjA(i);
    }
    for (size_type i = 0; i < B_size_; ++i) {
      variRefB_[i]->adj_ += adjB(i);
    }
  }
};

/**
 * This is a subclass of the vari class for matrix
 * exponential action exp(At) * B where A is an N by N
 * matrix of double, B is N by Cb, and t is data(time)
 *
 * The class stores the structure of each matrix,
 * the double values of A and B, and pointers to
 * the varis for A and B if B is a var. It
 * also instantiates and stores pointers to
 * varis for all elements of exp(At) * B.
 *
 * @tparam N Rows and cols for matrix A, also rows for B
 * @tparam Tb Scalar type for matrix B
 * @tparam Cb Columns for matrix B
 */
template <int N, typename Tb, int Cb>
class matrix_exp_action_vari<double, N, Tb, Cb> : public vari {
 public:
  int n_;
  int B_cols_;
  int A_size_;
  int B_size_;
  double t_;
  double* Ad_;
  double* Bd_;
  vari** variRefB_;
  vari** variRefexpAB_;

  /**
   * Constructor for matrix_exp_action_vari.
   *
   * All memory allocated in
   * ChainableStack's stack_alloc arena.
   *
   * It is critical for the efficiency of this object
   * that the constructor create new varis that aren't
   * popped onto the var_stack_, but rather are
   * popped onto the var_nochain_stack_. This is
   * controlled to the second argument to
   * vari's constructor.
   *
   * @param A matrix
   * @param B matrix
   * @param t double
   */
  matrix_exp_action_vari(const Eigen::Matrix<double, N, N>& A,
                         const Eigen::Matrix<Tb, N, Cb>& B, const double& t)
      : vari(0.0),
        n_(A.rows()),
        B_cols_(B.cols()),
        A_size_(A.size()),
        B_size_(B.size()),
        t_(t),
        Ad_(ChainableStack::instance().memalloc_.alloc_array<double>(A_size_)),
        Bd_(ChainableStack::instance().memalloc_.alloc_array<double>(B_size_)),
        variRefB_(
            ChainableStack::instance().memalloc_.alloc_array<vari*>(B_size_)),
        variRefexpAB_(
            ChainableStack::instance().memalloc_.alloc_array<vari*>(B_size_)) {
    using Eigen::Map;
    using Eigen::MatrixXd;
    for (size_type i = 0; i < A.size(); ++i)
      Ad_[i] = A.coeffRef(i);
    for (size_type i = 0; i < B.size(); ++i) {
      variRefB_[i] = B.coeffRef(i).vi_;
      Bd_[i] = B.coeffRef(i).val();
    }
    matrix_exp_action_handler handle;
    MatrixXd expAB = handle.action(Map<MatrixXd>(Ad_, n_, n_),
                                   Map<MatrixXd>(Bd_, n_, B_cols_), t_);
    for (size_type i = 0; i < expAB.size(); ++i)
      variRefexpAB_[i] = new vari(expAB.coeffRef(i), false);
  }

  /**
   * Chain for matrix_exp_action_vari.
   */
  virtual void chain() {
    using Eigen::Map;
    using Eigen::MatrixXd;
    MatrixXd adjexpAB(n_, B_cols_);
    // MatrixXd adjB(n_, B_cols_);

    for (size_type i = 0; i < adjexpAB.size(); ++i)
      adjexpAB(i) = variRefexpAB_[i]->adj_;

    MatrixXd adjB = exp_action_chain_dv(Ad_, n_, adjexpAB, t_);

    for (size_type i = 0; i < B_size_; ++i) {
      variRefB_[i]->adj_ += adjB(i);
    }
  }
};

/**
 * This is a subclass of the vari class for matrix
 * exponential action exp(At) * B where A is an N by N
 * matrix of var, B is N by Cb matrix of double, and t is data(time)
 *
 * The class stores the structure of each matrix,
 * the double values of A and B, and pointers to
 * the varis for A and B if B is a var. It
 * also instantiates and stores pointers to
 * varis for all elements of exp(At) * B.
 *
 * @tparam Ta Scalar type for matrix A
 * @tparam N Rows and cols for matrix A, also rows for B
 * @tparam Cb Columns for matrix B
 */
template <typename Ta, int N, int Cb>
class matrix_exp_action_vari<Ta, N, double, Cb> : public vari {
 public:
  int n_;
  int B_cols_;
  int A_size_;
  int B_size_;
  double t_;
  double* Ad_;
  double* Bd_;
  vari** variRefA_;
  vari** variRefexpAB_;

  /**
   * Constructor for matrix_exp_action_vari.
   *
   * All memory allocated in
   * ChainableStack's stack_alloc arena.
   *
   * It is critical for the efficiency of this object
   * that the constructor create new varis that aren't
   * popped onto the var_stack_, but rather are
   * popped onto the var_nochain_stack_. This is
   * controlled to the second argument to
   * vari's constructor.
   *
   * @param A matrix
   * @param B matrix
   * @param t double
   */
  matrix_exp_action_vari(const Eigen::Matrix<Ta, N, N>& A,
                         const Eigen::Matrix<double, N, Cb>& B, const double& t)
      : vari(0.0),
        n_(A.rows()),
        B_cols_(B.cols()),
        A_size_(A.size()),
        B_size_(B.size()),
        t_(t),
        Ad_(ChainableStack::instance().memalloc_.alloc_array<double>(A_size_)),
        Bd_(ChainableStack::instance().memalloc_.alloc_array<double>(B_size_)),
        variRefA_(
            ChainableStack::instance().memalloc_.alloc_array<vari*>(A_size_)),
        variRefexpAB_(
            ChainableStack::instance().memalloc_.alloc_array<vari*>(B_size_)) {
    using Eigen::Map;
    using Eigen::MatrixXd;
    for (size_type i = 0; i < A.size(); ++i) {
      variRefA_[i] = A.coeffRef(i).vi_;
      Ad_[i] = A.coeffRef(i).val();
    }
    for (size_type i = 0; i < B.size(); ++i) {
      Bd_[i] = B.coeffRef(i);
    }
    matrix_exp_action_handler handle;
    MatrixXd expAB = handle.action(Map<MatrixXd>(Ad_, n_, n_),
                                   Map<MatrixXd>(Bd_, n_, B_cols_), t_);
    for (size_type i = 0; i < expAB.size(); ++i)
      variRefexpAB_[i] = new vari(expAB.coeffRef(i), false);
  }

  virtual void chain() {
    using Eigen::Map;
    using Eigen::MatrixXd;
    MatrixXd adjexpAB(n_, B_cols_);

    for (size_type i = 0; i < adjexpAB.size(); ++i)
      adjexpAB(i) = variRefexpAB_[i]->adj_;

    MatrixXd adjA = exp_action_chain_vd(Ad_, Bd_, n_, B_cols_, adjexpAB, t_);

    for (size_type i = 0; i < A_size_; ++i) {
      variRefA_[i]->adj_ += adjA(i);
    }
  }
};

/**
 * Return product of exp(At) and B, where A is a NxN matrix,
 * B is a NxCb matrix, and t is a double
 * @tparam Ta scalar type matrix A
 * @tparam N Rows and cols matrix A, also rows of matrix B
 * @tparam Tb scalar type matrix B
 * @tparam Cb Columns matrix B
 * @param[in] A Matrix
 * @param[in] B Matrix
 * @param[in] t double
 * @return exponential of At multiplies B
 */
template <typename Ta, int N, typename Tb, int Cb>
inline typename boost::enable_if_c<boost::is_same<Ta, var>::value
                                       || boost::is_same<Tb, var>::value,
                                   Eigen::Matrix<var, N, Cb> >::type
matrix_exp_action(const Eigen::Matrix<Ta, N, N>& A,
                  const Eigen::Matrix<Tb, N, Cb>& B, const double& t = 1.0) {
  matrix_exp_action_vari<Ta, N, Tb, Cb>* baseVari
      = new matrix_exp_action_vari<Ta, N, Tb, Cb>(A, B, t);
  Eigen::Matrix<var, N, Cb> expAB_v(A.rows(), B.cols());
  for (size_type i = 0; i < expAB_v.size(); ++i) {
    expAB_v.coeffRef(i).vi_ = baseVari->variRefexpAB_[i];
  }
  return expAB_v;
}

/**
 * Wrapper of matrix_exp_action function for a more literal name
 * @tparam Ta scalar type matrix A
 * @tparam Tb scalar type matrix B
 * @tparam Cb Columns matrix B
 * @param[in] A Matrix
 * @param[in] B Matrix
 * @return exponential of A multiplies B
 */
template <typename Ta, typename Tb, int Cb>
inline Eigen::Matrix<typename stan::return_type<Ta, Tb>::type, -1, Cb>
matrix_exp_multiply(const Eigen::Matrix<Ta, -1, -1>& A,
                    const Eigen::Matrix<Tb, -1, Cb>& B) {
  check_nonzero_size("matrix_exp_multiply", "input matrix", A);
  check_nonzero_size("matrix_exp_multiply", "input matrix", B);
  check_multiplicable("matrix_exp_multiply", "A", A, "B", B);
  check_square("matrix_exp_multiply", "input matrix", A);
  return matrix_exp_action(A, B);
}

}  // namespace math
}  // namespace stan

#endif


/**
 * @file
 * @copyright This code is licensed under the 3-clause BSD license.\n
 *            Copyright ETH Zurich, Laboratory of Physical Chemistry, Reiher Group.\n
 *            See LICENSE.txt for details.
 */

#include "DihedralTerm.h"
#include "../MMExceptions.h"
#include <Utils/Math/AtomicSecondDerivativeCollection.h>
#include <Utils/Math/Tensor33.h>
#include <Eigen/Dense>

namespace Scine {
namespace MolecularMechanics {

DihedralTerm::DihedralTerm(AtomIndex firstAtom, AtomIndex secondAtom, AtomIndex thirdAtom, AtomIndex fourthAtom,
                           const Dihedral& dihedral, const DihedralType& typeOfDihedral)
  : firstAtom_(firstAtom),
    secondAtom_(secondAtom),
    thirdAtom_(thirdAtom),
    fourthAtom_(fourthAtom),
    dihedral_(dihedral),
    typeOfDihedral_(typeOfDihedral) {
}

DihedralTerm::~DihedralTerm() = default;

double DihedralTerm::evaluateDihedralTerm(const Utils::PositionCollection& positions,
                                          Utils::AtomicSecondDerivativeCollection& derivatives) const {
  if (this->disabled_)
    return 0.0;
  if (!dihedral_.hasParameters()) // Check this only if this term is not disabled
    throw MMDihedralParametersNotAvailableException(typeOfDihedral_.a1, typeOfDihedral_.a2, typeOfDihedral_.a3,
                                                    typeOfDihedral_.a4);

  // Calculating the dihedral energy and its derivatives according to
  // A. Blondel, M. Karplus, New Formulation for Derivatives of Torsion Angles and Improper Torsion Angles in Molecular
  // Mechanics: Elimination of Singularities, JCC, 17, 1996, 1132-1141.
  Eigen::Vector3d F(positions.row(firstAtom_) - positions.row(secondAtom_));
  Eigen::Vector3d G(positions.row(secondAtom_) - positions.row(thirdAtom_));
  Eigen::Vector3d H(positions.row(fourthAtom_) - positions.row(thirdAtom_));

  auto A = F.cross(G);
  auto B = H.cross(G);

  double A2 = A.squaredNorm();
  double B2 = B.squaredNorm();
  //  double A4 = A2 * A2;
  //  double B4 = B2 * B2;
  double G1 = G.norm();
  //  double G2 = G1 * G1;
  //  double G3 = G1 * G2;

  // Values needed for first derivatives
  auto gba2a = G1 / A2 * A;
  auto fgba2ga = F.dot(G) / (A2 * G1) * A;
  auto hgbb2gb = H.dot(G) / (B2 * G1) * B;
  auto gbb2b = G1 / B2 * B;

  // First derivatives:
  Eigen::Vector3d firstDer1 = -gba2a;
  Eigen::Vector3d firstDer2 = gba2a + fgba2ga - hgbb2gb;
  Eigen::Vector3d firstDer3 = hgbb2gb - fgba2ga - gbb2b;
  Eigen::Vector3d firstDer4 = gbb2b;

  //  // Values needed for second derivatives // TODO
  //  auto gca = G.cross(A);
  //  auto gcb = G.cross(B);
  //  auto fca = F.cross(A);
  //  auto hcb = H.cross(B);
  //  auto d2df2 = (Utils::tensor(A, gca) + Utils::tensor(gca, A)) * (G.norm() / A4);
  //  auto d2dh2 = (Utils::tensor(B, gcb) + Utils::tensor(gcb, B)) * (-G.norm() / B4);
  //  auto d2dg2 = (Utils::tensor(gca, A) + Utils::tensor(A, gca)) * (1.0 / (2 * G3 * A2)) +
  //               (Utils::tensor(A, fca) + Utils::tensor(fca, A)) * (F.dot(G) / (G.norm() * A4)) +
  //               (Utils::tensor(gcb, B) + Utils::tensor(B, gcb)) * (-1.0 / (2 * G3 * B2)) +
  //               (Utils::tensor(B, hcb) + Utils::tensor(hcb, B)) * (-H.dot(G) / (G.norm() * B4));
  //  auto d2dfdg = (Utils::tensor(-fca, A) * G.squaredNorm() + Utils::tensor(A, -gca) * F.dot(G)) * (1.0 / (G.norm() *
  //  A4)); auto d2dhdg = (Utils::tensor(-hcb, B) * G.squaredNorm() + Utils::tensor(B, -gcb) * H.dot(G)) * (-1.0 /
  //  (G.norm() * B4));

  // Derivative objects for theta
  Utils::AutomaticDifferentiation::Second3D h1, h2, h3, h4;

  //  // Add contributions to second derivatives // TODO
  //  setSecondDerivative(h1, d2df2, 1.0, 1.0);
  //  setSecondDerivative(h2, d2df2, -1.0, -1.0);
  //  setSecondDerivative(h2, d2dg2, 1.0, 1.0);
  //  setSecondDerivative(h2, d2dfdg, -1.0, 1.0);
  //  setSecondDerivative(h3, d2dh2, -1.0, -1.0);
  //  setSecondDerivative(h3, d2dg2, -1.0, -1.0);
  //  setSecondDerivative(h3, d2dhdg, -1.0, -1.0);
  //  setSecondDerivative(h4, d2dh2, 1.0, 1.0);

  // Add first derivatives contributions
  h1.setFirst3D(firstDer1);
  h2.setFirst3D(firstDer2);
  h3.setFirst3D(firstDer3);
  h4.setFirst3D(firstDer4);

  double theta = getTheta(A, B, G);
  auto result = dihedral_.getInteraction(theta);

  // Apply chain rule to get derivatives with respect to the energy
  derivatives[firstAtom_] += threeDimDer(result, h1);
  derivatives[secondAtom_] += threeDimDer(result, h2);
  derivatives[thirdAtom_] += threeDimDer(result, h3);
  derivatives[fourthAtom_] += threeDimDer(result, h4);

  return result.value();
}

Utils::AutomaticDifferentiation::Second3D DihedralTerm::threeDimDer(const Utils::AutomaticDifferentiation::Second1D& energy,
                                                                    const Utils::AutomaticDifferentiation::Second3D& alpha) const {
  return {energy.value(),
          energy.first() * alpha.dx(),
          energy.first() * alpha.dy(),
          energy.first() * alpha.dz(),
          energy.second() * alpha.dx() * alpha.dx() + energy.first() * alpha.XX(),
          energy.second() * alpha.dy() * alpha.dy() + energy.first() * alpha.YY(),
          energy.second() * alpha.dz() * alpha.dz() + energy.first() * alpha.ZZ(),
          energy.second() * alpha.dx() * alpha.dy() + energy.first() * alpha.XY(),
          energy.second() * alpha.dx() * alpha.dz() + energy.first() * alpha.XZ(),
          energy.second() * alpha.dy() * alpha.dz() + energy.first() * alpha.YZ()};
}

void DihedralTerm::setSecondDerivative(Utils::AutomaticDifferentiation::Second3D& h, const Utils::Tensor33& tensor,
                                       double derFirst, double derSecond) const {
  double f = derFirst * derSecond;
  h.setXX(h.XX() + f * tensor.x().x());
  h.setYY(h.YY() + f * tensor.y().y());
  h.setZZ(h.ZZ() + f * tensor.z().z());
  h.setXY(h.XY() + f * tensor.x().y() + f * tensor.y().x()); // TODO: DO I need 1/2 ?
  h.setXZ(h.XZ() + f * tensor.x().z() + f * tensor.z().x());
  h.setYZ(h.YZ() + f * tensor.y().z() + f * tensor.z().y());
}

double DihedralTerm::getTheta(const Eigen::Vector3d& A, const Eigen::Vector3d& B, const Eigen::Vector3d& G) {
  double acosArg = A.dot(B) / (A.norm() * B.norm());
  //  acosArg *= -1;
  double theta = acos(acosArg);
  // Needed because of numerical instabilities provoking theta = nan
  if (acosArg >= 1)
    theta = 0;
  else if (acosArg <= -1)
    theta = 4.0 * atan(1);

  // Invert sign of theta if it should be negative (NB: acos delivers only values between 0 and pi)
  double asinArg = B.cross(A).dot(G) / (A.norm() * B.norm() * G.norm());
  if (asinArg < 0) {
    theta *= -1;
  }

  return theta;
}

DihedralType DihedralTerm::getTypeOfDihedral() const {
  return typeOfDihedral_;
}

int DihedralTerm::getFirstAtom() const {
  return firstAtom_;
}

int DihedralTerm::getSecondAtom() const {
  return secondAtom_;
}

int DihedralTerm::getThirdAtom() const {
  return thirdAtom_;
}

int DihedralTerm::getFourthAtom() const {
  return fourthAtom_;
}

} // namespace MolecularMechanics
} // namespace Scine


#include <cstdio>

#if defined(USE_OPENGL)
    #include <GL/glut.h>
#endif

#include <iostream>

#include <Eigen/Dense>

#include <argparser.h>
#include <easyppm.h>

#include <Color.h>
#include <Material.h>
#include <Light.h>
#include <Sphere.h>
#include <Util.h>

enum class SHADEMODE {
    NONE,
    FLAT,
    GOURAUD,
    PHONG,
};

constexpr int NX = 512;
constexpr int NY = 512;

Color* buffer;
float zbuf[NX][NY];

PPM ppm;

char shade_str[20];
SHADEMODE shade_mode;
void assign_shade_mode() {
    if (strcmp(shade_str, "NONE") == 0)
        shade_mode = SHADEMODE::NONE;
    else if (strcmp(shade_str, "FLAT") == 0)
        shade_mode = SHADEMODE::FLAT;
    else if (strcmp(shade_str, "GOURAUD") == 0)
        shade_mode = SHADEMODE::GOURAUD;
    else
        shade_mode = SHADEMODE::PHONG;
}

void cleanup() {
    delete[] buffer;
}

#if defined(USE_OPENGL)
void gl_display() {
    glClearColor(0,0,0,1);
    glClear(GL_COLOR_BUFFER_BIT);

    float* float_buffer = new float[3*NX*NY];

    int k = 0;
    for (int i = 0; i < NX; i++) {
        for (int j = 0; j < NY; j++) {
            float_buffer[k++] = buffer[j*NY + i].r;
            float_buffer[k++] = buffer[j*NY + i].g;
            float_buffer[k++] = buffer[j*NY + i].b;
        }
    }

    glDrawPixels(NX, NY, GL_RGB, GL_FLOAT, float_buffer);

    glutSwapBuffers();

    delete[] float_buffer;
}

void gl_keyboard(unsigned char key, int x, int y) {
    switch (key) {
        // ESC
        case 27:
            cleanup();
            exit(EXIT_SUCCESS);
    }
}
#endif

int clamp(int x, int min, int max) {
    return std::max(min, std::min(x, max));
}

void draw(int x, int y, const Color& color) {
#if defined(USE_OPENGL)
    buffer[x*NY + y] = color.correct(2.2f);
#else
    auto c = color.correct(2.2f);

    auto r = clamp(c.r * 255, 0, 255);
    auto g = clamp(c.g * 255, 0, 255);
    auto b = clamp(c.b * 255, 0, 255);
    easyppm_set(&ppm, x, y, easyppm_rgb(r,g,b));
#endif
}

template <typename T>
T lerp(T a, T b, T c, float alpha, float beta) {
    return (a-c)*alpha + (b-c)*beta + c;
}

void rasterize(const Triangle& tri, const Light& light, const Material& mat, const Eigen::Matrix4f& M) {
    // Backface culling
    auto v = tri.centroid().normalized();
    if (v.dot(tri.n) > 0)
        return;

    // Convert triangle coordinates from world to viewport
    auto a_vp = vec4to3(M * Eigen::Vector4f(tri.a[0], tri.a[1], tri.a[2], 1.0f));
    auto b_vp = vec4to3(M * Eigen::Vector4f(tri.b[0], tri.b[1], tri.b[2], 1.0f));
    auto c_vp = vec4to3(M * Eigen::Vector4f(tri.c[0], tri.c[1], tri.c[2], 1.0f));

    // Viewport triangle bounds
    auto bounds = Triangle(a_vp, b_vp, c_vp).bounds();

    // Viewport triangle vertex values
    float ax = a_vp[0], ay = a_vp[1], az = a_vp[2];
    float bx = b_vp[0], by = b_vp[1], bz = b_vp[2];
    float cx = c_vp[0], cy = c_vp[1], cz = c_vp[2];

    Eigen::Matrix2f A;
    A << (ax-cx), (bx-cx),
         (ay-cy), (by-cy);

    // Step through viewport bounding box and check whether pixel is in triangle
    for (int y = bounds.ymin; y <= bounds.ymax; y++) {
        for (int x = bounds.xmin; x <= bounds.xmax; x++) {
            // Solve Ax=b for barycentric coordinates
            Eigen::Vector2f bary = A.lu().solve(Eigen::Vector2f(x-cx, y-cy));

            float alpha = bary[0];
            float beta  = bary[1];

            float z = lerp(az, bz, cz, alpha, beta);

            if (alpha >= 0 && beta >= 0 && alpha + beta <= 1 && z > zbuf[x][y]) {
                zbuf[x][y] = z;
                if (shade_mode == SHADEMODE::NONE) {
                    // White
                    draw(x, y, Color::white());
                } else if (shade_mode == SHADEMODE::FLAT) {
                    // Centroid color
                    draw(x, y, tri.shade(tri.centroid(), tri.n, light, mat));
                } else if (shade_mode == SHADEMODE::GOURAUD) {
                    // Vertex colors
                    auto ac = tri.shade(tri.a, tri.an, light, mat);
                    auto bc = tri.shade(tri.b, tri.bn, light, mat);
                    auto cc = tri.shade(tri.c, tri.cn, light, mat);

                    draw(x, y, lerp(ac, bc, cc, alpha, beta));
                } else {
                    // Interpolate position and normal
                    auto p = lerp(tri.a,  tri.b,  tri.c,  alpha, beta);
                    auto n = lerp(tri.an, tri.bn, tri.cn, alpha, beta);

                    draw(x, y, tri.shade(p, n, light, mat));
                }
            }
        }
    }
}

int main(int argc, char* argv[]) {
    // Parse system arguments
    argparser ap = argparser_create(argc, argv, PARSEMODE_LENIENT);
    argparser_add(&ap, "-s", "--shading", ARGTYPE_STRING, &shade_str, "Shade mode");
    argparser_parse(&ap);

    assign_shade_mode();

#ifndef USE_OPENGL
    ppm = easyppm_create(NX, NY, IMAGETYPE_PPM);
#endif

    constexpr float l = -0.1f;
    constexpr float r =  0.1f;
    constexpr float b = -0.1f;
    constexpr float t =  0.1f;
    constexpr float n = -0.1f;
    constexpr float f = -1000.0f;

    Eigen::Matrix4f M, M_world, M_m, M_cam, P, M_orth, M_vp;

    // Modeling transform
    M_m <<     2.0f,            0.0f,           0.0f,           0.0f,
               0.0f,            2.0f,           0.0f,           0.0f,
               0.0f,            0.0f,           2.0f,          -7.0f,
               0.0f,            0.0f,           0.0f,           1.0f;

    // Camera transform
    M_cam <<   1.0f,            0.0f,           0.0f,           0.0f,
               0.0f,            1.0f,           0.0f,           0.0f,
               0.0f,            0.0f,           1.0f,           0.0f,
               0.0f,            0.0f,           0.0f,           1.0f;

    // Perspective transform
    P <<       n,               0.0f,           0.0f,           0.0f,
               0.0f,            n,              0.0f,           0.0f,
               0.0f,            0.0f,           n+f,            -f*n,
               0.0f,            0.0f,           1.0f,           0.0f;

    // Orthographic transform
    M_orth <<  2.0f/(r-l),      0.0f,           0.0f,           -(r+l)/(r-l),
               0.0f,            2.0f/(t-b),     0.0f,           -(t+b)/(t-b),
               0.0f,            0.0f,           2.0f/(n-f),     -(n+f)/(n-f),
               0.0f,            0.0f,           0.0f,           1.0f;

    // Viewport transform
    M_vp <<    NX/2.0f,         0.0f,           0.0f,           (NX-1)/2.0f,
               0.0f,            NY/2.0f,        0.0f,           (NY-1)/2.0f,
               0.0f,            0.0f,           1.0f,           0.0f,
               0.0f,            0.0f,           0.0f,           1.0f;

    // World transform
    M_world = M_cam * M_m;

    // "Viewport" transform
    M = M_vp * M_orth * P;

    // Sphere
    Color ka(0.0f, 1.0f, 0.0f);
    Color kd(0.0f, 0.5f, 0.0f);
    Color ks(0.5f, 0.5f, 0.5f);
    Material mat(ka, kd, ks, 32);
    Sphere sphere(mat, M_world);

    // Light
    Light light(Eigen::Vector3f(-4, 4, -3), 1);

    // Black buffer
    buffer = new Color[NX*NY];
    for (int x = 0; x < NX; x++)
        for (int y = 0; y < NY; y++)
            draw(x, y, Color::black());

    // Z buffer starts at max depth
    for (int x = 0; x < NX; x++)
        for (int y = 0; y < NY; y++)
            zbuf[x][y] = f;

    // Rasterize sphere
    for (const auto& tri : sphere.triangles)
        rasterize(tri, light, mat, M);

#if defined(USE_OPENGL)
    // Write buffer to OpenGL window
    char window_name[50];
    if      (shade_mode == SHADEMODE::NONE)    strcpy(window_name, "Part 1 (Unshaded)");
    else if (shade_mode == SHADEMODE::FLAT)    strcpy(window_name, "Part 2 (Flat Shading)");
    else if (shade_mode == SHADEMODE::GOURAUD) strcpy(window_name, "Part 3 (Gouraud Shading)");
    else                                       strcpy(window_name, "Part 4 (Phong Shading)");
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
    glutInitWindowSize(NX, NY);
    glutCreateWindow(window_name);
    glutDisplayFunc(gl_display);
    glutKeyboardFunc(gl_keyboard);
    glutMainLoop();
#else
    // Write buffer to image file
    char ppmpath[50];
    if      (shade_mode == SHADEMODE::NONE)    strcpy(ppmpath, "images/part1-unshaded.ppm");
    else if (shade_mode == SHADEMODE::FLAT)    strcpy(ppmpath, "images/part2-flat.ppm");
    else if (shade_mode == SHADEMODE::GOURAUD) strcpy(ppmpath, "images/part3-gouraud.ppm");
    else                                       strcpy(ppmpath, "images/part4-phong.ppm");

    easyppm_invert_y(&ppm);
    easyppm_write(&ppm, ppmpath);
    easyppm_destroy(&ppm);
#endif

    cleanup();

    return EXIT_SUCCESS;
}


#pragma once

#include "GNAObject.hh"
#include "GNAObjectBind1N.hh"
#include <Eigen/Dense>

class Integrator21Base : public GNAObjectBind1N<double>,
                         public TransformationBind<Integrator21Base>
{
public:
  TransformationDescriptor add_transformation(const std::string& name="");
  void set_edges(OutputDescriptor& hist2_output);

  void dump();

protected:
  Integrator21Base(size_t xbins, int  xorders, double* edges, int yorder, double ymin, double ymax);
  Integrator21Base(size_t xbins, int* xorders, double* edges, int yorder, double ymin, double ymax);

  Eigen::ArrayXi m_xorders;
  Eigen::ArrayXd m_xedges;
  Eigen::ArrayXd m_xweights;

  int m_yorder;
  double m_ymin;
  double m_ymax;
  Eigen::ArrayXd m_yweights;

  Eigen::ArrayXXd m_weights;

  virtual void sample(FunctionArgs&)=0;

protected:
  void init_sampler();
  void check_sampler(TypesFunctionArgs& fargs);

private:
  void init_base(double* xedges);
  void check_base(TypesFunctionArgs&);
  void integrate(FunctionArgs&);
};


//
// Copyright 2010 Scott McMurray.
// 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_HASH_SHA2_HPP
#define BOOST_HASH_SHA2_HPP

#include <boost/hash/block_cyphers/shacal2.hpp>
#include <boost/hash/davies_meyer_compressor.hpp>
#include <boost/hash/detail/sha2_policy.hpp>
#include <boost/hash/detail/state_adder.hpp>
#include <boost/hash/merkle_damgard_block_hash.hpp>
#include <boost/hash/stream_preprocessor.hpp>

namespace boost {
namespace hashes {

template <unsigned Version>
struct sha2 {
  private:
    typedef detail::sha2_policy<Version> policy_type;
    typedef block_cyphers::shacal2<policy_type::cypher_version>
            block_cypher_type;
  public:
    typedef merkle_damgard_block_hash<
                stream_endian::big_octet_big_bit,
                policy_type::digest_bits,
                typename policy_type::iv_generator,
                davies_meyer_compressor<block_cypher_type,
                                        detail::state_adder>
            > block_hash_type_;
#ifdef BOOST_HASH_NO_HIDE_INTERNAL_TYPES
    typedef block_hash_type_ block_hash_type;
#else
    struct block_hash_type : block_hash_type_ {};
#endif
    template <unsigned value_bits>
    struct stream_hash {
        typedef stream_preprocessor<
                    stream_endian::big_octet_big_bit,
                    value_bits,
                    block_hash_type::word_bits * 2,
                    block_hash_type
                > type_;
#ifdef BOOST_HASH_NO_HIDE_INTERNAL_TYPES
        typedef type_ type;
#else
        struct type : type_ {};
#endif
    };
    typedef typename block_hash_type::digest_type digest_type;
};

} // namespace hashes
} // namespace boost

#endif // BOOST_HASH_SHA2_HPP