Revision: 21562 http://sourceforge.net/p/jmol/code/21562 Author: hansonr Date: 2017-04-27 17:06:47 +0000 (Thu, 27 Apr 2017) Log Message: ----------- passes Mikko's stereo_test_cases.sdf with exception of allenes and hypervalent P/S
Modified Paths: -------------- trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java Modified: trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java =================================================================== --- trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java 2017-04-27 05:11:09 UTC (rev 21561) +++ trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java 2017-04-27 17:06:47 UTC (rev 21562) @@ -238,6 +238,8 @@ static final int RULE_4 = 4; static final int RULE_5 = 5; + static final float TRIGONALITY_MIN = 0.2f; + public String getRuleName() { return "" + currentRule; } @@ -382,7 +384,7 @@ bsToDo.clear(i); continue; } - if (!haveAlkenes && couldBeEZ(atoms[i])) + if (!haveAlkenes && couldBeEZ(atoms[i], null)) // do Rule 3, and check for rings that in the end should force removal of E/Z designations haveAlkenes = true; } @@ -433,28 +435,58 @@ } } float d = getTrigonality(a, vNorm); - return ((Math.abs(d) < 0.2f) == mustBePlanar); // arbitrarily set + return ((Math.abs(d) < TRIGONALITY_MIN) == mustBePlanar); // arbitrarily set } - private boolean couldBeEZ(Node a) { + /** + * Allow double bonds only if trivalent and first-row atom. (IUPAC + * 2013.P-93.2.4) Currently: a) first row b) doubly bonded c) doubly bonded + * atom is also first row + * + * @param a + * @param b optional other atom + * @return if the atom could be an EZ node + */ + private boolean couldBeEZ(Node a, Node b) { + switch (a.getCovalentBondCount()) { + default: + return false; + case 2: + // imines and diazines + if (a.getElementNumber() != 7) // nitrogen + return false; + break; + case 3: + // first-row only (IUPAC 2013.P-93.2.4) + if (!isFirstRow(a)) + return false; + break; + } Edge[] bonds = a.getEdges(); - if (a.getElementNumber() > 10) - return false; - for (int i = bonds.length, pt = 0; --i >= 0 && pt < 3;) - if (bonds[i].getCovalentOrder() == 2) - return true; - return false; + int n = 0; + for (int i = bonds.length; --i >= 0;) + if (bonds[i].getCovalentOrder() == 2) { + if (++n > 1) + return false; // no central allenes + Node other = bonds[i].getOtherAtomNode(a); + if (!isFirstRow(other)) + return false; + if (b != null && (other != b || b.getCovalentBondCount() == 1)) { + // could be allene central, but I think this is not necessary + return false; + } + } + return true; } /** - * Allow double bonds only if trivalent and first-row atom. (IUPAC 2013.P-93.2.4) + * Check if an atom is 1st row * @param a - * @param b - * @return true if this bond could be E/Z + * @return elemno > 2 && elemno <= 10 */ - private boolean couldBeChiralBond(Node a, Node b) { - return (a.getElementNumber() < 10 && b.getElementNumber() < 10 - && a.getCovalentBondCount() == 3 && b.getCovalentBondCount() == 3); + boolean isFirstRow(Node a) { + int n = a.getElementNumber(); + return (n > 2 && n <= 10); } /** @@ -602,7 +634,7 @@ cipAtom = new CIPAtom(atom, null, false, isAlkene); int nSubs = atom.getCovalentBondCount(); int elemNo = atom.getElementNumber(); - isAlkene = (nSubs == 3 && elemNo < 10); // (IUPAC 2013.P-93.2.4) + isAlkene = (nSubs == 3 && elemNo <= 10); // (IUPAC 2013.P-93.2.4) if (nSubs != (parent == null ? 4 : 3) - (nSubs == 3 && !isAlkene ? 1 : 0)) return rs; @@ -645,7 +677,7 @@ } } if (currentRule == 5) - cipAtom.isPseudo = true; + cipAtom.isPseudo = cipAtom.canBePseudo; } if (isChiral) { rs = (!isAlkene ? cipAtom.checkHandedness() @@ -684,18 +716,15 @@ if (bond.getCovalentOrder() == 2) { Node a = atoms[bond.getAtomIndex1()]; Node b = atoms[bond.getAtomIndex2()]; - // no imines for now - if (!couldBeChiralBond(a,b)) + if (!couldBeEZ(a,b)) return NO_CHIRALITY; htPathPoints = new Hashtable<String, Integer>(); CIPAtom a1 = new CIPAtom(a, null, false, true); CIPAtom b1 = new CIPAtom(b, null, false, true); - //b1.fillNull(0); int atop = getAtomChiralityLimited(a, a1, b1, ruleMax) - 1; htPathPoints = new Hashtable<String, Integer>(); CIPAtom a2 = new CIPAtom(a, null, false, true); CIPAtom b2 = new CIPAtom(b, null, false, true); - //a2.fillNull(0); int btop = getAtomChiralityLimited(b, b2, a2, ruleMax) - 1; if (atop >= 0 && btop >= 0) { ez = (isCIS(b2.atoms[btop], b2, a1, a1.atoms[atop]) ? STEREO_Z @@ -864,7 +893,7 @@ public boolean isPseudo; /** - * Force achiral condition due to double ties. + * Force achiral condition due to two identical ligands after Rule 4 check. */ boolean achiral; @@ -930,6 +959,8 @@ */ private CIPAtom auxPseudo; + boolean canBePseudo = true; + /** * * @param atom @@ -945,13 +976,13 @@ return; this.isAlkene = isAlkene; this.atom = atom; + this.elemNo = atom.getElementNumber(); + this.massNo = atom.getNominalMass(); atomIndex = atom.getIndex(); bondCount = atom.getCovalentBondCount(); - if (bondCount == 3 && !isAlkene) { - getTrigonality(atom, vNorm); - lonePair = new P3(); - lonePair.sub2(atom.getXYZ(), vNorm); - } + if (bondCount == 3 && !isAlkene && elemNo > 10) + getLonePair(); + canBePseudo = (bondCount == 4 || bondCount == 3 && lonePair != null); String c = atom.getCIPChirality(false); // What we are doing here is creating a lexigraphically sortable string // R < S < r < s < ~ and C < T < ~ @@ -969,8 +1000,6 @@ rootSubstituent = this; else if (parent != null) rootSubstituent = parent.rootSubstituent; - this.elemNo = atom.getElementNumber(); - this.massNo = atom.getNominalMass(); this.bsPath = (parent == null ? new BS() : BSUtil.copy(parent.bsPath)); boolean wasDuplicate = isDuplicate; @@ -1003,6 +1032,16 @@ updateRingList(); } + private void getLonePair() { + float d = getTrigonality(atom, vNorm); + if (Math.abs(d) > TRIGONALITY_MIN) { + lonePair = new P3(); + vNorm.scale(d); + lonePair.add2(atom.getXYZ(), vNorm); +// System.out.println("$ draw " + lonePair + " // " + atom ); + } + } + /** * Create a bit set that gives all the atoms in this ring if it is smaller * than 8. @@ -1035,7 +1074,6 @@ isSet = true; if (isDuplicate) return true; - // atoms = new CIPAtom[4]; int nBonds = atom.getBondCount(); Edge[] bonds = atom.getEdges(); if (Logger.debuggingHigh) @@ -1049,7 +1087,7 @@ boolean isParent = (parent != null && parent.atom == other); int order = bond.getCovalentOrder(); if (order == 2) { - if (elemNo > 10 || other.getElementNumber() > 10) + if (elemNo > 10 || !isFirstRow(other)) order = 1; else { isAlkene = true; @@ -1079,7 +1117,7 @@ //$FALL-THROUGH$ case 1: if (!isParent - && addAtom(pt++, other, order != 1 && elemNo < 10, false) == null) { + && addAtom(pt++, other, order != 1 && elemNo <= 10, false) == null) { isTerminal = true; return false; } @@ -1091,7 +1129,8 @@ } isTerminal = (pt == 0); nAtoms = pt; - fillNull(pt); + for (; pt < atoms.length; pt++) + atoms[pt] = new CIPAtom(null, this, true, false); // Do an initial atom-only shallow sort using a.compareTo(b) @@ -1103,11 +1142,6 @@ return !isTerminal; } - private void fillNull(int pt) { - for (; pt < atoms.length; pt++) - atoms[pt] = new CIPAtom(null, this, true, false); - } - /** * Add a new atom or return null * @@ -1149,11 +1183,9 @@ */ void sortSubstituents() { - int n = 4; - if (Logger.debugging) { Logger.info(root + "---sortSubstituents---" + this); - for (int i = 0; i < n; i++) { + for (int i = 0; i < 4; i++) { Logger.info(getRuleName() + ": " + this + "[" + i + "]=" + atoms[i].myPath + " " + Integer.toHexString(prevPriorities[i])); } @@ -1161,17 +1193,17 @@ int[] indices = new int[4]; - BS ties = null; + Lst<int[]>ties = null; - for (int i = 0; i < n; i++) { + for (int i = 0; i < 4; i++) { priorities[i] = 1; if (prevPriorities[i] == 0 && currentRule > RULE_1) prevPriorities[i] = getBasePriority(atoms[i]); } boolean checkRule4List = (currentRule > RULE_3 && rule4List != null); - for (int i = 0; i < n; i++) { + for (int i = 0; i < 4; i++) { CIPAtom a = atoms[i]; - for (int j = i + 1; j < n; j++) { + for (int j = i + 1; j < 4; j++) { CIPAtom b = atoms[j]; int score = (a.atom == null ? B_WINS : b.atom == null ? A_WINS : prevPriorities[i] == prevPriorities[j] ? TIED @@ -1231,21 +1263,20 @@ // there are not two such sets. doCheckPseudo = false; if (ties == null) - ties = new BS(); - ties.set(i); - ties.set(j); + ties = new Lst<int[]>(); + ties.addLast(new int[]{ i, j }); } } } // update the substituent arrays - CIPAtom[] newAtoms = new CIPAtom[n]; - int[] newPriorities = new int[n]; - int[] newPrevPriorities = new int[n]; + CIPAtom[] newAtoms = new CIPAtom[4]; + int[] newPriorities = new int[4]; + int[] newPrevPriorities = new int[4]; BS bs = new BS(); int shift = PRIORITY_SHIFT[currentRule]; - for (int i = 0; i < n; i++) { + for (int i = 0; i < 4; i++) { int pt = indices[i]; CIPAtom a = newAtoms[pt] = atoms[i]; int p = priorities[i]; @@ -1262,17 +1293,18 @@ nPriorityMax = nPriorities; if (ties != null) { - if (ties.cardinality() == 2) { - //if (sphere != 0 || useAuxiliaries) { - checkPseudoHandedness(ties, indices); - //} - } else if (sphere == 0) { - achiral = true; + switch (ties.size()) { + case 1: + checkPseudoHandedness(ties.get(0), indices); + break; + case 2: + canBePseudo = false; + break; } } if (Logger.debugging) { Logger.info(atom + " nPriorities = " + nPriorities); - for (int i = 0; i < n; i++) + for (int i = 0; i < 4; i++) Logger.info(this.myPath + "[" + i + "]=" + atoms[i] + " " + priorities[i] + " new"); Logger.info("-------"); @@ -1620,7 +1652,6 @@ * * @param ia * @param ib - * @param isRule5 * @return 0 (TIED), -1 (A_WINS), or 1 (B_WINS), or Integer.MIN_VALUE * (IGNORE) */ @@ -1706,7 +1737,10 @@ return TIED; if (aref == bref) return IGNORE; - doCheckPseudo = (aref == 'R' || aref == 'S'); + // are opposites + if (!canBePseudo) + root.canBePseudo = false; + doCheckPseudo = canBePseudo && (aref == 'R' || aref == 'S'); return aref < bref ? A_WINS : B_WINS; } @@ -1792,9 +1826,12 @@ } /** - * @param lst - * @param aref - * @return + * Sort Mata list of ["RS...", "SR..."] by temporarily assigning the reference atom + * chirality the letter "A" and then sorting lexicographically. + * + * @param lst + * @param aref + * @return sorted list */ private String[] getMataSortedList(String[] lst, String aref) { int n = lst.length; @@ -1804,18 +1841,26 @@ Arrays.sort(sorted); for (int i = 0; i < n; i++) sorted[i] = PT.rep(sorted[i], "A", aref); - - for (int i = 0; i < n; i++) { - System.out.println("Sorted Mata list " + i + " " + sorted[i]); - } + if (Logger.debuggingHigh) + for (int i = 0; i < n; i++) + Logger.info("Sorted Mata list " + i + " " + sorted[i]); return sorted; } /** - * Determine the reference configuration. + * Determine the reference designation. * + * This is a key element of Mata analysis. We must above all, respect + * the already-determined priorities of these paths. So, for example, if + * we are looking at {1:SRRR, 2:RSSS, 2:RSSS}, where 1 and 2 are priorities, with + * 1 being higher priority than 2, then the reference designation is S, not R. + * + * When there is one S and one R, return "RS". + * + * Not fully tested. + * * @param lst - * @return + * @return R, S, or RS */ private String getMataRef(String[] lst) { // get highest-ranking chiral unit @@ -2003,13 +2048,13 @@ /** * Reverse the path to the parent and check r/s chirality * - * @param ties + * @param iab * @param indices * */ - private void checkPseudoHandedness(BS ties, int[] indices) { - int ia = ties.nextSetBit(0); - int ib = ties.nextSetBit(ia + 1); + private void checkPseudoHandedness(int[] iab, int[] indices) { + int ia = iab[0]; + int ib = iab[1]; CIPAtom atom1; if (auxPseudo == null) { // critical here that we do NOT include the tied branches 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