//---------------------------------------------------------------------------//
//!
//! \file   Utility_InterpolationPolicy_def.hpp
//! \author Alex Robinson
//! \brief  Intrepolation policy struct definitions
//!
//---------------------------------------------------------------------------//

#ifndef UTILITY_INTERPOLATION_POLICY_DEF_HPP
#define UTILITY_INTERPOLATION_POLICY_DEF_HPP

// Std Lib Includes
#include <cmath>

// Boost Includes
#include <boost/mpl/or.hpp>

// Trilinos Includes
#include <Teuchos_ScalarTraits.hpp>

// FRENSIE Includes
#include "Utility_ContractException.hpp"

namespace Utility{

// Interpolate between two points
template<typename IndepType, typename DepType>
inline DepType LogLog::interpolate( const IndepType indep_var_0,
				    const IndepType indep_var_1,
				    const IndepType indep_var,
				    const DepType dep_var_0,
				    const DepType dep_var_1 )
{
  // The IndepType must be a floating point type
  testStaticPrecondition( (QuantityTraits<IndepType>::is_floating_point::value) );
  testStaticPrecondition( (QuantityTraits<DepType>::is_floating_point::value) );
  // Make sure the independent variables are valid
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_0 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_1 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var ) );
  testPrecondition( LogLog::isIndepVarInValidRange( indep_var_0 ) );
  testPrecondition( LogLog::isIndepVarInValidRange( indep_var_1 ) );
  testPrecondition( LogLog::isIndepVarInValidRange( indep_var ) );
  testPrecondition( indep_var_0 < indep_var_1 );
  testPrecondition( indep_var >= indep_var_0 );
  testPrecondition( indep_var <= indep_var_1 );
  // Make sure the dependent variables are valid
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_0 ) );
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_1 ) );
  testPrecondition( LogLog::isDepVarInValidRange( dep_var_0 ) );
  testPrecondition( LogLog::isDepVarInValidRange( dep_var_1 ) );

  return dep_var_0*
    pow((dep_var_1/dep_var_0),
	log(indep_var/indep_var_0)/log(indep_var_1/indep_var_0));
}

// Interpolate between two processed point
template<typename T>
inline T LogLog::interpolate( const T processed_indep_var_0,
			      const T processed_indep_var,
			      const T processed_dep_var_0,
			      const T processed_slope )
{
  // T must be a floating point type
  testStaticPrecondition( (boost::is_floating_point<T>::value) );
  // Make sure the processed independent variables are valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						     processed_indep_var_0 ) );
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_indep_var ) );
  testPrecondition( processed_indep_var_0 <= processed_indep_var );
  // Make sure the processed dependent variable is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_dep_var_0 ) );
  // Make sure that the slope is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf( processed_slope ) );

  return exp( processed_dep_var_0 +
	      processed_slope*(processed_indep_var - processed_indep_var_0) );
}

// Interpolate between two points and return the processed value
template<typename IndepType, typename DepType>
inline typename QuantityTraits<DepType>::RawType
LogLog::interpolateAndProcess( const IndepType indep_var_0,
			       const IndepType indep_var_1,
			       const IndepType indep_var,
			       const DepType dep_var_0,
			       const DepType dep_var_1 )
{
  // T must be a floating point type
  testStaticPrecondition( (QuantityTraits<IndepType>::is_floating_point::value) );
  testStaticPrecondition( (QuantityTraits<DepType>::is_floating_point::value) );
  // Make sure the independent variables are valid
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_0 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_1 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var ) );
  testPrecondition( LogLog::isIndepVarInValidRange( indep_var_0 ) );
  testPrecondition( LogLog::isIndepVarInValidRange( indep_var_1 ) );
  testPrecondition( LogLog::isIndepVarInValidRange( indep_var ) );
  testPrecondition( indep_var_0 < indep_var_1 );
  testPrecondition( indep_var >= indep_var_0 );
  testPrecondition( indep_var <= indep_var_1 );
  // Make sure the dependent variables are valid
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_0 ) );
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_1 ) );
  testPrecondition( LogLog::isDepVarInValidRange( dep_var_0 ) );
  testPrecondition( LogLog::isDepVarInValidRange( dep_var_1 ) );

  return log( getRawQuantity(dep_var_0) ) + log ( dep_var_1/dep_var_0 )*
    log( indep_var/indep_var_0 )/log( indep_var_1/indep_var_0 );
}

// Interpolate between two processed points and return the processed value
template<typename T>
inline T LogLog::interpolateAndProcess( const T processed_indep_var_0,
					const T processed_indep_var,
					const T processed_dep_var_0,
					const T processed_slope )
{
  // T must be a floating point type
  testStaticPrecondition( (boost::is_floating_point<T>::value) );
  // Make sure the processed independent variables are valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						     processed_indep_var_0 ) );
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_indep_var ) );
  testPrecondition( processed_indep_var_0 <= processed_indep_var );
  // Make sure the processed dependent variable is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_dep_var_0 ) );
  // Make sure that the slope is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf( processed_slope ) );

  return processed_dep_var_0 +
    processed_slope*(processed_indep_var - processed_indep_var_0);
}

// Process the independent value
template<typename T>
inline typename QuantityTraits<T>::RawType
LogLog::processIndepVar( const T indep_var )
{
  // Make sure the indep var value is valid
  testPrecondition( LogLog::isIndepVarInValidRange( indep_var ) );

  return log( getRawQuantity(indep_var) );
}

// Process the dependent value
template<typename T>
inline typename QuantityTraits<T>::RawType
LogLog::processDepVar( const T dep_var )
{
  // Make sure the indep var value is valid
  testPrecondition( LogLog::isIndepVarInValidRange( dep_var ) );

  return log( getRawQuantity(dep_var) );
}

// Recover the processed independent value
template<typename T>
inline T LogLog::recoverProcessedIndepVar( const T processed_indep_var )
{
  return exp( processed_indep_var );
}

// Recover the processed dependent value
template<typename T>
inline T LogLog::recoverProcessedDepVar( const T processed_dep_var )
{
  return exp( processed_dep_var );
}

// Test if the independent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LogLog::isIndepVarInValidRange( const T indep_var )
{
  // Make sure the indep var is not inf or nan
  testPrecondition( !QuantityTraits<T>::isnaninf( indep_var ) );

  return indep_var > QuantityTraits<T>::zero();
}

// Test if the dependent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LogLog::isDepVarInValidRange( const T dep_var )
{
  // Make sure the indep var is not inf or nan
  testPrecondition( !QuantityTraits<T>::isnaninf( dep_var ) );

  return dep_var > QuantityTraits<T>::zero();
}

// The name of the policy
inline const std::string LogLog::name()
{
  return "LogLog";
}

// Interpolate between two points
template<typename IndepType, typename DepType>
inline DepType LogLin::interpolate( const IndepType indep_var_0,
				    const IndepType indep_var_1,
				    const IndepType indep_var,
				    const DepType dep_var_0,
				    const DepType dep_var_1 )
{
  // T must be a floating point type
  testStaticPrecondition( (QuantityTraits<IndepType>::is_floating_point::value) );
  testStaticPrecondition( (QuantityTraits<DepType>::is_floating_point::value) );
  // Make sure the independent variables are valid
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_0 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_1 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var ) );
  testPrecondition( LogLin::isIndepVarInValidRange( indep_var_0 ) );
  testPrecondition( LogLin::isIndepVarInValidRange( indep_var_1 ) );
  testPrecondition( LogLin::isIndepVarInValidRange( indep_var ) );
  testPrecondition( indep_var_0 < indep_var_1 );
  testPrecondition( indep_var >= indep_var_0 );
  testPrecondition( indep_var <= indep_var_1 );
  // Make sure the dependent variables are valid
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_0 ) );
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_1 ) );
  testPrecondition( LogLin::isDepVarInValidRange( dep_var_0 ) );
  testPrecondition( LogLin::isDepVarInValidRange( dep_var_1 ) );

  return dep_var_0*pow((dep_var_1/dep_var_0), (indep_var-indep_var_0)/(indep_var_1-indep_var_0));
}

// Interpolate between two processed points
template<typename T>
inline T LogLin::interpolate( const T processed_indep_var_0,
			      const T processed_indep_var,
			      const T processed_dep_var_0,
			      const T processed_slope )
{
  // T must be a floating point type
  testStaticPrecondition( (boost::is_floating_point<T>::value) );
  // Make sure the processed independent variables are valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						     processed_indep_var_0 ) );
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_indep_var ) );
  testPrecondition( processed_indep_var_0 <= processed_indep_var );
  // Make sure the processed dependent variable is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_dep_var_0 ) );
  // Make sure that the slope is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf( processed_slope ) );

  return exp( processed_dep_var_0 +
	      processed_slope*(processed_indep_var - processed_indep_var_0) );
}

// Interpolate between two points and return the processed value
template<typename IndepType, typename DepType>
inline typename QuantityTraits<DepType>::RawType
LogLin::interpolateAndProcess( const IndepType indep_var_0,
			       const IndepType indep_var_1,
			       const IndepType indep_var,
			       const DepType dep_var_0,
			       const DepType dep_var_1 )
{
  // T must be a floating point type
  testStaticPrecondition( (QuantityTraits<IndepType>::is_floating_point::value) );
  testStaticPrecondition( (QuantityTraits<DepType>::is_floating_point::value) );
  // Make sure the independent variables are valid
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_0 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_1 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var ) );
  testPrecondition( LogLin::isIndepVarInValidRange( indep_var_0 ) );
  testPrecondition( LogLin::isIndepVarInValidRange( indep_var_1 ) );
  testPrecondition( LogLin::isIndepVarInValidRange( indep_var ) );
  testPrecondition( indep_var_0 < indep_var_1 );
  testPrecondition( indep_var >= indep_var_0 );
  testPrecondition( indep_var <= indep_var_1 );
  // Make sure the dependent variables are valid
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_0 ) );
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_1 ) );
  testPrecondition( LogLin::isDepVarInValidRange( dep_var_0 ) );
  testPrecondition( LogLin::isDepVarInValidRange( dep_var_1 ) );

  return log( getRawQuantity(dep_var_0) ) + log( dep_var_1/dep_var_0 )*
    (indep_var-indep_var_0)/(indep_var_1-indep_var_0);
}

// Interpolate between two processed points and return the processed value
template<typename T>
inline T LogLin::interpolateAndProcess( const T processed_indep_var_0,
					const T processed_indep_var,
					const T processed_dep_var_0,
					const T processed_slope )
{
  // T must be a floating point type
  testStaticPrecondition( (boost::is_floating_point<T>::value) );
  // Make sure the processed independent variables are valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						     processed_indep_var_0 ) );
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_indep_var ) );
  testPrecondition( processed_indep_var_0 <= processed_indep_var );
  // Make sure the processed dependent variable is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_dep_var_0 ) );
  // Make sure that the slope is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf( processed_slope ) );

  return processed_dep_var_0 +
    processed_slope*(processed_indep_var - processed_indep_var_0);
}

// Process the independent value
template<typename T>
inline typename QuantityTraits<T>::RawType
LogLin::processIndepVar( const T indep_var )
{
  // Make sure the indep var value is valid
  testPrecondition( LogLin::isIndepVarInValidRange( indep_var ) );

  return getRawQuantity(indep_var);
}

// Process the dependent value
template<typename T>
inline typename QuantityTraits<T>::RawType
LogLin::processDepVar( const T dep_var )
{
  // Make sure the indep var value is valid
  testPrecondition( LogLin::isDepVarInValidRange( dep_var ) );

  return log( getRawQuantity(dep_var) );
}

// Recover the processed independent value
template<typename T>
inline T LogLin::recoverProcessedIndepVar( const T processed_indep_var )
{
  return processed_indep_var;
}

// Recover the processed dependent value
template<typename T>
inline T LogLin::recoverProcessedDepVar( const T processed_dep_var )
{
  return exp( processed_dep_var );
}

// Test if the independent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LogLin::isIndepVarInValidRange( const T indep_var )
{
  // Make sure the indep var is not inf or nan
  testPrecondition( !QuantityTraits<T>::isnaninf( indep_var ) );

  return true;
}

// Test if the dependent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LogLin::isDepVarInValidRange( const T dep_var )
{
  // Make sure the indep var is not inf or nan
  testPrecondition( !QuantityTraits<T>::isnaninf( dep_var ) );

  return dep_var > QuantityTraits<T>::zero();
}

// The name of the policy
inline const std::string LogLin::name()
{
  return "LogLin";
}

// Interpolate between two points
template<typename IndepType, typename DepType>
inline DepType LinLog::interpolate( const IndepType indep_var_0,
				    const IndepType indep_var_1,
				    const IndepType indep_var,
				    const DepType dep_var_0,
				    const DepType dep_var_1 )
{
  // T must be a floating point type
  testStaticPrecondition( (QuantityTraits<IndepType>::is_floating_point::value) );
  testStaticPrecondition( (QuantityTraits<DepType>::is_floating_point::value) );
  // Make sure the independent variables are valid
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_0 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_1 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var ) );
  testPrecondition( LinLog::isIndepVarInValidRange( indep_var_0 ) );
  testPrecondition( LinLog::isIndepVarInValidRange( indep_var_1 ) );
  testPrecondition( LinLog::isIndepVarInValidRange( indep_var ) );
  testPrecondition( indep_var_0 < indep_var_1 );
  testPrecondition( indep_var >= indep_var_0 );
  testPrecondition( indep_var <= indep_var_1 );
  // Make sure the dependent variables are valid
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_0 ) );
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_1 ) );
  testPrecondition( LinLog::isDepVarInValidRange( dep_var_0 ) );
  testPrecondition( LinLog::isDepVarInValidRange( dep_var_1 ) );

  DepType term_2( (dep_var_1 - dep_var_0)*log(indep_var/indep_var_0)/
		  log(indep_var_1/indep_var_0) );

  return dep_var_0 + term_2;
}

// Interpolate between two processed point
template<typename T>
inline T LinLog::interpolate( const T processed_indep_var_0,
			      const T processed_indep_var,
			      const T processed_dep_var_0,
			      const T processed_slope )
{
  // T must be a floating point type
  testStaticPrecondition( (boost::is_floating_point<T>::value) );
  // Make sure the processed independent variables are valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						     processed_indep_var_0 ) );
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_indep_var ) );
  testPrecondition( processed_indep_var_0 <= processed_indep_var );
  // Make sure the processed dependent variable is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_dep_var_0 ) );
  // Make sure that the slope is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf( processed_slope ) );

  return processed_dep_var_0 +
    processed_slope*(processed_indep_var - processed_indep_var_0 );
}

// Interpolate between two points and return the processed value
template<typename IndepType, typename DepType>
inline typename QuantityTraits<DepType>::RawType
LinLog::interpolateAndProcess( const IndepType indep_var_0,
			       const IndepType indep_var_1,
			       const IndepType indep_var,
			       const DepType dep_var_0,
			       const DepType dep_var_1 )
{
  return getRawQuantity( interpolate( indep_var_0,
				      indep_var_1,
				      indep_var,
				      dep_var_0,
				      dep_var_1 ) );
}

// Interpolate between two processed points and return the processed value
template<typename T>
inline T LinLog::interpolateAndProcess( const T processed_indep_var_0,
					const T processed_indep_var,
					const T processed_dep_var_0,
					const T processed_slope )
{
  return interpolate( processed_indep_var_0,
		      processed_indep_var,
		      processed_dep_var_0,
		      processed_slope );
}

// Process the independent value
template<typename T>
inline typename QuantityTraits<T>::RawType
LinLog::processIndepVar( const T indep_var )
{
  // Make sure the indep var value is valid
  testPrecondition( LinLog::isIndepVarInValidRange( indep_var ) );

  return log( getRawQuantity(indep_var) );
}

// Process the dependent value
template<typename T>
inline typename QuantityTraits<T>::RawType
LinLog::processDepVar( const T dep_var )
{
  // Make sure the indep var value is valid
  testPrecondition( LinLog::isDepVarInValidRange( dep_var ) );

  return getRawQuantity(dep_var);
}

// Recover the processed independent value
template<typename T>
inline T LinLog::recoverProcessedIndepVar( const T processed_indep_var )
{
  return exp( processed_indep_var );
}

// Recover the processed dependent value
template<typename T>
inline T LinLog::recoverProcessedDepVar( const T processed_dep_var )
{
  return processed_dep_var;
}

// Test if the independent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LinLog::isIndepVarInValidRange( const T indep_var )
{
  // Make sure the indep var is not inf or nan
  testPrecondition( !QuantityTraits<T>::isnaninf( indep_var ) );

  return indep_var > QuantityTraits<T>::zero();
}

// Test if the dependent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LinLog::isDepVarInValidRange( const T dep_var )
{
  // Make sure the indep var is not inf or nan
  testPrecondition( !QuantityTraits<T>::isnaninf( dep_var ) );

  return true;
}

// The name of the policy
inline const std::string LinLog::name()
{
  return "LinLog";
}

// Interpolate between two points
template<typename IndepType, typename DepType>
inline DepType LinLin::interpolate( const IndepType indep_var_0,
				    const IndepType indep_var_1,
				    const IndepType indep_var,
				    const DepType dep_var_0,
				    const DepType dep_var_1 )
{
  // T must be a floating point type
  testStaticPrecondition( (QuantityTraits<IndepType>::is_floating_point::value) );
  testStaticPrecondition( (QuantityTraits<DepType>::is_floating_point::value) );
  // Make sure the independent variables are valid
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_0 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_1 ) );
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var ) );
  testPrecondition( LinLin::isIndepVarInValidRange( indep_var_0 ) );
  testPrecondition( LinLin::isIndepVarInValidRange( indep_var_1 ) );
  testPrecondition( LinLin::isIndepVarInValidRange( indep_var ) );
  testPrecondition( indep_var_0 < indep_var_1 );
  testPrecondition( indep_var >= indep_var_0 );
  testPrecondition( indep_var <= indep_var_1 );
  // Make sure the dependent variables are valid
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_0 ) );
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_1 ) );
  testPrecondition( LinLin::isDepVarInValidRange( dep_var_0 ) );
  testPrecondition( LinLin::isDepVarInValidRange( dep_var_1 ) );

  DepType term_2( (dep_var_1 - dep_var_0)/(indep_var_1 - indep_var_0)*
		  (indep_var - indep_var_0) );

  return dep_var_0 + term_2;
}

// Interpolate between two processed point
template<typename T>
inline T LinLin::interpolate( const T processed_indep_var_0,
			      const T processed_indep_var,
			      const T processed_dep_var_0,
			      const T processed_slope )
{
  // T must be a floating point type
  testStaticPrecondition( (boost::is_floating_point<T>::value) );
  // Make sure the processed independent variables are valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						     processed_indep_var_0 ) );
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_indep_var ) );
  testPrecondition( processed_indep_var_0 <= processed_indep_var );
  // Make sure the processed dependent variable is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(
						       processed_dep_var_0 ) );
  // Make sure the slope is valid
  testPrecondition( !Teuchos::ScalarTraits<T>::isnaninf(processed_slope));

  return processed_dep_var_0 +
    processed_slope*(processed_indep_var - processed_indep_var_0 );
}

// Interpolate between two points and return the processed value
template<typename IndepType, typename DepType>
inline typename QuantityTraits<DepType>::RawType
LinLin::interpolateAndProcess( const IndepType indep_var_0,
			       const IndepType indep_var_1,
			       const IndepType indep_var,
			       const DepType dep_var_0,
			       const DepType dep_var_1 )
{
  return getRawQuantity( interpolate( indep_var_0,
				      indep_var_1,
				      indep_var,
				      dep_var_0,
				      dep_var_1 ) );
}

// Interpolate between two processed points and return the processed value
template<typename T>
inline T LinLin::interpolateAndProcess( const T processed_indep_var_0,
					const T processed_indep_var,
					const T processed_dep_var_0,
					const T processed_slope )
{
  return interpolate( processed_indep_var_0,
		      processed_indep_var,
		      processed_dep_var_0,
		      processed_slope );
}

// Process the independent value
template<typename T>
inline typename QuantityTraits<T>::RawType
LinLin::processIndepVar( const T indep_var )
{
  // Make sure the indep var value is valid
  testPrecondition( LinLin::isIndepVarInValidRange( indep_var  ) );

  return getRawQuantity(indep_var);
}

// Process the dependent value
template<typename T>
inline typename QuantityTraits<T>::RawType
LinLin::processDepVar( const T dep_var )
{
  // Make sure the indep var value is valid
  testPrecondition( LinLin::isIndepVarInValidRange( dep_var ) );

  return getRawQuantity(dep_var);
}

// Recover the processed independent value
template<typename T>
inline T LinLin::recoverProcessedIndepVar( const T processed_indep_var )
{
  return processed_indep_var;
}

// Recover the processed dependent value
template<typename T>
inline T LinLin::recoverProcessedDepVar( const T processed_dep_var )
{
  return processed_dep_var;
}

// Test if the independent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LinLin::isIndepVarInValidRange( const T indep_var )
{
  // Make sure the indep var is not inf or nan
  testPrecondition( !QuantityTraits<T>::isnaninf( indep_var ) );

  return true;
}

// Test if the dependent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LinLin::isDepVarInValidRange( const T dep_var )
{
  // Make sure the indep var is not inf or nan
  testPrecondition( !QuantityTraits<T>::isnaninf( dep_var ) );

  return true;
}

// The name of the policy
inline const std::string LinLin::name()
{
  return "LinLin";
}

} // end Utility namespace

#endif // end UTILITY_INTERPOLATION_POLICY_DEF_HPP

//---------------------------------------------------------------------------//
// end Utility_InterpolationPolicy_def.hpp
//---------------------------------------------------------------------------//


////////////////////////////////////////////////////////////////////////////////
// CorotatedLinearElasticity.hh
////////////////////////////////////////////////////////////////////////////////
/*! @file
//  A general linear elastic material model with geometric nonlinearities for
//  small strain, large deformation applications.
//
//  We use a (right) polar decomposition F = RS to remove the rigid rotation
//  component R from the deformation gradient F. The resulting large deformation
//  strain is S - I, also known as the Biot strain.
*/
//  Author:  Julian Panetta (jpanetta), julian.panetta@gmail.com
//  Created:  05/19/2020 12:15:32
////////////////////////////////////////////////////////////////////////////////
#ifndef COROTATEDLINEARELASTICITY_HH
#define COROTATEDLINEARELASTICITY_HH

#include <Eigen/Dense>
#include "EnergyTraits.hh"
#include "Tensor.hh"

// Dimension-specific calculations
template<typename _Real, size_t N>
struct CRQuantities;

template<typename _Real>
struct CRQuantities<_Real, 3> {
    using GType    = Eigen::Matrix<_Real, 3, 3>;
    using IRotType = Eigen::Matrix<_Real, 3, 1>; // infinitesimal rotation representation
    using Mat      = Eigen::Matrix<_Real, 3, 3>;

    template<typename Derived>
    static GType getG(const Eigen::MatrixBase<Derived> &S) { return S.trace() * GType::Identity() - S; }

    template<typename Derived>
    static GType getGinv(const Eigen::MatrixBase<Derived> &G) { return G.inverse(); }

    // Extract a vector representing the skew symmetric part 0.5 (A - A^T)
    //     0 -c  b      [a]
    //     c  0 -a  ==> [b]
    //    -b  a  0      [c]
    // (This is the vector `w` whose cross product `w x v` equals `0.5 (A - A^T) v`.)
    template<typename Derived>
    static IRotType sk_inv(const Eigen::MatrixBase<Derived> &A) {
        return IRotType(0.5 * (A(2, 1) - A(1, 2)),
                        0.5 * (A(0, 2) - A(2, 0)),
                        0.5 * (A(1, 0) - A(0, 1)));
    }

    // B * sk(w)
    template<typename Derived>
    static Mat right_mul_sk(const Eigen::MatrixBase<Derived> &B, const IRotType &w) {
        return B.rowwise().cross(w);
    }
};

template<typename _Real>
struct CRQuantities<_Real, 2> {
    using GType    = _Real;
    using IRotType = _Real;
    using Mat      = Eigen::Matrix<_Real, 2, 2>;

    template<typename Derived>
    static GType getG(const Eigen::MatrixBase<Derived> &S) { return S.trace(); }

    static GType getGinv(_Real G) { return 1.0 / G; }

    // Extract a scalar representing the skew symmetric part 0.5 (A - A^T)
    //   0 -a
    //   a  0
    // (This scalar represents the counterclockwise infinitesimal rotation
    //  applied by `0.5 (A - A^T)`
    template<typename Derived>
    static IRotType sk_inv(const Eigen::MatrixBase<Derived> &A) {
        return 0.5 * (A(1, 0) - A(0, 1));
    }

    // B * sk(w)
    template<typename Derived>
    static Mat right_mul_sk(const Eigen::MatrixBase<Derived> &B, const IRotType &w) {
        Mat result;
        result << w * B.col(1),
                 -w * B.col(0);
        return result;
    }
};

template <typename _Real, size_t _Dimension>
struct CorotatedLinearElasticity : public Concepts::CRLinearElaticEnergy {
    using SMatrix = SymmetricMatrixValue<_Real, _Dimension>;
    static constexpr EDensityType EDType = EDensityType::FBased;

    static constexpr size_t Dimension = _Dimension;
    static constexpr size_t N         = Dimension;
    using Real = _Real;
    using Matrix  = Eigen::Matrix<_Real, _Dimension, _Dimension>;
    using ETensor = ElasticityTensor<_Real, _Dimension>;
    using CRQ     = CRQuantities<_Real, _Dimension>;

    // We can use simplified formulas if we know `elasticity_tensor` is isotropic. This is
    // specified with the `isotropic` argument.
    CorotatedLinearElasticity(const ETensor& elasticity_tensor, bool isotropic = false) :
        m_elasticity_tensor(elasticity_tensor), m_isotropic(isotropic) {
        setDeformationGradient(Matrix::Identity());
    }

    // Constructor copying material properties only, not the current deformation
    CorotatedLinearElasticity(const CorotatedLinearElasticity &other, UninitializedDeformationTag &&)
        : m_elasticity_tensor(other.m_elasticity_tensor), m_isotropic(other.m_isotropic) { }

    void setDeformationGradient(const Matrix &F, const EvalLevel elevel = EvalLevel::Full) {
        m_F = F;
        Eigen::JacobiSVD<Matrix> svd;
        svd.compute(F, Eigen::ComputeFullU | Eigen::ComputeFullV );
        m_R = svd.matrixU() * svd.matrixV().transpose();
        if (m_R.determinant() < 0) {
            Matrix W = svd.matrixV();
            W.col(svd.matrixV().cols() - 1) *= -1;
            m_R = svd.matrixU() * W.transpose();
        }
        m_S = m_R.transpose() * F;

        // Analog to infinitesimal strain for linear elasticity.
        m_biotStrain = m_S - Matrix::Identity();

        // Analog to Cauchy stress for linear elasticity.
        m_biotStress = m_elasticity_tensor.doubleContract(SMatrix(m_biotStrain)).toMatrix();

        if (elevel == EvalLevel::EnergyOnly) return;

        m_G    = CRQ::getG(m_S);
        m_Ginv = CRQ::getGinv(m_G);
        if (!m_isotropic) {
            // 2 sk(G^{-1} sk^{-1}(sigma * S)) := sk(g)
            m_g = 2 * m_Ginv * CRQ::sk_inv(m_biotStress * m_S);
            m_pk1_stress = m_R * m_biotStress - CRQ::right_mul_sk(m_R, m_g);
        }
        else {
            // If the elasticity tensor is isotropic, then sigma and S commute
            // (Biot stress and stretch factor share eigenvectors) and all
            // "rotational stress" terms involving sk(g) vanish.
            m_pk1_stress = m_R * m_biotStress;
        }
    }

    const Matrix &getDeformationGradient() const { return m_F; }

    _Real energy() const {
        return 0.5 * doubleContract(m_biotStress, m_biotStrain);
    }

    // PK1 stress
    _Real denergy(const Matrix& dF) const { return doubleContract(denergy(), dF); }

    // Asymmetric!
    Matrix denergy() const {
        return m_pk1_stress;
    }

    // Symmetric!
    Matrix PK2Stress() const { return m_F.inverse() * denergy(); }

    const Matrix &R() const { return m_R; }
    const Matrix &S() const { return m_S; }
    const Matrix &biotStress() const { return m_biotStress; }

    template<class Mat_>
    Matrix delta_R(const Mat_ &dF) const {
        typename CRQ::IRotType w = 2 * m_Ginv * CRQ::sk_inv(m_R.transpose() * dF);
        return CRQ::right_mul_sk(m_R, w);
    }

    template<class Mat_>
    Matrix delta_S(const Mat_ &dF, const Matrix &dR) const {
        return dR.transpose() * m_F + m_R.transpose() * dF;
    }

    Matrix delta_sigma(const Matrix &dS) const {
        return m_elasticity_tensor.doubleContract(SMatrix(dS)).toMatrix();
    }

    template<class Mat_>
    Matrix delta_denergy(const Mat_ &dF) const {
        Matrix dR     = delta_R(dF);
        Matrix dS     = delta_S(dF, dR);
        Matrix dsigma = delta_sigma(dS);

        Matrix result = dR * m_biotStress + m_R * dsigma;

        if (!m_isotropic) {
            typename CRQ::IRotType dg = m_Ginv * (2 * CRQ::sk_inv(dsigma * m_S + m_biotStress * dS) - CRQ::getG(dS) * m_g);
            result -= CRQ::right_mul_sk(dR, m_g) + CRQ::right_mul_sk(m_R, dg);
        }
        return result;
    }

    _Real d2energy(const Matrix &dF_lhs, const Matrix &dF_rhs) const {
        return doubleContract(delta_denergy(dF_lhs), dF_rhs);
    }

    template<class Mat_, class Mat2_>
    Matrix delta2_denergy(const Mat_ &dF_a, const Mat2_ &dF_b) const {
        typename CRQ::IRotType w_a = 2 * m_Ginv * CRQ::sk_inv(m_R.transpose() * dF_a);
        Matrix dR_a = CRQ::right_mul_sk(m_R, w_a),
               dR_b = delta_R(dF_b);
        Matrix dS_a = delta_S(dF_a, dR_a),
               dS_b = delta_S(dF_b, dR_b);
        Matrix dsigma_a = delta_sigma(dS_a);
        Matrix dsigma_b = delta_sigma(dS_b);

        Matrix d2R, d2S, d2sigma;
        d2R = CRQ::right_mul_sk(dR_b, w_a)
            + CRQ::right_mul_sk(m_R, m_Ginv * (2 * CRQ::sk_inv(dR_b.transpose() * dF_a) - CRQ::getG(dS_b) * w_a));

        d2S = d2R.transpose() * m_F + dR_a.transpose() * dF_b + dR_b.transpose() * dF_a;
        d2sigma = delta_sigma(d2S);

        Matrix result = d2R * m_biotStress + dR_a * dsigma_b + dR_b * dsigma_a + m_R * d2sigma;

        if (!m_isotropic) {
            typename CRQ::IRotType dg_a = m_Ginv * (2 * CRQ::sk_inv(dsigma_a * m_S + m_biotStress * dS_a) - CRQ::getG(dS_a) * m_g),
                                   dg_b = m_Ginv * (2 * CRQ::sk_inv(dsigma_b * m_S + m_biotStress * dS_b) - CRQ::getG(dS_b) * m_g),
                                   d2g  = m_Ginv * (2 * CRQ::sk_inv(d2sigma  * m_S + dsigma_a * dS_b + dsigma_b * dS_a + m_biotStress * d2S) - CRQ::getG(d2S) * m_g - CRQ::getG(dS_a) * dg_b - CRQ::getG(dS_b) * dg_a);

            result -= CRQ::right_mul_sk(d2R, m_g) + CRQ::right_mul_sk(dR_a, dg_b) + CRQ::right_mul_sk(dR_b, dg_a)
                    + CRQ::right_mul_sk(m_R, d2g);
        }
        return result;
    }

    bool isIsotropic() const { return m_isotropic; }

private:
    ETensor m_elasticity_tensor;
    Matrix m_F,
           m_R, m_S, // Polar decomposition
           m_biotStrain, m_biotStress, m_pk1_stress,
           m_anisotropicRotationalStress; // Skew symmetric contribution to the PK1 stress; only nonzero for anisotropic material model.
    typename CRQ::GType m_G, m_Ginv;
    typename CRQ::IRotType m_g;
    bool m_isotropic;
};

#endif /* end of include guard: COROTATEDLINEARELASTICITY_HH */


//---------------------------------------------------------------------------//
//!
//! \file   DataGen_OccupationNumberEvaluator.cpp
//! \author Alex Robinson
//! \brief  The occupation number evaluator definition
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <limits>

// Boost Includes
#include <boost/function.hpp>
#include <boost/bind.hpp>

// Teuchos Includes
#include <Teuchos_ScalarTraits.hpp>

// FRENSIE Includes
#include "DataGen_OccupationNumberEvaluator.hpp"
#include "MonteCarlo_ComptonProfileHelpers.hpp"
#include "Utility_SortAlgorithms.hpp"
#include "Utility_PhysicalConstants.hpp"
#include "Utility_GaussKronrodIntegrator.hpp"
#include "Utility_ContractException.hpp"

namespace DataGen{

// Constructor
/*! \details The full electron momentum grid and Compton profile should
 * be given.
 */
OccupationNumberEvaluator::OccupationNumberEvaluator(
		   const Teuchos::Array<double>& electron_momentum_projections,
		   const Teuchos::Array<double>& compton_profile,
		   const double norm_constant_precision )
  : d_compton_profile_norm_constant( 1.0 )
{
  // Make sure the electron momentum projections are valid
  testPrecondition( electron_momentum_projections.size() > 1 );
  testPrecondition( electron_momentum_projections.front() != 0.0 );
  testPrecondition( electron_momentum_projections.front() ==
		    -electron_momentum_projections.back() );
  testPrecondition( Utility::Sort::isSortedAscending(
				       electron_momentum_projections.begin(),
				       electron_momentum_projections.end() ) );
  // Make sure the compton profile is valid
  testPrecondition( compton_profile.back() > 0.0 );
  testPrecondition( compton_profile.front() == compton_profile.back() );
  testPrecondition( compton_profile.size() ==
		    electron_momentum_projections.size() );

  // Store the profile in a tabular distribution for quick interpolation
  d_compton_profile.reset( new Utility::TabularDistribution<Utility::LogLin>(
					         electron_momentum_projections,
					         compton_profile ) );

  // Roundoff errors are common in the available Compton profile tables -
  // renormalize the table
  d_compton_profile_norm_constant = this->evaluateOccupationNumber(
			                  electron_momentum_projections.back(),
					  norm_constant_precision );
}

// Return the normalization constant used with the Compton profile
double OccupationNumberEvaluator::getComptonProfileNormConstant() const
{
  return d_compton_profile_norm_constant;
}

// Evaluate the compton profile
double OccupationNumberEvaluator::evaluateComptonProfile(
			      const double electron_momentum_projection ) const
{
  // Make sure the electron momentum projection is valid
  testPrecondition( !Teuchos::ScalarTraits<double>::isnaninf(
					      electron_momentum_projection ) );

  return d_compton_profile->evaluate( electron_momentum_projection )/
    d_compton_profile_norm_constant;
}

// Evaluate the occupation number at a given electron momentum projection
double OccupationNumberEvaluator::evaluateOccupationNumber(
				     const double electron_momentum_projection,
				     const double precision ) const
{
  // Make sure the electron momentum projection is valid
  testPrecondition( !Teuchos::ScalarTraits<double>::isnaninf(
					      electron_momentum_projection ) );

  double occupation_number;

  if( electron_momentum_projection <=
      d_compton_profile->getLowerBoundOfIndepVar() )
  {
    occupation_number = 0.0;
  }
  else if( electron_momentum_projection >
	   d_compton_profile->getLowerBoundOfIndepVar() )
  {
    boost::function<double (double pz)> compton_profile_wrapper =
      boost::bind<double>( &OccupationNumberEvaluator::evaluateComptonProfile,
			   boost::cref( *this ),
			   _1 );

    double abs_error;

    Utility::GaussKronrodIntegrator quadrature_gkq( precision );

    if( electron_momentum_projection <
	d_compton_profile->getUpperBoundOfIndepVar() )
    {
      quadrature_gkq.integrateAdaptively<15>(
				  compton_profile_wrapper,
				  d_compton_profile->getLowerBoundOfIndepVar(),
				  electron_momentum_projection,
				  occupation_number,
				  abs_error );
    }
    else
    {
      quadrature_gkq.integrateAdaptively<15>(
				  compton_profile_wrapper,
				  d_compton_profile->getLowerBoundOfIndepVar(),
				  d_compton_profile->getUpperBoundOfIndepVar(),
				  occupation_number,
				  abs_error );
    }
  }

  // Make sure the occupation number is valid
  testPostcondition( occupation_number >= 0.0 );

  return occupation_number;
}

} // end DataGen namespace

//---------------------------------------------------------------------------//
// end DataGen_OccupationNumberEvaluator.cpp
//---------------------------------------------------------------------------//


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

#ifndef NEUMANNINNERSURFACE_STOCHASTICPOLYCONVEXHGO_HPP
#define NEUMANNINNERSURFACE_STOCHASTICPOLYCONVEXHGO_HPP

#include "tensor_calculus.hpp"
#include "nearlyIncompressibleHyperelasticity.hpp"
#include "laplacepp.hpp"

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

class neumannInnerSurface_StochasticPolyconvexHGO : public nearlyIncompressibleHyperelasticity
{
public:

    laplace * Laplace;

    double w1, w2, w3, w4;
    double mean_c1, c1, deltaC1;
    double mean_c2, c2, deltaC2;
    double mean_u1, u1, deltaU1;
    double mean_mu4, mu4, deltaG4;
    double mean_mu1, mu1;
    double mean_mu2, mu2;
    double mean_mu3, mu3;
    double alpha1, alpha2;
    double alpha3, alpha4;
    double tau1, tau2;
    double alpha5, alpha6;
    double beta3, beta4;
    double theta;
    double epsilon = 1e-6;

    Epetra_IntSerialDenseVector cells_nodes_p1_med;
    Epetra_SerialDenseVector w1_gmrf, w2_gmrf, w3_gmrf, w4_gmrf;
    Epetra_SerialDenseVector a, b;
    Epetra_SerialDenseVector E1,E2,E3;
    Epetra_SerialDenseVector N;

    neumannInnerSurface_StochasticPolyconvexHGO(Epetra_Comm & comm, Teuchos::ParameterList & Parameters){

        std::string mesh_file               = Teuchos::getParameter<std::string>(Parameters.sublist("Mesh"),  "mesh_file");
        std::string boundary_file           = Teuchos::getParameter<std::string>(Parameters.sublist("Mesh"),  "boundary_file");
        unsigned int number_physical_groups = Teuchos::getParameter<unsigned int>(Parameters.sublist("Mesh"), "nb_phys_groups");
        std::string select_model            = Teuchos::getParameter<std::string>(Parameters.sublist("Mesh"),  "model");

        mean_mu1   = Teuchos::getParameter<double>(Parameters.sublist(select_model), "mu1");
        mean_mu2   = Teuchos::getParameter<double>(Parameters.sublist(select_model), "mu2");
        mean_mu3   = Teuchos::getParameter<double>(Parameters.sublist(select_model), "mu3");
        mean_mu4   = Teuchos::getParameter<double>(Parameters.sublist(select_model), "mu4");
        beta3      = Teuchos::getParameter<double>(Parameters.sublist(select_model), "beta3");
        beta4      = Teuchos::getParameter<double>(Parameters.sublist(select_model), "beta4");
        theta      = Teuchos::getParameter<double>(Parameters.sublist(select_model), "theta");
        deltaC1    = Teuchos::getParameter<double>(Parameters.sublist(select_model), "deltaC1");
        deltaC2    = Teuchos::getParameter<double>(Parameters.sublist(select_model), "deltaC2");
        deltaU1    = Teuchos::getParameter<double>(Parameters.sublist(select_model), "deltaU1");
        deltaG4    = Teuchos::getParameter<double>(Parameters.sublist(select_model), "deltaG4");

        mean_c1 = 2.0*mean_mu3*beta3*beta3;
        mean_c2 = 2.0*mean_mu1 + std::sqrt(3.0)*3.0*mean_mu2;
        mean_u1 = 2.0*mean_mu1/mean_c2;

        double gamma = 2.0*mean_mu1/(std::sqrt(3.0)*3.0*mean_mu2);
        tau2         = (1.0 - deltaU1*deltaU1)/(deltaU1*deltaU1*gamma*(gamma+1.0));
        tau1         = (2.0*mean_mu1/(std::sqrt(3.0)*3.0*mean_mu2))*tau2;

        alpha1 = 1.0/(deltaC1*deltaC1);
        alpha2 = mean_c1*deltaC1*deltaC1;
        alpha3 = 1.0/(deltaC2*deltaC2);
        alpha4 = mean_c2*deltaC2*deltaC2;
        alpha5 = 1.0/(deltaG4*deltaG4);
        alpha6 = mean_mu4*deltaG4*deltaG4;

        Mesh = new mesh(comm, Parameters); //mesh_file, 1000.0);
        Mesh->read_boundary_file(boundary_file,number_physical_groups);
        Comm = Mesh->Comm;

        Laplace = new laplace(*Mesh);

        //$$Laplace
        Epetra_FECrsMatrix matrix(Copy,*Laplace->FEGraph);
        Epetra_Vector psi(*Laplace->StandardMap);
        Epetra_Vector phi(*Laplace->StandardMap);
        Epetra_FEVector rhs(*Laplace->StandardMap);
        int bc_indx[2];
        double bc_val[2];
        bc_val[0] = 0.0; bc_val[1] = 1.0;
        //Problem number one with aztec
        bc_indx[0] = 2; bc_indx[1] = 3;
        Laplace->solve_aztec(Parameters.sublist("Laplace"), matrix, phi, rhs, &bc_indx[0], &bc_val[0]);
        Laplace->print_solution(phi, "laplace_inlet_to_outlet_aztec.mtx");
        //Problem number one with aztec
        bc_indx[0] = 0; bc_indx[1] = 1;
        Laplace->solve_aztec(Parameters.sublist("Laplace"), matrix, psi, rhs, &bc_indx[0], &bc_val[0]);
        Laplace->print_solution(psi, "laplace_inner_to_outer_aztec.mtx");
        //Get local directions
        Laplace->compute_local_directions(phi, psi);
        Laplace->compute_center_local_directions(phi, psi);
        //$$End

        StandardMap = new Epetra_Map(-1,3*Mesh->n_local_nodes_without_ghosts,&Mesh->local_dof_without_ghosts[0],0,*Comm);
        OverlapMap = new Epetra_Map(-1,3*Mesh->n_local_nodes,&Mesh->local_dof[0],0,*Comm);
        ImportToOverlapMap = new Epetra_Import(*OverlapMap,*StandardMap);
        create_FECrsGraph();

        a.Resize(3);
        b.Resize(3);
        E1.Resize(3);
        E2.Resize(3);
        E3.Resize(3);
        N.Resize(4);
        setup_dirichlet_conditions();
    }

    ~neumannInnerSurface_StochasticPolyconvexHGO(){
    }

    void get_media(unsigned int & n_cells, unsigned int & n_nodes, std::string & path){

        std::ifstream connectivity_file_med;
        connectivity_file_med.open(path);

        w1_gmrf.Resize(n_nodes);
        w2_gmrf.Resize(n_nodes);
        w3_gmrf.Resize(n_nodes);
        w4_gmrf.Resize(n_nodes);
        if (connectivity_file_med.is_open()){
            cells_nodes_p1_med.Resize(4*n_cells);
            for (unsigned int e=0; e<4*n_cells; ++e){
                connectivity_file_med >> cells_nodes_p1_med[e];
                cells_nodes_p1_med[e] = cells_nodes_p1_med[e]-1;
            }
            connectivity_file_med.close();
        }
        else{
            std::cout << "Couldn't open the connectivity file for the media.\n";
        }

    }

    void get_matrix_and_rhs(Epetra_Vector & x, Epetra_FECrsMatrix & K, Epetra_FEVector & F){
        assembleMixedDirichletDeformationDependentNeumann_homogeneousForcing(x,K,F);
    }

    void setup_dirichlet_conditions(){
        //setup_clamp();
        setup_semislipbc();
        //setup_slipbc_and_clamp();
        //setup_slipbc();
    }

    void apply_dirichlet_conditions(Epetra_FECrsMatrix & K, Epetra_FEVector & F, double & displacement){
        //apply_clamp(K,F,displacement);
        apply_semiselipbc(K,F,displacement);
        //apply_slipbc_and_clamp(K,F,displacement);
        //apply_slipbc(K,F,displacement);
    }

    void get_material_parameters(unsigned int & e_lid, unsigned int & gp){

        int n_gauss_points = Mesh->n_gauss_cells;
        int e_gid = Mesh->local_cells[e_lid];
        int node;
        double xi = Mesh->xi_cells[gp]; double eta = Mesh->eta_cells[gp]; double zeta = Mesh->zeta_cells[gp];
        tetra4::shape_functions(N,xi,eta,zeta);

        w1 = 0.0; w2 = 0.0; w3 = 0.0; w4 = 0.0;
        for (unsigned int j=0; j<4; ++j){
            node = cells_nodes_p1_med(4*e_gid+j);
            w1 += N(j)*w1_gmrf(node);
            w2 += N(j)*w2_gmrf(node);
            w3 += N(j)*w3_gmrf(node);
            w4 += N(j)*w4_gmrf(node);
        }

        c1  = icdf_gamma(w1,alpha1,alpha2);
        c2  = icdf_gamma(w2,alpha3,alpha4);
        u1  = icdf_beta (w3,tau1,tau2);
        mu4 = icdf_gamma(w4,alpha5,alpha6);

        mu1 = ( epsilon*mean_mu1 + (1.0/2.0)*c2*u1 )/( 1.0+epsilon );
        mu2 = ( epsilon*mean_mu2 + (1.0/(std::sqrt(3.0)*3.0))*c2*(1.0-u1) )/( 1.0+epsilon );
        mu3 = ( epsilon*mean_mu3 + c1/(2.0*beta3*beta3) )/( 1.0+epsilon );
        mu4 = ( epsilon*mean_mu4 + mu4 )/( 1.0+epsilon );

        for (int i=0; i<3; ++i){
            E1(i) = Laplace->laplace_direction_one(n_gauss_points*e_lid+gp,i);
            E2(i) = Laplace->laplace_direction_two_cross_one(n_gauss_points*e_lid+gp,i);
            E3(i) = Laplace->laplace_direction_two(n_gauss_points*e_lid+gp,i);
            a(i) = cos(theta)*E1(i) + sin(theta)*E2(i);
            b(i) = cos(theta)*E1(i) - sin(theta)*E2(i);
        }

    }

    double icdf_gamma(double & w, double & alpha, double & beta){
        double erfx = boost::math::erf<double>(w/std::sqrt(2.0));
        double y = (1.0/2.0)*(1.0 + erfx);
        double yinv = boost::math::gamma_p_inv<double,double>(alpha,y);
        double z = yinv*beta;
        return z;
    }

    double icdf_beta(double & w, double & tau1, double & tau2){
        double erfx = boost::math::erf<double>(w/std::sqrt(2.0));
        double y = (1.0/2.0)*(1.0 + erfx);
        double z = boost::math::ibeta_inv<double,double,double>(tau1,tau2,y);
        return z;
    }

    void get_constitutive_tensors_static_condensation(Epetra_SerialDenseMatrix & deformation_gradient, double & det, Epetra_SerialDenseVector & inverse_cauchy, Epetra_SerialDenseVector & piola_isc, Epetra_SerialDenseVector & piola_vol, Epetra_SerialDenseMatrix & tangent_piola_isc, Epetra_SerialDenseMatrix & tangent_piola_vol){
        model_C(deformation_gradient, det, inverse_cauchy, piola_isc, piola_vol, tangent_piola_isc, tangent_piola_vol);
    }

    void get_internal_pressure(double & theta, double & pressure, double & dpressure){
        double ptheta = std::pow(theta,beta3);
        pressure  = beta3*( (ptheta/theta) - (1.0/(ptheta*theta)) );
        dpressure = beta3*( (beta3-1.0)*(ptheta/(theta*theta)) + (beta3+1.0)/(ptheta*theta*theta) );
    }

    void get_material_parameters_for_recover(unsigned int & e_lid){

      int e_gid = Mesh->local_cells[e_lid];
      double xi = 1.0/3.0; double eta = 1.0/3.0; double zeta = 1.0/3.0;
      tetra4::shape_functions(N,xi,eta,zeta);

      w1 = 0.0; w2 = 0.0; w3 = 0.0; w4 = 0.0;
      for (unsigned int j=0; j<4; ++j){
          int node = cells_nodes_p1_med(4*e_gid+j);
          w1 += N(j)*w1_gmrf(node);
          w2 += N(j)*w2_gmrf(node);
          w3 += N(j)*w3_gmrf(node);
          w4 += N(j)*w4_gmrf(node);
      }

      c1  = icdf_gamma(w1,alpha1,alpha2);
      c2  = icdf_gamma(w2,alpha3,alpha4);
      u1  = icdf_beta (w3,tau1,tau2);
      mu4 = icdf_gamma(w4,alpha5,alpha6);

      mu1 = ( epsilon*mean_mu1 + (1.0/2.0)*c2*u1 )/( 1.0+epsilon );
      mu2 = ( epsilon*mean_mu2 + (1.0/(std::sqrt(3.0)*3.0))*c2*(1.0-u1) )/( 1.0+epsilon );
      mu3 = ( epsilon*mean_mu3 + c1/(2.0*beta3*beta3) )/( 1.0+epsilon );
      mu4 = ( epsilon*mean_mu4 + mu4 )/( 1.0+epsilon );

      for (int i=0; i<3; ++i){
          E1(i) = Laplace->laplace_direction_one_center(e_lid,i);
          E2(i) = Laplace->laplace_direction_two_cross_one_center(e_lid,i);
          E3(i) = Laplace->laplace_direction_two_center(e_lid,i);
          a(i) = cos(theta)*E1(i) + sin(theta)*E2(i);
          b(i) = cos(theta)*E1(i) - sin(theta)*E2(i);
      }

    }

