Skip to content

Add atomistic polycrystal generation with curved (APD) grain boundaries#7

Open
ligerzero-ai wants to merge 2 commits into
mbuze:mainfrom
ligerzero-ai:atomistic-polycrystal
Open

Add atomistic polycrystal generation with curved (APD) grain boundaries#7
ligerzero-ai wants to merge 2 commits into
mbuze:mainfrom
ligerzero-ai:atomistic-polycrystal

Conversation

@ligerzero-ai

@ligerzero-ai ligerzero-ai commented Jun 14, 2026

Copy link
Copy Markdown

Add atomistic polycrystal generation with curved (APD) grain boundaries

Motivation

With periodic APDs available, an APD tessellation can be turned directly into a periodic atomistic polycrystal: each grain is a randomly oriented crystal lattice, and the grain boundaries follow the curved, anisotropy-aware APD interfaces rather than flat Voronoi planes. This produces ready-to-use periodic cells for generating MD input, with realistic curved boundaries and per-grain crystallographic orientations.

This PR adds a self-contained, crystal-agnostic generator module and an end-to-end driver that writes a LAMMPS data file.

Summary of changes

New module PyAPD/polycrystal_atomistic.py (re-exported from PyAPD/__init__.py):

  • generate_polycrystal(cell, basis, n_atoms_target, n_grains, ...) — general driver. Takes an orthorhombic conventional cell (a scalar for cubic, or (ax, ay, az)) plus a fractional basis, so it builds any lattice: cubic (SC/BCC/FCC/diamond/B2/…) and tetragonal/orthorhombic/HCP. Builds a periodic APD over the box, optionally runs Lloyd iterations, samples a uniform-random orientation per grain, fills each grain, culls overlaps, and returns a bundle (positions, grain_ids, box (3-vector), cells, cell, basis, rotations, seeds, and the apd_system).
  • Convenience wrappers: generate_bcc_polycrystal(a, …), generate_fcc_polycrystal(a, …), generate_sc_polycrystal(a, …), and generate_hcp_polycrystal(a, c, …) (HCP via a C-centred orthorhombic supercell).
  • min_interatomic_distance(cell, basis) — nearest-neighbour spacing of the lattice, used to set the cull cutoff automatically (override via nn_distance=).
  • bulk_lattice_positions(cell, basis, counts) / bulk_bcc_positions(a, m) — un-rotated reference lattices.
  • shoemake_uniform_so3(rng) — uniform-random SO(3) rotation (Shoemake's quaternion method).
  • pbc_pair_cull(positions, grain_ids, cutoff, box, cull_same_grain=False) — removes one atom of every too-close pair under PBC (per-axis box) using a cKDTree.
  • write_lammps_data(...)atomic-style LAMMPS writer (accepts a scalar or per-axis box).
  • A python -m PyAPD.polycrystal_atomistic --crystal {sc,bcc,fcc,hcp} CLI.

Space-filling grain-fill design (the key part)

The naive approach — generate one box-sized block, rotate it about the grain seed, and wrap by the box — is not space-filling for a generic rotation. Rotating a finite block and wrapping leaves seam planes through each grain with paired overlaps and matching gaps, so culling the overlaps leaves interior voids (roughly the cull fraction) distributed through grain interiors.

Instead, each grain is filled by the preimage of the box under that grain's rotation: the 8 box corners are inverse-rotated about the seed to bound a lattice block large enough (with a small margin) to cover the box after forward rotation; the block is rotated forward without wrapping; in-box atoms are kept; and apd.grain_of() (from the periodic-APD PR) assigns each atom to a grain so only atoms actually owned by grain g are retained. This is dense everywhere inside each grain; only true cross-grain grain-boundary overlaps and the unavoidable periodic-seam duplicates are then removed by pbc_pair_cull at cull_factor · nn_distance. The result is a periodic, space-filling polycrystal with no interior voids.

Backward compatibility

Additive only. This PR introduces a new module and new public names; it changes no existing behaviour or signatures.

How it was tested

New CPU-only test module tests/test_polycrystal_atomistic.py:

  • Reference lattices (BCC/FCC and an orthorhombic cell) have the right atom count, stay in-box, and contain the expected sublattices.
  • min_interatomic_distance returns the exact NN spacing for SC (a), BCC (a√3/2), FCC (a/√2), and HCP (a), including orthorhombic cells.
  • shoemake_uniform_so3 returns proper rotations (RᵀR = I, det = +1) and is unbiased (Haar mean ≈ 0 over 1000 samples).
  • pbc_pair_cull keeps same-grain pairs, drops cross-grain overlaps, and respects PBC for both cubic and per-axis (orthorhombic) boxes.
  • Box-sizing produces the reference cubic box and a near-cubic physical box for anisotropic cells.
  • End-to-end generation for BCC, FCC, SC (cubic) and HCP (orthorhombic): validates bundle structure, in-box bounds, full grain coverage, and that no atom pair sits below the cull cutoff.
  • Determinism: identical seed → identical positions and grain IDs.
  • LAMMPS round-trip (skipped if ASE is not installed): a generated cell is written and re-read with ASE (lammps-data, atomic), confirming atom count, per-axis cell lengths, and bounds.

A tutorial notebook, notebooks/tutorials/periodic_polycrystal_bcc_fe.ipynb, walks through generating and inspecting a periodic BCC-Fe polycrystal end-to-end, with a closing section showing the FCC/HCP/custom-lattice wrappers.

Not included

  • Single conventional cell + basis per call (one phase / Bravais lattice); multi-phase composites are out of scope.
  • Orthorhombic (axis-aligned) periodic boxes only — matching the periodic-APD scope; no triclinic cells. (HCP is handled via its C-centred orthorhombic supercell.)
  • Output is the LAMMPS atomic-style data file; other writers/formats are out of scope.
  • No relaxation/energy minimisation of the boundary atoms — the generator produces geometry only.

Dependencies

This PR depends on #5 (periodic boundary support + the public grain_of() query) and is built on top of its branch. Please review/merge that first; until it merges, this PR's diff also contains those commits.

ligerzero-ai and others added 2 commits June 14, 2026 14:09
Introduce an opt-in `periodic` flag on `apd_system`. When enabled, every
seed-to-point distance uses the minimum-image convention across the
rectilinear domain, so the anisotropic power diagram becomes periodic and
grains may wrap across the domain boundary.

- new `periodic=False` constructor flag (fully backward-compatible)
- `_displacement(y, x)` helper computing the minimum-image displacement as
  a KeOps LazyTensor via `dy - L * round(dy / L)`
- all four `D_ij` computations (assemble_apd, OT_dual_function,
  check_optimality, adjust_X) route through `_displacement`
- new public `grain_of(points)` query returning the owning grain index for
  arbitrary points, periodic-aware
- `adjust_X` (Lloyd step) wraps periodic centroids back into the box
- CPU tests in tests/test_apds_periodic.py

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add PyAPD/polycrystal_atomistic.py, which turns a periodic anisotropic
power diagram into a space-filling, periodic atomistic polycrystal and
writes a LAMMPS atomic data file ready for MD relaxation. The generator is
crystal-agnostic: it takes an orthorhombic conventional cell plus a
fractional basis, so it works for cubic crystals (SC/BCC/FCC/diamond/B2/...)
and for tetragonal/orthorhombic/HCP (HCP via a C-centred orthorhombic cell).

Core: `generate_polycrystal(cell, basis, n_atoms_target, n_grains, ...)`:
- builds a periodic APD (apd_system(periodic=True)) over an orthorhombic box
- samples a uniform-random SO(3) rotation per grain (Shoemake's method)
- fills each grain by the lattice block that is the PREIMAGE of the box
  under that grain's rotation, rotated forward WITHOUT wrapping. A naive
  rotate-a-finite-box-and-wrap fill is not space-filling for a generic
  rotation: it leaves paired overlaps and matching interior voids. The
  preimage-block fill is dense everywhere; only true cross-grain overlaps
  and periodic-seam artifacts are then culled (pbc_pair_cull via a cKDTree).
  The cull cutoff defaults to 0.85x the lattice nearest-neighbour distance,
  computed automatically from (cell, basis).

Convenience wrappers: generate_bcc/fcc/sc_polycrystal(a, ...) and
generate_hcp_polycrystal(a, c, ...). Also exposes bulk_lattice_positions,
bulk_bcc_positions, shoemake_uniform_so3, pbc_pair_cull,
min_interatomic_distance, write_lammps_data, and a
`python -m PyAPD.polycrystal_atomistic --crystal {sc,bcc,fcc,hcp}` CLI.
Includes unit tests (cubic + orthorhombic) and a tutorial notebook. Builds
on the periodic-APD support added in the preceding commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ligerzero-ai ligerzero-ai requested a review from mbuze as a code owner June 14, 2026 12:35
@mbuze

mbuze commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Looks great, cheers, will get back to you on that when I have time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants