Using the SBV library, I'm trying to satisfy conditions on a symbolic list of states:
data State = Intro | Start | Content | Comma | Dot
mkSymbolicEnumeration ''State
-- examples of such lists
[Intro, Start, Content, Comma, Start, Comma, Content, Dot]
[Intro, Comma, Start, Content, Comma, Content, Start, Dot]
All works fine except that I need the final list to contain exactly n elements of either [Intro, Start, Content] in total. Currently I do it using a bounded filter:
answer :: Int -> Symbolic [State]
answer n = do
seq <- sList "seq"
let maxl = n+6
let minl = n+2
constrain $ L.length seq .<= fromIntegral maxl
constrain $ L.length seq .>= fromIntegral minl
-- some additional constraints hidden for brevity purposes
let etypes e = e `sElem` [sIntro, sStart, sContent]
constrain $ L.length (L.bfilter maxl etypes seq) .== fromIntegral n
As you can see, the list can be of any length between n+2 and n+6, the important bit is that it has the right count of [sIntro, sStart, sContent] elements within it.
It works all fine, except it's extremely slow. Like, for n=4 it takes a few seconds, but for n>=6 it takes forever (more than 30 minutes and still counting). If I remove the bounded filter constraint, the result is instant with n up to 25 or so.
In the end, I don't particularly care about using L.bfilter. All I need is a way to declare that the final symbolic list should contain exactly n elements of some given types.
-> Is there a faster way to be able to satisfy for count(sIntro || sStart || sContent)?
-- EDIT after discussion in comments:
The code below is supposed to make sure that all valid elements are up-front in the elts list. For example, if we count 8 valids elements from elts, then we take 8 elts and we count the validTaken valid elements in this sub-list. If the result is 8, it means that all the 8 valids elements are up-front in elts. Sadly, this results in a systematic Unsat outcome, even after removing all other constraints. The function works well when tested against some dummy lists of elements, though.
-- | test that all valid elements are upfront in the list of elements
validUpFront :: SInteger -> [Elem] -> SBool
validUpFront valids elts =
let takeValids = flip take elts <$> (fromInteger <$> unliteral valids)
validTaken = sum $ map (oneIf . included) $ fromMaybe [] takeValids
in valids .== validTaken
-- ...
answer n = runSMT $ do
-- ...
let valids = sum $ map (oneIf . included) elts :: SInteger
constrain $ validUpFront valids elts
Solvers for the sequence logic, while quite versatile, are notoriously slow. For this particular problem, I'd recommend using regular boolean logic, which will perform much better. Here's how I'd code your problem:
Example run:
I've been able to run upto
answer 500which returned in about 5 seconds on my relatively old machine.Making sure all valids are at the beginning
The easiest way to make all the valid elements are at the beginning of the list is to count the alternations in the included value, and make sure you allow only one such transition:
This'll make sure all the valids precede the suffix of the list that contain the invalid entries. When you write your other properties, you'd have to check that both the current element and the next element is valid. In template form:
By symbolically looking at the values of
included xandincluded y, you can determine if they are both included, or ifxis the last element, or if they're both out; and write the corresponding constraints as implications in each case. The above shows the case for when you're in the middle of the sequence somewhere, with bothxandyincluded.