packages feed

valuations-0.0.4: src/Data/Valuation/PresheafValuationAlgebra.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# OPTIONS_GHC -Wall -Werror #-}

-- | The presheaf formulation of a valuation algebra, following
-- Shenoy & Shafer (1990), Kohlas (2003), and Abramsky & Carù (2019).
--
-- In this formulation, valuations form a presheaf F over a domain lattice:
--
-- * For each domain d, F(d) is the set of valuations with domain d
-- * For d' <= d, the restriction map rho_{d,d'}: F(d) -> F(d') implements marginalisation
-- * Combination is a family of maps: F(d1) x F(d2) -> F(d1 \/ d2)
--
-- A 'PresheafValuationAlgebra' bundles a 'DomainLattice' with a 'ValuationAlgebra',
-- providing all the structure needed for the presheaf formulation with
-- operations that work directly on 'Valuation' values.
module Data.Valuation.PresheafValuationAlgebra
  ( PresheafValuationAlgebra (..),
    PresheafValuationAlgebra',
    SetPresheafValuationAlgebra,
    SetPresheafValuationAlgebra',
    HasPresheafValuationAlgebra (..),
    AsPresheafValuationAlgebra (..),
    marginalise,
    combine,
    neutralValuation,
    nullValuation,
    presheafCombineSemigroup,

    -- * laws
    lawTransitivity,
    lawCombinationDomain,
    lawMarginalisationIdentity,
    lawNeutralCombination,
    lawNullCombination,
    lawCombinationCommutative,
  )
where

import Control.Lens (Lens', Prism', review, view, _Wrapped)
import Data.Set (Set)
import Data.Valuation.DomainLattice
  ( DomainLattice (..),
    HasDomainLattice (..),
    runDomainJoin,
    runDomainLeq,
  )
import Data.Valuation.ProjectValuation (HasProjectValuation (..))
import Data.Valuation.SemiValuationAlgebra
  ( HasSemiValuationAlgebra (..),
  )
import Data.Valuation.Semigroup
  ( HasSemigroup (..),
    Semigroup',
    applySemigroup,
    runSemigroup,
  )
import Data.Valuation.Valuation
  ( HasValuation (valuationDomain, valuationInformation),
    Valuation (..),
  )
import Data.Valuation.ValuationAlgebra
  ( HasValuationAlgebra (..),
    ValuationAlgebra (..),
  )
import Prelude hiding (Semigroup)

-- $setup
-- >>> :set -Wno-name-shadowing -Wno-type-defaults
-- >>> import qualified Data.Set as Set
-- >>> import Control.Lens (review)
-- >>> import Data.Valuation.Semigroup (applySemigroup, runSemigroup)
-- >>> import Data.Valuation.DomainLattice (setDomainLattice, runDomainJoin)
-- >>> import Data.Valuation.SemiValuationAlgebra (SemiValuationAlgebra(..))
-- >>> import Data.Valuation.ProjectValuation (ProjectValuation(..))
-- >>> import Data.Valuation.ValuationAlgebraOp (ValuationAlgebraOp(..))
-- >>> import Prelude hiding (Semigroup)

-- |
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> let v1 = Valuation (Set.fromList [1,2]) 10 :: Valuation Set Int Int
-- >>> let v2 = Valuation (Set.fromList [2,3]) 20
-- >>> combine pva v1 v2
-- Valuation (fromList [1,2,3]) 30
data PresheafValuationAlgebra p q r s v set var
  = PresheafValuationAlgebra
      -- | lattice structure on domains
      (DomainLattice p (set var) (set var))
      -- | the valuation algebra
      (ValuationAlgebra q r s v set var)

type PresheafValuationAlgebra' v set var =
  PresheafValuationAlgebra (->) (->) (->) (->) v set var

-- | A 'PresheafValuationAlgebra' specialised to 'Set'.
type SetPresheafValuationAlgebra p q r s v var =
  PresheafValuationAlgebra p q r s v Set var

type SetPresheafValuationAlgebra' v var =
  SetPresheafValuationAlgebra (->) (->) (->) (->) v var

-- | Classy lens for types that contain a 'PresheafValuationAlgebra'.
class HasPresheafValuationAlgebra c p q r s v set var | c -> p q r s v set var where
  presheafValuationAlgebra :: Lens' c (PresheafValuationAlgebra p q r s v set var)

instance HasPresheafValuationAlgebra (PresheafValuationAlgebra p q r s v set var) p q r s v set var where
  presheafValuationAlgebra = id

-- | Classy prism for types that can be constructed from a 'PresheafValuationAlgebra'.
class AsPresheafValuationAlgebra c p q r s v set var | c -> p q r s v set var where
  _PresheafValuationAlgebra :: Prism' c (PresheafValuationAlgebra p q r s v set var)

instance AsPresheafValuationAlgebra (PresheafValuationAlgebra p q r s v set var) p q r s v set var where
  _PresheafValuationAlgebra = id

instance HasDomainLattice (PresheafValuationAlgebra p q r s v set var) p (set var) (set var) where
  domainLattice f (PresheafValuationAlgebra l a) = fmap (`PresheafValuationAlgebra` a) (f l)

instance HasValuationAlgebra (PresheafValuationAlgebra p q r s v set var) q r s v set var where
  valuationAlgebra f (PresheafValuationAlgebra l a) = fmap (PresheafValuationAlgebra l) (f a)

instance HasSemiValuationAlgebra (PresheafValuationAlgebra p q r s v set var) q r s v set var where
  semiValuationAlgebra = valuationAlgebra . semiValuationAlgebra

instance HasSemigroup (PresheafValuationAlgebra p q r s v set var) q v where
  semigroup = semiValuationAlgebra . semigroup

instance HasProjectValuation (PresheafValuationAlgebra p q r s v set var) r s v set var where
  projectValuation = semiValuationAlgebra . projectValuation

-- | Marginalise a valuation to a subdomain: the restriction map of the presheaf.
--
-- Given a target domain @d'@ and a valuation phi with domain @d@,
-- computes @phi↓d'@ with @d(phi↓d') = d'@.
--
-- This is the presheaf restriction map: @rho_{d,d'}: F(d) -> F(d')@.
--
-- The caller should ensure @d' <= d(phi)@.
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\s v -> v + Set.size s))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> marginalise pva (Set.fromList [1]) (Valuation (Set.fromList [1,2]) 10)
-- Valuation (fromList [1]) 11
{-# SPECIALIZE marginalise ::
  PresheafValuationAlgebra' v set var -> set var -> Valuation set var v -> Valuation set var v
  #-}
marginalise :: (HasProjectValuation algebra (->) (->) v set var, HasValuation valuation set' var' v) => algebra -> set var -> valuation -> Valuation set var v
marginalise algebra targetDomain =
  Valuation targetDomain . view (projectValuation . _Wrapped) algebra targetDomain . view valuationInformation

-- | Combine two valuations: the combination operation of the valuation algebra.
--
-- Computes @phi ⊗ psi@ with @d(phi ⊗ psi) = d(phi) \/ d(psi)@.
--
-- The information values are combined using the algebra's semigroup,
-- and the result has the joined domain.
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> combine pva (Valuation (Set.fromList [1,2]) 10) (Valuation (Set.fromList [2,3]) 20)
-- Valuation (fromList [1,2,3]) 30
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (*)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 1)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> combine pva (Valuation (Set.fromList [1]) 3) (Valuation (Set.fromList [2]) 4)
-- Valuation (fromList [1,2]) 12
{-# SPECIALIZE combine ::
  PresheafValuationAlgebra' v set var -> Valuation set var v -> Valuation set var v -> Valuation set var v
  #-}
combine ::
  (HasSemigroup s1 (->) a, HasDomainLattice s1 (->) (set var) (set var), HasValuation s2 set var a, HasValuation s3 set var a) => s1 -> s2 -> s3 -> Valuation set var a
combine alg phi =
  Valuation . runSemigroup (view domainLatticeJoin alg) (view valuationDomain phi) . view valuationDomain <*> runSemigroup (view semigroup alg) (view valuationInformation phi) . view valuationInformation

-- | The neutral valuation for a domain: @e_d@ such that @e_d ⊗ phi = phi@
-- for all phi with @d(phi) <= d@.
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 99)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> neutralValuation pva (Set.fromList [1,2])
-- Valuation (fromList [1,2]) 0
{-# SPECIALIZE neutralValuation ::
  PresheafValuationAlgebra' v set var -> set var -> Valuation set var v
  #-}
neutralValuation :: (HasValuationAlgebra s (->) (->) (->) a set var) => s -> set var -> Valuation set var a
neutralValuation algebra =
  Valuation <*> view (valuationAlgebra . valuationAlgebraUnit . _Wrapped) algebra

-- | The null/zero valuation for a domain: @z_d@ such that @z_d ⊗ phi = z_{d \/ d(phi)}@
-- for all phi.
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 99)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> nullValuation pva (Set.fromList [1,2])
-- Valuation (fromList [1,2]) 99
{-# SPECIALIZE nullValuation ::
  PresheafValuationAlgebra' v set var -> set var -> Valuation set var v
  #-}
nullValuation :: (HasValuationAlgebra s (->) (->) (->) a set var) => s -> set var -> Valuation set var a
nullValuation algebra =
  Valuation <*> view (valuationAlgebra . valuationAlgebraZero . _Wrapped) algebra

-- | A first-class 'Semigroup' on 'Valuation' derived from the presheaf algebra's
-- combination operation.
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> let sg = presheafCombineSemigroup pva
-- >>> runSemigroup sg (Valuation (Set.fromList [1]) 10) (Valuation (Set.fromList [2]) 20)
-- Valuation (fromList [1,2]) 30
{-# SPECIALIZE presheafCombineSemigroup ::
  PresheafValuationAlgebra' v set var -> Semigroup' (Valuation set var v)
  #-}
presheafCombineSemigroup :: (HasSemigroup algebra (->) v, HasDomainLattice algebra (->) (set var) (set var)) => algebra -> Semigroup' (Valuation set var v)
presheafCombineSemigroup = review applySemigroup . combine

-- | Transitivity of marginalisation: @(phi↓d')↓d'' = phi↓d''@ for @d'' <= d' <= d(phi)@.
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> let phi = Valuation (Set.fromList [1,2,3]) 10
-- >>> lawTransitivity pva (Set.fromList [1,2]) (Set.fromList [1]) phi
-- True
{-# SPECIALIZE lawTransitivity ::
  (Eq v) => PresheafValuationAlgebra' v set var -> set var -> set var -> Valuation set var v -> Bool
  #-}
lawTransitivity :: (Eq a, HasProjectValuation p (->) (->) a set var, HasValuation q set' var' a) => p -> set var -> set var -> q -> Bool
lawTransitivity pva d' d'' phi =
  let step = marginalise pva d'' (marginalise pva d' phi)
      direct = marginalise pva d'' phi
      valuationInfo = view valuationInformation
   in valuationInfo step == valuationInfo direct

-- | Domain of combination: @d(phi ⊗ psi) = d(phi) \/ d(psi)@.
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> lawCombinationDomain pva (Valuation (Set.fromList [1,2]) 10) (Valuation (Set.fromList [2,3]) 20)
-- True
{-# SPECIALIZE lawCombinationDomain ::
  (Eq (set var)) => PresheafValuationAlgebra' v set var -> Valuation set var v -> Valuation set var v -> Bool
  #-}
lawCombinationDomain :: (HasSemigroup s1 (->) a, Eq (set var), HasValuation s2 set var a, HasValuation s3 set var a, HasDomainLattice s1 (->) (set var) (set var)) => s1 -> s2 -> s3 -> Bool
lawCombinationDomain pva val1 val2 =
  let lat = view domainLattice pva
      d1 = view valuationDomain val1
      d2 = view valuationDomain val2
      d = view valuationDomain (combine pva val1 val2)
   in d == runDomainJoin lat d1 d2

-- | Marginalisation identity: @phi↓d(phi) = phi@ (marginalising to own domain is identity).
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> lawMarginalisationIdentity pva (Valuation (Set.fromList [1,2]) 42)
-- True
{-# SPECIALIZE lawMarginalisationIdentity ::
  (Eq (set var), Eq v) => PresheafValuationAlgebra' v set var -> Valuation set var v -> Bool
  #-}
lawMarginalisationIdentity :: (Eq a, Eq (set var), HasValuation s set var a, HasProjectValuation p (->) (->) a set var) => p -> s -> Bool
lawMarginalisationIdentity pva val =
  let d = view valuationDomain val
      v = view valuationInformation val
      Valuation d' v' = marginalise pva d val
   in d' == d && v' == v

-- | Neutral element axiom: @combine pva (neutralValuation pva d) phi = phi@
-- when @d(phi) <= d@ (the neutral valuation is an identity for combination).
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> let phi = Valuation (Set.fromList [1]) 42
-- >>> lawNeutralCombination pva (Set.fromList [1,2]) phi
-- True
{-# SPECIALIZE lawNeutralCombination ::
  (Eq (set var), Eq v) => PresheafValuationAlgebra' v set var -> set var -> Valuation set var v -> Bool
  #-}
lawNeutralCombination :: (HasSemigroup s1 (->) a, Eq a, Eq (set var), HasDomainLattice s1 (->) (set var) (set var), HasValuation s2 set var a, HasValuationAlgebra s1 (->) (->) (->) a set var) => s1 -> set var -> s2 -> Bool
lawNeutralCombination pva d phi =
  let lat = view domainLattice pva
      dPhi = view valuationDomain phi
      vPhi = view valuationInformation phi
   in not (runDomainLeq lat dPhi d)
        || let Valuation d' v' = combine pva (neutralValuation pva d) phi
               expectedDomain = runDomainJoin lat d dPhi
            in d' == expectedDomain && v' == vPhi

-- | Null element axiom: @combine pva (nullValuation pva d) phi = nullValuation pva (d \/ d(phi))@.
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (*)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 1)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> let phi = Valuation (Set.fromList [1]) 42
-- >>> lawNullCombination pva (Set.fromList [2]) phi
-- True
{-# SPECIALIZE lawNullCombination ::
  (Eq (set var), Eq v) => PresheafValuationAlgebra' v set var -> set var -> Valuation set var v -> Bool
  #-}
lawNullCombination :: (HasSemigroup s1 (->) a, Eq a, Eq (set var), HasValuation s2 set var a, HasDomainLattice s1 (->) (set var) (set var), HasValuationAlgebra s1 (->) (->) (->) a set var) => s1 -> set var -> s2 -> Bool
lawNullCombination pva d phi =
  let lat = view domainLattice pva
      dPhi = view valuationDomain phi
      lhs = combine pva (nullValuation pva d) phi
      expectedDomain = runDomainJoin lat d dPhi
      rhs = nullValuation pva expectedDomain
      valuationDom = view valuationDomain
      valuationInfo = view valuationInformation
   in valuationDom lhs == valuationDom rhs && valuationInfo lhs == valuationInfo rhs

-- | Combination is commutative: @phi ⊗ psi = psi ⊗ phi@.
--
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> let sva = SemiValuationAlgebra (review applySemigroup (+)) (ProjectValuation (\_ v -> v))
-- >>> let va = ValuationAlgebra sva (ValuationAlgebraOp (const 0)) (ValuationAlgebraOp (const 0)) :: ValuationAlgebra (->) (->) (->) Int Set Int
-- >>> let pva = PresheafValuationAlgebra lat va
-- >>> lawCombinationCommutative pva (Valuation (Set.fromList [1]) 10) (Valuation (Set.fromList [2]) 20)
-- True
{-# SPECIALIZE lawCombinationCommutative ::
  (Eq (set var), Eq v) => PresheafValuationAlgebra' v set var -> Valuation set var v -> Valuation set var v -> Bool
  #-}
lawCombinationCommutative :: (HasSemigroup s1 (->) a, Eq a, Eq (set var), HasDomainLattice s1 (->) (set var) (set var), HasValuation s2 set var a, HasValuation s3 set var a) => s1 -> s2 -> s3 -> Bool
lawCombinationCommutative pva phi psi =
  let Valuation d1 v1 = combine pva phi psi
      Valuation d2 v2 = combine pva psi phi
   in d1 == d2 && v1 == v2