flopscope.numpy.symmetrize
fnp.symmetrize(data: 'np.ndarray', *, symmetry, mode: 'str' = 'reynolds-projection') -> 'SymmetricTensor'[flopscope source]
Make an array invariant under a permutation group.
Adapted from NumPy docs np.symmetrize
Symmetrize onto a permutation group's invariant subspace. Cost depends on mode: mode='reynolds-projection' (default) is (|G|+1)*numel (|G| transposed adds + scaling pass; transpose/zeros free; validation uncounted); mode='canonical-copy' is numel (one write per element, copying each orbit's lexicographically-first entry over the orbit; orbit map built from generators and cached, never enumerates |G|; no validation pass, exact by construction). Reynolds upcasts to result_type(input, float64); canonical-copy preserves the input dtype.
Two modes, differing in whether the discarded entries get a vote.
"reynolds-projection" (the default) averages each orbit:
R_G(T) = (1 / |G|) * sum_{g in G} g · T
"canonical-copy" instead keeps one entry per orbit -- the one at the
lexicographically smallest index -- and copies it over the rest. Every
other value in the orbit is discarded rather than mixed in, which is what
makes it the right choice when the input is not trusted: nothing a caller
hid in the redundant positions can reach the result. It is also the
cheaper of the two, being one copy pass rather than |G| transposed
adds, and it never enumerates the group.
Parameters
- data:array_like
Input array to symmetrize.
- symmetry:SymmetryGroup
Symmetry group to average over. If
symmetry.axesisNone, axes are interpreted astuple(range(symmetry.degree)).- mode:{"reynolds-projection", "canonical-copy"}, optional
Which symmetrization to apply. Defaults to
"reynolds-projection".
Returns
- :SymmetricTensor
The projected tensor, validated and wrapped as a SymmetricTensor.
Raises
- :SymmetryError
If
datahas incompatible dimensions forgroupaxes or if the projected result cannot be validated as symmetric forgroup.
Notes
"reynolds-projection" performs exact Reynolds averaging internally,
billing (|G| + 1) * numel(data) FLOPs:
|G|transposed add passes overnumelelementsone final scaling pass (divide by
|G|)
Internal validation runs but is NOT billed (decision D1).
where |G| is the group order and numel = data.size.
"canonical-copy" bills numel(data) -- one write per output
element, the same rate as every other materializing copy (copy,
take, repeat). It needs no validation pass because its output is
invariant by construction, and it never enumerates |G|: the orbit map
is built from the group's generators and cached per
(shape, group action).
The two modes also differ in dtype. Averaging must divide, so
"reynolds-projection" accumulates in
result_type(data, float64) -- a float32 input comes back
float64. "canonical-copy" only moves values, so it preserves the
input dtype exactly, including integer, boolean and complex types.
The canonical pattern for generating random data with symmetry is:
fnp.random.symmetric(shape, symmetry_group, distribution=...).
Examples
>>> import flopscope as flops
>>> import flopscope.numpy as fnp
>>> data = fnp.random.randn(4, 4)
>>> S = flops.symmetrize(data, symmetry=flops.SymmetryGroup.symmetric(axes=(0, 1)))
>>> S.is_symmetric((0, 1))
True