clutch.gen_diffpriv.lib.laplace_tapes

Tape-based discrete-Laplace library for the generic DP logic.
This adds the *presampling* (tape) interface for the Laplace family on top of lib.laplace:
As with lib.laplace, a client only provides Definition Slap + SampleIn laplace_family Slap and works in a diffprivGS Slap-development.
From iris.proofmode Require Import proofmode.
From clutch.prob Require Import differential_privacy distribution couplings couplings_app couplings_dp.
From clutch.gen_diffpriv.lib Require Export laplace.
From clutch.gen_prob_lang.spec Require Import spec_ra.
From iris.prelude Require Import options.

Local Open Scope R.

AllocTapeLaplace num den mean allocates a fresh presampling tape for the Laplace family at parameter (num, den, mean). The family's index is read off the ambient SampleIn laplace_family _ instance.
Notation AllocTapeLaplace num den mean :=
  (AllocSampleTape (sample_idx (D := laplace_family)) (Pair num (Pair den mean)))
  (only parsing).

Typed tape views: a Laplace tape holding presampled outcomes zs : list Z, on the impl (↪L) and spec (↪Lₛ) tape maps respectively. Both are sugar over the generic tape predicate at index sample_idx. The parameter is written in the *explicit* value form PairV #num (PairV #den #mean) — i.e. exactly the shape the proof-mode tactics read off a reduced Sample redex — so wp_alloc_sample_tape/wp_sample_tape/tp_* match it syntactically. (This form is definitionally sf_param_to_val laplace_family (num,den,mean); the bridge is laplace_param_to_val below, used inside the coupling rule.)
Notation "α ↪L ( num , den , mean ; zs )" :=
  ((α ↪ (sample_idx (D := laplace_family),
           PairV (LitV (LitInt num)) (PairV (LitV (LitInt den)) (LitV (LitInt mean))),
           ((λ z : Z, LitV (LitInt z)) <$> zs)))%I)
  (at level 20, format "α ↪L ( num , den , mean ; zs )") : bi_scope.

Notation "α ↪Lₛ ( num , den , mean ; zs )" :=
  ((α ↪ₛ (sample_idx (D := laplace_family),
            PairV (LitV (LitInt num)) (PairV (LitV (LitInt den)) (LitV (LitInt mean))),
            ((λ z : Z, LitV (LitInt z)) <$> zs)))%I)
  (at level 20, format "α ↪Lₛ ( num , den , mean ; zs )") : bi_scope.

The two representations of a Laplace parameter / outcome coincide definitionally; these reflexivity lemmas let us rewrite between the family-level form (produced by wp_couple_tapes_family) and the explicit value form (used by the tape views and proof-mode tactics).
Lemma laplace_param_to_val (num den mean : Z) :
  sf_param_to_val laplace_family (num, den, mean)
  = PairV (LitV (LitInt num)) (PairV (LitV (LitInt den)) (LitV (LitInt mean))).
Proof. reflexivity. Qed.

Lemma laplace_sf_inj (z : Z) : sf_inj laplace_family z = LitV (LitInt z).
Proof. reflexivity. Qed.

Section laplace_tapes.
  Context {S : Sig} `{!SampleIn laplace_family S} `{!diffprivGS S Σ}.
  Local Notation fill := (@ectx_language.fill (gen_ectx_lang S)).
  Local Notation lidx := (@sample_idx laplace_family S _).

Allocate a fresh Laplace tape (empty). The Pair parameter reduces by wp_pures to a value, then the generic allocator fires.
  Lemma wp_alloc_tape_laplace (num den mean : Z) E s :
    {{{ True }}}
      AllocTapeLaplace #num #den #mean @ s; E
    {{{ (α : loc), RET (LitV (LitLbl α)); α ↪L (num, den, mean; []) }}}.
  Proof.
    iIntros (Φ) "_ HΦ".
    wp_pures.
    iApply (wp_alloc_sample_tape lidx
              (PairV (LitV (LitInt num)) (PairV (LitV (LitInt den)) (LitV (LitInt mean))))
              with "[//]").
    iIntros "!>" (α) "Hα". iApply "HΦ". iFrame.
  Qed.

