Skip to content

Commit 69efeae

Browse files
committed
coupling rule: one-sided and two-sided
1 parent 23cf445 commit 69efeae

File tree

9 files changed

+283
-26
lines changed

9 files changed

+283
-26
lines changed

src/ecHiTacticals.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ and process1_phl (_ : ttenv) (t : phltactic located) (tc : tcenv1) =
200200
| Psetmatch info -> EcPhlCodeTx.process_set_match info
201201
| Pweakmem info -> EcPhlCodeTx.process_weakmem info
202202
| Prnd (side, pos, info) -> EcPhlRnd.process_rnd side pos info
203+
| Pcoupling (side, f) -> EcPhlRnd.process_coupling side f
203204
| Prndsem (red, side, pos) -> EcPhlRnd.process_rndsem ~reduce:red side pos
204205
| Pconseq (opt, info) -> EcPhlConseq.process_conseq_opt opt info
205206
| Pconseqauto cm -> process_conseqauto cm

src/ecLexer.mll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
"cfold" , CFOLD ; (* KW: tactic *)
146146
"rnd" , RND ; (* KW: tactic *)
147147
"rndsem" , RNDSEM ; (* KW: tactic *)
148+
"coupling" , COUPLING ; (* KW: tactic *)
148149
"pr_bounded" , PRBOUNDED ; (* KW: tactic *)
149150
"bypr" , BYPR ; (* KW: tactic *)
150151
"byphoare" , BYPHOARE ; (* KW: tactic *)

src/ecParser.mly

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@
416416
%token CONSEQ
417417
%token CONST
418418
%token COQ
419+
%token COUPLING
419420
%token CHECK
420421
%token EDIT
421422
%token FIX
@@ -3000,9 +3001,13 @@ interleave_info:
30003001
| RND s=side? info=rnd_info c=prefix(COLON, semrndpos)?
30013002
{ Prnd (s, c, info) }
30023003

3004+
30033005
| RNDSEM red=boption(STAR) s=side? c=codepos1
30043006
{ Prndsem (red, s, c) }
30053007

3008+
| COUPLING s=side? f=sform
3009+
{ Pcoupling (s, f) }
3010+
30063011
| INLINE s=side? u=inlineopt? o=occurences?
30073012
{ Pinline (`ByName(s, u, ([], o))) }
30083013

src/ecParsetree.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,7 @@ type phltactic =
748748
| Pasgncase of (oside * pcodepos)
749749
| Prnd of oside * psemrndpos option * rnd_tac_info_f
750750
| Prndsem of bool * oside * pcodepos1
751+
| Pcoupling of oside * pformula
751752
| Palias of (oside * pcodepos * osymbol_r)
752753
| Pweakmem of (oside * psymbol * fun_params)
753754
| Pset of (oside * pcodepos * bool * psymbol * pexpr)

src/phl/ecPhlPrRw.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,4 +339,4 @@ let t_pr_rewrite (s, f) tc =
339339
let hyps = LDecl.push_active_ss mp hyps in
340340
{m;inv=EcProofTyping.process_form hyps f ty}
341341
in
342-
t_pr_rewrite_low (s, omap to_env f) tc
342+
t_pr_rewrite_low (s, omap to_env f) tc

src/phl/ecPhlRnd.ml

Lines changed: 130 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ open EcSubst
1010

1111
open EcMatching.Position
1212
open EcCoreGoal
13+
open EcCoreFol
1314
open EcLowGoal
1415
open EcLowPhlGoal
1516

@@ -442,6 +443,89 @@ module Core = struct
442443
| `Right -> f_equivS (snd es.es_ml) mt (es_pr es) es.es_sl s (es_po es) in
443444
FApi.xmutate1 tc (`RndSem pos) [concl]
444445

446+
(* -------------------------------------------------------------------- *)
447+
let t_equiv_coupling_r side g tc =
448+
(* process the following pRHL goal, where g is a coupling of g1 and g2 *)
449+
(* {phi} c1; x <$ g1 ~ c2; y <$ g2 {psi} *)
450+
let env = FApi.tc1_env tc in
451+
let es = tc1_as_equivS tc in
452+
let ml = fst es.es_ml in
453+
let mr = fst es.es_mr in
454+
let (lvL, muL), sl' = tc1_last_rnd tc es.es_sl in
455+
let (lvR, muR), sr' = tc1_last_rnd tc es.es_sr in
456+
let tyL = proj_distr_ty env (e_ty muL) in
457+
let tyR = proj_distr_ty env (e_ty muR) in
458+
let muL = (EcFol.ss_inv_of_expr ml muL).inv in
459+
let muR = (EcFol.ss_inv_of_expr mr muR).inv in
460+
let g = g.inv in
461+
462+
let goal =
463+
match side with
464+
| None ->
465+
(* Goal: {phi} c1 ~ c2 {iscoupling g muL muR /\ (forall a b, (a, b) \in supp(g) => psi[x -> a, y -> b])} *)
466+
(* Generate two free variables a and b and the pair (a, b) *)
467+
let a_id = EcIdent.create "a" in
468+
let b_id = EcIdent.create "b" in
469+
let a = f_local a_id tyL in
470+
let b = f_local b_id tyR in
471+
let ab = f_tuple [a; b] in
472+
473+
(* Generate the coupling distribution type: (tyL * tyR) distr *)
474+
let coupling_ty = ttuple [tyL; tyR] in
475+
let g_app = f_app_simpl g [] (tdistr coupling_ty) in
476+
477+
let iscoupling_op = EcPath.extend EcCoreLib.p_top ["Distr"; "iscoupling"] in
478+
let iscoupling_ty = tfun (tdistr tyL) (tfun (tdistr tyR) (tfun (tdistr coupling_ty) tbool)) in
479+
let iscoupling_pred = f_app (f_op iscoupling_op [tyL; tyR] iscoupling_ty) [muL; muR; g_app] tbool in
480+
481+
(* Substitute in the postcondition *)
482+
let post = (es_po es) in
483+
let post_subst = subst_form_lv_left env lvL {ml=ml;mr=mr;inv=a} post in
484+
let post_subst = subst_form_lv_right env lvR {ml=ml;mr=mr;inv=b} post_subst in
485+
486+
let goal = map_ts_inv1 (f_imp (f_in_supp ab g_app)) post_subst in
487+
let goal = map_ts_inv1 (f_forall_simpl [(a_id, GTty tyL); (b_id, GTty tyR)]) goal in
488+
map_ts_inv1 (f_and iscoupling_pred) goal
489+
| Some side ->
490+
(* Goal (left): {phi} c1 ~ c2 {dmap d1 g = d2 /\
491+
forall a b, a \in supp(d1) -> b = g(a) -> psi[x -> a, y -> b]} *)
492+
(* Goal (right): {phi} c1 ~ c2 {d1 = dmap d2 g /\
493+
forall a b, b \in supp(d2) -> a = g(b) -> psi[x -> a, y -> b]} *)
494+
let dmap_op = EcPath.extend EcCoreLib.p_top ["Distr"; "dmap"] in
495+
let dmap_eq =
496+
match side with
497+
| `Left ->
498+
let dmap_ty = tfun (tdistr tyL) (tfun (tfun tyL tyR) (tdistr tyR)) in
499+
let dmap_pred = f_app (f_op dmap_op [tyL; tyR] dmap_ty) [muL ; g] (tdistr tyR) in
500+
f_eq dmap_pred muR
501+
| `Right ->
502+
let dmap_ty = tfun (tdistr tyR) (tfun (tfun tyR tyL) (tdistr tyL)) in
503+
let dmap_pred = f_app (f_op dmap_op [tyR; tyL] dmap_ty) [muR ; g] (tdistr tyL) in
504+
f_eq muL dmap_pred in
505+
506+
let a_id = EcIdent.create "a" in
507+
let b_id = EcIdent.create "b" in
508+
let a = f_local a_id tyL in
509+
let b = f_local b_id tyR in
510+
let post = es_po es in
511+
let post_subst = subst_form_lv_left env lvL {ml=ml;mr=mr;inv=a} post in
512+
let post_subst = subst_form_lv_right env lvR {ml=ml;mr=mr;inv=b} post_subst in
513+
514+
let goal =
515+
match side with
516+
| `Left -> map_ts_inv1
517+
(f_imp (f_in_supp a muL))
518+
(map_ts_inv1 (f_imp (f_eq (f_app_simpl g [a] tyR) b)) post_subst)
519+
| `Right -> map_ts_inv1
520+
(f_imp (f_in_supp b muR))
521+
(map_ts_inv1 (f_imp (f_eq a (f_app_simpl g [b] tyL))) post_subst) in
522+
523+
let goal = map_ts_inv1 (f_forall_simpl [(a_id, GTty tyL); (b_id, GTty tyR)]) goal in
524+
map_ts_inv1 (f_and dmap_eq) goal
525+
in
526+
let goal = f_equivS (snd es.es_ml) (snd es.es_mr) (es_pr es) sl' sr' goal in
527+
528+
FApi.xmutate1 tc `Rnd [goal]
445529
end (* Core *)
446530

447531
(* -------------------------------------------------------------------- *)
@@ -666,32 +750,32 @@ let process_rnd side pos tac_info tc =
666750
t_bdhoare_rnd tac_info tc
667751

668752
| _, _, _ when is_equivS concl ->
669-
let process_form f ty1 ty2 =
670-
TTC.tc1_process_prhl_form tc (tfun ty1 ty2) f in
671-
672-
let bij_info =
673-
match tac_info with
674-
| PNoRndParams -> None, None
675-
| PSingleRndParam f -> Some (process_form f), None
676-
| PTwoRndParams (f, finv) -> Some (process_form f), Some (process_form finv)
677-
| _ -> tc_error !!tc "invalid arguments"
678-
in
753+
let process_form f ty1 ty2 =
754+
TTC.tc1_process_prhl_form tc (tfun ty1 ty2) f in
755+
756+
let bij_info =
757+
match tac_info with
758+
| PNoRndParams -> None, None
759+
| PSingleRndParam f -> Some (process_form f), None
760+
| PTwoRndParams (f, finv) -> Some (process_form f), Some (process_form finv)
761+
| _ -> tc_error !!tc "invalid arguments"
762+
in
679763

680-
let pos = pos |> Option.map (function
681-
| Single (b, p) ->
682-
let p =
683-
if Option.is_some side then
684-
EcProofTyping.tc1_process_codepos1 tc (side, p)
685-
else EcTyping.trans_codepos1 (FApi.tc1_env tc) p
686-
in Single (b, p)
687-
| Double ((b1, p1), (b2, p2)) ->
688-
let p1 = EcProofTyping.tc1_process_codepos1 tc (Some `Left , p1) in
689-
let p2 = EcProofTyping.tc1_process_codepos1 tc (Some `Right, p2) in
690-
Double ((b1, p1), (b2, p2))
691-
)
692-
in
693-
694-
t_equiv_rnd side ?pos bij_info tc
764+
let pos = pos |> Option.map (function
765+
| Single (b, p) ->
766+
let p =
767+
if Option.is_some side then
768+
EcProofTyping.tc1_process_codepos1 tc (side, p)
769+
else EcTyping.trans_codepos1 (FApi.tc1_env tc) p
770+
in Single (b, p)
771+
| Double ((b1, p1), (b2, p2)) ->
772+
let p1 = EcProofTyping.tc1_process_codepos1 tc (Some `Left , p1) in
773+
let p2 = EcProofTyping.tc1_process_codepos1 tc (Some `Right, p2) in
774+
Double ((b1, p1), (b2, p2))
775+
)
776+
in
777+
778+
t_equiv_rnd side ?pos bij_info tc
695779

696780
| _ -> tc_error !!tc "invalid arguments"
697781

@@ -713,3 +797,24 @@ let process_rndsem ~reduce side pos tc =
713797
| Some side when is_equivS concl ->
714798
t_equiv_rndsem reduce side pos tc
715799
| _ -> tc_error !!tc "invalid arguments"
800+
801+
let process_coupling side g tc =
802+
let concl = FApi.tc1_goal tc in
803+
804+
if not (is_equivS concl) then
805+
tc_error !!tc "coupling can only be used on pRHL goals"
806+
else
807+
let env = FApi.tc1_env tc in
808+
let es = tc1_as_equivS tc in
809+
let (_, muL), _ = tc1_last_rnd tc es.es_sl in
810+
let (_, muR), _ = tc1_last_rnd tc es.es_sr in
811+
let tyL = proj_distr_ty env (e_ty muL) in
812+
let tyR = proj_distr_ty env (e_ty muR) in
813+
814+
let coupling_ty =
815+
match side with
816+
| None -> tdistr (ttuple [tyL; tyR])
817+
| Some `Left -> tfun tyL tyR
818+
| Some `Right -> tfun tyR tyL in
819+
let g_form = TTC.tc1_process_prhl_form tc coupling_ty g in
820+
Core.t_equiv_coupling_r side g_form tc

src/phl/ecPhlRnd.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,8 @@ val t_equiv_rnd : ?pos:semrndpos -> oside -> (mkbij_t option) pair -> backward
2424
(* -------------------------------------------------------------------- *)
2525
val process_rnd : oside -> psemrndpos option -> rnd_infos_t -> backward
2626

27+
(* -------------------------------------------------------------------- *)
28+
val process_coupling : oside -> pformula -> backward
29+
2730
(* -------------------------------------------------------------------- *)
2831
val process_rndsem : reduce:bool -> oside -> pcodepos1 -> backward

tests/coupling-rnd.ec

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
require import AllCore List.
2+
require import Distr DBool DList.
3+
(*---*) import Biased.
4+
require import StdBigop.
5+
(*---*) import Bigreal Bigreal.BRA.
6+
7+
type t.
8+
9+
op test : t -> bool.
10+
op D : t distr.
11+
12+
axiom D_ll : is_lossless D.
13+
14+
module B = {
15+
proc f() : bool = {
16+
var x: bool;
17+
var garbage: int;
18+
(* put some garbage to avoid the random sampling being the only instruction in a procedure *)
19+
garbage <- 0;
20+
x <$ dbiased (mu D test);
21+
return x;
22+
}
23+
proc g() : t = {
24+
var l: t;
25+
var garbage: int;
26+
garbage <- 0;
27+
l <$ D;
28+
return l;
29+
}
30+
}.
31+
32+
op c : (bool * t) distr = dmap D (fun x => (test x, x)).
33+
34+
(* prove the lemma by directly providing a coupling *)
35+
lemma B_coupling:
36+
equiv [ B.f ~ B.g : true ==> res{1} = test res{2} ].
37+
proof.
38+
proc.
39+
coupling c.
40+
wp; skip => /=; split; last first.
41+
+ move => a b.
42+
by rewrite /c => /supp_dmap [x] />.
43+
rewrite /iscoupling; split; last first.
44+
+ by rewrite /c dmap_comp /(\o) /= dmap_id.
45+
rewrite /c dmap_comp /(\o) /=.
46+
apply eq_distr => b.
47+
rewrite dbiased1E.
48+
rewrite clamp_id.
49+
+ exact mu_bounded.
50+
rewrite dmap1E /pred1 /(\o) /=.
51+
case b => _.
52+
+ by apply mu_eq => x /#.
53+
rewrite (mu_eq _ _ (predC test)).
54+
+ by smt().
55+
by rewrite mu_not D_ll.
56+
qed.
57+
58+
(* prove the lemma by bypr, which means we compute the probability at eash side separately. *)
59+
lemma B_coupling_bypr:
60+
equiv [ B.f ~ B.g : true ==> res{1} = test res{2} ].
61+
proof.
62+
bypr res{1} (test res{2}).
63+
+ smt().
64+
+ move => &1 &2 b.
65+
have ->: Pr[B.f() @ &1 : res = b] = mu1 (dbiased (mu D test)) b.
66+
+ byphoare => //.
67+
proc; rnd; wp; skip => />.
68+
have -> //: Pr[B.g() @ &2 : test res = b] = mu1 (dbiased (mu D test)) b.
69+
+ byphoare => //.
70+
proc; rnd; sp; skip => />.
71+
rewrite dbiased1E.
72+
rewrite clamp_id.
73+
+ exact mu_bounded.
74+
case b => _.
75+
+ apply mu_eq => x /#.
76+
rewrite (mu_eq _ _ (predC test)).
77+
+ by smt().
78+
by rewrite mu_not D_ll.
79+
qed.
80+
81+
op f (x : t) : bool = test x.
82+
83+
lemma B_compress:
84+
equiv [ B.g ~ B.f : true ==> test res{1} = res{2} ].
85+
proof.
86+
proc.
87+
coupling{1} f.
88+
wp; skip => @/predT /=.
89+
+ split.
90+
+ apply eq_distr => b.
91+
rewrite dbiased1E.
92+
rewrite clamp_id.
93+
+ exact mu_bounded.
94+
rewrite dmap1E /pred1 /(\o) /=.
95+
case b => _.
96+
+ by apply mu_eq => x /#.
97+
rewrite (mu_eq _ _ (predC test)).
98+
+ by smt().
99+
by rewrite mu_not D_ll.
100+
+ by move => @/f * //=.
101+
qed.
102+
103+
lemma B_compress_reverse:
104+
equiv [ B.f ~ B.g : true ==> test res{2} = res{1} ].
105+
proof.
106+
proc.
107+
coupling{2} f.
108+
wp; skip => @/predT /=.
109+
+ split.
110+
+ apply eq_distr => b.
111+
rewrite dbiased1E.
112+
rewrite clamp_id.
113+
+ exact mu_bounded.
114+
rewrite dmap1E /pred1 /(\o) /=.
115+
case b => _.
116+
+ by apply mu_eq => x /#.
117+
rewrite (mu_eq _ (fun (x : t) => f x = false) (predC test)).
118+
+ by smt().
119+
by rewrite mu_not D_ll.
120+
+ by move => @/f //.
121+
qed.

theories/distributions/Distr.ec

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,6 +1437,26 @@ move=> a1 a2 _ /=; apply/iscpl_dlet; first by apply: cpl_d=> /#.
14371437
by move=> b1 b2 _ /=; apply/iscpl_dunit.
14381438
qed.
14391439

1440+
(* -------------------------------------------------------------------- *)
1441+
lemma iscpl_dmap1 ['a 'b] (d1 : 'a distr) (d2 : 'b distr) (f : 'a -> 'b) :
1442+
dmap d1 f = d2 => iscoupling<:'a, 'b> d1 d2 (dmap d1 (fun x => (x, f x))).
1443+
proof.
1444+
rewrite /iscoupling => ?.
1445+
split.
1446+
+ by rewrite dmap_comp /(\o) /= dmap_id //.
1447+
+ by rewrite dmap_comp /(\o) /=.
1448+
qed.
1449+
1450+
(* -------------------------------------------------------------------- *)
1451+
lemma iscpl_dmap2 ['a 'b] (d1 : 'a distr) (d2 : 'b distr) (f : 'b -> 'a) :
1452+
dmap d2 f = d1 => iscoupling<:'a, 'b> d1 d2 (dmap d2 (fun x => (f x, x))).
1453+
proof.
1454+
rewrite /iscoupling => ?.
1455+
split.
1456+
+ by rewrite dmap_comp /(\o) /=.
1457+
+ by rewrite dmap_comp /(\o) /= dmap_id //.
1458+
qed.
1459+
14401460
(* -------------------------------------------------------------------- *)
14411461
abbrev [-printing] dapply (F: 'a -> 'b) : 'a distr -> 'b distr =
14421462
fun d => dmap d F.

0 commit comments

Comments
 (0)