Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Class to fix bond orders for aromatic rings

Signed-off-by: ngn <jeliazkova.nina@gmail.com>
  • Loading branch information...
commit 36d6682729319dd462880e698a928f03be08cb4e 1 parent d71024e
Kevin Lawson authored committed
View
564 src/main/org/openscience/cdk/smiles/FixBondOrdersTool.java
@@ -0,0 +1,564 @@
+package org.openscience.cdk.smiles;
@egonw Owner
egonw added a note

Missing header like below, but need completing with email addresses:

+/* Copyright (C) 2012 Kevin Lawson <>

  • * Lucy Entwistle <>
  • *
  • * Contact: cdk-devel@lists.sourceforge.net
  • *
  • * This program is free software; you can redistribute it and/or
  • * modify it under the terms of the GNU Lesser General Public License
  • * as published by the Free Software Foundation; either version 2.1
  • * of the License, or (at your option) any later version.
  • *
  • * This program is distributed in the hope that it will be useful,
  • * but WITHOUT ANY WARRANTY; without even the implied warranty of
  • * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • * GNU Lesser General Public License for more details.
  • *
  • * You should have received a copy of the GNU Lesser General Public License
  • * along with this program; if not, write to the Free Software
  • * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  • */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.openscience.cdk.CDKConstants;
+import org.openscience.cdk.annotations.TestMethod;
+import org.openscience.cdk.exception.CDKException;
+import org.openscience.cdk.interfaces.IAtom;
+import org.openscience.cdk.interfaces.IAtomContainer;
+import org.openscience.cdk.interfaces.IAtomType.Hybridization;
+import org.openscience.cdk.interfaces.IBond;
+import org.openscience.cdk.interfaces.IMolecule;
+import org.openscience.cdk.interfaces.IRing;
+import org.openscience.cdk.interfaces.IRingSet;
+import org.openscience.cdk.ringsearch.SSSRFinder;
+
+/**
+ *
+ * Class to Fix bond orders
+ * at present for Aromatic Rings only
@egonw Owner
egonw added a note

the first line in JavaDoc must end with a period, to allow JavaDoc to detect the first sentence for summaries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ *
+ * Analyses which rings are marked aromatic/SP2
@egonw Owner
egonw added a note

JavaDoc is HTML; please use

    and
  • for this list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * Splits rings into groups containing independent sets of single/fused rings
+ * Loops over each ring group
+ * Uses an adjacency matrix of bonds (rows) and atoms (columns) to represent each fused ring system
+ * Scans the adjacency matrix for bonds for which there is no order choice (eg - both bonds to the NH of pyrrole must be single)
+ * All choices made to match valency against bonds used (including implicit H atoms)
+ * Solves other bonds as possible - dependent on previous choices - makes free (random) choices only where necessary and possible
+ * Makes assumption that where there is a choice in bond order (not forced by previous choices) - either choice is consistent with correct solution
+ *
+ * Requires molecule with all rings to be solved being marked aromatic (SP2 atoms). All bonds to non-ring atoms need to be fully defined (including implicit H atoms)
+ *
+ * @author Kevin Lawson
+ * @author Lucy Entwistle
@egonw Owner
egonw added a note

please add "@cdk.module smiles" here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ */
@egonw Owner
egonw added a note

Please add @TestClass("org.openscience.cdk.smiles.FixBondOrdersToolTest") here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+public class FixBondOrdersTool {
+
+ //private AllRingsFinder allRingsFinder;
+ //private static ILoggingTool logger = LoggingToolFactory.createLoggingTool(FixBondOrdersTool.class);
+ private boolean interrupted;
@egonw Owner
egonw added a note

please remove outcommented code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ private static class Matrix {
+ private int[] mArray;
+ private int rowCount;
+ private int columnCount;
+ public Matrix (Integer rows, Integer cols){
+
+ //Single array of size rows * cols in matrix
+ mArray = new int[rows * cols];
+
+ //Record no of rows and number of columns
+ rowCount = rows;
+ columnCount = cols;
+ }
+ public void set (Integer rIndex, Integer cIndex, Integer val){
+ mArray[rIndex * columnCount + cIndex] = val;
+ }
+ public Integer get(Integer rIndex, Integer cIndex){
+ return mArray[rIndex * columnCount + cIndex];
+ }
+ public Integer colIndexOf(Integer colIndex, Integer val){
+ for (int i = 0; i < rowCount; i++){
+ if(mArray[i * columnCount + colIndex] == val)
+ return i;
+ }
+ return -1;
+ }
+ public Integer rowIndexOf(Integer rowIndex, Integer val){
+ for(int i = 0; i < columnCount; i++){
+ if (mArray[rowIndex * getCols() + i] == val)
+ return i;
+ }
+ return -1;
+ }
+ public Integer sumOfRow(Integer rowIndex){
+ Integer sumOfRow = 0;
+ for (int i = 0; i < columnCount; i++){
+ sumOfRow += mArray[rowIndex * columnCount + i];
+ }
+ return sumOfRow;
+ }
+ public Integer getRows(){
+ return rowCount;
+ }
+ public Integer getCols(){
+ return columnCount;
+ }
+ }
+
+ /**
+ * Constructor for the FixBondOrdersTool object.
+ */
+ public FixBondOrdersTool() {
+ //allRingsFinder = new AllRingsFinder();
+ }
+
+ /**
+ * Determines if, according to the algorithms implemented in this class, the given
+ * molecule has properly distributed double bonds.
+ *
+ * @param m {@link IMolecule} to check the bond orders for.
+ * @return true, if bond orders are properly distributed
+ * @throws CDKException thrown when something went wrong
+ */
@egonw Owner
egonw added a note

Where does this JavaDoc belong to?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+
+ /**
+ * @throws CDKException if something went wrong.
@egonw Owner
egonw added a note

Please have full JavaDoc for all public methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ */
+ public IMolecule kekuliseAromaticRings(IMolecule molecule) throws CDKException {
@egonw Owner
egonw added a note

Please add @TestMethod("xxx") here, where 'xxx' points to a test method in your test class that executes this method.

Like you do on line 548.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ IRingSet ringSet;
+
+ try {
+ ringSet = removeExtraRings(molecule);
+ } catch (CDKException x) {
+ throw x;
+ } catch (Exception x) {
+ throw new CDKException("failure in SSSRFinder.findAllRings",x);
+ }
+
+ if (ringSet == null) {
+ throw new CDKException("failure in SSSRFinder.findAllRings");
+ }
+
+ //We need to establish which rings share bonds and set up sets of such interdependant rings
+ List<Integer[]> rBondsArray = null;
+ List<List<Integer>> ringGroups = null;
+
+ //Start by getting a list (same dimensions and ordering as ringset) of all the ring bond numbers in the reduced ring set
+ rBondsArray = getRingSystem(molecule, ringSet);
+
+ //Now find out which share a bond and assign them accordingly to groups
+ ringGroups = assignRingGroups(rBondsArray);
+
+ //Loop through each group of rings checking all choices of double bond combis and seeing if you can get a
+ //proper molecule.
+ for (int i = 0; i < ringGroups.size(); i++) {
+
+ //Set all ring bonds with single order to allow Matrix solving to work
+ setAllRingBondsSingleOrder(ringGroups.get(i), ringSet);
+
+ //Set up lists of atoms, bonds and atom pairs for this ringGroup
+ List<Integer> atomNos = null;
+ atomNos = getAtomNosForRingGroup(molecule, ringGroups.get(i), ringSet);
+
+ List<Integer> bondNos = null;
+ bondNos = getBondNosForRingGroup(molecule, ringGroups.get(i), ringSet);
+
+ //Array of same dimensions as bondNos (cols in Matrix)
+ List<Integer[]> atomNoPairs = null;
+ atomNoPairs = getAtomNoPairsForRingGroup(molecule, bondNos);
+
+ //Set up ajacency Matrix
+ Matrix M = new Matrix(atomNos.size(), bondNos.size());
+ for(int x = 0; x < M.getRows(); x++){
+ for(int y = 0; y < M.getCols(); y++){
+ if(atomNos.get(x) == atomNoPairs.get(y)[0])
+ M.set(x,y,1);
+ else {
+ if(atomNos.get(x) == atomNoPairs.get(y)[1])
+ M.set(x,y,1);
+ else
+ M.set(x,y,0);
+ }
+ }
+ }
+
+ //Array of same dimensions as atomNos (rows in Matrix)
+ List<Integer> freeValencies = null;
+ freeValencies = getFreeValenciesForRingGroup(molecule, atomNos, M);
+
+ //Array of "answers"
+ List<Integer> bondOrders = new ArrayList<Integer>();
+ for (int j = 0; j < bondNos.size(); j++){
+ bondOrders.add(0);
+ }
+
+ if (solveMatrix( M, atomNos, bondNos, freeValencies, atomNoPairs, bondOrders)){
+ for (int j = 0; j < bondOrders.size(); j++){
+ molecule.getBond(bondNos.get(j)).setOrder(bondOrders.get(j)==1?IBond.Order.SINGLE:IBond.Order.DOUBLE);
+ }
+ }
+ else {
+// TODO Put any failure code here
+ }
+ }
+ return molecule;
+ }
+
+ /**
+ * Remove rings.
+ * <p/>
+ * Removes rings which do not have all sp2/planar3 aromatic atoms and also gets rid of rings that have more than
+ * 8 atoms in them.
+ *
+ * @param m The molecule from which we want to remove rings
+ * @return The set of reduced rings
+ */
+ private IRingSet removeExtraRings(IMolecule m) throws Exception {
+
+ SSSRFinder arf = new SSSRFinder(m);
+ IRingSet rs = arf.findSSSR();
+
+ //remove rings which dont have all aromatic atoms (according to hybridization set by lower case symbols in smiles):
+
+ //logger.debug("numrings="+rs.size());
+
+ Iterator<IAtomContainer> i = rs.atomContainers().iterator();
+ while (i.hasNext()) {
+ IRing r = (IRing) i.next();
+ if (r.getAtomCount() > 8) {
+ i.remove();
+ } else {
+ for (IAtom a: r.atoms()) {
+ Hybridization h = a.getHybridization();
+ if (h == CDKConstants.UNSET ||
+ !(h == Hybridization.SP2 ||
+ h == Hybridization.PLANAR3)) {
+ i.remove();
+ break;
+ }
+ }
+ }
+ }
+ return rs;
+
+ }
+
+ /**
+ * Stores an IRingSet corresponding to a molecule using the bond numbers.
@egonw Owner
egonw added a note

You can also use this, which creates a link in the JavaDoc:

{@link IRingSet}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ *
+ * @param mol The IMolecule for which to store the IRingSet.
+ * @param ringSet The IRingSet to store
+ */
@egonw Owner
egonw added a note

A @return statement is missing in the JavaDoc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ private List<Integer[]> getRingSystem(IMolecule mol, IRingSet ringSet) {
+ List<Integer[]> bondsArray;
+ bondsArray= new ArrayList<Integer[]>();
+ for (int r = 0; r < ringSet.getAtomContainerCount(); ++r) {
+ IRing ring = (IRing)ringSet.getAtomContainer(r);
+ Integer[] bondNumbers = new Integer[ring.getBondCount()];
+ for (int i = 0; i < ring.getBondCount(); ++i)
+ bondNumbers[i] = mol.getBondNumber(ring.getBond(i));
+ bondsArray.add(bondNumbers);
+ }
+ return bondsArray;
+ }
+
+ /**
+ * Assigns a set of rings to groups each sharing a bond
@egonw Owner
egonw added a note

Missing period at the end of the first JavaDoc line (see the earlier comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * @param rBondsArray
+ * @param ringGroups
@egonw Owner
egonw added a note

Missing @return statement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ */
+ private List<List<Integer>> assignRingGroups(List<Integer[]> rBondsArray){
+ List<List<Integer>> ringGroups;
+ ringGroups = new ArrayList<List<Integer>>();
+ for(int i = 0; i < rBondsArray.size()-1; i++){ //for each ring except the last in rBondsArray
+ for(int j = 0; j < rBondsArray.get(i).length; j++){ //for each bond in each ring
+
+ //check there's no shared bond with any other ring already in ringGroups
+ for(int k = i+1; k < rBondsArray.size(); k++){
+ for(int l = 0; l < rBondsArray.get(k).length; l++){ //for each ring in each ring
+
+ //Is there a bond in common? Then add both rings
+ if(rBondsArray.get(i)[j] == rBondsArray.get(k)[l]){
+ if(i!=k){
+ ringGroups.add(new ArrayList<Integer>());
+ ringGroups.get(ringGroups.size()-1).add(i);
+ ringGroups.get(ringGroups.size()-1).add(k);
+ }
+ }
+ }
+ }
+ }
+ }
+ while(combineGroups(ringGroups));
+
+ //Anything not added yet is a singleton
+ for(int i = 0; i < rBondsArray.size(); i++){
+ boolean found = false;
+ for (int j = 0; j < ringGroups.size(); j++){
+ if(ringGroups.get(j).contains(i)){
+ found = true;
+ break;
+ }
+ }
+ if(!found){
+ ringGroups.add(new ArrayList<Integer>());
+ ringGroups.get(ringGroups.size()-1).add(i);
+ }
+ }
+ return ringGroups;
+ }
+ /**
+ *
+ * @param ringGroups
+ * @return
@egonw Owner
egonw added a note

Remove incomplete JavaDoc if none is required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ */
+ private Boolean combineGroups(List<List<Integer>> ringGroups) {
+ for (int i = 0; i < ringGroups.size() - 1; i++) {
+
+ //Look for another group to combine with it
+ for (int j = i + 1; j < ringGroups.size(); j++) {
+ for (int k = 0; k < ringGroups.get(j).size(); k++) {
+ if (ringGroups.get(i).contains(ringGroups.get(j).get(k))) {
+
+ //Add all the new elements
+ for (int l = 0; l < ringGroups.get(j).size(); l++) {
+ if (!ringGroups.get(i).contains(ringGroups.get(j).get(l))) {
+ ringGroups.get(i).add(ringGroups.get(j).get(l));
+ }
+ }
+ ringGroups.remove(j);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private Boolean setAllRingBondsSingleOrder(List<Integer> ringGroup, IRingSet ringSet){
+ for(Integer i: ringGroup){
+ for(IBond bond: ringSet.getAtomContainer(i).bonds()){
+ bond.setOrder(IBond.Order.SINGLE);
+ }
+ }
+ return true;
+ }
+ private List getAtomNosForRingGroup(IMolecule molecule, List<Integer> ringGroup, IRingSet ringSet){
+ List atc = new ArrayList<Integer>();
+ for(Integer i: ringGroup){
+ for(IAtom atom: ringSet.getAtomContainer(i).atoms()){
+ if(atc.size() > 0) {
+ if(!atc.contains(molecule.getAtomNumber(atom)))
+ atc.add(molecule.getAtomNumber(atom));
+ } else
+ atc.add(molecule.getAtomNumber(atom));
+ }
+ }
+ return atc;
+ }
+ private List getBondNosForRingGroup(IMolecule molecule, List<Integer> ringGroup, IRingSet ringSet){
+ List btc = new ArrayList<Integer>();
+ for(Integer i: ringGroup){
+ for(IBond bond: ringSet.getAtomContainer(i).bonds()){
+ if(btc.size() > 0) {
+ if(!btc.contains(molecule.getBondNumber(bond)))
+ btc.add(molecule.getBondNumber(bond));
+ } else
+ btc.add(molecule.getBondNumber(bond));
+ }
+ }
+ return btc;
+ }
+ private List getAtomNoPairsForRingGroup(IMolecule molecule, List<Integer> bondsToCheck){
+ List aptc = new ArrayList<Integer[]>();
+ for (Integer i: bondsToCheck){
+ Integer[] aps = new Integer[2];
+ aps[0] = molecule.getAtomNumber(molecule.getBond(i).getAtom(0));
+ aps[1] = molecule.getAtomNumber(molecule.getBond(i).getAtom(1));
+ aptc.add(aps);
+ }
+ return aptc;
+ }
+ /**
+ * Function to set up an array of integers corresponding to indicate how many free valencies need fulfilling for each atom through ring bonds
+ * @param molecule
+ * @param atomsToCheck
+ * @param M
+ * @return
+ */
+ private List getFreeValenciesForRingGroup (IMolecule molecule, List<Integer> atomsToCheck, Matrix M){
+ List fvtc = new ArrayList<Integer>();
+ for( int i = 0; i < atomsToCheck.size(); i++){
+ int j = atomsToCheck.get(i);
+ int implicitH = molecule.getAtom(j).getImplicitHydrogenCount()==null?0:molecule.getAtom(j).getImplicitHydrogenCount();
+ fvtc.add(molecule.getAtom(j).getValency() -
+ (implicitH +
+ (int)molecule.getBondOrderSum(molecule.getAtom(j))) + M.sumOfRow(i));
+ }
+ return fvtc;
+ }
+ /**
+ * Function to solve the adjacency Matrix. Returns true/false on success/failure. Passed a reference to an array of bond orders to be filled in
+ *
+ * Passed a setup Matrix M indicating the atoms that are part of each bond.
+ * The system v = Mb represents the set of equations: valence[atomA] = SUM OF ( M[A][B]*bondOrder[bondB] )
+ * where M[A][B] = 1 if atom A is part of bond B, and M[A][B] = 0 otherwise. Use the system to solve bondOrder.
+ * For example if atom 1 has free valence 2, and is part of bonds 5 and 6, we know that B5 = 1, B6 = 1
+ * if then also, atom 2 has free valence 3, and is part of bond 5 and bond 9, we know, from the solved equation above
+ * that B9 = 2. And so forth.
+ *
+ * If nothing can be deduced from previously solved equations, the code assigns a 1 to the first unknown bond
+ * it finds in the bondOrder array and continues.
+ *
+ * @param M
+ * @param atomNos
+ * @param bondNos
+ * @param freeValencies
+ * @param atomNoPairs
+ * @param bondOrder
+ * @return
+ */
+ private Boolean solveMatrix(Matrix M, List<Integer> atomNos, List<Integer> bondNos, List<Integer> freeValencies, List<Integer[]> atomNoPairs, List<Integer> bondOrder){
+
+ // Look for bonds that need to be a certain order
+ List<Integer> solved = new ArrayList<Integer>();
+ List<Integer> solvedRow = new ArrayList<Integer>();
+ for (int j = 0; j < atomNos.size(); j++){
+
+ // Count no.of bonds for this atom
+ int sumOfRow = M.sumOfRow(j);
+
+ // Atom with no of bonds equal to its valence - all must be single bonds.
+ if (sumOfRow == freeValencies.get(j)){
+ for (int k = 0; k < bondNos.size(); k++){
+ if (M.get(j,k) == 1){
+ bondOrder.set(k,1);
+ solved.add(k);
+ }
+ }
+ solvedRow.add(j);
+ }
+ // Atom with only one bond - bond must be equal to atom valence.
+ else if (sumOfRow == 1){
+ for (int k = 0; k < bondNos.size(); k++){
+ if (M.get(j,k) == 1){
+ bondOrder.set(k,freeValencies.get(j));
+ solved.add(k);
+ }
+ }
+ solvedRow.add(j);
+ }
+ }
+
+ /* thisRun indicates whether any bonds have been solved on this run through the Matrix. Loop continues
+ until all bonds have been solved or there is a run where no bonds were solved, showing that
+ the structure is unsolvable. */
+ int thisRun = 1;
+ while (solvedRow.size() != M.getRows() && thisRun == 1){
+ thisRun = 0;
+ if (solved.size() > 0){
+ for (int j = 0; j < M.getRows(); j++){
+ if (solvedRow.contains(j)==false){
+ int unknownBonds = 0;
+ int knownBondTotal = 0;
+ for (int k = 0; k < bondNos.size();k++){
+ if (M.get(j,k) == 1){
+ if (solved.contains(k)){
+ knownBondTotal += bondOrder.get(k);
+ }
+ else{
+ unknownBonds++;
+ }
+ }
+ }
+
+ // have any bonds for this atom been solved?
+ if(unknownBonds == 0){
+ solvedRow.add(j);
+ thisRun = 1;
+ }
+ else {
+ if (knownBondTotal != 0){
+ if (unknownBonds == freeValencies.get(j) - knownBondTotal){
+
+ // all remaining bonds must be single
+ for (int k = 0; k < bondNos.size(); k++){
+ if (M.get(j,k) == 1 && solved.contains(k) == false){
+ bondOrder.set(k,1);
+ solved.add(k);
+ }
+ }
+ solvedRow.add(j);
+ thisRun = 1;
+ }
+ else if (unknownBonds == 1){
+
+ // only one unsolved bond, so must equal remaining free valence
+ for (int k = 0; k < bondNos.size(); k++){
+ if (M.get(j,k) == 1 && solved.contains(k) == false){
+ bondOrder.set(k,freeValencies.get(j)-knownBondTotal);
+ solved.add(k);
+ }
+ }
+ solvedRow.add(j);
+ thisRun = 1;
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+ // If we can't solve any bonds from the information we have so far, there must be a choice to make.
+ // Pick a bond that is yet to be solved and set it as a single bond.
+ if (thisRun == 0){
+ int ring = 1;
+ int j = 0;
+ while (ring == 1 && j < bondNos.size()){
+ int badChoice = 0;
+ if (solvedRow.contains(atomNos.indexOf(atomNoPairs.get(j)[0]))){
+ badChoice = 1;
+ }
+ if (solvedRow.contains(atomNos.indexOf(atomNoPairs.get(j)[1]))){
+ badChoice = 1;
+ }
+ if (bondOrder.get(j) == 0 && badChoice == 0){
+// javax.swing.JOptionPane.showMessageDialog(null, j);
+ bondOrder.set(j,1);
+ ring = 0;
+ thisRun = 1;
+ solved.add(j);
+ }
+ j++;
+ }
+ }
+ }
+ if (solvedRow.size() != M.getRows())
+ return false;
+ else {
+ int errorFound = 0;
+ for (int j = 0; j < atomNos.size(); j++){
+ int checker = 0;
+ for (int k = 0; k < bondNos.size(); k++){
+ checker += M.get(j,k)*bondOrder.get(k);
+ }
+ if (checker != freeValencies.get(j)){
+ errorFound = 1;
+ }
+ }
+ if (errorFound == 1){
+ return false;
+ }
+ else
+ return true;
+ }
+
+ }
+ /**
+ * Sets if the calculation should be interrupted.
+ *
+ * @param interrupted true, if the calculation should be cancelled
+ */
+ @TestMethod("testInterruption")
+ public void setInterrupted(boolean interrupted) {
+ this.interrupted = interrupted;
+ }
+
+ /**
+ * Returns if the next or running calculation should be interrupted.
+ *
+ * @return true or false
+ */
+ @TestMethod("testInterruption")
+ public boolean isInterrupted() {
+ return this.interrupted;
+ }
+
+
+}
View
445 src/test/org/openscience/cdk/smiles/FixBondOrdersToolTest.java
@@ -0,0 +1,445 @@
+/* $Revision$ $Author$ $Date$
@egonw Owner
egonw added a note

This line with these three fields is from our Subversion times, and can be removed. Just start with the first 'Copyright' line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ *
+ * Copyright (C) 2006-2007 Rajarshi Guha <rajarshi@users.sf.net>
@egonw Owner
egonw added a note

Please add yourself here too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ *
+ * Contact: cdk-devel@lists.sourceforge.net
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package org.openscience.cdk.smiles;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openscience.cdk.CDKConstants;
+import org.openscience.cdk.CDKTestCase;
+import org.openscience.cdk.DefaultChemObjectBuilder;
+import org.openscience.cdk.config.Elements;
+import org.openscience.cdk.interfaces.IAtom;
+import org.openscience.cdk.interfaces.IAtomType.Hybridization;
+import org.openscience.cdk.interfaces.IBond;
+import org.openscience.cdk.interfaces.IBond.Order;
+import org.openscience.cdk.interfaces.IMolecule;
+import org.openscience.cdk.nonotify.NNAtom;
+import org.openscience.cdk.nonotify.NNBond;
+import org.openscience.cdk.nonotify.NNMolecule;
+import org.openscience.cdk.ringsearch.AllRingsFinder;
+import org.openscience.cdk.silent.SilentChemObjectBuilder;
+import org.openscience.cdk.tools.CDKHydrogenAdder;
+import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;
+
+/**
+ *
+ * @author Rajarshi Guha
+ * @cdk.created 2006-09-18
+ * @cdk.module test-smiles
+ */
+public class FixBondOrdersToolTest extends CDKTestCase {
+
+ private static FixBondOrdersTool fbot;
+
+ @BeforeClass public static void setup() {
+ fbot = new FixBondOrdersTool();
+ }
+
+ @Test public void testConstructors() {
+ // basically: just test that no exception is thrown
+ Assert.assertNotNull(new FixBondOrdersTool());
+ }
+
+ @Test public void testInterruption() {
+ fbot.setInterrupted(false);
+ Assert.assertFalse(fbot.isInterrupted());
+ fbot.setInterrupted(true);
+ Assert.assertTrue(fbot.isInterrupted());
+ fbot.setInterrupted(false);
+ }
+
+ @Test(timeout=1000)
+ public void testPyrrole() throws Exception {
+ String smiles = "c2ccc3n([H])c1ccccc1c3(c2)";
+ SmilesParser smilesParser = new SmilesParser(DefaultChemObjectBuilder.getInstance());
+ IMolecule molecule = smilesParser.parseSmiles(smiles);
+
+ molecule = fbot.kekuliseAromaticRings(molecule);
+ Assert.assertNotNull(molecule);
+
+ molecule = (IMolecule) AtomContainerManipulator.removeHydrogens(molecule);
+ int doubleBondCount = 0;
+ for (int i = 0; i < molecule.getBondCount(); i++) {
+ IBond bond = molecule.getBond(i);
+ Assert.assertTrue(bond.getFlag(CDKConstants.ISAROMATIC));
+ if (bond.getOrder() == Order.DOUBLE) doubleBondCount++;
+ }
+ Assert.assertEquals(6, doubleBondCount);
+ }
+
+ @Test(timeout=1000)
+ public void testPyrrole_Silent() throws Exception {
+ String smiles = "c2ccc3n([H])c1ccccc1c3(c2)";
+ SmilesParser smilesParser = new SmilesParser(SilentChemObjectBuilder.getInstance());
+ IMolecule molecule = smilesParser.parseSmiles(smiles);
+
+ molecule = fbot.kekuliseAromaticRings(molecule);
+ Assert.assertNotNull(molecule);
+
+ molecule = (IMolecule) AtomContainerManipulator.removeHydrogens(molecule);
+ int doubleBondCount = 0;
+ for (int i = 0; i < molecule.getBondCount(); i++) {
+ IBond bond = molecule.getBond(i);
+ Assert.assertTrue(bond.getFlag(CDKConstants.ISAROMATIC));
+ if (bond.getOrder() == Order.DOUBLE) doubleBondCount++;
+ }
+ Assert.assertEquals(6, doubleBondCount);
+ }
+
+ @Test
+ public void testLargeRingSystem() throws Exception {
+ String smiles = "O=C1Oc6ccccc6(C(O)C1C5c2ccccc2CC(c3ccc(cc3)c4ccccc4)C5)";
+ SmilesParser smilesParser = new SmilesParser(DefaultChemObjectBuilder.getInstance());
+ IMolecule molecule = smilesParser.parseSmiles(smiles);
+
+ DeduceBondSystemTool dbst = new DeduceBondSystemTool(new AllRingsFinder());
+ molecule = dbst.fixAromaticBondOrders(molecule);
+ Assert.assertNotNull(molecule);
+
+ molecule = (IMolecule) AtomContainerManipulator.removeHydrogens(molecule);
+ Assert.assertEquals(34, molecule.getAtomCount());
+
+ // we should have 14 double bonds
+ int doubleBondCount = 0;
+ for (int i = 0; i < molecule.getBondCount(); i++) {
+ IBond bond = molecule.getBond(i);
+ if (bond.getOrder() == Order.DOUBLE) doubleBondCount++;
+ }
+ Assert.assertEquals(13, doubleBondCount);
+ }
+
+ /**
+ * @cdk.bug 3506770
+ */
+ @Test
+ public void testLargeBioclipseUseCase() throws Exception {
+ String smiles = "COc1ccc2[C@@H]3[C@H](COc2c1)C(C)(C)OC4=C3C(=O)C(=O)C5=C4OC(C)(C)[C@@H]6COc7cc(OC)ccc7[C@H]56";
+ SmilesParser smilesParser = new SmilesParser(DefaultChemObjectBuilder.getInstance());
+ IMolecule molecule = smilesParser.parseSmiles(smiles);
+
+ DeduceBondSystemTool dbst = new DeduceBondSystemTool(new AllRingsFinder());
+ molecule = dbst.fixAromaticBondOrders(molecule);
+ Assert.assertNotNull(molecule);
+
+ molecule = (IMolecule) AtomContainerManipulator.removeHydrogens(molecule);
+ Assert.assertEquals(40, molecule.getAtomCount());
+
+ // we should have 14 double bonds
+ int doubleBondCount = 0;
+ for (int i = 0; i < molecule.getBondCount(); i++) {
+ IBond bond = molecule.getBond(i);
+ if (bond.getOrder() == Order.DOUBLE) doubleBondCount++;
+ }
+ Assert.assertEquals(10, doubleBondCount);
+ }
+
+ @Test(timeout=1000)
+ public void testPyrrole_CustomRingFinder() throws Exception {
+ String smiles = "c2ccc3n([H])c1ccccc1c3(c2)";
+ SmilesParser smilesParser = new SmilesParser(DefaultChemObjectBuilder.getInstance());
+ IMolecule molecule = smilesParser.parseSmiles(smiles);
+
+ DeduceBondSystemTool dbst = new DeduceBondSystemTool(
+ new AllRingsFinder()
+ );
+ molecule = dbst.fixAromaticBondOrders(molecule);
+ Assert.assertNotNull(molecule);
+
+ molecule = (IMolecule) AtomContainerManipulator.removeHydrogens(molecule);
+ int doubleBondCount = 0;
+ for (int i = 0; i < molecule.getBondCount(); i++) {
+ IBond bond = molecule.getBond(i);
+ Assert.assertTrue(bond.getFlag(CDKConstants.ISAROMATIC));
+ if (bond.getOrder() == Order.DOUBLE) doubleBondCount++;
+ }
+ Assert.assertEquals(6, doubleBondCount);
+ }
+
+ /**
+ * @cdk.inchi InChI=1/C6H4O2/c7-5-1-2-6(8)4-3-5/h1-4H
+ */
+ @Test public void xtestQuinone() throws Exception {
+ IMolecule enol = new NNMolecule();
+
+ // atom block
+ IAtom atom1 = new NNAtom(Elements.CARBON);
+ atom1.setHybridization(Hybridization.SP2);
+ IAtom atom2 = new NNAtom(Elements.CARBON);
+ atom2.setHybridization(Hybridization.SP2);
+ IAtom atom3 = new NNAtom(Elements.CARBON);
+ atom3.setHybridization(Hybridization.SP2);
+ IAtom atom4 = new NNAtom(Elements.CARBON);
+ atom4.setHybridization(Hybridization.SP2);
+ IAtom atom5 = new NNAtom(Elements.CARBON);
+ atom5.setHybridization(Hybridization.SP2);
+ IAtom atom6 = new NNAtom(Elements.CARBON);
+ atom6.setHybridization(Hybridization.SP2);
+ IAtom atom7 = new NNAtom(Elements.OXYGEN);
+ atom7.setHybridization(Hybridization.SP2);
+ IAtom atom8 = new NNAtom(Elements.OXYGEN);
+ atom8.setHybridization(Hybridization.SP2);
+
+ // bond block
+ IBond bond1 = new NNBond(atom1, atom2);
+ IBond bond2 = new NNBond(atom2, atom3);
+ IBond bond3 = new NNBond(atom3, atom4);
+ IBond bond4 = new NNBond(atom4, atom5);
+ IBond bond5 = new NNBond(atom5, atom6);
+ IBond bond6 = new NNBond(atom6, atom1);
+ IBond bond7 = new NNBond(atom7, atom1);
+ IBond bond8 = new NNBond(atom8, atom4);
+
+ enol.addAtom(atom1);
+ enol.addAtom(atom2);
+ enol.addAtom(atom3);
+ enol.addAtom(atom4);
+ enol.addAtom(atom5);
+ enol.addAtom(atom6);
+ enol.addAtom(atom7);
+ enol.addAtom(atom8);
+ enol.addBond(bond1);
+ enol.addBond(bond2);
+ enol.addBond(bond3);
+ enol.addBond(bond4);
+ enol.addBond(bond5);
+ enol.addBond(bond6);
+ enol.addBond(bond7);
+ enol.addBond(bond8);
+
+ // perceive atom types
+ AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(enol);
+ //
+ /**
+ * getImplicitHydrogenCount() returns null otherwise
+ * and throws NPE in getFreeValenciesForRingGroup()
+ CDKHydrogenAdder hadder = CDKHydrogenAdder.getInstance(enol.getBuilder());
+ hadder.addImplicitHydrogens(enol);
+ */
+ // now have the algorithm have a go at it
+ enol = fbot.kekuliseAromaticRings(enol);
+ Assert.assertNotNull(enol);
+ // Assert.assertTrue(fbot.isOK(enol));
+
+ // now check whether it did the right thing
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE, enol.getBond(0).getOrder());
+ Assert.assertEquals(CDKConstants.BONDORDER_DOUBLE, enol.getBond(1).getOrder());
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE, enol.getBond(2).getOrder());
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE, enol.getBond(3).getOrder());
+ Assert.assertEquals(CDKConstants.BONDORDER_DOUBLE, enol.getBond(4).getOrder());
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE, enol.getBond(5).getOrder());
+ Assert.assertEquals(CDKConstants.BONDORDER_DOUBLE, enol.getBond(6).getOrder());
+ Assert.assertEquals(CDKConstants.BONDORDER_DOUBLE, enol.getBond(7).getOrder());
+ }
+
+ /**
+ * @cdk.inchi InChI=1/C4H5N/c1-2-4-5-3-1/h1-5H
+ */
+ @Test public void xtestPyrrole() throws Exception {
+ IMolecule enol = new NNMolecule();
+
+ // atom block
+ IAtom atom1 = new NNAtom(Elements.CARBON);
+ atom1.setHybridization(Hybridization.SP2);
+ IAtom atom2 = new NNAtom(Elements.CARBON);
+ atom2.setHybridization(Hybridization.SP2);
+ IAtom atom3 = new NNAtom(Elements.CARBON);
+ atom3.setHybridization(Hybridization.SP2);
+ IAtom atom4 = new NNAtom(Elements.CARBON);
+ atom4.setHybridization(Hybridization.SP2);
+ IAtom atom5 = new NNAtom(Elements.NITROGEN);
+ atom5.setHybridization(Hybridization.SP2);
+ atom5.setImplicitHydrogenCount(1);
+
+ // bond block
+ IBond bond1 = new NNBond(atom1, atom2);
+ IBond bond2 = new NNBond(atom2, atom3);
+ IBond bond3 = new NNBond(atom3, atom4);
+ IBond bond4 = new NNBond(atom4, atom5);
+ IBond bond5 = new NNBond(atom5, atom1);
+
+ enol.addAtom(atom1);
+ enol.addAtom(atom2);
+ enol.addAtom(atom3);
+ enol.addAtom(atom4);
+ enol.addAtom(atom5);
+ enol.addBond(bond1);
+ enol.addBond(bond2);
+ enol.addBond(bond3);
+ enol.addBond(bond4);
+ enol.addBond(bond5);
+
+ // perceive atom types
+ AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(enol);
+
+ // now have the algorithm have a go at it
+ enol = fbot.kekuliseAromaticRings(enol);
+ Assert.assertNotNull(enol);
+ //Assert.assertTrue(fbot.isOK(enol));
+
+ // now check whether it did the right thing
+ Assert.assertEquals(CDKConstants.BONDORDER_DOUBLE, enol.getBond(0).getOrder());;
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE, enol.getBond(1).getOrder());;
+ Assert.assertEquals(CDKConstants.BONDORDER_DOUBLE, enol.getBond(2).getOrder());;
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE, enol.getBond(3).getOrder());;
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE, enol.getBond(4).getOrder());;
+ }
+
+ @Test public void xtestPyridine() throws Exception {
+ IMolecule enol = new NNMolecule();
+
+ // atom block
+ IAtom atom1 = new NNAtom(Elements.CARBON);
+ atom1.setHybridization(Hybridization.SP2);
+ IAtom atom2 = new NNAtom(Elements.CARBON);
+ atom2.setHybridization(Hybridization.SP2);
+ IAtom atom3 = new NNAtom(Elements.CARBON);
+ atom3.setHybridization(Hybridization.SP2);
+ IAtom atom4 = new NNAtom(Elements.CARBON);
+ atom4.setHybridization(Hybridization.SP2);
+ IAtom atom5 = new NNAtom(Elements.CARBON);
+ atom5.setHybridization(Hybridization.SP2);
+ IAtom atom6 = new NNAtom(Elements.NITROGEN);
+ atom6.setHybridization(Hybridization.SP2);
+
+ // bond block
+ IBond bond1 = new NNBond(atom1, atom2);
+ IBond bond2 = new NNBond(atom2, atom3);
+ IBond bond3 = new NNBond(atom3, atom4);
+ IBond bond4 = new NNBond(atom4, atom5);
+ IBond bond5 = new NNBond(atom5, atom6);
+ IBond bond6 = new NNBond(atom6, atom1);
+
+ enol.addAtom(atom1);
+ enol.addAtom(atom2);
+ enol.addAtom(atom3);
+ enol.addAtom(atom4);
+ enol.addAtom(atom5);
+ enol.addAtom(atom6);
+ enol.addBond(bond1);
+ enol.addBond(bond2);
+ enol.addBond(bond3);
+ enol.addBond(bond4);
+ enol.addBond(bond5);
+ enol.addBond(bond6);
+
+ // perceive atom types
+ AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(enol);
+
+ // now have the algorithm have a go at it
+ enol = fbot.kekuliseAromaticRings(enol);
+ Assert.assertNotNull(enol);
+ // Assert.assertTrue(dbst.isOK(enol));
+
+ // now check whether it did the right thing
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(0).getOrder().ordinal() + enol.getBond(5).getOrder().ordinal()); // around atom1
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(0).getOrder().ordinal() + enol.getBond(1).getOrder().ordinal()); // around atom2
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(1).getOrder().ordinal() + enol.getBond(2).getOrder().ordinal()); // around atom3
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(2).getOrder().ordinal() + enol.getBond(3).getOrder().ordinal()); // around atom4
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(3).getOrder().ordinal() + enol.getBond(4).getOrder().ordinal()); // around atom5
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(4).getOrder().ordinal() + enol.getBond(5).getOrder().ordinal()); // around atom6
+ }
+
+ /**
+ * @cdk.inchi InChI=1/C6H6/c1-2-4-6-5-3-1/h1-6H
+ * @cdk.bug 1931262
+ */
+ @Test public void xtestBenzene() throws Exception {
+ IMolecule enol = new NNMolecule();
+
+ // atom block
+ IAtom atom1 = new NNAtom(Elements.CARBON);
+ atom1.setHybridization(Hybridization.SP2);
+ IAtom atom2 = new NNAtom(Elements.CARBON);
+ atom2.setHybridization(Hybridization.SP2);
+ IAtom atom3 = new NNAtom(Elements.CARBON);
+ atom3.setHybridization(Hybridization.SP2);
+ IAtom atom4 = new NNAtom(Elements.CARBON);
+ atom4.setHybridization(Hybridization.SP2);
+ IAtom atom5 = new NNAtom(Elements.CARBON);
+ atom5.setHybridization(Hybridization.SP2);
+ IAtom atom6 = new NNAtom(Elements.CARBON);
+ atom6.setHybridization(Hybridization.SP2);
+
+ // bond block
+ IBond bond1 = new NNBond(atom1, atom2);
+ IBond bond2 = new NNBond(atom2, atom3);
+ IBond bond3 = new NNBond(atom3, atom4);
+ IBond bond4 = new NNBond(atom4, atom5);
+ IBond bond5 = new NNBond(atom5, atom6);
+ IBond bond6 = new NNBond(atom6, atom1);
+
+ enol.addAtom(atom1);
+ enol.addAtom(atom2);
+ enol.addAtom(atom3);
+ enol.addAtom(atom4);
+ enol.addAtom(atom5);
+ enol.addAtom(atom6);
+ enol.addBond(bond1);
+ enol.addBond(bond2);
+ enol.addBond(bond3);
+ enol.addBond(bond4);
+ enol.addBond(bond5);
+ enol.addBond(bond6);
+
+ // perceive atom types
+ AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(enol);
+
+ // now have the algorithm have a go at it
+ enol = fbot.kekuliseAromaticRings(enol);
+ Assert.assertNotNull(enol);
+ //Assert.assertTrue(dbst.isOK(enol));
+
+ // now check whether it did the right thing
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(0).getOrder().ordinal() + enol.getBond(5).getOrder().ordinal()); // around atom1
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(0).getOrder().ordinal() + enol.getBond(1).getOrder().ordinal()); // around atom2
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(1).getOrder().ordinal() + enol.getBond(2).getOrder().ordinal()); // around atom3
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(2).getOrder().ordinal() + enol.getBond(3).getOrder().ordinal()); // around atom4
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(3).getOrder().ordinal() + enol.getBond(4).getOrder().ordinal()); // around atom5
+ Assert.assertEquals(CDKConstants.BONDORDER_SINGLE.ordinal() + CDKConstants.BONDORDER_DOUBLE.ordinal(),
+ enol.getBond(4).getOrder().ordinal() + enol.getBond(5).getOrder().ordinal()); // around atom6
+ }
+ /**
+ * Just to ensure it doesn't throw exceptions
+ * @throws Exception
+ */
+ @Test(timeout=1000)
+ public void testAcyclic() throws Exception {
+ String smiles = "CCCCCCC";
+ SmilesParser smilesParser = new SmilesParser(SilentChemObjectBuilder.getInstance());
+ IMolecule molecule = smilesParser.parseSmiles(smiles);
+
+ molecule = fbot.kekuliseAromaticRings(molecule);
+ Assert.assertNotNull(molecule);
+
+ }
+}
@egonw

Missing header like below, but need completing with email addresses:

+/* Copyright (C) 2012 Kevin Lawson <>

  • * Lucy Entwistle <>
  • *
  • * Contact: cdk-devel@lists.sourceforge.net
  • *
  • * This program is free software; you can redistribute it and/or
  • * modify it under the terms of the GNU Lesser General Public License
  • * as published by the Free Software Foundation; either version 2.1
  • * of the License, or (at your option) any later version.
  • *
  • * This program is distributed in the hope that it will be useful,
  • * but WITHOUT ANY WARRANTY; without even the implied warranty of
  • * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • * GNU Lesser General Public License for more details.
  • *
  • * You should have received a copy of the GNU Lesser General Public License
  • * along with this program; if not, write to the Free Software
  • * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  • */
@egonw

the first line in JavaDoc must end with a period, to allow JavaDoc to detect the first sentence for summaries.

@egonw

JavaDoc is HTML; please use

    and
  • for this list.

@egonw

please add "@cdk.module smiles" here.

@egonw

Please add @TestClass("org.openscience.cdk.smiles.FixBondOrdersToolTest") here.

@egonw

please remove outcommented code.

@egonw

Where does this JavaDoc belong to?

@egonw

Please have full JavaDoc for all public methods.

@egonw

Please add @TestMethod("xxx") here, where 'xxx' points to a test method in your test class that executes this method.

Like you do on line 548.

@egonw

You can also use this, which creates a link in the JavaDoc:

{@link IRingSet}

@egonw

A @return statement is missing in the JavaDoc.

@egonw

Missing period at the end of the first JavaDoc line (see the earlier comment).

@egonw

Remove incomplete JavaDoc if none is required.

@egonw

This line with these three fields is from our Subversion times, and can be removed. Just start with the first 'Copyright' line.

Please sign in to comment.
Something went wrong with that request. Please try again.