torchref.symmetry.reciprocal_symmetry module
Reciprocal space symmetry operations for structure factor grids.
This module provides efficient symmetry operations for reciprocal space data (h, k, l grids), analogous to map_symmetry.py for real space density maps.
Key concepts: - Miller indices transform as h’ = h @ R^T under rotation R - Systematic absences occur when translation causes destructive interference - Centric reflections have phases restricted to 0 or π - Friedel pairs: F(h,k,l) = F*(-h,-k,-l) for normal scattering
Main interfaces: - ReciprocalSymmetry: Factory function for grid-based reciprocal space symmetry - expand_reflections: Expand ReflectionData from asymmetric unit to P1 - expand_reciprocal_grid: Expand a reciprocal space grid from asymmetric unit to P1
Space groups can be specified as strings, integers (1-230), or gemmi.SpaceGroup objects.
- torchref.symmetry.reciprocal_symmetry.ReciprocalSymmetry(space_group, grid_shape, dtype_float=torch.float32, verbose=1, device=device(type='cpu'))[source]
Factory function to create the appropriate ReciprocalSymmetry implementation.
- Parameters:
space_group (str, int, or gemmi.SpaceGroup) – Space group specification (e.g., ‘P21’, 4, gemmi.SpaceGroup(‘P 21’)).
grid_shape (tuple of int) – Shape of the reciprocal space grid (nh, nk, nl). The grid spans from -n//2 to n//2 for each dimension.
dtype_float (torch.dtype, default: configured dtypes.float) – Floating point precision to use.
verbose (int, default 1) – Verbosity level (0=silent, 1=info, 2=debug).
device (torch.device, default: configured device.current) – Device to use for computation.
- Returns:
Implementation for reciprocal space grid symmetry operations.
- Return type:
- class torchref.symmetry.reciprocal_symmetry.ReciprocalSymmetryGrid(space_group, grid_shape, dtype_float=torch.float32, verbose=1, device=device(type='cpu'))[source]
Bases:
DeviceMixin,ModuleReciprocal space symmetry operations for Miller index grids.
Handles symmetry operations in reciprocal space including: - Miller index transformation under symmetry operations - Systematic absence detection - Centric reflection identification - Friedel pair handling - Symmetry expansion/averaging of structure factors
- In reciprocal space, symmetry operations transform Miller indices as:
(h’, k’, l’) = (h, k, l) @ R^T
where R is the rotation matrix from real space symmetry.
- symmetry
Base symmetry operations handler.
- Type:
Examples
recip_sym = ReciprocalSymmetry('P21', grid_shape=(64, 64, 64)) F_expanded = recip_sym(F_asym) # Expand from asymmetric unit F_avg = recip_sym.symmetry_average(F_full) # Average symmetry-related reflections
- __init__(space_group, grid_shape, dtype_float=torch.float32, verbose=1, device=device(type='cpu'))[source]
Initialize reciprocal space symmetry operator.
- Parameters:
space_group (str) – Space group name.
grid_shape (tuple of int) – Shape of the reciprocal space grid (nh, nk, nl).
dtype_float (torch.dtype, default: configured dtypes.float) – Floating point precision.
verbose (int, default 1) – Verbosity level.
device (torch.device, default: configured device.current) – Computation device.
- apply_to_indices(hkl, operation_index=None)[source]
Apply symmetry operation(s) to Miller indices.
- Parameters:
hkl (torch.Tensor, shape (..., 3)) – Miller indices (h, k, l).
operation_index (int, optional) – If specified, apply only this operation. If None, apply all operations.
- Returns:
Transformed Miller indices. If operation_index is None: shape (n_ops, …, 3) Otherwise: shape (…, 3)
- Return type:
- get_phase_shift(hkl, operation_index)[source]
Get phase shift for a symmetry operation on given Miller indices.
The phase shift is exp(2πi h·t) where t is the translation.
- Parameters:
hkl (torch.Tensor, shape (..., 3)) – Miller indices.
operation_index (int) – Symmetry operation index.
- Returns:
Phase shift in radians, shape (…).
- Return type:
- get_symmetry_mate(F_grid, operation_index)[source]
Apply a single symmetry operation to a structure factor grid.
- Parameters:
F_grid (torch.Tensor, shape (nh, nk, nl)) – Complex structure factor grid.
operation_index (int) – Index of the symmetry operation (0 to n_ops-1).
- Returns:
Structure factors after applying symmetry operation. Includes phase shift from translation component.
- Return type:
torch.Tensor, shape (nh, nk, nl)
- get_all_symmetry_mates(F_grid)[source]
Get all symmetry-related structure factor grids.
- Parameters:
F_grid (torch.Tensor, shape (nh, nk, nl)) – Complex structure factor grid.
- Returns:
List of symmetry-related grids.
- Return type:
list of torch.Tensor
- symmetry_average(F_grid, weights=None)[source]
Average structure factors over all symmetry equivalents.
This is useful for enforcing symmetry constraints on structure factors.
- Parameters:
F_grid (torch.Tensor, shape (nh, nk, nl)) – Complex structure factor grid.
weights (torch.Tensor, optional) – Weights for averaging, shape (nh, nk, nl). If None, equal weights are used.
- Returns:
Symmetry-averaged structure factors.
- Return type:
torch.Tensor, shape (nh, nk, nl)
- expand_to_p1(F_asym, asym_mask=None)[source]
Expand structure factors from asymmetric unit to full P1.
Takes structure factors defined on the asymmetric unit and generates the full reciprocal space by applying all symmetry operations.
- Parameters:
F_asym (torch.Tensor, shape (nh, nk, nl)) – Structure factors on asymmetric unit (other positions can be zero).
asym_mask (torch.Tensor, optional) – Boolean mask indicating asymmetric unit positions. If None, non-zero values are assumed to be the asymmetric unit.
- Returns:
Full structure factor grid with all symmetry equivalents filled.
- Return type:
torch.Tensor, shape (nh, nk, nl)
- apply_friedel(F_grid)[source]
Apply Friedel’s law: F(-h,-k,-l) = F*(h,k,l).
For normal (non-anomalous) scattering, the structure factor at -h is the complex conjugate of F(h).
- Parameters:
F_grid (torch.Tensor, shape (nh, nk, nl)) – Complex structure factor grid.
- Returns:
Structure factors with Friedel symmetry enforced.
- Return type:
torch.Tensor, shape (nh, nk, nl)
- get_epsilon()[source]
Compute epsilon (multiplicity) factors for each reflection.
Epsilon is the number of symmetry operations that map h to itself (or to its Friedel mate for acentric space groups).
- Returns:
Epsilon factors for each reflection.
- Return type:
torch.Tensor, shape (nh, nk, nl)
- forward(F_grid, mode='average')[source]
Apply symmetry to structure factor grid.
- Parameters:
F_grid (torch.Tensor, shape (nh, nk, nl)) – Complex structure factor grid.
mode (str, default 'average') – Operation mode: - ‘average’: Average over all symmetry equivalents - ‘expand’: Expand from asymmetric unit to full grid - ‘sum’: Sum all symmetry mates (for accumulation)
- Returns:
Processed structure factor grid.
- Return type:
torch.Tensor, shape (nh, nk, nl)
- torchref.symmetry.reciprocal_symmetry.expand_hkl(hkl, spacegroup, include_friedel=True, remove_absences=True, device=None)[source]
Expand Miller indices under crystallographic symmetry.
This is the core low-level function for HKL expansion. It takes Miller indices and a space group, and returns the expanded indices along with an index mapping and phase offsets needed to expand any associated data.
- Parameters:
hkl (torch.Tensor, shape (N, 3)) – Input Miller indices (asymmetric unit).
spacegroup (str, int, or gemmi.SpaceGroup) – Space group specification.
include_friedel (bool, default True) – Include Friedel mates (-h, -k, -l).
remove_absences (bool, default True) – Remove systematically absent reflections.
device (torch.device, optional) – Computation device. If None, uses hkl’s device.
- Returns:
expanded_hkl (torch.Tensor, shape (M, 3), dtype=int32) – All unique expanded Miller indices.
orig_indices (torch.Tensor, shape (M,), dtype=int64) – Index mapping expanded → original reflection. Use to expand any data:
F_expanded = F_orig[orig_indices]phase_shifts (torch.Tensor, shape (M,), dtype=float32) – Phase offsets from translations (radians). Apply to phases:
phase_expanded = phase_orig[orig_indices] + phase_shifts
- Return type:
Examples
import torch from torchref.symmetry import expand_hkl hkl_asu = torch.tensor([[1, 0, 0], [0, 1, 0], [1, 1, 1]], dtype=torch.int32) hkl_p1, indices, phases = expand_hkl(hkl_asu, 'P21') # Expand amplitude data F_asu = torch.tensor([100.0, 80.0, 75.0]) F_p1 = F_asu[indices] # Expand phase data (apply phase shifts) phi_asu = torch.tensor([0.0, 1.5, 2.0]) phi_p1 = phi_asu[indices] + phases
- torchref.symmetry.reciprocal_symmetry.complete_hkl(input_hkl, cell, spacegroup, d_min, device=None)[source]
Complete a set of Miller indices by identifying missing reflections.
Generates all possible reflections within the resolution limit for the given spacegroup (removing systematic absences), then maps the input reflections to this complete set.
NOTE: This does NOT expand symmetry - stays in the same spacegroup. Use this to identify which reflections are missing from a dataset.
- Parameters:
input_hkl (torch.Tensor, shape (N, 3)) – Input Miller indices (may be incomplete).
cell (torch.Tensor, shape (6,)) – Unit cell parameters [a, b, c, alpha, beta, gamma].
spacegroup (str, int, or gemmi.SpaceGroup) – Space group specification.
d_min (float) – High resolution limit in Angstroms.
device (torch.device, optional) – Computation device. If None, uses input_hkl’s device.
- Returns:
complete_hkl (torch.Tensor, shape (M, 3), dtype int32) – All possible Miller indices within resolution (minus systematic absences).
input_indices (torch.Tensor, shape (M,), dtype int64) – Index mapping: complete → input. For present reflections, gives the index in input_hkl. For missing reflections, gives -1. Use:
F_complete[~missing] = F_input[input_indices[~missing]]missing_mask (torch.Tensor, shape (M,), dtype bool) – True where reflection is missing from input.
- Return type:
Examples
import torch from torchref.symmetry import complete_hkl # Incomplete dataset input_hkl = torch.tensor([[1, 0, 0], [0, 1, 0]], dtype=torch.int32) cell = torch.tensor([50.0, 60.0, 70.0, 90.0, 90.0, 90.0]) complete, indices, missing = complete_hkl(input_hkl, cell, 'P21', d_min=10.0) # Fill F values F_input = torch.tensor([100.0, 80.0]) F_complete = torch.zeros(len(complete)) present_mask = ~missing F_complete[present_mask] = F_input[indices[present_mask]]
- torchref.symmetry.reciprocal_symmetry.expand_reflections(reflection_data, include_friedel=True, remove_absences=True, verbose=1)[source]
Expand reflection data from asymmetric unit to P1 using symmetry operations.
Takes a ReflectionData object containing reflections in the asymmetric unit and generates all symmetry-equivalent reflections, returning a new ReflectionData object with the expanded set.
This is a high-level wrapper around expand_hkl() that handles all ReflectionData fields automatically.
- Parameters:
reflection_data (ReflectionData) – Input reflection data with hkl, F, F_sigma, etc.
include_friedel (bool, default True) – If True, also include Friedel mates (-h, -k, -l).
remove_absences (bool, default True) – If True, remove systematically absent reflections from output.
verbose (int, default 1) – Verbosity level.
- Returns:
New ReflectionData object with expanded reflections. The spacegroup is set to ‘P1’ since symmetry has been expanded.
- Return type:
See also
expand_hklLow-level function for HKL expansion without ReflectionData.
Examples
from torchref.io.datasets.reflection_data import ReflectionData from torchref.symmetry import expand_reflections data = ReflectionData().load_mtz('data.mtz') data_p1 = expand_reflections(data) print(f"Expanded from {len(data)} to {len(data_p1)} reflections")
- torchref.symmetry.reciprocal_symmetry.reduce_hkl(hkl_p1, spacegroup, include_friedel=True, device=None)[source]
Reduce P1 Miller indices to asymmetric unit of a target spacegroup.
This is the inverse of expand_hkl(). Takes a complete set of P1 reflections and maps them back to the asymmetric unit of the target spacegroup. Multiple P1 reflections that are symmetry-equivalent merge into single ASU reflections.
The return uses a 2D index mapping with “constant multiplicity” design: each ASU reflection has exactly n_ops slots (for symmetry operations), enabling simple vectorized aggregation.
- Parameters:
hkl_p1 (torch.Tensor, shape (N, 3)) – Input Miller indices in P1 (complete hemisphere).
spacegroup (str, int, or gemmi.SpaceGroup) – Target space group specification.
include_friedel (bool, default True) – If True, also consider Friedel mates when finding ASU representative.
device (torch.device, optional) – Computation device. If None, uses hkl_p1’s device.
- Returns:
hkl_asu (torch.Tensor, shape (M, 3), dtype int32) – Unique Miller indices in the asymmetric unit.
reduction_indices (torch.Tensor, shape (M, n_ops * (2 if include_friedel else 1)), dtype int64) – For each ASU reflection, indices into hkl_p1 for its symmetry equivalents. Value of -1 indicates no P1 reflection at that position. Use:
F_asu = aggregate(F_p1[reduction_indices], dim=1)phase_shifts (torch.Tensor, shape (M, n_ops * (2 if include_friedel else 1)), dtype float32) – Phase shifts to apply before aggregation. For Friedel mates, the phase is negated.
- Return type:
Examples
import torch from torchref.symmetry import expand_hkl, reduce_hkl # Start with ASU, expand to P1, then reduce back hkl_asu = torch.tensor([[1, 0, 0], [0, 1, 0], [1, 1, 1]], dtype=torch.int32) hkl_p1, exp_idx, exp_phase = expand_hkl(hkl_asu, 'P21') # Reduce back to ASU hkl_asu_back, red_idx, red_phase = reduce_hkl(hkl_p1, 'P21') # Aggregate F values from P1 to ASU F_p1 = torch.randn(len(hkl_p1)) valid_mask = red_idx >= 0 F_gathered = torch.where(valid_mask, F_p1[red_idx.clamp(min=0)], torch.zeros_like(F_p1[0])) F_asu = F_gathered.sum(dim=1) / valid_mask.sum(dim=1).clamp(min=1)
Notes
The “constant multiplicity” design means the second dimension is always n_ops (or 2*n_ops with Friedel), regardless of whether all equivalent reflections exist in hkl_p1. Missing positions are marked with -1. This enables efficient batch aggregation without variable-length operations.
- torchref.symmetry.reciprocal_symmetry.canonicalize_hkl(hkl, spacegroup, include_friedel=True, device=None)[source]
Map Miller indices to canonical CCP4 ASU representatives.
Uses the standard CCP4 asymmetric unit convention to select a unique representative for each reflection. The implementation is fully vectorized — symmetry equivalents are generated via batched matrix multiply and the ASU membership check is evaluated as a numpy boolean expression over all reflections at once.
- Parameters:
hkl (torch.Tensor, shape (N, 3), dtype int32) – Input Miller indices.
spacegroup (str, int, or gemmi.SpaceGroup) – Space group specification.
include_friedel (bool, default True) – Whether Friedel mates are considered equivalent.
device (torch.device, optional) – Computation device. If None, uses hkl’s device.
- Returns:
canonical_hkl (torch.Tensor, shape (N, 3), dtype int32) – Remapped indices, sorted lexicographically by (h, k, l).
phase_shifts (torch.Tensor, shape (N,), dtype float32) – Additive phase correction in radians.
friedel_flags (torch.Tensor, shape (N,), dtype bool) – True where Friedel conjugation was applied.
sort_indices (torch.Tensor, shape (N,), dtype int64) – Permutation from original to sorted order.
- Return type:
Notes
Phase correction contract — to convert structure factors to the canonical basis, the caller applies:
phi_new = torch.where(friedel_flags, -phi_old, phi_old) + phase_shifts
- torchref.symmetry.reciprocal_symmetry.expand_reciprocal_grid(F_grid, space_group, mode='average', include_friedel=True, device=None)[source]
Expand or symmetrize a reciprocal space grid using crystallographic symmetry.
This is a convenience function that creates a ReciprocalSymmetryGrid and applies it to the input grid.
- Parameters:
F_grid (torch.Tensor, shape (nh, nk, nl)) – Input structure factor grid (can be complex or real).
space_group (str) – Space group symbol (e.g., ‘P21’, ‘P212121’).
mode (str, default 'average') – Operation mode: - ‘average’: Average over all symmetry equivalents (symmetrize) - ‘expand’: Expand from asymmetric unit to full grid - ‘sum’: Sum all symmetry mates
include_friedel (bool, default True) – If True, also apply Friedel symmetry after space group symmetry.
device (torch.device, optional) – Device for computation. If None, uses F_grid’s device.
- Returns:
Symmetrized or expanded structure factor grid.
- Return type:
torch.Tensor, shape (nh, nk, nl)
Examples
import torch from torchref.symmetry import expand_reciprocal_grid # Create a test grid with some values in asymmetric unit F = torch.zeros(32, 32, 32, dtype=torch.complex64) F[5, 3, 2] = 1.0 + 0.5j # Expand to full grid F_full = expand_reciprocal_grid(F, 'P21', mode='expand') # Or symmetrize an existing full grid F_sym = expand_reciprocal_grid(F_noisy, 'P21', mode='average')