Revision: 21675 http://sourceforge.net/p/jmol/code/21675 Author: hansonr Date: 2017-07-23 20:07:02 +0000 (Sun, 23 Jul 2017) Log Message: ----------- adds Rule 6
Modified Paths: -------------- trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java trunk/Jmol/src/org/jmol/viewer/JC.java Modified: trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java =================================================================== --- trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java 2017-07-21 22:33:07 UTC (rev 21674) +++ trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java 2017-07-23 20:07:02 UTC (rev 21675) @@ -55,8 +55,10 @@ * * - deeply validated * - * - included revised Rules 1b, and 2. + * - includes revised Rules 1b, and 2. * + * - includes a proposed Rule 6. + * * - implemented in Java (Jmol) and JavaScript (JSmol) * * - only two Java classes; roughly 1000 lines @@ -64,7 +66,7 @@ * - efficient, one-pass process for each center using a single finite digraph * for all auxiliary descriptors * - * - exhaustive processing of all 8 sequence rules (1a, 1b, 2, 3, 4a, 4b, 4c, 5) + * - exhaustive processing of all 9 sequence rules (1a, 1b, 2, 3, 4a, 4b, 4c, 5, 6) * * - includes R/S, r/s, M/P (axial, not planar), E/Z * @@ -143,6 +145,7 @@ * * code history: * + * 7/23/17 Jmol 14.20.4 adds Rule 6; rewrite/consolidation of spiro, C3, double spiran code (846 lines) * 7/19/17 Jmol 14.20.3 fixing Rule 2 (880 lines) * 7/13/17 Jmol 14.20.3 more thorough spiro testing (858 lines) * 7/10/17 Jmol 14.20.2 adding check for C3 and double @@ -233,6 +236,14 @@ * * See CIPAtom.getMass(); * + * PROPOSED Rule 6: + * + * Rule 6: In the case of a digraph with more than two root-duplicate nodes, any one of + * the still-indistinguishable ligands is given higher priority than the others. + * + * Rationale: This rule is stated in CIP(1966) p. 357. + * + * * @author Bob Hanson hans...@stolaf.edu */ public class CIPChirality { @@ -539,10 +550,12 @@ 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, RULE_1b = 2, RULE_2 = 3, RULE_3 = 4, - RULE_4a = 5, RULE_4bTEST = 6, RULE_4b = 7, RULE_4c = 8, - RULE_5 = 9, - RULE_6 = 10; + 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, + RULE_6 = 9; boolean isRule4TEST = false; @@ -656,13 +669,10 @@ * */ boolean setAuxiliary; - public boolean haveIsotopes; public CIPChirality() { // for reflection System.out.println("TESTING Rule 1b option " + rule1bOption); - isRule4TEST = false; - System.out.println("TESTING Rule 4b option " + isRule4TEST); } /** @@ -1182,25 +1192,19 @@ parentAtom, null, true, false, false)).htPathPoints; } else { if (!(root = cipAtom = new CIPAtom().create(atom, null, false, false, - false)).canBePseudo) { + false)).isSP3) { // This is a root-atom call. // Just checking here that center has 4 covalent bonds or is trigonal pyramidal. return NO_CHIRALITY; } - haveIsotopes = false; } if (cipAtom.setNode()) { - for (currentRule = RULE_1a; currentRule <= RULE_5; currentRule++) { - + for (currentRule = RULE_1a; currentRule <= RULE_6; currentRule++) { + if (Logger.debugging) Logger.info("-Rule " + getRuleName() + " CIPChirality for " + cipAtom + "-----"); // Logger switch (currentRule) { - case RULE_4bTEST: - case RULE_4b: - if (isRule4TEST != (currentRule == RULE_4bTEST)) - continue; - break; case RULE_4a: case RULE_4c: if (currentRule == RULE_4a) { @@ -1210,11 +1214,20 @@ break; } } + // we need to presort with no tie-breaking for Rules 4 cipAtom.sortSubstituents(Integer.MIN_VALUE); + break; + case RULE_5: + break; + case RULE_6: + if (!cipAtom.setupRule6(false)) + continue; + break; } // initial call to sortSubstituents does all, recursively + int nPrioritiesPrev = cipAtom.nPriorities; if (cipAtom.sortSubstituents(0)) { if (Logger.debugging) { Logger.info(currentRule + ">>>>" + cipAtom); @@ -1229,13 +1242,45 @@ if (isAlkeneEndCheck) return (cipAtom.atoms[0].isDuplicate ? 2 : 1); - rs = cipAtom.checkHandedness() - | (currentRule == RULE_5 && cipAtom.canBePseudo ? JC.CIP_CHIRALITY_PSEUDO_FLAG - : 0); + rs = cipAtom.checkHandedness(); + if (currentRule == RULE_5 && (cipAtom.nPriorities != 4 || nPrioritiesPrev != 2)) { + + rs |= JC.CIP_CHIRALITY_PSEUDO_FLAG; + + // Exclude special case: + + // P-92.2.1.1(c) pseudoasymmetric centers must have + // two and only two enantiomorphic ligands + // + + // Rule 5 has decided the issue, but how many decisions did we make? + // If priorities [0 0 2 2] went to [0 1 2 3] then + // we have two Rule-5 decisions -- R,S,R',S'. + // In that case, Rule 5 results in R/S, not r/s. + // + // S + // - + // - + // R---C---R' despite this being Rule 5, the results is R, not r. + // - + // - + // S' + // + // --------- mirror plane + // + // R' + // - + // - + // S---C---S' not superimposible + // - + // - + // R + // + } if (Logger.debugging) Logger.info(atom + " " + JC.getCIPChiralityName(rs) + " by Rule " + getRuleName() + "\n----------------------------------"); // Logger - break; + return rs; } } } @@ -1290,10 +1335,10 @@ private int setBondChirality(SimpleNode a, SimpleNode pa, SimpleNode pb, SimpleNode b, boolean isAxial) { CIPAtom a1 = new CIPAtom().create(a, null, true, false, false); - int atop = getAlkeneEndTopPriority(a1, pa, isAxial); + int atop = getAtomChiralityLimited(null, a1, pa) - 1; int ruleA = currentRule; CIPAtom b2 = new CIPAtom().create(b, null, true, false, false); - int btop = getAlkeneEndTopPriority(b2, pb, isAxial); + int btop = getAtomChiralityLimited(null, b2, pb) - 1; int ruleB = currentRule; int c = (atop >= 0 && btop >= 0 ? getEneChirality(b2.atoms[btop], b2, a1, a1.atoms[atop], isAxial, true) : NO_CHIRALITY); @@ -1357,19 +1402,6 @@ } /** - * Alkene end check for final bond chirality, not auxiliary. - * - * @param a - * @param pa - * @param isAxial - * @return -1, 0, or 1 - */ - private int getAlkeneEndTopPriority(CIPAtom a, SimpleNode pa, boolean isAxial) { - a.canBePseudo = isAxial; - return getAtomChiralityLimited(null, a, pa) - 1; - } - - /** * Check cis vs. trans nature of a--b==c--d. * * @param a @@ -1473,7 +1505,7 @@ * updating lstSmallRings * */ - private int atomIndex; + int atomIndex; /** * true atom covalent bond count; cached for better performance @@ -1551,16 +1583,14 @@ * the number of distinct priorities determined for this atom for the * current rule; 0-4 for the root atom; 0-3 for all others */ - private int nPriorities; + int nPriorities; /** - * pointer to this branch's spiro end atom if it is found to be spiro + * number of root-duplicate atoms (root atom only */ - private CIPAtom spiroEnd; + int nRootDuplicates; - private int nSpiro; - /** * Rule 1b hash table that maintains distance of the associated * nonduplicated atom node @@ -1622,7 +1652,7 @@ * than one R/S decision made, so this center cannot be r/s; initially just * indicates that the atom has 4 covalent bonds or is trigonal pyriamidal */ - boolean canBePseudo = true; + boolean isSP3 = true; /** * auxiliary chirality as determined in createAuxiliaryRule4Data; @@ -1675,10 +1705,6 @@ */ int rule4Type; - private boolean rule4checkIdentical = true; - - private char rule4Ref = 'R'; - private BS bsTemp = new BS(); // flag to reverse the Rule 2 result for a few isotope types @@ -1685,10 +1711,9 @@ private boolean reverseRule2; - // flag for C3-symmetry checking reference atom index + // reference index for Rule 6 - private int c3RefIndex = -1; - private int c4RefIndex = -1; + int rule6refIndex = -1; CIPAtom() { // had a problem in JavaScript that the constructor of an inner function cannot @@ -1695,6 +1720,36 @@ // access this.b$ yet. That assignment is made after construction. } + public boolean setupRule6(boolean isAux) { + if (nPriorities > 2 || (isAux ? countDuplicates(atomIndex) : nRootDuplicates) <= 2) + return false; + // we have more than two root-duplicates and priorities array is one of: + // [0 0 0 0] CIP Helv Chim. Acta 1966 #33 -- double spiran + // [0 0 2 2] P-93.5.3.2 spiro + // [0 1 1 1] or [0 0 0 3] CIP Helv. Chim. Acta 1966 #32 -- C3-symmetric + root.rule6refIndex = atoms[priorities[2]].atomIndex; + // could be priorities[1] as well; just so it is not 0 or 3, + // as that could be the singlet in the C3-symmetric case. + // we need to presort again + sortSubstituents(Integer.MIN_VALUE); + return true; + } + + private int countDuplicates(int index) { + int n = 0; + for (int i = 0; i < 4; i++) { + if (atoms[i] == null) + continue; + if (atoms[i].isDuplicate) { + if (atoms[i].atomIndex == index) + n++; + } else { + n += atoms[i].countDuplicates(index); + } + } + return n; + } + /** * * @param atom @@ -1720,7 +1775,7 @@ elemNo = (isDuplicate && isKekuleAmbiguous ? parent .getKekuleElementNumber() : atom.getElementNumber()); bondCount = atom.getCovalentBondCount(); - canBePseudo = (bondCount == 4 || bondCount == 3 && !isAlkene + isSP3 = (bondCount == 4 || bondCount == 3 && !isAlkene && (elemNo > 10 || bsAzacyclic != null && bsAzacyclic.get(atomIndex))); if (parent != null) sphere = parent.sphere + 1; @@ -1757,9 +1812,7 @@ // pointing to original atom isDuplicate = true; rootDistance = 0; - root.nSpiro++; - if (rootSubstituent.spiroEnd == null) - rootSubstituent.spiroEnd = parent; + root.nRootDuplicates++; } else if (bsPath.get(atomIndex)) { isDuplicate = true; rootDistance = (isParentBond ? parent.sphere : htPathPoints.get( @@ -1791,8 +1844,6 @@ */ private float getMass() { if (mass == UNDETERMINED && (mass = atom.getMass()) == (int) mass) { - // this is an isotope - haveIsotopes = true; if (isType(RULE_2_nXX_REV_XX)) reverseRule2 = true; else if (elemNo > 92 || isType(RULE_2_nXX_EQ_XX)) @@ -1832,31 +1883,6 @@ return ave / n; } - // /** - // * Calculate the average element numbers of associated double-bond atoms - // * weighted by their most significant Kekule resonance contributor(s). We - // * only consider simple benzenoid systems -- 6-membered rings and their - // * 6-memebered rings fused to them. Calculated for the parent of an - // * sp2-duplicate atom. - // * - // * @return an averaged element number - // */ - // private float getKekuleMass() { - // SimpleEdge[] edges = atom.getEdges(); - // SimpleEdge bond; - // float ave = 0; - // int n = 0; - // for (int i = edges.length; --i >= 0;) - // if ((bond = edges[i]).isCovalent()) { - // SimpleNode other = bond.getOtherNode(atom); - // if (bsKekuleAmbiguous.get(other.getIndex())) { - // n++; - // ave += other.getMass(); - // } - // } - // return ave / n; - // } - /** * Create a bit set that gives all the atoms in this ring if it is smaller * than 8. @@ -2024,7 +2050,7 @@ if (rule4List[i] != null && atoms[i].atom != null && !atoms[i].isTerminal) atoms[i].sortSubstituents(Integer.MIN_VALUE); - if (!canBePseudo) + if (!isSP3) return false; } } @@ -2032,7 +2058,6 @@ ignoreTies |= (currentRule == RULE_4b || currentRule == RULE_5); int[] indices = new int[4], prevPrior = new int[4]; - int nPrioritiesPrev = nPriorities; for (int i = 0; i < 4; i++) { prevPrior[i] = priorities[i]; priorities[i] = 0; @@ -2099,49 +2124,7 @@ priorities = newPriorities; rule4List = newRule4List; nPriorities = bsTemp.cardinality(); - - 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 - - if (currentRule == RULE_5) { - if (nSpiro > 0) - checkSpiro(); - if (nPriorities == 4 && nPrioritiesPrev == 2) { - - // Rule 5 has decided the issue, but how many decisions did we make? - // If priorities [0 0 2 2] went to [0 1 2 3] then - // we have two Rule-5 decisions -- R,S,R',S'. - // In that case, Rule 5 results in R/S, not r/s. - // - // S - // - - // - - // R---C---R' despite this being Rule 5, the results is R, not r. - // - - // - - // S' - // - // --------- mirror plane - // - // R' - // - - // - - // S---C---S' not superimposible - // - - // - - // R - // - canBePseudo = false; - } - } - } - if ((Logger.debuggingHigh) && atoms[2].atom != null + if (Logger.debuggingHigh && atoms[2].atom != null && atoms[2].elemNo != 1) { // Logger Logger.info(dots() + atom + " nPriorities = " + nPriorities); for (int i = 0; i < 4; i++) { // Logger @@ -2157,71 +2140,6 @@ } /** - * CheckSpiro uses the concept that Andrey Yerin suggested that - * covers all spiro, double spiro, C3-, C3-symmetric cases from CIP(1966). - * Incredibly simple! - * - */ - private void checkSpiro() { - boolean swap23 = false; - int a = 0, b = -1; - switch (nPriorities * 10 + priorities[3]) { - case 10: - // // CIP Helv Chim. Acta 1966 #33 -- double spiran - // // 0-1,2-3 or 0-1,3-2 - // // 0-2,1-3 or - // // 0-3,1-2 - b = 1; - swap23 = true; // just the way it has to be - break; - case 21: - case 23: - // CIP Helv. Chim. Acta 1966 #32 - // [0 1 1 1] or [0 0 0 3] - a = priorities[1]; - break; - case 22: - // - // We have priorities [0 0 2 2], possibly being spiro - // - b = getSpiroEnd(0); - break; - default: - return; - } - if (atoms[a].spiroEnd == null) - return; - c3RefIndex = atoms[a].atomIndex; - if (b >= 0) - c4RefIndex = atoms[b].atomIndex; - if (sortByRule(RULE_6)) { - currentRule = RULE_6; - if (swap23) { - CIPAtom atom = atoms[2]; - atoms[2] = atoms[3]; - atoms[3] = atom; - } - } - } - - /** - * Find the other end of a loop to root. - * - * @param i0 - * index of the spiro starting atom - * @return pointer to spiro end of atoms[0] -- either -1 (not spiro), 0, 1, 2, - * or 3 - */ - private int getSpiroEnd(int i0) { - CIPAtom a = atoms[i0].spiroEnd; - if (a != null) - for (int i = 0; i < 4; i++) - if (i != i0 && a.atom == atoms[i].atom) - return i; - return -1; - } - - /** * Provide an indent for clarity in debugging messages * * @return a string of dots based on the value of atom.sphere. @@ -2391,28 +2309,27 @@ * @param maxRule * @return TIED or deciding rule RULE_1a - RULE_5 */ - private int sortToRule(int maxRule) { + private int sortToRule(int maxRule, boolean skipRules4And5) { for (int i = RULE_1a; i <= maxRule; i++) - if (sortByRule(i)) + if ((!skipRules4And5 || i < RULE_4a || i > RULE_5) &&sortByRule(i)) return i; - return TIED; + return 0; } /** - * Sort by a given rule, preserving currentRule. + * Sort by a given rule, preserving currentRule, which could be 4 or 5 * * @param rule * @return true if a decision has been made */ private boolean sortByRule(int rule) { - if ((rule == RULE_4b || rule == RULE_4bTEST) - && (rule == RULE_4bTEST) != isRule4TEST) - return false; int current = currentRule; currentRule = rule; + int rule6ref = root.rule6refIndex; if (rule == RULE_6) - sortSubstituents(Integer.MIN_VALUE); + setupRule6(true); boolean isChiral = sortSubstituents(0); + root.rule6refIndex = rule6ref; currentRule = current; return isChiral; } @@ -2428,7 +2345,7 @@ * @return 0 (TIED), -1 (A_WINS), or 1 (B_WINS), or Intege.MIN_VALUE * (IGNORE) */ - public int checkCurrentRule(CIPAtom b) { + private int checkCurrentRule(CIPAtom b) { switch (currentRule) { default: case RULE_1a: @@ -2443,38 +2360,19 @@ return checkRules4ac(b, " sr SR PM"); case RULE_4c: return checkRules4ac(b, " s r p m"); - case RULE_4bTEST: - return checkRule4Test(b); case RULE_4b: case RULE_5: return TIED; // not carried out here because these need access to a full list of ligands case RULE_6: - int score; - return ((score = checkRule6(b, root.c3RefIndex)) != TIED ? score : root.c4RefIndex >= 0 ? checkRule6(b, root.c4RefIndex) : score); + return checkRule6(b); } } - private int checkRule6(CIPAtom b, int refIndex) { - return ((atomIndex == refIndex) == (b.atomIndex == refIndex) ? TIED : - atomIndex == refIndex ? A_WINS : B_WINS); + private int checkRule6(CIPAtom b) { + return ((atomIndex == root.rule6refIndex) == (b.atomIndex == root.rule6refIndex) ? TIED : + atomIndex == root.rule6refIndex ? A_WINS : B_WINS); } - private int checkRule4Test(CIPAtom b) { - // TODO - boolean isNS = (auxChirality == '~'); - if (rootSubstituent.atomIndex == b.rootSubstituent.atomIndex) { - // sortSubstituent checking priority within a ligand - if (isNS || b.auxChirality == '~') - return (!isNS ? A_WINS : b.auxChirality != '~' ? B_WINS : TIED); - return (auxChirality == b.auxChirality ? TIED - : auxChirality == rootSubstituent.rule4Ref ? A_WINS : B_WINS); - } - // - return (isNS ? TIED - : (auxChirality == b.auxChirality) == rootSubstituent.rule4checkIdentical ? TIED - : auxChirality == rootSubstituent.rule4Ref ? A_WINS : B_WINS); - } - /** * Looking for same atom or ghost atom or element number * @@ -3003,16 +2901,19 @@ char c = '~'; if (atom == null) return "" + c; + rule4List = new String[4]; // full list based on atoms[] if (nPriorities == 0 && !isSet) { setNode(); if (!isAlkene && !isDuplicate && !isTerminal) - sortToRule(RULE_3); + sortToRule(RULE_3, false); } int rs = -1, nRS = 0; CIPAtom[] ret1 = new CIPAtom[1]; - int ruleMax = RULE_5; + boolean skipRules4And5 = false; boolean prevIsChiral = true; + // have to allow two same f because could be a C3-symmetric subunit + boolean allowTwoSame = (node1 != null && !isAlkene && nPriorities == 1); for (int i = 0; i < 4; i++) { CIPAtom a = atoms[i]; if (a != null && !a.isDuplicate && !a.isTerminal) { @@ -3033,11 +2934,13 @@ retThread = rsPath; prevIsChiral = true; } else { - if (!prevIsChiral && priorities[i] == priorities[i - 1]) { - if (node1 == null) + if (!allowTwoSame && !prevIsChiral && priorities[i] == priorities[i - 1]) { + // actually, I don't think priorities are set yet. + if (node1 == null) { for (; i >= 0; --i) rule4List[i] = null; // two groups have the same priority, and neither has a stereocenter + } return "~"; } prevIsChiral = false; @@ -3050,7 +2953,7 @@ retThread = ""; //$FALL-THROUGH$ case 1: - ruleMax = RULE_3; + skipRules4And5 = true; break; case 2: case 3: @@ -3110,7 +3013,7 @@ } } } - } else if (canBePseudo) { + } else if (isSP3) { // if here, adj is TIED (0) or NOT_RELEVANT CIPAtom atom1 = (CIPAtom) clone(); if (atom1.setNode()) { @@ -3124,8 +3027,8 @@ } } } - int rule = atom1.sortToRule(ruleMax); - if (rule == TIED) { + int rule = atom1.sortToRule(RULE_6, skipRules4And5); + if (rule < RULE_1a) { c = '~'; } else { rs = atom1.checkHandedness(); Modified: trunk/Jmol/src/org/jmol/viewer/JC.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/JC.java 2017-07-21 22:33:07 UTC (rev 21674) +++ trunk/Jmol/src/org/jmol/viewer/JC.java 2017-07-23 20:07:02 UTC (rev 21675) @@ -99,7 +99,7 @@ } } - private static final String[] ruleNames = {"", "1a", "1b", "2", "3", "4a", "4b", "4b", "4c", "5", "6"}; + private static final String[] ruleNames = {"", "1a", "1b", "2", "3", "4a", "4b", "4c", "5", "6"}; public static String getCIPRuleName(int i) { return ruleNames[i]; } 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