clutch.gen_diffpriv.lib.trunc_laplace
Reusable TRUNCATED discrete-Laplace library for the generic DP logic — the
metric approximate-DP client.
This mirrors lib.laplace but for the bounded-support truncated discrete
Laplace family trunc_laplace_family (see gen_prob_lang/families.v), whose
per-distance coupling prob/trunc_laplace.v.DPcoupl_trunc_lap is the EXACT
group-bound profile (s·ε, δ₁·grp ε s) (with ε = num/den,
δ₁ = exp(-ε·A)/Z_A, shift s). Importing this file and providing a
SampleIn trunc_laplace_family S instance makes the TruncLaplace surface
notation and the wp_couple_trunc_laplace mechanism rule available at
WHATEVER index the family occupies in the signature S.
The truncation half-width A is a genuine RUNTIME sample parameter (the
fourth Z of trunc_laplace_family's (A,num,den,loc) interface), NOT a
Coq-level family index — so A is chosen at sample time, and the
side-conditions 0 <= A (and the regime 0 <= s <= A) that
DPcoupl_trunc_lap requires are HYPOTHESES on the runtime A. The family's
sample at param (A,num,den,loc) is trunc_lap_rat (Z.max 0 A) num den loc;
at runtime A >= 0 the clamp is the identity (Z.max 0 A = A), connecting it
to DPcoupl_trunc_lap's trunc_lap_rat A via a small Z.max_r rewrite.
Unlike lib.laplace, the draw coupling carries a NONZERO additive error
δ = tlap_delta, so we go through wp_couple_sample_gen_dp (the
δ-carrying program-couple seam) rather than wp_couple_sample_gen. The
mechanism is packaged as a hoare_diffpriv_metric instance — the validating
example for the group-bound metric approximate-DP definition.
From iris.proofmode Require Import proofmode.
From clutch.prob Require Import differential_privacy distribution couplings couplings_app couplings_dp.
From clutch.prob Require Import trunc_laplace.
From clutch.gen_diffpriv Require Export primitive_laws coupling_rules proofmode diffpriv_rules.
From clutch.gen_prob_lang Require Import inject.
From clutch.gen_prob_lang Require Export families.
From iris.prelude Require Import options.
Local Open Scope R.
From clutch.prob Require Import differential_privacy distribution couplings couplings_app couplings_dp.
From clutch.prob Require Import trunc_laplace.
From clutch.gen_diffpriv Require Export primitive_laws coupling_rules proofmode diffpriv_rules.
From clutch.gen_prob_lang Require Import inject.
From clutch.gen_prob_lang Require Export families.
From iris.prelude Require Import options.
Local Open Scope R.
TruncLaplace A num den mean tape samples the truncated discrete Laplace
family at parameter (A,num,den,mean) — with A the RUNTIME truncation
half-width — using sample tape tape (#() for a direct, tape-less sample).
As in lib.laplace, the family's index in the ambient signature is recovered
from the SampleIn instance — NOT hardcoded. The value parameter is the
nested pair (A, (num, (den, mean))) matching trunc_laplace_family's
(A,num,den,loc) mkZNoise4 encoding.
Notation TruncLaplace A num den mean tape :=
(Sample (sample_idx (D := trunc_laplace_family))
(Pair A (Pair num (Pair den mean))) tape)
(only parsing).
(Sample (sample_idx (D := trunc_laplace_family))
(Pair A (Pair num (Pair den mean))) tape)
(only parsing).
Value-form of TruncLaplace (direct, tape-less): the nested
(A, (num, (den, mean))) parameter already reduced to a PairV tree, as it
appears AFTER wp_pures/tp_pures. The coupling rule wp_couple_trunc_laplace
is stated on this shape so its precondition matches post-reduction goals; the
couple_trunc_laplace tactic relies on it. Mirrors LaplaceV/ExpV, but
with the extra leading truncation half-width A.
Notation TruncLaplaceV A num den mean :=
(Sample (sample_idx (D := trunc_laplace_family))
(Val (PairV (LitV (LitInt A))
(PairV (LitV (LitInt num))
(PairV (LitV (LitInt den)) (LitV (LitInt mean))))))
(Val (LitV LitUnit)))
(only parsing).
Section trunc_laplace_lib.
Context {S : Sig} `{!SampleIn trunc_laplace_family S} `{!diffprivGS S Σ}.
Local Notation fill := (@ectx_language.fill (gen_ectx_lang S)).
(* the family's index in S, recovered from the SampleIn instance *)
Local Notation tlidx := (@sample_idx trunc_laplace_family S _).
(Sample (sample_idx (D := trunc_laplace_family))
(Val (PairV (LitV (LitInt A))
(PairV (LitV (LitInt num))
(PairV (LitV (LitInt den)) (LitV (LitInt mean))))))
(Val (LitV LitUnit)))
(only parsing).
Section trunc_laplace_lib.
Context {S : Sig} `{!SampleIn trunc_laplace_family S} `{!diffprivGS S Σ}.
Local Notation fill := (@ectx_language.fill (gen_ectx_lang S)).
(* the family's index in S, recovered from the SampleIn instance *)
Local Notation tlidx := (@sample_idx trunc_laplace_family S _).
The fixed additive boundary mass δ₁ = exp(-ε·A)/Z_A. Independent of the
location (the normaliser tlap_Z is shift-invariant), so we pin loc = 0.
Definition tlap_del (A num den : Z) : R :=
exp (- (IZR num / IZR den) * IZR A) / tlap_Z A num den 0.
exp (- (IZR num / IZR den) * IZR A) / tlap_Z A num den 0.
tlap_delta factors as tlap_del · grp ε |s| — the group-bound shape.
Now over the two-sided shift: tlap_delta depends only on |s|.
Lemma tlap_delta_grp (A num den loc s : Z) :
tlap_delta A num den loc s
= tlap_del A num den * grp (IZR num / IZR den) (IZR (Z.abs s)).
Proof.
rewrite /tlap_delta /tlap_del /grp.
rewrite (tlap_Z_shift_inv A num den loc 0).
lra.
Qed.
tlap_delta A num den loc s
= tlap_del A num den * grp (IZR num / IZR den) (IZR (Z.abs s)).
Proof.
rewrite /tlap_delta /tlap_del /grp.
rewrite (tlap_Z_shift_inv A num den loc 0).
lra.
Qed.
The family's clamped sample equals trunc_lap_rat A … for runtime A >= 0
(Z.max 0 A = A). This is what connects trunc_laplace_family's sample to
DPcoupl_trunc_lap's trunc_lap_rat A. Holds DEFINITIONALLY up to the
Z.max clamp, which the 0 <= A hypothesis discharges via Z.max_r.
Lemma sf_sample_trunc (A num den loc : Z) (HA : (0 <= A)%Z) :
sf_sample trunc_laplace_family (A, num, den, loc) = trunc_lap_rat A num den loc.
Proof.
simpl. by rewrite (Z.max_r 0 A HA).
Qed.
sf_sample trunc_laplace_family (A, num, den, loc) = trunc_lap_rat A num den loc.
Proof.
simpl. by rewrite (Z.max_r 0 A HA).
Qed.
The core truncated-Laplace draw coupling, on the family's outcome type Z:
for a TWO-SIDED shift s (UNCONDITIONALLY — no |s| ≤ A), sampling at
loc (impl) couples to sampling at loc+s (spec) along the DIAGONAL
(z = z') at cost (|s| · ε) multiplicative AND tlap_delta A num den loc s
additive. Family-level fact, discharged by the reusable two-sided (now
unconditional) DPcoupl_trunc_lap after the Z.max 0 A = A clamp rewrite
(sf_sample_trunc, needs 0 <= A).
Lemma DPcoupl_trunc_lap_draw (A num den loc s : Z)
(HA : (0 <= A)%Z)
(Hpos : (0 < IZR num / IZR den)%R) :
DPcoupl (sf_sample trunc_laplace_family (A, num, den, loc))
(sf_sample trunc_laplace_family (A, num, den, (loc + s)%Z))
eq (IZR (Z.abs s) * (IZR num / IZR den)) (tlap_delta A num den loc s).
Proof.
rewrite !sf_sample_trunc //. by apply (DPcoupl_trunc_lap A num den HA Hpos loc s).
Qed.
(HA : (0 <= A)%Z)
(Hpos : (0 < IZR num / IZR den)%R) :
DPcoupl (sf_sample trunc_laplace_family (A, num, den, loc))
(sf_sample trunc_laplace_family (A, num, den, (loc + s)%Z))
eq (IZR (Z.abs s) * (IZR num / IZR den)) (tlap_delta A num den loc s).
Proof.
rewrite !sf_sample_trunc //. by apply (DPcoupl_trunc_lap A num den HA Hpos loc s).
Qed.
The TIGHT truncated-Laplace draw coupling, on the family's outcome type Z:
for a TWO-SIDED shift s (UNCONDITIONALLY), sampling at loc (impl) couples
to sampling at loc+s (spec) along the DIAGONAL (z = z') at cost
(|s| · ε) multiplicative AND the OPTIMAL additive error
tlap_tail A num den |s| (the uncovered bad-set tail mass). Family-level
fact, discharged by the tight reusable DPcoupl_trunc_lap_tight after the
Z.max 0 A = A clamp rewrite (sf_sample_trunc, needs 0 <= A). At
adjacency (|s| ≤ 1) this gives the TIGHT δ expressed by
hoare_diffpriv_classic.
Lemma DPcoupl_trunc_lap_draw_tight (A num den loc s : Z)
(HA : (0 <= A)%Z)
(Hpos : (0 < IZR num / IZR den)%R) :
DPcoupl (sf_sample trunc_laplace_family (A, num, den, loc))
(sf_sample trunc_laplace_family (A, num, den, (loc + s)%Z))
eq (IZR (Z.abs s) * (IZR num / IZR den)) (tlap_tail A num den (Z.abs s)).
Proof.
rewrite !sf_sample_trunc //. by apply (DPcoupl_trunc_lap_tight A num den HA Hpos loc s).
Qed.
(HA : (0 <= A)%Z)
(Hpos : (0 < IZR num / IZR den)%R) :
DPcoupl (sf_sample trunc_laplace_family (A, num, den, loc))
(sf_sample trunc_laplace_family (A, num, den, (loc + s)%Z))
eq (IZR (Z.abs s) * (IZR num / IZR den)) (tlap_tail A num den (Z.abs s)).
Proof.
rewrite !sf_sample_trunc //. by apply (DPcoupl_trunc_lap_tight A num den HA Hpos loc s).
Qed.
The truncated-Laplace mechanism at the WP level: sampling at location loc
(impl) vs loc+s (spec), TWO-SIDED (UNCONDITIONAL — no |s| ≤ A), couples
to EQUAL outputs at multiplicative cost |s|·ε and additive cost
tlap_delta A num den loc s. Obtained by instantiating the δ-carrying
prog-couple seam wp_couple_sample_gen_dp with the single (now
unconditional) draw coupling DPcoupl_trunc_lap_draw — at the recovered
index sample_idx. A is a RUNTIME Z argument with 0 <= A a
hypothesis.
Lemma wp_couple_trunc_laplace (A loc s : Z)
(HA : (0 <= A)%Z)
(num den : Z) (ε ε' δ' : R) K E :
IZR num / IZR den = ε →
0 < IZR num / IZR den →
ε' = (IZR (Z.abs s) * ε) →
δ' = tlap_delta A num den loc s →
{{{ ⤇ fill K (Sample tlidx
(Val (PairV (LitV (LitInt A))
(PairV (LitV (LitInt num))
(PairV (LitV (LitInt den)) (LitV (LitInt (loc + s)))))))
(Val (LitV LitUnit))) ∗ ↯m ε' ∗ ↯ δ' }}}
Sample tlidx
(Val (PairV (LitV (LitInt A))
(PairV (LitV (LitInt num))
(PairV (LitV (LitInt den)) (LitV (LitInt loc))))))
(Val (LitV LitUnit)) @ E
{{{ (z : Z), RET #z; ⤇ fill K #z }}}.
Proof.
iIntros (Hε εpos Hε' Hδ' Φ) "(Hr & Hε & Hδ) HΦ".
iApply (wp_couple_sample_gen_dp S tlidx
(sf_param_to_val trunc_laplace_family (A, num, den, loc))
(sf_param_to_val trunc_laplace_family (A, num, den, (loc + s)%Z))
(dmap (sf_inj trunc_laplace_family) (sf_sample trunc_laplace_family (A, num, den, loc)))
(dmap (sf_inj trunc_laplace_family) (sf_sample trunc_laplace_family (A, num, den, (loc + s)%Z)))
(λ v v', ∃ z : Z, v = #z ∧ v' = #z) K E ε' δ'
_
(sig_sample_at trunc_laplace_family S (A, num, den, loc))
(sig_sample_at trunc_laplace_family S (A, num, den, (loc + s)%Z)) _ with "[$Hr $Hε $Hδ]").
{ iIntros "!>" (v v') "(Hspec & %HR)". destruct HR as (z & -> & ->).
iApply ("HΦ" $! z with "Hspec"). }
(* two obligations: the additive-credit nonnegativity 0 <= δ', and the
single δ-carrying DP obligation — lift the truncated draw coupling
through sf_inj. *)
Unshelve.
- subst δ'. rewrite tlap_delta_grp.
apply Rmult_le_pos.
+ rewrite /tlap_del. apply Rcomplements.Rdiv_le_0_compat;
[left; apply exp_pos | apply (tlap_Z_pos A num den 0 HA)].
+ apply grp_nonneg; [lra | apply IZR_le; lia].
- apply DPcoupl_map.
{ subst ε'. apply Rmult_le_pos; [apply IZR_le; lia | lra]. }
{ subst δ'. rewrite tlap_delta_grp.
apply Rmult_le_pos.
- rewrite /tlap_del. apply Rcomplements.Rdiv_le_0_compat;
[left; apply exp_pos | apply (tlap_Z_pos A num den 0 HA)].
- apply grp_nonneg; [lra | apply IZR_le; lia]. }
eapply (DPcoupl_mono _ _ _ _ eq _ ε' ε' δ' δ');
[ intros; reflexivity
| intros; reflexivity
| intros z z' Hzz'; exists z; split; [done | by rewrite Hzz']
| lra | lra
| subst ε' δ'; rewrite -Hε; by apply DPcoupl_trunc_lap_draw ].
Qed.
(HA : (0 <= A)%Z)
(num den : Z) (ε ε' δ' : R) K E :
IZR num / IZR den = ε →
0 < IZR num / IZR den →
ε' = (IZR (Z.abs s) * ε) →
δ' = tlap_delta A num den loc s →
{{{ ⤇ fill K (Sample tlidx
(Val (PairV (LitV (LitInt A))
(PairV (LitV (LitInt num))
(PairV (LitV (LitInt den)) (LitV (LitInt (loc + s)))))))
(Val (LitV LitUnit))) ∗ ↯m ε' ∗ ↯ δ' }}}
Sample tlidx
(Val (PairV (LitV (LitInt A))
(PairV (LitV (LitInt num))
(PairV (LitV (LitInt den)) (LitV (LitInt loc))))))
(Val (LitV LitUnit)) @ E
{{{ (z : Z), RET #z; ⤇ fill K #z }}}.
Proof.
iIntros (Hε εpos Hε' Hδ' Φ) "(Hr & Hε & Hδ) HΦ".
iApply (wp_couple_sample_gen_dp S tlidx
(sf_param_to_val trunc_laplace_family (A, num, den, loc))
(sf_param_to_val trunc_laplace_family (A, num, den, (loc + s)%Z))
(dmap (sf_inj trunc_laplace_family) (sf_sample trunc_laplace_family (A, num, den, loc)))
(dmap (sf_inj trunc_laplace_family) (sf_sample trunc_laplace_family (A, num, den, (loc + s)%Z)))
(λ v v', ∃ z : Z, v = #z ∧ v' = #z) K E ε' δ'
_
(sig_sample_at trunc_laplace_family S (A, num, den, loc))
(sig_sample_at trunc_laplace_family S (A, num, den, (loc + s)%Z)) _ with "[$Hr $Hε $Hδ]").
{ iIntros "!>" (v v') "(Hspec & %HR)". destruct HR as (z & -> & ->).
iApply ("HΦ" $! z with "Hspec"). }
(* two obligations: the additive-credit nonnegativity 0 <= δ', and the
single δ-carrying DP obligation — lift the truncated draw coupling
through sf_inj. *)
Unshelve.
- subst δ'. rewrite tlap_delta_grp.
apply Rmult_le_pos.
+ rewrite /tlap_del. apply Rcomplements.Rdiv_le_0_compat;
[left; apply exp_pos | apply (tlap_Z_pos A num den 0 HA)].
+ apply grp_nonneg; [lra | apply IZR_le; lia].
- apply DPcoupl_map.
{ subst ε'. apply Rmult_le_pos; [apply IZR_le; lia | lra]. }
{ subst δ'. rewrite tlap_delta_grp.
apply Rmult_le_pos.
- rewrite /tlap_del. apply Rcomplements.Rdiv_le_0_compat;
[left; apply exp_pos | apply (tlap_Z_pos A num den 0 HA)].
- apply grp_nonneg; [lra | apply IZR_le; lia]. }
eapply (DPcoupl_mono _ _ _ _ eq _ ε' ε' δ' δ');
[ intros; reflexivity
| intros; reflexivity
| intros z z' Hzz'; exists z; split; [done | by rewrite Hzz']
| lra | lra
| subst ε' δ'; rewrite -Hε; by apply DPcoupl_trunc_lap_draw ].
Qed.
The TIGHT truncated-Laplace WP rule: sampling at location loc (impl) vs
loc+s (spec), TWO-SIDED (UNCONDITIONAL), couples to EQUAL outputs at
multiplicative cost |s|·ε and the OPTIMAL additive cost
tlap_tail A num den |s|. Same wp_couple_sample_gen_dp seam as
wp_couple_trunc_laplace, but the draw coupling is the tight
DPcoupl_trunc_lap_draw_tight. This is what powers the TIGHT classic
adjacency instance hoare_trunc_laplace_diffpriv_classic.
Lemma wp_couple_trunc_laplace_tight (A loc s : Z)
(HA : (0 <= A)%Z)
(num den : Z) (ε ε' δ' : R) K E :
IZR num / IZR den = ε →
0 < IZR num / IZR den →
ε' = (IZR (Z.abs s) * ε) →
δ' = tlap_tail A num den (Z.abs s) →
{{{ ⤇ fill K (Sample tlidx
(Val (PairV (LitV (LitInt A))
(PairV (LitV (LitInt num))
(PairV (LitV (LitInt den)) (LitV (LitInt (loc + s)))))))
(Val (LitV LitUnit))) ∗ ↯m ε' ∗ ↯ δ' }}}
Sample tlidx
(Val (PairV (LitV (LitInt A))
(PairV (LitV (LitInt num))
(PairV (LitV (LitInt den)) (LitV (LitInt loc))))))
(Val (LitV LitUnit)) @ E
{{{ (z : Z), RET #z; ⤇ fill K #z }}}.
Proof.
iIntros (Hε εpos Hε' Hδ' Φ) "(Hr & Hε & Hδ) HΦ".
iApply (wp_couple_sample_gen_dp S tlidx
(sf_param_to_val trunc_laplace_family (A, num, den, loc))
(sf_param_to_val trunc_laplace_family (A, num, den, (loc + s)%Z))
(dmap (sf_inj trunc_laplace_family) (sf_sample trunc_laplace_family (A, num, den, loc)))
(dmap (sf_inj trunc_laplace_family) (sf_sample trunc_laplace_family (A, num, den, (loc + s)%Z)))
(λ v v', ∃ z : Z, v = #z ∧ v' = #z) K E ε' δ'
_
(sig_sample_at trunc_laplace_family S (A, num, den, loc))
(sig_sample_at trunc_laplace_family S (A, num, den, (loc + s)%Z)) _ with "[$Hr $Hε $Hδ]").
{ iIntros "!>" (v v') "(Hspec & %HR)". destruct HR as (z & -> & ->).
iApply ("HΦ" $! z with "Hspec"). }
Unshelve.
- subst δ'. apply tlap_tail_nonneg.
- apply DPcoupl_map.
{ subst ε'. apply Rmult_le_pos; [apply IZR_le; lia | lra]. }
{ subst δ'. apply tlap_tail_nonneg. }
eapply (DPcoupl_mono _ _ _ _ eq _ ε' ε' δ' δ');
[ intros; reflexivity
| intros; reflexivity
| intros z z' Hzz'; exists z; split; [done | by rewrite Hzz']
| lra | lra
| subst ε' δ'; rewrite -Hε; by apply DPcoupl_trunc_lap_draw_tight ].
Qed.
(HA : (0 <= A)%Z)
(num den : Z) (ε ε' δ' : R) K E :
IZR num / IZR den = ε →
0 < IZR num / IZR den →
ε' = (IZR (Z.abs s) * ε) →
δ' = tlap_tail A num den (Z.abs s) →
{{{ ⤇ fill K (Sample tlidx
(Val (PairV (LitV (LitInt A))
(PairV (LitV (LitInt num))
(PairV (LitV (LitInt den)) (LitV (LitInt (loc + s)))))))
(Val (LitV LitUnit))) ∗ ↯m ε' ∗ ↯ δ' }}}
Sample tlidx
(Val (PairV (LitV (LitInt A))
(PairV (LitV (LitInt num))
(PairV (LitV (LitInt den)) (LitV (LitInt loc))))))
(Val (LitV LitUnit)) @ E
{{{ (z : Z), RET #z; ⤇ fill K #z }}}.
Proof.
iIntros (Hε εpos Hε' Hδ' Φ) "(Hr & Hε & Hδ) HΦ".
iApply (wp_couple_sample_gen_dp S tlidx
(sf_param_to_val trunc_laplace_family (A, num, den, loc))
(sf_param_to_val trunc_laplace_family (A, num, den, (loc + s)%Z))
(dmap (sf_inj trunc_laplace_family) (sf_sample trunc_laplace_family (A, num, den, loc)))
(dmap (sf_inj trunc_laplace_family) (sf_sample trunc_laplace_family (A, num, den, (loc + s)%Z)))
(λ v v', ∃ z : Z, v = #z ∧ v' = #z) K E ε' δ'
_
(sig_sample_at trunc_laplace_family S (A, num, den, loc))
(sig_sample_at trunc_laplace_family S (A, num, den, (loc + s)%Z)) _ with "[$Hr $Hε $Hδ]").
{ iIntros "!>" (v v') "(Hspec & %HR)". destruct HR as (z & -> & ->).
iApply ("HΦ" $! z with "Hspec"). }
Unshelve.
- subst δ'. apply tlap_tail_nonneg.
- apply DPcoupl_map.
{ subst ε'. apply Rmult_le_pos; [apply IZR_le; lia | lra]. }
{ subst δ'. apply tlap_tail_nonneg. }
eapply (DPcoupl_mono _ _ _ _ eq _ ε' ε' δ' δ');
[ intros; reflexivity
| intros; reflexivity
| intros z z' Hzz'; exists z; split; [done | by rewrite Hzz']
| lra | lra
| subst ε' δ'; rewrite -Hε; by apply DPcoupl_trunc_lap_draw_tight ].
Qed.
grp ε · is monotone in the distance (for ε > 0): the numerator
exp(c·ε)-1 is increasing in c and the denominator exp ε - 1 > 0.
Lemma grp_mono (eps c1 c2 : R) :
0 < eps -> 0 <= c1 -> c1 <= c2 -> grp eps c1 <= grp eps c2.
Proof.
intros Heps Hc1 Hle. rewrite /grp.
apply Rmult_le_compat_r.
- left. apply Rinv_pos.
cut (1 < exp eps); [lra|]. rewrite -exp_0. apply exp_increasing. lra.
- apply Rplus_le_compat_r.
destruct (Rle_lt_or_eq_dec _ _ Hle) as [Hlt | <-].
+ left. apply exp_increasing. apply Rmult_lt_compat_r; lra.
+ lra.
Qed.
0 < eps -> 0 <= c1 -> c1 <= c2 -> grp eps c1 <= grp eps c2.
Proof.
intros Heps Hc1 Hle. rewrite /grp.
apply Rmult_le_compat_r.
- left. apply Rinv_pos.
cut (1 < exp eps); [lra|]. rewrite -exp_0. apply exp_increasing. lra.
- apply Rplus_le_compat_r.
destruct (Rle_lt_or_eq_dec _ _ Hle) as [Hlt | <-].
+ left. apply exp_increasing. apply Rmult_lt_compat_r; lra.
+ lra.
Qed.
The TRUNCATED-LAPLACE MECHANISM as a group-bound metric approximate-DP
program over PLAIN dZ — the validating CLIENT for hoare_diffpriv_metric.
At base rate ε = num/den and additive base mass tlap_del A num den, for
inputs at distance dZ x x' ≤ c the mechanism demands ↯m (c·ε) AND
↯ (tlap_del · grp ε c), and the two output draws agree — exactly the
hoare_diffpriv_metric precondition shape with eps := ε,
del := tlap_del A num den. A is a RUNTIME Z argument with 0 <= A a
hypothesis.
TWO-SIDED AND UNCONDITIONAL. Thanks to the now-unconditional coupling
DPcoupl_trunc_lap / wp_couple_trunc_laplace (the old |s| ≤ A regime
restriction is GONE), the group-bound budget ↯ (tlap_del · grp ε c) is
valid at EVERY distance c — so this is a bare hoare_diffpriv_metric
instance over the PLAIN metric dZ, with NO truncated-metric (dZ_trunc)
scaffolding and NO range split. (Beyond the half-width the group bound is
sound but strictly LOOSE; the TIGHT adjacency δ is reported by the classic
instance hoare_trunc_laplace_diffpriv_classic below.)
PROOF. The SAME single-coupling structure as lib.laplace's
hoare_laplace_diffpriv: tp_pures/wp_pures then tp_bind/wp_bind
the Sample, apply the value-form wp_couple_trunc_laplace at the actual
shift s = x'-x (so impl loc x, spec loc x + s = x'), and weaken its
(|s|·ε, tlap_delta x s) = (|s|·ε, tlap_del·grp ε |s|) profile DOWN from the
demanded (c·ε, tlap_del·grp ε c) credits — multiplicative via ecm_weaken
(|s| ≤ c); additive via ec_weaken and grp_mono (grp ε |s| ≤ grp ε c).
Fact hoare_trunc_laplace_diffpriv (A num den : Z)
(HA : (0 <= A)%Z)
(Hpos : 0 < IZR num / IZR den) :
⊢ hoare_diffpriv_metric S
(λ: "loc", TruncLaplace #A #num #den "loc" #())%V
(IZR num / IZR den) (tlap_del A num den) dZ Z.
Proof.
rewrite /hoare_diffpriv_metric. iIntros (K c x x' adj).
iIntros (phi) "!> (f' & ε & δ) hφ".
tp_pures. wp_pures.
(* the actual shift s = x'-x (EITHER sign), |s| = dZ x x' ≤ c *)
set (s := (x' - x)%Z).
assert (Habs : dZ x x' = IZR (Z.abs s))
by (rewrite /dZ /= /s -abs_IZR; f_equal; lia).
assert (Hsc : IZR (Z.abs s) <= c) by (rewrite -Habs; exact adj).
(* align the spec location x' with the coupling's x + s form *)
replace x' with (x + s)%Z by (rewrite /s; lia).
tp_bind (Sample _ _ _). wp_bind (Sample _ _ _).
(* value-form (UNCONDITIONAL) rule at loc x, shift s (spec loc x + s) *)
iApply (wp_couple_trunc_laplace A x s HA num den
(IZR num / IZR den) (IZR (Z.abs s) * (IZR num / IZR den))
(tlap_delta A num den x s) K ⊤
eq_refl Hpos eq_refl eq_refl with "[$f' ε δ]").
(* weaken the demanded (c·ε, tlap_del·grp ε c) DOWN to the consumed
(|s|·ε, tlap_delta x s) = (|s|·ε, tlap_del·grp ε |s|) *)
- rewrite tlap_delta_grp.
iSplitL "ε".
+ iApply ecm_weaken; [|iFrame]. split.
* apply Rmult_le_pos; [apply IZR_le; lia | lra].
* apply Rmult_le_compat_r; [lra | exact Hsc].
+ iApply ec_weaken; [|iFrame]. split.
* apply Rmult_le_pos.
-- rewrite /tlap_del. apply Rcomplements.Rdiv_le_0_compat;
[left; apply exp_pos
| apply (clutch.prob.trunc_laplace.tlap_Z_pos A num den 0 HA)].
-- apply grp_nonneg; [lra | apply IZR_le; lia].
* apply Rmult_le_compat_l.
-- rewrite /tlap_del. apply Rcomplements.Rdiv_le_0_compat;
[left; apply exp_pos
| apply (clutch.prob.trunc_laplace.tlap_Z_pos A num den 0 HA)].
-- apply grp_mono; [lra | apply IZR_le; lia | exact Hsc].
- iIntros "!> %z f'". iApply ("hφ" $! z). iFrame.
Qed.
(HA : (0 <= A)%Z)
(Hpos : 0 < IZR num / IZR den) :
⊢ hoare_diffpriv_metric S
(λ: "loc", TruncLaplace #A #num #den "loc" #())%V
(IZR num / IZR den) (tlap_del A num den) dZ Z.
Proof.
rewrite /hoare_diffpriv_metric. iIntros (K c x x' adj).
iIntros (phi) "!> (f' & ε & δ) hφ".
tp_pures. wp_pures.
(* the actual shift s = x'-x (EITHER sign), |s| = dZ x x' ≤ c *)
set (s := (x' - x)%Z).
assert (Habs : dZ x x' = IZR (Z.abs s))
by (rewrite /dZ /= /s -abs_IZR; f_equal; lia).
assert (Hsc : IZR (Z.abs s) <= c) by (rewrite -Habs; exact adj).
(* align the spec location x' with the coupling's x + s form *)
replace x' with (x + s)%Z by (rewrite /s; lia).
tp_bind (Sample _ _ _). wp_bind (Sample _ _ _).
(* value-form (UNCONDITIONAL) rule at loc x, shift s (spec loc x + s) *)
iApply (wp_couple_trunc_laplace A x s HA num den
(IZR num / IZR den) (IZR (Z.abs s) * (IZR num / IZR den))
(tlap_delta A num den x s) K ⊤
eq_refl Hpos eq_refl eq_refl with "[$f' ε δ]").
(* weaken the demanded (c·ε, tlap_del·grp ε c) DOWN to the consumed
(|s|·ε, tlap_delta x s) = (|s|·ε, tlap_del·grp ε |s|) *)
- rewrite tlap_delta_grp.
iSplitL "ε".
+ iApply ecm_weaken; [|iFrame]. split.
* apply Rmult_le_pos; [apply IZR_le; lia | lra].
* apply Rmult_le_compat_r; [lra | exact Hsc].
+ iApply ec_weaken; [|iFrame]. split.
* apply Rmult_le_pos.
-- rewrite /tlap_del. apply Rcomplements.Rdiv_le_0_compat;
[left; apply exp_pos
| apply (clutch.prob.trunc_laplace.tlap_Z_pos A num den 0 HA)].
-- apply grp_nonneg; [lra | apply IZR_le; lia].
* apply Rmult_le_compat_l.
-- rewrite /tlap_del. apply Rcomplements.Rdiv_le_0_compat;
[left; apply exp_pos
| apply (clutch.prob.trunc_laplace.tlap_Z_pos A num den 0 HA)].
-- apply grp_mono; [lra | apply IZR_le; lia | exact Hsc].
- iIntros "!> %z f'". iApply ("hφ" $! z). iFrame.
Qed.
tlap_tail is monotone in the bad-set radius Δ: the indicator
A - Δ < z grows as Δ grows, so the (nonneg) tail mass only increases.
Used to weaken the consumed adjacency δ (at |s| ≤ 1) up to the reported
tight value tlap_tail A num den 1.
Lemma tlap_tail_mono (A num den Δ1 Δ2 : Z) :
(Δ1 <= Δ2)%Z → tlap_tail A num den Δ1 <= tlap_tail A num den Δ2.
Proof.
intros HΔ. rewrite /tlap_tail.
apply SeriesC_le; last apply ex_seriesC_tlap_tail_ind.
intros z. repeat case_bool_decide; split;
try apply pmf_pos; try lra; try apply Rle_refl.
- exfalso. destruct H as [Hin H1]. apply H0. split; [exact Hin | lia].
Qed.
(Δ1 <= Δ2)%Z → tlap_tail A num den Δ1 <= tlap_tail A num den Δ2.
Proof.
intros HΔ. rewrite /tlap_tail.
apply SeriesC_le; last apply ex_seriesC_tlap_tail_ind.
intros z. repeat case_bool_decide; split;
try apply pmf_pos; try lra; try apply Rle_refl.
- exfalso. destruct H as [Hin H1]. apply H0. split; [exact Hin | lia].
Qed.
The TIGHT classic-adjacency instance.
Fact hoare_trunc_laplace_diffpriv_classic (A num den : Z)
(HA : (0 <= A)%Z)
(Hpos : 0 < IZR num / IZR den) :
⊢ hoare_diffpriv_classic S
(λ: "loc", TruncLaplace #A #num #den "loc" #())%V
(IZR num / IZR den) (tlap_tail A num den 1) dZ Z.
Proof.
rewrite /hoare_diffpriv_classic. iIntros (K x x' adj).
iIntros (phi) "!> (f' & ε & δ) hφ".
tp_pures. wp_pures.
set (s := (x' - x)%Z).
assert (Habs : dZ x x' = IZR (Z.abs s))
by (rewrite /dZ /= /s -abs_IZR; f_equal; lia).
(* adjacency dZ x x' ≤ 1 ⟹ |s| ≤ 1 *)
assert (Hs1 : (Z.abs s <= 1)%Z)
by (apply le_IZR; rewrite -Habs; exact adj).
replace x' with (x + s)%Z by (rewrite /s; lia).
tp_bind (Sample _ _ _). wp_bind (Sample _ _ _).
(* TIGHT value-form rule at loc x, shift s (spec loc x + s) *)
iApply (wp_couple_trunc_laplace_tight A x s HA num den
(IZR num / IZR den) (IZR (Z.abs s) * (IZR num / IZR den))
(tlap_tail A num den (Z.abs s)) K ⊤
eq_refl Hpos eq_refl eq_refl with "[$f' ε δ]").
(* weaken the demanded (ε, tlap_tail 1) DOWN to the consumed
(|s|·ε, tlap_tail |s|) (as |s| ≤ 1) *)
- iSplitL "ε".
+ iApply ecm_weaken; [|iFrame]. split.
* apply Rmult_le_pos; [apply IZR_le; lia | lra].
* etrans; [apply Rmult_le_compat_r; [lra | apply (IZR_le _ 1); exact Hs1] |].
rewrite Rmult_1_l. apply Rle_refl.
+ iApply ec_weaken; [|iFrame]. split.
* apply tlap_tail_nonneg.
* apply tlap_tail_mono. lia.
- iIntros "!> %z f'". iApply ("hφ" $! z). iFrame.
Qed.
End trunc_laplace_lib.
(HA : (0 <= A)%Z)
(Hpos : 0 < IZR num / IZR den) :
⊢ hoare_diffpriv_classic S
(λ: "loc", TruncLaplace #A #num #den "loc" #())%V
(IZR num / IZR den) (tlap_tail A num den 1) dZ Z.
Proof.
rewrite /hoare_diffpriv_classic. iIntros (K x x' adj).
iIntros (phi) "!> (f' & ε & δ) hφ".
tp_pures. wp_pures.
set (s := (x' - x)%Z).
assert (Habs : dZ x x' = IZR (Z.abs s))
by (rewrite /dZ /= /s -abs_IZR; f_equal; lia).
(* adjacency dZ x x' ≤ 1 ⟹ |s| ≤ 1 *)
assert (Hs1 : (Z.abs s <= 1)%Z)
by (apply le_IZR; rewrite -Habs; exact adj).
replace x' with (x + s)%Z by (rewrite /s; lia).
tp_bind (Sample _ _ _). wp_bind (Sample _ _ _).
(* TIGHT value-form rule at loc x, shift s (spec loc x + s) *)
iApply (wp_couple_trunc_laplace_tight A x s HA num den
(IZR num / IZR den) (IZR (Z.abs s) * (IZR num / IZR den))
(tlap_tail A num den (Z.abs s)) K ⊤
eq_refl Hpos eq_refl eq_refl with "[$f' ε δ]").
(* weaken the demanded (ε, tlap_tail 1) DOWN to the consumed
(|s|·ε, tlap_tail |s|) (as |s| ≤ 1) *)
- iSplitL "ε".
+ iApply ecm_weaken; [|iFrame]. split.
* apply Rmult_le_pos; [apply IZR_le; lia | lra].
* etrans; [apply Rmult_le_compat_r; [lra | apply (IZR_le _ 1); exact Hs1] |].
rewrite Rmult_1_l. apply Rle_refl.
+ iApply ec_weaken; [|iFrame]. split.
* apply tlap_tail_nonneg.
* apply tlap_tail_mono. lia.
- iIntros "!> %z f'". iApply ("hφ" $! z). iFrame.
Qed.
End trunc_laplace_lib.
The truncated-discrete-Laplace coupling tactics follow the consolidated
design of lib.laplace: TWO tactics — the bundled couple_trunc_laplace and
the apply-only couple_trunc_laplace_apply — built from the family-agnostic
couple_bind (defined in gen_diffpriv.proofmode) plus the thin
family-specific couple_trunc_laplace_iapply and the trunc-local discharge
batteries below. The old couple_trunc_laplace_cost is subsumed by
couple_trunc_laplace: the routed (non-exact cost) regime is selected by the
caller's \"[$Hr Hε Hδ]\" pattern, not by a separate tactic.
Two family wrinkles keep the discharge batteries trunc-LOCAL rather than the
family-agnostic couple_discharge/couple_discharge_apply: (1) this coupling
is built over the δ-carrying seam wp_couple_sample_gen_dp, so its
precondition threads THREE resources — the spec ⤇, the multiplicative credit
↯m ε' AND the additive credit ↯ δ' — hence the 3-way pattern
\"[$Hr $Hε $Hδ]\" (vs the 2-way pattern of laplace/exp); and (2) the regime
side-condition Hs can take the conjunctive form 0 ≤ s ≤ A, which needs the
split; lia alternative the shared batteries omit.
couple_trunc_laplace_iapply A s pat — the family-specific apply step shared
by couple_trunc_laplace and couple_trunc_laplace_apply. unshelve (the
tactical, not the Unshelve command) turns ONLY the goals THIS iApply
shelves — the regime HA/Hs and equational Hε/εpos/Hε'/Hδ' side-
conditions — into regular front goals.
Ltac couple_trunc_laplace_iapply A s pat :=
unshelve (iApply (wp_couple_trunc_laplace A _ s _ _ _ _ _ _ _ _ _ with pat)).
unshelve (iApply (wp_couple_trunc_laplace A _ s _ _ _ _ _ _ _ _ _ with pat)).
couple_trunc_discharge — the bundled-tactic battery (trunc analogue of the
shared couple_discharge, with the extra split; lia for the conjunctive
regime 0 ≤ s ≤ A). Pins the regime HA/Hs and equational
Hε/εpos/Hε'/Hδ' side-conditions; the |- R => fail-guarded
assumption stops a bare real-valued value-evar from being instantiated with
an in-context c : R at routed (cost) sites, where the two residual credit
goals ↯m (s·ε) / ↯ δ' (both iProps) survive for the caller's ecm_*.
Ltac couple_trunc_discharge :=
try first
[ reflexivity
| lia
| (split; lia)
| (match goal with |- R => fail 1 | _ => assumption end)
| (simpl; lra) ].
try first
[ reflexivity
| lia
| (split; lia)
| (match goal with |- R => fail 1 | _ => assumption end)
| (simpl; lra) ].
couple_trunc_discharge_apply — the slimmer apply-only battery (trunc
analogue of the shared couple_discharge_apply): same guarded shape without
the simpl; lra framed-closer, so the interleaved apply sites' hand-written
residual closers are left untouched.
Ltac couple_trunc_discharge_apply :=
try first
[ lia
| (split; lia)
| reflexivity
| (match goal with |- R => fail 1 | _ => assumption end) ].
try first
[ lia
| (split; lia)
| reflexivity
| (match goal with |- R => fail 1 | _ => assumption end) ].
couple_trunc_laplace A s with "[…]" — the ergonomic coupling step for the
truncated discrete Laplace. Focuses the Sample on both sides
(couple_bind) and applies the value-form wp_couple_trunc_laplace inferring
loc/num/den/ε/ε'/δ'/K/E from the goal — the author supplies only the runtime
truncation half-width A and the forward shift s (so impl loc loc, spec
loc loc+s), together with the 3-way resource pattern. Subsumes the old
exact-cost couple_trunc_laplace (eager-frame the credits, \"[$Hr $Hε $Hδ]\")
and the non-exact couple_trunc_laplace_cost (route the credits unframed,
\"[$Hr Hε Hδ]\", leaving ↯m (s·ε) / ↯ δ' for ecm_eq/ecm_weaken); the
regime is chosen by the resource pattern.
Tactic Notation "couple_trunc_laplace" uconstr(A) uconstr(s) "with" constr(pat) :=
couple_bind; couple_trunc_laplace_iapply A s pat; couple_trunc_discharge.
couple_bind; couple_trunc_laplace_iapply A s pat; couple_trunc_discharge.
couple_trunc_laplace_apply A s with "[$Hr Hε Hδ]" — the APPLY-ONLY variant,
for INTERLEAVED coupling sites where essential setup happens BETWEEN focusing
the Sample and applying the coupling rule. PRECONDITION: the caller has
ALREADY focused the Sample on both sides (its own couple_bind-equivalent)
and done any interleaved setup; this tactic runs ONLY the unshelve (iApply …)
+ the slimmer couple_trunc_discharge_apply battery, leaving the two credit
goals and the hand-closed residual goals (and postcondition) for the caller.
Tactic Notation "couple_trunc_laplace_apply" uconstr(A) uconstr(s) "with" constr(pat) :=
couple_trunc_laplace_iapply A s pat; couple_trunc_discharge_apply.
Section trunc_laplace_canary.
Context {Sg : Sig} `{!SampleIn trunc_laplace_family Sg} `{!diffprivGS Sg Σ}.
Local Notation fill := (@ectx_language.fill (gen_ectx_lang Sg)).
couple_trunc_laplace_iapply A s pat; couple_trunc_discharge_apply.
Section trunc_laplace_canary.
Context {Sg : Sig} `{!SampleIn trunc_laplace_family Sg} `{!diffprivGS Sg Σ}.
Local Notation fill := (@ectx_language.fill (gen_ectx_lang Sg)).
CANARY: two surface-form TruncLaplace #A #num #den #loc #() draws couple
to EQUAL outputs, at multiplicative cost s·(num/den) and additive cost
tlap_delta A num den loc s — driven entirely by the couple_trunc_laplace
tactic. Stated over the TruncLaplace notation (i.e. expr, with the
un-reduced nested Pair param) so it exercises the surface path a program
takes; the THREE resources (spec ⤇, multiplicative ↯m and additive ↯)
are threaded by the 3-way pattern, demonstrating the δ-carrying convenience
layer end to end. The regime side-conditions 0 ≤ A and 0 ≤ s ≤ A are
passed as hypotheses and closed by the tactic via assumption/lia.
Lemma wp_trunc_laplace_shift_canary (A loc s num den : Z)
(HA : (0 <= A)%Z)
(Hs : (Z.abs s <= A)%Z)
(Hpos : 0 < IZR num / IZR den) K E :
{{{ ⤇ fill K (TruncLaplace #A #num #den #(loc + s) #())
∗ ↯m (IZR (Z.abs s) * (IZR num / IZR den))
∗ ↯ (tlap_delta A num den loc s) }}}
TruncLaplace #A #num #den #loc #() @ E
{{{ (z : Z), RET #z; ⤇ fill K #z }}}.
Proof.
(* destructure the bundled 3-resource precondition first, so the spec ⤇ is a
standalone hypothesis for the tp_pures/tp_bind inside the tactic. *)
iIntros (Φ) "(Hr & Hε & Hδ) HΦ".
couple_trunc_laplace A s with "[$Hr $Hε $Hδ]".
iApply "HΦ".
Qed.
(HA : (0 <= A)%Z)
(Hs : (Z.abs s <= A)%Z)
(Hpos : 0 < IZR num / IZR den) K E :
{{{ ⤇ fill K (TruncLaplace #A #num #den #(loc + s) #())
∗ ↯m (IZR (Z.abs s) * (IZR num / IZR den))
∗ ↯ (tlap_delta A num den loc s) }}}
TruncLaplace #A #num #den #loc #() @ E
{{{ (z : Z), RET #z; ⤇ fill K #z }}}.
Proof.
(* destructure the bundled 3-resource precondition first, so the spec ⤇ is a
standalone hypothesis for the tp_pures/tp_bind inside the tactic. *)
iIntros (Φ) "(Hr & Hε & Hδ) HΦ".
couple_trunc_laplace A s with "[$Hr $Hε $Hδ]".
iApply "HΦ".
Qed.
CANARY for the NON-EXACT cost regime of the MERGED couple_trunc_laplace:
identical statement to the framed canary above, but BOTH credits are ROUTED
(unframed Hε/Hδ, pattern \"[$Hr Hε Hδ]\") into their residual
↯m (s·ε) / ↯ δ' goals rather than $-framed. Here the costs are exact
so the residual goals are closed by simply re-framing Hε/Hδ; a real
cost-reconciliation site would instead run iApply ecm_eq; … /
iApply ecm_weaken; … there. Exercises that the SAME couple_trunc_laplace
tactic, when handed a routed pattern, elaborates, auto-discharges the
regime/equational side-conditions, and leaves the two credit goals clean
(the old separate couple_trunc_laplace_cost is now subsumed).
Lemma wp_trunc_laplace_shift_canary_cost (A loc s num den : Z)
(HA : (0 <= A)%Z)
(Hs : (Z.abs s <= A)%Z)
(Hpos : 0 < IZR num / IZR den) K E :
{{{ ⤇ fill K (TruncLaplace #A #num #den #(loc + s) #())
∗ ↯m (IZR (Z.abs s) * (IZR num / IZR den))
∗ ↯ (tlap_delta A num den loc s) }}}
TruncLaplace #A #num #den #loc #() @ E
{{{ (z : Z), RET #z; ⤇ fill K #z }}}.
Proof.
iIntros (Φ) "(Hr & Hε & Hδ) HΦ".
couple_trunc_laplace A s with "[$Hr Hε Hδ]".
(* residual combined credit goal ↯m (s·ε) ∗ ↯ δ' — closed here by re-framing
the routed Hε/Hδ (exact cost); a non-exact site would iApply ecm_*. *)
2: iApply "HΦ".
iFrame "Hε Hδ".
Qed.
(HA : (0 <= A)%Z)
(Hs : (Z.abs s <= A)%Z)
(Hpos : 0 < IZR num / IZR den) K E :
{{{ ⤇ fill K (TruncLaplace #A #num #den #(loc + s) #())
∗ ↯m (IZR (Z.abs s) * (IZR num / IZR den))
∗ ↯ (tlap_delta A num den loc s) }}}
TruncLaplace #A #num #den #loc #() @ E
{{{ (z : Z), RET #z; ⤇ fill K #z }}}.
Proof.
iIntros (Φ) "(Hr & Hε & Hδ) HΦ".
couple_trunc_laplace A s with "[$Hr Hε Hδ]".
(* residual combined credit goal ↯m (s·ε) ∗ ↯ δ' — closed here by re-framing
the routed Hε/Hδ (exact cost); a non-exact site would iApply ecm_*. *)
2: iApply "HΦ".
iFrame "Hε Hδ".
Qed.
CANARY for the APPLY-ONLY couple_trunc_laplace_apply: identical
statement, but the caller does the wp_bind/tp_bind MANUALLY (mimicking
an interleaved site) before calling the apply-only tactic — exercising
that couple_trunc_laplace_apply does NOT itself re-bind the Sample and
works once the caller has focused both sides. BOTH credits are ROUTED
(unframed Hε/Hδ, pattern \"[$Hr Hε Hδ]\") into their residual goals;
here the costs are exact so the residuals are closed by re-framing.
Lemma wp_trunc_laplace_shift_canary_apply (A loc s num den : Z)
(HA : (0 <= A)%Z)
(Hs : (Z.abs s <= A)%Z)
(Hpos : 0 < IZR num / IZR den) K E :
{{{ ⤇ fill K (TruncLaplace #A #num #den #(loc + s) #())
∗ ↯m (IZR (Z.abs s) * (IZR num / IZR den))
∗ ↯ (tlap_delta A num den loc s) }}}
TruncLaplace #A #num #den #loc #() @ E
{{{ (z : Z), RET #z; ⤇ fill K #z }}}.
Proof.
iIntros (Φ) "(Hr & Hε & Hδ) HΦ".
(* MANUAL bind on both sides — the interleaved-site idiom the apply-only
tactic supports (and where setup could be interposed here) *)
wp_pures. tp_pures.
wp_bind (Sample _ _ _). tp_bind (Sample _ _ _).
couple_trunc_laplace_apply A s with "[$Hr Hε Hδ]".
(* residual combined credit goal ↯m (s·ε) ∗ ↯ δ' — closed here by re-framing
the routed Hε/Hδ (exact cost); a non-exact site would iApply ecm_*. *)
2: iApply "HΦ".
iFrame "Hε Hδ".
Qed.
End trunc_laplace_canary.
(HA : (0 <= A)%Z)
(Hs : (Z.abs s <= A)%Z)
(Hpos : 0 < IZR num / IZR den) K E :
{{{ ⤇ fill K (TruncLaplace #A #num #den #(loc + s) #())
∗ ↯m (IZR (Z.abs s) * (IZR num / IZR den))
∗ ↯ (tlap_delta A num den loc s) }}}
TruncLaplace #A #num #den #loc #() @ E
{{{ (z : Z), RET #z; ⤇ fill K #z }}}.
Proof.
iIntros (Φ) "(Hr & Hε & Hδ) HΦ".
(* MANUAL bind on both sides — the interleaved-site idiom the apply-only
tactic supports (and where setup could be interposed here) *)
wp_pures. tp_pures.
wp_bind (Sample _ _ _). tp_bind (Sample _ _ _).
couple_trunc_laplace_apply A s with "[$Hr Hε Hδ]".
(* residual combined credit goal ↯m (s·ε) ∗ ↯ δ' — closed here by re-framing
the routed Hε/Hδ (exact cost); a non-exact site would iApply ecm_*. *)
2: iApply "HΦ".
iFrame "Hε Hδ".
Qed.
End trunc_laplace_canary.