Quickstart Tutorial¶
This tutorial builds a cube-like WulffPack nanoparticle, converts it into a voxel coordination-surface mask, samples trial positions from that mask, and runs a minimal Monte Carlo loop. The default example uses ASE EMT as a small, local potential-energy scorer. ORB-V3 can be selected when the optional ORB dependencies are installed.
The complete script is available at
examples/mc/orb_v3_wulff_mc.py.
Install Tutorial Dependencies¶
AtomVoxelizer provides the grid machinery. This tutorial also uses ASE and WulffPack to build the nanoparticle:
pip install AtomVoxelizer ase wulffpack
For ORB-V3 scoring, install ORB and PyTorch in the environment you use for simulation. The example keeps that import optional because loading ORB can download weights and initialize accelerator libraries.
Build A Cube-Like WulffPack Nanoparticle¶
WulffPack creates a finite fcc particle from relative surface energies. The
natoms argument is a target; the final atom count can differ because the
particle is built from symmetry-compatible atomic shells.
from ase.build import bulk
from wulffpack import SingleCrystal
primitive = bulk("Pt", "fcc", a=3.92)
surface_energies = {(1, 0, 0): 1.0}
particle = SingleCrystal(surface_energies, primitive_structure=primitive, natoms=201)
atoms = particle.atoms
Using only the (100) facet creates a deliberately cube-like starting point.
That makes the MC demonstration visible: accepted moves can reduce the
spread in atom distances from the nanoparticle center.
WulffPack returns a finite cluster without a periodic simulation cell. A voxel grid needs an invertible cell, so the example translates the cluster into a padded cubic cell:
import numpy as np
padding = 6.0
positions = atoms.positions
span = positions.max(axis=0) - positions.min(axis=0)
cell_length = span.max() + 2.0 * padding
atoms.positions = positions - positions.min(axis=0) + padding
atoms.set_cell(np.eye(3) * cell_length)
The padding should be larger than the largest mask radius so periodic wrapping does not make opposite sides of the finite particle interact.
Build The Voxel Surface Mask¶
The coordination-surface mask is built with two sphere passes:
Add a larger sphere around every atom. This gives each voxel a count of how many atom-centered shells overlap it.
Set a smaller sphere around every atom back to zero. This removes atomic cores from the trial region.
import numpy as np
from ase.data import covalent_radii
from atomvoxelizer import VoxelGrid
radii = covalent_radii[atoms.numbers]
grid = VoxelGrid(atoms.cell.array, resolution=0.35, dtype=np.float32)
grid.add_spheres(atoms.positions, 1.4 * radii, value=1.0)
grid.set_spheres(atoms.positions, 1.1 * radii, value=0.0)
This is the same stencil-based operation described in Concepts. AtomVoxelizer visits the local sphere stencil around each atom instead of scanning every grid point against every atom.
Sample Trial Sites¶
For a surface trial region, sample voxels with values near three. The range
2.5 to 3.5 avoids depending on exact floating-point equality after
repeated additions.
trial_sites = []
for position in grid.sample_voxels_in_range(2.5, 3.5, min_dist=0.6, seed=7):
trial_sites.append(np.asarray(position))
if len(trial_sites) >= 500:
break
trial_sites = np.array(trial_sites)
Those positions are voxel centers in real space. They are useful as trial destinations or directions for Monte Carlo moves near the nanoparticle surface.
Minimal MC Loop¶
The example chooses likely surface atoms by radial distance, picks a local
sampled voxel trial site, and moves the atom a short distance toward that site.
Before scoring, the structure is relaxed with the selected ASE calculator. Each
trial move is also relaxed before applying the Metropolis criterion. This makes
the acceptance depend on relaxed local minima rather than on the raw trial
guess. The default score is ASE EMT; ORB-V3 can be selected with
--score orb-v3.
import math
rng = np.random.default_rng(11)
center = atoms.positions.mean(axis=0)
distances = np.linalg.norm(atoms.positions - center, axis=1)
movable = np.flatnonzero(distances >= np.quantile(distances, 0.65))
from ase.calculators.emt import EMT
atoms.calc = EMT()
# The full example relaxes the initial and trial structures before scoring.
current_score = atoms.get_potential_energy()
beta = 1.0 / (8.617333262145e-5 * 1500.0)
atom_index = int(rng.choice(movable))
target = trial_sites[int(rng.integers(len(trial_sites)))]
old_position = atoms.positions[atom_index].copy()
direction = target - old_position
direction *= min(1.0, 0.35 / np.linalg.norm(direction))
atoms.positions[atom_index] = old_position + direction
trial_score = atoms.get_potential_energy()
delta = trial_score - current_score
accept = delta <= 0.0 or rng.random() < math.exp(-beta * delta)
if not accept:
atoms.positions[atom_index] = old_position
In an ORB-V3 MC workflow, the voxel part stays the same. Only the scoring function changes: evaluate the old and trial structures with ORB-V3, then apply the usual Metropolis criterion to the energy difference.
Run The Example¶
Run the EMT tutorial example with:
python examples/mc/orb_v3_wulff_mc.py --natoms 201 --resolution 0.35 \
--shell-scale 1.25 --core-scale 1.05 \
--steps 250 --score emt --temperature 1500 \
--relax-fmax 0.05 --relax-steps 50 \
--plot quickstart_wulff_mc_sites.png \
--state-plot docs/source/_static/quickstart_wulff_mc_initial_final.png
The script prints the accepted move count, acceptance ratio, initial/final radial variance, and mean/max displacement from the starting structure so you can confirm that atoms actually moved during the run. It also writes an ASE trajectory by default:
examples/mc/orb_v3_wulff_mc.traj
The image below shows the final relaxed nanoparticle state from the MC run.
Open it with ASE to inspect the MC path:
ase gui examples/mc/orb_v3_wulff_mc.traj
The first frame is the starting cube-like particle. Each following frame is the
relaxed structure after one MC step; rejected steps repeat the previous relaxed
coordinates but still carry updated Atoms.info metadata such as
mc_accepted and mc_score. Pass --trajectory "" to skip writing frames, or
--trajectory path/to/file.traj to choose a different output path.
To try the optional ORB-V3 scorer after installing ORB and its model dependencies:
python examples/mc/orb_v3_wulff_mc.py --score orb-v3 --device cpu --steps 10
The ORB helper is intentionally isolated in the example script. ORB package APIs, weights, and accelerator setup can change independently of the voxel-grid workflow.