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:

ReciprocalSymmetryGrid

class torchref.symmetry.reciprocal_symmetry.ReciprocalSymmetryGrid(space_group, grid_shape, dtype_float=torch.float32, verbose=1, device=device(type='cpu'))[source]

Bases: DeviceMixin, Module

Reciprocal 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.

space_group

Space group name.

Type:

str

grid_shape

Shape of the reciprocal space grid (nh, nk, nl).

Type:

tuple of int

symmetry

Base symmetry operations handler.

Type:

SpaceGroup

n_ops

Number of symmetry operations.

Type:

int

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:

torch.Tensor

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:

torch.Tensor

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)

is_systematic_absence(h, k, l)[source]

Check if a reflection is systematically absent.

Parameters:
  • h (int) – Miller indices.

  • k (int) – Miller indices.

  • l (int) – Miller indices.

Returns:

True if the reflection is systematically absent.

Return type:

bool

is_centric(h, k, l)[source]

Check if a reflection is centric.

Parameters:
  • h (int) – Miller indices.

  • k (int) – Miller indices.

  • l (int) – Miller indices.

Returns:

True if the reflection is centric (phase restricted to 0 or π).

Return type:

bool

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)

__call__(F_grid, mode='average')[source]

Make the class callable.

get_symmetry_info()[source]

Get information about reciprocal space symmetry.

Returns:

Dictionary with symmetry information.

Return type:

dict

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:

Tuple[Tensor, Tensor, Tensor]

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:

Tuple[Tensor, Tensor, Tensor]

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:

ReflectionData

See also

expand_hkl

Low-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:

Tuple[Tensor, Tensor, Tensor]

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:

Tuple[Tensor, Tensor, Tensor, Tensor]

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')