1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2007-03-30 11:40:16 -0500 (Fri, 30 Mar 2007) $
4  * $Revision: 7273 $
5  *
6  * Copyright (C) 2007 Miguel, Bob, Jmol Development
7  *
8  * Contact: hansonr@stolaf.edu
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2.1 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 package org.jmol.jvxl.readers;
25 
26 import java.util.Random;
27 
28 
29 import org.jmol.jvxl.data.JvxlCoder;
30 import org.jmol.util.Logger;
31 import org.jmol.util.MeshSurface;
32 
33 import javajs.util.Measure;
34 import javajs.util.SB;
35 import javajs.util.P3;
36 import javajs.util.T3;
37 import javajs.util.V3;
38 
39 final class IsoShapeReader extends VolumeDataReader {
40   // final because we are initiating static fields using static{}
41 
42   private int psi_n = 2;
43   private int psi_l = 1;
44   private int psi_m = 1;
45   private float psi_Znuc = 1; // hydrogen
46   private float sphere_radiusAngstroms;
47   private int monteCarloCount;
48   private Random random;
49 
IsoShapeReader()50   IsoShapeReader() {}
51 
52   @Override
init(SurfaceGenerator sg)53   void init(SurfaceGenerator sg) {
54     initVDR(sg);
55     Object o = sg.getReaderData();
56     if (o instanceof Float) {
57       sphere_radiusAngstroms = ((Float) o).floatValue();
58     } else {
59       sphere_radiusAngstroms = 0;
60       float[] data = (float[]) o;
61       psi_n = (int) data[0];
62       psi_l = (int) data[1];
63       psi_m = (int) data[2];
64       psi_Znuc = data[3];//z_eff;
65       monteCarloCount = (int) data[4];
66     }
67   }
68 
69   private boolean allowNegative = true;
70 
71   private double[] rfactor = new double[10];
72   private double[] pfactor = new double[10];
73 
74   private final static double A0 = 0.52918; //x10^-10 meters
75   private final static double ROOT2 = 1.414214;
76   private static final float ATOMIC_ORBITAL_ZERO_CUT_OFF = 1e-7f;
77 
78   private float radius;
79   private final P3 ptPsi = new P3();
80 
81   @Override
setup(boolean isMapData)82   protected void setup(boolean isMapData) {
83     volumeData.sr = this; // we will provide point data for mapping
84     precalculateVoxelData = false;
85     isQuiet = true;
86     if (Float.isNaN(center.x))
87       center.set(0, 0, 0);
88     String type = "sphere";
89     switch (dataType) {
90     case Parameters.SURFACE_ATOMICORBITAL:
91       calcFactors(psi_n, psi_l, psi_m);
92       autoScaleOrbital();
93       ptsPerAngstrom = 5f;
94       maxGrid = 40;
95       type = "hydrogen-like orbital";
96       if (monteCarloCount > 0) {
97         vertexDataOnly = true;
98         //params.colorDensity = true;
99         random = new Random(params.randomSeed);
100       } else {
101         isQuiet = false;
102       }
103       break;
104     case Parameters.SURFACE_LONEPAIR:
105     case Parameters.SURFACE_RADICAL:
106       type = "lp";
107       vertexDataOnly = true;
108       radius = 0;
109       ptsPerAngstrom = 1;
110       maxGrid = 1;
111       break;
112     case Parameters.SURFACE_LOBE:
113       allowNegative = false;
114       calcFactors(psi_n, psi_l, psi_m);
115       psi_normalization = 1;
116       radius = 1.1f * eccentricityRatio * eccentricityScale;
117       if (eccentricityScale > 0 && eccentricityScale < 1)
118         radius /= eccentricityScale;
119       ptsPerAngstrom = 10f;
120       maxGrid = 21;
121       type = "lobe";
122       break;
123     case Parameters.SURFACE_ELLIPSOID3:
124       type = "ellipsoid(thermal)";
125       radius = 3.0f * sphere_radiusAngstroms;
126       ptsPerAngstrom = 10f;
127       maxGrid = 22;
128       break;
129     case Parameters.SURFACE_GEODESIC:
130       if (!isMapData && monteCarloCount == 0)
131         break;
132       type = "geodesic";
133       //$FALL-THROUGH$
134     case Parameters.SURFACE_ELLIPSOID2:
135       if (type.equals("sphere"))
136         type = "ellipsoid";
137       //$FALL-THROUGH$
138     case Parameters.SURFACE_SPHERE:
139     default:
140       radius = 1.2f * sphere_radiusAngstroms * eccentricityScale;
141       ptsPerAngstrom = 10f;
142       maxGrid = 22;
143       break;
144     }
145     if (monteCarloCount == 0)
146       setVolumeData();
147     setHeader(type + "\n");
148   }
149 
150   @Override
setVolumeData()151   protected void setVolumeData() {
152     setVoxelRange(0, -radius, radius, ptsPerAngstrom, maxGrid, 0);
153     setVoxelRange(1, -radius, radius, ptsPerAngstrom, maxGrid, 0);
154     if (allowNegative)
155       setVoxelRange(2, -radius, radius, ptsPerAngstrom, maxGrid, 0);
156     else
157       setVoxelRange(2, 0, radius / eccentricityRatio, ptsPerAngstrom, maxGrid, 0);
158   }
159 
160   @Override
getValue(int x, int y, int z, int ptyz)161   public float getValue(int x, int y, int z, int ptyz) {
162     volumeData.voxelPtToXYZ(x, y, z, ptPsi);
163     return getValueAtPoint(ptPsi, false);
164   }
165 
166   @Override
getValueAtPoint(T3 pt, boolean getSource)167   public float getValueAtPoint(T3 pt, boolean getSource) {
168     ptTemp.sub2(pt, center);
169     if (isEccentric)
170       eccentricityMatrixInverse.rotate(ptTemp);
171     if (isAnisotropic) {
172       ptTemp.x /= anisotropy[0];
173       ptTemp.y /= anisotropy[1];
174       ptTemp.z /= anisotropy[2];
175     }
176     if (sphere_radiusAngstroms > 0) {
177       if (params.anisoB != null) {
178 
179         return sphere_radiusAngstroms
180             -
181 
182             (float) Math.sqrt(ptTemp.x * ptTemp.x + ptTemp.y * ptTemp.y
183                 + ptTemp.z * ptTemp.z)
184             / (float) (Math.sqrt(params.anisoB[0] * ptTemp.x * ptTemp.x
185                 + params.anisoB[1] * ptTemp.y * ptTemp.y + params.anisoB[2]
186                 * ptTemp.z * ptTemp.z + params.anisoB[3] * ptTemp.x * ptTemp.y
187                 + params.anisoB[4] * ptTemp.x * ptTemp.z + params.anisoB[5]
188                 * ptTemp.y * ptTemp.z));
189       }
190       return sphere_radiusAngstroms
191           - (float) Math.sqrt(ptTemp.x * ptTemp.x + ptTemp.y * ptTemp.y
192               + ptTemp.z * ptTemp.z);
193     }
194     float value = (float) hydrogenAtomPsi(ptTemp);
195     if (Math.abs(value) < ATOMIC_ORBITAL_ZERO_CUT_OFF)
196       value = 0;
197     return (allowNegative || value >= 0 ? value : 0);
198   }
199 
setHeader(String line1)200   private void setHeader(String line1) {
201     jvxlFileHeaderBuffer = new SB();
202     jvxlFileHeaderBuffer.append(line1);
203     if (sphere_radiusAngstroms > 0) {
204       jvxlFileHeaderBuffer.append(" rad=").appendF(sphere_radiusAngstroms);
205     } else {
206       jvxlFileHeaderBuffer.append(" n=").appendI(psi_n).append(", l=").appendI(
207           psi_l).append(", m=").appendI(psi_m).append(" Znuc=").appendF(psi_Znuc)
208           .append(" res=").appendF(ptsPerAngstrom).append(" rad=")
209           .appendF(radius);
210     }
211     jvxlFileHeaderBuffer.append(isAnisotropic ? " anisotropy=(" + anisotropy[0]
212         + "," + anisotropy[1] + "," + anisotropy[2] + ")\n" : "\n");
213     JvxlCoder.jvxlCreateHeaderWithoutTitleOrAtoms(volumeData,
214         jvxlFileHeaderBuffer);
215   }
216 
217   private final static float[] fact = new float[20];
218   static {
219     fact[0] = 1;
220     for (int i = 1; i < 20; i++)
221       fact[i] = fact[i - 1] * i;
222   }
223 
224   private double psi_normalization = 1 / (2 * Math.sqrt(Math.PI));
225 
calcFactors(int n, int el, int m)226   private void calcFactors(int n, int el, int m) {
227     int abm = Math.abs(m);
228     double NnlLnl = Math.pow(2 * psi_Znuc / n / A0, 1.5)
229         * Math.sqrt(fact[n - el - 1] * fact[n + el] / 2 / n);
230     //double Lnl = fact[n + el] * fact[n + el];
231     double Plm = Math.pow(2, -el) * fact[el] * fact[el + abm]
232         * Math.sqrt((2 * el + 1) * fact[el - abm] / 2 / fact[el + abm]);
233 
234     for (int p = 0; p <= n - el - 1; p++)
235       rfactor[p] = NnlLnl / fact[p] / fact[n - el - p - 1]
236           / fact[2 * el + p + 1];
237     for (int p = abm; p <= el; p++)
238       pfactor[p] = Math.pow(-1, el - p) * Plm / fact[p] / fact[el + abm - p]
239           / fact[el - p] / fact[p - abm];
240   }
241 
242   private double aoMax;
243   private double aoMax2;
244   private double angMax2;
245   private V3 planeU;
246   private V3 planeV;
247   private P3 planeCenter;
248   private float planeRadius;
249 
autoScaleOrbital()250   private void autoScaleOrbital() {
251     aoMax = 0;
252     float rmax = 0;
253     aoMax2 = 0;
254     float rmax2 = 0;
255     double d;
256     if (params.distance == 0) {
257       for (int ir = 0; ir < 1000; ir++) {
258         float r = ir / 10f;
259         d = Math.abs(radialPart(r));
260         if (Logger.debugging)
261           Logger.debug("R\t" + r + "\t" + d);
262         if (d >= aoMax) {
263           rmax = r;
264           aoMax = d;
265         }
266         d *= d * r * r;
267         if (d >= aoMax2) {
268           rmax2 = r;
269           aoMax2 = d;
270         }
271       }
272     } else {
273       aoMax = Math.abs(radialPart(params.distance));
274       aoMax2 = aoMax * aoMax * params.distance * params.distance;
275       rmax = rmax2 = params.distance;
276     }
277     Logger.info("Atomic Orbital radial max = " + aoMax + " at " + rmax);
278     Logger.info("Atomic Orbital r2R2 max = " + aoMax2 + " at " + rmax2);
279 
280     if (monteCarloCount >= 0) {
281       angMax2 = 0;
282       for (float ang = 0; ang < 180; ang += 1) {
283         double th = ang / (2 * Math.PI);
284         d = Math.abs(angularPart(th, 0, 0));
285         if (Logger.debugging)
286           Logger.debug("A\t" + ang + "\t" + d);
287         if (d > angMax2) {
288           angMax2 = d;
289         }
290       }
291       angMax2 *= angMax2;
292       if (psi_m != 0) {
293         // if psi_m not 0, we include sqrt2 from creating real counterpart
294         // of the imaginary solution: 1/sqrt2(psi_a +/- psi_b)
295         // you get, for example, 1/sqrt2(2 cos phi) = sqrt2 * cos phi
296         // which has a max of sqrt2.
297         angMax2 *= 2;
298       }
299       Logger.info("Atomic Orbital phi^2theta^2 max = " + angMax2);
300       // (we don't apply the final 4pi here because it is just a constant)
301     }
302     double min;
303     if (params.cutoff == 0) {
304       min = (monteCarloCount > 0 ? aoMax * 0.01f : 0.01f);
305     } else if (monteCarloCount > 0) {
306       aoMax = Math.abs(params.cutoff);
307       min = aoMax * 0.01f;
308     } else {
309       min = Math.abs(params.cutoff / 2);
310       // ISSQUARED means cutoff is in terms of psi*2, not psi
311       if (params.isSquared)
312         min = Math.sqrt(min / 2);
313     }
314     float r0 = 0;
315     for (int ir = 1000; --ir >= 0; ir -= 1) {
316       float r = ir / 10f;
317       d = Math.abs(radialPart(r));
318       if (d >= min) {
319         r0 = r;
320         break;
321       }
322     }
323     radius = r0 + (monteCarloCount == 0 ? 1 : 0);
324     if (isAnisotropic) {
325       float aMax = 0;
326       for (int i = 3; --i >= 0;)
327         if (anisotropy[i] > aMax)
328           aMax = anisotropy[i];
329       radius *= aMax;
330     }
331     Logger.info("Atomic Orbital radial extent set to " + radius
332         + " for cutoff " + params.cutoff);
333     if (params.thePlane != null && monteCarloCount > 0) {
334       // get two perpendicular unit vectors in the plane.
335       planeCenter = new P3();
336       planeU = new V3();
337       Measure.getPlaneProjection(center, params.thePlane, planeCenter, planeU);
338       planeU.set(params.thePlane.x, params.thePlane.y, params.thePlane.z);
339       planeU.normalize();
340       planeV = V3.new3(1, 0, 0);
341       if (Math.abs(planeU.dot(planeV)) > 0.5f)
342         planeV.set(0, 1, 0);
343       planeV.cross(planeU, planeV);
344       planeU.cross(planeU, planeV);
345       aoMax2 = 0;
346       d = center.distance(planeCenter);
347       if (d < radius) {
348         planeRadius = (float) Math.sqrt(radius * radius - d * d);
349         int ir = (int) (planeRadius * 10);
350         for (int ix = -ir; ix <= ir; ix++)
351           for (int iy = -ir; iy <= ir; iy++) {
352             ptPsi.setT(planeU);
353             ptPsi.scale(ix / 10f);
354             ptPsi.scaleAdd2(iy / 10f, planeV, ptPsi);
355             d = hydrogenAtomPsi(ptPsi);
356             // we need an approximation  of the max value here
357             d = Math.abs(hydrogenAtomPsi(ptPsi));
358             if (d > aoMax2)
359               aoMax2 = d;
360           }
361         if (aoMax2 < 0.001f) // must be a node
362           aoMax2 = 0;
363         else
364           aoMax2 *= aoMax2;
365       }
366     }
367   }
368 
radialPart(double r)369   private double radialPart(double r) {
370     double rho = 2d * psi_Znuc * r / psi_n / A0;
371     double sum = 0;
372     for (int p = 0; p <= psi_n - psi_l - 1; p++)
373       sum += Math.pow(-rho, p) * rfactor[p];
374     return Math.exp(-rho / 2) * Math.pow(rho, psi_l) * sum;
375   }
376 
377   private double rnl;
378 
hydrogenAtomPsi(P3 pt)379   private double hydrogenAtomPsi(P3 pt) {
380     // ref: http://www.stolaf.edu/people/hansonr/imt/concept/schroed.pdf
381     double x2y2 = pt.x * pt.x + pt.y * pt.y;
382     rnl = radialPart(Math.sqrt(x2y2 + pt.z * pt.z));
383     double ph = Math.atan2(pt.y, pt.x);
384     double th = Math.atan2(Math.sqrt(x2y2), pt.z);
385     double theta_lm_phi_m = angularPart(th, ph, psi_m);
386     return rnl * theta_lm_phi_m;
387   }
388 
angularPart(double th, double ph, int m)389   private double angularPart(double th, double ph, int m) {
390     // note: we are factoring in 1 / 2 sqrt PI starting with Jmol 12.1.52
391     double cth = Math.cos(th);
392     double sth = Math.sin(th);
393     boolean isS = (m == 0 && psi_l == 0);
394     int abm = Math.abs(m);
395     double sum = 0;
396     if (isS)
397       sum = pfactor[0];
398     else
399       for (int p = abm; p <= psi_l; p++)
400         sum += (p == abm ? 1 : Math.pow(1 + cth, p - abm))
401             * (p == psi_l ? 1 : Math.pow(1 - cth, psi_l - p)) * pfactor[p];
402     double theta_lm = (abm == 0 ? sum : Math.abs(Math.pow(sth, abm)) * sum);
403     double phi_m;
404     if (m == 0)
405       phi_m = 1;
406     else if (m > 0)
407       phi_m = Math.cos(m * ph) * ROOT2;
408     else
409       phi_m = Math.sin(m * ph) * ROOT2;
410     return (Math.abs(phi_m) < 0.0000000001 ? 0 : theta_lm * phi_m * psi_normalization);
411   }
412 
413   private boolean surfaceDone;
414   private int nTries;
415 
createMonteCarloOrbital()416   private void createMonteCarloOrbital() {
417     if (surfaceDone || aoMax2 == 0 || params.distance > radius)
418       return;
419     boolean isS = (psi_m == 0 && psi_l == 0);
420     surfaceDone = true;
421     float value;
422     float rave = 0;
423     nTries = 0;
424     for (int i = 0; i < monteCarloCount; nTries++) {
425       // we do Pshemak's idea here -- force P(r2R2), then pick a random
426       // point on the sphere for that radius
427       if (params.thePlane == null) {
428         double r;
429         if (params.distance == 0) {
430           r = random.nextDouble() * radius;
431           double rp = r * radialPart(r);
432           if (rp * rp <= aoMax2 * random.nextDouble())
433             continue;
434         } else {
435           r = params.distance;
436         }
437         double u = random.nextDouble();
438         double v = random.nextDouble();
439         double theta = 2 * Math.PI * u;
440         double cosPhi = 2 * v - 1;
441         if (!isS) {
442           double phi = Math.acos(cosPhi);
443           double ap = angularPart(phi, theta, psi_m);
444           if (ap * ap <= angMax2 * random.nextDouble())
445             continue;
446         }
447         //http://mathworld.wolfram.com/SpherePointPicking.html
448         double sinPhi = Math.sin(Math.acos(cosPhi));
449         double x = r * Math.cos(theta) * sinPhi;
450         double y = r * Math.sin(theta) * sinPhi;
451         double z = r * cosPhi;
452         //x = r; y = r2R2/aoMax2 * 10; z = 0;
453         ptPsi.set((float) x, (float) y, (float) z);
454         ptPsi.add(center);
455         value = getValueAtPoint(ptPsi, false);
456       } else {
457         ptPsi.setT(planeU);
458         ptPsi.scale(random.nextFloat() * planeRadius * 2 - planeRadius);
459         ptPsi.scaleAdd2(random.nextFloat() * planeRadius * 2 - planeRadius,
460             planeV, ptPsi);
461         ptPsi.add(planeCenter);
462         value = getValueAtPoint(ptPsi, false);
463         if (value * value <= aoMax2 * random.nextFloat())
464           continue;
465       }
466       rave += ptPsi.distance(center);
467       addVC(ptPsi, value, 0, true);
468       i++;
469     }
470     if (params.distance == 0)
471       Logger.info("Atomic Orbital mean radius = " + rave / monteCarloCount
472           + " for " + monteCarloCount + " points (" + nTries + " tries)");
473   }
474 
475   @Override
readSurfaceData(boolean isMapData)476   protected void readSurfaceData(boolean isMapData) throws Exception {
477     switch (params.dataType) {
478     case Parameters.SURFACE_ATOMICORBITAL:
479       if (monteCarloCount <= 0)
480         break;
481       createMonteCarloOrbital();
482       return;
483     case Parameters.SURFACE_LONEPAIR:
484     case Parameters.SURFACE_RADICAL:
485       ptPsi.set(0, 0, eccentricityScale / 2);
486       eccentricityMatrixInverse.rotate(ptPsi);
487       ptPsi.add(center);
488       addVC(center, 0, 0, true);
489       addVC(ptPsi, 0, 0, true);
490       addTriangleCheck(0, 0, 0, 0, 0, false, 0);
491       return;
492     case Parameters.SURFACE_GEODESIC:
493       if (!isMapData) {
494         createGeodesic();
495         return;
496       }
497     }
498     readSurfaceDataVDR(isMapData);
499   }
500 
createGeodesic()501   private void createGeodesic() {
502     MeshSurface ms = MeshSurface.getSphereData(4);
503     T3[] pts = ms.altVertices;
504     for (int i = 0; i < pts.length; i++) {
505       P3 pt = P3.newP(pts[i]);
506       pt.scale(params.distance);
507       pt.add(center);
508       addVC(pt, 0, i, false);
509     }
510     int[][] faces = ms.pis;
511     for (int i = 0; i < faces.length; i++) {
512       int[] face = faces[i];
513       addTriangleCheck(face[0], face[1], face[2], 7, 7, false, 0);
514     }
515   }
516 
517 }
518