Read the head presampled outcome off a non-empty Laplace tape (deterministic). The Pair parameter reduces by wp_pures, then the generic wp_sample_tape fires.
  Lemma wp_laplace_tape (num den mean z : Z) (zs : list Z) (α : loc) E s :
    {{{ ▷ α ↪L (num, den, mean; z :: zs) }}}
      Laplace #num #den #mean (#lbl:α) @ s; E
    {{{ RET #z; α ↪L (num, den, mean; zs) }}}.
  Proof.
    iIntros (Φ) ">Hα HΦ".
    wp_pures.
    iApply (wp_sample_tape lidx
              (PairV (LitV (LitInt num)) (PairV (LitV (LitInt den)) (LitV (LitInt mean))))
              (LitV (LitInt z)) ((λ z0 : Z, LitV (LitInt z0)) <$> zs) α with "[Hα]").
    { iFrame. }
    iIntros "!> Hα". iApply "HΦ". iFrame.
  Qed.

The *reduced-form* (value-parameter) Laplace tape read. This is the same deterministic read as wp_laplace_tape, but stated on the Sample redex with its parameter ALREADY reduced to a value PairV … and the tape label as a value Val #lbl:α — i.e. exactly the goal left behind after wp_pures has reduced the surface Laplace #num #den #mean (#lbl:α) notation. The surface rule no longer wp_apply-matches that reduced goal (the parameter is a PairV, not a Pair); this rule does. No wp_pures is needed in the proof: the generic wp_sample_tape fires directly. This mirrors the surface-vs-reduced split between hoare_couple_laplace (surface) and wp_couple_laplace (reduced) in lib.laplace.
  Lemma wp_laplace_tape_val (num den mean z : Z) (zs : list Z) (α : loc) E s :
    {{{ ▷ α ↪L (num, den, mean; z :: zs) }}}
      Sample lidx (Val (PairV (LitV (LitInt num))
                          (PairV (LitV (LitInt den)) (LitV (LitInt mean)))))
             (Val (LitV (LitLbl α))) @ s; E
    {{{ RET #z; α ↪L (num, den, mean; zs) }}}.
  Proof.
    iIntros (Φ) ">Hα HΦ".
    iApply (wp_sample_tape lidx
              (PairV (LitV (LitInt num)) (PairV (LitV (LitInt den)) (LitV (LitInt mean))))
              (LitV (LitInt z)) ((λ z0 : Z, LitV (LitInt z0)) <$> zs) α with "[Hα]").
    { iFrame. }
    iIntros "!> Hα". iApply "HΦ". iFrame.
  Qed.

The presampling (tape) Laplace coupling: advancing two Laplace tapes with a shared draw shifts the spec draw by k at multiplicative error |k'|·ε, provided the two means differ by at most k' after the shift. This is the generic wp_couple_tapes_family instantiated with the single Laplace draw coupling DPcoupl_laplace_draw — at the index sample_idx, so one rule serves every signature containing laplace_family.
  Lemma wp_couple_tapes_laplace (mean mean' k k' : Z) α α' zs zs' e Φ
    (Hdist : (Z.abs (k + mean - mean') <= k')%Z)
    (num den : Z) (ε ε' : R) E :
    IZR num / IZR den = ε →
    0 < IZR num / IZR den →
    ε' = (IZR k' * ε) →
    ▷ α ↪L (num, den, mean; zs) ∗ ▷ α' ↪Lₛ (num, den, mean'; zs') ∗ ↯m ε' -∗
    (∀ z, α ↪L (num, den, mean; zs ++ [z]) ∗ α' ↪Lₛ (num, den, mean'; zs' ++ [(z + k)%Z]) -∗
          WP e @ E {{ Φ }}) -∗
    WP e @ E {{ Φ }}.
  Proof.
    iIntros (Hε εpos Hε') "(Hα & Hα' & Hcred) HΦ".
    assert (0 <= ε')%R as Hε'pos.
    { subst ε'. apply Rmult_le_pos; [apply IZR_le; lia | lra]. }
    assert (DPcoupl (sf_sample laplace_family (num, den, mean))
                    (sf_sample laplace_family (num, den, mean'))
                    (λ z z' : Z, (z + k = z')%Z) ε' 0) as Hcpl.
    { rewrite Hε' -Hε. by apply (DPcoupl_laplace_draw num den mean mean' k k' Hdist εpos). }
    iApply (wp_couple_tapes_family S laplace_family (num, den, mean) (num, den, mean')
              α α' ((λ z : Z, LitV (LitInt z)) <$> zs) ((λ z : Z, LitV (LitInt z)) <$> zs')
              (λ z z', (z + k = z')%Z) e Φ ε' E Hε'pos Hcpl with "[Hα Hα' Hcred]").
    { rewrite !laplace_param_to_val. iFrame. }
    iIntros (a a' Ha) "(Hα & Hα')".
    iEval (rewrite laplace_param_to_val) in "Hα".
    iEval (rewrite laplace_param_to_val) in "Hα'".
    iApply ("HΦ" $! a). rewrite Ha !fmap_app. iFrame.
  Qed.

End laplace_tapes.