com.actelion.research.chem.io.pdb.converter.BondsCalculator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openchemlib Show documentation
Show all versions of openchemlib Show documentation
Open Source Chemistry Library
package com.actelion.research.chem.io.pdb.converter;
/*
* Copyright 2016 Actelion Pharmaceuticals Ltd., Gewerbestrasse 16, CH-4123 Allschwil, Switzerland
*
* This file is part of cif2molecule.
*
* cif2molecule is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* cif2molecule is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with DataWarrior.
* If not, see http://www.gnu.org/licenses/.
*
* @author Antanas Vaitkus
*/
import com.actelion.research.chem.*;
import com.actelion.research.chem.conf.BondLengthSet;
import com.actelion.research.util.IntQueue;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import static com.actelion.research.chem.conf.VDWRadii.COVALENT_RADIUS;
/**
* BondsCalculator is used to recreate the bonds and / or calculate the bonds orders
* based on the 3D coordinates of the atoms
*/
public class BondsCalculator {
private static final boolean DEBUG = false;
private static final double BUMP_FACTOR = 0.5;
private static final double SPECIAL_POSITION_CUTOFF = 0.01;
/**
* Calculates the bonds of a molecule by checking the distance between
* all atoms. The bond order is not set with this function.
*
* Complexity O(nAtoms)
* Memory O(nAtoms)
*
* @param mol
* The molecule for which the bonds should be calculated.
* @param lenient
* Suppresses exceptions that would normally be raised
* due to chemical discrepancies found in the molecule.
* @throws Exception
* Exception containing information about the exceeded
* maximum valence of an atom.
*/
public static void createBonds(Molecule3D mol, boolean lenient) throws Exception {
if ( mol.getAllAtoms()==0 ) return;
// Processing organic atoms first:
// 1. Create a grid
MoleculeGrid grid = new MoleculeGrid(mol);
int[] neighborCount = new int[mol.getAllAtoms()];
// 2. For each atom, check the neighbours and create
// a connection if the distance is close to the sum
// of covalent radii.
for (int i = 0; i < mol.getAllAtoms(); i++) {
if (!mol.isOrganicAtom(i)) continue;
// Get the neighbours
Set set = grid.getNeighbours(mol, i, 3.2);
for (int j : set) {
if (i > j) continue;
if (!mol.isOrganicAtom(j)) continue;
// Two hydrogen (H) atoms are very unlikely to form bonds in crystal structures
if (mol.getAtomicNo(i) == 1 && mol.getAtomicNo(j) == 1) continue;
double dist = getBondLength(mol, i, j);
double idealDist = COVALENT_RADIUS[mol.getAtomicNo(i)] + COVALENT_RADIUS[mol.getAtomicNo(j)];
if (dist > idealDist + .45) continue;
if (neighborCount[i] >= maxNeighborCount(mol, i) ||
neighborCount[j] >= maxNeighborCount(mol, j)) {
int hypervalentAtom = (neighborCount[i] >= maxNeighborCount(mol, i)) ? i : j;
String error = "maximum valence of " + maxNeighborCount(mol, hypervalentAtom) +
" exceeded for atom '" + mol.getAtomName(hypervalentAtom) + "'";
if (!lenient) {
throw new BondCalculatorException(error);
} else {
System.err.println();
}
continue;
}
try {
mol.addBond(i, j, 1);
neighborCount[i]++;
neighborCount[j]++;
} catch (Exception e) {
if (!lenient) throw e;
}
}
}
mol.ensureHelperArrays(Molecule.cHelperRings);
// Deleting incorrectly assigned bonds
for (int i = 0; i < mol.getRingSet().getSize(); i++) {
int[] ringAtoms = mol.getRingSet().getRingAtoms(i);
if (ringAtoms.length == 3) {
if (markImprobableDiphosphorusBondForDeletionInThreeAtomRing(mol, i)) continue;
markImprobableBondWithHydrogenForDeletionInThreeAtomRing(mol, i);
} else if (ringAtoms.length == 4) {
// Small rings sometimes get assigned incorrect interring bonds.
// For example, a 4-membered ring with atom connectivity a1-a2,
// a2-a3, a3-a4, a4-a1 sometimes get assigned an additional
// a1-a3 (or a2-a4) bond.
// The ring must be more or less flat to avoid deleting bonds
// in molecules that have a closely packed tetrahedral geometry
// (e. g. tetrahedrane).
if (StrictMath.abs(getDihedral(mol, ringAtoms[0], ringAtoms[1],
ringAtoms[2], ringAtoms[3])) > 20 * StrictMath.PI / 180) continue;
for (int ringAtom : ringAtoms) {
int inRingNeighbours = 0;
for (int k = 0; k < mol.getConnAtoms(ringAtom); k++) {
if (isRingAtom(mol, i, mol.getConnAtom(ringAtom, k))) {
inRingNeighbours++;
}
}
if (inRingNeighbours > 2) {
int longestBondIndex = -1;
double longestBondLength = -Double.MAX_VALUE;
for (int k = 0; k < mol.getConnAtoms(ringAtom); k++) {
if (!isRingAtom(mol, i, mol.getConnAtom(ringAtom, k))) continue;
if (longestBondLength < getBondLength(mol, mol.getConnBond(ringAtom, k))) {
longestBondIndex = k;
longestBondLength = getBondLength(mol, mol.getConnBond(ringAtom, k));
}
}
// if (isPlanar(mol, ringAtom, mol.getConnAtom(ringAtom, longestBondIndex))) {
mol.markBondForDeletion(mol.getConnBond(ringAtom, longestBondIndex));
// }
}
}
}
}
mol.deleteMarkedAtomsAndBonds();
// Processing non-organic atoms
grid = new MoleculeGrid(mol);
for( int i = 0; i < mol.getAllAtoms(); i++ ) {
if (mol.isOrganicAtom(i)) continue;
// TODO: since these are not covalent bonds, maybe none of the metals should be excluded?
// Not Lithium or Magnesium
// TODO: also Be in organometallic compounds?
// TODO: allow Alkali metals to coordinate water?
// if ( (isAlkaliMetalAtom(mol, i) && mol.getAtomicNo(i) != 3 ) ||
// (isAlkalineEarthMetalAtom(mol, i) && mol.getAtomicNo(i) != 12 ) ) continue;
Set set = grid.getNeighbours(mol, i, 3.2);
for (int j : set) {
double dist = getBondLength( mol, i, j );
double idealDist = COVALENT_RADIUS[mol.getAtomicNo(i)] + COVALENT_RADIUS[mol.getAtomicNo(j)];
if (dist > idealDist + .45) continue;
mol.addBond(i, j, Molecule3D.cBondTypeMetalLigand);
}
}
int ringNumber = mol.getRingSet().getSize();
Coordinates[] ringCenters = new Coordinates[ringNumber];
for (int i = 0; i < ringNumber; i++) {
ringCenters[i] = getAtomRingCenter(mol, i);
}
ArrayList[] atomsToRings = getAtomToRings(mol);
for ( int i = 0; i < mol.getAllAtoms(); i++ ) {
if ( !mol.isMetalAtom(i) ) continue;
boolean[] ringProcessed = new boolean[ringNumber];
boolean[] ringCoordinated = new boolean[ringNumber];
for ( int j = mol.getAllConnAtoms(i); j < mol.getAllConnAtomsPlusMetalBonds(i); j++ ) {
int ligand = mol.getConnAtom(i, j);
if ( mol.isMetalAtom(ligand) ) continue;
if ( atomsToRings[ligand] == null ) continue;
int closestRing = -1;
double closestDist = 0;
for ( int ringIndex: atomsToRings[ligand] ) {
if ( closestRing < 0 ||
ringCenters[ringIndex].distance(mol.getCoordinates(i)) < closestDist ) {
closestDist = ringCenters[ringIndex].distance(mol.getCoordinates(i));
closestRing = ringIndex;
}
}
if ( ringProcessed[closestRing] ) continue;
int bondCount = 0;
for ( int k = 0; k < mol.getAllConnAtomsPlusMetalBonds(i); k++ ) {
if ( isRingAtom(mol, closestRing, mol.getConnAtom(i, k) ) ) bondCount++;
}
// TODO: check is ring is (potentially) aromatic?
ringProcessed[closestRing] = true;
if ( bondCount > mol.getRingSet().getRingSize(closestRing) / 2 ) {
ringCoordinated[closestRing] = true;
}
}
for ( int j = 0; j < ringNumber; j++ ) {
if ( ringCoordinated[j] ) {
int[] ringAtoms = mol.getRingSet().getRingAtoms(j);
for (int ringAtom : ringAtoms) {
mol.addBond(i, ringAtom, Molecule.cBondTypeMetalLigand);
}
}
}
}
mol.deleteMarkedAtomsAndBonds();
mol.ensureHelperArrays(Molecule.cHelperNeighbours);
// TODO: ask about the applicability of this part
// remove erroneously assigned metal-ligand bonds
ArrayList bondsToDelete = new ArrayList<>();
for (int i = 0; i < mol.getAllBonds(); i++) {
if ( mol.getBondType(i) != Molecule3D.cBondTypeMetalLigand ) continue;
int metal;
int ligand;
if ( mol.isMetalAtom( mol.getBondAtom(0, i) ) ) {
metal = mol.getBondAtom(0, i);
ligand = mol.getBondAtom(1, i);
} else {
metal = mol.getBondAtom(1, i);
ligand = mol.getBondAtom(0, i);
}
for (int j = 0; j < mol.getAllConnAtomsPlusMetalBonds(ligand); j++ ) {
int ligandNeighbour = mol.getConnAtom(ligand, j);
if ( ligandNeighbour == metal ) continue;
double sigma = 0.2;
if ( getBondLength( mol, metal, ligand ) - getBondLength(mol, metal, ligandNeighbour) > sigma) {
bondsToDelete.add(i);
}
}
}
bondsToDelete.forEach(mol::markBondForDeletion);
mol.deleteMarkedAtomsAndBonds();
}
/**
* Evaluates if the given three-membered ring contains an unlikely bond
* between two phosphorus atoms. The bond is considered unlikely if it
* occurs in a three-membered ring and is above the specified bond length
* threshold.
*
* Such incorrect bonds are likely to be placed in molecules where two
* phosphorus (P) atoms are bridged by a third atom forming an acute angle.
* For example, COD entry 4071806 contains the PCP pattern that is incorrectly
* fused into a P1CP1 ring due to the distance between the two P atoms (2.426 A)
* falling into the range of previously observed long P-P bonds. It should be noted,
* however, that not all diphosporus bonds appearing in three-membered rings are
* incorrect as illustrated by COD entry 4062100. As such, additional bond length
* threshold is used to determine is the bond should be marked for deletion.
*
* @param mol
* Molecule that contains the ring.
* @param ringIndex
* Index of the ring in the molecule.
* @return
* Boolean value denoting if an improbable diphosphorus
* bond was identified and marked for deletion.
*/
private static boolean markImprobableDiphosphorusBondForDeletionInThreeAtomRing(Molecule3D mol, int ringIndex) {
if (mol.getRingSet().getRingSize(ringIndex) != 3) return false;
int[] ringBonds = mol.getRingSet().getRingBonds(ringIndex);
int longestDiphosphoroBondIndex = -1;
double longestDiphosphoroBondLength = 0;
for (int ringBond : ringBonds) {
int a1 = mol.getBondAtom(0, ringBond);
if (mol.getAtomicNo(a1) != 15) continue;
int a2 = mol.getBondAtom(1, ringBond);
if (mol.getAtomicNo(a2) != 15) continue;
if (getBondLength(mol, ringBond) > longestDiphosphoroBondLength) {
longestDiphosphoroBondIndex = ringBond;
longestDiphosphoroBondLength = getBondLength(mol, ringBond);
}
}
if (longestDiphosphoroBondIndex == -1) return false;
// TODO: eventually replace the hardcoded value with one based on statistics
// Explanation: the current cutoff values of 2.42 A was selected
// after analysing about a hundred structures that were assigned
// extremely long diphosphorus bonds. The longest correct
// disphophorus bond in a three-membered ring with the bond
// length of 2.409 was observed in COD entry 7107333.
if (longestDiphosphoroBondLength < 2.42) return false;
mol.markBondForDeletion(longestDiphosphoroBondIndex);
return true;
}
/**
* Evaluates if the given three-membered ring contains an unlikely bond
* that involves a hydrogen atom.
*
* Such incorrect bonds are placed in molecules where a hydrogen atom
* is positioned withing a bonding distance of two atoms. For example,
* COD entry 7027071 contains a hydrogen atom ('H4C') that is within
* a 0.8 A distance of the nitrogen atom ('N4') and a 1.46 A distance
* of a carbon atom ('C8'). As a result, an incorrect bond between
* the 'H4C' and 'C8' atoms gets placed forming a three-membered ring
* (C1HN1).
*
* @param mol
* Molecule that contains the ring.
* @param ringIndex
* Index of the ring in the molecule.
* @return
* Boolean value denoting if an improbable bond that involves
* a hydrogen atom was identified and marked for deletion.
*/
private static boolean markImprobableBondWithHydrogenForDeletionInThreeAtomRing(Molecule3D mol, int ringIndex){
if (mol.getRingSet().getRingSize(ringIndex) != 3) return false;
// TODO: allow two boron atoms to share a hydrogen
boolean bondMarkedForDeletion = false;
int[] ringAtoms = mol.getRingSet().getRingAtoms(ringIndex);
for (int j = 0; j < ringAtoms.length; j++) {
if (mol.getAtomicNo(ringAtoms[j]) != 1) continue;
int n1 = ringAtoms[((j - 1) + ringAtoms.length) % ringAtoms.length];
int n2 = ringAtoms[((j + 1) + ringAtoms.length) % ringAtoms.length];
int boundAtom = n1;
int unboundAtom = n2;
if (getBondLength(mol, ringAtoms[j], n1) > getBondLength(mol, ringAtoms[j], n2)) {
boundAtom = n2;
unboundAtom = n1;
}
mol.markBondForDeletion(mol.getBond(ringAtoms[j], unboundAtom));
System.err.printf(
"WARNING",
"a bond between atoms '" + mol.getAtomName(ringAtoms[j]) + "' and '" +
mol.getAtomName(unboundAtom) + "' was initially assigned based on the interatomic " +
"distance (" + "%.3f" + " nm), but was " +
"subsequently removed based on the molecular geometry -- atoms '" +
mol.getAtomName(ringAtoms[j]) + "', '" + mol.getAtomName(boundAtom) + "' and '" +
mol.getAtomName(unboundAtom) + "' are very unlikely to form a three-membered atom ring" + "\n",
getBondLength(mol, ringAtoms[j], unboundAtom)/10
);
bondMarkedForDeletion = true;
}
return bondMarkedForDeletion;
}
/**
* Calculates the geometrical center of the atom ring.
* @param mol
* The molecule that contains the ring.
* @param ringIndex
* The ring index in the molecule.
* @return
* Coordinates of the geometrical center of the atom ring.
*/
private static Coordinates getAtomRingCenter(Molecule3D mol, int ringIndex) {
Coordinates c = new Coordinates();
for ( int i: mol.getRingSet().getRingAtoms(ringIndex) ) {
c.x += mol.getAtomX(i);
c.y += mol.getAtomY(i);
c.z += mol.getAtomZ(i);
}
int ringAtomCount = mol.getRingSet().getRingAtoms(ringIndex).length;
c.x /= ringAtomCount;
c.y /= ringAtomCount;
c.z /= ringAtomCount;
return c;
}
/**
* Calculates for the organic subset of atoms the maximum number of neighbors
* @param mol
* The molecule that contains the atom.
* @param atom
* The index of the atom in the molecule.
* @return
* The maximum neighbour count for the atom.
*/
private static int maxNeighborCount(Molecule3D mol, int atom) {
int atomicNo = mol.getAtomicNo(atom);
return atomicNo == 5 ? 6 // B
: atomicNo <= 7 ? 4 // N, C
: atomicNo == 8 ? 3 // O
: atomicNo == 9 ? 1 // F
: atomicNo == 14 ? 6 // Si
: atomicNo == 15 ? 6 // P
: atomicNo == 16 ? 6 // S
: atomicNo == 17 ? 4 // Cl
: atomicNo == 33 ? 6 // As
: atomicNo == 34 ? 6 // Se
: atomicNo == 35 ? 6 // Br
: atomicNo == 52 ? 6
: atomicNo == 53 ? 6 : 8;
}
/**
* Calculate the bond orders of the molecule (without knowing the hydrogens).
* The calculation is based on the bond distance between each atoms.
*
* http://www.ccp14.ac.uk/ccp/web-mirrors/i_d_brown/valence.txt
* s = exp((Ro - R)/B)
*
* The implementation of this method is also most similar to the algorithm
* described in:
* http://www.daylight.com/meetings/mug01/Sayle/m4xbondage.html
*
* @param mol
* The molecule for which the bond orders should be calculated.
*/
public static void calculateBondOrders(Molecule3D mol) {
mol.ensureHelperArrays(Molecule.cHelperRings);
/*
* Pass 1: assign higher order bonds in unambiguous locations
*/
assignObviousHigherOrderBonds(mol);
// Place disilene bonds
assignDisileneBonds(mol);
//////////////////////////////////
// Functional Group Recognition
// Hybridization State Determination
resolveKnownIons(mol);
int[] spOrder = calculateHybridizationStates(mol);
recogniseFunctionalGroups(mol, spOrder);
// FIXME: this method should eventually be moved to the start of the method
resolveSharedHydrogenAtoms(mol);
// hybridization states need to be recalculated since
// the resolveSharedHydrogenAtoms() method might affect
// the order of atoms in the array
spOrder = calculateHybridizationStates(mol);
boolean[] visited = new boolean[mol.getAllBonds()];
if (DEBUG) {
System.err.println("> DEBUG: entering preliminary pass phase");
}
//Preliminary pass: process obvious bonds outside rings
for (int i = 0; i < mol.getBonds(); i++) {
if (mol.getBondType(i) == Molecule.cBondTypeMetalLigand) continue;
if (mol.isRingBond(i)) continue;
int a1 = mol.getBondAtom(0, i);
int a2 = mol.getBondAtom(1, i);
if (mol.getLowestFreeValence(a1) < 2) continue;
if (mol.getLowestFreeValence(a2) < 2) continue;
// if (!isPlanar(mol, a1, a2)) continue;
if (spOrder[a1] != 1 || spOrder[a2] != 1) continue;
int index = BondLengthSet.getBondIndex(3, false, false, mol.getAtomicNo(a1), mol.getAtomicNo(a2), 2, 2);
if (index == -1) continue;
double bondLength = BondLengthSet.getBondLength(index);
double bondLengthStd = BondLengthSet.getBondStdDev(index);
// Used to be 3 order > TRIPLE_BOND_CUTOFF
if (bondLength + bondLengthStd > getBondLength(mol, i)) {
mol.setBondOrder(i, 3);
visited[i] = true;
if (DEBUG) {
System.err.println(">> DEBUG: placing a triple bond between atoms '" +
mol.getAtomName(mol.getBondAtom(0, i)) + "' and '" +
mol.getAtomName(mol.getBondAtom(1, i)) + "' based on " +
"the geometry of the molecule and bond length statistics.");
}
}
}
if (DEBUG) {
System.err.println("> DEBUG: exiting preliminary pass phase");
}
identifySquarates(mol, spOrder);
if (DEBUG) {
System.err.println("> DEBUG: entering exocyclic double bond detection phase.");
}
// go through all exocyclic atoms and set the most likely double bonds
boolean[] isNonAromatic = assignExocyclicDoubleBonds(mol, spOrder);
// check exocyclic atoms that are part of non-aromatic rings
for (int i = 0; i < mol.getRingSet().getSize(); i++) {
if (isNonAromatic[i]) continue;
int[] ringAtoms = mol.getRingSet().getRingAtoms(i);
for (int ringAtom : ringAtoms) {
if (mol.getLowestFreeValence(ringAtom) < 1) continue;
for (int j = 0; j < mol.getConnAtoms(ringAtom); j++) {
// check if atom is exocyclic and capable of a double bond
int exoAtom = mol.getConnAtom(ringAtom, j);
if (mol.getAtomCharge(exoAtom) != 0) continue;
if (mol.getLowestFreeValence(exoAtom) < 1) continue;
if (spOrder[exoAtom] != 2) continue;
// if ( mol.getAtomicNo(exoAtom) == 8 ) continue;
// Check if the neighbour atom doesn't belong to the same ring
if (isRingAtom(mol, i, exoAtom)) continue;
boolean allowsDoubleBonds = true;
if (mol.isRingAtom(exoAtom)) {
for (int k = 0; k < mol.getRingSet().getSize(); k++) {
if (isRingAtom(mol, k, exoAtom) && !isNonAromatic[k]) {
allowsDoubleBonds = false;
break;
}
}
}
if (!allowsDoubleBonds) continue;
if (!isPlanar(mol, ringAtom, exoAtom)) continue;
int minPiCount = (mol.getConnAtoms(exoAtom) > 1) ? 1 : 0;
double averageSingleBondLength = getIdealisedBondLength(1, mol.getAtomicNo(ringAtom), mol.getAtomicNo(exoAtom), 1, minPiCount);
double averageDoubleBondLength = getIdealisedBondLength(2, mol.getAtomicNo(ringAtom), mol.getAtomicNo(exoAtom), 1, 1);
double averageDoubleBondStd = BondLengthSet.getBondStdDev(BondLengthSet.getBondIndex(2, false, false, mol.getAtomicNo(ringAtom), mol.getAtomicNo(exoAtom), 1, 1));
double calculatedLength = getBondLength(mol, ringAtom, exoAtom);
double doubleBondDiff = StrictMath.abs(averageDoubleBondLength - calculatedLength);
if (averageSingleBondLength < averageDoubleBondLength) {
if (DEBUG) {
System.err.println(">>> DEBUG: single '" + mol.getAtomicNo(ringAtom) + "-" +
mol.getAtomicNo(exoAtom) + "' bond seems to be shorter than a the " +
"double bond of the same type -- skipping the estimation of this bond.");
}
continue;
}
if (doubleBondDiff < 0 || doubleBondDiff <= averageDoubleBondStd * 1.5) {
mol.setBondOrder(mol.getBond(ringAtom, exoAtom), 2);
if (DEBUG) {
System.err.println(">>> DEBUG: placing a double bond between atoms '" +
mol.getAtomName(ringAtom) + "' and '" + mol.getAtomName(exoAtom) + "'.");
}
}
}
}
}
if (DEBUG) {
System.err.println("> DEBUG: exiting exocyclic ring atom double bond detection phase.");
}
/////////////////////////////////////////////////////////
// Aromatic Ring Perception
// This procedure calculates a normal to the ring and check that all
// atoms in the ring and their neighbours are within the plane defined by the normal
if ( DEBUG ) {
System.err.println("> DEBUG: entering aromatic ring perception phase");
}
RingCollection ringSet = mol.getRingSet();
boolean[] aromaticRing = findAromaticRings(mol, spOrder);
for (int i = 0; i < ringSet.getSize(); i++) {
if ( aromaticRing[i] ) {
boolean isResolved = false;
if (resolveTetraatomicAromaticRingIons(mol,i)) {
isResolved = true;
// Cyclothiaselenazenium
} else if ( ringSet.getRingSize(i) == 7 ) {
int NSBondCount = 0;
int SSBondCount = 0;
for (int bond : ringSet.getRingBonds(i)) {
if ( mol.getAtomicNo(mol.getBondAtom(0, bond) ) == 16 &&
mol.getAtomicNo(mol.getBondAtom(1, bond) ) == 16 ) {
SSBondCount++;
} else if (
( mol.getAtomicNo(mol.getBondAtom(0, bond)) == 7 &&
mol.getAtomicNo(mol.getBondAtom(1, bond)) == 16 ) ||
( mol.getAtomicNo(mol.getBondAtom(0, bond)) == 16 &&
mol.getAtomicNo(mol.getBondAtom(1, bond)) == 7 ) ) {
NSBondCount++;
} else {
break;
}
}
if ( NSBondCount == 6 && SSBondCount == 1 ) {
int SSBond = -1;
int ringSize = ringSet.getRingSize(i);
for (int j = 0; j < ringSet.getRingBonds(i).length; j++) {
int bond = ringSet.getRingBonds(i)[j];
if ( mol.getAtomicNo(mol.getBondAtom(0, bond) ) == 16 &&
mol.getAtomicNo(mol.getBondAtom(1, bond) ) == 16 ) {
SSBond = j;
break;
}
}
if (SSBond == -1) {
throw new RuntimeException("the S-S bond could not be found even though " +
"it was previously detected.");
} else {
int doubleBond1 = ringSet.getRingBonds(i)[(SSBond - 2 + ringSize) % ringSize];
int doubleBond2 = ringSet.getRingBonds(i)[(SSBond + 2 + ringSize) % ringSize];
int chargedNBond = ringSet.getRingBonds(i)[(SSBond + 3 + ringSize) % ringSize];
mol.setBondOrder(doubleBond1, 2);
int sulphurAtom = mol.getAtomicNo(mol.getBondAtom(0,doubleBond1)) == 16 ? 0 : 1;
mol.setAtomCharge(mol.getBondAtom(sulphurAtom, doubleBond1), 1);
mol.setBondOrder(doubleBond2, 2);
sulphurAtom = mol.getAtomicNo(mol.getBondAtom(0,doubleBond2)) == 16 ? 0 : 1;
mol.setAtomCharge(mol.getBondAtom(sulphurAtom, doubleBond2), 1);
int nitrogenAtom = mol.getAtomicNo(mol.getBondAtom(0,doubleBond1)) == 7 ? 0 : 1;
mol.setAtomCharge(mol.getBondAtom(nitrogenAtom, chargedNBond), 1);
}
isResolved = true;
}
}
if (!isResolved) {
for (int bond : ringSet.getRingBonds(i)) {
mol.setBondType(bond, Molecule3D.cBondTypeDelocalized);
}
}
}
}
//Aromatizer
AromaticityResolver resolver = new AromaticityResolver(mol);
resolver.locateDelocalizedDoubleBonds(null, true, true);
mol.ensureHelperArrays(Molecule3D.cHelperRings);
if (DEBUG) {
for (int i = 0; i < ringSet.getSize(); i++) {
if ( aromaticRing[i] ) {
System.err.println(">> DEBUG: entering ring number " + i );
for (int bond : ringSet.getRingBonds(i)) {
System.err.println(">>> DEBUG: aromatic bond between atoms " +
mol.getAtomName(mol.getBondAtom(0, bond) )+ "' and '" +
mol.getAtomName(mol.getBondAtom(1, bond) ) + "' was marked as a " +
"bond of order " + mol.getBondOrder(bond));
}
}
}
System.err.println("> DEBUG: exiting aromatic ring perception phase");
}
assignObviousHigherOrderBonds(mol);
// Find pi bonds
if ( DEBUG ) {
System.err.println("> DEBUG: entering pi bond detection phase");
}
boolean[] visitedAtoms = new boolean[mol.getAtoms()];
List piBonds = new LinkedList<>();
for (int i = 0; i < mol.getAtoms(); i++) {
// skip atoms that cannot accommodate an additional double bond
if (mol.getConnAtoms(i) < 2) continue;
if (spOrder[i] != 2) continue;
if (mol.getAtomPi(i) != 0) continue;
if (mol.isAromaticAtom(i)) continue;
if (mol.getAtomCharge(i) != 0) continue;
if (mol.getLowestFreeValence(i) < 1) continue;
visitedAtoms[i] = true;
for (int j = 0; j < mol.getConnAtoms(i); j++) {
int a1 = mol.getConnAtom(i, (j + 1) % mol.getConnAtoms(i));
int a2 = mol.getConnAtom(i, j);
if (visitedAtoms[a2]) continue;
// skip atoms that cannot accommodate an additional double bond
if (spOrder[a2] != 2) continue;
if (mol.getAtomPi(a2) != 0) continue;
if (mol.getAtomCharge(a2) != 0) continue;
if (mol.getLowestFreeValence(a2) < 1) continue;
if (!fitsAromaticLengthConstraints(mol, i, a2)) continue;
Coordinates origin = mol.getCoordinates(i);
Coordinates normal = calculatePlaneNormal(origin, mol.getCoordinates(a1), mol.getCoordinates(a2));
if (normal.distSq() == 0) continue;
boolean isFlat = true;
// TODO: join them in a list?
for (int k = 0; k < mol.getConnAtoms(i); k++) {
if (calculatePointToPlaneDistance(normal, origin, mol.getCoordinates(mol.getConnAtom(i, k))) > 0.4) {
isFlat = false;
break;
}
}
for (int k = 0; k < mol.getConnAtoms(a2); k++) {
if (calculatePointToPlaneDistance(normal, origin, mol.getCoordinates(mol.getConnAtom(a2, k))) > 0.4) {
isFlat = false;
break;
}
}
if (isFlat) {
piBonds.add(mol.getConnBond(i, j));
if (DEBUG) {
System.err.println(">> DEBUG: bond between atoms '" + mol.getAtomName(i) +
"' and '" + mol.getAtomName(mol.getConnAtom(i, j)) + "' has been marked " +
"as delocalized");
}
}
}
}
if ( DEBUG ) {
System.err.println("> DEBUG: exiting pi bond detection phase");
}
for (int piBond : piBonds) {
mol.setBondType(piBond, Molecule3D.cBondTypeDelocalized);
}
mol.ensureHelperArrays(Molecule3D.cHelperRings);
List> fragments = getPiBondFragments(mol);
for (List fragment : fragments) {
if (!(mol.getLowestFreeValence(fragment.get(0)) > 0 &&
mol.getLowestFreeValence(fragment.get(fragment.size() - 1)) > 0)) {
continue;
}
// if the fragment is capable of interchanging single-double bonds
// then the more likely pattern should be selected
double[] singleBonds = new double[fragment.size() - 1];
double[] doubleBonds = new double[fragment.size() - 1];
for (int j = 0; j < fragment.size() - 1; j++) {
singleBonds[j] = estimatePiBondFitness(mol, fragment.get(j), fragment.get(j + 1), 1);
doubleBonds[j] = estimatePiBondFitness(mol, fragment.get(j), fragment.get(j + 1), 2);
}
if (fragmentCanResonate(mol, fragment)) {
double leftToRight = 0;
double rightToLeft = 0;
for (int j = 0; j < fragment.size() - 1; j++) {
// start with a single bond
leftToRight += (j % 2) == 0 ? singleBonds[j] : doubleBonds[j];
// start with a double bond
rightToLeft += (j % 2) == 0 ? doubleBonds[j] : singleBonds[j];
}
if (fragment.size() % 2 == 1) {
for (int j = 0; j < fragment.size() - 1; j++) {
int bondOrder;
if (leftToRight < rightToLeft) {
bondOrder = (j % 2 == 0 ? 1 : 2);
} else {
bondOrder = (j % 2 == 0 ? 2 : 1);
}
mol.setBondOrder(mol.getBond(fragment.get(j), fragment.get(j + 1)), bondOrder);
if (DEBUG) {
System.err.println(">>> DEBUG: a bond of order " + bondOrder + " was placed between " +
"atoms '" + mol.getAtomName(fragment.get(j)) + "' and '" +
mol.getAtomName(fragment.get(j + 1)) + "' outside of the aromatizer");
}
}
}
} else {
for (int j = 0; j < fragment.size() - 1; j++) {
if (doubleBonds[j] < 0.03) {
mol.setBondOrder(mol.getBond(fragment.get(j), fragment.get(j + 1)), 2);
}
}
}
}
mol.ensureHelperArrays(Molecule3D.cHelperRings);
resolver = new AromaticityResolver(mol);
resolver.locateDelocalizedDoubleBonds(null, true, true);
mol.ensureHelperArrays(Molecule3D.cHelperRings);
if ( DEBUG ) {
for (int piBond : piBonds) {
int a1 = mol.getBondAtom(0, piBond);
int a2 = mol.getBondAtom(1, piBond);
System.err.println(">>>> DEBUG: a bond of order " + mol.getBondOrder(piBond) + " was " +
"placed between atoms '" + mol.getAtomName(a1) + "' and '" + mol.getAtomName(a2) +
"'.");
}
}
// Try to recognise the N-C-N pattern and set the charges accordingly
int index = BondLengthSet.getBondIndex(1, true, false, 6, 7, 1 , 1);
double bondLength = BondLengthSet.getBondLength(index);
double bondStd = BondLengthSet.getBondStdDev(index);
for ( int i = 0; i < mol.getAtoms(); i++ ) {
if ( mol.getAtomicNo(i) == 6 && spOrder[i] == 2 && mol.getConnAtoms(i) > 1 ) {
for (int j = 0; j < mol.getConnAtoms(i); j++) {
int a1 = mol.getConnAtom(i, j);
int a2 = mol.getConnAtom(i, (j + 1) % mol.getConnAtoms(i));
if (spOrder[a1] != 2 || spOrder[a2] != 2) continue;
if (mol.getAtomicNo(a1) != 7 || mol.getAtomicNo(a2) != 7) continue;
double a1Distance = getBondLength(mol, i, a1);
double a2Distance = getBondLength(mol, i, a2);
if ( ( a1Distance > bondLength + 3 * bondStd ) ||
mol.getBondOrder(mol.getBond(i, a1)) == 2 ) continue;
if ( ( a2Distance > bondLength + 3 * bondStd ) ||
mol.getBondOrder(mol.getBond(i, a1)) == 2 ) continue;
int shorterBondNitrogen = a1Distance < a2Distance ? a1 : a2;
// TODO: add an angle constraint
if ( mol.getAtomPi(i) == 0 ) {
if ( mol.getAtomCharge(a1) != 0 || mol.getAtomCharge(a2) != 0 ) continue;
if ( mol.getAtomPi(a1) != 0 || mol.getAtomPi(a2) != 0 ) continue;
if ( mol.getBondOrder(mol.getBond(i, a1)) < 2 &&
mol.getBondOrder(mol.getBond(i, a2)) < 2) {
mol.setBondOrder(mol.getBond(i, shorterBondNitrogen), 2);
mol.setAtomCharge(shorterBondNitrogen, +1);
mol.ensureHelperArrays(Molecule.cHelperRings);
}
} else {
// Switch N-C=N to N=C-N in if the latter resonance form seems more likely
// exocyclic aromatic resonance should not be considered
if ( mol.isAromaticAtom(a1) != mol.isAromaticAtom(a2) ) continue;
int longerBondNitrogen = ( shorterBondNitrogen == a1 ) ? a2 : a1;
if ( mol.getBondOrder(mol.getBond(i, longerBondNitrogen)) == 2 &&
mol.getAtomCharge(longerBondNitrogen) == 1 &&
mol.getAtomPi(shorterBondNitrogen) == 0 ) {
mol.setBondOrder(mol.getBond(i, longerBondNitrogen), 1);
mol.setBondOrder(mol.getBond(i, shorterBondNitrogen), 2);
mol.setAtomCharge(longerBondNitrogen, 0);
mol.setAtomCharge(longerBondNitrogen, +1);
mol.ensureHelperArrays(Molecule.cHelperRings);
}
}
}
}
}
mol.ensureHelperArrays(Molecule.cHelperRings);
/*
* 2nd pass: find obvious double bonds on sp and sp2 B, C and N outside aromatic rings
*/
assignObviousDoubleBonds(mol, spOrder);
/*
* 3rd pass: double and triple bonds that correspond to the bond length
* (but might deviate from the expected geometry)
*/
IntQueue queue = new IntQueue();
for (int i = 0; i < mol.getAllBonds(); i++) {
if ( mol.getBondType(i) == Molecule3D.cBondTypeMetalLigand ) continue;
if (!visited[i] && !mol.isAromaticBond(i)) queue.push(i);
while (!queue.isEmpty()) {
int bnd = queue.pop();
if (visited[bnd]) continue;
visited[bnd] = true;
int a1 = mol.getBondAtom(0, bnd);
int a2 = mol.getBondAtom(1, bnd);
if ( a1 >= spOrder.length || a2 >= spOrder.length) continue;
// Push the neighbour bonds into the queue, but
// avoid processing hydrogen and metal atoms
for (int j = 0; j < mol.getConnAtoms(a1); j++) {
queue.push(mol.getConnBond(a1, j));
}
for (int j = 0; j < mol.getConnAtoms(a2); j++) {
queue.push(mol.getConnBond(a2, j));
}
//Compute the free valence and increase the bond order if needed
double realBondLength = getBondLength(mol, a1, a2);
int doubleBondIndex = BondLengthSet.getBondIndex(2, false, false, mol.getAtomicNo(a1), mol.getAtomicNo(a2), 1, 1);
if ( doubleBondIndex == -1 ) continue;
double idealisedDoubleBondLength = BondLengthSet.getBondLength(doubleBondIndex);
double idealisedDoubleBondStdDev = BondLengthSet.getBondStdDev(doubleBondIndex);
if ( idealisedDoubleBondStdDev == BondLengthSet.DEFAULT_BOND_STDDEV ) {
idealisedDoubleBondStdDev = 0.1;
}
if ( realBondLength < idealisedDoubleBondLength + 3 * idealisedDoubleBondStdDev &&
!mol.isAromaticAtom(a1) && !mol.isAromaticAtom(a2) ) {
//Special case CS
if (mol.getAtomicNo(a1) == 16 && mol.getAllConnAtoms(a1) <= 2) continue;
if (mol.getAtomicNo(a2) == 16 && mol.getAllConnAtoms(a2) <= 2) continue;
if ( mol.getAtomCharge(a1) != 0 || mol.getAtomCharge(a2) != 0 ) continue;
int freeValence1 = StrictMath.max( mol.getLowestFreeValence(a1),
StrictMath.max( ( mol.getAtomicNo(a1) == 7 && spOrder[a1] < 3 ) ? 1 : 0,
( mol.getAtomicNo(a1) == 5 && spOrder[a1] < 3 ) ? 1 : 0 ) );
int freeValence2 = StrictMath.max( mol.getLowestFreeValence(a2),
StrictMath.max( ( mol.getAtomicNo(a2) == 7 && spOrder[a2] < 3 ) ? 1 : 0,
( mol.getAtomicNo(a2) == 5 && spOrder[a2] < 2 ) ? 1 : 0 ) );
if ( freeValence1 < 1 || freeValence2 < 1 ) continue;
boolean aligned = ( spOrder[a1] == 1 && spOrder[a2] == 1 );
boolean fitsTripleBondLength;
int tripleBondIndex = BondLengthSet.getBondIndex(3, false, false, mol.getAtomicNo(a1), mol.getAtomicNo(a2), 2, 2);
if ( tripleBondIndex != -1 ) {
double idealisedTripleBondLength = BondLengthSet.getBondLength(tripleBondIndex);
double idealisedTripleBondStdDev = BondLengthSet.getBondStdDev(tripleBondIndex);
if (idealisedTripleBondStdDev == BondLengthSet.DEFAULT_BOND_STDDEV) {
idealisedTripleBondStdDev = 0.1;
}
fitsTripleBondLength = realBondLength > idealisedTripleBondLength + 3 * idealisedTripleBondStdDev;
} else {
fitsTripleBondLength = realBondLength < idealisedDoubleBondLength;
}
if ( aligned && mol.getAtomPi(a1) < 2 && mol.getAtomPi(a2) < 2 && fitsTripleBondLength ) {
mol.setBondOrder(bnd, 3);
} else if ( mol.getAtomPi(a1) < ( spOrder[a1] == 1 ? 2 : 1 ) &&
mol.getAtomPi(a2) < ( spOrder[a2] == 1 ? 2 : 1 ) ) {
mol.setBondOrder(bnd, 2);
}
}
}
}
mol.ensureHelperArrays(Molecule3D.cHelperRings);
/*
* 4th pass: increase bond order if the geometry requires it
* (even though the bond length might seem too great)
*/
int[] localAtomPi = getAtomPiCount(mol);
for (int i = 0; i < mol.getAtoms(); i++) {
if (!canAccommodateDoubleBond(mol, i, localAtomPi[i], spOrder[i])) continue;
// sp3 atoms smaller than Si should not have double bonds
if (spOrder[i] == 3 && mol.getAtomicNo(i) < 14) continue;
for (int j = 0; j < mol.getConnAtoms(i); j++) {
int a1 = mol.getConnAtom(i, j);
if (canAccommodateDoubleBond(mol, a1, localAtomPi[a1], spOrder[a1])) {
mol.setBondOrder(mol.getBond(i, a1), 2);
localAtomPi[i] += 1;
localAtomPi[a1] += 1;
}
if (localAtomPi[i] >= (spOrder[i] == 1 ? 2 : 1)) break;
}
}
// 5th pass: redistributing charges
/*
* v0 is the start atom
* v1 is the 1st order neighbour of atom v0
* v2 is the 2nd order neighbour of atom v0
* v3 is the 3rd order neighbour of atom v0
*/
localAtomPi = getAtomPiCount(mol);
for (int v0 = 0; v0 < mol.getAllAtoms(); v0++) {
if ( mol.getAtomicNo(v0) == 6 ) {
// C[-]-
if ( spOrder[v0] != 2 ) continue;
if ( mol.getLowestFreeValence(v0) != 1 ) continue;
if ( localAtomPi[v0] != 0 ) continue;
if ( hasMetalLigandBonds(mol, v0) ) continue;
// O=C-C(-)-C=O -> O=C-C=C-O(-)
List terminalOxygens = new LinkedList<>();
// C(-)-N -> C->N(+)
List sp2NitrogenNeighbours = new LinkedList<>();
for (int j = 0; j < mol.getConnAtoms(v0); j++) {
int v1 = mol.getConnAtom(v0, j);
if ( mol.getAtomicNo(v1) == 6 ) {
// C[-]-C(R)=
if ( spOrder[v1] != 2 ) continue;
if ( localAtomPi[v1] == 0 ) continue;
for (int k = 0; k < mol.getConnAtoms(v1); k++) {
int v2 = mol.getConnAtom(v1, k);
// select the second order neighbour atom that is double bonded
// to the first order neighbour atom
if ( v2 == v0 ) continue;
if ( mol.getBondOrder(mol.getConnBond(v1, k)) != 2 ) continue;
if ( mol.getAtomicNo(v2) == 8 &&
mol.getConnAtoms(v2) == 1 ) {
terminalOxygens.add(v2);
} else if ( mol.getAtomicNo(v2) == 6 ) {
// C[-]-C(R)=C(R)(R)
for ( int l = 0; l < mol.getConnAtoms(v2); l++ ) {
if ( localAtomPi[v0] > 0 ) break;
int v3 = mol.getConnAtom(v2, l);
if ( v1 == v3 ) continue;
if ( mol.getAtomicNo(v3) != 6 ) continue;
if ( spOrder[v3] != 2 ) continue;
if ( mol.getLowestFreeValence(v3) != 1 ) continue;
if ( hasMetalLigandBonds(mol, v3) ) continue;
mol.setBondOrder(mol.getBond(v0, v1), 2);
mol.setBondOrder(mol.getBond(v1, v2), 1);
mol.setBondOrder(mol.getBond(v2, v3), 2);
localAtomPi[v0]++;
localAtomPi[v3]++;
break;
}
}
}
} else if (mol.getAtomicNo(v1) == 7 && spOrder[v1] == 2 &&
mol.getAtomPi(v1) == 0 && mol.getImplicitHydrogens(v1) == 0) {
sp2NitrogenNeighbours.add(j);
}
}
// FIXME: the valency of associated C atoms might change due to previous loop
// This needs serious refactoring
// moving charges from carbon atoms to oxygen atoms where possible
if ( mol.getLowestFreeValence(v0) > 0 ) {
if (terminalOxygens.size() > 0 ) {
mol.setBondOrder(mol.getConnBond(terminalOxygens.get(0), 0), 1);
mol.setBondOrder(mol.getBond(mol.getConnAtom(terminalOxygens.get(0), 0), v0), 2);
} else if (sp2NitrogenNeighbours.size() > 0) {
int[] sortedAtom = sortAtomsByBondLength(mol, v0, sp2NitrogenNeighbours);
mol.setBondOrder(mol.getConnBond(v0, sortedAtom[0]), 2);
// nitrogen atoms may contain a negative charge that was assigned during previous processing
int nAtom = mol.getConnAtom(v0, sortedAtom[0]);
if (mol.getAtomCharge(nAtom) < 0) {
mol.setAtomCharge(nAtom, mol.getAtomCharge(nAtom) + 1);
}
}
}
} else if ( mol.getAtomicNo(v0) == 16 ) {
// Sp2 sulphur with +1 charge with no metal bonds
if ( spOrder[v0] != 2 ) continue;
if ( mol.getAtomPi(v0) == 0 ) continue;
if ( mol.getAtomCharge(v0) != 1 ) continue;
if ( hasMetalLigandBonds(mol, v0) ) continue;
// (S+)=C-C=(S+) -> S-C=C-S
// (S+)=C-C -> (S+)-C=C
for (int j = 0; j < mol.getConnAtoms(v0); j++) {
// Sp2 carbon that is double bonded to the v0 sulphur
int v1 = mol.getConnAtom(v0, j);
if ( mol.getAtomicNo(v1) != 6 ) continue;
if ( mol.getBondOrder( mol.getBond(v0, v1) ) != 2 ) continue;
for (int k = 0; k < mol.getConnAtoms(v1); k++) {
// Sp2 carbon that is single bonded to the v1 carbon
int v2 = mol.getConnAtom(v1, k);
if ( mol.getAtomicNo(v2) != 6 ) continue;
if ( mol.getBondOrder(mol.getBond(v1,v2)) != 1 ) continue;
if ( mol.getLowestFreeValence(v2) == 1 ) {
mol.setBondOrder(mol.getBond(v1, v2), 2);
mol.setBondOrder(mol.getBond(v0, v1), 1);
mol.setAtomCharge(v0, 0);
} else if (mol.getAtomPi(v2) != 0){
for (int l = 0; l < mol.getConnAtoms(v2); l++) {
int v3 = mol.getConnAtom(v2, l);
if ( mol.getAtomicNo(v3) != 16 ) continue;
if ( mol.getAtomCharge(v3) != 1 ) continue;
if ( mol.getBondOrder(mol.getBond(v2, v3)) != 2 ) continue;
mol.setBondOrder(mol.getBond(v0, v1), 1);
mol.setBondOrder(mol.getBond(v1, v2), 2);
mol.setBondOrder(mol.getBond(v2, v3), 1);
mol.setAtomCharge(v0, 0);
mol.setAtomCharge(v3, 0);
}
}
}
}
}
}
mol.ensureHelperArrays(Molecule.cHelperRings);
// Set charges for atoms other than metals
assignAtomChargesBasedOnValence(mol, spOrder);
/*
* Transform the molecule to acquire a more reasonable resonance structure
*/
// R1(+)=X-R2(-) -> R1-X=R2
for (int i = 0; i < mol.getAtoms(); i++) {
if ( mol.getAtomCharge(i) != 1 ) continue;
if ( mol.getAtomPi(i) == 0 ) continue;
for ( int j = 0; j < mol.getConnAtoms(i); j++ ) {
int a1 = mol.getConnAtom(i, j);
if ( mol.getAtomCharge(a1) == 0 && mol.getConnBondOrder(i, j) == 2 ) {
for (int k = 0; k < mol.getConnAtoms(a1); k++) {
int a2 = mol.getConnAtom(a1, k);
double R1XBondLengthDiff = BondLengthSet.getBondLength(
BondLengthSet.getBondIndex(2, false, false,
mol.getAtomicNo(i),
mol.getAtomicNo(a1), 1, 1) ) - getBondLength(mol, i, a1);
if ( mol.getAtomCharge(i) < 0 && spOrder[a2] == 2 &&
mol.getAtomPi(a2) == 0 &&
( mol.getAtomCharge(i) + mol.getAtomCharge(a2) ) == 0 ) {
double R2XBondLengthDiff = BondLengthSet.getBondLength(
BondLengthSet.getBondIndex(2, false, false,
mol.getAtomicNo(a1),
mol.getAtomicNo(a2), 1, 1)) - getBondLength(mol, a1, a2);
if ( R2XBondLengthDiff < R1XBondLengthDiff ) {
mol.setBondOrder(mol.getBond(a1, a2), 2);
mol.setBondOrder(mol.getBond(i, a1), 1);
mol.setAtomCharge(i, 0);
mol.setAtomCharge(a2, 0);
break;
}
}
}
}
}
}
processKnownIonsInvolvingMetals(mol);
assignMetalCharges(mol);
mol.ensureHelperArrays(Molecule.cHelperNeighbours);
}
/**
* Gets the count of used pi electrons for each atom in the molecule.
* @param mol
* The molecule that contains the bonded atoms.
* @return
* Counts of used pi electron.
*/
private static int[] getAtomPiCount(Molecule3D mol) {
int[] atomPiCount = new int[mol.getAllAtoms()];
for ( int i = 0; i < atomPiCount.length; i++ ) {
if (mol.getAtomicNo(i) == 1) {
atomPiCount[i] = 0;
} else {
atomPiCount[i] = mol.getAtomPi(i);
}
}
return atomPiCount;
}
/**
* Assigns obvious higher order bonds where the geometry explicitly requires it.
*
* @param mol
* Molecule that contains the bonded atoms.
*/
private static void assignObviousHigherOrderBonds(Molecule3D mol) {
// Place obvious triple bonds for sp atoms
boolean changeOccurred = true;
int tripleBondIterationNo = 1;
while (changeOccurred) {
if (DEBUG) {
System.err.println(">> DEBUG: entered obvious triple bond assignment iteration " +
tripleBondIterationNo + ".");
}
changeOccurred = assignObviousTripleBonds(mol);
if (DEBUG) {
System.err.println(">> DEBUG: exited obvious triple bond assignment iteration " +
tripleBondIterationNo + ".");
}
tripleBondIterationNo++;
}
}
/**
* Assigns obvious triple bonds where the geometry explicitly requires it.
*
* @param mol
* Molecule that contains the bonded atoms.
* @return
* Boolean value denoting if any triple bonds were assigned.
*/
private static boolean assignObviousTripleBonds(Molecule3D mol) {
// the local pi count array is used to avoid calling ensureHelperArrays()
// each time a triple bond is assigned
boolean changeOccured = false;
int[] localAtomPi = getAtomPiCount(mol);
for (int i = 0; i < mol.getAtoms(); i++) {
if (mol.isMetalAtom(i)) continue;
if (localAtomPi[i] != 0) continue;
if (calculateHybridizationState(mol, i) != 1) continue;
if (mol.getAllConnAtoms(i) != 2) continue; // skip terminal atoms
boolean allowNeighbourCharge = true;
if (!canAccommodateTripleBond(mol, i, false)) {
if (!canAccommodateTripleBond(mol, i, true)) continue;
allowNeighbourCharge = false;
}
int a1 = mol.getConnAtom(i, 0);
int a2 = mol.getConnAtom(i, 1);
// skip if both atoms are capable of at least a single double bond
boolean a1CanCarryDoubleBond = canAccommodateDoubleBond(mol, a1, localAtomPi[a1], calculateHybridizationState(mol, a1));
boolean a2CanCarryDoubleBond = canAccommodateDoubleBond(mol, a2, localAtomPi[a2], calculateHybridizationState(mol, a2));
boolean a1CanCarryTripleBond = canAccommodateTripleBond(mol, a1, allowNeighbourCharge);
boolean a2CanCarryTripleBond = canAccommodateTripleBond(mol, a2, allowNeighbourCharge);
if ((a1CanCarryDoubleBond || a1CanCarryTripleBond) &&
(a2CanCarryDoubleBond || a2CanCarryTripleBond)) {
continue;
}
if (!a1CanCarryTripleBond && !a2CanCarryTripleBond) continue;
int tripleBondAtom = a1CanCarryTripleBond ? a1 : a2;
mol.setBondOrder(mol.getBond(i, tripleBondAtom), 3);
localAtomPi[i] = 2;
localAtomPi[tripleBondAtom] = 2;
changeOccured = true;
if (DEBUG) {
System.err.println(">>> DEBUG: placed a triple bond between atoms '" +
mol.getAtomName(i) + "' and '" + mol.getAtomName(tripleBondAtom) +
"' without relying on the bond length statistics.");
}
}
mol.ensureHelperArrays(Molecule.cHelperRings);
return changeOccured;
}
/**
* Evaluates is the given atom is capable of carrying a triple bond.
*
* @param mol
* Molecule that contains the atom.
* @param atom
* Index of the atom that should be evaluated.
* @param allowCharge
* Boolean value denoting if the atom is allowed to gain
* a formal charge in order to accommodate the triple bond
* (i.e. N+, B-).
* @return
* Boolean value denoting if the atom is capable of carrying a triple bond.
*/
private static boolean canAccommodateTripleBond(Molecule3D mol, int atom, boolean allowCharge) {
if (mol.getAtomicNo(atom) == 1) return false;
if (mol.isHalogene(atom)) return false;
int requiredValence = 2;
if (allowCharge) {
// boron (B) with the charge of -1 may carry a triple bond
if (mol.getAtomicNo(atom) == 5 ) requiredValence = 1;
// nitrogen (N) with the charge of +1 may carry a triple bond
if (mol.getAtomicNo(atom) == 7 ) requiredValence = 1;
}
if (mol.getLowestFreeValence(atom) < requiredValence) return false;
int spOrder = calculateHybridizationState(mol, atom);
return spOrder == 1;
}
/**
* Assigns obvious double bonds to boron, nitrogen and carbon atoms.
*
* @param mol
* The molecule that contains the bonded atoms.
* @param spOrder
* The sp hybridization type for each atom.
*/
private static void assignObviousDoubleBonds(Molecule3D mol, int[] spOrder) {
// using the local pi count array to avoid calling ensureHelperArrays
// each time a double bond is assigned
int[] atomPi = getAtomPiCount(mol);
for (int i = 0; i < mol.getAtoms(); i++) {
if ( mol.isAromaticAtom(i) ) continue;
if ( mol.getAtomCharge(i) != 0 ) continue;
// B, N, C with valence lower than 4
if ( mol.getAtomicNo(i) > 3 && mol.getAtomicNo(i) < 8 && mol.getOccupiedValence(i) < 4 ) {
if ( spOrder[i] == 1 ) {
for (int j = 0; j < mol.getConnAtoms(i); j++) {
int a1 = mol.getConnAtom(i, j);
if ( mol.getLowestFreeValence(a1) > 0 &&
mol.getAtomCharge(a1) == 0 &&
spOrder[a1] < 3 && atomPi[a1] <
( spOrder[a1] == 1 ? 2 :
spOrder[a1] == 2 ? 1 : 0 ) ) {
mol.setBondOrder(mol.getConnBond(i, j), 2);
atomPi[i]++;
atomPi[a1]++;
}
}
} else if (spOrder[i] == 2) {
if (atomPi[i] != 0) continue;
List potentialDoubleBondNeighbours = new LinkedList<>();
// nitrogen atoms with filled valencies are handled a little differently
boolean isFilledNitrogenAtom = mol.getAtomicNo(i) == 7 && mol.getLowestFreeValence(i) == 0;
for (int j = 0; j < mol.getConnAtoms(i); j++) {
int neighbour = mol.getConnAtom(i, j);
if ( mol.getAtomCharge(neighbour) != 0 ) continue;
int freeValence = StrictMath.max( mol.getLowestFreeValence(neighbour),
mol.getAtomicNo(neighbour) == 5 && spOrder[neighbour] < 3 ? 1 : 0 );
if ( freeValence < 1 ) continue;
if ( isFilledNitrogenAtom && mol.getAtomicNo(neighbour) == 7 && spOrder[neighbour] == 2 ) continue;
if ( atomPi[neighbour] < ( spOrder[neighbour] == 1 ? 2 :
spOrder[neighbour] == 2 ? 1 : 0 ) ||
mol.getAtomicNo(neighbour) == 15 ) {
potentialDoubleBondNeighbours.add(j);
}
}
if ( potentialDoubleBondNeighbours.size() > 1 ) {
double minDiff = -Double.MAX_VALUE;
int minDiffNeighbour = -1;
for (Integer neighbourIndex : potentialDoubleBondNeighbours) {
int neighbour = mol.getConnAtom(i, neighbourIndex);
double singleBondLength =BondLengthSet.getBondLength(
BondLengthSet.getBondIndex(1, false, false, mol.getAtomicNo(i), mol.getAtomicNo(neighbour), 1, 1));
double doubleBondLength = BondLengthSet.getBondLength(
BondLengthSet.getBondIndex(2, false, false, mol.getAtomicNo(i), mol.getAtomicNo(neighbour), 1, 1));
// TODO: think of a more legitimate way of detecting statistical anomalies
// safeguard against statistical anomalies
if ( singleBondLength < doubleBondLength || singleBondLength - doubleBondLength < 0.1 ) {
continue;
}
double bondLengthDifference = doubleBondLength - getBondLength(mol, i, neighbour);
if (bondLengthDifference > minDiff) {
minDiff = bondLengthDifference;
minDiffNeighbour = neighbour;
}
}
if (minDiffNeighbour > -1) {
mol.setBondOrder(mol.getBond(i, minDiffNeighbour), 2);
atomPi[i]++;
atomPi[minDiffNeighbour]++;
}
}
}
}
}
mol.ensureHelperArrays(Molecule.cHelperRings);
}
/**
* Assigns obvious disilene bonds.
*
* @param mol
* The molecule that contains the bonded atoms.
*/
private static void assignDisileneBonds(Molecule3D mol) {
double doubleBondCutoff = 2.29;
// FIXME: properly check if a normal index is returned
int bondIndex = BondLengthSet.getBondIndex(2, false, false, 14, 14, 1, 1 );
double siliconDoubleBondLength = BondLengthSet.getBondLength( bondIndex );
double siliconDoubleBondStdDev = BondLengthSet.getBondStdDev( bondIndex );
if ( siliconDoubleBondLength != -1 && siliconDoubleBondStdDev != -1 ) {
if ( ( siliconDoubleBondLength + 3 * siliconDoubleBondStdDev ) < doubleBondCutoff ) {
doubleBondCutoff = siliconDoubleBondLength + 3 * siliconDoubleBondStdDev;
}
}
int[] atomPi = getAtomPiCount(mol);
for (int i = 0; i < mol.getAtoms(); i++) {
if ( mol.getAtomicNo(i) != 14 ) continue;
if ( atomPi[i] != 0 ) continue;
if ( mol.getOccupiedValence(i) > 3 ) continue;
if ( mol.isAromaticAtom(i) ) continue;
for (int j = 0; j < mol.getConnAtoms(i); j++) {
int a1 = mol.getConnAtom(i, j);
// ring bonds are handled in aromaticity perception stage
if ( mol.isRingBond( mol.getBond( i, a1 ) ) ) continue;
if ( mol.getAtomicNo(a1) != 14 ) continue;
if ( mol.getOccupiedValence(a1) > 3 ) continue;
if ( atomPi[a1] != 0 ) continue;
if ( mol.isAromaticAtom(a1) ) continue;
if ( getBondLength( mol, i, a1 ) < doubleBondCutoff ) {
mol.setBondOrder( mol.getBond(i, a1), 2 );
atomPi[i]++;
atomPi[a1]++;
break;
}
}
}
}
/**
* Detects and assigns exocyclic double bonds that are more likely than
* double bonds inside the rings.
* @param mol
* Molecule that contains the atoms.
* @param spOrder
* The sp hybridization type for each atom.
* @return
* Boolean values denoting is an exocyclic bond has been placed
* for the corresponding atom ring.
*/
private static boolean[] assignExocyclicDoubleBonds(Molecule3D mol, int[] spOrder) {
boolean[] hasExocyclicDoubleBond = new boolean[mol.getRingSet().getSize()];
int[] localAtomPi = getAtomPiCount(mol);
for (int i = 0; i < mol.getRingSet().getSize(); i++) {
int[] ringAtoms = mol.getRingSet().getRingAtoms(i);
for (int ringAtom : ringAtoms) {
// skip the atom if the only exocyclic neighbours are hydrogen atoms
if (mol.getConnAtoms(ringAtom) == 2) continue;
// additional exclusion rules for certain elements
if (mol.getAtomicNo(ringAtom) == 14 || // Silicon (Si)
mol.getAtomicNo(ringAtom) == 15 || // Phosphorus (P)
mol.getAtomicNo(ringAtom) == 32) { // Germanium (Ge)
// skip atoms with full valency
if (mol.getFreeValence(ringAtom) < 1) continue;
// skip atoms that already contain a double bond
if (localAtomPi[ringAtom] > 0) continue;
}
// sp3d2 P and Te
if (spOrder[ringAtom] == 5 &&
(mol.getAtomicNo(ringAtom) == 15 || mol.getAtomicNo(ringAtom) == 52)) continue;
// skip the atom if it is not capable of a double bond
if (mol.getLowestFreeValence(ringAtom) <= 0 && mol.getAtomicNo(ringAtom) < 14) {
continue;
}
// find the best fit for an aromatic double bond inside the ring
double inRingMinDeviation = Double.MAX_VALUE;
for (int j = 0; j < mol.getConnAtoms(ringAtom); j++) {
int ringAtomNeighbour = mol.getConnAtom(ringAtom, j);
if (isRingAtom(mol, i, ringAtomNeighbour)) {
int bondTypeIndex = BondLengthSet.getBondIndex(2, true, false, mol.getAtomicNo(ringAtom),
mol.getAtomicNo(ringAtomNeighbour), 1, 1);
/*
* FIXME: the logic used for exocyclic bonds involving silicon (Si) should be generalised.
* In the future the logic should be generalised for all elements to initially try
* to retrieve the aromatic double bond distance and if it is not available fall back
* to the simple double bond one.
*/
if (mol.getAtomicNo(ringAtom) == 14 || mol.getAtomicNo(ringAtomNeighbour) == 14) {
if (bondTypeIndex == -1) {
bondTypeIndex = BondLengthSet.getBondIndex(2, false, false, mol.getAtomicNo(ringAtom),
mol.getAtomicNo(ringAtomNeighbour), 1, 1);
}
}
double currentDeviation = determineExpectedBondDeviation(mol,
mol.getBond(ringAtom, ringAtomNeighbour),
bondTypeIndex);
if (currentDeviation < inRingMinDeviation) {
inRingMinDeviation = currentDeviation;
}
}
}
for (int k = 0; k < mol.getConnAtoms(ringAtom); k++) {
// check if the ring atom is still capable of
// carrying an additional double bond
if (mol.getAtomicNo(ringAtom) == 15) { // phosphorus (P)
if (localAtomPi[ringAtom] > 0) break;
}
// check if atom is exocyclic and capable of a double bond
int exoAtom = mol.getConnAtom(ringAtom, k);
if (mol.isRingBond(mol.getBond(ringAtom, exoAtom))) continue;
if (mol.getAtomCharge(exoAtom) != 0) continue;
if (mol.getLowestFreeValence(exoAtom) < 1) continue;
// additional exclusion rules for certain elements
if (mol.getAtomicNo(exoAtom) == 14 || // Silicon (Si)
mol.getAtomicNo(exoAtom) == 15 || // Phosphorus (P)
mol.getAtomicNo(exoAtom) == 32) { // Germanium (Ge)
// skip atoms with full valency
if (mol.getFreeValence(exoAtom) < 1) continue;
// skip atoms that already contains a double bond
if (localAtomPi[exoAtom] > 0) continue;
}
// exocyclic double-bonded atoms should normally be planar,
// however, some large atoms may disrupt the planarity
if (!isPlanar(mol, ringAtom, exoAtom) &&
mol.getAtomicNo(ringAtom) < 14 &&
mol.getAtomicNo(exoAtom) < 14) {
continue;
}
// FIXME: pentavalent phosphorus (P) should be considered conjungated,
// but not strictly aromatic as stated in 10.1016/j.ccr.2015.02.010
if ((spOrder[exoAtom] == 2 || getAllConnAtoms(mol, exoAtom, true) == 1)) {
// check exocyclic atom bond length
int minPiCount = (getAllConnAtoms(mol, exoAtom, true) > 1) ? 1 : 0;
double averageSingleBondLength = getIdealisedBondLength(1, mol.getAtomicNo(ringAtom), mol.getAtomicNo(exoAtom), 1, minPiCount);
double averageSingleBondStd = BondLengthSet.getBondStdDev(BondLengthSet.getBondIndex(1, false, false, mol.getAtomicNo(ringAtom), mol.getAtomicNo(exoAtom), 1, minPiCount));
double averageDoubleBondLength = getIdealisedBondLength(2, mol.getAtomicNo(ringAtom), mol.getAtomicNo(exoAtom), 1, 1);
double averageDoubleBondStd = BondLengthSet.getBondStdDev(BondLengthSet.getBondIndex(2, false, false, mol.getAtomicNo(ringAtom), mol.getAtomicNo(exoAtom), 1, 1));
double calculatedLength = getBondLength(mol, ringAtom, exoAtom);
double singleBondDiff = calculatedLength - averageSingleBondLength;
double doubleBondDiff = calculatedLength - averageDoubleBondLength;
if (averageSingleBondLength < averageDoubleBondLength) {
if (DEBUG) {
System.err.println(">>> DEBUG: single '" + mol.getAtomicNo(ringAtom) + "-" +
mol.getAtomicNo(exoAtom) + "' bond seems to be shorter than a the " +
"double bond of the same type -- skipping the estimation of this bond.");
}
continue;
}
// add an exocyclic double bond is the exocyclic atom
// has no alternative way to fill its valency
if (mol.getConnAtoms(exoAtom) > 1 && !hasMetalLigandBonds(mol, exoAtom)) {
int doubleBondNeighbours = 0;
for (int j = 0; j < mol.getConnAtoms(exoAtom); j++) {
if (mol.getLowestFreeValence(mol.getConnAtom(exoAtom, j)) != 0) {
doubleBondNeighbours++;
}
}
if (doubleBondNeighbours == 1 && doubleBondDiff <= 3 * averageDoubleBondStd) {
mol.setBondOrder(mol.getBond(ringAtom, exoAtom), 2);
localAtomPi[ringAtom] += 1;
localAtomPi[exoAtom] += 1;
continue;
}
}
// skip the atom if we are more likely to find a double bond inside the ring
if (doubleBondDiff > inRingMinDeviation) continue;
if (doubleBondDiff < 0 || doubleBondDiff <= 1.5 * averageDoubleBondStd
|| StrictMath.abs(singleBondDiff) > 2 * averageSingleBondStd) {
mol.setBondOrder(mol.getBond(ringAtom, exoAtom), 2);
localAtomPi[ringAtom] += 1;
localAtomPi[exoAtom] += 1;
hasExocyclicDoubleBond[i] = true;
if (DEBUG) {
System.err.println(">>> DEBUG: placing a double bond between atoms '" +
mol.getAtomName(ringAtom) + "' and '" + mol.getAtomName(exoAtom) + "'.");
}
}
}
}
}
}
mol.ensureHelperArrays(Molecule3D.cHelperRings);
return hasExocyclicDoubleBond;
}
/**
* Assigns positive charges to metal atoms in well-known ions.
* @param mol
* The molecule that contains the metal atoms.
*/
private static void processKnownIonsInvolvingMetals(Molecule3D mol) {
for (int i = 0; i < mol.getAllAtoms(); i++) {
if (!mol.isMetalAtom(i)) continue;
if (resolveMetalHexafluoride(mol, i)) continue;
if (resolveGoldInLinearCoordination(mol, i)) continue;
// Aluminium (Al)
if (mol.getAtomicNo(i) == 13) {
// AlCl4(-)
if (mol.getAllConnAtomsPlusMetalBonds(i) == 4) {
boolean isIon = true;
for (int j = 0; j < mol.getAllConnAtomsPlusMetalBonds(i); j++) {
int connAtom = mol.getConnAtom(i, j);
// is terminal Cl atom
if (!(mol.getAtomicNo(connAtom) == 17 && mol.getAllConnAtoms(connAtom) == 0)) {
isIon = false;
break;
}
}
if (isIon) {
mol.setAtomCharge(i, 3);
}
}
}
}
}
/**
* Evaluates if the given atom serves as the central metal atom of a hexafluoride complex
* and assigns appropriate charges if needed. Only hexafluorides that can be resolved
* unambiguously are modified.
*
* @param mol
* Molecule that contains the metal atom.
* @param metalAtom
* Metal atom to be evaluated.
* @return
* Boolean value denoting if the charge of the metal atom was modified.
*/
private static boolean resolveMetalHexafluoride(Molecule3D mol, int metalAtom) {
int charge;
switch (mol.getAtomicNo(metalAtom)) {
case 13: // AlF6(3-)
case 26: // FeF6(3-)
charge = +3;
break;
case 51: // SbF6(-1)
case 79: // AuF6(-1)
charge = +5;
break;
// -1 is treated as a special value
default:
charge = -1;
}
if (charge == -1) return false;
if (!isCentralHexafluoriteIonAtom(mol, metalAtom)) return false;
mol.setAtomCharge(metalAtom, charge);
return true;
}
/**
* Evaluates if the given atom is a gold atom in a linear coordination
* and assigns appropriate charges if needed.
*
* NOTE: gold atoms appearing in linear coordination most likely have
* the oxidation state of +1.
*
* @param mol
* Molecule that contains the metal atom.
* @param metalAtom
* Metal atom to be evaluated.
* @return
* Boolean value denoting if the charge of the metal atom was modified.
*/
private static boolean resolveGoldInLinearCoordination(Molecule3D mol, int metalAtom) {
if (mol.getAtomicNo(metalAtom) != 79) return false; // Gold (Au)
if (mol.getAllConnAtomsPlusMetalBonds(metalAtom) != 2) return false;
int n1 = mol.getConnAtom(metalAtom, 0);
int n2 = mol.getConnAtom(metalAtom, 1);
// 2.80 rad ~ 160 deg
if (getAngle(mol, n1, metalAtom, n2) < 2.80) return false;
mol.setAtomCharge(metalAtom, +1);
return true;
}
/**
* Evaluates if the given atom serves as the central atom of a hexafluoride complex.
* @param mol
* Molecule that contains the atom.
* @param atom
* Atom to be evaluated.
* @return
* Boolean value denoting if given atom serves as the central atom of
* a hexafluoride complex
*/
static boolean isCentralHexafluoriteIonAtom(Molecule3D mol, int atom) {
boolean isIon = true;
if (mol.getAllConnAtomsPlusMetalBonds(atom) != 6) return false;
for (int j = 0; j < mol.getAllConnAtomsPlusMetalBonds(atom); j++) {
int connAtom = mol.getConnAtom(atom, j);
// is terminal F atom
if (!(mol.getAtomicNo(connAtom) == 9 && mol.getAllConnAtoms(connAtom) == 0)) {
isIon = false;
break;
}
}
return isIon;
}
private static void assignAtomChargesBasedOnValence(Molecule3D mol, int[] spOrder) {
// Set charges for non-metal atoms
for (int i = 0; i < mol.getAllAtoms(); i++) {
if (mol.isMetalAtom(i)) continue;
// FIXME: check and properly test if the ( mol.getAtomCharge(i) != 0 )
// can be refactored to the start of this loop
// only apply this set of rules to atoms that do not yet have a charge
if (mol.getAtomCharge(i) == 0) {
if (mol.getAtomicNo(i) == 5) { // boron (B)
if (mol.getOccupiedValence(i) == 4) {
mol.setAtomCharge(i, -1);
}
// } else if ( getAllConnAtoms(mol, i, true) == 6 ) {
// mol.setAtomAbnormalValence(i, 6);
// }
}
// Phoshorus (P)
if (mol.getAtomicNo(i) == 15) {
// Phoshorus (P) atoms with 4 neighbours and no higher order bonds have a +1 formal charge
if (getAllConnAtoms(mol, i, true) == 4 && mol.getAtomPi(i) == 0) {
mol.setAtomCharge(i, +1);
}
// Phosphorus (P) atoms with the valence of 6 have a -1 formal charge
if (mol.getOccupiedValence(i) == 6) {
mol.setAtomCharge(i, -1);
}
}
// Sulphur (S)
if (mol.getAtomicNo(i) == 16) {
// Sulphur (S) sp3 atoms with 3 neighbours have a +1 formal charge
if (spOrder[i] == 3 && getAllConnAtoms(mol, i, true) == 3) {
mol.setAtomCharge(i, +1);
}
}
// Arsernic (As) atoms with 4 neighbours and no double bonds have a +1 formal charge
if (mol.getAtomicNo(i) == 33) {
if (getAllConnAtoms(mol, i, true) == 4 && mol.getAtomPi(i) == 0) {
mol.setAtomCharge(i, +1);
}
}
}
// Hydrogen (H) ions
if (mol.getAtomicNo(i) == 1 && mol.getAllConnAtoms(i) == 0) {
if (mol.getAllConnAtomsPlusMetalBonds(i) > 0 &&
mol.isMetalAtom(mol.getConnAtom(i, 0))) {
// Hydride anion H- (usually bonded to metals)
mol.setAtomCharge(i, -1);
} else {
// Hydron cation H+
mol.setAtomCharge(i, +1);
}
}
// Removing implicit hydrogens by setting a negative charge.
// '_atom_site_attached_hydrogens' data item values are taken
// into consideration
if (mol.getImplicitHydrogens(i) > 0 || mol.getAttachedHydrogenCount(i) > 0) {
// sp2 carbon with three non-hydrogen neighbours should have a positive charge
if (mol.getAtomicNo(i) == 6 && spOrder[i] == 2 &&
mol.getConnAtoms(i) == 3 && mol.getAtomPi(i) == 0
&& mol.getAtomCharge(i) == 0 && !mol.isAromaticAtom(i)) {
mol.setAtomCharge(i, +1);
} else {
mol.setAtomCharge(i, mol.getAtomCharge(i) - mol.getImplicitHydrogens(i)
+ mol.getAttachedHydrogenCount(i));
}
}
// Adding a positive charge to the elements that have a high valency
if (mol.getOccupiedValence(i) > mol.getMaxValence(i) && mol.getAtomCharge(i) == 0) {
mol.setAtomCharge(i, mol.getAtomCharge(i) + mol.getOccupiedValence(i) - mol.getMaxValence(i));
}
}
}
/**
* Assigns positive charges to metal atoms if needed to balance out the overall
* charge of the molecule. It is assumed that the metal atoms were not assigned
* a charge up to this point.
* @param mol
* The molecule that contains the metal atoms.
*/
private static void assignMetalCharges(Molecule3D mol) {
int netCharge = 0;
for (int i = 0; i < mol.getAllAtoms(); i++) {
netCharge += mol.getAtomCharge(i);
}
if ( netCharge >= 0 ) {
return;
}
List metalAtoms = new ArrayList<>();
for (int i = 0; i < mol.getAtoms(); i++) {
if ( !mol.isMetalAtom(i) ) continue;
if ( mol.getAtomCharge(i) != 0 ) continue;
metalAtoms.add(i);
}
if ( metalAtoms.size() == 0 ) {
return;
}
// Put all of the remaining negative charge on the single metal atom
if ( metalAtoms.size() == 1 ) {
mol.setAtomCharge(metalAtoms.get(0), -Math.max(netCharge, -8));
} else {
/*
// Process the metal coordination sphere surroundings first
// All negatively charged ligands are assumed to increase the
// oxidation state by one despite their actual charge
if ( netCharge < 0 ) {
for (Integer metal : metalAtoms) {
if (maximumIonChargeReached(mol, metal)) continue;
if (!hasMetalLigandBonds(mol, metal)) continue;
for (int k = mol.getAllConnAtoms(metal); k < mol.getAllConnAtomsPlusMetalBonds(metal); k++) {
int ligandCharge = mol.getAtomCharge(mol.getConnAtom(metal, k)) < 0 ? 1 : 0;
if (ligandCharge < 0) {
int oldMetalCharge = mol.getAtomCharge(metal);
int newMetalCharge =
StrictMath.min( oldMetalCharge + StrictMath.abs(ligandCharge),
getMaximumIonCharge(mol, metal) );
mol.setAtomCharge(metal, newMetalCharge);
netCharge += newMetalCharge - oldMetalCharge;
}
}
}
}
*/
// TODO: select only atoms that do not have maximum charge
// If all metals are of the same type, valence and charge
// and the net charge is divisible by the number of metals
// atoms, distribute the net charge equally
boolean similarMetals = false;
if ( StrictMath.abs(netCharge) % metalAtoms.size() == 0 ) {
similarMetals = true;
for (Integer metalAtom : metalAtoms) {
similarMetals = similarMetals &&
mol.getAtomicNo(metalAtoms.get(0)) == mol.getAtomicNo(metalAtom) &&
mol.getAtomCharge(metalAtoms.get(0)) == mol.getAtomCharge(metalAtom) &&
mol.getAllConnAtoms(metalAtoms.get(0)) == mol.getAllConnAtoms(metalAtom);
}
}
if (similarMetals) {
int increment = Math.min( StrictMath.abs(netCharge) / metalAtoms.size(), 8 );
for (Integer metalAtom : metalAtoms) {
mol.setAtomCharge(metalAtom, increment);
}
} else {
// Otherwise try to distribute it over all of the metal atoms
boolean changeOccurred = true;
while (netCharge < 0 && changeOccurred) {
changeOccurred = false;
for (Integer metal : metalAtoms) {
int newMetalCharge = mol.getAtomCharge(metal) + 1;
if (newMetalCharge <= getMaximumIonCharge(mol, metal)) {
netCharge += 1;
mol.setAtomCharge(metal, newMetalCharge);
changeOccurred = true;
}
if (netCharge == 0) break;
}
}
}
}
// Set abnormal valence to metals that exhibit uncommon
// charges in order to generate a proper id codes
for (Integer metal : metalAtoms) {
// TODO: extend the logic to other metals as well
// Currently, the logic is only applied to Aluminium (Al).
// The code should work is other metals as well, but
// more extensive testing is needed
if (mol.getAtomicNo(metal) != 13) continue;
if (Molecule.cAtomValence[mol.getAtomicNo(metal)] == null) continue;
if (mol.getMaxValence(metal) > 0) {
mol.setAtomAbnormalValence(metal, mol.getDefaultMaxValenceUncharged(metal) - mol.getMaxValence(metal));
}
}
}
/**
* Creates a list of atom ring indexes that an atom is a
* part of for each atom of the molecule.
*
* @param mol
* The molecule that contains the atoms and rings.
* @return
* Array of atom ring index lists. The array indexes
* match the indexes of the molecule atoms. If an
* atom is not a part of any rings, null value is
* placed in the array instead of an empty list.
*/
private static ArrayList[] getAtomToRings(Molecule3D mol) {
ArrayList[] atomToRings = new ArrayList[mol.getAllAtoms()];
RingCollection ringSet = mol.getRingSet();
for ( int r = 0; r < ringSet.getSize(); r++ ) {
int[] ringAtom = ringSet.getRingAtoms(r);
for (int atom: ringAtom) {
if (atomToRings[atom] == null) {
atomToRings[atom] = new ArrayList<>();
}
atomToRings[atom].add(r);
}
}
return atomToRings;
}
/**
* Determines which atom rings in the molecule are aromatic.
* The aromaticity is determined by checking the ring geometry
* against various constraints like atom chemical type, atom
* planarity, atom bond lengths, atom bond angles.
*
* @param mol
* The molecule that contains the atom rings.
* @param spOrder
* The sp hybridization type for each atom.
* @return
* An array of boolean values marking which atom rings could
* potentially accommodate aromaticity. The indexes of the
* returned array correspond to the atom ring indexes in
* the molecule.
*/
private static boolean[] findAromaticRings(Molecule3D mol, int[] spOrder) {
RingCollection ringSet = mol.getRingSet();
boolean[] looksLikeAromaticRing = new boolean[ringSet.getSize()];
for (int ringNo = 0; ringNo < ringSet.getSize(); ringNo++) {
looksLikeAromaticRing[ringNo] = looksLikeAromaticRing(mol, ringNo, spOrder);
}
return looksLikeAromaticRing;
}
/**
* Determines is the given atom ring could potentially accommodate aromaticity.
* False positive values may be returned.
* @param mol
* Molecule that contains the atom ring.
* @param ringNo
* Index of the ring in the molecule.
* @param spOrder
* The sp hybridization type for each atom.
* @return
* Boolean values denoting is the ring could potentially accommodate aromaticity.
*/
private static boolean looksLikeAromaticRing(Molecule3D mol, int ringNo, int[] spOrder) {
if (mol.getRingSet().getRingSize(ringNo) < 3) return false;
if (mol.getRingSet().getRingSize(ringNo) > 7) return false;
int[] ringAtoms = mol.getRingSet().getRingAtoms(ringNo);
// Rings that are potentially interacting with a metal using
// pi bonds should be allowed to be a little more deformed
boolean isCoordinatedRing = isRingCoordinated(mol, ringNo);
for (int i = 0; i < ringAtoms.length; i++) {
int ringAtom = ringAtoms[i];
if (!looksLikeAromaticAtom(mol, ringAtom, spOrder[ringAtom])) return false;
int a0 = ringAtoms[(i - 1 + ringAtoms.length) % ringAtoms.length];
int a1 = ringAtoms[(i) % ringAtoms.length];
int a2 = ringAtoms[(i + 1) % ringAtoms.length];
int a3 = ringAtoms[(i + 2) % ringAtoms.length];
// Make sure that all boron, carbon, nitrogen and silicon
// atoms in the ring are planar, but allow some deviation
// due to neighbouring sulphur atoms
if ((mol.getAtomicNo(a1) == 5 ||
mol.getAtomicNo(a1) == 6 ||
mol.getAtomicNo(a1) == 7 ||
mol.getAtomicNo(a1) == 14) &&
spOrder[ringAtom] != 2) {
if (!((mol.getAtomicNo(a0) == 16 || mol.getAtomicNo(a2) == 16) &&
(getAllConnAtoms(mol, a1, true)) < 4)) {
return false;
}
}
if (!(fitsAromaticLengthConstraints(mol, a1, a2) ||
hasMetalLigandBonds(mol, a1) ||
hasMetalLigandBonds(mol, a2) ||
isCoordinatedRing)) {
if (DEBUG) {
System.err.println(">>> DEBUG: bond length between atoms '" + mol.getAtomName(a1) + "' and '" +
mol.getAtomName(a2) + "' caused ring " + ringNo + " to be marked as non aromatic.");
}
return false;
}
// check if all of the ring atoms lie in a plane
double dihedral = getDihedral(mol, a0, a1, a2, a3);
if (Math.abs(Math.toDegrees(dihedral)) > 20) {
return false;
}
if (mol.getAtomicNo(a1) == 5 || mol.getAtomicNo(a1) == 6 || mol.getAtomicNo(a1) == 7) {
if (isCoordinatedRing) continue;
for (int j = 0; j < mol.getAllConnAtoms(a1); j++) {
if (isRingAtom(mol, ringNo, mol.getConnAtom(a1, j))) continue;
dihedral = getDihedral(mol, mol.getConnAtom(a1, j), a1, a2, a3);
if (StrictMath.min(Math.abs(Math.toDegrees(dihedral)), 180 - Math.abs(Math.toDegrees(dihedral))) > 25) {
if (DEBUG) {
System.err.println(">>> DEBUG: the torsion angle involving atoms '"
+ mol.getAtomName(mol.getConnAtom(a1, j)) + "', '"
+ mol.getAtomName(a1) + "' , "
+ mol.getAtomName(a2) + "' and '"
+ mol.getAtomName(a3) + "' caused ring " + ringNo +
" to be marked as non aromatic.");
}
return false;
}
}
}
}
// excluding 3-membered rings that are part of a tetrahedron
if (ringAtoms.length == 3) {
int ringAtom = ringAtoms[0];
int exocyclicAtom = -1;
if (mol.getConnAtoms(ringAtom) == 4) {
for (int j = 0; j < mol.getConnAtoms(ringAtom); j++) {
if (!isRingAtom(mol, ringNo, mol.getConnAtom(ringAtom, j))) {
exocyclicAtom = mol.getConnAtom(ringAtom, j);
break;
}
}
}
return (mol.getBond(ringAtoms[1], exocyclicAtom) != -1) &&
(mol.getBond(ringAtoms[2], exocyclicAtom) != -1);
}
return true;
}
/**
* Evaluates if the given atom could potentially participate in aromaticity.
* False positive values may be returned.
*
* @param mol
* Molecule that contains the atom.
* @param atom
* Index of the atom.
* @param spOrder
* The sp hybridization type for each atom.
* @return
* Boolean value denoting if the atom could participate in aromaticity.
*/
private static boolean looksLikeAromaticAtom(Molecule3D mol, int atom, int spOrder) {
// boron (B), carbon (C), nitrogen (N), oxygen (O), silicon (Si)
if (mol.getAtomicNo(atom) < 15) {
if (getAllConnAtoms(mol, atom, true) > 3) return false;
}
// germanium (Ge)
if (mol.getAtomicNo(atom) == 32) {
if (getAllConnAtoms(mol, atom, true) > 3) return false;
if (spOrder != 2) return false;
}
if (getAllConnAtoms(mol, atom, true) > 4) return false;
// Atoms that already have double bonds cannot participate in aromaticity
// TODO: check if it is only true in some cases, sulphur (S) for example
if (mol.getAtomPi(atom) != 0) return false;
return true;
}
/**
* Calculates the hybridization state for each atom in a molecule.
*
* @param mol
* Molecule that contains the atoms.
* @return
* Array of integers with values {1,2,3,4,5} that correspond to
* the the {sp1, sp2, sp3, sp3d1, sp3d2} hybridization states
* accordingly.
*/
private static int[] calculateHybridizationStates(Molecule3D mol) {
int[] spOrder = new int[mol.getAllAtoms()];
for (int i = 0; i < mol.getAllAtoms(); i++) {
spOrder[i] = calculateHybridizationState(mol, i);
if (DEBUG) {
System.err.println("Atom '" + mol.getAtomName(i) + "' was assigned hybridization state " + spOrder[i]);
}
}
return spOrder;
}
/**
* Calculates the hybridization state for the given atom.
*
* @param mol
* Molecule that contains the atom.
* @return
* Integers from the set of {1,2,3,4,5} that correspond to
* the the {sp1, sp2, sp3, sp3d1, sp3d2} hybridization states
* accordingly.
*/
private static int calculateHybridizationState(Molecule3D mol, int i) {
int spOrder = 0; // special value denoting an unrecognised hybridization state
if (getAllConnAtoms(mol, i, true) <= 1) {
// TODO: ask if this can always be assumed
if (mol.getAtomicNo(i) == 8) {
spOrder = 2;
} else {
spOrder = 1;
}
} else if (getAllConnAtoms(mol, i, true) == 2) {
if (getAllConnAtoms(mol, i, false) <= 1) {
spOrder = 2;
} else {
double angle = getAngle(mol, mol.getConnAtom(i, 0), i, mol.getConnAtom(i, 1));
if (Math.abs(angle - Math.PI) < Math.PI / 6) {
spOrder = 1;
} else {
spOrder = 2;
}
}
} else if (getAllConnAtoms(mol, i, true) == 3) {
if (getAllConnAtoms(mol, i, false) < 2) {
spOrder = 0; // TODO: to be changed to 2 or 3
} else if (getAllConnAtoms(mol, i, false) < 3) {
spOrder = 2;
} else {
Coordinates origin = mol.getCoordinates(i);
Coordinates normal = calculatePlaneNormal(origin,
mol.getCoordinates(mol.getConnAtom(i, 0)),
mol.getCoordinates(mol.getConnAtom(i, 1)));
if (normal.distSq() > 0) {
if (calculatePointToPlaneDistance(normal, origin, mol.getCoordinates(mol.getConnAtom(i, 2))) < 0.4) {
spOrder = 2;
} else {
spOrder = 3;
}
} else {
spOrder = 3;
}
}
} else if (getAllConnAtoms(mol, i, true) == 4) {
spOrder = 3;
} else if (getAllConnAtoms(mol, i, true) == 5) {
spOrder = 4;
} else if (getAllConnAtoms(mol, i, true) == 6) {
spOrder = 5;
}
return spOrder;
}
/**
* Assigns bond orders and charges to hydrogen (H) atoms that
* are bonded to two non-metal(lic) atoms.
*
* @param mol
* The molecule that contains the shared hydrogen atoms.
*/
private static void resolveSharedHydrogenAtoms(Molecule3D mol) {
// Shared hydrogen atoms are handled in one of the two ways
// depending on the bond lengths to the neighbouring atoms:
// - in case one of the bonds is significantly shorter,
// the longer bond gets removed;
// - in case neither of the bonds is significantly shorter,
// both bonds are marked as metal-ligand (coordinate) bonds
// and the hydrogen atom is assigned a +1 charge.
for (int i = 0; i < mol.getAllAtoms(); i++) {
// R-H-R bridging hydrogen atom
if (mol.getAtomicNo(i) != 1) continue;
if (mol.getConnAtoms(i) != 2) continue;
// boron compounds must be handled separately
if (mol.getAtomicNo(mol.getConnAtom(i, 0)) == 5) continue;
if (mol.getAtomicNo(mol.getConnAtom(i, 1)) == 5) continue;
int bond1 = mol.getConnBond(i, 0);
int bond2 = mol.getConnBond(i, 1);
double bondLength1 = getBondLength(mol, bond1);
double bondLength2 = getBondLength(mol, bond2);
if (StrictMath.abs(bondLength1 - bondLength2) < 0.1) {
mol.setBondType(bond1, Molecule3D.cBondTypeMetalLigand);
mol.setBondType(bond2, Molecule3D.cBondTypeMetalLigand);
mol.setAtomCharge(i, +1);
} else {
// delete the longer single bond
if (bondLength1 > bondLength2) {
// mol.setBondType(bond1, Molecule3D.cBondTypeMetalLigand);
mol.markBondForDeletion(bond1);
} else {
// mol.setBondType(bond2, Molecule3D.cBondTypeMetalLigand);
mol.markBondForDeletion(bond2);
}
}
}
mol.deleteMarkedAtomsAndBonds();
mol.ensureHelperArrays(Molecule.cHelperNeighbours);
}
/**
* Recognises known ions in a molecule and assigns the appropriate
* bond orders and atom charges if needed.
*
* @param mol
* Molecule that contains the ions.
*/
private static void resolveKnownIons(Molecule3D mol) {
for (int i = 0; i < mol.getAtoms(); i++) {
if (mol.getAtomicNo(i) != 6) continue;
if (calculateHybridizationState(mol, i) != 2) continue;
if (mol.getAllConnAtoms(i) != 3) continue;
if (mol.getAttachedHydrogenCount(i) != 0) continue;
resolveTricyanomethanideIon(mol, i);
}
}
/**
* Evaluates if the given atom acts as the central atom of the tricyanomethanide
* ion ([C-](C#N)(C#N)(C#N)) and assigns appropriate bond orders and atom charges
* if needed.
*
* @param mol
* Molecule that contains the atoms.
* @param cCentralAtom
* Index of the central carbon atom to be evaluated.
* @return
* Boolean value denoting if any bond order or atom charge
* modifications were applied.
*/
private static boolean resolveTricyanomethanideIon(Molecule3D mol, int cCentralAtom) {
if (mol.getAtomicNo(cCentralAtom) != 6) return false;
if (calculateHybridizationState(mol, cCentralAtom) != 2) return false;
if (mol.getAllConnAtoms(cCentralAtom) != 3) return false;
if (mol.getAttachedHydrogenCount(cCentralAtom) != 0) return false;
int[] cAtoms = new int[3];
int[] nAtoms = new int[3];
for (int i = 0; i < mol.getAllConnAtoms(cCentralAtom); i++) {
int cAtom = mol.getConnAtom(cCentralAtom, i);
if (mol.getAllConnAtoms(cAtom) != 2) return false;
for (int j = 0; j < mol.getAllConnAtoms(cAtom); j++) {
int nAtom = mol.getConnAtom(cAtom, j);
if (cCentralAtom == nAtom) continue;
if (!looksLikeCyanoGroup(mol, cAtom, nAtom)) return false;
cAtoms[i] = cAtom;
nAtoms[i] = nAtom;
}
}
// [C-](C#N)(C#N)(C#N)
for (int j = 0; j < 3; j++) {
mol.setBondOrder(mol.getBond(cAtoms[j], nAtoms[j]), 3);
mol.setAtomCharge(cCentralAtom, -1);
}
mol.ensureHelperArrays(Molecule3D.cHelperNeighbours);
return true;
}
/**
* Evaluates if the given atom ring looks like a tetraatomic aromatic ring ion
* and assigns appropriate bond orders and atom charges if needed.
*
* @param mol
* Molecule that contains the atoms ring.
* @param ringNo
* Index of the ring in the molecule.
* @return
* Boolean value denoting if any bond order or atom charge
* modifications were applied.
*/
private static boolean resolveTetraatomicAromaticRingIons(Molecule3D mol, int ringNo) {
if (!looksLikeTetraatomicAromaticRingIon(mol, ringNo)) return false;
boolean isChalcogen = true;
int atomicNo = mol.getAtomicNo(mol.getRingSet().getRingAtoms(ringNo)[0]);
switch (atomicNo) {
case 7: // nitrogen (N)
case 15: // phosphorus (P);
case 33: // arsenic (As);
isChalcogen = false;
break;
case 8: // oxygen (O)
case 16: // sulphur (S)
case 34: // selenium (Se); tetraselenium ion ([Se]1[Se+]=[Se+][Se]1)
case 52: // tellurium (Te)
break;
default:
return false;
}
int[] ringBonds = mol.getRingSet().getRingBonds(ringNo);
int shortestBondIndex = 0;
double shortestBondLength = getBondLength(mol, ringBonds[shortestBondIndex]);
for (int i = 0; i < ringBonds.length; i++) {
if (getBondLength(mol, ringBonds[i]) < shortestBondLength) {
shortestBondLength = getBondLength(mol, ringBonds[i]);
shortestBondIndex = i;
}
}
mol.setBondOrder(ringBonds[shortestBondIndex], 2);
int dbAtom1 = mol.getBondAtom(0, ringBonds[shortestBondIndex]);
int dbAtom2 = mol.getBondAtom(1, ringBonds[shortestBondIndex]);
if (isChalcogen) {
// chalcogen ring, i.e. [Se]1[Se][Se+]=[Se+]1
mol.setAtomCharge(dbAtom1, +1);
mol.setAtomCharge(dbAtom2, +1);
} else {
// pnictogen ring, i.e. [P-]1[P-][P]=[P]1
for (int atom : mol.getRingSet().getRingAtoms(ringNo)) {
if (atom != dbAtom1 && atom != dbAtom2) {
mol.setAtomCharge(atom, -1);
}
}
}
return true;
}
/**
* Evaluates if the given atom ring looks like a tetraatomic aromatic ring ion,
* i.e. the tetraselenium ion ([Se]1[Se+]=[Se+][Se]1).
*
* @param mol
* Molecule that contains the atoms ring.
* @param ringNo
* Index of the ring in the molecule.
* @return
* Boolean value denoting if the ring looks like a tetraatomic aromatic
* ring ion.
*/
private static boolean looksLikeTetraatomicAromaticRingIon(Molecule3D mol, int ringNo) {
int[] ringAtoms = mol.getRingSet().getRingAtoms(ringNo);
if (ringAtoms.length != 4) return false;
// rings must be homocyclic
int atomicNo = mol.getAtomicNo(ringAtoms[0]);
for (int atom : ringAtoms) {
if (mol.getAtomicNo(atom) != atomicNo) return false;
if (getAllConnAtoms(mol, atom, true) != 2) return false;
if (mol.getAtomRingCount(atom, 7) != 1) return false;
}
return true;
}
/**
* Recognises functional groups in a molecule and sets the bonds and charges accordingly.
* @param mol
* The molecule that contains the functional groups.
* @param spOrder
* Array that contains the orbital hybridization type for each atom.
*/
private static void recogniseFunctionalGroups(Molecule3D mol, int[] spOrder) {
// TODO: consider the atom type of terminal bonds when sorting
// TODO: ask about the best way to keep track of new double bonds
// calling molecule helpers to ensure pi count after each double
// bond assignment is inefficient so a local array is used instead
int[] localAtomPiCount = getAtomPiCount(mol);
for(int i = 0; i < mol.getAtoms(); i++) {
if ( mol.getAtomCharge(i) != 0 ) continue;
if ( mol.getAtomRadical(i) != Molecule.cAtomRadicalStateNone ) continue;
if ( spOrder[i] == 2 && localAtomPiCount[i] != 0 && mol.getAtomicNo(i) < 7 ) continue;
// Terminal oxygen atoms capable of double bonds
List terminalOxygenAtoms = new LinkedList<>();
// All terminal atoms capable of double bonds (including the oxygen atoms)
List terminalDoubleBondAtoms = new LinkedList<>();
int oxygenCount = 0;
int terminalAtomCount = 0;
for (int j = 0; j < mol.getConnAtoms(i); j++ ) {
int neighbour = mol.getConnAtom(i, j);
boolean isTerminal = ( getAllConnAtoms(mol, neighbour, true) == 1 );
boolean isOxygen = ( mol.getAtomicNo(neighbour) == 8 );
if (isOxygen) oxygenCount++;
if ( isTerminal && isOxygen ) {
terminalOxygenAtoms.add(j);
}
if ( mol.getAtomicNo(neighbour) == 1 ) continue;
if ( isTerminal ) {
terminalAtomCount++;
// for now only consider terminal N, O, P and S for double bonds
if ( localAtomPiCount[neighbour] == 0 &&
( mol.getAtomicNo(neighbour) == 7 ||
mol.getAtomicNo(neighbour) == 8 ||
mol.getAtomicNo(neighbour) == 15 ||
mol.getAtomicNo(neighbour) == 16 ) ) {
terminalDoubleBondAtoms.add(j);
}
}
}
if ( getAllConnAtoms(mol, i, true) == 1 ) {
if (mol.getConnAtoms(i) == 0) continue;
int a1 = mol.getConnAtom(i, 0);
if (resolveDiatomicPnictogenIons(mol, i)) continue;
if (resolveDiatomicChalcogenIons(mol, i)) continue;
if ( mol.getAtomicNo(i) == 6 ) {
// R-N(+)=-=C(-)
if (mol.getAtomicNo(a1) == 7 && spOrder[a1] == 1) {
mol.setBondOrder(mol.getBond(i, a1), 3);
mol.setAtomCharge(i, -1);
mol.setAtomCharge(a1, +1);
localAtomPiCount[i] += 2;
localAtomPiCount[a1] += 2;
}
} else if ( mol.getAtomicNo(i) == 7 ) {
if (mol.getAtomicNo(a1) == 16 ) {
if ( getAllConnAtoms(mol, a1, true) == 1 ) {
if ( hasMetalLigandBonds(mol, i) ) {
mol.setBondOrder(mol.getBond(i, a1), 2);
localAtomPiCount[i]++;
localAtomPiCount[a1]++;
mol.setAtomCharge(i, -1);
} else {
mol.setBondOrder(mol.getBond(i, a1), 3);
localAtomPiCount[i] = localAtomPiCount[i] + 2;
localAtomPiCount[a1] = localAtomPiCount[a1] + 2;
mol.setAtomCharge(a1, 1);
}
} else if (spOrder[a1] == 1) {
mol.setBondOrder(mol.getBond(i, a1), 2);
localAtomPiCount[i]++;
localAtomPiCount[a1]++;
} else if (mol.getAtomicNo(a1) == 16 && spOrder[a1] == 3) {
// R-S#N
// FIXME: eventually replace the hardcoded S#N bond length value once enough statistics
// are gathered
if (getBondLength(mol, mol.getBond(i, a1)) < 1.47) {
mol.setBondOrder(mol.getBond(i, a1), 3);
localAtomPiCount[i] = localAtomPiCount[i] + 2;
localAtomPiCount[a1] = localAtomPiCount[a1] + 2;
}
}
}
} else if ( mol.getAtomicNo(i) == 8 ) {
// TODO: change O-C distances with proper values
if ( mol.getAtomicNo(a1) == 6 ) {
// C-=-O
if ( getAllConnAtoms(mol, a1, true) == 1 ) {
mol.setBondOrder(mol.getBond(i, a1), 3);
mol.setAtomCharge(a1, -1);
mol.setAtomCharge(i, +1);
localAtomPiCount[i] += 2;
localAtomPiCount[a1] += 2;
}
} else if ( mol.getAtomicNo(a1) == 7 ) {
// TODO: add the correct processing of the nitrosonium ion (N-=-O+)
// N=O ion
if ( getAllConnAtoms(mol, a1, true) == 1 ) {
mol.setBondOrder(mol.getBond(i, a1), 2);
mol.setAtomRadical(a1, Molecule3D.cAtomRadicalStateD);
localAtomPiCount[i]++;
localAtomPiCount[a1]++;
}
} else if ( mol.getAtomicNo(a1) == 8 ) {
// O-O ions
double sigma = 0.05;
if ( getAllConnAtoms(mol, a1, true) == 1 ) {
if (getBondLength(mol, i, a1) < 1.21 + sigma) {
mol.setBondOrder(mol.getBond(i, a1), 2);
localAtomPiCount[i]++;
localAtomPiCount[a1]++;
} else if (getBondLength(mol, i, a1) < 1.28 + sigma) {
mol.setBondOrder(mol.getConnBond(i, 0), 1);
mol.setAtomCharge(i, -1);
mol.setAtomRadical(a1, Molecule3D.cAtomRadicalStateD);
} else {
mol.setBondOrder(mol.getConnBond(i, 0), 1);
mol.setAtomCharge(i, -1);
mol.setAtomCharge(a1, -1);
}
}
}
}
} else if ( mol.getAllConnAtoms(i) == 2 ) {
if ( localAtomPiCount[i] == 2 ) continue;
// assigning neighbour atoms ordered by chemical number
int a1 = mol.getAtomicNo(mol.getConnAtom(i, 0)) <=
mol.getAtomicNo(mol.getConnAtom(i, 1)) ?
mol.getConnAtom(i, 0) : mol.getConnAtom(i, 1);
int a2 = a1 != mol.getConnAtom(i, 0) ?
mol.getConnAtom(i, 0) : mol.getConnAtom(i, 1);
if ( mol.getAtomicNo(i) == 5 ) {
if ( spOrder[i] == 1 ) {
if ( terminalDoubleBondAtoms.size() == 2 ) {
mol.setBondOrder(mol.getBond(i, a1), 2);
mol.setBondOrder(mol.getBond(i, a2), 2);
localAtomPiCount[i] += 2;
localAtomPiCount[a1] += 1;
localAtomPiCount[a2] += 1;
mol.setAtomCharge(i, -1);
}
}
} else if ( mol.getAtomicNo(i) == 6 ) {
if ( spOrder[i] == 1 ) {
if (terminalDoubleBondAtoms.size() == 1) {
int terminalAtom = mol.getConnAtom(i, terminalDoubleBondAtoms.get(0));
int nonTerminalAtom = (terminalAtom != a1) ? a1 : a2;
// cyano group (R-C#N)
if (resolveCyanoGroup(mol, i, terminalAtom)) {
localAtomPiCount[i] += 2;
localAtomPiCount[terminalAtom] += 2;
continue;
}
// isocyanate group (R-N=C=O)
if (resolveIsocyanateGroup(mol, i, nonTerminalAtom, terminalAtom)) {
localAtomPiCount[i] += 2;
localAtomPiCount[terminalAtom] += 1;
localAtomPiCount[nonTerminalAtom] += 1;
continue;
}
// isothiocyanate group (R-N=C=S)
if (resolveIsothiocyanateGroup(mol, i, nonTerminalAtom, terminalAtom)) {
localAtomPiCount[i] += 2;
localAtomPiCount[terminalAtom] += 1;
localAtomPiCount[nonTerminalAtom] += 1;
continue;
}
} else if (terminalDoubleBondAtoms.size() == 2) {
// thiocyanate ion (S=C=N[-])
if (resolverThiocyanateIon(mol, i, a2, a1)) {
localAtomPiCount[i] += 2;
localAtomPiCount[a1] += 1;
localAtomPiCount[a2] += 1;
continue;
}
// atoms are of the same species
if (mol.getAtomicNo(a1) == mol.getAtomicNo(a2)) {
mol.setBondOrder(mol.getBond(i, a1), 2);
mol.setBondOrder(mol.getBond(i, a2), 2);
localAtomPiCount[i] += 2;
localAtomPiCount[a1] += 1;
localAtomPiCount[a2] += 1;
}
}
}
} else if ( mol.getAtomicNo(i) == 7 ) {
if ( spOrder[i] == 1 ) {
if (terminalDoubleBondAtoms.size() > 0) {
// azide anion ([N-]=[N+]=[N-])
if (resolveAzideIon(mol, i)) {
localAtomPiCount[i] += 2;
localAtomPiCount[a1] += 1;
localAtomPiCount[a2] += 1;
continue;
}
// azide group (R[N]=[N+]=[N-])
if (resolveAzideGroup(mol, i)) {
localAtomPiCount[i] = mol.getAtomPi(i);
localAtomPiCount[a1] = mol.getAtomPi(a1);
localAtomPiCount[a2] = mol.getAtomPi(a2);
continue;
}
if ((mol.getAtomicNo(a1) == 6 && mol.getAtomicNo(a2) == 7) ||
(mol.getAtomicNo(a1) == 7 && mol.getAtomicNo(a2) == 6)) {
int carbonAtom = (mol.getAtomicNo(a1) == 6) ? a1 : a2;
int nitrogenAtom = (mol.getAtomicNo(a1) == 7) ? a1 : a2;
if ( getAllConnAtoms(mol, nitrogenAtom, true) == 1 &&
spOrder[carbonAtom] == 2) {
double doubleNNBond = getIdealisedBondLength(2, 7, 7, 1, 2);
double tripleNNBond = getIdealisedBondLength(3, 7, 7, 2, 2);
double singleCNBond = getIdealisedBondLength(1, 6, 7, 1, 2);
double doubleCNBond = getIdealisedBondLength(2, 6, 7, 1, 2);
double NNBondLength = getBondLength(mol, i, nitrogenAtom);
double CNBondLength = getBondLength(mol, i, carbonAtom);
double C2N2N = StrictMath.max( doubleNNBond - NNBondLength, 0 ) +
StrictMath.max( doubleCNBond - CNBondLength, 0 );
double C1N3N = StrictMath.max( tripleNNBond - NNBondLength, 0 ) +
StrictMath.max( singleCNBond - CNBondLength, 0 );
// RC-[N+]#N
if ( C1N3N < C2N2N ) {
mol.setBondOrder(mol.getBond(i, nitrogenAtom), 3);
mol.setAtomCharge(i, +1);
localAtomPiCount[i] += 2;
localAtomPiCount[nitrogenAtom] += 1;
} else {
// RC=[N+]=[N-]
mol.setBondOrder(mol.getBond(i, nitrogenAtom), 2);
mol.setBondOrder(mol.getBond(i, carbonAtom), 2);
mol.setAtomCharge(i, +1);
mol.setAtomCharge(nitrogenAtom, -1);
localAtomPiCount[i] += 2;
localAtomPiCount[nitrogenAtom] += 1;
localAtomPiCount[carbonAtom] += 1;
}
}
// O=N=O, S=N=S
} else if ( terminalOxygenAtoms.size() == 2 ||
mol.getAtomicNo(a1) == 16 && mol.getAtomicNo(a2) == 16 ) {
mol.setAtomCharge(i, +1);
for (Integer terminalDoubleBondAtom : terminalDoubleBondAtoms) {
mol.setBondOrder(mol.getConnBond(i, terminalDoubleBondAtom), 2);
localAtomPiCount[i] += 1;
localAtomPiCount[mol.getConnAtom(i, terminalDoubleBondAtom)] += 1;
}
}
} else {
// R-P=[N+]=P-R
if ( mol.getAtomicNo(a1) == 15 && spOrder[a1] == 3 &&
mol.getAtomicNo(a2) == 15 && spOrder[a2] == 3 ) {
// cyclophosphaneses
if ( mol.isRingAtom(i) ) {
double NPBondLength1 = getBondLength(mol, i, mol.getConnAtom(i, 0));
double NPBondLength2 = getBondLength(mol, i, mol.getConnAtom(i, 1));
if ( NPBondLength1 < NPBondLength2 && localAtomPiCount[mol.getConnAtom(i, 0)] == 0) {
mol.setBondOrder(mol.getConnBond(i, 0), 2);
localAtomPiCount[i] += 1;
localAtomPiCount[mol.getConnAtom(i, 0)] += 1;
} else if ( localAtomPiCount[mol.getConnAtom(i, 1)] == 0 ) {
mol.setBondOrder(mol.getConnBond(i, 1), 2);
localAtomPiCount[i] += 1;
localAtomPiCount[mol.getConnAtom(i, 1)] += 1;
}
// R-P=N(+)=P-R (bridging)
} else {
mol.setBondOrder(mol.getConnBond(i, 0), 2);
mol.setBondOrder(mol.getConnBond(i, 1), 2);
mol.setAtomCharge(i, +1);
localAtomPiCount[i] += 2;
localAtomPiCount[mol.getConnAtom(i, 0)] += 1;
localAtomPiCount[mol.getConnAtom(i, 1)] += 1;
}
}
}
} else if ( spOrder[i] == 2 ) {
if (terminalOxygenAtoms.size() == 1) {
if (resolveNitrosoGroup(mol, i)) {
localAtomPiCount[i] += 1;
localAtomPiCount[mol.getConnAtom(i, terminalOxygenAtoms.get(0))] += 1;
}
} else {
// R-P=N(+)-P-R
if (mol.getAtomicNo(a1) != 15) continue;
if (spOrder[a1] != 3) continue;
if (mol.getAtomicNo(a2) != 15) continue;
if (spOrder[a2] != 3) continue;
// skip if both phosphorus atoms already have a double bond
if (localAtomPiCount[a1] != 0 && localAtomPiCount[a2] != 0) continue;
// select the shortest bond out of the available ones
int doubleBondAtom = a1;
if (localAtomPiCount[a1] != 0) {
doubleBondAtom = a2;
} else if (localAtomPiCount[a2] == 0) {
if (getBondLength(mol, mol.getBond(i, a1)) > getBondLength(mol, mol.getBond(i, a2))) {
doubleBondAtom = a2;
}
}
mol.setBondOrder(mol.getBond(i, doubleBondAtom), 2);
localAtomPiCount[i] += 1;
localAtomPiCount[doubleBondAtom] += 1;
}
}
// S
} else if ( mol.getAtomicNo(i) == 16 ) {
if (spOrder[i] == 2) {
// SO2
if (terminalOxygenAtoms.size() > 0) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalOxygenAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 1);
mol.setAtomCharge(i, +1);
}
}
// F, Cl, Br, I
} else if ( mol.getAtomicNo(i) == 7 ||
mol.getAtomicNo(i) == 17 ||
mol.getAtomicNo(i) == 35 ||
mol.getAtomicNo(i) == 53 ) {
if (terminalDoubleBondAtoms.size() > 0) {
// F is not capable of a double bond
if (mol.getAtomicNo(i) == 7) {
mol.setAtomCharge(i, +1);
} else {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalDoubleBondAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 1);
}
} else {
// R-X-R (linear)
if (spOrder[i] == 1) {
mol.setAtomCharge(i, -1);
// R-X-R (bent)
} else if (spOrder[i] == 2) {
mol.setAtomCharge(i, +1);
}
}
}
} else if ( mol.getAllConnAtoms(i) == 3 ) {
int a, b;
int a1 = mol.getConnAtom(i, 0);
int a2 = mol.getConnAtom(i, 1);
int a3 = mol.getConnAtom(i, 2);
if (mol.getAtomicNo(i) == 6 && spOrder[i] == 2) {
if (mol.getAtomRingSize(i) > 0) {
if (terminalDoubleBondAtoms.isEmpty()) continue;
// thioketone in TTF: C(R)(R)(=S)
if (mol.getAtomRingSize(i) != 5) continue;
if (mol.getAtomicNo(a1) != 16) continue;
if (mol.getAtomicNo(a2) != 16) continue;
if (mol.getAtomicNo(a3) != 16) continue;
mol.setBondOrder(mol.getConnBond(i, terminalDoubleBondAtoms.get(0)), 2);
} else {
//C(N)(N)(=O)
//C(R)(O)(=O)
if (terminalOxygenAtoms.size() >= 2) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalOxygenAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 1);
continue;
}
//C(R)(OR)(=O)
a = connectedAtom(mol, i, 8, 2, 0, 0);
b = connectedBond(mol, i, 8, 1);
if (a >= 0 && b >= 0) {
mol.setBondOrder(b, 2);
continue;
}
//C(R)(SR)(=O)
a = connectedAtom(mol, i, 16, 2, 0, 0);
b = connectedBond(mol, i, 8, 1);
if (a >= 0 && b >= 0) {
mol.setBondOrder(b, 2);
continue;
}
//C(R)(NR)(=O)
a = connectedAtom(mol, i, 7, 3, 0, 0);
b = connectedBond(mol, i, 8, 1);
if (a >= 0 && b >= 0) {
mol.setBondOrder(b, 2);
continue;
}
//C(R)(SR)(=S)
a = connectedAtom(mol, i, 16, 2, 0, 0);
b = connectedBond(mol, i, 16, 1);
if (a >= 0 && b >= 0) {
mol.setBondOrder(b, 2);
continue;
}
//C(R)(NR)(=S)
a = connectedAtom(mol, i, 7, 3, 0, 0);
b = connectedBond(mol, i, 16, 1);
if (a >= 0 && b >= 0) {
mol.setBondOrder(b, 2);
continue;
}
//C(CR)(N)(=N)
if (terminalDoubleBondAtoms.size() == 2 &&
mol.getAtomicNo(terminalDoubleBondAtoms.get(0)) == 7 &&
mol.getAtomicNo(terminalDoubleBondAtoms.get(1)) == 7 &&
(mol.getAtomicNo(a1) == 6 ||
mol.getAtomicNo(a2) == 6 ||
mol.getAtomicNo(a3) == 6)) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalDoubleBondAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 1);
}
}
} else if( mol.getAtomicNo(i) == 7 ) {
// TODO: evaluate the probability of a certain SP order from the geometry
// slightly disordered nitrate ion
if ( spOrder[i] == 2 ) {
//N(R)(R)C=O -> Amide
a = connectedAtom(mol, i, 6, 2, 8, 1);
b = connectedBond(mol, a, 8, 1);
if (a >= 0 && b >= 0) {
mol.setBondOrder(b, 2);
localAtomPiCount[i] += 1;
localAtomPiCount[b] += 1;
} else
// N(O)(=O) -> Nitro
// nitrate ion
// NF2O(1+)
if ( terminalAtomCount > 1 && terminalOxygenAtoms.size() > 0) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalOxygenAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 1);
mol.setAtomCharge(i, +1);
}
}
} else if (mol.getAtomicNo(i) == 16) {
if (spOrder[i] == 2) {
// sulphur trioxide
if (terminalOxygenAtoms.size() > 0) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalOxygenAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 3);
}
} else if (spOrder[i] == 3) {
// sulphite ion
if (terminalOxygenAtoms.size() > 0) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalOxygenAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 1);
}
}
// Cl, Br, I
} else if ( mol.getAtomicNo(i) == 17 ||
mol.getAtomicNo(i) == 35 ||
mol.getAtomicNo(i) == 53 ) {
if ( spOrder[i] == 3 && terminalDoubleBondAtoms.size() > 0 ) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalDoubleBondAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 2);
}
} else if ( mol.getAtomicNo(i) == 34 ) {
if (oxygenCount == 3) {
if (spOrder[i] == 2) {
// selenium trioxide
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalOxygenAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 2);
} else if (spOrder[i] == 3) {
// selenite ion
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalOxygenAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 1);
}
} else {
mol.setAtomCharge(i, +1);
}
// Te
} else if( mol.getAtomicNo(i) == 52 ) {
mol.setAtomCharge(i, +1);
}
} else if( mol.getAllConnAtoms(i) == 4 ) {
// phosphorus (P) and arsenic (As)
if( mol.getAtomicNo(i) == 15 || mol.getAtomicNo(i) == 33 ) {
if (terminalDoubleBondAtoms.isEmpty()) continue;
// skip is the atom already has a double bond
if (localAtomPiCount[i] != 0) continue;
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalDoubleBondAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 1);
localAtomPiCount[i] += 1;
localAtomPiCount[mol.getConnAtom(i, sortedAtoms[0])] += 1;
} else if( mol.getAtomicNo(i)==16 ) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalDoubleBondAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 2);
// Cl, Br, I
} else if( mol.getAtomicNo(i) == 17 ||
mol.getAtomicNo(i) == 35 ||
mol.getAtomicNo(i) == 53 ) {
if ( terminalDoubleBondAtoms.size() > 0 ) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalDoubleBondAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 3);
} else {
// Set a negative charge to hypervalent atom
mol.setAtomCharge(i, -1);
}
} else if( mol.getAtomicNo(i) == 34 ) {
if ( oxygenCount == 4 && terminalOxygenAtoms.size() > 1 ) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalOxygenAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 2);
}
}
} else if( mol.getAllConnAtoms(i) == 5 ) {
if ( mol.getAtomicNo(i) == 14 ) { // Silicon (Si)
mol.setAtomCharge(i, -1);
} else if ( mol.getAtomicNo(i) == 53 ) { // Iodine (I)
if ( terminalOxygenAtoms.size() > 0 ) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalOxygenAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 2);
}
}
} else if ( mol.getAllConnAtoms(i) == 6) {
if (mol.getAtomicNo(i) == 14) { // Silicon (Si)
mol.setAtomCharge(i, -2);
} else if (mol.getAtomicNo(i) == 32) { // Germanium (Ge)
mol.setAtomCharge(i, -2);
} else if (mol.getAtomicNo(i) == 33) { // Arsenic (As)
mol.setAtomCharge(i, -1);
} else if (mol.getAtomicNo(i) == 34) { // Selenium (Se)
if ( terminalDoubleBondAtoms.size() > 0 ) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalDoubleBondAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 1);
} else {
mol.setAtomCharge(i, -2);
}
} else if( mol.getAtomicNo(i) == 52 ) { // Tellurium (Te)
// telluric acid has a net charge of 0
// even though it has 6 neighbours
if ( terminalDoubleBondAtoms.size() == 0 && oxygenCount == 0 ) {
mol.setAtomCharge(i, -2);
}
} else if( mol.getAtomicNo(i) == 53 ) { // Iodine (I)
if ( terminalDoubleBondAtoms.size() > 0 ) {
int[] sortedAtoms = sortAtomsByBondLength(mol, i, terminalDoubleBondAtoms);
setTerminalDoubleBonds(mol, i, sortedAtoms, 1);
} else {
mol.setAtomCharge(i, +1);
}
}
}
}
mol.ensureHelperArrays(Molecule3D.cHelperRings);
}
/**
* Evaluates if the given nitrogen atom serves as a part of a nitroso (R-N=O)
* group and assigns appropriate bond orders and atom charges if needed.
*
* Current implementation uses the following assumptions that are not explicitly checked:
* - the provided nitrogen atom has only two neighbour atoms;
* - one of the neighbour atoms is an oxygen atom capable of a double bond;
*
* @param mol
* Molecule that contains the atoms.
* @param nAtom
* Index of the nitrogen atom to be evaluated.
* @return
* Boolean value denoting if any bond order or atom charge
* modifications were applied.
*/
private static boolean resolveNitrosoGroup(Molecule3D mol, int nAtom) {
int oAtom;
int rAtom;
if (mol.getAtomicNo(mol.getConnAtom(nAtom, 0)) == 8) {
oAtom = mol.getConnAtom(nAtom, 0);
rAtom = mol.getConnAtom(nAtom, 1);
} else {
oAtom = mol.getConnAtom(nAtom, 1);
rAtom = mol.getConnAtom(nAtom, 0);
}
// c-nitroso group (RC-N=O)
if (mol.getAtomicNo(rAtom) == 6) {
mol.setBondOrder(mol.getBond(nAtom, oAtom), 2);
return true;
}
return false;
}
/**
* Evaluates if the given atoms could potentially comprise a cyano group (R-C#N).
*
* @param mol
* Molecule that contains the atoms.
* @param cAtom
* Index of the carbon atom to be evaluated.
* @param nAtom
* Index of the nitrogen atom to be evaluated.
* @return
* Boolean value denoting if the provided atoms could potentially
* comprise the group.
*/
private static boolean looksLikeCyanoGroup(Molecule3D mol, int cAtom, int nAtom) {
if (mol.getAtomicNo(cAtom) != 6) return false;
if (calculateHybridizationState(mol, cAtom) != 1) return false;
if (mol.getAtomicNo(nAtom) != 7) return false;
if (mol.getAllConnAtoms(nAtom) != 1) return false;
if (mol.getAttachedHydrogenCount(nAtom) != 0) return false;
return true;
}
/**
* Evaluates if the given atoms comprise a cyano group (R-C#N)
* and assigns appropriate bond orders and atom charges if needed.
*
* @param mol
* Molecule that contains the atoms.
* @param cAtom
* Index of the carbon atom to be evaluated.
* @param nAtom
* Index of the nitrogen atom to be evaluated.
* @return
* Boolean value denoting if any bond order or atom charge
* modifications were applied.
*/
private static boolean resolveCyanoGroup(Molecule3D mol, int cAtom, int nAtom) {
if (!looksLikeCyanoGroup(mol, cAtom, nAtom)) return false;
// cyano group (R-C#N)
mol.setBondOrder(mol.getBond(cAtom, nAtom), 3);
return true;
}
/**
* Evaluates if the given atoms comprise an isocyanate group (R-N=C=O)
* and assigns appropriate bond orders and atom charges if needed.
*
* @param mol
* Molecule that contains the atoms.
* @param cAtom
* Index of the carbon atom to be evaluated.
* @param nAtom
* Index of the nitrogen atom to be evaluated.
* @param oAtom
* Index of the oxygen atom to be evaluated.
* @return
* Boolean value denoting if any bond order or atom charge
* modifications were applied.
*/
private static boolean resolveIsocyanateGroup(Molecule3D mol, int cAtom, int nAtom, int oAtom) {
if (mol.getAtomicNo(nAtom) != 7) return false;
if (mol.getAtomicNo(cAtom) != 6) return false;
if (calculateHybridizationState(mol, cAtom) != 1) return false;
if (mol.getAtomicNo(oAtom) != 8) return false;
if (mol.getAllConnAtoms(oAtom) != 1) return false;
if (mol.getAttachedHydrogenCount(oAtom) != 0) return false;
// isocyanate group (N=C=O)
mol.setBondOrder(mol.getBond(nAtom, cAtom), 2);
mol.setBondOrder(mol.getBond(cAtom, oAtom), 2);
return true;
}
/**
* Evaluates if the given atoms comprise an isothiocyanate group (R-N=C=S)
* and assigns appropriate bond orders and atom charges if needed.
*
* @param mol
* Molecule that contains the atoms.
* @param cAtom
* Index of the carbon atom to be evaluated.
* @param nAtom
* Index of the nitrogen atom to be evaluated.
* @param sAtom
* Index of the sulphur atom to be evaluated.
* @return
* Boolean value denoting if any bond order or atom charge
* modifications were applied.
*/
private static boolean resolveIsothiocyanateGroup(Molecule3D mol, int cAtom, int nAtom, int sAtom) {
if (mol.getAtomicNo(nAtom) != 7) return false;
if (mol.getAtomicNo(cAtom) != 6) return false;
if (calculateHybridizationState(mol, cAtom) != 1) return false;
if (mol.getAtomicNo(sAtom) != 16) return false;
if (mol.getAllConnAtoms(sAtom) != 1) return false;
if (mol.getAttachedHydrogenCount(sAtom) != 0) return false;
// isothiocyanate group (N=C=S)
mol.setBondOrder(mol.getBond(nAtom, cAtom), 2);
mol.setBondOrder(mol.getBond(cAtom, sAtom), 2);
return true;
}
/**
* Evaluates if the given atoms comprise a thiocyanate ion (S=C=[N-])
* and assigns appropriate bond orders and atom charges if needed.
*
* @param mol
* Molecule that contains the atoms.
* @param cAtom
* Index of the carbon atom to be evaluated.
* @param sAtom
* Index of the sulphur atom to be evaluated.
* @param nAtom
* Index of the nitrogen atom to be evaluated.
* @return
* Boolean value denoting if any bond order or atom charge
* modifications were applied.
*/
private static boolean resolverThiocyanateIon(Molecule3D mol, int cAtom, int sAtom, int nAtom) {
if (mol.getAtomicNo(cAtom) != 6) return false;
if (calculateHybridizationState(mol, cAtom) != 1) return false;
if (mol.getAtomicNo(nAtom) != 7) return false;
if (mol.getAtomicNo(sAtom) != 16) return false;
// TODO: consider the resonance structure of SCN ([S-]-C=N <=> S=C=N[-])?
// thiocyanate ion (S=C=N[-])
mol.setBondOrder(mol.getBond(sAtom, cAtom), 2);
mol.setBondOrder(mol.getBond(cAtom, nAtom), 2);
mol.setAtomCharge(nAtom, -1);
return true;
}
/**
* Evaluate if the given atom could potentially act as the central
* atom of a azide group (R[N]=[N+]=[N-]).
*
* @param mol
* Molecule that contains the atoms.
* @param nCentralAtom
* Index of the nitrogen atom to be evaluated.
* @return
* Boolean value denoting if the given atom could potentially
* act as the central atom of an azide group.
*/
private static boolean looksLikeUnresolvedAzideGroup(Molecule3D mol, int nCentralAtom) {
if (mol.getAtomicNo(nCentralAtom) != 7) return false;
if (calculateHybridizationState(mol, nCentralAtom) != 1) return false;
if (mol.getConnAtoms(nCentralAtom) != 2) return false;
if (getAllConnAtoms(mol, nCentralAtom, true) != 2) return false;
int nAtom1 = mol.getConnAtom(nCentralAtom, 0);
int nAtom2 = mol.getConnAtom(nCentralAtom, 1);
if (mol.getAtomicNo(nAtom1) != 7) return false;
if (getAllConnAtoms(mol, nAtom1, true) > 2) return false;
if (mol.getAtomicNo(nAtom2) != 7) return false;
if (getAllConnAtoms(mol, nAtom2, true) > 2) return false;
// check if there is at least one terminal atom
if (getAllConnAtoms(mol, nAtom1, true) != 1 &&
getAllConnAtoms(mol, nAtom2, true) != 1) {
return false;
}
return true;
}
/**
* Evaluate if the given atom could potentially act as the central
* atom of an azide group (R[N]=[N+]=[N-]) and assigns appropriate bond
* orders and atom charges for the entire group if needed.
*
* @param mol
* Molecule that contains the atoms.
* @param nCentralAtom
* Index of the nitrogen atom to be evaluated.
* @return
* Boolean value denoting if any bond order or atom charge
* modifications were applied.
*/
private static boolean resolveAzideGroup(Molecule3D mol, int nCentralAtom) {
if (!looksLikeUnresolvedAzideGroup(mol, nCentralAtom)) return false;
int nAtom1 = mol.getConnAtom(nCentralAtom, 0);
int nAtom2 = mol.getConnAtom(nCentralAtom, 1);
int shorterBondAtom;
int longerBondAtom;
if (getBondLength(mol, nCentralAtom, nAtom1) < getBondLength(mol, nCentralAtom, nAtom2)) {
shorterBondAtom = nAtom1;
longerBondAtom = nAtom2;
} else {
shorterBondAtom = nAtom2;
longerBondAtom = nAtom1;
}
double shorterBondLength = getBondLength(mol, nCentralAtom, shorterBondAtom);
double longerBondLength = getBondLength(mol, nCentralAtom, longerBondAtom);
boolean accommodatesTripleBond = calculateHybridizationState(mol, shorterBondAtom) == 1 &&
StrictMath.abs(shorterBondAtom - longerBondAtom) > 0.05;
if (accommodatesTripleBond) {
double NNSingleBondLength = getIdealisedBondLength(2, 7, 7, 1, 2);
double NNDoubleBondLength = getIdealisedBondLength(2, 7, 7, 1, 2);
double NNTripleBondLength = getIdealisedBondLength(3, 7, 7, 2, 2);
double N2N2N = StrictMath.abs(NNDoubleBondLength - shorterBondLength) +
StrictMath.abs(NNDoubleBondLength - longerBondLength);
double N1N3N = StrictMath.abs(NNTripleBondLength - shorterBondLength) +
StrictMath.abs(NNSingleBondLength - longerBondLength);
accommodatesTripleBond = N1N3N < N2N2N;
}
// choose between the resonance structures
// RN=[N+]=[N-] and R[N-]-[N+]#N
if (accommodatesTripleBond) {
mol.setBondOrder(mol.getBond(nCentralAtom, shorterBondAtom), 3);
mol.setBondOrder(mol.getBond(nCentralAtom, longerBondAtom), 1);
mol.setAtomCharge(nCentralAtom, +1);
// in case of NH2-[N+]#N
// TODO: check if this conditional is needed
if (mol.getFreeValence(longerBondAtom) > 0) {
mol.setAtomCharge(longerBondAtom, -1);
}
} else {
mol.setBondOrder(mol.getBond(nCentralAtom, shorterBondAtom), 2);
mol.setBondOrder(mol.getBond(nCentralAtom, longerBondAtom), 2);
mol.setAtomCharge(nCentralAtom, +1);
if (mol.getFreeValence(shorterBondAtom) > 0 || calculateHybridizationState(mol, shorterBondAtom) == 1) {
mol.setAtomCharge(shorterBondAtom, -1);
}
if (mol.getFreeValence(longerBondAtom) > 0 || calculateHybridizationState(mol, longerBondAtom) == 1) {
mol.setAtomCharge(longerBondAtom, -1);
}
}
mol.ensureHelperArrays(Molecule.cHelperRings);
return true;
}
/**
* Evaluate if the given atom could potentially act as the central
* atom of a azide ion ([N-]=[N+]=[N-]).
*
* @param mol
* Molecule that contains the atoms.
* @param nCentralAtom
* Index of the nitrogen atom to be evaluated.
* @return
* Boolean value denoting if the given atom could potentially
* act as the central atom of an azide ion.
*/
private static boolean looksLikeUnresolvedAzideIon(Molecule3D mol, int nCentralAtom) {
if (!looksLikeUnresolvedAzideGroup(mol, nCentralAtom)) return false;
int nAtom1 = mol.getConnAtom(nCentralAtom, 0);
int nAtom2 = mol.getConnAtom(nCentralAtom, 1);
if (getAllConnAtoms(mol, nAtom1, true) != 1) return false;
if (getAllConnAtoms(mol, nAtom2, true) != 1) return false;
return true;
}
/**
* Evaluate if the given atom could potentially act as the central
* atom of an azide ion ([N-]=[N+]=[N-]) and assigns appropriate bond
* orders and atom charges for the entire ion if needed.
*
* @param mol
* Molecule that contains the atoms.
* @param nCentralAtom
* Index of the nitrogen atom to be evaluated.
* @return
* Boolean value denoting if any bond order or atom charge
* modifications were applied.
*/
private static boolean resolveAzideIon(Molecule3D mol, int nCentralAtom) {
if (!looksLikeUnresolvedAzideIon(mol, nCentralAtom)) return false;
int a1 = mol.getConnAtom(nCentralAtom, 0);
int a2 = mol.getConnAtom(nCentralAtom, 1);
mol.setBondOrder(mol.getBond(nCentralAtom, a1), 2);
mol.setBondOrder(mol.getBond(nCentralAtom, a2), 2);
mol.setAtomCharge(nCentralAtom, +1);
mol.setAtomCharge(a1, -1);
mol.setAtomCharge(a2, -1);
mol.ensureHelperArrays(Molecule.cHelperRings);
return true;
}
/**
* Evaluates if the given atom is part of a diatomic pnictogen ion of
* the forms [E-2][E-2], [E-1]=[E-1] where E = P, As (i.e. [P-2][P-2],
* [P-1]=[P-1]) and assigns appropriate bond orders and atom charges
* for the entire ion if needed.
*
* @param mol
* Molecule that contains the atoms.
* @param atom
* Index of the atom to be evaluated.
* @return
* Boolean value denoting if any atom charge modifications
* were applied.
*/
private static boolean resolveDiatomicPnictogenIons(Molecule3D mol, int atom) {
if (!mol.isNitrogenFamily(atom)) return false;
// skip nitrogen (N) for now
if (mol.getAtomicNo(atom) == 7) return false;
if (getAllConnAtoms(mol, atom, true) != 1) return false;
if (mol.getConnAtoms(atom) != 1) return false;
int nAtom = mol.getConnAtom(atom, 0);
if (!mol.isNitrogenFamily(nAtom)) return false;
// skip nitrogen (N) for now
if (mol.getAtomicNo(nAtom) == 7) return false;
if (mol.getConnAtoms(nAtom) != 1) return false;
// double bonded form ([E-]=[E-])
if (mol.getAtomicNo(atom) == 15 && mol.getAtomicNo(nAtom) == 15) {
// TODO: eventually replace the hardcoded value with one based on statistics
// Explanation: the provided cutoff value was chosen based
// on bond distances from 6 crystal structures that were
// show to contain the P=P ligand. The hardcoded value may
// eventually be removed if sufficiently granular and accurate
// statistics become available.
double phosphorusDoubleBondCuttoff = 2.05;
if (phosphorusDoubleBondCuttoff > getBondLength(mol, atom, nAtom)) {
mol.setBondOrder(mol.getBond(atom, nAtom), 2);
mol.setAtomCharge(atom, -1);
mol.setAtomCharge(nAtom, -1);
return true;
}
}
// TODO: investigate if a cutoff value for As is also needed
// single bonded form ([E-2][E-2])
mol.setAtomCharge(atom, -2);
mol.setAtomCharge(nAtom, -2);
return true;
}
/**
* Evaluates if the given atom is part of a diatomic chalcogen ion of
* the form [E-][E-] where E = S, Se, Te (i.e. [S-][S-]) and assigns
* appropriate atom charges for the entire ion if needed.
*
* @param mol
* Molecule that contains the atoms.
* @param atom
* Index of the atom to be evaluated.
* @return
* Boolean value denoting if any atom charge modifications
* were applied.
*/
private static boolean resolveDiatomicChalcogenIons(Molecule3D mol, int atom) {
if (!mol.isChalcogene(atom)) return false;
// skip oxygen (O) for now
if (mol.getAtomicNo(atom) == 8) return false;
if (getAllConnAtoms(mol, atom, true) != 1) return false;
if (mol.getConnAtoms(atom) != 1) return false;
int nAtom = mol.getConnAtom(atom, 0);
if (!mol.isChalcogene(nAtom)) return false;
// skip oxygen (O) for now
if (mol.getAtomicNo(nAtom) == 8) return false;
if (mol.getConnAtoms(nAtom) != 1) return false;
mol.setAtomCharge(atom, -1);
mol.setAtomCharge(nAtom, -1);
return true;
}
static void coordinationBondsToCovalent(Molecule3D mol) {
for (int i = 0; i < mol.getRingSet().getSize(); i++ ) {
if ( isRingCoordinated(mol, i) ) {
int ringAtom = mol.getRingSet().getRingAtoms(i)[0];
int metalAtom = -1;
for ( int j = mol.getAllConnAtoms(ringAtom); j < mol.getAllConnAtomsPlusMetalBonds(ringAtom); j++) {
if ( mol.isMetalAtom( mol.getConnAtom(ringAtom, j) ) ) {
metalAtom = mol.getConnAtom(ringAtom, j);
break;
}
}
for (int j = 0; j < mol.getRingSet().getRingSize(i); j++) {
mol.markBondForDeletion(mol.getBond(mol.getRingSet().getRingAtoms(i)[j], metalAtom));
}
}
}
mol.deleteMarkedAtomsAndBonds();
mol.ensureHelperArrays(Molecule3D.cHelperNeighbours);
for (int i = 0; i < mol.getAllBonds(); i++ ) {
if ( mol.getBondType(i) == Molecule3D.cBondTypeMetalLigand ) {
mol.setBondOrder(i, 1);
int a1 = mol.getBondAtom(0, i);
int a2 = mol.getBondAtom(1, i);
if ( mol.isMetalAtom(a1) && mol.isMetalAtom(a2) ) continue;
if ( mol.isMetalAtom(a1) || mol.getAtomicNo(a1) == 1 ) {
mol.setAtomCharge( a1, mol.getAtomCharge(a1) - 1 );
mol.setAtomCharge( a2, mol.getAtomCharge(a2) + 1 );
} else if ( mol.isMetalAtom(a2) || mol.getAtomicNo(a2) == 1 ) {
mol.setAtomCharge( a1, mol.getAtomCharge(a1) + 1 );
mol.setAtomCharge( a2, mol.getAtomCharge(a2) - 1 );
} else {
throw new RuntimeException( "a metal-ligand bond was assigned to a non-metal atoms " +
"'" + mol.getAtomName(a1) + "' and '" + mol.getAtomName(a2) + "'" );
}
}
}
}
private static boolean isPlanar(Molecule3D mol, int a1, int a2) {
Coordinates ci = mol.getCoordinates(a1);
Coordinates u = null, v = null;
for (int i = 0; v == null && i < mol.getAllConnAtoms(a1); i++) {
if ( u == null ) {
u = mol.getCoordinates(mol.getConnAtom(a1, i)).subC(ci);
} else {
v = mol.getCoordinates(mol.getConnAtom(a1, i)).subC(ci);
}
}
for (int i = 0; v == null && i < mol.getAllConnAtoms(a2); i++) {
if ( u == null ) {
u = mol.getCoordinates(mol.getConnAtom(a2, i)).subC(ci);
} else {
v = mol.getCoordinates(mol.getConnAtom(a2, i)).subC(ci);
}
}
if ( u == null ) return false;
Coordinates normal = u.cross(v);
if ( normal.distSq() == 0 ) return false; //what to do?
normal = normal.unitC();
Coordinates cj = mol.getCoordinates(a2);
for(int k = 0; k < mol.getAllConnAtoms(a2); k++) {
Coordinates ck = mol.getCoordinates(mol.getConnAtom(a2, k));
if(Math.abs(ck.subC(cj).dot(normal))>0.25) return false;
}
for(int k=0; k0.25) return false;
}
return true;
}
/**
* Util function for substructure searches. It searches for the substructure
* of form a-b-c where a, b, c are consecutively connected atoms and a != c.
* If a negative value is provided as the atomic number or valency of atom c
* this atom will be ignored and only the a-b substructure search will be
* carried out.
*
* @param mol
* The molecule that contains the atoms.
* @param aIndex
* The index of the atom 'a' in the molecule. The atom 'a' is used
* as the starting position of the substructure search.
* @param bAtomicNo
* The atomic number of the substructure atom 'b' that should be
* connected directly to the atom 'a'.
* @param bValence
* The valence of the substructure atom 'b' that should be connected
* directly to the atom 'a'.
* @param cAtomicNo
* The atomic number of the substructure atom 'c' that should be
* connected directly to the atom 'b'.
* @param cValence
* The valence of the substructure atom 'c' that should be connected
* directly to the atom 'b'.
* @return
* Index of the atom 'b' in the molecule is the search is successful
* and -1 otherwise.
*/
private static int connectedAtom( Molecule3D mol, int aIndex, int bAtomicNo,
int bValence, int cAtomicNo, int cValence ) {
loop: for(int i=0; i 0 && mol.getAtomicNo(atm) != bAtomicNo ) continue;
if ( bValence > 0 && mol.getAllConnAtoms(atm) != bValence ) continue;
// Checking if the atom 'c' that is connected to atom 'b'
// is of the the requested chemical type and valence
if ( cAtomicNo > 0 || cValence > 0 ) {
for ( int j = 0; j < mol.getAllConnAtoms(atm); j++ ) {
int otherAtm = mol.getConnAtom(atm, j);
if(otherAtm==aIndex) continue loop;
if ( cAtomicNo > 0 && mol.getAtomicNo(otherAtm) != cAtomicNo ) continue loop;
if ( cValence > 0 && mol.getAllConnAtoms(otherAtm) != cValence ) continue loop;
}
}
return atm;
}
return -1;
}
private static double getBondLength(Molecule3D mol, int a1, int a2) {
return mol.getCoordinates(a1).distance(mol.getCoordinates(a2));
}
private static double getBondLength(Molecule3D mol, int bond) {
return getBondLength(mol, mol.getBondAtom(0, bond), mol.getBondAtom(1, bond));
}
private static int connectedBond(Molecule3D mol, int a, int atomicNo, int valence) {
if(a<0) return -1;
for(int i=0; i< mol.getAllConnAtoms(a); i++) {
int atm = mol.getConnAtom(a, i);
if(atomicNo>0 && mol.getAtomicNo(atm)!=atomicNo) continue;
if(valence>0 && mol.getAllConnAtoms(atm)!=valence) continue;
return mol.getConnBond(a, i);
}
return -1;
}
/**
* Returns the average bond length value based on bond order
* and the type and pi electron count of the bonded atoms.
* The values are taken from the statistical model
* constructed from previously observed bond parameter
* values.
* @param bondOrder
* Bond order expressed as a positive integer.
* @param atomicNo1
* Atomic number of the first atom.
* @param atomicNo2
* Atomic number of the second atom.
* @param atomPi1
* Pi electron count of the first atom.
* @param atomPi2
* Pi electron count of the second atom.
* @return
* The average bond length.
*/
private static double getIdealisedBondLength(int bondOrder, int atomicNo1, int atomicNo2, int atomPi1, int atomPi2) {
return BondLengthSet.getBondLength(BondLengthSet.getBondIndex(bondOrder, false, false, atomicNo1, atomicNo2, atomPi1, atomPi2));
}
/**
* Produce a logical estimate of the bond being aromatic based
* strictly on length constraints. The bond length is compared
* to the average single bond.
* @param mol
* The molecule that contains the bond.
* @param a1
* Index of the first atom that forms a bond.
* @param a2
* Index of the second atom that forms a bond.
* @return
* Estimate deciding if the bond could be aromatic
* based strictly on the bond length.
*/
private static boolean fitsAromaticLengthConstraints(Molecule3D mol, int a1, int a2) {
int atomType1 = Math.min(mol.getAtomicNo(a1), mol.getAtomicNo(a2));
int atomType2 = Math.max(mol.getAtomicNo(a1), mol.getAtomicNo(a2));
int pi1 = BondLengthSet.isPiConsidered(atomType1) ? 1 : -1;
int pi2 = BondLengthSet.isPiConsidered(atomType2) ? 1 : -1;
// Try to get statistics for the aromatic single bond
boolean aromaticConstraintsUsed = true;
int index = BondLengthSet.getBondIndex(1, true, false, atomType1, atomType2, pi1, pi2);
// If no such bond exist, try to get statistics for the
// regular single bond
if ( index == -1 ) {
aromaticConstraintsUsed = false;
index = BondLengthSet.getBondIndex(1, false, false, atomType1, atomType2, pi1, pi2);
if (index == -1) return false;
}
double bondLength = BondLengthSet.getBondLength(index);
double sigma = BondLengthSet.getBondStdDev(index);
if ( sigma < 0 ) sigma = 0.1;
double distance = getBondLength(mol, a1, a2);
// allow 3 standard deviations for aromatic bonds and 1 for non-aromatic
return ( distance < bondLength + ( aromaticConstraintsUsed ? 3 : 1 ) * sigma);
}
/**
* Produce a numerical estimate of how well the bond length fits the statistical model
* constructed from previously observed bond parameter values. The estimate is provided
* as a deviation value from the idealised value (smaller values mean better fitness).
* Currently it only compares the bond length with the average bond length of the same
* type, but a more intricate model might be implemented in the future.
* @param mol
* The molecule that contains the bond.
* @param a1
* Index of the first atom that forms a bond.
* @param a2
* Index of the second atom that forms a bond.
* @param bondOrder
* Order of the bond. Currently only values '1' (single bond)
* and '2' (double bond) are supported.
* @return
* Bond length deviation from the previously observed bond parameter value.
*/
private static double estimatePiBondFitness(Molecule3D mol, int a1, int a2, int bondOrder) {
int atomType1 = Math.min(mol.getAtomicNo(a1), mol.getAtomicNo(a2));
int atomType2 = Math.max(mol.getAtomicNo(a1), mol.getAtomicNo(a2));
double singleBond = getIdealisedBondLength(1, atomType1, atomType2, 1, 1);
double doubleBond = getIdealisedBondLength(2, atomType1, atomType2, 1, 1);
boolean statisticsAvailable = singleBond != -1 || doubleBond != -1;
if (!statisticsAvailable) {
throw new RuntimeException("bond length statistics for bond '" + atomType1
+ "-" + atomType2 + "' is not available");
}
double idealLength = ( bondOrder == 1 ) ? singleBond : doubleBond;
if ( bondOrder == 2 && getBondLength(mol, a1, a2) < idealLength ) {
return 0;
}
return StrictMath.abs( getBondLength(mol, a1, a2) - idealLength );
}
private static boolean isAlkaliMetalAtom (Molecule3D mol, int atom) {
return ( mol.getAtomicNo(atom) == 3 ) ||
( mol.getAtomicNo(atom) == 11 ) ||
( mol.getAtomicNo(atom) == 19 ) ||
( mol.getAtomicNo(atom) == 37 ) ||
( mol.getAtomicNo(atom) == 55 ) ||
( mol.getAtomicNo(atom) == 87 );
}
private static boolean isAlkalineEarthMetalAtom(Molecule3D mol, int atom) {
return ( mol.getAtomicNo(atom) == 4 ) ||
( mol.getAtomicNo(atom) == 12 ) ||
( mol.getAtomicNo(atom) == 20 ) ||
( mol.getAtomicNo(atom) == 38 ) ||
( mol.getAtomicNo(atom) == 56 ) ||
( mol.getAtomicNo(atom) == 88 );
}
List checkMoleculeForBumps(Molecule3D mol) {
List bumps = new LinkedList<>();
double distance;
for (int i = 0; i < mol.getAllAtoms(); i++) {
for (int j = i+1; j < mol.getAllAtoms(); j++) {
if ( i < j ) {
distance = mol.getCoordinates(i).distance(mol.getCoordinates(j));
if ( distance < SPECIAL_POSITION_CUTOFF ||
distance < BUMP_FACTOR * ( COVALENT_RADIUS[mol.getAtomicNo(i)] +
COVALENT_RADIUS[mol.getAtomicNo(j)] ) ) {
bumps.add( "atoms '" + mol.getAtomName(i) + "' and '" + mol.getAtomName(j) + "' " +
"are too close (distance = " + String.format("%6.4f", distance) + ") and " +
"are considered a bump" );
}
}
}
}
return bumps;
}
private static boolean hasMetalLigandBonds(Molecule3D mol, int atom) {
return (mol.getMetalBondedConnAtoms(atom) > 0);
}
private static boolean isCommonIonCharge(int atomicNo, int charge) {
for ( int i = 0; i < Molecule.cCommonOxidationState[atomicNo].length; i++ ) {
if ( Molecule.cCommonOxidationState[atomicNo][i] == charge ) {
return true;
} else if ( Molecule.cCommonOxidationState[atomicNo][i] > charge ) {
return false;
}
}
return false;
}
private static boolean isCommonIonCharge(Molecule3D mol, int atomicNo, int charge) {
return isCommonIonCharge(mol.getAtomicNo(atomicNo), charge);
}
public static int getMaximumIonCharge(Molecule3D mol, int atom) {
if ( Molecule.cCommonOxidationState[mol.getAtomicNo(atom)] == null ) {
return 0;
} else {
return Molecule.cCommonOxidationState[mol.getAtomicNo(atom)]
[Molecule.cCommonOxidationState[mol.getAtomicNo(atom)].length - 1];
}
}
private static boolean maximumIonChargeReached(Molecule3D mol, int atom) {
return mol.getAtomCharge(atom) >= getMaximumIonCharge(mol, atom);
}
private static int getDelocalizedBondCount(Molecule3D mol, int atom) {
return getDelocalizedBonds(mol, atom).size();
}
private static List getDelocalizedBonds(Molecule3D mol, int atom) {
List delocalizedBonds = new LinkedList<>();
for( int i = 0; i < mol.getAllConnAtoms(atom); i++) {
if ( mol.getBondType(mol.getConnBond(atom, i)) == Molecule3D.cBondTypeDelocalized ) {
delocalizedBonds.add(mol.getConnBond(atom, i));
}
}
return delocalizedBonds;
}
private static List> getPiBondFragments (Molecule3D mol) {
List> fragments = new LinkedList<>();
mol.ensureHelperArrays(Molecule3D.cHelperRings);
for (int i = 0; i < mol.getAllAtoms(); i++) {
if (getDelocalizedBondCount(mol, i) == 1) {
// Check if the selected atom is not already at the end of analyzed fragments
boolean used = false;
for (List fragment1 : fragments) {
if (fragment1.get(fragment1.size() - 1) == i) {
used = true;
break;
}
}
if ( used ) { continue; }
List fragment = new LinkedList<>();
fragment.add(i);
int prevBond = getDelocalizedBonds(mol, i).get(0);
int atom;
if ( mol.getBondAtom( 0, prevBond ) != i ) {
atom = mol.getBondAtom( 0, prevBond );
} else {
atom = mol.getBondAtom( 1, prevBond );
}
boolean finished = false;
while (!finished) {
// Currently only process linear pi-bond systems
// TODO: extend the logic to process branched systems
if (getDelocalizedBondCount(mol, atom) > 2) {
break;
}
fragment.add(atom);
if (getDelocalizedBondCount(mol, atom) == 1) {
fragments.add(fragment);
finished = true;
} else {
int currentBond = ( getDelocalizedBonds(mol, atom).get(0) != prevBond ) ?
getDelocalizedBonds(mol, atom).get(0) :
getDelocalizedBonds(mol, atom).get(1);
if ( mol.getBondAtom( 0, currentBond ) != atom ) {
atom = mol.getBondAtom( 0, currentBond );
} else {
atom = mol.getBondAtom( 1, currentBond );
}
prevBond = currentBond;
}
}
}
}
return fragments;
}
private static boolean fragmentCanResonate(Molecule3D mol, List fragment) {
for (Integer atom : fragment) {
if (mol.getFreeValence(atom) - 1 < 0) {
return false;
}
}
return true;
}
private static int[] sortAtomsByBondLength(Molecule3D mol, int a, List bondAtoms) {
double[] bondLength = new double[bondAtoms.size()];
for (int i = 0; i < bondLength.length; i++ ) {
bondLength[i] = getBondLength(mol, a, mol.getConnAtom(a, bondAtoms.get(i)));
}
// using primitive bubble sort for now to sort the indexes
int[] indexes = new int[bondAtoms.size()];
for (int i = 0; i < bondLength.length; i++) {
indexes[i] = i;
}
for (int i = 0; i < bondLength.length; i++) {
for (int j = i+1; j < bondLength.length; j++ ) {
if( bondLength[indexes[i]] > bondLength[indexes[j]] ) {
int tmp = indexes[i];
indexes[i] = indexes[j];
indexes[j] = tmp;
}
}
}
// replacing sort indexes with atom indexes
for (int i = 0; i < bondLength.length; i++) {
indexes[i] = bondAtoms.get(indexes[i]);
}
return indexes;
}
private static Coordinates calculatePlaneNormal (Coordinates p1, Coordinates p2, Coordinates p3) {
Coordinates u = p1.subC(p2);
Coordinates v = p1.subC(p3);
return u.cross(v);
}
private static double calculatePointToPlaneDistance ( Coordinates normal, Coordinates p0, Coordinates p1 ) {
Coordinates v1 = p0.subC(p1);
return StrictMath.abs(normal.unitC().dot(v1) / v1.dist());
}
private static void setTerminalDoubleBonds( Molecule3D mol, int a, int[] sortedAtoms,
int maxDoubleBondCount ) {
int doubleBondCount = 0;
for (int sortedAtom : sortedAtoms) {
if (doubleBondCount < maxDoubleBondCount) {
mol.setBondOrder(mol.getConnBond(a, sortedAtom), 2);
doubleBondCount++;
} else {
mol.setBondOrder(mol.getConnBond(a, sortedAtom), 1);
mol.setAtomCharge(mol.getConnAtom(a, sortedAtom), -1);
}
}
}
// TODO: ask to add to the molecule
private static boolean isRingAtom(Molecule3D mol, int ring, int a) {
int[] ringAtoms = mol.getRingSet().getRingAtoms(ring);
for (int ringAtom : ringAtoms) {
if (ringAtom == a) return true;
}
return false;
}
private static void identifySquarates(Molecule3D mol, int[] spOrder) {
for (int i = 0; i < mol.getAtoms(); i++) {
if (mol.getAtomRingSize(i) != 4) continue;
if (getRingCount(mol, i) != 1) continue;
int ring = getSmallestRing(mol, i);
int[] ringAtoms = mol.getRingSet().getRingAtoms(ring);
boolean[] allowsExocyclicDoubleBond = new boolean[ringAtoms.length];
int[] exocyclicAtoms = new int[ringAtoms.length];
boolean fitsConstraints = true;
for (int j = 0; j < ringAtoms.length; j++) {
int a = ringAtoms[j];
if (mol.getAtomicNo(a) == 6 && mol.getConnAtoms(a) == 3 &&
spOrder[a] == 2 && mol.getLowestFreeValence(a) > 0) {
int a1 = ringAtoms[(j - 1 + ringAtoms.length) % ringAtoms.length];
int a2 = ringAtoms[(j + 1 + ringAtoms.length) % ringAtoms.length];
if (StrictMath.abs(90 - StrictMath.toDegrees(
getAngle(mol, a1, a, a2))) > 5) {
fitsConstraints = false;
break;
}
for (int k = 0; k < mol.getConnAtoms(a); k++) {
if (!isRingAtom(mol, ring, mol.getConnAtom(a, k))) {
allowsExocyclicDoubleBond[j] = (mol.getLowestFreeValence(mol.getConnAtom(a, k)) > 0);
exocyclicAtoms[j] = mol.getConnAtom(a, k);
break;
}
}
} else {
fitsConstraints = false;
break;
}
}
if (fitsConstraints) {
for (int a1 = 0; a1 < ringAtoms.length; a1++) {
int a2 = (a1 + 1) % ringAtoms.length;
int a3 = (a1 + 2) % ringAtoms.length;
int a4 = (a1 + 3) % ringAtoms.length;
if (allowsExocyclicDoubleBond[a1] && allowsExocyclicDoubleBond[a2]) {
mol.setBondOrder(mol.getBond(ringAtoms[a1], exocyclicAtoms[a1]), 2);
mol.setBondOrder(mol.getBond(ringAtoms[a2], exocyclicAtoms[a2]), 2);
mol.setBondOrder(mol.getBond(ringAtoms[a3], ringAtoms[a4]), 2);
if (allowsExocyclicDoubleBond[a3]) mol.setAtomCharge(exocyclicAtoms[a3], -1);
if (allowsExocyclicDoubleBond[a4]) mol.setAtomCharge(exocyclicAtoms[a4], -1);
break;
}
}
}
}
}
private static int getRingCount(Molecule3D mol, int a) {
int ringCount = 0;
RingCollection rings = mol.getRingSet();
for (int i = 0; i < mol.getRingSet().getSize(); i++) {
int[] ringAtoms = rings.getRingAtoms(i);
for (int ringAtom : ringAtoms) {
if (ringAtom == a) {
ringCount++;
break;
}
}
}
return ringCount;
}
private static int getSmallestRing(Molecule3D mol, int a) {
int smallestRing = -1;
int ringSize = -1;
RingCollection rings = mol.getRingSet();
for (int i = 0; i < mol.getRingSet().getSize(); i++) {
int[] ringAtoms = rings.getRingAtoms(i);
for (int ringAtom : ringAtoms) {
if (ringAtom == a) {
if (ringSize == -1 || ringAtoms.length < ringSize) {
ringSize = ringAtoms.length;
smallestRing = i;
break;
}
}
}
}
return smallestRing;
}
private static int getAllConnAtoms(Molecule3D mol, int a, boolean includeAttachedHydrogens) {
if ( includeAttachedHydrogens ) {
return mol.getAllConnAtoms(a) + mol.getAttachedHydrogenCount(a);
} else {
return mol.getAllConnAtoms(a);
}
}
private static boolean isRingCoordinated(Molecule3D mol, int ringNo) {
int[] ringAtoms = mol.getRingSet().getRingAtoms(ringNo);
boolean atomsAreCoordinated = true;
for (int ringAtom : ringAtoms) {
atomsAreCoordinated = atomsAreCoordinated && hasMetalLigandBonds(mol, ringAtom);
}
return atomsAreCoordinated;
}
/**
* Determines if the given atom is capable of accommodating an additional double bond.
* @param mol
* Molecule that contains the atom.
* @param atom
* Index of the atom inside the molecule data structure.
* @param atomPi
* The number of pi electrons that the atom is currently using.
* @param spOrder
* The sp order (1, 2, 3) of the atom.
* @return
* Logical value denoting is an atom can accommodate an additional double bond.
*/
private static boolean canAccommodateDoubleBond(Molecule3D mol, int atom, int atomPi, int spOrder) {
// Tetravalent silicon (Si) cannot accommodate any double bonds
if (mol.getAtomicNo(atom) == 14 && mol.getOccupiedValence(atom) > 3) return false;
// Germanium (Ge)
if (mol.getAtomicNo(atom) == 32) {
// only sp3 germanium (Ge) can potentially accommodate a double bond
if (mol.getOccupiedValence(atom) > 3) return false;
if (spOrder != 2) return false;
}
// Phosphorus (P)
if (mol.getAtomicNo(atom) == 15) {
if (atomPi > 0 && spOrder != 1) return false;
}
if ( mol.getLowestFreeValence(atom) < 1 ) return false;
if ( mol.getAtomCharge(atom) != 0 ) return false;
// Sp1-like atoms are not allowed to have more than 2 pi electrons
if ( spOrder == 1 && atomPi > 1 ) return false;
// Sp2-like atoms are not allowed to have more than 1 pi electron
if ( spOrder == 2 && atomPi > 0 ) return false;
// Atoms smaller than phosphorus are not allowed a double bond in Sp3-like configuration
if ( spOrder == 3 && mol.getAtomicNo(atom) < 14 ) return false;
return true;
}
/**
* Calculates the bond length deviation from the statistical
* average for the bond assuming it is of the given bond type.
* @param mol
* Molecule that contains the bond.
* @param bond
* The bond that is being evaluated.
* @param bondTypeIndex
* The bond type index that is used to retrieve
* the average bond length and standard deviation
* for the given bond type.
* @return
* The difference between the actual and the average
* bond lengths.
*/
private static double determineExpectedBondDeviation(Molecule3D mol, int bond, int bondTypeIndex) {
return ( getBondLength(mol, bond ) - BondLengthSet.getBondLength(bondTypeIndex));
}
/**
* Calculates the angle a1-a2-a3 between 3 atoms.
* @param mol
* The molecule that contains the atoms.
* @param a1
* Index of the first atom in the molecule.
* @param a2
* Index of the second atom in the molecule.
* @param a3
* Index of the third atom in the molecule.
* @return
* The angle a1-a2-a3 in radians.
*/
private static double getAngle(Molecule mol, int a1, int a2, int a3) {
Coordinates c1 = mol.getCoordinates(a1);
Coordinates c2 = mol.getCoordinates(a2);
Coordinates c3 = mol.getCoordinates(a3);
return c1.subC(c2).getAngle(c3.subC(c2));
}
/**
* Calculates the dihedral angle a1-a2-a3-a4 between 4 atoms.
* @param mol
* The molecule that contains the atoms.
* @param a1
* Index of the first atom in the molecule.
* @param a2
* Index of the second atom in the molecule.
* @param a3
* Index of the third atom in the molecule.
* @param a4
* Index of the fourth atom in the molecule.
* @return
* The dihedral angle a1-a2-a3-a4 in radians.
*/
private static double getDihedral(Molecule mol, int a1, int a2, int a3, int a4) {
Coordinates c1 = mol.getCoordinates(a1);
Coordinates c2 = mol.getCoordinates(a2);
Coordinates c3 = mol.getCoordinates(a3);
Coordinates c4 = mol.getCoordinates(a4);
return c1.getDihedral(c2, c3, c4);
}
public static int connected(StereoMolecule mol, int a, int atomicNo, int bondOrder) {
for(int i=0; i=0 && mol.getAtomicNo(atm)!=atomicNo) continue;
if(bondOrder>0 && mol.getConnBondOrder(a, i)!=bondOrder) continue;
return atm;
}
return -1;
}
public static boolean aromatize(Molecule3D mol, Set aromaticAtoms, Set aromaticBonds) {
ArrayList[] atomToRings = getAtomToRings(mol);
RingCollection ringSet = mol.getRingSetSimple();
return aromatize(mol,atomToRings,ringSet,aromaticAtoms,aromaticBonds);
}
public static boolean aromatize(Molecule3D mol, ArrayList[] atomToRings, RingCollection ringSet, Set aromaticAtoms, Set aromaticBonds) {
//RingCollection ringSet = mol.getRingSetSimple();
//Flag the aromatic rings
boolean[] aromaticRings = new boolean[ringSet.getSize()];
for (int i = 0; i < ringSet.getSize(); i++) {
//Is is an aromatic ring
boolean isAromatic = true;
int ringSize = ringSet.getRingSize(i);
for (int j = -1; j < ringSize; j++) {
int[] ringAtom = ringSet.getRingAtoms(i);
int a1 = j==-1? ringAtom[ringSize-1]: ringAtom[j];
int a2 = j==ringSize-1? ringAtom[0]: ringAtom[j+1];
int b = mol.getBond(a1, a2);
if(!aromaticBonds.contains(b)) {
isAromatic = false;
}
}
aromaticRings[i] = isAromatic;
}
Set nonAromaticAtoms = new HashSet();
for (int i=0;i(), true);
if(!success) success = aromatize(mol, atomToRings, ringSet, i, aromaticRings, nonAromaticAtoms, new boolean[mol.getAllAtoms()], 0, ringSet.getRingSize(i)%2, new ArrayList(), false);
if(!success) {
System.out.println("Could not aromatize ring "+i);
aromaticRings[i] = false;
ok = false;
}
}
}
return ok;
}
private static boolean aromatize(Molecule3D mol, ArrayList[] atomToRings, RingCollection ringSet, int index, boolean[] aromatic, Set nonAromaticAtoms, boolean[] visited, int seed, int left, List bondsMade, boolean easy) {
//RingCollection ringSet = mol.getRingSetSimple();
//Ends if the ring has been fully visited
int[] ring = (int[]) ringSet.getRingAtoms(index);
boolean allVisited = true;
int bestSeed = -1;
for (int i = 0; i < ring.length; i++) {
if(!visited[ring[(seed + i)%ring.length]]) {
if(bestSeed<0) bestSeed = seed + i;
allVisited = false;
}
}
if(allVisited) {
return true;
} else {
seed = bestSeed;
}
int a = ring[seed%ring.length];
int ap = ring[(seed+1)%ring.length];
if(visited[a]) { //already treated
return aromatize(mol, atomToRings, ringSet, index, aromatic, nonAromaticAtoms, visited, seed+1, left, bondsMade, easy);
} else {
//Try to create a double bond from the atom a to a connected one
for (int j = -1; j < mol.getAllConnAtoms(a); j++) {
int a2 = j==-1? ap: mol.getConnAtom(a, j);
if(visited[a2]) continue;
if(j>=0 && a2==ap) continue;
if(nonAromaticAtoms.contains(a2)) continue;
if(nonAromaticAtoms.contains(a)) continue;
if(mol.getAtomicNo(a)==8 || mol.getAtomicNo(a)==16) continue;
if(mol.getAtomicNo(a2)==8 || mol.getAtomicNo(a2)==16) continue;
if(easy && mol.getFreeValence(a) <= 0) continue;
if(easy && mol.getFreeValence(a2) <= 0) continue;
if(connected(mol, a, -1, 2)>=0) continue;
if(connected(mol, a2, -1, 2)>=0) continue;
visited[a] = visited[a2] = true;
int b = mol.getBond(a, a2);
mol.setBondOrder(b, 2);
//Test whole ring
List trackBondsMade = new ArrayList();
boolean success = aromatize(mol, atomToRings, ringSet, index, aromatic, nonAromaticAtoms, visited, seed+1, left, trackBondsMade, easy);
//Test connecting rings
if(success) {
List rings = atomToRings[a2];
if(rings.size()>1) {
for (int r : rings) {
if(r!=index && r>=0 && r " +success+" "+trackBondsMade.size());
if(!success) break;
}
}
}
}
if(success) {
//It works!!!
bondsMade.add(b);
bondsMade.addAll(trackBondsMade);
return true;
} else {
//Backtrack changes
visited[a] = visited[a2] = false;
mol.setBondOrder(b, 1);
for (int b2 : trackBondsMade) {
//System.out.println("retrack "+mol.getBondAtom(0, b2)+"-"+mol.getBondAtom(1, b2));
mol.setBondOrder(b2, 1);
visited[mol.getBondAtom(0, b2)] = visited[mol.getBondAtom(1, b2)] = false;
}
}
}
//Try to skip this atom
if(left>0 && (mol.getAtomicNo(a)!=6)) {
visited[a] = true;
List trackBondsMade = new ArrayList();
boolean success = aromatize(mol, atomToRings, ringSet, index, aromatic, nonAromaticAtoms, visited, seed+1, left-1, trackBondsMade, easy);
if(success) {
bondsMade.addAll(trackBondsMade);
return true;
} else {
visited[a] = false;
for (int b2 : trackBondsMade) {
mol.setBondOrder(b2, 1);
visited[mol.getBondAtom(0, b2)] = visited[mol.getBondAtom(1, b2)] = false;
}
}
}
return false;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy