/*************************************************************************
* *
* 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;
}
}