content: /*************************************************************************
  *                                                                        *
  *  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.sars;
  
  import chemaxon.formats.MolExporter;
  import chemaxon.formats.MolImporter;
  import chemaxon.reaction.ReactionException;
  import chemaxon.reaction.Reactor;
  import chemaxon.sss.SearchConstants;
  import chemaxon.sss.search.MolSearchOptions;
  import chemaxon.sss.search.SearchException;
  import chemaxon.struc.Molecule;
  import chemaxon.struc.MoleculeGraph;
  import chemaxon.struc.RxnMolecule;
  import com.act.biointerpretation.Utils.ReactionProjector;
  import org.junit.Test;
  
  import java.io.IOException;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertNotNull;
  import static org.junit.Assert.assertNull;
  
  public class ExpandedReactionSearcherTest {
  
    private static final String ANTHRANILIC_ACID = "InChI=1S/C7H7NO2/c8-6-4-2-1-3-5(6)7(9)10/h1-4H,8H2,(H,9,10)";
    private static final String AMINOBENZYL_ALCOHOL = "InChI=1S/C7H9NO/c8-7-4-2-1-3-6(7)5-9/h1-4,9H,5,8H2";
  
    private static final String ANTHRANILIC_PRODUCT = "InChI=1S/C8H9NO2/c1-11-8(10)6-4-2-3-5-7(6)9/h2-5H,9H2,1H3";
    private static final String AMINOBENZYL_PRODUCT = "InChI=1S/C7H7NO/c8-7-4-2-1-3-6(7)5-9/h1-5H,8H2";
  
    private static final String ANTHRANILIC_SUBSTRUCTURE = "InChI=1S/C7H6O2/c8-7(9)6-4-2-1-3-5-6/h1-5H,(H,8,9)";
    private static final String ANTHRANILIC_SUBSTRUCTURE_NO_OVERLAP = "InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H";
    private static final String AMINOBENZYL_SUBSTRUCTURE = "InChI=1S/C7H8O/c8-6-7-4-2-1-3-5-7/h1-5,8H,6H2";
  
    private static final String BASIC_SEED_RULE = "[H][#8:12]-[#6:1]>>[H]C([H])([H])[#8:12]-[#6:1]";
    private static final String SEED_RULE_WITH_BENZENE =
        "[H][#8:7]-[#6:6]([H])-[c:5]1[c:8][c:1][c:2][c:3][c:4]1>>[O:7]=[#6:6]-[c:5]1[c:8][c:1][c:2][c:3][c:4]1";
  
    private static final MolSearchOptions SEARCH_OPTIONS = new MolSearchOptions(SearchConstants.SUBSTRUCTURE);
  
    static {
      SEARCH_OPTIONS.setStereoModel(SearchConstants.STEREO_MODEL_LOCAL);
      SEARCH_OPTIONS.setStereoSearchType(SearchConstants.STEREO_EXACT);
    }
  
    private static final String INCHI_SETTINGS = "inchi:AuxNone";
  
    private static final ReactionProjector PROJECTOR = new ReactionProjector();
  
    @Test
    public void ExpandedReactionSearcherFiltersBadRoMatch() throws IOException, ReactionException, SearchException {
      // Arrange
      String ambiguousSubstrateInchi = "InChI=1S/C8H8O3/c9-5-6-1-3-7(4-2-6)8(10)11/h1-4,9H,5H2,(H,10,11)";
      String expectedFulllRuleReactant = "InChI=1S/C7H6O2/c8-7(9)6-4-2-1-3-5-6/h1-5H,(H,8,9)";
      String expectedFullRuleProduct = "InChI=1S/C8H8O2/c1-10-8(9)7-5-3-2-4-6-7/h2-6H,1H3";
  
      Molecule substrate = MolImporter.importMol(ANTHRANILIC_ACID);
      Molecule expectedProduct = MolImporter.importMol(ANTHRANILIC_PRODUCT);
      Molecule substructure = MolImporter.importMol(ANTHRANILIC_SUBSTRUCTURE);
      Molecule ambiguousSubstrate = MolImporter.importMol(ambiguousSubstrateInchi);
  
      Reactor seedReactor = new Reactor();
      seedReactor.setReactionString(BASIC_SEED_RULE);
  
      ExpandedReactionSearcher searcher = new ExpandedReactionSearcher(PROJECTOR);
      searcher.initSearch(seedReactor,
          substrate,
          expectedProduct,
          substructure);
  
      // Act
      Reactor fullReactor = searcher.getNextReactor();
  
      // Assert
      seedReactor.setReactants(new Molecule[] {ambiguousSubstrate});
      int counter = 0;
      while (seedReactor.react() != null) {
        counter++;
      }
      assertEquals("Seed reactor should produce 2 products.", 2, counter);
  
      fullReactor.setReactants(new Molecule[] {ambiguousSubstrate});
      counter = 0;
      while (fullReactor.react() != null) {
        counter++;
      }
      assertEquals("Full reactor should produce only 1 product.", 1, counter);
  
      RxnMolecule rxnMolecule = fullReactor.getReaction();
      String reactant = MolExporter.exportToFormat(rxnMolecule.getComponent(RxnMolecule.REACTANTS, 0), INCHI_SETTINGS);
      String product = MolExporter.exportToFormat(rxnMolecule.getComponent(RxnMolecule.PRODUCTS, 0), INCHI_SETTINGS);
  
      assertEquals("Reactant of reactor should be as expected.", reactant, expectedFulllRuleReactant);
      assertEquals("Product of reactor should be as expected.", product, expectedFullRuleProduct);
    }
  
    @Test
    public void ExpandedReactionSearcherDisjointRegionsReturnsNull() throws IOException, ReactionException, SearchException {
      // Arrange
      Molecule substrate = MolImporter.importMol(ANTHRANILIC_ACID);
      Molecule expectedProduct = MolImporter.importMol(ANTHRANILIC_PRODUCT);
      Molecule substructure = MolImporter.importMol(ANTHRANILIC_SUBSTRUCTURE_NO_OVERLAP);
  
      Reactor seedReactor = new Reactor();
      seedReactor.setReactionString(BASIC_SEED_RULE);
  
      ExpandedReactionSearcher searcher = new ExpandedReactionSearcher(PROJECTOR);
      searcher.initSearch(seedReactor,
          substrate,
          expectedProduct,
          substructure);
  
      // Act
      Reactor fullReactor = searcher.getNextReactor();
  
      // Assert
      assertNull("Reactor should be null.", fullReactor);
    }
  
  
    @Test
    public void ExpandedReactionSearcherPrimaryOnPrimaryWorks() throws IOException, ReactionException, SearchException {
      // Arrange
      Molecule primarySubstrate = MolImporter.importMol(AMINOBENZYL_ALCOHOL);
      Molecule expectedProduct = MolImporter.importMol(AMINOBENZYL_PRODUCT);
      Molecule substructure = MolImporter.importMol(AMINOBENZYL_SUBSTRUCTURE);
  
      Reactor seedReactor = new Reactor();
      seedReactor.setReactionString(SEED_RULE_WITH_BENZENE);
  
      ExpandedReactionSearcher searcher = new ExpandedReactionSearcher(PROJECTOR);
      searcher.initSearch(seedReactor,
          primarySubstrate,
          expectedProduct,
          substructure);
  
      // Act
      Reactor fullReactor = searcher.getNextReactor();
  
      // Assert
      primarySubstrate = MolImporter.importMol(AMINOBENZYL_ALCOHOL);
      fullReactor.setReactants(new Molecule[] {primarySubstrate});
      int counter = 0;
      Molecule[] products;
      Molecule predictedProduct = null;
      while ((products = fullReactor.react()) != null) {
        predictedProduct = products[0];
        assertEquals("Reactor should produce 1 product.", 1, products.length);
        assertEquals("Product should be as expected.", AMINOBENZYL_PRODUCT, MolExporter.exportToFormat(predictedProduct, INCHI_SETTINGS));
        counter++;
      }
      assertEquals("Full reactor should produce 1 product set.", 1, counter);
    }
  
  
    @Test
    public void ExpandedReactionSearcherPrimaryOnSecondaryNoProduct() throws IOException, ReactionException, SearchException {
      // Arrange
      Molecule secondarySubstrate = MolImporter.importMol(ANTHRANILIC_ACID);
      Molecule primarySubstrate = MolImporter.importMol(AMINOBENZYL_ALCOHOL);
      Molecule expectedProduct = MolImporter.importMol(AMINOBENZYL_PRODUCT);
      Molecule substructure = MolImporter.importMol(AMINOBENZYL_SUBSTRUCTURE);
  
      Reactor seedReactor = new Reactor();
      seedReactor.setReactionString(SEED_RULE_WITH_BENZENE);
  
      ExpandedReactionSearcher searcher = new ExpandedReactionSearcher(PROJECTOR);
      searcher.initSearch(seedReactor,
          primarySubstrate,
          expectedProduct,
          substructure);
  
      // Act
      Reactor fullReactor = searcher.getNextReactor();
  
      // Assert
      fullReactor.setReactants(new Molecule[] {secondarySubstrate});
      int counter = 0;
      while (fullReactor.react() != null) {
        counter++;
      }
      assertEquals("Full reactor should produce no product set.", 0, counter);
    }
  
    @Test
    public void ExpandedReactionSearcherStereoMatching() throws IOException, ReactionException, SearchException {
      // Arrange
      String substrateInchi = "InChI=1S/C29H48O/c1-7-21(19(2)3)9-8-20(4)25-12-13-26-24-11-10-22-18-23(30)14-16-28(22,5)" +
          "27(24)15-17-29(25,26)6/h8-10,19-21,23-27,30H,7,11-18H2,1-6H3/b9-8+/t20-,21-,23+,24+,25-,26+,27+,28+,29-/m1/s1";
      String productInchi = "InChI=1S/C29H46O/c1-7-21(19(2)3)9-8-20(4)25-12-13-26-24-11-10-22-18-23(30)14-16-28(22,5)27" +
          "(24)15-17-29(25,26)6/h8-10,19-21,24-27H,7,11-18H2,1-6H3/t20-,21-,24+,25-,26+,27+,28+,29-/m1/s1";
      String substructureInchi = "InChI=1S/C29H50O/c1-7-21(19(2)3)9-8-20(4)25-12-13-26-24-11-10-22-18-23(30)14-16-28(22" +
          ",5)27(24)15-17-29(25,26)6/h10,19-21,23-27,30H,7-9,11-18H2,1-6H3/t20-,21-,23+,24+,25-,26+,27+,28+,29-/m1/s1";
      String roString = "[H:1][#8:4]-[#6:3]([H:5])-[#6:2]>>[#6:2]-[#6:3]=[O:4]";
  
      Molecule substrate = MolImporter.importMol(substrateInchi);
      Molecule expectedProduct = MolImporter.importMol(productInchi);
      Molecule substructure = MolImporter.importMol(substructureInchi);
  
      substructure.aromatize(MoleculeGraph.AROM_LOOSE);
      substrate.aromatize(MoleculeGraph.AROM_LOOSE);
  
      Reactor seedReactor = new Reactor();
      seedReactor.setReactionString(roString);
  
      ExpandedReactionSearcher searcher = new ExpandedReactionSearcher(PROJECTOR);
      searcher.initSearch(seedReactor,
          substrate,
          expectedProduct,
          substructure);
  
      // Act
      Reactor fullReactor = searcher.getNextReactor();
      assertNotNull("There should be at least one expanded reaction result.", fullReactor);
    }
  }
  