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.

A single node, where p is true

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;
  • Worlds 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 modal NamedConnective.

The situation is simply schematized as follows

A Kripke structure, representing a time serie

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 Worlds are defined, as well as relations (AbstractRelation), how those two concepts are bound togheter in AbstractFrames and KripkeStructures. You will also understand how to access one world from another by using (or implementing) Connectives such as (or DIAMOND) and the accessibles method.

SoleLogics.AbstractWorldType
abstract 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.

source
SoleLogics.WorldType
struct 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.

source
SoleLogics.AbstractRelationType
abstract 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.

source
SoleLogics.AbstractMultiModalFrameType
abstract 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.

source
SoleLogics.accessiblesMethod
accessibles(
    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.

source
SoleLogics.checkMethod
check(
    φ::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.

source
SoleLogics.KripkeStructureType
struct 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.

source
SoleLogics.ismodalMethod
ismodal(::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
source
SoleLogics.isboxMethod
isbox(::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
source
SoleLogics.DIAMONDConstant
const 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.

source
SoleLogics.AbstractRelationalConnectiveType
abstract 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.

source
SoleLogics.relationtypeMethod
relationtype(::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.

source
SoleLogics.DiamondRelationalConnectiveType
struct 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.

source
SoleLogics.diamondMethod
diamond() = 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.

source
SoleLogics.boxMethod
box() = 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.

source
SoleLogics.modallogicMethod
modallogic(;
    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.

source
SoleLogics.collateworldsMethod
collateworlds(
    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.

source