1 /* $RCSfile$ 2 * $Author: hansonr $ 3 * $Date: 2020-05-26 14:23:08 -0500 (Tue, 26 May 2020) $ 4 * $Revision: 22014 $ 5 * 6 * Copyright (C) 2002-2005 The Jmol Development Team 7 * 8 * Contact: jmol-developers@lists.sf.net 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 Public 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.modelset; 25 26 import java.util.Map; 27 28 import org.jmol.api.JmolDataManager; 29 import org.jmol.atomdata.RadiusData; 30 import org.jmol.atomdata.RadiusData.EnumType; 31 import org.jmol.c.VDW; 32 import org.jmol.quantum.NMRCalculation; 33 import org.jmol.util.Escape; 34 import org.jmol.util.Point3fi; 35 import org.jmol.viewer.JC; 36 import org.jmol.viewer.Viewer; 37 38 import javajs.util.A4; 39 import javajs.util.Lst; 40 import javajs.util.Measure; 41 import javajs.util.P3; 42 import javajs.util.PT; 43 import javajs.util.SB; 44 45 public class Measurement { 46 47 /* 48 * a class to contain a single measurement. 49 * 50 */ 51 52 public String thisID; 53 public ModelSet ms; 54 public int index; 55 public boolean isVisible = true; 56 public boolean isHidden = false; 57 public boolean isTrajectory = false; 58 public boolean isValid = true; 59 public short colix; 60 public short labelColix = -1; // use colix 61 public int mad; 62 public TickInfo tickInfo; 63 public int traceX = Integer.MIN_VALUE, traceY; 64 65 public int count; 66 public int[] countPlusIndices = new int[5]; 67 public Point3fi[] pts; 68 public float value; 69 70 public String strFormat; 71 public String property; 72 public String units; 73 74 public Text text; 75 76 private Viewer vwr; 77 private String strMeasurement; 78 private String type; 79 80 // next three are used by MeaurementRenderer 81 82 private boolean tainted; 83 public A4 renderAxis; 84 public P3 renderArc; 85 private String newUnits; 86 public float fixedValue = Float.NaN; 87 private boolean isPending; 88 isTainted()89 public boolean isTainted() { 90 return (tainted && !(tainted = false)); 91 } 92 setM(ModelSet modelSet, Measurement m, float value, short colix, String strFormat, int index)93 public Measurement setM(ModelSet modelSet, Measurement m, float value, short colix, 94 String strFormat, int index) { 95 //value Float.isNaN ==> pending 96 this.ms = modelSet; 97 this.index = index; 98 this.vwr = modelSet.vwr; 99 this.colix = colix; 100 this.strFormat = strFormat; 101 if (m != null) { 102 tickInfo = m.tickInfo; 103 pts = m.pts; 104 mad = m.mad; 105 thisID = m.thisID; 106 text = m.text; 107 property = m.property; 108 units = m.units; 109 if (property == null && "+hz".equals(units)) { 110 property = "property_J"; 111 } 112 if (thisID != null && text != null) 113 labelColix = text.colix; 114 } 115 if (pts == null) 116 pts = new Point3fi[4]; 117 int[] indices = (m == null ? null : m.countPlusIndices); 118 count = (indices == null ? 0 : indices[0]); 119 if (count > 0) { 120 System.arraycopy(indices, 0, countPlusIndices, 0, count + 1); 121 isTrajectory = modelSet.isTrajectoryMeasurement(countPlusIndices); 122 } 123 isPending = Float.isNaN(value); 124 this.value = (isPending || isTrajectory ? getMeasurement(null) : value); 125 formatMeasurement(null); 126 return this; 127 } 128 setPoints(ModelSet modelSet, int[] indices, Point3fi[] points, TickInfo tickInfo)129 public Measurement setPoints(ModelSet modelSet, int[] indices, Point3fi[] points, 130 TickInfo tickInfo) { 131 // temporary holding structure only; -- no vwr 132 this.ms = modelSet; 133 countPlusIndices = indices; 134 count = indices[0]; 135 this.pts = (points == null ? new Point3fi[4] : points); 136 vwr = modelSet.vwr; 137 this.tickInfo = tickInfo; 138 return this; 139 } 140 setCount(int count)141 public void setCount(int count) { 142 setCountM(count); 143 } 144 setCountM(int count)145 protected void setCountM(int count) { 146 this.count = countPlusIndices[0] = count; 147 } 148 getAtomIndex(int n)149 public int getAtomIndex(int n) { 150 return (n > 0 && n <= count ? countPlusIndices[n] : -1); 151 } 152 getAtom(int n)153 public Point3fi getAtom(int n) { 154 int pt = countPlusIndices[n]; 155 return (pt < -1 ? pts[-2 - pt] : ms.at[pt]); 156 } 157 getLastIndex()158 public int getLastIndex() { 159 return (count > 0 ? countPlusIndices[count] : -1); 160 } 161 getString()162 public String getString() { 163 return strMeasurement; 164 } 165 166 @Override toString()167 public String toString() { 168 return getString(); 169 } 170 getStringUsing(Viewer vwr, String strFormat, String units)171 String getStringUsing(Viewer vwr, String strFormat, String units) { 172 this.vwr = vwr; 173 value = getMeasurement(null); 174 formatMeasurementAs(strFormat, units, true); 175 if (strFormat == null) 176 return getInfoAsString(units); 177 return strMeasurement; 178 } 179 getStringDetail()180 public String getStringDetail() { 181 return (count == 2 ? "Distance" : count == 3 ? "Angle" : "Torsion") 182 + getMeasurementScript(" - ", false) + " : " + value; 183 } 184 refresh(Point3fi[] pts)185 public void refresh(Point3fi[] pts) { 186 value = getMeasurement(pts); 187 isTrajectory = ms.isTrajectoryMeasurement(countPlusIndices); 188 formatMeasurement(null); 189 } 190 191 /** 192 * Used by MouseManager and Picking Manager to build the script 193 * 194 * @param sep 195 * @param withModelIndex is needed for points only 196 * @return measure ((1}) ({2}).... 197 */ getMeasurementScript(String sep, boolean withModelIndex)198 public String getMeasurementScript(String sep, boolean withModelIndex) { 199 SB sb = new SB(); 200 boolean asBitSet = (sep.equals(" ")); 201 for (int i = 1; i <= count; i++) 202 sb.append(i > 1 ? sep : " ").append(getLabel(i, asBitSet, withModelIndex)); 203 return sb.toString(); 204 } 205 formatMeasurementAs(String strFormat, String units, boolean useDefault)206 public void formatMeasurementAs(String strFormat, String units, 207 boolean useDefault) { 208 if (strFormat != null && strFormat.length() == 0) 209 strFormat = null; 210 if (!useDefault && strFormat != null 211 && strFormat.indexOf(countPlusIndices[0] + ":") != 0) 212 return; 213 this.strFormat = strFormat; 214 formatMeasurement(units); 215 } 216 formatMeasurement(String units)217 public void formatMeasurement(String units) { 218 tainted = true; 219 switch (Float.isNaN(value) ? 0 : count) { 220 default: 221 strMeasurement = null; 222 return; 223 case 2: 224 strMeasurement = formatDistance(units); 225 return; 226 case 3: 227 case 4: 228 strMeasurement = formatAngle(value); 229 return; 230 } 231 } 232 reformatDistanceIfSelected()233 public void reformatDistanceIfSelected() { 234 if (count != 2) 235 return; 236 if (vwr.slm.isSelected(countPlusIndices[1]) 237 && vwr.slm.isSelected(countPlusIndices[2])) 238 formatMeasurement(null); 239 } 240 241 /** 242 * 243 * @param units from MEASURE or measure() 244 * @return 245 */ formatDistance(String units)246 private String formatDistance(String units) { 247 String label = getLabelString(); 248 if (label == null) 249 return ""; 250 int pt = strFormat.indexOf("//"); 251 if (units == null) { 252 units = this.units; 253 if (units == null) { 254 if (pt >= 0) { 255 units = strFormat.substring(pt + 2); 256 strFormat = strFormat.substring(0, pt); 257 } else { 258 units = (property == null ? vwr.g.measureDistanceUnits : ""); 259 } 260 } 261 } else if (pt >= 0){ 262 strFormat = strFormat.substring(0, pt); 263 } 264 strFormat += "//" + units; 265 units = fixUnits(units); 266 pt = label.indexOf("//"); 267 if (pt >= 0) { 268 label = label.substring(0, pt); 269 if (label.length() == 0) 270 label = "%VALUE"; 271 } 272 float f = fixValue(units, (label.indexOf("%V") >= 0)); 273 return formatString(f, newUnits, label); 274 } 275 fixUnits(String units)276 private static String fixUnits(String units) { 277 if (units.equals("nanometers")) 278 return "nm"; 279 else if (units.equals("picometers")) 280 return "pm"; 281 else if (units.equals("angstroms")) 282 return "\u00C5"; 283 else if (units.equals("vanderwaals") || units.equals("vdw")) 284 return "%"; 285 return units; 286 } 287 288 /** 289 * 290 * @param units final units 291 * @param andRound 292 * @return float value 293 */ fixValue(String units, boolean andRound)294 public float fixValue(String units, boolean andRound) { 295 checkJ(units); 296 if(units != null && units.startsWith("+")) { 297 if (!isPending) 298 value = Math.abs(value); 299 units = units.substring(1); 300 } 301 newUnits = units; 302 if (count != 2) 303 return value; 304 float dist = value; 305 if (units == null && property != null) 306 units = ""; 307 if (units != null) { 308 boolean isPercent = units.equals("%"); 309 if (property == null && (isPercent || units.endsWith("hz"))) { 310 int i1 = getAtomIndex(1); 311 int i2 = getAtomIndex(2); 312 if (i1 >= 0 && i2 >= 0) { 313 Atom a1 = (Atom) getAtom(1); 314 Atom a2 = (Atom) getAtom(2); 315 int itype = nmrType(units); 316 boolean isDC = (!isPercent && itype == NMR_DC); 317 type = (isPercent ? "percent" : isDC ? "dipoleCouplingConstant" 318 : itype == NMR_NOE_OR_J ? "NOE or 3JHH" : "J-CouplingConstant"); 319 if (itype == NMR_NOE_OR_J) { 320 double[] result = vwr.getNMRCalculation().getNOEorJHH(new Atom[] { a1, null, null, a2}, NMRCalculation.MODE_CALC_NOE | NMRCalculation.MODE_CALC_JHH); 321 if (result == null) { 322 dist = Float.NaN; 323 newUnits = units = ""; 324 } else { 325 dist = (float) result[1]; 326 units = newUnits = (result.length == 2 ? "noe" :"hz"); 327 } 328 } else { 329 dist = (isPercent ? dist 330 / (a1.getVanderwaalsRadiusFloat(vwr, VDW.AUTO) + a2 331 .getVanderwaalsRadiusFloat(vwr, VDW.AUTO)) : isDC ? vwr 332 .getNMRCalculation().getDipolarConstantHz(a1, a2) : vwr 333 .getNMRCalculation().getIsoOrAnisoHz(true, a1, a2, units, null)); 334 } 335 isValid = !Float.isNaN(dist); 336 if (isPercent) 337 units = "pm"; 338 } 339 } 340 if (Float.isNaN(dist)) 341 return Float.NaN; 342 if (units.equals("hz")) 343 return (andRound ? Math.round(dist * 10) / 10f : dist); 344 if (units.equals("noe")) 345 return (andRound ? Math.round(dist * 100) / 100f : dist); 346 if (units.equals("nm")) 347 return (andRound ? Math.round(dist * 100) / 1000f : dist / 10); 348 if (units.equals("pm")) 349 return (andRound ? Math.round(dist * 1000) / 10f : dist * 100); 350 if (units.equals("au")) 351 return (andRound ? Math.round(dist / JC.ANGSTROMS_PER_BOHR * 1000) / 1000f 352 : dist / JC.ANGSTROMS_PER_BOHR); 353 if (units.endsWith("khz")) 354 return (andRound ? Math.round(dist / 10) / 100f : dist / 1000); 355 } 356 return (andRound ? Math.round(dist * 100) / 100f : dist); 357 } 358 checkJ(String units)359 private void checkJ(String units) { 360 if (property != null || units != null || this.units != null) 361 return; 362 // if (units == null && (units = this.units) == null) 363 units = vwr.g.measureDistanceUnits; 364 if ("+hz".equals(units)) { 365 property = "property_J"; 366 this.units = units; 367 } 368 } 369 370 public final static int NMR_NOT = 0; 371 public final static int NMR_DC = 1; 372 public final static int NMR_JC = 2; 373 public final static int NMR_NOE_OR_J = 3; 374 nmrType(String units)375 public static int nmrType(String units) { 376 return (units.indexOf("hz") < 0 ? NMR_NOT : units.equals("noe_hz") ? NMR_NOE_OR_J : units.startsWith("dc_") || units.equals("khz") ? NMR_DC : NMR_JC); 377 } 378 formatAngle(float angle)379 private String formatAngle(float angle) { 380 String label = getLabelString(); 381 if (label.indexOf("%V") >= 0) 382 angle = Math.round(angle * 10) / 10f; 383 return formatString(angle, "\u00B0", label); 384 } 385 getLabelString()386 private String getLabelString() { 387 String s = countPlusIndices[0] + ":"; 388 String label = null; 389 if (strFormat != null) { 390 if (strFormat.length() == 0) 391 return null; 392 label = (strFormat.length() > 2 && strFormat.indexOf(s) == 0 ? strFormat 393 : null); 394 } 395 if (label == null) { 396 strFormat = null; 397 label = vwr.getDefaultMeasurementLabel(countPlusIndices[0]); 398 } 399 if (label.indexOf(s) == 0) 400 label = label.substring(2); 401 if (strFormat == null) 402 strFormat = s + label; 403 return label; 404 } 405 formatString(float value, String units, String label)406 private String formatString(float value, String units, String label) { 407 return LabelToken.formatLabelMeasure(vwr, this, label, value, units); 408 } 409 sameAsPoints(int[] indices, Point3fi[] points)410 public boolean sameAsPoints(int[] indices, Point3fi[] points) { 411 if (count != indices[0]) 412 return false; 413 boolean isSame = true; 414 for (int i = 1; i <= count && isSame; i++) 415 isSame = (countPlusIndices[i] == indices[i]); 416 if (isSame) 417 for (int i = 0; i < count && isSame; i++) { 418 if (points[i] != null) 419 isSame = (this.pts[i].distance(points[i]) < 0.01); 420 } 421 if (isSame) 422 return true; 423 switch (count) { 424 default: 425 return true; 426 case 2: 427 return sameAsIJ(indices, points, 1, 2) && sameAsIJ(indices, points, 2, 1); 428 case 3: 429 return sameAsIJ(indices, points, 1, 3) && sameAsIJ(indices, points, 2, 2) 430 && sameAsIJ(indices, points, 3, 1); 431 case 4: 432 return sameAsIJ(indices, points, 1, 4) && sameAsIJ(indices, points, 2, 3) 433 && sameAsIJ(indices, points, 3, 2) && sameAsIJ(indices, points, 4, 1); 434 } 435 } 436 sameAsIJ(int[] atoms, Point3fi[] points, int i, int j)437 private boolean sameAsIJ(int[] atoms, Point3fi[] points, int i, int j) { 438 int ipt = countPlusIndices[i]; 439 int jpt = atoms[j]; 440 return (ipt >= 0 || jpt >= 0 ? ipt == jpt : this.pts[-2 - ipt] 441 .distance(points[-2 - jpt]) < 0.01); 442 } 443 sameAs(int i, int j)444 public boolean sameAs(int i, int j) { 445 return sameAsIJ(countPlusIndices, pts, i, j); 446 } 447 448 getPropMeasurement(Point3fi[] pts)449 public float getPropMeasurement(Point3fi[] pts) { 450 if (countPlusIndices == null || count != 2) 451 return Float.NaN; 452 for (int i = count; --i >= 0;) 453 if (countPlusIndices[i + 1] < 0) { 454 return Float.NaN; 455 } 456 try { 457 Atom ptA = (Atom) (pts == null ? getAtom(1) : pts[0]); 458 Atom ptB = (Atom) (pts == null ? getAtom(2) : pts[1]); 459 float[][] props = (float[][]) vwr.getDataObj(property, null, 460 JmolDataManager.DATA_TYPE_AFF); 461 int ia = ptA.i; 462 int ib = ptB.i; 463 return (props == null || ib >= props.length || ia >= props.length ? Float.NaN : props[ia][ib]); 464 } catch (Throwable t) { 465 return Float.NaN; 466 } 467 } 468 469 getMeasurement(Point3fi[] pts)470 public float getMeasurement(Point3fi[] pts) { 471 checkJ(null); 472 if (!Float.isNaN(fixedValue)) 473 return fixedValue; 474 if (property != null) 475 return getPropMeasurement(pts); 476 if (countPlusIndices == null) 477 return Float.NaN; 478 if (count < 2) 479 return Float.NaN; 480 for (int i = count; --i >= 0;) 481 if (countPlusIndices[i + 1] == -1) { 482 return Float.NaN; 483 } 484 Point3fi ptA = (pts == null ? getAtom(1) : pts[0]); 485 Point3fi ptB = (pts == null ? getAtom(2) : pts[1]); 486 Point3fi ptC; 487 switch (count) { 488 case 2: 489 return ptA.distance(ptB); 490 case 3: 491 ptC = (pts == null ? getAtom(3) : pts[2]); 492 return Measure.computeAngleABC(ptA, ptB, ptC, true); 493 case 4: 494 ptC = (pts == null ? getAtom(3) : pts[2]); 495 Point3fi ptD = (pts == null ? getAtom(4) : pts[3]); 496 return Measure.computeTorsion(ptA, ptB, ptC, ptD, true); 497 default: 498 return Float.NaN; 499 } 500 } 501 getLabel(int i, boolean asBitSet, boolean withModelIndex)502 public String getLabel(int i, boolean asBitSet, boolean withModelIndex) { 503 int atomIndex = countPlusIndices[i]; 504 // double parens USED TO BE here because of situations like 505 // draw symop({3}), which the compiler USED TO interpret as symop() 506 return ( 507 atomIndex < 0 ? (withModelIndex ? "modelIndex " 508 + getAtom(i).mi + " " : "") + Escape.eP(getAtom(i)) 509 : asBitSet ? "({" + atomIndex + "})" 510 :vwr.getAtomInfo(atomIndex) 511 ); 512 } 513 setModelIndex(short modelIndex)514 public void setModelIndex(short modelIndex) { 515 if (pts == null) 516 return; 517 for (int i = 0; i < count; i++) { 518 if (pts[i] != null) 519 pts[i].mi = modelIndex; 520 } 521 } 522 isValid()523 public boolean isValid() { 524 // valid: no A-A, A-B-A, A-B-C-B 525 return !(sameAs(1, 2) || count > 2 && sameAs(1, 3) || count == 4 526 && sameAs(2, 4)); 527 } 528 find(Lst<Measurement> measurements, Measurement m)529 public static int find(Lst<Measurement> measurements, Measurement m) { 530 int[] indices = m.countPlusIndices; 531 Point3fi[] points = m.pts; 532 for (int i = measurements.size(); --i >= 0;) 533 if (measurements.get(i).sameAsPoints(indices, points)) 534 return i; 535 return -1; 536 } 537 isConnected(Atom[] atoms, int count)538 public boolean isConnected(Atom[] atoms, int count) { 539 int atomIndexLast = -1; 540 for (int i = 1; i <= count; i++) { 541 int atomIndex = getAtomIndex(i); 542 if (atomIndex < 0) 543 continue; 544 if (atomIndexLast >= 0 545 && !atoms[atomIndex].isBonded(atoms[atomIndexLast])) 546 return false; 547 atomIndexLast = atomIndex; 548 } 549 return true; 550 } 551 getInfoAsString(String units)552 public String getInfoAsString(String units) { 553 float f = fixValue(units, true); 554 SB sb = new SB(); 555 sb.append(count == 2 ? (property != null ? property : type == null ? "distance" : type) : count == 3 ? "angle" : "dihedral"); 556 sb.append(" \t").appendF(f); 557 sb.append(" \t").append(PT.esc(strMeasurement)); 558 for (int i = 1; i <= count; i++) 559 sb.append(" \t").append(getLabel(i, false, false)); 560 if (thisID != null) 561 sb.append(" \t").append(thisID); 562 return sb.toString(); 563 } 564 isInRange(RadiusData radiusData, float value)565 public boolean isInRange(RadiusData radiusData, float value) { 566 if (radiusData.factorType == EnumType.FACTOR) { 567 Atom atom1 = (Atom) getAtom(1); 568 Atom atom2 = (Atom) getAtom(2); 569 float d = (atom1.getVanderwaalsRadiusFloat(vwr, radiusData.vdwType) + atom2 570 .getVanderwaalsRadiusFloat(vwr, radiusData.vdwType)) 571 * radiusData.value; 572 return (value <= d); 573 } 574 return (radiusData.values[0] == Float.MAX_VALUE || value >= radiusData.values[0] 575 && value <= radiusData.values[1]); 576 } 577 isIntramolecular(Atom[] atoms, int count)578 public boolean isIntramolecular(Atom[] atoms, int count) { 579 int molecule = -1; 580 for (int i = 1; i <= count; i++) { 581 int atomIndex = getAtomIndex(i); 582 if (atomIndex < 0) 583 continue; 584 int m = atoms[atomIndex].getMoleculeNumber(false); 585 if (molecule < 0) 586 molecule = m; 587 else if (m != molecule) 588 return false; 589 } 590 return true; 591 } 592 isMin(Map<String, Integer> htMin)593 public boolean isMin(Map<String, Integer> htMin) { 594 Atom a1 = (Atom) getAtom(1); 595 Atom a2 = (Atom) getAtom(2); 596 int d = (int) (a2.distanceSquared(a1)*100); 597 String n1 = a1.getAtomName(); 598 String n2 = a2.getAtomName(); 599 String key = (n1.compareTo(n2) < 0 ? n1 + n2 : n2 + n1); 600 Integer min = htMin.get(key); 601 return (min != null && d == min.intValue()); 602 } 603 isUnits(String s)604 public static boolean isUnits(String s) { 605 return (PT.isOneOf((s.startsWith("+") ? s.substring(1) : s).toLowerCase(), 606 ";nm;nanometers;pm;picometers;angstroms;angstroms;ang;\u00C5;au;vanderwaals;vdw;%;noe;") 607 || s.indexOf(" ") < 0 && s.endsWith("hz")); 608 } 609 610 } 611