proarrow-0.4.0.0: test/Props/OpenHypergraph/Generated.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE OverloadedLists #-}
{-# OPTIONS_GHC -Wno-orphans #-}
-- | Interpret typed free diagrams directly in Mat Integer and read their open hypergraphs
-- back into TNET Int. Compare entries modulo the machine word size. The generator changes
-- boundaries only by typed composition, tensor and trace; it never builds raw node lists.
-- Three wires of dimensions two or three keep the independent dense reference small.
module Props.OpenHypergraph.Generated (test) where
import Data.Foldable (toList)
import Data.Kind (Constraint, Type)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Type.Equality ((:~:) (..))
import Data.Type.Nat (Nat2, Nat3)
import Test.Falsify (Gen, Property, testFailed)
import Test.Falsify qualified as F
import Test.Falsify.Generator (elem, oneof)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Falsify (testProperty)
import Prelude hiding (Monoid, elem, fmap, id, mappend, mempty, (**), (.))
import Prelude qualified as P
import Proarrow.Category.Enriched.Thin (Member (..), memberIndex)
import Proarrow.Category.Instance.Bool (BOOL (..))
import Proarrow.Category.Instance.Discrete (DISCRETE (..))
import Proarrow.Category.Instance.Discrete qualified as Discrete
import Proarrow.Category.Instance.Free (FREE (..), Free, emb, fold)
import Proarrow.Category.Instance.Mat (Mat (..), MatK (..))
import Proarrow.Category.Instance.OpenHypergraph
( SIMPLIFY
, Wires
, Wiring
, copyDiscard
, frobenius
, layered
, permutations
, prim1
, simplify
, tracedWith
)
import Proarrow.Category.Instance.TensorNetwork (TNET (..))
import Proarrow.Category.Instance.TensorNetwork qualified as TNet
import Proarrow.Category.Monoidal (Monoidal (..), MonoidalProfunctor (..), SymMonoidal (..), UnitF, type (**!))
import Proarrow.Category.Monoidal.CompactClosed (CompactClosedStructures)
import Proarrow.Category.Monoidal.Hypergraph (traceHG)
import Proarrow.Category.Monoidal.Strictified (Strictified (..), type (++))
import Proarrow.Core (CAT, CategoryOf (..), Kind, Promonad (..), UN, obj, type (+->))
import Proarrow.Functor (FunctorForRep (..))
import Proarrow.Monoid (Comonoid (..), Monoid (..), Supplies)
import Proarrow.Object (pattern Objs)
import Proarrow.Profunctor.Representable (Rep)
import Proarrow.Testing (expect, genNamed)
import Props.Mat ()
type M2 :: MatK Integer
type M2 = M Nat2
type M3 :: MatK Integer
type M3 = M Nat3
type Dimension :: BOOL -> MatK Integer
type family Dimension a where
Dimension FLS = M2
Dimension TRU = M3
-- The discrete base has two object sorts, with independent endomorphisms and rectangular gates.
data Gate :: CAT (DISCRETE BOOL) where
Gate :: forall a b. String -> Mat (Dimension a) (Dimension b) -> Gate (D a) (D b)
instance Show (Gate a b) where
show (Gate name _) = name
data family Dense :: DISCRETE BOOL +-> MatK Integer
instance FunctorForRep Dense where
type Dense @ a = Dimension (UN D a)
fmap @a Discrete.Refl = case memberIndex @a of
Here -> obj @M2
There Here -> obj @M3
type NetworkDimension :: BOOL -> TNET Int
type family NetworkDimension a where
NetworkDimension FLS = TN '[2]
NetworkDimension TRU = TN '[3]
data family Graph :: DISCRETE BOOL +-> SIMPLIFY (TNET Int)
instance FunctorForRep Graph where
type Graph @ a = Wires '[NetworkDimension (UN D a)]
fmap @a Discrete.Refl = case memberIndex @a of
Here -> obj @(Wires '[TN '[2]])
There Here -> obj @(Wires '[TN '[3]])
denseGate :: Gate a b -> Dimension (UN D a) ~> Dimension (UN D b)
denseGate (Gate _ m) = m
networkGate :: forall a b. (Ob a, Ob b) => Gate a b -> NetworkDimension (UN D a) ~> NetworkDimension (UN D b)
networkGate g = case (memberIndex @a, memberIndex @b) of
(Here, Here) -> TNet.fromEntries rows
(Here, There Here) -> TNet.fromEntries rows
(There Here, Here) -> TNet.fromEntries rows
(There Here, There Here) -> TNet.fromEntries rows
where
rows :: [[Int]] = map (map fromInteger) (entries (denseGate g))
type Structures :: [Kind -> Constraint]
type Structures = CompactClosedStructures ++ '[Supplies Monoid, Supplies Comonoid]
type Diagram :: Kind
type Diagram = FREE Structures Gate
type A :: Diagram
type A = EMB (D FLS)
type B :: Diagram
type B = EMB (D TRU)
type Shape :: Diagram -> Type
data Shape a where
UnitShape :: Shape UnitF
TwoShape :: Shape A
ThreeShape :: Shape B
TensorShape :: (Ob a, Ob b) => Shape a -> Shape b -> Shape (a **! b)
data SomeDiagram where
SomeDiagram :: Shape a -> Shape b -> Free a b -> SomeDiagram
instance Show SomeDiagram where
show (SomeDiagram a b f) = showShape a ++ " -> " ++ showShape b ++ ": " ++ show f
showShape :: Shape a -> String
showShape UnitShape = "I"
showShape TwoShape = "2"
showShape ThreeShape = "3"
showShape (TensorShape a b) = "(" ++ showShape a ++ " ** " ++ showShape b ++ ")"
width :: Shape a -> Int
width UnitShape = 0
width TwoShape = 1
width ThreeShape = 1
width (TensorShape a b) = width a + width b
sameShape :: Shape a -> Shape b -> Maybe (a :~: b)
sameShape UnitShape UnitShape = Just Refl
sameShape TwoShape TwoShape = Just Refl
sameShape ThreeShape ThreeShape = Just Refl
sameShape (TensorShape a b) (TensorShape c d) = do
Refl <- sameShape a c
Refl <- sameShape b d
pure Refl
sameShape _ _ = Nothing
data Fragment = Symmetric | Copying | TracedSymmetric | TracedCopying | Hypergraphs
deriving (Eq)
copies :: Fragment -> Bool
copies Symmetric = False
copies TracedSymmetric = False
copies _ = True
traces :: Fragment -> Bool
traces Symmetric = False
traces Copying = False
traces _ = True
-- Each step is an existing Free morphism, not another syntax tree to interpret.
data Step a where
Step :: Shape b -> Free a b -> Step a
genStep :: forall a. (Ob a) => Fragment -> NonEmpty SomeDiagram -> Int -> Shape a -> Gen (Step a)
genStep fragment gates capacity shape = oneof (pure (Step shape id) :| options)
where
options =
[ pure (Step (TensorShape UnitShape shape) leftUnitorInv)
, pure (Step (TensorShape shape UnitShape) rightUnitorInv)
]
++ [pure (Step target f) | SomeDiagram source target f <- toList gates, Just Refl <- [sameShape source shape]]
++ case shape of
TensorShape a b ->
[ pure (Step (TensorShape b a) swap)
, do
Step a' f@Objs <- genStep fragment gates (capacity - width b) a
pure (Step (TensorShape a' b) (f ** obj))
, do
Step b' f@Objs <- genStep fragment gates (capacity - width a) b
pure (Step (TensorShape a b') (obj ** f))
]
++ [pure (Step a mappend) | fragment == Hypergraphs, Just Refl <- [sameShape a b]]
++ case (a, b) of
(UnitShape, _) -> [pure (Step b leftUnitor)]
(_, UnitShape) -> [pure (Step a rightUnitor)]
(TensorShape x y, _) -> [pure (Step (TensorShape x (TensorShape y b)) associator)]
(_, TensorShape y z) -> [pure (Step (TensorShape (TensorShape a y) z) associatorInv)]
_ -> []
UnitShape
| fragment == Hypergraphs
, capacity > 0 ->
[pure (Step TwoShape mempty), pure (Step ThreeShape mempty)]
_ -> []
++ [pure (Step UnitShape counit) | copies fragment]
++ [pure (Step (TensorShape shape shape) comult) | copies fragment, 2 * width shape <= capacity]
genDiagram :: Fragment -> NonEmpty SomeDiagram -> Int -> Gen SomeDiagram
genDiagram fragment gates = grow
where
starts =
[ SomeDiagram UnitShape UnitShape id
, SomeDiagram TwoShape TwoShape id
, SomeDiagram ThreeShape ThreeShape id
, SomeDiagram (TensorShape TwoShape ThreeShape) (TensorShape TwoShape ThreeShape) id
, SomeDiagram (TensorShape ThreeShape TwoShape) (TensorShape ThreeShape TwoShape) id
]
grow 0 = oneof (elem gates :| [elem starts])
grow n = do
SomeDiagram a b f@Objs <- grow (n - 1)
let extend = do
Step c g <- genStep fragment gates 3 b
pure (SomeDiagram a c (g . f))
tensor =
[ do
SomeDiagram c d g@Objs <- elem gates
left <- elem [False, True]
pure $
if left
then SomeDiagram (TensorShape a c) (TensorShape b d) (f ** g)
else SomeDiagram (TensorShape c a) (TensorShape d b) (g ** f)
| max (width a) (width b) < 3
]
feedback = case (a, b) of
(TensorShape u x, TensorShape v y)
| traces fragment
, width u == 1
, Just Refl <- sameShape u v ->
[pure (SomeDiagram x y (traceHG f))]
_ -> []
oneof (extend :| (tensor ++ feedback))
primitives :: Property (NonEmpty SomeDiagram)
primitives = do
f22 <- integerMatrix <$> genNamed @(Mat (M Nat2 :: MatK Int) (M Nat2)) "f22"
g22 <- integerMatrix <$> genNamed @(Mat (M Nat2 :: MatK Int) (M Nat2)) "g22"
f23 <- integerMatrix <$> genNamed @(Mat (M Nat2 :: MatK Int) (M Nat3)) "f23"
f32 <- integerMatrix <$> genNamed @(Mat (M Nat3 :: MatK Int) (M Nat2)) "f32"
f33 <- integerMatrix <$> genNamed @(Mat (M Nat3 :: MatK Int) (M Nat3)) "f33"
g33 <- integerMatrix <$> genNamed @(Mat (M Nat3 :: MatK Int) (M Nat3)) "g33"
pure
[ SomeDiagram TwoShape TwoShape (emb (Gate @FLS @FLS "f22" f22))
, SomeDiagram TwoShape TwoShape (emb (Gate @FLS @FLS "g22" g22))
, SomeDiagram TwoShape ThreeShape (emb (Gate @FLS @TRU "f23" f23))
, SomeDiagram ThreeShape TwoShape (emb (Gate @TRU @FLS "f32" f32))
, SomeDiagram ThreeShape ThreeShape (emb (Gate @TRU @TRU "f33" f33))
, SomeDiagram ThreeShape ThreeShape (emb (Gate @TRU @TRU "g33" g33))
]
-- Reuse the small integer generators, then compute in an unbounded ring.
integerMatrix :: Mat (M m :: MatK Int) (M n) -> Mat (M m :: MatK Integer) (M n)
integerMatrix (Mat rows) = Mat (P.fmap (P.fmap toInteger) rows)
entries :: Mat (a :: MatK Integer) b -> [[Integer]]
entries (Mat rows) = map toList (toList rows)
-- TNET varies the rightmost wire fastest; Mat's tensor varies the left factor fastest.
-- For each TNET boundary index, give the corresponding index of the dense reference.
basisOrder :: Shape a -> [Int]
basisOrder UnitShape = [0]
basisOrder TwoShape = [0, 1]
basisOrder ThreeShape = [0, 1, 2]
basisOrder (TensorShape a b) =
let left = basisOrder a
in [i + length left * j | i <- left, j <- basisOrder b]
compareReadBack :: [(String, Wiring (TNET Int))] -> SomeDiagram -> Property ()
compareReadBack readers diagram@(SomeDiagram a b f) = do
let dense = entries (fold @Structures @(Rep Dense) denseGate f)
expected :: [[Int]] = [[fromInteger (dense !! i !! j) | j <- basisOrder a] | i <- basisOrder b]
graph = fold @Structures @(Rep Graph) (prim1 . networkGate) f
mapM_
( \(name, wiring) -> case simplify wiring graph of
Left err -> testFailed (name ++ ": " ++ err ++ "\n" ++ show diagram) :: Property ()
Right (Str actual) -> expect (name ++ ": " ++ show diagram) expected (TNet.entries actual)
)
(readers ++ [("layered " ++ name, layered wiring) | (name, wiring) <- readers])
test :: TestTree
test =
testGroup
"generated diagrams"
[ testProperty name $ do
gates <- primitives
size <- F.gen (elem [3 .. 6])
diagram <- F.gen (genDiagram fragment gates size)
compareReadBack [(name, wiring), ("hypergraph", other)] diagram
| (name, fragment, wiring, other) <-
[ ("symmetric monoidal", Symmetric, permutations, frobenius)
, ("copy/discard", Copying, copyDiscard, frobenius)
, ("traced symmetric monoidal", TracedSymmetric, tracedWith permutations, frobenius)
, ("traced copy/discard", TracedCopying, tracedWith copyDiscard, frobenius)
, ("hypergraph", Hypergraphs, frobenius, tracedWith frobenius)
]
]
-- For GHC 9.10
instance MonadFail Gen where
fail = error