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