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
endAn atom, or its negation.
See also CNF, DNF, SyntaxStructure.
Linear Forms
SoleLogics.LeftmostLinearForm — Typestruct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure
grandchildren::Vector{<:SS}
endA 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 Connectives are all CONJUNCTIONs.
See also SyntaxStructure, Connective, LeftmostLinearForm, CONJUNCTION.
SoleLogics.LeftmostDisjunctiveForm — TypeLeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS}Specific instantiation of a LeftmostLinearForm, where Connectives are all DISJUNCTIONs.
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
endA 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)::SyntaxStructureReturn 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...
)::AnchoredFormulaReturn 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...
)::AtomSynonym 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}: 4See also natoms, AbstractAlphabet.
randatom(
[rng::Union{Random.AbstractRNG,Integer},]
a::UnionAlphabet;
atompicking_mode::Symbol=:uniform,
subalphabets_weights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing
)::AtomSample 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 (seeAbstractWeightsof 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 = :maxheightconstrains 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 tomaxheightand 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...
)::FParse 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 syntaxstrings will be used for parsing them. Note that, in case of clashing syntaxstrings, the provided additional operators will override the standard ones.
When parsing SyntaxTrees, 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 theAtomitself;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, syntaxstrings 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
)::SyntaxTreeReturn 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 itsdualatom.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)::BoolReturn 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)
trueSee also isgrounding), SyntaxTree), Formula.