Skip to content

Commit 59cf290

Browse files
committed
Cache connectivity and reduce mapping cache churn
1 parent 86f2e7e commit 59cf290

3 files changed

Lines changed: 55 additions & 33 deletions

File tree

src/main/java/com/bioinceptionlabs/reactionblast/mapping/GraphMatcher.java

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,7 @@ public static class MCSThread implements Callable<MCSSolution> {
10041004
private final int invocationIndex;
10051005
private final Map<String, Integer> compound1SymbolCounts;
10061006
private final Map<String, Integer> compound2SymbolCounts;
1007+
private final boolean moleculesConnected;
10071008

10081009
/**
10091010
*
@@ -1042,6 +1043,7 @@ public static class MCSThread implements Callable<MCSSolution> {
10421043
this.numberOfCyclesProduct = 0;
10431044
this.compound1SymbolCounts = countAtomsBySymbol(this.compound1);
10441045
this.compound2SymbolCounts = countAtomsBySymbol(this.compound2);
1046+
this.moleculesConnected = isConnected(this.compound1) && isConnected(this.compound2);
10451047
}
10461048

10471049
void printMatch(BaseMapping isomorphism) {
@@ -1078,7 +1080,7 @@ public MCSSolution call() throws Exception {
10781080
/*
10791081
* IMP: Do not perform substructure matching for disconnected molecules
10801082
*/
1081-
boolean moleculeConnected = isMoleculeConnected(getCompound1(), getCompound2());
1083+
boolean moleculeConnected = moleculesConnected;
10821084
/*
10831085
Check if MCS matching required or not very IMP step
10841086
*/
@@ -1308,24 +1310,25 @@ MCSSolution mcs() throws CDKException, CloneNotSupportedException {
13081310
bm = AtomBondMatcher.bondMatcher(settings.bondMatch, settings.ringMatch);
13091311

13101312
key = generateUniqueKey(settings);
1311-
if (ThreadSafeCache.getInstance().containsKey(key)) {
1313+
ThreadSafeCache<String, MCSSolution> mappingCache = ThreadSafeCache.getInstance();
1314+
MCSSolution cachedSolution = mappingCache.get(key);
1315+
if (cachedSolution != null) {
13121316
LOGGER.debug("===={Aladdin} Mapping {Gini}====");
13131317
MappingDiagnostics.recordMcsCacheHit(reactionId, algorithmName, invocationIndex);
1314-
MCSSolution solution = (MCSSolution) ThreadSafeCache.getInstance().get(key);
13151318
mcs = copyOldSolutionToNew(
13161319
getQueryPosition(), getTargetPosition(),
13171320
getCompound1(), getCompound2(),
1318-
solution);
1321+
cachedSolution);
13191322

13201323
} else {
13211324
SearchEngine.McsOptions mcsOptions = new SearchEngine.McsOptions();
13221325
mcsOptions.timeoutMs = MCS_TIMEOUT_MS;
1323-
mcsOptions.connectedOnly = isMoleculeConnected(ac1, ac2);
1326+
mcsOptions.connectedOnly = moleculesConnected;
13241327
mcsOptions.disconnectedMCS = !mcsOptions.connectedOnly;
13251328
mcsOptions.maximizeBonds = settings.bondMatch;
13261329
MappingDiagnostics.recordActualMcsSearch(reactionId, algorithmName, invocationIndex);
13271330
isomorphism = MAPPING_ENGINE.findMcs(ac1, ac2, Algorithm.VFLibMCS, am, bm, mcsOptions);
1328-
mcs = addMCSSolution(key, ThreadSafeCache.getInstance(), isomorphism);
1331+
mcs = addMCSSolution(key, mappingCache, isomorphism);
13291332
}
13301333

13311334
return mcs;
@@ -1452,23 +1455,10 @@ boolean isHasPerfectRings() {
14521455
/*
14531456
* Check if fragmented container has single atom
14541457
*/
1455-
private boolean isMoleculeConnected(IAtomContainer compound1, IAtomContainer compound2) {
1456-
LOGGER.debug("isMoleculeConnected");
1457-
boolean connected1 = true;
1458-
1459-
IAtomContainerSet partitionIntoMolecules = ConnectivityChecker.partitionIntoMolecules(compound1);
1460-
if (partitionIntoMolecules.getAtomContainerCount() > 1) {
1461-
connected1 = false;
1462-
}
1463-
1464-
boolean connected2 = true;
1465-
1466-
partitionIntoMolecules = ConnectivityChecker.partitionIntoMolecules(compound2);
1467-
if (partitionIntoMolecules.getAtomContainerCount() > 1) {
1468-
connected2 = false;
1469-
}
1470-
1471-
return connected1 && connected2;
1458+
private boolean isConnected(IAtomContainer compound) {
1459+
LOGGER.debug("isConnected");
1460+
IAtomContainerSet partitionIntoMolecules = ConnectivityChecker.partitionIntoMolecules(compound);
1461+
return partitionIntoMolecules.getAtomContainerCount() <= 1;
14721462
}
14731463

14741464
void setEductRingCount(int numberOfCyclesEduct) {
@@ -1527,14 +1517,18 @@ MCSSolution addMCSSolution(String key, ThreadSafeCache<String, MCSSolution> mapp
15271517
long time = stopTime - startTime;
15281518
printMatch(isomorphism);
15291519
LOGGER.debug("\" Time:\" " + time);
1530-
if (!mappingcache.containsKey(key)) {
1520+
MCSSolution cached = mappingcache.putIfAbsent(key, mcs);
1521+
if (cached == mcs) {
15311522
LOGGER.debug("Key " + key);
15321523
LOGGER.debug("mcs size " + mcs.getAtomAtomMapping().getCount());
15331524
LOGGER.debug("mcs map " + mcs.getAtomAtomMapping().getMappingsByIndex());
15341525
LOGGER.debug("\n\n\n ");
1535-
mappingcache.put(key, mcs);
1526+
return mcs;
15361527
}
1537-
return mcs;
1528+
return copyOldSolutionToNew(
1529+
getQueryPosition(), getTargetPosition(),
1530+
getCompound1(), getCompound2(),
1531+
cached);
15381532
}
15391533
}
15401534

src/main/java/com/bioinceptionlabs/reactionblast/mapping/ThreadSafeCache.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,29 @@ public boolean containsKey(K key) {
6565
return map.containsKey(key);
6666
}
6767

68+
/**
69+
* Insert the value only if the key is absent.
70+
*
71+
* @return the existing value if present, otherwise the inserted value
72+
*/
73+
public V putIfAbsent(K key, V value) {
74+
if (map.size() >= MAX_CAPACITY) {
75+
evict();
76+
}
77+
if (map instanceof ConcurrentHashMap<?, ?> concurrentMap) {
78+
@SuppressWarnings("unchecked")
79+
ConcurrentHashMap<K, V> typedMap = (ConcurrentHashMap<K, V>) concurrentMap;
80+
V existing = typedMap.putIfAbsent(key, value);
81+
return existing != null ? existing : value;
82+
}
83+
V existing = map.get(key);
84+
if (existing == null) {
85+
map.put(key, value);
86+
return value;
87+
}
88+
return existing;
89+
}
90+
6891
/**
6992
* Clear all cached entries. Use sparingly — cross-reaction caching
7093
* benefits from keeping the cache warm between reactions.

src/main/java/com/bioinceptionlabs/reactionblast/mapping/algorithm/GameTheoryEngine.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -415,20 +415,23 @@ private MCSSolution quickMapping(String reactionId, String algorithmName,
415415
educt.getBondCount(), product.getBondCount(),
416416
false, false, false, false,
417417
numberOfCyclesEduct, numberOfCyclesProduct);
418-
if (mappingcache.containsKey(key)) {
418+
MCSSolution cached = mappingcache.get(key);
419+
if (cached != null) {
419420
MappingDiagnostics.recordQuickMappingCacheHit(reactionId, algorithmName);
420-
MCSSolution solution = mappingcache.get(key);
421421
MCSSolution mcs = copyOldSolutionToNew(
422422
queryPosition, targetPosition,
423-
educt, product, solution);
423+
educt, product, cached);
424424
return mcs;
425425
} else {
426426
AtomMatcher atomMatcher = AtomBondMatcher.atomMatcher(false, false);
427427
BondMatcher bondMatcher = AtomBondMatcher.bondMatcher(false, false);
428428
MappingDiagnostics.recordQuickMappingSearch(reactionId, algorithmName);
429429
BaseMapping isomorphism = MAPPING_ENGINE.findMcs(
430430
educt, product, Algorithm.DEFAULT, atomMatcher, bondMatcher);
431-
MCSSolution mcs = addMCSSolution(queryPosition, targetPosition, key, mappingcache, isomorphism);
431+
MCSSolution mcs = addMCSSolution(
432+
queryPosition, targetPosition,
433+
educt, product,
434+
key, mappingcache, isomorphism);
432435
return mcs;
433436
}
434437
} catch (CDKException ex) {
@@ -562,17 +565,19 @@ MCSSolution copyOldSolutionToNew(int queryPosition, int targetPosition,
562565
}
563566

564567
MCSSolution addMCSSolution(int queryPosition, int targetPosition,
568+
IAtomContainer educt, IAtomContainer product,
565569
String key, ThreadSafeCache<String, MCSSolution> mappingcache, BaseMapping isomorphism) {
566570
MAPPING_ENGINE.applyDefaultFilters(isomorphism);
567571
MCSSolution mcs = new MCSSolution(queryPosition, targetPosition,
568572
isomorphism.getQuery(), isomorphism.getTarget(), isomorphism.getFirstAtomMapping());
569573
mcs.setEnergy(isomorphism.getEnergyScore(0));
570574
mcs.setFragmentSize(isomorphism.getFragmentSize(0));
571575
mcs.setStereoScore(isomorphism.getStereoScore(0));
572-
if (!mappingcache.containsKey(key)) {
573-
mappingcache.put(key, mcs);
576+
MCSSolution cached = mappingcache.putIfAbsent(key, mcs);
577+
if (cached == mcs) {
578+
return mcs;
574579
}
575-
return mcs;
580+
return copyOldSolutionToNew(queryPosition, targetPosition, educt, product, cached);
576581
}
577582

578583
// ========== Inner class: GameTheoryFactory ==========

0 commit comments

Comments
 (0)