├── exec ├── src │ └── main │ │ ├── resources │ │ ├── META-INF │ │ │ └── services │ │ │ │ └── uk.ac.ebi.beam.CmdLnModule │ │ └── stub.sh │ │ └── java │ │ └── uk │ │ └── ac │ │ └── ebi │ │ └── beam │ │ ├── Aromatise.java │ │ ├── CmdLnModule.java │ │ ├── Main.java │ │ ├── Anonymise.java │ │ └── Shuffle.java ├── make-stub.sh └── pom.xml ├── core ├── src │ ├── test │ │ └── java │ │ │ └── uk │ │ │ └── ac │ │ │ └── ebi │ │ │ └── beam │ │ │ ├── AtomTest.java │ │ │ ├── IntSetTest.java │ │ │ ├── BiconnectedComponentsTest.java │ │ │ ├── ArbitraryMatchingTest.java │ │ │ ├── ElectronAssignmentTest.java │ │ │ ├── MatchingTest.java │ │ │ ├── ParsingHydrogenCountTest.java │ │ │ ├── UnionFindTest.java │ │ │ ├── ParsingAtomClassTest.java │ │ │ ├── AtomCentricDBConfigTest.java │ │ │ ├── ParsingAtomChargeTest.java │ │ │ ├── IntStackTest.java │ │ │ ├── BondTest.java │ │ │ ├── EdgeTest.java │ │ │ ├── ElementTest.java │ │ │ ├── MaximumMatchingTest.java │ │ │ ├── ParsingBracketAtomTest.java │ │ │ └── ConfigurationTest.java │ └── main │ │ ├── java │ │ └── uk │ │ │ └── ac │ │ │ └── ebi │ │ │ └── beam │ │ │ ├── Function.java │ │ │ ├── Matching.java │ │ │ ├── Tuple.java │ │ │ ├── IntStack.java │ │ │ ├── InvalidSmilesException.java │ │ │ ├── UnionFind.java │ │ │ ├── BiconnectedComponents.java │ │ │ ├── ElectronAssignment.java │ │ │ ├── Atom.java │ │ │ ├── IntSet.java │ │ │ ├── ArbitraryMatching.java │ │ │ └── Edge.java │ │ └── resources │ │ └── uk │ │ └── ac │ │ └── ebi │ │ └── beam │ │ └── element-defaults.txt └── pom.xml ├── func ├── src │ ├── main │ │ └── java │ │ │ └── uk │ │ │ └── ac │ │ │ └── ebi │ │ │ └── beam │ │ │ ├── BondBasedConfiguration.java │ │ │ ├── RenumberAtomMaps.java │ │ │ ├── ToSubsetAtoms.java │ │ │ ├── FromSubsetAtoms.java │ │ │ ├── AbstractFunction.java │ │ │ ├── ImplicitToExplicit.java │ │ │ ├── ExplicitToImplicit.java │ │ │ ├── FromTrigonalTopology.java │ │ │ └── Functions.java │ └── test │ │ └── java │ │ └── uk │ │ └── ac │ │ └── ebi │ │ └── beam │ │ ├── BondBasedConfigurationTest.java │ │ ├── FunctionsTest.java │ │ ├── ExplicitToImplicitTest.java │ │ ├── ToSubsetAtomsTest.java │ │ ├── RemoveUpDownBondsTest.java │ │ ├── NormaliseDirectionalLabelsTest.java │ │ ├── FromSubsetAtomsTest.java │ │ ├── FromTrigonalTopologyTest.java │ │ └── ToTrigonalTopologyTest.java └── pom.xml ├── .github └── workflows │ └── build.yml └── LICENSE /exec/src/main/resources/META-INF/services/uk.ac.ebi.beam.CmdLnModule: -------------------------------------------------------------------------------- 1 | uk.ac.ebi.beam.Anonymise 2 | uk.ac.ebi.beam.Aromatise 3 | uk.ac.ebi.beam.Shuffle 4 | -------------------------------------------------------------------------------- /exec/src/main/resources/stub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MYSELF="${BASH_SOURCE[0]}" 3 | [ $? -gt 0 -a -f "$0" ] && MYSELF="./$0" 4 | java=java 5 | if test -n "$JAVA_HOME"; then 6 | java="$JAVA_HOME/bin/java" 7 | fi 8 | exec "$java" $JAVA_OPTS -jar $MYSELF "$@" 9 | exit 1 -------------------------------------------------------------------------------- /exec/make-stub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SOURCE="${BASH_SOURCE[0]}" 4 | while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink 5 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 6 | SOURCE="$(readlink "$SOURCE")" 7 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located 8 | done 9 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 10 | 11 | # Builds self contained unix executable 12 | cat ${DIR}/src/main/resources/stub.sh ${DIR}/target/beam.jar > ${DIR}/target/beam && chmod +x ${DIR}/target/beam 13 | cp ${DIR}/target/beam /usr/local/bin/beam -------------------------------------------------------------------------------- /exec/src/main/java/uk/ac/ebi/beam/Aromatise.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015. John May 3 | */ 4 | 5 | package uk.ac.ebi.beam; 6 | 7 | import joptsimple.OptionSet; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * Simple module simply Kekulises then emits and normalised 13 | * (by beam's model) aromatic form of the SMILES. 14 | */ 15 | public final class Aromatise extends FunctorCmdLnModule { 16 | 17 | public Aromatise() { 18 | super("arom"); 19 | } 20 | 21 | @Override 22 | Functor createFunctor(OptionSet optionSet) { 23 | return new Functor() { 24 | @Override 25 | String map(String str) throws IOException { 26 | return Graph.fromSmiles(str).kekule().aromatic().toSmiles() + suffixedId(str); 27 | } 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/AtomTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.junit.Assert.assertThat; 7 | 8 | /** @author John May */ 9 | public class AtomTest { 10 | 11 | @Test public void aliphaticSubsetFromElement() { 12 | for (Atom a : AtomImpl.AliphaticSubset.values()) { 13 | assertThat(AtomImpl.AliphaticSubset.ofElement(a.element()), is(a)); 14 | } 15 | } 16 | 17 | @Test(expected = IllegalArgumentException.class) 18 | public void aliphaticSubsetInvalidElement() { 19 | AtomImpl.AliphaticSubset.ofElement(Element.Californium); 20 | } 21 | 22 | @Test public void aromaticSubsetFromElement() { 23 | for (Atom a : AtomImpl.AromaticSubset.values()) { 24 | assertThat(AtomImpl.AromaticSubset.ofElement(a.element()), is(a)); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /func/src/main/java/uk/ac/ebi/beam/BondBasedConfiguration.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | /** @author John May */ 4 | final class BondBasedConfiguration { 5 | 6 | static Configuration.DoubleBond configurationOf(Graph g, 7 | int x, int u, int v, int y) { 8 | 9 | Edge e = g.edge(u, v); 10 | 11 | if (e.bond() != Bond.DOUBLE) 12 | throw new IllegalArgumentException("atoms u,v are not labelled as a double bond"); 13 | 14 | Edge e1 = g.edge(u, x); 15 | Edge e2 = g.edge(v, y); 16 | 17 | Bond b1 = e1.bond(u); 18 | Bond b2 = e2.bond(v); 19 | 20 | if (b1 == Bond.IMPLICIT || b1 == Bond.SINGLE) 21 | return Configuration.DoubleBond.UNSPECIFIED; 22 | if (b2 == Bond.IMPLICIT || b2 == Bond.SINGLE) 23 | return Configuration.DoubleBond.UNSPECIFIED; 24 | 25 | return b1 == b2 ? Configuration.DoubleBond.TOGETHER 26 | : Configuration.DoubleBond.OPPOSITE; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | beam 7 | uk.ac.ebi.beam 8 | 1.3.9 9 | 10 | 4.0.0 11 | 12 | beam-core 13 | beam-core 14 | 15 | 16 | 17 | junit 18 | junit 19 | test 20 | 21 | 22 | org.hamcrest 23 | hamcrest-core 24 | test 25 | 26 | 27 | org.mockito 28 | mockito-core 29 | test 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /func/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | beam 7 | uk.ac.ebi.beam 8 | 1.3.9 9 | 10 | 4.0.0 11 | 12 | beam-func 13 | beam-func 14 | 15 | 16 | 17 | junit 18 | junit 19 | test 20 | 21 | 22 | org.mockito 23 | mockito-core 24 | test 25 | 26 | 27 | uk.ac.ebi.beam 28 | beam-core 29 | ${project.version} 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /exec/src/main/java/uk/ac/ebi/beam/CmdLnModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015. John May 3 | */ 4 | 5 | package uk.ac.ebi.beam; 6 | 7 | /** 8 | * A plugable command line module providing functionality 9 | * from the primary dispatch {@link Main}. Modules should 10 | * be implemented and added to an SPI 11 | * (META-INF/services/uk.ac.ebi.beam.CmdLnModule). 12 | * 13 | * @see SPI 14 | */ 15 | public interface CmdLnModule { 16 | 17 | /** 18 | * The module name is a concise, often abbreviated, description 19 | * of the module function. It is used to reference the 20 | * functionality from the primary dispatch: 21 | *
{@code $ beam {module.name} {module.args}}
22 | * 23 | * @return module name 24 | */ 25 | String name(); 26 | 27 | /** 28 | * Displays the usage/help for this module. 29 | * 30 | * @return help info 31 | */ 32 | String getHelpInfo(); 33 | 34 | /** 35 | * Executes the module with the specified arguments. The 36 | * arguments do not include the module name. 37 | * 38 | * @param args command line arguments 39 | */ 40 | void exec(String[] args); 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | types: [opened, synchronize, reopened] 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 16 | - name: Set up JDK 11 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: 11 20 | - name: Cache SonarCloud packages 21 | uses: actions/cache@v1 22 | with: 23 | path: ~/.sonar/cache 24 | key: ${{ runner.os }}-sonar 25 | restore-keys: ${{ runner.os }}-sonar 26 | - name: Cache Maven packages 27 | uses: actions/cache@v1 28 | with: 29 | path: ~/.m2 30 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 31 | restore-keys: ${{ runner.os }}-m2 32 | - name: Build and analyze 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 35 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 36 | run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=johnmay_beam -Pcoverage -------------------------------------------------------------------------------- /func/src/main/java/uk/ac/ebi/beam/RenumberAtomMaps.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | final class RenumberAtomMaps { 4 | 5 | private static final class State { 6 | Graph g; 7 | boolean[] visit; 8 | int[] map; 9 | int nMaps; 10 | 11 | public State(Graph g, int maxidx) { 12 | this.g = g; 13 | this.visit = new boolean[g.order()]; 14 | this.map = new int[maxidx + 1]; 15 | this.nMaps = 0; 16 | } 17 | } 18 | 19 | private static void traverse(State s, int idx) { 20 | s.visit[idx] = true; 21 | int mapIdx = s.g.atom(idx).atomClass(); 22 | if (mapIdx != 0) { 23 | if (s.map[mapIdx] == 0) 24 | s.map[mapIdx] = ++s.nMaps; 25 | mapIdx = s.map[mapIdx]; 26 | s.g.setAtom(idx, AtomBuilder.fromExisting(s.g.atom(idx)).atomClass(mapIdx).build()); 27 | } 28 | for (Edge e : s.g.edges(idx)) { 29 | int nbr = e.other(idx); 30 | if (!s.visit[nbr]) 31 | traverse(s, nbr); 32 | } 33 | } 34 | 35 | static void renumber(Graph g) { 36 | int maxMapIdx = 0; 37 | for (int i = 0; i < g.order(); i++) 38 | maxMapIdx = Math.max(maxMapIdx, g.atom(i).atomClass()); 39 | if (maxMapIdx == 0) 40 | return; 41 | State state = new State(g, maxMapIdx); 42 | for (int i = 0; i < g.order(); i++) 43 | if (!state.visit[i]) 44 | traverse(state, i); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | The views and conclusions contained in the software and documentation are those 25 | of the authors and should not be interpreted as representing official policies, 26 | either expressed or implied, of the FreeBSD Project. -------------------------------------------------------------------------------- /func/src/test/java/uk/ac/ebi/beam/BondBasedConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.junit.Assert.assertThat; 7 | 8 | /** @author John May */ 9 | public class BondBasedConfigurationTest { 10 | 11 | @Test(expected = IllegalArgumentException.class) 12 | public void nonDoubleBond() throws Exception { 13 | Graph g = Graph.fromSmiles("CCCC"); 14 | BondBasedConfiguration.configurationOf(g, 0, 1, 2, 3); 15 | } 16 | 17 | @Test 18 | public void opposite1() throws Exception { 19 | Graph g = Graph.fromSmiles("F/C=C/F"); 20 | assertThat(BondBasedConfiguration.configurationOf(g, 0, 1, 2, 3), 21 | is(Configuration.DoubleBond.OPPOSITE)); 22 | } 23 | 24 | @Test 25 | public void opposite2() throws Exception { 26 | Graph g = Graph.fromSmiles("F\\C=C\\F"); 27 | assertThat(BondBasedConfiguration.configurationOf(g, 0, 1, 2, 3), 28 | is(Configuration.DoubleBond.OPPOSITE)); 29 | } 30 | 31 | @Test 32 | public void together1() throws Exception { 33 | Graph g = Graph.fromSmiles("F/C=C\\F"); 34 | assertThat(BondBasedConfiguration.configurationOf(g, 0, 1, 2, 3), 35 | is(Configuration.DoubleBond.TOGETHER)); 36 | } 37 | 38 | @Test 39 | public void together2() throws Exception { 40 | Graph g = Graph.fromSmiles("F\\C=C/F"); 41 | assertThat(BondBasedConfiguration.configurationOf(g, 0, 1, 2, 3), 42 | is(Configuration.DoubleBond.TOGETHER)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /exec/src/main/java/uk/ac/ebi/beam/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015. John May 3 | */ 4 | 5 | package uk.ac.ebi.beam; 6 | 7 | import java.util.*; 8 | 9 | public class Main { 10 | 11 | private final static Map modules = new TreeMap<>(); 12 | 13 | static void addModule(CmdLnModule module) { 14 | modules.put(module.name().toLowerCase(Locale.ROOT), 15 | module); 16 | } 17 | 18 | static { 19 | for (CmdLnModule m : ServiceLoader.load(CmdLnModule.class)) 20 | addModule(m); 21 | } 22 | 23 | public static void main(String[] args) { 24 | 25 | if (args.length < 1) { 26 | printUsage(); 27 | return; 28 | } 29 | 30 | final String cmd = args[0].toLowerCase(Locale.ROOT); 31 | 32 | if ("help".equals(cmd)) { 33 | printUsage(); 34 | printHelp(); 35 | } else if (modules.containsKey(cmd)) { 36 | modules.get(cmd).exec(Arrays.copyOfRange(args, 1, args.length)); 37 | } else { 38 | printUsage(); 39 | } 40 | } 41 | 42 | private static void printUsage() { 43 | StringBuilder sb = new StringBuilder(); 44 | sb.append("usage: beam [help"); 45 | for (String key : modules.keySet()) 46 | sb.append('|').append(key); 47 | sb.append("] {ARGS}\n"); 48 | System.err.println(sb.toString()); 49 | } 50 | 51 | private static void printHelp() { 52 | StringBuilder sb = new StringBuilder(); 53 | for (CmdLnModule module : modules.values()) 54 | sb.append(module.getHelpInfo()); 55 | System.err.println(sb.toString()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/IntSetTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Random; 6 | 7 | import static org.junit.Assert.assertFalse; 8 | import static org.junit.Assert.assertTrue; 9 | 10 | /** @author John May */ 11 | public class IntSetTest { 12 | 13 | @Test public void universe() throws Exception { 14 | IntSet universe = IntSet.universe(); 15 | Random rnd = new Random(); 16 | for (int i = 0; i < 1000; i++) { 17 | assertTrue(universe.contains(rnd.nextInt())); 18 | } 19 | } 20 | 21 | @Test public void empty() throws Exception { 22 | IntSet empty = IntSet.empty(); 23 | Random rnd = new Random(); 24 | for (int i = 0; i < 1000; i++) { 25 | assertFalse(empty.contains(rnd.nextInt())); 26 | } 27 | } 28 | 29 | @Test public void singleton() throws Exception { 30 | IntSet one = IntSet.allOf(1); 31 | assertFalse(one.contains(0)); 32 | assertTrue(one.contains(1)); 33 | assertFalse(one.contains(2)); 34 | assertFalse(one.contains(3)); 35 | assertFalse(one.contains(4)); 36 | assertFalse(one.contains(5)); 37 | } 38 | 39 | @Test public void allOf() throws Exception { 40 | IntSet one = IntSet.allOf(4, 2); 41 | assertFalse(one.contains(0)); 42 | assertFalse(one.contains(1)); 43 | assertTrue(one.contains(2)); 44 | assertFalse(one.contains(3)); 45 | assertTrue(one.contains(4)); 46 | assertFalse(one.contains(5)); 47 | } 48 | 49 | @Test public void noneOf() throws Exception { 50 | IntSet one = IntSet.noneOf(0, 1, 3, 5); 51 | assertFalse(one.contains(0)); 52 | assertFalse(one.contains(1)); 53 | assertTrue(one.contains(2)); 54 | assertFalse(one.contains(3)); 55 | assertTrue(one.contains(4)); 56 | assertFalse(one.contains(5)); 57 | assertTrue(one.contains(6)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /exec/src/main/java/uk/ac/ebi/beam/Anonymise.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015. John May 3 | */ 4 | 5 | package uk.ac.ebi.beam; 6 | 7 | import joptsimple.OptionSet; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * Anonymise SMILES input to all '*' atoms. Bond orders can optionally be emitted. 13 | */ 14 | public final class Anonymise extends FunctorCmdLnModule { 15 | 16 | private final Atom UNKN_ATOM = AtomImpl.AliphaticSubset.Any; 17 | 18 | public Anonymise() { 19 | super("anon"); 20 | super.optparser.accepts("bo", "keep bond orders"); 21 | } 22 | 23 | @Override 24 | Functor createFunctor(OptionSet optionSet) { 25 | final boolean bondorders = optionSet.has("bo"); 26 | return new Functor() { 27 | @Override 28 | String map(String str) throws IOException { 29 | 30 | Graph g = Graph.fromSmiles(str); 31 | 32 | if (bondorders) 33 | g = g.kekule(); 34 | 35 | final GraphBuilder gb = GraphBuilder.create(g.order()); 36 | 37 | for (int v = 0; v < g.order(); v++) { 38 | gb.add(UNKN_ATOM); 39 | for (Edge e : g.edges(v)) { 40 | if (e.other(v) < v) { 41 | if (bondorders) 42 | gb.add(new Edge(v, e.other(v), bondForOrder(e.bond().order()))); 43 | else 44 | gb.add(new Edge(v, e.other(v), Bond.IMPLICIT)); 45 | } 46 | } 47 | } 48 | 49 | return gb.build().toSmiles() + suffixedId(str); 50 | } 51 | }; 52 | } 53 | 54 | private Bond bondForOrder(int ord) { 55 | switch (ord) { 56 | case 1: 57 | return Bond.IMPLICIT; 58 | case 2: 59 | return Bond.DOUBLE; 60 | case 3: 61 | return Bond.TRIPLE; 62 | case 4: 63 | return Bond.QUADRUPLE; 64 | default: 65 | return Bond.IMPLICIT; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /func/src/main/java/uk/ac/ebi/beam/ToSubsetAtoms.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import java.util.List; 4 | 5 | import static uk.ac.ebi.beam.Configuration.Type.None; 6 | 7 | /** 8 | * Given a chemical graph with 0 or more atoms. Convert that graph to one where 9 | * fully specified bracket atoms which can be specified as organic subsets. 10 | * 11 | * @author John May 12 | */ 13 | final class ToSubsetAtoms extends AbstractFunction { 14 | 15 | public Graph apply(Graph g) { 16 | 17 | Graph h = new Graph(g.order()); 18 | 19 | for (int u = 0; u < g.order(); u++) { 20 | 21 | // only attempt subset conversion if no known topology 22 | Topology t = g.topologyOf(u); 23 | 24 | if (t.type() == None) { 25 | h.addAtom(toSubset(g.atom(u), g, u)); 26 | } else { 27 | h.addAtom(g.atom(u)); 28 | h.addTopology(t); 29 | } 30 | } 31 | 32 | // edges are unchanged 33 | for (Edge e : g.edges()) 34 | h.addEdge(e); 35 | 36 | return h; 37 | } 38 | 39 | static Atom toSubset(Atom a, Graph g, int u) { 40 | 41 | // atom is already a subset atom 42 | if (a.subset()) 43 | return a; 44 | 45 | // element is not organic and thus cannot be part of the subset 46 | if (!a.element().organic()) 47 | return a; 48 | 49 | // if any of these values are set the atom cannot be a subset atom 50 | if (a.charge() != 0 || a.atomClass() != 0 || a.isotope() >= 0) 51 | return a; 52 | 53 | Atom subset = a.aromatic() ? AtomImpl.AromaticSubset.ofElement(a.element()) 54 | : AtomImpl.AliphaticSubset.ofElement(a.element()); 55 | 56 | // does the implied availableElectrons from the bond order sum match that 57 | // which was stored - if aromatic we only check the lowest valence state 58 | int impliedHCount = subset.hydrogens(g, u); 59 | 60 | // mismatch in number of hydrogens we must write this as a bracket atom 61 | return impliedHCount != a.hydrogens() ? a : subset; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/BiconnectedComponentsTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.junit.Assert.assertThat; 7 | 8 | /** @author John May */ 9 | public class BiconnectedComponentsTest { 10 | 11 | @Test public void benzene() throws Exception { 12 | Graph g = Graph.fromSmiles("c1ccccc1"); 13 | BiconnectedComponents bc = new BiconnectedComponents(g); 14 | assertThat(bc.components().size(), is(1)); 15 | assertThat(bc.components().get(0).size(), is(6)); 16 | } 17 | 18 | @Test public void benzylbenzene() throws Exception { 19 | Graph g = Graph.fromSmiles("c1ccccc1Cc1ccccc1"); 20 | BiconnectedComponents bc = new BiconnectedComponents(g, false); 21 | assertThat(bc.cyclic().cardinality(), is(12)); 22 | } 23 | 24 | @Test public void spiro() throws Exception { 25 | Graph g = Graph.fromSmiles("C1CCCCC11CCCCC1"); 26 | BiconnectedComponents bc = new BiconnectedComponents(g); 27 | assertThat(bc.components().size(), is(2)); 28 | assertThat(bc.components().get(0).size(), is(6)); 29 | assertThat(bc.components().get(0).size(), is(6)); 30 | } 31 | 32 | @Test public void fused() throws Exception { 33 | Graph g = Graph.fromSmiles("C1=CC2=CC=CC=C2C=C1"); 34 | BiconnectedComponents bc = new BiconnectedComponents(g); 35 | assertThat(bc.components().size(), is(1)); 36 | assertThat(bc.components().get(0).size(), is(11)); 37 | } 38 | 39 | @Test public void bridged() throws Exception { 40 | Graph g = Graph.fromSmiles("C1CC2CCC1C2"); 41 | BiconnectedComponents bc = new BiconnectedComponents(g); 42 | assertThat(bc.components().size(), is(1)); 43 | assertThat(bc.components().get(0).size(), is(8)); 44 | } 45 | 46 | @Test public void exocyclic() throws Exception { 47 | Graph g = Graph.fromSmiles("[AsH]=C1C=CC=CC=C1"); 48 | BiconnectedComponents bc = new BiconnectedComponents(g); 49 | assertThat(bc.components().size(), is(1)); 50 | assertThat(bc.components().get(0).size(), is(7)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/Function.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | /** 33 | * Defines a function which can be applied to one type to produce another. 34 | * 35 | * @author John May 36 | */ 37 | interface Function { 38 | 39 | /** 40 | * Apply the function to an instance of 's' and producing and instance 't'. 41 | * 42 | * @param s input instance 43 | * @return output instance 44 | */ 45 | T apply(S s) throws Exception; 46 | 47 | Function with(Function g); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /exec/src/main/java/uk/ac/ebi/beam/Shuffle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015. NextMove Software Ltd 3 | */ 4 | 5 | package uk.ac.ebi.beam; 6 | 7 | import joptsimple.OptionSet; 8 | 9 | import java.io.*; 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | public class Shuffle extends PipingCmdLnModule { 14 | 15 | public Shuffle() { 16 | super("shuf"); 17 | optparser.accepts("n", "try to produce 'n' random SMILES for each input") 18 | .withRequiredArg() 19 | .ofType(Integer.class) 20 | .defaultsTo(10); 21 | optparser.accepts("m", "max number of shuffles") 22 | .withRequiredArg() 23 | .ofType(Integer.class) 24 | .defaultsTo(500); 25 | } 26 | 27 | @Override 28 | void process(BufferedReader brdr, BufferedWriter bwtr, InputCounter counter, OptionSet optset) throws IOException { 29 | 30 | final int num = (Integer) optset.valueOf("n"); 31 | final int max = (Integer) optset.valueOf("m"); 32 | final boolean progress = !optset.has("prog-off"); 33 | 34 | String line; 35 | int cnt = 0, gencnt = 0; 36 | while ((line = brdr.readLine()) != null) { 37 | try { 38 | final String id = suffixedId(line); 39 | for (String str : generate(line, num, max)) { 40 | bwtr.write(str + id); 41 | bwtr.newLine(); 42 | ++gencnt; 43 | } 44 | if (progress && ++cnt % 2500 == 0) 45 | report("%d => %d", cnt, gencnt); 46 | } catch (InvalidSmilesException e) { 47 | System.err.println(e.getMessage()); 48 | } 49 | } 50 | if (progress) 51 | report("%d => %d\n", cnt, gencnt); 52 | } 53 | 54 | private Set generate(String str, int n, int m) throws IOException { 55 | 56 | final Set smis = new HashSet(); 57 | 58 | Graph g = Graph.fromSmiles(str); 59 | 60 | while (m-- > 0 && smis.size() < n) { 61 | smis.add(g.toSmiles()); 62 | g = Functions.randomise(g); 63 | } 64 | 65 | return smis; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /exec/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | beam 7 | uk.ac.ebi.beam 8 | 1.3.9 9 | 10 | 4.0.0 11 | 12 | beam-exec 13 | 14 | 15 | 16 | uk.ac.ebi.beam 17 | beam-core 18 | ${project.version} 19 | 20 | 21 | uk.ac.ebi.beam 22 | beam-func 23 | ${project.version} 24 | 25 | 26 | net.sf.jopt-simple 27 | jopt-simple 28 | 4.8 29 | 30 | 31 | 32 | 33 | 34 | exec 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-shade-plugin 40 | 2.2 41 | 42 | beam 43 | 44 | 45 | uk.ac.ebi.beam.Main 46 | 47 | 48 | 49 | 50 | 51 | 52 | package 53 | 54 | shade 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /func/src/main/java/uk/ac/ebi/beam/FromSubsetAtoms.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Given a chemical graph with 0 or more atoms. Convert that graph to one where 7 | * all atoms are fully specified bracket atoms. 8 | * 9 | * @author John May 10 | */ 11 | final class FromSubsetAtoms 12 | extends AbstractFunction { 13 | 14 | public Graph apply(Graph g) { 15 | 16 | Graph h = new Graph(g.order()); 17 | 18 | for (int u = 0; u < g.order(); u++) { 19 | h.addAtom(fromSubset(g.atom(u), 20 | g.bondedValence(u), 21 | g.degree(u))); 22 | h.addTopology(g.topologyOf(u)); 23 | } 24 | 25 | // edges are unchanged 26 | for (Edge e : g.edges()) 27 | h.addEdge(e); 28 | 29 | return h; 30 | } 31 | 32 | static Atom fromSubset(Atom a, int sum, int deg) { 33 | 34 | // atom is already a non-subset atom 35 | if (!a.subset()) 36 | return a; 37 | 38 | Element e = a.element(); 39 | if (a.aromatic() && deg <= sum) 40 | sum++; 41 | int hCount = a.aromatic() ? Element.implicitAromHydrogenCount(e, sum) 42 | : Element.implicitHydrogenCount(e, sum); 43 | 44 | // XXX: if there was an odd number of availableElectrons there was an odd number 45 | // or aromatic bonds (usually 1 or 3) - if there was one it was 46 | // only a single bond it's likely a spouting from a ring - otherwise 47 | // someones making our life difficult (e.g. c1=cc=cc=c1) in which we 48 | // 'give' back 2 free availableElectrons for use indeterminacy the hCount 49 | // int hCount = (electrons & 0x1) == 1 ? deg > 1 ? (electrons + 2) / 2 50 | // : electrons / 2 51 | // : electrons / 2; 52 | 53 | 54 | return new AtomImpl.BracketAtom(-1, 55 | a.element(), 56 | hCount, 57 | 0, 58 | 0, 59 | a.aromatic()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/ArbitraryMatchingTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.hamcrest.collection.IsIterableWithSize; 4 | import org.junit.Test; 5 | 6 | import java.util.BitSet; 7 | 8 | import static org.hamcrest.CoreMatchers.hasItems; 9 | import static org.hamcrest.MatcherAssert.assertThat; 10 | import static org.hamcrest.Matchers.hasItem; 11 | 12 | /** @author John May */ 13 | public class ArbitraryMatchingTest { 14 | 15 | // simple example on furan (happens to be maximum) 16 | @Test public void furan() throws Exception { 17 | Graph g = Graph.fromSmiles("o1cccc1"); 18 | Matching m = Matching.empty(g); 19 | ArbitraryMatching.initial(g, m, 20 | allOf(1, 2, 3, 4)); 21 | // note this matching is maximum 22 | assertThat(m.matches(), 23 | IsIterableWithSize.iterableWithSize(2)); 24 | assertThat(m.matches(), 25 | hasItems(Tuple.of(1, 2), 26 | Tuple.of(3, 4))); 27 | } 28 | 29 | // furan different order - non maximum this time 30 | @Test public void furan_2() throws Exception { 31 | Graph g = Graph.fromSmiles("c1ccoc1"); 32 | Matching m = Matching.empty(g); 33 | ArbitraryMatching.initial(g, 34 | m, 35 | allOf(0, 1, 2, 4)); 36 | assertThat(m.matches(), 37 | IsIterableWithSize.iterableWithSize(1)); 38 | assertThat(m.matches(), 39 | hasItem(Tuple.of(0, 1))); 40 | } 41 | 42 | @Test public void benzene() throws Exception { 43 | Graph g = Graph.fromSmiles("c1ccccc1"); 44 | Matching m = Matching.empty(g); 45 | ArbitraryMatching.initial(g, 46 | m, 47 | allOf(0, 1, 2, 3, 4, 5)); 48 | assertThat(m.matches(), 49 | IsIterableWithSize.iterableWithSize(3)); 50 | assertThat(m.matches(), 51 | hasItems(Tuple.of(0, 1), 52 | Tuple.of(2, 3), 53 | Tuple.of(4, 5))); 54 | } 55 | 56 | static BitSet allOf(int... xs) { 57 | BitSet s = new BitSet(); 58 | for (int x : xs) 59 | s.set(x); 60 | return s; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /func/src/test/java/uk/ac/ebi/beam/FunctionsTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.junit.Assert.assertThat; 9 | 10 | /** @author John May */ 11 | public class FunctionsTest { 12 | 13 | @Test public void reverse_ethanol() throws Exception { 14 | Graph g = Graph.fromSmiles("CCO"); 15 | assertThat(Functions.reverse(g).toSmiles(), 16 | is("OCC")); 17 | } 18 | 19 | @Test public void reverse_withBranch() throws Exception { 20 | Graph g = Graph.fromSmiles("CC(CC(CO)C)CCO"); 21 | assertThat(Functions.reverse(g).toSmiles(), 22 | is("OCCC(CC(C)CO)C")); 23 | } 24 | 25 | @Test public void atomBasedDBStereo() throws Exception { 26 | Graph g = Graph.fromSmiles("F/C=C/F"); 27 | assertThat(Functions.atomBasedDBStereo(g).toSmiles(), 28 | is("F[C@H]=[C@@H]F")); 29 | } 30 | 31 | @Test public void bondBasedDBStereo() throws Exception { 32 | Graph g = Graph.fromSmiles("F[C@H]=[C@@H]F"); 33 | assertThat(Functions.bondBasedDBStereo(g).toSmiles(), 34 | is("F/C=C/F")); 35 | } 36 | 37 | @Test public void bondBasedDBStereo2() throws Exception { 38 | Graph g = Graph.fromSmiles("F[N@]=[N@@]F"); 39 | assertThat(Functions.bondBasedDBStereo(g).toSmiles(), 40 | is("F/N=N/F")); } 41 | 42 | 43 | @Test public void ensureAlleneStereoDoesntBreakConversion() throws Exception { 44 | Graph g = Graph.fromSmiles("CC=[C@]=CC"); 45 | assertThat(Functions.bondBasedDBStereo(g).toSmiles(), 46 | is("CC=[C@]=CC")); 47 | } 48 | 49 | @Test public void canoncalise() throws IOException { 50 | Graph g = Graph.fromSmiles("CCOCC"); 51 | Graph h = Functions.canonicalize(g, 52 | new long[]{56, 67, 3, 67, 56}); 53 | assertThat(h.toSmiles(), 54 | is("O(CC)CC")); 55 | } 56 | 57 | @Test public void canoncalise2() throws IOException { 58 | Graph g = Graph.fromSmiles("CN1CCC1"); 59 | Graph h = Functions.canonicalize(g, 60 | new long[]{2, 1, 3, 5, 4}); 61 | assertThat(h.toSmiles(), 62 | is("N1(C)CCC1")); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/src/main/resources/uk/ac/ebi/beam/element-defaults.txt: -------------------------------------------------------------------------------- 1 | - Defines default valence and charge (range) and valence electrons for 2 | - several s- and p-block periodic elements. The values allow checking for 3 | - abnormal charge/valence on the defined element. A value of 'n/a' means it is 4 | - not checked. If a valence value is in specified parentheses then it is 5 | - considered fixed and valid for all charge states, if a value is in square 6 | - brackets it is only valid for neutral atoms 7 | 8 | - It should be noted that the model used is only for checking abnormal valence 9 | - and not adding implied hydrogens. The model follows a simple pattern which 10 | - leads to incorrect results (e.g. 5 valent carbon cation ion - [C+H5]) but 11 | - matches observed warnings from the Daylight Depict, daylight.com/daycgi/depict 12 | 13 | - period 1 14 | H (0),1 -1,1 1 15 | 16 | He 0 0,0 2 17 | 18 | - period 2 19 | Li (0),[1] 0,1 1 20 | Be n/a n/a 2 21 | 22 | B 0,3 0,0 3 23 | C [0],2,4 -1,1 4 24 | N [0],3,(5) -3,1 5 25 | O [0],2 -2,1 6 26 | F (0),(1) -1,0 7 27 | Ne 0,0 0,0 8 28 | 29 | - period 3 30 | Na (0),[1] 0,1 1 31 | Mg 0,2 0,2 2 32 | 33 | Al n/a n/a 3 34 | Si n/a n/a 4 35 | P [0],3,5 -3,1 5 36 | S [0],2,4,6 n/a 6 37 | Cl [0],1,3,5,7 n/a 7 38 | Ar 0 0,0 8 39 | 40 | - period 4 (excluding d-block) 41 | K (0),[1] 0,1 1 42 | Ca 0,2 0,2 2 43 | 44 | Ga n/a n/a 3 45 | Ge n/a n/a 4 46 | As [0],3,5 -3,1 5 47 | Se [0],2,4,6 n/a 6 48 | Br [0],1,3,5,7 n/a 7 49 | Kr 0 0,0 8 50 | 51 | - period 5 (excluding d-block) 52 | Rb n/a n/a 1 53 | Sr 0,2 0,2 2 54 | 55 | In n/a n/a 3 56 | Sn n/a n/a 4 57 | Sb n/a n/a 5 58 | Te [0],2,4,6 n/a 6 59 | I [0],1,3,5,7 n/a 7 60 | Xe 0 0,0 8 61 | 62 | - period 6 (excluding d-block and f-block) 63 | Cs n/a n/a 1 64 | Ba 0,2 0,2 2 65 | 66 | Tl n/a n/a 3 67 | Pb n/a n/a 4 68 | Bi n/a n/a 5 69 | Po n/a n/a 6 70 | At [0],1,3,5,7 n/a 7 71 | Rn 0 0,0 8 72 | 73 | - period 7 (excluding d-block and f-block) 74 | Fr n/a n/a 1 75 | Ra 0,2 0,2 2 -------------------------------------------------------------------------------- /func/src/main/java/uk/ac/ebi/beam/AbstractFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | /** 33 | * Provides ability to compose functions. 34 | * 35 | * @author John May 36 | */ 37 | abstract class AbstractFunction implements Function { 38 | 39 | /** {@inheritDoc} */ 40 | @Override public final Function with(final Function g) { 41 | return new Composition(this, g); 42 | } 43 | 44 | private static final class Composition implements Function { 45 | final Function f; 46 | final Function g; 47 | 48 | Composition(Function f, Function g) { 49 | this.f = f; 50 | this.g = g; 51 | } 52 | 53 | public U apply(S s) throws Exception { 54 | return g.apply(f.apply(s)); 55 | } 56 | 57 | public

Function with(Function g) { 58 | return new Composition(this, g); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/ElectronAssignmentTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Assert; 33 | import org.junit.Test; 34 | 35 | import java.io.IOException; 36 | 37 | /** @author John May */ 38 | public class ElectronAssignmentTest { 39 | 40 | @Test public void biphenylLike() throws Exception { 41 | assertAssignable("c1ccccc1-c1ccccc1"); 42 | assertUnassignable("c1ccccc1=c1ccccc1"); // n.b. daylight will assign this - for now we say it's invalid 43 | assertAssignable("c1ccccc1c1ccccc1"); 44 | } 45 | 46 | @Test public void fulvaleneLike() throws Exception { 47 | assertAssignable("c1cccc1-c1cccc1"); 48 | assertAssignable("c1cccc1=c1cccc1"); 49 | assertAssignable("c1cccc1c1cccc1"); 50 | } 51 | 52 | @Test public void cyclopentadiene() throws Exception { 53 | assertUnassignable("c1cccc1"); 54 | assertAssignable("c1cCcc1"); 55 | } 56 | 57 | static void assertAssignable(String smi) throws IOException { 58 | Assert.assertTrue(ElectronAssignment.verify(Graph.fromSmiles(smi))); 59 | } 60 | 61 | static void assertUnassignable(String smi) throws IOException { 62 | Assert.assertFalse(ElectronAssignment.verify(Graph.fromSmiles(smi))); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/MatchingTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.hamcrest.collection.IsIterableWithSize; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.hasItems; 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.CoreMatchers.not; 9 | import static org.junit.Assert.assertFalse; 10 | import static org.junit.Assert.assertThat; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | /** @author John May */ 14 | public class MatchingTest { 15 | 16 | @Test public void empty() throws Exception { 17 | Matching matching = Matching.empty(Graph.fromSmiles("CCCCC")); 18 | assertThat(matching.matches(), 19 | IsIterableWithSize.iterableWithSize(0)); 20 | } 21 | 22 | @Test public void basic() throws Exception { 23 | Matching matching = Matching.empty(Graph.fromSmiles("CCCCC")); 24 | matching.match(0, 1); 25 | matching.match(2, 3); 26 | assertThat(matching.matches(), 27 | IsIterableWithSize.iterableWithSize(2)); 28 | assertThat(matching.matches(), 29 | hasItems(Tuple.of(0, 1), 30 | Tuple.of(2, 3))); 31 | } 32 | 33 | @Test public void adjusted() throws Exception { 34 | Matching matching = Matching.empty(Graph.fromSmiles("CCCCC")); 35 | matching.match(0, 1); 36 | matching.match(2, 3); 37 | matching.match(1, 2); // 0-1 and 2-3 should not be 38 | 39 | assertThat(matching.matches(), 40 | not(hasItems(Tuple.of(0, 1), 41 | Tuple.of(2, 3)))); 42 | assertThat(matching.matches(), 43 | IsIterableWithSize.iterableWithSize(1)); 44 | assertThat(matching.matches(), 45 | hasItems(Tuple.of(1, 2))); 46 | } 47 | 48 | @Test public void adjusted_contains() throws Exception { 49 | Matching matching = Matching.empty(Graph.fromSmiles("CCCCC")); 50 | matching.match(0, 1); 51 | matching.match(2, 3); 52 | matching.match(1, 2); // 0-1 and 2-3 should not be 53 | 54 | assertFalse(matching.unmatched(1)); 55 | assertFalse(matching.unmatched(2)); 56 | assertTrue(matching.unmatched(0)); 57 | assertTrue(matching.unmatched(3)); 58 | } 59 | 60 | @Test public void adjusted_other() throws Exception { 61 | Matching matching = Matching.empty(Graph.fromSmiles("CCCCC")); 62 | matching.match(0, 1); 63 | matching.match(2, 3); 64 | matching.match(1, 2); // 0-1 and 2-3 should not be 65 | 66 | assertThat(matching.other(1), is(2)); 67 | assertThat(matching.other(2), is(1)); 68 | } 69 | 70 | @Test(expected = IllegalArgumentException.class) 71 | public void adjusted_other_invalid() throws Exception { 72 | Matching matching = Matching.empty(Graph.fromSmiles("CCCCC")); 73 | matching.match(0, 1); 74 | matching.match(2, 3); 75 | matching.match(1, 2); // 0-1 and 2-3 should not be 76 | 77 | matching.other(0); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/ParsingHydrogenCountTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Test; 33 | 34 | import static org.hamcrest.CoreMatchers.is; 35 | import static org.junit.Assert.assertThat; 36 | import static org.junit.Assert.assertTrue; 37 | 38 | /** 39 | * Unit tests verify correct handling of hydrogen count for bracket atoms. 40 | * 41 | * @author John May 42 | */ 43 | public class ParsingHydrogenCountTest { 44 | 45 | @Test public void impliedHCount() { 46 | verify("H", 1); 47 | } 48 | 49 | @Test public void H0() { 50 | verify("H0", 0); 51 | } 52 | 53 | @Test public void H1() { 54 | verify("H1", 1); 55 | } 56 | 57 | @Test public void H2() { 58 | verify("H2", 2); 59 | } 60 | 61 | @Test public void H3() { 62 | verify("H3", 3); 63 | } 64 | 65 | @Test public void H4() { 66 | verify("H4", 4); 67 | } 68 | 69 | @Test public void H42() { 70 | verify("H42", 42); 71 | } 72 | 73 | @Test public void H101() { 74 | verify("H101", 101); 75 | } 76 | 77 | @Test public void noHCount() { 78 | CharBuffer buffer = CharBuffer.fromString("-1"); 79 | assertThat(Parser.readHydrogens(buffer), is(0)); 80 | assertTrue(buffer.nextIs('-')); 81 | } 82 | 83 | private void verify(String str, int count) { 84 | assertThat(Parser.readHydrogens(CharBuffer.fromString(str)), is(count)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/UnionFindTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Test; 33 | 34 | import static org.hamcrest.CoreMatchers.is; 35 | import static org.junit.Assert.assertThat; 36 | import static org.junit.Assert.assertTrue; 37 | 38 | /** @author John May */ 39 | public class UnionFindTest { 40 | 41 | @Test public void connected() { 42 | UnionFind uf = new UnionFind(100); 43 | uf.union(1, 5); 44 | uf.union(7, 9); 45 | uf.union(7, 5); 46 | uf.union(10, 11); 47 | uf.union(11, 50); 48 | uf.union(15, 1); 49 | uf.union(15, 50); 50 | assertTrue(uf.connected(1, 5)); 51 | assertTrue(uf.connected(1, 7)); 52 | assertTrue(uf.connected(1, 9)); 53 | assertTrue(uf.connected(1, 10)); 54 | assertTrue(uf.connected(1, 11)); 55 | assertTrue(uf.connected(1, 15)); 56 | assertTrue(uf.connected(1, 50)); 57 | } 58 | 59 | @Test public void find() { 60 | UnionFind uf = new UnionFind(100); 61 | uf.union(1, 5); 62 | uf.union(7, 9); 63 | uf.union(10, 11); 64 | uf.union(15, 1); 65 | uf.union(15, 50); 66 | assertThat(uf.find(1), is(50)); 67 | assertThat(uf.find(5), is(50)); 68 | assertThat(uf.find(7), is(7)); 69 | assertThat(uf.find(8), is(8)); 70 | assertThat(uf.find(10), is(10)); 71 | assertThat(uf.find(11), is(10)); 72 | assertThat(uf.find(15), is(50)); 73 | assertThat(uf.find(50), is(50)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/ParsingAtomClassTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Test; 33 | 34 | import static org.hamcrest.CoreMatchers.is; 35 | import static org.junit.Assert.assertThat; 36 | 37 | /** 38 | * Unit tests verify correct handling of atom class in bracket atoms. 39 | * 40 | * @author John May 41 | */ 42 | public class ParsingAtomClassTest { 43 | 44 | @Test(expected = InvalidSmilesException.class) 45 | public void invalid() throws Exception { 46 | verify(":", 0); 47 | } 48 | 49 | @Test public void none() throws Exception { 50 | verify("", 0); 51 | } 52 | 53 | @Test public void none2() throws Exception { 54 | verify("]", 0); 55 | } 56 | 57 | @Test public void zero() throws Exception { 58 | verify(":0", 0); 59 | } 60 | 61 | @Test public void one() throws Exception { 62 | verify(":1", 1); 63 | } 64 | 65 | @Test public void two() throws Exception { 66 | verify(":2", 2); 67 | } 68 | 69 | @Test public void fortyTwo() throws Exception { 70 | verify(":42", 42); 71 | } 72 | 73 | @Test public void fivePadded() throws Exception { 74 | verify(":005", 5); 75 | } 76 | 77 | @Test public void fortyTwoPadded() throws Exception { 78 | verify(":042", 42); 79 | } 80 | 81 | private void verify(String str, int atomClass) throws 82 | InvalidSmilesException { 83 | assertThat(Parser.readClass(CharBuffer.fromString(str)), is(atomClass)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /func/src/test/java/uk/ac/ebi/beam/ExplicitToImplicitTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.hamcrest.CoreMatchers; 33 | import org.junit.Assert; 34 | import org.junit.Test; 35 | 36 | /** @author John May */ 37 | public class ExplicitToImplicitTest { 38 | 39 | // function (f) to convert explicit bond types to implicit types 40 | ExplicitToImplicit f = new ExplicitToImplicit(); 41 | 42 | @Test 43 | public void phenylbenzene() throws Exception { 44 | Graph g = Parser.parse("c:1:c:c:c(c:c1)-c:2:c:c:c:c:c2"); 45 | Assert.assertThat(Generator.generate(g), 46 | CoreMatchers.is("c:1:c:c:c(c:c1)-c:2:c:c:c:c:c2")); 47 | Assert.assertThat(Generator.generate(f.apply(g)), 48 | CoreMatchers.is("c1ccc(cc1)-c2ccccc2")); 49 | } 50 | 51 | @Test 52 | public void benzene() throws Exception { 53 | Graph g = Parser.parse("c:1:c:c:c:c:c1"); 54 | Assert.assertThat(Generator.generate(g), 55 | CoreMatchers.is("c:1:c:c:c:c:c1")); 56 | Assert.assertThat(Generator.generate(f.apply(g)), 57 | CoreMatchers.is("c1ccccc1")); 58 | } 59 | 60 | @Test 61 | public void benzeneMixed() throws Exception { 62 | Graph g = Parser.parse("C:1:C:C:C:C:C1"); 63 | Assert.assertThat(Generator.generate(g), 64 | CoreMatchers.is("C:1:C:C:C:C:C1")); 65 | Assert.assertThat(Generator.generate(f.apply(g)), 66 | CoreMatchers.is("C:1:C:C:C:C:C1")); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/Matching.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | /** 8 | * Defines a matching on a graph. A matching or independent edge set is a set of 9 | * edges without common vertices. A matching is perfect if every vertex in the 10 | * graph is matched. Another way of thinking about the matching is that each 11 | * vertex is incident to exactly one matched edge.

12 | * 13 | * This class provides storage and manipulation of a matching. A new match is 14 | * added with {@link #match(int, int)}, any existing match for the newly matched 15 | * vertices is non-longer available. For convenience {@link #matches()} provides 16 | * the current independent edge set. 17 | * 18 | * @author John May 19 | */ 20 | final class Matching { 21 | 22 | /** Indicates an unmatched vertex. */ 23 | private static final int UNMATCHED = -1; 24 | 25 | /** Storage of which each vertex is matched with. */ 26 | private final int[] match; 27 | 28 | /** 29 | * Create a matching of the given size. 30 | * 31 | * @param n number of items 32 | */ 33 | private Matching(int n) { 34 | this.match = new int[n]; 35 | Arrays.fill(match, UNMATCHED); 36 | } 37 | 38 | boolean matched(int v) { 39 | return !unmatched(v); 40 | } 41 | 42 | /** 43 | * Is the vertex v 'unmatched'. 44 | * 45 | * @param v a vertex 46 | * @return the vertex has no matching 47 | */ 48 | boolean unmatched(int v) { 49 | int w = match[v]; 50 | return w < 0 || match[w] != v; 51 | } 52 | 53 | /** 54 | * Access the vertex matched with 'v'. 55 | * 56 | * @param v a vertex 57 | * @return matched vertex 58 | * @throws IllegalArgumentException the vertex is currently unmatched 59 | */ 60 | int other(int v) { 61 | if (unmatched(v)) 62 | throw new IllegalArgumentException(v + " is not matched"); 63 | return match[v]; 64 | } 65 | 66 | /** 67 | * Add the edge '{u,v}' to the matched edge set. Any existing matches for 68 | * 'u' or 'v' are removed from the matched set. 69 | * 70 | * @param u a vertex 71 | * @param v another vertex 72 | */ 73 | void match(int u, int v) { 74 | // set the new match, don't need to update existing - we only provide 75 | // access to bidirectional mappings 76 | match[u] = v; 77 | match[v] = u; 78 | } 79 | 80 | /** 81 | * Access the current non-redundant set of edges. 82 | * 83 | * @return matched pairs 84 | */ 85 | Iterable matches() { 86 | 87 | List tuples = new ArrayList(match.length / 2); 88 | 89 | for (int v = 0; v < match.length; v++) { 90 | int w = match[v]; 91 | if (w > v && match[w] == v) { 92 | tuples.add(Tuple.of(v, w)); 93 | } 94 | } 95 | 96 | return tuples; 97 | } 98 | 99 | /** 100 | * Allocate a matching with enough capacity for the given graph. 101 | * 102 | * @param g a graph 103 | * @return matching 104 | */ 105 | static Matching empty(Graph g) { 106 | return new Matching(g.order()); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/Tuple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | /** 33 | * A simple utility class for storing two primitive integers. 34 | * 35 | * @author John May 36 | */ 37 | final class Tuple { 38 | 39 | private final int fst, snd; 40 | 41 | private Tuple(int fst, int snd) { 42 | this.fst = fst; 43 | this.snd = snd; 44 | } 45 | 46 | /** 47 | * Access the first value of the tuple. 48 | * 49 | * @return value 50 | */ 51 | int first() { 52 | return fst; 53 | } 54 | 55 | /** 56 | * Access the second value of the tuple. 57 | * 58 | * @return value 59 | */ 60 | int second() { 61 | return snd; 62 | } 63 | 64 | /** @inheritDoc */ 65 | @Override 66 | public boolean equals(Object o) { 67 | if (this == o) return true; 68 | if (o == null || getClass() != o.getClass()) return false; 69 | 70 | Tuple tuple = (Tuple) o; 71 | 72 | if (fst != tuple.fst) return false; 73 | if (snd != tuple.snd) return false; 74 | 75 | return true; 76 | } 77 | 78 | /** @inheritDoc */ 79 | @Override 80 | public int hashCode() { 81 | int result = fst; 82 | result = 31 * result + snd; 83 | return result; 84 | } 85 | 86 | /** @inheritDoc */ 87 | @Override public String toString() { 88 | return "{" + fst + ", " + snd + "}"; 89 | } 90 | 91 | /** 92 | * Create a new tuple for the provided values. 93 | * 94 | * @param fst a value 95 | * @param snd another value 96 | * @return a tuple of the two values 97 | */ 98 | static Tuple of(int fst, int snd) { 99 | return new Tuple(fst, snd); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /func/src/test/java/uk/ac/ebi/beam/ToSubsetAtomsTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.hamcrest.CoreMatchers; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.junit.Assert.assertThat; 9 | 10 | /** @author John May */ 11 | public class ToSubsetAtomsTest { 12 | 13 | @Test public void unknown() throws Exception { 14 | transform("[*]", "*"); 15 | } 16 | 17 | @Test public void inorganic() throws Exception { 18 | transform("[Ne]", "[Ne]"); 19 | } 20 | 21 | @Test public void methane() throws Exception { 22 | transform("[CH4]", "C"); 23 | } 24 | 25 | @Test public void monovalent_carbon() throws Exception { 26 | transform("[CH3]", "[CH3]"); 27 | } 28 | 29 | @Test public void divalent_carbon() throws Exception { 30 | transform("[CH2]", "[CH2]"); 31 | } 32 | 33 | @Test public void trivalent_carbon() throws Exception { 34 | transform("[CH]", "[CH]"); 35 | transform("[CH1]", "[CH]"); 36 | } 37 | 38 | @Test public void carbon_12() throws Exception { 39 | // note the isotope is specified and so must be a bracket atom 40 | transform("[12C]", "[12C]"); 41 | } 42 | 43 | @Test public void carbon_13() throws Exception { 44 | transform("[13C]", "[13C]"); 45 | } 46 | 47 | @Test public void carbon_14() throws Exception { 48 | transform("[14C]", "[14C]"); 49 | } 50 | 51 | @Test public void oxidanide() throws Exception { 52 | transform("[OH-]", "[OH-]"); 53 | } 54 | 55 | @Test public void azanium() throws Exception { 56 | transform("[NH4+]", "[NH4+]"); 57 | } 58 | 59 | @Test public void ethane_withAtomClass() throws Exception { 60 | transform("[CH3:1][CH3:0]", "[CH3:1]C"); 61 | } 62 | 63 | @Test public void ethanol() throws InvalidSmilesException { 64 | transform("[CH3][CH2][OH]", "CCO"); 65 | } 66 | 67 | @Test public void stereoSpecification() throws InvalidSmilesException { 68 | transform("[C@H]([NH2])([OH])[CH3]", "[C@H](N)(O)C"); 69 | transform("[C@@H]([NH2])([OH])[CH3]", "[C@@H](N)(O)C"); 70 | } 71 | 72 | @Test public void noStereoSpecification() throws InvalidSmilesException { 73 | transform("[CH]([NH2])([OH])[CH3]", "C(N)(O)C"); 74 | } 75 | 76 | @Test public void tricyclazole() throws Exception { 77 | transform("[CH3][c]1[cH][cH][cH][c]2[s][c]3[n][n][cH][n]3[c]12", 78 | "Cc1cccc2sc3nncn3c12"); 79 | } 80 | 81 | @Test public void pyrole_kekule() throws Exception { 82 | transform("[NH]1[CH]=[CH]N=[CH]1", 83 | "N1C=CN=C1"); 84 | } 85 | 86 | @Test public void pyrole() throws Exception { 87 | transform("[nH]1[cH][cH][n][cH]1", 88 | "[nH]1ccnc1"); 89 | } 90 | 91 | @Test public void zinc_1() throws Exception { 92 | transform("c1cc(ccc1/C=c\\2/c(=O)o/c(=C\\Cl)/[nH]2)F", 93 | "c1cc(ccc1/C=c\\2/c(=O)o/c(=C\\Cl)/[nH]2)F"); 94 | } 95 | 96 | 97 | private void transform(String input, String expected) throws 98 | InvalidSmilesException { 99 | Graph g = Parser.parse(input); 100 | ImplicitToExplicit ite = new ImplicitToExplicit(); 101 | ToSubsetAtoms tsa = new ToSubsetAtoms(); 102 | ExplicitToImplicit eti = new ExplicitToImplicit(); 103 | String actual = Generator.generate(eti.apply( 104 | tsa.apply( 105 | ite.apply(g)))); 106 | Assert.assertThat(actual, CoreMatchers.is(expected)); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/IntStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import java.util.Arrays; 33 | 34 | /** 35 | * A lightweight stack data structure for primitive 'int' types. For general 36 | * purpose programming {@link java.util.ArrayDeque} is preferred (note {@link 37 | * java.util.Stack} is synchronised). 38 | * 39 | * @author John May 40 | * @see java.util.ArrayDeque 41 | */ 42 | final class IntStack { 43 | 44 | /** Storage of values. */ 45 | private int[] xs; 46 | 47 | /** Number of items in the stack */ 48 | private int n; 49 | 50 | /** 51 | * Create a new stack with specified initial capacity. 52 | * 53 | * @param n capacity of the stack 54 | */ 55 | IntStack(final int n) { 56 | this.xs = new int[n]; 57 | } 58 | 59 | /** 60 | * Push the value {@literal x} on to the stack. 61 | * 62 | * @param x value to push 63 | */ 64 | void push(final int x) { 65 | if (n == xs.length) 66 | xs = Arrays.copyOf(xs, xs.length * 2); 67 | xs[n++] = x; 68 | } 69 | 70 | /** 71 | * Access and remove the value on the top of the stack. No check is made as 72 | * to whether the stack is empty. 73 | * 74 | * @return value on top of the stack 75 | */ 76 | int pop() { 77 | return xs[--n]; 78 | } 79 | 80 | /** 81 | * Access the value on top of the stack without removing it. No check is 82 | * made as to whether the stack is empty. 83 | * 84 | * @return the last value added 85 | */ 86 | int peek() { 87 | return xs[n - 1]; 88 | } 89 | 90 | /** 91 | * Determine if there are any items on the stack. 92 | * 93 | * @return whether the stack is empty 94 | */ 95 | boolean empty() { 96 | return n == 0; 97 | } 98 | 99 | /** 100 | * Number of items on the stack. 101 | * 102 | * @return size 103 | */ 104 | public int size() { 105 | return n; 106 | } 107 | 108 | /** Remove all values from the stack. */ 109 | public void clear() { 110 | n = 0; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /func/src/main/java/uk/ac/ebi/beam/ImplicitToExplicit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | /** 33 | * Convert a chemical graph with implicit edge labels to one with explicit 34 | * single or aromatic edge labels. 35 | * 36 | * @author John May 37 | */ 38 | final class ImplicitToExplicit extends AbstractFunction { 39 | 40 | /** 41 | * Transform all implicit to explicit bonds. The original graph is 42 | * unmodified 43 | * 44 | * @param g a chemical graph 45 | * @return new chemical graph but with all explicit bonds 46 | */ 47 | public Graph apply(final Graph g) { 48 | 49 | Graph h = new Graph(g.order()); 50 | 51 | // copy atom/topology information 52 | for (int u = 0; u < g.order(); u++) { 53 | h.addAtom(g.atom(u)); 54 | h.addTopology(g.topologyOf(u)); 55 | } 56 | 57 | // apply edges 58 | for (int u = 0; u < g.order(); u++) { 59 | for (final Edge e : g.edges(u)) { 60 | if (e.other(u) > u) 61 | h.addEdge(toExplicitEdge(g, e)); 62 | } 63 | } 64 | 65 | return h; 66 | } 67 | 68 | /** 69 | * Given a chemical graph and an edge in that graph, return the explicit 70 | * form of that edge. Neither the graph or the edge is modified, if the edge 71 | * is already explicit then 'e' is returned. 72 | * 73 | * @param g chemical graph 74 | * @param e an edge of g 75 | * @return the edge with specified explicit bond type 76 | */ 77 | static Edge toExplicitEdge(final Graph g, final Edge e) { 78 | final int u = e.either(), v = e.other(u); 79 | if (e.bond() == Bond.IMPLICIT) { 80 | return new Edge(u, v, 81 | type(g.atom(u), 82 | g.atom(v))); 83 | } 84 | return e; 85 | } 86 | 87 | /** 88 | * Given two atoms which are implicitly connected determine the explicit 89 | * bond type. The type is 'aromatic' if both atoms are aromatic, if either 90 | * or both atoms are non-aromatic then the bond type is 'single'. 91 | * 92 | * @param u an atom 93 | * @param v another atom (connected to u) 94 | * @return the bond type 95 | */ 96 | static Bond type(Atom u, Atom v) { 97 | return u.aromatic() && v.aromatic() ? Bond.AROMATIC : Bond.SINGLE; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/InvalidSmilesException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import java.io.IOException; 33 | 34 | /** 35 | * An exception thrown when parsing malformed SMILES. 36 | * 37 | * @author John May 38 | */ 39 | final class InvalidSmilesException extends IOException { 40 | 41 | InvalidSmilesException(String message, CharBuffer buffer) { 42 | this(message, buffer, 0); 43 | } 44 | 45 | InvalidSmilesException(String message, CharBuffer buffer, int offset) { 46 | super(message + display(buffer, offset)); 47 | } 48 | 49 | InvalidSmilesException(String message) { 50 | super(message); 51 | } 52 | 53 | /** 54 | * Displays the character buffer and marks on the next line the current 55 | * position in the buffer. 56 | * 57 | *

 58 |      * invalid bracket atom:
 59 |      * C[CCCC
 60 |      *    ^
 61 |      * 
62 | * 63 | * @param buffer a character buffer 64 | * @return a 3 line string showing the buffer and it's current position 65 | */ 66 | static String display(final CharBuffer buffer, int offset) { 67 | StringBuilder sb = new StringBuilder(); 68 | sb.append('\n'); 69 | sb.append(buffer); 70 | sb.append('\n'); 71 | for (int i = 1; i < (buffer.position() + offset); i++) 72 | sb.append(' '); 73 | sb.append('^'); 74 | return sb.toString(); 75 | } 76 | 77 | static String display(final CharBuffer buffer, int offset, int offset2) { 78 | StringBuilder sb = new StringBuilder(); 79 | sb.append('\n'); 80 | sb.append(buffer); 81 | sb.append('\n'); 82 | for (int i = 1; i < buffer.length(); i++) { 83 | if (i == buffer.position+offset || i == buffer.position+offset2) 84 | sb.append('^'); 85 | else 86 | sb.append(' '); 87 | } 88 | return sb.toString(); 89 | } 90 | 91 | /** 92 | * Utility for invalid bracket atom error. 93 | * 94 | * @param buffer the current buffer 95 | * @return the invalid smiles exception with buffer information 96 | */ 97 | static InvalidSmilesException invalidBracketAtom(CharBuffer buffer) { 98 | return new InvalidSmilesException("Invalid bracket atom, [ ? ? ? ? ? ], SMILES may be truncated:", 99 | buffer); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /func/src/main/java/uk/ac/ebi/beam/ExplicitToImplicit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | /** 33 | * Convert a chemical graph with explicit single or aromatic edge labels to one 34 | * with implicit edge labels. 35 | * 36 | * @author John May 37 | */ 38 | final class ExplicitToImplicit 39 | extends AbstractFunction { 40 | 41 | /** 42 | * Transform all explicit to implicit bonds. The original graph is 43 | * unmodified. 44 | * 45 | * @param g a chemical graph 46 | * @return new chemical graph but with all explicit bonds 47 | */ 48 | public Graph apply(final Graph g) { 49 | 50 | Graph h = new Graph(g.order()); 51 | 52 | // atom/topology information doesn't change 53 | for (int u = 0; u < g.order(); u++) { 54 | h.addAtom(g.atom(u)); 55 | h.addTopology(g.topologyOf(u)); 56 | } 57 | 58 | // transform edges 59 | for (int u = 0; u < g.order(); u++) { 60 | for (final Edge e : g.edges(u)) { 61 | if (e.other(u) > u) 62 | h.addEdge(toImplicitEdge(g, e)); 63 | } 64 | } 65 | 66 | return h; 67 | } 68 | 69 | /** 70 | * Given a chemical graph and an edge in that graph, return the implicit 71 | * form of that edge. Neither the graph or the edge is modified, if the edge 72 | * is already explicit then 'e' is returned. 73 | * 74 | * @param g chemical graph 75 | * @param e an edge of g 76 | * @return the edge with specified explicit bond type 77 | */ 78 | static Edge toImplicitEdge(final Graph g, final Edge e) { 79 | final int u = e.either(), v = e.other(u); 80 | if (e.bond() == Bond.SINGLE || e.bond() == Bond.AROMATIC) { 81 | return new Edge(u, v, 82 | type(g.atom(u), 83 | g.atom(v), 84 | e.bond())); 85 | } 86 | return e; 87 | } 88 | 89 | /** 90 | * Given two atoms which are explicit connected determine the implicit bond 91 | * type. If both atoms are aromatic but connected by a single bond the bond 92 | * type is {@link Bond#SINGLE} otherwise it is implicit. 93 | * 94 | * @param u an atom 95 | * @param v another atom (connected to u) 96 | * @param b explicit bond type 97 | * @return the bond type 98 | */ 99 | static Bond type(Atom u, Atom v, Bond b) { 100 | if (u.aromatic() && v.aromatic()) 101 | return b == Bond.AROMATIC ? Bond.IMPLICIT : b; 102 | else 103 | return b == Bond.AROMATIC ? Bond.AROMATIC : Bond.IMPLICIT; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/UnionFind.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import java.util.Arrays; 33 | 34 | /** 35 | * Fixed size Union-Find/Disjoint-Set implementation. 36 | * 37 | *
38 | * UnionFind uf = new UnionFind(11); 39 | * uf.join(0, 1); 40 | * uf.join(1, 10); 41 | * uf.connected(0, 10); // are 0 and 10 joint? 42 | * uf.find(10); // id for the set to which '10' belongs 43 | *
44 | * 45 | * @author John May 46 | */ 47 | final class UnionFind { 48 | 49 | /** 50 | * Each element is either a connected (negative), points to another element. 51 | * The size of the set is indicated by the size of the negation on the 52 | * connected. 53 | */ 54 | final int[] forest; 55 | 56 | /** 57 | * Create a new UnionFind data structure with enough space for 'n' 58 | * elements. 59 | * 60 | * @param n number of elements 61 | */ 62 | UnionFind(int n) { 63 | this.forest = new int[n]; 64 | Arrays.fill(forest, -1); 65 | } 66 | 67 | /** 68 | * Find the identifier of the set to which 'u' belongs. 69 | * 70 | * @param u an element 71 | * @return the connected 72 | */ 73 | int find(int u) { 74 | return forest[u] < 0 ? u : (forest[u] = find(forest[u])); 75 | } 76 | 77 | /** 78 | * Join the sets containing 'u' and 'v'. 79 | * 80 | * @param u an element 81 | * @param v another element 82 | */ 83 | void union(int u, int v) { 84 | 85 | int uRoot = find(u); 86 | int vRoot = find(v); 87 | 88 | if (uRoot == vRoot) 89 | return; 90 | 91 | if (forest[uRoot] < forest[vRoot]) 92 | join(vRoot, uRoot); 93 | else 94 | join(uRoot, vRoot); 95 | } 96 | 97 | /** 98 | * Join two disjoint sets. The larger set is appended onto the smaller set. 99 | * 100 | * @param sRoot root of a set (small) 101 | * @param lRoot root of another set (large) 102 | */ 103 | private void join(int sRoot, int lRoot) { 104 | forest[sRoot] = forest[sRoot] + forest[lRoot]; 105 | forest[lRoot] = sRoot; 106 | } 107 | 108 | /** 109 | * Are the elements 'u' and 'v' in the same set. 110 | * 111 | * @param u an element 112 | * @param v another element 113 | * @return the elements are in the same set. 114 | */ 115 | boolean connected(int u, int v) { 116 | return find(u) == find(v); 117 | } 118 | 119 | /** 120 | * Clear any joint sets - all items are once disjoint and are singletons. 121 | */ 122 | void clear() { 123 | for (int i = 0; i < forest.length; i++) 124 | forest[i] = -1; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/AtomCentricDBConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Assert; 33 | import org.junit.Test; 34 | 35 | import static org.hamcrest.CoreMatchers.is; 36 | 37 | /** @author John May */ 38 | public class AtomCentricDBConfigTest { 39 | 40 | @Test public void difluoroethene_implConfig() throws 41 | InvalidSmilesException { 42 | String input = "F[C@H]=[C@H]F"; 43 | Graph g = Parser.parse(input); 44 | Assert.assertThat(g.topologyOf(1) 45 | .configuration(), is(Configuration.DB1)); 46 | Assert.assertThat(g.topologyOf(2) 47 | .configuration(), is(Configuration.DB1)); 48 | } 49 | 50 | @Test public void difluoroethene_implConfig2() throws 51 | InvalidSmilesException { 52 | String input = "F[C@@H]=[C@@H]F"; 53 | Graph g = Parser.parse(input); 54 | Assert.assertThat(g.topologyOf(1) 55 | .configuration(), is(Configuration.DB2)); 56 | Assert.assertThat(g.topologyOf(2) 57 | .configuration(), is(Configuration.DB2)); 58 | } 59 | 60 | @Test public void difluoroethene_expConfig() throws InvalidSmilesException { 61 | String input = "F[C@DB1H]=[C@DB1H]F"; 62 | Graph g = Parser.parse(input); 63 | Assert.assertThat(g.topologyOf(1) 64 | .configuration(), is(Configuration.DB1)); 65 | Assert.assertThat(g.topologyOf(2) 66 | .configuration(), is(Configuration.DB1)); 67 | } 68 | 69 | @Test public void difluoroethene_expConfig2() throws 70 | InvalidSmilesException { 71 | String input = "F[C@DB2H]=[C@DB2H]F"; 72 | Graph g = Parser.parse(input); 73 | Assert.assertThat(g.topologyOf(1) 74 | .configuration(), is(Configuration.DB2)); 75 | Assert.assertThat(g.topologyOf(2) 76 | .configuration(), is(Configuration.DB2)); 77 | } 78 | 79 | @Test public void difluoroethene() throws InvalidSmilesException { 80 | GeneratorTest.assertRoundTrip("F[C@H]=[C@H]F"); 81 | GeneratorTest.assertRoundTrip("F[C@@H]=[C@@H]F"); 82 | GeneratorTest.assertRoundTrip("F[C@H]=[C@@H]F"); 83 | GeneratorTest.assertRoundTrip("F[C@@H]=[C@H]F"); 84 | } 85 | 86 | @Test public void difluoroethene_permute() throws InvalidSmilesException { 87 | GeneratorTest.assertRoundTrip("F[C@H]=[C@H]F", 88 | new int[]{1, 0, 2, 3}, 89 | "[C@@H](F)=[C@H]F"); 90 | GeneratorTest.assertRoundTrip("[C@@H](F)=[C@H]F", 91 | new int[]{1, 0, 2, 3}, 92 | "F[C@H]=[C@H]F"); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/BiconnectedComponents.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.BitSet; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | /** 10 | * see. http://en.wikipedia.org/wiki/Biconnected_component 11 | * 12 | * @author John May 13 | */ 14 | final class BiconnectedComponents { 15 | 16 | private int[] depth; 17 | 18 | private final Graph g; 19 | private final Edge[] stack; 20 | private int nstack = 0; 21 | 22 | private final List> components = new ArrayList>(2); 23 | 24 | private final BitSet cyclic = new BitSet(); 25 | private final BitSet simple = new BitSet(); 26 | 27 | int count = 0; 28 | int numfrags = 0; 29 | 30 | BiconnectedComponents(Graph g) { 31 | this(g, true); 32 | } 33 | 34 | BiconnectedComponents(Graph g, boolean storeComponents) { 35 | this.depth = new int[g.order()]; 36 | this.g = g; 37 | this.stack = new Edge[g.size()]; 38 | 39 | if (storeComponents) { 40 | for (int u = 0; count < g.order(); u++) { 41 | if (depth[u] == 0) { 42 | visitWithComp(u, null); 43 | ++numfrags; 44 | } 45 | } 46 | } else { 47 | for (int u = 0; count < g.order(); u++) { 48 | if (depth[u] == 0) { 49 | visit(u, null); 50 | ++numfrags; 51 | } 52 | } 53 | } 54 | } 55 | 56 | private int visit(final int u, final Edge from) { 57 | depth[u] = ++count; 58 | int d = g.degree(u); 59 | int lo = count + 1; 60 | 61 | while (--d>=0) { 62 | final Edge e = g.edgeAt(u, d); 63 | if (e==from) continue; 64 | final int v = e.other(u); 65 | if (depth[v] == 0) { 66 | int res = visit(v, e); 67 | if (res < lo) 68 | lo = res; 69 | } 70 | else if (depth[v] < lo) { 71 | lo = depth[v]; 72 | } 73 | } 74 | if (lo <= depth[u]) 75 | cyclic.set(u); 76 | return lo; 77 | } 78 | 79 | private int visitWithComp(final int u, final Edge from) { 80 | depth[u] = ++count; 81 | int j = g.degree(u); 82 | int lo = count + 1; 83 | while (--j>=0) { 84 | 85 | final Edge e = g.edgeAt(u, j); 86 | if (e==from) continue; 87 | 88 | final int v = e.other(u); 89 | if (depth[v] == 0) { 90 | stack[nstack] = e; 91 | ++nstack; 92 | int tmp = visitWithComp(v, e); 93 | if (tmp == depth[u]) 94 | storeWithComp(e); 95 | else if (tmp > depth[u]) 96 | --nstack; 97 | if (tmp < lo) 98 | lo = tmp; 99 | } 100 | else if (depth[v] < depth[u]) { 101 | // back edge 102 | stack[nstack] = e; 103 | ++nstack; 104 | if (depth[v] < lo) 105 | lo = depth[v]; 106 | } 107 | } 108 | return lo; 109 | } 110 | 111 | private void storeWithComp(Edge e) { 112 | List component = new ArrayList(6); 113 | Edge f; 114 | 115 | final BitSet tmp = new BitSet(); 116 | 117 | // count the number of unique vertices and edges 118 | int numEdges = 0; 119 | boolean spiro = false; 120 | 121 | do { 122 | f = stack[--nstack]; 123 | int v = f.either(); 124 | int w = f.other(v); 125 | 126 | if (cyclic.get(v) || cyclic.get(w)) 127 | spiro = true; 128 | 129 | tmp.set(v); 130 | tmp.set(w); 131 | 132 | component.add(f); 133 | numEdges++; 134 | } while (f != e); 135 | 136 | cyclic.or(tmp); 137 | 138 | if (!spiro && tmp.cardinality() == numEdges) 139 | simple.or(tmp); 140 | 141 | components.add(Collections.unmodifiableList(component)); 142 | } 143 | 144 | public List> components() { 145 | return Collections.unmodifiableList(components); 146 | } 147 | 148 | BitSet cyclic() { 149 | return cyclic; 150 | } 151 | 152 | public boolean connected() { 153 | return numfrags < 2; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/ParsingAtomChargeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Test; 33 | 34 | import static org.hamcrest.CoreMatchers.is; 35 | import static org.junit.Assert.assertThat; 36 | import static org.junit.Assert.assertTrue; 37 | 38 | /** 39 | * Unit tests verify correct handling of charge for bracket atoms. 40 | * 41 | * @author John May 42 | */ 43 | public class ParsingAtomChargeTest { 44 | 45 | @Test public void implicitPlusOne() { 46 | verify("+", +1); 47 | } 48 | 49 | @Test public void implicitPlusTwo() { 50 | verify("++", +2); 51 | } 52 | 53 | @Test public void implicitPlusThree() { 54 | verify("+++", +3); 55 | } 56 | 57 | @Test public void implicitPlusFour() { 58 | verify("++++", +4); 59 | } 60 | 61 | @Test public void implicitMinusOne() { 62 | verify("-", -1); 63 | } 64 | 65 | @Test public void implicitMinusTwo() { 66 | verify("--", -2); 67 | } 68 | 69 | @Test public void implicitMinusThree() { 70 | verify("---", -3); 71 | } 72 | 73 | @Test public void implicitMinusFour() { 74 | verify("----", -4); 75 | } 76 | 77 | @Test public void plusOne() { 78 | verify("+1", +1); 79 | } 80 | 81 | @Test public void plusTwo() { 82 | verify("+2", +2); 83 | } 84 | 85 | @Test public void minusOne() { 86 | verify("-1", -1); 87 | } 88 | 89 | @Test public void minusTwo() { 90 | verify("-2", -2); 91 | } 92 | 93 | @Test public void noCharge() { 94 | CharBuffer buffer = CharBuffer.fromString(":"); 95 | assertThat(Parser.readCharge(buffer), is(0)); 96 | assertTrue(buffer.nextIs(':')); 97 | } 98 | 99 | // really bad form but parsed okay 100 | @Test public void minusPlusOne() { 101 | verify("-+1", 0); 102 | } 103 | 104 | // really bad form but parsed okay 105 | @Test public void plusPlusMinusOne() { 106 | verify("++-1", +1); 107 | } 108 | 109 | // really bad form but parsed okay 110 | @Test public void minusMinusPlusOne() { 111 | verify("--+1", -1); 112 | } 113 | 114 | // really bad form but parsed okay 115 | @Test public void plusMinusOne() { 116 | verify("+-1", 0); 117 | } 118 | 119 | // really bad form but parsed okay 120 | @Test public void plusPlusOne() { 121 | verify("++1", 2); 122 | } 123 | 124 | // really bad form but parsed okay 125 | @Test public void plusPlusTwo() { 126 | verify("++2", 3); 127 | } 128 | 129 | // An implementation is required to accept charges in the range -15 to +15 130 | @Test public void rangeCheck() { 131 | for (int i = -15; i <= 15; i++) 132 | verify((i > 0 ? "+" : "") + Integer.toString(i), i); 133 | } 134 | 135 | private void verify(String str, int charge) { 136 | assertThat(Parser.readCharge(CharBuffer.fromString(str)), is(charge)); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /func/src/test/java/uk/ac/ebi/beam/RemoveUpDownBondsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.hamcrest.CoreMatchers; 33 | import org.junit.Assert; 34 | import org.junit.Test; 35 | 36 | import static org.hamcrest.CoreMatchers.is; 37 | 38 | /** @author John May */ 39 | public class RemoveUpDownBondsTest { 40 | 41 | @Test public void e_butene_expH() throws Exception { 42 | transform("C\\C(\\[H])=C\\C", 43 | "C\\C([H])=C\\C"); 44 | transform("C/C(/[H])=C/C", 45 | "C/C([H])=C/C"); 46 | transform("C\\C(\\[H])=C(/[H])\\C", 47 | "C\\C([H])=C(/[H])C"); 48 | transform("C/C(/[H])=C(\\[H])/C", 49 | "C/C([H])=C(\\[H])C"); 50 | } 51 | 52 | @Test public void z_butene_expH() throws Exception { 53 | transform("C\\C(\\[H])=C/C", 54 | "C\\C([H])=C/C"); 55 | transform("C/C(/[H])=C\\C", 56 | "C/C([H])=C\\C"); 57 | transform("C\\C(\\[H])=C(\\[H])/C", 58 | "C\\C([H])=C(\\[H])C"); 59 | transform("C/C(/[H])=C(/[H])\\C", 60 | "C/C([H])=C(/[H])C"); 61 | } 62 | 63 | @Test public void e_e_hexadiene_expH() throws InvalidSmilesException { 64 | transform("C\\C(\\[H])=C(/[H])\\C(\\[H])=C(/[H])\\C", 65 | "C\\C([H])=C(/[H])\\C([H])=C(/[H])C"); 66 | } 67 | 68 | @Test public void e_e_hexadiene_expH2() throws Exception { 69 | transform("[H]\\C(\\C(\\[H])=C(\\C)/[H])=C(/C)\\[H]", 70 | "[H]\\C(\\C([H])=C(\\C)[H])=C(/C)[H]"); 71 | } 72 | 73 | @Test public void e_e_hexadiene_expH3() throws Exception { 74 | transform("[H]/C(/C(=C(/C)\\[H])[H])=C(/[H])\\C", 75 | "[H]/C(/C(=C(/C)[H])[H])=C(/[H])C"); 76 | } 77 | 78 | @Test public void e_e_hexadiene_expH4() throws Exception { 79 | transform("C\\C(\\[H])=C(/[H])\\C(=C(/[H])\\C)\\[H]", 80 | "C\\C([H])=C(/[H])\\C(=C(/[H])C)[H]"); 81 | } 82 | 83 | @Test public void e_e_hexadiene_expH5() throws Exception { 84 | transform("[H]/C(/C)=C(/[H])\\C(\\[H])=C(/[H])\\C", 85 | "[H]/C(C)=C(/[H])\\C([H])=C(/[H])C"); 86 | } 87 | 88 | @Test public void e_e_hexadiene_permute() throws Exception { 89 | String input = "C\\C(=C(\\C(=C(/[H])\\C)\\[H])/[H])\\[H]"; 90 | int[] p = new int[]{7, 2, 4, 1, 3, 6, 8, 9, 0, 5}; 91 | Graph g = Parser.parse(input); 92 | Assert.assertThat(Generator.generate(g.permute(p)), 93 | CoreMatchers 94 | .is("[H]\\C(\\C(=C(/[H])\\C)\\[H])=C(\\[H])/C")); 95 | Assert.assertThat(Generator.generate(new RemoveUpDownBonds().apply(g.permute(p))), 96 | CoreMatchers.is("[H]\\C(\\C(=C(/[H])C)[H])=C(\\[H])C")); 97 | } 98 | 99 | static void transform(String smi, String exp) throws 100 | InvalidSmilesException { 101 | Assert.assertThat(Generator.generate(new RemoveUpDownBonds() 102 | .apply(Parser.parse(smi))), 103 | CoreMatchers.is(exp)); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/ElectronAssignment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import java.util.BitSet; 33 | 34 | /** 35 | * Verifies delocalised electrons can be assigned to a structure without 36 | * changing bond orders. To check and assign the electrons please use {@see 37 | * Localise} or {@see Graph#kekule}. Although faster than assigning a Kekulé 38 | * structure the method is the same and returning a structure with specified 39 | * bond orders is usually preferred. 40 | * 41 | * @author John May 42 | * @see Localise 43 | * @see uk.ac.ebi.beam.Graph#kekule() 44 | */ 45 | final class ElectronAssignment { 46 | 47 | private ElectronAssignment() { 48 | } 49 | 50 | /** 51 | * Check if it is possible to assign electrons to the subgraph (specified by 52 | * the set bits in of {@code bs}). Each connected subset is counted up and 53 | * checked for odd cardinality. 54 | * 55 | * @param g graph 56 | * @param bs binary set indicated vertices for the subgraph 57 | * @return there is an odd cardinality subgraph 58 | */ 59 | private static boolean containsOddCardinalitySubgraph(Graph g, BitSet bs) { 60 | 61 | // mark visited those which are not in any subgraph 62 | boolean[] visited = new boolean[g.order()]; 63 | for (int i = bs.nextClearBit(0); i < g.order(); i = bs.nextClearBit(i + 1)) 64 | visited[i] = true; 65 | 66 | // from each unvisited vertices visit the connected vertices and count 67 | // how many there are in this component. if there is an odd number there 68 | // is no assignment of double bonds possible 69 | for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) { 70 | if (!visited[i] && isOdd(visit(g, i, 0, visited))) 71 | return true; 72 | } 73 | 74 | return false; 75 | } 76 | 77 | 78 | /** 79 | * Determine the size the connected component using a depth-first-search. 80 | * 81 | * @param g graph 82 | * @param v vertex 83 | * @param c count 84 | * @param visited which vertices have been visited 85 | * @return size of the component from {@code v} 86 | */ 87 | private static int visit(Graph g, int v, int c, boolean[] visited) { 88 | visited[v] = true; 89 | for (final Edge e : g.edges(v)) { 90 | int w = e.other(v); 91 | if (!visited[w] && e.bond().order() == 1) 92 | c = visit(g, w, c, visited); 93 | } 94 | return 1 + c; 95 | } 96 | 97 | /** 98 | * Test if an a number, {@code x} is odd. 99 | * 100 | * @param x a number 101 | * @return the number is odd 102 | */ 103 | private static boolean isOdd(int x) { 104 | return (x & 0x1) == 1; 105 | } 106 | 107 | /** 108 | * Utility method to verify electrons can be assigned. 109 | * 110 | * @param g graph to check 111 | * @return electrons could be assigned to delocalised structure 112 | */ 113 | static boolean verify(Graph g) { 114 | return g.getFlags(Graph.HAS_AROM) == 0 || !containsOddCardinalitySubgraph(g, Localise.buildSet(g, new BitSet())); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/Atom.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | /** 33 | * Defines properties of a atom that can be encoded in SMILES. Atoms can be 34 | * built using the {@link AtomBuilder} class. 35 | * 36 | * @author John May 37 | * @see AtomBuilder 38 | */ 39 | public interface Atom { 40 | 41 | /** 42 | * The isotope number of the atom. If the isotope is undefined (default) a 43 | * value -1 is returned. 44 | * 45 | * @return isotope number 46 | */ 47 | int isotope(); 48 | 49 | /** 50 | * The element of the atom. 51 | * 52 | * @return element 53 | */ 54 | Element element(); 55 | 56 | /** 57 | * An label attached to an element (input only). Although invalid via the 58 | * specification 'CCC[R]' etc can occur in the 'wild'. If found the parser 59 | * provides an 'Unknown' element and a specified label. Not the labels are 60 | * never written. By default the label is the element symbol. 61 | * 62 | * @return the label in a bracket atom 63 | */ 64 | String label(); 65 | 66 | /** 67 | * Whether this atom is aromatic. 68 | * 69 | * @return atom is aromatic (true) or aliphatic (false) 70 | */ 71 | boolean aromatic(); 72 | 73 | /** 74 | * Formal charge of the atom. 75 | * 76 | * @return formal charge 77 | */ 78 | int charge(); 79 | 80 | /** 81 | * Number of hydrogens this atom has. This value defines atoms with an 82 | * explicit hydrogen count of bracket atoms (e.g. [CH4]). 83 | * 84 | * @return hydrogen count 85 | * @throws IllegalArgumentException thrown if element is part of the organic 86 | * subset and the number of hydrogens is 87 | * implied by the bond order sum. 88 | */ 89 | int hydrogens(); 90 | 91 | /** 92 | * The class of the atom is defined as an integer value. The atom class is 93 | * specified for bracketed atoms and is prefixed by a colon. 94 | * 95 | *
 96 |      *     [CH:1](C)([C])[H:2]
 97 |      * 
98 | * 99 | * @return class 100 | */ 101 | int atomClass(); 102 | 103 | /** 104 | * (internal) Is the atom a member of the organic (aromatic/aliphatic) 105 | * subset implementation? 106 | * 107 | * @return whether the atom is a subset - implementation 108 | */ 109 | boolean subset(); 110 | 111 | /** 112 | * Access an aromatic form of this atom. If the element can not be aromatic 113 | * then the same atom is returned. 114 | * 115 | * @return the aromatic form of this atom (or if it can't be aromatic just 116 | * this atom) 117 | */ 118 | Atom toAromatic(); 119 | 120 | /** 121 | * Access an aliphatic form of this atom. 122 | * 123 | * @return the aliphatic form of this atom 124 | */ 125 | Atom toAliphatic(); 126 | 127 | /** 128 | * (internal) the number of hydrogens this atom would have if it were vertex 129 | * 'u' in the graph 'g'. If the atom is in the organic subset the value is 130 | * computed - otherwise the labelled hydrogen count is returned. 131 | * 132 | * @see Graph#implHCount(int) 133 | */ 134 | int hydrogens(Graph g, int u); 135 | 136 | /** 137 | * (internal) The token to write for the atom when generating a SMILES 138 | * string. 139 | * 140 | * @return the atom token 141 | */ 142 | Generator.AtomToken token(); 143 | } 144 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/IntSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import java.util.BitSet; 33 | 34 | /** 35 | * Abstraction allows simple definitions of integer sets. Generally for this 36 | * library we are dealing with small bounded integer ranges (vertices of a 37 | * graph) which are most efficiently represented as a binary set. For 38 | * convenience the {@link #allOf(int, int...)} method can be used to construct a 39 | * binary set from varargs. 40 | * 41 | * @author John May 42 | */ 43 | abstract class IntSet { 44 | 45 | /** 46 | * Determine if value 'x' is a member of this set. 47 | * 48 | * @param x a value to 49 | * @return x is included in this set. 50 | */ 51 | abstract boolean contains(int x); 52 | 53 | /** 54 | * The universe is a set which includes every int value. 55 | * 56 | * @return int set with every item 57 | */ 58 | static IntSet universe() { 59 | return UNIVERSE; 60 | } 61 | 62 | /** 63 | * The empty set is a set which includes no int values. 64 | * 65 | * @return int set with no items 66 | */ 67 | static IntSet empty() { 68 | return complement(universe()); 69 | } 70 | 71 | /** 72 | * Convenience method to create a set with the specified contents. 73 | * 74 | *
IntSet.allOf(0, 2, 5); // a set with 0,2 and 5 75 | *
76 | * 77 | * @param x a value 78 | * @param xs more values 79 | * @return int set with specified items 80 | */ 81 | static IntSet allOf(int x, int... xs) { 82 | BitSet s = new BitSet(); 83 | s.set(x); 84 | for (int v : xs) 85 | s.set(v); 86 | return new BinarySet(s); 87 | } 88 | 89 | /** 90 | * Convenience method to create a set without the specified contents. 91 | * 92 | *
IntSet.noneOf(0, 2, 5); // a set with all but 0,2 and 5 93 | *
94 | * 95 | * @param x a value 96 | * @param xs more values 97 | * @return int set without the specified items 98 | */ 99 | static IntSet noneOf(int x, int... xs) { 100 | return complement(allOf(x, xs)); 101 | } 102 | 103 | /** 104 | * Create an set from a BitSet. 105 | * 106 | * @param s bitset 107 | * @return int set which uses the bit set to test for membership 108 | */ 109 | static IntSet fromBitSet(BitSet s) { 110 | return new BinarySet((BitSet) s.clone()); 111 | } 112 | 113 | /** 114 | * Make a complement of the specified set. 115 | * 116 | * @param set a set 117 | * @return complement of the set 118 | */ 119 | private static IntSet complement(IntSet set) { 120 | return new Complement(set); 121 | } 122 | 123 | /** An integer set based on the contents of a bit set. */ 124 | private static final class BinarySet extends IntSet { 125 | private final BitSet s; 126 | 127 | private BinarySet(BitSet s) { 128 | this.s = s; 129 | } 130 | 131 | @Override boolean contains(int x) { 132 | return s.get(x); 133 | } 134 | } 135 | 136 | /** Complement of a set - invert any membership of the provided 'delegate' */ 137 | private static final class Complement extends IntSet { 138 | private final IntSet delegate; 139 | 140 | private Complement(IntSet delegate) { 141 | this.delegate = delegate; 142 | } 143 | 144 | @Override boolean contains(int x) { 145 | return !delegate.contains(x); 146 | } 147 | } 148 | 149 | /** The universe - every object is a member of the set. */ 150 | private static final IntSet UNIVERSE = new IntSet() { 151 | @Override boolean contains(int x) { 152 | return true; 153 | } 154 | }; 155 | } 156 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/IntStackTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Test; 33 | 34 | import static org.hamcrest.CoreMatchers.is; 35 | import static org.junit.Assert.assertFalse; 36 | import static org.junit.Assert.assertThat; 37 | import static org.junit.Assert.assertTrue; 38 | 39 | /** @author John May */ 40 | public class IntStackTest { 41 | 42 | @Test public void push() throws Exception { 43 | IntStack stack = new IntStack(4); 44 | stack.push(1); 45 | assertThat(stack.peek(), is(1)); 46 | assertThat(stack.size(), is(1)); 47 | stack.push(2); 48 | assertThat(stack.peek(), is(2)); 49 | assertThat(stack.size(), is(2)); 50 | stack.push(4); 51 | assertThat(stack.peek(), is(4)); 52 | assertThat(stack.size(), is(3)); 53 | } 54 | 55 | @Test public void pushWithResize() throws Exception { 56 | IntStack stack = new IntStack(1); 57 | stack.push(1); 58 | assertThat(stack.peek(), is(1)); 59 | assertThat(stack.size(), is(1)); 60 | stack.push(2); 61 | assertThat(stack.peek(), is(2)); 62 | assertThat(stack.size(), is(2)); 63 | stack.push(4); 64 | assertThat(stack.peek(), is(4)); 65 | assertThat(stack.size(), is(3)); 66 | } 67 | 68 | @Test public void pushDuplicate() throws Exception { 69 | IntStack stack = new IntStack(4); 70 | stack.push(1); 71 | assertThat(stack.peek(), is(1)); 72 | assertThat(stack.size(), is(1)); 73 | stack.push(stack.peek()); 74 | assertThat(stack.peek(), is(1)); 75 | assertThat(stack.size(), is(2)); 76 | stack.push(stack.peek()); 77 | assertThat(stack.peek(), is(1)); 78 | assertThat(stack.size(), is(3)); 79 | } 80 | 81 | @Test public void pop() throws Exception { 82 | IntStack stack = new IntStack(4); 83 | stack.push(1); 84 | stack.push(2); 85 | stack.push(3); 86 | assertThat(stack.pop(), is(3)); 87 | assertThat(stack.pop(), is(2)); 88 | assertThat(stack.pop(), is(1)); 89 | } 90 | 91 | @Test public void popWithResize() throws Exception { 92 | IntStack stack = new IntStack(1); 93 | stack.push(1); 94 | stack.push(2); 95 | stack.push(3); 96 | assertThat(stack.pop(), is(3)); 97 | assertThat(stack.pop(), is(2)); 98 | assertThat(stack.pop(), is(1)); 99 | } 100 | 101 | @Test public void empty() throws Exception { 102 | assertTrue(new IntStack(4).empty()); 103 | } 104 | 105 | @Test public void nonEmpty() throws Exception { 106 | IntStack stack = new IntStack(4); 107 | assertTrue(stack.empty()); 108 | stack.push(1); 109 | assertFalse(stack.empty()); 110 | stack.pop(); 111 | assertTrue(stack.empty()); 112 | } 113 | 114 | @Test public void size() throws Exception { 115 | assertThat(new IntStack(4).size(), is(0)); 116 | } 117 | 118 | @Test public void clear() throws Exception { 119 | IntStack stack = new IntStack(1); 120 | stack.push(1); 121 | assertThat(stack.peek(), is(1)); 122 | assertThat(stack.size(), is(1)); 123 | stack.push(2); 124 | assertThat(stack.peek(), is(2)); 125 | assertThat(stack.size(), is(2)); 126 | stack.push(4); 127 | assertThat(stack.peek(), is(4)); 128 | assertThat(stack.size(), is(3)); 129 | stack.clear(); 130 | assertThat(stack.size(), is(0)); 131 | stack.push(4); 132 | assertThat(stack.peek(), is(4)); 133 | assertThat(stack.size(), is(1)); 134 | stack.push(8); 135 | assertThat(stack.peek(), is(8)); 136 | assertThat(stack.size(), is(2)); 137 | stack.push(9); 138 | assertThat(stack.peek(), is(9)); 139 | assertThat(stack.size(), is(3)); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /func/src/test/java/uk/ac/ebi/beam/NormaliseDirectionalLabelsTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.hamcrest.CoreMatchers; 4 | import org.junit.Assert; 5 | import org.junit.Ignore; 6 | import org.junit.Test; 7 | 8 | /** @author John May */ 9 | public class NormaliseDirectionalLabelsTest { 10 | 11 | @Test public void simple() throws InvalidSmilesException { 12 | transform("F\\C=C\\F", 13 | "F/C=C/F"); 14 | } 15 | 16 | @Test public void ordering2() throws InvalidSmilesException { 17 | transform("C(\\F)(/C)=C\\F", 18 | "C(/F)(\\C)=C/F"); 19 | } 20 | 21 | @Test public void simple2() throws InvalidSmilesException { 22 | transform("C(\\F)=C\\F", 23 | "C(/F)=C/F"); 24 | } 25 | 26 | @Test public void partial() throws InvalidSmilesException { 27 | transform("FC=C(\\F)/C=C/F", 28 | "FC=C(\\F)/C=C/F"); 29 | } 30 | 31 | @Test public void partial2() throws InvalidSmilesException { 32 | transform("FC=C(F)C=C(F)\\C=C\\F", 33 | "FC=C(F)C=C(F)/C=C/F"); 34 | } 35 | 36 | @Test public void conjugated() throws InvalidSmilesException { 37 | transform("F\\C=C(\\F)/C(/F)=C\\F", 38 | "F/C=C(/F)\\C(\\F)=C/F"); 39 | } 40 | 41 | @Test public void cyclic() throws InvalidSmilesException { 42 | transform("C/C=C\\1/C\\C(=C\\C)\\C1", 43 | "C/C=C\\1/C/C(=C/C)/C1"); 44 | transform("C/C=C\\1/C/C(=C/C)/C1", 45 | "C/C=C\\1/C/C(=C/C)/C1"); 46 | } 47 | 48 | @Ignore("invalid structure") 49 | public void chebi15617() throws InvalidSmilesException { 50 | transform("C/C=C\\1/[C@@H](C)C(=O)N/C1=C\\C/2=N/C(=C\\C3=C(CCC(=O)O)C(=C(\\C=C/4\\C(=C(CC)C(=O)N4)C)N3)C)/C(=C2C)CCC(=O)O", 51 | "C/C=C\\1/[C@@H](C)C(=O)N/C1=C\\C/2=N/C(=C\\C3=C(CCC(=O)O)C(=C(/C=C\\4/C(=C(CC)C(=O)N4)C)N3)C)/C(=C2C)CCC(=O)O"); 52 | transform("C/C=C\\1/[C@@H](C)C(=O)N/C1=C\\C/2=N/C(=C\\C3=C(CCC(=O)O)C(=C(/C=C\\4/C(=C(CC)C(=O)N4)C)N3)C)/C(=C2C)CCC(=O)O", 53 | "C/C=C\\1/[C@@H](C)C(=O)N/C1=C\\C/2=N/C(=C\\C3=C(CCC(=O)O)C(=C(/C=C\\4/C(=C(CC)C(=O)N4)C)N3)C)/C(=C2C)CCC(=O)O"); 54 | } 55 | 56 | @Test public void chembl2064754() throws InvalidSmilesException { 57 | transform("CC(=O)OCC/C=1/SS/C(/CCOC(C)=O)=C(/C)\\N(C=O)CCCCCCCCCCCCN(C=O)\\C1\\C", 58 | "CC(=O)OCC/C=1/SS/C(/CCOC(C)=O)=C(/C)\\N(C=O)CCCCCCCCCCCCN(C=O)\\C1\\C"); 59 | transform("CC(=O)OCC\\C=1\\SS/C(/CCOC(C)=O)=C(/C)\\N(C=O)CCCCCCCCCCCCN(C=O)/C1/C", 60 | "CC(=O)OCC/C=1/SS/C(/CCOC(C)=O)=C(/C)\\N(C=O)CCCCCCCCCCCCN(C=O)\\C1\\C"); 61 | } 62 | 63 | @Test public void pubchem16088588() throws InvalidSmilesException { 64 | transform("CC(C)N1CC(=O)NC=2C=CC(=CC2)OC3=CC=C(C=C3)NC(=O)CN(C(=O)CO/N=C\\4/C[C@H]5C[C@@H](O)[C@H]6[C@@H]7CC[C@H]([C@H](C)CCC(=O)N(CC(=O)NC8=CC=C(C=C8)OC=9C=CC(=CC9)NC(=O)CN(C(=O)CC[C@@H](C)[C@H]%10CC[C@H]%11[C@@H]%12[C@H](O)C[C@@H]%13C/C(/CC[C@]%13(C)[C@H]%12C[C@H](O)[C@]%10%11C)=N/OCC1=O)C(C)C)C(C)C)[C@@]7(C)[C@@H](O)C[C@@H]6[C@@]5(C)CC4)C(C)C", 65 | "CC(C)N1CC(=O)NC=2C=CC(=CC2)OC3=CC=C(C=C3)NC(=O)CN(C(=O)CO/N=C\\4/C[C@H]5C[C@@H](O)[C@H]6[C@@H]7CC[C@H]([C@H](C)CCC(=O)N(CC(=O)NC8=CC=C(C=C8)OC=9C=CC(=CC9)NC(=O)CN(C(=O)CC[C@@H](C)[C@H]%10CC[C@H]%11[C@@H]%12[C@H](O)C[C@@H]%13C/C(/CC[C@]%13(C)[C@H]%12C[C@H](O)[C@]%10%11C)=N/OCC1=O)C(C)C)C(C)C)[C@@]7(C)[C@@H](O)C[C@@H]6[C@@]5(C)CC4)C(C)C"); 66 | transform("CC(C)N1CC(=O)NC=2C=CC(=CC2)OC3=CC=C(C=C3)NC(=O)CN(C(=O)CO\\N=C/4\\C[C@H]5C[C@@H](O)[C@H]6[C@@H]7CC[C@H]([C@H](C)CCC(=O)N(CC(=O)NC8=CC=C(C=C8)OC=9C=CC(=CC9)NC(=O)CN(C(=O)CC[C@@H](C)[C@H]%10CC[C@H]%11[C@@H]%12[C@H](O)C[C@@H]%13C\\C(\\CC[C@]%13(C)[C@H]%12C[C@H](O)[C@]%10%11C)=N\\OCC1=O)C(C)C)C(C)C)[C@@]7(C)[C@@H](O)C[C@@H]6[C@@]5(C)CC4)C(C)C", 67 | "CC(C)N1CC(=O)NC=2C=CC(=CC2)OC3=CC=C(C=C3)NC(=O)CN(C(=O)CO/N=C\\4/C[C@H]5C[C@@H](O)[C@H]6[C@@H]7CC[C@H]([C@H](C)CCC(=O)N(CC(=O)NC8=CC=C(C=C8)OC=9C=CC(=CC9)NC(=O)CN(C(=O)CC[C@@H](C)[C@H]%10CC[C@H]%11[C@@H]%12[C@H](O)C[C@@H]%13C/C(/CC[C@]%13(C)[C@H]%12C[C@H](O)[C@]%10%11C)=N/OCC1=O)C(C)C)C(C)C)[C@@]7(C)[C@@H](O)C[C@@H]6[C@@]5(C)CC4)C(C)C"); 68 | } 69 | 70 | 71 | @Test public void chembl294514_1() throws InvalidSmilesException { 72 | transform("Cc1c(CCC(=O)NCCOCCOCCN)c2/C=C/3\\N\\C(=C/c4[nH]c(/C=C/5\\N=C(CC6=N/C(=C\\c1[nH]2)/C(=C6C)CC)C(=C5CC)C)c(C)c4CCC(=O)NCCOCCOCCN)\\C(=C3CC)CC", 73 | "Cc1c(CCC(=O)NCCOCCOCCN)c2/C=C/3\\N\\C(=C/c4[nH]c(/C=C/5\\N=C(CC6=N/C(=C\\c1[nH]2)/C(=C6C)CC)C(=C5CC)C)c(C)c4CCC(=O)NCCOCCOCCN)\\C(=C3CC)CC"); 74 | } 75 | 76 | @Test public void chembl294514_2() throws InvalidSmilesException { 77 | transform("Cc1c(CCC(=O)NCCOCCOCCN)c2\\C=C\\3/N/C(=C\\c4[nH]c(/C=C/5\\N=C(CC6=N/C(=C\\c1[nH]2)/C(=C6C)CC)C(=C5CC)C)c(C)c4CCC(=O)NCCOCCOCCN)/C(=C3CC)CC", 78 | "Cc1c(CCC(=O)NCCOCCOCCN)c2/C=C/3\\N\\C(=C/c4[nH]c(/C=C/5\\N=C(CC6=N/C(=C\\c1[nH]2)/C(=C6C)CC)C(=C5CC)C)c(C)c4CCC(=O)NCCOCCOCCN)\\C(=C3CC)CC"); 79 | } 80 | 81 | @Test public void chembl147624() throws InvalidSmilesException { 82 | transform("COc1cc2nc(nc(N)c2cc1OC)N3CCN(CC3)S(=O)(=O)c4no[n+]([O-])c4C=5\\C=C/C=C\\C=C/C5", 83 | "COc1cc2nc(nc(N)c2cc1OC)N3CCN(CC3)S(=O)(=O)c4no[n+]([O-])c4C=5/C=C\\C=C/C=C\\C5"); 84 | } 85 | 86 | 87 | static void transform(String smi, String exp) throws 88 | InvalidSmilesException { 89 | Assert.assertThat(Generator.generate(new NormaliseDirectionalLabels() 90 | .apply(Parser.parse(smi))), CoreMatchers 91 | .is(exp)); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/BondTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Test; 33 | 34 | import static org.hamcrest.CoreMatchers.is; 35 | import static org.junit.Assert.assertThat; 36 | import static uk.ac.ebi.beam.Bond.AROMATIC; 37 | import static uk.ac.ebi.beam.Bond.DOT; 38 | import static uk.ac.ebi.beam.Bond.DOUBLE; 39 | import static uk.ac.ebi.beam.Bond.DOWN; 40 | import static uk.ac.ebi.beam.Bond.IMPLICIT; 41 | import static uk.ac.ebi.beam.Bond.QUADRUPLE; 42 | import static uk.ac.ebi.beam.Bond.SINGLE; 43 | import static uk.ac.ebi.beam.Bond.TRIPLE; 44 | import static uk.ac.ebi.beam.Bond.UP; 45 | 46 | /** @author John May */ 47 | public class BondTest { 48 | 49 | @Test public void dotElectrons() throws Exception { 50 | assertThat(DOT.order(), is(0)); 51 | } 52 | 53 | @Test public void singleElectrons() throws Exception { 54 | assertThat(SINGLE.order(), is(1)); 55 | } 56 | 57 | @Test public void doubleElectrons() throws Exception { 58 | assertThat(DOUBLE.order(), is(2)); 59 | } 60 | 61 | @Test public void tripleElectrons() throws Exception { 62 | assertThat(TRIPLE.order(), is(3)); 63 | } 64 | 65 | @Test public void quadrupleElectrons() throws Exception { 66 | assertThat(QUADRUPLE.order(), is(4)); 67 | } 68 | 69 | @Test public void aromaticElectrons() throws Exception { 70 | assertThat(AROMATIC.order(), is(1)); 71 | } 72 | 73 | @Test public void upElectrons() throws Exception { 74 | assertThat(UP.order(), is(1)); 75 | } 76 | 77 | @Test public void downElectrons() throws Exception { 78 | assertThat(DOWN.order(), is(1)); 79 | } 80 | 81 | @Test public void dotInverse() throws Exception { 82 | assertThat(DOT.inverse(), is(DOT)); 83 | } 84 | 85 | @Test public void singleInverse() throws Exception { 86 | assertThat(SINGLE.inverse(), is(SINGLE)); 87 | } 88 | 89 | @Test public void doubleInverse() throws Exception { 90 | assertThat(DOUBLE.inverse(), is(DOUBLE)); 91 | } 92 | 93 | @Test public void tripleInverse() throws Exception { 94 | assertThat(TRIPLE.inverse(), is(TRIPLE)); 95 | } 96 | 97 | @Test public void quadrupleInverse() throws Exception { 98 | assertThat(QUADRUPLE.inverse(), is(QUADRUPLE)); 99 | } 100 | 101 | @Test public void aromaticInverse() throws Exception { 102 | assertThat(AROMATIC.inverse(), is(AROMATIC)); 103 | } 104 | 105 | @Test public void upInverse() throws Exception { 106 | assertThat(UP.inverse(), is(DOWN)); 107 | } 108 | 109 | @Test public void downInverse() throws Exception { 110 | assertThat(DOWN.inverse(), is(UP)); 111 | } 112 | 113 | @Test public void implicitInverse() throws Exception { 114 | assertThat(IMPLICIT.inverse(), is(IMPLICIT)); 115 | } 116 | 117 | @Test public void dotSymbol() throws Exception { 118 | assertThat(DOT.token(), is(".")); 119 | } 120 | 121 | @Test public void singleSymbol() throws Exception { 122 | assertThat(SINGLE.token(), is("-")); 123 | } 124 | 125 | @Test public void doubleSymbol() throws Exception { 126 | assertThat(DOUBLE.token(), is("=")); 127 | } 128 | 129 | @Test public void tripleSymbol() throws Exception { 130 | assertThat(TRIPLE.token(), is("#")); 131 | } 132 | 133 | @Test public void quadrupleSymbol() throws Exception { 134 | assertThat(QUADRUPLE.token(), is("$")); 135 | } 136 | 137 | @Test public void aromaticSymbol() throws Exception { 138 | assertThat(AROMATIC.token(), is(":")); 139 | } 140 | 141 | @Test public void upSymbol() throws Exception { 142 | assertThat(UP.token(), is("/")); 143 | } 144 | 145 | @Test public void downSymbol() throws Exception { 146 | assertThat(DOWN.token(), is("\\")); 147 | } 148 | 149 | @Test public void implicitSymbol() throws Exception { 150 | assertThat(IMPLICIT.token(), is("")); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/EdgeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Ignore; 33 | import org.junit.Test; 34 | 35 | import static org.hamcrest.CoreMatchers.is; 36 | import static org.hamcrest.CoreMatchers.not; 37 | import static org.junit.Assert.assertThat; 38 | 39 | /** @author John May */ 40 | public class EdgeTest { 41 | 42 | @Test public void either() throws Exception { 43 | assertThat(new Edge(2, 3, Bond.IMPLICIT).either(), is(2)); 44 | assertThat(new Edge(3, 2, Bond.IMPLICIT).either(), is(3)); 45 | } 46 | 47 | @Test public void other() throws Exception { 48 | assertThat(new Edge(2, 3, Bond.IMPLICIT).other(2), is(3)); 49 | assertThat(new Edge(2, 3, Bond.IMPLICIT).other(3), is(2)); 50 | assertThat(new Edge(3, 2, Bond.IMPLICIT).other(2), is(3)); 51 | assertThat(new Edge(3, 2, Bond.IMPLICIT).other(3), is(2)); 52 | } 53 | 54 | @Ignore("no longer thrown") 55 | public void invalidEndpoint() throws Exception { 56 | new Edge(2, 3, Bond.IMPLICIT).other(1); 57 | } 58 | 59 | @Test public void bond() throws Exception { 60 | assertThat(new Edge(2, 3, Bond.SINGLE).bond(), is(Bond.SINGLE)); 61 | assertThat(new Edge(2, 3, Bond.UP).bond(), is(Bond.UP)); 62 | assertThat(new Edge(2, 3, Bond.DOWN).bond(), is(Bond.DOWN)); 63 | } 64 | 65 | @Test public void relativeBond() throws Exception { 66 | assertThat(new Edge(2, 3, Bond.SINGLE).bond(2), is(Bond.SINGLE)); 67 | assertThat(new Edge(2, 3, Bond.SINGLE).bond(3), is(Bond.SINGLE)); 68 | assertThat(new Edge(2, 3, Bond.UP).bond(2), is(Bond.UP)); 69 | assertThat(new Edge(2, 3, Bond.UP).bond(3), is(Bond.DOWN)); 70 | assertThat(new Edge(2, 3, Bond.DOWN).bond(2), is(Bond.DOWN)); 71 | assertThat(new Edge(2, 3, Bond.DOWN).bond(3), is(Bond.UP)); 72 | } 73 | 74 | @Test(expected = IllegalArgumentException.class) 75 | public void invalidRelativeBond() throws Exception { 76 | new Edge(2, 3, Bond.IMPLICIT).bond(1); 77 | } 78 | 79 | @Test public void undirectedHashCode() { 80 | assertThat(new Edge(0, 1, Bond.IMPLICIT).hashCode(), 81 | is(new Edge(1, 0, Bond.IMPLICIT).hashCode())); 82 | } 83 | 84 | @Test public void directedHashCode() { 85 | assertThat(new Edge(0, 1, Bond.UP).hashCode(), 86 | is(new Edge(1, 0, Bond.DOWN).hashCode())); 87 | assertThat(new Edge(0, 1, Bond.UP).hashCode(), 88 | is(new Edge(1, 0, Bond.UP).hashCode())); 89 | } 90 | 91 | @Test public void undirectedEquality() { 92 | assertThat(new Edge(0, 1, Bond.IMPLICIT), 93 | is(new Edge(0, 1, Bond.IMPLICIT))); 94 | assertThat(new Edge(0, 1, Bond.IMPLICIT), 95 | is(new Edge(1, 0, Bond.IMPLICIT))); 96 | } 97 | 98 | @Test public void undirectedInequality() { 99 | assertThat(new Edge(0, 1, Bond.SINGLE), 100 | is(not(new Edge(0, 1, Bond.DOUBLE)))); 101 | assertThat(new Edge(0, 1, Bond.DOUBLE), 102 | is(not(new Edge(1, 0, Bond.SINGLE)))); 103 | } 104 | 105 | @Test public void directedEquality() { 106 | assertThat(new Edge(0, 1, Bond.UP), 107 | is(new Edge(0, 1, Bond.UP))); 108 | assertThat(new Edge(0, 1, Bond.UP), 109 | is(new Edge(1, 0, Bond.DOWN))); 110 | assertThat(new Edge(1, 0, Bond.DOWN), 111 | is(new Edge(0, 1, Bond.UP))); 112 | assertThat(new Edge(1, 0, Bond.DOWN), 113 | is(new Edge(1, 0, Bond.DOWN))); 114 | } 115 | 116 | @Test public void directedInequality() { 117 | assertThat(new Edge(0, 1, Bond.UP), 118 | is(not(new Edge(0, 1, Bond.DOWN)))); 119 | assertThat(new Edge(0, 1, Bond.UP), 120 | is(not(new Edge(1, 0, Bond.UP)))); 121 | assertThat(new Edge(1, 0, Bond.UP), 122 | is(not(new Edge(0, 1, Bond.UP)))); 123 | assertThat(new Edge(1, 0, Bond.DOWN), 124 | is(not(new Edge(1, 0, Bond.UP)))); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /func/src/test/java/uk/ac/ebi/beam/FromSubsetAtomsTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.hamcrest.CoreMatchers; 4 | import org.junit.Assert; 5 | import org.junit.Ignore; 6 | import org.junit.Test; 7 | 8 | /** @author John May */ 9 | public class FromSubsetAtomsTest { 10 | 11 | @Test public void unknown() throws Exception { 12 | transform("*", "[*]"); 13 | } 14 | 15 | @Test public void inorganic() throws Exception { 16 | transform("[Ne]", "[Ne]"); 17 | } 18 | 19 | @Test public void methane() throws Exception { 20 | transform("C", "[CH4]"); 21 | } 22 | 23 | @Test public void ethane_withAtomClass() throws Exception { 24 | transform("[CH3:1]C", "[CH3:1][CH3]"); 25 | } 26 | 27 | @Test public void ethanol() throws InvalidSmilesException { 28 | transform("CCO", "[CH3][CH2][OH]"); 29 | } 30 | 31 | @Test public void stereoSpecification() throws InvalidSmilesException { 32 | transform("[C@H](N)(O)C", "[C@H]([NH2])([OH])[CH3]"); 33 | transform("[C@@H](N)(O)C", "[C@@H]([NH2])([OH])[CH3]"); 34 | } 35 | 36 | @Test public void noStereoSpecification() throws InvalidSmilesException { 37 | transform("C(N)(O)C", "[CH]([NH2])([OH])[CH3]"); 38 | } 39 | 40 | @Test public void bracketAtom() { 41 | // should provide identity of bracket atom 42 | Atom input = new AtomImpl.BracketAtom(Element.Carbon, 1, 0); 43 | Atom output = FromSubsetAtoms.fromSubset(input, 0, 0); 44 | Assert.assertThat(input, CoreMatchers.is(CoreMatchers 45 | .sameInstance(output))); 46 | } 47 | 48 | @Test public void aliphatic_carbon() { 49 | Atom actual = FromSubsetAtoms 50 | .fromSubset(AtomImpl.AliphaticSubset.Carbon, 3, 0); 51 | Atom expect = new AtomImpl.BracketAtom(Element.Carbon, 1, 0); 52 | Assert.assertThat(expect, CoreMatchers.is(actual)); 53 | } 54 | 55 | @Test public void aromatic_carbon() { 56 | Atom actual = FromSubsetAtoms.fromSubset(AtomImpl.AromaticSubset.Carbon, 2, 0); 57 | Atom expect = new AtomImpl.BracketAtom(-1, Element.Carbon, 1, 0, 0, true); 58 | Assert.assertThat(expect, CoreMatchers.is(actual)); 59 | } 60 | 61 | @Test public void indolizine() throws InvalidSmilesException { 62 | transform("c2cc1cccn1cc2", 63 | "[cH]1[cH][c]2[cH][cH][cH][n]2[cH][cH]1"); 64 | } 65 | 66 | @Test public void indolizine_kekule() throws InvalidSmilesException { 67 | transform("C1=CN2C=CC=CC2=C1", 68 | "[CH]1=[CH][N]2[CH]=[CH][CH]=[CH][C]2=[CH]1"); 69 | } 70 | 71 | @Test public void _1H_imidazole() throws InvalidSmilesException { 72 | transform("[H]n1ccnc1", 73 | "[H][n]1[cH][cH][n][cH]1"); 74 | } 75 | 76 | @Test public void _1H_imidazole_kekule() throws InvalidSmilesException { 77 | transform("[H]N1C=CN=C1", 78 | "[H][N]1[CH]=[CH][N]=[CH]1"); 79 | } 80 | 81 | @Test public void cdk_bug_1363882() throws Exception{ 82 | transform("[H]c2c([H])c(c1c(nc(n1([H]))C(F)(F)F)c2Cl)Cl", 83 | "[H][c]1[c]([H])[c]([c]2[c]([n][c]([n]2[H])[C]([F])([F])[F])[c]1[Cl])[Cl]"); 84 | } 85 | 86 | @Test public void cdk_bug_1579235() throws Exception{ 87 | transform("c2cc1cccn1cc2", 88 | "[cH]1[cH][c]2[cH][cH][cH][n]2[cH][cH]1"); 89 | } 90 | 91 | @Test public void sulphur() throws Exception { 92 | transform("S([H])[H]", 93 | "[S]([H])[H]"); 94 | transform("[H]S([H])[H]", 95 | "[H][SH]([H])[H]"); 96 | transform("[H]S([H])([H])[H]", 97 | "[H][S]([H])([H])[H]"); 98 | transform("[H]S([H])([H])([H])[H]", 99 | "[H][SH]([H])([H])([H])[H]"); 100 | transform("[H]S([H])([H])([H])([H])[H]", 101 | "[H][S]([H])([H])([H])([H])[H]"); 102 | } 103 | 104 | @Test public void tricyclazole() throws InvalidSmilesException { 105 | transform("Cc1cccc2sc3nncn3c12", 106 | "[CH3][c]1[cH][cH][cH][c]2[s][c]3[n][n][cH][n]3[c]12"); 107 | } 108 | 109 | @Test public void tricyclazole_kekule() throws InvalidSmilesException { 110 | transform("CC1=C2N3C=NN=C3SC2=CC=C1", 111 | "[CH3][C]1=[C]2[N]3[CH]=[N][N]=[C]3[S][C]2=[CH][CH]=[CH]1"); 112 | } 113 | 114 | @Ignore("bad molecule - should have utility to find/fix this types of errors") 115 | public void mixingAromaticAndKekule() throws Exception { 116 | transform("c1=cc=cc=c1", 117 | "[cH]1=[cH][cH]=[cH][cH]=[cH]1"); 118 | } 119 | 120 | @Test public void quinone() throws Exception { 121 | transform("oc1ccc(o)cc1", 122 | "[o][c]1[cH][cH][c]([o])[cH][cH]1"); 123 | } 124 | 125 | /** 1-(1H-pyrrol-2-yl)pyrrole */ 126 | @Test public void pyroles() throws Exception { 127 | transform("c1ccn(c1)-c1ccc[nH]1", 128 | "[cH]1[cH][cH][n]([cH]1)-[c]2[cH][cH][cH][nH]2"); 129 | } 130 | 131 | @Test public void cdk_bug_956926() throws InvalidSmilesException { 132 | transform("[c+]1ccccc1", 133 | "[c+]1[cH][cH][cH][cH][cH]1"); 134 | } 135 | 136 | 137 | 138 | private void transform(String input, String expected) throws 139 | InvalidSmilesException { 140 | Graph g = Parser.parse(input); 141 | ImplicitToExplicit ite = new ImplicitToExplicit(); 142 | FromSubsetAtoms fsa = new FromSubsetAtoms(); 143 | ExplicitToImplicit eti = new ExplicitToImplicit(); 144 | String actual = Generator.generate(eti.apply( 145 | fsa.apply( 146 | ite.apply(g)))); 147 | Assert.assertThat(actual, CoreMatchers.is(expected)); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/ArbitraryMatching.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import java.util.BitSet; 4 | 5 | /** 6 | * Simple matching greedily chooses edges and matches. The produced matching is 7 | * not guaranteed to be maximum but provides a starting point for improvement 8 | * through augmentation. 9 | * 10 | * @author John May 11 | */ 12 | final class ArbitraryMatching { 13 | 14 | /** 15 | * Create an arbitrary matching on the subset of vertices ('s') of provided 16 | * graph. The provided matching should be empty. 17 | * 18 | * @param g graph to match 19 | * @param m empty matching (presumed) 20 | * @param s subset of vertices 21 | * @return number of vertices matched 22 | */ 23 | static int initial(final Graph g, Matching m, final BitSet s) { 24 | 25 | int nMatched = 0; 26 | 27 | for (int v = s.nextSetBit(0); v >= 0; v = s.nextSetBit(v + 1)) { 28 | 29 | // skip if already matched 30 | if (m.matched(v)) 31 | continue; 32 | 33 | // find a single edge which is not matched and match it 34 | int d = g.degree(v); 35 | for (int j = 0; j < d; ++j) { 36 | Edge e = g.edgeAt(v, j); 37 | int w = e.other(v); 38 | if ((e.bond() != Bond.SINGLE) && m.unmatched(w) && s.get(w)) { 39 | m.match(v, w); 40 | nMatched += 2; 41 | break; 42 | } 43 | } 44 | } 45 | 46 | return nMatched; 47 | } 48 | 49 | static int dfs(final Graph g, Matching m, final BitSet s) { 50 | 51 | int nMatched = 0; 52 | BitSet unvisited = (BitSet) s.clone(); 53 | 54 | // visit those with degree 1 first and expand out matching 55 | for (int v = unvisited.nextSetBit(0); v >= 0; v = unvisited.nextSetBit(v + 1)) { 56 | if (!m.matched(v)) { 57 | int cnt = 0; 58 | int d = g.degree(v); 59 | while (--d >= 0) { 60 | int w = g.edgeAt(v, d).other(v); 61 | if (unvisited.get(w)) 62 | ++cnt; 63 | } 64 | if (cnt == 1) 65 | nMatched += dfsVisit(g, v, m, unvisited, true); 66 | } 67 | } 68 | 69 | // now those which aren't degree 1 70 | for (int v = unvisited.nextSetBit(0); v >= 0; v = unvisited.nextSetBit(v + 1)) { 71 | if (!m.matched(v)) { 72 | nMatched += dfsVisit(g, v, m, unvisited, true); 73 | } 74 | } 75 | 76 | return nMatched; 77 | } 78 | 79 | static int dfsVisit(final Graph g, final int v, Matching m, final BitSet unvisited, boolean match) { 80 | unvisited.clear(v); 81 | int nMatched = 0; 82 | int d = g.degree(v); 83 | while (--d >= 0) { 84 | int w = g.edgeAt(v, d).other(v); 85 | if (unvisited.get(w)) { 86 | if (match) { 87 | m.match(v, w); 88 | return 2 + dfsVisit(g, w, m, unvisited, false); 89 | } 90 | else { 91 | nMatched += dfsVisit(g, w, m, unvisited, true); 92 | } 93 | } 94 | } 95 | return nMatched; 96 | } 97 | 98 | /** 99 | * When precisely two vertices are unmatched we only need to find a single 100 | * augmenting path. Rather than run through edmonds with blossoms etc we 101 | * simple do a targest DFS for the path. 102 | * 103 | * @param g graph 104 | * @param m matching 105 | * @param nMatched current matching cardinality must be |s|-nMathced == 2 106 | * @param s subset size 107 | * @return new match cardinality 108 | */ 109 | static int augmentOnce(final Graph g, final Matching m, int nMatched, final BitSet s) { 110 | 111 | int vStart = s.nextSetBit(0); 112 | while (vStart >= 0) { 113 | if (!m.matched(vStart)) break; 114 | vStart = s.nextSetBit(vStart + 1); 115 | } 116 | int vEnd = s.nextSetBit(vStart + 1); 117 | while (vEnd >= 0) { 118 | if (!m.matched(vEnd)) break; 119 | vEnd = s.nextSetBit(vEnd + 1); 120 | } 121 | 122 | // find an augmenting path between vStart and vEnd 123 | int[] path = new int[g.order()]; 124 | int len = findPath(g, vStart, vEnd, s, path, 0, m, false); 125 | if (len > 0) { 126 | // augment 127 | for (int i = 0; i < len; i += 2) { 128 | m.match(path[i], path[i + 1]); 129 | } 130 | nMatched += 2; 131 | } 132 | 133 | return nMatched; 134 | } 135 | 136 | static int findPath(Graph g, int v, int end, BitSet unvisited, int[] path, int len, Matching m, boolean matchNeeded) { 137 | unvisited.clear(v); 138 | path[len++] = v; 139 | int l; 140 | int d = g.degree(v); 141 | for (int j = 0; j < d; ++j) { 142 | Edge e = g.edgeAt(v, j); 143 | // explicit single bond can not be augmented along!! 144 | if (e.bond() == Bond.SINGLE) 145 | continue; 146 | int w = e.other(v); 147 | if (unvisited.get(w)) { 148 | if (w == end) { 149 | path[len] = w; 150 | len++; 151 | unvisited.set(v); 152 | // odd length path no good 153 | return ((len & 0x1) == 1) ? 0 : len; 154 | } 155 | else if ((m.other(w) == v) == matchNeeded) { 156 | if ((l = findPath(g, w, end, unvisited, path, len, m, !matchNeeded)) > 0) { 157 | unvisited.set(v); 158 | return l; 159 | } 160 | } 161 | } 162 | } 163 | unvisited.set(v); 164 | return 0; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /func/src/main/java/uk/ac/ebi/beam/FromTrigonalTopology.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import java.util.BitSet; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import static uk.ac.ebi.beam.Configuration.Type.DoubleBond; 9 | 10 | /** 11 | * Given a chemical graph with atom-centric double bond stereo configurations 12 | * (trigonal topology) - remove the topology but add in direction up/down edge 13 | * labels. 14 | * 15 | * @author John May 16 | */ 17 | final class FromTrigonalTopology extends AbstractFunction { 18 | 19 | public Graph apply(Graph g) { 20 | Graph h = new Graph(g.order()); 21 | 22 | // copy atom/topology information this is unchanged 23 | for (int u = 0; u < g.order(); u++) { 24 | if (g.topologyOf(u).type() == DoubleBond) { 25 | h.addAtom(reducedAtom(g, u)); 26 | } else { 27 | h.addAtom(g.atom(u)); 28 | h.addTopology(g.topologyOf(u)); 29 | } 30 | } 31 | 32 | Map replacements = new Traversal(g).replacement; 33 | 34 | 35 | // append the edges, replacing any which need to be changed 36 | for (int u = 0; u < g.order(); u++) { 37 | for (Edge e : g.edges(u)) { 38 | if (e.other(u) > u) { 39 | Edge replacement = replacements.get(e); 40 | if (replacement != null) 41 | e = replacement; 42 | h.addEdge(e); 43 | } 44 | } 45 | } 46 | 47 | return h; 48 | } 49 | 50 | private Atom reducedAtom(Graph g, int u) { 51 | Atom a = g.atom(u); 52 | 53 | int sum = 0; 54 | for (Edge e : g.edges(u)) { 55 | sum += e.bond().order(); 56 | } 57 | 58 | return ToSubsetAtoms.toSubset(g.atom(u), g, u); 59 | } 60 | 61 | private static final class Traversal { 62 | 63 | private final Graph g; 64 | private final boolean[] visited; 65 | private final int[] ordering; 66 | private int i; 67 | private Map replacement = new HashMap(); 68 | 69 | private static final Bond[] labels = new Bond[]{Bond.DOWN, Bond.UP}; 70 | 71 | private Traversal(Graph g) { 72 | this.g = g; 73 | this.visited = new boolean[g.order()]; 74 | this.ordering = new int[g.order()]; 75 | 76 | 77 | for (int u = 0; u < g.order(); u++) { 78 | if (!visited[u]) 79 | visit(u, u); 80 | } 81 | } 82 | 83 | private void visit(int p, int u) { 84 | 85 | visited[u] = true; 86 | 87 | // offset - the index of the edge with a double bond label 88 | int offset = -1; 89 | 90 | List es = g.edges(u); 91 | for (int i = 0; i < es.size(); i++) { 92 | Edge e = es.get(i); 93 | int v = e.other(u); 94 | if (!visited[v]) 95 | visit(u, v); 96 | ordering[v] = 2 + i; 97 | if (e.bond() == Bond.DOUBLE) 98 | offset = i; 99 | } 100 | 101 | ordering[p] = 0; 102 | ordering[u] = 1; 103 | 104 | 105 | Topology t = g.topologyOf(u); 106 | 107 | if (t.type() == DoubleBond) { 108 | 109 | if (offset < 0) 110 | throw new IllegalArgumentException("found atom-centric double bond" + 111 | "specifiation but no double bond label."); 112 | 113 | // order the topology to ensure it matches the traversal order 114 | Topology topology = t.orderBy(ordering); 115 | 116 | // labelling start depends on configuration ... 117 | int j = topology.configuration() 118 | .shorthand() == Configuration.ANTI_CLOCKWISE ? 0 119 | : 1; 120 | 121 | // ... and which end of the double bond we're looking from 122 | if (ordering[es.get(offset).other(u)] < ordering[u]) { 123 | 124 | } else if (es.size() == 2 && 125 | ordering[u] < ordering[es.get((offset + 1) % es.size()) 126 | .other(u)]) { 127 | j++; 128 | } 129 | 130 | // now create the new labels for the non-double bond atoms 131 | for (int i = 1; i < es.size(); i++) { 132 | Edge e = es.get((offset + i) % es.size()); 133 | Bond label = labels[j++ % 2]; 134 | 135 | Edge f = new Edge(u, 136 | e.other(u), 137 | label); 138 | Edge existing = replacement.get(e); 139 | 140 | // check for conflict - need to rewrite existing labels 141 | if (existing != null && existing.bond(u) != label) { 142 | BitSet visited = new BitSet(); 143 | visited.set(u); 144 | invertExistingDirectionalLabels(visited, e.other(u)); 145 | } 146 | replacement.put(e, f); 147 | } 148 | } 149 | } 150 | 151 | private void invertExistingDirectionalLabels(BitSet visited, int u) { 152 | visited.set(u); 153 | if (g.topologyOf(u) == null) 154 | return; 155 | for (Edge e : g.edges(u)) { 156 | int v = e.other(u); 157 | if (!visited.get(v)) { 158 | Edge f = replacement.get(e); 159 | if (f != null) { 160 | replacement.put(e, 161 | f.inverse()); 162 | } 163 | invertExistingDirectionalLabels(visited, v); 164 | } 165 | } 166 | } 167 | 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /core/src/main/java/uk/ac/ebi/beam/Edge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | /** 33 | * An edge defines two vertex end points and an associated {@link Bond} label. 34 | * Edges are created from their {@link Bond} label as follows. 35 | * 36 | *
37 | *
 38 |  * // an edge between the vertices 1 and 2 the bond label is implicit
 39 |  * Edge e = Bond.IMPLICIT.edge(1, 2);
 40 |  *
 41 |  * // an edge between the vertices 5 and 3 the bond label is double
 42 |  * Edge e = Bond.DOUBLE.edge(1, 2);
 43 |  * 
44 | *
45 | * 46 | * @author John May 47 | * @see Bond 48 | */ 49 | public final class Edge { 50 | 51 | /** Endpoints of the edge. */ 52 | private final int u, v, xor; 53 | 54 | /** Label on the edge. */ 55 | private Bond bond; 56 | 57 | Edge(final int u, final int v, final Bond bond) { 58 | this.u = u; 59 | this.v = v; 60 | this.xor = u ^ v; 61 | this.bond = bond; 62 | } 63 | 64 | Edge(Edge e) { 65 | this(e.u, e.v, e.bond); 66 | } 67 | 68 | /** 69 | * Access either endpoint of the edge. For directional bonds, the endpoint 70 | * can be considered as relative to this vertex. 71 | * 72 | * @return either endpoint 73 | */ 74 | public int either() { 75 | return u; 76 | } 77 | 78 | /** 79 | * Given one endpoint, access the other endpoint of the edge. 80 | * 81 | * @param x an endpoint of the edge 82 | * @return the other endpoint 83 | * @throws IllegalArgumentException {@code x} was not an endpoint of this 84 | * edge 85 | */ 86 | public int other(final int x) { 87 | return x ^ xor; 88 | } 89 | 90 | /** 91 | * Access the bond label without considering which endpoint the label is 92 | * relative to. For the directional bonds {@link Bond#UP} and {@link 93 | * Bond#DOWN} the {@link #bond(int)} should be used to provided the label 94 | * relative to rhe provided endpoint. 95 | * 96 | * @return bond label 97 | * @see #bond(int) 98 | */ 99 | public Bond bond() { 100 | return bond; 101 | } 102 | 103 | /** 104 | * Set the bond label. 105 | * 106 | * @param bond the bond label 107 | */ 108 | void bond(Bond bond) { 109 | this.bond = bond; 110 | } 111 | 112 | /** 113 | * Access the bond label relative to a specified endpoint. 114 | * 115 | *
116 |      * Edge e = Bond.UP.edge(2, 3);
117 |      * e.bond(2); // UP
118 |      * e.bond(3); // DOWN
119 |      * 
120 | * 121 | * @param x endpoint to which the label is relative to 122 | * @return the bond label 123 | */ 124 | public Bond bond(final int x) { 125 | if (x == u) return bond; 126 | if (x == v) return bond.inverse(); 127 | throw new IllegalArgumentException(invalidEndpointMessage(x)); 128 | } 129 | 130 | 131 | /** 132 | * Inverse of the edge label but keep the vertices the same. 133 | * 134 | * @return inverse edge 135 | */ 136 | Edge inverse() { 137 | return bond.inverse().edge(u, v); 138 | } 139 | 140 | /** Helper method to print error message. */ 141 | private String invalidEndpointMessage(final int x) { 142 | return "Vertex " + x + ", is not an endpoint of the edge " + toString(); 143 | } 144 | 145 | /** {@inheritDoc} */ 146 | @Override 147 | public int hashCode() { 148 | return xor; 149 | } 150 | 151 | /** {@inheritDoc} */ 152 | @Override 153 | public boolean equals(Object other) { 154 | if (this == other) return true; 155 | if (other == null || getClass() != other.getClass()) return false; 156 | final Edge o = (Edge) other; 157 | return (u == o.u && v == o.v && bond.equals(o.bond)) || 158 | (u == o.v && v == o.u && bond.equals(o.bond.inverse())); 159 | } 160 | 161 | /** {@inheritDoc} */ 162 | @Override public String toString() { 163 | return new StringBuilder(20).append('{') 164 | .append(u) 165 | .append(", ") 166 | .append(v) 167 | .append('}') 168 | .append(": '") 169 | .append(bond) 170 | .append("'") 171 | .toString(); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /func/src/test/java/uk/ac/ebi/beam/FromTrigonalTopologyTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.hamcrest.CoreMatchers; 33 | import org.junit.Assert; 34 | import org.junit.Test; 35 | 36 | import static org.hamcrest.CoreMatchers.is; 37 | 38 | /** @author John May */ 39 | public class FromTrigonalTopologyTest { 40 | 41 | @Test public void z_dichloroethene() throws Exception { 42 | transform("F[C@H]=[C@H]F", "F/C=C\\F"); 43 | } 44 | 45 | @Test public void z_dichloroethene_alt() throws Exception { 46 | transform("F[C@@H]=[C@@H]F", "F\\C=C/F"); 47 | } 48 | 49 | @Test public void z_dichloroethene_explicitH() throws Exception { 50 | transform("F[C@]([H])=[C@](F)[H]", "F/C(/[H])=C(\\F)/[H]"); 51 | } 52 | 53 | @Test public void z_dichloroethene_alt_explicitH() throws Exception { 54 | transform("F[C@@]([H])=[C@@](F)[H]", "F\\C(\\[H])=C(/F)\\[H]"); 55 | } 56 | 57 | @Test public void e_dichloroethene() throws Exception { 58 | transform("F[C@H]=[C@@H]F", "F/C=C/F"); 59 | } 60 | 61 | @Test public void e_dichloroethene_alt() throws Exception { 62 | transform("F[C@@H]=[C@H]F", "F\\C=C\\F"); 63 | } 64 | 65 | @Test public void e_dichloroethene_explicitH() throws Exception { 66 | transform("F[C@]([H])=[C@@](F)[H]", "F/C(/[H])=C(/F)\\[H]"); 67 | } 68 | 69 | @Test public void e_dichloroethene_alt_explicitH() throws Exception { 70 | transform("F[C@@]([H])=[C@](F)[H]", "F\\C(\\[H])=C(\\F)/[H]"); 71 | } 72 | 73 | @Test public void z_dichloroethene_permuted_1() throws Exception { 74 | transform("F[C@H]=[C@H]F", new int[]{1, 0, 2, 3}, "C(\\F)=C\\F"); 75 | } 76 | 77 | @Test public void z_dichloroethene_permuted_2() throws Exception { 78 | transform("F[C@H]=[C@H]F", new int[]{3, 2, 1, 0}, "F\\C=C/F"); 79 | } 80 | 81 | @Test public void z_dichloroethene_alt_permuted_1() throws Exception { 82 | transform("F[C@@H]=[C@@H]F", new int[]{1, 0, 2, 3}, "C(/F)=C/F"); 83 | } 84 | 85 | @Test public void z_dichloroethene_alt_permuted_2() throws Exception { 86 | transform("F[C@@H]=[C@@H]F", new int[]{3, 2, 1, 0}, "F/C=C\\F"); 87 | } 88 | 89 | @Test public void e_dichloroethene_permuted_1() throws Exception { 90 | transform("F[C@H]=[C@@H]F", new int[]{1, 0, 2, 3}, "C(\\F)=C/F"); 91 | } 92 | 93 | @Test public void e_dichloroethene_permuted_2() throws Exception { 94 | transform("F[C@@H]=[C@H]F", new int[]{3, 2, 1, 0}, "F\\C=C\\F"); 95 | } 96 | 97 | @Test public void conjugated() throws InvalidSmilesException { 98 | transform("F[C@H]=[C@@H][C@H]=[C@@H]F", "F/C=C/C=C/F"); 99 | } 100 | 101 | /** Ensures that conflicting directional assignments are resolved. */ 102 | @Test public void conjugated_conflict() throws InvalidSmilesException { 103 | transform("F[C@H]=[C@@H][C@@H]=[C@H]F", "F/C=C/C=C/F"); 104 | } 105 | 106 | 107 | 108 | @Test public void cyclooctatetraene_1() throws InvalidSmilesException { 109 | transform("[C@H]1=[C@@H][C@@H]=[C@@H][C@@H]=[C@@H][C@@H]=[C@@H]1", 110 | "C\\1=C\\C=C/C=C\\C=C1"); 111 | } 112 | 113 | @Test public void cyclooctatetraene_2() throws InvalidSmilesException { 114 | transform("[C@@H]1=[C@H][C@H]=[C@H][C@H]=[C@H][C@H]=[C@H]1", 115 | "C/1=C/C=C\\C=C/C=C1"); 116 | } 117 | 118 | // @Test public void cyclooctatetraene_3() throws InvalidSmilesException { 119 | // apply("[C@H]1=[C@@H][C@H]=[C@H][C@@H]=[C@@H][C@H]=[C@@H]1", 120 | // "C\\1=C/C=C\\C=C/C=C1"); 121 | // } 122 | 123 | 124 | static void transform(String smi, String exp) throws 125 | InvalidSmilesException { 126 | ImplicitToExplicit ite = new ImplicitToExplicit(); 127 | FromTrigonalTopology ftt = new FromTrigonalTopology(); 128 | ExplicitToImplicit eti = new ExplicitToImplicit(); 129 | Assert.assertThat(Generator 130 | .generate(eti.apply(ftt.apply(ite.apply(Parser.parse(smi))))), 131 | CoreMatchers.is(exp)); 132 | } 133 | 134 | static void transform(String smi, int[] p, String exp) throws 135 | InvalidSmilesException { 136 | ImplicitToExplicit ite = new ImplicitToExplicit(); 137 | FromTrigonalTopology ftt = new FromTrigonalTopology(); 138 | ExplicitToImplicit eti = new ExplicitToImplicit(); 139 | Assert.assertThat(Generator 140 | .generate(eti.apply(ftt.apply(ite.apply(Parser.parse(smi) 141 | .permute(p))))), 142 | CoreMatchers.is(exp)); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/ElementTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Ignore; 33 | import org.junit.Test; 34 | 35 | import java.util.Arrays; 36 | import java.util.Locale; 37 | 38 | import static org.junit.Assert.assertNull; 39 | import static org.hamcrest.CoreMatchers.is; 40 | import static org.junit.Assert.assertThat; 41 | import static org.junit.Assert.assertTrue; 42 | import static uk.ac.ebi.beam.Element.Arsenic; 43 | import static uk.ac.ebi.beam.Element.Boron; 44 | import static uk.ac.ebi.beam.Element.Bromine; 45 | import static uk.ac.ebi.beam.Element.Calcium; 46 | import static uk.ac.ebi.beam.Element.Carbon; 47 | import static uk.ac.ebi.beam.Element.Chlorine; 48 | import static uk.ac.ebi.beam.Element.Fluorine; 49 | import static uk.ac.ebi.beam.Element.Iodine; 50 | import static uk.ac.ebi.beam.Element.Nitrogen; 51 | import static uk.ac.ebi.beam.Element.Oxygen; 52 | import static uk.ac.ebi.beam.Element.Phosphorus; 53 | import static uk.ac.ebi.beam.Element.Selenium; 54 | import static uk.ac.ebi.beam.Element.Sulfur; 55 | import static uk.ac.ebi.beam.Element.Unknown; 56 | 57 | /** @author John May */ 58 | public class ElementTest { 59 | 60 | @Test public void organicSymbols() throws Exception { 61 | assertThat(Element.ofSymbol("B"), is(Boron)); 62 | assertThat(Element.ofSymbol("C"), is(Carbon)); 63 | assertThat(Element.ofSymbol("N"), is(Nitrogen)); 64 | assertThat(Element.ofSymbol("O"), is(Oxygen)); 65 | assertThat(Element.ofSymbol("P"), is(Phosphorus)); 66 | assertThat(Element.ofSymbol("S"), is(Sulfur)); 67 | assertThat(Element.ofSymbol("F"), is(Fluorine)); 68 | assertThat(Element.ofSymbol("Br"), is(Bromine)); 69 | assertThat(Element.ofSymbol("Cl"), is(Chlorine)); 70 | assertThat(Element.ofSymbol("I"), is(Iodine)); 71 | } 72 | 73 | @Test public void aromaticSymbols() throws Exception { 74 | assertThat(Element.ofSymbol("b"), is(Boron)); 75 | assertThat(Element.ofSymbol("c"), is(Carbon)); 76 | assertThat(Element.ofSymbol("n"), is(Nitrogen)); 77 | assertThat(Element.ofSymbol("o"), is(Oxygen)); 78 | assertThat(Element.ofSymbol("p"), is(Phosphorus)); 79 | assertThat(Element.ofSymbol("s"), is(Sulfur)); 80 | assertThat(Element.ofSymbol("se"), is(Selenium)); 81 | assertThat(Element.ofSymbol("as"), is(Arsenic)); 82 | } 83 | 84 | @Test public void symbols() { 85 | for (Element e : Element.values()) { 86 | assertThat(Element.ofSymbol(e.symbol()), is(e)); 87 | } 88 | } 89 | 90 | @Test public void invalidSymbol() { 91 | assertNull(Element.ofSymbol("J")); 92 | } 93 | 94 | @Test public void organic() { 95 | for (Element e : Arrays.asList(Boron, 96 | Carbon, 97 | Nitrogen, 98 | Oxygen, 99 | Phosphorus, 100 | Sulfur, 101 | Fluorine, 102 | Chlorine, 103 | Bromine, 104 | Iodine)) { 105 | assertTrue(e.organic()); 106 | } 107 | } 108 | 109 | @Test public void aromatic() { 110 | for (Element e : Arrays.asList(Boron, 111 | Carbon, 112 | Nitrogen, 113 | Oxygen, 114 | Phosphorus, 115 | Sulfur, 116 | Selenium, 117 | Arsenic)) { 118 | assertTrue(e.aromatic()); 119 | } 120 | } 121 | 122 | @Test public void verify() { 123 | for (Element e : Element.values()) { 124 | boolean valid = e.verify(0, 0); 125 | } 126 | } 127 | 128 | @Test public void ofNumber() { 129 | assertThat(Element.ofNumber(6), is(Element.Carbon)); 130 | assertThat(Element.ofNumber(8), is(Element.Oxygen)); 131 | } 132 | 133 | @Test 134 | public void read() { 135 | for (Element e : Element.values()) { 136 | if (e.aromatic()) 137 | assertThat(Element.read(CharBuffer.fromString(e.symbol() 138 | .toLowerCase(Locale.ENGLISH))), is(e)); 139 | assertThat(Element.read(CharBuffer.fromString(e.symbol())), is(e)); 140 | } 141 | } 142 | 143 | @Test 144 | public void readNone() { 145 | assertNull(Element.read(CharBuffer.fromString(""))); 146 | } 147 | 148 | @Test 149 | public void readInvalidElement() { 150 | assertNull(Element.read(CharBuffer.fromString("J"))); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/MaximumMatchingTest.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import org.hamcrest.collection.IsIterableWithSize; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.hasItems; 7 | import static org.junit.Assert.assertThat; 8 | 9 | /** @author John May */ 10 | public class MaximumMatchingTest { 11 | 12 | /** Contrived example to test blossoming. */ 13 | @Test public void blossom() throws Exception { 14 | 15 | Graph g = Graph.fromSmiles("CCCCCC1CCCC1CC"); 16 | Matching m = Matching.empty(g); 17 | 18 | // initial matching from double-bonds (size = 5) 19 | m.match(1, 2); 20 | m.match(3, 4); 21 | m.match(5, 6); 22 | m.match(7, 8); 23 | m.match(9, 10); 24 | 25 | MaximumMatching.maximise(g, m, 10); 26 | 27 | // once maximised the matching has been augmented such that there 28 | // are now six disjoint edges (only possibly by contracting blossom) 29 | assertThat(m.matches(), 30 | IsIterableWithSize.iterableWithSize(6)); 31 | assertThat(m.matches(), 32 | hasItems(Tuple.of(0, 1), 33 | Tuple.of(2, 3), 34 | Tuple.of(4, 5), 35 | Tuple.of(6, 7), 36 | Tuple.of(8, 9), 37 | Tuple.of(10, 11))); 38 | } 39 | 40 | @Test public void simple_maximal() throws Exception { 41 | Graph g = Graph.fromSmiles("cccc"); 42 | Matching m = MaximumMatching.maximal(g); 43 | assertThat(m.matches(), 44 | hasItems(Tuple.of(0, 1), 45 | Tuple.of(2, 3))); 46 | } 47 | 48 | @Test public void simple_augment() throws Exception { 49 | Graph g = Graph.fromSmiles("cccc"); 50 | Matching m = Matching.empty(g); 51 | m.match(1, 2); 52 | MaximumMatching.maximise(g, m, 2); 53 | assertThat(m.matches(), 54 | hasItems(Tuple.of(0, 1), 55 | Tuple.of(2, 3))); 56 | } 57 | 58 | @Test public void simple_augment_subset() throws Exception { 59 | Graph g = Graph.fromSmiles("cccc"); 60 | Matching m = Matching.empty(g); 61 | m.match(1, 2); 62 | // no vertex '3' matching can not be improved 63 | MaximumMatching.maximise(g, m, 2, IntSet.allOf(0, 1, 2)); 64 | assertThat(m.matches(), 65 | hasItems(Tuple.of(1, 2))); 66 | } 67 | 68 | @Test public void furan() throws Exception { 69 | Graph g = Graph.fromSmiles("o1cccc1"); 70 | IntSet s = IntSet.allOf(1, 2, 3, 4); // exclude the oxygen 71 | Matching m = Matching.empty(g); 72 | MaximumMatching.maximise(g, m, 0, s); 73 | assertThat(m.matches(), hasItems(Tuple.of(1, 2), 74 | Tuple.of(3, 4))); 75 | } 76 | 77 | @Test public void furan_augment() throws Exception { 78 | Graph g = Graph.fromSmiles("o1cccc1"); 79 | IntSet s = IntSet.allOf(1, 2, 3, 4); // exclude the oxygen 80 | Matching m = Matching.empty(g); 81 | m.match(2, 3); 82 | MaximumMatching.maximise(g, m, 2, s); 83 | assertThat(m.matches(), hasItems(Tuple.of(1, 2), 84 | Tuple.of(3, 4))); 85 | } 86 | 87 | @Test public void quinone() throws Exception { 88 | Graph g = Graph.fromSmiles("oc1ccc(o)cc1"); 89 | Matching m = MaximumMatching.maximal(g); 90 | assertThat(m.matches(), hasItems(Tuple.of(0, 1), 91 | Tuple.of(2, 3), 92 | Tuple.of(4, 5), 93 | Tuple.of(6, 7))); 94 | } 95 | 96 | @Test public void quinone_subset() throws Exception { 97 | Graph g = Graph.fromSmiles("oc1ccc(o)cc1"); 98 | // mocks the case where the oxygen atoms are already double bonded - we 99 | // therefore don't include those of the adjacent carbons in the vertex 100 | // subset to be matched 101 | Matching m = Matching.empty(g); 102 | MaximumMatching.maximise(g, m, 0, IntSet.allOf(2, 3, 6, 7)); 103 | assertThat(m.matches(), hasItems(Tuple.of(2, 3), 104 | Tuple.of(6, 7))); 105 | } 106 | 107 | @Test public void napthalene_augment() throws Exception { 108 | Graph g = Graph.fromSmiles("C1C=CC2=CCC=CC2=C1"); 109 | Matching m = Matching.empty(g); 110 | m.match(1, 2); 111 | m.match(3, 4); 112 | m.match(6, 7); 113 | m.match(8, 9); 114 | MaximumMatching.maximise(g, m, 8); 115 | assertThat(m.matches(), hasItems(Tuple.of(0, 1), 116 | Tuple.of(2, 3), 117 | Tuple.of(4, 5), 118 | Tuple.of(6, 7), 119 | Tuple.of(8, 9))); 120 | } 121 | 122 | @Test public void azulene() throws Exception { 123 | Graph g = Graph.fromSmiles("C1CC2CCCCCC2C1"); 124 | Matching m = MaximumMatching.maximal(g); 125 | assertThat(m.matches(), hasItems(Tuple.of(0, 1), 126 | Tuple.of(2, 3), 127 | Tuple.of(4, 5), 128 | Tuple.of(6, 7), 129 | Tuple.of(8, 9))); 130 | } 131 | 132 | @Test public void imidazole() throws Exception { 133 | Graph g = Graph.fromSmiles("[nH]1ccnc1"); 134 | Matching m = Matching.empty(g); 135 | MaximumMatching.maximise(g, 136 | m, 137 | 0, 138 | IntSet.allOf(1, 2, 3, 4)); // not the 'nH' 139 | assertThat(m.matches(), hasItems(Tuple.of(1, 2), 140 | Tuple.of(3, 4))); 141 | } 142 | 143 | @Test public void benzimidazole() throws Exception { 144 | Graph g = Graph.fromSmiles("c1nc2ccccc2[nH]1"); 145 | Matching m = Matching.empty(g); 146 | MaximumMatching.maximise(g, 147 | m, 148 | 0, 149 | IntSet.noneOf(8)); // not the 'nH' 150 | assertThat(m.matches(), hasItems(Tuple.of(0, 1), 151 | Tuple.of(2, 3), 152 | Tuple.of(4, 5), 153 | Tuple.of(6, 7))); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/ParsingBracketAtomTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Test; 33 | 34 | import static org.hamcrest.CoreMatchers.is; 35 | import static org.junit.Assert.assertThat; 36 | 37 | /** 38 | * Unit tests for bracket atoms. Examples are lifted from the specification. 39 | * 40 | * @author John May 41 | */ 42 | public class ParsingBracketAtomTest { 43 | 44 | @Test public void uranium() throws InvalidSmilesException { 45 | assertThat(parse("[U]"), is(atom(Element.Uranium))); 46 | } 47 | 48 | @Test public void lead() throws InvalidSmilesException { 49 | assertThat(parse("[Pb]"), is(atom(Element.Lead))); 50 | } 51 | 52 | @Test public void helium() throws InvalidSmilesException { 53 | assertThat(parse("[He]"), is(atom(Element.Helium))); 54 | } 55 | 56 | @Test public void unknown() throws InvalidSmilesException { 57 | assertThat(parse("[*]"), is(atom(Element.Unknown))); 58 | } 59 | 60 | @Test public void identical() throws InvalidSmilesException { 61 | assertThat(parse("[C]"), is(parse("[CH0]"))); 62 | } 63 | 64 | @Test public void identical2() throws InvalidSmilesException { 65 | assertThat(parse("[CH]"), is(parse("[CH1]"))); 66 | } 67 | 68 | @Test public void methane() throws InvalidSmilesException { 69 | assertThat(parse("[CH4]"), is(atom(Element.Carbon, 4))); 70 | } 71 | 72 | @Test public void hydrochloricAcid() throws InvalidSmilesException { 73 | assertThat(parse("[ClH]"), is(atom(Element.Chlorine, 1))); 74 | } 75 | 76 | @Test public void hydrochloricAcid1() throws InvalidSmilesException { 77 | assertThat(parse("[ClH1]"), is(atom(Element.Chlorine, 1))); 78 | } 79 | 80 | @Test public void chlorineAnion() throws InvalidSmilesException { 81 | assertThat(parse("[Cl-]"), is(atom(Element.Chlorine, 0, -1))); 82 | } 83 | 84 | @Test public void hydroxylAnion() throws InvalidSmilesException { 85 | assertThat(parse("[OH1-]"), is(atom(Element.Oxygen, 1, -1))); 86 | } 87 | 88 | @Test public void hydroxylAnionAlt() throws InvalidSmilesException { 89 | assertThat(parse("[OH-1]"), is(atom(Element.Oxygen, 1, -1))); 90 | } 91 | 92 | @Test public void copperCation() throws InvalidSmilesException { 93 | assertThat(parse("[Cu+2]"), is(atom(Element.Copper, 0, +2))); 94 | } 95 | 96 | @Test public void copperCationAlt() throws InvalidSmilesException { 97 | assertThat(parse("[Cu++]"), is(atom(Element.Copper, 0, +2))); 98 | } 99 | 100 | @Test public void methaneIsotope() throws InvalidSmilesException { 101 | assertThat(parse("[13CH4]"), is(atom(13, Element.Carbon, 4, 0))); 102 | } 103 | 104 | @Test public void deuteriumIon() throws InvalidSmilesException { 105 | assertThat(parse("[2H+]"), is(atom(2, Element.Hydrogen, 0, +1))); 106 | } 107 | 108 | @Test public void uranium238Atom() throws InvalidSmilesException { 109 | assertThat(parse("[238U]"), is(atom(238, Element.Uranium, 0, 0))); 110 | } 111 | 112 | // An isotope is interpreted as a number, so that [2H], [02H] and [002H] all mean deuterium. 113 | @Test public void isotopePadding() throws InvalidSmilesException { 114 | assertThat(parse("[2H]"), is(parse("[02H]"))); 115 | assertThat(parse("[2H]"), is(parse("[002H]"))); 116 | assertThat(parse("[2H]"), is(parse("[0002H]"))); 117 | } 118 | 119 | @Test public void chlorine36() throws InvalidSmilesException { 120 | assertThat(parse("[36Cl]"), is(atom(36, Element.Chlorine, 0, 0))); 121 | } 122 | 123 | // A general-purpose SMILES parser must accept at least three digits for the isotope and values from 0 to 999. 124 | @Test public void rangeCheck() throws InvalidSmilesException { 125 | for (int i = 0; i < 999; i++) { 126 | assertThat(parse("[" + Integer 127 | .toString(i) + "C]"), is(atom(i, Element.Carbon, 0, 0))); 128 | } 129 | } 130 | 131 | @Test public void methaneAtomClassIs2() throws InvalidSmilesException { 132 | assertThat(parse("[CH4:2]").atomClass(), is(2)); 133 | } 134 | 135 | private Atom parse(String str) throws InvalidSmilesException { 136 | CharBuffer buffer = CharBuffer.fromString(str); 137 | return new Parser(buffer, false).molecule().atom(0); 138 | } 139 | 140 | private Atom atom(Element e) { 141 | return new AtomImpl.BracketAtom(e, 0, 0); 142 | } 143 | 144 | private Atom atom(Element e, int hCount) { 145 | return new AtomImpl.BracketAtom(e, hCount, 0); 146 | } 147 | 148 | private Atom atom(Element e, int hCount, int charge) { 149 | return new AtomImpl.BracketAtom(e, hCount, charge); 150 | } 151 | 152 | private Atom atom(int isotope, Element e, int hCount, int charge) { 153 | return new AtomImpl.BracketAtom(isotope, e, hCount, charge, 0, false); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /core/src/test/java/uk/ac/ebi/beam/ConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.junit.Test; 33 | 34 | import static org.hamcrest.CoreMatchers.is; 35 | import static org.junit.Assert.assertThat; 36 | 37 | /** @author John May */ 38 | public class ConfigurationTest { 39 | 40 | @Test public void tetrahedralShorthand() throws Exception { 41 | assertThat(Configuration.TH1 42 | .shorthand(), is(Configuration.ANTI_CLOCKWISE)); 43 | assertThat(Configuration.TH2.shorthand(), is(Configuration.CLOCKWISE)); 44 | } 45 | 46 | @Test public void tetrahedralType() throws Exception { 47 | assertThat(Configuration.TH1 48 | .type(), is(Configuration.Type.Tetrahedral)); 49 | assertThat(Configuration.TH2 50 | .type(), is(Configuration.Type.Tetrahedral)); 51 | } 52 | 53 | @Test public void read() throws Exception { 54 | for (Configuration config : Configuration.values()) { 55 | assertThat(Configuration.read(CharBuffer 56 | .fromString(config.symbol())), 57 | is(config)); 58 | } 59 | } 60 | 61 | @Test public void readNone() throws Exception { 62 | assertThat(Configuration.read(CharBuffer 63 | .fromString("]")), 64 | is(Configuration.UNKNOWN)); 65 | } 66 | 67 | @Test public void readNone1() throws Exception { 68 | assertThat(Configuration.read(CharBuffer 69 | .fromString("")), 70 | is(Configuration.UNKNOWN)); 71 | } 72 | 73 | @Test(expected = InvalidSmilesException.class) 74 | public void noTHNumber() throws InvalidSmilesException { 75 | Configuration.read(CharBuffer.fromString("@TH")); 76 | } 77 | 78 | @Test(expected = InvalidSmilesException.class) 79 | public void invalidTHNumber() throws InvalidSmilesException { 80 | Configuration.read(CharBuffer.fromString("@TH5")); 81 | } 82 | 83 | @Test(expected = InvalidSmilesException.class) 84 | public void noSPNumber() throws InvalidSmilesException { 85 | Configuration.read(CharBuffer.fromString("@SP")); 86 | } 87 | 88 | @Test(expected = InvalidSmilesException.class) 89 | public void invalidSPNumber() throws InvalidSmilesException { 90 | Configuration.read(CharBuffer.fromString("@SP4")); 91 | } 92 | 93 | @Test(expected = InvalidSmilesException.class) 94 | public void noALNumber() throws InvalidSmilesException { 95 | Configuration.read(CharBuffer.fromString("@AL")); 96 | } 97 | 98 | @Test(expected = InvalidSmilesException.class) 99 | public void invalidALNumber() throws InvalidSmilesException { 100 | Configuration.read(CharBuffer.fromString("@AL3")); 101 | } 102 | 103 | @Test(expected = InvalidSmilesException.class) 104 | public void noTBNumber() throws InvalidSmilesException { 105 | Configuration.read(CharBuffer.fromString("@TB")); 106 | } 107 | 108 | @Test(expected = InvalidSmilesException.class) 109 | public void invalidLoTBNumber() throws InvalidSmilesException { 110 | Configuration.read(CharBuffer.fromString("@TB0")); 111 | } 112 | 113 | @Test(expected = InvalidSmilesException.class) 114 | public void invalidHiTBNumber() throws InvalidSmilesException { 115 | Configuration.read(CharBuffer.fromString("@TB21")); 116 | } 117 | 118 | @Test(expected = InvalidSmilesException.class) 119 | public void noOHNumber() throws InvalidSmilesException { 120 | Configuration.read(CharBuffer.fromString("@OH")); 121 | } 122 | 123 | @Test(expected = InvalidSmilesException.class) 124 | public void invalidLoOHNumber() throws InvalidSmilesException { 125 | Configuration.read(CharBuffer.fromString("@OH0")); 126 | } 127 | 128 | @Test(expected = InvalidSmilesException.class) 129 | public void invalidHiOHNumber() throws InvalidSmilesException { 130 | Configuration.read(CharBuffer.fromString("@OH31")); 131 | } 132 | 133 | @Test public void antiClockwise() throws InvalidSmilesException { 134 | assertThat(Configuration.read(CharBuffer.fromString("@H")), 135 | is(Configuration.ANTI_CLOCKWISE)); 136 | } 137 | 138 | @Test(expected = InvalidSmilesException.class) 139 | public void incompleteTHorTB() throws InvalidSmilesException { 140 | Configuration.read(CharBuffer.fromString("@T")); 141 | } 142 | 143 | @Test(expected = InvalidSmilesException.class) 144 | public void incompleteSP() throws InvalidSmilesException { 145 | Configuration.read(CharBuffer.fromString("@S")); 146 | } 147 | 148 | @Test(expected = InvalidSmilesException.class) 149 | public void incompleteOH() throws InvalidSmilesException { 150 | Configuration.read(CharBuffer.fromString("@O")); 151 | } 152 | 153 | @Test(expected = InvalidSmilesException.class) 154 | public void incompleteAL() throws InvalidSmilesException { 155 | Configuration.read(CharBuffer.fromString("@A")); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /func/src/main/java/uk/ac/ebi/beam/Functions.java: -------------------------------------------------------------------------------- 1 | package uk.ac.ebi.beam; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * Collection of utilities for transforming chemical graphs. 7 | * 8 | * @author John May 9 | */ 10 | public final class Functions { 11 | 12 | // convert to atom-based double-bond configurations 13 | private static final ToTrigonalTopology ttt = new ToTrigonalTopology(); 14 | 15 | // convert to bond-based double-bond configuration 16 | private static final FromTrigonalTopology ftt = new FromTrigonalTopology(); 17 | 18 | // bond label conversion -> to implicit 19 | private static final ExplicitToImplicit eti = new ExplicitToImplicit(); 20 | 21 | // bond label conversion -> to explicit 22 | private static final ImplicitToExplicit ite = new ImplicitToExplicit(); 23 | 24 | // use organic subset 25 | private static final ToSubsetAtoms tsa = new ToSubsetAtoms(); 26 | 27 | // expand organic subset 28 | private static final FromSubsetAtoms fsa = new FromSubsetAtoms(); 29 | 30 | // normalise directional labels 31 | private static final NormaliseDirectionalLabels ndl = new NormaliseDirectionalLabels(); 32 | 33 | private static final AddDirectionalLabels adl = new AddDirectionalLabels(); 34 | 35 | private static Random rand = new Random(); 36 | 37 | /// non-instantiable 38 | private Functions() { 39 | } 40 | 41 | /** 42 | * Randomise the atom order of the provided chemical graph. 43 | * 44 | * @param g chemical graph 45 | * @return a copy of the original graph with the order of the atoms 46 | * randomised 47 | */ 48 | public static Graph randomise(Graph g) { 49 | return g.permute(random(g.order())); 50 | } 51 | 52 | /** 53 | * Reverse the atom order of the provided chemical graph. 54 | * 55 | * @param g chemical graph 56 | * @return a copy of the original graph with the order of the atoms 57 | * reversed 58 | */ 59 | public static Graph reverse(Graph g) { 60 | return g.permute(reverse(g.order())); 61 | } 62 | 63 | /** 64 | * Convert any directional bond based stereo configuration to atom-based 65 | * specification. 66 | * 67 | * @param g chemical graph graph 68 | * @return a copy of the original graph but with directional bonds removed 69 | * and atom-based double-bond stereo configruation. 70 | */ 71 | public static Graph atomBasedDBStereo(Graph g) { 72 | return eti.apply(ttt.apply(ite.apply(g))); 73 | } 74 | 75 | /** 76 | * Convert a graph with atom-based double-bond stereo configuration to 77 | * bond-based specification (direction UP and DOWN bonds). 78 | * 79 | * @param g chemical graph graph 80 | * @return a copy of the original graph but with bond-based 81 | * stereo-chemistry 82 | */ 83 | public static Graph bondBasedDBStereo(Graph g) { 84 | return eti.apply(ftt.apply(ite.apply(g))); 85 | } 86 | 87 | /** 88 | * Expand a graph with organic subsets to one with specified atom 89 | * properties. 90 | * 91 | * @param g a chemical graph 92 | * @return the chemical graph expanded 93 | */ 94 | public static Graph expand(Graph g) { 95 | return eti.apply(fsa.apply(ite.apply(g))); 96 | } 97 | 98 | /** 99 | * Collapse a graph with specified atom properties to one with organic 100 | * subset atoms. 101 | * 102 | * @param g a chemical graph 103 | * @return the chemical graph expanded 104 | */ 105 | public static Graph collapse(Graph g) { 106 | return eti.apply(tsa.apply(ite.apply(g))); 107 | } 108 | 109 | public static Graph normaliseDirectionalLabels(Graph g) throws InvalidSmilesException { 110 | if (g.getFlags(Graph.HAS_BND_STRO) == 0) 111 | return g; 112 | return ndl.apply(g); 113 | } 114 | 115 | private static int[] ident(int n) { 116 | int[] p = new int[n]; 117 | for (int i = 0; i < n; i++) 118 | p[i] = i; 119 | return p; 120 | } 121 | 122 | /** 123 | * Apply the labeling {@code labels[]} to the graph {@code g}. The labels 124 | * are converted to a permutation which is then applied to the Graph and 125 | * rearrange it's vertex order. 126 | * 127 | * @param g the graph to permute 128 | * @param labels the vertex labels - for example from a cannibalisation 129 | * algorithm 130 | * @return a cpy of the original graph with it's vertices permuted by the 131 | * labelling 132 | */ 133 | public static Graph canonicalize(final Graph g, 134 | final long[] labels) { 135 | 136 | Integer[] is = new Integer[g.order()]; 137 | 138 | for (int i = 0; i < is.length; i++) 139 | is[i] = i; 140 | 141 | // TODO: replace with radix sort (i.e. using a custom comparator) 142 | Arrays.sort(is, new Comparator() { 143 | @Override public int compare(Integer i, Integer j) { 144 | if (labels[i] > labels[j]) 145 | return +1; 146 | else if (labels[i] < labels[j]) 147 | return -1; 148 | return 0; 149 | } 150 | }); 151 | 152 | int[] p = new int[g.order()]; 153 | for (int i = 0; i < is.length; i++) 154 | p[is[i]] = i; 155 | return g.permute(p); 156 | } 157 | 158 | /** 159 | * Renumbers atom-atom maps using a depth-first traversal. Note this function 160 | * modifies the input graph. 161 | * @param g the graph 162 | * @return the input graph 163 | */ 164 | public static Graph renumberAtomMaps(final Graph g) { 165 | RenumberAtomMaps.renumber(g); 166 | return g; 167 | } 168 | 169 | /** 170 | * Generate a random permutation. 171 | * @param n size of the permutation 172 | * @param rnd random number generator 173 | * @return the permutation 174 | */ 175 | private static int[] random(int n, Random rnd) { 176 | int[] p = ident(n); 177 | for (int i = n; i > 1; i--) 178 | swap(p, i - 1, rnd.nextInt(i)); 179 | return p; 180 | } 181 | 182 | /** 183 | * Generate a random permutation using a shared RNG instance. The method is synchronized 184 | * @param n size of the permutation 185 | * @return the permutation 186 | */ 187 | private synchronized static int[] random(int n) { 188 | return random(n, rand); 189 | } 190 | 191 | private static int[] reverse(int n) { 192 | int[] p = new int[n]; 193 | for (int i = 0; i < n; i++) 194 | p[i] = n - i - 1; 195 | return p; 196 | } 197 | 198 | // inverse of permutation 199 | private static int[] inv(int[] p) { 200 | int[] q = p.clone(); 201 | for (int i = 0; i < p.length; i++) 202 | q[p[i]] = i; 203 | return q; 204 | } 205 | 206 | private static void swap(int[] p, int i, int j) { 207 | int tmp = p[i]; 208 | p[i] = p[j]; 209 | p[j] = tmp; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /func/src/test/java/uk/ac/ebi/beam/ToTrigonalTopologyTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, European Bioinformatics Institute (EMBL-EBI) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * The views and conclusions contained in the software and documentation are those 26 | * of the authors and should not be interpreted as representing official policies, 27 | * either expressed or implied, of the FreeBSD Project. 28 | */ 29 | 30 | package uk.ac.ebi.beam; 31 | 32 | import org.hamcrest.CoreMatchers; 33 | import org.junit.Assert; 34 | import org.junit.Test; 35 | 36 | import static org.hamcrest.CoreMatchers.is; 37 | 38 | /** @author John May */ 39 | public class ToTrigonalTopologyTest { 40 | 41 | 42 | @Test public void e_difluoroethene_impl1() throws InvalidSmilesException { 43 | transform("F/C=C/F", "F[C@H]=[C@@H]F"); 44 | } 45 | 46 | @Test public void z_difluoroethene_impl2() throws InvalidSmilesException { 47 | transform("F/C=C\\F", "F[C@H]=[C@H]F"); 48 | } 49 | 50 | @Test public void e_difluoroethene_impl3() throws InvalidSmilesException { 51 | transform("F\\C=C\\F", "F[C@@H]=[C@H]F"); 52 | } 53 | 54 | @Test public void z_difluoroethene_impl4() throws InvalidSmilesException { 55 | transform("F\\C=C/F", "F[C@@H]=[C@@H]F"); 56 | } 57 | 58 | @Test public void e_difluoroethene_exp1() throws InvalidSmilesException { 59 | transform("F/C([H])=C([H])/F", "F[C@]([H])=[C@@]([H])F"); 60 | } 61 | 62 | @Test public void z_difluoroethene_exp2() throws InvalidSmilesException { 63 | transform("F/C([H])=C([H])\\F", "F[C@]([H])=[C@]([H])F"); 64 | } 65 | 66 | @Test public void e_difluoroethene_exp3() throws InvalidSmilesException { 67 | transform("F\\C([H])=C([H])\\F", "F[C@@]([H])=[C@]([H])F"); 68 | } 69 | 70 | @Test public void z_difluoroethene_exp4() throws InvalidSmilesException { 71 | transform("F\\C([H])=C([H])/F", "F[C@@]([H])=[C@@]([H])F"); 72 | } 73 | 74 | @Test public void z_difluoroethene_exp5() throws InvalidSmilesException { 75 | transform("FC(\\[H])=C([H])/F", "F[C@@]([H])=[C@@]([H])F"); 76 | transform("FC(\\[H])=C(\\[H])F", "F[C@@]([H])=[C@@]([H])F"); 77 | transform("F\\C([H])=C(\\[H])F", "F[C@@]([H])=[C@@]([H])F"); 78 | } 79 | 80 | @Test public void e_difluoroethene_exp6() throws InvalidSmilesException { 81 | transform("FC(\\[H])=C([H])\\F", "F[C@@]([H])=[C@]([H])F"); 82 | transform("FC(\\[H])=C(/[H])F", "F[C@@]([H])=[C@]([H])F"); 83 | transform("F\\C([H])=C(/[H])F", "F[C@@]([H])=[C@]([H])F"); 84 | } 85 | 86 | @Test public void z_difluoroethene_exp7() throws InvalidSmilesException { 87 | transform("FC(/[H])=C([H])\\F", "F[C@]([H])=[C@]([H])F"); 88 | transform("FC(/[H])=C(/[H])F", "F[C@]([H])=[C@]([H])F"); 89 | transform("F/C([H])=C(/[H])F", "F[C@]([H])=[C@]([H])F"); 90 | } 91 | 92 | @Test public void e_difluoroethene_exp8() throws InvalidSmilesException { 93 | transform("FC(/[H])=C([H])/F", "F[C@]([H])=[C@@]([H])F"); 94 | transform("FC(/[H])=C(\\[H])F", "F[C@]([H])=[C@@]([H])F"); 95 | transform("F/C([H])=C(\\[H])F", "F[C@]([H])=[C@@]([H])F"); 96 | } 97 | 98 | @Test public void e_difluoroethene_explicitH_9() throws InvalidSmilesException { 99 | transform("C(\\F)([H])=C([H])/F", 100 | "[C@](F)([H])=[C@@]([H])F"); 101 | } 102 | 103 | @Test public void e_difluoroethene_permuted() throws InvalidSmilesException { 104 | transform("F/C=C/F", 105 | new int[]{1, 0, 2, 3}, 106 | "[C@@H](F)=[C@@H]F"); 107 | } 108 | 109 | @Test public void e_difluoroethene_explicitH_permutation_1() throws InvalidSmilesException { 110 | transform("F/C([H])=C([H])/F", 111 | new int[]{1, 0, 2, 3, 4, 5}, 112 | "[C@](F)([H])=[C@@]([H])F"); 113 | } 114 | 115 | @Test public void e_difluoroethene_explicitH_permutation_2() throws InvalidSmilesException { 116 | transform("F/C([H])=C([H])/F", 117 | new int[]{2, 0, 1, 3, 4, 5}, 118 | "[C@@]([H])(F)=[C@@]([H])F"); 119 | } 120 | 121 | @Test public void e_difluoroethene_explicitH_permutation_3() throws InvalidSmilesException { 122 | transform("F/C([H])=C([H])/F", 123 | new int[]{2, 0, 1, 3, 5, 4}, 124 | "[C@@]([H])(F)=[C@](F)[H]"); 125 | } 126 | 127 | @Test public void cyclooctatetraene() throws InvalidSmilesException { 128 | transform("C/1=C/C=C\\C=C/C=C1", 129 | "[C@H]1=[C@@H][C@H]=[C@H][C@@H]=[C@@H][C@H]=[C@H]1"); 130 | } 131 | 132 | @Test public void unspecified() throws InvalidSmilesException { 133 | transform("FC=CF", 134 | "FC=CF"); 135 | } 136 | 137 | 138 | static void transform(String smi, String exp) throws 139 | InvalidSmilesException { 140 | ImplicitToExplicit ite = new ImplicitToExplicit(); 141 | ToTrigonalTopology ttt = new ToTrigonalTopology(); 142 | ExplicitToImplicit eti = new ExplicitToImplicit(); 143 | Assert.assertThat(Generator 144 | .generate(eti.apply(ttt.apply(ite.apply(Parser.parse(smi))))), 145 | CoreMatchers.is(exp)); 146 | } 147 | 148 | static void transform(String smi, int[] p, String exp) throws 149 | InvalidSmilesException { 150 | ImplicitToExplicit ite = new ImplicitToExplicit(); 151 | ToTrigonalTopology ttt = new ToTrigonalTopology(); 152 | ExplicitToImplicit eti = new ExplicitToImplicit(); 153 | Assert.assertThat(Generator 154 | .generate(eti.apply(ttt.apply(ite.apply(Parser.parse(smi) 155 | .permute(p))))), 156 | CoreMatchers.is(exp)); 157 | } 158 | 159 | } 160 | --------------------------------------------------------------------------------