2

I am trying to prove the correctness of a binary search tree implementation in Dafny, but I am struggling to prove that the computed size corresponds to the size of the elements set.

So far I have written the following code:

datatype TreeSet =
   Leaf
 | Node(left: TreeSet, data: int, right: TreeSet)

predicate Valid(treeSet: TreeSet) {
    match treeSet
        case Leaf => true
        case Node(left, data, right) => Valid(left) && Valid(right)
            && treeSet != left && treeSet != right
            && (forall elem :: elem in elems(left) ==> elem < data)
            && (forall elem :: elem in elems(right) ==> elem > data)
            && treeSet !in descendants(left)
            && treeSet !in descendants(right)
}

function descendants(treeSet: TreeSet) : set<TreeSet> {
    match treeSet {
        case Leaf => {}
        case Node(left, _, right) => descendants(left) + descendants(right)
    }
}

function size(treeSet: TreeSet): nat
requires Valid(treeSet)
ensures |elems(treeSet)| == size(treeSet)
{
  match treeSet
    case Leaf => 0
    case Node(left,_,right) =>
      size(left) + 1 + size(right)
}

function elems(treeSet: TreeSet): set<int>
{
    match treeSet
        case Leaf => {}
        case Node(left, data, right) => elems(left) + {data} + elems(right)
}

Dafny is failing to prove the size function, specifically the ensures |elems(treeSet)| == size(treeSet) postcondition.

It can infer that the cardinality of the element set is lesser than or equal to the size function, but not that it is equal. I tried to assert both the tree invariant the uniqueness of elements in the Valid predicate, but it still cannot prove the correctness of the function. Any idea of how I could make this work?

João Matos
  • 21
  • 1
  • 2

1 Answers1

1

You can prove this post condition by adding an inline assertion like this:

function size(treeSet: TreeSet): nat
requires Valid(treeSet)
ensures |elems(treeSet)| == size(treeSet)
{
  match treeSet
    case Leaf => 0
    case Node(left,x,right) =>
      assert forall elem :: !(elem in elems(left) && elem in elems(right));  // NEW
      size(left) + 1 + size(right)
}

Couple other notes:

  • I think something is weird with your descendants function: it always returns the empty set!

  • In Valid, you don't need the clauses treeSet != left or treeSet != right, those are "obvious" to Dafny based on the datatype declaration. You probably also don't need treeSet !in descendants(left) or treeSet !in descendants(right) even though they are not so obvious.

James Wilcox
  • 5,307
  • 16
  • 25
  • Regarding the other wonkiness in the file, I assumed there could be referencial cycles, but from further reading of the language reference manual, I am now convinced that inductive types will not really suffer from it. – João Matos Nov 28 '20 at 00:00
  • 1
    Right, datatypes don't have cycles in Dafny, even though other parts of Dafny can have cycles. – James Wilcox Nov 28 '20 at 03:16