Logical foundations
SoleLogics lays the logical foundations for this package. While the full reference for SoleLogics can be found here, these are the basic logical concepts needed for symbolic modelling.
SoleModels.AbstractModelSoleModels.BranchSoleModels.ConstantModelSoleModels.DecisionEnsembleSoleModels.DecisionForestSoleModels.DecisionListSoleModels.DecisionSetSoleModels.DecisionTreeSoleModels.DecisionXGBoostSoleModels.FunctionModelSoleModels.LeafModelSoleModels.MixedModelSoleModels.PlainRuleExtractorSoleModels.RuleSoleModels.RuleExtractorSoleLogics.atomsSoleLogics.connectivesSoleLogics.heightSoleLogics.heightSoleLogics.heightSoleLogics.natomsSoleLogics.nconnectivesSoleLogics.nleavesSoleLogics.nleavesSoleModels.antecedentSoleModels.applySoleModels.checkantecedentSoleModels.consequentSoleModels.defaultconsequentSoleModels.displaymodelSoleModels.evaluateruleSoleModels.evaluateruleSoleModels.extractrulesSoleModels.fSoleModels.hasinfoSoleModels.haslistrulesSoleModels.immediatesubmodelsSoleModels.infoSoleModels.info!SoleModels.iscompleteSoleModels.isexactSoleModels.issymbolicmodelSoleModels.joinrulesSoleModels.listimmediaterulesSoleModels.listrulesSoleModels.negconsequentSoleModels.nimmediatesubmodelsSoleModels.nnodesSoleModels.nnodesSoleModels.nsyntaxleavesSoleModels.ntreesSoleModels.outcomeSoleModels.outcometypeSoleModels.outputtypeSoleModels.parse_orange_decision_listSoleModels.posconsequentSoleModels.printmodelSoleModels.readmetricsSoleModels.rootSoleModels.rootSoleModels.rulebaseSoleModels.rulemetricsSoleModels.solemodelSoleModels.submodelsSoleModels.syntaxleavesSoleModels.treesSoleModels.wrap
SoleLogics.Atom — Typestruct Atom{V} <: AbstractAtom
value::V
endSimplest atom implementation, wrapping a value.
See also AbstractAtom, value, check, SyntaxToken.
SoleLogics.CONJUNCTION — Constantconst CONJUNCTION = NamedConnective{:∧}()
const ∧ = CONJUNCTION
arity(::typeof(∧)) = 2Logical conjunction. It can be typed by \wedge<tab>.
See also NamedConnective, Connective.
SoleLogics.DISJUNCTION — Constantconst DISJUNCTION = NamedConnective{:∨}()
const ∨ = DISJUNCTION
arity(::typeof(∨)) = 2Logical disjunction. It can be typed by \vee<tab>.
See also NamedConnective, Connective.
SoleLogics.Formula — Typeabstract type Formula <: Syntactical endAbstract type for logical formulas. Examples of Formulas are SyntaxLeafs (for example, Atoms and Truth values), SyntaxStructures (for example, SyntaxTrees and LeftmostLinearForms) and TruthTables ( enriched representation, which associates a syntactic structure with additional memoization structures, which can save computational time upon model checking).
Any formula can be converted into its SyntaxTree representation via tree; its height can be computed, and it can be queried for its syntax tokens, atoms, etc... It can be parsed from its syntaxstring representation via parseformula.
Interface
tree(φ::Formula)::SyntaxTreecomposeformulas(c::Connective, φs::NTuple{N,F})::F where {N,F<:Formula}- See also
Syntactical
Utility functions (requiring a walk of the tree)
Base.in(tok::SyntaxToken, φ::Formula)::Boolheight(φ::Formula)::Inttokens(φ::Formula)::AbstractVector{<:SyntaxToken}atoms(φ::Formula)::AbstractVector{<:AbstractAtom}truths(φ::Formula)::AbstractVector{<:Truth}leaves(φ::Formula)::AbstractVector{<:SyntaxLeaf}connectives(φ::Formula)::AbstractVector{<:Connective}operators(φ::Formula)::AbstractVector{<:Operator}ntokens(φ::Formula)::Intnatoms(φ::Formula)::Intntruths(φ::Formula)::Intnleaves(φ::Formula)::Intnconnectives(φ::Formula)::Intnoperators(φ::Formula)::Int
See also tree, SyntaxStructure, SyntaxLeaf.
SoleLogics.syntaxstring — Functionsyntaxstring(s::Syntactical; kwargs...)::StringReturn the string representation of any syntactic object (e.g., Formula, SyntaxTree, SyntaxToken, Atom, Truth, etc). Note that this representation may introduce redundant parentheses. kwargs can be used to specify how to display syntax tokens/trees under some specific conditions.
The following kwargs are currently supported:
function_notation = false::Bool: when set totrue, it forces the use of function notation for binary operators (see here).remove_redundant_parentheses = true::Bool: when set tofalse, it prints a syntaxstring where each syntactical element is wrapped in parentheses.parenthesize_atoms = !remove_redundant_parentheses::Bool: when set totrue, it forces the atoms (which are the leaves of a formula's tree structure) to be wrapped in parentheses.
Examples
julia> syntaxstring(parseformula("p∧q∧r∧s∧t"))
"p ∧ q ∧ r ∧ s ∧ t"
julia> syntaxstring(parseformula("p∧q∧r∧s∧t"), function_notation=true)
"∧(∧(∧(∧(p, q), r), s), t)"
julia> syntaxstring(parseformula("p∧q∧r∧s∧t"), remove_redundant_parentheses=false)
"((((p) ∧ (q)) ∧ (r)) ∧ (s)) ∧ (t)"
julia> syntaxstring(parseformula("p∧q∧r∧s∧t"), remove_redundant_parentheses=true, parenthesize_atoms=true)
"(p) ∧ (q) ∧ (r) ∧ (s) ∧ (t)"
julia> syntaxstring(parseformula("◊((p∧s)→q)"))
"◊((p ∧ s) → q)"
julia> syntaxstring(parseformula("◊((p∧s)→q)"); function_notation = true)
"◊(→(∧(p, s), q))"See also parseformula, SyntaxBranch, SyntaxToken.
Implementation
In the case of a syntax tree, syntaxstring is a recursive function that calls itself on the syntax children of each node. For a correct functioning, the syntaxstring must be defined (including the kwargs... part!) for every newly defined SyntaxToken (e.g., SyntaxLeafs, that is, Atoms and Truth values, and Operators), in a way that it produces a unique string representation, since Base.hash and Base.isequal, at least for SyntaxTrees, rely on it.
In particular, for the case of Atoms, the function calls itself on the wrapped value:
syntaxstring(a::Atom; kwargs...) = syntaxstring(value(a); kwargs...)The syntaxstring for any value defaults to its string representation, but it can be defined by defining the appropriate syntaxstring method.
The syntaxstring for syntax tokens (e.g., atoms, operators) should not be prefixed/suffixed by whitespaces, as this may cause ambiguities upon parsing. For similar reasons, syntaxstrings should not contain parentheses ('(', ')'), and, when parsing in function notation, commas (',').
See also SyntaxLeaf, Operator, parseformula.
SoleLogics.AbstractAlphabet — Typeabstract type AbstractAlphabet{V} endAbstract type for representing an alphabet of atoms with values of type V. An alphabet (or propositional alphabet) is a set of atoms (assumed to be countable).
Examples
julia> Atom(1) in ExplicitAlphabet(Atom.(1:10))
true
julia> Atom(1) in ExplicitAlphabet(1:10)
true
julia> Atom(1) in AlphabetOfAny{String}()
false
julia> Atom("mystring") in AlphabetOfAny{String}()
true
julia> "mystring" in AlphabetOfAny{String}()
┌ Warning: Please, use Base.in(Atom(mystring), alphabet::AlphabetOfAny{String}) instead of Base.in(mystring, alphabet::AlphabetOfAny{String})
└ @ SoleLogics ...
trueInterface
atoms(a::AbstractAlphabet)::AbstractVectorBase.isfinite(::Type{<:AbstractAlphabet})::Boolrandatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...)::AbstractAtom
Utility functions
natoms(a::AbstractAlphabet)::BoolBase.in(p::AbstractAtom, a::AbstractAlphabet)::BoolBase.eltype(a::AbstractAlphabet)randatom(a::AbstractAlphabet, args...; kwargs...)::AbstractAtomatomstype(a::AbstractAlphabet)valuetype(a::AbstractAlphabet)
Implementation
When implementing a new alphabet type MyAlphabet, you should provide a method for establishing whether an atom belongs to it or not; while, in general, this method should be:
function Base.in(p::AbstractAtom, a::MyAlphabet)::Boolin the case of finite alphabets, it suffices to define a method:
function atoms(a::AbstractAlphabet)::AbstractVector{atomstype(a)}By default, an alphabet is considered finite:
Base.isfinite(::Type{<:AbstractAlphabet}) = true
Base.isfinite(a::AbstractAlphabet) = Base.isfinite(typeof(a))
Base.in(p::AbstractAtom, a::AbstractAlphabet) = Base.isfinite(a) ? Base.in(p, atoms(a)) : error(...)See also AbstractGrammar, AlphabetOfAny, AbstractAtom, ExplicitAlphabet.
SoleLogics.AbstractInterpretation — Typeabstract type AbstractInterpretation endAbstract type for representing a logical interpretation. In the case of propositional logic, is essentially a map atom → truth value.
Properties expressed via logical formulas can be checked on logical interpretations.
Interface
valuetype(i::AbstractInterpretation)truthtype(i::AbstractInterpretation)interpret(φ::Formula, i::AbstractInterpretation, args...; kwargs...)::Formula
Utility functions
check(φ::Formula, i::AbstractInterpretation, args...; kwargs...)::Bool
See also check, AbstractAssignment, AbstractKripkeStructure.
SoleLogics.AbstractInterpretationSet — Typeabstract type AbstractInterpretationSet <: AbstractDataset endAbstract type for ordered sets of interpretations. A set of interpretations, also referred to as a dataset in this context, is a collection of instances, each of which is an interpretation, and is identified by an index iinstance::Integer. These structures are especially useful when performing [model checking](https://en.wikipedia.org/wiki/Modelchecking).
Interface
interpretationtype(S)alphabet(s)getinstance(s)concatdatasets(ss...)instances(s, idxs, return_view; kwargs...)ninstances(s)
Utility Functions
Utility Functions (with more-than-propositional logics)
worldtype(s)frametype(s)frame(s, i_instance)accessibles(s, i_instance, args...)allworlds(s, i_instance, args...)nworlds(s, i_instance)
See also InterpretationVector.
SoleLogics.check — Functioncheck(
φ::Formula,
i::AbstractInterpretation,
args...;
kwargs...
)::BoolCheck 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)
falseSee also interpret, Formula, AbstractInterpretation, TruthDict.
check(φ::Formula, i::Union{AbstractDict, AbstractVector}, args...)Takes a Formula as input and returns its truth value in relation to the dictionary or vector passed. We let any AbstractDict and AbstractVector be used as an interpretation when model checking.
See also Formula.
check(a::Atom, i::AbstractDict)Returns the Boolean value corresponding to the atom passed as parameter.
Examples
julia> check(Atom(1), Dict([1 => ⊤, 2 => ⊥]))
true
julia> check(Atom(3), Dict([1 => ⊤, 2 => ⊥]))
falseSee also Atom.
check(a::Atom, i::AbstractVector)Returns a truth value indicating whether or not that Atom is contained in the passed vector.
Examples
julia> check(Atom(1), [1,2,4])
true
julia> check(Atom(5), [2,3,4])
falseSee also Atom.
function check(
φ::SyntaxTree,
i::AbstractKripkeStructure,
w::Union{Nothing,AnyWorld,<:AbstractWorld} = nothing;
use_memo::Union{Nothing,AbstractDict{<:Formula,<:Vector{<:AbstractWorld}}} = nothing,
perform_normalization::Bool = true,
memo_max_height::Union{Nothing,Int} = nothing,
)::BoolCheck a formula on a specific word in a KripkeStructure.
Examples
julia> using Graphs, Random
julia> @atoms String p q
2-element Vector{Atom{String}}:
Atom{String}("p")
Atom{String}("q")
julia> fmodal = randformula(Random.MersenneTwister(14), 3, [p,q], SoleLogics.BASE_MODAL_CONNECTIVES)
¬□(p ∨ q)
# A special graph, called Kripke Frame, is created.
# Nodes are called worlds, and the edges are relations between worlds.
julia> worlds = SoleLogics.World.(1:5) # 5 worlds are created, numerated from 1 to 5
julia> edges = Edge.([(1,2), (1,3), (2,4), (3,4), (3,5)])
julia> kframe = SoleLogics.ExplicitCrispUniModalFrame(worlds, Graphs.SimpleDiGraph(edges))
# A valuation function establishes which fact are true on each world
julia> valuation = Dict([
worlds[1] => TruthDict([p => true, q => false]),
worlds[2] => TruthDict([p => true, q => true]),
worlds[3] => TruthDict([p => true, q => false]),
worlds[4] => TruthDict([p => false, q => false]),
worlds[5] => TruthDict([p => false, q => true]),
])
# Kripke Frame and valuation function are merged in a Kripke Structure
julia> kstruct = KripkeStructure(kframe, valuation)
julia> [w => check(fmodal, kstruct, w) for w in worlds]
5-element Vector{Pair{SoleLogics.World{Int64}, Bool}}:
SoleLogics.World{Int64}(1) => 0
SoleLogics.World{Int64}(2) => 1
SoleLogics.World{Int64}(3) => 1
SoleLogics.World{Int64}(4) => 0
SoleLogics.World{Int64}(5) => 0See also SyntaxTree, AbstractWorld, KripkeStructure.
check(
φ::Formula,
s::AbstractInterpretationSet,
i_instance::Integer,
args...;
kwargs...
)::BoolCheck a formula on the $i$-th instance of an AbstractInterpretationSet.
See also AbstractInterpretationSet, Formula.
check(
φ::Formula,
s::AbstractInterpretationSet,
args...;
kwargs...
)::Vector{Bool}Check a formula on all instances of an AbstractInterpretationSet.
See also AbstractInterpretationSet, Formula.
check(
φ::SoleLogics.SyntaxTree,
i::SoleLogics.LogicalInstance{<:AbstractModalLogiset{W,<:U}},
w::Union{Nothing,AnyWorld,<:AbstractWorld} = nothing;
kwargs...
)::BoolCheck whether a formula φ holds for a given instance i_instance of a logiset X, on a world w. Note that the world can be elided for grounded formulas (see isgrounded).
This implementation recursively evaluates the subformulas of φ and use memoization to store the results using Emerson-Clarke algorithm. The memoization structure is either the one stored in X itself (if X supports memoization) or a structure passed as the use_memo argument. If X supports onestep memoization, then it will be used for specific diamond formulas, up to an height equal to a keyword argument memo_max_height.
Arguments
φ::SoleLogics.SyntaxTree: the formula to check.i::SoleLogics.LogicalInstance{<:AbstractModalLogiset{W,<:U}}: the instance of the logiset to check in.w::Union{Nothing,AnyWorld,<:AbstractWorld} = nothing: the world to check in. Ifnothing, the method checks in all worlds of the instance.
Keyword arguments
use_memo::Union{Nothing,AbstractMemoset{<:AbstractWorld},AbstractVector{<:AbstractDict{<:FT,<:AbstractWorlds}}} = nothing: the memoization structure to use. Ifnothing, the method uses the one stored inXifXsupports memoization. IfAbstractMemoset, the method uses thei_instance-th element of the memoization structure. IfAbstractVector, the method uses thei_instance-th element of the vector.perform_normalization::Bool = true: whether to normalize the formula before checking it.memo_max_height::Union{Nothing,Int} = nothing: the maximum height up to which onestep memoization should be used. Ifnothing, the method does not use onestep memoization.onestep_memoset_is_complete = false: whether the onestep memoization structure is complete (i.e. it contains all possible values of the metaconditions in the structure).
SoleLogics.AbstractWorld — Typeabstract type AbstractWorld endAbstract 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.GeometricalWorld — Typeabstract type GeometricalWorld <: AbstractWorld endAbstract type for worlds with a geometrical interpretation.
See also Point, Interval, Interval2D, AbstractWorld.
SoleLogics.Interval — Typestruct Interval{T<:Real} <: GeometricalWorld
x :: T
y :: T
endAn interval in a 1-dimensional space, with coordinates of type T.
Examples
julia> SoleLogics.goeswithdim(SoleLogics.Interval(1,2),1)
true
julia> SoleLogics.goeswithdim(SoleLogics.Interval(1,2),2)
false
julia> collect(accessibles(SoleLogics.FullDimensionalFrame(5), Interval(1,2), SoleLogics.IA_L))
6-element Vector{Interval{Int64}}:
(3−4)
(3−5)
(4−5)
(3−6)
(4−6)
(5−6)
See also goeswithdim, accessibles, FullDimensionalFrame, Point, Interval2D, GeometricalWorld, AbstractWorld.
SoleLogics.Interval2D — Typestruct Interval2D{T<:Real} <: GeometricalWorld
x :: Interval{T}
y :: Interval{T}
endA orthogonal rectangle in a 2-dimensional space, with coordinates of type T. This is the 2-dimensional Interval counterpart, that is, the combination of two orthogonal Intervals.
Examples
julia> SoleLogics.goeswithdim(SoleLogics.Interval2D((1,2),(3,4)),1)
false
julia> SoleLogics.goeswithdim(SoleLogics.Interval2D((1,2),(3,4)),2)
true
julia> collect(accessibles(SoleLogics.FullDimensionalFrame(5,5), Interval2D((2,3),(2,4)), SoleLogics.IA_LL))
3-element Vector{Interval2D{Int64}}:
((4−5)×(5−6))
((4−6)×(5−6))
((5−6)×(5−6))
See also goeswithdim, accessibles, FullDimensionalFrame, Point, Interval, GeometricalWorld, AbstractWorld.
SoleLogics.AbstractRelation — Typeabstract type AbstractRelation endAbstract 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))
trueImplementation
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) = crIf the relation is many-to-one or one-to-one, please flag it with:
istoone(::R) = trueIf the relation is reflexive or transitive, flag it with:
isreflexive(::R) = true
istransitive(::R) = trueMost 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.globalrel — Constantstruct GlobalRel <: AbstractRelation end;
const globalrel = GlobalRel();Singleton type for the global relation. This is a binary relation via which a world accesses every other world within the frame. The relation is also symmetric, reflexive and transitive.
Examples
julia> syntaxstring(SoleLogics.globalrel)
"G"
julia> SoleLogics.converse(globalrel)
GlobalRel()See also identityrel, AbstractRelation, AbstractWorld, AbstractFrame. AbstractKripkeStructure,
SoleLogics.IA_L — ConstantSee IntervalRelation.
SoleLogics.AbstractFrame — Typeabstract type AbstractFrame{W<:AbstractWorld} endAbstract 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.AbstractKripkeStructure — Typeabstract type AbstractKripkeStructure <: AbstractInterpretation endAbstract 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 Atoms to Truth values.
See also frame, worldtype, accessibles, AbstractInterpretation.
SoleLogics.accessibles — Functionaccessibles(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.
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))
trueImplementation
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))
endAs 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)
endIn general, it should be true that collect(accessibles(fr, w, r)) isa AbstractWorlds{W}.
See also AbstractWorld, AbstractRelation, AbstractMultiModalFrame.