Revision: 21582 http://sourceforge.net/p/jmol/code/21582 Author: hansonr Date: 2017-05-05 23:23:47 +0000 (Fri, 05 May 2017) Log Message: ----------- CIP Chirality fix for all but three structures in AY-236
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-05-01 03:39:15 UTC (rev 21581) +++ trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java 2017-05-05 23:23:47 UTC (rev 21582) @@ -3,6 +3,7 @@ import java.util.Arrays; import java.util.Hashtable; import java.util.Map; + import javajs.util.Lst; import javajs.util.Measure; import javajs.util.P3; @@ -69,8 +70,12 @@ * 4/27/17 Ruled 3-5 completed 14.15.1 * 4/28/17 Validated for 146 compounds, including imines and diazines, sulfur, phosphorus * 4/29/17 validated for 160 compounds, including M/P, m/p (axial chirality for biaryls and odd-number cumulenes) + * 5/02/17 validated for 161 compounds, including M/P, m/p (axial chirality for biaryls and odd-number cumulenes) * + * TODO: mix seqCis and seqTrans with R/S + * TODO: mix allene M/P with R/S * + * * validation suite: see https://sourceforge.net/p/jmol/code/HEAD/tree/trunk/Jmol-datafiles/cip/ * * @author Bob Hanson hans...@stolaf.edu @@ -83,12 +88,16 @@ // // getChirality(molecule) { // checkForAlkenes() - // if (haveAlkenes) checkForSmallRings() - // for(all atoms) getChirality(applyRules1-3) - // for(all double bonds) checkEZ() - // for(all atoms still without designations) getChirality(applyRules1-5) - // for(all double bonds) checkEZ() - // if (haveAlkenes) removeUnnecessaryEZDesignations() + // for(all atoms) getAtomChirality(applyRules1-3) + // if (haveAlkenes) { + // checkForSmallRings() + // for(all double bonds) getBondChirality(applyRules1-3) + // } + // for(all atoms still without designations) getAtomChirality(applyRules1-5) + // if (haveAlkenes) { + // for(all double bonds still without designations) getBondChirality(applyRules1-5) + // removeUnnecessaryEZDesignations() + // } // } // // getChirality(atom) { @@ -160,19 +169,55 @@ static final int STEREO_BOTH_RS = STEREO_R | STEREO_S; static final int STEREO_BOTH_EZ = STEREO_Z | STEREO_E; - static final int RULE_1 = 1; - static final int RULE_2 = 2; - static final int RULE_3 = 3; - static final int RULE_4 = 4; - static final int RULE_5 = 5; + 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_4 = 5; + static final int RULE_5 = 6; - static final float TRIGONALITY_MIN = 0.2f; - public String getRuleName() { - return "" + currentRule; + switch (currentRule) { + case RULE_1a: + return "1a"; + case RULE_1b: + return "1b"; + default: + return "" + (currentRule - 1); + } } + final static int PRIORITY_12_MASK = 0x7FFFF000; + final static int PRIORITY_1b_MASK = 0x00F00000; + // 3 2 1 + // 210987654321098765432109876543210 + // -1a---- 0x7F << 24 + // -1b- 0xF << 20 + // -2------ 0xFF << 12 + // -3- 0x7 << 9 + // -4- 0x7 << 6 + // -5- 0x7 << 3 + + final static int[] PRIORITY_SHIFT = new int[] { -1, 24, 20, 12, 9, 6, 3, 0 }; + /** + * + * Create a priority key that matches elemNo and massNo. + * + * We can skip the duplicate flag, because all these have substituents. + * + * @param a + * @return a shifted key based on elemNo and massNo + */ + static int getBasePriority(CIPAtom a) { + return (a.atom == null ? PRIORITY_12_MASK + : ((127 - a.elemNo) << PRIORITY_SHIFT[RULE_1a]) + | ((255 - a.massNo) << PRIORITY_SHIFT[RULE_2])); + } + + static final float TRIGONALITY_MIN = 0.2f; + + /** * incremental pointer providing a unique ID to every CIPAtom for debugging * */ @@ -188,18 +233,11 @@ * The current rule being applied exhaustively. * */ - int currentRule = RULE_1; + int currentRule = RULE_1a; /** - * Rule 1b hash table that maintains distance of the associated nonduplicated - * atom node - * + * track small rings for removing E/Z indicators as per IUPAC 2013.P-93.5.1.4.1 */ - Map<String, Integer> htPathPoints; - - /** - * track small rings for removing E/Z indicators as per IUPAC 2013.P-93.5.7.3 - */ Lst<BS> lstSmallRings = new Lst<BS>(); /** @@ -248,31 +286,29 @@ public void getChiralityForAtoms(Node[] atoms, BS bsAtoms, BS bsAtropisomeric) { if (bsAtoms.isEmpty()) return; - this.bsAtropisomeric = bsAtropisomeric; - init(); - BS bsToDo = BSUtil.copy(bsAtoms); - boolean haveAlkenes = preFilterAtomList(atoms, bsToDo); - // Initial Rules 1-3 only + // Initially only Rules 1-3 for (int i = bsToDo.nextSetBit(0); i >= 0; i = bsToDo.nextSetBit(i + 1)) { Node atom = atoms[i]; - // This call checks to be sure we do not already have it. - + // Jmol will clear this anyway prior to CALCULATE CHIRALITY, so this is mute. + // But it is here in case we want to implement full molecular chirality generation + // prior to any request for atom.chirality. Right now we don't do that. String c = atom.getCIPChirality(false); if (c.length() > 0) { bsToDo.clear(i); } else { + ptID = 0; atom.setCIPChirality(getAtomChiralityLimited(atom, null, null, RULE_3)); } } - // E/Z -- Rule 3 + // bond chiralities E/Z and M/P only through Rule 3 Lst<int[]> lstEZ = new Lst<int[]>(); if (haveAlkenes) { @@ -282,27 +318,29 @@ getAtomBondChirality(atoms[i], false, RULE_3, lstEZ, bsToDo); } - // On to Rules 4 and 5 for atoms that still could be chiral but don't have a designation. + // Interesting question as to whether we need to do this in two stages, but + // for now that is what we are doing. The rules require that we can do this, + // because no later rule can reverse the result of a previous rule, and so + // if we do Rules 1-3 for all atoms and then Rules 1-5 for all atoms, that is + // at worst inefficient. for (int i = bsToDo.nextSetBit(0); i >= 0; i = bsToDo.nextSetBit(i + 1)) { Node a = atoms[i]; a.setCIPChirality(0); - a.setCIPChirality(getAtomChiralityLimited(a, null, null, 5)); + a.setCIPChirality(getAtomChiralityLimited(a, null, null, RULE_5)); } + // if (haveAlkenes) { for (int i = bsToDo.nextSetBit(0); i >= 0; i = bsToDo.nextSetBit(i + 1)) getAtomBondChirality(atoms[i], false, RULE_5, lstEZ, bsToDo); + // Finally, remove any E/Z indications in small rings + if (lstSmallRings.size() > 0 && lstEZ.size() > 0) + clearSmallRingEZ(atoms, lstEZ); } - - - - // Finally, remove any E/Z indications in small rings - - if (lstSmallRings.size() > 0 && lstEZ.size() > 0) - clearSmallRingEZ(atoms, lstEZ); + } /** @@ -433,7 +471,6 @@ */ private void getSmallRings(Node atom) { lstSmallRings = new Lst<BS>(); - htPathPoints = new Hashtable<String, Integer>(); root = new CIPAtom().create(atom, null, false, false); addSmallRings(root); } @@ -458,7 +495,7 @@ } /** - * Remove E/Z designations for small-rings double bonds (IUPAC 2013.P-93.5.7.3). + * Remove E/Z designations for small-rings double bonds (IUPAC 2013.P-93.5.1.4.1). * * @param atoms * @param lstEZ @@ -524,7 +561,7 @@ } /** - * Get E/Z characteristics for specific atoms + * Get E/Z characteristics for specific atoms. Also check here for atropisomeric M/P designations * * @param atom * @param allBonds @@ -539,27 +576,35 @@ Lst<int[]> lstEZ, BS bsToDo) { int index = atom.getIndex(); Edge[] bonds = atom.getEdges(); - if (bsAtropisomeric.get(index)) { - for (int j = bonds.length; --j >= 0;) { - Node atom1 = bonds[j].getOtherAtomNode(atom); - int index1 = atom1.getIndex(); + int c = NO_CHIRALITY; + boolean isAtropic = bsAtropisomeric.get(index); + for (int j = bonds.length; --j >= 0;) { + Edge bond = bonds[j]; + Node atom1; + int index1; + if (isAtropic) { + atom1 = bonds[j].getOtherAtomNode(atom); + index1 = atom1.getIndex(); if (!bsAtropisomeric.get(index1)) continue; - getAxialOrEZChirality(atom, atom1, true, ruleMax); - break; + c = getAxialOrEZChirality(atom, atom1, atom, atom1, true, ruleMax); + } else if (bond.getCovalentOrder() == 2) { + atom1 = getLastCumuleneAtom(bond, atom, null, null); + index1 = atom1.getIndex(); + if (!allBonds && index1 < index) + continue; + c = getBondChiralityLimited(bond, atom, ruleMax); + } else { + continue; } - return; - } - for (int j = bonds.length; --j >= 0;) { - Edge bond = bonds[j]; - if (bond.getCovalentOrder() == 2) { - Node atom2 = getLastCumuleneAtom(bond, atom, null); - int index2 = atom2.getIndex(); - if ((allBonds || index2 > index) - && getBondChiralityLimited(bond, atom, ruleMax) != NO_CHIRALITY) { - lstEZ.addLast(new int[] { index, index2 }); - } + if (c != NO_CHIRALITY) { + if (!isAtropic) + lstEZ.addLast(new int[] { index, index1 }); + bsToDo.clear(index); + bsToDo.clear(index1); } + if (isAtropic) + break; } } @@ -567,15 +612,23 @@ * * @param bond * @param atom - * @param nSP2 return of number of sp2 carbons in this alkene or cumulene + * @param nSP2 + * return of number of sp2 carbons in this alkene or cumulene + * @param parents * @return the terminal atom of this alkene or cumulene */ - private Node getLastCumuleneAtom(Edge bond, Node atom, int[] nSP2) { + private Node getLastCumuleneAtom(Edge bond, Node atom, int[] nSP2, + Node[] parents) { // we know this is a double bond Node atom2 = bond.getOtherAtomNode(atom); + if (parents != null) { + parents[0] = atom2; + parents[1] = atom; + } // connected atom must have only two covalent bonds if (nSP2 != null) nSP2[0] = 2; + int ppt = 0; while (true) { if (atom2.getCovalentBondCount() != 2) return atom2; @@ -587,6 +640,13 @@ // connected atom must only have one other bond, and it must be double to continue if (bond.getCovalentOrder() != 2) return atom2; // was atom3 + if (parents != null) { + if (ppt == 0) { + parents[0] = atom2; + ppt = 1; + } + parents[1] = atom2; + } // a=2=3 if (nSP2 != null) nSP2[0]++; @@ -618,7 +678,6 @@ boolean isAlkene = false; try { if (cipAtom == null) { - htPathPoints = new Hashtable<String, Integer>(); cipAtom = new CIPAtom().create(atom, null, false, isAlkene); int nSubs = atom.getCovalentBondCount(); int elemNo = atom.getElementNumber(); @@ -630,17 +689,23 @@ atom = cipAtom.atom; isAlkene = cipAtom.isAlkene; } + + +// if (atom.getAtomNumber() != 1) +// return NO_CHIRALITY; + root = cipAtom; cipAtom.parent = parent; - currentRule = RULE_1; + if (parent != null) + cipAtom.htPathPoints = parent.htPathPoints; + currentRule = RULE_1a; if (cipAtom.set()) { - for (currentRule = RULE_1; currentRule <= ruleMax && !isChiral; currentRule++) { + for (currentRule = RULE_1a; currentRule <= ruleMax; currentRule++) { if (Logger.debugging) Logger.info("-Rule " + getRuleName() + " CIPChirality for " + cipAtom + "-----"); - if (currentRule == RULE_4) { - cipAtom.resetAuxiliaryChirality(); - cipAtom.createAuxiliaryRSCenters(true); + //cipAtom.resetAuxiliaryChirality(); + cipAtom.createAuxiliaryRSCenters(null, null); } isChiral = false; cipAtom.sortSubstituents(); @@ -648,7 +713,7 @@ if (Logger.debugging) { Logger.info(currentRule + ">>>>" + cipAtom); for (int i = 0; i < cipAtom.bondCount; i++) { // Logger - if (cipAtom.atoms[i] == null) // Logger + if (cipAtom.atoms[i] != null) // Logger Logger.info(cipAtom.atoms[i] + " " + Integer.toHexString(cipAtom.prevPriorities[i])); } } @@ -662,15 +727,16 @@ break; } } - if (currentRule == 5) + if (currentRule == RULE_5) cipAtom.isPseudo = cipAtom.canBePseudo; + if (isChiral) { + rs = (!isAlkene ? cipAtom.checkHandedness() + : cipAtom.atoms[0].isDuplicate ? STEREO_S : STEREO_R); + if (cipAtom.isPseudo && !isAlkene) + rs = rs | JC.CIP_CHIRALITY_PSEUDO_FLAG; + break; + } } - if (isChiral) { - rs = (!isAlkene ? cipAtom.checkHandedness() - : cipAtom.atoms[0].isDuplicate ? STEREO_S : STEREO_R); - if (cipAtom.isPseudo && !isAlkene) - rs = rs | JC.CIP_CHIRALITY_PSEUDO_FLAG; - } if (Logger.debugging) Logger.info(atom + " " + rs + "\n----------------------------------"); } @@ -705,10 +771,11 @@ if (couldBeChiralAlkene(a, b) == STEREO_UNDETERMINED) return NO_CHIRALITY; int[] nSP2 = new int[1]; - b = getLastCumuleneAtom(bond, a, nSP2); + Node[] parents = new Node[2]; + b = getLastCumuleneAtom(bond, a, nSP2, parents); boolean isCumulene = (nSP2[0] > 2); boolean isAxial = isCumulene && (nSP2[0] % 2 == 1); - return getAxialOrEZChirality(a, b, isAxial, ruleMax); + return getAxialOrEZChirality(a, parents[0], parents[1], b, isAxial, ruleMax); } /** @@ -715,6 +782,8 @@ * Determine the axial or E/Z chirality for the a-b bond. * * @param a + * @param pa + * @param pb * @param b * @param isAxial * @param ruleMax @@ -721,14 +790,12 @@ * @return one of: {NO_CHIRALITY | STEREO_Z | STEREO_E | STEREO_Ra | STEREO_Sa * | STEREO_ra | STEREO_sa} */ - private int getAxialOrEZChirality(Node a, Node b, boolean isAxial, int ruleMax) { - htPathPoints = new Hashtable<String, Integer>(); + private int getAxialOrEZChirality(Node a, Node pa, Node pb, Node b, boolean isAxial, int ruleMax) { CIPAtom a1 = new CIPAtom().create(a, null, false, true); - CIPAtom b1 = new CIPAtom().create(b, null, false, true); + CIPAtom b1 = new CIPAtom().create(pa, null, false, true); a1.canBePseudo = a1.isOddCumulene = isAxial; int atop = getAtomChiralityLimited(a, a1, b1, ruleMax) - 1; - htPathPoints = new Hashtable<String, Integer>(); - CIPAtom a2 = new CIPAtom().create(a, null, false, true); + CIPAtom a2 = new CIPAtom().create(pb, null, false, true); CIPAtom b2 = new CIPAtom().create(b, null, false, true); b2.canBePseudo = b2.isOddCumulene = isAxial; int btop = getAtomChiralityLimited(b, b2, a2, ruleMax) - 1; @@ -736,7 +803,7 @@ if (atop >= 0 && btop >= 0) { if (isAxial) { c = (isPos(b2.atoms[btop], b2, a1, a1.atoms[atop]) ? STEREO_P : STEREO_M); - if ((a2.ties == null) != (b2.ties == null)) + if ((a1.ties == null) != (b2.ties == null)) c |= JC.CIP_CHIRALITY_PSEUDO_FLAG; } else { c = (isCis(b2.atoms[btop], b2, a1, a1.atoms[atop]) ? STEREO_Z : STEREO_E); @@ -783,12 +850,11 @@ * @return true if torsion angle is */ boolean isPos(CIPAtom a, CIPAtom b, CIPAtom c, CIPAtom d) { - return (Measure.computeTorsion(a.atom.getXYZ(), b.atom.getXYZ(), c.atom.getXYZ(), - d.atom.getXYZ(), true) > 0); + float angle = Measure.computeTorsion(a.atom.getXYZ(), b.atom.getXYZ(), c.atom.getXYZ(), + d.atom.getXYZ(), true); + return (angle > 0); } - final static int[] PRIORITY_SHIFT = new int[] { 24, 20, 12, 8, 4, 0 }; - private class CIPAtom implements Comparable<CIPAtom>, Cloneable { /** @@ -818,13 +884,13 @@ /** * Rule 1 element number */ - private int elemNo; + int elemNo; /** * Rule 2 nominal atomic mass; may be a rounded value so that 12C is the * same as C */ - private int massNo; + int massNo; /** * bond distance from the root atom to this atom @@ -949,20 +1015,18 @@ CIPAtom[] atoms = new CIPAtom[4]; /** - * priorities associated with each subsituent from high (1) to low (4); due + * priorities associated with each subsituent from high (0) to low (3); due * to equivaliencies at a given rule level, these numbers may duplicate and - * have gaps - for example, [1 1 3 4] + * have gaps - for example, [0 2 0 3] */ private int[] priorities = new int[4]; /** * a number that encodes a substituent's priority prior to any comparisons - * by previous rules; in the form abcde where a-e are digits 1-4, and a is - * for Rule 1, b is for Rule 2, etc. + * by previous rules; see PRIORITY_SHIFT. * */ - int[] prevPriorities = new int[4]; - + int[] prevPriorities = new int[] {-1, -1, -1, -1}; /** * a list that tracks stereochemical paths for Mata analysis * @@ -1015,12 +1079,27 @@ * first =X= atom in a string of =X=X=X=... */ private CIPAtom nextSP2; + + private CIPAtom nextChiralBranch; + + private int[] rule4Count; + + private int priority; + + /** + * Rule 1b hash table that maintains distance of the associated nonduplicated + * atom node + * + */ + Map<String, Integer> htPathPoints; + CIPAtom() { // had a problem in JavaScript that the constructor of an inner function cannot // access this.b$ yet. That assignment is made after construction. } + /** * * @param atom @@ -1027,10 +1106,11 @@ * or null to indicate a null placeholder * @param parent * @param isDuplicate - * @param isAlkene + * @param isAlkene * @return this */ - CIPAtom create(Node atom, CIPAtom parent, boolean isDuplicate, boolean isAlkene) { + CIPAtom create(Node atom, CIPAtom parent, boolean isDuplicate, + boolean isAlkene) { this.id = ++ptID; this.parent = parent; if (atom == null) @@ -1043,7 +1123,7 @@ bondCount = atom.getCovalentBondCount(); if (bondCount == 3 && !isAlkene && elemNo > 10) getLonePair(); - canBePseudo = (bondCount == 4 || bondCount == 3 && lonePair != null); + 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 < ~ @@ -1050,17 +1130,16 @@ // we ignore r and s here. if (c.equals("") || c.equals("r") || c.equals("s")) c = "~"; // none - else if (c.equals("E")) - c = "T"; - else if (c.equals("Z")) - c = "C"; knownAtomChirality = c; if (parent != null) sphere = parent.sphere + 1; - if (sphere == 1) + if (sphere == 1) { rootSubstituent = this; - else if (parent != null) + htPathPoints = new Hashtable<String, Integer>(); + } else if (parent != null) { rootSubstituent = parent.rootSubstituent; + htPathPoints = rootSubstituent.htPathPoints; + } this.bsPath = (parent == null ? new BS() : BSUtil.copy(parent.bsPath)); boolean wasDuplicate = isDuplicate; @@ -1076,19 +1155,19 @@ isDuplicate = true; } else if (bsPath.get(atomIndex)) { isDuplicate = true; - rootDistance = htPathPoints.get(rootSubstituent.atom.toString() + atom) + rootDistance = rootSubstituent.htPathPoints.get(atom.toString()) .intValue(); } else { bsPath.set(atomIndex); rootDistance = parent.rootDistance + 1; - htPathPoints.put(rootSubstituent.atom.toString() + atom, new Integer( + rootSubstituent.htPathPoints.put(atom.toString(), new Integer( rootDistance)); } this.isDuplicate = isDuplicate; myPath = (parent != null ? parent.myPath + "-" : "") + this; - if (Logger.debugging) - Logger.info("new CIPAtom " + parent + "->" + this); + // if (Logger.debugging) + // Logger.info("new CIPAtom " + parent + "->" + this); if (isDuplicate && !wasDuplicate) updateRingList(); return this; @@ -1099,8 +1178,7 @@ if (Math.abs(d) > TRIGONALITY_MIN) { lonePair = new P3(); vNorm.scale(d); - lonePair.add2(atom.getXYZ(), vNorm); -// System.out.println("$ draw " + lonePair + " // " + atom ); + lonePair.add2(atom.getXYZ(), vNorm); } } @@ -1136,6 +1214,7 @@ isSet = true; if (isDuplicate) return true; + int nBonds = atom.getBondCount(); Edge[] bonds = atom.getEdges(); if (Logger.debuggingHigh) @@ -1172,6 +1251,7 @@ isTerminal = true; return true; } + // from here on, isTerminal indicates an error condition switch (order) { case 3: if (addAtom(pt++, other, isParentBond, false) == null) { @@ -1198,6 +1278,7 @@ return false; } } + // don't think this can happen isTerminal = (pt == 0); nAtoms = pt; for (; pt < atoms.length; pt++) @@ -1206,10 +1287,12 @@ // Do an initial atom-only shallow sort using a.compareTo(b) int ruleNow = currentRule; - currentRule = RULE_1; + currentRule = RULE_1a; Arrays.sort(atoms); currentRule = ruleNow; - + // pretty sure this next test cannot be true + if (isTerminal) + System.out.println("????"); return !isTerminal; } @@ -1240,8 +1323,9 @@ } } atoms[i] = new CIPAtom().create(other, this, isDuplicate, isAlkene); - if (currentRule > RULE_2) + if (currentRule > RULE_2) { prevPriorities[i] = getBasePriority(atoms[i]); + } return atoms[i]; } @@ -1251,6 +1335,15 @@ * */ void sortSubstituents() { + + int[] indices = new int[4]; + + ties = null; + + for (int i = 0; i < 4; i++) { + priorities[i] = 0; + } + if (Logger.debugging) { Logger.info(root + "---sortSubstituents---" + this); for (int i = 0; i < 4; i++) { // Logger @@ -1259,28 +1352,21 @@ Logger.info("---"); } - int[] indices = new int[4]; - ties = null; - - for (int i = 0; i < 4; i++) { - priorities[i] = 1; - if (prevPriorities[i] == 0 && currentRule > RULE_1) - prevPriorities[i] = getBasePriority(atoms[i]); - } + // if this is Rule 4 or 5, then we do a check of the forward-based stereochemical path boolean checkRule4List = (currentRule > RULE_3 && rule4List != null); for (int i = 0; i < 4; i++) { CIPAtom a = atoms[i]; for (int j = i + 1; j < 4; j++) { CIPAtom b = atoms[j]; + boolean Logger_debugHigh = Logger.debuggingHigh && b.isHeavy() && a.isHeavy(); int score = (a.atom == null ? B_WINS : b.atom == null ? A_WINS : prevPriorities[i] == prevPriorities[j] ? TIED : prevPriorities[j] < prevPriorities[i] ? B_WINS : A_WINS); - // if this is Rule 4, then we do a check of the forward-based stereochemical path if (score == TIED) score = (checkRule4List ? checkRule4And5(i, j) : a.compareTo(b)); - if (Logger.debuggingHigh) - Logger.info("ordering " + this.id + "." + i + "." + j + " " + this + "-" + a + " vs " + b + " = " + score); + if (Logger_debugHigh) + Logger.info(dots() + "ordering " + this.id + "." + i + "." + j + " " + this + "-" + a + " vs " + b + " = " + score); switch (score) { case IGNORE: // just increment the index and go on to the next rule -- no breaking of the tie @@ -1287,21 +1373,21 @@ if (checkRule4List && sphere == 0) achiral = true; // two ligands for the root atom found to be equivalent in Rule 4 must be achiral indices[i]++; - if (Logger.debuggingHigh) - Logger.info(atom + "." + b + " ends up with tie with " + a); + if (Logger_debugHigh) + Logger.info(dots() + atom + "." + b + " ends up with tie with " + a); break; case B_WINS: indices[i]++; priorities[i]++; - if (Logger.debuggingHigh) - Logger.info(this + "." + b + " B-beats " + a); + if (Logger_debugHigh) + Logger.info(dots() + this + "." + b + " B-beats " + a); break; case A_WINS: indices[j]++; priorities[j]++; - if (Logger.debuggingHigh) - Logger.info(this + "." + a + " A-beats " + b); + if (Logger_debugHigh) + Logger.info(dots() + this + "." + a + " A-beats " + b); break; case TIED: score = a.breakTie(b); @@ -1308,20 +1394,20 @@ switch (sign(score)) { case TIED: indices[i]++; - if (Logger.debuggingHigh) - Logger.info(this + "." + b + " ends up with tie with " + a); + if (Logger_debugHigh) + Logger.info(dots() + this + "." + b + " ends up with tie with " + a); break; case B_WINS: indices[i]++; priorities[i]++; - if (Logger.debuggingHigh) - Logger.info(this + "." + b + " wins in tie with " + a); + if (Logger_debugHigh) + Logger.info(dots() + this + "." + b + " wins in tie with " + a); break; case A_WINS: indices[j]++; priorities[j]++; - if (Logger.debuggingHigh) - Logger.info(this + "." + a + " wins in tie with " + b); + if (Logger_debugHigh) + Logger.info(dots() + this + "." + a + " wins in tie with " + b); break; } break; @@ -1349,7 +1435,12 @@ CIPAtom a = newAtoms[pt] = atoms[i]; int p = priorities[i]; newPriorities[pt] = p; - newPrevPriorities[pt] = prevPriorities[i] | (p << shift); + int pp = prevPriorities[i]; + if (pp < 0) + pp = 0; + if (currentRule == RULE_1b) + pp = getBasePriority(atoms[i]) & ~PRIORITY_1b_MASK; + newPrevPriorities[pt] = pp | (p << shift); if (a.atom != null) bs.set(priorities[i]); } @@ -1371,13 +1462,18 @@ } } if (Logger.debugging) { - Logger.info(atom + " nPriorities = " + nPriorities); - for (int i = 0; i < 4; i++) // Logger - Logger.info(this.myPath + "[" + i + "]=" + atoms[i] + " " + priorities[i] + " new"); - Logger.info("-------"); + Logger.info(dots() + atom + " nPriorities = " + nPriorities); + for (int i = 0; i < 4; i++) { // Logger + Logger.info(dots() + myPath + "[" + i + "]=" + atoms[i] + " " + priorities[i] + " new"); + } + Logger.info(dots() + "-------"); } } + private String dots() { + return ".....................".substring(0, Math.min(20, sphere)); + } + /** * Break a tie at any level in the iteration between to atoms that otherwise * are the same by sorting their substituents. @@ -1387,19 +1483,30 @@ */ private int breakTie(CIPAtom b) { + + if (Logger.debugging && isHeavy() && b.isHeavy()) + Logger.info(dots() + "tie for " + this + " and " + b + " at sphere " + sphere); + + // Two duplicates of the same atom are always tied. + if (isDuplicate && b.isDuplicate && atom == b.atom && rootDistance == b.rootDistance) + return TIED; + // Do a duplicate check -- if that is not a TIE we do not have to go any further. - - int score = checkDuplicate(b); + // NOTE THAT THIS IS NOT EXPLICIT IN THE RULES + // BECAUSE DUPLICATES LOSE IN THE NEXT SPHERE NOT THIS + // THE NEED FOR THIS TEST SHOWS THAT + // SUBRULE 1a MUST BE COMPLETED EXHAUSTIVELY PRIOR TO SUBRULE 1b. + int score = checkNoSubs(b); if (score != TIED) - return score*sphere; + return score*(sphere+1); // TESTING sphere instead of sphere+1 here // return NO_CHIRALITY/TIED if: // a) both are null // b) one or the other can't be set (because it has too many connections) // c) or one or the other is terminal (has no substituents) - // d) or they are the same atom - if ((atom == null) != (b.atom == null)) - return (atom == null ? B_WINS : A_WINS)*(sphere + 1); +// if ((atom == null) != (b.atom == null)) +// return (atom == null ? B_WINS : A_WINS)*(sphere); + if (!set() || !b.set() || isTerminal && b.isTerminal || isDuplicate && b.isDuplicate) return TIED; @@ -1406,9 +1513,9 @@ if (isTerminal != b.isTerminal) return (isTerminal ? B_WINS : A_WINS)*(sphere + 1); - if (Logger.debugging) - Logger.info("tie for " + this + " and " + b); + + // Phase I -- shallow check only // // Check to see if any of the three connections to a and b are different. @@ -1433,6 +1540,10 @@ return compareDeep(b); } + private boolean isHeavy() { + return massNo > 1; + } + /** * The first part of breaking a tie at the current level. Compare only in the current substitutent sphere; a preliminary check * using the current rule. @@ -1444,16 +1555,14 @@ for (int i = 0; i < nAtoms; i++) { CIPAtom ai = atoms[i]; CIPAtom bi = b.atoms[i]; - if (ai == null || bi == null) - return (ai == null ? B_WINS : bi == null ? A_WINS : TIED); int score = ai.checkCurrentRule(bi); // checkCurrentRule can return IGNORE, but we ignore that here. if (score == IGNORE) score = TIED; if (score != TIED) { - if (Logger.debugging) - Logger.info("compareShallow " + ai + " " + bi + ": " + score*sphere); - return score*sphere; + if (Logger.debugging && ai.isHeavy() && bi.isHeavy()) + Logger.info(ai.dots() + "compareShallow " + ai + " " + bi + ": " + score*ai.sphere); + return score*ai.sphere; } } return TIED; @@ -1473,10 +1582,14 @@ for (int i = 0; i < nAtoms; i++) { CIPAtom ai = atoms[i]; CIPAtom bi = b.atoms[i]; + if (Logger.debugging && ai.isHeavy() && bi.isHeavy()) + Logger.info(ai.dots() + "compareDeep sub " + ai + " " + bi); int score = ai.breakTie(bi); if (score == TIED) continue; int abs = Math.abs(score); + if (Logger.debugging && ai.isHeavy() && bi.isHeavy()) + Logger.info(ai.dots() + "compareDeep sub " + ai + " " + bi + ": " + score); if (abs < absScore) { absScore = abs; finalScore = score; @@ -1483,7 +1596,7 @@ } } if (Logger.debugging) - Logger.info("compareDeep " + this + " " + b + ": " + finalScore); + Logger.info(dots() + "compareDeep " + this + " " + b + ": " + finalScore); return finalScore; } /** @@ -1499,7 +1612,7 @@ return (b == null ? A_WINS : (atom == null) != (b.atom == null) ? (atom == null ? B_WINS : A_WINS) : (score = checkCurrentRule(b)) == IGNORE ? TIED - : score != TIED ? score : checkDuplicate(b)); + : score != TIED ? score : checkNoSubs(b)); } /** @@ -1511,7 +1624,7 @@ * @param b * @return 0 (TIED), -1 (A_WINS), or 1 (B_WINS) */ - private int checkDuplicate(CIPAtom b) { + private int checkNoSubs(CIPAtom b) { return b.isDuplicate == isDuplicate ? TIED : b.isDuplicate ? A_WINS : B_WINS; } @@ -1519,6 +1632,9 @@ /** * Check this atom "A" vs a challenger "B" against the current rule. * + * Note that example BB 2013 page 1258, P93.5.2.3 requires that RULE_1b be applied + * only after RULE_1a has been applied exhaustively for a sphere; otherwise C1 is R, not S. + * * @param b * @return 0 (TIED), -1 (A_WINS), or 1 (B_WINS), or Intege.MIN_VALUE (IGNORE) */ @@ -1525,19 +1641,21 @@ public int checkCurrentRule(CIPAtom b) { switch (currentRule) { default: - case RULE_1: - int score = checkRule1a(b); - return (score == TIED ? checkRule1b(b) : score); + case RULE_1a: + return checkRule1a(b); + case RULE_1b: + return TIED;//checkRule1b(b); case RULE_2: return checkRule2(b); case RULE_3: return checkRule3(b); // can be IGNORE - case RULE_4: - return TIED; // not carried out here because it needs access + case RULE_4: // Q: Must we also separate out 4a,b,c? + return TIED; // not carried out here because it needs access to full list of ligands case RULE_5: - return checkRule5(b);// handled at the end of Rule 4 checkRule5(b); + return checkRule5(b);// only used when Rule 4 is not involved, since in that case it is more complicated } } + /** * Looking for same atom or ghost atom or element number @@ -1555,13 +1673,18 @@ * Looking for root distance -- duplicate atoms only. * * @param b + * + * * @return 0 (TIED), -1 (A_WINS), or 1 (B_WINS) */ private int checkRule1b(CIPAtom b) { return !b.isDuplicate || !isDuplicate ? TIED - : b.rootDistance < rootDistance ? B_WINS - : b.rootDistance > rootDistance ? A_WINS : TIED; + : b.rootDistance < rootDistance ? + B_WINS + : b.rootDistance > rootDistance ? + A_WINS + : TIED; } /** @@ -1707,8 +1830,6 @@ * @return 0 (TIED), -1 (A_WINS), 1 (B_WINS), Integer.MIN_VALUE (IGNORE) */ private int checkRule4And5(int i, int j) { - // rule4List[i] = ?1[RR;;SR;;SR;;] ==> atoms[i].rule4List = [RR; SR; SR;] - // rule4List[j] = ?1[SR;;RS;;RS;;] ==> atoms[j].rule4List = [SR; RS; RS;] if (rule4List[i] == null && rule4List[j] == null) return TIED; if (rule4List[i] == null || rule4List[j] == null) @@ -1737,126 +1858,94 @@ // note that this analysis cannot be done ahead of time String aStr = rule4List[ia]; String bStr = rule4List[ib]; - - if (aStr != null && aStr.indexOf("?") == 1) { - aStr = getMataList(ia, isRule5); - bStr = getMataList(ib, isRule5); - if (aStr.length() != bStr.length()) { - // bStr must have R and S options, but aStr does not - return (aStr.length() < bStr.length() ? A_WINS : B_WINS); - } - if (isRule5) - return aStr.compareTo(bStr); - if (aStr.indexOf("|") >= 0 || bStr.indexOf("|") >= 0) { - // Mata(2005) Figure 9 - // We are trying to ascertain that - // R lull R luuu - // S luuu is the same as S lull - // - // Solution is to SUM all winners. If that is 0, then they are the same - String[] aList = PT.split(aStr, "|"); - String[] bList = PT.split(bStr, "|"); - int minScore = Integer.MAX_VALUE; - int sumScore = 0; - aStr = aList[0]; - bStr = bList[0]; - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - int score = compareRule4PairStr(aList[i], bList[j], true); - sumScore += score; - if (score != TIED && Math.abs(score) <= minScore) { - minScore = Math.abs(score); - aStr = aList[i]; - bStr = bList[j]; - } + if (atoms[ia].nextChiralBranch != null) + aStr += atoms[ia].getMataList(getFirstRef(aStr), isRule5); + if (atoms[ib].nextChiralBranch != null) + bStr += atoms[ib].getMataList(getFirstRef(bStr), isRule5); + aStr = aStr.substring(1); + bStr = bStr.substring(1); + if (Logger.debugging) + Logger.info(this + " comparing " + atoms[ia] + " " + aStr + " to " + atoms[ib] + " " + bStr); + if (aStr.length() != bStr.length()) + return TIED; + if (isRule5) + return aStr.compareTo(bStr); + if (aStr.indexOf("|") >= 0 || bStr.indexOf("|") >= 0) { + // Mata(2005) Figure 9 + // We are trying to ascertain that + // R lull R luuu + // S luuu is the same as S lull + // + // Solution is to SUM all winners. If that is 0, then they are the same + String[] aList = PT.split(aStr, "|"); + String[] bList = PT.split(bStr, "|"); + int minScore = Integer.MAX_VALUE; + int sumScore = 0; + aStr = aList[0]; + bStr = bList[0]; + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + int score = compareRule4PairStr(aList[i], bList[j], true); + sumScore += score; + if (score != TIED && Math.abs(score) <= minScore) { + minScore = Math.abs(score); + aStr = aList[i]; + bStr = bList[j]; } } - if (sumScore == TIED) - return TIED; } - } else { - // trim off priority numbers - aStr = PT.rep(aStr.substring(1), "~", ""); - bStr = PT.rep(bStr.substring(1), "~", ""); + if (sumScore == TIED) + return TIED; } + aStr = PT.rep(aStr, "~", ""); + bStr = PT.rep(bStr, "~", ""); return compareRule4PairStr(aStr, bStr, false); } /** - * Comparison of two strings such as RSSR and SRSS for Rule 4b. - * + * Just get the first R- or S-equivalent in "~~~~xxxxx" * @param aStr - * @param bStr - * @param isRSTest - * return a score qualified by measure of how far into the comparison - * when we find a like/unlike distance - * - * @return 0 (TIED), -1 (A_WINS), 1 (B_WINS), Integer.MIN_VALUE (IGNORE) + * @return "R", "S", or null */ - private int compareRule4PairStr(String aStr, String bStr, boolean isRSTest) { - System.out.println(this + " Rule 4b comparing " + aStr + " " + bStr); - doCheckPseudo = false; - int n = aStr.length(); - if (n == 0 || n != bStr.length()) - return TIED; - char aref = fixMataRef(aStr.charAt(0)); - char bref = fixMataRef(bStr.charAt(0)); - for (int c = 1; c < n; c++) { - boolean alike = (aref == fixMataRef(aStr.charAt(c))); - boolean blike = (bref == fixMataRef(bStr.charAt(c))); - if (alike != blike) - return (isRSTest ? c : 1) * (alike ? A_WINS : B_WINS); + private String getFirstRef(String aStr) { + for (int i = 0, n = aStr.length(); i < n; i++) { + char c = fixMataRef(aStr.charAt(i)); + switch (c) { + case 'R': + case 'S': + return "" + c; + } } - if (isRSTest) - return TIED; - if (aref == bref) - return IGNORE; - // are opposites - if (!canBePseudo) - root.canBePseudo = false; - doCheckPseudo = canBePseudo && (aref == 'R' || aref == 'S'); - return aref < bref ? A_WINS : B_WINS; + return null; } - - private char fixMataRef(char c) { - switch (c) { - case 'R': - case 'M': - case 'Z': - return 'R'; - case 'S': - case 'P': - case 'E': - return 'S'; - default: - return c; - } - } /** * Retrieve the Mata Rule 4b list for a given atom. * - * @param ia + * @param aref * @param isRule5 * @return a String representation of the path through the atoms * */ - private String getMataList(int ia, boolean isRule5) { - String[] rule4List = atoms[ia].rule4List; + private String getMataList(String aref, boolean isRule5) { int n = 0; for (int i = rule4List.length; --i >= 0;) if (rule4List[i] != null) n++; String[] listA = new String[n]; - for (int i = rule4List.length; --i >= 0;) - if (rule4List[i] != null) - listA[--n] = rule4List[i]; - Arrays.sort(listA); - String aref = (isRule5 ? "R" : getMataRef(listA)); + for (int j = n, i = rule4List.length; --i >= 0;) + if (rule4List[i] != null) { + listA[--j] = rule4List[i]; + } +// Arrays.sort(listA); + if (aref == null) { + aref = getMataRef(isRule5); + } else { + // we need to add the priority business only if this is the first case + for (int i = 0; i < n; i++) + listA[i] = "." + listA[i].substring(1); + } switch (aref.length()) { default: - case 0: - System.out.println("???"); - return "???"; case 1: return getMataSequence(listA, aref, isRule5); case 2: @@ -1865,6 +1954,20 @@ } /** + * The reference designation is the most popular of R and S of the highest- + * priority node, or both if there are the same number at highest-priority node level + * @param isRule5 + * @return "R", "S", or "RS" + */ + private String getMataRef(boolean isRule5) { + String rs = isRule5 ? "R" + : rule4Count[STEREO_R] > rule4Count[STEREO_S] ? "R" + : rule4Count[STEREO_R] < rule4Count[STEREO_S] ? "S" : "RS"; + if (Logger.debugging) + Logger.info(this + "mata ref: " + rs + " Rule5?" + isRule5 + " " + PT.toJSON("rule4Count", rule4Count)); + return rs; + } + /** * This is the key Mata method -- getting the correct sequence of R and S * from a set of diasteromorphic paths. Given a specific reference * designation, the task is to sort the paths based on priority (we can't @@ -1876,14 +1979,23 @@ * (65). * * @param lst - * @param aref + * @param chRef * @param isRule5 * @return one string, possibly separated by | indicating that the result * has both an R and S side to it */ - private String getMataSequence(String[] lst, String aref, boolean isRule5) { - String[] sorted = (isRule5 ? lst : getMataSortedList(lst, aref)); - int n = sorted.length; + private String getMataSequence(String[] lst, String chRef, boolean isRule5) { + int n = lst.length; + String[] lst1 = new String[n]; + for (int j = n, i = rule4List.length; --i >= 0;) { + if (rule4List[i] != null) { + --j; + lst1[j] = lst[j]; + if (atoms[i].nextChiralBranch != null) + lst1[j] += atoms[i].nextChiralBranch.getMataList(chRef, isRule5); + } + } + String[] sorted = (isRule5 ? lst1 : getMataSortedList(lst1, chRef)); int len = 0; for (int i = 0; i < n; i++) { String rs = sorted[i]; @@ -1914,6 +2026,58 @@ } /** + * Comparison of two strings such as RSSR and SRSS for Rule 4b. + * + * @param aStr + * @param bStr + * @param isRSTest + * return a score qualified by measure of how far into the comparison + * when we find a like/unlike distance + * + * @return 0 (TIED), -1 (A_WINS), 1 (B_WINS), Integer.MIN_VALUE (IGNORE) + */ + private int compareRule4PairStr(String aStr, String bStr, boolean isRSTest) { + if (Logger.debugging) + Logger.info(this + " Rule 4b comparing " + aStr + " " + bStr); + doCheckPseudo = false; + int n = aStr.length(); + if (n == 0 || n != bStr.length()) + return TIED; + char aref = fixMataRef(aStr.charAt(0)); + char bref = fixMataRef(bStr.charAt(0)); + for (int c = 1; c < n; c++) { + boolean alike = (aref == fixMataRef(aStr.charAt(c))); + boolean blike = (bref == fixMataRef(bStr.charAt(c))); + if (alike != blike) + return (isRSTest ? c : 1) * (alike ? A_WINS : B_WINS); + } + if (isRSTest) + return TIED; + if (aref == bref) + return IGNORE; + // are opposites + if (!canBePseudo) + root.canBePseudo = false; + doCheckPseudo = canBePseudo && (aref == 'R' || aref == 'S'); + return aref < bref ? A_WINS : B_WINS; + } + + private char fixMataRef(char c) { + switch (c) { + case 'R': + case 'M': + case 'Z': + return 'R'; + case 'S': + case 'P': + case 'E': + return 'S'; + default: + return c; + } + } + + /** * Sort Mata list of ["RS...", "SR..."] by temporarily assigning the reference atom * chirality the letter "A" and then sorting lexicographically. * @@ -1931,112 +2095,64 @@ sorted[i] = PT.rep(sorted[i], "A", aref); if (Logger.debuggingHigh) for (int i = 0; i < n; i++) - Logger.info("Sorted Mata list " + i + " " + sorted[i]); + Logger.info("Sorted Mata list " + i + " " + aref + ": " + sorted[i]); return sorted; } /** - * Determine the reference designation. + * This method creates a list of downstream (higher-sphere) auxiliary + * chirality designators R, S, r, s, u, M, P, m, p, C, T, c, t (seqCis, seqTrans) that are passed upstream ultimately + * to the Sphere-1 root substituent. * - * 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 R, S, or RS - */ - private String getMataRef(String[] lst) { - // get highest-ranking chiral unit - int pt = Integer.MAX_VALUE; - for (int i = 0; i < lst.length; i++) { - String s = lst[i]; - int j = 1; - for (int n = s.length(); j < n; j++) - if (s.charAt(j) != '~') - break; - if (j < pt) - pt = j; - } - switch (lst.length) { - case 1: - // R or S - return lst[0].substring(pt, pt + 1); - case 2: - // 1R2R 1R2S 1S2R 1S2S - // 1R1R 1R1S 1S1R 1S1S - // 1R1~ 1S1~ 1R2~ 1S2~ - // 1~2R 1~2S - char pa = lst[0].charAt(0); - char pb = lst[1].charAt(0); - char ca = lst[0].charAt(pt); - char cb = lst[1].charAt(pt); - return (ca == cb || cb == '~' || pa < pb && ca != '~' ? "" + ca - : pa == pb ? "RS" : "" + cb); - case 3: - char p1 = lst[0].charAt(0); - char p2 = lst[1].charAt(0); - char p3 = lst[2].charAt(0); - char c1 = lst[0].charAt(pt); - char c2 = lst[1].charAt(pt); - char c3 = lst[2].charAt(pt); - // 1 1 1 - if (p1 == p2 && p2 == p3) - return (c1 == c2 || c2 == '~' ? "" + c1 // RRR RRS SSS RR~ SS~ R~~ S~~ - : c2 == c3 ? "" + c3 // RSS SSS - : "RS" // RS~ - ); - // 1 1 2 - if (p1 == p2) - return (p1 == '~' ? "" + c3 : c1 == c2 || c2 == '~' ? "" + c1 : "RS"); - // 1 2 2 - if (p2 == p3) - return (p1 != '~' ? "" + c1 : c2 == c3 || c3 == '~' ? "" + c2 : "RS"); - // 1 2 3 - return "" + (c1 != '~' ? c1 : c2 != '~' ? c2 : c3); - } - return ""; - } - - /** - * This method creates a list of downstream (higher-sphere) auxiliary chirality designators - * R, S, r, and s that are passed upstream ultimately to the Sphere-1 root substituent. - * * work in progress * - * @param isRoot + * @param node1 + * first node; sphere 1 + * @param ret + * CIPAtom of next stereochemical branching point * * @return collective string, with setting of rule4List */ - String createAuxiliaryRSCenters(boolean isRoot) { + String createAuxiliaryRSCenters(CIPAtom node1, CIPAtom[] ret) { + //System.out.println("create " + node1 + myPath); + + // still deciding when/if this is necessary. Only for root? + if (auxParentReversed != null) - auxParentReversed.createAuxiliaryRSCenters(true); + auxParentReversed.createAuxiliaryRSCenters(null, null); if (auxPseudo != null) - auxPseudo.createAuxiliaryRSCenters(true); + auxPseudo.createAuxiliaryRSCenters(null, null); + int rs = -1; String subRS = ""; - String s = (isRoot ? "" : "~"); - boolean done = true; + String s = (node1 == null ? "" : "~"); + boolean isBranch = false; if (atom != null) { rule4List = new String[4]; // full list based on atoms[] int[] mataList = new int[4]; //sequential pointers into rule4List int nRS = 0; + CIPAtom[] ret1 = new CIPAtom[1]; for (int i = 0; i < 4; i++) { CIPAtom a = atoms[i]; + if (a != null) + a.set(); if (a != null && !a.isDuplicate && !a.isTerminal) { - String ssub = a.createAuxiliaryRSCenters(false); - rule4List[i] = priorities[i] + ssub; - if ("sr".indexOf(ssub) >= 0 || ssub.indexOf("R") >= 0 || ssub.indexOf("S") >= 0) { + a.priority = priorities[i]; + ret1[0] = null; + String ssub = a.createAuxiliaryRSCenters(node1 == null ? a : node1, + ret1); + if (ret1[0] != null) { + a.nextChiralBranch = ret1[0]; + if (ret != null) + ret[0] = ret1[0]; + } + rule4List[i] = a.priority + ssub; + if (a.nextChiralBranch != null || isChiralSequence(ssub)) { mataList[nRS] = i; nRS++; - subRS += ssub + ";"; + subRS += ssub; } else { rule4List[i] = null; -// subRS += "~"; } } } @@ -2048,24 +2164,34 @@ case 1: break; case 2: - if (!isRoot) { + if (node1 != null) { // we want to now if these two are enantiomorphic, identical, or diastereomorphic. - switch (adj = (compareRule4aEnantiomers(rule4List[mataList[0]], rule4List[mataList[1]]))) { + switch (adj = (compareRule4aEnantiomers(rule4List[mataList[0]], + rule4List[mataList[1]]))) { case DIASTEREOMERIC: - done = false; + isBranch = true; + s = ""; + // create a ?<sphere>[....] object ? break; case NOT_RELEVANT: // create a ?<sphere>[....] object ? - done = false; + s = ""; + isBranch = true; adj = TIED; break; case TIED: - // identical -- nothing we can do about this -- two identical ligands + // identical + isBranch = true; + s = "u"; subRS = ""; + if (ret != null) + ret[0] = null; break; case A_WINS: case B_WINS: + isBranch = true; // enantiomers -- we have an r/s situation + // process to determine chirality, but then set ret[0] to be null subRS = ""; break; } @@ -2073,44 +2199,79 @@ break; case 3: case 4: - done = false; + s = ""; + isBranch = true; } - if (!done) { - s = "?" + sphere; - subRS = "[" + subRS + "]"; - } else if (isAlkene && alkeneChild != null) { - // we must check for seqCis or M/P - // this does not work. Is "E" a "chiral unit"? -// int ez = alkeneChild.getEZaux(); -// s = (ez == STEREO_Z ? "Z" : ez == STEREO_E ? "E" : "~"); -// System.out.println(myPath + s); -// System.out.println("?"); - } else if (!isRoot && ( - bondCount == 4 && nPriorities >= 3 - Math.abs(adj) - || bondCount == 3 && elemNo > 10 && nPriorities >= 2 - Math.abs(adj)) - ) { + if (isBranch) { + subRS = ""; + if (ret != null && s != "u") + ret[0] = this; + } + + if (!isBranch || adj == A_WINS || adj == B_WINS) { + if (isAlkene && alkeneChild != null) { + // we must check for seqCis or M/P + } else if (node1 != null + && (bondCount == 4 && nPriorities >= 3 - Math.abs(adj) || bondCount == 3 + && elemNo > 10 && nPriorities >= 2 - Math.abs(adj))) { // if here, adj is TIED (0), A_WINS (-1), or B_WINS (1) CIPAtom atom1 = (CIPAtom) clone(); if (atom1.set()) { atom1.addReturnPath(null, this); int thisRule = currentRule; - currentRule = RULE_1; + currentRule = RULE_1a; atom1.sortSubstituents(); currentRule = thisRule; rs = atom1.checkHandedness(); s = (rs == STEREO_R ? "R" : rs == STEREO_S ? "S" : "~"); - if (adj != TIED) + if (adj == TIED) { + node1.addMataRef(sphere, priority, rs); + } else { s = s.toLowerCase(); + subRS = ""; + if (ret != null) + ret[0] = null; + } } + } } } s += subRS; -// System.out.println(this + " creating aux " + s); + if (Logger.debugging && !s.equals("~")) + Logger.info("creating aux " + myPath + s); return s; } + private boolean isChiralSequence(String ssub) { + return ssub.indexOf("R") >= 0 || ssub.indexOf("S") >= 0 + || ssub.indexOf("r") >= 0 || ssub.indexOf("s") >= 0 + || ssub.indexOf("u") >= 0; + } /** + * Accumlate the number of R and S centers at a given sphere+priority level + * + * @param sphere 1,2,3... + * @param priority 1-4 + * @paramPriority + * @param rs + */ + private void addMataRef(int sphere, int priority, int rs) { + if (rule4Count == null) { + rule4Count = new int[] {Integer.MAX_VALUE, 0, 0}; + } + int n = sphere * 10 + priority; + if (n <= rule4Count[0]) { + if (n < rule4Count[0]) { + rule4Count[0] = n; + rule4Count[STEREO_R] = rule4Count[STEREO_S] = 0; + } + rule4Count[rs]++; + } +// System.out.println(this + " " + sphere + " " + priority + " " + rs + " " +// + PT.toJSON("rule4Count", rule4Count)); + } + /** * Check for enantiomeric strings such as S;R; or SR * * @param rs1 @@ -2163,7 +2324,7 @@ atom1 = auxPseudo; } int thisRule = currentRule; - currentRule = RULE_1; + currentRule = RULE_1a; atom1.sortSubstituents(); currentRule = thisRule; // Now add the tied branches at the end; it doesn't matter where they @@ -2181,42 +2342,21 @@ } - /** - * - * Create a priority key that matches elemNo and massNo. - * - * We can skip the duplicate flag, because all these have substituents. - * - * @param a - * @return a shifted key based on elemNo and massNo - */ - private int getBasePriority(CIPAtom a) { - int code = 0; - if (a.atom == null) { - code = 0x7FFFF << PRIORITY_SHIFT[2]; - } else { - code = ((127 - a.elemNo) << PRIORITY_SHIFT[0]) - | ((255 - a.massNo) << PRIORITY_SHIFT[2]); - } - return code; - } - +// /** +// * reset auxEZ chirality to "undetermined" +// */ +// void resetAuxiliaryChirality() { +// auxEZ = STEREO_UNDETERMINED; +// for (int i = 0; i < 4; i++) +// if (atoms[i] != null && atoms[i].atom != null) +// atoms[i].resetAuxiliaryChirality(); +// if (auxParentReversed != null) +// auxParentReversed.resetAuxiliaryChirality(); +// if (auxPseudo != null) +// auxPseudo.resetAuxiliaryChirality(); +// } /** - * reset auxEZ chirality to "undetermined" - */ - void resetAuxiliaryChirality() { - auxEZ = STEREO_UNDETERMINED; - for (int i = 0; i < 4; i++) - if (atoms[i] != null && atoms[i].atom != null) - atoms[i].resetAuxiliaryChirality(); - if (auxParentReversed != null) - auxParentReversed.resetAuxiliaryChirality(); - if (auxPseudo != null) - auxPseudo.resetAuxiliaryChirality(); - } - - /** * Swap a substituent and the parent in preparation for reverse traversal of * this path * @@ -2246,15 +2386,6 @@ : priorities[i] < priorities[i + 1] ? atoms[i] : atoms[i + 1]; } - /** - * Get the appropriate chirality for this atom if it has been determined. - * - * @return known chirality unless it is r or s, as those are being created dyamically - */ - private String getWorkingChirality() { - return ("rs".indexOf(knownAtomChirality) >= 0 ? "~" : knownAtomChirality); - } - // // private CIPAtom getCommonAncestor(CIPAtom b) { // CIPAtom a = this; @@ -2263,7 +2394,8 @@ // } /** - * Chapter 9 Rule 5. Implemented only for a single R/S. + * Chapter 9 Rule 5. "T" and "C" are seqTrans and seqCis, as determined while + * applying Rule 4. * * For 3D models is it not necessary to consider "unspecified" chirality, as * that is only a 2D concept. @@ -2274,8 +2406,8 @@ private int checkRule5(CIPAtom b) { if (isTerminal || isDuplicate) return TIED; - int isRa = ";srSR;".indexOf(getWorkingChirality()); - int isRb = ";srSR;".indexOf(b.getWorkingChirality()); + int isRa = ";SRPMTC;".indexOf(knownAtomChirality); + int isRb = ";SRPMTC;".indexOf(b.knownAtomChirality); return (isRa == isRb ? TIED : isRa > isRb ? A_WINS : B_WINS); } @@ -2304,7 +2436,8 @@ a.id = ptID++; a.atoms = new CIPAtom[4]; a.priorities = new int[4]; - a.prevPriorities = new int[4]; + a.prevPriorities = new int[] {-1, -1, -1, -1}; + a.htPathPoints = htPathPoints; for (int i = 0; i < 4; i++) { a.priorities[i] = priorities[i]; if (atoms[i] != null) { @@ -2321,7 +2454,7 @@ @Override public String toString() { - return (atom == null ? "<null>" : "[" + currentRule + "." + sphere + "." + atom.getAtomName() + (isDuplicate ? "*" : "") + "]" + return (atom == null ? "<null>" : "[" + currentRule + "." + sphere + "," + rootDistance + "." + id + "." + atom.getAtomName() + (isDuplicate ? "*" : "") + "]" //"[" + sphere + "." + id + " " // + atom.toString() + (isDuplicate ? "*" : "") + knownAtomChirality + auxAtomChirality // + "]" 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