Skip to content

File su2dagger.h

File List > algebra > su2algebra > su2dagger.h

Go to the documentation of this file

#ifndef COSMOINTERFACE_SU2ALGEBRA_SU2DAGGER_H
#define COSMOINTERFACE_SU2ALGEBRA_SU2DAGGER_H

/* This file is part of TempLat, available at https://cosmolattice.github.io/templat .
   Copyright 2021-2026 The TempLat authors, see AUTHORS.md.
   Released under the MIT license, see LICENSE.md. */

// File info: Main contributor(s): Adrien Florio, Franz R. Sattler, Year: 2019

#include <type_traits>

#include "TempLat/lattice/algebra/su2algebra/helpers/hassu2get.h"
#include "TempLat/util/rangeiteration/tagliteral.h"
#include "TempLat/lattice/algebra/su2algebra/su2unaryoperator.h"
#include "TempLat/lattice/algebra/su2algebra/su2multiply.h"
#include "TempLat/lattice/algebra/su2algebra/su2shift.h"
#include "TempLat/lattice/algebra/su2algebra/helpers/su2getgetreturntype.h"
#include "TempLat/lattice/algebra/helpers/doeval.h"

namespace TempLat
{
  template <typename R> class SU2Dagger : public SU2UnaryOperator<R>
  {
  public:
    using SV = typename SU2GetGetReturnType<R>::type;
    using SU2UnaryOperator<R>::mR;

    // Put public methods here. These should change very little over time.
    SU2Dagger(const R &pR) : SU2UnaryOperator<R>(pR) {}

    template <int N> auto SU2Get(Tag<N> t) const
    {
      static_assert(N >= 0 && N <= 3, "SU2Get: N must be between 0 and 3 for SU2Dagger");
      if constexpr (N == 0) {
        return mR.SU2Get(Tag<N>());
      } else {
        return -mR.SU2Get(Tag<N>());
      }
    }

    template <int N> auto operator()(Tag<N> t) const
    {
      static_assert(N >= 0 && N <= 3, "Operator(): N must be between 0 and 3 for SU2Dagger");
      return SU2Get(t);
    }

    template <typename... IDX>
      requires requires(std::decay_t<R> r, IDX... idx) {
        requires IsVariadicIndex<IDX...>;
        DoEval::eval(r, idx...);
      }
    DEVICE_INLINE_FUNCTION auto eval(const IDX &...idx) const
    {
      auto c = DoEval::eval(mR, idx...);
      c[1] = -c[1];
      c[2] = -c[2];
      c[3] = -c[3];
      return c;
    }

    std::string toString() const { return GetString::get(mR) + "^\u2020"; }
  };

  template <class R>
    requires HasSU2Get<R>
  auto dagger(const R &r)
  {
    return SU2Dagger<R>(r);
  };

  // ---------------------------------------------------------------------------------------------------
  // Algebraic simplification of dagger expressions.
  //
  // SU(2) dagger is the quaternion conjugate, an anti-involution: (U^dagger)^dagger = U and
  // (A.B)^dagger = B^dagger.A^dagger, valid for any quaternion-valued expression (unit or scaled). We
  // apply these ONLY when they remove a dagger (i.e. when a sub-dagger cancels): the plain
  // anti-homomorphism dag(A.B) -> dag(B).dag(A) on two non-dagger operands would turn one 3-component
  // negation into two, a pessimisation, so we never fire it there. The dag(dag U) -> U case is also
  // folded by the optimiser at runtime, but eliminating it here additionally shrinks the instantiated
  // type. Net effect: removes daggers the back-end optimiser cannot (it does not know the quaternion
  // anti-homomorphism), and exposes leaf-level operands for common-subexpression elimination.
  // ---------------------------------------------------------------------------------------------------

  template <class T> struct IsSU2DaggerT : std::false_type {
  };
  template <class R> struct IsSU2DaggerT<SU2Dagger<R>> : std::true_type {
  };
  template <class T>
  concept IsSU2Dagger = IsSU2DaggerT<std::decay_t<T>>::value;

  // Involution: (U^dagger)^dagger = U.
  template <class R> auto dagger(const SU2Dagger<R> &r) { return r.getOperand(); }

  // Anti-homomorphism, fired only when at least one operand is itself a dagger, so that the rewrite
  // cancels that dagger through the involution above: (A.B)^dagger = B^dagger.A^dagger.
  template <class A, class B>
    requires(IsSU2Dagger<A> || IsSU2Dagger<B>)
  auto dagger(const SU2Multiplication<A, B> &m)
  {
    return dagger(m.getSecond()) * dagger(m.getFirst());
  }

  // Commute dagger past a shift: (shift(X))^dagger = shift(X^dagger). A shift is a coordinate relabeling
  // and the dagger a per-site quaternion conjugation, so they commute; cost-neutral at eval. Unlike the
  // anti-homomorphism this fires unconditionally - it lets nested dag(shift(dag(Y))) collapse to
  // shift(Y) via the involution above (e.g. inside the axion-current theta_ijk).
  template <class R, int... N> auto dagger(const SU2Shifter<R, N...> &s) { return shift<N...>(dagger(s.getOperand())); }
  template <class R, int N> auto dagger(const SU2ShifterByOne<R, N> &s) { return shift<N>(dagger(s.getOperand())); }

  template <class R>
    requires HasSU2Get<R>
  auto dag(const R &r)
  {
    return dagger(r);
  };
} // namespace TempLat

#endif