Skip to content
12 changes: 9 additions & 3 deletions Cslib.lean
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public import Cslib.Foundations.Data.OmegaSequence.InfOcc
public import Cslib.Foundations.Data.OmegaSequence.Init
public import Cslib.Foundations.Data.OmegaSequence.Temporal
public import Cslib.Foundations.Data.OmegaSequence.Topology
public import Cslib.Foundations.Data.PFunctor.Basic
public import Cslib.Foundations.Data.PFunctor.Free
public import Cslib.Foundations.Data.RelatesInSteps
public import Cslib.Foundations.Data.Set.Saturation
Expand All @@ -117,6 +118,8 @@ public import Cslib.Foundations.Semantics.FLTS.Basic
public import Cslib.Foundations.Semantics.FLTS.FLTSToLTS
public import Cslib.Foundations.Semantics.FLTS.LTSToFLTS
public import Cslib.Foundations.Semantics.FLTS.Prod
public import Cslib.Foundations.Semantics.Frame.Basic
public import Cslib.Foundations.Semantics.Frame.LTS
public import Cslib.Foundations.Semantics.LTS.Basic
public import Cslib.Foundations.Semantics.LTS.Bisimulation
public import Cslib.Foundations.Semantics.LTS.Divergence
Expand Down Expand Up @@ -184,18 +187,21 @@ public import Cslib.Languages.Mech.LocalComputation
public import Cslib.Languages.StatefulProcesses.Basic
public import Cslib.Languages.StatefulProcesses.Network
public import Cslib.Logics.HML.Basic
public import Cslib.Logics.HML.LogicalEquivalence
public import Cslib.Logics.LinearLogic.CLL.Basic
public import Cslib.Logics.LinearLogic.CLL.CutElimination
public import Cslib.Logics.LinearLogic.CLL.EtaExpansion
public import Cslib.Logics.LinearLogic.CLL.MLL
public import Cslib.Logics.LinearLogic.CLL.PhaseSemantics.Basic
public import Cslib.Logics.Modal.Basic
public import Cslib.Logics.Modal.Cube
public import Cslib.Logics.Modal.Denotation
public import Cslib.Logics.Modal.Lean.Basic
public import Cslib.Logics.Modal.Lean.SMul
public import Cslib.Logics.Modal.LogicalEquivalence
public import Cslib.Logics.Modal.Semantics
public import Cslib.Logics.Modal.Unary.Basic
public import Cslib.Logics.Modal.Unimodal.Basic
public import Cslib.Logics.Modal.Unimodal.Cube
public import Cslib.Logics.Modal.Unimodal.Lean.Basic
public import Cslib.Logics.Modal.Unimodal.Lean.SMul
public import Cslib.Logics.Propositional.Defs
public import Cslib.Logics.Propositional.NaturalDeduction.Basic
public import Cslib.Logics.Propositional.NaturalDeduction.Theory
Expand Down
59 changes: 59 additions & 0 deletions Cslib/Foundations/Data/PFunctor/Basic.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/-
Copyright (c) 2026 Fabrizio Montesi. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Fabrizio Montesi
-/

module

public import Cslib.Init
public import Mathlib.Data.PFunctor.Univariate.Basic

/-! # Additional basic theory on polynomial functors -/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR seems a bit large. Maybe this API for PFunctors could be a separate PR. It seems fairly straightforward merge.


@[expose] public section

namespace PFunctor

/-- The constant child map for `a`. -/
def const {P : PFunctor} (a : P.A) (x : α) : P.B a → α := fun _ => x

@[simp, scoped grind =]
theorem const_apply {P : PFunctor} (a : P.A) (x : α) (i : P.B a) : PFunctor.const a x i = x := rfl

section Unary

/-- A polynomial functor is unary if all child types have exactly one element. -/
class Unary (P : PFunctor) where
unary (a : P.A) : Unique (P.B a)

attribute [instance_reducible, instance] PFunctor.Unary.unary

theorem Unary.fun_eq_const [Unary P]
(a : P.A) (f : P.B a → α) : f = fun _ => f default := by
funext i
exact congrArg f (Subsingleton.elim i default)

/-- A polynomial functor has children with decidable equality. -/
class DecidableEqChildren (P : PFunctor) where
decidableEq (a : P.A) : DecidableEq (P.B a)

attribute [instance_reducible, instance] DecidableEqChildren.decidableEq

/-- A unary polynomial functor has decidable child equality. -/
instance (P : PFunctor) [P.Unary] : P.DecidableEqChildren where
decidableEq _ _ _ := isTrue (Subsingleton.elim _ _)

/-- Constructs a unary polynomial functor. -/
abbrev mkUnary (A : Type*) : PFunctor where
A := A
B := fun _ => Unit

instance {A : Type u} : (mkUnary A).Unary where
unary _ := by
change Unique Unit
infer_instance

end Unary

end PFunctor
116 changes: 98 additions & 18 deletions Cslib/Foundations/Logic/Operators.lean
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Authors: Fabrizio Montesi, Thomas Waring
module

public import Cslib.Init
public import Cslib.Foundations.Data.PFunctor.Basic

/-! # Logical operators

Expand Down Expand Up @@ -37,6 +38,30 @@ class HasOr (α : Type*) where

@[inherit_doc] scoped infixr:30 " ∨ " => HasOr.or

/-- Finite conjunction, with empty conjunction equal to truth. -/
def finiteAnd [HasAnd α] [Top α] (as : List α) : α := as.foldr (· ∧ ·) ⊤

@[inherit_doc] scoped prefix:max "⋀" => finiteAnd

@[simp, scoped grind =]
theorem finiteAnd_nil [HasAnd α] [Top α] : ⋀([] : List α) = ⊤ := rfl

@[simp, scoped grind =]
theorem finiteAnd_cons [HasAnd α] [Top α] (a : α) (as : List α) :
⋀(a :: as) = (a ∧ ⋀as) := rfl

/-- Finite disjunction, with empty disjunction equal to falsehood. -/
def finiteOr [HasOr α] [Bot α] (as : List α) : α := as.foldr (· ∨ ·) ⊥

@[inherit_doc] scoped prefix:max "⋁" => finiteOr

@[simp, scoped grind =]
theorem finiteOr_nil [HasOr α] [Bot α] : ⋁([] : List α) = ⊥ := rfl

@[simp, scoped grind =]
theorem finiteOr_cons [HasOr α] [Bot α] (a : α) (as : List α) :
⋁(a :: as) = (a ∨ ⋁as) := rfl

/-- The type `α` has an implication connective (`→`). -/
class HasImp (α : Type*) where
/-- `a → b` denotes `a` implies `b`. -/
Expand All @@ -62,21 +87,22 @@ end Propositional

section Modal

/-! ## Basic modalities -/
/-! ## General modalities from modal similarity types (polynomial functors) -/

/-- The type `α` has a box modality (`□`). -/
class HasBox (α : Type*) where
/-- `a` is valid in all immediately reachable states. -/
box (a : α) : α
/-- The type `α` has a family of triangle operators (`Δ`). -/
class HasTriangle (α : Type*) (τ : outParam PFunctor) where
/-- `Δ[op](φ₁, ..., φₙ)` means that `φ₁`, ..., `φₙ` are valid at some respective related states.
-/
triangle (op : τ.A) (arg : τ.B op → α) : α

@[inherit_doc] scoped prefix:40 "□" => HasBox.box
@[inherit_doc] scoped notation:50 "Δ[" op "]" arg:max => HasTriangle.triangle op arg

/-- The type `α` has a diamond modality (`◇`). -/
class HasDiamond (α : Type*) where
/-- `a` is valid in a reachable state. -/
diamond (a : α) : α
/-- The type `α` has a family of nabla operators (`∇`). -/
class HasNabla (α : Type*) (τ : outParam PFunctor) where
/-- `∇[op](φ₁, ..., φₙ)` means that `φ₁`, ..., `φₙ` are valid at all respective related states. -/
nabla (op : τ.A) (arg : τ.B op → α) : α

@[inherit_doc] scoped prefix:40 "◇" => HasDiamond.diamond
@[inherit_doc] scoped notation:50 "∇[" op "]" arg:max => HasNabla.nabla op arg

end Modal

Expand All @@ -88,22 +114,76 @@ Here we need to use the prefix `d` to distinguish our notation from the normal `
A refactoring that makes this unnecessary would be welcome.
-/

/-- The type `α` has a dynamic diamond modality with action type `β` (`d⟨a⟩φ`). -/
class HasDynamicDiamond (α : Type*) (β : outParam Type*) where
/-- `b` is possibly valid after `a`. -/
dynDiamond (a : β) (b : α) : α

@[inherit_doc] scoped notation "d⟨" a "⟩" φ:max => HasDynamicDiamond.dynDiamond a φ

/-- The type `α` has a dynamic box modality with action type `β` (`d[a]φ`). -/
class HasDynamicBox (α β : Type*) where
class HasDynamicBox (α : Type*) (β : outParam Type*) where
/-- `b` is necessarily valid after `a`. -/
dynBox (a : β) (b : α) : α

@[inherit_doc] scoped notation "d[" a "]" φ => HasDynamicBox.dynBox a φ
@[inherit_doc] scoped notation "d[" a "]" φ:max => HasDynamicBox.dynBox a φ

/-- The type `α` has a dynamic diamond modality with action type `β` (`d⟨a⟩φ`). -/
class HasDynamicDiamond (α β : Type*) where
/-- `b` is possibly valid after `a`. -/
dynDiamond (a : β) (b : α) : α
/-- A family of triangle operators over induces dynamic diamond modalities by applying each operator
to the constant argument family. -/
instance [HasTriangle α τ] : HasDynamicDiamond α τ.A where
dynDiamond op φ := Δ[op](PFunctor.const op φ)

@[simp, scoped grind =, modal =]
theorem dynDiamond_eq_triangle [HasTriangle α τ] (op : τ.A) (φ : α) :
(d⟨op⟩φ) = (Δ[op](PFunctor.const op φ)) := rfl

/-- A family of nabla operators induces dynamic box modalities by applying each operator to the
constant argument family. -/
instance [HasNabla α τ] : HasDynamicBox α τ.A where
dynBox op φ := ∇[op](PFunctor.const op φ)

@[inherit_doc] scoped notation "d⟨" a "⟩" φ => HasDynamicDiamond.dynDiamond a φ
@[simp, scoped grind =, modal =]
theorem dynBox_eq_nabla [HasNabla α τ] (op : τ.A) (φ : α) :
(d[op]φ) = (∇[op](PFunctor.const op φ)) := rfl

end Dynamic

section Unimodal

/-! ## Basic modalities (Unimodal logic operators) -/

/-- The type `α` has a box modality (`□`). -/
class HasBox (α : Type*) where
/-- `a` is valid in all immediately reachable states. -/
box (a : α) : α

@[inherit_doc] scoped prefix:40 "□" => HasBox.box

/-- The type `α` has a diamond modality (`◇`). -/
class HasDiamond (α : Type*) where
/-- `a` is valid in a reachable state. -/
diamond (a : α) : α

@[inherit_doc] scoped prefix:40 "◇" => HasDiamond.diamond

/-- A dynamic diamond modality with a unique action induces a basic diamond modality. -/
instance [Unique β] [HasDynamicDiamond α β] : HasDiamond α where
diamond φ := d⟨default⟩φ

@[simp, scoped grind =, modal =]
theorem diamond_eq_dynDiamond [Unique β] [HasDynamicDiamond α β] {φ : α} :
(◇φ) = (d⟨default⟩φ) := rfl

/-- A dynamic box modality with a unique action induces a basic box modality. -/
instance [Unique β] [HasDynamicBox α β] : HasBox α where
box φ := d[default]φ

@[simp, scoped grind =, modal =]
theorem box_eq_dynBox [Unique β] [HasDynamicBox α β] {φ : α} :
(□φ) = (d[default]φ) := rfl

end Unimodal

section Linear

/-! ## Linear connectives -/
Expand Down
105 changes: 105 additions & 0 deletions Cslib/Foundations/Semantics/Frame/Basic.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/-
Copyright (c) 2026 Fabrizio Montesi. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Fabrizio Montesi
-/

module

public import Cslib.Init
public import Cslib.Foundations.Data.PFunctor.Basic

/-! # Modal Frames

A frame is a structure of relations, each with its own arity.

## Implementation notes

Frames for general modal logic were formulated with modal similarity types [Blackburn2001], which
we generalise here to arbitrary polynomial functors.

## References

* [P. Blackburn, M. de Rijke, Y. Venema, *Modal Logic*][Blackburn2001]
-/

@[expose] public section

namespace Cslib

/-- A frame is an indexed structure of potentially heterogeneous relations.

Frames are typically used in combination with modal logics or akin concepts. This is why we use
`op` (for operator) to range over relation indexes.
-/
structure Frame World (τ : PFunctor) where
/-- Accessibility relations. -/
r : (op : τ.A) → World → (τ.B op → World) → Prop

namespace Frame

/-- The binary relation obtained by observing position `i` of the worlds accessible via `op`. -/
def project (f : Frame World τ) (op : τ.A) (i : τ.B op) : World → World → Prop :=
fun w w' => ∃ ws, f.r op w ws ∧ ws i = w'

/-- The binary relation induced by restricting the accessibility relation of `op`
to constant tuples of worlds. -/
@[instance_reducible]
def diagonal (f : Frame World τ) (op : τ.A) : World → World → Prop :=
fun w w' => f.r op w (fun _ => w')

@[scoped grind →, modal →]
theorem r_const_of_diagonal {f : Frame World τ} (h : f.diagonal op w w') :
f.r op w (fun _ => w') := by grind [Frame.diagonal]

@[scoped grind →, modal →]
theorem diagonal_of_r [PFunctor.Unary τ] {f : Frame World τ} (h : f.r op w ws) :
f.diagonal op w (ws default) := by grind [Frame.diagonal, PFunctor.Unary.fun_eq_const op ws]

/-- A frame is diagonally symmetric at `op` if, whenever `w` accesses `ws`, some component of `ws`
accesses the constant sequence at `w`. -/
class DiagonalSymm (f : Frame World τ) (op : τ.A) where
symm w ws : f.r op w ws → ∃ i, f.r op (ws i) (fun _ => w)

/-- A frame is transitive at `op` if accessibility can be composed through any accessible component:
whenever `w` accesses `ws₁` and `ws₁ i` accesses `ws₂`, then `w` accesses `ws₂`.
-/
class Trans (f : Frame World τ) (op : τ.A) where
trans w ws₁ i ws₂ : f.r op w ws₁ → f.r op (ws₁ i) ws₂ → f.r op w ws₂

instance (f : Frame World τ) [f.Trans op] (i : τ.B op) :
IsTrans World (f.project op i) where
trans w₁ w₂ w₃ h₁ h₂ := by
rcases h₁ with ⟨ws₁, hr₁, rfl⟩
rcases h₂ with ⟨ws₂, hr₂, h⟩
exact ⟨ws₂, Frame.Trans.trans _ _ _ _ hr₁ hr₂, h⟩

/-- A frame is right Euclidean at `op` if, whenever a world `w` accesses two tuples `ws₁` and
`ws₂`, some component of `ws₂` accesses `ws₁`. -/
class RightEuclidean (f : Frame World τ) (op : τ.A) where
rightEuclidean w ws₁ ws₂ : f.r op w ws₁ → f.r op w ws₂ → ∃ i, f.r op (ws₂ i) ws₁

/-- A predicate map `Ps` is preserved from `P` by `op` if, whenever `P` holds at a world `w`,
then for every tuple `ws` accessible from `w` via `op`, each component `ws i` satisfies the
corresponding predicate `Ps i`. -/
def PreservesMap (f : Frame α τ) (op : τ.A) (P : α → Prop) (Ps : τ.B op → α → Prop) : Prop :=
∀ w ws, f.r op w ws → P w → ∃ i, Ps i (ws i)

/-- Builds a unary frame from an indexed family of binary relations. -/
def ofRelations {τ : PFunctor} [τ.Unary] (r : τ.A → World → World → Prop) :
Frame World τ where
r i w ws := r i w (ws default)

@[scoped grind =, modal =]
lemma ofRelations_iff {τ : PFunctor} [τ.Unary] (r : τ.A → World → World → Prop) (i : τ.A)
(w : World) (ws : τ.B i → World) : (ofRelations r).r i w ws ↔ r i w (ws default) := by rfl

@[simp, scoped grind =, modal =]
lemma ofRelations_diagonal_iff {τ : PFunctor} [τ.Unary]
(r : τ.A → World → World → Prop) (i : τ.A) (w w' : World) :
(ofRelations r).diagonal i w w' ↔ r i w w' := by
rfl

end Frame

end Cslib
Loading
Loading