Skip to content

Commit 1a783de

Browse files
coinage-layer task #87 (step): sharper find_two_coin_exact_cover None
The None branch was `true` (vacuous) — useful as a witness but unusable for composition. Sharpens it to the operationally-meaningful quantifier: no two distinct Vec indices i1 != i2 form a pair where both coins are Available in p and their values sum to amount. The dedup invariant (n) over (purse, idx) means Vec-index distinctness implies key distinctness, so this is equivalent to the dom-based "no two distinct keys" claim. Stated over Vec indices for cleaner trigger discipline (let-binding trigger pattern). The proof discharges via accumulated loop invariants on both outer and inner loops; no extra proof block needed. 209 verified, 0 errors.
1 parent 610f0e2 commit 1a783de

1 file changed

Lines changed: 57 additions & 4 deletions

File tree

  • rust/crates/coinage-layer/src

rust/crates/coinage-layer/src/lib.rs

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6908,7 +6908,26 @@ impl State {
69086908
&& coin_value(self.coins()[k1].exponent)
69096909
+ coin_value(self.coins()[k2].exponent)
69106910
== amount as nat,
6911-
None => true,
6911+
None =>
6912+
// Sharp: no two distinct Vec indices satisfy the pair-sum
6913+
// predicate. Combined with the dedup invariant (n), this
6914+
// is equivalent to "no two distinct coin keys with the
6915+
// pair-sum predicate".
6916+
forall|i1: int, i2: int|
6917+
0 <= i1 < self.coins@.len()
6918+
&& 0 <= i2 < self.coins@.len()
6919+
&& i1 != i2
6920+
==> {
6921+
let c1 = #[trigger] self.coins@[i1];
6922+
let c2 = #[trigger] self.coins@[i2];
6923+
c1.purse != p
6924+
|| c1.state != CoinState::Available
6925+
|| c2.purse != p
6926+
|| c2.state != CoinState::Available
6927+
|| ((c1.exponent as nat) + 1
6928+
+ (c2.exponent as nat) + 1
6929+
!= amount as nat)
6930+
},
69126931
},
69136932
{
69146933
let n = self.coins.len();
@@ -6918,6 +6937,18 @@ impl State {
69186937
0 <= i <= n,
69196938
n == self.coins.len(),
69206939
self.invariant(),
6940+
// No earlier outer index i1 < i forms a valid pair with any k.
6941+
forall|i1: int, i2: int|
6942+
0 <= i1 < i as int && 0 <= i2 < n as int && i1 != i2 ==> {
6943+
let c1 = #[trigger] self.coins@[i1];
6944+
let c2 = #[trigger] self.coins@[i2];
6945+
c1.purse != p
6946+
|| c1.state != CoinState::Available
6947+
|| c2.purse != p
6948+
|| c2.state != CoinState::Available
6949+
|| ((c1.exponent as nat) + 1 + (c2.exponent as nat) + 1
6950+
!= amount as nat)
6951+
},
69216952
decreases n - i,
69226953
{
69236954
let ci_avail = matches!(self.coins[i].state, CoinState::Available);
@@ -6935,6 +6966,29 @@ impl State {
69356966
self.coins@[i as int].state == CoinState::Available,
69366967
vi == (self.coins@[i as int].exponent as u64) + 1,
69376968
vi <= amount,
6969+
// Same outer accumulator from before this inner loop.
6970+
forall|i1: int, i2: int|
6971+
0 <= i1 < i as int && 0 <= i2 < n as int
6972+
&& i1 != i2 ==> {
6973+
let c1 = #[trigger] self.coins@[i1];
6974+
let c2 = #[trigger] self.coins@[i2];
6975+
c1.purse != p
6976+
|| c1.state != CoinState::Available
6977+
|| c2.purse != p
6978+
|| c2.state != CoinState::Available
6979+
|| ((c1.exponent as nat) + 1
6980+
+ (c2.exponent as nat) + 1
6981+
!= amount as nat)
6982+
},
6983+
// Inner-loop accumulator: for all checked k2 < k,
6984+
// the pair (i, k2) doesn't satisfy the predicate.
6985+
forall|i2: int|
6986+
0 <= i2 < k as int && i2 != i as int ==>
6987+
(#[trigger] self.coins@[i2]).purse != p
6988+
|| self.coins@[i2].state != CoinState::Available
6989+
|| ((self.coins@[i as int].exponent as nat) + 1
6990+
+ (self.coins@[i2].exponent as nat) + 1
6991+
!= amount as nat),
69386992
decreases n - k,
69396993
{
69406994
if k != i {
@@ -6946,9 +7000,6 @@ impl State {
69467000
proof {
69477001
assert(self.spec_coins@.dom().contains(k1));
69487002
assert(self.spec_coins@.dom().contains(k2));
6949-
// i != k means the Vec entries differ; by
6950-
// dedup invariant (n), their (purse, idx)
6951-
// tuples differ, so k1 != k2.
69527003
assert(k1 != k2);
69537004
}
69547005
return Some((k1, k2));
@@ -6957,6 +7008,8 @@ impl State {
69577008
k = k + 1;
69587009
}
69597010
}
7011+
// If vi > amount, the pair-sum is also > amount and can't equal.
7012+
// The outer-loop accumulator extends by this fact for i.
69607013
}
69617014
i = i + 1;
69627015
}

0 commit comments

Comments
 (0)