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

"""Twin Generation -- Part K of the Twinned FCC walkthrough.

Introduces primary + secondary twins into the oriented, host-allocated
base structure, then cleans the result (removes spike voxels / splits
non-convex lobes left behind by the voxel carving).
"""


[docs] def generate_twins(base, rg, parent_info, assigner, twin_thickness, vf_targets=None, csl_label="S3 (twin)", n_lamellae_per_host=10, nucleation_site='gb_centroid', meshing_route='conformal', min_thick_conformal=2, min_thick_nonconformal=1, min_host_vox=8, tvf_tolerance=0.05, tvf_2d_to_3d_scale=1.15, thick_scale_factor=0.80, max_thick_um=None, max_vf_per_host=0.50, prob_separated=0.30, prob_contacting=0.70, prob_sec_outward=0.60, prob_sec_inward=0.40, schmid_dir=(0.0, 0.0, 1.0), host_schmid_weight=1.0, use_schmid=True, orient_scatter_deg=1.5, rng_seed=123): """Introduces primary twins (at host grain-boundary centroids by default) then secondary twins (nucleating inward/outward from the primaries), targeting the EBSD twin-area-fraction (TVF) for `csl_label`. `twin_thickness`: the dict from steps_ebsd_analysis_2.compute_twin_thickness -- Twin Generation reuses that measurement rather than recomputing it (including whatever abrupt-boundary threshold it was computed with). `assigner`: the result of steps_orientation_assignment.assign_orientations. `vf_targets`: the dict from steps_ebsd_analysis_2.compute_vf_partition ('tvf_stage1'/'tvf_secondary_2a'/'tvf_secondary_2b'). Passing this is what makes primary-twin introduction stop at its own Stage-1 share of the total EBSD twin fraction, instead of the combined overall fraction -- without it, primary twins alone can reach the full target and secondary introduction never triggers (0 secondary twins). None (default) falls back to that combined-fraction behaviour. `max_thick_um`: None (default) means no cap; pass a number to enable one. Returns ------- TwinGenerator3D : `tg` -- tg.lgi_twinned/tg.all_quats/tg.twin_role/ tg.twin_parent_of feed steps_twin_generation.clean_structure(); tg.summary()/tg.diagnose(tvf) give the achieved-vs-target report. """ from upxo.pxtal.twinned_simple_3d.twin_generator_3d import TwinGenerator3D tvf = rg.compute_ebsd_tvf(parent_info, csl_label=csl_label) tg = TwinGenerator3D( base, n_lamellae_per_host=n_lamellae_per_host, twin_nucleation_site=nucleation_site, tvf_tolerance=tvf_tolerance, twin_orient_scatter_deg=orient_scatter_deg, meshing_route=meshing_route, min_lamella_thickness_conformal=min_thick_conformal, min_lamella_thickness_non_conformal=min_thick_nonconformal, min_host_vox_for_lamella=min_host_vox, prob_lamella_separated=prob_separated, prob_lamella_contacting=prob_contacting, prob_secondary_outward_twinNucleation=prob_sec_outward, prob_secondary_inward_twinNucleation=prob_sec_inward, twin_thick_scale_factor=thick_scale_factor, tvf_2d_to_3d_scale_factor=tvf_2d_to_3d_scale, max_lamella_thickness_um=max_thick_um, max_lamella_vf_per_host=max_vf_per_host, schmid_loading_direction=schmid_dir, host_schmid_weight=host_schmid_weight, use_schmid_for_variant_selection=use_schmid, rng_seed=rng_seed, ) tvf_stage1 = vf_targets['tvf_stage1'] if vf_targets else None tvf_2a = vf_targets['tvf_secondary_2a'] if vf_targets else None tvf_2b = vf_targets['tvf_secondary_2b'] if vf_targets else None tg.introduce_primary_twins( host_orientations=assigner.all_grain_orientations, twin_thickness=twin_thickness, tvf=tvf, tvf_stage1=tvf_stage1) tg.introduce_secondary_twins( tvf=tvf, twin_thickness=twin_thickness, tvf_2a=tvf_2a, tvf_2b=tvf_2b) return tg, tvf
[docs] def clean_structure(tg, min_voxels=0, n_passes=5, upscale_fallback=False, split_jitter_deg=0.0, rng_seed=7, do_spike_removal=True, do_lobe_split=True, verbose=True): """Topology Cleaning -- removes spike voxels and splits non-convex ("lobed") grains the twin-carving step can leave behind, recursively up to `n_passes` (capped at 20 as a safety limit). Returns ------- (cleaner, n_passes_run) : `cleaner` (StructureCleaner3D) carries lgi_clean/all_quats_clean/twin_role_clean/twin_parent_of_clean -- pass it into steps_post_twin_validation's functions. """ from upxo.pxtal.twinned_simple_3d.cleaning_3d import StructureCleaner3D n_passes = min(n_passes, 20) return StructureCleaner3D.clean_recursive( tg.lgi_twinned, tg.all_quats, tg.twin_role, tg.twin_parent_of, n_passes=n_passes, upscale_fallback=upscale_fallback, split_jitter_deg=split_jitter_deg, rng_seed=rng_seed, min_clean_voxels=min_voxels, do_spike_removal=do_spike_removal, do_lobe_split=do_lobe_split, verbose=verbose)