Revision: 21657
          http://sourceforge.net/p/jmol/code/21657
Author:   hansonr
Date:     2017-07-08 16:34:52 +0000 (Sat, 08 Jul 2017)
Log Message:
-----------
Jmol.___JmolVersion="14.20.2" // 2017.07.08

bug fix: CIPChirality adding presort for Rules 4a and 4c (test12.mol; 828 
lines) 
bug fix: write SDF and write MOL do not set atom parity field
bug fix: JavaScript JSmol broken for chirality due to bug in Clazz.clone(obj)

adds SDF write/read of jmol_atom_names

Modified Paths:
--------------
    trunk/Jmol/src/org/jmol/adapter/readers/molxyz/MolReader.java
    trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java
    trunk/Jmol/src/org/jmol/symmetry/Symmetry.java
    trunk/Jmol/src/org/jmol/viewer/Jmol.properties
    trunk/Jmol/src/org/jmol/viewer/PropertyManager.java

Modified: trunk/Jmol/src/org/jmol/adapter/readers/molxyz/MolReader.java
===================================================================
--- trunk/Jmol/src/org/jmol/adapter/readers/molxyz/MolReader.java       
2017-07-07 14:47:49 UTC (rev 21656)
+++ trunk/Jmol/src/org/jmol/adapter/readers/molxyz/MolReader.java       
2017-07-08 16:34:52 UTC (rev 21657)
@@ -357,12 +357,12 @@
     float[] fdata = null;
     // officially, we need a terminating blank line, and $$$$ could be data,
     // but here we do not allow $$$$ due to Jmol legacy writing of 
JMOL_PARTIAL_CHARGES
-    while (rd() != null && !line.equals("$$$$")
-        && line.length() > 0)
+    while (rd() != null && !line.equals("$$$$") && line.length() > 0)
       data += line + "\n";
     data = PT.trim(data, "\n");
     Logger.info(dataName + ":" + PT.esc(data));
     molData.put(dataName, data);
+    int ndata = 0;
     if (dataName.toUpperCase().contains("_PARTIAL_CHARGES")) {
       try {
         fdata = PT.parseFloatArray(data);
@@ -373,12 +373,33 @@
           int atomIndex = (int) fdata[pt++] + iatom0 - 1;
           float partialCharge = fdata[pt++];
           atoms[atomIndex].partialCharge = partialCharge;
+          ndata++;
         }
-      } catch (Exception e) {
+      } catch (Throwable e) {
         for (int i = asc.getLastAtomSetAtomIndex(), n = asc.ac; i < n; i++)
           atoms[i].partialCharge = 0;
-        return;
+        Logger.error("error reading " + dataName + " field -- partial charges 
cleared");
       }
+      Logger.info(ndata + " partial charges read");
+    } else if (dataName.toUpperCase().contains("ATOM_NAMES")) {
+      ndata = 0;
+      try {
+        String[] tokens = PT.getTokens(data);
+        int pt = 0;
+        for (int i = parseIntStr(tokens[pt++]); --i >= 0;) {
+          int iatom;
+          // try to skip over extra stuff if it is not a number 
+          while ((iatom = parseIntStr(tokens[pt++])) == Integer.MIN_VALUE){}
+          int atomIndex = iatom + iatom0 - 1;
+          String name = tokens[pt++];
+          if (!name.equals("."))
+            atoms[atomIndex].atomName = name;
+          ndata++;
+        }
+      } catch (Throwable e) {
+        Logger.error("error reading " + dataName + " field");
+      }
+      Logger.info(ndata + " atom names read");
     }
   }
 

Modified: trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java
===================================================================
--- trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java  2017-07-07 14:47:49 UTC 
(rev 21656)
+++ trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java  2017-07-08 16:34:52 UTC 
(rev 21657)
@@ -140,6 +140,8 @@
  * 
  * code history:
  * 
+ * 7/8/17 Jmol 14.20.2 adding presort for Rules 4a and 4c (test12.mol; 828 
lines) 
+ * 
  * 7/7/17 Jmol 14.20.1 minor coding efficiencies (833 lines)
  *  
  * 7/6/17 Jmol 14.20.1 major rewrite to correct and simplify logic; full 
validation
@@ -188,26 +190,10 @@
  * 
  * NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! 
NOTE!
  * 
- * Added logic to Rule 1b: The root distance for a Kekule-ambiguous duplicate
- * atom is its own sphere, not the sphere of its duplicated atom.
+ * Added logic to Rule 1b: 
  * 
- * Stated more precisely:
+ * the root distance for multiple-bond 
  * 
