geolog-zeta-fork/examples/geolog/petri_net_showcase.geolog

346 lines
12 KiB
Plaintext
Raw Permalink Normal View History

2026-02-26 11:50:51 +01:00
// Petri Net Reachability - Full Type-Theoretic Encoding
//
// This showcase demonstrates geolog's core capabilities through a
// non-trivial domain: encoding Petri net reachability as dependent types.
//
// A solution to a reachability problem is NOT a yes/no boolean but a
// CONSTRUCTIVE WITNESS: a diagrammatic proof that tokens can flow from
// initial to target markings via a sequence of transition firings.
//
// Key concepts demonstrated:
// - Parameterized theories (Marking depends on PetriNet instance)
// - Nested instance types (ReachabilityProblem contains Marking instances)
// - Sort-parameterized theories (Iso takes two sorts as parameters)
// - Cross-instance references (solution's trace elements reference problem's tokens)
//
// Original design: loose_thoughts/2025-12-12_12:10_VanillaPetriNetRechability.md
// ============================================================
// THEORY: PetriNet
// Places, transitions, and arcs with proper arc semantics
// ============================================================
theory PetriNet {
P : Sort; // Places
T : Sort; // Transitions
in : Sort; // Input arcs (place -> transition)
out : Sort; // Output arcs (transition -> place)
in/src : in -> P; // Input arc source place
in/tgt : in -> T; // Input arc target transition
out/src : out -> T; // Output arc source transition
out/tgt : out -> P; // Output arc target place
}
// ============================================================
// THEORY: Marking (parameterized by N : PetriNet)
// A marking assigns tokens to places
// ============================================================
theory (N : PetriNet instance) Marking {
token : Sort;
token/of : token -> N/P;
}
// ============================================================
// THEORY: ReachabilityProblem (parameterized by N : PetriNet)
// Initial and target markings as nested instances
// ============================================================
theory (N : PetriNet instance) ReachabilityProblem {
initial_marking : N Marking instance;
target_marking : N Marking instance;
}
// ============================================================
// THEORY: Trace (parameterized by N : PetriNet)
// A trace records transition firings and token flow via wires
// ============================================================
//
// A trace is a diagrammatic proof of reachability:
// - Firings represent transition occurrences
// - Wires connect output arcs of firings to input arcs of other firings
// - Terminals connect to the initial/target markings
//
// The completeness axiom (ax/must_be_fed) ensures every input arc
// of every firing is accounted for - either wired from another firing
// or fed by an input terminal.
theory (N : PetriNet instance) Trace {
// Firings
F : Sort;
F/of : F -> N/T;
// Wires connect output arcs of firings to input arcs of other firings
W : Sort;
W/src_firing : W -> F;
W/src_arc : W -> N/out;
W/tgt_firing : W -> F;
W/tgt_arc : W -> N/in;
// Wire coherence: source arc must belong to source firing's transition
ax/wire_src_coherent : forall w : W.
|- w W/src_arc N/out/src = w W/src_firing F/of;
// Wire coherence: target arc must belong to target firing's transition
ax/wire_tgt_coherent : forall w : W.
|- w W/tgt_arc N/in/tgt = w W/tgt_firing F/of;
// Wire place coherence: wire connects matching places
ax/wire_place_coherent : forall w : W.
|- w W/src_arc N/out/tgt = w W/tgt_arc N/in/src;
// Terminals
input_terminal : Sort;
output_terminal : Sort;
input_terminal/of : input_terminal -> N/P;
output_terminal/of : output_terminal -> N/P;
// Terminals connect to specific firings and arcs
input_terminal/tgt_firing : input_terminal -> F;
input_terminal/tgt_arc : input_terminal -> N/in;
output_terminal/src_firing : output_terminal -> F;
output_terminal/src_arc : output_terminal -> N/out;
// Terminal coherence axioms
ax/input_terminal_coherent : forall i : input_terminal.
|- i input_terminal/tgt_arc N/in/tgt = i input_terminal/tgt_firing F/of;
ax/output_terminal_coherent : forall o : output_terminal.
|- o output_terminal/src_arc N/out/src = o output_terminal/src_firing F/of;
// Terminal place coherence
ax/input_terminal_place : forall i : input_terminal.
|- i input_terminal/of = i input_terminal/tgt_arc N/in/src;
ax/output_terminal_place : forall o : output_terminal.
|- o output_terminal/of = o output_terminal/src_arc N/out/tgt;
// COMPLETENESS: Every arc of every firing must be accounted for.
// Input completeness: every input arc must be fed by a wire or input terminal
ax/input_complete : forall f : F, arc : N/in.
arc N/in/tgt = f F/of |-
(exists w : W. w W/tgt_firing = f, w W/tgt_arc = arc) \/
(exists i : input_terminal. i input_terminal/tgt_firing = f, i input_terminal/tgt_arc = arc);
// Output completeness: every output arc must be captured by a wire or output terminal
ax/output_complete : forall f : F, arc : N/out.
arc N/out/src = f F/of |-
(exists w : W. w W/src_firing = f, w W/src_arc = arc) \/
(exists o : output_terminal. o output_terminal/src_firing = f, o output_terminal/src_arc = arc);
}
// ============================================================
// THEORY: Iso (parameterized by two sorts)
// Isomorphism (bijection) between two sorts
// ============================================================
theory (X : Sort) (Y : Sort) Iso {
fwd : X -> Y;
bwd : Y -> X;
// Roundtrip axioms ensure this is a true bijection
fb : forall x : X. |- x fwd bwd = x;
bf : forall y : Y. |- y bwd fwd = y;
}
// ============================================================
// THEORY: Solution (parameterized by N and RP)
// A constructive witness that target is reachable from initial
// ============================================================
theory (N : PetriNet instance) (RP : N ReachabilityProblem instance) Solution {
trace : N Trace instance;
// Bijection: input terminals <-> initial marking tokens
initial_iso : (trace/input_terminal) (RP/initial_marking/token) Iso instance;
// Bijection: output terminals <-> target marking tokens
target_iso : (trace/output_terminal) (RP/target_marking/token) Iso instance;
// Commutativity axioms (currently unchecked):
// ax/init_comm : forall i : trace/input_terminal.
// |- i trace/input_terminal/of = i initial_iso/fwd RP/initial_marking/token/of;
// ax/target_comm : forall o : trace/output_terminal.
// |- o trace/output_terminal/of = o target_iso/fwd RP/target_marking/token/of;
}
// ============================================================
// INSTANCE: ExampleNet
//
// A Petri net with places A, B, C and transitions:
// ab: consumes 1 token from A, produces 1 token in B
// ba: consumes 1 token from B, produces 1 token in A
// abc: consumes 1 token from A AND 1 from B, produces 1 token in C
//
// +---[ba]----+
// v |
// (A) --[ab]->(B) --+
// | |
// +----[abc]-------+--> (C)
//
// The abc transition is interesting: it requires BOTH an A-token
// and a B-token to fire, producing a C-token.
// ============================================================
instance ExampleNet : PetriNet = {
A : P; B : P; C : P;
ab : T; ba : T; abc : T;
// A -> B (via ab)
ab_in : in; ab_in in/src = A; ab_in in/tgt = ab;
ab_out : out; ab_out out/src = ab; ab_out out/tgt = B;
// B -> A (via ba)
ba_in : in; ba_in in/src = B; ba_in in/tgt = ba;
ba_out : out; ba_out out/src = ba; ba_out out/tgt = A;
// A + B -> C (via abc) - note: two input arcs!
abc_in1 : in; abc_in1 in/src = A; abc_in1 in/tgt = abc;
abc_in2 : in; abc_in2 in/src = B; abc_in2 in/tgt = abc;
abc_out : out; abc_out out/src = abc; abc_out out/tgt = C;
}
// ============================================================
// PROBLEM 0: Can we reach B from A with one token?
// Initial: 1 token in A
// Target: 1 token in B
// ============================================================
instance problem0 : ExampleNet ReachabilityProblem = {
initial_marking = {
tok : token;
tok token/of = ExampleNet/A;
};
target_marking = {
tok : token;
tok token/of = ExampleNet/B;
};
}
// ============================================================
// SOLUTION 0: Yes! Fire transition 'ab' once.
//
// This Solution instance is a CONSTRUCTIVE PROOF:
// - The trace contains one firing (f1) of transition 'ab'
// - The input terminal feeds the A-token into f1's input arc
// - The output terminal captures f1's B-token output
// - The isomorphisms prove the token counts match exactly
// ============================================================
instance solution0 : ExampleNet problem0 Solution = {
trace = {
// One firing of transition 'ab'
f1 : F;
f1 F/of = ExampleNet/ab;
// Input terminal: feeds the initial A-token into f1
it : input_terminal;
it input_terminal/of = ExampleNet/A;
it input_terminal/tgt_firing = f1;
it input_terminal/tgt_arc = ExampleNet/ab_in;
// Output terminal: captures f1's B-token output
ot : output_terminal;
ot output_terminal/of = ExampleNet/B;
ot output_terminal/src_firing = f1;
ot output_terminal/src_arc = ExampleNet/ab_out;
};
initial_iso = {
trace/it fwd = problem0/initial_marking/tok;
problem0/initial_marking/tok bwd = trace/it;
};
target_iso = {
trace/ot fwd = problem0/target_marking/tok;
problem0/target_marking/tok bwd = trace/ot;
};
}
// ============================================================
// PROBLEM 2: Can we reach C from two A-tokens?
// Initial: 2 tokens in A
// Target: 1 token in C
//
// This is interesting because the only path to C is via 'abc',
// which requires tokens in BOTH A and B simultaneously.
// ============================================================
instance problem2 : ExampleNet ReachabilityProblem = {
initial_marking = {
t1 : token; t1 token/of = ExampleNet/A;
t2 : token; t2 token/of = ExampleNet/A;
};
target_marking = {
t : token;
t token/of = ExampleNet/C;
};
}
// ============================================================
// SOLUTION 2: Yes! Fire 'ab' then 'abc'.
//
// Token flow diagram:
//
// [it1]--A-->[f1: ab]--B--wire-->[f2: abc]--C-->[ot]
// [it2]--A-----------------^
//
// Step 1: Fire 'ab' to move one token A -> B
// - it1 feeds A-token into f1 via ab_in
// - f1 produces B-token via ab_out
// Step 2: Fire 'abc' consuming one A-token and one B-token
// - it2 feeds A-token into f2 via abc_in1
// - Wire connects f1's ab_out to f2's abc_in2 (the B-input)
// - f2 produces C-token via abc_out
// ============================================================
instance solution2 : ExampleNet problem2 Solution = {
trace = {
// Two firings
f1 : F; f1 F/of = ExampleNet/ab; // First: A -> B
f2 : F; f2 F/of = ExampleNet/abc; // Second: A + B -> C
// Wire connecting f1's B-output to f2's B-input
// This is the crucial connection that makes the trace valid!
w1 : W;
w1 W/src_firing = f1;
w1 W/src_arc = ExampleNet/ab_out;
w1 W/tgt_firing = f2;
w1 W/tgt_arc = ExampleNet/abc_in2;
// Input terminal 1: feeds first A-token into f1
it1 : input_terminal;
it1 input_terminal/of = ExampleNet/A;
it1 input_terminal/tgt_firing = f1;
it1 input_terminal/tgt_arc = ExampleNet/ab_in;
// Input terminal 2: feeds second A-token into f2
it2 : input_terminal;
it2 input_terminal/of = ExampleNet/A;
it2 input_terminal/tgt_firing = f2;
it2 input_terminal/tgt_arc = ExampleNet/abc_in1;
// Output terminal: captures f2's C-token output
ot : output_terminal;
ot output_terminal/of = ExampleNet/C;
ot output_terminal/src_firing = f2;
ot output_terminal/src_arc = ExampleNet/abc_out;
};
// Bijection: 2 input terminals <-> 2 initial tokens
initial_iso = {
trace/it1 fwd = problem2/initial_marking/t1;
trace/it2 fwd = problem2/initial_marking/t2;
problem2/initial_marking/t1 bwd = trace/it1;
problem2/initial_marking/t2 bwd = trace/it2;
};
// Bijection: 1 output terminal <-> 1 target token
target_iso = {
trace/ot fwd = problem2/target_marking/t;
problem2/target_marking/t bwd = trace/ot;
};
}