// Copyright 2016-2019 Doug Moen
// Licensed under the Apache License, version 2.0
// See accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0

#include <libcurv/builtin.h>

#include <libcurv/analyser.h>
#include <libcurv/array_op.h>
#include <libcurv/die.h>
#include <libcurv/dir_record.h>
#include <libcurv/exception.h>
#include <libcurv/function.h>
#include <libcurv/sc_compiler.h>
#include <libcurv/sc_context.h>
#include <libcurv/import.h>
#include <libcurv/math.h>
#include <libcurv/pattern.h>
#include <libcurv/picker.h>
#include <libcurv/program.h>
#include <libcurv/source.h>
#include <libcurv/system.h>
#include <libcurv/typeconv.h>

#include <boost/math/constants/constants.hpp>
#include <boost/filesystem.hpp>

#include <cassert>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <string>

using namespace std;
using namespace boost::math::double_constants;

namespace curv {

Shared<Meaning>
Builtin_Value::to_meaning(const Identifier& id) const
{
    return make<Constant>(share(id), value_);
}

//----------------------------------------------//
// Templates for constructing builtin functions //
//----------------------------------------------//

template <class Prim>
struct Unary_Array_Func : public Function
{
    static const char* name() { return Prim::name(); }
    Unary_Array_Func() : Function(name()) {}
    static Unary_Array_Op<Prim> array_op;
    Value call(Value arg, Frame& f) override
    {
        return array_op.op(At_Arg(*this, f), arg);
    }
    SC_Value sc_call_expr(Operation& argx, Shared<const Phrase> ph, SC_Frame& f)
    const override
    {
        return array_op.sc_op(At_SC_Arg_Expr(*this, ph, f), argx, f);
    }
};

template <class Prim>
struct Binary_Array_Func : public Legacy_Function
{
    static const char* name() { return Prim::name(); }
    Binary_Array_Func() : Legacy_Function(2,name()) {}
    static Binary_Array_Op<Prim> array_op;
    Value call(Frame& args) override
    {
        return array_op.op(At_Arg(*this, args), args[0], args[1]);
    }
    SC_Value sc_call_expr(Operation& argx, Shared<const Phrase> ph, SC_Frame& f)
    const override
    {
        return array_op.sc_op(At_SC_Arg_Expr(*this, ph, f), argx, f);
    }
};

template <class Prim>
struct Monoid_Func final : public Function
{
    static const char* name() { return Prim::name(); }
    Monoid_Func() : Function(name()) {}
    static Binary_Array_Op<Prim> array_op;
    Value call(Value arg, Frame& f) override
    {
        return array_op.reduce(At_Arg(*this, f), Prim::zero(), arg);
    }
    SC_Value sc_call_expr(Operation& argx, Shared<const Phrase> ph, SC_Frame& f)
    const override
    {
        return array_op.sc_reduce(At_SC_Arg_Expr(*this, ph, f),
            Prim::zero(), argx, f);
    }
};

//-------------------//
// Builtin Functions //
//-------------------//

struct Is_Bool_Function : public Function
{
    static const char* name() { return "is_bool"; }
    Is_Bool_Function() : Function(name()) {}
    Value call(Value arg, Frame&) override
    {
        return {isbool(arg)};
    }
};
struct Is_Symbol_Function : public Function
{
    static const char* name() { return "is_symbol"; }
    Is_Symbol_Function() : Function(name()) {}
    Value call(Value arg, Frame&) override
    {
        return {issymbol(arg)};
    }
};
struct Is_Num_Function : public Function
{
    static const char* name() { return "is_num"; }
    Is_Num_Function() : Function(name()) {}
    Value call(Value arg, Frame&) override
    {
        return {isnum(arg)};
    }
};
struct Is_String_Function : public Function
{
    static const char* name() { return "is_string"; }
    Is_String_Function() : Function(name()) {}
    Value call(Value arg, Frame&) override
    {
        return {arg.dycast<String>() != nullptr};
    }
};
struct Is_List_Function : public Function
{
    static const char* name() { return "is_list"; }
    Is_List_Function() : Function(name()) {}
    Value call(Value arg, Frame&) override
    {
        return {islist(arg)};
    }
};
struct Is_Record_Function : public Function
{
    static const char* name() { return "is_record"; }
    Is_Record_Function() : Function(name()) {}
    Value call(Value arg, Frame&) override
    {
        return {arg.dycast<Record>() != nullptr};
    }
};
struct Is_Fun_Function : public Function
{
    static const char* name() { return "is_fun"; }
    Is_Fun_Function() : Function(name()) {}
    Value call(Value arg, Frame&) override
    {
        return {arg.dycast<Function>() != nullptr};
    }
};

struct Bit_Prim : public Unary_Bool_Prim
{
    static const char* name() { return "bit"; }
    static Value call(bool b, const Context&)
    {
        return {double(b)};
    }
    static SC_Value sc_call(SC_Frame& f, SC_Value arg)
    {
        auto result = f.sc_.newvalue(SC_Type::Num());
        f.sc_.out() << "  float "<<result<<" = float("<<arg<<");\n";
        return result;
    }
};
using Bit_Function = Unary_Array_Func<Bit_Prim>;

#define UNARY_NUMERIC_FUNCTION(Class_Name,curv_name,c_name,glsl_name) \
struct Class_Name : public Legacy_Function \
{ \
    static const char* name() { return #curv_name; } \
    Class_Name() : Legacy_Function(1,name()) {} \
    struct Prim { \
        static double call(double x) { return c_name(x); } \
        Shared<Operation> make_expr(Shared<Operation> x) const \
        { \
            return make<Call_Expr>( \
                cx.call_frame_.call_phrase_, \
                make<Constant>( \
                    func_part(cx.call_frame_.call_phrase_), \
                    Value{share(cx.func_)}), \
                x); \
        } \
        static Shared<const String> callstr(Value x) { \
            return stringify(x); \
        } \
        At_Arg cx; \
        Prim(Function& func, Frame& args) : cx(func,args) {} \
    }; \
    static Unary_Numeric_Array_Op<Prim> array_op; \
    Value call(Frame& args) override \
    { \
        return array_op.op(Prim(*this, args), args[0]); \
    } \
    SC_Value sc_call_legacy(SC_Frame& f) const override \
    { \
        return sc_call_unary_numeric(f, #glsl_name); \
    } \
}; \

UNARY_NUMERIC_FUNCTION(Sqrt_Function, sqrt, sqrt, sqrt)
UNARY_NUMERIC_FUNCTION(Log_Function, log, log, log)
UNARY_NUMERIC_FUNCTION(Abs_Function, abs, abs, abs)
UNARY_NUMERIC_FUNCTION(Floor_Function, floor, floor, floor)
UNARY_NUMERIC_FUNCTION(Ceil_Function, ceil, ceil, ceil)
UNARY_NUMERIC_FUNCTION(Trunc_Function, trunc, trunc, trunc)
UNARY_NUMERIC_FUNCTION(Round_Function, round, rint, roundEven)

inline double frac(double n) { return n - floor(n); }
UNARY_NUMERIC_FUNCTION(Frac_Function, frac, frac, fract)

UNARY_NUMERIC_FUNCTION(Sin_Function, sin, sin, sin)
UNARY_NUMERIC_FUNCTION(Cos_Function, cos, cos, cos)
UNARY_NUMERIC_FUNCTION(Tan_Function, tan, tan, tan)
UNARY_NUMERIC_FUNCTION(Acos_Function, acos, acos, acos)
UNARY_NUMERIC_FUNCTION(Asin_Function, asin, asin, asin)
UNARY_NUMERIC_FUNCTION(Atan_Function, atan, atan, atan)

UNARY_NUMERIC_FUNCTION(Sinh_Function, sinh, sinh, sinh)
UNARY_NUMERIC_FUNCTION(Cosh_Function, cosh, cosh, cosh)
UNARY_NUMERIC_FUNCTION(Tanh_Function, tanh, tanh, tanh)
UNARY_NUMERIC_FUNCTION(Acosh_Function, acosh, acosh, acosh)
UNARY_NUMERIC_FUNCTION(Asinh_Function, asinh, asinh, asinh)
UNARY_NUMERIC_FUNCTION(Atanh_Function, atanh, atanh, atanh)

struct Atan2_Function : public Legacy_Function
{
    static const char* name() { return "atan2"; }
    Atan2_Function() : Legacy_Function(2,name()) {}

    struct Prim {
        static double call(double x, double y) { return atan2(x, y); }
        Shared<Operation> make_expr(
            Shared<Operation> x, Shared<Operation> y) const
        {
            throw Exception(cx,
                "Internal error: atan2 applied to a reactive value");
            //return make<Divide_Expr>(share(syntax), std::move(x), std::move(y));
        }
        static const char* name() { return "atan2"; }
        static Shared<const String> callstr(Value x, Value y) {
            return stringify("[",x,",",y,"]");
        }
        At_Arg cx;
        Prim(Function& func, Frame& args) : cx(func, args) {}
    };
    static Binary_Numeric_Array_Op<Prim> array_op;
    Value call(Frame& args) override
    {
        return array_op.op(Prim(*this, args), args[0], args[1]);
    }
    SC_Value sc_call_legacy(SC_Frame& f) const override
    {
        auto x = f[0];
        auto y = f[1];

        SC_Type rtype = SC_Type::Bool();
        if (x.type == y.type)
            rtype = x.type;
        else if (x.type == SC_Type::Num())
            rtype = y.type;
        else if (y.type == SC_Type::Num())
            rtype = x.type;
        if (rtype == SC_Type::Bool())
            throw Exception(At_SC_Phrase(f.call_phrase_, f),
                "domain error");

        SC_Value result = f.sc_.newvalue(rtype);
        f.sc_.out() <<"  "<<rtype<<" "<<result<<" = atan(";
        sc_put_as(f, x, At_SC_Arg(0, f), rtype);
        f.sc_.out() << ",";
        sc_put_as(f, y, At_SC_Arg(1, f), rtype);
        f.sc_.out() << ");\n";
        return result;
    }
};

SC_Value sc_minmax(const char* name, Operation& argx, SC_Frame& f)
{
    auto list = dynamic_cast<List_Expr*>(&argx);
    if (list) {
        std::list<SC_Value> args;
        SC_Type type = SC_Type::Num();
        for (auto op : *list) {
            auto val = sc_eval_op(f, *op);
            args.push_back(val);
            if (val.type == SC_Type::Num())
                ;
            else if (val.type.is_vec()) {
                if (type == SC_Type::Num())
                    type = val.type;
                else if (type != val.type)
                    throw Exception(At_SC_Phrase(op->syntax_, f), stringify(
                        name, ": vector arguments of different lengths"));
            } else {
                throw Exception(At_SC_Phrase(op->syntax_, f), stringify(
                    name,": argument has bad type"));
            }
        }
        auto result = f.sc_.newvalue(type);
        if (args.size() == 0) {
            // TODO: BUG: this only works for 'max'. min requires +inf.
            f.sc_.out() << "  " << type << " " << result << " = -0.0/0.0;\n";
        }
        else if (args.size() == 1)
            return args.front();
        else {
            f.sc_.out() << "  " << type << " " << result << " = ";
            int rparens = 0;
            while (args.size() > 2) {
                f.sc_.out() << name << "(" << args.front() << ",";
                args.pop_front();
                ++rparens;
            }
            f.sc_.out() << name << "(" << args.front() << "," << args.back() << ")";
            while (rparens > 0) {
                f.sc_.out() << ")";
                --rparens;
            }
            f.sc_.out() << ";\n";
        }
        return result;
    } else {
        auto arg = sc_eval_op(f, argx);
        auto result = f.sc_.newvalue(SC_Type::Num());
        f.sc_.out() << "  float "<<result<<" = ";
        if (arg.type == SC_Type::Vec(2))
            f.sc_.out() << name <<"("<<arg<<".x,"<<arg<<".y);\n";
        else if (arg.type == SC_Type::Vec(3))
            f.sc_.out() << name<<"("<<name<<"("<<arg<<".x,"<<arg<<".y),"
                <<arg<<".z);\n";
        else if (arg.type == SC_Type::Vec(4))
            f.sc_.out() << name<<"("<<name<<"("<<name<<"("<<arg<<".x,"<<arg<<".y),"
                <<arg<<".z),"<<arg<<".w);\n";
        else
            throw Exception(At_SC_Phrase(argx.syntax_, f), stringify(
                name,": argument is not a vector"));
        return result;
    }
}

struct Max_Function : public Legacy_Function
{
    static const char* name() { return "max"; }
    Max_Function() : Legacy_Function(1,name()) {}

    struct Prim {
        static double call(double x, double y) {
            // return NaN if either argument is NaN.
            if (x >= y) return x;
            if (x < y) return y;
            return 0.0/0.0;
        }
        Shared<Operation> make_expr(
            Shared<Operation> x, Shared<Operation> y) const
        {
            Shared<List_Expr> args =
                List_Expr::make({x, y}, arg_part(cx.call_frame_.call_phrase_));
            args->init();
            return make<Call_Expr>(
                cx.call_frame_.call_phrase_,
                make<Constant>(
                    func_part(cx.call_frame_.call_phrase_),
                    Value{share(cx.func_)}),
                args);
        }
        static const char* name() { return "max"; }
        static Shared<const String> callstr(Value x, Value y) {
            return stringify("[",x,",",y,"]");
        }
        At_Arg cx;
        Prim(Function& func, Frame& args) : cx(func,args) {}
    };
    static Binary_Numeric_Array_Op<Prim> array_op;
    Value call(Frame& args) override
    {
        return array_op.reduce(Prim(*this, args), -INFINITY, args[0]);
    }
    SC_Value sc_call_expr(Operation& argx, Shared<const Phrase>, SC_Frame& f)
    const override
    {
        return sc_minmax(name(),argx,f);
    }
};

struct Min_Function : public Legacy_Function
{
    static const char* name() { return "min"; }
    Min_Function() : Legacy_Function(1,name()) {}

    struct Prim {
        static double call(double x, double y) {
            // return NaN if either argument is NaN
            if (x <= y) return x;
            if (x > y) return y;
            return 0.0/0.0;
        }
        Shared<Operation> make_expr(
            Shared<Operation> x, Shared<Operation> y) const
        {
            Shared<List_Expr> args =
                List_Expr::make({x, y}, arg_part(cx.call_frame_.call_phrase_));
            args->init();
            return make<Call_Expr>(
                cx.call_frame_.call_phrase_,
                make<Constant>(
                    func_part(cx.call_frame_.call_phrase_),
                    Value{share(cx.func_)}),
                args);
        }
        static const char* name() { return "min"; }
        static Shared<const String> callstr(Value x, Value y) {
            return stringify("[",x,",",y,"]");
        }
        At_Arg cx;
        Prim(Function& func, Frame& args) : cx(func, args) {}
    };
    static Binary_Numeric_Array_Op<Prim> array_op;
    Value call(Frame& args) override
    {
        return array_op.reduce(Prim(*this, args), INFINITY, args[0]);
    }
    SC_Value sc_call_expr(Operation& argx, Shared<const Phrase>, SC_Frame& f)
    const override
    {
        return sc_minmax("min",argx,f);
    }
};

struct Sum_Prim : public Binary_Num_Prim
{
    static const char* name() { return "sum"; }
    static Value zero() { return {0.0}; }
    static Value call(double x, double y, const Context&) { return {x + y}; }
    static SC_Value sc_call(SC_Frame& f, SC_Value x, SC_Value y)
    {
        auto result = f.sc_.newvalue(x.type);
        f.sc_.out() << "  " << result.type << " " << result << " = "
            << x << "+" << y << ";\n";
        return result;
    }
};
using Sum_Function = Monoid_Func<Sum_Prim>;

struct And_Prim : public Binary_Bool_Or_Bool32_Prim
{
    static const char* name() { return "and"; }
    static Value zero() { return {true}; }
    static Value call(bool x, bool y, const Context&) { return {x && y}; }
    static SC_Value sc_call(SC_Frame& f, SC_Value x, SC_Value y)
    {
        auto result = f.sc_.newvalue(x.type);
        f.sc_.out() << "  " << x.type << " " << result << " = " << x
            << (x.type.is_bool32() ? "&" : "&&")
            << y << ";\n";
        return result;
    }
};
using And_Function = Monoid_Func<And_Prim>;

struct Or_Prim : public Binary_Bool_Or_Bool32_Prim
{
    static const char* name() { return "or"; }
    static Value zero() { return {false}; }
    static Value call(bool x, bool y, const Context&) { return {x || y}; }
    static SC_Value sc_call(SC_Frame& f, SC_Value x, SC_Value y)
    {
        auto result = f.sc_.newvalue(x.type);
        f.sc_.out() << "  " << x.type << " " << result << " = " << x
            << (x.type.is_bool32() ? "|" : "||")
            << y << ";\n";
        return result;
    }
};
using Or_Function = Monoid_Func<Or_Prim>;

struct Xor_Prim : public Binary_Bool_Or_Bool32_Prim
{
    static const char* name() { return "xor"; }
    static Value zero() { return {false}; }
    static Value call(bool x, bool y, const Context&) { return {x != y}; }
    static SC_Value sc_call(SC_Frame& f, SC_Value x, SC_Value y)
    {
        auto result = f.sc_.newvalue(x.type);
        f.sc_.out() << "  " << x.type << " " << result << " = ";
        if (x.type.is_bool32())
            f.sc_.out() << x << "^" << y;
        else if (x.type == SC_Type::Bool())
            f.sc_.out() << x << "!=" << y;
        else if (x.type.is_bool())
            f.sc_.out() << "notEqual(" << x << "," << y << ")";
        else
            die("Xor_Prim::sc_call: unknown type");
        f.sc_.out() << ";\n";
        return result;
    }
};
using Xor_Function = Monoid_Func<Xor_Prim>;

struct Lshift_Prim : public Shift_Prim
{
    static const char* name() { return "lshift"; }
    static Value call(Shared<const List> a, double b, const Context &cx)
    {
        At_Index acx(0, cx);
        At_Index bcx(1, cx);
        unsigned n = (unsigned) num_to_int(b, 0, a->size()-1, bcx);
        Shared<List> result = List::make(a->size());
        for (unsigned i = 0; i < n; ++i)
            result->at(i) = {false};
        for (unsigned i = n; i < a->size(); ++i)
            result->at(i) = {a->at(i-n).to_bool(acx)};
        return {result};
    }
    static SC_Value sc_call(SC_Frame& f, SC_Value x, SC_Value y)
    {
        auto result = f.sc_.newvalue(x.type);
        f.sc_.out() << "  " << x.type << " " << result << " = "
            << x << " << int(" << y << ");\n";
        return result;
    }
};
using Lshift_Function = Binary_Array_Func<Lshift_Prim>;

struct Rshift_Prim : public Shift_Prim
{
    static const char* name() { return "rshift"; }
    static Value call(Shared<const List> a, double b, const Context &cx)
    {
        At_Index acx(0, cx);
        At_Index bcx(1, cx);
        unsigned n = (unsigned) num_to_int(b, 0, a->size()-1, bcx);
        Shared<List> result = List::make(a->size());
        for (unsigned i = a->size()-n; i < a->size(); ++i)
            result->at(i) = {false};
        for (unsigned i = 0; i < a->size()-n; ++i)
            result->at(i) = {a->at(i+n).to_bool(acx)};
        return {result};
    }
    static SC_Value sc_call(SC_Frame& f, SC_Value x, SC_Value y)
    {
        auto result = f.sc_.newvalue(x.type);
        f.sc_.out() << "  " << x.type << " " << result << " = "
            << x << " >> int(" << y << ");\n";
        return result;
    }
};
using Rshift_Function = Binary_Array_Func<Rshift_Prim>;

struct Bool32_Sum_Prim : public Binary_Bool32_Prim
{
    static const char* name() { return "bool32_sum"; }
    static Value zero()
    {
        static Value z = {nat_to_bool32(0)};
        return z;
    }
    static Value call(unsigned a, unsigned b, const Context&)
    {
        return {nat_to_bool32(a + b)};
    }
    static SC_Value sc_call(SC_Frame& f, SC_Value x, SC_Value y)
    {
        auto result = f.sc_.newvalue(x.type);
        f.sc_.out() << "  " << x.type << " " << result << " = "
            << x << " + " << y << ";\n";
        return result;
    }
};
using Bool32_Sum_Function = Monoid_Func<Bool32_Sum_Prim>;

struct Bool32_Product_Prim : public Binary_Bool32_Prim
{
    static const char* name() { return "bool32_product"; }
    static Value zero()
    {
        static Value z = {nat_to_bool32(1)};
        return z;
    }
    static Value call(unsigned a, unsigned b, const Context&)
    {
        return {nat_to_bool32(a * b)};
    }
    static SC_Value sc_call(SC_Frame& f, SC_Value x, SC_Value y)
    {
        auto result = f.sc_.newvalue(x.type);
        f.sc_.out() << "  " << x.type << " " << result << " = "
            << x << " * " << y << ";\n";
        return result;
    }
};
using Bool32_Product_Function = Monoid_Func<Bool32_Product_Prim>;

struct Bool32_To_Nat_Function : public Function
{
    static const char* name() { return "bool32_to_nat"; }
    Bool32_To_Nat_Function() : Function(name()) {}
    struct Prim : public Unary_Bool32_Prim
    {
        static Value call(unsigned n, const Context&)
        {
            return {double(n)};
        }
        // No SubCurv support because a Num (32 bit float)
        // cannot hold a Nat (32 bit natural).
    };
    static Unary_Array_Op<Prim> array_op;
    Value call(Value arg, Frame& f) override
    {
        return array_op.op(At_Arg(*this, f), arg);
    }
};

struct Nat_To_Bool32_Function : public Function
{
    static const char* name() { return "nat_to_bool32"; }
    Nat_To_Bool32_Function() : Function(name()) {}
    struct Prim : public Unary_Num_Prim
    {
        static Value call(double n, const Context& cx)
        {
            return {nat_to_bool32(num_to_nat(n, cx))};
        }
    };
    static Unary_Array_Op<Prim> array_op;
    Value call(Value arg, Frame& f) override
    {
        return array_op.op(At_Arg(*this, f), arg);
    }
    SC_Value sc_call_expr(Operation& argx, Shared<const Phrase> ph, SC_Frame& f)
    const override
    {
        At_SC_Arg_Expr cx(*this, ph, f);
        if (auto k = dynamic_cast<const Constant*>(&argx)) {
            unsigned n = num_to_nat(k->value_.to_num(cx), cx);
            auto type = SC_Type::Bool32();
            auto result = f.sc_.newvalue(type);
            f.sc_.out() << "  " << type << " " << result << " = "
                << n << "u;\n";
            return result;
        }
        else {
            throw Exception(cx, "argument must be a constant");
        }
    }
};

struct Bool32_To_Float_Prim : public Unary_Bool32_Prim
{
    static const char* name() { return "bool32_to_float"; }
    static Value call(unsigned n, const Context&)
    {
        return {nat_to_float(n)};
    }
    static SC_Value sc_call(SC_Frame& f, SC_Value x)
    {
        unsigned count = x.type == SC_Type::Bool32() ? 1 : x.type.count();
        auto result = f.sc_.newvalue(SC_Type::Num_Or_Vec(count));
        f.sc_.out() << "  " << result.type << " " << result
            << " = uintBitsToFloat(" << x << ");\n";
        return result;
    }
};
using Bool32_To_Float_Function = Unary_Array_Func<Bool32_To_Float_Prim>;

struct Float_To_Bool32_Prim : public Unary_Num_Prim
{
    static const char* name() { return "float_to_bool32"; }
    static Value call(double n, const Context&)
    {
        return {nat_to_bool32(float_to_nat(n))};
    }
    static SC_Value sc_call(SC_Frame& f, SC_Value x)
    {
        auto result = f.sc_.newvalue(SC_Type::Bool32(x.type.count()));
        f.sc_.out() << "  " << result.type << " " << result
            << " = floatBitsToUint(" << x << ");\n";
        return result;
    }
};
using Float_To_Bool32_Function = Unary_Array_Func<Float_To_Bool32_Prim>;

// Generalized dot product that includes vector dot product and matrix product.
// Same as Mathematica Dot[A,B]. Like APL A+.×B, Python numpy.dot(A,B)
struct Dot_Function : public Legacy_Function
{
    static const char* name() { return "dot"; }
    Dot_Function() : Legacy_Function(2,name()) {}
    Value call(Frame& args) override
    {
        return dot(args[0], args[1], At_Arg(*this, args));
    }
    SC_Value sc_call_legacy(SC_Frame& f) const override
    {
        auto a = f[0];
        auto b = f[1];
        if (!a.type.is_vec())
            throw Exception(At_SC_Arg(0, f), "dot: argument is not a vector");
        if (a.type != b.type)
            throw Exception(At_SC_Arg(1, f), "dot: arguments have different types");
        auto result = f.sc_.newvalue(SC_Type::Num());
        f.sc_.out() << "  float "<<result<<" = dot("<<a<<","<<b<<");\n";
        return result;
    }
};

struct Mag_Function : public Legacy_Function
{
    static const char* name() { return "mag"; }
    Mag_Function() : Legacy_Function(1,name()) {}
    Value call(Frame& args) override
    {
        // TODO: use hypot() or BLAS DNRM2 or Eigen stableNorm/blueNorm?
        // Avoids overflow/underflow due to squaring of large/small values.
        // Slower.  https://forum.kde.org/viewtopic.php?f=74&t=62402
        auto list = args[0].to<List>(At_Arg(*this, args));
        // Fast path: assume we have a list of number, compute a result.
        double sum = 0.0;
        for (auto val : *list) {
            double x = val.to_num_or_nan();
            sum += x * x;
        }
        if (sum == sum)
            return {sqrt(sum)};
        // The computation failed. Second fastest path: assume a mix of numbers
        // and reactive numbers, try to return a reactive result.
        Shared<List_Expr> rlist =
            List_Expr::make(list->size(),arg_part(args.call_phrase_));
        for (unsigned i = 0; i < list->size(); ++i) {
            Value val = list->at(i);
            if (val.is_num()) {
                rlist->at(i) = make<Constant>(arg_part(args.call_phrase_), val);
                continue;
            }
            auto r = val.dycast<Reactive_Value>();
            if (r && r->sctype_ == SC_Type::Num()) {
                rlist->at(i) = r->expr(*arg_part(args.call_phrase_));
                continue;
            }
            rlist = nullptr;
            break;
        }
        if (rlist) {
            rlist->init();
            return {make<Reactive_Expression>(
                SC_Type::Num(),
                make<Call_Expr>(
                    args.call_phrase_,
                    make<Constant>(
                        func_part(args.call_phrase_),
                        Value{share(*this)}),
                    rlist),
                At_Arg(*this, args))};
        }
        throw Exception(At_Arg(*this, args),
            stringify(args[0],": domain error"));
    }
    SC_Value sc_call_legacy(SC_Frame& f) const override
    {
        auto arg = f[0];
        if (!arg.type.is_vec())
            throw Exception(At_SC_Arg(0, f), "mag: argument is not a vector");
        auto result = f.sc_.newvalue(SC_Type::Num());
        f.sc_.out() << "  float "<<result<<" = length("<<arg<<");\n";
        return result;
    }
};

struct Count_Function : public Legacy_Function
{
    static const char* name() { return "count"; }
    Count_Function() : Legacy_Function(1,name()) {}
    Value call(Frame& args) override
    {
        if (auto list = args[0].dycast<const List>())
            return {double(list->size())};
        if (auto string = args[0].dycast<const String>())
            return {double(string->size())};
        if (auto re = args[0].dycast<const Reactive_Value>()) {
            if (re->sctype_.is_list())
                return {double(re->sctype_.count())};
            //TODO:
            //if (re->sctype_ == SC_Type::Any())
        }
        throw Exception(At_Arg(*this, args), "not a list or string");
    }
    SC_Value sc_call_legacy(SC_Frame& f) const override
    {
        auto arg = f[0];
        if (!arg.type.is_list())
            throw Exception(At_SC_Arg(0, f), "count: argument is not a list");
        auto result = f.sc_.newvalue(SC_Type::Num());
        f.sc_.out() << "  float "<<result<<" = "<<arg.type.count()<<";\n";
        return result;
    }
};
struct Fields_Function : public Legacy_Function
{
    static const char* name() { return "fields"; }
    Fields_Function() : Legacy_Function(1,name()) {}
    Value call(Frame& args) override
    {
        if (auto record = args[0].dycast<const Record>())
            return {record->fields()};
        throw Exception(At_Arg(*this, args), "not a record");
    }
};

struct Strcat_Function : public Legacy_Function
{
    static const char* name() { return "strcat"; }
    Strcat_Function() : Legacy_Function(1,name()) {}
    Value call(Frame& args) override
    {
        if (auto list = args[0].dycast<const List>()) {
            String_Builder sb;
            for (auto val : *list) {
                if (auto str = val.dycast<const String_or_Symbol>())
                    sb << *str;
                else if (val.is_bool())
                    sb << (val.to_bool_unsafe() ? "true" : "false");
                else
                    sb << val;
            }
            return {sb.get_string()};
        }
        throw Exception(At_Arg(*this, args), "not a list");
    }
};
struct Repr_Function : public Legacy_Function
{
    static const char* name() { return "repr"; }
    Repr_Function() : Legacy_Function(1,name()) {}
    Value call(Frame& args) override
    {
        String_Builder sb;
        sb << args[0];
        return {sb.get_string()};
    }
};
struct Decode_Function : public Legacy_Function
{
    static const char* name() { return "decode"; }
    Decode_Function() : Legacy_Function(1,name()) {}
    Value call(Frame& f) override
    {
        String_Builder sb;
        At_Arg cx(*this, f);
        auto list = f[0].to<List>(cx);
        for (size_t i = 0; i < list->size(); ++i)
            sb << (char)(*list)[i].to_int(1, 127, At_Index(i,cx));
        return {sb.get_string()};
    }
};
struct Encode_Function : public Legacy_Function
{
    static const char* name() { return "encode"; }
    Encode_Function() : Legacy_Function(1,name()) {}
    Value call(Frame& f) override
    {
        List_Builder lb;
        At_Arg cx(*this, f);
        auto str = f[0].to<String>(cx);
        for (size_t i = 0; i < str->size(); ++i)
            lb.push_back({(double)(int)str->at(i)});
        return {lb.get_list()};
    }
};

struct Match_Function : public Legacy_Function
{
    static const char* name() { return "match"; }
    Match_Function() : Legacy_Function(1,name()) {}
    Value call(Frame& f) override
    {
        At_Arg ctx0(*this, f);
        auto list = f[0].to<List>(ctx0);
        std::vector<Shared<Function>> cases;
        for (size_t i = 0; i < list->size(); ++i)
            cases.push_back(list->at(i).to<Function>(At_Index(i,ctx0)));
        auto mf = make<Piecewise_Function>(cases);
        mf->name_ = name_;
        mf->argpos_ = 1;
        return {mf};
    }
};

// The filename argument to "file", if it is a relative filename,
// is interpreted relative to the parent directory of the source file from
// which "file" is called.
//
// Because "file" has this hidden parameter (the name of the source file from
// which it is called), it is not a pure function. For this reason, it isn't
// a function value at all, it's a metafunction.
struct File_Expr : public Just_Expression
{
    Shared<Operation> arg_;
    File_Expr(Shared<const Call_Phrase> src, Shared<Operation> arg)
    :
        Just_Expression(std::move(src)),
        arg_(std::move(arg))
    {}
    virtual Value eval(Frame& f) const override
    {
        // Metafunction calls do not automatically get a new stack frame
        // allocated. But I want the call to `file pathname` to appear
        // in stack traces, so I need a Frame.
        auto& callphrase = dynamic_cast<const Call_Phrase&>(*syntax_);
        std::unique_ptr<Frame> f2 =
            Frame::make(0, f.system_, &f, &callphrase, nullptr);
        At_Metacall_With_Call_Frame cx("file", 0, *f2);

        // construct file pathname from argument
        Value arg = arg_->eval(f);
        auto argstr = arg.to<String>(cx);
        namespace fs = boost::filesystem;
        fs::path filepath;
        auto caller_filename = syntax_->location().source().name_;
        if (caller_filename->empty()) {
            filepath = fs::path(argstr->c_str());
        } else {
            filepath = fs::path(caller_filename->c_str()).parent_path()
                / fs::path(argstr->c_str());
        }

        return import(filepath, cx);
    }
};
struct File_Metafunction : public Metafunction
{
    using Metafunction::Metafunction;
    virtual Shared<Meaning> call(const Call_Phrase& ph, Environ& env) override
    {
        return make<File_Expr>(share(ph), analyse_op(*ph.arg_, env));
    }
};

/// The meaning of a call to `print`, such as `print "foo"`.
struct Print_Action : public Operation
{
    Shared<Operation> arg_;
    Print_Action(
        Shared<const Phrase> syntax,
        Shared<Operation> arg)
    :
        Operation(std::move(syntax)),
        arg_(std::move(arg))
    {}
    virtual void exec(Frame& f, Executor&) const override
    {
        Value arg = arg_->eval(f);
        auto str = arg.dycast<String>();
        if (str == nullptr)
            str = stringify(arg);
        f.system_.print(str->c_str());
    }
};
/// The meaning of the phrase `print` in isolation.
struct Print_Metafunction : public Metafunction
{
    using Metafunction::Metafunction;
    virtual Shared<Meaning> call(const Call_Phrase& ph, Environ& env) override
    {
        return make<Print_Action>(share(ph), analyse_op(*ph.arg_, env));
    }
};

struct Warning_Action : public Operation
{
    Shared<Operation> arg_;
    Warning_Action(
        Shared<const Phrase> syntax,
        Shared<Operation> arg)
    :
        Operation(std::move(syntax)),
        arg_(std::move(arg))
    {}
    virtual void exec(Frame& f, Executor&) const override
    {
        Value arg = arg_->eval(f);
        Shared<String> msg;
        if (auto str = arg.dycast<String>())
            msg = str;
        else
            msg = stringify(arg);
        Exception exc{At_Phrase(*syntax_, f), msg};
        f.system_.warning(exc);
    }
};
/// The meaning of the phrase `warning` in isolation.
struct Warning_Metafunction : public Metafunction
{
    using Metafunction::Metafunction;
    virtual Shared<Meaning> call(const Call_Phrase& ph, Environ& env) override
    {
        return make<Warning_Action>(share(ph), analyse_op(*ph.arg_, env));
    }
};

/// The meaning of a call to `error`, such as `error("foo")`.
struct Error_Operation : public Operation
{
    Shared<Operation> arg_;
    Error_Operation(
        Shared<const Phrase> syntax,
        Shared<Operation> arg)
    :
        Operation(std::move(syntax)),
        arg_(std::move(arg))
    {}
    [[noreturn]] void run(Frame& f) const
    {
        Value val = arg_->eval(f);
        Shared<const String> msg;
        if (auto s = val.dycast<String>())
            msg = s;
        else
            msg = stringify(val);
        throw Exception{At_Phrase(*syntax_, f), msg};
    }
    virtual void exec(Frame& f, Executor&) const override
    {
        run(f);
    }
    virtual Value eval(Frame& f) const override
    {
        run(f);
    }
};
/// The meaning of the phrase `error` in isolation.
struct Error_Metafunction : public Metafunction
{
    using Metafunction::Metafunction;
    virtual Shared<Meaning> call(const Call_Phrase& ph, Environ& env) override
    {
        return make<Error_Operation>(share(ph), analyse_op(*ph.arg_, env));
    }
};

// exec(expr) -- a debug action that evaluates expr, then discards the result.
// It is used to call functions or source files for their side effects.
struct Exec_Action : public Operation
{
    Shared<Operation> arg_;
    Exec_Action(
        Shared<const Phrase> syntax,
        Shared<Operation> arg)
    :
        Operation(std::move(syntax)),
        arg_(std::move(arg))
    {}
    virtual void exec(Frame& f, Executor&) const override
    {
        arg_->eval(f);
    }
};
struct Exec_Metafunction : public Metafunction
{
    using Metafunction::Metafunction;
    virtual Shared<Meaning> call(const Call_Phrase& ph, Environ& env) override
    {
        return make<Exec_Action>(share(ph), analyse_op(*ph.arg_, env));
    }
};

struct Assert_Action : public Operation
{
    Shared<Operation> arg_;
    Assert_Action(
        Shared<const Phrase> syntax,
        Shared<Operation> arg)
    :
        Operation(std::move(syntax)),
        arg_(std::move(arg))
    {}
    virtual void exec(Frame& f, Executor&) const override
    {
        At_Metacall cx{"assert", 0, *arg_->syntax_, f};
        bool b = arg_->eval(f).to_bool(cx);
        if (!b)
            throw Exception(At_Phrase(*syntax_, f), "assertion failed");
    }
};
struct Assert_Metafunction : public Metafunction
{
    using Metafunction::Metafunction;
    virtual Shared<Meaning> call(const Call_Phrase& ph, Environ& env) override
    {
        auto arg = analyse_op(*ph.arg_, env);
        return make<Assert_Action>(share(ph), arg);
    }
};

struct Assert_Error_Action : public Operation
{
    Shared<Operation> expected_message_;
    Shared<const String> actual_message_;
    Shared<Operation> expr_;

    Assert_Error_Action(
        Shared<const Phrase> syntax,
        Shared<Operation> expected_message,
        Shared<const String> actual_message,
        Shared<Operation> expr)
    :
        Operation(std::move(syntax)),
        expected_message_(std::move(expected_message)),
        actual_message_(std::move(actual_message)),
        expr_(std::move(expr))
    {}

    virtual void exec(Frame& f, Executor&) const override
    {
        Value expected_msg_val = expected_message_->eval(f);
        auto expected_msg_str = expected_msg_val.to<const String>(
            At_Phrase(*expected_message_->syntax_, f));

        if (actual_message_ != nullptr) {
            if (*actual_message_ != *expected_msg_str)
                throw Exception(At_Phrase(*syntax_, f),
                    stringify("assertion failed: expected error \"",
                        expected_msg_str,
                        "\", actual error \"",
                        actual_message_,
                        "\""));
            return;
        }

        Value result;
        try {
            result = expr_->eval(f);
        } catch (Exception& e) {
            if (*e.shared_what() != *expected_msg_str) {
                throw Exception(At_Phrase(*syntax_, f),
                    stringify("assertion failed: expected error \"",
                        expected_msg_str,
                        "\", actual error \"",
                        e.shared_what(),
                        "\""));
            }
            return;
        }
        throw Exception(At_Phrase(*syntax_, f),
            stringify("assertion failed: expected error \"",
                expected_msg_str,
                "\", got value ", result));
    }
};
struct Assert_Error_Metafunction : public Metafunction
{
    using Metafunction::Metafunction;
    virtual Shared<Meaning> call(const Call_Phrase& ph, Environ& env) override
    {
        auto parens = cast<Paren_Phrase>(ph.arg_);
        Shared<Comma_Phrase> commas = nullptr;
        if (parens) commas = cast<Comma_Phrase>(parens->body_);
        if (parens && commas && commas->args_.size() == 2) {
            auto msg = analyse_op(*commas->args_[0].expr_, env);
            Shared<Operation> expr = nullptr;
            Shared<const String> actual_msg = nullptr;
            try {
                expr = analyse_op(*commas->args_[1].expr_, env);
            } catch (Exception& e) {
                actual_msg = e.shared_what();
            }
            return make<Assert_Error_Action>(share(ph), msg, actual_msg, expr);
        } else {
            throw Exception(At_Phrase(ph, env),
                "assert_error: expecting 2 arguments");
        }
    }
};

struct Defined_Expression : public Just_Expression
{
    Shared<const Operation> expr_;
    Symbol_Expr selector_;

    Defined_Expression(
        Shared<const Phrase> syntax,
        Shared<const Operation> expr,
        Symbol_Expr selector)
    :
        Just_Expression(std::move(syntax)),
        expr_(std::move(expr)),
        selector_(std::move(selector))
    {
    }

    virtual Value eval(Frame& f) const override
    {
        auto val = expr_->eval(f);
        auto s = val.dycast<Record>();
        if (s) {
            auto id = selector_.eval(f);
            return {s->hasfield(id)};
        } else {
            return {false};
        }
    }
};
struct Defined_Metafunction : public Metafunction
{
    using Metafunction::Metafunction;
    virtual Shared<Meaning> call(const Call_Phrase& ph, Environ& env) override
    {
        auto arg = analyse_op(*ph.arg_, env);
        auto dot = cast<Dot_Expr>(arg);
        if (dot != nullptr)
            return make<Defined_Expression>(
                share(ph), dot->base_, dot->selector_);
        throw Exception(At_Phrase(*ph.arg_, env),
            "defined: argument must be `expression.identifier`");
    }
};

const Namespace&
builtin_namespace()
{
    #define FUNCTION(f) {make_symbol(f::name()), make<Builtin_Value>(Value{make<f>()})}

    static const Namespace names = {
    {make_symbol("pi"), make<Builtin_Value>(pi)},
    {make_symbol("tau"), make<Builtin_Value>(two_pi)},
    {make_symbol("inf"), make<Builtin_Value>(INFINITY)},
    {make_symbol("false"), make<Builtin_Value>(Value(false))},
    {make_symbol("true"), make<Builtin_Value>(Value(true))},

    FUNCTION(Is_Bool_Function),
    FUNCTION(Is_Symbol_Function),
    FUNCTION(Is_Num_Function),
    FUNCTION(Is_String_Function),
    FUNCTION(Is_List_Function),
    FUNCTION(Is_Record_Function),
    FUNCTION(Is_Fun_Function),
    FUNCTION(Bit_Function),
    FUNCTION(Sqrt_Function),
    FUNCTION(Log_Function),
    FUNCTION(Abs_Function),
    FUNCTION(Floor_Function),
    FUNCTION(Ceil_Function),
    FUNCTION(Trunc_Function),
    FUNCTION(Round_Function),
    FUNCTION(Frac_Function),
    FUNCTION(Sin_Function),
    FUNCTION(Cos_Function),
    FUNCTION(Tan_Function),
    FUNCTION(Asin_Function),
    FUNCTION(Acos_Function),
    FUNCTION(Atan_Function),
    FUNCTION(Atan2_Function),
    FUNCTION(Sinh_Function),
    FUNCTION(Cosh_Function),
    FUNCTION(Tanh_Function),
    FUNCTION(Asinh_Function),
    FUNCTION(Acosh_Function),
    FUNCTION(Atanh_Function),
    FUNCTION(Max_Function),
    FUNCTION(Min_Function),
    FUNCTION(Sum_Function),
    FUNCTION(And_Function),
    FUNCTION(Or_Function),
    FUNCTION(Xor_Function),
    FUNCTION(Lshift_Function),
    FUNCTION(Rshift_Function),
    FUNCTION(Bool32_Sum_Function),
    FUNCTION(Bool32_Product_Function),
    FUNCTION(Bool32_To_Nat_Function),
    FUNCTION(Nat_To_Bool32_Function),
    FUNCTION(Bool32_To_Float_Function),
    FUNCTION(Float_To_Bool32_Function),
    FUNCTION(Dot_Function),
    FUNCTION(Mag_Function),
    FUNCTION(Count_Function),
    FUNCTION(Fields_Function),
    FUNCTION(Strcat_Function),
    FUNCTION(Repr_Function),
    FUNCTION(Decode_Function),
    FUNCTION(Encode_Function),
    FUNCTION(Match_Function),

    {make_symbol("file"), make<Builtin_Meaning<File_Metafunction>>()},
    {make_symbol("print"), make<Builtin_Meaning<Print_Metafunction>>()},
    {make_symbol("warning"), make<Builtin_Meaning<Warning_Metafunction>>()},
    {make_symbol("error"), make<Builtin_Meaning<Error_Metafunction>>()},
    {make_symbol("assert"), make<Builtin_Meaning<Assert_Metafunction>>()},
    {make_symbol("assert_error"), make<Builtin_Meaning<Assert_Error_Metafunction>>()},
    {make_symbol("exec"), make<Builtin_Meaning<Exec_Metafunction>>()},
    {make_symbol("defined"), make<Builtin_Meaning<Defined_Metafunction>>()},
    };
    return names;
}

} // namespace curv


#ifndef PARTICLE
#define PARTICLE

#include <vector>
#include <boost/numeric/ublas/vector.hpp>
#include <string>
#include <sstream>
#include <random>

class DecayChannel;

namespace ublas = boost::numeric::ublas;

class Particle {
  private:
    double mass;
    int charge;
    int neutrons;
    double spin;
    double currentExcitationEnergy;

    std::string name;

    ublas::vector<double> fourMomentum;
    std::vector<DecayChannel*> decayChannels;

    static std::default_random_engine randomGen;
  public:
    Particle();
    Particle(const std::string&, double, int, int, double, double);
    Particle(const Particle&);
    Particle& operator=(const Particle& rhs);

    inline std::string ListInformation() const {
      std::ostringstream oss;
      oss << "-------------------------------------------------\n";
      oss << "  Particle: " << name << "\n";
      oss << "-------------------------------------------------\n";
      oss << "  Charge: " << charge << "\t\tNeutrons: " << neutrons << "\tSpin: " << spin << "\n";
      oss << "  Mass: " << mass << "\tG.S. Lifetime: " << GetLifetime() << "\n";
      return oss.str();
    }
    std::vector<Particle*> Decay();
    double GetDecayTime();
    inline std::string GetName() const {
      std::string n;
      std::ostringstream oss(n);
      oss << name << " (" << currentExcitationEnergy << " keV) Kin. En.: " << GetKinEnergy();
      return oss.str();
    };
    inline std::string GetRawName() const { return name; };
    inline std::string GetInfoForFile() const {
      std::string n;
      std::ostringstream oss(n);
      oss << name << "\t" << currentExcitationEnergy << "\t" << GetKinEnergy() << "\t" << fourMomentum(0) << "\t" << fourMomentum(1) << "\t" << fourMomentum(2) << "\t" << fourMomentum(3);
      return oss.str();
    };
    inline void SetMomentum(ublas::vector<double> v) { fourMomentum = v; };
    inline void SetExcitationEnergy(double e) { currentExcitationEnergy = e; fourMomentum(0)+=e; };
    double GetLifetime() const;
    inline int GetCharge() const { return charge; };
    inline int GetNeutrons() const { return neutrons; };
    inline double GetMass() const { return mass + currentExcitationEnergy; };
    inline ublas::vector<double> GetMomentum() const { return fourMomentum; };
    inline double GetExcitationEnergy() const { return currentExcitationEnergy; };
    inline void AddDecayChannel(DecayChannel* dc) { decayChannels.push_back(dc); };
    ublas::vector<double> GetVelocity() const;
    inline double GetKinEnergy() const { return fourMomentum(0)-GetMass(); };
    inline void SetKinEnergy(double e) { fourMomentum(0) = GetMass() + e; };
    inline ublas::vector<double> Get3Momentum() const {
      ublas::vector<double> v (3);
      v[0] = fourMomentum[1];
      v[1] = fourMomentum[2];
      v[2] = fourMomentum[3];
      return v;
    };
    inline std::vector<DecayChannel*>& GetDecayChannels() { return decayChannels; };
};

#endif


#include <iostream>
#include <fstream>
#include <string>
#include <Eigen/Geometry>
#include <chrono>
#include "mpi.h"
#include <vector>
#include <random>

#include "General_Utilities.hpp"
#include "Physical_Parameters.hpp"
#include "IC_Generator.hpp"
#include "Trajectory_Simulation.hpp"
#include "PREM.hpp"
#include "RN_Generators.hpp"

using namespace std;
using namespace std::chrono;

int main(int argc, char *argv[])
{

//INITIALIZATION
////////////////////////////////////////////////////////////
	//MPI Enviroment
		// Initialize the MPI environment
	    	MPI_Init(NULL, NULL);
	    // Get the number of processes
	    	int numprocs;
	    	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	    // Get the ID number of the process
	    	int myRank;
	    	MPI_Comm_rank(MPI_COMM_WORLD, &myRank);

	//Starting time
	    high_resolution_clock::time_point tStart,t1,t2,tEnd;
	    double durationIni=0.0;
	    double durationMain=0.0;
	    if(myRank==0)
	    {
	    	tStart = high_resolution_clock::now();
	    }
	//Read in configuration file, set up the detector and define the earth velocity
		Read_Config_File(argv[1]);
	//Initialize Logfile and create folders for inputfiles
		if(myRank==0)
		{
			Copy_Config_File(argv[1]);
			std::chrono::time_point<std::chrono::system_clock> start;
			start = std::chrono::system_clock::now();
			std::time_t start_time = std::chrono::system_clock::to_time_t(start);
			cout <<"\n##############################"<<endl
			<<"DaMaSCUS"<<version<<" - Simulation"<<endl<<endl
			<<"Starting Time: " <<std::ctime(&start_time)
			<<"Simulation ID: " <<SimID <<endl<<endl
			<<"Creating logfile." <<endl;
			LogFile_Initialize(numprocs);
		}
	//Initialize Random Number Generator:
		std::random_device rd;
		std::mt19937 PRNG(rd());
	//Synchronize processes:
		MPI_Barrier(MPI_COMM_WORLD);

////////////////////////////////////////////////////////////

	//1. Initial MC run without scatterings:
		//Initialize the earth model for initial run
			double sigmaInitial=1e-20*pb;
			Initialize_PREM(mChi,sigmaInitial);
		//Deactivate formfactor, if it is used:
			string formfactor0=FormFactor;
			FormFactor="None";
		if (myRank==0) cout <<"Start initial MC simulation run without DM scatterings." <<endl;
		//Desired Data Sample for inital and main MC simulation
			unsigned long long int Local_SampleSize_Initial =ceil((double)Global_SampleSize_Initial/numprocs);
			Global_SampleSize_Initial=Local_SampleSize_Initial*numprocs;
		//Particle counter per isodetection ring
			unsigned long long int Global_N0[Isodetection_Rings];
			unsigned long long int Local_N0[Isodetection_Rings];
			for (int i=0;i<Isodetection_Rings;i++)Local_N0[i]=0;
			long double Global_W0[Isodetection_Rings];
			long double Local_W0[Isodetection_Rings];
			for (int i=0;i<Isodetection_Rings;i++)Local_W0[i]=0.0;
			long double Global_W0sq[Isodetection_Rings];
			long double Local_W0sq[Isodetection_Rings];
			for (int i=0;i<Isodetection_Rings;i++)Local_W0sq[i]=0.0;

		//Depth crossing counter
			unsigned long long int Global_Counter_Crossings0=0;
			unsigned long long int Local_Counter_Crossings0=0;

		//Simulation of trajectories without scatterings and without formfactor implementation
			for(unsigned int i=0;i<Local_SampleSize_Initial;i++)
			{
				Event IC = InitialCondition(0,vEarth,PRNG);
				Trajectory trajectory=ParticleTrack(mChi,sigmaInitial,IC,vcut,PRNG);
				std::vector<Event> CrossingEvents=trajectory.DepthCrossing(Detector_Depth);
				Local_Counter_Crossings0+=CrossingEvents.size();
				//Increment the isodetection counters and record the weighted velocity norm.
				for (unsigned int j=0;j<CrossingEvents.size();j++)
				{
					int IsoRing=CrossingEvents[j].IsodetectionRing(Isodetection_Rings);
					Local_N0[IsoRing]++;

					double speed_ratio = IC.NormVelocity() / CrossingEvents[j].NormVelocity();
					double weight = CrossingEvents[j].DataWeight(speed_ratio);
					Local_W0[IsoRing]+=weight;
					Local_W0sq[IsoRing]+=weight*weight;
				}
			}
			//Reactivate form factor
				FormFactor=formfactor0;
		//Reduce the local isodetection counter
			MPI_Reduce(&Local_N0,&Global_N0,Isodetection_Rings,MPI_UNSIGNED_LONG_LONG,MPI_SUM,0,MPI_COMM_WORLD);
			MPI_Reduce(&Local_Counter_Crossings0,&Global_Counter_Crossings0,1,MPI_UNSIGNED_LONG_LONG,MPI_SUM,0,MPI_COMM_WORLD);
			MPI_Reduce(&Local_W0,&Global_W0,Isodetection_Rings,MPI_LONG_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);
			MPI_Reduce(&Local_W0sq,&Global_W0sq,Isodetection_Rings,MPI_LONG_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);

		//Status update on the console
			if(myRank==0)
			{
				//computing time
				t1 = high_resolution_clock::now();
				durationIni =1e-6*duration_cast<microseconds>( t1 - tStart ).count();
				cout <<"\tInitial run finished\t(" <<floor(durationIni) <<" s)." <<endl <<endl;
			}
	//2. MC run with scatterings and velocity data collection.
		//Initialize the earth model for the main run
			Initialize_PREM(mChi,sigma0);
		if (myRank==0) cout <<"Start main MC simulation run with scatterings." <<endl;
		//Particle counter per isodetection ring
			unsigned long long int Global_N[Isodetection_Rings];
			unsigned long long int Local_N[Isodetection_Rings];
			for (int i=0;i<Isodetection_Rings;i++)Local_N[i]=0;
		//Sum of data weights, necessary for determination of local DM number density
			long double Global_W[Isodetection_Rings];
			long double Local_W[Isodetection_Rings];
			for (int i=0;i<Isodetection_Rings;i++)Local_W[i]=0.0;
		//Sum of squared data weights, necessary for determination of the local DM number density uncertainty.
			long double Global_Wsq[Isodetection_Rings];
			long double Local_Wsq[Isodetection_Rings];
			for (int i=0;i<Isodetection_Rings;i++)Local_Wsq[i]=0.0;
		//Work Load Distribution: Desired Velocity Data Sample Size for each isodetection ring for each MPI process.
			unsigned int Local_SampleSize_Velocity=ceil((double)Global_SampleSize_Velocity/numprocs);
			Global_SampleSize_Velocity=Local_SampleSize_Velocity*numprocs;
		//Counter for simulated trajectories
			unsigned long long int Global_Counter_Tracks=0;
			unsigned long long int Local_Counter_Tracks=0;
		//Counter for scattering events
			unsigned long long int Global_Counter_Scatterings=0;
			unsigned long long int Local_Counter_Scatterings=0;
		//Counter for free particles:
			unsigned long long int Global_Counter_Free=0;
			unsigned long long int Local_Counter_Free=0;
		//Counter for depth crossing
			unsigned long long int Global_Counter_Crossings=0;
			unsigned long long int Local_Counter_Crossings=0;
		//Counter for velocity cutoffs
			unsigned long long int Global_Counter_vCutoff=0;
			unsigned long long int Local_Counter_vCutoff=0;
		//Local Counter for velocity data
			unsigned int Local_Counter_DatapointsTotal=0;
			unsigned int Local_Counter_Datapoints[Isodetection_Rings];
			for(int i=0;i<Isodetection_Rings;i++) Local_Counter_Datapoints[i]=0;

		//MPI Output File and Offset
			MPI_Offset offset;
 			MPI_File   file_Velocity[Isodetection_Rings];
 			MPI_File   file_Weights[Isodetection_Rings];
 		   	MPI_Status status;
 		//Velocity Output files including process dependent offset
 		   	for(int i=0;i<Isodetection_Rings;i++)
 		   	{
 		   		//Velocity output files
	 		   		string filename="../data/"+SimID+"_data/velocity."+std::to_string(i);
	 		   		int test=MPI_File_open(MPI_COMM_WORLD,const_cast<char*>(filename.c_str()),MPI_MODE_CREATE|MPI_MODE_EXCL|MPI_MODE_WRONLY,MPI_INFO_NULL, &file_Velocity[i]);
	 	   			//if it already exists, it will be overwritten!
		    			if(test != MPI_SUCCESS)
		    			{
		    				if (myRank == 0) MPI_File_delete(const_cast<char*>(filename.c_str()),MPI_INFO_NULL);
		    				//MPI_Barrier(MPI_COMM_WORLD);
		    				test=MPI_File_open(MPI_COMM_WORLD,const_cast<char*>(filename.c_str()),MPI_MODE_CREATE|MPI_MODE_EXCL|MPI_MODE_WRONLY,MPI_INFO_NULL, &file_Velocity[i]);
		    			}
		    		//Offset
		 		   		offset = myRank * Local_SampleSize_Velocity * sizeof(Eigen::Vector3d);
				 		MPI_File_seek(file_Velocity[i], offset,MPI_SEEK_SET);
			 	//Weights output files
			 		filename="../data/"+SimID+"_data/weights."+std::to_string(i);
	 		   		test=MPI_File_open(MPI_COMM_WORLD,const_cast<char*>(filename.c_str()),MPI_MODE_CREATE|MPI_MODE_EXCL|MPI_MODE_WRONLY,MPI_INFO_NULL, &file_Weights[i]);
	 	   			//if it already exists, it will be overwritten!
		    			if(test != MPI_SUCCESS)
		    			{
		    				if (myRank == 0) MPI_File_delete(const_cast<char*>(filename.c_str()),MPI_INFO_NULL);
		    				//MPI_Barrier(MPI_COMM_WORLD);
		    				test=MPI_File_open(MPI_COMM_WORLD,const_cast<char*>(filename.c_str()),MPI_MODE_CREATE|MPI_MODE_EXCL|MPI_MODE_WRONLY,MPI_INFO_NULL, &file_Weights[i]);
		    			}
		    		//Offset
	 		   			offset = myRank * Local_SampleSize_Velocity * sizeof(double);
			 			MPI_File_seek(file_Weights[i], offset,MPI_SEEK_SET);
 		   	}
 		//Simulation of Tracks
 		   	while(Local_Counter_DatapointsTotal<Isodetection_Rings*Local_SampleSize_Velocity)
 		   	{
 		   		//Simulate track.
					Event IC = InitialCondition(0,vEarth,PRNG);
 		   			Trajectory trajectory=ParticleTrack(mChi,sigma0,IC,vcut,PRNG);
 		   		//Increase counter of tracks and scatterings and vcutoff reachers
 		   			Local_Counter_Tracks++;
 		   			int scatterings = trajectory.NoOfScatterings();
 		   			if(scatterings==0) Local_Counter_Free++;
 		   			else Local_Counter_Scatterings+=scatterings;
 		   			if(trajectory.Trajectory_Type()==2)	Local_Counter_vCutoff++;
				//Where did the particle pass the isodetection rings.
					std::vector<Event> CrossingEvents=trajectory.DepthCrossing(Detector_Depth);
					Local_Counter_Crossings+=CrossingEvents.size();
					for (unsigned int j=0;j<CrossingEvents.size();j++)
					{
						//Which ring?
							int IsoRing=CrossingEvents[j].IsodetectionRing(Isodetection_Rings);
						//Increase the weight sums
							double speed_ratio = IC.NormVelocity() / CrossingEvents[j].NormVelocity();
							double weight = CrossingEvents[j].DataWeight(speed_ratio);
							Local_W[IsoRing]+=weight;
							Local_Wsq[IsoRing]+=weight*weight;
						//Increase the particle counter.
							Local_N[IsoRing]++;
						//If this isodetection ring is not finished collecting data, save the velocity and weight
							if(Local_Counter_Datapoints[IsoRing]<Local_SampleSize_Velocity)
							{
								//Save velocity.
									Eigen::Vector3d vel = CrossingEvents[j].Velocity();
								//Increase data counters
									Local_Counter_Datapoints[IsoRing]++;
									Local_Counter_DatapointsTotal++;
								//Save velocity and weights to file
									MPI_File_write(file_Velocity[IsoRing],vel.data(),vel.size(),MPI_DOUBLE,&status);
									MPI_File_write(file_Weights[IsoRing],&weight,1,MPI_DOUBLE,&status);
							}

					}
 		   	}
 		   	MPI_Barrier(MPI_COMM_WORLD);
 		//MPI Reductions
  	  		MPI_Reduce(&Local_Counter_Tracks,&Global_Counter_Tracks,1,MPI_UNSIGNED_LONG_LONG,MPI_SUM,0,MPI_COMM_WORLD);
  	  		MPI_Reduce(&Local_Counter_Scatterings,&Global_Counter_Scatterings,1,MPI_UNSIGNED_LONG_LONG,MPI_SUM,0,MPI_COMM_WORLD);
  	  		MPI_Reduce(&Local_Counter_vCutoff,&Global_Counter_vCutoff,1,MPI_UNSIGNED_LONG_LONG,MPI_SUM,0,MPI_COMM_WORLD);
  	  		MPI_Reduce(&Local_Counter_Free,&Global_Counter_Free,1,MPI_UNSIGNED_LONG_LONG,MPI_SUM,0,MPI_COMM_WORLD);
  	  		MPI_Reduce(&Local_Counter_Crossings,&Global_Counter_Crossings,1,MPI_UNSIGNED_LONG_LONG,MPI_SUM,0,MPI_COMM_WORLD);
  	  		MPI_Reduce(&Local_N,&Global_N,Isodetection_Rings,MPI_UNSIGNED_LONG_LONG,MPI_SUM,0,MPI_COMM_WORLD);
			MPI_Reduce(&Local_W,&Global_W,Isodetection_Rings,MPI_LONG_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);
			MPI_Reduce(&Local_Wsq,&Global_Wsq,Isodetection_Rings,MPI_LONG_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);

  	  	//Calculate DM Densities
  	  		vector<vector<double>> Edensity=DM_EnergyDensity(Global_W0,Global_SampleSize_Initial,Global_W,Global_Counter_Tracks,Global_W0sq,Global_Wsq,Isodetection_Rings);
  	  		vector<vector<double>> Ndensity=DM_NumberDensity(mChi,Edensity);
			double averageNdensity=DM_AverageDensity(Ndensity,Isodetection_Rings,Detector_Depth);
 	   	//Status update and save N(theta) and rho(theta)
 	   		if(myRank==0)
			{
				//Save density
					ofstream f;
					f.open("../data/"+SimID+".rho");
					for(int i=0;i<Isodetection_Rings;i++) f <<180.0 / Isodetection_Rings * i  <<"\t" <<InUnits(Edensity[i][0],GeV/cm/cm/cm)<<"\t" <<InUnits(Edensity[i][1],GeV/cm/cm/cm) <<endl;//<<"\t" <<InUnits(AverageVelocity_0[i],km/sec)<<"\t" <<InUnits(AverageVelocity[i],km/sec)<<endl;// <<"\t" <<Global_N0[i]<<"\t" <<Global_N[i] <<endl;
					f.close();
				//computing time
					t2 = high_resolution_clock::now();
					durationMain =1e-6*duration_cast<microseconds>( t2 - t1 ).count();
					cout <<"\tMain MC run finished\t" <<"("<<floor(durationMain) <<" s)." <<endl;
			}
		//Close all output files
		for(int i=0;i<Isodetection_Rings;i++)
		{
			MPI_File_close(&file_Velocity[i]);
			MPI_File_close(&file_Weights[i]);
		}
////////////////////////////////////////////////////////////

//Finalize simulation
////////////////////////////////////////////////////////////
    //Master process finishes logfile
		if(myRank==0)
		{
			//Ending time and computing time
				tEnd = high_resolution_clock::now();
				double durationTotal =1e-6*duration_cast<microseconds>( tEnd - tStart ).count();
				cout <<"\nProcessing Time:\t"<< durationTotal<<"s ("<< floor(durationTotal/3600.0)<<":"<<floor(fmod(durationTotal/60.0,60.0))<<":"<<floor(fmod(durationTotal,60.0))<<":"<<floor(fmod(1000*durationTotal,1000.0))<<")."<<endl
				<<"##############################"<<endl;
			//Finalize Logfile
				LogFile_Finalize(Global_Counter_Tracks,Global_Counter_Scatterings,Global_Counter_Free,Global_Counter_vCutoff,Global_SampleSize_Initial,Global_SampleSize_Velocity,Global_Counter_Crossings0,Global_Counter_Crossings,averageNdensity,durationIni,durationMain,durationTotal);
		}
	// Finalize the MPI environment.
	    MPI_Finalize();
    	return 0;
////////////////////////////////////////////////////////////
}


// Copyright (c) 2015-2021 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#ifndef subclone_model_hpp
#define subclone_model_hpp

#include <vector>
#include <unordered_map>
#include <utility>

#include <boost/optional.hpp>

#include "config/common.hpp"
#include "core/types/haplotype.hpp"
#include "core/types/indexed_haplotype.hpp"
#include "core/types/genotype.hpp"
#include "core/types/cancer_genotype.hpp"
#include "core/models/haplotype_likelihood_array.hpp"
#include "containers/mappable_block.hpp"
#include "exceptions/unimplemented_feature_error.hpp"
#include "variational_bayes_mixture_model.hpp"
#include "genotype_prior_model.hpp"
#include "cancer_genotype_prior_model.hpp"

namespace octopus { namespace model {

template <typename GenotypeType, typename GenotypePriorModel_>
class SubcloneModelBase
{
public:
    constexpr static unsigned max_ploidy = 100;

