1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2006-03-05 12:22:08 -0600 (Sun, 05 Mar 2006) $
4  * $Revision: 4545 $
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 
25 package org.jmol.scriptext;
26 
27 import java.io.BufferedInputStream;
28 import java.util.Date;
29 import java.util.HashMap;
30 import java.util.Hashtable;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Random;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 
37 import org.jmol.api.Interface;
38 import org.jmol.api.JmolDataManager;
39 import org.jmol.api.JmolNMRInterface;
40 import org.jmol.api.JmolPatternMatcher;
41 import org.jmol.api.SymmetryInterface;
42 import org.jmol.atomdata.RadiusData;
43 import org.jmol.atomdata.RadiusData.EnumType;
44 import org.jmol.bspt.PointIterator;
45 import org.jmol.c.VDW;
46 import org.jmol.i18n.GT;
47 import org.jmol.modelset.Atom;
48 import org.jmol.modelset.Bond;
49 import org.jmol.modelset.BondSet;
50 import org.jmol.modelset.Measurement;
51 import org.jmol.modelset.ModelSet;
52 import org.jmol.script.SV;
53 import org.jmol.script.ScriptEval;
54 import org.jmol.script.ScriptException;
55 import org.jmol.script.ScriptMathProcessor;
56 import org.jmol.script.ScriptParam;
57 import org.jmol.script.T;
58 import org.jmol.util.BSUtil;
59 import org.jmol.util.ColorEncoder;
60 import org.jmol.util.Edge;
61 import org.jmol.util.Escape;
62 import org.jmol.util.JmolMolecule;
63 import org.jmol.util.Logger;
64 import org.jmol.util.Parser;
65 import org.jmol.util.Point3fi;
66 import org.jmol.util.SimpleUnitCell;
67 import org.jmol.util.Tensor;
68 import org.jmol.viewer.FileManager;
69 import org.jmol.viewer.JC;
70 import org.jmol.viewer.Viewer;
71 
72 import javajs.util.AU;
73 import javajs.util.BS;
74 import javajs.util.CU;
75 import javajs.util.Lst;
76 import javajs.util.M3;
77 import javajs.util.M4;
78 import javajs.util.Measure;
79 import javajs.util.OC;
80 import javajs.util.P3;
81 import javajs.util.P4;
82 import javajs.util.PT;
83 import javajs.util.Quat;
84 import javajs.util.Rdr;
85 import javajs.util.SB;
86 import javajs.util.T3;
87 import javajs.util.V3;
88 
89 public class MathExt {
90 
91   private Viewer vwr;
92   private ScriptEval e;
93 
MathExt()94   public MathExt() {
95     // used by Reflection
96   }
97 
init(Object se)98   public MathExt init(Object se) {
99     e = (ScriptEval) se;
100     vwr = e.vwr;
101     return this;
102   }
103 
104   ///////////// ScriptMathProcessor extensions ///////////
105   private static long t0 = System.currentTimeMillis();
106 
evaluate(ScriptMathProcessor mp, T op, SV[] args, int tok)107   public boolean evaluate(ScriptMathProcessor mp, T op, SV[] args, int tok)
108       throws ScriptException {
109     switch (tok) {
110     case T.now:
111       return (args.length >= 1 && args[0].tok == T.string
112           ? mp.addXStr((args.length == 1 ? new Date().toString()
113               : vwr.apiPlatform.getDateFormat(SV.sValue(args[1]))) + "\t"
114               + SV.sValue(args[0]).trim())
115           : mp.addXInt(((int) (System.currentTimeMillis() - t0))
116               - (args.length == 0 ? 0 : args[0].asInt())));
117     case T.abs:
118       return (args.length == 1 && args[0].tok == T.integer
119           ? mp.addXInt(args[0].intValue)
120           : mp.addXFloat(Math.abs(args[0].asFloat())));
121     case T.acos:
122     case T.cos:
123     case T.sin:
124     case T.sqrt:
125       return (args.length == 1 && evaluateMath(mp, args, tok));
126     case T.add:
127     case T.div:
128     case T.mul:
129     case T.mul3:
130     case T.sub:
131     case T.push:
132     case T.pop:
133       return evaluateList(mp, op.intValue, args);
134     case T.leftsquare:
135       if (args.length == 0)
136         mp.wasX = false;
137       //$FALL-THROUGH$
138     case T.array:
139       return evaluateArray(mp, args,
140           tok == T.array && op.tok == T.propselector);
141     case T.axisangle:
142     case T.quaternion:
143       return evaluateQuaternion(mp, args, tok);
144     case T.bin:
145       return evaluateBin(mp, args);
146     case T.cache:
147       return evaluateCache(mp, args);
148     case T.col:
149     case T.row:
150       return evaluateRowCol(mp, args, tok);
151     case T.color:
152       return evaluateColor(mp, args);
153     case T.compare:
154       return evaluateCompare(mp, args);
155     case T.bondcount:
156     case T.connected:
157     case T.polyhedra:
158       return evaluateConnected(mp, args, tok, op.intValue);
159     case T.unitcell:
160       return evaluateUnitCell(mp, args, op.tok == T.propselector);
161     case T.contact:
162       return evaluateContact(mp, args);
163     case T.data:
164       return evaluateData(mp, args);
165     case T.dot:
166     case T.cross:
167       return evaluateDotDist(mp, args, tok, op.intValue);
168     case T.distance:
169       if (op.tok == T.propselector)
170         return evaluateDotDist(mp, args, tok, op.intValue);
171       //$FALL-THROUGH$
172     case T.angle:
173     case T.measure:
174       return evaluateMeasure(mp, args, op.tok);
175     case T.file:
176     case T.load:
177       return evaluateLoad(mp, args, tok == T.file);
178     case T.find:
179       return evaluateFind(mp, args);
180     case T.inchi:
181       return evaluateInChI(mp, args);
182     case T.format:
183     case T.label:
184       return evaluateFormat(mp, op.intValue, args, tok == T.label);
185     case T.function:
186       return evaluateUserFunction(mp, (String) op.value, args, op.intValue,
187           op.tok == T.propselector);
188     case T.__:
189     case T.pivot:
190     case T.select:
191     case T.getproperty:
192       return evaluateGetProperty(mp, args, tok, op.tok == T.propselector);
193     case T.helix:
194       return evaluateHelix(mp, args);
195     case T.hkl:
196     case T.plane:
197     case T.intersection:
198       return evaluatePlane(mp, args, tok);
199     case T.eval:
200     case T.javascript:
201     case T.script:
202     case T.show:
203       return evaluateScript(mp, args, tok);
204     case T.join:
205     case T.split:
206     case T.trim:
207       return evaluateString(mp, op.intValue, args);
208     case T.point:
209       return evaluatePoint(mp, args);
210     case T.pointgroup:
211       return evaluatePointGroup(mp, args);
212     case T.prompt:
213       return evaluatePrompt(mp, args);
214     case T.random:
215       return evaluateRandom(mp, args);
216     case T.in:
217       return evaluateIn(mp, args);
218     case T.modulation:
219       return evaluateModulation(mp, args);
220     case T.replace:
221       return evaluateReplace(mp, args);
222     case T.search:
223     case T.smiles:
224     case T.substructure:
225       return evaluateSubstructure(mp, args, tok, op.tok == T.propselector);
226     case T.sort:
227     case T.count:
228       return evaluateSort(mp, args, tok);
229     case T.spacegroup:
230       return evaluateSpacegroup(mp, args);
231     case T.symop:
232       return evaluateSymop(mp, args, op.tok == T.propselector);
233     //    case Token.volume:
234     //    return evaluateVolume(args);
235     case T.tensor:
236       return evaluateTensor(mp, args);
237     case T.within:
238       return evaluateWithin(mp, args);
239     case T.write:
240       return evaluateWrite(mp, args);
241     }
242     return false;
243   }
244 
evaluateSpacegroup(ScriptMathProcessor mp, SV[] args)245   private boolean evaluateSpacegroup(ScriptMathProcessor mp, SV[] args) {
246     // spacegroup();
247     // spacegroup(3);
248     // spacegroup("x,y,z,-x,-y,-z");
249     // spacegroup("x,y,z,-x,-y,-z", [unitcellParams]);
250 
251     float[] unitCell = null;
252     switch (args.length) {
253     case 0:
254       return mp.addXObj(vwr.getSymTemp().getSpaceGroupInfo(vwr.ms, null,
255           vwr.am.cmi, true, null));
256     case 2:
257       unitCell = SV.flistValue(args[1], 0);
258       if (unitCell.length < 6)
259         unitCell = null;
260       //$FALL-THROUGH$
261     case 1:
262       return mp.addXObj(vwr.getSymTemp().getSpaceGroupInfo(vwr.ms,
263           "" + args[0].asString(), Integer.MIN_VALUE, true, unitCell));
264     default:
265       return false;
266     }
267   }
268 
269   @SuppressWarnings("unchecked")
evaluatePointGroup(ScriptMathProcessor mp, SV[] args)270   private boolean evaluatePointGroup(ScriptMathProcessor mp, SV[] args) {
271     // pointGroup(points)
272     // pointGroup(points, center)
273     // pointGroup(points, center, distanceTolerance (def. 0.2 A), linearTolerance (def. 8 deg.)
274     // center can be non-point to ignore, such as 0 or ""
275     T3[] pts = null;
276     P3 center = null;
277     float distanceTolerance = Float.NaN;
278     float linearTolerance = Float.NaN;
279     BS bsAtoms;
280     switch (args.length) {
281     case 4:
282       linearTolerance = args[3].asFloat();
283       //$FALL-THROUGH$
284     case 3:
285       distanceTolerance = args[2].asFloat();
286       //$FALL-THROUGH$
287     case 2:
288       switch (args[1].tok) {
289       case T.point3f:
290         center = SV.ptValue(args[1]);
291         break;
292       case T.bitset:
293         // pointgroup {vertices} {center}
294         bsAtoms = SV.getBitSet(args[1], false);
295         int iatom = bsAtoms.nextSetBit(0);
296         if (iatom < 0 || iatom >= vwr.ms.ac || bsAtoms.cardinality() != 1)
297           return false;
298         if (SV.sValue(args[0]).equalsIgnoreCase("spaceGroup")) {
299           // pointgroup("spaceGroup", @1)
300           Lst<P3> lst = vwr.ms.generateCrystalClass(iatom,
301               P3.new3(Float.NaN, Float.NaN, Float.NaN));
302           pts = new T3[lst.size()];
303           for (int i = pts.length; --i >= 0;)
304             pts[i] = lst.get(i);
305           center = new P3();
306           if (args.length == 2)
307             distanceTolerance = 0; // will set tolerances especially tight
308         } else {
309           center = vwr.ms.at[iatom];
310         }
311       }
312       if (pts != null)
313         break;
314       //$FALL-THROUGH$
315     case 1:
316       switch (args[0].tok) {
317       case T.varray:
318         Lst<SV> points = args[0].getList();
319         pts = new T3[points.size()];
320         for (int i = pts.length; --i >= 0;)
321           pts[i] = SV.ptValue(points.get(i));
322         break;
323       case T.bitset:
324         bsAtoms = SV.getBitSet(args[0], false);
325         Lst<P3> atoms = vwr.ms.getAtomPointVector(bsAtoms);
326         pts = new T3[atoms.size()];
327         for (int i = pts.length; --i >= 0;)
328           pts[i] = atoms.get(i);
329         break;
330       default:
331         return false;
332       }
333       break;
334     case 0:
335       return mp.addXObj(vwr.ms.getPointGroupInfo(null));
336     default:
337       return false;
338     }
339     SymmetryInterface pointGroup = vwr.getSymTemp().setPointGroup(null, center,
340         pts, null, false,
341         Float.isNaN(distanceTolerance)
342             ? vwr.getFloat(T.pointgroupdistancetolerance)
343             : distanceTolerance,
344         Float.isNaN(linearTolerance) ? vwr.getFloat(T.pointgrouplineartolerance)
345             : linearTolerance,
346         true);
347     return mp.addXMap((Map<String, ?>) pointGroup.getPointGroupInfo(-1, null,
348         true, null, 0, 1));
349   }
350 
evaluateUnitCell(ScriptMathProcessor mp, SV[] args, boolean isSelector)351   private boolean evaluateUnitCell(ScriptMathProcessor mp, SV[] args,
352                                    boolean isSelector)
353       throws ScriptException {
354     // optional last parameter: scale
355     // unitcell("-a,-b,c;0,0,0.50482") (polar groups can have irrational translations along z)
356     // unitcell(uc)
357     // unitcell(uc, "reciprocal")
358     // unitcell(origin, [va, vb, vc])
359     // unitcell(origin, pta, ptb, ptc)
360 
361     // next can be without {1.1}, but then assume "all atoms"
362     // {1.1}.unitcell()
363     // {1.1}.unitcell(ucconv, "primitive","BCC"|"FCC")
364     // {1.1}.unitcell(ucprim, "conventional","BCC"|"FCC")
365 
366     BS x1 = (isSelector ? SV.getBitSet(mp.getX(), true) : null);
367     int iatom = ((x1 == null ? vwr.getAllAtoms() : x1).nextSetBit(0));
368     int lastParam = args.length - 1;
369     float scale = 1;
370     switch (lastParam < 0 ? T.nada : args[lastParam].tok) {
371     case T.integer:
372     case T.decimal:
373       scale = args[lastParam].asFloat();
374       lastParam--;
375       break;
376     }
377     int tok0 = (lastParam < 0 ? T.nada : args[0].tok);
378     T3[] ucnew = null;
379     Lst<SV> uc = null;
380     switch (tok0) {
381     case T.varray:
382       uc = args[0].getList();
383       break;
384     case T.string:
385       String s = args[0].asString();
386       if (s.indexOf("a=") == 0) {
387         ucnew = new P3[4];
388         for (int i = 0; i < 4; i++)
389           ucnew[i] = new P3();
390         SimpleUnitCell.setOabc(s, null, ucnew);
391       } else if (s.indexOf(",") >= 0) {
392         return mp.addXObj(vwr.getV0abc(-1, s));
393       }
394       break;
395     }
396     SymmetryInterface u = null;
397     boolean haveUC = (uc != null);
398     if (ucnew == null && haveUC && uc.size() < 4)
399       return false;
400     int ptParam = (haveUC ? 1 : 0);
401     if (ucnew == null && !haveUC && tok0 != T.point3f) {
402       // unitcell() or {1.1}.unitcell
403       u = (iatom < 0 ? null : vwr.ms.getUnitCell(vwr.ms.at[iatom].mi));
404       ucnew = (u == null
405           ? new P3[] { P3.new3(0, 0, 0), P3.new3(1, 0, 0), P3.new3(0, 1, 0),
406               P3.new3(0, 0, 1) }
407           : u.getUnitCellVectors());
408     }
409     if (ucnew == null) {
410       ucnew = new P3[4];
411       if (haveUC) {
412         switch (uc.size()) {
413         case 3:
414           // [va. vb. vc]
415           ucnew[0] = new P3();
416           for (int i = 0; i < 3; i++)
417             ucnew[i + 1] = P3.newP(SV.ptValue(uc.get(i)));
418           break;
419         case 4:
420           for (int i = 0; i < 4; i++)
421             ucnew[i] = P3.newP(SV.ptValue(uc.get(i)));
422           break;
423         case 6:
424           // unitcell([a b c alpha beta gamma])
425           float[] params = new float[6];
426           for (int i = 0; i < 6; i++)
427             params[i] = uc.get(i).asFloat();
428           SimpleUnitCell.setOabc(null, params, ucnew);
429           break;
430         default:
431           return false;
432         }
433       } else {
434         ucnew[0] = SV.ptValue(args[0]);
435         switch (lastParam) {
436         case 3:
437           // unitcell(origin, pa, pb, pc)
438           for (int i = 1; i < 4; i++)
439             (ucnew[i] = P3.newP(SV.ptValue(args[i]))).sub(ucnew[0]);
440           break;
441         case 1:
442           // unitcell(origin, [va, vb, vc])
443           Lst<SV> l = args[1].getList();
444           if (l != null && l.size() == 3) {
445             for (int i = 0; i < 3; i++)
446               ucnew[i + 1] = SV.ptValue(l.get(i));
447             break;
448           }
449           //$FALL-THROUGH$
450         default:
451           return false;
452         }
453       }
454     }
455 
456     String op = (ptParam <= lastParam ? args[ptParam].asString() : null);
457     boolean toPrimitive = "primitive".equalsIgnoreCase(op);
458     if (toPrimitive || "conventional".equalsIgnoreCase(op)) {
459       String stype = (++ptParam > lastParam ? ""
460           : args[ptParam].asString().toUpperCase());
461       if (stype.equals("BCC"))
462         stype = "I";
463       else if (stype.length() == 0)
464         stype = (String) vwr.getSymmetryInfo(iatom, null, 0, null, null,
465             null, T.lattice, null, 0, -1, 0);
466       if (stype == null || stype.length() == 0)
467         return false;
468       if (u == null)
469         u = vwr.getSymTemp();
470       M3 m3 = (M3) vwr.getModelForAtomIndex(iatom).auxiliaryInfo
471           .get("primitiveToCrystal");
472       if (!u.toFromPrimitive(toPrimitive, stype.charAt(0), ucnew, m3))
473         return false;
474     } else if ("reciprocal".equalsIgnoreCase(op)) {
475       ucnew = SimpleUnitCell.getReciprocal(ucnew, null, scale);
476       scale = 1;
477     }
478     if (scale != 1)
479       for (int i = 1; i < 4; i++)
480         ucnew[i].scale(scale);
481     return mp.addXObj(ucnew);
482   }
483 
484   @SuppressWarnings("unchecked")
evaluateArray(ScriptMathProcessor mp, SV[] args, boolean isSelector)485   private boolean evaluateArray(ScriptMathProcessor mp, SV[] args,
486                                 boolean isSelector)
487       throws ScriptException {
488     if (isSelector) {
489       SV x1 = mp.getX();
490       switch (args.length == 1 ? x1.tok : T.nada) {
491       case T.hash:
492         // map of maps to lst of maps
493         Lst<SV> lst = new Lst<SV>();
494         String id = args[0].asString();
495         Map<String, SV> map = x1.getMap();
496         String[] keys = x1.getKeys(false);
497         // values must all be maps
498         for (int i = 0, n = keys.length; i < n; i++)
499           if (map.get(keys[i]).getMap() == null)
500             return false;
501         for (int i = 0, n = keys.length; i < n; i++) {
502           SV m = map.get(keys[i]);
503           Map<String, SV> m1 = m.getMap();
504           Map<String, SV> m2 = (Map<String, SV>) SV.deepCopy(m1, true, false);
505           m2.put(id, SV.newS(keys[i]));
506           lst.addLast(SV.newV(T.hash, m2));
507         }
508         return mp.addXList(lst);
509       case T.varray:
510         Map<String, SV> map1 = new Hashtable<String, SV>();
511         Lst<SV> lst1 = x1.getList();
512         String id1 = args[0].asString();
513         // values must all be maps
514         for (int i = 0, n = lst1.size(); i < n; i++) {
515           Map<String, SV> m0 = lst1.get(i).getMap();
516           if (m0 == null || m0.get(id1) == null)
517             return false;
518         }
519         for (int i = 0, n = lst1.size(); i < n; i++) {
520           SV m = lst1.get(i);
521           Map<String, SV> m1 = (Map<String, SV>) SV.deepCopy(m.getMap(), true,
522               false);
523           SV mid = m1.remove(id1);
524           map1.put(mid.asString(), SV.newV(T.hash, m1));
525         }
526         return mp.addXObj(map1);
527       }
528       return false;
529     }
530     SV[] a = new SV[args.length];
531     for (int i = a.length; --i >= 0;)
532       a[i] = SV.newT(args[i]);
533     return mp.addXAV(a);
534   }
535 
evaluateBin(ScriptMathProcessor mp, SV[] args)536   private boolean evaluateBin(ScriptMathProcessor mp, SV[] args)
537       throws ScriptException {
538     int n = args.length;
539     if (n < 3 || n > 5)
540       return false;
541     SV x1 = mp.getX();
542     boolean isListf = (x1.tok == T.listf);
543     if (!isListf && x1.tok != T.varray)
544       return mp.addX(x1);
545     float f0 = SV.fValue(args[0]);
546     float f1 = SV.fValue(args[1]);
547     float df = SV.fValue(args[2]);
548     boolean addBins = (n >= 4 && args[n - 1].tok == T.on);
549     String key = ((n == 5 || n == 4 && !addBins) && args[3].tok != T.off
550         ? SV.sValue(args[3])
551         : null);
552     float[] data;
553     Map<String, SV>[] maps = null;
554     if (isListf) {
555       data = (float[]) x1.value;
556     } else {
557       Lst<SV> list = x1.getList();
558       data = new float[list.size()];
559       if (key != null)
560         maps = AU.createArrayOfHashtable(list.size());
561       try {
562         for (int i = list.size(); --i >= 0;)
563           data[i] = SV.fValue(key == null ? list.get(i)
564               : (maps[i] = list.get(i).getMap()).get(key));
565       } catch (Exception e) {
566         return false;
567       }
568     }
569     int nbins = Math.max((int) Math.floor((f1 - f0) / df + 0.01f), 1);
570     int[] array = new int[nbins];
571     int nPoints = data.length;
572     for (int i = 0; i < nPoints; i++) {
573       float v = data[i];
574       int bin = (int) Math.floor((v - f0) / df);
575       if (bin < 0 || bin >= nbins)
576         continue;
577       array[bin]++;
578       if (key != null) {
579         Map<String, SV> map = maps[i];
580         if (map == null)
581           continue;
582         map.put("_bin", SV.newI(bin));
583         float v1 = f0 + df * bin;
584         float v2 = v1 + df;
585         map.put("_binMin", SV.newF(bin == 0 ? -Float.MAX_VALUE : v1));
586         map.put("_binMax", SV.newF(bin == nbins - 1 ? Float.MAX_VALUE : v2));
587       }
588     }
589     if (addBins) {
590       Lst<float[]> lst = new Lst<float[]>();
591       for (int i = 0; i < nbins; i++)
592         lst.addLast(new float[] { f0 + df * i, array[i] });
593       return mp.addXList(lst);
594     }
595     return mp.addXAI(array);
596   }
597 
evaluateCache(ScriptMathProcessor mp, SV[] args)598   private boolean evaluateCache(ScriptMathProcessor mp, SV[] args) {
599     if (args.length > 0)
600       return false;
601     return mp.addXMap(vwr.fm.cacheList());
602   }
603 
evaluateColor(ScriptMathProcessor mp, SV[] args)604   private boolean evaluateColor(ScriptMathProcessor mp, SV[] args) {
605     // color("toHSL", {r g b})         # r g b in 0 to 255 scale
606     // color("toRGB", "colorName or hex code") # r g b in 0 to 255 scale
607     // color("toRGB", {h s l})         # h s l in 360, 100, 100 scale
608     // color("rwb")                  # "" for most recently used scheme for coloring by property
609     // color("rwb", min, max)        # min/max default to most recent property mapping
610     // color("rwb", min, max, value) # returns color
611     // color("$isosurfaceId")        # info for a given isosurface
612     // color("$isosurfaceId", value) # color for a given mapped isosurface value
613     // color(ptColor1, ptColor2, n, asHSL)
614 
615     String colorScheme = (args.length > 0 ? SV.sValue(args[0]) : "");
616     boolean isIsosurface = colorScheme.startsWith("$");
617     if (args.length == 2 && colorScheme.equalsIgnoreCase("TOHSL"))
618       return mp.addXPt(
619           CU.rgbToHSL(P3.newP(args[1].tok == T.point3f ? SV.ptValue(args[1])
620               : CU.colorPtFromString(args[1].asString())), true));
621     if (args.length == 2 && colorScheme.equalsIgnoreCase("TORGB")) {
622       P3 pt = P3.newP(args[1].tok == T.point3f ? SV.ptValue(args[1])
623           : CU.colorPtFromString(args[1].asString()));
624       return mp.addXPt(args[1].tok == T.point3f ? CU.hslToRGB(pt) : pt);
625     }
626     if (args.length == 4 && (args[3].tok == T.on || args[3].tok == T.off)) {
627       P3 pt1 = P3.newP(args[0].tok == T.point3f ? SV.ptValue(args[0])
628           : CU.colorPtFromString(args[0].asString()));
629       P3 pt2 = P3.newP(args[1].tok == T.point3f ? SV.ptValue(args[1])
630           : CU.colorPtFromString(args[1].asString()));
631       boolean usingHSL = (args[3].tok == T.on);
632       if (usingHSL) {
633         pt1 = CU.rgbToHSL(pt1, false);
634         pt2 = CU.rgbToHSL(pt2, false);
635       }
636 
637       SB sb = new SB();
638       V3 vd = V3.newVsub(pt2, pt1);
639       int n = args[2].asInt();
640       if (n < 2)
641         n = 20;
642       vd.scale(1f / (n - 1));
643       for (int i = 0; i < n; i++) {
644         sb.append(Escape
645             .escapeColor(CU.colorPtToFFRGB(usingHSL ? CU.hslToRGB(pt1) : pt1)));
646         pt1.add(vd);
647       }
648       return mp.addXStr(sb.toString());
649     }
650 
651     ColorEncoder ce = (isIsosurface ? null
652         : vwr.cm.getColorEncoder(colorScheme));
653     if (!isIsosurface && ce == null)
654       return mp.addXStr("");
655     float lo = (args.length > 1 ? SV.fValue(args[1]) : Float.MAX_VALUE);
656     float hi = (args.length > 2 ? SV.fValue(args[2]) : Float.MAX_VALUE);
657     float value = (args.length > 3 ? SV.fValue(args[3]) : Float.MAX_VALUE);
658     boolean getValue = (value != Float.MAX_VALUE
659         || lo != Float.MAX_VALUE && hi == Float.MAX_VALUE);
660     boolean haveRange = (hi != Float.MAX_VALUE);
661     if (!haveRange && colorScheme.length() == 0) {
662       value = lo;
663       float[] range = vwr.getCurrentColorRange();
664       lo = range[0];
665       hi = range[1];
666     }
667     if (isIsosurface) {
668       // isosurface color scheme
669       String id = colorScheme.substring(1);
670       Object[] data = new Object[] { id, null };
671       if (!vwr.shm.getShapePropertyData(JC.SHAPE_ISOSURFACE, "colorEncoder",
672           data))
673         return mp.addXStr("");
674       ce = (ColorEncoder) data[1];
675     } else {
676       ce.setRange(lo, hi, lo > hi);
677     }
678     Map<String, Object> key = ce.getColorKey();
679     if (getValue)
680       return mp.addXPt(CU.colorPtFromInt(
681           ce.getArgb(hi == Float.MAX_VALUE ? lo : value), null));
682     return mp.addX(SV.getVariableMap(key));
683   }
684 
evaluateCompare(ScriptMathProcessor mp, SV[] args)685   private boolean evaluateCompare(ScriptMathProcessor mp, SV[] args)
686       throws ScriptException {
687     // compare([{bitset} or {positions}],[{bitset} or {positions}] [,"stddev"])
688     // compare({bitset},{bitset}[,"SMARTS"|"SMILES"],smilesString [,"stddev"])
689     // returns matrix4f for rotation/translation or stddev
690     // compare({bitset},{bitset},"ISOMER")  12.1.5
691     // compare({bitset},{bitset},smartsString, "BONDS") 13.1.17
692     // compare({bitset},{bitset},"SMILES", "BONDS") 13.3.9
693     // compare({bitest},{bitset},"MAP","smilesString")
694     // compare({bitset},{bitset},"MAP")
695     // compare(@1,@2,"SMILES", "polyhedra"[,"stddev"])
696     // compare(@1,@2,"SMARTS", "polyhedra"[,"stddev"])
697 
698     if (args.length < 2 || args.length > 5)
699       return false;
700     float stddev;
701     String sOpt = SV.sValue(args[args.length - 1]);
702     boolean isStdDev = sOpt.equalsIgnoreCase("stddev");
703     boolean isIsomer = sOpt.equalsIgnoreCase("ISOMER");
704     boolean isBonds = sOpt.equalsIgnoreCase("BONDS");
705     boolean isPoints = (args[0].tok == T.varray && args[1].tok == T.varray);
706     boolean isSmiles = (!isPoints && !isIsomer
707         && args.length > (isStdDev ? 3 : 2));
708     BS bs1 = (args[0].tok == T.bitset ? (BS) args[0].value : null);
709     BS bs2 = (args[1].tok == T.bitset ? (BS) args[1].value : null);
710     String smiles1 = (bs1 == null ? SV.sValue(args[0]) : "");
711     String smiles2 = (bs2 == null ? SV.sValue(args[1]) : "");
712     stddev = Float.NaN;
713     try {
714       if (isBonds) {
715         if (args.length != 4)
716           return false;
717         // A, B, ........................BONDS
718         // A, B, SMILES,...................BONDS
719         smiles1 = SV.sValue(args[2]);
720         isSmiles = smiles1.equalsIgnoreCase("SMILES");
721         try {
722           if (isSmiles)
723             smiles1 = vwr.getSmiles(bs1);
724         } catch (Exception ex) {
725           e.evalError(ex.getMessage(), null);
726         }
727         float[] data = e.getSmilesExt().getFlexFitList(bs1, bs2, smiles1,
728             !isSmiles);
729         return (data == null ? mp.addXStr("") : mp.addXAF(data));
730       }
731       if (isIsomer) {
732         if (args.length != 3)
733           return false;
734 
735         // A, B, ISOMER
736 
737         if (bs1 == null && bs2 == null)
738           return mp.addXStr(vwr.getSmilesMatcher()
739               .getRelationship(smiles1, smiles2).toUpperCase());
740         String mf1 = (bs1 == null
741             ? vwr.getSmilesMatcher().getMolecularFormula(smiles1, false)
742             : JmolMolecule.getMolecularFormulaAtoms(vwr.ms.at, bs1, null,
743                 false));
744         String mf2 = (bs2 == null
745             ? vwr.getSmilesMatcher().getMolecularFormula(smiles2, false)
746             : JmolMolecule.getMolecularFormulaAtoms(vwr.ms.at, bs2, null,
747                 false));
748         if (!mf1.equals(mf2))
749           return mp.addXStr("NONE");
750         if (bs1 != null)
751           smiles1 = (String) e.getSmilesExt().getSmilesMatches("/strict///",
752               null, bs1, null, JC.SMILES_TYPE_SMILES, true, false);
753         boolean check;
754         if (bs2 == null) {
755           // note: find smiles1 IN smiles2 here
756           check = (vwr.getSmilesMatcher().areEqual(smiles2, smiles1) > 0);
757         } else {
758           smiles2 = (String) e.getSmilesExt().getSmilesMatches("/strict///",
759               null, bs2, null, JC.SMILES_TYPE_SMILES, true, false);
760           check = (((BS) e.getSmilesExt().getSmilesMatches(
761               "/strict///" + smiles1, null, bs2, null, JC.SMILES_TYPE_SMILES,
762               true, false)).nextSetBit(0) >= 0);
763         }
764         if (!check) {
765           // MF matched, but didn't match SMILES
766           String s = smiles1 + smiles2;
767           if (s.indexOf("/") >= 0 || s.indexOf("\\") >= 0
768               || s.indexOf("@") >= 0) {
769             if (smiles1.indexOf("@") >= 0
770                 && (bs2 != null || smiles2.indexOf("@") >= 0)
771                 && smiles1.indexOf("@SP") < 0) {
772               // reverse chirality centers to see if we have an enantiomer
773               // but only if not square planar
774               int pt = smiles1.toLowerCase().indexOf("invertstereo");
775               smiles1 = (pt >= 0
776                   ? "/strict/" + smiles1.substring(0, pt)
777                       + smiles1.substring(pt + 12)
778                   : "/invertstereo strict/" + smiles1);//vwr.getSmilesMatcher().reverseChirality(smiles1);
779 
780               if (bs2 == null) {
781                 check = (vwr.getSmilesMatcher().areEqual(smiles1, smiles2) > 0);
782               } else {
783                 check = (((BS) e.getSmilesExt().getSmilesMatches(smiles1, null,
784                     bs2, null, JC.SMILES_TYPE_SMILES, true, false))
785                         .nextSetBit(0) >= 0);
786               }
787               if (check)
788                 return mp.addXStr("ENANTIOMERS");
789             }
790             // remove all stereochemistry from SMILES string
791             if (bs2 == null) {
792               check = (vwr.getSmilesMatcher().areEqual("/nostereo/" + smiles2,
793                   smiles1) > 0);
794             } else {
795               Object ret = e.getSmilesExt().getSmilesMatches(
796                   "/nostereo/" + smiles1, null, bs2, null,
797                   JC.SMILES_TYPE_SMILES, true, false);
798               check = (((BS) ret).nextSetBit(0) >= 0);
799             }
800             if (check)
801               return mp.addXStr("DIASTEREOMERS");
802           }
803           // MF matches, but not enantiomers or diastereomers
804           return mp.addXStr("CONSTITUTIONAL ISOMERS");
805         }
806         //identical or conformational
807         if (bs1 == null || bs2 == null)
808           return mp.addXStr("IDENTICAL");
809         stddev = e.getSmilesExt().getSmilesCorrelation(bs1, bs2, smiles1, null,
810             null, null, null, false, null, null, false, JC.SMILES_TYPE_SMILES);
811         return mp.addXStr(stddev < 0.2f ? "IDENTICAL"
812             : "IDENTICAL or CONFORMATIONAL ISOMERS (RMSD=" + stddev + ")");
813       }
814       M4 m = new M4();
815       Lst<P3> ptsA = null, ptsB = null;
816       if (isSmiles) {
817         // A, B, MAP
818         // A, B, MAP, [pattern "H" "allH" "bestH"], ["stddev"]
819         // A, B, SMILES, [pattern "H" "allH" "bestH"], ["stddev"]
820         // A, B, SMARTS, pattern, ["stddev"]
821         // A, B, SMILES, "polyhedron", [pattern "all" "best"], ["stddev"]
822         // A, B, SMARTS, "polyhedron", pattern, ["all" "best"], ["stddev"]
823         if (bs1 == null || bs2 == null)
824           return false;
825         sOpt = SV.sValue(args[2]);
826         boolean isMap = sOpt.equalsIgnoreCase("MAP");
827         isSmiles = sOpt.equalsIgnoreCase("SMILES");
828         boolean isSearch = (isMap || sOpt.equalsIgnoreCase("SMARTS"));
829         if (isSmiles || isSearch)
830           sOpt = (args.length > (isStdDev ? 4 : 3) ? SV.sValue(args[3]) : null);
831 
832         // sOpts = "H", "allH", "bestH", "polyhedra", pattern, or null
833         boolean hMaps = (("H".equalsIgnoreCase(sOpt)
834             || "allH".equalsIgnoreCase(sOpt)
835             || "bestH".equalsIgnoreCase(sOpt)));
836 
837         boolean isPolyhedron = !hMaps && ("polyhedra".equalsIgnoreCase(sOpt)
838             || "polyhedron".equalsIgnoreCase(sOpt));
839         if (isPolyhedron) {
840           stddev = e.getSmilesExt().mapPolyhedra(bs1.nextSetBit(0),
841               bs2.nextSetBit(0), isSmiles, m);
842         } else {
843           ptsA = new Lst<P3>();
844           ptsB = new Lst<P3>();
845           boolean allMaps = (("all".equalsIgnoreCase(sOpt)
846               || "allH".equalsIgnoreCase(sOpt)));
847           boolean bestMap = (("best".equalsIgnoreCase(sOpt)
848               || "bestH".equalsIgnoreCase(sOpt)));
849           if ("stddev".equals(sOpt))
850             sOpt = null;
851           String pattern = sOpt;
852           if (sOpt == null || hMaps || allMaps || bestMap) {
853             // with explicitH we set to find only the first match.
854             if (!isMap && !isSmiles || hMaps && isPolyhedron)
855               return false;
856             pattern = "/noaromatic" + (allMaps || bestMap ? "/" : " nostereo/")
857                 + e.getSmilesExt().getSmilesMatches((hMaps ? "H" : ""), null,
858                     bs1, null, JC.SMILES_TYPE_SMILES, true, false);
859           } else {
860             allMaps = true;
861           }
862           stddev = e.getSmilesExt().getSmilesCorrelation(bs1, bs2, pattern,
863               ptsA, ptsB, m, null, isMap, null, null, bestMap,
864               (isSmiles ? JC.SMILES_TYPE_SMILES : JC.SMILES_TYPE_SMARTS)
865                   | (!allMaps && !bestMap ? JC.SMILES_FIRST_MATCH_ONLY : 0));
866           if (isMap) {
867             int nAtoms = ptsA.size();
868             if (nAtoms == 0)
869               return mp.addXStr("");
870             int nMatch = ptsB.size() / nAtoms;
871             Lst<int[][]> ret = new Lst<int[][]>();
872             for (int i = 0, pt = 0; i < nMatch; i++) {
873               int[][] a = AU.newInt2(nAtoms);
874               ret.addLast(a);
875               for (int j = 0; j < nAtoms; j++, pt++)
876                 a[j] = new int[] { ((Atom) ptsA.get(j)).i,
877                     ((Atom) ptsB.get(pt)).i };
878             }
879             return (allMaps ? mp.addXList(ret)
880                 : ret.size() > 0 ? mp.addXAII(ret.get(0)) : mp.addXStr(""));
881           }
882         }
883       }
884       if (isPoints) {
885         // A, B
886         // A, B, stddev
887         // A, B, int[] map
888         // A, B, int[] map, stddev
889         ptsA = e.getPointVector(args[0], 3);
890         ptsB = e.getPointVector(args[1], 3);
891         Lst<SV> a = (args.length >= 3 ? args[2].getList() : null);
892         int na = args.length;
893         if (a != null) {
894           na--;
895           int n = a.size();
896           if (n != ptsA.size() || n != ptsB.size())
897             return false;
898           Lst<P3> list = new Lst<P3>();
899           for (int i = 0; i < n; i++)
900             list.addLast(ptsA.get(a.get(i).intValue));
901           ptsA = list;
902         }
903         switch (na) {
904         case 2:
905           break;
906         case 3:
907           if (isStdDev)
908             break;
909           //$FALL-THROUGH$
910         default:
911           return false;
912         }
913         if (ptsA != null && ptsB != null && ptsA.size() == ptsB.size()) {
914           Interface.getInterface("javajs.util.Eigen", vwr, "script");
915           stddev = Measure.getTransformMatrix4(ptsA, ptsB, m, null);
916         }
917       }
918       // now have m and stddev
919       return (isStdDev || Float.isNaN(stddev) ? mp.addXFloat(stddev)
920           : mp.addXM4(m.round(1e-7f)));
921     } catch (Exception ex) {
922       e.evalError(ex.getMessage() == null ? ex.toString() : ex.getMessage(),
923           null);
924       return false;
925     }
926   }
927 
evaluateConnected(ScriptMathProcessor mp, SV[] args, int tok, int intValue)928   private boolean evaluateConnected(ScriptMathProcessor mp, SV[] args, int tok,
929                                     int intValue)
930       throws ScriptException {
931     /*
932      * Several options here:
933      *
934      * .bondCount({_Au})
935      *
936      * connected(1, 3, "single", {carbon})
937      *
938      * means "atoms connected to carbon by from 1 to 3 single bonds"
939      * and returns an atom set.
940      *
941      * connected(1.0, 1.5, "single", {carbon}, {oxygen})
942      *
943      * means "single bonds from 1.0 to 1.5 Angstroms between carbon and oxygen"
944      * and returns a bond bitset.
945      *
946      * connected({*}.bonds, "DOUBLE")
947      *
948      * means just that and returns a bond set
949      *
950      *
951      */
952 
953     if (args.length > 5)
954       return false;
955     float min = Integer.MIN_VALUE, max = Integer.MAX_VALUE;
956     float fmin = 0, fmax = Float.MAX_VALUE;
957 
958     int order = Edge.BOND_ORDER_ANY;
959     BS atoms1 = null;
960     BS atoms2 = null;
961     boolean haveDecimal = false;
962     boolean isBonds = false;
963     switch (tok) {
964     case T.polyhedra:
965       // polyhedra()
966       // polyhedra(n)
967       // polyhedra(smilesString)
968       // {xx}.polyhedra()
969       // {xx}.polyhedra(n)
970       // {xx}.polyhedra(smilesString)
971       int nv = Integer.MIN_VALUE;
972       String smiles = null;
973       if (args.length > 0) {
974         switch (args[0].tok) {
975         case T.integer:
976           nv = args[0].intValue;
977           break;
978         case T.string:
979           smiles = SV.sValue(args[0]);
980           break;
981         }
982       }
983       if (intValue == T.polyhedra)
984         atoms1 = SV.getBitSet(mp.getX(), true);
985       Object[] data = new Object[] { Integer.valueOf(nv), smiles, atoms1 };
986       if (!vwr.shm.getShapePropertyData(JC.SHAPE_POLYHEDRA, "getCenters", data))
987         data[1] = null;
988       return mp.addXBs(data[1] == null ? new BS() : (BS) data[1]);
989     case T.bondcount:
990       // {atoms1}.bondCount({atoms2})
991       SV x1 = mp.getX();
992       if (x1.tok != T.bitset || args.length != 1 || args[0].tok != T.bitset)
993         return false;
994       atoms1 = (BS) x1.value;
995       atoms2 = (BS) args[0].value;
996       Lst<Integer> list = new Lst<Integer>();
997       Atom[] atoms = vwr.ms.at;
998       for (int i = atoms1.nextSetBit(0); i >= 0; i = atoms1.nextSetBit(i + 1)) {
999         int n = 0;
1000         Bond[] b = atoms[i].bonds;
1001         for (int j = b.length; --j >= 0;)
1002           if (atoms2.get(b[j].getOtherAtom(atoms[i]).i))
1003             n++;
1004         list.addLast(Integer.valueOf(n));
1005       }
1006       return mp.addXList(list);
1007     }
1008 
1009     // connected(
1010     for (int i = 0; i < args.length; i++) {
1011       SV var = args[i];
1012       switch (var.tok) {
1013       case T.bitset:
1014         isBonds = (var.value instanceof BondSet);
1015         if (isBonds && atoms1 != null)
1016           return false;
1017         if (atoms1 == null)
1018           atoms1 = (BS) var.value;
1019         else if (atoms2 == null)
1020           atoms2 = (BS) var.value;
1021         else
1022           return false;
1023         break;
1024       case T.string:
1025         String type = SV.sValue(var);
1026         if (type.equalsIgnoreCase("hbond"))
1027           order = Edge.BOND_HYDROGEN_MASK;
1028         else
1029           order = ScriptParam.getBondOrderFromString(type);
1030         if (order == Edge.BOND_ORDER_NULL)
1031           return false;
1032         break;
1033       case T.decimal:
1034         haveDecimal = true;
1035         //$FALL-THROUGH$
1036       default:
1037         int n = var.asInt();
1038         float f = var.asFloat();
1039         if (max != Integer.MAX_VALUE)
1040           return false;
1041 
1042         if (min == Integer.MIN_VALUE) {
1043           min = Math.max(n, 0);
1044           fmin = f;
1045         } else {
1046           max = n;
1047           fmax = f;
1048         }
1049       }
1050     }
1051     if (min == Integer.MIN_VALUE) {
1052       min = 1;
1053       max = 100;
1054       fmin = JC.DEFAULT_MIN_CONNECT_DISTANCE;
1055       fmax = JC.DEFAULT_MAX_CONNECT_DISTANCE;
1056     } else if (max == Integer.MAX_VALUE) {
1057       max = min;
1058       fmax = fmin;
1059       fmin = JC.DEFAULT_MIN_CONNECT_DISTANCE;
1060     }
1061     if (atoms1 == null)
1062       atoms1 = vwr.getAllAtoms();
1063     if (haveDecimal && atoms2 == null)
1064       atoms2 = atoms1;
1065     if (atoms2 != null) {
1066       BS bsBonds = new BS();
1067       vwr.makeConnections(fmin, fmax, order, T.identify, atoms1, atoms2,
1068           bsBonds, isBonds, false, 0);
1069       return mp.addX(SV.newV(T.bitset, BondSet.newBS(bsBonds,
1070           vwr.ms.getAtomIndices(vwr.ms.getAtoms(T.bonds, bsBonds)))));
1071     }
1072     return mp.addXBs(vwr.ms.getAtomsConnected(min, max, order, atoms1));
1073   }
1074 
evaluateContact(ScriptMathProcessor mp, SV[] args)1075   private boolean evaluateContact(ScriptMathProcessor mp, SV[] args) {
1076     if (args.length < 1 || args.length > 3)
1077       return false;
1078     int i = 0;
1079     float distance = 100;
1080     int tok = args[0].tok;
1081     switch (tok) {
1082     case T.decimal:
1083     case T.integer:
1084       distance = SV.fValue(args[i++]);
1085       break;
1086     case T.bitset:
1087       break;
1088     default:
1089       return false;
1090     }
1091     if (i == args.length || !(args[i].value instanceof BS))
1092       return false;
1093     BS bsA = BSUtil.copy((BS) args[i++].value);
1094     BS bsB = (i < args.length ? BSUtil.copy((BS) args[i].value) : null);
1095     RadiusData rd = new RadiusData(null,
1096         (distance > 10 ? distance / 100 : distance),
1097         (distance > 10 ? EnumType.FACTOR : EnumType.OFFSET), VDW.AUTO);
1098     bsB = setContactBitSets(bsA, bsB, true, Float.NaN, rd, false);
1099     bsB.or(bsA);
1100     return mp.addXBs(bsB);
1101   }
1102 
evaluateData(ScriptMathProcessor mp, SV[] args)1103   private boolean evaluateData(ScriptMathProcessor mp, SV[] args) {
1104 
1105     // x = data("somedataname") # the data
1106     // x = data("data2d_xxxx") # 2D data (x,y paired values)
1107     // x = data("data2d_xxxx", iSelected) # selected row of 2D data, with <=0
1108     // meaning "relative to the last row"
1109     // x = data("property_x", "property_y") # array mp.addition of two property
1110     // sets
1111     // x = data({atomno < 10},"xyz") # (or "pdb" or "mol") coordinate data in
1112     // xyz, pdb, or mol format
1113     // x = data(someData,ptrFieldOrColumn,nBytes,firstLine) # extraction of a
1114     // column of data based on a field (nBytes = 0) or column range (nBytes >
1115     // 0)
1116     String selected = (args.length == 0 ? "" : SV.sValue(args[0]));
1117     String type = "";
1118     switch (args.length) {
1119     case 0:
1120     case 1:
1121       break;
1122     case 2:
1123     case 3:
1124       if (args[0].tok == T.bitset)
1125         return mp.addXStr(vwr.getModelFileData(selected, SV.sValue(args[1]),
1126             args.length == 3 && SV.bValue(args[2])));
1127       break;
1128     case 4:
1129       int iField = args[1].asInt();
1130       int nBytes = args[2].asInt();
1131       int firstLine = args[3].asInt();
1132       float[] f = Parser.parseFloatArrayFromMatchAndField(SV.sValue(args[0]),
1133           null, 0, 0, null, iField, nBytes, null, firstLine);
1134       return mp.addXStr(Escape.escapeFloatA(f, false));
1135     default:
1136       return false;
1137     }
1138     if (selected.indexOf("data2d_") == 0) {
1139       // tab, newline separated data
1140       float[][] f1 = (float[][]) vwr.getDataObj(selected, null,
1141           JmolDataManager.DATA_TYPE_AFF);
1142       if (f1 == null)
1143         return mp.addXStr("");
1144       if (args.length == 2 && args[1].tok == T.integer) {
1145         int pt = args[1].intValue;
1146         if (pt < 0)
1147           pt += f1.length;
1148         if (pt >= 0 && pt < f1.length)
1149           return mp.addXStr(Escape.escapeFloatA(f1[pt], false));
1150         return mp.addXStr("");
1151       }
1152       return mp.addXStr(Escape.escapeFloatAA(f1, false));
1153     }
1154 
1155     // parallel mp.addition of float property data sets
1156 
1157     if (selected.indexOf("property_") == 0) {
1158       float[] f1 = (float[]) vwr.getDataObj(selected, null,
1159           JmolDataManager.DATA_TYPE_AF);
1160       if (f1 == null)
1161         return mp.addXStr("");
1162       float[] f2 = (type.indexOf("property_") == 0
1163           ? (float[]) vwr.getDataObj(selected, null,
1164               JmolDataManager.DATA_TYPE_AF)
1165           : null);
1166       if (f2 != null) {
1167         f1 = AU.arrayCopyF(f1, -1);
1168         for (int i = Math.min(f1.length, f2.length); --i >= 0;)
1169           f1[i] += f2[i];
1170       }
1171       return mp.addXStr(Escape.escapeFloatA(f1, false));
1172     }
1173 
1174     // some other data type -- just return it
1175 
1176     //if (args.length == 1) {
1177     Object[] data = (Object[]) vwr.getDataObj(selected, null,
1178         JmolDataManager.DATA_TYPE_UNKNOWN);
1179     return mp.addXStr(data == null ? "" : "" + data[1]);
1180     // }
1181   }
1182 
1183   /**
1184    * x = y.distance({atoms}) the average distance from elements of y to the
1185    * CENTER of {atoms}
1186    *
1187    * x = {atomset1}.distance.min({atomset2}, asAtomSet) If asAtomSet is true,
1188    * returns the closest atom in atomset1 to any atom of atomset2; if false or
1189    * omitted, returns an array listing the distance of each atom in atomset1 to
1190    * the closest atom in atomset2. This array can be used to assign properties
1191    * to atomset1: {1.1}.property_d = {1.1}.distance.min({2.1}); color {1.1}
1192    * property_d.
1193    *
1194    * x = {atomset1}.distance.min({point}, asAtomSet) If asAtomSet is true,
1195    * returns the atom in atomset1 closest to the specified point;if false or
1196    * omitted, returns the closest distance to the specified point from any atom
1197    * in atomset1.
1198    *
1199    * x = {atomset1}.distance.min({atomset2}).min returns the shortest distance
1200    * from any atom in atomset1 to any atom in atomset2.
1201    *
1202    * x = {atomset1}.distance.max({atomset2}, asAtomSet) If asAtomSet is true,
1203    * returns the furthest atom in atomset1 to any atom of atomset2; if false or
1204    * omitted, returns an array listing the distance of each atom in atomset1 to
1205    * the furthest atom in atomset2.
1206    *
1207    * x = {atomset1}.distance.max({point}, asAtomSet) If asAtomSet is true,
1208    * returns the atom in atomset1 furthest from the specified point;if false or
1209    * omitted, returns the furthest distance to the specified point from any atom
1210    * in atomset1.
1211    *
1212    * x = {atomset1}.distance.max({atomset2}).max returns the furthest distance
1213    * from any atom in atomset1 to any atom in atomset2.
1214    *
1215    * x = {atomset1}.distance.all({atomset2}) returns an array or array of arrays
1216    * of values
1217    *
1218    * @param mp
1219    * @param args
1220    * @param tok
1221    * @param op
1222    *        optional .min .max for distance
1223    * @return true if successful
1224    * @throws ScriptException
1225    */
1226 
evaluateDotDist(ScriptMathProcessor mp, SV[] args, int tok, int op)1227   private boolean evaluateDotDist(ScriptMathProcessor mp, SV[] args, int tok,
1228                                   int op)
1229       throws ScriptException {
1230     boolean isDist = (tok == T.distance);
1231     SV x1, x2, x3 = null;
1232     switch (args.length) {
1233     case 2:
1234       if (op == Integer.MAX_VALUE) {
1235         x1 = args[0];
1236         x2 = args[1];
1237         break;
1238       }
1239       x3 = args[1];
1240       //$FALL-THROUGH$
1241     case 1:
1242       x1 = mp.getX();
1243       x2 = args[0];
1244       break;
1245     default:
1246       return false;
1247     }
1248 
1249     float f = Float.NaN;
1250     try {
1251       if (tok == T.cross) {
1252         P3 a = P3.newP(mp.ptValue(x1, null));
1253         a.cross(a, mp.ptValue(x2, null));
1254         return mp.addXPt(a);
1255       }
1256 
1257       P3 pt2 = (x2.tok == T.varray ? null : mp.ptValue(x2, null));
1258       P4 plane2 = ScriptMathProcessor.planeValue(x2);
1259       if (isDist) {
1260         int minMax = (op == Integer.MIN_VALUE ? 0 : op & T.minmaxmask);
1261         boolean isMinMax = (minMax == T.min || minMax == T.max);
1262         boolean isAll = minMax == T.minmaxmask;
1263         switch (x1.tok) {
1264         case T.varray:
1265         case T.bitset:
1266           boolean isAtomSet1 = (x1.tok == T.bitset);
1267           boolean isAtomSet2 = (x2.tok == T.bitset);
1268           boolean isPoint2 = (x2.tok == T.point3f);
1269           BS bs1 = (isAtomSet1 ? (BS) x1.value : null);
1270           BS bs2 = (isAtomSet2 ? (BS) x2.value : null);
1271           Lst<SV> list1 = (isAtomSet1 ? null : x1.getList());
1272           Lst<SV> list2 = (isAtomSet2 ? null : x2.getList());
1273           boolean returnAtom = (isMinMax && x3 != null && x3.asBoolean());
1274           switch (x2.tok) {
1275           case T.bitset:
1276           case T.varray:
1277             //$FALL-THROUGH$
1278           case T.point3f:
1279             Atom[] atoms = vwr.ms.at;
1280             if (returnAtom) {
1281               float dMinMax = Float.NaN;
1282               int iMinMax = Integer.MAX_VALUE;
1283               if (isAtomSet1) {
1284                 for (int i = bs1.nextSetBit(0); i >= 0; i = bs1
1285                     .nextSetBit(i + 1)) {
1286                   float d = (isPoint2 ? atoms[i].distanceSquared(pt2)
1287                       : ((Float) e.getBitsetProperty(bs2, list2, op, atoms[i],
1288                           plane2, x1.value, null, false, x1.index, false))
1289                               .floatValue());
1290                   if (minMax == T.min ? d >= dMinMax : d <= dMinMax)
1291                     continue;
1292                   dMinMax = d;
1293                   iMinMax = i;
1294                 }
1295                 return mp.addXBs(iMinMax == Integer.MAX_VALUE ? new BS()
1296                     : BSUtil.newAndSetBit(iMinMax));
1297               }
1298               // list of points
1299               for (int i = list1.size(); --i >= 0;) {
1300                 P3 pt = SV.ptValue(list1.get(i));
1301                 float d = (isPoint2 ? pt.distanceSquared(pt2)
1302                     : ((Float) e.getBitsetProperty(bs2, list2, op, pt, plane2,
1303                         x1.value, null, false, Integer.MAX_VALUE, false))
1304                             .floatValue());
1305                 if (minMax == T.min ? d >= dMinMax : d <= dMinMax)
1306                   continue;
1307                 dMinMax = d;
1308                 iMinMax = i;
1309               }
1310               return mp.addXInt(iMinMax);
1311             }
1312             if (isAll) {
1313               if (bs2 == null) {
1314                 float[] data = new float[bs1.cardinality()];
1315                 for (int p = 0, i = bs1.nextSetBit(0); i >= 0; i = bs1
1316                     .nextSetBit(i + 1), p++)
1317                   data[p] = atoms[i].distance(pt2);
1318                 return mp.addXAF(data);
1319               }
1320               float[][] data2 = new float[bs1.cardinality()][bs2.cardinality()];
1321               for (int p = 0, i = bs1.nextSetBit(0); i >= 0; i = bs1
1322                   .nextSetBit(i + 1), p++)
1323                 for (int q = 0, j = bs2.nextSetBit(0); j >= 0; j = bs2
1324                     .nextSetBit(j + 1), q++)
1325                   data2[p][q] = atoms[i].distance(atoms[j]);
1326               return mp.addXAFF(data2);
1327             }
1328             if (isMinMax) {
1329               float[] data = new float[isAtomSet1 ? bs1.cardinality()
1330                   : list1.size()];
1331               if (isAtomSet1) {
1332                 for (int i = bs1.nextSetBit(0), p = 0; i >= 0; i = bs1
1333                     .nextSetBit(i + 1))
1334                   data[p++] = ((Float) e.getBitsetProperty(bs2, list2, op,
1335                       atoms[i], plane2, x1.value, null, false, x1.index, false))
1336                           .floatValue();
1337                 return mp.addXAF(data);
1338               }
1339               // list of points
1340               for (int i = data.length; --i >= 0;)
1341                 data[i] = ((Float) e.getBitsetProperty(bs2, list2, op,
1342                     SV.ptValue(list1.get(i)), plane2, null, null, false,
1343                     Integer.MAX_VALUE, false)).floatValue();
1344               return mp.addXAF(data);
1345             }
1346             return mp.addXObj(e.getBitsetProperty(bs1, list1, op, pt2, plane2,
1347                 x1.value, null, false, x1.index, false));
1348           }
1349         }
1350       }
1351       P3 pt1 = mp.ptValue(x1, null);
1352       P4 plane1 = ScriptMathProcessor.planeValue(x1);
1353       if (isDist) {
1354         if (plane2 != null && x3 != null)
1355           f = Measure.directedDistanceToPlane(pt1, plane2, SV.ptValue(x3));
1356         else
1357           f = (plane1 == null
1358               ? (plane2 == null ? pt2.distance(pt1)
1359                   : Measure.distanceToPlane(plane2, pt1))
1360               : Measure.distanceToPlane(plane1, pt2));
1361       } else {
1362         if (plane1 != null && plane2 != null) {
1363           // q1.dot(q2) assume quaternions
1364           f = plane1.x * plane2.x + plane1.y * plane2.y + plane1.z * plane2.z
1365               + plane1.w * plane2.w;
1366           // plane.dot(point) =
1367         } else {
1368           if (plane1 != null)
1369             pt1 = P3.new3(plane1.x, plane1.y, plane1.z);
1370           // point.dot(plane)
1371           else if (plane2 != null)
1372             pt2 = P3.new3(plane2.x, plane2.y, plane2.z);
1373           f = pt1.dot(pt2);
1374         }
1375       }
1376     } catch (Exception e) {
1377     }
1378     return mp.addXFloat(f);
1379   }
1380 
evaluateHelix(ScriptMathProcessor mp, SV[] args)1381   private boolean evaluateHelix(ScriptMathProcessor mp, SV[] args)
1382       throws ScriptException {
1383     if (args.length < 1 || args.length > 5)
1384       return false;
1385     // helix({resno=3})
1386     // helix({resno=3},"point|axis|radius|angle|draw|measure|array")
1387     // helix(resno,"point|axis|radius|angle|draw|measure|array")
1388     // helix(pt1, pt2, dq, "point|axis|radius|angle|draw|measure|array|")
1389     // helix(pt1, pt2, dq, "draw","someID")
1390     // helix(pt1, pt2, dq)
1391     int pt = (args.length > 2 ? 3 : 1);
1392     String type = (pt >= args.length ? "array" : SV.sValue(args[pt]));
1393     int tok = T.getTokFromName(type);
1394     if (args.length > 2) {
1395       // helix(pt1, pt2, dq ...)
1396       P3 pta = mp.ptValue(args[0], null);
1397       P3 ptb = mp.ptValue(args[1], null);
1398       if (tok == T.nada || args[2].tok != T.point4f || pta == null
1399           || ptb == null)
1400         return false;
1401       Quat dq = Quat.newP4((P4) args[2].value);
1402       T3[] data = Measure.computeHelicalAxis(pta, ptb, dq);
1403       //new T3[] { pt_a_prime, n, r, P3.new3(theta, pitch, residuesPerTurn) };
1404       return (data == null ? false
1405           : mp.addXObj(Escape.escapeHelical(type, tok, pta, ptb, data)));
1406     }
1407     BS bs = (args[0].value instanceof BS ? (BS) args[0].value
1408         : vwr.ms.getAtoms(T.resno, new Integer(args[0].asInt())));
1409     switch (tok) {
1410     case T.point:
1411     case T.axis:
1412     case T.radius:
1413       return mp.addXObj(getHelixData(bs, tok));
1414     case T.angle:
1415       return mp.addXFloat(((Float) getHelixData(bs, T.angle)).floatValue());
1416     case T.draw:
1417     case T.measure:
1418       return mp.addXObj(getHelixData(bs, tok));
1419     case T.array:
1420       String[] data = (String[]) getHelixData(bs, T.list);
1421       if (data == null)
1422         return false;
1423       return mp.addXAS(data);
1424     }
1425     return false;
1426   }
1427 
getHelixData(BS bs, int tokType)1428   private Object getHelixData(BS bs, int tokType) {
1429     int iAtom = bs.nextSetBit(0);
1430     return (iAtom < 0 ? "null"
1431         : vwr.ms.at[iAtom].group.getHelixData(tokType, vwr.getQuaternionFrame(),
1432             vwr.getInt(T.helixstep)));
1433   }
1434 
evaluateInChI(ScriptMathProcessor mp, SV[] args)1435   private boolean evaluateInChI(ScriptMathProcessor mp, SV[] args)
1436       throws ScriptException {
1437     // {*}.inchi(options)
1438     // InChI.inchi("key")
1439     // smiles.inchi(options) // including "key"
1440     // molFIleData.inchi(options) // including "key"
1441     SV x1 = mp.getX();
1442     String flags = (args.length > 0 ? SV.sValue(args[0]) : "");
1443     BS atoms = SV.getBitSet(x1, true);
1444     String molData = null;
1445     if (atoms == null) {
1446       molData = SV.sValue(x1);
1447     }
1448     return mp.addXStr(vwr.getInchi(atoms, molData, flags));
1449   }
1450 
evaluateFind(ScriptMathProcessor mp, SV[] args)1451   private boolean evaluateFind(ScriptMathProcessor mp, SV[] args)
1452       throws ScriptException {
1453 
1454     // {1.1}.find("2.1","map", labelFormat)
1455     // {*}.find("inchi",inchi-options)
1456     // {*}.find("crystalClass")
1457     // {*}.find("CF",true|false)
1458     // {*}.find("MF")
1459     // {*}.find("MF", "C2H4")
1460     // {*}.find("SEQUENCE")
1461     // {*}.find("SEQ")
1462     // {*}.find("SEQ", true)
1463     // {*}.find("SEQUENCE", true)
1464     // {*}.find("SEQUENCE", "H")
1465     // "AVA".find("SEQUENCE")
1466     // "a sequence".find("sequence", "i") // case insensitive
1467     // "a sequence".find("sequence", true) // case insensitive
1468     // "a sequence".find("sequence", false) // case sensitive
1469     // {*}.find("SMARTS", "CCCC")
1470     // "CCCC".find("SMARTS", "CC")
1471     // "CCCC".find("SMILES", "MF")
1472     // {2.1}.find("CCCC",{1.1}) // find pattern "CCCC" in {2.1} with conformation given by {1.1}
1473     // {*}.find("ccCCN","BONDS")
1474     // {*}.find("SMILES","H")
1475     // {*}.find("chemical",type)
1476     // {1.1}.find("SMILES", {2.1})
1477     // {1.1}.find("SMARTS", {2.1})
1478     // smiles1.find("SMILES", smiles2)
1479     // smiles1.find("SMILES", smiles2)
1480 
1481 
1482     SV x1 = mp.getX();
1483     boolean isList = (x1.tok == T.varray);
1484     boolean isAtoms = (x1.tok == T.bitset);
1485     boolean isEmpty = (args.length == 0);
1486     int tok0 = (args.length == 0 ? T.nada : args[0].tok);
1487     String sFind = (isEmpty || tok0 != T.string ? "" : SV.sValue(args[0]));
1488     boolean isOff = (args.length > 1 && args[1].tok == T.off);
1489     int tokLast = (tok0 == T.nada ? T.nada : args[args.length - 1].tok);
1490     SV argLast = (tokLast == T.nada ? SV.vF : args[args.length - 1]);
1491     boolean isON = !isList && (tokLast == T.on);
1492     String flags = (args.length > 1 && args[1].tok != T.on
1493         && args[1].tok != T.off && args[1].tok != T.bitset ? SV.sValue(args[1])
1494             : "");
1495     boolean isSequence = !isList && !isOff
1496         && sFind.equalsIgnoreCase("SEQUENCE");
1497     boolean isSeq = !isList && !isOff && sFind.equalsIgnoreCase("SEQ");
1498     if (sFind.toUpperCase().startsWith("SMILES/")) {
1499       if (!sFind.endsWith("/"))
1500         sFind += "/";
1501       String s = sFind.substring(6) + "//";
1502       if (JC.isSmilesCanonical(s)) {
1503         flags = "SMILES";
1504         sFind = "CHEMICAL";
1505       } else {
1506         sFind = "SMILES";
1507         flags = s + flags;
1508       }
1509     } else if (sFind.toUpperCase().startsWith("SMARTS/")) {
1510       if (!sFind.endsWith("/"))
1511         sFind += "/";
1512       flags = sFind.substring(6) + (flags.length() == 0 ? "//" : flags);
1513       sFind = "SMARTS";
1514     }
1515 
1516     boolean isSmiles = !isList && sFind.equalsIgnoreCase("SMILES");
1517     boolean isSMARTS = !isList && sFind.equalsIgnoreCase("SMARTS");
1518     boolean isChemical = !isList && sFind.equalsIgnoreCase("CHEMICAL");
1519     boolean isMF = !isList && sFind.equalsIgnoreCase("MF");
1520     boolean isCF = !isList && sFind.equalsIgnoreCase("CELLFORMULA");
1521     boolean isInchi = isAtoms && !isList && sFind.equalsIgnoreCase("INCHI");
1522     boolean isInchiKey = isAtoms && !isList
1523         && sFind.equalsIgnoreCase("INCHIKEY");
1524     boolean isStructureMap = (!isSmiles && !isSMARTS && tok0 == T.bitset
1525         && flags.toLowerCase().indexOf("map") >= 0);
1526     try {
1527       if (isInchi || isInchiKey) {
1528         if (isInchiKey)
1529           flags += " key";
1530         return mp.addXStr(vwr.getInchi(SV.getBitSet(x1, true), null, flags));
1531       }
1532       if (isChemical) {
1533         BS bsAtoms = (isAtoms ? (BS) x1.value : null);
1534         String data = (bsAtoms == null ? SV.sValue(x1)
1535             : vwr.getOpenSmiles(bsAtoms));
1536         data = (data.length() == 0 ? ""
1537             : vwr.getChemicalInfo(data, flags.toLowerCase(), bsAtoms)).trim();
1538         if (data.startsWith("InChI"))
1539           data = PT.rep(PT.rep(data, "InChI=", ""), "InChIKey=", "");
1540         return mp.addXStr(data);
1541       }
1542       if (isSmiles || isSMARTS || isAtoms) {
1543         int iPt = (isStructureMap ? 0 : isSmiles || isSMARTS ? 2 : 1);
1544         BS bs2 = (iPt < args.length && args[iPt].tok == T.bitset
1545             ? (BS) args[iPt++].value
1546             : null);
1547         boolean asBonds = ("bonds".equalsIgnoreCase(SV.sValue(argLast)));
1548         boolean isAll = (asBonds || isON);
1549         Object ret = null;
1550         switch (x1.tok) {
1551         case T.string:
1552           String smiles = SV.sValue(x1);
1553           if (bs2 != null || isSmiles && args.length == 1)
1554             return false;
1555           if (flags.equalsIgnoreCase("mf")) {
1556             ret = vwr.getSmilesMatcher().getMolecularFormula(smiles, isSMARTS);
1557           } else {
1558             String pattern = flags;
1559             // "SMARTS",flags,asMap, allMappings
1560             boolean allMappings = true;
1561             boolean asMap = false;
1562             switch (args.length) {
1563             case 4:
1564               allMappings = SV.bValue(args[3]);
1565               //$FALL-THROUGH$
1566             case 3:
1567               asMap = SV.bValue(args[2]);
1568               break;
1569             }
1570             boolean isChirality = pattern.equals("chirality");
1571             boolean justOne = (!asMap
1572                 && (!allMappings || !isSMARTS && !isChirality));
1573             try {
1574               ret = e.getSmilesExt().getSmilesMatches(pattern, smiles, null,
1575                   null,
1576                   isSMARTS ? JC.SMILES_TYPE_SMARTS : JC.SMILES_TYPE_SMILES,
1577                   !asMap, !allMappings);
1578             } catch (Exception ex) {
1579               System.out.println(ex.getMessage());
1580               return mp.addXInt(-1);
1581             }
1582             int len = (isChirality ? 1
1583                 : AU.isAI(ret) ? ((int[]) ret).length : ((int[][]) ret).length);
1584             if (len == 0
1585                 && vwr.getSmilesMatcher().getLastException() != "MF_FAILED"
1586                 && smiles.toLowerCase().indexOf("noaromatic") < 0
1587                 && smiles.toLowerCase().indexOf("strict") < 0) {
1588               // problem arising from Jmol interpreting one string as aromatic
1589               // and the other not, perhaps because of one using [N] as in NCI caffeine
1590               // and one not, as from PubChem.
1591               // There is no loss in doing this search again, except for
1592               ret = e.getSmilesExt().getSmilesMatches(pattern, smiles, null,
1593                   null,
1594                   JC.SMILES_NO_AROMATIC | (isSMARTS ? JC.SMILES_TYPE_SMARTS
1595                       : JC.SMILES_TYPE_SMILES),
1596                   !asMap, !allMappings);
1597             }
1598             if (justOne) {
1599               return mp.addXInt(!allMappings && len > 0 ? 1 : len);
1600             }
1601           }
1602           break;
1603         case T.bitset:
1604           BS bs = (BS) x1.value;
1605           if (sFind.equalsIgnoreCase("crystalClass")) {
1606             // {*}.find("crystalClass")
1607             // {*}.find("crystalClass", pt)
1608             int n = bs.nextSetBit(0);
1609             BS bsNew = null;
1610             if (args.length != 2) {
1611               bsNew = new BS();
1612               bsNew.set(n);
1613             }
1614             return mp.addXList(vwr.ms.generateCrystalClass(n,
1615                 (bsNew != null ? vwr.ms.getAtomSetCenter(bsNew)
1616                     : argLast.tok == T.bitset
1617                         ? vwr.ms.getAtomSetCenter((BS) argLast.value)
1618                         : SV.ptValue(argLast))));
1619           }
1620           if (isMF && flags.length() != 0) {
1621             return mp.addXBs(JmolMolecule.getBitSetForMF(vwr.ms.at, bs, flags));
1622           }
1623           if (isMF || isCF) {
1624             return mp.addXStr(JmolMolecule.getMolecularFormulaAtoms(vwr.ms.at,
1625                 bs, (isMF ? null : vwr.ms.getCellWeights(bs)), isON));
1626           }
1627           if (isSequence || isSeq) {
1628             boolean isHH = (argLast.asString().equalsIgnoreCase("H"));
1629             isAll |= isHH;
1630             return mp.addXStr(vwr.getSmilesOpt(bs, -1, -1, (isAll
1631                 ? JC.SMILES_GEN_BIO_ALLOW_UNMATCHED_RINGS
1632                     | JC.SMILES_GEN_BIO_COV_CROSSLINK
1633                     | (isHH ? JC.SMILES_GEN_BIO_HH_CROSSLINK : 0)
1634                 : 0)
1635                 | (isSeq ? JC.SMILES_GEN_BIO_NOCOMMENTS : JC.SMILES_GEN_BIO),
1636                 null));
1637           }
1638           if (isStructureMap) {
1639             int[] map = null, map1 = null, map2 = null;
1640             Object[][] mapNames = null;
1641             String key = (args.length == 3 ? SV.sValue(argLast) : null);
1642             char itype = (key == null || key.equals("%i")
1643                 || key.equals("number") ? 'i'
1644                     : key.equals("%a") || key.equals("name") ? 'a'
1645                         : key.equals("%D") || key.equals("index") ? 'D' : '?');
1646             if (key == null)
1647               key = "number";
1648             String err = null;
1649             flags = flags.replace("map", "").trim();
1650             sFind = vwr.getSmilesOpt(bs, 0, 0, 0, flags);
1651             if (bs.cardinality() != bs2.cardinality()) {
1652               err = "atom sets are not the same size";
1653             } else {
1654               try {
1655                 int iflags = (JC.SMILES_TYPE_SMILES | JC.SMILES_MAP_UNIQUE
1656                     | JC.SMILES_FIRST_MATCH_ONLY);
1657                 if (flags.length() > 0)
1658                   sFind = "/" + flags + "/" + sFind;
1659                 map1 = vwr.getSmilesMatcher().getCorrelationMaps(sFind,
1660                     vwr.ms.at, vwr.ms.ac, bs, iflags)[0];
1661                 int[][] m2 = vwr.getSmilesMatcher().getCorrelationMaps(sFind,
1662                     vwr.ms.at, vwr.ms.ac, bs2, iflags);
1663                 if (m2.length > 0) {
1664                   map = new int[bs.length()];
1665                   for (int i = map.length; --i >= 0;)
1666                     map[i] = -1;
1667                   map2 = m2[0];
1668                   for (int i = map1.length; --i >= 0;)
1669                     map[map1[i]] = map2[i];
1670                   mapNames = new Object[map1.length][2];
1671                   BS bsAll = BS.copy(bs);
1672                   bsAll.or(bs2);
1673                   String[] names = (itype == '?' ? new String[bsAll.length()]
1674                       : null);
1675                   if (names != null)
1676                     names = (String[]) e.getCmdExt().getBitsetIdentFull(bsAll,
1677                         key, null, false, Integer.MAX_VALUE, false, names);
1678                   Atom[] at = vwr.ms.at;
1679                   for (int pt = 0, i = bs.nextSetBit(0); i >= 0; i = bs
1680                       .nextSetBit(i + 1)) {
1681                     int j = map[i];
1682                     if (j == -1)
1683                       continue;
1684                     Object[] a;
1685                     switch (itype) {
1686                     case 'a':
1687                       a = new String[] { at[i].getAtomName(),
1688                           at[j].getAtomName() };
1689                       break;
1690                     case 'i':
1691                       a = new Integer[] {
1692                           Integer.valueOf(at[i].getAtomNumber()),
1693                           Integer.valueOf(at[j].getAtomNumber()) };
1694                       break;
1695                     case 'D':
1696                       a = new Integer[] { Integer.valueOf(i),
1697                           Integer.valueOf(j) };
1698                       break;
1699                     default:
1700                       a = new String[] { names[i], names[j] };
1701                       break;
1702                     }
1703                     mapNames[pt++] = a;
1704                   }
1705                 }
1706               } catch (Exception ee) {
1707                 err = ee.getMessage();
1708               }
1709             }
1710             Map<String, Object> m = new HashMap<String, Object>();
1711             m.put("BS1", bs);
1712             m.put("BS2", bs2);
1713             m.put("SMILES", sFind);
1714             if (err == null) {
1715               m.put("SMILEStoBS1", map1);
1716               m.put("SMILEStoBS2", map2);
1717               m.put("BS1toBS2", map);
1718               m.put("MAP1to2", mapNames);
1719               m.put("key", key);
1720             } else {
1721               m.put("error", err);
1722             }
1723             return mp.addXMap(m);
1724           }
1725           if (isSmiles || isSMARTS) {
1726             sFind = (args.length > 1 && args[1].tok == T.bitset
1727                 ? vwr.getSmilesOpt((BS) args[1].value, 0, 0, 0, flags)
1728                 : flags);
1729           }
1730           flags = flags.toUpperCase();
1731           BS bsMatch3D = bs2;
1732           if (asBonds) {
1733             // this will return a single match
1734             int[][] map = vwr.getSmilesMatcher().getCorrelationMaps(sFind,
1735                 vwr.ms.at, vwr.ms.ac, bs,
1736                 (isSmiles ? JC.SMILES_TYPE_SMILES : JC.SMILES_TYPE_SMARTS)
1737                     | JC.SMILES_FIRST_MATCH_ONLY);
1738             ret = (map.length > 0 ? vwr.ms.getDihedralMap(map[0]) : new int[0]);
1739           } else if (flags.equalsIgnoreCase("map")) {
1740             // we add NO_AROMATIC because that is not important for structure-SMILES matching
1741             int[][] map = vwr.getSmilesMatcher().getCorrelationMaps(sFind,
1742                 vwr.ms.at, vwr.ms.ac, bs,
1743                 (isSmiles ? JC.SMILES_TYPE_SMILES : JC.SMILES_TYPE_SMARTS)
1744                     | JC.SMILES_MAP_UNIQUE | JC.SMILES_NO_AROMATIC);
1745             ret = map;
1746           } else {
1747             // SMILES or SMARTS only
1748             int smilesFlags = (isSmiles ?
1749 
1750                 (flags.indexOf("OPEN") >= 0 ? JC.SMILES_TYPE_OPENSMILES
1751                     : JC.SMILES_TYPE_SMILES)
1752                 : JC.SMILES_TYPE_SMARTS)
1753                 | (isON && sFind.length() == 0 ? JC.SMILES_GEN_BIO_COV_CROSSLINK
1754                     | JC.SMILES_GEN_BIO_COMMENT : 0);
1755             if (flags.indexOf("/MOLECULE/") >= 0) {
1756               // all molecules
1757               JmolMolecule[] mols = vwr.ms.getMolecules();
1758               Lst<BS> molList = new Lst<BS>();
1759               for (int i = 0; i < mols.length; i++) {
1760                 if (mols[i].atomList.intersects(bs)) {
1761                   BS bsRet = (BS) e.getSmilesExt().getSmilesMatches(sFind, null,
1762                       mols[i].atomList, bsMatch3D, smilesFlags, !isON, false);
1763                   if (!bsRet.isEmpty())
1764                     molList.addLast(bsRet);
1765                 }
1766               }
1767               ret = molList;
1768             } else {
1769               ret = e.getSmilesExt().getSmilesMatches(sFind, null, bs,
1770                   bsMatch3D, smilesFlags, !isON, false);
1771             }
1772           }
1773           break;
1774         }
1775         if (ret == null)
1776           e.invArg();
1777         return mp.addXObj(ret);
1778       }
1779     } catch (Exception ex) {
1780       e.evalError(ex.getMessage(), null);
1781     }
1782     BS bs = new BS();
1783     Lst<SV> svlist = (isList ? x1.getList() : null);
1784     if (isList && tok0 != T.string && tok0 != T.nada) {
1785       SV v = args[0];
1786       for (int i = 0, n = svlist.size(); i < n; i++) {
1787         if (SV.areEqual(svlist.get(i), v))
1788           bs.set(i);
1789       }
1790       int[] ret = new int[bs.cardinality()];
1791       for (int pt = 0, i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
1792         ret[pt++] = i + 1;
1793       return mp.addXAI(ret);
1794     }
1795 
1796     boolean isReverse = (flags.indexOf("v") >= 0);
1797     boolean isCaseInsensitive = (flags.indexOf("i") >= 0) || isOff;
1798     boolean asMatch = (flags.indexOf("m") >= 0);
1799     boolean checkEmpty = (sFind.length() == 0);
1800     boolean isPattern = (!checkEmpty && args.length == 2);
1801     if (isList || isPattern) {
1802       JmolPatternMatcher pm = (isPattern ? getPatternMatcher() : null);
1803       Pattern pattern = null;
1804       if (isPattern) {
1805         try {
1806           pattern = pm.compile(sFind, isCaseInsensitive);
1807         } catch (Exception ex) {
1808           e.evalError(ex.toString(), null);
1809         }
1810       }
1811       String[] list = (checkEmpty ? null : SV.strListValue(x1));
1812       int nlist = (checkEmpty ? svlist.size() : list.length);
1813       if (Logger.debugging)
1814         Logger.debug("finding " + sFind);
1815       int n = 0;
1816       Matcher matcher = null;
1817       Lst<String> v = (asMatch ? new Lst<String>() : null);
1818       String what = "";
1819       for (int i = 0; i < nlist; i++) {
1820         boolean isMatch;
1821         if (checkEmpty) {
1822           SV o = svlist.get(i);
1823           switch (o.tok) {
1824           case T.hash:
1825             isMatch = (o.getMap().isEmpty() != isEmpty);
1826             break;
1827           case T.varray:
1828             isMatch = ((o.getList().size() == 0) != isEmpty);
1829             break;
1830           case T.string:
1831             isMatch = ((o.asString().length() == 0) != isEmpty);
1832             break;
1833           default:
1834             isMatch = true;
1835           }
1836         } else if (isPattern) {
1837           what = list[i];
1838           matcher = pattern.matcher(what);
1839           isMatch = matcher.find();
1840         } else {
1841           isMatch = (SV.sValue(svlist.get(i)).indexOf(sFind) >= 0);
1842         }
1843         if (asMatch && isMatch || !asMatch && isMatch == !isReverse) {
1844           n++;
1845           bs.set(i);
1846           if (asMatch)
1847             v.addLast(
1848                 isReverse
1849                     ? what.substring(0, matcher.start())
1850                         + what.substring(matcher.end())
1851                     : matcher.group());
1852         }
1853       }
1854       if (!isList) {
1855         return (asMatch ? mp.addXStr(v.size() == 1 ? (String) v.get(0) : "")
1856             : isReverse ? mp.addXBool(n == 1)
1857                 : asMatch ? mp.addXStr(n == 0 ? "" : matcher.group())
1858                     : mp.addXInt(n == 0 ? 0 : matcher.start() + 1));
1859       }
1860       // removed in 14.2/3.14 -- not documented and not expected      if (n == 1)
1861       //    return mp.addXStr(asMatch ? (String) v.get(0) : list[ipt]);
1862 
1863       if (asMatch) {
1864         String[] listNew = new String[n];
1865         if (n > 0)
1866           for (int i = list.length; --i >= 0;)
1867             if (bs.get(i)) {
1868               --n;
1869               listNew[n] = (asMatch ? (String) v.get(n) : list[i]);
1870             }
1871         return mp.addXAS(listNew);
1872       }
1873       Lst<SV> l = new Lst<SV>();
1874       for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
1875         l.addLast(svlist.get(i));
1876       return mp.addXList(l);
1877     }
1878     if (isSequence) {
1879       return mp.addXStr(vwr.getJBR().toStdAmino3(SV.sValue(x1)));
1880     }
1881     return mp.addXInt(SV.sValue(x1).indexOf(sFind) + 1);
1882   }
1883 
1884   /**
1885    * _ by itself, not as a function, is shorthand for
1886    * getProperty("auxiliaryInfo")
1887    *
1888    * $ print _.keys
1889    *
1890    * boundbox group3Counts group3Lists modelLoadNote models properties
1891    * someModelsHaveFractionalCoordinates someModelsHaveSymmetry
1892    * someModelsHaveUnitcells symmetryRange
1893    *
1894    *
1895    * _m by itself, not as a function, is shorthand for
1896    * getProperty("auxiliaryInfo.models")[_currentFrame]
1897    *
1898    * $ print format("json",_m.unitCellParams)
1899    *
1900    * [ 0.0,0.0,0.0,0.0,0.0,0.0,0.0,-2.1660376,-2.1660376,0.0,-2.1660376,
1901    * 2.1660376,-4.10273,0.0,0.0,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN ]
1902    *
1903    *
1904    * {atomset}._ by itself delivers a subset array of auxiliaryInfo.models for
1905    * all models in {atomset}
1906    *
1907    * $ print {*}._..1..aflowInfo
1908    *
1909    * (first model's aflowInfo)
1910    *
1911    *
1912    * _(key) prepends "auxiliaryInfo.models", delivering a modelCount-length
1913    * array of information
1914    *
1915    * $ print _("aflowInfo[SELECT auid WHERE H__eV___VASP_ < 0]")
1916    *
1917    *
1918    * {atomset}._(key) selects for model Auxiliary info related to models of the
1919    * specified atoms
1920    *
1921    * {atomset}.getProperty(key) defaults to atomInfo, but also allows key to
1922    * start with "bondInfo"
1923    *
1924    * Examples:
1925    *
1926    * print _("aflowInfo[select sg where volume_cell > 70]")
1927    *
1928    * print {model>10}._("aflowInfo[select sg where volume_cell > 70]")
1929    *
1930    * @param mp
1931    * @param args
1932    * @param tok0
1933    * @param isAtomProperty
1934    * @return true if no syntax problems
1935    * @throws ScriptException
1936    */
evaluateGetProperty(ScriptMathProcessor mp, SV[] args, int tok0, boolean isAtomProperty)1937   private boolean evaluateGetProperty(ScriptMathProcessor mp, SV[] args,
1938                                       int tok0, boolean isAtomProperty)
1939       throws ScriptException {
1940     int nargs = args.length;
1941     boolean isSelect = (isAtomProperty && tok0 == T.select);
1942     boolean isPivot = (isAtomProperty && tok0 == T.pivot && nargs > 0);
1943     boolean isAuxiliary = (tok0 == T.__);
1944     int pt = 0;
1945     int tok = (nargs == 0 ? T.nada : args[0].tok);
1946     if (nargs == 2 && (tok == T.varray || tok == T.hash || tok == T.context)) {
1947       return mp.addXObj(
1948           vwr.extractProperty(args[0].value, args[1].value.toString(), -1));
1949     }
1950     BS bsSelect = (isAtomProperty && nargs == 1 && args[0].tok == T.bitset
1951         ? (BS) args[0].value
1952         : null);
1953     String pname = (bsSelect == null && nargs > 0 ? SV.sValue(args[pt++]) : "");
1954     String propertyName = pname;
1955     String lc = propertyName.toLowerCase();
1956     if (!isSelect && lc.indexOf("[select ") < 0)
1957       propertyName = lc;
1958     boolean isJSON = false;
1959     if (propertyName.equals("json") && nargs > pt) {
1960       isJSON = true;
1961       propertyName = SV.sValue(args[pt++]);
1962     }
1963     SV x = null;
1964     if (isAtomProperty) { // also includes $isosurface1.getProperty
1965       x = mp.getX();
1966       switch (x.tok) {
1967       case T.bitset:
1968         break;
1969       case T.string:
1970         String name = (String) x.value;
1971         Object[] data = new Object[3];
1972         int shapeID;
1973         if (name.startsWith("$")) {
1974           // "$P4".getProperty....
1975           name = name.substring(1);
1976           shapeID = vwr.shm.getShapeIdFromObjectName(name);
1977           if (shapeID >= 0) {
1978             data[0] = name;
1979             vwr.shm.getShapePropertyData(shapeID, "index", data);
1980             if (data[1] != null && !pname.equals("index")) {
1981               int index = ((Integer) data[1]).intValue();
1982               data[1] = vwr.shm.getShapePropertyIndex(shapeID, pname.intern(),
1983                   index);
1984             }
1985           }
1986         } else {
1987           shapeID = JC.shapeTokenIndex(T.getTokFromName(name));
1988           if (shapeID >= 0) {
1989             // "isosurface".getProperty...
1990             data[0] = pname;
1991             data[1] = Integer.valueOf(-1);
1992             vwr.shm.getShapePropertyData(shapeID, pname.intern(), data);
1993           }
1994         }
1995         return (data[1] == null ? mp.addXStr("") : mp.addXObj(data[1]));
1996       case T.varray:
1997         if (isPivot) {
1998           Lst<SV> lstx = x.getList();
1999           // array of hash pivot("key")
2000           Map<String, SV> map = new Hashtable<String, SV>();
2001           String sep = (nargs > 1 ? SV.sValue(args[nargs - 1]) : null);
2002           if (sep != null)
2003             nargs--;
2004           String[] keys = new String[nargs];
2005           for (int i = 0; i < nargs; i++)
2006             keys[i] = SV.sValue(args[i]);
2007           for (int i = 0, n = lstx.size(); i < n; i++) {
2008             SV sv = lstx.get(i);
2009             if (sv.tok != T.hash)
2010               continue;
2011             Map<String, SV> mapi = sv.getMap();
2012             String key = "";
2013             for (int j = 0; j < nargs; j++) {
2014               SV obj = mapi.get(keys[j]);
2015               key += (j == 0 ? "" : sep) + SV.sValue(obj);
2016             }
2017             SV vlist = map.get(key);
2018             if (vlist == null)
2019               map.put(key, vlist = SV.newV(T.varray, new Lst<SV>()));
2020             vlist.getList().addLast(sv);
2021           }
2022           return mp.addXMap(map);
2023         }
2024         if (bsSelect != null) {
2025           Lst<SV> l0 = x.getList();
2026           Lst<SV> lst = new Lst<SV>();
2027           for (int i = bsSelect.nextSetBit(0); i >= 0; i = bsSelect
2028               .nextSetBit(i + 1))
2029             lst.addLast(l0.get(i));
2030           return mp.addXList(lst);
2031         }
2032         //$FALL-THROUGH$
2033       default:
2034         if (isSelect)
2035           propertyName = "[SELECT " + propertyName + "]";
2036         return mp.addXObj(vwr.extractProperty(x, propertyName, -1));
2037       }
2038       if (!lc.startsWith("bondinfo") && !lc.startsWith("atominfo")
2039           && !lc.startsWith("modelkitinfo"))
2040         propertyName = "atomInfo." + propertyName;
2041     }
2042     Object propertyValue = "";
2043     if (propertyName.equalsIgnoreCase("fileContents") && nargs >= 2) {
2044       String s = SV.sValue(args[1]);
2045       for (int i = 2; i < nargs; i++)
2046         s += "|" + SV.sValue(args[i]);
2047       propertyValue = s;
2048       pt = nargs;
2049     } else if (nargs > pt) {
2050       switch (args[pt].tok) {
2051       case T.bitset:
2052         propertyValue = args[pt++].value;
2053         if (propertyName.equalsIgnoreCase("bondInfo") && nargs > pt
2054             && args[pt].tok == T.bitset)
2055           propertyValue = new BS[] { (BS) propertyValue, (BS) args[pt].value };
2056         break;
2057       case T.hash:
2058       case T.string:
2059         if (vwr.checkPropertyParameter(propertyName))
2060           propertyValue = args[pt++].value;
2061         break;
2062       }
2063     }
2064     if (isAtomProperty) {
2065       BS bs = (BS) x.value;
2066       int iAtom = bs.nextSetBit(0);
2067       if (iAtom < 0)
2068         return mp.addXStr("");
2069       propertyValue = bs;
2070     }
2071     if (isAuxiliary && !isAtomProperty)
2072       propertyName = "auxiliaryInfo.models." + propertyName;
2073     propertyName = PT.rep(propertyName, ".[", "[");
2074     Object property = vwr.getProperty(null, propertyName, propertyValue);
2075     if (pt < nargs)
2076       property = vwr.extractProperty(property, args, pt);
2077     return mp.addXObj(isJSON ? SV.safeJSON("value", property)
2078         : SV.isVariableType(property) ? property
2079             : Escape.toReadable(propertyName, property));
2080   }
2081 
evaluateFormat(ScriptMathProcessor mp, int intValue, SV[] args, boolean isLabel)2082   private boolean evaluateFormat(ScriptMathProcessor mp, int intValue,
2083                                  SV[] args, boolean isLabel)
2084       throws ScriptException {
2085     // NOT {xxx}.label
2086     // {xxx}.label("....")
2087     // {xxx}.yyy.format("...")
2088     // (value).format("...")
2089     // format("....",a,b,c...)
2090     // format("....",[a1, a2, a3, a3....])
2091     // format("base64", x)
2092     // format("JSON", x)
2093     // format("byteArray", x)
2094     // format("array", x)
2095     SV x1 = (args.length < 2 || intValue == T.format ? mp.getX() : null);
2096     String format = (args.length == 0 ? "%U"
2097         : args[0].tok == T.varray ? null : SV.sValue(args[0]));
2098     if (!isLabel && args.length > 0 && x1 != null && x1.tok != T.bitset
2099         && format != null) {
2100       // x1.format(["energy", "pointGroup"]);
2101       // x1.format("%5.3f %5s", ["energy", "pointGroup"])
2102       // but not x1.format() or {*}.format(....)
2103       if (args.length == 2) {
2104         Lst<SV> listIn = x1.getList();
2105         Lst<SV> formatList = args[1].getList();
2106         if (listIn == null || formatList == null)
2107           return false;
2108         x1 = SV.getVariableList(getSublist(listIn, formatList));
2109       }
2110       args = new SV[] { args[0], x1 };
2111       x1 = null;
2112     }
2113     if (x1 == null) {
2114       int pt = (isLabel ? -1 : SV.getFormatType(format));
2115       if (pt >= 0 && args.length != 2)
2116         return false;
2117       if (pt >= 0 || args.length < 2 || args[1].tok != T.varray) {
2118         Object o = SV.format(args, pt);
2119         return (format.equalsIgnoreCase("json") ? mp.addXStr((String) o)
2120             : mp.addXObj(o));
2121       }
2122       // fill an array with applied formats
2123       Lst<SV> a = args[1].getList();
2124       SV[] args2 = new SV[] { args[0], null };
2125       String[] sa = new String[a.size()];
2126       for (int i = sa.length; --i >= 0;) {
2127         args2[1] = a.get(i);
2128         sa[i] = SV.format(args2, pt).toString();
2129       }
2130       return mp.addXAS(sa);
2131     }
2132     if (x1.tok == T.varray && format == null) {
2133       Lst<SV> listIn = x1.getList();
2134       Lst<SV> formatList = args[0].getList();
2135       Lst<SV> listOut = getSublist(listIn, formatList);
2136       return mp.addXList(listOut);
2137     }
2138 
2139     BS bs = (x1.tok == T.bitset ? (BS) x1.value : null);
2140     boolean asArray = T.tokAttr(intValue, T.minmaxmask); // "all"
2141     return mp.addXObj(format == null ? ""
2142         : bs == null ? SV.sprintf(PT.formatCheck(format), x1)
2143             : e.getCmdExt().getBitsetIdent(bs, format, x1.value, true, x1.index,
2144                 asArray));
2145 
2146   }
2147 
2148   /**
2149    * [ {...},{...}... ] ==> [[...],[...]]
2150    *
2151    * @param listIn
2152    * @param formatList
2153    * @return sublist
2154    */
getSublist(Lst<SV> listIn, Lst<SV> formatList)2155   private Lst<SV> getSublist(Lst<SV> listIn, Lst<SV> formatList) {
2156     Lst<SV> listOut = new Lst<SV>();
2157     Map<String, SV> map;
2158     SV v;
2159     Lst<SV> list;
2160     for (int i = 0, n = listIn.size(); i < n; i++) {
2161       SV element = listIn.get(i);
2162       switch (element.tok) {
2163       case T.hash:
2164         map = element.getMap();
2165         list = new Lst<SV>();
2166         for (int j = 0, n1 = formatList.size(); j < n1; j++) {
2167           v = map.get(SV.sValue(formatList.get(j)));
2168           list.addLast(v == null ? SV.newS("") : v);
2169         }
2170         listOut.addLast(SV.getVariableList(list));
2171         break;
2172       case T.varray:
2173         map = new Hashtable<String, SV>();
2174         list = element.getList();
2175         for (int j = 0, n1 = Math.min(list.size(),
2176             formatList.size()); j < n1; j++) {
2177           map.put(SV.sValue(formatList.get(j)), list.get(j));
2178         }
2179         listOut.addLast(SV.getVariable(map));
2180       }
2181     }
2182     return listOut;
2183   }
2184 
evaluateList(ScriptMathProcessor mp, int tok, SV[] args)2185   private boolean evaluateList(ScriptMathProcessor mp, int tok, SV[] args)
2186       throws ScriptException {
2187     // array.add(x)
2188     // array.add(sep, x)
2189     // array.sub(x)
2190     // array.mul(x)
2191     // array.mul3(x)
2192     // array.div(x)
2193     // array.push()
2194     // array.pop()
2195 
2196     // array.join()
2197     // array.join(sep)
2198     // array.join(sep,true)
2199     // array.join("",true) (CSV)
2200 
2201     // array.split()
2202     // array.split(sep)
2203     // array.split("\t",true)
2204     // array.split("",true) (CSV)
2205 
2206     int len = args.length;
2207     SV x1 = mp.getX();
2208     boolean isArray1 = (x1.tok == T.varray);
2209     SV x2;
2210     switch (tok) {
2211     case T.push:
2212       return (len == 2 && mp.addX(x1.pushPop(args[0], args[1]))
2213           || len == 1 && mp.addX(x1.pushPop(null, args[0])));
2214     case T.pop:
2215       return (len == 1 && mp.addX(x1.pushPop(args[0], null))
2216           || len == 0 && mp.addX(x1.pushPop(null, null)));
2217     case T.add:
2218       if (len != 1 && len != 2)
2219         return false;
2220       break;
2221     case T.split:
2222     case T.join:
2223       break;
2224     default:
2225       if (len != 1)
2226         return false;
2227     }
2228     String[] sList1 = null, sList2 = null, sList3 = null;
2229 
2230     if (len == 2) {
2231       // special add, join, split
2232       String tab = SV.sValue(args[0]);
2233       x2 = args[1];
2234       if (tok == T.add) {
2235         // [...].add("\t", [...])
2236         sList1 = (isArray1 ? SV.strListValue(x1)
2237             : PT.split(SV.sValue(x1), "\n"));
2238         sList2 = (x2.tok == T.varray ? SV.strListValue(x2)
2239             : PT.split(SV.sValue(x2), "\n"));
2240         sList3 = new String[len = Math.max(sList1.length, sList2.length)];
2241         for (int i = 0; i < len; i++)
2242           sList3[i] = (i >= sList1.length ? "" : sList1[i]) + tab
2243               + (i >= sList2.length ? "" : sList2[i]);
2244         return mp.addXAS(sList3);
2245       }
2246       if (x2.tok != T.on)
2247         return false; // second parameter must be "true" for now.
2248       Lst<SV> l = x1.getList();
2249       boolean isCSV = (tab.length() == 0);
2250       if (isCSV)
2251         tab = ",";
2252       if (tok == T.join) {
2253         // [...][...].join(sep, true) [2D-array line join]
2254         // [...][...].join("", true)  [CSV join]
2255         SV[] s2 = new SV[l.size()];
2256         for (int i = l.size(); --i >= 0;) {
2257           Lst<SV> a = l.get(i).getList();
2258           if (a == null)
2259             s2[i] = l.get(i);
2260           else {
2261             SB sb = new SB();
2262             for (int j = 0, n = a.size(); j < n; j++) {
2263               if (j > 0)
2264                 sb.append(tab);
2265               SV sv = a.get(j);
2266               sb.append(isCSV && sv.tok == T.string
2267                   ? "\"" + PT.rep((String) sv.value, "\"", "\"\"") + "\""
2268                   : "" + sv.asString());
2269             }
2270             s2[i] = SV.newS(sb.toString());
2271           }
2272         }
2273         return mp.addXAV(s2);
2274       }
2275       // [...].split(sep, true) [split individual elements as strings]
2276       // [...].split("", true) [CSV split]
2277       Lst<SV> sa = new Lst<SV>();
2278       if (isCSV)
2279         tab = "\0";
2280       int[] next = new int[2];
2281       for (int i = 0, nl = l.size(); i < nl; i++) {
2282         String line = l.get(i).asString();
2283         if (isCSV) {
2284           next[1] = 0;
2285           next[0] = 0;
2286           int last = 0;
2287           while (true) {
2288             String s = PT.getCSVString(line, next);
2289             if (s == null) {
2290               if (next[1] == -1) {
2291                 // unmatched -- continue with next line if present
2292                 // or just close quotes gracefully
2293                 line += (++i < nl ? "\n" + l.get(i).asString() : "\"");
2294                 next[1] = last;
2295                 continue;
2296               }
2297               line = line.substring(0, last)
2298                   + line.substring(last).replace(',', '\0');
2299               break;
2300             }
2301             line = line.substring(0, last)
2302                 + line.substring(last, next[0]).replace(',', '\0') + s
2303                 + line.substring(next[1]);
2304             next[1] = last = next[0] + s.length();
2305           }
2306         }
2307         String[] linaa = line.split(tab);
2308         Lst<SV> la = new Lst<SV>();
2309         for (int j = 0, n = linaa.length; j < n; j++) {
2310           String s = linaa[j];
2311           if (s.indexOf(".") < 0)
2312             try {
2313               la.addLast(SV.newI(Integer.parseInt(s)));
2314               continue;
2315             } catch (Exception e) {
2316             }
2317           else
2318             try {
2319               la.addLast(SV.getVariable(Float.valueOf(Float.parseFloat(s))));
2320               continue;
2321             } catch (Exception ee) {
2322             }
2323           la.addLast(SV.newS(s));
2324         }
2325         sa.addLast(SV.getVariableList(la));
2326       }
2327       return mp.addXObj(SV.getVariableList(sa));
2328     }
2329     x2 = (len == 0 ? SV.newV(T.all, "all") : args[0]);
2330     boolean isAll = (x2.tok == T.all);
2331     if (!isArray1 && x1.tok != T.string)
2332       return mp.binaryOp(opTokenFor(tok), x1, x2);
2333     boolean isScalar1 = SV.isScalar(x1);
2334     boolean isScalar2 = SV.isScalar(x2);
2335 
2336     float[] list1 = null;
2337     float[] list2 = null;
2338     Lst<SV> alist1 = x1.getList();
2339     Lst<SV> alist2 = x2.getList();
2340 
2341     if (isArray1) {
2342       len = alist1.size();
2343     } else if (isScalar1) {
2344       len = Integer.MAX_VALUE;
2345     } else {
2346       sList1 = (PT.split(SV.sValue(x1), "\n"));
2347       list1 = new float[len = sList1.length];
2348       PT.parseFloatArrayData(sList1, list1);
2349     }
2350     if (isAll && tok != T.join) {
2351       float sum = 0f;
2352       if (isArray1) {
2353         for (int i = len; --i >= 0;)
2354           sum += SV.fValue(alist1.get(i));
2355       } else if (!isScalar1) {
2356         for (int i = len; --i >= 0;)
2357           sum += list1[i];
2358       }
2359       return mp.addXFloat(sum);
2360     }
2361     if (tok == T.join && x2.tok == T.string) {
2362       SB sb = new SB();
2363       if (isScalar1) {
2364         sb.append(SV.sValue(x1));
2365       } else {
2366         String s = (isAll ? "" : x2.value.toString());
2367         for (int i = 0; i < len; i++)
2368           sb.append(i > 0 ? s : "").append(SV.sValue(alist1.get(i)));
2369       }
2370       return mp.addXStr(sb.toString());
2371     }
2372 
2373     SV scalar = null;
2374     if (isScalar2) {
2375       scalar = x2;
2376     } else if (x2.tok == T.varray) {
2377       len = Math.min(len, alist2.size());
2378     } else {
2379       sList2 = PT.split(SV.sValue(x2), "\n");
2380       list2 = new float[sList2.length];
2381       PT.parseFloatArrayData(sList2, list2);
2382       len = Math.min(len, list2.length);
2383     }
2384 
2385     T token = opTokenFor(tok);
2386 
2387     if (isArray1 && isAll) {
2388       Lst<SV> llist = new Lst<SV>();
2389       return mp.addXList(addAllLists(x1.getList(), llist));
2390     }
2391     SV a = (isScalar1 ? x1 : null);
2392     SV b;
2393     boolean justVal = (len == Integer.MAX_VALUE);
2394     if (justVal)
2395       len = 1;
2396     SV[] olist = new SV[len];
2397     for (int i = 0; i < len; i++) {
2398       if (isScalar2)
2399         b = scalar;
2400       else if (x2.tok == T.varray)
2401         b = alist2.get(i);
2402       else if (Float.isNaN(list2[i]))
2403         b = SV.getVariable(SV.unescapePointOrBitsetAsVariable(sList2[i]));
2404       else
2405         b = SV.newF(list2[i]);
2406       if (!isScalar1) {
2407         if (isArray1)
2408           a = alist1.get(i);
2409         else if (Float.isNaN(list1[i]))
2410           a = SV.getVariable(SV.unescapePointOrBitsetAsVariable(sList1[i]));
2411         else
2412           a = SV.newF(list1[i]);
2413       }
2414       if (tok == T.join) {
2415         if (a.tok != T.varray) {
2416           Lst<SV> l = new Lst<SV>();
2417           l.addLast(a);
2418           a = SV.getVariableList(l);
2419         }
2420       }
2421       if (!mp.binaryOp(token, a, b))
2422         return false;
2423       olist[i] = mp.getX();
2424     }
2425     return (justVal ? mp.addXObj(olist[0]) : mp.addXAV(olist));
2426   }
2427 
addAllLists(Lst<SV> list, Lst<SV> l)2428   private Lst<SV> addAllLists(Lst<SV> list, Lst<SV> l) {
2429     int n = list.size();
2430     for (int i = 0; i < n; i++) {
2431       SV v = list.get(i);
2432       if (v.tok == T.varray)
2433         addAllLists(v.getList(), l);
2434       else
2435         l.addLast(v);
2436     }
2437     return l;
2438   }
2439 
evaluateLoad(ScriptMathProcessor mp, SV[] args, boolean isFile)2440   private boolean evaluateLoad(ScriptMathProcessor mp, SV[] args,
2441                                boolean isFile)
2442       throws ScriptException {
2443     // file("myfile.xyz")
2444     // load("myfile.png",true)
2445     // load("myfile.txt",1000)
2446     // load("myfile.xyz",0,true)
2447     // load("myfile.json","JSON")
2448     // load("myfile.json","JSON", true)
2449 
2450     if (args.length < 1 || args.length > 3)
2451       return false;
2452     String file = FileManager.fixDOSName(SV.sValue(args[0]));
2453     boolean asBytes = (args.length > 1 && args[1].tok == T.on);
2454     boolean async = (vwr.async
2455         || args.length > 2 && args[args.length - 1].tok == T.on);
2456     int nBytesMax = (args.length > 1 && args[1].tok == T.integer
2457         ? args[1].asInt()
2458         : -1);
2459     boolean asJSON = (args.length > 1
2460         && args[1].asString().equalsIgnoreCase("JSON"));
2461     if (asBytes)
2462       return mp.addXMap(vwr.fm.getFileAsMap(file, null));
2463     boolean isQues = file.startsWith("?");
2464     if (Viewer.isJS && (isQues || async)) {
2465       if (isFile && isQues)
2466         return mp.addXStr("");
2467       file = e.loadFileAsync("load()_", file, mp.oPt, true);
2468       // A ScriptInterrupt will be thrown, and an asynchronous
2469       // file load will initiate, which will return to the script
2470       // at this command when the load operation has completed.
2471       // Note that we need to have just a simple command here.
2472       // The evaluation will be repeated up to this point, so for example,
2473       // x = (i++) + load("?") would increment i twice.
2474     }
2475     String str = isFile ? vwr.fm.getFilePath(file, false, false)
2476         : vwr.getFileAsString4(file, nBytesMax, false, false, true, "script");
2477     try {
2478       return (asJSON ? mp.addXObj(vwr.parseJSON(str)) : mp.addXStr(str));
2479     } catch (Exception e) {
2480       return false;
2481     }
2482   }
2483 
evaluateMath(ScriptMathProcessor mp, SV[] args, int tok)2484   private boolean evaluateMath(ScriptMathProcessor mp, SV[] args, int tok) {
2485     double x = SV.fValue(args[0]);
2486     switch (tok) {
2487     case T.sqrt:
2488       x = Math.sqrt(x);
2489       break;
2490     case T.sin:
2491       x = Math.sin(x * Math.PI / 180);
2492       break;
2493     case T.cos:
2494       x = Math.cos(x * Math.PI / 180);
2495       break;
2496     case T.acos:
2497       x = Math.acos(x) * 180 / Math.PI;
2498       break;
2499     }
2500     return mp.addXFloat((float) x);
2501   }
2502 
2503   //  private boolean evaluateVolume(ScriptVariable[] args) throws ScriptException {
2504   //    ScriptVariable x1 = mp.getX();
2505   //    if (x1.tok != Token.bitset)
2506   //      return false;
2507   //    String type = (args.length == 0 ? null : ScriptVariable.sValue(args[0]));
2508   //    return mp.addX(vwr.getVolume((BitSet) x1.value, type));
2509   //  }
2510 
evaluateMeasure(ScriptMathProcessor mp, SV[] args, int tok)2511   private boolean evaluateMeasure(ScriptMathProcessor mp, SV[] args, int tok)
2512       throws ScriptException {
2513     int nPoints = 0;
2514     switch (tok) {
2515     case T.measure:
2516       // note: min/max are always in Angstroms
2517       // note: order is not important (other than min/max)
2518       // measure({a},{b},{c},{d}, min, max, property, format, units)
2519       // measure({a},{b},{c}, min, max, property, format, units)
2520       // measure({a},{b}, min, max, property, format, units)
2521       // measure({a},{b},{c},{d}, min, max, property, format, units)
2522       // measure({a} {b} "minArray") -- returns array of minimum distance values
2523       String property = null;
2524       Lst<Object> points = new Lst<Object>();
2525       float[] rangeMinMax = new float[] { Float.MAX_VALUE, Float.MAX_VALUE };
2526       String strFormat = null;
2527       String units = null;
2528       boolean isAllConnected = false;
2529       boolean isNotConnected = false;
2530       int rPt = 0;
2531       boolean isNull = false;
2532       RadiusData rd = null;
2533       int nBitSets = 0;
2534       float vdw = Float.MAX_VALUE;
2535       boolean asMinArray = false;
2536       boolean asFloatArray = false;
2537       for (int i = 0; i < args.length; i++) {
2538         switch (args[i].tok) {
2539         case T.bitset:
2540           BS bs = (BS) args[i].value;
2541           if (bs.length() == 0)
2542             isNull = true;
2543           points.addLast(bs);
2544           nPoints++;
2545           nBitSets++;
2546           break;
2547         case T.point3f:
2548           Point3fi v = new Point3fi();
2549           v.setT((P3) args[i].value);
2550           points.addLast(v);
2551           nPoints++;
2552           break;
2553         case T.integer:
2554         case T.decimal:
2555           rangeMinMax[rPt++ % 2] = SV.fValue(args[i]);
2556           break;
2557 
2558         case T.string:
2559           String s = SV.sValue(args[i]);
2560           if (s.startsWith("property_")) {
2561             property = s;
2562             break;
2563           }
2564           if (s.equalsIgnoreCase("vdw") || s.equalsIgnoreCase("vanderwaals"))
2565             vdw = (i + 1 < args.length && args[i + 1].tok == T.integer
2566                 ? args[++i].asInt()
2567                 : 100) / 100f;
2568           else if (s.equalsIgnoreCase("notConnected"))
2569             isNotConnected = true;
2570           else if (s.equalsIgnoreCase("connected"))
2571             isAllConnected = true;
2572           else if (s.equalsIgnoreCase("minArray"))
2573             asMinArray = (nBitSets >= 1);
2574           else if (s.equalsIgnoreCase("asArray") || s.length() == 0)
2575             asFloatArray = (nBitSets >= 1);
2576           else if (Measurement.isUnits(s))
2577             units = s.toLowerCase();
2578           else
2579             strFormat = nPoints + ":" + s;
2580           break;
2581         default:
2582           return false;
2583         }
2584       }
2585       if (nPoints < 2 || nPoints > 4 || rPt > 2
2586           || isNotConnected && isAllConnected)
2587         return false;
2588       if (isNull)
2589         return mp.addXStr("");
2590       if (vdw != Float.MAX_VALUE && (nBitSets != 2 || nPoints != 2))
2591         return mp.addXStr("");
2592       rd = (vdw == Float.MAX_VALUE ? new RadiusData(rangeMinMax, 0, null, null)
2593           : new RadiusData(null, vdw, EnumType.FACTOR, VDW.AUTO));
2594       Object obj = (vwr.newMeasurementData(null, points))
2595           .set(0, null, rd, property, strFormat, units, null, isAllConnected,
2596               isNotConnected, null, true, 0, (short) 0, null, Float.NaN)
2597           .getMeasurements(asFloatArray, asMinArray);
2598       return mp.addXObj(obj);
2599     case T.angle:
2600       if ((nPoints = args.length) != 3 && nPoints != 4)
2601         return false;
2602       break;
2603     default: // distance
2604       if ((nPoints = args.length) != 2)
2605         return false;
2606     }
2607     P3[] pts = new P3[nPoints];
2608     for (int i = 0; i < nPoints; i++) {
2609       if ((pts[i] = mp.ptValue(args[i], null)) == null)
2610         return false;
2611     }
2612     switch (nPoints) {
2613     case 2:
2614       return mp.addXFloat(pts[0].distance(pts[1]));
2615     case 3:
2616       return mp
2617           .addXFloat(Measure.computeAngleABC(pts[0], pts[1], pts[2], true));
2618     case 4:
2619       return mp.addXFloat(
2620           Measure.computeTorsion(pts[0], pts[1], pts[2], pts[3], true));
2621     }
2622     return false;
2623   }
2624 
evaluateModulation(ScriptMathProcessor mp, SV[] args)2625   private boolean evaluateModulation(ScriptMathProcessor mp, SV[] args)
2626       throws ScriptException {
2627     String type = "";
2628     float t = Float.NaN;
2629     P3 t456 = null;
2630     switch (args.length) {
2631     case 0:
2632       break;
2633     case 1:
2634       switch (args[0].tok) {
2635       case T.point3f:
2636         t456 = (P3) args[0].value;
2637         break;
2638       case T.string:
2639         type = args[0].asString();
2640         break;
2641       default:
2642         t = SV.fValue(args[0]);
2643       }
2644       break;
2645     case 2:
2646       type = SV.sValue(args[0]);
2647       t = SV.fValue(args[1]);
2648       break;
2649     default:
2650       return false;
2651     }
2652     if (t456 == null && t < 1e6)
2653       t456 = P3.new3(t, t, t);
2654     SV x = mp.getX();
2655     BS bs = (x.tok == T.bitset ? (BS) x.value : new BS());
2656     return mp.addXList(vwr.ms.getModulationList(bs,
2657         (type + "D").toUpperCase().charAt(0), t456));
2658   }
2659 
2660   /**
2661    * plane() or intersection()
2662    *
2663    * @param mp
2664    * @param args
2665    * @param tok
2666    * @return true
2667    * @throws ScriptException
2668    */
evaluatePlane(ScriptMathProcessor mp, SV[] args, int tok)2669   private boolean evaluatePlane(ScriptMathProcessor mp, SV[] args, int tok)
2670       throws ScriptException {
2671     if (tok == T.hkl && args.length != 3 || tok == T.intersection
2672         && args.length != 2 && args.length != 3 && args.length != 4
2673         || args.length == 0 || args.length > 4)
2674       return false;
2675     P3 pt1, pt2, pt3;
2676     P4 plane = ScriptMathProcessor.planeValue(args[0]);
2677     V3 norm, vTemp;
2678     switch (args.length) {
2679     case 1:
2680       if (args[0].tok == T.bitset) {
2681         BS bs = (BS) args[0].value;
2682         if (bs.cardinality() == 3) {
2683           Lst<P3> pts = vwr.ms.getAtomPointVector(bs);
2684           return mp.addXPt4(Measure.getPlaneThroughPoints(pts.get(0),
2685               pts.get(1), pts.get(2), new V3(), new V3(), new P4()));
2686         }
2687       }
2688       return (plane != null && mp.addXPt4(plane));
2689     case 2:
2690       if (tok == T.intersection) {
2691         // intersection(plane, plane)
2692         // intersection(point, plane)
2693         P4 plane1 = ScriptMathProcessor.planeValue(args[1]);
2694         if (plane1 == null)
2695           return false;
2696         pt3 = new P3();
2697         norm = new V3();
2698         vTemp = new V3();
2699 
2700         if (plane != null) {
2701           Lst<Object> list = Measure.getIntersectionPP(plane, plane1);
2702           if (list == null)
2703             return mp.addXStr("");
2704           return mp.addXList(list);
2705         }
2706         pt2 = mp.ptValue(args[0], null);
2707         if (pt2 == null)
2708           return mp.addXStr("");
2709         return mp.addXPt(
2710             Measure.getIntersection(pt2, null, plane1, pt3, norm, vTemp));
2711       }
2712       //$FALL-THROUGH$
2713     case 3:
2714     case 4:
2715       switch (tok) {
2716       case T.hkl:
2717         // hkl(i,j,k)
2718         return mp.addXPt4(e.getHklPlane(P3.new3(SV.fValue(args[0]),
2719             SV.fValue(args[1]), SV.fValue(args[2])), Float.NaN, false));
2720       case T.intersection:
2721         pt1 = mp.ptValue(args[0], null);
2722         pt2 = mp.ptValue(args[1], null);
2723         if (pt1 == null || pt2 == null)
2724           return mp.addXStr("");
2725         V3 vLine = V3.newV(pt2);
2726         vLine.normalize();
2727         P4 plane2 = ScriptMathProcessor.planeValue(args[2]);
2728         if (plane2 != null) {
2729           // intersection(ptLine, vLine, plane)
2730           pt3 = new P3();
2731           norm = new V3();
2732           vTemp = new V3();
2733           pt1 = Measure.getIntersection(pt1, vLine, plane2, pt3, norm, vTemp);
2734           if (pt1 == null)
2735             return mp.addXStr("");
2736           return mp.addXPt(pt1);
2737         }
2738         pt3 = mp.ptValue(args[2], null);
2739         if (pt3 == null)
2740           return mp.addXStr("");
2741         // intersection(ptLine, vLine, ptCenter, radius)
2742         // intersection(ptLine, vLine, pt2);
2743         // IE intersection of plane perp to line through pt2
2744         V3 v = new V3();
2745         pt3 = P3.newP(pt3);
2746         if (args.length == 3) {
2747           // intersection(ptLine, vLine, pt2);
2748           // IE intersection of plane perp to line through pt2
2749           Measure.projectOntoAxis(pt3, pt1, vLine, v);
2750           return mp.addXPt(pt3);
2751         }
2752         // intersection(ptLine, vLine, ptCenter, radius)
2753         // IE intersection of a line with a sphere -- return list of 0, 1, or 2 points
2754         float r = SV.fValue(args[3]);
2755         P3 ptCenter = P3.newP(pt3);
2756         Measure.projectOntoAxis(pt3, pt1, vLine, v);
2757         float d = ptCenter.distance(pt3);
2758         Lst<P3> l = new Lst<P3>();
2759         if (d == r) {
2760           l.addLast(pt3);
2761         } else if (d < r) {
2762           d = (float) Math.sqrt(r * r - d * d);
2763           v.scaleAdd2(d, vLine, pt3);
2764           l.addLast(P3.newP(v));
2765           v.scaleAdd2(-d, vLine, pt3);
2766           l.addLast(P3.newP(v));
2767         }
2768         return mp.addXList(l);
2769       }
2770       switch (args[0].tok) {
2771       case T.integer:
2772       case T.decimal:
2773         if (args.length == 3) {
2774           // plane(r theta phi)
2775           float r = SV.fValue(args[0]);
2776           float theta = SV.fValue(args[1]); // longitude, azimuthal, in xy plane
2777           float phi = SV.fValue(args[2]); // 90 - latitude, polar, from z
2778           // rotate {0 0 r} about y axis need to stay in the x-z plane
2779           norm = V3.new3(0, 0, 1);
2780           pt2 = P3.new3(0, 1, 0);
2781           Quat q = Quat.newVA(pt2, phi);
2782           q.getMatrix().rotate(norm);
2783           // rotate that vector around z
2784           pt2.set(0, 0, 1);
2785           q = Quat.newVA(pt2, theta);
2786           q.getMatrix().rotate(norm);
2787           pt2.setT(norm);
2788           pt2.scale(r);
2789           plane = new P4();
2790           Measure.getPlaneThroughPoint(pt2, norm, plane);
2791           return mp.addXPt4(plane);
2792         }
2793         break;
2794       case T.bitset:
2795       case T.point3f:
2796         pt1 = mp.ptValue(args[0], null);
2797         pt2 = mp.ptValue(args[1], null);
2798         if (pt2 == null)
2799           return false;
2800         pt3 = (args.length > 2
2801             && (args[2].tok == T.bitset || args[2].tok == T.point3f)
2802                 ? mp.ptValue(args[2], null)
2803                 : null);
2804         norm = V3.newV(pt2);
2805         if (pt3 == null) {
2806           plane = new P4();
2807           if (args.length == 2 || args[2].tok != T.integer
2808               && args[2].tok != T.decimal && !args[2].asBoolean()) {
2809             // plane(<point1>,<point2>) or
2810             // plane(<point1>,<point2>,false)
2811             pt3 = P3.newP(pt1);
2812             pt3.add(pt2);
2813             pt3.scale(0.5f);
2814             norm.sub(pt1);
2815             norm.normalize();
2816           } else if (args[2].tok == T.on) {
2817             // plane(<point1>,<vLine>,true)
2818             pt3 = pt1;
2819           } else {
2820             // plane(<point1>,<point2>,f)
2821             norm.sub(pt1);
2822             pt3 = new P3();
2823             pt3.scaleAdd2(args[2].asFloat(), norm, pt1);
2824           }
2825           Measure.getPlaneThroughPoint(pt3, norm, plane);
2826           return mp.addXPt4(plane);
2827         }
2828         // plane(<point1>,<point2>,<point3>)
2829         // plane(<point1>,<point2>,<point3>,<pointref>)
2830         V3 vAB = new V3();
2831         P3 ptref = (args.length == 4 ? mp.ptValue(args[3], null) : null);
2832         float nd = Measure.getDirectedNormalThroughPoints(pt1, pt2, pt3, ptref,
2833             norm, vAB);
2834         return mp.addXPt4(P4.new4(norm.x, norm.y, norm.z, nd));
2835       }
2836     }
2837     if (args.length != 4)
2838       return false;
2839     float x = SV.fValue(args[0]);
2840     float y = SV.fValue(args[1]);
2841     float z = SV.fValue(args[2]);
2842     float w = SV.fValue(args[3]);
2843     return mp.addXPt4(P4.new4(x, y, z, w));
2844   }
2845 
evaluatePoint(ScriptMathProcessor mp, SV[] args)2846   private boolean evaluatePoint(ScriptMathProcessor mp, SV[] args) {
2847     // point(1.3)  // rounds toward 0
2848     // point(pt, true) // to screen coord
2849     // point(pt, false) // from screen coord
2850     // point(x, y, z)
2851     // point(x, y, z, w)
2852     // point(["{1,2,3", "{2,3,4}"])
2853 
2854     switch (args.length) {
2855     default:
2856       return false;
2857     case 1:
2858       if (args[0].tok == T.decimal || args[0].tok == T.integer)
2859         return mp.addXInt(args[0].asInt());
2860       String s = null;
2861       if (args[0].tok == T.varray) {
2862         Lst<SV> list = args[0].getList();
2863         int len = list.size();
2864         if (len == 0) {
2865           return false;
2866         }
2867         switch (list.get(0).tok) {
2868         case T.integer:
2869         case T.decimal:
2870           break;
2871         case T.string:
2872           s = (String) list.get(0).value;
2873           if (!s.startsWith("{") || Escape.uP(s) instanceof String) {
2874             s = null;
2875             break;
2876           }
2877           Lst<SV> a = new Lst<SV>();
2878           for (int i = 0; i < len; i++) {
2879             a.addLast(SV.getVariable(Escape.uP(SV.sValue(list.get(i)))));
2880           }
2881           return mp.addXList(a);
2882         }
2883         s = "{" + SV.sValue(args[0]) + "}";
2884       }
2885       if (s == null)
2886         s = SV.sValue(args[0]);
2887       Object pt = Escape.uP(s);
2888       return (pt instanceof P3 ? mp.addXPt((P3) pt) : mp.addXStr("" + pt));
2889     case 2:
2890       P3 pt3;
2891       switch (args[1].tok) {
2892       case T.off:
2893       case T.on:
2894         // to/from screen coordinates
2895         switch (args[0].tok) {
2896         case T.point3f:
2897           pt3 = P3.newP((T3) args[0].value);
2898           break;
2899         case T.bitset:
2900           pt3 = vwr.ms.getAtomSetCenter((BS) args[0].value);
2901           break;
2902         default:
2903           return false;
2904         }
2905         if (args[1].tok == T.on) {
2906           // this is TO screen coordinates, 0 at bottom left
2907           vwr.tm.transformPt3f(pt3, pt3);
2908           pt3.y = vwr.tm.height - pt3.y;
2909           if (vwr.antialiased)
2910             pt3.scale(0.5f);
2911         } else {
2912           // this is FROM screen coordinates
2913           if (vwr.antialiased)
2914             pt3.scale(2f);
2915           pt3.y = vwr.tm.height - pt3.y;
2916           vwr.tm.unTransformPoint(pt3, pt3);
2917         }
2918         break;
2919       case T.point3f:
2920         // unitcell transform
2921         Lst<SV> sv = args[0].getList();
2922         if (sv == null || sv.size() != 4)
2923           return false;
2924         P3 pt1 = SV.ptValue(args[1]);
2925         pt3 = P3.newP(SV.ptValue(sv.get(0)));
2926         pt3.scaleAdd2(pt1.x, SV.ptValue(sv.get(1)), pt3);
2927         pt3.scaleAdd2(pt1.y, SV.ptValue(sv.get(2)), pt3);
2928         pt3.scaleAdd2(pt1.z, SV.ptValue(sv.get(3)), pt3);
2929         break;
2930       default:
2931         return false;
2932       }
2933       return mp.addXPt(pt3);
2934     case 3:
2935       return mp.addXPt(
2936           P3.new3(args[0].asFloat(), args[1].asFloat(), args[2].asFloat()));
2937     case 4:
2938       return mp.addXPt4(P4.new4(args[0].asFloat(), args[1].asFloat(),
2939           args[2].asFloat(), args[3].asFloat()));
2940     }
2941   }
2942 
evaluatePrompt(ScriptMathProcessor mp, SV[] args)2943   private boolean evaluatePrompt(ScriptMathProcessor mp, SV[] args) {
2944     //x = prompt("testing")
2945     //x = prompt("testing","defaultInput")
2946     //x = prompt("testing","yes|no|cancel", true)
2947     //x = prompt("testing",["button1", "button2", "button3"])
2948 
2949     if (args.length != 1 && args.length != 2 && args.length != 3)
2950       return false;
2951     String label = SV.sValue(args[0]);
2952     String[] buttonArray = (args.length > 1 && args[1].tok == T.varray
2953         ? SV.strListValue(args[1])
2954         : null);
2955     boolean asButtons = (buttonArray != null || args.length == 1
2956         || args.length == 3 && args[2].asBoolean());
2957     String input = (buttonArray != null ? null
2958         : args.length >= 2 ? SV.sValue(args[1]) : "OK");
2959     String s = "" + vwr.prompt(label, input, buttonArray, asButtons);
2960     return (asButtons && buttonArray != null
2961         ? mp.addXInt(Integer.parseInt(s) + 1)
2962         : mp.addXStr(s));
2963   }
2964 
evaluateQuaternion(ScriptMathProcessor mp, SV[] args, int tok)2965   private boolean evaluateQuaternion(ScriptMathProcessor mp, SV[] args, int tok)
2966       throws ScriptException {
2967     P3 pt0 = null;
2968     // quaternion([quaternion array]) // mean
2969     // quaternion([quaternion array1], [quaternion array2], "relative") //
2970     // difference array
2971     // quaternion(matrix)
2972     // quaternion({atom1}) // quaternion (1st if array)
2973     // quaternion({atomSet}, nMax) // nMax quaternions, by group; 0 for all
2974     // quaternion({atom1}, {atom2}) // difference
2975     // quaternion({atomSet1}, {atomset2}, nMax) // difference array, by group; 0 for all
2976     // quaternion(vector, theta)
2977     // quaternion(q0, q1, q2, q3)
2978     // quaternion("{x, y, z, w"})
2979     // quaternion("best")
2980     // quaternion(center, X, XY)
2981     // quaternion(mcol1, mcol2)
2982     // quaternion(q, "id", center) // draw code
2983     // axisangle(vector, theta)
2984     // axisangle(x, y, z, theta)
2985     // axisangle("{x, y, z, theta"})
2986     int nArgs = args.length;
2987     int nMax = Integer.MAX_VALUE;
2988     boolean isRelative = false;
2989     if (tok == T.quaternion) {
2990       if (nArgs > 1 && args[nArgs - 1].tok == T.string
2991           && ((String) args[nArgs - 1].value).equalsIgnoreCase("relative")) {
2992         nArgs--;
2993         isRelative = true;
2994       }
2995       if (nArgs > 1 && args[nArgs - 1].tok == T.integer
2996           && args[0].tok == T.bitset) {
2997         nMax = args[nArgs - 1].asInt();
2998         if (nMax <= 0)
2999           nMax = Integer.MAX_VALUE - 1;
3000         nArgs--;
3001       }
3002     }
3003 
3004     switch (nArgs) {
3005     case 0:
3006     case 1:
3007     case 4:
3008       break;
3009     case 2:
3010       if (tok == T.quaternion) {
3011         if (args[0].tok == T.varray
3012             && (args[1].tok == T.varray || args[1].tok == T.on))
3013           break;
3014         if (args[0].tok == T.bitset
3015             && (args[1].tok == T.integer || args[1].tok == T.bitset))
3016           break;
3017       }
3018       if ((pt0 = mp.ptValue(args[0], null)) == null
3019           || tok != T.quaternion && args[1].tok == T.point3f)
3020         return false;
3021       break;
3022     case 3:
3023       if (tok != T.quaternion)
3024         return false;
3025       if (args[0].tok == T.point4f) {
3026         if (args[2].tok != T.point3f && args[2].tok != T.bitset)
3027           return false;
3028         break;
3029       }
3030       for (int i = 0; i < 3; i++)
3031         if (args[i].tok != T.point3f && args[i].tok != T.bitset)
3032           return false;
3033       break;
3034     default:
3035       return false;
3036     }
3037     Quat q = null;
3038     Quat[] qs = null;
3039     P4 p4 = null;
3040     switch (nArgs) {
3041     case 0:
3042       return mp.addXPt4(vwr.tm.getRotationQ().toPoint4f());
3043     case 1:
3044     default:
3045       if (tok == T.quaternion && args[0].tok == T.varray) {
3046         Quat[] data1 = e.getQuaternionArray(args[0].getList(), T.list);
3047         Object mean = Quat.sphereMean(data1, null, 0.0001f);
3048         q = (mean instanceof Quat ? (Quat) mean : null);
3049         break;
3050       } else if (tok == T.quaternion && args[0].tok == T.bitset) {
3051         qs = vwr.getAtomGroupQuaternions((BS) args[0].value, nMax);
3052       } else if (args[0].tok == T.matrix3f) {
3053         q = Quat.newM((M3) args[0].value);
3054       } else if (args[0].tok == T.point4f) {
3055         p4 = (P4) args[0].value;
3056       } else {
3057         String s = SV.sValue(args[0]);
3058         Object v = Escape.uP(s.equalsIgnoreCase("best")
3059             ? vwr.getOrientationText(T.best, "best", null).toString()
3060             : s);
3061         if (!(v instanceof P4))
3062           return false;
3063         p4 = (P4) v;
3064       }
3065       if (tok == T.axisangle)
3066         q = Quat.newVA(P3.new3(p4.x, p4.y, p4.z), p4.w);
3067       break;
3068     case 2:
3069       if (tok == T.quaternion) {
3070         if (args[0].tok == T.varray && args[1].tok == T.varray) {
3071           Quat[] data1 = e.getQuaternionArray(args[0].getList(), T.list);
3072           Quat[] data2 = e.getQuaternionArray(args[1].getList(), T.list);
3073           qs = Quat.div(data2, data1, nMax, isRelative);
3074           break;
3075         }
3076         if (args[0].tok == T.varray && args[1].tok == T.on) {
3077           Quat[] data1 = e.getQuaternionArray(args[0].getList(), T.list);
3078           float[] stddev = new float[1];
3079           Quat.sphereMean(data1, stddev, 0.0001f);
3080           return mp.addXFloat(stddev[0]);
3081         }
3082         if (args[0].tok == T.bitset && args[1].tok == T.bitset) {
3083           Quat[] data1 = vwr.getAtomGroupQuaternions((BS) args[0].value,
3084               Integer.MAX_VALUE);
3085           Quat[] data2 = vwr.getAtomGroupQuaternions((BS) args[1].value,
3086               Integer.MAX_VALUE);
3087           qs = Quat.div(data2, data1, nMax, isRelative);
3088           break;
3089         }
3090       }
3091       P3 pt1 = mp.ptValue(args[1], null);
3092       p4 = ScriptMathProcessor.planeValue(args[0]);
3093       if (pt1 != null)
3094         q = Quat.getQuaternionFrame(P3.new3(0, 0, 0), pt0, pt1);
3095       else
3096         q = Quat.newVA(pt0, SV.fValue(args[1]));
3097       break;
3098     case 3:
3099       if (args[0].tok == T.point4f) {
3100         P3 pt = (args[2].tok == T.point3f ? (P3) args[2].value
3101             : vwr.ms.getAtomSetCenter((BS) args[2].value));
3102         return mp.addXStr(Escape.drawQuat(Quat.newP4((P4) args[0].value), "q",
3103             SV.sValue(args[1]), pt, 1f));
3104       }
3105       P3[] pts = new P3[3];
3106       for (int i = 0; i < 3; i++)
3107         pts[i] = (args[i].tok == T.point3f ? (P3) args[i].value
3108             : vwr.ms.getAtomSetCenter((BS) args[i].value));
3109       q = Quat.getQuaternionFrame(pts[0], pts[1], pts[2]);
3110       break;
3111     case 4:
3112       if (tok == T.quaternion)
3113         p4 = P4.new4(SV.fValue(args[1]), SV.fValue(args[2]), SV.fValue(args[3]),
3114             SV.fValue(args[0]));
3115       else
3116         q = Quat.newVA(
3117             P3.new3(SV.fValue(args[0]), SV.fValue(args[1]), SV.fValue(args[2])),
3118             SV.fValue(args[3]));
3119       break;
3120     }
3121     if (qs != null) {
3122       if (nMax != Integer.MAX_VALUE) {
3123         Lst<P4> list = new Lst<P4>();
3124         for (int i = 0; i < qs.length; i++)
3125           list.addLast(qs[i].toPoint4f());
3126         return mp.addXList(list);
3127       }
3128       q = (qs.length > 0 ? qs[0] : null);
3129     }
3130     return mp.addXPt4((q == null ? Quat.newP4(p4) : q).toPoint4f());
3131   }
3132 
3133   private Random rand;
3134 
evaluateRandom(ScriptMathProcessor mp, SV[] args)3135   private boolean evaluateRandom(ScriptMathProcessor mp, SV[] args) {
3136     if (args.length > 3)
3137       return false;
3138     if (rand == null)
3139       rand = new Random();
3140     float lower = 0, upper = 1;
3141     switch (args.length) {
3142     case 3:
3143       rand.setSeed((int) SV.fValue(args[2]));
3144       //$FALL-THROUGH$
3145     case 2:
3146       upper = SV.fValue(args[1]);
3147       //$FALL-THROUGH$
3148     case 1:
3149       lower = SV.fValue(args[0]);
3150       //$FALL-THROUGH$
3151     case 0:
3152       break;
3153     default:
3154       return false;
3155     }
3156     return mp.addXFloat((rand.nextFloat() * (upper - lower)) + lower);
3157   }
3158 
evaluateRowCol(ScriptMathProcessor mp, SV[] args, int tok)3159   private boolean evaluateRowCol(ScriptMathProcessor mp, SV[] args, int tok)
3160       throws ScriptException {
3161     if (args.length != 1)
3162       return false;
3163     int n = args[0].asInt() - 1;
3164     SV x1 = mp.getX();
3165     float[] f;
3166     switch (x1.tok) {
3167     case T.matrix3f:
3168       if (n < 0 || n > 2)
3169         return false;
3170       M3 m = (M3) x1.value;
3171       switch (tok) {
3172       case T.row:
3173         f = new float[3];
3174         m.getRow(n, f);
3175         return mp.addXAF(f);
3176       case T.col:
3177       default:
3178         f = new float[3];
3179         m.getColumn(n, f);
3180         return mp.addXAF(f);
3181       }
3182     case T.matrix4f:
3183       if (n < 0 || n > 2)
3184         return false;
3185       M4 m4 = (M4) x1.value;
3186       switch (tok) {
3187       case T.row:
3188         f = new float[4];
3189         m4.getRow(n, f);
3190         return mp.addXAF(f);
3191       case T.col:
3192       default:
3193         f = new float[4];
3194         m4.getColumn(n, f);
3195         return mp.addXAF(f);
3196       }
3197     case T.varray:
3198       // column of a[][]
3199       Lst<SV> l1 = x1.getList();
3200       Lst<SV> l2 = new Lst<SV>();
3201       for (int i = 0, len = l1.size(); i < len; i++) {
3202         Lst<SV> l3 = l1.get(i).getList();
3203         if (l3 == null)
3204           return mp.addXStr("");
3205         l2.addLast(n < l3.size() ? l3.get(n) : SV.newS(""));
3206       }
3207       return mp.addXList(l2);
3208     }
3209     return false;
3210 
3211   }
3212 
3213   private boolean evaluateIn(ScriptMathProcessor mp, SV[] args)
3214       throws ScriptException {
3215     SV x1 = mp.getX();
3216     switch (args.length) {
3217     case 1:
3218       Lst<SV> lst = args[0].getList();
3219       if (lst != null)
3220         for (int i = 0, n = lst.size(); i < n; i++)
3221           if (SV.areEqual(x1, lst.get(i)))
3222             return mp.addXInt(i + 1);
3223       break;
3224     default:
3225       for (int i = 0; i < args.length; i++)
3226         if (SV.areEqual(x1, args[i]))
3227           return mp.addXInt(i + 1);
3228       break;
3229     }
3230     return mp.addXInt(0);
3231   }
3232 
3233   private boolean evaluateReplace(ScriptMathProcessor mp, SV[] args)
3234       throws ScriptException {
3235     boolean isAll = false;
3236     String sFind, sReplace;
3237     switch (args.length) {
3238     case 0:
3239       isAll = true;
3240       sFind = sReplace = null;
3241       break;
3242     case 3:
3243       isAll = SV.bValue(args[2]);
3244       //$FALL-THROUGH$
3245     case 2:
3246       sFind = SV.sValue(args[0]);
3247       sReplace = SV.sValue(args[1]);
3248       break;
3249     default:
3250       return false;
3251     }
3252     SV x = mp.getX();
3253     if (x.tok == T.varray) {
3254       String[] list = SV.strListValue(x);
3255       String[] l = new String[list.length];
3256       for (int i = list.length; --i >= 0;)
3257         l[i] = (sFind == null ? PT.clean(list[i])
3258             : isAll ? PT.replaceAllCharacters(list[i], sFind, sReplace)
3259                 : PT.rep(list[i], sFind, sReplace));
3260       return mp.addXAS(l);
3261     }
3262     String s = SV.sValue(x);
3263     return mp.addXStr(sFind == null ? PT.clean(s)
3264         : isAll ? PT.replaceAllCharacters(s, sFind, sReplace)
3265             : PT.rep(s, sFind, sReplace));
3266   }
3267 
3268   private boolean evaluateScript(ScriptMathProcessor mp, SV[] args, int tok)
3269       throws ScriptException {
3270     // eval(cmd)
3271     // eval("JSON",json)
3272     // javascript(cmd)
3273     // script(cmd)
3274     // script(cmd, syncTarget)
3275     // show(showCmd)
3276     if ((tok == T.show || tok == T.javascript) && args.length != 1
3277         || args.length == 0)
3278       return false;
3279     String s = SV.sValue(args[0]);
3280     SB sb = new SB();
3281     switch (tok) {
3282     case T.eval:
3283       return (args.length == 2
3284           ? s.equalsIgnoreCase("JSON")
3285               && mp.addXObj(vwr.parseJSON(SV.sValue(args[1])))
3286           : mp.addXObj(vwr.evaluateExpressionAsVariable(s)));
3287     case T.script:
3288       String appID = (args.length == 2 ? SV.sValue(args[1]) : ".");
3289       // options include * > . or an appletID with or without "jmolApplet"
3290       if (!appID.equals("."))
3291         sb.append(vwr.jsEval(appID + "\1" + s));
3292       if (appID.equals(".") || appID.equals("*"))
3293         e.runScriptBuffer(s, sb, true);
3294       break;
3295     case T.show:
3296       e.runScriptBuffer("show " + s, sb, true);
3297       break;
3298     case T.javascript:
3299       return mp.addX(vwr.jsEvalSV(s));
3300     }
3301     s = sb.toString();
3302     float f;
3303     return (Float.isNaN(f = PT.parseFloatStrict(s)) ? mp.addXStr(s)
3304         : s.indexOf(".") >= 0 ? mp.addXFloat(f) : mp.addXInt(PT.parseInt(s)));
3305   }
3306 
3307   /**
3308    * sort() or sort(n) or count() or count("xxxx")
3309    *
3310    * @param mp
3311    * @param args
3312    * @param tok
3313    * @return true if no error
3314    * @throws ScriptException
3315    */
3316   private boolean evaluateSort(ScriptMathProcessor mp, SV[] args, int tok)
3317       throws ScriptException {
3318     if (args.length > 1)
3319       return false;
3320     if (tok == T.sort) {
3321       if (args.length == 1 && args[0].tok == T.string) {
3322         return mp.addX(mp.getX().sortMapArray(args[0].asString()));
3323       }
3324       int n = (args.length == 0 ? 0 : args[0].asInt());
3325       return mp.addX(mp.getX().sortOrReverse(n));
3326     }
3327     SV x = mp.getX();
3328     SV match = (args.length == 0 ? null : args[0]);
3329     if (x.tok == T.string) {
3330       int n = 0;
3331       String s = SV.sValue(x);
3332       if (match == null)
3333         return mp.addXInt(0);
3334       String m = SV.sValue(match);
3335       for (int i = 0; i < s.length(); i++) {
3336         int pt = s.indexOf(m, i);
3337         if (pt < 0)
3338           break;
3339         n++;
3340         i = pt;
3341       }
3342       return mp.addXInt(n);
3343     }
3344     Lst<SV> counts = new Lst<SV>();
3345     SV last = null;
3346     SV count = null;
3347     Lst<SV> xList = SV.getVariable(x.value).sortOrReverse(0).getList();
3348     if (xList == null)
3349       return (match == null ? mp.addXStr("") : mp.addXInt(0));
3350     for (int i = 0, nLast = xList.size(); i <= nLast; i++) {
3351       SV a = (i == nLast ? null : xList.get(i));
3352       if (match != null && a != null && !SV.areEqual(a, match))
3353         continue;
3354       if (SV.areEqual(a, last)) {
3355         count.intValue++;
3356         continue;
3357       } else if (last != null) {
3358         Lst<SV> y = new Lst<SV>();
3359         y.addLast(last);
3360         y.addLast(count);
3361         counts.addLast(SV.getVariableList(y));
3362       }
3363       count = SV.newI(1);
3364       last = a;
3365     }
3366     if (match == null)
3367       return mp.addX(SV.getVariableList(counts));
3368     if (counts.isEmpty())
3369       return mp.addXInt(0);
3370     return mp.addX(counts.get(0).getList().get(1));
3371   }
3372 
evaluateString(ScriptMathProcessor mp, int tok, SV[] args)3373   private boolean evaluateString(ScriptMathProcessor mp, int tok, SV[] args)
3374       throws ScriptException {
3375     SV x = mp.getX();
3376     String sArg = (args.length > 0 ? SV.sValue(args[0])
3377         : tok == T.trim ? "" : "\n");
3378     switch (args.length) {
3379     case 0:
3380       break;
3381     case 1:
3382       if (args[0].tok == T.on) {
3383         return mp.addX(SV.getVariable(PT.getTokens(x.asString())));
3384       }
3385       break;
3386     case 2:
3387       if (x.tok == T.varray)
3388         break;
3389       if (tok == T.split) {
3390         x = SV.getVariable(PT.split(
3391             PT.rep((String) x.value, "\n\r", "\n").replace('\r', '\n'), "\n"));
3392         break;
3393       }
3394       //$FALL-THROUGH$
3395     default:
3396       return false;
3397     }
3398 
3399     if (x.tok == T.varray && tok != T.trim
3400         && (tok != T.split || args.length == 2)) {
3401       mp.addX(x);
3402       return evaluateList(mp, tok, args);
3403     }
3404     String s = (tok == T.split && x.tok == T.bitset
3405         || tok == T.trim && x.tok == T.varray ? null : SV.sValue(x));
3406     switch (tok) {
3407     case T.split:
3408       if (x.tok == T.bitset) {
3409         BS bsSelected = (BS) x.value;
3410         int modelCount = vwr.ms.mc;
3411         Lst<SV> lst = new Lst<SV>();
3412         for (int i = 0; i < modelCount; i++) {
3413           BS bs = vwr.getModelUndeletedAtomsBitSet(i);
3414           bs.and(bsSelected);
3415           lst.addLast(SV.getVariable(bs));
3416         }
3417         return mp.addXList(lst);
3418       }
3419       return mp.addXAS(PT.split(s, sArg));
3420     case T.join:
3421       if (s.length() > 0 && s.charAt(s.length() - 1) == '\n')
3422         s = s.substring(0, s.length() - 1);
3423       return mp.addXStr(PT.rep(s, "\n", sArg));
3424     case T.trim:
3425       if (s != null)
3426         return mp.addXStr(PT.trim(s, sArg));
3427       String[] list = SV.strListValue(x);
3428       for (int i = list.length; --i >= 0;)
3429         list[i] = PT.trim(list[i], sArg);
3430       return mp.addXAS(list);
3431     }
3432     return mp.addXStr("");
3433   }
3434 
evaluateSubstructure(ScriptMathProcessor mp, SV[] args, int tok, boolean isSelector)3435   private boolean evaluateSubstructure(ScriptMathProcessor mp, SV[] args,
3436                                        int tok, boolean isSelector)
3437       throws ScriptException {
3438     // select substucture(....) legacy - was same as smiles(), now search()
3439     // select smiles(...)
3440     // select search(...)  now same as substructure
3441     // print {*}.search(...)
3442     if (args.length == 0 || isSelector && args.length > 1)
3443       return false;
3444     BS bs = new BS();
3445     String pattern = SV.sValue(args[0]);
3446     if (pattern.length() > 0)
3447       try {
3448         BS bsSelected = (isSelector ? (BS) mp.getX().value
3449             : args.length == 2 && args[1].tok == T.bitset ? (BS) args[1].value
3450                 : vwr.getModelUndeletedAtomsBitSet(-1));
3451         bs = vwr.getSmilesMatcher().getSubstructureSet(pattern, vwr.ms.at,
3452             vwr.ms.ac, bsSelected,
3453             (tok == T.smiles ? JC.SMILES_TYPE_SMILES : JC.SMILES_TYPE_SMARTS));
3454       } catch (Exception ex) {
3455         e.evalError(ex.getMessage(), null);
3456       }
3457     return mp.addXBs(bs);
3458   }
3459 
3460   @SuppressWarnings("unchecked")
evaluateSymop(ScriptMathProcessor mp, SV[] args, boolean haveBitSet)3461   private boolean evaluateSymop(ScriptMathProcessor mp, SV[] args,
3462                                 boolean haveBitSet)
3463       throws ScriptException {
3464 
3465     // In the following, "op" can be the operator number in a space group
3466     // or a string representation of the operator, such as "x,-y,z"
3467 
3468     // x = y.symop(op,atomOrPoint)
3469     // Returns the point that is the result of the transformation of atomOrPoint
3470     // via a crystallographic symmetry operation. The atom set y selects the unit
3471     // cell and spacegroup to be used. If only one model is present, this can simply be all.
3472     // Otherwise, it could be any atom or group of atoms from any model, for example
3473     // {*/1.2} or {atomno=1}. The first parameter, op, is a symmetry operation.
3474     // This can be the 1-based index of a symmetry operation in a file (use show spacegroup to get this listing)
3475     // or a specific Jones-Faithful expression in quotes such as "x,1/2-y,z".
3476 
3477     // x = y.symop(op,"label")
3478     // This form of the .symop() function returns a set of draw commands that describe
3479     // the symmetry operation in terms of rotation axes, inversion centers, planes, and
3480     // translational vectors. The draw objects will all have IDs starting with whatever
3481     // is given for the label.
3482 
3483     // x = y.symop(op)
3484     // Returns the 4x4 matrix associated with this operator.
3485 
3486     // x = y.symop(n,"time")
3487     // Returns the time reversal of a symmetry operator in a magnetic space group
3488 
3489     // x = y.symop(atomOrPoint, atomOrPoint)
3490 
3491     // x = y.symop(n, [h,k,l])
3492     // adds a lattice translation to the symmetry operation
3493 
3494     // x = y.symop(n,...,"cif2")
3495     // return a <symop> <translation> as nn [a b c] suitable for CIF2 inclusion
3496 
3497 
3498     SV x1 = (haveBitSet ? mp.getX() : null);
3499     if (x1 != null && x1.tok != T.bitset)
3500       return false;
3501     BS bsAtoms = (x1 == null ? null : (BS) x1.value);
3502     if (bsAtoms == null && vwr.ms.mc == 1)
3503       bsAtoms = vwr.getModelUndeletedAtomsBitSet(0);
3504     int narg = args.length;
3505     if (narg == 0) {
3506       if (bsAtoms.isEmpty())
3507         return false;
3508       String[] ops = PT.split(PT.trim((String) vwr
3509           .getSymTemp().getSpaceGroupInfo(vwr.ms, null,
3510               vwr.ms.at[bsAtoms.nextSetBit(0)].mi, false, null)
3511           .get("symmetryInfo"), "\n"), "\n");
3512       Lst<String[]> lst = new Lst<String[]>();
3513       for (int i = 0, n = ops.length; i < n; i++)
3514         lst.addLast(PT.split(ops[i], "\t"));
3515       return mp.addXList(lst);
3516     }
3517     String xyz = null;
3518     int iOp = Integer.MIN_VALUE;
3519     int apt = 0;
3520     switch (args[0].tok) {
3521     case T.string:
3522       xyz = SV.sValue(args[0]);
3523       apt++;
3524       break;
3525     case T.matrix4f:
3526       xyz = args[0].escape();
3527       apt++;
3528       break;
3529     case T.integer:
3530       iOp = args[0].asInt();
3531       apt++;
3532       break;
3533     }
3534     if (bsAtoms == null) {
3535       if (apt < narg && args[apt].tok == T.bitset)
3536         (bsAtoms = new BS()).or((BS) args[apt].value);
3537       if (apt + 1 < narg && args[apt + 1].tok == T.bitset)
3538         (bsAtoms == null ? (bsAtoms = new BS()) : bsAtoms)
3539             .or((BS) args[apt + 1].value);
3540     }
3541     // allow for [ h k l ] lattice translation
3542     P3 trans = null;
3543     if (narg > apt && args[apt].tok == T.varray) {
3544       List<SV> a = args[apt++].getList();
3545       if (a.size() != 3)
3546         return false;
3547       trans = P3.new3(SV.fValue(a.get(0)), SV.fValue(a.get(1)),
3548           SV.fValue(a.get(2)));
3549     } else if (narg > apt && args[apt].tok == T.integer) {
3550       SimpleUnitCell.ijkToPoint3f(SV.iValue(args[apt++]), trans = new P3(), 0,
3551           0);
3552     }
3553     P3 pt1 = null, pt2 = null;
3554     if ((pt1 = (narg > apt ? mp.ptValue(args[apt], bsAtoms) : null)) != null)
3555       apt++;
3556     if ((pt2 = (narg > apt ? mp.ptValue(args[apt], bsAtoms) : null)) != null)
3557       apt++;
3558     int nth = (pt2 != null && args.length > apt && iOp == Integer.MIN_VALUE
3559         && args[apt].tok == T.integer ? args[apt].intValue : -1);
3560     if (nth >= 0) // 0 here means "give me all of them"
3561       apt++;
3562     if (iOp == Integer.MIN_VALUE)
3563       iOp = 0;
3564     Map<String, ?> map = null;
3565     if (xyz != null) {
3566       if (apt == narg) {
3567         map = vwr.ms.getPointGroupInfo(null);
3568       } else if (args[apt].tok == T.hash) {
3569         map = args[apt].getMap();
3570       }
3571     }
3572     if (map != null) {
3573       M3 m;
3574       // pointgroup.
3575       int pt = xyz.indexOf('.');
3576       int p1 = xyz.indexOf('^');
3577       if (p1 > 0) {
3578         nth = PT.parseInt(xyz.substring(p1 + 1));
3579       } else {
3580         p1 = xyz.length();
3581         nth = 1;
3582       }
3583       if (pt > 0 && p1 > pt + 1) {
3584         iOp = PT.parseInt(xyz.substring(pt + 1, p1));
3585         if (iOp < 1)
3586           iOp = 1;
3587         p1 = pt;
3588       } else {
3589         iOp = 1;
3590       }
3591       xyz = xyz.substring(0, p1);
3592       Object o = map.get(xyz + "_m");
3593       if (o == null) {
3594         o = map.get(xyz);
3595         return (o == null ? mp.addXStr("") : mp.addXObj(o));
3596       }
3597       P3 centerPt;
3598       try {
3599         if (o instanceof SV) {
3600           centerPt = (P3) ((SV) map.get("center")).value;
3601           SV obj = (SV) o;
3602           if (obj.tok == T.matrix3f) {
3603             m = (M3) obj.value;
3604           } else if (obj.tok == T.varray) {
3605             m = (M3) obj.getList().get(iOp - 1).value;
3606           } else {
3607             return false;
3608           }
3609         } else {
3610           centerPt = (P3) map.get("center");
3611           if (o instanceof M3) {
3612           m = (M3) o;
3613         } else {
3614           m = ((Lst<M3>) o).get(iOp - 1);
3615         }
3616         }
3617         M3 m0 = m;
3618         m = M3.newM3(m);
3619         if (nth > 1) {
3620           for (int i = 1; i < nth; i++) {
3621             m.mul(m0);
3622           }
3623         }
3624         if (pt1 == null)
3625           return mp.addXObj(m);
3626         pt1 = P3.newP(pt1);
3627         pt1.sub(centerPt);
3628         m.rotate(pt1);
3629         pt1.add(centerPt);
3630         return mp.addXPt(pt1);
3631       } catch (Exception e) {
3632         return false;
3633       }
3634     }
3635     String desc = (narg == apt
3636         ? (pt2 != null ? "all" : pt1 != null ? "point" : "matrix")
3637         : SV.sValue(args[apt++]).toLowerCase());
3638 
3639     return (bsAtoms != null && !bsAtoms.isEmpty() && apt == args.length
3640         && mp.addXObj(vwr.getSymmetryInfo(bsAtoms.nextSetBit(0), xyz, iOp,
3641             trans, pt1, pt2, T.array, desc, 0, nth, 0)));
3642   }
3643 
evaluateTensor(ScriptMathProcessor mp, SV[] args)3644   private boolean evaluateTensor(ScriptMathProcessor mp, SV[] args)
3645       throws ScriptException {
3646     // {*}.tensor()
3647     // {*}.tensor("isc")            // only within this atom set
3648     // {atomindex=1}.tensor("isc")  // all to this atom
3649     // {*}.tensor("efg","eigenvalues")
3650     //     tensor(t,what)
3651     boolean isTensor = (args.length == 2 && args[1].tok == T.tensor);
3652     SV x = (isTensor ? null : mp.getX());
3653     if (args.length > 2 || !isTensor && x.tok != T.bitset)
3654       return false;
3655     BS bs = (BS) x.value;
3656     String tensorType = (isTensor || args.length == 0 ? null
3657         : SV.sValue(args[0]).toLowerCase());
3658     JmolNMRInterface calc = vwr.getNMRCalculation();
3659     if ("unique".equals(tensorType))
3660       return mp.addXBs(calc.getUniqueTensorSet(bs));
3661     String infoType = (args.length < 2 ? null
3662         : SV.sValue(args[1]).toLowerCase());
3663     if (isTensor) {
3664       return mp.addXObj(((Tensor) args[0].value).getInfo(infoType));
3665     }
3666     return mp.addXList(calc.getTensorInfo(tensorType, infoType, bs));
3667   }
3668 
evaluateUserFunction(ScriptMathProcessor mp, String name, SV[] args, int tok, boolean isSelector)3669   private boolean evaluateUserFunction(ScriptMathProcessor mp, String name,
3670                                        SV[] args, int tok, boolean isSelector)
3671       throws ScriptException {
3672     SV x1 = null;
3673     if (isSelector) {
3674       x1 = mp.getX();
3675       switch (x1.tok) {
3676       case T.bitset:
3677         break;
3678       case T.hash:
3679         // really xx.yyy where yyy is a function;
3680         if (args.length > 0)
3681           return false;
3682         x1 = x1.getMap().get(name);
3683         return (x1 == null ? mp.addXStr("") : mp.addX(x1));
3684       default:
3685         return false;
3686       }
3687     }
3688     name = name.toLowerCase();
3689     mp.wasX = false;
3690     Lst<SV> params = new Lst<SV>();
3691     for (int i = 0; i < args.length; i++) {
3692       params.addLast(args[i]);
3693     }
3694     if (isSelector) {
3695       return mp
3696           .addXObj(e.getBitsetProperty((BS) x1.value, null, tok, null, null,
3697               x1.value, new Object[] { name, params }, false, x1.index, false));
3698     }
3699     SV var = e.getUserFunctionResult(name, params, null);
3700     return (var == null ? false : mp.addX(var));
3701   }
3702 
evaluateWithin(ScriptMathProcessor mp, SV[] args)3703   private boolean evaluateWithin(ScriptMathProcessor mp, SV[] args)
3704       throws ScriptException {
3705     int len = args.length;
3706     if (len < 1 || len > 5)
3707       return false;
3708     if (len == 1 && args[0].tok == T.bitset)
3709       return mp.addX(args[0]);
3710     float distance = 0;
3711     Object withinSpec = args[0].value;
3712     String withinStr = "" + withinSpec;
3713     int tok = args[0].tok;
3714     if (tok == T.string)
3715       tok = T.getTokFromName(withinStr);
3716     ModelSet ms = vwr.ms;
3717     boolean isVdw = false;
3718     boolean isWithinModelSet = false;
3719     boolean isWithinGroup = false;
3720     boolean isDistance = false;
3721     RadiusData rd = null;
3722     switch (tok) {
3723     case T.vanderwaals:
3724       isVdw = true;
3725       withinSpec = null;
3726       //$FALL-THROUGH$
3727     case T.decimal:
3728     case T.integer:
3729       isDistance = true;
3730       if (len < 2
3731           || len == 3 && args[1].tok == T.varray && args[2].tok != T.varray)
3732         return false;
3733       distance = (isVdw ? 100 : SV.fValue(args[0]));
3734       switch (tok = args[1].tok) {
3735       case T.on:
3736       case T.off:
3737         isWithinModelSet = args[1].asBoolean();
3738         if (len > 2 && SV.sValue(args[2]).equalsIgnoreCase("unitcell"))
3739           tok = T.unitcell;
3740         len = 0;
3741         break;
3742       case T.string:
3743         String s = SV.sValue(args[1]);
3744         if (s.startsWith("$"))
3745           return mp.addXBs(getAtomsNearSurface(distance, s.substring(1)));
3746         if (s.equalsIgnoreCase("group")) {
3747           isWithinGroup = true;
3748           tok = T.group;
3749         } else if (s.equalsIgnoreCase("vanderwaals")
3750             || s.equalsIgnoreCase("vdw")) {
3751           withinSpec = null;
3752           isVdw = true;
3753           tok = T.vanderwaals;
3754         } else if (s.equalsIgnoreCase("unitcell")) {
3755           tok = T.unitcell;
3756         } else {
3757           return false;
3758         }
3759         break;
3760       }
3761       break;
3762     case T.varray:
3763       if (len == 1) {
3764         withinSpec = args[0].asString(); // units ids8
3765         tok = T.nada;
3766       }
3767       break;
3768     case T.branch:
3769       return (len == 3 && args[1].value instanceof BS
3770           && args[2].value instanceof BS
3771           && mp.addXBs(vwr.getBranchBitSet(((BS) args[2].value).nextSetBit(0),
3772               ((BS) args[1].value).nextSetBit(0), true)));
3773     case T.smiles:
3774     case T.substructure: // same as "SMILES"
3775     case T.search:
3776       // within("smiles", "...", {bitset})
3777       // within("smiles", "...", {bitset})
3778       BS bsSelected = null;
3779       boolean isOK = true;
3780       switch (len) {
3781       case 2:
3782         break;
3783       case 3:
3784         isOK = (args[2].tok == T.bitset);
3785         if (isOK)
3786           bsSelected = (BS) args[2].value;
3787         break;
3788       default:
3789         isOK = false;
3790       }
3791       if (!isOK)
3792         e.invArg();
3793       return mp.addXObj(e.getSmilesExt().getSmilesMatches(SV.sValue(args[1]),
3794           null, bsSelected, null,
3795           tok == T.search ? JC.SMILES_TYPE_SMARTS : JC.SMILES_TYPE_SMILES,
3796           mp.asBitSet, false));
3797     }
3798 
3799     if (withinSpec instanceof String) {
3800       if (tok == T.nada) {
3801         tok = T.spec_seqcode;
3802         if (len > 2)
3803           return false;
3804         len = 2;
3805       }
3806     } else if (!isDistance) {
3807       return false;
3808     }
3809 
3810     switch (len) {
3811     case 1:
3812       // within (sheet)
3813       // within (helix)
3814       // within (boundbox)
3815       switch (tok) {
3816       case T.helix:
3817       case T.sheet:
3818       case T.boundbox:
3819         return mp.addXBs(ms.getAtoms(tok, null));
3820       case T.basepair:
3821         return mp.addXBs(ms.getAtoms(tok, ""));
3822       case T.spec_seqcode:
3823         return mp.addXBs(ms.getAtoms(T.sequence, withinStr));
3824       }
3825       return false;
3826     case 2:
3827       // within (atomName, "XX,YY,ZZZ")
3828       switch (tok) {
3829       case T.spec_seqcode:
3830         tok = T.sequence;
3831         break;
3832       case T.identifier:
3833       case T.atomname:
3834       case T.atomtype:
3835       case T.basepair:
3836       case T.sequence:
3837       case T.dssr:
3838       case T.rna3d:
3839       case T.domains:
3840       case T.validation:
3841         return mp.addXBs(vwr.ms.getAtoms(tok, SV.sValue(args[1])));
3842       case T.cell:
3843       case T.centroid:
3844         return mp.addXBs(vwr.ms.getAtoms(tok, SV.ptValue(args[1])));
3845       }
3846       break;
3847     case 3:
3848       switch (tok) {
3849       case T.on:
3850       case T.off:
3851       case T.group:
3852       case T.vanderwaals:
3853       case T.unitcell:
3854       case T.plane:
3855       case T.hkl:
3856       case T.coord:
3857       case T.point3f:
3858       case T.varray:
3859         break;
3860       case T.sequence:
3861         // within ("sequence", "CII", *.ca)
3862         withinStr = SV.sValue(args[2]);
3863         break;
3864       default:
3865         return false;
3866       }
3867       // within (distance, group, {atom collection})
3868       // within (distance, true|false, {atom collection})
3869       // within (distance, plane|hkl, [plane definition] )
3870       // within (distance, coord, [point or atom center] )
3871       break;
3872     }
3873     P4 plane = null;
3874     P3 pt = null;
3875     Lst<SV> pts1 = null;
3876     int last = args.length - 1;
3877     switch (args[last].tok) {
3878     case T.point4f:
3879       plane = (P4) args[last].value;
3880       break;
3881     case T.point3f:
3882       pt = (P3) args[last].value;
3883       if (SV.sValue(args[1]).equalsIgnoreCase("hkl"))
3884         plane = e.getHklPlane(pt, Float.NaN, false);
3885       break;
3886     case T.varray:
3887       pts1 = (last == 2 && args[1].tok == T.varray ? args[1].getList() : null);
3888       pt = (last == 2 ? SV.ptValue(args[1])
3889           : last == 1 ? P3.new3(Float.NaN, 0, 0) : null);
3890       break;
3891     }
3892     if (plane != null)
3893       return mp.addXBs(ms.getAtomsNearPlane(distance, plane));
3894     BS bs = (args[last].tok == T.bitset ? (BS) args[last].value : null);
3895     if (last > 0 && pt == null && pts1 == null && bs == null)
3896       return false;
3897     // if we have anything, it must have a point or an array or a plane or a bitset from here on out
3898     if (tok == T.unitcell) {
3899       boolean asMap = isWithinModelSet;
3900       return ((bs != null || pt != null) && mp
3901           .addXObj(vwr.ms.getUnitCellPointsWithin(distance, bs, pt, asMap)));
3902     }
3903     if (pt != null || pts1 != null) {
3904       if (args[last].tok == T.varray) {
3905         // within(dist, pt, [pt1, pt2, pt3...])
3906         // within(dist, [pt1, pt2, pt3...])
3907         Lst<SV> sv = args[last].getList();
3908         P3[] ap3 = new P3[sv.size()];
3909         for (int i = ap3.length; --i >= 0;)
3910           ap3[i] = SV.ptValue(sv.get(i));
3911         P3[] ap31 = null;
3912         if (pts1 != null) {
3913           ap31 = new P3[pts1.size()];
3914           for (int i = ap31.length; --i >= 0;)
3915             ap31[i] = SV.ptValue(pts1.get(i));
3916         }
3917         Object[] ret = new Object[1];
3918         switch (PointIterator.withinDistPoints(distance, pt, ap3, ap31, ret)) {
3919         case T.point:
3920           return mp.addXPt((P3) ret[0]);
3921         case T.list:
3922           return mp.addXList((Lst<?>) ret[0]);
3923         case T.array: //
3924           return mp.addXAI((int[]) ret[0]);
3925         case T.string: // ""  return
3926           return mp.addXStr((String) ret[0]);
3927         default:
3928           return false; // error return
3929         }
3930       }
3931       return mp.addXBs(vwr.getAtomsNearPt(distance, pt));
3932     }
3933 
3934     if (tok == T.sequence)
3935       return mp.addXBs(vwr.ms.getSequenceBits(withinStr, bs, new BS()));
3936     if (bs == null)
3937       bs = new BS();
3938     if (!isDistance) {
3939       try {
3940       return mp.addXBs(vwr.ms.getAtoms(tok, bs));
3941       } catch (Exception e) {
3942         return false;
3943       }
3944     }
3945     if (isWithinGroup)
3946       return mp.addXBs(vwr.getGroupsWithin((int) distance, bs));
3947     if (isVdw) {
3948       rd = new RadiusData(null, (distance > 10 ? distance / 100 : distance),
3949           (distance > 10 ? EnumType.FACTOR : EnumType.OFFSET), VDW.AUTO);
3950       if (distance < 0)
3951         distance = 0; // not used, but this prevents a diversion
3952     }
3953     return mp.addXBs(
3954         vwr.ms.getAtomsWithinRadius(distance, bs, isWithinModelSet, rd));
3955   }
3956 
evaluateWrite(ScriptMathProcessor mp, SV[] args)3957   private boolean evaluateWrite(ScriptMathProcessor mp, SV[] args)
3958       throws ScriptException {
3959     switch (args.length) {
3960     case 0:
3961       return false;
3962     case 1:
3963       String type = args[0].asString().toUpperCase();
3964       if (type.equals("PNGJ")) {
3965         return mp.addXMap(vwr.fm.getFileAsMap(null, "PNGJ"));
3966       }
3967       if (PT.isOneOf(type, ";ZIP;ZIPALL;JMOL;")) {
3968         Map<String, Object> params = new Hashtable<String, Object>();
3969         OC oc = new OC();
3970         params.put("outputChannel", oc);
3971         vwr.createZip(null, type, params);
3972         BufferedInputStream bis = Rdr.getBIS(oc.toByteArray());
3973         params = new Hashtable<String, Object>();
3974         vwr.readFileAsMap(bis, params, null);
3975         return mp.addXMap(params);
3976       }
3977       break;
3978     }
3979     return mp.addXStr(e.getCmdExt().dispatch(T.write, true, args));
3980   }
3981 
3982   ///////// private methods used by evaluateXXXXX ////////
3983 
getAtomsNearSurface(float distance, String surfaceId)3984   private BS getAtomsNearSurface(float distance, String surfaceId) {
3985     Object[] data = new Object[] { surfaceId, null, null };
3986     if (e.getShapePropertyData(JC.SHAPE_ISOSURFACE, "getVertices", data))
3987       return getAtomsNearPts(distance, (T3[]) data[1], (BS) data[2]);
3988     data[1] = Integer.valueOf(0);
3989     data[2] = Integer.valueOf(-1);
3990     if (e.getShapePropertyData(JC.SHAPE_DRAW, "getCenter", data))
3991       return vwr.getAtomsNearPt(distance, (P3) data[2]);
3992     data[1] = Float.valueOf(distance);
3993     if (e.getShapePropertyData(JC.SHAPE_POLYHEDRA, "getAtomsWithin", data))
3994       return (BS) data[2];
3995     return new BS();
3996   }
3997 
getAtomsNearPts(float distance, T3[] points, BS bsInclude)3998   private BS getAtomsNearPts(float distance, T3[] points, BS bsInclude) {
3999     BS bsResult = new BS();
4000     if (points.length == 0 || bsInclude != null && bsInclude.isEmpty())
4001       return bsResult;
4002     if (bsInclude == null)
4003       bsInclude = BSUtil.setAll(points.length);
4004     Atom[] at = vwr.ms.at;
4005     for (int i = vwr.ms.ac; --i >= 0;) {
4006       Atom atom = at[i];
4007       for (int j = bsInclude.nextSetBit(0); j >= 0; j = bsInclude
4008           .nextSetBit(j + 1))
4009         if (atom.distance(points[j]) < distance) {
4010           bsResult.set(i);
4011           break;
4012         }
4013     }
4014     return bsResult;
4015   }
4016 
4017   @SuppressWarnings("unchecked")
getMinMax(Object floatOrSVArray, int tok)4018   public Object getMinMax(Object floatOrSVArray, int tok) {
4019     float[] data = null;
4020     Lst<SV> sv = null;
4021     int ndata = 0;
4022     Map<String, Integer> htPivot = null;
4023     while (true) {
4024       if (AU.isAF(floatOrSVArray)) {
4025         if (tok == T.pivot)
4026           return "NaN";
4027         data = (float[]) floatOrSVArray;
4028         ndata = data.length;
4029         if (ndata == 0)
4030           break;
4031       } else if (floatOrSVArray instanceof Lst<?>) {
4032         sv = (Lst<SV>) floatOrSVArray;
4033         ndata = sv.size();
4034         if (ndata == 0) {
4035           if (tok != T.pivot)
4036             break;
4037         } else {
4038           if (tok != T.pivot) {
4039             SV sv0 = sv.get(0);
4040             if (sv0.tok == T.point3f)
4041               return getMinMaxPoint(sv, tok);
4042             if (sv0.tok == T.string && ((String) sv0.value).startsWith("{")) {
4043               Object pt = SV.ptValue(sv0);
4044               if (pt instanceof P3)
4045                 return getMinMaxPoint(sv, tok);
4046               if (pt instanceof P4)
4047                 return getMinMaxQuaternion(sv, tok);
4048               break;
4049             }
4050           }
4051         }
4052       } else {
4053         break;
4054       }
4055       double sum;
4056       int minMax;
4057       boolean isMin = false;
4058       switch (tok) {
4059       case T.pivot:
4060         htPivot = new Hashtable<String, Integer>();
4061         sum = minMax = 0;
4062         break;
4063       case T.min:
4064         isMin = true;
4065         sum = Float.MAX_VALUE;
4066         minMax = Integer.MAX_VALUE;
4067         break;
4068       case T.max:
4069         sum = -Float.MAX_VALUE;
4070         minMax = -Integer.MAX_VALUE;
4071         break;
4072       default:
4073         sum = minMax = 0;
4074       }
4075       double sum2 = 0;
4076       int n = 0;
4077       boolean isInt = true;
4078       boolean isPivot = (tok == T.pivot);
4079       for (int i = ndata; --i >= 0;) {
4080         SV svi = (sv == null ? SV.vF : sv.get(i));
4081         float v = (isPivot ? 1 : data == null ? SV.fValue(svi) : data[i]);
4082         if (Float.isNaN(v))
4083           continue;
4084         n++;
4085         switch (tok) {
4086         case T.sum2:
4087         case T.stddev:
4088           sum2 += ((double) v) * v;
4089           //$FALL-THROUGH$
4090         case T.sum:
4091         case T.average:
4092           sum += v;
4093           break;
4094         case T.pivot:
4095           isInt &= (svi.tok == T.integer);
4096           String key = svi.asString();
4097           Integer ii = htPivot.get(key);
4098           htPivot.put(key,
4099               (ii == null ? new Integer(1) : new Integer(ii.intValue() + 1)));
4100           break;
4101         case T.min:
4102         case T.max:
4103           isInt &= (svi.tok == T.integer);
4104           if (isMin == (v < sum)) {
4105             sum = v;
4106             if (isInt)
4107               minMax = svi.intValue;
4108           }
4109           break;
4110         }
4111       }
4112       if (tok == T.pivot) {
4113         return htPivot;
4114       }
4115       if (n == 0)
4116         break;
4117       switch (tok) {
4118       case T.average:
4119         sum /= n;
4120         break;
4121       case T.stddev:
4122         if (n == 1)
4123           break;
4124         sum = Math.sqrt((sum2 - sum * sum / n) / (n - 1));
4125         break;
4126       case T.min:
4127       case T.max:
4128         if (isInt)
4129           return Integer.valueOf(minMax);
4130         break;
4131       case T.sum:
4132         break;
4133       case T.sum2:
4134         sum = sum2;
4135         break;
4136       }
4137       return Float.valueOf((float) sum);
4138     }
4139     return "NaN";
4140   }
4141 
4142   /**
4143    * calculates the statistical value for x, y, and z independently
4144    *
4145    * @param pointOrSVArray
4146    * @param tok
4147    * @return Point3f or "NaN"
4148    */
4149   @SuppressWarnings("unchecked")
getMinMaxPoint(Object pointOrSVArray, int tok)4150   private Object getMinMaxPoint(Object pointOrSVArray, int tok) {
4151     P3[] data = null;
4152     Lst<SV> sv = null;
4153     int ndata = 0;
4154     if (pointOrSVArray instanceof Quat[]) {
4155       data = (P3[]) pointOrSVArray;
4156       ndata = data.length;
4157     } else if (pointOrSVArray instanceof Lst<?>) {
4158       sv = (Lst<SV>) pointOrSVArray;
4159       ndata = sv.size();
4160     }
4161     if (sv == null && data == null)
4162       return "NaN";
4163     P3 result = new P3();
4164     float[] fdata = new float[ndata];
4165     for (int xyz = 0; xyz < 3; xyz++) {
4166       for (int i = 0; i < ndata; i++) {
4167         P3 pt = (data == null ? SV.ptValue(sv.get(i)) : data[i]);
4168         if (pt == null)
4169           return "NaN";
4170         switch (xyz) {
4171         case 0:
4172           fdata[i] = pt.x;
4173           break;
4174         case 1:
4175           fdata[i] = pt.y;
4176           break;
4177         case 2:
4178           fdata[i] = pt.z;
4179           break;
4180         }
4181       }
4182       Object f = getMinMax(fdata, tok);
4183       if (!(f instanceof Number))
4184         return "NaN";
4185       float value = ((Number) f).floatValue();
4186       switch (xyz) {
4187       case 0:
4188         result.x = value;
4189         break;
4190       case 1:
4191         result.y = value;
4192         break;
4193       case 2:
4194         result.z = value;
4195         break;
4196       }
4197     }
4198     return result;
4199   }
4200 
getMinMaxQuaternion(Lst<SV> svData, int tok)4201   private Object getMinMaxQuaternion(Lst<SV> svData, int tok) {
4202     Quat[] data;
4203     switch (tok) {
4204     case T.min:
4205     case T.max:
4206     case T.sum:
4207     case T.sum2:
4208       return "NaN";
4209     }
4210 
4211     // only stddev and average
4212 
4213     while (true) {
4214       data = e.getQuaternionArray(svData, T.list);
4215       if (data == null)
4216         break;
4217       float[] retStddev = new float[1];
4218       Quat result = Quat.sphereMean(data, retStddev, 0.0001f);
4219       switch (tok) {
4220       case T.average:
4221         return result;
4222       case T.stddev:
4223         return Float.valueOf(retStddev[0]);
4224       }
4225       break;
4226     }
4227     return "NaN";
4228   }
4229 
4230   private JmolPatternMatcher pm;
4231 
getPatternMatcher()4232   private JmolPatternMatcher getPatternMatcher() {
4233     return (pm == null
4234         ? pm = (JmolPatternMatcher) Interface.getUtil("PatternMatcher", e.vwr,
4235             "script")
4236         : pm);
4237   }
4238 
opTokenFor(int tok)4239   private T opTokenFor(int tok) {
4240     switch (tok) {
4241     case T.add:
4242     case T.join:
4243       return T.tokenPlus;
4244     case T.sub:
4245       return T.tokenMinus;
4246     case T.mul:
4247       return T.tokenTimes;
4248     case T.mul3:
4249       return T.tokenMul3;
4250     case T.div:
4251       return T.tokenDivide;
4252     }
4253     return null;
4254   }
4255 
setContactBitSets(BS bsA, BS bsB, boolean localOnly, float distance, RadiusData rd, boolean warnMultiModel)4256   public BS setContactBitSets(BS bsA, BS bsB, boolean localOnly, float distance,
4257                               RadiusData rd, boolean warnMultiModel) {
4258     boolean withinAllModels;
4259     BS bs;
4260     if (bsB == null) {
4261       // default is within just one model when {B} is missing
4262       bsB = BSUtil.setAll(vwr.ms.ac);
4263       BSUtil.andNot(bsB, vwr.slm.bsDeleted);
4264       bsB.andNot(bsA);
4265       withinAllModels = false;
4266     } else {
4267       // two atom sets specified; within ALL MODELS here
4268       bs = BSUtil.copy(bsA);
4269       bs.or(bsB);
4270       int nModels = vwr.ms.getModelBS(bs, false).cardinality();
4271       withinAllModels = (nModels > 1);
4272       if (warnMultiModel && nModels > 1 && !e.tQuiet)
4273         e.showString(
4274             GT.$("Note: More than one model is involved in this contact!"));
4275     }
4276     // B always within some possibly extended VDW of A or just A itself
4277     if (!bsA.equals(bsB)) {
4278       boolean setBfirst = (!localOnly || bsA.cardinality() < bsB.cardinality());
4279       if (setBfirst) {
4280         bs = vwr.ms.getAtomsWithinRadius(distance, bsA, withinAllModels,
4281             (Float.isNaN(distance) ? rd : null));
4282         bsB.and(bs);
4283       }
4284       if (localOnly) {
4285         // we can just get the near atoms for A as well.
4286         bs = vwr.ms.getAtomsWithinRadius(distance, bsB, withinAllModels,
4287             (Float.isNaN(distance) ? rd : null));
4288         bsA.and(bs);
4289         if (!setBfirst) {
4290           bs = vwr.ms.getAtomsWithinRadius(distance, bsA, withinAllModels,
4291               (Float.isNaN(distance) ? rd : null));
4292           bsB.and(bs);
4293         }
4294         // If the two sets are not the same,
4295         // we AND them and see if that is A.
4296         // If so, then the smaller set is
4297         // removed from the larger set.
4298         bs = BSUtil.copy(bsB);
4299         bs.and(bsA);
4300         if (bs.equals(bsA))
4301           bsB.andNot(bsA);
4302         else if (bs.equals(bsB))
4303           bsA.andNot(bsB);
4304       }
4305     }
4306     return bsB;
4307   }
4308 }
4309