/***********************************************************************************************************************
*  OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
*  following conditions are met:
*
*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
*  disclaimer.
*
*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
*  disclaimer in the documentation and/or other materials provided with the distribution.
*
*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
*  derived from this software without specific prior written permission from the respective party.
*
*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
*  written permission from Alliance for Sustainable Energy, LLC.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
***********************************************************************************************************************/

#include "Geometry.hpp"
#include "Intersection.hpp"
#include "Transformation.hpp"
#include "Vector3d.hpp"

#include "../core/Assert.hpp"

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

#include <polypartition/polypartition.h>

namespace openstudio{
  /// convert degrees to radians
  double degToRad(double degrees)
  {
    return degrees*boost::math::constants::pi<double>()/180.0;
  }

  /// convert radians to degrees
  double radToDeg(double radians)
  {
    return radians*180.0/boost::math::constants::pi<double>();
  }

  /// compute area from surface as Point3dVector
  boost::optional<double> getArea(const Point3dVector& points)
  {
    boost::optional<double> result;
    OptionalVector3d newall = getNewallVector(points);
    if (newall){
      result = newall->length() / 2.0;
    }
    return result;
  }

  // compute Newall vector from Point3dVector, direction is same as outward normal
  // magnitude is twice the area
  OptionalVector3d getNewallVector(const Point3dVector& points)
  {
    OptionalVector3d result;
    size_t N = points.size();
    if (N >= 3){
      Vector3d vec;
      for (unsigned i = 1; i < N-1; ++i){
        Vector3d v1 = points[i] - points[0];
        Vector3d v2 = points[i+1] - points[0];
        vec += v1.cross(v2);
      }
     result = vec;
   }
   return result;
  }

  // compute outward normal from Point3dVector
  OptionalVector3d getOutwardNormal(const Point3dVector& points)
  {
    OptionalVector3d result = getNewallVector(points);
    if (result){
      if (!result->normalize()){
        result.reset();
      }
    }
    return result;
  }

  /// compute centroid from surface as Point3dVector
  OptionalPoint3d getCentroid(const Point3dVector& points)
  {
    OptionalPoint3d result;

    if (points.size() >= 3){
      // convert to face coordinates
      Transformation alignFace = Transformation::alignFace(points);
      Point3dVector surfacePoints = alignFace.inverse()*points;

      size_t N = surfacePoints.size();
      double A = 0;
      double cx = 0;
      double cy = 0;
      for (size_t i = 0; i < N; ++i){
        double x1, x2, y1, y2;
        if (i == N-1){
          x1 = surfacePoints[i].x();
          x2 = surfacePoints[0].x();
          y1 = surfacePoints[i].y();
          y2 = surfacePoints[0].y();
        }else{
          x1 = surfacePoints[i].x();
          x2 = surfacePoints[i+1].x();
          y1 = surfacePoints[i].y();
          y2 = surfacePoints[i+1].y();
        }

        double dA = (x1*y2-x2*y1);
        A += 0.5*dA;
        cx += (x1+x2)*dA;
        cy += (y1+y2)*dA;
      }

      if (A > 0){
        // centroid in face coordinates
        Point3d surfaceCentroid(cx/(6.0*A), cy/(6.0*A), 0.0);

        // centroid
        result = alignFace*surfaceCentroid;
      }
    }
    return result;
  }

  /// reorder points to upper-left-corner convention
  Point3dVector reorderULC(const Point3dVector& points)
  {
    size_t N = points.size();
    if (N < 3){
      return Point3dVector();
    }

    // transformation to align face
    Transformation t = Transformation::alignFace(points);
    Point3dVector facePoints = t.inverse()*points;

    // find ulc index in face coordinates
    double maxY = std::numeric_limits<double>::min();
    double minX = std::numeric_limits<double>::max();
    unsigned ulcIndex = 0;
    for(unsigned i = 0; i < N; ++i){
      OS_ASSERT(std::abs(facePoints[i].z()) < 0.001);
      if ((maxY < facePoints[i].y()) || ((maxY < facePoints[i].y() + 0.00001) && (minX > facePoints[i].x()))){
        ulcIndex = i;
        maxY = facePoints[i].y();
        minX = facePoints[i].x();
      }
    }

    // no-op
    if (ulcIndex == 0){
      return points;
    }

    // create result
    Point3dVector result;
    std::copy (points.begin() + ulcIndex, points.end(), std::back_inserter(result));
    std::copy (points.begin(), points.begin() + ulcIndex, std::back_inserter(result));
    OS_ASSERT(result.size() == N);
    return result;
  }

  std::vector<Point3d> removeCollinear(const Point3dVector& points, double tol)
  {
    Transformation t = Transformation::alignFace(points);
    std::vector<Point3d> result = t*simplify(t.inverse()*points, true, tol);
    return result;
  }

  std::vector<Point3d> removeCollinearLegacy(const Point3dVector& points, double tol)
  {
    size_t N = points.size();
    if (N < 3){
      return points;
    }

    std::vector<Point3d> result;
    Point3d lastPoint = points[0];
    result.push_back(lastPoint);

    for (unsigned i = 1; i < N; ++i){
      Point3d currentPoint = points[i];
      Point3d nextPoint = points[0];
      if (i < N-1){
        nextPoint = points[i+1];
      }

      Vector3d a = (currentPoint - lastPoint);
      Vector3d b = (nextPoint - currentPoint);

      // if these fail to normalize we have zero length vectors (e.g. adjacent points)
      if (a.normalize()){
        if (b.normalize()){

          Vector3d c = a.cross(b);
          if (c.length() >= tol){
            // cross product is significant
            result.push_back(currentPoint);
            lastPoint = currentPoint;
          }else{
            // see if dot product is near -1
            double d = a.dot(b);
            if (d <= -1.0 + tol){
              // this is a line reversal
              result.push_back(currentPoint);
              lastPoint = currentPoint;
            }
          }
        }
      }
    }

    size_t iBegin = 0;
    size_t iEnd = result.size();

    bool resizeBegin = true;
    while (resizeBegin){
      resizeBegin = false;
      unsigned N = iEnd - iBegin;
      if (N > 3){
        Vector3d a = (result[iBegin] - result[iEnd - 1]);
        Vector3d b = (result[iBegin + 1] - result[iBegin]);
        if (a.normalize()){
          if (b.normalize()){
            double d = a.dot(b);
            if (d >= 1.0 - tol){
              iBegin++;
              resizeBegin = true;
            }
          } else{
            iBegin++;
            resizeBegin = true;
          }
        } else{
          iBegin++;
          resizeBegin = true;
        }
      }
    }

    bool resizeEnd = true;
    while (resizeEnd){
      resizeEnd = false;
      unsigned N = iEnd - iBegin;
      if (N > 3){
        Vector3d a = (result[iEnd - 1] - result[iEnd - 2]);
        Vector3d b = (result[iBegin] - result[iEnd - 1]);
        if (a.normalize()){
          if (b.normalize()){
            double d = a.dot(b);
            if (d >= 1.0 - tol){
              iEnd--;
              resizeEnd = true;
            }
          } else{
            iEnd--;
            resizeEnd = true;
          }
        } else{
          iEnd--;
          resizeEnd = true;
        }
      }
    }

    result = std::vector<Point3d>(result.begin() + iBegin, result.begin() + iEnd);
    return result;
  }

  double getDistance(const Point3d& point1, const Point3d& point2) {
    double dx = point1.x() - point2.x();
    double dy = point1.y() - point2.y();
    double dz = point1.z() - point2.z();
    double result = std::sqrt(dx*dx + dy*dy + dz*dz);
    return result;
  }

  double getDistancePointToLineSegment(const Point3d& point, const std::vector<Point3d>& lineSegment)
  {
    if (lineSegment.size() != 2){
      return 0;
    }

    // http://paulbourke.net/geometry/pointlineplane/

    Point3d point1 = lineSegment[0];
    Point3d point2 = lineSegment[1];

    Vector3d p2p1 = point2-point1;
    Vector3d p3p1 = point-point1;

    double d12 = p2p1.length();
    if (d12 < 1.0e-12){
      return p3p1.length();
    }

    Point3d closestPoint;
    double u = p3p1.dot(p2p1) / (d12*d12);
    if (u < 0){
      closestPoint = point1;
    }else if (u > 1){
      closestPoint = point2;
    }else{
      closestPoint = point1 + u*p2p1;
    }

    Vector3d diff = point - closestPoint;

    return diff.length();
  }

  double getDistancePointToTriangle(const Point3d& point, const std::vector<Point3d>& triangle)
  {
    if (triangle.size() != 3){
      return 0;
    }

    //Distance Between Point and Triangle in 3D
    //David Eberly
    //Geometric Tools, LLC
    //http://www.geometrictools.com/

    //T(s; t) = B+sE0+tE1

    Point3d B = triangle[0];
    Vector3d E0 = triangle[1] - triangle[0];
    Vector3d E1 = triangle[2] - triangle[0];
    Vector3d BminusP = B - point;

    double b = E0.dot(E1);

    if (std::abs(b) > 1.0-1.0E-12){
      // triangle is collinear
      return 0;
    }

    double a = E0.dot(E0);
    double c = E1.dot(E1);
    double d = E0.dot(BminusP);
    double e = E1.dot(BminusP);
    // double f = BminusP.dot(BminusP); // unused

    double det = a*c-b*b;
    double s = b*e-c*d;
    double t = b*d-a*e;

    Point3d closestPoint;

    if ( s+t <= det ) {
      if ( s < 0 ) {
        if ( t < 0 ) {
          //region 4, closest to point triangle[0]
          return getDistance(point, triangle[0]);
        } else {
          //region 3, closest to line triangle[0] to triangle[2]
          std::vector<Point3d> line;
          line.push_back(triangle[0]);
          line.push_back(triangle[2]);
          return getDistancePointToLineSegment(point, line);
        }
      } else if ( t < 0 ) {
        //region 5, closest to line triangle[0] to triangle[1]
        std::vector<Point3d> line;
        line.push_back(triangle[0]);
        line.push_back(triangle[1]);
        return getDistancePointToLineSegment(point, line);
      } else {
        //region 0, closest point is inside triangle
        double invDet = 1.0/det;
        closestPoint = B + invDet*s*E0 + invDet*t*E1;
      }
    } else {
      if ( s < 0 ) {
        //region 2, closest to point triangle[2]
        return getDistance(point, triangle[2]);
      } else if ( t < 0 ) {
        //region 6, closest to point triangle[1]
        return getDistance(point, triangle[1]);
      } else {
        //region 1, closest to line triangle[1] to triangle[2]
        std::vector<Point3d> line;
        line.push_back(triangle[1]);
        line.push_back(triangle[2]);
        return getDistancePointToLineSegment(point, line);
      }
    }

    Vector3d diff = point-closestPoint;
    return diff.length();
  }

  double getAngle(const Vector3d& vector1, const Vector3d& vector2) {
    Vector3d working1(vector1);
    working1.normalize();
    Vector3d working2(vector2);
    working2.normalize();
    return acos(working1.dot(working2));
  }

  bool circularEqual(const Point3dVector& points1, const Point3dVector& points2, double tol)
  {
    size_t N = points1.size();
    if (N != points2.size()){
      return false;
    }

    if (N == 0){
      return true;
    }

    bool result = false;

    // look for a common starting point
    for (size_t i = 0; i < N; ++i){
      if (getDistance(points1[0], points2[i]) <= tol){

        result = true;

        // check all other points
        for (unsigned j = 0; j < N; ++j){
          if (getDistance(points1[j], points2[(i + j) % N]) > tol){
            result = false;
            break;
          }
        }
      }

      if (result){
        return result;
      }
    }

    return result;
  }

  Point3d getCombinedPoint(const Point3d& point3d, std::vector<Point3d>& allPoints, double tol)
  {
    for (const Point3d& otherPoint : allPoints){
      if (std::sqrt(std::pow(point3d.x()-otherPoint.x(), 2) + std::pow(point3d.y()-otherPoint.y(), 2) + std::pow(point3d.z()-otherPoint.z(), 2)) < tol){
        return otherPoint;
      }
    }
    allPoints.push_back(point3d);
    return point3d;
  }

  std::vector<std::vector<Point3d> > computeTriangulation(const Point3dVector& vertices, const std::vector<std::vector<Point3d> >& holes, double tol)
  {
    std::vector<std::vector<Point3d> > result;

    // check input
    if (vertices.size () < 3){
      return result;
    }

    boost::optional<Vector3d> normal = getOutwardNormal(vertices);
    if (!normal || normal->z() > -0.999){
      return result;
    }

    for (const auto& hole : holes){
      normal = getOutwardNormal(hole);
      if (!normal || normal->z() > -0.999){
        return result;
      }
    }

    std::vector<Point3d> allPoints;

    // PolyPartition does not support holes which intersect the polygon or share an edge
    // if any hole is not fully contained we will use boost to remove all the holes
    bool polyPartitionHoles = true;
    for (const std::vector<Point3d>& hole : holes){
      if (!within(hole, vertices, tol)){
        // PolyPartition can't handle this
        polyPartitionHoles = false;
        break;
      }
    }

    if (!polyPartitionHoles){
      // use boost to do all the intersections
      std::vector<std::vector<Point3d> > allFaces = subtract(vertices, holes, tol);
      std::vector<std::vector<Point3d> > noHoles;
      for (const std::vector<Point3d>& face : allFaces){
        std::vector<std::vector<Point3d> > temp = computeTriangulation(face, noHoles);
        result.insert(result.end(), temp.begin(), temp.end());
      }
      return result;
    }

    // convert input to vector of TPPLPoly
    std::list<TPPLPoly> polys;

    TPPLPoly outerPoly; // must be counter-clockwise, input vertices are clockwise
    outerPoly.Init(vertices.size());
    outerPoly.SetHole(false);
    size_t n = vertices.size();
    for(size_t i = 0; i < n; ++i){

      // should all have zero z coordinate now
      double z = vertices[n-i-1].z();
      if (abs(z) > tol){
        LOG_FREE(Error, "utilities.geometry.computeTriangulation", "All points must be on z = 0 plane for triangulation methods");
        return result;
      }

      Point3d point = getCombinedPoint(vertices[n-i-1], allPoints, tol);
      outerPoly[i].x = point.x();
      outerPoly[i].y = point.y();
    }
    outerPoly.SetOrientation(TPPL_CCW);
    polys.push_back(outerPoly);

    for (const std::vector<Point3d>& holeVertices : holes){

      if (holeVertices.size () < 3){
        LOG_FREE(Error, "utilities.geometry.computeTriangulation", "Hole has fewer than 3 points, ignoring");
        continue;
      }

      TPPLPoly innerPoly; // must be clockwise, input vertices are clockwise
      innerPoly.Init(holeVertices.size());
      innerPoly.SetHole(true);
      //std::cout << "inner :";
      for(unsigned i = 0; i < holeVertices.size(); ++i){

        // should all have zero z coordinate now
        double z = holeVertices[i].z();
        if (abs(z) > tol){
          LOG_FREE(Error, "utilities.geometry.computeTriangulation", "All points must be on z = 0 plane for triangulation methods");
          return result;
        }

        Point3d point = getCombinedPoint(holeVertices[i], allPoints, tol);
        innerPoly[i].x = point.x();
        innerPoly[i].y = point.y();
      }
      innerPoly.SetOrientation(TPPL_CW);
      polys.push_back(innerPoly);
    }

    // do partitioning
    TPPLPartition pp;
    std::list<TPPLPoly> resultPolys;
    int test = pp.Triangulate_EC(&polys,&resultPolys);
    if (test == 0){
      test = pp.Triangulate_MONO(&polys, &resultPolys);
    }
    if (test == 0){
      //std::stringstream ss;
      //ss << "Vertices: " << vertices << std::endl;
      //for (const auto& hole : holes){ ss << "Hole:" << hole << std::endl; }
      //std::string testStr = ss.str();
      LOG_FREE(Error, "utilities.geometry.computeTriangulation", "Failed to partition polygon");
      return result;
    }

    // convert back to vertices
    std::list<TPPLPoly>::iterator it, itend;
    //std::cout << "Start" << std::endl;
    for(it = resultPolys.begin(), itend = resultPolys.end(); it != itend; ++it){

      it->SetOrientation(TPPL_CW);

      std::vector<Point3d> triangle;
      for (long i = 0; i < it->GetNumPoints(); ++i){
        TPPLPoint point = it->GetPoint(i);
        triangle.push_back(Point3d(point.x, point.y, 0));
      }
      //std::cout << triangle << std::endl;
      result.push_back(triangle);
    }
    //std::cout << "End" << std::endl;

    return result;
  }

