z3prooftheorem-provingdafny

Not able to make a simple proof in Dafny: if `0<=a<=1 & 0<=b<=1 & -1<=c<=1` then `a*b*c==1 ==> a=1 & b=1 & c=1`


I am not able to prove a simple property: if 0<=a<=1 & 0<=b<=1 & -1<=c<=1 then a*b*c==1 ==> a=1 & b=1 & c=1.

I first tested the following:

lemma threepowers(a:real,b:real,c:real)
   requires 0.0<=a<=1.0 && 0.0<=b<=1.0 && -1.0<=c<=1.0
   ensures a*b*c==1.0 ==> a==1.0 && b==1.0 && c==1.0
   {
     if (a<1.0 || b<1.0 || c<1.0){
       assert a*b*c < 1.0;
     }
   }

But the assertion fails.

So I decided to prove it by cases, as follows (note that I only show a couple of cases):

lemma threepowers(a:real,b:real,c:real)
   requires 0.0<=a<=1.0 && 0.0<=b<=1.0 && -1.0<=c<=1.0
   ensures a*b*c==1.0 ==> a==1.0 && b==1.0 && c==1.0
   {
     if (a==1.0){
       if ((b == 1.0 && c!=1.0)||(b != 1.0 && c==1.0)){
         assert a*b*c!=1.0;
       }
       else if (b != 1.0 && c!=1.0){
         assert a*b*c!=1.0;
       }
     }
     
     
   }

But Dafny is not able to verify that if b and c are both not 1 (i.e., they are smaller than 1, taking into account the requires), then the product a*b*c is not 1.

What basic issue am I missing?

Of course, note that a Z3 call is not able to prove it either:

lemma threepowers(a:real,b:real,c:real)
   requires 0.0<=a<=1.0 && 0.0<=b<=1.0 && -1.0<=c<=1.0
   ensures a*b*c==1.0 ==> a==1.0 && b==1.0 && c==1.0
   {
     assert forall x:real :: forall y:real :: forall z:real :: (0.0<=x<1.0) || (0.0<=y<1.0) || (-1.0<=z<1.0) ==> (x*y*z<1.0);
   }

Solution

  • I found a two-line proof for a variant of your problem

    lemma threepowers(a:real,b:real,c:real)
       requires 0.0<=a<=1.0 && 0.0<=b<=1.0 && -1.0<=c<=1.0
       ensures a*b*c==1.0 ==> a==1.0 && b==1.0 && c==1.0
       {
         if a*b*c == 1.0 && a < 1.0 && b < 1.0 && b != 0.0 && c != 0.0 {
         }
       }
    

    but because I find the process more interesting that the solution, here is the methodology I followed.

    1. I first started by trying to prove a lemma with only two reals instead of three.
    lemma twopowers(a: real, b: real)
      requires 0.0<=a<=1.0 && 0.0<=b<=1.0
      ensures a*b==1.0 ==> a==1.0 && b==1.0
    {
    }
    

    That did not work either. So I started to follow verification debugging techniques and inserted

      if a*b == 1.0 {             // Just added
        assert a==1.0 && b==1.0;  // Just added
      }                           // Just added
    

    but that did not work yet. My next move was to think. Well, if it cannot prove that a == 1.0, I can assume that a != 1.0 and perhaps obtain a proof by contradiction. So my proof changed to

      if a*b == 1.0 {
        if a != 1.0 {    // Just added
          assert false;  // Just added
        }                // Just added
        assert a==1.0 && b==1.0;
      }
    

    And curiously, it conditionally proved the two assertions a==1.0 and b==1.0 So now, how do I prove there is a contradiction? Well, the only thing I could try was to derive more facts. Since a is not 1, it has to be strictly less than 1, so I asserted it.

      if a*b == 1.0 {
        if a != 1.0 {
          assert a < 1.0; // Just added
          assert false;   // Still not verifying
        }
        assert a==1.0 && b==1.0;
      }
    

    Then, since I knew that a*b == 1.0, I thought I would be able to derive that a*b < 1.0 * b. but it turned out, this is not true.

      if a*b == 1.0 {
        if a != 1.0 {
          assert a < 1.0; 
          assert a*b < 1.0*b; // Does not verify
          assert false;       // OK now
        }
        assert a==1.0 && b==1.0;
      }
    

    The reason is obvious. If b is zero, there should be an equality. So, I needed the case split b != 0 And suddenly, the entire function verified.

      if a*b == 1.0 {
        if a != 1.0 {
          assert a < 1.0;
          if b != 0.0 {
            assert a*b < 1.0*b;
          } else {
            assert a*b == 1.0*b;
          }
          assert false;
        }
        assert a==1.0 && b==1.0;
      }
    

    Dafny figured out that, in one case a*b < 1.0*b <= b <= 1, so a*b < 1 and we had a contradiction. In the other case, a*b == 0 so 0 == 1 and that's another contradiction.

    So I tried a similar strategy for your lemma, it was a bit longer, but I found the solution above by trial and error. I first changed the interval of c to 0.0<=c<=1.0, and then supposed that a*b*c == 1. Then I tried a < 1.0, then b < 1.0 and then what if b != 0 and c != 0, and it arrived not only at a contradiction in this case, but it was enough for the verifier to figure out all the other cases. I then simplified the nested if-else structure until I kept the minimum of the proof, and voilĂ  !

    Then, to make it easier to reproduce, I did another experiment. I created the skeleton of a proof:

      if a*b*c == 1.0 {
        if a < 1.0 || b < 1.0 || c < 1.0 {
          if a < 1.0 {
            assert false;
          } else if b < 1.0 {
            assert false;
          } else {
            assert c < 1.0;
            assert false;
          }
        }
        assert a==1.0 && b==1.0 && c==1.0;
      }
    

    Only the two first assert were failing. I focused on the first. I added the split if a != 0.0 with an assert false in its body, and only the first branch was failing.

    Similarly, I added the split if b != 0.0 with assert false in its body, and only the first branch was failing.

    Then I added the triple split if c < 0.0 { assert false; } else if c == 0.0 { assert false; } else { assert c > 0.0; assert false; } And only the third assert was failing.

    Then, in this branch, I added assertions to prove manually the contradiction:

    assert c*b > 0.0;
    assert a*(b*c) < 1.0*(b*c);
    assert c*b <= 1.0; // Failing
    

    I added two new assertions in front of the failing assert:

    assert b <= 1.0;
    assert b*c <= 1.0 * b;
    

    And the second one did the trick. Then, all my job was to simplify the proof. I removed all the else { assert false; } for example. I removed the wrapper if a < 1.0 || b < 1.0 || c < 1.0 { since proving it on one case make it possible to prove it for all cases. I collapsed the tower of ifs, and my proof became the simple

      if a*b*c == 1.0 && a < 1.0 && a != 0.0 && b != 0.0 && c > 0.0 {
        assert b*c <= 1.0 * b;
      }
    

    From here one would be very happy to have a proof, but I did one more step: I put the assertion in the condition:

      if a*b*c == 1.0 && a < 1.0 && a != 0.0 && b != 0.0 && c > 0.0 && b*c <= 1.0 * b {
      }
    

    and that worked! I was even able to remove c > 0.0 and a*b*c == 1.0 and here is the shortest proof I could find:

      if a < 1.0 && a != 0.0 && b != 0.0 && b*c <= 1.0 * b {}