All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xmlcml.cml.tools.MoleculeTool 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.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import nu.xom.Attribute;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.Node;
import nu.xom.Nodes;
import nu.xom.ParentNode;
import nu.xom.Text;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.xmlcml.cml.attribute.UnitsAttribute;
import org.xmlcml.cml.base.CMLConstants;
import org.xmlcml.cml.base.CMLElement;
import org.xmlcml.cml.base.CMLElements;
import org.xmlcml.cml.base.CMLUtil;
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.base.CMLLog.Severity;
import org.xmlcml.cml.element.CMLAmount;
import org.xmlcml.cml.element.CMLAngle;
import org.xmlcml.cml.element.CMLAtom;
import org.xmlcml.cml.element.CMLAtomArray;
import org.xmlcml.cml.element.CMLAtomSet;
import org.xmlcml.cml.element.CMLBond;
import org.xmlcml.cml.element.CMLBondArray;
import org.xmlcml.cml.element.CMLBondSet;
import org.xmlcml.cml.element.CMLBondStereo;
import org.xmlcml.cml.element.CMLCrystal;
import org.xmlcml.cml.element.CMLElectron;
import org.xmlcml.cml.element.CMLFormula;
import org.xmlcml.cml.element.CMLLabel;
import org.xmlcml.cml.element.CMLLength;
import org.xmlcml.cml.element.CMLLink;
import org.xmlcml.cml.element.CMLMap;
import org.xmlcml.cml.element.CMLMatrix;
import org.xmlcml.cml.element.CMLMolecule;
import org.xmlcml.cml.element.CMLMoleculeList;
import org.xmlcml.cml.element.CMLName;
import org.xmlcml.cml.element.CMLProperty;
import org.xmlcml.cml.element.CMLSymmetry;
import org.xmlcml.cml.element.CMLTorsion;
import org.xmlcml.cml.element.CMLTransform3;
import org.xmlcml.cml.element.CMLMap.Direction;
import org.xmlcml.cml.element.CMLMolecule.HydrogenControl;
import org.xmlcml.cml.graphics.CMLDrawable;
import org.xmlcml.cml.graphics.GraphicsElement;
import org.xmlcml.cml.graphics.SVGElement;
import org.xmlcml.cml.graphics.SVGG;
import org.xmlcml.cml.graphics.SVGGBox;
import org.xmlcml.cml.graphics.SVGRect;
import org.xmlcml.cml.graphics.SVGText;
import org.xmlcml.cml.tools.MoleculeDisplay.Position;
import org.xmlcml.euclid.Angle;
import org.xmlcml.euclid.EuclidRuntimeException;
import org.xmlcml.euclid.Point3;
import org.xmlcml.euclid.Point3Vector;
import org.xmlcml.euclid.Real;
import org.xmlcml.euclid.Real2;
import org.xmlcml.euclid.Real2Interval;
import org.xmlcml.euclid.Real2Range;
import org.xmlcml.euclid.Real3Range;
import org.xmlcml.euclid.RealRange;
import org.xmlcml.euclid.RealSquareMatrix;
import org.xmlcml.euclid.Transform2;
import org.xmlcml.euclid.Transform3;
import org.xmlcml.euclid.Util;
import org.xmlcml.euclid.Vector2;
import org.xmlcml.euclid.Vector3;
import org.xmlcml.molutil.ChemicalElement;
import org.xmlcml.molutil.Molutils;
import org.xmlcml.molutil.ChemicalElement.AS;
import org.xmlcml.molutil.ChemicalElement.Type;

/**
 * additional tools for molecule. not fully developed
 *
 * @author pmr
 *
 */
public class MoleculeTool extends AbstractSVGTool {


	static final Logger LOG = Logger.getLogger(MoleculeTool.class.getName());

    /** dewisott */
	public static String HYDROGEN_COUNT = "hydrogenCount";
	
	private CMLMolecule molecule;
	private Map atomToolMap;
	private Map bondToolMap;
	private SelectionTool selectionTool;
	private MoleculeDisplay moleculeDisplay;
	private CMLAtom currentAtom;
	private CMLBond currentBond;

	private Morgan morgan;
	private SVGRect boundingRectSVG;
	

	/**
	 * constructor
	 *
	 * @param molecule
	 * @deprecated use getOrCreateTool
	 */
	public MoleculeTool(CMLMolecule molecule) {
		if (molecule == null) {
			throw new RuntimeException("null molecule");
		}
		this.molecule = molecule;
		this.molecule.setTool(this);
	}

	/**
	 * get molecule.
	 *
	 * @return the molecule
	 */
	public CMLMolecule getMolecule() {
		return molecule;
	}
	
	private void enableAtomToolMap() {
		if (this.atomToolMap == null) {
			this.atomToolMap = new HashMap();
		}
	}
	private void enableBondToolMap() {
		if (this.bondToolMap == null) {
			this.bondToolMap = new HashMap();
		}
	}

	/**
	 * @param atom 
	 * @return atomTool (created if not present)
	 */
	public AtomTool getOrCreateAtomTool(CMLAtom atom) {
		enableAtomToolMap();
		AtomTool atomTool = atomToolMap.get(atom);
		if (atomTool== null) {
			atomTool = AtomTool.getOrCreateTool(atom);
			atomToolMap.put(atom, atomTool);
		}
		return atomTool;
	}

	/**
	 * @param bond 
	 * @return bondTool (created if not present)
	 */
	public BondTool getOrCreateBondTool(CMLBond bond) {
		enableBondToolMap();
		BondTool bondTool = bondToolMap.get(bond);
		if (bondTool== null) {
			bondTool = BondTool.getOrCreateTool(bond);
			bondToolMap.put(bond, bondTool);
		}
		return bondTool;
	}
	
	/** gets MoleculeTool associated with molecule.
	 * if null creates one and sets it in molecule
	 * @param molecule
	 * @return tool
	 */
	@SuppressWarnings("all")
	public static MoleculeTool getOrCreateTool(CMLMolecule molecule) {
		MoleculeTool moleculeTool = null;
		if (molecule != null) {
			moleculeTool = (MoleculeTool) molecule.getTool();
			if (moleculeTool == null) {
				moleculeTool = new MoleculeTool(molecule);
				molecule.setTool(moleculeTool);
			}
		}
		return moleculeTool;
	}

	/** get charge.
	 * 
	 * @return charge
	 */
	public int getFormalCharge() {
		int formalCharge = 0;
		Nodes chargedAtoms = molecule.getAtomArray().query(".//"+CMLAtom.NS+"[@formalCharge]", CMLConstants.CML_XPATH);
		for (int i = 0; i < chargedAtoms.size(); i++) {
			formalCharge += Integer.parseInt(((Element)chargedAtoms.get(i)).getAttributeValue("formalCharge"));
		}
		return formalCharge;
	}
	
	/** get charged atoms.
	 * 
	 * @return atoms
	 */
	public List getChargedAtoms() {
		List chargedAtoms = new ArrayList();
		Nodes atoms = molecule.getAtomArray().query(".//"+CMLAtom.NS+"[@formalCharge != 0]", CMLConstants.CML_XPATH);
		for (int i = 0; i < atoms.size(); i++) {
			chargedAtoms.add((CMLAtom)atoms.get(i));
		}
		return chargedAtoms;
	}


	/**
	 * Adjust bond orders to satisfy valence.
	 *
	 * in impossible systems appropriate atoms are marked as radicals
	 * (spinMultiplicity) assumes explicit hydrogens
	 * uses default PISystemManager
	 */
	public void adjustBondOrdersToValency() {
        molecule.setBondOrders(CMLBond.SINGLE_S);
        PiSystemControls piSystemManager = new PiSystemControls();
        piSystemManager.setUpdateBonds(true);
//        piSystemManager.setKnownUnpaired(knownUnpaired);
        piSystemManager.setDistributeCharge(true);
		this.adjustBondOrdersToValency(piSystemManager);
	}
	
	/**
	 * Adjust bond orders to satisfy valence.
	 *
	 * in impossible systems appropriate atoms are marked as radicals
	 * (spinMultiplicity) assumes explicit hydrogens
	 *
	 * @param piSystemManager
	 */
	public void adjustBondOrdersToValency(PiSystemControls piSystemManager) {
		// normalize bond orders
		molecule.setNormalizedBondOrders();
		PiSystem piSystem2 = new PiSystem(molecule.getAtoms());
		piSystem2.setPiSystemManager(new PiSystemControls(piSystemManager));
		List piSystemList2 = piSystem2.generatePiSystemList();
		for (PiSystem subPiSystem : piSystemList2) {
			List atomList = subPiSystem.getAtomList();
			int npi = atomList.size();
			if (npi < 2) {
//				LOG.debug("Cannot find pi system for " + npi);
			} else {
				subPiSystem.identifyDoubleBonds();
			}
		}
		// remove temporary pi electrons
		List electrons = CMLUtil.getQueryNodes(molecule, ".//"+CMLElectron.NS+"[@dictRef='cml:piElectron']", CMLConstants.CML_XPATH);
		for (Node electron : electrons) {
			electron.detach();
		}
		normalizeBondOrders();
	}

	public void normalizeBondOrders() {
		List bonds = molecule.getBonds();
		for (CMLBond bond : bonds) {
//			bond.debug("XXXX");
			BondOrder.normalizeBondOrder(bond);
//			bond.debug("BBBBBBBBBBB");
		}
	}

	/**
<<<<<<< .working
=======
	 * 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
	 * @param fcd
	 * @return the bond sum (0, 1, 2)
	 * @throws RuntimeException
	 *             if cannot get formal charges
	 */
	public int getDoubleBondEquivalents(CMLAtom atom, FormalChargeControl fcd) {
		if (atom.getMolecule() == 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(atom);
		return doubleBondEquivalents;
	}

	/**
	 * 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(CMLAtom atom) {
		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();
			if (order == null) {
				multipleBondSum += 0;
			} else if (CMLBond.isDouble(order)) {
				multipleBondSum += 2;
			} else if (CMLBond.isTriple(order)) {
				multipleBondSum += 3;
			} else if (CMLBond.isSingle(order)) {
				multipleBondSum += 1;
			} else if (order.equals(CMLBond.AROMATIC)) {
				aromaticBondSum += 1;
			} else {
				LOG.info("Unknown bond order:" + order + CMLConstants.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);
	}

	/**
	 * 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(CMLAtom atom) {
		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) {
				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) {
				LOG.error("BUG " + e);
			}
		} else {
			geomHybridization = Hybridization.SP3;
		}
		return geomHybridization;
	}

	/**
	 * 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 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) {
			LOG.error("BUG " + e);
		}
		int g = (elNum == -1) ? -1 : group[elNum];
		return g;
	}

	/**
	 * 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(CMLAtom atom,
			CMLMolecule.HydrogenControl control) {
		if (atom.getHydrogenCountAttribute() == null) {
			int group = this.getHydrogenValencyGroup(atom);
			if (group == -1) {
				return;
			} else if (group == -2) {
				return;
			}
			// hydrogen and metals
			if (group < 4) {
				return;
			}
			int sumBo = getSumNonHydrogenBondOrder(molecule, atom);
			int fc = (atom.getFormalChargeAttribute() == null) ? 0 : atom
					.getFormalCharge();
			int nh = 8 - group - sumBo + fc;
			// non-octet species
			if (group == 4 && fc == 1) {
				nh -= 2;
			}
			atom.setHydrogenCount(nh);
			this.expandImplicitHydrogens(atom, control);
		}
	}

	/**
>>>>>>> .merge-right.r915
	 * get calculated molecular mass. 
     * uses hydrogenCount attribute to check hydrogens
	 * @return calculated molecular mass.
	 * @throws RuntimeException unknown/unsupported element type (Dummy counts as zero mass)
	 */
	public double getCalculatedMolecularMass() throws RuntimeException {
		getTotalHydrogenCount();
		return this.getCalculatedMolecularMass(HydrogenControl.USE_HYDROGEN_COUNT);
	}
	
	/** gets total hydrogen count on molecule.
	 * if molecule@hydrogenCount uses that
	 * else ensures all atoms have hydrogenCount
	 * if not uses adjustHydrogenCountsToValency()
	 * then summs over all atoms
	 * then adds hydrogenCountAttribute
	 * 
	 * THIS IS A MENACE - it calculates new hydrogens
	 * @return total hydrogen count
	 */
	@Deprecated
	public int getTotalHydrogenCount() {
		String hydrogenCount = molecule.getAttributeValue(HYDROGEN_COUNT);
		int sum = -1;
		if (hydrogenCount == null || CMLConstants.S_EMPTY.equals(hydrogenCount.trim())) {
			this.adjustHydrogenCountsToValency(HydrogenControl.NO_EXPLICIT_HYDROGENS);
			sum = sumHydrogenCountOnAtoms();
			molecule.addAttribute(new Attribute(HYDROGEN_COUNT, ""+sum));
		} else {
			sum = Integer.parseInt(hydrogenCount);
		}
		return sum;
	}

	private int sumHydrogenCountOnAtoms() {
		int sum = 0;
		List atoms = molecule.getAtoms();
		for (CMLAtom atom : atoms) {
			if (!AS.H.equals(atom.getElementType())) {
				sum += atom.getHydrogenCount();
			}
		}
		return sum;
	}

	/**
<<<<<<< .working
=======
	 * 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 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(CMLAtom atom) {
		return MoleculeTool.getSumNonHydrogenBondOrder(this.molecule, atom);
	}
	/**
	 * 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 static int getSumNonHydrogenBondOrder(CMLMolecule molecule, CMLAtom atom)
	throws RuntimeException {
		float sumBo = 0.0f;
		List ligandList = atom.getLigandAtoms();
		for (CMLAtom ligand : ligandList) {
			if (AS.H.equals(ligand.getElementType())) {
				continue;
			}
			CMLBond bond = molecule.getBond(atom, ligand);
			if (bond == null) {
				throw new RuntimeException(
						"Serious bug in getSumNonHydrogenBondOrder");
			}
			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);
	}

	/**
>>>>>>> .merge-right.r915
	 * 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
<<<<<<< .working
	 * @deprecated use AtomTool method
	 * @exception RuntimeException
=======
	 * @exception RuntimeException
>>>>>>> .merge-right.r915
	 *                no hydrogen ligands on atom
	 *                
	 */
	public void deleteHydrogen(CMLAtom atom) {
		if (atom.getHydrogenCountAttribute() != null) {
			if (atom.getHydrogenCount() > 0) {
				atom.setHydrogenCount(atom.getHydrogenCount() - 1);
			} else {
				throw new RuntimeException("No hydrogens to delete");
			}
		} else {
			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));
			} else {
				throw new RuntimeException("No hydrogens to delete");
			}
		}
		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));
		}
	}

	/**
	 * 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 atom
	 *  @param otherAtom not to be visited
	 *  @return the atomSet (empty if none)
	 *
	 */
	public CMLAtomSet getDownstreamAtoms(CMLAtom atom, CMLAtom otherAtom) {
		CMLAtomSet atomSet = new CMLAtomSet();
		boolean forceUpdate = false;
		getDownstreamAtoms(atom, atomSet, otherAtom, forceUpdate);
		atomSet.updateContent();
		return atomSet;
	}

	/**
	 * 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 * */ private void getDownstreamAtoms(CMLAtom atom, CMLAtomSet atomSet, CMLAtom otherAtom, boolean forceUpdate) { atomSet.addAtom(atom, forceUpdate); List ligandList = atom.getLigandAtoms(); for (CMLAtom ligandAtom : ligandList) { // do not revisit atoms if (atomSet.contains(ligandAtom)) { ; // do not backtrack } else if (ligandAtom.equals(otherAtom)) { ; } else { this.getDownstreamAtoms(ligandAtom, atomSet, atom, forceUpdate); } } } /** * append to id creates new id, perhaps to disambiguate * * @param atom * @param s */ public void appendToId(CMLAtom atom, final String s) { String id = atom.getId(); if ((id != null) && (id.length() > 0)) { atom.renameId(id + s); } else { atom.renameId(s); } } /** * 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 atom * @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(CMLAtom atom, int geometry, double length, double angle) { 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(this)) { 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; } // ============ atom matcher ============== /** * finds a pair with neighbours that map to each other can be called * iteratively until returns null * * @param atomSet1 * to match * @param atomSet2 * diminished if match found * @param from1to2map * the mapping from atomSet1 to atomSet2 (i.e. from ids in * atomSet1, to in atomSet2) * @param atomMatcher * @return pair of matched atoms * */ // FIXME @SuppressWarnings("unused") private AtomPair getAtomsWithSameMappedNeighbours00( AtomMatcher atomMatcher, CMLAtomSet atomSet1, CMLAtomSet atomSet2, CMLMap from1to2map) { Set matchingSet = new HashSet(); AtomPair pair = null; for (CMLAtom atom : atomSet1.getAtoms()) { if (atomMatcher.skipAtom(atom)) { continue; } matchingSet.add(atom); } for (CMLAtom atom : atomSet2.getAtoms()) { String elementType2 = atom.getElementType(); if (atomMatcher.skipAtom(atom)) { continue; } CMLAtomSet ligand2Set = CMLAtomSet.createFromAtoms(atom.getLigandAtoms()); // for each ligandList or atom2 loop through all atom1s to find // match // which has most ligands? // FIXME better - store all atoms with at least one ligand List matchAtomList = new ArrayList(); int maxLigands = 0; CMLAtom mostLigandedAtom1 = null; for (int j = 0; j < atomSet1.size(); j++) { CMLAtom atom1 = (CMLAtom) atomSet1.getAtom(j); if (atomMatcher.skipAtom(atom1)) { continue; } String elementType1 = atom1.getElementType(); // atoms must match type if (!elementType1.equals(elementType2)) { continue; } int ligandCount = 0; List ligandList = atom1.getLigandAtoms(); for (CMLAtom ligandAtom1 : ligandList) { if (!atomMatcher.skipLigandAtom(ligandAtom1)) { String id1 = ligandAtom1.getId(); String id2 = from1to2map.getRef(id1, CMLMap.Direction.FROM); // does id2 exist and is it a ligand of atom 2 if (id2 != null && ligand2Set.getAtomById(id2) != null) { ligandCount++; } } } if (ligandCount > maxLigands) { maxLigands = ligandCount; mostLigandedAtom1 = atom1; } if (ligandCount > 0) { matchAtomList.add(atom1); } } // ligand atoms in common? // if (matchAtomList.size() == 1) { CMLAtom matchAtom1 = matchAtomList.get(0); pair = new AtomPair(matchAtom1, atom); atomSet1.removeAtom(matchAtom1); atomSet2.removeAtom(atom); break; // old method } else if (false && mostLigandedAtom1 != null) { pair = new AtomPair(mostLigandedAtom1, atom); atomSet1.removeAtom(mostLigandedAtom1); atomSet2.removeAtom(atom); break; } else { pair = null; } } return pair; } public List getAtomList(CMLMap map, Direction toFrom) { List idList = map.getRefs(toFrom); List atomList = new ArrayList(); for (String id : idList) { CMLAtom atom = molecule.getAtomById(id); if (atom == null) { // maybe a fromSet // for (String ss : idList) { // LOG.debug(ss); // } // map.debug(); // throw new RuntimeException("missing atom: "+id); } atomList.add(atom); } return atomList; } /** * 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(CMLAtom atom, CMLMolecule.HydrogenControl control) throws RuntimeException { if (control.equals(CMLMolecule.HydrogenControl.USE_HYDROGEN_COUNT)) { 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 (control.equals(CMLMolecule.HydrogenControl.NO_EXPLICIT_HYDROGENS) && 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("1"); } } /** * 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 * @return number of lone electrons (< 0 means cannot calculate) */ public int getLoneElectronCount(CMLAtom atom) { int loneElectronCount = -1; int group = this.getHydrogenValencyGroup(atom); if (group == -1) { return -1; } int sumNonHBo = MoleculeTool.getSumNonHydrogenBondOrder(molecule, atom); int nHyd = atom.getHydrogenCount(); int formalCharge = 0; if (atom.getFormalChargeAttribute() != null) { formalCharge = atom.getFormalCharge(); } loneElectronCount = group - (sumNonHBo + nHyd) - formalCharge; return loneElectronCount; } @SuppressWarnings("unused") private void getHybridizationFromConnectivty() { // FIXME List atoms = molecule.getAtoms(); // process atoms first. known terminal atoms or atoms with maximum // connectivity // gives precise information; int valence[] = new int[atoms.size()]; int i = -1; for (CMLAtom atom : molecule.getAtoms()) { i++; int bondOrder = -1; int ligandCount = atom.getLigandAtoms().size(); String elType = null; try { elType = atom.getElementType(); } catch (Exception e) { LOG.error("BUG " + e); } valence[i] = -1; if (AS.H.equals(elType) || AS.F.equals(elType) || AS.Cl.equals(elType) || AS.Br.equals(elType) || AS.I.equals(elType)) { valence[i] = 1; } else if (AS.O.equals(elType) || AS.S.equals(elType)) { valence[i] = 2; } else if (AS.N.equals(elType)) { valence[i] = 3; } else if (AS.C.equals(elType)) { valence[i] = 4; } int valLig = valence[i] - ligandCount; // FIXME many times! if (valLig >= 0) { if (valence[i] < 3) { if (ligandCount == 1 || valLig == 0) { // bondOrder = orderX[valLig]; } // atom.setGeometricHybridization(spX[valLig]); } else if (valence[i] == 4) { if (ligandCount == 4) { // bondOrder = orderX[valLig]; // atom.setGeometricHybridization(spX[valLig]); } } } if (bondOrder == -1) { continue; } // set hybridization and orders for ligand bonds for ( // LigandIterator it = atom.getLigandIterator(); // it.hasNext(); // ) { // CMLAtom ligand = (CMLAtom) it.next(); // FIXME CMLAtom ligand = null; CMLBond bond = molecule.getBond(atom, ligand); // FIXME // order = bond.getOrder(); } if (bondOrder == -1) { } // bond.setOrder(S_EMPTY+bondOrder); // // bond.orderKnown = true; } boolean change = true; while (change) { change = false; // iterate using the known bond orders and filling in any gaps... for (int j = 0; j < atoms.size(); j++) { if (valence[j] < 0) { continue; } int bondSum = 0; int aromSum = 0; CMLBond missingBond[] = new CMLBond[atoms.size()]; int nMissing = 0; for (CMLAtom atom1 : atoms) { // CMLAtom ligand = (CMLAtom) it1.next(); CMLAtom ligand = null; CMLBond bond = molecule.getBond(atom1, ligand); String order = null; try { order = bond.getOrder(); } catch (Exception e) { LOG.error("BUG " + e); } if (order.equals(CMLBond.UNKNOWN_ORDER)) { missingBond[nMissing++] = bond; } else { if (CMLBond.isSingle(order)) { bondSum++; } else if (CMLBond.isDouble(order)) { bondSum += 2; } else if (CMLBond.isTriple(order)) { bondSum += 3; } else if (order.equals(CMLBond.AROMATIC)) { aromSum++; } } } change = assignMissingBonds(valence, j, bondSum, aromSum, missingBond, nMissing); } } } private boolean assignMissingBonds(int[] valence, int i, int bondSum, int aromSum, CMLBond[] missingBond, int nMissing) { boolean change = false; if (nMissing > 0) { if (aromSum == 0) { int delta = valence[i] - bondSum; if (nMissing == delta) { for (int j = 0; j < nMissing; j++) { try { missingBond[j].setOrder(CMLBond.SINGLE_S); } catch (Exception e) { LOG.error("BUG " + e); } } change = true; } else if (nMissing == 1) { String newOrder = null; if (delta == 1) { newOrder = CMLBond.SINGLE_S; } else if (delta == 2) { newOrder = CMLBond.DOUBLE_D; } else if (delta == 3) { newOrder = CMLBond.TRIPLE_T; } else if (newOrder != null) { try { missingBond[0].setOrder(newOrder); } catch (Exception e) { LOG.error("BUG " + e); } change = true; } } } } return change; } // =============== ATOMSET ========================= /** * gets bondset for all bonds in current atom set. slow order of bond is * undetermined but probably in atom document order and iteration through * ligands * * @param atomSet * @return the bondSet */ public CMLBondSet getBondSet(CMLAtomSet atomSet) { // List atoms = molecule.getAtoms(); List atoms = atomSet.getAtoms(); molecule.getBonds(); CMLBondSet bondSet = new CMLBondSet(); for (CMLAtom atom : atoms) { List ligandList = atom.getLigandAtoms(); List ligandBondList = atom.getLigandBonds(); // loop through ligands and examine each for membership of this set; // if so add bond int i = 0; for (CMLAtom ligand : ligandList) { CMLBond ligandBond = ligandBondList.get(i++); if (bondSet.contains(ligandBond)) { continue; } if (atomSet.contains(ligand)) { bondSet.addBond(ligandBond); } } } return bondSet; } /** * Creates new Molecule one ligand shell larger than the atomSet. * * @param atomSet * to sprout from (must all be in this molecule). * @return new Molecule (or null if no atoms) */ public CMLMolecule sprout(CMLAtomSet atomSet) { CMLMolecule newMolecule = null; if (atomSet != null && atomSet.getAtoms().size() > 0) { AtomSetTool atomSetTool = AtomSetTool.getOrCreateTool(atomSet); CMLAtomSet newAtomSet = atomSetTool.sprout(); CMLMolecule molecule = atomSetTool.getMoleculeOrAncestor(); CMLBondSet newBondSet = MoleculeTool.getOrCreateTool(molecule) .getBondSet(newAtomSet); newMolecule = MoleculeTool.createMolecule(newAtomSet, newBondSet); } return newMolecule; } /** * get bond connecting 2 atoms. * * @param a1 * first atom * @param a2 * second atom * @return bond or null if not found */ public CMLBond getBond(String id1, String id2) { String atomHash = CMLBond.atomHash(id1, id2); return getBondFromHash(atomHash); } private CMLBond getBondFromHash(String atomHash) { CMLBond theBond = null; if (atomHash != null) { for (CMLBond bond : molecule.getBonds()) { String bondHash = CMLBond.atomHash(bond); if (bondHash.equals(atomHash)) { theBond = bond; break; } } } return theBond; } /** * create a cluster of connected atoms of given types. * * @param typeList * list of elements allowed * @return list of clusters (or empty list) */ public List createClusters(List typeList) { List clusterList = new ArrayList(); if (molecule.getMoleculeCount() > 0) { for (CMLMolecule subMolecule : molecule.getMoleculeElements()) { MoleculeTool subMoleculeTool = MoleculeTool.getOrCreateTool(subMolecule); List subClusterList = subMoleculeTool .createClusters(typeList); for (CMLMolecule subCluster : subClusterList) { clusterList.add(subCluster); } } } else { CMLAtomSet unusedAtomSet = this.getAtomSet(); while (unusedAtomSet.size() > 0) { CMLAtomSet clusterSet = new CMLAtomSet(); expandCluster(clusterSet, unusedAtomSet, typeList); if (clusterSet.size() > 1) { CMLMolecule clusterMolecule = MoleculeTool.createMolecule(clusterSet); MoleculeTool.getOrCreateTool(clusterMolecule).calculateBondedAtoms(); clusterList.add(clusterMolecule); molecule.addToLog(Severity.INFO, "NEW CLUSTER SIZE " + clusterSet.size()); } } } return clusterList; } private boolean expandCluster(CMLAtomSet clusterSet, CMLAtomSet unusedAtomSet, List typeList) { boolean change = false; List unusedAtomList = unusedAtomSet.getAtoms(); for (CMLAtom currentAtom : unusedAtomList) { if (sproutAtom(currentAtom, clusterSet, unusedAtomSet, typeList)) { change = true; break; } } return change; } private boolean sproutAtom(CMLAtom currentAtom, CMLAtomSet clusterSet, CMLAtomSet unusedAtomSet, List typeList) { boolean change = false; unusedAtomSet.removeAtom(currentAtom); if (currentAtom.atomIsCompatible(typeList)) { change = true; if (!clusterSet.contains(currentAtom)) { clusterSet.addAtom(currentAtom); } List ligandList = currentAtom.getLigandAtoms(); for (CMLAtom ligand : ligandList) { // atom already used? if (!unusedAtomSet.contains(ligand)) { continue; } // atom not of right kind if (!ligand.atomIsCompatible(typeList)) { continue; } boolean bonded = CMLBond.areWithinBondingDistance(currentAtom, ligand); if (bonded) { clusterSet.addAtom(ligand); } sproutAtom(ligand, clusterSet, unusedAtomSet, typeList); } } return change; } /** * extract ligands from connected molecule components. * * @param typeList * list of elements allowed * @return list of clusters (or empty list) */ public List createLigands(List typeList) { new ConnectionTableTool(molecule).partitionIntoMolecules(); List ligandList = new ArrayList(); for (CMLMolecule splitMol : this.getMoleculeList()) { CMLMolecule subMolecule = new CMLMolecule(splitMol); //subMolecule.setId("NEW_" + subMolecule.getId()); boolean deleted = false; List atomList = subMolecule.getAtoms(); for (CMLAtom atom : atomList) { if (atom.atomIsCompatible(typeList)) { deleted = true; subMolecule.deleteAtom(atom); } } if (deleted) { if (subMolecule.getAtomCount() == 0) { continue; } else { new ConnectionTableTool(subMolecule).partitionIntoMolecules(); List ligands = MoleculeTool.getOrCreateTool(subMolecule).getMoleculeList(); for (CMLMolecule ligand : ligands) { ligandList.add(ligand); } } } } return ligandList; } public void addGroupLabels() { for (CMLAtom atom : molecule.getAtoms()) { AtomTool atomTool = AtomTool.getOrCreateTool(atom); atomTool.createGroupLabelAndAtomSet(); } } public void addFormula() { if (molecule.getFormulaElements().size() == 0) { CMLFormula formula = new CMLFormula(molecule); molecule.appendChild(formula); } } /** * Creates new Molecule one ligand shell larger than this one. uses * molecule.getAtomSet() * * @see #sprout(CMLAtomSet) * @return new Molecule */ public CMLMolecule sprout() { return sprout(this.getAtomSet()); } // ====================== BOND ============ /** <<<<<<< .working ======= * 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 */ private 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; } /** * gets four atoms defining cis/trans isomerism. * * selects 4 atoms lig0-atom0-atom1-lig1, where atom0 and atom1 are the * atoms in the bond and lig0 and lig1 are atoms bonded to each end * respectively Returns null unless atom0 and atom 1 each have 1 or 2 * ligands (besides themselves) if atom0 and/or atom1 have 2 ligands the * selection is deterministic but arbitrary - hydrogens are not used if * possible - normally the first Id in lexical order (i.e. no implied * semantics). Exceptions are thrown if ligands are linear with atom0-atom1 * or 2 ligands on same atom are on the same side these may be normal * exceptions (e.g. in a C=O bond) so should be caught and analysed rather * than dumping the program * * @param bond * @exception RuntimeException * 2 ligand atoms on same atom on same side too few or too * many ligands at either end (any) ligand is linear with * bond * @return the four atoms */ static CMLAtom[] createAtomRefs4(CMLBond bond) throws RuntimeException { CMLAtom[] atom4 = null; List atomList = bond.getAtoms(); List ligands0 = getSubstituentLigandList(bond, atomList.get(0)); List ligands1 = getSubstituentLigandList(bond, atomList.get(1)); if (ligands0.size() == 0) { // no ligands on atom } else if (ligands1.size() == 0) { // no ligands on atom } else if (ligands0.size() > 2) { throw new RuntimeException("Too many ligands on atom: " + atomList.get(0).getId()); } else if (ligands1.size() > 2) { throw new RuntimeException("Too many ligands on atom: " + atomList.get(1).getId()); } else { CMLAtom ligand0 = ligands0.get(0); if (AS.H.equals(ligand0.getElementType()) && ligands0.size() > 1 && ligands0.get(1) != null) { ligand0 = ligands0.get(1); } else if (ligands0.size() > 1 && ligands0.get(1).getId().compareTo(atomList.get(0).getId()) < 0) { ligand0 = ligands0.get(1); } CMLAtom ligand1 = ligands1.get(0); if (AS.H.equals(ligand1.getElementType()) && ligands1.size() > 1 && ligands1.get(1) != null) { ligand1 = ligands1.get(1); } else if (ligands1.size() > 1 && ligands1.get(1).getId().compareTo(atomList.get(1).getId()) < 0) { ligand1 = ligands1.get(1); } atom4 = new CMLAtom[4]; atom4[0] = ligand0; atom4[1] = atomList.get(0); atom4[2] = atomList.get(1); atom4[3] = ligand1; } return atom4; } /** * 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 */ static CMLAtom[] getAtomRefs4(CMLAtom atom) 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; } /** * gets atoms on one side of bond. only applicable to acyclic bonds if bond * is cyclic, whole molecule will be returned! returns atom and all * descendant atoms. * * @param bond * @param atom defining side of bond * @throws RuntimeException atom is not in bond * @return atomSet of downstream atoms */ public CMLAtomSet getDownstreamAtoms(CMLBond bond, CMLAtom atom) { CMLAtomSet atomSet = new CMLAtomSet(); CMLAtom otherAtom = bond.getOtherAtom(atom); if (otherAtom != null) { atomSet = getDownstreamAtoms(otherAtom, atom); } return atomSet; } // ==================== MOLECULE ==================== /** >>>>>>> .merge-right.r915 * add suffix to atom IDs. * * Add a distinguishing suffix to all atom IDs this allows multiple copies * of a fragment in a molecule * * @param suffix * @throws RuntimeException */ public void addSuffixToAtomIDs(String suffix) { for (CMLAtom atom : molecule.getAtoms()) { atom.renameId(atom.getId() + suffix); } } /** * Add or delete hydrogen atoms to satisy valence. * * does not use CDK * * @param control * specifies whether H are explicit or in hydorgenCount */ public void adjustHydrogenCountsToValency(HydrogenControl control) { if (molecule.isMoleculeContainer()) { CMLElements molecules = molecule.getMoleculeElements(); for (CMLMolecule mol : molecules) { MoleculeTool.getOrCreateTool(mol).adjustHydrogenCountsToValency(control); } } else { List atoms = molecule.getAtoms(); for (CMLAtom atom : atoms) { AtomTool atomTool = AtomTool.getOrCreateTool(atom); atomTool.adjustHydrogenCountsToValency(control); } } } /** Traverses all non-H atoms and contracts the hydrogens on each. * Can control whether H with stereogenic bonds are contracted or not. * * @param control * @param contractStereoH */ public void contractExplicitHydrogens(HydrogenControl control, boolean contractStereoH) { if (molecule.isMoleculeContainer()) { CMLElements molecules = molecule.getMoleculeElements(); for (CMLMolecule mol : molecules) { MoleculeTool.getOrCreateTool(mol).contractExplicitHydrogens(control, contractStereoH); } } else { List atoms = molecule.getAtoms(); for (CMLAtom atom : atoms) { if (!AS.H.equals(atom.getElementType())) { if (contractStereoH) { AtomTool.getOrCreateTool(atom).contractExplicitHydrogens(control); } else { boolean contract = true; for (CMLBond bond : atom.getLigandBonds()) { List bondStereoNodes = CMLUtil.getQueryNodes(bond, ".//cml:"+CMLBondStereo.TAG, CMLConstants.CML_XPATH); for (Node bondStereoNode : bondStereoNodes) { String stereo = bondStereoNode.getValue(); if (CMLBond.WEDGE.equals(stereo) || CMLBond.HATCH.equals(stereo)) { contract = false; } } } if ("R".equals(atom.getElementType()) || "Xx".equals(atom.getElementType()) || atom.getChemicalElement().isChemicalElementType(Type.METAL)) { contract = false; } if (contract) { AtomTool.getOrCreateTool(atom).contractExplicitHydrogens(control); } } } } } } /** * Traverses all non-H atoms and calls CMLAtom.expandImplicitHydrogens on each. * * @param control as in CMLAtom * @see CMLAtom */ public void expandImplicitHydrogens(HydrogenControl control) { for (CMLAtom atom : molecule.getAtoms()) { if (!AS.H.equals(atom.getElementType())) { AtomTool atomTool = AtomTool.getOrCreateTool(atom); atomTool.expandImplicitHydrogens(control); } } } /** * removes atoms from this with identical coordinates to those in mol. uses * deleteAtom (which may delete bonds if present) * * @param mol * should be unaltered * @param type * of coordinate to use */ public void removeOverlapping3DAtoms(CMLMolecule mol, CoordinateType type) { CMLAtomSet atomSet = AtomSetTool.getOrCreateTool(this.getAtomSet()) .getOverlapping3DAtoms(MoleculeTool.getOrCreateTool(mol).getAtomSet(), type); for (CMLAtom atom : atomSet.getAtoms()) { molecule.deleteAtom(atom); } } /** * */ public void create2DCoordinatesFrom3D(double bondLengthScale) { List atomList = molecule.getAtoms(); for (CMLAtom atom : atomList) { double x3 = atom.getX3(); atom.setX2(x3*bondLengthScale); double y3 = atom.getY3(); atom.setY2(y3*bondLengthScale); } Real2 centroid2 = this.getAtomSet().getCentroid2D(); molecule.translate2D(centroid2.multiplyBy(-1.0)); } /** * gets atomset corresponding to bondset. * * @param bondSet * @return the atomset */ public CMLAtomSet getAtomSet(CMLBondSet bondSet) { CMLAtomSet atomSet = new CMLAtomSet(); List bonds = bondSet.getBonds(); for (CMLBond bond : bonds) { CMLAtom atom0 = bond.getAtom(0); CMLAtom atom1 = bond.getAtom(1); atomSet.addAtom(atom0); atomSet.addAtom(atom1); } return atomSet; } /** * calculate bondorders from coordinates if present. * * uses Pauling bond order/length formula. use with extreme care! many bonds * will have fractional orders and these will be difficult to manage */ public void calculateBondOrdersFromXYZ3() { for (CMLBond bond : molecule.getBonds()) { String order = null; try { order = bond.getOrder(); } catch (Exception e) { LOG.error("BUG " + e); } // order not available? if (order == null || order.equals(CMLBond.UNKNOWN_ORDER)) { CMLAtom at0 = bond.getAtom(0); if (at0 == null) { throw new RuntimeException("NULL atom"); } String elType = at0.getElementType(); if (elType == null) { throw new RuntimeException("missing elementType"); } ChemicalElement el0 = ChemicalElement .getChemicalElement(elType); if (el0 == null) { continue; } double r0 = el0.getCovalentRadius(); CMLAtom at1 = bond.getAtom(1); ChemicalElement el1 = ChemicalElement.getChemicalElement(at1 .getElementType()); if (el1 == null) { continue; } double r1 = el1.getCovalentRadius(); double dlen = at0.getDistanceTo(at1); if (dlen < 0.0) { continue; } double paulingBO = Math.exp(2.303 * (-dlen + r0 + r1) / 0.7); try { if (paulingBO < 1.3) { bond.setOrder(CMLBond.SINGLE_S); } else if (paulingBO < 1.75) { bond.setOrder(CMLBond.AROMATIC); } else if (paulingBO < 2.5) { bond.setOrder(CMLBond.DOUBLE_D); } else if (paulingBO < 5) { bond.setOrder(CMLBond.TRIPLE_T); } } catch (Exception e) { Util.BUG(e); } } } } /** * calculates which atoms are bonded by covalent radius criterion. * */ public void calculateBondedAtoms() { for (CMLMolecule mol : molecule.getDescendantsOrMolecule()) { List atoms = mol.getAtoms(); // need to remove old bonds first for (CMLBond bond : mol.getBonds()) { bond.detach(); } CMLBondArray ba = mol.getBondArray(); if (ba != null) { ba.indexBonds(); } calculateBondedAtoms(atoms, mol); } } /** * @param atoms */ public void calculateBondedAtoms(List atoms) { CMLMolecule mol = null; for (CMLAtom atom : atoms) { CMLMolecule m = atom.getMolecule(); if (mol != null) { if (mol != atom.getMolecule()) { throw new RuntimeException("All CMLAtoms must belong to the same CMLMolecule"); } } mol = m; } calculateBondedAtoms(atoms, mol); } private void calculateBondedAtoms(List atoms, CMLMolecule molecule) { for (int i = 0; i < atoms.size(); i++) { CMLAtom atomi = (CMLAtom) atoms.get(i); for (int j = i + 1; j < atoms.size(); ++j) { CMLAtom atomj = (CMLAtom) atoms.get(j); if (CMLBond.areWithinBondingDistance(atomi, atomj)) { molecule.addBond(new CMLBond(atomi, atomj)); } } } } /** * calculate all bonds from this to molecule and join. will transfer all * atoms from molecule to this except overlapping ones, thereby corrupting * molecule. Overlapping atoms are discarded * * @param molecule2 * to join (will be corrupted) * @return true if 1 or more bonds were made */ boolean calculateBondsToAndJoin(CMLMolecule molecule2) { List atoms = molecule.getAtoms(); List atoms2 = molecule2.getAtoms(); boolean madeBond = false; int i = 0; int idMax = this.getMaximumId("a"); for (CMLAtom atomi : atoms) { int j = 0; for (CMLAtom atomj : atoms2) { if (CMLBond .areWithinBondingDistance(atomi, atomj)) { madeBond = true; if (atomi.getDistanceTo(atomj) < 0.2) { // remove overlapping atoms atomj.detach(); //LOG.debug("OVERLAP........................... " // + atomj.getId()); } else { // transfer atom and add new bond atomj.detach(); String id = "a" + (++idMax); atomj.setId(id); molecule.addAtom(atomj); molecule.addBond(new CMLBond(atomi, atomj)); } } j++; } i++; } if (madeBond) { // transfer rest of atoms from molecule 2 idMax = this.getMaximumId("a"); atoms2 = molecule2.getAtoms(); for (CMLAtom atom2 : atoms2) { atom2.detach(); String id = "a" + (++idMax); atom2.setId(id); molecule.addAtom(atom2); } calculateBondedAtoms(atoms2, molecule); } return madeBond; } /** * iterates through atom ids and finds the largest numerically. assumes they * are of form: "a123" and returns largest value of integer * * @param prefix * (e.g. "a") * @return largest integer following prefix */ private int getMaximumId(String prefix) { int max = -1; int l = prefix.length(); List atoms = molecule.getAtoms(); for (CMLAtom atom : atoms) { String id = atom.getId(); try { int ii = Integer.parseInt(id.substring(l)); max = (max < ii) ? ii : max; } catch (NumberFormatException e) { ;// } } return max; } /** * Generates and adds unique bond ids. * * Uses CMLBond.generateId. */ public void generateBondIds() { for (CMLBond bond : molecule.getBonds()) { bond.generateAndSetId(); } } /** * get the average atom-atom bond distance. * * This is primarily for scaling purposes and should not be taken too * seriously. * * @param type * @param omitHydrogens coordinates * @return average distance in origianl units. If no bonds returns negative. */ public double getAverageBondLength(CoordinateType type, boolean omitHydrogens) { double bondSum = 0.0; int nBonds = 0; for (CMLBond bond : molecule.getBonds()) { if (omitHydrogens && ( "H".equals(bond.getAtom(0).getElementType()) || "H".equals(bond.getAtom(1).getElementType())) ) { continue; } try { double length = bond.calculateBondLength(type); if (!Double.isNaN(length)) { LOG.trace("len "+length); bondSum += length; nBonds++; } } catch (RuntimeException e) { // no coordinates } } return (nBonds == 0 || Double.isNaN(bondSum) || Real.isZero(bondSum, Real.EPS)) ? Double.NaN : bondSum / ((double) nBonds); } /** * get the average atom-atom bond distance. * * This is primarily for scaling purposes and should not be taken too * seriously. * * @param type * @param omitHydrogens coordinates * @return average distance in origianl units. If no bonds returns negative. */ public void scaleAverage2DBondLength(double len) { boolean omitHydrogens = true; double averageBondLength = this.getAverageBondLength(CoordinateType.TWOD , omitHydrogens); double scale = len/averageBondLength; molecule.multiply2DCoordsBy(scale); } /** * get the average atom-atom bond distance. * * This is primarily for scaling purposes and should not be taken too * seriously. * * @param type * @return average distance in origianl units. If no bonds returns negative. */ public double getAverageBondLength(CoordinateType type) { return getAverageBondLength(type, false); } /** * get matched bond using atom mapping. does not work with sets * * if bond atomRefs2="a1 a2" and link to="a1" from="b1" // atomId and link * to="a2" from="b2" // atomId and toFrom = Direction.FROM then will return * bond atomRefs2="b1 b2" or atomRefs2="b2 b1" in molecule1 * * @param bond0 bond to search with. the values in must occur in a single * toFrom attribute * @param map with links * @param toFrom specifies attribute for search atoms in atomRefs2 * @return mapped bond or null */ public CMLBond getMappedBondViaAtoms(CMLMap map, CMLBond bond0, Direction toFrom) { CMLBond targetBond = null; CMLAtom atom0 = bond0.getAtom(0); CMLAtom atom1 = bond0.getAtom(1); if (atom0 != null && atom1 != null) { String targetRef0 = map.getRef(atom0.getId(), toFrom); String targetRef1 = map.getRef(atom1.getId(), toFrom); targetBond = molecule.getBond(molecule.getAtomById(targetRef0), molecule.getAtomById(targetRef1)); } return targetBond; } /** * make sub molecule. * * @param molecule * @param atomIds * @return molecule * */ public static CMLMolecule createMolecule(CMLMolecule molecule, String[] atomIds) { CMLMolecule newMolecule = new CMLMolecule(); for (String atomId : atomIds) { CMLAtom atom = new CMLAtom(atomId); newMolecule.addAtom(atom); } return newMolecule; } public CMLAtomSet createAtomSet(int[] serials) { CMLAtomSet atomSet = new CMLAtomSet(); for (int serial : serials) { CMLAtom atom = molecule.getAtom(serial); if (atom != null && !atomSet.contains(atom)) { atomSet.addAtom(atom); } } return atomSet; } /** * creates bonds and partitions molecules and returns contacts. currently * looks only for homo-molecule contacts. * * @param dist2Range * @param crystalTool * @return sorted contact list */ public List getSymmetryContacts(RealRange dist2Range, CrystalTool crystalTool) { this.createCartesiansFromFractionals(crystalTool.getCrystal()); this.calculateBondedAtoms(); List contactList = crystalTool.getSymmetryContactsToMolecule(dist2Range); Collections.sort(contactList); return contactList; } /** * convenience method to get molecule or its child molecules as a list. * * @return list of either just the molecule (if no children) or list of the * children */ public List getMoleculeList() { List moleculeList = molecule.getDescendantsOrMolecule(); return moleculeList; } /** * get linkers * * @return linkerMolecules */ public List getChainMolecules() { List linkers = new ArrayList(); List subMolList = molecule.getDescendantsOrMolecule(); for (CMLMolecule subMol : subMolList) { ConnectionTableTool ct = new ConnectionTableTool(subMol); List acyclicBonds = ct.getAcyclicBonds(); List cyclicBonds = ct.getCyclicBonds(); List acyclicBondAtoms = new ArrayList(); List cyclicBondAtoms = new ArrayList(); for (CMLBond acyclicBond : acyclicBonds) { for (CMLAtom atom : acyclicBond.getAtoms()) { acyclicBondAtoms.add(atom); } } for (CMLBond cyclicBond : cyclicBonds) { for (CMLAtom atom : cyclicBond.getAtoms()) { cyclicBondAtoms.add(atom); } } // if bond count equal to number of acyclic bonds then there // aren't any cyclic bonds in the molecule, hence no linkers if (subMol.getBondCount() != acyclicBonds.size()) { List acyclicAtoms = new ArrayList(); for (CMLAtom atom : subMol.getAtoms()) { if (acyclicBondAtoms.contains(atom) && !cyclicBondAtoms.contains(atom)) { acyclicAtoms.add(atom); } } CMLAtomSet atomSet = CMLAtomSet.createFromAtoms(acyclicAtoms); CMLMolecule newMol = MoleculeTool.createMolecule(atomSet); MoleculeTool.getOrCreateTool(newMol).calculateBondedAtoms(); new ConnectionTableTool(newMol).partitionIntoMolecules(); List linkerList = newMol.getDescendantsOrMolecule(); for (CMLMolecule mol : linkerList) { if (mol.getAtomCount() > 1) { linkers.add(mol); } } } } return linkers; } /** * get ring nuclei. calls connectionTableTool.getRingNucleiMolecules * * @return ringNucleiMolecules */ public List getRingNucleiMolecules() { ConnectionTableTool connectionTableTool = // not sure whether we have to clone the molecule // new ConnectionTableTool(new CMLMolecule(subMolecule)); new ConnectionTableTool(molecule); return connectionTableTool.getRingNucleiMolecules(); } /** * join one molecule to another. * manages the XML but not yet the geometry * empties the added molecule of elements and copies them * to this.molecule and then detaches the addedMolecule * @param addedMolecule to be joined * @param takeAtomWithLowestId DUMMY AT PRESENT */ public void addMoleculeTo(CMLMolecule addedMolecule, boolean takeAtomWithLowestId) { addMoleculeTo(addedMolecule); } /** add atoms. * calls addAtom * @param atomSet * @throws CMLRuntimeException * null, non-uniqueID, etc. */ public void addAtoms(CMLAtomSet atomSet) { for (CMLAtom atom : atomSet.getAtoms()) { molecule.addAtom(atom); } } /** add bonds. * calls addBond * @param bondSet * @throws RuntimeException * null, non-uniqueID, etc. */ public void addBonds(CMLBondSet bondSet) { for (CMLBond bond : bondSet.getBonds()) { molecule.addBond(bond); } } /** * join one molecule to another. * manages the XML but not yet the geometry * empties the added molecule of elements and copies them * to this.molecule and then detaches the addedMolecule * @param addedMolecule to be joined */ public void addMoleculeTo(CMLMolecule addedMolecule) { MoleculeTool addedMoleculeTool = MoleculeTool.getOrCreateTool(addedMolecule); addedMoleculeTool.renumberToUniqueIds(this.getAtomIdList()); molecule.getOrCreateAtomArray(); // bonds must be done first as atom.detach() destroys bonds List bondList = addedMolecule.getBonds(); for (CMLBond bond : bondList) { bond.detach(); molecule.addBond(bond); } List atomList = addedMolecule.getAtoms(); for (CMLAtom atom : atomList) { atom.detach(); molecule.addAtom(atom); } // copy any remaining nodes except atomArray and bondArray Nodes nodes = addedMolecule.query(".//*", CMLConstants.CML_XPATH); int nnodes = nodes.size(); for (int i = nnodes - 1; i >= 0; i--) { Node node = nodes.get(i); node.detach(); if (node instanceof CMLAtomArray) { // don't copy } else if (node instanceof CMLBondArray) { // don't copy } else { molecule.appendChild(node); } } addedMolecule.detach(); } /** * join one molecule to another. * manages the XML but not yet the geometry * empties the added molecule of elements and copies them * to this.molecule and then detaches the addedMolecule * @param addedMolecule to be joined */ public void addMoleculesTo(List addedMolecules) { if (addedMolecules != null) { for (CMLMolecule addedMolecule : addedMolecules) { this.addMoleculeTo(addedMolecule); } } } /** * @return list of atomIds in order */ public List getAtomIdList() { List idList = new ArrayList(); for (CMLAtom atom : molecule.getAtoms()) { idList.add(atom.getId()); } return idList; } /** * renumbers atoms in molecule so they do not clash with other Ids * iterates through "a1", "a2" finding the free spaces. * hydrogens will not be prettily numbered * @param avoidList ids already alocated in other molecules */ public void renumberToUniqueIds(List avoidList) { Set stringSet = checkUnique(avoidList); int totalSize = molecule.getAtomCount() + avoidList.size(); List fromIdList = new ArrayList(); List toIdList = new ArrayList(); // TODO hydrogens will not be pretty - int ii = 1; for (CMLAtom atom : molecule.getAtoms()) { String id = atom.getId(); if (stringSet.contains(id)) { fromIdList.add(id); String newId = null; while (ii < totalSize) { newId = "a"+ii; if (!stringSet.contains(newId) && molecule.getAtomById(newId) == null) { stringSet.add(newId); // new unique break; } ii++; } toIdList.add(newId); LOG.trace(id+" => "+newId); } } renumberAtomIds(fromIdList, toIdList); } /** adjust the cartesians to fit declared torsions. */ public void adjustTorsions() { List torsions = this.getTorsionElements(); for (CMLTorsion torsion : torsions) { TorsionTool.getOrCreateTool(torsion).adjustCoordinates(molecule); } } /** adjust the cartesians to fit declared angles. */ public void adjustAngles() { List angles = this.getAngleElements(); for (CMLAngle angle : angles) { AngleTool.getOrCreateTool(angle).adjustCoordinates(molecule); } } /** adjust the cartesians to fit declared lengths. */ public void adjustLengths() { List lengths = this.getLengthElements(); for (CMLLength length : lengths) { LengthTool lengthTool = LengthTool.getOrCreateTool(length); lengthTool.adjustCoordinates(molecule); } } /** iterates through torsions in molecule. * if they contain atom0 and atom1, adjusts them to value of torsion * @param atom0 * @param atom1 */ public void adjustTorsions(CMLAtom atom0, CMLAtom atom1) { if (molecule.hasCoordinates(CoordinateType.CARTESIAN)) { // set torsions CMLAtomSet moleculeAtomSet = this.getAtomSet(); Nodes torsions = molecule.query(".//"+CMLTorsion.NS, CMLConstants.CML_XPATH); int nTors = torsions.size(); for (int i = 0; i < nTors; i++) { CMLTorsion torsion = (CMLTorsion) torsions.get(i); TorsionTool torsionTool = TorsionTool.getOrCreateTool(torsion); String[] atomRefs4 = torsion.getAtomRefs4(); if (atomRefs4 != null && (atom0.getId().equals(atomRefs4[1]) && atom1.getId().equals(atomRefs4[2]) || atom0.getId().equals(atomRefs4[2]) && atom1.getId().equals(atomRefs4[1])) ) { double d = Double.NaN; try { d = torsion.getXMLContent(); } catch (RuntimeException e) { // no value given continue; // empty torsion; } CMLAtomSet moveableAtomSet = AtomTool.getOrCreateTool(atom1).getDownstreamAtoms(atom0); torsionTool.adjustCoordinates(new Angle(d, Angle.Units.DEGREES), moleculeAtomSet, moveableAtomSet); // this is to avoid the torsion being reused torsion.removeAttribute("atomRefs4"); } } } } public void adjustHydrogenLengths(double factor) { LOG.debug("hlen factor "+factor); List atoms = molecule.getAtoms(); for (CMLAtom atom : atoms) { if ("H".equals(atom.getElementType())) { Real2 xy = atom.getXY2(); if (xy != null) { List ligands = atom.getLigandAtoms(); if (ligands.size() == 1) { Real2 xyLig = ligands.get(0).getXY2(); Real2 vector = xy.subtract(xyLig); vector = vector.multiplyBy(factor); Real2 xy1 = xyLig.plus(vector); atom.setXY2(xy1); } } } } } /** flatten molecules. * * @param parent */ public void flattenMoleculeDescendants(ParentNode parent) { Elements moleculeLists = molecule.getChildCMLElements(CMLMoleculeList.TAG); List moleculeListList = new ArrayList(); for (int i = 0; i < moleculeLists.size(); i++) { moleculeListList.add((CMLMoleculeList) moleculeLists.get(i)); } for (CMLMoleculeList moleculeList : moleculeListList) { flattenMoleculeListDescendants(moleculeList, molecule); if (parent != null) { moleculeList.detach(); } else { CMLUtil.transferChildren(moleculeList, molecule); moleculeList.detach(); } } } private void flattenMoleculeListDescendants( CMLMoleculeList moleculeList, CMLMolecule parentMolecule) { ParentNode grandParent = parentMolecule.getParent(); int idx = grandParent.indexOf(parentMolecule); // moleculeList.debug("MOLL"); Elements childs = moleculeList.getChildElements(); for (int i = 0; i < childs.size(); i++) { Node child = childs.get(i); if (child instanceof Text) { child.detach(); } else { if (grandParent instanceof CMLElement) { child.detach(); grandParent.insertChild(child, ++idx); } if (child instanceof CMLMolecule) { MoleculeTool childTool = MoleculeTool.getOrCreateTool((CMLMolecule) child); childTool.flattenMoleculeDescendants(moleculeList); } } } } /** copies attributes on bonds and atoms to another molecule. * for each atom/bond in this.molecule finds Id and hence corresponding * atom/bond in 'to'. Copies all attributes from that atom to to.atom/@* * If corresponding atom does not exist, throws exception. * If target attribute exists throws exception * @param to * @param permitOverwrite allow existing attributes on target to be overwritten * @exception RuntimeException ids in molecules do not correspond or * attributes are already present */ public void copyAtomAndBondAttributesById(CMLMolecule to, boolean permitOverwrite) { copyAtomAttributesById(to, permitOverwrite); copyBondAttributesById(to, permitOverwrite); } /** copies attributes on atoms to another molecule. * for each atom in this.molecule finds Id and hence corresponding * atom in 'to'. Copies all attributes from that atom to to.atom/@* * If corresponding atom does not exist, throws exception. * If target attribute exists throws exception * @param to * @param permitOverwrite allow existing attributes on target to be overwritten * @exception RuntimeException ids in molecules do not correspond or * attributes are already present */ public void copyAtomAttributesById(CMLMolecule to, boolean permitOverwrite) { List fromAtoms = molecule.getAtoms(); for (CMLAtom fromAtom : fromAtoms) { String fromId = fromAtom.getId(); if (fromId != null) { CMLAtom toAtom = to.getAtomById(fromId); if (toAtom == null) { throw new RuntimeException("Cannot find target atom: "+fromId); } copyAttributes(fromAtom, toAtom, permitOverwrite); } } } /** copies attributes on bonds to another molecule. * for each bond in this.molecule finds Id and hence corresponding * bond in 'to'. Copies all attributes from that bond to to.bond/@* * If corresponding bond does not exist, throws exception. * If target attribute exists throws exception * @param to * @param permitOverwrite allow existing attributes on target to be overwritten * @exception RuntimeException ids in molecules do not correspond or * attributes are already present */ public void copyBondAttributesById(CMLMolecule to, boolean permitOverwrite) { List fromBonds = molecule.getBonds(); for (CMLBond fromBond : fromBonds) { String fromId = fromBond.getId(); if (fromId != null) { CMLBond toBond = to.getBondById(fromId); if (toBond == null) { throw new RuntimeException("Cannot find target bond: "+fromId); } copyAttributes(fromBond, toBond, permitOverwrite); } } } private void copyAttributes(Element from, Element to, boolean permitOverwrite) { List nodes = CMLUtil.getQueryNodes(from, "./@*"); for (Node node : nodes) { Attribute attribute = (Attribute) node; String name = attribute.getLocalName(); if ("id".equals(name)) { continue; } if (!permitOverwrite && to.getAttribute(name) != null) { throw new RuntimeException("cannot overwrite attribute: "+name); } Attribute newAttribute = new Attribute(name, attribute.getValue()); to.addAttribute(newAttribute); } } /** * @param type * @param contactDist * @return pairs of atoms which bump */ public List getBumps(CoordinateType type, double contactDist) { List bumpList = new ArrayList(); List atomList = molecule.getAtoms(); CMLAtomSet allAtomSet = new CMLAtomSet(molecule); int natoms = atomList.size(); for (int i = 0; i < natoms; i++) { CMLAtom atomi = atomList.get(i); if (AS.H.equals(atomi.getElementType())) { continue; } // get all atoms within 3 bonds CMLAtomSet atomSet13 = AtomTool.getOrCreateTool(atomi).getCoordinationSphereSet(3); CMLAtomSet nonBonded = allAtomSet.complement(atomSet13); List nonBondedAtomList = nonBonded.getAtoms(); for (CMLAtom atomj : nonBondedAtomList) { if (AS.H.equals(atomj.getElementType())) { continue; } if (atomi.getId().compareTo(atomj.getId()) <= 0) { continue; } boolean bump = false; double dist = Double.NaN; if (type == CoordinateType.CARTESIAN) { bump = atomi.isWithinRadiusSum(atomj, ChemicalElement.RadiusType.VDW); dist = atomi.getDistanceTo(atomj); } else if (type == CoordinateType.TWOD) { dist = atomi.getDistance2(atomj); if (dist < contactDist) { bump = true; } } if (bump) { AtomPair atomPair = new AtomPair(atomi, atomj); atomPair.setDistance(dist, type); bumpList.add(atomPair); } } } return bumpList; } /** * * @param molecule * @return */ public static AbstractSVGTool getOrCreateSVGTool(CMLMolecule molecule) { return (AbstractSVGTool) MoleculeTool.getOrCreateTool(molecule); } /** returns a "g" element * will require to be added to an svg element * @param drawable * @throws IOException * @return null if problem */ public SVGElement createGraphicsElement(CMLDrawable drawable) { SVGElement g = null; enableMoleculeDisplay(); Transform2 transform2 = new Transform2( new double[] { 1., 0., 0.0, 0., -1., 0.0, 0., 0., 1.} ); List atoms = molecule.getAtoms(); List bonds = molecule.getBonds(); setAtomBondAndGroupVisibility(atoms, bonds); if (atoms.size() == 0) { g = this.getCascadingFormulaOrTextSVGElement(); } else if (atoms.size() == 1) { g = this.getCascadingFormulaOrTextSVGElement(); } else { g = drawMultiAtomMolecule(drawable, g, transform2); } return g; } private SVGElement getCascadingFormulaOrTextSVGElement() { SVGElement gg = getCascadingFormulaSVGElement(); if (gg == null) { gg = getCascadingTextSVGElement(); } return gg; } private SVGG getCascadingTextSVGElement() { String text = getCascadingTextElement(); if (text == null) { text = "Cannot create meaningful text"; } return createTextWithBoundingBox(text); } private SVGGBox createTextWithBoundingBox(String text) { SVGText textSVGElement = new SVGText(new Real2(0.0, 0.0), text); textSVGElement.setFontSize(10.0); SVGRect rect = textSVGElement.getBoundingSVGRect(); SVGGBox box = new SVGGBox(); box.appendChild(textSVGElement); box.appendChild(rect); return box; } private String getCascadingTextElement() { String text = null; CMLName name = getCascadingName(); if (name != null) { text = name.getValue(); } if (text == null) { CMLLabel label = getCascadingLabel(); if (label != null) { text = label.getCMLValue(); } } if (text == null) { text = molecule.getTitle(); } if (text == null) { text = molecule.getId(); } return text; } private CMLLabel getCascadingLabel() { // fixme - maybe use more complex strategy CMLElements labels = molecule.getLabelElements(); return (labels.size() == 0) ? null : (CMLLabel) labels.get(0); } private CMLName getCascadingName() { // fixme - maybe use more complex strategy CMLElements names = molecule.getNameElements(); return (names.size() == 0) ? null : (CMLName) names.get(0); } private SVGElement getCascadingFormulaSVGElement() { String formulaS = null; CMLElements formulas = molecule.getFormulaElements(); if (formulas.size() > 0) { for (CMLFormula formula : formulas) { formulaS = formula.getConcise(); if (formulaS == null) { formulaS = formula.getInline(); } if (formulas != null) { break; } } } else { CMLFormula formula = new CMLFormula(molecule); formulaS = formula.getConcise(); } SVGElement textSVGElement = null; if (formulaS != null) { textSVGElement = createTextWithBoundingBox(formulaS); } return textSVGElement; } private SVGElement drawMultiAtomMolecule(CMLDrawable drawable, SVGElement g, Transform2 transform2) { if (applyScale) { transform2 = scaleToBoundingBoxesAndScreenLimits(transform2); } if(drawable instanceof MoleculeDisplayList) { g = ((MoleculeDisplayList) drawable).getSvg(); } if (g == null) { g = createSVGElement(drawable, transform2); g.setProperties(moleculeDisplay); } double avlength = MoleculeTool.getOrCreateTool(molecule).getAverageBondLength(CoordinateType.TWOD); BondDisplay defaultBondDisplay = moleculeDisplay.getDefaultBondDisplay(); defaultBondDisplay.setScale(avlength); displayBonds(drawable, g, defaultBondDisplay); displayAtoms(drawable, g, avlength); ensureBoundingRect(); displayFormula(drawable, g); displayMoleculeLabels(drawable, g); displayMoleculeNames(drawable, g); if (moleculeDisplay.isDrawBoundingBox()) { drawBoundingBox(drawable, g); scaleToScreen(g); } if (drawable != null) { try { drawable.output(g); } catch (IOException e) { throw new RuntimeException(e); } } return g; } private void scaleToScreen(SVGElement g) { if (molecule.getMoleculeElements().size() == 0) { if (moleculeDisplay.getMaxScreenSize() != null || moleculeDisplay.getMinScreenSize() != null) { double scale = 1.0; scale = scaleToMaximumScreen(scale); scale = scaleToMinimumScreen(scale); Transform2 t2 = g.getTransform2FromAttribute(); t2.multiplyBy(scale); g.setTransform(t2); } } } private double scaleToMaximumScreen(double scale) { Real2 maxScreenSize = moleculeDisplay.getMaxScreenSize(); if (maxScreenSize != null) { Real2 scales = getScales(maxScreenSize); if (scales != null) { double scalex = Math.min(scale, scales.getX()); double scaley = Math.min(scale, scales.getY()); scale = Math.min(scale, Math.min(scalex, scaley)); } } return scale; } private double scaleToMinimumScreen(double scale) { Real2 minScreenSize = moleculeDisplay.getMinScreenSize(); if (minScreenSize != null) { Real2 scales = getScales(minScreenSize); if (scales != null) { double scalex = Math.max(scale, scales.getX()); double scaley = Math.max(scale, scales.getY()); scale = Math.max(scale, Math.max(scalex, scaley)); } } return scale; } private Real2 getScales(Real2 screenSize) { Real2 scales = null; if (boundingRectSVG != null) { scales = new Real2(); scales.x = screenSize.x / boundingRectSVG.getWidth(); scales.y = screenSize.y / boundingRectSVG.getHeight(); } return scales; } private void displayAtoms(CMLDrawable drawable, SVGElement g, double avlength) { AtomDisplay defaultAtomDisplay = moleculeDisplay.getDefaultAtomDisplay(); defaultAtomDisplay.setScale(avlength); if (molecule.getAtomCount() == 1) { defaultAtomDisplay.setDisplayCarbons(true); } displayAtoms(drawable, g, defaultAtomDisplay); } private void drawBoundingBox(CMLDrawable drawable, SVGElement g) { ensureBoundingRect(); if (boundingRectSVG != null) { boundingRectSVG.setStroke(moleculeDisplay.getBoundingBoxBundle().getStroke()); boundingRectSVG.setStrokeWidth(moleculeDisplay.getBoundingBoxBundle().getStrokeWidth()); boundingRectSVG.setStrokeWidth(moleculeDisplay.getBoundingBoxBundle().getOpacity()); appendNonNullChild(g, boundingRectSVG); } } private void ensureBoundingRect() { calculateBoundingBox2D(); if (userBoundingBox != null && userBoundingBox.getXRange() != null && userBoundingBox.getYRange() != null) { double bondLength = moleculeDisplay.getBondLength(); Real2 xy0 = new Real2( userBoundingBox.getXRange().getMin() - bondLength, userBoundingBox.getYRange().getMin() - bondLength ); Real2 xy1 = new Real2( userBoundingBox.getXRange().getMax() + bondLength, userBoundingBox.getYRange().getMax() + bondLength ); boundingRectSVG = new SVGRect(xy0, xy1); } } private void setAtomBondAndGroupVisibility( List atoms, List bonds) { if (/*true || */ Level.DEBUG.equals(LOG.getLevel())) { molecule.debug("GROUP VISIBILITY ON"); } // add atomDisplay to each atomTool for (CMLAtom atom : atoms) { AtomTool atomTool = AtomTool.getOrCreateTool(atom); // FIXME PMR atomTool.ensureAtomDisplay(); atomTool.getAtomDisplay().setMoleculeDisplay(moleculeDisplay); // group visibility (NYI) if (atomTool.isGroupRoot()) { atomTool.ensureAtomDisplay(); atomTool.setDisplay(false); } } // add bondDisplay to each bondTool for (CMLBond bond : bonds) { BondTool bondTool = BondTool.getOrCreateTool(bond); bondTool.ensureBondDisplay(); bondTool.getBondDisplay().setMoleculeDisplay(moleculeDisplay); } } private Transform2 scaleToBoundingBoxesAndScreenLimits(Transform2 transform2) { try { calculateBoundingBox2D(); // Real2Range moleculeBoundingBox = AtomSetTool.getOrCreateTool( // new CMLAtomSet(molecule)).getExtent2(); Real2Interval screenBoundingBox = moleculeDisplay.getScreenExtent(); // Real2Interval moleculeInterval = new Real2Interval(moleculeBoundingBox); Real2Interval moleculeInterval = new Real2Interval(userBoundingBox); double scale = moleculeInterval.scaleTo(screenBoundingBox); double[] offsets = moleculeInterval.offsetsTo(screenBoundingBox, scale); transform2 = new Transform2 ( new double[] { scale, 0., offsets[0], 0.,-scale, offsets[1], 0., 0., 1.} ); } catch (NullPointerException npe) { // happens with small number of atoms } return transform2; } public Real2Range calculateBoundingBox2D() { userBoundingBox = null; if (molecule != null) { AtomSetTool atomSetTool = AtomSetTool.getOrCreateTool(new CMLAtomSet(molecule)); userBoundingBox = (atomSetTool == null) ? null : atomSetTool.getExtent2(); } return userBoundingBox; } public Real3Range calculateBoundingBox3D() { Real3Range userBoundingBox3 = null; if (molecule != null) { AtomSetTool atomSetTool = AtomSetTool.getOrCreateTool(new CMLAtomSet(molecule)); userBoundingBox3 = (atomSetTool == null) ? null : atomSetTool.getExtent3(); } return userBoundingBox3; } private void displayAtoms(CMLDrawable drawable, SVGElement g, AtomDisplay atomDisplay) { for (CMLAtom atom : molecule.getAtoms()) { AtomTool atomTool = getOrCreateAtomTool(atom); atomTool.ensureAtomDisplay(); atomTool.getAtomDisplay().setMoleculeDisplay(moleculeDisplay); atomTool.setMoleculeTool(this); if (atomTool.getAtomDisplay().omitAtom(atom)) { continue; } GraphicsElement atomSvg = atomTool.createGraphicsElement(drawable); appendNonNullChild(g, atomSvg); } } private void displayBonds( CMLDrawable drawable, SVGElement g, BondDisplay bondDisplay) { for (CMLBond bond : molecule.getBonds()) { BondTool bondTool = getOrCreateBondTool(bond); bondTool.setBondDisplay(bondDisplay); bondTool.setMoleculeTool(this); if (!bondTool.getBondDisplay().omitBond(bond)) { SVGElement bondSvg = bondTool.createGraphicsElement(drawable); appendNonNullChild(g, bondSvg); } } } private void displayMoleculeNames(CMLDrawable drawable, SVGElement g) { if (moleculeDisplay.isDisplayNames()) { // Real2 displayPoisition = moleculeDisplay.getNameXPosition() double x = 50.; double y = 0.; // double y = 50.; double deltay = 20.0; int i = 0; Transform2 transform2 = new Transform2( new double[] { 1., 0., 0.0, 0., -1., 0.0, 0., 0., 1.} ); for (CMLName name : molecule.getNameElements()) { SVGText text = new SVGText(new Real2(x, y+(i*deltay)), name.getValue()); if (moleculeDisplay.getLabelFontSize() != null) { text.setFontSize(moleculeDisplay.getLabelFontSize()); } text.setTransform(transform2); appendNonNullChild(g, text); i++; } } } private void displayMoleculeLabels(CMLDrawable drawable, SVGElement g) { if (moleculeDisplay.isDisplayLabels()) { calculateBoundingBox2D(); RealRange xRange = userBoundingBox.getXRange(); if (xRange != null) { double xMid = xRange.getMidPoint(); double maxY = userBoundingBox.getYRange().getMax(); Transform2 transform2 = new Transform2( new double[] { 1., 0., 0.0, 0., -1., 0.0, 0., 0., 1.} ); if (molecule.getLabelElements().size() > 0) { displayLabels(g, xMid, maxY, transform2); } } } } private void displayLabels(SVGElement g, double xMid, double maxY, Transform2 transform2) { double labelFontSize = moleculeDisplay.getLabelFontSize(); double labelYSpacing = moleculeDisplay.getLabelYSpacing(); // double yLabel0 = maxY + labelYSpacing * labelFontSize; double yLabel0 = maxY; for (CMLLabel label : molecule.getLabelElements()) { String labelS = label.getCMLValue(); double xLabel = xMid - labelFontSize * (labelS.length() / 2.); double yLabel = labelYSpacing * labelFontSize; SVGText text = new SVGText(new Real2(xLabel, yLabel0 + yLabel), labelS); text.setFontWeight("bold"); text.setFontSize(labelFontSize); text.setTransform(transform2); appendNonNullChild(g, text); } } private void displayFormula(CMLDrawable drawable, SVGElement g) { if (moleculeDisplay.isDisplayFormula() && molecule.getFormulaElements().size() > 0) { CMLFormula formula = molecule.getFormulaElements().get(0); FormulaTool formulaTool = FormulaTool.getOrCreateTool(formula); SVGElement f = formulaTool.createGraphicsElement(drawable); if (f != null) { Real2 formulaOffsets = calculateFormulaOffsets(); Transform2 transform2 = new Transform2( new double[] { 1., 0., formulaOffsets.getX(), 0., -1., formulaOffsets.getY(), 0., 0., 1.} ); f.setTransform(transform2); appendNonNullChild(g, f); } } } private Real2 calculateFormulaOffsets() { Real2 offsets = new Real2(getBoundingRectOffsets()); if (moleculeDisplay.getFormulaXPosition().equals(Position.LEFT)) { offsets.x += moleculeDisplay.getFormulaDisplay().getFontSize() * 0.5; } if (moleculeDisplay.getFormulaYPosition().equals(Position.BOTTOM)) { offsets.y += moleculeDisplay.getFormulaDisplay().getFontSize() * 0.5; } else if (moleculeDisplay.getFormulaYPosition().equals(Position.TOP)) { if (boundingRectSVG != null) { offsets.y = boundingRectSVG.getY(); offsets.y += boundingRectSVG.getHeight(); offsets.y -= moleculeDisplay.getFormulaDisplay().getFontSize() * 1.5; } } return offsets; } private Real2 calculateNameOffsets() { Real2 offsets = new Real2(getBoundingRectOffsets()); if (moleculeDisplay.getNameXPosition().equals(Position.LEFT)) { offsets.x += moleculeDisplay.getNameDisplay().getFontSize() * 0.2; } if (moleculeDisplay.getNameYPosition().equals(Position.BOTTOM)) { offsets.y += moleculeDisplay.getNameDisplay().getFontSize() * 0.5; } else if (moleculeDisplay.getNameYPosition().equals(Position.TOP)) { offsets.y = boundingRectSVG.getY(); offsets.y += moleculeDisplay.getNameDisplay().getFontSize() * 0.5; } return offsets; } private Real2 getBoundingRectOffsets() { Real2 offsets = new Real2(0.0, 0.0); if (boundingRectSVG != null) { offsets.x = boundingRectSVG.getX(); offsets.y = boundingRectSVG.getY(); } return offsets; } public void enableMoleculeDisplay() { if (moleculeDisplay == null) { moleculeDisplay = MoleculeDisplay.getDEFAULT(); } } /** * @return the atomToolMap */ public Map getAtomToolMap() { return atomToolMap; } /** * @return the bondToolMap */ public Map getBondToolMap() { return bondToolMap; } /** * @return the selectionTool */ public SelectionTool getOrCreateSelectionTool() { if (selectionTool == null) { selectionTool = new SelectionTool(); } return selectionTool; } /** * @return the selectionTool */ public SelectionTool getSelectionTool() { return selectionTool; } /** * @param selectionTool the selectionTool to set */ public void setSelectionTool(SelectionTool selectionTool) { this.selectionTool = selectionTool; } /** * @return the MoleculeDisplay */ public MoleculeDisplay getMoleculeDisplay() { if (moleculeDisplay == null) { moleculeDisplay = new MoleculeDisplay(this); } return moleculeDisplay; } /** * @param MoleculeDisplay the MoleculeDisplay to set */ public void setMoleculeDisplay(MoleculeDisplay MoleculeDisplay) { this.moleculeDisplay = MoleculeDisplay; } // // public void setCoordinateAxes(CoordinateType type, AxisType axis) { // if (CoordinateType.CARTESIAN.equals(type)) { //// String hand = molecule.get // } else { // throw new RuntimeException("May not change 3D axes handedness"); // } // } /** * @return the currentAtom */ public CMLAtom getCurrentAtom() { return currentAtom; } /** * @param currentAtom the currentAtom to set */ public void setCurrentAtom(CMLAtom currentAtom) { this.currentAtom = currentAtom; } /** * @return the currentBond */ public CMLBond getCurrentBond() { return currentBond; } /** * @param currentBond the currentBond to set */ public void setCurrentBond(CMLBond currentBond) { this.currentBond = currentBond; } /** * clears currentBond and then ensureCurrentBond() * @return currentBond */ public CMLBond resetCurrentBond() { this.currentBond = null; ensureCurrentBond(); return currentBond; } /** makes sure there is a current atom. * if none set, select first atom (fragile) * @return current atom or null if no atoms */ public CMLAtom ensureCurrentAtom() { if (currentAtom == null && molecule.getAtomCount() > 0) { currentAtom = molecule.getAtoms().get(0); } return currentAtom; } /** makes sure there is a current bond. * if none set, select first ligandBond on current atom (fragile) * @return current bond or null if currentAtom is null or has no ligands */ public CMLBond ensureCurrentBond() { if (currentBond == null && currentAtom != null) { List ligandBonds = currentAtom.getLigandBonds(); if (ligandBonds.size() > 0) { currentBond = ligandBonds.get(0); } } return currentBond; } /** steps through ligands of currentAtom. * if currentBond contains currentAtom, steps through * ligands of currentAtom else no-op * @return next ligand bond or null if no currentBond */ public CMLBond incrementCurrentBond() { ensureCurrentBond(); if (currentBond != null) { List ligands = currentAtom.getLigandBonds(); int idx = ligands.indexOf(currentBond); if (idx != -1) { idx = (idx+1) % ligands.size(); currentBond = ligands.get(idx); } } return currentBond; } /** * normalize all molecules which are descendant of node * @param node */ public static void normalizeDescendantMolecules(Node node) { Nodes molecules = node.query(".//*[local-name()='molecule']"); for (int i = 0; i < molecules.size(); i++) { MoleculeTool.getOrCreateTool((CMLMolecule)molecules.get(i)).normalize(); } } /** normalize. * may be obsolete * currently adjusts Hydrogen counts to valency * also adds formula representing atomArray contents */ public void normalize() { this.adjustHydrogenCountsToValency( HydrogenControl.ADD_TO_HYDROGEN_COUNT); Nodes nodes = molecule.query("./*[local-name()='formula' and @convention='"+Convention.ATOMARRAY+"']"); for (int i = 0; i < nodes.size(); i++) { nodes.get(i).detach(); } CMLFormula formula = new CMLFormula(molecule); formula.setConvention(Convention.ATOMARRAY.v); molecule.appendChild(formula); } /** renumbers atoms in molecule. * uses renumberAtomId * includes any attributes including atomRef/atomRefs2/atomRefs3/atomRefs4/atomRefs * @param fromIdList * @param toIdList * @throws RuntimeException if toId is already in molecule or if lists contain duplicates */ public void renumberAtomIds(List fromIdList, List toIdList) { if (fromIdList == null || toIdList == null) { throw new RuntimeException("lists must not be null"); } if (fromIdList.size() != toIdList.size()) { throw new RuntimeException("Lists must be of equal size, "+fromIdList.size()+ " / "+toIdList.size()); } checkUnique(fromIdList); checkUnique(toIdList); for (int i = 0; i < fromIdList.size(); i++) { renumberAtomId(fromIdList.get(i), toIdList.get(i)); } generateBondIds(); } /** force renumbering to a1, a2... a(size) * */ public void renumberAtomIdsSequentially() { List fromIdList = this.getAtomIds(); List toIdList = MoleculeTool.createSequentialAtomIds("a", fromIdList.size()); renumberAtomIds(fromIdList, toIdList); } /** returns a list of ids idRoot+1, idRoot+2 ... idRoot+size * * @param idRoot (e.g. "a" * @param size * @return list (e.g.) a1, a2 ... */ public static List createSequentialAtomIds(String idRoot, int size) { List atomIds = new ArrayList(size); for (int i = 1; i <= size; i++) { atomIds.add(idRoot+i); } return atomIds; } /** return atom ids in order * */ public List getAtomIds() { List atoms = molecule.getAtoms(); List atomIds = new ArrayList(atoms.size()); for (int i = 0, max = atoms.size(); i < max; i++) { atomIds.add(atoms.get(i).getId()); } return atomIds; } /** * for each bond apply Bond.createId */ public void createBondIdsFromAtomRefs2() { List bonds = molecule.getBonds(); for (CMLBond bond : bonds) { String id = bond.createId(); bond.setId(id); } } private Set checkUnique(List stringList) { Set stringSet = new HashSet(); for (String string : stringList) { if (stringSet.contains(string)) { throw new RuntimeException("duplicate id: "+string); } stringSet.add(string); } return stringSet; } /** renumbers atom in molecule. * includes any attributes including atomRef/atomRefs2/atomRefs3/atomRefs4/atomRefs * @param fromId * @param toId * @throws RuntimeException if toId is already in molecule */ public void renumberAtomId(String fromId, String toId) { if (molecule.getAtomById(toId) != null) { throw new RuntimeException("Atom with id already in molecule: "+toId); } CMLAtom atom = molecule.getAtomById(fromId); if (atom != null) { Nodes nodes = molecule.query( ".//*[@atomRef or @atomRefs2 or @atomRefs3 or @atomRefs4 or @atomRefs]", CMLConstants.CML_XPATH); for (int i = 0; i < nodes.size(); i++) { renumberAtomRefs((CMLElement)nodes.get(i), fromId, toId); } atom.resetId(toId); } } private void renumberAtomRefs(CMLElement element, String fromId, String toId) { for (int i = 0; i < element.getAttributeCount(); i++) { Attribute attribute = element.getAttribute(i); if (attribute.getLocalName().indexOf("atomRef") != -1) { String[] values = attribute.getValue().split(S_WHITEREGEX); for (int j = 0; j < values.length; j++) { if (values[j].equals(fromId)) { values[j] = toId; } } attribute.setValue(Util.concatenate(values, CMLConstants.S_SPACE)); element.addAttribute(attribute); } } } /** get single electron by id * @param id * @return electron */ public CMLElectron getElectronById(String id) { Nodes electronNodes = molecule.query(".//cml:electron[@id='"+id+"']", CMLConstants.CML_XPATH); if (electronNodes.size() > 1) { throw new RuntimeException("Electrons with duplicate id:"+id); } return (electronNodes.size() == 0) ? null : (CMLElectron) electronNodes.get(0); } /** gets molar volume. * if molecule has a child property, uses that. * else if molecule has a child property for density uses that * else fails * new property has units of CMLCUBED * @return property with molarVolume */ public CMLProperty getMolarVolume() { CMLProperty volume = CMLProperty.getProperty(molecule, CMLProperty.Prop.MOLAR_VOLUME.value); if (volume == null) { CMLProperty density = CMLProperty.getProperty(molecule, CMLProperty.Prop.DENSITY.value); if (density == null) { throw new RuntimeException("Cannot calculate molar volume without density"); } if (!Units.GRAM_PER_CMCUBED.value.equals(density.getUnits())) { throw new RuntimeException("Cannot use density without units=g.cm-3"); } double densityV = density.getDouble(); double volumeV = this.getCalculatedMolecularMass() / densityV; volume = new CMLProperty(CMLProperty.Prop.MOLAR_VOLUME.value, volumeV, Units.CMCUBED.value); } return volume; } /** gets molar mass. * if molecule has a child property, uses that. * else if molecule has atomArray uses that to count atoms * else if molecule has a child formula uses that * else fails * new property has units of Units.GRAM_PER_MOLE * @return property with molarMass */ public CMLProperty getMolarMass() { CMLProperty mass = CMLProperty.getProperty(molecule, CMLProperty.Prop.MOLAR_MASS.value); if (mass == null) { double massV = this.getCalculatedMolecularMass(); if (!Double.isNaN(massV)) { mass = new CMLProperty(CMLProperty.Prop.MOLAR_MASS.value, massV, Units.GRAM_PER_MOLE.value); } } return mass; } /** fits two aligned molecules. * normally connection tables will be identical * ignores any atoms for which there are no 3D coordinates * also ignores any multiple atom sets in map * @param map atom-atom map * @param molecule * * @return */ MoleculePair fitToMoleculeTool(CMLMap map, CMLMolecule moleculeRef) { MoleculeTool moleculeRefTool = MoleculeTool.getOrCreateTool(moleculeRef); List atomListi = this.getAtomList(map, Direction.FROM); Point3Vector p3vi = AtomTool.getPoint3Vector(atomListi); List atomListj = moleculeRefTool.getAtomList(map, Direction.TO); Point3Vector p3vj = new Point3Vector(AtomTool.getPoint3Vector(atomListj)); Point3Vector.removeNullValues(p3vi, p3vj); p3vj = new Point3Vector(p3vj); Transform3 t3 = null; if (p3vi.size() > 3) { t3 = p3vj.fitTo(p3vi); Point3Vector pvtemp = new Point3Vector(p3vj); pvtemp.transform(t3); LOG.debug("RMS "+pvtemp.rms(p3vi)+" / \n"); } List fromSets = map.getFromSetRefs(); // for (String[] from : fromSets) { // for (String s : from) { // Util.prints+" "); // } // LOG.debug("..."); // } List toSets = map.getToSetRefs(); // for (String[] to : toSets) { // for (String s : to) { // Util.prints+" "); // } // LOG.debug("..."); // } AtomMatcher atomMatcher = new AtomMatcher3D(); for (int i = 0; i < fromSets.size(); i++) { CMLAtomSet atomSet1 = new CMLAtomSet(molecule, fromSets.get(i)); CMLAtomSet atomSet2 = new CMLAtomSet(moleculeRef, toSets.get(i)); CMLMap geomMap = ((AtomMatcher3D)atomMatcher).match(atomSet1, atomSet2, t3); geomMap.debug("GEOMMAP"); } MoleculePair moleculePair = new MoleculePair(this.molecule, moleculeRef); moleculePair.setTransform3(t3); moleculePair.setMap(map); return moleculePair; } /** * joins RGroups to molecule. * Looks for atoms with elementType="R" and a child label * cml:atom[@elementYype='R' and cml:label] * takes value of label ( labelS = cml:label/@value) * Looks for correspoding defintion of R as a descendant cml:molecule within * scopeElement, * i.e. scopeElement.query(".//cml:atom[label/@value=$labelS", CMLConstants.CML_XPATH) * @deprecated doen't work at all */ public void joinRGroupsExplicitly(Element scopeElement) { List atomList = molecule.getAtoms(); for (CMLAtom atom : atomList) { if ("R".equals(atom.getElementType())) { // AtomTool atomTool = AtomTool.getOrCreateTool(atom); //FIXME // CMLAtom refAtom = atomTool.getReferencedGroup(scopeElement); } } } /** removes all child nodes except atomArray and bondArray and molecule. * This is because many public programs only accept these nodes */ public void stripNonArray() { Nodes nodes = molecule.query( "*[not(self::cml:atomArray) and " + "not(self::cml:bondArray) and "+ "not(self::cml:molecule)]", CML_XPATH); for (int i = 0; i < nodes.size(); i++) { nodes.get(i).detach(); } } /** * calculates Morgan (caches result) * @return cached Morgan */ public Morgan calculateMorgan() { if (morgan == null) { morgan = new Morgan(molecule); } return morgan; } /** * calculate list of atom equivalence classes * @return list of equivalent atoms as atomSets */ public List calculateListOfMorganAtomMatches() { calculateMorgan(); List atomSetList = morgan.getAtomSetList(); return atomSetList; } /** * * @return morgan string */ public String calculateMorganString() { calculateMorgan(); String morganString = morgan.getEquivalenceString(); return morganString; } /** sets morgan to null * */ public void clearMorgan() { this.morgan = null; } /** converts a mass to a molarAmount. * @param molecule * @return molar Amount (units are mol) */ public CMLAmount getMolarAmount(CMLAmount amount0) { CMLAmount molarAmount = null; UnitsAttribute units = (UnitsAttribute) amount0.getUnitsAttribute(); if (units == null) { throw new RuntimeException("No units given on amount"); } String unitValue = (String) units.getCMLValue(); if (Units.GRAM.value.equals(unitValue)) { double d = this.getCalculatedMolecularMass(HydrogenControl.NO_EXPLICIT_HYDROGENS); double amountx = amount0.getXMLContent(); molarAmount = new CMLAmount(); molarAmount.setUnits(Units.MMOL.value); molarAmount.setXMLContent(amountx / d); } else { throw new RuntimeException("Cannot handle units other than gram"); } return molarAmount; } /** converts a volume to a molarAmount. * @param molecule (must contain data for molarVolume - moleculeTool.getMolarVolume()) * @return molar Amount (units are mol) */ public CMLAmount getMolarAmountFromVolume(CMLAmount amount0) { CMLAmount molarAmount = null; UnitsAttribute units = (UnitsAttribute) amount0.getUnitsAttribute(); if (units == null) { throw new RuntimeException("No units given on amount"); } String unitValue = (String) units.getCMLValue(); if (unitValue != null && Units.ML.value.equals(unitValue)) { CMLProperty volume = MoleculeTool.getOrCreateTool(molecule).getMolarVolume(); if (volume != null) { double amountv = amount0.getXMLContent(); molarAmount = new CMLAmount(); molarAmount.setUnits(Units.MOL.value); molarAmount.setXMLContent(amountv / volume.getDouble()); } } return molarAmount; } /** * Calculate formula. * * @param control * USE_EXPLICIT_HYDROGENS (do not use hydrogenCount) OR * USE_HYDROGEN_COUNT (use hydrogenCount and ignore explicit H) * @throws RuntimeException * @return formula */ public CMLFormula calculateFormula(HydrogenControl control) throws RuntimeException { CMLFormula form = new CMLFormula(); if (!control.equals(HydrogenControl.USE_HYDROGEN_COUNT) && !control.equals(HydrogenControl.USE_EXPLICIT_HYDROGENS)) { throw new RuntimeException( "No hydrogen count control on Formula - found(" + control + CMLConstants.S_RBRAK); } CMLAtomSet atomSet = CMLAtomSet.createFromAtoms(molecule.getAtoms()); form = atomSet.getCalculatedFormula(control); return form; } /** * creates and adds cartesian coordinates from crystal and fractional * coordinates. * * @param crystal */ public void createCartesiansFromFractionals(CMLCrystal crystal) { this.createCartesiansFromFractionals(crystal .getOrthogonalizationMatrix()); } /** * creates and adds cartesian coordinates from orthogonalizationMatrix and * fractional coordinates. * * @param orthogonalMatrix */ public void createCartesiansFromFractionals( Transform3 orthogonalMatrix) { for (CMLAtom atom : molecule.getAtoms()) { Point3 point = atom.getPoint3(CoordinateType.FRACTIONAL); if (point != null) { point = point.transform(orthogonalMatrix); atom.setPoint3(point, CoordinateType.CARTESIAN); } } } /** * creates and adds cartesian coordinates from orthogonalizationMatrix and * fractional coordinates. * * @param orthogonalMatrix * @throws RuntimeException */ void createCartesiansFromFractionals( RealSquareMatrix orthogonalMatrix) { Transform3 t = null; if (orthogonalMatrix == null || orthogonalMatrix.getCols() != 3) { throw new RuntimeException("invalid or null orthogonalMatrix"); } try { t = new Transform3(orthogonalMatrix); } catch (EuclidRuntimeException e) { throw new RuntimeException("invalid orthogonalMatrix"); } this.createCartesiansFromFractionals(t); } /** * finds angle in XOM corresponding to 3 atoms. * * @param at0 * first atom * @param at1 * middle atom * @param at2 * third atom * @return CMLAngle in XOM * @throws RuntimeException */ public CMLAngle getAngle(CMLAtom at0, CMLAtom at1, CMLAtom at2) throws RuntimeException { if (at0 == null || at1 == null || at2 == null) { throw new RuntimeException("FindAngle: null atom(s)"); } for (CMLAngle angle : this.getAngleElements()) { String[] atomRefs3 = angle.getAtomRefs3(); if (atomRefs3 == null) { continue; } CMLAtom a1 = molecule.getAtomById(atomRefs3[1]); if (!at1.equals(a1)) { continue; } CMLAtom a0 = molecule.getAtomById(atomRefs3[0]); CMLAtom a2 = molecule.getAtomById(atomRefs3[2]); if ((a0.equals(at0) && a2.equals(at2)) || (a0.equals(at2) && a2.equals(at0))) { return angle; } } return null; } /** * calculates formula for molecule or each molecule child. * * @param control * treatment of hydrogens */ public void calculateAndAddFormula(HydrogenControl control) { List molecules = this.getMoleculeElements(); if (molecules.size() > 0) { for (CMLMolecule molecule1 : molecules) { MoleculeTool moleculeTool1 = MoleculeTool.getOrCreateTool(molecule1); moleculeTool1.calculateAndAddFormula(control); } } else { CMLFormula formula = null; formula = this.calculateFormula(control); molecule.appendChild(formula); } } /** * get atomSet for all atoms. * * @return the atomSet */ public CMLAtomSet getAtomSet() { return new CMLAtomSet(molecule); } /** * get formula. * if molecule has atoms uses those, else uses formula else null * @param control * @return calculated formula * @throws RuntimeException */ public CMLFormula getCalculatedFormula(CMLMolecule.HydrogenControl control) { CMLFormula formula = null; CMLAtomSet atomSet = getAtomSet(); if (atomSet == null || atomSet.size() == 0) { formula = this.getFormulaElements().get(0); } else { formula = atomSet.getCalculatedFormula(control); } return formula; } /** * get calculated molecular mass. * @param control * @return calculated molecular mass. * @throws RuntimeException unknown/unsupported element type (Dummy counts as zero mass) */ public double getCalculatedMolecularMass(CMLMolecule.HydrogenControl control) throws RuntimeException { CMLFormula formula = this.getCalculatedFormula(control); if (formula == null) { throw new RuntimeException("Cannot calculate formula"); } return formula.getCalculatedMolecularMass(); } // /** // * get calculated molecular mass. Assumes correct hydrogen count // * @return calculated molecular mass. // * @throws RuntimeException unknown/unsupported element type (Dummy counts as zero mass) // * @deprecated use MoleculeTool.getCalculatedMolecularMass() // */ // public double getCalculatedMolecularMass() throws RuntimeException { // return this.getCalculatedMolecularMass(HydrogenControl.NO_EXPLICIT_HYDROGENS); // } /** calculate 3D centroid. * * @param type CARTESIAN or FRACTIONAL * @return centroid of 3D coords or null */ public Point3 calculateCentroid3(CoordinateType type) { CMLAtomSet atomSet = getAtomSet(); return atomSet.getCentroid3(type); } /** calculate 3D range. * * @param type CARTESIAN or FRACTIONAL * @return range of 3D coords or null */ public Real3Range calculateRange3(CoordinateType type) { CMLAtomSet atomSet = getAtomSet(); return atomSet.calculateRange3(type); } /** * get id mapping between molecules of equal size. * * default is to assume atoms in same order. map is owned by this document * but is not appended to molecule. * * @param mol2 * to compare * @exception RuntimeException * atoms cannot be mapped * @return the map */ public CMLMap getMap(CMLMolecule mol2) { CMLMap map = new CMLMap(); map.addAttribute(new Attribute("toMolecule", mol2.getId())); List atoms = molecule.getAtoms(); List atoms2 = mol2.getAtoms(); if (atoms.size() != atoms2.size()) { throw new RuntimeException( "MoleculesMolecules have different lengths: " + atoms.size() + " != " + atoms2.size()); } for (int i = 0; i < atoms.size(); i++) { String id = ((CMLAtom) atoms.get(i)).getId(); String id2 = ((CMLAtom) atoms2.get(i)).getId(); String e1 = ((CMLAtom) atoms.get(i)).getElementType(); String e2 = ((CMLAtom) atoms2.get(i)).getElementType(); if (!(e1 == null && e2 == null || e1.equals(e2))) { throw new RuntimeException( "atoms have different excludeElementTypes: " + id + CMLConstants.S_LBRAK + e1 + ") != " + id2 + CMLConstants.S_LBRAK + e2 + CMLConstants.S_RBRAK); } CMLLink link = new CMLLink(); link.setFrom(id); link.setTo(id2); map.addLink(link); } return map; } /** get matched atom. does not work with sets * * if atom id="a1" and map hass a link to="a1" from="b1" and toFrom = * Direction.FROM then will return atom id="b1" in resultMolecule * * @param atom0 * atom to search with. Its id must occur in a single toFrom * attribute * @param map * with links * @param toFrom * specifies attribute for target atom * @return mapped atom or null */ public CMLAtom getMappedAtom(CMLMap map, CMLAtom atom0, Direction toFrom) { CMLAtom targetAtom = null; if (atom0 != null && map != null) { String targetId = map.getRef(atom0.getId(), toFrom); if (targetId != null) { targetAtom = molecule.getAtomById(targetId); } } return targetAtom; } /** * gets vector of 3D coordinates. * * all atoms must have coordinates * * @param type * CARTESIAN or FRACTIONAL * @return the vector (null if missing 3D coordinates) */ public Point3Vector getCoordinates3(CoordinateType type) { CMLAtomSet atomSet = this.getAtomSet(); return atomSet.getCoordinates3(type); } /** * generate list of transformed molecules. operates on Cartesians; does not * modify this * * @param sym * the symmetries * @return list of molecules */ public List transformCartesians(CMLSymmetry sym) { List molList = new ArrayList(); for (CMLTransform3 tr : sym.getTransform3Elements()) { CMLMolecule molecule = new CMLMolecule(this.getMolecule()); MoleculeTool moleculeTool = MoleculeTool.getOrCreateTool(molecule); moleculeTool.transformCartesians(tr); } return molList; } /** * transform 3D Cartesian coordinates. modifies this * * @param transform * the transformation */ public void transformCartesians(CMLTransform3 transform) { CMLAtomSet atomSet = this.getAtomSet(); if (atomSet != null) { AtomSetTool atomSetTool = AtomSetTool.getOrCreateTool(atomSet); atomSetTool.transformCartesians(transform); } } /** transform 3D Cartesian coordinates. modifies this * * @param transform * the transformation */ public void transformCartesians(Transform3 transform) { CMLAtomSet atomSet = this.getAtomSet(); if (atomSet != null) { AtomSetTool atomSetTool = AtomSetTool.getOrCreateTool(atomSet); atomSetTool.transformCartesians(transform); } } /** generate list of transformed molecules. operates on fractionals; does * modify Cartesians or modify this * * @param sym * the symmetries * @return list of molecules */ public List transformFractionalCoordinates(CMLSymmetry sym) { List molList = new ArrayList(); for (CMLTransform3 tr : sym.getTransform3Elements()) { CMLMolecule molecule = new CMLMolecule(this.getMolecule()); MoleculeTool moleculeTool = MoleculeTool.getOrCreateTool(molecule); moleculeTool.transformFractionalCoordinates(tr); molList.add(molecule); } return molList; } /** * transform 3D fractional coordinates. modifies this does not affect x3, * y3, z3 (may need to re-generate cartesians) * * @param transform * the transformation */ public void transformFractionalCoordinates(CMLTransform3 transform) { CMLAtomSet atomSet = this.getAtomSet(); if (atomSet != null) { AtomSetTool atomSetTool = AtomSetTool.getOrCreateTool(atomSet); atomSetTool.transformFractionals(transform); } } /** * transform 3D fractional coordinates. modifies this does not affect x3, * y3, z3 (may need to re-generate cartesians) * * @param transform * the transformation */ public void transformFractionalCoordinates(Transform3 transform) { CMLAtomSet atomSet = this.getAtomSet(); if (atomSet != null) { AtomSetTool atomSetTool = AtomSetTool.getOrCreateTool(atomSet); atomSetTool.transformFractionals(transform); } } /** * transform fractional and 3D coordinates. does NOT alter 2D coordinates * transforms fractionals then applies orthogonalisation to result * @param transform * the fractional symmetry transformation * @param orthMat * orthogonalisation matrix */ public void transformFractionalsAndCartesians(CMLTransform3 transform, Transform3 orthMat) { CMLAtomSet atomSet = this.getAtomSet(); if (atomSet != null) { AtomSetTool atomSetTool = AtomSetTool.getOrCreateTool(atomSet); atomSetTool.transformFractionalsAndCartesians(transform, orthMat); } } /** * transform molecule in 2D. * * @param matrix 3*3 matrix apply to all 2D coordinates */ public void transform(CMLMatrix matrix) { if (matrix == null || matrix.getRows() != 3 || matrix.getColumns() != 3) { throw new RuntimeException("bad transformation matrix"); } double[] mm = matrix.getDoubleArray(); Transform2 t2 = new Transform2(mm); this.transform(t2); } /** * transform * @param t2 */ public void transform(Transform2 t2) { for (CMLAtom atom : molecule.getAtoms()) { if (atom.hasCoordinates(CoordinateType.TWOD)) { Real2 dd = new Real2(atom.getX2(), atom.getY2()); dd.transformBy(t2); atom.setXY2(dd); } } } /** * translate molecule in 3D. * * @param delta3 * add to all 3D coordinates */ public void translate3D(Vector3 delta3) { CMLAtomSet atomSet = getAtomSet(); if (atomSet != null) { atomSet.translate3D(delta3); } } /** set this.molecule for all descendant atomSets. * this is messy but a consequence of the serialization * of atomSets in CML since molecules are not explicit. */ public void setThisInDescendantAtomSets() { Nodes nodes = molecule.query(".//cml:atomSet", CMLConstants.CML_XPATH); for (int i = 0; i < nodes.size(); i++) { CMLAtomSet atomSet = (CMLAtomSet) nodes.get(i); atomSet.setMolecule(molecule); } } /** moves centroid to origin. * */ public void translateCentroidToOrigin3(CoordinateType type) { AtomSetTool.getOrCreateTool(this.getAtomSet()).translateCentroidToOrigin3(type); } // /** ensure integrity between list and children. // * @return CMLMoleculeList.class // */ // public Class getIndexableListClass() { // return CMLMoleculeList.class; // } /** create molecule from atomSet. * clones atoms * does not add bonds. * @param atomSet */ public static CMLMolecule createMolecule(CMLAtomSet atomSet) { // this(); CMLMolecule molecule = new CMLMolecule(); List atoms = atomSet.getAtoms(); for (CMLAtom atom : atoms) { molecule.addAtom(new CMLAtom(atom)); } return molecule; } /** create molecule from atomSet and bondSet. * clones atoms and bonds * @param atomSet * @param bondSet */ public static CMLMolecule createMolecule(CMLAtomSet atomSet, CMLBondSet bondSet) { CMLMolecule molecule = MoleculeTool.createMolecule(atomSet); List bonds = bondSet.getBonds(); for (CMLBond bond : bonds) { molecule.addBond(new CMLBond(bond)); } return molecule; } // /** converts a mass to a molarAmount. // * @param molecule // * @return molar Amount (units are mol) // */ // public CMLAmount getMolarAmount(CMLMolecule molecule) { // CMLAmount molarAmount = null; // UnitsAttribute units = (UnitsAttribute) molecule.getUnitsAttribute(); // if (units == null) { // throw new RuntimeException("No units given on amount"); // } // String unitValue = (String) units.getCMLValue(); // if (Units.GRAM.value.equals(unitValue)) { // double d = this.getCalculatedMolecularMass(HydrogenControl.NO_EXPLICIT_HYDROGENS); // double amountx = molecule.getXMLContent(); // molarAmount = new CMLAmount(); // molarAmount.setUnits(Units.MMOL.value); // molarAmount.setXMLContent(amountx / d); // } else { // throw new RuntimeException("Cannot handle units other than gram"); // } // return molarAmount; // } /** get list of length children. * * @return list (empty if none) */ public List getLengthElements() { Nodes nodes = molecule.query("./cml:length", CMLConstants.CML_XPATH); List list = new ArrayList(); for (int i = 0; i < nodes.size(); i++) { list.add((CMLLength)nodes.get(i)); } return list; } /** get list of angle children. * * @return list (empty if none) */ public List getAngleElements() { Nodes nodes = molecule.query("./cml:angle", CMLConstants.CML_XPATH); List list = new ArrayList(); for (int i = 0; i < nodes.size(); i++) { list.add((CMLAngle)nodes.get(i)); } return list; } /** get list of torsion children. * * @return list (empty if none) */ public List getTorsionElements() { Nodes nodes = molecule.query("./cml:torsion", CMLConstants.CML_XPATH); List list = new ArrayList(); for (int i = 0; i < nodes.size(); i++) { list.add((CMLTorsion)nodes.get(i)); } return list; } /** get list of formula children. * * @return list (empty if none) */ public List getFormulaElements() { Nodes nodes = molecule.query("./cml:formula", CMLConstants.CML_XPATH); List list = new ArrayList(); for (int i = 0; i < nodes.size(); i++) { list.add((CMLFormula)nodes.get(i)); } return list; } /** get list of molecule children. * * @return list (empty if none) */ public List getMoleculeElements() { Nodes nodes = molecule.query("./cml:molecule", CMLConstants.CML_XPATH); List list = new ArrayList(); for (int i = 0; i < nodes.size(); i++) { list.add((CMLMolecule)nodes.get(i)); } return list; } public static CMLAtomSet createAtomSet(List molecules) { CMLAtomSet atomSet = null; if (molecules.size() > 0) { CMLMolecule newMolecule = new CMLMolecule(molecules.get(0)); MoleculeTool newMoleculeTool = MoleculeTool.getOrCreateTool(newMolecule); newMoleculeTool.createLabelsOnMolFromLastIds("m1", molecules.get(0)); for (int i = 1, max = molecules.size(); i < max; i++) { CMLMolecule mol = new CMLMolecule(molecules.get(i)); newMoleculeTool.addMoleculeTo(mol); newMoleculeTool.createLabelsOnMolFromLastIds("m"+(i+1), molecules.get(i)); } atomSet = new CMLAtomSet(newMolecule); } return atomSet; } private void createLabelsOnMolFromLastIds(String prefix, CMLMolecule mol) { List atoms = mol.getAtoms(); int molCount = mol.getAtomCount(); List thisAtoms = molecule.getAtoms(); int start = thisAtoms.size() - molCount; for (int i = 0, max = molCount; i < max; i++) { String id = atoms.get(i).getId(); String labelValue = prefix+"_"+id; CMLLabel label = new CMLLabel(); label.setCMLValue(labelValue); thisAtoms.get(i+start).addLabel(label); } } public SVGGBox draw(MoleculeDisplay moleculeDisplay) { ensure2DCoordinates(moleculeDisplay.isOmitHydrogens()); MoleculeDisplayList displayList = new MoleculeDisplayList(); try { displayList.setAndProcess(this); displayList.createOrDisplayGraphics(); } catch (IOException e) { e.printStackTrace(); } SVGG g = displayList.getSvg(); if (g == null) { throw new RuntimeException("NULL GGG"); } g = replaceThisBySingleGChild(g); return SVGGBox.createSVGGBox(g); } private SVGG replaceThisBySingleGChild(SVGG g) { if (g != null) { Nodes gNodes = g.query("./*[local-name()='"+SVGG.TAG+"']"); if (gNodes.size() == 1) { g = new SVGG((SVGG)gNodes.get(0)); } } return g; } public void ensure2DCoordinates(boolean omitHydrogen) { if (!molecule.hasCoordinates(CoordinateType.TWOD, omitHydrogen)) { MoleculeLayout moleculeLayout = new MoleculeLayout(this); try { moleculeLayout.create2DCoordinates(molecule); } catch (RuntimeException e) { e.printStackTrace(); LOG.error("Cannot create coordinates: "+e); } } } public void stripHydrogens() { List atoms = molecule.getAtoms(); for (CMLAtom atom : atoms) { if (ChemicalElement.AS.H.toString().equals(atom.getElementType())) { molecule.deleteAtom(atom); } } } public SVGGBox drawAndTranslateToRectCorner(MoleculeDisplay moleculeDisplay) { SVGGBox svgg = this.draw(moleculeDisplay); BoundingRect brect = BoundingRect.createBoundingRect(svgg); if (brect != null) { brect.translateGToRectCorner(); } return svgg; } public void addExplicitHydrogensFromHydrogenCount() { List atoms = molecule.getAtoms(); for (CMLAtom atom : atoms) { if (!ChemicalElement.AS.H.toString().equals(atom.getElementType())) { Attribute hcatt = atom.getHydrogenCountAttribute(); if (hcatt != null) { int hc = atom.getHydrogenCount(); for (int i = 0; i < hc; i++) { CMLAtom hatom = new CMLAtom(atom.getId()+"_h"+(i+1), ChemicalElement.AS.H); molecule.addAtom(hatom); CMLBond bond = new CMLBond(atom, hatom); molecule.addBond(bond); bond.setOrder(CMLBond.SINGLE_S); } hcatt.detach(); } } } } public static void ensureAtoms(List molecules) { for (CMLMolecule molecule : molecules) { if (molecule != null) { MoleculeTool.getOrCreateTool(molecule).ensureAtomsUsingSMILES(); molecule.normalizeFormulas(); CMLFormula formula = molecule.getFirstConciseFormula(); } } } private void ensureAtomsUsingSMILES() { CMLAtomArray atomArray = molecule.getAtomArray(); if (atomArray == null) { String smilesString = getSmilesFromkFormula(); CMLMolecule smilesMolecule = SMILESTool.createMolecule(smilesString); if (smilesMolecule != null) { CMLAtomArray smilesAtomArray = smilesMolecule.getAtomArray(); CMLBondArray smilesBondArray = smilesMolecule.getBondArray(); if (smilesAtomArray != null) { smilesAtomArray.detach(); molecule.addAtomArray(smilesAtomArray); if (smilesBondArray != null) { smilesBondArray.detach(); molecule.addBondArray(smilesBondArray); } } } } } public String getSmilesFromkFormula() { Nodes formulaNodes = molecule.query("./*[local-name()='"+CMLFormula.TAG+"' and" + " @inline and @convention='SMILES']"); CMLFormula formula = (formulaNodes.size() == 1) ? (CMLFormula) formulaNodes.get(0) : null; return (formula == null) ? null : formula.getInline(); } public static Angle getCalculatedAngle2D(CMLAtom atom0, CMLAtom atom1, CMLAtom atom2) { Angle angle = null; if (atom0 != null && atom1 != null && atom2 != null) { Real2 xy0 = atom0.getXY2(); Real2 xy1 = atom1.getXY2(); Real2 xy2 = atom2.getXY2(); try { angle = Real2.getAngle(xy0, xy1, xy2); } catch (Exception e) { // null coordinates } } return angle; } public void transformToInertialAxes() { Point3Vector p3v = new CMLAtomSet(molecule).getCoordinates3(CoordinateType.CARTESIAN); RealSquareMatrix eigvec = p3v.calculateRotationToInertialAxes(); if (eigvec != null) { p3v.transform(new Transform3(eigvec)); this.setCoordinates(CoordinateType.CARTESIAN, p3v); } } public void setCoordinates(CoordinateType type, Point3Vector p3v) { if (molecule.getAtomCount() != p3v.size()) { throw new RuntimeException("inconsistent numbers of atoms"); } List atoms = molecule.getAtoms(); for (int i = 0; i < p3v.size(); i++) { if (CoordinateType.CARTESIAN.equals(type)) { atoms.get(i).setXYZ3(p3v.get(i)); } else if (CoordinateType.FRACTIONAL.equals(type)) { atoms.get(i).setXYZFract(p3v.get(i)); } } } /** * Calculates the precise masses of the different isotopomers of the molecule, * returning a maximum of ten isotopomers masses * @param maxCount the maximum number of return values * @return a list of double pairs (mass, abundance) sorted by abundance. The * abundance is expressed as a fraction NOT a percentage */ public List getCalculatedIsotopomerMasses() { return getCalculatedIsotopomerMasses(10); } /** * Calculates the precise masses of the different isotopomers of the molecule * @param maxCount the maximum number of return values * @return a list of double pairs (mass, abundance) sorted by abundance. The * abundance is expressed as a fraction NOT a percentage */ public List getCalculatedIsotopomerMasses(int maxCount) { List isotopomers = new ArrayList(); isotopomers.add(new double[] {0, 1d}); for (CMLAtom atom : molecule.getAtoms()) { isotopomers = addAtomToCurrentIsotopomers(atom, isotopomers); Collections.sort(isotopomers, new Comparator() { public int compare(double[] arg0, double[] arg1) { if (arg0[1] < arg1 [1]) { return 1; } if (arg0[1] > arg1 [1]) { return -1; } return 0; } }); isotopomers = mergeIdenticalMasses(isotopomers); trimIsotopomerList(isotopomers, maxCount); } return isotopomers; } private void trimIsotopomerList(List isotopomers, int maxCount) { while (isotopomers.size() > maxCount) { isotopomers.remove(maxCount); } } private List mergeIdenticalMasses(List isotopomers) { List newIsotopomers = new ArrayList(); newIsotopomers.add(isotopomers.get(0)); for (int i = 1; i < isotopomers.size(); i++) { double mass = isotopomers.get(i)[0]; if (Math.abs(mass - newIsotopomers.get(newIsotopomers.size()-1)[0]) < 1E-12 ) { newIsotopomers.get(newIsotopomers.size()-1)[1] += isotopomers.get(i)[1]; } else { newIsotopomers.add(isotopomers.get(i)); } } return newIsotopomers; } private List addAtomToCurrentIsotopomers(CMLAtom atom, List isotopomers) { List newIsotopomers = new ArrayList(); for (double[] isotopomer : isotopomers) { ChemicalElement element = ChemicalElement.getChemicalElement(atom.getElementType()); double [] abundancies = element.getIsotopeAbundances(); for (int i = 0; i < abundancies.length; i++) { if (element.getIsotopePreciseMasses() == null) { throw new RuntimeException("isotopes not known for " + element.getSymbol()); } double newMass = isotopomer[0] + element.getIsotopePreciseMasses()[i]; double newAbundance = isotopomer[1] * abundancies[i]/100; newIsotopomers.add(new double [] {newMass, newAbundance}); } } return newIsotopomers; } public void addLoneElectronCountToAtoms() { List atoms = molecule.getAtoms(); for (CMLAtom atom : atoms) { int loneElectronCount = this.getLoneElectronCount(atom); atom.setCMLXAttribute(ElectronTool.LONE_ELECTRONS, ""+loneElectronCount); } } public void addElectronCountToBonds() { List bonds = molecule.getBonds(); for (CMLBond bond : bonds) { BondTool bondTool = BondTool.getOrCreateTool(bond); int electronCount = bondTool.getElectronCount(); bond.setCMLXAttribute(ElectronTool.ELECTRONS, ""+electronCount); } } } class BoundingRect { private SVGRect rect; private double xorig; private double yorig; private double width; private double height; private SVGG svgg; private BoundingRect(Nodes rects, SVGG svgg) { Element rectx = (Element)rects.get(0); rect = (SVGRect) SVGElement.createSVG(rectx); xorig = rect.getX(); yorig = rect.getY(); width = rect.getWidth(); height = rect.getHeight(); this.svgg = svgg; } public static BoundingRect createBoundingRect(SVGG svgg) { Nodes rects = svgg.query("./*[local-name()='"+SVGRect.TAG+"']"); return (rects.size() == 0) ? null : new BoundingRect(rects, svgg); } public void translateGToRectCorner() { Transform2 transform = svgg.getTransform2FromAttribute(); if (transform == null) { transform = new Transform2(); } // subtract height since we are inverting box Vector2 translateVector = new Vector2(-xorig, -yorig - height); Transform2 translate = new Transform2(translateVector); transform = transform.concatenate(translate); svgg.setTransform(transform); } public double getHeight() { return height; } public double getWidth() { return width; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy