//============================================================================
// Daniel J. Greenhoe
// normed linear space R^2
//=============================================================================
//=====================================
// headers
//=====================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <Eigen/Dense>  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89325
#include "r1.h"
#include "r4.h"
typedef Eigen::Matrix< double, 4, 1 > Vector4d;

//=====================================
// oquad
//=====================================
//-----------------------------------------------------------------------------
// oquad constructors
//-----------------------------------------------------------------------------
oquad::oquad(void)
{
  xx.at(0) = 0.0;
  xx.at(1) = 0.0;
  xx.at(2) = 0.0;
  xx.at(3) = 0.0;
}

oquad::oquad(double u0, double u1, double u2, double u3)
{
  xx.at(0) = u0;
  xx.at(1) = u1;
  xx.at(2) = u2;
  xx.at(3) = u3;
}

oquad::oquad(double u)
{
  xx.at(0) = u;
  xx.at(1) = u;
  xx.at(2) = u;
  xx.at(3) = u;
}

//-----------------------------------------------------------------------------
//! \brief oquad put member functions
//-----------------------------------------------------------------------------
void oquad::put(double u)
{
  xx.at(0) = u;
  xx.at(1) = u;
  xx.at(2) = u;
  xx.at(3) = u;
}

void oquad::put(oquad u)
{
  xx.at(0) = u.get(0);
  xx.at(1) = u.get(1);
  xx.at(2) = u.get(2);
  xx.at(3) = u.get(3);
}

//-----------------------------------------------------------------------------
//! \brief Write values to oquad
//-----------------------------------------------------------------------------
void oquad::put(double u0, double u1, double u2, double u3)
{
  xx.at(0) = u0;
  xx.at(1) = u1;
  xx.at(2) = u2;
  xx.at(3) = u3;
}

//-----------------------------------------------------------------------------
//! \brief Write values to oquad
//-----------------------------------------------------------------------------
void oquad::put(int n,double u)
{
  xx.at(n) = u;
}

//-----------------------------------------------------------------------------
//! \brief return the 4-tuple value
//-----------------------------------------------------------------------------
oquad oquad::get(void)
{
  oquad u;
  u.put( 0, get1() );
  u.put( 1, get2() );
  u.put( 2, get3() );
  u.put( 3, get4() );
  return u;
}

//-----------------------------------------------------------------------------
//! \brief return the minimum element of the 4 tupple
//-----------------------------------------------------------------------------
double oquad::min(void) const
{
  const Eigen::Map< const Vector4d > a( getdata() );
  const double minVal = a.minCoeff();
  return minVal;
}

//-----------------------------------------------------------------------------
//! \brief return the maximum element of the 4 tupple
//-----------------------------------------------------------------------------
double oquad::max(void) const
{
  const Eigen::Map< const Vector4d > a( getdata() );
  const double maxVal = a.maxCoeff();
  return maxVal;
}

//-----------------------------------------------------------------------------
//! \brief print the tuple
//-----------------------------------------------------------------------------
void oquad::list(const char *str1, const char *str2) const
{
  if(strlen(str1)!=0)printf("%s",str1);
  putchar('(');
  printf("%9.6lf,", get1() );
  printf("%9.6lf,", get2() );
  printf("%9.6lf,", get3() );
  printf("%9.6lf)", get4() );
  if(strlen(str2)!=0)printf("%s",str2);
}

//=====================================
// vectR4 functions
//=====================================
//-----------------------------------------------------------------------------
//! \brief return the 4-tuple value
//-----------------------------------------------------------------------------
const vectR4 vectR4::get(void)
{
  vectR4 u;
  u.put( 0, get1() );
  u.put( 1, get2() );
  u.put( 2, get3() );
  u.put( 3, get4() );
  return u;
}

//-----------------------------------------------------------------------------
//! \brief magnitude
//-----------------------------------------------------------------------------
double vectR4::mag(void) const
{
  const Eigen::Map< const Vector4d > a( getdata() );
  return a.norm();
}

//-----------------------------------------------------------------------------
//! \brief Multiply the vector by a scalar a
//-----------------------------------------------------------------------------
vectR4 vectR4::mpy(const double a)
{
  vectR4 w;
  const Eigen::Map< const Vector4d > vv( getdata() );
  Eigen::Map< Vector4d > ww( w.getdataa() );
  ww = a * vv;
  return w;
}

//-----------------------------------------------------------------------------
//! \brief operator: +=
//-----------------------------------------------------------------------------
void vectR4::operator+=(vectR4 q)
{
  Eigen::Map< Vector4d > pp( getdataa() );
  const Eigen::Map< const Vector4d > qq( q.getdata() );
  pp = pp + qq;
}

//-----------------------------------------------------------------------------
//! \brief operator: -=
//-----------------------------------------------------------------------------
void vectR4::operator-=(vectR4 q)
{
  Eigen::Map< Vector4d > pp( getdataa() );
  const Eigen::Map< const Vector4d > qq( q.getdata() );
  pp = pp - qq;
}

//-----------------------------------------------------------------------------
//! \brief operator: -=
//-----------------------------------------------------------------------------
void vectR4::operator*=(double a)
{
  vectR4  p=get();
  p = a*p;
  put(p);
}

//-----------------------------------------------------------------------------
//! \brief operator: a*y
//-----------------------------------------------------------------------------
vectR4 operator*(const double a, const vectR4 x)
{
  vectR4 y;
  const Eigen::Map< const Vector4d > xx( x.getdata() );
  Eigen::Map< Vector4d > yy( y.getdataa() );
  yy = a * xx;
  return y;
}

//-----------------------------------------------------------------------------
//! \brief operator: dot product of p and q
//-----------------------------------------------------------------------------
double operator^(vectR4 p,vectR4 q)
{
  const  Eigen::Map< const Vector4d > pp( p.getdata() );
  const  Eigen::Map< const Vector4d > qq( q.getdata() );
  double innerProduct = pp.adjoint() * qq;
  return innerProduct;
}

//=====================================
//! \brief seqR4
//=====================================
//-----------------------------------------------------------------------------
//! \brief constructor initializing seqR1 to 0
//-----------------------------------------------------------------------------
seqR4::seqR4(long M)
{
  N=M;
  x = new vectR4[N];
  clear();
}

//-----------------------------------------------------------------------------
//! \brief constructor initializing seqR1 to <u>
//-----------------------------------------------------------------------------
seqR4::seqR4( long M, double u )
{
  N=M;
  x = new vectR4[N];
  fill( u );
}

//-----------------------------------------------------------------------------
//! \brief Fill the seqR3 with a value 0
//-----------------------------------------------------------------------------
void seqR4::clear(void)
{
  fill( 0.0 );
}

//-----------------------------------------------------------------------------
//! \brief fill the seqR4 with a value <u>
//-----------------------------------------------------------------------------
void seqR4::fill(double u)
{
  for(long n=0; n<N; n++) put( n, u );
}

//-----------------------------------------------------------------------------
//! \brief put a single value u=(u1,u2,u3,u4) into the seqR4 x at location n
//-----------------------------------------------------------------------------
int seqR4::put(long n, double u1,double u2,double u3,double u4)
{
  int retval=0;
  if(n<N)x[n].put(u1,u2,u3,u4);
  else{
    fprintf(stderr,"n=%ld larger than seqR1 size N=%ld\n",n,N);
    retval=-1;
    }
  return retval;
}

//-----------------------------------------------------------------------------
//! \brief put a single value (u,u,u,u) into the seqR4 x at location n
//-----------------------------------------------------------------------------
int seqR4::put(long n, vectR4 u)
{
  int retval=0;
  if(n<N)x[n].put(u);
  else{
    fprintf(stderr,"n=%ld larger than seqR4 size N=%ld\n",n,N);
    retval=-1;
    }
  return retval;
}

//-----------------------------------------------------------------------------
//! \brief list contents of sequence
//-----------------------------------------------------------------------------
void seqR4::list(const long start, const long end, const char* str1, const char *str2, FILE *fptr){
  long n,m;
  vectR4 p;
  if(strlen(str1)>0){
    printf("%s",str1);
    if(fptr!=NULL)fprintf(fptr,"%s",str1);
    }
  for(n=start,m=1; n<=end; n++,m++){
    p=x[n];
    printf("(%5.2lf,%5.2lf,%5.2lf,%5.2lf) ",p.get1(),p.get2(),p.get3(),p.get4());
    if(m%2==0)printf("\n");
    if(fptr!=NULL){
      fprintf(fptr,"(%5.2lf,%5.2lf,%5.2lf,%5.2lf) ",p.get1(),p.get2(),p.get3(),p.get4());
      if(m%2==0)fprintf(fptr,"\n");
      }
    }
  if(strlen(str2)>0){
    printf("%s",str2);
    if(fptr!=NULL)fprintf(fptr,"%s",str2);
    }
  }

//-----------------------------------------------------------------------------
//! \brief list contents of seqR1 using 1 digit per element
//-----------------------------------------------------------------------------
void seqR4::list1(const long start, const long end, const char *str1, const char *str2,FILE *fptr){
  long n,m;
  vectR4 p;
  if(strlen(str1)>0){
    printf("%s",str1);
    if(fptr!=NULL)fprintf(fptr,"%s",str1);
    }
  for(n=start,m=1; n<=end; n++,m++){
    p=x[n];
    printf(" %1.0lf%1.0lf%1.0lf%1.0lf",p.get1(),p.get2(),p.get3(),p.get4());
    if(fptr!=NULL)fprintf(fptr," %1.0lf%1.0lf%1.0lf%1.0lf",p.get1(),p.get2(),p.get3(),p.get4());
    if(m%10==0)printf("\n");
    if(fptr!=NULL)if(m%10==0)fprintf(fptr,"\n");
    }
  if(strlen(str2)>0){
    printf("%s",str2);
    if(fptr!=NULL)fprintf(fptr,"%s",str2);
    }
  }

/*=====================================
//! \brief external operations
 *=====================================*/
//-----------------------------------------------------------------------------
//! \brief operator: return p+q
//-----------------------------------------------------------------------------
vectR4 operator+(vectR4 p, vectR4 q){
  int i;
  vectR4 y;
  for(i=0;i<4;i++)y.put(i,p.get(i)+q.get(i));
  return y;
  }

//-----------------------------------------------------------------------------
//! \brief operator: return p-q
//-----------------------------------------------------------------------------
vectR4 operator-(vectR4 p, vectR4 q){
  int i;
  vectR4 y;
  for(i=0;i<4;i++)y.put(i,p.get(i)-q.get(i));
  return y;
  }

//-----------------------------------------------------------------------------
//! \brief operator: return -p
//-----------------------------------------------------------------------------
vectR4 operator-(vectR4 p){
  vectR4 q;
  int i;
  for(i=0;i<4;i++)q.put(i,-p.get(i));
  return q;
  }

//-----------------------------------------------------------------------------
//! \brief return the angle theta in radians between the two vectors induced by
//! \brief the points <p> and <q> in the space R^4.
//! \brief on SUCCESS return theta in the closed interval [0:PI]
//! \brief on ERROR   return negative value or exit with value EXIT_FAILURE
//-----------------------------------------------------------------------------
double pqtheta(const vectR4 p, const vectR4 q){
  const double rp = p.r();
  const double rq = q.r();
  double y,theta;
  if(rp==0) return -1;
  if(rq==0) return -2;
  y = (p^q)/(rp*rq);
  if(y>+1)  {fprintf(stderr,"\nERROR using pqtheta(vectR4 p, vectR4 q): (p^q)/(rp*rq)=%lf>+1\n",y); exit(EXIT_FAILURE);}
  if(y<-1)  {fprintf(stderr,"\nERROR using pqtheta(vectR4 p, vectR4 q): (p^q)/(rp*rq)=%lf<-1\n",y); exit(EXIT_FAILURE);}
  theta = acos(y);
  return theta;
  }

/*=====================================
//! \brief external operations
 *=====================================*/
//-----------------------------------------------------------------------------
//! \brief compute magnitude of R^1 sequence
//-----------------------------------------------------------------------------
//int mag(seqR4 *xR4, seqR1 *ymag){
//  const long Nx=xR4->getN();
//  const long Ny=ymag->getN();
//  long n;
//  int retval=0;
//  vectR4 u;
//  ymag->clear();
//  if(Nx!=Ny){
//    fprintf(stderr,"ERROR using y=mag(xR4): lengths of xR4 (%ld) and ymag (%ld) differ.\n",Nx,Ny);
//    exit(EXIT_FAILURE);
//    }
//  for(n=0;n<Nx;n++){
//    u=xR4->get(n);
//    ymag->put(n,u.mag());
//    }
//  return retval;
//  }


// $Id$
//
//  Copyright (C) 2003-2009 Greg Landrum and Rational Discovery LLC
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
#include "MolOps.h"
#include "RDKitBase.h"
#include <RDGeneral/Invariant.h>
#include <RDGeneral/RDLog.h>

#include <boost/dynamic_bitset.hpp>
#include <iomanip>

namespace RDKit {
namespace MolOps {
double computeBalabanJ(double *distMat, int nb, int nAts) {
  // NOTE that the distance matrix is modified here for the sake of
  // efficiency
  PRECONDITION(distMat, "bogus distance matrix")
  double sum = 0.0;
  int nActive = nAts;
  int mu = nb - nActive + 1;

  if (mu == -1) return 0.0;

  for (int i = 0; i < nAts; i++) {
    int iTab = i * nAts;
    sum = 0.0;
    for (int j = 0; j < nAts; j++) {
      if (j != i) {
        sum += distMat[iTab + j];
      }
    }
    distMat[iTab + i] *= sum;
  }
  double accum = 0.0;
  for (int i = 0; i < nAts; i++) {
    int iTab = i * nAts + i;
    for (int j = i + 1; j < nAts; j++) {
      // NOTE: this isn't strictly the Balaban J value, because we
      // aren't only adding in adjacent atoms.  Since we're doing a
      // discriminator, that shouldn't be a problem.
      if (j != i) {
        accum += (1.0 / sqrt(distMat[iTab] * distMat[j * nAts + j]));
      }
    }
  }
  return nActive / ((mu + 1) * accum);
}

double computeBalabanJ(const ROMol &mol, bool useBO, bool force,
                       const std::vector<int> *bondPath, bool cacheIt) {
  RDUNUSED_PARAM(useBO);
  double res = 0.0;
  if (!force && mol.hasProp(common_properties::BalabanJ)) {
    mol.getProp(common_properties::BalabanJ, res);
  } else {
    double *dMat;
    int nb = 0, nAts = 0;
    if (bondPath) {
      boost::dynamic_bitset<> atomsUsed(mol.getNumAtoms());
      boost::dynamic_bitset<> bondsUsed(mol.getNumBonds());
      for (int ci : *bondPath) {
        bondsUsed[ci] = 1;
      }
      std::vector<const Bond *> bonds;
      bonds.reserve(bondPath->size());
      std::vector<int> atomsInPath;
      atomsInPath.reserve(bondPath->size() + 1);

      ROMol::EDGE_ITER beg, end;
      boost::tie(beg, end) = mol.getEdges();
      while (beg != end) {
        const Bond *bond = mol[*beg];
        if (bondsUsed[bond->getIdx()]) {
          int begIdx = bond->getBeginAtomIdx();
          int endIdx = bond->getEndAtomIdx();
          bonds.push_back(bond);
          if (!atomsUsed[begIdx]) {
            atomsInPath.push_back(begIdx);
            atomsUsed[begIdx] = 1;
          }
          if (!atomsUsed[endIdx]) {
            atomsInPath.push_back(endIdx);
            atomsUsed[endIdx] = 1;
          }
        }
        beg++;
      }
      nb = rdcast<int>(bondPath->size());
      nAts = rdcast<int>(atomsInPath.size());
      dMat = MolOps::getDistanceMat(mol, atomsInPath, bonds, true, true);
      res = computeBalabanJ(dMat, nb, nAts);
      delete[] dMat;
    } else {
      nb = mol.getNumBonds();
      nAts = mol.getNumAtoms();
      dMat = MolOps::getDistanceMat(mol, true, true, true, nullptr);
      res = computeBalabanJ(dMat, nb, nAts);
      delete[] dMat;
    }

    if (cacheIt) mol.setProp(common_properties::BalabanJ, res, true);
  }
  return res;
}
}  // end of namespace MolOps
}  // end of namespace RDKit


#include "smooth_pair_finder.h"

#include <NTL/RR.h>

#include "norm_finding.h"
#include "util.h"

namespace {
    using namespace NTL;
}

namespace gnfs {
    SmoothPairFinder::SmoothPairFinder(const ZZ& n, long B, const ZZX& f, const ZZ& m)
      : n_(n), B_(B), f_(f), m_(m), M_(B), Mold_(0), a_(B), b_(-1) {
        ZZX fprime = diff(f);

        // Figure out what the factorbase columns represent.
        // Each one is a pair (p,r), where p is a prime up to B
        // and r is something where f(r) \equiv 0 (mod p).
        PrimeSeq pseq;
        long p;
        while ((p = pseq.next()) <= B) {
            cols_modular_.push_back(p);
            for (long r = 0; r < p; r++) {
                ZZ res = eval(f, ZZ(r));
                if (res % p == 0)
                    cols_algebraic_.emplace_back(p, r);
            }
        }

        // Generate pairs (q,s) for Adleman columns.
        // For each q (named p here), find an s where
        // f(s) \equiv 0 (mod p) and f'(s) \not\equiv 0 (mod p).
        long k = conv<long>(3 * log(conv<RR>(n)));
        long k_so_far = 0;
        while (k_so_far < k) {
            for (long s = 0; s < p; s++) {
                ZZ zs(s);
                if (eval(f, zs) % p == 0 && eval(fprime, zs) % p != 0) {
                    cols_adleman_.emplace_back(p, s);
                    k_so_far++;
                }
            }

            p = pseq.next();
        }
    }

    bool SmoothPairFinder::add_cols_modular(long a, long b, vec_GF2& ret) {
        ZZ ammb = a - m_ * b;
        ammb %= n_;

        if (ammb < 0) {
            ammb *= -1;
            ret.append(GF2(1));
        } else {
            ret.append(GF2(0));
        }

        for (const auto& p : cols_modular_) {
            bool divcount = false;
            while (ammb % p == 0) {
                ammb /= p;
                divcount ^= true;
            }
            ret.append(divcount ? GF2(1) : GF2(0));
        }

        return ammb == 1;
    }

    bool SmoothPairFinder::add_cols_algebraic(long a, long b, vec_GF2& ret) {
        ZZ namab = eval_norm(f_, ZZ(a), ZZ(b));

        // Iterate through each prime.
        // For each one, find r first,
        // then set the appropriate entry in the index vector.

        // Here, we'll keep a cursor into cols_algebraic_.
        auto alg_it = cols_algebraic_.cbegin();

        for (const auto& p : cols_modular_) {
            long r = lincon(a, b, p);

            // Get the divcount as before
            bool divcount = false;
            while (namab % p == 0) {
                namab /= p;
                divcount ^= true;
            }
            GF2 to_add = divcount ? GF2(1) : GF2(0);

            // Scroll past algebraic factorbase entries
            // until we find where we want to add this item.
            // Then add it.
            while (alg_it->first != p || alg_it->second != r) {
                ++alg_it;
                ret.append(GF2(0));
            }
            ++alg_it;
            ret.append(to_add);
        }

        // Add the rest of the zeros to the vector that we return.
        while (alg_it != cols_algebraic_.cend()) {
            ++alg_it;
            ret.append(GF2(0));
        }

        return namab == 1;
    }

    bool SmoothPairFinder::add_cols_adleman(long a, long b, NTL::vec_GF2& ret) {
        for (const auto& qs : cols_adleman_) {
            long q = qs.first;
            long s = qs.second;
            long jacobi_top = a - b * s;
            long jacobi = Jacobi(ZZ(jacobi_top % q), ZZ(q));
            ret.append(jacobi == -1 ? GF2(1) : GF2(0));
        }

        return true;
    }

    vec_GF2 SmoothPairFinder::generate_row(long a, long b) {
        vec_GF2 ret;  // if this length != num_cols, invalid
        // we always return ret so we can have the return value optimzn
        // even if ret ends up being invalid

        // Modular factorbase and -1.
        if (!add_cols_modular(a, b, ret)) {
            // ammb is not B-smooth
            // make SURE the length is invalid
            if (ret.length() == num_cols()) ret.append(GF2(0));
            return ret;
        }

        // Algebraic factorbase.
        if (!add_cols_algebraic(a, b, ret)) {
            // namab is not B-smooth
            // make SURE the length is invalid
            if (ret.length() == num_cols()) ret.append(GF2(0));
            return ret;
        }

        // Adleman columns.
        if (!add_cols_adleman(a, b, ret)) {
            // result is wrong
            // make SURE the length is invalid
            if (ret.length() == num_cols()) ret.append(GF2(0));
            return ret;
        }

        return ret;
    }

    std::pair<std::pair<long, long>, vec_GF2> SmoothPairFinder::get() {
        // TODO
        return std::make_pair(std::make_pair(0, 0), vec_GF2(INIT_SIZE, num_cols()));
    }
}


 // graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2018 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include <boost/math/special_functions/zeta.hpp>
#include <boost/math/special_functions/gamma.hpp>

#include "graph_tool.hh"
#include "hash_map_wrap.hh"
#include "int_part.hh"
#include "util.hh"

double spence(double);

namespace graph_tool
{

using namespace std;

boost::multi_array<double, 2> __q_cache;

double log_sum(double a, double b)
{
    return std::max(a, b) + std::log1p(exp(-abs(a-b)));
}

void init_q_cache(size_t n_max)
{
    size_t old_n = __q_cache.shape()[0];
    if (old_n >= n_max)
        return;

    __q_cache.resize(boost::extents[n_max + 1][n_max + 1]);
    std::fill(__q_cache.data(), __q_cache.data() + __q_cache.num_elements(),
              -std::numeric_limits<double>::infinity());

    for (size_t n = 1; n <= n_max; ++n)
    {
        __q_cache[n][1] = 0;
        for (size_t k = 2; k <= n; ++k)
        {
            __q_cache[n][k] = log_sum(__q_cache[n][k], __q_cache[n][k - 1]);
            if (n > k)
                __q_cache[n][k] = log_sum(__q_cache[n][k], __q_cache[n - k][k]);
        }
    }
}

double q_rec(int n, int k)
{
    if (n <= 0 || k < 1)
        return 0;
    if (k > n)
        k = n;
    if (k == 1)
        return 1;
    return q_rec(n, k - 1) + q_rec(n - k, k);
}

gt_hash_map<pair<int, int>, double> __q_memo;

double q_rec_memo(int n, int k)
{
    if (k > n || n <= 0 || k < 1)
        return 0;
    if (k > n)
        k = n;
    if (k == 1)
        return 1;
    auto key = make_pair(n, k);
    auto iter = __q_memo.find(key);
    if (iter != __q_memo.end())
        return iter->second;
    auto res = q_rec_memo(n, k - 1) + q_rec_memo(n - k, k);
    __q_memo[key] = res;
    return res;
}

double log_q_approx_big(size_t n, size_t k)
{
    double C = M_PI * sqrt(2/3.);
    double S = C * sqrt(n) - log(4 * sqrt(3) * n);
    if (k < n)
    {
        double x = k / sqrt(n) - log(n) / C;
        S -= (2 / C) * exp(-C * x / 2);
    }
    return S;
}

double log_q_approx_small(size_t n, size_t k)
{
    return lbinom_fast(n - 1, k - 1) - lgamma_fast(k + 1);
}

double get_v(double u, double epsilon=1e-8)
{
    double v = u;
    double delta = 1;
    while (delta > epsilon)
    {
        // spence(exp(v)) = -spence(exp(-v)) - (v*v)/2
        double n_v = u * sqrt(spence(exp(-v)));
        delta = abs(n_v - v);
        v = n_v;
    }
    return v;
}

double log_q_approx(size_t n, size_t k)
{
    if (k < pow(n, 1/4.))
        return log_q_approx_small(n, k);
    double u = k / sqrt(n);
    double v = get_v(u);
    double lf = log(v) - log1p(- exp(-v) * (1 + u * u/2)) / 2 - log(2) * 3 / 2.
        - log(u) - log(M_PI);
    double g = 2 * v / u - u * log1p(-exp(-v));
    return lf - log(n) + sqrt(n) * g;
}

} // namespace graph_tool


#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <png.h>
#include <TaskGraph>
#include <TaskUserFunctions.h>
#include <boost/static_assert.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include "mandelbrot.hpp"

using namespace tg;

const int IMG_X = 12800;
const int IMG_Y ((IMG_X/(NUM_SPES*4))*3*NUM_SPES); // Approximately 4:3 aspect ratio with number of rows multiple of NUM_SPES

const int MAX_ITERATIONS = 1000;
const char* OUTPUT_FILE = "mandelbrot_set.png";
const float GAMMA_EXPONENT = 3.5f;

const int  UNROLL_FACTOR = 8; // MUST BE A DIVISOR OF IMG_X!
BOOST_STATIC_ASSERT(IMG_X % UNROLL_FACTOR == 0);

typedef TaskGraph<void, int, char[IMG_X]> mandel_tg;

int main(int argc, char* argv[]) {
  int threads;
  const Options options = getOptions("mandelbrot", argc, argv);

  if (options.threads < 1 || options.threads > NUM_SPES)
  {
    std::cerr << "Please select a number of threads between 1 and " << NUM_SPES << " (inclusive)." << std::endl;
    exit(EXIT_FAILURE);
  }
  else if (IMG_Y % options.threads != 0)
  {
    std::cerr << "Please choose a number of threads that is a factor of the image height (" << IMG_Y << ")." << std::endl;
    exit(EXIT_FAILURE);
  }
  else
  {
    threads = options.threads;
  }

  FILE *output_file;
  boost::ptr_vector<mandel_tg> taskGraphs;
  TaskFarm tFarm;

  mandel_tg T;
  taskgraph(mandel_tg, T, tuple2(y_pos, line)) {
    tVar(float, x);
    tVar(float, y);
    tVar(float, x0);
    tVar(float, y0);
    tVar(float, xtemp);
    tVar(int, x_pos);
    tVar(int, iteration);
    tVar(float, pixel);

    y0 = (y_pos * (3.0f / IMG_Y)) - 1.5f;

    // Iterate over the scanline
    tFor (x_pos, 0, IMG_X - 1) {
      x0 = (x_pos * (3.5f / IMG_X)) - 2.5f;

      // Unroll the loop
      for (int i = 0; i < UNROLL_FACTOR; ++i) {
        x = x0;
        y = y0;
        iteration = 0;

        // Test if this location is in the set
        tWhile ((x*x + y*y) < 4 && iteration < MAX_ITERATIONS) {
          xtemp = (x*x) - (y*y) + x0;
          y = 2*x*y + y0;
          x = xtemp;
          iteration+=1;
        }

        // Calculate and set the value of the pixel
        tIf(iteration == MAX_ITERATIONS)
          pixel = 0.0f;
        tElse
          pixel = 8.0f * (iteration + 1.0f - tLogf(tLogf(tSqrtf(x*x + y*y))) / std::log(2.0f)) / MAX_ITERATIONS;

        line[x_pos] = tPowf(pixel, (1.0f / GAMMA_EXPONENT)) * 255.0f;
        x_pos+=1;
      }
      x_pos-=1;
    }
  }

  T.compile(tg::SPU_GCC, false);

  for(int index=0; index<threads; ++index)
  {
    taskGraphs.push_back(new mandel_tg(T.duplicate()));
    tFarm.add(&taskGraphs.back());
  }

  png_structp png_ptr;
  png_infop info_ptr;

  if (!(output_file = fopen(OUTPUT_FILE, "wb"))) {
    std::cerr << "Unable to open " << OUTPUT_FILE << " for writing. Aborting now." << std::endl;
    exit(1);
  }

  if (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) {
    std::cerr << "Failed to create png write struct. Aborting now." << std::endl;
    exit(1);
  }

  if (!(info_ptr = png_create_info_struct(png_ptr))) {
    std::cerr << "Failed to create png info struct. Aborting now." << std::endl;
    // Clean up
    png_destroy_write_struct(&png_ptr,static_cast<png_infopp>(NULL));
    exit(1);
  }

  png_init_io(png_ptr, output_file);
  png_set_IHDR(png_ptr, info_ptr, IMG_X, IMG_Y, 8,
               PNG_COLOR_TYPE_PALETTE,
               PNG_INTERLACE_NONE,
               PNG_COMPRESSION_TYPE_DEFAULT,
               PNG_FILTER_TYPE_DEFAULT);
  png_set_gamma(png_ptr, 0.5, 0.45455);

  std::vector<png_color> palette(generateBlueYellowColourMap());
  png_set_PLTE(png_ptr, info_ptr, &palette[0], palette.size());
  png_write_info(png_ptr, info_ptr);

  std::vector<char*> scanlines(threads);

  for (int spe = 0; spe < threads; ++spe)
    scanlines[spe] = static_cast<char*>(spu_malloc(IMG_X));

  for (int line = 0; line < IMG_Y; line+=threads)
  {
    std::vector<int> lineNumbers(threads);

    for(int spe=0; spe<threads; ++spe)
    {
      lineNumbers[spe] = line+spe;
      taskGraphs[spe].setParameters(lineNumbers[spe], scanlines[spe]);
    }

    tFarm.execute();

    for (int spe = 0; spe < threads; ++spe)
    {
      png_bytep png_row_ptr = reinterpret_cast<png_bytep>(scanlines[spe]);
      png_write_row(png_ptr, png_row_ptr);
    }
  }

  for (int spe = 0; spe < threads; ++spe)
    spu_free(scanlines[spe]);

  png_write_end(png_ptr, info_ptr);
  png_destroy_write_struct(&png_ptr, &info_ptr);

  exit(EXIT_SUCCESS);
}


/*
// Copyright 2018 University of Liege
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Authors:
// - Adrien Crovato
*/

//// Compressible module (field solver)
// Compute field variables and field sources
//
// I/O:
// - Minf: freestream Mach number
// - vInf: freestream velocity vector
// - bPan: (network of) body panels (structure)
// - fPan: field panels (structure)
// - sp: sub-panels (structure)
// - b2fAIC: body to field AIC (structure)
// - f2fAIC: field to field AIC (structure)
// - spAIC: body to field AIC for subpanels (structure)

#include <iostream>
#include <Eigen/Dense>
#include "solve_field.h"
#include "compute_fVars.h"

using namespace std;
using  namespace Eigen;

#define GAMMA 1.4
#define CMU 1.0
#define M_C 1.0

void solve_field(double Minf, Vector3d &vInf, Network &bPan, Field &fPan, Subpanel &sp,
                 Body_AIC &b2fAIC, Field2field_AIC &f2fAIC, Subpanel_AIC &spAIC) {

    //// Begin
    cout << "Computing field variables... " << flush;

    //// Field variables
    compute_fVars(Minf, vInf, bPan, fPan, sp, b2fAIC, f2fAIC, spAIC);

    //// Field sources
    // Density gradient
    for (int i = 0; i < fPan.nE; i++) {
        int f = fPan.eIdx(i);
        double dU, dV, dW; // Variables for residual

        // X-derivative
        double fb=0, ff=0, fb2=0, ff2=0;
        if (fPan.fbdMap(f,0)) {
            fb = (fPan.rho(f) - fPan.rho(f - 1)) / fPan.deltaX;
            fb2 = (fPan.U(f,0) - fPan.U(f - 1,0)) / fPan.deltaX;
        }
        if (fPan.fbdMap(f,1)) {
            ff = (fPan.rho(f + 1) - fPan.rho(f)) / fPan.deltaX;
            ff2 = (fPan.U(f + 1,0) - fPan.U(f,0)) / fPan.deltaX;
        }
        if (fPan.fbdMap(f,0) && fPan.fbdMap(f,1)) {
            fPan.dRho(f,0) = 0.5*(fb+ff);
            dU = 0.5*(fb2+ff2);
        }
        else {
            fPan.dRho(f,0) = fb+ff;
            dU = fb2+ff2;
        }

        // Y-derivative
        fb=0, ff=0, fb2=0, ff2=0;
        if (fPan.fbdMap(f,2)) {
            fb = (fPan.rho(f) - fPan.rho(f - fPan.nX*fPan.nZ)) / fPan.deltaY;
            fb2 = (fPan.U(f,1) - fPan.U(f - fPan.nX*fPan.nZ,1)) / fPan.deltaY;
        }
        if (fPan.fbdMap(f,3)) {
            ff = (fPan.rho(f + fPan.nX*fPan.nZ) - fPan.rho(f)) / fPan.deltaY;
            ff2 = (fPan.U(f + fPan.nX*fPan.nZ,1) - fPan.U(f,1)) / fPan.deltaY;
        }
        if (fPan.fbdMap(f,2) && fPan.fbdMap(f,3)) {
            fPan.dRho(f,1) = 0.5*(fb+ff);
            dV = 0.5*(fb2+ff2);
        }
        else {
            fPan.dRho(f,1) = fb+ff;
            dV = fb2+ff2;
        }

        // Z-derivative
        fb=0, ff=0, fb2=0, ff2=0;
        if (fPan.fbdMap(f,4)) {
            fb = (fPan.rho(f) - fPan.rho(f - fPan.nX)) / fPan.deltaZ;
            fb2 = (fPan.U(f,2) - fPan.U(f - fPan.nX,2)) / fPan.deltaZ;
        }
        if (fPan.fbdMap(f,5)) {
            ff = (fPan.rho(f + fPan.nX) - fPan.rho(f)) / fPan.deltaZ;
            ff2 = (fPan.U(f + fPan.nX,2) - fPan.U(f,2)) / fPan.deltaZ;
        }
        if (fPan.fbdMap(f,4) && fPan.fbdMap(f,5)) {
            fPan.dRho(f,2) = 0.5*(fb+ff);
            dW = 0.5*(fb2+ff2);
        }
        else {
            fPan.dRho(f,2) = fb+ff;
            dW = fb2+ff2;
        }

        // Field source
        fPan.sigma(f) = -1 / (fPan.rho(f)) * fPan.U.row(f).dot(fPan.dRho.row(f));
        // Residual
        fPan.epsilon(f) = dU + dV + dW - fPan.sigma(f);
    }

    // TODO 1) Artificial density (pros: physical; cons: does not work on MG or RG/MG)
    // TODO 2) Artificial viscosity (pros: works on RG/MG; cons: cut through surface, not physical)
    // TODO NB) With current form, x-upwinding gives same results as s-upwinding.

    //// Artificial viscosity
    for (int i = 0; i < fPan.nE; i++) {
        int f = fPan.eIdx(i);
        double mu, deltaSigmaX, deltaSigmaY, deltaSigmaZ; // Variables for artificial viscosity

        if (fPan.M(f) > M_C) {
            mu = CMU * (1 - M_C * M_C / (fPan.M(f) * fPan.M(f)));

            // X-contribution
            if (fPan.U(f,0) > 0 && fPan.fbdMap(f,0))
                deltaSigmaX = fPan.sigma(f) - fPan.sigma(f - 1);
            else if (fPan.U(f,0) < 0 && fPan.fbdMap(f,1))
                deltaSigmaX = fPan.sigma(f + 1) - fPan.sigma(f);
            else
                deltaSigmaX = 0;
            // Y-contribution
            if (fPan.U(f,1) > 0 && fPan.fbdMap(f,2))
                deltaSigmaY = fPan.sigma(f) - fPan.sigma(f - fPan.nX*fPan.nZ);
            else if (fPan.U(f,1) < 0 && fPan.fbdMap(f,3))
                deltaSigmaY = fPan.sigma(f + fPan.nX*fPan.nZ) - fPan.sigma(f);
            else
                deltaSigmaY = 0;
            // Z-contribution
            if (fPan.U(f,2) > 0 && fPan.fbdMap(f,4))
                deltaSigmaZ = fPan.sigma(f) - fPan.sigma(f - fPan.nX);
            else if (fPan.U(f,2) < 0 && fPan.fbdMap(f,5))
                deltaSigmaZ = fPan.sigma(f + fPan.nX) - fPan.sigma(f);
            else
                deltaSigmaZ = 0;

            fPan.sigma(f) -= mu / fPan.U.row(f).norm() *
                               (fPan.U(f, 0) * deltaSigmaX + fPan.U(f, 1) * deltaSigmaY + fPan.U(f, 2) * deltaSigmaZ);
        }
    }

    cout << "Done!" << endl;
    cout << "Max. Mach number: " << fPan.M.maxCoeff() << endl;
    cout << "Field sources strength: " << fPan.sigma.norm() << endl << endl;
}


#include <iostream>
#include <complex>
#include <cmath>
#include <vector>
#include <numeric>
#include <tuple>
#include <fstream>
#include <ctime>
#include <chrono>
#include <stdio.h>
#include <Eigen/Dense>
#include <Eigen/Core>
#include <omp.h>

// Defining special types
typedef std::complex<double> dcomp;
typedef std::vector<double> dvec;
typedef std::vector< std::vector<double> > ddvec;
typedef std::vector<int> ivec;
typedef std::vector< std::vector<int> > iivec;
typedef std::vector< std::complex<double> > dcvec;
typedef std::vector< std::vector<std::complex<double> > > ddcvec;

/* --------------------------------------------------------------------

		FUNCTIONS

-----------------------------------------------------------------------*/

inline const double pi()
{
	// Set teh value fo pi
	const double pi = std::atan(1) * 4;
	return pi;
}

