/*! \file Peridigm_CorrespondenceMaterial.cpp */

//@HEADER
// ************************************************************************
//
//                             Peridigm
//                 Copyright (2011) Sandia Corporation
//
// Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
// the U.S. Government retains certain rights in this software.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the Corporation nor the names of the
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Questions?
// David J. Littlewood   djlittl@sandia.gov
// John A. Mitchell      jamitch@sandia.gov
// Michael L. Parks      mlparks@sandia.gov
// Stewart A. Silling    sasilli@sandia.gov
//
// ************************************************************************
//@HEADER

#include "Peridigm_CorrespondenceMaterial.hpp"
#include "Peridigm_Field.hpp"
#include "elastic.h"
#include "correspondence.h"
#include "Peridigm_DegreesOfFreedomManager.hpp"
#include "elastic_correspondence.h"
#include <Teuchos_Assert.hpp>
#include <Epetra_SerialComm.h>
#include <Sacado.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
using namespace std;

PeridigmNS::CorrespondenceMaterial::CorrespondenceMaterial(const Teuchos::ParameterList& params)
  : Material(params),
    m_density(0.0), m_hourglassCoefficient(0.0),
        m_OMEGA(PeridigmNS::InfluenceFunction::self().getInfluenceFunction()),
    m_horizonFieldId(-1), m_volumeFieldId(-1),
    m_modelCoordinatesFieldId(-1), m_coordinatesFieldId(-1), m_velocitiesFieldId(-1),
    m_hourglassForceDensityFieldId(-1), m_forceDensityFieldId(-1), m_bondDamageFieldId(-1),
    m_deformationGradientFieldId(-1),
    m_shapeTensorInverseFieldId(-1),
    m_leftStretchTensorFieldId(-1),
    m_rotationTensorFieldId(-1),
    m_unrotatedCauchyStressFieldId(-1),
    m_cauchyStressFieldId(-1),
    m_unrotatedRateOfDeformationFieldId(-1),
    m_partialStressFieldId(-1),
    m_hourglassStiffId(-1)

{

  //! \todo Add meaningful asserts on material properties.
  m_bulkModulus = calculateBulkModulus(params);
  m_shearModulus = calculateShearModulus(params);
  m_density = params.get<double>("Density");

  m_stabilizationType = 3;

  m_plane = false;

  //************************************
  // wie komme ich an den Namen??
  //************************************
  //if (params.isParameter("Linear Elastic Correspondence")){
    nonLin = false;

    bool m_planeStrain = false, m_planeStress = false;
    if (params.isParameter("Plane Strain"))
        m_planeStrain = params.get<bool>("Plane Strain");

    if (params.isParameter("Plane Stress"))
        m_planeStress = params.get<bool>("Plane Stress");

    if (m_planeStrain==true){
        m_plane=true;

    }
    if (m_planeStress==true){
        m_plane=true;

    }
  m_tension = true;
  if (params.isParameter("Tension pressure separation for damage model")){
      m_tension = params.get<bool>("Tension pressure separation for damage model");

  }
  m_applyAutomaticDifferentiationJacobian = true;
  if(params.isParameter("Apply Automatic Differentiation Jacobian"))
    m_applyAutomaticDifferentiationJacobian = params.get<bool>("Apply Automatic Differentiation Jacobian");
  if (params.isParameter("Stabilizaton Type")){

    if (params.get<string>("Stabilizaton Type")=="Bond Based"){
        m_stabilizationType = 1;
        m_hourglassCoefficient = params.get<double>("Hourglass Coefficient");
    }
    if (params.get<string>("Stabilizaton Type")=="State Based"){
        m_stabilizationType = 2;
        m_hourglassCoefficient = params.get<double>("Hourglass Coefficient");
    }
    //if (params.get<string>("Stabilizaton Type")=="Sub Horizon"){
    //    m_stabilizationType = 4;
    //    // works only for linear elastic correspondence first
    //    // based on: Shubhankar Roy Chowdhury, Pranesh Roy, Debasish Roy and J N Reddy,
    //    // "A simple alteration of the peridynamics correspondence principle to eliminate zero-energy deformation"
    //}
    if (params.get<string>("Stabilizaton Type")=="Global Stiffness"){

        m_stabilizationType = 3;
        m_hourglassCoefficient = params.get<double>("Hourglass Coefficient");

        double C11 = 0.0, C12 = 0.0, C13 = 0.0, C14 = 0.0, C15 = 0.0, C16 = 0.0;
        double            C22 = 0.0, C23 = 0.0, C24 = 0.0, C25 = 0.0, C26 = 0.0;
        double                       C33 = 0.0, C34 = 0.0, C35 = 0.0, C36 = 0.0;
        double                                  C44 = 0.0, C45 = 0.0, C46 = 0.0;
        double                                             C55 = 0.0, C56 = 0.0;
        double                                                        C66 = 0.0;

        bool iso = false;
        if (params.isParameter("Material Symmetry")){
            if (params.get<string>("Material Symmetry")=="Isotropic"){
                C11 = params.get<double>("C11");
                C44 = params.get<double>("C44");
                C55 = params.get<double>("C44");
                C66 = params.get<double>("C44");
                C12 = C11 - 2*C55;
                C13 = C12;
                C14 = 0.0;
                C15 = 0.0;
                C16 = 0.0;
                C22 = params.get<double>("C11");
                C33 = params.get<double>("C11");
                C23 = C12;
                C24 = 0.0;
                C25 = 0.0;
                C26 = 0.0;
                C34 = 0.0;
                C35 = 0.0;
                C36 = 0.0;
                C45 = 0.0;
                C46 = 0.0;
                C56 = 0.0;
                iso  = true;
            }
            if (params.get<string>("Material Symmetry")=="Anisotropic"){
                C11 = params.get<double>("C11");
                C12 = params.get<double>("C12");
                C13 = params.get<double>("C13");
                C14 = params.get<double>("C14");
                C15 = params.get<double>("C15");
                C16 = params.get<double>("C16");
                C22 = params.get<double>("C22");
                C23 = params.get<double>("C23");
                C24 = params.get<double>("C24");
                C25 = params.get<double>("C25");
                C26 = params.get<double>("C26");
                C33 = params.get<double>("C33");
                C34 = params.get<double>("C34");
                C35 = params.get<double>("C35");
                C36 = params.get<double>("C36");
                C44 = params.get<double>("C44");
                C45 = params.get<double>("C45");
                C46 = params.get<double>("C46");
                C55 = params.get<double>("C55");
                C56 = params.get<double>("C56");
                C66 = params.get<double>("C66");

            }

        }
        else{
            m_bulkModulus = calculateBulkModulus(params);
            m_shearModulus = calculateShearModulus(params);

            C11 = (4*m_shearModulus*(3*m_bulkModulus + m_shearModulus))/(3*m_bulkModulus + 4*m_shearModulus);
            C44 = m_shearModulus;
            C55 = m_shearModulus;
            C66 = m_shearModulus;
            C12 = (2*(3*m_bulkModulus - 2*m_shearModulus)*m_shearModulus)/(3*m_bulkModulus + 4*m_shearModulus);
            C13 = C12;
            C14 = 0.0;
            C15 = 0.0;
            C16 = 0.0;
            C22 = C11;
            C33 = C11;
            C23 = C12;
            C24 = 0.0;
            C25 = 0.0;
            C26 = 0.0;
            C34 = 0.0;
            C35 = 0.0;
            C36 = 0.0;
            C45 = 0.0;
            C46 = 0.0;
            C56 = 0.0;
        }
        // Equation (8) Dipasquale, D., Sarego, G., Zaccariotto, M., Galvanetto, U., A discussion on failure criteria
          // for ordinary state-based Peridynamics, Engineering Fracture Mechanics (2017), doi: https://doi.org/10.1016/
          // j.engfracmech.2017.10.011
        if (m_planeStrain==true)m_plane=true;
        if (m_planeStress==true)m_plane=true;
        // have to be done after rotation if angles exist
        if (m_plane==false){
         C[0][0] = C11;C[0][1] = C12;C[0][2]= C13; C[0][3] = C14; C[0][4] = C15; C[0][5]= C16;
         C[1][0] = C12;C[1][1] = C22;C[1][2]= C23; C[1][3] = C24; C[1][4] = C25; C[1][5]= C26;
         C[2][0] = C13;C[2][1] = C23;C[2][2]= C33; C[2][3] = C34; C[2][4] = C35; C[2][5]= C36;
         C[3][0] = C14;C[3][1] = C24;C[3][2]= C34; C[3][3] = C44; C[3][4] = C45; C[3][5]= C46;
         C[4][0] = C15;C[4][1] = C25;C[4][2]= C35; C[4][3] = C45; C[4][4] = C55; C[4][5]= C56;
         C[5][0] = C16;C[5][1] = C26;C[5][2]= C36; C[5][3] = C46; C[5][4] = C56; C[5][5]= C66;
        }
        // tbd in future
        if (m_planeStress==true && iso == true){
            //only transversal isotropic in the moment --> definition of iso missing
         C[0][0] = C11-C13*C13/C22;C[0][1] = C12-C13*C23/C22;C[0][2] = 0.0; C[0][3] = 0.0; C[0][4] = 0.0; C[0][5] = 0.0;
         C[1][0] = C12-C13*C23/C22;C[1][1] = C22-C13*C23/C22;C[1][2] = 0.0; C[1][3] = 0.0; C[1][4] = 0.0; C[1][5] = 0.0;
         C[2][0] = 0.0;            C[2][1] = 0.0;            C[2][2] = 0.0; C[2][3] = 0.0; C[2][4] = 0.0; C[2][5] = 0.0;
         C[3][0] = 0.0;            C[3][1] = 0.0;            C[3][2] = 0.0; C[3][3] = 0.0; C[3][4] = 0.0; C[3][5] = 0.0;
         C[4][0] = 0.0;            C[4][1] = 0.0;            C[4][2] = 0.0; C[4][3] = 0.0; C[4][4] = 0.0; C[4][5] = 0.0;
         C[5][0] = 0.0;            C[5][1] = 0.0;            C[5][2] = 0.0; C[5][3] = 0.0; C[5][4] = 0.0; C[5][5] = C66;
        }
        // not correct for plane stress!!!
        if (m_plane==true && iso == false){
         C[0][0] = C11;C[0][1] = C12;C[0][2] = 0.0;C[0][3] = 0.0;C[0][4] = 0.0;C[0][5] = C16;
         C[1][0] = C12;C[1][1] = C22;C[1][2] = 0.0;C[1][3] = 0.0;C[1][4] = 0.0;C[1][5] = C26;
         C[2][0] = 0.0;C[2][1] = 0.0;C[2][2] = 0.0;C[2][3] = 0.0;C[2][4] = 0.0;C[2][5] = 0.0;
         C[3][0] = 0.0;C[3][1] = 0.0;C[3][2] = 0.0;C[3][3] = 0.0;C[3][4] = 0.0;C[3][5] = 0.0;
         C[4][0] = 0.0;C[4][1] = 0.0;C[4][2] = 0.0;C[4][3] = 0.0;C[4][4] = 0.0;C[4][5] = 0.0;
         C[5][0] = C16;C[5][1] = C26;C[5][2] = 0.0;C[5][3] = 0.0;C[5][4] = 0.0;C[5][5] = C66;

        }

    }
  }
  nonLin = false;
  lin = true;

  if (params.isParameter("Elastic Correspondence")){

      nonLin = true;
      lin = false;

    }

  //TEUCHOS_TEST_FOR_EXCEPT_MSG(params.isParameter("Apply Automatic Differentiation Jacobian"), "**** Error:  Automatic Differentiation is not supported for the ElasticCorrespondence material model.\n");
  TEUCHOS_TEST_FOR_EXCEPT_MSG(params.isParameter("Apply Shear Correction Factor"), "**** Error:  Shear Correction Factor is not supported for the ElasticCorrespondence material model.\n");
  TEUCHOS_TEST_FOR_EXCEPT_MSG(params.isParameter("Thermal Expansion Coefficient"), "**** Error:  Thermal expansion is not currently supported for the ElasticCorrespondence material model.\n");

  PeridigmNS::FieldManager& fieldManager = PeridigmNS::FieldManager::self();
  m_horizonFieldId                    = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::SCALAR, PeridigmField::CONSTANT, "Horizon");
  m_volumeFieldId                     = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::SCALAR, PeridigmField::CONSTANT, "Volume");
  m_modelCoordinatesFieldId           = fieldManager.getFieldId(PeridigmField::NODE,    PeridigmField::VECTOR, PeridigmField::CONSTANT, "Model_Coordinates");
  m_modelAnglesId                     = fieldManager.getFieldId(PeridigmField::NODE,    PeridigmField::VECTOR, PeridigmField::CONSTANT, "Local_Angles");
  m_coordinatesFieldId                = fieldManager.getFieldId(PeridigmField::NODE,    PeridigmField::VECTOR, PeridigmField::TWO_STEP, "Coordinates");
  m_velocitiesFieldId                 = fieldManager.getFieldId(PeridigmField::NODE,    PeridigmField::VECTOR, PeridigmField::TWO_STEP, "Velocity");
  m_forceDensityFieldId               = fieldManager.getFieldId(PeridigmField::NODE,    PeridigmField::VECTOR, PeridigmField::TWO_STEP, "Force_Density");
  m_hourglassForceDensityFieldId      = fieldManager.getFieldId(PeridigmField::NODE,    PeridigmField::VECTOR, PeridigmField::TWO_STEP, "Hourglass_Force_Density");
  m_bondDamageFieldId                 = fieldManager.getFieldId(PeridigmField::BOND,    PeridigmField::SCALAR, PeridigmField::TWO_STEP, "Bond_Damage");
  m_deformationGradientFieldId        = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::FULL_TENSOR, PeridigmField::CONSTANT, "Deformation_Gradient");
  m_leftStretchTensorFieldId          = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::FULL_TENSOR, PeridigmField::TWO_STEP, "Left_Stretch_Tensor");
  m_rotationTensorFieldId             = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::FULL_TENSOR, PeridigmField::TWO_STEP, "Rotation_Tensor");
  m_shapeTensorInverseFieldId         = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::FULL_TENSOR, PeridigmField::CONSTANT, "Shape_Tensor_Inverse");
  m_unrotatedCauchyStressFieldId      = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::FULL_TENSOR, PeridigmField::TWO_STEP, "Unrotated_Cauchy_Stress");
  m_cauchyStressFieldId               = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::FULL_TENSOR, PeridigmField::TWO_STEP, "Cauchy_Stress");
  m_piolaStressTimesInvShapeTensorXId = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::VECTOR, PeridigmField::TWO_STEP, "PiolaStressTimesInvShapeTensorX");
  m_piolaStressTimesInvShapeTensorYId = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::VECTOR, PeridigmField::TWO_STEP, "PiolaStressTimesInvShapeTensorY");
  m_piolaStressTimesInvShapeTensorZId = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::VECTOR, PeridigmField::TWO_STEP, "PiolaStressTimesInvShapeTensorZ");
  m_unrotatedRateOfDeformationFieldId = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::FULL_TENSOR, PeridigmField::CONSTANT, "Unrotated_Rate_Of_Deformation");
  m_partialStressFieldId              = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::FULL_TENSOR, PeridigmField::TWO_STEP, "Partial_Stress");
  m_detachedNodesFieldId              = fieldManager.getFieldId(PeridigmField::NODE,    PeridigmField::SCALAR, PeridigmField::TWO_STEP, "Detached_Nodes");
  m_hourglassStiffId                  = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::FULL_TENSOR, PeridigmField::CONSTANT, "Hourglass_Stiffness");

  m_fieldIds.push_back(m_horizonFieldId);
  m_fieldIds.push_back(m_volumeFieldId);
  m_fieldIds.push_back(m_modelCoordinatesFieldId);
  m_fieldIds.push_back(m_coordinatesFieldId);
  m_fieldIds.push_back(m_velocitiesFieldId);
  m_fieldIds.push_back(m_hourglassForceDensityFieldId);
  m_fieldIds.push_back(m_forceDensityFieldId);
  m_fieldIds.push_back(m_bondDamageFieldId);
  m_fieldIds.push_back(m_deformationGradientFieldId);

  m_fieldIds.push_back(m_leftStretchTensorFieldId);
  m_fieldIds.push_back(m_rotationTensorFieldId);
  m_fieldIds.push_back(m_shapeTensorInverseFieldId);
  m_fieldIds.push_back(m_unrotatedCauchyStressFieldId);
  m_fieldIds.push_back(m_cauchyStressFieldId);
  m_fieldIds.push_back(m_piolaStressTimesInvShapeTensorXId);
  m_fieldIds.push_back(m_piolaStressTimesInvShapeTensorYId);
  m_fieldIds.push_back(m_piolaStressTimesInvShapeTensorZId);
  m_fieldIds.push_back(m_unrotatedRateOfDeformationFieldId);
  m_fieldIds.push_back(m_partialStressFieldId);
  m_fieldIds.push_back(m_detachedNodesFieldId);
  m_fieldIds.push_back(m_hourglassStiffId);
  m_fieldIds.push_back(m_modelAnglesId);

  }

