Skip to content

Commit

Permalink
Merge pull request #205 from cdk/patch/sdgsublayout
Browse files Browse the repository at this point in the history
Looks good to me! A few comments, but nothing that scares me :)
  • Loading branch information
egonw committed Jul 8, 2016
2 parents 40eea14 + 6d9768f commit bc1c053
Show file tree
Hide file tree
Showing 12 changed files with 993 additions and 382 deletions.
Expand Up @@ -26,6 +26,8 @@
import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.exception.InvalidSmilesException;
import org.openscience.cdk.graph.ConnectedComponents;
import org.openscience.cdk.graph.ConnectivityChecker;
import org.openscience.cdk.graph.Cycles;
import org.openscience.cdk.graph.GraphUtil;
import org.openscience.cdk.graph.GraphUtil.EdgeToBondMap;
Expand Down Expand Up @@ -77,7 +79,7 @@
* Utility class for abbreviating (sub)structures. Using either self assigned structural
* motifs or pre-loading a common set a structure depiction can be made more concise with
* the use of abbreviations (sometimes called superatoms). <p/>
*
* <p>
* Basic usage:
* <pre>{@code
* Abbreviations abrv = new Abbreviations();
Expand All @@ -99,10 +101,10 @@
* // set/update the CDKConstants.CTAB_SGROUPS property of mol
* List<Sgroup> sgroups = abrv.generate(mol);
* }</pre>
*
* <p>
* Predefined sets of abbreviations can be loaded, the following are
* on the classpath.
*
* <p>
* <pre>{@code
* // https://www.github.com/openbabel/superatoms
* abrv.loadFromFile("obabel_superatoms.smi");
Expand All @@ -118,11 +120,11 @@ public class Abbreviations implements Iterable<String> {

private static final int MAX_FRAG = 50;

private final Map<String, String> connectedAbbreviations = new LinkedHashMap<>();
private final Map<String, String> disconnectedAbbreviations = new LinkedHashMap<>();
private final Set<String> labels = new LinkedHashSet<>();
private final Set<String> disabled = new HashSet<>();
private final SmilesGenerator usmigen = SmilesGenerator.unique();
private final Map<String, String> connectedAbbreviations = new LinkedHashMap<>();
private final Map<String, String> disconnectedAbbreviations = new LinkedHashMap<>();
private final Set<String> labels = new LinkedHashSet<>();
private final Set<String> disabled = new HashSet<>();
private final SmilesGenerator usmigen = SmilesGenerator.unique();

private final SmilesParser smipar = new SmilesParser(SilentChemObjectBuilder.getInstance());

Expand Down Expand Up @@ -166,7 +168,7 @@ private static Set<IBond> findCutBonds(IAtomContainer mol, EdgeToBondMap bmap, i
int numAtoms = mol.getAtomCount();
for (int i = 0; i < numAtoms; i++) {
final IAtom atom = mol.getAtom(i);
int deg = adjlist[i].length;
int deg = adjlist[i].length;
int elem = atom.getAtomicNumber();

if (elem == 6 && deg <= 2 || deg < 2)
Expand All @@ -184,13 +186,14 @@ private static Set<IBond> findCutBonds(IAtomContainer mol, EdgeToBondMap bmap, i

private static final String CUT_BOND = "cutbond";

private static List<IAtomContainer> makeCut(IBond cut, IAtomContainer mol, Map<IAtom,Integer> idx, int[][] adjlist) {
private static List<IAtomContainer> makeCut(IBond cut, IAtomContainer mol, Map<IAtom, Integer> idx,
int[][] adjlist) {

IAtom beg = cut.getAtom(0);
IAtom end = cut.getAtom(1);

Set<IAtom> bvisit = new LinkedHashSet<>();
Set<IAtom> evisit = new LinkedHashSet<>();
Set<IAtom> bvisit = new LinkedHashSet<>();
Set<IAtom> evisit = new LinkedHashSet<>();
Deque<IAtom> queue = new ArrayDeque<>();

bvisit.add(beg);
Expand Down Expand Up @@ -268,14 +271,14 @@ else if (evisit.contains(a1) && evisit.contains(a2))

private static List<IAtomContainer> generateFragments(IAtomContainer mol) {

final EdgeToBondMap bmap = EdgeToBondMap.withSpaceFor(mol);
final int[][] adjlist = GraphUtil.toAdjList(mol, bmap);
final EdgeToBondMap bmap = EdgeToBondMap.withSpaceFor(mol);
final int[][] adjlist = GraphUtil.toAdjList(mol, bmap);

Cycles.markRingAtomsAndBonds(mol, adjlist, bmap);

Set<IBond> cuts = findCutBonds(mol, bmap, adjlist);

Map<IAtom,Integer> atmidx = new HashMap<>();
Map<IAtom, Integer> atmidx = new HashMap<>();
for (IAtom atom : mol.atoms())
atmidx.put(atom, atmidx.size());

Expand Down Expand Up @@ -318,16 +321,53 @@ public List<Sgroup> generate(final IAtomContainer mol) {
// disconnected abbreviations, salts, common reagents, large compounds
if (usedAtoms.isEmpty()) {
try {
String cansmi = usmigen.create(AtomContainerManipulator.copyAndSuppressedHydrogens(mol));
IAtomContainer copy = AtomContainerManipulator.copyAndSuppressedHydrogens(mol);
String cansmi = usmigen.create(copy);
String label = disconnectedAbbreviations.get(cansmi);

if (label != null && !disabled.contains(label)) {
Sgroup sgroup = new Sgroup();
sgroup.setType(SgroupType.CtabAbbreviation);
sgroup.setSubscript(label);
for (IAtom atom : mol.atoms())
sgroup.addAtom(atom);
return Collections.singletonList(sgroup);
} else if (cansmi.contains(".")) {
List<Sgroup> newSgroups = new ArrayList<>();
for (IAtomContainer part : ConnectivityChecker.partitionIntoMolecules(mol).atomContainers()) {
cansmi = usmigen.create(part);
label = disconnectedAbbreviations.get(cansmi);
if (label != null && !disabled.contains(label)) {
Sgroup sgroup = new Sgroup();
sgroup.setType(SgroupType.CtabAbbreviation);
sgroup.setSubscript(label);
for (IAtom atom : part.atoms())
sgroup.addAtom(atom);
newSgroups.add(sgroup);
}
}
if (!newSgroups.isEmpty()) {
// merge together
if (newSgroups.size() > 1) {
Sgroup combined = new Sgroup();
label = null;
for (Sgroup sgroup : newSgroups) {
if (label == null)
label = sgroup.getSubscript();
else
label += "/" + sgroup.getSubscript();
for (IAtom atom : sgroup.getAtoms())
combined.addAtom(atom);
}
combined.setSubscript(label);
combined.setType(SgroupType.CtabAbbreviation);
newSgroups.clear();
newSgroups.add(combined);
}
return newSgroups;
}
}

} catch (CDKException ignored) {
}
}
Expand Down Expand Up @@ -390,7 +430,7 @@ public List<Sgroup> generate(final IAtomContainer mol) {
*/
public int apply(final IAtomContainer mol) {
List<Sgroup> newSgroups = generate(mol);
List<Sgroup> sgroups = mol.getProperty(CDKConstants.CTAB_SGROUPS);
List<Sgroup> sgroups = mol.getProperty(CDKConstants.CTAB_SGROUPS);

if (sgroups == null)
sgroups = new ArrayList<>();
Expand Down Expand Up @@ -518,12 +558,12 @@ public boolean add(String line) throws InvalidSmilesException {
/**
* Add an abbreviation to the factory. Abbreviations can be of various flavour based
* on the number of attachments:
*
* <p>
* <p/>
* <b>Detached</b> - zero attachments, the abbreviation covers the whole structure (e.g. THF) <p/>
* <b>Terminal</b> - one attachment, covers substituents (e.g. Ph for Phenyl)<p/>
* <b>Linker</b> - [NOT SUPPORTED YET] two attachments, covers long repeated chains (e.g. PEG4) <p/>
*
* <p>
* Attachment points (if present) must be specified with zero element atoms. <p/>
* <pre>
* *c1ccccc1 Ph
Expand Down Expand Up @@ -591,7 +631,7 @@ private int loadSmiles(final InputStream in) throws IOException {
* *c1ccccc1 Ph
* *c1ccccc1 OAc
* </pre>
*
* <p>
* Available:
* <pre>
* obabel_superatoms.smi - https://www.github.com/openbabel/superatoms
Expand Down
Expand Up @@ -174,11 +174,6 @@ public final class DepictionGenerator {
*/
private final List<IGenerator<IAtomContainer>> gens = new ArrayList<>();

/**
* Structure diagram generator instance.
*/
private final StructureDiagramGenerator sdg = new StructureDiagramGenerator();

/**
* Flag to indicate atom numbers should be displayed.
*/
Expand All @@ -199,6 +194,11 @@ public final class DepictionGenerator {
*/
private Color[] atomMapColors = null;

/**
* Reactions are aligned such that mapped atoms have the same coordinates on the left/right.
*/
private boolean alignMappedReactions = true;

/**
* Object that should be highlighted
*/
Expand Down Expand Up @@ -239,8 +239,6 @@ public DepictionGenerator(Font font) {
// since it depends on raster (px) vs vector (mm)
setParam(BasicSceneGenerator.Margin.class, AUTOMATIC);
setParam(RendererModel.Padding.class, AUTOMATIC);

sdg.setUseTemplates(false);
}

/**
Expand All @@ -258,6 +256,7 @@ private DepictionGenerator(DepictionGenerator org) {
this.highlight.putAll(org.highlight);
this.gens.addAll(org.gens);
this.params.putAll(org.params);
this.alignMappedReactions = org.alignMappedReactions;
}

private <U, T extends IGeneratorParameter<U>> U getParameterValue(Class<T> key) {
Expand Down Expand Up @@ -457,6 +456,7 @@ public Depiction depict(IReaction rxn) throws CDKException {
myHighlight.putAll(highlight);
highlight.clear();

ensure2dLayout(rxn);
final List<Double> reactantScales = prepareCoords(reactants);
final List<Double> productScales = prepareCoords(products);
final List<Double> agentScales = prepareCoords(agents);
Expand Down Expand Up @@ -672,11 +672,8 @@ private Bounds generateReactionConditions(IReaction chemObj, Color fg, double sc
*/
private boolean ensure2dLayout(IAtomContainer container) throws CDKException {
if (!GeometryUtil.has2DCoordinates(container)) {
// SDG - mutable state is not thread safe :(
synchronized (sdg) {
sdg.setMolecule(container, false);
sdg.generateCoordinates();
}
StructureDiagramGenerator sdg = new StructureDiagramGenerator();
sdg.generateCoordinates(container);
return true;
}
return false;
Expand All @@ -689,12 +686,9 @@ private boolean ensure2dLayout(IAtomContainer container) throws CDKException {
* @throws CDKException coordinates could not be generated
*/
private void ensure2dLayout(IReaction rxn) throws CDKException {
for (IAtomContainer mol : rxn.getReactants().atomContainers())
ensure2dLayout(mol);
for (IAtomContainer mol : rxn.getProducts().atomContainers())
ensure2dLayout(mol);
for (IAtomContainer mol : rxn.getAgents().atomContainers())
ensure2dLayout(mol);
StructureDiagramGenerator sdg = new StructureDiagramGenerator();
sdg.setAlignMappedReaction(alignMappedReactions);
sdg.generateCoordinates(rxn);
}

/**
Expand Down Expand Up @@ -866,6 +860,19 @@ public DepictionGenerator withRxnTitle() {
true);
}

/**
* Specifies that reactions with atom-atom mappings should have their reactants/product
* coordinates aligned. Default: true.
*
* @param val setting value
* @return new generator for method chaining
*/
public DepictionGenerator withMappedRxnAlign(boolean val) {
DepictionGenerator copy = new DepictionGenerator(this);
copy.alignMappedReactions = val;
return copy;
}

/**
* Set the color annotations (e.g. atom-numbers) will appear in.
*
Expand Down
49 changes: 49 additions & 0 deletions base/core/src/main/java/org/openscience/cdk/graph/GraphUtil.java
Expand Up @@ -24,10 +24,13 @@
package org.openscience.cdk.graph;

import com.google.common.collect.Maps;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IBond;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import static java.util.Arrays.copyOf;

Expand Down Expand Up @@ -90,6 +93,52 @@ public static int[][] toAdjList(IAtomContainer container) {
return graph;
}

/**
* Create an adjacent list representation of the {@literal container} that only
* includes bonds that are in the set provided as an argument.
*
* @param container the molecule
* @return adjacency list representation stored as an {@literal int[][]}.
* @throws NullPointerException the container was null
* @throws IllegalArgumentException a bond was found which contained atoms
* not in the molecule
*/
public static int[][] toAdjListSubgraph(IAtomContainer container, Set<IBond> include) {

if (container == null) throw new NullPointerException("atom container was null");

int n = container.getAtomCount();

int[][] graph = new int[n][DEFAULT_DEGREE];
int[] degree = new int[n];

for (IBond bond : container.bonds()) {

if (!include.contains(bond))
continue;

int v = container.getAtomNumber(bond.getAtom(0));
int w = container.getAtomNumber(bond.getAtom(1));

if (v < 0 || w < 0)
throw new IllegalArgumentException("bond at index " + container.getBondNumber(bond)
+ " contained an atom not pressent in molecule");

graph[v][degree[v]++] = w;
graph[w][degree[w]++] = v;

// if the vertex degree of v or w reaches capacity, double the size
if (degree[v] == graph[v].length) graph[v] = copyOf(graph[v], degree[v] * 2);
if (degree[w] == graph[w].length) graph[w] = copyOf(graph[w], degree[w] * 2);
}

for (int v = 0; v < n; v++) {
graph[v] = copyOf(graph[v], degree[v]);
}

return graph;
}

/**
* Create an adjacent list representation of the {@code container} and
* fill in the {@code bondMap} for quick lookup.
Expand Down
Expand Up @@ -80,10 +80,18 @@ public static boolean isConnected(IAtomContainer atomContainer) {
* @cdk.dictref blue-obelisk:graphPartitioning
*/
public static IAtomContainerSet partitionIntoMolecules(IAtomContainer container) {

ConnectedComponents cc = new ConnectedComponents(GraphUtil.toAdjList(container));
int[] components = cc.components();
IAtomContainer[] containers = new IAtomContainer[cc.nComponents() + 1];
return partitionIntoMolecules(container, cc.components());
}

public static IAtomContainerSet partitionIntoMolecules(IAtomContainer container, int[] components) {

int maxComponentIndex = 0;
for (int component : components)
if (component > maxComponentIndex)
maxComponentIndex = component;

IAtomContainer[] containers = new IAtomContainer[maxComponentIndex + 1];
Map<IAtom, IAtomContainer> componentsMap = new HashMap<IAtom, IAtomContainer>(2 * container.getAtomCount());

for (int i = 1; i < containers.length; i++)
Expand All @@ -96,8 +104,12 @@ public static IAtomContainerSet partitionIntoMolecules(IAtomContainer container)
containers[components[i]].addAtom(container.getAtom(i));
}

for (IBond bond : container.bonds())
componentsMap.get(bond.getAtom(0)).addBond(bond);
for (IBond bond : container.bonds()) {
IAtomContainer begComp = componentsMap.get(bond.getAtom(0));
IAtomContainer endComp = componentsMap.get(bond.getAtom(1));
if (begComp == endComp)
begComp.addBond(bond);
}

for (ISingleElectron electron : container.singleElectrons())
componentsMap.get(electron.getAtom()).addSingleElectron(electron);
Expand Down

0 comments on commit bc1c053

Please sign in to comment.