Exemple #1
0
def colour_singlets(operators: List[Operator], overcomplete=False):
    """Contracts colour indices into singlets."""
    result = []
    for op in operators:
        # collect colour indices
        colour_indices = []
        for i in op.free_indices:
            if i.index_type == Index.get_index_types()["c"]:
                colour_indices.append(i)

        # separate up and down indices
        ups, downs = [], []
        for i in colour_indices:
            if i.is_up:
                ups.append(i)
            else:
                downs.append(i)

        # can only contract into singlet with deltas if equal number of raised
        # and lowered colour indices. Otherwise need to contract with epsilons ΔL
        # = 2 EFT only contains 2 and 4 quark operators up to dim 11, so no need
        # to implement contraction with epsilons yet. Make exception for simple
        # contraction with epsilon

        if len(ups) == 3 and not downs:
            result.append(op * eps(" ".join(str(-i) for i in ups)))
        elif len(downs) == 3 and not ups:
            result.append(op * eps(" ".join(str(-i) for i in downs)))
        elif len(downs) == 3 and len(ups) == 3:
            result.append(
                op
                * eps(" ".join(str(-i) for i in ups))
                * eps(" ".join(str(-i) for i in downs))
            )
        elif len(ups) != len(downs):
            raise ValueError("Cannot contract colour indices into a singlet.")

        delta_index_combos = [tuple(zip(perm, downs)) for perm in permutations(ups)]
        for combo in delta_index_combos:
            deltas = [delta(" ".join([(-j).label, (-i).label])) for i, j in combo]

            # multiply deltas into operator
            prod = op
            for d in deltas:
                prod *= d
            result.append(prod)

        if len(deltas) == 2 and overcomplete:
            a, b = deltas
            upa, downa = a.indices
            upb, downb = b.indices
            dummy = Index.fresh("c")
            up_index_str = " ".join(str(i) for i in (upa, upb, dummy))
            down_index_str = " ".join(str(i) for i in (downa, downb, -dummy))
            up_epsilon = eps(up_index_str)
            down_epsilon = eps(down_index_str)
            # append additional structure to results
            result.append(op * up_epsilon * down_epsilon)

    return result
Exemple #2
0
def contract_su2_helper(
    left: Indexed, right: Indexed, out: Union[int, str], index_type: str
):
    """Returns epsilon structures so that `left * right * epsilon` for each
    structure transforms as `out`.

    Example:
        >>> contract_su2_helper(left=L("u0 i0"), right=L("u1 i1"), out=0, index_type="i")
        [eps(-i0, -i1)]

    """
    # create an index dict mapping index type to position in dynkin str
    index_dict = {}
    for pos, (_, label) in enumerate(Index.get_dynkin_labels()):
        index_dict[label] = pos

    # if out is a string, take to be dynkin string and extract appropriate
    # dynkin label for index type
    if isinstance(out, int):
        n_target_indices = out
    else:
        n_target_indices = int(out[index_dict[index_type]])

    n_input_indices = (
        left.dynkin_ints[index_dict[index_type]]
        + right.dynkin_ints[index_dict[index_type]]
    )

    assert n_target_indices <= n_input_indices
    assert (n_target_indices - n_input_indices) % 2 == 0

    # if target indices == input indices, no need for epsilons
    if n_target_indices == n_input_indices:
        return [1]

    # get indices of index_type from tensors
    left_indices = left.indices_by_type[Index.get_index_types()[index_type]]
    right_indices = right.indices_by_type[Index.get_index_types()[index_type]]

    # There is in general more than one way to contract the indices.
    # Construct every possible way and return a list of results.

    # get different epsilon combinations
    shortest, longest = sorted([left_indices, right_indices], key=len)
    eps_index_combos = [tuple(zip(perm, shortest)) for perm in permutations(longest)]

    results = []
    for combo in eps_index_combos:
        prod = 1
        counter = n_input_indices
        for i, j in combo:
            prod *= eps(" ".join([(-i).label, (-j).label]))
            counter -= 2
            if counter == n_target_indices:
                results.append(prod)

    return results
Exemple #3
0
def test_extract_relabellings():
    a = [eps("-i0 -i2"), eps("-i3 -i1")]
    b = [eps("-i3 -i2"), eps("-i2 -i1")]
    # relabellings action on a
    #    0 -> 2, 2 -> 3, 3 -> 2
    #    1 -> 2, 0 -> 1
    relabellings1 = [
        (Index("-i0"), Index("-i2")),
        (Index("-i2"), Index("-i3")),
        (Index("-i3"), Index("-i2")),
    ]
    relabellings2 = [(Index("-i1"), Index("-i2")),
                     (Index("-i0"), Index("-i1"))]
    assert extract_relabellings(a, b) == [relabellings1, relabellings2]
Exemple #4
0
    def majorana_partner(self):
        """New copy of fermion with colour indices flipped"""
        undotted, dotted, colour, isospin, _ = Index.indices_by_type(
            self.indices).values()
        colour = tuple(i.conj for i in colour)
        indices = undotted + dotted + colour + isospin

        return MajoranaFermion(
            self.label,
            indices,
            latex=self.latex,
            is_conj=self.is_conj,
            symmetry=self.symmetry,
            comm=FERMI,
            charges=None,
        )
Exemple #5
0
    def swap_colour_indices(self):
        """New copy of field with colour indices flipped.

        # TODO Refactor this out with majorana_partner to FieldType
        """
        undotted, dotted, colour, isospin, _ = Index.indices_by_type(
            self.indices).values()
        colour = tuple(i.conj for i in colour)
        indices = undotted + dotted + colour + isospin

        return RealScalar(
            self.label,
            indices,
            latex=self.latex,
            is_conj=self.is_conj,
            symmetry=self.symmetry,
            comm=BOSE,
            charges=None,
        )
Exemple #6
0
def su2_singlets_type(op: Operator, index_type: str) -> List[Operator]:
    """Contracts su2 indices into singlets by type."""
    result = []
    # collect undotted indices
    indices = []
    for i in op.free_indices:
        if i.index_type == Index.get_index_types()[index_type]:
            indices.append(i)

    # can only contract into singlet if even number of indices
    if len(indices) % 2:
        raise ValueError("Cannot contract odd number of indices into a singlet.")

    for combo in epsilon_combos(indices):
        epsilons = [eps(" ".join([(-i).label, (-j).label])) for i, j in combo]

        # multiply epsilons into operator
        prod = op
        for e in epsilons:
            prod *= e
        result.append(prod)

    return result
Exemple #7
0
    def dirac_partner(self):
        undotted, dotted, colour, isospin, _ = Index.indices_by_type(
            self.indices).values()
        colour = tuple(i.conj for i in colour)
        indices = undotted + dotted + colour + isospin
        charges = {k: -v for k, v in self.charges.items()}

        is_unbarred = self.is_unbarred
        symb = "~"  # indicate bar with circumflex
        if is_unbarred:
            label = self.label + symb
        else:
            label = self.label.replace(symb, "")

        return VectorLikeDiracFermion(
            label,
            indices,
            charges=charges,
            latex=self.latex,
            is_unbarred=(not self.is_unbarred),
            is_conj=self.is_conj,
            symmetry=self.symmetry,
            comm=FERMI,
        )
Exemple #8
0
def exotic_field_and_term(
    op: Operator, symbols: Dict[str, List[str]], field_dict: Dict[tuple, str]
) -> Tuple[IndexedField, IndexedField, Union[Operator, str]]:
    """Returns exotic field, partner (that couples in Lagrangian) and Lagrangian
    term. Mutates field_dict with exotic field's symbol.

    The last item returned may be a string explaining why the contraction
    failed.

    """

    fields = [f for f in op.tensors if isinstance(f, IndexedField)]
    exotic_charges = {}
    pairs = list(map(lambda x: x.charges.items(), fields))

    # ensure no fields have missing or extra charges
    fst, *rst = pairs
    for pair in rst:
        assert len(pair) == len(fst)

    for n_pairs in zip(*pairs):
        # fish out key
        (k, _), *rst = n_pairs
        # sum over values
        exotic_charges[k] = sum(map(lambda x: x[1], n_pairs))

    indices_by_type = op.indices_by_type.values()
    exotic_undotted, exotic_dotted, exotic_colour, exotic_isospin, _ = map(
        sorted, indices_by_type
    )

    exotic_indices = " ".join(
        str(i)
        for i in [*exotic_undotted, *exotic_dotted, *exotic_colour, *exotic_isospin]
    )

    # establish fermion or boson for symbols
    lorentz_dynkin = get_dynkin(exotic_indices)[:2]
    if lorentz_dynkin in ("10", "01"):
        symbols_to_use = symbols["fermion"]
    else:
        symbols_to_use = symbols["boson"]

    fs = sorted([f.field for f in fields], key=lambda x: x.label_with_dagger)
    fs = tuple(fs)
    if fs in field_dict.keys():
        symbol = field_dict[fs]
    else:
        symbol = symbols_to_use.pop(0)
        # field_dict mutated here!
        field_dict[fs] = symbol

    # for Dirac and Majorana fermions, always keep plain symbol left handed
    to_conj = False
    if exotic_indices and exotic_indices[0] == "d":
        exotic_indices = Index.conj_index_string(exotic_indices)
        to_conj = True

    exotic_indexed_field = IndexedField(
        label=symbol, indices=exotic_indices, charges=exotic_charges
    )

    # construct MajoranaFermion, VectorLikeDiracFermion, ...
    # `partner` is in the Lagrangian, `exotic_field` transforms like contracted pair
    exotic_field = cons_completion_field(exotic_indexed_field)
    exotic_field = exotic_field.conj_indices if to_conj else exotic_field

    partner = exotic_field
    if isinstance(exotic_field, ComplexScalar):
        partner = exotic_field.conj
    elif isinstance(exotic_field, RealScalar):
        partner = exotic_field.swap_colour_indices()
    elif isinstance(exotic_field, VectorLikeDiracFermion):
        partner = exotic_field.dirac_partner()
    elif isinstance(exotic_field, MajoranaFermion):
        partner = exotic_field.majorana_partner()

    # Need additional su2 epsilons to fix su2 indices (since not working with
    # lowered indices at all). Won't need to do this if removing a derivative in
    # the process
    partner, fix_su2_epsilons = partner.lower_su2()
    term = reduce(lambda x, y: x * y, fix_su2_epsilons, op * partner)

    # construct term and check to see if vanishes. This is a very costly step,
    # check first whether there are any doubled up fields in the term and only
    # run on those
    set_fields = set([f.label for f in term.fields])
    if len(set_fields) < len(term.fields) and term.safe_simplify() == 0:
        return exotic_field, partner, f"Vanishing coupling at {term}"

    # need to construct term again because sympy is annoying
    term = reduce(lambda x, y: x * y, fix_su2_epsilons, op * partner)

    check_singlet(term)

    return exotic_field, partner, term
Exemple #9
0
def make_coupling(symbol: str, indices: str, sym=None):
    indices_list = indices.split()
    if sym is None:
        sym = [[1]] * len(indices_list)
    return tensorhead(symbol, [Index(i) for i in indices_list], sym)