Source code for upxo.pxtal.twinned_simple_3d.steps.steps_orientation_assignment

"""Orientation Assignment -- Part J of the Twinned FCC walkthrough.

Assigns a crystal orientation (quaternion) to every grain in the
host-allocated base structure, using EBSD-derived pools (parent-grain
quaternions for hosts, a fallback pool -- random by default -- for
non-hosts), resolving twin-pair conflicts per the selected mode.
optimize_mapping() below runs many mode/seed combinations and scores each
against a real EBSD pole-figure reference, keeping the best-matching
assignment instead of a single, unscored run.
"""


[docs] def assign_orientations(base, rg, parent_info, csl_label="S3 (twin)", ori_mode='conflict_free', rng_seed=42, connectivity=6, n_fallback=500, fallback_tc_info=None, fallback_apply_symmetry=None, mdf_n_bins=65, mdf_angle_max=65.0, pair_similarity_deg=10.0, max_retries=50, mrf_max_sweeps=20, mrf_eps_convergence=0.3, mrf_eps_quality=1.5, mrf_init_mode='mdf_analytical', mrf_kde_threshold=None, mrf_bilateral_symmetry=False, mrf_sa_t_start=5.0, mrf_sa_t_end=0.05): """Runs one full orientation assignment. `ori_mode='conflict_free'` is the simplest/fastest mode -- the mrf_*/pair_similarity_deg/ max_retries parameters only matter for the other modes ('paired_pool', 'mdf_conditioned_pairs', 'mdf_analytical', 'mrf_gibbs', 'mrf_map'), but are always accepted regardless of which mode is active. Returns ------- OrientationAssigner3D : `assigner` -- assigner.n_conflicts and its assigned quaternions/grain-orientation maps are the results; pass this straight into steps_twin_generation's functions. """ from upxo.pxtal.twinned_simple_3d.orientation_3d import run_orientation_assignment return run_orientation_assignment( base=base, rg=rg, parent_info=parent_info, csl_label=csl_label, ori_mode=ori_mode, rng_seed=rng_seed, connectivity=connectivity, n_fallback=n_fallback, fallback_tc_info=fallback_tc_info, fallback_apply_symmetry=fallback_apply_symmetry, mdf_n_bins=mdf_n_bins, mdf_angle_max=mdf_angle_max, pair_similarity_deg=pair_similarity_deg, max_retries=max_retries, mrf_max_sweeps=mrf_max_sweeps, mrf_eps_convergence=mrf_eps_convergence, mrf_eps_quality=mrf_eps_quality, mrf_init_mode=mrf_init_mode, mrf_kde_threshold=mrf_kde_threshold, mrf_bilateral_symmetry=mrf_bilateral_symmetry, mrf_sa_t_start=mrf_sa_t_start, mrf_sa_t_end=mrf_sa_t_end, cancel_event=None, prebuilt_pools=None)
[docs] def optimize_mapping(base, rg, parent_info, csl_label="S3 (twin)", selected=(('conflict_free', 5), ('paired_pool', 5)), base_seed=42, pole_family='100', resolution='Medium', unit_normalize=True, connectivity=6, n_fallback=500, fallback_tc_info=None, fallback_apply_symmetry=None, mdf_n_bins=65, mdf_angle_max=65.0, pair_similarity_deg=10.0, max_retries=50, mrf_max_sweeps=20, mrf_eps_convergence=0.3, mrf_eps_quality=1.5, mrf_init_mode='mdf_analytical', mrf_kde_threshold=None, mrf_bilateral_symmetry=False, mrf_sa_t_start=5.0, mrf_sa_t_end=0.05): """Sweeps orientation-assignment mode/seed combinations, scoring each run's synthetic host-grain pole figure against the real EBSD "pure parents" reference for `csl_label` (grains that, in the EBSD data, always host this CSL type's twin and are never one themselves), via the interquartile range (IQR) of their cell-by-cell MUD-grid difference -- smaller IQR means a tighter match. `unit_normalize=True` (default) compares pattern shape rather than absolute texture strength. `selected`: an iterable of (mode, n_iterations) pairs. Available modes: 'conflict_free', 'paired_pool', 'mdf_conditioned_pairs', 'mdf_analytical', 'mrf_gibbs', 'mrf_map' -- the last two are substantially slower and excluded from the default selection. Every successful run is scored; the 10 lowest-IQR runs (across every swept mode) are re-run once more so their full OrientationAssigner3D objects are available for inspection -- cheap relative to the full sweep, and avoids holding every single run's assigner in memory at once. Returns ------- (records, top10) : records is every run's score dict ('mode', 'seed', 'iqr', 'min', 'max', 'q25', 'q75', 'n_host'); top10 is the 10 lowest-IQR runs, each {**record, 'assigner': OrientationAssigner3D}, ranked ascending (index 0 = best match) -- top10[0]['assigner'] is the recommended assignment to carry into Twin Generation. """ from upxo.pxtal.twinned_simple_3d.orientation_3d import run_optimize_sweep from upxo.viz.xphy.pole_figure import PoleFigure resolution_fraction = {'Low': 0.25, 'Medium': 0.5, 'High': 1.0}[resolution] grid_points = max(int(PoleFigure.auto_grid_points(7.5) * resolution_fraction), 20) return run_optimize_sweep( base=base, rg=rg, parent_info=parent_info, csl_label=csl_label, selected=list(selected), connectivity=connectivity, n_fallback=n_fallback, fallback_tc_info=fallback_tc_info, fallback_apply_symmetry=fallback_apply_symmetry, mdf_n_bins=mdf_n_bins, mdf_angle_max=mdf_angle_max, pair_similarity_deg=pair_similarity_deg, max_retries=max_retries, mrf_max_sweeps=mrf_max_sweeps, mrf_eps_convergence=mrf_eps_convergence, mrf_eps_quality=mrf_eps_quality, mrf_init_mode=mrf_init_mode, mrf_kde_threshold=mrf_kde_threshold, mrf_bilateral_symmetry=mrf_bilateral_symmetry, mrf_sa_t_start=mrf_sa_t_start, mrf_sa_t_end=mrf_sa_t_end, pole_family=pole_family, grid_points=grid_points, unit_normalize=unit_normalize, base_seed=base_seed, cancel_event=None)
[docs] def plot_optimize_mapping_distribution(records): """Boxplot of IQR by mode across every run in `records` (from optimize_mapping()) -- lower IQR means a tighter EBSD match; compares modes and run-to-run variability within a mode at a glance. Returns ------- (fig, ax) """ import matplotlib.pyplot as plt by_mode = {} for r in records: by_mode.setdefault(r['mode'], []).append(r['iqr']) modes = list(by_mode.keys()) data = [by_mode[m] for m in modes] fig, ax = plt.subplots(figsize=(7, 5)) ax.boxplot(data, tick_labels=modes) ax.set_ylabel('IQR (lower = better match)') ax.set_title(f'Optimize Mapping -- IQR distribution by mode ({len(records)} runs)') return fig, ax