  std::vector<Point3d> moveVerticesTowardsPoint(const Point3dVector& vertices, const Point3d& point, double distance)
  {
    Point3dVector result;
    for (const Point3d& vertex : vertices){
      Vector3d vector = point-vertex;
      vector.setLength(distance);
      result.push_back(vertex+vector);
    }
    return result;
  }

  std::vector<Point3d> reverse(const Point3dVector& vertices)
  {
    std::vector<Point3d> result(vertices);
    std::reverse(result.begin(), result.end());
    return result;
  }

  bool applyViewAndDaylightingGlassRatios(double viewGlassToWallRatio, double daylightingGlassToWallRatio,
    double desiredViewGlassSillHeight, double desiredDaylightingGlassHeaderHeight,
    double exteriorShadingProjectionFactor, double interiorShelfProjectionFactor,
    const Point3dVector& vertices, Point3dVector& viewVertices,
    Point3dVector& daylightingVertices, Point3dVector& exteriorShadingVertices,
    Point3dVector& interiorShelfVertices)
  {
    // check inputs for reasonableness
    double totalWWR = viewGlassToWallRatio + daylightingGlassToWallRatio;
    if (totalWWR == 0){
      // requesting no glass? remove existing windows?
      return false;
    }else if (totalWWR < 0.0 || totalWWR >= 1.0){
      return false;
    }

    boost::optional<double> grossArea = getArea(vertices);
    if (!grossArea){
      return false;
    }

    Transformation transformation = Transformation::alignFace(vertices);
    Point3dVector faceVertices = transformation.inverse() * vertices;

    if (faceVertices.empty()){
      return false;
    }

    bool doViewGlass = (viewGlassToWallRatio > 0);
    bool doDaylightGlass = (daylightingGlassToWallRatio > 0);
    bool doExteriorShading = (doViewGlass && (exteriorShadingProjectionFactor > 0));
    bool doInteriorShelf = (doDaylightGlass && (interiorShelfProjectionFactor > 0));
    bool doViewAndDaylightGlass = (doViewGlass && doDaylightGlass);

    // ignore these user arguments?
    if (!doViewGlass){
      desiredViewGlassSillHeight = 0.0;
    }
    if (!doDaylightGlass){
      desiredDaylightingGlassHeaderHeight = 0.0;
    }

    // new coordinate system has z' in direction of outward normal, y' is up
    double xmin = std::numeric_limits<double>::max();
    double xmax = std::numeric_limits<double>::min();
    double ymin = std::numeric_limits<double>::max();
    double ymax = std::numeric_limits<double>::min();
    for (const Point3d& faceVertex : faceVertices){
      xmin = std::min(xmin, faceVertex.x());
      xmax = std::max(xmax, faceVertex.x());
      ymin = std::min(ymin, faceVertex.y());
      ymax = std::max(ymax, faceVertex.y());
    }
    if ((xmin > xmax) || (ymin > ymax)){
      return false;
    }

    double oneInch = 0.0254;

    // DLM: preserve a 1" gap between window and edge to keep SketchUp happy
    double minGlassToEdgeDistance = oneInch;
    double minViewToDaylightDistance = 0;
    if (doViewAndDaylightGlass){
      minViewToDaylightDistance = oneInch;
    }

    // wall parameters
    double wallWidth = xmax - xmin;
    double wallHeight = ymax - ymin;
    double wallArea = wallWidth*wallHeight;

    if (wallWidth < 2*minGlassToEdgeDistance){
      return false;
    }

    if (wallHeight < 2*minGlassToEdgeDistance + minViewToDaylightDistance){
      return false;
    }

    // check against actual surface area to ensure this is a rectangle?
    if (std::abs(wallArea - grossArea.get()) > oneInch*oneInch){
      return false;
    }

    double maxWindowArea = wallArea - 2*wallHeight*minGlassToEdgeDistance - (wallWidth-2*minGlassToEdgeDistance)*(2*minGlassToEdgeDistance + minViewToDaylightDistance);
    double requestedViewArea = viewGlassToWallRatio*wallArea;
    double requestedDaylightingArea = daylightingGlassToWallRatio*wallArea;
    double requestedTotalWindowArea = totalWWR*wallArea;

    if (requestedTotalWindowArea > maxWindowArea){
      return false;
    }

    // view glass parameters
    double viewMinX = 0;
    double viewMinY = 0;
    double viewWidth = 0;
    double viewHeight = 0;

    // daylighting glass parameters
    double daylightingWidth = 0;
    double daylightingHeight = 0;
    double daylightingMinX = 0;
    double daylightingMinY = 0;

    // initial free parameters
    double viewWidthInset = minGlassToEdgeDistance;
    double viewSillHeight = std::max(desiredViewGlassSillHeight, minGlassToEdgeDistance);
    double daylightingWidthInset = minGlassToEdgeDistance;
    double daylightingHeaderHeight = std::max(desiredDaylightingGlassHeaderHeight, minGlassToEdgeDistance);

    bool converged = false;
    for (unsigned i = 0; i < 100; ++i){

      // view glass parameters
      viewMinX = viewWidthInset;
      viewMinY = viewSillHeight;
      viewWidth = wallWidth - 2*viewWidthInset;
      viewHeight = requestedViewArea/viewWidth;

      // daylighting glass parameters
      daylightingWidth = wallWidth - 2*daylightingWidthInset;
      daylightingHeight = requestedDaylightingArea/daylightingWidth;
      daylightingMinX = viewWidthInset;
      daylightingMinY = wallHeight - daylightingHeaderHeight - daylightingHeight;

      if (viewMinY + viewHeight + minViewToDaylightDistance > daylightingMinY){
        // windows overlap or exceed maximum size

        if (doViewAndDaylightGlass){

          // try shrinking vertical offsets
          viewSillHeight = std::max(viewSillHeight - oneInch, minGlassToEdgeDistance);
          daylightingHeaderHeight = std::max(daylightingHeaderHeight - oneInch, minGlassToEdgeDistance);

        }else if (doViewGlass){

          // solve directly
          viewSillHeight = wallHeight - minGlassToEdgeDistance - viewHeight;

          if (viewSillHeight < minGlassToEdgeDistance){
            // cannot make window this large
            return false;
          }

        }else if (doDaylightGlass){

          // solve directly
          daylightingHeaderHeight = wallHeight - minGlassToEdgeDistance - daylightingHeight;

          if (daylightingHeaderHeight < minGlassToEdgeDistance){
            // cannot make window this large
            return false;
          }

        }

      }else{

        converged = true;
        break;

      }
    }

    if (!converged){
      return false;
    }

    Point3dVector surfacePolygon;
    for (const Point3d& point : faceVertices){
      if (std::abs(point.z()) > 0.001){
        LOG_FREE(Warn, "utilities.geometry.applyViewAndDaylightingGlassRatios", "Surface point z not on plane, z =" << point.z());
      }
      surfacePolygon.push_back(Point3d(point.x(),point.y(), 0.0));
    }
    std::reverse(surfacePolygon.begin(), surfacePolygon.end());

    if (doViewGlass){
      viewVertices.push_back(Point3d(viewMinX, viewMinY + viewHeight, 0));
      viewVertices.push_back(Point3d(viewMinX, viewMinY, 0));
      viewVertices.push_back(Point3d(viewMinX + viewWidth, viewMinY, 0));
      viewVertices.push_back(Point3d(viewMinX + viewWidth, viewMinY + viewHeight, 0));

      Point3dVector windowPolygon;
      for (const Point3d& point : viewVertices){
        if (std::abs(point.z()) > 0.001){
          LOG_FREE(Warn, "utilities.geometry.applyViewAndDaylightingGlassRatios", "Surface point z not on plane, z =" << point.z());
        }
        windowPolygon.push_back(Point3d(point.x(),point.y(), 0.0));
      }

      // sub surface must be fully contained by base surface
      for (const Point3d& point : windowPolygon){
        if (!within(point, surfacePolygon, 0.001)){
          std::cout << "point: " << point << std::endl;
          std::cout << "surfacePolygon: " << surfacePolygon << std::endl;
          LOG_FREE(Debug, "utilities.geometry.applyViewAndDaylightingGlassRatios", "Surface does not fully contain SubSurface");
          return false;
        }
      }
    }

    if (doDaylightGlass){
      daylightingVertices.push_back(Point3d(daylightingMinX, daylightingMinY + daylightingHeight, 0));
      daylightingVertices.push_back(Point3d(daylightingMinX, daylightingMinY, 0));
      daylightingVertices.push_back(Point3d(daylightingMinX + daylightingWidth, daylightingMinY, 0));
      daylightingVertices.push_back(Point3d(daylightingMinX + daylightingWidth, daylightingMinY + daylightingHeight, 0));

      Point3dVector windowPolygon;
      for (const Point3d& point : daylightingVertices){
        if (std::abs(point.z()) > 0.001){
          LOG_FREE(Warn, "utilities.geometry.applyViewAndDaylightingGlassRatios", "Surface point z not on plane, z =" << point.z());
        }
        windowPolygon.push_back(Point3d(point.x(),point.y(), 0.0));
      }

      // sub surface must be fully contained by base surface
      for (const Point3d& point : windowPolygon){
        if (!within(point, surfacePolygon, 0.001)){
          LOG_FREE(Debug, "utilities.geometry.applyViewAndDaylightingGlassRatios", "Surface does not fully contain SubSurface");
          return false;
        }
      }
    }

    if (doExteriorShading) {
      exteriorShadingVertices.push_back(Point3d(viewMinX, viewMinY + viewHeight, 0));
      exteriorShadingVertices.push_back(Point3d(viewMinX, viewMinY + viewHeight, exteriorShadingProjectionFactor*viewHeight));
      exteriorShadingVertices.push_back(Point3d(viewMinX + viewWidth, viewMinY + viewHeight, exteriorShadingProjectionFactor*viewHeight));
      exteriorShadingVertices.push_back(Point3d(viewMinX + viewWidth, viewMinY + viewHeight, 0));
    }

    if (doInteriorShelf) {
      interiorShelfVertices.push_back(Point3d(daylightingMinX + daylightingWidth, daylightingMinY, 0));
      interiorShelfVertices.push_back(Point3d(daylightingMinX + daylightingWidth, daylightingMinY, -interiorShelfProjectionFactor*daylightingHeight));
      interiorShelfVertices.push_back(Point3d(daylightingMinX, daylightingMinY, -interiorShelfProjectionFactor*daylightingHeight));
      interiorShelfVertices.push_back(Point3d(daylightingMinX, daylightingMinY, 0));
    }

    // put all vertices back into input coordinate system
    viewVertices = transformation*viewVertices;
    daylightingVertices = transformation*daylightingVertices;
    exteriorShadingVertices = transformation*exteriorShadingVertices;
    interiorShelfVertices = transformation*interiorShelfVertices;

    return true;
  }

} // openstudio


#include "BlocksEngine/pch.h"
#include "BlocksEngine/Core/Math/Vector3.h"

#include <boost/container_hash/hash.hpp>

using namespace BlocksEngine;
using namespace DirectX;

//------------------------------------------------------------------------------
// Constructors
//------------------------------------------------------------------------------

template <class T>
Vector3<T>::Vector3() noexcept
    : Vector3Base<T>::Base{0, 0, 0}
{
}

template <class T>
constexpr Vector3<T>::Vector3(const T a) noexcept
    : Vector3Base<T>::Base{a, a, a}
{
}

/*template <class T>
constexpr Vector3<T>::Vector3(const T x, const T y, const T z) noexcept
    : Vector3Base<T>::Base{x, y, z}
{
}*/

template <typename T>
Vector3<T>::Vector3(const physx::PxVec3& vec3) noexcept
    : Vector3Base<T>::Base{
        static_cast<float>(vec3.x),
        static_cast<float>(vec3.y),
        static_cast<float>(vec3.z)
    }
{
}

template <typename T>
Vector3<T>::Vector3(const physx::PxExtendedVec3& vec3) noexcept
    : Vector3Base<T>::Base{
        static_cast<float>(vec3.x),
        static_cast<float>(vec3.y),
        static_cast<float>(vec3.z)
    }
{
}

template <class T>
Vector3<T>::Vector3(_In_reads_(3) const T* pArray) noexcept
    : Vector3Base<T>::Base{pArray}
{
}

template <class T>
Vector3<T>::Vector3(FXMVECTOR v) noexcept // NOLINT(cppcoreguidelines-pro-type-member-init)
{
    Store(this, v);
}

template <class T>
Vector3<T>::Vector3(const typename Vector3Base<T>::Base& v) noexcept
    : Vector3Base<T>::Base{v.x, v.y, v.z}
{
}

template <>
Vector3<float>::Vector3(const Vector3Base<float>::Vector& v) noexcept
    : Vector3Base<float>::Base{v.f[0], v.f[1], v.f[2]}
{
}

template <>
Vector3<int32_t>::Vector3(const Vector3Base<int32_t>::Vector& v) noexcept
    : Vector3Base<int32_t>::Base{v.i[0], v.i[1], v.i[2]}
{
}

template <>
Vector3<uint32_t>::Vector3(const Vector3Base<uint32_t>::Vector& v) noexcept
    : Vector3Base<uint32_t>::Base{v.u[0], v.u[1], v.u[2]}
{
}

//------------------------------------------------------------------------------
// Converter operators
//------------------------------------------------------------------------------

template <class T>
Vector3<T>::operator XMVECTOR() const noexcept
{
    return Load(this);
}

//------------------------------------------------------------------------------
// Comparision operators
//------------------------------------------------------------------------------

template <class T>
bool Vector3<T>::operator==(const Vector3& v) const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR v2 = Load(&v);
    return XMVector3Equal(v1, v2);
}

template <class T>
bool Vector3<T>::operator!=(const Vector3& v) const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR v2 = Load(&v);
    return XMVector3NotEqual(v1, v2);
}

//------------------------------------------------------------------------------
// Assignment operators
//------------------------------------------------------------------------------

template <>
Vector3<float>& Vector3<float>::operator=(const Vector3Base<float>::Vector& v) noexcept
{
    this->x = v.f[0];
    this->y = v.f[1];
    this->z = v.f[2];

    return *this;
}

template <>
Vector3<int32_t>& Vector3<int32_t>::operator=(const Vector3Base<int32_t>::Vector& v) noexcept
{
    this->x = v.i[0];
    this->y = v.i[1];
    this->z = v.i[2];

    return *this;
}

template <>
Vector3<uint32_t>& Vector3<uint32_t>::operator=(const Vector3Base<uint32_t>::Vector& v) noexcept
{
    this->x = v.u[0];
    this->y = v.u[1];
    this->z = v.u[2];

    return *this;
}

template <class T>
Vector3<T>& Vector3<T>::operator/=(const float s) noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR x = XMVectorScale(v1, 1.0f / s);
    Store(this, x);

    return *this;
}

//------------------------------------------------------------------------------
// Unary operators
//------------------------------------------------------------------------------

template <class T>
Vector3<T> Vector3<T>::operator+() const noexcept
{
    return *this;
}

template <class T>
Vector3<T> Vector3<T>::operator-() const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR x = XMVectorNegate(v1);

    Vector3<T> r;
    Store(&r, x);
    return r;
}

//------------------------------------------------------------------------------
// Hashing
//------------------------------------------------------------------------------

template <class T>
std::size_t hash_value(const Vector3<T>& v)
{
    std::size_t seed = 0;
    boost::hash_combine(seed, v.x);
    boost::hash_combine(seed, v.y);
    boost::hash_combine(seed, v.z);

    return seed;
}

//------------------------------------------------------------------------------
// Vector operations
//------------------------------------------------------------------------------

template <class T>
template <class U>
bool Vector3<T>::InBounds(const Vector3<U>& bounds) const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR v2 = Load(&bounds);
    return XMVector3InBounds(v1, v2);
}

template <class T>
float Vector3<T>::Length() const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR x = XMVector3Length(v1);
    return XMVectorGetX(x);
}

template <class T>
float Vector3<T>::LengthSquared() const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR x = XMVector3LengthSq(v1);
    return XMVectorGetX(x);
}

template <class T>
template <class U>
float Vector3<T>::Dot(const Vector3<U>& v) const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR v2 = Load(&v);
    const XMVECTOR x = XMVector3Dot(v1, v2);
    return XMVectorGetX(x);
}

template <class T>
template <class U>
void Vector3<T>::Cross(const Vector3<U>& v, Vector3<T>& result) const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR v2 = Load(&v);
    const XMVECTOR x = XMVector3Cross(v1, v2);
    Store(&result, x);
}

template <class T>
template <class U>
Vector3<T> Vector3<T>::Cross(const Vector3<U>& v) const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR v2 = Load(&v);
    const XMVECTOR x = XMVector3Cross(v1, v2);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
void Vector3<T>::Normalize() noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR x = XMVector3Normalize(v1);
    Store(this, x);
}

template <class T>
template <class U>
void Vector3<T>::Normalize(Vector3<U>& result) const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR x = XMVector3Normalize(v1);
    Store(&result, x);
}

//------------------------------------------------------------------------------
// Static functions
//------------------------------------------------------------------------------

template <typename T>
void Vector3<T>::Abs() noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR x = XMVectorAbs(v1);
    Store(this, x);
}

template <typename T>
void Vector3<T>::Abs(Vector3<T>& result) const noexcept
{
    const XMVECTOR v1 = Load(this);
    const XMVECTOR x = XMVectorAbs(v1);
    Store(&result, x);
}

template <class T>
template <class U, class V>
float Vector3<T>::Distance(const Vector3<U>& v1, const Vector3<V>& v2) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR v = XMVectorSubtract(x2, x1);
    const XMVECTOR x = XMVector3Length(v);

    return XMVectorGetX(x);
}

template <class T>
template <class U, class V>
float Vector3<T>::DistanceSquared(const Vector3<U>& v1, const Vector3<V>& v2) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR v = XMVectorSubtract(x2, x1);
    const XMVECTOR x = XMVector3LengthSq(v);
    return XMVectorGetX(x);
}

template <class T>
template <class U, class V>
void Vector3<T>::Min(const Vector3<U>& v1, const Vector3<V>& v2, Vector3<T>& result) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x = XMVectorMin(x1, x2);

    Store(&result, x);
}

template <class T>
template <class U, class V>
Vector3<T> Vector3<T>::Min(const Vector3<U>& v1, const Vector3<V>& v2) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x = XMVectorMin(x2, x1);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
template <class U, class V>
void Vector3<T>::Max(const Vector3<U>& v1, const Vector3<V>& v2, Vector3<T>& result) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x = XMVectorMax(x2, x1);

    Store(&result, x);
}

template <class T>
template <class U, class V>
Vector3<T> Vector3<T>::Max(const Vector3<U>& v1, const Vector3<V>& v2) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x = XMVectorMax(x2, x1);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
template <class U, class V>
void Vector3<T>::Lerp(const Vector3<U>& v1, const Vector3<V>& v2, const float t, Vector3<T>& result) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x = XMVectorLerp(x2, x1, t);

    Store(&result, x);
}

template <class T>
template <class U, class V>
Vector3<T> Vector3<T>::Lerp(const Vector3<U>& v1, const Vector3<V>& v2, const float t) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x = XMVectorLerp(x2, x1, t);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
template <class U, class V>
void Vector3<T>::SmoothStep(const Vector3<U>& v1, const Vector3<V>& v2, float t, Vector3<T>& result) noexcept
{
    t = t > 1.0f ? 1.0f : t < 0.0f ? 0.0f : t; // Clamp value between 0 and 1
    t = t * t * (3.0f - 2.0f * t);
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x = XMVectorLerp(x1, x2, t);

    Store(&result, x);
}

template <class T>
template <class U, class V>
Vector3<T> Vector3<T>::SmoothStep(const Vector3<U>& v1, const Vector3<V>& v2, float t) noexcept
{
    t = t > 1.0f ? 1.0f : t < 0.0f ? 0.0f : t; // Clamp value between 0 and 1
    t = t * t * (3.0f - 2.0f * t);
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x = XMVectorLerp(x1, x2, t);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
template <class U, class V, class W>
void Vector3<T>::Barycentric(const Vector3<U>& v1, const Vector3<V>& v2, const Vector3<W>& v3, const float f,
                             const float g, Vector3<T>& result) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x3 = Load(&v3);
    const XMVECTOR x = XMVectorBaryCentric(x1, x2, x3, f, g);

    Store(&result, x);
}

template <class T>
template <class U, class V, class W>
Vector3<T> Vector3<T>::Barycentric(const Vector3<U>& v1, const Vector3<V>& v2, const Vector3<W>& v3,
                                   const float f, const float g) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x3 = Load(&v3);
    const XMVECTOR x = XMVectorBaryCentric(x1, x2, x3, f, g);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
template <class U, class V, class W, class X>
void Vector3<T>::CatmullRom(const Vector3<U>& v1, const Vector3<V>& v2, const Vector3<W>& v3, const Vector3<X>& v4,
                            const float t, Vector3<T>& result) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x3 = Load(&v3);
    const XMVECTOR x4 = Load(&v4);
    const XMVECTOR x = XMVectorCatmullRom(x1, x2, x3, x4, t);

    Store(&result, x);
}

template <class T>
template <class U, class V, class W, class X>
Vector3<T> Vector3<T>::CatmullRom(const Vector3<U>& v1, const Vector3<V>& v2, const Vector3<W>& v3,
                                  const Vector3<X>& v4, const float t) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&v2);
    const XMVECTOR x3 = Load(&v3);
    const XMVECTOR x4 = Load(&v4);
    const XMVECTOR x = XMVectorCatmullRom(x1, x2, x3, x4, t);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
template <class U, class V, class W, class X>
void Vector3<T>::Hermite(const Vector3<U>& v1, const Vector3<V>& t1, const Vector3<W>& v2, const Vector3<X>& t2,
                         const float t, Vector3<T>& result) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&t1);
    const XMVECTOR x3 = Load(&v2);
    const XMVECTOR x4 = Load(&t2);
    const XMVECTOR x = XMVectorHermite(x1, x2, x3, x4, t);

    Store(&result, x);
}

