def __init__(self, axis: float = np.pi / 2, fibergroup: Group = None): r""" Describes reflectional symmetries of the plane :math:`\R^2`. Reflections are applied along the line through the origin with an angle ``axis`` degrees with respect to the *X*-axis. Args: axis (float, optional): the slope of the axis of the reflection (in radians). By default, the vertical axis is used (:math:`\pi/2`). fibergroup (Group, optional): use an already existing instance of the symmetry group Attributes: ~.axis (float): Angle with respect to the horizontal axis which defines the reflection axis. """ self.axis = axis if fibergroup is None: fibergroup = cyclic_group(2) else: assert isinstance(fibergroup, CyclicGroup) and fibergroup.order() == 2 name = 'Flips' super(Flip2dOnR2, self).__init__(fibergroup, name)
def test_quotient_cyclic_odd(self): N = 21 dg = cyclic_group(N) for n in range(1, int(round(np.sqrt(N))) + 1): if N % n == 0: sg_id = 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, fibergroup: Group = None): r""" Describes rotation symmetries of the plane :math:`\R^2`. If ``N > 1``, the class models *discrete* rotations by angles which are multiple of :math:`\frac{2\pi}{N}` (:class:`~e2cnn.group.CyclicGroup`). Otherwise, if ``N=-1``, the class models *continuous* planar rotations (:class:`~e2cnn.group.SO2`). In that case the parameter ``maximum_frequency`` is required to specify the maximum frequency of the irreps of :class:`~e2cnn.group.SO2` (see its documentation for more details) 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.SO2`'s irreps if ``N = -1`` fibergroup (Group, optional): use an already existing instance of the symmetry group. In that case, the other parameters should not be provided. """ 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, CyclicGroup) or isinstance( fibergroup, SO2) assert maximum_frequency is None, "Maximum Frequency can't be set when the group is already provided in input" N = fibergroup.order() assert isinstance(N, int) if N > 1: assert maximum_frequency is None, "Maximum Frequency can't be set for finite cyclic groups" name = '{}-Rotations'.format(N) elif N == -1: name = 'Continuous-Rotations' 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 = cyclic_group(N) elif N == -1: fibergroup = so2_group(maximum_frequency) super(Rot2dOnR2, self).__init__(fibergroup, name)
def __init__(self, fibergroup: Group = None): r""" Describes the plane :math:`\R^2` without considering any origin-preserving symmetry. This is modeled by a choosing trivial fiber group :math:`\{e\}`. .. note :: This models the symmetries of conventional *Convolutional Neural Networks* which are not equivariant to origin preserving transformations such as rotations and reflections. Args: fibergroup (Group, optional): use an already existing instance of the symmetry group. By default, it builds a new instance of the trivial group. """ if fibergroup is None: fibergroup = cyclic_group(1) else: assert isinstance(fibergroup, CyclicGroup) and fibergroup.order() == 1 name = "Trivial" super(TrivialOnR2, self).__init__(fibergroup, name)
def test_induce_irreps_cyclic_odd_cyclic_odd(self): dg = cyclic_group(9) sg_id = 3 sg, _, _ = dg.subgroup(sg_id) for name, irrep in sg.irreps.items(): self.check_induction(dg, sg_id, irrep)
def test_induce_rr_cyclic_odd_cyclic_odd(self): dg = cyclic_group(15) sg_id = 5 sg, _, _ = dg.subgroup(sg_id) repr = sg.regular_representation self.check_induction(dg, sg_id, repr)
def __init__( self, group: Group, in_irrep: Union[str, IrreducibleRepresentation, int], out_irrep: Union[str, IrreducibleRepresentation, int], axis: float, max_frequency: int = None, max_offset: int = None, ): if isinstance(group, int): group = cyclic_group(2) assert isinstance(group, CyclicGroup) and group.order() == 2 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, int): in_irrep = group.irrep(in_irrep) 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, int): out_irrep = group.irrep(out_irrep) 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 = 1 self.fi = in_irrep.attributes['frequency'] self.fo = out_irrep.attributes['frequency'] self.ts = [] self.gamma = ((self.fi + self.fo) % 2) * np.pi / 2 mus = [] # for each available frequency offset, build the corresponding basis vector for t in offset_iterator(0, 1, self.max_offset, self.max_frequency, non_negative=True): # the current shifted frequency mu = t 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), (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(R2FlipsSolution, self).__init__(group, in_irrep, out_irrep, dim)
def __init__( self, group: Union[Group, int], in_irrep: Union[str, IrreducibleRepresentation, int], out_irrep: Union[str, IrreducibleRepresentation, int], max_frequency: int = None, max_offset: int = None, ): if isinstance(group, int): group = cyclic_group(group) assert isinstance(group, CyclicGroup) 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) if isinstance(in_irrep, int): in_irrep = group.irrep(in_irrep) 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") self.n = in_irrep.attributes['frequency'] if isinstance(out_irrep, int): out_irrep = group.irrep(out_irrep) 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.m = out_irrep.attributes['frequency'] self.N = group.order() self.ts = [] if in_irrep.size == 2 and out_irrep.size == 2: # m, n > 0 gammas = [] mus = [] ss = [] for gamma in [0., np.pi / 2]: 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) gammas.append(gamma) mus.append(mu) ss.append(s) self.ts.append(t) self.gamma = np.array(gammas).reshape(-1, 1) 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)) # n > 0, m = 0 or N/2 gammas = [] mus = [] for gamma in [0., np.pi / 2]: 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) gammas.append(gamma) mus.append(mu) self.ts.append(t) self.gamma = np.array(gammas).reshape(-1, 1) 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)) # m > 0, n = 0 or N/2 gammas = [] mus = [] for gamma in [0., np.pi / 2]: 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) gammas.append(gamma) mus.append(mu) self.ts.append(t) self.gamma = np.array(gammas).reshape(-1, 1) 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)) gammas = [] mus = [] for gamma in [0., np.pi / 2]: 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 gamma == 0.: # don't add sin(0*theta) as a basis since it is zero everywhere gammas.append(gamma) mus.append(mu) self.ts.append(t) self.gamma = np.array(gammas).reshape(-1, 1) 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.gamma.shape[0] super(R2DiscreteRotationsSolution, self).__init__(group, in_irrep, out_irrep, dim)