    struct AlgorithmParameters
    {
        unsigned max_iterations = 1000;
        double epsilon          = 0.05;
        unsigned max_seeds      = 12;
        boost::optional<MemoryFootprint> target_max_memory = boost::none;
        ExecutionPolicy execution_policy = ExecutionPolicy::seq;
    };

    struct Priors
    {
        using GenotypeMixturesDirichletAlphas   = std::vector<double>;
        using GenotypeMixturesDirichletAlphaMap = std::unordered_map<SampleName, GenotypeMixturesDirichletAlphas>;

        const GenotypePriorModel_& genotype_prior_model;
        GenotypeMixturesDirichletAlphaMap alphas;
    };

    struct Latents
    {
        using GenotypeMixturesDirichletAlphas   = std::vector<double>;
        using GenotypeMixturesDirichletAlphaMap = std::unordered_map<SampleName, GenotypeMixturesDirichletAlphas>;
        using ProbabilityVector                 = std::vector<double>;
        using LogProbabilityVector              = std::vector<double>;

        ProbabilityVector genotype_probabilities;
        LogProbabilityVector genotype_log_probabilities;
        GenotypeMixturesDirichletAlphaMap alphas;
    };

    struct InferredLatents
    {
        Latents max_evidence_params;
        typename Latents::LogProbabilityVector genotype_log_priors;
        typename Latents::ProbabilityVector weighted_genotype_posteriors;
        double approx_log_evidence;
    };

