Details: free algebra
A free algebra is a polynomial ring in noncommutative variables. Variables are standard Scala objects and can bear indices and flags. Those objects must exist in an object inheriting free.MonoDef
, and themselves inherit Op
.
Free algebras are defined as monoid rings over a free monoid (with little added mathematical structure, see below); the ring we use is the field of cyclotomic numbers.
Definitions
We return to our CHSH example, but removing the syntactic sugar present in the simple example:
import net.alasc.symdpoly._
import math.Phase
import defaults._
object Free extends free.MonoDef(cyclotomicOrder = 2) {
case class A(x: Int) extends Op {
def adjoint: Op = this
}
object A extends OpFamily {
val allInstances = Seq(A(0), A(1))
}
case class B(y: Int) extends Op {
def adjoint: Op = this
}
object B extends OpFamily {
val allInstances = Seq(B(0), B(1))
}
val families = Seq(A, B)
}
import Free.{A, B}
scala> (A(0)*B(0)).adjoint
res0: net.alasc.symdpoly.freebased.Mono[Free.type,Free.Free] = B(0) A(0)
We can also represent non-Hermitian variables:
object Free1 extends free.MonoDef(cyclotomicOrder = 4) {
case class X(i: Int, isAdjoint: Boolean = false) extends Op {
override def toString = if (isAdjoint) s"X($i)*" else s"X($i)"
def adjoint: Op = X(i, !isAdjoint)
}
object X extends OpFamily {
val allInstances = Seq(X(0), X(1), X(2), X(0).adjoint, X(1).adjoint, X(2).adjoint)
}
val families = Seq(X)
}
import Free1.X
scala> (X(0)*X(1)).adjoint
res1: net.alasc.symdpoly.freebased.Mono[Free1.type,Free1.Free] = X(1)* X(0)*
Note that Scala’s type system prevents us from mixing operators coming from different free monoids:
scala> X(0)*A(0)
<console>:24: error: overloaded method value * with alternatives:
(rhs: net.alasc.symdpoly.generic.MonoLike[Free1.Free])Free1.Free#MonoType <and>
(rhs: cyclo.Cyclo)Free1.Free#PolyType <and>
(rhs: spire.math.Rational)Free1.Free#PolyType <and>
(rhs: Int)Free1.Free#PolyType <and>
(rhs: Free1.Free#PolyType)Free1.Free#PolyType <and>
(rhs: net.alasc.symdpoly.math.Phase)(implicit d: DummyImplicit)net.alasc.symdpoly.free.PhasedOp[Free1.Free]
cannot be applied to (Free.A)
X(0)*A(0)
^
Syntactic sugar
To ease the boilerplate of defining allInstances
and the adjoint
method for Hermitian operators, we can use the following shortcuts.
object FreeSimplified extends free.MonoDef(2) {
case class A(x: Int) extends HermitianOp // defines an `adjoint` method that is the identity
object A extends HermitianOpFamily1(0 to 1) // enumerates the instances from a given Range, if the operator has a single index
case class B(y: Int) extends HermitianOp
object B extends HermitianOpFamily1(0 to 1)
val families = Seq(A, B)
}
The same holds for non-Hermitian variables.
object Free1Simplified extends free.MonoDef(4) {
case class X(i: Int, isAdjoint: Boolean = false) extends Op {
override def toString = if (isAdjoint) s"X($i)*" else s"X($i)"
def adjoint: Op = X(i, !isAdjoint)
}
object X extends OpFamily1(0 to 2)
val families = Seq(X)
}
scala> Free1Simplified.X.allInstances
res3: Seq[Free1Simplified.Op] = Vector(X(0), X(0)*, X(1), X(1)*, X(2), X(2)*)
Additional structure compared to free monoids
Compared to standard *
-free monoids, we already include the following structure at the level of our free monoids.
Our free monoids are *
-monoids
Our monoids have an involution compatible with multiplication.
scala> val x = X(0)*X(1).adjoint
x: Free1.Free#MonoType = X(0) X(1)*
scala> val y = X(1)*X(0)
y: Free1.Free#MonoType = X(1) X(0)
scala> (x*y).adjoint
res4: net.alasc.symdpoly.freebased.Mono[Free1.type,Free1.Free] = X(0)* X(1)* X(1) X(0)*
scala> (y.adjoint)*(x.adjoint)
res5: net.alasc.symdpoly.freebased.Mono[Free1.type,Free1.Free] = X(0)* X(1)* X(1) X(0)*
Self-adjoint elements are handled
Instead of defining an equivalence rule X(0) ~ X(0).adjoint
at the level of the quotient monoid, we already have an identity between operator variables and their adjoints when the variable is Hermitian.
scala> A(0).adjoint
res6: Free.Op = A(0)
Our free monoids are binoids
Binoids are monoids with an absorbing element, which we denote by zero (see the thesis of Simone Boettger). This absorbing element is unique.
scala> Free1.zero * X(0)
res7: Free1.MonoType = 0
scala> X(0) * Free1.zero
res8: Free1.Free#MonoType = 0
scala> Free1.zero * Free1.zero
res9: net.alasc.symdpoly.freebased.Mono[Free1.type,Free1.Free] = 0
Polynomials over free monoids
First and foremost, we can have polynomials whose monomials are elements of a free monoid (excluding zero):
scala> val p = A(0) * B(0) + A(1) * B(0) + A(0) * B(1) - A(1) * B(1)
p: Free.PolyType = A(0) B(0) + A(0) B(1) + A(1) B(0) - A(1) B(1)
scala> p * p - p
res10: Free.PolyType = - A(0) B(0) - A(0) B(1) - A(1) B(0) + A(1) B(1) + A(0) B(0) A(0) B(0) + A(0) B(0) A(0) B(1) + A(0) B(0) A(1) B(0) - A(0) B(0) A(1) B(1) + A(0) B(1) A(0) B(0) + A(0) B(1) A(0) B(1) + A(0) B(1) A(1) B(0) - A(0) B(1) A(1) B(1) + A(1) B(0) A(0) B(0) + A(1) B(0) A(0) B(1) + A(1) B(0) A(1) B(0) - A(1) B(0) A(1) B(1) - A(1) B(1) A(0) B(0) - A(1) B(1) A(0) B(1) - A(1) B(1) A(1) B(0) + A(1) B(1) A(1) B(1)
Due to syntax issues in the Scala programming language, you must multiply by scalars on the right, or either use the left multiplication *:
operator.
scala> p * sqrt(2)
res11: Free.PolyType = sqrt(2) A(0) B(0) + sqrt(2) A(0) B(1) + sqrt(2) A(1) B(0) - sqrt(2) A(1) B(1)
scala> sqrt(2) *: p
res12: net.alasc.symdpoly.freebased.Poly[Free.type,Free.Free] = sqrt(2) A(0) B(0) + sqrt(2) A(0) B(1) + sqrt(2) A(1) B(0) - sqrt(2) A(1) B(1)
Due to the same syntax issue, constant terms must be added from the right:
scala> p - sqrt(2)*2
res13: Free.PolyType = - 2*sqrt(2) 1 + A(0) B(0) + A(0) B(1) + A(1) B(0) - A(1) B(1)
or use a cumbersome syntax:
scala> Free.one*(sqrt(2)*2)
res14: Free.PolyType = 2*sqrt(2) 1
scala> Free.one*(sqrt(2)*2) - p
res15: Free.PolyType = 2*sqrt(2) 1 - A(0) B(0) - A(0) B(1) - A(1) B(0) + A(1) B(1)
This will fail:
scala> sqrt(2)*2 - p
<console>:25: error: overloaded method value - with alternatives:
(rhs: cyclo.Cyclo)cyclo.Cyclo <and>
(r: spire.math.Rational)cyclo.Cyclo
cannot be applied to (Free.PolyType)
sqrt(2)*2 - p
^
as for binary operations, the left term handles the evaluation, and thus it should be the most complex type present.
Other objects on free monoids
Likewise Other objects include operators with a phase, used to define symmetries:
scala> A(0) -> -A(0)
res17: (Free.A, net.alasc.symdpoly.free.PhasedOp[Free.Free]) = (A(0),- A(0))
and monomials with a phase, useful to define substitution rules:
scala> X(0) * X(1) -> X(2) * Phase.minusI
res18: (Free1.Free#MonoType, net.alasc.symdpoly.free.PhasedOp[Free1.Free]) = (X(0) X(1),-i X(2))
When constructing the moment matrix, SymDPoly processes monomials with a phase.