- * Proposed amended Rule 1:
- * 
- * (1a) higher atomic number precedes lower;
- * 
- * (1b) in comparing two duplicate nodes, lower root distance precedes higher
- * root distance, where "root distance" is defined:
- * 
- * (i) in the case of a duplicate atom for which the atomic number is averaged
- * over two or more atoms in applying Rule 1a (including carbon-only rings such
- * as phenyl), the distance from the duplicate node itself to the root node; 
and
- * 
- * (ii) in all other cases, the distance of its corresponding nonduplicated 
atom
- * node to the root node.
- * 
- * 
  * Rationale: Using only the distance of the duplicated atom (ii, current
  * definition) introduces a Kekule bias, which can be illustrated with various
  * simple models. By moving that distance by one atom (to the duplicate atom
@@ -346,71 +332,73 @@
   //            but in this case by setting the reference descriptor to "R" 
for both sequences.
   //
 
-  /**
-   * The basic idea is to switch from a tree metaphor to a "twisted strand" or
-   * "thread" metaphor. For example:
-   * 
-   * (a) In Rule 1b, all ring-duplicates terminate on one of the nodes in the
-   * sequence of parent nodes going back to the root. This has nothing to do
-   * with branching.
-   * 
-   * (b) Generation of auxiliary descriptors prior to implementation of Rule 4
-   * must start from the highest sphere, proceeding toward the root. In this
-   * process the path leading back to the root will have no stereodescriptors,
-   * but that does not matter, as its priority is guaranteed to be set by Rule
-   * 1a.
-   * 
-   * (c) All auxiliary nodes can be normalized by labeling them one of
-   * {R,S,r,s}; there is no need to consider them to be C/T (seqCis or
-   * seqTrans), M/P, or m/p, other than to immediately equate that to R/S or
-   * r/s. Note that C/T and M/P must be assigned to the sp2 node closest to the
-   * root.
-   * 
-   * (d) Rule 4b can be analyzed using a "thread" metaphor in a five-step
-   * process:
-   * 
-   * (d1) Generate a set of n threads leading from the root and terminating on
-   * highest-ranking stereogenic centers. All nodes must be included, not just
-   * stereogenic centers.
-   * 
-   * (d2) Determine the reference descriptors.
-   * 
-   * (d3) Sort these threads by priority (including that determined by Rule 4a)
-   * and reference descriptors.
-   * 
-   * (d4) Note that the data can be seen as two n x m matrices, where the rows
-   * are the threads. Now "flatten" the data to produce two 1D sequences of
-   * descriptors by simply reading out the data in column order.
-   * 
-   * (d5) Prioritize these two sequences, looking for the first diastereotopic
-   * difference.
-   * 
-   * (e) Rule 5 processing is just a repeat of Rule 4b processing, where the
-   * reference descriptor is now set to "R".
-   * 
-   * (f) Tests for root-only spiro cases must be done along with each rule's
-   * processing prior to continuing to the next rule. This is done by looking
-   * for situations where there are two sets of matched priorities. These will
-   * be broken by the axial nature of spiro connections.
-   * 
-   * (g) A test for root-only double enantiotopic cases (RSR'S') must be done
-   * after Rule 5, allowing for the possibility for this test to return R/S or
-   * M/P, not just r/s and m/p.
-   * 
-   * Jmol's threads in Step d1 are just strings. Jmol carries out Steps d1 and
-   * d2 simultaneously with auxiliary descriptor generation, tracking the 
sphere
-   * and priority of all auxiliary descriptors as it generates them. Sorting in
-   * Step d3 is done in Jmol using a simple java Array.sort(); no actual
-   * matrices are involved.
-   * 
-   * Finally, the "like/unlike" business is replaced with a simple
-   * diastereotopic test. Thus, the strings are tested for the first point at
-   * which they are neither the same nor opposites. I like thinking about it
-   * this way because it focuses on what Rule 4b's role -- the identification 
of
-   * diastereomorphism. Rule 4c takes care of diasteriomorphism related to
-   * enantiomorphic sub-paths; Rule 5 finally takes care of any remaining
-   * enantiomorphic issues.
-   */
+/**
+ * The basic idea is to switch from a tree metaphor to a "twisted strand" or
+ * "thread" metaphor. For example:
+ * 
+ * (a) In Rule 1b, all ring-duplicates terminate on one of the nodes in the
+ * sequence of parent nodes going back to the root. This has nothing to do
+ * with branching.
+ * 
+ * (b) Generation of auxiliary descriptors prior to implementation of Rule 4
+ * must start from the highest sphere, proceeding toward the root. In this
+ * process the path leading back to the root will have no stereodescriptors,
+ * but that does not matter, as its priority is guaranteed to be set by Rule
+ * 1a.
+ * 
+ * (c) All auxiliary nodes can be normalized by labeling them one of
+ * {R,S,r,s}; there is no need to consider them to be C/T (seqCis or
+ * seqTrans), M/P, or m/p, other than to immediately equate that to R/S or
+ * r/s. Note that C/T and M/P must be assigned to the sp2 node closest to the
+ * root.
+ * 
+ * (d) Rule 4b can be analyzed using a "thread" metaphor in a five-step
+ * process:
+ * 
+ * (d1) Generate a set of n threads leading from the root and terminating on
+ * highest-ranking stereogenic centers. All nodes must be included, not just
+ * stereogenic centers.
+ * 
+ * (d2) Determine the reference descriptors.
+ * 
+ * (d3) Sort these threads by priority (including that determined by Rule 4a)
+ * and reference descriptors.
+ * 
+ * (d4) Note that the data can be seen as two n x m matrices, where the rows
+ * are the threads. Now "flatten" the data to produce two 1D sequences of
+ * descriptors by simply reading out the data in column order.
+ * 
+ * (d5) Prioritize these two sequences, looking for the first diastereotopic
+ * difference.
+ * 
+ * (e) Rule 5 processing is just a repeat of Rule 4b processing, where the
+ * reference descriptor is now set to "R".
+ * 
+ * (f) Tests for root-only spiro cases must be done along with each rule's
+ * processing prior to continuing to the next rule. This is done by looking
+ * for situations where there are two sets of matched priorities. These will
+ * be broken by the axial nature of spiro connections.
+ * 
+ * (g) A test for root-only double enantiotopic cases (RSR'S') must be done
+ * after Rule 5, allowing for the possibility for this test to return R/S or
+ * M/P, not just r/s and m/p.
+ * 
+ * Jmol's threads in Step d1 are just strings. Jmol carries out Steps d1 and
+ * d2 simultaneously with auxiliary descriptor generation, tracking the sphere
+ * and priority of all auxiliary descriptors as it generates them. Sorting in
+ * Step d3 is done in Jmol using a simple java Array.sort(); no actual
+ * matrices are involved.
+ * 
+ * Finally, the "like/unlike" business can be thought of as a simple
+ * diastereotopic test. Thus, the strings are tested for the first point at
+ * which they become neither identical nor opposites, and only that point need 
be  
+ * checked for likeness or unlikeness to the reference. I like thinking about 
it
+ * this way because it focuses on what Rule 4b's role is -- the identification 
of
+ * diastereomorphism. Rule 4c takes care of diasteriomorphism related to
+ * enantiomorphic (r/s, m/p) sub-paths; Rule 5 finally takes care of any 
remaining
+ * enantiomorphic issues, including the possibilty that two enantiomorphic 
pairs are 
+ * present.
+ */
 
   // The algorithm:
   // 
@@ -485,40 +473,24 @@
   //  type t |find " " /C
   
 
-  static final int NO_CHIRALITY = 0;
-  static final int TIED = NO_CHIRALITY;
-  static final int B_WINS = 1;
-  static final int A_WINS = -1;
+  static final int NO_CHIRALITY = 0, TIED = 0;
+  static final int A_WINS = -1, B_WINS = 1;
   static final int DIASTEREOMERIC = -3;
-  static final int DIASTEREOMERIC_A_WINS = -2;
-  static final int DIASTEREOMERIC_B_WINS = 2;
-  static final int ENANTIOMERIC_A_WINS = -3;
-  static final int ENANTIOMERIC_B_WINS = 3;
+  static final int DIASTEREOMERIC_A_WINS = -2, DIASTEREOMERIC_B_WINS = 2;
+  static final int ENANTIOMERIC_A_WINS = -3, ENANTIOMERIC_B_WINS = 3;
+  
   static final int IGNORE = Integer.MIN_VALUE;
-  static final int CHECK_BRANCH = Integer.MIN_VALUE;
 
   static final int STEREO_UNDETERMINED = -1;
 
-  static final int STEREO_R = JC.CIP_CHIRALITY_R_FLAG;
-  static final int STEREO_S = JC.CIP_CHIRALITY_S_FLAG;
+  static final int STEREO_R = JC.CIP_CHIRALITY_R_FLAG, STEREO_S = 
JC.CIP_CHIRALITY_S_FLAG;
+  static final int STEREO_M = JC.CIP_CHIRALITY_M_FLAG, STEREO_P = 
JC.CIP_CHIRALITY_P_FLAG;
+  static final int STEREO_Z = JC.CIP_CHIRALITY_Z_FLAG, STEREO_E = 
JC.CIP_CHIRALITY_E_FLAG;
 
-  static final int STEREO_M = JC.CIP_CHIRALITY_M_FLAG;
-  static final int STEREO_P = JC.CIP_CHIRALITY_P_FLAG;
-
-  static final int STEREO_Z = JC.CIP_CHIRALITY_Z_FLAG;
-  static final int STEREO_E = JC.CIP_CHIRALITY_E_FLAG;
-
   static final int STEREO_BOTH_RS = STEREO_R | STEREO_S; // must be the number 
3
   static final int STEREO_BOTH_EZ = STEREO_E | STEREO_Z;
 
-  static final int RULE_1a = 1;
-  static final int RULE_1b = 2;
-  static final int RULE_2  = 3;
-  static final int RULE_3  = 4;
-  static final int RULE_4a = 5;
-  static final int RULE_4b = 6;
-  static final int RULE_4c = 7;
-  static final int RULE_5  = 8;
+  static final int RULE_1a = 1, RULE_1b = 2, RULE_2  = 3, RULE_3  = 4, RULE_4a 
= 5, RULE_4b = 6, RULE_4c = 7, RULE_5  = 8;
 
   static String prefixString = ".........."; // Logger only
   
@@ -592,14 +564,34 @@
   V3 vNorm = new V3(), vNorm2 = new V3(), vTemp = new V3();
 
   /**
-   * a testing flag that when false does not involve alkenes in
-   * duplicate root distance checks; AY236_215 and BH64_002,003,004,009 
-   * all fail if this is set false 
+   * Rule 1b Option A: assign multiple-bond duplicates to parent sphere
    */
-  boolean allowRule1bAlkenes = true;
+  final static int RULE_1b_TEST_OPTION_A_PARENT = 1;
   
+  /**
+   * Rule 1b Option B: assign multiple-bond duplicates to own sphere
+   */
+  final static int RULE_1b_TEST_OPTION_B_SELF = 2;
   
   /**
+   * Rule 1b Option C: do not consider multiple-bond duplicates in Rule 1b
+   */
+  final static int RULE_1b_TEST_OPTION_C_NONE = 3;
+  
+  /**
+   * Rule 1b Option D: assign multiple-bond duplicates to own sphere only if 
Kekule-ambiguous
+   */
+  final static int RULE_1b_TEST_OPTION_D_SELF_KEKULE = 4;
+  
+
+  /**
+   * a test for different Rule 1b options.
+   *    *  
+   */
+  int rule1bOption = RULE_1b_TEST_OPTION_A_PARENT;
+
+  
+  /**
    * return auxiliary chirality settings for all atoms when only one atom is
    * selected and TESTFLAG1 has been set TRUE in Jmol
    * 
@@ -608,8 +600,8 @@
 
   public CIPChirality() {
     // for reflection
-    //System.out.println("TESTING ALLOWRULE1B");
-    //allowRule1bAlkenes = false;
+    //rule1bOption = RULE_1b_TEST_OPTION_A_SELF;
+    System.out.println("TESTING Rule 1b option " + rule1bOption);
   }
 
   /**
@@ -958,43 +950,10 @@
    *        tracks all atoms in this molecular unit
    */
   private void getSmallRings(SimpleNode atom, BS bs) {
-    root = new CIPAtom().create(atom, null, false, false, false);
-    addSmallRings(root, bs);
+    (root = new CIPAtom().create(atom, null, false, false, 
false)).addSmallRings(bs);
   }
 
   /**
-   * initiate a new CIPAtom for each substituent of atom, and as part of this
-   * process, check to see if a new ring is being formed.
-   * 
-   * @param a
-   * @param bs
-   */
-  private void addSmallRings(CIPAtom a, BS bs) {
-    if (a == null || a.atom == null || a.sphere > SMALL_RING_MAX)
-      return;
-    if (bs != null)
-      bs.clear(a.atom.getIndex());
-    if (a.isTerminal || a.isDuplicate || a.atom.getCovalentBondCount() > 4)
-      return;
-    SimpleNode atom2;
-    int pt = 0;
-    SimpleEdge[] bonds = a.atom.getEdges();
-    for (int i = bonds.length; --i >= 0;) {
-      SimpleEdge bond = bonds[i];
-      if (!bond.isCovalent()
-          || (atom2 = bond.getOtherNode(a.atom)).getCovalentBondCount() == 1
-          || a.parent != null && atom2 == a.parent.atom)
-        continue;
-      CIPAtom r = a.addAtom(pt++, atom2, false, false, false);
-      if (r.isDuplicate)
-        r.updateRingList();
-    }
-    for (int i = 0; i < pt; i++) {
-      addSmallRings(a.atoms[i], bs);
-    }
-  }
-
-  /**
    * Remove E/Z designations for small-rings double bonds (IUPAC
    * 2013.P-93.5.1.4.1).
    * 
@@ -1155,10 +1114,11 @@
         atom = (root = cipAtom).atom;
         cipAtom.htPathPoints = (cipAtom.parent = new CIPAtom().create(
             parentAtom, null, true, false, false)).htPathPoints;
-      } else if (!(root = cipAtom = new CIPAtom().create(atom, null, false, 
false, false)).canBePseudo) {
+      } else if (!(root = cipAtom = new CIPAtom().create(atom, null, false,
+          false, false)).canBePseudo) {
         // This is a root-atom call. 
         // Just checking here that center has 4 covalent bonds or is trigonal 
pyramidal.
-          return NO_CHIRALITY;
+        return NO_CHIRALITY;
       }
       if (cipAtom.setNode()) {
         for (currentRule = RULE_1a; currentRule <= RULE_5; currentRule++) {
@@ -1165,24 +1125,26 @@
           if (Logger.debugging)
             Logger.info("-Rule " + getRuleName() + " CIPChirality for "
                 + cipAtom + "-----"); // Logger
-          if (currentRule == RULE_4a) {
-            cipAtom.createRule4AuxiliaryData(null, null);
-            if (cipAtom.rule4Type == 0) {
-              // we can skip Rules 4a - 5 if there are no chirality centers
-              break;
+          if (currentRule == RULE_4a || currentRule == RULE_4c) {
+            if (currentRule == RULE_4a) {
+              cipAtom.createRule4AuxiliaryData(null, null);
+              if (cipAtom.rule4Type == 0) {
+                // we can skip Rules 4a - 5 if there are no chirality centers
+                break;
+              }
             }
+            cipAtom.sortSubstituents(Integer.MIN_VALUE);
           }
 
           // initial call to sortSubstituents does all, recursively
 
           if (cipAtom.sortSubstituents(0)) {
-
             if (Logger.debugging) {
               Logger.info(currentRule + ">>>>" + cipAtom);
               for (int i = 0; i < cipAtom.bondCount; i++) { // Logger
                 if (cipAtom.atoms[i] != null) // Logger
                   Logger.info(cipAtom.atoms[i] + " "
-                      + Integer.toHexString(cipAtom.priorities[i])); // Logger
+                      + cipAtom.priorities[i]); // Logger
               }
             }
 
@@ -1375,7 +1337,7 @@
     /**
      * bond distance from the root atom to this atom
      */
-    int sphere;
+    private int sphere;
 
     /**
      * Rule 1b measure: Distance from root of the corresponding nonduplicated
@@ -1414,19 +1376,13 @@
      * typically H or a halogen (F, Cl, Br, I)
      * 
      */
-    boolean isTerminal;
+    private boolean isTerminal;
 
     /**
-     * at atom such as N, P, or S with three non-coplanar bonds
-     *  
-     */
-    boolean isTrigonalPyramidal;
-
-    /**
      * is one atom of a double bond
      */
 
-    boolean isAlkene;
+    private boolean isAlkene;
 
     /**
      * the associated Jmol (or otherwise) atom; use of the SimpleNode interface
@@ -1451,13 +1407,13 @@
     /**
      * Rule 1a nominal element number; may be fractional for Kekule issues
      */
-    float elemNo;
+    private float elemNo;
 
     /**
      * Rule 2 nominal atomic mass; may be a rounded value so that 12C is the
      * same as C
      */
-    int massNo;
+    private int massNo;
 
     ///// SUBSTITUENTS ////
     
@@ -1496,7 +1452,7 @@
      * duplicate atom.
      * 
      */
-    BS bsPath;
+    private BS bsPath;
 
     /**
      * String path, for debugging 
@@ -1521,7 +1477,7 @@
      * pointer to this branch's spiro end atom if it is found to be spiro
      */
 
-    CIPAtom spiroEnd;
+    private CIPAtom spiroEnd;
 
     /**
      * Rule 1b hash table that maintains distance of the associated
@@ -1536,13 +1492,13 @@
      * first atom of an alkene or cumulene atom
      */
 
-    CIPAtom alkeneParent;
+    private CIPAtom alkeneParent;
 
     /**
      * last atom of an alkene or cumulene atom
      */
 
-    CIPAtom alkeneChild;
+    private CIPAtom alkeneChild;
 
     /**
      * a flag used in Rule 3 to indicate the second carbon of a double bond
@@ -1553,7 +1509,7 @@
     /**
      * is an atom that is involved in more than one Kekule form
      */
-    boolean isKekuleAmbiguous;
+    private boolean isKekuleAmbiguous;
 
     /**
      * first =X= atom in a string of =X=X=X=...
@@ -1564,7 +1520,7 @@
      * potentially useful information that this duplicate is from an double- or
      * triple-bond, not a ring closure
      */
-    boolean sp2Duplicate;
+    private boolean multipleBondDuplicate;
 
     /**
      * alkene or even cumulene, so chirality will be EZ, not MP
@@ -1634,6 +1590,8 @@
      */
     int rule4Type;
     
+    private BS bsTemp = new BS();
+
     CIPAtom() {
       // had a problem in JavaScript that the constructor of an inner function 
cannot
       // access this.b$ yet. That assignment is made after construction.
@@ -1665,9 +1623,8 @@
           .getKekuleElementNumber() : atom.getElementNumber());
       massNo = atom.getNominalMass();
       bondCount = atom.getCovalentBondCount();
-      isTrigonalPyramidal = (bondCount == 3 && !isAlkene && (elemNo > 10 || 
bsAzacyclic != null
-          && bsAzacyclic.get(atomIndex)));
-      canBePseudo = (bondCount == 4 || isTrigonalPyramidal);
+      canBePseudo = (bondCount == 4 || bondCount == 3 && !isAlkene
+          && (elemNo > 10 || bsAzacyclic != null && 
bsAzacyclic.get(atomIndex)));
       if (parent != null)
         sphere = parent.sphere + 1;
       if (sphere == 1) {
@@ -1675,11 +1632,12 @@
         htPathPoints = new Hashtable<Integer, Integer>();
       } else if (parent != null) {
         rootSubstituent = parent.rootSubstituent;
-        htPathPoints = (Map<Integer, Integer>) ((Hashtable<Integer, Integer>) 
parent.htPathPoints).clone();
+        htPathPoints = (Map<Integer, Integer>) ((Hashtable<Integer, Integer>) 
parent.htPathPoints)
+            .clone();
       }
       bsPath = (parent == null ? new BS() : BSUtil.copy(parent.bsPath));
 
-      sp2Duplicate = isDuplicate;
+      multipleBondDuplicate = isDuplicate;
 
       rootDistance = sphere;
       // The rootDistance for a nonDuplicate atom is just its sphere.
@@ -1691,9 +1649,14 @@
       if (parent == null) {
         // original atom
         bsPath.set(atomIndex);
-      } else if (sp2Duplicate && (!allowRule1bAlkenes || isKekuleAmbiguous)) {
+      } else if (multipleBondDuplicate
+          && (rule1bOption == RULE_1b_TEST_OPTION_D_SELF_KEKULE
+              && isKekuleAmbiguous || rule1bOption == 
RULE_1b_TEST_OPTION_B_SELF)) {
         // *** Rule 1b Jmol amendment ***
         // just leaving the rootDistance to be as for other atoms
+      } else if (multipleBondDuplicate
+          && rule1bOption == RULE_1b_TEST_OPTION_A_PARENT) {
+        rootDistance--;
       } else if (atom == root.atom) {
         // pointing to original atom
         isDuplicate = true;
@@ -1872,8 +1835,6 @@
           .create(other, this, isAlkene, isDuplicate, isParentBond);
     }
     
-    private BS bsTemp = new BS();
-
     /**
      * Deep-Sort the substituents of an atom, setting the node's atoms[] and
      * priorities[] arrays. Checking for "ties" that will lead to
@@ -1885,9 +1846,22 @@
      * 
      */
     boolean sortSubstituents(int sphere) {
-      
+
       // Note that this method calls breakTie and is called recursively from 
breakTie.
 
+      boolean ignoreTies = (sphere == Integer.MIN_VALUE);
+      if (ignoreTies) {
+        // If this is Rule 4a or 4c, we must presort the full tree
+        // Just before Rule4a we must sort all substituents without breaking 
ties
+        if (isTerminal)
+          return false;
+        for (int i = 0; i < 4; i++)
+          if (rule4List[i] != null && atoms[i].atom != null && 
!atoms[i].isTerminal)
+            atoms[i].sortSubstituents(Integer.MIN_VALUE);
+        if (!canBePseudo)
+          return false;
+      }
+
       int[] indices = new int[4], prevPrior = new int[4];
       int nPrioritiesPrev = nPriorities;
       for (int i = 0; i < 4; i++) {
@@ -1904,8 +1878,8 @@
         Logger.info("---");
       }
 
-      // if this is Rule 4 or 5, then we do a check of the forward-based 
stereochemical path
-      
+      // if this is Rule 4b or 5, then we do a check of the forward-based 
stereochemical path
+
       boolean checkRule4List = (rule4List != null && (currentRule == RULE_4b 
|| currentRule == RULE_5));
 
       int loser, score;
@@ -1921,13 +1895,15 @@
           // (c) if the current rule decides; if not, then
           // (d) if the tie can be broken in the next sphere
 
+          if (a == null || b == null)
+            System.out.println("?????");
           switch (a.atom == null ? B_WINS : b.atom == null ? A_WINS
               : prevPrior[i] < prevPrior[j] ? A_WINS
                   : prevPrior[j] < prevPrior[i] ? B_WINS
-                      : (score = (checkRule4List ? 
-                          checkRule4And5(i, j) : 
-                          a.checkPriority(b))) != TIED ? score : 
-                            sign(a.breakTie(b, sphere + 1))) {
+                      : (score = (checkRule4List ? checkRule4And5(i, j) : a
+                          .checkPriority(b))) != TIED ? score
+                          : ignoreTies ? IGNORE : sign(a
+                              .breakTie(b, sphere + 1))) {
           case B_WINS:
             loser = i;
             //$FALL-THROUGH$
@@ -1965,12 +1941,11 @@
       if (parent == null) {
 
         // Check for special root-only cases:
-        
+
         //  P-92.2.1.1(c) pseudoasymmetric centers must have 
         //                two and only two enantiomorphic ligands
         //
         //  P-33.5.3.1 spiro compounds have increased constraints
-        
 
         // We do the spiro test first, as it may inform the pseudoasymmetric 
test.
 
@@ -2058,9 +2033,9 @@
         }
         Logger.info(dots() + "-------");
       }
-      
+
       // We are done if the number of priorities equals the bond count.
-      
+
       return (nPriorities == bondCount);
     }
 
@@ -2317,9 +2292,13 @@
 
     private int checkRule1b(CIPAtom b) {
       return b.isDuplicate != isDuplicate ? TIED
-          : !allowRule1bAlkenes && (b.isAlkene || b.isAlkene) ? TIED :   
-            b.rootDistance != rootDistance ? (b.rootDistance > rootDistance ? 
A_WINS
-              : B_WINS)
+          : rule1bOption == RULE_1b_TEST_OPTION_C_NONE && (parent.isAlkene || 
b.parent.isAlkene) ? 
+              TIED :   
+            b.rootDistance != rootDistance ? 
+                (b.rootDistance > rootDistance ? 
+                    A_WINS
+              : B_WINS
+              )
               : TIED;
     }
 
@@ -2451,7 +2430,7 @@
       if (sortByRule(rule))
         for (int i = 0; i < 4; i++) {
           CIPAtom a = atoms[i];
-          if (!a.sp2Duplicate)
+          if (!a.multipleBondDuplicate)
             return priorities[i] == priorities[i + 1] ? null : atoms[i];
         }
       return null;
@@ -3026,6 +3005,38 @@
       return (score < 0 ? -1 : score > 0 ? 1 : 0);
     }
 
+    /**
+     * initiate a new CIPAtom for each substituent of atom, and as part of this
+     * process, check to see if a new ring is being formed.
+     * 
+     * @param bs
+     */
+    void addSmallRings(BS bs) {
+      if (atom == null || sphere > SMALL_RING_MAX)
+        return;
+      if (bs != null)
+        bs.clear(atom.getIndex());
+      if (isTerminal || isDuplicate || atom.getCovalentBondCount() > 4)
+        return;
+      SimpleNode atom2;
+      int pt = 0;
+      SimpleEdge[] bonds = atom.getEdges();
+      for (int i = bonds.length; --i >= 0;) {
+        SimpleEdge bond = bonds[i];
+        if (!bond.isCovalent()
+            || (atom2 = bond.getOtherNode(atom)).getCovalentBondCount() == 1
+            || parent != null && atom2 == parent.atom)
+          continue;
+        CIPAtom r = addAtom(pt++, atom2, false, false, false);
+        if (r.isDuplicate)
+          r.updateRingList();
+      }
+      for (int i = 0; i < pt; i++)
+        if (atoms[i] != null)
+          atoms[i].addSmallRings(bs);
+    }
+
+
     @Override
     public Object clone() {
       CIPAtom a = null;

Modified: trunk/Jmol/src/org/jmol/symmetry/Symmetry.java
===================================================================
--- trunk/Jmol/src/org/jmol/symmetry/Symmetry.java      2017-07-07 14:47:49 UTC 
(rev 21656)
+++ trunk/Jmol/src/org/jmol/symmetry/Symmetry.java      2017-07-08 16:34:52 UTC 
(rev 21657)
@@ -24,6 +24,7 @@
 
 package org.jmol.symmetry;
 
+import java.awt.Cursor;
 import java.util.Map;
 
 import javajs.util.Lst;
@@ -766,6 +767,7 @@
 
   @Override
   public void calculateCIPChiralityForAtoms(Viewer vwr, BS bsAtoms) {
+    vwr.setCursor(Cursor.WAIT_CURSOR);
     CIPChirality cip = getCIPChirality(vwr);
     BS bsAtropisomer = null, bsHelixM = null, bsHelixP = null;
     boolean setAuxiliary = vwr.getBoolean(T.testflag1);
@@ -780,6 +782,7 @@
      // ignore
     }
     cip.getChiralityForAtoms(vwr.ms.at, bsAtoms, bsAtropisomer, bsHelixM, 
bsHelixP, setAuxiliary);
+    vwr.setCursor(Cursor.DEFAULT_CURSOR);
   }
   
   CIPChirality cip;

Modified: trunk/Jmol/src/org/jmol/viewer/Jmol.properties
===================================================================
--- trunk/Jmol/src/org/jmol/viewer/Jmol.properties      2017-07-07 14:47:49 UTC 
(rev 21656)
+++ trunk/Jmol/src/org/jmol/viewer/Jmol.properties      2017-07-08 16:34:52 UTC 
(rev 21657)
@@ -56,8 +56,9 @@
 
 # TODO: fix UNDO
 
-Jmol.___JmolVersion="14.20.2"
+Jmol.___JmolVersion="14.20.2" // 2017.07.08
 
+bug fix: CIPChirality adding presort for Rules 4a and 4c (test12.mol; 828 
lines) 
 bug fix: write SDF and write MOL do not set atom parity field
 bug fix: JavaScript JSmol broken for chirality due to bug in Clazz.clone(obj)
 

Modified: trunk/Jmol/src/org/jmol/viewer/PropertyManager.java
===================================================================
--- trunk/Jmol/src/org/jmol/viewer/PropertyManager.java 2017-07-07 14:47:49 UTC 
(rev 21656)
+++ trunk/Jmol/src/org/jmol/viewer/PropertyManager.java 2017-07-08 16:34:52 UTC 
(rev 21657)
@@ -1355,7 +1355,7 @@
       mol.append("M  V30 END ATOM\nM  V30 BEGIN BOND\n");
     } else if (asJSON) {
       mol.append("],\"b\":[");
-    } 
+    }
     for (int i = bsBonds.nextSetBit(0), n = 0; i >= 0; i = bsBonds
         .nextSetBit(i + 1))
       getBondRecordMOL(mol, ++n, ms.bo[i], atomMap, asV3000, asJSON, 
noAromatic);
@@ -1372,10 +1372,10 @@
     if (asSDF) {
       try {
         float[] pc = ms.getPartialCharges();
+        if (molData == null)
+          molData = new Hashtable<String, Object>();
+        SB sb = new SB();
         if (pc != null) {
-          if (molData == null)
-            molData = new Hashtable<String, Object>();
-          SB sb = new SB();
           sb.appendI(nAtoms).appendC('\n');
           for (int i = bsAtoms.nextSetBit(0), n = 0; i >= 0; i = bsAtoms
               .nextSetBit(i + 1))
@@ -1382,6 +1382,17 @@
             sb.appendI(++n).append(" ").appendF(pc[i]).appendC('\n');
           molData.put("jmol_partial_charges", sb.toString());
         }
+        sb.setLength(0);
+        sb.appendI(nAtoms).appendC('\n');
+        for (int i = bsAtoms.nextSetBit(0), n = 0; i >= 0; i = bsAtoms
+            .nextSetBit(i + 1)) {
+          String name = ms.at[i].getAtomName().trim();
+          if (name.length() == 0)
+            name = ".";
+          sb.appendI(++n).append(" ")
+              .append(name.replace(' ', '_')).appendC('\n');
+        }
+        molData.put("jmol_atom_names", sb.toString());
         for (String key : molData.keySet()) {
           o = molData.get(key);
           if (o instanceof SV)

This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Jmol-commits mailing list
Jmol-commits@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jmol-commits

Reply via email to