More on Formulas
In this chapter, you are going to learn more on Formula
representations that are alternative to syntax trees. As you will see, for example, formulas with specific structure (e.g., normal forms) can be represented in ways that make them more easy to handle, and can lead to great benefits in terms of both computational and memory load.
We proceed by presenting the random formulae generation engine, parsing and some utility function.
Recalling the type hierarchy presented in man-core, it is here enriched with the following new types and structures.
Literals
SoleLogics.Literal
— Typestruct Literal{T<:SyntaxLeaf} <: SyntaxStructure
ispos::Bool
atom::T
end
An atom, or its negation.
See also CNF
, DNF
, SyntaxStructure
.
Linear Forms
SoleLogics.LeftmostLinearForm
— Typestruct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure
grandchildren::Vector{<:SS}
end
A syntax structure representing the foldl
of a set of other syntax structure of type SS
by means of a connective C
. This structure enables a structured instantiation of formulas in conjuctive/disjunctive forms, and conjuctive normal form (CNF) or disjunctive normal form (DNF), defined as:
const LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS}
const LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS}
const CNF{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),LeftmostLinearForm{typeof(∨),SS}}
const DNF{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),LeftmostLinearForm{typeof(∧),SS}}
Examples
julia> LeftmostLinearForm(→, parseformula.(["p", "q", "r"]))
LeftmostLinearForm{SoleLogics.NamedConnective{:→},Atom{String}}
"(p) → (q) → (r)"
julia> LeftmostConjunctiveForm(parseformula.(["¬p", "q", "¬r"]))
LeftmostLinearForm{SoleLogics.NamedConnective{:∧},SyntaxTree}
"(¬p) ∧ (q) ∧ (¬r)"
julia> LeftmostDisjunctiveForm{Literal}([Literal(false, Atom("p")), Literal(true, Atom("q")), Literal(false, Atom("r"))])
LeftmostLinearForm{SoleLogics.NamedConnective{:∨},Literal}
"(¬p) ∨ (q) ∨ (¬r)"
julia> LeftmostDisjunctiveForm([LeftmostConjunctiveForm(parseformula.(["¬p", "q", "¬r"]))]) isa DNF
true
julia> conj = LeftmostConjunctiveForm(@atoms p q)
LeftmostConjunctiveForm with 2 Atom{String} grandchildren:
p
q
julia> tree(conj)
SyntaxBranch: p ∧ q
julia> nconj = NEGATION(conj)
LeftmostLinearForm with connective ¬ and 1 LeftmostConjunctiveForm{Atom{String}} grandchildren:
(p) ∧ (q)
julia> tree(nconj)
SyntaxBranch: ¬(p ∧ q)
julia> tree(nconj ∧ nconj)
SyntaxBranch: ¬(p ∧ q) ∧ ¬(p ∧ q)
See also SyntaxStructure
, SyntaxTree
, LeftmostConjunctiveForm
, LeftmostDisjunctiveForm
, Literal
.
SoleLogics.LeftmostConjunctiveForm
— TypeLeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS}
Specific instantiation of a LeftmostLinearForm
, where Connective
s are all CONJUNCTION
s.
See also SyntaxStructure
, Connective
, LeftmostLinearForm
, CONJUNCTION
.
SoleLogics.LeftmostDisjunctiveForm
— TypeLeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS}
Specific instantiation of a LeftmostLinearForm
, where Connective
s are all DISJUNCTION
s.
See also SyntaxStructure
, Connective
, LeftmostLinearForm
, DISJUNCTION
.
SoleLogics.CNF
— TypeCNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}}
Conjunctive Normal Form of an SyntaxStructure
.
See also SyntaxStructure
, LeftmostConjunctiveForm
, LeftmostDisjunctiveForm
, CONJUNCTION
, DISJUNCTION
.
SoleLogics.DNF
— TypeDNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostConjunctiveForm{SS}}
Disjunctive Normal Form of an SyntaxStructure
.
See also SyntaxStructure
, LeftmostConjunctiveForm
, LeftmostDisjunctiveForm
, CONJUNCTION
, DISJUNCTION
.
<!–
SoleLogics.AnchoredFormula
— Typestruct AnchoredFormula{L<:AbstractLogic} <: Formula
_logic::Base.RefValue{L}
synstruct::SyntaxStructure
end
A formula anchored to a logic of type L
, and wrapping a syntax structure. The structure encodes a formula belonging to the grammar of the logic, and the truth of the formula can be evaluated on interpretations of the same logic. Note that, here, the logic is represented by a reference.
Upon construction, the logic can be passed either directly, or via a RefValue. Additionally, the following keyword arguments may be specified:
check_atoms::Bool = false
: whether to perform or not a check that the atoms belong to the alphabet of the logic;check_tree::Bool = false
: whether to perform or not a check that the formula's syntactic structure honors the grammar (includes the check performed withcheck_atoms = true
);
Cool feature: a AnchoredFormula
can be used for instating other formulas of the same logic. See the examples.
Examples
julia> φ = parseformula(AnchoredFormula, "◊(p→q)");
julia> f2 = φ(parseformula("p"));
julia> syntaxstring(φ)
"◊(→(p, q))"
julia> syntaxstring(f2)
"p"
julia> @assert logic(φ) == logic(f2)
julia> @assert ◊ in operators(logic(f2))
julia> @assert ◊ isa operatorstype(logic(f2))
See also AbstractLogic
, logic
, SyntaxToken
, SyntaxBranch
, tree
.
SoleLogics.logic
— MethodSoleLogics.synstruct
— Methodsynstruct(φ::AnchoredFormula)::SyntaxStructure
Return the syntactic component of an anchored formula.
See AnchoredFormula
.
SoleLogics.baseformula
— Methodfunction baseformula(
φ::Formula;
infer_logic = true,
additional_operators::Union{Nothing,Vector{<:Operator}} = nothing,
kwargs...
)
Attempt at instantiating a AnchoredFormula
from a syntax token/formula, by inferring the logic it belongs to. If infer_logic
is true, then a canonical logic (e.g., propositional logic with all the BASE_PROPOSITIONAL_CONNECTIVES
) is inferred; if it's false, then a logic with exactly the operators appearing in the syntax tree, plus the additional_operators
is instantiated.
Examples
julia> t = parseformula("◊((p∧q)→r)");
julia> unique(operators(logic(SoleLogics.baseformula(t))))
3-element Vector{Union{SoleLogics.NamedConnective{:→}, SoleLogics.NamedConnective{:◊}, SoleLogics.NamedConnective{:∧}}}:
∧
◊
→
julia> unique(operators(logic(SoleLogics.baseformula(t; additional_operators = SoleLogics.BASE_MODAL_CONNECTIVES))))
8-element Vector{Union{SoleLogics.BottomOperator, SoleLogics.NamedConnective{:¬}, SoleLogics.NamedConnective{:∧}, SoleLogics.NamedConnective{:∨}, SoleLogics.NamedConnective{:→}, SoleLogics.NamedConnective{:◊}, SoleLogics.NamedConnective{:□}, SoleLogics.TopOperator}}:
¬
∧
∨
→
◊
□
SoleLogics.parseformula
— Functionparseformula(
T::Type{AnchoredFormula},
expr::AbstractString,
additional_operators::Union{Nothing,Vector{<:Operator}} = nothing;
operators::Union{Nothing,Vector{<:Operator}},
grammar::Union{Nothing,AbstractGrammar} = nothing,
algebra::Union{Nothing,AbstractAlgebra} = nothing,
kwargs...
)::AnchoredFormula
Return a AnchoredFormula
which is the result of parsing an expression via the Shunting yard algorithm. By default, this function is only able to parse operators in SoleLogics.BASE_PARSABLE_CONNECTIVES
; additional operators may be provided as a second argument.
The grammar
and algebra
of the associated logic is inferred using the baseformula
function from the operators encountered in the expression, and those in additional_operators
.
–>
Random sampling and generation
Base.rand
— MethodBase.rand(
[rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,]
alphabet::AbstractAlphabet,
args...;
kwargs...
)::Atom
Synonym for randatom(::AbstractAlphabet)
.
Randomly generate an Atom
from a finite AbstractAlphabet
according to a uniform distribution.
See also [AbstractAlphabet
], randatom(::AbstractAlphabet)
.
SoleLogics.randatom
— Functionrandatom(
[rng::Union{Random.AbstractRNG,Integer},]
a::AbstractAlphabet,
args...;
kwargs...
)
Randomly generate an Atom
from a finite AbstractAlphabet
according to a uniform distribution.
Examples
julia> alphabet = ExplicitAlphabet(1:5)
ExplicitAlphabet{Int64}(Atom{Int64}[Atom{Int64}: 1, Atom{Int64}: 2, Atom{Int64}: 3, Atom{Int64}: 4, Atom{Int64}: 5])
julia> randatom(42, alphabet)
Atom{Int64}: 4
See also natoms
, AbstractAlphabet
.
randatom(
[rng::Union{Random.AbstractRNG,Integer},]
a::UnionAlphabet;
atompicking_mode::Symbol=:uniform,
subalphabets_weights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing
)::Atom
Sample an atom from a UnionAlphabet
. By default, the sampling is uniform with respect to the atoms.
By setting atompicking_mode = :uniform_subalphabets
one can force a uniform sampling with respect to the sub-alphabets.
Moreover, one can specify a :weighted
atompicking_mode
, together with a subalphabets_weights
vector.
Examples
julia> alphabet1 = ExplicitAlphabet(Atom.(1:10));
julia> alphabet2 = ExplicitAlphabet(Atom.(11:20));
julia> union_alphabet = UnionAlphabet([alphabet1, alphabet2]);
julia> randatom(42, union_alphabet)
Atom{Int64}: 11
julia> randatom(42, union_alphabet; atompicking_mode=:uniform_subalphabets)
Atom{Int64}: 11
julia> for i in 1:10
randatom(
union_alphabet;
atompicking_mode=:weighted,
subalphabets_weights=[0.8,0.2]
) |> syntaxstring |> vcat |> print
end
["6"]["3"]["10"]["7"]["2"]["2"]["6"]["9"]["20"]["16"]
See also UnionAlphabet
.
StatsBase.sample
— Methodfunction StatsBase.sample(
[rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,]
alphabet::AbstractAlphabet,
weights::AbstractWeights,
args...;
kwargs...
)
Sample an Atom
from an alphabet
, with probabilities proportional to the weights given in weights
.
See also AbstractAlphabet
, AbstractWeights
, Atom
.
SoleLogics.randformula
— Functionfunction randformula(
[T::Type{<:Formula}=SyntaxTree,]
[rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,]
maxheight::Integer,
alphabet::Union{AbstractVector,AbstractAlphabet},
operators::AbstractVector{<:Operator},
args...;
maxmodaldepth::Integer=maxheight,
atompicker::Union{Nothing,Function,AbstractWeights,AbstractVector{<:Real}}=randatom,
opweights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing,
alphabet_sample_kwargs::Union{Nothing,AbstractVector}=nothing,
kwargs...
)
Return a pseudo-randomic formula of type T
.
Arguments
rng::Union{Intger,AbstractRNG}=Random.GLOBAL_RNG
: random number generator;maxheight::Integer
: maximum height of the generated structure;alphabet::AbstractAlphabet
: collection from which atoms are chosen randomly;operators::AbstractVector{<:Operator}
: vector from which legal operators are chosen.
Keyword Arguments
maxmodaldepth::Integer
: maximum modal depth;atompicker::Union{Nothing,Function,AbstractWeights,AbstractVector{<:Real}}
: method used to pick a random element. For example, this could be Base.rand, StatsBase.sample or an array of integers or an array ofStatsBase.AbstractWeights
;opweights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}
: operators are sampled with probabilities proportional to this vector (seeAbstractWeights
of StatsBase package).alphabet_sample_kwargs::AbstractVector
: pool of atoms to pull from if the given alphabet is not finite.basecase::Function
= method to specify the base case of the recursion; if not specified, it returnsatompicker
.
[!WARNING] The basecase is applied at the end of the recustion (i.e., when height = 0). If introducting a basecase which produces a subformula, please adjust the maxheight
parameter value accordingly (e.g., when producing a subformula of the type o(p,q), where o
is a connective and p,q
are atoms, to generate a formula of maxheight n
provide a value of n-1
for the maxheight
parameter).
mode::Bool = :maxheight
constrains the generated syntax tree to having a height smaller or equal tomaxheight
(mode = :maxheight
), to having height equal tomaxheight
(mode = :exactheight
), or to having height equal tomaxheight
and being full (mode = :full
),.earlystoppingtreshold::Float
: whenmode = :maxheight
, controls the probability of calling the basecase before reaching.
Examples
julia> syntaxstring(randformula(4, ExplicitAlphabet([1,2]), [NEGATION, CONJUNCTION, IMPLICATION]))
"¬((¬(¬(2))) → ((1 → 2) → (1 → 2)))"
See also AbstractAlphabet
, AbstractWeights
, Atom
, Operator
, SyntaxBranch
, SyntaxTree
.
function randformula(
[T::Type{<:Formula}=SyntaxTree,]
[rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,]
maxheight::Integer,
[g::AbstractGrammar,]
args...;
kwargs...
)
Fallback to randformula
, specifying only the maxheight
(possibly also a grammar
) of the generated SyntaxTree
.
See also AbstractGrammar
, randformula(::Integer, ::Union{AbstractVector,AbstractAlphabet}, ::AbstractVector)
.
Parsing
SoleLogics.BASE_PARSABLE_CONNECTIVES
— Constantconst BASE_PARSABLE_CONNECTIVES = SoleLogics.Syntactical[¬, ∧, ∨, →, ◊, □, ⟨G⟩, [G], ⟨=⟩, [=], ⊤, ⊥]
Vector of (standard) operators that are automatically taken care of when parsing. These are ¬, ∧, ∨, →, ◊, □, ⟨G⟩, [G], ⟨=⟩, [=], ⊤ and ⊥.
See also parseformula
.
SoleLogics.parseformula
— Functionparseformula(
[F::Type{<:Formula}=SyntaxTree],
expr::AbstractString,
additional_operators::Union{Nothing,AbstractVector} = nothing;
kwargs...
)::F
parseformula(
F::Type{<:SyntaxTree},
expr::AbstractString,
logic::AbstractLogic;
kwargs...
)::F
Parse a formula of type F
from a string expression (its syntaxstring
).
By default, this function is only able to parse operators in SoleLogics.BASE_PARSABLE_CONNECTIVES
(e.g., ¬, ∧, ∨ and →); additional, non-standard operators may be provided as a vector additional_operators
, and their syntaxstring
s will be used for parsing them. Note that, in case of clashing syntaxstring
s, the provided additional operators will override the standard ones.
When parsing SyntaxTree
s, the Shunting yard algorithm is used, and the method allows the following keywords arguments.
Keyword Arguments
function_notation::Bool = false
: if set totrue
, the expression is considered in function notation (e.g.,"⨁(arg1, arg2)"
); otherwise, it is considered in infix notation (e.g.,"arg1 ⨁ arg2"
);atom_parser::Base.Callable = Atom{String}
: a callable to be used for parsing atoms, once they are recognized in the expression. It must return the atom, or theAtom
itself;additional_whitespaces::Vector{Char} = Char[]
: characters to be stripped out from each syntax token. For example, if'@' in additional_whitespaces
, "¬@p@" is parsed just as "¬p".opening_parenthesis::String = "("
: the string signaling the opening of an expression block;closing_parenthesis::String = ")"
: the string signaling the closing of an expression block;arg_delim::String = ","
: whenfunction_notation = true
, the string that delimits the different arguments of a function call.
For a proper functioning, the syntaxstring
of any syntax token cannot be prefixed/suffixed by whitespaces. For example, for any operator ⨁
, it should hold that syntaxstring(⨁) == strip(syntaxstring(⨁))
. Also, syntaxstring
s cannot contain special symbols (opening_parenthesis
, closing_parenthesis
, and arg_delim
) as substrings.
Examples
julia> syntaxstring(parseformula("¬p∧q∧(¬s∧¬z)"))
"¬p ∧ q ∧ ¬s ∧ ¬z"
julia> syntaxstring(parseformula("∧(¬p,∧(q,∧(¬s,¬z)))", function_notation=true))
"¬p ∧ q ∧ ¬s ∧ ¬z"
julia> syntaxstring(parseformula("¬1→0"; atom_parser = (x -> Atom{Float64}(parse(Float64, x)))))
"(¬1.0) → 0.0"
For any Formula
type F
, this function should be the inverse of syntaxstring
; that is, if φ::F
then the following should hold, for at least some args
, and for every kwargs
allowing correct parsing: φ == parseformula(F, syntaxstring(φ, args...; kwargs...), args...; kwargs...)
.
See also SyntaxTree
, BASE_PARSABLE_CONNECTIVES
, syntaxstring
.
Utilities
SoleLogics.treewalk
— Methodtreewalk(
st::SyntaxTree,
args...;
rng::AbstractRNG = Random.GLOBAL_RNG,
criterion::Function = ntokens,
toleaf::Bool = true,
returnnode::Bool = false,
transformnode::Function = nothing
)::SyntaxTree
Return a subtree of syntax tree, by following these options:
criterion
: function used to compute the probability of stopping at a random node;returnnode
: true if only the subtree is to be returned;transformnode
: function that will be applied to the chosen subtree.
SoleLogics.subformulas
— Methodsubformulas(f::Formula; sorted=true)
Return all sub-formulas (sorted by size when sorted=true
) of a given formula.
Examples
julia> syntaxstring.(SoleLogics.subformulas(parseformula("◊((p∧q)→r)")))
6-element Vector{String}:
"p"
"q"
"r"
"p ∧ q"
"◊(p ∧ q)"
"(◊(p ∧ q)) → r"
See also SyntaxTree
), Formula
.
SoleLogics.normalize
— Methodnormalize(
f::Formula;
profile = :readability,
remove_boxes = nothing,
reduce_negations = true,
simplify_constants = true,
allow_atom_flipping = false,
prefer_implications = false,
remove_implications = false,
forced_negation_removal = nothing,
remove_identities = true,
unify_toones = true,
rotate_commutatives = true,
flip_atom = nothing,
)
Return a modified version of a given formula, that has the same semantics but different syntax. This is useful for simplifying formulas for readability, or when checking the truth of many (possibly semantically similar) formulas; for example, when performing model checking. The current implementation assumes the underlying algebra is Boolean!
Arguments
f::Formula
: when set totrue
, the formula;profile::Symbol
: possible values are :readability, which optimizes for qualitative simplicity for a human to understand, and :modelchecking, which optimizes model checking speed;remove_boxes::Bool
: remove all (non-relational and relational) box operators by using the equivalence ◊φ ≡ ¬□¬φ. Note: this assumes an underlying Boolean algebra.reduce_negations::Bool
: when set totrue
, attempts at reducing the number of negations by appling some transformation rules (e.g., De Morgan's laws). Note: this assumes an underlying Boolean algebra.allow_atom_flipping::Bool
: when set totrue
, together withreduce_negations=true
, this may cause the negation of an atom to be replaced with the itsdual
atom.flip_atom::Union{Nothing,Callable}
: when a callable is provided, it is used for deciding when to flip atoms.
Examples
julia> f = parseformula("□¬((p∧¬q)→r)∧⊤");
julia> syntaxstring(f)
"□¬((p ∧ ¬q) → r) ∧ ⊤"
julia> syntaxstring(SoleLogics.normalize(f; profile = :modelchecking, allow_atom_flipping = false))
"¬◊(q ∨ ¬p ∨ r)"
julia> syntaxstring(SoleLogics.normalize(f; profile = :readability, allow_atom_flipping = false))
"□(¬r ∧ p ∧ ¬q)"
See also SyntaxTree
), Formula
.
SoleLogics.isgrounded
— Methodisgrounded(f::Formula)::Bool
Return true
if the formula is grounded, that is, if it can be inferred from its syntactic structure that, given any frame-based model, the truth value of the formula is the same on every world.
Examples
julia> f = parseformula("⟨G⟩p → [G]q");
julia> syntaxstring(f)
"(⟨G⟩p) → ([G]q)"
julia> SoleLogics.isgrounded(f)
true
See also isgrounding
), SyntaxTree
), Formula
.