Builder¶
System construction. mp.builder mirrors molrs.builder by identity —
site-graph assembly (Assembler with its placers and orienter), the
Coarsener, and the graphene / nanotube builders — and adds molpy's own: the
polymer planning layer, crystals, virtual sites, PackingTemplate, and the
AmberTools polymer builder, which makes a GAFF chain without the assembler:
prepgen cuts one antechamber-typed oligomer and tleap sequence joins the
residues. Every name has one path, mp.builder.<Name>; the modules behind it
are private.
Quick reference¶
| Symbol | Summary | Preferred for |
|---|---|---|
mp.builder.Assembler |
Assembler(library, placer, orienter=None).assemble(sites, cls=None): one copy of library[bead_type] per site of an mp.CoarseGrain, each site bond joining one accepting port of each end; returns the world as cls (mp.MolGraph by default) |
Every site-graph build: chains, blocks, rings, stars, combs, backmapping |
mp.builder.GrowthPlacer |
Grows each molecule breadth-first onto its parent's ports; needs no site positions | Topologies from CGsmiles notation |
mp.builder.SitePlacer |
Puts each copy's centre of mass on its site | Sites with positions (a coarsened CG model) |
mp.builder.AxisOrienter |
Turns each copy onto its site: backbone-to-centre along the site axis and joining atoms along the bonds for chain units; port directions fitted to bond directions for branch units | Backmapping with SitePlacer |
mp.io.cgsmiles.CgSmilesIr |
.templates() → name → ported mp.Atomistic; .to_coarsegrain() → site graph |
Writing units and topologies |
mp.io.smiles.read_smiles_fragment |
read_smiles_fragment(body).to_template() → one ported mp.Atomistic from a fragment body |
Writing one unit |
mp.perceive.SubgraphMatcher / mp.builder.Coarsener |
Find bead groups in a CG model; turn them into sites with a position and an axis | Site graphs from a CG model |
SystemPlanner / PolydisperseChainGenerator / Chain / SystemPlan |
Sample a polydisperse chain plan to a target mass | Bulk / MW-distributed systems |
SchulzZimmPolydisperse / FlorySchulzPolydisperse / PoissonPolydisperse / UniformPolydisperse |
Chain-length distributions (MassDistribution / DpDistribution protocols) |
Choosing a molecular-weight distribution |
WeightedSequenceGenerator / BlockSequenceGenerator / AlternatingSequenceGenerator |
Monomer sequences (SequenceGenerator protocol) |
Copolymer composition |
CarbonTubeBuilder |
CarbonTubeBuilder(n, m, ...) → .build() Frame + .cell() box |
Zigzag, armchair, and chiral nanotubes |
GrapheneBuilder |
GrapheneBuilder(nx, ny, ...) → .build() Frame + .cell() box |
Rectangular graphene honeycomb sheet |
Lattice / Site / SpaceGroup |
Lattice(box, basis): a mp.Box unit cell (lattice vectors are the columns of box.h) plus basis sites in fractional coordinates; Lattice.from_cif(frame) expands a CIF's asymmetric unit by the operators read_cif keeps; .build(region, repeats=...) tiles it |
Crystals |
mp.core.Cuboid / mp.core.Sphere / mp.core.HalfSpace / mp.core.Region |
Native geometric regions with mask(block), on the molpy root |
Selecting a volume, clipping a crystal |
DrudeBuilder / Tip4pBuilder / VirtualSiteBuilder |
Virtual-site augmentation (Drude parameters from mp.ff.params.clpol_polarizability) |
Polarizable / 4-site models |
AmberPolymerBuilder |
AmberPolymerBuilder(library, cuts, parameter_set="gaff2").assemble(sites) → AmberBuildResult (chain, forcefield, prmtop / inpcrd paths): antechamber (once, -fo ac) + parmchk2 on each oligomer, prepgen per residue, tleap sequence over a linear site graph; needs AmberTools |
GAFF / GAFF2 polymer chains; forcefield merges with a natively typed (GaffTypifier) small molecule's |
AmberCut |
One prepgen residue: omit, head / tail connection atoms, pre_head / post_tail (or *_type), charge |
Cutting an oligomer you built |
AmberPieces |
AmberPieces(head, repeat, tail).oligomer() → the embedded oligomer and its head / chain / tail cuts, from three SMILES |
Writing the oligomer and cuts from SMILES |
The crystal Site is a lattice basis site; it is unrelated to the sites of an
assembly site graph.
Canonical example¶
A unit is a CGsmiles fragment whose bonding descriptors are its ports (<
joins >, $ joins $, ! joins !; labels and bond orders must match),
embedded by mp.conformer.Conformer. The topology is a CGsmiles string turned into a
site graph. GrowthPlacer needs no coordinates.
import molpy as mp
conformer = mp.conformer.Conformer(seed=42)
eo = conformer.generate(
mp.io.smiles.read_smiles_fragment("[<]OCC[>]").to_template()
)[0]
assert eo.n_ports == 2
sites = mp.io.cgsmiles.read_cgsmiles("{[#EO]|5}").to_coarsegrain()
assembler = mp.builder.Assembler({"EO": eo}, mp.builder.GrowthPlacer())
chain = assembler.assemble(sites, mp.Atomistic)
atoms = chain.to_frame()["atoms"]
assert sorted(set(atoms["frag_id"].tolist())) == [0, 1, 2, 3, 4] # one per site
assert set(atoms["mol_id"].tolist()) == {1} # one connected component
assert chain.n_ports == 2 # the chain ends keep their ports and hydrogens
Each bond joins one accepting port of each end, chosen by the assembler; the
leaving handles are removed and their charge folds onto the anchors. A build
that cannot give every bond a port raises ValueError naming the site. For a
site graph coarsened from a CG model, use
mp.builder.Assembler(library, mp.builder.SitePlacer(), mp.builder.AxisOrienter()) — see
Guide: Assembly.
Topology when needed¶
The assembled world has atoms and bonds only. Generate the angle and dihedral topology once, when an MD writer needs it:
assert not list(chain.angles)
chain.generate_topology(gen_angle=True, gen_dihedral=True)
assert list(chain.angles)
A walk of architectures from one ethylene-oxide kit — linear, block, ring,
star, comb, telechelic — is the user-guide section
Polymer Topologies (paired with
examples/topology/). Statistical crosslinking of melts is not available.
Nanostructure topology¶
The nanostructure builders are molrs's and keep their lattice planning
private. They expose two products: the structure as a Frame, and the
simulation cell it was laid out in; mp.Atomistic.from_frame makes the frame
a graph.
from molpy.builder import CarbonTubeBuilder
tube_builder = CarbonTubeBuilder(6, 6, cells=2, periodic=True)
tube = mp.Atomistic.from_frame(tube_builder.build())
assert len(tube.atoms) == 48
assert len(tube.bonds) == 72
assert tube_builder.cell().pbc.tolist() == [False, False, True]
The same builder accepts zigzag (n, 0), armchair (n, n), and general chiral
(n, m) tubes. See Nanostructures for
open ends, length selection, and deferred topology.
Crystals¶
A Lattice is a mp.Box unit cell and a basis of Sites in fractional
coordinates. The box's cell matrix box.h holds the lattice vectors as
columns, so lattice.a1 is lattice.box.h[:, 0], and the box does every
fractional ↔ Cartesian conversion. build tiles the basis over repeats
cells from the origin, or over every cell, negative indices included, that
reaches a region's bounding box, and keeps the atoms inside the region.
from molpy.builder import Lattice
nickel = Lattice.fcc(a=3.52, species="Ni")
assert len(nickel.build(repeats=(2, 2, 2)).atoms) == 32
cluster = nickel.build(mp.core.Sphere([0.0, 0.0, 0.0], 5.0)) # centred on an atom
box = nickel.supercell((2, 2, 2)) # the tile's simulation cell
A CIF lists the asymmetric unit and the space group's operators.
mp.io.read_cif keeps the operators verbatim in frame.meta["symops"]
(mp.core.keys.SYMOPS) and the Hermann–Mauguin symbol in
frame.meta["space_group"] (mp.core.keys.SPACE_GROUP), each only when the
file states it. Lattice.from_cif builds the whole conventional cell from
that frame: the cell is frame.box, each atom of the atoms block is a site
(label from name, species from element), and every symmetry image is a
basis site labelled "<name>_<k>". Images on a special position collapse
within symprec.
# docs: skip — reads an offline CIF; Lattice.from_cif is unit-tested with fixtures
frame = mp.io.read_cif("NaCl.cif") # Fm-3m: Na1 at 4a, Cl1 at 4b
salt = Lattice.from_cif(frame)
assert len(salt.basis) == 8
structure = salt.build(repeats=(4, 4, 4))
frame = structure.to_frame()
frame.box = salt.supercell((4, 4, 4))
from_cif refuses a frame it would have to guess about. With no box, or no
symops because the file has no operator loop, it raises ValueError naming
what is missing rather than assume P1; list x,y,z in the file for a P1
structure. A partially occupied site (an occupancy below 1) is rejected
too, because a lattice places one atom per site: resolve the disorder in the
file first.
Without a CIF, give the cell, the asymmetric unit and a SpaceGroup, from an
explicit operator list or closed from generators:
from molpy.builder import Site, SpaceGroup
fm3m = SpaceGroup.from_generators(
["x+1/2,y+1/2,z", "x+1/2,y,z+1/2", "-x,-y,z", "-x,y,-z", "z,x,y", "y,x,-z", "-x,-y,-z"]
)
assert fm3m.order == 192
salt = Lattice.from_spacegroup(
mp.Box(mp.core.Cell.lengths([5.64] * 3)),
[Site("Na1", "Na", (0.0, 0.0, 0.0)), Site("Cl1", "Cl", (0.5, 0.5, 0.5))],
fm3m,
)
assert len(salt.basis) == 8
Polydisperse systems¶
Sample a chain plan, then build each chain's monomer sequence as a CGsmiles path with the same assembler:
import numpy as np
from molpy.builder import (
PolydisperseChainGenerator,
SchulzZimmPolydisperse,
SystemPlanner,
WeightedSequenceGenerator,
)
planner = SystemPlanner(
PolydisperseChainGenerator(
WeightedSequenceGenerator({"EO": 1.0}),
{"EO": 44.05},
distribution=SchulzZimmPolydisperse(1500, 3000),
),
target_total_mass=5e3,
)
plan = planner.plan_system(np.random.default_rng(42))
chains = [
assembler.assemble(
mp.io.cgsmiles.read_cgsmiles(
"{" + "".join(f"[#{m}]" for m in chain.monomers) + "}"
).to_coarsegrain(),
mp.Atomistic,
)
for chain in plan.chains[:2]
]
assert len(chains) == 2
plan_system returns a plan whose total mass is within max_rel_error of the
target, or raises RuntimeError saying how far short it fell and why (trimming
off, no shorter chain fits, or max_chains reached). A mass distribution's
sampled mass becomes one whole generate_sequence(dp) call, so alternating and
block generators keep their pattern.