From b4ca0610918ce703bcc5714a7719ec13e553c6cc Mon Sep 17 00:00:00 2001 From: Anton Akhmerov <anton.akhmerov@gmail.com> Date: Wed, 21 Dec 2016 18:05:33 +0100 Subject: [PATCH] add `subgroup` method to symmetries --- kwant/builder.py | 27 +++++++++++++++++++++++++++ kwant/lattice.py | 32 ++++++++++++++++++++++++++++++++ kwant/tests/test_builder.py | 16 ++++++++++++++++ kwant/tests/test_lattice.py | 25 +++++++++++++++++++++++++ 4 files changed, 100 insertions(+) diff --git a/kwant/builder.py b/kwant/builder.py index f60a7712..89e4377e 100644 --- a/kwant/builder.py +++ b/kwant/builder.py @@ -317,6 +317,22 @@ class Symmetry(metaclass=abc.ABCMeta): return False return True + @abc.abstractmethod + def subgroup(self, *generators): + """Return the subgroup generated by a sequence of group elements.""" + pass + + @abc.abstractmethod + def isstrictsupergroup(self, other): + """Test whether other symmetry is a strict supergroup.""" + pass + + def issubgroup(self, other): + """Test whether other symmetry is a subgroup.""" + return other.isstrictsupergroup(self) + + __le__ = issubgroup + class NoSymmetry(Symmetry): """A symmetry with a trivial symmetry group.""" @@ -334,6 +350,8 @@ class NoSymmetry(Symmetry): def num_directions(self): return 0 + periods = () + _empty_array = ta.array((), int) def which(self, site): @@ -350,6 +368,15 @@ class NoSymmetry(Symmetry): def in_fd(self, site): return True + def subgroup(self, *generators): + if any(generators): + raise ValueError('Generators must be empty for NoSymmetry.') + return NoSymmetry(generators) + + def isstrictsupergroup(self, other): + return False + + ################ Hopping kinds diff --git a/kwant/lattice.py b/kwant/lattice.py index 5bc1e88f..5b2ac3d9 100644 --- a/kwant/lattice.py +++ b/kwant/lattice.py @@ -542,6 +542,38 @@ class TranslationalSymmetry(builder.Symmetry): self.site_family_data = {} self.is_reversed = False + def subgroup(self, *generators): + """Return the subgroup generated by a sequence of group elements. + + Parameters + ---------- + *generators: sequence of int + Each generator must have length ``self.num_directions``. + """ + generators = ta.array(generators) + if generators.dtype != int: + raise ValueError('Generators must be sequences of integers.') + return TranslationalSymmetry(*ta.dot(generators, self.periods)) + + def isstrictsupergroup(self, other): + if isinstance(other, builder.NoSymmetry): + return True + elif not isinstance(other, TranslationalSymmetry): + raise ValueError("Unknown symmetry type.") + + if other.periods.shape[1] != self.periods.shape[1]: + return False # Mismatch of spatial dimensionalities. + + inv = np.linalg.pinv(self.periods) + factors = np.dot(other.periods, inv) + # Absolute tolerance is correct in the following since we want an error + # relative to the closest integer. + if not (np.allclose(factors, np.round(factors), rtol=0, atol=1e-8) and + np.allclose(ta.dot(factors, self.periods), other.periods)): + return False + else: + return True + def add_site_family(self, fam, other_vectors=None): """ Select a fundamental domain for site family and cache associated data. diff --git a/kwant/tests/test_builder.py b/kwant/tests/test_builder.py index 65dcf150..bfe757ed 100644 --- a/kwant/tests/test_builder.py +++ b/kwant/tests/test_builder.py @@ -125,6 +125,22 @@ class VerySimpleSymmetry(builder.Symmetry): def num_directions(self): return 1 + def isstrictsupergroup(self, other): + if isinstance(other, builder.NoSymmetry): + return True + elif isinstance(other, VerySimpleSymmetry): + return not other.period % self.period + else: + return False + + def subgroup(self, *generators): + generators = ta.array(generators) + assert generators.shape == (1, 1) + if generators.dtype != int: + raise ValueError('Generators must be sequences of integers.') + g = generators[0, 0] + return VerySimpleSymmetry(g * self.period) + def which(self, site): return ta.array((site.tag[0] // self.period,), int) diff --git a/kwant/tests/test_lattice.py b/kwant/tests/test_lattice.py index fa4196dc..45337c10 100644 --- a/kwant/tests/test_lattice.py +++ b/kwant/tests/test_lattice.py @@ -229,3 +229,28 @@ def test_norbs(): assert lat != lat1 assert lat != lat2 assert lat1 != lat2 + + +def test_symmetry_subgroup(): + rng = np.random.RandomState(0) + ## test whether actual subgroups are detected as such + vecs = rng.randn(3, 3) + sym1 = lattice.TranslationalSymmetry(*vecs) + assert sym1 >= sym1 + assert sym1 >= builder.NoSymmetry() + assert sym1 >= lattice.TranslationalSymmetry(2 * vecs[0], + 3 * vecs[1] + 4 * vecs[2]) + assert not sym1 <= lattice.TranslationalSymmetry(*(0.8 * vecs)) + + ## test subgroup creation + for dim in range(1, 4): + generators = rng.randint(10, size=(dim, 3)) + assert sym1.subgroup(*generators) <= sym1 + + # generators are not linearly independent + with raises(ValueError): + sym1.subgroup(*rng.randint(10, size=(4, 3))) + + # generators are not integer sequences + with raises(ValueError): + sym1.subgroup(*rng.rand(1, 3)) -- GitLab