    SubcloneModelBase() = delete;

    SubcloneModelBase(std::vector<SampleName> samples, Priors priors);
    SubcloneModelBase(std::vector<SampleName> samples, Priors priors, AlgorithmParameters parameters);

    SubcloneModelBase(const SubcloneModelBase&)            = default;
    SubcloneModelBase& operator=(const SubcloneModelBase&) = default;
    SubcloneModelBase(SubcloneModelBase&&)                 = default;
    SubcloneModelBase& operator=(SubcloneModelBase&&)      = default;

    ~SubcloneModelBase() = default;

    const Priors& priors() const noexcept;

    void prime(const MappableBlock<Haplotype>& haplotypes);
    void unprime() noexcept;
    bool is_primed() const noexcept;

    InferredLatents
    evaluate(const std::vector<GenotypeType>& genotypes,
             const HaplotypeLikelihoodArray& haplotype_likelihoods,
             std::vector<typename Latents::LogProbabilityVector> hints = {}) const;

private:
    std::vector<SampleName> samples_;
    Priors priors_;
    AlgorithmParameters parameters_;
    const MappableBlock<Haplotype>* haplotypes_;
};

using SubcloneModel = SubcloneModelBase<Genotype<IndexedHaplotype<>>, GenotypePriorModel>;
using SomaticSubcloneModel = SubcloneModelBase<CancerGenotype<IndexedHaplotype<>>, CancerGenotypePriorModel>;

template <typename G, typename GPM>
constexpr unsigned SubcloneModelBase<G, GPM>::max_ploidy;

template <typename G, typename GPM>
SubcloneModelBase<G, GPM>::SubcloneModelBase(std::vector<SampleName> samples, Priors priors)
: SubcloneModelBase {std::move(samples), std::move(priors), AlgorithmParameters {}}
{}

template <typename G, typename GPM>
SubcloneModelBase<G, GPM>::SubcloneModelBase(std::vector<SampleName> samples, Priors priors, AlgorithmParameters parameters)
: samples_ {std::move(samples)}
, priors_ {std::move(priors)}
, parameters_ {parameters}
{}

template <typename G, typename GPM>
const typename SubcloneModelBase<G, GPM>::Priors& SubcloneModelBase<G, GPM>::priors() const noexcept
{
    return priors_;
}

template <typename G, typename GPM>
void SubcloneModelBase<G, GPM>::prime(const MappableBlock<Haplotype>& haplotypes)
{
    haplotypes_ = std::addressof(haplotypes);
}

template <typename G, typename GPM>
void SubcloneModelBase<G, GPM>::unprime() noexcept
{
    haplotypes_ = nullptr;
}

template <typename G, typename GPM>
bool SubcloneModelBase<G, GPM>::is_primed() const noexcept
{
    return haplotypes_;
}

namespace detail {

std::vector<LogProbabilityVector>
generate_seeds(const std::vector<SampleName>& samples,
               const std::vector<Genotype<IndexedHaplotype<>>>& genotypes,
               const LogProbabilityVector& genotype_log_priors,
               const HaplotypeLikelihoodArray& haplotype_log_likelihoods,
               const SubcloneModel::Priors& priors,
               std::size_t max_seeds,
               std::vector<LogProbabilityVector> hints = {},
               const MappableBlock<Haplotype>* haplotypes = nullptr);

std::vector<LogProbabilityVector>
generate_seeds(const std::vector<SampleName>& samples,
               const std::vector<CancerGenotype<IndexedHaplotype<>>>& genotypes,
               const LogProbabilityVector& genotype_log_priors,
               const HaplotypeLikelihoodArray& haplotype_log_likelihoods,
               const SomaticSubcloneModel::Priors& priors,
               std::size_t max_seeds,
               std::vector<LogProbabilityVector> hints = {},
               const MappableBlock<Haplotype>* haplotypes = nullptr);

template <std::size_t K, typename G, typename GPM>
VBAlpha<K> flatten(const typename SubcloneModelBase<G, GPM>::Priors::GenotypeMixturesDirichletAlphas& alpha)
{
    VBAlpha<K> result {};
    std::copy_n(std::cbegin(alpha), K, std::begin(result));
    return result;
}

template <std::size_t K, typename G, typename GPM>
VBAlphaVector<K> flatten(const typename SubcloneModelBase<G, GPM>::Priors::GenotypeMixturesDirichletAlphaMap& alphas,
                         const std::vector<SampleName>& samples)
{
    VBAlphaVector<K> result(samples.size());
    std::transform(std::cbegin(samples), std::cend(samples), std::begin(result),
                   [&alphas] (const auto& sample) { return flatten<K, G, GPM>(alphas.at(sample)); });
    return result;
}

template <std::size_t K>
VBGenotype<K>
flatten(const Genotype<IndexedHaplotype<>>& genotype,
        const SampleName& sample,
        const HaplotypeLikelihoodArray& haplotype_likelihoods)
{
    VBGenotype<K> result {};
    haplotype_likelihoods.prime(sample);
    std::transform(std::cbegin(genotype), std::cend(genotype), std::begin(result),
                   [&] (const auto& haplotype) { return std::cref(haplotype_likelihoods[haplotype]); });
    return result;
}

template <std::size_t K>
auto copy_cref(const Genotype<IndexedHaplotype<>>& genotype,
               const SampleName& sample,
               const HaplotypeLikelihoodArray& haplotype_likelihoods,
               typename VBGenotype<K>::iterator result_itr)
{
    haplotype_likelihoods.prime(sample);
    return std::transform(std::cbegin(genotype), std::cend(genotype), result_itr,
                          [&] (const auto& haplotype) { return std::cref(haplotype_likelihoods[haplotype]); });
}

template <std::size_t K>
VBGenotype<K>
flatten(const CancerGenotype<IndexedHaplotype<>>& genotype,
        const SampleName& sample,
        const HaplotypeLikelihoodArray& haplotype_likelihoods)
{
    VBGenotype<K> result {};
    assert(genotype.ploidy() == K);
    auto itr = copy_cref<K>(genotype.germline(), sample, haplotype_likelihoods, std::begin(result));
    copy_cref<K>(genotype.somatic(), sample, haplotype_likelihoods, itr);
    return result;
}

template <std::size_t K, typename G>
VBGenotypeVector<K>
flatten(const std::vector<G>& genotypes, const SampleName& sample,
        const HaplotypeLikelihoodArray& haplotype_likelihoods)
{
    VBGenotypeVector<K> result(genotypes.size());
    std::transform(std::cbegin(genotypes), std::cend(genotypes), std::begin(result),
                   [&sample, &haplotype_likelihoods] (const auto& genotype) {
                       return flatten<K>(genotype, sample, haplotype_likelihoods);
                   });
    return result;
}

template <std::size_t K, typename G>
VBReadLikelihoodMatrix<K>
flatten(const std::vector<G>& genotypes,
        const std::vector<SampleName>& samples,
        const HaplotypeLikelihoodArray& haplotype_likelihoods)
{
    VBReadLikelihoodMatrix<K> result {};
    result.reserve(samples.size());
    std::transform(std::cbegin(samples), std::cend(samples), std::back_inserter(result),
                   [&genotypes, &haplotype_likelihoods] (const auto& sample) {
                       return flatten<K>(genotypes, sample, haplotype_likelihoods);
                   });
    return result;
}

template <std::size_t K, typename G, typename GPM>
auto expand(VBAlpha<K>& alpha)
{
    return typename SubcloneModelBase<G, GPM>::Latents::GenotypeMixturesDirichletAlphas(std::begin(alpha), std::end(alpha));
}

template <std::size_t K, typename G, typename GPM>
auto expand(const std::vector<SampleName>& samples, VBAlphaVector<K>&& alphas)
{
    typename SubcloneModelBase<G, GPM>::Latents::GenotypeMixturesDirichletAlphaMap result {};
    std::transform(std::cbegin(samples), std::cend(samples), std::begin(alphas),
                   std::inserter(result, std::begin(result)),
                   [] (const auto& sample, auto&& vb_alpha) {
                       return std::make_pair(sample, expand<K, G, GPM>(vb_alpha));
                   });
    return result;
}

template <std::size_t K, typename G, typename GPM>
typename SubcloneModelBase<G, GPM>::InferredLatents
expand(VBResultPacket<K>& vb_results, const std::vector<SampleName>& samples, LogProbabilityVector genotype_log_priors)
{
    typename SubcloneModelBase<G, GPM>::Latents posterior_latents {
        std::move(vb_results.map_latents.genotype_posteriors),
        std::move(vb_results.map_latents.genotype_log_posteriors),
        expand<K, G, GPM>(samples, std::move(vb_results.map_latents.alphas))
        };
    return {std::move(posterior_latents),
            std::move(genotype_log_priors),
            std::move(vb_results.evidence_weighted_genotype_posteriors),
            vb_results.max_log_evidence};
}

template <std::size_t K, typename G, typename GPM>
typename SubcloneModelBase<G, GPM>::InferredLatents
run_variational_bayes_helper(const std::vector<SampleName>& samples,
                             const std::vector<G>& genotypes,
                             const typename SubcloneModelBase<G, GPM>::Priors::GenotypeMixturesDirichletAlphaMap& prior_alphas,
                             const LogProbabilityVector& genotype_log_priors,
                             const HaplotypeLikelihoodArray& haplotype_log_likelihoods,
                             const typename SubcloneModelBase<G, GPM>::AlgorithmParameters& params,
                             std::vector<LogProbabilityVector>&& seeds)
{
    VariationalBayesParameters vb_params {params.epsilon, params.max_iterations};
    if (params.target_max_memory) {
        const auto estimated_memory_default = estimate_memory_requirement<K>(samples, haplotype_log_likelihoods, genotypes.size(), vb_params);
        if (estimated_memory_default > *params.target_max_memory) {
            vb_params.save_memory = true;
        }
    }
    if (params.execution_policy == ExecutionPolicy::par) {
        vb_params.parallel_execution = true;
    }
    const auto vb_prior_alphas = flatten<K, G, GPM>(prior_alphas, samples);
    const auto log_likelihoods = flatten<K>(genotypes, samples, haplotype_log_likelihoods);
    auto vb_results = octopus::model::run_variational_bayes(vb_prior_alphas, genotype_log_priors, log_likelihoods, vb_params, std::move(seeds));
    return expand<K, G, GPM>(vb_results, samples, std::move(genotype_log_priors));
}

template <typename G, typename GPM,
          std::size_t... Is>
typename SubcloneModelBase<G, GPM>::InferredLatents
run_variational_bayes_helper(const std::vector<SampleName>& samples,
                             const std::vector<G>& genotypes,
                             const typename SubcloneModelBase<G, GPM>::Priors::GenotypeMixturesDirichletAlphaMap& prior_alphas,
                             LogProbabilityVector genotype_log_priors,
                             const HaplotypeLikelihoodArray& haplotype_log_likelihoods,
                             const typename SubcloneModelBase<G, GPM>::AlgorithmParameters& params,
                             std::vector<LogProbabilityVector>&& seeds,
                             std::index_sequence<Is...>)
{
    constexpr auto max_ploidy = std::index_sequence<Is...>::size();
    const auto ploidy = genotypes.front().ploidy();
    if (ploidy > max_ploidy) {
        throw UnimplementedFeatureError {"ploidies above " + std::to_string(max_ploidy), "SubcloneModel"};
    }
    typename SubcloneModelBase<G, GPM>::InferredLatents result;
    int unused[] = {(ploidy == Is ?
                    (result = run_variational_bayes_helper<Is, G, GPM>(samples, genotypes, prior_alphas, std::move(genotype_log_priors),
                                                                       haplotype_log_likelihoods, params, std::move(seeds))
                                                                       , 0) : 0)...};
    (void) unused;
    return result;
}

template<std::size_t N, std::size_t... Seq>
constexpr std::index_sequence<N + Seq ...>
add(std::index_sequence<Seq...>) { return {}; }

template<std::size_t Min, std::size_t Max>
using make_index_range = decltype(add<Min>(std::make_index_sequence<Max - Min>()));

template <typename G, typename GPM>
typename SubcloneModelBase<G, GPM>::InferredLatents
run_variational_bayes(const std::vector<SampleName>& samples,
                      const std::vector<G>& genotypes,
                      const typename SubcloneModelBase<G, GPM>::Priors& priors,
                      const HaplotypeLikelihoodArray& haplotype_log_likelihoods,
                      const typename SubcloneModelBase<G, GPM>::AlgorithmParameters& params,
                      std::vector<typename SubcloneModelBase<G, GPM>::Latents::LogProbabilityVector> hints,
                      const MappableBlock<Haplotype>* haplotypes)
{
    constexpr auto max_ploidy = SubcloneModelBase<G, GPM>::max_ploidy;
    auto genotype_log_priors = evaluate(genotypes, priors.genotype_prior_model);
    auto seeds = generate_seeds(samples, genotypes, genotype_log_priors, haplotype_log_likelihoods, priors, params.max_seeds, std::move(hints), haplotypes);
    return run_variational_bayes_helper<G, GPM>(samples, genotypes, priors.alphas, std::move(genotype_log_priors),
                                                    haplotype_log_likelihoods, params, std::move(seeds),
                                                    make_index_range<1, max_ploidy + 1> {});
}

} // namespace detail

template <typename G, typename GPM>
typename SubcloneModelBase<G, GPM>::InferredLatents
SubcloneModelBase<G, GPM>::evaluate(const std::vector<G>& genotypes,
                                    const HaplotypeLikelihoodArray& haplotype_likelihoods,
                                    std::vector<typename Latents::LogProbabilityVector> hints) const
{
    assert(!genotypes.empty());
    return detail::run_variational_bayes<G, GPM>(samples_, genotypes, priors_, haplotype_likelihoods, parameters_, std::move(hints), haplotypes_);
}

} // namespace model
} // namespace octopus

#endif


#include <koinos/system/system_calls.hpp>
#include <koinos/contracts/token/token.h>

#include <koinos/buffer.hpp>
#include <koinos/common.h>

#include <boost/multiprecision/cpp_int.hpp>

#include <string>

using namespace koinos;
using namespace koinos::contracts;

using int128_t = boost::multiprecision::int128_t;

namespace constants {

static const std::string koinos_name   = "Test Koinos";
static const std::string koinos_symbol = "tKOIN";
constexpr uint32_t koinos_decimals     = 8;
constexpr uint64_t mana_regen_time_ms  = 1200000; // 20 minutes
constexpr std::size_t max_address_size = 25;
constexpr std::size_t max_name_size    = 32;
constexpr std::size_t max_symbol_size  = 8;
constexpr std::size_t max_buffer_size  = 2048;
constexpr uint32_t supply_id           = 0;
constexpr uint32_t balance_id          = 1;
std::string supply_key                 = "";
const auto contract_id                 = system::get_contract_id();

} // constants

namespace state {

namespace detail {

system::object_space create_supply_space()
{
   system::object_space supply_space;
   supply_space.mutable_zone().set( reinterpret_cast< const uint8_t* >( constants::contract_id.data() ), constants::contract_id.size() );
   supply_space.set_id( constants::supply_id );
   return supply_space;
}

system::object_space create_balance_space()
{
   system::object_space balance_space;
   balance_space.mutable_zone().set( reinterpret_cast< const uint8_t* >( constants::contract_id.data() ), constants::contract_id.size() );
   balance_space.set_id( constants::balance_id );
   return balance_space;
}

} // detail

const system::object_space& supply_space()
{
   static const auto supply_space = detail::create_supply_space();
   return supply_space;
}

const system::object_space& balance_space()
{
   static const auto balance_space = detail::create_balance_space();
   return balance_space;
}

} // state

enum entries : uint32_t
{
   get_account_rc_entry     = 0,
   consume_account_rc_entry = 1,
   name_entry               = 0x76ea4297,
   symbol_entry             = 0x7e794b24,
   decimals_entry           = 0x59dc15ce,
   total_supply_entry       = 0xcf2e8212,
   balance_of_entry         = 0x15619248,
   transfer_entry           = 0x62efa292,
   mint_entry               = 0xc2f82bdc
};

using get_account_rc_arguments
   = chain::get_account_rc_arguments<
      constants::max_name_size
   >;

using consume_account_rc_arguments
   = chain::consume_account_rc_arguments<
      constants::max_name_size
   >;

void regenerate_mana( token::mana_balance_object& bal )
{
   auto head_block_time = system::get_head_info().head_block_time();
   auto delta = std::min( head_block_time - bal.last_mana_update(), constants::mana_regen_time_ms );
   if ( delta )
   {
      auto new_mana = bal.mana() + ( ( int128_t( delta ) * int128_t( bal.balance() ) ) / constants::mana_regen_time_ms ).convert_to< uint64_t >() ;
      bal.set_mana( std::min( new_mana, bal.balance() ) );
      bal.set_last_mana_update( head_block_time );
   }
}

chain::get_account_rc_result get_account_rc( const get_account_rc_arguments& args )
{
   std::string owner( reinterpret_cast< const char* >( args.get_account().get_const() ), args.get_account().get_length() );
   token::mana_balance_object bal_obj;
   system::get_object( state::balance_space(), owner, bal_obj );

   regenerate_mana( bal_obj );

   chain::get_account_rc_result res;
   res.set_value( bal_obj.get_mana() );
   return res;
}

chain::consume_account_rc_result consume_account_rc( const consume_account_rc_arguments& args )
{
   chain::consume_account_rc_result res;
   res.set_value( false );

   const auto [caller, privilege] = system::get_caller();
   if ( privilege != chain::privilege::kernel_mode )
   {
      system::print( "consume_account_rc must be called from kernel context\n" );
      return res;
   }

   std::string owner( reinterpret_cast< const char* >( args.get_account().get_const() ), args.get_account().get_length() );
   token::mana_balance_object bal_obj;
   system::get_object( state::balance_space(), owner, bal_obj );

   regenerate_mana( bal_obj );

   // Assumes mana cannot go negative...
   if ( bal_obj.mana() < args.value() )
   {
      system::print( "account has insufficient mana for consumption\n" );
      return res;
   }

   bal_obj.set_mana( bal_obj.mana() - args.value() );

   system::put_object( state::balance_space(), owner, bal_obj );

   res.set_value( true );
   return res;
}

token::name_result< constants::max_name_size > name()
{
   token::name_result< constants::max_name_size > res;
   res.mutable_value() = constants::koinos_name.c_str();
   return res;
}

token::symbol_result< constants::max_symbol_size > symbol()
{
   token::symbol_result< constants::max_symbol_size > res;
   res.mutable_value() = constants::koinos_symbol.c_str();
   return res;
}

token::decimals_result decimals()
{
   token::decimals_result res;
   res.mutable_value() = constants::koinos_decimals;
   return res;
}

token::total_supply_result total_supply()
{
   token::total_supply_result res;

   token::balance_object bal_obj;
   system::get_object( state::supply_space(), constants::supply_key, bal_obj );

   res.mutable_value() = bal_obj.get_value();
   return res;
}

token::balance_of_result balance_of( const token::balance_of_arguments< constants::max_address_size >& args )
{
   token::balance_of_result res;

   std::string owner( reinterpret_cast< const char* >( args.get_owner().get_const() ), args.get_owner().get_length() );

   token::mana_balance_object bal_obj;
   system::get_object( state::balance_space(), owner, bal_obj );

   res.set_value( bal_obj.get_balance() );
   return res;
}

token::transfer_result transfer( const token::transfer_arguments< constants::max_address_size, constants::max_address_size >& args )
{
   token::transfer_result res;
   res.set_value( false );

   std::string from( reinterpret_cast< const char* >( args.get_from().get_const() ), args.get_from().get_length() );
   std::string to( reinterpret_cast< const char* >( args.get_to().get_const() ), args.get_to().get_length() );
   uint64_t value = args.get_value();

   if ( from == to )
   {
      system::print( "cannot transfer to self\n" );
      return res;
   }

   const auto [ caller, privilege ] = system::get_caller();
   if ( caller != from )
   {
      system::require_authority( from );
   }

   token::mana_balance_object from_bal_obj;
   system::get_object( state::balance_space(), from, from_bal_obj );

   if ( from_bal_obj.balance() < value )
   {
      system::print( "'from' has insufficient balance\n" );
      return res;
   }

   regenerate_mana( from_bal_obj );

   if ( from_bal_obj.mana() < value )
   {
      system::print( "'from' has insufficient mana for transfer\n" );
      return res;
   }

   token::mana_balance_object to_bal_obj;
   system::get_object( state::balance_space(), to, to_bal_obj );

   regenerate_mana( to_bal_obj );

   from_bal_obj.set_balance( from_bal_obj.balance() - value );
   from_bal_obj.set_mana( from_bal_obj.mana() - value );
   to_bal_obj.set_balance( to_bal_obj.balance() + value );
   to_bal_obj.set_mana( to_bal_obj.mana() + value );

   system::put_object( state::balance_space(), from, from_bal_obj );
   system::put_object( state::balance_space(), to, to_bal_obj );

   token::transfer_event< constants::max_address_size, constants::max_address_size > transfer_event;
   transfer_event.mutable_from().set( args.get_from().get_const(), args.get_from().get_length() );
   transfer_event.mutable_to().set( args.get_to().get_const(), args.get_to().get_length() );
   transfer_event.set_value( args.get_value() );

   std::vector< std::string > impacted;
   impacted.push_back( to );
   impacted.push_back( from );
   koinos::system::event( "koin.transfer", transfer_event, impacted );

   res.set_value( true );
   return res;
}

token::mint_result mint( const token::mint_arguments< constants::max_address_size >& args )
{
   token::mint_result res;
   res.set_value( false );

   std::string to( reinterpret_cast< const char* >( args.get_to().get_const() ), args.get_to().get_length() );
   uint64_t amount = args.get_value();

   const auto [ caller, privilege ] = system::get_caller();
   if ( privilege != chain::privilege::kernel_mode )
   {
      system::print( "can only mint token from kernel context\n" );
      return res;
   }

   auto supply = total_supply().get_value();
   auto new_supply = supply + amount;

   // Check overflow
   if ( new_supply < supply )
   {
      system::print( "mint would overflow supply\n" );
      return res;
   }

   token::mana_balance_object to_bal_obj;
   system::get_object( state::balance_space(), to, to_bal_obj );

   regenerate_mana( to_bal_obj );

   to_bal_obj.set_balance( to_bal_obj.balance() + amount );
   to_bal_obj.set_mana( to_bal_obj.mana() + amount );

   token::balance_object supply_obj;
   supply_obj.set_value( new_supply );

   system::put_object( state::supply_space(), constants::supply_key, supply_obj );
   system::put_object( state::balance_space(), to, to_bal_obj );

   token::mint_event< constants::max_address_size > mint_event;
   mint_event.mutable_to().set( args.get_to().get_const(), args.get_to().get_length() );
   mint_event.set_value( amount );

   std::vector< std::string > impacted;
   impacted.push_back( to );
   koinos::system::event( "koin.mint", mint_event, impacted );

   res.set_value( true );
   return res;
}

int main()
{
   auto entry_point = system::get_entry_point();
   auto args = system::get_contract_arguments();

   std::array< uint8_t, constants::max_buffer_size > retbuf;

   koinos::read_buffer rdbuf( (uint8_t*)args.c_str(), args.size() );
   koinos::write_buffer buffer( retbuf.data(), retbuf.size() );

   switch( std::underlying_type_t< entries >( entry_point ) )
   {
      case entries::get_account_rc_entry:
      {
         get_account_rc_arguments arg;
         arg.deserialize( rdbuf );

         auto res = get_account_rc( arg );
         res.serialize( buffer );
         break;
      }
      case entries::consume_account_rc_entry:
      {
         consume_account_rc_arguments arg;
         arg.deserialize( rdbuf );

         auto res = consume_account_rc( arg );
         res.serialize( buffer );
         break;
      }
      case entries::name_entry:
      {
         auto res = name();
         res.serialize( buffer );
         break;
      }
      case entries::symbol_entry:
      {
         auto res = symbol();
         res.serialize( buffer );
         break;
      }
      case entries::decimals_entry:
      {
         auto res = decimals();
         res.serialize( buffer );
         break;
      }
      case entries::total_supply_entry:
      {
         auto res = total_supply();
         res.serialize( buffer );
         break;
      }
      case entries::balance_of_entry:
      {
         token::balance_of_arguments< constants::max_address_size > arg;
         arg.deserialize( rdbuf );

         auto res = balance_of( arg );
         res.serialize( buffer );
         break;
      }
      case entries::transfer_entry:
      {
         token::transfer_arguments< constants::max_address_size, constants::max_address_size > arg;
         arg.deserialize( rdbuf );

         auto res = transfer( arg );
         res.serialize( buffer );
         break;
      }
      case entries::mint_entry:
      {
         token::mint_arguments< constants::max_address_size > arg;
         arg.deserialize( rdbuf );

         auto res = mint( arg );
         res.serialize( buffer );
         break;
      }
      default:
         system::exit_contract( 1 );
   }

   std::string retval( reinterpret_cast< const char* >( buffer.data() ), buffer.get_size() );
   system::set_contract_result_bytes( retval );

   system::exit_contract( 0 );
   return 0;
}