locus · bigraph tutorial · Chapter III
The π-calculus in binding bigraphs, as it now stands in Lean: names that are sent and received, rules that pass them, and a proof that the reactions of an encoded process are exactly its reductions. Every figure is computed from a Lean term and every result is kernel-checked.
Chapter I, Rooms, Wires and Laws, built bigraphs and said when two are the same. The second, Rules, Reactions and CCS, added reaction rules and encoded CCS, whose communication moves no names. This chapter encodes a calculus in which the name sent is itself a channel, so that a process can learn a link it did not have. The model is Jensen's [Jen06, Ch 7], and the rules are his two.
How this chapter is made. As in the other chapters, no figure is drawn by hand. Each
is computed by lake exe bigraphdraw from a Lean term, read off by
Bg.describe, and the label under it is the Lean source of the term drawn. Every block
of Lean is cut from its source file by name. The examples are in
Pi/Examples.lean, and every reaction stated about them is a theorem
there.
A theory of its own. The π development is the library Pi/. It
imports the generic bigraph modules and the shape layer Bigraph/Shapes/, which
holds what an encoding of processes needs whatever its signature: shapes and their
isomorphisms, binding, the canonical instance, and rules that pass names; the CCS encodings of
Chapter II stand on it too. It imports nothing of the CCS encodings and nothing of
Locus; the π-calculus has its own signature.
π notation. Processes in the text are printed from their Lean terms. The free
channels 0 and 1 are written x and y, in the
text and in the figures. x̄y sends y on x;
x(u) receives a name on x and calls it u. A restriction binds
z, w, … and an input binds u, v, …, each
counted from the outside in. In Lean every bound name is a de Bruijn index.
Numbering is separate for definitions, figures and theorems, and the generator checks every reference and every citation.
The calculus has choice between sends and inputs, parallel composition, restriction and replicated input. It is the calculus of Jensen's sixth chapter [Jen06, §6.1], which his seventh models, with channels written as in Chapter II: a channel is a free name or a de Bruijn index.
Definition 1 (Processes). There are three binders. A restriction
νP binds index 0 of P; so do an input
x(u).P and a replicated input !x(u).P, whose index 0 is
the name received.
mutual inductive Proc where | nil : Proc | par : Proc → Proc → Proc | res : Proc → Proc | sum : Sum → Proc /-- `!x(z).P`: replicated input; `z` is index `0` of `P`. -/ | rep : Chan → Proc → Proc inductive Sum where /-- `x̄y.P` -/ | snd : Chan → Chan → Proc → Sum /-- `x(z).P`: `z` is index `0` of `P`. -/ | rcv : Chan → Proc → Sum | plus : Sum → Sum → Sum end
Definition 2 (Substitution). p.subst k c makes index
k of p the channel c and moves the indices above it down
by one. Under a binder both the index and c move up.
def Chan.subst (k : Nat) (c : Chan) : Chan → Chan | .free n => .free n | .bound i => if i < k then .bound i else if i = k then c else .bound (i - 1)
Definition 3 (Reduction). A send x̄y.P meets an input
x(u).Q on the same channel, in two sums or with a replicated input. The receiver's
body becomes Q with y for u, carried as a field
q' = q.subst 0 y. A replicated input stays for the next sender. Reduction is closed
under parallel composition, restriction and structural congruence, whose scope extrusion
likewise carries the shifted process as a field.
inductive Red : Proc → Proc → Prop where
| comm {x y p m q n q'} (h₁ : InSnd x y p m) (h₂ : InRcv x q n) (hq : q' = q.subst 0 y) :
Red (.par (.sum m) (.sum n)) (.par p q')
| rep {x y p m q q'} (h₁ : InSnd x y p m) (hq : q' = q.subst 0 y) :
Red (.par (.sum m) (.rep x q)) (.par (.par p q') (.rep x q))
| parL {p p'} (q) : Red p p' → Red (.par p q) (.par p' q)
| res {p p'} : Red p p' → Red (.res p) (.res p')
| struct {p p' q' q} : SC p p' → Red p' q' → SC q' q → Red p q
A process becomes a shape, as in Chapter II: a node for each sum, send and input, an edge
for each restriction with its nu node, and names for the free channels. The
signature is the π-calculus's own. A sum alt has no port. A send snd
has two ports, the channel and the name sent. An input get, and its replicated form
rget, has a channel port and a port that binds: it binds the edge of the
name to be received, and the body's uses of that name are linked to the same edge. This is how a
bigraph declares that a name is bound [JM04, §12]. The
binder nu of a restriction has one port, which binds too. Every control is
passive.
inductive Ctl where
/-- A sum. -/
| alt
/-- An output `x̄y`: the channel, and the datum. -/
| snd
/-- An input `x(z)`: the channel, and the datum, which binds. -/
| get
/-- A replicated input `!x(z)`: the channel, and the datum, which binds. -/
| rget
/-- The binder of a restriction (Jensen's `res`). -/
| nu
deriving DecidableEq, Repr
def sig : Sig Ctl where
ar := fun
| .alt => 0
| .nu => 1
| _ => 2
atomic := fun _ => false
active := fun _ _ => false
Definition 4 (An input as a shape). x(u).P is the node
on x holding P, under one more restriction. The restriction's edge is
the received name: the node's binding port and the uses of index 0 in
P reach it. No new way of building shapes is needed. The binding ports are those
the signature's Binder instance marks. The rules use only part of what binding
means: that a bound edge stays in one region when a body is copied. Jensen's full
scope rule, that every point of a bound link lies inside its binder's scope (the input
node's contents, the restriction node's parent), is checked separately and holds of every
agent.
def inLab (c : Ctl) (x : Chan) : Lab sig := .two c (x.shift 0) (.bound 0)
def inShape (c : Ctl) (x : Chan) (s : Shape sig) : Shape sig := (s.node (inLab c x)).res
instance binder : Binder sig where
binds := fun
| .get, i => i.val == 1
| .rget, i => i.val == 1
| .nu, _ => true
| _, _ => false
nu := .nu
nu_ar := rfl
binds_nu := fun _ => rfl
Definition 5 (The shape of a process). Its agent is the shape's
bigraph over the names X, the dangling indices named by the environment
ρ.
mutual
/-- **The shape of a π process.** -/
def shapeπ : Proc → Shape sig
| .nil => .empty
| .par p q => (shapeπ p).par (shapeπ q)
| .res p => ((shapeπ p).par nuS).res
| .sum m => (shapeπS m).node altLab
| .rep x p => inShape .rget x (shapeπ p)
def shapeπS : Sum → Shape sig
| .snd x y p => (shapeπ p).node (.two Ctl.snd x y)
| .rcv x p => inShape .get x (shapeπ p)
| .plus m n => (shapeπS m).par (shapeπS n)
end
def agentπ (X : NameSet Nat) (ρ : Nat → Nat) (p : Proc) (h : NamesIn X.names ρ p) :
Abstract sig Nat :=
Abstract.mk (encπ X ρ p h)
x binds the edge of u, and the send inside it uses that
edge as its channel. Right, !x(u).ūy: the same body under a
replicated input.Theorem 1 (Congruent processes are one agent, and conversely). Every law of
structural congruence is an isomorphism of shapes, including Jensen's exchange of two
restrictions, νxνy P ≡ νyνx P, where the two binder nodes simply trade places.
The law was added after a check showed it is needed: without it
νxνy x̄y and νyνx x̄y are one agent but not
congruent (Pi/Converse.lean). So is substitution: the shape of
P with c for index k is the shape of P with
index k made c (substπ, an isomorphism defined by
recursion on the process).
theorem sc_soundπ {X : NameSet Nat} {ρ : Nat → Nat} {p q : Proc} (h : SC p q)
(hp : NamesIn X.names ρ p) : agentπ X ρ p hp = agentπ X ρ q (Pi.NamesIn.of_sc h hp)
Conversely, for closed processes, one agent means congruent. Equal agents have isomorphic shapes, since no edge of an encoding is idle and no port of a closed process reaches past its binders; and isomorphic shapes are congruent processes, open or closed, so structural congruence is exactly isomorphism of shapes. The proof is an induction on the number of nodes: a binder at the top is brought out to the outermost restriction on both sides and removed, and otherwise a top component is split off on both sides and the parts compared. The exchange law is used there, once; without it the converse fails, as above. Closedness is needed: a port on a bound index and a port on the free name the environment gives that index are linked alike.
theorem sc_iffπ {X : NameSet Nat} {ρ : Nat → Nat} {p q : Proc} (hp : Closed p)
(hq : Closed q) (hpX : NamesIn X.names ρ p) (hqX : NamesIn X.names ρ q) :
agentπ X ρ p hpX = agentπ X ρ q hqX ↔ SC p q
A CCS rule only matches; a π rule must also connect. When x̄y meets
x(u).Q, every use of u in Q must end up linked to
y. Jensen's rules do this with names that belong to a region of the rule
[Jen06, Def 7.12]. In the redex, the received name is an edge
of the rule, bound by the input's port, and the parameter's body reaches it through an inner name
of its own region. In the reactum, the copy of the body has an inner name that the reactum wires to
y. The instance renames the one inner name into the other
[Jen06, Def 5.2].
A first idea was to let the received name pass out through the redex's outer face, for the context to close. It was dropped before any code. A context could then merge that name with another name of the parameter, and the reactum would leave the body's other name unbound. Names local to a region keep the binder out of the context's reach.
Definition 6 (Rules that pass names). A rule shape has edges of its
own, and each of its ports and inner names is linked to an edge or to one of k
slots for outer names. A rule pairs a redex and a reactum over the same slots, with an
instantiation ϱ of the reactum's sites by the redex's and, for each reactum site
j, a renaming η j of inner names.
structure RShape (S : Sig C) (m k l : Nat) where
V : Type
finV : Finite V
ctl : V → C
up : V → Option V
depth : V → Nat
up_depth : ∀ {v u}, up v = some u → depth u < depth v
bound : Nat
depth_lt : ∀ v, depth v < bound
site : Fin m → Option V
E : Type
finE : Finite E
port : (v : V) → Fin (S.ar (ctl v)) → E ⊕ Fin k
inner : Fin l → E ⊕ Fin k
structure PRuleN (S : Sig C) where
{m m' k l l' : Nat}
R : RShape S m k l
R' : RShape S m' k l'
ϱ : Fin m' → Fin m
η : Fin m' → Fin l → Fin l'
def renN {l l' m' : Nat} (zn : Fin l → Nat) (zn' : Fin l' → Nat) (η : Fin m' → Fin l → Fin l')
(j : Fin m') (n : Nat) : Nat :=
match slotOf zn n with
| some s => zn' (η j s)
| none => n
Definition 7 (Their ground rules). For every choice of names for the slots, inner names apart from the global names, and every binding-discrete parameter, the redex is the rule beside the identity on the parameter's global names, composed with the parameter. The reactum composes the reactum rule with the instance that copies and renames.
def PRuleN.rules (ob : OutBind S) (Ps : PRuleN S → Prop) : Rules S Nat :=
fun g => ∃ (P : PRuleN S) (nm : Fin P.k → Nat) (zn : Fin P.l → Nat) (hz : ∀ s t, zn s = zn t → s = t)
(zn' : Fin P.l' → Nat) (hz' : ∀ s t, zn' s = zn' t → s = t) (X : NameSet Nat)
(hX : ∀ s, zn s ∉ X.names) (hX' : ∀ s, zn' s ∉ X.names)
(d : Bg S Iface.origin ⟨P.m, NameSet.union (innN zn hz) X⟩) (hb : BDiscrete ob d),
Ps P ∧ g = P.groundN ob nm zn hz zn' hz' X hX hX' d hb
z. In the reactum, the body's copy has the inner name w, wired to
y.w and wired to y; the body inside the replicated
input keeps z, on the input's edge.Definition 8 (The two rules). Communication and replicated communication, after Jensen [Jen06, Def 7.12] and [Jen06, Def 7.19]. In the second, the body's site is instantiated twice, once as the copy and once inside the replicated input, and only the copy's inner name is renamed.
def commRule : PRuleN sig where R := commRedex R' := commReactum ϱ := ssInst η := fun _ s => s def repRule : PRuleN sig where R := repRedex R' := repReactum ϱ := repInst η := fun j _ => if j.val = 1 then 0 else 1 def piRules : Rules sig Nat := PRuleN.rules ob (fun P => P = commRule ∨ P = repRule)
Theorem 2 (Soundness). Every reduction of every process is a reaction of the encodings under the two rules, for every name set and environment.
theorem red_soundπ {p q : Proc} (h : Red p q) : ∀ (X : NameSet Nat) (ρ : Nat → Nat),
(hp : NamesIn X.names ρ p) →
React piRules (agentπ X ρ p hp) (agentπ X ρ q (NamesIn.of_red h hp))
As in Chapter II, the parameter keeps its edges, so a component whose dangling indices are named by the environment is the rule plugged with its parts. Substitution meets the reactum in one lemma: a received body, copied and its local name wired to the datum, is the body with the datum substituted.
theorem plugRef_ren_subst {m k l : Nat} (R : RShape sig m k l) (nm : Fin k → Nat) (zn : Fin l → Nat)
(hzn : ∀ s t, zn s = zn t → s = t) {X : NameSet Nat} (hX : ∀ s, zn s ∉ X.names)
(z : Nat) (g : Nat → Nat) (hg : ∀ n ∈ X.names, g n = n) (s₀ : Fin l) (hgz : g z = zn s₀)
(t : Fin k) (hin : R.inner s₀ = .inr t) (y : Chan) (ρ : Nat → Nat) (hy : nm t = y.name ρ)
{E F : Type} (f : E → F) (r : Ref E) (hr : ∀ n ∈ r.fv ρ 1, n ∈ X.names) :
R.plugRef nm zn (((r.cls (consEnv z ρ)).mapE f).ren g) =
(((r.subst 0 y).cls ρ).mapE f).mapE Sum.inl
The converse is proved for closed processes, as for CCS: a dangling index is named by the environment, so an open process could react on a link it does not own.
Definition 9 (Closed processes). A process is closed when its free names do not depend on the environment, so that no index escapes its binders.
def Closed (p : Proc) : Prop := ∀ (ρ ρ' : Nat → Nat) (n : Nat), n ∈ p.fn ρ 0 → n ∈ p.fn ρ' 0
Theorem 3 (Occurrence determinacy for rules that pass names). Two instances of a rule in context, equivalent by a map that fixes the rule's nodes, have equivalent reactums. The contexts are any, the parameters any binding-discrete ones. Three properties of the redex are assumed, and both π redexes have them: every edge of the rule is reached by a port of the rule, so the map fixes it; every inner name reaches an edge of the rule, different ones different edges, so a parameter port on an inner name goes to one on the same inner name; and every slot is reached by a port of the rule, so the contexts' links of a slot correspond.
structure OccN {m k l : Nat} (R : RShape S m k l) (l' : Nat) where
nm : Fin k → Nat
zn : Fin l → Nat
hz : ∀ s t, zn s = zn t → s = t
zn' : Fin l' → Nat
hz' : ∀ s t, zn' s = zn' t → s = t
X : NameSet Nat
hX : ∀ s, zn s ∉ X.names
hX' : ∀ s, zn' s ∉ X.names
d : Bg S Iface.origin ⟨m, NameSet.union (innN zn hz) X⟩
hb : BDiscrete ob d
K : Iface Nat
D : Bg S (nFace nm X) K
noncomputable def occEquivN :
SupportEquivI ι κ (o.Gy R' ϱ η).lean (o₀.Gy R' ϱ η).lean
Theorem 4 (The correspondence). For every closed process
p, the reactions of the agent of p are exactly the agents of the
reducts of p, over all active contexts and all binding-discrete parameters.
theorem react_iffπ (X : NameSet Nat) (ρ : Nat → Nat) {p : Proc} (hcl : Closed p)
(hp : NamesIn X.names ρ p) (b : Abstract sig Nat) :
React piRules (agentπ X ρ p hp) b ↔
∃ q, ∃ hr : Red p q, b = agentπ X ρ q (NamesIn.of_red hr hp) :=
⟨react_completeπ X ρ hcl hp, fun ⟨_, hr, hb⟩ => hb ▸ red_soundπ hr X ρ hp⟩
The rule's nodes are read back as positions of the process. The process is rearranged around them up to structural congruence: the restrictions at the top to the front, the two matched components split out, the send and the input split out of their sums. The π reduction is then read off. Only the two channels are read back from the agent, and they are equal because the rule links both to one slot. The received name is never read back: it is the rule's own edge, and the reactum wires it to the datum, which is the send's. Occurrence determinacy (Theorem 3) then carries the given reaction to the canonical instance of that reduction, whose reactum is the reduct. Jensen states his correspondence with the proof sketched as following the earlier ones [Jen06, Thm 7.21]; this is a kernel-checked version, for closed processes.
Jensen's own example of why restriction needs a place
[Jen06, p. 113] is a pair of processes that differ only in
where νz stands: x̄y | (νz !x(u).(z̄u | z(v))) and
x̄y | !x(u).νz (z̄u | z(v)). In the first, every copy of the replicated body
uses the one z. In the second, every copy gets its own.
nu node sits in the region, beside the replicated input. Right,
x̄y | !x(u).νz (z̄u | z(v)): it sits inside the replicated input's
body.Theorem 5 (Different agents). The two agents differ: the first has a binder in a region, the second has none.
theorem jensen_agents_ne : agentπ X01 ρ01 pShared namesShared ≠ agentπ X01 ρ01 pPrivate namesPrivate
νz, after scope extrusion, and shares its edge with the body that
stays.nu node and its
own edge, apart from those inside the replicated input.Theorem 6 (Each reacts to its reduct). By Theorem 4, these are also the only reactions of the two agents, since both processes are closed.
theorem jensen_react :
React piRules (agentπ X01 ρ01 pShared namesShared)
(agentπ X01 ρ01 qShared (NamesIn.of_red red_shared namesShared)) ∧
React piRules (agentπ X01 ρ01 pPrivate namesPrivate)
(agentπ X01 ρ01 qPrivate (NamesIn.of_red red_private namesPrivate))
Jensen's printed reduct of the second process puts νz outside the replicated
input [Jen06, p. 113]. His rule keeps it inside, and so
does 0 | (νz (z̄y | z(u))) | !x(u).νz (z̄u | z(v)) here.
In (νz x̄z.z̄y) | x(u).u(v) the sender owns a private z and sends
it along x. The receiver will listen on whatever it receives. After the first
reaction, νz (z̄y | z(u)), the receiver's input is on z, and
the scope of z holds both. The second reaction, on z itself, leaves
νz (0 | 0). Before the first reaction no input is on
z: the link was made by passing the name.
z joins the send and the input that the first reaction released.Theorem 7 (Mobility). The two reductions, and the two reactions of the agents.
theorem red_mob₁ : Red pMob qMob
theorem red_mob₂ : Red qMob rMob
theorem mob_react :
React piRules (agentπ X01 ρ01 pMob namesMob)
(agentπ X01 ρ01 qMob (NamesIn.of_red red_mob₁ namesMob)) ∧
React piRules (agentπ X01 ρ01 qMob (NamesIn.of_red red_mob₁ namesMob))
(agentπ X01 ρ01 rMob (NamesIn.of_red red_mob₂ (NamesIn.of_red red_mob₁ namesMob)))
PROVED means sorry-free in Lean, with the axioms checked: every result below rests on
propext and Quot.sound only. The gate LocusGate/Pi.lean
fixes the correspondence, Jensen's pair, the mobility example, the scope rule and the converse
of Theorem 1. It was watched failing on two wrong statements before it was kept, the scope rule
with two wrong binding signatures, and the converse with the exchange law taken out.
| What | Where | Status |
|---|---|---|
| The calculus: substitution, structural congruence, reduction; reduction keeps names | Pi/Calculus | PROVED |
| Rules that pass names, their ground rules, the instance that copies and renames | Shapes/NameRules | PROVED |
| The encoding; shifting and substitution are isomorphisms; congruent processes are one agent (Theorem 1) | Pi/Sig, Pi/Enc | PROVED |
| Soundness for every process (Theorem 2) | Pi/Sound | PROVED |
| Occurrence determinacy for rules that pass names (Theorem 3); the canonical instance | Shapes/NameComplete, Shapes/NameCanon | PROVED |
| The correspondence on every closed process (Theorem 4) | Pi/Canon, Pi/Iff | PROVED |
| Jensen's pair told apart, each reacting to its reduct (Theorems 5 and 6); mobility (Theorem 7) | Pi/Examples, LocusGate/Pi | PROVED |
| Every agent obeys Jensen's scope rule [Jen06, Def 4.20]: every point of a bound link lies inside its binder's scope | Bigraph/Scope, Pi/Scope, LocusGate/Pi | PROVED |
| Without the exchange law, equal agents need not be congruent: the converse of Theorem 1 fails for the congruence first used | Pi/Converse | REFUTED |
| With the exchange law, equal agents of closed processes are congruent (the converse of Theorem 1); structural congruence is isomorphism of shapes, open processes included | Shapes/Decomp, Pi/Complete, LocusGate/Pi | PROVED |
| Completeness for open processes | — | OPEN |
| Labelled transitions and bisimulation for the π encoding | — | OPEN |
The quotations are checked against transcriptions of the sources in
docs/sources/.