def check_representation(self, repr: Representation): group = repr.group np.set_printoptions(precision=2, threshold=2 * repr.size ** 2, suppress=True, linewidth=10 * repr.size + 3) P = directsum([group.irreps[irr] for irr in repr.irreps], name="irreps") self.assertTrue(np.allclose(repr.change_of_basis @ repr.change_of_basis.T, np.eye(repr.size))) self.assertTrue(np.allclose(repr.change_of_basis.T @ repr.change_of_basis, np.eye(repr.size))) for a in group.testing_elements(): repr_1 = repr(a) repr_2 = repr.change_of_basis @ P(a) @ repr.change_of_basis_inv self.assertTrue(np.allclose(repr_1, repr_2), msg=f"{a}:\n{repr_1}\ndifferent from\n {repr_2}\n") for b in group.testing_elements(): repr_ab = repr(a) @ repr(b) c = group.combine(a, b) repr_c = repr(c) self.assertTrue(np.allclose(repr_ab, repr_c), msg=f"{a} x {b} = {c}:\n{repr_ab}\ndifferent from\n {repr_c}\n")
def test_mix_o2(self): g = O2(6) rr = directsum(list(g.representations.values())) N = 3 size = rr.size * N bcob = ortho_group.rvs(dim=size//5) bsize = bcob.shape[0] p = np.eye(size, size) for i in range(size//bsize): p[i*bsize:(i+1)*bsize, i*bsize:(i+1)*bsize] = bcob p = p[:, np.random.permutation(size)] repr = directsum([rr]*N, change_of_basis=p) self.check_disentangle(repr)
def representation(self) -> Representation: r""" The (combined) representations of this field type. They describe how the feature vectors transform under the fiber group action, that is, how the channels mix. It is the direct sum (:func:`~e2cnn.group.directsum`) of the representations in :attr:`e2cnn.nn.FieldType.representations`. Because a feature space can contain a very large number of feature fields, computing this representation as the direct sum of many small representations can be expensive. Hence, this representation is only built the first time it is explicitly used, in order to avoid unnecessary overhead when not needed. Returns: the :class:`~e2cnn.group.Representation` describing the whole feature space """ if self._representation is None: uniques_fields_names = sorted( [r.name for r in self._unique_representations]) self._representation = directsum( self.representations, name= f"FiberRepresentation:[{self.size}], [{uniques_fields_names}]") return self._representation
def test_regular_dihedral(self): g = DihedralGroup(10) rr = g.regular_representation N = 4 size = rr.size * N p = np.eye(size, size) p = p[:, np.random.permutation(size)] repr = directsum([rr]*N, change_of_basis=p) self.check_disentangle(repr)
def test_mix_so2(self): space = Rot2dOnR2(-1, maximum_frequency=4) g = space.fibergroup rr = directsum(list(g.representations.values())) N = 3 size = rr.size * N bcob = ortho_group.rvs(dim=size//5) bsize = bcob.shape[0] p = np.eye(size, size) for i in range(size//bsize): p[i*bsize:(i+1)*bsize, i*bsize:(i+1)*bsize] = bcob p = p[:, np.random.permutation(size)] repr = directsum([rr] * N, change_of_basis=p) cls = FieldType(space, [repr] * 8) el = DisentangleModule(cls) el.check_equivariance()
def get_pre_cov_rep(G_act, dim_cov_est): ''' G_act - instance of e2cnn.gspaces.r2.rot2d_on_r2.Rot2dOnR2 - underlying group dim_cov_est - int - either 1,2,3 or 4 - gives dimension and also the type of the covariance converter ''' if dim_cov_est == 1: return (G_act.trivial_repr) elif dim_cov_est == 2: return (group.directsum(2 * [G_act.trivial_repr])) elif dim_cov_est == 3: return (get_eig_val_cov_conv_rep(G_act)) elif dim_cov_est == 4: if isinstance(G_act, gspaces.FlipRot2dOnR2): vec_rep = G_act.irrep(1, 1) elif isinstance(G_act, gspaces.Rot2dOnR2): vec_rep = G_act.irrep(1) else: sys.exit('Error: unknown group.') return (group.directsum(2 * [vec_rep])) else: sys.exit( 'Error when loading pre covariance representation: dim_cov_est can only be 1,2,3 or 4' )
def test_regular_dihedral(self): space = FlipRot2dOnR2(5) g = space.fibergroup rr = g.regular_representation N = 4 size = rr.size * N p = np.eye(size, size) p = p[:, np.random.permutation(size)] repr = directsum([rr] * N, change_of_basis=p) cls = FieldType(space, [repr] * 8) el = DisentangleModule(cls) el.check_equivariance()
def check_disentangle(self, repr: Representation): group = repr.group cob, reprs = disentangle(repr) self.assertEqual(repr.size, sum([r.size for r in reprs])) ds = directsum(reprs, name="directsum") for e in group.testing_elements(): repr_a = repr(e) repr_b = cob.T @ ds(e) @ cob np.set_printoptions(precision=2, threshold=2 * repr_a.size**2, suppress=True, linewidth=10*repr_a.size + 3) self.assertTrue(np.allclose(repr_a, repr_b), msg=f"{e}:\n{repr_a}\ndifferent from\n {repr_b}\n")
def check_induction_so2_o2(self, group, subgroup_id, repr): # print("#######################################################################################################") subgroup, parent, child = group.subgroup(subgroup_id) assert repr.group == subgroup # induced_repr = build_induced_representation(group, subgroup_id, repr) induced_repr = group.induced_representation(subgroup_id, repr) assert induced_repr.group == group assert np.allclose( induced_repr.change_of_basis @ induced_repr.change_of_basis_inv, np.eye(induced_repr.size)) assert np.allclose( induced_repr.change_of_basis_inv @ induced_repr.change_of_basis, np.eye(induced_repr.size)) restricted_repr = group.restrict_representation( subgroup_id, induced_repr) for e in subgroup.testing_elements(): repr_a = repr(e) repr_b = induced_repr(parent(e))[:repr.size, :repr.size] repr_c = restricted_repr(e)[:repr.size, :repr.size] np.set_printoptions(precision=2, threshold=2 * repr_a.size**2, suppress=True, linewidth=10 * repr_a.size + 3) self.assertTrue( np.allclose(repr_a, repr_b), msg= f"{group.name}\{subgroup.name}: {repr.name} - {e}:\n{repr_a}\ndifferent from\n {repr_b}\n" ) if not np.allclose(repr_c, repr_b): print(e, parent(e)) print(induced_repr.change_of_basis_inv @ induced_repr( parent(e)) @ induced_repr.change_of_basis) print(restricted_repr.change_of_basis_inv @ restricted_repr(e) @ restricted_repr.change_of_basis) print(induced_repr.irreps) print(restricted_repr.irreps) # print(induced_repr.change_of_basis) # print(restricted_repr.change_of_basis) print( np.allclose(induced_repr.change_of_basis, restricted_repr.change_of_basis)) self.assertTrue( np.allclose(repr_c, repr_b), msg= f"{group.name}\{subgroup.name}: {repr.name} - {e}:\n{repr_c}\ndifferent from\n {repr_b}\n" ) quotient_size = 2 size = repr.size * quotient_size # the coset each element belongs to cosets = {} # map from a representative to the elements of its coset representatives = defaultdict(lambda: []) for e in group.testing_elements(): flip, rot = e cosets[e] = (flip, 0.) representatives[(flip, 0.)].append(e) index = {e: i for i, e in enumerate(representatives)} P = directsum([group.irreps[irr] for irr in induced_repr.irreps], name="irreps") for g in group.testing_elements(): repr_g = np.zeros((size, size), dtype=np.float) for r in representatives: gr = group.combine(g, r) g_r = cosets[gr] i = index[r] j = index[g_r] hp = group.combine(group.inverse(g_r), gr) h = child(hp) assert h is not None, (g, r, gr, g_r, group.inverse(g_r), hp) repr_g[j * repr.size:(j + 1) * repr.size, i * repr.size:(i + 1) * repr.size] = repr(h) ind_g = induced_repr(g) self.assertTrue( np.allclose(repr_g, ind_g), msg= f"{group.name}\{subgroup.name}: {repr.name} - {g}:\n{repr_g}\ndifferent from\n {ind_g}\n" ) ind_g2 = induced_repr.change_of_basis @ P( g) @ induced_repr.change_of_basis_inv self.assertTrue( np.allclose(ind_g2, ind_g), msg= f"{group.name}\{subgroup.name}: {repr.name} - {g}:\n{ind_g2}\ndifferent from\n {ind_g}\n" )
def test_restrict_so2_cyclic_odd(self): dg = SO2(10) repr = directsum(list(dg.irreps.values())) sg_id = 7 self.check_restriction(dg, sg_id, repr)
def test_restrict_o2_dihedral_odd(self): dg = O2(10) repr = directsum(list(dg.irreps.values())) sg_id = (0., 3) self.check_restriction(dg, sg_id, repr)
def _induced_from_irrep( self, subgroup_id: Tuple[float, int], repr: IrreducibleRepresentation, ) -> Tuple[List[IrreducibleRepresentation], np.ndarray, np.ndarray]: if subgroup_id == (None, -1): # Induced representation from SO(2) # As the quotient set is finite, a finite dimensional representation of SO(2) # defines a finite dimensional induced representation of O(2) subgroup, parent, child = self.subgroup(subgroup_id) assert repr.group == subgroup name = f"induced[{subgroup_id}][{repr.name}]" frequency = repr.attributes["frequency"] if frequency > 0: multiplicities = [(self.irrep(1, frequency), 2)] else: multiplicities = [(self.irrep(0, 0), 1), (self.irrep(1, 0), 1)] irreps = [] for irr, multiplicity in multiplicities: irreps += [irr] * multiplicity P = directsum(irreps, name=f"{name}_irreps") size = P.size v = np.zeros((repr.size, size), dtype=np.float) def build_commuting_matrix(rho, t): k = rho.attributes["frequency"] if rho.size == 1: E = np.eye(1) M = 2 * np.pi * np.eye(1) else: E = np.array([[1, -1], [1, 1]]) if t % 2 == 0: E = E.T I = np.eye(4) A = np.fliplr(np.eye(4)) * np.array([1, -1, -1, 1]) M = np.pi * (A + I) # compute the averaging of rho(g).T @ E @ rho(g) # i.e. X = 1/2pi Integral_{0, 2pi} rho(theta).T @ E @ rho(theta) d theta # as vec(X) = 1/2pi Integral_{0, 2pi} (rho *tensor* rho)(theta) @ vec(E) d theta # where M = Integral_{0, 2pi} (rho *tensor* rho)(theta) d theta X = M @ E.reshape(-1, 1) X /= 2 * np.pi # normalization X /= np.sqrt(np.sum(X @ X.T) / rho.size) X = X.reshape(rho.size, rho.size) return X p = 0 for irr, m in multiplicities: assert irr.size >= m if m > 0: restricted_irr = self.restrict_representation( subgroup_id, irr) n_repetitions = len([ name for name in restricted_irr.irreps if name == repr.name ]) assert repr.size * n_repetitions >= m, ( f"{self.name}\{subgroup.name}:{repr.name}", irr.name, m, n_repetitions) for shift in range(m): commuting_matrix = build_commuting_matrix( repr, shift // n_repetitions) x = p i = 0 for r_irrep in restricted_irr.irreps: if r_irrep == repr.name: if i == shift % n_repetitions: v[:, x:x + repr.size] = commuting_matrix i += 1 x += subgroup.irreps[r_irrep].size v[:, p:p + irr. size] = v[:, p:p + irr. size] @ restricted_irr.change_of_basis_inv v[:, p:p + irr.size] *= np.sqrt(irr.size) p += irr.size v /= np.sqrt(size) change_of_basis = np.zeros((size, size)) change_of_basis[:repr.size, :] = v @ P(self.identity) change_of_basis[repr.size:, :] = v @ P(self.reflection) change_of_basis_inv = change_of_basis.T return irreps, change_of_basis, change_of_basis_inv else: raise ValueError( f"Induction from discrete subgroups of O(2) leads to infinite dimensional induced " f"representations. Hence, induction from the subgroup identified " f"by {subgroup_id} is not allowed.")
def test_mix_o2(self): g = O2(6) rr = directsum(list(g.representations.values())) self.check_representation(rr) self.check_character(rr)
def test_mix_dihedral(self): g = DihedralGroup(10) rr = directsum(list(g.representations.values())) self.check_representation(rr) self.check_character(rr)
def test_mix_cyclic(self): g = CyclicGroup(15) rr = directsum(list(g.representations.values())) self.check_representation(rr) self.check_character(rr)
def test_restrict_o2_dihedral_even(self): dg = O2(10) repr = directsum(list(dg.irreps.values())) sg_id = (0., 6) self.check_disentangle(dg.restrict_representation(sg_id, repr))
def test_restrict_o2_so2(self): dg = O2(10) repr = directsum(list(dg.irreps.values())) sg_id = (None, -1) self.check_disentangle(dg.restrict_representation(sg_id, repr))
def test_restrict_o2_cyclic_even(self): dg = O2(10) repr = directsum(list(dg.irreps.values())) sg_id = (None, 4) self.check_restriction(dg, sg_id, repr)
def test_restrict_so2_cyclic_odd(self): dg = SO2(10) repr = directsum(list(dg.irreps.values())) sg_id = 7 self.check_disentangle(dg.restrict_representation(sg_id, repr))
def test_restrict_o2_o2(self): dg = O2(10) repr = directsum(list(dg.irreps.values())) sg_id = (1., -1) self.check_restriction(dg, sg_id, repr)
def __init__(self, hidden_reps_ids, kernel_sizes, dim_cov_est, context_rep_ids=[1], N=4, flip=False, non_linearity=["NormReLU"], max_frequency=30): ''' Input: hidden_reps_ids - list: encoding the hidden fiber representation (see give_fib_reps_from_ids) kernel_sizes - list of ints - sizes of kernels for convolutional layers dim_cov_est - dimension of covariance estimation, either 1,2,3 or 4 context_rep_ids - list: gives the input fiber representation (see give_fib_reps_from_ids) non_linearity - list of strings - gives names of non-linearity to be used Either length 1 (then same non-linearity for all) or length is the number of layers (giving a custom non-linearity for every layer) N - int - gives the group order, -1 is infinite flip - Bool - indicates whether we have a flip in the rotation group (i.e.O(2) vs SO(2), D_N vs C_N) max_frequency - int - maximum irrep frequency to computed, only relevant if N=-1 ''' super(SteerDecoder, self).__init__() #Save the rotation group, if flip is true, then include all corresponding reflections: self.flip = flip self.max_frequency = max_frequency if self.flip: self.G_act = gspaces.FlipRot2dOnR2( N=N) if N != -1 else gspaces.FlipRot2dOnR2( N=N, maximum_frequency=self.max_frequency) #The output fiber representation is the identity: self.target_rep = self.G_act.irrep(1, 1) else: self.G_act = gspaces.Rot2dOnR2( N=N) if N != -1 else gspaces.Rot2dOnR2( N=N, maximum_frequency=self.max_frequency) #The output fiber representation is the identity: self.target_rep = self.G_act.irrep(1) #Save the N defining D_N or C_N (if N=-1 it is infinity): self.polygon_corners = N #Save the id's for the context representation and extract the context fiber representation: self.context_rep_ids = context_rep_ids self.context_rep = group.directsum( self.give_reps_from_ids(self.context_rep_ids)) #Save the parameters: self.kernel_sizes = kernel_sizes self.n_layers = len(hidden_reps_ids) + 2 self.hidden_reps_ids = hidden_reps_ids self.dim_cov_est = dim_cov_est #-----CREATE LIST OF NON-LINEARITIES---- if len(non_linearity) == 1: self.non_linearity = (self.n_layers - 2) * non_linearity elif len(non_linearity) != (self.n_layers - 2): sys.exit( "List of non-linearities invalid: must have either length 1 or n_layers-2" ) else: self.non_linearity = non_linearity #-----ENDE LIST OF NON-LINEARITIES---- #-----------CREATE DECODER----------------- ''' Create a list of layers based on the kernel sizes. Compute the padding such that the height h and width w of a tensor with shape (batch_size,n_channels,h,w) does not change while being passed through the decoder ''' #Create list of feature types: feat_types = self.give_feat_types() self.feature_emb = feat_types[0] self.feature_out = feat_types[-1] #Create layers list and append it: layers_list = [ G_CNN.R2Conv(feat_types[0], feat_types[1], kernel_size=kernel_sizes[0], padding=(kernel_sizes[0] - 1) // 2) ] for it in range(self.n_layers - 2): if self.non_linearity[it] == "ReLU": layers_list.append(G_CNN.ReLU(feat_types[it + 1], inplace=True)) elif self.non_linearity[it] == "NormReLU": layers_list.append(G_CNN.NormNonLinearity(feat_types[it + 1])) else: sys.exit("Unknown non-linearity.") layers_list.append( G_CNN.R2Conv(feat_types[it + 1], feat_types[it + 2], kernel_size=kernel_sizes[it], padding=(kernel_sizes[it] - 1) // 2)) #Create a steerable decoder out of the layers list: self.decoder = G_CNN.SequentialModule(*layers_list) #-----------END CREATE DECODER--------------- #-----------CONTROL INPUTS------------------ #Control that all kernel sizes are odd (otherwise output shape is not correct): if any([j % 2 - 1 for j in kernel_sizes]): sys.exit("All kernels need to have odd sizes") if len(kernel_sizes) != (self.n_layers - 1): sys.exit("Number of layers and number kernels do not match.") if len(self.non_linearity) != (self.n_layers - 2): sys.exit( "Number of layers and number of non-linearities do not match.")