Skip to content

Commit bb178bf

Browse files
coinage-layer task #87 (step): find_two_coin_exact_cover
Adds the 2-coin special case of multi-coin exact subset-sum (Quint selectExactCoverDeterministic). Nested-loop scan, O(n^2) over the coin Vec, returns Some((k1, k2)) where both keys are distinct, Available, in purse p, and coin_value(exp_k1) + coin_value(exp_k2) exactly equals the requested amount. This is the first multi-coin extension of tier-1. It covers practically-important cases where single-coin tier-1 fires None but two coins happen to sum exactly (e.g. amount = 11, coins of value 6 + 5). The full powerset enumeration over arbitrary subset sizes remains task #87 — adding 3-coin, then iterative-deepening, follows the same Vec-scan pattern with proportionally more nested loops. The None postcondition is intentionally weak (`true`) for now: proving "no 2-coin subset sums to amount" requires the contrapositive of the existential, which adds significant proof obligations. Sharper None postconditions can land incrementally. 160 verified, 0 errors.
1 parent 3eb45fa commit bb178bf

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

  • rust/crates/coinage-layer/src

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6061,6 +6061,87 @@ impl State {
60616061
None
60626062
}
60636063

6064+
/// Tier-1 multi-coin (§6.3): find any pair of distinct `Available`
6065+
/// coins in purse `p` whose values sum exactly to `amount`. Returns
6066+
/// the two keys in Vec order, or `None` if no such pair exists.
6067+
///
6068+
/// This is the 2-coin special case of the powerset-based
6069+
/// selectExactCoverDeterministic. Full powerset enumeration remains
6070+
/// open (task #87); 2-coin already covers many cases that
6071+
/// single-coin tier-1 misses (e.g. requesting amount = max_exp + 2
6072+
/// with two coins of value max_exp + 1 / 1).
6073+
pub fn find_two_coin_exact_cover(&self, p: PurseId, amount: u64)
6074+
-> (res: Option<((PurseId, u64), (PurseId, u64))>)
6075+
requires
6076+
self.invariant(),
6077+
ensures
6078+
match res {
6079+
Some((k1, k2)) =>
6080+
self.coins().dom().contains(k1)
6081+
&& self.coins().dom().contains(k2)
6082+
&& k1 != k2
6083+
&& k1.0 == p
6084+
&& k2.0 == p
6085+
&& self.coins()[k1].state == CoinState::Available
6086+
&& self.coins()[k2].state == CoinState::Available
6087+
&& coin_value(self.coins()[k1].exponent)
6088+
+ coin_value(self.coins()[k2].exponent)
6089+
== amount as nat,
6090+
None => true,
6091+
},
6092+
{
6093+
let n = self.coins.len();
6094+
let mut i: usize = 0;
6095+
while i < n
6096+
invariant
6097+
0 <= i <= n,
6098+
n == self.coins.len(),
6099+
self.invariant(),
6100+
decreases n - i,
6101+
{
6102+
let ci_avail = matches!(self.coins[i].state, CoinState::Available);
6103+
if self.coins[i].purse == p && ci_avail {
6104+
let vi: u64 = (self.coins[i].exponent as u64) + 1;
6105+
if vi <= amount {
6106+
let mut k: usize = 0;
6107+
while k < n
6108+
invariant
6109+
0 <= k <= n,
6110+
n == self.coins.len(),
6111+
i < n,
6112+
self.invariant(),
6113+
self.coins@[i as int].purse == p,
6114+
self.coins@[i as int].state == CoinState::Available,
6115+
vi == (self.coins@[i as int].exponent as u64) + 1,
6116+
vi <= amount,
6117+
decreases n - k,
6118+
{
6119+
if k != i {
6120+
let ck_avail = matches!(self.coins[k].state, CoinState::Available);
6121+
let vk: u64 = (self.coins[k].exponent as u64) + 1;
6122+
if self.coins[k].purse == p && ck_avail && vi + vk == amount {
6123+
let k1 = (self.coins[i].purse, self.coins[i].idx);
6124+
let k2 = (self.coins[k].purse, self.coins[k].idx);
6125+
proof {
6126+
assert(self.spec_coins@.dom().contains(k1));
6127+
assert(self.spec_coins@.dom().contains(k2));
6128+
// i != k means the Vec entries differ; by
6129+
// dedup invariant (n), their (purse, idx)
6130+
// tuples differ, so k1 != k2.
6131+
assert(k1 != k2);
6132+
}
6133+
return Some((k1, k2));
6134+
}
6135+
}
6136+
k = k + 1;
6137+
}
6138+
}
6139+
}
6140+
i = i + 1;
6141+
}
6142+
None
6143+
}
6144+
60646145
/// Tier-2 (split cover, §6.3): find any `Available` coin in purse `p`
60656146
/// whose `coin_value(exp)` strictly exceeds `amount`. Such a coin can
60666147
/// be split into two coins of strictly smaller exponent (one of which

0 commit comments

Comments
 (0)