PeridigmNS::CorrespondenceMaterial::~CorrespondenceMaterial()
{
}

void
PeridigmNS::CorrespondenceMaterial::initialize(const double dt,
                                               const int numOwnedPoints,
                                               const int* ownedIDs,
                                               const int* neighborhoodList,
                                               PeridigmNS::DataManager& dataManager)
{

  dataManager.getData(m_unrotatedRateOfDeformationFieldId, PeridigmField::STEP_NONE)->PutScalar(0.0);
  dataManager.getData(m_hourglassStiffId, PeridigmField::STEP_NONE)->PutScalar(0.0);
  dataManager.getData(m_leftStretchTensorFieldId, PeridigmField::STEP_N)->PutScalar(0.0);
  dataManager.getData(m_leftStretchTensorFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);
  dataManager.getData(m_rotationTensorFieldId, PeridigmField::STEP_N)->PutScalar(0.0);
  dataManager.getData(m_rotationTensorFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);
  dataManager.getData(m_unrotatedCauchyStressFieldId, PeridigmField::STEP_N)->PutScalar(0.0);
  dataManager.getData(m_unrotatedCauchyStressFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);
  dataManager.getData(m_cauchyStressFieldId, PeridigmField::STEP_N)->PutScalar(0.0);
  dataManager.getData(m_cauchyStressFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);
  dataManager.getData(m_detachedNodesFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);

  dataManager.getData(m_partialStressFieldId, PeridigmField::STEP_N)->PutScalar(0.0);
  dataManager.getData(m_partialStressFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);

  dataManager.getData(m_piolaStressTimesInvShapeTensorXId, PeridigmField::STEP_NP1)->PutScalar(0.0);
  dataManager.getData(m_piolaStressTimesInvShapeTensorYId, PeridigmField::STEP_NP1)->PutScalar(0.0);
  dataManager.getData(m_piolaStressTimesInvShapeTensorZId, PeridigmField::STEP_NP1)->PutScalar(0.0);

  double *leftStretchTensorN;
  double *leftStretchTensorNP1;
  double *rotationTensorN;
  double *rotationTensorNP1;
  double *detachedNodes;
  dataManager.getData(m_leftStretchTensorFieldId, PeridigmField::STEP_N)->ExtractView(&leftStretchTensorN);
  dataManager.getData(m_leftStretchTensorFieldId, PeridigmField::STEP_NP1)->ExtractView(&leftStretchTensorNP1);
  dataManager.getData(m_rotationTensorFieldId, PeridigmField::STEP_N)->ExtractView(&rotationTensorN);
  dataManager.getData(m_rotationTensorFieldId, PeridigmField::STEP_NP1)->ExtractView(&rotationTensorNP1);
  dataManager.getData(m_detachedNodesFieldId, PeridigmField::STEP_NP1)->ExtractView(&detachedNodes);
  //Initialize the left stretch and rotation tenor to the identity matrix
  CORRESPONDENCE::setOnesOnDiagonalFullTensor(leftStretchTensorN, numOwnedPoints);
  CORRESPONDENCE::setOnesOnDiagonalFullTensor(leftStretchTensorNP1, numOwnedPoints);
  CORRESPONDENCE::setOnesOnDiagonalFullTensor(rotationTensorN, numOwnedPoints);
  CORRESPONDENCE::setOnesOnDiagonalFullTensor(rotationTensorNP1, numOwnedPoints);

  //Initialize the inverse of the shape tensor and the deformation gradient
  double *volume;
  double *horizon;
  double *modelCoordinates;
  double *coordinates;
  double *coordinatesNP1;
  double *shapeTensorInverse;
  double *deformationGradient;
  double *bondDamage;

  dataManager.getData(m_volumeFieldId, PeridigmField::STEP_NONE)->ExtractView(&volume);
  dataManager.getData(m_horizonFieldId, PeridigmField::STEP_NONE)->ExtractView(&horizon);

  dataManager.getData(m_modelCoordinatesFieldId, PeridigmField::STEP_NONE)->ExtractView(&modelCoordinates);
  dataManager.getData(m_coordinatesFieldId, PeridigmField::STEP_N)->ExtractView(&coordinates);
  dataManager.getData(m_coordinatesFieldId, PeridigmField::STEP_NP1)->ExtractView(&coordinatesNP1);
  dataManager.getData(m_shapeTensorInverseFieldId, PeridigmField::STEP_NONE)->ExtractView(&shapeTensorInverse);
  dataManager.getData(m_deformationGradientFieldId, PeridigmField::STEP_NONE)->ExtractView(&deformationGradient);
  dataManager.getData(m_unrotatedRateOfDeformationFieldId, PeridigmField::STEP_NONE)->PutScalar(0.0);
  dataManager.getData(m_bondDamageFieldId, PeridigmField::STEP_NP1)->ExtractView(&bondDamage);

 int shapeTensorReturnCode = 0;
  shapeTensorReturnCode =
            CORRESPONDENCE::computeShapeTensorInverseAndApproximateDeformationGradient(volume,
                                                                                horizon,
                                                                                modelCoordinates,
                                                                                coordinates,
                                                                                coordinatesNP1,
                                                                                shapeTensorInverse,
                                                                                deformationGradient,
                                                                                bondDamage,
                                                                                neighborhoodList,
                                                                                numOwnedPoints,
                                                                                m_plane,
                                                                                detachedNodes);

  string shapeTensorErrorMessage =
    "**** Error:  CorrespondenceMaterial::initialize() failed to compute shape tensor.\n";
  shapeTensorErrorMessage +=
    "****         Note that all nodes must have a minimum of three neighbors.  Is the horizon too small?\n";
  TEUCHOS_TEST_FOR_EXCEPT_MSG(shapeTensorReturnCode != 0, shapeTensorErrorMessage);

}

