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
Syntactical
Connective
(e.g., ∧, ∨, ¬, →)Formula
SyntaxStructure
SyntaxTree
(e.g., ¬p ∧ q → s)SyntaxLeaf
SyntaxBranch
(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 end
Master 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...)::String
Return 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., SyntaxLeaf
s, that is, Atom
s and Truth
values, and Operator
s), in a way that it produces a unique string representation, since Base.hash
and Base.isequal
, at least for SyntaxTree
s, rely on it.
In particular, for the case of Atom
s, 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, syntaxstring
s should not contain parentheses ('('
, ')'
), and, when parsing in function notation, commas (','
).
See also SyntaxLeaf
, Operator
, parseformula
.
SoleLogics.Connective
— Typeabstract type Connective <: Syntactical end
Abstract 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)::Int
iscommutative(::Connective)::Bool
precedence(::Connective)::Int
associativity(::Connective)::Symbol
collatetruth(::Connective, ::NTuple{N,Truth})::Truth
simplify(::Connective, ::NTuple{N,SyntaxTree})::SyntaxTree
dual(s::Connective)::Connective
hasdual(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) = 2
When 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 : t1
Beware 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 Connective
s 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)::Integer
Return 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. Connective
s with arity
equal to 0, 1 or 2 are called nullary
, unary
and binary
, respectively. SyntaxLeaf
s (Atom
s 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 end
Abstract type for logical formulas. Examples of Formula
s are SyntaxLeaf
s (for example, Atom
s and Truth
values), SyntaxStructure
s (for example, SyntaxTree
s and LeftmostLinearForm
s) and TruthTable
s ( 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)::SyntaxTree
composeformulas(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)::Bool
height(φ::Formula)::Int
tokens(φ::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)::Int
natoms(φ::Formula)::Int
ntruths(φ::Formula)::Int
nleaves(φ::Formula)::Int
nconnectives(φ::Formula)::Int
noperators(φ::Formula)::Int
See also tree
, SyntaxStructure
, SyntaxLeaf
.
The following methods define Formula
interface.
SoleLogics.tree
— Methodtree(φ::Formula)::SyntaxTree
Return the SyntaxTree
representation of a formula; note that this is equivalent to Base.convert(SyntaxTree, φ)
.
See also Formula
, SyntaxTree
.
SoleLogics.height
— Methodheight(φ::Formula)::Int
Return 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)::Integer
Return the list/number of (non-unique) SyntaxToken
s, Atom
s, 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 ∧ ¬p
Implementation
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}
...
end
These 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 Formula
s 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...))
end
To allow for the composition of Formula
s 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 end
Abstract 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 end
Abstract type for syntax trees; that is, syntax leaves (see SyntaxLeaf
, such as Truth
values and Atom
s), and their composition via Connective
s (i.e., SyntaxBranch
).
Note that SyntaxTree
are ranked trees, and (should) implement AbstractTrees
interface.
Interface
children(φ::SyntaxTree)::NTuple{N,SyntaxTree} where N
token(φ::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)::Bool
height(φ::SyntaxTree)::Int
tokens(φ::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)::Int
natoms(φ::SyntaxTree)::Int
ntruths(φ::SyntaxTree)::Int
nleaves(φ::SyntaxTree)::Int
nconnectives(φ::SyntaxTree)::Int
noperators(φ::SyntaxTree)::Int
tokenstype(φ::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)::SyntaxToken
Return the token at the root of a syntax tree.
See also SyntaxBranch
, SyntaxLeaf
.
SoleLogics.SyntaxLeaf
— Typeabstract type SyntaxLeaf <: SyntaxStructure end
An atomic logical element, like a Truth
value or an Atom
. SyntaxLeaf
s have arity
equal to zero, meaning that they are not allowed to have children in tree-like syntactic structures.
Interface
syntaxstring(s::SyntaxLeaf; kwargs...)::String
dual(s::SyntaxLeaf)::SyntaxLeaf
hasdual(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 dual
s, ¬(¬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 Atom
s.
Implementation
When providing a dual
for an operator of type O
, please also provide:
hasdual(::O) = true
The 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)::Bool
Return whether a syntax token appears in a formula.
See also Formula
, SyntaxToken
.
SoleLogics.Atom
— Typestruct Atom{V} <: AbstractAtom
value::V
end
Simplest atom implementation, wrapping a value
.
See also AbstractAtom
, value
, check
, SyntaxToken
.
SoleLogics.Truth
— Typeabstract type Truth <: SyntaxLeaf end
Abstract 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...)::String
dual(s::Truth)::Truth
hasdual(s::Truth)::Bool
istop(t::Truth)::Bool
isbot(t::Truth)::Bool
precedes(t1::Truth, t2::Truth)::Bool
truthmeet(t1::Truth, t2::Truth)::Truth
truthjoin(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) = false
Note that precedes
is used to define the (partial) order between Truth
values.
See also Connective
, BooleanTruth
.
Similarly to the Connective
s 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)::Bool
Return 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)::Bool
Return 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 Connective
s 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 Connective
s 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 Connective
s).
See also Connective
, Truth
.
An Operator
can be used to compose syntax tokens (e.g., Atom
s), SyntaxTree
s and/or Formula
s.
¬(Atom(1)) ∨ Atom(1) ∧ ⊤
∧(⊤,⊤)
⊤()
The internal nodes in a SyntaxTree
definitely have ariety
greater than 0
, and thus, cannot wrap Atom
s 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}
end
Simple 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)
1
See also token
, children
, arity
, Connective
, height
, atoms
, natoms
, operators
, noperators
, tokens
, ntokens
,
Semantics Basics
SoleLogics.AbstractInterpretation
— Typeabstract type AbstractInterpretation end
Abstract 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 check
ed 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...
)::Formula
Return 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...
)::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
.