    void get_stress_for_recover(Epetra_SerialDenseMatrix & deformation_gradient, double & det, Epetra_SerialDenseMatrix & piola_stress){

        det = deformation_gradient(0,0)*deformation_gradient(1,1)*deformation_gradient(2,2)
            -deformation_gradient(0,0)*deformation_gradient(1,2)*deformation_gradient(2,1)
            -deformation_gradient(0,1)*deformation_gradient(1,0)*deformation_gradient(2,2)
            +deformation_gradient(0,1)*deformation_gradient(1,2)*deformation_gradient(2,0)
            +deformation_gradient(0,2)*deformation_gradient(1,0)*deformation_gradient(2,1)
            -deformation_gradient(0,2)*deformation_gradient(1,1)*deformation_gradient(2,0);

        double alpha = std::pow(det,-2.0/3.0);
        double beta = 1.0/(det*det);
        Epetra_SerialDenseMatrix eye(3,3);
        Epetra_SerialDenseMatrix M1(3,3), M2(3,3);
        Epetra_SerialDenseMatrix C(3,3), L(3,3);
        Epetra_SerialDenseMatrix piola_ani1(3,3), piola_ani2(3,3);

        eye(0,0) = 1.0; eye(0,1) = 0.0; eye(0,2) = 0.0;
        eye(1,0) = 0.0; eye(1,1) = 1.0; eye(1,2) = 0.0;
        eye(2,0) = 0.0; eye(2,1) = 0.0; eye(2,2) = 1.0;

        M1.Multiply('N','T',1.0,a,a,0.0);
        M2.Multiply('N','T',1.0,b,b,0.0);

        C.Multiply('T','N',1.0,deformation_gradient,deformation_gradient,0.0);

        L(0,0) = (1.0/(det*det))*(C(1,1)*C(2,2)-C(1,2)*C(2,1));
        L(1,1) = (1.0/(det*det))*(C(0,0)*C(2,2)-C(0,2)*C(2,0));
        L(2,2) = (1.0/(det*det))*(C(0,0)*C(1,1)-C(0,1)*C(1,0));
        L(1,2) = (1.0/(det*det))*(C(0,2)*C(1,0)-C(0,0)*C(1,2));
        L(0,2) = (1.0/(det*det))*(C(0,1)*C(1,2)-C(0,2)*C(1,1));
        L(0,1) = (1.0/(det*det))*(C(0,2)*C(2,1)-C(0,1)*C(2,2));
        L(2,1) = L(1,2); L(2,0) = L(0,2); L(1,0) = L(0,1);

        double I1   = C(0,0) + C(1,1) + C(2,2);
        double II1  = C(0,0)*C(0,0) + C(1,1)*C(1,1) + C(2,2)*C(2,2) + 2.0*C(1,2)*C(1,2) + 2.0*C(0,2)*C(0,2) + 2.0*C(0,1)*C(0,1);
        double I2   = (1.0/2.0)*(I1*I1-II1);
        double I4_1 = C(0,0)*M1(0,0) + C(1,1)*M1(1,1) + C(2,2)*M1(2,2) + 2.0*C(0,1)*M1(0,1) + 2.0*C(0,2)*M1(0,2) + 2.0*C(1,2)*M1(1,2);
        double I4_2 = C(0,0)*M2(0,0) + C(1,1)*M2(1,1) + C(2,2)*M2(2,2) + 2.0*C(0,1)*M2(0,1) + 2.0*C(0,2)*M2(0,2) + 2.0*C(1,2)*M2(1,2);
        double pI2  = std::sqrt(I2);

        double S4_1 = (I4_1-1.0)*(I4_1-1.0);
        double S4_2 = (I4_2-1.0)*(I4_2-1.0);

        double ptheta = std::pow(det,beta3);
        double pressure = mu3*beta3*( (ptheta/det) - (1.0/(ptheta*det)) );

        for (unsigned int i=0; i<3; ++i){
            for (unsigned int j=0; j<3; ++j){
                piola_stress(i,j) = 2.0*mu1*alpha*(eye(i,j)-(1.0/3.0)*L(i,j))
                + mu2*beta*( 3.0*pI2*(I1*eye(i,j)-C(i,j)) - 2.0*I2*pI2*L(i,j) )
                + det*pressure*L(i,j);
                piola_ani1(i,j) = 4.0*mu4*(I4_1-1.0)*exp(beta4*S4_1)*M1(i,j);
                piola_ani2(i,j) = 4.0*mu4*(I4_2-1.0)*exp(beta4*S4_2)*M2(i,j);
            }
        }

        if (I4_1>1.0){
            piola_stress += piola_ani1;
        }
        if (I4_2>1.0){
            piola_stress += piola_ani2;
        }

    }

    void setup_clamp(){
        n_bc_dof = 0;
        for (unsigned int i=0; i<Mesh->n_local_nodes_without_ghosts; ++i){
            if (Mesh->nodes_to_boundaries(i,2)==1 || Mesh->nodes_to_boundaries(i,3)==1){
                n_bc_dof+=3;
            }
        }

        int indbc = 0;
        dof_on_boundary = new int [n_bc_dof];
        for (unsigned int inode=0; inode<Mesh->n_local_nodes_without_ghosts; ++inode){
            if (Mesh->nodes_to_boundaries(inode,2)==1 || Mesh->nodes_to_boundaries(inode,3)==1){
                dof_on_boundary[indbc+0] = 3*inode+0;
                dof_on_boundary[indbc+1] = 3*inode+1;
                dof_on_boundary[indbc+2] = 3*inode+2;
                indbc+=3;
            }
        }
    }

    void setup_slipbc(){
        const int nodesblk_gid0 = 480-1;
        const int nodesblk_gid1 = 538-1;
        const int nodesblk_gid2 = 577-1;

        int node;
        n_bc_dof = 0;
        for (unsigned int i=0; i<Mesh->n_local_nodes_without_ghosts; ++i){
            if (Mesh->nodes_to_boundaries(i,2)==1 || Mesh->nodes_to_boundaries(i,3)==1){
                node = Mesh->local_nodes[i];
                switch (node){
                    case nodesblk_gid0:
                        n_bc_dof+=2;
                        break;
                    case nodesblk_gid1:
                        n_bc_dof+=2;
                        break;
                    case nodesblk_gid2:
                        n_bc_dof+=2;
                        break;
                    default:
                        n_bc_dof+=1;
                        break;
                };
            }
        }

        int indbc = 0;
        dof_on_boundary = new int [n_bc_dof];
        for (unsigned int inode=0; inode<Mesh->n_local_nodes_without_ghosts; ++inode){
            if (Mesh->nodes_to_boundaries(inode,2)==1 || Mesh->nodes_to_boundaries(inode,3)==1){
                node = Mesh->local_nodes[inode];
                switch (node){
                    case nodesblk_gid0:
                        dof_on_boundary[indbc+0] = 3*inode+0;
                        dof_on_boundary[indbc+1] = 3*inode+1;
                        indbc+=2;
                        break;
                    case nodesblk_gid1:
                        dof_on_boundary[indbc+0] = 3*inode+0;
                        dof_on_boundary[indbc+1] = 3*inode+2;
                        indbc+=2;
                        break;
                    case nodesblk_gid2:
                        dof_on_boundary[indbc+0] = 3*inode+0;
                        dof_on_boundary[indbc+1] = 3*inode+2;
                        indbc+=2;
                        break;
                    default:
                        dof_on_boundary[indbc+0] = 3*inode+0;
                        indbc+=1;
                        break;
                };
            }
        }
    }

    void setup_semislipbc(){
        n_bc_dof = 0;
        for (unsigned int i=0; i<Mesh->n_local_nodes_without_ghosts; ++i){
            if (Mesh->nodes_to_boundaries(i,2)==1){
                n_bc_dof+=1;
            }
            if (Mesh->nodes_to_boundaries(i,3)==1){
                n_bc_dof+=3;
            }
        }

        int indbc = 0;
        dof_on_boundary = new int [n_bc_dof];
        for (unsigned int inode=0; inode<Mesh->n_local_nodes_without_ghosts; ++inode){
            if (Mesh->nodes_to_boundaries(inode,2)==1){
                dof_on_boundary[indbc] = 3*inode+0;
                indbc+=1;
            }
            if (Mesh->nodes_to_boundaries(inode,3)==1){
                dof_on_boundary[indbc+0] = 3*inode+0;
                dof_on_boundary[indbc+1] = 3*inode+1;
                dof_on_boundary[indbc+2] = 3*inode+2;
                indbc+=3;
            }
        }
    }

    void setup_slipbc_and_clamp(){
        Epetra_IntSerialDenseVector nodesblk_gid(3);
        nodesblk_gid(0) = 480-1;
        nodesblk_gid(1) = 481-1;
        nodesblk_gid(2) = 479-1;

        /*nodesblk_gid(0) = 203-1;
         nodesblk_gid(1) = 204-1;
         nodesblk_gid(2) = 205-1;
         nodesblk_gid(3) = 206-1;
         nodesblk_gid(4) = 207-1;
         nodesblk_gid(5) = 208-1;
         nodesblk_gid(6) = 281-1;
         nodesblk_gid(7) = 282-1;
         nodesblk_gid(8) = 283-1;
         nodesblk_gid(9) = 284-1;
         nodesblk_gid(10) = 285-1;
         nodesblk_gid(11) = 286-1;*/

        int node;
        n_bc_dof = 0;
        for (unsigned int i=0; i<Mesh->n_local_nodes_without_ghosts; ++i){
            if (Mesh->nodes_to_boundaries(i,2)==1 || Mesh->nodes_to_boundaries(i,3)==1){
                node = Mesh->local_nodes[i];
                if (node==nodesblk_gid(0)||node==nodesblk_gid(1)||node==nodesblk_gid(2)){
                    n_bc_dof+=3;
                }
                else{
                    n_bc_dof+=1;
                }
            }
        }

        int indbc = 0;
        dof_on_boundary = new int [n_bc_dof];
        for (unsigned int inode=0; inode<Mesh->n_local_nodes_without_ghosts; ++inode){
            if (Mesh->nodes_to_boundaries(inode,2)==1 || Mesh->nodes_to_boundaries(inode,3)==1){
                node = Mesh->local_nodes[inode];
                if (node==nodesblk_gid(0)||node==nodesblk_gid(1)||node==nodesblk_gid(2)){
                    dof_on_boundary[indbc+0] = 3*inode+0;
                    dof_on_boundary[indbc+1] = 3*inode+1;
                    dof_on_boundary[indbc+2] = 3*inode+2;
                    indbc+=3;
                }
                else{
                    dof_on_boundary[indbc+0] = 3*inode+0;
                    indbc+=1;
                }
            }
        }
    }

    void apply_clamp(Epetra_FECrsMatrix & K, Epetra_FEVector & F, double & displacement){
        if (n_bc_dof>0){
            int node;
            for (int inode=0; inode<Mesh->n_local_nodes_without_ghosts; ++inode){
                node = Mesh->local_nodes[inode];
                if (Mesh->nodes_to_boundaries(inode,2)==1 || Mesh->nodes_to_boundaries(inode,3)==1){
                    F[0][StandardMap->LID(3*node+0)] = displacement;
                    F[0][StandardMap->LID(3*node+1)] = displacement;
                    F[0][StandardMap->LID(3*node+2)] = displacement;
                }
            }
        }
        ML_Epetra::Apply_OAZToMatrix(dof_on_boundary,n_bc_dof,K);
    }

    void apply_slipbc(Epetra_FECrsMatrix & K, Epetra_FEVector & F, double & displacement){
        const int nodesblk_gid0 = 480-1;
        const int nodesblk_gid1 = 538-1;
        const int nodesblk_gid2 = 577-1;

        if (n_bc_dof>0){
            int node;
            for (int inode=0; inode<Mesh->n_local_nodes_without_ghosts; ++inode){
                node = Mesh->local_nodes[inode];
                if (Mesh->nodes_to_boundaries(inode,2)==1 || Mesh->nodes_to_boundaries(inode,3)==1){
                    switch (node){
                        case nodesblk_gid0:
                            F[0][StandardMap->LID(3*node+0)] = displacement;
                            F[0][StandardMap->LID(3*node+1)] = displacement;
                            break;
                        case nodesblk_gid1:
                            F[0][StandardMap->LID(3*node+0)] = displacement;
                            F[0][StandardMap->LID(3*node+2)] = displacement;
                            break;
                        case nodesblk_gid2:
                            F[0][StandardMap->LID(3*node+0)] = displacement;
                            F[0][StandardMap->LID(3*node+2)] = displacement;
                            break;
                        default:
                            F[0][StandardMap->LID(3*node+0)] = displacement;
                            break;
                    };
                }
            }
        }
        ML_Epetra::Apply_OAZToMatrix(dof_on_boundary,n_bc_dof,K);
    }

    void apply_semiselipbc(Epetra_FECrsMatrix & K, Epetra_FEVector & F, double & displacement){
        if (n_bc_dof>0){
            int node;
            for (int inode=0; inode<Mesh->n_local_nodes_without_ghosts; ++inode){
                node = Mesh->local_nodes[inode];
                if (Mesh->nodes_to_boundaries(inode,2)==1){
                    F[0][StandardMap->LID(3*node+0)] = displacement;
                }
                if (Mesh->nodes_to_boundaries(inode,3)==1){
                    F[0][StandardMap->LID(3*node+0)] = displacement;
                    F[0][StandardMap->LID(3*node+1)] = displacement;
                    F[0][StandardMap->LID(3*node+2)] = displacement;
                }
            }
        }
        ML_Epetra::Apply_OAZToMatrix(dof_on_boundary,n_bc_dof,K);
    }

    void apply_slipbc_and_clamp(Epetra_FECrsMatrix & K, Epetra_FEVector & F, double & displacement){
        Epetra_IntSerialDenseVector nodesblk_gid(3);
        nodesblk_gid(0) = 480-1;
        nodesblk_gid(1) = 481-1;
        nodesblk_gid(2) = 479-1;

        if (n_bc_dof>0){
            int node;
            for (int inode=0; inode<Mesh->n_local_nodes_without_ghosts; ++inode){
                node = Mesh->local_nodes[inode];
                if (Mesh->nodes_to_boundaries(inode,2)==1 || Mesh->nodes_to_boundaries(inode,3)==1){
                    if (node==nodesblk_gid(0)||node==nodesblk_gid(1)||node==nodesblk_gid(2)){
                        F[0][StandardMap->LID(3*node+0)] = displacement;
                        F[0][StandardMap->LID(3*node+1)] = displacement;
                        F[0][StandardMap->LID(3*node+2)] = displacement;
                    }
                    else{
                        F[0][StandardMap->LID(3*node+0)] = displacement;
                    }
                }
            }
        }
        ML_Epetra::Apply_OAZToMatrix(dof_on_boundary,n_bc_dof,K);
    }

    void model_C(Epetra_SerialDenseMatrix & deformation_gradient, double & det, Epetra_SerialDenseVector & L, Epetra_SerialDenseVector & piola_isc, Epetra_SerialDenseVector & piola_vol, Epetra_SerialDenseMatrix & tangent_piola_isc, Epetra_SerialDenseMatrix & tangent_piola_vol){

        det = deformation_gradient(0,0)*deformation_gradient(1,1)*deformation_gradient(2,2)
            -deformation_gradient(0,0)*deformation_gradient(1,2)*deformation_gradient(2,1)
            -deformation_gradient(0,1)*deformation_gradient(1,0)*deformation_gradient(2,2)
            +deformation_gradient(0,1)*deformation_gradient(1,2)*deformation_gradient(2,0)
            +deformation_gradient(0,2)*deformation_gradient(1,0)*deformation_gradient(2,1)
            -deformation_gradient(0,2)*deformation_gradient(1,1)*deformation_gradient(2,0);

        double alpha = std::pow(det,-2.0/3.0);

        Epetra_SerialDenseVector eye(6);
        Epetra_SerialDenseMatrix rightCauchy(3,3);
        Epetra_SerialDenseVector M1(6), M2(6);
        Epetra_SerialDenseVector C(6), D(6);
        Epetra_SerialDenseVector piola_nh(6), piola_ani1(6), piola_ani2(6);

        M1(0) = a(0)*a(0); M2(0) = b(0)*b(0);
        M1(1) = a(1)*a(1); M2(1) = b(1)*b(1);
        M1(2) = a(2)*a(2); M2(2) = b(2)*b(2);
        M1(3) = a(1)*a(2); M2(3) = b(1)*b(2);
        M1(4) = a(0)*a(2); M2(4) = b(0)*b(2);
        M1(5) = a(0)*a(1); M2(5) = b(0)*b(1);

        rightCauchy.Multiply('T','N',1.0,deformation_gradient,deformation_gradient,0.0);

        eye(0) = 1.0; eye(1) = 1.0; eye(2) = 1.0; eye(3) = 0.0; eye(4) = 0.0; eye(5) = 0.0;

        C(0) = rightCauchy(0,0); C(1) = rightCauchy(1,1); C(2) = rightCauchy(2,2);
        C(3) = rightCauchy(1,2); C(4) = rightCauchy(0,2); C(5) = rightCauchy(0,1);

        L(0) = (1.0/(det*det))*(rightCauchy(1,1)*rightCauchy(2,2)-rightCauchy(1,2)*rightCauchy(2,1));
        L(1) = (1.0/(det*det))*(rightCauchy(0,0)*rightCauchy(2,2)-rightCauchy(0,2)*rightCauchy(2,0));
        L(2) = (1.0/(det*det))*(rightCauchy(0,0)*rightCauchy(1,1)-rightCauchy(0,1)*rightCauchy(1,0));
        L(3) = (1.0/(det*det))*(rightCauchy(0,2)*rightCauchy(1,0)-rightCauchy(0,0)*rightCauchy(1,2));
        L(4) = (1.0/(det*det))*(rightCauchy(0,1)*rightCauchy(1,2)-rightCauchy(0,2)*rightCauchy(1,1));
        L(5) = (1.0/(det*det))*(rightCauchy(0,2)*rightCauchy(2,1)-rightCauchy(0,1)*rightCauchy(2,2));

        double I1   = C(0) + C(1) + C(2);
        double II1  = C(0)*C(0) + C(1)*C(1) + C(2)*C(2) + 2.0*C(3)*C(3) + 2.0*C(4)*C(4) + 2.0*C(5)*C(5);
        double I2   = (1.0/2.0)*(I1*I1-II1);
        double I4_1 = C(0)*M1(0) + C(1)*M1(1) + C(2)*M1(2) + 2.0*C(5)*M1(5) + 2.0*C(4)*M1(4) + 2.0*C(3)*M1(3);
        double I4_2 = C(0)*M2(0) + C(1)*M2(1) + C(2)*M2(2) + 2.0*C(5)*M2(5) + 2.0*C(4)*M2(4) + 2.0*C(3)*M2(3);
        double pI2  = std::sqrt(I2);

        double S4_1 = (I4_1-1.0)*(I4_1-1.0);
        double S4_2 = (I4_2-1.0)*(I4_2-1.0);

        for (unsigned int i=0; i<6; ++i){
            D(i) = I1*eye(i) - C(i);
            piola_nh(i) = 2.0*alpha*mu1*(eye(i)-(1.0/3.0)*I1*L(i));
            piola_isc(i) = piola_nh(i) + (mu2/(det*det))*( 3.0*pI2*D(i) - 2.0*pI2*I2*L(i) );

            piola_ani1(i) = 4.0*mu4*(I4_1-1.0)*exp(beta4*S4_1)*M1(i);
            piola_ani2(i) = 4.0*mu4*(I4_2-1.0)*exp(beta4*S4_2)*M2(i);

            piola_vol(i) = mu3*det*L(i);
        }

        double scalarAB;

        scalarAB = mu3*det;
        tensor_product(mu3*det,L,L,tangent_piola_vol,0.0);
        scalarAB = -2.0*mu3*det;
        sym_tensor_product(scalarAB,L,L,tangent_piola_vol,1.0);

        scalarAB = -2.0/3.0;
        tensor_product(scalarAB,piola_nh,L,tangent_piola_isc,0.0);
        tensor_product(scalarAB,L,piola_nh,tangent_piola_isc,1.0);

        scalarAB = -6.0*mu2*pI2/(det*det);
        tensor_product(scalarAB,D,eye,tangent_piola_isc,1.0);
        tensor_product(scalarAB,eye,D,tangent_piola_isc,1.0);

        scalarAB = (-4.0/9.0)*mu1*alpha*I1 + 4.0*mu2*pI2*I2/(det*det);
        tensor_product(scalarAB,L,L,tangent_piola_isc,1.0);

        scalarAB = (4.0/3.0)*mu1*alpha*I1 + 4.0*mu2*pI2*I2/(det*det);
        sym_tensor_product(scalarAB,L,L,tangent_piola_isc,1.0);

        scalarAB = 3.0*mu2/(det*det*pI2);
        tensor_product(scalarAB,D,D,tangent_piola_isc,1.0);

        scalarAB = 6.0*mu2*pI2/(det*det);
        tensor_product(scalarAB,eye,eye,tangent_piola_isc,1.0);

        scalarAB = -scalarAB;
        sym_tensor_product(scalarAB,eye,eye,tangent_piola_isc,1.0);

        if (I4_1>1.0){
            piola_isc += piola_ani1;
            scalarAB = (8.0*mu4 + 16.0*mu4*beta4*S4_1)*exp(beta4*S4_1);
            tensor_product(scalarAB,M1,M1,tangent_piola_isc,1.0);
        }
        if (I4_2>1.0){
            piola_isc += piola_ani2;
            scalarAB = (8.0*mu4 + 16.0*mu4*beta4*S4_2)*exp(beta4*S4_2);
            tensor_product(scalarAB,M2,M2,tangent_piola_isc,1.0);
        }
    }

    void model_B(Epetra_SerialDenseMatrix & deformation_gradient, double & det, Epetra_SerialDenseVector & piola_isc, Epetra_SerialDenseVector & piola_vol, Epetra_SerialDenseMatrix & tangent_piola_isc, Epetra_SerialDenseMatrix & tangent_piola_vol){

        det = deformation_gradient(0,0)*deformation_gradient(1,1)*deformation_gradient(2,2)-deformation_gradient(0,0)*deformation_gradient(1,2)*deformation_gradient(2,1)-deformation_gradient(0,1)*deformation_gradient(1,0)*deformation_gradient(2,2)+deformation_gradient(0,1)*deformation_gradient(1,2)*deformation_gradient(2,0)+deformation_gradient(0,2)*deformation_gradient(1,0)*deformation_gradient(2,1)-deformation_gradient(0,2)*deformation_gradient(1,1)*deformation_gradient(2,0);

        double alpha = std::pow(det,-2.0/3.0);

        Epetra_SerialDenseMatrix rightCauchy(3,3);
        Epetra_SerialDenseVector M1(6), M2(6);
        Epetra_SerialDenseVector eye(6);
        Epetra_SerialDenseVector C(6), D(6), L(6);
        Epetra_SerialDenseVector dI4_1(6), dI4_2(6);
        Epetra_SerialDenseVector piola_nh(6), piola_ani1(6), piola_ani2(6);

        M1(0) = a(0)*a(0); M2(0) = b(0)*b(0);
        M1(1) = a(1)*a(1); M2(1) = b(1)*b(1);
        M1(2) = a(2)*a(2); M2(2) = b(2)*b(2);
        M1(3) = a(1)*a(2); M2(3) = b(1)*b(2);
        M1(4) = a(0)*a(2); M2(4) = b(0)*b(2);
        M1(5) = a(0)*a(1); M2(5) = b(0)*b(1);

        rightCauchy.Multiply('T','N',1.0,deformation_gradient,deformation_gradient,0.0);

        eye(0) = 1.0; eye(1) = 1.0; eye(2) = 1.0; eye(3) = 0.0; eye(4) = 0.0; eye(5) = 0.0;

        C(0) = rightCauchy(0,0); C(1) = rightCauchy(1,1); C(2) = rightCauchy(2,2);
        C(3) = rightCauchy(1,2); C(4) = rightCauchy(0,2); C(5) = rightCauchy(0,1);

        L(0) = (1.0/(det*det))*(rightCauchy(1,1)*rightCauchy(2,2)-rightCauchy(1,2)*rightCauchy(2,1));
        L(1) = (1.0/(det*det))*(rightCauchy(0,0)*rightCauchy(2,2)-rightCauchy(0,2)*rightCauchy(2,0));
        L(2) = (1.0/(det*det))*(rightCauchy(0,0)*rightCauchy(1,1)-rightCauchy(0,1)*rightCauchy(1,0));
        L(3) = (1.0/(det*det))*(rightCauchy(0,2)*rightCauchy(1,0)-rightCauchy(0,0)*rightCauchy(1,2));
        L(4) = (1.0/(det*det))*(rightCauchy(0,1)*rightCauchy(1,2)-rightCauchy(0,2)*rightCauchy(1,1));
        L(5) = (1.0/(det*det))*(rightCauchy(0,2)*rightCauchy(2,1)-rightCauchy(0,1)*rightCauchy(2,2));

        double I1 = C(0) + C(1) + C(2);
        double II1 = C(0)*C(0) + C(1)*C(1) + C(2)*C(2) + 2.0*C(3)*C(3) + 2.0*C(4)*C(4) + 2.0*C(5)*C(5);
        double I2 = (1.0/2.0)*(I1*I1-II1);
        double I4_1 = C(0)*M1(0) + C(1)*M1(1) + C(2)*M1(2) + 2.0*C(5)*M1(5) + 2.0*C(4)*M1(4) + 2.0*C(3)*M1(3);
        double I4_2 = C(0)*M2(0) + C(1)*M2(1) + C(2)*M2(2) + 2.0*C(5)*M2(5) + 2.0*C(4)*M2(4) + 2.0*C(3)*M2(3);
        double pI2 = std::sqrt(I2);

        double S4_1 = (alpha*I4_1-1.0)*(alpha*I4_1-1.0);
        double S4_2 = (alpha*I4_2-1.0)*(alpha*I4_2-1.0);

        for (unsigned int i=0; i<6; ++i){
            D(i) = I1*eye(i) - C(i);
            dI4_1(i) = M1(i)-(1.0/3.0)*I4_1*L(i);
            dI4_2(i) = M2(i)-(1.0/3.0)*I4_2*L(i);
            piola_nh(i) = 2.0*alpha*mu1*(eye(i)-(1.0/3.0)*I1*L(i));
            piola_isc(i) = piola_nh(i) + (mu2/(det*det))*( 3.0*pI2*D(i) - 2.0*pI2*I2*L(i) );

            piola_ani1(i) = 4.0*mu4*alpha*(alpha*I4_1-1.0)*exp(beta4*S4_1)*(M1(i)-(1.0/3.0)*I4_1*L(i));
            piola_ani2(i) = 4.0*mu4*alpha*(alpha*I4_2-1.0)*exp(beta4*S4_2)*(M2(i)-(1.0/3.0)*I4_2*L(i));;

            piola_vol(i) = det*L(i);
        }

        double scalarAB;

        scalarAB = det;
        tensor_product(det,L,L,tangent_piola_vol,0.0);
        scalarAB = -2.0*det;
        sym_tensor_product(scalarAB,L,L,tangent_piola_vol,1.0);

        scalarAB = -2.0/3.0;
        tensor_product(scalarAB,piola_nh,L,tangent_piola_isc,0.0);
        tensor_product(scalarAB,L,piola_nh,tangent_piola_isc,1.0);

        scalarAB = -6.0*mu2*pI2/(det*det);
        tensor_product(scalarAB,D,eye,tangent_piola_isc,1.0);
        tensor_product(scalarAB,eye,D,tangent_piola_isc,1.0);

        scalarAB = (-4.0/9.0)*mu1*alpha*I1 + 4.0*mu2*pI2*I2/(det*det);
        tensor_product(scalarAB,L,L,tangent_piola_isc,1.0);

        scalarAB = (4.0/3.0)*mu1*alpha*I1 + 4.0*mu2*pI2*I2/(det*det);
        sym_tensor_product(scalarAB,L,L,tangent_piola_isc,1.0);

        scalarAB = 3.0*mu2/(det*det*pI2);
        tensor_product(scalarAB,D,D,tangent_piola_isc,1.0);

        scalarAB = 6.0*mu2*pI2/(det*det);
        tensor_product(scalarAB,eye,eye,tangent_piola_isc,1.0);

        scalarAB = -scalarAB;
        sym_tensor_product(scalarAB,eye,eye,tangent_piola_isc,1.0);

        if (I4_1>1.0){
            piola_isc += piola_ani1;
            scalarAB = -(8.0/3.0)*mu4*alpha*(alpha*I4_1-1.0)*exp(beta4*S4_1);
            tensor_product(scalarAB,dI4_1,L,tangent_piola_isc,1.0);
            scalarAB = (8.0/3.0)*mu4*alpha*alpha*exp(beta4*S4_1);
            tensor_product(scalarAB,dI4_1,dI4_1,tangent_piola_isc,1.0);
            scalarAB = 16.0*mu4*beta4*alpha*alpha*S4_1*exp(beta4*S4_1);
            tensor_product(scalarAB,dI4_1,dI4_1,tangent_piola_isc,1.0);
        }
        if (I4_2>1.0){
            piola_isc += piola_ani2;
            scalarAB = -(8.0/3.0)*mu4*alpha*(alpha*I4_2-1.0)*exp(beta4*S4_2);
            tensor_product(scalarAB,dI4_2,L,tangent_piola_isc,1.0);
            scalarAB = (8.0/3.0)*mu4*alpha*alpha*exp(beta4*S4_2);
            tensor_product(scalarAB,dI4_2,dI4_2,tangent_piola_isc,1.0);
            scalarAB = 16.0*mu4*beta4*alpha*alpha*S4_2*exp(beta4*S4_2);
            tensor_product(scalarAB,dI4_2,dI4_2,tangent_piola_isc,1.0);
        }

    }

    void model_A(Epetra_SerialDenseMatrix & deformation_gradient, double & det, Epetra_SerialDenseVector & piola_isc, Epetra_SerialDenseVector & piola_vol, Epetra_SerialDenseMatrix & tangent_piola_isc, Epetra_SerialDenseMatrix & tangent_piola_vol){

        det = deformation_gradient(0,0)*deformation_gradient(1,1)*deformation_gradient(2,2)-deformation_gradient(0,0)*deformation_gradient(1,2)*deformation_gradient(2,1)-deformation_gradient(0,1)*deformation_gradient(1,0)*deformation_gradient(2,2)+deformation_gradient(0,1)*deformation_gradient(1,2)*deformation_gradient(2,0)+deformation_gradient(0,2)*deformation_gradient(1,0)*deformation_gradient(2,1)-deformation_gradient(0,2)*deformation_gradient(1,1)*deformation_gradient(2,0);

        double alpha = std::pow(det,-2.0/3.0);

        Epetra_SerialDenseMatrix rightCauchy(3,3);
        Epetra_SerialDenseVector M1(6), M2(6);
        Epetra_SerialDenseVector eye(6);
        Epetra_SerialDenseVector C(6), L(6), CC(6);
        Epetra_SerialDenseVector CMMC1(6), CMMC2(6);
        Epetra_SerialDenseVector D(6);
        Epetra_SerialDenseVector dK3_1(6), dK3_2(6);
        Epetra_SerialDenseVector piola_nh(6), piola_ani1(6), piola_ani2(6);

        M1(0) = a(0)*a(0);
        M1(1) = a(1)*a(1);
        M1(2) = a(2)*a(2);
        M1(3) = a(1)*a(2);
        M1(4) = a(0)*a(2);
        M1(5) = a(0)*a(1);

        M2(0) = b(0)*b(0);
        M2(1) = b(1)*b(1);
        M2(2) = b(2)*b(2);
        M2(3) = b(1)*b(2);
        M2(4) = b(0)*b(2);
        M2(5) = b(0)*b(1);

        rightCauchy.Multiply('T','N',1.0,deformation_gradient,deformation_gradient,0.0);

        eye(0) = 1.0; eye(1) = 1.0; eye(2) = 1.0; eye(3) = 0.0; eye(4) = 0.0; eye(5) = 0.0;

        C(0) = rightCauchy(0,0); C(1) = rightCauchy(1,1); C(2) = rightCauchy(2,2);
        C(3) = rightCauchy(1,2); C(4) = rightCauchy(0,2); C(5) = rightCauchy(0,1);

        L(0) = (1.0/(det*det))*(rightCauchy(1,1)*rightCauchy(2,2)-rightCauchy(1,2)*rightCauchy(2,1));
        L(1) = (1.0/(det*det))*(rightCauchy(0,0)*rightCauchy(2,2)-rightCauchy(0,2)*rightCauchy(2,0));
        L(2) = (1.0/(det*det))*(rightCauchy(0,0)*rightCauchy(1,1)-rightCauchy(0,1)*rightCauchy(1,0));
        L(3) = (1.0/(det*det))*(rightCauchy(0,2)*rightCauchy(1,0)-rightCauchy(0,0)*rightCauchy(1,2));
        L(4) = (1.0/(det*det))*(rightCauchy(0,1)*rightCauchy(1,2)-rightCauchy(0,2)*rightCauchy(1,1));
        L(5) = (1.0/(det*det))*(rightCauchy(0,2)*rightCauchy(2,1)-rightCauchy(0,1)*rightCauchy(2,2));

        CMMC1(0) = 2.0*C(0)*M1(0) + 2.0*C(4)*M1(4) + 2.0*C(5)*M1(5);
        CMMC1(1) = 2.0*C(1)*M1(1) + 2.0*C(3)*M1(3) + 2.0*C(5)*M1(5);
        CMMC1(2) = 2.0*C(2)*M1(2) + 2.0*C(3)*M1(3) + 2.0*C(4)*M1(4);
        CMMC1(3) = C(1)*M1(3) + C(3)*M1(1) + C(2)*M1(3) + C(3)*M1(2) + C(4)*M1(5) + C(5)*M1(4);
        CMMC1(4) = C(0)*M1(4) + C(4)*M1(0) + C(2)*M1(4) + C(4)*M1(2) + C(3)*M1(5) + C(5)*M1(3);
        CMMC1(5) = C(0)*M1(5) + C(5)*M1(0) + C(1)*M1(5) + C(5)*M1(1) + C(3)*M1(4) + C(4)*M1(3);

        CMMC2(0) = 2.0*C(0)*M2(0) + 2.0*C(4)*M2(4) + 2.0*C(5)*M2(5);
        CMMC2(1) = 2.0*C(1)*M2(1) + 2.0*C(3)*M2(3) + 2.0*C(5)*M2(5);
        CMMC2(2) = 2.0*C(2)*M2(2) + 2.0*C(3)*M2(3) + 2.0*C(4)*M2(4);
        CMMC2(3) = C(1)*M2(3) + C(3)*M2(1) + C(2)*M2(3) + C(3)*M2(2) + C(4)*M2(5) + C(5)*M2(4);
        CMMC2(4) = C(0)*M2(4) + C(4)*M2(0) + C(2)*M2(4) + C(4)*M2(2) + C(3)*M2(5) + C(5)*M2(3);
        CMMC2(5) = C(0)*M2(5) + C(5)*M2(0) + C(1)*M2(5) + C(5)*M2(1) + C(3)*M2(4) + C(4)*M2(3);

        CC(0) = C(0)*C(0) + C(4)*C(4) + C(5)*C(5);
        CC(1) = C(1)*C(1) + C(3)*C(3) + C(5)*C(5);
        CC(2) = C(2)*C(2) + C(3)*C(3) + C(4)*C(4);
        CC(3) = C(1)*C(3) + C(2)*C(3) + C(4)*C(5);
        CC(4) = C(0)*C(4) + C(2)*C(4) + C(3)*C(5);
        CC(5) = C(0)*C(5) + C(1)*C(5) + C(3)*C(4);

        double I1 = C(0) + C(1) + C(2);
        double II1 = C(0)*C(0) + C(1)*C(1) + C(2)*C(2) + 2.0*C(3)*C(3) + 2.0*C(4)*C(4) + 2.0*C(5)*C(5);
        double I2 = (1.0/2.0)*(I1*I1-II1);
        double I4_1 = C(0)*M1(0) + C(1)*M1(1) + C(2)*M1(2) + 2.0*C(5)*M1(5) + 2.0*C(4)*M1(4) + 2.0*C(3)*M1(3);
        double I4_2 = C(0)*M2(0) + C(1)*M2(1) + C(2)*M2(2) + 2.0*C(5)*M2(5) + 2.0*C(4)*M2(4) + 2.0*C(3)*M2(3);
        double I5_1 = CC(0)*M1(0) + CC(1)*M1(1) + CC(2)*M1(2) + 2.0*CC(5)*M1(5) + 2.0*CC(4)*M1(4) + 2.0*CC(3)*M1(3);
        double I5_2 = CC(0)*M2(0) + CC(1)*M2(1) + CC(2)*M2(2) + 2.0*CC(5)*M2(5) + 2.0*CC(4)*M2(4) + 2.0*CC(3)*M2(3);
        double K3_1 = I1*I4_1-I5_1;
        double K3_2 = I1*I4_2-I5_2;
        double pI2 = std::sqrt(I2);

        for (unsigned int i=0; i<6; ++i){
            D(i) = I1*eye(i) - C(i);
            piola_nh(i) = 2.0*alpha*mu1*(eye(i)-(1.0/3.0)*I1*L(i));
            piola_isc(i) = piola_nh(i) + (mu2/(det*det))*( 3.0*pI2*D(i) - 2.0*pI2*I2*L(i) );

            dK3_1(i) = I4_1*eye(i) + I1*M1(i) - CMMC1(i);
            dK3_2(i) = I4_2*eye(i) + I1*M2(i) - CMMC2(i);
            piola_ani1(i) = 2.0*mu4*beta4*std::pow(K3_1-2.0,beta4-1.0)*dK3_1(i);
            piola_ani2(i) = 2.0*mu4*beta4*std::pow(K3_2-2.0,beta4-1.0)*dK3_2(i);

            piola_vol(i) = det*L(i);
        }

        double scalarAB;

        scalarAB = det;
        tensor_product(det,L,L,tangent_piola_vol,0.0);
        scalarAB = -2.0*det;
        sym_tensor_product(scalarAB,L,L,tangent_piola_vol,1.0);

        scalarAB = -2.0/3.0;
        tensor_product(scalarAB,piola_nh,L,tangent_piola_isc,0.0);
        tensor_product(scalarAB,L,piola_nh,tangent_piola_isc,1.0);

        scalarAB = -6.0*mu2*pI2/(det*det);
        tensor_product(scalarAB,D,eye,tangent_piola_isc,1.0);
        tensor_product(scalarAB,eye,D,tangent_piola_isc,1.0);

        scalarAB = (-4.0/9.0)*mu1*alpha*I1 + 4.0*mu2*pI2*I2/(det*det);
        tensor_product(scalarAB,L,L,tangent_piola_isc,1.0);

        scalarAB = (4.0/3.0)*mu1*alpha*I1 + 4.0*mu2*pI2*I2/(det*det);
        sym_tensor_product(scalarAB,L,L,tangent_piola_isc,1.0);

        scalarAB = 3.0*mu2/(det*det*pI2);
        tensor_product(scalarAB,D,D,tangent_piola_isc,1.0);

        scalarAB = 6.0*mu2*pI2/(det*det);
        tensor_product(scalarAB,eye,eye,tangent_piola_isc,1.0);

        scalarAB = -scalarAB;
        sym_tensor_product(scalarAB,eye,eye,tangent_piola_isc,1.0);

        if (K3_1>2.0){
            piola_isc += piola_ani1;
            scalarAB = 4.0*mu4*beta4*(beta4-1.0)*std::pow(K3_1-2.0,beta4-2.0);
            tensor_product(scalarAB,dK3_1,dK3_1,tangent_piola_isc,1.0);

            scalarAB = 4.0*mu4*beta4*std::pow(K3_1-2.0,beta4-1.0);
            tensor_product(scalarAB,eye,M1,tangent_piola_isc,1.0);
            tensor_product(scalarAB,M1,eye,tangent_piola_isc,1.0);
            scalarAB = -scalarAB;
            sym_tensor_product(scalarAB,M1,eye,tangent_piola_isc,1.0);
            sym_tensor_product(scalarAB,eye,M1,tangent_piola_isc,1.0);
        }
        if (K3_2>2.0){
            piola_isc += piola_ani2;
            scalarAB = 4.0*mu4*beta4*(beta4-1.0)*std::pow(K3_2-2.0,beta4-2.0);
            tensor_product(scalarAB,dK3_2,dK3_2,tangent_piola_isc,1.0);

            scalarAB = 4.0*mu4*beta4*std::pow(K3_2-2.0,beta4-1.0);
            tensor_product(scalarAB,eye,M2,tangent_piola_isc,1.0);
            tensor_product(scalarAB,M2,eye,tangent_piola_isc,1.0);
            scalarAB = -scalarAB;
            sym_tensor_product(scalarAB,M2,eye,tangent_piola_isc,1.0);
            sym_tensor_product(scalarAB,eye,M2,tangent_piola_isc,1.0);
        }
    }

};

#endif


#include "teca_descriptive_statistics.h"

#include "teca_cartesian_mesh.h"
#include "teca_table.h"
#include "teca_array_collection.h"
#include "teca_variant_array.h"
#include "teca_metadata.h"

#include <algorithm>
#include <iostream>
#include <string>
#include <set>
#include <cmath>

#if defined(TECA_HAS_BOOST)
#include <boost/program_options.hpp>
#endif

using std::string;
using std::vector;
using std::set;
using std::cerr;
using std::endl;

//#define TECA_DEBUG

namespace internal
{
template <typename num_t>
void quartiles(const num_t *ptr, size_t n, num_t &lq, num_t &med, num_t &uq)
{
    size_t nb = n*sizeof(num_t);
    num_t *tmp = static_cast<num_t*>(malloc(nb));
    memcpy(tmp, ptr, nb);
    size_t n25 = n/4;
    size_t n50 = n/2;
    size_t n75 = (n*3)/4;
    std::partial_sort(tmp, tmp+n75+1, tmp+n);
    lq = tmp[n25];
    med = tmp[n50];
    uq = tmp[n75];
    free(tmp);
}

template <typename num_t>
void quartiles2(const num_t *ptr, size_t n, num_t &lq, num_t &med, num_t &uq)
{
    size_t nb = n*sizeof(num_t);
    num_t *tmp = static_cast<num_t*>(malloc(nb));
    memcpy(tmp, ptr, nb);

    size_t n25 = n/4;
    std::nth_element(tmp, tmp+n25, tmp+n);
    lq = tmp[n25];

    size_t n50 = n/2;
    std::nth_element(tmp, tmp+n50, tmp+n);
    med = tmp[n50];

    size_t n75 = (n*3)/4;
    std::nth_element(tmp, tmp+n75, tmp+n);
    uq = tmp[n75];

    free(tmp);
}

template <typename num_t>
num_t min(const num_t *ptr, size_t n)
{
    num_t min = std::numeric_limits<num_t>::max();
    for (size_t i = 0; i < n; ++i)
        min = min > ptr[i] ? ptr[i] : min;
    return min;
}

template <typename num_t>
num_t max(const num_t *ptr, size_t n)
{
    num_t max = std::numeric_limits<num_t>::lowest();
    for (size_t i = 0; i < n; ++i)
        max = max < ptr[i] ? ptr[i] : max;
    return max;
}

template <typename num_t>
num_t sum(const num_t *ptr, size_t n)
{
    num_t s = num_t();
    for (size_t i = 0; i < n; ++i)
        s += ptr[i];
    return s;
}

template <typename num_t>
num_t var(const num_t *ptr, size_t n, num_t av)
{
    if (!n) return num_t();
    num_t v = num_t();
    for (size_t i = 0; i < n; ++i)
    {
        num_t d = ptr[i] - av;
        v += d*d;
    }
    v /= num_t(n);
    return v;
}
};

// --------------------------------------------------------------------------
teca_descriptive_statistics::teca_descriptive_statistics()
{
    this->set_number_of_input_connections(1);
    this->set_number_of_output_ports(1);
}

// --------------------------------------------------------------------------
teca_descriptive_statistics::~teca_descriptive_statistics()
{}

#if defined(TECA_HAS_BOOST)
// --------------------------------------------------------------------------
void teca_descriptive_statistics::get_properties_description(
    const string &prefix, options_description &global_opts)
{
    options_description opts("Options for "
        + (prefix.empty()?"teca_descriptive_statistics":prefix));

    opts.add_options()
        TECA_POPTS_GET(std::vector<std::string>, prefix, dependent_variables,
            "list of arrays to compute statistics for")
        ;

    global_opts.add(opts);
}

// --------------------------------------------------------------------------
void teca_descriptive_statistics::set_properties(
    const string &prefix, variables_map &opts)
{
    TECA_POPTS_SET(opts, std::vector<std::string>, prefix, dependent_variables)
}
#endif

// --------------------------------------------------------------------------
void teca_descriptive_statistics::get_dependent_variables(
    const teca_metadata &request, std::vector<std::string> &dep_vars)
{
    dep_vars = this->dependent_variables;

    if (dep_vars.empty())
    {
        std::string key = "teca_descriptive_statistics::dependent_variables";
        if (request.has(key))
            request.get(key, dep_vars);
    }
}

// --------------------------------------------------------------------------
std::vector<teca_metadata>
teca_descriptive_statistics::get_upstream_request(
    unsigned int port, const std::vector<teca_metadata> &input_md,
    const teca_metadata &request)
{
#ifdef TECA_DEBUG
    cerr << teca_parallel_id()
        << "teca_descriptive_statistics::get_upstream_request" << endl;
#endif
    (void)port;
    (void)input_md;

    vector<teca_metadata> up_reqs;

    // copy the incoming request to preserve the downstream
    // requirements and add the arrays we need
    teca_metadata req(request);

    std::set<std::string> arrays;
    if (req.has("arrays"))
        req.get("arrays", arrays);

    // intercept request for our output
    //arrays.erase(this->get_derived_variable(request));

    // get the names of the arrays we need to request
    std::vector<std::string> dep_vars;
    this->get_dependent_variables(req, dep_vars);

    size_t n = dependent_variables.size();
    for (size_t i = 0; i < n; ++i)
        arrays.insert(dep_vars[i]);

    req.set("arrays", arrays);
    up_reqs.push_back(req);

    return up_reqs;
}

// --------------------------------------------------------------------------
const_p_teca_dataset teca_descriptive_statistics::execute(
    unsigned int port,
    const std::vector<const_p_teca_dataset> &input_data,
    const teca_metadata &request)
{
#ifdef TECA_DEBUG
    cerr << teca_parallel_id() << "teca_descriptive_statistics::execute" << endl;
#endif
    (void)port;

    // get the input mesh
    const_p_teca_cartesian_mesh in_mesh
        = std::dynamic_pointer_cast<const teca_cartesian_mesh>(input_data[0]);

    if (!in_mesh)
    {
        TECA_ERROR("dataset is not a teca_cartesian_mesh")
        return nullptr;
    }

    // set up the output
    p_teca_table table = teca_table::New();
    table->declare_columns("step", long(), "time", double());

    std::string calendar;
    in_mesh->get_calendar(calendar);
    table->set_calendar(calendar);

    std::string time_units;
    in_mesh->get_time_units(time_units);
    table->set_time_units(time_units);

    unsigned long step;
    in_mesh->get_time_step(step);
    table << step;

    double time;
    in_mesh->get_time(time);
    table << time;

    // dependent variables
    std::vector<std::string> dep_var_names;
    this->get_dependent_variables(request, dep_var_names);

    // for each variable
    size_t n_dep_vars = dep_var_names.size();
    for (size_t i = 0; i < n_dep_vars; ++i)
    {
        const std::string &dep_var_name = dep_var_names[i];

        // get the array
        const_p_teca_variant_array dep_var
            = in_mesh->get_point_arrays()->get(dep_var_name);
        if (!dep_var)
        {
            TECA_ERROR("dependent variable " << i << " \""
                << dep_var_name << "\" not present.")
            return nullptr;
        }

        TEMPLATE_DISPATCH(const teca_variant_array_impl,
            dep_var.get(),

            size_t n = dep_var->size();
            const NT *pv = static_cast<const TT*>(dep_var.get())->get();

            // compute stats
            NT mn = internal::min(pv, n);
            NT mx = internal::max(pv, n);
            NT av = internal::sum(pv, n)/NT(n);
            NT vr = internal::var(pv, n, av);
            NT lq; NT med; NT uq;
            internal::quartiles2(pv, n, lq, med, uq);

            // add to output table
            table->declare_columns(
                "min " + dep_var_name, NT(), "max " + dep_var_name, NT(),
                "avg " + dep_var_name, NT(), "var " + dep_var_name, NT(),
                "low_q " + dep_var_name, NT(), "med " + dep_var_name, NT(),
                "up_q " + dep_var_name, NT());

            table << mn << mx << av << vr << lq << med << uq;
            )
    }

    return table;
}