1 package uk.ac.cam.ch.wwmm.opsin; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.Collection; 6 import java.util.Collections; 7 import java.util.Comparator; 8 import java.util.HashMap; 9 import java.util.HashSet; 10 import java.util.List; 11 import java.util.Map; 12 import java.util.Set; 13 import java.util.Map.Entry; 14 15 /** 16 * Identifies stereocentres and determines the CIP order of connected atoms 17 * @author dl387 18 * 19 */ 20 class StereoAnalyser { 21 /** The atoms/bonds upon which this StereoAnalyser is operating */ 22 private final Collection<Atom> atoms; 23 private final Collection<Bond> bonds; 24 25 /** Maps each atom to its currently assigned colour. Eventually all atoms in non identical environments will have different colours. Higher is higher priority*/ 26 private final Map<Atom, Integer> mappingToColour; 27 28 /** Maps each atom to an array of the colours of its neighbours*/ 29 private final Map<Atom, int[]> atomNeighbourColours; 30 31 private final AtomNeighbouringColoursComparator atomNeighbouringColoursComparator = new AtomNeighbouringColoursComparator(); 32 private static final AtomicNumberThenAtomicMassComparator atomicNumberThenAtomicMassComparator = new AtomicNumberThenAtomicMassComparator(); 33 34 /** 35 * Holds information about a tetrahedral stereocentre 36 * @author dl387 37 * 38 */ 39 class StereoCentre{ 40 private final Atom stereoAtom; 41 private final boolean trueStereoCentre; 42 43 /** 44 * Creates a stereocentre object from a tetrahedral stereocentre atom 45 * @param stereoAtom 46 */ StereoCentre(Atom stereoAtom, Boolean isTrueStereoCentre)47 StereoCentre(Atom stereoAtom, Boolean isTrueStereoCentre) { 48 this.stereoAtom = stereoAtom; 49 this.trueStereoCentre = isTrueStereoCentre; 50 } 51 getStereoAtom()52 Atom getStereoAtom() { 53 return stereoAtom; 54 } 55 56 /** 57 * Does this atom have 4 constitutionally different groups (or 3 and a lone pair) 58 * or is it only a stereo centre due to the presence of other centres in the molecule 59 * @return 60 */ isTrueStereoCentre()61 boolean isTrueStereoCentre() { 62 return trueStereoCentre; 63 } 64 getCipOrderedAtoms()65 List<Atom> getCipOrderedAtoms() throws CipOrderingException { 66 List<Atom> cipOrderedAtoms = new CipSequenceRules(stereoAtom).getNeighbouringAtomsInCipOrder(); 67 if (cipOrderedAtoms.size()==3){//lone pair is the 4th. This is represented by the atom itself and is always the lowest priority 68 cipOrderedAtoms.add(0, stereoAtom); 69 } 70 return cipOrderedAtoms; 71 } 72 } 73 74 /*** 75 * Holds information about a double bond that can possess E/Z stereochemistry 76 * @author dl387 77 * 78 */ 79 class StereoBond{ 80 private final Bond bond; StereoBond(Bond bond)81 StereoBond(Bond bond) { 82 this.bond = bond; 83 } getBond()84 Bond getBond() { 85 return bond; 86 } 87 88 /** 89 * Returns the following atoms: 90 * Highest CIP atom on one side 91 * atom in bond 92 * other atom in bond 93 * Highest CIP atom on other side 94 * @return 95 * @throws CipOrderingException 96 */ getOrderedStereoAtoms()97 List<Atom> getOrderedStereoAtoms() throws CipOrderingException { 98 Atom a1 = bond.getFromAtom(); 99 Atom a2 = bond.getToAtom(); 100 List<Atom> cipOrdered1 = new CipSequenceRules(a1).getNeighbouringAtomsInCipOrderIgnoringGivenNeighbour(a2); 101 List<Atom> cipOrdered2 = new CipSequenceRules(a2).getNeighbouringAtomsInCipOrderIgnoringGivenNeighbour(a1); 102 List<Atom> stereoAtoms = new ArrayList<Atom>(); 103 stereoAtoms.add(cipOrdered1.get(cipOrdered1.size()-1));//highest CIP adjacent to a1 104 stereoAtoms.add(a1); 105 stereoAtoms.add(a2); 106 stereoAtoms.add(cipOrdered2.get(cipOrdered2.size()-1));//highest CIP adjacent to a2 107 return stereoAtoms; 108 } 109 } 110 111 /** 112 * Sorts atoms by their atomic number, low to high 113 * In the case of a tie sorts by atomic mass 114 * @author dl387 115 * 116 */ 117 private static class AtomicNumberThenAtomicMassComparator implements Comparator<Atom> { compare(Atom a, Atom b)118 public int compare(Atom a, Atom b){ 119 return compareAtomicNumberThenAtomicMass(a, b); 120 } 121 } 122 compareAtomicNumberThenAtomicMass(Atom a, Atom b)123 private static int compareAtomicNumberThenAtomicMass(Atom a, Atom b){ 124 int atomicNumber1 = a.getElement().ATOMIC_NUM; 125 int atomicNumber2 = b.getElement().ATOMIC_NUM; 126 if (atomicNumber1 > atomicNumber2){ 127 return 1; 128 } 129 else if (atomicNumber1 < atomicNumber2){ 130 return -1; 131 } 132 Integer atomicMass1 = a.getIsotope(); 133 Integer atomicMass2 = b.getIsotope(); 134 if (atomicMass1 != null && atomicMass2 == null){ 135 return 1; 136 } 137 else if (atomicMass1 == null && atomicMass2 != null){ 138 return -1; 139 } 140 else if (atomicMass1 != null && atomicMass2 != null){ 141 if (atomicMass1 > atomicMass2){ 142 return 1; 143 } 144 else if (atomicMass1 < atomicMass2){ 145 return -1; 146 } 147 } 148 return 0; 149 } 150 151 /** 152 * Sorts based on the list of colours for neighbouring atoms 153 * e.g. [1,2] > [1,1] [1,1,3] > [2,2,2] [1,1,3] > [3] 154 * @author dl387 155 * 156 */ 157 private class AtomNeighbouringColoursComparator implements Comparator<Atom> { compare(Atom a, Atom b)158 public int compare(Atom a, Atom b){ 159 int[] colours1 = atomNeighbourColours.get(a); 160 int[] colours2 = atomNeighbourColours.get(b); 161 162 int colours1Size = colours1.length; 163 int colours2Size = colours2.length; 164 165 int maxCommonColourSize = Math.min(colours1Size, colours2Size); 166 for (int i = 1; i <= maxCommonColourSize; i++) { 167 int difference = colours1[colours1Size - i] - colours2[colours2Size - i]; 168 if (difference > 0){ 169 return 1; 170 } 171 if (difference < 0){ 172 return -1; 173 } 174 } 175 int differenceInSize = colours1Size - colours2Size; 176 if (differenceInSize > 0){ 177 return 1; 178 } 179 if (differenceInSize < 0){ 180 return -1; 181 } 182 return 0; 183 } 184 } 185 186 /** 187 * Employs a derivative of the InChI algorithm to label which atoms are equivalent. 188 * These labels can then be used by the findStereo(Atoms/Bonds) functions to find features that 189 * can possess stereoChemistry 190 * @param molecule 191 */ StereoAnalyser(Fragment molecule)192 StereoAnalyser(Fragment molecule) { 193 this (molecule.getAtomList(), molecule.getBondSet()); 194 } 195 196 /** 197 * Employs a derivative of the InChI algorithm to label which atoms are equivalent. 198 * These labels can then be used by the findStereo(Atoms/Bonds) functions to find features that 199 * can possess stereoChemistry 200 * NOTE: All bonds of every atom must be in the set of bonds, no atom may have a bond to an atom not in the list 201 * @param atoms 202 * @param bonds 203 */ StereoAnalyser(Collection<Atom> atoms, Collection<Bond> bonds)204 StereoAnalyser(Collection<Atom> atoms, Collection<Bond> bonds) { 205 this.atoms = atoms; 206 this.bonds = bonds; 207 List<Atom> ghostAtoms = addGhostAtoms(); 208 List<Atom> atomsToSort = new ArrayList<Atom>(atoms); 209 atomsToSort.addAll(ghostAtoms); 210 mappingToColour = new HashMap<Atom, Integer>(atomsToSort.size()); 211 atomNeighbourColours = new HashMap<Atom, int[]>(atomsToSort.size()); 212 Collections.sort(atomsToSort, atomicNumberThenAtomicMassComparator); 213 List<List<Atom>> groupsByColour = populateColoursByAtomicNumberAndMass(atomsToSort); 214 boolean changeFound = true; 215 while(changeFound){ 216 for (List<Atom> groupWithAColour : groupsByColour) { 217 for (Atom atom : groupWithAColour) { 218 int[] neighbourColours = findColourOfNeighbours(atom); 219 atomNeighbourColours.put(atom, neighbourColours); 220 } 221 } 222 List<List<Atom>> updatedGroupsByColour = new ArrayList<List<Atom>>(); 223 changeFound = populateColoursAndReportIfColoursWereChanged(groupsByColour, updatedGroupsByColour); 224 groupsByColour = updatedGroupsByColour; 225 } 226 removeGhostAtoms(ghostAtoms); 227 } 228 229 /** 230 * Adds "ghost" atoms in the same way as the CIP rules for handling double bonds 231 * e.g. C=C --> C(G)=C(G) where ghost is a carbon with no hydrogen bonded to it 232 * @return The ghost atoms created 233 */ addGhostAtoms()234 private List<Atom> addGhostAtoms() { 235 List<Atom> ghostAtoms = new ArrayList<Atom>(); 236 for (Bond bond : bonds) { 237 int bondOrder = bond.getOrder(); 238 for (int i = bondOrder; i > 1; i--) { 239 Atom fromAtom = bond.getFromAtom(); 240 Atom toAtom = bond.getToAtom(); 241 242 Atom ghost1 = new Atom(fromAtom.getElement()); 243 Bond b1 = new Bond(ghost1, toAtom, 1); 244 toAtom.addBond(b1); 245 ghost1.addBond(b1); 246 ghostAtoms.add(ghost1); 247 248 Atom ghost2 = new Atom(toAtom.getElement()); 249 Bond b2 = new Bond(ghost2, fromAtom, 1); 250 fromAtom.addBond(b2); 251 ghost2.addBond(b2); 252 ghostAtoms.add(ghost2); 253 } 254 } 255 return ghostAtoms; 256 } 257 258 /** 259 * Removes the ghost atoms added by addGhostAtoms 260 * @param ghostAtoms 261 */ removeGhostAtoms(List<Atom> ghostAtoms)262 private void removeGhostAtoms(List<Atom> ghostAtoms) { 263 for (Atom atom : ghostAtoms) { 264 Bond b = atom.getFirstBond(); 265 b.getOtherAtom(atom).removeBond(b); 266 } 267 } 268 269 270 /** 271 * Takes a list of atoms sorted by atomic number/mass 272 * and populates the mappingToColour map 273 * @param atomList 274 * @return 275 */ populateColoursByAtomicNumberAndMass(List<Atom> atomList)276 private List<List<Atom>> populateColoursByAtomicNumberAndMass(List<Atom> atomList) { 277 List<List<Atom>> groupsByColour = new ArrayList<List<Atom>>(); 278 Atom previousAtom = null; 279 List<Atom> atomsOfThisColour = new ArrayList<Atom>(); 280 int atomsSeen = 0; 281 for (Atom atom : atomList) { 282 if (previousAtom != null && compareAtomicNumberThenAtomicMass(previousAtom, atom) != 0){ 283 for (Atom atomOfthisColour : atomsOfThisColour) { 284 mappingToColour.put(atomOfthisColour, atomsSeen); 285 } 286 groupsByColour.add(atomsOfThisColour); 287 atomsOfThisColour = new ArrayList<Atom>(); 288 } 289 previousAtom = atom; 290 atomsOfThisColour.add(atom); 291 atomsSeen++; 292 } 293 if (!atomsOfThisColour.isEmpty()){ 294 for (Atom atomOfThisColour : atomsOfThisColour) { 295 mappingToColour.put(atomOfThisColour, atomsSeen); 296 } 297 groupsByColour.add(atomsOfThisColour); 298 } 299 return groupsByColour; 300 } 301 302 /** 303 * Takes the lists of atoms pre-grouped by colour and sorts each by its neighbours colours 304 * The updatedGroupsByColour is populated with those for which this process caused a change 305 * and populates the mappingToColour map 306 * Returns whether mappingToColour was changed 307 * @param groupsByColour 308 * @param updatedGroupsByColour 309 * @return boolean Whether mappingToColour was changed 310 */ populateColoursAndReportIfColoursWereChanged(List<List<Atom>> groupsByColour, List<List<Atom>> updatedGroupsByColour)311 private boolean populateColoursAndReportIfColoursWereChanged(List<List<Atom>> groupsByColour, List<List<Atom>> updatedGroupsByColour) { 312 boolean changeFound = false; 313 int atomsSeen = 0; 314 for (List<Atom> groupWithAColour : groupsByColour) { 315 Collections.sort(groupWithAColour, atomNeighbouringColoursComparator); 316 Atom previousAtom = null; 317 List<Atom> atomsOfThisColour = new ArrayList<Atom>(); 318 for (Atom atom : groupWithAColour) { 319 if (previousAtom != null && atomNeighbouringColoursComparator.compare(previousAtom, atom) != 0){ 320 for (Atom atomOfThisColour : atomsOfThisColour) { 321 if (!changeFound && atomsSeen != mappingToColour.get(atomOfThisColour)){ 322 changeFound = true; 323 } 324 mappingToColour.put(atomOfThisColour, atomsSeen); 325 } 326 updatedGroupsByColour.add(atomsOfThisColour); 327 atomsOfThisColour = new ArrayList<Atom>(); 328 } 329 previousAtom = atom; 330 atomsOfThisColour.add(atom); 331 atomsSeen++; 332 } 333 if (!atomsOfThisColour.isEmpty()){ 334 for (Atom atomOfThisColour : atomsOfThisColour) { 335 if (!changeFound && atomsSeen != mappingToColour.get(atomOfThisColour)){ 336 changeFound = true; 337 } 338 mappingToColour.put(atomOfThisColour, atomsSeen); 339 } 340 updatedGroupsByColour.add(atomsOfThisColour); 341 } 342 } 343 return changeFound; 344 } 345 346 /** 347 * Produces a sorted (low to high) array of the colour of the atoms surrounding a given atom 348 * @param atom 349 * @return int[] colourOfAdjacentAtoms 350 */ findColourOfNeighbours(Atom atom)351 private int[] findColourOfNeighbours(Atom atom) { 352 List<Bond> bonds = atom.getBonds(); 353 int bondCount = bonds.size(); 354 int[] colourOfAdjacentAtoms = new int[bondCount]; 355 for (int i = 0; i < bondCount; i++) { 356 Bond bond = bonds.get(i); 357 Atom otherAtom = bond.getOtherAtom(atom); 358 colourOfAdjacentAtoms[i] = mappingToColour.get(otherAtom); 359 } 360 Arrays.sort(colourOfAdjacentAtoms);//sort such that this goes from low to high 361 return colourOfAdjacentAtoms; 362 } 363 364 /** 365 * Retrieves a list of any tetrahedral stereoCentres 366 * Internally this is done by checking whether the "colour" of all neighbouring atoms of the tetrahedral atom are different 367 * @return List<StereoCentre> 368 */ findStereoCentres()369 List<StereoCentre> findStereoCentres(){ 370 List<Atom> potentialStereoAtoms = getPotentialStereoCentres(); 371 List<Atom> trueStereoCentres = new ArrayList<Atom>(); 372 for (Atom potentialStereoAtom : potentialStereoAtoms) { 373 if (isTrueStereCentre(potentialStereoAtom)){ 374 trueStereoCentres.add(potentialStereoAtom); 375 } 376 } 377 List<StereoCentre> stereoCentres = new ArrayList<StereoCentre>(); 378 for (Atom trueStereoCentreAtom : trueStereoCentres) { 379 stereoCentres.add(new StereoCentre(trueStereoCentreAtom, true)); 380 } 381 382 potentialStereoAtoms.removeAll(trueStereoCentres); 383 List<Atom> paraStereoCentres = findParaStereoCentres(potentialStereoAtoms, trueStereoCentres); 384 for (Atom paraStereoCentreAtom : paraStereoCentres) { 385 stereoCentres.add(new StereoCentre(paraStereoCentreAtom, false)); 386 } 387 return stereoCentres; 388 } 389 390 /** 391 * Retrieves atoms that pass the isPossiblyStereogenic() criteria 392 * @return 393 */ getPotentialStereoCentres()394 private List<Atom> getPotentialStereoCentres() { 395 List<Atom> potentialStereoAtoms = new ArrayList<Atom>(); 396 for (Atom atom : atoms) { 397 if (isPossiblyStereogenic(atom)){ 398 potentialStereoAtoms.add(atom); 399 } 400 } 401 return potentialStereoAtoms; 402 } 403 404 /** 405 * Checks whether the atom has 3 or 4 neighbours all of which are constitutionally different 406 * @param potentialStereoAtom 407 * @return 408 */ isTrueStereCentre(Atom potentialStereoAtom)409 private boolean isTrueStereCentre(Atom potentialStereoAtom) { 410 List<Atom> neighbours = potentialStereoAtom.getAtomNeighbours(); 411 if (neighbours.size()!=3 && neighbours.size()!=4){ 412 return false; 413 } 414 int[] colours = new int[4]; 415 for (int i = neighbours.size() - 1 ; i >=0; i--) { 416 colours[i] = mappingToColour.get(neighbours.get(i)); 417 } 418 419 boolean foundIdenticalNeighbour =false; 420 for (int i = 0; i < 4; i++) { 421 int cl = colours[i]; 422 for (int j = i +1; j < 4; j++) { 423 if (cl == colours[j]){ 424 foundIdenticalNeighbour =true; 425 break; 426 } 427 } 428 } 429 return !foundIdenticalNeighbour; 430 } 431 432 /** 433 * Finds a subset of the stereocentres associated with rule 2 from: 434 * DOI: 10.1021/ci00016a003 435 * @param potentialStereoAtoms 436 * @param trueStereoCentres 437 */ findParaStereoCentres(List<Atom> potentialStereoAtoms, List<Atom> trueStereoCentres)438 private List<Atom> findParaStereoCentres(List<Atom> potentialStereoAtoms, List<Atom> trueStereoCentres) { 439 List<Atom> paraStereoCentres = new ArrayList<Atom>(); 440 for (Atom potentialStereoAtom : potentialStereoAtoms) { 441 List<Atom> neighbours = potentialStereoAtom.getAtomNeighbours(); 442 if (neighbours.size() == 4){ 443 int[] colours = new int[4]; 444 for (int i = neighbours.size() - 1 ; i >=0; i--) { 445 colours[i] = mappingToColour.get(neighbours.get(i)); 446 } 447 //find pairs of constitutionally identical substituents 448 Map<Integer, Integer> foundPairs = new HashMap<Integer, Integer>(); 449 for (int i = 0; i < 4; i++) { 450 int cl = colours[i]; 451 for (int j = i +1; j < 4; j++) { 452 if (cl == colours[j]){ 453 foundPairs.put(i, j); 454 break; 455 } 456 } 457 } 458 int pairs = foundPairs.keySet().size(); 459 if (pairs==1 || pairs==2){ 460 boolean hasTrueStereoCentreInAllBranches = true; 461 for (Entry<Integer, Integer> entry: foundPairs.entrySet()) { 462 if (!branchesHaveTrueStereocentre(neighbours.get(entry.getKey()), neighbours.get(entry.getValue()), potentialStereoAtom, trueStereoCentres)){ 463 hasTrueStereoCentreInAllBranches = false; 464 break; 465 } 466 } 467 if (hasTrueStereoCentreInAllBranches){ 468 paraStereoCentres.add(potentialStereoAtom); 469 } 470 } 471 } 472 } 473 return paraStereoCentres; 474 } 475 476 branchesHaveTrueStereocentre(Atom branchAtom1, Atom branchAtom2, Atom potentialStereoAtom, List<Atom> trueStereoCentres)477 private boolean branchesHaveTrueStereocentre(Atom branchAtom1, Atom branchAtom2, Atom potentialStereoAtom, List<Atom> trueStereoCentres) { 478 List<Atom> atomsToVisit= new ArrayList<Atom>(); 479 Set<Atom> visitedAtoms = new HashSet<Atom>(); 480 visitedAtoms.add(potentialStereoAtom); 481 atomsToVisit.add(branchAtom1); 482 atomsToVisit.add(branchAtom2); 483 while(!atomsToVisit.isEmpty()){ 484 List<Atom> newAtomsToVisit = new ArrayList<Atom>(); 485 while(!atomsToVisit.isEmpty()){ 486 Atom atom = atomsToVisit.remove(0); 487 if (trueStereoCentres.contains(atom)){ 488 return true; 489 } 490 if (atomsToVisit.contains(atom)){//the two branches have converged on this atom, don't investigate neighbours of it 491 do{ 492 atomsToVisit.remove(atom); 493 } 494 while (atomsToVisit.contains(atom)); 495 continue; 496 } 497 else{ 498 List<Atom> neighbours = atom.getAtomNeighbours(); 499 for (Atom neighbour : neighbours) { 500 if (visitedAtoms.contains(neighbour)){ 501 continue; 502 } 503 newAtomsToVisit.add(neighbour); 504 } 505 } 506 visitedAtoms.add(atom); 507 } 508 atomsToVisit = newAtomsToVisit; 509 } 510 return false; 511 } 512 513 /** 514 * Checks whether an atom could be a tetrahedral stereocentre by checking that it is both tetrahedral 515 * and does not have neighbours that are identical due to resonance/tautomerism 516 * @param atom 517 * @return 518 */ isPossiblyStereogenic(Atom atom)519 static boolean isPossiblyStereogenic(Atom atom){ 520 return isKnownPotentiallyStereogenic(atom) && !isAchiralDueToResonanceOrTautomerism(atom); 521 } 522 523 /** 524 * Roughly corresponds to the list of atoms in table 8 of the InChI manual 525 * tetrahedral phosphorus/arsenic are also allowed by InChI but are, perhaps due to an oversight, omitted from this table 526 * Essentially does a crude check for whether an atom is known to be able to possess tetrahedral geometry 527 * and whether it is currently tetrahedral. Atoms that are tetrahedral but not typically considered chiral 528 * like tertiary amines are not recognised 529 * @param atom 530 * @return 531 */ isKnownPotentiallyStereogenic(Atom atom)532 static boolean isKnownPotentiallyStereogenic(Atom atom) { 533 List<Atom> neighbours = atom.getAtomNeighbours(); 534 ChemEl chemEl = atom.getElement(); 535 if (neighbours.size() == 4){ 536 if (chemEl == ChemEl.B || chemEl == ChemEl.C || chemEl == ChemEl.Si || chemEl == ChemEl.Ge || 537 chemEl == ChemEl.Sn || chemEl == ChemEl.N || chemEl == ChemEl.P || chemEl == ChemEl.As || 538 chemEl == ChemEl.S || chemEl == ChemEl.Se){ 539 return true; 540 } 541 } 542 else if (neighbours.size() ==3){ 543 if ((chemEl == ChemEl.S || chemEl == ChemEl.Se) && (atom.getIncomingValency()==4 || (atom.getCharge() ==1 && atom.getIncomingValency()==3))){ 544 //tetrahedral sulfur/selenium - 3 bonds and the lone pair 545 return true; 546 } 547 if (chemEl == ChemEl.N && atom.getCharge() ==0 && atom.getIncomingValency()==3 && atomsContainABondBetweenThemselves(neighbours)){ 548 return true; 549 //nitrogen where two attached atoms are connected together 550 } 551 if ((chemEl == ChemEl.P || chemEl == ChemEl.As) &&atom.getIncomingValency() == 3) { 552 //tetrahedral phosphorus/arsenic - 3 bonds and the lone pair 553 return true; 554 } 555 } 556 return false; 557 } 558 atomsContainABondBetweenThemselves(List<Atom> atoms)559 private static boolean atomsContainABondBetweenThemselves(List<Atom> atoms) { 560 for (Atom atom : atoms) { 561 for (Atom neighbour : atom.getAtomNeighbours()) { 562 if (atoms.contains(neighbour)){ 563 return true; 564 } 565 } 566 } 567 return false; 568 } 569 isAchiralDueToResonanceOrTautomerism(Atom atom)570 static boolean isAchiralDueToResonanceOrTautomerism(Atom atom) { 571 ChemEl chemEl = atom.getElement(); 572 if(chemEl == ChemEl.N || 573 chemEl == ChemEl.P || 574 chemEl == ChemEl.As || 575 chemEl == ChemEl.S || 576 chemEl == ChemEl.Se) { 577 List<Atom> neighbours = atom.getAtomNeighbours(); 578 Set<String> resonanceAndTautomerismAtomicElementPlusIsotopes = new HashSet<String>(); 579 for (Atom neighbour : neighbours) { 580 ChemEl neighbourChemEl = neighbour.getElement(); 581 if ((neighbourChemEl.isChalcogen() || neighbourChemEl == ChemEl.N) 582 && isOnlyBondedToHydrogensOtherThanGivenAtom(neighbour, atom)){ 583 if (resonanceAndTautomerismAtomicElementPlusIsotopes.contains(neighbourChemEl.toString() + atom.getIsotope())){ 584 return true; 585 } 586 resonanceAndTautomerismAtomicElementPlusIsotopes.add(neighbourChemEl.toString() + atom.getIsotope()); 587 } 588 if (neighbourChemEl == ChemEl.H && neighbour.getBondCount()==1){ 589 //terminal H atom neighbour 590 return true; 591 } 592 } 593 } 594 return false; 595 } 596 isOnlyBondedToHydrogensOtherThanGivenAtom(Atom atom, Atom attachedNonHydrogen)597 private static boolean isOnlyBondedToHydrogensOtherThanGivenAtom(Atom atom, Atom attachedNonHydrogen) { 598 for (Atom neighbour: atom.getAtomNeighbours()) { 599 if (neighbour.equals(attachedNonHydrogen)){ 600 continue; 601 } 602 if (neighbour.getElement() != ChemEl.H){ 603 return false; 604 } 605 } 606 return true; 607 } 608 609 /** 610 * Retrieves a list of any double bonds possessing the potential to have E/Z stereoChemistry 611 * This is done internally by checking the two atoms attached to the ends of the double bond are different 612 * As an exception nitrogen's lone pair is treated like a low priority group and so is allowed to only have 1 atom connected to it 613 * @return 614 */ findStereoBonds()615 List<StereoBond> findStereoBonds() { 616 List<StereoBond> stereoBonds = new ArrayList<StereoBond>(); 617 for (Bond bond : bonds) { 618 if (bond.getOrder()==2){ 619 Atom a1 = bond.getFromAtom(); 620 List<Atom> neighbours1 = a1.getAtomNeighbours(); 621 neighbours1.remove(bond.getToAtom()); 622 if (neighbours1.size()==2 || (neighbours1.size()==1 && a1.getElement() == ChemEl.N && a1.getIncomingValency()==3 && a1.getCharge()==0)){ 623 if (neighbours1.size()==2 && mappingToColour.get(neighbours1.get(0)).equals(mappingToColour.get(neighbours1.get(1)))){ 624 continue; 625 } 626 Atom a2 = bond.getToAtom(); 627 List<Atom> neighbours2 = a2.getAtomNeighbours(); 628 neighbours2.remove(bond.getFromAtom()); 629 if (neighbours2.size()==2 || (neighbours2.size()==1 && a2.getElement() == ChemEl.N && a2.getIncomingValency()==3 && a2.getCharge()==0)){ 630 if (neighbours2.size()==2 && mappingToColour.get(neighbours2.get(0)).equals(mappingToColour.get(neighbours2.get(1)))){ 631 continue; 632 } 633 stereoBonds.add(new StereoBond(bond)); 634 } 635 } 636 } 637 } 638 return stereoBonds; 639 } 640 641 /** 642 * Returns a number describing the environment of an atom. Atoms with the same number are in identical environments 643 * Null if atom was not part of this environment analysis 644 * @param a 645 * @return 646 */ getAtomEnvironmentNumber(Atom a)647 Integer getAtomEnvironmentNumber(Atom a) { 648 return mappingToColour.get(a); 649 } 650 } 651