org.xmlcml.cml.tools.AtomTool Maven / Gradle / Ivy
/**
* Copyright 2011 Peter Murray-Rust et. al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmlcml.cml.tools;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import nu.xom.Attribute;
import nu.xom.Element;
import nu.xom.Nodes;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.xmlcml.cml.base.CMLConstants;
import org.xmlcml.cml.base.CMLElements;
import org.xmlcml.cml.base.CMLElement.CoordinateType;
import org.xmlcml.cml.base.CMLElement.FormalChargeControl;
import org.xmlcml.cml.base.CMLElement.Hybridization;
import org.xmlcml.cml.element.CMLAtom;
import org.xmlcml.cml.element.CMLAtomSet;
import org.xmlcml.cml.element.CMLBond;
import org.xmlcml.cml.element.CMLLabel;
import org.xmlcml.cml.element.CMLMolecule;
import org.xmlcml.cml.element.CMLSymmetry;
import org.xmlcml.cml.element.CMLTransform3;
import org.xmlcml.cml.element.CMLMolecule.HydrogenControl;
import org.xmlcml.cml.graphics.CMLDrawable;
import org.xmlcml.cml.graphics.SVGElement;
import org.xmlcml.cml.graphics.SVGG;
import org.xmlcml.euclid.Angle;
import org.xmlcml.euclid.EuclidRuntimeException;
import org.xmlcml.euclid.Point3;
import org.xmlcml.euclid.Point3Vector;
import org.xmlcml.euclid.Real2;
import org.xmlcml.euclid.Transform2;
import org.xmlcml.euclid.Transform3;
import org.xmlcml.euclid.Vector2;
import org.xmlcml.euclid.Vector3;
import org.xmlcml.euclid.Angle.Range;
import org.xmlcml.molutil.ChemicalElement;
import org.xmlcml.molutil.Molutils;
import org.xmlcml.molutil.ChemicalElement.AS;
/**
* additional tools for atom. not fully developed
*
* @author pmr
*
*/
public class AtomTool extends AbstractSVGTool {
final static Logger LOG = Logger.getLogger(AtomTool.class.getName());
static {
LOG.setLevel(Level.DEBUG);
}
public final static String RIGHT = "RIGHT__";
public final double fontWidthFontSizeFactor = 0.8;
private static final Transform2 ROT90 = new Transform2(new Angle(Math.PI/2.));
private CMLAtom atom;
private CMLMolecule molecule;
private MoleculeTool moleculeTool;
private MoleculeDisplay moleculeDisplay;
private AtomDisplay atomDisplay;
private List coordinationSphereList;
private CMLAtomSet coordinationSphereSet;
private double fontSize;
// private String fontFamily;
// private String fontStyle;
// private String fontWeight;
private double radiusFactor = 1.0;
/**
* constructor
*
* @param atom
* @deprecated use getOrCreateTool
*/
public AtomTool(CMLAtom atom) {
if (atom == null) {
throw new RuntimeException("null atom");
}
this.atom = atom;
this.atom.setTool(this);
setDefaults();
molecule = atom.getMolecule();
if (molecule == null) {
throw new RuntimeException("Atom must be in molecule: "+atom.getId());
}
}
/** gets AtomTool associated with atom.
* if null creates one and sets it in atom
* @param atom
* @return tool
*/
public static AtomTool getOrCreateTool(CMLAtom atom) {
AtomTool atomTool = (AtomTool) atom.getTool();
if (atomTool == null) {
atomTool = new AtomTool(atom);
atom.setTool(atomTool);
}
return atomTool;
}
private void setDefaults() {
// this.ensureAtomDisplay();
// this.ensureMoleculeDisplay();
}
/**
*
* @param reaction
* @return
*/
public static AbstractSVGTool getOrCreateSVGTool(CMLAtom atom) {
return (AbstractSVGTool) AtomTool.getOrCreateTool(atom);
}
/** sort list of atoms by atomic number.
* TODO this is all incomplete..
* @param atomList of atoms
* @return sorted list
*/
public static List sortListByAtomicNumber(List atomList) {
List newAtomList = new ArrayList(atomList);
List sortedList = new ArrayList();
while (newAtomList.size() > 0) {
int heaviestAtomicNum = -1;
CMLAtom heaviestAtom = null;
for (int i = 0; i < newAtomList.size(); i++) {
CMLAtom atom = newAtomList.get(i);
int atomicNumber = atom.getAtomicNumber();
if (atomicNumber > heaviestAtomicNum) {
heaviestAtom = atom;
heaviestAtomicNum = atomicNumber;
} else if (atomicNumber == heaviestAtomicNum) {
// we've got two
// atoms of the
// same
// weight... //
// heaviestAtom.compareWiv(atom);
}
}
sortedList.add(heaviestAtom);
newAtomList.remove(heaviestAtom);
}
return sortedList;
}
/**
* lazy evaluation
* @param depth 0 = atom itself, 1 = first coord sphere...
*/
private void ensureCoordinationSphereList(int depth) {
if (coordinationSphereList == null) {
coordinationSphereList = new ArrayList();
CMLAtomSet atomSet = new CMLAtomSet();
atomSet.addAtom(atom);
coordinationSphereList.add(atomSet);
}
if (coordinationSphereList.size() < depth+1) {
ensureCoordinationSphereList(depth-1);
CMLAtomSet nextLigandSphereSet = new CMLAtomSet();
coordinationSphereList.add(nextLigandSphereSet);
CMLAtomSet outerAtomSet = coordinationSphereList.get(depth-1);
List outerAtomList = outerAtomSet.getAtoms();
CMLAtomSet innerAtomSet = (depth <= 1) ? null : coordinationSphereList.get(depth-1);
for (CMLAtom rootAtom : outerAtomList) {
List ligandList = rootAtom.getLigandAtoms();
for (CMLAtom ligand : ligandList) {
// atom already in outer sphere
if (outerAtomSet.contains(ligand)) {
continue;
}
// or in inner sphere?
if (innerAtomSet != null && innerAtomSet.contains(ligand)) {
continue;
}
// form next sphere
nextLigandSphereSet.addAtom(ligand);
}
coordinationSphereSet = null;
}
}
}
/**
* @param depth 0 = atom itself, 1 = first coord sphere...
* @return the coordinationSphereList
*/
public List getCoordinationSphereList(int depth) {
ensureCoordinationSphereList(depth);
return coordinationSphereList;
}
/**
* reset to null
*/
public void resetCoordinationSphereList() {
this.coordinationSphereList = null;
}
/**
* always recalculated
* @param depth 0 = atom itself, 1 = first coord sphere...
* @return the coordinationSphereList
*/
public CMLAtomSet getCoordinationSphereSet(int depth) {
ensureCoordinationSphereList(depth);
// always recalculate - slower but safer
coordinationSphereSet = new CMLAtomSet();
for (int iSphere = 0; iSphere <= depth; iSphere++) {
CMLAtomSet atomSet = coordinationSphereList.get(iSphere);
coordinationSphereSet.addAtomSet(atomSet);
}
return coordinationSphereSet;
}
/** gets 3D coordinates in ordered list.
*
* @param atomList
* @return
*/
public static Point3Vector getPoint3Vector(List atomList) {
Point3Vector p3v = new Point3Vector();
for (CMLAtom atom : atomList) {
Point3 p3 = (atom == null) ? null : atom.getXYZ3();
if (p3 == null) {
// throw new RuntimeException("Missing 3D coordinates");
}
p3v.add(p3);
}
return p3v;
}
/**
* Computes a ligand list and sorts the ligands in it by atomic number if
* two ligands have the same atomic number their order is unchanged.
*
* @return a sorted ligand list.
*/
/*
* //TODO this is all incomplete..
*/
public List getSortedLigandList() {
List sortedLigandList = new ArrayList();
List ligandList = atom.getLigandAtoms();
while (ligandList.size() > 0) {
int heaviestAtomicNum = -1;
CMLAtom heaviestAtom = null;
for (int i = 0; i < ligandList.size(); i++) {
CMLAtom atom = ligandList.get(i);
int atomicNumber = atom.getAtomicNumber();
if (atomicNumber > heaviestAtomicNum) {
heaviestAtom = atom;
heaviestAtomicNum = atomicNumber;
}
}
sortedLigandList.add(heaviestAtom);
ligandList.remove(heaviestAtom);
}
return sortedLigandList;
}
/** convenience method to get single atom ligand.
* useful for R groups know to have single attachment
* @return the single atom (null if zero or > 1 ligands
*/
public CMLAtom getSingleLigand() {
List ligands = atom.getLigandAtoms();
return (ligands.size() != 1) ? null : ligands.get(0);
}
/** gets sphere of atoms - topological depth
*
* @param depth
* @return atom set
*/
public CMLAtomSet getSproutedSet(int depth) {
CMLAtomSet atomSet = new CMLAtomSet();
for (int d = 0; d < depth; d++) {
//
}
return atomSet;
}
/**
* append to id creates new id, perhaps to disambiguate
*
* @param atom
* @param s
*/
public void appendToId(final String s) {
String id = atom.getId();
if ((id != null) && (id.length() > 0)) {
atom.renameId(id + s);
} else {
atom.renameId(s);
}
}
/**
* Compares this atom to another atom using simple CIP prioritization, if
* both atoms are the same it then looks at both atoms ligands and recurses
* until a mismatch is found.
*
* if thisAtom has higher priority return 1 if thisAtom and otherAtom have
* equal priority return 0 if otherAtom has higher priority return -1
*
* @param otherAtom
* CMLAtom to compare this atom with
* @return the comparision
*/
/*
* //TODO this is all incomplete..
*/
public int recursiveCompare(CMLAtom otherAtom) {
AtomTool otherAtomTool = AtomTool.getOrCreateTool(otherAtom);
int thisAtomicNumber = atom.getAtomicNumber();
int otherAtomicNumber = otherAtom.getAtomicNumber();
if (thisAtomicNumber < otherAtomicNumber) {
return -1;
} else if (thisAtomicNumber > otherAtomicNumber) {
return 1;
} else {
// okay, so both atoms are the
// same - lets compare their ligands (step along tree once..)
List thisAtomsLigands = this.getSortedLigandList();
List otherAtomsLigands = otherAtomTool.getSortedLigandList();
int length = Math.min(thisAtomsLigands.size(), otherAtomsLigands
.size());
for (int i = 0; i < length; i++) {
int compareResult = AtomTool.getOrCreateTool(thisAtomsLigands.get(i)).recursiveCompare(
otherAtomsLigands.get(i));
if (compareResult != 0) { // we've found a mismatch here,
// return the result
return compareResult;
}
}
return 0;
// still no mismatch, so far identical atoms.
}
}
// ---------------------to be looked at:-----------------------------------------
// these functions seem to deal mostly with chirality (& associated)
// there also appears to be a lot of duplicate functionality
/**
* add calculated coordinates for hydrogens.
*
* @param control 2D or 3D
*/
public void addCalculatedCoordinatesForHydrogens(CoordinateType type, double bondLength) {
if (CoordinateType.TWOD.equals(type)) {
calculateAndAddHydrogenCoordinates(bondLength);
} else if (CoordinateType.CARTESIAN.equals(type)) {
throw new RuntimeException("CARTESIAN H coords nyi");
} else {
throw new RuntimeException("THREED H coords nyi");
}
}
public void calculateAndAddHydrogenCoordinates(double bondLength) {
List ligandHydrogenList = atom.getLigandHydrogenAtoms();
List ligandList = atom.getLigandAtoms();
List nonHydrogenLigandHydrogenList = new ArrayList();
for (CMLAtom ligand : ligandList) {
if (!AS.H.equals(ligand.getElementType())) {
nonHydrogenLigandHydrogenList.add(ligand);
}
}
int hydrogenCount = atom.getHydrogenCount();
if (hydrogenCount != ligandHydrogenList.size()) {
atom.debug("HC "+hydrogenCount+" "+ligandHydrogenList.size());
throw new RuntimeException("inconsistent hydrogen count in add coordinates for atom "+atom.getId());
}
List vectorList = new ArrayList();
try {
vectorList = addCoords(nonHydrogenLigandHydrogenList, ligandHydrogenList, bondLength);
} catch (Exception e) {
LOG.error("Cannot add Hydrogen ", e);
}
if (vectorList.size() == 0) {
} else if (vectorList.size() != ligandHydrogenList.size()) {
LOG.error("vectorList ("+vectorList.size()+") != ligandHydrogenList ("+ligandHydrogenList.size()+")");
} else {
Real2 xy2 = atom.getXY2();
for (int i = 0; i < ligandHydrogenList.size(); i++) {
ligandHydrogenList.get(i).setXY2(xy2.plus(vectorList.get(i)));
}
}
}
private static Transform2 PI120 = new Transform2(new Angle(Math.PI * 2./3.));
private static Transform2 PI90 = new Transform2(new Angle(Math.PI * 0.5));
private static Transform2 PI270 = new Transform2(new Angle(Math.PI * 1.5));
private List addCoords(List ligandList, List hydrogenList, double bondLength) {
List vectorList = new ArrayList();
if (hydrogenList.size() == 0) {
// nothing to do
} else if (ligandList.size() == 0) {
if (hydrogenList.size() == 1) {
vectorList.add(new Vector2(0, bondLength));
} else if (hydrogenList.size() == 2) {
vectorList.add(new Vector2(0, bondLength));
vectorList.add(new Vector2(0, -bondLength));
} else if (hydrogenList.size() == 3) {
vectorList.add(new Vector2(0, bondLength));
vectorList.add(new Vector2(bondLength * Math.sqrt(0.75), -bondLength *0.5));
vectorList.add(new Vector2(-bondLength * Math.sqrt(0.75), -bondLength *0.5));
} else if (hydrogenList.size() == 4) {
vectorList.add(new Vector2(0, bondLength));
vectorList.add(new Vector2(0, -bondLength));
vectorList.add(new Vector2(bondLength, 0));
vectorList.add(new Vector2(-bondLength, 0));
}
} else if (ligandList.size() == 1) {
Vector2 ligandVector = new Vector2(ligandList.get(0).getXY2().subtract(atom.getXY2()));
ligandVector = new Vector2(ligandVector.getUnitVector().multiplyBy(-bondLength));
if (hydrogenList.size() == 1) {
vectorList.add(new Vector2(ligandVector));
} else if (hydrogenList.size() == 2) {
Vector2 vector = new Vector2(ligandVector.multiplyBy(-1.0));
vector.transformBy(PI120);
vectorList.add(new Vector2(vector));
vector.transformBy(PI120);
vectorList.add(new Vector2(vector));
} else if (hydrogenList.size() == 3) {
Vector2 vector = new Vector2(ligandVector);
vectorList.add(new Vector2(vector));
vector.transformBy(PI90);
vectorList.add(new Vector2(vector));
vector = new Vector2(ligandVector);
vector.transformBy(PI270);
vectorList.add(new Vector2(vector));
} else {
}
} else if (ligandList.size() == 2) {
Vector2 ligandVector0 = new Vector2(ligandList.get(0).getXY2().subtract(atom.getXY2()));
ligandVector0 = new Vector2(ligandVector0.getUnitVector());
Vector2 ligandVector1 = new Vector2(ligandList.get(1).getXY2().subtract(atom.getXY2()));
ligandVector1 = new Vector2(ligandVector1.getUnitVector());
Angle angle = ligandVector0.getAngleMadeWith(ligandVector1);
angle.setRange(Angle.Range.SIGNED);
Vector2 bisectVector = null;
boolean nearlyLinear = Math.abs(angle.getRadian()) > 0.9 * Math.PI;
if (nearlyLinear) {
bisectVector = new Vector2(ligandVector0.getUnitVector());
bisectVector.transformBy(ROT90);
bisectVector.multiplyBy(bondLength);
} else {
bisectVector = new Vector2(ligandVector0.plus(ligandVector1));
bisectVector = new Vector2(bisectVector.getUnitVector());
bisectVector = new Vector2(bisectVector.multiplyBy(-bondLength));
}
if (hydrogenList.size() == 1) {
Vector2 vector = new Vector2(bisectVector);
vector = new Vector2(vector.multiplyBy(1.0));
vectorList.add(vector);
} else if (hydrogenList.size() == 2) {
if (nearlyLinear) {
vectorList.add(new Vector2(bisectVector));
vectorList.add(new Vector2(bisectVector.multiplyBy(-1.)));
} else {
Angle halfAngle = new Angle(Math.PI*0.5 - Math.abs(angle.getRadian()*0.5));
Transform2 t2 = new Transform2(halfAngle);
Vector2 vector = new Vector2(bisectVector);
vector.transformBy(t2);
vectorList.add(vector);
t2 = new Transform2(halfAngle.multiplyBy(-1.0));
vector = new Vector2(bisectVector);
vector.transformBy(t2);
vectorList.add(vector);
}
} else {
}
} else if (ligandList.size() == 3) {
Vector2[] vectors = new Vector2[3];
Vector2 bisectVector = null;
for (int i = 0; i < 3; i++) {
vectors[i] = new Vector2(ligandList.get(i).getXY2().subtract(atom.getXY2()));
bisectVector = (bisectVector == null) ? vectors[i] : new Vector2(bisectVector.plus(vectors[i]));
}
bisectVector = new Vector2(bisectVector.multiplyBy(-1.0));
// short vector
try {
bisectVector = new Vector2(bisectVector.getUnitVector().multiplyBy(vectors[0].getLength()*0.7));
// must not overlap too badly
for (int i = 0; i < 3; i++) {
Angle angle = bisectVector.getAngleMadeWith(vectors[i]);
angle.setRange(Range.SIGNED);
double angleR = Math.abs(angle.getRadian());;
if (angleR < 0.2) {
bisectVector = new Vector2(vectors[(i+1) % 3]);
bisectVector = new Vector2(bisectVector.multiplyBy(-1.0));
break;
}
}
} catch (EuclidRuntimeException e) {
bisectVector = vectors[0];
bisectVector = new Vector2(bisectVector);
}
if (hydrogenList.size() == 1) {
vectorList.add(new Vector2(bisectVector));
} else {
}
} else {
// skip
}
return vectorList;
}
/**
* Add or delete hydrogen atoms to satisy valence.
* ignore if hydrogenCount attribute is set
* Uses algorithm: nH = 8 - group - sumbondorder + formalCharge, where group
* is 0-8 in first two rows
*
* @param atom
* @param control specifies whether H are explicit or in hydrogenCount
*/
public void adjustHydrogenCountsToValency(HydrogenControl control) {
if (atom.getHydrogenCountAttribute() == null) {
AtomTool atomTool = AtomTool.getOrCreateTool(atom);
int group = atomTool.getHydrogenValencyGroup();
// these states cannot have hydrogen
if (group == -1) {
return;
} else if (group == -2) {
return;
}
// hydrogen and metals
if (group < 4) {
return;
}
int sumBo = atomTool.getSumNonHydrogenBondOrder();
int fc = (atom.getFormalChargeAttribute() == null) ? 0 :
atom.getFormalCharge();
int nh = 8 - group - sumBo + fc;
// non-octet species
if (group == 4 && fc == 1) {
nh -= 2;
}
// negative counts are meaningless
if (nh < 0) {
nh = 0;
}
atom.setHydrogenCount(nh);
}
this.expandImplicitHydrogens(control);
}
/**
* Expand implicit hydrogen atoms.
*
* This needs looking at
*
* CMLMolecule.NO_EXPLICIT_HYDROGENS
* CMLMolecule.USE_HYDROGEN_COUNT // no
* action
*
* @param atom
* @param control
* @throws RuntimeException
*/
public void expandImplicitHydrogens(HydrogenControl control) throws RuntimeException {
if (HydrogenControl.USE_HYDROGEN_COUNT.equals(control)) {
return;
}
if (atom.getHydrogenCountAttribute() == null
|| atom.getHydrogenCount() == 0) {
return;
}
int hydrogenCount = atom.getHydrogenCount();
int currentHCount = 0;
List ligandList = atom.getLigandAtoms();
for (CMLAtom ligand : ligandList) {
if (ligand.getElementType().equals(AS.H.value)) {
currentHCount++;
}
}
// FIXME. This needs rethinking
if (HydrogenControl.NO_EXPLICIT_HYDROGENS.equals(control) && currentHCount != 0) {
return;
}
String id = atom.getId();
for (int i = 0; i < hydrogenCount - currentHCount; i++) {
CMLAtom hatom = new CMLAtom(id + "_h" + (i + 1));
molecule.addAtom(hatom);
hatom.setElementType(AS.H.value);
CMLBond bond = new CMLBond(atom, hatom);
molecule.addBond(bond);
bond.setOrder(CMLBond.SINGLE_S);
}
}
/**
* Sums the formal orders of all bonds from atom to non-hydrogen ligands.
*
* Uses 1,2,3,A orders and creates the nearest integer. Thus 2 aromatic
* bonds sum to 3 and 3 sum to 4. Bonds without order are assumed to be
* single
* @param molecule
*
* @param atom
* @exception RuntimeException
* null atom in argument
* @return sum of bond orders. May be 0 for isolated atom or atom with only
* H ligands
*/
public int getSumNonHydrogenBondOrder() throws RuntimeException {
float sumBo = 0.0f;
List ligandList = atom.getLigandAtoms();
List ligandBondList = atom.getLigandBonds();
for (int i = 0; i < ligandList.size(); i++) {
CMLAtom ligand = ligandList.get(i);
if (AS.H.equals(ligand.getElementType())) {
continue;
}
CMLBond bond = ligandBondList.get(i);
String bo = bond.getOrder();
if (bo != null) {
if (CMLBond.isSingle(bo)) {
sumBo += 1.0;
}
if (CMLBond.isDouble(bo)) {
sumBo += 2.0;
}
if (CMLBond.isTriple(bo)) {
sumBo += 3.0;
}
if (bo.equals(CMLBond.AROMATIC)) {
sumBo += 1.4;
}
} else {
// if no bond order, assume single
sumBo += 1.0;
}
}
return Math.round(sumBo);
}
/** gets lone electrons on atom.
* electrons not involved in bonding
* assumes accurate hydrogen count
* only currently really works for first row (C, N, O, F)
* calculated as getHydrogenValencyGroup() -
* (getSumNonHydrogenBondOrder() + getHydrogenCount()) - atom.getFormalCharge()
* @throws RuntimeException
* @return number of lone electrons (< 0 means cannot calculate)
*/
public int getLoneElectronCount() {
AtomTool atomTool = AtomTool.getOrCreateTool(atom);
int group = atomTool.getHydrogenValencyGroup();
if (group == -1) {
return -1;
}
int sumNonHBo = atomTool.getSumNonHydrogenBondOrder();
int nHyd = atom.getHydrogenCount();
int loneElectronCount = group - (sumNonHBo + nHyd) - atom.getFormalCharge();
return loneElectronCount;
}
static String[] elems = {AS.H.value, AS.C.value, AS.N.value, AS.O.value, AS.F.value, AS.Si.value, AS.P.value, AS.S.value, AS.Cl.value, AS.Br.value, AS.I.value};
static int[] group = { 1, 4, 5, 6, 7, 4, 5, 6, 7, 7, 7};
static int[] eneg0 = { 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1};
static int[] eneg1 = { 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1};
/** a simple lookup for common atoms.
*
* examples are C, N, O, F, Si, P, S, Cl, Br, I
* if atom has electronegative ligands, (O, F, Cl...) returns -1
*
*/
public int getHydrogenValencyGroup() {
int elNum = -1;
try {
String elType = atom.getElementType();
elNum = getElemNumb(elType);
if (elNum == -1) {
return -1;
}
if (eneg0[elNum] == 0) {
return group[elNum];
}
List ligands = atom.getLigandAtoms();
// if atom is susceptible to enegative ligands, exit if they are present
for (CMLAtom ligand : ligands) {
int ligElNum = getElemNumb(ligand.getElementType());
if (ligElNum == -1 || eneg1[ligElNum] == 1) {
return -2;
}
}
} catch (Exception e) {
LOG.error("BUG "+e);
}
int g = (elNum == -1) ? -1 : group[elNum];
return g;
}
private int getElemNumb(String elemType) {
for (int i = 0; i < elems.length; i++) {
if (elems[i].equals(elemType)) {
return i;
}
}
return -1;
}
/**
* Contracts the hydrogens on an atom.
* actually deletes them
*
* @param moleculeTool TODO
* @param control
*/
public void contractExplicitHydrogens(HydrogenControl control) {
int hCount = (atom.getHydrogenCountAttribute() == null) ? 0 : atom.getHydrogenCount();
Set hSet = ChemicalElement.getElementSet(
new String[] { AS.H.value });
List ligands = CMLAtom.filter(atom.getLigandAtoms(), hSet);
CMLMolecule molecule = atom.getMolecule();
for (CMLAtom ligand : ligands) {
molecule.deleteAtom(ligand);
}
atom.setHydrogenCount(Math.max(hCount, ligands.size()));
}
/**
* Adds 3D coordinates for singly-bonded ligands of this.
*
*
* this is labelled A.
* Initially designed for hydrogens. The ligands of A are identified
* and those with 3D coordinates used to generate the new points. (This
* allows structures with partially known 3D coordinates to be used, as when
* groups are added.)
* "Bent" and "non-planar" groups can be formed by taking a subset of the
* calculated points. Thus R-NH2 could use 2 of the 3 points calculated
* from (1,iii)
* nomenclature: "this" is point to which new ones are "attached".
* this may have ligands B, C...
* B may have ligands J, K..
* points X1, X2... are returned
* The cases (see individual routines, which use idealised geometry by default):
* (0) zero ligands of A. The resultant points are randomly oriented:
* (i) 1 points required; +x,0,0
* (ii) 2 points: use +x,0,0 and -x,0,0
* (iii) 3 points: equilateral triangle in xy plane
* (iv) 4 points x,x,x, x,-x,-x, -x,x,-x, -x,-x,x
* (1a) 1 ligand(B) of A which itself has a ligand (J)
* (i) 1 points required; vector along AB vector
* (ii) 2 points: 2 vectors in ABJ plane, staggered and eclipsed wrt J
* (iii) 3 points: 1 staggered wrt J, the others +- gauche wrt J
* (1b) 1 ligand(B) of A which has no other ligands. A random J is
* generated and (1a) applied
* (2) 2 ligands(B, C) of this
* (i) 1 points required; vector in ABC plane bisecting AB, AC. If ABC is
* linear, no points
* (ii) 2 points: 2 vectors at angle ang, whose resultant is 2i
* (3) 3 ligands(B, C, D) of this
* (i) 1 points required; if A, B, C, D coplanar, no points.
* else vector is resultant of BA, CA, DA
*
* The method identifies the ligands without coordinates, calculates them
* and adds them. It assumes that the total number of ligands determines the
* geometry. This can be overridden by the geometry parameter. Thus if there
* are three ligands and TETRAHEDRAL is given a pyramidal geometry is created
*
* Inappropriate cases throw exceptions.
*
* fails if atom itself has no coordinates or >4 ligands
* see org.xmlcml.molutils.Molutils for more details
*
*
*
* @param moleculeTool TODO
* @param geometry
* from: Molutils.DEFAULT, Molutils.ANY, Molutils.LINEAR,
* Molutils.TRIGONAL, Molutils.TETRAHEDRAL
* @param length
* A-X length
* @param angle
* B-A-X angle (used for some cases)
* @return atomSet with atoms which were calculated. If request could not be
* fulfilled (e.g. too many atoms, or strange geometry) returns
* empty atomSet (not null)
* @throws RuntimeException
*/
public CMLAtomSet calculate3DCoordinatesForLigands(int geometry, double length, double angle) throws RuntimeException {
Point3 thisPoint;
// create sets of atoms with and without ligands
CMLAtomSet noCoordsLigandsAS = new CMLAtomSet();
if (atom.getX3Attribute() == null) {
return noCoordsLigandsAS;
} else {
thisPoint = atom.getXYZ3();
}
CMLAtomSet coordsLigandsAS = new CMLAtomSet();
// atomSet containing atoms without coordinates
List ligandList = atom.getLigandAtoms();
for (CMLAtom ligandAtom : ligandList) {
if (ligandAtom.getX3Attribute() == null) {
noCoordsLigandsAS.addAtom(ligandAtom);
} else {
coordsLigandsAS.addAtom(ligandAtom);
}
}
int nWithoutCoords = noCoordsLigandsAS.size();
int nWithCoords = coordsLigandsAS.size();
if (geometry == Molutils.DEFAULT) {
geometry = atom.getLigandAtoms().size();
}
// too many ligands at present
if (nWithCoords > 3) {
CMLAtomSet emptyAS = new CMLAtomSet();
// FIXME??
return emptyAS;
// nothing needs doing
} else if (nWithoutCoords == 0) {
return noCoordsLigandsAS;
}
List newPoints = null;
List coordAtoms = coordsLigandsAS.getAtoms();
List noCoordAtoms = noCoordsLigandsAS.getAtoms();
if (nWithCoords == 0) {
newPoints = Molutils.calculate3DCoordinates0(thisPoint,
geometry, length);
} else if (nWithCoords == 1) {
// ligand on A
CMLAtom bAtom = (CMLAtom) coordAtoms.get(0);
// does B have a ligand (other than A)
CMLAtom jAtom = null;
List bLigandList = bAtom.getLigandAtoms();
for (CMLAtom bLigand : bLigandList) {
if (!bLigand.equals(moleculeTool)) {
jAtom = bLigand;
break;
}
}
newPoints = Molutils.calculate3DCoordinates1(thisPoint, bAtom
.getXYZ3(), (jAtom != null) ? jAtom.getXYZ3() : null,
geometry, length, angle);
} else if (nWithCoords == 2) {
Point3 bPoint = ((CMLAtom) coordAtoms.get(0)).getXYZ3();
Point3 cPoint = ((CMLAtom) coordAtoms.get(1)).getXYZ3();
newPoints = Molutils.calculate3DCoordinates2(thisPoint, bPoint,
cPoint, geometry, length, angle);
} else if (nWithCoords == 3) {
Point3 bPoint = ((CMLAtom) coordAtoms.get(0)).getXYZ3();
Point3 cPoint = ((CMLAtom) coordAtoms.get(1)).getXYZ3();
Point3 dPoint = ((CMLAtom) coordAtoms.get(2)).getXYZ3();
newPoints = new ArrayList(1);
newPoints.add(Molutils.calculate3DCoordinates3(thisPoint,
bPoint, cPoint, dPoint, length));
}
int np = Math.min(noCoordsLigandsAS.size(), newPoints.size());
for (int i = 0; i < np; i++) {
((CMLAtom) noCoordAtoms.get(i)).setXYZ3(newPoints.get(i));
}
return noCoordsLigandsAS;
}
/**
* gets all atoms downstream of a bond.
* recursively visits all atoms in branch until all leaves have been
* visited. if branch is cyclic, halts when it rejoins atom or otherAtom the
* routine is passed a new AtomSetImpl to be populated
*
* Example: CMLBond b ... (contains atom)
* CMLAtom otherAtom = b.getOtherAtom(atom);
* AtomSet ast = new AtomSetImpl();
* atom.getDownstreamAtoms(ast, otherAtom);
*
* @param atom
* @param atomSet to accumulate ligand atoms
* @param otherAtom not to be visited
*
*/
public void getDownstreamAtoms(CMLAtomSet atomSet,
CMLAtom otherAtom, boolean forceUpdate, CMLAtomSet stopSet) {
atomSet.addAtom(atom, forceUpdate);
LOG.trace("============="+atom.getId()+"===="+otherAtom.getId());
List ligandList = atom.getLigandAtoms();
for (CMLAtom ligandAtom : ligandList) {
// do not revisit atoms
if (false) {
} else if (stopSet != null && stopSet.contains(ligandAtom)) {
LOG.trace("STOP "+ligandAtom.getId());
} else if (ligandAtom.equals(otherAtom)) {
LOG.trace("PARENT "+ligandAtom.getId());
} else if (atomSet.contains(ligandAtom)) {
LOG.trace("ALREADY "+ligandAtom.getId());
// do not backtrack
} else {
LOG.trace("RECURSE "+ligandAtom.getId());
AtomTool ligandTool = AtomTool.getOrCreateTool(ligandAtom);
ligandTool.getDownstreamAtoms(atomSet, atom, forceUpdate, stopSet);
}
}
}
/**
* gets all atoms downstream of a bond.
* VERY SLOW I THINK
* recursively visits all atoms in branch until all leaves have been
* visited. if branch is cyclic, halts when it rejoins atom or otherAtom the
* routine is passed a new AtomSetImpl to be populated
*
* Example: CMLBond b ... (contains atom)
* CMLAtom otherAtom = b.getOtherAtom(atom);
* AtomSet ast = new AtomSetImpl();
* atom.getDownstreamAtoms(ast, otherAtom);
*
* @param atom
* @param atomSet to accumulate ligand atoms
* @param otherAtom not to be visited
*
*/
public void getDownstreamAtoms(CMLAtomSet atomSet,
CMLAtom otherAtom, boolean forceUpdate) {
getDownstreamAtoms(atomSet, otherAtom, forceUpdate, null);
}
/**
* gets all atoms downstream of a bond.
*
* recursively visits all atoms in branch until all leaves have been
* visited. if branch is cyclic, halts when it rejoins atom or otherAtom
* Example:
*
*
*
* MoleculeTool moleculeTool ... contains relevant molecule
* CMLBond b ... (contains atom)
* CMLAtom otherAtom = b.getOtherAtom(atom);
* AtomSet ast = moleculeTool.getDownstreamAtoms(atom, otherAtom);
*
* @param moleculeTool TODO
* @param otherAtom not to be visited
* @return the atomSet (empty if none)
*
*/
public CMLAtomSet getDownstreamAtoms(CMLAtom otherAtom, CMLAtomSet stopSet) {
CMLAtomSet atomSet = new CMLAtomSet();
boolean forceUpdate = false;
AtomTool.getOrCreateTool(atom).getDownstreamAtoms(atomSet, otherAtom, forceUpdate, stopSet);
atomSet.updateContent();
return atomSet;
}
/**
* gets all atoms downstream of a bond.
*
* recursively visits all atoms in branch until all leaves have been
* visited. if branch is cyclic, halts when it rejoins atom or otherAtom
* Example:
*
*
*
* MoleculeTool moleculeTool ... contains relevant molecule
* CMLBond b ... (contains atom)
* CMLAtom otherAtom = b.getOtherAtom(atom);
* AtomSet ast = moleculeTool.getDownstreamAtoms(atom, otherAtom);
* calls getDownstreamAtoms(otherAtom, allowSpiro=true)
*
* @param moleculeTool TODO
* @param otherAtom not to be visited
* @return the atomSet (empty if none)
*
*/
public CMLAtomSet getDownstreamAtoms(CMLAtom otherAtom) {
return getDownstreamAtoms(otherAtom, null);
}
/** finds atom with lowest lexical id.
*
* @param atomList
* @return atom
*/
public static CMLAtom getAtomWithLowestId(List atomList) {
String[] ids = new String[atomList.size()];
int i = 0;
for (CMLAtom atom : atomList) {
ids[i++] = atom.getId();
}
Arrays.sort(ids);
CMLAtom lowestAtom = null;
for (CMLAtom atom : atomList) {
if (atom.getId().equals(ids[0])) {
lowestAtom = atom;
break;
}
}
return lowestAtom;
}
/** translates an atom with single ligand to the covalent radius.
* in a bond (say C-R) where R has only one ligand will translate
* R along the bond vector to be at a distance CR where CR
* is covalent radious of the other atom (in this case C)
*
*/
public void translateToCovalentRadius() {
Point3 groupPoint = atom.getPoint3(CoordinateType.CARTESIAN);
if (groupPoint == null) {
throw new RuntimeException("atom has no coordinates: "+atom.getId());
}
CMLAtom rAtom = this.getSingleLigand();
if (rAtom == null) {
throw new RuntimeException("Expected 1 ligand for: "+atom.getId());
}
Point3 atomPoint = rAtom.getPoint3(CoordinateType.CARTESIAN);
if (atomPoint == null) {
throw new RuntimeException("atom has no coordinates: "+rAtom.getId());
}
Vector3 vector = groupPoint.subtract(atomPoint);
vector = vector.normalize();
String rElement = rAtom.getElementType();
double covRad = 0.3;
if (rElement == null) {
covRad = 0.3;
} else if (rElement.equals(AS.R)) {
} else {
ChemicalElement element = ChemicalElement.getChemicalElement(rElement);
covRad = element.getCovalentRadius();
if (covRad < 0.1) {
covRad = 1.0;
}
}
vector = vector.multiplyBy(covRad);
Point3 newGroupPoint = atomPoint.plus(vector);
atom.setXYZ3(newGroupPoint);
}
/** returns a "g" element
* this contains the text for symbol and charge
* @param drawable
* @return null if no symbol or charge
*/
public SVGElement createGraphicsElement(CMLDrawable drawable) {
g = null;
ensureMoleculeDisplay();
ensureAtomDisplay();
atomDisplay.ensureMoleculeDisplay(moleculeDisplay);
if (false) {
} else if (hasNo2DCoords()) {
} else if (hideHydrogens()) {
} else if (hideGroupRoot()) {
} else {
drawAtom(drawable);
}
return (g == null || g.getChildElements().size() == 0) ? null : g;
}
private void drawAtom(CMLDrawable drawable) {
double x = atom.getX2();
double y = atom.getY2();
g = (drawable == null) ? new SVGG() : drawable.createGraphicsElement();
g.setUserElement(atom);
g.addAttribute(new Attribute("class", "atom"));
g.addAttribute(new Attribute("id", "g"+S_UNDER+atom.getId()));
g.setTransform(new Transform2(
new double[]{
1., 0., x,
0.,-1., y,
0., 0., 1.
}));
String fill = getAtomFill(atom.getElementType());
TextDisplay elementDisplay = atomDisplay.getElementDisplay();
TextDisplay chargeDisplay = atomDisplay.getChargeDisplay();
TextDisplay groupDisplay = atomDisplay.getGroupDisplay();
TextDisplay idDisplay = atomDisplay.getIdDisplay();
TextDisplay isotopeDisplay = atomDisplay.getIsotopeDisplay();
TextDisplay labelDisplay = atomDisplay.getLabelDisplay();
String atomString = getAtomString();
// always draw atom
elementDisplay.setFontSize(11.0);
// elementDisplay.setFontSize(atomDisplay.getFontSize());
elementDisplay.setFill(fill);
elementDisplay.setUserElement(atom);
elementDisplay.displayElement(g, atomString);
if (atomString.equals(S_EMPTY)) {
elementDisplay.setOpacity(0.0);
}
if (atom.getFormalChargeAttribute() != null) {
chargeDisplay.displaySignedInteger(g, atom.getFormalCharge());
}
if (atom.getIsotopeNumberAttribute() != null) {
isotopeDisplay.display(g, ""+atom.getIsotopeNumber());
}
if (atomDisplay.isDisplayLabels()) {
labelDisplay.displayLabel(g, getLabel());
}
if (atomDisplay.isDisplayIds()) {
idDisplay.displayId(g, atom.getId());
}
if (atomDisplay.isDisplayGroups()) {
groupDisplay.displayGroup(g, getGroup());
}
}
private boolean hasNo2DCoords() {
return atom.getX2Attribute() == null || atom.getY2Attribute() == null;
}
private boolean hideHydrogens() {
return atomDisplay != null &&
atomDisplay.isOmitHydrogens() &&
atom.hasElement("H");
}
private boolean hideGroupRoot() {
return atomDisplay != null &&
!atomDisplay.isDisplay() &&
!this.isGroupRoot();
}
private void ensureMoleculeDisplay() {
ensureMoleculeTool();
moleculeDisplay = (moleculeTool == null) ? null : moleculeTool.getMoleculeDisplay();
}
public void ensureMoleculeTool() {
if (moleculeTool == null) {
moleculeTool = MoleculeTool.getOrCreateTool(atom.getMolecule());
}
}
private CMLLabel getGroup() {
Nodes nodes = atom.query(".//cml:label", CMLConstants.CML_XPATH);
return (nodes.size() == 0) ? null : (CMLLabel) nodes.get(0);
}
public void ensureAtomDisplay() {
if (atomDisplay == null) {
if (moleculeDisplay == null) {
CMLMolecule molecule = atom.getMolecule();
MoleculeTool moleculeTool = MoleculeTool.getOrCreateTool(molecule);
if (moleculeTool != null) {
moleculeDisplay = moleculeTool.getMoleculeDisplay();
}
}
if (moleculeDisplay != null) {
AtomDisplay atomDisplayx = moleculeDisplay.getDefaultAtomDisplay();
if (atomDisplayx != null) {
atomDisplay = new AtomDisplay(atomDisplayx);
}
}
}
}
public void setDisplay(boolean display) {
ensureAtomDisplay();
if (atomDisplay != null) {
// new Exception().printStackTrace();
// LOG.debug("ATOM DISPLAY "+display+" "+atom.getId());
atomDisplay.setDisplay(display);
}
}
// private void displayAtomText(String fill, double xOffsetFactor,
// double yOffsetFactor, String atomString) {
// double width = 0.0;
// if (atomString.startsWith(RIGHT)) {
// atomString = atomString.substring(RIGHT.length());
// width = (atomString.length()-1)*fontSize*fontWidthFontSizeFactor;
// }
// SVGText text = new SVGText(
// new Real2(xOffsetFactor*fontSize - width, yOffsetFactor*fontSize), atomString);
// fill = getAtomFill(atomString);
// drawBackgroundCircle();
// text.setFill(fill);
// text.setFontSize(fontSize);
// text.setFontFamily(fontFamily);
// text.setFontStyle(fontStyle);
// text.setFontWeight(fontWeight);
//
// g.appendChild(text);
// }
// // get rid of this some time
// private void drawBackgroundCircle() {
// double rad = radiusFactor*fontSize;
// LOG.debug("RAD "+rad);
// SVGCircle circle = new SVGCircle(new Real2(0., 0.), rad);
// circle.setStroke("none");
// // should be background
// String circleFill = /*AS.C.equals(elementType) ? "none" :*/ "white";
// g.appendChild(circle);
// circle.setFill(circleFill);
// }
private static String getAtomFill(String atomString) {
String fill = null;
if (false) {
} else if(AS.C.equals(atomString)) {
fill = "black";
} else if (AS.N.equals(atomString)) {
fill = "blue";
} else if (AS.O.equals(atomString)) {
fill = "red";
} else if (AS.S.equals(atomString)) {
fill = "orange";
} else if (AS.Cl.equals(atomString)) {
fill = "green";
} else if (AS.F.equals(atomString)) {
fill = "#77ff00";
} else if (AS.Br.equals(atomString)) {
fill = "#ff7700";
} else if (AS.I.equals(atomString)) {
fill = "#ff00ff";
} else if (AS.H.equals(atomString)) {
fill = "gray";
} else if(AS.R.equals(atomString)) {
fill = "brown";
}
return fill;
}
private String getAtomString() {
String s = atom.getElementType();
// omit carbons?
if (!atomDisplay.isDisplayCarbons() && AS.C.equals(s)) {
// s = S_EMPTY;
} else if (moleculeDisplay.isShowChildLabels() && AS.R.equals(s)) {
// R-groups
CMLLabel label = getLabel();
if (label != null) {
s = label.getCMLValue();
if ("cml:abbrevRight".equals(label.getAttributeValue("convention"))) {
s = RIGHT+s;
}
}
} else if (isGroupRoot()) {
// contractable group
List atomSetList = AtomSetTool.getChildAtomSetList(atom);
if (isGroupRoot(atomSetList)) {
// List atomList = atomSetList.get(0).getAtoms();
}
CMLLabel label = getLabel();
s = label.getCMLValue();
} else {
}
return s;
}
private boolean isGroupRoot(List atomSetList) {
return (atomSetList.size() == 1 &&
Role.GROUP.toString().equals(atomSetList.get(0).getAttributeValue("role")));
}
public boolean isGroupRoot() {
return isGroupRoot(AtomSetTool.getChildAtomSetList(this.getAtom()));
}
private CMLLabel getLabel() {
CMLLabel label = null;
CMLElements labels = atom.getLabelElements();
if (labels.size() == 1) {
label = labels.get(0);
}
return label;
}
/**
// * @param xIdOffsetFactor
// * @param yIdOffsetFactor
// * @param idFontFactor
// * @param backgroundIdRadiusFactor
// */
// private void drawId(double xIdOffsetFactor, double yIdOffsetFactor, double idFontFactor, double backgroundIdRadiusFactor) {
// SVGCircle circle;
// SVGText text;
// String idS = atom.getId();
// double idFontSize = idFontFactor*fontSize;
// Real2 chargeXY = new Real2(xIdOffsetFactor*fontSize, yIdOffsetFactor*fontSize);
// circle = new SVGCircle(chargeXY, backgroundIdRadiusFactor*idFontSize);
// circle.setFill("white");
// circle.setStroke("black");
// circle.setStrokeWidth(0.05);
// // circle isn't centered properly yet
//// g.appendChild(circle);
// Real2 idXYd = new Real2((xIdOffsetFactor-0.3)*(fontSize), (yIdOffsetFactor+0.3)*(fontSize));
// text = new SVGText(idXYd, idS);
// text.setFill("black");
// text.setStroke("black");
// text.setFontSize(idFontSize);
// g.appendChild(text);
// }
// /**
// * @param xChargeOffsetFactor
// * @param yChargeOffsetFactor
// * @param chargeFontFactor
// * @param backgroundChargeRadiusFactor
// */
// private void drawCharge(double xChargeOffsetFactor, double yChargeOffsetFactor, double chargeFontFactor, double backgroundChargeRadiusFactor) {
// SVGCircle circle;
// SVGText text;
// int formalCharge = atom.getFormalCharge();
// String chargeS = "";
// if (formalCharge == -1) {
// chargeS = "-";
// } else if (formalCharge == 1) {
// chargeS = "+";
// } else if (formalCharge > 1) {
// chargeS = "+"+formalCharge;
// } else if (formalCharge < -1) {
// chargeS = ""+formalCharge;
// }
// // skip zero charge
// if (!chargeS.equals("")) {
// double chargeFontSize = chargeFontFactor*fontSize;
// Real2 chargeXY = new Real2(xChargeOffsetFactor*fontSize, yChargeOffsetFactor*fontSize);
// circle = new SVGCircle(chargeXY, backgroundChargeRadiusFactor*chargeFontSize);
// circle.setFill("white");
// circle.setStroke("black");
// circle.setStrokeWidth(0.05);
// g.appendChild(circle);
// Real2 chargeXYd = new Real2((xChargeOffsetFactor-0.3)*(fontSize), (yChargeOffsetFactor+0.3)*(fontSize));
// text = new SVGText(chargeXYd, chargeS);
// text.setFontSize(chargeFontSize);
// g.appendChild(text);
// }
// }
/**
* @return the atomDisplay
*/
public AtomDisplay getAtomDisplay() {
return atomDisplay;
}
/**
* @param atomDisplay the atomDisplay to set
*/
public void setAtomDisplay(AtomDisplay atomDisplay) {
this.atomDisplay = atomDisplay;
}
/**
* @return the moleculeTool
*/
public AbstractSVGTool getMoleculeTool() {
return moleculeTool;
}
/**
* @param moleculeTool the moleculeTool to set
*/
public void setMoleculeTool(AbstractSVGTool moleculeTool) {
this.moleculeTool = (MoleculeTool) moleculeTool;
}
/**
* @return the atom
*/
public CMLAtom getAtom() {
return atom;
}
/**
* @return the coordinationSphereSet
*/
public CMLAtomSet getCoordinationSphereSet() {
return coordinationSphereSet;
}
/**
* @return the molecule
*/
public CMLMolecule getMolecule() {
return molecule;
}
/**
* @return the fontSize
*/
public double getFontSize() {
return fontSize;
}
/**
* @param fontSize the fontSize to set
*/
public void setFontSize(double fontSize) {
this.fontSize = fontSize;
}
/**
* @return the radiusFactor
*/
public double getRadiusFactor() {
return radiusFactor;
}
/**
* @param radiusFactor the radiusFactor to set
*/
public void setRadiusFactor(double radiusFactor) {
this.radiusFactor = radiusFactor;
}
/** gets list of atoms referenced by R group.
* scopeElement limits the search to its descendants
* current atom must have elementType='R' and
* CMLLabel child with a non-null value attribute. This value
* is used to match R groups in atoms under scopeElement
*
* @param scopeElement
* @param label of atom in scope
* @return
*/
public List getReferencedAtoms(Element scopeElement, String label) {
Nodes refAtomNodes = scopeElement.query(".//cml:atom[@elementType='R' and cml:label[@value='"+label+"']]", CMLConstants.CML_XPATH);
List refAtomList = new ArrayList();
for (int i = 0; i < refAtomNodes.size(); i++) {
CMLAtom refAtom = (CMLAtom) refAtomNodes.get(i);
if (!refAtom.equals(atom)) {
refAtomList.add(refAtom);
}
}
return refAtomList;
}
/**
* Gets the nonHydrogenLigandList attribute of the AtomImpl object
*
* @return The nonHydrogenLigandList value
*/
public List getNonHydrogenLigandList() {
List newLigandList = new ArrayList();
List ligandList = atom.getLigandAtoms();
for (CMLAtom ligand : ligandList) {
if (!AS.H.equals(ligand.getElementType())) {
newLigandList.add(ligand);
}
}
return newLigandList;
}
/**
* @return
*/
public List getNonHydrogenLigandBondList() {
List newLigandBondList = new ArrayList();
List ligandBondList = atom.getLigandBonds();
for (CMLBond ligandBond : ligandBondList) {
CMLAtom other = ligandBond.getOtherAtom(atom);
if (!AS.H.equals(other.getElementType())) {
newLigandBondList.add(ligandBond);
}
}
return newLigandBondList;
}
/**
*
* @return hydrogen ligands
*/
public List getHydrogenLigandList() {
List ligands = atom.getLigandAtoms();
List hatoms = new ArrayList();
for (CMLAtom ligand : ligands) {
if (AS.H.equals(ligand.getElementType())) {
hatoms.add(ligand);
}
}
return hatoms;
}
/**
* deletes a hydrogen from an atom.
*
* used for building up molecules. If there are implicit H atoms it reduces
* the hydrogenCount by 1. If H's are explicit it removes the first hydrogen
* ligand
*
* @param atom
* @exception RuntimeException
* no hydrogen ligands on atom
*/
public void deleteHydrogen() {
decrementHydrogenCountAttribute();
Set hSet = ChemicalElement.getElementSet(
new String[] { AS.H.value });
List hLigandVector = CMLAtom.filter(atom.getLigandAtoms(),
hSet);
if (hLigandVector.size() > 0) {
molecule.deleteAtom(hLigandVector.get(0));
}
}
private void decrementHydrogenCountAttribute() {
if (atom.getHydrogenCountAttribute() != null) {
if (atom.getHydrogenCount() > 0) {
atom.setHydrogenCount(atom.getHydrogenCount() - 1);
}
}
}
private void incrementHydrogenCountAttribute() {
if (atom.getHydrogenCountAttribute() != null) {
if (atom.getHydrogenCount() > 0) {
atom.setHydrogenCount(atom.getHydrogenCount() + 1);
}
}
}
/** adds hydrogen atom.
* if hydrogenCount is already present, increments it.
* id is created as atomId_hn where n first free integer
* if n > 20 fails
*/
public CMLAtom addHydrogen() {
CMLAtom hAtom = null;
incrementHydrogenCountAttribute();
// List hList = this.getHydrogenLigandList();
// iterate through all hydrogens with similar name (not always ligands)
for (int i = 1; i <= 20; i++) {
String hId = atom.getId()+S_UNDER+"h"+i;
hAtom = molecule.getAtomById(hId);
if (hAtom == null) {
hAtom = new CMLAtom(hId, AS.H);
molecule.addAtom(hAtom);
CMLBond bond = new CMLBond(atom, hAtom);
bond.setOrder(CMLBond.SINGLE_S);
molecule.addBond(bond);
break;
}
}
return hAtom;
}
/**
* gets lone electrons on atom. electrons not involved in bonding assumes
* accurate hydrogen count only currently really works for first row (C, N,
* O, F) calculated as getHydrogenValencyGroup() -
* (getSumNonHydrogenBondOrder() + getHydrogenCount()) -
* atom.getFormalCharge()
*
* @param atom TODO
* @return number of lone electrons (< 0 means cannot calculate)
*/
public int getLoneElectronCount(CMLAtom atom) {
int loneElectronCount = -1;
int group = getHydrogenValencyGroup();
if (group == -1) {
return -1;
}
int sumNonHBo = getSumNonHydrogenBondOrder();
int nHyd = atom.getHydrogenCount();
int formalCharge = 0;
if (atom.getFormalChargeAttribute() != null) {
formalCharge = atom.getFormalCharge();
}
loneElectronCount = group - (sumNonHBo + nHyd) - formalCharge;
return loneElectronCount;
}
/**
* get the double bond equivalents.
*
* this is the number of double bonds the atom can make an sp2 atom has 1 an
* sp atom has 2
*
* @param atom TODO
* @param fcd
* @return the bond sum (0, 1, 2)
* @throws RuntimeException
* if cannot get formal charges
*/
public int getDoubleBondEquivalents(FormalChargeControl fcd) {
CMLMolecule molecule = atom.getMolecule();
if (molecule == null) {
throw new RuntimeException("WARNING skipping DBE");
}
int valenceElectrons = atom.getValenceElectrons();
int formalCharge = 0;
try {
formalCharge = atom.getFormalCharge(fcd);
} catch (RuntimeException e) {
e.printStackTrace();
}
valenceElectrons -= formalCharge;
int maxBonds = (valenceElectrons < 4) ? valenceElectrons
: 8 - valenceElectrons;
// hydrogens are include in bondSum
int doubleBondEquivalents = maxBonds - this.getBondOrderSum();
return doubleBondEquivalents;
}
/**
* gets list of ligands in 2D diagram in clockwise order.
*
* starting atom is arbitrary (makes smallest clockwise angle with xAxis).
* The 4 atoms can be compared to atomRefs4 given by author or other methods
* to see if they are of the same or alternative parity.
*
* use compareAtomRefs4(CMLAtom[] a, CMLAtom[] b) for comparison
*
* @param atom2 TODO
* @param atom4 the original list of 4 atoms
* @return ligands sorted into clockwise order
* @throws RuntimeException
*/
public CMLAtom[] getClockwiseLigands(CMLAtom[] atom4) throws RuntimeException {
Vector2 vx = new Vector2(1.0, 0.0);
Real2 thisxy = atom.getXY2();
double[] angle = new double[4];
Vector2 v = null;
for (int i = 0; i < 4; i++) {
try {
v = new Vector2(atom4[i].getXY2().subtract(thisxy));
// Angle class appears to be broken, hence the degrees
angle[i] = vx.getAngleMadeWith(v).getDegrees();
} catch (NullPointerException npe) {
throw new RuntimeException(
"Cannot compute clockwise ligands");
}
if (angle[i] < 0) {
angle[i] += 360.;
}
if (angle[i] > 360.) {
angle[i] -= 360.;
}
}
// get atom4Refs sorted in cyclic order
CMLAtom[] cyclicAtom4 = new CMLAtom[4];
for (int i = 0; i < 4; i++) {
double minAngle = Double.MAX_VALUE;
int low = -1;
for (int j = 0; j < 4; j++) {
if (angle[j] >= 0 && angle[j] < minAngle) {
low = j;
minAngle = angle[j];
}
}
if (low != -1) {
cyclicAtom4[i] = atom4[low];
angle[low] = -100.;
} else {
throw new RuntimeException(
"Couldn't get AtomRefs4 sorted in cyclic order");
}
}
// all 4 angles must be less than PI
// the ligands in clockwise order
for (int i = 0; i < 4; i++) {
CMLAtom cyclicAtomNext = cyclicAtom4[(i < 3) ? i + 1 : 0];
Real2 cyclicXy = cyclicAtom4[i].getXY2();
Real2 cyclicXyNext = cyclicAtomNext.getXY2();
v = new Vector2(cyclicXy.subtract(thisxy));
Vector2 vNext = new Vector2(cyclicXyNext.subtract(thisxy));
double ang = v.getAngleMadeWith(vNext).getDegrees();
if (ang < 0) {
ang += 360.;
}
if (ang > 360.) {
ang -= 360.;
}
if (ang > 180.) {
throw new RuntimeException("All 4 ligands on same side "
+ atom.getId());
}
}
return cyclicAtom4;
}
/** is this atom clode to another.
*
* @param atom
* @param atom2 TODO
* @param atom1 TODO
* @return true if close
*/
public boolean hasCloseContact(CMLAtom atom1) {
double valenceDist = atom.getChemicalElement().getCovalentRadius()+atom1.getChemicalElement().getVDWRadius();
double dist = atom.getDistanceTo(atom1);
if ((valenceDist/2) > dist) {
return true;
} else {
return false;
}
}
// /**
// * gets four atoms defining atomParity isomerism.
// * applies only to 4-coordinate atoms l1-X(l2)(l3)-l4
// * and possibly 3-coordinate atoms l1-X(l2)-l3
// * when central atom is added to list: l1-l2-l3-X
// *
// * @param atom
// * @return the four atoms or null
// */
// private CMLAtom[] getAtomRefs4() throws RuntimeException {
// CMLAtom[] atom4 = null;
// List ligandList = atom.getLigandAtoms();
// if (ligandList.size() < 3) {
// } else {
// atom4 = new CMLAtom[4];
// atom4[0] = ligandList.get(0);
// atom4[1] = ligandList.get(1);
// atom4[2] = ligandList.get(2);
// atom4[3] = (ligandList.size() == 3) ? atom : ligandList.get(3);
// }
// return atom4;
// }
public void createGroupLabelAndAtomSet() {
if (false) {
} else if ("C".equals(atom.getElementType())) {
List hatoms = AtomTool.getOrCreateTool(atom).getHydrogenLigandList();
if (hatoms.size() == 3) {
addLabel(hatoms, "Me");
} else if (hatoms.size() == 2) {
addLabel(hatoms, "CH2");
} else if (hatoms.size() == 1) {
addLabel(hatoms, "CH");
}
} else {
List hatoms = AtomTool.getOrCreateTool(atom).getHydrogenLigandList();
String elementType = atom.getElementType();
if (hatoms.size() == 3) {
addLabel(hatoms, elementType+"H3");
} else if (hatoms.size() == 2) {
addLabel(hatoms, elementType+"H");
} else if (hatoms.size() == 1) {
addLabel(hatoms, elementType+"H");
}
}
}
public String createChargeString() {
String ss = S_EMPTY;
if (atom.getFormalChargeAttribute() != null) {
int ch = atom.getFormalCharge();
if (ch == 1) {
ss = S_PLUS;
} else if (ch == -1) {
ss = S_MINUS;
} else if (ch > 1) {
ss = S_PLUS + ch;
} else if (ch < -1) {
ss = S_EMPTY + ch;
}
}
return ss;
}
private void addLabel(List atoms, String text) {
for (CMLAtom atom : atoms) {
AtomTool.getOrCreateTool(atom).setDisplay(false);
}
CMLLabel label = new CMLLabel();
label.setCMLValue(text);
this.atom.addLabel(label);
this.atom.setRole(Role.GROUP.toString());
}
/**
* get substituent ligands of one end of bond.
*
* gets all substituent atoms of atom (but not otherAtom in bond)
*
* @param bond
* @param atom at one end of bond
* @return the list of substituent atoms
*/
static List getSubstituentLigandList(CMLBond bond, CMLAtom atom) {
CMLAtom otherAtom = bond.getOtherAtom(atom);
List substituentLigandList = new ArrayList();
List ligandList = atom.getLigandAtoms();
for (CMLAtom ligand : ligandList) {
if (!ligand.equals(otherAtom)) {
substituentLigandList.add(ligand);
}
}
return substituentLigandList;
}
/**
* a simple lookup for common atoms.
*
* examples are C, N, O, F, Si, P, S, Cl, Br, I if atom has electronegative
* ligands, (O, F, Cl...) returns -1
*
* @param atom
* @return group
*/
public static int getHydrogenValencyGroup(CMLAtom atom) {
final int[] group = { 1, 4, 5, 6, 7, 4, 5, 6, 7, 7, 7 };
final int[] eneg0 = { 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1 };
final int[] eneg1 = { 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1 };
int elNum = -1;
try {
String elType = atom.getElementType();
elNum = CMLAtom.getCommonElementSerialNumber(elType);
if (elNum == -1) {
return -1;
}
if (eneg0[elNum] == 0) {
return group[elNum];
}
// if atom is susceptible to enegative ligands, exit if they are
// present
List ligandList = atom.getLigandAtoms();
for (CMLAtom lig : ligandList) {
int ligElNum = CMLAtom.getCommonElementSerialNumber(lig
.getElementType());
if (ligElNum == -1 || eneg1[ligElNum] == 1) {
return -2;
}
}
} catch (Exception e) {
MoleculeTool.LOG.error("BUG " + e);
}
int g = (elNum == -1) ? -1 : group[elNum];
return g;
}
/**
* calculates the geometrical hybridization.
*
* calculates the geometrical hybridisation from 3D coords tetrahedron or
* pyramid gives SP3 trigonal gives SP2 bent gives BENT linear gives SP
* square planar gives SQ
*
* pyramidal and planar are distinguished by improper dihedral of 18 degrees
*
* @param atom
* @return hybridisation (SP3, SP2, BENT, SP, SQUARE_PLANAR)
*/
public Hybridization getGeometricHybridization() {
List ligands = atom.getLigandAtoms();
Hybridization geomHybridization = null;
if (ligands.size() <= 1 || ligands.size() > 4) {
return null;
}
Point3 thisXyz = atom.getXYZ3();
Angle angle = null;
if (thisXyz == null) {
} else if (ligands.size() == 2) {
Point3 thisXyz1 = ligands.get(0).getXYZ3();
Point3 thisXyz2 = ligands.get(1).getXYZ3();
try {
angle = Point3.getAngle(thisXyz1, thisXyz, thisXyz2);
if (angle.getDegrees() > 150) {
geomHybridization = Hybridization.SP;
} else {
geomHybridization = Hybridization.BENT;
// if (angle.getDegrees() > 115) return CMLAtom.SP2;
// return CMLAtom.SP3;
}
} catch (Exception e) {
MoleculeTool.LOG.error("BUG " + e);
}
} else if (ligands.size() == 3) {
Point3 thisXyz1 = ligands.get(0).getXYZ3();
Point3 thisXyz2 = ligands.get(1).getXYZ3();
Point3 thisXyz3 = ligands.get(2).getXYZ3();
// improper dihedral
try {
angle = Point3
.getTorsion(thisXyz1, thisXyz2, thisXyz3, thisXyz);
geomHybridization = Hybridization.SP2;
if (Math.abs(angle.getDegrees()) > 18) {
geomHybridization = Hybridization.SP3;
}
} catch (Exception e) {
MoleculeTool.LOG.error("BUG " + e);
}
} else {
geomHybridization = Hybridization.SP3;
}
return geomHybridization;
}
/**
* get sum of formal bondOrders. uses the actual ligands (i.e. implicit
* hydrogens are not used) aromatic bonds are counted as 1.5, 1,2,3 aromatic
* bonds add 1 double bond
*
* @param atom
* @return sum (-1 means cannot be certain)
*/
public int getBondOrderSum() {
int multipleBondSum = 0;
int aromaticBondSum = 0;
boolean ok = true;
List ligandBonds = atom.getLigandBonds();
for (CMLBond ligandBond : ligandBonds) {
// if the bond is to a H atom then don't increment
// multipleBondSum as H's are dealt with further down
// the method.
boolean hasH = false;
for (CMLAtom bondAt : ligandBond.getAtoms()) {
if (AS.H.equals(bondAt.getElementType()) && bondAt != atom) {
hasH = true;
}
}
if (hasH) {
continue;
}
String order = ligandBond.getOrder();
BondTool ligandBondTool = BondTool.getOrCreateTool(ligandBond);
double numericOrder = ligandBondTool.getNumericOrder();
if (order == null) {
} else if (order.equals(CMLBond.AROMATIC)) {
aromaticBondSum += 1;
} else if (multipleBondSum >= 0.0){
multipleBondSum += (int) Math.round(numericOrder);
} else {
MoleculeTool.LOG.info("Unknown bond order:" + order + S_COLON);
ok = false;
}
}
// any aromatic bonds dictate SP2, so...
if (aromaticBondSum > 0) {
multipleBondSum = atom.getLigandBonds().size() + 1;
}
int hydrogenCount = 0;
try {
hydrogenCount = atom.getHydrogenCount();
} catch (RuntimeException e) {
}
multipleBondSum += hydrogenCount;
return ((ok) ? multipleBondSum : -1);
}
/**
* gets cross product for 3 atoms in 3D.
*
* gets cross products of this->at1 X this->at2
*
* @param atom3 TODO
* @param atom1 first atom
* @param atom2 second atom
* @return the cross product (null if parameters are null; zero if atoms are
* coincident or colinear)
*/
// should this really be a public function?
Vector3 get3DCrossProduct(CMLAtom atom1, CMLAtom atom2) {
Vector3 cross = null;
Vector3 v1 = atom.getVector3(atom1);
Vector3 v2 = atom.getVector3(atom2);
cross = v1.cross(v2);
return cross;
}
/**
* gets cross product for 3 atoms in 2D.
*
* gets cross products of this->at1 X this->at2 the result is a 3D vector
* perpendicular to xy2 plane
*
* @param atom3 TODO
* @param atom1 first atom
* @param atom2 second atom
* @return the cross product (null if parameters are null; zero if atoms are
* coincident or colinear)
*/
// should this really be a public function?
Vector3 get2DCrossProduct(CMLAtom atom1, CMLAtom atom2) {
Vector3 cross = null;
if (atom1 != null && atom2 != null) {
Point3 p0 = atom.get2DPoint3();
Point3 p1 = atom1.get2DPoint3();
Point3 p2 = atom2.get2DPoint3();
if (p1 != null && p2 != null) {
Vector3 v1 = p1.subtract(p0);
Vector3 v2 = p2.subtract(p0);
cross = v1.cross(v2);
}
}
return cross;
}
/**
* transform 3D coordinates. does NOT alter fractional or 2D coordinates
*
* @param transform
* the transformation
*/
public void transformCartesians(CMLTransform3 transform) {
Point3 point = atom.getXYZ3();
point = point.transform(transform.getEuclidTransform3());
atom.setXYZ3(point);
}
/**
* transform fractional and 3D coordinates. does NOT alter 2D coordinates
* transforms fractionals then applies orthogonalisation to result
* @param transform
* the fractional symmetry transformation
* @param orthTransform
* orthogonalisation transform
*/
public void transformFractionalsAndCartesians(CMLTransform3 transform, Transform3 orthTransform) {
Point3 point = atom.getXYZFract();
point = point.transform(transform.getEuclidTransform3());
atom.setXYZFract(point);
point = point.transform(orthTransform);
atom.setXYZ3(point);
}
/**
* transform 3D fractional coordinates. modifies this does not modify x3,
* y3, z3 (may need to re-generate cartesians)
*
* @param transform
* the transformation
*/
public void transformFractionals(CMLTransform3 transform) {
Point3 point = atom.getXYZFract();
point = point.transform(transform.getEuclidTransform3());
atom.setXYZFract(point);
}
/**
* calculate the spaceGroup multiplicity of the atom. this is defined by
* attribute spaceGroupMultiplicity and is the number of symmetry operators
* that transform the atom onto itself with normalization of cell
* translations.
*
* @param symmetry
* spaceGroup operators
* @return the multiplicity (0 if no coordinates else 1 or more)
*/
public int calculateSpaceGroupMultiplicity(CMLSymmetry symmetry) {
int multiplicity = 0;
if (symmetry != null && atom.hasCoordinates(CoordinateType.FRACTIONAL)) {
Point3 xyz = atom.getXYZFract();
multiplicity = symmetry.getSpaceGroupMultiplicity(xyz);
}
return multiplicity;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy