/*=========================================================================

  Module:    $RCSfile: V_HexMetric.cpp,v $

  Copyright (c) 2006 Sandia Corporation.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

/*
 *
 * HexMetric.cpp contains quality calculations for hexes
 *
 * This file is part of VERDICT
 *
 */

#define VERDICT_EXPORTS

#include "moab/verdict.h"
#include "VerdictVector.hpp"
#include "V_GaussIntegration.hpp"
#include "verdict_defines.hpp"
#include <memory.h>

#if defined(__BORLANDC__)
#pragma warn -8004 /* "assigned a value that is never used" */
#endif

//! the average volume of a hex
double verdict_hex_size = 0;

//! weights based on the average size of a hex
int v_hex_get_weight( VerdictVector &v1, 
    VerdictVector &v2,
    VerdictVector &v3 )
{

  if( verdict_hex_size == 0)
    return 0;

  v1.set(1,0,0);
  v2.set(0,1,0);
  v3.set(0,0,1);

  double scale = pow(verdict_hex_size/ (v1 % (v2 * v3 )), 0.33333333333); 
  v1 *= scale;
  v2 *= scale;
  v3 *= scale;

  return 1;
}

//! returns the average volume of a hex
C_FUNC_DEF void v_set_hex_size( double size )
{
  verdict_hex_size = size;
}

#define make_hex_nodes(coord, pos)                      \
  for (int mhcii = 0; mhcii < 8; mhcii++ )              \
  {                                                     \
    pos[mhcii].set( coord[mhcii][0],                    \
        coord[mhcii][1],                                \
        coord[mhcii][2] );                              \
  }


#define make_edge_length_squares(edges, lengths)        \
{                                                       \
  for(int melii=0; melii<12; melii++)                   \
    lengths[melii] = edges[melii].length_squared();     \
}
  

//! make VerdictVectors from coordinates
void make_hex_edges( double coordinates[][3], VerdictVector edges[12] )
{
  edges[0].set(
      coordinates[1][0] - coordinates[0][0],
      coordinates[1][1] - coordinates[0][1],
      coordinates[1][2] - coordinates[0][2]
      );
  edges[1].set(
      coordinates[2][0] - coordinates[1][0],
      coordinates[2][1] - coordinates[1][1],
      coordinates[2][2] - coordinates[1][2]
      );
  edges[2].set(
      coordinates[3][0] - coordinates[2][0],
      coordinates[3][1] - coordinates[2][1],
      coordinates[3][2] - coordinates[2][2]
      );
  edges[3].set(
      coordinates[0][0] - coordinates[3][0],
      coordinates[0][1] - coordinates[3][1],
      coordinates[0][2] - coordinates[3][2]
      );
  edges[4].set(
      coordinates[5][0] - coordinates[4][0],
      coordinates[5][1] - coordinates[4][1],
      coordinates[5][2] - coordinates[4][2]
      );
  edges[5].set(
      coordinates[6][0] - coordinates[5][0],
      coordinates[6][1] - coordinates[5][1],
      coordinates[6][2] - coordinates[5][2]
      );
  edges[6].set(
      coordinates[7][0] - coordinates[6][0],
      coordinates[7][1] - coordinates[6][1],
      coordinates[7][2] - coordinates[6][2]
      );
  edges[7].set(
      coordinates[4][0] - coordinates[7][0],
      coordinates[4][1] - coordinates[7][1],
      coordinates[4][2] - coordinates[7][2]
      );
  edges[8].set(
      coordinates[4][0] - coordinates[0][0],
      coordinates[4][1] - coordinates[0][1],
      coordinates[4][2] - coordinates[0][2]
      );
  edges[9].set(
      coordinates[5][0] - coordinates[1][0],
      coordinates[5][1] - coordinates[1][1],
      coordinates[5][2] - coordinates[1][2]
      );
  edges[10].set(
      coordinates[6][0] - coordinates[2][0],
      coordinates[6][1] - coordinates[2][1],
      coordinates[6][2] - coordinates[2][2]
      );
  edges[11].set(
      coordinates[7][0] - coordinates[3][0],
      coordinates[7][1] - coordinates[3][1],
      coordinates[7][2] - coordinates[3][2]
      );
}

/*!
  localizes hex coordinates
*/
void localize_hex_coordinates(double coordinates[][3], VerdictVector position[8] )
{

  int ii;
  for ( ii = 0; ii < 8; ii++ )
  {
    position[ii].set( coordinates[ii][0],
        coordinates[ii][1],
        coordinates[ii][2] );
  }
  
  // ... Make centroid of element the center of coordinate system
  VerdictVector point_1256 = position[1];
  point_1256 += position[2];
  point_1256 += position[5];
  point_1256 += position[6];

  VerdictVector point_0374 = position[0];
  point_0374 += position[3];
  point_0374 += position[7];
  point_0374 += position[4];

  VerdictVector centroid = point_1256;
  centroid += point_0374;
  centroid /= 8.0;

  int i;
  for ( i = 0; i < 8; i++)
    position[i] -= centroid;

  // ... Rotate element such that center of side 1-2 and 0-3 define X axis
  double DX = point_1256.x() - point_0374.x();
  double DY = point_1256.y() - point_0374.y();
  double DZ = point_1256.z() - point_0374.z();

  double AMAGX = sqrt(DX*DX + DZ*DZ);
  double AMAGY = sqrt(DX*DX + DY*DY + DZ*DZ);
  double FMAGX = AMAGX == 0.0 ? 1.0 : 0.0;
  double FMAGY = AMAGY == 0.0 ? 1.0 : 0.0;

  double CZ = DX / (AMAGX + FMAGX) + FMAGX;
  double SZ = DZ / (AMAGX + FMAGX);
  double CY = AMAGX / (AMAGY + FMAGY) + FMAGY;
  double SY = DY / (AMAGY + FMAGY);

  double temp;
 
  for (i = 0; i < 8; i++) 
  {
    temp =  CY * CZ * position[i].x() + CY * SZ * position[i].z() +
      SY * position[i].y();
    position[i].y( -SY * CZ * position[i].x() - SY * SZ * position[i].z() +
        CY * position[i].y());
    position[i].z( -SZ * position[i].x() + CZ * position[i].z());
    position[i].x(temp);
  }


  // ... Now, rotate about Y
  VerdictVector delta = -position[0];
  delta -= position[1];
  delta += position[2];
  delta += position[3];
  delta -= position[4];
  delta -= position[5];
  delta += position[6];
  delta += position[7];

  DY = delta.y();
  DZ = delta.z();

  AMAGY = sqrt(DY*DY + DZ*DZ);
  FMAGY = AMAGY == 0.0 ? 1.0 : 0.0;

  double CX = DY / (AMAGY + FMAGY) + FMAGY;
  double SX = DZ / (AMAGY + FMAGY);
  
  for (i = 0; i < 8; i++) 
    {
    temp =  CX * position[i].y() + SX * position[i].z();
    position[i].z(-SX * position[i].y() + CX * position[i].z());
    position[i].y(temp);
    }
}


double safe_ratio3( const double numerator, 
    const double denominator,
    const double max_ratio )
{
    // this filter is essential for good running time in practice
  double return_value;

  const double filter_n = max_ratio * 1.0e-16;
  const double filter_d = 1.0e-16;
  if ( fabs( numerator ) <= filter_n && fabs( denominator ) >= filter_d )
    {
    return_value = numerator / denominator;
    }
  else
    {
    return_value = fabs(numerator) / max_ratio >= fabs(denominator) ?
      ( (numerator >= 0.0 && denominator >= 0.0) ||
        (numerator < 0.0 && denominator < 0.0) ?
        max_ratio : -max_ratio )
      : numerator / denominator;
    }

  return return_value;
}


double safe_ratio( const double numerator, 
    const double denominator )
{

  double return_value;
  const double filter_n = VERDICT_DBL_MAX; 
  const double filter_d = VERDICT_DBL_MIN;
  if ( fabs( numerator ) <= filter_n && fabs( denominator ) >= filter_d )
    {
    return_value = numerator / denominator;
    }
  else
    {
    return_value = VERDICT_DBL_MAX;
    }

  return return_value;
  
}



double condition_comp( const VerdictVector &xxi, 
    const VerdictVector &xet, 
    const VerdictVector &xze )
{
  double det =  xxi % (xet * xze);
  
  if( det <= VERDICT_DBL_MIN) { return VERDICT_DBL_MAX; }
  
  
  double term1 = xxi % xxi + xet % xet + xze % xze;
  double term2 = (( xxi*xet ) % ( xxi*xet )) + (( xet*xze ) % ( xet*xze )) + (( xze*xxi ) % ( xze*xxi ));
  
  return sqrt( term1 * term2 ) / det;
}



double oddy_comp( const VerdictVector &xxi, 
    const VerdictVector &xet, 
    const VerdictVector &xze )
{
  static const double third=1.0/3.0;
  
  double g11, g12, g13, g22, g23, g33, rt_g;
  
  g11 = xxi % xxi;
  g12 = xxi % xet;
  g13 = xxi % xze;
  g22 = xet % xet;
  g23 = xet % xze;
  g33 = xze % xze;
  rt_g = xxi % (xet * xze);
  
  double oddy_metric;
  if( rt_g > VERDICT_DBL_MIN ) 
  {
    double norm_G_squared = g11*g11 + 2.0*g12*g12 + 2.0*g13*g13 + g22*g22 + 2.0*g23*g23 +g33*g33;
    
    double norm_J_squared = g11 + g22 + g33;
    
    oddy_metric = ( norm_G_squared - third*norm_J_squared*norm_J_squared ) / pow( rt_g, 4.*third );
    
  }
  
  else
    oddy_metric = VERDICT_DBL_MAX;
  
  return oddy_metric;
  
}


//! calcualates edge lengths of a hex
double hex_edge_length(int max_min, double coordinates[][3])
{
  double temp[3], edge[12];
  int i;
  
  //lengths^2 of edges
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[1][i] - coordinates[0][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[0] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[2][i] - coordinates[1][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[1] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[3][i] - coordinates[2][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[2] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[0][i] - coordinates[3][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[3] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[5][i] - coordinates[4][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[4] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[6][i] - coordinates[5][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[5] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[7][i] - coordinates[6][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[6] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[4][i] - coordinates[7][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[7] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[4][i] - coordinates[0][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[8] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[5][i] - coordinates[1][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[9] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[6][i] - coordinates[2][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[10] = sqrt( temp[0] + temp[1] + temp[2] );
  
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[7][i] - coordinates[3][i];
    temp[i] = temp[i] * temp[i];
  }
  edge[11] = sqrt( temp[0] + temp[1] + temp[2] );
  
  double _edge = edge[0];
  
  if(max_min == 0)
  {
    for( i = 1; i<12; i++) 
      _edge = VERDICT_MIN( _edge, edge[i] ); 
    return (double)_edge;
  }  
  else
  {
    for( i = 1; i<12; i++) 
      _edge = VERDICT_MAX( _edge, edge[i] );
    return (double)_edge;
  }
  
}


double diag_length(int max_min, double coordinates[][3]) 
{
  double temp[3], diag[4];
  int i;
  
  //lengths^2  f diag nals
  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[6][i] - coordinates[0][i];
    temp[i] = temp[i] * temp[i];
  }
  diag[0] = sqrt( temp[0] + temp[1] + temp[2] );

  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[4][i] - coordinates[2][i];
    temp[i] = temp[i] * temp[i];
  }
  diag[1] = sqrt( temp[0] + temp[1] + temp[2] );

  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[7][i] - coordinates[1][i];
    temp[i] = temp[i] * temp[i];
  }
  diag[2] = sqrt( temp[0] + temp[1] + temp[2] );

  for (i = 0; i < 3; i++ )
  {
    temp[i] = coordinates[5][i] - coordinates[3][i];
    temp[i] = temp[i] * temp[i];
  }
  diag[3] = sqrt( temp[0] + temp[1] + temp[2] );

  double diagonal = diag[0];
  if(max_min == 0 )  //Return min diagonal
  { 
    for( i = 1; i<4; i++)
      diagonal = VERDICT_MIN( diagonal, diag[i] );
    return (double)diagonal;
  }
  else          //Return max diagonal
  {
    for( i = 1; i<4; i++)
      diagonal = VERDICT_MAX( diagonal, diag[i] );
    return (double)diagonal;  
  }

}

//! calculates efg values
VerdictVector calc_hex_efg( int efg_index, VerdictVector coordinates[8])
{
  
  VerdictVector efg;
  
  switch(efg_index) {
    
    case 1:
      efg =  coordinates[1];
      efg += coordinates[2];
      efg += coordinates[5];
      efg += coordinates[6];
      efg -= coordinates[0];
      efg -= coordinates[3];
      efg -= coordinates[4];
      efg -= coordinates[7];
      break;
      
    case 2:
      efg =  coordinates[2];
      efg += coordinates[3];
      efg += coordinates[6];
      efg += coordinates[7];
      efg -= coordinates[0];
      efg -= coordinates[1];
      efg -= coordinates[4];
      efg -= coordinates[5];
      break;
      
    case 3:
      efg =  coordinates[4];
      efg += coordinates[5];
      efg += coordinates[6];
      efg += coordinates[7];
      efg -= coordinates[0];
      efg -= coordinates[1];
      efg -= coordinates[2];
      efg -= coordinates[3];
      break;

    case 12:
      efg =  coordinates[0];
      efg += coordinates[2];
      efg += coordinates[4];
      efg += coordinates[6];
      efg -= coordinates[1];
      efg -= coordinates[3];
      efg -= coordinates[5];
      efg -= coordinates[7];
      break;
      
    case 13:
      efg =  coordinates[0];
      efg += coordinates[3];
      efg += coordinates[5];
      efg += coordinates[6];
      efg -= coordinates[1];
      efg -= coordinates[2];
      efg -= coordinates[4];
      efg -= coordinates[7];
      break;

   case 23:
      efg =  coordinates[0];
      efg += coordinates[1];
      efg += coordinates[6];
      efg += coordinates[7];
      efg -= coordinates[2];
      efg -= coordinates[3];
      efg -= coordinates[4];
      efg -= coordinates[5];
      break;

   case 123:
      efg =  coordinates[0];
      efg += coordinates[2];
      efg += coordinates[5];
      efg += coordinates[7];
      efg -= coordinates[1];
      efg -= coordinates[5];
      efg -= coordinates[6];
      efg -= coordinates[2];
      break;
      
   default:
      efg.set(0,0,0);
      
  }
  
  return efg;
}

/*!
   the edge ratio of a hex

   NB (P. Pebay 01/23/07): 
     Hmax / Hmin where Hmax and Hmin are respectively the maximum and the
     minimum edge lengths
*/
C_FUNC_DEF double v_hex_edge_ratio (int /*num_nodes*/, double coordinates[][3])
{

  VerdictVector edges[12];
  make_hex_edges(coordinates, edges);
  
  double a2 = edges[0].length_squared();
  double b2 = edges[1].length_squared();
  double c2 = edges[2].length_squared();
  double d2 = edges[3].length_squared();
  double e2 = edges[4].length_squared();
  double f2 = edges[5].length_squared();
  double g2 = edges[6].length_squared();
  double h2 = edges[7].length_squared();
  double i2 = edges[8].length_squared();
  double j2 = edges[9].length_squared();
  double k2 = edges[10].length_squared();
  double l2 = edges[11].length_squared();

  double mab,mcd,mef,Mab,Mcd,Mef;
  double mgh,mij,mkl,Mgh,Mij,Mkl;

  if ( a2 < b2 )
    {
      mab = a2;
      Mab = b2;
    }
  else // b2 <= a2
    {
      mab = b2;
      Mab = a2;
    }
  if ( c2 < d2 )
    {
      mcd = c2;
      Mcd = d2;
    }
  else // d2 <= c2
    {
      mcd = d2;
      Mcd = c2;
    }
  if ( e2 < f2 )
    {
      mef = e2;
      Mef = f2;
    }
  else // f2 <= e2
    {
      mef = f2;
      Mef = e2;
    }
  if ( g2 < h2 )
    {
      mgh = g2;
      Mgh = h2;
    }
  else // h2 <= g2
    {
      mgh = h2;
      Mgh = g2;
    }
  if ( i2 < j2 )
    {
      mij = i2;
      Mij = j2;
    }
  else // j2 <= i2
    {
      mij = j2;
      Mij = i2;
    }
  if ( k2 < l2 )
    {
      mkl = k2;
      Mkl = l2;
    }
  else // l2 <= k2
    {
      mkl = l2;
      Mkl = k2;
    }

  double m2;
  m2 = mab < mcd ? mab : mcd;
  m2 = m2  < mef ? m2  : mef;
  m2 = m2  < mgh ? m2  : mgh;
  m2 = m2  < mij ? m2  : mij;
  m2 = m2  < mkl ? m2  : mkl;

  if( m2 < VERDICT_DBL_MIN ) 
    return (double)VERDICT_DBL_MAX;

  double M2;
  M2 = Mab > Mcd ? Mab : Mcd;
  M2 = M2  > Mef ? M2  : Mef;
  M2 = M2  > Mgh ? M2  : Mgh;
  M2 = M2  > Mij ? M2  : Mij;
  M2 = M2  > Mkl ? M2  : Mkl;
  m2 = m2  < mef ? m2  : mef;

  M2 = Mab > Mcd ? Mab : Mcd;
  M2 = M2  > Mef ? M2  : Mef;

  double edge_ratio = sqrt( M2 / m2 );
  
  if( edge_ratio > 0 )
    return (double) VERDICT_MIN( edge_ratio, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( edge_ratio, -VERDICT_DBL_MAX );
}

/*!
  max edge ratio of a hex

  Maximum edge length ratio at hex center
*/
C_FUNC_DEF double v_hex_max_edge_ratio (int /*num_nodes*/, double coordinates[][3])
{
  double aspect;
  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );
  
  double aspect_12, aspect_13, aspect_23;
  
  VerdictVector efg1 = calc_hex_efg( 1, node_pos);
  VerdictVector efg2 = calc_hex_efg( 2, node_pos);
  VerdictVector efg3 = calc_hex_efg( 3, node_pos);

  double mag_efg1 = efg1.length();
  double mag_efg2 = efg2.length();
  double mag_efg3 = efg3.length();
  
  aspect_12 = safe_ratio( VERDICT_MAX( mag_efg1, mag_efg2 ) , VERDICT_MIN( mag_efg1, mag_efg2 ) );
  aspect_13 = safe_ratio( VERDICT_MAX( mag_efg1, mag_efg3 ) , VERDICT_MIN( mag_efg1, mag_efg3 ) );
  aspect_23 = safe_ratio( VERDICT_MAX( mag_efg2, mag_efg3 ) , VERDICT_MIN( mag_efg2, mag_efg3 ) );

  aspect = VERDICT_MAX( aspect_12, VERDICT_MAX( aspect_13, aspect_23 ) );

  if( aspect > 0 )
    return (double) VERDICT_MIN( aspect, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( aspect, -VERDICT_DBL_MAX );
 
}

/*!
  skew of a hex

  Maximum ||cosA|| where A is the angle between edges at hex center.
*/
C_FUNC_DEF double v_hex_skew( int /*num_nodes*/, double coordinates[][3] )
{
  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );
  
  double skew_1, skew_2, skew_3;
  
  VerdictVector efg1 = calc_hex_efg( 1, node_pos);
  VerdictVector efg2 = calc_hex_efg( 2, node_pos);
  VerdictVector efg3 = calc_hex_efg( 3, node_pos);
 
  if( efg1.normalize() <= VERDICT_DBL_MIN )
    return VERDICT_DBL_MAX;
  if( efg2.normalize() <= VERDICT_DBL_MIN )
    return VERDICT_DBL_MAX;
  if( efg3.normalize() <= VERDICT_DBL_MIN )
    return VERDICT_DBL_MAX;

  skew_1 = fabs(efg1 % efg2);
  skew_2 = fabs(efg1 % efg3);
  skew_3 = fabs(efg2 % efg3);

  double skew = (VERDICT_MAX( skew_1, VERDICT_MAX( skew_2, skew_3 ) ));

  if( skew > 0 )
    return (double) VERDICT_MIN( skew, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( skew, -VERDICT_DBL_MAX );
}

/*!
  taper of a hex

  Maximum ratio of lengths derived from opposite edges.
*/
C_FUNC_DEF double v_hex_taper( int /*num_nodes*/, double coordinates[][3] )
{
  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );
  
  VerdictVector efg1 = calc_hex_efg( 1, node_pos);
  VerdictVector efg2 = calc_hex_efg( 2, node_pos);
  VerdictVector efg3 = calc_hex_efg( 3, node_pos);

  VerdictVector efg12 = calc_hex_efg( 12, node_pos);
  VerdictVector efg13 = calc_hex_efg( 13, node_pos);
  VerdictVector efg23 = calc_hex_efg( 23, node_pos);

  double taper_1 = fabs( safe_ratio( efg12.length(), VERDICT_MIN( efg1.length(), efg2.length())));
  double taper_2 = fabs( safe_ratio( efg13.length(), VERDICT_MIN( efg1.length(), efg3.length())));
  double taper_3 = fabs( safe_ratio( efg23.length(), VERDICT_MIN( efg2.length(), efg3.length())));

  double taper = (double)VERDICT_MAX(taper_1, VERDICT_MAX(taper_2, taper_3));  

  if( taper > 0 )
    return (double) VERDICT_MIN( taper, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( taper, -VERDICT_DBL_MAX );
}

/*!
  volume of a hex

  Jacobian at hex center
*/
C_FUNC_DEF double v_hex_volume( int /*num_nodes*/, double coordinates[][3] )
{
  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );
  
  VerdictVector efg1 = calc_hex_efg( 1, node_pos);
  VerdictVector efg2 = calc_hex_efg( 2, node_pos);
  VerdictVector efg3 = calc_hex_efg( 3, node_pos);

  double volume;
  volume = (double) (efg1 % (efg2 * efg3))/64.0;

  if( volume > 0 )
    return (double) VERDICT_MIN( volume, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( volume, -VERDICT_DBL_MAX );
}

/*!
  stretch of a hex

  sqrt(3) * minimum edge length / maximum diagonal length
*/
C_FUNC_DEF double v_hex_stretch( int /*num_nodes*/, double coordinates[][3] )
{
  static const double HEX_STRETCH_SCALE_FACTOR = sqrt(3.0);
  
  double min_edge = hex_edge_length( 0, coordinates );
  double max_diag = diag_length( 1, coordinates );  
  
  double stretch = HEX_STRETCH_SCALE_FACTOR * safe_ratio(min_edge, max_diag);

  if( stretch > 0 )
    return (double) VERDICT_MIN( stretch, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( stretch, -VERDICT_DBL_MAX );
}

/*!
  diagonal ratio of a hex
  
  Minimum diagonal length / maximum diagonal length
*/
C_FUNC_DEF double v_hex_diagonal( int /*num_nodes*/, double coordinates[][3] )
{
  
  double min_diag = diag_length( 0, coordinates ); 
  double max_diag = diag_length( 1, coordinates );
  
  double diagonal = safe_ratio( min_diag, max_diag);

  if( diagonal > 0 )
    return (double) VERDICT_MIN( diagonal, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( diagonal, -VERDICT_DBL_MAX );
}

#define SQR(x) ((x) * (x))

/*!
  dimension of a hex

  Pronto-specific characteristic length for stable time step calculation. 
  Char_length = Volume / 2 grad Volume
*/
C_FUNC_DEF double v_hex_dimension( int /*num_nodes*/, double coordinates[][3] )
{
  
  double gradop[9][4];

  double x1 = coordinates[0][0];
  double x2 = coordinates[1][0];
  double x3 = coordinates[2][0];
  double x4 = coordinates[3][0];
  double x5 = coordinates[4][0];
  double x6 = coordinates[5][0];
  double x7 = coordinates[6][0];
  double x8 = coordinates[7][0];

  double y1 = coordinates[0][1];
  double y2 = coordinates[1][1];
  double y3 = coordinates[2][1];
  double y4 = coordinates[3][1];
  double y5 = coordinates[4][1];
  double y6 = coordinates[5][1];
  double y7 = coordinates[6][1];
  double y8 = coordinates[7][1];

  double z1 = coordinates[0][2];
  double z2 = coordinates[1][2];
  double z3 = coordinates[2][2];
  double z4 = coordinates[3][2];
  double z5 = coordinates[4][2];
  double z6 = coordinates[5][2];
  double z7 = coordinates[6][2];
  double z8 = coordinates[7][2];

  double z24 = z2 - z4;
  double z52 = z5 - z2;
  double z45 = z4 - z5;
  gradop[1][1] = ( y2*(z6-z3-z45) + y3*z24 + y4*(z3-z8-z52)
      + y5*(z8-z6-z24) + y6*z52 + y8*z45 ) / 12.0;
         
  double z31 = z3 - z1;
  double z63 = z6 - z3;
  double z16 = z1 - z6;
  gradop[2][1] = ( y3*(z7-z4-z16) + y4*z31 + y1*(z4-z5-z63)
      + y6*(z5-z7-z31) + y7*z63 + y5*z16 ) / 12.0;

  double z42 = z4 - z2;
  double z74 = z7 - z4;
  double z27 = z2 - z7;
  gradop[3][1] = ( y4*(z8-z1-z27) + y1*z42 + y2*(z1-z6-z74)
      + y7*(z6-z8-z42) + y8*z74 + y6*z27 ) / 12.0;
             
  double z13 = z1 - z3;
  double z81 = z8 - z1;
  double z38 = z3 - z8;
  gradop[4][1] = ( y1*(z5-z2-z38) + y2*z13 + y3*(z2-z7-z81)
      + y8*(z7-z5-z13) + y5*z81 + y7*z38 ) / 12.0;
   
  double z86 = z8 - z6;
  double z18 = z1 - z8;
  double z61 = z6 - z1;
  gradop[5][1] = ( y8*(z4-z7-z61) + y7*z86 + y6*(z7-z2-z18)
      + y1*(z2-z4-z86) + y4*z18 + y2*z61 ) / 12.0;
     
  double z57 = z5 - z7;
  double z25 = z2 - z5;
  double z72 = z7 - z2;
  gradop[6][1] = ( y5*(z1-z8-z72) + y8*z57 + y7*(z8-z3-z25)
      + y2*(z3-z1-z57) + y1*z25 + y3*z72 ) / 12.0;
       
  double z68 = z6 - z8;
  double z36 = z3 - z6;
  double z83 = z8 - z3;
  gradop[7][1] = ( y6*(z2-z5-z83) + y5*z68 + y8*(z5-z4-z36)
      + y3*(z4-z2-z68) + y2*z36 + y4*z83 ) / 12.0;
         
  double z75 = z7 - z5;
  double z47 = z4 - z7;
  double z54 = z5 - z4;
  gradop[8][1] = ( y7*(z3-z6-z54) + y6*z75 + y5*(z6-z1-z47)
      + y4*(z1-z3-z75) + y3*z47 + y1*z54 ) / 12.0;
           
  double x24 = x2 - x4;
  double x52 = x5 - x2;
  double x45 = x4 - x5;
  gradop[1][2] = ( z2*(x6-x3-x45) + z3*x24 + z4*(x3-x8-x52)
      + z5*(x8-x6-x24) + z6*x52 + z8*x45 ) / 12.0;

  double x31 = x3 - x1;
  double x63 = x6 - x3;
  double x16 = x1 - x6;
  gradop[2][2] = ( z3*(x7-x4-x16) + z4*x31 + z1*(x4-x5-x63)
      + z6*(x5-x7-x31) + z7*x63 + z5*x16 ) / 12.0;

  double x42 = x4 - x2;
  double x74 = x7 - x4;
  double x27 = x2 - x7;
  gradop[3][2] = ( z4*(x8-x1-x27) + z1*x42 + z2*(x1-x6-x74)
      + z7*(x6-x8-x42) + z8*x74 + z6*x27 ) / 12.0;

  double x13 = x1 - x3;
  double x81 = x8 - x1;
  double x38 = x3 - x8;
  gradop[4][2] = ( z1*(x5-x2-x38) + z2*x13 + z3*(x2-x7-x81)
      + z8*(x7-x5-x13) + z5*x81 + z7*x38 ) / 12.0;

  double x86 = x8 - x6;
  double x18 = x1 - x8;
  double x61 = x6 - x1;
  gradop[5][2] = ( z8*(x4-x7-x61) + z7*x86 + z6*(x7-x2-x18)
      + z1*(x2-x4-x86) + z4*x18 + z2*x61 ) / 12.0;
                     
  double x57 = x5 - x7;
  double x25 = x2 - x5;
  double x72 = x7 - x2;
  gradop[6][2] = ( z5*(x1-x8-x72) + z8*x57 + z7*(x8-x3-x25)
      + z2*(x3-x1-x57) + z1*x25 + z3*x72 ) / 12.0;

  double x68 = x6 - x8;
  double x36 = x3 - x6;
  double x83 = x8 - x3;
  gradop[7][2] = ( z6*(x2-x5-x83) + z5*x68 + z8*(x5-x4-x36)
      + z3*(x4-x2-x68) + z2*x36 + z4*x83 ) / 12.0;

  double x75 = x7 - x5;
  double x47 = x4 - x7;
  double x54 = x5 - x4;
  gradop[8][2] = ( z7*(x3-x6-x54) + z6*x75 + z5*(x6-x1-x47)
      + z4*(x1-x3-x75) + z3*x47 + z1*x54 ) / 12.0;
           
  double y24 = y2 - y4;
  double y52 = y5 - y2;
  double y45 = y4 - y5;
  gradop[1][3] = ( x2*(y6-y3-y45) + x3*y24 + x4*(y3-y8-y52)
      + x5*(y8-y6-y24) + x6*y52 + x8*y45 ) / 12.0;
             
  double y31 = y3 - y1;
  double y63 = y6 - y3;
  double y16 = y1 - y6;
  gradop[2][3] = ( x3*(y7-y4-y16) + x4*y31 + x1*(y4-y5-y63)
      + x6*(y5-y7-y31) + x7*y63 + x5*y16 ) / 12.0;
               
  double y42 = y4 - y2;
  double y74 = y7 - y4;
  double y27 = y2 - y7;
  gradop[3][3] = ( x4*(y8-y1-y27) + x1*y42 + x2*(y1-y6-y74)
      + x7*(y6-y8-y42) + x8*y74 + x6*y27 ) / 12.0;
                 
  double y13 = y1 - y3;
  double y81 = y8 - y1;
  double y38 = y3 - y8;
  gradop[4][3] = ( x1*(y5-y2-y38) + x2*y13 + x3*(y2-y7-y81)
      + x8*(y7-y5-y13) + x5*y81 + x7*y38 ) / 12.0;
                   
  double y86 = y8 - y6;
  double y18 = y1 - y8;
  double y61 = y6 - y1;
  gradop[5][3] = ( x8*(y4-y7-y61) + x7*y86 + x6*(y7-y2-y18)
      + x1*(y2-y4-y86) + x4*y18 + x2*y61 ) / 12.0;
                     
  double y57 = y5 - y7;
  double y25 = y2 - y5;
  double y72 = y7 - y2;
  gradop[6][3] = ( x5*(y1-y8-y72) + x8*y57 + x7*(y8-y3-y25)
      + x2*(y3-y1-y57) + x1*y25 + x3*y72 ) / 12.0;
                       
  double y68 = y6 - y8;
  double y36 = y3 - y6;
  double y83 = y8 - y3;
  gradop[7][3] = ( x6*(y2-y5-y83) + x5*y68 + x8*(y5-y4-y36)
      + x3*(y4-y2-y68) + x2*y36 + x4*y83 ) / 12.0;
                         
  double y75 = y7 - y5;
  double y47 = y4 - y7;
  double y54 = y5 - y4;
  gradop[8][3] = ( x7*(y3-y6-y54) + x6*y75 + x5*(y6-y1-y47)
      + x4*(y1-y3-y75) + x3*y47 + x1*y54 ) / 12.0;

  //     calculate element volume and characteristic element aspect ratio
  //     (used in time step and hourglass control) - 



  double volume =  coordinates[0][0] * gradop[1][1]
    + coordinates[1][0] * gradop[2][1]
    + coordinates[2][0] * gradop[3][1]
    + coordinates[3][0] * gradop[4][1]
    + coordinates[4][0] * gradop[5][1]
    + coordinates[5][0] * gradop[6][1]
    + coordinates[6][0] * gradop[7][1]
    + coordinates[7][0] * gradop[8][1];
  double aspect = .5*SQR(volume) /
    ( SQR(gradop[1][1]) + SQR(gradop[2][1])
      + SQR(gradop[3][1]) + SQR(gradop[4][1])
      + SQR(gradop[5][1]) + SQR(gradop[6][1])
      + SQR(gradop[7][1]) + SQR(gradop[8][1])
      + SQR(gradop[1][2]) + SQR(gradop[2][2])
      + SQR(gradop[3][2]) + SQR(gradop[4][2])
      + SQR(gradop[5][2]) + SQR(gradop[6][2])
      + SQR(gradop[7][2]) + SQR(gradop[8][2])
      + SQR(gradop[1][3]) + SQR(gradop[2][3])
      + SQR(gradop[3][3]) + SQR(gradop[4][3])
      + SQR(gradop[5][3]) + SQR(gradop[6][3])
      + SQR(gradop[7][3]) + SQR(gradop[8][3]) );
     
  return (double)sqrt(aspect);
  
}

/*!
  oddy of a hex

  General distortion measure based on left Cauchy-Green Tensor
*/
C_FUNC_DEF double v_hex_oddy( int /*num_nodes*/, double coordinates[][3] )
{
  
  double oddy = 0.0, current_oddy;
  VerdictVector xxi, xet, xze;
  
  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );
  
  xxi = calc_hex_efg(1, node_pos);
  xet = calc_hex_efg(2, node_pos);
  xze = calc_hex_efg(3, node_pos);
  
  current_oddy = oddy_comp( xxi, xet, xze);
  if ( current_oddy > oddy ) { oddy = current_oddy; }
  
  xxi.set(coordinates[1][0] - coordinates[0][0],
          coordinates[1][1] - coordinates[0][1],
          coordinates[1][2] - coordinates[0][2] );
  
  xet.set(coordinates[3][0] - coordinates[0][0],
          coordinates[3][1] - coordinates[0][1],
          coordinates[3][2] - coordinates[0][2] );
  
  xze.set(coordinates[4][0] - coordinates[0][0],
          coordinates[4][1] - coordinates[0][1],
          coordinates[4][2] - coordinates[0][2] );
  
  
  current_oddy = oddy_comp( xxi, xet, xze);
  if ( current_oddy > oddy ) { oddy = current_oddy; }
  
  xxi.set(coordinates[2][0] - coordinates[1][0],
          coordinates[2][1] - coordinates[1][1],
          coordinates[2][2] - coordinates[1][2] );
  
  xet.set(coordinates[0][0] - coordinates[1][0],
          coordinates[0][1] - coordinates[1][1],
          coordinates[0][2] - coordinates[1][2] );
  
  xze.set(coordinates[5][0] - coordinates[1][0],
          coordinates[5][1] - coordinates[1][1],
          coordinates[5][2] - coordinates[1][2] );
  
  
  current_oddy = oddy_comp( xxi, xet, xze);
  if ( current_oddy > oddy ) { oddy = current_oddy; }
  
  xxi.set(coordinates[3][0] - coordinates[2][0],
          coordinates[3][1] - coordinates[2][1],
          coordinates[3][2] - coordinates[2][2] );
  
  xet.set(coordinates[1][0] - coordinates[2][0],
          coordinates[1][1] - coordinates[2][1],
          coordinates[1][2] - coordinates[2][2] );
  
  xze.set(coordinates[6][0] - coordinates[2][0],
          coordinates[6][1] - coordinates[2][1],
          coordinates[6][2] - coordinates[2][2] );
  
  
  current_oddy = oddy_comp( xxi, xet, xze);
  if ( current_oddy > oddy ) { oddy = current_oddy; }
  
  xxi.set(coordinates[0][0] - coordinates[3][0],
          coordinates[0][1] - coordinates[3][1],
          coordinates[0][2] - coordinates[3][2] );
  
  xet.set(coordinates[2][0] - coordinates[3][0],
          coordinates[2][1] - coordinates[3][1],
          coordinates[2][2] - coordinates[3][2] );
  
  xze.set(coordinates[7][0] - coordinates[3][0],
          coordinates[7][1] - coordinates[3][1],
          coordinates[7][2] - coordinates[3][2] );
 
 
  current_oddy = oddy_comp( xxi, xet, xze);
  if ( current_oddy > oddy ) { oddy = current_oddy; }
  
  xxi.set(coordinates[7][0] - coordinates[4][0],
          coordinates[7][1] - coordinates[4][1],
          coordinates[7][2] - coordinates[4][2] );
  
  xet.set(coordinates[5][0] - coordinates[4][0],
          coordinates[5][1] - coordinates[4][1],
          coordinates[5][2] - coordinates[4][2] );
  
  xze.set(coordinates[0][0] - coordinates[4][0],
          coordinates[0][1] - coordinates[4][1],
          coordinates[0][2] - coordinates[4][2] );
  
  
  current_oddy = oddy_comp( xxi, xet, xze);
  if ( current_oddy > oddy ) { oddy = current_oddy; }
  
  xxi.set(coordinates[4][0] - coordinates[5][0],
          coordinates[4][1] - coordinates[5][1],
          coordinates[4][2] - coordinates[5][2] );
  
  xet.set(coordinates[6][0] - coordinates[5][0],
          coordinates[6][1] - coordinates[5][1],
          coordinates[6][2] - coordinates[5][2] );
  
  xze.set(coordinates[1][0] - coordinates[5][0],
          coordinates[1][1] - coordinates[5][1],
          coordinates[1][2] - coordinates[5][2] );
  
  
  current_oddy = oddy_comp( xxi, xet, xze);
  if ( current_oddy > oddy ) { oddy = current_oddy; }
  
  xxi.set(coordinates[5][0] - coordinates[6][0],
          coordinates[5][1] - coordinates[6][1],
          coordinates[5][2] - coordinates[6][2] );
  
  xet.set(coordinates[7][0] - coordinates[6][0],
          coordinates[7][1] - coordinates[6][1],
          coordinates[7][2] - coordinates[6][2] );
  
  xze.set(coordinates[2][0] - coordinates[6][0],
          coordinates[2][1] - coordinates[6][1],
          coordinates[2][2] - coordinates[6][2] );
  
  
  current_oddy = oddy_comp( xxi, xet, xze);
  if ( current_oddy > oddy ) { oddy = current_oddy; }
  
  xxi.set(coordinates[6][0] - coordinates[7][0],
          coordinates[6][1] - coordinates[7][1],
          coordinates[6][2] - coordinates[7][2] );
  
  xet.set(coordinates[4][0] - coordinates[7][0],
          coordinates[4][1] - coordinates[7][1],
          coordinates[4][2] - coordinates[7][2] );
  
  xze.set(coordinates[3][0] - coordinates[7][0],
          coordinates[3][1] - coordinates[7][1],
          coordinates[3][2] - coordinates[7][2] );
  
  
  current_oddy = oddy_comp( xxi, xet, xze);
  if ( current_oddy > oddy ) { oddy = current_oddy; }
  
  if( oddy > 0 )
    return (double) VERDICT_MIN( oddy, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( oddy, -VERDICT_DBL_MAX );

}

/*!
   the average Frobenius aspect of a hex

   NB (P. Pebay 01/20/07): 
     this metric is calculated by averaging the 8 Frobenius aspects at
     each corner of the hex, when the reference corner is right isosceles.
*/
C_FUNC_DEF double v_hex_med_aspect_frobenius( int /*num_nodes*/, double coordinates[][3] )
{

  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );

  double med_aspect_frobenius = 0.; 
  VerdictVector xxi, xet, xze;

  // J(0,0,0):

  xxi = node_pos[1] - node_pos[0];
  xet = node_pos[3] - node_pos[0];
  xze = node_pos[4] - node_pos[0];

  med_aspect_frobenius += condition_comp( xxi, xet, xze );
  // J(1,0,0):
  
  xxi = node_pos[2] - node_pos[1];
  xet = node_pos[0] - node_pos[1];
  xze = node_pos[5] - node_pos[1];

  med_aspect_frobenius += condition_comp( xxi, xet, xze );
  // J(1,1,0):
  
  xxi = node_pos[3] - node_pos[2];
  xet = node_pos[1] - node_pos[2];
  xze = node_pos[6] - node_pos[2];

  med_aspect_frobenius += condition_comp( xxi, xet, xze );
  // J(0,1,0):
  
  xxi = node_pos[0] - node_pos[3];
  xet = node_pos[2] - node_pos[3];
  xze = node_pos[7] - node_pos[3];

  med_aspect_frobenius += condition_comp( xxi, xet, xze );
  // J(0,0,1):

  xxi = node_pos[7] - node_pos[4];
  xet = node_pos[5] - node_pos[4];
  xze = node_pos[0] - node_pos[4];

  med_aspect_frobenius += condition_comp( xxi, xet, xze );
  // J(1,0,1):

  xxi = node_pos[4] - node_pos[5];
  xet = node_pos[6] - node_pos[5];
  xze = node_pos[1] - node_pos[5];

  med_aspect_frobenius += condition_comp( xxi, xet, xze );
  // J(1,1,1):

  xxi = node_pos[5] - node_pos[6];
  xet = node_pos[7] - node_pos[6];
  xze = node_pos[2] - node_pos[6];

  med_aspect_frobenius += condition_comp( xxi, xet, xze );
  // J(1,1,1):

  xxi = node_pos[6] - node_pos[7];
  xet = node_pos[4] - node_pos[7];
  xze = node_pos[3] - node_pos[7];

  med_aspect_frobenius += condition_comp( xxi, xet, xze );
  med_aspect_frobenius /= 24.;

  if( med_aspect_frobenius > 0 )
    return (double) VERDICT_MIN( med_aspect_frobenius, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( med_aspect_frobenius, -VERDICT_DBL_MAX );

}

/*!
  maximum Frobenius condition number of a hex

  Maximum Frobenius condition number of the Jacobian matrix at 8 corners
   NB (P. Pebay 01/25/07): 
     this metric is calculated by taking the maximum of the 8 Frobenius aspects at
     each corner of the hex, when the reference corner is right isosceles.
*/
C_FUNC_DEF double v_hex_max_aspect_frobenius( int /*num_nodes*/, double coordinates[][3] )
{

  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );

  double condition = 0.0, current_condition; 
  VerdictVector xxi, xet, xze;

  xxi = calc_hex_efg(1, node_pos );
  xet = calc_hex_efg(2, node_pos );
  xze = calc_hex_efg(3, node_pos );

  current_condition = condition_comp( xxi, xet, xze );
  if ( current_condition > condition ) { condition = current_condition; }

        // J(0,0,0):

  xxi = node_pos[1] - node_pos[0];
  xet = node_pos[3] - node_pos[0];
  xze = node_pos[4] - node_pos[0];

  current_condition = condition_comp( xxi, xet, xze );
  if ( current_condition > condition ) { condition = current_condition; }

        // J(1,0,0):
  
  xxi = node_pos[2] - node_pos[1];
  xet = node_pos[0] - node_pos[1];
  xze = node_pos[5] - node_pos[1];

  current_condition = condition_comp( xxi, xet, xze );
  if ( current_condition > condition ) { condition = current_condition; }

  // J(1,1,0):
  
  xxi = node_pos[3] - node_pos[2];
  xet = node_pos[1] - node_pos[2];
  xze = node_pos[6] - node_pos[2];

  current_condition = condition_comp( xxi, xet, xze );
  if ( current_condition > condition ) { condition = current_condition; }

  // J(0,1,0):
  
  xxi = node_pos[0] - node_pos[3];
  xet = node_pos[2] - node_pos[3];
  xze = node_pos[7] - node_pos[3];

  current_condition = condition_comp( xxi, xet, xze );
  if ( current_condition > condition ) { condition = current_condition; }

  // J(0,0,1):

  xxi = node_pos[7] - node_pos[4];
  xet = node_pos[5] - node_pos[4];
  xze = node_pos[0] - node_pos[4];

  current_condition = condition_comp( xxi, xet, xze );
  if ( current_condition > condition ) { condition = current_condition; }

  // J(1,0,1):

  xxi = node_pos[4] - node_pos[5];
  xet = node_pos[6] - node_pos[5];
  xze = node_pos[1] - node_pos[5];

  current_condition = condition_comp( xxi, xet, xze );
  if ( current_condition > condition ) { condition = current_condition; }

  // J(1,1,1):

  xxi = node_pos[5] - node_pos[6];
  xet = node_pos[7] - node_pos[6];
  xze = node_pos[2] - node_pos[6];

  current_condition = condition_comp( xxi, xet, xze );
  if ( current_condition > condition ) { condition = current_condition; }

  // J(1,1,1):

  xxi = node_pos[6] - node_pos[7];
  xet = node_pos[4] - node_pos[7];
  xze = node_pos[3] - node_pos[7];

  current_condition = condition_comp( xxi, xet, xze );
  if ( current_condition > condition ) { condition = current_condition; }

  condition /= 3.0;

  if( condition > 0 )
    return (double) VERDICT_MIN( condition, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( condition, -VERDICT_DBL_MAX );

}

/*!
  The maximum Frobenius condition of a hex, a.k.a. condition
  NB (P. Pebay 01/25/07): 
     this method is maintained for backwards compatibility only.
     It will become deprecated at some point.

*/
C_FUNC_DEF double v_hex_condition( int /*num_nodes*/, double coordinates[][3] )
{

  return v_hex_max_aspect_frobenius(8, coordinates);
}

/*!
  jacobian of a hex

  Minimum pointwise volume of local map at 8 corners & center of hex
*/
C_FUNC_DEF double v_hex_jacobian( int /*num_nodes*/, double coordinates[][3] )
{
  
  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );

  double jacobian = VERDICT_DBL_MAX;
  double current_jacobian; 
  VerdictVector xxi, xet, xze;

  xxi = calc_hex_efg(1, node_pos );
  xet = calc_hex_efg(2, node_pos );
  xze = calc_hex_efg(3, node_pos );


  current_jacobian = xxi % (xet * xze) / 64.0;
  if ( current_jacobian < jacobian ) { jacobian = current_jacobian; }

  // J(0,0,0):

  xxi = node_pos[1] - node_pos[0];
  xet = node_pos[3] - node_pos[0];
  xze = node_pos[4] - node_pos[0];

  current_jacobian = xxi % (xet * xze);
  if ( current_jacobian < jacobian ) { jacobian = current_jacobian; }

  // J(1,0,0):
  
  xxi = node_pos[2] - node_pos[1];
  xet = node_pos[0] - node_pos[1];
  xze = node_pos[5] - node_pos[1];

  current_jacobian = xxi % (xet * xze);
  if ( current_jacobian < jacobian ) { jacobian = current_jacobian; }

  // J(1,1,0):
  
  xxi = node_pos[3] - node_pos[2];
  xet = node_pos[1] - node_pos[2];
  xze = node_pos[6] - node_pos[2];

  current_jacobian = xxi % (xet * xze);
  if ( current_jacobian < jacobian ) { jacobian = current_jacobian; }

  // J(0,1,0):
  
  xxi = node_pos[0] - node_pos[3];
  xet = node_pos[2] - node_pos[3];
  xze = node_pos[7] - node_pos[3];

  current_jacobian = xxi % (xet * xze);
  if ( current_jacobian < jacobian ) { jacobian = current_jacobian; }

  // J(0,0,1):

  xxi = node_pos[7] - node_pos[4];
  xet = node_pos[5] - node_pos[4];
  xze = node_pos[0] - node_pos[4];

  current_jacobian = xxi % (xet * xze);
  if ( current_jacobian < jacobian ) { jacobian = current_jacobian; }

  // J(1,0,1):

  xxi = node_pos[4] - node_pos[5];
  xet = node_pos[6] - node_pos[5];
  xze = node_pos[1] - node_pos[5];

  current_jacobian = xxi % (xet * xze);
  if ( current_jacobian < jacobian ) { jacobian = current_jacobian; }

  // J(1,1,1):

  xxi = node_pos[5] - node_pos[6];
  xet = node_pos[7] - node_pos[6];
  xze = node_pos[2] - node_pos[6];

  current_jacobian = xxi % (xet * xze);
  if ( current_jacobian < jacobian ) { jacobian = current_jacobian; }

  // J(0,1,1):

  xxi = node_pos[6] - node_pos[7];
  xet = node_pos[4] - node_pos[7];
  xze = node_pos[3] - node_pos[7];

  current_jacobian = xxi % (xet * xze);
  if ( current_jacobian < jacobian ) { jacobian = current_jacobian; }

  if( jacobian > 0 )
    return (double) VERDICT_MIN( jacobian, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( jacobian, -VERDICT_DBL_MAX );
}

/*!
  scaled jacobian of a hex

  Minimum Jacobian divided by the lengths of the 3 edge vectors
*/
C_FUNC_DEF double v_hex_scaled_jacobian( int /*num_nodes*/, double coordinates[][3] )
{

  double jacobi, min_norm_jac = VERDICT_DBL_MAX;  
  //double min_jacobi = VERDICT_DBL_MAX;
  double temp_norm_jac, lengths;
  double len1_sq, len2_sq, len3_sq; 
  VerdictVector xxi, xet, xze;

  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );

  xxi = calc_hex_efg(1, node_pos );
  xet = calc_hex_efg(2, node_pos );
  xze = calc_hex_efg(3, node_pos );

  jacobi = xxi % ( xet * xze );
  //if( jacobi < min_jacobi) { min_jacobi = jacobi; }

  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return (double) VERDICT_DBL_MAX;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  temp_norm_jac = jacobi / lengths;
  
  if( temp_norm_jac < min_norm_jac)
    min_norm_jac = temp_norm_jac;  
  else  
    temp_norm_jac = jacobi;

  // J(0,0,0):

  xxi = node_pos[1] - node_pos[0];
  xet = node_pos[3] - node_pos[0];
  xze = node_pos[4] - node_pos[0];

  jacobi = xxi % ( xet * xze );
  //if( jacobi < min_jacobi ) { min_jacobi = jacobi; }

  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return (double) VERDICT_DBL_MAX;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  temp_norm_jac = jacobi / lengths;
  if( temp_norm_jac < min_norm_jac)
    min_norm_jac = temp_norm_jac;  
  else  
    temp_norm_jac = jacobi;


  // J(1,0,0):
  
  xxi = node_pos[2] - node_pos[1];
  xet = node_pos[0] - node_pos[1];
  xze = node_pos[5] - node_pos[1];

  jacobi = xxi % ( xet * xze );
  //if( jacobi < min_jacobi ) { min_jacobi = jacobi; }
  
  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return (double) VERDICT_DBL_MAX;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  temp_norm_jac = jacobi / lengths;
  if( temp_norm_jac < min_norm_jac)
    min_norm_jac = temp_norm_jac;  
  else  
    temp_norm_jac = jacobi;

  // J(1,1,0):
  
  xxi = node_pos[3] - node_pos[2];
  xet = node_pos[1] - node_pos[2];
  xze = node_pos[6] - node_pos[2];

  jacobi = xxi % ( xet * xze );
  //if( jacobi < min_jacobi ) { min_jacobi = jacobi; }
  
  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return (double) VERDICT_DBL_MAX;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  temp_norm_jac = jacobi / lengths;
  if( temp_norm_jac < min_norm_jac)
    min_norm_jac = temp_norm_jac;  
  else  
    temp_norm_jac = jacobi;

  // J(0,1,0):
  
  xxi = node_pos[0] - node_pos[3];
  xet = node_pos[2] - node_pos[3];
  xze = node_pos[7] - node_pos[3];

  jacobi = xxi % ( xet * xze );
  //if( jacobi < min_jacobi ) { min_jacobi = jacobi; }
  
  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return (double) VERDICT_DBL_MAX;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  temp_norm_jac = jacobi / lengths;
  if( temp_norm_jac < min_norm_jac)
    min_norm_jac = temp_norm_jac;  
  else  
    temp_norm_jac = jacobi;

  // J(0,0,1):

  xxi = node_pos[7] - node_pos[4];
  xet = node_pos[5] - node_pos[4];
  xze = node_pos[0] - node_pos[4];

  jacobi = xxi % ( xet * xze );
  //if( jacobi < min_jacobi ) { min_jacobi = jacobi; }
  
  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return (double) VERDICT_DBL_MAX;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  temp_norm_jac = jacobi / lengths;
  if( temp_norm_jac < min_norm_jac)
    min_norm_jac = temp_norm_jac;  
  else 
    temp_norm_jac = jacobi;

  // J(1,0,1):

  xxi = node_pos[4] - node_pos[5];
  xet = node_pos[6] - node_pos[5];
  xze = node_pos[1] - node_pos[5];

  jacobi = xxi % ( xet * xze );
  //if( jacobi < min_jacobi ) { min_jacobi = jacobi; }
  
  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return (double) VERDICT_DBL_MAX;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  temp_norm_jac = jacobi / lengths;
  if( temp_norm_jac < min_norm_jac)
    min_norm_jac = temp_norm_jac;  
  else
    temp_norm_jac = jacobi;

  // J(1,1,1):

  xxi = node_pos[5] - node_pos[6];
  xet = node_pos[7] - node_pos[6];
  xze = node_pos[2] - node_pos[6];

  jacobi = xxi % ( xet * xze );
  //if( jacobi < min_jacobi ) { min_jacobi = jacobi; }
  
  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return (double) VERDICT_DBL_MAX;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  temp_norm_jac = jacobi / lengths;
  if( temp_norm_jac < min_norm_jac)
    min_norm_jac = temp_norm_jac;  
  else
    temp_norm_jac = jacobi;

  // J(0,1,1):

  xxi = node_pos[6] - node_pos[7];
  xet = node_pos[4] - node_pos[7];
  xze = node_pos[3] - node_pos[7];

  jacobi = xxi % ( xet * xze );
  //if( jacobi < min_jacobi ) { min_jacobi = jacobi; }
  
  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return (double) VERDICT_DBL_MAX;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  temp_norm_jac = jacobi / lengths;
  if( temp_norm_jac < min_norm_jac)
    min_norm_jac = temp_norm_jac;  
  //else
    //temp_norm_jac = jacobi;

  if( min_norm_jac> 0 )
    return (double) VERDICT_MIN( min_norm_jac, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( min_norm_jac, -VERDICT_DBL_MAX );
}

/*!
  shear of a hex
  
  3/Condition number of Jacobian Skew matrix
*/
C_FUNC_DEF double v_hex_shear( int /*num_nodes*/, double coordinates[][3] )
{

  double shear;
  double min_shear = 1.0; 
  VerdictVector xxi, xet, xze;
  double det, len1_sq, len2_sq, len3_sq, lengths;


  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );

  // J(0,0,0):

  xxi = node_pos[1] - node_pos[0];
  xet = node_pos[3] - node_pos[0];
  xze = node_pos[4] - node_pos[0];

  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return 0;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  det = xxi % (xet * xze);
  if ( det < VERDICT_DBL_MIN ) { return 0; }

  shear = det / lengths;
  min_shear = VERDICT_MIN( shear, min_shear );


  // J(1,0,0):
  
  xxi = node_pos[2] - node_pos[1];
  xet = node_pos[0] - node_pos[1];
  xze = node_pos[5] - node_pos[1];

  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return 0;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  det = xxi % (xet * xze);
  if ( det < VERDICT_DBL_MIN ) { return 0; }

  shear = det / lengths; 
  min_shear = VERDICT_MIN( shear, min_shear );


  // J(1,1,0):
  
  xxi = node_pos[3] - node_pos[2];
  xet = node_pos[1] - node_pos[2];
  xze = node_pos[6] - node_pos[2];

  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return 0;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  det = xxi % (xet * xze);
  if ( det < VERDICT_DBL_MIN ) { return 0; }

  shear = det / lengths; 
  min_shear = VERDICT_MIN( shear, min_shear );


  // J(0,1,0):
  
  xxi = node_pos[0] - node_pos[3];
  xet = node_pos[2] - node_pos[3];
  xze = node_pos[7] - node_pos[3];

  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return 0;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  det = xxi % (xet * xze);
  if ( det < VERDICT_DBL_MIN ) { return 0; }

  shear = det / lengths; 
  min_shear = VERDICT_MIN( shear, min_shear );


  // J(0,0,1):

  xxi = node_pos[7] - node_pos[4];
  xet = node_pos[5] - node_pos[4];
  xze = node_pos[0] - node_pos[4];

  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return 0;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  det = xxi % (xet * xze);
  if ( det < VERDICT_DBL_MIN ) { return 0; }

  shear = det / lengths; 
  min_shear = VERDICT_MIN( shear, min_shear );


  // J(1,0,1):

  xxi = node_pos[4] - node_pos[5];
  xet = node_pos[6] - node_pos[5];
  xze = node_pos[1] - node_pos[5];

  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return 0;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  det = xxi % (xet * xze);
  if ( det < VERDICT_DBL_MIN ) { return 0; }

  shear = det / lengths; 
  min_shear = VERDICT_MIN( shear, min_shear );


  // J(1,1,1):

  xxi = node_pos[5] - node_pos[6];
  xet = node_pos[7] - node_pos[6];
  xze = node_pos[2] - node_pos[6];

  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return 0;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  det = xxi % (xet * xze);
  if ( det < VERDICT_DBL_MIN ) { return 0; }

  shear = det / lengths; 
  min_shear = VERDICT_MIN( shear, min_shear );


  // J(0,1,1):

  xxi = node_pos[6] - node_pos[7];
  xet = node_pos[4] - node_pos[7];
  xze = node_pos[3] - node_pos[7];

  len1_sq = xxi.length_squared();
  len2_sq = xet.length_squared();
  len3_sq = xze.length_squared();

  if( len1_sq <= VERDICT_DBL_MIN || len2_sq <= VERDICT_DBL_MIN ||
      len3_sq <= VERDICT_DBL_MIN)
    return 0;

  lengths = sqrt( len1_sq * len2_sq * len3_sq );
  det = xxi % (xet * xze);
  if ( det < VERDICT_DBL_MIN ) { return 0; }

  shear = det / lengths; 
  min_shear = VERDICT_MIN( shear, min_shear );

  if( min_shear <= VERDICT_DBL_MIN )
    min_shear = 0;
  
  if( min_shear > 0 )
    return (double) VERDICT_MIN( min_shear, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( min_shear, -VERDICT_DBL_MAX );
}

/*!
  shape of a hex

  3/Condition number of weighted Jacobian matrix
*/
C_FUNC_DEF double v_hex_shape( int /*num_nodes*/, double coordinates[][3] )
{


  double det, shape;
  double min_shape = 1.0; 
  static const double two_thirds = 2.0/3.0;

  VerdictVector xxi, xet, xze;

  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );


  // J(0,0,0):

  xxi = node_pos[1] - node_pos[0];
  xet = node_pos[3] - node_pos[0];
  xze = node_pos[4] - node_pos[0];

  det = xxi % (xet * xze);
  if ( det > VERDICT_DBL_MIN ) 
    shape = 3 * pow( det, two_thirds) / (xxi%xxi + xet%xet + xze%xze);
  else
    return 0;
  
  if( shape < min_shape ) { min_shape = shape; }
  

  // J(1,0,0):
  
  xxi = node_pos[2] - node_pos[1];
  xet = node_pos[0] - node_pos[1];
  xze = node_pos[5] - node_pos[1];

  det = xxi % (xet * xze);
  if ( det > VERDICT_DBL_MIN ) 
    shape = 3 * pow( det, two_thirds) / (xxi%xxi + xet%xet + xze%xze);
  else
    return 0;
  
  if( shape < min_shape ) { min_shape = shape; }
  

  // J(1,1,0):
  
  xxi = node_pos[3] - node_pos[2];
  xet = node_pos[1] - node_pos[2];
  xze = node_pos[6] - node_pos[2];

  det = xxi % (xet * xze);
  if ( det > VERDICT_DBL_MIN ) 
    shape = 3 * pow( det, two_thirds) / (xxi%xxi + xet%xet + xze%xze);
  else
    return 0;
  
  if( shape < min_shape ) { min_shape = shape; }
  

  // J(0,1,0):
  
  xxi = node_pos[0] - node_pos[3];
  xet = node_pos[2] - node_pos[3];
  xze = node_pos[7] - node_pos[3];

  det = xxi % (xet * xze);
  if ( det > VERDICT_DBL_MIN ) 
    shape = 3 * pow( det, two_thirds) / (xxi%xxi + xet%xet + xze%xze);
  else
    return 0;
  
  if( shape < min_shape ) { min_shape = shape; }
  

  // J(0,0,1):

  xxi = node_pos[7] - node_pos[4];
  xet = node_pos[5] - node_pos[4];
  xze = node_pos[0] - node_pos[4];

  det = xxi % (xet * xze);
  if ( det > VERDICT_DBL_MIN ) 
    shape = 3 * pow( det, two_thirds) / (xxi%xxi + xet%xet + xze%xze);
  else
    return 0;
  
  if( shape < min_shape ) { min_shape = shape; }
  

  // J(1,0,1):

  xxi = node_pos[4] - node_pos[5];
  xet = node_pos[6] - node_pos[5];
  xze = node_pos[1] - node_pos[5];

  det = xxi % (xet * xze);
  if ( det > VERDICT_DBL_MIN ) 
    shape = 3 * pow( det, two_thirds) / (xxi%xxi + xet%xet + xze%xze);
  else
    return 0;
  
  if( shape < min_shape ) { min_shape = shape; }
  

  // J(1,1,1):

  xxi = node_pos[5] - node_pos[6];
  xet = node_pos[7] - node_pos[6];
  xze = node_pos[2] - node_pos[6];

  det = xxi % (xet * xze);
  if ( det > VERDICT_DBL_MIN ) 
    shape = 3 * pow( det, two_thirds) / (xxi%xxi + xet%xet + xze%xze);
  else
    return 0;
  
  if( shape < min_shape ) { min_shape = shape; }
  

  // J(1,1,1):

  xxi = node_pos[6] - node_pos[7];
  xet = node_pos[4] - node_pos[7];
  xze = node_pos[3] - node_pos[7];

  det = xxi % (xet * xze);
  if ( det > VERDICT_DBL_MIN ) 
    shape = 3 * pow( det, two_thirds) / (xxi%xxi + xet%xet + xze%xze);
  else
    return 0;
  
  if( shape < min_shape ) { min_shape = shape; }

  if( min_shape <= VERDICT_DBL_MIN )
    min_shape = 0;

  if( min_shape > 0 )
    return (double) VERDICT_MIN( min_shape, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( min_shape, -VERDICT_DBL_MAX );
}

/*!
  relative size of a hex

  Min( J, 1/J ), where J is determinant of weighted Jacobian matrix
*/
C_FUNC_DEF double v_hex_relative_size_squared( int /*num_nodes*/, double coordinates[][3] )
{
  double size = 0;
  double tau; 

  VerdictVector xxi, xet, xze;
  double det, det_sum = 0;

  v_hex_get_weight( xxi, xet, xze );
 
  //This is the average relative size 
  double detw = xxi % (xet * xze);

  if ( detw < VERDICT_DBL_MIN ) 
    return 0; 

  VerdictVector node_pos[8];
  make_hex_nodes ( coordinates, node_pos );

  // J(0,0,0):

  xxi = node_pos[1] - node_pos[0];
  xet = node_pos[3] - node_pos[0];
  xze = node_pos[4] - node_pos[0];
  
  det = xxi % (xet * xze);
  det_sum += det;  


  // J(1,0,0):
  
  xxi = node_pos[2] - node_pos[1];
  xet = node_pos[0] - node_pos[1];
  xze = node_pos[5] - node_pos[1];

  det = xxi % (xet * xze);
  det_sum += det;  


  // J(0,1,0):
  
  xxi = node_pos[3] - node_pos[2];
  xet = node_pos[1] - node_pos[2];
  xze = node_pos[6] - node_pos[2];

  det = xxi % (xet * xze);
  det_sum += det;  

  // J(1,1,0):
  
  xxi = node_pos[0] - node_pos[3];
  xet = node_pos[2] - node_pos[3];
  xze = node_pos[7] - node_pos[3];

  det = xxi % (xet * xze);
  det_sum += det;  


  // J(0,1,0):

  xxi = node_pos[7] - node_pos[4];
  xet = node_pos[5] - node_pos[4];
  xze = node_pos[0] - node_pos[4];

  det = xxi % (xet * xze);
  det_sum += det;  


  // J(1,0,1):

  xxi = node_pos[4] - node_pos[5];
  xet = node_pos[6] - node_pos[5];
  xze = node_pos[1] - node_pos[5];

  det = xxi % (xet * xze);
  det_sum += det;  


  // J(1,1,1):

  xxi = node_pos[5] - node_pos[6];
  xet = node_pos[7] - node_pos[6];
  xze = node_pos[2] - node_pos[6];

  det = xxi % (xet * xze);
  det_sum += det;  


  // J(1,1,1):

  xxi = node_pos[6] - node_pos[7];
  xet = node_pos[4] - node_pos[7];
  xze = node_pos[3] - node_pos[7];

  det = xxi % (xet * xze);
  det_sum += det;  


  if( det_sum > VERDICT_DBL_MIN )
  {
    tau = det_sum / ( 8*detw);

    tau = VERDICT_MIN( tau, 1.0/tau); 

    size = tau*tau; 
  }

  if( size > 0 )
    return (double) VERDICT_MIN( size, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( size, -VERDICT_DBL_MAX );
}

/*!
  shape and size of a hex

  Product of Shape and Relative Size
*/
C_FUNC_DEF double v_hex_shape_and_size( int num_nodes, double coordinates[][3] )
{
  double size = v_hex_relative_size_squared( num_nodes, coordinates );
  double shape = v_hex_shape( num_nodes, coordinates );

  double shape_size = size * shape;

  if( shape_size > 0 )
    return (double) VERDICT_MIN( shape_size, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( shape_size, -VERDICT_DBL_MAX );

}



/*!
  shear and size of a hex

  Product of Shear and Relative Size
*/
C_FUNC_DEF double v_hex_shear_and_size( int num_nodes, double coordinates[][3] )
{
  double size = v_hex_relative_size_squared( num_nodes, coordinates );
  double shear = v_hex_shear( num_nodes, coordinates );

  double shear_size = shear * size; 

  if( shear_size > 0 )
    return (double) VERDICT_MIN( shear_size, VERDICT_DBL_MAX );
  return (double) VERDICT_MAX( shear_size, -VERDICT_DBL_MAX );

}

/*!
  distortion of a hex
*/
C_FUNC_DEF double v_hex_distortion( int num_nodes, double coordinates[][3] )
{

   //use 2x2 gauss points for linear hex and 3x3 for 2nd order hex
   int number_of_gauss_points=0;
   if (num_nodes ==8)
      //2x2 quadrature rule
      number_of_gauss_points = 2;
   else if (num_nodes ==20)
      //3x3 quadrature rule
      number_of_gauss_points = 3;

   int number_dimension = 3;
   int total_number_of_gauss_points = number_of_gauss_points
      *number_of_gauss_points*number_of_gauss_points;
   double distortion = VERDICT_DBL_MAX;

   // maxTotalNumberGaussPoints =27, maxNumberNodes = 20
   // they are defined in GaussIntegration.hpp
   // This is used to make these arrays static.
   // I tried dynamically allocated arrays but the new and delete
   // was very expensive

   double shape_function[maxTotalNumberGaussPoints][maxNumberNodes];
   double dndy1[maxTotalNumberGaussPoints][maxNumberNodes];
   double dndy2[maxTotalNumberGaussPoints][maxNumberNodes];
   double dndy3[maxTotalNumberGaussPoints][maxNumberNodes];
   double weight[maxTotalNumberGaussPoints];


   //create an object of GaussIntegration
   GaussIntegration::initialize(number_of_gauss_points,num_nodes,number_dimension );
   GaussIntegration::calculate_shape_function_3d_hex();
   GaussIntegration::get_shape_func(shape_function[0], dndy1[0], dndy2[0], dndy3[0],weight);


   VerdictVector xxi, xet, xze, xin;

   double jacobian, minimum_jacobian;
   double element_volume =0.0;
   minimum_jacobian = VERDICT_DBL_MAX;
   // calculate element volume
   int ife, ja;
   for (ife=0;ife<total_number_of_gauss_points; ife++)
   {

      xxi.set(0.0,0.0,0.0);
      xet.set(0.0,0.0,0.0);
      xze.set(0.0,0.0,0.0);

      for (ja=0;ja<num_nodes;ja++)
      {
   xin.set(coordinates[ja][0], coordinates[ja][1], coordinates[ja][2]);
         xxi += dndy1[ife][ja]*xin;
         xet += dndy2[ife][ja]*xin;
         xze += dndy3[ife][ja]*xin;
      }

      jacobian = xxi % (xet * xze);
      if (minimum_jacobian > jacobian)
         minimum_jacobian = jacobian;

      element_volume += weight[ife]*jacobian;
      }

   // loop through all nodes
   double dndy1_at_node[maxNumberNodes][maxNumberNodes];
   double dndy2_at_node[maxNumberNodes][maxNumberNodes];
   double dndy3_at_node[maxNumberNodes][maxNumberNodes];

   GaussIntegration::calculate_derivative_at_nodes_3d( dndy1_at_node, dndy2_at_node, dndy3_at_node);
   int node_id;
   for (node_id=0;node_id<num_nodes; node_id++)
   {

      xxi.set(0.0,0.0,0.0);
      xet.set(0.0,0.0,0.0);
      xze.set(0.0,0.0,0.0);

      for (ja=0;ja<num_nodes;ja++)
      {
   xin.set(coordinates[ja][0], coordinates[ja][1], coordinates[ja][2]);
         xxi += dndy1_at_node[node_id][ja]*xin;
         xet += dndy2_at_node[node_id][ja]*xin;
         xze += dndy3_at_node[node_id][ja]*xin;
      }

      jacobian = xxi % (xet * xze);
      if (minimum_jacobian > jacobian)
         minimum_jacobian = jacobian;

      }
   distortion = minimum_jacobian/element_volume*8.;
   return (double)distortion;
}





/*
C_FUNC_DEF double hex_jac_normjac_oddy_cond( int choices[], 
                      double coordinates[][3],
                      double answers[4]  )
{

  //Define variables
  int i;
  
  double xxi[3], xet[3], xze[3];
  double norm_jacobian = 0.0, current_norm_jac = 0.0;
        double jacobian = 0.0, current_jacobian = 0.0;
  double oddy = 0.0, current_oddy = 0.0;  
  double condition = 0.0, current_condition = 0.0;


        for( i=0; i<3; i++)
          xxi[i] = calc_hex_efg( 2, i, coordinates );
        for( i=0; i<3; i++)
          xet[i] = calc_hex_efg( 3, i, coordinates );
        for( i=0; i<3; i++)
          xze[i] = calc_hex_efg( 6, i, coordinates );

  // norm jacobian and jacobian
  if( choices[0] || choices[1] )
  {
    current_jacobian = determinant( xxi, xet, xze  );
    current_norm_jac = normalize_jacobian( current_jacobian,
            xxi, xet, xze );
    
    if (current_norm_jac < norm_jacobian) { norm_jacobian = current_norm_jac; } 

    current_jacobian /= 64.0;
    if (current_jacobian < jacobian) { jacobian = current_jacobian; }
  }

  // oddy 
  if( choices[2] )
  {
    current_oddy = oddy_comp( xxi, xet, xze );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
  }

  // condition
  if( choices[3] )
  {
    current_condition = condition_comp( xxi, xet, xze );
    if ( current_condition > condition ) { condition = current_condition; }
  }


  for( i=0; i<3; i++)
  {
    xxi[i] = coordinates[1][i] - coordinates[0][i];
    xet[i] = coordinates[3][i] - coordinates[0][i];
    xze[i] = coordinates[4][i] - coordinates[0][i];
  } 

  // norm jacobian and jacobian
  if( choices[0] || choices[1] )
  {
    current_jacobian = determinant( xxi, xet, xze  );
    current_norm_jac = normalize_jacobian( current_jacobian,
            xxi, xet, xze );
    
    if (current_norm_jac < norm_jacobian) { norm_jacobian = current_norm_jac; } 
    if (current_jacobian < jacobian) { jacobian = current_jacobian; }
  }

  // oddy 
  if( choices[2] )
  {
    current_oddy = oddy_comp( xxi, xet, xze );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
  }

  // condition
  if( choices[3] )
  {
    current_condition = condition_comp( xxi, xet, xze );
    if ( current_condition > condition ) { condition = current_condition; }
  }

  
  for( i=0; i<3; i++)
  {
          xxi[i] = coordinates[1][i] - coordinates[0][i];
          xet[i] = coordinates[2][i] - coordinates[1][i];
          xze[i] = coordinates[5][i] - coordinates[1][i];
  }

  // norm jacobian and jacobian
  if( choices[0] || choices[1] )
  {
    current_jacobian = determinant( xxi,  xet, xze );
    current_norm_jac = normalize_jacobian( current_jacobian,
            xxi, xet, xze );
    
    if (current_norm_jac < norm_jacobian) { norm_jacobian = current_norm_jac; } 
    if (current_jacobian < jacobian) { jacobian = current_jacobian; }
  }

  // oddy 
  if( choices[2] )
  {
    current_oddy = oddy_comp( xxi, xet, xze );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
  }

  // condition
  if( choices[3] )
  {
    current_condition = condition_comp( xxi, xet, xze );
    if ( current_condition > condition ) { condition = current_condition; }
  }


  for( i=0; i<3; i++)
  {
          xxi[i] = coordinates[2][i] - coordinates[3][i];
          xet[i] = coordinates[3][i] - coordinates[0][i];
          xze[i] = coordinates[7][i] - coordinates[3][i];
  }

  // norm jacobian and jacobian
  if( choices[0] || choices[1] )
  {
    current_jacobian = determinant( xxi, xet, xze );
    current_norm_jac = normalize_jacobian( current_jacobian,
            xxi, xet, xze );
    
    if (current_norm_jac < norm_jacobian) { norm_jacobian = current_norm_jac; } 
    if (current_jacobian < jacobian) { jacobian = current_jacobian; }
  }

  // oddy 
  if( choices[2] )
  {
    current_oddy = oddy_comp( xxi, xet, xze );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
  }

  // condition
  if( choices[3] )
  {
    current_condition = condition_comp( xxi, xet, xze );
    if ( current_condition > condition ) { condition = current_condition; }
  }

  
  for( i=0; i<3; i++)
  {
          xxi[i] = coordinates[5][i] - coordinates[4][i];
          xet[i] = coordinates[7][i] - coordinates[4][i];
          xze[i] = coordinates[4][i] - coordinates[0][i];
  }  


  // norm jacobian and jacobian
  if( choices[0] || choices[1] )
  {
    current_jacobian = determinant( xxi, xet, xze );
    current_norm_jac = normalize_jacobian( current_jacobian,
            xxi, xet, xze );
    
    if (current_norm_jac < norm_jacobian) { norm_jacobian = current_norm_jac; } 
    if (current_jacobian < jacobian) { jacobian = current_jacobian; }
  }

  // oddy 
  if( choices[2] )
  {
    current_oddy = oddy_comp( xxi, xet, xze );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
  }

  // condition
  if( choices[3] )
  {
    current_condition = condition_comp( xxi, xet, xze );
    if ( current_condition > condition ) { condition = current_condition; }
  }

  
  for( i=0; i<3; i++)
  {
          xxi[i] = coordinates[2][i] - coordinates[3][i];
          xet[i] = coordinates[2][i] - coordinates[1][i];
          xze[i] = coordinates[6][i] - coordinates[2][i];
  }  

  // norm jacobian and jacobian
  if( choices[0] || choices[1] )
  {
    current_jacobian = determinant( xxi, xet, xze );
    current_norm_jac = normalize_jacobian( current_jacobian,
            xxi, xet, xze );
    
    if (current_norm_jac < norm_jacobian) { norm_jacobian = current_norm_jac; } 
    if (current_jacobian < jacobian) { jacobian = current_jacobian; }
  }

  // oddy 
  if( choices[2] )
  {
    current_oddy = oddy_comp( xxi, xet, xze );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
  }

  // condition
  if( choices[3] )
  {
    current_condition = condition_comp( xxi, xet, xze );
    if ( current_condition > condition ) { condition = current_condition; }
  }


  for( i=0; i<3; i++)
  {
          xxi[i] = coordinates[5][i] - coordinates[4][i];
          xet[i] = coordinates[6][i] - coordinates[5][i];
          xze[i] = coordinates[5][i] - coordinates[1][i];
  }


  // norm jacobian and jacobian
  if( choices[0] || choices[1] )
  {
    current_jacobian = determinant( xxi, xet, xze );
    current_norm_jac = normalize_jacobian( current_jacobian,
            xxi, xet, xze );
    
    if (current_norm_jac < norm_jacobian) { norm_jacobian = current_norm_jac; } 
    if (current_jacobian < jacobian) { jacobian = current_jacobian; }
  }

  // oddy 
  if( choices[2] )
  {
    current_oddy = oddy_comp( xxi, xet, xze );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
  }

  // condition
  if( choices[3] )
  {
    current_condition = condition_comp( xxi, xet, xze );
    if ( current_condition > condition ) { condition = current_condition; }
  }

    
  for( i=0; i<3; i++)
  {
          xxi[i] = coordinates[6][i] - coordinates[7][i];
          xet[i] = coordinates[7][i] - coordinates[4][i];
          xze[i] = coordinates[7][i] - coordinates[3][i];
  }


  // norm jacobian and jacobian
  if( choices[0] || choices[1] )
  {
    current_jacobian = determinant( xxi, xet, xze );
    current_norm_jac = normalize_jacobian( current_jacobian,
            xxi, xet, xze );
    
    if (current_norm_jac < norm_jacobian) { norm_jacobian = current_norm_jac; } 
    if (current_jacobian < jacobian) { jacobian = current_jacobian; }
  }

  // oddy 
  if( choices[2] )
  {
    current_oddy = oddy_comp( xxi, xet, xze );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
  }

  // condition
  if( choices[3] )
  {
    current_condition = condition_comp( xxi, xet, xze );
    if ( current_condition > condition ) { condition = current_condition; }
  }

  
  for( i=0; i<3; i++)
  {
          xxi[i] = coordinates[6][i] - coordinates[7][i];
          xet[i] = coordinates[6][i] - coordinates[5][i];
          xze[i] = coordinates[6][i] - coordinates[2][i];
  }

  // norm jacobian and jacobian
  if( choices[0] || choices[1] )
  {
    current_jacobian = determinant( xxi, xet, xze );
    current_norm_jac = normalize_jacobian( current_jacobian,
            xxi, xet, xze );
    
    if (current_norm_jac < norm_jacobian) { norm_jacobian = current_norm_jac; } 
    if (current_jacobian < jacobian) { jacobian = current_jacobian; }
  }

  // oddy 
  if( choices[2] )
  {
    current_oddy = oddy_comp( xxi, xet, xze );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
  }

  // condition
  if( choices[3] )
  {
    current_condition = condition_comp( xxi, xet, xze );
    if ( current_condition > condition ) { condition = current_condition; }

    condition /= 3.0;
  }

  
  answers[0] = jacobian;
  answers[1] = norm_jacobian;
  answers[2] = oddy;
  answers[3] = condition;

  return 1.0;

}
*/

/*!
  multiple quality metrics of a hex
*/
C_FUNC_DEF void v_hex_quality( int num_nodes, double coordinates[][3], 
  unsigned int metrics_request_flag, HexMetricVals *metric_vals )
{ 
  memset( metric_vals, 0, sizeof(HexMetricVals) );

  // max edge ratio, skew, taper, and volume
  if(metrics_request_flag & (V_HEX_MAX_EDGE_RATIO | V_HEX_SKEW | V_HEX_TAPER ) ) 
  {
    VerdictVector node_pos[8];
    make_hex_nodes ( coordinates, node_pos );
   
    VerdictVector efg1, efg2, efg3;
    efg1 = calc_hex_efg( 1, node_pos);
    efg2 = calc_hex_efg( 2, node_pos);
    efg3 = calc_hex_efg( 3, node_pos);

    if(metrics_request_flag & V_HEX_MAX_EDGE_RATIO)
    {
      double max_edge_ratio_12, max_edge_ratio_13, max_edge_ratio_23;

      double mag_efg1 = efg1.length();
      double mag_efg2 = efg2.length();
      double mag_efg3 = efg3.length();
      
      max_edge_ratio_12 = safe_ratio( VERDICT_MAX( mag_efg1, mag_efg2 ) , VERDICT_MIN( mag_efg1, mag_efg2 ) );
      max_edge_ratio_13 = safe_ratio( VERDICT_MAX( mag_efg1, mag_efg3 ) , VERDICT_MIN( mag_efg1, mag_efg3 ) );
      max_edge_ratio_23 = safe_ratio( VERDICT_MAX( mag_efg2, mag_efg3 ) , VERDICT_MIN( mag_efg2, mag_efg3 ) );

      metric_vals->max_edge_ratio = (double)VERDICT_MAX( max_edge_ratio_12, VERDICT_MAX( max_edge_ratio_13, max_edge_ratio_23 ) );
    }
    
    if(metrics_request_flag & V_HEX_SKEW)
    {
      
      VerdictVector vec1 = efg1;
      VerdictVector vec2 = efg2;
      VerdictVector vec3 = efg3;

      if( vec1.normalize() <= VERDICT_DBL_MIN ||
          vec2.normalize() <= VERDICT_DBL_MIN ||
          vec3.normalize() <= VERDICT_DBL_MIN  )
      {
        metric_vals->skew = (double)VERDICT_DBL_MAX;
      }
      else
      {
        double skewx = fabs(vec1 % vec2);
        double skewy = fabs(vec1 % vec3);
        double skewz = fabs(vec2 % vec3);

        metric_vals->skew = (double)(VERDICT_MAX( skewx, VERDICT_MAX( skewy, skewz ) ));
      }
    }
  
    if(metrics_request_flag & V_HEX_TAPER)
    {
      VerdictVector efg12 = calc_hex_efg( 12, node_pos);
      VerdictVector efg13 = calc_hex_efg( 13, node_pos);
      VerdictVector efg23 = calc_hex_efg( 23, node_pos);

      double taperx = fabs( safe_ratio( efg12.length(), VERDICT_MIN( efg1.length(), efg2.length())));
      double tapery = fabs( safe_ratio( efg13.length(), VERDICT_MIN( efg1.length(), efg3.length())));
      double taperz = fabs( safe_ratio( efg23.length(), VERDICT_MIN( efg2.length(), efg3.length())));
      
      metric_vals->taper = (double)VERDICT_MAX(taperx, VERDICT_MAX(tapery, taperz));  
    }
  }
  
  if(metrics_request_flag & V_HEX_VOLUME)
  {
    metric_vals->volume = v_hex_volume(8, coordinates ); 
  }

  if(metrics_request_flag & ( V_HEX_JACOBIAN | V_HEX_SCALED_JACOBIAN | 
        V_HEX_CONDITION |V_HEX_SHEAR | V_HEX_SHAPE | V_HEX_RELATIVE_SIZE_SQUARED 
        | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE |
        V_HEX_STRETCH ))
  {

    static const double two_thirds = 2.0/3.0;
    VerdictVector edges[12];
    // the length squares
    double length_squared[12];
    // make vectors from coordinates
    make_hex_edges(coordinates, edges);

    // calculate the length squares if we need to
    if(metrics_request_flag & ( V_HEX_JACOBIAN | V_HEX_SHEAR | V_HEX_SCALED_JACOBIAN | V_HEX_SHAPE | 
          V_HEX_SHAPE_AND_SIZE | V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHEAR_AND_SIZE | V_HEX_STRETCH))
      make_edge_length_squares(edges, length_squared);

    double jacobian = VERDICT_DBL_MAX, scaled_jacobian = VERDICT_DBL_MAX,
      condition = 0.0, shear=1.0, shape=1.0, oddy = 0.0;
    double current_jacobian, current_scaled_jacobian, current_condition, current_shape,
      detw=0, det_sum=0, current_oddy;
    VerdictBoolean rel_size_error = VERDICT_FALSE;
    
    VerdictVector xxi, xet, xze;

    // get weights if we need based on average size of a hex
    if(metrics_request_flag & (V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE ))
    {
      v_hex_get_weight(xxi, xet, xze);
      detw = xxi % (xet * xze);
      if(detw < VERDICT_DBL_MIN)
        rel_size_error = VERDICT_TRUE;
    }

    xxi = edges[0] - edges[2] + edges[4] - edges[6];
    xet = edges[1] - edges[3] + edges[5] - edges[7];
    xze = edges[8] + edges[9] + edges[10] + edges[11];

    current_jacobian = xxi % (xet * xze) / 64.0;
    if ( current_jacobian < jacobian ) 
      jacobian = current_jacobian;


    if(metrics_request_flag & ( V_HEX_SCALED_JACOBIAN | V_HEX_SHEAR | V_HEX_SHEAR_AND_SIZE ))
    {
      current_jacobian *= 64.0;
      current_scaled_jacobian = current_jacobian / 
        sqrt(xxi.length_squared() * xet.length_squared() * xze.length_squared());
      if(current_scaled_jacobian < scaled_jacobian)
        shear = scaled_jacobian = current_scaled_jacobian;
    }

    if(metrics_request_flag & V_HEX_CONDITION)
    {
      current_condition = condition_comp( xxi, xet, xze );
      if ( current_condition > condition ) { condition = current_condition; }
    }

    if(metrics_request_flag & V_HEX_ODDY)
    {
      current_oddy = oddy_comp( xxi, xet, xze );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
    }
    

    // J(0,0,0)
    current_jacobian = edges[0] % (-edges[3] * edges[8]);
    if ( current_jacobian < jacobian )
      jacobian = current_jacobian;

    if(metrics_request_flag & ( V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE ))
    {
      det_sum += current_jacobian;
    }
    
    if(metrics_request_flag & ( V_HEX_SCALED_JACOBIAN | V_HEX_SHEAR | V_HEX_SHEAR_AND_SIZE ))
    {
      if(length_squared[0] <= VERDICT_DBL_MIN || length_squared[3] <= VERDICT_DBL_MIN
         || length_squared[8] <= VERDICT_DBL_MIN)
      {
        current_scaled_jacobian = VERDICT_DBL_MAX;
      }
      else
      {
        current_scaled_jacobian = current_jacobian / 
          sqrt(length_squared[0] * length_squared[3] * length_squared[8]);
      }
      if(current_scaled_jacobian < scaled_jacobian)
        shear = scaled_jacobian = current_scaled_jacobian;
    }

    if(metrics_request_flag & V_HEX_CONDITION)
    {
      current_condition = condition_comp( edges[0], -edges[3], edges[8] );
      if ( current_condition > condition ) { condition = current_condition; }
    }

    if(metrics_request_flag & V_HEX_ODDY)
    {
      current_oddy = oddy_comp( edges[0], -edges[3], edges[8] );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
    }

    if(metrics_request_flag & ( V_HEX_SHAPE | V_HEX_SHAPE_AND_SIZE ))
    {
      if(current_jacobian > VERDICT_DBL_MIN)
        current_shape = 3 * pow( current_jacobian, two_thirds) / 
          (length_squared[0] + length_squared[3] + length_squared[8]);
      else
        current_shape = 0;

      if(current_shape < shape) { shape = current_shape; }

    }

    // J(1,0,0)
    current_jacobian = edges[1] % (-edges[0] * edges[9]);
    if ( current_jacobian < jacobian )
      jacobian = current_jacobian;
    
    if(metrics_request_flag & ( V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE ))
    {
      det_sum += current_jacobian;
    }
    
    if(metrics_request_flag & ( V_HEX_SCALED_JACOBIAN | V_HEX_SHEAR | V_HEX_SHEAR_AND_SIZE ))
    {
      if(length_squared[1] <= VERDICT_DBL_MIN || length_squared[0] <= VERDICT_DBL_MIN
         || length_squared[9] <= VERDICT_DBL_MIN)
      {
        current_scaled_jacobian = VERDICT_DBL_MAX;
      }
      else
      {
        current_scaled_jacobian = current_jacobian / 
          sqrt(length_squared[1] * length_squared[0] * length_squared[9]);
      }
      if(current_scaled_jacobian < scaled_jacobian)
        shear = scaled_jacobian = current_scaled_jacobian;
    }

    if(metrics_request_flag & V_HEX_CONDITION)
    {
      current_condition = condition_comp( edges[1], -edges[0], edges[9] );
      if ( current_condition > condition ) { condition = current_condition; }
    }

    if(metrics_request_flag & V_HEX_ODDY)
    {
      current_oddy = oddy_comp( edges[1], -edges[0], edges[9] );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
    }
    
    if(metrics_request_flag & ( V_HEX_SHAPE | V_HEX_SHAPE_AND_SIZE ))
    {
      if(current_jacobian > VERDICT_DBL_MIN)
        current_shape = 3 * pow( current_jacobian, two_thirds) / 
          (length_squared[1] + length_squared[0] + length_squared[9]);
      else
        current_shape = 0;

      if(current_shape < shape) { shape = current_shape; }

    }

    // J(1,1,0)
    current_jacobian = edges[2] % (-edges[1] * edges[10]);
    if ( current_jacobian < jacobian )
      jacobian = current_jacobian;
    
    if(metrics_request_flag & ( V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE ))
    {
      det_sum += current_jacobian;
    }
    
    if(metrics_request_flag & ( V_HEX_SCALED_JACOBIAN | V_HEX_SHEAR | V_HEX_SHEAR_AND_SIZE ))
    {
      if(length_squared[2] <= VERDICT_DBL_MIN || length_squared[1] <= VERDICT_DBL_MIN
         || length_squared[10] <= VERDICT_DBL_MIN)
      {
        current_scaled_jacobian = VERDICT_DBL_MAX;
      }
      else
      {
        current_scaled_jacobian = current_jacobian / 
          sqrt(length_squared[2] * length_squared[1] * length_squared[10]);
      }
      if(current_scaled_jacobian < scaled_jacobian)
        shear = scaled_jacobian = current_scaled_jacobian;
    }
    
    if(metrics_request_flag & V_HEX_CONDITION)
    {
      current_condition = condition_comp( edges[2], -edges[1], edges[10] );
      if ( current_condition > condition ) { condition = current_condition; }
    }
    
    if(metrics_request_flag & V_HEX_ODDY)
    {
      current_oddy = oddy_comp( edges[2], -edges[1], edges[10] );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
    }

    if(metrics_request_flag & ( V_HEX_SHAPE | V_HEX_SHAPE_AND_SIZE ))
    {
      if(current_jacobian > VERDICT_DBL_MIN)
        current_shape = 3 * pow( current_jacobian, two_thirds) / 
          (length_squared[2] + length_squared[1] + length_squared[10]);
      else
        current_shape = 0;

      if(current_shape < shape) { shape = current_shape; }

    }


    // J(0,1,0)
    current_jacobian = edges[3] % (-edges[2] * edges[11]);
    if ( current_jacobian < jacobian )
      jacobian = current_jacobian;
    
    if(metrics_request_flag & ( V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE ))
    {
      det_sum += current_jacobian;
    }
    
    if(metrics_request_flag & ( V_HEX_SCALED_JACOBIAN | V_HEX_SHEAR | V_HEX_SHEAR_AND_SIZE ))
    {
      if(length_squared[3] <= VERDICT_DBL_MIN || length_squared[2] <= VERDICT_DBL_MIN
         || length_squared[11] <= VERDICT_DBL_MIN)
      {
        current_scaled_jacobian = VERDICT_DBL_MAX;
      }
      else
      {
        current_scaled_jacobian = current_jacobian / 
          sqrt(length_squared[3] * length_squared[2] * length_squared[11]);
      }
      if(current_scaled_jacobian < scaled_jacobian)
        shear = scaled_jacobian = current_scaled_jacobian;
    }

    if(metrics_request_flag & V_HEX_CONDITION)
    {
      current_condition = condition_comp( edges[3], -edges[2], edges[11] );
      if ( current_condition > condition ) { condition = current_condition; }
    }
    
    if(metrics_request_flag & V_HEX_ODDY)
    {
      current_oddy = oddy_comp( edges[3], -edges[2], edges[11] );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
    }

    if(metrics_request_flag & ( V_HEX_SHAPE | V_HEX_SHAPE_AND_SIZE ))
    {
      if(current_jacobian > VERDICT_DBL_MIN)
        current_shape = 3 * pow( current_jacobian, two_thirds) / 
          (length_squared[3] + length_squared[2] + length_squared[11]);
      else
        current_shape = 0;

      if(current_shape < shape) { shape = current_shape; }

    }

    // J(0,0,1)
    current_jacobian = edges[4] % (-edges[8] * -edges[7]);
    if ( current_jacobian < jacobian )
      jacobian = current_jacobian;
    
    if(metrics_request_flag & ( V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE ))
    {
      det_sum += current_jacobian;
    }
    
    if(metrics_request_flag & ( V_HEX_SCALED_JACOBIAN | V_HEX_SHEAR | V_HEX_SHEAR_AND_SIZE ))
    {
      if(length_squared[4] <= VERDICT_DBL_MIN || length_squared[8] <= VERDICT_DBL_MIN
         || length_squared[7] <= VERDICT_DBL_MIN)
      {
        current_scaled_jacobian = VERDICT_DBL_MAX;
      }
      else
      {
        current_scaled_jacobian = current_jacobian / 
          sqrt(length_squared[4] * length_squared[8] * length_squared[7]);
      }
      if(current_scaled_jacobian < scaled_jacobian)
        shear = scaled_jacobian = current_scaled_jacobian;
    }

    if(metrics_request_flag & V_HEX_CONDITION)
    {
      current_condition = condition_comp( edges[4], -edges[8], -edges[7] );
      if ( current_condition > condition ) { condition = current_condition; }
    }
    
    if(metrics_request_flag & V_HEX_ODDY)
    {
      current_oddy = oddy_comp( edges[4], -edges[8], -edges[7] );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
    }

    if(metrics_request_flag & ( V_HEX_SHAPE | V_HEX_SHAPE_AND_SIZE ))
    {
      if(current_jacobian > VERDICT_DBL_MIN)
        current_shape = 3 * pow( current_jacobian, two_thirds) / 
          (length_squared[4] + length_squared[8] + length_squared[7]);
      else
        current_shape = 0;

      if(current_shape < shape) { shape = current_shape; }

    }

    // J(1,0,1)
    current_jacobian = -edges[4] % (edges[5] * -edges[9]);
    if ( current_jacobian < jacobian )
      jacobian = current_jacobian;
    
    if(metrics_request_flag & ( V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE ))
    {
      det_sum += current_jacobian;
    }
    
    if(metrics_request_flag & ( V_HEX_SCALED_JACOBIAN | V_HEX_SHEAR | V_HEX_SHEAR_AND_SIZE ))
    {
      if(length_squared[4] <= VERDICT_DBL_MIN || length_squared[5] <= VERDICT_DBL_MIN
         || length_squared[9] <= VERDICT_DBL_MIN)
      {
        current_scaled_jacobian = VERDICT_DBL_MAX;
      }
      else
      {
        current_scaled_jacobian = current_jacobian / 
          sqrt(length_squared[4] * length_squared[5] * length_squared[9]);
      }
      if(current_scaled_jacobian < scaled_jacobian)
        shear = scaled_jacobian = current_scaled_jacobian;
    }
    
    if(metrics_request_flag & V_HEX_CONDITION)
    {
      current_condition = condition_comp( -edges[4], edges[5], -edges[9] );
      if ( current_condition > condition ) { condition = current_condition; }
    }
    
    if(metrics_request_flag & V_HEX_ODDY)
    {
      current_oddy = oddy_comp( -edges[4], edges[5], -edges[9] );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
    }

    if(metrics_request_flag & ( V_HEX_SHAPE | V_HEX_SHAPE_AND_SIZE ))
    {
      if(current_jacobian > VERDICT_DBL_MIN)
        current_shape = 3 * pow( current_jacobian, two_thirds) / 
          (length_squared[4] + length_squared[5] + length_squared[9]);
      else
        current_shape = 0;

      if(current_shape < shape) { shape = current_shape; }

    }

    // J(1,1,1)
    current_jacobian = -edges[5] % (edges[6] * -edges[10]);
    if ( current_jacobian < jacobian )
      jacobian = current_jacobian;
    
    if(metrics_request_flag & ( V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE ))
    {
      det_sum += current_jacobian;
    }
    
    if(metrics_request_flag & ( V_HEX_SCALED_JACOBIAN | V_HEX_SHEAR | V_HEX_SHEAR_AND_SIZE ))
    {
      if(length_squared[5] <= VERDICT_DBL_MIN || length_squared[6] <= VERDICT_DBL_MIN
         || length_squared[10] <= VERDICT_DBL_MIN)
      {
        current_scaled_jacobian = VERDICT_DBL_MAX;
      }
      else
      {
        current_scaled_jacobian = current_jacobian / 
          sqrt(length_squared[5] * length_squared[6] * length_squared[10]);
      }
      if(current_scaled_jacobian < scaled_jacobian)
        shear = scaled_jacobian = current_scaled_jacobian;
    }
    
    if(metrics_request_flag & V_HEX_CONDITION)
    {
      current_condition = condition_comp( -edges[5], edges[6], -edges[10] );
      if ( current_condition > condition ) { condition = current_condition; }
    }
    
    if(metrics_request_flag & V_HEX_ODDY)
    {
      current_oddy = oddy_comp( -edges[5], edges[6], -edges[10] );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
    }

    if(metrics_request_flag & ( V_HEX_SHAPE | V_HEX_SHAPE_AND_SIZE ))
    {
      if(current_jacobian > VERDICT_DBL_MIN)
        current_shape = 3 * pow( current_jacobian, two_thirds) / 
          (length_squared[5] + length_squared[6] + length_squared[10]);
      else
        current_shape = 0;

      if(current_shape < shape) { shape = current_shape; }

    }

    // J(0,1,1)
    current_jacobian = -edges[6] % (edges[7] * -edges[11]);
    if ( current_jacobian < jacobian )
      jacobian = current_jacobian;
    
    if(metrics_request_flag & ( V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE ))
    {
      det_sum += current_jacobian;
    }
    
    if(metrics_request_flag & ( V_HEX_SCALED_JACOBIAN | V_HEX_SHEAR | V_HEX_SHEAR_AND_SIZE ))
    {
      if(length_squared[6] <= VERDICT_DBL_MIN || length_squared[7] <= VERDICT_DBL_MIN
         || length_squared[11] <= VERDICT_DBL_MIN)
      {
        current_scaled_jacobian = VERDICT_DBL_MAX;
      }
      else
      {
        current_scaled_jacobian = current_jacobian / 
          sqrt(length_squared[6] * length_squared[7] * length_squared[11]);
      }
      if(current_scaled_jacobian < scaled_jacobian)
        shear = scaled_jacobian = current_scaled_jacobian;
    }
    
    if(metrics_request_flag & V_HEX_CONDITION)
    {
      current_condition = condition_comp( -edges[6], edges[7], -edges[11] );
      if ( current_condition > condition ) { condition = current_condition; }
    }
    
    if(metrics_request_flag & V_HEX_ODDY)
    {
      current_oddy = oddy_comp( -edges[6], edges[7], -edges[11] );
      if ( current_oddy > oddy ) { oddy = current_oddy; }
    }

    if(metrics_request_flag & ( V_HEX_SHAPE | V_HEX_SHAPE_AND_SIZE ))
    {
      if(current_jacobian > VERDICT_DBL_MIN)
        current_shape = 3 * pow( current_jacobian, two_thirds) / 
          (length_squared[6] + length_squared[7] + length_squared[11]);
      else
        current_shape = 0;

      if(current_shape < shape) { shape = current_shape; }

    }

    if(metrics_request_flag & ( V_HEX_RELATIVE_SIZE_SQUARED | V_HEX_SHAPE_AND_SIZE | V_HEX_SHEAR_AND_SIZE ))
    {
      if(det_sum > VERDICT_DBL_MIN && rel_size_error != VERDICT_TRUE)
      {
        double tau = det_sum / ( 8 * detw );
        metric_vals->relative_size_squared = (double)VERDICT_MIN( tau*tau, 1.0/tau/tau);
      }
      else
        metric_vals->relative_size_squared = 0.0;
    }

    // set values from above calculations
    if(metrics_request_flag & V_HEX_JACOBIAN)
      metric_vals->jacobian = (double)jacobian;

    if(metrics_request_flag & V_HEX_SCALED_JACOBIAN)
      metric_vals->scaled_jacobian = (double)scaled_jacobian;

    if(metrics_request_flag & V_HEX_CONDITION)
      metric_vals->condition = (double)(condition/3.0);

    if(metrics_request_flag & V_HEX_SHEAR)
    {
      if( shear < VERDICT_DBL_MIN )  //shear has range 0 to +1
        shear = 0;
      metric_vals->shear = (double)shear;
    }

    if(metrics_request_flag & V_HEX_SHAPE)
      metric_vals->shape = (double)shape; 
  
    if(metrics_request_flag & V_HEX_SHAPE_AND_SIZE)
      metric_vals->shape_and_size = (double)(shape * metric_vals->relative_size_squared);
  
    if(metrics_request_flag & V_HEX_SHEAR_AND_SIZE)
      metric_vals->shear_and_size = (double)(shear * metric_vals->relative_size_squared);
  
    if(metrics_request_flag & V_HEX_ODDY)
      metric_vals->oddy = (double)oddy;

    if(metrics_request_flag & V_HEX_STRETCH)
    {
      static const double HEX_STRETCH_SCALE_FACTOR = sqrt(3.0);
      double min_edge=length_squared[0];
      for(int j=1; j<12; j++)
        min_edge = VERDICT_MIN(min_edge, length_squared[j]);

      double max_diag = diag_length(1, coordinates);
        
      metric_vals->stretch = (double)(HEX_STRETCH_SCALE_FACTOR * ( safe_ratio( sqrt(min_edge), max_diag) ));
    }
  }


  if(metrics_request_flag & V_HEX_DIAGONAL)
    metric_vals->diagonal = v_hex_diagonal(num_nodes, coordinates);
  if(metrics_request_flag & V_HEX_DIMENSION)
    metric_vals->dimension = v_hex_dimension(num_nodes, coordinates);
  if(metrics_request_flag & V_HEX_DISTORTION)
                metric_vals->distortion = v_hex_distortion(num_nodes, coordinates);

  
  //take care of any overflow problems
  //max_edge_ratio
  if( metric_vals->max_edge_ratio > 0 )
    metric_vals->max_edge_ratio = (double) VERDICT_MIN( metric_vals->max_edge_ratio, VERDICT_DBL_MAX );
  else
    metric_vals->max_edge_ratio = (double) VERDICT_MAX( metric_vals->max_edge_ratio, -VERDICT_DBL_MAX );

  //skew
  if( metric_vals->skew > 0 )
    metric_vals->skew = (double) VERDICT_MIN( metric_vals->skew, VERDICT_DBL_MAX );
  else
    metric_vals->skew = (double) VERDICT_MAX( metric_vals->skew, -VERDICT_DBL_MAX );

  //taper
  if( metric_vals->taper > 0 )
    metric_vals->taper = (double) VERDICT_MIN( metric_vals->taper, VERDICT_DBL_MAX );
  else
    metric_vals->taper = (double) VERDICT_MAX( metric_vals->taper, -VERDICT_DBL_MAX );

  //volume
  if( metric_vals->volume > 0 )
    metric_vals->volume = (double) VERDICT_MIN( metric_vals->volume, VERDICT_DBL_MAX );
  else
    metric_vals->volume = (double) VERDICT_MAX( metric_vals->volume, -VERDICT_DBL_MAX );

  //stretch
  if( metric_vals->stretch > 0 )
    metric_vals->stretch = (double) VERDICT_MIN( metric_vals->stretch, VERDICT_DBL_MAX );
  else
    metric_vals->stretch = (double) VERDICT_MAX( metric_vals->stretch, -VERDICT_DBL_MAX );

  //diagonal
  if( metric_vals->diagonal > 0 )
    metric_vals->diagonal = (double) VERDICT_MIN( metric_vals->diagonal, VERDICT_DBL_MAX );
  else
    metric_vals->diagonal = (double) VERDICT_MAX( metric_vals->diagonal, -VERDICT_DBL_MAX );

  //dimension
  if( metric_vals->dimension > 0 )
    metric_vals->dimension = (double) VERDICT_MIN( metric_vals->dimension, VERDICT_DBL_MAX );
  else
    metric_vals->dimension = (double) VERDICT_MAX( metric_vals->dimension, -VERDICT_DBL_MAX );

  //oddy
  if( metric_vals->oddy > 0 )
    metric_vals->oddy = (double) VERDICT_MIN( metric_vals->oddy, VERDICT_DBL_MAX );
  else
    metric_vals->oddy = (double) VERDICT_MAX( metric_vals->oddy, -VERDICT_DBL_MAX );

  //condition
  if( metric_vals->condition > 0 )
    metric_vals->condition = (double) VERDICT_MIN( metric_vals->condition, VERDICT_DBL_MAX );
  else
    metric_vals->condition = (double) VERDICT_MAX( metric_vals->condition, -VERDICT_DBL_MAX );

  //jacobian
  if( metric_vals->jacobian > 0 )
    metric_vals->jacobian = (double) VERDICT_MIN( metric_vals->jacobian, VERDICT_DBL_MAX );
  else
    metric_vals->jacobian = (double) VERDICT_MAX( metric_vals->jacobian, -VERDICT_DBL_MAX );

  //scaled_jacobian
  if( metric_vals->scaled_jacobian > 0 )
    metric_vals->scaled_jacobian = (double) VERDICT_MIN( metric_vals->scaled_jacobian, VERDICT_DBL_MAX );
  else
    metric_vals->scaled_jacobian = (double) VERDICT_MAX( metric_vals->scaled_jacobian, -VERDICT_DBL_MAX );

  //shear
  if( metric_vals->shear > 0 )
    metric_vals->shear = (double) VERDICT_MIN( metric_vals->shear, VERDICT_DBL_MAX );
  else
    metric_vals->shear = (double) VERDICT_MAX( metric_vals->shear, -VERDICT_DBL_MAX );

  //shape
  if( metric_vals->shape > 0 )
    metric_vals->shape = (double) VERDICT_MIN( metric_vals->shape, VERDICT_DBL_MAX );
  else
    metric_vals->shape = (double) VERDICT_MAX( metric_vals->shape, -VERDICT_DBL_MAX );

  //relative_size_squared
  if( metric_vals->relative_size_squared > 0 )
    metric_vals->relative_size_squared = (double) VERDICT_MIN( metric_vals->relative_size_squared, VERDICT_DBL_MAX );
  else
    metric_vals->relative_size_squared = (double) VERDICT_MAX( metric_vals->relative_size_squared, -VERDICT_DBL_MAX );

  //shape_and_size
  if( metric_vals->shape_and_size > 0 )
    metric_vals->shape_and_size = (double) VERDICT_MIN( metric_vals->shape_and_size, VERDICT_DBL_MAX );
  else
    metric_vals->shape_and_size = (double) VERDICT_MAX( metric_vals->shape_and_size, -VERDICT_DBL_MAX );

  //shear_and_size
  if( metric_vals->shear_and_size > 0 ) metric_vals->shear_and_size = (double) VERDICT_MIN( metric_vals->shear_and_size, VERDICT_DBL_MAX );
  else
    metric_vals->shear_and_size = (double) VERDICT_MAX( metric_vals->shear_and_size, -VERDICT_DBL_MAX );
  
  //distortion
  if( metric_vals->distortion > 0 )
    metric_vals->distortion = (double) VERDICT_MIN( metric_vals->distortion, VERDICT_DBL_MAX );
  else
    metric_vals->distortion = (double) VERDICT_MAX( metric_vals->distortion, -VERDICT_DBL_MAX );

  if(metrics_request_flag & V_HEX_MED_ASPECT_FROBENIUS)
    metric_vals->med_aspect_frobenius = v_hex_med_aspect_frobenius(8, coordinates);

  if(metrics_request_flag & V_HEX_EDGE_RATIO)
    metric_vals->edge_ratio = v_hex_edge_ratio(8, coordinates);
}



