Skip to content

Commit 43fd769

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

File tree

8 files changed

+251
-0
lines changed

8 files changed

+251
-0
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: 4 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
@@ -3003,6 +3004,9 @@ interleave_info:
30033004
| RNDSEM red=boption(STAR) s=side? c=codepos1
30043005
{ Prndsem (red, s, c) }
30053006

3007+
| COUPLING s=side? f=sform
3008+
{ Pcoupling (s, f) }
3009+
30063010
| INLINE s=side? u=inlineopt? o=occurences?
30073011
{ Pinline (`ByName(s, u, ([], o))) }
30083012

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/ecPhlRnd.ml

Lines changed: 100 additions & 0 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,84 @@ 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 u v, (u, v) \in supp(g) => psi[x -> u, y -> v])} *)
466+
(* Generate two free variables u and v and the pair (u, v) *)
467+
let u_id = EcIdent.create "u" in
468+
let v_id = EcIdent.create "v" in
469+
let u = f_local u_id tyL in
470+
let v = f_local v_id tyR in
471+
let ab = f_tuple [u; v] 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=u} post in
484+
let post_subst = subst_form_lv_right env lvR {ml=ml;mr=mr;inv=v} 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 [(u_id, GTty tyL); (v_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 u, u \in supp(d1) -> psi[x -> u, y -> g(u)]} *)
492+
(* Goal (right): {phi} c1 ~ c2 {d1 = dmap d2 g /\
493+
forall v, v \in supp(d2) -> psi[x -> g(v), y -> v]} *)
494+
let is_left = (side = `Left) in
495+
let (mu_main, mu_other) = if is_left then (muL, muR) else (muR, muL) in
496+
let (ty_main, ty_other) = if is_left then (tyL, tyR) else (tyR, tyL) in
497+
let (lv_main, lv_other) = if is_left then (lvL, lvR) else (lvR, lvL) in
498+
let (subst_main, subst_other) =
499+
if is_left then (subst_form_lv_left, subst_form_lv_right)
500+
else (subst_form_lv_right, subst_form_lv_left) in
501+
502+
let dmap_op = EcPath.extend EcCoreLib.p_top ["Distr"; "dmap"] in
503+
let dmap_ty = tfun (tdistr ty_main) (tfun (tfun ty_main ty_other) (tdistr ty_other)) in
504+
let dmap_pred = f_app (f_op dmap_op [ty_main; ty_other] dmap_ty) [mu_main ; g] (tdistr ty_other) in
505+
let dmap_eq = f_eq dmap_pred mu_other in
506+
507+
let var_name = if is_left then "u" else "v" in
508+
let var_ty = if is_left then tyL else tyR in
509+
let var_id = EcIdent.create var_name in
510+
let var = f_local var_id var_ty in
511+
let g_applied = f_app_simpl g [var] (if is_left then tyR else tyL) in
512+
513+
let post = es_po es in
514+
let post_subst = subst_main env lv_main {ml;mr;inv=var} post in
515+
let post_subst = subst_other env lv_other {ml;mr;inv=g_applied} post_subst in
516+
let goal = map_ts_inv1 (f_imp (f_in_supp var mu_main)) post_subst in
517+
let goal = map_ts_inv1 (f_forall_simpl [(var_id, GTty var_ty)]) goal in
518+
519+
map_ts_inv1 (f_and dmap_eq) goal
520+
in
521+
let goal = f_equivS (snd es.es_ml) (snd es.es_mr) (es_pr es) sl' sr' goal in
522+
523+
FApi.xmutate1 tc `Rnd [goal]
445524
end (* Core *)
446525

447526
(* -------------------------------------------------------------------- *)
@@ -713,3 +792,24 @@ let process_rndsem ~reduce side pos tc =
713792
| Some side when is_equivS concl ->
714793
t_equiv_rndsem reduce side pos tc
715794
| _ -> tc_error !!tc "invalid arguments"
795+
796+
let process_coupling side g tc =
797+
let concl = FApi.tc1_goal tc in
798+
799+
if not (is_equivS concl) then
800+
tc_error !!tc "coupling can only be used on pRHL goals"
801+
else
802+
let env = FApi.tc1_env tc in
803+
let es = tc1_as_equivS tc in
804+
let (_, muL), _ = tc1_last_rnd tc es.es_sl in
805+
let (_, muR), _ = tc1_last_rnd tc es.es_sr in
806+
let tyL = proj_distr_ty env (e_ty muL) in
807+
let tyR = proj_distr_ty env (e_ty muR) in
808+
809+
let coupling_ty =
810+
match side with
811+
| None -> tdistr (ttuple [tyL; tyR])
812+
| Some `Left -> tfun tyL tyR
813+
| Some `Right -> tfun tyR tyL in
814+
let g_form = TTC.tc1_process_prhl_form tc coupling_ty g in
815+
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 each 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)