Introduction
At the end of this chapter, you are going to understand what modal logic is, and why it is so important from a computational standpoint of view, with respect to propositional logic and first order logic. For those of you who want to fully immerse in the topic, we recommend reading the thesis Foundations of Modal Symbolic Learning, by Stan Ionel Eduard.
Recalling the type hierarchy presented in man-core, it is here enriched with the following new types and structures.
Pills of Modal Logic
Narrowly speaking, modal logic was initially investigated as the logic of necessary and possible truths judgments due to Aristotle's foresighted analysis of statements containing the words "necessary" and "possible".
Modal logic is, essentially, Propositional Logic enriched with a set of modal connectives (or modalities), that are used to express some form of quantification over a set of entities. Interpretations in modal logic act as directed, possibly many-relation graphs of propositional assigments, and are called Kripke structures. The vertices in the graph model the entities, and are called possible worlds (or, simply, worlds). Furthermore, worlds are connected via one or more accessibility relations, and each world has a propositional assignment. Modal formula are, generally, interpreted on specific worlds of Kripke structures.
Why Modal Logic? An introduction
Very often real-world applications give rise to non-static sets of data (e.g., temporal, spatial, graph-based data). The standard approach is to pre-process such data, so that their aspect becomes easy to fit in a tabular structure (e.g., datasets, spreadsheets).
Pre-processing non-static data means denaturing it: we want to deal with this kind of data natively, avoiding losing valuable information about its structure. Because of this, we require a logic which is more expressive than propositional one. This is where modal logic comes in.
Imagine the following scenario: you have a speech audio sample of $9$ seconds and you want to establish whether the propositional letter $p$ is true or no on the sample. Let's say that $p$ is the fact the audio is loud. The latter is referred to the sample in its entirety: by looking at it as a whole we can compute whether $p$ is true or no. Let's say that $p$ is true. We can easily schematize the situation by just drawing a graph with a single node containing $p$, where the node is the entire audio.
This single node above is what we call world, and it is exactly a propositional model. We can represent it as follows.
p = Atom("p") # "the audio is loud"
world = SoleLogics.World(1) # this is just an abstract reference to a 9s audio sample
# ... calculations here ...
valuation = Dict([world => TruthDict(p => true)]) # after some calculations, we establish "p" is true in "world"
Now here's a challenge. Try to express the following fact using just propositional logic: the audio contains at least two pauses of three seconds that are interspersed with each other by three or more seconds of loud speaking. After a few tries you should be convinced that this is not possible, because through propositional logic we are not able to quantify over relations inside the sample. What we can do instead, is upgrade propositional logic to modal logic. We have to deal in a more granular manner with the original audio sample, and we don't want to denature it. Here is what we need:
- an
Atom
$p$, representing the fact the audio is loud; - an
Atom
$q$, representing the fact the audio is silence; World
s describing small pieces of the original audio. In particular, we establish that each world is representative for $3$ seconds of audio;- an accessibility relation (a specific
AbstractRelation
) to express which worlds are reachable from another through a modalNamedConnective
.
The situation is simply schematized as follows
where each world identifies $3$ seconds of audio, and we consider a relation between two worlds only if they represent adjacent parts of the audio. At this point, let's create the Kripke structure in the example using SoleLogics.jl.
p = Atom("p") # "the audio is loud"
q = Atom("q") # "the audio is silence"
# Worlds representing 3-second-pieces of the original audio
worlds = [SoleLogics.World(1), SoleLogics.World(2), SoleLogics.World(3)]
edges = Edge.([(1,2), (1,3), (2,3)])
kripkeframe = SoleLogics.ExplicitCrispUniModalFrame(worlds, Graphs.SimpleDiGraph(edges))
valuation = Dict([worlds[1] => TruthDict([p => false, q => true]), worlds[2] => TruthDict([p => true, q => false]), worlds[3] => TruthDict([p => false, q => true])])
kripkestructure = KripkeStructure(kripkeframe, valuation)
In the Kripke structure above, the central world $w_2$ (the only one where $p$ is true) is accessible from the leftmost world $w_1$ (where $q$ is true). In modal logic terms, this is expressed by the notation
\[K,w_1 \models \lozenge p\]
where $K$ is a Kripke structure, $w_1$ is the leftmost world in the image, and $\lozenge p$ means look at the world accessibles from $w_1$ and check whether p is true or false on those neighbors.
Now we are ready to resume the long statement of a few paragraphs ago, the one we could not express using only propositional logic. We can translate it using modal logic! The formula we are looking for is
\[q \wedge \lozenge p \wedge \lozenge \lozenge q\]
which has to be read check whether this sub-sample of audio is silence, and the sub-sample after this is loud, and the sub-sample after the latter is silence again. Let's see if this formula is true on the Kripke structure above, starting from $w_1$.
# ... continuing code above ...
phi = ∧(q, ∧(◊(p),◊(◊(q)))) # \wedge+TAB can also be written as CONJUNCTION, while \lozenge+TAB is called DIAMOND and is a modal operator
check(phi, kripkestructure, worlds[1]) # prints true
By reading the following sections, you will better grasp how World
s are defined, as well as relations (AbstractRelation
), how those two concepts are bound togheter in AbstractFrame
s and KripkeStructure
s. You will also understand how to access one world from another by using (or implementing) Connective
s such as ◊
(or DIAMOND
) and the accessibles
method.
Worlds and Frames
SoleLogics.AbstractWorld
— Typeabstract type AbstractWorld end
Abstract type for the nodes of an annotated accessibility graph (Kripke structure). This is used, for example, in modal logic, where the truth of formulas is relativized to worlds, that is, nodes of a graph.
Implementing
When implementing a new world type, the logical semantics should be defined via accessibles
methods; refer to the help for accessibles
.
See also AbstractKripkeStructure
, AbstractFrame
.
SoleLogics.World
— Typestruct World{T} <: AbstractWorld
name::T
end
A world that is solely identified by its name
. This can be useful when instantiating the underlying graph of a modal frame in an explicit way.
See also OneWorld
, AbstractWorld
.
SoleLogics.AbstractFrame
— Typeabstract type AbstractFrame{W<:AbstractWorld} end
Abstract type for an accessibility graph (Kripke frame), that gives the topology to Kripke structures. A frame can be queried for its set of vertices (also called worlds, see allworlds
), and it can be browsed via its accessibility relation(s) (see accessibles
). Refer to FullDimensionalFrame
as an example.
See also truthtype
, , allworlds
, nworlds
, AbstractKripkeStructure
, AbstractWorld
.
SoleLogics.worldtype
— Methodworldtype(fr::AbstractFrame)
worldtype(i::AbstractKripkeStructure)
Return the world type of the Kripke frame/structure.
See also AbstractFrame
.
SoleLogics.allworlds
— Methodallworlds(fr::AbstractFrame{W})::AbstractVector{<:W} where {W<:AbstractWorld}
Return all worlds within the frame.
See also nworlds
, AbstractFrame
.
SoleLogics.nworlds
— Methodnworlds(fr::AbstractFrame)::Integer
Return the number of worlds within the frame.
See also nworlds
, AbstractFrame
.
SoleLogics.AbstractUniModalFrame
— Typeabstract type AbstractUniModalFrame{W<:AbstractWorld} <: AbstractFrame{W} end
A frame of a modal logic based on a single (implicit) accessibility relation.
See also AbstractMultiModalFrame
, AbstractFrame
.
SoleLogics.accessibles
— Methodaccessibles(fr::AbstractUniModalFrame{W}, w::W)::Worlds{W} where {W<:AbstractWorld}
Return the worlds in frame fr
that are accessible from world w
.
See also AbstractWorld
, AbstractUniModalFrame
.
Relations
SoleLogics.AbstractRelation
— Typeabstract type AbstractRelation end
Abstract type for the relations of a multi-modal annotated accessibility graph (Kripke structure). Two noteworthy relations are identityrel
and globalrel
, which access the current world and all worlds, respectively.
Examples
julia> fr = SoleLogics.FullDimensionalFrame((10,), Interval{Int});
julia> Interval(8,11) in (accessibles(fr, Interval(2,5), IA_L))
true
Implementation
When implementing a new relation type R
, please provide the methods:
arity(::R)::Int = ...
syntaxstring(::R; kwargs...)::String = ...
If the relation is symmetric, please specify its converse relation cr
with:
hasconverse(::R) = true
converse(::R) = cr
If the relation is many-to-one or one-to-one, please flag it with:
istoone(::R) = true
If the relation is reflexive or transitive, flag it with:
isreflexive(::R) = true
istransitive(::R) = true
Most importantly, the logical semantics for R
should be defined via accessibles
methods; refer to the help for accessibles
.
See also issymmetric
, isreflexive
, istransitive
, isgrounding
, arity
, syntaxstring
, converse
, hasconverse
, istoone
, identityrel
, globalrel
, accessibles
, AbstractKripkeStructure
, AbstractFrame
, AbstractWorld
.
SoleLogics.arity
— MethodSoleLogics.converse
— Methodhasconverse(r::AbstractRelation)::Bool
converse(r::AbstractRelation)::AbstractRelation
If the relation hasconverse
, return the converse relation (type) of a given relation (type).
See also issymmetric
, isreflexive
, istransitive
, AbstractRelation
.
SoleLogics.istoone
— Methodistoone(r::AbstractRelation) = false
Return whether it is known that a relation is istoone.
See also hasconverse
, converse
, issymmetric
, istransitive
, isgrounding
, AbstractRelation
.
SoleLogics.issymmetric
— Methodissymmetric(r::AbstractRelation) = hasconverse(r) ? converse(r) == r : false
Return whether it is known that a relation is symmetric.
See also hasconverse
, converse
, isreflexive
, istransitive
, isgrounding
, AbstractRelation
.
SoleLogics.isreflexive
— Methodisreflexive(::AbstractRelation)
Return whether it is known that a relation is reflexive.
See also issymmetric
, istransitive
, isgrounding
, AbstractRelation
.
SoleLogics.istransitive
— Methodistransitive(::AbstractRelation)
Return whether it is known that a relation is transitive.
See also istoone
, issymmetric
, isgrounding
, AbstractRelation
.
SoleLogics.isgrounding
— Methodisgrounding(::AbstractRelation)
Return whether it is known that a relation is grounding. A relation R
is grounding if ∀x,z,y R(x,y) ⇔ R(z,y).
See also isreflexive
, issymmetric
, istransitive
, AbstractRelation
.
More on Frames and Kripke Structures
SoleLogics.AbstractMultiModalFrame
— Typeabstract type AbstractMultiModalFrame{W<:AbstractWorld} <: AbstractFrame{W} end
A frame of a multi-modal logic, that is, a modal logic based on a set of accessibility relations.
Implementation
When implementing a new multi-modal frame type, the logical semantics for the frame should be defined via accessibles
methods; refer to the help for accessibles
.
See also AbstractUniModalFrame
, AbstractFrame
.
SoleLogics.accessibles
— Methodaccessibles(
fr::AbstractMultiModalFrame{W},
w::W,
r::AbstractRelation
) where {W<:AbstractWorld}
Return the worlds in frame fr
that are accessible from world w
via relation r
.
Examples
julia> fr = SoleLogics.FullDimensionalFrame((10,), Interval{Int});
julia> typeof(accessibles(fr, Interval(2,5), IA_L))
Base.Generator{...}
julia> typeof(accessibles(fr, globalrel))
Base.Generator{...}
julia> @assert SoleLogics.nworlds(fr) == length(collect(accessibles(fr, globalrel)))
julia> typeof(accessibles(fr, Interval(2,5), identityrel))
Vector{Interval{Int64}}
julia> Interval(8,11) in collect(accessibles(fr, Interval(2,5), IA_L))
true
Implementation
Since accessibles
always returns an iterator to worlds of the same type W
, the current implementation of accessibles
for multi-modal frames delegates the enumeration to a lower level _accessibles
function, which returns an iterator to parameter tuples that are, then, fed to the world constructor the using IterTools generators, as in:
function accessibles(
fr::AbstractMultiModalFrame{W},
w::W,
r::AbstractRelation,
) where {W<:AbstractWorld}
IterTools.imap(W, _accessibles(fr, w, r))
end
As such, when defining new frames, worlds, and/or relations, one should provide new methods for _accessibles
. For example:
_accessibles(fr::Full1DFrame, w::Interval{<:Integer}, ::_IA_A) = zip(Iterators.repeated(w.y), w.y+1:X(fr)+1)
This pattern is generally convenient; it can, however, be bypassed, although this requires defining two additional methods in order to resolve dispatch ambiguities. When defining a new frame type FR{W}
, one can resolve the ambiguities and define a custom accessibles
method by providing these three methods:
# access worlds through relation `r`
function accessibles(
fr::FR{W},
w::W,
r::AbstractRelation,
) where {W<:AbstractWorld}
...
end
# access current world
function accessibles(
fr::FR{W},
w::W,
r::IdentityRel,
) where {W<:AbstractWorld}
[w]
end
# access all worlds
function accessibles(
fr::FR{W},
w::W,
r::GlobalRel,
) where {W<:AbstractWorld}
allworlds(fr)
end
In general, it should be true that collect(accessibles(fr, w, r)) isa AbstractWorlds{W}
.
See also AbstractWorld
, AbstractRelation
, AbstractMultiModalFrame
.
SoleLogics.AbstractKripkeStructure
— Typeabstract type AbstractKripkeStructure <: AbstractInterpretation end
Abstract type for representing Kripke structures's. It comprehends a directed graph structure (Kripke frame), where nodes are referred to as worlds, and the binary relation between them is referred to as the accessibility relation. Additionally, each world is associated with a mapping from Atom
s to Truth
values.
See also frame
, worldtype
, accessibles
, AbstractInterpretation
.
SoleLogics.check
— Methodcheck(
φ::Formula,
i::AbstractInterpretation,
args...;
kwargs...
)::Bool
Check a formula on a logical interpretation (or model), returning true
if the truth value for the formula istop
. This process is referred to as (finite) model checking, and there are many algorithms for it, typically depending on the complexity of the logic.
Examples
julia> @atoms String p q
2-element Vector{Atom{String}}:
Atom{String}("p")
Atom{String}("q")
julia> td = TruthDict([p => TOP, q => BOT])
TruthDict with values:
┌────────┬────────┐
│ q │ p │
│ String │ String │
├────────┼────────┤
│ ⊥ │ ⊤ │
└────────┴────────┘
julia> check(CONJUNCTION(p,q), td)
false
See also interpret
, Formula
, AbstractInterpretation
, TruthDict
.
SoleLogics.KripkeStructure
— Typestruct KripkeStructure{
FR<:AbstractFrame,
MAS<:AbstractDict
} <: AbstractKripkeStructure
frame::FR
assignment::AS
end
Type for representing Kripke structures. explicitly; it wraps a frame
, and an abstract dictionary that assigns an interpretation to each world.
Modal Connectives
SoleLogics.ismodal
— Methodismodal(::Type{<:Connective})::Bool = false
ismodal(c::Connective)::Bool = ismodal(typeof(c))
Return whether it is known that an Connective
is modal.
Examples
julia> ismodal(◊)
true
julia> ismodal(∧)
false
SoleLogics.isbox
— Methodisbox(::Type{<:Connective})::Bool = false
isbox(c::Connective)::Bool = isbox(typeof(c))
Return whether it is known that an Connective
is a box (i.e., universal) connective.
Examples
julia> SoleLogics.isbox(◊)
false
julia> SoleLogics.isbox(∧)
false
julia> SoleLogics.isbox(□)
true
SoleLogics.DIAMOND
— Constantconst DIAMOND = NamedConnective{:◊}()
const ◊ = DIAMOND
ismodal(::typeof(◊)) = true
arity(::typeof(◊)) = 1
Logical diamond connective, typically interpreted as the modal existential quantifier. See here.
See also BOX
, NamedConnective
, Connective
.
SoleLogics.BOX
— Constantconst BOX = NamedConnective{:□}()
const □ = BOX
arity(::typeof(□)) = 1
Logical box connective, typically interpreted as the modal universal quantifier. See here.
See also DIAMOND
, NamedConnective
, Connective
.
Relational Connectives
SoleLogics.AbstractRelationalConnective
— Typeabstract type AbstractRelationalConnective{R<:AbstractRelation} <: Connective end
Abstract type for relational logical connectives. A relational connective allows for semantic quantification across relational structures (e.g., Kripke structures). It has arity equal to the arity of its underlying relation minus one.
See, for example temporal modal logic.
See also DiamondRelationalConnective
, BoxRelationalConnective
, AbstractKripkeStructure
, AbstractFrame
.
SoleLogics.relationtype
— Methodrelationtype(::AbstractRelationalConnective{R}) where {R<:AbstractRelation} = R
relation(op::AbstractRelationalConnective) = relationtype(op)()
Return the underlying relation (and relation type) of the relational connective.
See also AbstractFrame
.
SoleLogics.DiamondRelationalConnective
— Typestruct DiamondRelationalConnective{R<:AbstractRelation} <: AbstractRelationalConnective{R} end
struct BoxRelationalConnective{R<:AbstractRelation} <: AbstractRelationalConnective{R} end
Singleton types for relational connectives, typically interpreted as the modal existential and universal quantifier, respectively.
Both connectives can be easily instantiated with relation instances, such as DiamondRelationalConnective(rel)
, which is a shortcut for DiamondRelationalConnective{typeof(rel)}()
.
Examples
julia> syntaxstring(DiamondRelationalConnective(IA_A))
"⟨A⟩"
julia> syntaxstring(BoxRelationalConnective(IA_A))
"[A]"
julia> @assert DiamondRelationalConnective(IA_A) == SoleLogics.dual(BoxRelationalConnective(IA_A))
See also DiamondRelationalConnective
, BoxRelationalConnective
, syntaxstring
, dual
, AbstractKripkeStructure
, AbstractFrame
.
SoleLogics.diamond
— Methoddiamond() = DIAMOND
diamond(r::AbstractRelation) = DiamondRelationalConnective(r)
Return either the diamond modal connective from unimodal logic (i.e., ◊), or a a diamond relational connective from a multi-modal logic, wrapping the relation r
.
See also DiamondRelationalConnective
, diamond
, DIAMOND
.
SoleLogics.box
— Methodbox() = BOX
box(r::AbstractRelation) = BoxRelationalConnective(r)
Return either the box modal connective from unimodal logic (i.e., □), or a a box relational connective from a multi-modal logic, wrapping the relation r
.
See also BoxRelationalConnective
, box
, BOX
.
Final steps
SoleLogics.modallogic
— Methodmodallogic(;
alphabet = AlphabetOfAny{String}(),
operators = [⊤, ⊥, ¬, ∧, ∨, →, ◊, □],
grammar = CompleteFlatGrammar(AlphabetOfAny{String}(), [⊤, ⊥, ¬, ∧, ∨, →, ◊, □]),
algebra = BooleanAlgebra(),
)
Instantiate a modal logic given a grammar and an algebra. Alternatively, an alphabet and a set of operators can be specified instead of the grammar.
Examples
julia> (¬) isa operatorstype(modallogic());
true
julia> (□) isa operatorstype(modallogic());
true
julia> (□) isa operatorstype(modallogic(; operators = [¬, ∨]))
┌ Warning: Instantiating modal logic (via `modallogic`) with solely propositional operators (SoleLogics.NamedConnective[¬, ∨]). Consider using propositionallogic instead.
└ @ SoleLogics ~/.julia/dev/SoleLogics/src/modal-logic.jl:642
false
julia> modallogic(; alphabet = ["p", "q"]);
julia> modallogic(; alphabet = ExplicitAlphabet([Atom("p"), Atom("q")]));
See also propositionallogic
, AbstractAlphabet
, AbstractAlgebra
.
SoleLogics.collateworlds
— Methodcollateworlds(
fr::AbstractFrame{W},
op::Operator,
t::NTuple{N,WS},
)::AbstractVector{<:W} where {N,W<:AbstractWorld,WS<:AbstractWorlds}
For a given crisp frame (truthtype == Bool
), return the set of worlds where a composed formula op(φ1, ..., φN) is true, given the N
sets of worlds where the each immediate sub-formula is true.
See also check
, iscrisp
, Operator
, AbstractFrame
.