def test_induce_rr_dihedral_even_flips(self): dg = dihedral_group(10) sg_id = (0, 1) sg, _, _ = dg.subgroup(sg_id) repr = sg.regular_representation self.check_induction(dg, sg_id, repr)
def test_induce_irreps_dihedral_odd_flips(self): dg = dihedral_group(11) for axis in range(11): sg_id = (axis, 1) sg, _, _ = dg.subgroup(sg_id) for name, irrep in sg.irreps.items(): self.check_induction(dg, sg_id, irrep)
def test_quotient_dihedral(self): N = 7 dg = dihedral_group(N) sg_id = (None, 1) sg, _, _ = dg.subgroup(sg_id) self.check_induction(dg, sg_id, sg.trivial_representation)
def test_induce_irreps_dihedral_even_dihedral_even(self): dg = dihedral_group(12) for axis in range(2): sg_id = (axis, 6) sg, _, _ = dg.subgroup(sg_id) for name, irrep in sg.irreps.items(): self.check_induction(dg, sg_id, irrep)
def test_quotient_dihedral_odd(self): N = 21 dg = dihedral_group(N) for n in range(1, int(round(np.sqrt(N))) + 1): if N % n == 0: for f in range(N // n): sg_id = (f, n) sg, _, _ = dg.subgroup(sg_id) self.check_induction(dg, sg_id, sg.trivial_representation) sg_id = (None, n) sg, _, _ = dg.subgroup(sg_id) self.check_induction(dg, sg_id, sg.trivial_representation)
def __init__(self, N: int = None, maximum_frequency: int = None, axis: float = np.pi / 2, fibergroup: Group = None): r""" Describes reflectional and rotational symmetries of the plane :math:`\R^2`. Reflections are applied with respect to the line through the origin with an angle ``axis`` degrees with respect to the *X*-axis. If ``N > 1``, the class models reflections and *discrete* rotations by angles multiple of :math:`\frac{2\pi}{N}` (:class:`~e2cnn.group.DihedralGroup`). Otherwise, if ``N=-1``, the class models reflections and *continuous* planar rotations (:class:`~e2cnn.group.O2`). In that case the parameter ``maximum_frequency`` is required to specify the maximum frequency of the irreps of :class:`~e2cnn.group.O2` (see its documentation for more details) .. note :: All axes obtained from the axis defined by ``axis`` with a rotation in the symmetry group are equivalent. For instance, if ``N = 4``, an axis :math:`\beta` is equivalent to the axis :math:`\beta + \pi/2`. It follows that for ``N = -1``, i.e. in case the symmetry group contains all continuous rotations, any reflection axis is theoretically equivalent. In practice, though, a basis for equivariant convolutional filter sampled on a grid is affected by the specific choice of the axis. In general, choosing an axis aligned with the grid (an horizontal or a vertical axis, i.e. :math:`0` or :math:`\pi/2`) is suggested. Args: N (int): number of discrete rotations (integer greater than 1) or -1 for continuous rotations maximum_frequency (int): maximum frequency of :class:`~e2cnn.group.O2` 's irreps if ``N = -1`` axis (float, optional): the slope of the axis of the flip (in radians) fibergroup (Group, optional): use an already existing instance of the symmetry group. In that case only the parameter ``axis`` should be used. Attributes: ~.axis (float): Angle with respect to the horizontal axis which defines the reflection axis. """ assert N is not None or fibergroup is not None, "Error! Either use the parameter `N` or the parameter `group`!" if fibergroup is not None: assert isinstance(fibergroup, DihedralGroup) or isinstance(fibergroup, O2) assert maximum_frequency is None, "Maximum Frequency can't be set when the group is already provided in input" N = fibergroup.rotation_order assert isinstance(N, int) self.axis = axis if N > 1: assert maximum_frequency is None, "Maximum Frequency can't be set for finite cyclic groups" name = 'Flip_{}-Rotations(f={:.5f})'.format(N, self.axis) elif N == -1: name = 'Flip_Continuous-Rotations(f={:.5f})'.format(self.axis) # self.axis = np.pi/2 else: raise ValueError(f'Error! "N" has to be an integer greater than 1 or -1, but got {N}') if fibergroup is None: if N > 1: fibergroup = dihedral_group(N) elif N == -1: fibergroup = o2_group(maximum_frequency) super(FlipRot2dOnR2, self).__init__(fibergroup, name)
def test_induce_irreps_dihedral_odd_cyclic_odd(self): dg = dihedral_group(9) sg_id = (None, 3) sg, _, _ = dg.subgroup(sg_id) for name, irrep in sg.irreps.items(): self.check_induction(dg, sg_id, irrep)
def test_induce_rr_dihedral_odd_cyclic_odd(self): dg = dihedral_group(9) sg_id = (None, 3) sg, _, _ = dg.subgroup(sg_id) repr = sg.regular_representation self.check_induction(dg, sg_id, repr)
def __init__( self, group: Union[Group, int], in_irrep: Union[str, IrreducibleRepresentation, Tuple[int]], out_irrep: Union[str, IrreducibleRepresentation, Tuple[int, int]], axis: float, max_frequency: int = None, max_offset: int = None, ): if isinstance(group, int): group = dihedral_group(group) assert isinstance(group, DihedralGroup) assert (max_frequency is not None or max_offset is not None), \ 'Error! Either the maximum frequency or the maximum offset for the frequencies must be set' self.max_frequency = max_frequency self.max_offset = max_offset assert max_frequency is None or (isinstance(max_frequency, int) and max_frequency >= 0) assert max_offset is None or (isinstance(max_offset, int) and max_offset >= 0) assert isinstance(axis, float) self.axis = axis if isinstance(in_irrep, tuple): in_irrep = group.irrep(in_irrep[0], in_irrep[1]) elif isinstance(in_irrep, str): in_irrep = group.irreps[in_irrep] elif not isinstance(in_irrep, IrreducibleRepresentation): raise ValueError( f"'in_irrep' should be a non-negative integer, a string or an instance" f" of IrreducibleRepresentation but {in_irrep} found") if isinstance(out_irrep, tuple): out_irrep = group.irrep(out_irrep[0], out_irrep[1]) elif isinstance(out_irrep, str): out_irrep = group.irreps[out_irrep] elif not isinstance(out_irrep, IrreducibleRepresentation): raise ValueError( f"'out_irrep' should be a non-negative integer, a string or an instance" f" of IrreducibleRepresentation but {in_irrep} found") self.N = group.rotation_order self.m = out_irrep.attributes['frequency'] self.n = in_irrep.attributes['frequency'] self.fi = in_irrep.attributes['flip_frequency'] self.fo = out_irrep.attributes['flip_frequency'] self.ts = [] if in_irrep.size == 2 and out_irrep.size == 2: assert (self.m > 0 and self.n > 0 and self.fi == 1 and self.fo == 1) # m, n > 0 mus = [] ss = [] self.gamma = 0. for s in [0, 1]: k = self.m - self.n * (-1)**s # for each available frequency offset, build the corresponding basis vector for t in offset_iterator(k, self.N, self.max_offset, self.max_frequency): # the current shifted frequency mu = k + t * self.N if self.max_offset is not None: assert (math.fabs(t) <= self.max_offset), (t, self.max_offset) if self.max_frequency is not None: assert (math.fabs(mu) <= self.max_frequency), ( k, t, mu, self.max_frequency) mus.append(mu) ss.append(s) self.ts.append(t) self.mu = np.array(mus).reshape(-1, 1) self.s = np.array(ss).reshape(-1, 1) elif in_irrep.size == 2 and out_irrep.size == 1: assert ((self.m == 0 or (self.m == self.N // 2 and self.N % 2 == 0)) and (self.fi == 1)) # n > 0, m = 0 or N/2 self.gamma = self.fo * np.pi / 2 mus = [] k = self.n + self.m # for each available frequency offset, build the corresponding basis vector for t in offset_iterator(k, self.N, self.max_offset, self.max_frequency): # the current shifted frequency mu = k + t * self.N if self.max_offset is not None: assert (math.fabs(t) <= self.max_offset), (t, self.max_offset) if self.max_frequency is not None: assert (math.fabs(mu) <= self.max_frequency), (k, t, mu, self.max_frequency) mus.append(mu) self.ts.append(t) self.mu = np.array(mus).reshape(-1, 1) elif in_irrep.size == 1 and out_irrep.size == 2: assert ((self.n == 0 or (self.n == self.N // 2 and self.N % 2 == 0)) and self.fo == 1) # m > 0, n = 0 or N/2 self.gamma = self.fi * np.pi / 2 mus = [] k = self.n + self.m # for each available frequency offset, build the corresponding basis vector for t in offset_iterator(k, self.N, self.max_offset, self.max_frequency): # the current shifted frequency mu = k + t * self.N if self.max_offset is not None: assert (math.fabs(t) <= self.max_offset), (t, self.max_offset) if self.max_frequency is not None: assert (math.fabs(mu) <= self.max_frequency), (k, t, mu, self.max_frequency) mus.append(mu) self.ts.append(t) self.mu = np.array(mus).reshape(-1, 1) elif in_irrep.size == 1 and out_irrep.size == 1: assert (self.n == 0 or (self.n == self.N // 2 and self.N % 2 == 0)) assert (self.m == 0 or (self.m == self.N // 2 and self.N % 2 == 0)) self.gamma = ((self.fi + self.fo) % 2) * np.pi / 2 mus = [] k = self.m - self.n # for each available frequency offset, build the corresponding basis vector for t in offset_iterator(k, self.N, self.max_offset, self.max_frequency, non_negative=True): # the current shifted frequency mu = k + t * self.N if self.max_offset is not None: assert (math.fabs(t) <= self.max_offset), (t, self.max_offset) if self.max_frequency is not None: assert (math.fabs(mu) <= self.max_frequency), (k, t, mu, self.max_frequency) if mu > 0 or self.gamma == 0.: # don't add sin(0*theta) as a basis since it is zero everywhere mus.append(mu) self.ts.append(t) self.mu = np.array(mus).reshape(-1, 1) self._non_zero_frequencies = self.mu != 0 self._has_non_zero_frequencies = np.any(self._non_zero_frequencies) dim = self.mu.shape[0] super(R2FlipsDiscreteRotationsSolution, self).__init__(group, in_irrep, out_irrep, dim)