/************************************************************************* * * * 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.reactionmerging; import act.server.NoSQLAPI; import act.shared.Reaction; import act.shared.Seq; import com.act.biointerpretation.test.util.TestUtils; import com.act.biointerpretation.test.util.MockedNoSQLAPI; import org.biopax.paxtools.model.level3.ConversionDirectionType; import org.biopax.paxtools.model.level3.StepDirection; import org.json.JSONArray; import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; // TODO: consider using https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo instead of manual mocking? public class ReactionMergerTest { TestUtils utilsObject; @Before public void setUp() throws Exception { // In case we ever use Mockito annotations, don't forget to initialize them. MockitoAnnotations.initMocks(ReactionMergerTest.class); utilsObject = new TestUtils(); } @After public void tearDown() throws Exception { } public static class ReactionHashingTestCase { String description; Integer expectedHashBucketCount; List<Reaction> reactions = new ArrayList<>(); public ReactionHashingTestCase(String description, Integer expectedHashBucketCount) { this.description = description; this.expectedHashBucketCount = expectedHashBucketCount; } public ReactionHashingTestCase addRxn(Reaction rxn) { this.reactions.add(rxn); return this; } public Iterator<Reaction> rxnIterator() { return this.reactions.iterator(); } public String getDescription() { return this.description; } public Integer getExpectedHashBucketCount() { return this.expectedHashBucketCount; } public ReactionHashingTestCase setSubstrateCoefficient(int reactionIdx, long chemId, int count) { this.reactions.get(reactionIdx).setSubstrateCoefficient(chemId, count); return this; } public ReactionHashingTestCase setProductCoefficient(int reactionIdx, long chemId, int count) { this.reactions.get(reactionIdx).setProductCoefficient(chemId, count); return this; } } @Test public void testHashing() throws Exception { ReactionHashingTestCase[] testCases = new ReactionHashingTestCase[] { new ReactionHashingTestCase("Rxns with same sub/prod and no cofactors/enzymes should be hashed to one bucket", 1). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)), new ReactionHashingTestCase("Reactions with subset substrates should be hashed to two buckets", 2). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{1L, 2L, 5L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)), new ReactionHashingTestCase("Reactions with subset substreates should be hashed to two buckets", 2). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{1L, 2L, 5L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)), new ReactionHashingTestCase("Rxns with different subs. and no cofactors/enzymes should be hashed as two buckets", 2). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{5L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)), new ReactionHashingTestCase("Rxn with same sub/prod but different EC numbers should be hashed to two buckets", 2). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.2", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)), new ReactionHashingTestCase("Rxns with same sub/prod and different sub. cofactors should be hashed to two buckets", 2). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[]{1L}, new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)), new ReactionHashingTestCase("Rxns with same sub/prod and different prod. cofactors should be hashed to two buckets", 2). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[]{1L}, new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)), new ReactionHashingTestCase("Rxns with same sub/prod and different coenzymes should be hashed to two buckets", 2). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[]{1L}, "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[]{2L}, "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)), new ReactionHashingTestCase("Rxns with same sub/prod and different coefficients should be hashed to two buckets", 2). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)).setSubstrateCoefficient(0, 1, 1). addRxn(new Reaction( 2L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)).setSubstrateCoefficient(1, 1, 2), new ReactionHashingTestCase("Rxns with same sub/prod and different conv. directions should be hashed to two buckets", 2). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.RIGHT_TO_LEFT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)), new ReactionHashingTestCase("Rxns with same sub/prod and different step directions should be hashed to two buckets", 2). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.RIGHT_TO_LEFT, "Reaction 2", Reaction.RxnDetailType.CONCRETE)), new ReactionHashingTestCase("Rxns with same sub/prod and different rxn detail types should be hashed to one bucket", 1). addRxn(new Reaction( 1L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 1", Reaction.RxnDetailType.CONCRETE)). addRxn(new Reaction( 2L, new Long[]{1L, 2L}, new Long[]{3L, 4L}, new Long[0], new Long[0], new Long[0], "1.1.1.1", ConversionDirectionType.LEFT_TO_RIGHT, StepDirection.LEFT_TO_RIGHT, "Reaction 2", Reaction.RxnDetailType.ABSTRACT)) }; for (ReactionHashingTestCase testCase : testCases) { Map<ReactionMerger.SubstratesProducts, PriorityQueue<Long>> results = ReactionMerger.hashReactions(testCase.rxnIterator()); assertEquals(testCase.getDescription(), testCase.getExpectedHashBucketCount(), Integer.valueOf(results.size())); } } /** * BRENDA and Metacyc structure their protein objects differently. BRENDA specifies an "organism" field with a * single ID value, while Metacyc specifies a list of "organisms" ids. Handling these incorrectly can cause * exceptions to be thrown when running the reaction merger. * @throws Exception */ @Test public void testOrganismMigrationForDifferentProteinTypes() throws Exception { List<Reaction> testReactions = new ArrayList<>(); // BRENDA style testReactions.add(utilsObject.makeTestReaction(new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L}, false)); // Metacyc style testReactions.add(utilsObject.makeTestReaction(new Long[]{4L, 5L, 6L}, new Long[]{7L, 8L, 9L}, true)); MockedNoSQLAPI mockAPI = new MockedNoSQLAPI(); mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, new HashMap<>()); NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI(); ReactionMerger merger = new ReactionMerger(mockNoSQLAPI); merger.init(); merger.run(); assertEquals("Reactions should not have been merged", 2, mockAPI.getWrittenReactions().size()); Reaction writtenReaction1 = mockAPI.getWrittenReactions().get(0); assertEquals("Reaction 1 has one protein", 1, writtenReaction1.getProteinData().size()); JSONObject reaction1Protein = writtenReaction1.getProteinData().iterator().next(); assertTrue("Reaction 1's protein has an organism field", reaction1Protein.has("organism")); assertFalse("Reaction 1's protein does not have an organisms field", reaction1Protein.has("organisms")); assertTrue("Reaction 2's protein's organisms is a Long", reaction1Protein.get("organism") instanceof Long); assertEquals("Reaction 1's protein has the correct single organism id", 1L, reaction1Protein.getLong("organism")); Reaction writtenReaction2 = mockAPI.getWrittenReactions().get(1); assertEquals("Reaction 2 has one protein", 1, writtenReaction2.getProteinData().size()); JSONObject reaction2Protein = writtenReaction2.getProteinData().iterator().next(); assertFalse("Reaction 2's protein does not have an organism field", reaction2Protein.has("organism")); assertTrue("Reaction 2's protein has an organisms field", reaction2Protein.has("organisms")); assertTrue("Reaction 2's protein's organisms is a JSONArray", reaction2Protein.get("organisms") instanceof JSONArray); JSONArray reaction2ProteinOrganisms = reaction2Protein.getJSONArray("organisms"); assertEquals("Reaction 2's protein's organisms has one entry", 1L, reaction2ProteinOrganisms.length()); assertTrue("Reaction 2's protein's organisms' first entry is a long", reaction2ProteinOrganisms.get(0) instanceof Long); assertEquals("Reaction 2's protein's organisms' first entry is unchanged from the original", 1L, reaction2ProteinOrganisms.getLong(0)); } /** * This large and sprawling test verifies that reactions are merged as expected based on their substrates and * products. It's volume is primarily due to the complicated context the ReactionMerger requires, both in terms * of the data it consumes (reactions, chemicals, sequences) and the fact that it relies on the NoSQLAPI class to * iterate over reactions and store the merged results. * @throws Exception */ @Test public void testMergingEndToEnd() throws Exception { List<Reaction> testReactions = new ArrayList<>(); // Group 1 testReactions.add(utilsObject.makeTestReaction(new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L})); testReactions.add(utilsObject.makeTestReaction(new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L})); testReactions.add(utilsObject.makeTestReaction(new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L})); // Group 2 testReactions.add(utilsObject.makeTestReaction(new Long[]{7L, 2L, 3L}, new Long[]{8L, 5L, 6L})); testReactions.add(utilsObject.makeTestReaction(new Long[]{7L, 2L, 3L}, new Long[]{8L, 5L, 6L})); // Group 3 testReactions.add(utilsObject.makeTestReaction(new Long[]{9L, 2L, 3L}, new Long[]{8L, 5L, 6L})); MockedNoSQLAPI mockAPI = new MockedNoSQLAPI(); mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, new HashMap<>()); NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI(); /* **************************************** * Create a reaction merger and run it on the mocked objects. */ ReactionMerger merger = new ReactionMerger(mockNoSQLAPI); merger.init(); merger.run(); // Test the results of the merge. assertEquals("Input reactions should be merged into three output reactions", 3, mockAPI.getWrittenReactions().size()); // Check merged structure of first three reactions. // TODO: we might be able to do this faster by creating an expected reaction and doing a deep comparison. /* Beware: sloppy, repetative test code. I'll let this sort of thing slide in tests that aren't likely to be * reused elsewhere, but it's definitely not pretty. */ Reaction r1 = mockAPI.getWrittenReactions().get(0); assertNotNull("Merged reaction 1 should not be null", r1); assertEquals("Merged reaction 1 has expected substrates", mockAPI.readDBChemicalIdsToInchis(new Long[] {1L, 2L, 3L}), mockAPI.writeDBChemicalIdsToInchis(r1.getSubstrates()) ); assertEquals("Merged reaction 1 has expected products", mockAPI.readDBChemicalIdsToInchis(new Long[] {4L, 5L, 6L}), mockAPI.writeDBChemicalIdsToInchis(r1.getProducts()) ); assertEquals("Merged reaction 1 should have 3 protein objects", 3, r1.getProteinData().size()); Set<String> r1Sequences = new HashSet<>(3); for (JSONObject o : r1.getProteinData()) { Long id = o.getLong("id"); JSONArray sequences = o.getJSONArray("sequences"); assertNotNull("Sequences for protein %d should not be null", id); assertEquals(String.format("Protein %d should have one sequence", id), 1, sequences.length()); Seq seq = mockAPI.getWrittenSequences().get(sequences.getLong(0)); assertNotNull("Referenced seq object should not be null", seq); assertEquals("New sequence object's sequence string should match original", utilsObject.SEQ_MAP.get(id * 10L).getSequence(), seq.getSequence()); r1Sequences.add(seq.getSequence()); assertEquals("New Seq object should reference the migrated reaction by id", Long.valueOf(r1.getUUID()), seq.getReactionsCatalyzed().iterator().next()); } assertEquals("All expected sequences are accounted for", new HashSet<>(Arrays.asList("SEQA", "SEQB", "SEQC")), r1Sequences ); Reaction r2 = mockAPI.getWrittenReactions().get(1); assertNotNull("Merged reaction 2 should not be null", r2); assertEquals("Merged reaction 2 has expected substrates", mockAPI.readDBChemicalIdsToInchis(new Long[] {7L, 2L, 3L}), mockAPI.writeDBChemicalIdsToInchis(r2.getSubstrates()) ); assertEquals("Merged reaction 2 has expected products", mockAPI.readDBChemicalIdsToInchis(new Long[] {8L, 5L, 6L}), mockAPI.writeDBChemicalIdsToInchis(r2.getProducts()) ); assertEquals("Merged reaction 2 should have 2 protein objects", 2, r2.getProteinData().size()); Set<String> r2Sequences = new HashSet<>(2); for (JSONObject o : r2.getProteinData()) { Long id = o.getLong("id"); JSONArray sequences = o.getJSONArray("sequences"); assertNotNull("Sequences for protein %d should not be null", id); assertEquals(String.format("Protein %d should have one sequence", id), 1, sequences.length()); Seq seq = mockAPI.getWrittenSequences().get(sequences.getLong(0)); assertNotNull("Referenced seq object should not be null", seq); assertEquals("New sequence object's sequence string should match original", utilsObject.SEQ_MAP.get(id * 10L).getSequence(), seq.getSequence()); r2Sequences.add(seq.getSequence()); assertEquals("New Seq object should reference the migrated reaction by id", Long.valueOf(r2.getUUID()), seq.getReactionsCatalyzed().iterator().next()); } assertEquals("All expected sequences are accounted for", new HashSet<>(Arrays.asList("SEQD", "SEQE")), r2Sequences ); Reaction r3 = mockAPI.getWrittenReactions().get(2); assertNotNull("Merged reaction 3 should not be null", r3); assertEquals("Merged reaction 3 has expected substrates", mockAPI.readDBChemicalIdsToInchis(new Long[] {9L, 2L, 3L}), mockAPI.writeDBChemicalIdsToInchis(r3.getSubstrates()) ); assertEquals("Merged reaction 3 has expected products", mockAPI.readDBChemicalIdsToInchis(new Long[] {8L, 5L, 6L}), mockAPI.writeDBChemicalIdsToInchis(r3.getProducts()) ); assertEquals("Merged reaction 3 should have 1 protein objects", 1, r3.getProteinData().size()); Set<String> r3Sequences = new HashSet<>(1); for (JSONObject o : r3.getProteinData()) { Long id = o.getLong("id"); JSONArray sequences = o.getJSONArray("sequences"); assertNotNull("Sequences for protein %d should not be null", id); assertEquals(String.format("Protein %d should have one sequence", id), 1, sequences.length()); Seq seq = mockAPI.getWrittenSequences().get(sequences.getLong(0)); assertNotNull("Referenced seq object should not be null", seq); assertEquals("New sequence object's sequence string should match original", utilsObject.SEQ_MAP.get(id * 10L).getSequence(), seq.getSequence()); r3Sequences.add(seq.getSequence()); assertEquals("New Seq object should reference the migrated reaction by id", Long.valueOf(r3.getUUID()), seq.getReactionsCatalyzed().iterator().next()); } assertEquals("All expected sequences are accounted for", new HashSet<>(Arrays.asList("SEQF")), r3Sequences ); } @Test public void testCoefficientsAreCorrectlyTransferred() throws Exception { List<Reaction> testReactions = new ArrayList<>(); Long[] substrates = {1L, 2L, 3L}; Long[] products = {4L, 5L, 6L}; Integer[] substrateCoefficients = {1, 2, 3}; Integer[] productCoefficients = {2, 3, 1}; // Group 1 testReactions.add(utilsObject.makeTestReaction(substrates, products, substrateCoefficients, productCoefficients, false)); testReactions.add(utilsObject.makeTestReaction(substrates, products, substrateCoefficients, productCoefficients, false)); for (int i = 0; i < substrates.length; i++) { assertEquals(String.format("Input reaction substrate %d has correct coefficient set", substrates[i]), substrateCoefficients[i], testReactions.get(0).getSubstrateCoefficient(substrates[i])); } for (int i = 0; i < products.length; i++) { assertEquals(String.format("Input reaction product %d has correct coefficient set", products[i]), productCoefficients[i], testReactions.get(0).getProductCoefficient(products[i])); } MockedNoSQLAPI mockAPI = new MockedNoSQLAPI(); mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, new HashMap<>()); NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI(); /* **************************************** */ ReactionMerger merger = new ReactionMerger(mockNoSQLAPI); merger.init(); merger.run(); assertEquals("Input reactions should be merged into one output reactions", 1, mockAPI.getWrittenReactions().size()); Reaction rxn = mockAPI.getWrittenReactions().get(0); /* We don't necessarily know what ids the products/substrates will get (we can guess, but it's just a guess), but we * do know for certain that they'll be inserted in the same order they appear in the original reaction. By sorting * the substrate/product ids, we can iterate over them in the same order they would have appeared originally, which * allows us to compare each new reaction's coefficient against the similarly positioned coefficient from the * old reaction. */ // Copy the substrates/products into new arrays before we modify them. List<Long> writtenSubstrates = new ArrayList<>(Arrays.asList(rxn.getSubstrates())); List<Long> writtenProducts = new ArrayList<>(Arrays.asList(rxn.getProducts())); Collections.sort(writtenSubstrates); Collections.sort(writtenProducts); for (int i = 0; i < writtenSubstrates.size(); i++) { Integer newCoefficient = rxn.getSubstrateCoefficient(writtenSubstrates.get(i)); assertNotNull(String.format("Output reaction substrate coefficient for chemical %d is not null", i), newCoefficient); assertEquals(String.format("Coefficient for output chemical %d matches original", i), substrateCoefficients[i], newCoefficient); } for (int i = 0; i < writtenProducts.size(); i++) { Integer newCoefficient = rxn.getProductCoefficient(writtenProducts.get(i)); assertNotNull(String.format("Output reaction product coefficient for chemical %d is not null", i), newCoefficient); assertEquals(String.format("Coefficient for output chemical %d matches original", i), productCoefficients[i], newCoefficient); } } }