usainboltz.examples.alcohols

A Boltzmann sampler for alcohols

>>> from usainboltz import *
>>> from usainboltz.generator import rng_seed
>>> rng_seed(0xDEADBEEFB0175)  # For reproducibility

According to [FS2009] page 477, alcohols can been seen as rooted non-plane trees of degree 3. The carbonic skeleton gives the tree structure and the carbon atom to which the (unique) -OH group is attached is the root of the tree. We thus use the MSet together with the eq=3 constraint to specify these trees.

>>> carbon = Atom()
>>> A = RuleName("A")  # A for alcohol
>>> hydrogen = Marker("H")
>>> grammar = Grammar({A: hydrogen + carbon * MSet(A, eq=3)})
>>> grammar
{
  A : Union(H, Product(z, MSet(A, eq = 3)))
}

According to [FS2009], the singularity of the generating function of alcohols is near 0.35518174. Let us verify that the oracle finds a similar value.

>>> oracle = build_oracle(grammar)
>>> values = oracle.tuning(carbon, singular=True)
>>> print(f"{values[carbon][1]:.8f}")
0.35518174

The tree representation does not help a lot to visualise the molecule so let us define a custom data structure for representing alcohols

>>> class Alcohol:
...     def __init__(self, *args):
...         main_builder = union_builder(self._init_hydrogen, self._init_carbon)
...         main_builder(*args)
...
...     def _init_hydrogen(self, epsilon):
...         self.element = "H"
...
...     def _init_carbon(self, args):
...         c, children = args
...         self.element = "C"
...         self.children = children
...
...     def to_dot(self):
...         # Print in graphviz format
...         print('graph {\n  node [shape="none", width=0.4, height=0.4]')
...         root_id = self._to_dot(0)
...         print('  root_node [label="OH"]')
...         print(f"  root_node -- node{root_id}")
...         print("}")
...
...     def _to_dot(self, counter):
...         # counter is the id of the last printed object
...         if self.element == "H":
...             counter += 1
...             print(f'  node{counter} [label="H"]')
...             return counter
...         else:
...             children_names = []
...             for c in self.children:
...                 counter = c._to_dot(counter)
...                 children_names.append(f"node{counter}")
...             counter += 1
...             print(f'  node{counter} [label="C"]')
...             print(f'  node{counter} -- {{{" ".join(children_names)}}}')
...             return counter

We defined the __init__ method of our class so that it can be passed directly to the generator as a builder. Let us to so and run the random sampler:

>>> generator = Generator(grammar)
>>> generator.set_builder(A, Alcohol)  # pass the class constructor directly
>>> res = generator.sample((10, 15))

We generated an alcohol with the following elements:

>>> SUB = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉")
>>> nb_carbons = str(res.sizes[carbon]).translate(SUB)
>>> nb_hydrogens = str(res.sizes[hydrogen] + 1).translate(SUB)
>>> print(f"C{nb_carbons}OH{nb_hydrogens}")
C₁₅OH₃₂

Using the printer we defined above, we can print a DOT reprensetation of the generated alcohol:

>>> res.obj.to_dot()  # doctest: +ELLIPSIS
graph {
  node [shape="none", width=0.4, height=0.4]
  node1 [label="H"]
  node2 [label="H"]
  node3 [label="H"]
  node4 [label="C"]
  node4 -- {node1 node2 node3}
  node5 [label="H"]
...

Using the neato rendered of the graphviz library, we get the following picture:

../_images/alcohol.svg