org.xmlcml.cml.tools.Ring 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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.xmlcml.cml.base.AbstractTool;
import org.xmlcml.cml.base.CMLElement;
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.Real2;
import org.xmlcml.euclid.Real2Vector;
import org.xmlcml.euclid.Transform2;
import org.xmlcml.euclid.Util;
/**
* tool to support a ring. not fully developed
* all rings are indexed to provide cyclic list of bonds
*
* @author pmr
*
*/
public class Ring extends AbstractTool implements Comparable {
final static Logger logger = Logger.getLogger(Ring.class.getName());
private CMLAtomSet atomSet;
private CMLBondSet bondSet;
// cyclic array of bonds
private CyclicBondList cyclicBondList;
// cyclic array of atoms
private CyclicAtomList cyclicAtomList;
private Map> atomToBondMap;
private String cyclicAtomIdString = null;
private CMLBond canonicalBond = null;
private CMLAtom canonicalAtom = null;
private Map> junctionMap = null;
private MoleculeLayout moleculeDraw;
private Map atomCoordMap;
private static double RINGSCALE = 0.75;
/** constructor.
* copies reference to sets
*
* @param atomSet
* @param bondSet
*/
public Ring(CMLAtomSet atomSet, CMLBondSet bondSet) {
this.atomSet = atomSet;
this.bondSet = bondSet;
if (atomSet.size() != bondSet.size()) {
throw new RuntimeException("atomSet and bondset different sizes");
}
makeCyclicLists();
}
/** create from cyclic bonds.
*
* @param bondSet
*/
public Ring(CMLBondSet bondSet) {
this(bondSet.getAtomSet(), bondSet);
}
private void makeCyclicLists() {
atomToBondMap = new HashMap>();
List atoms = atomSet.getAtoms();
List bonds = bondSet.getBonds();
int nb = 0;
for (CMLAtom atom : atoms) {
for (CMLBond bond : bonds) {
nb = indexBondsByAtom(bond, atom, atomToBondMap);
if (nb== 2) {
break;
}
}
}
if (nb != 2) {
throw new RuntimeException("Bad list "+nb);
}
orderAtomsInCycle();
cyclicAtomList.canonicalize();
cyclicBondList.canonicalize();
}
private int indexBondsByAtom(CMLBond bond, CMLAtom atom, Map> atomToBondMap) {
int i = 0;
if (bond.getAtom(0).equals(atom) || bond.getAtom(1).equals(atom)) {
List bonds = atomToBondMap.get(atom);
if (bonds == null) {
bonds = new ArrayList();
atomToBondMap.put(atom, bonds);
}
bonds.add(bond);
i = bonds.size();
}
return i;
}
private void orderAtomsInCycle() {
cyclicAtomList = new CyclicAtomList();
cyclicBondList = new CyclicBondList();
// start at any atom
CMLAtom currentAtom = atomSet.getAtoms().get(0);
for (int i = 0; i < atomSet.size(); i++) {
currentAtom = addAtomAndBond(currentAtom);
}
getCyclicAtomList();
getCyclicBondList();
}
private CMLAtom addAtomAndBond(CMLAtom currentAtom) {
List bonds = atomToBondMap.get(currentAtom);
CMLBond currentBond = bonds.get(0);
bonds.remove(currentBond);
cyclicAtomList.add(currentAtom);
cyclicBondList.add(currentBond);
currentAtom = currentBond.getOtherAtom(currentAtom);
bonds = atomToBondMap.get(currentAtom);
bonds.remove(currentBond);
return currentAtom;
}
/**
* @return the atomSet
*/
public CMLAtomSet getAtomSet() {
return atomSet;
}
/**
* @return the bondSet
*/
public CMLBondSet getBondSet() {
return bondSet;
}
/**
* @return the cyclicBondList
*/
public CyclicList getCyclicBondList() {
return cyclicBondList;
}
/**
* @return the cyclicAtomList
*/
public CyclicAtomList getCyclicAtomList() {
return cyclicAtomList;
}
void add(Ring ring, Junction junction) {
if (junctionMap == null) {
junctionMap = new HashMap>();
}
List junctionList = junctionMap.get(ring);
if (junctionList == null) {
junctionList = new ArrayList();
junctionMap.put(ring, junctionList);
}
// only add one copy
if (!junctionList.contains(junction)) {
junctionList.add(junction);
}
}
/** add coordinates to isolated ring
* actually adds coordinates
* @param moleculeDraw
*/
public void calculate2DCoordinates(MoleculeLayout moleculeDraw) {
ensureAtomCoordMap();
this.setMoleculeDraw(moleculeDraw);
Double bondLength = ((MoleculeDisplay)moleculeDraw.getAbstractDisplay()).getBondLength();
if (bondLength == null) {
throw new RuntimeException("Null bondLength");
}
Real2Vector points = Real2Vector.regularPolygon(size(), bondLength*RINGSCALE);
List atoms = this.getCyclicAtomList();
for (int i = 0; i < atoms.size(); i++) {
atomCoordMap.put(atoms.get(i), points.get(i));
}
}
/**
* transfers coordinates to atoms
*/
public void updateCoordinates() {
Set atoms = atomCoordMap.keySet();
for (CMLAtom atom : atoms) {
atom.setXY2(atomCoordMap.get(atom));
}
}
/**
* @param scale
*/
public void multiplyCoordMap(double scale) {
for (CMLAtom atom : atomCoordMap.keySet()) {
Real2 xy2 = atomCoordMap.get(atom);
xy2.multiplyEquals(scale);
}
}
/**
* @param trans
*/
public void translateCoordMap(Real2 trans) {
for (CMLAtom atom : atomCoordMap.keySet()) {
Real2 xy2 = atomCoordMap.get(atom);
xy2.plusEquals(trans);
}
}
/**
* @param transform
*/
public void transformBy(Transform2 transform) {
for (CMLAtom atom : atomCoordMap.keySet()) {
Real2 xy2 = atomCoordMap.get(atom);
xy2.transformBy(transform);
}
}
private void ensureAtomCoordMap() {
if (atomCoordMap == null) {
atomCoordMap = new HashMap();
}
}
/** get list of junctions.
* normally should only be one element in list
* @param ring
* @return list of junctions or null
*/
public List getJunctionList(Ring ring) {
return (junctionMap == null) ? null : junctionMap.get(ring);
}
/** get junction map
*
* @return map or null
*/
public Map> getJunctionMap() {
return junctionMap;
}
/**
* get the (cyclic) List of atomIds
* no duplicates. starts with canonical atom
* @return the (cyclic) List of atomIds
*/
public List getCyclicAtomIdList() {
List idList = new ArrayList();
for (CMLAtom atom : cyclicAtomList) {
idList.add(atom.getId());
}
return idList;
}
/** gets canonical string for ring.
*
* @return concatenated atoms in cyclicAtomlist
*/
public String getCyclicAtomIdString() {
if (cyclicAtomIdString == null) {
String[] ss = getCyclicAtomIdList().toArray(new String[0]);
cyclicAtomIdString = Util.concatenate(ss, S_SPACE);
}
return cyclicAtomIdString;
}
/**
* get the (cyclic) List of bondIds
* no duplicates. starts with canonical bond
* @return the (cyclic) List of bondIds
*/
public List getCyclicBondIdList() {
List idList = new ArrayList();
for (CMLBond bond : cyclicBondList) {
idList.add(bond.getId());
}
return idList;
}
/** get size.
*
* @return size
*/
public int size() {
return bondSet.size();
}
/** get consistent bond for starting algorithms.
* return bond with lowest atomHash()
* @return bond
*/
public CMLBond getCanonicalStartBond() {
if (canonicalBond == null) {
canonicalBond = (CMLBond) cyclicBondList.get(0);
for (CMLBond bondx : cyclicBondList) {
if (bondx.atomHash().compareTo(canonicalBond.atomHash()) < 0) {
canonicalBond = bondx;
}
}
}
return canonicalBond;
}
/** get consistent atom for starting algorithms.
* return atom in canonicalBond which does NOT occur in next bond
* i.e. atom which is start and end of cycle
* since bonds may be in either direction it might look like:
* a3-a1 a3-a7 a2-a7 a1-a2 with a1 as canonical atom
* @return atom
*/
public CMLAtom getCanonicalStartAtom() {
if (canonicalAtom == null) {
getCanonicalStartBond();
// set the pointer
cyclicBondList.getIndexOfAndCache(canonicalBond);
CMLBond nextBond = (CMLBond) cyclicBondList.getNext();
CMLAtom atom0 = canonicalBond.getAtom(0);
CMLAtom atom1 = canonicalBond.getAtom(1);
canonicalAtom = atom1;
try {
// atom0 in next bond?
nextBond.getOtherAtom(atom0);
} catch (RuntimeException e) {
// no, use atom0
canonicalAtom = atom0;
}
}
return canonicalAtom;
}
/**
* @return the atomCoordMap
*/
public Map getAtomCoordMap() {
return atomCoordMap;
}
/**
* @return the atomToBondMap
*/
public Map> getAtomToBondMap() {
return atomToBondMap;
}
/**
* @return the canonicalAtom
*/
public CMLAtom getCanonicalAtom() {
return canonicalAtom;
}
/**
* @return the canonicalBond
*/
public CMLBond getCanonicalBond() {
return canonicalBond;
}
/** get difference between canonical starts.
*
* @return atomStart - bondStart
*/
public int getCanonicalBondToAtomOffset() {
getCanonicalStartBond();
getCanonicalStartAtom();
int bondIdx = cyclicBondList.getIndexOfAndCache(canonicalBond);
int atomIdx = cyclicAtomList.getIndexOfAndCache(canonicalAtom);
return atomIdx - bondIdx;
}
/** compare rings.
* first uses ring sizes, then uses canonicalised cyclicAtomLists
* @param theRing
* @return -1, 0, 1
*/
public int compareTo(Ring theRing) {
int compare = 0;
if (this.size() < theRing.size()) {
compare = -1;
} else if (this.size() > theRing.size()) {
compare = 1;
}
if (compare == 0) {
compare = this.getCyclicAtomIdString().compareTo(theRing.getCyclicAtomIdString());
}
return compare;
}
/** debug.
* not sure this works
*/
public void debug() {
ensureAtomCoordMap();
Util.print("atoms ");
for (CMLAtom atom : cyclicAtomList) {
Util.print(" .. "+atom.getId());
Util.print("["+atom.getXY2()+"]");
Util.print(" ["+atomCoordMap.get(atom)+"]");
}
Util.println();
if (cyclicAtomList.get(0).getXY2() != null) {
for (int i = 0; i < cyclicAtomList.size(); i++) {
int j = (i+1) % cyclicAtomList.size();
try {
Util.print(" .. "+cyclicAtomList.get(i).getXY2().getDistance(cyclicAtomList.get(j).getXY2()));
} catch (Throwable t) {
System.err.println("DEBUG"+t);
}
}
}
Util.println();
Util.print("bonds ");
for (CMLBond bond : cyclicBondList) {
Util.print(" .. "+bond.getId());
}
Util.println();
}
/**
* @return the moleculeDraw
*/
public MoleculeLayout getMoleculeDraw() {
return moleculeDraw;
}
/**
* @param moleculeDraw the moleculeDraw to set
*/
public void setMoleculeDraw(MoleculeLayout moleculeDraw) {
this.moleculeDraw = moleculeDraw;
}
};
/** list supporting cycli structures
*
* @author pm286
*
* @param
*/
abstract class CyclicList extends ArrayList {
/**
*
*/
private static final long serialVersionUID = -589276046564365497L;
int counter;
/** constructor
*/
public CyclicList() {
super();
}
/** get index of element
* resets counter if found
* @param e element to find
* @return index or -1
*/
public int getIndexOfAndCache(E e) {
int idx = super.indexOf(e);
if (idx != -1) {
counter = idx;
}
return idx;
}
/** get element in cycle mod size()
* @param counter
* @return element
*/
public E get(int counter) {
this.counter = counter % size();
return super.get(counter);
}
/** get next element in cycle.
* increment counter mod size()
* @return element
*/
public E getNext() {
counter++;
if (counter >= size()) {
counter = 0;
}
return super.get(counter);
}
/** get previous element in cycle.
* decrement counter mod size()
* @return element
*/
public E getPrevious() {
counter--;
if (counter < 0) {
counter = size()-1;
}
return get(counter);
}
/** get current element in cycle.
* do not alter counter
* @return element
*/
public E getCurrent() {
return get(counter);
}
/** set index 0 to canonical element.
*/
public void canonicalize() {
String s = "";
int idx = 0;
for (int i = 0; i < size(); i++) {
E e = get(i);
if (i == 0) {
s = stringId(e);
} else if (stringId(e).compareTo(s) < 0) {
idx = i;
s = stringId(e);
}
}
List list = new ArrayList();
E e = get(idx);
for (int i = 0; i < size(); i++) {
list.add(e);
e = getNext();
}
for (int i = 0; i < size(); i++) {
super.set(i, list.get(i));
}
}
/** get direction relative to two neighbouring atoms
*
* @param a0
* @param a1
* @return 1 if a0->a1 is same as direction of ring
*/
public int getDirection(E a0, E a1) {
int dir = 0;
int counter0 = counter;
getIndexOfAndCache(a0);
if (getNext().equals(a1)) {
dir = 1;
}
if (dir == 0) {
getIndexOfAndCache(a1);
if (getNext().equals(a0)) {
dir = -1;
}
}
counter = counter0;
return dir;
}
/** get number of steps between atoms
* for a0-a1-a2-a3-a4
* a0-a2 = 2
* a0-a1 = 1
* a1-a0 = 4
* a1-a1 = 0
* a1-a99 = -1
* @param a0
* @param a1
* @return nsteps
*/
public int getStepCount(E a0, E a1) {
int nsteps = 0;
E a = a0;
getIndexOfAndCache(a0);
while (!a1.equals(a)) {
a = getNext();
nsteps++;
}
if (nsteps >= size()) {
nsteps = -1;
}
return nsteps;
}
/** concatenated values
* @return whitespace concatenated values
*/
public String toString() {
StringBuffer sb = new StringBuffer();
int counter0 = counter;
for (int i = 0; i < size(); i++) {
if (i != 0) {
sb.append(" ");
}
if (counter0 == i) {
sb.append("^");
}
sb.append(((CMLElement)get(i)).getAttributeValue("id"));
if (counter0 == i) {
sb.append("^");
}
}
return sb.toString();
}
protected abstract String stringId(E e);
protected void debug() {
for (E e : this) {
Util.print(" .. "+stringId(e));
}
Util.println();
}
};
/** special routines for atoms
*
* @author pm286
*
*/
class CyclicAtomList extends CyclicList {
/**
*
*/
private static final long serialVersionUID = 47311612331052340L;
/** constructor
*/
public CyclicAtomList() {
super();
}
protected String stringId(CMLAtom e) {
return e.getId();
}
}
/** special routines for atoms
*
* @author pm286
*
*/
class CyclicBondList extends CyclicList {
/**
*
*/
private static final long serialVersionUID = 1279106530802984344L;
/** constructor
*/
public CyclicBondList() {
super();
}
protected String stringId(CMLBond e) {
return e.atomHash();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy