/************************************************************************* * * * This file is part of the 20n/act project. * * 20n/act enables DNA prediction for synthetic biology/bioengineering. * * Copyright (C) 2017 20n Labs, Inc. * * * * Please direct all queries to act@20n.com. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * *************************************************************************/ package com.act.biointerpretation.mechanisminspection; import chemaxon.calculations.clean.Cleaner; import chemaxon.formats.MolExporter; import chemaxon.formats.MolFormatException; import chemaxon.formats.MolImporter; import chemaxon.reaction.Reactor; import chemaxon.struc.Molecule; import com.act.biointerpretation.Utils.ReactionProjector; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; public class ReactionProjectorTest { static final String SUBSTRATE_1 = "InChI=1S/C10H6O2/c11-9-5-6-10(12)8-4-2-1-3-7(8)9/h1-6H"; static final String SUBSTRATE_2 = "InChI=1S/C4H6O4/c5-3(6)1-2-4(7)8/h1-2H2,(H,5,6)(H,7,8)"; static final String SUBSTRATE_3_SPECIFIC = "InChI=1S/C7H9N/c8-6-7-4-2-1-3-5-7/h1-5H,6,8H2"; static final String SUBSTRATE_3_AMBIGUOUS = "InChI=1S/C6H8N2/c7-8-6-4-2-1-3-5-6/h1-5,8H,7H2"; static final String PRODUCT_1 = "InChI=1S/C10H8O2/c11-9-5-6-10(12)8-4-2-1-3-7(8)9/h1-6,11-12H"; static final String PRODUCT_2 = "InChI=1S/C4H4O4/c5-3(6)1-2-4(7)8/h1-2H,(H,5,6)(H,7,8)/b2-1+"; static final String PRODUCT_3_SPECIFIC = "InChI=1S/C7H9NO3S/c9-12(10,11)8-6-7-4-2-1-3-5-7/h1-5,8H,6H2,(H,9,10,11)"; static final String PRODUCT_3_AMBIGUOUS_OPTION_1 = "InChI=1S/C6H8N2O3S/c9-12(10,11)8-7-6-4-2-1-3-5-6/h1-5,7-8H,(H,9,10,11)"; static final String PRODUCT_3_AMBIGUOUS_OPTION_2 = "InChI=1S/C6H8N2O3S/c7-8(12(9,10)11)6-4-2-1-3-5-6/h1-5H,7H2,(H,9,10,11)"; static final String NO_COEFFICIENT_RO = "[C:1](=[O:7])1[C,c:2]=[C,c:3][C:4](=[O:8])[c:5][c:6]1.[C:9](H)[C:10](H).[N:11](H)>>[c:1]([OH:7])1[c:2][c:3]" + "[c:4]([OH:8])[c:5][c:6]1.[C:9]=[C:10].[N:11]S(=O)(=O)[OH]"; static final String COEFFICIENT_CORRECT_RO = "[H][#8:3]-[#6:2].[H][#8:9]-[#6:8].[#6:4]-[#8:5][P:13]([#8:14])([#8:15])=[O:16]>>[H][#8:5]-[#6:4].[#6:2]-[#8:3][P:13]([#8:14])([#8:15])=[O:16].[H][#8]P(=O)([#8][H])[#8:9]-[#6:8]"; static final String COEFFICIENT_INCORRECT_RO = "[H][#8:9]-[#6:8].[#6:4]-[#8:5][P:13]([#8:14])([#8:15])=[O:16].[H][#8]P(=O)([#8][H])[#8:11]-[#6:10]>>[H][#8:5]-[#6:4].[H][#8:11]-[#6:10].[#6:8]-[#8:9][P:13]([#8:14])([#8:15])=[O:16]"; static final String COEFFICIENT_SUBSTRATE_1 = "InChI=1S/CH4O/c1-2/h2H,1H3"; static final String COEFFICIENT_SUBSTRATE_2 = "InChI=1S/CH5O4P/c1-5-6(2,3)4/h1H3,(H2,2,3,4)"; static final String COEFFICIENT_PRODUCT_1 = "InChI=1S/CH4O/c1-2/h2H,1H3"; static final String COEFFICIENT_PRODUCT_2 = "InChI=1S/CH5O4P/c1-5-6(2,3)4/h1H3,(H2,2,3,4)"; @Test public void testReactionProjectorWorksOnMultipleSubstrateReactionsWithoutClearOrdering() throws Exception { List<String> expectedProducts = Arrays.asList(PRODUCT_1, PRODUCT_2, PRODUCT_3_SPECIFIC); String[] correctCombination = new String[]{ SUBSTRATE_1, SUBSTRATE_2, SUBSTRATE_3_SPECIFIC }; String[] permutation1 = new String[]{ SUBSTRATE_1, SUBSTRATE_3_SPECIFIC, SUBSTRATE_2 }; String[] permutation2 = new String[]{ SUBSTRATE_3_SPECIFIC, SUBSTRATE_1, SUBSTRATE_2 }; String[][] testCombinations = new String[][]{ correctCombination, permutation1, permutation2 }; for (String[] substratesCombination : testCombinations) { Molecule[] molSubstrates = getMoleculeArray(substratesCombination); Reactor reactor = new Reactor(); reactor.setReactionString(NO_COEFFICIENT_RO); ReactionProjector projector = new ReactionProjector(); Map<Molecule[], List<Molecule[]>> productsMap = projector.getRoProjectionMap(molSubstrates, reactor); Assert.assertEquals("The products map should contain exactly one entry.", 1, productsMap.size()); Molecule[] substratesFromMap = new ArrayList<>(productsMap.keySet()).get(0); Assert.assertEquals("The products map should contain only the correct substrate combination as a key.", getInchiList(substratesFromMap), Arrays.asList(correctCombination)); List<Molecule[]> productSet = productsMap.get(substratesFromMap); Assert.assertEquals("The product set should contain exactly one product array.", 1, productSet.size()); Molecule[] productArray = productSet.get(0); List<String> productInchis = getInchiList(productArray); Assert.assertEquals("The expected products has to match the actual products produced by the ReactionProjector", expectedProducts, productInchis); } } @Test public void testReactionWithMultiplePossibleOutputsReturnsBoth() throws Exception { List<String> expectedProducts1 = Arrays.asList(PRODUCT_1, PRODUCT_2, PRODUCT_3_AMBIGUOUS_OPTION_1); List<String> expectedProducts2 = Arrays.asList(PRODUCT_1, PRODUCT_2, PRODUCT_3_AMBIGUOUS_OPTION_2); String[] substratesCombination = new String[]{ SUBSTRATE_1, SUBSTRATE_2, SUBSTRATE_3_AMBIGUOUS }; Molecule[] molSubstrates = getMoleculeArray(substratesCombination); Reactor reactor = new Reactor(); reactor.setReactionString(NO_COEFFICIENT_RO); ReactionProjector projector = new ReactionProjector(); Map<Molecule[], List<Molecule[]>> productsMap = projector.getRoProjectionMap(molSubstrates, reactor); Assert.assertEquals("The products map should contain exactly one entry.", 1, productsMap.size()); Molecule[] substratesFromMap = new ArrayList<>(productsMap.keySet()).get(0); Assert.assertEquals("The products map should contain only the correct substrate combination as a key.", getInchiList(substratesFromMap), Arrays.asList(substratesCombination)); List<Molecule[]> productSet = productsMap.get(substratesFromMap); Assert.assertEquals("Product set should contain exactly two predictions.", productSet.size(), 2); List<String> actualSet1 = getInchiList(productSet.get(0)); List<String> actualSet2 = getInchiList(productSet.get(1)); Assert.assertTrue("The first actual product set has to match one set of expected products.", expectedProducts1.equals(actualSet1) || expectedProducts2.equals(actualSet1)); Assert.assertTrue("The second actual product set has to match one set of expected products.", expectedProducts1.equals(actualSet2) || expectedProducts2.equals(actualSet2)); Assert.assertFalse("The two product sets should not be the same.", actualSet1.equals(actualSet2)); } @Test public void testCoefficientDependentReactionRoMatches() throws Exception { List<String> expectedProducts = Arrays.asList(COEFFICIENT_PRODUCT_1, COEFFICIENT_PRODUCT_2, COEFFICIENT_PRODUCT_2); String[] substratesCombination = new String[]{ COEFFICIENT_SUBSTRATE_1, COEFFICIENT_SUBSTRATE_1, COEFFICIENT_SUBSTRATE_2 }; Molecule[] molSubstrates = getMoleculeArray(substratesCombination); // Test a coefficient-dependent RO that should match the substrates. Reactor reactor = new Reactor(); reactor.setReactionString(COEFFICIENT_CORRECT_RO); ReactionProjector projector = new ReactionProjector(); Map<Molecule[], List<Molecule[]>> productsMap = projector.getRoProjectionMap(molSubstrates, reactor); Assert.assertEquals("The products map should have exactly one entry,", 1, productsMap.size()); Molecule[] substratesFromMap = new ArrayList<>(productsMap.keySet()).get(0); Assert.assertEquals("The products map should contain only the correct substrate combination as a key.", getInchiList(substratesFromMap), Arrays.asList(substratesCombination)); List<Molecule[]> productSet = productsMap.get(substratesFromMap); Assert.assertEquals("The product set should contain only one product array.", 1, productSet.size()); Molecule[] productArray = productSet.get(0); List<String> productInchis = getInchiList(productArray); Assert.assertEquals("The expected products has to match the actual products produced by the ReactionProjector", expectedProducts, productInchis); } @Test public void testCoefficientDependentReactionRoDoesNotMatch() throws Exception { String[] substratesCombination = new String[]{ COEFFICIENT_SUBSTRATE_1, COEFFICIENT_SUBSTRATE_1, COEFFICIENT_SUBSTRATE_2 }; Molecule[] molSubstrates = getMoleculeArray(substratesCombination); // Test a coefficient-dependent RO that should not match the substrates. Reactor reactor = new Reactor(); reactor.setReactionString(COEFFICIENT_INCORRECT_RO); ReactionProjector projector = new ReactionProjector(); Map<Molecule[], List<Molecule[]>> products = projector.getRoProjectionMap(molSubstrates, reactor); Assert.assertTrue("The products map should be empty", products.isEmpty()); } private static List<String> getInchiList(Molecule[] molecules) throws IOException { List<String> inchiList = new ArrayList<>(); for (Molecule product : molecules) { inchiList.add(MolExporter.exportToObject(product, "inchi:AuxNone").toString()); } return inchiList; } private static Molecule[] getMoleculeArray(String[] inchiArray) throws MolFormatException { Molecule[] molArray = new Molecule[inchiArray.length]; int counter = 0; for (String inchi : inchiArray) { Molecule mol = MolImporter.importMol(inchi, "inchi"); Cleaner.clean(mol, 2); molArray[counter] = mol; counter++; } return molArray; } }