Roly.jl (Reverse-Search Polyform Enumerator) is a Julia package for the enumeration of arbitrary polyforms via reverse search. It makes it possible to exhaustively enumerate polyforms (aggregates formed by connecting arbitrarily shaped building blocks at their binding sites) in 2D or 3D and provides an interface to define your own building block geometries and binding rules. Roly.jl is under active development, and breaking changes can occur at any time. Because of its dependencies, Roly.jl currently does not work on Windows.
To install Roly.jl directly from your Julia REPL, first press ] to enter Pkg mode, and then run
pkg> add https://github.com/goodrichgroup/Roly.jl
Enumeration in Roly.jl starts from a BindingRules object, which is a list of building block geometries together with an interaction matrix that specifies which binding sites are allowed to bind to each other.
The allowed polyforms can then be enumerated with polyenum, or generated and stored with polygen.
To illustrate the basic process, let's construct a system consisting of four species of triangular building blocks. Binding rules are defined as a list of bonds, where every bond is specified in the form [species_i site_i species_j site_j]. For example, [1 3 2 3] indicates that site 3 of species 1 is allowed to bind to site 3 of species 2. Roly already comes with definitions for simple polygonal particle geometries (e.g. UnitTriangle, UnitSquare, UnitHexagon), convex polyhedra (e.g. UnitCube, UnitPrism(n)), as well as patchy particles (e.g. PatchyDisk, PatchySphere). The BindingRules constructor takes either a list of geometries or a single geometry if all building blocks are identically shaped.
using Roly
bonds = [1 3 2 3;
2 2 3 2;
2 1 4 1;
3 1 4 1]
rules = BindingRules(bonds, UnitTriangle)Once you have defined a set of binding rules, use polyenum to enumerate all allowed polyforms:
result = polyenum(rules; maxsize=20, maxstrs=100_000)
result.nstructures # number of polyforms found
result.largest_size # size of the largest polyform found
result.status # Finished, MaxDepthReached, MaxVerticesReached, or BreakTriggeredThe simple system we have chosen here only allows 16 different polyforms to form. In general however, the number of polyforms might be unbounded, so it is advisable to always impose either a maximal size (maxsize) or a maximal count (maxstrs). To store all polyforms in memory for further processing, use polygen, which returns a list sorted by size:
strs = polygen(rules; maxsize=20, maxstrs=100_000)To count polyforms without storing them, or to estimate the number of polyforms when full enumeration is too expensive, use countpolyforms:
c = countpolyforms(rules)
c.n # count (exact or estimated mean)
c.exact # true if the count is exact
c.uncertainty # standard error of the estimate (0 if exact)countpolyforms enumerates exactly up to a configurable budget and switches to estimation beyond it. Pass maxsize for systems that allow unbounded growth.
It is often desirable to impose additional constraints on generated polyforms. For example, to enumerate only polyforms with at most one particle of species 4:
constraint(s, n) = composition(s)[4] <= 1 ? ACCEPT : REJECT
polyenum(constraint, rules)Warning: To ensure well-defined behavior, if a polyform s violates the constraint, all larger polyforms that can be generated by adding particles to s must also violate the constraint.
Roly.jl provides a Makie extension. Load any Makie backend to activate it:
using GLMakie # or CairoMakie, WGLMakie, ...
render(s) # display a single polyform, or a speciesrender picks a 2D or 3D axis to match. polyformplot! can be used to draw onto an existing Makie axis.
If you use Roly.jl in your work, please cite our paper below:
@article{roly2025,
year = {2025},
title = {{Accessing Semiaddressable Self-Assembly with Efficient Structure Enumeration}},
author = {Hübl, Maximilian C. and Goodrich, Carl P.},
journal = {Physical Review Letters},
issn = {0031-9007},
doi = {10.1103/physrevlett.134.058204},
pages = {058204},
number = {5},
volume = {134},
keywords = {}
}