1 /* Copyright (c) 2015  Gerald Knizia
2  *
3  * This file is part of the IboView program (see: http://www.iboview.org)
4  *
5  * IboView is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, version 3.
8  *
9  * IboView is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with bfint (LICENSE). If not, see http://www.gnu.org/licenses/
16  *
17  * Please see IboView documentation in README.txt for:
18  * -- A list of included external software and their licenses. The included
19  *    external software's copyright is not touched by this agreement.
20  * -- Notes on re-distribution and contributions to/further development of
21  *    the IboView software
22  */
23 
24 #include "Iv.h"
25 
26 #include <iostream>
27 #include <math.h>
28 
29 #include <boost/format.hpp>
30 
31 #include "IvMesh.h"
32 #include "IvIsoSurface.h"
33 #include "IvDocument.h" // for accessing orbitals.
34 
35 #include "CtMatrix.h"
36 #include "CtTiming.h"
37 #include "CtDftGrid_ivb.h" // FIXME: merge back with main version once done.
38 
39 #include "CxAlgebra.h" // for diagonalizing matrices.
40 #include "CxVec3.h"
41 #include "CxPodArray.h"
42 #include "CtConstants.h"
43 
44 using boost::format;
45 using boost::str;
46 
47 namespace ctIsoSurf {
48    // defines a not-necessarily-axis-aligned cartesian grid.
49    struct FCartesianGridInfo
50    {
51       FVec3d
52          vRef,
53          dXyz[3];
54       unsigned
55          nPts[3];
vPtctIsoSurf::FCartesianGridInfo56       FVec3d vPt(unsigned iX, unsigned iY, unsigned iZ) const {
57          assert(iX < nPts[0] && iY < nPts[1] && iZ < nPts[2]);
58          FVec3d
59             v(vRef[0] + iX * dXyz[0][0] + iY * dXyz[1][0] + iZ * dXyz[2][0],
60             vRef[1] + iX * dXyz[0][1] + iY * dXyz[1][1] + iZ * dXyz[2][1],
61             vRef[2] + iX * dXyz[0][2] + iY * dXyz[1][2] + iZ * dXyz[2][2]);
62          return v;
63       }
vPtctIsoSurf::FCartesianGridInfo64       FVec3d vPt(double iX, double iY, double iZ) const {
65          FVec3d
66             v(vRef[0] + iX * dXyz[0][0] + iY * dXyz[1][0] + iZ * dXyz[2][0],
67             vRef[1] + iX * dXyz[0][1] + iY * dXyz[1][1] + iZ * dXyz[2][1],
68             vRef[2] + iX * dXyz[0][2] + iY * dXyz[1][2] + iZ * dXyz[2][2]);
69          return v;
70       }
71    };
72 
73    typedef void (*FEvalFn)(double *pXyLayer, FCartesianGridInfo const *pGridInfo, unsigned iz, void *pEvanFnArgs_);
74    FIndexedTriangleListPtr TraceIsoSurface(FCartesianGridInfo const &g, FEvalFn f, void *pEvanFnArgs_, double fIsoValue, FBaseVertex const &RefVertex);
75 }
76 
77 
78 
79 // bfint/CtMain.cpp
80 namespace ct {
81    void MakeGridValues(double *pOut, FMatrixView Grid, FMatrixView Orb, uint GridDxOrder, FBasisSet const *pBasis, FMemoryStack &Mem);
82 }
83 
84 
85 using namespace ct;
86 
87 struct FGridDataRange;
88 struct FOrbEvalData;
89 using ctIsoSurf::FCartesianGridInfo;
90 
91 // grid and orbital values for a subrange of a cartesian grid.
92 // idea is that we can either calculate it inline (low memory)
93 // or make it beforehand to share the data for multiple iso values.
94 struct FGridDataRange : public FIntrusivePtrDest
95 {
FGridDataRangeFGridDataRange96    FGridDataRange() {}
97    FGridDataRange(uint nx, uint ny, uint iz0_, uint izN_, FOrbEvalData *pData);
98    ~FGridDataRange();
99    uint
100       iz0, izN;
101 
102    FMatrixView
103       Values;
104    double
105       *pOrbValues; // nx * ny * (izN-iz0) array.
106 private:
107    FGridDataRange(FGridDataRange const &); // not implemented.
108    void operator = (FGridDataRange const &); // not implemented.
109 };
110 typedef boost::intrusive_ptr<FGridDataRange>
111    FGridDataRangePtr;
112 
113 struct FOrbEvalData
114 {
115    FMemoryStack
116       *pMem;
117    FBasisSet
118       *pBasisSet;
119    FMatrixView
120       Orbs;
121    FCartesianGridInfo
122       *pCaGrid;
123    FGridDataRangePtr
124       pGridData;
125 };
126 
127 
128 
FGridDataRange(uint nx,uint ny,uint iz0_,uint izN_,FOrbEvalData * pData)129 FGridDataRange::FGridDataRange(uint nx, uint ny, uint iz0_, uint izN_, FOrbEvalData *pData)
130    : iz0(iz0_), izN(izN_)
131 {
132    uint N = nx * ny * (izN-iz0);
133    pOrbValues = (double*)::malloc(N * sizeof(pOrbValues[0]));
134    Values = FMatrixView(pOrbValues, nx * ny, (izN-iz0));
135 
136    // make the orbital values in slices. one for each z.
137    for (uint iz = iz0; iz != izN; ++ iz) {
138       // store the x/y/z coordinates of the grid points
139       FStackMatrix
140          Grid(3, nx * ny, pData->pMem);
141       for (unsigned ix = 0; ix < nx; ++ ix)
142          for (unsigned iy = 0; iy < ny; ++ iy) {
143             unsigned ixy = ix + nx * iy;
144             FVec3d
145                v = pData->pCaGrid->vPt(ix,iy,iz);
146             Grid(0, ixy) = v[0];
147             Grid(1, ixy) = v[1];
148             Grid(2, ixy) = v[2];
149          }
150       // evaluate orbitals on the grid points for the current iz.
151       assert(pData->Orbs.nCols == 1);
152       MakeGridValues(&Values(0,iz-iz0), Grid, pData->Orbs, 0, pData->pBasisSet, *pData->pMem);
153    }
154 }
155 
~FGridDataRange()156 FGridDataRange::~FGridDataRange()
157 {
158    ::free(pOrbValues);
159    pOrbValues = 0;
160 };
161 
162 
163 
EvalOrbital2(double * pVxy,FCartesianGridInfo const * pGridInfo,unsigned iz,void * pData_)164 static void EvalOrbital2(double *pVxy, FCartesianGridInfo const *pGridInfo, unsigned iz, void *pData_)
165 {
166    FOrbEvalData
167       *pData = reinterpret_cast<FOrbEvalData*>(pData_);
168    unsigned
169       nx = pGridInfo->nPts[0],
170       ny = pGridInfo->nPts[1];
171 
172    FGridDataRangePtr
173       pGridData = pData->pGridData;
174    if (pGridData.get() == 0)
175       // no precalculated data. make data for current slice.
176       pGridData = FGridDataRangePtr(new FGridDataRange(nx, ny, iz, iz+1, pData));
177 
178    // copy to target location.
179    for (unsigned ix = 0; ix < nx; ++ ix)
180       for (unsigned iy = 0; iy < ny; ++ iy)
181          pVxy[ix + nx*iy] = pGridData->Values(ix + nx * iy, iz - pGridData->iz0);
182 }
183 
184 
185 
186 struct FOrbitalBox
187 {
188    FVec3d vCenter;
189    FVec3d vAxes[3];
190    // nPts[i] points evenly distributed from
191    //      fMinExtend[i]*vAxes[i] ... fMaxExtend[i]*vAxes[i]
192    double fMinExtend[3];
193    double fMaxExtend[3];
194    unsigned nPts[3];
195    double fLinearDensity;
196 
197    void FixAxisDensity();
198    void PrintInfo(std::string const &OrbitalDesc);
199 
nPtsTotalFOrbitalBox200    size_t nPtsTotal() const { return nPts[0] * nPts[1] * nPts[2]; }
201 };
202 
203 // find number of points for each axis and adjust fMinExtend/fMaxExtend
204 // in such away that fLinearDensity is achieved exactly.
FixAxisDensity()205 void FOrbitalBox::FixAxisDensity()
206 {
207    // decide on number of points in each direction, and fix up extends
208    // in such a way that they meet the linear density requirements exactly.
209    for (unsigned i = 0; i < 3; ++ i) {
210       // ^- why do we have matrix classes if we cannot access them as vector rows?
211 //       double fLengthOrig = 2 * length(Box.vAxes[i]); // 2*:   -vAxes[i] ... +vAxes[i]
212       double fLengthOrig = fMaxExtend[i] - fMinExtend[i];
213       nPts[i] = 1 + (unsigned)(fLengthOrig / fLinearDensity);
214       if (nPts[i] < 8)
215          nPts[i] = 8;
216       double f = nPts[i] * fLinearDensity / fLengthOrig;
217       fMaxExtend[i] *= f;
218       fMinExtend[i] *= f;
219    }
220 }
221 
PrintInfo(std::string const &)222 void FOrbitalBox::PrintInfo(std::string const &/*OrbitalDesc*/)
223 {
224    int w = 8, p = 3;
225    IvEmit(" Box:  vCen = (%1,%2,%3)  nPts = (%4,%5,%6)",
226       fmtf(vCenter[0],w,p), fmtf(vCenter[1],w,p), fmtf(vCenter[2],w,p),
227       fmti(nPts[0],4), fmti(nPts[1],4), fmti(nPts[2],4));
228    IvEmit("       vExt = (%1,%2,%3)  -> %4 points total",
229       fmtf(fMaxExtend[0]-fMinExtend[0],w,p), fmtf(fMaxExtend[1]-fMinExtend[1],w,p), fmtf(fMaxExtend[2]-fMinExtend[2],w,p),
230       nPtsTotal());
231 //    std::cout << format(" Box:  vCen = (%8.3f,%8.3f,%8.3f)  nPts = (%4i,%4i,%4i)")
232 //       % vCenter[0] % vCenter[1] % vCenter[2]
233 //       % nPts[0] % nPts[1] % nPts[2]
234 //       << std::endl;
235 //    std::cout << format("       vExt = (%8.3f,%8.3f,%8.3f)  -> %i points total")
236 //       % (fMaxExtend[0]-fMinExtend[0]) % (fMaxExtend[1]-fMinExtend[1]) % (fMaxExtend[2]-fMinExtend[2])
237 //       % (nPts[0] * nPts[1] * nPts[2])
238 //       << std::endl;
239 }
240 
241 
MakeOrbitalBox(FOrbitalBox & Box,FOrbital const & Orb,double fLinearDensity,FMemoryStack & Mem)242 static void MakeOrbitalBox(FOrbitalBox &Box, FOrbital const &Orb, double fLinearDensity, FMemoryStack &Mem)
243 {
244    Box.fLinearDensity = fLinearDensity;
245 
246    FStackMatrix
247       QuadMom(3, 3, &Mem);
248    if (Orb.HaveMoments) {
249       for (uint i = 0; i < 3; ++ i) {
250          Box.vCenter[i] = Orb.vDipMom[i];
251          for (uint j = 0; j < 3; ++ j)
252             QuadMom(i,j) = Orb.mQuadMom[i][j];
253       }
254    } else {
255       // calculate center.
256       unsigned
257          nAo = Orb.pBasisSet->nFn();
258       for (unsigned i = 0; i < 3; ++ i) {
259          FStackMatrix
260             Value(1, 1, &Mem),
261             DipN(nAo, nAo, &Mem);
262          FMatrixView const
263             OrbMatrix(const_cast<double*>(&Orb.pCoeffs[0]), nAo, 1);
264 //          OrbMatrix.Print(std::cout, "CURRENT ORB MATRIX.");
265          TVector3<unsigned> CartMom(0, 0, 0);
266          TVector3<double> ExpansionPoint(0., 0., 0.);
267          CartMom[i] = 1;
268          Orb.pAtoms->MakeCartesianMomentMatrix(DipN, *Orb.pBasisSet, *Orb.pBasisSet, CartMom, ExpansionPoint, Mem);
269          ChainMxm(Value, Transpose(OrbMatrix), DipN, OrbMatrix, Mem);
270          Box.vCenter[i] = Value(0,0);
271       }
272       for (unsigned i = 0; i < 3; ++ i) {
273          for (unsigned j = 0; j <= i; ++ j) {
274             // expand quadrupole ops around center of orbital.
275             FStackMatrix
276                Value(1, 1, &Mem),
277                DipN(nAo, nAo, &Mem);
278             FMatrixView const
279                OrbMatrix(const_cast<double*>(&Orb.pCoeffs[0]), nAo, 1);
280             TVector3<unsigned> CartMom(0, 0, 0);
281             TVector3<double> ExpansionPoint(Box.vCenter[0], Box.vCenter[1], Box.vCenter[2]);
282             CartMom[i] += 1;
283             CartMom[j] += 1;
284             Orb.pAtoms->MakeCartesianMomentMatrix(DipN, *Orb.pBasisSet, *Orb.pBasisSet, CartMom, ExpansionPoint, Mem);
285             ChainMxm(Value, Transpose(OrbMatrix), DipN, OrbMatrix, Mem);
286             QuadMom(i,j) = Value(0,0);
287             QuadMom(j,i) = Value(0,0);
288 //             std::cout << boost::format("    Orbital %s:  Center-QM(%i,%i) = %12.5f\n") % Orb.GetDesc().toStdString() % i % j % Value(0,0);
289          }
290       }
291    }
292 
293    // the correct thing to do would be to diagonalize the quadrupole matrix
294    // to obtain a view-independent alignment of the iso-surface. For a start I
295    // just obtain the extends in xx, yy, zz directions, and use them for an axis-aligned box.
296 //    Box.vAxes = mat3d(0.,0.,0., 0.,0.,0., 0.,0.,0.);
297    memset(&Box.vAxes[0][0], 0, sizeof(Box.vAxes));
298    double fEnlargeRms = 4.; // 2 should be good for 95% coverage.
299    if (0) {
300       for (unsigned i = 0; i < 3; ++ i)
301          Box.vAxes[i][i] = fEnlargeRms * std::sqrt(QuadMom(i,i));
302    } else {
303       double
304          Ew[3];
305 //       Scale(QuadMom, -1.); // want large EW first.
306       Diagonalize(&Ew[0], QuadMom.pData, 3, 3);
307 //       for (uint i = 0; i < 3; ++ i)
308 //          Ew[i] *= -1.;
309       for (unsigned i = 0; i < 3; ++ i) {
310          Box.fMaxExtend[i] = fEnlargeRms * std::sqrt(Ew[i]);
311          Box.fMinExtend[i] = -Box.fMaxExtend[i];
312          for (unsigned j = 0; j < 3; ++ j)
313             Box.vAxes[i][j] = QuadMom(j,i);
314       }
315       if (det(*(mat3d*)QuadMom.pData) < 0) {
316          // should keep positive orientation of axes. swap y and z to restore it.
317          std::swap(Box.vAxes[1], Box.vAxes[2]);
318          std::swap(Box.fMinExtend[1], Box.fMinExtend[2]);
319          std::swap(Box.fMaxExtend[1], Box.fMaxExtend[2]);
320       }
321    };
322 
323    Box.FixAxisDensity();
324 }
325 
FixOrbitalNormals(FIndexedTriangleList * p,FOrbEvalData * pData)326 void FixOrbitalNormals(FIndexedTriangleList *p, FOrbEvalData *pData)
327 {
328    unsigned
329       nPt = p->Vertices.size();
330    FStackMatrix
331       Grid(3, nPt, pData->pMem),
332       Values(nPt, 4, pData->pMem);
333    for (uint iPt = 0; iPt < nPt; ++ iPt)
334       for (uint ixyz = 0; ixyz < 3; ++ ixyz)
335          Grid(ixyz, iPt) = p->Vertices[iPt].vPos[ixyz];
336    Values.Clear(); // FIXME: remove this.
337    // calculate actual gradient of orbital on the grid points
338    MakeGridValues(Values.pData, Grid, pData->Orbs, 1, pData->pBasisSet, *pData->pMem);
339 
340    // check if we are on a positive or negative side of the iso-surface.
341    // Surface construction is such that the triangles' front sides
342    // point into the direction of more positive function values.
343    double fSum = 0.;
344    for (uint iPt = 0; iPt < nPt; ++ iPt)
345       fSum += Values(iPt,0);
346 
347    // copy back gradient, and normalize.
348    for (uint iPt = 0; iPt < nPt; ++ iPt) {
349       vec3f &vNorm = p->Vertices[iPt].vNorm;
350       for (uint ixyz = 0; ixyz < 3; ++ ixyz)
351          vNorm[ixyz] = Values(iPt, 1+ixyz);
352       // -: we want them to point to the outside.
353 //       std::cout << format("iPt = %5i  nx = %10.5e ny = %10.5e  nz = %10.5e") % iPt % vNorm[0] % vNorm[1] % vNorm[2] << "\n";
354       if (fSum >= 0)
355          vNorm *= -1;
356       vNorm /= vmath::length(vNorm);
357    }
358 
359    if (fSum >= 0) {
360       // invert the triangles; they are currently showing backside out.
361       for (unsigned i = 0; i < p->Triangles.size(); ++ i)
362          std::swap(p->Triangles[i][1], p->Triangles[i][2]);
363    }
364 }
365 
sgn(T val)366 template <typename T> int sgn(T val) {
367     return (T(0) < val) - (val < T(0));
368 }
369 
PrintTiming(QString const & Msg,double t)370 static void PrintTiming(QString const &Msg, double t)
371 {
372 //    std::cout << format(" Time for %-30s%8.2f\n") % (Msg + ":") % t;
373    IvEmit(" Time for %1%2", fmts(Msg,-30), fmtf(t,8,2));
374 }
375 
PrintNumber(QString const & Msg,double fValue,QString Info=QString ())376 static void PrintNumber(QString const &Msg, double fValue, QString Info = QString())
377 {
378    if (Info.isEmpty())
379       IvEmit(" %1%2", fmts(Msg,-41), fmtf(fValue,16,12));
380    else
381       IvEmit(" %1%2 %3", fmts(Msg,-41), fmtf(fValue,16,12), Info);
382 }
383 
FindIsoSettings(double & cIsoValue,FOrbital const & Orb,FOrbitalBox & Box,FIsoSurfaceSettings const & Options,FMemoryStack & Mem)384 void FindIsoSettings(double &cIsoValue, FOrbital const &Orb, FOrbitalBox &Box, FIsoSurfaceSettings const &Options, FMemoryStack &Mem)
385 {
386    ct::FTimer tGridGen;
387 #if 1
388    // make a DFT grid for the atoms in the input box
389    FAtomSet
390       DummyAtoms;
391    for (uint iAtom = 0; iAtom < Orb.pAtoms->size(); ++ iAtom) {
392       bool Keep = true;
393       for (uint iAxis = 0; iAxis < 3; ++ iAxis) {
394          FVector3 c(Box.vCenter[0], Box.vCenter[1], Box.vCenter[2]);
395          FVector3 vAxisPos = (*Orb.pAtoms)[iAtom].vPos - c;
396          double f = Dot(&vAxisPos[0], &Box.vAxes[iAxis][0], 3);
397          if (f < Box.fMinExtend[iAxis] || f > Box.fMaxExtend[iAxis])
398             Keep = false;
399       }
400       if (Keep) {
401          DummyAtoms.Atoms.push_back((*Orb.pAtoms)[iAtom]);
402 //          std::cout << boost::format("   Atom %i is in expansion.") % (iAtom+1) << std::endl;
403       }
404    }
405 //    DummyAtoms.Atoms = Orb.pAtoms->Atoms; // FIXME: remove this.
406 
407    ct::FDftGrid
408       DftGrid(DummyAtoms, FDftGridParams(2));
409    IvEmit(" Generated DFT grid with %1 points for %2 atoms.", DftGrid.Points.size(), DummyAtoms.size());
410 #else
411    // make a DFT grid for the entire molecule
412    ct::FDftGrid
413       DftGrid(*Orb.pAtoms, FDftGridParams(1));
414 #endif
415    unsigned
416       nPts = DftGrid.Points.size();
417 //    std::cout << format(" Generated DFT grid with %i points for %i atoms.\n") % nPts % DummyAtoms.size();
418 //    PrintTiming("DFT grid generation", tGridGen);
419 
420    // calculate orbital values on the grid.
421    TArray<double>
422       Values(nPts);
423 
424    FMatrixView
425       Grid_(&DftGrid.Positions[0][0], 3, nPts),
426       Orbs_ = FMatrixView(const_cast<double*>(&Orb.pCoeffs[0]), Orb.pCoeffs.size(), 1);
427 
428    // evaluate orbitals on the grid points.
429    ct::FTimer tOrbOnGrid;
430    MakeGridValues(&Values[0], Grid_, Orbs_, 0, &*Orb.pBasisSet, Mem);
431 //    PrintTiming("Orbital on grid (scan)", tOrbOnGrid);
432 
433    double
434       fPosOrNeg = 0;
435    for (uint i = 0; i < nPts; ++ i) {
436       fPosOrNeg += sqr(Values[i]) * sgn(Values[i]) * DftGrid.Weights[i];
437    }
438 //    std::cout << format(" %-41s%16.12f (current side: %f)\n") % "Positive/negative balance" % fPosOrNeg % cIsoValue;
439    PrintNumber("Positive/negative balance", fPosOrNeg, IvFmt("(current side: %1)", fmtf(cIsoValue,3)));
440    bool
441       // try to put the bigger lobe on the positive side.
442 //       FlipIso = ((fPosOrNeg > 0) != (cIsoValue > 0));
443       FlipIso = Options.AllowFlip() && (fPosOrNeg < 0.);
444 
445    ct::FTimer tIsoValueAndBounds;
446    double
447       fOrbNorm = 0.;
448    for (unsigned i = 0; i < nPts; ++ i)
449       fOrbNorm += sqr(Values[i]) * DftGrid.Weights[i];
450 
451 //    std::cout << format(" %-41s%16.12f\n") % "Integrated orbital norm" % fOrbNorm;
452    PrintNumber("Integrated orbital norm", fOrbNorm);
453    // ^- should be around 1
454 
455    TArray<std::pair<double,double> >
456       IntWt(nPts);
457    for (unsigned i = 0; i < nPts; ++ i) {
458       IntWt[i].first = -std::abs(Values[i]); // sort in decreasing order.
459       IntWt[i].second = sqr(Values[i]) * DftGrid.Weights[i] / fOrbNorm;  // should sum up to 1.
460    }
461    // sort by orbital value
462    std::sort(IntWt.begin(), IntWt.end());
463 
464 //    for (unsigned i = 0; i < nPts; i += nPts/30) {
465 //       std::cout << format("       %8i   %16.8f   %16.8f\n") % i % IntWt[i].first % IntWt[i].second;
466 //    }
467 
468    if (Options.IsoType == ISOTYPE_Density) {
469       // find smallest value for which
470       //    sum_i w_i orb_i^2 > fThresh.
471       unsigned
472          iThresh;
473       double
474          fAcc0 = 0.;
475       double
476          fThresh = Options.fIsoValue; // e.g., 0.8 -> 80% density surface.
477       for (iThresh = 0; iThresh < nPts; ++ iThresh) {
478          fAcc0 += IntWt[iThresh].second;
479          if (fAcc0 > fThresh)
480             break;
481       }
482 //       assert(iThresh != 0);
483       if (iThresh != 0)
484          cIsoValue = std::sqrt(IntWt[iThresh-1].first * IntWt[iThresh].first);
485       else {
486 //          std::cout << "WARNING: failed to find iso-surface threshold!" << std::endl;
487          IvNotify(NOTIFY_Warning, "Failed to find iso-surface threshold!");
488          cIsoValue = 0.; // no points or all values are the same!!
489       }
490 //       std::cout << format(" %-41s%16.12f%s\n") % boost::str(format("Iso-surface value (%i%% density)")%(int)(100.*fThresh)) % cIsoValue % (FlipIso?" (flipped)":"");
491       PrintNumber(IvFmt("Iso-surface value (%1% density)", int(100.*fThresh)), cIsoValue, (FlipIso?" (flipped)":""));
492    } else if (Options.IsoType == ISOTYPE_Absolute) {
493       cIsoValue = Options.fIsoValue;
494 //       std::cout << format(" %-41s%16.12f%s\n") % "Iso-surface value (absolute)" % cIsoValue % (FlipIso?" (flipped)":"");
495       PrintNumber("Iso-surface value (absolute)", cIsoValue, (FlipIso?" (flipped)":""));
496    } else {
497       throw std::runtime_error("stumbled over unrecognized iso surface type.");
498    }
499 
500    // find extends such that all orbital values on the grid are smaller than the iso value
501    // we found.
502    for (unsigned iAxis = 0; iAxis < 3; ++ iAxis) {
503       double fMin = 1e99, fMax = -1e99;
504       double fRef = Dot(&Box.vCenter[0], &Box.vAxes[iAxis][0], 3);
505       for (unsigned iPt = 0; iPt < nPts; ++ iPt) {
506          if (std::abs(Values[iPt]) > .9 * cIsoValue) {
507             // ^- hm... goldberg/fe35.xml orbital 136.1 is cut if I put in
508             //    a tight check here.
509             //    UPDATE: seems this was caused by broken DFT grids (on the wrong atoms)
510             double fCoord = Dot(&DftGrid.Positions[iPt][0], &Box.vAxes[iAxis][0], 3) - fRef;
511             if (fCoord < fMin) fMin = fCoord;
512             if (fCoord > fMax) fMax = fCoord;
513          }
514       }
515       // add a few percent of buffer zone.
516       double
517          fBufferSize = 0.1 * (fMax-fMin) + 2*Box.fLinearDensity;
518       Box.fMinExtend[iAxis] = fMin - fBufferSize;
519       Box.fMaxExtend[iAxis] = fMax + fBufferSize;
520    }
521    Box.FixAxisDensity();
522 //    PrintTiming("Iso value and bounds", tIsoValueAndBounds);
523    Box.PrintInfo(Orb.GetDesc().toStdString());
524 
525    if (FlipIso)
526       cIsoValue *= -1;
527 }
528 
529 
530 
531 // FIndexedTriangleListPtr MakeIsoSurface(FOrbital const *pOrbital, FIsoSurfaceSettings const &Options, double IsoValue, uint32_t dwColor)
MakeIsoSurface(FOrbital const * pOrbital,FIsoSurfaceSettings const & Options)532 FIndexedTriangleListPtr MakeIsoSurface(FOrbital const *pOrbital, FIsoSurfaceSettings const &Options)
533 {
534 //    std::cout << boost::format("\n!Trace Orbital %s [density: (%.1f/A)^3].") % pOrbital->GetDesc().toStdString() % Options.fLinearDensity << std::endl;
535 //    IvEmit("\n!Trace Orbital %1 [density: (%2/A)^3].", pOrbital->GetDesc(), fmtf(Options.fLinearDensity,0,1));
536    IvEmit("\n");
537    IvNotify(NOTIFY_StartWork, IvFmt("!Tracing Orbital %1 [density: (%2/A)^3]", pOrbital->GetDesc(), fmtf(Options.fLinearDensity,0,1)));
538 
539    FMemoryStack2
540       Mem(200000000); // ~200 mb
541 
542    ct::FTimer tStart;
543 
544    FOrbEvalData
545       data;
546    data.pMem = &Mem;
547    data.pBasisSet = &*pOrbital->pBasisSet;
548    data.Orbs = FMatrixView(const_cast<double*>(&pOrbital->pCoeffs[0]), pOrbital->pCoeffs.size(), 1);
549 
550 
551    double
552       fLinearDensity = 1./(ToAng*Options.fLinearDensity);
553    ct::FTimer tInitialOrbitalBox;
554    FOrbitalBox
555       ob;
556    MakeOrbitalBox(ob, *pOrbital, fLinearDensity, Mem);
557 //    PrintTiming("Multipole moments", tInitialOrbitalBox);
558    // ^- that one is actually quite bad IF we calculate them here, and via
559    // CtInt1e.
560 
561    double
562       cIsoValue1 = Options.fIsoValue;
563    FindIsoSettings(cIsoValue1, *pOrbital, ob, Options, Mem);
564    double
565       IsoValue = cIsoValue1 * sgn(Options.fIsoValue); // <- to fix the sign.
566 
567    FCartesianGridInfo
568       CaGrid;
569    CaGrid.vRef = ob.vCenter;  // should be ob.vCenter - \sum[i] ob.vAxes[i] in the end.
570    for (unsigned i = 0; i < 3; ++ i) {
571       double fExtend = ob.fMaxExtend[i] - ob.fMinExtend[i];
572       CaGrid.dXyz[i] = (fExtend/(ob.nPts[i]-1)) * ob.vAxes[i];
573 //       CaGrid.vRef -= (.5*ob.nPts[i]) * CaGrid.dXyz[i];
574       CaGrid.vRef += ob.fMinExtend[i] * ob.vAxes[i];
575       CaGrid.nPts[i] = ob.nPts[i];
576    }
577 
578    data.pCaGrid = &CaGrid;
579 
580    size_t
581       nTrianglesTotal = 0;
582    PrintTiming("Iso-surface setup", tStart);
583    bool
584       PrecalcValues = true;
585    if (PrecalcValues) {
586       // precalculate orbital values on the grid. This way they can be shared
587       // between the plus and the minus surfaces.
588       // (otherwise they'd be calculated inline. this required much less memory,
589       //  but requires re-calculating the values for each surface.)
590       ct::FTimer tOrbOnGrid;
591       data.pGridData = FGridDataRangePtr(new FGridDataRange(CaGrid.nPts[0], CaGrid.nPts[1], 0, CaGrid.nPts[2], &data));
592       PrintTiming("Orbital on grid (trace)", tOrbOnGrid);
593    }
594 
595    FIndexedTriangleListPtr
596       pTriListCombined,
597       pTriList;
598 
599    for (uint iPlusOrMinus = 0; iPlusOrMinus <= (uint)Options.PlusAndMinus(); ++ iPlusOrMinus) {
600       FBaseVertex RefVertex;
601       RefVertex.dwColor = Options.dwColorPlus;
602       RefVertex.iRole = iPlusOrMinus;
603 
604       if (iPlusOrMinus == 1) {
605          IsoValue *= -1;
606          RefVertex.dwColor = Options.dwColorMinus;
607       }
608       ct::FTimer tSurfaceTrace;
609 
610       pTriList = ctIsoSurf::TraceIsoSurface(CaGrid, EvalOrbital2, &data, IsoValue, RefVertex);
611 //       std::cout << format(" -> %i vertices and %i triangles.") % pTriList->Vertices.size() % pTriList->Triangles.size() << std::endl;
612       IvEmit("    -> %1 vertices and %2 triangles.", pTriList->Vertices.size(), pTriList->Triangles.size());
613       nTrianglesTotal += pTriList->Triangles.size();
614 
615       ct::FTimer tTriangleConv;
616       FixOrbitalNormals(&*pTriList, &data);
617       PrintTiming("Iso-surface conv/normals", tTriangleConv);
618       if (iPlusOrMinus == 0)
619          pTriListCombined = pTriList;
620       else
621          pTriListCombined->Append(*pTriList);
622    }
623    PrintTiming("Iso-surface total", tStart);
624 
625 //    IvNotify(NOTIFY_FinishWork, IvFmt("Ready"));
626    IvNotify(NOTIFY_FinishWork, IvFmt("Ready [%1k grid points and %2 triangles finished after %3 sec].", ob.nPtsTotal()/1000, nTrianglesTotal, QString("%1").arg((double)tStart,0,'f',2)));
627    return pTriListCombined;
628 }
629 
630 
631 
632 
633 
634 namespace ctIsoSurf {
635    // really just a bunch of triangles...
636    struct FSurface
637    {
638       typedef TArray<FTriangle1>
639          FTriangleArray;
640       FTriangleArray
641          Triangles;
642       void AddTriangle(FVec3d const &v0, FVec3d const &v1, FVec3d const &v2);
643 
644       // note: this does not generate normals.
645       FIndexedTriangleListPtr ConvertToIndexed(FBaseVertex const &RefVertex);
646    };
647 
AddTriangle(FVec3d const & v0,FVec3d const & v1,FVec3d const & v2)648    void FSurface::AddTriangle(FVec3d const &v0, FVec3d const &v1, FVec3d const &v2)
649    {
650       Triangles.push_back(FTriangle1(v0,v1,v2));
651    }
652 
ConvertToIndexed(FBaseVertex const & RefVertex)653    FIndexedTriangleListPtr FSurface::ConvertToIndexed(FBaseVertex const &RefVertex)
654    {
655       ct::TArray<FVec3d> Pos;
656       ct::TArray<unsigned> Indices;
657 
658       MakeIndexedTriangles(Pos, Indices, this->Triangles);
659       return FIndexedTriangleListPtr(new FIndexedTriangleList(&Pos[0], &Pos[0], Pos.size(), &Indices[0], Indices.size(), RefVertex));
660    }
661 
662 
663    enum FVertexType {
664       VERTEX_None, // "no iso-surface intersection here (vertex does not exist)"
665       VERTEX_LeftSide,
666       VERTEX_RightSide,
667    };
668 
669    struct FIsoVertex
670    {
671       FVec3d
672          vPos;
673       FVertexType
674          Type;
675    };
676 
677    // one two-dimensional layer of data (i.e., nx*ny for a given iz)
678    template<class T>
679    struct TLayer : public TArray<T>
680    {
681       typedef typename ct::TArray<T>
682          FBase;
683 
TLayerctIsoSurf::TLayer684       TLayer() {};
685 
TLayerctIsoSurf::TLayer686       TLayer(size_t nx_, size_t ny_)  {
687          resize(nx_, ny_);
688       }
689 
resizectIsoSurf::TLayer690       void resize(size_t nx_, size_t ny_) {
691          nx = nx_;
692          ny = ny_;
693          FBase::resize(nx*ny);
694       }
695 
operator ()ctIsoSurf::TLayer696       T &operator() (size_t ix, size_t iy) {
697          assert(ix < nx && iy < ny);
698          return (*this)[ix + nx*iy];
699       }
700 
701       size_t
702          nx, ny;
703 
swapctIsoSurf::TLayer704       void swap(TLayer &other) {
705          FBase::swap(*(FBase*)&other);
706          std::swap(this->nx, other.nx);
707          std::swap(this->ny, other.ny);
708          // ^- not really required because we anyway use it only with equal
709          //    dimensions.
710       }
711    };
712    typedef TLayer<double>
713       FDataLayer;
714    typedef TLayer<FIsoVertex>
715       FVertexLayer;
716 
717    // one set of iso surface/cube edge intersections
718    struct FIsoSlice
719    {
FIsoSlicectIsoSurf::FIsoSlice720       FIsoSlice(size_t nx, size_t ny) {
721          for (size_t ixyz = 0; ixyz < 3; ++ ixyz)
722             VerticesXyz[ixyz].resize(nx, ny);
723       }
724 
swapctIsoSurf::FIsoSlice725       void swap(FIsoSlice &other) {
726          for (size_t ixyz = 0; ixyz < 3; ++ ixyz)
727             VerticesXyz[ixyz].swap(other.VerticesXyz[ixyz]);
728       }
729 
730       FVertexLayer
731          // vertices for iso-surface intersections along the three cartesian
732          // directions.
733          // Note:
734          //   [0] has (nx-1) x ny entries
735          //   [1] has nx * (ny-1) entries
736          //   [2] has nx*ny entries.
737          VerticesXyz[3];
738    };
739 
740 
FindVerticesForIsoIntersectionsOnAxis(FVertexLayer & VertexLayer,FCartesianGridInfo const & g,size_t ld,size_t nx,size_t ny,double * pLayerA,double * pLayerB,size_t iz,size_t ixyz,double fIsoValue)741    static void FindVerticesForIsoIntersectionsOnAxis(FVertexLayer &VertexLayer,
742       FCartesianGridInfo const &g,  size_t ld, size_t nx, size_t ny,
743       double *pLayerA, double *pLayerB, size_t iz, size_t ixyz, double fIsoValue)
744    {
745       for (size_t iy = 0; iy < ny; ++ iy)
746          for (size_t ix = 0; ix < nx; ++ ix) {
747             double
748                dA = pLayerA[ix + ld*iy] - fIsoValue,
749                dB = pLayerB[ix + ld*iy] - fIsoValue;
750             bool
751                Left = (dA >= 0. && dB < 0),
752                Right = (dA < 0 && dB >= 0.);
753             FIsoVertex
754                *v = &VertexLayer(ix, iy);
755             if (Left || Right) {
756                v->vPos = g.vPt((uint)ix, (uint)iy, (uint)iz);
757                v->vPos += dA/(dA-dB) * g.dXyz[ixyz];
758                v->Type = Left? VERTEX_LeftSide : VERTEX_RightSide;
759             } else {
760                v->Type = VERTEX_None;
761             }
762          }
763    }
764 
FindVerticesForIsoIntersections(FIsoSlice & Slice,FCartesianGridInfo const & g,FDataLayer * pLayerZ0,FDataLayer * pLayerZ1,double fIsoValue,size_t iz0)765    static void FindVerticesForIsoIntersections(FIsoSlice &Slice, FCartesianGridInfo const &g, FDataLayer *pLayerZ0, FDataLayer *pLayerZ1, double fIsoValue, size_t iz0)
766    {
767       size_t ld = pLayerZ0->nx;
768 
769       // x/x+1 direction (at z = 0)
770       FindVerticesForIsoIntersectionsOnAxis(Slice.VerticesXyz[0], g, ld, g.nPts[0]-1, g.nPts[1], &(*pLayerZ0)[0], &(*pLayerZ0)[1], iz0, 0, fIsoValue);
771       // y/y+1 direction (at z = 0)
772       FindVerticesForIsoIntersectionsOnAxis(Slice.VerticesXyz[1], g, ld, g.nPts[0], g.nPts[1]-1, &(*pLayerZ0)[0], &(*pLayerZ0)[ld], iz0, 1, fIsoValue);
773       // z/z+1 direction
774       if (pLayerZ1) {
775          FindVerticesForIsoIntersectionsOnAxis(Slice.VerticesXyz[2], g, ld, g.nPts[0], g.nPts[1], &(*pLayerZ0)[0], &(*pLayerZ1)[0], iz0, 2, fIsoValue);
776       } else {
777          for (size_t iy = 0; iy < g.nPts[1]; ++ iy)
778             for (size_t ix = 0; ix < g.nPts[0]; ++ ix)
779                Slice.VerticesXyz[2](ix,iy).Type = VERTEX_None;
780       }
781    }
782 
783    // Format is as follows:
784    //         {iDirection, ix,iy,iz}
785    // where iDirection gives the direction along which the edge is oriented,
786    // and ix/iy/iz specify the *TWO* other edge coordinates, which are *NOT* determined
787    // by iDirection. That is, e.g., the edge specification
788    //        {#y, ix,iy,iz}
789    // translates to
790    //        (ix,0,iz)--(ix,1,iz)
791    // with iy being ignored.
792    //
793    // Note: I use the same edge order and numbering as GTS, from which the algorithm
794    // idea used here is recycled (see GTS's isocube.fig).
795    static unsigned char const iEdgeCoords[12][4] = {
796       {0,0,0,0}, {0,0,0,1}, {0,0,1,1}, {0,0,1,0},    // <- the 4 edges along the x direction
797       {1,0,0,0}, {1,0,0,1}, {1,1,0,1}, {1,1,0,0},    // <- the 4 edges along the y direction
798       {2,0,0,0}, {2,1,0,0}, {2,1,1,0}, {2,0,1,0}     // <- the 4 edges along the z direction
799    };
800 
801    // For each edge, this gives the indices of the other three edges which lie
802    // on the same cube face. First index decides about which of the two faces
803    // lying on the same edge are meant.
804    static unsigned char const iEdgeLinks[2][12][3] = {
805       {{9,1,8}, {6,2,5}, {10,3,11}, {7,0,4}, {3,7,0}, {11,4,8}, {2,5,1}, {10,6,9}, {5,11,4}, {1,8,0}, {6,9,7}, {2,10,3}},
806       {{4,3,7}, {8,0,9}, {5,1,6}, {11,2,10}, {8,5,11}, {1,6,2}, {9,7,10}, {0,4,3}, {0,9,1}, {7,10,6}, {3,11,2}, {4,8,5}}
807    };
808 
809 
810    // make iso surface fragments (for already calculated vertices) between two layers of data.
811    // I.e., make triangle indices.
MakeIsoSurfaceSlice(FSurface & SurfaceOut,FIsoSlice * pSliceZ0,FIsoSlice * pSliceZ1)812    static void MakeIsoSurfaceSlice(FSurface &SurfaceOut, FIsoSlice *pSliceZ0, FIsoSlice *pSliceZ1)
813    {
814       size_t
815          nx = pSliceZ0->VerticesXyz[0].nx,
816          ny = pSliceZ0->VerticesXyz[0].ny;
817       FIsoSlice
818          *(pSlicesZ01[2]) = {pSliceZ0, pSliceZ1};
819       FIsoVertex
820          EdgeVertices[12],
821          FaceVertices[12]; // vertices at edge/surface intersections
822 
823       // iterate over the (nx-1) x (ny-1) cubes in between layers at z0 and z1.
824       for (size_t iy = 0; iy < ny - 1; ++ iy)
825          for (size_t ix = 0; ix < nx - 1; ++ ix) {
826             // copy all vertices of cube-edge/iso-surface intersections of current cube.
827             for (size_t iEdge = 0; iEdge < 12; ++ iEdge) {
828                unsigned char const
829                   *ec = &iEdgeCoords[iEdge][0];
830                EdgeVertices[iEdge] = pSlicesZ01[ec[3]]->VerticesXyz[ec[0]](ix + ec[1], iy + ec[2]);
831             }
832             // marker for whether the edge intersection has already been handled
833             // as part of another edge trace
834             bool EdgeHandled[12] = {0};
835 
836             for (size_t iEdge0 = 0; iEdge0 < 12; ++ iEdge0) {
837                if (EdgeHandled[iEdge0] || EdgeVertices[iEdge0].Type == VERTEX_None)
838                   continue;
839                // current edge intersects with the iso surface and has not
840                // already been part of a face starting at a previous edge.
841 
842                // -> make a new face and find all other edges which belong to
843                // the current face.
844                size_t
845                   // number intersections of current cube's edges with iso surface.
846                   // corresponding edges are stored in v[].
847                   nEdges = 0,
848                   iEdge1 = iEdge0;
849                while (!EdgeHandled[iEdge1] && EdgeVertices[iEdge1].Type != VERTEX_None) {
850                   FIsoVertex
851                      &v1 = EdgeVertices[iEdge1];
852                   FaceVertices[nEdges] = v1;
853                   EdgeHandled[iEdge1] = true; // don't visit this edge again.
854                   nEdges += 1;
855                   size_t
856                      iSide = (v1.Type == VERTEX_LeftSide)? 0 : 1;
857                   // look for another edge on the current face which intersects the surface
858                   unsigned char const
859                      *iEdge1Links = &iEdgeLinks[iSide][iEdge1][0];
860                   for (size_t iLink = 0; iLink < 3; ++ iLink) {
861                      iEdge1 = iEdge1Links[iLink];
862                      if (EdgeVertices[iEdge1].Type != VERTEX_None)
863                         break;
864                   }
865                }
866 
867                if (nEdges >= 3)
868                   for (size_t iTri = 0; iTri < nEdges - 2; ++ iTri)
869                      SurfaceOut.AddTriangle(FaceVertices[0].vPos, FaceVertices[iTri+1].vPos, FaceVertices[iTri+2].vPos);
870             }
871          }
872    }
873 
TraceIsoSurface(FCartesianGridInfo const & g,FEvalFn f,void * pEvanFnArgs_,double fIsoValue,FBaseVertex const & RefVertex)874    FIndexedTriangleListPtr TraceIsoSurface(FCartesianGridInfo const &g, FEvalFn f, void *pEvanFnArgs_, double fIsoValue, FBaseVertex const &RefVertex)
875    {
876       FSurface
877          SurfaceOut;
878 
879       size_t
880          nx = g.nPts[0],
881          ny = g.nPts[1],
882          nz = g.nPts[2];
883       // we keep two layers (iz and iz+1) in memory at each time. This here
884       // stores the data of them. Format: [ix + nx*iy]
885       FDataLayer
886          LayerA(nx, ny), // last data layer
887          LayerB(nx, ny); // current data layer
888       FIsoSlice
889          SliceA(nx, ny),
890          SliceB(nx, ny);
891          // ^- if we generalize this for multiple simultaneous surface traces, then
892          // these things need to be supplied once for each iso value.
893 
894       // initialize first layer.
895       f(&LayerA[0], &g, 0, pEvanFnArgs_);
896 
897       for (size_t iz = 0; iz < nz; ++ iz) {
898          if (iz != nz-1) {
899             // evaluate function at next z
900             f(&LayerB[0], &g, iz+1, pEvanFnArgs_);
901             // find intersections of cube edges:
902             //   - edges along x axis for current iz
903             //   - edges along y axis for current iz
904             //   - edges along z axis for z between (iz and iz+1)
905             FindVerticesForIsoIntersections(SliceB, g, &LayerA, &LayerB, fIsoValue, iz);
906          } else {
907             // this is the last slice: do not evaluate f anymore,
908             // but still make cube edge/iso surface intersections along x and y
909             // directions.
910             FindVerticesForIsoIntersections(SliceB, g, &LayerA, 0, fIsoValue, iz);
911          }
912 
913          if (iz != 0)
914             // Construct faces for iso surface elements between LayersA and LayerB
915             // With both SliceA and SliceB we now have all edge intersections for
916             // the 1x1x1 cubes with start coordinate index (ix,iy,iz-1),
917             // where ix in [0..nx-1], iy in [0..ny-1].
918             MakeIsoSurfaceSlice(SurfaceOut, &SliceA, &SliceB);
919 
920          LayerA.swap(LayerB);
921          SliceA.swap(SliceB);
922       }
923       return SurfaceOut.ConvertToIndexed(RefVertex);
924    }
925 } // namespace ctIsoSurf
926