2

I'm having trouble showing how to ensure recursively decreasing functions on a tree class in Dafny. I have the following definitions which verify.

class RoseTree {
    var NodeType: int
    var id: string
    var children: array<RoseTree>
    ghost var nodeSet: set<RoseTree>

    constructor(nt: int, id: string, children: array<RoseTree>) 
        ensures forall x :: 0 <= x < children.Length ==> children[x].nodeSet <= this.nodeSet
        ensures forall x :: 0 <= x < this.children.Length ==> this.children[x].nodeSet <= this.nodeSet
    {
        this.NodeType := nt;
        this.id := id;
        this.children := children;
        if children.Length == 0 {
            this.nodeSet := {this};
        }else{
            this.nodeSet := {this}+childrenNodeSet(children);
        }
    }
}

function setRosePick(s: set<set<RoseTree>>): set<RoseTree> 
    requires s != {}
{
    var x :| x in s; x
}

function setUnion(setosets: set<set<RoseTree>>) : set<RoseTree> 
    decreases setosets
{
    if setosets == {} then {} else
        var x := setRosePick(setosets);
        assert x <= x + setUnion(setosets-{x});
        x + setUnion(setosets-{x})
}

lemma setUnionDef(s: set<set<RoseTree>>, y: set<RoseTree>)
    requires y in s
    ensures setUnion(s) == y + setUnion(s - {y})
{
    var x := setRosePick(s);
    if y == x {

    }else{
        calc {
            setUnion(s);
            ==
            x + setUnion(s - {x});
            == {setUnionDef(s - {x}, y); }
            x + y + setUnion(s - {x} - {y});
            == { assert s - {x} - {y} == s - {y} - {x}; }
            y + x + setUnion(s - {y} - {x});
            == {setUnionDef(s - {y}, x); }
            y + setUnion(s - {y});

        }
    }
}

lemma setUnionReturns(s: set<set<RoseTree>>) 
   ensures s == {} ==> setUnion(s) == {}
   ensures s != {} ==> forall x :: x in s ==> x <= setUnion(s)
{
    if s == {} {
        assert setUnion(s) == {};
    } else {
        forall x | x in s 
            ensures x <= setUnion(s)
        {
            setUnionDef(s, x);
            assert x <= x + setUnion(s-{x});
        }
    }
}

function childNodeSets(children: array<RoseTree>): set<set<RoseTree>> 
    reads children
    reads set x | 0 <= x < children.Length :: children[x]
{
    set x | 0 <= x < children.Length :: children[x].nodeSet
}

function childNodeSetsPartial(children: array<RoseTree>, index: int): set<set<RoseTree>> 
    requires 0 <= index < children.Length
    reads children
    reads set x | index <= x < children.Length :: children[x]
{
    set x | index <= x < children.Length :: children[x].nodeSet
}

function childrenNodeSet(children: array<RoseTree>): set<RoseTree> 
    reads children
    reads set x | 0 <= x < children.Length :: children[x]
    ensures forall x :: x in childNodeSets(children) ==> x <= childrenNodeSet(children)
    ensures forall i :: 0 <= i < children.Length ==> children[i].nodeSet <= childrenNodeSet(children)
{
    var y := childNodeSets(children);
    setUnionReturns(y);
    setUnion(y)
} 

In particular I'm trying to define the height function for the tree.

function height(node: RoseTree):nat 
    reads node
    reads node.children
    reads set x | 0 <= x < node.children.Length :: node.children[x]
    decreases node.nodeSet
{
    if node.children.Length == 0 then 1 else 1 + maxChildHeight(node, node.children,node.children.Length-1,0)
}

function maxChildHeight(node: RoseTree, children: array<RoseTree>, index: nat, best: nat) : nat 
    reads node
    reads node.children
    reads set x | 0 <= x < node.children.Length :: node.children[x]
    requires children == node.children
    requires 0 <= index < children.Length
    ensures forall x :: 0 <= x <= index < children.Length ==> maxChildHeight(node, children, index, best) >= height(children[x])
    decreases node.nodeSet - setUnion(childNodeSetsPartial(children, index)), 1
{
    if index == 0 then best else if height(children[index]) >= best then maxChildHeight(node, children, index-1, height(children[index])) else maxChildHeight(node, children, index-1, best)
}

I though it should be possible to show that the nodeSet of the node will be a subset of its parent node or that the union of child node sets will be a subset of the parent node, and thus both functions will terminate. My decreases expressions don't prove it to dafny and I'm not quite sure how to proceed. Is there another way to prove termination or can I fix these decrease statements?

Also, do all instances of a class have the constructor ensure statements applied implicitly or only if explicitly constructed using the constructor?

Edit: updated definitions of childNodeSetsPartial and maxChildHeight to recurse downward. It still doesn't verify.

Hath995
  • 821
  • 6
  • 12
  • 1
    If I am not mistake, isn't call from height to maxChildHeight doesn't necessary decrease. It only removes nodetSet of upto index children but original call removes all children. It might move from {1} to {1, 3, 4} assuming 1 is root and 2, 3, 4 are children – Divyanshu Ranjan Jun 06 '22 at 16:02
  • oh so if I switched the maxChildHeight to recurse down from Children.Length instead of up from 0 then I would have better results. – Hath995 Jun 06 '22 at 17:55
  • So although height doesn't call itself , it should be called by maxChildHeight on each of its node, each which should be a subset of the parent.nodeSet. – Hath995 Jun 06 '22 at 18:22

1 Answers1

3

Defining mutable linked heap-allocated data structures in Dafny is not very common except as an exercise. So you should consider whether a datatype would serve you better, as in

datatype RoseTree = Node(children: seq<RoseTree>)

function height(r: RoseTree): int
{
  if r.children == [] then
    1
  else
    var c := set i | 0 <= i < |r.children| :: height(r.children[i]);
    assert height(r.children[0]) in c;
    assert c != {};
    SetMax(c) + 1
}

If you insist on mutable linked heap-allocated data structures, then there is a standard idiom for doing that. Please read sections 0 and 1 of these lecture notes and check out the modern version of the example code here.

Applying this idiom to your code, we get the following.

class RoseTree {
    var NodeType: int
    var id: string
    var children: array<RoseTree>
    ghost var repr: set<object>

    predicate Valid()
      reads this, repr
      decreases repr
    {
      && this in repr
      && children in repr
      && (forall i | 0 <= i < children.Length :: 
            children[i] in repr 
          && children[i].repr <= repr 
          && this !in children[i].repr 
          && children[i].Valid())
    }

    constructor(nt: int, id: string, children: array<RoseTree>) 
      requires forall i | 0 <= i < children.Length :: children[i].Valid()
      ensures Valid()
    {
        this.NodeType := nt;
        this.id := id;
        this.children := children;
        this.repr := {this, children} + 
          (set i | 0 <= i < children.Length :: children[i]) +
          (set x, i | 0 <= i < children.Length && x in children[i].repr :: x);
    }
}


function SetMax(s: set<int>): int 
  requires s != {}
  ensures forall x | x in s :: SetMax(s) >= x
{
  var x :| x in s;
  if s == {x} then
    x
  else
    var y := SetMax(s - {x});
    assert forall z | z in s :: z == x || (z in (s - {x}) && y >= z);
    if x > y then x else y
}

function height(node: RoseTree): nat
  requires node.Valid()
  reads node.repr
{
  if node.children.Length == 0 then 
    1 
  else 
    var c := set i | 0 <= i < node.children.Length :: height(node.children[i]);
    assert height(node.children[0]) in c;
    assert c != {};
    SetMax(c) + 1
}

do all instances of a class have the constructor ensure statements applied implicitly or only if explicitly constructed using the constructor?

I'm not sure if I understand this question. I think the answer is "no", though. Since a class might have multiple constructors with different postconditions.

James Wilcox
  • 5,307
  • 16
  • 25
  • Thanks for the answer! I suppose I could replace the array with a sequence. Sort of more importantly how does Dafny handle reference equality? The algorithm I was trying to describe focuses heavily on this so I was thinking that a class in the heap would be the place to start. – Hath995 Jun 06 '22 at 20:27
  • Likewise, would a datatype allow assignment to the children sequence or would it require creating a new node to represent it? Thats why I started with an array. – Hath995 Jun 06 '22 at 20:31
  • 1
    It's not just about array->seq it's also about class->datatype for the outer RoseTree. You probably want to either make both these changes or neither of them. I will answer your other questions as separate comments. – James Wilcox Jun 07 '22 at 01:48
  • 1
    "How does Dafny handle reference equality?" There is no such distinction in Dafny per se. Instead, there is only one built-in `==` operator, but it is defined differently for heap data structures versus functional data structures. For heap data structures, such as arrays and any class types, `==` means reference equality. For functional data structures such as sequences, integers, etc., `==` means structural equality. If you are implementing an algorithm that requires reference equality, you must use a heap-allocated data structure, or change the algorithm. – James Wilcox Jun 07 '22 at 01:50
  • 1
    "Would a datatype allow assignment to the children sequence or would it require creating a new node to represent it?" Assignment is not allowed. You would create a new node. If you need mutation, then you should use a heap-allocated data structure. – James Wilcox Jun 07 '22 at 01:50