Getting started
In this introductory section you will learn about the main building blocks of SoleLogics. Their definition, usage examples and how to customize them to your own needs.
In short, you can consider this package as divided into two halves.:
- the syntactical half, which defines structures to represent logical constructs such as assertions, logical constants, alphabets, grammars, crisp and fuzzy algebras, formulas etc. A consistent part of SoleLogics is devoted to randomly generate such structures, as well as parse and minimize formulas;
- the semantic half, which defines rules to apply when interpreting a logical formulas. The "semantic heart" of SoleLogics is its finite model checking algorithm, whose purpose is to efficiently check whether a formula is satisfied by an interpretation or not.
Please, feel free to use the following tree structures to orient yourself in the reading of this section. More pieces will be added to this type-hierarchy tree in the following sections.
Type hierarchy
SyntacticalConnective(e.g., ∧, ∨, ¬, →)FormulaSyntaxStructureSyntaxTree(e.g., ¬p ∧ q → s)SyntaxLeafSyntaxBranch(e.g., p ∧ q)
AbstractInterpretation(e.g., p is ⊤, equivalent to p is true in boolean logic)
Also, two union types are defined:
Operator, that is,Union{Connective,Truth},SyntaxToken, that is,Union{Atom,Connective}.
Syntax Basics
SoleLogics.Syntactical — Typeabstract type Syntactical endMaster abstract type for all syntactical objects (e.g., formulas, connectives).
Interface
syntaxstring(s::Syntactical; kwargs...)::String
See also Formula, Connective.
To print out a generic Syntactical element, we must define how it is converted into a string. To do this, we can implement a custom syntaxstring.
SoleLogics.syntaxstring — Methodsyntaxstring(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.Connective — Typeabstract type Connective <: Syntactical endAbstract type for logical connectives, that are used to express non-atomic statements; for example, CONJUNCTION, DISJUNCTION, NEGATION and IMPLICATION (stylized as ∧, ∨, ¬ and →).
Interface
arity(::Connective)::Intiscommutative(::Connective)::Boolprecedence(::Connective)::Intassociativity(::Connective)::Symbolcollatetruth(::Connective, ::NTuple{N,Truth})::Truthsimplify(::Connective, ::NTuple{N,SyntaxTree})::SyntaxTreedual(s::Connective)::Connectivehasdual(s::Connective)::Bool- See also
Syntactical
Implementation
When implementing a new type C for a connective, please define its arity. For example, with a binary operator (e.g., ∨ or ∧):
arity(::C) = 2When implementing a new type C for a commutative connective with arity higher than 1, please provide a method iscommutative(::C). This can speed up model checking operations.
When implementing a custom binary connective, one can override the default precedence and associativity (see here. If the custom connective is a NamedConnective and renders as something considered as a math symbol (for example, ⊙, see https://stackoverflow.com/a/60321302/5646732), by the Julia parser, Base.operator_precedence and Base.operator_associativity are used to define these behaviors, and you might want to avoid providing these methods at all.
The semantics of a propositional connective can be specified via collatetruth (see example below); in principle, the definition can rely on the partial order between truth values (specified via precedes).
Here is an example of a custom implementation of the xor (⊻) Boolean operator.
import SoleLogics: arity, iscommutative, collatetruth
const ⊻ = SoleLogics.NamedConnective{:⊻}()
SoleLogics.arity(::typeof(⊻)) = 2
SoleLogics.iscommutative(::typeof(⊻)) = true
SoleLogics.collatetruth(::typeof(⊻), (t1, t2)::NTuple{N,T where T<:BooleanTruth}) where {N} = (count(istop, (t1, t2)) == 1)Note that collatetruth must be defined at least for some truth value types T via methods accepting an NTuple{arity,T} as a second argument.
To make the operator work with incomplete interpretations (e.g., when the Truth value for an atom is not known), simplification rules for NTuple{arity,T where T<:Formula}s should be provided via methods for simplify. For example, these rules suffice for simplifying xors between TOP/BOT`s, and other formulas:
import SoleLogics: simplify
simplify(::typeof(⊻), (t1, t2)::Tuple{BooleanTruth,BooleanTruth}) = istop(t1) == istop(t2) ? BOT : TOP
simplify(::typeof(⊻), (t1, t2)::Tuple{BooleanTruth,Formula}) = istop(t1) ? ¬t2 : t2
simplify(::typeof(⊻), (t1, t2)::Tuple{Formula,BooleanTruth}) = istop(t2) ? ¬t1 : t1Beware of dispatch ambiguities!
See also arity, SyntaxBranch, associativity, precedence, check, iscommutative, NamedConnective, Syntactical.
If the definition above overwhelms you, don't worry: it will be clearer later. For now we are simply interested in understanding that Connectives are simply symbols used to concatenate other logical constructs with each other.
Later, we will see some interesting example about how to equip these symbols with semantics, that is, what rules should be applied when interpreting connectives in a generic Formula. We will also understand how to define our own custom connectives.
SoleLogics.arity — Methodarity(φ::SyntaxTree)::Integer
arity(tok::Connective)::IntegerReturn the arity of a Connective or a SyntaxTree. The arity is an integer representing the number of allowed children for a node in a tree. Connectives with arity equal to 0, 1 or 2 are called nullary, unary and binary, respectively. SyntaxLeafs (Atoms and Truth values) are always nullary.
See also SyntaxLeaf, Connective, SyntaxBranch.
The vast majority of data structures involved in encoding a logical formula, are children of the Formula abstract type. When such data structures purely represents tree-shaped data structures (or single nodes in them), then they are also children of the SyntaxStructure abstract type.
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.
The following methods define Formula interface.
SoleLogics.tree — Methodtree(φ::Formula)::SyntaxTreeReturn the SyntaxTree representation of a formula; note that this is equivalent to Base.convert(SyntaxTree, φ).
See also Formula, SyntaxTree.
SoleLogics.height — Methodheight(φ::Formula)::IntReturn the height of a formula, in its syntax tree representation.
See also SyntaxTree.
SoleLogics.tokens — Methodtokens(φ::Formula)::AbstractVector{<:SyntaxToken}
atoms(φ::Formula)::AbstractVector{<:Atom}
truths(φ::Formula)::AbstractVector{<:Truth}
leaves(φ::Formula)::AbstractVector{<:SyntaxLeaf}
connectives(φ::Formula)::AbstractVector{<:Connective}
operators(φ::Formula)::AbstractVector{<:Operator}
ntokens(φ::Formula)::Integer
natoms(φ::Formula)::Integer
ntruths(φ::Formula)::Integer
nleaves(φ::Formula)::Integer
nconnectives(φ::Formula)::Integer
noperators(φ::Formula)::IntegerReturn the list/number of (non-unique) SyntaxTokens, Atoms, etc... appearing in a formula.
See also Formula, SyntaxToken.
Now, let us see how to compose syntax elements, to express more complex concepts.
SoleLogics.composeformulas — Methodcomposeformulas(c::Connective, φs::NTuple{N,F})::F where {N,F<:Formula}Return a new formula of type F by composing N formulas of the same type via a connective c. This function allows one to use connectives for flexibly composing formulas (see Implementation section).
Examples
julia> f = parseformula("◊(p→q)");
julia> p = Atom("p");
julia> ∧(f, p) # Easy way to compose a formula
SyntaxBranch: ◊(p → q) ∧ p
julia> f ∧ ¬p # Leverage infix notation ;) (see https://stackoverflow.com/a/60321302/5646732)
SyntaxBranch: ◊(p → q) ∧ ¬p
julia> ∧(f, p, ¬p) # Shortcut for ∧(f, ∧(p, ¬p))
SyntaxBranch: ◊(p → q) ∧ p ∧ ¬pImplementation
Upon composeformulas lies a flexible way of using connectives for composing formulas and syntax tokens (e.g., atoms), given by methods like the following:
function (c::Connective)(φs::NTuple{N,Formula}) where {N}
...
endThese allow composing formulas as in ∧(f, ¬p), and in order to access this composition with any newly defined subtype of Formula, a new method for composeformulas should be defined, together with promotion from/to other Formulas should be taken care of (see here and here).
Similarly, for allowing a (possibly newly defined) connective to be applied on a number of syntax tokens/formulas that differs from its arity, for any newly defined connective c, new methods similar to the two above should be defined. For example, although ∧ and ∨ are binary, (i.e., have arity equal to 2), compositions such as ∧(f, f2, f3, ...) and ∨(f, f2, f3, ...) can be done thanks to the following two methods that were defined in SoleLogics:
function ∧(
c1::Formula,
c2::Formula,
c3::Formula,
cs::Formula...
)
return ∧(c1, ∧(c2, c3, cs...))
end
function ∨(
c1::Formula,
c2::Formula,
c3::Formula,
cs::Formula...
)
return ∨(c1, ∨(c2, c3, cs...))
endTo allow for the composition of Formulas of different types, promotion rules should be provided.
See also Formula, Connective.
We are ready to see how logical formulas are represented using syntax trees
SoleLogics.SyntaxStructure — Typeabstract type SyntaxStructure <: Formula endAbstract type for the purely-syntactic component of a logical formula (e.g., no fancy memoization structure associated). The typical representation is the SyntaxTree, however, different implementations can cover specific syntactic forms (e.g., conjunctive or disjunctive normal forms).
Interface
- See also
Formula
See also Formula, AbstractLogic, SyntaxTree, tree.
SoleLogics.SyntaxTree — Typeabstract type SyntaxTree <: SyntaxStructure endAbstract type for syntax trees; that is, syntax leaves (see SyntaxLeaf, such as Truth values and Atoms), and their composition via Connectives (i.e., SyntaxBranch).
Note that SyntaxTree are ranked trees, and (should) implement AbstractTrees interface.
Interface
children(φ::SyntaxTree)::NTuple{N,SyntaxTree} where Ntoken(φ::SyntaxTree)::Connective- See also
SyntaxStructure
Utility functions
tokentype(φ::SyntaxTree)arity(φ::SyntaxTree)::Int
Other utility functions (requiring a walk of the tree)
Base.in(tok::SyntaxToken, φ::SyntaxTree)::Boolheight(φ::SyntaxTree)::Inttokens(φ::SyntaxTree)::AbstractVector{<:SyntaxToken}atoms(φ::SyntaxTree)::AbstractVector{<:AbstractAtom}truths(φ::SyntaxTree)::AbstractVector{<:Truth}leaves(φ::SyntaxTree)::AbstractVector{<:SyntaxLeaf}connectives(φ::SyntaxTree)::AbstractVector{<:Connective}operators(φ::SyntaxTree)::AbstractVector{<:Operator}ntokens(φ::SyntaxTree)::Intnatoms(φ::SyntaxTree)::Intntruths(φ::SyntaxTree)::Intnleaves(φ::SyntaxTree)::Intnconnectives(φ::SyntaxTree)::Intnoperators(φ::SyntaxTree)::Inttokenstype(φ::SyntaxTree)atomstype(φ::SyntaxTree)truthstype(φ::SyntaxTree)leavestype(φ::SyntaxTree)connectivestype(φ::SyntaxTree)operatorstype(φ::SyntaxTree)composeformulas(c::Connective, φs::NTuple{N,SyntaxTree})
See also SyntaxLeaf, SyntaxBranch, SyntaxStructure, Formula.
AbstractTrees.children — Methodchildren(φ::SyntaxTree)Return the immediate children of a syntax tree.
See also SyntaxBranch, SyntaxLeaf.
SoleLogics.token — Methodtoken(φ::SyntaxTree)::SyntaxTokenReturn the token at the root of a syntax tree.
See also SyntaxBranch, SyntaxLeaf.
SoleLogics.SyntaxLeaf — Typeabstract type SyntaxLeaf <: SyntaxStructure endAn atomic logical element, like a Truth value or an Atom. SyntaxLeafs have arity equal to zero, meaning that they are not allowed to have children in tree-like syntactic structures.
Interface
syntaxstring(s::SyntaxLeaf; kwargs...)::Stringdual(s::SyntaxLeaf)::SyntaxLeafhasdual(s::SyntaxLeaf)::Bool
See also SyntaxStructure, arity, SyntaxBranch.
SoleLogics.SyntaxToken — Typeconst SyntaxToken = Union{Connective,SyntaxLeaf}Union type for values wrapped in SyntaxTree nodes.
See also SyntaxTree, SyntaxLeaf, Connective.
SoleLogics.dual — Methoddual(tok::SyntaxToken)Return the dual of a syntax token.
If tok is an Operator of arity n, the dual dtok is such that, on a Boolean algebra, tok(ch_1, ..., ch_n) ≡ ¬dtok(¬ch_1, ..., ¬ch_n).
Duality can be used to perform syntactic simplifications on formulas. For example, since ∧ and ∨ are duals, ¬(¬p ∧ ¬q) can be simplified to (p ∧ q) (De Morgan's law). Duality also applies to operators with existential/universal semantics (◊/□), to Truth values (⊤/⊥), and to Atoms.
Implementation
When providing a dual for an operator of type O, please also provide:
hasdual(::O) = trueThe dual of an Atom (that is, the atom with inverted semantics) is defined as:
dual(p::Atom{V}) where {V} = Atom(dual(value(p)))As such, hasdual(::V) and dual(::V) should be defined when wrapping objects of type A.
See also normalize, SyntaxToken.
Base.in — MethodBase.in(tok::SyntaxToken, φ::Formula)::BoolReturn whether a syntax token appears in a formula.
See also Formula, SyntaxToken.
SoleLogics.Atom — Typestruct Atom{V} <: AbstractAtom
value::V
endSimplest atom implementation, wrapping a value.
See also AbstractAtom, value, check, SyntaxToken.
SoleLogics.Truth — Typeabstract type Truth <: SyntaxLeaf endAbstract type for syntax leaves representing values of a lattice algebra. In Boolean logic, the two BooleanTruth values TOP (⊤) and BOT (⊥) are used.
See also BooleanTruth.
Interface
syntaxstring(s::Truth; kwargs...)::Stringdual(s::Truth)::Truthhasdual(s::Truth)::Boolistop(t::Truth)::Boolisbot(t::Truth)::Boolprecedes(t1::Truth, t2::Truth)::Booltruthmeet(t1::Truth, t2::Truth)::Truthtruthjoin(t1::Truth, t2::Truth)::Truth
Implementation
A three-valued algebra, that is, an algebra with three truth values (top, bottom and unknown), can be based on the following Truth value definitions:
import SoleLogics: precedes
abstract type ThreeVTruth <: Truth end
struct ThreeTop <: ThreeVTruth end
const ⫪ = ThreeTop() # Note that ⊤ is already use to indicate BooleanTruth's top.
syntaxstring(::ThreeTop; kwargs...) = "⫪"
struct ThreeBot <: ThreeVTruth end
const ⫫ = ThreeBot() # Note that ⊥ is already use to indicate BooleanTruth's top.
syntaxstring(::ThreeBot; kwargs...) = "⫫"
struct ThreeUnknown <: ThreeVTruth end
const υ = ThreeUnknown()
syntaxstring(::ThreeUnknown; kwargs...) = "υ"
istop(t::ThreeTop) = true
isbot(t::ThreeBot) = true
precedes(::ThreeBot, ::ThreeTop) = true
precedes(::ThreeBot, ::ThreeUnknown) = true
precedes(::ThreeUnknown, ::ThreeTop) = true
precedes(::ThreeTop, ::ThreeBot) = false
precedes(::ThreeUnknown, ::ThreeBot) = false
precedes(::ThreeTop, ::ThreeUnknown) = falseNote that precedes is used to define the (partial) order between Truth values.
See also Connective, BooleanTruth.
Similarly to the Connectives case, Truth explanation could be unfamiliar at first sight. At the moment, what is of our interest is that SoleLogics provides us a simple interface to create custom, complex at will, algebras without worrying about adapting all the underlying algorithms (e.g., formulas generation, parsing, model checking etc.).
SoleLogics.istop — Methodistop(::Truth)::BoolReturn true if the Truth value is the top of its algebra. For example, in the crisp case, with Bool truth values, it is:
istop(t::Bool)::Bool = (t == true)SoleLogics.isbot — Methodisbot(::Truth)::BoolReturn true if the Truth value is the bottom of its algebra. For example, in the crisp case, with Bool truth values, it is:
isbot(t::Bool)::Bool = (t == false)The union of Connectives and Truth values are exactly what is called logical operators, or simply Operator. In SoleLogics, logical operators are splitted in two parts to highlight some differences that always holds (e.g., Truth values arity is always 0, while Connectives arity is always greater than 0); apart from this technical decision, many dispatches are defined using the more general union type Operator.
SoleLogics.Operator — Typeconst Operator = Union{Connective,Truth}Union type for logical constants of any ariety (zero for Truth values, non-zero for Connectives).
See also Connective, Truth.
An Operator can be used to compose syntax tokens (e.g., Atoms), SyntaxTrees and/or Formulas.
¬(Atom(1)) ∨ Atom(1) ∧ ⊤
∧(⊤,⊤)
⊤()The internal nodes in a SyntaxTree definitely have ariety greater than 0, and thus, cannot wrap Atoms nor Truth values. To clearly distinguish internal nodes and leaves, the SyntaxBranch type is defined, making each SyntaxTree arity-complaint.
SoleLogics.SyntaxBranch — Typestruct SyntaxBranch <: AbstractSyntaxBranch
token::Connective
children::NTuple{N,SyntaxTree} where {N}
endSimple implementation of a syntax branch. The implementation is arity-compliant, in that, upon construction, the arity of the token is checked against the number of children provided.
Examples
julia> p,q = Atom.([p, q])
2-element Vector{Atom{String}}:
Atom{String}: p
Atom{String}: q
julia> branch = SyntaxBranch(CONJUNCTION, p, q)
SyntaxBranch: p ∧ q
julia> token(branch)
∧
julia> syntaxstring.(children(branch))
(p, q)
julia> ntokens(a) == nconnectives(a) + nleaves(a)
true
julia> arity(a)
2
julia> height(a)
1See also token, children, arity, Connective, height, atoms, natoms, operators, noperators, tokens, ntokens,
Semantics Basics
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.interpret — Methodinterpret(
φ::Formula,
i::AbstractInterpretation,
args...;
kwargs...
)::FormulaReturn the truth value for a formula on a logical interpretation (or model).
Examples
julia> @atoms p q
2-element Vector{Atom{String}}:
p
q
julia> td = TruthDict([p => true, q => false])
TruthDict with values:
┌────────┬────────┐
│ q │ p │
│ String │ String │
├────────┼────────┤
│ ⊥ │ ⊤ │
└────────┴────────┘
julia> interpret(CONJUNCTION(p,q), td)
⊥See also check, Formula, AbstractInterpretation, AbstractAlgebra.
SoleLogics.check — Methodcheck(
φ::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.