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

org.xmlcml.cml.tools.ReactionTool 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.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import nu.xom.Attribute;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Nodes;

import org.apache.log4j.Logger;
import org.xmlcml.cml.base.CMLConstants;
import org.xmlcml.cml.base.CMLElement;
import org.xmlcml.cml.base.CMLElements;
import org.xmlcml.cml.element.CMLAtom;
import org.xmlcml.cml.element.CMLAtomSet;
import org.xmlcml.cml.element.CMLBond;
import org.xmlcml.cml.element.CMLBondSet;
import org.xmlcml.cml.element.CMLElectron;
import org.xmlcml.cml.element.CMLFormula;
import org.xmlcml.cml.element.CMLLabel;
import org.xmlcml.cml.element.CMLLink;
import org.xmlcml.cml.element.CMLMap;
import org.xmlcml.cml.element.CMLMolecule;
import org.xmlcml.cml.element.CMLProduct;
import org.xmlcml.cml.element.CMLProductList;
import org.xmlcml.cml.element.CMLReactant;
import org.xmlcml.cml.element.CMLReactantList;
import org.xmlcml.cml.element.CMLReaction;
import org.xmlcml.cml.element.CMLSpectator;
import org.xmlcml.cml.element.CMLSpectatorList;
import org.xmlcml.cml.element.ReactionComponent;
import org.xmlcml.cml.element.CMLFormula.Sort;
import org.xmlcml.cml.element.CMLMap.Direction;
import org.xmlcml.cml.element.CMLReaction.Component;
import org.xmlcml.cml.graphics.CMLDrawable;
import org.xmlcml.cml.graphics.GraphicsElement;
import org.xmlcml.cml.graphics.SVGCircle;
import org.xmlcml.cml.graphics.SVGElement;
import org.xmlcml.cml.graphics.SVGG;
import org.xmlcml.cml.graphics.SVGGBox;
import org.xmlcml.cml.graphics.SVGLayout;
import org.xmlcml.cml.graphics.SVGLine;
import org.xmlcml.cml.graphics.SVGRect;
import org.xmlcml.cml.graphics.SVGSVG;
import org.xmlcml.cml.graphics.SVGText;
import org.xmlcml.cml.tools.ReactionDisplay.Orientation;
import org.xmlcml.cml.tools.matcher.Matcher2D;
import org.xmlcml.euclid.Real2;
import org.xmlcml.euclid.Real2Interval;
import org.xmlcml.euclid.Real2Range;
import org.xmlcml.euclid.Transform2;
import org.xmlcml.euclid.Vector2;

/**
 * too to support reactions. not fully developed
 * 
 * @author pmr
 * 
 */
public class ReactionTool extends AbstractSVGTool {

	public static final String REACTANT = "reactant";
	public static final String PRODUCT  = "product";

    private static final String OMIT_REACTANTS = null;
	public static final String REACTANT_VERTICAL = null;
	private static final double MINDIST = 1.0;

	Logger LOG = Logger.getLogger(ReactionTool.class);

	public static String DRAW = "draw";
	public static String MASS = "mass";
	public static String STRIP_HYD = "strip_hydrogen";
	
    private CMLReaction reaction = null;
	private ReactionDisplay reactionDisplay = new ReactionDisplay();
	private static int patternCount = 0;
	private static Element defs = null;
	private static String PATTERN = "pattern";
	

	private CMLFormula aggregateReactantFormula;
	private CMLFormula aggregateProductFormula;
	private CMLFormula differenceFormula;
    private List electronIdList;
	private MoleculeDisplayList displayList;
	private Map atomChangeById;
	private Map bondChangeById;
    
	/**
     * constructor.
     * 
     * @param reaction
     * @deprecated use getOrCreateTool()
     */
    public ReactionTool(CMLReaction reaction) {
    	if (reaction == null) {
    		throw new RuntimeException("null reaction");
    	}
        this.reaction = reaction;
		this.reaction.setTool(this);
    }

	/** gets ReactionTool associated with reaction.
	 * if null creates one and sets it in reaction
	 * @param reaction
	 * @return tool
	 */
	public static ReactionTool getOrCreateTool(CMLReaction reaction) {
		ReactionTool reactionTool = null;
		if (reaction != null) {
			reactionTool = (ReactionTool) reaction.getTool();
			if (reactionTool == null) {
				reactionTool = new ReactionTool(reaction);
				reaction.setTool(reactionTool);
			}
		}
		return reactionTool;
	}

	/**
	 * 
	 * @param reaction
	 * @return
	 */
	public static AbstractSVGTool getOrCreateSVGTool(CMLReaction reaction) {
		return (AbstractSVGTool) ReactionTool.getOrCreateTool(reaction);
	}

    /**
     * output and analyse aggregate formula for products and reactants.
     * 
     * @param w the output writer
     * @throws RuntimeException
     * @throws IOException
     */
    public void outputBalance(Writer w) throws IOException {
        calculateDifferenceFormula();
        if (aggregateReactantFormula != null) {
            w.write(aggregateReactantFormula.getFormattedString());
        } else {
            w.write("Null reactantList");
        }
        w.write(" = ");
        if (aggregateProductFormula != null) {
            w.write(aggregateProductFormula.getFormattedString());
        } else {
            w.write("Null productList");
        }
		if (differenceFormula != null) {
            w.write(" ; difference: ");
            differenceFormula.setAllowNegativeCounts(true);
            w.write(differenceFormula.getFormattedString(CMLFormula.Type.ELEMENT_WHITESPACE_COUNT,
    				Sort.CHFIRST, false));
        }
    }

	/**
	 * @return difference formula
	 */
	public CMLFormula calculateDifferenceFormula() {
		differenceFormula = null;
		aggregateReactantFormula = createAggregateReactantFormula();
		aggregateProductFormula = createAggregateProductFormula();
		if (aggregateReactantFormula != null && aggregateProductFormula != null) {
            differenceFormula = aggregateReactantFormula.getDifference(aggregateProductFormula);
        }
		return differenceFormula;
	}

	/**
	 * @return formula
	 */
	public CMLFormula createAggregateProductFormula() {
		List products = reaction.getDescendantProducts();
        CMLFormula aggregateProductFormula = null;
        for (CMLProduct product : products) {
            CMLFormula formula = product.getOrCreateFormula();
            if (formula != null) {
            	double thisCount = product.getCount();
            	if (!Double.isNaN(thisCount)) {
            		formula.setCount(thisCount);
            	}
                if (aggregateProductFormula == null) {
                    aggregateProductFormula = new CMLFormula();
                }
                aggregateProductFormula = aggregateProductFormula
                        .createAggregatedFormula(formula);
            }
        }
		return aggregateProductFormula;
	}

	/**
	 * @return aggregate reaction formula
	 */
	public CMLFormula createAggregateReactantFormula() {
		List reactants = reaction.getDescendantReactants();
        CMLFormula aggregateReactantFormula = null;
        for (CMLReactant reactant : reactants) {
            CMLFormula formula = reactant.getOrCreateFormula();
            if (formula != null) {
            	double thisCount = reactant.getCount();
            	if (!Double.isNaN(thisCount)) {
            		formula.setCount(thisCount);
            	}
                if (aggregateReactantFormula == null) {
                    aggregateReactantFormula = new CMLFormula();
                }
                aggregateReactantFormula = aggregateReactantFormula
                        .createAggregatedFormula(formula);
            }
        }
		return aggregateReactantFormula;
	}

    /**
     * output simple inline version of reaction.
     * 
     * of form "C2H4 + H2O = C2H6O" without newline
     * 
     * @param w
     *            the output writer
     * @throws RuntimeException
     * @throws IOException
     */
    public void outputReaction(Writer w) throws IOException {
        // get reactants (null if no formulae)
        List reactants = reaction.getDescendantReactants();
        int i = 0;
        for (CMLReactant reactant : reactants) {
            CMLFormula formula = reactant.getOrCreateFormula();
            if (i > 0) {
                w.write(" + ");
            }
            if (formula != null) {
                w.write(formula.getFormattedString());
            } else {
                w.write("NULL");
            }
            i++;
        }

        // get products (null if no formulae)
        List products = reaction.getDescendantProducts();
        i = 0;
        for (CMLProduct product : products) {
            CMLFormula formula = product.getOrCreateFormula();
            if (i > 0) {
                w.write(" + ");
            } else {
                w.write(" = ");
            }
            if (formula != null) {
                w.write(formula.getFormattedString());
            } else {
                w.write("NULL");
            }
            i++;
        }

    }

    /**
     * gets formula for product or reactant.
     * 
     * If product has a child formula, returns it, else returns the formula on
     * molecules, else null. resulting CMLFormula is not aggregated
     * @param element
     * @return the product stoichiometry
     */
     public static CMLFormula getFormula(Element element) {
         CMLFormula formula = null;
         if (element instanceof CMLReactant) {
             formula = (CMLFormula) ((CMLReactant)element).getFirstCMLChild(CMLFormula.TAG);
         } else if (element instanceof CMLProduct) {
             formula = (CMLFormula) ((CMLProduct)element).getFirstCMLChild(CMLFormula.TAG);
         } else {
        	 throw new ClassCastException("must use CMLReactant ot CMLProduct");
         }
         if (formula == null) {
             CMLMolecule molecule = null;
             if (element instanceof CMLReactant) { 
                 molecule = (CMLMolecule) ((CMLReactant)element).getFirstCMLChild(CMLMolecule.TAG);
             } else if (element instanceof CMLProduct) {
                 molecule = (CMLMolecule) ((CMLProduct)element).getFirstCMLChild(CMLMolecule.TAG);
             }
             if (molecule != null) {
            	 MoleculeTool moleculeTool = MoleculeTool.getOrCreateTool(molecule);
                 formula = (CMLFormula) molecule.getFirstCMLChild(CMLFormula.TAG);
                 if (formula == null) {
                     formula = moleculeTool.calculateFormula(CMLMolecule.HydrogenControl.USE_EXPLICIT_HYDROGENS);
                 } else {
                     formula = new CMLFormula(molecule);
                 }
             }
         }
         return formula;
     }

    /**
     * gets aggregate formula for product or reactant.
     * 
     * @param prodReact
     * @return the formula (null if no child formulas)
     */
    public static CMLFormula getAggregateFormula(ReactionComponent prodReact) {
        if (prodReact == null) {
            throw new RuntimeException("null prodReact");
        }
        List formulaList = prodReact.getFormulas();
        CMLFormula aggregateFormula = null;
        if (formulaList.size() > 0) {
            aggregateFormula = new CMLFormula();
            for (CMLFormula formula : formulaList) {
                aggregateFormula.createAggregatedFormula(formula);
            }
        }
        return aggregateFormula;
    }

    /**
     * gets all descendant molecules.
     * 
     * @param reactionComponent REACTANTLIST or PRODUCTLIST
     * @return the descendant molecules or empty list
     */
    public List getMolecules(Component reactionComponent) {
    	Element reactantProductList = null; 
    	if (Component.REACTANTLIST.equals(reactionComponent) || Component.REACTANT.equals(reactionComponent)) {
    		reactantProductList = reaction.getReactantList();
    	} else if (Component.PRODUCTLIST.equals(reactionComponent) || Component.PRODUCT.equals(reactionComponent)) {
    		reactantProductList = reaction.getProductList();
//    	} else if (Component.SPECTATORLIST.equals(reactionComponent) || Component.SPECTATOR.equals(reactionComponent)) {
//    		reactantProductList = reaction.getSpectatorList();
    	} else {
    		throw new RuntimeException("Bad ReactionComponent type: "+reactionComponent);
    	}
        List moleculeList = new ArrayList();
        if (reactantProductList != null) {
	        Nodes moleculeNodes = reactantProductList.query(".//cml:molecule", CMLConstants.CML_XPATH);
	        for (int i = 0; i < moleculeNodes.size(); i++) {
	        	moleculeList.add((CMLMolecule) moleculeNodes.get(i));
	        }
        }
        return moleculeList;
    }


    /**
     * gets all descendant atoms.
     * 
     * @param type REACTANTLIST or PRODUCTLIST
     * @return the descendant atoms.
     */
    public List getAtoms(Component type) {
        List atomList = new ArrayList();
        List molecules = getMolecules(type);
        for (CMLMolecule molecule : molecules) {
            List atoms = molecule.getAtoms();
            for (CMLAtom atom : atoms) {
                atomList.add(atom);
            }
        }
        return atomList;
    }

    /**
     * gets all descendant bonds.
     * 
     * @param type REACTANTLIST or PRODUCTLIST
     * @return the descendant bonds.
     */
    public List getBonds(Component type) {
        List bondList = new ArrayList();
        List molecules = getMolecules(type);
        for (CMLMolecule molecule : molecules) {
            List bonds = molecule.getBonds();
            for (CMLBond bond : bonds) {
                bondList.add(bond);
            }
        }
        return bondList;
    }


    
    /** create from reaction scheme in literature.
     * 
     * @param doc
     */
    public static void createFromOSCAR(Document doc) {
        try {
            OscarTool oscar = new OscarTool(doc);
            oscar.convertToCML();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 
     * @param iReaction
     * @param atomPairList
     * @param bondPairList
     * @param atomMap
     * @return electronPai list
     */
    @SuppressWarnings("unchecked")
	public List /*List> */ getElectronPairList(int iReaction, List atomPairList, List bondPairList, CMLMap atomMap) {
        List electronPairList = new ArrayList();
        ElectronPair.currentReaction = iReaction;
        
        electronIdList = new ArrayList();
        List changedAtomPairList = new ArrayList();
        List changedBondPairList = new ArrayList();
        for (int i = 0; i < atomPairList.size(); i++) {
            MappedAtomPair atomPair = (MappedAtomPair) atomPairList.get(i);
            compareAtoms(atomPair, changedAtomPairList);
        }
//        List bondPairList = getBondPairList(null, moleculeTool1, moleculeTool2, serial);
        for (int i = 0; i < bondPairList.size(); i++) {
            MappedBondPair bondPair = (MappedBondPair) bondPairList.get(i);
            compareBonds(bondPair, changedBondPairList);
        }

        boolean change = true;
// there may be several unconnected fragments so go on until no change
        while (change) {
            change = false;
// get start atom
            MappedAtomPair atomPair = getNextChangedAtomSource(changedAtomPairList);
            if (atomPair != null) {
                change = true;
                MappedBondPair bondPair = getUniqueBondPairContaining(changedBondPairList, atomPair);
                if (bondPair == null) {
                	LOG.debug("cannot find unique bond containing: "+atomPair);
                    change = false;
                    break;
                }
                addElectrons(atomPair, bondPair, electronPairList);
                changedAtomPairList.remove(atomPair);
                changedBondPairList.remove(bondPair);
//                @SuppressWarnings("unused")

//                MappedAtomPair nextAtomPair = getOtherAtomPair(bondPair, atomPair, atomMap, atomPairList);
// FIXME                iterateChain(atomPairList, changedAtomPairList, changedBondPairList, nextAtomPair, atomMap, electronPairList);
            }
        }

// cycles? take an arbitrary starting point and try. Only bonds should be left
        if (changedAtomPairList.size() > 0) {
        	LOG.debug(""+changedAtomPairList.size()+" unmatched atoms");
        	for (int i = 0; i < changedAtomPairList.size() && i < 3; i++) {
        		LOG.debug(": "+changedAtomPairList.get(i));
        	}
        }
        
        while (changedBondPairList.size() > 0) {
            int size = changedBondPairList.size();
            if (size == 0) {
                break;
            } else {
                MappedBondPair bondPair = (MappedBondPair) changedBondPairList.get(0);
                @SuppressWarnings("unused")
                MappedAtomPair startingAtomPair = bondPair.getAtomPair(0, atomPairList);
// FIXME                iterateCycle(atomPairList, changedBondPairList, startingAtomPair, atomMap, electronPairList);
            }
            if (size == changedBondPairList.size()) {
                LOG.debug("Cannot match "+size+" bonds");
            	for (int i = 0; i < changedBondPairList.size() && i < 3; i++) {
            		LOG.debug(": "+changedBondPairList.get(i));
            	}
                break;
            }
        }
        
        return electronPairList;
    }
    
    private void compareAtoms(MappedAtomPair atomPair, List changedPairList) {
        AtomTool atomTool1 = AtomTool.getOrCreateTool(atomPair.atom1);
        AtomTool atomTool2 = AtomTool.getOrCreateTool(atomPair.atom2);
        if (atomTool1 == null) {
//            logger.error("Null atom partner for (2): "+atomPair.atom2.getId());
            return;
        }
        if (atomTool2 == null) {
//            logger.error("Null atom partner for (1): "+atomPair.atom1.getId());
            return;
        }
        int loneElectronCount1 = atomTool1.getLoneElectronCount();
        int loneElectronCount2 = atomTool2.getLoneElectronCount();
        if (loneElectronCount1 != loneElectronCount2) {
            changedPairList.add(atomPair);
            atomPair.setElectronChange(loneElectronCount2 - loneElectronCount1);
        }
    }

    private void compareBonds(MappedBondPair bondPair, List changedPairList) {
        int order1 = (bondPair.bond1 == null) ? 0 : CMLElectron.getElectronCount(bondPair.bond1.getOrder());
        int order2 = (bondPair.bond2 == null) ? 0 : CMLElectron.getElectronCount(bondPair.bond2.getOrder());
        if (order1 != order2) {
            changedPairList.add(bondPair);
            bondPair.setElectronChange(order2 - order1);
        }
    }

    
    private List getElectronPairList(
    		Element parent, CMLMolecule mol1, CMLMolecule mol2, int iReaction) {
//    	 electrons
        List electronPairList = new ArrayList();
        Nodes electronNodes1 = mol1.query(".//cml:electron", CMLConstants.CML_XPATH);
        Nodes electronNodes2 = mol2.query(".//cml:electron", CMLConstants.CML_XPATH);
//    	 find electrons in first molecule or both
        MoleculeTool molTool1 = MoleculeTool.getOrCreateTool(mol1);
        MoleculeTool molTool2 = MoleculeTool.getOrCreateTool(mol2);
        for (int i = 0; i < electronNodes1.size(); i++) {
            CMLElectron electron1 = (CMLElectron) electronNodes1.get(i);
            String id = (electron1.getId());
            CMLElectron electron2 = molTool2.getElectronById(id);
            electronPairList.add(new ElectronPair(electron1, electron2, iReaction));
        }
//    	 find electrons in second molecule only
        for (int i = 0; i < electronNodes2.size(); i++) {
            CMLElectron electron2 = (CMLElectron) electronNodes2.get(i);
            String id = (electron2.getId());
            CMLElectron electron1 = molTool1.getElectronById(id);
            if (electron1 == null) {
                electronPairList.add(new ElectronPair(null, electron2, iReaction));
            }
        }
        return electronPairList;
    }
    
    
    @SuppressWarnings("unused")
    private boolean iterateChain(List atomPairList, List changedAtomPairList,
    		List changedBondPairList, MappedAtomPair nextAtomPair,
    		CMLMap atomMap, List electronPairList) {
        // iterate down chain two bonds at a time until terminal atom
        boolean change = true;
        while (true) {
            MappedBondPair bondPair = getUniqueBondPairContaining(changedBondPairList, nextAtomPair);
            if (bondPair == null) {
                change = false;
                break;
            }
            MappedAtomPair middleAtomPair = getOtherAtomPair(bondPair, nextAtomPair, atomMap, atomPairList);
            changedBondPairList.remove(bondPair);
            MappedBondPair otherBondPair = getUniqueBondPairContaining(changedBondPairList, middleAtomPair);
            if (otherBondPair == null) {
                if (!changedAtomPairList.contains(middleAtomPair)) {
                	LOG.debug("Cannot find terminal atom ("+middleAtomPair+") in electron chain");
                } else {
                    addElectrons(bondPair, middleAtomPair, electronPairList);
                    changedAtomPairList.remove(middleAtomPair);
                    changedBondPairList.remove(bondPair);
               }
               break;
            }
            addElectrons(bondPair, otherBondPair, electronPairList);
            changedAtomPairList.remove(middleAtomPair);
            changedBondPairList.remove(otherBondPair);
            nextAtomPair = getOtherAtomPair(otherBondPair, middleAtomPair, atomMap, atomPairList);
        }
        return change;
    }
    
    @SuppressWarnings("unused")
    private boolean iterateCycle(List atomPairList, List changedBondPairList, MappedAtomPair nextAtomPair, CMLMap atomMap, List electronPairList) {
        // iterate down chain two bonds at a time until terminal atom
        boolean change = true;
        while (true) {
            MappedBondPair bondPair = getUniqueBondPairContaining(changedBondPairList, nextAtomPair);
            if (bondPair == null) {
                change = false;
                break;
            }
            MappedAtomPair middleAtomPair = getOtherAtomPair(bondPair, nextAtomPair, atomMap, atomPairList);
            changedBondPairList.remove(bondPair);
            MappedBondPair otherBondPair = getUniqueBondPairContaining(changedBondPairList, middleAtomPair);
            if (otherBondPair == null) {
                changedBondPairList.remove(bondPair);
                break;
            }
            addElectrons(bondPair, otherBondPair, electronPairList);
            changedBondPairList.remove(otherBondPair);
            nextAtomPair = getOtherAtomPair(otherBondPair, middleAtomPair, atomMap, atomPairList);
        }
        return change;
    }

    /** gets next atom with a negative electron change.
     * 
     * @param changedPairList
     * @return atompair
     */
    private MappedAtomPair getNextChangedAtomSource(List changedPairList) {
        for (AtomBondPair abp : changedPairList) {
            if (abp instanceof MappedAtomPair && abp.electronChange < 0) {
                return (MappedAtomPair) abp;
            }
        }
        return null;
    }
    
    @SuppressWarnings("unchecked")
	private MappedAtomPair getOtherAtomPair(MappedBondPair bondPair, MappedAtomPair atomPair, CMLMap atomMap, List atomPairList) {
    	if (bondPair == null || atomPair == null || atomMap == null) {
    		return null;
    	}
    	String fromId = null;
    	String toId = null;
    	if (atomMap == null) {
    	} else if (bondPair.bond1 == null) {
        	toId = bondPair.bond2.getOtherAtomId(atomPair.id2);
        	fromId = atomMap.getRef(fromId, CMLMap.Direction.FROM);
        } else if (bondPair.bond2 == null) {
        	fromId = bondPair.bond1.getOtherAtomId(atomPair.id1);
        	toId = atomMap.getRef(toId, CMLMap.Direction.TO);
        } else {
        	fromId = bondPair.bond1.getOtherAtomId(atomPair.id1);
        	toId = bondPair.bond2.getOtherAtomId(atomPair.id2);
        }
    	MappedAtomPair otherAtomPair = MappedAtomPair.getAtomPair(toId, fromId, atomPairList);
    	return otherAtomPair;
    }


    private MappedBondPair getUniqueBondPairContaining(List bondPairList, MappedAtomPair atomPair) {
        for (int i = 0; i < bondPairList.size(); i++) {
            MappedBondPair bondPair = bondPairList.get(i);
            if (bondPair.containsAtomPair(atomPair)) {
                return bondPair;
            }
        }
        return null;
    }
    
    /** add electrons to appropriate to/from atom/bonds as result of transfer.
     * 
     * add electron id to electronIdList
     * @param changedPairList
     * @param abp1
     * @param abp2
     */
    private void addElectrons(AtomBondPair abp1, AtomBondPair abp2, List electronPairList) {
        String electronId = "e"+(electronIdList.size()+1);
        electronIdList.add(electronId);
        // create electrons and add to appropriate atom or bond
        CMLElectron electron1 = abp1.createElectrons(abp1.electronChange, electronId);
        CMLElectron electron2 = abp2.createElectrons(abp2.electronChange, electronId);
        electronPairList.add(new ElectronPair(electron1, electron2));
    }
    
    // must redo this to cope with mapping 
    @SuppressWarnings("unchecked")
	void processElectrons(CMLMolecule molecule1, CMLMolecule molecule2, int serial) {
        List atomPairList = getAtomPairList(null, molecule1, molecule2, serial);
        // the generics are inconsistent here - FIXME
        List changedPairList = new ArrayList();
        for (MappedAtomPair atomPair : atomPairList) {
            compareAtoms(atomPair, changedPairList);
        }
// FIXME
        //        List bondPairList = getBondPairList(null, molecule1, molecule2, serial);
        List bondPairList = null;
        for (MappedBondPair bondPair : bondPairList) {
            compareBonds(bondPair, changedPairList);
        }
        if (changedPairList.size() > 0) {
            LOG.debug("Changed atoms/bonds ("+serial+")");
            for (int i = 0; i < changedPairList.size(); i++) {
                LOG.debug("BP "+changedPairList.get(i));
            }
        }
//        List electronPairList = getElectronPairList(null, molecule1, molecule2, serial);
        @SuppressWarnings("unused")
        List electronPairList = getElectronPairList(null, molecule1, molecule2, serial);

// there may be several unconnected fragments so go on until no change
        boolean change = true;
        while (change == true) {
            change = false;
// get start atom
            MappedAtomPair atomPair = getNextChangedAtomSource(changedPairList);
            if (atomPair != null) {
//                AtomBondPair abp1 = atomPair;
                change = true;
                MappedBondPair bondPair = getUniqueBondPairContaining(changedPairList, atomPair);
                if (bondPair == null) {
                    LOG.error("terminal atom cannot find unique ligand bond");
                    change = false;
                    break;
                }
                if (true) throw new RuntimeException("FIX ME");
//                removeFromChangedListAddElectrons(changedMappedAtomPairList, atomPair, bondPair);
//                LOG.debug("started: "+atomPair.id1+" ==> "+bondPair);
//
////                String nextAtomId = getOtherAtomId(bondPair, atomPair);
//                String nextAtomId = bondPair.bond1.getOtherAtomId(atomPair.id1);
//                iterateChain(changedMappedAtomPairList, nextAtomId);
            }
        }

// cycles? take an arbitrary starting point and try
        while (true) {
            int size = changedPairList.size();
            if (size == 0) {
                LOG.info("Finished all electrons");
                break;
            } else {
                LOG.debug("Cycles atoms/bonds ("+serial+")");
                AtomBondPair abp = (AtomBondPair) changedPairList.get(0);
                if (abp instanceof MappedBondPair) {
                    MappedBondPair bondPair = (MappedBondPair) abp;
                    @SuppressWarnings("unused")
                    String atomId = (bondPair.bond1 != null) ?
                        bondPair.bond1.getAtomId(0) :
                        bondPair.bond2.getAtomId(0);
// FIXME                    iterateChain(changedPairList, atomId);
                }
            }
            if (size == changedPairList.size()) {
                LOG.error("Cannot exhaust electron transfers");
                break;
            }
        }
    }
    
    /** add atoms from two molecules.
     * the mapping is done through IDs
     * @param parent
     * @param molTool1
     * @param molTool2
     * @param serial
     * @return atompair list
     */
    List getAtomPairList(Element parent, 
    		CMLMolecule mol1, CMLMolecule mol2, int serial) {
        List atomPairList = new ArrayList();
        List atoms1 = mol1.getAtoms();
        List atoms2 = mol2.getAtoms();
//      find atoms in first molecule or both
        for (CMLAtom atom1 : atoms1) {
            String id = (atom1.getId());
            CMLAtom atom2 = mol2.getAtomById(id);
            atomPairList.add(new MappedAtomPair(atom1, atom2));
        }
// find atoms in second molecule only
        for (CMLAtom atom2 : atoms2) {
            String id = (atom2.getId());
            if (mol1.getAtomById(id) == null) {
                atomPairList.add(new MappedAtomPair(null, atom2));
            }
        }
        return atomPairList;
    }

    /** add atoms aligned through map
     * 
     * @param parent the SVG element (maybe don't need at this stage?)
     * @param atoms1 atoms with "from" references in map
     * @param atoms2 atoms with "to" references in map
     * @param atomPairList to add the mapped atoms to
     * @param atomMapTool from atoms1 to atoms2
     * @return list
     */
    List addToMappedAtomPairList(Element parent, CMLAtom[] atoms1, CMLAtom[] atoms2,
    		List atomPairList, CMLMap atomMap, int serial) {
    	Map atomMap1 = createLookupTableById(atoms1);
    	Map atomMap2 = createLookupTableById(atoms2);
// find atoms atomMap
    	List fromRefs = atomMap.getFromRefs();
    	if (fromRefs.size() == 0) {
    		LOG.debug("NO FROM REFS+++++++++++++++++");
    	}
        for (String fromRef : fromRefs) {
            CMLAtom atom1 = atomMap1.get(fromRef);
            String id2 = atomMap.getToRef(fromRef);
            CMLAtom atom2 = atomMap2.get(id2);
            if (atom2 == null) {
            	LOG.debug("NO MATCHED ATOM"+id2);
            }
            atomPairList.add(new MappedAtomPair(atom1, atom2));
        }
        return atomPairList;
    }
    
    /** create a table to lookup atoms by Id.
     * 
     * @param atoms array of atoms to index by ID
     * @return Map indexed on ID
     */
    private static Map createLookupTableById(CMLAtom[] atoms) {
    	Map map = new HashMap();
    	for (CMLAtom atom : atoms) {
    		map.put(atom.getId(), atom);
    	}
    	return map;
    }

    /**
     * @return reaction
     */
	public CMLReaction getReaction() {
		return reaction;
	}
	
    /** 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) {
    	ensureReactionDisplay();
    	Transform2 transform2 = new Transform2(
    			new double[] {
    				1.,  0., 0.0,
    				0., -1., 0.0,
    				0.,  0., 1.}
    			);
    
    	List molecules = reaction.getMolecules(Component.REACTANT);
    	if (molecules.size() == 0) {
    		LOG.debug("No molecules to display");
    	} else if (applyScale) {
    		transform2 = scaleToBoundingBoxesAndScreenLimits(molecules);
    	}
    	
    	SVGElement g = createSVGElement(drawable, transform2);
    	g.setProperties(getReactionDisplay());
    	MoleculeDisplay moleculeDisplay = getReactionDisplay().getMoleculeDisplay();
    	displayMolecules(drawable, g, moleculeDisplay, molecules);
    	try {
    		drawable.output(g);
    	} catch (IOException e) {
    		throw new RuntimeException(e);
    	}
    	return g;
    }

	private Transform2 scaleToBoundingBoxesAndScreenLimits(List molecules) {
		Transform2 transform2 = null;
		try {
			Real2Range boundingBox = getBoundingBox(molecules);
			Real2Interval screenBoundingBox = getReactionDisplay().getMoleculeDisplay().getScreenExtent();
			Real2Interval moleculeInterval = new Real2Interval(boundingBox);
			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;
	}
    
	Real2Range getBoundingBox(List molecules) {
		Real2Range range = new Real2Range();
		for (CMLMolecule molecule : molecules) {
			AbstractSVGTool moleculeTool = MoleculeTool.getOrCreateTool(molecule);
			Real2Range molRange = moleculeTool.calculateBoundingBox2D();
			range.plus(molRange);
		}
		return range;
	}

    
	private void displayMolecules(CMLDrawable drawable, SVGElement g,
			MoleculeDisplay moleculeDisplay, List molecules) {
		for (CMLMolecule molecule : molecules) {
    		MoleculeTool moleculeTool = MoleculeTool.getOrCreateTool(molecule);
    		moleculeTool.setMoleculeDisplay(moleculeDisplay);
    		GraphicsElement a = moleculeTool.createGraphicsElement(drawable);
    		if (a != null) {
    			a.detach();
    			g.appendChild(a);
    		}
		}
	}


    private void ensureReactionDisplay() {
    	if (getReactionDisplay() == null) {
    		setReactionDisplay(ReactionDisplay.getDEFAULT());
    	}
    }

    /** convenience method
     * 
     */
    public List getReactants() {
    	List reactants = new ArrayList();
    	CMLReactantList reactantList = reaction.getReactantList();
    	if (reactantList != null) {
        	for (CMLReactant reactant : reactantList.getReactantElements()) {
	    		reactants.add(reactant);
	    	}
    	}
    	return reactants;
    }
    
    /** convenience method
     * 
     * @param i
     * @return i'th reactant
     */
    public CMLReactant getReactant(int i) {
    	return this.getReactants().get(i);
    }
    
    /** convenience method
     * 
     * @param i
       * @return i'th reactant
     */
    public CMLMolecule getProductMolecule(int i) {
    	CMLProduct product = getProduct(i);
    	return (product == null) ? null : product.getMolecule();
    }
    
    /** convenience method
     * 
     */
    public List getProducts() {
    	List products = new ArrayList();
    	CMLProductList productList = reaction.getProductList();
    	if (productList != null) {
	    	for (CMLProduct product : productList.getProductElements()) {
	    		products.add(product);
	    	}
    	}
    	return products;
    }
    
    
    /** convenience method
     * 
     * @param i
     * @return i'th product
     */
    public CMLProduct getProduct(int i) {
    	return reaction.getProductList().getProductElements().get(i);
    }
    /** convenience method
     * 
     * @param i
     * @return i'th reactant
     */
    public CMLMolecule getReactantMolecule(int i) {
    	CMLReactant reactant = getReactant(i);
    	return (reactant == null) ? null : reactant.getMolecule();
    }
    
    /** convenience method
     */
    public List getReactantMolecules() {
    	List list = new ArrayList();
    	for (CMLReactant reactant : this.getReactants()) {
    		CMLMolecule molecule = reactant.getMolecule();
    		if (molecule != null) {
    			list.add(molecule);
    		}
    	}
    	return list;
    }


    /** convenience method
     */
    public List getProductMolecules() {
    	List list = new ArrayList();
    	for (CMLProduct product : this.getProducts()) {
    		CMLMolecule molecule = product.getMolecule();
    		if (molecule != null) {
    			list.add(molecule);
    		}
    	}
    	return list;
    }

    /** convenience method
     * 
     */
    public List getSpectators() {
    	List spectators = new ArrayList();
    	Nodes spectatorNodes = reaction.query("./*[local-name()='"+CMLSpectatorList.TAG+"']/*[local-name()='"+CMLSpectator.TAG+"']");
    	for (int i = 0; i < spectatorNodes.size(); i++) {
    		spectators.add((CMLSpectator)spectatorNodes.get(i));
    	}
    	return spectators;
    }
    
    /** convenience method
     */
    public List getSpectatorMolecules() {
    	List list = new ArrayList();
    	for (CMLSpectator spectator : this.getSpectators()) {
    		CMLMolecule molecule = spectator.getMolecule();
    		if (molecule != null) {
    			list.add(molecule);
    		}
    	}
    	return list;
    }


	public CMLMap mapReactantAtomsToProductsUsingIds() {
		CMLMap map = new CMLMap();
		CMLAtomSet reactantAtomSet = getReactantAtomSet();
		CMLAtomSet productAtomSet = getProductAtomSet();
		mapReactantAtomsToProductsIncludingMissing(map, reactantAtomSet, productAtomSet);
		return map;
	}

	public void flattenReactantAndProductMolecules() {
		ReactionComponent component = reaction.getReactantList();
		CMLMolecule molecule = flatten(component);
		molecule.setId("r_mol");
		((CMLElement)component).detach();
		reaction.addReactant(molecule);
		
		component = reaction.getProductList();
		molecule = flatten(component);
		molecule.setId("p_mol");
		((CMLElement)component).detach();
		reaction.addProduct(molecule);
	}

	private CMLMolecule flatten(ReactionComponent component) {
		List molecules = component.getMolecules();
		CMLMolecule molecule = new CMLMolecule();
		ConnectionTableTool.flattenMolecules(molecules, molecule);
		return molecule;
		
	}


	public CMLMap mapReactantAtomsToProductsUsingIdsAndIncludingMissing() {
		CMLMap map = new CMLMap();
		mapReactantAtomsToProductsIncludingMissing(
				map, this.getReactantAtomSet(), this.getProductAtomSet());
		return map;
	}

	private void mapReactantAtomsToProductsIncludingMissing(CMLMap map,
			CMLAtomSet reactantAtomSet, CMLAtomSet productAtomSet) {
		List reactantAtoms = reactantAtomSet.getAtoms();
		List productAtoms = productAtomSet.getAtoms();
		mapAtomsIncludingMissing(map, productAtomSet, reactantAtoms, Direction.FROM);
		mapAtomsIncludingMissing(map, reactantAtomSet, productAtoms, Direction.TO);

	}

	private void mapAtomsIncludingMissing(
			CMLMap map, CMLAtomSet atomSet, List otherAtoms, CMLMap.Direction direction) {
		String toContext = otherAtoms.get(0).getMolecule().getId();
		map.setToContext(toContext);
		String fromContext = atomSet.getMolecule().getId();
		map.setFromContext(fromContext);
		for (CMLAtom otherAtom : otherAtoms) {
			CMLAtom atom = getAtomOrCreateAndAddGhost(atomSet, otherAtom);
			addUniqueLink(map, direction, otherAtom, atom);
		}
	}

	private void addUniqueLink(CMLMap map, CMLMap.Direction direction,
			CMLAtom otherAtom, CMLAtom atom) {
		if (map.getLink(otherAtom.getId(), direction) == null) {
			CMLLink link = new CMLLink();
			LinkTool linkTool = LinkTool.getOrCreateTool(link);
			linkTool.addToAndFrom(direction, otherAtom, atom);
			map.addLink(link);
		}
	}

	private CMLAtom getAtomOrCreateAndAddGhost(CMLAtomSet atomSet, CMLAtom otherAtom) {
		CMLAtom atom = atomSet.getAtomById(otherAtom.getId());
		if (atom == null) {
			atom = new CMLAtom(otherAtom);
			atom.setOccupancy(0.0);
			atomSet.getMolecule().addAtom(atom);
		}
		return atom;
	}

	private CMLAtomSet getProductAtomSet() {
		CMLProductList productList = reaction.getProductList();
		List productAtoms = productList.getAtoms();
		return new CMLAtomSet(productAtoms.toArray(new CMLAtom[0]));
	}

	private CMLAtomSet getReactantAtomSet() {
		CMLReactantList reactantList = reaction.getReactantList();
		List reactantAtoms = reactantList.getAtoms();
		return new CMLAtomSet(reactantAtoms.toArray(new CMLAtom[0]));
	}


	public CMLMap mapReactantBondsToProductsUsingIdsAndIncludingMissing() {
		CMLMap map = new CMLMap();
		mapReactantBondsToProductsIncludingMissing(
				map, this.getReactantBondSet(), this.getProductBondSet());
		return map;
	}

	private void mapReactantBondsToProductsIncludingMissing(CMLMap map,
			CMLBondSet reactantBondSet, CMLBondSet productBondSet) {
		List reactantBonds = reactantBondSet.getBonds();
		List productBonds = productBondSet.getBonds();
		mapBondsIncludingMissing(map, productBondSet, reactantBonds, Direction.FROM);
		mapBondsIncludingMissing(map, reactantBondSet, productBonds, Direction.TO);

	}

	private void mapBondsIncludingMissing(
			CMLMap map, CMLBondSet bondSet, List otherBonds, CMLMap.Direction direction) {
		for (CMLBond otherBond : otherBonds) {
			CMLBond bond = getBondOrCreateAndAddGhost(bondSet, otherBond);
			addUniqueLink(map, direction, otherBond, bond);
		}
	}
	
	private void addUniqueLink(CMLMap map, CMLMap.Direction direction,
			CMLBond otherBond, CMLBond bond) {
		if (map.getLink(otherBond.getId(), direction) == null) {
			CMLLink link = new CMLLink();
			LinkTool linkTool = LinkTool.getOrCreateTool(link);
			linkTool.addToAndFrom(direction, otherBond, bond);
			map.addLink(link);
		}
	}


	private CMLBond getBondOrCreateAndAddGhost(CMLBondSet bondSet,
			CMLBond otherBond) {
		CMLBond bond = bondSet.getBondById(otherBond.getId());
		if (bond == null) {
			bond = new CMLBond(otherBond);
			bond.setOrder(CMLBond.ZERO);
			bondSet.getMolecule().addBond(bond);
			bond.setCMLXAttribute("electrons", "0");
		}
		return bond;
	}

	private CMLBondSet getProductBondSet() {
		CMLProductList productList = reaction.getProductList();
		List productBonds = productList.getBonds();
		return new CMLBondSet(productBonds.toArray(new CMLBond[0]));
	}

	private CMLBondSet getReactantBondSet() {
		CMLReactantList reactantList = reaction.getReactantList();
		List reactantBonds = reactantList.getBonds();
		return new CMLBondSet(reactantBonds.toArray(new CMLBond[0]));
	}



	public void mapReactantsToProducts() {
//		List reactantMolecules = getMolecules(Component.REACTANT);
//		List productMolecules = getMolecules(Component.PRODUCT);
//		List> reactantMorgans = extractMorgans(reactantMolecules);
//		List> productMorgans = extractMorgans(productMolecules);
		mapSingleReactantToSingleProductWithAtomMatcher();
	}
	
	public CMLMap mapReactantsToProductsUsingAtomSets() {
		List reactantMolecules = getMolecules(Component.REACTANT);
		if (reactantMolecules.size() == 0) {
			throw new RuntimeException("No reactants");
		}
		CMLAtomSet reactantAtomSet = MoleculeTool.createAtomSet(reactantMolecules);
		if (reactantAtomSet == null || reactantAtomSet.size() == 0) {
			throw new RuntimeException("No atoms in reactants");
		}
		List productMolecules = getMolecules(Component.PRODUCT);
		if (reactantMolecules.size() == 0) {
			throw new RuntimeException("No products");
		}
		CMLAtomSet productAtomSet = MoleculeTool.createAtomSet(productMolecules);
		if (productAtomSet == null || productAtomSet.size() == 0) {
			throw new RuntimeException("No atoms in products");
		}
		AtomMatcher atomMatcher = new AtomTreeMatcher();
		CMLMap cmlMap = atomMatcher.mapAtomSets(reactantAtomSet, productAtomSet);
		translateMapToUseOriginalIds(cmlMap, reactantAtomSet, productAtomSet);
		reaction.addMap(cmlMap);
		
		return cmlMap;
	}

	private void translateMapToUseOriginalIds(CMLMap cmlMap,
			CMLAtomSet reactantAtomSet, CMLAtomSet productAtomSet) {
		translateMap(Direction.FROM, reactantAtomSet, cmlMap);
		translateMap(Direction.TO, productAtomSet, cmlMap);
	}

	private void translateMap(Direction direction, CMLAtomSet atomSet, CMLMap cmlMap) {
		Map atomSet2Map = new HashMap();
		List atoms = atomSet.getAtoms();
		for (CMLAtom atom : atoms) {
			String id = atom.getId();
			CMLLabel label = atom.getLabelElements().get(0);
			String labelValue = (label == null) ? null : label.getCMLValue();
			if (labelValue != null) {
				atomSet2Map.put(id, labelValue);
			}
		}
		MapTool cmlMapTool = MapTool.getOrCreateTool(cmlMap);
		cmlMapTool.translateIds(direction, atomSet2Map);
	}

	public CMLReactant addReactant(String smiles) {
		SMILESTool smilesTool = new SMILESTool();
		CMLReactant reactant = new CMLReactant();
		reactant.addMolecule(FormulaTool.calculateMolecule(smilesTool, smiles));
		reaction.addReactant(reactant);
		return reactant;
	}

	public CMLReactant addReactant(CMLMolecule reactantMol) {
		CMLReactant reactant = new CMLReactant();
		reactant.addMolecule(reactantMol);
		this.reaction.addReactant(reactant);
		return reactant;
	}

	public CMLSpectator addSpectator(String smiles) {
		SMILESTool smilesTool = new SMILESTool();
		CMLSpectator spectator = new CMLSpectator();
		spectator.addMolecule(FormulaTool.calculateMolecule(smilesTool, smiles));
		reaction.addSpectator(spectator);
		return spectator;
	}

	public CMLSpectator addSpectator(CMLMolecule spectatorMol) {
		CMLSpectator spectator = new CMLSpectator();
		spectator.addMolecule(spectatorMol);
		this.reaction.addSpectator(spectator);
		return spectator;
	}

	public CMLProduct addProduct(String smiles) {
		SMILESTool smilesTool = new SMILESTool();
		CMLMolecule productMolecule = FormulaTool.calculateMolecule(smilesTool, smiles);
		CMLProduct product = new CMLProduct();
		product.addMolecule(productMolecule);
		this.addProduct(product);
		return product;
	}
	
	/** should be part of CMLReaction
	 * 
	 * @param product
	 */
	public CMLProduct addProduct(CMLProduct product) {
		CMLProductList productList = reaction.getProductList();
		if (productList == null) {
			productList = new CMLProductList();
			reaction.addProductList(productList);
		}
		productList.addProduct(product);
		return product;
	}

//	private List> extractMorgans(List molecules) {
//		List> morgans = new ArrayList>();
//		for (CMLMolecule molecule : molecules) {
//			Morgan morgan = new Morgan(MoleculeTool.getOrCreateTool(molecule).getAtomSet());
//			List morganLongs = morgan.getMorganList();
//			List atomSets = morgan.getAtomSetList();
//			for (int i = 0; i < morganLongs.size(); i++) {
//				System.out.println(" "+morganLongs.get(i)+" .. "+atomSets.get(i).getValue());
//			}
//			System.out.println();
//		}
//		return morgans;
//	}

	/** 
	 * if reaction has a single reactant and single product uses
	 * AtomMatcher.mapMolecules to get atom2atom match
	 * @return
	 */
	public CMLMap mapSingleReactantToSingleProductWithAtomMatcher() {
		CMLMap map = null;
		List reactantMolecules = reaction.getMolecules(Component.REACTANT);
		List productMolecules = reaction.getMolecules(Component.PRODUCT);
		if (reactantMolecules.size() == 1 && productMolecules.size() == 1) {
			CMLMolecule reactantMolecule = reactantMolecules.get(0);
			CMLMolecule productMolecule = productMolecules.get(0);
			AtomMatcher atomMatcher = new AtomTreeMatcher();
			map = atomMatcher.mapMolecules(reactantMolecule, productMolecule);
			CMLAtomSet reactantAtomSet = new CMLAtomSet(reactantMolecule);
			CMLAtomSet productAtomSet = new CMLAtomSet(productMolecule);			
//			reactantAtomSet.debug("react");
//			productAtomSet.debug("prod");
//			map = atomMatcher.getUniqueMatchedAtoms(reactantAtomSet, productAtomSet);			
			map.debug("FINAL MAP");
		}
		return map;
	}

	public void guessProducts() {
		CMLFormula diffFormula = this.calculateDifferenceFormula();
		String concise = diffFormula.getConcise();
		if (concise == null || concise.trim().equals(CMLConstants.S_EMPTY)) {
			// do nothing
		} else {
			CMLMolecule guessedProductMolecule = getProbableProduct(concise);
			if (guessedProductMolecule != null) {
				this.addProduct(guessedProductMolecule);
			}
		}
	}

	public static CMLMolecule getProbableProduct(String concise) {
		CMLMolecule molecule = null;
		if (concise != null) {
			concise = concise.trim();
			molecule = FormulaTool.ensureConcise2MoleculeMap().get(concise);
		}
		return molecule;
	}

	public CMLProduct addProduct(CMLMolecule productMolecule) {
		CMLProduct product = new CMLProduct();
		product.addMolecule(productMolecule);
		this.addProduct(product);
		return product;
	}

	public void addPatternsToLinkedAtoms(SVGGBox reactantsSVG, SVGGBox productsSVG, CMLMap map) {
		CMLElements links = map.getLinkElements();
		List uniqueLinks = new ArrayList();
		List balancedCommonLinks = new ArrayList();
		List otherLinks = new ArrayList();
		
		for (CMLLink link : links) {
			String title = link.getTitle();
			if (title.startsWith(AtomTreeMatcher.UNIQUE_TREE)) {
				uniqueLinks.add(link);
			} else if (title.startsWith("balanced commonAtomTree")) {
				balancedCommonLinks.add(link);
			} else {
				otherLinks.add(link);
			}
		}
		addPatternsToLinkAtoms(reactantsSVG, productsSVG, uniqueLinks, 6.0, "black");
		addPatternsToLinkAtoms(reactantsSVG, productsSVG,  balancedCommonLinks, 6.0, "red");
		addPatternsToLinkAtoms(reactantsSVG, productsSVG,  otherLinks, 6.0, "blue");
	}
	
	private void addPatternsToLinkAtoms(SVGGBox reactantsSVG, SVGGBox productsSVG, List links, double strokeWidth, String strokeColour) {
		int gradient = 0;
		int nlinks = links.size();
		for (CMLLink link : links) {
			gradient++;
			addPattern(reactantsSVG, link.getFromSet(), gradient, strokeWidth, strokeColour);
			addDefs(reactantsSVG);
			addPattern(productsSVG, link.getToSet(), gradient, strokeWidth, strokeColour);
			addDefs(productsSVG);
		}
	}

	private void addDefs(SVGGBox svgg) {
		ensurePatterns();
		if (svgg != null && svgg.query("//*[local-name()='defs']").size() == 0) {
			svgg.insertChild(defs.copy(), 0);
		}
	}
	
	private static Element ensurePatterns() {
		if (patternCount == 0 && defs == null) {
			defs = new Element("defs", CMLConstants.CML_NS);
			double w01 = 4.0;
			double h01 = 4.0;
			double w11 = 4.0;
			double h11 = 4.0;
			
			double w02 = 8.0;
			double h02 = 4.0;
			double w12 = 8.0;
			double h12 = 4.0;
			
			double w03 = 4.0;
			double h03 = 8.0;
			double w13 = 4.0;
			double h13 = 8.0;
			
			double w04 = 8.0;
			double h04 = 8.0;
			double w14 = 8.0;
			double h14 = 8.0;
			
			double w05 = 4.0;
			double h05 = 12.0;
			double w15 = 4.0;
			double h15 = 12.0;
			
			double w06 = 12.0;
			double h06 = 4.0;
			double w16 = 12.0;
			double h16 = 4.0;
			
			double w07 = 8.0;
			double h07 = 12.0;
			double w17 = 8.0;
			double h17 = 12.0;
			
			double w08 = 12.0;
			double h08 = 8.0;
			double w18 = 12.0;
			double h18 = 8.0;
			
			String red = "#FF7777";
			defs.appendChild(createPattern(w01, h01, w11, h11, ++patternCount, red));
			defs.appendChild(createPattern(w02, h02, w12, h12, ++patternCount, red));
			defs.appendChild(createPattern(w03, h03, w13, h13, ++patternCount, red));
			defs.appendChild(createPattern(w04, h04, w14, h14, ++patternCount, red));
			defs.appendChild(createPattern(w05, h05, w15, h15, ++patternCount, red));
			defs.appendChild(createPattern(w06, h06, w16, h16, ++patternCount, red));
			defs.appendChild(createPattern(w07, h07, w17, h17, ++patternCount, red));
			defs.appendChild(createPattern(w08, h08, w18, h18, ++patternCount, red));
			
			String blue = "#7777FF";
			defs.appendChild(createPattern(w01, h01, w11, h11, ++patternCount, blue));
			defs.appendChild(createPattern(w02, h02, w12, h12, ++patternCount, blue));
			defs.appendChild(createPattern(w03, h03, w13, h13, ++patternCount, blue));
			defs.appendChild(createPattern(w04, h04, w14, h14, ++patternCount, blue));
			defs.appendChild(createPattern(w05, h05, w15, h15, ++patternCount, blue));
			defs.appendChild(createPattern(w06, h06, w16, h16, ++patternCount, blue));
			defs.appendChild(createPattern(w07, h07, w17, h17, ++patternCount, blue));
			defs.appendChild(createPattern(w08, h08, w18, h18, ++patternCount, blue));
			
			String green = "#77FF77";
			defs.appendChild(createPattern(w01, h01, w11, h11, ++patternCount, green));
			defs.appendChild(createPattern(w02, h02, w12, h12, ++patternCount, green));
			defs.appendChild(createPattern(w03, h03, w13, h13, ++patternCount, green));
			defs.appendChild(createPattern(w04, h04, w14, h14, ++patternCount, green));
			defs.appendChild(createPattern(w05, h05, w15, h15, ++patternCount, green));
			defs.appendChild(createPattern(w06, h06, w16, h16, ++patternCount, green));
			defs.appendChild(createPattern(w07, h07, w17, h17, ++patternCount, green));
			defs.appendChild(createPattern(w08, h08, w18, h18, ++patternCount, green));
			
			defs.appendChild(createPattern(w01, h01, w11, h11, ++patternCount, "black"));
			defs.appendChild(createPattern(w02, h02, w12, h12, ++patternCount, "black"));
			defs.appendChild(createPattern(w03, h03, w13, h13, ++patternCount, "black"));
			defs.appendChild(createPattern(w04, h04, w14, h14, ++patternCount, "black"));
			defs.appendChild(createPattern(w05, h05, w15, h15, ++patternCount, "black"));
			defs.appendChild(createPattern(w06, h06, w16, h16, ++patternCount, "black"));
			defs.appendChild(createPattern(w07, h07, w17, h17, ++patternCount, "black"));
			defs.appendChild(createPattern(w08, h08, w18, h18, ++patternCount, "black"));
		}
		return defs;
	}


	private void addPattern(SVGGBox svgg, String[] ids, int gradient, 
			double strokeWidth, String strokeColour) {
		if (svgg != null && ids != null) {
			for (String id : ids) {
				String aid = id.substring("m1_".length());
				String gid = "g_"+aid;
				Nodes gNodes = svgg.query("//*[local-name()='g' and @id='g_"+aid+"']/*[local-name()='circle']");
				if (gNodes.size() == 1) {
					SVGCircle circle = (SVGCircle) gNodes.get(0);
					circle.setFill("url(#"+PATTERN+gradient+")");
					circle.setStrokeWidth(strokeWidth);
					circle.setStroke(strokeColour);
					Nodes textNodes = ((Element)gNodes.get(0)).getParent().query("*[local-name()='text']");
					if (textNodes.size() == 1) {
						((SVGText) textNodes.get(0)).setFill("black");
					}
				}
			}
		}
	}

	private static Element createPattern(double width0, double height0, double width,
			double height, int iid, String fill) {
		String id = PATTERN+iid;
		String patternUnits = "userSpaceOnUse";
		Element pattern = new Element("pattern", CMLConstants.SVG_NS);
		pattern.addAttribute(new Attribute("id", id));
		pattern.addAttribute(new Attribute("patternUnits", patternUnits));
		pattern.addAttribute(new Attribute("width", ""+width0));
		pattern.addAttribute(new Attribute("height", ""+height0));
		SVGRect rect = new SVGRect(0.,0., width, height);
		rect.setFill(fill);
		rect.setStroke("white");
		rect.setStrokeWidth(2.0);
		pattern.appendChild(rect);
		return pattern;
	}
	
	private void addGraphicalLinks(SVGSVG svgTot, SVGG g0, SVGG g1) {
		Real2 offset0 = getOffset(g0);
		Real2 offset1 = getOffset(g1);
		Map> map0 = indexCirclesByFill(g0);
		Map> map1 = indexCirclesByFill(g1);
		for (String fill : map0.keySet()) {
			List circles0 = map0.get(fill);
			List circles1 = map1.get(fill);
			if (circles0 != null && circles1 != null) {
				for (SVGCircle circle0 : circles0) {
					for (SVGCircle circle1 : circles1) {
						SVGLine line = createLine(offset0, offset1, circle0, circle1);
						svgTot.appendChild(line);
					}
				}
			}
		}
	}

	private Real2 getOffset(SVGG g) {
		return g.getTransform2FromAttribute().getTranslation();
	}

	private SVGLine createLine(Real2 offset0, Real2 offset1, SVGCircle circle0, SVGCircle circle1) {
		Real2 cxy0 = getCircleCentre(circle0);
		cxy0 = cxy0.plus(offset0);
		Real2 cxy1 = getCircleCentre(circle1);
		cxy1 = cxy1.plus(offset1);
		SVGLine line = new SVGLine(cxy0, cxy1);
		line.setStroke("green");
		line.setStrokeWidth(0.5);
		return line;
	}

	private Real2 getCircleCentre(SVGCircle circle) {
		SVGG g = (SVGG) circle.getParent();
		Transform2 t = g.getTransform2FromAttribute();
		Real2 cxy = t.getTranslation();
		cxy.y = -cxy.y;
		return cxy;
	}

	private Map> indexCirclesByFill(SVGG g) {
		Map> circleListByFill = new HashMap>();
		Nodes circles = g.query(".//*[local-name()='"+SVGCircle.TAG+"']");
		for (int i = 0; i < circles.size(); i++) {
			SVGCircle circle = (SVGCircle) circles.get(i);
			String fill = circle.getFill();
			List circleList = circleListByFill.get(fill);
			if (circleList == null) {
				circleList = new ArrayList();
				circleListByFill.put(fill, circleList);
			}
			circleList.add(circle);
		}
		return circleListByFill;
	}

	public static boolean getCommand(String[] commands, String string) {
		for (String command : commands) {
			if (command != null && command.equals(string)) {
				return true;
			}
		}
		return false;
	}

	public void alignFirstReactantProduct2D() {
		CMLMolecule reactant0 = this.getReactantMolecule(0);
		CMLMolecule product0 = this.getProductMolecule(0);
	}

	public SVGGBox drawSVG() {
		SVGGBox svgTot = new SVGGBox();
		if (reactionDisplay.getId() != null) {
			svgTot.setId(reactionDisplay.getId());
		}
		SVGGBox reactantsSVGG = drawReactants();
		svgTot.addSVGG(reactantsSVGG);
		double maxHeight = getMaxHeight(reactantsSVGG);
		
		SVGGBox spectatorsSVGG = drawSpectators();
		drawAndAdd(svgTot, maxHeight, spectatorsSVGG);
		double maxHeight0 = getMaxHeight(reactantsSVGG);
		maxHeight += maxHeight0;
		
		SVGGBox productsSVGG = drawProducts();
		drawAndAdd(svgTot, maxHeight, productsSVGG);
		
		Transform2 transformG = svgTot.ensureTransform2();
		transformG = transformG.concatenate(
				Transform2.applyScales(getReactionDisplay().getScales().x, getReactionDisplay().getScales().y));
		svgTot.setTransform(transformG);
		return svgTot;
	}

	private void drawAndAdd(SVGGBox svgTot, double maxHeight, SVGGBox componentsSVG) {
		Transform2 transform = componentsSVG.getTransform2FromAttribute();
		if (transform == null) {
			transform = new Transform2();
		}
		transform = transform.concatenate(new Transform2(new Vector2(0.0, maxHeight)));
		componentsSVG.setTransform(transform);
		svgTot.addSVGG(componentsSVG);
	}

	private double getMaxHeight(SVGG reactantsSVGG) {
		Nodes gs = reactantsSVGG.query("./*[local-name()='"+SVGG.TAG+"']");
		double maxHeight = -1.0;
		for (int i = 0; i < gs.size(); i++) {
			SVGG g = (SVGG) gs.get(i);
			BoundingRect boundingRect = BoundingRect.createBoundingRect(g);
			if (boundingRect != null) {
				double height = boundingRect.getHeight();
				maxHeight = (maxHeight < height) ? height : maxHeight;
			}
		}
		return maxHeight;
	}

	private double getTotalHeight(SVGG reactantsSVGG) {
		Nodes gs = reactantsSVGG.query("./*[local-name()='"+SVGG.TAG+"']");
		double totalHeight = 0.0;
		for (int i = 0; i < gs.size(); i++) {
			BoundingRect boundingRect = BoundingRect.createBoundingRect((SVGG) gs.get(i));
			if (boundingRect != null) {
				totalHeight += boundingRect.getHeight();
			}
		}
		return totalHeight;
	}

	private double getMaxWidth(SVGG reactantsSVGG) {
		Nodes gs = reactantsSVGG.query("./*[local-name()='"+SVGG.TAG+"']");
		double maxWidth = -1.0;
		BoundingRect boundingRect = BoundingRect.createBoundingRect(g);
		if (boundingRect != null) {
			double width = boundingRect.getWidth();
			maxWidth = (maxWidth < width) ? width : maxWidth;
		}
		return maxWidth;
	}

	private double getTotalWidth(SVGG reactantsSVGG) {
		Nodes gs = reactantsSVGG.query("./*[local-name()='"+SVGG.TAG+"']");
		double totalWidth = 0.0;
		for (int i = 0; i < gs.size(); i++) {
			BoundingRect boundingRect = BoundingRect.createBoundingRect((SVGG) gs.get(i));
			if (boundingRect != null) {
				totalWidth += boundingRect.getWidth();
			}
		}
		return totalWidth;
	}

	public SVGGBox drawReactants() {
		return createMoleculeSVGs(getLayout(reactionDisplay.reactantOrientation),
				this.getReactantMolecules());
	}

	public SVGGBox drawProducts() {
		return createMoleculeSVGs(getLayout(reactionDisplay.productOrientation),
				this.getProductMolecules());
	}

	public SVGGBox drawSpectators() {
		List molecules = this.getSpectatorMolecules();
		return createMoleculeSVGs(getLayout(reactionDisplay.productOrientation),  molecules);
	}

	private SVGLayout getLayout(Orientation orientation) {
		SVGLayout layout = null;
		if (orientation.equals(Orientation.HORIZONTAL)) {
			layout = SVGLayout.LEFT2RIGHT;
		} else if (orientation.equals(Orientation.VERTICAL)) {
			layout = SVGLayout.TOP2BOTTOM;
		}
		return layout;
	}

	private SVGGBox createMoleculeSVGs(SVGLayout layout, List molecules) {
		SVGGBox svggBox = new SVGGBox();
		svggBox.setLayout(layout);
		ensureReactionDisplay();
		MoleculeDisplay moleculeDisplay = getReactionDisplay().getMoleculeDisplay();
		for (CMLMolecule molecule : molecules) {
			MoleculeTool moleculeTool = MoleculeTool.getOrCreateTool(molecule);
			if (moleculeTool != null) {
				SVGGBox svgg = moleculeTool.drawAndTranslateToRectCorner(moleculeDisplay);
				svggBox.addSVGG(svgg);
			}
		}
		return svggBox;
	}

	public void setReactionDisplay(ReactionDisplay reactionDisplay) {
		this.reactionDisplay = reactionDisplay;
	}

	public ReactionDisplay getReactionDisplay() {
		return reactionDisplay;
	}

	public void ensureIds() {
		int i = 0;
		for (CMLReactant reactant : this.getReactants()) {
			ReactantTool.getOrCreateTool(reactant).ensureId(ReactantTool.ID_PREFIX+(i++));
		}
		i = 0;
		for (CMLProduct product : this.getProducts()) {
			ProductTool.getOrCreateTool(product).ensureId(ProductTool.ID_PREFIX+(i++));
		}
	}

	/**
	 * finds reactant with smallest value of molarAmount / count.
	 * if count is absent, assume unity
	 * skip reactants without amounts
	 * @return
	 */
	public CMLReactant findLimitingReactant() {
		List reactants = this.getReactants();
		CMLReactant limitingReactant = null;
		double maximumMolesPerCount = Double.MAX_VALUE;
		for (CMLReactant reactant : reactants) {
			ReactantTool reactantTool = ReactantTool.getOrCreateTool(reactant);
			double molesPerCount = reactantTool.getMolesPerCount();
			if (!Double.isNaN(molesPerCount) && molesPerCount < maximumMolesPerCount) {
				maximumMolesPerCount = molesPerCount;
				limitingReactant = reactant;
			}
		}
		return limitingReactant;
	}

	public void createAndAlign2DCoordinates(CMLMap cmlMap) {
		boolean omitHydrogen = true;
		CMLMolecule reactantMolecule0 = this.getReactantMolecule(0);
		MoleculeTool.getOrCreateTool(reactantMolecule0).ensure2DCoordinates(omitHydrogen);
		CMLMolecule productMolecule0 = this.getProductMolecule(0);
		MoleculeTool productMoleculeTool0 = MoleculeTool.getOrCreateTool(productMolecule0);
//		if (ReactionTool.getCommand(commands, ReactionTool.STRIP_HYD)) {
//			MoleculeTool.getOrCreateTool(productMolecule0).stripHydrogens();
//			omitHydrogen = true;
//		}
		MoleculeTool.getOrCreateTool(productMolecule0).ensure2DCoordinates(omitHydrogen);
		Transform2 t2 = new Matcher2D().fit2D(reactantMolecule0, productMolecule0, cmlMap);
		productMoleculeTool0.transform(t2);
	}

	public void scaleMolecules(double scale) {
		List molecules = this.getAllMolecules();
		for (CMLMolecule molecule : molecules) {
			MoleculeTool moleculeTool = MoleculeTool.getOrCreateTool(molecule);
			CMLAtomSet atomSet = (moleculeTool == null) ? null : moleculeTool.getAtomSet();
			if (atomSet != null) {
				atomSet.scale2D(scale);
			}
		}
	}

	private List getAllMolecules() {
		List molecules = this.getProductMolecules();
		molecules.addAll(this.getReactantMolecules());
		molecules.addAll(this.getSpectatorMolecules());
		return molecules;
	}

	public void ensureAtoms() {
		List molecules = this.getAllMolecules();
		MoleculeTool.ensureAtoms(molecules);
	}

	public void ensureCoordinates() {
		List molecules = this.getAllMolecules();
		for (CMLMolecule molecule : molecules) {
			MoleculeTool moleculeTool = MoleculeTool.getOrCreateTool(molecule);
			boolean omitHydrogen = true;
			moleculeTool.ensure2DCoordinates(omitHydrogen);
		}
	}

	public void mapReactantAtomsAndBondsToProductsUsingIdsAndIncludingMissing() {
		this.flattenReactantAndProductMolecules();
		CMLMap atomMap = this.mapReactantAtomsToProductsUsingIdsAndIncludingMissing();
		reaction.appendChild(atomMap);
		CMLMap bondMap = this.mapReactantBondsToProductsUsingIdsAndIncludingMissing();
		reaction.appendChild(bondMap);
	}
	
	public SVGElement displayAnimatedReactionUsingMap() {
		displayList = new MoleculeDisplayList();
//		MoleculeDisplay moleculeDisplay = displayList.getMoleculeDisplay();
//		moleculeDisplay.getDefaultAtomDisplay().setDisplayCarbons(false);
//		moleculeDisplay.getDefaultAtomDisplay().setFontSize(11.0);
//		System.out.println(moleculeDisplay.getDefaultAtomDisplay().getFontSize());
		SVGSVG svg = new SVGSVG();
		SVGG gg = createG(svg);
		displayBonds1(gg);
		displayAtoms1(gg);
		displayElectrons(gg);
		return svg;
	}
	
	private SVGG createG(SVGSVG svg) {
		SVGG gg = new SVGG();
		Transform2 transform = new Transform2(new double[]{1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0});
		gg.setTransform(transform);
		svg.appendChild(gg);
		svg.setBegin(0.0);
		svg.setDur(5.0);
		return gg;
	}

//	private void displayAtoms(SVGG g) {
//		CMLMap atomMap = reaction.getMapElements().get(0);
//		List reactantAtoms = reaction.getReactantList().getAtoms();
//		List productAtoms = reaction.getProductList().getAtoms();
//		CMLAtomSet productAtomSet = new CMLAtomSet(productAtoms.toArray(new CMLAtom[0]));
//		for (CMLAtom reactantAtom : reactantAtoms) {
//			SVGElement svgReactant = ReactionAtomChange.createSVGElement(
//					ReactionChange.REACTANT_R, reactantAtom, displayList);
//			if (svgReactant != null) {
//				g.appendChild(svgReactant);
//				SVGElement svgProduct = null;
//				String productId = atomMap.getToRef(reactantAtom.getId());
//				CMLAtom productAtom = productAtomSet.getAtomById(productId);
//				ReactionAtomChange atomChange = ReactionAtomChange.getReactionAtomChange(reactantAtom, productAtom);
//				if (atomChange != null) {
//					svgProduct = atomChange.createAndAddProductDisplay(g, displayList);
//					atomChange.applyAnimation(svgReactant, svgProduct);
//					atomChange.processElectrons();
//				}
//				cleanPositiveAndNegative(svgReactant);
//				cleanPositiveAndNegative(svgProduct);
//			}
//		}
//	}
//
//	private void displayBonds(SVGG g) {
//		CMLMap bondMap = reaction.getMapElements().get(1);
//		List reactantBonds = reaction.getReactantList().getBonds();
//		List productBonds = reaction.getProductList().getBonds();
//		CMLBondSet productBondSet = new CMLBondSet(productBonds.toArray(new CMLBond[0]));
//		for (CMLBond reactantBond : reactantBonds) {
//			SVGElement svgReactant = ReactionBondChange.createSVGElement(ReactionChange.REACTANT_R, reactantBond, displayList);
//			if (svgReactant != null) {
//				g.appendChild(svgReactant);
//				SVGElement svgProduct = null;
//				String productId = bondMap.getToRef(reactantBond.getId());
//				System.err.println("Bond id "+productId);
//				CMLBond productBond = productBondSet.getBondById(productId);
//				ReactionBondChange bondChange = ReactionBondChange.getReactionBondChange(reactantBond, productBond);
//				if (bondChange != null) {
//					svgProduct = bondChange.createAndAddProductDisplay(g, displayList);
//					bondChange.applyAnimation(svgReactant, svgProduct);
//					bondChange.processElectrons();
//				}
//			}
//		}
//	}

	private void displayAtoms1(SVGG g) {
//		CMLMap atomMap = reaction.getMapElements().get(1);
		List reactantAtoms = reaction.getReactantList().getAtoms();
		List productAtoms = reaction.getProductList().getAtoms();
		CMLAtomSet reactantAtomSet = new CMLAtomSet(reactantAtoms.toArray(new CMLAtom[0]));
		CMLAtomSet productAtomSet = new CMLAtomSet(productAtoms.toArray(new CMLAtom[0]));
		atomChangeById = ReactionAtomChange.addAtomChangeById(productAtomSet, reactantAtomSet);
		for (String id : atomChangeById.keySet()) {
			ReactionAtomChange atomChange = atomChangeById.get(id);
			if (atomChange != null) {
				SVGElement svgReactant = atomChange.createAndAddReactantDisplay(g, displayList);
				SVGElement svgProduct = atomChange.createAndAddProductDisplay(g, displayList);
				ReactionAtomChange.cleanPositiveAndNegative(svgReactant);
				ReactionAtomChange.cleanPositiveAndNegative(svgProduct);
				atomChange.applyAnimation(svgReactant, svgProduct);
				atomChange.processElectrons();
			}
		}
	}

	private void displayBonds1(SVGG g) {
//		CMLMap bondMap = reaction.getMapElements().get(1);
		List reactantBonds = reaction.getReactantList().getBonds();
		List productBonds = reaction.getProductList().getBonds();
		CMLBondSet reactantBondSet = new CMLBondSet(reactantBonds.toArray(new CMLBond[0]));
		CMLBondSet productBondSet = new CMLBondSet(productBonds.toArray(new CMLBond[0]));
		bondChangeById = ReactionBondChange.addBondChangeById(productBondSet, reactantBondSet);
		for (String id : bondChangeById.keySet()) {
			ReactionBondChange bondChange = bondChangeById.get(id);
			if (bondChange != null) {
				SVGElement svgReactant = bondChange.createAndAddReactantDisplay(g, displayList);
				SVGElement svgProduct = bondChange.createAndAddProductDisplay(g, displayList);
				bondChange.applyAnimation(svgReactant, svgProduct);
				bondChange.processElectrons();
			}
		}
	}

	private void displayElectrons(SVGG g) {
		// currently no-op
	}


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy