/*
 *  testRLEOperations.cpp
 *  Trogdor6
 *
 *  Created by Paul Hansen on 5/17/10.
 *  Copyright 2010 Stanford University. All rights reserved.
 *
 */

#define BOOST_TEST_MAIN
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE Test3D

#include <boost/test/unit_test.hpp>

#include <iostream>
#include <cstdlib>
#include <vector>
#include "geometry.h"
#include "RLEOperations.h"

using namespace std;
using namespace RLE;

BOOST_AUTO_TEST_CASE( TestDownsample2x2x2 )
{
    Rect3i allCells(0,0,0,1,1,1);
    Rect3i outCells(0,0,0,0,0,0);

    DynamicRLE3<int> rle;
    rle.mark(0,0,0,0,0,0,1);    // rle(0,0,0) = 1
    rle.mark(1,1,0,1,1,0,2);    // rle(1,1,0) = 2
    rle.mark(0,1,1,0,1,1,3);    // rle(0,1,1) = 3

    DynamicRLE3<int> out;
    DynamicRLE3<int> outSlow;

    // Get (0,0,0)
    out = downsample2(rle, allCells, outCells);
    outSlow = downsample2_slow(rle, allCells, outCells);
    BOOST_CHECK(out == outSlow);
    BOOST_CHECK_EQUAL(out(0,0,0), 1);
    BOOST_CHECK_EQUAL(outSlow(0,0,0), 1);

    // Get (1,0,0)
    out = downsample2(rle, allCells+Vector3i(1,0,0), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(1,0,0), outCells);
    BOOST_CHECK(out == outSlow);
    BOOST_CHECK(!out.isMarked(0,0,0));
    BOOST_CHECK(!outSlow.isMarked(0,0,0));

    // Get (0,1,0)
    out = downsample2(rle, allCells+Vector3i(0,1,0), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(0,1,0), outCells);
    BOOST_CHECK(out == outSlow);
    BOOST_CHECK(!out.isMarked(0,0,0));
    BOOST_CHECK(!outSlow.isMarked(0,0,0));

    // Get (1,1,0)
    out = downsample2(rle, allCells+Vector3i(1,1,0), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(1,1,0), outCells);
    BOOST_CHECK(out == outSlow);
    BOOST_CHECK_EQUAL(out(0,0,0), 2);
    BOOST_CHECK_EQUAL(outSlow(0,0,0), 2);

    // Get (0,0,1)
    out = downsample2(rle, allCells+Vector3i(0,0,1), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(0,0,1), outCells);
    BOOST_CHECK(out == outSlow);
    BOOST_CHECK(!out.isMarked(0,0,0));
    BOOST_CHECK(!outSlow.isMarked(0,0,0));

    // Get (1,0,1)
    out = downsample2(rle, allCells+Vector3i(1,0,1), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(1,0,1), outCells);
    BOOST_CHECK(out == outSlow);
    BOOST_CHECK(!out.isMarked(0,0,0));
    BOOST_CHECK(!outSlow.isMarked(0,0,0));

    // Get (0,1,1)
    out = downsample2(rle, allCells+Vector3i(0,1,1), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(0,1,1), outCells);
    BOOST_CHECK(out == outSlow);
    BOOST_CHECK_EQUAL(out(0,0,0), 3);
    BOOST_CHECK_EQUAL(outSlow(0,0,0), 3);

    // Get (1,1,1)
    out = downsample2(rle, allCells+Vector3i(1,1,1), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(1,1,1), outCells);
    BOOST_CHECK(out == outSlow);
    BOOST_CHECK(!out.isMarked(0,0,0));
    BOOST_CHECK(!outSlow.isMarked(0,0,0));
}

BOOST_AUTO_TEST_CASE( TestDownsampleX )
{
    int outY = -3, outZ = 10;
    Rect3i allCells(-10,0,0,9,1,1);
    Rect3i outCells(-5,outY,outZ,4,outY,outZ);

    DynamicRLE3<int> rle;

    rle.mark(-10,0,0,9,0,0,1);
    rle.mark(-10,1,0,9,1,0,2);
    rle.mark(-10,0,1,9,0,1,3);
    rle.mark(-10,1,1,9,1,1,4);

    DynamicRLE3<int> out, outSlow;

    // Get row (:,0,0)
    out = downsample2(rle, allCells, outCells);
    outSlow = downsample2_slow(rle, allCells, outCells);
    BOOST_CHECK(out == outSlow);
    for (int xx = -5; xx <= 4; xx++)
    {
        BOOST_CHECK_EQUAL(out(xx,outY,outZ), 1);
        BOOST_CHECK_EQUAL(outSlow(xx,outY,outZ), 1);
    }

    // Get row (:,1,0)
    out = downsample2(rle, allCells+Vector3i(0,1,0), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(0,1,0), outCells);
    BOOST_CHECK(out == outSlow);
    for (int xx = -5; xx <= 4; xx++)
    {
        BOOST_CHECK_EQUAL(out(xx,outY,outZ), 2);
        BOOST_CHECK_EQUAL(outSlow(xx,outY,outZ), 2);
    }

    // Get row (:,0,1)
    out = downsample2(rle, allCells+Vector3i(0,0,1), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(0,0,1), outCells);
    BOOST_CHECK(out == outSlow);
    for (int xx = -5; xx <= 4; xx++)
    {
        BOOST_CHECK_EQUAL(out(xx,outY,outZ), 3);
        BOOST_CHECK_EQUAL(outSlow(xx,outY,outZ), 3);
    }

    // Get row (:,1,1)
    out = downsample2(rle, allCells+Vector3i(0,1,1), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(0,1,1), outCells);
    BOOST_CHECK(out == outSlow);
    for (int xx = -5; xx <= 4; xx++)
    {
        BOOST_CHECK_EQUAL(out(xx,outY,outZ), 4);
        BOOST_CHECK_EQUAL(outSlow(xx,outY,outZ), 4);
    }
}

BOOST_AUTO_TEST_CASE( TestDownsampleY )
{
    int outX = -100, outZ = -9;
    Rect3i allCells(0,-10,0,1,9,1);
    Rect3i outCells(outX,-5,outZ,outX,4,outZ);

    DynamicRLE3<int> rle;

    rle.mark(0,-10,0,0,9,0,1);
    rle.mark(1,-10,0,1,9,0,2);
    rle.mark(0,-10,1,0,9,1,3);
    rle.mark(1,-10,1,1,9,1,4);

    DynamicRLE3<int> out, outSlow;

    // Get row (0,:,0)
    out = downsample2(rle, allCells, outCells);
    outSlow = downsample2_slow(rle, allCells, outCells);
    BOOST_CHECK(out == outSlow);
    for (int yy = -5; yy <= 4; yy++)
    {
        BOOST_CHECK_EQUAL(out(outX,yy,outZ), 1);
        BOOST_CHECK_EQUAL(outSlow(outX,yy,outZ), 1);
    }

    // Get row (1,:,0)
    out = downsample2(rle, allCells+Vector3i(1,0,0), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(1,0,0), outCells);
    BOOST_CHECK(out == outSlow);
    for (int yy = -5; yy <= 4; yy++)
    {
        BOOST_CHECK_EQUAL(out(outX,yy,outZ), 2);
        BOOST_CHECK_EQUAL(outSlow(outX,yy,outZ), 2);
    }

    // Get row (0,:,1)
    out = downsample2(rle, allCells+Vector3i(0,0,1), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(0,0,1), outCells);
    BOOST_CHECK(out == outSlow);
    for (int yy = -5; yy <= 4; yy++)
    {
        BOOST_CHECK_EQUAL(out(outX,yy,outZ), 3);
        BOOST_CHECK_EQUAL(outSlow(outX,yy,outZ), 3);
    }

    // Get row (1,:,1)
    out = downsample2(rle, allCells+Vector3i(1,0,1), outCells);
    outSlow = downsample2_slow(rle, allCells+Vector3i(1,0,1), outCells);
    BOOST_CHECK(out == outSlow);
    for (int yy = -5; yy <= 4; yy++)
    {
        BOOST_CHECK_EQUAL(out(outX,yy,outZ), 4);
        BOOST_CHECK_EQUAL(outSlow(outX,yy,outZ), 4);
    }
}

BOOST_AUTO_TEST_CASE( TestDownsample2Wrap2x2x2 )
{
    Rect3i allCells(0,0,0,1,1,1);
    Rect3i outCells(0,0,0,0,0,0);

    DynamicRLE3<int> rle;
    rle.mark(0,0,0,0,0,0,1);    // rle(0,0,0) = 1
    rle.mark(1,1,0,1,1,0,2);    // rle(1,1,0) = 2
    rle.mark(0,1,1,0,1,1,3);    // rle(0,1,1) = 3

    Vector3i shifts[] = { Vector3i(-2,0,0), Vector3i(2,0,0),
        Vector3i(0,-2,0), Vector3i(0,2,0),
        Vector3i(0,0,-2), Vector3i(0,0,2) };

    DynamicRLE3<int> out;
    DynamicRLE3<int> outSlow;

    for (int ss = 0; ss < 6; ss++)
    {
        BOOST_TEST_MESSAGE("Shift " << shifts[ss]);

        // Get (0,0,0)
        out = downsample2Wrap(rle, allCells+shifts[ss], allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss], allCells,
            outCells);
        BOOST_CHECK(out == outSlow);
        BOOST_CHECK_EQUAL(out(0,0,0), 1);
        BOOST_CHECK_EQUAL(outSlow(0,0,0), 1);

        // Get (1,0,0)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(1,0,0),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(1,0,0),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        BOOST_CHECK(!out.isMarked(0,0,0));
        BOOST_CHECK(!outSlow.isMarked(0,0,0));

        // Get (0,1,0)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(0,1,0),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(0,1,0),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        BOOST_CHECK(!out.isMarked(0,0,0));
        BOOST_CHECK(!outSlow.isMarked(0,0,0));

        // Get (1,1,0)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(1,1,0),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(1,1,0),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        BOOST_CHECK_EQUAL(out(0,0,0), 2);
        BOOST_CHECK_EQUAL(outSlow(0,0,0), 2);

        // Get (0,0,1)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(0,0,1),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(0,0,1),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        BOOST_CHECK(!out.isMarked(0,0,0));
        BOOST_CHECK(!outSlow.isMarked(0,0,0));

        // Get (1,0,1)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(1,0,1),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(1,0,1),
            allCells, outCells);
        BOOST_CHECK(!out.isMarked(0,0,0));
        BOOST_CHECK(!outSlow.isMarked(0,0,0));

        // Get (0,1,1)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(0,1,1),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(0,1,1),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        BOOST_CHECK_EQUAL(out(0,0,0), 3);
        BOOST_CHECK_EQUAL(outSlow(0,0,0), 3);

        // Get (1,1,1)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(1,1,1),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(1,1,1),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        BOOST_CHECK(!out.isMarked(0,0,0));
        BOOST_CHECK(!outSlow.isMarked(0,0,0));
    }
}

BOOST_AUTO_TEST_CASE( TestDownsample2WrapX )
{
    int outY = -3, outZ = 10;
    Rect3i allCells(-10,0,0,9,1,1);
    Rect3i outCells(-5,outY,outZ,4,outY,outZ);

    DynamicRLE3<int> rle;

    rle.mark(-10,0,0,9,0,0,1);
    rle.mark(-10,1,0,9,1,0,2);
    rle.mark(-10,0,1,9,0,1,3);
    rle.mark(-10,1,1,9,1,1,4);

    Vector3i shifts[] = { Vector3i(-2,0,0), Vector3i(2,0,0),
        Vector3i(0,-2,0), Vector3i(0,2,0),
        Vector3i(0,0,-2), Vector3i(0,0,2) };

    DynamicRLE3<int> out, outSlow;

    for (int ss = 0; ss < 6; ss++)
    {
        // Get row (:,0,0)
        out = downsample2Wrap(rle, allCells+shifts[ss], allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss], allCells,
            outCells);
        BOOST_CHECK(out == outSlow);
        for (int xx = -5; xx <= 4; xx++)
        {
            BOOST_CHECK_EQUAL(out(xx,outY,outZ), 1);
            BOOST_CHECK_EQUAL(outSlow(xx,outY,outZ), 1);
        }

        // Get row (:,1,0)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(0,1,0),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(0,1,0),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        for (int xx = -5; xx <= 4; xx++)
        {
            BOOST_CHECK_EQUAL(out(xx,outY,outZ), 2);
            BOOST_CHECK_EQUAL(outSlow(xx,outY,outZ), 2);
        }

        // Get row (:,0,1)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(0,0,1),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(0,0,1),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        for (int xx = -5; xx <= 4; xx++)
        {
            BOOST_CHECK_EQUAL(out(xx,outY,outZ), 3);
            BOOST_CHECK_EQUAL(outSlow(xx,outY,outZ), 3);
        }

        // Get row (:,1,1)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(0,1,1),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(0,1,1),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        for (int xx = -5; xx <= 4; xx++)
        {
            BOOST_CHECK_EQUAL(out(xx,outY,outZ), 4);
            BOOST_CHECK_EQUAL(outSlow(xx,outY,outZ), 4);
        }
    }
}

BOOST_AUTO_TEST_CASE( TestDownsample2WrapY )
{
    int outX = -100, outZ = -9;
    Rect3i allCells(0,-10,0,1,9,1);
    Rect3i outCells(outX,-5,outZ,outX,4,outZ);

    DynamicRLE3<int> rle;

    rle.mark(0,-10,0,0,9,0,1);
    rle.mark(1,-10,0,1,9,0,2);
    rle.mark(0,-10,1,0,9,1,3);
    rle.mark(1,-10,1,1,9,1,4);

    Vector3i shifts[] = { Vector3i(-2,0,0), Vector3i(2,0,0),
        Vector3i(0,-2,0), Vector3i(0,2,0),
        Vector3i(0,0,-2), Vector3i(0,0,2) };

    DynamicRLE3<int> out, outSlow;

    for (int ss = 0; ss < 6; ss++)
    {
        // Get row (0,:,0)
        out = downsample2Wrap(rle, allCells+shifts[ss], allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss], allCells,
            outCells);
        BOOST_CHECK(out == outSlow);
        for (int yy = -5; yy <= 4; yy++)
        {
            BOOST_CHECK_EQUAL(out(outX,yy,outZ), 1);
            BOOST_CHECK_EQUAL(outSlow(outX,yy,outZ), 1);
        }

        // Get row (1,:,0)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(1,0,0),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(1,0,0),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        for (int yy = -5; yy <= 4; yy++)
        {
            BOOST_CHECK_EQUAL(out(outX,yy,outZ), 2);
            BOOST_CHECK_EQUAL(outSlow(outX,yy,outZ), 2);
        }

        // Get row (0,:,1)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(0,0,1),
            allCells, outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(0,0,1),
            allCells,outCells);
        BOOST_CHECK(out == outSlow);
        for (int yy = -5; yy <= 4; yy++)
        {
            BOOST_CHECK_EQUAL(out(outX,yy,outZ), 3);
            BOOST_CHECK_EQUAL(outSlow(outX,yy,outZ), 3);
        }

        // Get row (1,:,1)
        out = downsample2Wrap(rle, allCells+shifts[ss]+Vector3i(1,0,1),
            allCells,outCells);
        outSlow = downsample2Wrap_slow(rle, allCells+shifts[ss]+Vector3i(1,0,1),
            allCells, outCells);
        BOOST_CHECK(out == outSlow);
        for (int yy = -5; yy <= 4; yy++)
        {
            BOOST_CHECK_EQUAL(out(outX,yy,outZ), 4);
            BOOST_CHECK_EQUAL(outSlow(outX,yy,outZ), 4);
        }
    }
}

BOOST_AUTO_TEST_CASE( TestDownsample2ClampX )
{
    Rect3i allCells(0,0,0,1,1,1);
    Rect3i outCells(0,0,0,0,0,0);

    DynamicRLE3<int> rle;
    rle.mark(0,0,0,0,0,0,1);    // rle(0,0,0) = 1
    rle.mark(1,1,0,1,1,0,2);    // rle(1,1,0) = 2
    rle.mark(0,1,1,0,1,1,3);    // rle(0,1,1) = 3

    DynamicRLE3<int> out;
    DynamicRLE3<int> outSlow;

    vector<Vector3i> shifts;
    vector<int> values;
    static const int UNMARKED = -1;

    shifts.push_back(Vector3i(-10,0,0));
    shifts.push_back(Vector3i(-10,-1,0));
    shifts.push_back(Vector3i(-10,1,0));
    shifts.push_back(Vector3i(-10,0,-1));
    shifts.push_back(Vector3i(-10,0,1));

    values.push_back(1);
    values.push_back(1);
    values.push_back(UNMARKED);
    values.push_back(1);
    values.push_back(UNMARKED);

    shifts.push_back(Vector3i(0,10,0));
    shifts.push_back(Vector3i(-1,10,0));
    shifts.push_back(Vector3i(1,10,0));
    shifts.push_back(Vector3i(0,10,-1));
    shifts.push_back(Vector3i(0,10,1));

    values.push_back(UNMARKED);
    values.push_back(UNMARKED);
    values.push_back(2);
    values.push_back(UNMARKED);
    values.push_back(3);

    shifts.push_back(Vector3i(0,0,-10));
    shifts.push_back(Vector3i(-1,0,-10));
    shifts.push_back(Vector3i(1,0,-10));
    shifts.push_back(Vector3i(0,-1,-10));
    shifts.push_back(Vector3i(0,1,-10));

    values.push_back(1);
    values.push_back(1);
    values.push_back(UNMARKED);
    values.push_back(1);
    values.push_back(UNMARKED);

    for (int ss = 0; ss < shifts.size(); ss++)
    {
        out = downsample2Clamp(rle, allCells+shifts[ss], allCells, outCells);
        outSlow = downsample2Clamp_slow(rle, allCells+shifts[ss], allCells,
            outCells);
        BOOST_CHECK(out == outSlow);
        if (values[ss] != UNMARKED)
            BOOST_CHECK_EQUAL(out(0,0,0), values[ss]);
        else
            BOOST_CHECK(!out.isMarked(0,0,0));
    }
}

BOOST_AUTO_TEST_CASE( SumDownsample2Clamp_AllFilled )
{
    Rect3i allCells(-1,-1,-1,0,0,0);
    Vector3i p(10,-10,-10);
    Rect3i outCells(p[0], p[1], p[2], p[0], p[1], p[2]); // put in a weird place

    DynamicRLE3<int> rle;

    // Test a case where all the cells are marked.

    int mark = 0;
    for (int zz = -1; zz <= 0; zz++)
    for (int yy = -1; yy <= 0; yy++)
    for (int xx = -1; xx <= 0; xx++)
        rle.mark(xx,yy,zz,xx,yy,zz, mark++);

    DynamicRLE3<int> out;

    // Sum all eight cells
    out = sumDownsample2Clamp(rle, allCells, allCells, outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 28);

    // Shift x - 1
    out = sumDownsample2Clamp(rle, allCells+Vector3i(-1,0,0), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 24);

    // Shift x - 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(-10,0,0), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 24);

    // Shift x + 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(10,0,0), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 32);

    // Shift y - 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(0,-10,0), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 20);

    // Shift y + 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(0,10,0), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 36);

    // Shift z - 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(0,0,-10), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 12);

    // Shift z + 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(0,0,10), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 44);
}

BOOST_AUTO_TEST_CASE( SumDownsample2Clamp_SomeFilled )
{
    Rect3i allCells(-1,-1,-1,0,0,0);
    Vector3i p(10,-10,-10);
    Rect3i outCells(p[0], p[1], p[2], p[0], p[1], p[2]); // put in a weird place

    DynamicRLE3<int> rle;

    // Test a case where all the cells are marked.

    int mark = 0;
    for (int zz = -1; zz <= 0; zz++)
    for (int yy = -1; yy <= 0; yy++)
    for (int xx = -1; xx <= 0; xx++)
        rle.mark(xx,yy,zz,xx,yy,zz, mark++);
    rle.erase(0,0,0,0,0,0);
    rle.erase(-1,-1,-1,-1,-1,-1);
    BOOST_CHECK_EQUAL(rle.numRuns(), 6);

    DynamicRLE3<int> out;

    // Sum all eight cells
    out = sumDownsample2Clamp(rle, allCells, allCells, outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 21);

    // Shift x - 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(-10,0,0), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 24);

    // Shift x + 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(10,0,0), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 18);

    // Shift y - 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(0,-10,0), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 20);

    // Shift y + 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(0,10,0), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 22);

    // Shift z - 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(0,0,-10), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 12);

    // Shift z + 10
    out = sumDownsample2Clamp(rle, allCells+Vector3i(0,0,10), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 30);

    // Shift + (-10,-10,-10)
    out = sumDownsample2Clamp(rle, allCells+Vector3i(-10,-10,-10), allCells,
        outCells);
    BOOST_CHECK(!out.isMarked(p[0], p[1], p[2]));

    // Shift + (10, -10, -10)
    out = sumDownsample2Clamp(rle, allCells+Vector3i(10,-10,-10), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 8*1);

    // Shift + (-10, 10, -10)
    out = sumDownsample2Clamp(rle, allCells+Vector3i(-10,10,-10), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 8*2);

    // Shift + (10, 10, -10)
    out = sumDownsample2Clamp(rle, allCells+Vector3i(10,10,-10), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 8*3);

    // Shift + (-10, -10, 10)
    out = sumDownsample2Clamp(rle, allCells+Vector3i(-10,-10,10), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 8*4);

    // Shift + (10, -10, 10)
    out = sumDownsample2Clamp(rle, allCells+Vector3i(10,-10,10), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 8*5);

    // Shift + (-10, 10, 10)
    out = sumDownsample2Clamp(rle, allCells+Vector3i(-10,10,10), allCells,
        outCells);
    BOOST_CHECK_EQUAL(out(p[0], p[1], p[2]), 8*6);

    // Shift + (10, 10, 10)
    out = sumDownsample2Clamp(rle, allCells+Vector3i(10,10,10), allCells,
        outCells);
    BOOST_CHECK(!out.isMarked(p[0], p[1], p[2]));
}


#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/error_of.hpp>
#include <boost/accumulators/statistics/error_of_mean.hpp>
#include <boost/accumulators/statistics/max.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/sum.hpp>
#include <boost/optional.hpp>
#include <chrono>
#include <cstdint>
#include <fstream>
#include <iostream>

namespace BM
{
/**
 * A class getting high resolution time durations.
 */
struct high_resolution_timer {
  high_resolution_timer() : m_start_time(take_time_stamp()) {}
  void
      /**
       * restart the timer
       */
      restart()
  {
    m_start_time = take_time_stamp();
  }

  /**
   * return the time, in seconds, since the timer was started.
   */
  double elapsed() const
  {
    return static_cast<double>(elapsed_nanoseconds()) * 1e-9;
  }

  /**
   * return the time, in nanoseconds, since the timer was
   * started.
   */
  std::uint64_t elapsed_nanoseconds() const
  {
    return take_time_stamp() - m_start_time;
  }

 private:
  std::uint64_t m_start_time;
  static std::uint64_t take_time_stamp()
  {
    // need to cast results of high_resolution_clock::now().time_since_epoch()
    // to nanoseconds in case system's minimum time resultion is different.
    // VS2012 for example does not return nanoseconds.
    return std::uint64_t(
        std::chrono::duration_cast<std::chrono::nanoseconds>(
            std::chrono::high_resolution_clock::now().time_since_epoch())
            .count());
  }
};

using namespace boost::accumulators;
/**
 * A class for accumulating timing statistice for benchmarks.
 */
typedef accumulator_set<
    double, stats<tag::count, tag::min, tag::max, tag::sum, tag::mean,
                  tag::variance, tag::error_of<tag::mean>>>
    timer_statistics;

/**
 * A class representing a measurement which consists of a nominal value with
 * uncertainty.
 */
template <typename T>
class Measurement
{
 public:
  T m_nominal;
  T m_uncertainty;

  Measurement() = default;
  Measurement(const Measurement&) = default;
  Measurement(T n, T u) : m_nominal(n), m_uncertainty(u) {}
  T nominal() const { return m_nominal; }
  T uncertainty() const { return m_uncertainty; }
  T upper() const { return m_nominal + m_uncertainty; }
  T lower() const { return m_nominal - m_uncertainty; }
  double relative_uncertainty() const { return m_uncertainty / m_nominal; }
  bool operator==(const Measurement& other) const
  {
    // two measurments are "consistent" if
    //
    // z^\prime = \frac{ |m_1 - m_2| }{ \sqrt{\Delta m_1^2 + \Delta m_2^2} } \ge
    // 2
    //
    T diff = std::abs(this->m_nominal - other.m_nominal);
    T unc = sqrt(this->m_uncertainty * this->m_uncertainty +
                 other.m_uncertainty * other.m_uncertainty);

    // don't want to inadvertently divide by zero.
    return diff <= 2 * unc;
  }

  bool operator<(const Measurement& other) const
  {
    // this measurment is less than the other if
    // 1. the two measurements are not consistent
    // 2. the nominal value of this is less than the other
    return !(*this == other) && this->m_nominal < other.m_nominal;
  }

  bool operator!=(const Measurement& other) const { return !(*this == other); }
  bool operator>(const Measurement& other) const
  {
    // this measurment is greater than the other if
    // 1. the two measurements are not consistent
    // 2. the nominal value of this is greater than the other
    return !(*this == other) && this->m_nominal > other.m_nominal;
  }

  bool operator<=(const Measurement& other) const
  {
    return (*this == other) || (*this < other);
  }

  bool operator>=(const Measurement& other) const
  {
    return (*this == other) || (*this > other);
  }

  template <typename TT>
  friend std::ostream& operator<<(std::ostream& os, const Measurement<TT>& uq)
  {
    os << uq.nominal() << " +/- " << uq.uncertainty();
    return os;
  }
};

struct Benchmark {
  high_resolution_timer m_timer;
  timer_statistics m_accum;
  double m_tolerance;
  size_t m_max_iters;
  size_t m_min_iters;
  size_t m_burn_iters;
  std::uint64_t m_max_runtime;  // max runtime in nanoseconds

  Benchmark()
      : m_tolerance(0.01),  // 1% error
        m_max_iters(100000),
        m_min_iters(10),
        m_burn_iters(10),
        m_max_runtime(100000000000ULL)
  {
  }

  /**
   * Add a duration to the Benchmark statistics
   */
  void add_time(std::uint64_t duration) { m_accum(duration); }
  /**
   * Executes a callable, returns the runtime in nanoseconds, and adds it to the
   * Benchmark statistics.
   */
  template <typename F>
  void record(const F& f)
  {
    m_timer.restart();
    f();
    auto duration = m_timer.elapsed_nanoseconds();
    add_time(duration);
  }

  /**
   * Executes a callable multiple times and accumulates statistics on the
   * runtime. The number of times the executable runs is determined by the
   * variance in run times. The callable will be ran until the relative variance
   * is less than the value stored in m_tolerance.
   */
  template <typename F>
  void run(const F& f)
  {
    high_resolution_timer run_timer;
    // allow algorithm to "burn in"
    reset();
    while (count(m_accum) < m_burn_iters &&
           run_timer.elapsed_nanoseconds() < m_max_runtime) {
      record(f);
    }

    reset();
    run_timer.restart();
    while (count(m_accum) < m_max_iters &&
           run_timer.elapsed_nanoseconds() < m_max_runtime &&
           (error_of<tag::mean>(m_accum) / mean(m_accum) > m_tolerance ||
            count(m_accum) < m_min_iters)) {
      record(f);
    }
    if (error_of<tag::mean>(m_accum) / mean(m_accum) > m_tolerance)
      std::cerr << "WARNING: relative error exceeds tolerance. Timing loop was "
                   "exited early for some reason.\n";
  }

  /**
   * Resets all benchmark statistics.
   */
  void reset() { m_accum = timer_statistics(); }
  /**
   * Returns the runtime measurement (in nanoseconds) compiled by the benchmark.
   * This will be a nominal value with an uncertainty that represents the error
   * of the mean.
   */
  Measurement<double> get_measurement() const
  {
    return Measurement<double>(mean(m_accum), error_of<tag::mean>(m_accum));
  }

  double get_longest_runtime() const { return max(m_accum); }
  double get_shortest_runtime() const { return min(m_accum); }
  double get_average_runtime() const { return mean(m_accum); }
  double get_stddev_runtime() const { return sqrt(variance(m_accum)); }
  double get_total_runtime() const { return sum(m_accum); }
  size_t get_iterations() const { return count(m_accum); }
  /**
   * Print the benchmarks current statistics.
   */
  void print_results(std::ostream& os) const
  {
    os << "iterations / max iterations: " << get_iterations() << " / "
       << m_max_iters << " (" << 100. * get_iterations() / m_max_iters
       << "%) \n";
    os << "total time / max allowed total runtime (ns): " << get_total_runtime()
       << " / " << m_max_runtime << " ("
       << 100 * get_total_runtime() / m_max_runtime << "%)\n";
    os << "min - max (ns): " << get_shortest_runtime() << " - "
       << get_longest_runtime() << "\n";
    os << "MEASUREMENT (ns): " << get_measurement() << "\n";
  }
};

/* // function to escape the optimizer */
/* static void escape(void* p) */
/* { */
/* #if defined(_MSC_VER) */
/* // TODO: find an implementation for VS */
/* #else */
/*   asm volatile("" : : "g"(p) : "memory"); */
/* #endif */
/* } */

/* static void clobber() */
/* { */
/* #if defined(_MSC_VER) */
/* // TODO: find an implementation for VS */
/* #else */
/*   asm volatile("" : : : "memory"); */
/* #endif */
/* } */

/**
 * A class for comparing benchmarks
 */
class BenchmarkComparison
{
};

/**
 * A class for testing performance improvements.
 */
class PerformanceBenchmark
{
 protected:
  std::string m_Name;
  boost::optional<Measurement<double>> m_Baseline;
  boost::optional<Measurement<double>> m_Minimum;

 public:
  struct Result {
    bool is_faster_than_baseline;
    Measurement<double> speedup_over_baseline;
    bool is_faster_than_minimum;
    Measurement<double> speedup_over_minimum;
  };

  static std::string getStorageFilename(std::string name)
  {
    return "PerformanceBenchmark-" + name + ".txt";
  }

  PerformanceBenchmark(const std::string& Name = "Default") : m_Name(Name)
  {
    std::string store = getStorageFilename(m_Name);

    std::ifstream in;
    in.open(store);
    if (in.is_open()) {
      double n, u;
      in >> n;
      in >> u;
      m_Baseline = Measurement<double>(n, u);
      in >> n;
      in >> u;
      m_Minimum = Measurement<double>(n, u);
    }
  }

  ~PerformanceBenchmark()
  {
    std::string store = getStorageFilename(m_Name);

    try {
      std::ofstream out;
      out.open(store);
      if (out.is_open()) {
        out << m_Baseline->nominal() << "\n";
        out << m_Baseline->uncertainty() << "\n";
        out << m_Minimum->nominal() << "\n";
        out << m_Minimum->uncertainty() << "\n";
      } else {
        std::cout << "ERROR: could not open " << store
                  << " to write benchmark data." << std::endl;
      }
      out.close();
    } catch (...) {
    }
  }

  Result operator()(const Benchmark& bench)
  {
    if (!m_Baseline) {
      m_Baseline = bench.get_measurement();
    }

    if (!m_Minimum) {
      m_Minimum = bench.get_measurement();
    }

    Measurement<double> measurement = bench.get_measurement();

    Result res;

    res.is_faster_than_baseline = measurement < m_Baseline;
    res.is_faster_than_minimum = measurement < m_Minimum;

    double s, dsn, dsd;

    s = m_Baseline->nominal() / measurement.nominal();
    dsn = m_Baseline->upper() / measurement.nominal() - s;
    dsd = m_Baseline->nominal() / measurement.upper() - s;
    res.speedup_over_baseline =
        Measurement<double>(s, sqrt(dsn * dsn + dsd * dsd));

    s = m_Minimum->nominal() / measurement.nominal();
    dsn = m_Minimum->upper() / measurement.nominal() - s;
    dsd = m_Minimum->nominal() / measurement.upper() - s;
    res.speedup_over_minimum =
        Measurement<double>(s, sqrt(dsn * dsn + dsd * dsd));

    if (measurement < m_Minimum.get()) {
      m_Minimum = measurement;
    }

    return res;
  }

  bool have_baseline() { return static_cast<bool>(m_Baseline); }
  Measurement<double> get_baseline()
  {
    if (m_Baseline) {
      return m_Baseline.get();
    } else {
      return Measurement<double>(0, 0);
    }
  }

  bool have_minimum() { return static_cast<bool>(m_Minimum); }
  Measurement<double> get_minimum()
  {
    if (m_Minimum) {
      return m_Minimum.get();
    } else {
      return Measurement<double>(0, 0);
    }
  }
};

}  // namespace BM


#include <Eigen/Core>
#include <Eigen/SparseCore>
#include <mimkl/definitions.hpp>
#include <mimkl/linear_algebra.hpp>
#include <numeric>
#include <sstream>
#include <string>
#include <vector>

using mimkl::definitions::Index;
typedef std::function<
void(const MATRIX(double) &, const MATRIX(double) &, MATRIX(double) &)>
void_fun;
typedef std::function<MATRIX(double)(const MATRIX(double) &, const MATRIX(double) &)>
nonvoid_fun;

class Example
{
    public:
    void set(std::string msg) { _msg = msg; }
    void many(const std::vector<std::string> &msgs);
    std::string greet() { return _msg; }

    private:
    std::string _msg;
};

double apply_5(std::function<double(double)>);
double add_to_3(double);
std::vector<double> map(std::function<double(double)>, std::vector<double>);
std::vector<double>
zip_map(std::vector<std::function<double(double)>>, std::vector<double>);
MATRIX(double) sum_plain_matrices(const std::vector<MATRIX(double)>);
MATRIX(double)
sum_matrices(const MATRIX(double) &,
             const MATRIX(double) &,
             const std::vector<nonvoid_fun> &);

template <typename Scalar, typename Function>
MATRIX(Scalar)
sum_matrices_temp(const MATRIX(Scalar) & lhs,
                  const MATRIX(Scalar) & rhs,
                  const std::vector<Function> &Ks)
{
    // fold over Ks
    return std::accumulate(Ks.begin(), Ks.end(),
                           MATRIX(Scalar)::Zero(lhs.rows(), rhs.rows()).eval(),
                           [&, lhs, rhs](const MATRIX(Scalar) & acc,
                                         const Function &fun) {
                               return acc + fun(lhs, rhs);
                           });
}

template <typename Scalar, typename Function>
MATRIX(Scalar)
sum_matrices_temp_inline(const MATRIX(Scalar) & lhs,
                         const MATRIX(Scalar) & rhs,
                         const std::vector<Function> &Ks)
{
    MATRIX(Scalar)
    acc = MATRIX(Scalar)::Zero(lhs.rows(), rhs.rows()).eval();
    // for( auto iter = Ks.begin(); iter != Ks.end(); ++iter){
    for (auto iter : Ks)
    {
        // acc = acc + (*iter)(lhs,rhs);
        acc += iter(lhs, rhs); // (*iter)
    }
    return acc;
}

template <typename Scalar, typename Function>
Scalar sum_temp(const Scalar &x, const Scalar &y, const std::vector<Function> &fs)
{
    // fold over Ks
    return std::accumulate(fs.begin(), fs.end(), (Scalar)0,
                           [&](const Scalar &acc, const Function &fun) {
                               return acc + fun(x, y);
                           });
}

template <typename Scalar, typename Function>
Scalar sum_mat_return_scalar_temp(const MATRIX(Scalar) & x,
                                  const MATRIX(Scalar) & y,
                                  const std::vector<Function> &fs)
{
    // fold over Ks
    return std::accumulate(fs.begin(), fs.end(), (Scalar)0,
                           [&](const Scalar &acc, const Function &fun) {
                               return acc + fun(x, y);
                           });
}

MATRIX(double)
sum_fun_matrices_reference(const MATRIX(double) &,
                           const MATRIX(double) &,
                           const std::vector<void_fun>);
void_fun convert_function(nonvoid_fun);
nonvoid_fun convert_function(void_fun);
std::vector<void_fun> convert_function(std::vector<nonvoid_fun>);
std::vector<nonvoid_fun> convert_function(std::vector<void_fun>);
MATRIX(double)
sum_fun_matrices_return(const MATRIX(double) &,
                        const MATRIX(double) &,
                        const std::vector<nonvoid_fun>);
MATRIX(double)
sum_converted_fun_matrices_return(const MATRIX(double) &,
                                  const MATRIX(double) &,
                                  const std::vector<void_fun>);

Eigen::SparseMatrix<double>
add_sparse(Eigen::SparseMatrix<double>, Eigen::SparseMatrix<double>);


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   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
//==============================================================================
#ifndef NT2_TRIGONOMETRIC_CONSTANTS_PIO2_1_HPP_INCLUDED
#define NT2_TRIGONOMETRIC_CONSTANTS_PIO2_1_HPP_INCLUDED

#include <nt2/include/functor.hpp>
#include <boost/simd/constant/hierarchy.hpp>
#include <boost/simd/constant/register.hpp>
namespace nt2
{
  namespace tag
  {
   /*!
     @brief Pio2_1 generic tag

     Represents the Pio2_1 constant in generic contexts.

     @par Models:
        Hierarchy
   **/
    // 1.57079632673412561417e+00
    BOOST_SIMD_CONSTANT_REGISTER( Pio2_1, double
                                , 1, 0x3fc90f80
                                , 0x3FF921FB54400000ll
                                )
  }
  namespace ext
  {
   template<class Site, class... Ts>
   BOOST_FORCEINLINE generic_dispatcher<tag::Pio2_1, Site> dispatching_Pio2_1(adl_helper, boost::dispatch::meta::unknown_<Site>, boost::dispatch::meta::unknown_<Ts>...)
   {
     return generic_dispatcher<tag::Pio2_1, Site>();
   }
   template<class... Args>
   struct impl_Pio2_1;
  }
  /*!
    Constant used in modular computation involving \f$\pi\f$

    @par Semantic:

    For type T0:

    @code
    T0 r = Pio2_1<T0>();
    @endcode

    @return a value of type T0
  **/
  BOOST_SIMD_CONSTANT_IMPLEMENTATION(tag::Pio2_1, Pio2_1);
}

#endif


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

#include <koinos/contracts/pow/pow.h>

#include <boost/multiprecision/cpp_int.hpp>

#include <vector>

using namespace koinos;
using namespace std::string_literals;

using uint256_t = boost::multiprecision::uint256_t;
using EmbeddedProto::FieldBytes;

enum class entries : uint32_t
{
   get_difficulty = 0x4a758831,
};

namespace constants {

constexpr std::size_t max_buffer_size         = 2048;
constexpr std::size_t max_signature_size      = 65;
constexpr std::size_t max_proof_size          = 128;
const std::string difficulty_metadata_key     = "";
constexpr std::size_t target_block_interval_s = 10;
constexpr uint64_t sha256_id                  = 0x12;
constexpr uint64_t pow_end_date               = 1640995199000; // 2021-12-31T23:59:59Z
constexpr uint64_t block_reward               = 10000000000;
constexpr uint32_t initial_difficulty_bits    = 24;

// 0x005b1e61d37259b9c2d99bf417f592e0b77725165d2488be45
const std::string koin_contract               = "\x00\x5b\x1e\x61\xd3\x72\x59\xb9\xc2\xd9\x9b\xf4\x17\xf5\x92\xe0\xb7\x77\x25\x16\x5d\x24\x88\xbe\x45"s;

} // constants

namespace state {

system::object_space contract_space()
{
   system::object_space obj_space;
   auto contract_id = system::get_contract_id();
   obj_space.mutable_zone().set( reinterpret_cast< const uint8_t* >( contract_id.data() ), contract_id.size() );
   obj_space.set_id( 0 );
   return obj_space;
}

}

using pow_signature_data = koinos::contracts::pow::pow_signature_data< 32, 65 >;
using difficulty_metadata = koinos::contracts::pow::difficulty_metadata< 32, 32 >;
using get_difficulty_metadata_result = koinos::contracts::pow::get_difficulty_metadata_result< 32, 32 >;
using process_block_signature_arguments = koinos::chain::process_block_signature_arguments<
      koinos::system::detail::max_hash_size,
      koinos::system::detail::max_hash_size,
      koinos::system::detail::max_hash_size,
      koinos::system::detail::max_hash_size,
      koinos::system::detail::max_address_size,
      constants::max_proof_size >;

template< uint32_t MAX_LENGTH >
void to_binary( FieldBytes< MAX_LENGTH >& f, const uint256_t& n )
{
   static_assert( MAX_LENGTH >= 32 );
   std::vector< uint8_t > bin;
   bin.reserve( 32 );
   boost::multiprecision::export_bits( n, std::back_inserter( bin ), 8 );

   std::size_t leading_zeros = 32 - bin.size();
   for( std::size_t i = 0; i < leading_zeros; i++ )
      f[i] = 0;
   for( std::size_t i = 0; i < bin.size(); i++ )
      f[i + leading_zeros] = bin[i];
}

template< uint32_t MAX_LENGTH >
void from_binary( const FieldBytes< MAX_LENGTH >& f, uint256_t& n, size_t start = 0 )
{
   assert( MAX_LENGTH >= start + 32 );
   std::vector< uint8_t > bin;

   for ( size_t i = start; i < start + 32; i++ )
   {
      bin.push_back( f[i] );
   }

   boost::multiprecision::import_bits( n, bin.begin(), bin.end(), 8 );
}

void initialize_difficulty( difficulty_metadata& diff_meta )
{
   uint256_t target = std::numeric_limits< uint256_t >::max() / (1 << constants::initial_difficulty_bits);
   auto difficulty = 1 << constants::initial_difficulty_bits;
   to_binary( diff_meta.mutable_target(), target );
   diff_meta.set_last_block_time( system::get_head_info().get_head_block_time() );
   to_binary( diff_meta.mutable_difficulty(), difficulty );
   diff_meta.set_target_block_interval( constants::target_block_interval_s );
}

difficulty_metadata get_difficulty_meta()
{
   difficulty_metadata diff_meta;
   if ( !system::get_object( state::contract_space(), constants::difficulty_metadata_key, diff_meta ) )
   {
      initialize_difficulty( diff_meta );
   }

   return diff_meta;
}

void update_difficulty( difficulty_metadata& diff_meta, uint64_t current_block_time )
{
   uint256_t difficulty;
   from_binary( diff_meta.get_difficulty(), difficulty );
   difficulty = difficulty + difficulty / 2048 * std::max(1 - int64_t((current_block_time - diff_meta.last_block_time()) / 7000), -99ll);
   to_binary( diff_meta.mutable_difficulty(), difficulty );
   diff_meta.set_last_block_time( current_block_time );
   auto target = std::numeric_limits< uint256_t >::max() / difficulty;
   to_binary( diff_meta.mutable_target(), target );

   system::put_object( state::contract_space(), constants::difficulty_metadata_key, diff_meta );
}

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

   std::array< uint8_t, constants::max_buffer_size > retbuf;
   koinos::write_buffer buffer( retbuf.data(), retbuf.size() );

   if ( entry_point == std::underlying_type_t< entries >( entries::get_difficulty ) )
   {
      get_difficulty_metadata_result res;
      res.set_value( get_difficulty_meta() );
      res.serialize( buffer );
      std::string retval( reinterpret_cast< const char* >( buffer.data() ), buffer.get_size() );
      system::set_contract_result_bytes( retval );
      return 0;
   }

   koinos::chain::process_block_signature_result ret;
   ret.mutable_value() = false;

   auto head_block_time = system::get_head_info().get_head_block_time();
   if ( uint64_t( head_block_time ) > constants::pow_end_date )
   {
      system::print( "testnet has ended" );
      ret.serialize( buffer );
      std::string retval( reinterpret_cast< const char* >( buffer.data() ), buffer.get_size() );
      system::set_contract_result_bytes( retval );
      return 0;
   }

   const auto [ caller, privilege ] = system::get_caller();
   if ( privilege != chain::privilege::kernel_mode )
   {
      system::print( "pow contract must be called from kernel" );\
      ret.serialize( buffer );
      std::string retval( reinterpret_cast< const char* >( buffer.data() ), buffer.get_size() );
      system::set_contract_result_bytes( retval );
      return 0;
   }

   auto argstr = system::get_contract_arguments();
   koinos::read_buffer rdbuf( (uint8_t*)argstr.c_str(), argstr.size() );
   process_block_signature_arguments args;
   args.deserialize( rdbuf );

   pow_signature_data sig_data;
   rdbuf = koinos::read_buffer( const_cast< uint8_t* >( reinterpret_cast< const uint8_t* >( args.get_signature().get_const() ) ), args.get_signature().get_length() );
   sig_data.deserialize( rdbuf );

   std::string nonce_str( reinterpret_cast< const char* >( sig_data.get_nonce().get_const() ), sig_data.get_nonce().get_length() );
   std::string digest_str( const_cast< char* >( reinterpret_cast< const char* >( args.get_digest().get_const() ) ) + 2, args.get_digest().get_length() - 2 );
   nonce_str.insert( nonce_str.end(), digest_str.begin(), digest_str.end() );

   auto pow = system::hash( constants::sha256_id, nonce_str );

   // Get/update difficulty from database
   auto diff_meta = get_difficulty_meta();

   if ( memcmp( pow.c_str() + 2, diff_meta.get_target().get_const(), pow.size() - 2 ) > 0 )
   {
      system::print( "pow did not meet target\n" );
      ret.serialize( buffer );
      std::string retval( reinterpret_cast< const char* >( buffer.data() ), buffer.get_size() );
      system::set_contract_result_bytes( retval );
      return 0;
   }

   update_difficulty( diff_meta, head_block_time );

   // Recover address from signature
   std::string sig_str( reinterpret_cast< const char* >( sig_data.get_recoverable_signature().get_const() ), sig_data.get_recoverable_signature().get_length() );
   digest_str = std::string( reinterpret_cast< const char* >( args.get_digest().get_const() ), args.get_digest().get_length() );
   auto producer = system::recover_public_key( sig_str, digest_str );

   std::string signer( reinterpret_cast< const char* >( args.get_header().get_signer().get_const() ), args.get_header().get_signer().get_length() );

   if ( producer != signer )
   {
      system::print( "signature and signer are mismatching\n" );
      ret.serialize( buffer );
      std::string retval( reinterpret_cast< const char* >( buffer.data() ), buffer.get_size() );
      system::set_contract_result_bytes( retval );
      return 0;
   }

   // Mint block reward to address
   auto koin_token = koinos::token( constants::koin_contract );

   auto success = koin_token.mint( signer, constants::block_reward );
   if ( !success )
   {
      system::print( "could not mint KOIN to producer address " );
   }

   ret.set_value( success );

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

   system::set_contract_result_bytes( retval );

   return 0;
}


#include <boost/python/module.hpp>
#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/args.hpp>
#include <mmtbx/utils/utils.h>
#include <boost/python/return_value_policy.hpp>
#include <boost/python/return_by_value.hpp>

namespace mmtbx { namespace utils {
namespace {

  void init_module()
  {
    using namespace boost::python;
    using boost::python::arg;
    typedef return_value_policy<return_by_value> rbv;

    class_<fit_hoh<> >("fit_hoh")
       .def(init<
            cctbx::fractional<> const&,
            cctbx::fractional<> const&,
            cctbx::fractional<> const&,
            cctbx::fractional<> const&,
            cctbx::fractional<> const&,
            double const&,
            cctbx::uctbx::unit_cell const& >((arg("site_frac_o"),
                                              arg("site_frac_h1"),
                                              arg("site_frac_h2"),
                                              arg("site_frac_peak1"),
                                              arg("site_frac_peak2"),
                                              arg("angular_shift"),
                                              arg("unit_cell"))))
       .add_property("site_cart_o_fitted",
          make_getter(&fit_hoh<>::site_cart_o_fitted, rbv()))
       .add_property("site_cart_h1_fitted",
          make_getter(&fit_hoh<>::site_cart_h1_fitted, rbv()))
       .add_property("site_cart_h2_fitted",
          make_getter(&fit_hoh<>::site_cart_h2_fitted, rbv()))
       .def("dist_best", &fit_hoh<>::dist_best)
    ;
    //
    class_<density_distribution_per_atom<> >("density_distribution_per_atom")
       .def(init<
            af::ref<vec3<double> > const&,
            af::const_ref<vec3<double> > const&,
            af::const_ref<double> const&,
            cctbx::uctbx::unit_cell const& >((
                                              arg("sites_frac_atoms"),
                                              arg("sites_frac_peaks"),
                                              arg("density_values"),
                                              arg("unit_cell"))))
       .def("distances", &density_distribution_per_atom<>::distances)
       .def("map_values", &density_distribution_per_atom<>::map_values)
    ;
    //

    def("filter_water",
         (af::shared<std::size_t>(*)
               (af::shared<vec3<double> > const&,
                af::shared<vec3<double> > const&,
                double const&,
                double const&,
                cctbx::uctbx::unit_cell const&)) filter_water,
                  (arg("sites_frac"),
                   arg("sites_frac_water"),
                   arg("dist_max"),
                   arg("dist_min"),
                   arg("unit_cell")))
   ;

    def("select_water_by_distance",
         (af::shared<std::size_t>(*)
               (af::shared<vec3<double> > const&,
                af::shared<std::string> const&,
                af::shared<std::size_t> const&,
                double const&,
                double const&,
                double const&,
                cctbx::uctbx::unit_cell const&)) select_water_by_distance,
                                                          (arg("sites_frac_all"),
                                                           arg("element_symbols_all"),
                                                           arg("water_selection_o"),
                                                           arg("dist_max"),
                                                           arg("dist_min_mac"),
                                                           arg("dist_min_sol"),
                                                           arg("unit_cell")))
   ;
       def("correct_drifted_waters",
         (void(*)
               (af::ref<vec3<double> > const&,
                af::const_ref<vec3<double> > const&,
                af::const_ref<bool> const&,
                cctbx::uctbx::unit_cell const&)) correct_drifted_waters,
                                                          (arg("sites_frac_all"),
                                                           arg("sites_frac_peaks"),
                                                           arg("water_selection"),
                                                           arg("unit_cell")))
   ;

   def("create_twin_mate",
         (af::shared<cctbx::miller::index<> >(*)
               (af::const_ref<cctbx::miller::index<> > const&,
                scitbx::mat3<double>)) create_twin_mate,
                  (arg("miller_indices"),
                   arg("twin_law_matrix")))
   ;

   def("apply_twin_fraction",
         (af::shared<double>(*)
               (af::const_ref<double> const&,
                af::const_ref<double> const&,
                double const&)) apply_twin_fraction,
                  (arg("amplitude_data_part_one"),
                   arg("amplitude_data_part_two"),
                   arg("twin_fraction")))
   ;

  }

} // namespace <anonymous>
}} // namespace mmtbx::utils

BOOST_PYTHON_MODULE(mmtbx_utils_ext)
{
  mmtbx::utils::init_module();
}


//
//  main.cpp
//  eigendemo
//
//  Created by Daniel Grigg on 1/04/2014.
//  Copyright (c) 2014 Daniel Grigg. All rights reserved.
//

#include <iostream>
#include <algorithm>
#include <Eigen/Eigen>
#include <Eigen/OpenGLSupport>

using namespace Eigen;
int main() {
  float rawA[16];

  Matrix4f A = Matrix4f::Random();
  std::cout << "A:\n" << A << std::endl;

  // Stored column-major weee
  std::copy(A.data(), A.data() + 16, rawA);
  rawA[1] = 1234.1234;
  Map<Matrix4f> M(rawA, 4, 4);
  std::cout << "M:\n" << M << std::endl;
  return 0;
}


//
// Encrypter.cpp
//

#include <boost/format.hpp>
#include <sha.h>
#include <whrlpool.h>
#include <osrng.h>
#include "Encrypter.hpp"
#include "Utils.hpp"

#ifdef _WIN32
#pragma comment(lib, "cryptlib.lib")
#endif

namespace network {

const int Encrypter::TRIP_LENGTH = 12;

using namespace CryptoPP;

Encrypter::Encrypter()
{
    AutoSeededRandomPool rnd;

    byte common_key[AES::DEFAULT_KEYLENGTH];
    byte common_key_iv[AES::BLOCKSIZE];
    rnd.GenerateBlock(common_key, sizeof(common_key));
    rnd.GenerateBlock(common_key_iv, sizeof(common_key_iv));

    common_key_ = std::string((const char*)common_key, sizeof(common_key));
    common_key_iv_ = std::string((const char*)common_key_iv, sizeof(common_key_iv));

    aes_encrypt_.SetKeyWithIV(common_key, sizeof(common_key), common_key_iv);
    aes_decrypt_.SetKeyWithIV(common_key, sizeof(common_key), common_key_iv);

    InvertibleRSAFunction params;
    params.GenerateRandomWithKeySize(rnd, 3072);

    private_key_ = RSA::PrivateKey(params);
    public_key_ = RSA::PublicKey(params);
}

Encrypter::~Encrypter()
{
}

std::string Encrypter::Encrypt(const std::string& in)
{
     std::unique_ptr<char[]> outbuf(new char [in.size()]);
     aes_encrypt_.ProcessData((byte*)outbuf.get(), (const byte*)in.data(), in.size());
     std::string out((const char*)outbuf.get(), in.size());
     return out;
}

std::string Encrypter::Decrypt(const std::string& in)
{
     std::unique_ptr<char[]> outbuf(new char [in.size()]);
     aes_decrypt_.ProcessData((byte*)outbuf.get(), (const byte*)in.data(), in.size());
     std::string out((const char*)outbuf.get(), in.size());
     return out;
}

std::string Encrypter::GetPublicKey()
{
    ByteQueue queue;
    public_key_.Save(queue);

    size_t length = static_cast<size_t>(queue.CurrentSize());
    std::unique_ptr<char[]> outbuf(new char [length]);
    queue.Get((byte*)outbuf.get(), length);

    return std::string((const char*)outbuf.get(), length);
}

void Encrypter::SetPublicKey(const std::string& in)
{
    if (in.empty()) return;
    ByteQueue queue;
    queue.Put((const byte*)in.data(), in.size());
    public_key_.Load(queue);
}

std::string Encrypter::GetPrivateKey()
{
    ByteQueue queue;
    private_key_.Save(queue);

    size_t length = static_cast<size_t>(queue.CurrentSize());
    std::unique_ptr<char[]> outbuf(new char [length]);
    queue.Get((byte*)outbuf.get(), length);

    return std::string((const char*)outbuf.get(), length);
}

void Encrypter::SetPrivateKey(const std::string& in)
{
    if (in.empty()) return;
    ByteQueue queue;
    queue.Put((const byte*)in.data(), in.size());
    private_key_.Load(queue);
}

void Encrypter::SetPairKey(const std::string& pub, const std::string& pri)
{
    SetPublicKey(pub);
    SetPrivateKey(pri);
}

std::string Encrypter::GetCryptedCommonKey()
{
    return PublicEncrypt(GetCommonKey());
}

void Encrypter::SetCryptedCommonKey(const std::string& in)
{
    std::string key = PublicDecrypt(in);
    common_key_ = key.substr(0, AES::DEFAULT_KEYLENGTH);
    common_key_iv_ = key.substr(AES::DEFAULT_KEYLENGTH, AES::BLOCKSIZE);

    aes_encrypt_.SetKeyWithIV((const byte*)common_key_.data(), common_key_.size(), (const byte*)common_key_iv_.data());
    aes_decrypt_.SetKeyWithIV((const byte*)common_key_.data(), common_key_.size(), (const byte*)common_key_iv_.data());
}

std::string Encrypter::PublicEncrypt(const std::string& in)
{
    AutoSeededRandomPool rng;
    RSAES_OAEP_SHA_Encryptor encryptor(public_key_);

    // Now that there is a concrete object, we can validate
    assert(0 != encryptor.FixedMaxPlaintextLength());
    assert(in.size() <= encryptor.FixedMaxPlaintextLength());

    // Create cipher text space
    size_t ecl = encryptor.CiphertextLength(in.size());
    assert(0 != ecl);
    SecByteBlock ciphertext(ecl);

    encryptor.Encrypt(rng, (const byte*)in.data(), in.size(), ciphertext);

    return std::string(ciphertext.begin(), ciphertext.end());
}

std::string Encrypter::PublicDecrypt(const std::string& in)
{
    AutoSeededRandomPool rng;
    RSAES_OAEP_SHA_Decryptor decryptor(private_key_);

    // Now that there is a concrete object, we can check sizes
    assert(0 != decryptor.FixedCiphertextLength());
    assert(in.size() <= decryptor.FixedCiphertextLength());

    // Create recovered text space
    size_t dpl = decryptor.MaxPlaintextLength(in.size());
    assert(0 != dpl);
    SecByteBlock recovered(dpl);

    DecodingResult result = decryptor.Decrypt(rng, (const byte*)in.data(), in.size(), recovered);

    return std::string(recovered.begin(), recovered.end());
}

std::string Encrypter::GetPublicKeyFingerPrint()
{
    return GetHash(GetPublicKey());
}

std::string Encrypter::GetHash(const std::string& in)
{
    std::unique_ptr<byte[]> outbuf(new byte [64]);
	CryptoPP::Whirlpool().CalculateDigest(outbuf.get(), (const byte*)in.data(), in.size());
    return std::string((char*)outbuf.get(), 64);
}

std::string Encrypter::GetTripHash(const std::string& in)
{
    std::unique_ptr<byte[]> outbuf(new byte [64]);
	CryptoPP::SHA512().CalculateDigest(outbuf.get(), (const byte*)in.data(), in.size());
    return std::string((char*)outbuf.get(), 64);
}

std::string Encrypter::GetTrip(const std::string& in)
{
    static const uint8_t trip_chars[] =
            "opt0uXE{WZABCcdvi&_gMrsmn9)<"
			"Q(TU}q5wx:DP3yFKL1Gab>|@=*8[$%"
            "NO6Ye#-!?R4ShHIJV72)f+](jklz/\\";

    int pattern_size = sizeof(trip_chars) - 1;

	std::string digest = GetTripHash(in);
	for (int i = 0; i < 20; i++) {
		digest = GetTripHash(digest + in) + GetHash(in + digest);
	}

    auto buffer = digest.substr(0, TRIP_LENGTH);
    std::string out;

    for (auto it = buffer.begin(); it != buffer.end(); ++it) {
        const uint8_t & c = *it;
        out += trip_chars[c % pattern_size];
    }

    return out;
}

bool Encrypter::CheckKeyPair()
{
    // 復号化できるかチェック
    std::string test_data("test");
    bool result = (PublicDecrypt(PublicEncrypt(test_data)).substr(0, 4) == test_data);
    return result;
}

std::string Encrypter::GetCommonKey()
{
    return common_key_ + common_key_iv_;
}

}


#include "duck.h"
#include "vertices.h"

#include <GL/glew.h>
#include <GL/glfw.h>
#include "opengp.h"

#include <Eigen/Geometry>

#include "duck_vshader.h"
#include "duck_fshader.h"

RenderedDuck::RenderedDuck(unsigned int width, unsigned int height):
	RenderingContext(width, height) {
}

void RenderedDuck::init(Vertices* vertices) {

	/// Common initialization.
    preinit(vertices, duck_vshader, duck_fshader, NULL, "vertexPosition3DWorld", "normal_mv");

    /// Light properties. (no specular)
	/// Define light properties and pass them to the shaders.
    vec3 Ia(1.0f, 1.0f, 1.0f);
    vec3 Id(1.0f, 1.0f, 1.0f);
    vec3 Is(1.0f, 1.0f, 1.0f);
    GLuint _IaID = glGetUniformLocation(_programID, "Ia");
    GLuint _IdID = glGetUniformLocation(_programID, "Id");
    glUniform3fv( _IaID, 1, Ia.data());
    glUniform3fv( _IdID, 1, Id.data());

    /// Set uniform IDs.
    _viewID = glGetUniformLocation(_programID, "view");
    _projectionID = glGetUniformLocation(_programID, "projection");
	_transID = glGetUniformLocation(_programID, "translation");
	_lightPositionWorldID = glGetUniformLocation(_programID, "lightPositionWorld");

	_aniMatID = glGetUniformLocation(_programID, "animation");
}

void RenderedDuck::draw(const mat4& projection, const mat4 views[], const vec3& lightPositionWorld) {

	 /// Common drawing.
    predraw();

    /// Update the content of the uniforms.
    glUniformMatrix4fv( _projectionID, 1, GL_FALSE, projection.data());
	glUniform3fv(_lightPositionWorldID, 1, lightPositionWorld.data());

    /// Do not clear the framebuffers : done by Terrain.
    /// Otherwise already drawn pixels will be cleared.

    /// Flip the terrain by multiplying the Z coordinate by -1 in world space.
    //mat4 flip = mat4::Identity();
    //flip(2,2) = -1.0f;
    //mat4 viewFlip = view * flip;

    /// Render the terrain from camera point of view to the reflection FBO.
 /*   glUniformMatrix4fv(_viewID, 1, GL_FALSE, viewFlip.data());
    _vertices->draw();*/

	/* Translation matrix */
	mat4 trans_mat;
	trans_mat << 1,0,0, -0.55,
				 0,1,0, 0.15,
				 0,0,1, 0,
				 0,0,0,1;
	glUniformMatrix4fv(_transID, 1, GL_FALSE, trans_mat.data());

	/* For Animation: Move circular around a fixed point */
	//float radius =
	static int step = 0;
	mat4 ani_mat;
	ani_mat << 1,0,0, 0 ,
			   0,1,0,float(step%1000)*0.0005,
			   0,0,1, 0,
			   0,0,0,1;
	glUniformMatrix4fv(_aniMatID, 1, GL_FALSE, ani_mat.data());
	step++;

    /// Render from camera point of view to 'normal' FBOs.
    glUniformMatrix4fv(_viewID, 1, GL_FALSE, views[0].data());
    glBindFramebuffer(GL_FRAMEBUFFER, framebufferIDs["controllerView"]);
    _vertices->draw();
    glUniformMatrix4fv(_viewID, 1, GL_FALSE, views[1].data());
    glBindFramebuffer(GL_FRAMEBUFFER, framebufferIDs["cameraView"]);
    _vertices->draw();

    /// Flip the world by multiplying the Z coordinate by -1 in world space.
    mat4 flip = mat4::Identity();
    flip(2,2) = -1.0f;
    mat4 viewFlip[] = {views[0]*flip, views[1]*flip};

    /// Render from flipped camera point of view to 'reflection' FBOs.
    glUniformMatrix4fv(_viewID, 1, GL_FALSE, viewFlip[0].data());
    glBindFramebuffer(GL_FRAMEBUFFER, framebufferIDs["controllerViewReflected"]);
    _vertices->draw();
    glUniformMatrix4fv(_viewID, 1, GL_FALSE, viewFlip[1].data());
    glBindFramebuffer(GL_FRAMEBUFFER, framebufferIDs["cameraViewReflected"]);
    _vertices->draw();
}


//
// Created by Joris on 09/07/2018.
//

// This code performs an iterative Monte Carlo procedure to obtain a Maximum Entropy
// model for the chromosome organization of C. crescentus

#include <iostream>
#include <Eigen/Dense>
#include <vector>
#include <random>
#include <chrono>
#include <fstream>
#include <unordered_map>
#include <thread>
#include "Initialize.h"
#include "Moves.h"
using namespace Eigen;

const int number_of_threads = 36;

const double diameter = 6.4;
const int length_cylinder = 21;
const int midway = int(ceil(length_cylinder/2));
const int cap_length = 2;

const int pol_length = 1620;

const long int mc_moves_start = 100000000;
long int mc_moves;
const int burn_in_time = 20000000;
const int update_steps = 30;
const double learning_rate = 0.5;

std::vector<std::vector<Vector3i>> polymer(number_of_threads);

bool boundary_cond = 1; //enforces boundary conditions if 1
bool orient = 1; //orients the cell such that the origin is always in the left half

std::vector< std::vector<double>> Interaction_E(pol_length, std::vector<double>(pol_length,0));

std::uniform_real_distribution<double> unif(0.0,1.0);
std::uniform_int_distribution<int> unimove(0,2);
std::uniform_int_distribution<int> unisite(0,pol_length-1);
std::vector<std::vector<std::vector<double>>> total_contacts(number_of_threads, std::vector< std::vector<double>>(pol_length, std::vector<double>(pol_length, 0)));
std::vector<std::vector<double>> final_contacts(pol_length, std::vector<double>(pol_length, 0));

void move(std::vector<Vector3i> &polymer,int thread_num, int m){ //performs a single Monte Carlo step
    int action;
    int site;

    action = unimove(gen);
    site = unisite(gen);
    if (action==0){
        kink_move(polymer,site, thread_num,m);
    }
    else if (action==1){
        crankshaft_move(polymer,site, thread_num,m);
    }
    else if (action==2){
       loop_move(polymer,site, thread_num,m);
    }
}

void run_burnin(int thread_num, int mc_moves) { //burns in the polymer configurations
    for (int m = 1; m < mc_moves; m++) {
        move(polymer[thread_num], thread_num, m);
    }
}

void run(int thread_num, int mc_moves) {
    for (int m = 1; m < mc_moves; m++) {    //performs a forward polymer simulation
        move(polymer[thread_num], thread_num, m);
    }
}

//after each forward run, the polymer interaction energies are updated according to the pairwise difference
// between model contact frequencies and experimental contact frequencies
void update_energies(std::vector< std::vector<double>> &total_contacts, std::vector< std::vector<double>> &reference_contacts, std::vector< std::vector<double>> &Interaction_E) {

    float checksum = 0;
    for (int i = 0; i < pol_length/4; i++) {
        for (int j = i+1; j < pol_length/4; j++) {
            if ((i != (j+1)%(pol_length/4)) && (j != (i+1)%(pol_length/4))) {
                Interaction_E[4 * i][4 * j] += learning_rate * sqrt(1/(std::max(reference_contacts[4*i][4*j],0.0001)))*(float(total_contacts[4 * i][4 * j])- reference_contacts[4 * i][4 * j]);
                checksum += float(total_contacts[4 * i][4 * j])*(pol_length/4);
                Interaction_E[4 * j][4 * i] = Interaction_E[4 * i][4 * j];
            }
        }
    }

    //calculates the shift of all energies, imposed to ensure a MaxEnt solution is found for the contact frequency scale
    float shift = 0;
    for (int i = 0; i < pol_length/4; i++) {
        for (int j = i + 2; j < pol_length / 4; j++) {
           shift += 2*(Interaction_E[4 * i][4 * j] * reference_contacts[4 * i][4 * j]) / (pol_length / 4);
        }
    }

    std::cout << "Shift: " << shift << std::endl;
    for (int i = 0; i < pol_length/4; i++) {
        for (int j = i + 2; j < pol_length / 4; j++) {
            //if ((i != (j + 1) % (pol_length / 2)) && (j != (i + 1) % (pol_length / 2)) && not(i >=0 && i<=30 && j>=150 && j<=220) && not(i >=150 && i <=230 && j>=370 && j<=410) ) {
            Interaction_E[4 * i][4 * j] -= shift;
            Interaction_E[4 * j][4 * i] = Interaction_E[4 * i][4 * j];
            //}
        }
    }
}

//Normalizes model contact frequencies to allow for comparison with experimental data
void normalize() {
    double sum = 0;
    for (int i = 0; i < pol_length; i++) {
        for (int j = 0; j < pol_length; j++) {
            if (i%4 == 0 && j%4 == 0 && i!=(j+4)%pol_length && j!=(i+4)%pol_length) {
                sum += final_contacts[i][j];
            }
        }
    }
    for (int i = 0; i < pol_length; i++) {
        for (int j = 0; j < pol_length; j++) {
            final_contacts[i][j] *= double(pol_length)/(8*sum);
        }
    }
}

int main() {
    auto start = std::chrono::high_resolution_clock::now();
    std::cout << "started! " << std::endl;

    for (int l = 0; l < number_of_threads; l++) {
        initialize(polymer[l], pol_length, l);
    }
    std::cout << "Initialized " << std::endl;

    // Read in starting interaction energies
    std::ifstream couplings;
    couplings.open("/media/joris/raid_data/Chromosome_Maxent/Dataprocess_check_review/Processed_new_r0/Inverse/Energies_Intermediate/energies_intermediate_49.txt");
    //couplings.open ("/media/joris/raid_data/Chromosome_Maxent/Saved_energies/energies_smallcell.txt");
    for (int i = 0; i < pol_length; i++) { //read in energies
        for (int j = 0; j < pol_length; j++) {
            couplings >> Interaction_E[i][j];
        }
    }
    couplings.close();

    // Read in reference contacts
    std::vector<std::vector<double>> reference_contacts(pol_length, std::vector<double>(pol_length));
    std::ifstream input_contacts;
    input_contacts.open("/media/joris/raid_data/Chromosome_Maxent/experimental_data_noisecancel_r1.txt");
    for (int i = 0; i < pol_length / 4; i++) { //read in contacts
        for (int j = 0; j < pol_length / 4; j++) {
            input_contacts >> reference_contacts[4 * i][4 * j];
            if (reference_contacts[4*i][4*j] == 0 && i != j && i != (j + 1) % (pol_length / 2) &&
                j != (i + 1) % (pol_length / 2)) {
                Interaction_E[4 * i][4 * j] = 10;
            }
        }
    }
    input_contacts.close();

    // burn in configurations
    std::vector<std::thread> threads(number_of_threads);
    for (auto l = 0; l < number_of_threads; l++) {
        threads[l] = std::thread(run_burnin, l, burn_in_time);
    }
    for (auto &&l : threads) {
        l.join();
    }

    std::cout << "Done with burn in " << std::endl;

    for (int n =0; n<update_steps;n++) { //do iterative update scheme
        mc_moves = mc_moves_start*sqrt(n+10)/sqrt(10); //number of MC moves grows with each iteration (implicitly converted to long int)

        for (int l = 0; l < number_of_threads; l++) {
            for (int i = 0; i < pol_length; i++) { //reset contact frequencies before starting new forward round
                for (int j = 0; j < pol_length; j++) {
                    total_contacts[l][i][j] = 0;
                    if (l==0){
                        final_contacts[i][j] =0;
                    }
                }

            }
            for (auto elem : contacts[l]) { //reset contacts
                contacts[l][elem.first] = 0;
            }
        }

        //run forward simulation
        for (auto l = 0; l < number_of_threads; l++) {
            threads[l] = std::thread(run, l, mc_moves);
        }
        for (auto &&l : threads) {
            l.join();
        }
        // read in remaining contacts at the end of forward simulation
        for (auto l = 0; l < number_of_threads; l++) {
            for (auto elem : contacts[l]) { //add the contacts remaining at the end of the simulation
                total_contacts[l][elem.first.first][elem.first.second] += mc_moves - elem.second;
                contacts[l][elem.first] = 0;
            }
        }

        //add up contacts from threads
        for (int i = 0; i < number_of_threads; i++) {
            for (int j = 0; j < pol_length; j++) {
                for (int k = 0; k < pol_length; k++) {
                    final_contacts[j][k] += total_contacts[i][j][k];
                }
            }
        }

        //normalize contact frequencies
        normalize();

        //update energies
        update_energies(final_contacts,reference_contacts,Interaction_E);

        //output intermediate results
        std::ofstream intermediate_energies;
        intermediate_energies.open ("/media/joris/raid_data/Chromosome_Maxent/Dataprocess_check_review/Processed_new_r0/Inverse/Energies_Intermediate/energies_intermediate_" + std::to_string(n) + ".txt");
        for(int i = 0; i < pol_length; i++){ //output contact frequencies
            for(int j = 0; j < pol_length; j++){
                intermediate_energies << Interaction_E[i][j] << std::endl;
            }
        }
        intermediate_energies.close();

        std::ofstream intermediate_contacts;
        intermediate_contacts.open ("/media/joris/raid_data/Chromosome_Maxent/Dataprocess_check_review/Processed_new_r0/Inverse/Contacts_Intermediate/contacts_intermediate_" + std::to_string(n) + ".txt");
        for(int i = 0; i < pol_length; i++){ //output contact frequencies
            for(int j = 0; j < pol_length; j++){
                double contact = double(final_contacts[i][j]);
                intermediate_contacts << contact << std::endl;
            }
        }
        intermediate_contacts.close();
    }

    std::cout << "Finished! "  << std::endl;

    //output final configurations for each thread
    int n_capture = 0;
    while (n_capture <number_of_threads) {
        for (int thread_num = 0; thread_num < number_of_threads; thread_num++) {
            if (polymer[thread_num][0][2] < 20) {
                std::ofstream final_configuration;
                final_configuration.open(
                        "/media/joris/raid_data/Chromosome_Maxent/Dataprocess_check_review/Processed_new_r0/Inverse/configuration_init_" +
                        std::to_string(n_capture) + ".txt");
                for (int i = 0; i < pol_length; i++) { //output contact frequencies
                    for (int j = 0; j < 3; j++) {
                        final_configuration << polymer[thread_num][i][j] << std::endl;
                    }
                }
                final_configuration.close();
                n_capture++;
            }
        }
    }

    auto finish = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> elapsed = finish - start;
    std::cout << "Elapsed time: " <<  elapsed.count() << " seconds\n";
    return 0;
}


//
// $Id$
//
//
// Original author: Austin Keller <atkeller .@. uw.edu>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#ifndef _CUBICHERMITESPLINE_HPP
#define _CUBICHERMITESPLINE_HPP

#include "IInterpolation.hpp"
#include <CSpline.h>
#include <Eigen>
#include <vector>

namespace pwiz
{
namespace analysis
{
    /// An implementation of the IInterpolation interface that acts as a wrapper for a cSpline.
    class CubicHermiteSpline : public IInterpolation
    {
    public:

        /// Constructs a CubicHermiteSpline using standard vectors
        /// @param points The independent values as a monotonically increasing series
        /// @param values The dependent values
        /// \pre points must be sorted in order of increasing value with no duplicates
        /// \pre points and values must be of the same size
        CubicHermiteSpline(const std::vector<double>& points, const std::vector<double>& values);

        /// Constructs a CubicHermiteSpline using Eigen vectors
        /// @param points The independent values as a monotonically increasing series
        /// @param values The dependent values
        /// \pre points must be sorted in order of increasing value with no duplicates
        /// \pre points and values must be of the same size
        CubicHermiteSpline(const Eigen::VectorXd& points, const Eigen::VectorXd& values);

        virtual ~CubicHermiteSpline();

        /// \name IInterpolation interface
        ///@{

        bool IsDifferentiable() override;
        bool IsIntegrable() override;
        double Differentiate(double x) override;
        double Integrate(double a, double b) override;
        double Interpolate(double x) override;
        ///@}
    private:

        /// Checks for errors and throws exception if cSpline initialization resulted in an error
        void CheckForError() const;

        /// The class containing the algorithm for constructing splines and retrieving interpolated values
        cSpline* cSpline_;
    };
} // namespace analysis
} // namespace pwiz
#endif // _CUBICHERMITESPLINE_HPP


#include <algorithm>
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <iterator>
#include <limits>
#include <map>
#include <math.h>
#include <pthread.h>
#include <sstream>
#include <string>
#include <sys/ptrace.h>
#include <sys/time.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>
#include <vector>

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>

#include "papi.h"

#include "supereasyjson/json.h"
#include "papi-helpers.hpp"

#ifdef DEBUG
#define print(...) printf(__VA_ARGS__)
#else
#define print(...)
#endif

using namespace std;
using namespace boost::numeric;
using namespace json;
using namespace papi;

namespace{

/*
 * Constants
 */
const unsigned kProcStatIdx = 39;
const long kDefaultSamplePeriodUsecs = 1000;
const long kMicroToBase = 1e6;
const long kNanoToBase = 1e9;
const char* kDefaultPrefix = "eaudit";
const string kPackageEnergyName = "rapl:::PACKAGE_ENERGY:PACKAGE0";
const string kDRAMEnergyName = "rapl:::DRAM_ENERGY:PACKAGE0";
const string kCoreEnergyName = "rapl:::PP0_ENERGY:PACKAGE0";
const vector<string> kAllEnergyNames = {kCoreEnergyName, kPackageEnergyName, kDRAMEnergyName};
const char* kDefaultModelName = "default.model";
const int kTotalCoreAssignments = 5;

struct stats_t {
  long time; // in microseconds
  vector<long long> counters;
  stats_t& operator+=(const stats_t &rhs){
    time += rhs.time;
    counters.resize(rhs.counters.size());
    for(unsigned int i = 0; i < rhs.counters.size(); ++i){
      counters[i] += rhs.counters[i];
    }
    return *this;
  }
  // initialize time to 0 for new stats
  stats_t() : time{0} {}
};

struct result_stats_t {
  stats_t per_core_stats;
  long long estimated_energy;
  result_stats_t& operator+=(const result_stats_t &rhs){
    per_core_stats += rhs.per_core_stats;
    estimated_energy += rhs.estimated_energy;
    return *this;
  }
};

/*
 * Global variables
 */
volatile bool is_timer_done = false;
} // end unnamed namespace

struct Model {
  Model(const string& model_fname) : model_fname_{model_fname} {
    ifstream in{model_fname_};
    if(!in.is_open()){
      cerr << "Unable to open model file\n";
      exit(-1);
    }
    stringstream buffer;
    buffer << in.rdbuf();
    json::Value modelval = json::Deserialize(buffer.str());
    if(modelval.GetType() == json::NULLVal){
      cerr << "Unable to parse json from model file\n";
      exit(-1);
    }
    json::Object model = modelval.ToObject();

    for(const auto& name : model["metric_names"].ToArray()){
      input_metrics_.push_back(name.ToString());
    }

    const auto means = model["means"].ToArray();
    means_.resize(means.size());
    for(size_t i = 0; i < means.size(); ++i){
      means_[i] = means[i].ToDouble();
    }

    const auto std_devs = model["std_devs"].ToArray();
    std_deviations_.resize(std_devs.size());
    for(size_t i = 0; i < std_devs.size(); ++i){
      std_deviations_[i] = std_devs[i].ToDouble();
    }

    const auto rot_mat = model["rotation_matrix"].ToArray();
    const auto n_rot_mat_rows = rot_mat.size();
    if(n_rot_mat_rows > 0){
      const auto n_rot_mat_cols = rot_mat[0].ToArray().size();
      principal_components_.resize(n_rot_mat_rows, n_rot_mat_cols);
      for(size_t i = 0; i < n_rot_mat_rows; ++i){
        for(size_t j = 0; j < n_rot_mat_cols; ++j){
          principal_components_(i,j) = rot_mat[i][j].ToDouble();
        }
      }
    }

    const auto clusters = model["clusters"].ToArray();
    for(const auto& cluster_val : clusters){
      const auto& cluster = cluster_val.ToObject();
      model_t internalized_model;

      const auto center = cluster["center"].ToArray();
      internalized_model.centroid_.resize(center.size());
      for(size_t i = 0; i < center.size(); ++i){
        internalized_model.centroid_[i] = center[i].ToDouble();
      }

      const auto regressors = cluster["regressors"].ToArray();
      internalized_model.regressors_.resize(regressors.size());
      internalized_model.weights_.resize(regressors.size());
      for(size_t i = 0; i < regressors.size(); ++i){
        // turn function object into a lambda doing the right thing
        auto regressor = regressors[i].ToObject();
        auto regressor_name = regressor["function"].ToString();
        if(regressor_name == "identity"){
          internalized_model.regressors_[i] = function<double(const ublas::vector<double>&)>(
                                   [](ublas::vector<double>){ return 1.0; });
        } else if(regressor_name == "power"){
          auto idx = regressor["index"].ToInt();
          auto exp = regressor["exponent"].ToDouble();
          internalized_model.regressors_[i] = function<double(const ublas::vector<double>&)>(
            [=](const ublas::vector<double>& v) {
              return v(idx) != 0.0 ? pow(fabs(v(idx)), exp) : 1.0;
            });
        } else if(regressor_name == "product"){
          auto first_idx = regressor["first_idx"].ToInt();
          auto second_idx = regressor["second_idx"].ToInt();
          internalized_model.regressors_[i] = function<double(const ublas::vector<double>&)>(
            [=](const ublas::vector<double>& v) {
              return v(first_idx) * v(second_idx);
            });
        } else if(regressor_name == "sqrt"){
          auto idx = regressor["index"].ToInt();
          internalized_model.regressors_[i] = function<double(const ublas::vector<double>&)>(
            [=](const ublas::vector<double>& v) {
              return sqrt(fabs(v(idx)));
            });
        } else if(regressor_name == "log"){
          auto idx = regressor["index"].ToInt();
          internalized_model.regressors_[i] = function<double(const ublas::vector<double>&)>(
            [=](const ublas::vector<double>& v) {
              return idx == 0 ? 1.0 : log(fabs(v(idx)))/log(2);
            });
        } else {
          cerr << "Invalid function name '" << regressor["name"].ToString() << "\n";
          exit(-1);
        }
        internalized_model.weights_[i] = regressor["weight"].ToDouble();
      }
      models_.push_back(internalized_model);
    }
  }

  double poll(const vector<long long>& values, const vector<string>& names) const {
    ublas::vector<double> v = ublas::vector<double>(input_metrics_.size());
    //vector<string> input_metrics_;

    for(unsigned i = 0; i < v.size(); ++i){
      for(unsigned j = 0; j < names.size(); ++j){
        if(names[j] == input_metrics_[j]){
          v[i] = values[j];
          break;
        }
      }
    }

    ublas::vector<double> inputs;
    inputs = prod(v,principal_components_);
    ublas::vector<double> norm_inputs = element_div(inputs - means_, std_deviations_);

    int closest = 0;
    double min_distance = numeric_limits<double>::max();
    for(unsigned k = 0; k < models_.size(); ++k) {
      //get distance
      double distance = norm_2(norm_inputs - models_[k].centroid_);
      if(distance < min_distance) {
        closest = k;
        min_distance = distance;
      }
    }
    ublas::vector<double> function_vals =
      ublas::vector<double>(models_[closest].regressors_.size());
    int i = 0;
    for(auto it =
          begin(models_[closest].regressors_);
        it != end(models_[closest].regressors_); ++it, ++i) {
      function_vals(i) = (*it)(inputs);
    }
    double final_res = fabs(inner_prod(models_[closest].weights_,function_vals));
    return final_res;
  }

  string model_fname_;

  struct model_t {
    ublas::vector<double> centroid_;
    ublas::vector<double> weights_;
    vector<function<double(const ublas::vector<double>&)>> regressors_;
  };

  ublas::vector<double> means_;
  ublas::vector<double> std_deviations_;
  ublas::matrix<double> principal_components_;
  vector<string> input_metrics_;
  vector<model_t> models_;
};

void overflow(int signum, siginfo_t* info, void* context){
  (void)info;
  (void)context;
  if(signum == SIGALRM){
    is_timer_done = true;
  }
}

stats_t read_rapl(const event_info_t& eventsets, long period){
  stats_t res;
  res.counters.resize(eventsets.codes.size());
  int retval=PAPI_stop(eventsets.set, &res.counters[0]);
  if(retval != PAPI_OK){
    cerr << "Error: bad PAPI stop: ";
    PAPI_perror(NULL);
    terminate();
  }
  retval = PAPI_start(eventsets.set);
  if(retval != PAPI_OK){
    cerr << "Error: bad PAPI stop: ";
    PAPI_perror(NULL);
    terminate();
  }
  res.time = period;
  return res;
}

vector<long long> modelPerCoreEnergies(const Model& model,
                                       const vector<stats_t>& core_stats,
                                       const vector<string>& counter_names,
                                       long long total_energy) {
  vector<long long> results(core_stats.size());
  //double poll(map<string, double> params) const {
  vector<double> model_vals;
  model_vals.reserve(core_stats.size());
  for(const auto& core_stat : core_stats){
    model_vals.push_back(model.poll(core_stat.counters, counter_names));
  }
  double total = accumulate(begin(model_vals), end(model_vals), double{0});
  for(unsigned i = 0; i < results.size(); ++i){
    results[i] = model_vals[i] / total * total_energy;
  }
  return results;
}

struct ProfileValue{
  double processor_energy, uncore_energy, dram_energy;
  double time;
  double instructions;
  ProfileValue() : processor_energy{0}, uncore_energy(0), dram_energy(0), time{0}, instructions{0} {}
};

struct ProfileEntry{
  string name;
  double processor_energy, uncore_energy, dram_energy, time, instructions;
};

void do_profiling(int profilee_pid, const char* profilee_name,
                  const long period, const char* prefix,
                  const Model& proc_model, const Model& uncore_model, const Model& dram_model) {
  /*
   * Structures holding profiling data
   */
  vector<int> children_pids;
  vector<map<void*, ProfileValue>> core_profiles;
  stats_t global_stats;
  global_stats.counters.resize(3);
  vector<event_info_t> core_counters;
  // TODO: assumption here is that hyperthreading is turned on, and that there
  // are two hardware threads per physical core. We have to make sure we only
  // run the correct number of threads during auditing, since this assumption
  // is made.
  auto ncores = thread::hardware_concurrency() / 2;
  //auto ncores = 1u;
  core_profiles.resize(ncores);
  core_counters.reserve(ncores);

  /*
   * Initialize PAPI
   */
  print("Init PAPI\n");
  int retval;
  if ((retval = PAPI_library_init(PAPI_VER_CURRENT)) != PAPI_VER_CURRENT) {
    cerr << "Unable to init PAPI library - " << PAPI_strerror(retval) << endl;
    exit(-1);
  }

  /*
   * Initialize PAPI measurement of all cores
   */
  int inst_counter_idx = 0;
  // get all the names for all the input counters
  vector<string> counter_names = proc_model.input_metrics_;
  counter_names.insert(end(counter_names), begin(uncore_model.input_metrics_), end(uncore_model.input_metrics_));
  counter_names.insert(end(counter_names), begin(dram_model.input_metrics_), end(dram_model.input_metrics_));
  // remove all duplicates
  sort(begin(counter_names), end(counter_names));
  auto last_elem = unique(begin(counter_names), end(counter_names));
  counter_names.erase(last_elem, end(counter_names));
  auto inst_iter = find(begin(counter_names), end(counter_names), "PAPI_TOT_INS");
  if(inst_iter == end(counter_names)){
    inst_counter_idx = counter_names.size();
    counter_names.push_back("PAPI_TOT_INS");
  } else {
    inst_counter_idx = distance(begin(counter_names), inst_iter);
  }
  // setup all core counters
  for(unsigned int i = 0; i < ncores; ++i){
    print("Creating per-core counters on core %d\n", i);
    core_counters.emplace_back(init_papi_counters(counter_names));
    auto& counters = core_counters[i];
    attach_counters_to_core(counters, i);
    start_counters(counters);
  }
  print("Creating global counters.\n");
  auto global_counters = init_papi_counters(kAllEnergyNames);
  start_counters(global_counters);

  /*
   * Setup tracing of all profilee threads
   */
  children_pids.push_back(profilee_pid);

  /*
   * Set up timer
   */
  struct sigaction sa;
  sa.sa_sigaction = overflow;
  sa.sa_flags = SA_SIGINFO;
  if (sigaction(SIGALRM, &sa, nullptr) != 0) {
    cerr << "Unable to set up signal handler\n";
    exit(-1);
  }
  struct itimerval work_time;
  time_t sleep_secs = period / kMicroToBase;
  suseconds_t sleep_usecs = period % kMicroToBase;
  work_time.it_value.tv_sec = sleep_secs;
  work_time.it_value.tv_usec = sleep_usecs;
  work_time.it_interval.tv_sec = sleep_secs;
  work_time.it_interval.tv_usec = sleep_usecs;
  setitimer(ITIMER_REAL, &work_time, nullptr);

  /*
   * Let the profilee run, periodically interrupting to collect profile data.
   */
  ptrace(PTRACE_CONT, profilee_pid, nullptr, nullptr); // Allow child to fork
  int status;
  wait(&status); // wait for child to begin executing
  print("Start profiling.\n");
  /* Reassert that we want the profilee to stop when it clones */
  ptrace(PTRACE_SETOPTIONS, profilee_pid, nullptr,
         PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT);
  ptrace(PTRACE_CONT, profilee_pid, nullptr, nullptr); // Allow child to run!
  using core_id_t = int;
  using assignments_left_t = int;
  map<int, pair<core_id_t, assignments_left_t>> children_cores;
  // We only want to match child PIDs to processors infrequently, since it
  // requires a filesystem read. The assumption here is that threads are bound
  // to cores, and only get created at the beginning of the function
  // 50 is a magic number derived by running some apps a bunch of times.
  auto start_time = PAPI_get_real_usec();
  for (;;) {
    auto wait_res = waitpid(-1, &status, __WALL);
    if(wait_res == -1){ // bad wait!
      if(errno == EINTR && is_timer_done){ // timer expired, do profiling
        // halt timer
        work_time.it_value.tv_sec = 0;
        work_time.it_value.tv_usec = 0;
        setitimer(ITIMER_REAL, &work_time, nullptr);

        // kill all the children
        for(const auto& child : children_pids){
          kill(child, SIGSTOP);
        }

        // find last executing core ID for each child
        for(const auto& child : children_pids){
          auto core_iter = children_cores.find(child);
          if(core_iter == end(children_cores)){
            auto& elem = children_cores[child];
            elem.second = kTotalCoreAssignments;
            core_iter = children_cores.find(child);
          }
          if(core_iter->second.second > 0){
            core_iter->second.second--;
            stringstream proc_fname;
            proc_fname << "/proc/" << child << "/stat";
            ifstream procfile(proc_fname.str());
            if(!procfile.is_open()){
              cerr << "Error: couldn't open proc file!\n";
              exit(-1);
            }
            string line;
            // ignore the first kProcStatIdx lines
            for(unsigned int i = 0; i < kProcStatIdx; ++i){
              getline(procfile, line, ' ');
            }
            core_iter->second.first = stoi(line);
          }
        }

        // collect stats from cores
        print("EAUDIT collating stats\n");
        // read all rapl counters
        vector<stats_t> stats(ncores);
        for(unsigned int i = 0; i < ncores; ++i){
          stats[i] = read_rapl(core_counters[i], period);
        }
        auto cur_global_stats = read_rapl(global_counters, period);
        global_stats += cur_global_stats;
        cout << "p: " << cur_global_stats.counters[0] << endl;
        cout << "u: " << cur_global_stats.counters[1] << endl;
        cout << "m: " << cur_global_stats.counters[2] << endl;

        // global counter 0 is processor plane energy
        auto proc_energies = modelPerCoreEnergies(
          proc_model, stats, counter_names, cur_global_stats.counters[0]);

        // global counter 1 is package energy, including the processor plane
        // (which we have to remove to calcluate uncore energy
        auto uncore_energies = modelPerCoreEnergies(
          uncore_model, stats, counter_names, cur_global_stats.counters[1] - cur_global_stats.counters[0]);
        // global counter 2 is DRAM energy
        auto dram_energies = modelPerCoreEnergies(
          dram_model, stats, counter_names, cur_global_stats.counters[2]);

        // read all the children registers
        for(const auto& child : children_pids){
          struct user_regs_struct regs;
          ptrace(PTRACE_GETREGS, child, nullptr, &regs);
          void* rip = (void*)regs.rip;
          auto child_core = children_cores[child].first;
          // TODO This is a hack to avoid attempting to log threads that
          // miraculously end up on the second hardware thread of a core.
          // We try to make sure things are bound appropriately, but there's
          // no guarantee, so we just ignore things that happen where we don't
          // want them.
          if((unsigned)child_core >= ncores) { continue; }
          auto& profile = core_profiles[child_core][rip];
          profile.processor_energy += proc_energies[child_core];
          profile.uncore_energy += uncore_energies[child_core];
          profile.dram_energy += dram_energies[child_core];
          profile.time += stats[child_core].time;
          profile.instructions += stats[child_core].counters[inst_counter_idx];
        }

        // resume all children
        for(const auto& child : children_pids){
          ptrace(PTRACE_CONT, child, nullptr, nullptr);
        }
        is_timer_done = false;
        // resume timer
        work_time.it_value.tv_sec = sleep_secs;
        work_time.it_value.tv_usec = sleep_usecs;
        setitimer(ITIMER_REAL, &work_time, nullptr);
      } else {
        cerr << "Error: unexpected return from wait - " << strerror(errno) << "\n";
        exit(-1);
      }
    } else { // good wait, add new thread
      if(status>>8 == (SIGTRAP | (PTRACE_EVENT_CLONE<<8))) { // new thread created
        print("New thread created.\n");
        unsigned long new_pid;
        ptrace(PTRACE_GETEVENTMSG, wait_res, nullptr, &new_pid);
        auto pid_iter = find(begin(children_pids), end(children_pids), new_pid);
        if(pid_iter != end(children_pids)) {
          cerr << "Already have this newly cloned pid: " << new_pid << ".\n";
          exit(-1);
        }
        print("Thread ID %lu created from thread ID %d\n", new_pid, wait_res);
        children_pids.push_back(new_pid);
        ptrace(PTRACE_SETOPTIONS, new_pid, nullptr,
               PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT);
        ptrace(PTRACE_CONT, wait_res, nullptr, nullptr);
      } else {
        if(status>>8 == (SIGTRAP | (PTRACE_EVENT_EXIT<<8))){
          print("Deleting child %d\n", wait_res);
          auto pid_iter = find(begin(children_pids), end(children_pids), wait_res);
          if(pid_iter == end(children_pids)){
            cerr << "Error: Saw exit from pid " << wait_res << ". We haven't seen before!\n";
            exit(-1);
          }
          children_pids.erase(pid_iter);
          if(children_pids.size() == 0){ // All done, not tracking any more threads
            break;
          }
          print("%lu children left\n", children_pids.size());
        }
        // always let the stopped tracee continue
        ptrace(PTRACE_CONT, wait_res, nullptr, nullptr);
      }
    }
  }
  auto elapsed_time = PAPI_get_real_usec() - start_time;

  /*
   * Done profiling. Convert data to output file.
   */
  print("Finalize profile.\n");
  auto profile_start_time = PAPI_get_real_usec();
  for(unsigned int i = 0; i < ncores; ++i){
    vector<ProfileEntry> profile;
    // Convert stack IDs into function names.
    for (auto& core_profile : core_profiles[i]) {
      stringstream cmd;
      cmd << "addr2line -f -s -C -e " << profilee_name << " " << core_profile.first;
      auto pipe = popen(cmd.str().c_str(), "r");  // call command and read output
      if (!pipe) {
        cerr << "Unable to open pipe to call addr2line.\n";
        return;
      }
      char buffer[128];
      string result = "";
      while (!feof(pipe)) {
        if (fgets(buffer, 128, pipe) != nullptr) {
          result += buffer;
        }
      }
      pclose(pipe);
      stringstream resultstream{result};
      string func_name, file_name;
      getline(resultstream, func_name);
      getline(resultstream, file_name);
      // NOTE: remove the trailing function annotation that says that this
      // function has been used/called by different threads
      if(func_name.back() == ']'){
        auto last_open_bracket_pos = func_name.find_last_of('[');
        func_name.erase(last_open_bracket_pos - 1);
      }
      file_name.erase(file_name.find(':'));
      string entry_name = func_name + " at " + file_name;
      print("Reporting function %s\n", entry_name.c_str());

      auto profile_iter =
          find_if(begin(profile), end(profile),
                  [&](const ProfileEntry& e) { return e.name == entry_name; });
      if(profile_iter == end(profile)){
        ProfileEntry entry;
        entry.name = entry_name;
        profile.push_back(entry);
        profile_iter = end(profile) - 1;
      }
      profile_iter->processor_energy += core_profile.second.processor_energy;
      profile_iter->uncore_energy += core_profile.second.uncore_energy;
      profile_iter->dram_energy += core_profile.second.dram_energy;
      profile_iter->time += core_profile.second.time;
      profile_iter->instructions += core_profile.second.instructions;
    }
    sort(begin(profile), end(profile),
         [&](const ProfileEntry& a, const ProfileEntry& b) {
           auto a_energy = a.processor_energy + a.uncore_energy + a.dram_energy;
           auto b_energy = b.processor_energy + b.uncore_energy + b.dram_energy;
           return a_energy > b_energy;
         });

    /*
     * Write profile to file
     */
    stringstream namestream;
    namestream << prefix << "." << i << ".tsv";
    ofstream outfile{namestream.str()};
    outfile << "Name\tProcessor Energy\tUncore Energy\tDRAM Energy\tTime\tInstructions\n";
    for(const auto& elem : profile){
      outfile << elem.name << "\t"
              << elem.processor_energy / kNanoToBase << "\t"
              << elem.uncore_energy / kNanoToBase << "\t"
              << elem.dram_energy / kNanoToBase << "\t"
              << elem.time / kMicroToBase << "\t"
              << elem.instructions << "\n";
    }
  }

  cout << "Total Processor Energy:\t" << global_stats.counters[0] / (double)kNanoToBase << " joules\n"
       << "Total Uncore Energy:\t" << (global_stats.counters[1] - global_stats.counters[0]) / (double)kNanoToBase << " joules\n"
       << "Total DRAM Energy:\t" << global_stats.counters[2] / (double)kNanoToBase << " joules\n"
       << "Elapsed Time:\t" << elapsed_time / (double)kMicroToBase << " seconds\n";

  auto profile_elapsed = PAPI_get_real_usec() - profile_start_time;
  cout << "Profile creation time:\t" << profile_elapsed / (double) kMicroToBase << " seconds\n";
}

int main(int argc, char* argv[], char* envp[]) {
  /*
   * Check params
   */
  auto usage =
    "Usage:\n"
    " eaudit [options] executable\n"
    "\n"
    "Options:\n"
    " -h                  Show this help\n"
    " -p <microseconds>   Sample period in microseconds, default 1000\n"
    " -o <prefix>         Prefix to use when writing files, default eaudit\n"
    " -m <filename>       Model file name, default 'default.model'\n"
    "\n";

  auto period = kDefaultSamplePeriodUsecs;
  auto proc_model_fname = kDefaultModelName;
  auto uncore_model_fname = kDefaultModelName;
  auto dram_model_fname = kDefaultModelName;
  auto prefix = kDefaultPrefix;
  int param;
  while((param = getopt(argc, argv, "+hp:o:c:u:m:")) != -1){
    switch(param){
      case 'p':
        period = stol(optarg);
        break;
      case 'o':
        prefix = optarg;
        break;
      case 'c':
        {
          proc_model_fname = optarg;
        }
        break;
      case 'u':
        {
          uncore_model_fname = optarg;
        }
        break;
      case 'm':
        {
          dram_model_fname = optarg;
        }
        break;
      case 'h':
      case '?':
        cout << usage;
        exit(0);
        break;
      default:
        cerr << "Error: bad getopt parse of parameter.\n";
        exit(-1);
        break;
    }
  }

  /*
   * Make our models
   */
  Model proc_model{proc_model_fname};
  Model uncore_model{uncore_model_fname};
  Model dram_model{dram_model_fname};

  /*
   * Fork a process to run the profiled application
   */
  auto profilee = fork();
  if(profilee > 0){ /* parent */
    // Let's do this.
    ptrace(PTRACE_SETOPTIONS, profilee, nullptr,
           PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT);
    do_profiling(profilee, argv[optind], period, prefix, proc_model, uncore_model, dram_model);
  } else if(profilee == 0){ /* profilee */
    // prepare for tracing
    ptrace(PTRACE_TRACEME, 0, nullptr, nullptr);
    raise(SIGSTOP);
    // start up client program
    execve(argv[optind], &argv[optind], envp);
    cerr << "Error: profilee couldn't start its program!\n";
    perror(nullptr);
    exit(-1);
  } else { /* error */
    cerr << "Error: couldn't fork audited program.\n";
    return -1;
  }
}