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