packages feed

lazyppl-1.1.0: src/LazyPPL/Distributions/Mondrian.hs

{-# LANGUAGE ExtendedDefaultRules, ScopedTypeVariables, BangPatterns #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use newtype instead of data" #-}

module LazyPPL.Distributions.Mondrian where

import LazyPPL
import Data.IORef
-- import Debug.Trace
import LazyPPL.Distributions
import LazyPPL.Distributions.Counter
import System.IO.Unsafe
import Data.Map (Map,empty,lookup,insert,size,keys,findWithDefault,fromList,(!))
-- import Text.RawString.QQ
-- import qualified Data.List
-- import Control.DeepSeq


-- | From Roy-Teh "The Mondrian Process", NIPS 2009

-- | 'oneDimMondrian' corresponds to a Poisson point process on [low, high] with uniform intensity
oneDimMondrian :: Double -> (Double, Double) -> Prob [Double]
oneDimMondrian budget (low, high) = do
  cutCost <- exponential (high - low)
  if budget < cutCost
    then return []
    else do
      let remaining = budget - cutCost
      cut <- uniformbounded low high
      leftcuts <- oneDimMondrian remaining (low, cut)
      rightcuts <- oneDimMondrian remaining (cut, high)
      return $ leftcuts ++ [cut] ++ rightcuts

-- | Some data types. Intuitively, Matrix is a type of 2D exchangeable arrays, and 'Mondrian Double' would be the type of block-structured graphons.

data Mondrian a
  = Block a [(Double, Double)] -- ^ args:  and intervals making up the block
  | Partition Int Double [(Double, Double)] (Mondrian a) (Mondrian a) -- ^ args: dimension cutPosition intervals (hypercube over which the Mondrian is defined) subtree1 
  deriving (Eq, Show)

newtype Row = Row Int -- ^ in [0, 1]

newtype Col = Col Int

data MatrixIO = MatrixIO Counter Counter [[Bool]]

lookup :: MatrixIO -> Row -> Col -> Bool
lookup (MatrixIO _ _ matrix) (Row r) (Col c) = matrix !! r !! c

newRow :: MatrixIO -> Prob Row
newRow (MatrixIO c _ _) = readAndIncrement c >>= (return . Row)

newCol :: MatrixIO -> Prob Col
newCol (MatrixIO _ c _) = readAndIncrement c >>= (return . Col)

data Matrix = Matrix [[Bool]]

{- | Generates a random Mondrian tree: a random block partition of the rectangle [a1, b1] × ⋯ × [an, bn]
where each block has an associated random draw from the base distribution.
-}
randomMondrian :: Prob a -> Double -> [(Double, Double)] -> Prob (Mondrian a)
randomMondrian base budget abs = do
  let lengths = map (\(a, b) -> b - a) abs
  let sumLengths = sum lengths
  cutCost <- exponential sumLengths
  if budget < cutCost
    then do p <- base; return $ Block p abs
    else do
      let remaining = budget - cutCost
      dim <- categorical $ map (/sumLengths) lengths -- if dim is true then cut is perpendicular to (a_d, b_d)
      let (a_d, b_d) = abs !! dim
      cut <- uniformbounded a_d b_d
      leftMondrian <- randomMondrian base remaining
        $ zipWith (\ab i -> if i == dim then (a_d, cut) else ab) abs [0 ..]
      rightMondrian <- randomMondrian base remaining
        $ zipWith (\ab i -> if i == dim then (cut, b_d) else ab) abs [0 ..]
      return $ Partition dim cut abs leftMondrian rightMondrian

-- | Given a Mondrian tree and two data points, apply a function on the biais of the corresponding block. 
applyOnBiaisFromMondrian2D :: Mondrian Double -> Double -> Double -> (Double -> a) -> a
applyOnBiaisFromMondrian2D mondrian x y f = do
  case mondrian of
    Block p _ -> f p
    Partition !dim !cut _ left right ->
      let !xy = case dim of {0 -> x; _ -> y}
          !cond = xy < cut
          !lr = if cond then left else right in
      applyOnBiaisFromMondrian2D lr x y f

-- | Given a Mondrian tree and two data points, sample an 'edge'.
sampleFromMondrian2D :: Mondrian Double -> Double -> Double -> Prob Bool
sampleFromMondrian2D mondrian x y = applyOnBiaisFromMondrian2D mondrian x y bernoulli

-- | Probability of getting a given truth value 'val' at a (r, c) pair from a Mondrian. 
likelihoodFromMondrian2D :: Mondrian Double -> Double -> Double -> Bool -> Double
likelihoodFromMondrian2D mondrian x y val =
  let !f = if val then id else (1-) in
    applyOnBiaisFromMondrian2D mondrian x y f

{- | Sample a block structure over [0, 1]^2 from the Mondrian process,
and use it to generate an infinite exchangeable array.
-}
sampleRelationFromRandomMondrian2D :: Prob Double -> Double -> Prob Matrix
sampleRelationFromRandomMondrian2D base budget = do
  mondrian <- randomMondrian base budget [(0, 1), (0, 1)]
  sampleRelationFromMondrian2D mondrian

sampleRelationFromMondrian2D :: Mondrian Double -> Prob Matrix
sampleRelationFromMondrian2D mondrian = do
  rs <- iid uniform
  cs <- iid uniform
  matrix <- mapM (\r -> mapM (sampleFromMondrian2D mondrian r) cs) rs
  return $ Matrix matrix

sampleMapRelationFromMondrian2D :: Mondrian Double -> Int -> Prob (Map (Double, Double) Bool)
sampleMapRelationFromMondrian2D mondrian size = do
  xs <- iid uniform
  ys <- iid uniform
  matrix <- mapM (\x ->
    mapM (\y -> (do b <- sampleFromMondrian2D mondrian x y; return ((x, y), b))) (take size xs))
    (take size ys)
  return $ fromList $ concat matrix

sampleMatrixRelationFromMondrian2D :: Mondrian Double -> Prob Matrix
sampleMatrixRelationFromMondrian2D mondrian = do
  rs <- iid uniform
  cs <- iid uniform
  matrix <- mapM (\r ->
    mapM (sampleFromMondrian2D mondrian r) cs) rs
  return $ Matrix matrix


data BinTree a = Leaf | Node a (BinTree a) (BinTree a)
  deriving (Eq, Show)

{- | Create a Mondrian tree over a hypercube `abs` from a tree of (dimension, cut) given in the chronological order, and a map associating to each block its bernoulli biais.
-}
mondrianFromCuts :: [(Double, Double)] -> BinTree (Int, Double) -> Map [(Double, Double)] Double -> Double -> Mondrian Double
mondrianFromCuts abs dcs biais defaultBiais =
  case dcs of
    Leaf -> Block (findWithDefault defaultBiais abs biais) abs
    Node (dim, cut) left right ->
      Partition dim cut abs
      (mondrianFromCuts leftAbs left biais defaultBiais)
      (mondrianFromCuts rightAbs right biais defaultBiais)
      where
        leftAbs = zipWith (\ab i -> if i == dim then (a0, cut) else ab) abs [0 ..]
        rightAbs = zipWith (\ab i -> if i == dim then (cut, b0) else ab) abs [0 ..]
        (a0, b0) = abs !! dim