content: /*
   * Copyright (c) 1997 - 2016
   * Actelion Pharmaceuticals Ltd.
   * Gewerbestrasse 16
   * CH-4123 Allschwil, Switzerland
   *
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * 1. Redistributions of source code must retain the above copyright notice, this
   *    list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   * 3. Neither the name of the the copyright holder nor the
   *    names of its contributors may be used to endorse or promote products
   *    derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * @author Thomas Sander
   */
  
  package com.actelion.research.chem.coords;
  
  import com.actelion.research.chem.*;
  
  import java.util.*;
  
  public class CoordinateInventor {
  	public static final int MODE_SKIP_DEFAULT_TEMPLATES = 1;
  	public static final int MODE_REMOVE_HYDROGEN = 2;
  	public static final int MODE_KEEP_MARKED_ATOM_COORDS = 4;
  	public static final int MODE_PREFER_MARKED_ATOM_COORDS = 8;
  	private static final int MODE_CONSIDER_MARKED_ATOMS = MODE_KEEP_MARKED_ATOM_COORDS | MODE_PREFER_MARKED_ATOM_COORDS;
  	public static final int MODE_DEFAULT = MODE_REMOVE_HYDROGEN;
  
  	private static final byte FLIP_AS_LAST_RESORT = 1;
  	private static final byte FLIP_POSSIBLE = 2;
  	private static final byte FLIP_PREFERRED = 3;
  	private static final int  PREFERRED_FLIPS = 32;
  	private static final int  POSSIBLE_FLIPS = 64;
  	private static final int  LAST_RESORT_FLIPS = 128;
  	private static final int  TOTAL_FLIPS = PREFERRED_FLIPS + POSSIBLE_FLIPS + LAST_RESORT_FLIPS;
  	private static final float JOIN_DISTANCE_METAL_BONDS = 1.2f;
  	private static final float JOIN_DISTANCE_CHARGED_ATOMS = 1.4f;
  	public static final float JOIN_DISTANCE_UNCHARGED_FRAGMENTS = 1.6f;
  
  	private static volatile List<InventorTemplate> sDefaultTemplateList;
  
  	private StereoMolecule mMol;
  	private long[]		mFFP;
  	private Random		mRandom;
  	private boolean[]	mAtomHandled;
  	private boolean[]	mBondHandled;
  	private boolean[]	mAtomIsPartOfCustomTemplate;
  	private boolean     mAbsoluteOrientationTemplateFound;  // we just use the first matching template, which is set to define absolute orientation
  	private int[]		mUnPairedCharge;
  	private final int   mMode;
  	private List<InventorFragment> mFragmentList;
  	private List<InventorTemplate> mCustomTemplateList;
  
  	private static synchronized void buildDefaultTemplateList() {
  		if (sDefaultTemplateList == null)
  			sDefaultTemplateList = new InventorDefaultTemplateList();
  	}
  
  
  	/**
  	 * Creates an CoordinateInventor, which removes unneeded hydrogen atoms
  	 * and creates new atom coordinates for all(!) atoms.
  	 * This constructor creates a CoordinateInventor that uses templates
  	 * of the InventorDefaultTemplateList to create 3D-projection derived coordinates for
  	 * polycyclic structures from these templates (adamantanes, cubane, etc.).
  	 */
  	public CoordinateInventor () {
  		this(MODE_DEFAULT);
  		}
  
  
  	/**
  	 * Creates an CoordinateInventor, which removes unneeded hydrogens, if mode flags include
  	 * MODE_REMOVE_HYDROGEN. If mode includes MODE_KEEP_MARKED_ATOM_COORDS, then marked atoms
  	 * keep their coordinates. If mode includes MODE_PREFER_MARKED_ATOM_COORDS, then coordinates
  	 * of marked atoms are changed only, if perfect coordinates are not possible without.
  	 * Unless mode includes MODE_SKIP_DEFAULT_TEMPLATES, the CoordinateInventor uses templates
  	 * of the InventorDefaultTemplateList to create 3D-projection derived coordinates for
  	 * polycyclic structures from these templates (adamantanes, cubane, etc.).
  	 * @param mode
  	 */
  	public CoordinateInventor (int mode) {
  		mMode = mode;
  		if ((mode & MODE_SKIP_DEFAULT_TEMPLATES) == 0 && sDefaultTemplateList == null)
  			buildDefaultTemplateList();
  		}
  
  
  	public void setRandomSeed(long seed) {
  		mRandom = new Random(seed);
  		}
  
  
  	/**
  	 * A custom template list contains substructures with predefined atom coordinates.
  	 * When such a list is provided, and if a molecules contains one of the list's substructures,
  	 * then the matching atoms will receive the relative atom coordinates of the provided template,
  	 * unless the substructure shares two or more atoms with another earlier found template or
  	 * if mode is MODE_????_MARKED_ATOM_COORDS and the substructure match contains two or more
  	 * non-marked (and therefore untouchable) atoms. If a template substructure contains an E- or Z-
  	 * double bound, then the query feature 'match EZ-parity' should be set.
  	 * @param templateList
  	 */
  	public void setCustomTemplateList(List<InventorTemplate> templateList) {
  		mCustomTemplateList = templateList;
  		for (InventorTemplate template:templateList)
  			template.normalizeCoordinates();
  		}
  
  	/**
  	 * Creates new atom 2D-coordinates for a molecule or a part of a molecule.
  	 * Typically, the molecule has defined TH- and EZ-parities (even if unknown or none), which were not
  	 * calculated, but taken from a SMILES or from an IDCode. In these cases setParitiesValid() should have
  	 * been called to indicate that a parity calculation is not needed and even would destroy given parities.
  	 * New coordinates will correctly reflect E/Z double bond parities, unless the double bond is in a small ring.
  	 * If atom parities are available, this call is typically followed by calling mol.setStereoBondsFromParity();
  	 * Unneeded explicit hydrogens are removed, if mode includes MODE_REMOVE_HYDROGEN.
  	 * The relative orientation of all marked atoms is retained, if mode includes MODE_KEEP_MARKED_ATOM_COORDS.
  	 * The relative orientation of all marked atoms is changed as last resort only, if mode includes MODE_PREFER_MARKED_ATOM_COORDS.
  	 * If setTemplateList() was called, then any substructures matching any of the templates will be shown
  	 * with the relative atom orientation provided with the template. If many molecule's coordinates are invented
  	 * with templates, then you should also provide the molecules' fragment fingerprint to speed up template search
  	 * using invent(mol, ffp).
  	 * @param mol the molecule that gets new 2D coordinates in place
  	 */
  	public void invent(StereoMolecule mol) {
  		invent(mol, null);
  		}
  
  
  	/**
  	 * Creates new atom 2D-coordinates for a molecule or a part of a molecule.
  	 * Typically, the molecule has defined TH- and EZ-parities (even if unknown or none), which were not
  	 * calculated, but taken from a SMILES or from an IDCode. In these cases setParitiesValid() should have
  	 * been called to indicate that a parity calculation is not needed and even would destroy given parities.
  	 * New coordinates will correctly reflect E/Z double bond parities, unless the double bond is in a small ring.
  	 * If atom parities are available, this call is typically followed by calling mol.setStereoBondsFromParity();
  	 * Unneeded explicit hydrogens are removed, if mode includes MODE_REMOVE_HYDROGEN.
  	 * The relative orientation of all marked atoms is retained, if mode includes MODE_KEEP_MARKED_ATOM_COORDS.
  	 * The relative orientation of all marked atoms is changed as last resort only, if mode includes MODE_PREFER_MARKED_ATOM_COORDS.
  	 * If setTemplateList() was called, then any substructures matching any of the templates will be shown
  	 * with the relative atom orientation provided with the template. If many molecule's coordinates are invented
  	 * with templates, then you should also provide the molecules' fragment fingerprint to speed up template search.
  	 * @param mol the molecule that gets new 2D coordinates in place
  	 * @parem ffp null or fragment fingerprint of the molecule, which is used (if available) for faster template location
  	 */
  	public void invent(StereoMolecule mol, long[] ffp) {
  		boolean paritiesPresent = (mol.getHelperArrayStatus() & Molecule.cHelperParities) != 0;
  		int parityState = mol.getHelperArrayStatus() & Molecule.cHelperBitsStereo;
  
  		if (mRandom == null)
  			mRandom = new Random();
  
  		if ((mMode & MODE_REMOVE_HYDROGEN) != 0)
  			mol.removeExplicitHydrogens(false);
  
  		mMol = mol;
  		mMol.ensureHelperArrays(Molecule.cHelperRings);
  
  		mFFP = ffp;
  
  		mFragmentList = new ArrayList<InventorFragment>() {
  			@Override
  			public boolean add(InventorFragment f) {
  				for (InventorFragment ff:this)
  					if (ff.equals(f))
  						return false;
  				return super.add(f);
  				}
  			};
  		mAtomHandled = new boolean[mMol.getAllAtoms()];
  		mBondHandled = new boolean[mMol.getAllBonds()];
  
  		mUnPairedCharge = new int[mMol.getAllAtoms()];
  		for (int atom=0; atom<mMol.getAllAtoms(); atom++)
  			mUnPairedCharge[atom] = mMol.getAtomCharge(atom);
  
  		if ((mMode & MODE_CONSIDER_MARKED_ATOMS) != 0) {
  			locateMarkedFragments();
  			}
  
  		if (mCustomTemplateList != null)
  			mAtomIsPartOfCustomTemplate = locateTemplateFragments(mCustomTemplateList, 512);
  
  		if ((mMode & MODE_SKIP_DEFAULT_TEMPLATES) == 0 && sDefaultTemplateList != null)
  			locateTemplateFragments(sDefaultTemplateList, 256);
  
  		locateInitialFragments();
  		joinOverlappingFragments();
  
  		locateChainFragments();
  		joinOverlappingFragments();
  
  		for (InventorFragment f:mFragmentList)
  			f.locateBonds();	// mGlobalBond & mGlobalToLocalAtom needed in correctChainEZParities() & optimizeFragments()
  
  		correctChainEZParities();
  		optimizeFragments();
  
  		locateSingleAtoms();
  
  		joinMetalBondedFragments();
  		joinChargedFragments();
  
  		// using one-by-one rotate and approximate strategy
  		joinRemainingFragments();
  
  		for (InventorFragment f : mFragmentList) {
  			for (int j = 0; j<f.size(); j++) {
  				mMol.setAtomX(f.mGlobalAtom[j], f.mAtomX[j]);
  				mMol.setAtomY(f.mGlobalAtom[j], f.mAtomY[j]);
  				mMol.setAtomZ(f.mGlobalAtom[j], 0.0);
  			}
  		}
  
  		if (paritiesPresent) {
  			mMol.setParitiesValid(parityState);
  			mMol.setStereoBondsFromParity();
  			}
  
  		if (mAbsoluteOrientationTemplateFound)
  			mMol.removeAtomMarkers();
  		}
  
  
  	public boolean[] getCustomTemplateAtomMask() {
  		return mAtomIsPartOfCustomTemplate;
  		}
  
  
  	private boolean[] locateTemplateFragments(List<InventorTemplate> templateList, int priority) {
  		boolean useFFP = (mFFP != null && !templateList.isEmpty() && templateList.get(0).getFFP() != null);
  
  		SSSearcher searcher = null;
  		SSSearcherWithIndex searcherWithIndex = null;
  		if (useFFP) {
  			searcherWithIndex = new SSSearcherWithIndex();
  			searcherWithIndex.setMolecule(mMol, mFFP);
  			}
  		else {
  			searcher = new SSSearcher();
  			searcher.setMolecule(mMol);
  			}
  
  		boolean[] atomIsPartOfTemplate = new boolean[mMol.getAtoms()];
  
  		for (InventorTemplate template: templateList) {
  			ArrayList<int[]> matchList = null;
  			StereoMolecule templateMol = template.getFragment();
  			if (useFFP) {
  				searcherWithIndex.setFragment(templateMol, template.getFFP());
  				if (searcherWithIndex.findFragmentInMolecule(SSSearcher.cCountModeOverlapping, SSSearcher.cDefaultMatchMode) != 0)
  					matchList = searcherWithIndex.getGraphMatcher().getMatchList();
  				}
  			else {
  				searcher.setFragment(templateMol);
  				if (searcher.findFragmentInMolecule(SSSearcher.cCountModeOverlapping, SSSearcher.cDefaultMatchMode) != 0)
  					matchList = searcher.getMatchList();
  				}
  
  			if (matchList != null) {
  				for (int[] match:matchList) {
  					int templateAtomCount = 0;
  					for (int atom:match)
  						if (atomIsPartOfTemplate[atom])
  							templateAtomCount++;
  					if (templateAtomCount <= 1) {
  						// we just use the first matching template that is supposed to keep its absolute orientation
  						boolean definesAbsoluteOrientation = template.keepAbsoluteOrientation();
  						if (mAbsoluteOrientationTemplateFound)
  							definesAbsoluteOrientation = false;
  						else
  							mAbsoluteOrientationTemplateFound = true;
  
  						InventorFragment fragment = new InventorFragment(mMol, match.length, definesAbsoluteOrientation);
  
  						for (int i=0; i<match.length; i++) {
  							int atom = match[i];
  
  							// translate templates, which need to retain absolute coordinates, into atom markers
  							if (definesAbsoluteOrientation)
  								mMol.setAtomMarker(atom, true);
  
  							fragment.mPriority[i] = priority;
  							fragment.mGlobalAtom[i] = atom;
  							fragment.mAtomX[i] = template.getNormalizedAtomX(i);
  							fragment.mAtomY[i] = template.getNormalizedAtomY(i);
  							atomIsPartOfTemplate[atom] = true;
  							mAtomHandled[atom] = true;
  							}
  
  						for (int b=0; b<templateMol.getBonds(); b++)
  							mBondHandled[mMol.getBond(match[templateMol.getBondAtom(0, b)], match[templateMol.getBondAtom(1, b)])] = true;
  
  						mFragmentList.add(fragment);
  						}
  					}
  				}
  			}
  		return atomIsPartOfTemplate;
  		}
  
  
  	private void locateMarkedFragments() {
  		int atomCount = 0;
  		for (int atom=0; atom<mMol.getAllAtoms(); atom++)
  			if (mMol.isMarkedAtom(atom))
  				atomCount++;
  
  		// there are no relative coordinates with less than 2 points
  		if (atomCount < 2)
  			return;
  
  		int bondCount = 0;
  		double avbl = 0;
  		for (int bond=0; bond<mMol.getAllBonds(); bond++) {
  			int atom1 = mMol.getBondAtom(0, bond);
  			int atom2 = mMol.getBondAtom(1, bond);
  			if (mMol.isMarkedAtom(atom1)
  			 && mMol.isMarkedAtom(atom2)) {
  				mBondHandled[bond] = true;
  				mAtomHandled[atom1] = true;
  				mAtomHandled[atom2] = true;
  				avbl += mMol.getBondLength(bond);
  				bondCount++;
  				}
  			}
  
  		if (bondCount != 0 && avbl != 0.0)
  			avbl /= bondCount;
  		else {	// if we don't have an avbl from the marked bonds, we take it from all bonds
  			avbl = mMol.getAverageBondLength();
  			}
  
  		for (int atom=0; atom<mMol.getAllAtoms(); atom++)
  			if (mMol.isMarkedAtom(atom) && !mAtomHandled[atom])
  				atomCount--;
  
  		if (atomCount < 2)
  			return;
  
  		int[] fragmentNo = new int[mMol.getAllAtoms()];
  		int coreFragmentCount = mMol.getFragmentNumbers(fragmentNo, true, true);
  
  		int[] fragmentAtomCount = new int[coreFragmentCount];
  		for (int atom=0; atom<mMol.getAllAtoms(); atom++)
  			if (fragmentNo[atom] != -1)
  				fragmentAtomCount[fragmentNo[atom]]++;
  
  		InventorFragment[] fragment = new InventorFragment[coreFragmentCount];
  		for (int f=0; f<coreFragmentCount; f++)
  			fragment[f] = new InventorFragment(mMol, fragmentAtomCount[f], true);
  
  		int[] atomIndex = new int[coreFragmentCount];
  		for (int atom=0; atom<mMol.getAllAtoms(); atom++) {
  			int f = fragmentNo[atom];
  			if (f != -1) {
  				fragment[f].mPriority[atomIndex[f]] = 1024;
  				fragment[f].mGlobalAtom[atomIndex[f]] = atom;
  				fragment[f].mAtomX[atomIndex[f]] = mMol.getAtomX(atom) / avbl;
  				fragment[f].mAtomY[atomIndex[f]] = mMol.getAtomY(atom) / avbl;
  				atomIndex[f]++;
  				}
  			}
  
  			// Find the largest core fragment and retain its orientation
  			// by adding it first to the fragment list
  		int maxFragment = -1;
  		int maxFragmentAtoms = 0;
  		for (int f=0; f<coreFragmentCount; f++) {
  			if (maxFragmentAtoms < fragmentAtomCount[f]) {
  				maxFragmentAtoms = fragmentAtomCount[f];
  				maxFragment = f;
  				}
  			}
  
  		mFragmentList.add(fragment[maxFragment]);
  		for (int f=0; f<coreFragmentCount; f++)
  			if (f != maxFragment)
  				mFragmentList.add(fragment[f]);
  		}
  
  
  	private void locateInitialFragments() {
  		// take every atom with more than 4 neighbours including first neighbour shell
  		for (int atom=0; atom<mMol.getAtoms(); atom++) {
  			if (mMol.getAllConnAtoms(atom) > 4) {
  				InventorFragment f = new InventorFragment(mMol, 1+mMol.getAllConnAtoms(atom), false);
  
  				f.mAtomX[mMol.getAllConnAtoms(atom)] = 0.0;
  				f.mAtomY[mMol.getAllConnAtoms(atom)] = 0.0;
  				f.mPriority[mMol.getAllConnAtoms(atom)] = 32;
  				f.mGlobalAtom[mMol.getAllConnAtoms(atom)] = atom;
  				mAtomHandled[atom] = true;
  
  				for (int i=0; i<mMol.getAllConnAtoms(atom); i++) {
  					int connAtom = mMol.getConnAtom(atom, i);
  					f.mAtomX[i] = Math.sin(Math.PI/3*i-Math.PI/3*2);
  					f.mAtomY[i] = Math.cos(Math.PI/3*i-Math.PI/3*2);
  					f.mPriority[i] = 32;
  					f.mGlobalAtom[i] = connAtom;
  					mAtomHandled[connAtom] = true;
  					mBondHandled[mMol.getConnBond(atom, i)] = true;
  					}
  
  				mFragmentList.add(f);
  				}
  			}
  
  
  			// take every small ring whose atoms are not a superset of another small ring
  		RingCollection ringSet = mMol.getRingSet();
  		for (int ringNo=0; ringNo<ringSet.getSize(); ringNo++) {
  			int ringSize = ringSet.getRingSize(ringNo);
  			int[] ringAtom = ringSet.getRingAtoms(ringNo);
  
  				// skip rings that are entirely in the core fragment, if retainCore is true
  			boolean skipRing = false;
  			if ((mMode & MODE_CONSIDER_MARKED_ATOMS) != 0) {
  				skipRing = true;
  				for (int i=0; i<ringSize; i++) {
  					if (!mMol.isMarkedAtom(ringAtom[i])) {
  						skipRing = false;
  						break;
  						}
  					}
  				}
  
  			if (!skipRing) {
  				boolean isElementaryRing = false;
  				for (int i=0; i<ringSize; i++) {
  					if (mMol.getAtomRingSize(ringAtom[i]) == ringSize) {
  						isElementaryRing = true;
  						break;
  						}
  					}
  				if (isElementaryRing) {
  					int[] ringBond = ringSet.getRingBonds(ringNo);
  
  					addRingFragment(ringAtom, ringBond);
  
  					for (int i=0; i<ringSize; i++) {
  						mAtomHandled[ringAtom[i]] = true;
  						mBondHandled[ringBond[i]] = true;
  						}
  					}
  				}
  			}
  
  			// take every large ring that has ring bonds that are not member of a fragment added already
  		for (int bond=0; bond<mMol.getBonds(); bond++) {
  			if (mMol.isRingBond(bond) && !mBondHandled[bond]) {
  				InventorChain theRing = getSmallestRingFromBond(bond);
  				int[] ringAtom = theRing.getRingAtoms();
  				int[] ringBond = theRing.getRingBonds();
  				addRingFragment(ringAtom, ringBond);
  
  				for (int i=0; i<theRing.getChainLength(); i++) {
  					mAtomHandled[ringAtom[i]] = true;
  					mBondHandled[ringBond[i]] = true;
  					}
  				}
  			}
  
  			// take every triple bond including first level attached atoms
  		for (int bond=0; bond<mMol.getAllBonds(); bond++) {
  			if (!mBondHandled[bond] && mMol.getBondOrder(bond) == 3) {
  				int atom1 = mMol.getBondAtom(0, bond);
  				int atom2 = mMol.getBondAtom(1, bond);
  				int members = mMol.getAllConnAtoms(atom1) + mMol.getAllConnAtoms(atom2);
  				if (members > 2) {
  					InventorFragment f = new InventorFragment(mMol, members, false);
  					int count = 0;
  					for (int i=0; i<mMol.getAllConnAtoms(atom1); i++) {
  						int connAtom = mMol.getConnAtom(atom1, i);
  						if (connAtom != atom2) {
  							f.mGlobalAtom[count++] = connAtom;
  							mAtomHandled[connAtom] = true;
  							mBondHandled[mMol.getConnBond(atom1, i)] = true;
  							}
  						}
  					f.mGlobalAtom[count++] = atom1;
  					f.mGlobalAtom[count++] = atom2;
  					for (int i=0; i<mMol.getAllConnAtoms(atom2); i++) {
  						int connAtom = mMol.getConnAtom(atom2, i);
  						if (connAtom != atom1) {
  							f.mGlobalAtom[count++] = connAtom;
  							mAtomHandled[connAtom] = true;
  							mBondHandled[mMol.getConnBond(atom2, i)] = true;
  							}
  						}
  					for (int i=0; i<members; i++) {
  						f.mAtomX[i] = i;
  						f.mAtomY[i] = 0.0;
  						f.mPriority[i] = 1;
  						}
  					mAtomHandled[atom1] = true;
  					mAtomHandled[atom2] = true;
  					mBondHandled[bond] = true;
  					mFragmentList.add(f);
  					}
  				}
  			}
  
  			// take cumulated double bonds including first level single bonded atoms
  		for (int bond=0; bond<mMol.getAllBonds(); bond++) {
  			if (!mBondHandled[bond] && mMol.getBondOrder(bond) == 2) {
  				int[] alleneAtom = new int[mMol.getAllAtoms()];
  				for (int i=0; i<2; i++) {
  					alleneAtom[0] = mMol.getBondAtom(i, bond);
  					alleneAtom[1] = mMol.getBondAtom(1-i, bond);
  					if (mMol.getAtomPi(alleneAtom[0]) == 1
  					 && mMol.getAtomPi(alleneAtom[1]) == 2
  					 && mMol.getAllConnAtoms(alleneAtom[1]) == 2) { // found start of cumulated double bonds
  						mAtomHandled[alleneAtom[0]] = true;
  						mAtomHandled[alleneAtom[1]] = true;
  						mBondHandled[bond] = true;
  						int last = 1;
  						do {
  							int nextIndex = (mMol.getConnAtom(alleneAtom[last], 0)
  											 == alleneAtom[last-1]) ? 1 : 0;
  							alleneAtom[last+1] = mMol.getConnAtom(alleneAtom[last], nextIndex);
  
  								// stop at centers like C=Cr(Rn)=N
  							if (mMol.getAtomPi(alleneAtom[last+1]) == 2
  							 && mMol.getAllConnAtoms(alleneAtom[last+1]) > 2)
  								break;
  
  							mAtomHandled[alleneAtom[last+1]] = true;
  							mBondHandled[mMol.getConnBond(alleneAtom[last], nextIndex)] = true;
  							last++;
  							} while (mMol.getAtomPi(alleneAtom[last]) == 2
  								  && mMol.getAllConnAtoms(alleneAtom[last]) == 2);
  
  						int members = mMol.getAllConnAtoms(alleneAtom[0])
  									+ mMol.getAllConnAtoms(alleneAtom[last])
  									+ last - 1;
  						InventorFragment f = new InventorFragment(mMol, members, false);
  						for (int j=0; j<=last; j++) {
  							f.mAtomX[j] = j;
  							f.mAtomY[j] = 0.0;
  							f.mPriority[j] = 64;
  							f.mGlobalAtom[j] = alleneAtom[j];
  							}
  
  						int current = last+1;
  						boolean found = false;
  						for (int j=0; j<mMol.getAllConnAtoms(alleneAtom[0]); j++) {
  							int connAtom = mMol.getConnAtom(alleneAtom[0], j);
  							if (connAtom != alleneAtom[1]) {
  								f.mAtomX[current] = -0.5;
  								f.mAtomY[current] = (found) ? Math.sin(Math.PI/3) : -Math.sin(Math.PI/3);
  								f.mPriority[current] = 64;
  								f.mGlobalAtom[current] = connAtom;
  								current++;
  								found = true;
  								}
  							}
  
  						found = false;
  						for (int j=0; j<mMol.getAllConnAtoms(alleneAtom[last]); j++) {
  							int connAtom = mMol.getConnAtom(alleneAtom[last], j);
  							if (connAtom != alleneAtom[last-1]) {
  								f.mAtomX[current] = (double)last + 0.5;
  								f.mAtomY[current] = (found) ? -Math.sin(Math.PI/3) : Math.sin(Math.PI/3);
  								f.mPriority[current] = 64;
  								f.mGlobalAtom[current] = connAtom;
  								current++;
  								found = true;
  								}
  							}
  
  						mFragmentList.add(f);
  						}
  					}
  				}
  			}
  
  			// predefine quarternary centers with exactly 2 not further subtituted substituents
  		for (int atom=0; atom<mMol.getAllAtoms(); atom++) {
  			if (mMol.getAllConnAtoms(atom) == 4) {
  				int[] primaryConnAtom = new int[4];
  				int[] primaryConnBond = new int[4];
  				int primaryConns = 0;
  				for (int i=0; i<4; i++) {
  					primaryConnAtom[primaryConns] = mMol.getConnAtom(atom, i);
  					primaryConnBond[primaryConns] = mMol.getConnBond(atom, i);
  					if (mMol.getAllConnAtoms(primaryConnAtom[primaryConns]) == 1
  					 && !mBondHandled[primaryConnBond[primaryConns]])
  						primaryConns++;
  					}
  
  				if (primaryConns == 2) {
  //					mAtomHandled[atom] = true;	don't break zig-zag of chains that are handled later
  					InventorFragment f = new InventorFragment(mMol, 3, false);
  					for (int i=0; i<2; i++) {
  						mAtomHandled[primaryConnAtom[i]] = true;
  						mBondHandled[primaryConnBond[i]] = true;
  						f.mGlobalAtom[i] = primaryConnAtom[i];
  						f.mPriority[i] = 32;
  						}
  
  					f.mAtomX[0] = -0.5;
  					f.mAtomY[0] = 0.866;
  					f.mAtomX[1] = 0.5;
  					f.mAtomY[1] = 0.866;
  
  					f.mAtomX[2] = 0.0;
  					f.mAtomY[2] = 0.0;
  					f.mPriority[2] = 32;
  					f.mGlobalAtom[2] = atom;
  
  					mFragmentList.add(f);
  					}
  				if (primaryConns == 3) {
  					// if there is a single bond make sure that primaryConnBond[2] is one
  					for (int i=0; i<2; i++) {
  						if (mMol.getBondOrder(primaryConnBond[i]) == 1) {
  							int temp = primaryConnAtom[i];
  							primaryConnAtom[i] = primaryConnAtom[2];
  							primaryConnAtom[2] = temp;
  							temp = primaryConnBond[i];
  							primaryConnBond[i] = primaryConnBond[2];
  							primaryConnBond[2] = temp;
  							}
  						}
  
  //					mAtomHandled[atom] = true;	don't break zig-zag of chains that are handled later
  					InventorFragment f = new InventorFragment(mMol, 4, false);
  					for (int i=0; i<3; i++) {
  						mAtomHandled[primaryConnAtom[i]] = true;
  						mBondHandled[primaryConnBond[i]] = true;
  						f.mGlobalAtom[i] = primaryConnAtom[i];
  						f.mPriority[i] = 32;
  						}
  
  					f.mAtomX[0] = -1.0;
  					f.mAtomY[0] = 0.0;
  					f.mAtomX[1] = 1.0;
  					f.mAtomY[1] = 0.0;
  					f.mAtomX[2] = 0.0;
  					f.mAtomY[2] = 1.0;
  
  					f.mAtomX[3] = 0.0;
  					f.mAtomY[3] = 0.0;
  					f.mPriority[3] = 32;
  					f.mGlobalAtom[3] = atom;
  
  					mFragmentList.add(f);
  					}
  				}
  			}
  
  /*	The current implementation does not (!!!) retain E/Z geometry of double bonds in a ring...
  	Use the following to create E/Z fragments of ring double bonds with E/Z geometry reflecting
  	coordinates. Retaining Reliably E/Z geometries, however, will need in addition:
  	- consider these fragments coordinates when joining even if all fragments atom are already
  	  part of the other fragment
  	- a more capable joining algorithm that prevents E/Z inversions of double bonds by relocating
  	  atoms that are part of the joint fragment
  
  			// take stero defined ring-double-bonds including first level attached atoms
  		for (int bond=0; bond<mMol.getAllBonds(); bond++) {
  			int bondParity = mMol.getBondParity(bond);
  			if (mMol.isRingBond(bond)
  			 && (bondParity == Molecule.cBondParityE
  			  || bondParity == Molecule.cBondParityZ)) {
  			 	int[] bondAtom = new int[2];
  				bondAtom[0] = mMol.getBondAtom(0, bond);
  				bondAtom[1] = mMol.getBondAtom(1, bond);
  				int members = mMol.getAllConnAtoms()[bondAtom[0]] + mMol.getAllConnAtoms()[bondAtom[1]];
  
  				InventorFragment f = new InventorFragment(mMol, members);
  				int count = 0;
  				boolean[] secondAtomCounts = new boolean[2];
  				for (int i=0; i<2; i++) {
  					mAtomHandled[bondAtom[i]] = true;
  					f.mAtomX[count] = (double)i-0.5;
  					f.mAtomY[count] = 0.0;
  					f.mPriority[count] = 128;
  					f.mAtom[count++] = bondAtom[i];
  					int neighbours = 0;
  					for (int j=0; j<mMol.getAllConnAtoms()[bondAtom[i]]; j++) {
  						int connAtom = mMol.getConnAtom(bondAtom[i], j);
  						if (connAtom != bondAtom[1-i]) {
  							if (neighbours == 1 && f.mAtom[count-1] > connAtom)
  								secondAtomCounts[i] = true;
  							f.mAtomX[count] = (i==0) ? -1.0 : 1.0;
  							f.mAtomY[count] = (neighbours==0) ? -Math.sin(Math.PI/3) : Math.sin(Math.PI/3);
  							f.mAtom[count++] = connAtom;
  							mAtomHandled[connAtom] = true;
  							mBondHandled[mMol.getConnBond(bondAtom[i], j)] = true;
  							neighbours++;
  							}
  						}
  					}
  
  				if ((bondParity == Molecule.cBondParityE) ^ (secondAtomCounts[0] ^ secondAtomCounts[1]))
  					for (int i=1; i<mMol.getAllConnAtoms()[mMol.getBondAtom(0, bond)]; i++)
  						f.mAtomY[i] *= -1.0;
  
  				mBondHandled[bond] = true;
  				mFragmentList.addElement(f);
  				}
  			}	*/
  		}
  
  
  	private void locateChainFragments() {
  		while (true) {
  			InventorChain longestChain = null;
  
  			for (int atom=0; atom<mMol.getAllAtoms(); atom++) {
  				int unhandledBonds = 0;
  				for (int i=0; i<mMol.getAllConnAtoms(atom); i++)
  					if (!mBondHandled[mMol.getConnBond(atom, i)])
  						unhandledBonds++;
  
  				if (unhandledBonds == 1) {
  					InventorChain theChain = getLongestUnhandledChain(atom);
  					if (longestChain == null
  					 || theChain.getChainLength() > longestChain.getChainLength())
  						longestChain = theChain;
  					}
  				}
  
  			if (longestChain == null)
  				break;
  
  			InventorFragment f = new InventorFragment(mMol, longestChain.getChainLength(), false);
  			for (int i=0; i<longestChain.getChainLength(); i++) {
  				mAtomHandled[longestChain.mAtom[i]] = true;
  				if (i < longestChain.getChainLength() - 1)
  					mBondHandled[longestChain.mBond[i]] = true;
  				f.mGlobalAtom[i] = longestChain.mAtom[i];
  				f.mAtomX[i] = Math.cos(Math.PI / 6) * i;
  				f.mAtomY[i] = ((i & 1) == 1) ? 0.0 : 0.5;
  				f.mPriority[i] = 128 + longestChain.getChainLength();
  				}
  			mFragmentList.add(f);
  			}
  		}
  
  
  	private void locateSingleAtoms() {
  		for (int atom=0; atom<mMol.getAllAtoms(); atom++) {
  			if (!mAtomHandled[atom] && mMol.getAllConnAtoms(atom) == 0) {
  				InventorFragment f = new InventorFragment(mMol, 1, false);
  				mAtomHandled[atom] = true;
  				f.mGlobalAtom[0] = atom;
  				f.mAtomX[0] = 0.0;
  				f.mAtomY[0] = 0.0;
  				f.mPriority[0] = 0;
  				mFragmentList.add(f);
  				}
  			}
  		}
  
  
  	private void addRingFragment(int[] ringAtom, int[] ringBond) {
  		int ringSize = ringAtom.length;
  		InventorFragment f = new InventorFragment(mMol, ringSize, false);
  		f.mAtomX[0] = 0.0;
  		f.mAtomY[0] = 0.0;
  		for (int i=0; i<ringSize; i++) {
  			f.mPriority[i] = 128 - ringSize;
  			f.mGlobalAtom[i] = ringAtom[i];
  			}
  
  		if (ringSize < 8)
  			createRegularRingFragment(f);
  		else  // create a large ring considering E-double bonds and other constraints
  			createLargeRingFragment(f, ringAtom, ringBond);
  
  		mFragmentList.add(f);
  		}
  
  
  	private void createRegularRingFragment(InventorFragment f) {
  		double angleChange = Math.PI - (Math.PI * (f.size()-2))/f.size();
  		for (int i=1; i<f.size(); i++) {
  			f.mAtomX[i] = f.mAtomX[i-1] + Math.sin(angleChange*(i-1));
  			f.mAtomY[i] = f.mAtomY[i-1] + Math.cos(angleChange*(i-1));
  			}
  		}
  
  
  	private void createRegularRingFragment(InventorFragment f, int bondEConstraint, int bondZConstraint) {
  		if (bondEConstraint == 0    // no E-bonds
  		 || (bondEConstraint & bondZConstraint) != 0) { // contradictory constraints
  			createRegularRingFragment(f);
  			return;
  			}
  
  		int startIndex = -1;
  		int startPriority = 0;
  		int bitMinus2 = 1 << (f.size()-2);
  		int bitMinus1 = 1 << (f.size()-1);
  		int currentBit = 1;
  		int bitPlus1 = 2;
  		for (int i=0; i<f.size(); i++) {
  			// if none of previous bond nor current bond are Z and at least one of them is E and the bond  before is not E
  			if ((bondZConstraint & (bitMinus1 | currentBit)) == 0
  			 && (bondEConstraint & (bitMinus1 | currentBit)) != 0
  			 && (bondEConstraint & bitMinus2) == 0) {
  				int priority = 0;
  				if ((bondZConstraint & bitMinus2) != 0)
  					priority += 4;
  				if ((bondEConstraint & bitMinus1) != 0)
  					priority += 2;
  				if ((bondEConstraint & currentBit) != 0)
  					priority += 1;
  				if (startPriority < priority) {
  					startPriority = priority;
  					startIndex = i;
  					}
  				}
  
  			bitMinus2 = bitMinus1;
  			bitMinus1 = currentBit;
  			currentBit = bitPlus1;
  			bitPlus1 = 1 << (i+2 < f.size() ? i+2 : i+2-f.size());
  			}
  
  		if (startIndex == -1) {
  			createRegularRingFragment(f);
  			return;
  			}
  
  		int moveToCenter = 0;   // bit mask marking atoms to be moved towards ring center to satisfy E/Z constraints
  		moveToCenter |= (1 << startIndex);
  		int offset=2;
  		while (offset<f.size()-1) {
  			int index = (startIndex + offset < f.size()) ? startIndex+offset : startIndex+offset-f.size();
  			bitMinus1 = 1 << (index==0 ? f.size()-1 : index-1);
  
  			if ((bondZConstraint & bitMinus1) != 0) {
  				offset++;
  				continue;
  				}
  
  			currentBit = 1 << index;
  			if ((bondEConstraint & bitMinus1) != 0) {
  				if ((bondZConstraint & currentBit) != 0) {
  					createRegularRingFragment(f);
  					return;
  					}
  				moveToCenter |= currentBit;
  				offset += 2;
  				continue;
  				}
  
  			bitPlus1 = 1 << (index+1 < f.size() ? index+1 : index+1-f.size());
  			if ((bondEConstraint & currentBit) != 0
  			 && (bondZConstraint & bitPlus1) != 0) {
  				moveToCenter |= currentBit;
  				offset += 3;
  				continue;
  				}
  			offset++;
  			}
  
  		if (moveToCenter == 0) {
  			createRegularRingFragment(f);
  			return;
  			}
  
  		double angleChange = Math.PI - (Math.PI * (f.size()-2))/f.size();
  		for (int i=1; i<f.size(); i++) {
  			f.mAtomX[i] = f.mAtomX[i-1] + Math.sin(angleChange*(i-1));
  			f.mAtomY[i] = f.mAtomY[i-1] + Math.cos(angleChange*(i-1));
  			}
  
  		currentBit = 1;
  		double shift = 2*Math.sin(angleChange/2);
  		for (int i=0; i<f.size(); i++) {
  			if ((moveToCenter & currentBit) != 0) {
  				f.mAtomX[i] += shift * Math.cos(angleChange * ((double) i - 0.5));
  				f.mAtomY[i] -= shift * Math.sin(angleChange * ((double) i - 0.5));
  				}
  			currentBit <<= 1;
  			}
  		}
  
  
  	private void createLargeRingFragment(InventorFragment f, int[] ringAtom, int[] ringBond) {
  		final int FIRST_RING_SIZE = 9;
  		final int LAST_RING_SIZE = 25;
  		final double[][] cAngleCorrection = { {20}, null, null, {0,10}, null, null, {-4,12},
  				{0,0,-7.5}, null, null, null, null, {60.0/7, -60.0/7}, null, null, null, {-2.4} };
  		final int[][] cBondZList = { // sequence of E/Z parities in rings (E=0, Z=1)
  				{   // 9-membered ring
  					0x00000092,  // 010010010 sym
  				},
  				{   // 10-membered ring
  					0x00000273,  // 1001110011 sym
  				},
  				null,
  				{   // 12-membered rings
  					0x00000999,  // 100110011001 sym
  					0x00000492   // 010010010010 sym
  				},
  				null,
  				{   // 14-membered rings
  					0x00000993,  // 00100110010011 sym
  					0x000021C3,  // 10000111000011 sym
  					0x000009D7   // 00100111010111 sym
  				},
  				{   // 15-membered rings
  					0x00002492,  // 010010010010010 sym
  					0x000039CE,  // 011100111001110 sym
  				},
  				{   // 16-membered rings
  					0x00008649,  // 1000011001001001 sym
  					0x80008759,  // 1000011101011001 asy
  					0x00006666,  // 0110011001100110 sym
  				},
  				null,
  				{   // 18-membered rings
  					0x00009249,  // 001001001001001001 sym
  					0x00021861,  // 100001100001100001 sym
  					0x000175D7,  // 010111010111010111 sym
  					0x00008643,  // 001000011001000011 sym
  					0x000093B7,  // 001001001110110111 sym
  					0x0000D66B,  // 001101011001101011 sym
  					0x00020703,  // 100000011100000011 sym
  					0x8002A753,  // 101010011101010011 asy
  					0x0000D649,  // 001101011001001001 sym
  					0x0000D759,  // 001101011101011001 sym
  					0x80008753,  // 001000011101010011 asy
  					0x80008717   // 001000011100010111 asy
  				},
  				null,
  				{   // 20-membered rings
  					0x00081909,  // 10000001100100001001 sym
  					0x00081D6B,  // 10000001110101101011 sym
  					0x000DB861,  // 11011011100001100001 sym
  					0x00021849,  // 00100001100001001001 sym
  					0x000A9959,  // 10101001100101011001 sym
  					0x80081D49,  // 10000001110101001001 asy
  					0x800819A3,  // 10000001100110100011 asy
  					0x80084ED9,  // 10000100111011011001 asy
  					0x80087475,  // 10000111010001110101 asy
  					0x80087464,  // 10000111010001100100 asy
  					0x800D19A9,  // 11010001100110101001 asy
  					0x80086BA9,  // 10000110101110101001 asy
  					0x800849A9,  // 10000100100110101001 asy
  					0x80086B21   // 10000110101100100001 asy
  				},
  				{   // 21-membered rings
  					0x000F5EBD,  // 011110101111010111101 sym
  					0x00095263   // 010010101001010100101 sym
  				},
  				{   // 22-membered rings
  					0x00084909,  // 0010000100100100001001 sym
  					0x00021843,  // 0000100001100001000011 sym
  					0x00206121,  // 1000000110000100100001 sym
  					0x00081903,  // 0010000001100100000011 sym
  					0x0021AC35,  // 1000011010110000110101 sym
  					0x802A4D49,  // 1010100100110101001001 asy
  					0x00035849,  // 0000110101100001001001 sym
  					0x002B5909,  // 1010110101100100001001 sym
  					0x00021953,  // 0000100001100101010011 sym
  					0x80095909,  // 0010010101100100001001 asy
  					0x80035959,  // 0000110101100101011001 asy
  					0x00095D49,  // 0010010101110101001001 sym
  					0x80206561,  // 1000000110010101100001 asy
  					0x800D1909,  // 0011010001100100001001 asy
  					0x000A9953,  // 0010101001100101010011 sym
  					0x00257535,  // 1001010111010100110101 sym
  					0x80207461,  // 1000000111010001100001 asy
  					0x80021D13,  // 0000100001110100010011 asy
  					0x800876C9,  // 0010000111011011001001 asy
  					0x80086BA3,  // 0010000110101110100011 asy
  					0x802B5D49,  // 1010110101110101001001 asy
  					0x80081D43,  // 0010000001110101000011 asy
  					0x800D192B,  // 0011010001100100101011 asy
  					0x800D1D49,  // 0011010001110101001001 asy
  					0x002B5D6B,  // 1010110101110101101011 sym
  					0x001066D9,  // 1000000110011011011001 sym
  					0x800D19A3,  // 0011010001100110100011 asy
  					0x002AB953,  // 1010101011100101010011 sym
  					0x802A1D43,  // 1010100001110101000011 asy
  					0x00021D57,  // 0000100001110101010111 sym
  					0x000D1C59,  // 0011010001110001011001 sym
  					0x8021DB35,  // 1000011101101100110101 asy
  					0x80229903,  // 1000101001100100000011 asy
  					0x800D1D6B,  // 0011010001110101101011 asy
  					0x802A76C9,  // 1010100111011011001001 asy
  					0x800876EB,  // 0010000111011011101011 asy
  					0x80369909,  // 1101101001100100001001 asy
  					0x80347535,  // 1101000111010100110101 asy
  					0x800A9917,  // 0010101001100100010111 asy
  					0x0022EBA3,  // 1000101110101110100011 sym
  					0x00084E97,  // 0010000100111010010111 sym
  					0x00201C03,  // 1000000001110000000011 sym
  					0x8008B917,  // 0010001011100100010111 asy
  					0x802DD753,  // 1011011101011101010011 asy
  					0x00377249,  // 1101110111001001001001 sym
  					0x80095CB7,  // 0010010101110010110111 asy
  					0x80081C17   // 0010000001110000010111 asy
  				},
  				null,
  				{   // 24-membered rings
  					0x00818181,  // 100000011000000110000001 sym
  					0x002126D9,  // 001000010010011011011001 sym
  					0x00204C03,  // 001000000100110000000011 sym
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  					0x00000000,  //
  
  					0x0086BB75   // 100001101011101101110101 sym
  				},
  				{   // 25-membered rings
  					0x00D6B5AD   // 0110101101011010110101101 sym
  				},
  			};
  
  //		if (f.size() >= FIRST_RING_SIZE)
  //			printRingStats(f, ringAtom, ringBond);
  
  		int maxBit = (1 << f.size());
  		int bondEConstraint = 0;
  		int bondZConstraint = 0;
  		if (f.size() > 7) {
  			for (int i=0; i<f.size(); i++) {
  				int bondParity = getLargeRingBondParity(ringAtom, ringBond, i);
  				if (bondParity == Molecule.cBondParityEor1)
  					bondEConstraint += maxBit;
  				else if (bondParity == Molecule.cBondParityZor2)
  					bondZConstraint += maxBit;
  				bondEConstraint >>>= 1;
  				bondZConstraint >>>= 1;
  				}
  			}
  
  		int ringIndex = f.size()-FIRST_RING_SIZE;
  		if (f.size() >= FIRST_RING_SIZE && f.size() <= LAST_RING_SIZE && cBondZList[ringIndex] != null) {
  			for (int zList=0; zList<cBondZList[ringIndex].length; zList++) {
  				boolean isSymmetrical = ((0x80000000 & cBondZList[ringIndex][zList]) == 0);
  				int bondZList = (0x7FFFFFFF & cBondZList[ringIndex][zList]);
  				for (boolean inverted=false; !inverted; inverted = !inverted) {
  					if (inverted) {
  						if (isSymmetrical)
  							break;
  
  						int newBondZList = 0;
  						for (int bit=1; bit!=maxBit; bit<<=1) {
  							newBondZList <<= 1;
  							if ((bondZList & bit) != 0)
  								newBondZList |= 1;
  							}
  						bondZList = newBondZList;
  						}
  					for (int rotation=0; rotation<f.size(); rotation++) {
  						if ((bondZList & bondEConstraint) == 0
  						 && (~bondZList & bondZConstraint) == 0) {
  							// constraints are satisfied with current E/Z sequence
  							double bondAngle = 0.0;
  							double correction = Math.PI / 180 * (cAngleCorrection[ringIndex] == null ?
  									0 : cAngleCorrection[ringIndex][zList]);
  
  							// we determine whether we have more right or left turns if we start with a right turn
  							int rightTurns = 0;
  							int tempZList = bondZList;
  							boolean isRightTurn = true;
  							for (int i=0; i<f.size(); i++) {
  								if (isRightTurn)
  									rightTurns++;
  								if ((tempZList & 1) == 0) // is E-bond
  									isRightTurn = !isRightTurn;
  								tempZList >>>= 1;
  								}
  
  							// We choose the starting turn direction such that we end up
  							// with more right turns and, thus, close the ring in clockwise direction.
  							boolean wasRightTurn = (rightTurns > f.size() / 2); // create a ring that closes with right turns
  
  							for (int i=1; i<f.size(); i++) {
  								f.mAtomX[i] = f.mAtomX[i-1] + Math.sin(bondAngle);
  								f.mAtomY[i] = f.mAtomY[i-1] + Math.cos(bondAngle);
  								if ((bondZList & 1) == 0) // is E-bond
  									wasRightTurn = !wasRightTurn;
  								bondAngle += correction + (wasRightTurn ? Math.PI/3.0 : -Math.PI/3.0);
  								bondZList >>>= 1;
  								}
  							return;
  							}
  						if ((bondZList & 1) != 0)
  							bondZList |= maxBit;
  						bondZList >>>= 1;
  						}
  					}
  				}
  			}
  
  			// if not successful so far
  		createRegularRingFragment(f, bondEConstraint, bondZConstraint);
  		}
  
  
  	private int getLargeRingBondParity(int[] ringAtom, int[] ringBond, int index) {
  		int higherIndex = (index == ringAtom.length-1) ? 0 : index+1;    // second ringAtom index
  		int lowerIndex = (index == 0) ? ringAtom.length-1 : index-1;
  		int highestIndex = (higherIndex == ringAtom.length-1) ? 0 : higherIndex+1;
  		if (mMol.getBondOrder(ringBond[index]) == 2) {
  			int bondParity = mMol.getBondParity(ringBond[index]);
  			if (bondParity == Molecule.cBondParityEor1
  			 || bondParity == Molecule.cBondParityZor2) {
  				// translate bond configuration from lowest atom index to ring members
  				if (isLowestIndexNeighbour(ringAtom[lowerIndex], ringAtom[index], ringAtom[higherIndex])
  				  ^ isLowestIndexNeighbour(ringAtom[highestIndex], ringAtom[higherIndex], ringAtom[index]))
  						bondParity = (bondParity == Molecule.cBondParityEor1) ?
  								Molecule.cBondParityZor2 : Molecule.cBondParityEor1;
  				return bondParity;
  				}
  			}
  
  		// If the bond is also a member of a small ring
  		if (mMol.isSmallRingBond(ringBond[index])) {
  			int sharedRing1 = mMol.getRingSet().getSharedRing(ringBond[lowerIndex], ringBond[index]);
  			int sharedRing2 = mMol.getRingSet().getSharedRing(ringBond[higherIndex], ringBond[index]);
  
  			// If more than one bond is shared by the large and the small ring,
  			// then the first and the last shared bond are E and the ones between Z.
  			if (sharedRing1 != -1 || sharedRing2 != -1)
  				return sharedRing1 == sharedRing2 ? Molecule.cBondParityZor2 : Molecule.cBondParityEor1;
  
  			// If only this one bond is shared, then we have a Z-configuration
  			return Molecule.cBondParityZor2;
  			}
  
  		return Molecule.cBondParityNone;
  		}
  
  
  	/**
  	 * @param atom
  	 * @param rootAtom
  	 * @param excludeAtom
  	 * @return whether atom has the lowest atom index of rootAtom's neighbours not considering excludeAtom
  	 */
  	private boolean isLowestIndexNeighbour(int atom, int rootAtom, int excludeAtom) {
  		for (int i=0; i<mMol.getConnAtoms(rootAtom); i++) {
  			int connAtom = mMol.getConnAtom(rootAtom, i);
  			if (connAtom != excludeAtom && connAtom < atom)
  				return false;
  			}
  		return true;
  		}
  
  
  /*	private void printRingStats(InventorFragment f, int[] ringAtom, int[] ringBond) {
  		int maxBit = (1 << f.size());
  		int bondEConstraint = 0;
  		int bondZConstraint = 0;
  		for (int i = 0; i < f.size(); i++) {
  			int bondParity = getLargeRingBondParity(ringAtom, ringBond, i);
  			if (bondParity == Molecule.cBondParityEor1)
  				bondEConstraint += maxBit;
  			else if (bondParity == Molecule.cBondParityZor2)
  				bondZConstraint += maxBit;
  			bondEConstraint >>>= 1;
  			bondZConstraint >>>= 1;
  			}
  		System.out.println("ringSize:"+f.size()
  				+" E-list:"+Integer.toBinaryString(bondEConstraint)
  				+" Z-list:"+Integer.toBinaryString(bondZConstraint));
  		}*/
  
  
  	/**
  	 * Find the smallest ring of given bond
  	 * @param bond
  	 * @return
  	 */
  	private InventorChain getSmallestRingFromBond(int bond) {
  		int atom1 = mMol.getBondAtom(0, bond);
  		int atom2 = mMol.getBondAtom(1, bond);
  		int[] graphAtom = new int[mMol.getAllAtoms()];
  		int[] graphBond = new int[mMol.getAllAtoms()];
  		int[] graphLevel = new int[mMol.getAllAtoms()];
  		int[] graphParent = new int[mMol.getAllAtoms()];
  		graphAtom[0] = atom1;
  		graphAtom[1] = atom2;
  		graphBond[1] = bond;
  		graphLevel[atom1] = 1;
  		graphLevel[atom2] = 2;
  		graphParent[0] = -1;
  		graphParent[1] = 0;
  		int current = 1;
  		int highest = 1;
  		while (current <= highest) {
  			for (int i=0; i<mMol.getConnAtoms(graphAtom[current]); i++) {
  				int candidate = mMol.getConnAtom(graphAtom[current], i);
  				if ((current > 1) && candidate == atom1) {
  					InventorChain theRing = new InventorChain(graphLevel[graphAtom[current]]);
  					graphBond[0] = mMol.getConnBond(graphAtom[current], i);
  					int index = current;
  					for (int j=0; j<theRing.getChainLength(); j++) {
  						theRing.mAtom[j] = graphAtom[index];
  						theRing.mBond[j] = graphBond[index];
  						index = graphParent[index];
  						}
  					return theRing;
  					}
  				if (graphLevel[candidate] == 0 && mMol.isRingAtom(candidate)) {
  					graphAtom[++highest] = candidate;
  					graphBond[highest] = mMol.getConnBond(graphAtom[current], i);
  					graphLevel[candidate] = graphLevel[graphAtom[current]] + 1;
  					graphParent[highest] = current;
  					}
  				}
  			current++;
  			}
  		return null;
  		}
  
  
  	private int getSmallestRingSize(int atom1, int atom2, int atom3) {
  			// return size of smallest ring where atom1, atom2 and atom3 occurr in this order
  		int[] graphAtom = new int[mMol.getAllAtoms()];
  		int[] graphLevel = new int[mMol.getAllAtoms()];
  		graphAtom[0] = atom2;
  		graphAtom[1] = atom1;
  		graphLevel[atom2] = 1;
  		graphLevel[atom1] = 2;
  		int current = 1;
  		int highest = 1;
  		while (current <= highest) {
  			for (int i=0; i<mMol.getConnAtoms(graphAtom[current]); i++) {
  				int candidate = mMol.getConnAtom(graphAtom[current], i);
  				if (candidate == atom3)
  					return 1 + graphLevel[graphAtom[current]];
  
  				if (graphLevel[candidate] == 0 && mMol.isRingAtom(candidate)) {
  					graphAtom[++highest] = candidate;
  					graphLevel[candidate] = graphLevel[graphAtom[current]] + 1;
  					}
  				}
  			current++;
  			}
  		return 0;
  		}
  
  
  	private void joinOverlappingFragments() {
  		while (true) {
  			int maxJoinPriority = 0;
  			int maxCommonAtoms = 0;
  			InventorFragment maxFragment1 = null;
  			InventorFragment maxFragment2 = null;
  			for (int i=1; i<mFragmentList.size(); i++) {
  				InventorFragment f1 = mFragmentList.get(i);
  				for (int j=0; j<i; j++) {
  					InventorFragment f2 = mFragmentList.get(j);
  					int commonAtom = 0;
  					int commonAtoms = 0;
  					int maxF1Priority = 0;
  					int maxF2Priority = 0;
  					for (int k=0; k<f1.size(); k++) {
  						for (int l=0; l<f2.size(); l++) {
  							if (f1.mGlobalAtom[k] == f2.mGlobalAtom[l]) {
  								commonAtoms++;
  								commonAtom = f1.mGlobalAtom[k];
  								if (maxF1Priority < f1.mPriority[k])
  									maxF1Priority = f1.mPriority[k];
  								if (maxF2Priority < f2.mPriority[l])
  									maxF2Priority = f2.mPriority[l];
  								}
  							}
  						}
  
  					if (commonAtoms > 0) {
  						int handlePreferred = (commonAtoms == 1
  											&& getConnAtoms(f1, commonAtom) == 1
  											&& getConnAtoms(f2, commonAtom) == 1) ? 0 : 1;
  
  						int joinPriority;
  						if (maxF1Priority > maxF2Priority)
  							joinPriority = (handlePreferred << 24)
  										 + (maxF1Priority << 16)
  										 + (maxF2Priority << 8)
  										 + commonAtoms;
  						else
  							joinPriority = (handlePreferred << 24)
  										 + (maxF2Priority << 16)
  										 + (maxF1Priority << 8)
  										 + commonAtoms;
  
  						if (maxJoinPriority < joinPriority) {
  							maxJoinPriority = joinPriority;
  							maxCommonAtoms = commonAtoms;
  
  							// retain coordinates of fragment with highest priority atom
  							maxF1Priority = 0;
  							maxF2Priority = 0;
  							for (int k=0; k<f1.size(); k++)
  								if (maxF1Priority < f1.mPriority[k])
  									maxF1Priority = f1.mPriority[k];
  							for (int k=0; k<f2.size(); k++)
  								if (maxF2Priority < f2.mPriority[k])
  									maxF2Priority = f2.mPriority[k];
  
  							if (maxF1Priority > maxF2Priority) {
  								maxFragment1 = f1;
  								maxFragment2 = f2;
  								}
  							else {
  								maxFragment1 = f2;
  								maxFragment2 = f1;
  								}
  							}
  						}
  					}
  				}
  
  			if (maxJoinPriority == 0)
  				break;
  
  			if (maxCommonAtoms == maxFragment1.size())
  				mFragmentList.remove(maxFragment1);
  			else if (maxCommonAtoms == maxFragment2.size())
  				mFragmentList.remove(maxFragment2);
  			else
  				joinFragments(maxFragment1, maxFragment2, maxCommonAtoms);
  			}
  		}
  
  
  	private void joinFragments(InventorFragment f1, InventorFragment f2, int commonAtoms) {
  		int[] commonAtom = new int[commonAtoms];
  		int count = 0;
  		for (int i = 0; i<f1.mGlobalAtom.length; i++)
  			for (int j = 0; j<f2.mGlobalAtom.length; j++)
  				if (f1.mGlobalAtom[i] == f2.mGlobalAtom[j])
  					commonAtom[count++] = f1.mGlobalAtom[i];
  
  		InventorFragment joinedFragment = (commonAtoms == 1) ?
  				  getFusedFragment(f1, f2, commonAtom[0])
  				: getFusedFragment(f1, f2, commonAtom, commonAtoms);
  
  		updateFragmentList(f1, f2, joinedFragment);
  		}
  
  
  	private InventorFragment getFusedFragment(InventorFragment f1, InventorFragment f2, int commonAtom) {
  		int index1 = f1.getLocalAtom(commonAtom);
  		int index2 = f2.getLocalAtom(commonAtom);
  
  		f2.translate(f1.mAtomX[index1]-f2.mAtomX[index2],
  					 f1.mAtomY[index1]-f2.mAtomY[index2]);
  
  		double angle1 = suggestNewBondAngle(f1, commonAtom);
  		double angle2 = suggestNewBondAngle(f2, commonAtom);
  
  		double angleInc = 0.0;
  		if (getConnAtoms(f1, commonAtom) == 1
  		 && getConnAtoms(f2, commonAtom) == 1)
  			angleInc = Math.PI / 3;
  
  		f2.rotate(f2.mAtomX[index2], f2.mAtomY[index2], angle1 - angle2 + angleInc + Math.PI);
  
  		return getMergedFragment(f1, f2, 1);
  		}
  
  
  	private InventorFragment getFusedFragment(InventorFragment f1, InventorFragment f2,
  											  int[] commonAtom, int commonAtoms) {
  		int[] index1 = new int[commonAtoms];
  		int[] index2 = new int[commonAtoms];
  		for (int i=0; i<commonAtoms; i++) {
  			index1[i] = f1.getLocalAtom(commonAtom[i]);
  			index2[i] = f2.getLocalAtom(commonAtom[i]);
  			}
  
  		double meanX1 = 0.0;
  		double meanY1 = 0.0;
  		double meanX2 = 0.0;
  		double meanY2 = 0.0;
  
  		for (int i=0; i<commonAtoms; i++) {
  			meanX1 += f1.mAtomX[index1[i]];
  			meanY1 += f1.mAtomY[index1[i]];
  			meanX2 += f2.mAtomX[index2[i]];
  			meanY2 += f2.mAtomY[index2[i]];
  			}
  		meanX1 /= commonAtoms;
  		meanY1 /= commonAtoms;
  		meanX2 /= commonAtoms;
  		meanY2 /= commonAtoms;
  		f2.translate(meanX1 - meanX2, meanY1 - meanY2);
  
  		InventorAngle[] f1Angle = new InventorAngle[commonAtoms];
  		InventorAngle[] f2Angle = new InventorAngle[commonAtoms];
  		InventorAngle[] angleDif = new InventorAngle[commonAtoms];
  		InventorAngle[] angleDifFlip = new InventorAngle[commonAtoms];
  		for (int i=0; i<commonAtoms; i++) {
  			f1Angle[i] = new InventorAngle(meanX1, meanY1, f1.mAtomX[index1[i]], f1.mAtomY[index1[i]]);
  			f2Angle[i] = new InventorAngle(meanX1, meanY1, f2.mAtomX[index2[i]], f2.mAtomY[index2[i]]);
  			angleDif[i] = new InventorAngle(f1Angle[i].mAngle - f2Angle[i].mAngle,
  											f1Angle[i].mLength * f2Angle[i].mLength);
  			angleDifFlip[i] = new InventorAngle(f1Angle[i].mAngle + f2Angle[i].mAngle,
  												f1Angle[i].mLength * f2Angle[i].mLength);
  			}
  		InventorAngle meanAngleDif = getMeanAngle(angleDif, commonAtoms);
  		InventorAngle meanAngleDifFlip = getMeanAngle(angleDifFlip, commonAtoms);
  
  		int neighbourCountF1 = 0;
  		int neighbourCountF2 = 0;
  		for (int i=0; i<commonAtoms; i++) {
  			for (int j=0; j<mMol.getAllConnAtoms(commonAtom[i]); j++) {
  				int connAtom = mMol.getConnAtom(commonAtom[i], j);
  
  				if (f1.isMember(connAtom) && !f2.isMember(connAtom))
  					neighbourCountF1++;
  
  				if (!f1.isMember(connAtom) && f2.isMember(connAtom))
  					neighbourCountF2++;
  				}
  			}
  
  		InventorAngle[] f1NeighbourAngle = new InventorAngle[neighbourCountF1];
  		InventorAngle[] f2NeighbourAngle = new InventorAngle[neighbourCountF2];
  		InventorAngle[] f2NeighbourAngleFlip = new InventorAngle[neighbourCountF2];
  		neighbourCountF1 = 0;
  		neighbourCountF2 = 0;
  		for (int i=0; i<commonAtoms; i++) {
  			for (int j=0; j<mMol.getAllConnAtoms(commonAtom[i]); j++) {
  				int connAtom = mMol.getConnAtom(commonAtom[i], j);
  
  				if (f1.isMember(connAtom) && !f2.isMember(connAtom)) {
  					int connIndex = f1.getLocalAtom(connAtom);
  					f1NeighbourAngle[neighbourCountF1] = new InventorAngle(f1.mAtomX[index1[i]],
  																		   f1.mAtomY[index1[i]],
  																		   f1.mAtomX[connIndex],
  																		   f1.mAtomY[connIndex]);
  					neighbourCountF1++;
  					}
  
  				if (!f1.isMember(connAtom) && f2.isMember(connAtom)) {
  					int connIndex = f2.getLocalAtom(connAtom);
  					InventorAngle neighbourAngle = new InventorAngle(f2.mAtomX[index2[i]],
  																	 f2.mAtomY[index2[i]],
  																	 f2.mAtomX[connIndex],
  																	 f2.mAtomY[connIndex]);
  					f2NeighbourAngle[neighbourCountF2] = new InventorAngle(meanAngleDif.mAngle
  																		   + neighbourAngle.mAngle,
  																		   neighbourAngle.mLength);
  					f2NeighbourAngleFlip[neighbourCountF2] = new InventorAngle(meanAngleDifFlip.mAngle
  																			   - neighbourAngle.mAngle,
  																			   neighbourAngle.mLength);
  					neighbourCountF2++;
  					}
  				}
  			}
  
  		InventorAngle meanNeighbourAngleF1 = getMeanAngle(f1NeighbourAngle, neighbourCountF1);
  		InventorAngle meanNeighbourAngleF2 = getMeanAngle(f2NeighbourAngle, neighbourCountF2);
  		InventorAngle meanNeighbourAngleF2Flip = getMeanAngle(f2NeighbourAngleFlip, neighbourCountF2);
  
  		if (Math.abs(getAngleDif(meanNeighbourAngleF1.mAngle, meanNeighbourAngleF2.mAngle))
  		  > Math.abs(getAngleDif(meanNeighbourAngleF1.mAngle, meanNeighbourAngleF2Flip.mAngle))) {
  			f2.rotate(meanX1, meanY1, meanAngleDif.mAngle);
  			}
  		else {
  			f2.flip(meanX1, meanY1, 0.0);
  			f2.rotate(meanX1, meanY1, meanAngleDifFlip.mAngle);
  			}
  
  		return getMergedFragment(f1, f2, commonAtoms);
  		}
  
  
  	private InventorFragment getMergedFragment(InventorFragment f1, InventorFragment f2, int commonAtoms) {
  			// merges all atoms of two fragments into a new one retaining original coordinates
  		InventorFragment f = new InventorFragment(mMol, f1.mGlobalAtom.length + f2.mGlobalAtom.length - commonAtoms, f1.mKeepMarkedAtoms | f2.mKeepMarkedAtoms);
  		int count = 0;
  		for (int i = 0; i<f1.mGlobalAtom.length; i++) {
  			f.mGlobalAtom[count] = f1.mGlobalAtom[i];
  			f.mPriority[count] = f1.mPriority[i];
  			f.mAtomX[count] = f1.mAtomX[i];
  			f.mAtomY[count++] = f1.mAtomY[i];
  			}
  		for (int i = 0; i<f2.mGlobalAtom.length; i++) {
  			int index = f1.getLocalAtom(f2.mGlobalAtom[i]);
  			if (index == -1) {
  				f.mGlobalAtom[count] = f2.mGlobalAtom[i];
  				f.mPriority[count] = f2.mPriority[i];
  				f.mAtomX[count] = f2.mAtomX[i];
  				f.mAtomY[count++] = f2.mAtomY[i];
  				}
  			else {
  				if (f.mPriority[index] < f2.mPriority[i]) {
  					f.mPriority[index] = f2.mPriority[i];
  					f.mAtomX[index] = f2.mAtomX[i];
  					f.mAtomY[index] = f2.mAtomY[i];
  					}
  				}
  			}
  		return f;
  		}
  
  
  	private InventorChain getLongestUnhandledChain(int atom) {
  		int[] graphAtom = new int[mMol.getAllAtoms()];
  		int[] graphBond = new int[mMol.getAllAtoms()];
  		int[] graphLevel = new int[mMol.getAllAtoms()];
  		int[] graphParent = new int[mMol.getAllAtoms()];
  		graphAtom[0] = atom;
  		graphLevel[atom] = 1;
  		graphParent[0] = -1;
  		int current = 0;
  		int highest = 0;
  		while (current <= highest) {
  			if (current == 0 || !mAtomHandled[graphAtom[current]]) {
  				for (int i=0; i<mMol.getAllConnAtoms(graphAtom[current]); i++) {
  					int candidate = mMol.getConnAtom(graphAtom[current], i);
  					int theBond = mMol.getConnBond(graphAtom[current], i);
  					if (graphLevel[candidate] == 0 && !mBondHandled[theBond]) {
  						graphAtom[++highest] = candidate;
  						graphBond[highest] = theBond;
  						graphLevel[candidate] = graphLevel[graphAtom[current]] + 1;
  						graphParent[highest] = current;
  						}
  					}
  				}
  			if (current == highest) {
  				InventorChain theChain = new InventorChain(graphLevel[graphAtom[current]]);
  				int index = current;
  				for (int j=0; j<theChain.getChainLength(); j++) {
  					theChain.mAtom[j] = graphAtom[index];
  					theChain.mBond[j] = graphBond[index];
  					index = graphParent[index];
  					}
  				return theChain;
  				}
  			current++;
  			}
  		return null;
  		}
  
  
  	private double suggestNewBondAngle(InventorFragment f, int atom) {
  		double[] connAngle = new double[mMol.getAllConnAtoms(atom)+1];
  		int[] connAtom = new int[mMol.getAllConnAtoms(atom)+1];
  		int[] connBond = new int[mMol.getAllConnAtoms(atom)+1];
  		int rootIndex = f.getLocalAtom(atom);
  		int connAngles = 0;
  		for (int i=0; i<mMol.getAllConnAtoms(atom); i++) {
  			connAtom[connAngles] = mMol.getConnAtom(atom, i);
  			connBond[connAngles] = mMol.getConnBond(atom, i);
  			int index = f.getLocalAtom(connAtom[connAngles]);
  			if (index != -1)
  				connAngle[connAngles++] = InventorAngle.getAngle(f.mAtomX[rootIndex],
  																 f.mAtomY[rootIndex],
  																 f.mAtomX[index],
  																 f.mAtomY[index]);
  			}
  
  		if (connAngles == 1)
  			return connAngle[0] + Math.PI;
  
  		for (int i=connAngles-1; i>0; i--) {	// bubble sort
  			for (int j=0; j<i; j++) {
  				if (connAngle[j] > connAngle[j+1]) {
  					double tempAngle = connAngle[j];
  					connAngle[j] = connAngle[j+1];
  					connAngle[j+1] = tempAngle;
  					int tempAtom = connAtom[j];
  					connAtom[j] = connAtom[j+1];
  					connAtom[j+1] = tempAtom;
  					int tempBond = connBond[j];
  					connBond[j] = connBond[j+1];
  					connBond[j+1] = tempBond;
  					}
  				}
  			}
  
  		connAngle[connAngles] = connAngle[0] + 2 * Math.PI;
  		connAtom[connAngles] = connAtom[0];
  		connBond[connAngles] = connBond[0];
  
  		double maxAngleDif = -100.0;
  		int maxIndex = 0;
  		for (int i=0; i<connAngles; i++) {
  			double angleDif = connAngle[i+1] - connAngle[i];
  			if (connAngles > 2
  			 && mMol.isRingBond(connBond[i])
  			 && mMol.isRingBond(connBond[i+1])) {
  				int ringSize = getSmallestRingSize(connAtom[i], atom, connAtom[i+1]);
  				if (ringSize != 0)
  					angleDif -= 100.0 - ringSize;
  				}
  
  			if (maxAngleDif < angleDif) {
  				maxAngleDif = angleDif;
  				maxIndex = i;
  				}
  			}
  
  		return (connAngle[maxIndex] + connAngle[maxIndex+1]) / 2;
  		}
  
  
  	private double getAngleDif(double angle1, double angle2) {
  		double angleDif = angle1 - angle2;
  		while (angleDif < -Math.PI)
  			angleDif += 2 * Math.PI;
  		while (angleDif > Math.PI)
  			angleDif -= 2 * Math.PI;
  		return angleDif;
  		}
  
  
  	protected static InventorAngle getMeanAngle(InventorAngle[] angle, int noOfAngles) {
  		// adds noOfAngles vectors of length=1 with angles angle[i]
  		// and returns angle of sum-vector
  		// length of sum-vector is criteria for deviation
  		double sinSum = 0;
  		double cosSum = 0;
  		for (int i=0; i<noOfAngles; i++) {
  			sinSum += angle[i].mLength * Math.sin(angle[i].mAngle);
  			cosSum += angle[i].mLength * Math.cos(angle[i].mAngle);
  			}
  
  		double meanAngle;
  		if (cosSum == 0)
  			meanAngle = (sinSum > 0) ? Math.PI/2 : -Math.PI/2;
  		else {
  			meanAngle = Math.atan(sinSum/cosSum);
  			if (cosSum < 0)
  				meanAngle += Math.PI;
  			}
  
  		double length = Math.sqrt(sinSum * sinSum + cosSum * cosSum) / noOfAngles;
  
  		return new InventorAngle(meanAngle, length);
  		}
  
  
  	private int getConnAtoms(InventorFragment f, int atom) {
  		int connAtoms = 0;
  		for (int i=0; i<mMol.getAllConnAtoms(atom); i++) {
  			if (f.isMember(mMol.getConnAtom(atom, i)))
  				connAtoms++;
  			}
  		return connAtoms;
  		}
  
  
  	private void correctChainEZParities() {
  		for (InventorFragment f : mFragmentList) {
  			for (int i = 0; i<f.mGlobalBond.length; i++) {
  				int bond = f.mGlobalBond[i];
  
  				if (mMol.getBondOrder(bond) == 2) {
  					if (!mMol.isSmallRingBond(bond)
  							&& (mMol.getBondParity(bond) == Molecule.cBondParityUnknown
  							|| mMol.getBondParity(bond) == Molecule.cBondParityNone))
  						mMol.setBondParityUnknownOrNone(bond);
  
  					if (!mMol.isRingBond(bond)
  							&& (mMol.getConnAtoms(mMol.getBondAtom(0, bond))>1)
  							&& (mMol.getConnAtoms(mMol.getBondAtom(1, bond))>1)
  							&& (mMol.getBondParity(bond) == Molecule.cBondParityEor1
  							|| mMol.getBondParity(bond) == Molecule.cBondParityZor2)) {
  						int[] minConnAtom = new int[2];
  						int[] bondAtom = new int[2];
  						for (int j = 0; j<2; j++) {
  							minConnAtom[j] = mMol.getMaxAtoms();
  							bondAtom[j] = mMol.getBondAtom(j, bond);
  							for (int k = 0; k<mMol.getAllConnAtoms(bondAtom[j]); k++) {
  								int connAtom = mMol.getConnAtom(bondAtom[j], k);
  								if (connAtom != mMol.getBondAtom(1 - j, bond)
  										&& minConnAtom[j]>connAtom)
  									minConnAtom[j] = connAtom;
  								}
  							}
  
  						double dbAngle = InventorAngle.getAngle(f.mAtomX[f.mGlobalToLocalAtom[bondAtom[0]]],
  								f.mAtomY[f.mGlobalToLocalAtom[bondAtom[0]]],
  								f.mAtomX[f.mGlobalToLocalAtom[bondAtom[1]]],
  								f.mAtomY[f.mGlobalToLocalAtom[bondAtom[1]]]);
  						double angle1 = InventorAngle.getAngle(f.mAtomX[f.mGlobalToLocalAtom[minConnAtom[0]]],
  								f.mAtomY[f.mGlobalToLocalAtom[minConnAtom[0]]],
  								f.mAtomX[f.mGlobalToLocalAtom[bondAtom[0]]],
  								f.mAtomY[f.mGlobalToLocalAtom[bondAtom[0]]]);
  						double angle2 = InventorAngle.getAngle(f.mAtomX[f.mGlobalToLocalAtom[bondAtom[1]]],
  								f.mAtomY[f.mGlobalToLocalAtom[bondAtom[1]]],
  								f.mAtomX[f.mGlobalToLocalAtom[minConnAtom[1]]],
  								f.mAtomY[f.mGlobalToLocalAtom[minConnAtom[1]]]);
  
  						if (((getAngleDif(dbAngle, angle1)<0)
  								^ (getAngleDif(dbAngle, angle2)<0))
  								^ (mMol.getBondParity(bond) == Molecule.cBondParityZor2)) {
  							f.flipOneSide(bond);
  							}
  						}
  					}
  				}
  			}
  		}
  
  
  	private void optimizeFragments() {
  		int[] atomSymRank = calculateAtomSymmetries();
  
  		byte[] bondFlipPriority = new byte[mMol.getAllBonds()];
  		locateFlipBonds(bondFlipPriority, atomSymRank);
  		for (int bond=0; bond<mMol.getAllBonds(); bond++)
  			if (bondFlipPriority[bond] == FLIP_POSSIBLE
  			 && (mMol.isRingAtom(mMol.getBondAtom(0, bond))
  			  || mMol.isRingAtom(mMol.getBondAtom(1, bond))))
  				bondFlipPriority[bond] = FLIP_PREFERRED;
  
  		for (int fragmentNo=0; fragmentNo<mFragmentList.size(); fragmentNo++) {
  			InventorFragment f = mFragmentList.get(fragmentNo);
  			ArrayList<int[]> collisionList = f.getCollisionList();
  			double minCollisionPanalty = f.getCollisionPanalty();
  			InventorFragment minCollisionFragment = new InventorFragment(f);
  
  			int lastBond = -1;
  			for (int flip = 0; flip<TOTAL_FLIPS && !collisionList.isEmpty(); flip++) {
  				int collisionNo = mRandom.nextInt(collisionList.size());
  				int[] collidingAtom = collisionList.get(collisionNo);
  				int[] bondSequence = getShortestConnection(collidingAtom[0], collidingAtom[1]);
  				int[] availableBond = new int[bondSequence.length];
  				int availableBonds = 0;
  
  				if (flip < PREFERRED_FLIPS) {
  					for (int i=1; i<bondSequence.length-1; i++)
  						if (bondFlipPriority[bondSequence[i]] == FLIP_PREFERRED)
  							availableBond[availableBonds++] = bondSequence[i];
  					}
  				else if (flip < PREFERRED_FLIPS + POSSIBLE_FLIPS) {
  					for (int i=1; i<bondSequence.length-1; i++)
  						if (bondFlipPriority[bondSequence[i]] >= FLIP_POSSIBLE)
  							availableBond[availableBonds++] = bondSequence[i];
  					}
  				else {
  					for (int i=1; i<bondSequence.length-1; i++)
  						if (bondFlipPriority[bondSequence[i]] >= FLIP_AS_LAST_RESORT)
  							availableBond[availableBonds++] = bondSequence[i];
  					}
  
  				if (availableBonds != 0) {
  					int theBond = availableBond[0];
  					if (availableBonds > 1) {
  						do {	// don't rotate 2 times around same bond
  							theBond = availableBond[mRandom.nextInt(availableBonds)];
  							} while (theBond == lastBond);
  						}
  
  					if (theBond != lastBond) {
  						lastBond = theBond;
  		
  						f.flipOneSide(theBond);
  
  							// getCollisionList() is necessary to update collision panalty
  						collisionList = f.getCollisionList();
  						if (minCollisionPanalty > f.getCollisionPanalty()) {
  							minCollisionPanalty = f.getCollisionPanalty();
  							minCollisionFragment = new InventorFragment(f);
  							}
  						}
  					}
  				}
  
  			mFragmentList.set(fragmentNo, minCollisionFragment);
  				// finished optimization by rotating around single bonds
  
  				// starting optimization by moving individual atoms
  /*
  double avbl = mMol.getAverageBondLength();
  for (int i=0; i<f.mAtom.length; i++) {
  f.mAtomX[i] = mMol.getAtomX(f.mAtom[i]) / avbl;
  f.mAtomY[i] = mMol.getAtomY(f.mAtom[i]) / avbl;
  }*/
  			f = minCollisionFragment;
  			int currentRank = 1;
  			int nextAvailableRank;
  			do {
  				nextAvailableRank = 9999;
  				for (int i=0; i<f.size(); i++) {
  					int theRank = atomSymRank[f.mGlobalAtom[i]];
  					if (theRank == currentRank)
  						f.optimizeAtomCoordinates(i);
  					else if (theRank > currentRank && theRank < nextAvailableRank)
  						nextAvailableRank = theRank;
  					}
  				currentRank = nextAvailableRank;
  				} while (nextAvailableRank != 9999);
  			}
  		}
  
  
  	private int[] getShortestConnection(int atom1, int atom2) {
  		int[] graphAtom = new int[mMol.getAllAtoms()];
  		int[] graphBond = new int[mMol.getAllAtoms()];
  		int[] graphLevel = new int[mMol.getAllAtoms()];
  		int[] graphParent = new int[mMol.getAllAtoms()];
  		graphAtom[0] = atom2;
  		graphLevel[atom2] = 1;
  		graphParent[0] = -1;
  		int current = 0;
  		int highest = 0;
  		while (current <= highest) {
  			for (int i=0; i<mMol.getAllConnAtoms(graphAtom[current]); i++) {
  				int candidate = mMol.getConnAtom(graphAtom[current], i);
  				int theBond = mMol.getConnBond(graphAtom[current], i);
  
  				if (candidate == atom1) {
  					int chainLength = graphLevel[graphAtom[current]];
  					int[] bondSequence = new int[chainLength];
  					bondSequence[0] = theBond;
  					for (int j=1; j<chainLength; j++) {
  						bondSequence[j] = graphBond[current];
  						current = graphParent[current];
  						}
  
  					return bondSequence;
  					}
  
  				if (graphLevel[candidate] == 0) {
  					graphAtom[++highest] = candidate;
  					graphBond[highest] = theBond;
  					graphLevel[candidate] = graphLevel[graphAtom[current]] + 1;
  					graphParent[highest] = current;
  					}
  				}
  
  			if (current == highest)
  				return null;
  
  			current++;
  			}
  		return null;
  		}
  
  
  	private void locateFlipBonds(byte[] bondFlipPriority, int[] atomSymRank) {
  		for (int bond=0; bond<mMol.getAllBonds(); bond++) {
  			int atom1 = mMol.getBondAtom(0, bond);
  			int atom2 = mMol.getBondAtom(1, bond);
  
  			if (mMol.isRingBond(bond)
  			 || mMol.getBondOrder(bond) != 1
  			 || mMol.getAllConnAtoms(atom1) == 1
  			 || mMol.getAllConnAtoms(atom2) == 1)
  				continue;
  
  			if ((mMode & MODE_KEEP_MARKED_ATOM_COORDS) != 0
  			 && mMol.isMarkedAtom(atom1)
  			 && mMol.isMarkedAtom(atom2))
  			 	continue;
  
  			boolean oneBondEndIsSymmetric = false;
  			for (int i=0; i<2; i++) {
  				int bondAtom = mMol.getBondAtom(i, bond);
  				if (mMol.getAllConnAtoms(bondAtom) > 2) {
  					boolean symmetricEndFound = true;
  					int connSymRank = -1;
  					for (int j=0; j<mMol.getAllConnAtoms(bondAtom); j++) {
  						int connAtom = mMol.getConnAtom(bondAtom, j);
  						if (connAtom != mMol.getBondAtom(1-i, bond)) {
  							if (connSymRank == -1)
  								connSymRank = atomSymRank[connAtom];
  							else if (connSymRank != atomSymRank[connAtom])
  								symmetricEndFound = false;
  							}
  						}
  					if (symmetricEndFound) {
  						oneBondEndIsSymmetric = true;
  						break;
  						}
  					}
  				}
  			if (!oneBondEndIsSymmetric) {
  				if ((mMode & MODE_PREFER_MARKED_ATOM_COORDS) != 0
  				 && mMol.isMarkedAtom(atom1)
  				 && mMol.isMarkedAtom(atom2))
  					bondFlipPriority[bond] = FLIP_AS_LAST_RESORT;
  				else
  					bondFlipPriority[bond] = FLIP_POSSIBLE;
  				}
  			}
  		}
  
  
  	private int[] calculateAtomSymmetries() {
  		int atomBits = Canonizer.getNeededBits(mMol.getAtoms());
  		int maxConnAtoms = 2;
  		for (int atom=0; atom<mMol.getAtoms(); atom++)
  			maxConnAtoms = Math.max(maxConnAtoms, mMol.getAllConnAtoms(atom));
  		int baseValueSize = (62 + 2 * atomBits + maxConnAtoms * (atomBits+1)) / 63;
  
  		CanonizerBaseValue[] baseValue = new CanonizerBaseValue[mMol.getAllAtoms()];
  		for (int atom=0; atom<mMol.getAllAtoms(); atom++) {
  			baseValue[atom] = new CanonizerBaseValue(baseValueSize);
  			baseValue[atom].init(atom);
  			}
  
  		int[] symRank = new int[mMol.getAllAtoms()];
  
  			// For calculating atom symmetries all atoms are considered the same.
  			// Only the different connectivity makes an atom different from another.
  			// However, double bonds of different parities must be taken into account.
  		for (int bond=0; bond<mMol.getBonds(); bond++) {
  			int bondParity = mMol.getBondParity(bond);
  			if (bondParity == Molecule.cBondParityEor1
  			 || bondParity == Molecule.cBondParityZor2) {
  				baseValue[mMol.getBondAtom(0, bond)].add(bondParity);
  				baseValue[mMol.getBondAtom(1, bond)].add(bondParity);
  				}
  			}
  
  		int oldNoOfRanks;
  		int newNoOfRanks = consolidateRanks(baseValue, symRank);
  		do {
  			oldNoOfRanks = newNoOfRanks;
  			calcNextBaseValues(baseValue, symRank, atomBits, maxConnAtoms);
  			newNoOfRanks = consolidateRanks(baseValue, symRank);
  			} while (oldNoOfRanks != newNoOfRanks);
  
  		return symRank;
  		}
  
  
  	private void calcNextBaseValues(CanonizerBaseValue[] baseValue, int[] symRank, int atomBits, int maxConnAtoms) {
  		int[] connRank = new int[maxConnAtoms];
  		for (int atom=0; atom<mMol.getAllAtoms(); atom++) {
  								// generate sorted list of ranks of neighbours
  			for (int i=0; i<mMol.getAllConnAtoms(atom); i++) {
  				int rank = symRank[mMol.getConnAtom(atom,i)];
  				int j;
  				for (j=0; j<i; j++)
  					if (rank < connRank[j])
  						break;
  				for (int k=i; k>j; k--)
  					connRank[k] = connRank[k-1];
  				connRank[j] = rank;
  				}
  
  			int neighbours = mMol.getAllConnAtoms(atom);
  			baseValue[atom].init(atom);
  			baseValue[atom].add(atomBits, symRank[atom]);
  			baseValue[atom].add((maxConnAtoms - neighbours)*(atomBits + 1), 0);
  			for (int i=0; i<neighbours; i++)
  				baseValue[atom].add(atomBits + 1, connRank[i]);
  			}
  		}
  
  
  	private int consolidateRanks(CanonizerBaseValue[] baseValue, int[] symRank) {
  		int rank = 0;
  		Arrays.sort(baseValue);
  		for (int i=0; i<baseValue.length; i++) {
  			if (i == 0 || baseValue[i].compareTo(baseValue[i-1]) != 0)
  				rank++;
  			symRank[baseValue[i].getAtom()] = rank;
  			}
  
  		return rank;
  		}
  
  
  	private void joinMetalBondedFragments() {
  		ArrayList<FragmentAssociation> associationList = createMetalBondAssociations();
  		while (associationList != null) {
  			FragmentAssociation association = getMaxPriorityAssociation(associationList);
  			joinAssociatedFragments(association, JOIN_DISTANCE_METAL_BONDS);
  			associationList = createMetalBondAssociations();
  			}
  		}
  
  	private void joinChargedFragments() {
  		FragmentAssociation association = createChargeAssociation();
  		while (association != null) {
  			joinAssociatedFragments(association, JOIN_DISTANCE_CHARGED_ATOMS);
  			association = createChargeAssociation();
  			}
  		}
  
  	private void joinRemainingFragments() {
  		FragmentAssociation association = createDisconnectedAssociation();
  		while (association != null) {
  			joinAssociatedFragments(association, JOIN_DISTANCE_UNCHARGED_FRAGMENTS);
  			association = createDisconnectedAssociation();
  			}
  		}
  
  	private void joinAssociatedFragments(FragmentAssociation association, double minDistance) {
  		association.arrange(minDistance, (mMode & MODE_CONSIDER_MARKED_ATOMS) != 0);
  		InventorFragment mergedFragment = getMergedFragment(association.getFragment(0), association.getFragment(1), 0);
  		updateFragmentList(association.getFragment(0), association.getFragment(1), mergedFragment);
  		}
  
  	private FragmentAssociation getMaxPriorityAssociation(ArrayList<FragmentAssociation> associationList) {
  		int maxPriority = 0;
  		FragmentAssociation maxFA = null;
  		for (FragmentAssociation association:associationList) {
  			if (maxPriority < association.getPriority()) {
  				maxPriority = association.getPriority();
  				maxFA = association;
  				}
  			}
  		return maxFA;
  		}
  
  	private ArrayList<FragmentAssociation> createMetalBondAssociations() {
  		ArrayList<FragmentAssociation> associationList = null;
  		FragmentAssociation[][] fa = null;
  		for (int bond=0; bond<mMol.getBonds(); bond++) {
  			if (mMol.getBondType(bond) == Molecule.cBondTypeMetalLigand) {
  				int atom1 = mMol.getBondAtom(0, bond);
  				int atomIndex1 = -1;
  				int f1 = 0;
  				for (; f1<mFragmentList.size(); f1++) {
  					atomIndex1 = mFragmentList.get(f1).getLocalAtom(atom1);
  					if (atomIndex1 != -1)
  						break;
  					}
  				int atom2 = mMol.getBondAtom(1, bond);
  				int atomIndex2 = -1;
  				int f2 = 0;
  				for (; f2<mFragmentList.size(); f2++) {
  					atomIndex2 = mFragmentList.get(f2).getLocalAtom(atom2);
  					if (atomIndex2 != -1)
  						break;
  					}
  
  				if (f1 != f2) {
  					if (f1 > f2) {	// f1 must be the smaller one (and potentially the core fragment with fixed coords)
  						int temp = f1;
  						f1 = f2;
  						f2 = temp;
  						temp = atomIndex1;
  						atomIndex1 = atomIndex2;
  						atomIndex2 = temp;
  						}
  					if (fa == null)
  						fa = new FragmentAssociation[mFragmentList.size()][];
  					if (fa[f2] == null)
  						fa[f2] = new FragmentAssociation[f2];
  					if (fa[f2][f1] != null)
  						fa[f2][f1].add(atomIndex1, atomIndex2);
  					else {
  						fa[f2][f1] = new FragmentAssociation(mFragmentList.get(f1), mFragmentList.get(f2), atomIndex1, atomIndex2);
  						if (associationList == null)
  							associationList = new ArrayList<FragmentAssociation>();
  						associationList.add(fa[f2][f1]);
  						}
  					}
  				}
  			}
  
  		return associationList;
  		}
  
  	private FragmentAssociation createChargeAssociation() {
  		ArrayList<InventorCharge> negChargeList = new ArrayList<InventorCharge>();
  		ArrayList<InventorCharge> posChargeList = new ArrayList<InventorCharge>();
  		ArrayList<InventorCharge> chargeList = new ArrayList<InventorCharge>();
  
  		for (InventorFragment f:mFragmentList) {
  			int fragmentCharge = 0;
  			chargeList.clear();
  			for (int i=0; i<f.size(); i++) {
  				int atom = f.getGlobalAtom(i);
  				int charge = mUnPairedCharge[atom];
  				if (charge != 0) {
  					chargeList.add(new InventorCharge(f, i, charge));
  					fragmentCharge += charge;
  					}
  				}
  			if (fragmentCharge != 0) {
  				//@Override Annotation incompatible with 1.5
  				chargeList.sort((o1, o2) -> {
  					int c1 = Math.abs(o1.charge);
  					int c2 = Math.abs(o2.charge);
  					return Integer.compare(c1, c2);
  				});
  				for (InventorCharge ic:chargeList) {
  					if (fragmentCharge * ic.charge > 0) {	// charges have same sign
  						int charge = (Math.abs(fragmentCharge) >= Math.abs(ic.charge)) ? ic.charge : fragmentCharge;
  						fragmentCharge -= charge;
  						(charge < 0 ? negChargeList : posChargeList).add(new InventorCharge(f, ic.atom, charge));
  						if (fragmentCharge == 0)
  							break;
  						}
  					}
  				}
  			}
  
  		if (negChargeList.isEmpty() || posChargeList.isEmpty())
  			return null;
  
  		// with positive charges we have the large fragments first
  		posChargeList.sort(new Comparator<InventorCharge>() {
  			//@Override Annotation incompatible with 1.5
  			public int compare(InventorCharge o1, InventorCharge o2) {
  				int c1 = o1.fragment.size();
  				int c2 = o1.fragment.size();
  				return Integer.compare(c2, c1);
  			}
  		});
  
  		// with negative charges we have the small o1.fragments first
  		negChargeList.sort(new Comparator<InventorCharge>() {
  			//@Override Annotation incompatible with 1.5
  			public int compare(InventorCharge o1, InventorCharge o2) {
  				int c1 = o1.fragment.size();
  				int c2 = o1.fragment.size();
  				return Integer.compare(c1, c2);
  			}
  		});
  
  		// we combine preferably small with large
  		for (InventorCharge pc:posChargeList) {
  			for (InventorCharge nc : negChargeList) {
  				if (pc.charge == -nc.charge) {
  					mUnPairedCharge[pc.fragment.getGlobalAtom(pc.atom)] -= pc.charge;
  					mUnPairedCharge[nc.fragment.getGlobalAtom(nc.atom)] -= nc.charge;
  					return new FragmentAssociation(pc.fragment, nc.fragment, pc.atom, nc.atom);
  					}
  				}
  			}
  
  		for (InventorCharge pc:posChargeList) {
  			for (InventorCharge nc : negChargeList) {
  				if (pc.charge > -nc.charge) {
  					mUnPairedCharge[pc.fragment.getGlobalAtom(pc.atom)] += nc.charge;
  					mUnPairedCharge[nc.fragment.getGlobalAtom(nc.atom)] -= nc.charge;
  					return new FragmentAssociation(pc.fragment, nc.fragment, pc.atom, nc.atom);
  					}
  				}
  			}
  
  		for (InventorCharge pc:posChargeList) {
  			for (InventorCharge nc : negChargeList) {
  				if (pc.charge < -nc.charge) {
  					mUnPairedCharge[pc.fragment.getGlobalAtom(pc.atom)] -= pc.charge;
  					mUnPairedCharge[nc.fragment.getGlobalAtom(nc.atom)] += pc.charge;
  					return new FragmentAssociation(pc.fragment, nc.fragment, pc.atom, nc.atom);
  					}
  				}
  			}
  
  		return null;
  		}
  
  	private FragmentAssociation createDisconnectedAssociation() {
  		if (mFragmentList.size() < 2)
  			return null;
  
  		return new FragmentAssociation(mFragmentList.get(0), mFragmentList.get(1));
  		}
  
  	/* ingenious solution to whether we have a cyclic relationship between fragments and metal bond associations
  	private boolean isCyclic(FragmentAssociation[][] fa) {
  		for (int i=1; i<fa.length; i++) {
  			for (int j=0; j<i; j++) {
  				if (fa[i][j] != null) {
  					for (int ii=i+1; ii<fa.length; ii++) {
  						if (fa[ii][j] != null) {
  							if (fa[ii][i] != null)
  								return true;
  							fa[ii][i] = fa[i][j];	// indicate the higher order connection
  							}
  						}
  					for (int jj=j+1; jj<i; jj++) {
  						if (fa[i][jj] != null) {
  							if (fa[jj][j] != null)
  								return true;
  							fa[jj][j] = fa[i][j];	// indicate the higher order connection
  							}
  						}
  					}
  				}
  			}
  		return false;
  		}*/
  
  	private void updateFragmentList(InventorFragment fOld1, InventorFragment fOld2, InventorFragment fJoined) {
  		int index = Math.min(mFragmentList.indexOf(fOld1), mFragmentList.indexOf(fOld2));
  		mFragmentList.add(index, fJoined);
  		mFragmentList.remove(fOld1);
  		mFragmentList.remove(fOld2);
  		}
  	}
  