/******************************************************************************

  This source file is part of the Avogadro project.

  Copyright (C) 2010 Eric C. Brown

  This source code is released under the New BSD License, (the "License").

  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.

******************************************************************************/

#include <cmath>

#include "qtaimwavefunctionevaluator.h"

namespace Avogadro {
namespace QtPlugins {

QTAIMWavefunctionEvaluator::QTAIMWavefunctionEvaluator(QTAIMWavefunction& wfn)
{

  m_nmo = wfn.numberOfMolecularOrbitals();
  m_nprim = wfn.numberOfGaussianPrimitives();
  m_nnuc = wfn.numberOfNuclei();

  m_nucxcoord =
    Map<const Matrix<qreal, Dynamic, 1>>(wfn.xNuclearCoordinates(), m_nnuc);
  m_nucycoord =
    Map<const Matrix<qreal, Dynamic, 1>>(wfn.yNuclearCoordinates(), m_nnuc);
  m_nuczcoord =
    Map<const Matrix<qreal, Dynamic, 1>>(wfn.zNuclearCoordinates(), m_nnuc);
  m_nucz = Map<const Matrix<qint64, Dynamic, 1>>(wfn.nuclearCharges(), m_nnuc);
  m_X0 = Map<const Matrix<qreal, Dynamic, 1>>(
    wfn.xGaussianPrimitiveCenterCoordinates(), m_nprim, 1);
  m_Y0 = Map<const Matrix<qreal, Dynamic, 1>>(
    wfn.yGaussianPrimitiveCenterCoordinates(), m_nprim, 1);
  m_Z0 = Map<const Matrix<qreal, Dynamic, 1>>(
    wfn.zGaussianPrimitiveCenterCoordinates(), m_nprim, 1);
  m_xamom = Map<const Matrix<qint64, Dynamic, 1>>(
    wfn.xGaussianPrimitiveAngularMomenta(), m_nprim, 1);
  m_yamom = Map<const Matrix<qint64, Dynamic, 1>>(
    wfn.yGaussianPrimitiveAngularMomenta(), m_nprim, 1);
  m_zamom = Map<const Matrix<qint64, Dynamic, 1>>(
    wfn.zGaussianPrimitiveAngularMomenta(), m_nprim, 1);
  m_alpha = Map<const Matrix<qreal, Dynamic, 1>>(
    wfn.gaussianPrimitiveExponentCoefficients(), m_nprim, 1);
  // TODO Implement screening for unoccupied molecular orbitals.
  m_occno = Map<const Matrix<qreal, Dynamic, 1>>(
    wfn.molecularOrbitalOccupationNumbers(), m_nmo, 1);
  m_orbe = Map<const Matrix<qreal, Dynamic, 1>>(
    wfn.molecularOrbitalEigenvalues(), m_nmo, 1);
  m_coef = Map<const Matrix<qreal, Dynamic, Dynamic, RowMajor>>(
    wfn.molecularOrbitalCoefficients(), m_nmo, m_nprim);
  m_totalEnergy = wfn.totalEnergy();
  m_virialRatio = wfn.virialRatio();

  m_cutoff = log(1.e-15);

  m_cdg000.resize(m_nmo);
  m_cdg100.resize(m_nmo);
  m_cdg010.resize(m_nmo);
  m_cdg001.resize(m_nmo);
  m_cdg200.resize(m_nmo);
  m_cdg110.resize(m_nmo);
  m_cdg101.resize(m_nmo);
  m_cdg020.resize(m_nmo);
  m_cdg011.resize(m_nmo);
  m_cdg002.resize(m_nmo);
  m_cdg300.resize(m_nmo);
  m_cdg120.resize(m_nmo);
  m_cdg102.resize(m_nmo);
  m_cdg210.resize(m_nmo);
  m_cdg030.resize(m_nmo);
  m_cdg012.resize(m_nmo);
  m_cdg201.resize(m_nmo);
  m_cdg021.resize(m_nmo);
  m_cdg003.resize(m_nmo);
  m_cdg111.resize(m_nmo);
  m_cdg400.resize(m_nmo);
  m_cdg220.resize(m_nmo);
  m_cdg202.resize(m_nmo);
  m_cdg310.resize(m_nmo);
  m_cdg130.resize(m_nmo);
  m_cdg112.resize(m_nmo);
  m_cdg301.resize(m_nmo);
  m_cdg121.resize(m_nmo);
  m_cdg103.resize(m_nmo);
  m_cdg040.resize(m_nmo);
  m_cdg022.resize(m_nmo);
  m_cdg211.resize(m_nmo);
  m_cdg031.resize(m_nmo);
  m_cdg013.resize(m_nmo);
  m_cdg004.resize(m_nmo);
}

qreal QTAIMWavefunctionEvaluator::molecularOrbital(
  const qint64 mo, const Matrix<qreal, 3, 1> xyz)
{

  qreal value = 0.0;

  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qreal ax0 = ipow(xx0, m_xamom(p));
      qreal ay0 = ipow(yy0, m_yamom(p));
      qreal az0 = ipow(zz0, m_zamom(p));

      qreal b0 = exp(b0arg);

      qreal dg000 = ax0 * ay0 * az0 * b0;

      value += m_coef(mo, p) * dg000;
    }
  }

  return value;
}

qreal QTAIMWavefunctionEvaluator::electronDensity(const Matrix<qreal, 3, 1> xyz)
{

  qreal value;

  m_cdg000.setZero();
  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qreal ax0 = ipow(xx0, m_xamom(p));
      qreal ay0 = ipow(yy0, m_yamom(p));
      qreal az0 = ipow(zz0, m_zamom(p));

      qreal b0 = exp(b0arg);

      qreal dg000 = ax0 * ay0 * az0 * b0;

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
      }
    }
  }

  value = 0.0;
  for (qint64 m = 0; m < m_nmo; ++m) {
    value += m_occno(m) * ipow(m_cdg000(m), 2);
  }

  return value;
}

const Matrix<qreal, 3, 1> QTAIMWavefunctionEvaluator::gradientOfElectronDensity(
  Matrix<qreal, 3, 1> xyz)
{

  Matrix<qreal, 3, 1> value;

  const qreal zero = 0.0;
  const qreal one = 1.0;

  m_cdg000.setZero();
  m_cdg100.setZero();
  m_cdg010.setZero();
  m_cdg001.setZero();
  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qint64 aax0 = 1;
      qint64 aay0 = 1;
      qint64 aaz0 = 1;
      qint64 aax1 = m_xamom(p);
      qint64 aay1 = m_yamom(p);
      qint64 aaz1 = m_zamom(p);

      qreal ax0 = aax0 * ipow(xx0, m_xamom(p));
      qreal ay0 = aay0 * ipow(yy0, m_yamom(p));
      qreal az0 = aaz0 * ipow(zz0, m_zamom(p));

      qreal ax1;
      qreal ay1;
      qreal az1;
      if (m_xamom(p) < 1) {
        ax1 = zero;
      } else if (m_xamom(p) == 1) {
        ax1 = one;
      } else {
        ax1 = aax1 * ipow(xx0, m_xamom(p) - 1);
      }

      if (m_yamom(p) < 1) {
        ay1 = zero;
      } else if (m_yamom(p) == 1) {
        ay1 = one;
      } else {
        ay1 = aay1 * ipow(yy0, m_yamom(p) - 1);
      }

      if (m_zamom(p) < 1) {
        az1 = zero;
      } else if (m_zamom(p) == 1) {
        az1 = one;
      } else {
        az1 = aaz1 * ipow(zz0, m_zamom(p) - 1);
      }

      qreal b0 = exp(b0arg);

      qreal bx1 = -2 * m_alpha(p) * xx0;
      qreal by1 = -2 * m_alpha(p) * yy0;
      qreal bz1 = -2 * m_alpha(p) * zz0;

      qreal dg000 = ax0 * ay0 * az0 * b0;
      qreal dg100 = ay0 * az0 * b0 * (ax1 + ax0 * bx1);
      qreal dg010 = ax0 * az0 * b0 * (ay1 + ay0 * by1);
      qreal dg001 = ax0 * ay0 * b0 * (az1 + az0 * bz1);

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
        m_cdg100(m) += m_coef(m, p) * dg100;
        m_cdg010(m) += m_coef(m, p) * dg010;
        m_cdg001(m) += m_coef(m, p) * dg001;
      }
    }
  }

  value.setZero();
  for (qint64 m = 0; m < m_nmo; ++m) {
    value(0) += m_occno(m) * m_cdg100(m) * m_cdg000(m);
    value(1) += m_occno(m) * m_cdg010(m) * m_cdg000(m);
    value(2) += m_occno(m) * m_cdg001(m) * m_cdg000(m);
  }

  return value;
}

const Matrix<qreal, 3, 3> QTAIMWavefunctionEvaluator::hessianOfElectronDensity(
  const Matrix<qreal, 3, 1> xyz)
{

  Matrix<qreal, 3, 3> value;

  const qreal zero = 0.0;
  const qreal one = 1.0;

  m_cdg000.setZero();
  m_cdg100.setZero();
  m_cdg010.setZero();
  m_cdg001.setZero();
  m_cdg200.setZero();
  m_cdg020.setZero();
  m_cdg002.setZero();
  m_cdg110.setZero();
  m_cdg101.setZero();
  m_cdg011.setZero();
  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qint64 aax0 = 1;
      qint64 aay0 = 1;
      qint64 aaz0 = 1;
      qint64 aax1 = m_xamom(p);
      qint64 aay1 = m_yamom(p);
      qint64 aaz1 = m_zamom(p);
      qint64 aax2 = m_xamom(p) * (m_xamom(p) - 1);
      qint64 aay2 = m_yamom(p) * (m_yamom(p) - 1);
      qint64 aaz2 = m_zamom(p) * (m_zamom(p) - 1);

      qreal ax0 = aax0 * ipow(xx0, m_xamom(p));
      qreal ay0 = aay0 * ipow(yy0, m_yamom(p));
      qreal az0 = aaz0 * ipow(zz0, m_zamom(p));

      qreal ax1;
      qreal ay1;
      qreal az1;
      if (m_xamom(p) < 1) {
        ax1 = zero;
      } else if (m_xamom(p) == 1) {
        ax1 = one;
      } else {
        ax1 = aax1 * ipow(xx0, m_xamom(p) - 1);
      }

      if (m_yamom(p) < 1) {
        ay1 = zero;
      } else if (m_yamom(p) == 1) {
        ay1 = one;
      } else {
        ay1 = aay1 * ipow(yy0, m_yamom(p) - 1);
      }

      if (m_zamom(p) < 1) {
        az1 = zero;
      } else if (m_zamom(p) == 1) {
        az1 = one;
      } else {
        az1 = aaz1 * ipow(zz0, m_zamom(p) - 1);
      }

      qreal ax2;
      qreal ay2;
      qreal az2;
      if (m_xamom(p) < 2) {
        ax2 = zero;
      } else if (m_xamom(p) == 2) {
        ax2 = one;
      } else {
        ax2 = aax2 * ipow(xx0, m_xamom(p) - 2);
      }

      if (m_yamom(p) < 2) {
        ay2 = zero;
      } else if (m_yamom(p) == 2) {
        ay2 = one;
      } else {
        ay2 = aay2 * ipow(yy0, m_yamom(p) - 2);
      }

      if (m_zamom(p) < 2) {
        az2 = zero;
      } else if (m_zamom(p) == 2) {
        az2 = one;
      } else {
        az2 = aaz2 * ipow(zz0, m_zamom(p) - 2);
      }

      qreal b0 = exp(b0arg);

      qreal bx1 = -2 * m_alpha(p) * xx0;
      qreal by1 = -2 * m_alpha(p) * yy0;
      qreal bz1 = -2 * m_alpha(p) * zz0;
      qreal bx2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(xx0, 2));
      qreal by2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(yy0, 2));
      qreal bz2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(zz0, 2));

      qreal dg000 = ax0 * ay0 * az0 * b0;
      qreal dg100 = ay0 * az0 * b0 * (ax1 + ax0 * bx1);
      qreal dg010 = ax0 * az0 * b0 * (ay1 + ay0 * by1);
      qreal dg001 = ax0 * ay0 * b0 * (az1 + az0 * bz1);
      qreal dg200 = ay0 * az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2);
      qreal dg020 = ax0 * az0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg002 = ax0 * ay0 * b0 * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg110 = az0 * b0 * (ax1 + ax0 * bx1) * (ay1 + ay0 * by1);
      qreal dg101 = ay0 * b0 * (ax1 + ax0 * bx1) * (az1 + az0 * bz1);
      qreal dg011 = ax0 * b0 * (ay1 + ay0 * by1) * (az1 + az0 * bz1);

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
        m_cdg100(m) += m_coef(m, p) * dg100;
        m_cdg010(m) += m_coef(m, p) * dg010;
        m_cdg001(m) += m_coef(m, p) * dg001;
        m_cdg200(m) += m_coef(m, p) * dg200;
        m_cdg020(m) += m_coef(m, p) * dg020;
        m_cdg002(m) += m_coef(m, p) * dg002;
        m_cdg110(m) += m_coef(m, p) * dg110;
        m_cdg101(m) += m_coef(m, p) * dg101;
        m_cdg011(m) += m_coef(m, p) * dg011;
      }
    }
  }

  value.setZero();
  for (qint64 m = 0; m < m_nmo; ++m) {
    value(0, 0) +=
      2 * m_occno(m) * (ipow(m_cdg100(m), 2) + m_cdg000(m) * m_cdg200(m));
    value(1, 1) +=
      2 * m_occno(m) * (ipow(m_cdg010(m), 2) + m_cdg000(m) * m_cdg020(m));
    value(2, 2) +=
      2 * m_occno(m) * (ipow(m_cdg001(m), 2) + m_cdg000(m) * m_cdg002(m));
    value(0, 1) +=
      2 * m_occno(m) * (m_cdg100(m) * m_cdg010(m) + m_cdg000(m) * m_cdg110(m));
    value(0, 2) +=
      2 * m_occno(m) * (m_cdg100(m) * m_cdg001(m) + m_cdg000(m) * m_cdg101(m));
    value(1, 2) +=
      2 * m_occno(m) * (m_cdg010(m) * m_cdg001(m) + m_cdg000(m) * m_cdg011(m));
  }
  value(1, 0) = value(0, 1);
  value(2, 0) = value(0, 2);
  value(2, 1) = value(1, 2);

  return value;
}

const Matrix<qreal, 3, 4>
QTAIMWavefunctionEvaluator::gradientAndHessianOfElectronDensity(
  const Matrix<qreal, 3, 1> xyz)
{

  Matrix<qreal, 3, 1> gValue;
  Matrix<qreal, 3, 3> hValue;
  Matrix<qreal, 3, 4> value;

  const qreal zero = 0.0;
  const qreal one = 1.0;

  m_cdg000.setZero();
  m_cdg100.setZero();
  m_cdg010.setZero();
  m_cdg001.setZero();
  m_cdg200.setZero();
  m_cdg020.setZero();
  m_cdg002.setZero();
  m_cdg110.setZero();
  m_cdg101.setZero();
  m_cdg011.setZero();
  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qint64 aax0 = 1;
      qint64 aay0 = 1;
      qint64 aaz0 = 1;
      qint64 aax1 = m_xamom(p);
      qint64 aay1 = m_yamom(p);
      qint64 aaz1 = m_zamom(p);
      qint64 aax2 = m_xamom(p) * (m_xamom(p) - 1);
      qint64 aay2 = m_yamom(p) * (m_yamom(p) - 1);
      qint64 aaz2 = m_zamom(p) * (m_zamom(p) - 1);

      qreal ax0 = aax0 * ipow(xx0, m_xamom(p));
      qreal ay0 = aay0 * ipow(yy0, m_yamom(p));
      qreal az0 = aaz0 * ipow(zz0, m_zamom(p));

      qreal ax1;
      qreal ay1;
      qreal az1;
      if (m_xamom(p) < 1) {
        ax1 = zero;
      } else if (m_xamom(p) == 1) {
        ax1 = one;
      } else {
        ax1 = aax1 * ipow(xx0, m_xamom(p) - 1);
      }

      if (m_yamom(p) < 1) {
        ay1 = zero;
      } else if (m_yamom(p) == 1) {
        ay1 = one;
      } else {
        ay1 = aay1 * ipow(yy0, m_yamom(p) - 1);
      }

      if (m_zamom(p) < 1) {
        az1 = zero;
      } else if (m_zamom(p) == 1) {
        az1 = one;
      } else {
        az1 = aaz1 * ipow(zz0, m_zamom(p) - 1);
      }

      qreal ax2;
      qreal ay2;
      qreal az2;
      if (m_xamom(p) < 2) {
        ax2 = zero;
      } else if (m_xamom(p) == 2) {
        ax2 = one;
      } else {
        ax2 = aax2 * ipow(xx0, m_xamom(p) - 2);
      }

      if (m_yamom(p) < 2) {
        ay2 = zero;
      } else if (m_yamom(p) == 2) {
        ay2 = one;
      } else {
        ay2 = aay2 * ipow(yy0, m_yamom(p) - 2);
      }

      if (m_zamom(p) < 2) {
        az2 = zero;
      } else if (m_zamom(p) == 2) {
        az2 = one;
      } else {
        az2 = aaz2 * ipow(zz0, m_zamom(p) - 2);
      }

      qreal b0 = exp(b0arg);

      qreal bx1 = -2 * m_alpha(p) * xx0;
      qreal by1 = -2 * m_alpha(p) * yy0;
      qreal bz1 = -2 * m_alpha(p) * zz0;
      qreal bx2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(xx0, 2));
      qreal by2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(yy0, 2));
      qreal bz2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(zz0, 2));

      qreal dg000 = ax0 * ay0 * az0 * b0;
      qreal dg100 = ay0 * az0 * b0 * (ax1 + ax0 * bx1);
      qreal dg010 = ax0 * az0 * b0 * (ay1 + ay0 * by1);
      qreal dg001 = ax0 * ay0 * b0 * (az1 + az0 * bz1);
      qreal dg200 = ay0 * az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2);
      qreal dg020 = ax0 * az0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg002 = ax0 * ay0 * b0 * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg110 = az0 * b0 * (ax1 + ax0 * bx1) * (ay1 + ay0 * by1);
      qreal dg101 = ay0 * b0 * (ax1 + ax0 * bx1) * (az1 + az0 * bz1);
      qreal dg011 = ax0 * b0 * (ay1 + ay0 * by1) * (az1 + az0 * bz1);

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
        m_cdg100(m) += m_coef(m, p) * dg100;
        m_cdg010(m) += m_coef(m, p) * dg010;
        m_cdg001(m) += m_coef(m, p) * dg001;
        m_cdg200(m) += m_coef(m, p) * dg200;
        m_cdg020(m) += m_coef(m, p) * dg020;
        m_cdg002(m) += m_coef(m, p) * dg002;
        m_cdg110(m) += m_coef(m, p) * dg110;
        m_cdg101(m) += m_coef(m, p) * dg101;
        m_cdg011(m) += m_coef(m, p) * dg011;
      }
    }
  }

  gValue.setZero();
  for (qint64 m = 0; m < m_nmo; ++m) {
    gValue(0) += m_occno(m) * m_cdg100(m) * m_cdg000(m);
    gValue(1) += m_occno(m) * m_cdg010(m) * m_cdg000(m);
    gValue(2) += m_occno(m) * m_cdg001(m) * m_cdg000(m);
  }

  hValue.setZero();
  for (qint64 m = 0; m < m_nmo; ++m) {
    hValue(0, 0) +=
      2 * m_occno(m) * (ipow(m_cdg100(m), 2) + m_cdg000(m) * m_cdg200(m));
    hValue(1, 1) +=
      2 * m_occno(m) * (ipow(m_cdg010(m), 2) + m_cdg000(m) * m_cdg020(m));
    hValue(2, 2) +=
      2 * m_occno(m) * (ipow(m_cdg001(m), 2) + m_cdg000(m) * m_cdg002(m));
    hValue(0, 1) +=
      2 * m_occno(m) * (m_cdg100(m) * m_cdg010(m) + m_cdg000(m) * m_cdg110(m));
    hValue(0, 2) +=
      2 * m_occno(m) * (m_cdg100(m) * m_cdg001(m) + m_cdg000(m) * m_cdg101(m));
    hValue(1, 2) +=
      2 * m_occno(m) * (m_cdg010(m) * m_cdg001(m) + m_cdg000(m) * m_cdg011(m));
  }
  hValue(1, 0) = hValue(0, 1);
  hValue(2, 0) = hValue(0, 2);
  hValue(2, 1) = hValue(1, 2);

  value(0, 0) = gValue(0);
  value(1, 0) = gValue(1);
  value(2, 0) = gValue(2);
  value(0, 1) = hValue(0, 0);
  value(1, 1) = hValue(1, 0);
  value(2, 1) = hValue(2, 0);
  value(0, 2) = hValue(0, 1);
  value(1, 2) = hValue(1, 1);
  value(2, 2) = hValue(2, 1);
  value(0, 3) = hValue(0, 2);
  value(1, 3) = hValue(1, 2);
  value(2, 3) = hValue(2, 2);

  return value;
}

qreal QTAIMWavefunctionEvaluator::laplacianOfElectronDensity(
  const Matrix<qreal, 3, 1> xyz)
{

  qreal value;

  const qreal zero = 0.0;
  const qreal one = 1.0;

  m_cdg000.setZero();
  m_cdg100.setZero();
  m_cdg010.setZero();
  m_cdg001.setZero();
  m_cdg200.setZero();
  m_cdg020.setZero();
  m_cdg002.setZero();
  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qint64 aax0 = 1;
      qint64 aay0 = 1;
      qint64 aaz0 = 1;
      qint64 aax1 = m_xamom(p);
      qint64 aay1 = m_yamom(p);
      qint64 aaz1 = m_zamom(p);
      qint64 aax2 = m_xamom(p) * (m_xamom(p) - 1);
      qint64 aay2 = m_yamom(p) * (m_yamom(p) - 1);
      qint64 aaz2 = m_zamom(p) * (m_zamom(p) - 1);

      qreal ax0 = aax0 * ipow(xx0, m_xamom(p));
      qreal ay0 = aay0 * ipow(yy0, m_yamom(p));
      qreal az0 = aaz0 * ipow(zz0, m_zamom(p));

      qreal ax1;
      qreal ay1;
      qreal az1;
      if (m_xamom(p) < 1) {
        ax1 = zero;
      } else if (m_xamom(p) == 1) {
        ax1 = one;
      } else {
        ax1 = aax1 * ipow(xx0, m_xamom(p) - 1);
      }

      if (m_yamom(p) < 1) {
        ay1 = zero;
      } else if (m_yamom(p) == 1) {
        ay1 = one;
      } else {
        ay1 = aay1 * ipow(yy0, m_yamom(p) - 1);
      }

      if (m_zamom(p) < 1) {
        az1 = zero;
      } else if (m_zamom(p) == 1) {
        az1 = one;
      } else {
        az1 = aaz1 * ipow(zz0, m_zamom(p) - 1);
      }

      qreal ax2;
      qreal ay2;
      qreal az2;
      if (m_xamom(p) < 2) {
        ax2 = zero;
      } else if (m_xamom(p) == 2) {
        ax2 = one;
      } else {
        ax2 = aax2 * ipow(xx0, m_xamom(p) - 2);
      }

      if (m_yamom(p) < 2) {
        ay2 = zero;
      } else if (m_yamom(p) == 2) {
        ay2 = one;
      } else {
        ay2 = aay2 * ipow(yy0, m_yamom(p) - 2);
      }

      if (m_zamom(p) < 2) {
        az2 = zero;
      } else if (m_zamom(p) == 2) {
        az2 = one;
      } else {
        az2 = aaz2 * ipow(zz0, m_zamom(p) - 2);
      }

      qreal b0 = exp(b0arg);

      qreal bx1 = -2 * m_alpha(p) * xx0;
      qreal by1 = -2 * m_alpha(p) * yy0;
      qreal bz1 = -2 * m_alpha(p) * zz0;
      qreal bx2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(xx0, 2));
      qreal by2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(yy0, 2));
      qreal bz2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(zz0, 2));

      qreal dg000 = ax0 * ay0 * az0 * b0;
      qreal dg100 = ay0 * az0 * b0 * (ax1 + ax0 * bx1);
      qreal dg010 = ax0 * az0 * b0 * (ay1 + ay0 * by1);
      qreal dg001 = ax0 * ay0 * b0 * (az1 + az0 * bz1);
      qreal dg200 = ay0 * az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2);
      qreal dg020 = ax0 * az0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg002 = ax0 * ay0 * b0 * (az2 + 2 * az1 * bz1 + az0 * bz2);

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
        m_cdg100(m) += m_coef(m, p) * dg100;
        m_cdg010(m) += m_coef(m, p) * dg010;
        m_cdg001(m) += m_coef(m, p) * dg001;
        m_cdg200(m) += m_coef(m, p) * dg200;
        m_cdg020(m) += m_coef(m, p) * dg020;
        m_cdg002(m) += m_coef(m, p) * dg002;
      }
    }
  }

  value = 0.0;
  for (qint64 m = 0; m < m_nmo; ++m) {
    value +=
      2 * m_occno(m) * (ipow(m_cdg100(m), 2) + m_cdg000(m) * m_cdg200(m)) +
      2 * m_occno(m) * (ipow(m_cdg010(m), 2) + m_cdg000(m) * m_cdg020(m)) +
      2 * m_occno(m) * (ipow(m_cdg001(m), 2) + m_cdg000(m) * m_cdg002(m));
  }

  return value;
}

const Matrix<qreal, 3, 1>
QTAIMWavefunctionEvaluator::gradientOfElectronDensityLaplacian(
  const Matrix<qreal, 3, 1> xyz)
{

  Matrix<qreal, 3, 1> value;

  const qreal zero = 0.0;
  const qreal one = 1.0;

  m_cdg000.setZero();
  m_cdg100.setZero();
  m_cdg010.setZero();
  m_cdg001.setZero();
  m_cdg200.setZero();
  m_cdg020.setZero();
  m_cdg002.setZero();
  m_cdg110.setZero();
  m_cdg101.setZero();
  m_cdg011.setZero();
  m_cdg300.setZero();
  m_cdg120.setZero();
  m_cdg102.setZero();
  m_cdg210.setZero();
  m_cdg030.setZero();
  m_cdg012.setZero();
  m_cdg201.setZero();
  m_cdg021.setZero();
  m_cdg003.setZero();
  // m_cdg111.setZero();
  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qint64 aax0 = 1;
      qint64 aay0 = 1;
      qint64 aaz0 = 1;
      qint64 aax1 = m_xamom(p);
      qint64 aay1 = m_yamom(p);
      qint64 aaz1 = m_zamom(p);
      qint64 aax2 = m_xamom(p) * (m_xamom(p) - 1);
      qint64 aay2 = m_yamom(p) * (m_yamom(p) - 1);
      qint64 aaz2 = m_zamom(p) * (m_zamom(p) - 1);
      qint64 aax3 = m_xamom(p) * (m_xamom(p) - 1) * (m_xamom(p) - 2);
      qint64 aay3 = m_yamom(p) * (m_yamom(p) - 1) * (m_yamom(p) - 2);
      qint64 aaz3 = m_zamom(p) * (m_zamom(p) - 1) * (m_zamom(p) - 2);

      qreal ax0 = aax0 * ipow(xx0, m_xamom(p));
      qreal ay0 = aay0 * ipow(yy0, m_yamom(p));
      qreal az0 = aaz0 * ipow(zz0, m_zamom(p));

      qreal ax1;
      qreal ay1;
      qreal az1;
      if (m_xamom(p) < 1) {
        ax1 = zero;
      } else if (m_xamom(p) == 1) {
        ax1 = one;
      } else {
        ax1 = aax1 * ipow(xx0, m_xamom(p) - 1);
      }

      if (m_yamom(p) < 1) {
        ay1 = zero;
      } else if (m_yamom(p) == 1) {
        ay1 = one;
      } else {
        ay1 = aay1 * ipow(yy0, m_yamom(p) - 1);
      }

      if (m_zamom(p) < 1) {
        az1 = zero;
      } else if (m_zamom(p) == 1) {
        az1 = one;
      } else {
        az1 = aaz1 * ipow(zz0, m_zamom(p) - 1);
      }

      qreal ax2;
      qreal ay2;
      qreal az2;
      if (m_xamom(p) < 2) {
        ax2 = zero;
      } else if (m_xamom(p) == 2) {
        ax2 = one;
      } else {
        ax2 = aax2 * ipow(xx0, m_xamom(p) - 2);
      }

      if (m_yamom(p) < 2) {
        ay2 = zero;
      } else if (m_yamom(p) == 2) {
        ay2 = one;
      } else {
        ay2 = aay2 * ipow(yy0, m_yamom(p) - 2);
      }

      if (m_zamom(p) < 2) {
        az2 = zero;
      } else if (m_zamom(p) == 2) {
        az2 = one;
      } else {
        az2 = aaz2 * ipow(zz0, m_zamom(p) - 2);
      }

      qreal ax3;
      qreal ay3;
      qreal az3;
      if (m_xamom(p) < 3) {
        ax3 = zero;
      } else if (m_xamom(p) == 3) {
        ax3 = one;
      } else {
        ax3 = aax3 * ipow(xx0, m_xamom(p) - 3);
      }

      if (m_yamom(p) < 3) {
        ay3 = zero;
      } else if (m_yamom(p) == 3) {
        ay3 = one;
      } else {
        ay3 = aay3 * ipow(yy0, m_yamom(p) - 3);
      }

      if (m_zamom(p) < 3) {
        az3 = zero;
      } else if (m_zamom(p) == 3) {
        az3 = one;
      } else {
        az3 = aaz3 * ipow(zz0, m_zamom(p) - 3);
      }

      qreal b0 = exp(b0arg);

      qreal bx1 = -2 * m_alpha(p) * xx0;
      qreal by1 = -2 * m_alpha(p) * yy0;
      qreal bz1 = -2 * m_alpha(p) * zz0;
      qreal bx2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(xx0, 2));
      qreal by2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(yy0, 2));
      qreal bz2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(zz0, 2));
      qreal bx3 = (12 * ipow(m_alpha(p), 2) * xx0) -
                  (8 * ipow(m_alpha(p), 3) * ipow(xx0, 3));
      qreal by3 = (12 * ipow(m_alpha(p), 2) * yy0) -
                  (8 * ipow(m_alpha(p), 3) * ipow(yy0, 3));
      qreal bz3 = (12 * ipow(m_alpha(p), 2) * zz0) -
                  (8 * ipow(m_alpha(p), 3) * ipow(zz0, 3));

      qreal dg000 = ax0 * ay0 * az0 * b0;
      qreal dg100 = ay0 * az0 * b0 * (ax1 + ax0 * bx1);
      qreal dg010 = ax0 * az0 * b0 * (ay1 + ay0 * by1);
      qreal dg001 = ax0 * ay0 * b0 * (az1 + az0 * bz1);
      qreal dg200 = ay0 * az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2);
      qreal dg020 = ax0 * az0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg002 = ax0 * ay0 * b0 * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg110 = az0 * b0 * (ax1 + ax0 * bx1) * (ay1 + ay0 * by1);
      qreal dg101 = ay0 * b0 * (ax1 + ax0 * bx1) * (az1 + az0 * bz1);
      qreal dg011 = ax0 * b0 * (ay1 + ay0 * by1) * (az1 + az0 * bz1);
      qreal dg300 =
        ay0 * az0 * b0 * (ax3 + 3 * ax2 * bx1 + 3 * ax1 * bx2 + ax0 * bx3);
      qreal dg030 =
        ax0 * az0 * b0 * (ay3 + 3 * ay2 * by1 + 3 * ay1 * by2 + ay0 * by3);
      qreal dg003 =
        ax0 * ay0 * b0 * (az3 + 3 * az2 * bz1 + 3 * az1 * bz2 + az0 * bz3);
      qreal dg210 =
        az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) * (ay1 + ay0 * by1);
      qreal dg201 =
        ay0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) * (az1 + az0 * bz1);
      qreal dg120 =
        az0 * b0 * (ax1 + ax0 * bx1) * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg021 =
        ax0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2) * (az1 + az0 * bz1);
      qreal dg102 =
        ay0 * b0 * (ax1 + ax0 * bx1) * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg012 =
        ax0 * b0 * (ay1 + ay0 * by1) * (az2 + 2 * az1 * bz1 + az0 * bz2);
      // qreal dg111 = b0*(ax1+ax0*bx1)*(ay1+ay0*by1)*(az1+az0*bz1);

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
        m_cdg100(m) += m_coef(m, p) * dg100;
        m_cdg010(m) += m_coef(m, p) * dg010;
        m_cdg001(m) += m_coef(m, p) * dg001;
        m_cdg200(m) += m_coef(m, p) * dg200;
        m_cdg020(m) += m_coef(m, p) * dg020;
        m_cdg002(m) += m_coef(m, p) * dg002;
        m_cdg110(m) += m_coef(m, p) * dg110;
        m_cdg101(m) += m_coef(m, p) * dg101;
        m_cdg011(m) += m_coef(m, p) * dg011;
        m_cdg300(m) += m_coef(m, p) * dg300;
        m_cdg030(m) += m_coef(m, p) * dg030;
        m_cdg003(m) += m_coef(m, p) * dg003;
        m_cdg210(m) += m_coef(m, p) * dg210;
        m_cdg201(m) += m_coef(m, p) * dg201;
        m_cdg120(m) += m_coef(m, p) * dg120;
        m_cdg021(m) += m_coef(m, p) * dg021;
        m_cdg102(m) += m_coef(m, p) * dg102;
        m_cdg012(m) += m_coef(m, p) * dg012;
        // m_cdg111(m) += m_coef(m,p) * dg111;
      }
    }
  }

  qreal deriv300 = zero;
  qreal deriv030 = zero;
  qreal deriv003 = zero;
  qreal deriv210 = zero;
  qreal deriv201 = zero;
  qreal deriv120 = zero;
  qreal deriv021 = zero;
  qreal deriv102 = zero;
  qreal deriv012 = zero;
  // qreal deriv111=zero;
  for (qint64 m = 0; m < m_nmo; ++m) {
    deriv300 += (m_occno(m) * (6 * m_cdg100(m) * m_cdg200(m) +
                               2 * m_cdg000(m) * m_cdg300(m)));
    deriv030 += (m_occno(m) * (6 * m_cdg010(m) * m_cdg020(m) +
                               2 * m_cdg000(m) * m_cdg030(m)));
    deriv003 += (m_occno(m) * (6 * m_cdg001(m) * m_cdg002(m) +
                               2 * m_cdg000(m) * m_cdg003(m)));
    deriv210 += (m_occno(m) *
                 (2 * (2 * m_cdg100(m) * m_cdg110(m) +
                       m_cdg010(m) * m_cdg200(m) + m_cdg000(m) * m_cdg210(m))));
    deriv201 += (m_occno(m) *
                 (2 * (2 * m_cdg100(m) * m_cdg101(m) +
                       m_cdg001(m) * m_cdg200(m) + m_cdg000(m) * m_cdg201(m))));
    deriv120 += (m_occno(m) * (2 * (m_cdg020(m) * m_cdg100(m) +
                                    2 * m_cdg010(m) * m_cdg110(m) +
                                    m_cdg000(m) * m_cdg120(m))));
    deriv021 += (m_occno(m) *
                 (2 * (2 * m_cdg010(m) * m_cdg011(m) +
                       m_cdg001(m) * m_cdg020(m) + m_cdg000(m) * m_cdg021(m))));
    deriv102 += (m_occno(m) * (2 * (m_cdg002(m) * m_cdg100(m) +
                                    2 * m_cdg001(m) * m_cdg101(m) +
                                    m_cdg000(m) * m_cdg102(m))));
    deriv012 += (m_occno(m) * (2 * (m_cdg002(m) * m_cdg010(m) +
                                    2 * m_cdg001(m) * m_cdg011(m) +
                                    m_cdg000(m) * m_cdg012(m))));
    // deriv111+=(m_occno(m)*(
    // 2*(m_cdg011(m)*m_cdg100(m)+m_cdg010(m)*m_cdg101(m)+m_cdg001(m)*m_cdg110(m)+m_cdg000(m)*m_cdg111(m))
    // ));
  }

  value(0) = deriv300 + deriv120 + deriv102;
  value(1) = deriv210 + deriv030 + deriv012;
  value(2) = deriv201 + deriv021 + deriv003;

  return value;
}

const Matrix<qreal, 3, 3>
QTAIMWavefunctionEvaluator::hessianOfElectronDensityLaplacian(
  const Matrix<qreal, 3, 1> xyz)
{

  Matrix<qreal, 3, 3> value;

  const qreal zero = 0.0;
  const qreal one = 1.0;

  m_cdg000.setZero();
  m_cdg100.setZero();
  m_cdg010.setZero();
  m_cdg001.setZero();
  m_cdg200.setZero();
  m_cdg020.setZero();
  m_cdg002.setZero();
  m_cdg110.setZero();
  m_cdg101.setZero();
  m_cdg011.setZero();
  m_cdg300.setZero();
  m_cdg120.setZero();
  m_cdg102.setZero();
  m_cdg210.setZero();
  m_cdg030.setZero();
  m_cdg012.setZero();
  m_cdg201.setZero();
  m_cdg021.setZero();
  m_cdg003.setZero();
  m_cdg111.setZero();
  m_cdg400.setZero();
  m_cdg040.setZero();
  m_cdg004.setZero();
  m_cdg310.setZero();
  m_cdg301.setZero();
  m_cdg130.setZero();
  m_cdg031.setZero();
  m_cdg103.setZero();
  m_cdg013.setZero();
  m_cdg220.setZero();
  m_cdg202.setZero();
  m_cdg022.setZero();
  m_cdg211.setZero();
  m_cdg121.setZero();
  m_cdg112.setZero();

  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qint64 aax0 = 1;
      qint64 aay0 = 1;
      qint64 aaz0 = 1;
      qint64 aax1 = m_xamom(p);
      qint64 aay1 = m_yamom(p);
      qint64 aaz1 = m_zamom(p);
      qint64 aax2 = m_xamom(p) * (m_xamom(p) - 1);
      qint64 aay2 = m_yamom(p) * (m_yamom(p) - 1);
      qint64 aaz2 = m_zamom(p) * (m_zamom(p) - 1);
      qint64 aax3 = m_xamom(p) * (m_xamom(p) - 1) * (m_xamom(p) - 2);
      qint64 aay3 = m_yamom(p) * (m_yamom(p) - 1) * (m_yamom(p) - 2);
      qint64 aaz3 = m_zamom(p) * (m_zamom(p) - 1) * (m_zamom(p) - 2);
      qint64 aax4 =
        m_xamom(p) * (m_xamom(p) - 1) * (m_xamom(p) - 2) * (m_xamom(p) - 3);
      qint64 aay4 =
        m_yamom(p) * (m_yamom(p) - 1) * (m_yamom(p) - 2) * (m_xamom(p) - 3);
      qint64 aaz4 =
        m_zamom(p) * (m_zamom(p) - 1) * (m_zamom(p) - 2) * (m_xamom(p) - 3);

      qreal ax0 = aax0 * ipow(xx0, m_xamom(p));
      qreal ay0 = aay0 * ipow(yy0, m_yamom(p));
      qreal az0 = aaz0 * ipow(zz0, m_zamom(p));

      qreal ax1;
      qreal ay1;
      qreal az1;
      if (m_xamom(p) < 1) {
        ax1 = zero;
      } else if (m_xamom(p) == 1) {
        ax1 = one;
      } else {
        ax1 = aax1 * ipow(xx0, m_xamom(p) - 1);
      }

      if (m_yamom(p) < 1) {
        ay1 = zero;
      } else if (m_yamom(p) == 1) {
        ay1 = one;
      } else {
        ay1 = aay1 * ipow(yy0, m_yamom(p) - 1);
      }

      if (m_zamom(p) < 1) {
        az1 = zero;
      } else if (m_zamom(p) == 1) {
        az1 = one;
      } else {
        az1 = aaz1 * ipow(zz0, m_zamom(p) - 1);
      }

      qreal ax2;
      qreal ay2;
      qreal az2;
      if (m_xamom(p) < 2) {
        ax2 = zero;
      } else if (m_xamom(p) == 2) {
        ax2 = one;
      } else {
        ax2 = aax2 * ipow(xx0, m_xamom(p) - 2);
      }

      if (m_yamom(p) < 2) {
        ay2 = zero;
      } else if (m_yamom(p) == 2) {
        ay2 = one;
      } else {
        ay2 = aay2 * ipow(yy0, m_yamom(p) - 2);
      }

      if (m_zamom(p) < 2) {
        az2 = zero;
      } else if (m_zamom(p) == 2) {
        az2 = one;
      } else {
        az2 = aaz2 * ipow(zz0, m_zamom(p) - 2);
      }

      qreal ax3;
      qreal ay3;
      qreal az3;
      if (m_xamom(p) < 3) {
        ax3 = zero;
      } else if (m_xamom(p) == 3) {
        ax3 = one;
      } else {
        ax3 = aax3 * ipow(xx0, m_xamom(p) - 3);
      }

      if (m_yamom(p) < 3) {
        ay3 = zero;
      } else if (m_yamom(p) == 3) {
        ay3 = one;
      } else {
        ay3 = aay3 * ipow(yy0, m_yamom(p) - 3);
      }

      if (m_zamom(p) < 3) {
        az3 = zero;
      } else if (m_zamom(p) == 3) {
        az3 = one;
      } else {
        az3 = aaz3 * ipow(zz0, m_zamom(p) - 3);
      }

      qreal ax4;
      qreal ay4;
      qreal az4;
      if (m_xamom(p) < 4) {
        ax4 = zero;
      } else if (m_xamom(p) == 4) {
        ax4 = one;
      } else {
        ax4 = aax4 * ipow(xx0, m_xamom(p) - 4);
      }

      if (m_yamom(p) < 4) {
        ay4 = zero;
      } else if (m_yamom(p) == 4) {
        ay4 = one;
      } else {
        ay4 = aay4 * ipow(yy0, m_yamom(p) - 4);
      }

      if (m_zamom(p) < 4) {
        az4 = zero;
      } else if (m_zamom(p) == 4) {
        az4 = one;
      } else {
        az4 = aaz4 * ipow(zz0, m_zamom(p) - 4);
      }

      qreal b0 = exp(b0arg);

      qreal bx1 = -2 * m_alpha(p) * xx0;
      qreal by1 = -2 * m_alpha(p) * yy0;
      qreal bz1 = -2 * m_alpha(p) * zz0;
      qreal bx2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(xx0, 2));
      qreal by2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(yy0, 2));
      qreal bz2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(zz0, 2));
      qreal bx3 = (12 * ipow(m_alpha(p), 2) * xx0) -
                  (8 * ipow(m_alpha(p), 3) * ipow(xx0, 3));
      qreal by3 = (12 * ipow(m_alpha(p), 2) * yy0) -
                  (8 * ipow(m_alpha(p), 3) * ipow(yy0, 3));
      qreal bz3 = (12 * ipow(m_alpha(p), 2) * zz0) -
                  (8 * ipow(m_alpha(p), 3) * ipow(zz0, 3));
      qreal bx4 = (12 * ipow(m_alpha(p), 2)) -
                  (48 * ipow(m_alpha(p), 3) * ipow(xx0, 2)) +
                  (16 * ipow(m_alpha(p), 4) * ipow(xx0, 4));
      qreal by4 = (12 * ipow(m_alpha(p), 2)) -
                  (48 * ipow(m_alpha(p), 3) * ipow(yy0, 2)) +
                  (16 * ipow(m_alpha(p), 4) * ipow(yy0, 4));
      qreal bz4 = (12 * ipow(m_alpha(p), 2)) -
                  (48 * ipow(m_alpha(p), 3) * ipow(zz0, 2)) +
                  (16 * ipow(m_alpha(p), 4) * ipow(zz0, 4));

      qreal dg000 = ax0 * ay0 * az0 * b0;
      qreal dg100 = ay0 * az0 * b0 * (ax1 + ax0 * bx1);
      qreal dg010 = ax0 * az0 * b0 * (ay1 + ay0 * by1);
      qreal dg001 = ax0 * ay0 * b0 * (az1 + az0 * bz1);
      qreal dg200 = ay0 * az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2);
      qreal dg020 = ax0 * az0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg002 = ax0 * ay0 * b0 * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg110 = az0 * b0 * (ax1 + ax0 * bx1) * (ay1 + ay0 * by1);
      qreal dg101 = ay0 * b0 * (ax1 + ax0 * bx1) * (az1 + az0 * bz1);
      qreal dg011 = ax0 * b0 * (ay1 + ay0 * by1) * (az1 + az0 * bz1);
      qreal dg300 =
        ay0 * az0 * b0 * (ax3 + 3 * ax2 * bx1 + 3 * ax1 * bx2 + ax0 * bx3);
      qreal dg030 =
        ax0 * az0 * b0 * (ay3 + 3 * ay2 * by1 + 3 * ay1 * by2 + ay0 * by3);
      qreal dg003 =
        ax0 * ay0 * b0 * (az3 + 3 * az2 * bz1 + 3 * az1 * bz2 + az0 * bz3);
      qreal dg210 =
        az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) * (ay1 + ay0 * by1);
      qreal dg201 =
        ay0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) * (az1 + az0 * bz1);
      qreal dg120 =
        az0 * b0 * (ax1 + ax0 * bx1) * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg021 =
        ax0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2) * (az1 + az0 * bz1);
      qreal dg102 =
        ay0 * b0 * (ax1 + ax0 * bx1) * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg012 =
        ax0 * b0 * (ay1 + ay0 * by1) * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg111 =
        b0 * (ax1 + ax0 * bx1) * (ay1 + ay0 * by1) * (az1 + az0 * bz1);
      qreal dg400 = ay0 * az0 * b0 * (ax4 + 4 * ax3 * bx1 + 6 * ax2 * bx2 +
                                      4 * ax1 * bx3 + ax0 * bx4);
      qreal dg040 = ax0 * az0 * b0 * (ay4 + 4 * ay3 * by1 + 6 * ay2 * by2 +
                                      4 * ay1 * by3 + ay0 * by4);
      qreal dg004 = ax0 * ay0 * b0 * (az4 + 4 * az3 * bz1 + 6 * az2 * bz2 +
                                      4 * az1 * bz3 + az0 * bz4);
      qreal dg310 = az0 * b0 *
                    (ax3 + 3 * ax2 * bx1 + 3 * ax1 * bx2 + ax0 * bx3) *
                    (ay1 + ay0 * by1);
      qreal dg301 = ay0 * b0 *
                    (ax3 + 3 * ax2 * bx1 + 3 * ax1 * bx2 + ax0 * bx3) *
                    (az1 + az0 * bz1);
      qreal dg130 = az0 * b0 * (ax1 + ax0 * bx1) *
                    (ay3 + 3 * ay2 * by1 + 3 * ay1 * by2 + ay0 * by3);
      qreal dg031 = ax0 * b0 *
                    (ay3 + 3 * ay2 * by1 + 3 * ay1 * by2 + ay0 * by3) *
                    (az1 + az0 * bz1);
      qreal dg103 = ay0 * b0 * (ax1 + ax0 * bx1) *
                    (az3 + 3 * az2 * bz1 + 3 * az1 * bz2 + az0 * bz3);
      qreal dg013 = ax0 * b0 * (ay1 + ay0 * by1) *
                    (az3 + 3 * az2 * bz1 + 3 * az1 * bz2 + az0 * bz3);
      qreal dg220 = az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) *
                    (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg202 = ay0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) *
                    (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg022 = ax0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2) *
                    (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg211 = b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) * (ay1 + ay0 * by1) *
                    (az1 + az0 * bz1);
      qreal dg121 = b0 * (ax1 + ax0 * bx1) * (ay2 + 2 * ay1 * by1 + ay0 * by2) *
                    (az1 + az0 * bz1);
      qreal dg112 = b0 * (ax1 + ax0 * bx1) * (ay1 + ay0 * by1) *
                    (az2 + 2 * az1 * bz1 + az0 * bz2);

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
        m_cdg100(m) += m_coef(m, p) * dg100;
        m_cdg010(m) += m_coef(m, p) * dg010;
        m_cdg001(m) += m_coef(m, p) * dg001;
        m_cdg200(m) += m_coef(m, p) * dg200;
        m_cdg020(m) += m_coef(m, p) * dg020;
        m_cdg002(m) += m_coef(m, p) * dg002;
        m_cdg110(m) += m_coef(m, p) * dg110;
        m_cdg101(m) += m_coef(m, p) * dg101;
        m_cdg011(m) += m_coef(m, p) * dg011;
        m_cdg300(m) += m_coef(m, p) * dg300;
        m_cdg030(m) += m_coef(m, p) * dg030;
        m_cdg003(m) += m_coef(m, p) * dg003;
        m_cdg210(m) += m_coef(m, p) * dg210;
        m_cdg201(m) += m_coef(m, p) * dg201;
        m_cdg120(m) += m_coef(m, p) * dg120;
        m_cdg021(m) += m_coef(m, p) * dg021;
        m_cdg102(m) += m_coef(m, p) * dg102;
        m_cdg012(m) += m_coef(m, p) * dg012;
        m_cdg111(m) += m_coef(m, p) * dg111;
        m_cdg400(m) += m_coef(m, p) * dg400;
        m_cdg040(m) += m_coef(m, p) * dg040;
        m_cdg004(m) += m_coef(m, p) * dg004;
        m_cdg310(m) += m_coef(m, p) * dg310;
        m_cdg301(m) += m_coef(m, p) * dg301;
        m_cdg130(m) += m_coef(m, p) * dg130;
        m_cdg031(m) += m_coef(m, p) * dg031;
        m_cdg103(m) += m_coef(m, p) * dg103;
        m_cdg013(m) += m_coef(m, p) * dg013;
        m_cdg220(m) += m_coef(m, p) * dg220;
        m_cdg202(m) += m_coef(m, p) * dg202;
        m_cdg022(m) += m_coef(m, p) * dg022;
        m_cdg211(m) += m_coef(m, p) * dg211;
        m_cdg121(m) += m_coef(m, p) * dg121;
        m_cdg112(m) += m_coef(m, p) * dg112;
      }
    }
  }

  qreal deriv400 = zero;
  qreal deriv040 = zero;
  qreal deriv004 = zero;
  qreal deriv310 = zero;
  qreal deriv301 = zero;
  qreal deriv130 = zero;
  qreal deriv031 = zero;
  qreal deriv103 = zero;
  qreal deriv013 = zero;
  qreal deriv220 = zero;
  qreal deriv202 = zero;
  qreal deriv022 = zero;
  qreal deriv211 = zero;
  qreal deriv121 = zero;
  qreal deriv112 = zero;
  for (qint64 m = 0; m < m_nmo; ++m) {
    deriv400 +=
      (m_occno(m) * (6 * ipow(m_cdg200(m), 2) + 8 * m_cdg100(m) * m_cdg300(m) +
                     2 * m_cdg000(m) * m_cdg400(m)));
    deriv040 +=
      (m_occno(m) * (6 * ipow(m_cdg020(m), 2) + 8 * m_cdg010(m) * m_cdg030(m) +
                     2 * m_cdg000(m) * m_cdg040(m)));
    deriv004 +=
      (m_occno(m) * (6 * ipow(m_cdg002(m), 2) + 8 * m_cdg001(m) * m_cdg003(m) +
                     2 * m_cdg000(m) * m_cdg004(m)));
    deriv310 +=
      (m_occno(m) *
       (2 * (3 * m_cdg110(m) * m_cdg200(m) + 3 * m_cdg100(m) * m_cdg210(m) +
             m_cdg010(m) * m_cdg300(m) + m_cdg000(m) * m_cdg310(m))));
    deriv301 +=
      (m_occno(m) *
       (2 * (3 * m_cdg101(m) * m_cdg200(m) + 3 * m_cdg100(m) * m_cdg201(m) +
             m_cdg001(m) * m_cdg300(m) + m_cdg000(m) * m_cdg301(m))));
    deriv130 +=
      (m_occno(m) *
       (2 * (m_cdg030(m) * m_cdg100(m) + 3 * m_cdg020(m) * m_cdg110(m) +
             3 * m_cdg010(m) * m_cdg120(m) + m_cdg000(m) * m_cdg130(m))));
    deriv031 +=
      (m_occno(m) *
       (2 * (3 * m_cdg011(m) * m_cdg020(m) + 3 * m_cdg010(m) * m_cdg021(m) +
             m_cdg001(m) * m_cdg030(m) + m_cdg000(m) * m_cdg031(m))));
    deriv103 +=
      (m_occno(m) *
       (2 * (m_cdg003(m) * m_cdg100(m) + 3 * m_cdg002(m) * m_cdg101(m) +
             3 * m_cdg001(m) * m_cdg102(m) + m_cdg000(m) * m_cdg103(m))));
    deriv013 +=
      (m_occno(m) *
       (2 * (m_cdg003(m) * m_cdg010(m) + 3 * m_cdg002(m) * m_cdg011(m) +
             3 * m_cdg001(m) * m_cdg012(m) + m_cdg000(m) * m_cdg013(m))));
    deriv220 +=
      (m_occno(m) *
       (2 * (2 * ipow(m_cdg110(m), 2) + 2 * m_cdg100(m) * m_cdg120(m) +
             m_cdg020(m) * m_cdg200(m) + 2 * m_cdg010(m) * m_cdg210(m) +
             m_cdg000(m) * m_cdg220(m))));
    deriv202 +=
      (m_occno(m) *
       (2 * (2 * ipow(m_cdg101(m), 2) + 2 * m_cdg100(m) * m_cdg102(m) +
             m_cdg002(m) * m_cdg200(m) + 2 * m_cdg001(m) * m_cdg201(m) +
             m_cdg000(m) * m_cdg202(m))));
    deriv022 +=
      (m_occno(m) *
       (2 * (2 * ipow(m_cdg011(m), 2) + 2 * m_cdg010(m) * m_cdg012(m) +
             m_cdg002(m) * m_cdg020(m) + 2 * m_cdg001(m) * m_cdg021(m) +
             m_cdg000(m) * m_cdg022(m))));
    deriv211 +=
      (m_occno(m) *
       (2 * (2 * m_cdg101(m) * m_cdg110(m) + 2 * m_cdg100(m) * m_cdg111(m) +
             m_cdg011(m) * m_cdg200(m) + m_cdg010(m) * m_cdg201(m) +
             m_cdg001(m) * m_cdg210(m) + m_cdg000(m) * m_cdg211(m))));
    deriv121 +=
      (m_occno(m) *
       (2 * (m_cdg021(m) * m_cdg100(m) + m_cdg020(m) * m_cdg101(m) +
             2 * m_cdg011(m) * m_cdg110(m) + 2 * m_cdg010(m) * m_cdg111(m) +
             m_cdg001(m) * m_cdg120(m) + m_cdg000(m) * m_cdg121(m))));
    deriv112 +=
      (m_occno(m) *
       (2 * (m_cdg012(m) * m_cdg100(m) + 2 * m_cdg011(m) * m_cdg101(m) +
             m_cdg010(m) * m_cdg102(m) + m_cdg002(m) * m_cdg110(m) +
             2 * m_cdg001(m) * m_cdg111(m) + m_cdg000(m) * m_cdg112(m))));
  }

  value(0, 0) = deriv400 + deriv220 + deriv202;
  value(1, 1) = deriv220 + deriv040 + deriv022;
  value(2, 2) = deriv202 + deriv022 + deriv004;
  value(0, 1) = deriv310 + deriv130 + deriv112;
  value(0, 2) = deriv301 + deriv121 + deriv103;
  value(1, 2) = deriv211 + deriv031 + deriv013;
  value(1, 0) = value(0, 1);
  value(2, 0) = value(0, 2);
  value(2, 1) = value(1, 2);

  return value;
}

const Matrix<qreal, 3, 4>
QTAIMWavefunctionEvaluator::gradientAndHessianOfElectronDensityLaplacian(
  const Matrix<qreal, 3, 1> xyz)
{

  Matrix<qreal, 3, 1> gValue;
  Matrix<qreal, 3, 3> hValue;
  Matrix<qreal, 3, 4> value;

  const qreal zero = 0.0;
  const qreal one = 1.0;

  m_cdg000.setZero();
  m_cdg100.setZero();
  m_cdg010.setZero();
  m_cdg001.setZero();
  m_cdg200.setZero();
  m_cdg020.setZero();
  m_cdg002.setZero();
  m_cdg110.setZero();
  m_cdg101.setZero();
  m_cdg011.setZero();
  m_cdg300.setZero();
  m_cdg120.setZero();
  m_cdg102.setZero();
  m_cdg210.setZero();
  m_cdg030.setZero();
  m_cdg012.setZero();
  m_cdg201.setZero();
  m_cdg021.setZero();
  m_cdg003.setZero();
  m_cdg111.setZero();
  m_cdg400.setZero();
  m_cdg040.setZero();
  m_cdg004.setZero();
  m_cdg310.setZero();
  m_cdg301.setZero();
  m_cdg130.setZero();
  m_cdg031.setZero();
  m_cdg103.setZero();
  m_cdg013.setZero();
  m_cdg220.setZero();
  m_cdg202.setZero();
  m_cdg022.setZero();
  m_cdg211.setZero();
  m_cdg121.setZero();
  m_cdg112.setZero();

  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qint64 aax0 = 1;
      qint64 aay0 = 1;
      qint64 aaz0 = 1;
      qint64 aax1 = m_xamom(p);
      qint64 aay1 = m_yamom(p);
      qint64 aaz1 = m_zamom(p);
      qint64 aax2 = m_xamom(p) * (m_xamom(p) - 1);
      qint64 aay2 = m_yamom(p) * (m_yamom(p) - 1);
      qint64 aaz2 = m_zamom(p) * (m_zamom(p) - 1);
      qint64 aax3 = m_xamom(p) * (m_xamom(p) - 1) * (m_xamom(p) - 2);
      qint64 aay3 = m_yamom(p) * (m_yamom(p) - 1) * (m_yamom(p) - 2);
      qint64 aaz3 = m_zamom(p) * (m_zamom(p) - 1) * (m_zamom(p) - 2);
      qint64 aax4 =
        m_xamom(p) * (m_xamom(p) - 1) * (m_xamom(p) - 2) * (m_xamom(p) - 3);
      qint64 aay4 =
        m_yamom(p) * (m_yamom(p) - 1) * (m_yamom(p) - 2) * (m_xamom(p) - 3);
      qint64 aaz4 =
        m_zamom(p) * (m_zamom(p) - 1) * (m_zamom(p) - 2) * (m_xamom(p) - 3);

      qreal ax0 = aax0 * ipow(xx0, m_xamom(p));
      qreal ay0 = aay0 * ipow(yy0, m_yamom(p));
      qreal az0 = aaz0 * ipow(zz0, m_zamom(p));

      qreal ax1;
      qreal ay1;
      qreal az1;
      if (m_xamom(p) < 1) {
        ax1 = zero;
      } else if (m_xamom(p) == 1) {
        ax1 = one;
      } else {
        ax1 = aax1 * ipow(xx0, m_xamom(p) - 1);
      }

      if (m_yamom(p) < 1) {
        ay1 = zero;
      } else if (m_yamom(p) == 1) {
        ay1 = one;
      } else {
        ay1 = aay1 * ipow(yy0, m_yamom(p) - 1);
      }

      if (m_zamom(p) < 1) {
        az1 = zero;
      } else if (m_zamom(p) == 1) {
        az1 = one;
      } else {
        az1 = aaz1 * ipow(zz0, m_zamom(p) - 1);
      }

      qreal ax2;
      qreal ay2;
      qreal az2;
      if (m_xamom(p) < 2) {
        ax2 = zero;
      } else if (m_xamom(p) == 2) {
        ax2 = one;
      } else {
        ax2 = aax2 * ipow(xx0, m_xamom(p) - 2);
      }

      if (m_yamom(p) < 2) {
        ay2 = zero;
      } else if (m_yamom(p) == 2) {
        ay2 = one;
      } else {
        ay2 = aay2 * ipow(yy0, m_yamom(p) - 2);
      }

      if (m_zamom(p) < 2) {
        az2 = zero;
      } else if (m_zamom(p) == 2) {
        az2 = one;
      } else {
        az2 = aaz2 * ipow(zz0, m_zamom(p) - 2);
      }

      qreal ax3;
      qreal ay3;
      qreal az3;
      if (m_xamom(p) < 3) {
        ax3 = zero;
      } else if (m_xamom(p) == 3) {
        ax3 = one;
      } else {
        ax3 = aax3 * ipow(xx0, m_xamom(p) - 3);
      }

      if (m_yamom(p) < 3) {
        ay3 = zero;
      } else if (m_yamom(p) == 3) {
        ay3 = one;
      } else {
        ay3 = aay3 * ipow(yy0, m_yamom(p) - 3);
      }

      if (m_zamom(p) < 3) {
        az3 = zero;
      } else if (m_zamom(p) == 3) {
        az3 = one;
      } else {
        az3 = aaz3 * ipow(zz0, m_zamom(p) - 3);
      }

      qreal ax4;
      qreal ay4;
      qreal az4;
      if (m_xamom(p) < 4) {
        ax4 = zero;
      } else if (m_xamom(p) == 4) {
        ax4 = one;
      } else {
        ax4 = aax4 * ipow(xx0, m_xamom(p) - 4);
      }

      if (m_yamom(p) < 4) {
        ay4 = zero;
      } else if (m_yamom(p) == 4) {
        ay4 = one;
      } else {
        ay4 = aay4 * ipow(yy0, m_yamom(p) - 4);
      }

      if (m_zamom(p) < 4) {
        az4 = zero;
      } else if (m_zamom(p) == 4) {
        az4 = one;
      } else {
        az4 = aaz4 * ipow(zz0, m_zamom(p) - 4);
      }

      qreal b0 = exp(b0arg);

      qreal bx1 = -2 * m_alpha(p) * xx0;
      qreal by1 = -2 * m_alpha(p) * yy0;
      qreal bz1 = -2 * m_alpha(p) * zz0;
      qreal bx2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(xx0, 2));
      qreal by2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(yy0, 2));
      qreal bz2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(zz0, 2));
      qreal bx3 = (12 * ipow(m_alpha(p), 2) * xx0) -
                  (8 * ipow(m_alpha(p), 3) * ipow(xx0, 3));
      qreal by3 = (12 * ipow(m_alpha(p), 2) * yy0) -
                  (8 * ipow(m_alpha(p), 3) * ipow(yy0, 3));
      qreal bz3 = (12 * ipow(m_alpha(p), 2) * zz0) -
                  (8 * ipow(m_alpha(p), 3) * ipow(zz0, 3));
      qreal bx4 = (12 * ipow(m_alpha(p), 2)) -
                  (48 * ipow(m_alpha(p), 3) * ipow(xx0, 2)) +
                  (16 * ipow(m_alpha(p), 4) * ipow(xx0, 4));
      qreal by4 = (12 * ipow(m_alpha(p), 2)) -
                  (48 * ipow(m_alpha(p), 3) * ipow(yy0, 2)) +
                  (16 * ipow(m_alpha(p), 4) * ipow(yy0, 4));
      qreal bz4 = (12 * ipow(m_alpha(p), 2)) -
                  (48 * ipow(m_alpha(p), 3) * ipow(zz0, 2)) +
                  (16 * ipow(m_alpha(p), 4) * ipow(zz0, 4));

      qreal dg000 = ax0 * ay0 * az0 * b0;
      qreal dg100 = ay0 * az0 * b0 * (ax1 + ax0 * bx1);
      qreal dg010 = ax0 * az0 * b0 * (ay1 + ay0 * by1);
      qreal dg001 = ax0 * ay0 * b0 * (az1 + az0 * bz1);
      qreal dg200 = ay0 * az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2);
      qreal dg020 = ax0 * az0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg002 = ax0 * ay0 * b0 * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg110 = az0 * b0 * (ax1 + ax0 * bx1) * (ay1 + ay0 * by1);
      qreal dg101 = ay0 * b0 * (ax1 + ax0 * bx1) * (az1 + az0 * bz1);
      qreal dg011 = ax0 * b0 * (ay1 + ay0 * by1) * (az1 + az0 * bz1);
      qreal dg300 =
        ay0 * az0 * b0 * (ax3 + 3 * ax2 * bx1 + 3 * ax1 * bx2 + ax0 * bx3);
      qreal dg030 =
        ax0 * az0 * b0 * (ay3 + 3 * ay2 * by1 + 3 * ay1 * by2 + ay0 * by3);
      qreal dg003 =
        ax0 * ay0 * b0 * (az3 + 3 * az2 * bz1 + 3 * az1 * bz2 + az0 * bz3);
      qreal dg210 =
        az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) * (ay1 + ay0 * by1);
      qreal dg201 =
        ay0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) * (az1 + az0 * bz1);
      qreal dg120 =
        az0 * b0 * (ax1 + ax0 * bx1) * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg021 =
        ax0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2) * (az1 + az0 * bz1);
      qreal dg102 =
        ay0 * b0 * (ax1 + ax0 * bx1) * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg012 =
        ax0 * b0 * (ay1 + ay0 * by1) * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg111 =
        b0 * (ax1 + ax0 * bx1) * (ay1 + ay0 * by1) * (az1 + az0 * bz1);
      qreal dg400 = ay0 * az0 * b0 * (ax4 + 4 * ax3 * bx1 + 6 * ax2 * bx2 +
                                      4 * ax1 * bx3 + ax0 * bx4);
      qreal dg040 = ax0 * az0 * b0 * (ay4 + 4 * ay3 * by1 + 6 * ay2 * by2 +
                                      4 * ay1 * by3 + ay0 * by4);
      qreal dg004 = ax0 * ay0 * b0 * (az4 + 4 * az3 * bz1 + 6 * az2 * bz2 +
                                      4 * az1 * bz3 + az0 * bz4);
      qreal dg310 = az0 * b0 *
                    (ax3 + 3 * ax2 * bx1 + 3 * ax1 * bx2 + ax0 * bx3) *
                    (ay1 + ay0 * by1);
      qreal dg301 = ay0 * b0 *
                    (ax3 + 3 * ax2 * bx1 + 3 * ax1 * bx2 + ax0 * bx3) *
                    (az1 + az0 * bz1);
      qreal dg130 = az0 * b0 * (ax1 + ax0 * bx1) *
                    (ay3 + 3 * ay2 * by1 + 3 * ay1 * by2 + ay0 * by3);
      qreal dg031 = ax0 * b0 *
                    (ay3 + 3 * ay2 * by1 + 3 * ay1 * by2 + ay0 * by3) *
                    (az1 + az0 * bz1);
      qreal dg103 = ay0 * b0 * (ax1 + ax0 * bx1) *
                    (az3 + 3 * az2 * bz1 + 3 * az1 * bz2 + az0 * bz3);
      qreal dg013 = ax0 * b0 * (ay1 + ay0 * by1) *
                    (az3 + 3 * az2 * bz1 + 3 * az1 * bz2 + az0 * bz3);
      qreal dg220 = az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) *
                    (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg202 = ay0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) *
                    (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg022 = ax0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2) *
                    (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg211 = b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2) * (ay1 + ay0 * by1) *
                    (az1 + az0 * bz1);
      qreal dg121 = b0 * (ax1 + ax0 * bx1) * (ay2 + 2 * ay1 * by1 + ay0 * by2) *
                    (az1 + az0 * bz1);
      qreal dg112 = b0 * (ax1 + ax0 * bx1) * (ay1 + ay0 * by1) *
                    (az2 + 2 * az1 * bz1 + az0 * bz2);

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
        m_cdg100(m) += m_coef(m, p) * dg100;
        m_cdg010(m) += m_coef(m, p) * dg010;
        m_cdg001(m) += m_coef(m, p) * dg001;
        m_cdg200(m) += m_coef(m, p) * dg200;
        m_cdg020(m) += m_coef(m, p) * dg020;
        m_cdg002(m) += m_coef(m, p) * dg002;
        m_cdg110(m) += m_coef(m, p) * dg110;
        m_cdg101(m) += m_coef(m, p) * dg101;
        m_cdg011(m) += m_coef(m, p) * dg011;
        m_cdg300(m) += m_coef(m, p) * dg300;
        m_cdg030(m) += m_coef(m, p) * dg030;
        m_cdg003(m) += m_coef(m, p) * dg003;
        m_cdg210(m) += m_coef(m, p) * dg210;
        m_cdg201(m) += m_coef(m, p) * dg201;
        m_cdg120(m) += m_coef(m, p) * dg120;
        m_cdg021(m) += m_coef(m, p) * dg021;
        m_cdg102(m) += m_coef(m, p) * dg102;
        m_cdg012(m) += m_coef(m, p) * dg012;
        m_cdg111(m) += m_coef(m, p) * dg111;
        m_cdg400(m) += m_coef(m, p) * dg400;
        m_cdg040(m) += m_coef(m, p) * dg040;
        m_cdg004(m) += m_coef(m, p) * dg004;
        m_cdg310(m) += m_coef(m, p) * dg310;
        m_cdg301(m) += m_coef(m, p) * dg301;
        m_cdg130(m) += m_coef(m, p) * dg130;
        m_cdg031(m) += m_coef(m, p) * dg031;
        m_cdg103(m) += m_coef(m, p) * dg103;
        m_cdg013(m) += m_coef(m, p) * dg013;
        m_cdg220(m) += m_coef(m, p) * dg220;
        m_cdg202(m) += m_coef(m, p) * dg202;
        m_cdg022(m) += m_coef(m, p) * dg022;
        m_cdg211(m) += m_coef(m, p) * dg211;
        m_cdg121(m) += m_coef(m, p) * dg121;
        m_cdg112(m) += m_coef(m, p) * dg112;
      }
    }
  }

  qreal deriv300 = zero;
  qreal deriv030 = zero;
  qreal deriv003 = zero;
  qreal deriv210 = zero;
  qreal deriv201 = zero;
  qreal deriv120 = zero;
  qreal deriv021 = zero;
  qreal deriv102 = zero;
  qreal deriv012 = zero;
  qreal deriv400 = zero;
  qreal deriv040 = zero;
  qreal deriv004 = zero;
  qreal deriv310 = zero;
  qreal deriv301 = zero;
  qreal deriv130 = zero;
  qreal deriv031 = zero;
  qreal deriv103 = zero;
  qreal deriv013 = zero;
  qreal deriv220 = zero;
  qreal deriv202 = zero;
  qreal deriv022 = zero;
  qreal deriv211 = zero;
  qreal deriv121 = zero;
  qreal deriv112 = zero;
  for (qint64 m = 0; m < m_nmo; ++m) {
    deriv300 += (m_occno(m) * (6 * m_cdg100(m) * m_cdg200(m) +
                               2 * m_cdg000(m) * m_cdg300(m)));
    deriv030 += (m_occno(m) * (6 * m_cdg010(m) * m_cdg020(m) +
                               2 * m_cdg000(m) * m_cdg030(m)));
    deriv003 += (m_occno(m) * (6 * m_cdg001(m) * m_cdg002(m) +
                               2 * m_cdg000(m) * m_cdg003(m)));
    deriv210 += (m_occno(m) *
                 (2 * (2 * m_cdg100(m) * m_cdg110(m) +
                       m_cdg010(m) * m_cdg200(m) + m_cdg000(m) * m_cdg210(m))));
    deriv201 += (m_occno(m) *
                 (2 * (2 * m_cdg100(m) * m_cdg101(m) +
                       m_cdg001(m) * m_cdg200(m) + m_cdg000(m) * m_cdg201(m))));
    deriv120 += (m_occno(m) * (2 * (m_cdg020(m) * m_cdg100(m) +
                                    2 * m_cdg010(m) * m_cdg110(m) +
                                    m_cdg000(m) * m_cdg120(m))));
    deriv021 += (m_occno(m) *
                 (2 * (2 * m_cdg010(m) * m_cdg011(m) +
                       m_cdg001(m) * m_cdg020(m) + m_cdg000(m) * m_cdg021(m))));
    deriv102 += (m_occno(m) * (2 * (m_cdg002(m) * m_cdg100(m) +
                                    2 * m_cdg001(m) * m_cdg101(m) +
                                    m_cdg000(m) * m_cdg102(m))));
    deriv012 += (m_occno(m) * (2 * (m_cdg002(m) * m_cdg010(m) +
                                    2 * m_cdg001(m) * m_cdg011(m) +
                                    m_cdg000(m) * m_cdg012(m))));
    // deriv111+=(m_occno(m)*(
    // 2*(m_cdg011(m)*m_cdg100(m)+m_cdg010(m)*m_cdg101(m)+m_cdg001(m)*m_cdg110(m)+m_cdg000(m)*m_cdg111(m))
    // ));
    deriv400 +=
      (m_occno(m) * (6 * ipow(m_cdg200(m), 2) + 8 * m_cdg100(m) * m_cdg300(m) +
                     2 * m_cdg000(m) * m_cdg400(m)));
    deriv040 +=
      (m_occno(m) * (6 * ipow(m_cdg020(m), 2) + 8 * m_cdg010(m) * m_cdg030(m) +
                     2 * m_cdg000(m) * m_cdg040(m)));
    deriv004 +=
      (m_occno(m) * (6 * ipow(m_cdg002(m), 2) + 8 * m_cdg001(m) * m_cdg003(m) +
                     2 * m_cdg000(m) * m_cdg004(m)));
    deriv310 +=
      (m_occno(m) *
       (2 * (3 * m_cdg110(m) * m_cdg200(m) + 3 * m_cdg100(m) * m_cdg210(m) +
             m_cdg010(m) * m_cdg300(m) + m_cdg000(m) * m_cdg310(m))));
    deriv301 +=
      (m_occno(m) *
       (2 * (3 * m_cdg101(m) * m_cdg200(m) + 3 * m_cdg100(m) * m_cdg201(m) +
             m_cdg001(m) * m_cdg300(m) + m_cdg000(m) * m_cdg301(m))));
    deriv130 +=
      (m_occno(m) *
       (2 * (m_cdg030(m) * m_cdg100(m) + 3 * m_cdg020(m) * m_cdg110(m) +
             3 * m_cdg010(m) * m_cdg120(m) + m_cdg000(m) * m_cdg130(m))));
    deriv031 +=
      (m_occno(m) *
       (2 * (3 * m_cdg011(m) * m_cdg020(m) + 3 * m_cdg010(m) * m_cdg021(m) +
             m_cdg001(m) * m_cdg030(m) + m_cdg000(m) * m_cdg031(m))));
    deriv103 +=
      (m_occno(m) *
       (2 * (m_cdg003(m) * m_cdg100(m) + 3 * m_cdg002(m) * m_cdg101(m) +
             3 * m_cdg001(m) * m_cdg102(m) + m_cdg000(m) * m_cdg103(m))));
    deriv013 +=
      (m_occno(m) *
       (2 * (m_cdg003(m) * m_cdg010(m) + 3 * m_cdg002(m) * m_cdg011(m) +
             3 * m_cdg001(m) * m_cdg012(m) + m_cdg000(m) * m_cdg013(m))));
    deriv220 +=
      (m_occno(m) *
       (2 * (2 * ipow(m_cdg110(m), 2) + 2 * m_cdg100(m) * m_cdg120(m) +
             m_cdg020(m) * m_cdg200(m) + 2 * m_cdg010(m) * m_cdg210(m) +
             m_cdg000(m) * m_cdg220(m))));
    deriv202 +=
      (m_occno(m) *
       (2 * (2 * ipow(m_cdg101(m), 2) + 2 * m_cdg100(m) * m_cdg102(m) +
             m_cdg002(m) * m_cdg200(m) + 2 * m_cdg001(m) * m_cdg201(m) +
             m_cdg000(m) * m_cdg202(m))));
    deriv022 +=
      (m_occno(m) *
       (2 * (2 * ipow(m_cdg011(m), 2) + 2 * m_cdg010(m) * m_cdg012(m) +
             m_cdg002(m) * m_cdg020(m) + 2 * m_cdg001(m) * m_cdg021(m) +
             m_cdg000(m) * m_cdg022(m))));
    deriv211 +=
      (m_occno(m) *
       (2 * (2 * m_cdg101(m) * m_cdg110(m) + 2 * m_cdg100(m) * m_cdg111(m) +
             m_cdg011(m) * m_cdg200(m) + m_cdg010(m) * m_cdg201(m) +
             m_cdg001(m) * m_cdg210(m) + m_cdg000(m) * m_cdg211(m))));
    deriv121 +=
      (m_occno(m) *
       (2 * (m_cdg021(m) * m_cdg100(m) + m_cdg020(m) * m_cdg101(m) +
             2 * m_cdg011(m) * m_cdg110(m) + 2 * m_cdg010(m) * m_cdg111(m) +
             m_cdg001(m) * m_cdg120(m) + m_cdg000(m) * m_cdg121(m))));
    deriv112 +=
      (m_occno(m) *
       (2 * (m_cdg012(m) * m_cdg100(m) + 2 * m_cdg011(m) * m_cdg101(m) +
             m_cdg010(m) * m_cdg102(m) + m_cdg002(m) * m_cdg110(m) +
             2 * m_cdg001(m) * m_cdg111(m) + m_cdg000(m) * m_cdg112(m))));
  }

  gValue(0) = deriv300 + deriv120 + deriv102;
  gValue(1) = deriv210 + deriv030 + deriv012;
  gValue(2) = deriv201 + deriv021 + deriv003;

  hValue(0, 0) = deriv400 + deriv220 + deriv202;
  hValue(1, 1) = deriv220 + deriv040 + deriv022;
  hValue(2, 2) = deriv202 + deriv022 + deriv004;
  hValue(0, 1) = deriv310 + deriv130 + deriv112;
  hValue(0, 2) = deriv301 + deriv121 + deriv103;
  hValue(1, 2) = deriv211 + deriv031 + deriv013;
  hValue(1, 0) = hValue(0, 1);
  hValue(2, 0) = hValue(0, 2);
  hValue(2, 1) = hValue(1, 2);

  value(0, 0) = gValue(0);
  value(1, 0) = gValue(1);
  value(2, 0) = gValue(2);
  value(0, 1) = hValue(0, 0);
  value(1, 1) = hValue(1, 0);
  value(2, 1) = hValue(2, 0);
  value(0, 2) = hValue(0, 1);
  value(1, 2) = hValue(1, 1);
  value(2, 2) = hValue(2, 1);
  value(0, 3) = hValue(0, 2);
  value(1, 3) = hValue(1, 2);
  value(2, 3) = hValue(2, 2);

  return value;
}

qreal QTAIMWavefunctionEvaluator::kineticEnergyDensityG(Matrix<qreal, 3, 1> xyz)
{

  qreal value;

  const qreal zero = 0.0;
  const qreal one = 1.0;

  m_cdg000.setZero();
  m_cdg100.setZero();
  m_cdg010.setZero();
  m_cdg001.setZero();
  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qint64 aax0 = 1;
      qint64 aay0 = 1;
      qint64 aaz0 = 1;
      qint64 aax1 = m_xamom(p);
      qint64 aay1 = m_yamom(p);
      qint64 aaz1 = m_zamom(p);

      qreal ax0 = aax0 * ipow(xx0, m_xamom(p));
      qreal ay0 = aay0 * ipow(yy0, m_yamom(p));
      qreal az0 = aaz0 * ipow(zz0, m_zamom(p));

      qreal ax1;
      qreal ay1;
      qreal az1;
      if (m_xamom(p) < 1) {
        ax1 = zero;
      } else if (m_xamom(p) == 1) {
        ax1 = one;
      } else {
        ax1 = aax1 * ipow(xx0, m_xamom(p) - 1);
      }

      if (m_yamom(p) < 1) {
        ay1 = zero;
      } else if (m_yamom(p) == 1) {
        ay1 = one;
      } else {
        ay1 = aay1 * ipow(yy0, m_yamom(p) - 1);
      }

      if (m_zamom(p) < 1) {
        az1 = zero;
      } else if (m_zamom(p) == 1) {
        az1 = one;
      } else {
        az1 = aaz1 * ipow(zz0, m_zamom(p) - 1);
      }

      qreal b0 = exp(b0arg);

      qreal bx1 = -2 * m_alpha(p) * xx0;
      qreal by1 = -2 * m_alpha(p) * yy0;
      qreal bz1 = -2 * m_alpha(p) * zz0;

      qreal dg000 = ax0 * ay0 * az0 * b0;
      qreal dg100 = ay0 * az0 * b0 * (ax1 + ax0 * bx1);
      qreal dg010 = ax0 * az0 * b0 * (ay1 + ay0 * by1);
      qreal dg001 = ax0 * ay0 * b0 * (az1 + az0 * bz1);

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
        m_cdg100(m) += m_coef(m, p) * dg100;
        m_cdg010(m) += m_coef(m, p) * dg010;
        m_cdg001(m) += m_coef(m, p) * dg001;
      }
    }
  }

  value = zero;
  for (qint64 m = 0; m < m_nmo; ++m) {
    value +=
      (0.5) * (m_occno(m) * (ipow(m_cdg100(m), 2) + ipow(m_cdg010(m), 2) +
                             ipow(m_cdg001(m), 2)));
  }

  return value;
}

qreal QTAIMWavefunctionEvaluator::kineticEnergyDensityK(
  const Matrix<qreal, 3, 1> xyz)
{

  qreal value;

  const qreal zero = 0.0;
  const qreal one = 1.0;

  m_cdg000.setZero();
  m_cdg200.setZero();
  m_cdg020.setZero();
  m_cdg002.setZero();
  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qint64 aax0 = 1;
      qint64 aay0 = 1;
      qint64 aaz0 = 1;
      qint64 aax1 = m_xamom(p);
      qint64 aay1 = m_yamom(p);
      qint64 aaz1 = m_zamom(p);
      qint64 aax2 = m_xamom(p) * (m_xamom(p) - 1);
      qint64 aay2 = m_yamom(p) * (m_yamom(p) - 1);
      qint64 aaz2 = m_zamom(p) * (m_zamom(p) - 1);

      qreal ax0 = aax0 * ipow(xx0, m_xamom(p));
      qreal ay0 = aay0 * ipow(yy0, m_yamom(p));
      qreal az0 = aaz0 * ipow(zz0, m_zamom(p));

      qreal ax1;
      qreal ay1;
      qreal az1;
      if (m_xamom(p) < 1) {
        ax1 = zero;
      } else if (m_xamom(p) == 1) {
        ax1 = one;
      } else {
        ax1 = aax1 * ipow(xx0, m_xamom(p) - 1);
      }

      if (m_yamom(p) < 1) {
        ay1 = zero;
      } else if (m_yamom(p) == 1) {
        ay1 = one;
      } else {
        ay1 = aay1 * ipow(yy0, m_yamom(p) - 1);
      }

      if (m_zamom(p) < 1) {
        az1 = zero;
      } else if (m_zamom(p) == 1) {
        az1 = one;
      } else {
        az1 = aaz1 * ipow(zz0, m_zamom(p) - 1);
      }

      qreal ax2;
      qreal ay2;
      qreal az2;
      if (m_xamom(p) < 2) {
        ax2 = zero;
      } else if (m_xamom(p) == 2) {
        ax2 = one;
      } else {
        ax2 = aax2 * ipow(xx0, m_xamom(p) - 2);
      }

      if (m_yamom(p) < 2) {
        ay2 = zero;
      } else if (m_yamom(p) == 2) {
        ay2 = one;
      } else {
        ay2 = aay2 * ipow(yy0, m_yamom(p) - 2);
      }

      if (m_zamom(p) < 2) {
        az2 = zero;
      } else if (m_zamom(p) == 2) {
        az2 = one;
      } else {
        az2 = aaz2 * ipow(zz0, m_zamom(p) - 2);
      }

      qreal b0 = exp(b0arg);

      qreal bx1 = -2 * m_alpha(p) * xx0;
      qreal by1 = -2 * m_alpha(p) * yy0;
      qreal bz1 = -2 * m_alpha(p) * zz0;
      qreal bx2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(xx0, 2));
      qreal by2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(yy0, 2));
      qreal bz2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(zz0, 2));

      qreal dg000 = ax0 * ay0 * az0 * b0;
      qreal dg200 = ay0 * az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2);
      qreal dg020 = ax0 * az0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg002 = ax0 * ay0 * b0 * (az2 + 2 * az1 * bz1 + az0 * bz2);

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
        m_cdg200(m) += m_coef(m, p) * dg200;
        m_cdg020(m) += m_coef(m, p) * dg020;
        m_cdg002(m) += m_coef(m, p) * dg002;
      }
    }
  }

  value = 0.0;
  for (qint64 m = 0; m < m_nmo; ++m) {
    value +=
      (0.25) * (m_occno(m) *
                (2 * m_cdg000(m) * (m_cdg200(m) + m_cdg020(m) + m_cdg002(m))));
  }

  return value;
}

const Matrix<qreal, 3, 3> QTAIMWavefunctionEvaluator::quantumStressTensor(
  const Matrix<qreal, 3, 1> xyz)
{

  Matrix<qreal, 3, 3> value;

  const qreal zero = 0.0;
  const qreal one = 1.0;

  m_cdg000.setZero();
  m_cdg100.setZero();
  m_cdg010.setZero();
  m_cdg001.setZero();
  m_cdg200.setZero();
  m_cdg020.setZero();
  m_cdg002.setZero();
  m_cdg110.setZero();
  m_cdg101.setZero();
  m_cdg011.setZero();
  for (qint64 p = 0; p < m_nprim; ++p) {
    qreal xx0 = xyz(0) - m_X0(p);
    qreal yy0 = xyz(1) - m_Y0(p);
    qreal zz0 = xyz(2) - m_Z0(p);

    qreal b0arg = -m_alpha(p) * (xx0 * xx0 + yy0 * yy0 + zz0 * zz0);

    if (b0arg > m_cutoff) {
      qint64 aax0 = 1;
      qint64 aay0 = 1;
      qint64 aaz0 = 1;
      qint64 aax1 = m_xamom(p);
      qint64 aay1 = m_yamom(p);
      qint64 aaz1 = m_zamom(p);
      qint64 aax2 = m_xamom(p) * (m_xamom(p) - 1);
      qint64 aay2 = m_yamom(p) * (m_yamom(p) - 1);
      qint64 aaz2 = m_zamom(p) * (m_zamom(p) - 1);

      qreal ax0 = aax0 * ipow(xx0, m_xamom(p));
      qreal ay0 = aay0 * ipow(yy0, m_yamom(p));
      qreal az0 = aaz0 * ipow(zz0, m_zamom(p));

      qreal ax1;
      qreal ay1;
      qreal az1;
      if (m_xamom(p) < 1) {
        ax1 = zero;
      } else if (m_xamom(p) == 1) {
        ax1 = one;
      } else {
        ax1 = aax1 * ipow(xx0, m_xamom(p) - 1);
      }

      if (m_yamom(p) < 1) {
        ay1 = zero;
      } else if (m_yamom(p) == 1) {
        ay1 = one;
      } else {
        ay1 = aay1 * ipow(yy0, m_yamom(p) - 1);
      }

      if (m_zamom(p) < 1) {
        az1 = zero;
      } else if (m_zamom(p) == 1) {
        az1 = one;
      } else {
        az1 = aaz1 * ipow(zz0, m_zamom(p) - 1);
      }

      qreal ax2;
      qreal ay2;
      qreal az2;
      if (m_xamom(p) < 2) {
        ax2 = zero;
      } else if (m_xamom(p) == 2) {
        ax2 = one;
      } else {
        ax2 = aax2 * ipow(xx0, m_xamom(p) - 2);
      }

      if (m_yamom(p) < 2) {
        ay2 = zero;
      } else if (m_yamom(p) == 2) {
        ay2 = one;
      } else {
        ay2 = aay2 * ipow(yy0, m_yamom(p) - 2);
      }

      if (m_zamom(p) < 2) {
        az2 = zero;
      } else if (m_zamom(p) == 2) {
        az2 = one;
      } else {
        az2 = aaz2 * ipow(zz0, m_zamom(p) - 2);
      }

      qreal b0 = exp(b0arg);

      qreal bx1 = -2 * m_alpha(p) * xx0;
      qreal by1 = -2 * m_alpha(p) * yy0;
      qreal bz1 = -2 * m_alpha(p) * zz0;
      qreal bx2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(xx0, 2));
      qreal by2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(yy0, 2));
      qreal bz2 = -2 * m_alpha(p) + 4 * (ipow(m_alpha(p), 2) * ipow(zz0, 2));

      qreal dg000 = ax0 * ay0 * az0 * b0;
      qreal dg100 = ay0 * az0 * b0 * (ax1 + ax0 * bx1);
      qreal dg010 = ax0 * az0 * b0 * (ay1 + ay0 * by1);
      qreal dg001 = ax0 * ay0 * b0 * (az1 + az0 * bz1);
      qreal dg200 = ay0 * az0 * b0 * (ax2 + 2 * ax1 * bx1 + ax0 * bx2);
      qreal dg020 = ax0 * az0 * b0 * (ay2 + 2 * ay1 * by1 + ay0 * by2);
      qreal dg002 = ax0 * ay0 * b0 * (az2 + 2 * az1 * bz1 + az0 * bz2);
      qreal dg110 = az0 * b0 * (ax1 + ax0 * bx1) * (ay1 + ay0 * by1);
      qreal dg101 = ay0 * b0 * (ax1 + ax0 * bx1) * (az1 + az0 * bz1);
      qreal dg011 = ax0 * b0 * (ay1 + ay0 * by1) * (az1 + az0 * bz1);

      for (qint64 m = 0; m < m_nmo; ++m) {
        m_cdg000(m) += m_coef(m, p) * dg000;
        m_cdg100(m) += m_coef(m, p) * dg100;
        m_cdg010(m) += m_coef(m, p) * dg010;
        m_cdg001(m) += m_coef(m, p) * dg001;
        m_cdg200(m) += m_coef(m, p) * dg200;
        m_cdg020(m) += m_coef(m, p) * dg020;
        m_cdg002(m) += m_coef(m, p) * dg002;
        m_cdg110(m) += m_coef(m, p) * dg110;
        m_cdg101(m) += m_coef(m, p) * dg101;
        m_cdg011(m) += m_coef(m, p) * dg011;
      }
    }
  }

  value.setZero();
  for (qint64 m = 0; m < m_nmo; ++m) {
    value(0, 0) +=
      (m_occno(m) * (2 * m_cdg000(m) * m_cdg200(m) - 2 * ipow(m_cdg100(m), 2)));
    value(0, 1) += (m_occno(m) * (2 * m_cdg000(m) * m_cdg110(m) -
                                  2 * m_cdg100(m) * m_cdg010(m)));
    value(0, 2) += (m_occno(m) * (2 * m_cdg000(m) * m_cdg101(m) -
                                  2 * m_cdg100(m) * m_cdg001(m)));
    value(1, 1) +=
      (m_occno(m) * (2 * m_cdg000(m) * m_cdg020(m) - 2 * ipow(m_cdg010(m), 2)));
    value(1, 2) += (m_occno(m) * (2 * m_cdg000(m) * m_cdg011(m) -
                                  2 * m_cdg010(m) * m_cdg001(m)));
    value(2, 2) +=
      (m_occno(m) * (2 * m_cdg000(m) * m_cdg002(m) - 2 * ipow(m_cdg001(m), 2)));
  }
  value(1, 0) = value(0, 1);
  value(2, 0) = value(0, 2);
  value(2, 1) = value(1, 2);

  return 0.25 * value;
}

} // namespace QtPlugins
} // namespace Avogadro