/* --------------------------------------------------------------------
		FROM MICROSECONDS TO HH:MM:SS:MS
-----------------------------------------------------------------------*/
std::tuple<long long, long long, long long, long long> ms_to_time(long long ms)
{
	// Defining the variables
	long long s, m, h;

	// Calculating the time in hour:minutes:seconds:microseconds
	s = ms / 1000000;
	ms = ms % 1000000;
	m = s / 60;
	s = s % 60;
	h = m / 60;
	m = m % 60;

	return { ms,s,m,h };
}
/* --------------------------------------------------------------------
		PRINT in HH:MM:SS:MS FORMAT
-----------------------------------------------------------------------*/
void time_print(long long ms, long long s, long long m, long long h)
{

	// Print the time
	if (h < 10)
	{
		std::cout << "0" << h << ":";
	}
	else
	{
		std::cout << "" << h << ":";
	}
	if (m < 10)
	{
		std::cout << "0" << m << ":";
	}
	else
	{
		std::cout << "" << m << ":";
	}
	if (s < 10)
	{
		std::cout << "0" << s << ":" << ms;
	}
	else
	{
		std::cout << "" << s << ":" << ms;
	}

	return;
}
/* --------------------------------------------------------------------
		CHI FROM Z
-----------------------------------------------------------------------*/
inline dcomp chi_from_z(dcomp z, dcomp z1, dcomp z2, double L, double mu)
{
	// Defining the variables
	dcomp z0, Z, chi;

	// Calculating the chi from a z value
	z0 = 0.5 * (z1 + z2);
	Z = exp(dcomp(0, -1) * mu) * 2.0 * (z - z0) / L;
	chi = Z + sqrt(Z - 1.0) * sqrt(Z + 1.0);

	return chi;
}
/* --------------------------------------------------------------------
		Z FROM CHI
-----------------------------------------------------------------------*/
inline dcomp z_from_chi(dcomp chi, dcomp z1, dcomp z2, double L, double mu)
{
	// Defining the variables
	dcomp z, z0, Z;

	// Calculating the z from a chi value
	z0 = 0.5 * (z1 + z2);
	Z = 0.5 * (chi + 1.0 / chi);
	z = 0.5 * L * Z * exp(dcomp(0, 1) * mu) + z0;

	return z;
}
/* --------------------------------------------------------------------
		ANGEL CHANGE
-----------------------------------------------------------------------*/
inline double angel_change(dcomp z, dcomp z1, dcomp z2)
{
	// Defining the variables
	double u, v, eta;

	// Calculating the angels
	u = arg(z - z1);
	v = arg(z1 - z2);

	// Correct to angel of 0 to 2pi
	if (u < 0)
	{
		u += 2 * pi();
	}
	if (v < 0)
	{
		v += 2 * pi();
	}

	// Calculate angel between the vectors
	eta = abs(u - v);

	// Correct to angel of 0 to pi
	if (eta > pi())
	{
		eta -= 2 * pi();
	}

	eta = abs(eta);

	return eta;
}
/* --------------------------------------------------------------------
		FIND INTERSECTION POINT
-----------------------------------------------------------------------*/
inline std::tuple<dcomp, int> intersection_point(dcomp z1, dcomp z2, dcomp z3, dcomp z4)
{
	// Defining the variables
	dcomp zint, Z1, Z2;
	int int_check = 0;

	if (z1 != z3 || z2 != z4)
	{
		// Calculating the intersection point
		zint = ((conj(z2 - z1) * z1 - (z2 - z1) * conj(z1)) * (z4 - z3) - (conj(z4 - z3) * z3 - (z4 - z3) * conj(z3)) * (z2 - z1)) / ((z4 - z3) * conj(z2 - z1) - (z2 - z1) * conj(z4 - z3));

		// Check if intersection is on the lines
		Z1 = (zint - .5 * (z1 + z2)) / (.5 * (z1 - z2));
		Z2 = (zint - .5 * (z3 + z4)) / (.5 * (z3 - z4));

		if (real(Z1 * conj(Z1)) < 1.0)
		{
			if (real(Z2 * conj(Z2)) < 1.0)
			{
				int_check = 1;
			}
		}

	}

	return { zint, int_check };
}
/* --------------------------------------------------------------------
		GET DELTA
-----------------------------------------------------------------------*/
inline dcomp get_delta(ddcvec ab, int nab, int m, ivec pos_z1, ivec pos_z2)
{
	// Defintion of variables
	dcomp delta;

	delta = 0;
	for (int ii = 0; ii < nab; ii++)
	{
		// Adding the jump for the z1 elements
		if (pos_z1[ii] == 1)
		{
			for (int jj = 0; jj < m; jj++)
			{
				delta += ab[ii][jj]* pow(-1, jj);
			}
		}
		// Adding the jump for the z2 elements
		if (pos_z2[ii] == 1)
		{
			for (int jj = 0; jj < m; jj++)
			{
				delta -= ab[ii][jj];
			}
		}

	}

	return delta;
}
/* --------------------------------------------------------------------
		TAU UNIFORM STRESS
-----------------------------------------------------------------------*/
inline std::tuple<dcomp, dcomp>  tau_uni(double sigma_11inf)
{
	// Defining the variables
	dcomp tau_11, tau_12, phi, psi;

	// Get the phi and psi
	phi = -0.5 * sigma_11inf;
	psi = -0.5 * sigma_11inf;

	// calculating the tau
	tau_11 = -phi - psi;
	tau_12 = -phi - phi;

	return { tau_11, tau_12 };
}
/* --------------------------------------------------------------------
		TAU CRACK
-----------------------------------------------------------------------*/
inline std::tuple<dcomp, dcomp> tau_crack(dcomp z, dcomp z1, dcomp z2, double L, double mu, int m, dcvec a)
{
	// Defining the variables
	dcomp chi, chi_bar, Z, chi_pow;
	dcomp dphi, dphi_bar, ddphi, dpsi;
	dcomp tau_11, tau_12, S1, L_frac;

	// Getting the chi - and Z - coordinates
	chi = chi_from_z(z, z1, z2, L, mu);
	chi_bar = conj(chi);
	Z = exp(dcomp(0, -1) * mu) * 2.0 * (z - 0.5 * (z1 + z2)) / L;

	// Calculating the series
	dphi = 0;
	dphi_bar = 0;
	ddphi = 0;
	dpsi = 0;
	chi_pow = chi * chi - 1.0;
	for (int ii = 0; ii < m; ii++)
	{
		double n = ii + 1.0;
		dcomp beta_n = a[ii] * n;
		dcomp chipow = pow(chi, (1.0 - n)) / chi_pow;
		dphi += conj(beta_n) * chipow;
		dphi_bar += beta_n * conj(chipow);
		ddphi -= conj(beta_n) * (pow(chi, (2.0 - n)) / (chi_pow * chi_pow * chi_pow)) * ((n + 1.0) * chi * chi - n + 1.0);
		dpsi -= beta_n * chipow;
	}

	// Multiplying the constants
	L_frac = (4.0 / L) * exp(dcomp(0, -1) * mu);
	dphi *= L_frac;
	dphi_bar *= conj(L_frac);
	ddphi *= (16.0 / (L * L)) * exp(dcomp(0, -2) * mu);
	dpsi *= L_frac;

	// Calcualting tau
	tau_11 = -0.5 * L * (Z - conj(Z)) * ddphi - exp(dcomp(0, -1) * mu) * (dphi + dpsi);
	tau_12 = -exp(dcomp(0, 1) * mu) * dphi - exp(dcomp(0, -1) * mu) * dphi_bar;

	return { tau_11, tau_12 };
}
/* --------------------------------------------------------------------
		TAU TOTAL
-----------------------------------------------------------------------*/
inline std::tuple<dcomp, dcomp>  tau_total(dcomp z, double sigma_11inf, dcvec z1, dcvec z2, dvec L, dvec mu, int ma, int na, ddcvec a, int m_not_a)
{
	// Defining the variables
	dcomp tau_11, tau_12;

	// Add the unifrom stress field
	std::tie(tau_11, tau_12) = tau_uni(sigma_11inf);

	// Add the analytic element for a crack
	if (na > 0)
	{
		for (int ii = 0; ii < na; ii++)
		{
			if (ii != m_not_a)
			{
				dcomp tau_11c, tau_12c;
				std::tie(tau_11c, tau_12c) = tau_crack(z, z1[ii], z2[ii], L[ii], mu[ii], ma, a[ii]);
				tau_11 += tau_11c;
				tau_12 += tau_12c;
			}
		}
	}

	return { tau_11, tau_12 };
}
/* --------------------------------------------------------------------
		T TOTAL
-----------------------------------------------------------------------*/
inline dcomp T_total(dcomp z, double sigma_11inf, dcvec z1a, dcvec z2a, dvec La, dvec mua, int ma, int na, ddcvec a, int m_not_a, int m_is_a)
{
	// Defining the variables
	dcomp tau_11, tau_12, T;

	// Get teh taus
	std::tie(tau_11, tau_12) = tau_total(z, sigma_11inf, z1a, z2a, La, mua, ma, na, a, m_not_a);

	// Calculate the tractions
	T = dcomp(0, -.5) * (tau_11 * exp(dcomp(0, 2) * mua[m_is_a]) - tau_12);

	return { T };
}
/* --------------------------------------------------------------------
		SOLVE CRACK ANALYTIC ELEMENT A (BETA IN PAPER)
-----------------------------------------------------------------------*/
inline dcvec AE_crack_solver(Eigen::VectorXd T_s, Eigen::VectorXd T_n, Eigen::MatrixXd A, int ma, int Na, dvec pa, double sigma_11inf, dcvec z1a, dcvec z2a, dvec La, dvec mua, int na, ddcvec a_in, dcvec zint, ivec int_check, int m_is_a)
{
	// Defining the variables
	dcvec a(ma);
	Eigen::VectorXd b1(ma);
	Eigen::VectorXd b2(ma);

	// Count number of intersections
	int int_count = std::accumulate(int_check.begin(), int_check.end(), 0.0);

	// Check if the crack has any intersection
	if (int_count == 0)
	{
		// Solving the linear system (without intersections)
		b1 = A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(T_s);
		b2 = A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(T_n);
	}
	else
	{
		// Define new matrices for A and T and assign the pre-calculated values
		Eigen::MatrixXd A_int_s(Na + int_count * 3, ma);
		Eigen::MatrixXd A_int_n(Na + int_count, ma);
		Eigen::VectorXd T_s_int(Na + int_count * 3);
		Eigen::VectorXd T_n_int(Na + int_count);
		#pragma omp parallel for default(none) shared(A_int_s, A_int_n, A, T_n_int, T_n, T_s_int, T_s)
		for (int ii = 0; ii < Na; ii++)
		{
			for (int jj = 0; jj < ma; jj++)
			{
				/*A_int_re(ii, jj) = A(ii, jj);
				A_int_im(ii, jj) = A(ii, jj);*/
				A_int_s(ii, jj) = A(ii, jj);
				A_int_n(ii, jj) = A(ii, jj);
			}
			T_s_int(ii) = T_s(ii);
			T_n_int(ii) = T_n(ii);
		}
		int cnt_s = 0;
		int cnt_n = 0;
		for (int ii = 0; ii < na; ii++)
		{
			// Check if element ii has any intersections
			if (int_check[ii] == 1)
			{
				// Define variables
				dcomp chia, tau11, chi_pow, L_frac, T_temp;
				double thetaa;

				// Compute the intersection angel in the chi-plane
				chia = chi_from_z(zint[ii], z1a[m_is_a], z2a[m_is_a], La[m_is_a], mua[m_is_a]);
				thetaa = imag(log(chia));

				// Pre-calcualte terms
				chi_pow = chia /( chia * chia - 1.0 );
				L_frac = (dcomp(0,8.0) / La[m_is_a]) * exp(dcomp(0, -2) * mua[m_is_a]);

				// Add the intersection to the A matrix
				for (int jj = 0; jj < ma; jj++)
				{
					double n = jj + 1.0;
					// Add a control point at the intersection - tau^11 condition
					A_int_s(Na + cnt_s, jj) = real( chi_pow * L_frac * n * pow(chia, -n) );
					A_int_s(Na + (cnt_s + 1), jj) = imag( chi_pow * L_frac * n * pow(chia, -n) );

					// Add a control point at the intersection - traction condition
					A_int_s(Na + (cnt_s + 2), jj) = n * sin(n * thetaa);
					A_int_n(Na + cnt_n, jj) = n * sin(n * thetaa);
				}

				// Calculate the taus at the intersection
				dcomp tau_11, tau_12;
				std::tie(tau_11, tau_12) = tau_total(zint[ii], sigma_11inf, z1a, z2a, La, mua, ma, na, a_in, m_is_a);

				// Add the intersection to the T_s vector
				T_s_int(Na + cnt_s) = -real(tau_11);
				T_s_int(Na + (cnt_s + 1)) = -imag(tau_11);

				// Calculate the traction at the control point at the intersection
				T_temp = T_total(zint[ii], sigma_11inf, z1a, z2a, La, mua, ma, na, a_in, m_is_a, m_is_a);

				// Add the intersection to the T vectors
				T_s_int(Na + (cnt_s + 2)) = real(T_temp) * 0.5 * La[m_is_a] * sin(thetaa);
				T_n_int(Na + cnt_n) = (pa[ii] + imag(T_temp)) * -0.5 * La[m_is_a] * sin(thetaa);

				// Add step to loop count
				cnt_s += 3;
				cnt_n += 1;
			}

		}

		// Solving the linear system (with intersections)
		b1 = A_int_s.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(T_s_int);
		b2 = A_int_n.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(T_n_int);
	}
	// Assign to a
	for (int ii = 0; ii < ma; ii++)
	{
		a[ii] = dcomp(b2[ii], b1[ii]);
	}

	return { a };
}
/* --------------------------------------------------------------------
		ITERATOR
-----------------------------------------------------------------------*/
inline ddcvec iterator(double cond, int ITR, int Na, dvec pa, double sigma_11inf, dcvec z1a, dcvec z2a, dvec La, dvec mua, int ma, int na)
{
	// Defining the variables
	dcomp tau_11, tau_12, T;
	Eigen::MatrixXd A(Na, ma);
	dvec theta_a(Na);
	ddvec term(na, dvec(Na));
	ddcvec za(na, dcvec(Na));
	ddcvec a(na, dcvec(ma));
	ddcvec zint(na, dcvec(na));
	iivec int_check(na, ivec(na));
	ivec int_count(na);
	double theta_0a, delthetaa;

	// Print the conditions
	std::cout << std::scientific;
	std::cout << "Solver for " << na << " analytical element cracks with " << ma << " coefficients at " << Na << " integration points." << std::endl;
	std::cout << "Iterations break after: error < " << cond << " or iterations > " << ITR << "." << std::endl << std::endl;
	std::cout << "	Error:" << "		Iteration:" << std::endl;

	// Assigning variables
	theta_0a = pi() / (Na);
	delthetaa = (pi() - 2.5*theta_0a) / (Na - 1.0);

	if (na > 0)
	{
		// Find the intersection points
		#pragma omp parallel for default(none) shared(zint, int_check, int_count, z1a, z2a)
		for (int ii = 0; ii < na; ii++)
		{
			for (int jj = 0; jj < na; jj++)
			{
				std::tie(zint[ii][jj], int_check[ii][jj]) = intersection_point(z1a[ii], z2a[ii], z1a[jj], z2a[jj]);
			}
			int_count[ii] = std::accumulate(int_check[ii].begin(), int_check[ii].end(), 0.0);
		}

		// Calculating the A, a theta and term matrices
		#pragma omp parallel for default(none) shared(theta_a, theta_0a, delthetaa)
		for (int ii = 0; ii < Na; ii++)
		{
			theta_a[ii] = theta_0a + ii * delthetaa;
		}

		//#pragma omp parallel for default(none) shared(A, za, term, z1a, z2a, La, mua, theta_a)
		for (int ii = 0; ii < Na; ii++)
		{
			for (int mm = 0; mm < ma; mm++)
			{
				A(ii, mm) = (mm + 1.0) * sin((mm + 1.0) * theta_a[ii]);
			}
			for (int jj = 0; jj < na; jj++)
			{
				dcomp chi;
				chi = exp(dcomp(0, 1) * theta_a[ii]);
				za[jj][ii] = z_from_chi(chi, z1a[jj], z2a[jj], La[jj], mua[jj]);
				term[jj][ii] = -0.5 * La[jj] * sin(theta_a[ii]);
			}
		}

	}

	// Sovle the a (betas in paper)
	double error = 1;
	ddcvec a_old(na, dcvec(ma, 0));
	double error_a;
	int NIT = 0;
	while ( error > cond && NIT < ITR )
	{
				for (int ii = 0; ii < na; ii++)
				{
					Eigen::VectorXd T_s(Na);
					Eigen::VectorXd T_n(Na);
					#pragma omp parallel for default(none) shared(T_s, T_n, za, sigma_11inf, z1a, z2a, La, mua, ma, na, a)
					for (int jj = 0; jj < Na; jj++)
					{
						dcomp T;
						T = T_total(za[ii][jj], sigma_11inf, z1a, z2a, La, mua, ma, na, a, ii, ii);
						T_s(jj) = -real(T) * term[ii][jj];
						T_n(jj) = (pa[ii] + imag(T)) * term[ii][jj];
					}
					dcomp tau_11, tau_12;
					a[ii] = AE_crack_solver(T_s, T_n, A, ma, Na, pa, sigma_11inf, z1a, z2a, La, mua, na, a, zint[ii], int_check[ii], ii);
					std::tie(tau_11, tau_12) = tau_total(zint[0][1], sigma_11inf, z1a, z2a, La, mua, ma, na, a, -1);
				}

		// Calcualte the error
		error_a = 0;
		if (na > 0)
		{
			dvec dela(na);
			#pragma omp parallel for default(none) shared(dela, a, error_a)
			for (int ii = 0; ii < na; ii++)
			{
				dvec dela_temp(ma);
				for (int jj = 0; jj < ma; jj++)
				{
					dela_temp[jj] = abs(a[ii][jj] - a_old[ii][jj]);
				}
				dela[ii] = *max_element(dela_temp.begin(), dela_temp.end());
			}
			error_a = *max_element(dela.begin(), dela.end());
		}
		dvec error_vec{ error_a };
		error = *max_element(error_vec.begin(), error_vec.end());

		NIT += 1;
		a_old = a;

		std::cout << std::scientific;
		std::cout << "	" << error << "	" << NIT << std::endl;

	}

	return { a };
}
/* --------------------------------------------------------------------
		STRESS TOTAL
-----------------------------------------------------------------------*/
inline std::tuple<double, double, double> sigma_total(dcomp z, double sigma_11inf, dcvec z1a, dcvec z2a, dvec La, dvec mua, int ma, int na, ddcvec a)
{
	// Defining the variables
	dcomp S1, S2, tau_11, tau_12;
	double sigma_11, sigma_22, sigma_12;

	// Calculating the tau
	std::tie(tau_11, tau_12) = tau_total(z, sigma_11inf, z1a, z2a, La, mua, ma, na, a, -1);

	// Calculate the sigmas
	S1 = .5 * (tau_11 + tau_12);
	S2 = .5 * (-tau_11 + tau_12);
	sigma_11 = real(S1);
	sigma_22 = real(S2);
	sigma_12 = -imag(S1);

	return { sigma_11, sigma_22, sigma_12 };
}
/* --------------------------------------------------------------------
		STRESS FIELD
-----------------------------------------------------------------------*/
std::tuple<dvec, dvec, ddvec, ddvec, ddvec> stress_field(double xfrom, double xto, double yfrom, double yto, int Nx, int Ny, double sigma_11inf, dcvec z1a, dcvec z2a, dvec La, dvec mua, int ma, int na, ddcvec a)
{
	// Defining the variables
	ddvec grid_11(Nx, dvec(Ny));
	ddvec grid_22(Nx, dvec(Ny));
	ddvec grid_12(Nx, dvec(Ny));
	double dx;
	double dy;
	dvec x_vec(Nx);
	dvec y_vec(Ny);

	// Calcualte the sigma grids
	dx = (xto - xfrom) / (Nx - 1.0);
	dy = (yto - yfrom) / (Ny - 1.0);
	#pragma omp parallel for default(none) shared(grid_11, grid_22, grid_12, x_vec, y_vec, sigma_11inf, z1a, z2a, La, mua, ma, na, a)
	for (int ii = 0; ii < Nx; ii++)
	{
		for (int jj = Ny; jj--;)
		{
			x_vec[ii] = xfrom + ii * dx;
			y_vec[jj] = yfrom + jj * dy;
			std::tie(grid_11[ii][jj], grid_22[ii][jj], grid_12[ii][jj]) = sigma_total(dcomp(x_vec[ii], y_vec[jj]), sigma_11inf, z1a, z2a, La, mua, ma, na, a);
		}
	}
	return { x_vec, y_vec, grid_11, grid_22, grid_12 };
}
/* --------------------------------------------------------------------
		PRINCIPAL STRESSES
-----------------------------------------------------------------------*/
std::tuple<double, double, double> principal_sigma(dcomp z, double sigma_11inf, dcvec z1a, dcvec z2a, dvec La, dvec mua, int ma, int na, ddcvec a)
{
	// Defining the variables
	double sigma_1;
	double sigma_2;
	double theta_p;
	double frac1, frac2, sqrt1;
	dcomp S1, S2, tau_11, tau_12;
	double sigma_11, sigma_22, sigma_12;

	// Calculating the tau
	std::tie(tau_11, tau_12) = tau_total(z, sigma_11inf, z1a, z2a, La, mua, ma, na, a, -1);

	// Calculate the sigmas
	S1 = .5 * (tau_11 + tau_12);
	S2 = .5 * (-tau_11 + tau_12);
	sigma_11 = real(S1);
	sigma_22 = real(S2);
	sigma_12 = -imag(S1);

	// Calculating the terms
	frac1 = (sigma_11 + sigma_22) / 2.0;
	frac2 = (sigma_11 - sigma_22) / 2.0;
	sqrt1 = sqrt(frac2 * frac2 + sigma_12 * sigma_12);

	// Calculating the principal stresses and the angel of sigma
	sigma_1 = frac1 + sqrt1;
	sigma_2 = frac1 - sqrt1;

	// Calcuating the angel theta_p
	theta_p = -0.5 * imag(log(tau_11));

	// Changing the absolut sigma is sigma_2 > sigma_1 (TURNED OFF)
	/*if (abs(sigma_2) > abs(sigma_1))
	{
		sigma_1 = frac1 - sqrt1;
		sigma_2 = frac1 + sqrt1;
		theta_p = -0.5*imag(log(tau_11)) + pi() / 2;
	}
	*/

	return { sigma_1, sigma_2, theta_p };
}
/* --------------------------------------------------------------------
		PRINCIPAL STRESSES PLOT
-----------------------------------------------------------------------*/
std::tuple<double, double, double> principal_sigma_plt(double sigma_11, double sigma_22, double sigma_12)
{
	// Defining the variables
	double sigma_1;
	double sigma_2;
	double theta_p;
	double frac1, frac2, sqrt1;
	dcomp tau_11;

	// Calculating the tau
	tau_11 = sigma_11 - sigma_22 - dcomp(0, 2) * sigma_12;

	// Calculating the terms
	frac1 = (sigma_11 + sigma_22) / 2.0;
	frac2 = (sigma_11 - sigma_22) / 2.0;
	sqrt1 = sqrt(frac2 * frac2 + sigma_12 * sigma_12);

	// Calculating the principal stresses and the angel of sigma
	sigma_1 = frac1 + sqrt1;
	sigma_2 = frac1 - sqrt1;

	// Calcuating the angel theta_p
	theta_p = -0.5 * imag(log(tau_11));

	// Changing the absolut sigma is sigma_2 > sigma_1 (TURNED OFF)
	/*if (abs(sigma_2) > abs(sigma_1))
	{
		sigma_1 = frac1 - sqrt1;
		sigma_2 = frac1 + sqrt1;
		theta_p = -0.5*imag(log(tau_11)) + pi() / 2;
	}
	*/

	return { sigma_1, sigma_2, theta_p };
}
/* --------------------------------------------------------------------
		PRINCIPAL STRESS FIELDS
-----------------------------------------------------------------------*/
std::tuple<ddvec, ddvec, ddvec> principal_stress_field(int Nx, int Ny, ddvec grid_11, ddvec grid_22, ddvec grid_12)
{
	// Defining the variables
	ddvec grid_1(Nx, dvec(Ny));
	ddvec grid_2(Nx, dvec(Ny));
	ddvec grid_tp(Nx, dvec(Ny));

	// Calculate teh principal stresses from the stress feilds
	#pragma omp parallel for default(none) shared(grid_1, grid_2, grid_tp, grid_11, grid_22, grid_12)
	for (int ii = 0; ii < Nx; ii++)
	{
		for (int jj = Ny; jj--;)
		{
			std::tie(grid_1[ii][jj], grid_2[ii][jj], grid_tp[ii][jj]) = principal_sigma_plt(grid_11[ii][jj], grid_22[ii][jj], grid_12[ii][jj]);
		}
	}
	return { grid_1, grid_2, grid_tp };
}
/* --------------------------------------------------------------------
		PRINCIPAL STRESS TRAJECTORIES
-----------------------------------------------------------------------*/
std::tuple<ddcvec, ddcvec> principal_stress_trajectories(double xfrom, double xto, double yfrom, double yto, dcvec xtraj, dcvec ytraj, int Ntraj, int lvs_traj, double sigma_11inf, dcvec z1a, dcvec z2a, dvec La, dvec mua, int ma, int na, ddcvec a)
{
	// Defining the variables
	ddcvec traj_1(lvs_traj * 2, dcvec(Ntraj));
	ddcvec traj_2(lvs_traj * 2, dcvec(Ntraj));
	double dx, dy, dx_lvsre, dx_lvsim, dy_lvsre, dy_lvsim, pi_val;
	double cond;
	int NIT;

	// Getting the starting points
	dx = (xto - xfrom) / (Ntraj * 0.8);
	dy = (yto - yfrom) / (Ntraj * 0.8);
	dx_lvsre = (real(xtraj[1]) - real(xtraj[0])) / (lvs_traj - 1.0);
	dx_lvsim = (imag(xtraj[1]) - imag(xtraj[0])) / (lvs_traj - 1.0);
	dy_lvsre = (real(ytraj[1]) - real(ytraj[0])) / (lvs_traj - 1.0);
	dy_lvsim = (imag(ytraj[1]) - imag(ytraj[0])) / (lvs_traj - 1.0);
	pi_val = 0.5 * pi();
	cond = 1e-6;
	NIT = 10;

	// SIGMA 1
	#pragma omp parallel for default(none) shared(traj_1, sigma_11inf, z1a, z2a, La, mua, ma, na, a)
	for (int ii = 0; ii < lvs_traj; ii++)
	{
		traj_1[ii][0] = dcomp(real(xtraj[0]) + ii * dx_lvsre, imag(xtraj[0]) + ii * dx_lvsim);
		dcomp z = traj_1[ii][0];
		dcomp z_old = z;
		double dx1 = dx;
		for (int jj = 1; jj < Ntraj; jj++)
		{
			dcomp zt, z11, z_oldt, sigma, sigmat;
			double sigma_1, theta_p;
			double ee, eta;
			std::tie(sigma_1, std::ignore, theta_p) = principal_sigma(z, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
			sigma = abs(sigma_1) * exp(dcomp(0, 1) * theta_p);
			z11 = z + sigma / abs(sigma) * dx1;
			eta = angel_change(z11, z, z_old);
			if (eta > pi_val && jj > 1) {
				dx1 = -dx1;
			}
			zt = z + sigma / abs(sigma) * dx1;

			ee = 1;
			for (int rr = NIT; rr--;)
			{
				z_oldt = zt;
				std::tie(sigma_1, std::ignore, theta_p) = principal_sigma(zt, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
				sigmat = abs(sigma_1) * exp(dcomp(0, 1) * theta_p);
				z11 = z + (sigma + sigmat) / abs(sigma + sigmat) * dx1;
				eta = angel_change(z11, z, z_old);
				if (eta > pi_val && jj > 1) {
					dx1 = -dx1;
				}
				zt = z + (sigma + sigmat) / abs(sigma + sigmat) * dx1;
				ee = std::norm(z_oldt - zt);
				if (ee < cond)
				{
					break;
				}
			}

			traj_1[ii][jj] = zt;
			z_old = z;
			z = zt;
		}

		int kk = ii + lvs_traj;
		traj_1[kk][0] = traj_1[ii][0];
		z = traj_1[kk][0];
		z_old = z;
		dx1 = -dx;
		for (int jj = 1; jj < Ntraj; jj++)
		{
			dcomp zt, z11, z_oldt, sigma, sigmat;
			double sigma_1, theta_p;
			double ee, eta;
			std::tie(sigma_1, std::ignore, theta_p) = principal_sigma(z, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
			sigma = abs(sigma_1) * exp(dcomp(0, 1) * theta_p);
			z11 = z + sigma / abs(sigma) * dx1;
			eta = angel_change(z11, z, z_old);
			if (eta > pi_val && jj > 1) {
				dx1 = -dx1;
			}
			zt = z + sigma / abs(sigma) * dx1;

			ee = 1;
			for (int rr = NIT; rr--;)
			{
				z_oldt = zt;
				std::tie(sigma_1, std::ignore, theta_p) = principal_sigma(zt, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
				sigmat = abs(sigma_1) * exp(dcomp(0, 1) * theta_p);
				z11 = z + (sigma + sigmat) / abs(sigma + sigmat) * dx1;
				eta = angel_change(z11, z, z_old);
				if (eta > pi_val && jj > 1) {
					dx1 = -dx1;
				}
				zt = z + (sigma + sigmat) / abs(sigma + sigmat) * dx1;
				ee = std::norm(z_oldt - zt);
				if (ee < cond)
				{
					break;
				}
			}

			traj_1[kk][jj] = zt;
			z_old = z;
			z = zt;
		}
	}

	// SIGMA 2
	#pragma omp parallel for default(none) shared(traj_2, sigma_11inf, z1a, z2a, La, mua, ma, na, a)
	for (int ii = 0; ii < lvs_traj; ii++)
	{
		traj_2[ii][0] = dcomp(real(ytraj[0]) + ii * dy_lvsre, imag(ytraj[0]) + ii * dy_lvsim);
		double dy1 = dy;
		dcomp z = traj_2[ii][0];
		dcomp z_old = z;
		for (int jj = 1; jj < Ntraj; jj++)
		{
			dcomp zt, z11, z_oldt, sigma, sigmat;
			double sigma_2, theta_p;
			double ee, eta;
			std::tie(std::ignore, sigma_2, theta_p) = principal_sigma(z, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
			sigma = abs(sigma_2) * exp(dcomp(0, 1) * (theta_p + pi_val));
			z11 = z + sigma / abs(sigma) * dy1;
			eta = angel_change(z11, z, z_old);
			if (eta > pi_val && jj > 1) {
				dy1 = -dy1;
			}
			zt = z + sigma / abs(sigma) * dy1;

			ee = 1;
			for (int rr = NIT; rr--;)
			{
				z_oldt = zt;
				std::tie(std::ignore, sigma_2, theta_p) = principal_sigma(zt, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
				sigmat = abs(sigma_2) * exp(dcomp(0, 1) * (theta_p + pi_val));
				z11 = z + (sigma + sigmat) / abs(sigma + sigmat) * dy1;
				eta = angel_change(z11, z, z_old);
				if (eta > pi_val && jj > 1) {
					dy1 = -dy1;
				}
				zt = z + (sigma + sigmat) / abs(sigma + sigmat) * dy1;
				ee = std::norm(z_oldt - zt);
				if (ee < cond)
				{
					break;
				}
			}

			traj_2[ii][jj] = zt;
			z_old = traj_2[ii][(int)(jj - 1)];
			z = zt;
		}

		int kk = ii + lvs_traj;
		traj_2[kk][0] = traj_2[ii][0];
		dy1 = -dy;
		z = traj_2[kk][0];
		z_old = z;
		for (int jj = 1; jj < Ntraj; jj++)
		{
			dcomp zt, z11, z_oldt, sigma, sigmat;
			double sigma_2, theta_p;
			double ee, eta;
			std::tie(std::ignore, sigma_2, theta_p) = principal_sigma(z, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
			sigma = abs(sigma_2) * exp(dcomp(0, 1) * (theta_p + pi_val));
			z11 = z + sigma / abs(sigma) * dy1;
			eta = angel_change(z11, z, z_old);
			if (eta > pi_val && jj > 1) {
				dy1 = -dy1;
			}
			zt = z + sigma / abs(sigma) * dy1;

			ee = 1;
			for (int rr = NIT; rr--;)
			{
				z_oldt = zt;
				std::tie(std::ignore, sigma_2, theta_p) = principal_sigma(zt, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
				sigmat = abs(sigma_2) * exp(dcomp(0, 1) * (theta_p + pi_val));
				z11 = z + (sigma + sigmat) / abs(sigma) * dy1;
				eta = angel_change(z11, z, z_old);
				if (eta > pi_val && jj > 1) {
					dy1 = -dy1;
				}
				zt = z + (sigma + sigmat) / abs(sigma + sigmat) * dy1;
				ee = std::norm(z_oldt - zt);
				if (ee < cond)
				{
					break;
				}
			}

			traj_2[kk][jj] = zt;
			z_old = z;
			z = zt;
		}
	}
	return { traj_1, traj_2 };
}
/* --------------------------------------------------------------------
		W UNIFORM STRESS
-----------------------------------------------------------------------*/
inline dcomp  w_uni(dcomp z, double kappa, double G, double sigma_11inf)
{
	// Defining the variables
	dcomp phi_bar, dphi, psi, w;

	// calculating the veriables
	phi_bar = -0.5 * sigma_11inf * conj(z);
	dphi = -0.5 * sigma_11inf;
	psi = -0.5 * sigma_11inf * z;

	// Calculating w
	w = 1 / (4 * G) * ((z - conj(z)) * dphi + kappa * phi_bar + psi);

	return { w };
}
/* --------------------------------------------------------------------
		W CRACK - ANALYTIC ELEMENT
-----------------------------------------------------------------------*/
inline dcomp w_crack(dcomp z, double kappa, double G, dcomp z1, dcomp z2, double L, double mu, int m, dcvec a)
{
	// Defining the variables
	dcomp chi, chi_bar, Z, chi_pow;
	dcomp dphi, phi_bar, psi;
	dcomp w, L_frac;
	double n;

	// Getting the chi - and Z - coordinates
	chi = chi_from_z(z, z1, z2, L, mu);
	chi_bar = conj(chi);
	Z = exp(dcomp(0, -1) * mu) * 2.0 * (z - 0.5 * (z1 + z2)) / L;

	// Calculating the series
	phi_bar = 0;
	dphi = 0;
	psi = 0;
	n = 0;
	chi_pow = chi * chi - 1.0;
	for (int ii = 0; ii < m; ii++)
	{
		dcomp a_n;
		n += 1;
		a_n = a[ii] * n;
		dphi += conj(a_n) * pow(chi, (1.0 - n)) / chi_pow;
		phi_bar -= a[ii] * pow(chi_bar, -n);
		psi += a[ii] * pow(chi, -n);
	}

	// Multiplying the constants
	L_frac = (4.0 / L) * exp(dcomp(0, -1) * mu);
	dphi *= L_frac;

	// Calcualting w
	w = 1 / (4 * G) * (0.5 * L * (Z - conj(Z)) * exp(dcomp(0, 1) * mu) * dphi + kappa * phi_bar + psi);

	return { w };
}
/* --------------------------------------------------------------------
		W TOTAL
-----------------------------------------------------------------------*/
inline dcomp  w_total(dcomp z, double kappa, double G, double sigma_11inf, dcvec z1a, dcvec z2a, dvec La, dvec mua, int ma, int na, ddcvec a)
{
	// Defining the variables
	dcomp w, wg;

	// Add the unfirm stress field
	w = w_uni(z, kappa, G, sigma_11inf);

	// Add the anlytic element for a crack
	if (na > 0)
	{
		for (int ii = 0; ii < na; ii++)
		{
			dcomp wc;
			wc = w_crack(z, kappa, G, z1a[ii], z2a[ii], La[ii], mua[ii], ma, a[ii]);
			w += wc * exp(dcomp(0, -1) * mua[ii]);
		}
	}

	return { w };
}
/* --------------------------------------------------------------------
		DISPLACEMENT FIELD
-----------------------------------------------------------------------*/
std::tuple<dvec, dvec, ddcvec> w_field(double xfrom, double xto, double yfrom, double yto, int Nw, double kappa, double G, double sigma_11inf, dcvec z1a, dcvec z2a, dvec La, dvec mua, int ma, int na, ddcvec a)

{
	// Defining the variables
	ddcvec grid_w(Nw, dcvec(Nw));
	dvec x_vecw(Nw), y_vecw(Nw);
	double dx;
	double dy;

	// Calcualte the displacement grid
	dx = (xto - xfrom) / (Nw - 1.0);
	dy = (yto - yfrom) / (Nw - 1.0);
#pragma omp parallel for default(none) shared(grid_w, x_vecw, y_vecw, kappa, G, sigma_11inf, z1a, z2a, La, mua, ma, na, a)
	for (int ii = 0; ii < Nw; ii++)
	{
		for (int jj = 0; jj < Nw; jj++)
		{

			x_vecw[ii] = xfrom + ii * dx;
			y_vecw[jj] = yfrom + jj * dy;
			grid_w[ii][jj] = w_total(dcomp(x_vecw[ii], y_vecw[jj]), kappa, G, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
		}
	}
	return { x_vecw, y_vecw, grid_w };
}
/*---------------------------------------------------------------------
		DISPLACEMENT TRAJECTORIES
-----------------------------------------------------------------------*/
ddcvec w_trajectories(double xfrom, double xto, double yfrom, double yto, int Ntraj, int Nw, double kappa, double G, double sigma_11inf, dcvec z1a, dcvec z2a, dvec La, dvec mua, int ma, int na, ddcvec a)
{
	// Defining the variables
	ddcvec traj_w((int)(Nw * 2), dcvec(Ntraj));
	double dx, dy_lvs;
	double cond, pi_val;
	int NIT;

	// Getting the starting points
	dx = (xto - xfrom) / (Ntraj);
	dy_lvs = (yto - yfrom) / (Nw - 1.0);
	pi_val = 0.5 * pi();
	cond = 1e-6;
	NIT = 10;

	// w trajectories
#pragma omp parallel for default(none) shared(traj_w, kappa, G, sigma_11inf, z1a, z2a, La, mua, ma, na, a)
	for (int ii = 0; ii < Nw; ii++)
	{
		traj_w[ii][0] = dcomp(xfrom, yfrom + ii * dy_lvs);
		dcomp z = traj_w[ii][0];
		dcomp z_old = z;
		double dx1 = dx;
		for (int jj = 1; jj < Ntraj; jj++)
		{
			dcomp zt, z_oldt, w, w1;
			double ee;
			w = w_total(z, kappa, G, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
			zt = z + conj(w) / abs(w) * dx1;

			ee = 1;
			for (int rr = NIT; rr--;)
			{
				z_oldt = zt;
				w1 = w_total(zt, kappa, G, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
				zt = z + conj(w + w1) / abs(w + w1) * dx1;
				ee = std::norm(z_oldt - zt);
				if (ee < cond)
				{
					break;
				}
			}

			traj_w[ii][jj] = zt;
			z_old = z;
			z = zt;
		}

		int kk = ii + Nw;
		traj_w[kk][0] = dcomp(xto, yfrom + ii * dy_lvs);
		z = traj_w[kk][0];
		z_old = z;
		dx1 = dx;
		for (int jj = 1; jj < Ntraj; jj++)
		{
			dcomp zt, z_oldt, w, w1;
			double ee;
			w = w_total(z, kappa, G, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
			zt = z + conj(w) / abs(w) * dx1;

			ee = 1;
			for (int rr = NIT; rr--;)
			{
				z_oldt = zt;
				w1 = w_total(zt, kappa, G, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
				zt = z + conj(w + w1) / abs(w + w1) * dx1;
				ee = std::norm(z_oldt - zt);
				if (ee < cond)
				{
					break;
				}
			}

			traj_w[kk][jj] = zt;
			z_old = z;
			z = zt;

		}
	}
	return { traj_w };
}

/* --------------------------------------------------------------------

		MAIN SCRIPT

-----------------------------------------------------------------------*/
int main()
{
	/* --------------------------------------------------------------------
			Defining variables and importing data
	-----------------------------------------------------------------------*/

	// Header in console window
	auto start = std::chrono::high_resolution_clock::now(); // Start the clock

	std::cout << "=================================================================" << std::endl << std::endl;
	std::cout << "		ANALYTIC ELEMENT LINEAR ELASTIC SOLVER	" << std::endl << std::endl;
	std::cout << "=================================================================" << std::endl << std::endl;

	auto date_time1 = std::chrono::system_clock::now();
	std::time_t start_time = std::chrono::system_clock::to_time_t(date_time1);
	char str_time1[26];
	ctime_s(str_time1, sizeof str_time1, &start_time);
	std::cout << "Program started: " << str_time1 << std::endl;

	// Setting the data types
	dcomp z;
	double kappa, sigma_11inf, G, cond;
	int na, ma, Na, NIT;

	// Read the input data from binary file PART 1
	std::ifstream input_file("geometry_data.bin", std::ios::in | std::ios::binary | std::ios::ate);
	std::streampos size = input_file.tellg();
	char* memblock = new char[size];
	input_file.seekg(0, std::ios::beg);
	input_file.read(memblock, size);
	double* fin = (double*)memblock;//reinterpret as doubles

	sigma_11inf = fin[0];
	kappa = fin[1];
	G = fin[2];
	na = (int)fin[3];
	ma = (int)fin[4];
	Na = (int)fin[5];
	cond = (double)fin[6];
	NIT = (int)fin[7];

	// Declaring the vecotrs
	dcvec z1a(na), z2a(na);
	dvec pa(na), La(na), mua(na);
	ddcvec a(na, dcvec(ma));

	int pos = 7 + 1;
	if (na > 0)
	{
		for (int ii = 0; ii < na; ii++)
		{
			int re = pos + ii;
			int im = pos + na + ii;
			z1a[ii] = dcomp(fin[re], fin[im]);
		}
		pos += 2 * na;
		for (int ii = 0; ii < na; ii++)
		{
			int re = pos + ii;
			int im = pos + na + ii;
			z2a[ii] = dcomp(fin[re], fin[im]);
		}
		pos += 2 * na;
		for (int ii = 0; ii < na; ii++)
		{
			pa[ii] = fin[pos + ii];
		}
		pos += na;
		for (int ii = 0; ii < na; ii++)
		{
			La[ii] = fin[pos + ii];
		}
		pos += na;
		for (int ii = 0; ii < na; ii++)
		{
			mua[ii] = fin[pos + ii];
		}
		pos += na;
	}
	else
	{
		z1a = { dcomp(0, 0) };
		z2a = { dcomp(0, 0) };
		La = { 0 };
		mua = { 0 };
		a = { { dcomp(0,0) } };
		ma = 1;
		Na = 1;
	}

	// Setting the data types
	double xfrom, xto, yfrom, yto;
	int Nx, Ny, Nw, Ntraj, lvs_traj;
	ddvec grid_11, grid_22, grid_12, grid_1, grid_2, theta_p;
	ddcvec traj_1, traj_2, grid_w, traj_w;
	dvec x_vec, y_vec, x_vecw, y_vecw;
	dcvec xtraj, ytraj;

	// Read the plot data from binary file
	std::ifstream plot_file("plot_data.bin", std::ios::in | std::ios::binary | std::ios::ate);
	std::streampos size2 = plot_file.tellg();
	char* memblock2 = new char[size2];
	plot_file.seekg(0, std::ios::beg);
	plot_file.read(memblock2, size2);
	double* fplot = (double*)memblock2;//reinterpret as doubles

	xfrom = fplot[0];
	xto = fplot[1];
	yfrom = fplot[2];
	yto = fplot[3];
	Nx = (int)fplot[4];
	Ny = (int)fplot[5];
	Nw = (int)fplot[6];
	Ntraj = (int)fplot[7];
	lvs_traj = (int)fplot[8];
	xtraj = { fplot[9] + dcomp(0,1) * fplot[10], fplot[11] + dcomp(0,1) * fplot[12] };
	ytraj = { fplot[13] + dcomp(0,1) * fplot[14], fplot[15] + dcomp(0,1) * fplot[16] };

	// Displying the plot data in the command window
	std::cout << "=================================================================" << std::endl << std::endl;
	std::cout << "		THE GEOMETRY DATA	" << std::endl << std::endl;
	std::cout << "=================================================================" << std::endl << std::endl;
	std::cout << "This is the retrived geometry data:\n";
	std::cout << "sigma_11inf = " << sigma_11inf << std::endl;
	std::cout << "      kappa = " << kappa << std::endl;
	std::cout << "          G = " << G << std::endl;
	std::cout << "         na = " << na << std::endl;
	std::cout << "         ma = " << ma << std::endl;
	std::cout << "         Na = " << Na << std::endl;
	// Displying the plot data in the command window
	std::cout << "=================================================================" << std::endl << std::endl;
	std::cout << "		THE READ PLOT DATA	" << std::endl << std::endl;
	std::cout << "=================================================================" << std::endl << std::endl;
	std::cout << "This is the retrived plot data:\n";
	std::cout << "x from: " << xfrom << " to " << xto << std::endl;
	std::cout << "y from: " << yfrom << " to " << yto << std::endl;
	std::cout << "x resolution: " << Nx << std::endl;
	std::cout << "y resolution: " << Ny << std::endl;
	std::cout << "Total number of points: " << Nx * Ny << std::endl;
	std::cout << "Number of steps in trajectories: " << Ntraj << std::endl;
	std::cout << "Number of trajectory levels: " << lvs_traj << std::endl;
	std::cout << "Total number of trajectory points: " << Ntraj * lvs_traj * 4 << std::endl;
	std::cout << "sigma_1 starting line from: " << xtraj[0] << " to " << xtraj[1] << std::endl;
	std::cout << "sigma_2 starting line from: " << ytraj[0] << " to " << ytraj[1] << std::endl;
	std::cout << "x and y quiver resolution: " << Nw << std::endl << std::endl;
	std::cout << std::endl;

	/* --------------------------------------------------------------------
			Solve the system
	-----------------------------------------------------------------------*/
	std::cout << "=================================================================" << std::endl << std::endl;
	std::cout << "		INITIALIZING THE SOVLER	" << std::endl << std::endl;
	std::cout << "=================================================================" << std::endl << std::endl;

	auto start_solv = std::chrono::high_resolution_clock::now();
	a = iterator(cond, NIT, Na, pa, sigma_11inf, z1a, z2a, La, mua, ma, na);
	auto stop_solv = std::chrono::high_resolution_clock::now();
	auto duration_solv = std::chrono::duration_cast<std::chrono::microseconds>(stop_solv - start_solv);

	// Displaying the computation time
	std::cout << std::endl;
	long long ms_solv = duration_solv.count();
	long long s_solv, m_solv, h_solv;
	std::tie(ms_solv, s_solv, m_solv, h_solv) = ms_to_time(ms_solv);
	std::cout << "Computations finnished after ";
	time_print(ms_solv, s_solv, m_solv, h_solv);

	std::cout << std::endl;

	/* --------------------------------------------------------------------
			Checking the error
	-----------------------------------------------------------------------*/
	double error_med_a_re{}, error_mean_a_re{}, error_max_a_re{}, error_med_a_im{}, error_mean_a_im{}, error_max_a_im{}, error_med_int{}, error_mean_int{}, error_max_int{};
	std::cout << "=================================================================" << std::endl << std::endl;
	std::cout << "		ERRORS	" << std::endl << std::endl;
	std::cout << "=================================================================" << std::endl << std::endl;
	int numa;
	numa = (int)round(Na);
	dcvec T_check(numa* na);
	if (na > 0)
	{
		// Calculate the error along the cracks, i.e. T = t_s + i*t_n = 0 + i*p
		dvec T_check_a_re(numa * na), T_check_a_im(numa * na);
		double theta_0a = pi()/numa;
		double delthetaa = (pi() - 2.0*theta_0a) / (numa-1.0);
		for (int ii = 0; ii < na; ii++)
		{
			for (int jj = 0; jj < numa; jj++)
			{
				dcomp z, tau_11, tau_12;
				double theta;
				theta = theta_0a + (jj) * delthetaa;
				z = z_from_chi(exp(dcomp(0, 1) * theta), z1a[ii], z2a[ii], La[ii], mua[ii]);
				T_check[(ii * numa) + jj] = T_total(z, sigma_11inf, z1a, z2a, La, mua, ma, na, a, -1, ii) + dcomp(0, pa[ii]);
				T_check_a_re[(ii * numa) + jj] = abs(real(T_check[(ii * numa) + jj]));
				T_check_a_im[(ii * numa) + jj] = abs(imag(T_check[(ii * numa) + jj]));
			}
		}
		// Median
		size_t n1 = T_check_a_re.size() / 2;
		nth_element(T_check_a_re.begin(), T_check_a_re.begin() + n1, T_check_a_re.end());
		error_med_a_re = T_check_a_re[n1];
		nth_element(T_check_a_im.begin(), T_check_a_im.begin() + n1, T_check_a_im.end());
		error_med_a_im = T_check_a_im[n1];
		// Mean
		error_mean_a_re = 1.0 * std::accumulate(T_check_a_re.begin(), T_check_a_re.end(), 0.0) / (n1 * 2);
		error_mean_a_im = 1.0 * std::accumulate(T_check_a_im.begin(), T_check_a_im.end(), 0.0) / (n1 * 2);
		// Max
		error_max_a_re = *max_element(T_check_a_re.begin(), T_check_a_re.end());
		error_max_a_im = *max_element(T_check_a_im.begin(), T_check_a_im.end());
		// Print the results
		std::cout << "     Analytic Element for a Crack" << std::endl;
		std::cout << "     Difference for ts" << std::endl;
		std::cout << "     Maximum = " << error_max_a_re << std::endl;
		std::cout << "     Mean    = " << error_mean_a_re << std::endl;
		std::cout << "     Median  = " << error_med_a_re << std::endl;
		std::cout << "     Difference for tn" << std::endl;
		std::cout << "     Maximum = " << error_max_a_im << std::endl;
		std::cout << "     Mean    = " << error_mean_a_im << std::endl;
		std::cout << "     Median  = " << error_med_a_im << std::endl;

		// Error at the intersection
		ddcvec zint(na, dcvec(na));
		iivec int_check(na, ivec(na));
		ivec int_count(na);

		// Find the intersection points
		#pragma omp parallel for default(none) shared(zint, int_check, int_count, z1a, z2a)
		for (int ii = 0; ii < na; ii++)
		{
			for (int jj = 0; jj < na; jj++)
			{
				std::tie(zint[ii][jj], int_check[ii][jj]) = intersection_point(z1a[ii], z2a[ii], z1a[jj], z2a[jj]);
			}
			int_count[ii] = std::accumulate(int_check[ii].begin(), int_check[ii].end(), 0.0);
		}
		int int_sum = std::accumulate(int_count.begin(), int_count.end(), 0.0);
		dvec int_error(int_sum);
		int cnt_int = 0;

		// Calcualte the error at the intersections, i.e. real(tau^11) = 0
		if (int_sum > 0)
		{
			for (int ii = 0; ii < na; ii++)
			{
				if (int_count[ii] > 0)
				{
					int cnt_er = 0;
					for (int jj = 0; jj < na; jj++)
					{
						if (int_check[ii][jj] == 1)
						{
							dcomp tau_11, tau_12, T;
							std::tie(tau_11, tau_12) = tau_total(zint[ii][jj], sigma_11inf, z1a, z2a, La, mua, ma, na, a, -1);
							T = T_total(z, sigma_11inf, z1a, z2a, La, mua, ma, na, a, -1, ii);
							std::cout << "z = " << zint[ii][jj] << " tau_11 = " << tau_11 << " tau_12 = " << tau_12 << " T = " << T << std::endl;
							int_error[cnt_int + cnt_er] = abs(real(tau_11));
							cnt_er += 1;
						}
					}
				}
				cnt_int += int_count[ii];
			}

			// Mean
			error_mean_int = std::accumulate(int_error.begin(), int_error.end(), 0.0) / (int_sum);
			// Max
			error_max_int = *max_element(int_error.begin(), int_error.end());
			// Median
			size_t n2 = int_error.size() / 2;
			nth_element(int_error.begin(), int_error.begin() + n2, int_error.end());
			error_med_int = int_error[n2];
			// Print the results
			std::cout << "     Intersection/s:" << std::endl;
			std::cout << "     Maximum = " << error_max_int << std::endl;
			std::cout << "     Mean    = " << error_mean_int << std::endl;
			std::cout << "     Median  = " << error_med_int << std::endl;
		}
	}

	// Create/open the two output files
	std::ofstream outfile_coef("input_data.bin", std::ios::out | std::ios::binary);

	// Save the plot properties
	dvec prop = { sigma_11inf,  kappa, G, 1.0 * na, 1.0 * ma};
	const char* pointerprop = reinterpret_cast<const char*>(&prop[0]);
	std::size_t bytesprop = prop.size() * sizeof(prop[0]);
	outfile_coef.write(pointerprop, bytesprop);

	// saving the coordinates of the cracks
	if (na > 0)
	{
		dvec fz1_re(na);
		dvec fz1_im(na);
		for (int jj = 0; jj < na; jj++)
		{
			fz1_re[jj] = real(z1a[jj]);
			fz1_im[jj] = imag(z1a[jj]);
		}
		const char* pointerz1_re = reinterpret_cast<const char*>(&fz1_re[0]);
		std::size_t bytesz1_re = fz1_re.size() * sizeof(fz1_re[0]);
		outfile_coef.write(pointerz1_re, bytesz1_re);
		const char* pointerz1_im = reinterpret_cast<const char*>(&fz1_im[0]);
		std::size_t bytesz1_im = fz1_im.size() * sizeof(fz1_im[0]);
		outfile_coef.write(pointerz1_im, bytesz1_im);

		dvec fz2_re(na);
		dvec fz2_im(na);
		for (int jj = 0; jj < na; jj++)
		{
			fz2_re[jj] = real(z2a[jj]);
			fz2_im[jj] = imag(z2a[jj]);
		}
		const char* pointerz2_re = reinterpret_cast<const char*>(&fz2_re[0]);
		std::size_t bytesz2_re = fz2_re.size() * sizeof(fz2_re[0]);
		outfile_coef.write(pointerz2_re, bytesz2_re);
		const char* pointerz2_im = reinterpret_cast<const char*>(&fz2_im[0]);
		std::size_t bytesz2_im = fz2_im.size() * sizeof(fz2_im[0]);
		outfile_coef.write(pointerz2_im, bytesz2_im);

		dvec fL = La;
		const char* pointerL = reinterpret_cast<const char*>(&fL[0]);
		std::size_t bytesL = fL.size() * sizeof(fL[0]);
		outfile_coef.write(pointerL, bytesL);

		dvec fmu = mua;
		const char* pointermu = reinterpret_cast<const char*>(&fmu[0]);
		std::size_t bytesmu = fmu.size() * sizeof(fmu[0]);
		outfile_coef.write(pointermu, bytesmu);

		// Save the a
		for (int ii = 0; ii < na; ii++)
		{
			dvec fbeta_re(ma);
			dvec fbeta_im(ma);

			for (int jj = 0; jj < ma; jj++)
			{
				fbeta_re[jj] = real(a[ii][jj]);
				fbeta_im[jj] = imag(a[ii][jj]);
			}
			const char* pointerbeta_re = reinterpret_cast<const char*>(&fbeta_re[0]);
			std::size_t bytesbeta_re = fbeta_re.size() * sizeof(fbeta_re[0]);
			outfile_coef.write(pointerbeta_re, bytesbeta_re);
			const char* pointerbeta_im = reinterpret_cast<const char*>(&fbeta_im[0]);
			std::size_t bytesbeta_im = fbeta_im.size() * sizeof(fbeta_im[0]);
			outfile_coef.write(pointerbeta_im, bytesbeta_im);
		}
	}

	// Estimate the time of the program
	std::cout << "=================================================================" << std::endl << std::endl;
	std::cout << "		ESTIMATED CALCULATION TIME	" << std::endl << std::endl;
	std::cout << "=================================================================" << std::endl << std::endl;
	int Ne = (Nx + Ny + Ntraj) / 100;
	if (Ne < 8)
	{
		Ne = 8;
	}
	auto start0 = std::chrono::high_resolution_clock::now();
	stress_field(xfrom, xto, yfrom, yto, Ne, Ne, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
	auto stop0 = std::chrono::high_resolution_clock::now();
	auto start01 = std::chrono::high_resolution_clock::now();
	principal_stress_trajectories(xfrom, xto, yfrom, yto, xtraj, ytraj, Ne, Ne, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
	auto stop01 = std::chrono::high_resolution_clock::now();
	auto duration0 = std::chrono::duration_cast<std::chrono::microseconds>(stop0 - start0);
	long long ms0 = duration0.count();
	auto duration01 = std::chrono::duration_cast<std::chrono::microseconds>(stop01 - start01);
	long long ms01 = duration01.count();
	long long calcs0 = (int)(2 * Nx * Ny) + (int)(Nw * Nw);
	long long calcs01 = (int)(Ntraj * lvs_traj) + (int)(Ntraj * Nw);
	ms0 = ((ms0 / ((int)Ne * Ne) * calcs0 + ms01 / ((int)Ne * Ne) * calcs01))*0.7;
	long long s0, m0, h0;
	std::tie(ms0, s0, m0, h0) = ms_to_time(ms0);
	std::cout << "Estimated calculation time:  ";
	time_print(ms0, s0, m0, h0);
	std::cout << std::endl << std::endl;
	auto date_time2 = std::chrono::system_clock::now();
	std::time_t start_time2 = std::chrono::system_clock::to_time_t(date_time2);
	char str_time2[26];
	ctime_s(str_time2, sizeof str_time2, &start_time2);
	std::cout << "Plotting started: " << str_time2 << std::endl << std::endl;

	/* --------------------------------------------------------------------
			Calculating the plots
	-----------------------------------------------------------------------*/

	std::cout << "=================================================================" << std::endl << std::endl;
	std::cout << "		COMPUTING THE PLOTS	" << std::endl << std::endl;
	std::cout << "=================================================================" << std::endl << std::endl;

	// Get the Cartisian stress field
	std::cout << "Initiating the stress field calculation\n";
	auto start1 = std::chrono::high_resolution_clock::now();
	std::tie(x_vec, y_vec, grid_11, grid_22, grid_12) = stress_field(xfrom, xto, yfrom, yto, Nx, Ny, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
	auto stop1 = std::chrono::high_resolution_clock::now();
	auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>(stop1 - start1);
	std::cout << "Completed, time taken by function: stress_field = ";
	long long ms1, s1, m1, h1;
	ms1 = duration1.count();
	std::tie(ms1, s1, m1, h1) = ms_to_time(ms1);
	time_print(ms1, s1, m1, h1);
	std::cout << std::endl << std::endl;

	// Get the principal stress field
	std::cout << "Initiating the principal stress field calculation\n";
	auto start2 = std::chrono::high_resolution_clock::now();
	std::tie(grid_1, grid_2, theta_p) = principal_stress_field(Nx, Ny, grid_11, grid_22, grid_12);
	auto stop2 = std::chrono::high_resolution_clock::now();
	auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>(stop2 - start2);
	std::cout << "Completed, time taken by function: principal_stress_field = ";
	ms1 = duration2.count();
	std::tie(ms1, s1, m1, h1) = ms_to_time(ms1);
	time_print(ms1, s1, m1, h1);
	std::cout << std::endl << std::endl;

	// Get the stress trajectories
	std::cout << "Initiating the principal stress trajectories calculation\n";
	auto start3 = std::chrono::high_resolution_clock::now();
	std::tie(traj_1, traj_2) = principal_stress_trajectories(xfrom, xto, yfrom, yto, xtraj, ytraj, Ntraj, lvs_traj, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
	auto stop3 = std::chrono::high_resolution_clock::now();
	auto duration3 = std::chrono::duration_cast<std::chrono::microseconds>(stop3 - start3);
	std::cout << "Completed, time taken by function: principal_stress_trajectories = ";
	ms1 = duration3.count();
	std::tie(ms1, s1, m1, h1) = ms_to_time(ms1);
	time_print(ms1, s1, m1, h1);
	std::cout << std::endl << std::endl;

	// Get the displacement field
	std::cout << "Initiating the displacement field calculation\n";
	auto start4 = std::chrono::high_resolution_clock::now();
	std::tie(x_vecw, y_vecw, grid_w) = w_field(xfrom, xto, yfrom, yto, Nw, kappa, G, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
	auto stop4 = std::chrono::high_resolution_clock::now();
	auto duration4 = std::chrono::duration_cast<std::chrono::microseconds>(stop4 - start4);
	std::cout << "Completed, time taken by function: w_field = ";
	ms1 = duration4.count();
	std::tie(ms1, s1, m1, h1) = ms_to_time(ms1);
	time_print(ms1, s1, m1, h1);
	std::cout << std::endl << std::endl;

	// Get the displacement trajectories
	std::cout << "Initiating the displacement trajectories calculation\n";
	auto start5 = std::chrono::high_resolution_clock::now();
	traj_w = w_trajectories(xfrom, xto, yfrom, yto, Ntraj, Nw, kappa, G, sigma_11inf, z1a, z2a, La, mua, ma, na, a);
	auto stop5 = std::chrono::high_resolution_clock::now();
	auto duration5 = std::chrono::duration_cast<std::chrono::microseconds>(stop5 - start5);
	std::cout << "Completed, time taken by function: w_trajectories = ";
	ms1 = duration5.count();
	std::tie(ms1, s1, m1, h1) = ms_to_time(ms1);
	time_print(ms1, s1, m1, h1);
	std::cout << std::endl << std::endl;

	// Displaying the computation time
	std::cout << "=================================================================" << std::endl << std::endl;
	long long mstime = duration1.count() + duration2.count() + duration3.count() + duration4.count() + duration5.count();
	long long stime, mtime, htime;
	std::tie(mstime, stime, mtime, htime) = ms_to_time(mstime);
	std::cout << "Total calculation time: ";
	time_print(mstime, stime, mtime, htime);
	std::cout << std::endl << std::endl;

	/* --------------------------------------------------------------------
			Saving the data as binary files
	-----------------------------------------------------------------------*/

	// Create/open the two output files
	std::ofstream outfile("data.bin", std::ios::out | std::ios::binary);
	std::ofstream outfiledim("dim_data.bin", std::ios::out | std::ios::binary);

	// Save the x and y vectors
	dvec fx = x_vec;
	const char* pointerx = reinterpret_cast<const char*>(&fx[0]);
	std::size_t bytesx = fx.size() * sizeof(fx[0]);
	outfile.write(pointerx, bytesx);

	dvec fy = y_vec;
	const char* pointery = reinterpret_cast<const char*>(&fy[0]);
	std::size_t bytesy = fy.size() * sizeof(fy[0]);
	outfile.write(pointery, bytesy);

	dvec fxw = x_vecw;
	const char* pointerxw = reinterpret_cast<const char*>(&fxw[0]);
	std::size_t bytesxw = fxw.size() * sizeof(fxw[0]);
	outfile.write(pointerxw, bytesxw);

	dvec fyw = y_vecw;
	const char* pointeryw = reinterpret_cast<const char*>(&fyw[0]);
	std::size_t bytesyw = fyw.size() * sizeof(fyw[0]);
	outfile.write(pointeryw, bytesyw);

	// Save the grids
	for (size_t ii = 0; ii < grid_11.size(); ii++)
	{
		dvec fg11 = grid_11[ii];
		const char* pointerg11 = reinterpret_cast<const char*>(&fg11[0]);
		std::size_t bytesg11 = fg11.size() * sizeof(fg11[0]);
		outfile.write(pointerg11, bytesg11);
	}
	for (size_t ii = 0; ii < grid_22.size(); ii++)
	{
		dvec fg22 = grid_22[ii];
		const char* pointerg22 = reinterpret_cast<const char*>(&fg22[0]);
		std::size_t bytesg22 = fg22.size() * sizeof(fg22[0]);
		outfile.write(pointerg22, bytesg22);
	}
	for (size_t ii = 0; ii < grid_12.size(); ii++)
	{
		dvec fg12 = grid_12[ii];
		const char* pointerg12 = reinterpret_cast<const char*>(&fg12[0]);
		std::size_t bytesg12 = fg12.size() * sizeof(fg12[0]);
		outfile.write(pointerg12, bytesg12);
	}
	for (size_t ii = 0; ii < grid_1.size(); ii++)
	{
		dvec fg1 = grid_1[ii];
		const char* pointerg1 = reinterpret_cast<const char*>(&fg1[0]);
		std::size_t bytesg1 = fg1.size() * sizeof(fg1[0]);
		outfile.write(pointerg1, bytesg1);
	}
	for (size_t ii = 0; ii < grid_2.size(); ii++)
	{
		dvec fg2 = grid_2[ii];
		const char* pointerg2 = reinterpret_cast<const char*>(&fg2[0]);
		std::size_t bytesg2 = fg2.size() * sizeof(fg2[0]);
		outfile.write(pointerg2, bytesg2);
	}
	for (size_t ii = 0; ii < theta_p.size(); ii++)
	{
		dvec fgtp = theta_p[ii];
		const char* pointergtp = reinterpret_cast<const char*>(&fgtp[0]);
		std::size_t bytesgtp = fgtp.size() * sizeof(fgtp[0]);
		outfile.write(pointergtp, bytesgtp);
	}
	for (size_t ii = 0; ii < traj_1.size(); ii++)
	{
		dvec fgt1_re(Ntraj);
		dvec fgt1_im(Ntraj);

		for (size_t jj = 0; jj < traj_1[0].size(); jj++)
		{
			fgt1_re[jj] = real(traj_1[ii][jj]);
			fgt1_im[jj] = imag(traj_1[ii][jj]);
		}
		const char* pointergt1_re = reinterpret_cast<const char*>(&fgt1_re[0]);
		std::size_t bytesgt1_re = fgt1_re.size() * sizeof(fgt1_re[0]);
		outfile.write(pointergt1_re, bytesgt1_re);
		const char* pointergt1_im = reinterpret_cast<const char*>(&fgt1_im[0]);
		std::size_t bytesgt1_im = fgt1_im.size() * sizeof(fgt1_im[0]);
		outfile.write(pointergt1_im, bytesgt1_im);
	}
	for (size_t ii = 0; ii < traj_2.size(); ii++)
	{
		dvec fgt2_re(Ntraj);
		dvec fgt2_im(Ntraj);
		for (size_t jj = 0; jj < traj_2[0].size(); jj++)
		{
			fgt2_re[jj] = real(traj_2[ii][jj]);
			fgt2_im[jj] = imag(traj_2[ii][jj]);
		}
		const char* pointergt2_re = reinterpret_cast<const char*>(&fgt2_re[0]);
		std::size_t bytesgt2_re = fgt2_re.size() * sizeof(fgt2_re[0]);
		outfile.write(pointergt2_re, bytesgt2_re);
		const char* pointergt2_im = reinterpret_cast<const char*>(&fgt2_im[0]);
		std::size_t bytesgt2_im = fgt2_im.size() * sizeof(fgt2_im[0]);
		outfile.write(pointergt2_im, bytesgt2_im);
	}
	for (size_t ii = 0; ii < grid_w.size(); ii++)
	{
		dvec fgw_re(Nw);
		dvec fgw_im(Nw);
		for (size_t jj = 0; jj < grid_w[0].size(); jj++)
		{
			fgw_re[jj] = real(grid_w[ii][jj]);
			fgw_im[jj] = imag(grid_w[ii][jj]);
		}
		const char* pointergw_re = reinterpret_cast<const char*>(&fgw_re[0]);
		std::size_t bytesgw_re = fgw_re.size() * sizeof(fgw_re[0]);
		outfile.write(pointergw_re, bytesgw_re);
		const char* pointergw_im = reinterpret_cast<const char*>(&fgw_im[0]);
		std::size_t bytesgw_im = fgw_im.size() * sizeof(fgw_im[0]);
		outfile.write(pointergw_im, bytesgw_im);
	}
	for (size_t ii = 0; ii < traj_w.size(); ii++)
	{
		dvec fgtw_re(Ntraj);
		dvec fgtw_im(Ntraj);
		for (size_t jj = 0; jj < traj_w[0].size(); jj++)
		{
			fgtw_re[jj] = real(traj_w[ii][jj]);
			fgtw_im[jj] = imag(traj_w[ii][jj]);
		}
		const char* pointergtw_re = reinterpret_cast<const char*>(&fgtw_re[0]);
		std::size_t bytesgtw_re = fgtw_re.size() * sizeof(fgtw_re[0]);
		outfile.write(pointergtw_re, bytesgtw_re);
		const char* pointergtw_im = reinterpret_cast<const char*>(&fgtw_im[0]);
		std::size_t bytesgtw_im = fgtw_im.size() * sizeof(fgtw_im[0]);
		outfile.write(pointergtw_im, bytesgtw_im);
	}
	if (na > 0)
	{
		dvec fT_res(numa * na);
		dvec fT_ims(numa * na);
		for (int jj = 0; jj < numa * na; jj++)
		{
			fT_res[jj] = real(T_check[jj]);
			fT_ims[jj] = imag(T_check[jj]);
		}
		const char* pointerT_res = reinterpret_cast<const char*>(&fT_res[0]);
		std::size_t bytesT_res = fT_res.size() * sizeof(fT_res[0]);
		outfile.write(pointerT_res, bytesT_res);
		const char* pointerT_ims = reinterpret_cast<const char*>(&fT_ims[0]);
		std::size_t bytesT_ims = fT_ims.size() * sizeof(fT_ims[0]);
		outfile.write(pointerT_ims, bytesT_ims);
	}

	// Save the plot properties
	dvec dim = { 1.0 * Nx, 1.0 * Ny, 1.0 * Nw, 1.0 * Ntraj, 1.0 * lvs_traj, 1.0 * na, error_med_a_re, error_mean_a_re, error_max_a_re, error_med_a_im, error_mean_a_im, error_max_a_im, error_med_int, error_mean_int, error_max_int};
	const char* pointerdim = reinterpret_cast<const char*>(&dim[0]);
	std::size_t bytesdim = dim.size() * sizeof(dim[0]);
	outfiledim.write(pointerdim, bytesdim);

	// saving the coordinates of the cracks
	if (na > 0)
	{
		dvec fz1_res(na);
		dvec fz1_ims(na);
		for (int jj = 0; jj < na; jj++)
		{
			fz1_res[jj] = real(z1a[jj]);
			fz1_ims[jj] = imag(z1a[jj]);
		}
		const char* pointerz1_res = reinterpret_cast<const char*>(&fz1_res[0]);
		std::size_t bytesz1_res = fz1_res.size() * sizeof(fz1_res[0]);
		outfiledim.write(pointerz1_res, bytesz1_res);
		const char* pointerz1_ims = reinterpret_cast<const char*>(&fz1_ims[0]);
		std::size_t bytesz1_ims = fz1_ims.size() * sizeof(fz1_ims[0]);
		outfiledim.write(pointerz1_ims, bytesz1_ims);

		dvec fz2_res(na);
		dvec fz2_ims(na);
		for (int jj = 0; jj < na; jj++)
		{
			fz2_res[jj] = real(z2a[jj]);
			fz2_ims[jj] = imag(z2a[jj]);
		}
		const char* pointerz2_re = reinterpret_cast<const char*>(&fz2_res[0]);
		std::size_t bytesz2_re = fz2_res.size() * sizeof(fz2_res[0]);
		outfiledim.write(pointerz2_re, bytesz2_re);
		const char* pointerz2_im = reinterpret_cast<const char*>(&fz2_ims[0]);
		std::size_t bytesz2_im = fz2_ims.size() * sizeof(fz2_ims[0]);
		outfiledim.write(pointerz2_im, bytesz2_im);

	}

	// Close the output files
	outfile.close();
	outfiledim.close();

	// Get the date and execution time
	auto end = std::chrono::high_resolution_clock::now();
	auto date_time3 = std::chrono::system_clock::now();
	auto elapsed_seconds = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
	long long mseconds = elapsed_seconds.count();
	long long seconds, hours, minutes;
	std::tie(mseconds, seconds, minutes, hours) = ms_to_time(mseconds);
	std::time_t end_time = std::chrono::system_clock::to_time_t(date_time3);
	char str_time[26];
	ctime_s(str_time, sizeof str_time, &end_time);

	std::cout << "Program finnished after ";
	time_print(mseconds, seconds, minutes, hours);
	std::cout << std::endl;
	std::cout << "Output data saved to binary files: data.bin and dim_data.bin" << std::endl;

	return 0;
}


#include <emscripten.h>
#include <emscripten/bind.h>
#include "geometrycentral/surface/manifold_surface_mesh.h"
#include "geometrycentral/utilities/mesh_data.h"
#include "geometrycentral/surface/edge_length_geometry.h"
#include "geometrycentral/surface/heat_method_distance.h"
#include <Eigen/Core>
#include <stdio.h>
#include <iostream>

using namespace geometrycentral;
using namespace geometrycentral::surface;
typedef int iptr_t;
typedef int dptr_t;

// mesh data
static Eigen::MatrixX3i faces;
static Eigen::MatrixX3d edges;
static EdgeData<double> edgeLengths;

// mesh containers
static std::unique_ptr<ManifoldSurfaceMesh> mesh;
static std::unique_ptr<EdgeLengthGeometry> geometry;

// the Heat Method solver
static std::unique_ptr<HeatMethodDistanceSolver> heatSolver;

// the output vertex data
static VertexData<double> distToSource;

// parameters
static double timeStep = 1.0;
static bool robust = false;
static bool verbose = true;

std::string getExceptionMessage(intptr_t exceptionPtr) {
    return std::string(reinterpret_cast<std::exception *>(exceptionPtr)->what());
}

EMSCRIPTEN_BINDINGS(Bindings) {
  emscripten::function("getExceptionMessage", &getExceptionMessage);
};

extern "C" {

  EMSCRIPTEN_KEEPALIVE
  iptr_t allocate_faces(size_t num_faces){
    faces.resize(num_faces, 3);
    edges.resize(num_faces, 3);
    return reinterpret_cast<iptr_t>(&faces(0, 0));
  }
  EMSCRIPTEN_KEEPALIVE
  void set_face(size_t f, size_t idx0, size_t idx1, size_t idx2){
    faces(f, 0) = idx0;
    faces(f, 1) = idx1;
    faces(f, 2) = idx2;
  }
  EMSCRIPTEN_KEEPALIVE
  void set_face_edges(size_t f, double e0, double e1, double e2){
    edges(f, 0) = e0;
    edges(f, 1) = e1;
    edges(f, 2) = e2;
  }
  EMSCRIPTEN_KEEPALIVE
  void print_faces(){
    std::cout << "Faces:\n" << faces << "\n";
  }
  EMSCRIPTEN_KEEPALIVE
  dptr_t get_edge_ptr(){
    return reinterpret_cast<dptr_t>(&edges(0, 0));
  }
  EMSCRIPTEN_KEEPALIVE
  void print_edges(){
    std::cout << "Edges:\n" << edges << "\n";
  }

  EMSCRIPTEN_KEEPALIVE
  iptr_t allocate_edges(size_t num_edges){
    edges.resize(num_edges, 3);
    return reinterpret_cast<iptr_t>(&edges(0, 0));
  }

  EMSCRIPTEN_KEEPALIVE
  void set_verbose(bool v = true){
    verbose = v;
  }
  EMSCRIPTEN_KEEPALIVE
  void set_quiet(){
    set_verbose(false);
  }
  EMSCRIPTEN_KEEPALIVE
  void set_time_step(double step){
    timeStep = step;
  }
  EMSCRIPTEN_KEEPALIVE
  void set_robust(bool flag){
    robust = flag;
  }

  EMSCRIPTEN_KEEPALIVE
  void create_surface_mesh(){
    // create underlying mesh topology
    mesh.reset(new ManifoldSurfaceMesh(faces));
    mesh->compress();
    if(verbose)
      mesh->printStatistics();
  }

  EMSCRIPTEN_KEEPALIVE
  void precompute(){

    // create implicit geometry using edge lengths and mesh
    edgeLengths = EdgeData<double>(*mesh);
    for(size_t i = 0; i < faces.rows(); ++i){
      if(verbose)
        printf("Setting lengths of face #%zu\n", i);
      Face f = mesh->face(i);
      if(!f.isTriangle()){
        printf("Face is not a triangle\n");
        return;
      }
      Halfedge he = f.halfedge(); edgeLengths[he.edge()] = edges(i, 0);
      he = he.next(); edgeLengths[he.edge()] = edges(i, 1);
      he = he.next(); edgeLengths[he.edge()] = edges(i, 2);
    }
    if(verbose){
      std::cout << "Edges:\n" << edgeLengths.raw() << "\n";
      for(Edge e : mesh->edges()){
        printf("Edge #%zu = %g\n", e.getIndex(), edgeLengths[e]);
      }
    }
    geometry.reset(new EdgeLengthGeometry(*mesh, edgeLengths));

    // create heat method distance solver (precomputation happens here)
    heatSolver.reset(new HeatMethodDistanceSolver(*geometry, timeStep, robust));
  }

  EMSCRIPTEN_KEEPALIVE
  dptr_t compute_from_source(size_t srcIndex){
    const Vertex v = mesh->vertex(srcIndex);
    distToSource = heatSolver->computeDistance(v);

    if(verbose)
      printf("Returning result pointer\n");

    Eigen::VectorXd &mat = distToSource.raw();
    double* ptr = &mat(0, 0);
    return reinterpret_cast<dptr_t>(ptr);
  }

}