Details: quotient algebra
Many equivalence relations can be handled directly at the level of monomials. Thus, the most efficient way of representing equality constraints is to provide a confluent rewriting system over a free monoid. These are the simplified equivalent of Groebner bases for polynomials. While the rewriting system provided by the user has to be confluent for SymDPoly to work correctly, we do not verify that property; accordingly, we do not require the user to specify the monomial ordering by which the confluence property can be verified. Additionally, we require that this ordering is graded: substitutions cannot increase the degree of monomials.
CHSH
For the CHSH example involving measurements with outcomes +1
, -1
, we have the following rules: the operators square to the identity, and the operators for different parties commute. This translates to:
import net.alasc.symdpoly._
import net.alasc.symdpoly.math.Phase
import defaults._
import net.alasc.symdpoly.examples.quantum.CHSH.Free
val Quotient = Free.quotientMonoid(quotient.pairs {
case (Free.A(x1), Free.A(x2)) if x1 == x2 => Free.one
case (Free.B(y1), Free.B(y2)) if y1 == y2 => Free.one
case (Free.B(y), Free.A(x)) => Free.A(x) * Free.B(y)
})
Collins-Gisin notation
Now, if we have more than two outcomes, we write using the Collins-Gisin basis:
val nOutputs = 3
val nInputs = 2
object FreeCG extends free.MonoDef(cyclotomicOrder = 1) {
case class A(a: Int, x: Int) extends HermitianOp
object A extends HermitianOpFamily2(0 to nOutputs - 1, 0 to nInputs - 1)
case class B(a: Int, x: Int) extends HermitianOp
object B extends HermitianOpFamily2(0 to nOutputs - 1, 0 to nInputs - 1)
val families = Seq(A, B)
}
val QuotientCG = FreeCG.quotientMonoid(quotient.pairs {
case (FreeCG.A(a1, x1), FreeCG.A(a2, x2)) if x1 == x2 && a1 == a2 => FreeCG.A(a1, x1)
case (FreeCG.A(a1, x1), FreeCG.A(a2, x2)) if x1 == x2 && a1 != a2 => FreeCG.zero
case (FreeCG.B(b1, y1), FreeCG.B(b2, y2)) if y1 == y2 && b1 == b2 => FreeCG.B(b1, y1)
case (FreeCG.B(b1, y1), FreeCG.B(b2, y2)) if y1 == y2 && b1 != b2 => FreeCG.zero
case (FreeCG.B(b, y), FreeCG.A(a, x)) => FreeCG.A(a, x) * FreeCG.B(b, y)
})
scala> QuotientCG.quotient(FreeCG.A(0, 1) * FreeCG.A(1, 1))
res0: QuotientCG.MonoType = [0]
scala> QuotientCG.quotient(FreeCG.A(0, 1) * FreeCG.A(0, 1))
res1: QuotientCG.MonoType = [A(0,1)]
Then, polynomials over those operators can be defined using the helper functions:
def allA(x: Int) = for(a <- 0 to nOutputs - 2) yield (QuotientCG.quotient(FreeCG.A(a, x).toPoly))
def A(a: Int, x: Int): QuotientCG.PolyType = if (a == nOutputs - 1) QuotientCG.one - allA(x).reduce(_ + _) else QuotientCG.quotient(FreeCG.A(a, x).toPoly)
def allB(y: Int) = for(b <- 0 to nOutputs - 2) yield (QuotientCG.quotient(FreeCG.B(b, y).toPoly))
def B(b: Int, y: Int): QuotientCG.PolyType = if (b == nOutputs - 1) QuotientCG.one - allB(y).reduce(_ + _) else QuotientCG.quotient(FreeCG.B(b, y).toPoly)
scala> A(0, 1)
res2: QuotientCG.PolyType = [A(0,1)]
scala> A(2, 1)
res3: QuotientCG.PolyType = [1] - [A(0,1)] - [A(1,1)]
scala> A(2, 1) * B(2, 1)
res4: QuotientCG.PolyType = [1] - [A(0,1)] - [A(1,1)] - [B(0,1)] - [B(1,1)] + [A(0,1) B(0,1)] + [A(0,1) B(1,1)] + [A(1,1) B(0,1)] + [A(1,1) B(1,1)]
Pauli matrices
Finally, we show an example where the rewrite rule includes a phase.
Note that we use cyclotomicOrder = 4
in order to use the roots of unity 1
, i
, -1
, -i
as phases.
object PauliFree extends free.MonoDef(cyclotomicOrder = 4) {
case class σ(i: Int) extends HermitianOp
object σ extends HermitianOpFamily1(1 to 3)
val families = Seq(σ)
}
import PauliFree.σ
def mod1(i: Int, n: Int): Int = ((i - 1) % n) + 1
val PauliQuotient = PauliFree.quotientMonoid(quotient.pairs {
case (σ(i), σ(j)) if i == j => PauliFree.one
case (σ(i), σ(j)) if mod1(i + 1, 3) == j => -σ(6 - i - j)*Phase.i
case (σ(i), σ(j)) if mod1(i + 2, 3) == j => σ(6 - i - j)*Phase.i
})
Here is the result:
scala> PauliQuotient.quotient(σ(1)*σ(2)*σ(3))
res5: PauliQuotient.MonoType = [-i]