Nanocrystalline graphite (sp²)

C, 40 × 40 × 40 Å, regime preset "sp2_nc" (100% sp² regime). All grains sampled from graphite (P6₃/mmc, a = 2.467 Å, c = 6.708 Å). In-plane C-C bonds at 1.42 Å with 120° trigonal angles dominate; the inter-layer 3.4 Å spacing is preserved through the repulsion term.

Orientation-refinement movie

Each frame is one accepted grain rotation; the schedule walks 30° → 15° → 5° → 2° and accepts the best of 50 trials per (amplitude, grain). Discrete steps, no FIRE between accepts.

FIRE quench movie

Continuous atomic relaxation after rotation refinement. sp³ tetrahedra are detected at the final state and rendered through the playback (locked-index polyhedra following the trajectory).

Cost trace: refinement + FIRE

Total / bond / angle / repulsion components. Left of the dashed line: rotation refinement (one point per accepted rotation). Right: FIRE convergence (downsampled).

Cost trace for C Nanocrystalline graphite (sp²)

g3 distribution: initial · after refine · after FIRE

Three rooted three-body distributions captured at three points along the pipeline so the algorithmic effect of each stage is visible.

Initial (post-build, post-retile): grain interiors are perfect crystal slabs at random orientations.

After refinement (pre-FIRE): SO(3) coordinate descent has walked each grain into a better-aligned basin against its neighbours. Differences from the initial g3 are concentrated at grain boundaries.

After FIRE (final): all atoms relaxed. A small post-FIRE thermal jitter (σ scaled by regime grain density) is applied before measurement so the peaks have realistic finite-T width rather than the perfectly sharp 0K-FIRE-quench result.

Code

from ase.io import read
import tricor as tc

atoms_graphite = read("docs/structures/C_graphite.cif")
atoms_diamond  = read("docs/structures/C_diamond.cif")
shell_sp2 = tc.CoordinationShellTarget.from_atoms(atoms_graphite, phi_num_bins=90)
shell_sp3 = tc.CoordinationShellTarget.from_atoms(atoms_diamond,  phi_num_bins=90)
shell_target = tc.CoordinationShellTarget.from_targets(
    {"sp2": shell_sp2, "sp3": shell_sp3}
)

cell = tc.Supercell.from_atoms(
    atoms_graphite,
    cell_dim_angstroms=(40, 40, 40),
    r_max=10, r_step=0.1, phi_num_bins=90,
    rng_seed=42,
)
cell.generate(
    shell_target,
    grain_size=10.0,
    grain_sources=[
        {"atoms": atoms_graphite, "species_offset": 0, "weight": 1.0},
        {"atoms": atoms_diamond,  "species_offset": 1, "weight": 0.0},
    ],
    num_steps=120,
    bond_weight=2.0, angle_weight=1.0, repulsion_weight=2.0,
    hard_core_scale=0.9, nonbond_push_scale=0.8,
    displacement_sigma=0.03,
    refine_orientations=True,
    refine_orientations_kwargs=dict(
        amplitudes_deg=(30.0, 15.0, 5.0, 2.0),
        trials_per_amplitude_per_grain=50,
        max_rounds_per_amplitude=2,
        cost_function="pair_distance",
        score_cutoff_factor=1.5,
        time_budget_sec=180.0,
        capture_trajectory=True,
    ),
    capture_trajectory=True,
)