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