@@ -42,6 +42,7 @@ public final class ReactionResult {
4242 private final List <String > stereoChangedBonds ;
4343 private final List <String > reactionCentreFingerprint ;
4444 private final String algorithmUsed ;
45+ private final String reactionSignature ;
4546
4647 ReactionResult (String inputSmiles , String mappedSmiles ,
4748 int formedCleavedCount , int orderChangeCount , int stereoChangeCount ,
@@ -58,6 +59,7 @@ public final class ReactionResult {
5859 this .stereoChangedBonds = Collections .unmodifiableList (stereoChangedBonds );
5960 this .reactionCentreFingerprint = Collections .unmodifiableList (reactionCentreFingerprint );
6061 this .algorithmUsed = algorithmUsed ;
62+ this .reactionSignature = buildReactionSignature ();
6163 }
6264
6365 /** Original input SMILES */
@@ -96,6 +98,49 @@ public final class ReactionResult {
9698 /** Algorithm that produced this mapping (RINGS, MIN, MAX, MIXTURE) */
9799 public String getAlgorithm () { return algorithmUsed ; }
98100
101+ /**
102+ * Canonical, hierarchical reaction signature (R-string).
103+ * Deterministic, invariant, and searchable. Encodes the complete
104+ * electron shift pattern as a canonical string.
105+ *
106+ * Format: FC[patterns]|OC[patterns]|SC[patterns]|RC[patterns]
107+ * Where FC=formed/cleaved, OC=order changes, SC=stereo, RC=reaction centre.
108+ * Patterns are sorted alphabetically within each level.
109+ *
110+ * Two reactions with identical signatures have identical bond changes
111+ * (same R-matrix in the Dugundji-Ugi model / Leber canonicalization).
112+ *
113+ * @return canonical reaction signature string, or empty string if unmapped
114+ */
115+ public String getReactionSignature () { return reactionSignature ; }
116+
117+ /**
118+ * Build the canonical reaction signature from sorted fingerprint patterns.
119+ * Strips weights, sorts alphabetically, joins with semicolons.
120+ */
121+ private String buildReactionSignature () {
122+ if (!isMapped ()) return "" ;
123+ StringBuilder sb = new StringBuilder ();
124+ sb .append ("FC[" ).append (canonicalPatterns (formedCleavedBonds )).append ("]" );
125+ sb .append ("|OC[" ).append (canonicalPatterns (orderChangedBonds )).append ("]" );
126+ sb .append ("|SC[" ).append (canonicalPatterns (stereoChangedBonds )).append ("]" );
127+ sb .append ("|RC[" ).append (canonicalPatterns (reactionCentreFingerprint )).append ("]" );
128+ return sb .toString ();
129+ }
130+
131+ /**
132+ * Extract pattern names (strip weights), sort, join with semicolons.
133+ */
134+ private static String canonicalPatterns (List <String > features ) {
135+ List <String > patterns = new java .util .ArrayList <>();
136+ for (String f : features ) {
137+ int colon = f .lastIndexOf (':' );
138+ patterns .add (colon > 0 ? f .substring (0 , colon ) : f );
139+ }
140+ Collections .sort (patterns );
141+ return String .join (";" , patterns );
142+ }
143+
99144 /**
100145 * Compute Tanimoto similarity between this reaction and another
101146 * based on bond change fingerprints. Returns 0.0 (no overlap) to 1.0 (identical).
@@ -152,6 +197,7 @@ public String toString() {
152197 ", orderChanges=" + orderChangedBonds +
153198 ", stereoChanges=" + stereoChangedBonds +
154199 ", reactionCentre=" + reactionCentreFingerprint +
200+ ", signature=" + reactionSignature +
155201 '}' ;
156202 }
157203}
0 commit comments