Skip to content

Commit 95f5bc0

Browse files
committed
Add clean public API: RDT.map(smiles) → ReactionResult
New api/ package with toolkit-agnostic public facade: - RDT.map("CC(=O)O.OCC>>CC(=O)OCC.O") → ReactionResult - ReactionResult: immutable, plain Java (no CDK in public types) - getMappedSmiles(), getFormedCleavedBonds(), getTotalBondChanges() - isMapped(), getOrderChangedBonds(), getStereoChangedBonds() 3 new API tests added (138 total, all pass). Users no longer need to know CDK to use RDT: ReactionResult r = RDT.map("CC>>CC"); System.out.println(r.getTotalBondChanges()); Co-Authored-By: Syed Asad Rahman <asad.rahman@bioinceptionlabs.com>
1 parent 9366268 commit 95f5bc0

3 files changed

Lines changed: 261 additions & 0 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright (C) 2007-2026 Syed Asad Rahman <asad.rahman@bioinceptionlabs.com>.
3+
*
4+
* This library is free software; you can redistribute it and/or
5+
* modify it under the terms of the GNU Lesser General Public
6+
* License as published by the Free Software Foundation; either
7+
* version 2.1 of the License, or (at your option) any later version.
8+
*
9+
* This library is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public
15+
* License along with this library; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17+
* MA 02110-1301 USA
18+
*/
19+
package com.bioinceptionlabs.reactionblast.api;
20+
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
24+
import org.openscience.cdk.interfaces.IReaction;
25+
import org.openscience.cdk.silent.SilentChemObjectBuilder;
26+
import org.openscience.cdk.smiles.SmilesParser;
27+
import com.bioinceptionlabs.reactionblast.fingerprints.IPatternFingerprinter;
28+
import com.bioinceptionlabs.reactionblast.mechanism.BondChangeCalculator;
29+
import com.bioinceptionlabs.reactionblast.mechanism.MappingSolution;
30+
import com.bioinceptionlabs.reactionblast.mechanism.ReactionMechanismTool;
31+
import com.bioinceptionlabs.reactionblast.tools.StandardizeReaction;
32+
33+
/**
34+
* Simple, clean public API for Reaction Decoder Tool.
35+
*
36+
* <pre>
37+
* // Map a reaction from SMILES
38+
* ReactionResult result = RDT.map("CC(=O)O.OCC>>CC(=O)OCC.O");
39+
*
40+
* // Check results
41+
* System.out.println(result.getBondChanges()); // [C-O, O-H, C=O, ...]
42+
* System.out.println(result.getMappedSmiles()); // mapped SMILES
43+
* System.out.println(result.getBondChangeCount()); // number of bond changes
44+
* </pre>
45+
*
46+
* @author Syed Asad Rahman <asad.rahman@bioinceptionlabs.com>
47+
*/
48+
public final class RDT {
49+
50+
private RDT() {}
51+
52+
/**
53+
* Map a reaction from SMILES and extract bond changes.
54+
*
55+
* @param reactionSmiles reaction SMILES (reactants>>products)
56+
* @return ReactionResult with mapping, bond changes, and fingerprints
57+
* @throws IllegalArgumentException if SMILES is invalid
58+
*/
59+
public static ReactionResult map(String reactionSmiles) {
60+
return map(reactionSmiles, true, true);
61+
}
62+
63+
/**
64+
* Map a reaction with control over stereo perception and ring handling.
65+
*
66+
* @param reactionSmiles reaction SMILES (reactants>>products)
67+
* @param generate2D perceive 2D stereo centers
68+
* @param complexMapping handle ring system mapping
69+
* @return ReactionResult with mapping, bond changes, and fingerprints
70+
* @throws IllegalArgumentException if SMILES is invalid
71+
*/
72+
public static ReactionResult map(String reactionSmiles, boolean generate2D, boolean complexMapping) {
73+
if (reactionSmiles == null || !reactionSmiles.contains(">>")) {
74+
throw new IllegalArgumentException("Invalid reaction SMILES: must contain '>>'");
75+
}
76+
try {
77+
SmilesParser sp = new SmilesParser(SilentChemObjectBuilder.getInstance());
78+
IReaction reaction = sp.parseReactionSmiles(reactionSmiles);
79+
reaction.setID("RDT_" + Integer.toHexString(reactionSmiles.hashCode()));
80+
81+
ReactionMechanismTool rmt = new ReactionMechanismTool(
82+
reaction, true, generate2D, false, complexMapping, true, new StandardizeReaction());
83+
84+
return extractResult(rmt, reactionSmiles);
85+
} catch (Exception e) {
86+
throw new RuntimeException("Mapping failed for: " + reactionSmiles, e);
87+
}
88+
}
89+
90+
private static ReactionResult extractResult(ReactionMechanismTool rmt, String inputSmiles) {
91+
MappingSolution solution = rmt.getSelectedSolution();
92+
if (solution == null) {
93+
return new ReactionResult(inputSmiles, null, 0, 0, 0,
94+
new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
95+
}
96+
97+
BondChangeCalculator bcc = solution.getBondChangeCalculator();
98+
List<String> formedCleaved;
99+
List<String> orderChanges;
100+
List<String> stereoChanges;
101+
try {
102+
formedCleaved = extractFeatures(bcc.getFormedCleavedWFingerprint());
103+
orderChanges = extractFeatures(bcc.getOrderChangesWFingerprint());
104+
stereoChanges = extractFeatures(bcc.getStereoChangesWFingerprint());
105+
} catch (Exception e) {
106+
formedCleaved = new ArrayList<>();
107+
orderChanges = new ArrayList<>();
108+
stereoChanges = new ArrayList<>();
109+
}
110+
111+
String mappedSmiles = null;
112+
try {
113+
org.openscience.cdk.smiles.SmilesGenerator sg = new org.openscience.cdk.smiles.SmilesGenerator(
114+
org.openscience.cdk.smiles.SmiFlavor.Stereo | org.openscience.cdk.smiles.SmiFlavor.AtomAtomMap);
115+
mappedSmiles = sg.create(bcc.getReaction());
116+
} catch (Exception ignored) {}
117+
118+
return new ReactionResult(
119+
inputSmiles,
120+
mappedSmiles,
121+
formedCleaved.size(),
122+
orderChanges.size(),
123+
stereoChanges.size(),
124+
formedCleaved,
125+
orderChanges,
126+
stereoChanges);
127+
}
128+
129+
private static List<String> extractFeatures(IPatternFingerprinter fp) {
130+
List<String> features = new ArrayList<>();
131+
if (fp != null) {
132+
for (var feature : fp.getFeatures()) {
133+
features.add(feature.getPattern() + ":" + (int) feature.getWeight());
134+
}
135+
}
136+
return features;
137+
}
138+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright (C) 2007-2026 Syed Asad Rahman <asad.rahman@bioinceptionlabs.com>.
3+
*
4+
* This library is free software; you can redistribute it and/or
5+
* modify it under the terms of the GNU Lesser General Public
6+
* License as published by the Free Software Foundation; either
7+
* version 2.1 of the License, or (at your option) any later version.
8+
*
9+
* This library is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public
15+
* License along with this library; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17+
* MA 02110-1301 USA
18+
*/
19+
package com.bioinceptionlabs.reactionblast.api;
20+
21+
import java.util.Collections;
22+
import java.util.List;
23+
24+
/**
25+
* Immutable result of a reaction mapping. Contains bond changes,
26+
* mapped SMILES, and fingerprint features as plain Java types
27+
* (no CDK/toolkit dependency).
28+
*
29+
* @author Syed Asad Rahman <asad.rahman@bioinceptionlabs.com>
30+
*/
31+
public final class ReactionResult {
32+
33+
private final String inputSmiles;
34+
private final String mappedSmiles;
35+
private final int formedCleavedCount;
36+
private final int orderChangeCount;
37+
private final int stereoChangeCount;
38+
private final List<String> formedCleavedBonds;
39+
private final List<String> orderChangedBonds;
40+
private final List<String> stereoChangedBonds;
41+
42+
ReactionResult(String inputSmiles, String mappedSmiles,
43+
int formedCleavedCount, int orderChangeCount, int stereoChangeCount,
44+
List<String> formedCleavedBonds, List<String> orderChangedBonds,
45+
List<String> stereoChangedBonds) {
46+
this.inputSmiles = inputSmiles;
47+
this.mappedSmiles = mappedSmiles;
48+
this.formedCleavedCount = formedCleavedCount;
49+
this.orderChangeCount = orderChangeCount;
50+
this.stereoChangeCount = stereoChangeCount;
51+
this.formedCleavedBonds = Collections.unmodifiableList(formedCleavedBonds);
52+
this.orderChangedBonds = Collections.unmodifiableList(orderChangedBonds);
53+
this.stereoChangedBonds = Collections.unmodifiableList(stereoChangedBonds);
54+
}
55+
56+
/** Original input SMILES */
57+
public String getInputSmiles() { return inputSmiles; }
58+
59+
/** Mapped reaction SMILES with atom-atom mapping numbers */
60+
public String getMappedSmiles() { return mappedSmiles; }
61+
62+
/** Number of bonds formed or cleaved */
63+
public int getFormedCleavedCount() { return formedCleavedCount; }
64+
65+
/** Number of bond order changes */
66+
public int getOrderChangeCount() { return orderChangeCount; }
67+
68+
/** Number of stereochemistry changes */
69+
public int getStereoChangeCount() { return stereoChangeCount; }
70+
71+
/** Total bond changes (formed/cleaved + order changes) */
72+
public int getTotalBondChanges() { return formedCleavedCount + orderChangeCount; }
73+
74+
/** Whether mapping was successful */
75+
public boolean isMapped() { return mappedSmiles != null; }
76+
77+
/** Bond formation/cleavage patterns, e.g. ["C-O:1", "O-H:-1"] */
78+
public List<String> getFormedCleavedBonds() { return formedCleavedBonds; }
79+
80+
/** Bond order change patterns, e.g. ["C=C:1"] */
81+
public List<String> getOrderChangedBonds() { return orderChangedBonds; }
82+
83+
/** Stereo change patterns */
84+
public List<String> getStereoChangedBonds() { return stereoChangedBonds; }
85+
86+
@Override
87+
public String toString() {
88+
return "ReactionResult{" +
89+
"mapped=" + isMapped() +
90+
", bondChanges=" + getTotalBondChanges() +
91+
", formed/cleaved=" + formedCleavedBonds +
92+
", orderChanges=" + orderChangedBonds +
93+
", stereoChanges=" + stereoChangedBonds +
94+
'}';
95+
}
96+
}

src/test/java/com/bioinceptionlabs/aamtool/SMARTS2AAMTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,33 @@ public void IdentityWithStereo() throws Exception {
940940
* @throws AssertionError
941941
* @throws Exception
942942
*/
943+
// ---- Tests for the new RDT public API ----
944+
945+
@Test
946+
public void testRDTApiEsterification() {
947+
com.bioinceptionlabs.reactionblast.api.ReactionResult result =
948+
com.bioinceptionlabs.reactionblast.api.RDT.map("CC(=O)O.OCC>>CC(=O)OCC.O");
949+
assertTrue("RDT.map should return mapped result", result.isMapped());
950+
assertTrue("Esterification should have bond changes", result.getTotalBondChanges() > 0);
951+
assertTrue("Should have formed/cleaved bonds", result.getFormedCleavedCount() > 0);
952+
}
953+
954+
@Test
955+
public void testRDTApiIdentity() {
956+
com.bioinceptionlabs.reactionblast.api.ReactionResult result =
957+
com.bioinceptionlabs.reactionblast.api.RDT.map("CC>>CC");
958+
assertTrue("RDT.map should return mapped result", result.isMapped());
959+
assertEquals("Identity should have 0 bond changes", 0, result.getTotalBondChanges());
960+
}
961+
962+
@Test
963+
public void testRDTApiDielsAlder() {
964+
com.bioinceptionlabs.reactionblast.api.ReactionResult result =
965+
com.bioinceptionlabs.reactionblast.api.RDT.map("C=CC=C.C=C>>C1CC=CCC1");
966+
assertTrue("RDT.map should return mapped result", result.isMapped());
967+
assertTrue("Diels-Alder should have bond changes", result.getFormedCleavedCount() > 0);
968+
}
969+
943970
public ReactionMechanismTool performAtomAtomMapping(IReaction cdkReaction, String reactionName) throws InvalidSmilesException, AssertionError, Exception {
944971
cdkReaction.setID(reactionName);
945972
/*

0 commit comments

Comments
 (0)