示例#1
0
def test_inverse(n):
    for p in perm.group(n):
        ip = perm.inverse(p)

        assert perm.compose(p, ip) == perm.identity(n)
        assert perm.compose(ip, p) == perm.identity(n)
示例#2
0
def reduce_tensor(formula, eps=1e-9, has_parity=None, **kw_Rs):
    """
    Usage
    Rs, Q = rs.reduce_tensor('ijkl=jikl=ikjl=ijlk', i=[(1, 1)])
    Rs = 0,2,4
    Q = tensor of shape [15, 81]
    """
    dtype = torch.get_default_dtype()
    with torch_default_dtype(torch.float64):
        # reformat `formulas` and make checks
        formulas = [(-1 if f.startswith('-') else 1, f.replace('-', ''))
                    for f in formula.split('=')]
        s0, f0 = formulas[0]
        assert s0 == 1

        for _s, f in formulas:
            if len(set(f)) != len(f) or set(f) != set(f0):
                raise RuntimeError(f'{f} is not a permutation of {f0}')
            if len(f0) != len(f):
                raise RuntimeError(
                    f'{f0} and {f} don\'t have the same number of indices')

        # `formulas` is a list of (sign, permutation of indices)
        # each formula can be viewed as a permutation of the original formula
        formulas = {(s, tuple(f.index(i) for i in f0))
                    for s, f in formulas}  # set of generators (permutations)

        # they can be composed, for instance if you have ijk=jik=ikj
        # you also have ijk=jki
        # applying all possible compositions creates an entire group
        while True:
            n = len(formulas)
            formulas = formulas.union([(s, perm.inverse(p))
                                       for s, p in formulas])
            formulas = formulas.union([(s1 * s2, perm.compose(p1, p2))
                                       for s1, p1 in formulas
                                       for s2, p2 in formulas])
            if len(formulas) == n:
                break  # we break when the set is stable => it is now a group \o/

        # lets clean the `kw_Rs` before checking that they are compatible with the formulas
        for i in kw_Rs:
            if not callable(kw_Rs[i]):
                Rs = convention(kw_Rs[i])
                if has_parity is None:
                    has_parity = any(p != 0 for _, _, p in Rs)
                if not has_parity and not all(p == 0 for _, _, p in Rs):
                    raise RuntimeError(
                        f'{format_Rs(Rs)} parity has to be specified everywhere or nowhere'
                    )
                if has_parity and any(p == 0 for _, _, p in Rs):
                    raise RuntimeError(
                        f'{format_Rs(Rs)} parity has to be specified everywhere or nowhere'
                    )
                kw_Rs[i] = Rs

        if has_parity is None:
            raise RuntimeError(f'please specify the argument `has_parity`')

        # here we check that each index has one and only one representation
        for _s, p in formulas:
            f = "".join(f0[i] for i in p)
            for i, j in zip(f0, f):
                if i in kw_Rs and j in kw_Rs and kw_Rs[i] != kw_Rs[j]:
                    raise RuntimeError(
                        f'Rs of {i} (Rs={format_Rs(kw_Rs[i])}) and {j} (Rs={format_Rs(kw_Rs[j])}) should be the same'
                    )
                if i in kw_Rs:
                    kw_Rs[j] = kw_Rs[i]
                if j in kw_Rs:
                    kw_Rs[i] = kw_Rs[j]

        for i in f0:
            if i not in kw_Rs:
                raise RuntimeError(f'index {i} has not Rs associated to it')

        e = (0, 0, 0, 0) if has_parity else (0, 0, 0)
        dims = {
            i: len(kw_Rs[i](*e)) if callable(kw_Rs[i]) else dim(kw_Rs[i])
            for i in f0
        }  # dimension of each index
        full_base = list(itertools.product(
            *(range(dims[i])
              for i in f0)))  # (0, 0, 0), (0, 0, 1), (0, 0, 2), ... (3, 3, 3)
        # len(full_base) degrees of freedom in an unconstrained tensor

        # but there is constraints given by the group `formulas`
        # For instance if `ij=-ji`, then 00=-00, 01=-01 and so on
        base = set()
        for x in full_base:
            # T[x] is a coefficient of the tensor T and is related to other coefficient T[y]
            # if x and y are related by a formula
            xs = {(s, tuple(x[i] for i in p)) for s, p in formulas}
            # s * T[x] are all equal for all (s, x) in xs
            # if T[x] = -T[x] it is then equal to 0 and we lose this degree of freedom
            if not (-1, x) in xs:
                # the sign is arbitrary, put both possibilities
                base.add(
                    frozenset(
                        {frozenset(xs),
                         frozenset({(-s, x)
                                    for s, x in xs})}))

        # len(base) is the number of degrees of freedom in the tensor.
        # Now we want to decompose these degrees of freedom into irreps

        base = sorted([
            sorted([sorted(xs) for xs in x]) for x in base
        ])  # requested for python 3.7 but not for 3.8 (probably a bug in 3.7)

        # First we compute the change of basis (projection) between full_base and base
        d_sym = len(base)
        d = len(full_base)
        Q = torch.zeros(d_sym, d)

        for i, x in enumerate(base):
            x = max(x, key=lambda xs: sum(s for s, x in xs))
            for s, e in x:
                j = full_base.index(e)
                Q[i, j] = s / len(x)**0.5

        assert torch.allclose(Q @ Q.T, torch.eye(d_sym))

        if d_sym == 0:
            return [], torch.zeros(d_sym, d).to(dtype=dtype)

        # We project the representation on the basis `base`
        def representation(alpha, beta, gamma, parity=None):
            def re(r):
                if callable(r):
                    if has_parity:
                        return r(alpha, beta, gamma, parity)
                    return r(alpha, beta, gamma)
                return rep(r, alpha, beta, gamma, parity)

            m = o3.kron(*(re(kw_Rs[i]) for i in f0))
            return Q @ m @ Q.T

        # And check that after this projection it is still a representation
        assert _is_representation(representation, eps, has_parity)

        # The rest of the code simply extract the irreps present in this representation
        Rs_out = []
        A = Q.clone()
        for l in range(int((d_sym - 1) // 2) + 1):
            for p in [-1, 1] if has_parity else [0]:
                if 2 * l + 1 > d_sym - dim(Rs_out):
                    break

                mul, B, representation = o3.reduce(representation,
                                                   partial(rep, [(1, l, p)]),
                                                   eps, has_parity)
                A = o3.direct_sum(torch.eye(d_sym - B.shape[0]), B) @ A
                A = _round_sqrt(A, eps)
                Rs_out += [(mul, l, p)]

                if dim(Rs_out) == d_sym:
                    break

        if dim(Rs_out) != d_sym:
            raise RuntimeError(
                f'unable to decompose into irreducible representations')

        return simplify(Rs_out), A.to(dtype=dtype)
示例#3
0
def reduce_tensor(formula, eps=1e-9, has_parity=None, **kw_Rs):
    """
    Usage
    Rs, Q = rs.reduce_tensor('ijkl=jikl=ikjl=ijlk', i=[(1, 1)])
    Rs = 0,2,4
    Q = tensor of shape [15, 81]
    """
    with torch_default_dtype(torch.float64):
        formulas = [(-1 if f.startswith('-') else 1, f.replace('-', ''))
                    for f in formula.split('=')]
        s0, f0 = formulas[0]
        assert s0 == 1

        for _s, f in formulas:
            if len(set(f)) != len(f) or set(f) != set(f0):
                raise RuntimeError(f'{f} is not a permutation of {f0}')
            if len(f0) != len(f):
                raise RuntimeError(
                    f'{f0} and {f} don\'t have the same number of indices')

        formulas = {(s, tuple(f.index(i) for i in f0))
                    for s, f in formulas}  # set of generators (permutations)

        # create the entire group
        while True:
            n = len(formulas)
            formulas = formulas.union([(s, perm.inverse(p))
                                       for s, p in formulas])
            formulas = formulas.union([(s1 * s2, perm.compose(p1, p2))
                                       for s1, p1 in formulas
                                       for s2, p2 in formulas])
            if len(formulas) == n:
                break

        for i in kw_Rs:
            if not callable(kw_Rs[i]):
                Rs = convention(kw_Rs[i])
                if has_parity is None:
                    has_parity = any(p != 0 for _, _, p in Rs)
                if not has_parity and not all(p == 0 for _, _, p in Rs):
                    raise RuntimeError(
                        f'{format_Rs(Rs)} parity has to be specified everywhere or nowhere'
                    )
                if has_parity and any(p == 0 for _, _, p in Rs):
                    raise RuntimeError(
                        f'{format_Rs(Rs)} parity has to be specified everywhere or nowhere'
                    )
                kw_Rs[i] = Rs

        if has_parity is None:
            raise RuntimeError(f'please specify the argument `has_parity`')

        for _s, p in formulas:
            f = "".join(f0[i] for i in p)
            for i, j in zip(f0, f):
                if i in kw_Rs and j in kw_Rs and kw_Rs[i] != kw_Rs[j]:
                    raise RuntimeError(
                        f'Rs of {i} (Rs={format_Rs(kw_Rs[i])}) and {j} (Rs={format_Rs(kw_Rs[j])}) should be the same'
                    )
                if i in kw_Rs:
                    kw_Rs[j] = kw_Rs[i]
                if j in kw_Rs:
                    kw_Rs[i] = kw_Rs[j]

        for i in f0:
            if i not in kw_Rs:
                raise RuntimeError(f'index {i} has not Rs associated to it')

        e = (0, 0, 0, 0) if has_parity else (0, 0, 0)
        full_base = list(
            itertools.product(*(range(
                len(kw_Rs[i](*e)) if callable(kw_Rs[i]) else dim(kw_Rs[i]))
                                for i in f0)))

        base = set()
        for x in full_base:
            xs = {(s, tuple(x[i] for i in p)) for s, p in formulas}
            # s * T[x] all equal for (s, x) in xs
            if not (-1, x) in xs:
                # the sign is arbitrary, put both possibilities
                base.add(
                    frozenset(
                        {frozenset(xs),
                         frozenset({(-s, x)
                                    for s, x in xs})}))

        base = sorted([
            sorted([sorted(xs) for xs in x]) for x in base
        ])  # requested for python 3.7 but not for 3.8 (probably a bug in 3.7)

        d_sym = len(base)
        d = len(full_base)
        Q = torch.zeros(d_sym, d)

        for i, x in enumerate(base):
            x = max(x, key=lambda xs: sum(s for s, x in xs))
            for s, e in x:
                j = full_base.index(e)
                Q[i, j] = s / len(x)**0.5

        assert torch.allclose(Q @ Q.T, torch.eye(d_sym))

        if d_sym == 0:
            return [], torch.zeros(d_sym, d)

        def representation(alpha, beta, gamma, parity=None):
            def re(r):
                if callable(r):
                    if has_parity:
                        return r(alpha, beta, gamma, parity)
                    return r(alpha, beta, gamma)
                return rep(r, alpha, beta, gamma, parity)

            m = o3.kron(*(re(kw_Rs[i]) for i in f0))
            return Q @ m @ Q.T

        assert _is_representation(representation, eps, has_parity)

        Rs_out = []
        A = Q.clone()
        for l in range(int((d_sym - 1) // 2) + 1):
            for p in [-1, 1] if has_parity else [0]:
                if 2 * l + 1 > d_sym - dim(Rs_out):
                    break

                mul, B, representation = o3.reduce(representation,
                                                   partial(rep, [(1, l, p)]),
                                                   eps, has_parity)
                A = o3.direct_sum(torch.eye(d_sym - B.shape[0]), B) @ A
                A = _round_sqrt(A, eps)
                Rs_out += [(mul, l, p)]

                if dim(Rs_out) == d_sym:
                    break

        if dim(Rs_out) != d_sym:
            raise RuntimeError(
                f'unable to decompose into irreducible representations')
        return simplify(Rs_out), A