Molecules#

Before MolFoundry can rewrite chemistry it needs the chemistry written down. This section covers the three everyday ways to hand it a molecule — a SMILES string, an explicit GML graph, or (for a single complex group) a GML node with an arbitrary label — and what a molecule graph can tell you about itself.

Molecules from SMILES#

The Simplified Molecular Input Line Entry System (SMILES) is a typographical line notation for chemical structures. It is compact because it throws away everything redundant: a SMILES string is essentially a spanning tree of the molecule, with the non-essential hydrogens removed, the rings cut open, and the remaining atoms and bonds written out in a depth-first walk. The main conventions are few:

  1. Atoms are their atomic symbol. The organic subset — B, C, N, O, P, S, F, Cl, Br, I — may be written bare; everything else goes in square brackets. Lower case denotes an aromatic atom, and implicit hydrogens are filled in to satisfy the normal valence.

  2. Bonds are - (single), = (double), # (triple) and : (aromatic); single and aromatic bonds are usually omitted.

  3. Branches are enclosed in parentheses.

  4. Rings are cut, and the two ends of the cut bond carry the same digit.

smiles() parses a SMILES string into a molecule graph; the optional second argument names it.

pyruvate = smiles("CC(=O)C(=O)O", "pyruvate", add=False)
pyruvate.print()
pyruvate
O O OH

add=False keeps the molecule out of the global inputGraphs collection; drop it and every parsed molecule is registered there automatically, ready for a derivation-graph build.

A molecule graph knows its own canonical SMILES, its InChI, its exact (monoisotopic) mass, and how many atoms or bonds of a given kind it has:

print("canonical SMILES:", pyruvate.smiles)
print("InChI:           ", pyruvate.inchi)
print("exact mass:      ", round(pyruvate.exactMass, 4))
print("#atoms, #carbons:", pyruvate.numVertices, pyruvate.vLabelCount("C"))
canonical SMILES: O=C(C(=O)O)C
InChI:            InChI=1S/C3H4O3/c1-2(4)3(5)6/h1H3,(H,5,6)
exact mass:       88.016
#atoms, #carbons: 10 3

The complete script also renders a more elaborate structure and prints its descriptors:

01_visualizing_molecules.py#
"""1. Visualizing molecules.

The smallest useful MolFoundry program: turn a SMILES string into a molecule
graph and render it. ``enable_post()`` turns on the HTML summary; every
``.print()`` appends a depiction, and when the process exits a self-contained
``summary/summary.html`` is written next to this script (open it in a browser).

Run:  python 01_visualizing_molecules.py
"""
import _bootstrap  # noqa: F401  (puts molfoundry on sys.path)
from molfoundry import smiles, enable_post

enable_post()

# Pyruvate, CC(=O)C(=O)O. The optional second argument names the graph.
pyruvate = smiles("CC(=O)C(=O)O", "pyruvate")
pyruvate.print()

# A molecule graph knows its canonical SMILES, InChI and exact mass.
print("SMILES:     ", pyruvate.smiles)
print("InChI:      ", pyruvate.inchi)
print("InChIKey:   ", pyruvate.inchiKey)
print("exact mass: ", round(pyruvate.exactMass, 4))
print("#atoms:     ", pyruvate.numVertices, "(#C =", pyruvate.vLabelCount("C"), ")")

print("\nWrote summary/summary.html -- open it in a browser to see the depiction.")

Molecules in GML#

GML (Graph Modeling Language) is the explicit, atom-by-atom description of a graph — a hierarchy of key/value pairs. A molecule is a single graph [ … ] block of node and edge entries: each node carries an integer id and a label (the element symbol), and each edge names its source and target ids and a bond label. Because a chemical bond is undirected, it is listed once, conventionally with the smaller id as source. Here is water:

water = graphGMLString('''graph [
    node [ id 0 label "O" ]
    node [ id 1 label "H" ]
    node [ id 2 label "H" ]
    edge [ source 0 target 1 label "-" ]
    edge [ source 0 target 2 label "-" ]
]''', "water", add=False)
water.print()
water
OH2

MolFoundry stores graphs canonically, so getGMLString() re-emits the graph in a normalized vertex order — the same molecule, described consistently:

print(water.getGMLString())
graph [
	node [ id 0 label "H" ]
	node [ id 1 label "H" ]
	node [ id 2 label "O" ]
	edge [ source 0 target 2 label "-" ]
	edge [ source 1 target 2 label "-" ]
]

Special atom states — charge and radical — are written straight into the node label by appending +, - (repeatable, e.g. Fe+++) or . (radical). The hydronium cation is an oxygen written as "O+":

hydronium = graphGMLString('''graph [
    node [ id 0 label "O+" ]
    node [ id 1 label "H" ] node [ id 2 label "H" ] node [ id 3 label "H" ]
    edge [ source 0 target 1 label "-" ]
    edge [ source 0 target 2 label "-" ]
    edge [ source 0 target 3 label "-" ]
]''', "hydronium", add=False)
hydronium.print()
print(hydronium.smiles, " exact mass:", round(hydronium.exactMass, 4))
hydronium
OH3+
[OH3+]  exact mass: 19.0178
02_molecules_in_gml.py#
"""2. Specifying molecules in GML.

GML (Graph Modeling Language) is the explicit, atom-by-atom way to write a
molecule graph: a ``graph [ ... ]`` block of ``node`` and ``edge`` entries.
Each ``node`` has an ``id`` and a ``label`` (the element symbol, with an
optional trailing ``+``, ``-`` or ``.`` for charge / radical state); each
``edge`` names its ``source`` and ``target`` ids and a bond ``label``
(``-`` single, ``=`` double, ``#`` triple, ``:`` aromatic). A bond is listed
once, with the smaller id as source.

See ``gml/water.gml`` and ``gml/hydronium.gml`` for the two graphs loaded here.

Run:  python 02_molecules_in_gml.py
"""
import _bootstrap
from _bootstrap import graphGMLFile
from molfoundry import enable_post

enable_post()

water = graphGMLFile("water.gml", "water")
hydronium = graphGMLFile("hydronium.gml", "hydronium")

for g in (water, hydronium):
    g.print()
    print(f"{g.name:10s} SMILES={g.smiles!r:12} mass={g.exactMass:8.4f}  isMolecule={g.isMolecule}")

# The GML is canonical and round-trips: getGMLString() re-emits the graph.
print("\n--- water.gml as MolFoundry sees it ---")
print(water.getGMLString())

Complex group labels#

A bracket atom in a SMILES string must be a real element, so a whole functional group cannot be hidden behind a made-up symbol like [CoA]. When you want a single node to stand for a complex group, write the graph in GML and give the node any label you like. Coenzyme-A, with only its reactive thiol modeled explicitly:

coa = graphGMLString('''graph [
    node [ id 0 label "CoA" ]  node [ id 1 label "S" ]  node [ id 2 label "H" ]
    edge [ source 0 target 1 label "-" ]
    edge [ source 1 target 2 label "-" ]
]''', "CoA-SH", add=False)
coa.print()
print("is a molecule:", coa.isMolecule)
CoA-SH
CoA SH
is a molecule: False

isMolecule is False: a "CoA" pseudo-atom is not a real element, so this is a first-class graph you can still rewrite and put into a network, but not one MolFoundry can weigh or hand to InChI. A graph that is a real molecule additionally exposes an inchi, inchiKey, and exactMass.

03_molecules_from_smiles.py#
"""3. Molecules from SMILES (and complex group labels).

``smiles()`` parses the usual line notation: organic-subset atoms
(B, C, N, O, P, S, F, Cl, Br, I) need no brackets, aromaticity is lower-case,
branches are ``( ... )``, and rings are broken with matching digits. Implicit
hydrogens are filled in.

Note on "complex" labels: a bracket atom in SMILES must be a real element, so a
whole functional group cannot be hidden behind a made-up symbol like ``[CoA]``
in a SMILES string. When you want a single node to stand for a complex group,
write the graph in GML and give the node any label you like -- shown here for
coenzyme-A, where only the reactive thiol is modeled explicitly.

Run:  python 03_molecules_from_smiles.py
"""
import _bootstrap
from molfoundry import smiles, graphGMLString, enable_post

enable_post()

# Organic-subset atoms, a branch and a ring closure.
acetone = smiles("CC(=O)C", "acetone")
ciprofloxacin = smiles("N1CCN(CC1)C(C(F)=C2)=CC(=C2C4=O)N(C3CC3)C=C4C(=O)O", "ciprofloxacin")
for g in (acetone, ciprofloxacin):
    g.print()
    print(f"{g.name:14s} canonical SMILES = {g.smiles}")

# A single node standing for a complex group (coenzyme-A), thiol explicit.
coa = graphGMLString(
    """graph [
        node [ id 0 label "CoA" ]
        node [ id 1 label "S" ]
        node [ id 2 label "H" ]
        edge [ source 0 target 1 label "-" ]
        edge [ source 1 target 2 label "-" ]
    ]""",
    "CoA-SH",
)
coa.print()
print(f"\n{coa.name}: isMolecule={coa.isMolecule} "
      f"(a 'CoA' pseudo-atom is not a real element, so it is a graph, not a molecule)")

# A real molecule additionally exposes an InChI (and InChIKey).
print("\nacetone InChI:", acetone.inchi)

See also

The SMILES and GML format guides go deeper — atom-mapped SMILES, electron graphs, stereochemistry, and the full GML key set. GraphDFS line notation is introduced alongside the rules in Reactions.