void
PeridigmNS::CorrespondenceMaterial::computeForce(const double dt,
                                                 const int numOwnedPoints,
                                                 const int* ownedIDs,
                                                 const int* neighborhoodList,
                                                 PeridigmNS::DataManager& dataManager) const
{
  // Zero out the forces and partial stress

  dataManager.getData(m_forceDensityFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);
  dataManager.getData(m_partialStressFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);
  dataManager.getData(m_unrotatedRateOfDeformationFieldId, PeridigmField::STEP_NONE)->PutScalar(0.0);
  double *horizon, *volume, *modelCoordinates, *coordinates, *coordinatesNP1, *shapeTensorInverse, *deformationGradient, *bondDamage, *pointAngles, *detachedNodes;
  //double *deformationGradientNonInc;

  dataManager.getData(m_horizonFieldId, PeridigmField::STEP_NONE)->ExtractView(&horizon);
  dataManager.getData(m_volumeFieldId, PeridigmField::STEP_NONE)->ExtractView(&volume);
  dataManager.getData(m_modelCoordinatesFieldId, PeridigmField::STEP_NONE)->ExtractView(&modelCoordinates);
  dataManager.getData(m_modelAnglesId,           PeridigmField::STEP_NONE)->ExtractView(&pointAngles);
  dataManager.getData(m_coordinatesFieldId, PeridigmField::STEP_N)->ExtractView(&coordinates);
  dataManager.getData(m_coordinatesFieldId, PeridigmField::STEP_NP1)->ExtractView(&coordinatesNP1);
  dataManager.getData(m_shapeTensorInverseFieldId, PeridigmField::STEP_NONE)->ExtractView(&shapeTensorInverse);
  dataManager.getData(m_deformationGradientFieldId, PeridigmField::STEP_NONE)->ExtractView(&deformationGradient);

  dataManager.getData(m_bondDamageFieldId, PeridigmField::STEP_NP1)->ExtractView(&bondDamage);

  dataManager.getData(m_detachedNodesFieldId, PeridigmField::STEP_NP1)->ExtractView(&detachedNodes);
  // Compute the inverse of the shape tensor and the approximate deformation gradient
  // The approximate deformation gradient will be used by the derived class (specific correspondence material model)
  // to compute the Cauchy stress.
  // The inverse of the shape tensor is stored for later use after the Cauchy stress calculation

   // vier szenarien
   int shapeTensorReturnCode = 0;
  // if (lin == true){

    shapeTensorReturnCode =
        CORRESPONDENCE::computeShapeTensorInverseAndApproximateDeformationGradient(volume,
                                                                                horizon,
                                                                                modelCoordinates,
                                                                                coordinates,
                                                                                coordinatesNP1,
                                                                                shapeTensorInverse,
                                                                                deformationGradient,
                                                                                bondDamage,
                                                                                neighborhoodList,
                                                                                numOwnedPoints,
                                                                                m_plane,
                                                                                detachedNodes);

   //}

  string shapeTensorErrorMessage =
    "**** Error:  CorrespondenceMaterial::computeForce() failed to compute shape tensor.\n";
  shapeTensorErrorMessage +=
    "****         Note that all nodes must have a minimum of three neighbors.  Is the horizon too small?\n";
  TEUCHOS_TEST_FOR_EXCEPT_MSG(shapeTensorReturnCode != 0, shapeTensorErrorMessage);

  double *velocities, *leftStretchTensorN, *leftStretchTensorNP1, *rotationTensorN, *rotationTensorNP1, *unrotatedRateOfDeformation;
  if (nonLin==true){
      dataManager.getData(m_leftStretchTensorFieldId, PeridigmField::STEP_N)->ExtractView(&leftStretchTensorN);
      dataManager.getData(m_leftStretchTensorFieldId, PeridigmField::STEP_NP1)->ExtractView(&leftStretchTensorNP1);
      dataManager.getData(m_rotationTensorFieldId, PeridigmField::STEP_N)->ExtractView(&rotationTensorN);
      dataManager.getData(m_rotationTensorFieldId, PeridigmField::STEP_NP1)->ExtractView(&rotationTensorNP1);
      dataManager.getData(m_unrotatedRateOfDeformationFieldId, PeridigmField::STEP_NONE)->ExtractView(&unrotatedRateOfDeformation);
      dataManager.getData(m_velocitiesFieldId, PeridigmField::STEP_NP1)->ExtractView(&velocities);
      // Compute left stretch tensor, rotation tensor, and unrotated rate-of-deformation.
      // Performs a polar decomposition via Flanagan & Taylor (1987) algorithm.
      //"A non-ordinary state-based peridynamic method to model solid material deformation and fracture"
      // Thomas L. Warren, Stewart A. Silling, Abe Askari, Olaf Weckner, Michael A. Epton, Jifeng Xu

      int rotationTensorReturnCode = 0;

      rotationTensorReturnCode = CORRESPONDENCE::computeUnrotatedRateOfDeformationAndRotationTensor(volume,
                                                                                horizon,
                                                                                modelCoordinates,
                                                                                velocities,
                                                                                deformationGradient,
                                                                                shapeTensorInverse,
                                                                                leftStretchTensorN,
                                                                                rotationTensorN,
                                                                                leftStretchTensorNP1,
                                                                                rotationTensorNP1,
                                                                                unrotatedRateOfDeformation,
                                                                                neighborhoodList,
                                                                                numOwnedPoints,
                                                                                dt,
                                                                                bondDamage,
                                                                                m_plane,
                                                                                detachedNodes);

      string rotationTensorErrorMessage =
        "**** Error:  CorrespondenceMaterial::computeForce() failed to compute rotation tensor.\n";
      rotationTensorErrorMessage +=
        "****         Note that all nodes must have a minimum of three neighbors.  Is the horizon too small?\n";
      string rotationTensorErrorMessage2 =
        "**** Error:  CorrespondenceMaterial::computeForce() failed to invert deformation gradient tensor.\n";

      string rotationTensorErrorMessage3 =
        "**** Error:  CorrespondenceMaterial::computeForce() failed to invert temp.\n";

      TEUCHOS_TEST_FOR_EXCEPT_MSG(rotationTensorReturnCode == 1, rotationTensorErrorMessage);
      TEUCHOS_TEST_FOR_EXCEPT_MSG(rotationTensorReturnCode == 2, rotationTensorErrorMessage2);
      TEUCHOS_TEST_FOR_EXCEPT_MSG(rotationTensorReturnCode == 3, rotationTensorErrorMessage3);

     }

  // Evaluate the Cauchy stress using the routine implemented in the derived class (specific correspondence material model)
  // The general idea is to compute the stress based on:
  //   1) The unrotated rate-of-deformation tensor
  //   2) The time step
  //   3) Whatever state variables are managed by the derived class
  //
  // computeCauchyStress() typically uses the following fields which are accessed via the DataManager:
  //   Input:  unrotated rate-of-deformation tensor
  //   Input:  unrotated Cauchy stress at step N
  //   Input:  internal state data (managed in the derived class)
  //   Output: unrotated Cauchy stress at step N+1

  // multiple Cauchy stresses will be provided over the datamanager --> Peridigm_ElasticLinearCorrespondence

  computeCauchyStress(dt, numOwnedPoints, dataManager);

  // rotate back to the Eulerian frame
  double *unrotatedCauchyStressNP1, *cauchyStressNP1;

  if (nonLin == true) {
      dataManager.getData(m_unrotatedCauchyStressFieldId, PeridigmField::STEP_NP1)->ExtractView(&unrotatedCauchyStressNP1);
      dataManager.getData(m_cauchyStressFieldId, PeridigmField::STEP_NP1)->ExtractView(&cauchyStressNP1);

      CORRESPONDENCE::rotateCauchyStress(rotationTensorNP1,
                                         unrotatedCauchyStressNP1,
                                         cauchyStressNP1,
                                         numOwnedPoints);
                                         // Cauchy stress is now updated and in the rotated state.
  }
  else
  {
    dataManager.getData(m_cauchyStressFieldId, PeridigmField::STEP_NP1)->ExtractView(&cauchyStressNP1);

  }

  // Proceed with conversion to Piola-Kirchoff and force-vector states.
//--------------------------------------------------------------------------
 double *forceDensity;
 dataManager.getData(m_forceDensityFieldId, PeridigmField::STEP_NP1)->ExtractView(&forceDensity);
 dataManager.getData(m_forceDensityFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);
 double *partialStress;
 dataManager.getData(m_partialStressFieldId, PeridigmField::STEP_NP1)->ExtractView(&partialStress);
 dataManager.getData(m_partialStressFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);

 double *tempStressX, *tempStressY, *tempStressZ;
 dataManager.getData(m_piolaStressTimesInvShapeTensorXId, PeridigmField::STEP_NP1)->ExtractView(&tempStressX);
 dataManager.getData(m_piolaStressTimesInvShapeTensorYId, PeridigmField::STEP_NP1)->ExtractView(&tempStressY);
 dataManager.getData(m_piolaStressTimesInvShapeTensorZId, PeridigmField::STEP_NP1)->ExtractView(&tempStressZ);
 dataManager.getData(m_piolaStressTimesInvShapeTensorXId, PeridigmField::STEP_NP1)->PutScalar(0.0);
 dataManager.getData(m_piolaStressTimesInvShapeTensorYId, PeridigmField::STEP_NP1)->PutScalar(0.0);
 dataManager.getData(m_piolaStressTimesInvShapeTensorZId, PeridigmField::STEP_NP1)->PutScalar(0.0);

 double *hourglassStiff;

 dataManager.getData(m_hourglassStiffId, PeridigmField::STEP_NONE)->ExtractView(&hourglassStiff);

 CORRESPONDENCE::computeForcesAndStresses(
                                       numOwnedPoints,
                                       neighborhoodList,
                                       volume,
                                       horizon,
                                       modelCoordinates,
                                       coordinatesNP1,
                                       deformationGradient,
                                       cauchyStressNP1,
                                       shapeTensorInverse,
                                       bondDamage,
                                       C,
                                       pointAngles,
                                       forceDensity,
                                       partialStress,
                                       tempStressX,
                                       tempStressY,
                                       tempStressZ,
                                       hourglassStiff,
                                       m_hourglassCoefficient,
                                       m_stabilizationType,
                                       m_plane,
                                       m_tension,
                                       detachedNodes);

 //     std::cout<<numOwnedPoints<< " "<< *(deformationGradient)<<" "<<*(partialStress)<<std::endl;

  if (m_incremental == false){
      if (m_stabilizationType == 1){
      CORRESPONDENCE::computeHourglassForce(volume,
                                            horizon,
                                            modelCoordinates,
                                            coordinates,
                                            deformationGradient,
                                            forceDensity,
                                            neighborhoodList,
                                            numOwnedPoints,
                                            m_bulkModulus,
                                            m_hourglassCoefficient,
                                            bondDamage
                                            );
      }

      if (m_stabilizationType == 2){

      CORRESPONDENCE::computeCorrespondenceStabilityForce(volume,
                                            horizon,
                                            modelCoordinates,
                                            coordinates,
                                            deformationGradient,
                                            forceDensity,
                                            neighborhoodList,
                                            numOwnedPoints,
                                            m_bulkModulus,
                                            m_hourglassCoefficient,
                                            bondDamage
                                            );
      }
  }
}

void
PeridigmNS::CorrespondenceMaterial::computeJacobian(const double dt,
                                             const int numOwnedPoints,
                                             const int* ownedIDs,
                                             const int* neighborhoodList,
                                             PeridigmNS::DataManager& dataManager,
                                             PeridigmNS::SerialMatrix& jacobian,
                                             PeridigmNS::Material::JacobianType jacobianType) const
{

  if(m_applyAutomaticDifferentiationJacobian){
    // Compute the Jacobian via automatic differentiation
    computeAutomaticDifferentiationJacobian(dt, numOwnedPoints, ownedIDs, neighborhoodList, dataManager, jacobian, jacobianType);
  }
  else{
  //  // Call the base class function, which computes the Jacobian by finite difference
    computeJacobianFiniteDifference(dt, numOwnedPoints, ownedIDs, neighborhoodList, dataManager, jacobian, CENTRAL_DIFFERENCE, jacobianType);
  }
}

void
PeridigmNS::CorrespondenceMaterial::computeAutomaticDifferentiationJacobian(const double dt,
                                                                     const int numOwnedPoints,
                                                                     const int* ownedIDs,
                                                                     const int* neighborhoodList,
                                                                     PeridigmNS::DataManager& dataManager,
                                                                     PeridigmNS::SerialMatrix& jacobian,
                                                                     PeridigmNS::Material::JacobianType jacobianType) const
{
  // Compute contributions to the tangent matrix on an element-by-element basis

  // To reduce memory re-allocation, use static variable to store Fad types for
  // current coordinates (independent variables).

  static vector<Sacado::Fad::DFad<double> > coordinatesNP1_AD;
  static vector<Sacado::Fad::DFad<double> > coordinates_AD;

  static Sacado::Fad::DFad<double> C_AD[6][6];

  // Loop over all points.
  int neighborhoodListIndex = 0;
  for(int iID=0 ; iID<numOwnedPoints ; ++iID){

    // Create a temporary neighborhood consisting of a single point and its neighbors.
    int numNeighbors = neighborhoodList[neighborhoodListIndex++];
    int numEntries = numNeighbors+1;
    int numDof = 3*numEntries;
    vector<int> tempMyGlobalIDs(numEntries);
    // Put the node at the center of the neighborhood at the beginning of the list.
    tempMyGlobalIDs[0] = dataManager.getOwnedScalarPointMap()->GID(iID);
    vector<int> tempNeighborhoodList(numEntries);
    tempNeighborhoodList[0] = numNeighbors;

    for(int iNID=0 ; iNID<numNeighbors ; ++iNID){
      int neighborID = neighborhoodList[neighborhoodListIndex++];
      tempMyGlobalIDs[iNID+1] = dataManager.getOverlapScalarPointMap()->GID(neighborID);
      tempNeighborhoodList[iNID+1] = iNID+1;
    }

    Epetra_SerialComm serialComm;
    Teuchos::RCP<Epetra_BlockMap> tempOneDimensionalMap = Teuchos::rcp(new Epetra_BlockMap(numEntries, numEntries, &tempMyGlobalIDs[0], 1, 0, serialComm));
    Teuchos::RCP<Epetra_BlockMap> tempThreeDimensionalMap = Teuchos::rcp(new Epetra_BlockMap(numEntries, numEntries, &tempMyGlobalIDs[0], 3, 0, serialComm));
    Teuchos::RCP<Epetra_BlockMap> tempBondMap = Teuchos::rcp(new Epetra_BlockMap(1, 1, &tempMyGlobalIDs[0], numNeighbors, 0, serialComm));

    // Create a temporary DataManager containing data for this point and its neighborhood.
    PeridigmNS::DataManager tempDataManager;
    tempDataManager.setMaps(Teuchos::RCP<const Epetra_BlockMap>(),
                            tempOneDimensionalMap,
                            Teuchos::RCP<const Epetra_BlockMap>(),
                            tempThreeDimensionalMap,
                            tempBondMap);

    // The temporary data manager will have the same field specs and data as the real data manager.
    vector<int> fieldIds = dataManager.getFieldIds();
    tempDataManager.allocateData(fieldIds);
    tempDataManager.copyLocallyOwnedDataFromDataManager(dataManager);

    // Set up numOwnedPoints and ownedIDs.
    // There is only one owned ID, and it has local ID zero in the tempDataManager.
    int tempNumOwnedPoints = 1;
    vector<int> tempOwnedIDs(tempNumOwnedPoints);
    tempOwnedIDs[0] = 0;

    // Use the scratchMatrix as sub-matrix for storing tangent values prior to loading them into the global tangent matrix.
    // Resize scratchMatrix if necessary
    if(scratchMatrix.Dimension() < numDof)
      scratchMatrix.Resize(numDof);

    // Create a list of global indices for the rows/columns in the scratch matrix.
    vector<int> globalIndices(numDof);
    for(int i=0 ; i<numEntries ; ++i){
      int globalID = tempOneDimensionalMap->GID(i);
      for(int j=0 ; j<3 ; ++j)
        globalIndices[3*i+j] = 3*globalID+j;
    }

    // Extract pointers to the underlying data in the constitutive data array.

    double *modelCoordinates, *coordinates, *volume, *coordinatesNP1, *angles, *detachedNodes;
    tempDataManager.getData(m_modelAnglesId,           PeridigmField::STEP_NONE)->ExtractView(&angles);
    tempDataManager.getData(m_volumeFieldId, PeridigmField::STEP_NONE)->ExtractView(&volume);
    tempDataManager.getData(m_modelCoordinatesFieldId, PeridigmField::STEP_NONE)->ExtractView(&modelCoordinates);
    tempDataManager.getData(m_coordinatesFieldId, PeridigmField::STEP_N)->ExtractView(&coordinates);
    tempDataManager.getData(m_coordinatesFieldId, PeridigmField::STEP_NP1)->ExtractView(&coordinatesNP1);
    dataManager.getData(m_detachedNodesFieldId, PeridigmField::STEP_NP1)->ExtractView(&detachedNodes);
    double *horizon, *bondDamage;
    tempDataManager.getData(m_horizonFieldId, PeridigmField::STEP_NONE)->ExtractView(&horizon);
    tempDataManager.getData(m_bondDamageFieldId, PeridigmField::STEP_NP1)->ExtractView(&bondDamage);

    // Create arrays of Fad objects for the current coordinates, dilatation, and force density
    // Modify the existing vector of Fad objects for the current coordinates
    if((int)coordinatesNP1_AD.size() < numDof) {
      coordinates_AD.resize(numDof);
      coordinatesNP1_AD.resize(numDof);
    }
    for(int i=0 ; i<numDof ; ++i){
      coordinates_AD[i].diff(i, numDof);
      coordinates_AD[i].val() = coordinates[i];
      coordinatesNP1_AD[i].diff(i, numDof);
      coordinatesNP1_AD[i].val() = coordinatesNP1[i];
    }

    vector<Sacado::Fad::DFad<double> > shapeTensorInverse_AD;
    vector<Sacado::Fad::DFad<double> > deformationGradient_AD;
    vector<Sacado::Fad::DFad<double> > cauchyStress_AD;
    vector<Sacado::Fad::DFad<double> > cauchyStressNP1_AD;
    vector<Sacado::Fad::DFad<double> > partialStress_AD;
    vector<Sacado::Fad::DFad<double> > tempStressX_AD;
    vector<Sacado::Fad::DFad<double> > tempStressY_AD;
    vector<Sacado::Fad::DFad<double> > tempStressZ_AD;
    vector<Sacado::Fad::DFad<double> > hourglassStiff_AD;

    partialStress_AD.resize(numDof*numDof);
    shapeTensorInverse_AD.resize(numDof*numDof);
    deformationGradient_AD.resize(numDof*numDof);
    cauchyStress_AD.resize(numDof*numDof);
    cauchyStressNP1_AD.resize(numDof*numDof);
    tempStressX_AD.resize(numDof*numDof);
    tempStressY_AD.resize(numDof*numDof);
    tempStressZ_AD.resize(numDof*numDof);
    hourglassStiff_AD.resize(numDof*numDof);

    for(int i=0 ; i<6 ; ++i){
        for(int j=0 ; j<6 ; ++j){
            C_AD[i][j].val() = C[i][j];
    }}

    int shapeTensorReturnCode = 0;
      shapeTensorReturnCode =
                CORRESPONDENCE::computeShapeTensorInverseAndApproximateDeformationGradient(volume,
                                                                                horizon,
                                                                                modelCoordinates,
                                                                                &coordinates_AD[0],
                                                                                &coordinatesNP1_AD[0],
                                                                                &shapeTensorInverse_AD[0],
                                                                                &deformationGradient_AD[0],
                                                                                bondDamage,
                                                                                &tempNeighborhoodList[0],
                                                                                tempNumOwnedPoints,
                                                                                m_plane,
                                                                                detachedNodes);
    double *cauchyStress;
    double *cauchyStressNP1;

    tempDataManager.getData(m_cauchyStressFieldId, PeridigmField::STEP_N)->ExtractView(&cauchyStress);
    tempDataManager.getData(m_cauchyStressFieldId, PeridigmField::STEP_N)->ExtractView(&cauchyStressNP1);
    CORRESPONDENCE::updateElasticCauchyStressSmallDef(&deformationGradient_AD[0],
                                          &cauchyStress_AD[0],
                                          &cauchyStressNP1_AD[0],
                                          tempNumOwnedPoints,
                                          &C_AD[0],
                                          angles,
                                          m_type,
                                          dt,
                                          m_incremental);

    // define the sacadoFAD force vector
    vector<Sacado::Fad::DFad<double> > force_AD(numDof);
    //std::cout<<m_stabilizationType<<std::endl;
    CORRESPONDENCE::computeForcesAndStresses(
                                        tempNumOwnedPoints,
                                        &tempNeighborhoodList[0],
                                        volume,
                                        horizon,
                                        modelCoordinates,
                                        &coordinatesNP1_AD[0],
                                        &deformationGradient_AD[0],
                                        &cauchyStressNP1_AD[0],
                                        &shapeTensorInverse_AD[0],
                                        bondDamage,
                                        &C_AD[0],
                                        angles,
                                        &force_AD[0],
                                        &partialStress_AD[0],
                                        &tempStressX_AD[0],
                                        &tempStressY_AD[0],
                                        &tempStressZ_AD[0],
                                        &hourglassStiff_AD[0],
                                        m_hourglassCoefficient,
                                        m_stabilizationType,
                                        m_plane,
                                        m_tension,
                                        detachedNodes);

    // Load derivative values into scratch matrix
    // Multiply by volume along the way to convert force density to force
    double value;
    for(int row=0 ; row<numDof ; ++row){
      for(int col=0 ; col<numDof ; ++col){
	value = force_AD[row].dx(col) ; //--> I think this must be it, because forces are already provided
    //value = force_AD[row].dx(col) * volume[row/3]; // given by peridigm org
	TEUCHOS_TEST_FOR_EXCEPT_MSG(!boost::math::isfinite(value), "**** NaN detected in correspondence::computeAutomaticDifferentiationJacobian().\n");
        scratchMatrix(row, col) = value;
      }
    }

    // Sum the values into the global tangent matrix (this is expensive).
    if (jacobianType == PeridigmNS::Material::FULL_MATRIX)
      jacobian.addValues((int)globalIndices.size(), &globalIndices[0], scratchMatrix.Data());
    else if (jacobianType == PeridigmNS::Material::BLOCK_DIAGONAL) {
      jacobian.addBlockDiagonalValues((int)globalIndices.size(), &globalIndices[0], scratchMatrix.Data());
    }
    else // unknown jacobian type
      TEUCHOS_TEST_FOR_EXCEPT_MSG(true, "**** Unknown Jacobian Type\n");
  }
}

void
PeridigmNS::CorrespondenceMaterial::computeJacobianFiniteDifference(const double dt,
                                                           const int numOwnedPoints,
                                                           const int* ownedIDs,
                                                           const int* neighborhoodList,
                                                           PeridigmNS::DataManager& dataManager,
                                                           PeridigmNS::SerialMatrix& jacobian,
                                                           FiniteDifferenceScheme finiteDifferenceScheme,
                                                           PeridigmNS::Material::JacobianType jacobianType) const
{

  // The mechanics Jacobian is of the form:
  //
  // dF_0x/dx_0  dF_0x/dy_0  dF_0x/dz_0  dF_0x/dx_1  dF_0x/dy_1  dF_0x/dz_1  ...  dF_0x/dx_n  dF_0x/dy_n  dF_0x/dz_n
  // dF_0y/dx_0  dF_0y/dy_0  dF_0y/dz_0  dF_0y/dx_1  dF_0y/dy_1  dF_0y/dz_1  ...  dF_0y/dx_n  dF_0y/dy_n  dF_0y/dz_n
  // dF_0z/dx_0  dF_0z/dy_0  dF_0z/dz_0  dF_0z/dx_1  dF_0z/dy_1  dF_0z/dz_1  ...  dF_0z/dx_n  dF_0z/dy_n  dF_0z/dz_n
  // dF_1x/dx_0  dF_1x/dy_0  dF_1x/dz_0  dF_1x/dx_1  dF_1x/dy_1  dF_1x/dz_1  ...  dF_1x/dx_n  dF_1x/dy_n  dF_1x/dz_n
  // dF_1y/dx_0  dF_1y/dy_0  dF_1y/dz_0  dF_1y/dx_1  dF_1y/dy_1  dF_1y/dz_1  ...  dF_1y/dx_n  dF_1y/dy_n  dF_1y/dz_n
  // dF_1z/dx_0  dF_1z/dy_0  dF_1z/dz_0  dF_1z/dx_1  dF_1z/dy_1  dF_1z/dz_1  ...  dF_1z/dx_n  dF_1z/dy_n  dF_1z/dz_n
  //     .           .           .           .           .           .                .           .           .
  //     .           .           .           .           .           .                .           .           .
  //     .           .           .           .           .           .                .           .           .
  // dF_nx/dx_0  dF_nx/dy_0  dF_nx/dz_0  dF_nx/dx_1  dF_nx/dy_1  dF_nx/dz_1  ...  dF_nx/dx_n  dF_nx/dy_n  dF_nx/dz_n
  // dF_ny/dx_0  dF_ny/dy_0  dF_ny/dz_0  dF_ny/dx_1  dF_ny/dy_1  dF_ny/dz_1  ...  dF_ny/dx_n  dF_ny/dy_n  dF_ny/dz_n
  // dF_nz/dx_0  dF_nz/dy_0  dF_nz/dz_0  dF_nz/dx_1  dF_nz/dy_1  dF_nz/dz_1  ...  dF_nz/dx_n  dF_nz/dy_n  dF_nz/dz_n

  // Each entry is computed by finite difference:
  //
  // Forward difference:
  // dF_0x/dx_0 = ( F_0x(perturbed x_0) - F_0x(unperturbed) ) / epsilon
  //
  // Central difference:
  // dF_0x/dx_0 = ( F_0x(positive perturbed x_0) - F_0x(negative perturbed x_0) ) / ( 2.0*epsilon )

  TEUCHOS_TEST_FOR_EXCEPT_MSG(m_finiteDifferenceProbeLength == DBL_MAX, "**** Finite-difference Jacobian requires that the \"Finite Difference Probe Length\" parameter be set.\n");
  double epsilon = m_finiteDifferenceProbeLength;

  PeridigmNS::DegreesOfFreedomManager& dofManager = PeridigmNS::DegreesOfFreedomManager::self();
  bool solveForDisplacement = dofManager.displacementTreatedAsUnknown();
  //bool solveForTemperature = dofManager.temperatureTreatedAsUnknown();
  int numDof = dofManager.totalNumberOfDegreesOfFreedom();
  int numDisplacementDof = dofManager.numberOfDisplacementDegreesOfFreedom();
  //int numTemperatureDof = dofManager.numberOfTemperatureDegreesOfFreedom();
  int displacementDofOffset = dofManager.displacementDofOffset();
  //int temperatureDofOffset = dofManager.temperatureDofOffset();

  // Get field ids for all relevant data
  PeridigmNS::FieldManager& fieldManager = PeridigmNS::FieldManager::self();
  int volumeFId(-1), coordinatesFId(-1), velocityFId(-1), forceDensityFId(-1); //, temperatureFId(-1), fluxDivergenceFId(-1);
  volumeFId = fieldManager.getFieldId("Volume");
  if (solveForDisplacement) {
    coordinatesFId = fieldManager.getFieldId("Coordinates");
    velocityFId = fieldManager.getFieldId("Velocity");
    forceDensityFId = fieldManager.getFieldId("Force_Density");
  }
  //if (solveForTemperature) {
  //  temperatureFId = fieldManager.getFieldId("Temperature");
  //  fluxDivergenceFId = fieldManager.getFieldId("Flux_Divergence");
  //}

  int neighborhoodListIndex = 0;
  for(int iID=0 ; iID<numOwnedPoints ; ++iID){

    // Create a temporary neighborhood consisting of a single point and its neighbors.
    int numNeighbors = neighborhoodList[neighborhoodListIndex++];
    vector<int> tempMyGlobalIDs(numNeighbors+1);
    // Put the node at the center of the neighborhood at the beginning of the list.
    tempMyGlobalIDs[0] = dataManager.getOwnedScalarPointMap()->GID(iID);
    vector<int> tempNeighborhoodList(numNeighbors+1);
    tempNeighborhoodList[0] = numNeighbors;
    for(int iNID=0 ; iNID<numNeighbors ; ++iNID){
      int neighborID = neighborhoodList[neighborhoodListIndex++];
      tempMyGlobalIDs[iNID+1] = dataManager.getOverlapScalarPointMap()->GID(neighborID);
      tempNeighborhoodList[iNID+1] = iNID+1;
    }

    Epetra_SerialComm serialComm;
    Teuchos::RCP<Epetra_BlockMap> tempOneDimensionalMap = Teuchos::rcp(new Epetra_BlockMap(numNeighbors+1, numNeighbors+1, &tempMyGlobalIDs[0], 1, 0, serialComm));
    Teuchos::RCP<Epetra_BlockMap> tempThreeDimensionalMap = Teuchos::rcp(new Epetra_BlockMap(numNeighbors+1, numNeighbors+1, &tempMyGlobalIDs[0], 3, 0, serialComm));
    Teuchos::RCP<Epetra_BlockMap> tempBondMap = Teuchos::rcp(new Epetra_BlockMap(1, 1, &tempMyGlobalIDs[0], numNeighbors, 0, serialComm));

    // Create a temporary DataManager containing data for this point and its neighborhood.
    PeridigmNS::DataManager tempDataManager;
    tempDataManager.setMaps(Teuchos::RCP<const Epetra_BlockMap>(),
                            tempOneDimensionalMap,
                            Teuchos::RCP<const Epetra_BlockMap>(),
                            tempThreeDimensionalMap,
                            tempBondMap);

    // The temporary data manager will have the same fields and data as the real data manager.
    vector<int> fieldIds = dataManager.getFieldIds();
    tempDataManager.allocateData(fieldIds);
    tempDataManager.copyLocallyOwnedDataFromDataManager(dataManager);

    // Set up numOwnedPoints and ownedIDs.
    // There is only one owned ID, and it has local ID zero in the tempDataManager.
    int tempNumOwnedPoints = 1;
    vector<int> tempOwnedIDs(1);
    tempOwnedIDs[0] = 0;

    // Extract pointers to the underlying data.
    double *volume, *y, *v, *force;//, *temperature, *fluxDivergence;
    tempDataManager.getData(volumeFId, PeridigmField::STEP_NONE)->ExtractView(&volume);
    if (solveForDisplacement) {
      tempDataManager.getData(coordinatesFId, PeridigmField::STEP_NP1)->ExtractView(&y);
      tempDataManager.getData(velocityFId, PeridigmField::STEP_NP1)->ExtractView(&v);
      tempDataManager.getData(forceDensityFId, PeridigmField::STEP_NP1)->ExtractView(&force);
    }
    //if (solveForTemperature) {
    //  tempDataManager.getData(temperatureFId, PeridigmField::STEP_NP1)->ExtractView(&temperature);
    //  tempDataManager.getData(fluxDivergenceFId, PeridigmField::STEP_NP1)->ExtractView(&fluxDivergence);
    //}

    // Create a temporary vector for storing force and/or flux divergence.
    Teuchos::RCP<Epetra_Vector> forceVector, tempForceVector, fluxDivergenceVector, tempFluxDivergenceVector;
    double *tempForce, *tempFluxDivergence;
    if (solveForDisplacement) {
      forceVector = tempDataManager.getData(forceDensityFId, PeridigmField::STEP_NP1);
      tempForceVector = Teuchos::rcp(new Epetra_Vector(*forceVector));
      tempForceVector->ExtractView(&tempForce);
    }
    //if (solveForTemperature) {
    //  fluxDivergenceVector = tempDataManager.getData(fluxDivergenceFId, PeridigmField::STEP_NP1);
    //  tempFluxDivergenceVector = Teuchos::rcp(new Epetra_Vector(*fluxDivergenceVector));
    //  tempFluxDivergenceVector->ExtractView(&tempFluxDivergence);
    //}

    // Use the scratchMatrix as sub-matrix for storing tangent values prior to loading them into the global tangent matrix.
    // Resize scratchMatrix if necessary
    if(scratchMatrix.Dimension() < numDof*(numNeighbors+1))
      scratchMatrix.Resize(numDof*(numNeighbors+1));

    // Create a list of global indices for the rows/columns in the scratch matrix.
    vector<int> globalIndices(numDof*(numNeighbors+1));
    for(int i=0 ; i<numNeighbors+1 ; ++i){
      int globalID = tempOneDimensionalMap->GID(i);
      for(int j=0 ; j<numDof ; ++j){
        globalIndices[numDof*i+j] = numDof*globalID+j;
      }
    }

    if(finiteDifferenceScheme == FORWARD_DIFFERENCE){
      if (solveForDisplacement) {
        // Compute and store the unperturbed force.
        computeForce(dt, tempNumOwnedPoints, &tempOwnedIDs[0], &tempNeighborhoodList[0], tempDataManager);

        for(int i=0 ; i<forceVector->MyLength() ; ++i)
          tempForce[i] = force[i];
      }
      //if (solveForTemperature) {
      //  // Compute and store the unperturbed flux divergence.
      //  computeFluxDivergence(dt, tempNumOwnedPoints, &tempOwnedIDs[0], &tempNeighborhoodList[0], tempDataManager);
      //  for(int i=0 ; i<fluxDivergenceVector->MyLength() ; ++i)
      //    tempFluxDivergence[i] = fluxDivergence[i];
      //}
    }

    // Perturb one dof in the neighborhood at a time and compute the force and/or flux divergence.
    // The point itself plus each of its neighbors must be perturbed.
    for(int iNID=0 ; iNID<numNeighbors+1 ; ++iNID){

      int perturbID;
      if(iNID < numNeighbors)
        perturbID = tempNeighborhoodList[iNID+1];
      else
        perturbID = 0;

      // Displacement degrees of freedom
      for(int dof=0 ; dof<numDisplacementDof ; ++dof){

        // Perturb a dof and compute the forces.
        double oldY = y[numDof*perturbID+dof];
        double oldV = v[numDof*perturbID+dof];

        if(finiteDifferenceScheme == CENTRAL_DIFFERENCE){
          // Compute and store the negatively perturbed force.
          y[numDof*perturbID+dof] -= epsilon;
          v[numDof*perturbID+dof] -= epsilon/dt;
          computeForce(dt, tempNumOwnedPoints, &tempOwnedIDs[0], &tempNeighborhoodList[0], tempDataManager);
          y[numDof*perturbID+dof] = oldY;
          v[numDof*perturbID+dof] = oldV;
          for(int i=0 ; i<forceVector->MyLength() ; ++i)
            tempForce[i] = force[i];
        }

        // Compute the purturbed force.
        y[numDof*perturbID+dof] += epsilon;
        v[numDof*perturbID+dof] += epsilon/dt;
        computeForce(dt, tempNumOwnedPoints, &tempOwnedIDs[0], &tempNeighborhoodList[0], tempDataManager);
        y[numDof*perturbID+dof] = oldY;
        v[numDof*perturbID+dof] = oldV;

        for(int i=0 ; i<numNeighbors+1 ; ++i){
          int forceID;
          if(i < numNeighbors)
            forceID = tempNeighborhoodList[i+1];
          else
            forceID = 0;

          for(int d=0 ; d<numDof ; ++d){
            double value = ( force[numDof*forceID+d] - tempForce[numDof*forceID+d] ) / epsilon;
            if(finiteDifferenceScheme == CENTRAL_DIFFERENCE)
              value *= 0.5;
            scratchMatrix(numDof*forceID + displacementDofOffset + d, numDof*perturbID + displacementDofOffset + dof) = value;
          }
        }
      }

      // Temperature degrees of freedom
    //if(solveForTemperature){
    //
    //  // Perturb a temperature value and compute the flux divergence.
    //  double oldTemperature = temperature[perturbID];
    //
    //  if(finiteDifferenceScheme == CENTRAL_DIFFERENCE){
    //    // Compute and store the negatively perturbed flux divergence.
    //    temperature[perturbID] -= epsilon;
    //    computeFluxDivergence(dt, tempNumOwnedPoints, &tempOwnedIDs[0], &tempNeighborhoodList[0], tempDataManager);
    //    temperature[perturbID] = oldTemperature;
    //    for(int i=0 ; i<fluxDivergenceVector->MyLength() ; ++i)
    //      tempFluxDivergence[i] = fluxDivergence[i];
    //  }
    //
    //// Compute the purturbed flux divergence.
    //  temperature[perturbID] += epsilon;
    //  computeFluxDivergence(dt, tempNumOwnedPoints, &tempOwnedIDs[0], &tempNeighborhoodList[0], tempDataManager);
    //  temperature[perturbID] = oldTemperature;
    //
    //  for(int i=0 ; i<numNeighbors+1 ; ++i){
    //    int fluxDivergenceID;
    //    if(i < numNeighbors)
    //      fluxDivergenceID = tempNeighborhoodList[i+1];
    //    else
    //      fluxDivergenceID = 0;
    //
    //    double value = ( fluxDivergence[fluxDivergenceID] - tempFluxDivergence[fluxDivergenceID] ) / epsilon;
    //    if(finiteDifferenceScheme == CENTRAL_DIFFERENCE)
    //      value *= 0.5;
    //    scratchMatrix(numDof*fluxDivergenceID + temperatureDofOffset, numDof*perturbID + temperatureDofOffset) = value;
    //}
    //}
    }

    // Multiply by nodal volume
    for(unsigned int row=0 ; row<globalIndices.size() ; ++row){
      for(unsigned int col=0 ; col<globalIndices.size() ; ++col){
        scratchMatrix(row, col) *= volume[row/numDof];
      }
    }

    // Check for NaNs
    for(unsigned int row=0 ; row<globalIndices.size() ; ++row){
      for(unsigned int col=0 ; col<globalIndices.size() ; ++col){
        TEUCHOS_TEST_FOR_EXCEPT_MSG(!std::isfinite(scratchMatrix(row, col)), "**** NaN detected in finite-difference Jacobian.\n");
      }
    }

    // Sum the values into the global tangent matrix (this is expensive).
    if (jacobianType == PeridigmNS::Material::FULL_MATRIX)
      jacobian.addValues((int)globalIndices.size(), &globalIndices[0], scratchMatrix.Data());
    else if (jacobianType == PeridigmNS::Material::BLOCK_DIAGONAL) {
      jacobian.addBlockDiagonalValues((int)globalIndices.size(), &globalIndices[0], scratchMatrix.Data());
    }
    else // unknown jacobian type
      TEUCHOS_TEST_FOR_EXCEPT_MSG(true, "**** Unknown Jacobian Type\n");
  }
}


// Copyright (c) 2020 The Navcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <blsct/transaction.h>
#include <chainparams.h>
#include <coins.h>
#include <hash.h>
#include <key.h>
#include <random.h>
#include <uint256.h>
#include <wallet/test/wallet_test_fixture.h>
#include <main.h>

#include <vector>
#include <map>

#include <boost/test/unit_test.hpp>

const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;

BOOST_FIXTURE_TEST_SUITE(blsct_tests, WalletTestingSetup)

BOOST_AUTO_TEST_CASE(blsct)
{
    LOCK(cs_main);

    CStateViewDB *pcoinsdbview = new CStateViewDB(1<<23, true);
    CStateViewCache *base = new CStateViewCache(pcoinsdbview);
    CStateViewCache view(pcoinsTip);

    CKey key;
    key.MakeNewKey(0);

    CHashWriter h(0, 0);
    std::vector<unsigned char> vKey(key.begin(), key.end());

    h << vKey;

    uint256 hash = h.GetHash();
    unsigned char h_[32];
    memcpy(h_, &hash, 32);

    blsctKey masterBLSKey = blsctKey(bls::PrivateKey::FromSeed(h_, 32));
    blsctKey childBLSKey = blsctKey(masterBLSKey.PrivateChild(BIP32_HARDENED_KEY_LIMIT|130));
    blsctKey transactionBLSKey = blsctKey(childBLSKey.PrivateChild(BIP32_HARDENED_KEY_LIMIT));
    blsctKey blindingBLSKey = blsctKey(childBLSKey.PrivateChild(BIP32_HARDENED_KEY_LIMIT|1));
    bls::PrivateKey viewKey = blsctKey(transactionBLSKey).PrivateChild(BIP32_HARDENED_KEY_LIMIT);
    bls::PrivateKey spendKey = blsctKey(transactionBLSKey).PrivateChild(BIP32_HARDENED_KEY_LIMIT|1);

    blsctDoublePublicKey doubleKey(viewKey.GetG1Element(), spendKey.GetG1Element());

    BOOST_CHECK(pwalletMain->SetBLSCTDoublePublicKey(doubleKey));
    BOOST_CHECK(pwalletMain->SetBLSCTViewKey(blsctKey(viewKey)));
    BOOST_CHECK(pwalletMain->SetBLSCTSpendKey(blsctKey(spendKey)));
    BOOST_CHECK(pwalletMain->SetBLSCTBlindingMasterKey(blindingBLSKey));

    BOOST_CHECK(pwalletMain->NewBLSCTBlindingKeyPool());
    BOOST_CHECK(pwalletMain->NewBLSCTSubAddressKeyPool(0));
    BOOST_CHECK(!pwalletMain->IsLocked());

    BOOST_CHECK(pwalletMain->TopUpBLSCTBlindingKeyPool());
    BOOST_CHECK(pwalletMain->TopUpBLSCTSubAddressKeyPool(0));

    CKeyID keyID;
    BOOST_CHECK(pwalletMain->GetBLSCTSubAddressKeyFromPool(0, keyID));

    blsctPublicKey bpk;
    BOOST_CHECK(pwalletMain->GetBLSCTBlindingKeyFromPool(bpk));

    blsctKey b;
    BOOST_CHECK(pwalletMain->GetBLSCTBlindingKey(bpk, b));

    blsctDoublePublicKey destKey;
    BOOST_CHECK(pwalletMain->GetBLSCTSubAddressPublicKeys(keyID, destKey));

    CMutableTransaction prevTx;

    prevTx.vout.resize(2);
    prevTx.vout[0].nValue = 10*COIN;
    prevTx.vout[0].scriptPubKey << ToByteVector(key.GetPubKey()) << OP_CHECKSIG;

    bls::PrivateKey bk = b.GetKey();
    Scalar gammaIns, gammaOuts, gammaPrevOut;
    std::string strFailReason;
    std::vector<bls::G2Element> vBLSSignatures;
    bls::G1Element nonce;

    BOOST_CHECK(CreateBLSCTOutput(bk, nonce, prevTx.vout[1], destKey, 10*COIN, "", gammaPrevOut, strFailReason, false, vBLSSignatures));

    view.ModifyCoins(prevTx.GetHash())->FromTx(prevTx, 0);

    CMutableTransaction spendingTx;

    spendingTx.nVersion |= TX_BLS_CT_FLAG;

    spendingTx.vin.resize(1);
    spendingTx.vin[0].prevout = COutPoint(prevTx.GetHash(), 0);

    spendingTx.vout.resize(1);

    vBLSSignatures.clear();
    spendingTx.vchTxSig.clear();
    spendingTx.vchBalanceSig.clear();

    gammaIns = 0;
    gammaOuts = 0;

    BOOST_CHECK(CreateBLSCTOutput(bk, nonce, spendingTx.vout[0], destKey, 10, "test", gammaOuts, strFailReason, true, vBLSSignatures));

    std::vector<RangeproofEncodedData> vData;
    CValidationState state;

    // Public to Private. Different amount. Balance signature empty. Tx signature empty.
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(vData[0].message == "test");
    BOOST_CHECK(vData[0].amount == 10);

    Scalar diff = gammaIns-gammaOuts;
    bls::PrivateKey balanceSigningKey = bls::PrivateKey::FromBN(diff.bn);

    spendingTx.vchBalanceSig = bls::BasicSchemeMPL::Sign(balanceSigningKey, balanceMsg).Serialize();

    // Public to Private. Different amount. Balance signature correct. Tx signature empty.
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));

    vBLSSignatures.clear();
    spendingTx.vchTxSig.clear();
    spendingTx.vchBalanceSig.clear();

    gammaIns = 0;
    gammaOuts = 0;

    BOOST_CHECK(CreateBLSCTOutput(bk, nonce, spendingTx.vout[0], destKey, 10*COIN, "test2test2test2test2test2test2test2test2test2test2test", gammaOuts, strFailReason, true, vBLSSignatures));

    // Public to Private. Same amount. Balance signature empty. Tx signature empty.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));

    diff = gammaIns-gammaOuts;
    balanceSigningKey = bls::PrivateKey::FromBN(diff.bn);

    spendingTx.vchBalanceSig = bls::BasicSchemeMPL::Sign(balanceSigningKey, balanceMsg).Serialize();

    // Public to Private. Same amount. Balance signature correct. Tx signature empty.
    state = CValidationState();
    BOOST_CHECK(VerifyBLSCT(spendingTx, viewKey, vData, view, state));

    BOOST_CHECK(vData[0].message == "test2test2test2test2test2test2test2test2test2test2test");
    BOOST_CHECK(vData[0].amount == 10*COIN);

    vBLSSignatures.clear();
    spendingTx.vchTxSig.clear();
    spendingTx.vchBalanceSig.clear();

    gammaIns = gammaPrevOut;
    gammaOuts = 0;

    spendingTx.vin[0].prevout = COutPoint(prevTx.GetHash(), 1);
    spendingTx.nVersion |= TX_BLS_INPUT_FLAG;

    BOOST_CHECK(!CreateBLSCTOutput(bk, nonce, spendingTx.vout[0], destKey, 10*COIN, "123456789012345678901234567890123456789012345678901234567", gammaOuts, strFailReason, true, vBLSSignatures));
    BOOST_CHECK(strFailReason.substr(0,33) == "Range proof failed with exception");

    gammaIns = gammaPrevOut;
    gammaOuts = 0;

    BOOST_CHECK(CreateBLSCTOutput(bk, nonce, spendingTx.vout[0], destKey, 10*COIN, "test2test2test2test2test2test2test2test2test2test2test", gammaOuts, strFailReason, true, vBLSSignatures));
    // Private to Private. Same amount. Balance signature empty. Tx signature empty.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));

    diff = gammaIns-gammaOuts;
    balanceSigningKey = bls::PrivateKey::FromBN(diff.bn);
    spendingTx.vchBalanceSig = bls::BasicSchemeMPL::Sign(balanceSigningKey, balanceMsg).Serialize();

    // Private to Private. Same amount. Balance signature correct. Tx signature empty.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(state.GetRejectReason() == "could-not-read-blstxsig");

    spendingTx.vchTxSig = bls::BasicSchemeMPL::Aggregate(vBLSSignatures).Serialize();

    // Private to Private. Same amount. Balance signature correct. Tx signature incomplete.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(state.GetRejectReason() == "invalid-bls-signature");

    blsctKey sk_;

    BOOST_CHECK(pwalletMain->GetBLSCTSubAddressSpendingKeyForOutput(keyID, prevTx.vout[1].outputKey, sk_));
    BOOST_CHECK(sk_.GetG1Element() == bls::G1Element::FromBytes(prevTx.vout[1].spendingKey.data()));

    SignBLSInput(sk_.GetKey(), spendingTx.vin[0], vBLSSignatures);

    spendingTx.vchTxSig = bls::BasicSchemeMPL::Aggregate(vBLSSignatures).Serialize();

    // Private to Private. Same amount. Balance signature correct. Tx signature complete.
    state = CValidationState();
    BOOST_CHECK(VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(vData[0].message == "test2test2test2test2test2test2test2test2test2test2test");
    BOOST_CHECK(vData[0].amount == 10*COIN);

    vBLSSignatures.clear();
    spendingTx.vchTxSig.clear();
    spendingTx.vchBalanceSig.clear();

    gammaIns = gammaPrevOut;
    gammaOuts = 0;

    spendingTx.vin[0].prevout = COutPoint(prevTx.GetHash(), 1);
    spendingTx.nVersion |= TX_BLS_INPUT_FLAG;

    BOOST_CHECK(CreateBLSCTOutput(bk, nonce, spendingTx.vout[0], destKey, 10, "test2", gammaOuts, strFailReason, true, vBLSSignatures));

    // Private to Private. Different amount. Balance signature empty. Tx signature empty.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(state.GetRejectReason() == "could-not-read-balanceproof");

    diff = gammaIns-gammaOuts;
    balanceSigningKey = bls::PrivateKey::FromBN(diff.bn);
    spendingTx.vchBalanceSig = bls::BasicSchemeMPL::Sign(balanceSigningKey, balanceMsg).Serialize();

    // Private to Private. Different amount. Balance signature complete but incorrect due different amount. Tx signature empty.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(state.GetRejectReason() == "invalid-balanceproof");
    spendingTx.vchTxSig = bls::BasicSchemeMPL::Aggregate(vBLSSignatures).Serialize();

    // Private to Private. Different amount. Balance signature complete but incorrect due different amount. Tx signature empty.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(state.GetRejectReason() == "invalid-balanceproof");

    SignBLSInput(sk_.GetKey(), spendingTx.vin[0], vBLSSignatures);

    spendingTx.vchTxSig = bls::BasicSchemeMPL::Aggregate(vBLSSignatures).Serialize();

    // Private to Private. Different amount. Balance signature correct. Tx signature complete.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(state.GetRejectReason() == "invalid-balanceproof");
    BOOST_CHECK(vData[0].message == "test2");
    BOOST_CHECK(vData[0].amount == 10);

    spendingTx.nVersion &= ~TX_BLS_CT_FLAG;

    spendingTx.vout[0] = CTxOut();
    spendingTx.vout[0].nValue = 10*COIN;
    spendingTx.vout[0].scriptPubKey << ToByteVector(key.GetPubKey()) << OP_CHECKSIG;
    spendingTx.vout[0].ephemeralKey = bk.GetG1Element().Serialize();

    vBLSSignatures.clear();
    spendingTx.vchTxSig.clear();
    spendingTx.vchBalanceSig.clear();

    gammaIns = gammaPrevOut;
    gammaOuts = 0;

    // Private to Public. Same amount. Balance signature empty. Tx signature empty.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(state.GetRejectReason() == "could-not-read-balanceproof");

    SignBLSInput(sk_.GetKey(), spendingTx.vin[0], vBLSSignatures);
    SignBLSOutput(bk, spendingTx.vout[0], vBLSSignatures);

    spendingTx.vchTxSig = bls::BasicSchemeMPL::Aggregate(vBLSSignatures).Serialize();

    // Private to Public. Same amount. Balance signature empty. Tx signature complete.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(state.GetRejectReason() == "could-not-read-balanceproof");

    diff = gammaIns-gammaOuts;
    balanceSigningKey = bls::PrivateKey::FromBN(diff.bn);

    spendingTx.vchBalanceSig = bls::BasicSchemeMPL::Sign(balanceSigningKey, balanceMsg).Serialize();

    // Private to Public. Same amount. Balance signature correct. Tx signature complete.
    state = CValidationState();

    BOOST_CHECK(VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    spendingTx.vout[0].nValue = 10;

    BulletproofsRangeproof proofCheck = spendingTx.vout[0].GetBulletproof();
    BulletproofsRangeproof proofCheck2(proofCheck.GetVch());

    BOOST_CHECK(proofCheck == proofCheck2);
    BOOST_CHECK(proofCheck.GetVch() == proofCheck2.GetVch());

    // Private to Public. Different amount. Balance signature correct. Tx signature complete.
    state = CValidationState();
    BOOST_CHECK(!VerifyBLSCT(spendingTx, viewKey, vData, view, state));
    BOOST_CHECK(state.GetRejectReason() == "invalid-balanceproof");
}

BOOST_AUTO_TEST_SUITE_END()


//==================================================================================================
/*!

  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/constant/real.hpp>
#include <boost/simd/pack.hpp>
#include <algorithm>

#include <simd_test.hpp>

STF_CASE_TPL( "Check real constant behavior for IEEE packs", (double)(float) )
{
  namespace bs =  boost::simd;
  using pack_t  = bs::pack<T>;
  using hpack_t = bs::pack<T,pack_t::static_size/2>;
  using dpack_t = bs::pack<T,pack_t::static_size*2>;

  auto are_correct = [](T e) { return e == bs::Real<T,0x402ABD70A3D70A3DULL,0x4155EB85UL>(); };

  auto pack  = bs::Real<pack_t ,0x402ABD70A3D70A3DULL,0x4155EB85UL>();
  auto hpack = bs::Real<hpack_t,0x402ABD70A3D70A3DULL,0x4155EB85UL>();
  auto dpack = bs::Real<dpack_t,0x402ABD70A3D70A3DULL,0x4155EB85UL>();

  STF_EXPECT(( std::all_of(pack.begin() , pack.end(), are_correct ) ));
  STF_EXPECT(( std::all_of(hpack.begin(),hpack.end(), are_correct ) ));
  STF_EXPECT(( std::all_of(dpack.begin(),dpack.end(), are_correct ) ));
}


#include <iostream>
#include <boost/python.hpp>
#include <map>
#include <string>
#include <exception>
#include <stdexcept>
#include "pyutil.hpp"

using namespace std;
using namespace boost::python;

struct Vec4d
{
  double values[4];

  Vec4d()
  {
    for(int iter = 0; iter < 4; ++iter)
      values[iter] = 0;
  }

  double dist(const Vec4d&o) const
  {
    double ssd = 0;
    for(int iter = 0; iter < 4; ++iter)
    {
      double diff = (values[iter] - o.values[iter]);
      ssd += diff * diff;
    }
    return ssd;
  }
};

std::ostream& operator<<(ostream&os, const Vec4d&o)
{
  os << "[" << o.values[0] << ", " << o.values[1] << ", " << o.values[2] << ", " << o.values[3] << "] ";
  return os;
}

#include "eval_ik.cpp"

bool strhas(const string&str,const string&has)
{
  return str.find(has) != std::string::npos;
}

class IK
{
public:
  map<string,double> null_solution;
  map<string,double> regularizers;
  float step_size;

  IK()
  {
  }

  double objective_cost(map<string,Vec4d>&target_positions,map<string,double>&solution)
  {
    // compute target component
    double ssd = 0;
    for(auto && targ : target_positions)
    {
      Vec4d cur = joint_position(targ.first,solution);
      ssd += cur.dist(targ.second);
    }

    // compute the reguarizer cost.
    for(auto && param : solution)
    {
      auto null_value = null_solution.find(param.first);
      if(null_value != null_solution.end())
      {
	double C = regularizers[param.first];
	double diff = null_value->second - param.second;
	ssd += C * diff * diff;
      }
    }

    return ssd;
  }

  map<string,double> perturb_solution_random(const map<string,double>&solution)
  {
    map<string,double> sol_prime = solution;
    static std::default_random_engine generator;
    static std::normal_distribution<double> distribution(0,1.0);

    int param_id = rand() % sol_prime.size();
    int iter =0;
    for(auto && param : sol_prime)
    {
      if(iter == param_id)
	param.second += distribution(generator);
      ++iter;
    }

    return sol_prime;
  }

  map<string,double> perturb_solution_gradient(map<string,Vec4d>&targets,map<string,double>&solution)
  {
    map<string,double> sp;

    for(auto && param : solution)
    {
      // data gradient
      double grad = 0;
      for(auto && target : targets)
      {
	Vec4d cur_pos = joint_position(target.first,solution);
	for(int i = 0; i < 4; ++i)
	{
	  grad += 2 * (cur_pos.values[i] - target.second.values[i]) * diff(target.first,param.first,i,solution);
	}
      }

      // regularizer gradient
      auto null_value = null_solution.at(param.first);
      double C = regularizers.at(param.first);
      double diff = null_value - param.second;
      grad += C * -2 * diff;

      // update
      sp[param.first] = param.second - step_size*grad;
    }

    return sp;
  }

  map<string,double> perturb_solution(map<string,Vec4d>&target,map<string,double>&solution)
  {
    static long ctr = 0;
    ctr++;
    if(ctr % 500 == 0)
      return perturb_solution_gradient(target,solution);
    else
      return perturb_solution_random(solution);
  }

  void print_match(map<string,Vec4d>&target_positions,map<string,double>&solution)
  {
    for(auto && targ : target_positions)
    {
      Vec4d cur = joint_position(targ.first,solution);
      cout << targ.first << targ.second << " vs " << cur << endl;
    }
  }

  map<string,double> extract_solution(boost::python::dict pySol)
  {
    boost::python::list sol_keys = pySol.keys();
    map<string,double> sol;
    for(int iter = 0; iter < len(sol_keys); ++iter)
    {
      boost::python::extract<std::string> key(sol_keys[iter]);
      boost::python::extract<double> value(pySol[sol_keys[iter]]);
      sol[key] = value;
    }
    return sol;
  }

  map<string,Vec4d> extract_targets(boost::python::dict target_positions)
  {
    boost::python::list target_keys = target_positions.keys();
    map<string,Vec4d> target;
    for( int iter = 0; iter < len(target_keys); ++iter)
    {
      boost::python::extract<std::string> key(target_keys[iter]);
      boost::python::list value = boost::python::extract<boost::python::list>(target_positions[target_keys[iter]]);
      Vec4d v;
      assert(len(value) == 4);
      for(int jter = 0; jter < len(value); ++jter)
	v.values[jter] = boost::python::extract<double>(value[jter]);
      target[key] = v;
    }
    return target;
  }

  void set_null_solution(boost::python::dict null_solution)
  {
    // convert init solution
    this->null_solution = extract_solution(null_solution);
  }

  void set_regularizers(boost::python::dict py_reguarizers)
  {
    // convert init solution
    this->regularizers = extract_solution(py_reguarizers);
  }

  void optimize(map<string,Vec4d>&target,map<string,double>&sol)
  {
    ScopedGILRelease unlock;

    double start_cost = objective_cost(target,sol);
    double best_cost = start_cost;
    cout << "init cost = " << best_cost << endl;
    int max_iter = 1000;
    for(int iter = 0; iter < max_iter; ++iter)
    {
      auto sol_prime = perturb_solution(target,sol);
      double cost_prime = objective_cost(target,sol_prime);
      if(cost_prime < best_cost)
      {
	cout << "ACCEPT: " << best_cost << " => " << cost_prime << endl;
	sol = sol_prime;
	best_cost = cost_prime;
      }
      else if(iter % 100 == 0)
      {
	cout << "REJECT: " << best_cost << " => " << cost_prime << endl;
      }

      if(iter % 1000 == 0)
      {
	cout << "iter " << iter << " of " << 50000 << endl;
      }
    }

    print_match(target,sol);
    cout << "init cost = " << start_cost << endl;
    cout << "final cost = " << objective_cost(target,sol) << endl;
  }

  boost::python::dict solve(boost::python::dict target_positions,boost::python::dict init_solution)
  {
    // convert arguments
    map<string,Vec4d> target = extract_targets(target_positions);

    // convert init solution
    map<string,double> sol = extract_solution(init_solution);

    // OPTIMIZE THE DAMN THING!!!
    {
      optimize(target,sol);
    }

    // convert to output
    boost::python::dict dictionary;
    for(auto && param : sol)
    {
      dictionary[param.first] = param.second;
    }
    return dictionary;
  }

  void set_grad_step_size(float s)
  {
    step_size = s;
  }
};

BOOST_PYTHON_MODULE(iksolver)
{
  class_<IK>("iksolver", init<>())
    .def(init<>())
    .def("solve", &IK::solve)
    .def("set_null_solution",&IK::set_null_solution)
    .def("set_regularizers",&IK::set_regularizers)
    .def("set_grad_step_size",&IK::set_grad_step_size);
}


//          Copyright Carl Philipp Reh 2009 - 2016.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include <fcppt/algorithm/contains.hpp>
#include <fcppt/container/grid/moore_neighbor_array.hpp>
#include <fcppt/container/grid/moore_neighbors.hpp>
#include <fcppt/container/grid/pos.hpp>
#include <fcppt/math/vector/comparison.hpp>
#include <fcppt/preprocessor/disable_gcc_warning.hpp>
#include <fcppt/preprocessor/pop_warning.hpp>
#include <fcppt/preprocessor/push_warning.hpp>
#include <fcppt/config/external_begin.hpp>
#include <boost/test/unit_test.hpp>
#include <algorithm>
#include <fcppt/config/external_end.hpp>

namespace
{

typedef
fcppt::container::grid::pos<
	unsigned,
	2
>
pos;

typedef
fcppt::container::grid::moore_neighbor_array<
	pos
>
array;

bool
contains_element(
	array const &_result,
	pos const _pos
)
{
	return
		fcppt::algorithm::contains(
			_result,
			_pos
		);
}

}

FCPPT_PP_PUSH_WARNING
FCPPT_PP_DISABLE_GCC_WARNING(-Weffc++)

BOOST_AUTO_TEST_CASE(
	container_grid_moore_neighbors
)
{
FCPPT_PP_POP_WARNING

	array const result(
		fcppt::container::grid::moore_neighbors(
			pos(
				5u,
				7u
			)
		)
	);

	BOOST_CHECK(
		contains_element(
			result,
			pos(
				4u,
				7u
			)
		)
	);

	BOOST_CHECK(
		contains_element(
			result,
			pos(
				6u,
				7u
			)
		)
	);

	BOOST_CHECK(
		contains_element(
			result,
			pos(
				5u,
				6u
			)
		)
	);

	BOOST_CHECK(
		contains_element(
			result,
			pos(
				5u,
				8u
			)
		)
	);

	BOOST_CHECK(
		contains_element(
			result,
			pos(
				4u,
				6u
			)
		)
	);

	BOOST_CHECK(
		contains_element(
			result,
			pos(
				6u,
				6u
			)
		)
	);

	BOOST_CHECK(
		contains_element(
			result,
			pos(
				4u,
				8u
			)
		)
	);

	BOOST_CHECK(
		contains_element(
			result,
			pos(
				6u,
				8u
			)
		)
	);
}


// TRENTO: Reduced Thickness Event-by-event Nuclear Topology
// Copyright 2015 Jonah E. Bernhard, J. Scott Moreland
// MIT License

#include "../src/output.h"

#include "catch.hpp"
#include "util.h"

#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>

#include "../src/event.h"
#include "../src/hdf5_utils.h"
#include "../src/nucleus.h"

using namespace trento;

TEST_CASE( "output" ) {
  auto var_map = make_var_map({
    {"normalization", 1.},
    {"reduced-thickness", 0.},
    {"grid-max", 9.},
    {"grid-step", 0.3},
    {"fluctuation", 1.},
    {"cross-section", 6.4},
    {"nucleon-width", 0.5}
  });

  // create a test event
  Event event{var_map};
  NucleonProfile profile{var_map};

  auto nucleusA = Nucleus::create("Pb");
  auto nucleusB = Nucleus::create("Pb");

  auto b = 4.*std::sqrt(random::canonical<>());
  nucleusA->sample_nucleons(+.5*b);
  nucleusB->sample_nucleons(-.5*b);

  for (auto&& A : *nucleusA)
    for (auto&& B : *nucleusB)
      profile.participate(A, B);

  event.compute(*nucleusA, *nucleusB, profile);

  SECTION( "no output" ) {
    capture_stdout capture;
    Output output{make_var_map({{"quiet", true}, {"number-events", 1}})};
    output(1, b, event);
    CHECK( capture.stream.str().empty() );  // stdout should be empty
  }

  SECTION( "stdout only" ) {
    // write event 0 to stdout for the given number of events
    auto first_line = [&b, &event](int nev) {
      capture_stdout capture;
      Output output{make_var_map({{"quiet", false}, {"number-events", nev}})};
      output(0, b, event);
      std::string line;
      std::getline(capture.stream, line);
      return line;
    };

    // verify event number padding
    CHECK( first_line(1).substr(0, 1) == "0" );
    CHECK( first_line(10).substr(0, 1) == "0" );
    CHECK( first_line(11).substr(0, 2) == " 0" );
    CHECK( first_line(100).substr(0, 2) == " 0" );
    CHECK( first_line(101).substr(0, 3) == "  0" );

    // output lines for different total event numbers should be identical except
    // for the padding
    CHECK( first_line(1).substr(1) == first_line(1000).substr(3) );

    // read an output line back into separate objects
    int num, npart;
    double impact, mult, e2, e3, e4, e5;
    char end;
    {
      capture_stdout capture;
      Output output{make_var_map({{"quiet", false}, {"number-events", 1}})};
      output(0, b, event);
      capture.stream >> num >> impact >> npart >> mult >> e2 >> e3 >> e4 >> e5 >> std::ws;
      end = capture.stream.get();
    }

    // verify output data is correct
    CHECK( num == 0 );
    CHECK( impact == Approx(b) );
    CHECK( npart == event.npart() );
    CHECK( mult == Approx(event.multiplicity()) );
    CHECK( e2 == Approx(event.eccentricity().at(2)) );
    CHECK( e3 == Approx(event.eccentricity().at(3)) );
    CHECK( e4 == Approx(event.eccentricity().at(4)) );
    CHECK( e5 == Approx(event.eccentricity().at(5)) );

    // verify the end character is really the end of file
    CHECK( end == std::char_traits<char>::eof() );
  }

  SECTION( "text and stdout" ) {
    // configure for writing text files to a random temporary path
    temporary_path temp{};
    auto output_var_map = make_var_map({
      {"quiet", false},
      {"no-header", false},
      {"number-events", 50},
      {"output", temp.path}
    });
    Output output{output_var_map};

    // output directory should have been created
    CHECK( fs::exists(temp.path) );

    {
      // output two events and verify two lines were printed to stdout
      capture_stdout capture;
      output(3, b, event);
      output(27, b, event);
      int n = 0;
      std::string line;
      while (std::getline(capture.stream, line)) { ++n; }
      CHECK( n == 2 );
    }

    // verify event files were created
    CHECK( fs::exists(temp.path/"03.dat") );
    CHECK( fs::exists(temp.path/"27.dat") );

    {
      // read event file back in
      fs::ifstream ifs{temp.path/"03.dat"};
      std::string line;

      // check header
      std::getline(ifs, line);
      CHECK( line == "# event 3" );

      std::getline(ifs, line);
      CHECK( line.substr(0, 10) == "# b     = " );
      CHECK( std::stod(line.substr(10)) == Approx(b) );

      std::getline(ifs, line);
      CHECK( line.substr(0, 10) == "# npart = " );
      CHECK( std::stoi(line.substr(10)) == event.npart() );

      std::getline(ifs, line);
      CHECK( line.substr(0, 10) == "# mult  = " );
      CHECK( std::stod(line.substr(10)) == Approx(event.multiplicity()) );

      for (const auto& ecc : event.eccentricity()) {
        std::getline(ifs, line);
        CHECK( line.substr(0, 10) == ("# e" + std::to_string(ecc.first) + "    = ") );
        CHECK( std::stod(line.substr(10)) == Approx(ecc.second) );
      }

      // read the grid back in and check each element
      const auto* iter = event.reduced_thickness_grid().origin();
      double check;
      bool all_correct = false;
      while (ifs >> check)
        all_correct = (check == Approx(*(iter++))) || all_correct;
      CHECK( all_correct );

      // verify that all grid elements were checked
      const auto* grid_end = event.reduced_thickness_grid().origin() +
                             event.reduced_thickness_grid().num_elements();
      CHECK( iter == grid_end );
    }

    {
      // check event number header in second file
      fs::ifstream ifs{temp.path/"27.dat"};
      std::string line;
      std::getline(ifs, line);
      CHECK( line == "# event 27" );
    }

    // attempting to output to the same directory again should throw an error
    CHECK_THROWS_AS( Output{output_var_map}, std::runtime_error );
  }

#ifdef TRENTO_HDF5
  SECTION( "hdf5 only" ) {
    auto nev = 10;

    // configure for writing a random hdf5 file
    temporary_path temp{".hdf5"};

    auto output_var_map = make_var_map({
      {"quiet", true},
      {"number-events", nev},
      {"output", temp.path}
    });
    Output output{output_var_map};

    for (auto n = 0; n < nev; ++n)
      output(n, b, event);

    {
      H5::H5File file{temp.path.string(), H5F_ACC_RDONLY};
      CHECK( static_cast<int>(file.getNumObjs()) == nev );

      auto name = file.getObjnameByIdx(0);
      CHECK( name == "event_0" );

      auto dataset = file.openDataSet(name);

      // read back in the event grid to another array
      Event::Grid grid_check{event.reduced_thickness_grid()};
      dataset.read(grid_check.data(), H5::PredType::NATIVE_DOUBLE);

      // verify each grid element
      auto grid_correct = std::equal(
        grid_check.origin(),
        grid_check.origin() + grid_check.num_elements(),
        event.reduced_thickness_grid().origin(),
        [](const double& value_check, const double& value) {
          return value_check == Approx(value);
        }
      );
      CHECK( grid_correct );

      // verify attributes
      double double_check;
      int int_check;

      dataset.openAttribute("b").read(H5::PredType::NATIVE_DOUBLE, &double_check);
      CHECK( double_check == Approx(b) );

      dataset.openAttribute("npart").read(H5::PredType::NATIVE_INT, &int_check);
      CHECK( int_check == event.npart() );

      dataset.openAttribute("mult").read(H5::PredType::NATIVE_DOUBLE, &double_check);
      CHECK( double_check == Approx(event.multiplicity()) );

      for (const auto& ecc : event.eccentricity()) {
        dataset.openAttribute("e" + std::to_string(ecc.first))
          .read(H5::PredType::NATIVE_DOUBLE, &double_check);
        CHECK( double_check == Approx(ecc.second) );
      }

#if H5_VERSION_GE(1, 8, 14)  // causes memory leak on earlier versions
      CHECK( dataset.getNumAttrs() == 7 );
#endif
    }

    // attempting to output to the same file again should throw an error
    CHECK_THROWS_AS( Output{output_var_map}, std::runtime_error );

    // create another empty temporary file
    temporary_path temp2{".hdf5"};
    {
      fs::ofstream{temp2.path};
    }

    // should be able to write to an existing but empty file
    Output{
      make_var_map({
        {"quiet", true},
        {"number-events", 1},
        {"output", temp2.path}
      })
    }(0, b, event);

    CHECK( fs::file_size(temp2.path) > 0);
  }
#endif  // TRENTO_HDF5
}


/*
 * Copyright: 2018 Gerard Choinka (gerard.choinka@gmail.com)
 *
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or
 * copy at http://www.boost.org/LICENSE_1_0.txt)
 */
#pragma once

#pragma once
#include <fmt/format.h>
#include <boost/type_index.hpp>
#include <boost/variant.hpp>
#include <string>
#include <vector>

#include "../common/nodes.hpp"

using namespace fmt::literals;

class AsTupletNotation : public boost::static_visitor<std::string> {
 public:
  std::string operator()(ant::IfFoodAhead const& node) const {
    return fmt::format(
        R"""(
  hana::tuple<tag::IfFood, {true_branch}, {false_branch}>
)""",
        "true_branch"_a = boost::apply_visitor(*this, node.get(true)),
        "false_branch"_a = boost::apply_visitor(*this, node.get(false)));
  }

  std::string operator()(ant::Prog2 const& node) const {
    return fmt::format(
        R"""(
  hana::tuple<tag::Prog2, {branch0}, {branch1}>
)""",
        "branch0"_a = boost::apply_visitor(*this, node.children[0]),
        "branch1"_a = boost::apply_visitor(*this, node.children[1]));
  }

  std::string operator()(ant::Prog3 const& node) const {
    return fmt::format(
        R"""(
  hana::tuple<tag::Prog3, {branch0}, {branch1}, {branch2}>
)""",
        "branch0"_a = boost::apply_visitor(*this, node.children[0]),
        "branch1"_a = boost::apply_visitor(*this, node.children[1]),
        "branch2"_a = boost::apply_visitor(*this, node.children[2]));
  }

  std::string operator()(ant::Move const&) const {
    return "hana::tuple<tag::Move>";
  }
  std::string operator()(ant::Left const&) const {
    return "hana::tuple<tag::Left>";
  }
  std::string operator()(ant::Right const&) const {
    return "hana::tuple<tag::Right>";
  }
};

struct TupleCTStatic {
  std::string fileName() const { return __FILE__; }
  std::vector<std::string> includes() const { return {"nodes_hana_tuple.hpp"}; }
  std::string name() const { return "tupleCTStatic"; }
  std::string functionName() const { return "tupleCTStatic"; }
  std::string body(ant::NodesVariant ant) const {
    return fmt::format(
        R"""(
template<typename AntBoardSimT, typename CursorType>
static int tupleCTStatic(AntBoardSimT antBoardSim, CursorType, BenchmarkPart toMessure)
{{
  using namespace tup;
  constexpr auto anAnt = {tupleNotation}{{}};
  if(toMessure == BenchmarkPart::Create) {{
    benchmark::DoNotOptimize(anAnt);
    return 0;
  }}
  while(!antBoardSim.is_finish())
  {{
    tup::eval(anAnt, antBoardSim);
  }}
  benchmark::DoNotOptimize(antBoardSim.score());
  return antBoardSim.score();
}}
)""",
        "tupleNotation"_a = boost::apply_visitor(AsTupletNotation{}, ant)

    );
  }
};


#include <boost/date_time/local_time/local_time.hpp>
#include <iostream>

using namespace boost::posix_time;
using namespace boost::gregorian;

int main()
{
  ptime pt{date{2014, 5, 12}, time_duration{12, 0, 0}};
  time_iterator it{pt, time_duration{6, 30, 0}};
  std::cout << *++it << '\n';
  std::cout << *++it << '\n';
}