com.actelion.research.chem.IsomericSmilesCreator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openchemlib Show documentation
Show all versions of openchemlib Show documentation
Open Source Chemistry Library
/*
* 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;
import com.actelion.research.chem.reaction.Reaction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class IsomericSmilesCreator {
public static final int MODE_CREATE_SMARTS = 1;
public static final int MODE_INCLUDE_MAPPING = 2;
public static final int MODE_KEKULIZED_OUTPUT = 4; // no lower case atom labels and single/double bonds to represent aromaticity
private StereoMolecule mMol;
private Canonizer mCanonizer;
private String mSmiles;
private int mMode;
private int[] mAtomRank;
private int[] mClosureNumber;
private int[] mSmilesIndex;
private int[][] mKnownTHCountInESRGroup;
private List mGraphAtomList;
private boolean[] mAtomUsed;
private boolean[] mBondUsed;
private boolean[] mPseudoStereoGroupInversion;
private boolean[] mPseudoStereoGroupInitialized;
private int[] mEZHalfParity;
/**
* Convenience method to generate a canonical and isomeric SMILES from a given molecule.
* @param mol
* @return
*/
public static String createSmiles(StereoMolecule mol) {
return new IsomericSmilesCreator(mol, 0).getSmiles();
}
/**
* Convenience method to generate a canonical and isomeric SMILES from a given molecule.
* @param mol
* @return
*/
public static String createSmarts(StereoMolecule mol) {
return new IsomericSmilesCreator(mol, MODE_CREATE_SMARTS).getSmiles();
}
public static String createReactionSmarts(Reaction rxn) {
return createReactionSmiles(rxn, MODE_INCLUDE_MAPPING | MODE_CREATE_SMARTS);
}
public static String createReactionSmiles(Reaction rxn) {
return createReactionSmiles(rxn, MODE_INCLUDE_MAPPING);
}
public static String createReactionSmiles(Reaction rxn, int mode) {
StringBuilder sb = new StringBuilder();
for (int i=0; i');
for (int i=0; i');
for (int i=0; i();
int atom = findUnusedStartAtom();
while (atom != -1) {
int graphIndex = mGraphAtomList.size();
addToGraph(new SmilesAtom(atom, -1, -1, false, false), graphIndex);
if (mMol.getConnAtoms(atom) != 0) {
addHighestRankingChain(graphIndex, false);
while (graphIndex < mGraphAtomList.size() - 1) {
while (hasUnusedNeighborAtom(mGraphAtomList.get(graphIndex).atom))
addHighestRankingChain(graphIndex, true);
graphIndex++;
}
}
// we may have multiple unconnected fragments
atom = findUnusedStartAtom();
}
// assign a smiles position index to every atom (needed for stereo assignment)
mSmilesIndex = new int[mMol.getAtoms()];
int index = 0;
for (SmilesAtom smilesAtom:mGraphAtomList)
mSmilesIndex[smilesAtom.atom] = index++;
}
private void findRingClosures() {
// find closure neighbours of every atom and put them in canonical order. i.e. order of appearance in SMILES
for (SmilesAtom smilesAtom:mGraphAtomList) {
int closureCount = 0;
for (int i=0; i relativeBondParityList = new ArrayList<>();
for (SmilesAtom currentSA:mGraphAtomList) {
if (currentSA.parent != -1) {
int ezBond = mMol.getBond(currentSA.atom, currentSA.parent);
if (!mMol.isBINAPChiralityBond(ezBond)
&& !mMol.isSmallRingBond(ezBond)
&& (mMol.getBondParity(ezBond) == Molecule.cBondParityEor1
|| mMol.getBondParity(ezBond) == Molecule.cBondParityZor2)) {
SmilesAtom parentSA = mGraphAtomList.get(mSmilesIndex[currentSA.parent]);
// Here we collect for every stereo double bond a list of relative halfParities,
// which collects the relative bond symbol dependency of all connected single bonds.
int[] bondWithHalfParity = new int[mMol.getConnAtoms(currentSA.atom)+mMol.getConnAtoms(parentSA.atom)-2];
int halfParityIndex = 0;
// halfParities translate 1-to-1 into the bond symbol ('/' or '\').
// The value of halfParity1 is arbitrary, but for canonical SMILES its value must be reproducible.
boolean parity = false;
if (parentSA.parent != -1) {
// If there is a bond leading to the first double bond atom, we take it as the reference.
bondWithHalfParity[halfParityIndex++] = parentSA.bond; // reference bond with halfParity flag unset
}
else {
// If the graph starts with one of the double bond atoms (rare, but possible), then this atom has no parent atom.
// Instead, it may be added in forward direction as a branch or it may be connected as a ring closure.
// If that single bonded neighbour, which has the lower SMILES index, is effectively the second atom of the '/' or '\'
// bond in the SMILES, we need to invert its halfParity.
int firstNeighbourIndex = -1;
int secondNeighbourIndex = -1;;
int firstSmilesIndex = Integer.MAX_VALUE;
for (int i=0; i=0; i--) {
int[] bondWithHalfParity = relativeBondParityList.get(i);
int overlapCount = 0;
boolean inverted = false;
boolean collides = false;
for (int bwhp:bondWithHalfParity) {
int bond = bwhp & 0x3FFFFFFF;
if (mEZHalfParity[bond] != 0) {
boolean differs = ((bwhp & 0x40000000) != 0) ^ (mEZHalfParity[bond] == 2);
if (overlapCount == 0)
inverted = differs;
else if (inverted != differs)
collides = true;
overlapCount++;
}
}
if (overlapCount != 0) {
bondWithHalfParity = relativeBondParityList.remove(i);
// collisions, i.e. incompatible halfParity constraints should be very rare, but not impossible
if (!collides)
addRelativeBondHalfParities(bondWithHalfParity, inverted);
}
}
// if we haven't found a constrained set, we just add the first one
if (startSize == relativeBondParityList.size())
addRelativeBondHalfParities(relativeBondParityList.remove(0), false);
}
}
private void addRelativeBondHalfParities(int[] bondWithHalfParity, boolean inverted) {
for (int bwhp:bondWithHalfParity) {
mEZHalfParity[bwhp & 0x3FFFFFFF] = ((bwhp & 0x40000000) != 0) ^ inverted ? 2 : 1;
}
}
private boolean isBondFromTo(int atom1, int atom2) {
SmilesAtom sa1 = mGraphAtomList.get(mSmilesIndex[atom1]);
if (sa1.parent == atom2)
return false;
SmilesAtom sa2 = mGraphAtomList.get(mSmilesIndex[atom2]);
if (sa2.parent == atom1)
return true;
return sa2.isOpeningClosureTo(atom1);
}
/**
* Of the not yet used atoms find that atom with the lowest number of neighbour atoms.
* Exception: Atom with zero neighbors are only considered if no other atoms are left.
* If more than one atom with the same neighbor count exist, then the one of them with
* the lowest symmetry rank is returned.
* @return -1 if all atoms were already used
*/
private int findUnusedStartAtom() {
int startAtom = -1;
int startRank = Integer.MAX_VALUE;
for (int atom=0; atom rank) {
startRank = rank;
startAtom = atom;
}
}
}
return startAtom;
}
private boolean hasUnusedNeighborAtom(int atom) {
for (int i=0; i 1)
builder.append("H"+hCount);
}
if (charge != 0) {
builder.append(charge > 0 ? '+' : '-');
if (Math.abs(charge) > 1)
builder.append(Math.abs(charge));
}
if (smartsFeatures != null)
builder.append(smartsFeatures);
if (mapNo != 0) {
builder.append(':');
builder.append(mapNo);
}
if (useBrackets)
builder.append(']');
appendClosureBonds(smilesAtom, builder);
if (smilesAtom.isSideChainEnd)
builder.append(')');
}
private String createMultiAtomLabel(int atom, int[] atomicNo, StringBuilder buffer) {
buffer.setLength(0);
boolean isLowerCase = mMol.isAromaticAtom(atom) && (mMode & MODE_KEKULIZED_OUTPUT) == 0;
for (int a:atomicNo) {
if (buffer.length() != 0)
buffer.append(',');
String label = Molecule.cAtomLabel[a];
buffer.append(isLowerCase ? label.toLowerCase() : label);
}
return buffer.toString();
}
private String getAtomSMARTSFeatures(int atom, StringBuilder buffer) {
buffer.setLength(0);
long queryFeatures = mMol.getAtomQueryFeatures(atom);
// SMARTS don't distinguish between a charged atom and atom charge query features
int chargeFeatures = (int)((queryFeatures & Molecule.cAtomQFCharge) >> Molecule.cAtomQFChargeBits);
switch (chargeFeatures) {
case (int)((Molecule.cAtomQFNotChargeNeg | Molecule.cAtomQFNotChargePos) >> Molecule.cAtomQFChargeBits):
buffer.append("+0"); // 'require negative charge' is translated into charge := -1
break;
case (int)((Molecule.cAtomQFNotCharge0 | Molecule.cAtomQFNotChargePos) >> Molecule.cAtomQFChargeBits):
if (mMol.getAtomCharge(atom) == 0)
buffer.append("-"); // 'require negative charge' is translated into charge := -1
break;
case (int)((Molecule.cAtomQFNotCharge0 | Molecule.cAtomQFNotChargeNeg) >> Molecule.cAtomQFChargeBits):
if (mMol.getAtomCharge(atom) == 0)
buffer.append("+"); // 'require positive charge' is translated into charge := +1
break;
}
long aromState = queryFeatures & Molecule.cAtomQFAromState;
if (aromState == Molecule.cAtomQFAromatic)
buffer.append(";a");
else if (aromState == Molecule.cAtomQFNotAromatic)
buffer.append(";A");
long hydrogenQueryFeatures = queryFeatures & Molecule.cAtomQFHydrogen;
if (hydrogenQueryFeatures != 0) {
if (hydrogenQueryFeatures == (Molecule.cAtomQFNot1Hydrogen | Molecule.cAtomQFNot2Hydrogen | Molecule.cAtomQFNot3Hydrogen))
buffer.append(";H0");
else if (hydrogenQueryFeatures == (Molecule.cAtomQFNot0Hydrogen | Molecule.cAtomQFNot2Hydrogen | Molecule.cAtomQFNot3Hydrogen))
buffer.append(";H1");
else if (hydrogenQueryFeatures == (Molecule.cAtomQFNot0Hydrogen | Molecule.cAtomQFNot1Hydrogen | Molecule.cAtomQFNot3Hydrogen))
buffer.append(";H2");
else if (hydrogenQueryFeatures == (Molecule.cAtomQFNot0Hydrogen | Molecule.cAtomQFNot1Hydrogen | Molecule.cAtomQFNot2Hydrogen))
buffer.append(";H3");
else if (hydrogenQueryFeatures == Molecule.cAtomQFNot0Hydrogen)
buffer.append(";!H0");
else if (hydrogenQueryFeatures == (Molecule.cAtomQFNot0Hydrogen | Molecule.cAtomQFNot1Hydrogen))
buffer.append(";!H0;!H1");
else if (hydrogenQueryFeatures == (Molecule.cAtomQFNot2Hydrogen | Molecule.cAtomQFNot3Hydrogen))
buffer.append(";!H2;!H3");
else if (hydrogenQueryFeatures == Molecule.cAtomQFNot3Hydrogen)
buffer.append(";!H3");
}
// Atom membership of SSSR rings cannot be directly translated into number of ring bonds.
// We try to get close...
long ringState = queryFeatures & Molecule.cAtomQFRingState;
if (ringState == Molecule.cAtomQFNotChain)
buffer.append(";!R0");
else if (ringState == Molecule.cAtomQFNot2RingBonds)
buffer.append(";!R1");
else if (ringState == Molecule.cAtomQFNot3RingBonds)
buffer.append(";!R2");
else if (ringState == Molecule.cAtomQFNot4RingBonds)
buffer.append(";!R3");
else if (ringState == (Molecule.cAtomQFNot2RingBonds | Molecule.cAtomQFNot3RingBonds | Molecule.cAtomQFNot4RingBonds))
buffer.append(";R0");
else if (ringState == (Molecule.cAtomQFNotChain | Molecule.cAtomQFNot3RingBonds | Molecule.cAtomQFNot4RingBonds))
buffer.append(";R1");
else if (ringState == (Molecule.cAtomQFNotChain | Molecule.cAtomQFNot2RingBonds | Molecule.cAtomQFNot4RingBonds))
buffer.append(";R2");
else if (ringState == (Molecule.cAtomQFNotChain | Molecule.cAtomQFNot2RingBonds | Molecule.cAtomQFNot3RingBonds))
buffer.append(";R3");
long ringSize = queryFeatures & Molecule.cAtomQFNewRingSize;
if (ringSize == Molecule.cAtomQFRingSize0)
buffer.append(";!r" + ringSize);
else if (ringSize == (Molecule.cAtomQFNewRingSize & ~Molecule.cAtomQFRingSize0))
buffer.append(";r" + ringSize);
else if (ringSize != 0) {
if ((ringSize & Molecule.cAtomQFRingSizeLarge) != 0) { // negative logic
if ((ringSize & Molecule.cAtomQFRingSize0) == 0)
buffer.append(";!r0" + ringSize);
if ((ringSize & Molecule.cAtomQFRingSize3) == 0)
buffer.append(";!r3" + ringSize);
if ((ringSize & Molecule.cAtomQFRingSize4) == 0)
buffer.append(";!r4" + ringSize);
if ((ringSize & Molecule.cAtomQFRingSize5) == 0)
buffer.append(";!r5" + ringSize);
if ((ringSize & Molecule.cAtomQFRingSize6) == 0)
buffer.append(";!r6" + ringSize);
if ((ringSize & Molecule.cAtomQFRingSize7) == 0)
buffer.append(";!r7" + ringSize);
}
else {
buffer.append(";");
if ((ringSize & Molecule.cAtomQFRingSize0) != 0)
buffer.append("r0," + ringSize);
if ((ringSize & Molecule.cAtomQFRingSize3) != 0)
buffer.append("r3," + ringSize);
if ((ringSize & Molecule.cAtomQFRingSize4) != 0)
buffer.append("r4," + ringSize);
if ((ringSize & Molecule.cAtomQFRingSize5) != 0)
buffer.append("r5," + ringSize);
if ((ringSize & Molecule.cAtomQFRingSize6) != 0)
buffer.append("r6," + ringSize);
if ((ringSize & Molecule.cAtomQFRingSize7) != 0)
buffer.append("r7," + ringSize);
buffer.setLength(buffer.length()-1);
}
}
if (ringSize == 0) {
ringSize = (queryFeatures & Molecule.cAtomQFSmallRingSize) >> Molecule.cAtomQFSmallRingSizeShift;
if (ringSize != 0)
buffer.append(";r" + ringSize);
}
long neighbourFeatures = queryFeatures & Molecule.cAtomQFNeighbours;
if (neighbourFeatures == (Molecule.cAtomQFNeighbours & ~Molecule.cAtomQFNot1Neighbour))
buffer.append(";D1"); // exactly 1
if (neighbourFeatures == (Molecule.cAtomQFNeighbours & ~Molecule.cAtomQFNot2Neighbours))
buffer.append(";D2"); // exactly 2
if (neighbourFeatures == (Molecule.cAtomQFNeighbours & ~Molecule.cAtomQFNot3Neighbours))
buffer.append(";D3"); // exactly 3
if (neighbourFeatures == (Molecule.cAtomQFNot3Neighbours | Molecule.cAtomQFNot4Neighbours))
buffer.append(";!D3;!D4"); // less than 3
if (neighbourFeatures == Molecule.cAtomQFNot4Neighbours)
buffer.append(";!D4"); // less than 4
if (neighbourFeatures == (Molecule.cAtomQFNot0Neighbours | Molecule.cAtomQFNot1Neighbour))
buffer.append(";!D0;!D1"); // more than 1
if (neighbourFeatures == (Molecule.cAtomQFNot0Neighbours | Molecule.cAtomQFNot1Neighbour | Molecule.cAtomQFNot2Neighbours))
buffer.append(";!D0;!D1;!D2"); // more than 2
if (neighbourFeatures == (Molecule.cAtomQFNeighbours & ~Molecule.cAtomQFNot4Neighbours))
buffer.append(";!D0;!D1;!D2;!D3"); // more than 3
if ((queryFeatures & Molecule.cAtomQFNoMoreNeighbours) != 0)
buffer.append(";D"+mMol.getConnAtoms(atom)); // Convert into exact explicit neighbour count 'D'
if ((queryFeatures & Molecule.cAtomQFMoreNeighbours) != 0)
buffer.append(";!D"+mMol.getConnAtoms(atom)); // Convert into exact explicit neighbour count 'D'
return buffer.length() == 0 ? null : buffer.toString();
}
/**
* Don't store nitrogen parities in SMILES unless we have a quarternary nitrogen.
* Some software packages seem to have promblems when decoding parities on nitrogen atoms.
* @param atom
* @return
*/
private boolean qualifiesForAtomParity(int atom) {
return (mMol.getAtomParity(atom) == Molecule.cAtomParity1
|| mMol.getAtomParity(atom) == Molecule.cAtomParity2)
&& !isSingleKnownStereoCenterInESRGroup(atom)
&& (mMol.getAtomicNo(atom) != 7 || mMol.getAtomCharge(atom) > 0);
}
private boolean isSingleKnownStereoCenterInESRGroup(int atom) {
int type = mMol.getAtomESRType(atom) - 1;
return type == -1 ? false : mKnownTHCountInESRGroup[type][mMol.getAtomESRGroup(atom)] <= 1;
}
private void appendClosureBonds(SmilesAtom smilesAtom, StringBuilder builder) {
if (smilesAtom.closureNeighbour != null) {
for (int i=0; i 9)
builder.append('%');
builder.append(mClosureNumber[bond]);
}
}
}
}
}
private void appendBondOrderSymbol(int bond, int parentAtom, StringBuilder builder) {
int startLength = builder.length();
if (mEZHalfParity[bond] != 0)
builder.append(mEZHalfParity[bond] == 1 ? '/' : '\\');
if (mMode == MODE_CREATE_SMARTS) {
int bondTypes = mMol.getBondQueryFeatures(Molecule.cBondQFBondTypes | Molecule.cBondQFRareBondTypes);
if (bondTypes != 0) {
if ((bondTypes & Molecule.cBondTypeSingle) != 0 && mEZHalfParity[bond] == 0) {
builder.append('-');
}
if ((bondTypes & Molecule.cBondTypeDouble) != 0) {
if (builder.length() != startLength)
builder.append(',');
builder.append('=');
}
if ((bondTypes & Molecule.cBondTypeTriple) != 0) {
if (builder.length() != startLength)
builder.append(',');
builder.append('#');
}
if ((bondTypes & Molecule.cBondTypeQuadruple) != 0) {
if (builder.length() != startLength)
builder.append(',');
builder.append('$');
}
if ((bondTypes & Molecule.cBondTypeQuintuple) != 0) { // SMILES doesn't support quintuple bonds, thus we use quadruple
if (builder.length() != startLength)
builder.append(',');
builder.append('$');
}
if ((bondTypes & Molecule.cBondTypeDelocalized) != 0) {
if (builder.length() != startLength)
builder.append(',');
builder.append(':');
}
if ((bondTypes & Molecule.cBondTypeMetalLigand) != 0) {
if (builder.length() != startLength)
builder.append(',');
builder.append(mMol.isMetalAtom(parentAtom) ? "<-" : "->");
}
}
}
if (startLength == builder.length() && (!mMol.isAromaticBond(bond) || (mMode & MODE_KEKULIZED_OUTPUT) != 0)) {
int bondType = mMol.getBondType(bond) & Molecule.cBondTypeMaskSimple;
if (bondType == Molecule.cBondTypeSingle) {
if (mMol.isAromaticAtom(mMol.getBondAtom(0, bond))
&& mMol.isAromaticAtom(mMol.getBondAtom(1, bond))
&& (mMode & MODE_KEKULIZED_OUTPUT) == 0
&& mEZHalfParity[bond] == 0)
builder.append('-');
} else if (bondType == Molecule.cBondTypeDouble)
builder.append('=');
else if (bondType == Molecule.cBondTypeTriple)
builder.append('#');
else if (bondType == Molecule.cBondTypeQuadruple)
builder.append('$');
else if (bondType == Molecule.cBondTypeQuintuple) // not supported by SMILES, we use quadruple instead
builder.append('$');
else if (bondType == Molecule.cBondTypeDelocalized)
builder.append(':');
else if (bondType == Molecule.cBondTypeMetalLigand)
builder.append(mMol.isMetalAtom(parentAtom) ? "<-" : "->");
}
if (mMode == MODE_CREATE_SMARTS) {
String gap = (startLength == builder.length()) ? "" : ";";
int ringState = mMol.getBondQueryFeatures(bond) & Molecule.cBondQFRingState;
if (ringState == Molecule.cBondQFRing)
builder.append(gap+"@");
else if (ringState == Molecule.cBondQFNotRing)
builder.append(gap+"!@");
}
}
private String getAtomParitySymbol(int atom, int parent) {
boolean inversion = false;
if (mMol.getAtomPi(atom) != 0
&& mMol.getConnAtoms(atom) == 2
&& mMol.getConnBondOrder(atom,0) == 2
&& mMol.getConnBondOrder(atom,1) == 2) { // allene parities
for (int i=0; i neighborAtom[i])
inversion = !inversion;
if (neighborRank[j] > neighborRank[i])
inversion = !inversion;
}
}
return inversion;
}
/**
* @param atom for which to return a neighbor's smiles rank
* @param neighbourIndex index for getConnAtoms() to get neighbor atom and bond
* @return neighbor's position rank in smiles from a perspective of atom using closure digit positions for closure neighbors
*/
private int getSmilesRank(int atom, int neighbourIndex) {
int bond = mMol.getConnBond(atom, neighbourIndex);
int neighbour = mMol.getConnAtom(atom, neighbourIndex);
if (mClosureNumber[bond] != 0) {
// if neighbour is attached via a closure digit, then the rank is based primarily on atom's position
// in the smiles and secondary on the count of other closures at atom that precede this closure
int rank = 8 * mSmilesIndex[atom] + 1;
int[] closureNeighbour = mGraphAtomList.get(mSmilesIndex[atom]).closureNeighbour;
for (int i=0; i= 5 && atomicNo <= 9) // B,C,N,O,F
|| (atomicNo >= 15 && atomicNo <= 17) // P,S,Cl
|| atomicNo == 35 // Br
|| atomicNo == 53; // I
}
}
class SmilesAtom {
public int atom,bond,parent;
public boolean isSideChainStart,isSideChainEnd;
public int[] closureNeighbour;
public boolean[] closureOpens;
public SmilesAtom(int atom, int bond, int parent, boolean isSideChainStart, boolean isSideChainEnd) {
this.atom = atom;
this.bond = bond;
this.parent = parent;
this.isSideChainStart = isSideChainStart;
this.isSideChainEnd = isSideChainEnd;
}
public boolean isOpeningClosureTo(int atom) {
if (closureNeighbour != null)
for (int i=0; i