org.xmlcml.cml.tools.Chain 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.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.log4j.Logger;
import org.xmlcml.cml.base.AbstractTool;
import org.xmlcml.cml.element.CMLAtom;
import org.xmlcml.cml.element.CMLAtomSet;
import org.xmlcml.cml.element.CMLBond;
import org.xmlcml.cml.element.CMLBondSet;
import org.xmlcml.euclid.Angle;
import org.xmlcml.euclid.Int2;
import org.xmlcml.euclid.IntMatrix;
import org.xmlcml.euclid.Real2;
import org.xmlcml.euclid.Transform2;
import org.xmlcml.euclid.Vector2;
import org.xmlcml.molutil.ChemicalElement.AS;
/**
* tool to support a ring. not fully developed
* @author pmr
*
*/
public class Chain extends AbstractTool {
final static Logger LOG = Logger.getLogger(RingNucleus.class);
private List sproutList;
private CMLAtomSet atomSet;
private CMLBondSet bondSet;
private List terminalBondList;
private List terminalAtomList;
private Map> bondMap;
private MoleculeLayout moleculeDraw;
private IntMatrix distanceMatrix;
private SpanningTree spanningTree;
private SortedMap atomCoordinateMap;
private CMLAtom startAtom;
private double bondLength;
private static Transform2 ROT60 = new Transform2(new Angle(Math.PI/3.));
private static Transform2 ROT300 = new Transform2(new Angle(-Math.PI/3.));
private static Transform2 ROT0 = new Transform2(new Angle(0.0));
// private static Transform2 ROT30 = new Transform2(new Angle(Math.PI/6.));
// private static Transform2 ROT330 = new Transform2(new Angle(Math.PI/6.));
private static final Transform2 ROT90 = new Transform2(new Angle(Math.PI/2.));
private static final Transform2 ROT270 = new Transform2(new Angle(-Math.PI/2.));
/**
* @return the atomCoordinateMap
*/
public Map getAtomCoordinateMap() {
return atomCoordinateMap;
}
/**
*/
public Chain() {
init();
}
/**
* @param moleculeDraw
*/
public Chain(MoleculeLayout moleculeDraw) {
init();
this.setMoleculeDraw(moleculeDraw);
}
private void init() {
this.sproutList = new ArrayList();
this.atomSet = new CMLAtomSet();
this.bondSet = new CMLBondSet();
this.terminalBondList = new ArrayList();
this.terminalAtomList = new ArrayList();
this.bondMap = new HashMap>();
}
/** add bond
* also adds atoms
* @param bond
*/
public void addBond(CMLBond bond) {
this.bondSet.addBond(bond);
this.addAtom(bond.getAtom(0), bond);
this.addAtom(bond.getAtom(1), bond);
}
private void addAtom(CMLAtom atom, CMLBond bond) {
this.atomSet.addAtom(atom);
List bondList = this.bondMap.get(atom);
if (bondList == null) {
bondList = new ArrayList();
this.bondMap.put(atom, bondList);
}
if (!bondList.contains(bond)) {
bondList.add(bond);
}
}
/**
* gets distances (in bonds) between terminals.
* if bonds are identical returns 0, etc.
* if bonds are touching returns 1, etc.
* @return symmetric distanceMatrix
*/
public IntMatrix calculateDistanceMatrixFromSpanningTree() {
if (spanningTree != null) {
int nterm = terminalAtomList.size();
distanceMatrix = new IntMatrix(nterm, nterm);
for (int i = 0; i < nterm -1; i++) {
CMLAtom atomi = terminalAtomList.get(i);
distanceMatrix.setElementAt(i, i, 0);
for (int j = i+1; j < nterm; j++) {
CMLAtom atomj = terminalAtomList.get(j);
AtomPath path = spanningTree.getPath(atomi, atomj);
int dist = path.size();
distanceMatrix.setElementAt(i, j, dist);
distanceMatrix.setElementAt(j, i, dist);
}
}
}
return distanceMatrix;
}
/**
* gets distances (in bonds) between terminals.
* if bonds are identical returns 0, etc.
* if bonds are touching returns 1, etc.
* @return symmetric distanceMatrix
*/
public IntMatrix calculateDistanceMatrix() {
int nterm = terminalBondList.size();
distanceMatrix = new IntMatrix(nterm, nterm);
for (int i = 0; i < nterm-1; i++) {
CMLBond bondi = terminalBondList.get(i);
CMLAtom atomi = terminalAtomList.get(i);
distanceMatrix.setElementAt(i, i, 0);
for (int j = i+1; j < nterm; j++) {
CMLBond bondj = terminalBondList.get(j);
int dist = getDistance(atomi, bondi, bondj);
distanceMatrix.setElementAt(i, j, dist);
distanceMatrix.setElementAt(j, i, dist);
}
}
return distanceMatrix;
}
/**
* @param startAtom
* @param startBond
* @param targetBond
* @return distance
*/
public int getDistance(CMLAtom startAtom, CMLBond startBond, CMLBond targetBond) {
int level = 0;
CMLAtomSet usedAtomSet = new CMLAtomSet();
CMLBondSet usedBondSet = new CMLBondSet();
List atomBondList = new ArrayList();
atomBondList.add(new AtomBond(startAtom, startBond));
usedAtomSet.addAtom(startAtom);
usedBondSet.addBond(startBond);
while (!AtomBond.contains(atomBondList, targetBond)) {
atomBondList = expand(atomBondList, usedAtomSet, usedBondSet);
if (atomBondList.size() == 0) {
break;
}
level++;
}
return level;
}
private List expand(List atomBondList,
CMLAtomSet usedAtomSet, CMLBondSet usedBondSet) {
List newAtomBondList = new ArrayList();
for (AtomBond atomBond : atomBondList) {
CMLBond startBond = atomBond.bond;
CMLAtom startAtom = startBond.getOtherAtom(atomBond.atom);
for (CMLBond ligandBond : startAtom.getLigandBonds()) {
if (usedBondSet.contains(ligandBond)) {
continue;
}
newAtomBondList.add(new AtomBond(startAtom, ligandBond));
usedAtomSet.addAtom(startAtom);
usedBondSet.addBond(ligandBond);
}
}
return newAtomBondList;
}
/**
* @param sprout (can be null)
* @param moleculeLayout
*/
public void calculate2DCoordinates(Sprout sprout, MoleculeLayout moleculeLayout) {
this.setMoleculeDraw(moleculeLayout);
ensureAtomCoordinateMap();
bondLength = moleculeLayout.getMoleculeDisplay().getBondLength();
spanningTree = new SpanningTree(atomSet, bondSet);
spanningTree.setOmitHydrogens(moleculeLayout.getMoleculeDisplay().isOmitHydrogens());
spanningTree.setIncludedAtomSet(atomSet);
spanningTree.setIncludedBondSet(bondSet);
spanningTree.generate(terminalAtomList.get(0));
spanningTree.generateTerminalPaths();
List pathList = null;
startAtom = null;
if (sprout == null) {
this.calculateDistanceMatrixFromSpanningTree();
// get longest path
Int2 ij = distanceMatrix.indexOfLargestElement();
startAtom = terminalAtomList.get(ij.getX());
atomCoordinateMap.put(startAtom, new Real2(0., 0.));
} else {
startAtom = sprout.getRingAtom();
atomCoordinateMap.put(startAtom, startAtom.getXY2());
}
pathList = this.getPathsToTerminalAtoms(startAtom);
// sort on path lengths
Collections.sort(pathList);
// longest paths first
Collections.reverse(pathList);
// longest path
@SuppressWarnings("unused")
AtomPath path = (pathList.size() == 0) ? null : pathList.get(0);
expandPaths(pathList);
}
private void expandPaths(List pathList) {
if (pathList.size() > 0) {
AtomPath atomPath0 = pathList.get(0);
CMLAtom atom0 = atomPath0.get(0);
Real2 xy0 = new Real2(0., 0.);
atom0.setXY2(xy0);
for (AtomPath atomPath : pathList) {
expandPath(atomPath, 1);
}
}
}
private void expandPath(AtomPath atomPath, int start) {
for (int i = start; i < atomPath.size(); i++) {
CMLAtom node = atomPath.get(i);
Real2 xy = node.getXY2();
if (xy == null) {
Real2 direction = getDirection(atomPath, i, node);
// kludge
if (direction.getLength() < 0.0001) {
direction = new Real2(1.0, 0.0);
}
direction = direction.getUnitVector().multiplyBy(MoleculeDisplay.getDefaultBondLength());
// System.out.println("DIRECTION... "+direction.getLength());
xy = atomPath.get(i-1).getXY2().plus(direction);
node.setXY2(xy);
}
}
}
private Real2 getDirection(AtomPath atomPath, int position, CMLAtom node) {
Real2 direction = null;
CMLAtom parent = (position == 0) ? null : atomPath.get(position-1);
CMLAtom grandParent = (position <= 1) ? null : atomPath.get(position-2);
CMLAtom greatGrandParent = (position <= 2) ? null : atomPath.get(position-3);
List ligandList = parent.getLigandAtoms();
int freeLigand = -1;
int nlig = ligandList.size();
for (int i = 0; i < nlig; i++) {
CMLAtom ligand = ligandList.get(i);
if (ligand.equals(grandParent)) {
continue;
}
freeLigand++;
if (ligand.equals(node)) {
break;
}
}
Real2 parentDirection = null;
if (grandParent != null) {
parentDirection = parent.getXY2().subtract(grandParent.getXY2());
LOG.trace("PARENTD"+parentDirection);
Vector2 v2 = new Vector2(parentDirection);
parentDirection = v2.getUnitVector();
parentDirection = parentDirection.multiplyBy(bondLength);
// double d = parentDirection.getLength();
// parentDirection.getUnitVector();
} else {
// LOG.debug("BONDLENGTH"+bondLength);
parentDirection = new Real2(bondLength, 0.0);
}
direction = new Real2(parentDirection);
if (nlig == 2) {
// may also need to think about CH2 groups
Real2 grandDirection = null;
if (greatGrandParent != null) {
grandDirection = grandParent.getXY2().subtract(greatGrandParent.getXY2());
}
direction = parent.getXY2();
if (grandParent != null) {
direction = parent.getXY2().subtract(grandParent.getXY2());
}
if (grandDirection == null) {
// direction.transformBy(ROT330);
direction.transformBy(ROT0);
} else {
Real2 direction1 = new Real2(direction);
// direction1.transformBy(ROT30);
direction1.transformBy(ROT0);
double dot1 = Math.abs(direction1.dotProduct(grandDirection));
Real2 direction2 = new Real2(direction);
// direction2.transformBy(ROT330);
direction2.transformBy(ROT0);
double dot2 = Math.abs(direction2.dotProduct(grandDirection));
direction = (dot1 > dot2) ? direction1 : direction2;
}
// direction = parent.getXY2().subtract(grandParent.getXY2());
// linear at present - change later when we understand bond orders
} else if (nlig == 3) {
if (freeLigand == 0) {
direction.transformBy(ROT60);
} else if (freeLigand == 1) {
direction.transformBy(ROT300);
}
} else if (nlig == 4) {
if (freeLigand == 0) {
// straight on
} else if (freeLigand == 1){
direction.transformBy(ROT90);
} else if (freeLigand == 2){
direction.transformBy(ROT270);
}
}
return direction;
}
// private void calculate2DCoordinates(AtomPath atomPath, int serial) {
// ensureAtomCoordinateMap();
// if (atomPath.size() < 2) {
// throw new RuntimeException("Path must be at least 2 atoms");
// }
// if (atomPath.get(0) != startAtom || atomPath.get(1) != nextAtom) {
// throw new RuntimeException("atomPath does not start with start/nextAtom");
// }
// Real2 xy0 = atomCoordinateMap.get(startAtom);
// Real2 xy1 = atomCoordinateMap.get(nextAtom);
//// LOG.debug(">>>start "+startAtom.getId()+"/"+xy0);
//// LOG.debug(">>>next "+nextAtom.getId()+"/"+xy1);
// if (xy0 == null || xy1 == null) {
// throw new RuntimeException("First 2 atoms must have coordinates");
// }
// Real2[] vv = new Real2[2];
// vv[1] = xy1.subtract(xy0);
// vv[0] = new Real2(vv[1]);
// vv[0].transformBy(ROT60);
// LOG.debug(vv[0]+"/"+vv[1]);
// int start = -1;
// CMLAtom previousAtom = nextAtom;
// Real2 currentXY2 = atomCoordinateMap.get(previousAtom);
// // go down path
// for (int atomPathi = 2; atomPathi < atomPath.size(); atomPathi++) {
// CMLAtom atom = atomPath.get(atomPathi);
// Real2 newXY2 = atomCoordinateMap.get(atom);
// if (newXY2 != null) {
// if (start >= 0) {
// throw new RuntimeException("wrong way down path?");
// }
// LOG.debug(">SKIP> "+atom.getId());
// previousAtom = atom;
// continue;
// }
// // found first null atom
// if (start < 0) {
// start = atomPathi;
// }
// // are we branching?
// List ligandList = getLigandsInChain(previousAtom);
// if (ligandList.size() == 0) {
// throw new RuntimeException("previous atom has no ligands");
// } else if (ligandList.size() == 1) {
// // no, flip vector with parity
// Real2 vvv = vv[(atomPathi) % 2];
// LOG.debug("V1 "+vvv);
// currentXY2 = currentXY2.plus(vvv);
// atomCoordinateMap.put(atom, currentXY2);
//// LOG.debug(">>"+atom.getElementType()+">> "+atom.getId()+"/"+currentXY2);
// } else if (ligandList.size() == 2) {
// currentXY2 = process2LigandList(vv, previousAtom, atomPathi, atom, ligandList);
// } else if (ligandList.size() == 3) {
// LOG.debug("BUG 3LigandList not processed");
// for (CMLAtom ligand : ligandList) {
// LOG.debug("LIG "+ligand.getId()+" / "+ligand.getXY2());
// }
// currentXY2 = process3LigandList(vv, previousAtom, atomPathi, atom, ligandList);
// }
// previousAtom = atom;
// }
// if (start == -1 && atomPath.size() > 2) {
// throw new RuntimeException("path full of coordinates");
// }
// }
// private Real2 process2LigandList(Real2[] vv, CMLAtom previousAtom, int i,
// CMLAtom atom, List ligandList) {
// Real2 currentXY2;
// LOG.debug(">>BRANCH>> "+atom.getId());
// LOG.debug("Midpoint for "+atom.getId()+" on "+previousAtom.getId());
// currentXY2 = atomCoordinateMap.get(previousAtom);
// CMLAtom prevLig0 = ligandList.get(0);
// CMLAtom prevLig1 = ligandList.get(1);
// LOG.debug(prevLig0.getId()+"["+previousAtom.getId()+"]"+prevLig1.getId());
// Real2 prevLigXY0 = atomCoordinateMap.get(prevLig0);
// Real2 prevLigXY1 = atomCoordinateMap.get(prevLig1);
// LOG.debug("MID "+prevLigXY0+"/"+prevLigXY1);
// Real2 centroid = prevLigXY0.getMidPoint(prevLigXY1);
// LOG.debug(centroid);
// Real2 vect = currentXY2.subtract(centroid);
// LOG.debug("V2 "+vect);
// if (vect.getLength() < 0.00001) {
// vect = prevLigXY0.subtract(centroid);
// vect.transformBy(ROT90);
// }
// vect = vect.getUnitVector().multiplyBy(bondLength);
// int i0 = (i) % 2;
// int i1 = (i + 1) % 2;
// vv[i0] = vect;
// vv[i1] = new Real2(vv[i0]);
// vv[i1].transformBy(ROT60);
//// LOG.debug("V22 "+vect);
// currentXY2 = currentXY2.plus(vv[i0]);
// atomCoordinateMap.put(atom, currentXY2);
// return currentXY2;
// }
// private Real2 process3LigandList(Real2[] vv, CMLAtom previousAtom, int i,
// CMLAtom atom, List ligandList) {
// Real2 currentXY2;
// LOG.debug(">>BRANCH>> "+atom.getId());
// LOG.debug("Midpoint for "+atom.getId()+" on "+previousAtom.getId());
// currentXY2 = atomCoordinateMap.get(previousAtom);
// CMLAtom prevLig0 = ligandList.get(0);
// CMLAtom prevLig1 = ligandList.get(1);
// LOG.debug(prevLig0.getId()+"["+previousAtom.getId()+"]"+prevLig1.getId());
// Real2 prevLigXY0 = atomCoordinateMap.get(prevLig0);
// Real2 prevLigXY1 = atomCoordinateMap.get(prevLig1);
// LOG.debug("MID "+prevLigXY0+"/"+prevLigXY1);
// Real2 centroid = prevLigXY0.getMidPoint(prevLigXY1);
// LOG.debug(centroid);
// Real2 vect = currentXY2.subtract(centroid);
// LOG.debug("V2 "+vect);
// if (vect.getLength() < 0.00001) {
// vect = prevLigXY0.subtract(centroid);
// vect.transformBy(ROT90);
// }
// vect = vect.getUnitVector().multiplyBy(bondLength);
// int i0 = (i) % 2;
// int i1 = (i + 1) % 2;
// vv[i0] = vect;
// vv[i1] = new Real2(vv[i0]);
// vv[i1].transformBy(ROT30);
//// LOG.debug("V22 "+vect);
// currentXY2 = currentXY2.plus(vv[i0]);
// atomCoordinateMap.put(atom, currentXY2);
// return currentXY2;
// }
// private List getLigandsInChain(CMLAtom atom) {
// List chainLigandList = new ArrayList();
// List ligandList = atom.getLigandAtoms();
// for (CMLAtom ligand : ligandList) {
// if (!atomSet.contains(ligand)) {
// // atom must be in set
// } else if (atomCoordinateMap.get(ligand) != null) {
// // ligand needs coordinates
// chainLigandList.add(ligand);
// }
// }
// return chainLigandList;
// }
private void applyCoordinates() {
if (atomCoordinateMap != null) {
for (CMLAtom atom : atomCoordinateMap.keySet()) {
atom.setXY2(atomCoordinateMap.get(atom));
}
}
}
private void ensureAtomCoordinateMap() {
if (atomCoordinateMap == null) {
atomCoordinateMap = new TreeMap();
}
}
/** gets all paths starting at single atom.
*
* @param startAtom
* @return list of other termibal atoms
*/
public List getPathsToTerminalAtoms(CMLAtom startAtom) {
List pathList = new ArrayList();
// TODO ensure paths. this is messy and should be refactored
for (CMLAtom terminalAtom : terminalAtomList) {
spanningTree.getPath(startAtom, terminalAtom);
}
Map> pathMap = spanningTree.getPathMap();
Map paths = pathMap.get(startAtom);
for (CMLAtom terminalAtom : paths.keySet()) {
if (startAtom.equals(terminalAtom)) {
continue;
} else {
AtomPath path = spanningTree.getPath(startAtom, terminalAtom);
pathList.add(path);
}
}
return pathList;
}
/**
* layout chain attached to ring
* recursively visit attachments
* @param sprout
*/
public void layout(Sprout sprout, int cycles) {
sprout.generateCoordinates();
applyCoordinates();
for (Sprout otherSprout : sproutList) {
if (sprout != otherSprout) {
RingNucleus nextNucleus = moleculeDraw.getSproutNucleusMap().get(otherSprout);
nextNucleus.layout(otherSprout, cycles);
}
}
}
/**
* @return the sproutList
*/
public List getSproutList() {
return sproutList;
}
/** add sprout
*
* @param sprout
*/
public void addSprout(Sprout sprout) {
if (this.sproutList.contains(sprout)) {
throw new RuntimeException("duplicate sprout in chain");
}
this.sproutList.add(sprout);
this.addTerminalBond(sprout.getRingAtom(), sprout.getBond());
}
/**
* @return the terminalBondList
*/
public List getTerminalBondList() {
return terminalBondList;
}
/**
* mark bond as terminal (includes sprouts)
* @param atom terminalAtom
* @param bond terminalBond
*/
public void addTerminalBond(CMLAtom atom, CMLBond bond) {
if (((MoleculeDisplay)moleculeDraw.getAbstractDisplay()).isOmitHydrogens() && AS.H.equals(atom.getElementType())) {
// omit hydrogens
} else {
if (terminalBondList == null) {
terminalBondList = new ArrayList();
}
if (!terminalBondList.contains(bond)) {
terminalBondList.add(bond);
}
if (terminalAtomList == null) {
terminalAtomList = new ArrayList();
}
if (!terminalAtomList.contains(atom)) {
terminalAtomList.add(atom);
}
}
}
/**
* @return the atomSet
*/
public CMLAtomSet getAtomSet() {
return atomSet;
}
/**
* @return the bondSet
*/
public CMLBondSet getBondSet() {
return bondSet;
}
/**
* @return the distanceMatrix
*/
public IntMatrix getDistanceMatrix() {
return distanceMatrix;
}
/**
* @return the terminalAtomList
*/
public List getTerminalAtomList() {
return terminalAtomList;
}
/**
* @return string
*/
public String toString() {
String s = "chain: ";
for (CMLBond bond : bondSet.getBonds()) {
s += "["+bond.getId()+"]";
}
s += "\n";
for (Sprout sprout : sproutList) {
s += "{"+sprout+"}";
}
s += "\n";
for (int i = 0; i < terminalBondList.size(); i++) {
s += "<"+terminalAtomList.get(i).getId()+"; "+terminalBondList.get(i).getId()+">";
}
return s;
}
/**
* @return the bondMap
*/
public Map> getBondMap() {
return bondMap;
}
/**
* @return the moleculeDraw
*/
public MoleculeLayout getMoleculeDraw() {
return moleculeDraw;
}
/**
* @param moleculeDraw the moleculeDraw to set
*/
public void setMoleculeDraw(MoleculeLayout moleculeDraw) {
this.moleculeDraw = moleculeDraw;
}
/**
* @return the spanningTree
*/
public SpanningTree getSpanningTree() {
return spanningTree;
}
}
class AtomBond {
CMLAtom atom;
CMLBond bond;
/**
* @param atom
* @param bond
*/
public AtomBond(CMLAtom atom, CMLBond bond) {
this.atom = atom;
this.bond = bond;
}
/**
* @param atomBondList
* @param bond
* @return true if in list
*/
public static boolean contains(List atomBondList, CMLBond bond) {
boolean contains = false;
for (AtomBond atomBond : atomBondList) {
if (atomBond.bond.equals(bond)) {
contains = true;
break;
}
}
return contains;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy