The Wolfram Chemistry Functions paclet features a function for generating a random alkane with a given number of atoms called, appropriately enough, RandomAlkane. The function either randomly chooses from a precomputed list of alkanes, or uses RandomGraph along with a few changes to make a "plausible" structure. I thought I'd share yet another method for generating random alkanes, which might be a bit more efficient in some cases.
My proposal rests on generating a random integer partition, using the same method from the old Combinatorica package (also available as the resource function RandomIntegerPartition). To keep this notebook self-contained, here's the relevant routine:
In[]:=
randomIntegerPartition[n_Integer?Positive]:=Module[{mult=ConstantArray[0,n],r=n,d,j,z},While[d=1;j=0;z=RandomReal[]rPartitionsP[r];While[z≥0,j++;If[r-jd<0,j=1;d++];z-=jPartitionsP[r-jd]];r-=jd;mult[[j]]+=d;r>0];Reverse[Flatten[Table[ConstantArray[j,mult[[j]]],{j,Length[mult]}]]]]
Having generated a random partition, the algorithm I propose can be summarized as follows:
◼
1. For an integer partition (p1, p2, p3, p4, …), generate the path graph with p1 vertices (or equivalently, the straight-chain alkane with p1 carbon atoms), represented as its adjacency matrix.
◼
2. Randomly choose p2 of the vertices of the path graph, to attach an additional p2 vertices on. The random choice can be weighted by preferring less substituted vertices over more substituted ones (i.e., primary > secondary > tertiary), with quaternary and isolated vertices corresponding to zero weights.
◼
3. Repeat step 2 with p3, p4, …in place of p2, until the components of the partition are exhausted.
The method above will obviously fail for a partition of the form (1, 1, …); this can be treated instead as equivalent to the singleton partition.
The following function is one possible implementation of this idea:
In[]:=
randomAlkane[n_Integer]/;n>3:=Module[{adj,rpart,sp,tp},rpart=randomIntegerPartition[n];If[MatchQ[rpart,{1..}],rpart={n}];adj=SparseArray[{Band[{1,2},rpart[[1]]-{1,0}]1,Band[{2,1},rpart[[1]]-{0,1}]1},{n,n}];tp=TakeList[Range[rpart[[1]]+1,n],Rest[rpart]];Do[sp=RandomSample[4-Mod[Total[adj],4,1]Range[n],rpart[[k+1]]];Scan[(adj[[#[[1]],#[[2]]]]=1;adj[[#[[2]],#[[1]]]]=1;)&,Transpose[{tp[[k]],sp}]],{k,Length[rpart]-1}];Molecule[AdjacencyGraph[adj,AnnotationRulesTable[k{"AtomicNumber"6},{k,n}]]]]
(I wrote this one in a hurry, so it's possible that this can be done even more efficiently than the current implementation.) This method seems to do well compared to the method in the Chemistry Functions paclet:
In[]:=
(*ifyoudon'talreadyhavethepacletinstalled*)PacletInstall["WolframChemistry/ChemistryFunctions"];Needs["WolframChemistry`ChemistryFunctions`"];
RepeatedTiming[Table[randomAlkane[19],{10}]]
Out[]=
0.0261589,Molecule,Molecule,Molecule,Molecule,Molecule,Molecule,Molecule,Molecule,Molecule,Molecule
RepeatedTiming[RandomAlkane[19,10]]
Out[]=
0.125988,Molecule,Molecule,Molecule,Molecule,Molecule,Molecule,Molecule,Molecule,Molecule,Molecule
Pretty good! Let's try an alkane with more atoms, and a larger sample (and also suppress the output so that we don't wreck the frontend with a display of so many molecules):
RepeatedTiming[l1=Table[randomAlkane[37],{100}];]
Out[]=
{0.418903,Null}
RepeatedTiming[l2=RandomAlkane[37,100];]
Out[]=
{1.26401,Null}
I made a capricious choice in the proposed method to bias the choice of attachment points towards less-substituted carbons, but one could also consider choosing any non-quaternary carbon with equal probability. I'll leave that experiment for someone else to do.
CITE THIS NOTEBOOK
CITE THIS NOTEBOOK
Generating random alkanes from adjacency graphs
by J. M.
Wolfram Community, STAFF PICKS, March 14, 2025
https://community.wolfram.com/groups/-/m/t/3416635
by J. M.
Wolfram Community, STAFF PICKS, March 14, 2025
https://community.wolfram.com/groups/-/m/t/3416635