template <class T>
template <class U, class V, class W, class X>
Vector3<T> Vector3<T>::Hermite(const Vector3<U>& v1, const Vector3<V>& t1, const Vector3<W>& v2,
                               const Vector3<X>& t2, const float t) noexcept
{
    const XMVECTOR x1 = Load(&v1);
    const XMVECTOR x2 = Load(&t1);
    const XMVECTOR x3 = Load(&v2);
    const XMVECTOR x4 = Load(&t2);
    const XMVECTOR x = XMVectorHermite(x1, x2, x3, x4, t);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
template <class U, class V>
void Vector3<T>::Reflect(const Vector3<U>& iVec, const Vector3<V>& nVec, Vector3<T>& result) noexcept
{
    const XMVECTOR x1 = Load(&iVec);
    const XMVECTOR x2 = Load(&nVec);
    const XMVECTOR x = XMVector3Reflect(x1, x2);

    Store(&result, x);
}

template <class T>
template <class U, class V>
Vector3<T> Vector3<T>::Reflect(const Vector3<U>& iVec, const Vector3<V>& nVec) noexcept
{
    const XMVECTOR x1 = Load(&iVec);
    const XMVECTOR x2 = Load(&nVec);
    const XMVECTOR x = XMVector3Reflect(x1, x2);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
template <class U, class V>
void Vector3<T>::Refract(const Vector3<U>& iVec, const Vector3<V>& nVec, const float refractionIndex,
                         Vector3<T>& result) noexcept
{
    const XMVECTOR x1 = Load(&iVec);
    const XMVECTOR x2 = Load(&nVec);
    const XMVECTOR x = XMVector3Refract(x1, x2, refractionIndex);

    Store(&result, x);
}

template <class T>
template <class U, class V>
Vector3<T> Vector3<T>::Refract(const Vector3<U>& iVec, const Vector3<V>& nVec,
                               const float refractionIndex) noexcept
{
    const XMVECTOR x1 = Load(&iVec);
    const XMVECTOR x2 = Load(&nVec);
    const XMVECTOR x = XMVector3Refract(x1, x2, refractionIndex);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
template <class U>
_Use_decl_annotations_

void Vector3<T>::Transform(const Vector3<U>* varray, const size_t count, const Matrix& m,
                           Vector3<T>* resultArray) noexcept
{
    const XMMATRIX m1 = XMLoadFloat4x4(&m);
    XMVector3TransformCoordStream(resultArray, sizeof Vector3Base<U>::Vector, varray, sizeof Vector3Base<T>::Vector,
                                  count, m1);
}

template <class T>
template <class U>
void Vector3<T>::TransformNormal(const Vector3<U>& v, const Matrix& m, Vector3<T>& result) noexcept
{
    const XMVECTOR v1 = Load(&v);
    const XMMATRIX m1 = XMLoadFloat4x4(&m);
    const XMVECTOR x = XMVector3TransformNormal(v1, m1);

    Store(&result, x);
}

template <class T>
template <class U>
Vector3<T> Vector3<T>::TransformNormal(const Vector3<U>& v, const Matrix& m) noexcept
{
    const XMVECTOR v1 = Load(&v);
    const XMMATRIX m1 = XMLoadFloat4x4(&m);
    const XMVECTOR x = XMVector3TransformNormal(v1, m1);

    Vector3<T> result;
    Store(&result, x);
    return result;
}

template <class T>
template <class U>
_Use_decl_annotations_

void Vector3<T>::TransformNormal(const Vector3<U>* varray, const size_t count, const Matrix& m,
                                 Vector3<T>* resultArray) noexcept
{
    const XMMATRIX m1 = XMLoadFloat4x4(&m);
    XMVector3TransformNormalStream(resultArray, sizeof Vector3Base<U>::Vector, varray, sizeof Vector3Base<T>::Vector,
                                   count, m1);
}

template struct Vector3<float>;
template struct Vector3<int32_t>;
template struct Vector3<uint32_t>;


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

#ifndef UTILITY_INTERPOLATION_POLICY_DEF_HPP
#define UTILITY_INTERPOLATION_POLICY_DEF_HPP

// Std Lib Includes
#include <cmath>

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

// FRENSIE Includes
#include "Utility_QuantityTraits.hpp"
#include "Utility_IsFloatingPoint.hpp"
#include "Utility_DesignByContract.hpp"

namespace Utility{

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

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

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

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

// Calculate the unit base grid length (L)
/*! \details For LinLog and LogLog interpolation types the grid length
 * that is calculated is not a traditional length. It is the distance between
 * the processed upper independent value and the processed lower
 * independent value. This is why any units associated with the independent
 * grid limits are stripped away.
 */
template<typename ParentInterpolationType>
template<typename IndepType>
inline typename QuantityTraits<IndepType>::RawType
InterpolationHelper<ParentInterpolationType>::calculateUnitBaseGridLength(
                                       const IndepType grid_lower_indep_value,
                                       const IndepType grid_upper_indep_value )
{
  // Make sure the grid is valid
  testPrecondition( grid_lower_indep_value <= grid_upper_indep_value );
  testPrecondition( ParentInterpolationType::isIndepVarInValidRange(
                                                    grid_lower_indep_value ) );

  return ThisType::calculateUnitBaseGridLengthProcessed(
          ParentInterpolationType::processIndepVar( grid_lower_indep_value ),
          ParentInterpolationType::processIndepVar( grid_upper_indep_value ) );
}

// Calculate the unit base grid length from a processed grid (L)
/*! \details For LinLog and LogLog interpolation types the grid length
 * that is calculated is not a traditional length. It is the distance between
 * the processed upper independent value and the processed lower
 * independent value. This is why any units associated with the independent
 * grid limits are stripped away. Due to conversion of the independent
 * values from a cosine (mu) to a delta cosine ( 1 - mu ) + nudge for LogLogCos
 * and LinLogCos, it is assumed the processed grids are inverted to ensure they
 * are in ascending order.
 */
template<typename ParentInterpolationType>
template<typename T>
inline T InterpolationHelper<ParentInterpolationType>::calculateUnitBaseGridLengthProcessed(
                                     const T processed_grid_lower_indep_value,
                                     const T processed_grid_upper_indep_value )
{
  // Make sure the grid is valid
  testPrecondition( processed_grid_lower_indep_value <=
                    processed_grid_upper_indep_value );

  const T processed_grid_length =
    processed_grid_upper_indep_value - processed_grid_lower_indep_value;

  // Make sure the grid length is valid
  testPrecondition( processed_grid_length >= 0.0 );

  return processed_grid_length;
}

// Calculate the unit base independent variable (eta)
/*! \details The independent grid length is calculated using the
 * processed independent grid limits. It has been found that a tolerance of
 * 1e-3 works best for most applications.
 */
template<typename ParentInterpolationType>
template<typename IndepType>
inline typename QuantityTraits<IndepType>::RawType
InterpolationHelper<ParentInterpolationType>::calculateUnitBaseIndepVar(
          const IndepType indep_var,
          const IndepType indep_var_min,
          const typename QuantityTraits<IndepType>::RawType indep_grid_length,
          const double tol )
{
  // Make sure the intermediate grid min indep var is valid
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_min ) );
  testPrecondition( ParentInterpolationType::isIndepVarInValidRange( indep_var_min ) );
  // Make sure the intermediate grid length is valid
  testPrecondition( !QuantityTraits<typename QuantityTraits<IndepType>::RawType>::isnaninf( indep_grid_length ) );
  testPrecondition( indep_grid_length > 0.0 );
  // Make sure the independent variable is valid
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var ) );
  testPrecondition( ParentInterpolationType::isIndepVarInValidRange( indep_var ) );
  testPrecondition( indep_var >=
                    ThisType::calculateFuzzyLowerBound( indep_var_min, tol ) );
  remember( typename QuantityTraits<IndepType>::RawType test_difference =
            ParentInterpolationType::processIndepVar(indep_var) -
            ParentInterpolationType::processIndepVar(indep_var_min) );

  testPrecondition( test_difference <= ThisType::calculateFuzzyUpperBound(
                                                    indep_grid_length, tol ) );

  return calculateUnitBaseIndepVarProcessed(
                       ParentInterpolationType::processIndepVar(indep_var),
                       ParentInterpolationType::processIndepVar(indep_var_min),
                       indep_grid_length,
                       tol );
}

// Calculate the unit base independent variable (eta)
/*! \details It has been found that a tolerance of 1e-3 works best for most
 * applications.
 */
template<typename ParentInterpolationType>
template<typename T>
inline T InterpolationHelper<ParentInterpolationType>::calculateUnitBaseIndepVarProcessed(
                                               const T processed_indep_var,
                                               const T processed_indep_var_min,
                                               const T indep_grid_length,
                                               const double tol )
{
  // Make sure the intermediate grid min indep var is valid
  testPrecondition( !QuantityTraits<T>::isnaninf( processed_indep_var_min ) );
  // Make sure the independent y variable is valid
  testPrecondition( !QuantityTraits<T>::isnaninf(processed_indep_var) );
  testPrecondition( processed_indep_var >= ThisType::calculateFuzzyLowerBound(
                                              processed_indep_var_min, tol ) );
  remember( T test_difference = processed_indep_var - processed_indep_var_min);
  testPrecondition( test_difference <= ThisType::calculateFuzzyUpperBound(
                                                    indep_grid_length, tol ) );
  // Make sure the intermediate grid length is valid
  testPrecondition( !QuantityTraits<T>::isnaninf( indep_grid_length ) );
  testPrecondition( indep_grid_length > 0.0 );

  T eta = (processed_indep_var - processed_indep_var_min)/indep_grid_length;

  // Check for rounding errors and correct
  if( eta > 1.0 )
  {
    if( eta - 1.0 < tol )
      eta = 1.0;
  }
  else if( eta < 0.0 )
  {
    if( eta > -tol )
      eta = 0.0;
  }

  // Make sure eta is valid
  testPostcondition( eta >= 0.0 );
  testPostcondition( eta <= 1.0 );

  return eta;
}

// Calculate the independent variable (from eta)
/*! \details It has been found that a tolerance of 1e-3 works best for most
 * applications.
 */
template<typename ParentInterpolationType>
template<typename IndepType>
inline IndepType
InterpolationHelper<ParentInterpolationType>::calculateIndepVar(
          const typename QuantityTraits<IndepType>::RawType eta,
          const IndepType indep_var_min,
          const typename QuantityTraits<IndepType>::RawType indep_grid_length,
          const double tol )
{
  // Make sure the eta value is valid
  testPrecondition( eta >= 0.0 );
  testPrecondition( eta <= 1.0 );
  // Make sure the grid min indep var is valid
  testPrecondition( !QuantityTraits<IndepType>::isnaninf( indep_var_min ) );
  testPrecondition( ParentInterpolationType::isIndepVarInValidRange( indep_var_min ) );
  // Make sure the grid length is valid
  testPrecondition( !QuantityTraits<typename QuantityTraits<IndepType>::RawType>::isnaninf( indep_grid_length ) );
  testPrecondition( indep_grid_length >= 0.0 );

  IndepType grid_indep_var( QuantityTraits<IndepType>::initializeQuantity(
    ParentInterpolationType::recoverProcessedIndepVar(
                    ParentInterpolationType::processIndepVar( indep_var_min ) +
                    indep_grid_length*eta ) ) );

  // Check for rounding errors
  if( grid_indep_var < indep_var_min &&
      grid_indep_var >= ThisType::calculateFuzzyLowerBound(indep_var_min, tol))
    grid_indep_var = indep_var_min;

  // Make sure the calculated independent variable is valid
  testPostcondition( grid_indep_var >= indep_var_min );

  return grid_indep_var;
}

// Calculate the processed independent variable (from eta)
/*! \details A tolerance is not required with this method because no variable
 * processing is done. Due to conversion of the independent values from a cosine
 * (mu) to a delta cosine ( 1 - mu ) + nudge for LogLogCos and LinLogCos, it is
 * assumed the processed grids are inverted to ensure they are in ascending
 * order.
 */
template<typename ParentInterpolationType>
template<typename T>
inline T InterpolationHelper<ParentInterpolationType>::calculateProcessedIndepVar(
                                               const T eta,
                                               const T processed_indep_var_min,
                                               const T indep_grid_length )
{
  // Make sure the eta value is valid
  testPrecondition( eta >= 0.0 );
  testPrecondition( eta <= 1.0 );
  // Make sure the grid min indep var is valid
  testPrecondition( !QuantityTraits<T>::isnaninf( processed_indep_var_min ) );
  // Make sure the grid length is valid
  testPrecondition( !QuantityTraits<T>::isnaninf( indep_grid_length ) );
  testPrecondition( indep_grid_length >= 0.0 );

  return processed_indep_var_min + indep_grid_length*eta;
}

// Calculate the "fuzzy" lower bound (lower bound with roundoff tolerance)
/*! \details It has been found that a tolerance of 1e-3 works best for most
 * applications.
 */
template<typename ParentInterpolationType>
template<typename T>
inline T InterpolationHelper<ParentInterpolationType>::calculateFuzzyLowerBound(
                                                             const T value,
                                                             const double tol )
{
  if( value < QuantityTraits<T>::zero() )
    return value*(1+tol);
  else
    return value*(1-tol);
}

// Calculate the "fuzzy" upper bound (upper bound with roundoff tolerance)
/*! \details It has been found that a tolerance of 1e-3 works best for most
 * applications
 */
template<typename ParentInterpolationType>
template<typename T>
inline T InterpolationHelper<ParentInterpolationType>::calculateFuzzyUpperBound(
                                                             const T value,
                                                             const double tol )
{
  if( value > QuantityTraits<T>::zero() )
    return value*(1+tol);
  else
    return value*(1-tol);
}

// The name of the policy
template<typename ParentInterpolationType>
inline std::string InterpolationHelper<ParentInterpolationType>::name()
{
  return Utility::typeName<ParentInterpolationType>();
}

// Get the interpolation type
inline InterpolationType LogLog::getInterpolationType()
{
  return LOGLOG_INTERPOLATION;
}

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

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

// Interpolate between two points using the indep variable ratio (beta)
template<typename T, typename DepType>
inline DepType LogLog::interpolate( const T beta,
                                    const DepType dep_var_0,
                                    const DepType dep_var_1 )
{
  // The IndepType must be a floating point type
  testStaticPrecondition( (std::is_floating_point<typename QuantityTraits<T>::RawType>::value) );
  testStaticPrecondition( (std::is_floating_point<typename QuantityTraits<DepType>::RawType>::value ) );
  // Make sure the independent variables are valid
  testPrecondition( beta >= QuantityTraits<T>::zero() );
  testPrecondition( beta <= QuantityTraits<T>::one() );
  // Make sure the dependent variables are valid
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_0 ) );
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_1 ) );
  testPrecondition( LogLog::isDepVarInValidRange( dep_var_0 ) );
  testPrecondition( LogLog::isDepVarInValidRange( dep_var_1 ) );

  return dep_var_0*pow((dep_var_1/dep_var_0),beta);
}

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

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

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

  return log( getRawQuantity(indep_var) );
}

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

  return log( getRawQuantity(dep_var) );
}

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

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

// Test if the independent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LogLog::isIndepVarInValidRange( const T indep_var )
{
  return indep_var > QuantityTraits<T>::zero();
}

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

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

// Get the interpolation type
inline InterpolationType LogLin::getInterpolationType()
{
  return LOGLIN_INTERPOLATION;
}

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

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

// Interpolate between two points using the indep variable ratio (beta)
template<typename T, typename DepType>
inline DepType LogLin::interpolate( const T beta,
                                    const DepType dep_var_0,
                                    const DepType dep_var_1 )
{
  // The IndepType must be a floating point type
  testStaticPrecondition( (std::is_floating_point<typename QuantityTraits<T>::RawType>::value) );
  testStaticPrecondition( (std::is_floating_point<typename QuantityTraits<DepType>::RawType>::value) );
  // Make sure the independent variables are valid
  testPrecondition( beta >= QuantityTraits<T>::zero() );
  testPrecondition( beta <= QuantityTraits<T>::one() );
  // Make sure the dependent variables are valid
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_0 ) );
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_1 ) );
  testPrecondition( LogLin::isDepVarInValidRange( dep_var_0 ) );
  testPrecondition( LogLin::isDepVarInValidRange( dep_var_1 ) );

  return dep_var_0*pow((dep_var_1/dep_var_0),beta);
}

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

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

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

  return getRawQuantity(indep_var);
}

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

  return log( getRawQuantity(dep_var) );
}

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

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

// Test if the independent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LogLin::isIndepVarInValidRange( const T indep_var )
{
  return true;
}

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

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

// Get the interpolation type
inline InterpolationType LinLog::getInterpolationType()
{
  return LINLOG_INTERPOLATION;
}

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

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

  return dep_var_0 + term_2;
}

// Interpolate between two points using the indep variable ratio (beta)
template<typename T, typename DepType>
inline DepType LinLog::interpolate( const T beta,
                                    const DepType dep_var_0,
                                    const DepType dep_var_1 )
{
  // The IndepType must be a floating point type
  testStaticPrecondition( (std::is_floating_point<typename QuantityTraits<T>::RawType>::value) );
  testStaticPrecondition( (std::is_floating_point<typename QuantityTraits<DepType>::RawType>::value) );
  // Make sure the independent variables are valid
  testPrecondition( beta >= QuantityTraits<T>::zero() );
  testPrecondition( beta <= QuantityTraits<T>::one() );
  // Make sure the dependent variables are valid
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_0 ) );
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_1 ) );
  testPrecondition( LinLog::isDepVarInValidRange( dep_var_0 ) );
  testPrecondition( LinLog::isDepVarInValidRange( dep_var_1 ) );

  return dep_var_0 + (dep_var_1 - dep_var_0)*beta;
}

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

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

  return log( getRawQuantity(indep_var) );
}

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

  return getRawQuantity(dep_var);
}

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

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

// Test if the independent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LinLog::isIndepVarInValidRange( const T indep_var )
{
  return indep_var > QuantityTraits<T>::zero();
}

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

  return true;
}

// Get the interpolation type
inline InterpolationType LinLin::getInterpolationType()
{
  return LINLIN_INTERPOLATION;
}

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

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

  return dep_var_0 + term_2;
}

// Interpolate between two points using the indep variable ratio (beta)
template<typename T, typename DepType>
inline DepType LinLin::interpolate( const T beta,
                                    const DepType dep_var_0,
                                    const DepType dep_var_1 )
{
  // The IndepType must be a floating point type
  testStaticPrecondition( (std::is_floating_point<typename QuantityTraits<T>::RawType>::value) );
  testStaticPrecondition( (std::is_floating_point<typename QuantityTraits<DepType>::RawType>::value) );
  // Make sure the independent variables are valid
  testPrecondition( beta >= QuantityTraits<T>::zero() );
  testPrecondition( beta <= QuantityTraits<T>::one() );
  // Make sure the dependent variables are valid
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_0 ) );
  testPrecondition( !QuantityTraits<DepType>::isnaninf( dep_var_1 ) );
  testPrecondition( LinLin::isDepVarInValidRange( dep_var_0 ) );
  testPrecondition( LinLin::isDepVarInValidRange( dep_var_1 ) );

  return dep_var_0 + (dep_var_1 - dep_var_0)*beta;
}

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

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

  return getRawQuantity(indep_var);
}

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

  return getRawQuantity(dep_var);
}

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

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

// Test if the independent value is in a valid range (doesn't check nan/inf)
template<typename T>
inline bool LinLin::isIndepVarInValidRange( const T indep_var )
{
  return true;
}

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

  return true;
}

} // end Utility namespace

#endif // end UTILITY_INTERPOLATION_POLICY_DEF_HPP

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


/* CirKit: A circuit toolkit
 * Copyright (C) 2009-2015  University of Bremen
 * Copyright (C) 2015-2017  EPFL
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "error_metrics.hpp"

#include <cmath>
#include <stack>

#include <boost/algorithm/string/join.hpp>
#include <boost/dynamic_bitset.hpp>
#include <boost/format.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/combine.hpp>
#include <boost/range/numeric.hpp>
#include <boost/version.hpp>

#include <core/utils/bitset_utils.hpp>
#include <core/utils/timer.hpp>
#include <classical/dd/arithmetic.hpp>
#include <classical/dd/bdd_to_truth_table.hpp>
#include <classical/dd/characteristic.hpp>
#include <classical/dd/count_solutions.hpp>

namespace cirkit
{

/******************************************************************************
 * Types                                                                      *
 ******************************************************************************/

/******************************************************************************
 * Private functions                                                          *
 ******************************************************************************/

inline void assert_valid( const std::vector<bdd>& f, const std::vector<bdd>& fhat )
{
  assert( f.size() == fhat.size() );
  assert( !f.empty() );
}

std::vector<bdd> compute_diff( const std::vector<bdd>& f, const std::vector<bdd>& fhat, bool print_truthtables = false )
{
  assert_valid( f, fhat );

  const auto to = f.size() + 1u;

  const auto zf    = zero_extend( f, to );
  const auto zfhat = zero_extend( fhat, to );
  const auto subtr = bdd_subtract( zf, zfhat );
  const auto diff  = bdd_abs( subtr );

#if ( BOOST_VERSION / 100000 ) >= 1 && ( BOOST_VERSION / 100 % 1000 ) >= 56
  if ( print_truthtables )
  {
    using boost::adaptors::transformed;
    using boost::format;
    using boost::str;

    auto valid = true;
    auto max   = 0u;
    auto sum   = 0u;

    std::cout << std::endl << "    f    zf  fhat zfhat subtr  diff valid" << std::endl
              << boost::join( boost::combine( bdds_to_truth_table_unsigned( f ),
                                              bdds_to_truth_table_unsigned( zf ),
                                              bdds_to_truth_table_unsigned( fhat ),
                                              bdds_to_truth_table_unsigned( zfhat ),
                                              bdds_to_truth_table_signed( subtr ),
                                              bdds_to_truth_table_unsigned( diff ) )
                              | transformed( [&]( const boost::tuple<unsigned, unsigned, unsigned, unsigned, int, unsigned>& p ) {
                                  const auto vf     = boost::get<0>( p );
                                  const auto vzf    = boost::get<1>( p );
                                  const auto vfhat  = boost::get<2>( p );
                                  const auto vzfhat = boost::get<3>( p );
                                  const auto vsubtr = boost::get<4>( p );
                                  const auto vdiff  = boost::get<5>( p );

                                  const auto row_valid = ( vf == vzf ) && ( vfhat == vzfhat ) && ( vsubtr == static_cast<int>( vzf - vzfhat ) ) && ( abs( vsubtr ) == static_cast<int>( vdiff ) );
                                  valid = valid && row_valid;
                                  max   = std::max( max, vdiff );
                                  sum  += vdiff;

                                  return str( format( "%5d %5d %5d %5d %5d %5d     %d") % vf % vzf % vfhat % vzfhat % vsubtr % vdiff % row_valid );
                                } ), "\n" ) << std::endl
              << format( "valid: %d, max: %d, avg: %.2f" ) % valid % max % ( sum / pow( 2.0, f.front().manager->num_vars() ) ) << std::endl;
  }
#endif

  assert( diff.size() == to );
  assert( diff.back().index == 0u );

  return diff;
}

boost::multiprecision::uint256_t get_max_value( const std::vector<bdd>& f )
{
  assert( !f.empty() );

  boost::dynamic_bitset<> bs( f.size() );
  auto mask = f.front().manager->bdd_top();

  for ( int k = f.size() - 1; k >= 0; --k )
  {
    auto r = mask && f[k];
    if ( r.index != 0 )
    {
      bs.set( k );
      mask = r;
    }
  }

  return to_multiprecision<boost::multiprecision::uint256_t>( bs );
}

boost::multiprecision::uint256_t get_max_value_with_chi( const std::vector<bdd>& f )
{
  bdd_manager mgr_chi( f.front().manager->num_vars() + f.size(), 10u ); /* can we approximate the number of used nodes? */

  auto fr  = f; boost::reverse( fr );
  auto chi = characteristic_function( fr, mgr_chi );

  boost::dynamic_bitset<> bs( f.size() );

  while ( chi.var() < f.size() )
  {
    if ( chi.high().is_bot() )
    {
      chi = chi.low();
    }
    else
    {
      bs.set( f.size() - 1u - chi.var() );
      chi = chi.high();
    }
  }

  return to_multiprecision<boost::multiprecision::uint256_t>( bs );
}

boost::multiprecision::uint256_t get_weighted_sum( const std::vector<bdd>& f )
{
  auto level = f.size();
  bdd_manager mgr_chi( f.front().manager->num_vars() + level, 10u ); /* can we approximate the number of used nodes? */

  auto fr = f; boost::reverse( fr );
  auto chi = characteristic_function( fr, mgr_chi );

  boost::multiprecision::uint256_t sum = 0;
  const boost::multiprecision::uint256_t one = 1;
  std::stack<std::pair<bdd, boost::dynamic_bitset<>>> stack;

  stack.push( {chi, boost::dynamic_bitset<>( level )} );

  while ( !stack.empty() )
  {
    auto p = stack.top(); stack.pop();

    if ( p.first.var() >= level )
    {
      sum += to_multiprecision<boost::multiprecision::uint256_t>( p.second ) * ( count_solutions( p.first ) / ( one << level ) );
    }
    else
    {
      stack.push( {p.first.low(), p.second} );
      p.second.set( p.first.var() );
      stack.push( {p.first.high(), p.second} );
    }
  }

  return sum;
}

/******************************************************************************
 * Public functions                                                           *
 ******************************************************************************/

boost::multiprecision::uint256_t error_rate( const std::vector<bdd>& f, const std::vector<bdd>& fhat,
                                             const properties::ptr& settings,
                                             const properties::ptr& statistics )
{
  properties_timer t( statistics );

  assert_valid( f, fhat );

  auto h = f.front().manager->bdd_bot();

  for ( auto i = 0u; i < f.size(); ++i )
  {
    h = h || ( f[i] ^ fhat[i] );
  }

  return count_solutions( h );
}

boost::multiprecision::uint256_t worst_case( const std::vector<bdd>& f, const std::vector<bdd>& fhat,
                                             const properties::ptr& settings,
                                             const properties::ptr& statistics )
{
  auto print_truthtables = get( settings, "print_truthtables", false );
  auto maximum_method    = get( settings, "maximum_method",    worst_case_maximum_method::shift );

  properties_timer t( statistics );

  auto diff = compute_diff( f, fhat, print_truthtables );

  switch ( maximum_method )
  {
  case worst_case_maximum_method::shift:
    return get_max_value( diff );
  case worst_case_maximum_method::chi:
    return get_max_value_with_chi( diff );
  default:
    assert( false );
  }
}

boost::multiprecision::cpp_dec_float_100 average_case( const std::vector<bdd>& f, const std::vector<bdd>& fhat,
                                                       const properties::ptr& settings, const properties::ptr& statistics )
{
  auto print_truthtables = get( settings, "print_truthtables", false );

  properties_timer t( statistics );

  auto diff = compute_diff( f, fhat, print_truthtables );

  const boost::multiprecision::uint256_t one = 1;
  return boost::multiprecision::cpp_dec_float_100( get_weighted_sum( diff ) ) /
         boost::multiprecision::cpp_dec_float_100( one << f.front().manager->num_vars() );
}

}

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End: