/*************************************************************************
* *
* 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.networkanalysis;
import com.act.lcms.v2.Metabolite;
import com.act.lcms.v2.MolecularStructure;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Tests for class MetabolismNetwork
*/
public class MetabolismNetworkTest {
private static final String METABOLITE_1 = "A";
private static final String METABOLITE_2 = "B";
private static final String METABOLITE_3 = "C";
private static final String METABOLITE_4 = "D";
private static final String METABOLITE_5 = "E";
private static final String METABOLITE_6 = "F";
private static final String METABOLITE_7 = "G";
private static final Double MASS_1 = 0.1;
private static final Double MASS_2 = 0.2;
private static final Double MASS_3 = 0.4;
private static final Double MASS_4 = 0.6;
private static final Double MASS_5 = 0.7;
private static final Double MASS_6 = 0.9;
private static final Double MASS_7 = 1.0;
private static List<NetworkNode> nodes = new ArrayList<>();
private static Map<String, Integer> inchiToID = new HashMap<>();
private static final Integer RXN_1 = 100;
private static final Integer RXN_2 = 102;
private static final String PROJECTOR = "RO";
@Before
public void createMockNodes() {
nodes.add(new NetworkNode(getMockMetabolite(METABOLITE_1, MASS_1)));
nodes.add(new NetworkNode(getMockMetabolite(METABOLITE_2, MASS_2)));
nodes.add(new NetworkNode(getMockMetabolite(METABOLITE_3, MASS_3)));
nodes.add(new NetworkNode(getMockMetabolite(METABOLITE_4, MASS_4)));
nodes.add(new NetworkNode(getMockMetabolite(METABOLITE_5, MASS_5)));
nodes.add(new NetworkNode(getMockMetabolite(METABOLITE_6, MASS_6)));
nodes.add(new NetworkNode(getMockMetabolite(METABOLITE_7, MASS_7)));
nodes.forEach(n -> inchiToID.put(n.getMetabolite().getStructure().get().getInchi(), n.getUID()));
}
private Metabolite getMockMetabolite(String s, double mass) {
MolecularStructure structure = Mockito.mock(MolecularStructure.class);
Mockito.when(structure.getInchi()).thenReturn(s);
Mockito.when(structure.getMonoIsotopicMass()).thenReturn(mass);
Metabolite m = Mockito.mock(Metabolite.class);
Mockito.when(m.getStructure()).thenReturn(Optional.of(structure));
Mockito.when(m.getMonoIsotopicMass()).thenReturn(mass);
return m;
}
@Test
public void testAddNewEdge() {
// Arrange
MetabolismNetwork network = new MetabolismNetwork();
nodes.forEach(network::addNode);
// Act
network.addEdgeFromInchis(Arrays.asList(METABOLITE_1), Arrays.asList(METABOLITE_2));
// Assert
assertEquals("Graph should have one edge", 1, network.getEdges().size());
assertEquals("Product should have one in edge.", 1, network.getNodeByInchi(METABOLITE_2).getInEdges().size());
assertEquals("Product should have no out edges.", 0, network.getNodeByInchi(METABOLITE_2).getOutEdges().size());
assertEquals("Substrate should have one out edge.", 1, network.getNodeByInchi(METABOLITE_1).getOutEdges().size());
assertEquals("Substrate should have no in edge.", 0, network.getNodeByInchi(METABOLITE_1).getInEdges().size());
NetworkEdge edge = network.getEdges().iterator().next();
assertEquals("Edge should have one substrate", 1, edge.getSubstrates().size());
;
assertEquals("Edge's substrate should be METABOLITE_1", inchiToID.get(METABOLITE_1), edge.getSubstrates().get(0));
assertEquals("Edge should have one product", 1, edge.getProducts().size());
assertEquals("Edge's product should be METABOLITE_2", inchiToID.get(METABOLITE_2), edge.getProducts().get(0));
assertTrue("Edge's reaction info should be empty", edge.getReactionIds().isEmpty());
assertTrue("Edge's RO info should be empty", edge.getProjectorNames().isEmpty());
}
/**
* Tests adding two edges with same substrates and products. Verifies that the edge is not duplicated,
* but the reaction and projection info from the edges is merged.
*/
@Test
public void testAddNewRedundantEdge() {
// Arrange
MetabolismNetwork network = new MetabolismNetwork();
nodes.forEach(network::addNode);
network.addEdgeFromInchis(Arrays.asList(METABOLITE_1), Arrays.asList(METABOLITE_2)).addReactionId(RXN_1);
// Act
network.addEdgeFromInchis(Arrays.asList(METABOLITE_1), Arrays.asList(METABOLITE_2)).addReactionId(RXN_2).addProjectorName(PROJECTOR);
// Assert
assertEquals("Graph should have one edge", 1, network.getEdges().size());
assertEquals("Product should have one in edge.", 1, network.getNodeByInchi(METABOLITE_2).getInEdges().size());
assertEquals("Substrate should have one out edge.", 1, network.getNodeByInchi(METABOLITE_1).getOutEdges().size());
NetworkEdge edge = network.getEdges().iterator().next();
assertEquals("Edge should have one substrate", 1, edge.getSubstrates().size());
assertEquals("Edge's substrate should be METABOLITE_1", inchiToID.get(METABOLITE_1), edge.getSubstrates().get(0));
assertEquals("Edge should have one product", 1, edge.getProducts().size());
assertEquals("Edge's product should be METABOLITE_2", inchiToID.get(METABOLITE_2), edge.getProducts().get(0));
assertEquals("Edge's reaction info should contain 2 reactions", 2, edge.getReactionIds().size());
assertTrue("Edge's reaction info should contain the right reactions",
edge.getReactionIds().containsAll(Arrays.asList(RXN_1, RXN_2)));
assertEquals("Edge's RO info should contain one RO", 1, edge.getProjectorNames().size());
assertTrue("Edge's RO info should contain the right RO", edge.getProjectorNames().contains(PROJECTOR));
}
@Test
public void testGetDerivatives() {
// Arrange
MetabolismNetwork network = new MetabolismNetwork();
nodes.forEach(network::addNode);
network.addEdgeFromInchis(Arrays.asList(METABOLITE_1), Arrays.asList(METABOLITE_2));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_1), Arrays.asList(METABOLITE_3, METABOLITE_4));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_5), Arrays.asList(METABOLITE_3, METABOLITE_1));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_5), Arrays.asList(METABOLITE_3, METABOLITE_2));
// Act
List<NetworkNode> derivatives = network.getDerivatives(network.getNodeByInchi(METABOLITE_1));
// Assert
List<String> expectedInchis = Arrays.asList(METABOLITE_2, METABOLITE_3, METABOLITE_4);
List<String> inchis = derivatives.stream().map(n -> n.getMetabolite().getStructure().get().getInchi()).collect(Collectors.toList());
assertEquals("Should be 3 derivatives.", 3, inchis.size());
assertTrue("Derivatives should be metabolites 2,3,4", inchis.containsAll(expectedInchis));
}
@Test
public void testGetPrecursors() {
// Arrange
MetabolismNetwork network = new MetabolismNetwork();
nodes.forEach(network::addNode);
network.addEdgeFromInchis(Arrays.asList(METABOLITE_2), Arrays.asList(METABOLITE_1));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_3, METABOLITE_4), Arrays.asList(METABOLITE_1));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_1, METABOLITE_3), Arrays.asList(METABOLITE_5));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_2, METABOLITE_3), Arrays.asList(METABOLITE_5));
// Act
List<NetworkNode> precursors = network.getPrecursors(network.getNodeByInchi(METABOLITE_1));
// Assert
List<String> expectedInchis = Arrays.asList(METABOLITE_2, METABOLITE_3, METABOLITE_4);
List<String> inchis = precursors.stream().map(n -> n.getMetabolite().getStructure().get().getInchi()).collect(Collectors.toList());
assertEquals("Should be 3 precursors.", 3, inchis.size());
assertTrue("Precursors should be metabolites 2,3,4", inchis.containsAll(expectedInchis));
}
/**
* Test precursor report of 1 level.
* Adds one relevant inedge, one irrelevant outedge of the precursor, and two level 2 precursors.
* Test verifies that only the substrates of the first inedge are included in the subgraph.
*/
@Test
public void testPrecursorSubgraphN1() {
// Arrange
MetabolismNetwork network = new MetabolismNetwork();
nodes.forEach(network::addNode);
network.addEdgeFromInchis(Arrays.asList(METABOLITE_3, METABOLITE_4), Arrays.asList(METABOLITE_5));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_3), Arrays.asList(METABOLITE_1, METABOLITE_2));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_1), Arrays.asList(METABOLITE_3));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_2), Arrays.asList(METABOLITE_3));
NetworkNode e = network.getNodeByInchi(METABOLITE_5);
// Act
PrecursorReport report = network.getPrecursorReport(e, 1);
// Assert
assertEquals("Report has correct target", e.getMetabolite(), report.getTarget());
ImmutableNetwork precursorNetwork = report.getNetwork();
assertEquals("Subgraph should contain three nodes", 3, precursorNetwork.getNodes().size());
assertEquals("Subgraph should contain one edge", 1, precursorNetwork.getEdges().size());
assertTrue("Subgraph should contain query node", precursorNetwork.getNodeOptionByInchi(METABOLITE_5).isPresent());
assertTrue("Subgraph should contain first precursor", precursorNetwork.getNodeOptionByInchi(METABOLITE_3).isPresent());
assertTrue("Subgraph should contain second precursor", precursorNetwork.getNodeOptionByInchi(METABOLITE_4).isPresent());
}
/**
* Test precursor report of 2 levels.
* Adds one relevant inedge, one irrelevant outedge of a precursor, and two level 2 precursors.
* Test verifies that every metabolite except METABOLITE 6 is reported. Metabolite 6 should not be reported since
* it is the derivative of a precursor of the target metabolite 5. Metabolites 1-5 are either direct percursors,
* or precursors of precursors, and so they should all be reported. Metabolite 7 is a second product of an in-edge
* of the target, so it should also be in the subgraph, but not the level map.
*/
@Test
public void testPrecursorSubgraphN2() {
// Arrange
MetabolismNetwork network = new MetabolismNetwork();
nodes.forEach(network::addNode);
network.addEdgeFromInchis(Arrays.asList(METABOLITE_3, METABOLITE_4), Arrays.asList(METABOLITE_5, METABOLITE_7));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_3, METABOLITE_4), Arrays.asList(METABOLITE_6));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_1), Arrays.asList(METABOLITE_3));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_2), Arrays.asList(METABOLITE_3));
NetworkNode e = network.getNodeByInchi(METABOLITE_5);
// Act
PrecursorReport report = network.getPrecursorReport(e, 2);
// Assert
ImmutableNetwork precursorNetwork = report.getNetwork();
assertEquals("Subgraph should contain six nodes", 6, precursorNetwork.getNodes().size());
assertEquals("Subgraph should contain three edges", 3, precursorNetwork.getEdges().size());
assertTrue("Subgraph should contain query node", precursorNetwork.getNodeOptionByInchi(METABOLITE_5).isPresent());
assertTrue("Subgraph should contain first n1 precursor", precursorNetwork.getNodeOptionByInchi(METABOLITE_3).isPresent());
assertTrue("Subgraph should contain second n1 precursor", precursorNetwork.getNodeOptionByInchi(METABOLITE_4).isPresent());
assertTrue("Subgraph should contain second product of target's in-edge1", precursorNetwork.getNodeOptionByInchi(METABOLITE_7).isPresent());
assertTrue("Subgraph should contain second n2 precursor", precursorNetwork.getNodeOptionByInchi(METABOLITE_1).isPresent());
assertTrue("Subgraph should contain second n2 precursor", precursorNetwork.getNodeOptionByInchi(METABOLITE_2).isPresent());
assertEquals("Level of target is 0", 0, (int) report.getLevel(precursorNetwork.getNodeByInchi(METABOLITE_5)));
assertEquals("Level of 1st precursor is 1", 1, (int) report.getLevel(precursorNetwork.getNodeByInchi(METABOLITE_3)));
assertEquals("Level of 2nd precursor is 2", 2, (int) report.getLevel(precursorNetwork.getNodeByInchi(METABOLITE_1)));
assertEquals("Level of non-precursor is null", null, report.getLevel(precursorNetwork.getNodeByInchi(METABOLITE_7)));
}
/**
* Test precursor report of 2 levels to see how it handles cycles. The key test is that the target, METABOLITE_5,
* should have level 0, despite the fact that it is also its own second-level precursor.
*/
@Test
public void testPrecursorSubgraphCycles() {
// Arrange
MetabolismNetwork network = new MetabolismNetwork();
nodes.forEach(network::addNode);
network.addEdgeFromInchis(Arrays.asList(METABOLITE_4), Arrays.asList(METABOLITE_5));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_3), Arrays.asList(METABOLITE_4));
network.addEdgeFromInchis(Arrays.asList(METABOLITE_5), Arrays.asList(METABOLITE_4));
NetworkNode e = network.getNodeByInchi(METABOLITE_5);
// Act
PrecursorReport report = network.getPrecursorReport(e, 2);
// Assert
ImmutableNetwork precursorNetwork = report.getNetwork();
assertEquals("Level of target is 0", 0, (int) report.getLevel(precursorNetwork.getNodeByInchi(METABOLITE_5)));
assertEquals("Level of 1st precursor is 1", 1, (int) report.getLevel(precursorNetwork.getNodeByInchi(METABOLITE_4)));
assertEquals("Level of 2nd precursor is 2", 2, (int) report.getLevel(precursorNetwork.getNodeByInchi(METABOLITE_3)));
}
}