packages feed

sbv-14.8: Documentation/SBV/Examples/ADT/Shapes.hs

-----------------------------------------------------------------------------
-- |
-- Module    : Documentation.SBV.Examples.ADT.Shapes
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Generate shapes satisfying constraints on their dimensions and area. This
-- example demonstrates symbolic pattern matching on an algebraic datatype,
-- enumerating models with 'allSatWith', and extracting concrete Haskell values
-- in query mode.
-----------------------------------------------------------------------------

{-# OPTIONS_GHC -Wall -Werror #-}

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeApplications  #-}

module Documentation.SBV.Examples.ADT.Shapes where

import Data.SBV
import Data.SBV.Control

-- | Shapes with signed 32-bit dimensions. We reserve 'Rectangle' for shapes
-- with unequal sides, so squares have their own constructor.
data Shape = Dot                                        -- ^ A point, with zero area.
           | Square    { side  :: Int32 }                -- ^ A square.
           | Rectangle { sideA :: Int32, sideB :: Int32 } -- ^ A rectangle.
           deriving Show

-- | Generate 'SShape', symbolic constructors such as 'sSquare', and the
-- support needed to pass shapes to and from the solver.
mkSymbolic [''Shape]

-- | Compute the area using 'sCase' to match a symbolic shape. Ordinary Haskell
-- pattern matching cannot inspect a shape whose constructor is not yet known.
area :: SShape -> SInt32
area s = [sCase| s of
            Dot           -> 0
            Square a      -> a * a
            Rectangle a b -> a * b
         |]

-- | Require positive dimensions below 100, and unequal sides for rectangles.
-- The bounds also ensure that area calculations cannot overflow 'Int32'.
-- A dot has no dimensions to constrain.
goodShape :: SShape -> SBool
goodShape s = [sCase| s of
                 Dot           -> sTrue
                 Square a      -> a .> 0 .&& a .< 100
                 Rectangle a b -> a .> 0 .&& a .< 100 .&& b .> 0 .&& b .< 100 .&& a ./= b
              |]

-- | Create a symbolic shape and constrain its dimensions. Creating the value
-- automatically declares the datatype to the solver.
newShape :: Symbolic SShape
newShape = do x <- free "shape"
              constrain $ goodShape x
              pure x

-- | Select non-dot shapes with area between 10 and 30, inclusive.
-- The explicit exclusion of 'Dot' is redundant with the area bound, but
-- illustrates a symbolic comparison with a datatype constructor.
interesting :: SShape -> SBool
interesting x = area x .>= 10 .&& area x .<= 30 .&& x ./= sDot

-- | Ask for five distinct models using SBV's built-in enumeration.
--
-- >>> examples
-- Solution #1:
--   shape = Rectangle 24 1 :: Shape
-- Solution #2:
--   shape = Rectangle 1 25 :: Shape
-- Solution #3:
--   shape = Rectangle 25 1 :: Shape
-- Solution #4:
--   shape = Square 5 :: Shape
-- Solution #5:
--   shape = Square 4 :: Shape
-- Found 5 different solutions.
examples :: IO AllSatResult
examples = allSatWith z3{allSatMaxModelCount = Just 5} (interesting <$> newShape)

-- | Enumerate shapes manually in query mode. After each satisfiability check,
-- 'getValue' returns a concrete 'Shape'. We exclude that value using 'literal'
-- before asking for the next model. Stop after five shapes, or earlier if
-- there are no more solutions, and return them in discovery order.
--
-- >>> extracted
-- [Square {side = 4},Rectangle {sideA = 15, sideB = 1},Square {side = 5},Rectangle {sideA = 4, sideB = 6},Rectangle {sideA = 12, sideB = 2}]
extracted :: IO [Shape]
extracted = runSMT $ do
        x <- newShape
        constrain $ interesting x

        let loop :: Int -> [Shape] -> Query [Shape]
            loop 5 sofar = pure $ reverse sofar
            loop i sofar = do cs <- checkSat
                              case cs of
                                Sat   -> do xv <- getValue x
                                            -- Exclude the entire shape, including its fields.
                                            constrain $ x ./= literal xv
                                            loop (i+1) (xv : sofar)
                                Unsat -> pure $ reverse sofar
                                _     -> error $ "Solver said: " ++ show cs

        query $ loop 0 []