From 7ec36a2b5991ac8733a63d0ccfdcf5318a8cf720 Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Tue, 16 Jul 2024 01:35:54 +0000 Subject: [PATCH 01/90] CompatHelper: bump compat for Dictionaries to 0.4, (keep existing compat) --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index c0553bdd..3550c027 100644 --- a/Project.toml +++ b/Project.toml @@ -22,7 +22,7 @@ ThreadSafeDicts = "4239201d-c60e-5e0a-9702-85d713665ba7" [compat] AbstractTrees = "0.4" DataStructures = "0.18" -Dictionaries = "0.3" +Dictionaries = "0.3, 0.4" FunctionWrappers = "1" Graphs = "1.8" IterTools = "1" @@ -37,8 +37,8 @@ ThreadSafeDicts = "0.1.0" julia = "1" [extras] -InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" From 510d17b05952f7c225080cddbdd5de27e4f2139d Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:56:04 +0200 Subject: [PATCH 02/90] Separating interfaces from utils --- .JuliaFormatter.toml | 1 + Project.toml | 2 +- docs/TODO.md | 2 +- docs/src/getting-started.md | 6 +- docs/src/index.md | 2 +- docs/src/more-on-formulas.md | 12 +- src/SoleLogics.jl | 38 +- src/anchored-formula.jl | 18 +- src/base-logic.jl | 460 ------------- src/deprecate.jl | 7 + src/propositional-logic.jl | 6 +- src/syntax-utils.jl | 70 +- src/{ => types}/docstrings.jl | 0 src/types/interpretation.jl | 166 +++++ src/{logics.jl => types/logic.jl} | 320 +-------- src/{core.jl => types/syntactical.jl} | 816 +++++++++------------- src/utils.jl | 936 +++++++++++++++++++++++++- test/misc.jl | 6 +- 18 files changed, 1521 insertions(+), 1347 deletions(-) delete mode 100644 src/base-logic.jl rename src/{ => types}/docstrings.jl (100%) create mode 100644 src/types/interpretation.jl rename src/{logics.jl => types/logic.jl} (56%) rename src/{core.jl => types/syntactical.jl} (67%) diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml index 580b7511..1d1dd36a 100644 --- a/.JuliaFormatter.toml +++ b/.JuliaFormatter.toml @@ -1 +1,2 @@ style = "sciml" +trailing_comma = true diff --git a/Project.toml b/Project.toml index 07631399..30da58a0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SoleLogics" uuid = "b002da8f-3cb3-4d91-bbe3-2953433912b5" authors = ["Mauro MILELLA", "Giovanni PAGLIARINI", "Edoardo PONSANESI", "Alberto PAPARELLA", "Eduard I. STAN"] -version = "0.9.5" +version = "0.9.6" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" diff --git a/docs/TODO.md b/docs/TODO.md index a4fec12b..30e3b0fe 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -23,7 +23,7 @@ # Notes for Giovanni; # 1) many important definitions are introduced here: is this the correct place to explain them or it's better to just link the documentation? # 2) I'm still working on putting a modal_depth parameter in both randbaseformula and randformula -# 3) As stated in AbstractSyntaxStructure docstring, classically a logical formula is implemented using a tree structure (an AST) but other representations may exist: I guess we could add a "randnormalform" method +# 3) As stated in SyntaxStructure docstring, classically a logical formula is implemented using a tree structure (an AST) but other representations may exist: I guess we could add a "randnormalform" method # ## Generating random interpretations # Kripke structures generation is provided by gen_kstructure interface. As you can see from the documentation, internally it builds up a random directed graph structure (using some, possibly custom algorithm). The adjacency list obtained is enriched with informations to obtain a so called Kripke frame; this is done by taking each vertex in the list and # - converting it into a World structure; diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index e76eb3ae..7ece2f35 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -16,7 +16,7 @@ Please, feel free to use the following tree structures to orient yourself in the - [`Syntactical`](@ref) - [`Connective`](@ref) (e.g., ∧, ∨, ¬, →) - [`Formula`](@ref) - - [`AbstractSyntaxStructure`](@ref) + - [`SyntaxStructure`](@ref) - [`SyntaxTree`](@ref) (e.g., ¬p ∧ q → s) - [`SyntaxLeaf`](@ref) - [`Atom`](@ref) (e.g., p, q) @@ -52,7 +52,7 @@ Later, we will see some interesting example about how to equip these symbols wit arity(φ::SyntaxTree) ``` -The vast majority of data structures involved in encoding a logical formula, are children of the [`Formula`](@ref) abstract type. When such data structures purely represents tree-shaped data structures (or single nodes in them), then they are also children of the [`AbstractSyntaxStructure`](@ref) abstract type. +The vast majority of data structures involved in encoding a logical formula, are children of the [`Formula`](@ref) 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`](@ref) abstract type. ```@docs Formula @@ -75,7 +75,7 @@ composeformulas(c::Connective, φs::NTuple{N,F}) where {N,F<:Formula} We are ready to see how logical formulas are represented using syntax trees ```@docs -AbstractSyntaxStructure +SyntaxStructure SyntaxTree children(φ::SyntaxTree) token(φ::SyntaxTree) diff --git a/docs/src/index.md b/docs/src/index.md index c3d987a5..03099ace 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -51,7 +51,7 @@ Here is a map of SoleLogics' most important types and structures. Feels overwhel - [`BoxRelationalConnective`](@ref) - [`Formula`](@ref) - [`AnchoredFormula`](@ref) - - [`AbstractSyntaxStructure`](@ref) + - [`SyntaxStructure`](@ref) - [`SyntaxTree`](@ref) - [`SyntaxLeaf`](@ref) - [`Atom`](@ref) diff --git a/docs/src/more-on-formulas.md b/docs/src/more-on-formulas.md index 43cc2199..541534cf 100644 --- a/docs/src/more-on-formulas.md +++ b/docs/src/more-on-formulas.md @@ -15,7 +15,7 @@ Recalling the type hierarchy presented in [man-core](@ref), it is here enriched - [`Formula`](@ref) - [`AnchoredFormula`](@ref) - - [`AbstractSyntaxStructure`](@ref) + - [`SyntaxStructure`](@ref) - [`Literal`](@ref) - [`LeftmostLinearForm`](@ref) --- @@ -27,13 +27,13 @@ Literal ## Linear Forms ```@docs -LeftmostLinearForm{C<:Connective,SS<:AbstractSyntaxStructure} +LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} -LeftmostConjunctiveForm{SS<:AbstractSyntaxStructure} -LeftmostDisjunctiveForm{SS<:AbstractSyntaxStructure} +LeftmostConjunctiveForm{SS<:SyntaxStructure} +LeftmostDisjunctiveForm{SS<:SyntaxStructure} -CNF{SS<:AbstractSyntaxStructure} -DNF{SS<:AbstractSyntaxStructure} +CNF{SS<:SyntaxStructure} +DNF{SS<:SyntaxStructure} ``` ```@docs diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index b081c191..737260a1 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -17,7 +17,7 @@ using SoleBase: initrng export iscrisp, isfinite, isnullary, isunary, isbinary export Syntactical, Connective, - Formula, AbstractSyntaxStructure, SyntaxTree, SyntaxLeaf, + Formula, SyntaxStructure, SyntaxTree, SyntaxLeaf, Atom, Truth, SyntaxBranch export Operator, SyntaxToken @@ -36,9 +36,13 @@ export tokens, ntokens, atoms, natoms, truths, ntruths, leaves, nleaves, connectives, nconnectives, operators, noperators, height export composeformulas +include("types/syntactical.jl") + +############################################################################################ + export interpret, check -include("core.jl") +include("types/interpretation.jl") ############################################################################################ @@ -47,7 +51,7 @@ export AlphabetOfAny, ExplicitAlphabet, UnionAlphabet export alphabet, alphabets export domain, top, bot, grammar, algebra, logic -include("logics.jl") +include("types/logic.jl") ############################################################################################ @@ -63,7 +67,7 @@ export BooleanAlgebra export BaseLogic -include("base-logic.jl") +include("utils.jl") ############################################################################################ @@ -154,8 +158,32 @@ include("experimentals.jl") include("deprecate.jl") ############################################################################################ +# Fast isempty(intersect(u, v)) +function intersects(u, v) + for x in u + if x in v + return true + end + end + false +end -include("utils.jl") +function inittruthvalues(truthvalues::Union{Vector{<:Truth}, AbstractAlgebra}) + return (truthvalues isa AbstractAlgebra) ? domain(truthvalues) : truthvalues +end + +function displaysyntaxvector(a, maxnum = 8; quotes = true) + q = e -> (quotes ? "\"$(e)\"" : "$(e)") + els = begin + if length(a) > maxnum + [(q.(syntaxstring.(a)[1:div(maxnum, 2)]))..., "...", + (q.(syntaxstring.(a)[(end - div(maxnum, 2)):end]))...,] + else + q.(syntaxstring.(a)) + end + end + "$(eltype(a))[$(join(els, ", "))]" +end ############################################################################################ diff --git a/src/anchored-formula.jl b/src/anchored-formula.jl index 13faf8f1..e7ac14f0 100644 --- a/src/anchored-formula.jl +++ b/src/anchored-formula.jl @@ -2,7 +2,7 @@ """ struct AnchoredFormula{L<:AbstractLogic} <: Formula _logic::Base.RefValue{L} - synstruct::AbstractSyntaxStructure + synstruct::SyntaxStructure end A formula anchored to a logic of type `L`, and wrapping a syntax structure. @@ -46,14 +46,14 @@ See also [`AbstractLogic`](@ref), [`logic`](@ref), [`SyntaxToken`](@ref), """ struct AnchoredFormula{L<:AbstractLogic} <: Formula _logic::Base.RefValue{L} - synstruct::AbstractSyntaxStructure + synstruct::SyntaxStructure _l(l::AbstractLogic) = Base.RefValue(l) _l(l::Base.RefValue) = l function AnchoredFormula{L}( l::Union{L,Base.RefValue{L}}, - synstruct::AbstractSyntaxStructure; + synstruct::SyntaxStructure; check_atoms::Bool = false, check_tree::Bool = false, ) where {L<:AbstractLogic} @@ -83,7 +83,7 @@ struct AnchoredFormula{L<:AbstractLogic} <: Formula # function AnchoredFormula{L}( # l::Union{L,Base.RefValue{L}}, - # synstruct::AbstractSyntaxStructure; + # synstruct::SyntaxStructure; # kwargs... # ) where {L<:AbstractLogic} # t = convert(SyntaxTree, synstruct) @@ -111,7 +111,7 @@ See [`AnchoredFormula`](@ref). logic(φ::AnchoredFormula) = φ._logic[] """ - synstruct(φ::AnchoredFormula)::AbstractSyntaxStructure + synstruct(φ::AnchoredFormula)::SyntaxStructure Return the syntactic component of an anchored formula. @@ -140,17 +140,17 @@ function composeformulas(c::Connective, φs::NTuple{N,AnchoredFormula}) where {N end # When constructing a new formula from a syntax tree, the logic is passed by reference. -(φ::AnchoredFormula)(t::AbstractSyntaxStructure, args...) = AnchoredFormula(_logic(φ), t, args...) +(φ::AnchoredFormula)(t::SyntaxStructure, args...) = AnchoredFormula(_logic(φ), t, args...) # A logic can be used to instantiate `AnchoredFormula`s out of syntax trees. -(l::AbstractLogic)(t::AbstractSyntaxStructure, args...) = AnchoredFormula(Base.RefValue(l), t; args...) +(l::AbstractLogic)(t::SyntaxStructure, args...) = AnchoredFormula(Base.RefValue(l), t; args...) # Adapted from https://github.com/JuliaLang/julia/blob/master/base/promotion.jl -function Base._promote(x::AnchoredFormula, y::AbstractSyntaxStructure) +function Base._promote(x::AnchoredFormula, y::SyntaxStructure) @inline return (x, x(y)) end -Base._promote(x::AbstractSyntaxStructure, y::AnchoredFormula) = reverse(Base._promote(y, x)) +Base._promote(x::SyntaxStructure, y::AnchoredFormula) = reverse(Base._promote(y, x)) iscrisp(φ::AnchoredFormula) = iscrisp(logic(φ)) grammar(φ::AnchoredFormula) = grammar(logic(φ)) diff --git a/src/base-logic.jl b/src/base-logic.jl deleted file mode 100644 index 73352c6c..00000000 --- a/src/base-logic.jl +++ /dev/null @@ -1,460 +0,0 @@ -""" - collatetruth(c::Connective, ts::NTuple{N,T where T<:Truth})::Truth where {N} - -Return the truth value for a composed formula `c(t1, ..., tN)`, given the `N` -with t1, ..., tN being `Truth` values. - -See also [`simplify`](@ref), [`Connective`](@ref), [`Truth`](@ref). -""" -function collatetruth( - c::Connective, - ts::NTuple{N,T where T<:Truth} -)::Truth where {N} - if arity(c) != length(ts) - return error("Cannot collate $(length(ts)) truth values for " * - "connective $(typeof(c)) with arity $(arity(c))).") - else - return error("Please, provide method collatetruth(::$(typeof(c)), " * - "::NTuple{$(arity(c)),$(T)}).") - end -end - -# Helper (so that collatetruth work for all operators) -collatetruth(t::Truth, ::Tuple{}) = t - - -# With generic formulas, it composes formula -""" - simplify(c::Connective, ts::NTuple{N,F where F<:Formula})::Truth where {N} - -Return a formula with the same semantics of a composed formula `c(φ1, ..., φN)`, -given the `N` -immediate sub-formulas. - -See also [`collatetruth`](@ref), [`Connective`](@ref), [`Formula`](@ref). -""" -function simplify(c::Connective, φs::NTuple{N,T where T<:Formula}) where {N} - c(φs) -end - -function simplify(c::Connective, φs::NTuple{N,T where T<:Truth}) where {N} - collatetruth(c, φs) -end - -############################################################################################ -##################################### BASE CONNECTIVES ##################################### -############################################################################################ - -""" - struct NamedConnective{Symbol} <: Connective end - -A singleton type for representing connectives defined by a name or a symbol. - -# Examples -The AND connective (i.e., the logical conjunction) is defined as the subtype: - - const CONJUNCTION = NamedConnective{:∧}() - const ∧ = CONJUNCTION - arity(::typeof(∧)) = 2 - -See also [`NEGATION`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref), -[`IMPLICATION`](@ref), [`Connective`](@ref). -""" -struct NamedConnective{Symbol} <: Connective end - -name(::NamedConnective{S}) where {S} = S - -Base.show(io::IO, c::NamedConnective) = print(io, "$(syntaxstring(c))") - -syntaxstring(c::NamedConnective; kwargs...) = string(name(c)) - -function precedence(c::NamedConnective) - op = SoleLogics.name(c) - # Using default Base.operator_precedence is risky. For example, - # Base.isoperator(:(¬)) is true, but Base.operator_precedence(:(¬)) is 0. - # See Base.operator_precedence documentation. - if !Base.isoperator(op) || Base.operator_precedence(op) == 0 - error("Please, provide method SoleLogics.precedence(::$(typeof(c))).") - else - Base.operator_precedence(op) - end -end - -function associativity(c::NamedConnective) - op = SoleLogics.name(c) - # Base.isoperator(:(++)) is true, but Base.operator_precedence(:(++)) is :none - if !Base.isoperator(op) || !(Base.operator_associativity(op) in [:left, :right]) - error("Please, provide method SoleLogics.associativity(::$(typeof(c))).") - else - Base.operator_associativity(op) - end -end - -doc_NEGATION = """ - const NEGATION = NamedConnective{:¬}() - const ¬ = NEGATION - arity(::typeof(¬)) = 1 - -Logical negation (also referred to as complement). -It can be typed by `\\neg`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_NEGATION)""" -const NEGATION = NamedConnective{:¬}() -"""$(doc_NEGATION)""" -const ¬ = NEGATION -arity(::typeof(¬)) = 1 - -# ¬ is a risky symbol, since by default it's precedence is defaulted to 0 by julia. -# Because of this, we override Base.operator_precedence. -precedence(::typeof(¬)) = Base.operator_precedence(:∧)+1 - -doc_CONJUNCTION = """ - const CONJUNCTION = NamedConnective{:∧}() - const ∧ = CONJUNCTION - arity(::typeof(∧)) = 2 - -Logical conjunction. -It can be typed by `\\wedge`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_CONJUNCTION)""" -const CONJUNCTION = NamedConnective{:∧}() -"""$(doc_CONJUNCTION)""" -const ∧ = CONJUNCTION -arity(::typeof(∧)) = 2 - -doc_DISJUNCTION = """ - const DISJUNCTION = NamedConnective{:∨}() - const ∨ = DISJUNCTION - arity(::typeof(∨)) = 2 - -Logical disjunction. -It can be typed by `\\vee`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_DISJUNCTION)""" -const DISJUNCTION = NamedConnective{:∨}() -"""$(doc_DISJUNCTION)""" -const ∨ = DISJUNCTION -arity(::typeof(∨)) = 2 - -doc_IMPLICATION = """ - const IMPLICATION = NamedConnective{:→}() - const → = IMPLICATION - arity(::typeof(→)) = 2 - -Logical implication. -It can be typed by `\\to`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_IMPLICATION)""" -const IMPLICATION = NamedConnective{:→}() -"""$(doc_IMPLICATION)""" -const → = IMPLICATION -arity(::typeof(→)) = 2 - -iscommutative(::typeof(∧)) = true -iscommutative(::typeof(∨)) = true - -hasdual(::typeof(∧)) = true -dual(c::typeof(∧)) = typeof(∨) -hasdual(::typeof(∨)) = true -dual(c::typeof(∨)) = typeof(∧) - - -############################################################################################ -###################################### BOOLEAN ALGEBRA ##################################### -############################################################################################ - -""" - struct BooleanTruth <: Truth - flag::Bool - end - -Structure for representing the Boolean truth values ⊤ and ⊥. -It wraps a flag which takes value `true` for ⊤ ([`TOP`](@ref)), -and `false` for ⊥ ([`BOT`](@ref)) - -See also [`BooleanAlgebra`](@ref). -""" -struct BooleanTruth <: Truth - flag::Bool -end - -istop(t::BooleanTruth) = t.flag -isbot(t::BooleanTruth) = !istop(t) - -syntaxstring(t::BooleanTruth; kwargs...) = istop(t) ? "⊤" : "⊥" - -function Base.show(io::IO, φ::BooleanTruth) - print(io, "$(syntaxstring(φ))") -end - -doc_TOP = """ - const TOP = BooleanTruth(true) - const ⊤ = TOP - -Canonical truth operator representing the value `true`. -It can be typed by `\\top`. - -See also [`BOT`](@ref), [`Truth`](@ref). -""" -"""$(doc_TOP)""" -const TOP = BooleanTruth(true) -"""$(doc_TOP)""" -const ⊤ = TOP - -doc_BOTTOM = """ - const BOT = BooleanTruth(false) - const ⊥ = BOT - -Canonical truth operator representing the value `false`. -It can be typed by `\\bot`. - -See also [`TOP`](@ref), [`Truth`](@ref). -""" -"""$(doc_BOTTOM)""" -const BOT = BooleanTruth(false) -"""$(doc_BOTTOM)""" -const ⊥ = BOT - -# NOTE: it could be useful to provide a macro to easily create -# a new set of Truth types. In particular, a new subtree of types must be planted -# as children of Truth, and new promotion rules are to be defined like below. -Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:BooleanTruth}) = BooleanTruth - -function Base.convert(::Type{BooleanTruth}, t::Bool)::BooleanTruth - return (t ? TOP : BOT) -end -function Base.convert(::Type{BooleanTruth}, t::Integer)::BooleanTruth - if isone(t) - return TOP - elseif iszero(t) - return BOT - else - return error("Cannot interpret Integer value $t as BooleanTruth.") - end -end - -Base.convert(::Type{Truth}, t::Bool) = Base.convert(BooleanTruth, t) -Base.convert(::Type{Truth}, t::Integer) = Base.convert(BooleanTruth, t) - -# NOTE: are these useful? -hasdual(::BooleanTruth) = true -dual(c::BooleanTruth) = BooleanTruth(!istop(c)) - -precedes(t1::BooleanTruth, t2::BooleanTruth) = istop(t1) < istop(t2) -truthmeet(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t1 : t2 -truthjoin(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t2 : t1 - -""" - struct BooleanAlgebra <: AbstractAlgebra{Bool} end - -A [Boolean algebra](https://en.wikipedia.org/wiki/Boolean_algebra), defined on the values -TOP (representing *truth*) and BOT (for bottom, representing *falsehood*). -For this algebra, the basic operators negation, -conjunction and disjunction (stylized as ¬, ∧, ∨) can be defined as the complement, minimum -and maximum, of the integer cast of `true` and `false`, respectively. - -See also [`Truth`](@ref). -""" -struct BooleanAlgebra <: AbstractAlgebra{BooleanTruth} end - -domain(::BooleanAlgebra) = [TOP, BOT] - -top(::BooleanAlgebra) = TOP -bot(::BooleanAlgebra) = BOT - -############################################################################################ - -# Standard semantics for NOT, AND, OR, IMPLIES -collatetruth(::typeof(¬), (ts,)::Tuple{BooleanTruth}) = istop(ts) ? BOT : TOP -collatetruth(::typeof(∧), (t1, t2)::NTuple{N,T where T<:BooleanTruth}) where {N} = truthmeet(t1, t2) -collatetruth(::typeof(∨), (t1, t2)::NTuple{N,T where T<:BooleanTruth}) where {N} = truthjoin(t1, t2) - -# Incomplete information -function simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth,BooleanTruth}) - istop(t1) && istop(t2) ? TOP : BOT -end -simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth,Formula}) = istop(t1) ? t2 : t1 -simplify(::typeof(∧), (t1, t2)::Tuple{Formula,BooleanTruth}) = istop(t2) ? t1 : t2 - - -function simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth,BooleanTruth}) - isbot(t1) && isbot(t2) ? BOT : TOP -end -simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth,Formula}) = isbot(t1) ? t2 : t1 -simplify(::typeof(∨), (t1, t2)::Tuple{Formula,BooleanTruth}) = isbot(t2) ? t1 : t2 - -# The IMPLIES operator, →, falls back to using ¬ and ∨ -function collatetruth(::typeof(→), (t1, t2)::NTuple{2,BooleanTruth}) - return collatetruth(∨, (collatetruth(¬, (t1,)), t2)) -end - -############################################################################################ - -# With dense, discrete algebras, floats can be used. -# These are sketches for a few ideas. Note that truth values should be wrapped into Truth substructures: -# istop(ts::AbstractFloat)::Bool = isone(ts) -# isbot(ts::AbstractFloat)::Bool = iszero(ts) - -# # TODO idea: use full range for numbers! -# # istop(ts::AbstractFloat)::Bool = ts == typemax(typeof(ts)) -# # isbot(ts::AbstractFloat)::Bool = ts == typemin(typeof(ts)) -# istop(ts::Integer)::Bool = ts == typemax(typeof(ts)) -# isbot(ts::Integer)::Bool = ts == typemin(typeof(ts)) - -# TODO: -# struct DiscreteChainAlgebra{T} <: AbstractAlgebra{T} domain::Vector{T} end -# struct DenseChainAlgebra{T<:AbstractFloat} <: AbstractAlgebra{T} end - -# TODO: -# struct HeytingNode{T} end -# struct HeytingAlgebra{T} <: AbstractAlgebra{HeytingNode{T}} ... end - -############################################################################################ -########################################### LOGIC ########################################## -############################################################################################ - -""" - struct BaseLogic{G<:AbstractGrammar,A<:AbstractAlgebra} <: AbstractLogic{G,A} - grammar::G - algebra::A - end - -A basic logic based on a grammar and an algebra, where both the grammar and the algebra -are instantiated. - -See also [`grammar`](@ref), [`algebra`](@ref), -[`AbstractGrammar`](@ref), [`AbstractAlgebra`](@ref), [`AbstractLogic`](@ref). -""" -struct BaseLogic{G<:AbstractGrammar,A<:AbstractAlgebra} <: AbstractLogic{G,A} - grammar::G - algebra::A - - function BaseLogic{G,A}( - grammar::G = BASE_GRAMMAR, - algebra::A = BooleanAlgebra(), - ) where {G<:AbstractGrammar,A<:AbstractAlgebra} - # @assert all([goeswith(c, algebra) for c in operators(grammar)]) "Cannot instantiate BaseLogic{$(G),$(A)}: operators $(operators(grammar)[[goeswith(c, algebra) for c in operators(grammar)]]) cannot be interpreted on $(algebra)." # requires `goeswith` trait - return new{G,A}(grammar, algebra) - end - - function BaseLogic{G}( - grammar::G = BASE_GRAMMAR, - algebra::A = BooleanAlgebra(), - ) where {G<:AbstractGrammar,A<:AbstractAlgebra} - return BaseLogic{G,A}(grammar, algebra) - end - - function BaseLogic( - grammar::G = BASE_GRAMMAR, - algebra::A = BooleanAlgebra(), - ) where {G<:AbstractGrammar,A<:AbstractAlgebra} - return BaseLogic{G,A}(grammar, algebra) - end -end - -grammar(l::BaseLogic) = l.grammar -algebra(l::BaseLogic) = l.algebra - -function Base.isequal(a::BaseLogic, b::BaseLogic) - return Base.isequal(grammar(a), grammar(b)) && Base.isequal(algebra(a), algebra(b)) -end - -Base.hash(a::BaseLogic) = Base.hash(algebra(a), Base.hash(grammar(a))) - - -function Base.show(io::IO, l::BaseLogic{G,A}) where {G<:AbstractGrammar,A<:AbstractAlgebra} - if G <: CompleteFlatGrammar - print(io, "BaseLogic with:\n\t- operators = [$(join(syntaxstring.(operators(l)), ", "))];\n\t- alphabet: $(alphabet(l));\n\t- algebra: $(algebra(l)).") - else - print(io, "BaseLogic{$(G),$(A)}(\n\t- grammar: $(grammar(l));\n\t- algebra: $(algebra(l))\n)") - end -end - -############################################################################################ -########################################### BASE ########################################### -############################################################################################ - - -# This can be useful for standard phrasing of propositional formulas with string atoms. - -""" - const BASE_CONNECTIVES = [¬, ∧, ∨, →] - -Basic logical operators. - -See also [`NEGATION`](@ref), -[`CONJUNCTION`](@ref), -[`DISJUNCTION`](@ref), -[`IMPLICATION`](@ref), -[`Connective`](@ref). -""" -const BASE_CONNECTIVES = [¬, ∧, ∨, →] -const BaseConnectives = Union{typeof.(BASE_CONNECTIVES)...} - -const BASE_ALPHABET = AlphabetOfAny{String}() - -const BASE_GRAMMAR = CompleteFlatGrammar(BASE_ALPHABET, BASE_CONNECTIVES) -const BASE_ALGEBRA = BooleanAlgebra() - -const BASE_LOGIC = BaseLogic(BASE_GRAMMAR, BASE_ALGEBRA) - -function _baselogic(; - alphabet::Union{Nothing,Vector,AbstractAlphabet} = nothing, - operators::Union{Nothing,Vector{<:Operator}} = nothing, - grammar::Union{Nothing,AbstractGrammar} = nothing, - algebra::Union{Nothing,AbstractAlgebra} = nothing, - default_operators::Vector{<:Operator}, - logictypename::String, -) - if !(isnothing(grammar) || (isnothing(alphabet) && isnothing(operators))) - error("Cannot instantiate $(logictypename) by specifing a grammar " * - "together with argument(s): " * join([ - (!isnothing(alphabet) ? ["alphabet"] : [])..., - (!isnothing(operators) ? ["operators"] : [])..., - (!isnothing(grammar) ? ["grammar"] : [])..., - ], ", ") * ".") - end - grammar = begin - if isnothing(grammar) - # @show alphabet - # @show operators - # @show BASE_GRAMMAR - # if isnothing(alphabet) && isnothing(operators) - # BASE_GRAMMAR - # else - alphabet = isnothing(alphabet) ? BASE_ALPHABET : alphabet - operators = begin - if isnothing(operators) - default_operators - else - if length(setdiff(operators, default_operators)) > 0 - @warn "Instantiating $(logictypename) with operators not in " * - "$(default_operators): " * - join(", ", setdiff(operators, default_operators)) * "." - end - operators - end - end - if alphabet isa Vector - alphabet = ExplicitAlphabet(map(Atom, alphabet)) - end - CompleteFlatGrammar(alphabet, operators) - # end - else - @assert isnothing(alphabet) && isnothing(operators) - grammar - end - end - - algebra = isnothing(algebra) ? BASE_ALGEBRA : algebra - - return BaseLogic(grammar, algebra) -end diff --git a/src/deprecate.jl b/src/deprecate.jl index d7493f8c..4c3ab66a 100644 --- a/src/deprecate.jl +++ b/src/deprecate.jl @@ -2,6 +2,13 @@ export atom, Proposition, NamedOperator const NamedOperator = NamedConnective +const AbstractSyntaxStructure = SyntaxStructure + +# Helper +function Base.getindex(i::AbstractInterpretation, v, args...; kwargs...) + Base.getindex(i, Atom(v), args...; kwargs...) +end + const Proposition = Atom Base.@deprecate atom(p::Proposition) value(p) diff --git a/src/propositional-logic.jl b/src/propositional-logic.jl index 7e09a4e0..50545056 100644 --- a/src/propositional-logic.jl +++ b/src/propositional-logic.jl @@ -391,16 +391,16 @@ end struct TruthTable{A,T<:Truth} Dictionary which associates an [`AbstractAssignment`](@ref)s to the truth value of the -assignment itself on a [`AbstractSyntaxStructure`](@ref). +assignment itself on a [`SyntaxStructure`](@ref). -See also [`AbstractAssignment`](@ref), [`AbstractSyntaxStructure`](@ref), +See also [`AbstractAssignment`](@ref), [`SyntaxStructure`](@ref), [`Truth`](@ref). """ struct TruthTable{ A, T<:Truth } <: Formula # TODO is this correct? Remove? - truth::Dict{<:AbstractAssignment,Vector{Pair{AbstractSyntaxStructure,T}}} + truth::Dict{<:AbstractAssignment,Vector{Pair{SyntaxStructure,T}}} end ############################################################################################ diff --git a/src/syntax-utils.jl b/src/syntax-utils.jl index 08f4d002..7b70037c 100644 --- a/src/syntax-utils.jl +++ b/src/syntax-utils.jl @@ -2,7 +2,7 @@ import Base: show, promote_rule, length, getindex using SoleBase doc_lmlf = """ - struct LeftmostLinearForm{C<:Connective,SS<:AbstractSyntaxStructure} <: AbstractSyntaxStructure + struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure children::Vector{<:SS} end @@ -11,11 +11,11 @@ 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<:AbstractSyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} - const LeftmostDisjunctiveForm{SS<:AbstractSyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} + const LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} + const LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} - const CNF{SS<:AbstractSyntaxStructure} = LeftmostLinearForm{typeof(∧),LeftmostLinearForm{typeof(∨),SS}} - const DNF{SS<:AbstractSyntaxStructure} = LeftmostLinearForm{typeof(∨),LeftmostLinearForm{typeof(∧),SS}} + const CNF{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),LeftmostLinearForm{typeof(∨),SS}} + const DNF{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),LeftmostLinearForm{typeof(∧),SS}} # Examples ```julia-repl @@ -56,16 +56,16 @@ SyntaxBranch: ¬(p ∧ q) ∧ ¬(p ∧ q) """$(doc_lmlf) -See also [`AbstractSyntaxStructure`](@ref), [`SyntaxTree`](@ref), +See also [`SyntaxStructure`](@ref), [`SyntaxTree`](@ref), [`LeftmostConjunctiveForm`](@ref), [`LeftmostDisjunctiveForm`](@ref), [`Literal`](@ref). """ -struct LeftmostLinearForm{C<:Connective,SS<:AbstractSyntaxStructure} <: AbstractSyntaxStructure +struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure children::Vector{SS} function LeftmostLinearForm{C,SS}( children::Vector, - ) where {C<:Connective,SS<:AbstractSyntaxStructure} + ) where {C<:Connective,SS<:SyntaxStructure} a = arity(C()) # TODO maybe add member connective::C and use that instead of C() n_children = length(children) @@ -84,7 +84,7 @@ struct LeftmostLinearForm{C<:Connective,SS<:AbstractSyntaxStructure} <: Abstract new{C,SS}(children) end - function LeftmostLinearForm{C}(children::AbstractVector{SS}) where {C<:Connective,SS<:AbstractSyntaxStructure} + function LeftmostLinearForm{C}(children::AbstractVector{SS}) where {C<:Connective,SS<:SyntaxStructure} # SS = SoleBase._typejoin(typeof.(children)...) LeftmostLinearForm{C,SS}(children) end @@ -128,7 +128,7 @@ struct LeftmostLinearForm{C<:Connective,SS<:AbstractSyntaxStructure} <: Abstract # Get a vector of `SyntaxTree`s, having `c` as common ancestor, then, # call LeftmostLinearForm constructor. - _children = AbstractSyntaxStructure[] + _children = SyntaxStructure[] function _dig_and_retrieve(tree::SyntaxTree, c::SoleLogics.Connective) token(tree) != c ? @@ -249,7 +249,7 @@ function Base.show(io::IO, lf::LeftmostLinearForm{C,SS}) where {C,SS} else print(io, "LeftmostLinearForm with connective $(syntaxstring(connective(lf))) and") end - println(io, " $(nchildren(lf)) $((SS == AbstractSyntaxStructure ? "" : "$(SS) "))children:") + println(io, " $(nchildren(lf)) $((SS == SyntaxStructure ? "" : "$(SS) "))children:") end # println(io, "\t$(join(syntaxstring.(children(lf)), " $(syntaxstring(connective(lf))) \n\t"))") println(io, "\t$(join(syntaxstring.(children(lf)), "\n\t"))") @@ -257,8 +257,8 @@ end # TODO fix Base.promote_rule(::Type{<:LeftmostLinearForm}, ::Type{<:LeftmostLinearForm}) = SyntaxTree -Base.promote_rule(::Type{SS}, ::Type{LF}) where {SS<:AbstractSyntaxStructure,LF<:LeftmostLinearForm} = SyntaxTree -Base.promote_rule(::Type{LF}, ::Type{SS}) where {LF<:LeftmostLinearForm,SS<:AbstractSyntaxStructure} = SyntaxTree +Base.promote_rule(::Type{SS}, ::Type{LF}) where {SS<:SyntaxStructure,LF<:LeftmostLinearForm} = SyntaxTree +Base.promote_rule(::Type{LF}, ::Type{SS}) where {LF<:LeftmostLinearForm,SS<:SyntaxStructure} = SyntaxTree function Base.in(tok::SyntaxToken, φ::LeftmostLinearForm)::Bool return (tok isa Connective && connective(φ) == tok) || @@ -322,19 +322,19 @@ Base.promote_rule(::Type{SS}, ::Type{LF}) where {LF<:LeftmostLinearForm,SS<:Synt ############################################################################################ # TODO actually: -# const CNF{SS<:AbstractSyntaxStructure} = Union{LeftmostLinearForm{typeof(∧),LeftmostLinearForm{typeof(∨),SS}},LeftmostLinearForm{typeof(∨),SS}} -# const DNF{SS<:AbstractSyntaxStructure} = Union{LeftmostLinearForm{typeof(∨),LeftmostLinearForm{typeof(∧),SS}},LeftmostLinearForm{typeof(∧),SS}} +# const CNF{SS<:SyntaxStructure} = Union{LeftmostLinearForm{typeof(∧),LeftmostLinearForm{typeof(∨),SS}},LeftmostLinearForm{typeof(∨),SS}} +# const DNF{SS<:SyntaxStructure} = Union{LeftmostLinearForm{typeof(∨),LeftmostLinearForm{typeof(∧),SS}},LeftmostLinearForm{typeof(∧),SS}} """ - LeftmostConjunctiveForm{SS<:AbstractSyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} + LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} Specific instantiation of a [`LeftmostLinearForm`](@ref), where [`Connective`](@ref)s are all [`CONJUNCTION`](@ref)s. -See also [`AbstractSyntaxStructure`](@ref), [`Connective`](@ref), [`LeftmostLinearForm`](@ref), +See also [`SyntaxStructure`](@ref), [`Connective`](@ref), [`LeftmostLinearForm`](@ref), [`CONJUNCTION`](@ref). """ -const LeftmostConjunctiveForm{SS<:AbstractSyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} +const LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} function check( φ::LeftmostConjunctiveForm, @@ -354,15 +354,15 @@ function check( end """ - LeftmostDisjunctiveForm{SS<:AbstractSyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} + LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} Specific instantiation of a [`LeftmostLinearForm`](@ref), where [`Connective`](@ref)s are all [`DISJUNCTION`](@ref)s. -See also [`AbstractSyntaxStructure`](@ref), [`Connective`](@ref), +See also [`SyntaxStructure`](@ref), [`Connective`](@ref), [`LeftmostLinearForm`](@ref), [`DISJUNCTION`](@ref). """ -const LeftmostDisjunctiveForm{SS<:AbstractSyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} +const LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} function check( φ::LeftmostDisjunctiveForm, @@ -382,14 +382,14 @@ function check( end """ - CNF{SS<:AbstractSyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} + CNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} -Conjunctive Normal Form of an [`AbstractSyntaxStructure`](@ref). +Conjunctive Normal Form of an [`SyntaxStructure`](@ref). -See also [`AbstractSyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), +See also [`SyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), [`LeftmostDisjunctiveForm`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref). """ -const CNF{SS<:AbstractSyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} +const CNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} function check( φ::CNF, @@ -409,14 +409,14 @@ function check( end """ - DNF{SS<:AbstractSyntaxStructure} = LeftmostConjunctiveForm{LeftmostConjunctiveForm{SS}} + DNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostConjunctiveForm{SS}} -Disjunctive Normal Form of an [`AbstractSyntaxStructure`](@ref). +Disjunctive Normal Form of an [`SyntaxStructure`](@ref). -See also [`AbstractSyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), +See also [`SyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), [`LeftmostDisjunctiveForm`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref). """ -const DNF{SS<:AbstractSyntaxStructure} = LeftmostDisjunctiveForm{LeftmostConjunctiveForm{SS}} +const DNF{SS<:SyntaxStructure} = LeftmostDisjunctiveForm{LeftmostConjunctiveForm{SS}} function check( φ::DNF, @@ -468,8 +468,8 @@ function DNF(φ::SyntaxBranch) end -literaltype(::CNF{SS}) where {SS<:AbstractSyntaxStructure} = SS -literaltype(::DNF{SS}) where {SS<:AbstractSyntaxStructure} = SS +literaltype(::CNF{SS}) where {SS<:SyntaxStructure} = SS +literaltype(::DNF{SS}) where {SS<:SyntaxStructure} = SS # # TODO maybe not needed? # Base.promote_rule(::Type{<:LeftmostConjunctiveForm}, ::Type{<:LeftmostConjunctiveForm}) = LeftmostConjunctiveForm @@ -493,16 +493,16 @@ pushdisjunct(φ::LeftmostDisjunctiveForm, el) = Base.push!(children(φ), el) ############################################################################################ """ - struct Literal{T<:SyntaxLeaf} <: AbstractSyntaxStructure + struct Literal{T<:SyntaxLeaf} <: SyntaxStructure ispos::Bool prop::T end An atom, or its negation. -See also [`CNF`](@ref), [`DNF`](@ref), [`AbstractSyntaxStructure`](@ref). +See also [`CNF`](@ref), [`DNF`](@ref), [`SyntaxStructure`](@ref). """ -struct Literal{T<:SyntaxLeaf} <: AbstractSyntaxStructure +struct Literal{T<:SyntaxLeaf} <: SyntaxStructure ispos::Bool prop::T @@ -562,7 +562,7 @@ function cnf(φ::Formula, literaltype = Literal; kwargs...) return _cnf(normalize(φ; profile = :nnf, kwargs...), literaltype) end -function cnf(φ::CNF{T}, literaltype = Literal; kwargs...) where {T<:AbstractSyntaxStructure} +function cnf(φ::CNF{T}, literaltype = Literal; kwargs...) where {T<:SyntaxStructure} if T == literaltype return φ else diff --git a/src/docstrings.jl b/src/types/docstrings.jl similarity index 100% rename from src/docstrings.jl rename to src/types/docstrings.jl diff --git a/src/types/interpretation.jl b/src/types/interpretation.jl new file mode 100644 index 00000000..a59d0959 --- /dev/null +++ b/src/types/interpretation.jl @@ -0,0 +1,166 @@ + +############################################################################################ +#### AbstractInterpretation ################################################################ +############################################################################################ + +""" + abstract type AbstractInterpretation end + +Abstract type for representing a [logical +interpretation](https://en.wikipedia.org/wiki/Interpretation_(logic)). +In the case of +[propositional logic](https://simple.wikipedia.org/wiki/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`](@ref), [`AbstractAssignment`](@ref), [`AbstractKripkeStructure`](@ref). +""" +abstract type AbstractInterpretation end + +function valuetype(i::AbstractInterpretation) + return error("Please, provide method valuetype(::$(typeof(i))).") +end +function truthtype(i::AbstractInterpretation) + return error("Please, provide method truthtype(::$(typeof(i))).") +end + +############################################################################################ +#### Interpret & Check ##################################################################### +############################################################################################ + +""" + interpret( + φ::Formula, + i::AbstractInterpretation, + args...; + kwargs... + )::Formula + +Return the truth value for a formula on a logical interpretation (or model). + +# Examples +```julia-repl +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`](@ref), [`Formula`](@ref), [`AbstractInterpretation`](@ref), +[`AbstractAlgebra`](@ref). +""" +function interpret( + φ::Formula, + i::AbstractInterpretation, + args...; + kwargs... +)::Formula + interpret(tree(φ), i, args...; kwargs...) +end + +function interpret( + φ::AbstractAtom, + i::AbstractInterpretation, + args...; + kwargs..., +)::Formula + return error("Please, provide method " * + "interpret(φ::$(typeof(φ)), i::$(typeof(i)), " * + "args...::$(typeof(args)); " * + "kwargs...::$(typeof(kwargs))).") +end + +function interpret( + φ::AbstractSyntaxBranch, + i::AbstractInterpretation, + args...; + kwargs..., +) + connective = token(φ) + ts = Tuple( + [interpret(ch, i, args...; kwargs...) for ch in children(φ)] + ) + return simplify(connective, ts, args...; kwargs...) +end + + +interpret(t::Truth, i::AbstractInterpretation, args...; kwargs...) = t + +""" + check( + φ::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](https://en.wikipedia.org/wiki/Model_checking), and there are many +algorithms for it, typically depending on the complexity of the logic. + +# Examples +```julia-repl +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`](@ref), [`Formula`](@ref), [`AbstractInterpretation`](@ref), +[`TruthDict`](@ref). +""" +function check( + φ::Formula, + i::AbstractInterpretation, + args...; + kwargs... +)::Bool + istop(interpret(φ, i, args...; kwargs...)) +end + +############################################################################################ +#### Utilities ############################################################################# +############################################################################################ + +# Formula interpretation via i[φ] -> φ +Base.getindex(i::AbstractInterpretation, φ::Formula, args...; kwargs...) = + interpret(φ, i, args...; kwargs...) + +# Formula interpretation via φ(i) -> φ +(φ::Formula)(i::AbstractInterpretation, args...; kwargs...) = + interpret(φ, i, args...; kwargs...) diff --git a/src/logics.jl b/src/types/logic.jl similarity index 56% rename from src/logics.jl rename to src/types/logic.jl index 75e757f1..5f59952c 100644 --- a/src/logics.jl +++ b/src/types/logic.jl @@ -41,7 +41,7 @@ When implementing a new alphabet type `MyAlphabet`, you should provide a method establishing whether an atom belongs to it or not; while, in general, this method should be: - function Base.in(p::Atom, a::MyAlphabet)::Bool + function Base.in(p::AbstractAtom, a::MyAlphabet)::Bool in the case of *finite* alphabets, it suffices to define a method: @@ -51,9 +51,9 @@ By default, an alphabet is considered finite: Base.isfinite(::Type{<:AbstractAlphabet}) = true Base.isfinite(a::AbstractAlphabet) = Base.isfinite(typeof(a)) - Base.in(p::Atom, a::AbstractAlphabet) = Base.isfinite(a) ? Base.in(p, atoms(a)) : error(...) + Base.in(p::AbstractAtom, a::AbstractAlphabet) = Base.isfinite(a) ? Base.in(p, atoms(a)) : error(...) -See also [`AbstractGrammar`](@ref), [`AlphabetOfAny`](@ref), [`Atom`](@ref), +See also [`AbstractGrammar`](@ref), [`AlphabetOfAny`](@ref), [`AbstractAtom`](@ref), [`ExplicitAlphabet`](@ref). """ abstract type AbstractAlphabet{V} end @@ -96,13 +96,13 @@ function atoms(a::AbstractAlphabet)::AbstractVector{atomstype(a)} end """ - Base.in(p::Atom, a::AbstractAlphabet)::Bool + Base.in(p::AbstractAtom, a::AbstractAlphabet)::Bool Return whether an atom belongs to an alphabet. -See also [`AbstractAlphabet`](@ref), [`Atom`](@ref). +See also [`AbstractAlphabet`](@ref), [`AbstractAtom`](@ref). """ -function Base.in(p::Atom, a::AbstractAlphabet)::Bool +function Base.in(p::AbstractAtom, a::AbstractAlphabet)::Bool if Base.isfinite(a) Base.in(p, atoms(a)) else @@ -168,7 +168,7 @@ end Return an iterator to the next element in an alhabet. -See also [`AbstractAlphabet`](@ref), [`SyntaxBranch`](@ref). +See also [`AbstractAlphabet`](@ref), [`AbstractSyntaxBranch`](@ref). """ function Base.iterate(a::AbstractAlphabet) if isfinite(a) @@ -190,154 +190,6 @@ function Base.IteratorSize(::Type{V}) where {V<:AbstractAlphabet} return Base.isfinite(V) ? Base.HasLength() : Base.IsInfinite() end -############################################################################################ -#### ExplicitAlphabet ###################################################################### -############################################################################################ - -""" - struct ExplicitAlphabet{V} <: AbstractAlphabet{V} - atoms::Vector{Atom{V}} - end - -An alphabet wrapping atoms in a (finite) `Vector`. - -See also [`AbstractAlphabet`](@ref), [`atoms`](@ref). -""" -struct ExplicitAlphabet{V} <: AbstractAlphabet{V} - atoms::Vector{Atom{V}} - - function ExplicitAlphabet{V}(atoms) where {V} - return new{V}(collect(atoms)) - end - - function ExplicitAlphabet(atoms::AbstractVector{Atom{V}}) where {V} - return ExplicitAlphabet{V}(collect(atoms)) - end - - function ExplicitAlphabet(atoms::AbstractVector{V}) where {V} - return ExplicitAlphabet{V}(Atom.(collect(atoms))) - end -end -atoms(a::ExplicitAlphabet) = a.atoms -natoms(a::ExplicitAlphabet) = length(atoms(a)) - -Base.convert(::Type{AbstractAlphabet}, alphabet::Vector{<:Atom}) = - ExplicitAlphabet(alphabet) - -############################################################################################ -#### AlphabetOfAny ######################################################################### -############################################################################################ - -""" - struct AlphabetOfAny{V} <: AbstractAlphabet{V} end - -An implicit, infinite alphabet that includes all atoms with values of a subtype of V. - -See also [`AbstractAlphabet`](@ref). -""" -struct AlphabetOfAny{V} <: AbstractAlphabet{V} end -Base.isfinite(::Type{<:AlphabetOfAny}) = false -Base.in(::Atom{PV}, ::AlphabetOfAny{VV}) where {PV,VV} = (PV <: VV) - -############################################################################################ -#### UnionAlphabet ######################################################################### -############################################################################################ - -# Finite alphabet of conditions induced from a set of metaconditions -""" -Alphabet given by the *union* of a number of (sub-)alphabets. - -See also -[`UnboundedScalarAlphabet`](@ref), -[`ScalarCondition`](@ref), -[`ScalarMetaCondition`](@ref). -""" - -struct UnionAlphabet{C,A<:AbstractAlphabet{C}} <: AbstractAlphabet{C} - subalphabets::Vector{A} -end - -subalphabets(a::UnionAlphabet) = a.subalphabets -nsubalphabets(a::UnionAlphabet) = length(subalphabets(a)) - - - -function Base.show(io::IO, a::UnionAlphabet) - println(io, "$(typeof(a)):") - for sa in subalphabets(a) - Base.show(io, sa) - end -end - - - -function atoms(a::UnionAlphabet) - return Iterators.flatten(Iterators.map(atoms, subalphabets(a))) -end - -natoms(a::UnionAlphabet) = sum(natoms, subalphabets(a)) - -function Base.in(p::Atom, a::UnionAlphabet) - return any(sa -> Base.in(p, sa), subalphabets(a)) -end - - -""" - randatom( - rng::Union{Integer,AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol=:uniform, - subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing - )::Atom - -Sample an atom from a `UnionAlphabet`. By default, the sampling is uniform with respect to the atoms. -However, 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. - -See also [`UnionAlphabet`](@ref). -""" -function randatom( - rng::Union{Integer,AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol=:uniform, - subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing -)::Atom - - # @show a - @assert atompicking_mode in [:uniform, :uniform_subalphabets, :weighted] "Value for `atompicking_mode` not..." - rng = initrng(rng) - alphs = subalphabets(a) - - if atompicking_mode == :weighted - if isnothing(subalphabets_weights) - error("`:weighted` picking_mode requires weights in `subalphabets_weights` ") - end - @assert length(subalphabets_weights) == length(alphs) "Mismatching numbers of alphabets " * - "($(length(alphs))) and weights ($(length(subalphabets_weights)))." - subalphabets_weights = StatsBase.weights(subalphabets_weights) - pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) - else - subalphabets_weights = begin - # This atomatically excludes subalphabets with empty threshold vector - if atompicking_mode == :uniform_subalphabets - # set the weight of the empty alphabets to zero - weights = Weights(ones(Int, length(alphs))) - weights[natoms.(alphs) == 0] .= 0 - elseif atompicking_mode == :uniform - weights = Weights(natoms.(alphs)) - end - weights - end - pickedalphabet = sample(rng, alphs, subalphabets_weights) - end - # @show a - # @show subalphabets_weights - # @show pickedalphabet - return randatom(rng, pickedalphabet) -end - ############################################################################################ #### AbstractGrammar ####################################################################### ############################################################################################ @@ -389,7 +241,7 @@ end # TODO actually differentiate Connective's and SyntaxLeaves, and define+use leaves(g) # Note: when using this file's syntax tokens, these methods suffice: -Base.in(a::Atom, g::AbstractGrammar) = Base.in(a, alphabet(g)) +Base.in(a::AbstractAtom, g::AbstractGrammar) = Base.in(a, alphabet(g)) Base.in(op::Truth, g::AbstractGrammar) = (op <: operatorstype(g)) Base.in(op::Connective, g::AbstractGrammar) = (op <: operatorstype(g)) @@ -399,7 +251,7 @@ Base.in(op::Connective, g::AbstractGrammar) = (op <: operatorstype(g)) maxdepth::Integer, nformulas::Union{Nothing,Integer} = nothing, args... - )::Vector{<:SyntaxBranch} + )::Vector{<:AbstractSyntaxBranch} Enumerate the formulas produced by a given grammar with a finite and iterable alphabet. @@ -411,14 +263,14 @@ At least these two arguments should be covered: - a `maxdepth` argument can be used to limit the syntactic component, represented as a syntax tree, to a given maximum depth; -See also [`AbstractGrammar`](@ref), [`SyntaxBranch`](@ref). +See also [`AbstractGrammar`](@ref), [`AbstractSyntaxBranch`](@ref). """ function formulas( g::AbstractGrammar{V,O} where {V,O}; maxdepth::Integer, nformulas::Union{Nothing,Integer} = nothing, args... -)::Vector{<:SyntaxBranch} +)::Vector{<:AbstractSyntaxBranch} @assert maxdepth >= 0 @assert nformulas > 0 if isfinite(alphabet(g)) @@ -436,152 +288,6 @@ function Base.isequal(a::AbstractGrammar, b::AbstractGrammar) end Base.hash(a::AbstractGrammar) = Base.hash(alphabet(a), Base.hash(operatorstype(a))) -############################################################################################ -#### CompleteFlatGrammar ################################################################### -############################################################################################ - -""" - struct CompleteFlatGrammar{V<:AbstractAlphabet,O<:Operator} <: AbstractGrammar{V,O} - alphabet::V - operators::Vector{<:O} - end - -V grammar of all well-formed formulas obtained by the arity-complying composition -of atoms of an alphabet of type `V`, and all operators in `operators`. -With n operators, this grammar has exactly n+1 production rules. -For example, with `operators = [∧,∨]`, the grammar (in Backus-Naur form) is: - - φ ::= p | φ ∧ φ | φ ∨ φ - -with p ∈ alphabet. Note: it is *flat* in the sense that all rules substitute the same -(unique and starting) non-terminal symbol φ. - -See also [`AbstractGrammar`](@ref), [`Operator`](@ref), [`alphabet`](@ref), -[`formulas`](@ref), [`connectives`](@ref), [`operators`](@ref), [`leaves`](@ref). -""" -struct CompleteFlatGrammar{V<:AbstractAlphabet,O<:Operator} <: AbstractGrammar{V,O} - alphabet::V - operators::Vector{<:O} - - function CompleteFlatGrammar{V,O}( - alphabet::V, - operators::Vector{<:O}, - ) where {V<:AbstractAlphabet,O<:Operator} - return new{V,O}(alphabet, operators) - end - - function CompleteFlatGrammar{V}( - alphabet::V, - operators::Vector{<:Operator}, - ) where {V<:AbstractAlphabet} - return new{V,Union{typeof.(operators)...}}( - alphabet, - Vector{Union{typeof.(operators)...}}(operators) - ) - end - - function CompleteFlatGrammar( - alphabet::V, - operators::Vector{<:Operator}, - ) where {V<:AbstractAlphabet} - return new{V,Union{typeof.(operators)...}}( - alphabet, - Vector{Union{typeof.(operators)...}}(operators) - ) - end -end - -alphabet(g::CompleteFlatGrammar) = g.alphabet -operators(g::CompleteFlatGrammar) = g.operators - -""" - connectives(g::AbstractGrammar) - -List all connectives appearing in a grammar. - -See also [`Connective`](@ref), [`nconnectives`](@ref). -""" -function connectives(g::AbstractGrammar)::AbstractVector{Connective} - return filter(!isnullary, operators(g)) -end - -""" - leaves(g::AbstractGrammar) - -List all leaves appearing in a grammar. - -See also [`SyntaxLeaf`](@ref), [`nleaves`](@ref). -""" -function leaves(g::AbstractGrammar) - return [atoms(alphabet(g))..., filter(isnullary, operators(g))...] -end - -# V complete grammar includes any *safe* syntax tree that can be built with -# the grammar token types. -function Base.in(φ::SyntaxTree, g::CompleteFlatGrammar)::Bool - return if token(φ) isa Atom - token(φ) in alphabet(g) - elseif token(φ) isa Operator - if operatorstype(φ) <: operatorstype(g) - true - else - all([Base.in(c, g) for c in children(φ)]) - end - else - false - end -end - -""" - formulas( - g::CompleteFlatGrammar{V,O} where {V,O}; - maxdepth::Integer, - nformulas::Union{Nothing,Integer} = nothing - )::Vector{SyntaxBranch} - -Generate all formulas whose `SyntaxBranch`s that are not taller than a given `maxdepth`. - -See also [`AbstractGrammar`](@ref), [`SyntaxBranch`](@ref). -""" -function formulas( - g::CompleteFlatGrammar{V,O} where {V,O}; - maxdepth::Integer, - nformulas::Union{Nothing,Integer} = nothing, -)::Vector{SyntaxTree} - @assert maxdepth >= 0 - @assert isnothing(nformulas) || nformulas > 0 - # With increasing `depth`, accumulate all formulas of length `depth` by combining all - # formulas of `depth-1` using all non-terminal symbols. - # Stop as soon as `maxdepth` is reached or `nformulas` have been generated. - depth = 0 - cur_formulas = Vector{SyntaxTree}(leaves(g)) - all_formulas = SyntaxTree[cur_formulas...] - while depth < maxdepth && (isnothing(nformulas) || length(all_formulas) < nformulas) - _nformulas = length(all_formulas) - cur_formulas = [] - for op in connectives(g) - for children in Iterators.product(fill(all_formulas, arity(op))...) - if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) - break - end - push!(cur_formulas, SyntaxTree(op, Tuple(children))) - end - if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) - break - end - end - append!(all_formulas, cur_formulas) - depth += 1 - end - return all_formulas -end - -# This dispatches are needed, since ambiguities might arise when choosing between -# in(φ::SyntaxTree, g::SoleLogics.CompleteFlatGrammar) and -# in(p::Atom, g::SoleLogics.AbstractGrammar) -Base.in(p::Atom, g::CompleteFlatGrammar) = Base.in(p, alphabet(g)) -Base.in(op::Truth, g::CompleteFlatGrammar) = (op <: operatorstype(g)) - ############################################################################################ #### AbstractAlgebra, semantics ############################################################ ############################################################################################ @@ -705,8 +411,8 @@ tokenstype(l::AbstractLogic) = tokenstype(grammar(l)) formulas(l::AbstractLogic, args...; kwargs...) = formulas(grammar(l), args...; kwargs...) Base.in(op::Operator, l::AbstractLogic) = Base.in(op, grammar(l)) -Base.in(φ::SyntaxBranch, l::AbstractLogic) = Base.in(φ, grammar(l)) -Base.in(p::Atom, l::AbstractLogic) = Base.in(p, alphabet(l)) +Base.in(φ::AbstractSyntaxBranch, l::AbstractLogic) = Base.in(φ, grammar(l)) +Base.in(p::AbstractAtom, l::AbstractLogic) = Base.in(p, alphabet(l)) """ algebra(l::AbstractLogic{G,V})::V where {G,V} diff --git a/src/core.jl b/src/types/syntactical.jl similarity index 67% rename from src/core.jl rename to src/types/syntactical.jl index fd756e09..9f715f78 100644 --- a/src/core.jl +++ b/src/types/syntactical.jl @@ -1,29 +1,15 @@ #= - Syntactical Type Hierarchy + Syntactical Types Hierarchy Syntactical - ├── Formula - │ ├── AbstractSyntaxStructure - │ │ ├── SyntaxTree - │ │ │ ├── SyntaxLeaf - │ │ │ │ ├── Atom - │ │ │ │ └── Truth - │ │ │ │ ├── BooleanTruth (⊤ and ⊥) - │ │ │ │ └── ... - │ │ │ └── SyntaxBranch (e.g., p ∧ q) - │ │ ├── LeftmostLinearForm (e.g., conjunctions, disjunctions, DNFs, CNFs) - │ │ ├── Literal (e.g., p, ¬p) - │ │ └── ... - │ ├── TruthTable - │ ├── AnchoredFormula - │ └── ... - └── Connective - ├── NamedConnective (e.g., ∧, ∨, →, ¬, □, ◊) - ├── AbstractRelationalConnective - │ ├── DiamondRelationalConnective (e.g., ⟨G⟩) - │ ├── BoxRelationalConnective (e.g., [G]) - │ └── ... - └── ... + ├── Connective (e.g, ∧, ¬, □, ◊, ⟨G⟩) + └── Formula + └── SyntaxStructure + └── SyntaxTree + ├── SyntaxLeaf + │ ├── AbstractAtom (e.g., p) + │ └── Truth (e.g, ⊤, ⊥) + └── AbstractSyntaxBranch (e.g., p ∧ q) Also: const Operator = Union{Connective,Truth} @@ -40,6 +26,9 @@ include("docstrings.jl") Master abstract type for all syntactical objects (e.g., formulas, connectives). +# Interface +- `syntaxstring(s::Syntactical; kwargs...)::String` + See also [`Formula`](@ref), [`Connective`](@ref). """ abstract type Syntactical end @@ -65,6 +54,17 @@ Abstract type for [logical connectives](https://en.wikipedia.org/wiki/Logical_co 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,Truth})::SyntaxTree` +- `dual(s::Connective)::Connective` +- `hasdual(s::Connective)::Bool` +- See also [`Syntactical`](@ref) + # Implementation When implementing a new type `C` for a connective, please define its `arity`. @@ -130,7 +130,9 @@ isternary(c) = arity(c) == 3 """$(doc_iscommutative)""" function iscommutative(c::Connective) - return arity(c) <= 1 # Unless otherwise specified + # Unless otherwise specified + return arity(c) <= 1 ? true : + error("Please, provide method iscommutative(::$(typeof(c))).") end """$(doc_precedence)""" @@ -150,7 +152,7 @@ associativity(::Connective) = :left Abstract type for logical formulas. Examples of `Formula`s are `SyntaxLeaf`s (for example, `Atom`s and -`Truth` values), `AbstractSyntaxStructure`s (for example, `SyntaxTree`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](https://en.wikipedia.org/wiki/Memoization) structures, @@ -162,7 +164,28 @@ representation via [`tree`](@ref); its [`height`](@ref) can be computed, and it can be queried for its syntax [`tokens`](@ref), [`atoms`](@ref), etc... It can be parsed from its [`syntaxstring`](@ref) representation via [`parseformula`](@ref). -See also [`tree`](@ref), [`AbstractSyntaxStructure`](@ref), [`SyntaxLeaf`](@ref). +# Interface +- `tree(φ::Formula)::SyntaxTree` +- `composeformulas(c::Connective, φs::NTuple{N,F})::F where {N,F<:Formula}` +- See also [`Syntactical`](@ref) + +# 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`](@ref), [`SyntaxStructure`](@ref), [`SyntaxLeaf`](@ref). """ abstract type Formula <: Syntactical end @@ -179,13 +202,13 @@ function tree(φ::Formula) end """ - height(φ::Formula)::Integer + height(φ::Formula)::Int Return the height of a formula, in its syntax tree representation. See also [`SyntaxTree`](@ref). """ -function height(φ::Formula)::Integer +function height(φ::Formula)::Int return height(tree(φ)) end @@ -194,7 +217,7 @@ function tokens(φ::Formula) # ::AbstractVector{<:SyntaxToken} return tokens(tree(φ)) end """$(doc_tokopprop)""" -function atoms(φ::Formula) # ::AbstractVector{<:Atom} +function atoms(φ::Formula) # ::AbstractVector{<:AbstractAtom} return atoms(tree(φ)) end """$(doc_tokopprop)""" @@ -214,27 +237,27 @@ function operators(φ::Formula) # ::AbstractVector{<:Operator} return operators(tree(φ)) end """$(doc_tokopprop)""" -function ntokens(φ::Formula)::Integer +function ntokens(φ::Formula)::Int return ntokens(tree(φ)) end """$(doc_tokopprop)""" -function natoms(φ::Formula)::Integer +function natoms(φ::Formula)::Int return natoms(tree(φ)) end """$(doc_tokopprop)""" -function ntruths(φ::Formula)::Integer +function ntruths(φ::Formula)::Int return ntruths(tree(φ)); end """$(doc_tokopprop)""" -function nleaves(φ::Formula)::Integer +function nleaves(φ::Formula)::Int return nleaves(tree(φ)); end """$(doc_tokopprop)""" -function nconnectives(φ::Formula)::Integer +function nconnectives(φ::Formula)::Int return nconnectives(tree(φ)); end """$(doc_tokopprop)""" -function noperators(φ::Formula)::Integer +function noperators(φ::Formula)::Int return noperators(tree(φ)); end @@ -254,17 +277,18 @@ function composeformulas(c::Connective, φs::NTuple{N,F})::F where {N,F<:Formula "composeformulas(c::Connective, φs::NTuple{N,$(F)}) where {N}.") end +# Helper (?) # Note: don't type the output as F function composeformulas(c::Connective, φs::Vararg{Formula,N}) where {N} return composeformulas(c, φs) end ############################################################################################ -#### AbstractSyntaxStructure ############################################################### +#### SyntaxStructure ############################################################### ############################################################################################ """ - abstract type AbstractSyntaxStructure <: Formula end + abstract 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 @@ -272,12 +296,15 @@ no fancy memoization structure associated). The typical representation is the (e.g., [conjunctive](https://en.wikipedia.org/wiki/Conjunctive_normal_form) or [disjunctive](https://en.wikipedia.org/wiki/Disjunctive_normal_form) normal forms). +# Interface +- See also [`Formula`](@ref) + See also [`Formula`](@ref), [`AbstractLogic`](@ref), [`SyntaxTree`](@ref), [`tree`](@ref). """ -abstract type AbstractSyntaxStructure <: Formula end +abstract type SyntaxStructure <: Formula end -function composeformulas(c::Connective, φs::NTuple{N,AbstractSyntaxStructure}) where {N} +function composeformulas(c::Connective, φs::NTuple{N,SyntaxStructure}) where {N} return composeformulas(c, tree.(φs)) end @@ -288,21 +315,52 @@ end import AbstractTrees: children """ - abstract type SyntaxTree <: AbstractSyntaxStructure end + abstract type SyntaxTree <: SyntaxStructure end Abstract type for [syntax trees](https://en.wikipedia.org/wiki/Abstract_syntax_tree); that is, syntax leaves (see `SyntaxLeaf`, such as `Truth` values and `Atom`s), and their composition via `Connective`s (i.e., `SyntaxBranch`). -!!! note - Note that `SyntaxTree`s are *ranked trees*, - and (should) adhere to the `AbstractTrees` interface. +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`](@ref) + +# 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`](@ref), [`SyntaxBranch`](@ref), -[`AbstractSyntaxStructure`](@ref), [`Formula`](@ref). +[`SyntaxStructure`](@ref), [`Formula`](@ref). """ -abstract type SyntaxTree <: AbstractSyntaxStructure end +abstract type SyntaxTree <: SyntaxStructure end tree(φ::SyntaxTree) = φ @@ -316,17 +374,16 @@ function token(φ::SyntaxTree) return error("Please, provide method token(::$(typeof(φ))).") end -"""$(doc_arity)""" arity(φ::SyntaxTree) = length(children(φ)) -function height(φ::SyntaxTree) +function height(φ::SyntaxTree)::Int return length(children(φ)) == 0 ? 0 : 1 + maximum(height(c) for c in children(φ)) end function tokens(φ::SyntaxTree) # ::AbstractVector{<:SyntaxToken} return SyntaxToken[vcat(tokens.(children(φ))...)..., token(φ)] end -function atoms(φ::SyntaxTree) # ::AbstractVector{<:Atom} - a = token(φ) isa Atom ? [token(φ)] : [] +function atoms(φ::SyntaxTree) # ::AbstractVector{<:AbstractAtom} + a = token(φ) isa AbstractAtom ? [token(φ)] : [] return Atom[vcat(atoms.(children(φ))...)..., a...] end function truths(φ::SyntaxTree) # ::AbstractVector{<:Truth} @@ -345,26 +402,26 @@ function operators(φ::SyntaxTree) # ::AbstractVector{<:Operator} c = token(φ) isa Operator ? [token(φ)] : [] return Operator[vcat(operators.(children(φ))...)..., c...] end -function ntokens(φ::SyntaxTree)::Integer +function ntokens(φ::SyntaxTree)::Int return length(children(φ)) == 0 ? 1 : 1 + sum(ntokens(c) for c in children(φ)) end -function natoms(φ::SyntaxTree)::Integer - a = token(φ) isa Atom ? 1 : 0 +function natoms(φ::SyntaxTree)::Int + a = token(φ) isa AbstractAtom ? 1 : 0 return length(children(φ)) == 0 ? a : a + sum(natoms(c) for c in children(φ)) end -function ntruths(φ::SyntaxTree)::Integer +function ntruths(φ::SyntaxTree)::Int t = token(φ) isa Truth ? 1 : 0 return length(children(φ)) == 0 ? t : t + sum(ntruths(c) for c in children(φ)) end -function nleaves(φ::SyntaxTree)::Integer +function nleaves(φ::SyntaxTree)::Int op = token(φ) isa SyntaxLeaf ? 1 : 0 return length(children(φ)) == 0 ? op : op + sum(nleaves(c) for c in children(φ)) end -function nconnectives(φ::SyntaxTree)::Integer +function nconnectives(φ::SyntaxTree)::Int c = token(φ) isa Connective ? 1 : 0 return length(children(φ)) == 0 ? c : c + sum(nconnectives(c) for c in children(φ)) end -function noperators(φ::SyntaxTree)::Integer +function noperators(φ::SyntaxTree)::Int op = token(φ) isa Operator ? 1 : 0 return length(children(φ)) == 0 ? op : op + sum(noperators(c) for c in children(φ)) end @@ -383,7 +440,7 @@ Base.hash(φ::SyntaxTree) = Base.hash(token(φ), Base.hash(children(φ))) # Helpers tokentype(φ::SyntaxTree) = typeof(token(φ)) tokenstype(φ::SyntaxTree) = Union{tokentype(φ),tokenstype.(children(φ))...} -atomstype(φ::SyntaxTree) = typeintersect(Atom, tokenstype(φ)) +atomstype(φ::SyntaxTree) = typeintersect(AbstractAtom, tokenstype(φ)) truthstype(φ::SyntaxTree) = typeintersect(Truth, tokenstype(φ)) leavestype(φ::SyntaxTree) = typeintersect(SyntaxLeaf, tokenstype(φ)) connectivestype(φ::SyntaxTree) = typeintersect(Connective, tokenstype(φ)) @@ -403,8 +460,8 @@ end # Syntax tree, the universal syntax structure representation, # wins when promoted with syntax structures/tokens and syntax trees. Base.promote_rule(::Type{<:SyntaxTree}, ::Type{<:SyntaxTree}) = SyntaxTree -Base.promote_rule(::Type{<:AbstractSyntaxStructure}, ::Type{S}) where {S<:SyntaxTree} = S -Base.promote_rule(::Type{S}, ::Type{<:AbstractSyntaxStructure}) where {S<:SyntaxTree} = S +Base.promote_rule(::Type{<:SyntaxStructure}, ::Type{S}) where {S<:SyntaxTree} = S +Base.promote_rule(::Type{S}, ::Type{<:SyntaxStructure}) where {S<:SyntaxTree} = S # TODO figure out: are both of these needed? Maybe one of the two is enough SyntaxTree(φ::Formula) = tree(φ) @@ -429,18 +486,22 @@ end ############################################################################################ """ - abstract type SyntaxLeaf <: AbstractSyntaxStructure end + abstract 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. -See also [`AbstractSyntaxStructure`](@ref), [`arity`](@ref), [`SyntaxBranch`](@ref). +# Interface +- `syntaxstring(s::SyntaxLeaf; kwargs...)::String` +- `dual(s::SyntaxLeaf)::SyntaxLeaf` +- `hasdual(s::SyntaxLeaf)::Bool` + +See also [`SyntaxStructure`](@ref), [`arity`](@ref), [`SyntaxBranch`](@ref). """ abstract type SyntaxLeaf <: SyntaxTree end children(::SyntaxLeaf) = () - token(φ::SyntaxLeaf) = φ ############################################################################################ @@ -459,84 +520,199 @@ const SyntaxToken = Union{Connective,SyntaxLeaf} """$(doc_dual)""" dual(t::SyntaxToken) = error("Please, provide method dual(::$(typeof(t))).") -"""$(doc_dual)""" +"""See [`dual`](@ref)""" hasdual(t::SyntaxToken) = false -"""$(doc_formula_basein)""" -function Base.in(tok::SyntaxToken, φ::SyntaxTree)::Bool # TODO Note that this is interface for SyntaxTree's - return error("Please, provide method Base.in(tok::$(typeof(tok)), φ::$(typeof(φ))).") -end - -function Base.in(tok::SyntaxToken, φ::Formula)::Bool - return Base.in(tok, tree(φ)) -end - -function Base.in(tok::SyntaxToken, φ::SyntaxLeaf)::Bool - return tok == φ -end - ############################################################################################ -#### Atom ################################################################################## +#### AbstractAtom ########################################################################## ############################################################################################ """ - struct Atom{V} <: SyntaxLeaf - value::V - end + abstract type AbstractAtom <: SyntaxLeaf end An atom, sometimes called an atomic proposition, -propositional letter (or simply *letter*), of type -`Atom{V}` wraps a `value::V` representing a fact which truth can be assessed on +propositional letter (or simply *letter*), +representing a fact which truth can be assessed on a logical interpretation. Atoms are nullary tokens (i.e, they are at the leaves of a syntax tree); note that their atoms cannot be `Atom`s. +# Interface +- `syntaxstring(s::AbstractAtom; kwargs...)::String` +- `dual(s::AbstractAtom)::AbstractAtom` +- `hasdual(s::AbstractAtom)::Bool` + See also [`AbstractInterpretation`](@ref), [`atoms`](@ref), [`check`](@ref), [`SyntaxToken`](@ref). """ -struct Atom{V} <: SyntaxLeaf - value::V +abstract type AbstractAtom <: SyntaxLeaf end - function Atom{V}(value::V) where {V} - @assert !(value isa Union{Formula,Connective}) "Illegal nesting. " * - "Cannot instantiate Atom with value of type $(typeof(value))" - new{V}(value) - end - function Atom(value::V) where {V} - Atom{V}(value) - end - function Atom{V}(p::Atom) where {V} - Atom{V}(value(p)) - end - function Atom(p::Atom) - p +############################################################################################ +#### AbstractSyntaxBranch ################################################################## +############################################################################################ + +""" + abstract type AbstractSyntaxBranch <: SyntaxTree end + +An internal node of a syntax tree encoding a logical formula. +Such a node holds a syntax `token` (a `Connective`), +and has as many children as the `arity` of the token. + +# Interface +- `children(φ::SyntaxTree)::NTuple{N,SyntaxTree} where N` +- `token(φ::SyntaxTree)::Connective` +- See also [`Syntactical`](@ref) + +See also +[`token`](@ref), [`children`](@ref), +[`arity`](@ref), +[`Connective`](@ref), +[`height`](@ref), +[`atoms`](@ref), [`natoms`](@ref), +[`operators`](@ref), [`noperators`](@ref), +[`tokens`](@ref), [`ntokens`](@ref), +""" +abstract type AbstractSyntaxBranch <: SyntaxTree end + +function syntaxstring( + φ::AbstractSyntaxBranch; + function_notation = false, + remove_redundant_parentheses = true, + parenthesize_atoms = !remove_redundant_parentheses, + parenthesization_level = 1, + parenthesize_commutatives = false, + kwargs..., +)::String + ch_kwargs = merge((; kwargs...), + (; + function_notation = function_notation, + remove_redundant_parentheses = remove_redundant_parentheses, + parenthesize_atoms = parenthesize_atoms, + parenthesization_level = parenthesization_level, + parenthesize_commutatives = parenthesize_commutatives, + ),) + + # Parenthesization rules for binary operators in infix notation + function _binary_infix_syntaxstring( + ptok::SyntaxToken, + ch::SyntaxTree, + childtype::Symbol, + ) + chtok = token(ch) + chtokstring = syntaxstring(ch; ch_kwargs...) + + parenthesize = begin + if !remove_redundant_parentheses + true + elseif arity(chtok) == 0 + if chtok isa Atom && parenthesize_atoms + true + else + false + end + elseif arity(chtok) == 2 # My child is infix + tprec = precedence(ptok) + chprec = precedence(chtok) + if ptok == chtok + if !parenthesize_commutatives && iscommutative(ptok) + false + elseif associativity(ptok) == :left && childtype == :left + false # a ∧ b ∧ c = (a ∧ b) ∧ c + elseif associativity(ptok) == :right && childtype == :right + false # a → b → c = a → (b → c) + else + true + end + elseif tprec == chprec # Read left to right + if childtype == :left + false + elseif childtype == :right + true + end + elseif tprec < chprec + if chprec - tprec <= parenthesization_level + true + else + false + end + elseif tprec > chprec + true + # # 1st condition, before "||" -> "◊¬p ∧ ¬q" instead of "(◊¬p) ∧ (¬q)" + # # 2nd condition, after "||" -> "(q → p) → ¬q" instead of "q → p → ¬q" <- Not sure: wrong? + # # 3nd condition + # @show !(tprec <= chprec) + # @show ((chprec-tprec) <= parenthesization_level) + # @show tprec <= chprec + # @show chprec-tprec + # @show chprec-tprec <= parenthesization_level + # @show iscommutative(ptok) + # @show ptok, chtok, iscommutative(ptok), tprec, chprec + # @show ((!iscommutative(ptok) || ptok != chtok) && (tprec > chprec)) + # @show (!iscommutative(ptok) && tprec <= chprec) + + # if ( + # (tprec > chprec && (!iscommutative(ptok) || ptok != chtok)) || # 1 + # (tprec <= chprec && (!iscommutative(ptok))) # 2 + # ) + # true + # else + # false + # end + end + else + false + end + end + lpar, rpar = parenthesize ? ["(", ")"] : ["", ""] + return "$(lpar)$(chtokstring)$(rpar)" end -end -value(p::Atom) = p.value + tok = token(φ) + tokstr = syntaxstring(tok; ch_kwargs...) -dual(p::Atom) = Atom(dual(value(p))) -hasdual(p::Atom) = hasdual(value(p)) -hasdual(value) = false -dual(value) = error("Please, provide method SoleLogics.dual(::$(typeof(value))).") # TODO explain why? + if arity(tok) == 0 + # Leaf nodes parenthesization is parent's respsonsability + return tokstr + elseif arity(tok) == 2 && !function_notation + # Infix notation for binary operators -valuetype(::Atom{V}) where {V} = V -valuetype(::Type{Atom{V}}) where {V} = V + "$(_binary_infix_syntaxstring(tok, children(φ)[1], :left)) " * + "$tokstr $(_binary_infix_syntaxstring(tok, children(φ)[2], :right))" + else + # Infix notation with arity != 2, or function notation + lpar, rpar = "(", ")" + ch = token(children(φ)[1]) + charity = arity(ch) + if !function_notation && arity(tok) == 1 && + (charity == 1 || (ch isa Atom && !parenthesize_atoms)) + # When not in function notation, print "¬p" instead of "¬(p)"; + # note that "◊((p ∧ q) → s)" must not be simplified as "◊(p ∧ q) → s". + lpar, rpar = "", "" + end -Base.convert(::Type{A}, p::Atom) where {A<:Atom} = A(p) -Base.convert(::Type{A}, a) where {A<:Atom} = A(a) + if length(children(φ)) == 0 + tokstr + else + tokstr * "$(lpar)" * + join( + [syntaxstring(c; ch_kwargs...) for c in children(φ)], ", ",) * "$(rpar)" + end + end +end -Base.isequal(a::Atom, b::Atom) = Base.isequal(value(a), value(b)) # Needed to avoid infinite recursion -Base.isequal(a::Atom, b) = Base.isequal(value(a), b) -Base.isequal(a, b::Atom) = Base.isequal(a, value(b)) -Base.isequal(a::Atom, b::SyntaxTree) = (a == b) # Needed for resolving ambiguities -Base.isequal(a::SyntaxTree, b::Atom) = (a == b) # Needed for resolving ambiguities -Base.hash(a::Atom) = Base.hash(value(a)) +"""$(doc_formula_basein)""" +function Base.in(tok::SyntaxToken, φ::Formula)::Bool + return Base.in(tok, tree(φ)) +end -syntaxstring(a::Atom; kwargs...)::String = syntaxstring(value(a); kwargs...) +function Base.in(tok::SyntaxToken, tree::AbstractSyntaxBranch)::Bool + return tok == token(tree) || any([Base.in(tok, c) for c in children(tree)]) +end -syntaxstring(value; kwargs...) = string(value) +function Base.in(tok::SyntaxToken, φ::SyntaxLeaf)::Bool + return tok == φ +end ############################################################################################ #### Truth ################################################################################# @@ -551,6 +727,16 @@ In Boolean logic, the two [`BooleanTruth`](@ref) values TOP (⊤) and BOT (⊥) See also [`BooleanTruth`](@ref). +# 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](https://en.wikipedia.org/wiki/Three-valued_logic), that is, an algebra with three truth values @@ -616,9 +802,20 @@ See also [`istop`](@ref), [`Truth`](@ref). isbot(t::Truth)::Bool = false """ -TODO docstring. + precedes(t1::Truth, t2::Truth)::Bool + +Encodes the order relation (also denoted as `≺`) between truth values. + +# Examples +``` +julia> using SoleLogics + + +julia> SoleLogics.precedes(⊥, ⊤) +true +``` """ -function precedes(t1::Truth, t2::Truth) +function precedes(t1::Truth, t2::Truth)::Bool if Base.isequal(t1, t2) return false else @@ -626,14 +823,13 @@ function precedes(t1::Truth, t2::Truth) end end -function truthmeet(t1::Truth, t2::Truth) +function truthmeet(t1::Truth, t2::Truth)::Truth error("Please, provide method truthmeet(::$(typeof(t1)), ::$(typeof(t2))).") end -function truthjoin(t1::Truth, t2::Truth) +function truthjoin(t1::Truth, t2::Truth)::Truth error("Please, provide method truthjoin(::$(typeof(t1)), ::$(typeof(t2))).") end - # Alias """Alias for [`precedes`](@ref).""" const ≺ = precedes @@ -663,6 +859,7 @@ composeformulas(c::Truth, ::Tuple{}) = c function istop(φ::Formula) false end + ############################################################################################ #### Operator ############################################################################## ############################################################################################ @@ -708,387 +905,10 @@ function (op::Operator)(φs::NTuple{N,Formula}) where {N} end end - if AbstractSyntaxStructure <: typejoin(typeof.(φs)...) + if SyntaxStructure <: typejoin(typeof.(φs)...) φs = Base.promote(φs...) end return composeformulas(op, φs) end (c::Truth)(::Tuple{}) = c - -############################################################################################ -#### SyntaxBranch ########################################################################## -############################################################################################ - -""" - struct SyntaxBranch <: SyntaxTree - token::Connective - children::NTuple{N,SyntaxTree} where {N} - end - -An internal node of a syntax tree encoding a logical formula. -Such a node holds a syntax `token` (a `Connective`, -and has as many children as the `arity` of the token. - -This implementation is *arity-compliant*, in that, upon construction, -the arity of the token is checked against the number of children provided. - -# Examples -```julia-repl -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`](@ref), [`children`](@ref), -[`arity`](@ref), -[`Connective`](@ref), -[`height`](@ref), -[`atoms`](@ref), [`natoms`](@ref), -[`operators`](@ref), [`noperators`](@ref), -[`tokens`](@ref), [`ntokens`](@ref), -""" -struct SyntaxBranch <: SyntaxTree - - # The syntax token at the current node - token::Connective - - # The child nodes of the current node - children::NTuple{N,SyntaxTree} where {N} - - function _aritycheck(N, token, children) - @assert arity(token) == N "Cannot instantiate SyntaxBranch with token " * - "$(token) of arity $(arity(token)) and $(N) children." - return nothing - end - - function SyntaxBranch( - token::Connective, - children::NTuple{N,SyntaxTree} = (), - ) where {N} - _aritycheck(N, token, children) - return new(token, children) - end - - # Helpers - function SyntaxBranch(token::Connective, children...) - return SyntaxBranch(token, children) - end - -end - -children(φ::SyntaxBranch) = φ.children -token(φ::SyntaxBranch) = φ.token - -function syntaxstring( - φ::SyntaxBranch; - function_notation = false, - remove_redundant_parentheses = true, - parenthesize_atoms = !remove_redundant_parentheses, - parenthesization_level = 1, - parenthesize_commutatives = false, - kwargs... -)::String - ch_kwargs = merge((; kwargs...), (; - function_notation = function_notation, - remove_redundant_parentheses = remove_redundant_parentheses, - parenthesize_atoms = parenthesize_atoms, - parenthesization_level = parenthesization_level, - parenthesize_commutatives = parenthesize_commutatives, - )) - - # Parenthesization rules for binary operators in infix notation - function _binary_infix_syntaxstring( - ptok::SyntaxToken, - ch::SyntaxTree, - childtype::Symbol - ) - chtok = token(ch) - chtokstring = syntaxstring(ch; ch_kwargs...) - - parenthesize = begin - if !remove_redundant_parentheses - true - elseif arity(chtok) == 0 - if chtok isa Atom && parenthesize_atoms - true - else - false - end - elseif arity(chtok) == 2 # My child is infix - tprec = precedence(ptok) - chprec = precedence(chtok) - if ptok == chtok - if !parenthesize_commutatives && iscommutative(ptok) - false - elseif associativity(ptok) == :left && childtype == :left - false # a ∧ b ∧ c = (a ∧ b) ∧ c - elseif associativity(ptok) == :right && childtype == :right - false # a → b → c = a → (b → c) - else - true - end - elseif tprec == chprec # Read left to right - if childtype == :left - false - elseif childtype == :right - true - end - elseif tprec < chprec - if chprec-tprec <= parenthesization_level - true - else - false - end - elseif tprec > chprec - true - # # 1st condition, before "||" -> "◊¬p ∧ ¬q" instead of "(◊¬p) ∧ (¬q)" - # # 2nd condition, after "||" -> "(q → p) → ¬q" instead of "q → p → ¬q" <- Not sure: wrong? - # # 3nd condition - # @show !(tprec <= chprec) - # @show ((chprec-tprec) <= parenthesization_level) - # @show tprec <= chprec - # @show chprec-tprec - # @show chprec-tprec <= parenthesization_level - # @show iscommutative(ptok) - # @show ptok, chtok, iscommutative(ptok), tprec, chprec - # @show ((!iscommutative(ptok) || ptok != chtok) && (tprec > chprec)) - # @show (!iscommutative(ptok) && tprec <= chprec) - - - # if ( - # (tprec > chprec && (!iscommutative(ptok) || ptok != chtok)) || # 1 - # (tprec <= chprec && (!iscommutative(ptok))) # 2 - # ) - # true - # else - # false - # end - end - else - false - end - end - lpar, rpar = parenthesize ? ["(", ")"] : ["", ""] - return "$(lpar)$(chtokstring)$(rpar)" - end - - tok = token(φ) - tokstr = syntaxstring(tok; ch_kwargs...) - - if arity(tok) == 0 - # Leaf nodes parenthesization is parent's respsonsability - return tokstr - elseif arity(tok) == 2 && !function_notation - # Infix notation for binary operators - - "$(_binary_infix_syntaxstring(tok, children(φ)[1], :left)) " * - "$tokstr $(_binary_infix_syntaxstring(tok, children(φ)[2], :right))" - else - # Infix notation with arity != 2, or function notation - lpar, rpar = "(", ")" - ch = token(children(φ)[1]) - charity = arity(ch) - if !function_notation && arity(tok) == 1 && - (charity == 1 || (ch isa Atom && !parenthesize_atoms)) - # When not in function notation, print "¬p" instead of "¬(p)"; - # note that "◊((p ∧ q) → s)" must not be simplified as "◊(p ∧ q) → s". - lpar, rpar = "", "" - end - - if length(children(φ)) == 0 - tokstr - else - tokstr * "$(lpar)" * join( - [syntaxstring(c; ch_kwargs...) for c in children(φ)], ", ") * "$(rpar)" - end - end -end - -function Base.in(tok::SyntaxToken, tree::SyntaxBranch)::Bool - return tok == token(tree) || any([Base.in(tok, c) for c in children(tree)]) -end - -############################################################################################ -#### AbstractInterpretation ################################################################ -############################################################################################ - -""" - abstract type AbstractInterpretation end - -Abstract type for representing a [logical -interpretation](https://en.wikipedia.org/wiki/Interpretation_(logic)). -In the case of -[propositional logic](https://simple.wikipedia.org/wiki/Propositional_logic), -is essentially a map *atom → truth value*. - -Properties expressed via logical formulas can be `check`ed on logical interpretations. - -See also [`check`](@ref), [`AbstractAssignment`](@ref), [`AbstractKripkeStructure`](@ref). -""" -abstract type AbstractInterpretation end - -function valuetype(i::AbstractInterpretation) - return error("Please, provide method valuetype(::$(typeof(i))).") -end -function truthtype(i::AbstractInterpretation) - return error("Please, provide method truthtype(::$(typeof(i))).") -end - -############################################################################################ -#### Interpret & Check ##################################################################### -############################################################################################ - -""" - interpret( - φ::Formula, - i::AbstractInterpretation, - args...; - kwargs... - )::Formula - -Return the truth value for a formula on a logical interpretation (or model). - -# Examples -```julia-repl -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`](@ref), [`Formula`](@ref), [`AbstractInterpretation`](@ref), -[`AbstractAlgebra`](@ref). -""" -function interpret( - φ::Formula, - i::AbstractInterpretation, - args...; - kwargs... -)::Formula - interpret(tree(φ), i, args...; kwargs...) -end - -function interpret( - φ::Atom, - i::AbstractInterpretation, - args...; - kwargs... -)::Formula - return error("Please, provide method " * - "interpret(φ::Atom, i::$(typeof(i)), " * - "args...::$(typeof(args)); " * - "kwargs...::$(typeof(kwargs))).") -end - - - -function interpret( - φ::SyntaxBranch, - i::AbstractInterpretation, - args...; - kwargs..., -) - connective = token(φ) - ts = Tuple( - [interpret(ch, i, args...; kwargs...) for ch in children(φ)] - ) - return simplify(connective, ts, args...; kwargs...) -end - -interpret(t::Truth, i::AbstractInterpretation, args...; kwargs...) = t - -""" - check( - φ::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](https://en.wikipedia.org/wiki/Model_checking), and there are many -algorithms for it, typically depending on the complexity of the logic. - -# Examples -```julia-repl -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`](@ref), [`Formula`](@ref), [`AbstractInterpretation`](@ref), -[`TruthDict`](@ref). -""" -function check( - φ::Formula, - i::AbstractInterpretation, - args...; - kwargs... -)::Bool - istop(interpret(φ, i, args...; kwargs...)) -end - -############################################################################################ -#### Utilities ############################################################################# -############################################################################################ - -# Formula interpretation via i[φ] -> φ -Base.getindex(i::AbstractInterpretation, φ::Formula, args...; kwargs...) = - interpret(φ, i, args...; kwargs...) - -# Helper -function Base.getindex(i::AbstractInterpretation, v, args...; kwargs...) - Base.getindex(i, Atom(v), args...; kwargs...) -end - -# Formula interpretation via φ(i) -> φ -(φ::Formula)(i::AbstractInterpretation, args...; kwargs...) = - interpret(φ, i, args...; kwargs...) diff --git a/src/utils.jl b/src/utils.jl index d94c3d79..817c5d21 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,25 +1,931 @@ -# Fast isempty(intersect(u, v)) -function intersects(u, v) - for x in u - if x in v - return true +#= + Syntactical Type Hierarchy + + Syntactical + ├── Formula + │ ├── SyntaxStructure + │ │ ├── SyntaxTree + │ │ │ ├── SyntaxLeaf + │ │ │ │ ├── AbstractAtom + │ │ │ │ │ └── Atom (e.g., p) + │ │ │ │ └── Truth + │ │ │ │ ├── BooleanTruth (⊤ and ⊥) + │ │ │ │ └── ... + │ │ │ └── AbstractSyntaxBranch + │ │ │ │ └── SyntaxBranch (e.g., p ∧ q) + │ │ ├── LinearForm + │ │ │ └── LeftmostLinearForm (e.g., conjunctions, disjunctions, DNFs, CNFs) + │ │ ├── Literal (e.g., p, ¬p) + │ │ └── ... + │ ├── TruthTable + │ ├── AnchoredFormula + │ └── ... + └── Connective + ├── NamedConnective (e.g., ∧, ∨, →, ¬, □, ◊) + ├── AbstractRelationalConnective + │ ├── DiamondRelationalConnective (e.g., ⟨G⟩) + │ ├── BoxRelationalConnective (e.g., [G]) + │ └── ... + └── ... +=# + +############################################################################################ +#### Atom ################################################################################## +############################################################################################ + +""" + struct Atom{V} <: AbstractAtom + value::V + end + +Simplest atom implementation, wrapping a `value`. + +See also [`AbstractAtom`](@ref), [`value`](@ref), [`check`](@ref), +[`SyntaxToken`](@ref). +""" +struct Atom{V} <: AbstractAtom + value::V + + function Atom{V}(value::V) where {V} + @assert !(value isa Union{Formula, Connective}) "Illegal nesting. "* + "Cannot instantiate Atom with value of type $(typeof(value))" + new{V}(value) + end + function Atom(value::V) where {V} + Atom{V}(value) + end + function Atom{V}(p::Atom) where {V} + Atom{V}(value(p)) + end + function Atom(p::Atom) + p + end +end + +valuetype(::Atom{V}) where {V} = V +valuetype(::Type{Atom{V}}) where {V} = V + +value(p::Atom) = p.value + +dual(p::Atom) = Atom(dual(value(p))) +hasdual(p::Atom) = hasdual(value(p)) +hasdual(value) = false +dual(value) = error("Please, provide method SoleLogics.dual(::$(typeof(value))).") # TODO explain why? + +Base.convert(::Type{A}, p::Atom) where {A <: Atom} = A(p) +Base.convert(::Type{A}, a) where {A <: Atom} = A(a) + +Base.isequal(a::Atom, b::Atom) = Base.isequal(value(a), value(b)) # Needed to avoid infinite recursion +Base.isequal(a::Atom, b) = Base.isequal(value(a), b) +Base.isequal(a, b::Atom) = Base.isequal(a, value(b)) +Base.isequal(a::Atom, b::SyntaxTree) = (a == b) # Needed for resolving ambiguities +Base.isequal(a::SyntaxTree, b::Atom) = (a == b) # Needed for resolving ambiguities +Base.hash(a::Atom) = Base.hash(value(a)) + +syntaxstring(a::Atom; kwargs...)::String = syntaxstring(value(a); kwargs...) + +syntaxstring(value; kwargs...) = string(value) + +############################################################################################ +#### SyntaxBranch ########################################################################## +############################################################################################ + +""" + struct 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-repl +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`](@ref), [`children`](@ref), +[`arity`](@ref), +[`Connective`](@ref), +[`height`](@ref), +[`atoms`](@ref), [`natoms`](@ref), +[`operators`](@ref), [`noperators`](@ref), +[`tokens`](@ref), [`ntokens`](@ref), +""" +struct SyntaxBranch <: AbstractSyntaxBranch + + # The syntax token at the current node + token::Connective + + # The child nodes of the current node + children::NTuple{N, SyntaxTree} where {N} + + function _aritycheck(N, token, children) + @assert arity(token)==N "Cannot instantiate SyntaxBranch with token "* + "$(token) of arity $(arity(token)) and $(N) children." + return nothing + end + + function SyntaxBranch( + token::Connective, + children::NTuple{N, SyntaxTree} = (), + ) where {N} + _aritycheck(N, token, children) + return new(token, children) + end + + # Helpers + function SyntaxBranch(token::Connective, children...) + return SyntaxBranch(token, children) + end +end + +children(φ::SyntaxBranch) = φ.children +token(φ::SyntaxBranch) = φ.token + +################################################################################ +################################################################################ + +""" + collatetruth(c::Connective, ts::NTuple{N,T where T<:Truth})::Truth where {N} + +Return the truth value for a composed formula `c(t1, ..., tN)`, given the `N` +with t1, ..., tN being `Truth` values. + +See also [`simplify`](@ref), [`Connective`](@ref), [`Truth`](@ref). +""" +function collatetruth( + c::Connective, + ts::NTuple{N, T where T <: Truth}, +)::Truth where {N} + if arity(c) != length(ts) + return error("Cannot collate $(length(ts)) truth values for " * + "connective $(typeof(c)) with arity $(arity(c))).") + else + return error("Please, provide method collatetruth(::$(typeof(c)), " * + "::NTuple{$(arity(c)),$(T)}).") + end +end + +# Helper (so that collatetruth work for all operators) +collatetruth(t::Truth, ::Tuple{}) = t + +# With generic formulas, it composes formula +""" + simplify(c::Connective, ts::NTuple{N,F where F<:Formula})::Truth where {N} + +Return a formula with the same semantics of a composed formula `c(φ1, ..., φN)`, +given the `N` +immediate sub-formulas. + +See also [`collatetruth`](@ref), [`Connective`](@ref), [`Formula`](@ref). +""" +function simplify(c::Connective, φs::NTuple{N, T where T <: Formula}) where {N} + c(φs) +end + +function simplify(c::Connective, φs::NTuple{N, T where T <: Truth}) where {N} + collatetruth(c, φs) +end + +############################################################################################ +##################################### BASE CONNECTIVES ##################################### +############################################################################################ + +""" + struct NamedConnective{Symbol} <: Connective end + +A singleton type for representing connectives defined by a name or a symbol. + +# Examples +The AND connective (i.e., the logical conjunction) is defined as the subtype: + + const CONJUNCTION = NamedConnective{:∧}() + const ∧ = CONJUNCTION + arity(::typeof(∧)) = 2 + +See also [`NEGATION`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref), +[`IMPLICATION`](@ref), [`Connective`](@ref). +""" +struct NamedConnective{Symbol} <: Connective end + +name(::NamedConnective{S}) where {S} = S + +Base.show(io::IO, c::NamedConnective) = print(io, "$(syntaxstring(c))") + +syntaxstring(c::NamedConnective; kwargs...) = string(name(c)) + +function precedence(c::NamedConnective) + op = SoleLogics.name(c) + # Using default Base.operator_precedence is risky. For example, + # Base.isoperator(:(¬)) is true, but Base.operator_precedence(:(¬)) is 0. + # See Base.operator_precedence documentation. + if !Base.isoperator(op) || Base.operator_precedence(op) == 0 + error("Please, provide method SoleLogics.precedence(::$(typeof(c))).") + else + Base.operator_precedence(op) + end +end + +function associativity(c::NamedConnective) + op = SoleLogics.name(c) + # Base.isoperator(:(++)) is true, but Base.operator_precedence(:(++)) is :none + if !Base.isoperator(op) || !(Base.operator_associativity(op) in [:left, :right]) + error("Please, provide method SoleLogics.associativity(::$(typeof(c))).") + else + Base.operator_associativity(op) + end +end + +doc_NEGATION = """ + const NEGATION = NamedConnective{:¬}() + const ¬ = NEGATION + arity(::typeof(¬)) = 1 + +Logical negation (also referred to as complement). +It can be typed by `\\neg`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_NEGATION)""" +const NEGATION = NamedConnective{:¬}() +"""$(doc_NEGATION)""" +const ¬ = NEGATION +arity(::typeof(¬)) = 1 + +# ¬ is a risky symbol, since by default it's precedence is defaulted to 0 by julia. +# Because of this, we override Base.operator_precedence. +precedence(::typeof(¬)) = Base.operator_precedence(:∧) + 1 + +doc_CONJUNCTION = """ + const CONJUNCTION = NamedConnective{:∧}() + const ∧ = CONJUNCTION + arity(::typeof(∧)) = 2 + +Logical conjunction. +It can be typed by `\\wedge`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_CONJUNCTION)""" +const CONJUNCTION = NamedConnective{:∧}() +"""$(doc_CONJUNCTION)""" +const ∧ = CONJUNCTION +arity(::typeof(∧)) = 2 + +doc_DISJUNCTION = """ + const DISJUNCTION = NamedConnective{:∨}() + const ∨ = DISJUNCTION + arity(::typeof(∨)) = 2 + +Logical disjunction. +It can be typed by `\\vee`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_DISJUNCTION)""" +const DISJUNCTION = NamedConnective{:∨}() +"""$(doc_DISJUNCTION)""" +const ∨ = DISJUNCTION +arity(::typeof(∨)) = 2 + +doc_IMPLICATION = """ + const IMPLICATION = NamedConnective{:→}() + const → = IMPLICATION + arity(::typeof(→)) = 2 + +Logical implication. +It can be typed by `\\to`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_IMPLICATION)""" +const IMPLICATION = NamedConnective{:→}() +"""$(doc_IMPLICATION)""" +const → = IMPLICATION +arity(::typeof(→)) = 2 + +iscommutative(::typeof(∧)) = true +iscommutative(::typeof(→)) = false +iscommutative(::typeof(∨)) = true + +hasdual(::typeof(∧)) = true +dual(c::typeof(∧)) = typeof(∨) +hasdual(::typeof(∨)) = true +dual(c::typeof(∨)) = typeof(∧) + +############################################################################################ +###################################### BOOLEAN ALGEBRA ##################################### +############################################################################################ + +""" + struct BooleanTruth <: Truth + flag::Bool + end + +Structure for representing the Boolean truth values ⊤ and ⊥. +It wraps a flag which takes value `true` for ⊤ ([`TOP`](@ref)), +and `false` for ⊥ ([`BOT`](@ref)) + +See also [`BooleanAlgebra`](@ref). +""" +struct BooleanTruth <: Truth + flag::Bool +end + +istop(t::BooleanTruth) = t.flag +isbot(t::BooleanTruth) = !istop(t) + +syntaxstring(t::BooleanTruth; kwargs...) = istop(t) ? "⊤" : "⊥" + +function Base.show(io::IO, φ::BooleanTruth) + print(io, "$(syntaxstring(φ))") +end + +doc_TOP = """ + const TOP = BooleanTruth(true) + const ⊤ = TOP + +Canonical truth operator representing the value `true`. +It can be typed by `\\top`. + +See also [`BOT`](@ref), [`Truth`](@ref). +""" +"""$(doc_TOP)""" +const TOP = BooleanTruth(true) +"""$(doc_TOP)""" +const ⊤ = TOP + +doc_BOTTOM = """ + const BOT = BooleanTruth(false) + const ⊥ = BOT + +Canonical truth operator representing the value `false`. +It can be typed by `\\bot`. + +See also [`TOP`](@ref), [`Truth`](@ref). +""" +"""$(doc_BOTTOM)""" +const BOT = BooleanTruth(false) +"""$(doc_BOTTOM)""" +const ⊥ = BOT + +# NOTE: it could be useful to provide a macro to easily create +# a new set of Truth types. In particular, a new subtree of types must be planted +# as children of Truth, and new promotion rules are to be defined like below. +Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:BooleanTruth}) = BooleanTruth + +function Base.convert(::Type{BooleanTruth}, t::Bool)::BooleanTruth + return (t ? TOP : BOT) +end +function Base.convert(::Type{BooleanTruth}, t::Integer)::BooleanTruth + if isone(t) + return TOP + elseif iszero(t) + return BOT + else + return error("Cannot interpret Integer value $t as BooleanTruth.") + end +end + +Base.convert(::Type{Truth}, t::Bool) = Base.convert(BooleanTruth, t) +Base.convert(::Type{Truth}, t::Integer) = Base.convert(BooleanTruth, t) + +# NOTE: are these useful? +hasdual(::BooleanTruth) = true +dual(c::BooleanTruth) = BooleanTruth(!istop(c)) + +precedes(t1::BooleanTruth, t2::BooleanTruth) = istop(t1) < istop(t2) +truthmeet(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t1 : t2 +truthjoin(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t2 : t1 + +""" + struct BooleanAlgebra <: AbstractAlgebra{Bool} end + +A [Boolean algebra](https://en.wikipedia.org/wiki/Boolean_algebra), defined on the values +TOP (representing *truth*) and BOT (for bottom, representing *falsehood*). +For this algebra, the basic operators negation, +conjunction and disjunction (stylized as ¬, ∧, ∨) can be defined as the complement, minimum +and maximum, of the integer cast of `true` and `false`, respectively. + +See also [`Truth`](@ref). +""" +struct BooleanAlgebra <: AbstractAlgebra{BooleanTruth} end + +domain(::BooleanAlgebra) = [TOP, BOT] + +top(::BooleanAlgebra) = TOP +bot(::BooleanAlgebra) = BOT + +############################################################################################ + +# Standard semantics for NOT, AND, OR, IMPLIES +collatetruth(::typeof(¬), (ts,)::Tuple{BooleanTruth}) = istop(ts) ? BOT : TOP +function collatetruth(::typeof(∧), (t1, t2)::NTuple{N, T where T <: BooleanTruth}) where {N} + truthmeet(t1, t2) +end +function collatetruth(::typeof(∨), (t1, t2)::NTuple{N, T where T <: BooleanTruth}) where {N} + truthjoin(t1, t2) +end + +# Incomplete information +function simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth, BooleanTruth}) + istop(t1) && istop(t2) ? TOP : BOT +end +simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth, Formula}) = istop(t1) ? t2 : t1 +simplify(::typeof(∧), (t1, t2)::Tuple{Formula, BooleanTruth}) = istop(t2) ? t1 : t2 + +function simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth, BooleanTruth}) + isbot(t1) && isbot(t2) ? BOT : TOP +end +simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth, Formula}) = isbot(t1) ? t2 : t1 +simplify(::typeof(∨), (t1, t2)::Tuple{Formula, BooleanTruth}) = isbot(t2) ? t1 : t2 + +# The IMPLIES operator, →, falls back to using ¬ and ∨ +function collatetruth(::typeof(→), (t1, t2)::NTuple{2, BooleanTruth}) + return collatetruth(∨, (collatetruth(¬, (t1,)), t2)) +end + +############################################################################################ + +# With dense, discrete algebras, floats can be used. +# These are sketches for a few ideas. Note that truth values should be wrapped into Truth substructures: +# istop(ts::AbstractFloat)::Bool = isone(ts) +# isbot(ts::AbstractFloat)::Bool = iszero(ts) + +# # TODO idea: use full range for numbers! +# # istop(ts::AbstractFloat)::Bool = ts == typemax(typeof(ts)) +# # isbot(ts::AbstractFloat)::Bool = ts == typemin(typeof(ts)) +# istop(ts::Integer)::Bool = ts == typemax(typeof(ts)) +# isbot(ts::Integer)::Bool = ts == typemin(typeof(ts)) + +# TODO: +# struct DiscreteChainAlgebra{T} <: AbstractAlgebra{T} domain::Vector{T} end +# struct DenseChainAlgebra{T<:AbstractFloat} <: AbstractAlgebra{T} end + +# TODO: +# struct HeytingNode{T} end +# struct HeytingAlgebra{T} <: AbstractAlgebra{HeytingNode{T}} ... end + +############################################################################################ +########################################### LOGIC ########################################## +############################################################################################ + +""" + struct BaseLogic{G<:AbstractGrammar,A<:AbstractAlgebra} <: AbstractLogic{G,A} + grammar::G + algebra::A + end + +A basic logic based on a grammar and an algebra, where both the grammar and the algebra +are instantiated. + +See also [`grammar`](@ref), [`algebra`](@ref), +[`AbstractGrammar`](@ref), [`AbstractAlgebra`](@ref), [`AbstractLogic`](@ref). +""" +struct BaseLogic{G <: AbstractGrammar, A <: AbstractAlgebra} <: AbstractLogic{G, A} + grammar::G + algebra::A + + function BaseLogic{G, A}( + grammar::G = BASE_GRAMMAR, + algebra::A = BooleanAlgebra(), + ) where {G <: AbstractGrammar, A <: AbstractAlgebra} + # @assert all([goeswith(c, algebra) for c in operators(grammar)]) "Cannot instantiate BaseLogic{$(G),$(A)}: operators $(operators(grammar)[[goeswith(c, algebra) for c in operators(grammar)]]) cannot be interpreted on $(algebra)." # requires `goeswith` trait + return new{G, A}(grammar, algebra) + end + + function BaseLogic{G}( + grammar::G = BASE_GRAMMAR, + algebra::A = BooleanAlgebra(), + ) where {G <: AbstractGrammar, A <: AbstractAlgebra} + return BaseLogic{G, A}(grammar, algebra) + end + + function BaseLogic( + grammar::G = BASE_GRAMMAR, + algebra::A = BooleanAlgebra(), + ) where {G <: AbstractGrammar, A <: AbstractAlgebra} + return BaseLogic{G, A}(grammar, algebra) + end +end + +grammar(l::BaseLogic) = l.grammar +algebra(l::BaseLogic) = l.algebra + +function Base.isequal(a::BaseLogic, b::BaseLogic) + return Base.isequal(grammar(a), grammar(b)) && Base.isequal(algebra(a), algebra(b)) +end + +Base.hash(a::BaseLogic) = Base.hash(algebra(a), Base.hash(grammar(a))) + +function Base.show( + io::IO, l::BaseLogic{G, A},) where {G <: AbstractGrammar, A <: AbstractAlgebra} + if G <: CompleteFlatGrammar + print(io, + "BaseLogic with:\n\t- operators = [$(join(syntaxstring.(operators(l)), ", "))];\n\t- alphabet: $(alphabet(l));\n\t- algebra: $(algebra(l)).",) + else + print(io, + "BaseLogic{$(G),$(A)}(\n\t- grammar: $(grammar(l));\n\t- algebra: $(algebra(l))\n)",) + end +end + + + + +############################################################################################ +#### ExplicitAlphabet ###################################################################### +############################################################################################ + +""" + struct ExplicitAlphabet{V} <: AbstractAlphabet{V} + atoms::Vector{Atom{V}} + end + +An alphabet wrapping atoms in a (finite) `Vector`. + +See also [`AbstractAlphabet`](@ref), [`atoms`](@ref). +""" +struct ExplicitAlphabet{V} <: AbstractAlphabet{V} + atoms::Vector{Atom{V}} + + function ExplicitAlphabet{V}(atoms) where {V} + return new{V}(collect(atoms)) + end + + function ExplicitAlphabet(atoms::AbstractVector{Atom{V}}) where {V} + return ExplicitAlphabet{V}(collect(atoms)) + end + + function ExplicitAlphabet(atoms::AbstractVector{V}) where {V} + return ExplicitAlphabet{V}(Atom.(collect(atoms))) + end +end +atoms(a::ExplicitAlphabet) = a.atoms +natoms(a::ExplicitAlphabet) = length(atoms(a)) + +function Base.convert(::Type{AbstractAlphabet}, alphabet::Vector{<:Atom}) + ExplicitAlphabet(alphabet) +end + +############################################################################################ +#### AlphabetOfAny ######################################################################### +############################################################################################ + +""" + struct AlphabetOfAny{V} <: AbstractAlphabet{V} end + +An implicit, infinite alphabet that includes all atoms with values of a subtype of V. + +See also [`AbstractAlphabet`](@ref). +""" +struct AlphabetOfAny{V} <: AbstractAlphabet{V} end +Base.isfinite(::Type{<:AlphabetOfAny}) = false +Base.in(::Atom{PV}, ::AlphabetOfAny{VV}) where {PV, VV} = (PV <: VV) + +############################################################################################ +#### UnionAlphabet ######################################################################### +############################################################################################ + +# Finite alphabet of conditions induced from a set of metaconditions +""" +Alphabet given by the *union* of a number of (sub-)alphabets. + +See also +[`UnboundedScalarAlphabet`](@ref), +[`ScalarCondition`](@ref), +[`ScalarMetaCondition`](@ref). +""" + +struct UnionAlphabet{C, A <: AbstractAlphabet{C}} <: AbstractAlphabet{C} + subalphabets::Vector{A} +end + +subalphabets(a::UnionAlphabet) = a.subalphabets +nsubalphabets(a::UnionAlphabet) = length(subalphabets(a)) + +function Base.show(io::IO, a::UnionAlphabet) + println(io, "$(typeof(a)):") + for sa in subalphabets(a) + Base.show(io, sa) + end +end + +function atoms(a::UnionAlphabet) + return Iterators.flatten(Iterators.map(atoms, subalphabets(a))) +end + +natoms(a::UnionAlphabet) = sum(natoms, subalphabets(a)) + +function Base.in(p::Atom, a::UnionAlphabet) + return any(sa -> Base.in(p, sa), subalphabets(a)) +end + +""" + randatom( + rng::Union{Integer,AbstractRNG}, + a::UnionAlphabet; + atompicking_mode::Symbol=:uniform, + subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing + )::Atom + +Sample an atom from a `UnionAlphabet`. By default, the sampling is uniform with respect to the atoms. +However, 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. + +See also [`UnionAlphabet`](@ref). +""" +function randatom( + rng::Union{Integer, AbstractRNG}, + a::UnionAlphabet; + atompicking_mode::Symbol = :uniform, + subalphabets_weights::Union{AbstractWeights, AbstractVector{<:Real}, Nothing} = nothing, +)::Atom + + # @show a + @assert atompicking_mode in [:uniform, :uniform_subalphabets, :weighted] "Value for `atompicking_mode` not..." + rng = initrng(rng) + alphs = subalphabets(a) + + if atompicking_mode == :weighted + if isnothing(subalphabets_weights) + error("`:weighted` picking_mode requires weights in `subalphabets_weights` ") + end + @assert length(subalphabets_weights)==length(alphs) "Mismatching numbers of alphabets "* + "($(length(alphs))) and weights ($(length(subalphabets_weights)))." + subalphabets_weights = StatsBase.weights(subalphabets_weights) + pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) + else + subalphabets_weights = begin + # This atomatically excludes subalphabets with empty threshold vector + if atompicking_mode == :uniform_subalphabets + # set the weight of the empty alphabets to zero + weights = Weights(ones(Int, length(alphs))) + weights[natoms.(alphs) == 0] .= 0 + elseif atompicking_mode == :uniform + weights = Weights(natoms.(alphs)) + end + weights + end + pickedalphabet = sample(rng, alphs, subalphabets_weights) + end + # @show a + # @show subalphabets_weights + # @show pickedalphabet + return randatom(rng, pickedalphabet) +end + +############################################################################################ +#### CompleteFlatGrammar ################################################################### +############################################################################################ + +""" + struct CompleteFlatGrammar{V<:AbstractAlphabet,O<:Operator} <: AbstractGrammar{V,O} + alphabet::V + operators::Vector{<:O} + end + +V grammar of all well-formed formulas obtained by the arity-complying composition +of atoms of an alphabet of type `V`, and all operators in `operators`. +With n operators, this grammar has exactly n+1 production rules. +For example, with `operators = [∧,∨]`, the grammar (in Backus-Naur form) is: + + φ ::= p | φ ∧ φ | φ ∨ φ + +with p ∈ alphabet. Note: it is *flat* in the sense that all rules substitute the same +(unique and starting) non-terminal symbol φ. + +See also [`AbstractGrammar`](@ref), [`Operator`](@ref), [`alphabet`](@ref), +[`formulas`](@ref), [`connectives`](@ref), [`operators`](@ref), [`leaves`](@ref). +""" +struct CompleteFlatGrammar{V <: AbstractAlphabet, O <: Operator} <: AbstractGrammar{V, O} + alphabet::V + operators::Vector{<:O} + + function CompleteFlatGrammar{V, O}( + alphabet::V, + operators::Vector{<:O}, + ) where {V <: AbstractAlphabet, O <: Operator} + return new{V, O}(alphabet, operators) + end + + function CompleteFlatGrammar{V}( + alphabet::V, + operators::Vector{<:Operator}, + ) where {V <: AbstractAlphabet} + return new{V, Union{typeof.(operators)...}}( + alphabet, + Vector{Union{typeof.(operators)...}}(operators), + ) + end + + function CompleteFlatGrammar( + alphabet::V, + operators::Vector{<:Operator}, + ) where {V <: AbstractAlphabet} + return new{V, Union{typeof.(operators)...}}( + alphabet, + Vector{Union{typeof.(operators)...}}(operators), + ) + end +end + +alphabet(g::CompleteFlatGrammar) = g.alphabet +operators(g::CompleteFlatGrammar) = g.operators + +""" + connectives(g::AbstractGrammar) + +List all connectives appearing in a grammar. + +See also [`Connective`](@ref), [`nconnectives`](@ref). +""" +function connectives(g::AbstractGrammar)::AbstractVector{Connective} + return filter(!isnullary, operators(g)) +end + +""" + leaves(g::AbstractGrammar) + +List all leaves appearing in a grammar. + +See also [`SyntaxLeaf`](@ref), [`nleaves`](@ref). +""" +function leaves(g::AbstractGrammar) + return [atoms(alphabet(g))..., filter(isnullary, operators(g))...] +end + +# V complete grammar includes any *safe* syntax tree that can be built with +# the grammar token types. +function Base.in(φ::SyntaxTree, g::CompleteFlatGrammar)::Bool + return if token(φ) isa Atom + token(φ) in alphabet(g) + elseif token(φ) isa Operator + if operatorstype(φ) <: operatorstype(g) + true + else + all([Base.in(c, g) for c in children(φ)]) end + else + false end - false end -inittruthvalues(truthvalues::Union{Vector{<:Truth},AbstractAlgebra}) = - return (truthvalues isa AbstractAlgebra) ? domain(truthvalues) : truthvalues +""" + formulas( + g::CompleteFlatGrammar{V,O} where {V,O}; + maxdepth::Integer, + nformulas::Union{Nothing,Integer} = nothing + )::Vector{SyntaxBranch} +Generate all formulas whose `SyntaxBranch`s that are not taller than a given `maxdepth`. -function displaysyntaxvector(a, maxnum = 8; quotes = true) - q = e->(quotes ? "\"$(e)\"" : "$(e)") - els = begin - if length(a) > maxnum - [(q.(syntaxstring.(a)[1:div(maxnum, 2)]))..., "...", (q.(syntaxstring.(a)[end-div(maxnum, 2):end]))...] +See also [`AbstractGrammar`](@ref), [`SyntaxBranch`](@ref). +""" +function formulas( + g::CompleteFlatGrammar{V, O} where {V, O}; + maxdepth::Integer, + nformulas::Union{Nothing, Integer} = nothing, +)::Vector{SyntaxTree} + @assert maxdepth >= 0 + @assert isnothing(nformulas) || nformulas > 0 + # With increasing `depth`, accumulate all formulas of length `depth` by combining all + # formulas of `depth-1` using all non-terminal symbols. + # Stop as soon as `maxdepth` is reached or `nformulas` have been generated. + depth = 0 + cur_formulas = Vector{SyntaxTree}(leaves(g)) + all_formulas = SyntaxTree[cur_formulas...] + while depth < maxdepth && (isnothing(nformulas) || length(all_formulas) < nformulas) + _nformulas = length(all_formulas) + cur_formulas = [] + for op in connectives(g) + for children in Iterators.product(fill(all_formulas, arity(op))...) + if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) + break + end + push!(cur_formulas, SyntaxTree(op, Tuple(children))) + end + if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) + break + end + end + append!(all_formulas, cur_formulas) + depth += 1 + end + return all_formulas +end + +# This dispatches are needed, since ambiguities might arise when choosing between +# in(φ::SyntaxTree, g::SoleLogics.CompleteFlatGrammar) and +# in(p::Atom, g::SoleLogics.AbstractGrammar) +Base.in(p::Atom, g::CompleteFlatGrammar) = Base.in(p, alphabet(g)) +Base.in(op::Truth, g::CompleteFlatGrammar) = (op <: operatorstype(g)) + +############################################################################################ +########################################### BASE ########################################### +############################################################################################ + +# This can be useful for standard phrasing of propositional formulas with string atoms. + +""" + const BASE_CONNECTIVES = [¬, ∧, ∨, →] + +Basic logical operators. + +See also [`NEGATION`](@ref), +[`CONJUNCTION`](@ref), +[`DISJUNCTION`](@ref), +[`IMPLICATION`](@ref), +[`Connective`](@ref). +""" +const BASE_CONNECTIVES = [¬, ∧, ∨, →] +const BaseConnectives = Union{typeof.(BASE_CONNECTIVES)...} + +const BASE_ALPHABET = AlphabetOfAny{String}() + +const BASE_GRAMMAR = CompleteFlatGrammar(BASE_ALPHABET, BASE_CONNECTIVES) +const BASE_ALGEBRA = BooleanAlgebra() + +const BASE_LOGIC = BaseLogic(BASE_GRAMMAR, BASE_ALGEBRA) + +function _baselogic(; + alphabet::Union{Nothing, Vector, AbstractAlphabet} = nothing, + operators::Union{Nothing, Vector{<:Operator}} = nothing, + grammar::Union{Nothing, AbstractGrammar} = nothing, + algebra::Union{Nothing, AbstractAlgebra} = nothing, + default_operators::Vector{<:Operator}, + logictypename::String, +) + if !(isnothing(grammar) || (isnothing(alphabet) && isnothing(operators))) + error("Cannot instantiate $(logictypename) by specifing a grammar " * + "together with argument(s): " * + join( + [ + (!isnothing(alphabet) ? ["alphabet"] : [])..., + (!isnothing(operators) ? ["operators"] : [])..., + (!isnothing(grammar) ? ["grammar"] : [])..., + ], + ", ",) * ".") + end + grammar = begin + if isnothing(grammar) + # @show alphabet + # @show operators + # @show BASE_GRAMMAR + # if isnothing(alphabet) && isnothing(operators) + # BASE_GRAMMAR + # else + alphabet = isnothing(alphabet) ? BASE_ALPHABET : alphabet + operators = begin + if isnothing(operators) + default_operators + else + if length(setdiff(operators, default_operators)) > 0 + @warn "Instantiating $(logictypename) with operators not in " * + "$(default_operators): " * + join(", ", setdiff(operators, default_operators)) * "." + end + operators + end + end + if alphabet isa Vector + alphabet = ExplicitAlphabet(map(Atom, alphabet)) + end + CompleteFlatGrammar(alphabet, operators) + # end else - q.(syntaxstring.(a)) + @assert isnothing(alphabet) && isnothing(operators) + grammar end end - "$(eltype(a))[$(join(els, ", "))]" + + algebra = isnothing(algebra) ? BASE_ALGEBRA : algebra + + return BaseLogic(grammar, algebra) end + diff --git a/test/misc.jl b/test/misc.jl index 573010fc..d258258c 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -32,8 +32,8 @@ interp2 = TruthDict(1:4, BOT) ############################################################################################ @test Formula <: Syntactical -@test AbstractSyntaxStructure <: Formula -@test SyntaxLeaf <: AbstractSyntaxStructure +@test SyntaxStructure <: Formula +@test SyntaxLeaf <: SyntaxStructure @test Truth <: SyntaxLeaf @test TOP isa Truth @@ -49,7 +49,7 @@ interp2 = TruthDict(1:4, BOT) @test Connective <: SyntaxToken @test SyntaxLeaf <: SyntaxToken -@test SyntaxTree <: AbstractSyntaxStructure +@test SyntaxTree <: SyntaxStructure @test SyntaxBranch <: SyntaxTree @test NEGATION isa NamedConnective From c5a51bc641aebe9690dd4d95162fde445eb86d1f Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:19:27 +0200 Subject: [PATCH 03/90] Add sections to docstrings --- src/types/logic.jl | 52 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/types/logic.jl b/src/types/logic.jl index 5f59952c..b160e4e5 100644 --- a/src/types/logic.jl +++ b/src/types/logic.jl @@ -35,6 +35,19 @@ julia> "mystring" in AlphabetOfAny{String}() true ``` +# Interface +- `atoms(a::AbstractAlphabet)::Bool` +- `Base.isfinite(::Type{<:AbstractAlphabet})::Bool` +- `randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...)::AbstractAtom` + +# Utility functions +- `natoms(a::AbstractAlphabet)::Bool` +- `Base.in(p::AbstractAtom, a::AbstractAlphabet)::Bool` +- `Base.eltype(a::AbstractAlphabet)` +- `randatom(a::AbstractAlphabet, args...; kwargs...)::AbstractAtom` +- `atomstype(a::AbstractAlphabet)` +- `valuetype(a::AbstractAlphabet)` + # Implementation When implementing a new alphabet type `MyAlphabet`, you should provide a method for @@ -134,8 +147,8 @@ function natoms(a::AbstractAlphabet)::Integer end """ - randatom(a::AbstractAlphabet) - randatom(rng, a::AbstractAlphabet) + randatom(a::AbstractAlphabet, args...; kwargs...) + randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...) Return a random atom from a *finite* alphabet. @@ -203,6 +216,18 @@ based on a *single* alphabet of type `V`, and a set of operators that consists of all the (singleton) child types of `O`. V context-free grammar is a simple structure for defining formulas inductively. +# Interface +- `alphabet(g::AbstractGrammar)::AbstractAlphabet` +- `Base.in(::SyntaxTree, g::AbstractGrammar)::Bool` +- `formulas(g::AbstractGrammar; kwargs...)::Vector{<:SyntaxTree}` + +# Utility functions +- `Base.in(a::AbstractAtom, g::AbstractGrammar)` +- `atomstype(g::AbstractGrammar)` +- `tokenstype(g::AbstractGrammar)` +- `operatorstype(g::AbstractGrammar)` +- `alphabettype(g::AbstractGrammar)` + See also [`alphabet`](@ref), [`AbstractAlphabet`](@ref), [`Operator`](@ref). """ @@ -251,7 +276,7 @@ Base.in(op::Connective, g::AbstractGrammar) = (op <: operatorstype(g)) maxdepth::Integer, nformulas::Union{Nothing,Integer} = nothing, args... - )::Vector{<:AbstractSyntaxBranch} + )::Vector{<:SyntaxTree} Enumerate the formulas produced by a given grammar with a finite and iterable alphabet. @@ -270,7 +295,7 @@ function formulas( maxdepth::Integer, nformulas::Union{Nothing,Integer} = nothing, args... -)::Vector{<:AbstractSyntaxBranch} +)::Vector{<:SyntaxTreeo} @assert maxdepth >= 0 @assert nformulas > 0 if isfinite(alphabet(g)) @@ -303,6 +328,16 @@ and *bot* (or minimum). Each node in the lattice represents a truth value that an atom or a formula can have on an interpretation, and the semantics of operators is given in terms of operations between truth values. +# Interface + +- `truthtype(a::AbstractAlgebra)` +- `domain(a::AbstractAlgebra)` +- `top(a::AbstractAlgebra)` +- `bot(a::AbstractAlgebra)` + +# Utility functions +- `iscrisp(a::AbstractAlgebra)` + # Implementation When implementing a new algebra type, the methods `domain`, @@ -380,6 +415,15 @@ iscrisp(a::AbstractAlgebra) = (length(domain(a)) == 2) Abstract type of a logic, which comprehends a context-free grammar (*syntax*) and an algebra (*semantics*). +# Interface + +- `grammar(l::AbstractLogic)::AbstractGrammar` +- `algebra(l::AbstractLogic)::AbstractAlgebra` + +# Utility functions +- See also [`AbstractGrammar`](@ref) +- See also [`AbstractAlgebra`](@ref) + # Implementation When implementing a new logic type, From 216de3cf4783f91454cef5f97de5ca1bd809001d Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:25:22 +0200 Subject: [PATCH 04/90] Fix --- Project.toml | 2 +- docs/Manifest.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index 30da58a0..9900db6c 100644 --- a/Project.toml +++ b/Project.toml @@ -14,7 +14,6 @@ Lazy = "50d2b5c4-7a5e-59d5-8109-a42b560f39c0" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" -Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" SoleBase = "4475fa32-7023-44a0-aa70-4813b230e492" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" ThreadSafeDicts = "4239201d-c60e-5e0a-9702-85d713665ba7" @@ -39,6 +38,7 @@ julia = "1" [extras] InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/docs/Manifest.toml b/docs/Manifest.toml index b9dacafc..b17a6e68 100644 --- a/docs/Manifest.toml +++ b/docs/Manifest.toml @@ -456,11 +456,11 @@ version = "0.12.1" [[deps.SoleLogics]] deps = ["AbstractTrees", "DataStructures", "Dictionaries", "FunctionWrappers", "Graphs", "IterTools", "Lazy", "PrettyTables", "Random", "Reexport", "Revise", "SoleBase", "StatsBase", "ThreadSafeDicts"] -git-tree-sha1 = "d1397a39e65fea01379e74a8ec99393395122ebe" -repo-rev = "many-valued-logics" +git-tree-sha1 = "629e1c1c4b46e82b4ea8435833a8bdd457b0aec1" +repo-rev = "dev" repo-url = ".." uuid = "b002da8f-3cb3-4d91-bbe3-2953433912b5" -version = "0.7.1" +version = "0.9.6" [[deps.SortingAlgorithms]] deps = ["DataStructures"] From d6602122220d41c5d70661ce5311986376cd82b2 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:32:28 +0200 Subject: [PATCH 05/90] Update documentation workflow --- .github/workflows/Documentation.yml | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index e8415bce..3df6e63b 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -1,23 +1,19 @@ -name: Documentation +name: Documenter on: push: - branches: - - main - - dev - tags: '*' + branches: [main, master] + tags: [v*] pull_request: jobs: - build: + Documenter: + permissions: + contents: write + statuses: write + name: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@latest - with: - version: '1.9' - - name: Install dependencies - run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - - name: Build and deploy + - uses: actions/checkout@v4 + - uses: julia-actions/julia-docdeploy@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token - run: julia --project=docs/ docs/make.jl + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 754a085c08c2ace6910dda47ded7f8c2efc233de Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:33:42 +0200 Subject: [PATCH 06/90] minor --- docs/make.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/make.jl b/docs/make.jl index 2b3216ae..162eee84 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -28,6 +28,7 @@ makedocs(; warnonly = :true, ) + @info "`makedocs` has finished running. " deploydocs(; From fd03a6b4d888d4dfe2f31d4f37d129183cd46896 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:34:29 +0200 Subject: [PATCH 07/90] minor --- .github/workflows/Documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 3df6e63b..30037084 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -1,7 +1,7 @@ name: Documenter on: push: - branches: [main, master] + branches: [main, master, dev] tags: [v*] pull_request: From d35f67514f8c2ab48179c24b576490ddbbafbef2 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:43:56 +0200 Subject: [PATCH 08/90] Fix doc workflow --- .github/workflows/Documentation.yml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 30037084..dbbc9ab6 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -1,19 +1,30 @@ -name: Documenter +name: Documentation + on: push: - branches: [main, master, dev] - tags: [v*] + branches: + - master # update to match your development branch (master, main, dev, trunk, ...) + tags: '*' pull_request: jobs: - Documenter: + build: permissions: + actions: write contents: write + pull-requests: read statuses: write - name: Documentation runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: julia-actions/julia-docdeploy@v1 + - uses: julia-actions/setup-julia@v2 + with: + version: '1' + - uses: julia-actions/cache@v2 + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key + run: julia --project=docs/ docs/make.jl From 108e3f948a04a089f99087c664db5cf966f8d621 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:44:08 +0200 Subject: [PATCH 09/90] Fix doc workflow --- .github/workflows/Documentation.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index dbbc9ab6..2cd9f168 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -3,7 +3,9 @@ name: Documentation on: push: branches: - - master # update to match your development branch (master, main, dev, trunk, ...) + - master + - main + - dev tags: '*' pull_request: From f30d6a86d4604f538278016b3fa21f6c68da7f66 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:46:23 +0200 Subject: [PATCH 10/90] Fix doc workflow --- .github/workflows/Documentation.yml | 27 +++++++-------------------- docs/Project.toml | 2 +- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 2cd9f168..30037084 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -1,32 +1,19 @@ -name: Documentation - +name: Documenter on: push: - branches: - - master - - main - - dev - tags: '*' + branches: [main, master, dev] + tags: [v*] pull_request: jobs: - build: + Documenter: permissions: - actions: write contents: write - pull-requests: read statuses: write + name: Documentation runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v2 - with: - version: '1' - - uses: julia-actions/cache@v2 - - name: Install dependencies - run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - - name: Build and deploy + - uses: julia-actions/julia-docdeploy@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key - run: julia --project=docs/ docs/make.jl + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/Project.toml b/docs/Project.toml index f2df945b..fbff2d91 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -4,4 +4,4 @@ SoleBase = "4475fa32-7023-44a0-aa70-4813b230e492" SoleLogics = "b002da8f-3cb3-4d91-bbe3-2953433912b5" [compat] -Documenter = "1" +Documenter = "1.7" From cdf7b1add99e1c7b5001d74a8141b79baf571e9f Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:52:14 +0200 Subject: [PATCH 11/90] Fix docs --- docs/make.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/make.jl b/docs/make.jl index 162eee84..1bca5c3a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -33,6 +33,7 @@ makedocs(; deploydocs(; repo = "github.com/aclai-lab/SoleLogics.jl", + devbranch = "dev", target = "build", branch = "gh-pages", versions = ["main" => "main", "stable" => "v^", "v#.#", "dev" => "dev"], From a6640518dce5fc61bb733b9a2320dde1ac206b6b Mon Sep 17 00:00:00 2001 From: Perro2110 Date: Thu, 10 Oct 2024 09:50:27 +0200 Subject: [PATCH 12/90] pollo --- src/types/propositional-logic.jl | 108 +++++++++++++++++++++++++ src/{ => utils}/propositional-logic.jl | 108 ------------------------- 2 files changed, 108 insertions(+), 108 deletions(-) create mode 100644 src/types/propositional-logic.jl rename src/{ => utils}/propositional-logic.jl (76%) diff --git a/src/types/propositional-logic.jl b/src/types/propositional-logic.jl new file mode 100644 index 00000000..5be60ce4 --- /dev/null +++ b/src/types/propositional-logic.jl @@ -0,0 +1,108 @@ +const BASE_PROPOSITIONAL_CONNECTIVES = BASE_CONNECTIVES +const BasePropositionalConnectives = Union{typeof.(BASE_PROPOSITIONAL_CONNECTIVES)...} + +# A propositional logic based on the base propositional operators +const BasePropositionalLogic = AbstractLogic{G,A} where { + ALP, + G<:AbstractGrammar{ALP,<:BasePropositionalConnectives}, + A<:AbstractAlgebra + } + +""" + propositionallogic(; + alphabet = AlphabetOfAny{String}(), + operators = $(BASE_PROPOSITIONAL_CONNECTIVES), + grammar = CompleteFlatGrammar(AlphabetOfAny{String}(), $(BASE_PROPOSITIONAL_CONNECTIVES)), + algebra = BooleanAlgebra() + ) + +Instantiate a [propositional logic](https://simple.wikipedia.org/wiki/Propositional_logic) +given a grammar and an algebra. Alternatively, an alphabet and a set of operators +can be specified instead of the grammar. + +# Examples +```julia-repl +julia> (¬) isa operatorstype(propositionallogic()) +true + +julia> (¬) isa operatorstype(propositionallogic(; operators = [∨])) +false + +julia> propositionallogic(; alphabet = ["p", "q"]); + +julia> propositionallogic(; alphabet = ExplicitAlphabet([Atom("p"), Atom("q")])); + +``` + +See also [`modallogic`](@ref), [`AbstractAlphabet`](@ref), [`AbstractAlgebra`](@ref). +""" +function propositionallogic(; + alphabet::Union{Nothing,Vector,AbstractAlphabet} = nothing, + operators::Union{Nothing,Vector{<:Operator}} = nothing, + grammar::Union{Nothing,AbstractGrammar} = nothing, + algebra::Union{Nothing,AbstractAlgebra} = nothing +) + _baselogic( + alphabet = alphabet, + operators = operators, + grammar = grammar, + algebra = algebra; + default_operators = BASE_PROPOSITIONAL_CONNECTIVES, + logictypename = "propositional logic", + ) +end + +############################################################################################ + +""" + abstract type AbstractAssignment <: AbstractInterpretation end + +Abstract type for assigments, that is, interpretations of propositional logic, +encoding mappings from `Atom`s to `Truth` values. + +See also [`AbstractInterpretation`](@ref). +""" +abstract type AbstractAssignment <: AbstractInterpretation end + +""" + Base.haskey(i::AbstractAssignment, ::Atom)::Bool + +Return whether an assigment has a truth value for a given atom. + +See also [`AbstractInterpretation`](@ref). +""" +function Base.haskey(i::AbstractAssignment, ::Atom)::Bool + return error("Please, provide method Base.haskey(::$(typeof(i)), ::Atom)::Bool.") +end + +# Helper +function Base.haskey(i::AbstractAssignment, v)::Bool + Base.haskey(i, Atom(v)) +end + +function inlinedisplay(i::AbstractAssignment) + return error("Please, provide method inlinedisplay(::$(typeof(i)))::String.") +end + +# When interpreting a single atom, if the lookup fails, then return the atom itself +function interpret(a::Atom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf + if Base.haskey(i, a) + Base.getindex(i, a, args...; kwargs...) + else + a + end +end + +# # TODO remove repetition!!! +# function interpret( +# φ::SyntaxBranch, +# i::AbstractAssignment, +# args...; +# kwargs..., +# ) +# connective = token(φ) +# return simplify(connective, Tuple( +# [interpret(ch, i, args...; kwargs...) for ch in children(φ)] +# ), args...; kwargs...) +# end + diff --git a/src/propositional-logic.jl b/src/utils/propositional-logic.jl similarity index 76% rename from src/propositional-logic.jl rename to src/utils/propositional-logic.jl index 50545056..ec5defd8 100644 --- a/src/propositional-logic.jl +++ b/src/utils/propositional-logic.jl @@ -1,111 +1,3 @@ -const BASE_PROPOSITIONAL_CONNECTIVES = BASE_CONNECTIVES -const BasePropositionalConnectives = Union{typeof.(BASE_PROPOSITIONAL_CONNECTIVES)...} - -# A propositional logic based on the base propositional operators -const BasePropositionalLogic = AbstractLogic{G,A} where { - ALP, - G<:AbstractGrammar{ALP,<:BasePropositionalConnectives}, - A<:AbstractAlgebra - } - -""" - propositionallogic(; - alphabet = AlphabetOfAny{String}(), - operators = $(BASE_PROPOSITIONAL_CONNECTIVES), - grammar = CompleteFlatGrammar(AlphabetOfAny{String}(), $(BASE_PROPOSITIONAL_CONNECTIVES)), - algebra = BooleanAlgebra() - ) - -Instantiate a [propositional logic](https://simple.wikipedia.org/wiki/Propositional_logic) -given a grammar and an algebra. Alternatively, an alphabet and a set of operators -can be specified instead of the grammar. - -# Examples -```julia-repl -julia> (¬) isa operatorstype(propositionallogic()) -true - -julia> (¬) isa operatorstype(propositionallogic(; operators = [∨])) -false - -julia> propositionallogic(; alphabet = ["p", "q"]); - -julia> propositionallogic(; alphabet = ExplicitAlphabet([Atom("p"), Atom("q")])); - -``` - -See also [`modallogic`](@ref), [`AbstractAlphabet`](@ref), [`AbstractAlgebra`](@ref). -""" -function propositionallogic(; - alphabet::Union{Nothing,Vector,AbstractAlphabet} = nothing, - operators::Union{Nothing,Vector{<:Operator}} = nothing, - grammar::Union{Nothing,AbstractGrammar} = nothing, - algebra::Union{Nothing,AbstractAlgebra} = nothing -) - _baselogic( - alphabet = alphabet, - operators = operators, - grammar = grammar, - algebra = algebra; - default_operators = BASE_PROPOSITIONAL_CONNECTIVES, - logictypename = "propositional logic", - ) -end - -############################################################################################ - -""" - abstract type AbstractAssignment <: AbstractInterpretation end - -Abstract type for assigments, that is, interpretations of propositional logic, -encoding mappings from `Atom`s to `Truth` values. - -See also [`AbstractInterpretation`](@ref). -""" -abstract type AbstractAssignment <: AbstractInterpretation end - -""" - Base.haskey(i::AbstractAssignment, ::Atom)::Bool - -Return whether an assigment has a truth value for a given atom. - -See also [`AbstractInterpretation`](@ref). -""" -function Base.haskey(i::AbstractAssignment, ::Atom)::Bool - return error("Please, provide method Base.haskey(::$(typeof(i)), ::Atom)::Bool.") -end - -# Helper -function Base.haskey(i::AbstractAssignment, v)::Bool - Base.haskey(i, Atom(v)) -end - -function inlinedisplay(i::AbstractAssignment) - return error("Please, provide method inlinedisplay(::$(typeof(i)))::String.") -end - -# When interpreting a single atom, if the lookup fails, then return the atom itself -function interpret(a::Atom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf - if Base.haskey(i, a) - Base.getindex(i, a, args...; kwargs...) - else - a - end -end - -# # TODO remove repetition!!! -# function interpret( -# φ::SyntaxBranch, -# i::AbstractAssignment, -# args...; -# kwargs..., -# ) -# connective = token(φ) -# return simplify(connective, Tuple( -# [interpret(ch, i, args...; kwargs...) for ch in children(φ)] -# ), args...; kwargs...) -# end - ############################################################################################ #### Implementations ####################################################################### ############################################################################################ From e93e5a0d3d98217821265a750167fcbc9ff39754 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:03:07 +0200 Subject: [PATCH 13/90] Minor --- src/SoleLogics.jl | 3 +- src/anchored-formula.jl | 3 +- src/old-code/generator.jl | 180 ---- src/old-code/install.jl | 29 - .../many-valued-logics/flew-algebras.jl | 143 --- .../many-valued-logics/heyting-algebras.jl | 837 ------------------ src/types/propositional-logic.jl | 8 +- src/{utils.jl => utils/synctactical.jl} | 9 +- 8 files changed, 11 insertions(+), 1201 deletions(-) delete mode 100644 src/old-code/generator.jl delete mode 100644 src/old-code/install.jl delete mode 100644 src/old-code/many-valued-logics/flew-algebras.jl delete mode 100644 src/old-code/many-valued-logics/heyting-algebras.jl rename src/{utils.jl => utils/synctactical.jl} (99%) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 737260a1..e5214dd4 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -76,7 +76,8 @@ export propositionallogic export TruthDict, DefaultedTruthDict export truth_table -include("propositional-logic.jl") +include("types/propositional-logic.jl") +include("utils/propositional-logic.jl") ############################################################################################ diff --git a/src/anchored-formula.jl b/src/anchored-formula.jl index e7ac14f0..9c7c5550 100644 --- a/src/anchored-formula.jl +++ b/src/anchored-formula.jl @@ -146,8 +146,7 @@ end (l::AbstractLogic)(t::SyntaxStructure, args...) = AnchoredFormula(Base.RefValue(l), t; args...) # Adapted from https://github.com/JuliaLang/julia/blob/master/base/promotion.jl -function Base._promote(x::AnchoredFormula, y::SyntaxStructure) - @inline +@inline function Base._promote(x::AnchoredFormula, y::SyntaxStructure) return (x, x(y)) end Base._promote(x::SyntaxStructure, y::AnchoredFormula) = reverse(Base._promote(y, x)) diff --git a/src/old-code/generator.jl b/src/old-code/generator.jl deleted file mode 100644 index f5a0ab78..00000000 --- a/src/old-code/generator.jl +++ /dev/null @@ -1,180 +0,0 @@ -###################### -# Models # -# generation # -###################### - -# https://hal.archives-ouvertes.fr/hal-00471255v2/document - -# Erdos-Rényi method -# Create a graph as an adjacency matrix by randomling -# sampling (probability p) the edges between n nodes. -# Convert the same graph to an adjacency list and return it. -function gnp(n::Integer, p::Float64; rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG) - M = _gnp(n, p, rng) - - worlds = Worlds([Point(i) for i = 1:n]) - adjs = Adjacents{Point}() - - # Left triangular matrix is used to generate an adjacency list - for i = 1:n - neighbors = Worlds{Point}([]) - for j = 1:i - if M[i, j] == 1 - push!(neighbors.worlds, worlds[j]) - end - end - setindex!(adjs, neighbors, worlds[i]) - end - - return adjs -end - -function _gnp(n::Integer, p::Real, rng::AbstractRNG) - M = zeros(Int8, n, n) - - for i = 1:n, j = 1:i - if rand(rng) < p - M[i, j] = 1 - end - end - - return M -end - -# Fan-in/Fan-out method -# Create a graph with n nodes as an adjacency list and return it. -# It's possible to set a global maximum to input_degree and output_degree. -# Also it's possible to choose how likely a certain "phase" will happen -# 1) _fanout increases a certain node's output_degree grow by spawning new vertices -# 2) _fanin increases the input_degree of a certain group of nodes -# by linking a single new vertices to all of them -function fanfan( - n::Integer, - id::Integer, - od::Integer; - threshold::Float64 = 0.5, - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, -) - rng = initrng(rng) - adjs = Adjacents{Point}() - setindex!(adjs, Worlds{Point}([]), Point(0)) # Ecco qua ad esempio metti un GenericWorld - - od_queue = PriorityQueue{Point,Int64}(Point(0) => 0) - - while length(adjs.adjacents) <= n - if rand(rng) <= threshold - _fanout(adjs, od_queue, od, rng) - else - _fanin(adjs, od_queue, id, od, rng) - end - end - - return adjs -end - -function _fanout( - adjs::Adjacents{Point}, - od_queue::PriorityQueue{Point,Int}, - od::Integer, - rng::AbstractRNG, -) - #= - Find the vertex v with the biggest difference between its out-degree and od. - Create a random number of vertices between 1 and (od-m) - and add edges from v to these new vertices. - =# - v, m = peek(od_queue) - - for i in rand(rng, 1:(od-m)) - new_node = Point(length(adjs)) - setindex!(adjs, Worlds{Point}([]), new_node) - push!(adjs, v, new_node) - - od_queue[new_node] = 0 - od_queue[v] = od_queue[v] + 1 - end -end - -function _fanin( - adjs::Adjacents{Point}, - od_queue::PriorityQueue{Point,Int}, - id::Integer, - od::Integer, - rng::AbstractRNG, -) - #= - Find the set S of all vertices that have out-degree < od. - Compute a subset T of S of size at most id. - Add a new vertex v and add new edges (v, t) for all t ∈ T - =# - S = filter(x -> x[2] < od, od_queue) - T = Set(sample(collect(S), rand(rng, 1:min(id, length(S))), replace = false)) - - v = Point(length(adjs)) - for t in T - setindex!(adjs, Worlds{Point}([]), v) - push!(adjs, t[1], v) - - od_queue[t[1]] = od_queue[t[1]] + 1 - od_queue[v] = 0 - end -end - -# Associate each world to a subset of proposistional letters -function dispense_alphabet( - ws::Worlds{T}; - P::LetterAlphabet = SoleLogics.alphabet(MODAL_LOGIC), - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, -) where {T<:AbstractWorld} - rng = initrng(rng) - evals = Dict{T,LetterAlphabet}() - for w in ws - evals[w] = sample(P, rand(rng, 0:length(P)), replace = false) - end - return evals -end - -# NOTE: read the other gen_kmodel dispatch below as it's signature is more flexible. -# Generate and return a kripke structure. -# This utility uses `fanfan` and `dispense_alphabet` default methods -# to define `adjacents` and `evaluations` but one could create its model -# piece by piece and then calling KripkeStructure constructor. -function gen_kmodel( - n::Integer, - in_degree::Integer, # needed by fanfan - out_degree::Integer; # needed by fanfan - P::LetterAlphabet = SoleLogics.alphabet(MODAL_LOGIC), - threshold = 0.5, # needed by fanfan - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, -) - rng = initrng(rng) - ws = Worlds{Point}(world_gen(n)) - adjs = fanfan(n, in_degree, out_degree, threshold = threshold, rng = rng) - evs = dispense_alphabet(ws, P = P, rng = rng) - return KripkeStructure(ws, adjs, evs) -end - -# Generate and return a kripke structure. -# Example of valid calls: -# gen_kmodel(15, MODAL_LOGIC, :erdos_renyi, 0.42) -# gen_kmodel(10, MODAL_LOGIC, :fanin_fanout, 3, 4) -# -# NOTE: -# This function is a bit tricky as in kwargs (that is, the arguments of the selected method) -# n has to be excluded (in fact it is already the first argument) -# In other words this dispatch is not compatible with graph-generation functions whose -# signature differs from fx(n, other_arguments...) -function gen_kmodel(n::Integer, P::LetterAlphabet, method::Symbol, kwargs...) - if method == :fanin_fanout - fx = fanfan - elseif method == :erdos_renyi - fx = gnp - else - error("Invalid method provided: $method. Refer to the docs ") - end - - ws = Worlds{Point}(world_gen(n)) - adjs = fx(n, kwargs...) - evs = dispense_alphabet(ws, P = P) - return KripkeStructure(ws, adjs, evs) -end diff --git a/src/old-code/install.jl b/src/old-code/install.jl deleted file mode 100644 index 221ad9df..00000000 --- a/src/old-code/install.jl +++ /dev/null @@ -1,29 +0,0 @@ -# This file can be used to automatically resolve dependencies -# involving unregistered packages. -# To do so, simply call `install` one time for each package -# respecting the correct dependency order. - -using Pkg -Pkg.activate(".") - -# Remove the specified package (do not abort if it is already removed) and reinstall it. -function install(package::String, url::String, rev::String) - printstyled(stdout, "\nRemoving: $package\n", color=:green) - try - Pkg.rm(package) - catch e - println(); showerror(stdout, e); println() - end - - printstyled(stdout, "\nFetching: $url at branch $rev\n", color=:green) - try - Pkg.add(url=url, rev=rev) - printstyled(stdout, "\nPackage $package instantiated correctly\n", color=:green) - catch e - println(); showerror(stdout, e); println() - end -end - -install("SoleBase", "https://github.com/aclai-lab/SoleBase.jl", "dev-v0.9.1") - -Pkg.instantiate() diff --git a/src/old-code/many-valued-logics/flew-algebras.jl b/src/old-code/many-valued-logics/flew-algebras.jl deleted file mode 100644 index deb92aaa..00000000 --- a/src/old-code/many-valued-logics/flew-algebras.jl +++ /dev/null @@ -1,143 +0,0 @@ -using ..SoleLogics: AbstractAlgebra -import ..SoleLogics: syntaxstring -import Base: convert - -struct FLewTruth <: Truth - label::String - - function FLewTruth(label::String) - return new(label) - end - - function FLewTruth(t::BooleanTruth) - return convert(FLewTruth, t) - end -end - -syntaxstring(t::FLewTruth; kwargs...) = t.label -convert(::Type{FLewTruth}, t::BooleanTruth) = istop(t) ? FLewTruth("⊤") : FLewTruth("⊥") - -""" - struct FLewAlgebra <: AbstractAlgebra{FLewTruth} - domain::Set{FLewTruth} - jointable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} - meettable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} - monoidtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} - implicationtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} - bot::FLewTruth - top::FLewTruth - end - -An FLew-algebra is an algebra (L, ∨, ∧, ⋅, →, ⊥, ⊤), where - - (L, ∨, ∧, ⊥, ⊤) is a lattice with top element ⊤ and bottom element ⊥ - - (L, ⋅, ⊤) is a commutative monoid - - The residuation property holds: x ⋅ y ≤ z iff x ≤ y → z - -A lattice is an algebraic structure (L, ∨, ∧) consisting of a set L and two binary, -commutative and associative operations ∨ and ∧ on L satisfying the following axiomatic -identities for all elements a, b ∈ L (sometimes called absorption laws): - - a ∨ (a ∧ b) = a - - a ∧ (a ∨ b) = a - -The following two identities are also usally regarded as axioms, even though they follow -from the two absorption laws taken together. These are called idempotent laws: - - a ∨ a = a - - a ∧ a = a - -A bounded lattice is an algebraic structure (L, ∨, ∧, ⊥, ⊤) such that (L, ∨, ∧) is a -lattice, the bottom element ⊥ is the identity element for the join operation ∨, and the top -element ⊤ is the identity element for the meet operation ∧: - - a ∨ ⊥ = a - - a ∧ ⊤ = a - -A monoid (L, ⋅, e) is a set L equipped with a binary operation L × L → L, denoted as ⋅, -satisfying the following axiomatic identities: - - (Associativity) ∀ a, b, c ∈ L, the equation (a ⋅ b) ⋅ c = a ⋅ (b ⋅ c) holds. - - (Identity element) There exists an element e ∈ L such that for every element a ∈ L, the equalities e ⋅ a = a - and a ⋅ e = a hold. - -The identity element of a monoid is unique. - -A commutative monoid is a monoid whose operation is commutative. -""" -struct FLewAlgebra <: AbstractAlgebra{FLewTruth} - domain::Set{FLewTruth} - jointable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} - meettable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} - monoidtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} - implicationtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} - bot::FLewTruth - top::FLewTruth - - function FLewAlgebra( - domain::Set{FLewTruth}, - jointable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth}, - meettable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth}, - monoidtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth}, - implicationtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth}, - bot::FLewTruth, - top::FLewTruth - ) - for i ∈ domain - for j ∈ domain - @assert (i, j) ∈ keys(jointable) "jointable[($i, $j)] is not defined." - @assert (i, j) ∈ keys(meettable) "meettable[($i, $j)] is not defined." - @assert (i, j) ∈ keys(monoidtable) "monoidtable[($i, $j)] is not defined." - @assert (i, j) ∈ keys(monoidtable) "implicationtable[($i, $j)] is not defined." - end - end - @assert length(jointable) == length(domain)^2 "Found jointable[(i, j)] where i or j ∉ domain." - @assert length(meettable) == length(domain)^2 "Found meettable[(i, j)] where i or j ∉ domain." - @assert length(monoidtable) == length(domain)^2 "Found monoidtable[(i, j)] where i or j ∉ domain." - @assert length(implicationtable) == length(domain)^2 "Found implicationtable[(i, j)] where i or j ∉ domain." - for i ∈ domain - @assert monoidtable[(i, FLewTruth(⊤))] == i "Defined monoid don't satisfy the neutral element rule: monoidtable[($i, ⊤) ≠ $i]." - for j ∈ domain - @assert jointable[(i, j)] == jointable[(j, i)] "Defined join is not commutative: jointable[($i,$j)] ≠ jointable[($j,$i)]." - @assert meettable[(i, j)] == meettable[(j, i)] "Defined meet is not commutative: meettable[($i,$j)] ≠ meettable[($j,$i)]." - @assert monoidtable[(i, j)] == monoidtable[(j, i)] "Defined monoid is not commutative: monoidtable[($i,$j)] ≠ monoidtable[($j,$i)]." - for k ∈ domain - @assert jointable[(jointable[(i, j)], k)] == jointable[(i, jointable[(j, k)])] "Defined join is not associative: jointable[(jointable[(i, j)], k)] ≠ jointable[(i, jointable[(j, k)])]." - @assert meettable[(meettable[(i, j)], k)] == meettable[(i, meettable[(j, k)])] "Defined meet is not associative: meettable[(meettable[(i, j)], k)] ≠ meettable[(i, meettable[(j, k)])]." - @assert monoidtable[(monoidtable[(i,j)], k)] == monoidtable[(i, monoidtable[(j, k)])] "Defined monoid is not associative: monoidtable[(monoidtable[(i,j)], k)] ≠ monoidtable[(i, monoidtable[(j, k)])]." - end - @assert jointable[(i, meettable[(i, j)])] == i "Defined join and meet don't satisfy the asborption law: jointable[($i, meettable[($i, $j)])] ≠ $i." - @assert meettable[(i, jointable[(i, j)])] == i "Defined join and meet don't satisfy the asborption law: meettable[($i, jointable[($i, $j)])] ≠ $i." - end - @assert jointable[(i, i)] == i "Defined join don't satisfy the idempotent law: jointable[($i, $i) ≠ $i]." - @assert meettable[(i, i)] == i "Defined meet don't satisfy the idempotent law: meettable[($i, $i) ≠ $i]." - @assert meettable[(bot, i)] == bot "$bot isn't a valid bottom element: $bot ≰ $i" - @assert jointable[(i, top)] == top "$top isn't a valid top element: $i ≰ $top" - end - return new(domain, meettable, jointable, monoidtable, implicationtable, bot, top) - end -end - -join(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) = a.jointable[(t1, t2)] -meet(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) = a.meettable[(t1, t2)] -monoid(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) = a.monoidtable[(t1, t2)] -implication(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) = a.implicationtable[(t1, t2)] -isbot(a::FLewAlgebra, t::FLewTruth) = t == a.bot -itop(a::FLewAlgebra, t::FLewTruth) = t == a.top - -""" - precedes(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) - -Given an algebraically defined lattice (L, ∨, ∧), one can define a partial ordern ≤ on L by -setting: - - a ≤ b if a = a ∧ b, or - - a ≤ b if b = a ∨ b, - -for all elements a, b ∈ L. The laws of absorption ensure that both definitions are -equivalent: - - a = a ∧ b implies b = b ∨ (b ∧ a) = (a ∧ b) ∨ b = a ∨ b - -and dually for the other direction. -""" -function precedes(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) - if meet(a, t1, t2) == t1 - return true - else - return false - end -end diff --git a/src/old-code/many-valued-logics/heyting-algebras.jl b/src/old-code/many-valued-logics/heyting-algebras.jl deleted file mode 100644 index 557f2f04..00000000 --- a/src/old-code/many-valued-logics/heyting-algebras.jl +++ /dev/null @@ -1,837 +0,0 @@ -using Graphs -using ..SoleLogics: AbstractAlgebra -import ..SoleLogics: istop, simplify - -# Author: alberto-paparella - -############################################################################################ -#### HeytingTruth ########################################################################## -############################################################################################ - -""" - struct HeytingTruth <: Truth - label::String - index::Int - end - -A truth value of a Heyting algebra. -Heyting truth values are represented by a label, and an index corresponding to its -position in the domain vector of the associated algebra. -Values `⊤` and `⊥` always exist with index 1 and 2, respectively. -New values can be easily constructed via the [`@heytingtruths`](@ref) macro. - -See also [`@heytingtruths`](@ref), [`HeytingAlgebra`](@ref), [`Truth`](@ref) -""" -struct HeytingTruth <: Truth - label::String - index::Int # the index of the node in the domain vector: no order is implied! - - function HeytingTruth(label::String, index::Int) - return new(label, index) - end - - # Helper - function HeytingTruth(booleantruth::BooleanTruth) - return convert(HeytingTruth, booleantruth) - end -end - -""" -Return the label of a [`HeytingTruth`](@ref). -""" -label(t::HeytingTruth)::String = t.label - -""" -Return the index of a [`HeytingTruth`](@ref). -""" -index(t::HeytingTruth)::Int = t.index - -istop(t::HeytingTruth) = index(t) == 1 -isbot(t::HeytingTruth) = index(t) == 2 - -syntaxstring(t::HeytingTruth; kwargs...) = label(t) - -convert(::Type{HeytingTruth}, t::HeytingTruth) = t - -function convert(::Type{HeytingTruth}, t::BooleanTruth) - return istop(t) ? HeytingTruth("⊤", 1) : HeytingTruth("⊥", 2) -end - -# Convert an object of type HeytingTruth to an object of type `BooleanTruth` (if possible). -function convert(::Type{BooleanTruth}, t::HeytingTruth) - if istop(t) - return TOP - elseif isbot(t) - return BOT - else - error("Cannot convert HeytingTruth \"" * syntaxstring(t) * "\" to BooleanTruth. " * - "Only ⊤ and ⊥ can be converted to BooleanTruth.") - end -end - -# Helper -function Base.show(io::IO, v::Vector{HeytingTruth}) - print(io, SoleLogics.displaysyntaxvector(v; quotes = false)) -end - -############################################################################################ -#### HeytingAlgebra ######################################################################## -############################################################################################ - -# TODO verify: these may be useful: -# - https://github.com/scheinerman/SimplePosets.jl -# - https://github.com/simonschoelly/SimpleValueGraphs.jl -""" - struct HeytingAlgebra{D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph} - domain::D - graph::G - transitiveclosure::G - end - -A Heyting algebra, represented explicitly -as a domain of truth values, and a graph over them encoding a partial order with -specific constraints (see [here](https://en.wikipedia.org/wiki/Heyting_algebra)). -⊤ and ⊥ are always the first and the second element of each algebra, respectively. -A copy of the graph under transitive closure is also stored for optimization purposes. - -See also [`@heytingalgebra`](@ref), [`HeytingTruth`](@ref) -""" -struct HeytingAlgebra{ - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} <: AbstractAlgebra{HeytingTruth} - domain::D - graph::G # directed graph where (α, β) represents α ≺ β - transitiveclosure::G # transitive closure of the graph (useful for some optimization) - isevaluated::Bool - meettable::Vector{Vector{HeytingTruth}} - jointable::Vector{Vector{HeytingTruth}} - implication::Vector{Vector{HeytingTruth}} - maxmembers::Vector{Vector{HeytingTruth}} - minmembers::Vector{Vector{HeytingTruth}} - - function HeytingAlgebra( - domain::D, - graph::G; - evaluate::Bool - ) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph - } - @assert length(domain) >= 2 "Cannot instantiate `HeytingAlgebra` with domain " * - "of length $(length(domain)). Need to specify at least a top and a bottom " * - "element (to be placed at positions 1 and 2, respectively)." - @assert isbounded(domain, graph) "Tried to define an HeytingAlgebra with a graph " * - "which is not a bounded lattice." - @assert iscomplete(domain, graph) "Tried to define an HeytingAlgebra " * - "with a graph which is not a complete lattice." - if evaluate - tc = transitiveclosure(graph) - meettable = [Vector{HeytingTruth}(undef, length(domain)) for _ in 1:length(domain)] - jointable = [Vector{HeytingTruth}(undef, length(domain)) for _ in 1:length(domain)] - implication = [Vector{HeytingTruth}(undef, length(domain)) for _ in 1:length(domain)] - maxmembers = Vector{Vector{HeytingTruth}}(undef, length(domain)) - minmembers = Vector{Vector{HeytingTruth}}(undef, length(domain)) - for α ∈ domain - for β ∈ domain - meettable[index(α)][index(β)] = greatestlowerbound(domain, tc, α, β) - jointable[index(α)][index(β)] = leastupperbound(domain, tc, α, β) - end - end - for α ∈ domain - for β ∈ domain - η = HeytingTruth(⊥) - for γ ∈ domain - if precedeq(domain, tc, meettable[index(α)][index(γ)], β) - η = jointable[index(η)][index(γ)] - end - end - implication[index(α)][index(β)] = η - end - end - for α ∈ domain - maxmembers[index(α)] = maximalmembers(domain, tc, α) - minmembers[index(α)] = minimalmembers(domain, tc, α) - end - return new{D,G}(domain, graph, tc, true, meettable, jointable, implication, maxmembers, minmembers) - else - return new{D,G}(domain, graph, transitiveclosure(graph), false) - end - end - - function HeytingAlgebra(domain::Vector{HeytingTruth}, relations::Vector{Edge{Int64}}; evaluate::Bool=false) - return HeytingAlgebra(domain, SimpleDiGraph(relations), evaluate=evaluate) - end -end - -domain(h::HeytingAlgebra) = h.domain -top(h::HeytingAlgebra) = h.domain[1] -bot(h::HeytingAlgebra) = h.domain[2] -graph(h::HeytingAlgebra) = h.graph -Graphs.transitiveclosure(h::HeytingAlgebra) = h.transitiveclosure -isevaluated(h::HeytingAlgebra) = h.isevaluated - -meet(h::HeytingAlgebra) = h.meettable -meet(h::HeytingAlgebra, α, β) = meet(h)[index(α)][index(β)] - -join(h::HeytingAlgebra) = h.jointable -join(h::HeytingAlgebra, α, β) = join(h)[index(α)][index(β)] - -implication(h::HeytingAlgebra) = h.implication -implication(h::HeytingAlgebra, α, β) = implication(h)[index(α)][index(β)] - -maxmembers(h::HeytingAlgebra) = h.maxmembers -maxmembers(h::HeytingAlgebra, t::HeytingTruth) = maxmembers(h)[index(t)] - -minmembers(h::HeytingAlgebra) = h.minmembers -minmembers(h::HeytingAlgebra, t::HeytingTruth) = minmembers(h)[index(t)] - -cardinality(h::HeytingAlgebra) = length(domain(h)) -isboolean(h::HeytingAlgebra) = (cardinality(h) == 2) - -function Graphs.has_path( - g::G, - α::HeytingTruth, - β::HeytingTruth -) where { - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - return has_path(g, index(α), index(β)) -end - -function isbounded( - d::D, - g::G -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - for α ∈ d - if !has_path(g, HeytingTruth(⊥), α) || !has_path(g, α, HeytingTruth(⊤)) - return false - end - end - return true -end - -function Graphs.inneighbors( - d::D, - g::G, - t::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - return d[inneighbors(g, index(t))] -end - -function Graphs.inneighbors(h::HeytingAlgebra, t::HeytingTruth) - return domain(h)[inneighbors(graph(h), index(t))] -end - -function Graphs.outneighbors( - d::D, - g::G, - t::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - return d[outneighbors(g, index(t))] -end - -function Graphs.outneighbors(h::HeytingAlgebra, t::HeytingTruth) - return domain(h)[outneighbors(graph(h), index(t))] -end - -# α ≺ β (note: in general, α ⊀ β ≠ α ⪰ β) -function precedes( - d::D, - tc::G, - α::HeytingTruth, - β::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - return β ∈ outneighbors(d, tc, α) -end - -function precedes(h::HeytingAlgebra, α::HeytingTruth, β::HeytingTruth) - return precedes(domain(h), transitiveclosure(h), α, β) -end - -function precedes(h::HeytingAlgebra, α::HeytingTruth, β::BooleanTruth) - return precedes(domain(h), transitiveclosure(h), α, convert(HeytingTruth, β)) -end - -function precedes(h::HeytingAlgebra, α::BooleanTruth, β::HeytingTruth) - return precedes(domain(h), transitiveclosure(h), convert(HeytingTruth, α), β) -end - -function precedes(h::HeytingAlgebra, α::BooleanTruth, β::BooleanTruth) - return precedes( - domain(h), - transitiveclosure(h), - convert(HeytingTruth, α), - convert(HeytingTruth, β) - ) -end - -# α ⪯ β (note: in general, α ⪯̸ β ≠ α ≻ β) -function precedeq( - d::D, - tc::G, - α::HeytingTruth, - β::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - return α == β || precedes(d, tc, α, β) -end - -function precedeq(h::HeytingAlgebra, α::HeytingTruth, β::HeytingTruth) - return α == β || precedes(domain(h), transitiveclosure(h), α, β) -end - -function precedeq(h::HeytingAlgebra, α::HeytingTruth, β::BooleanTruth) - return precedeq(h, α, convert(HeytingTruth, β)) -end - -function precedeq(h::HeytingAlgebra, α::BooleanTruth, β::HeytingTruth) - return precedeq(h, convert(HeytingTruth, α), β) -end - -function precedeq(h::HeytingAlgebra, α::BooleanTruth, β::BooleanTruth) - return α == β || precedes( - domain(h), - transitiveclosure(h), - convert(HeytingTruth, α), - convert(HeytingTruth, β) - ) -end - -# α ≻ β (note: in general, α ⊁ β ≠ α ⪯ β) -function succeedes( - d::D, - tc::G, - α::HeytingTruth, - β::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - return β ∈ inneighbors(d, tc, α) -end - -function succeedes(h::HeytingAlgebra, α::HeytingTruth, β::HeytingTruth) - return succeedes(domain(h), transitiveclosure(h), α, β) -end - -function succeedes(h::HeytingAlgebra, α::HeytingTruth, β::BooleanTruth) - return succeedes(domain(h), transitiveclosure(h), α, convert(HeytingTruth, β)) -end - -function succeedes(h::HeytingAlgebra, α::BooleanTruth, β::HeytingTruth) - return succeedes(domain(h), transitiveclosure(h), convert(HeytingTruth, α), β) -end - -function succeedes(h::HeytingAlgebra, α::BooleanTruth, β::BooleanTruth) - return succeedes( - domain(h), - transitiveclosure(h), - convert(HeytingTruth, α), - convert(HeytingTruth, β) - ) -end - -# α ⪰ β (note: in general, α ⪰̸ β ≠ α ≺ β) -function succeedeq( - d::D, - tc::G, - α::HeytingTruth, - β::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - return α == β || succeedes(d, tc, α, β) -end - -function succeedeq(h::HeytingAlgebra, α::HeytingTruth, β::HeytingTruth) - return α == β || succeedes(domain(h), transitiveclosure(h), α, β) -end - -function succeedeq(h::HeytingAlgebra, α::HeytingTruth, β::BooleanTruth) - return succeedeq(h, α, convert(HeytingTruth, β)) -end - -function succeedeq(h::HeytingAlgebra, α::BooleanTruth, β::HeytingTruth) - return succeedeq(h, convert(HeytingTruth, α), β) -end - -function succeedeq(h::HeytingAlgebra, α::BooleanTruth, β::BooleanTruth) - return α == β || succeedes( - domain(h), - transitiveclosure(h), - convert(HeytingTruth, α), - convert(HeytingTruth, β) - ) -end - -""" -Return all maximal members of h not above t. -""" -function maximalmembers( - d::D, - tc::G, - t::HeytingTruth, - α::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - ismm = true - mm = Set{HeytingTruth}() - for o ∈ outneighbors(d, tc, α) - if !succeedeq(d, tc, o, t) - ismm = false - push!(mm, maximalmembers(d, tc, t, o)...) - end - end - ismm ? HeytingTruth[α] : collect(mm) -end - -function maximalmembers( - d::D, - tc::G, - t::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - if isbot(t) - return HeytingTruth[] - else - return maximalmembers(d, tc, t, HeytingTruth(⊥)) - end -end - -function maximalmembers(h::HeytingAlgebra, t::HeytingTruth) - if isbot(t) - return HeytingTruth[] - elseif isevaluated(h) - return maxmembers(h, t) - else - return maximalmembers(domain(h), transitiveclosure(h), t, HeytingTruth(⊥)) - end -end - -maximalmembers(h::HeytingAlgebra, t::BooleanTruth) = maximalmembers(h, HeytingTruth(t)) - -""" -Return all minimal members of h not below t -""" -function minimalmembers( - d::D, - tc::G, - t::HeytingTruth, - α::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - ismm = true - mm = Set{HeytingTruth}() - for i ∈ inneighbors(d, tc, α) - if !precedeq(d, tc, i, t) - ismm = false - push!(mm, minimalmembers(d, tc, t, i)...) - end - end - ismm ? HeytingTruth[α] : collect(mm) -end - -function minimalmembers( - d::D, - tc::G, - t::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - if istop(t) - return HeytingTruth[] - else - return minimalmembers(d, tc, t, HeytingTruth(⊤)) - end -end - -function minimalmembers(h::HeytingAlgebra, t::HeytingTruth) - if istop(t) - return HeytingTruth[] - elseif isevaluated(h) - return minmembers(h, t) - else - return minimalmembers(domain(h), transitiveclosure(h), t, HeytingTruth(⊤)) - end -end - -minimalmembers(h::HeytingAlgebra, t::BooleanTruth) = minimalmembers(h, HeytingTruth(t)) - -function greatervalues( - d::D, - tc::G, - α::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - return outneighbors(d, tc, α) -end - -function upperbounds( - d::D, - tc::G, - α::HeytingTruth, - β::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - geqα = push!(greatervalues(d, tc, α), α) - geqβ = push!(greatervalues(d, tc, β), β) - return geqα[in.(geqα, Ref(geqβ))] -end - -function isleastupperbound( - d::D, - tc::G, - α::HeytingTruth, - ubs::D -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - for ub ∈ ubs - if !precedeq(d, tc, α, ub) # note: in general, α ⪯̸ β ≠ α ≻ β - return false - end - end - return true -end - -function leastupperbound( - d::D, - tc::G, - α::HeytingTruth, - β::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - ubs = upperbounds(d, tc, α, β) - for ub ∈ ubs - if isleastupperbound(d, tc, ub, ubs) - return ub - end - end - return nothing -end - -function lesservalues( - d::D, - tc::G, - α::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - return inneighbors(d, tc, α) -end - -function lesservalues(h::HeytingAlgebra, t::HeytingTruth) - return lesservalues(domain(h), transitiveclosure(h), t) -end - -function lowerbounds( - d::D, - tc::G, - α::HeytingTruth, - β::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - leqα = push!(lesservalues(d, tc, α), α) - leqβ = push!(lesservalues(d, tc, β), β) - return leqα[in.(leqα, Ref(leqβ))] -end - -function isgreatestlowerbound( - d::D, - tc::G, - α::HeytingTruth, - lbs::D -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - for lb ∈ lbs - if !succeedeq(d, tc, α, lb) # note: in general, α ⪰̸ β ≠ α ≺ β - return false - end - end - return true -end - -function greatestlowerbound( - d::D, - tc::G, - α::HeytingTruth, - β::HeytingTruth -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - lbs = lowerbounds(d, tc, α, β) - for lb ∈ lbs - if isgreatestlowerbound(d, tc, lb, lbs) - return lb - end - end - return nothing -end - -function iscomplete( - d::D, - g::G -) where { - D<:AbstractVector{HeytingTruth}, - G<:Graphs.SimpleGraphs.SimpleDiGraph -} - tc = transitiveclosure(g) - for α ∈ d - for β ∈ d - if α != β && ( - isnothing(leastupperbound(d, tc, α, β)) || - isnothing(greatestlowerbound(d, tc, α, β)) - ) - return false - end - end - end - return true -end - -Graphs.Edge(t::Tuple{HeytingTruth, HeytingTruth}) = Edge(index(t[1]), index(t[2])) - -function Graphs.Edge(t::Tuple{HeytingTruth, BooleanTruth}) - return Edge((t[1], convert(HeytingTruth, t[2]))) -end - -function Graphs.Edge(t::Tuple{BooleanTruth, HeytingTruth}) - return Edge((convert(HeytingTruth, t[1]), t[2])) -end - -function Graphs.Edge(t::Tuple{BooleanTruth, BooleanTruth}) - return Edge((convert(HeytingTruth, t[1]), convert(HeytingTruth, t[2]))) -end - -""" - @heytingtruths(labels...) - -Instantiate a collection of [`HeytingTruth`](@ref)s and return them as a vector. -⊤ and ⊥ already exist as `const`s of type `BooleanTruth` and they are -treated as `HeytingTruth`s with index 1 and 2, respectively. - -!!! info - `HeytingTruth`s instantiated with this macro are defined in the global scope as - constants. - -# Examples -```julia-repl -julia> SoleLogics.@heytingtruths α β -2-element Vector{HeytingTruth}: - HeytingTruth: α - HeytingTruth: β - -julia> α -HeytingTruth: α - -See also [`HeytingTruth`](@ref), [`@heytingalgebra`](@ref) -""" -macro heytingtruths(labels...) - quote - $(map(t -> begin - if !(t[2] in [Symbol(:⊤), Symbol(:⊥)]) - :(const $(t[2]) = $(HeytingTruth(string(t[2]), t[1]+2))) - else - return error("Invalid heyting truth provided: $(t[2]). " * - "Symbols `⊤` and `⊥` are reserved for the top and bottom of the " - * "algebra, and they do not need to be specified.") - end - end, enumerate(labels))...) - HeytingTruth[$(labels...)] - end |> esc -end - -""" - @heytingalgebra(values, relations...) - -Construct a [`HeytingAlgebra`](@ref) -with domain containing `values` and graph represented by the tuples in `relations`, with -each tuple (α, β) representing a direct edge in the graph asserting α ≺ β. - -# Examples -```julia-repl - -julia> myalgebra = SoleLogics.@heytingalgebra (α, β) (⊥, α) (⊥, β) (α, ⊤) (β, ⊤) -HeytingAlgebra(HeytingTruth[⊤, ⊥, α, β], SimpleDiGraph{Int64}(4, [Int64[], [3, 4], [1], [1]] -, [[3, 4], Int64[], [2], [2]])) - -See also [`HeytingTruth`](@ref), [`@heytingalgebra`](@ref) -""" -macro heytingalgebra(values, relations...) - quote - labels = @heytingtruths $(values.args...) - domain = HeytingTruth[convert(HeytingTruth, ⊤), convert(HeytingTruth, ⊥), labels...] - edges = Vector{SoleLogics.Graphs.Edge{Int64}}() - map(e -> push!(edges, SoleLogics.Graphs.Edge(eval(e))), $relations) - HeytingAlgebra(domain, edges) - end |> esc -end - -# Meet (greatest lower bound) between values α and β -function collatetruth( - ::typeof(∧), - (α, β)::NTuple{N, T where T<:HeytingTruth}, - h::HeytingAlgebra -) where { - N -} - if isevaluated(h) - return meet(h, α, β) - else - return greatestlowerbound(domain(h), transitiveclosure(h), α, β) - end -end - -# Join (least upper bound) between values α and β -function collatetruth( - ::typeof(∨), - (α, β)::NTuple{N, T where T<:HeytingTruth}, - h::HeytingAlgebra -) where { - N -} - if isevaluated(h) - return join(h, α, β) - else - return leastupperbound(domain(h), transitiveclosure(h), α, β) - end -end - -# Implication/pseudo-complement α → β = join(γ | meet(α, γ) ⪯ β) -function collatetruth( - ::typeof(→), - (α, β)::NTuple{N, T where T<:HeytingTruth}, - h::HeytingAlgebra -) where { - N -} - if isevaluated(h) - return implication(h, α, β) - else - η = bot(h) - for γ ∈ domain(h) - if precedeq(h, collatetruth(∧, (α, γ), h), β) - η = collatetruth(∨, (η, γ), h) - end - end - return η - end -end - -function collatetruth( - c::Connective, - (α, β)::Tuple{HeytingTruth, BooleanTruth}, - h::HeytingAlgebra -) - return collatetruth(c, (α, convert(HeytingTruth, β)), h) -end - -function collatetruth( - c::Connective, - (α, β)::Tuple{BooleanTruth, HeytingTruth}, - h::HeytingAlgebra -) - return collatetruth(c, (convert(HeytingTruth, α), β), h) -end - -function collatetruth( - c::Connective, - (α, β)::Tuple{BooleanTruth, BooleanTruth}, - h::HeytingAlgebra -) - return collatetruth(c, (convert(HeytingTruth, α), convert(HeytingTruth, β)), h) -end - -function simplify( - c::Connective, - (α, β)::Tuple{HeytingTruth,HeytingTruth}, - h::HeytingAlgebra -) - return collatetruth(c, (α, β), h) -end - - function simplify( - c::Connective, - (α, β)::Tuple{HeytingTruth,BooleanTruth}, - h::HeytingAlgebra -) - return collatetruth(c, (α, convert(HeytingTruth, β)), h) -end - - function simplify( - c::Connective, - (α, β)::Tuple{BooleanTruth,HeytingTruth}, - h::HeytingAlgebra -) - return collatetruth(c, (convert(HeytingTruth, α), β), h) -end - - function simplify( - c::Connective, - (α, β)::Tuple{BooleanTruth,BooleanTruth}, - h::HeytingAlgebra -) - return collatetruth(c, (convert(HeytingTruth, α), convert(HeytingTruth, β)), h) -end - - -function collatetruth(::typeof(¬), (α,)::Tuple{HeytingTruth}, h::HeytingAlgebra) - if isboolean(h) - if istop(α) - return ⊥ - else - return ⊤ - end - else - return error("¬ operation isn't defined outside of BooleanAlgebra") - end -end - -function collatetruth(c::Connective, (α,)::Tuple{BooleanTruth}, h::HeytingAlgebra) - return collatetruth(c, convert(HeytingTruth, α), h) -end - -function simplify(c::Connective, (α,)::Tuple{HeytingTruth}, h::HeytingAlgebra) - return collatetruth(c, (α,), h) -end - - function simplify(c::Connective, (α,)::Tuple{BooleanTruth}, h::HeytingAlgebra) - return simplify(c, (convert(HeytingTruth, α),), h) -end diff --git a/src/types/propositional-logic.jl b/src/types/propositional-logic.jl index 5be60ce4..e4439d68 100644 --- a/src/types/propositional-logic.jl +++ b/src/types/propositional-logic.jl @@ -65,14 +65,14 @@ See also [`AbstractInterpretation`](@ref). abstract type AbstractAssignment <: AbstractInterpretation end """ - Base.haskey(i::AbstractAssignment, ::Atom)::Bool + Base.haskey(i::AbstractAssignment, ::AbstractAtom)::Bool Return whether an assigment has a truth value for a given atom. See also [`AbstractInterpretation`](@ref). """ -function Base.haskey(i::AbstractAssignment, ::Atom)::Bool - return error("Please, provide method Base.haskey(::$(typeof(i)), ::Atom)::Bool.") +function Base.haskey(i::AbstractAssignment, ::AbstractAtom)::Bool + return error("Please, provide method Base.haskey(::$(typeof(i)), ::AbstractAtom)::Bool.") end # Helper @@ -85,7 +85,7 @@ function inlinedisplay(i::AbstractAssignment) end # When interpreting a single atom, if the lookup fails, then return the atom itself -function interpret(a::Atom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf +function interpret(a::AbstractAtom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf if Base.haskey(i, a) Base.getindex(i, a, args...; kwargs...) else diff --git a/src/utils.jl b/src/utils/synctactical.jl similarity index 99% rename from src/utils.jl rename to src/utils/synctactical.jl index 817c5d21..a5de22a9 100644 --- a/src/utils.jl +++ b/src/utils/synctactical.jl @@ -610,7 +610,6 @@ Base.in(::Atom{PV}, ::AlphabetOfAny{VV}) where {PV, VV} = (PV <: VV) #### UnionAlphabet ######################################################################### ############################################################################################ -# Finite alphabet of conditions induced from a set of metaconditions """ Alphabet given by the *union* of a number of (sub-)alphabets. @@ -661,10 +660,10 @@ together with a `subalphabets_weights` vector. See also [`UnionAlphabet`](@ref). """ function randatom( - rng::Union{Integer, AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol = :uniform, - subalphabets_weights::Union{AbstractWeights, AbstractVector{<:Real}, Nothing} = nothing, + rng::Union{Integer,AbstractRNG}, + a::UnionAlphabet; + atompicking_mode::Symbol = :uniform, + subalphabets_weights::Union{AbstractWeights, AbstractVector{<:Real}, Nothing} = nothing, )::Atom # @show a From 69744a9166c3a9f4e08679b3fdefe5204879b692 Mon Sep 17 00:00:00 2001 From: Perro2110 Date: Thu, 10 Oct 2024 11:18:12 +0200 Subject: [PATCH 14/90] minorPt2 --- src/SoleLogics.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index e5214dd4..ec71e137 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -67,7 +67,7 @@ export BooleanAlgebra export BaseLogic -include("utils.jl") +include("utils/synctactical.jl") ############################################################################################ From 2c090bdc1ce3296e6152d6d67ac8ebfc4bec9070 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:25:17 +0200 Subject: [PATCH 15/90] Separate interpretation sets --- src/SoleLogics.jl | 4 +- src/types/interpretation-sets.jl | 56 +++++++++++++++++++++++++ src/{ => utils}/interpretation-sets.jl | 57 +++----------------------- 3 files changed, 64 insertions(+), 53 deletions(-) create mode 100644 src/types/interpretation-sets.jl rename src/{ => utils}/interpretation-sets.jl (82%) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 737260a1..1007eb7e 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -122,7 +122,9 @@ include("syntax-utils.jl") ############################################################################################ -include("interpretation-sets.jl") +include("types/interpretation-sets.jl") + +include("utils/interpretation-sets.jl") ############################################################################################ diff --git a/src/types/interpretation-sets.jl b/src/types/interpretation-sets.jl new file mode 100644 index 00000000..22469f21 --- /dev/null +++ b/src/types/interpretation-sets.jl @@ -0,0 +1,56 @@ +import SoleBase: AbstractDataset, ninstances, eachinstance + +import Base: getindex + +""" + abstract type AbstractInterpretationSet <: AbstractDataset end + +Abstract 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 i_instance::Integer. +These structures are especially useful when performing +[model checking](https://en.wikipedia.org/wiki/Model_checking). + +# Interface +- `interpretationtype(s)` +- `alphabet(s)` +- See also [`AbstractDataset`](@ref) + +# Utility Functions +- `valuetype(s)` +- `truthtype(s)` + +See also[`truthtype`](@ref), +[`InterpretationVector`](@ref). +""" +abstract type AbstractInterpretationSet <: AbstractDataset end + +# TODO improve general doc. +function interpretationtype(S::Type{<:AbstractInterpretationSet}) + return error("Please, provide method interpretationtype(::$(typeof(S))).") +end +interpretationtype(s::AbstractInterpretationSet) = interpretationtype(typeof(s)) + +# TODO improve general doc. +valuetype(S::Type{<:AbstractInterpretationSet}) = valuetype(interpretationtype(S)) +valuetype(s::AbstractInterpretationSet) = valuetype(typeof(s)) + +# TODO improve general doc. +truthtype(S::Type{<:AbstractInterpretationSet}) = truthtype(interpretationtype(S)) +truthtype(s::AbstractInterpretationSet) = truthtype(typeof(s)) + +""" + alphabet(s::AbstractInterpretationSet)::Alphabet + +Return the propositional alphabet of an interpretation set. + +See also [`AbstractAlphabet`](@ref), [`AbstractGrammar`](@ref). +""" +function alphabet(s::AbstractInterpretationSet)::Alphabet + return error("Please, provide method alphabet(::$(typeof(s))).") +end + +function eachinstance(s::AbstractInterpretationSet) + return (getinstance(s, i_instance) for i_instance in 1:ninstances(s)) +end diff --git a/src/interpretation-sets.jl b/src/utils/interpretation-sets.jl similarity index 82% rename from src/interpretation-sets.jl rename to src/utils/interpretation-sets.jl index c2e0cee9..03b478ed 100644 --- a/src/interpretation-sets.jl +++ b/src/utils/interpretation-sets.jl @@ -1,55 +1,3 @@ -import SoleBase: AbstractDataset, ninstances, eachinstance - -import Base: getindex - -""" - abstract type AbstractInterpretationSet <: AbstractDataset end - -Abstract 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 i_instance::Integer. -These structures are especially useful when performing -[model checking](https://en.wikipedia.org/wiki/Model_checking). - -See also[`truthtype`](@ref), -[`InterpretationVector`](@ref). -""" -abstract type AbstractInterpretationSet <: AbstractDataset end - -# TODO improve general doc. -function interpretationtype(S::Type{<:AbstractInterpretationSet}) - return error("Please, provide method interpretationtype(::$(typeof(S))).") -end -interpretationtype(s::AbstractInterpretationSet) = interpretationtype(typeof(s)) - -# TODO improve general doc. -valuetype(S::Type{<:AbstractInterpretationSet}) = valuetype(interpretationtype(S)) -valuetype(s::AbstractInterpretationSet) = valuetype(typeof(s)) - -# TODO improve general doc. -truthtype(S::Type{<:AbstractInterpretationSet}) = truthtype(interpretationtype(S)) -truthtype(s::AbstractInterpretationSet) = truthtype(typeof(s)) - -""" - alphabet(s::AbstractInterpretationSet)::Alphabet - -Return the propositional alphabet of an interpretation set. - -See also [`AbstractAlphabet`](@ref), [`AbstractGrammar`](@ref). -""" -function alphabet(s::AbstractInterpretationSet)::Alphabet - return error("Please, provide method alphabet(::$(typeof(s))).") -end - -# Fallback -function getinstance(s::AbstractInterpretationSet, i_instance::Integer) - return LogicalInstance(s, i_instance) -end - -function eachinstance(s::AbstractInterpretationSet) - return (getinstance(s, i_instance) for i_instance in 1:ninstances(s)) -end """ struct LogicalInstance{S<:AbstractInterpretationSet} @@ -260,6 +208,11 @@ function check( ), 1:ninstances(s)) end +# Fallback +function getinstance(s::AbstractInterpretationSet, i_instance::Integer) + return LogicalInstance(s, i_instance) +end + ############################################################################################ """ From c544f14b902bb158fd6371f17fa4bfa23f0ba8e9 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Sun, 13 Oct 2024 21:19:04 +0200 Subject: [PATCH 16/90] random.jl splitted in generation/formula.jl and generation/models.jl; randatom moved from utils.jl to generation/formula.jl --- src/SoleLogics.jl | 3 +- src/generation/formula.jl | 450 +++++++++++++++++++++++++++++++++ src/generation/models.jl | 78 ++++++ src/{ => generation}/random.jl | 3 + src/utils.jl | 57 ----- 5 files changed, 533 insertions(+), 58 deletions(-) create mode 100644 src/generation/formula.jl create mode 100644 src/generation/models.jl rename src/{ => generation}/random.jl (99%) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 737260a1..3a957a08 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -135,7 +135,8 @@ include("parse.jl") export randbaseformula, randformula export randframe, randmodel -include("random.jl") +include("generation/formula.jl") +include("generation/models.jl") ############################################################################################ diff --git a/src/generation/formula.jl b/src/generation/formula.jl new file mode 100644 index 00000000..00aba010 --- /dev/null +++ b/src/generation/formula.jl @@ -0,0 +1,450 @@ +using Random +using StatsBase + +import Random: rand +import StatsBase: sample + +""" + randatom( + rng::Union{Integer,AbstractRNG}, + a::UnionAlphabet; + atompicking_mode::Symbol=:uniform, + subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = 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. + +See also [`UnionAlphabet`](@ref). +""" +function randatom( + rng::Union{Integer, AbstractRNG}, + a::UnionAlphabet; + atompicking_mode::Symbol = :uniform, + subalphabets_weights::Union{AbstractWeights, AbstractVector{<:Real}, Nothing} = nothing, +)::Atom + + # @show a + @assert atompicking_mode in [:uniform, :uniform_subalphabets, :weighted] "Value for `atompicking_mode` not..." + rng = initrng(rng) + alphs = subalphabets(a) + + if atompicking_mode == :weighted + if isnothing(subalphabets_weights) + error("`:weighted` picking_mode requires weights in `subalphabets_weights` ") + end + @assert length(subalphabets_weights)==length(alphs) "Mismatching numbers of alphabets "* + "($(length(alphs))) and weights ($(length(subalphabets_weights)))." + subalphabets_weights = StatsBase.weights(subalphabets_weights) + pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) + else + subalphabets_weights = begin + # This atomatically excludes subalphabets with empty threshold vector + if atompicking_mode == :uniform_subalphabets + # set the weight of the empty alphabets to zero + weights = Weights(ones(Int, length(alphs))) + weights[natoms.(alphs) == 0] .= 0 + elseif atompicking_mode == :uniform + weights = Weights(natoms.(alphs)) + end + weights + end + pickedalphabet = sample(rng, alphs, subalphabets_weights) + end + # @show a + # @show subalphabets_weights + # @show pickedalphabet + return randatom(rng, pickedalphabet) +end + +doc_rand = """ + Base.rand( + [rng::AbstractRNG = Random.GLOBAL_RNG], + height::Integer, + l::AbstractLogic, + args...; + kwargs... + )::Formula + + Base.rand( + [rng::AbstractRNG = Random.GLOBAL_RNG,] + height::Integer, + g::CompleteFlatGrammar, + args... + )::Formula + + Base.rand( + height::Integer, + connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, + atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, + truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, + args...; + rng::AbstractRNG = Random.GLOBAL_RNG, + kwargs... + )::Formula + +If a [`CompleteFlatGrammar`](@ref) is provided together with an +`height` a [`Formula`](@ref) could also be generated. + +# Implementation +If the `alphabet` is finite, the function defaults to `rand(rng, atoms(alphabet))`; +otherwise, it must be implemented, and additional keyword arguments should be provided +in order to limit the (otherwise infinite) sampling domain. + +See also +[`AbstractAlphabet`](@ref), [`Atom`](@ref), [`CompleteFlatGrammar`](@ref), +[`Formula`](@ref), [`randformula`](@ref). +""" + +""" + Base.rand( + [rng::AbstractRNG = Random.GLOBAL_RNG,] + alphabet::AbstractAlphabet, + args...; + kwargs... + )::Atom + +Randomly generate an [`Atom`](@ref) from an [`AbstractAlphabet`](@ref) according to a +uniform distribution. +""" +function Base.rand(a::AbstractAlphabet, args...; kwargs...) + Base.rand(Random.GLOBAL_RNG, a, args...; kwargs...) +end +function Base.rand( + rng::AbstractRNG, + a::AbstractAlphabet, + args...; + kwargs... +) + randatom(rng, a, args...; kwargs...) +end + +function Base.rand(height::Integer, l::AbstractLogic, args...; kwargs...) + Base.rand(Random.GLOBAL_RNG, height, l, args...; kwargs...) +end + +function Base.rand( + rng::AbstractRNG, + height::Integer, + l::AbstractLogic, + args...; + kwargs... +) + Base.rand(rng, grammar(l), args...; kwargs...) +end + +# For the case of a CompleteFlatGrammar, the alphabet and the operators suffice. +function Base.rand( + height::Integer, + g::CompleteFlatGrammar, + args... +) + Base.rand(Random.GLOBAL_RNG, height, g, args...) +end + +function Base.rand( + rng::AbstractRNG, + height::Integer, + g::CompleteFlatGrammar, + args...; + kwargs... +) + randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) +end + +function Base.rand( + height::Integer, + atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, + connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, + truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, + args...; + kwargs... +) + Base.rand(Random.GLOBAL_RNG, height, atoms, connectives, truthvalues, args...; + kwargs...) +end + +function Base.rand( + rng::AbstractRNG, + height::Integer, + atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, + connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, + truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, + args...; + kwargs... +) + # If Truth's are specified as `operators`, then they cannot be simultaneously + # provided as `truthvalues` + @assert (connectives isa AbstractVector{<:Connective} || + !(truthvalues isa AbstractVector{<:Truth}) + ) "Unexpected connectives and truth values: $(connectives) and $(truthvalues)." + + atoms = atoms isa AbstractAlphabet ? SoleLogics.atoms(atoms) : atoms + ops = connectives + if !isnothing(truthvalues) + truthvalues = inittruthvalues(truthvalues) + @assert typejoin(typeof.(truthvalues)...) != Truth "Truth values " * + "$(truthvalues) must belong to the same algebra " * + "(and have a common supertype that is not Truth)." + ops = vcat(ops, truthvalues) + end + + randformula(height, ops, atoms, args...; rng=rng, kwargs...) +end + +doc_sample = """ + function StatsBase.sample( + [rng::AbstractRNG = Random.GLOBAL_RNG,] + alphabet::AbstractAlphabet, + weights::AbstractWeights, + args...; + kwargs... + ) + + function StatsBase.sample( + rng::AbstractRNG, + l::AbstractLogic, + weights::AbstractWeights, + args...; + kwargs... + ) + + StatsBase.sample( + [rng::AbstractRNG = Random.GLOBAL_RNG,] + height::Integer, + g::AbstractGrammar, + [opweights::Union{Nothing,AbstractWeights} = nothing,] + args...; + kwargs... + )::Formula + +Randomly sample an [`Atom`](@ref) from an `alphabet`, or a logic formula of given `height` +from a grammar `g`. +Sampling is weighted, thus, for example, if the first weight in `weights` is higher than +the others, then the first atom in the alphabet is selected more frequently. + +See also [`AbstractAlphabet`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref). +""" +function StatsBase.sample( + alphabet::AbstractAlphabet, + weights::AbstractWeights, + args...; + kwargs... +)::Atom + StatsBase.sample(Random.GLOBAL_RNG, alphabet, weights, args...; kwargs...) +end + +function StatsBase.sample( + rng::AbstractRNG, + alphabet::AbstractAlphabet, + weights::AbstractWeights, + args...; + kwargs... +)::Atom + if isfinite(alphabet) + StatsBase.sample(rng, atoms(alphabet), weights, args...; kwargs...) + else + error("Please, provide method StatsBase.sample(rng::AbstractRNG, " * + "alphabet::$(typeof(alphabet)), args...; kwargs...).") + end +end + +function StatsBase.sample( + l::AbstractLogic, + atomweights::AbstractWeights, + opweights::AbstractWeights, + args...; + kwargs... +) + StatsBase.sample(Random.GLOBAL_RNG, l, atomweights, opweights, args...; kwargs...) +end + +function StatsBase.sample( + rng::AbstractRNG, + l::AbstractLogic, + atomweights::AbstractWeights, + opweights::AbstractWeights, + args...; + kwargs... +) + StatsBase.sample(init(rng), grammar(l), atomweights, opweights, args...; kwargs...) +end + +"""$(doc_sample)""" +function StatsBase.sample( + height::Integer, + g::AbstractGrammar, + atomweights::Union{Nothing,AbstractWeights} = nothing, + opweights::Union{Nothing,AbstractWeights} = nothing, + args...; + kwargs... +) + StatsBase.sample(Random.GLOBAL_RNG, height, g, atomweights, opweights, args...; + kwargs...) +end + +"""$(doc_sample)""" +function StatsBase.sample( + rng::AbstractRNG, + height::Integer, + g::AbstractGrammar, + atomweights::Union{Nothing,AbstractWeights} = nothing, + opweights::Union{Nothing,AbstractWeights} = nothing, + args...; + kwargs... +) + randformula( + rng, height, alphabet(g), operators(g), args...; + # atompicker=(rng,dom)->StatsBase.sample(rng, dom, atomweights), kwargs...) + atompicker = atomweights, opweights = opweights, kwargs...) +end + + + +# TODO +# - make rng first (optional) argument of randformula (see above) +# - in randformula, keyword argument alphabet_sample_kwargs that are unpacked upon sampling atoms, as in: Base.rand(rng, a; alphabet_sample_kwargs...). This would allow to sample from infinite alphabets, so when this parameter, !isfinite(alphabet) is allowed! + +# TODO @Mauro implement this method. +doc_randformula = """ + randformula( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, ] + height::Integer, + alphabet, + operators::AbstractVector; + kwargs... + )::SyntaxTree + + randformula( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, ] + height::Integer, + g::AbstractGrammar; + kwargs... + )::SyntaxTree + +Return a pseudo-randomic `SyntaxTree`. + +# Arguments +- `rng::Union{Intger,AbstractRNG} = Random.GLOBAL_RNG`: random number generator; +- `height::Integer`: height of the generated structure; +- `alphabet::AbstractAlphabet`: collection from which atoms are chosen randomly; +- `operators::AbstractVector`: vector from which legal operators are chosen; +- `g::AbstractGrammar`: alternative to passing alphabet and operators separately. (TODO explain?) + +# Keyword Arguments +- `modaldepth::Integer`: maximum modal depth +- `atompicker::Function`: method used to pick a random element. For example, this could be + Base.rand or StatsBase.sample. +- `opweights::AbstractWeights`: weight vector over the set of operators (see `StatsBase`). + +# Examples + +```julia-repl +julia> syntaxstring(randformula(4, ExplicitAlphabet([1,2]), [NEGATION, CONJUNCTION, IMPLICATION])) +"¬((¬(¬(2))) → ((1 → 2) → (1 → 2)))" +``` + +See also [`AbstractAlphabet`](@ref), [`SyntaxBranch`](@ref). +""" + +"""$(doc_randformula)""" +function randformula( + rng::Union{Integer,AbstractRNG}, + height::Integer, + alphabet::Union{AbstractVector,AbstractAlphabet}, + operators::AbstractVector; + modaldepth::Integer = height, + atompicker::Union{Function,AbstractWeights,AbstractVector{<:Real},Nothing} = randatom, + opweights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing +)::SyntaxTree + + rng = initrng(rng) + alphabet = convert(AbstractAlphabet, alphabet) + @assert all(x->x isa Operator, operators) "Unexpected object(s) passed as" * + " operator:" * " $(filter(x->!(x isa Operator), operators))" + + if (isnothing(opweights)) + opweights = StatsBase.uweights(length(operators)) + elseif (opweights isa AbstractVector) + @assert length(opweights) == length(operators) "Mismatching numbers of operators " * + "($(length(operators))) and opweights ($(length(opweights)))." + opweights = StatsBase.weights(opweights) + end + + if (isnothing(atompicker)) + atompicker = StatsBase.uweights(natoms(alphabet)) + elseif (atompicker isa AbstractVector) + @assert length(atompicker) == natoms(alphabet) "Mismatching numbers of atoms " * + "($(natoms(alphabet))) and atompicker ($(length(atompicker)))." + atompicker = StatsBase.weights(atompicker) + end + + if !(atompicker isa Function) + atomweights = atompicker + atompicker = (rng, dom)->StatsBase.sample(rng, dom, atomweights) + end + + nonmodal_operators = findall(!ismodal, operators) + + # recursive call + function _randformula( + rng::AbstractRNG, + height::Integer, + modaldepth::Integer; + )::SyntaxTree + + if height == 0 + return atompicker(rng, alphabet) + else + # Sample operator and generate children (modal connectives only if modaldepth > 0) + ops, ops_w = begin + if modaldepth > 0 + operators, opweights + else + operators[nonmodal_operators], opweights[nonmodal_operators] + end + end + + # op = rand(rng, ops) + op = sample(rng, ops, ops_w) + ch = Tuple([ + _randformula(rng, height-1, modaldepth-(ismodal(op) ? 1 : 0)) + for _ in 1:arity(op)]) + return SyntaxTree(op, ch) + end + end + + # If the alphabet is not iterable, this function should not work. + if !isfinite(alphabet) + @warn "Attempting to generate random formulas from " * + "(infinite) alphabet of type $(typeof(alphabet))!" + end + + return _randformula(rng, height, modaldepth) +end + +function randformula( + rng::AbstractRNG, + height::Integer, + g::AbstractGrammar, + args...; + kwargs... +) + randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) +end + +# Helper +function randformula( + height::Integer, + args...; + rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, + kwargs... +) + randformula(initrng(rng), height, args...; kwargs...) +end diff --git a/src/generation/models.jl b/src/generation/models.jl new file mode 100644 index 00000000..f99bec80 --- /dev/null +++ b/src/generation/models.jl @@ -0,0 +1,78 @@ +using Graphs +using Random + +""" + function randframe( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + nworlds::Int64, + nedges::Int64, + facts::Vector{SyntaxLeaf} + end + +Return a random Kripke Frame, which is a directed graph interpreted as a +[`SoleLogics.ExplicitCrispUniModalFrame`](@ref). The underlying graph is generated using +[`Graphs.SimpleGraphs.SimpleDiGraph`](@ref). + +# Arguments: +* `rng` is a random number generator, or the seed used to create one; +* `nworld` is the number of worlds (nodes) in the frame. Worlds are numbered from `1` + to `nworld` included. +* `nedges` is the number of relations (edges) in the frame; +* `facts` is a vector of generic [`SyntaxLeaf`](@ref). + +See also [`SyntaxLeaf`](@ref), [`Graphs.SimpleGraphs.SimpleDiGraph`](@ref), +[`SoleLogics.ExplicitCrispUniModalFrame`](@ref). +""" +function randframe( + nworlds::Int64, + nedges::Int64; + rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG +) + randframe(rng, nworlds, nedges) +end + +function randframe( + rng::Union{Integer,AbstractRNG}, + nworlds::Int64, + nedges::Int64 +) + worlds = World.(1:nworlds) + graph = Graphs.SimpleDiGraph(nworlds, nedges, rng=initrng(rng)) + return SoleLogics.ExplicitCrispUniModalFrame(worlds, graph) +end + +""" + function randmodel( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + nworlds::Int64, + nedges::Int64, + facts::Vector{SyntaxLeaf}; + truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} = BooleanAlgebra(); + rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG + ) +""" +function randmodel( + nworlds::Int64, + nedges::Int64, + facts::Vector{<:SyntaxLeaf}, + truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} = BooleanAlgebra(); + rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG +) + truthvalues = inittruthvalues(truthvalues) + randmodel(initrng(rng), nworlds, nedges, facts, truthvalues) +end + +function randmodel( + rng::AbstractRNG, + nworlds::Int64, + nedges::Int64, + facts::Vector{<:SyntaxLeaf}, + truthvalues::AbstractVector{<:Truth} +) + fr = randframe(rng, nworlds, nedges) + valuation = Dict( + [w => TruthDict([f => rand(truthvalues) for f in facts]) for w in fr.worlds] + ) + + return KripkeStructure(fr, valuation) +end diff --git a/src/random.jl b/src/generation/random.jl similarity index 99% rename from src/random.jl rename to src/generation/random.jl index 08141200..3fc810f2 100644 --- a/src/random.jl +++ b/src/generation/random.jl @@ -1,3 +1,6 @@ +# This script is deprecated. +# See generation/formula.jl and generation/models.jl + using Graphs using Random using StatsBase diff --git a/src/utils.jl b/src/utils.jl index 817c5d21..a0807de4 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -644,62 +644,6 @@ function Base.in(p::Atom, a::UnionAlphabet) return any(sa -> Base.in(p, sa), subalphabets(a)) end -""" - randatom( - rng::Union{Integer,AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol=:uniform, - subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing - )::Atom - -Sample an atom from a `UnionAlphabet`. By default, the sampling is uniform with respect to the atoms. -However, 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. - -See also [`UnionAlphabet`](@ref). -""" -function randatom( - rng::Union{Integer, AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol = :uniform, - subalphabets_weights::Union{AbstractWeights, AbstractVector{<:Real}, Nothing} = nothing, -)::Atom - - # @show a - @assert atompicking_mode in [:uniform, :uniform_subalphabets, :weighted] "Value for `atompicking_mode` not..." - rng = initrng(rng) - alphs = subalphabets(a) - - if atompicking_mode == :weighted - if isnothing(subalphabets_weights) - error("`:weighted` picking_mode requires weights in `subalphabets_weights` ") - end - @assert length(subalphabets_weights)==length(alphs) "Mismatching numbers of alphabets "* - "($(length(alphs))) and weights ($(length(subalphabets_weights)))." - subalphabets_weights = StatsBase.weights(subalphabets_weights) - pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) - else - subalphabets_weights = begin - # This atomatically excludes subalphabets with empty threshold vector - if atompicking_mode == :uniform_subalphabets - # set the weight of the empty alphabets to zero - weights = Weights(ones(Int, length(alphs))) - weights[natoms.(alphs) == 0] .= 0 - elseif atompicking_mode == :uniform - weights = Weights(natoms.(alphs)) - end - weights - end - pickedalphabet = sample(rng, alphs, subalphabets_weights) - end - # @show a - # @show subalphabets_weights - # @show pickedalphabet - return randatom(rng, pickedalphabet) -end - ############################################################################################ #### CompleteFlatGrammar ################################################################### ############################################################################################ @@ -928,4 +872,3 @@ function _baselogic(; return BaseLogic(grammar, algebra) end - From 334f29cefd968388807c65b4fbdea59c0015b193 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Sun, 13 Oct 2024 21:31:21 +0200 Subject: [PATCH 17/90] another randatom dispatch moved from logic.jl to generation/formula.jl --- src/SoleLogics.jl | 1 + src/generation/formula.jl | 34 ++++++++++++++++++++++++++++++++-- src/types/logic.jl | 23 ----------------------- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 3a957a08..597b29cc 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -132,6 +132,7 @@ include("parse.jl") ############################################################################################ +export randatom export randbaseformula, randformula export randframe, randmodel diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 00aba010..6348f1d0 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -4,6 +4,29 @@ using StatsBase import Random: rand import StatsBase: sample +""" + randatom(a::AbstractAlphabet, args...; kwargs...) + randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...) + +Return a random atom from a *finite* alphabet. + +See also [`natoms`](@ref), [`AbstractAlphabet`](@ref). +""" +function randatom(a::AbstractAlphabet, args...; kwargs...) + randatom(Random.GLOBAL_RNG, a, args...; kwargs...) +end + +function randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...) + if isfinite(a) + # TODO: note that `atoms(a)` can lead to brutal reduction in performance, + # if one forgets to implement specific methods for `randatom` for custom alphabets! + return Base.rand(rng, atoms(a), args...; kwargs...) + else + error("Please provide method randatom(rng::$(typeof(rng)), " * + "alphabet::$(typeof(a)), args...; kwargs...)") + end +end + """ randatom( rng::Union{Integer,AbstractRNG}, @@ -12,8 +35,8 @@ import StatsBase: sample subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing )::Atom -Sample an atom from a `UnionAlphabet`. By default, the sampling is uniform with respect to -the atoms. +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. @@ -21,6 +44,13 @@ respect to the sub-alphabets. Moreover, one can specify a `:weighted` `atompicking_mode`, together with a `subalphabets_weights` vector. +# Examples +```julia-repl +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(alphabet) +``` + See also [`UnionAlphabet`](@ref). """ function randatom( diff --git a/src/types/logic.jl b/src/types/logic.jl index b160e4e5..f460c709 100644 --- a/src/types/logic.jl +++ b/src/types/logic.jl @@ -146,29 +146,6 @@ function natoms(a::AbstractAlphabet)::Integer end end -""" - randatom(a::AbstractAlphabet, args...; kwargs...) - randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...) - -Return a random atom from a *finite* alphabet. - -See also [`natoms`](@ref), [`AbstractAlphabet`](@ref). -""" -function randatom(a::AbstractAlphabet, args...; kwargs...) - randatom(Random.GLOBAL_RNG, a, args...; kwargs...) -end - -function randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...) - if isfinite(a) - # TODO: note that `atoms(a)` can lead to brutal reduction in performance, - # if one forgets to implement specific methods for `randatom` for custom alphabets! - return Base.rand(rng, atoms(a), args...; kwargs...) - else - error("Please provide method randatom(rng::$(typeof(rng)), " * - "alphabet::$(typeof(a)), args...; kwargs...)") - end -end - # Helper function Base.length(a::AbstractAlphabet) @warn "Please use `natoms` instead of `Base.length` with alphabets." From e5c81ed6779af7d5139a63f41622d144ed568375 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Sun, 13 Oct 2024 22:02:27 +0200 Subject: [PATCH 18/90] randatom for UnionAlphabet refactored --- src/generation/formula.jl | 56 +++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 6348f1d0..a9c3b5f3 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -5,21 +5,40 @@ import Random: rand import StatsBase: sample """ - randatom(a::AbstractAlphabet, args...; kwargs...) - randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...) + randatom( + [rng::Union{Random.AbstractRNG,Integer},] + a::AbstractAlphabet, + args...; + kwargs...) Return a random atom from a *finite* alphabet. +# Examples +```julia-repl +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`](@ref), [`AbstractAlphabet`](@ref). """ function randatom(a::AbstractAlphabet, args...; kwargs...) randatom(Random.GLOBAL_RNG, a, args...; kwargs...) end - -function randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...) +function randatom( + rng::Union{Random.AbstractRNG,Integer}, + a::AbstractAlphabet, + args...; + kwargs... +) if isfinite(a) - # TODO: note that `atoms(a)` can lead to brutal reduction in performance, - # if one forgets to implement specific methods for `randatom` for custom alphabets! + # Commented because otherwise this is getting spammed + # @warn "Consider implementing a specific `randatom` dispatch for your alphabet " * + # "type ($(typeof(a))) to increase performances." + + rng = initrng(rng) return Base.rand(rng, atoms(a), args...; kwargs...) else error("Please provide method randatom(rng::$(typeof(rng)), " * @@ -46,15 +65,30 @@ Moreover, one can specify a `:weighted` `atompicking_mode`, together with a # Examples ```julia-repl -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(alphabet) +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`](@ref). """ function randatom( - rng::Union{Integer, AbstractRNG}, + rng::Union{Integer,AbstractRNG}, a::UnionAlphabet; atompicking_mode::Symbol = :uniform, subalphabets_weights::Union{AbstractWeights, AbstractVector{<:Real}, Nothing} = nothing, @@ -79,7 +113,7 @@ function randatom( if atompicking_mode == :uniform_subalphabets # set the weight of the empty alphabets to zero weights = Weights(ones(Int, length(alphs))) - weights[natoms.(alphs) == 0] .= 0 + weights[natoms.(alphs) .== 0] .= 0 elseif atompicking_mode == :uniform weights = Weights(natoms.(alphs)) end From 75cc52a0dae80c9ce36015b6adba2ef26b75dec1 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Sun, 13 Oct 2024 22:03:11 +0200 Subject: [PATCH 19/90] Union type in randatom minor change (commas spacing and Nothing is now the first type) --- src/generation/formula.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/generation/formula.jl b/src/generation/formula.jl index a9c3b5f3..1fe5f66c 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -91,7 +91,8 @@ function randatom( rng::Union{Integer,AbstractRNG}, a::UnionAlphabet; atompicking_mode::Symbol = :uniform, - subalphabets_weights::Union{AbstractWeights, AbstractVector{<:Real}, Nothing} = nothing, + subalphabets_weights::Union{ + Nothing,AbstractWeights,AbstractVector{<:Real}} = nothing, )::Atom # @show a From aa953186e8f939e293f18f3ae71e249ad33ddda9 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Sun, 13 Oct 2024 23:09:29 +0200 Subject: [PATCH 20/90] Archived anchored-formula, fix interpretation sets and Int64->Integer --- src/SoleLogics.jl | 4 +- src/{ => old-code}/anchored-formula.jl | 2 +- src/random.jl | 24 +++++----- src/types/interpretation-sets.jl | 66 ++++++++++++++++++++++---- src/utils/interpretation-sets.jl | 54 ++------------------- test/check/propositional.jl | 16 +++---- test/parse.jl | 62 +++++++++++++----------- test/random.jl | 13 +++-- 8 files changed, 122 insertions(+), 119 deletions(-) rename src/{ => old-code}/anchored-formula.jl (99%) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 1007eb7e..e9b45cc7 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -141,9 +141,9 @@ include("random.jl") ############################################################################################ -export AnchoredFormula +# export AnchoredFormula -include("anchored-formula.jl") +# include("anchored-formula.jl") ############################################################################################ diff --git a/src/anchored-formula.jl b/src/old-code/anchored-formula.jl similarity index 99% rename from src/anchored-formula.jl rename to src/old-code/anchored-formula.jl index e7ac14f0..06e2ad31 100644 --- a/src/anchored-formula.jl +++ b/src/old-code/anchored-formula.jl @@ -298,7 +298,7 @@ function parseformula( end """$(doc_randformula)""" -function randbaseformula( +function randformula( height::Integer, g::AbstractGrammar; kwargs... diff --git a/src/random.jl b/src/random.jl index 08141200..51c7f5a5 100644 --- a/src/random.jl +++ b/src/random.jl @@ -396,8 +396,8 @@ end """ function randframe( [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] - nworlds::Int64, - nedges::Int64, + nworlds::Int, + nedges::Int, facts::Vector{SyntaxLeaf} end @@ -416,8 +416,8 @@ See also [`SyntaxLeaf`](@ref), [`Graphs.SimpleGraphs.SimpleDiGraph`](@ref), [`SoleLogics.ExplicitCrispUniModalFrame`](@ref). """ function randframe( - nworlds::Int64, - nedges::Int64; + nworlds::Int, + nedges::Int; rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG ) randframe(rng, nworlds, nedges) @@ -425,8 +425,8 @@ end function randframe( rng::Union{Integer,AbstractRNG}, - nworlds::Int64, - nedges::Int64 + nworlds::Int, + nedges::Int ) worlds = World.(1:nworlds) graph = Graphs.SimpleDiGraph(nworlds, nedges, rng=initrng(rng)) @@ -436,16 +436,16 @@ end """ function randmodel( [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] - nworlds::Int64, - nedges::Int64, + nworlds::Int, + nedges::Int, facts::Vector{SyntaxLeaf}; truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} = BooleanAlgebra(); rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG ) """ function randmodel( - nworlds::Int64, - nedges::Int64, + nworlds::Int, + nedges::Int, facts::Vector{<:SyntaxLeaf}, truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} = BooleanAlgebra(); rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG @@ -456,8 +456,8 @@ end function randmodel( rng::AbstractRNG, - nworlds::Int64, - nedges::Int64, + nworlds::Int, + nedges::Int, facts::Vector{<:SyntaxLeaf}, truthvalues::AbstractVector{<:Truth} ) diff --git a/src/types/interpretation-sets.jl b/src/types/interpretation-sets.jl index 22469f21..d538928b 100644 --- a/src/types/interpretation-sets.jl +++ b/src/types/interpretation-sets.jl @@ -13,30 +13,48 @@ These structures are especially useful when performing [model checking](https://en.wikipedia.org/wiki/Model_checking). # Interface -- `interpretationtype(s)` -- `alphabet(s)` -- See also [`AbstractDataset`](@ref) +- [`interpretationtype(S)`](@ref) +- [`alphabet(s)`](@ref) +- [`getinstance(s)`](@ref) +- [`concatdatasets(ss...)`](@ref) +- [`instances(s, idxs, return_view; kwargs...)`](@ref) +- [`ninstances(s)`](@ref) # Utility Functions -- `valuetype(s)` -- `truthtype(s)` +- [`valuetype(s)`](@ref) +- [`truthtype(s)`](@ref) +- [`slicedataset(s, idxs; kwargs...)`](@ref) +- [`eachinstance(s)`](@ref) -See also[`truthtype`](@ref), -[`InterpretationVector`](@ref). +# Utility Functions (with more-than-propositional logics) +- [`worldtype(s)`](@ref) +- [`frametype(s)`](@ref) +- [`frame(s, i_instance)`](@ref) +- [`accessibles(s, i_instance, args...)`](@ref) +- [`allworlds(s, i_instance, args...)`](@ref) +- [`nworlds(s, i_instance)`](@ref) + +See also [`InterpretationVector`](@ref). """ abstract type AbstractInterpretationSet <: AbstractDataset end -# TODO improve general doc. +""" + interpretationtype(S::Type{<:AbstractInterpretationSet}) + interpretationtype(s::AbstractInterpretationSet) + +Return a supertype for the interpretations of a given (type of) + interpretation set. + +See also[`truthtype`](@ref), [`InterpretationSet`](@ref). +""" function interpretationtype(S::Type{<:AbstractInterpretationSet}) return error("Please, provide method interpretationtype(::$(typeof(S))).") end interpretationtype(s::AbstractInterpretationSet) = interpretationtype(typeof(s)) -# TODO improve general doc. valuetype(S::Type{<:AbstractInterpretationSet}) = valuetype(interpretationtype(S)) valuetype(s::AbstractInterpretationSet) = valuetype(typeof(s)) -# TODO improve general doc. truthtype(S::Type{<:AbstractInterpretationSet}) = truthtype(interpretationtype(S)) truthtype(s::AbstractInterpretationSet) = truthtype(typeof(s)) @@ -51,6 +69,34 @@ function alphabet(s::AbstractInterpretationSet)::Alphabet return error("Please, provide method alphabet(::$(typeof(s))).") end +# function getinstance(s::AbstractInterpretationSet, i_instance::Integer) +# return error("Please, provide method getinstance(::$(typeof(s)), i_instance::Integer).") +# end + function eachinstance(s::AbstractInterpretationSet) return (getinstance(s, i_instance) for i_instance in 1:ninstances(s)) end + +############################################################################################ +############################# Helpers for (Multi-)modal logics ############################# +############################################################################################ + +worldtype(S::Type{<:AbstractInterpretationSet}) = worldtype(interpretationtype(S)) +worldtype(s::AbstractInterpretationSet) = worldtype(typeof(s)) + +frametype(S::Type{<:AbstractInterpretationSet}) = frametype(interpretationtype(S)) +frametype(s::AbstractInterpretationSet) = frametype(typeof(s)) + +function frame(s::AbstractInterpretationSet, i_instance::Integer) + return error("Please, provide method frame(::$(typeof(s)), ::$(typeof(i_instance))).") +end +function accessibles(s::AbstractInterpretationSet, i_instance::Integer, args...) + accessibles(frame(s, i_instance), args...) +end +function allworlds(s::AbstractInterpretationSet, i_instance::Integer, args...) + allworlds(frame(s, i_instance), args...) +end + +function nworlds(s::AbstractInterpretationSet, i_instance::Integer) + nworlds(frame(s, i_instance)) +end diff --git a/src/utils/interpretation-sets.jl b/src/utils/interpretation-sets.jl index 03b478ed..c43f128a 100644 --- a/src/utils/interpretation-sets.jl +++ b/src/utils/interpretation-sets.jl @@ -2,7 +2,7 @@ """ struct LogicalInstance{S<:AbstractInterpretationSet} s::S - i_instance::Int64 + i_instance::Int end Object representing the i-th interpretation of an interpretation set. @@ -15,7 +15,7 @@ set structures. struct LogicalInstance{S<:AbstractInterpretationSet} <: AbstractInterpretation s::S - i_instance::Int64 + i_instance::Int function LogicalInstance{S}( s::S, @@ -220,9 +220,9 @@ end instances::Vector{M} end -A dataset of interpretations instantiated as a vector. +A dataset of interpretations, instantiated as a vector. -[`AbstractInterpretationSet`](@ref). +See also [`AbstractInterpretationSet`](@ref). """ struct InterpretationVector{M<:AbstractInterpretation} <: AbstractInterpretationSet instances::Vector{M} @@ -234,49 +234,3 @@ end Base.getindex(s::InterpretationVector, i_instance::Integer) = Base.getindex(s.instances, i_instance) getinstance(s::InterpretationVector, i_instance::Integer) = Base.getindex(s, i_instance) - -############################################################################################ - -# TODO -# abstract type AbstractFrameSet{FR<:AbstractFrame} end - -# function Base.getindex(::AbstractFrameSet{FR}, i_instance::Integer)::FR where {FR<:AbstractFrame} -# return error("Please, provide ...") -# end - -# struct FrameSet{FR<:AbstractFrame} <: AbstractFrameSet{FR} -# frames::Vector{FR} -# end - -# Base.getindex(ks::FrameSet, i_instance::Integer) = Base.getindex(ks.frames, i_instance::Integer) - -# struct UniqueFrameSet{FR<:AbstractFrame} <: AbstractFrameSet{FR} -# frame::FR -# end - -# Base.getindex(ks::UniqueFrameSet, i_instance::Integer) = ks.frame - -############################################################################################ -############################# Helpers for (Multi-)modal logics ############################# -############################################################################################ - -worldtype(S::Type{AbstractInterpretationSet}) = worldtype(interpretationtype(S)) -worldtype(s::AbstractInterpretationSet) = worldtype(typeof(s)) - -frametype(S::Type{AbstractInterpretationSet}) = frametype(interpretationtype(S)) -frametype(s::AbstractInterpretationSet) = frametype(typeof(s)) - -# function relations(s::AbstractInterpretationSet) -# return error("Please, provide method relations(::$(typeof(s))).") -# end - -# function frame(s::AbstractInterpretationSet, i_instance::Integer) -# return frame(getinstance(s, i_instance)) -# end - -function frame(s::AbstractInterpretationSet, i_instance::Integer) - return error("Please, provide method frame(::$(typeof(s)), ::$(typeof(i_instance))).") -end -accessibles(s::AbstractInterpretationSet, i_instance::Integer, args...) = accessibles(frame(s, i_instance), args...) -allworlds(s::AbstractInterpretationSet, i_instance::Integer, args...) = allworlds(frame(s, i_instance), args...) -nworlds(s::AbstractInterpretationSet, i_instance::Integer) = nworlds(frame(s, i_instance)) diff --git a/test/check/propositional.jl b/test/check/propositional.jl index e196879c..6157c188 100644 --- a/test/check/propositional.jl +++ b/test/check/propositional.jl @@ -1,5 +1,5 @@ # using Revise; using SoleLogics; using Test -using SoleLogics: parsebaseformula +# using SoleLogics: parsebaseformula @testset "Propositional model checking" begin @@ -9,7 +9,7 @@ d0 = Dict(["a" => true, "b" => false, "c" => true]) @test haskey(d0, Atom("a")) @test d0["a"] @test !d0["b"] -@test check(parsebaseformula("a ∧ ¬b"), d0) +# @test check(parsebaseformula("a ∧ ¬b"), d0) @test check(parseformula("a ∧ c"), d0) v0 = ["a", "c"] @@ -17,11 +17,11 @@ v0 = ["a", "c"] @test !("b" in v0) @test !(Atom("a") in v0) @test check(parseformula("a ∧ ¬b"), v0) -@test check(parsebaseformula("a ∧ c"), v0) +# @test check(parsebaseformula("a ∧ c"), v0) -@test !check(parsebaseformula("a ∧ b"), ["a"]) +# @test !check(parsebaseformula("a ∧ b"), ["a"]) @test !check(parseformula("a ∧ ¬b"), ["a", "b"]) -@test check(parsebaseformula("a ∧ ¬b"), ["a"]) +# @test check(parsebaseformula("a ∧ ¬b"), ["a"]) @test_nowarn TruthDict(1:4) @test_nowarn TruthDict(1:4, false) @@ -33,7 +33,7 @@ t0 = @test_nowarn TruthDict(["a" => true, "b" => false, "c" => true]) @test haskey(t0, "b") @test check(Atom("a"), t0) @test !check(Atom("b"), t0) -@test check(parsebaseformula("a ∨ b"), t0) +# @test check(parsebaseformula("a ∨ b"), t0) t1 = @test_nowarn TruthDict([1 => true, 2 => false, 3 => true]) @@ -81,8 +81,8 @@ t2 = @test_nowarn TruthDict(Pair{Real,Bool}[1.0 => true, 2 => true, 3 => true]) @test_nowarn DefaultedTruthDict(Atom(1.0) => true) @test !check(parseformula("a ∧ b"), DefaultedTruthDict(["a"])) -@test !check(parsebaseformula("a ∧ ¬b"), DefaultedTruthDict(["a", "b"])) -@test check(parsebaseformula("a ∧ ¬b"), DefaultedTruthDict(["a"])) +# @test !check(parsebaseformula("a ∧ ¬b"), DefaultedTruthDict(["a", "b"])) +# @test check(parsebaseformula("a ∧ ¬b"), DefaultedTruthDict(["a"])) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/test/parse.jl b/test/parse.jl index c2d91528..d7044ad1 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -1,6 +1,6 @@ import SoleLogics: arity -using SoleLogics: parsebaseformula, relation +using SoleLogics: relation # testing utilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -27,13 +27,6 @@ end @test parseformula("¬p∧q") == parseformula("¬(p)∧q") @test parseformula("¬p∧q") != parseformula("¬(p∧q)") -@test_nowarn parsebaseformula("p") - -@test_nowarn ¬ parsebaseformula("p") -@test_nowarn ¬ parseformula("p") -@test_nowarn ¬ parseformula("(s∧z)", propositionallogic()) -@test_nowarn ¬ parsebaseformula("p", propositionallogic()) - @test_nowarn parseformula("¬p∧q∧(¬s∧¬z)", [NEGATION, CONJUNCTION]) @test_nowarn parseformula("¬p∧q∧(¬s∧¬z)", [NEGATION]) @test_nowarn parseformula("¬p∧q∧{¬s∧¬z}", @@ -41,15 +34,6 @@ end @test_nowarn parseformula("¬p∧q∧ A ¬s∧¬z B", opening_parenthesis="A", closing_parenthesis="B") -@test operatorstype( - logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: SoleLogics.BaseModalConnectives -@test !(operatorstype( - logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: SoleLogics.BasePropositionalConnectives) -@test !(operatorstype(logic( - parsebaseformula("¬p∧q∧(¬s∧¬z)", modallogic()))) <: SoleLogics.BasePropositionalConnectives) -@test (@test_nowarn operatorstype( - logic(parsebaseformula("¬p∧q∧(¬s∧¬z)"))) <: SoleLogics.BasePropositionalConnectives) - @test_nowarn parseformula("¬p∧q→(¬s∧¬z)") @test syntaxstring(parseformula("⟨G⟩p")) == "⟨G⟩p" @@ -60,8 +44,6 @@ end @test_nowarn parseformula("⟨G⟩p") -@test alphabet(logic(parsebaseformula("p→q"))) == AlphabetOfAny{String}() - @test syntaxstring(parseformula("(◊¬p) ∧ (¬q)")) == "◊¬p ∧ ¬q" @test syntaxstring(parseformula("q → p → ¬q"), remove_redundant_parentheses=false) == "(q) → ((p) → (¬(q)))" # function notation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -260,16 +242,38 @@ _f = parseformula("{Gp ∧ ¬{G}q", [CurlyRelationalConnective(globalrel)]) [BoxRelationalConnective(testrel), DiamondRelationalConnective(testrel)] ) -# parsebaseformula ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -@test_throws ErrorException parsebaseformula("") -@test_broken parsebaseformula("⊤") -@test_broken parsebaseformula("⊤ ∧ ⊤") -@test_broken parsebaseformula("⊤ ∧ p") -@test_broken parsebaseformula("⊥ ∧ □¬((p∧¬q)→r)") -@test_broken parsebaseformula("□¬((p∧¬q)→r) ∧ ⊤") -@test_broken parsebaseformula("⊤ ∧ (⊥∧¬⊤→⊤)") -@test_nowarn parsebaseformula("□¬((p∧¬q)→r)") +# # parseba#= seformula ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# using SoleLogics: parsebaseformula + +# @test_throws ErrorException parsebaseformula("") +# @test_broken parsebaseformula("⊤") +# @test_broken parsebaseformula("⊤ ∧ ⊤") +# @test_broken parsebaseformula("⊤ ∧ p") +# @test_broken parsebaseformula("⊥ ∧ □¬((p∧¬q)→r)") +# @test_broken parsebaseformula("□¬((p∧¬q)→r) ∧ ⊤") +# @test_broken parsebaseformula("⊤ ∧ (⊥∧¬⊤→⊤)") +# @test_nowarn parsebaseformula("□¬((p∧¬q)→r)") + +# @test_nowarn parsebaseformula("p") + +# @test_nowarn ¬parsebaseformula("p") +# @test_nowarn ¬parseformula("p") +# @test_nowarn ¬parseformula("(s∧z)", propositionallogic()) +# @test_nowarn ¬parsebaseformula("p", propositionallogic()) + +# @test operatorstype( +# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: SoleLogics.BaseModalConnectives +# @test !(operatorstype( +# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: +# SoleLogics.BasePropositionalConnectives) +# @test !(operatorstype(logic( +# parsebaseformula("¬p∧q∧(¬s∧¬z)", modallogic()))) <: +# SoleLogics.BasePropositionalConnectives) +# @test (@test_nowarn operatorstype( +# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)"))) <: SoleLogics.BasePropositionalConnectives) + +# @test alphabet(logi =#c(parsebaseformula("p→q"))) == AlphabetOfAny{String}() # stress test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/test/random.jl b/test/random.jl index 685e63b6..3c8a9d20 100644 --- a/test/random.jl +++ b/test/random.jl @@ -1,6 +1,5 @@ using StatsBase import SoleLogics: arity -using SoleLogics: parsebaseformula using Random # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ random logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -47,12 +46,12 @@ w = [5,1,1,1,1,1,1] function_notation = true) end for i in 1:1000]) -@test all([begin - f = randbaseformula(i%5, _alphabet, _operators) - s = syntaxstring(f; function_notation = true) - s == syntaxstring(parsebaseformula(s; function_notation = true); - function_notation = true) - end for i in 1:1000]) +# @test all([begin +# f = randbaseformula(i%5, _alphabet, _operators) +# s = syntaxstring(f; function_notation = true) +# s == syntaxstring(parsebaseformula(s; function_notation = true); +# function_notation = true) +# end for i in 1:1000]) end From f7e864d434a0c5c81825de6fedef5200d4095774 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Mon, 14 Oct 2024 01:00:10 +0200 Subject: [PATCH 21/90] @__rng_dispatch macro to avoid writing 2 dispatches, where the only difference between the two is a fallback to a default seed. Not working at the moment. --- src/SoleLogics.jl | 5 +- src/generation/docstrings.jl | 127 +++++++++++++++++ src/generation/formula.jl | 260 ++++++++++++++--------------------- 3 files changed, 233 insertions(+), 159 deletions(-) create mode 100644 src/generation/docstrings.jl diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 597b29cc..c6e5dd1b 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -133,10 +133,11 @@ include("parse.jl") ############################################################################################ export randatom -export randbaseformula, randformula -export randframe, randmodel +export randformula, randbaseformula include("generation/formula.jl") + +export randframe, randmodel include("generation/models.jl") ############################################################################################ diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl new file mode 100644 index 00000000..0f5dd9b9 --- /dev/null +++ b/src/generation/docstrings.jl @@ -0,0 +1,127 @@ +randatom_docstring = """ + randatom( + [rng::Union{Random.AbstractRNG,Integer},] + a::AbstractAlphabet, + args...; + kwargs... + ) + +Return a random atom from a *finite* alphabet. + +# Examples +```julia-repl +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`](@ref), [`AbstractAlphabet`](@ref). +""" + +randatom_unionalphabet_docstring = """ + randatom( + rng::Union{Integer,AbstractRNG}, + a::UnionAlphabet; + atompicking_mode::Symbol=:uniform, + subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = 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-repl +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`](@ref). +""" + +rand_abstractalphabet_docstring = """ + Base.rand( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + alphabet::AbstractAlphabet, + args...; + kwargs... + )::Atom + +Randomly generate an [`Atom`](@ref) from an [`AbstractAlphabet`](@ref) according to a +uniform distribution. + +!!! details +If the `alphabet` is finite, the function defaults to `randatom(rng, alphabet)`, which +internally calls `Base.rand(rng, atoms(alphabet)`; +To limit the (otherwise infinite) sampling domain, a new dispatch must be implemented, +and additional keyword arguments should be provided: those will be forwarded to the innter +`randatom` call. + +See also [`AbstractAlphabet`], [`randatom`](@ref). +""" + +rand_abstractlogic_docstring = """ + function Base.rand( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + height::Integer, + l::AbstractLogic, + args...; + kwargs... + ) + +Generate a random formula of height `height` and belonging to logic `l`. + +See also [`AbstractLogic`](@ref). +""" + +rand_completeflatgrammar_docstring = """ + Base.rand( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + height::Integer, + g::CompleteFlatGrammar, + args... + )::Formula + +Generate a random formula of height `height`, honoring the grammar `g`. + +See also [`CompleteFlatGrammar`](@ref). +""" + +rand_granular_docstring = """ + Base.rand( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + height::Integer, + connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, + atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, + truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, + args...; + rng::AbstractRNG = Random.GLOBAL_RNG, + kwargs... + )::Formula + +See also [`AbstractAlphabet`](@ref), [`AbstractAlgebra`](@ref), [`Connective`](@ref), +[`Operator`](@ref). +""" diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 1fe5f66c..b53c7954 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -4,30 +4,53 @@ using StatsBase import Random: rand import StatsBase: sample -""" - randatom( - [rng::Union{Random.AbstractRNG,Integer},] - a::AbstractAlphabet, - args...; - kwargs...) +include("docstrings.jl") -Return a random atom from a *finite* alphabet. +macro __rng_dispatch(ex, docstr) + if ex.head != :function + throw(ArgumentError("Expected a function definition")) + end -# Examples -```julia-repl -julia> alphabet = ExplicitAlphabet(1:5) -ExplicitAlphabet{Int64}(Atom{Int64}[Atom{Int64}: 1, Atom{Int64}: 2, Atom{Int64}: 3, Atom{Int64}: 4, Atom{Int64}: 5]) + # Extract the function signature, its name and arguments + func_signature = ex.args[1] + func_name = func_signature.args[1] + args = func_signature.args[2:end] + + # Extract the arguments after the first one; + # the first one must be an Union{Random.AbstractRNG,Integer}, + # otherwise, this macro makes no sense. + if length(args) > 0 + quote + if !isa($args[1], Union{Random.AbstractRNG,Integer}) + throw(ArgumentError("Expected function's first argument to be of type " * + "Union{Random.AbstractRNG,Integer}.") + ) + end + end -julia> randatom(42, alphabet) -Atom{Int64}: 4 -``` + new_args = args[2:end] + else + throw(ArgumentError("Expected function's argument to be atleast 2, the first of " * + "which of type Union{Random.AbstractRNG,Integer}.")) + end -See also [`natoms`](@ref), [`AbstractAlphabet`](@ref). -""" -function randatom(a::AbstractAlphabet, args...; kwargs...) - randatom(Random.GLOBAL_RNG, a, args...; kwargs...) + # Prepare the new dispatch logic + new_body = quote + $func_name(Random.GLOBAL_RNG, $(new_args...)) + end + + # Define both old and new dispatch (their name is the same); + # for the second dispatch (those with less arguments), also include docstring. + quote + @eval @doc $docstr function $func_name($(new_args...)) + $new_body + end + end end -function randatom( + + + +@__rng_dispatch function randatom( rng::Union{Random.AbstractRNG,Integer}, a::AbstractAlphabet, args...; @@ -44,59 +67,24 @@ function randatom( error("Please provide method randatom(rng::$(typeof(rng)), " * "alphabet::$(typeof(a)), args...; kwargs...)") end -end - -""" - randatom( - rng::Union{Integer,AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol=:uniform, - subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing - )::Atom - -Sample an atom from a `UnionAlphabet`. -By default, the sampling is uniform with respect to the atoms. +end $(randatom_docstring) -By setting `atompicking_mode = :uniform_subalphabets` one can force a uniform sampling with -respect to the sub-alphabets. +# TODO - remove this dispatch if test works +# function randatom(a::AbstractAlphabet, args...; kwargs...) +# randatom(Random.GLOBAL_RNG, a, args...; kwargs...) +# end -Moreover, one can specify a `:weighted` `atompicking_mode`, together with a -`subalphabets_weights` vector. - -# Examples -```julia-repl -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`](@ref). -""" -function randatom( +@__rng_dispatch function randatom( rng::Union{Integer,AbstractRNG}, a::UnionAlphabet; atompicking_mode::Symbol = :uniform, subalphabets_weights::Union{ Nothing,AbstractWeights,AbstractVector{<:Real}} = nothing, )::Atom + _atompicking_modes = [:uniform, :uniform_subalphabets, :weighted] + @assert atompicking_mode in _atompicking_modes "Invalid value for `atompicking_mode` " * + "($(atompicking_mode)). Chosee between $(atompicking_modes)." - # @show a - @assert atompicking_mode in [:uniform, :uniform_subalphabets, :weighted] "Value for `atompicking_mode` not..." rng = initrng(rng) alphs = subalphabets(a) @@ -104,8 +92,8 @@ function randatom( if isnothing(subalphabets_weights) error("`:weighted` picking_mode requires weights in `subalphabets_weights` ") end - @assert length(subalphabets_weights)==length(alphs) "Mismatching numbers of alphabets "* - "($(length(alphs))) and weights ($(length(subalphabets_weights)))." + @assert length(subalphabets_weights)==length(alphs) "Mismatching numbers of "* + "alphabets ($(length(alphs))) and weights ($(length(subalphabets_weights)))." subalphabets_weights = StatsBase.weights(subalphabets_weights) pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) else @@ -122,108 +110,77 @@ function randatom( end pickedalphabet = sample(rng, alphs, subalphabets_weights) end - # @show a - # @show subalphabets_weights - # @show pickedalphabet - return randatom(rng, pickedalphabet) -end - -doc_rand = """ - Base.rand( - [rng::AbstractRNG = Random.GLOBAL_RNG], - height::Integer, - l::AbstractLogic, - args...; - kwargs... - )::Formula - - Base.rand( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - height::Integer, - g::CompleteFlatGrammar, - args... - )::Formula - Base.rand( - height::Integer, - connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, - atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, - truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, - args...; - rng::AbstractRNG = Random.GLOBAL_RNG, - kwargs... - )::Formula - -If a [`CompleteFlatGrammar`](@ref) is provided together with an -`height` a [`Formula`](@ref) could also be generated. - -# Implementation -If the `alphabet` is finite, the function defaults to `rand(rng, atoms(alphabet))`; -otherwise, it must be implemented, and additional keyword arguments should be provided -in order to limit the (otherwise infinite) sampling domain. + return randatom(rng, pickedalphabet) +end $(randatom_unionalphabet_docstring) -See also -[`AbstractAlphabet`](@ref), [`Atom`](@ref), [`CompleteFlatGrammar`](@ref), -[`Formula`](@ref), [`randformula`](@ref). -""" -""" - Base.rand( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - alphabet::AbstractAlphabet, - args...; - kwargs... - )::Atom -Randomly generate an [`Atom`](@ref) from an [`AbstractAlphabet`](@ref) according to a -uniform distribution. -""" -function Base.rand(a::AbstractAlphabet, args...; kwargs...) - Base.rand(Random.GLOBAL_RNG, a, args...; kwargs...) -end -function Base.rand( +# TODO - remove this dispatch if test works +# function Base.rand(a::AbstractAlphabet, args...; kwargs...) +# Base.rand(Random.GLOBAL_RNG, a, args...; kwargs...) +# end +@__rng_dispatch function Base.rand( rng::AbstractRNG, a::AbstractAlphabet, args...; kwargs... ) - randatom(rng, a, args...; kwargs...) -end + randatom(initrng(rng), a, args...; kwargs...) +end $(rand_abstractalphabet_docstring) -function Base.rand(height::Integer, l::AbstractLogic, args...; kwargs...) - Base.rand(Random.GLOBAL_RNG, height, l, args...; kwargs...) -end -function Base.rand( - rng::AbstractRNG, +# TODO - remove this dispatch if test works +# function Base.rand(height::Integer, l::AbstractLogic, args...; kwargs...) +# Base.rand(Random.GLOBAL_RNG, height, l, args...; kwargs...) +# end +@__rng_dispatch function Base.rand( + rng::Union{Integer,AbstractRNG}, height::Integer, l::AbstractLogic, args...; kwargs... ) - Base.rand(rng, grammar(l), args...; kwargs...) -end + Base.rand(initrng(rng), height, grammar(l), args...; kwargs...) +end $(rand_abstractlogic_docstring) -# For the case of a CompleteFlatGrammar, the alphabet and the operators suffice. -function Base.rand( - height::Integer, - g::CompleteFlatGrammar, - args... -) - Base.rand(Random.GLOBAL_RNG, height, g, args...) -end -function Base.rand( - rng::AbstractRNG, + + +# TODO - remove this dispatch if test works +# function Base.rand( +# height::Integer, +# g::CompleteFlatGrammar, +# args... +# ) +# Base.rand(Random.GLOBAL_RNG, height, g, args...) +# end +@__rng_dispatch function Base.rand( + rng::Union{Integer,AbstractRNG}, height::Integer, g::CompleteFlatGrammar, args...; kwargs... ) - randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) -end - -function Base.rand( + randformula(initrng(rng), height, alphabet(g), operators(g), args...; kwargs...) +end $(rand_completeflatgrammar_docstring) + + + +# TODO - remove this dispatch if test works +# function Base.rand( +# height::Integer, +# atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, +# connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, +# truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, +# args...; +# kwargs... +# ) +# Base.rand( +# Random.GLOBAL_RNG, height, atoms, connectives, truthvalues, args...; kwargs...) +# end +@__rng_dispatch function Base.rand( + rng::Union{Integer,AbstractRNG}, height::Integer, atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, @@ -231,19 +188,8 @@ function Base.rand( args...; kwargs... ) - Base.rand(Random.GLOBAL_RNG, height, atoms, connectives, truthvalues, args...; - kwargs...) -end + rng = initrng(rng) -function Base.rand( - rng::AbstractRNG, - height::Integer, - atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, - connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, - truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, - args...; - kwargs... -) # If Truth's are specified as `operators`, then they cannot be simultaneously # provided as `truthvalues` @assert (connectives isa AbstractVector{<:Connective} || @@ -261,7 +207,7 @@ function Base.rand( end randformula(height, ops, atoms, args...; rng=rng, kwargs...) -end +end $(rand_granular_docstring) doc_sample = """ function StatsBase.sample( From 996f3402bea3a19b4629a0e834002f669488e00f Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:18:02 +0200 Subject: [PATCH 22/90] Fix tests --- src/SoleLogics.jl | 2 +- test/random.jl | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index e9b45cc7..1ae1c12d 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -134,7 +134,7 @@ include("parse.jl") ############################################################################################ -export randbaseformula, randformula +export randformula export randframe, randmodel include("random.jl") diff --git a/test/random.jl b/test/random.jl index 3c8a9d20..5f9aa0d1 100644 --- a/test/random.jl +++ b/test/random.jl @@ -10,8 +10,7 @@ _alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) _operators = [NEGATION, CONJUNCTION, IMPLICATION] w = [10,1,1] -@test_nowarn [randbaseformula(i, _alphabet, _operators) for i in 1:15] -@test_nowarn [randbaseformula(i, _alphabet, _operators, opweights=w) for i in 1:2] +@test_nowarn [randformula(i, _alphabet, _operators) for i in 1:15] @test_nowarn [randformula(i, _alphabet, _operators, opweights=w) for i in 1:10] end @@ -30,7 +29,7 @@ _operators = [NEGATION, CONJUNCTION, IMPLICATION, w = [5,1,1,1,1,1,1] @test all([begin - f = randbaseformula(3, _alphabet, _operators) + f = randformula(3, _alphabet, _operators) s = syntaxstring(f) s == syntaxstring(parseformula(s)) end for i in 1:1000]) @@ -47,7 +46,7 @@ w = [5,1,1,1,1,1,1] end for i in 1:1000]) # @test all([begin -# f = randbaseformula(i%5, _alphabet, _operators) +# f = randformula(i%5, _alphabet, _operators) # s = syntaxstring(f; function_notation = true) # s == syntaxstring(parsebaseformula(s; function_notation = true); # function_notation = true) @@ -65,8 +64,8 @@ g = SoleLogics.CompleteFlatGrammar(alph, ops) @test_nowarn Base.rand(4, g) @test_nowarn Base.rand(Random.MersenneTwister(1), 4, g) -@test_nowarn randbaseformula(4, g) -@test_nowarn randbaseformula(4, g; rng = Random.MersenneTwister(1)) +@test_nowarn randformula(4, g) +@test_nowarn randformula(4, g; rng = Random.MersenneTwister(1)) @test_nowarn StatsBase.sample(4, g) @test_nowarn StatsBase.sample(Random.MersenneTwister(1), 4, g) From 47e2f02d43ba5fcfc5c651070478dd1fb373f2d6 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:20:13 +0200 Subject: [PATCH 23/90] Archived AnchoredFormula --- docs/src/index.md | 1 - docs/src/more-on-formulas.md | 6 ++++-- src/SoleLogics.jl | 1 - src/utils.jl | 1 - test/core.jl | 14 +++++++------- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 03099ace..c2ab8262 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -50,7 +50,6 @@ Here is a map of SoleLogics' most important types and structures. Feels overwhel - [`DiamondRelationalConnective`](@ref) - [`BoxRelationalConnective`](@ref) - [`Formula`](@ref) - - [`AnchoredFormula`](@ref) - [`SyntaxStructure`](@ref) - [`SyntaxTree`](@ref) - [`SyntaxLeaf`](@ref) diff --git a/docs/src/more-on-formulas.md b/docs/src/more-on-formulas.md index 541534cf..3d0bfc3c 100644 --- a/docs/src/more-on-formulas.md +++ b/docs/src/more-on-formulas.md @@ -14,7 +14,6 @@ We proceed by presenting the random formulae generation engine, parsing and some Recalling the type hierarchy presented in [man-core](@ref), it is here enriched with the following new types and structures. - [`Formula`](@ref) - - [`AnchoredFormula`](@ref) - [`SyntaxStructure`](@ref) - [`Literal`](@ref) - [`LeftmostLinearForm`](@ref) @@ -36,6 +35,9 @@ CNF{SS<:SyntaxStructure} DNF{SS<:SyntaxStructure} ``` + ## Random sampling and generation diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 1ae1c12d..1fb7754d 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -142,7 +142,6 @@ include("random.jl") ############################################################################################ # export AnchoredFormula - # include("anchored-formula.jl") ############################################################################################ diff --git a/src/utils.jl b/src/utils.jl index 817c5d21..a2076a91 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -18,7 +18,6 @@ │ │ ├── Literal (e.g., p, ¬p) │ │ └── ... │ ├── TruthTable - │ ├── AnchoredFormula │ └── ... └── Connective ├── NamedConnective (e.g., ∧, ∨, →, ¬, □, ◊) diff --git a/test/core.jl b/test/core.jl index 6023d732..c7f9b0c0 100644 --- a/test/core.jl +++ b/test/core.jl @@ -124,12 +124,12 @@ logic_int = BaseLogic(grammar_int, SoleLogics.BooleanAlgebra()) @test Atom("aoeu") in propositionallogic() @test ! (Atom(1) in propositionallogic()) -@test_nowarn AnchoredFormula(Base.RefValue(logic_int), t1_int) -f_int = @test_nowarn AnchoredFormula(logic_int, t1_int) -@test_nowarn AnchoredFormula(logic_int, p1) -@test_nowarn AnchoredFormula(logic_int, p1; check_atoms = true) -@test_nowarn AnchoredFormula(logic_int, p100) -@test_throws AssertionError AnchoredFormula(logic_int, p100; check_atoms = true) +# @test_nowarn AnchoredFormula(Base.RefValue(logic_int), t1_int) +# f_int = @test_nowarn AnchoredFormula(logic_int, t1_int) +# @test_nowarn AnchoredFormula(logic_int, p1) +# @test_nowarn AnchoredFormula(logic_int, p1; check_atoms = true) +# @test_nowarn AnchoredFormula(logic_int, p100) +# @test_throws AssertionError AnchoredFormula(logic_int, p100; check_atoms = true) @test_throws MethodError 1 in f_int @test p1 in f_int @@ -212,7 +212,7 @@ f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int) @test_nowarn ∧((t2_int, ¬(f_int)),) @test_nowarn f_int(p1 ∧ p100) -@test f_int(p1 ∧ p100) isa AnchoredFormula +# @test f_int(p1 ∧ p100) isa AnchoredFormula @test_throws ErrorException f_int(p1 ∧ p100 ∧ p1_float) @test_throws ErrorException f_int(⊥ ∨ (p1 ∧ p100 ∧ p2 ∧ ⊤)) From 52c589260d04c26c7633ea1c4a4790c5ca112224 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:26:24 +0200 Subject: [PATCH 24/90] Fix tests --- test/core.jl | 72 +------------------------------ test/formulas/anchored-formula.jl | 71 ++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 71 deletions(-) create mode 100644 test/formulas/anchored-formula.jl diff --git a/test/core.jl b/test/core.jl index c7f9b0c0..fb9e46c4 100644 --- a/test/core.jl +++ b/test/core.jl @@ -124,20 +124,6 @@ logic_int = BaseLogic(grammar_int, SoleLogics.BooleanAlgebra()) @test Atom("aoeu") in propositionallogic() @test ! (Atom(1) in propositionallogic()) -# @test_nowarn AnchoredFormula(Base.RefValue(logic_int), t1_int) -# f_int = @test_nowarn AnchoredFormula(logic_int, t1_int) -# @test_nowarn AnchoredFormula(logic_int, p1) -# @test_nowarn AnchoredFormula(logic_int, p1; check_atoms = true) -# @test_nowarn AnchoredFormula(logic_int, p100) -# @test_throws AssertionError AnchoredFormula(logic_int, p100; check_atoms = true) - -@test_throws MethodError 1 in f_int -@test p1 in f_int -@test p1 in grammar(f_int) -@test ! (p1_number in f_int) -@test ! (p100 in f_int) -@test ! (Atom("1") in f_int) - t2_int = @test_nowarn ¬(t1_int) @test_nowarn ⊥() @@ -171,28 +157,7 @@ t2_int = @test_nowarn ¬(t1_int) @test_nowarn ∧(¬(t2_int), t2_int, ¬(t2_int) ∧ t2_int) @test_nowarn ¬(¬(p1)) -@test_throws ErrorException f_int ∨ ⊤ -@test_throws ErrorException ⊥ ∨ f_int -@test_nowarn ¬(f_int) -@test_nowarn f_int ∨ f_int -@test_nowarn ¬(f_int) ∨ f_int -@test_nowarn p1 ∨ f_int -@test_nowarn f_int ∨ p1 -@test_nowarn t2_int ∨ f_int -@test_nowarn f_int ∨ t2_int -# @test atoms(f_int ∨ (p1 ∨ p100)) == [p1, p1, p100] -@test unique(atoms(f_int ∨ (p1 ∨ p100))) == [p1, p100] -@test all(isa.(atoms(f_int ∨ (p1 ∨ p100)), atomstype(logic(f_int)))) - -f_conj_int = @test_throws AssertionError SoleLogics.composeformulas(CONJUNCTION, (f_int, f_int, f_int)) -f_conj_int = @test_nowarn SoleLogics.composeformulas(CONJUNCTION, (f_int, f_int)) -f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int, f_int) -f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int) -@test_nowarn DISJUNCTION(f_int, f_int, f_conj_int) -@test_nowarn CONJUNCTION(f_int, f_int, p1) -@test_nowarn CONJUNCTION(p1, f_int, p1) -@test_nowarn CONJUNCTION(t2_int, f_int, p1) -@test_nowarn CONJUNCTION(f_int, t2_int, p1) + @test_nowarn CONJUNCTION(t2_int, t2_int) @test_nowarn CONJUNCTION(t2_int, t2_int, p1) @test_nowarn CONJUNCTION(t2_int, p1, p1) @@ -200,21 +165,11 @@ f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int) @test_nowarn CONJUNCTION(p1, p1, p1) @test_nowarn p1 ∨ t2_int -@test typeof(¬(f_int)) == typeof(f_int) -@test_nowarn ∧((¬(f_int), f_int),) -# @test promote_type(typeof(f_int), typeof(t2_int)) == typeof(f_int) # @test promote_type(AnchoredFormula, SyntaxBranch) == AnchoredFormula # @test promote_type(SyntaxBranch, AnchoredFormula) == AnchoredFormula -@test_nowarn ∧((¬(f_int), f_int),) -@test_nowarn ∧((¬(f_int), t2_int),) -@test_nowarn ∧((t2_int, ¬(f_int)),) -@test_nowarn f_int(p1 ∧ p100) -# @test f_int(p1 ∧ p100) isa AnchoredFormula -@test_throws ErrorException f_int(p1 ∧ p100 ∧ p1_float) -@test_throws ErrorException f_int(⊥ ∨ (p1 ∧ p100 ∧ p2 ∧ ⊤)) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ checking ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -231,30 +186,6 @@ f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int) @test_nowarn TruthDict([p1 => true]) @test_nowarn TruthDict(Dict([p1 => true])) -anch_φ_int = f_int(p1 ∧ p100 ∧ p2) -anch2_φ_int = f_int(p1 ∧ p100 → p2) - -for i in 1:10 - _tdict = TruthDict(Dict([p => rand([true, false]) for p in unique(atoms(anch_φ_int))])) - # i == 1 && println(_tdict) - check(anch_φ_int, _tdict) && @test all(istop, collect(values(_tdict.truth))) - !check(anch_φ_int, _tdict) && @test !all(istop, collect(values(_tdict.truth))) - check(anch2_φ_int, _tdict) - - @test_nowarn _tdict[anch_φ_int] - @test_nowarn anch_φ_int(_tdict) - @test anch_φ_int(_tdict) == _tdict[anch_φ_int] -end - -tdict = TruthDict(Dict([p => true for p in unique(atoms(anch_φ_int))])) -@test check(anch_φ_int, tdict) - -tdict = TruthDict(Dict([p => false for p in unique(atoms(anch_φ_int))])) -@test !check(anch_φ_int, tdict) - -@test check(anch_φ_int, DefaultedTruthDict([], true)) -@test check(anch_φ_int, DefaultedTruthDict(true)) -@test !check(anch_φ_int, DefaultedTruthDict(false)) φ_int = (⊥ ∨ (p1 ∧ p100 ∧ p2 ∧ ⊤)) @@ -303,7 +234,6 @@ emptylogic = @test_nowarn propositionallogic(; operators = SoleLogics.Operator[] @test syntaxstring(diamond(SoleLogics._Topo_TPPi()); use_modal_notation=:superscript) == "◊ᵀ̅ᴾ̅ᴾ̅" @test_broken syntaxstring(diamond(SoleLogics._Topo_TPPi()); use_modal_notation=:subscript) == "◊TODO" - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include("check/propositional.jl") diff --git a/test/formulas/anchored-formula.jl b/test/formulas/anchored-formula.jl new file mode 100644 index 00000000..8c066cea --- /dev/null +++ b/test/formulas/anchored-formula.jl @@ -0,0 +1,71 @@ + + +# @test_nowarn AnchoredFormula(Base.RefValue(logic_int), t1_int) +# @test_nowarn AnchoredFormula(logic_int, p1) +# @test_nowarn AnchoredFormula(logic_int, p1; check_atoms = true) +# @test_nowarn AnchoredFormula(logic_int, p100) +# @test_throws AssertionError AnchoredFormula(logic_int, p100; check_atoms = true) + +# f_int = @test_nowarn AnchoredFormula(logic_int, t1_int) +# @test_throws MethodError 1 in f_int +# @test p1 in f_int +# @test p1 in grammar(f_int) +# @test ! (p1_number in f_int) +# @test ! (p100 in f_int) +# @test ! (Atom("1") in f_int) +# @test_throws ErrorException f_int ∨ ⊤ +# @test_throws ErrorException ⊥ ∨ f_int +# @test_nowarn ¬(f_int) +# @test_nowarn f_int ∨ f_int +# @test_nowarn ¬(f_int) ∨ f_int +# @test_nowarn p1 ∨ f_int +# @test_nowarn f_int ∨ p1 +# @test_nowarn t2_int ∨ f_int +# @test_nowarn f_int ∨ t2_int +# # @test atoms(f_int ∨ (p1 ∨ p100)) == [p1, p1, p100] +# @test unique(atoms(f_int ∨ (p1 ∨ p100))) == [p1, p100] +# @test all(isa.(atoms(f_int ∨ (p1 ∨ p100)), atomstype(logic(f_int)))) +# f_conj_int = @test_throws AssertionError SoleLogics.composeformulas(CONJUNCTION, (f_int, f_int, f_int)) +# f_conj_int = @test_nowarn SoleLogics.composeformulas(CONJUNCTION, (f_int, f_int)) +# f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int, f_int) +# f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int) +# @test_nowarn DISJUNCTION(f_int, f_int, f_conj_int) +# @test_nowarn CONJUNCTION(f_int, f_int, p1) +# @test_nowarn CONJUNCTION(p1, f_int, p1) +# @test_nowarn CONJUNCTION(t2_int, f_int, p1) +# @test_nowarn CONJUNCTION(f_int, t2_int, p1) +# @test typeof(¬(f_int)) == typeof(f_int) +# @test_nowarn ∧((¬(f_int), f_int),) +# # @test promote_type(typeof(f_int), typeof(t2_int)) == typeof(f_int) +# @test_nowarn ∧((¬(f_int), f_int),) +# @test_nowarn ∧((¬(f_int), t2_int),) +# @test_nowarn ∧((t2_int, ¬(f_int)),) +# @test_nowarn f_int(p1 ∧ p100) +# # @test f_int(p1 ∧ p100) isa AnchoredFormula +# @test_throws ErrorException f_int(p1 ∧ p100 ∧ p1_float) +# @test_throws ErrorException f_int(⊥ ∨ (p1 ∧ p100 ∧ p2 ∧ ⊤)) +# anch_φ_int = f_int(p1 ∧ p100 ∧ p2) +# anch2_φ_int = f_int(p1 ∧ p100 → p2) + +# for i in 1:10 +# _tdict = TruthDict(Dict([p => rand([true, false]) for p in unique(atoms(anch_φ_int))])) +# # i == 1 && println(_tdict) +# check(anch_φ_int, _tdict) && @test all(istop, collect(values(_tdict.truth))) +# !check(anch_φ_int, _tdict) && @test !all(istop, collect(values(_tdict.truth))) +# check(anch2_φ_int, _tdict) + +# @test_nowarn _tdict[anch_φ_int] +# @test_nowarn anch_φ_int(_tdict) +# @test anch_φ_int(_tdict) == _tdict[anch_φ_int] +# end + +# tdict = TruthDict(Dict([p => true for p in unique(atoms(anch_φ_int))])) +# @test check(anch_φ_int, tdict) + +# tdict = TruthDict(Dict([p => false for p in unique(atoms(anch_φ_int))])) +# @test !check(anch_φ_int, tdict) + +# @test check(anch_φ_int, DefaultedTruthDict([], true)) +# @test check(anch_φ_int, DefaultedTruthDict(true)) +# @test !check(anch_φ_int, DefaultedTruthDict(false)) + From beccfcd6d53589e5cc572ec55659719702ae1dbc Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:34:14 +0200 Subject: [PATCH 25/90] Minor --- test/formulas/anchored-formula.jl | 33 +++++++++++++++++++++++++++++++ test/parse.jl | 33 ------------------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/test/formulas/anchored-formula.jl b/test/formulas/anchored-formula.jl index 8c066cea..efa5067a 100644 --- a/test/formulas/anchored-formula.jl +++ b/test/formulas/anchored-formula.jl @@ -69,3 +69,36 @@ # @test check(anch_φ_int, DefaultedTruthDict(true)) # @test !check(anch_φ_int, DefaultedTruthDict(false)) + +# # parsebaseformula ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# using SoleLogics: parsebaseformula + +# @test_throws ErrorException parsebaseformula("") +# @test_broken parsebaseformula("⊤") +# @test_broken parsebaseformula("⊤ ∧ ⊤") +# @test_broken parsebaseformula("⊤ ∧ p") +# @test_broken parsebaseformula("⊥ ∧ □¬((p∧¬q)→r)") +# @test_broken parsebaseformula("□¬((p∧¬q)→r) ∧ ⊤") +# @test_broken parsebaseformula("⊤ ∧ (⊥∧¬⊤→⊤)") +# @test_nowarn parsebaseformula("□¬((p∧¬q)→r)") + +# @test_nowarn parsebaseformula("p") +# @test_nowarn ¬parsebaseformula("p") +# @test_nowarn ¬parsebaseformula("p", propositionallogic()) + +# @test_nowarn ¬parseformula("p") +# @test_nowarn ¬parseformula("(s∧z)", propositionallogic()) + +# @test operatorstype( +# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: SoleLogics.BaseModalConnectives +# @test !(operatorstype( +# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: +# SoleLogics.BasePropositionalConnectives) +# @test !(operatorstype(logic( +# parsebaseformula("¬p∧q∧(¬s∧¬z)", modallogic()))) <: +# SoleLogics.BasePropositionalConnectives) +# @test (@test_nowarn operatorstype( +# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)"))) <: SoleLogics.BasePropositionalConnectives) + +# @test alphabet(logi =#c(parsebaseformula("p→q"))) == AlphabetOfAny{String}() diff --git a/test/parse.jl b/test/parse.jl index d7044ad1..9641f85f 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -242,39 +242,6 @@ _f = parseformula("{Gp ∧ ¬{G}q", [CurlyRelationalConnective(globalrel)]) [BoxRelationalConnective(testrel), DiamondRelationalConnective(testrel)] ) -# # parseba#= seformula ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# using SoleLogics: parsebaseformula - -# @test_throws ErrorException parsebaseformula("") -# @test_broken parsebaseformula("⊤") -# @test_broken parsebaseformula("⊤ ∧ ⊤") -# @test_broken parsebaseformula("⊤ ∧ p") -# @test_broken parsebaseformula("⊥ ∧ □¬((p∧¬q)→r)") -# @test_broken parsebaseformula("□¬((p∧¬q)→r) ∧ ⊤") -# @test_broken parsebaseformula("⊤ ∧ (⊥∧¬⊤→⊤)") -# @test_nowarn parsebaseformula("□¬((p∧¬q)→r)") - -# @test_nowarn parsebaseformula("p") - -# @test_nowarn ¬parsebaseformula("p") -# @test_nowarn ¬parseformula("p") -# @test_nowarn ¬parseformula("(s∧z)", propositionallogic()) -# @test_nowarn ¬parsebaseformula("p", propositionallogic()) - -# @test operatorstype( -# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: SoleLogics.BaseModalConnectives -# @test !(operatorstype( -# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: -# SoleLogics.BasePropositionalConnectives) -# @test !(operatorstype(logic( -# parsebaseformula("¬p∧q∧(¬s∧¬z)", modallogic()))) <: -# SoleLogics.BasePropositionalConnectives) -# @test (@test_nowarn operatorstype( -# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)"))) <: SoleLogics.BasePropositionalConnectives) - -# @test alphabet(logi =#c(parsebaseformula("p→q"))) == AlphabetOfAny{String}() - # stress test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ s = "¬((¬(([G](⟨G⟩(¬((¬([G](⟨G⟩(⟨G⟩(q))))) → (¬(⟨G⟩((¬(q)) ∧ ([G](p))))))))) ∧ (⟨G⟩((" * From 58cb5d050b67e042f9d4b1763747e77251296e5d Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:38:57 +0200 Subject: [PATCH 26/90] Fix tests, logic is not callable --- src/old-code/anchored-formula.jl | 8 +- test/core.jl | 2 - test/formulas/anchored-formula.jl | 206 +++++++++++++++--------------- 3 files changed, 109 insertions(+), 107 deletions(-) diff --git a/src/old-code/anchored-formula.jl b/src/old-code/anchored-formula.jl index 06e2ad31..68b3a72b 100644 --- a/src/old-code/anchored-formula.jl +++ b/src/old-code/anchored-formula.jl @@ -142,9 +142,6 @@ end # When constructing a new formula from a syntax tree, the logic is passed by reference. (φ::AnchoredFormula)(t::SyntaxStructure, args...) = AnchoredFormula(_logic(φ), t, args...) -# A logic can be used to instantiate `AnchoredFormula`s out of syntax trees. -(l::AbstractLogic)(t::SyntaxStructure, args...) = AnchoredFormula(Base.RefValue(l), t; args...) - # Adapted from https://github.com/JuliaLang/julia/blob/master/base/promotion.jl function Base._promote(x::AnchoredFormula, y::SyntaxStructure) @inline @@ -158,6 +155,11 @@ algebra(φ::AnchoredFormula) = algebra(logic(φ)) syntaxstring(φ::AnchoredFormula; kwargs...) = syntaxstring(φ.synstruct; kwargs...) +# A logic can be used to instantiate `AnchoredFormula`s out of syntax trees. +function (l::AbstractLogic)(t::SyntaxStructure, args...) + AnchoredFormula(Base.RefValue(l), t; args...) +end + ############################################################################################ subformulas(φ::AnchoredFormula; kwargs...) = φ.(subformulas(tree(φ); kwargs...)) diff --git a/test/core.jl b/test/core.jl index fb9e46c4..96cf0a0b 100644 --- a/test/core.jl +++ b/test/core.jl @@ -217,8 +217,6 @@ emptylogic = @test_nowarn propositionallogic(; operators = SoleLogics.Operator[] @test propositionallogic() isa SoleLogics.BasePropositionalLogic @test propositionallogic(; operators = [¬, ∨]) isa SoleLogics.BasePropositionalLogic -@test_throws ErrorException propositionallogic(; operators = [¬, ∨])(¬ p1) -@test_nowarn propositionallogic(; operators = [¬, ∨])(¬ p_string) @test propositionallogic(; alphabet = ["p", "q"]) isa SoleLogics.BasePropositionalLogic @test modallogic() isa SoleLogics.BaseModalLogic diff --git a/test/formulas/anchored-formula.jl b/test/formulas/anchored-formula.jl index efa5067a..28876bc1 100644 --- a/test/formulas/anchored-formula.jl +++ b/test/formulas/anchored-formula.jl @@ -1,104 +1,106 @@ -# @test_nowarn AnchoredFormula(Base.RefValue(logic_int), t1_int) -# @test_nowarn AnchoredFormula(logic_int, p1) -# @test_nowarn AnchoredFormula(logic_int, p1; check_atoms = true) -# @test_nowarn AnchoredFormula(logic_int, p100) -# @test_throws AssertionError AnchoredFormula(logic_int, p100; check_atoms = true) - -# f_int = @test_nowarn AnchoredFormula(logic_int, t1_int) -# @test_throws MethodError 1 in f_int -# @test p1 in f_int -# @test p1 in grammar(f_int) -# @test ! (p1_number in f_int) -# @test ! (p100 in f_int) -# @test ! (Atom("1") in f_int) -# @test_throws ErrorException f_int ∨ ⊤ -# @test_throws ErrorException ⊥ ∨ f_int -# @test_nowarn ¬(f_int) -# @test_nowarn f_int ∨ f_int -# @test_nowarn ¬(f_int) ∨ f_int -# @test_nowarn p1 ∨ f_int -# @test_nowarn f_int ∨ p1 -# @test_nowarn t2_int ∨ f_int -# @test_nowarn f_int ∨ t2_int -# # @test atoms(f_int ∨ (p1 ∨ p100)) == [p1, p1, p100] -# @test unique(atoms(f_int ∨ (p1 ∨ p100))) == [p1, p100] -# @test all(isa.(atoms(f_int ∨ (p1 ∨ p100)), atomstype(logic(f_int)))) -# f_conj_int = @test_throws AssertionError SoleLogics.composeformulas(CONJUNCTION, (f_int, f_int, f_int)) -# f_conj_int = @test_nowarn SoleLogics.composeformulas(CONJUNCTION, (f_int, f_int)) -# f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int, f_int) -# f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int) -# @test_nowarn DISJUNCTION(f_int, f_int, f_conj_int) -# @test_nowarn CONJUNCTION(f_int, f_int, p1) -# @test_nowarn CONJUNCTION(p1, f_int, p1) -# @test_nowarn CONJUNCTION(t2_int, f_int, p1) -# @test_nowarn CONJUNCTION(f_int, t2_int, p1) -# @test typeof(¬(f_int)) == typeof(f_int) -# @test_nowarn ∧((¬(f_int), f_int),) -# # @test promote_type(typeof(f_int), typeof(t2_int)) == typeof(f_int) -# @test_nowarn ∧((¬(f_int), f_int),) -# @test_nowarn ∧((¬(f_int), t2_int),) -# @test_nowarn ∧((t2_int, ¬(f_int)),) -# @test_nowarn f_int(p1 ∧ p100) -# # @test f_int(p1 ∧ p100) isa AnchoredFormula -# @test_throws ErrorException f_int(p1 ∧ p100 ∧ p1_float) -# @test_throws ErrorException f_int(⊥ ∨ (p1 ∧ p100 ∧ p2 ∧ ⊤)) -# anch_φ_int = f_int(p1 ∧ p100 ∧ p2) -# anch2_φ_int = f_int(p1 ∧ p100 → p2) - -# for i in 1:10 -# _tdict = TruthDict(Dict([p => rand([true, false]) for p in unique(atoms(anch_φ_int))])) -# # i == 1 && println(_tdict) -# check(anch_φ_int, _tdict) && @test all(istop, collect(values(_tdict.truth))) -# !check(anch_φ_int, _tdict) && @test !all(istop, collect(values(_tdict.truth))) -# check(anch2_φ_int, _tdict) - -# @test_nowarn _tdict[anch_φ_int] -# @test_nowarn anch_φ_int(_tdict) -# @test anch_φ_int(_tdict) == _tdict[anch_φ_int] -# end - -# tdict = TruthDict(Dict([p => true for p in unique(atoms(anch_φ_int))])) -# @test check(anch_φ_int, tdict) - -# tdict = TruthDict(Dict([p => false for p in unique(atoms(anch_φ_int))])) -# @test !check(anch_φ_int, tdict) - -# @test check(anch_φ_int, DefaultedTruthDict([], true)) -# @test check(anch_φ_int, DefaultedTruthDict(true)) -# @test !check(anch_φ_int, DefaultedTruthDict(false)) - - -# # parsebaseformula ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# using SoleLogics: parsebaseformula - -# @test_throws ErrorException parsebaseformula("") -# @test_broken parsebaseformula("⊤") -# @test_broken parsebaseformula("⊤ ∧ ⊤") -# @test_broken parsebaseformula("⊤ ∧ p") -# @test_broken parsebaseformula("⊥ ∧ □¬((p∧¬q)→r)") -# @test_broken parsebaseformula("□¬((p∧¬q)→r) ∧ ⊤") -# @test_broken parsebaseformula("⊤ ∧ (⊥∧¬⊤→⊤)") -# @test_nowarn parsebaseformula("□¬((p∧¬q)→r)") - -# @test_nowarn parsebaseformula("p") -# @test_nowarn ¬parsebaseformula("p") -# @test_nowarn ¬parsebaseformula("p", propositionallogic()) - -# @test_nowarn ¬parseformula("p") -# @test_nowarn ¬parseformula("(s∧z)", propositionallogic()) - -# @test operatorstype( -# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: SoleLogics.BaseModalConnectives -# @test !(operatorstype( -# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: -# SoleLogics.BasePropositionalConnectives) -# @test !(operatorstype(logic( -# parsebaseformula("¬p∧q∧(¬s∧¬z)", modallogic()))) <: -# SoleLogics.BasePropositionalConnectives) -# @test (@test_nowarn operatorstype( -# logic(parsebaseformula("¬p∧q∧(¬s∧¬z)"))) <: SoleLogics.BasePropositionalConnectives) - -# @test alphabet(logi =#c(parsebaseformula("p→q"))) == AlphabetOfAny{String}() +@test_nowarn AnchoredFormula(Base.RefValue(logic_int), t1_int) +@test_nowarn AnchoredFormula(logic_int, p1) +@test_nowarn AnchoredFormula(logic_int, p1; check_atoms = true) +@test_nowarn AnchoredFormula(logic_int, p100) +@test_throws AssertionError AnchoredFormula(logic_int, p100; check_atoms = true) + +f_int = @test_nowarn AnchoredFormula(logic_int, t1_int) +@test_throws MethodError 1 in f_int +@test p1 in f_int +@test p1 in grammar(f_int) +@test ! (p1_number in f_int) +@test ! (p100 in f_int) +@test ! (Atom("1") in f_int) +@test_throws ErrorException f_int ∨ ⊤ +@test_throws ErrorException ⊥ ∨ f_int +@test_nowarn ¬(f_int) +@test_nowarn f_int ∨ f_int +@test_nowarn ¬(f_int) ∨ f_int +@test_nowarn p1 ∨ f_int +@test_nowarn f_int ∨ p1 +@test_nowarn t2_int ∨ f_int +@test_nowarn f_int ∨ t2_int +# @test atoms(f_int ∨ (p1 ∨ p100)) == [p1, p1, p100] +@test unique(atoms(f_int ∨ (p1 ∨ p100))) == [p1, p100] +@test all(isa.(atoms(f_int ∨ (p1 ∨ p100)), atomstype(logic(f_int)))) +f_conj_int = @test_throws AssertionError SoleLogics.composeformulas(CONJUNCTION, (f_int, f_int, f_int)) +f_conj_int = @test_nowarn SoleLogics.composeformulas(CONJUNCTION, (f_int, f_int)) +f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int, f_int) +f_conj_int = @test_nowarn CONJUNCTION(f_int, f_int, f_int) +@test_nowarn DISJUNCTION(f_int, f_int, f_conj_int) +@test_nowarn CONJUNCTION(f_int, f_int, p1) +@test_nowarn CONJUNCTION(p1, f_int, p1) +@test_nowarn CONJUNCTION(t2_int, f_int, p1) +@test_nowarn CONJUNCTION(f_int, t2_int, p1) +@test typeof(¬(f_int)) == typeof(f_int) +@test_nowarn ∧((¬(f_int), f_int),) +# @test promote_type(typeof(f_int), typeof(t2_int)) == typeof(f_int) +@test_nowarn ∧((¬(f_int), f_int),) +@test_nowarn ∧((¬(f_int), t2_int),) +@test_nowarn ∧((t2_int, ¬(f_int)),) +@test_nowarn f_int(p1 ∧ p100) +# @test f_int(p1 ∧ p100) isa AnchoredFormula +@test_throws ErrorException f_int(p1 ∧ p100 ∧ p1_float) +@test_throws ErrorException f_int(⊥ ∨ (p1 ∧ p100 ∧ p2 ∧ ⊤)) +anch_φ_int = f_int(p1 ∧ p100 ∧ p2) +anch2_φ_int = f_int(p1 ∧ p100 → p2) + +for i in 1:10 + _tdict = TruthDict(Dict([p => rand([true, false]) for p in unique(atoms(anch_φ_int))])) + # i == 1 && println(_tdict) + check(anch_φ_int, _tdict) && @test all(istop, collect(values(_tdict.truth))) + !check(anch_φ_int, _tdict) && @test !all(istop, collect(values(_tdict.truth))) + check(anch2_φ_int, _tdict) + + @test_nowarn _tdict[anch_φ_int] + @test_nowarn anch_φ_int(_tdict) + @test anch_φ_int(_tdict) == _tdict[anch_φ_int] +end + +tdict = TruthDict(Dict([p => true for p in unique(atoms(anch_φ_int))])) +@test check(anch_φ_int, tdict) + +tdict = TruthDict(Dict([p => false for p in unique(atoms(anch_φ_int))])) +@test !check(anch_φ_int, tdict) + +@test check(anch_φ_int, DefaultedTruthDict([], true)) +@test check(anch_φ_int, DefaultedTruthDict(true)) +@test !check(anch_φ_int, DefaultedTruthDict(false)) + + + +using SoleLogics: parsebaseformula + +@test_throws ErrorException parsebaseformula("") +@test_broken parsebaseformula("⊤") +@test_broken parsebaseformula("⊤ ∧ ⊤") +@test_broken parsebaseformula("⊤ ∧ p") +@test_broken parsebaseformula("⊥ ∧ □¬((p∧¬q)→r)") +@test_broken parsebaseformula("□¬((p∧¬q)→r) ∧ ⊤") +@test_broken parsebaseformula("⊤ ∧ (⊥∧¬⊤→⊤)") +@test_nowarn parsebaseformula("□¬((p∧¬q)→r)") + +@test_nowarn parsebaseformula("p") +@test_nowarn ¬parsebaseformula("p") +@test_nowarn ¬parsebaseformula("p", propositionallogic()) + +@test_nowarn ¬parseformula("p") +@test_nowarn ¬parseformula("(s∧z)", propositionallogic()) + +@test operatorstype( + logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: SoleLogics.BaseModalConnectives +@test !(operatorstype( + logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: + SoleLogics.BasePropositionalConnectives) +@test !(operatorstype(logic( + parsebaseformula("¬p∧q∧(¬s∧¬z)", modallogic()))) <: + SoleLogics.BasePropositionalConnectives) +@test (@test_nowarn operatorstype( + logic(parsebaseformula("¬p∧q∧(¬s∧¬z)"))) <: SoleLogics.BasePropositionalConnectives) + +@test alphabet(logic(parsebaseformula("p→q"))) == AlphabetOfAny{String}() + +@test_throws ErrorException propositionallogic(; operators = [¬, ∨])(¬p1) +@test_nowarn propositionallogic(; operators = [¬, ∨])(¬p_string) From 58cf9ca1286a9478936654251685ce0f8d5b077f Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:53:58 +0200 Subject: [PATCH 27/90] Add tests --- src/utils/interpretation-sets.jl | 4 +++- test/interpretation-sets.jl | 14 ++++++++++++++ test/runtests.jl | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 test/interpretation-sets.jl diff --git a/src/utils/interpretation-sets.jl b/src/utils/interpretation-sets.jl index c43f128a..63ea0bbf 100644 --- a/src/utils/interpretation-sets.jl +++ b/src/utils/interpretation-sets.jl @@ -1,3 +1,4 @@ +import SoleBase: ninstances, getinstance """ struct LogicalInstance{S<:AbstractInterpretationSet} @@ -233,4 +234,5 @@ function interpretationtype(::Type{S}) where {M<:AbstractInterpretation,S<:Inter end Base.getindex(s::InterpretationVector, i_instance::Integer) = Base.getindex(s.instances, i_instance) -getinstance(s::InterpretationVector, i_instance::Integer) = Base.getindex(s, i_instance) +SoleBase.ninstances(s::InterpretationVector) = Base.length(s) +SoleBase.getinstance(s::InterpretationVector, i_instance::Integer) = Base.getindex(s, i_instance) diff --git a/test/interpretation-sets.jl b/test/interpretation-sets.jl new file mode 100644 index 00000000..30b5d3d4 --- /dev/null +++ b/test/interpretation-sets.jl @@ -0,0 +1,14 @@ +using Test +using SoleLogics + +s = SoleLogics.InterpretationVector([TruthDict((1,false)), TruthDict((1,true)), TruthDict((1,true)),]) + +@test_nowarn check(Atom(1), s, 1) +@test check(Atom(1), s, 1) == false +@test check(Atom(1), s, 2) == true + +@test_nowarn check(Atom(1), s) +@test !all(check(Atom(1), s, 1)) + +@test_nowarn check.(Atom(1), eachinstance(s)) +@test check.(Atom(1), eachinstance(s)) == [false, true, true] diff --git a/test/runtests.jl b/test/runtests.jl index c61d4f53..fdfaba4b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -25,6 +25,8 @@ test_suites = [ ("Operators", ["logics/operators.jl"]), # ("Logics", ["logics/logics.jl"]), + ("Interpretation Sets", ["interpretation-sets.jl"]), + ("Algebras: worlds", ["algebras/worlds.jl",]), ("Algebras: frames", ["algebras/frames.jl",]), ("Algebras: relations", ["algebras/relations.jl",]), From 656297a0fa0d2680eab84cca6a73c752481d4ebd Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:57:48 +0200 Subject: [PATCH 28/90] Fix tests --- src/utils/interpretation-sets.jl | 6 +++--- test/interpretation-sets.jl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/interpretation-sets.jl b/src/utils/interpretation-sets.jl index 63ea0bbf..bff155c6 100644 --- a/src/utils/interpretation-sets.jl +++ b/src/utils/interpretation-sets.jl @@ -1,4 +1,4 @@ -import SoleBase: ninstances, getinstance +import SoleBase: ninstances """ struct LogicalInstance{S<:AbstractInterpretationSet} @@ -234,5 +234,5 @@ function interpretationtype(::Type{S}) where {M<:AbstractInterpretation,S<:Inter end Base.getindex(s::InterpretationVector, i_instance::Integer) = Base.getindex(s.instances, i_instance) -SoleBase.ninstances(s::InterpretationVector) = Base.length(s) -SoleBase.getinstance(s::InterpretationVector, i_instance::Integer) = Base.getindex(s, i_instance) +getinstance(s::InterpretationVector, i_instance::Integer) = Base.getindex(s, i_instance) +SoleBase.ninstances(s::InterpretationVector) = Base.length(s.instances) diff --git a/test/interpretation-sets.jl b/test/interpretation-sets.jl index 30b5d3d4..63bd6c90 100644 --- a/test/interpretation-sets.jl +++ b/test/interpretation-sets.jl @@ -10,5 +10,5 @@ s = SoleLogics.InterpretationVector([TruthDict((1,false)), TruthDict((1,true)), @test_nowarn check(Atom(1), s) @test !all(check(Atom(1), s, 1)) -@test_nowarn check.(Atom(1), eachinstance(s)) -@test check.(Atom(1), eachinstance(s)) == [false, true, true] +@test_nowarn [check(Atom(1), i) for i in SoleLogics.eachinstance(s)] +@test [check(Atom(1), i) for i in SoleLogics.eachinstance(s)] == [false, true, true] From 779a6dc0048ca8dd62e59e1f4b6d37c3e362f29b Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:26:17 +0200 Subject: [PATCH 29/90] minor --- pluto-demo.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pluto-demo.jl b/pluto-demo.jl index c9b01b23..1dffdd5e 100644 --- a/pluto-demo.jl +++ b/pluto-demo.jl @@ -93,8 +93,9 @@ end # ╔═╡ 996c3107-ab3f-4fc9-a9f1-4b95a634bfde # Define a new logical operator `⊕` begin - import SoleLogics: arity + import SoleLogics: iscommutative, arity const ⊕ = SoleLogics.NamedConnective{:⊕}() + SoleLogics.iscommutative(::typeof(⊕)) = true SoleLogics.arity(::typeof(⊕)) = 2 # Compose a formula with `⊕` From 2f4ccb3f91fe7a1d4c4e19d2536f478fe642d4d8 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Mon, 14 Oct 2024 23:37:23 +0200 Subject: [PATCH 30/90] @__rng_dispatch and tests working --- src/generation/docstrings.jl | 2 +- src/generation/formula.jl | 82 +++++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index 0f5dd9b9..4e305558 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -22,7 +22,7 @@ See also [`natoms`](@ref), [`AbstractAlphabet`](@ref). randatom_unionalphabet_docstring = """ randatom( - rng::Union{Integer,AbstractRNG}, + [rng::Union{Random.AbstractRNG,Integer},] a::UnionAlphabet; atompicking_mode::Symbol=:uniform, subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing diff --git a/src/generation/formula.jl b/src/generation/formula.jl index b53c7954..893446a3 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -6,50 +6,56 @@ import StatsBase: sample include("docstrings.jl") -macro __rng_dispatch(ex, docstr) +macro __rng_dispatch(ex) if ex.head != :function throw(ArgumentError("Expected a function definition")) end - # Extract the function signature, its name and arguments - func_signature = ex.args[1] - func_name = func_signature.args[1] - args = func_signature.args[2:end] + fsignature = ex.args[1] + fname = fsignature.args[1] + + @show fsignature.args + + fargs = fsignature.args[2:end] + # At this point, fargs is shaped similar to: + # Any[ + # :($(Expr(:parameters, :(kwargs...)))), + # :(rng::Union{Random.AbstractRNG, Integer}), :(a::AbstractAlphabet), :(args...) + # ] # Extract the arguments after the first one; # the first one must be an Union{Random.AbstractRNG,Integer}, # otherwise, this macro makes no sense. - if length(args) > 0 + if length(fargs) > 0 quote - if !isa($args[1], Union{Random.AbstractRNG,Integer}) + if !isa($fargs[1], Union{Random.AbstractRNG,Integer}) throw(ArgumentError("Expected function's first argument to be of type " * "Union{Random.AbstractRNG,Integer}.") ) end end - new_args = args[2:end] + # From fargs, we would like to isolate the first field (kwargs), + # skip the second field (rng), and go on. + newargs = Any[fargs[1], fargs[3:end]...] else throw(ArgumentError("Expected function's argument to be atleast 2, the first of " * "which of type Union{Random.AbstractRNG,Integer}.")) end - # Prepare the new dispatch logic - new_body = quote - $func_name(Random.GLOBAL_RNG, $(new_args...)) - end - - # Define both old and new dispatch (their name is the same); - # for the second dispatch (those with less arguments), also include docstring. + # Define both dispatches; + # the names are the same, and the new dispatches (the one without rng) + # also gets the docstring written just before the macro invocation. quote - @eval @doc $docstr function $func_name($(new_args...)) - $new_body + Core.@__doc__ function $(esc(fname))($(newargs...)) + $fname(Random.GLOBAL_RNG, $(newargs)) end + + $(esc(ex)) end end - - +"""$(randatom_docstring)""" @__rng_dispatch function randatom( rng::Union{Random.AbstractRNG,Integer}, a::AbstractAlphabet, @@ -57,7 +63,7 @@ end kwargs... ) if isfinite(a) - # Commented because otherwise this is getting spammed + # commented because otherwise this is getting spammed # @warn "Consider implementing a specific `randatom` dispatch for your alphabet " * # "type ($(typeof(a))) to increase performances." @@ -67,20 +73,25 @@ end error("Please provide method randatom(rng::$(typeof(rng)), " * "alphabet::$(typeof(a)), args...; kwargs...)") end -end $(randatom_docstring) +end # TODO - remove this dispatch if test works # function randatom(a::AbstractAlphabet, args...; kwargs...) # randatom(Random.GLOBAL_RNG, a, args...; kwargs...) # end +# @__rng_dispatch + +"""$(randatom_unionalphabet_docstring)""" @__rng_dispatch function randatom( rng::Union{Integer,AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol = :uniform, + a::UnionAlphabet, + args...; + atompicking_mode::Symbol=:uniform, subalphabets_weights::Union{ - Nothing,AbstractWeights,AbstractVector{<:Real}} = nothing, -)::Atom + Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing, + kwargs... +) _atompicking_modes = [:uniform, :uniform_subalphabets, :weighted] @assert atompicking_mode in _atompicking_modes "Invalid value for `atompicking_mode` " * "($(atompicking_mode)). Chosee between $(atompicking_modes)." @@ -98,7 +109,7 @@ end $(randatom_docstring) pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) else subalphabets_weights = begin - # This atomatically excludes subalphabets with empty threshold vector + # this atomatically excludes subalphabets with empty threshold vector if atompicking_mode == :uniform_subalphabets # set the weight of the empty alphabets to zero weights = Weights(ones(Int, length(alphs))) @@ -112,7 +123,7 @@ end $(randatom_docstring) end return randatom(rng, pickedalphabet) -end $(randatom_unionalphabet_docstring) +end @@ -120,20 +131,22 @@ end $(randatom_unionalphabet_docstring) # function Base.rand(a::AbstractAlphabet, args...; kwargs...) # Base.rand(Random.GLOBAL_RNG, a, args...; kwargs...) # end +"""$(rand_abstractalphabet_docstring)""" @__rng_dispatch function Base.rand( - rng::AbstractRNG, + rng::Union{Integer,AbstractRNG}, a::AbstractAlphabet, args...; kwargs... ) randatom(initrng(rng), a, args...; kwargs...) -end $(rand_abstractalphabet_docstring) +end # TODO - remove this dispatch if test works # function Base.rand(height::Integer, l::AbstractLogic, args...; kwargs...) # Base.rand(Random.GLOBAL_RNG, height, l, args...; kwargs...) # end +"""$(rand_abstractlogic_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -142,7 +155,7 @@ end $(rand_abstractalphabet_docstring) kwargs... ) Base.rand(initrng(rng), height, grammar(l), args...; kwargs...) -end $(rand_abstractlogic_docstring) +end @@ -155,6 +168,7 @@ end $(rand_abstractlogic_docstring) # ) # Base.rand(Random.GLOBAL_RNG, height, g, args...) # end +"""$(rand_completeflatgrammar_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -163,7 +177,7 @@ end $(rand_abstractlogic_docstring) kwargs... ) randformula(initrng(rng), height, alphabet(g), operators(g), args...; kwargs...) -end $(rand_completeflatgrammar_docstring) +end @@ -179,13 +193,14 @@ end $(rand_completeflatgrammar_docstring) # Base.rand( # Random.GLOBAL_RNG, height, atoms, connectives, truthvalues, args...; kwargs...) # end +"""$(rand_granular_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, height::Integer, atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, - truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, args...; + truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}}=nothing, kwargs... ) rng = initrng(rng) @@ -207,7 +222,8 @@ end $(rand_completeflatgrammar_docstring) end randformula(height, ops, atoms, args...; rng=rng, kwargs...) -end $(rand_granular_docstring) +end + doc_sample = """ function StatsBase.sample( From a35ce6c7e4047bb24de31e5b79b7775d80a1a30e Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 15 Oct 2024 00:14:45 +0200 Subject: [PATCH 31/90] @__rng_dispatch moved to generation/utils.jl file; docstrings minor changes; @assert to throw(ErrorType(msg)) --- src/generation/docstrings.jl | 16 ++-- src/generation/formula.jl | 153 ++++++++++------------------------- src/generation/utils.jl | 50 ++++++++++++ 3 files changed, 98 insertions(+), 121 deletions(-) create mode 100644 src/generation/utils.jl diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index 4e305558..4056cd64 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -6,7 +6,8 @@ randatom_docstring = """ kwargs... ) -Return a random atom from a *finite* alphabet. +Randomly generate an [`Atom`](@ref) from a *finite* [`AbstractAlphabet`](@ref) according to +a uniform distribution. # Examples ```julia-repl @@ -70,17 +71,12 @@ rand_abstractalphabet_docstring = """ kwargs... )::Atom -Randomly generate an [`Atom`](@ref) from an [`AbstractAlphabet`](@ref) according to a -uniform distribution. +Synonym for [`randatom(::AbstractAlphabet)`](@ref). -!!! details -If the `alphabet` is finite, the function defaults to `randatom(rng, alphabet)`, which -internally calls `Base.rand(rng, atoms(alphabet)`; -To limit the (otherwise infinite) sampling domain, a new dispatch must be implemented, -and additional keyword arguments should be provided: those will be forwarded to the innter -`randatom` call. +Randomly generate an [`Atom`](@ref) from a *finite* [`AbstractAlphabet`](@ref) according to +a uniform distribution. -See also [`AbstractAlphabet`], [`randatom`](@ref). +See also [`AbstractAlphabet`], [`randatom(::AbstractAlphabet)`](@ref). """ rand_abstractlogic_docstring = """ diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 893446a3..69604889 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -5,55 +5,7 @@ import Random: rand import StatsBase: sample include("docstrings.jl") - -macro __rng_dispatch(ex) - if ex.head != :function - throw(ArgumentError("Expected a function definition")) - end - - fsignature = ex.args[1] - fname = fsignature.args[1] - - @show fsignature.args - - fargs = fsignature.args[2:end] - # At this point, fargs is shaped similar to: - # Any[ - # :($(Expr(:parameters, :(kwargs...)))), - # :(rng::Union{Random.AbstractRNG, Integer}), :(a::AbstractAlphabet), :(args...) - # ] - - # Extract the arguments after the first one; - # the first one must be an Union{Random.AbstractRNG,Integer}, - # otherwise, this macro makes no sense. - if length(fargs) > 0 - quote - if !isa($fargs[1], Union{Random.AbstractRNG,Integer}) - throw(ArgumentError("Expected function's first argument to be of type " * - "Union{Random.AbstractRNG,Integer}.") - ) - end - end - - # From fargs, we would like to isolate the first field (kwargs), - # skip the second field (rng), and go on. - newargs = Any[fargs[1], fargs[3:end]...] - else - throw(ArgumentError("Expected function's argument to be atleast 2, the first of " * - "which of type Union{Random.AbstractRNG,Integer}.")) - end - - # Define both dispatches; - # the names are the same, and the new dispatches (the one without rng) - # also gets the docstring written just before the macro invocation. - quote - Core.@__doc__ function $(esc(fname))($(newargs...)) - $fname(Random.GLOBAL_RNG, $(newargs)) - end - - $(esc(ex)) - end -end +include("utils.jl") """$(randatom_docstring)""" @__rng_dispatch function randatom( @@ -67,21 +19,13 @@ end # @warn "Consider implementing a specific `randatom` dispatch for your alphabet " * # "type ($(typeof(a))) to increase performances." - rng = initrng(rng) - return Base.rand(rng, atoms(a), args...; kwargs...) + return Base.rand(initrng(rng), atoms(a), args...; kwargs...) else error("Please provide method randatom(rng::$(typeof(rng)), " * "alphabet::$(typeof(a)), args...; kwargs...)") end end -# TODO - remove this dispatch if test works -# function randatom(a::AbstractAlphabet, args...; kwargs...) -# randatom(Random.GLOBAL_RNG, a, args...; kwargs...) -# end - -# @__rng_dispatch - """$(randatom_unionalphabet_docstring)""" @__rng_dispatch function randatom( rng::Union{Integer,AbstractRNG}, @@ -93,19 +37,26 @@ end kwargs... ) _atompicking_modes = [:uniform, :uniform_subalphabets, :weighted] - @assert atompicking_mode in _atompicking_modes "Invalid value for `atompicking_mode` " * - "($(atompicking_mode)). Chosee between $(atompicking_modes)." + if !(atompicking_mode in _atompicking_modes) + throw(ArgumentError("Invalid value for `atompicking_mode` ($(atompicking_mode))." * + "Chosee between $(atompicking_modes).")) + end rng = initrng(rng) alphs = subalphabets(a) if atompicking_mode == :weighted if isnothing(subalphabets_weights) - error("`:weighted` picking_mode requires weights in `subalphabets_weights` ") + throw(ArgumentError("`:weighted` picking_mode requires weights in " * + "`subalphabets_weights` ")) + end + + if length(subalphabets_weights) != length(alphs) + throw(ArgumentError("Mismatching numbers of alphabets " * + "($(length(alphs))) and weights ($(length(subalphabets_weights))).")) end - @assert length(subalphabets_weights)==length(alphs) "Mismatching numbers of "* - "alphabets ($(length(alphs))) and weights ($(length(subalphabets_weights)))." - subalphabets_weights = StatsBase.weights(subalphabets_weights) + + subalphabets_weights = StatsBase.weights(subalphabets_weights) pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) else subalphabets_weights = begin @@ -127,10 +78,6 @@ end -# TODO - remove this dispatch if test works -# function Base.rand(a::AbstractAlphabet, args...; kwargs...) -# Base.rand(Random.GLOBAL_RNG, a, args...; kwargs...) -# end """$(rand_abstractalphabet_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, @@ -142,10 +89,6 @@ end end -# TODO - remove this dispatch if test works -# function Base.rand(height::Integer, l::AbstractLogic, args...; kwargs...) -# Base.rand(Random.GLOBAL_RNG, height, l, args...; kwargs...) -# end """$(rand_abstractlogic_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, @@ -157,17 +100,6 @@ end Base.rand(initrng(rng), height, grammar(l), args...; kwargs...) end - - - -# TODO - remove this dispatch if test works -# function Base.rand( -# height::Integer, -# g::CompleteFlatGrammar, -# args... -# ) -# Base.rand(Random.GLOBAL_RNG, height, g, args...) -# end """$(rand_completeflatgrammar_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, @@ -179,20 +111,6 @@ end randformula(initrng(rng), height, alphabet(g), operators(g), args...; kwargs...) end - - -# TODO - remove this dispatch if test works -# function Base.rand( -# height::Integer, -# atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, -# connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, -# truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, -# args...; -# kwargs... -# ) -# Base.rand( -# Random.GLOBAL_RNG, height, atoms, connectives, truthvalues, args...; kwargs...) -# end """$(rand_granular_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, @@ -203,28 +121,33 @@ end truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}}=nothing, kwargs... ) - rng = initrng(rng) - # If Truth's are specified as `operators`, then they cannot be simultaneously # provided as `truthvalues` - @assert (connectives isa AbstractVector{<:Connective} || + if !(connectives isa AbstractVector{<:Connective} || !(truthvalues isa AbstractVector{<:Truth}) - ) "Unexpected connectives and truth values: $(connectives) and $(truthvalues)." + ) + thorw(ArgumentError("Unexpected connectives and truth values: " * + "$(connectives) and $(truthvalues).")) + end atoms = atoms isa AbstractAlphabet ? SoleLogics.atoms(atoms) : atoms ops = connectives if !isnothing(truthvalues) truthvalues = inittruthvalues(truthvalues) - @assert typejoin(typeof.(truthvalues)...) != Truth "Truth values " * - "$(truthvalues) must belong to the same algebra " * - "(and have a common supertype that is not Truth)." + if typejoin(typeof.(truthvalues)...) == Truth + throw(ArgumentError("Truth values " * + "$(truthvalues) must belong to the same algebra " * + "(and have a common supertype that is not Truth).")) + end + ops = vcat(ops, truthvalues) end - randformula(height, ops, atoms, args...; rng=rng, kwargs...) + randformula(height, ops, atoms, args...; rng=initrng(rng), kwargs...) end + doc_sample = """ function StatsBase.sample( [rng::AbstractRNG = Random.GLOBAL_RNG,] @@ -393,22 +316,30 @@ function randformula( rng = initrng(rng) alphabet = convert(AbstractAlphabet, alphabet) - @assert all(x->x isa Operator, operators) "Unexpected object(s) passed as" * - " operator:" * " $(filter(x->!(x isa Operator), operators))" + if !(all(x->x isa Operator, operators)) + throw(ArgumentError("Unexpected object(s) passed as" * + " operator:" * " $(filter(x->!(x isa Operator), operators))")) + end if (isnothing(opweights)) opweights = StatsBase.uweights(length(operators)) elseif (opweights isa AbstractVector) - @assert length(opweights) == length(operators) "Mismatching numbers of operators " * - "($(length(operators))) and opweights ($(length(opweights)))." + if !(length(opweights) == length(operators)) + throw(ArgumentError("Mismatching numbers of operators " * + "($(length(operators))) and opweights ($(length(opweights))).")) + end + opweights = StatsBase.weights(opweights) end if (isnothing(atompicker)) atompicker = StatsBase.uweights(natoms(alphabet)) elseif (atompicker isa AbstractVector) - @assert length(atompicker) == natoms(alphabet) "Mismatching numbers of atoms " * - "($(natoms(alphabet))) and atompicker ($(length(atompicker)))." + if !(length(atompicker) == natoms(alphabet)) + throw(ArgumentError("Mismatching numbers of atoms " * + "($(natoms(alphabet))) and atompicker ($(length(atompicker))).")) + end + atompicker = StatsBase.weights(atompicker) end diff --git a/src/generation/utils.jl b/src/generation/utils.jl new file mode 100644 index 00000000..5aa80d59 --- /dev/null +++ b/src/generation/utils.jl @@ -0,0 +1,50 @@ +# Given a function whose first argument is of type Union{Random.AbstractRNG,Integer}, +# write an identical dispatch defaulting that field to Random.GLOBAL_RNG. +# +# This is useful to adhere to Base.rand methods, where the RNG is not a kwarg and it's +# placed as the first argument. +macro __rng_dispatch(ex) + if ex.head != :function + throw(ArgumentError("Expected a function definition")) + end + + fsignature = ex.args[1] + fname = fsignature.args[1] + + fargs = fsignature.args[2:end] + # At this point, fargs is shaped similar to: + # Any[ + # :($(Expr(:parameters, :(kwargs...)))), + # :(rng::Union{Random.AbstractRNG, Integer}), :(a::AbstractAlphabet), :(args...) + # ] + + # Extract the arguments after the first one; + # the first one must be an Union{Random.AbstractRNG,Integer}, + # otherwise, this macro makes no sense. + if length(fargs) > 0 + quote + if !isa($fargs[1], Union{Random.AbstractRNG,Integer}) + throw(ArgumentError("Expected function's first argument to be of type " * + "Union{Random.AbstractRNG,Integer}.")) + end + end + + # From fargs, we would like to isolate the first field (kwargs), + # skip the second field (rng), and go on. + newargs = Any[fargs[1], fargs[3:end]...] + else + throw(ArgumentError("Expected function's argument to be atleast 2, the first of " * + "which of type Union{Random.AbstractRNG,Integer}.")) + end + + # Define both dispatches; + # the names are the same, and the new dispatches (the one without rng) + # also gets the docstring written just before the macro invocation. + quote + Core.@__doc__ function $(esc(fname))($(newargs...)) + $fname(Random.GLOBAL_RNG, $(newargs)) + end + + $(esc(ex)) + end +end From 8791a27088c69653491121eb71c34cc45930de7b Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 15 Oct 2024 00:15:26 +0200 Subject: [PATCH 32/90] in test/random.jl, a check about AssertionError is now ArgumentError (in the previous commit, @assert where removed in favour of throw(ErrorType(msg))) --- test/random.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/random.jl b/test/random.jl index 685e63b6..b4f3e1d1 100644 --- a/test/random.jl +++ b/test/random.jl @@ -76,7 +76,7 @@ g = SoleLogics.CompleteFlatGrammar(alph, ops) @test_nowarn randformula(Random.MersenneTwister(1), 4, g) @test_nowarn randformula(4, alph, ops) @test_nowarn randformula(Random.MersenneTwister(1), 4, alph, ops; atompicker = 1:5) -@test_throws AssertionError randformula(Random.MersenneTwister(1), 4, alph, ops; atompicker = 1:6) +@test_throws ArgumentError randformula(Random.MersenneTwister(1), 4, alph, ops; atompicker = 1:6) @test_nowarn StatsBase.sample(4, g, Weights([1:natoms(alphabet(g))]...)) @test_nowarn StatsBase.sample(4, g, Weights([1,1,1,1,100])) From 13789b2b4dcc7b47b977dea0830380a6e6f42432 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 15 Oct 2024 12:19:41 +0200 Subject: [PATCH 33/90] @__rng_dispatch fix (it returned an Expr instead of defining a new dispatch); sample dispatches and docstrings tied up --- src/generation/docstrings.jl | 50 +++++++++++++++++++ src/generation/formula.jl | 94 ++++++------------------------------ src/generation/utils.jl | 50 ++++++++++--------- 3 files changed, 92 insertions(+), 102 deletions(-) diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index 4056cd64..edbeb315 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -121,3 +121,53 @@ rand_granular_docstring = """ See also [`AbstractAlphabet`](@ref), [`AbstractAlgebra`](@ref), [`Connective`](@ref), [`Operator`](@ref). """ + +sample_aw_docstring = """ + function StatsBase.sample( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + alphabet::AbstractAlphabet, + weights::AbstractWeights, + args...; + kwargs... + ) + +Sample an [`Atom`](@ref) from an `alphabet`, with probabilities proportional to the weights +given in `weights`. + +See also [`AbstractAlphabet`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref). +""" + +sample_lao_docstring = """ + function StatsBase.sample( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + l::AbstractLogic, + weights::AbstractWeights, + args...; + kwargs... + ) + +Sample from the [`grammar`](@ref) of logic `l`, with probabilities proportional to the +weights given in `weights`. + +See also [`AbstractLogic`](@ref), [`AbstractWeights`](@ref), +[`grammar(::AbstractLogic{G}) where {G}`](@ref). +""" + +sample_hgao_docstring = """ + function StatsBase.sample( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + height::Integer, + g::AbstractGrammar, + atomweights::Union{Nothing,AbstractWeights} = nothing, + opweights::Union{Nothing,AbstractWeights} = nothing, + args...; + kwargs... + ) + +Sample a formula from grammar `g`. +[`Atom`](@ref)s and [`Operator`](@ref)s sampling probabilities are proportional +respectively to `atomweights` and `opweights`. + +See also [`AbstractGrammar`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref), +[`Operator`](@ref). +""" diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 69604889..a078879a 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -1,7 +1,7 @@ using Random using StatsBase -import Random: rand +import Base: rand import StatsBase: sample include("docstrings.jl") @@ -148,75 +148,25 @@ end -doc_sample = """ - function StatsBase.sample( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - alphabet::AbstractAlphabet, - weights::AbstractWeights, - args...; - kwargs... - ) - - function StatsBase.sample( - rng::AbstractRNG, - l::AbstractLogic, - weights::AbstractWeights, - args...; - kwargs... - ) - - StatsBase.sample( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - height::Integer, - g::AbstractGrammar, - [opweights::Union{Nothing,AbstractWeights} = nothing,] - args...; - kwargs... - )::Formula - -Randomly sample an [`Atom`](@ref) from an `alphabet`, or a logic formula of given `height` -from a grammar `g`. -Sampling is weighted, thus, for example, if the first weight in `weights` is higher than -the others, then the first atom in the alphabet is selected more frequently. - -See also [`AbstractAlphabet`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref). -""" -function StatsBase.sample( - alphabet::AbstractAlphabet, - weights::AbstractWeights, - args...; - kwargs... -)::Atom - StatsBase.sample(Random.GLOBAL_RNG, alphabet, weights, args...; kwargs...) -end - -function StatsBase.sample( - rng::AbstractRNG, +"""$(sample_aw_docstring)""" +@__rng_dispatch function StatsBase.sample( + rng::Union{Integer,AbstractRNG}, alphabet::AbstractAlphabet, weights::AbstractWeights, args...; kwargs... -)::Atom +) if isfinite(alphabet) - StatsBase.sample(rng, atoms(alphabet), weights, args...; kwargs...) + StatsBase.sample(initrng(rng), atoms(alphabet), weights, args...; kwargs...) else error("Please, provide method StatsBase.sample(rng::AbstractRNG, " * "alphabet::$(typeof(alphabet)), args...; kwargs...).") end end -function StatsBase.sample( - l::AbstractLogic, - atomweights::AbstractWeights, - opweights::AbstractWeights, - args...; - kwargs... -) - StatsBase.sample(Random.GLOBAL_RNG, l, atomweights, opweights, args...; kwargs...) -end - -function StatsBase.sample( - rng::AbstractRNG, +"""$(sample_lao_docstring)""" +@__rng_dispatch function StatsBase.sample( + rng::Union{Integer,AbstractRNG}, l::AbstractLogic, atomweights::AbstractWeights, opweights::AbstractWeights, @@ -226,32 +176,18 @@ function StatsBase.sample( StatsBase.sample(init(rng), grammar(l), atomweights, opweights, args...; kwargs...) end -"""$(doc_sample)""" -function StatsBase.sample( - height::Integer, - g::AbstractGrammar, - atomweights::Union{Nothing,AbstractWeights} = nothing, - opweights::Union{Nothing,AbstractWeights} = nothing, - args...; - kwargs... -) - StatsBase.sample(Random.GLOBAL_RNG, height, g, atomweights, opweights, args...; - kwargs...) -end - -"""$(doc_sample)""" -function StatsBase.sample( - rng::AbstractRNG, +"""$(sample_hgao_docstring)""" +@__rng_dispatch function StatsBase.sample( + rng::Union{Integer,AbstractRNG}, height::Integer, g::AbstractGrammar, - atomweights::Union{Nothing,AbstractWeights} = nothing, - opweights::Union{Nothing,AbstractWeights} = nothing, args...; + atomweights::Union{Nothing,AbstractWeights}=nothing, + opweights::Union{Nothing,AbstractWeights}=nothing, kwargs... ) randformula( - rng, height, alphabet(g), operators(g), args...; - # atompicker=(rng,dom)->StatsBase.sample(rng, dom, atomweights), kwargs...) + initrng(rng), height, alphabet(g), operators(g), args...; atompicker = atomweights, opweights = opweights, kwargs...) end diff --git a/src/generation/utils.jl b/src/generation/utils.jl index 5aa80d59..e899d4a2 100644 --- a/src/generation/utils.jl +++ b/src/generation/utils.jl @@ -3,6 +3,8 @@ # # This is useful to adhere to Base.rand methods, where the RNG is not a kwarg and it's # placed as the first argument. +# +# At the moment, this macro does not handle functions giving a return type hint. macro __rng_dispatch(ex) if ex.head != :function throw(ArgumentError("Expected a function definition")) @@ -10,39 +12,41 @@ macro __rng_dispatch(ex) fsignature = ex.args[1] fname = fsignature.args[1] - fargs = fsignature.args[2:end] - # At this point, fargs is shaped similar to: - # Any[ - # :($(Expr(:parameters, :(kwargs...)))), - # :(rng::Union{Random.AbstractRNG, Integer}), :(a::AbstractAlphabet), :(args...) - # ] - # Extract the arguments after the first one; - # the first one must be an Union{Random.AbstractRNG,Integer}, - # otherwise, this macro makes no sense. - if length(fargs) > 0 - quote - if !isa($fargs[1], Union{Random.AbstractRNG,Integer}) - throw(ArgumentError("Expected function's first argument to be of type " * - "Union{Random.AbstractRNG,Integer}.")) - end - end - - # From fargs, we would like to isolate the first field (kwargs), - # skip the second field (rng), and go on. - newargs = Any[fargs[1], fargs[3:end]...] - else + # Later, fargs is sliced from 3rd index to end + if length(fargs) <= 2 throw(ArgumentError("Expected function's argument to be atleast 2, the first of " * "which of type Union{Random.AbstractRNG,Integer}.")) end + # The first argument one must be an Union{Random.AbstractRNG,Integer}, + # otherwise, this macro makes no sense. + quote + if !isa($fargs[2], Union{Random.AbstractRNG,Integer}) + throw(ArgumentError("Expected function's first argument to be of type " * + "Union{Random.AbstractRNG,Integer}.")) + end + end + + # At this point, fargs is shaped similar to: + # + # Any[ + # :($(Expr(:parameters, :(kwargs...)))), + # :(rng::Union{Random.AbstractRNG, Integer}), + # :(a::AbstractAlphabet), :(args...) + # ] + # + # We would like to remove both kwargs... and `rng`. + # kwargs are reinserted manually later (just writing $(newargs...) gives problems). + newargs = Any[fargs[3:end]...] + # Define both dispatches; # the names are the same, and the new dispatches (the one without rng) # also gets the docstring written just before the macro invocation. quote - Core.@__doc__ function $(esc(fname))($(newargs...)) - $fname(Random.GLOBAL_RNG, $(newargs)) + Core.@__doc__ function $(esc(fname))($(newargs...); kwargs...) + $(esc(fname))(Random.GLOBAL_RNG, $(newargs...); kwargs...) end $(esc(ex)) From 437de9531295b529cc3f5ef9357b2de8f781af3d Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 15 Oct 2024 12:38:01 +0200 Subject: [PATCH 34/90] randformula docstring and dispatches tidied upd. Minor changes: } = val -> }=val in kwargs, one-line comments now does start with lower case --- src/generation/docstrings.jl | 77 ++++++++++++++++++++++++++++++------ src/generation/formula.jl | 71 ++++++++------------------------- 2 files changed, 83 insertions(+), 65 deletions(-) diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index edbeb315..36ca9ebf 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -26,7 +26,7 @@ randatom_unionalphabet_docstring = """ [rng::Union{Random.AbstractRNG,Integer},] a::UnionAlphabet; atompicking_mode::Symbol=:uniform, - subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing + subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing}=nothing )::Atom Sample an atom from a `UnionAlphabet`. @@ -65,7 +65,7 @@ See also [`UnionAlphabet`](@ref). rand_abstractalphabet_docstring = """ Base.rand( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] alphabet::AbstractAlphabet, args...; kwargs... @@ -81,7 +81,7 @@ See also [`AbstractAlphabet`], [`randatom(::AbstractAlphabet)`](@ref). rand_abstractlogic_docstring = """ function Base.rand( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] height::Integer, l::AbstractLogic, args...; @@ -95,7 +95,7 @@ See also [`AbstractLogic`](@ref). rand_completeflatgrammar_docstring = """ Base.rand( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] height::Integer, g::CompleteFlatGrammar, args... @@ -108,11 +108,11 @@ See also [`CompleteFlatGrammar`](@ref). rand_granular_docstring = """ Base.rand( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] height::Integer, connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, - truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, + truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}}=nothing, args...; rng::AbstractRNG = Random.GLOBAL_RNG, kwargs... @@ -124,7 +124,7 @@ See also [`AbstractAlphabet`](@ref), [`AbstractAlgebra`](@ref), [`Connective`](@ sample_aw_docstring = """ function StatsBase.sample( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] alphabet::AbstractAlphabet, weights::AbstractWeights, args...; @@ -139,7 +139,7 @@ See also [`AbstractAlphabet`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref). sample_lao_docstring = """ function StatsBase.sample( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] l::AbstractLogic, weights::AbstractWeights, args...; @@ -155,11 +155,11 @@ See also [`AbstractLogic`](@ref), [`AbstractWeights`](@ref), sample_hgao_docstring = """ function StatsBase.sample( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] height::Integer, g::AbstractGrammar, - atomweights::Union{Nothing,AbstractWeights} = nothing, - opweights::Union{Nothing,AbstractWeights} = nothing, + atomweights::Union{Nothing,AbstractWeights}=nothing, + opweights::Union{Nothing,AbstractWeights}=nothing, args...; kwargs... ) @@ -171,3 +171,58 @@ respectively to `atomweights` and `opweights`. See also [`AbstractGrammar`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref), [`Operator`](@ref). """ + +randformula_docstring = """ + function randformula( + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + height::Integer, + alphabet::Union{AbstractVector,AbstractAlphabet}, + operators::AbstractVector, + args...; + modaldepth::Integer=height, + atompicker::Union{Function,AbstractWeights,AbstractVector{<:Real},Nothing}=randatom, + opweights::Union{AbstractWeights,AbstractVector{<:Real},Nothing}=nothing, + kwargs... + ) + +Return a pseudo-randomic [`SyntaxTree`](@ref). + +# Arguments +- `rng::Union{Intger,AbstractRNG}=Random.GLOBAL_RNG`: random number generator; +- `height::Integer`: height of the generated structure; +- `alphabet::AbstractAlphabet`: collection from which atoms are chosen randomly; +- `operators::AbstractVector`: vector from which legal operators are chosen. + +# Keyword Arguments +- `modaldepth::Integer`: maximum modal depth; +- `atompicker::Function`: method used to pick a random element. For example, this could be + Base.rand or StatsBase.sample; +- `opweights::AbstractWeights`: operators are sampled with probabilities proportional to + this vector vector (see [`AbstractWeights`](@ref) of StatsBase package). + +# Examples + +```julia-repl +julia> syntaxstring(randformula(4, ExplicitAlphabet([1,2]), [NEGATION, CONJUNCTION, IMPLICATION])) +"¬((¬(¬(2))) → ((1 → 2) → (1 → 2)))" +``` + +See also [`AbstractAlphabet`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref), +[`Operator`](@ref), [`SyntaxBranch`](@ref), [`SyntaxTree`](@ref). +""" + +randformula_hg_docstring = """ + function randformula( + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + height::Integer, + [g::AbstractGrammar,] + args...; + kwargs... + ) + +Fallback to `randformula`, specifying only the `height` (possibly also a `grammar`) of the +generated [`SyntaxTree`](@ref). + +See also [`AbstractGrammar`](@ref), +[`randformula(::Integer, ::Union{AbstractVector,AbstractAlphabet}, ::AbstractVector)`](@ref). +""" diff --git a/src/generation/formula.jl b/src/generation/formula.jl index a078879a..4043439d 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -198,58 +198,20 @@ end # - in randformula, keyword argument alphabet_sample_kwargs that are unpacked upon sampling atoms, as in: Base.rand(rng, a; alphabet_sample_kwargs...). This would allow to sample from infinite alphabets, so when this parameter, !isfinite(alphabet) is allowed! # TODO @Mauro implement this method. -doc_randformula = """ - randformula( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, ] - height::Integer, - alphabet, - operators::AbstractVector; - kwargs... - )::SyntaxTree - - randformula( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, ] - height::Integer, - g::AbstractGrammar; - kwargs... - )::SyntaxTree - -Return a pseudo-randomic `SyntaxTree`. -# Arguments -- `rng::Union{Intger,AbstractRNG} = Random.GLOBAL_RNG`: random number generator; -- `height::Integer`: height of the generated structure; -- `alphabet::AbstractAlphabet`: collection from which atoms are chosen randomly; -- `operators::AbstractVector`: vector from which legal operators are chosen; -- `g::AbstractGrammar`: alternative to passing alphabet and operators separately. (TODO explain?) -# Keyword Arguments -- `modaldepth::Integer`: maximum modal depth -- `atompicker::Function`: method used to pick a random element. For example, this could be - Base.rand or StatsBase.sample. -- `opweights::AbstractWeights`: weight vector over the set of operators (see `StatsBase`). - -# Examples - -```julia-repl -julia> syntaxstring(randformula(4, ExplicitAlphabet([1,2]), [NEGATION, CONJUNCTION, IMPLICATION])) -"¬((¬(¬(2))) → ((1 → 2) → (1 → 2)))" -``` - -See also [`AbstractAlphabet`](@ref), [`SyntaxBranch`](@ref). -""" - -"""$(doc_randformula)""" -function randformula( +"""$(randformula_docstring)""" +@__rng_dispatch function randformula( rng::Union{Integer,AbstractRNG}, height::Integer, alphabet::Union{AbstractVector,AbstractAlphabet}, - operators::AbstractVector; - modaldepth::Integer = height, - atompicker::Union{Function,AbstractWeights,AbstractVector{<:Real},Nothing} = randatom, - opweights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing -)::SyntaxTree - + operators::AbstractVector, + args...; + modaldepth::Integer=height, + atompicker::Union{Function,AbstractWeights,AbstractVector{<:Real},Nothing}=randatom, + opweights::Union{AbstractWeights,AbstractVector{<:Real},Nothing}=nothing, + kwargs... +) rng = initrng(rng) alphabet = convert(AbstractAlphabet, alphabet) if !(all(x->x isa Operator, operators)) @@ -296,7 +258,8 @@ function randformula( if height == 0 return atompicker(rng, alphabet) else - # Sample operator and generate children (modal connectives only if modaldepth > 0) + # sample operator and generate children + # (modal connectives only if modaldepth is set > 0) ops, ops_w = begin if modaldepth > 0 operators, opweights @@ -314,7 +277,7 @@ function randformula( end end - # If the alphabet is not iterable, this function should not work. + # if the alphabet is not iterable, this function should not work. if !isfinite(alphabet) @warn "Attempting to generate random formulas from " * "(infinite) alphabet of type $(typeof(alphabet))!" @@ -323,8 +286,9 @@ function randformula( return _randformula(rng, height, modaldepth) end -function randformula( - rng::AbstractRNG, +"""$(randformula_hg_docstring)""" +@__rng_dispatch function randformula( + rng::Union{Integer,AbstractRNG}, height::Integer, g::AbstractGrammar, args...; @@ -333,11 +297,10 @@ function randformula( randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) end -# Helper -function randformula( +@__rng_dispatch function randformula( + rng::Union{Integer,AbstractRNG}, height::Integer, args...; - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, kwargs... ) randformula(initrng(rng), height, args...; kwargs...) From cf2783af001b31e1f7fe81eeb9bf0f6cb36b6c50 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:29:44 +0200 Subject: [PATCH 35/90] Remove parsebaseformula and randombaseformula --- docs/TODO.md | 4 ++-- docs/src/more-on-formulas.md | 2 +- src/SoleLogics.jl | 12 ++++++----- src/old-code/anchored-formula.jl | 16 +++++++-------- src/types/parse.jl | 18 ++++++++++++++++ src/{ => utils}/parse.jl | 21 +------------------ test/check/propositional.jl | 16 +++++++-------- test/formulas/anchored-formula.jl | 34 +++++++++++++++---------------- test/random.jl | 2 +- 9 files changed, 63 insertions(+), 62 deletions(-) create mode 100644 src/types/parse.jl rename src/{ => utils}/parse.jl (96%) diff --git a/docs/TODO.md b/docs/TODO.md index 30e3b0fe..dcdcd53a 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -13,8 +13,8 @@ (C) Improve this general description about SoleLogics.jl, in order to make the first page in SoleLogics documentation # # Usage # ## Parsing Formulas -# Consider the string representation of a logical formula (in infix notation, e.g., "p∧q" or in function notation, e.g. "∧(p,q)". Such expressions can easily be parsed to a `SyntaxTree` representation using the method `parsebaseformula`. As you can see from the documentation, it is highly customizable, allowing parsing custom-defined operators and changing how recognized atoms must be interpreted (e.g. in "true∧false" atoms are booleans, while in "1∧0" they are integers). -# The `SyntaxBranch` returned by `parsebaseformula` can be paired with a logic (a grammar and an algebra) using `parsebaseformula`, thus returning a AnchoredFormula: the latter method disposes of the same flexibility of `parseformula` TODO not true. +# Consider the string representation of a logical formula (in infix notation, e.g., "p∧q" or in function notation, e.g. "∧(p,q)". Such expressions can easily be parsed to a `SyntaxTree` representation using the method `parseformula`. As you can see from the documentation, it is highly customizable, allowing parsing custom-defined operators and changing how recognized atoms must be interpreted (e.g. in "true∧false" atoms are booleans, while in "1∧0" they are integers). +# The `SyntaxBranch` returned by `parseformula` can be paired with a logic (a grammar and an algebra) using `parsebaseformula`, thus returning a AnchoredFormula: the latter method disposes of the same flexibility of `parseformula` TODO not true. # ## Generating random formulas # Random formulas generation is provided by the following methods: # - randformula (which returns a SyntaxBranch); diff --git a/docs/src/more-on-formulas.md b/docs/src/more-on-formulas.md index 3d0bfc3c..4826acb9 100644 --- a/docs/src/more-on-formulas.md +++ b/docs/src/more-on-formulas.md @@ -45,7 +45,7 @@ synstruct(φ::AnchoredFormula) baseformula(φ::Formula; infer_logic = true, additional_operators::Union{Nothing,Vector{<:Operator}} = nothing, kwargs...) -parsebaseformula(expr::String, additional_operators::Union{Nothing,Vector{<:Operator}} = nothing; operators::Union{Nothing,Vector{<:Operator}}, grammar::Union{Nothing,AbstractGrammar} = nothing, algebra::Union{Nothing,AbstractAlgebra} = nothing, kwargs...) +parseformula(::Type{AnchoredFormula}, expr::String, additional_operators::Union{Nothing,Vector{<:Operator}} = nothing; operators::Union{Nothing,Vector{<:Operator}}, grammar::Union{Nothing,AbstractGrammar} = nothing, algebra::Union{Nothing,AbstractAlgebra} = nothing, kwargs...) ``` --> ## Random sampling and generation diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 1fb7754d..a8967a02 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -40,6 +40,12 @@ include("types/syntactical.jl") ############################################################################################ +export parseformula + +include("types/parse.jl") + +############################################################################################ + export interpret, check include("types/interpretation.jl") @@ -126,11 +132,7 @@ include("types/interpretation-sets.jl") include("utils/interpretation-sets.jl") -############################################################################################ - -export parseformula - -include("parse.jl") +include("utils/parse.jl") ############################################################################################ diff --git a/src/old-code/anchored-formula.jl b/src/old-code/anchored-formula.jl index 68b3a72b..0bc22f82 100644 --- a/src/old-code/anchored-formula.jl +++ b/src/old-code/anchored-formula.jl @@ -23,7 +23,7 @@ See the examples. # Examples ```julia-repl -julia> φ = parsebaseformula("◊(p→q)"); +julia> φ = parseformula(AnchoredFormula, "◊(p→q)"); julia> f2 = φ(parseformula("p")); @@ -242,7 +242,8 @@ end ############################################################################################ """ - parsebaseformula( + parseformula( + ::Type{AnchoredFormula}, expr::String, additional_operators::Union{Nothing,Vector{<:Operator}} = nothing; operators::Union{Nothing,Vector{<:Operator}}, @@ -263,8 +264,6 @@ in the expression, and those in `additional_operators`. See [`parseformula`](@ref), [`baseformula`](@ref), [`BASE_PARSABLE_CONNECTIVES`](@ref). """ -parsebaseformula(expr::String, args...; kwargs...) = parseformula(AnchoredFormula, expr, args...; kwargs...) - function parseformula( ::Type{AnchoredFormula}, expr::String, @@ -299,7 +298,6 @@ function parseformula( AnchoredFormula(logic, parseformula(SyntaxTree, expr, operators(logic); kwargs...)) end -"""$(doc_randformula)""" function randformula( height::Integer, g::AbstractGrammar; @@ -314,7 +312,8 @@ function randformula( ) end -function randbaseformula( +function randformula( + ::Type{AnchoredFormula}, height::Integer, alphabet, operators::AbstractVector{<:Operator}; @@ -328,12 +327,13 @@ function randbaseformula( ) end -function randbaseformula( +function randformula( + T::Type{AnchoredFormula}, height::Integer, g::AbstractGrammar, args...; rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, kwargs... )::AnchoredFormula - randbaseformula(height, alphabet(g), operators(g), args...; rng=rng, kwargs...) + randformula(T, height, alphabet(g), operators(g), args...; rng=rng, kwargs...) end diff --git a/src/types/parse.jl b/src/types/parse.jl new file mode 100644 index 00000000..11f7a767 --- /dev/null +++ b/src/types/parse.jl @@ -0,0 +1,18 @@ +import Base: parse + +function Base.parse( + F::Type{<:Formula}, + expr::String, + args...; + kwargs... +) + return parseformula(F, expr, args...; kwargs...) +end + + +function parseformula(F::Type{<:Formula}, expr::String, args...; kwargs...) + return error("Please, provide method parseformula(::Type{$(F)}, expr::String, ::$(typeof(args))...; ::$(typeof(kwargs))...).") +end + +parseformula(expr::String, args...; kwargs...) = parseformula(SyntaxTree, expr, args...; kwargs...) + diff --git a/src/parse.jl b/src/utils/parse.jl similarity index 96% rename from src/parse.jl rename to src/utils/parse.jl index 49840550..390aba2a 100644 --- a/src/parse.jl +++ b/src/utils/parse.jl @@ -1,13 +1,3 @@ -import Base: parse - -function Base.parse( - F::Type{<:Formula}, - expr::String, - args...; - kwargs... -) - return parseformula(F, expr, args...; kwargs...) -end const STACK_TOKEN_TYPE = Union{SyntaxToken,Symbol} @@ -116,21 +106,14 @@ julia> syntaxstring(parseformula("¬1→0"; atom_parser = (x -> Atom{Float64}(pa See also [`SyntaxTree`](@ref), [`BASE_PARSABLE_CONNECTIVES`](@ref), [`syntaxstring`](@ref). """ -"""$(doc_parseformula)""" -function parseformula(F::Type{<:Formula}, expr::String, args...; kwargs...) - return error("Please, provide method parseformula(::Type{$(F)}, expr::String, ::$(typeof(args))...; ::$(typeof(kwargs))...).") -end -parseformula(expr::String, args...; kwargs...) = parseformula(SyntaxTree, expr, args...; kwargs...) + # This is just an utility function used later function strip_whitespaces(expr::String; additional_whitespaces::Vector{Char} = Char[]) return strip(x -> isspace(x) || x in additional_whitespaces, expr) end -############################################################################################ -#### parseformula ########################################################################## -############################################################################################ """$(doc_parseformula)""" function parseformula( @@ -530,7 +513,6 @@ function parseformula( return (function_notation ? _fxbuild() : _infixbuild()) end -"""$(doc_parseformula)""" function parseformula( F::Type{<:SyntaxTree}, expr::String, @@ -540,7 +522,6 @@ function parseformula( parseformula(F, expr, operators(g); kwargs...) end -"""$(doc_parseformula)""" function parseformula( F::Type{<:SyntaxTree}, expr::String, diff --git a/test/check/propositional.jl b/test/check/propositional.jl index 6157c188..858718f9 100644 --- a/test/check/propositional.jl +++ b/test/check/propositional.jl @@ -1,5 +1,5 @@ # using Revise; using SoleLogics; using Test -# using SoleLogics: parsebaseformula +# using SoleLogics: AnchoredFormula @testset "Propositional model checking" begin @@ -9,7 +9,7 @@ d0 = Dict(["a" => true, "b" => false, "c" => true]) @test haskey(d0, Atom("a")) @test d0["a"] @test !d0["b"] -# @test check(parsebaseformula("a ∧ ¬b"), d0) +# @test check(parseformula(AnchoredFormula, "a ∧ ¬b"), d0) @test check(parseformula("a ∧ c"), d0) v0 = ["a", "c"] @@ -17,11 +17,11 @@ v0 = ["a", "c"] @test !("b" in v0) @test !(Atom("a") in v0) @test check(parseformula("a ∧ ¬b"), v0) -# @test check(parsebaseformula("a ∧ c"), v0) +# @test check(parseformula(AnchoredFormula, "a ∧ c"), v0) -# @test !check(parsebaseformula("a ∧ b"), ["a"]) +# @test !check(parseformula(AnchoredFormula, "a ∧ b"), ["a"]) @test !check(parseformula("a ∧ ¬b"), ["a", "b"]) -# @test check(parsebaseformula("a ∧ ¬b"), ["a"]) +# @test check(parseformula(AnchoredFormula, "a ∧ ¬b"), ["a"]) @test_nowarn TruthDict(1:4) @test_nowarn TruthDict(1:4, false) @@ -33,7 +33,7 @@ t0 = @test_nowarn TruthDict(["a" => true, "b" => false, "c" => true]) @test haskey(t0, "b") @test check(Atom("a"), t0) @test !check(Atom("b"), t0) -# @test check(parsebaseformula("a ∨ b"), t0) +# @test check(parseformula(AnchoredFormula, "a ∨ b"), t0) t1 = @test_nowarn TruthDict([1 => true, 2 => false, 3 => true]) @@ -81,8 +81,8 @@ t2 = @test_nowarn TruthDict(Pair{Real,Bool}[1.0 => true, 2 => true, 3 => true]) @test_nowarn DefaultedTruthDict(Atom(1.0) => true) @test !check(parseformula("a ∧ b"), DefaultedTruthDict(["a"])) -# @test !check(parsebaseformula("a ∧ ¬b"), DefaultedTruthDict(["a", "b"])) -# @test check(parsebaseformula("a ∧ ¬b"), DefaultedTruthDict(["a"])) +# @test !check(parseformula(AnchoredFormula, "a ∧ ¬b"), DefaultedTruthDict(["a", "b"])) +# @test check(parseformula(AnchoredFormula, "a ∧ ¬b"), DefaultedTruthDict(["a"])) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/test/formulas/anchored-formula.jl b/test/formulas/anchored-formula.jl index 28876bc1..4f824a36 100644 --- a/test/formulas/anchored-formula.jl +++ b/test/formulas/anchored-formula.jl @@ -71,36 +71,36 @@ tdict = TruthDict(Dict([p => false for p in unique(atoms(anch_φ_int))])) -using SoleLogics: parsebaseformula +using SoleLogics: AnchoredFormula -@test_throws ErrorException parsebaseformula("") -@test_broken parsebaseformula("⊤") -@test_broken parsebaseformula("⊤ ∧ ⊤") -@test_broken parsebaseformula("⊤ ∧ p") -@test_broken parsebaseformula("⊥ ∧ □¬((p∧¬q)→r)") -@test_broken parsebaseformula("□¬((p∧¬q)→r) ∧ ⊤") -@test_broken parsebaseformula("⊤ ∧ (⊥∧¬⊤→⊤)") -@test_nowarn parsebaseformula("□¬((p∧¬q)→r)") +@test_throws ErrorException parseformula(AnchoredFormula, "") +@test_broken parseformula(AnchoredFormula, "⊤") +@test_broken parseformula(AnchoredFormula, "⊤ ∧ ⊤") +@test_broken parseformula(AnchoredFormula, "⊤ ∧ p") +@test_broken parseformula(AnchoredFormula, "⊥ ∧ □¬((p∧¬q)→r)") +@test_broken parseformula(AnchoredFormula, "□¬((p∧¬q)→r) ∧ ⊤") +@test_broken parseformula(AnchoredFormula, "⊤ ∧ (⊥∧¬⊤→⊤)") +@test_nowarn parseformula(AnchoredFormula, "□¬((p∧¬q)→r)") -@test_nowarn parsebaseformula("p") -@test_nowarn ¬parsebaseformula("p") -@test_nowarn ¬parsebaseformula("p", propositionallogic()) +@test_nowarn parseformula(AnchoredFormula, "p") +@test_nowarn ¬parseformula(AnchoredFormula, "p") +@test_nowarn ¬parseformula(AnchoredFormula, "p", propositionallogic()) @test_nowarn ¬parseformula("p") @test_nowarn ¬parseformula("(s∧z)", propositionallogic()) @test operatorstype( - logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: SoleLogics.BaseModalConnectives + logic(parseformula(AnchoredFormula, "¬p∧q∧(¬s∧¬z)", [BOX]))) <: SoleLogics.BaseModalConnectives @test !(operatorstype( - logic(parsebaseformula("¬p∧q∧(¬s∧¬z)", [BOX]))) <: + logic(parseformula(AnchoredFormula, "¬p∧q∧(¬s∧¬z)", [BOX]))) <: SoleLogics.BasePropositionalConnectives) @test !(operatorstype(logic( - parsebaseformula("¬p∧q∧(¬s∧¬z)", modallogic()))) <: + parseformula(AnchoredFormula, "¬p∧q∧(¬s∧¬z)", modallogic()))) <: SoleLogics.BasePropositionalConnectives) @test (@test_nowarn operatorstype( - logic(parsebaseformula("¬p∧q∧(¬s∧¬z)"))) <: SoleLogics.BasePropositionalConnectives) + logic(parseformula(AnchoredFormula, "¬p∧q∧(¬s∧¬z)"))) <: SoleLogics.BasePropositionalConnectives) -@test alphabet(logic(parsebaseformula("p→q"))) == AlphabetOfAny{String}() +@test alphabet(logic(parseformula(AnchoredFormula, "p→q"))) == AlphabetOfAny{String}() @test_throws ErrorException propositionallogic(; operators = [¬, ∨])(¬p1) @test_nowarn propositionallogic(; operators = [¬, ∨])(¬p_string) diff --git a/test/random.jl b/test/random.jl index 5f9aa0d1..712a392c 100644 --- a/test/random.jl +++ b/test/random.jl @@ -48,7 +48,7 @@ w = [5,1,1,1,1,1,1] # @test all([begin # f = randformula(i%5, _alphabet, _operators) # s = syntaxstring(f; function_notation = true) -# s == syntaxstring(parsebaseformula(s; function_notation = true); +# s == syntaxstring(parseformula(AnchoredFormula, s; function_notation = true); # function_notation = true) # end for i in 1:1000]) From 5e475df2f3e1bd12cc8a43976685bab94fd99dd8 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 15 Oct 2024 20:20:43 +0200 Subject: [PATCH 36/90] randbaseformula moved from anchored-formula.jl to generation/formula.jl; random.jl removed (all its content is in generation/formula.jl and generation/models.jl) --- src/anchored-formula.jl | 39 ---- src/generation/formula.jl | 41 ++++ src/generation/random.jl | 473 -------------------------------------- 3 files changed, 41 insertions(+), 512 deletions(-) delete mode 100644 src/generation/random.jl diff --git a/src/anchored-formula.jl b/src/anchored-formula.jl index e7ac14f0..41c58782 100644 --- a/src/anchored-formula.jl +++ b/src/anchored-formula.jl @@ -296,42 +296,3 @@ function parseformula( ) AnchoredFormula(logic, parseformula(SyntaxTree, expr, operators(logic); kwargs...)) end - -"""$(doc_randformula)""" -function randbaseformula( - height::Integer, - g::AbstractGrammar; - kwargs... -)::AnchoredFormula - _alphabet = alphabet(g) - _operators = operators(g) - baseformula( - randformula(height, _alphabet, _operators; kwargs...); - alphabet = _alphabet, - additional_operators = _operators - ) -end - -function randbaseformula( - height::Integer, - alphabet, - operators::AbstractVector{<:Operator}; - kwargs... -)::AnchoredFormula - alphabet = convert(AbstractAlphabet, alphabet) - baseformula( - randformula(height, alphabet, operators; kwargs...); - alphabet = alphabet, - additional_operators = operators, - ) -end - -function randbaseformula( - height::Integer, - g::AbstractGrammar, - args...; - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, - kwargs... -)::AnchoredFormula - randbaseformula(height, alphabet(g), operators(g), args...; rng=rng, kwargs...) -end diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 4043439d..79833174 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -210,6 +210,7 @@ end modaldepth::Integer=height, atompicker::Union{Function,AbstractWeights,AbstractVector{<:Real},Nothing}=randatom, opweights::Union{AbstractWeights,AbstractVector{<:Real},Nothing}=nothing, + kwargs... ) rng = initrng(rng) @@ -305,3 +306,43 @@ end ) randformula(initrng(rng), height, args...; kwargs...) end + + +function randbaseformula( + height::Integer, + g::AbstractGrammar; + kwargs... +)::AnchoredFormula + _alphabet = alphabet(g) + _operators = operators(g) + baseformula( + randformula(height, _alphabet, _operators; kwargs...); + alphabet = _alphabet, + additional_operators = _operators + ) +end + +"""""" +function randbaseformula( + height::Integer, + alphabet, + operators::AbstractVector{<:Operator}; + kwargs... +)::AnchoredFormula + alphabet = convert(AbstractAlphabet, alphabet) + baseformula( + randformula(height, alphabet, operators; kwargs...); + alphabet = alphabet, + additional_operators = operators, + ) +end + +function randbaseformula( + height::Integer, + g::AbstractGrammar, + args...; + rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, + kwargs... +)::AnchoredFormula + randbaseformula(height, alphabet(g), operators(g), args...; rng=rng, kwargs...) +end diff --git a/src/generation/random.jl b/src/generation/random.jl deleted file mode 100644 index 3fc810f2..00000000 --- a/src/generation/random.jl +++ /dev/null @@ -1,473 +0,0 @@ -# This script is deprecated. -# See generation/formula.jl and generation/models.jl - -using Graphs -using Random -using StatsBase - -import Random: rand -import StatsBase: sample - -#= ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Formulas ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ =# - -doc_rand = """ - Base.rand( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - alphabet::AbstractAlphabet, - args...; - kwargs... - )::Atom - - Base.rand( - [rng::AbstractRNG = Random.GLOBAL_RNG], - height::Integer, - l::AbstractLogic, - args...; - kwargs... - )::Formula - - Base.rand( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - height::Integer, - g::CompleteFlatGrammar, - args... - )::Formula - - Base.rand( - height::Integer, - connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, - atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, - truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, - args...; - rng::AbstractRNG = Random.GLOBAL_RNG, - kwargs... - )::Formula - -Randomly generate an [`Atom`](@ref) from an [`AbstractAlphabet`](@ref) according to a -uniform distribution. If a [`CompleteFlatGrammar`](@ref) is provided together with an -`height` a [`Formula`](@ref) could also be generated. - -# Implementation -If the `alphabet` is finite, the function defaults to `rand(rng, atoms(alphabet))`; -otherwise, it must be implemented, and additional keyword arguments should be provided -in order to limit the (otherwise infinite) sampling domain. - -See also -[`AbstractAlphabet`](@ref), [`Atom`](@ref), [`CompleteFlatGrammar`](@ref), -[`Formula`](@ref), [`randformula`](@ref). -""" - -"""$(doc_rand)""" -function Base.rand(a::AbstractAlphabet, args...; kwargs...) - Base.rand(Random.GLOBAL_RNG, a, args...; kwargs...) -end - -function Base.rand( - rng::AbstractRNG, - a::AbstractAlphabet, - args...; - kwargs... -) - randatom(rng, a, args...; kwargs...) -end - -function Base.rand(height::Integer, l::AbstractLogic, args...; kwargs...) - Base.rand(Random.GLOBAL_RNG, height, l, args...; kwargs...) -end - -function Base.rand( - rng::AbstractRNG, - height::Integer, - l::AbstractLogic, - args...; - kwargs... -) - Base.rand(rng, grammar(l), args...; kwargs...) -end - -# For the case of a CompleteFlatGrammar, the alphabet and the operators suffice. -function Base.rand( - height::Integer, - g::CompleteFlatGrammar, - args... -) - Base.rand(Random.GLOBAL_RNG, height, g, args...) -end - -function Base.rand( - rng::AbstractRNG, - height::Integer, - g::CompleteFlatGrammar, - args...; - kwargs... -) - randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) -end - -function Base.rand( - height::Integer, - atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, - connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, - truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, - args...; - kwargs... -) - Base.rand(Random.GLOBAL_RNG, height, atoms, connectives, truthvalues, args...; - kwargs...) -end - -function Base.rand( - rng::AbstractRNG, - height::Integer, - atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, - connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, - truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, - args...; - kwargs... -) - # If Truth's are specified as `operators`, then they cannot be simultaneously - # provided as `truthvalues` - @assert (connectives isa AbstractVector{<:Connective} || - !(truthvalues isa AbstractVector{<:Truth}) - ) "Unexpected connectives and truth values: $(connectives) and $(truthvalues)." - - atoms = atoms isa AbstractAlphabet ? SoleLogics.atoms(atoms) : atoms - ops = connectives - if !isnothing(truthvalues) - truthvalues = inittruthvalues(truthvalues) - @assert typejoin(typeof.(truthvalues)...) != Truth "Truth values " * - "$(truthvalues) must belong to the same algebra " * - "(and have a common supertype that is not Truth)." - ops = vcat(ops, truthvalues) - end - - randformula(height, ops, atoms, args...; rng=rng, kwargs...) -end - -doc_sample = """ - function StatsBase.sample( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - alphabet::AbstractAlphabet, - weights::AbstractWeights, - args...; - kwargs... - ) - - function StatsBase.sample( - rng::AbstractRNG, - l::AbstractLogic, - weights::AbstractWeights, - args...; - kwargs... - ) - - StatsBase.sample( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - height::Integer, - g::AbstractGrammar, - [opweights::Union{Nothing,AbstractWeights} = nothing,] - args...; - kwargs... - )::Formula - -Randomly sample an [`Atom`](@ref) from an `alphabet`, or a logic formula of given `height` -from a grammar `g`. -Sampling is weighted, thus, for example, if the first weight in `weights` is higher than -the others, then the first atom in the alphabet is selected more frequently. - -See also [`AbstractAlphabet`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref). -""" -function StatsBase.sample( - alphabet::AbstractAlphabet, - weights::AbstractWeights, - args...; - kwargs... -)::Atom - StatsBase.sample(Random.GLOBAL_RNG, alphabet, weights, args...; kwargs...) -end - -function StatsBase.sample( - rng::AbstractRNG, - alphabet::AbstractAlphabet, - weights::AbstractWeights, - args...; - kwargs... -)::Atom - if isfinite(alphabet) - StatsBase.sample(rng, atoms(alphabet), weights, args...; kwargs...) - else - error("Please, provide method StatsBase.sample(rng::AbstractRNG, " * - "alphabet::$(typeof(alphabet)), args...; kwargs...).") - end -end - -function StatsBase.sample( - l::AbstractLogic, - atomweights::AbstractWeights, - opweights::AbstractWeights, - args...; - kwargs... -) - StatsBase.sample(Random.GLOBAL_RNG, l, atomweights, opweights, args...; kwargs...) -end - -function StatsBase.sample( - rng::AbstractRNG, - l::AbstractLogic, - atomweights::AbstractWeights, - opweights::AbstractWeights, - args...; - kwargs... -) - StatsBase.sample(init(rng), grammar(l), atomweights, opweights, args...; kwargs...) -end - -"""$(doc_sample)""" -function StatsBase.sample( - height::Integer, - g::AbstractGrammar, - atomweights::Union{Nothing,AbstractWeights} = nothing, - opweights::Union{Nothing,AbstractWeights} = nothing, - args...; - kwargs... -) - StatsBase.sample(Random.GLOBAL_RNG, height, g, atomweights, opweights, args...; - kwargs...) -end - -"""$(doc_sample)""" -function StatsBase.sample( - rng::AbstractRNG, - height::Integer, - g::AbstractGrammar, - atomweights::Union{Nothing,AbstractWeights} = nothing, - opweights::Union{Nothing,AbstractWeights} = nothing, - args...; - kwargs... -) - randformula( - rng, height, alphabet(g), operators(g), args...; - # atompicker=(rng,dom)->StatsBase.sample(rng, dom, atomweights), kwargs...) - atompicker = atomweights, opweights = opweights, kwargs...) -end - -#= ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CompleteFlatGrammar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ =# - -# TODO -# - make rng first (optional) argument of randformula (see above) -# - in randformula, keyword argument alphabet_sample_kwargs that are unpacked upon sampling atoms, as in: Base.rand(rng, a; alphabet_sample_kwargs...). This would allow to sample from infinite alphabets, so when this parameter, !isfinite(alphabet) is allowed! - -# TODO @Mauro implement this method. -doc_randformula = """ - randformula( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, ] - height::Integer, - alphabet, - operators::AbstractVector; - kwargs... - )::SyntaxTree - - randformula( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, ] - height::Integer, - g::AbstractGrammar; - kwargs... - )::SyntaxTree - -Return a pseudo-randomic `SyntaxTree`. - -# Arguments -- `rng::Union{Intger,AbstractRNG} = Random.GLOBAL_RNG`: random number generator; -- `height::Integer`: height of the generated structure; -- `alphabet::AbstractAlphabet`: collection from which atoms are chosen randomly; -- `operators::AbstractVector`: vector from which legal operators are chosen; -- `g::AbstractGrammar`: alternative to passing alphabet and operators separately. (TODO explain?) - -# Keyword Arguments -- `modaldepth::Integer`: maximum modal depth -- `atompicker::Function`: method used to pick a random element. For example, this could be - Base.rand or StatsBase.sample. -- `opweights::AbstractWeights`: weight vector over the set of operators (see `StatsBase`). - -# Examples - -```julia-repl -julia> syntaxstring(randformula(4, ExplicitAlphabet([1,2]), [NEGATION, CONJUNCTION, IMPLICATION])) -"¬((¬(¬(2))) → ((1 → 2) → (1 → 2)))" -``` - -See also [`AbstractAlphabet`](@ref), [`SyntaxBranch`](@ref). -""" - -"""$(doc_randformula)""" -function randformula( - rng::Union{Integer,AbstractRNG}, - height::Integer, - alphabet::Union{AbstractVector,AbstractAlphabet}, - operators::AbstractVector; - modaldepth::Integer = height, - atompicker::Union{Function,AbstractWeights,AbstractVector{<:Real},Nothing} = randatom, - opweights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing -)::SyntaxTree - - rng = initrng(rng) - alphabet = convert(AbstractAlphabet, alphabet) - @assert all(x->x isa Operator, operators) "Unexpected object(s) passed as" * - " operator:" * " $(filter(x->!(x isa Operator), operators))" - - if (isnothing(opweights)) - opweights = StatsBase.uweights(length(operators)) - elseif (opweights isa AbstractVector) - @assert length(opweights) == length(operators) "Mismatching numbers of operators " * - "($(length(operators))) and opweights ($(length(opweights)))." - opweights = StatsBase.weights(opweights) - end - - if (isnothing(atompicker)) - atompicker = StatsBase.uweights(natoms(alphabet)) - elseif (atompicker isa AbstractVector) - @assert length(atompicker) == natoms(alphabet) "Mismatching numbers of atoms " * - "($(natoms(alphabet))) and atompicker ($(length(atompicker)))." - atompicker = StatsBase.weights(atompicker) - end - - if !(atompicker isa Function) - atomweights = atompicker - atompicker = (rng, dom)->StatsBase.sample(rng, dom, atomweights) - end - - nonmodal_operators = findall(!ismodal, operators) - - # recursive call - function _randformula( - rng::AbstractRNG, - height::Integer, - modaldepth::Integer; - )::SyntaxTree - - if height == 0 - return atompicker(rng, alphabet) - else - # Sample operator and generate children (modal connectives only if modaldepth > 0) - ops, ops_w = begin - if modaldepth > 0 - operators, opweights - else - operators[nonmodal_operators], opweights[nonmodal_operators] - end - end - - # op = rand(rng, ops) - op = sample(rng, ops, ops_w) - ch = Tuple([ - _randformula(rng, height-1, modaldepth-(ismodal(op) ? 1 : 0)) - for _ in 1:arity(op)]) - return SyntaxTree(op, ch) - end - end - - # If the alphabet is not iterable, this function should not work. - if !isfinite(alphabet) - @warn "Attempting to generate random formulas from " * - "(infinite) alphabet of type $(typeof(alphabet))!" - end - - return _randformula(rng, height, modaldepth) -end - -function randformula( - rng::AbstractRNG, - height::Integer, - g::AbstractGrammar, - args...; - kwargs... -) - randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) -end - -# Helper -function randformula( - height::Integer, - args...; - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, - kwargs... -) - randformula(initrng(rng), height, args...; kwargs...) -end - -#= ~~~~~~~~~~~~~~~~~~~~~~~~~~ Kripke Models generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ =# -""" - function randframe( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] - nworlds::Int64, - nedges::Int64, - facts::Vector{SyntaxLeaf} - end - -Return a random Kripke Frame, which is a directed graph interpreted as a -[`SoleLogics.ExplicitCrispUniModalFrame`](@ref). The underlying graph is generated using -[`Graphs.SimpleGraphs.SimpleDiGraph`](@ref). - -# Arguments: -* `rng` is a random number generator, or the seed used to create one; -* `nworld` is the number of worlds (nodes) in the frame. Worlds are numbered from `1` - to `nworld` included. -* `nedges` is the number of relations (edges) in the frame; -* `facts` is a vector of generic [`SyntaxLeaf`](@ref). - -See also [`SyntaxLeaf`](@ref), [`Graphs.SimpleGraphs.SimpleDiGraph`](@ref), -[`SoleLogics.ExplicitCrispUniModalFrame`](@ref). -""" -function randframe( - nworlds::Int64, - nedges::Int64; - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG -) - randframe(rng, nworlds, nedges) -end - -function randframe( - rng::Union{Integer,AbstractRNG}, - nworlds::Int64, - nedges::Int64 -) - worlds = World.(1:nworlds) - graph = Graphs.SimpleDiGraph(nworlds, nedges, rng=initrng(rng)) - return SoleLogics.ExplicitCrispUniModalFrame(worlds, graph) -end - -""" - function randmodel( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] - nworlds::Int64, - nedges::Int64, - facts::Vector{SyntaxLeaf}; - truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} = BooleanAlgebra(); - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG - ) -""" -function randmodel( - nworlds::Int64, - nedges::Int64, - facts::Vector{<:SyntaxLeaf}, - truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} = BooleanAlgebra(); - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG -) - truthvalues = inittruthvalues(truthvalues) - randmodel(initrng(rng), nworlds, nedges, facts, truthvalues) -end - -function randmodel( - rng::AbstractRNG, - nworlds::Int64, - nedges::Int64, - facts::Vector{<:SyntaxLeaf}, - truthvalues::AbstractVector{<:Truth} -) - fr = randframe(rng, nworlds, nedges) - valuation = Dict( - [w => TruthDict([f => rand(truthvalues) for f in facts]) for w in fr.worlds] - ) - - return KripkeStructure(fr, valuation) -end From 579ee8628f02737027425a46da468eb9db8f8409 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 15 Oct 2024 20:26:24 +0200 Subject: [PATCH 37/90] AbstractAlphabet{V} docstring typo: atoms()::Bool instead of atoms()::AbstractVector --- src/generation/formula.jl | 2 +- src/types/logic.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 79833174..84338bfd 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -210,7 +210,7 @@ end modaldepth::Integer=height, atompicker::Union{Function,AbstractWeights,AbstractVector{<:Real},Nothing}=randatom, opweights::Union{AbstractWeights,AbstractVector{<:Real},Nothing}=nothing, - + # alphabet_sample_kwargs kwargs... ) rng = initrng(rng) diff --git a/src/types/logic.jl b/src/types/logic.jl index f460c709..c01d54e3 100644 --- a/src/types/logic.jl +++ b/src/types/logic.jl @@ -36,7 +36,7 @@ true ``` # Interface -- `atoms(a::AbstractAlphabet)::Bool` +- `atoms(a::AbstractAlphabet)::AbstractVector` - `Base.isfinite(::Type{<:AbstractAlphabet})::Bool` - `randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...)::AbstractAtom` From d92c9f2f23e1542877518f96d727bb61754fb916 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 15 Oct 2024 20:28:37 +0200 Subject: [PATCH 38/90] Union{...,Nothing} to Union{Nothing,...} in the entire package --- src/generation/docstrings.jl | 6 +++--- src/generation/formula.jl | 6 +++--- src/modal-logic.jl | 2 +- src/syntax-utils.jl | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index 36ca9ebf..82ef2d99 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -26,7 +26,7 @@ randatom_unionalphabet_docstring = """ [rng::Union{Random.AbstractRNG,Integer},] a::UnionAlphabet; atompicking_mode::Symbol=:uniform, - subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing}=nothing + subalphabets_weights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing )::Atom Sample an atom from a `UnionAlphabet`. @@ -180,8 +180,8 @@ randformula_docstring = """ operators::AbstractVector, args...; modaldepth::Integer=height, - atompicker::Union{Function,AbstractWeights,AbstractVector{<:Real},Nothing}=randatom, - opweights::Union{AbstractWeights,AbstractVector{<:Real},Nothing}=nothing, + atompicker::Union{Nothing,Function,AbstractWeights,AbstractVector{<:Real}}=randatom, + opweights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing, kwargs... ) diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 84338bfd..44b28347 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -208,9 +208,9 @@ end operators::AbstractVector, args...; modaldepth::Integer=height, - atompicker::Union{Function,AbstractWeights,AbstractVector{<:Real},Nothing}=randatom, - opweights::Union{AbstractWeights,AbstractVector{<:Real},Nothing}=nothing, - # alphabet_sample_kwargs + atompicker::Union{Nothing,Function,AbstractWeights,AbstractVector{<:Real}}=randatom, + opweights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing, + alphabet_sample_kwargs::Union{Nothing,AbstractVector}, kwargs... ) rng = initrng(rng) diff --git a/src/modal-logic.jl b/src/modal-logic.jl index 707aac74..df6204e8 100644 --- a/src/modal-logic.jl +++ b/src/modal-logic.jl @@ -645,7 +645,7 @@ end function interpret( φ::Formula, i::AbstractKripkeStructure, - w::Union{AbstractWorld,Nothing}, + w::Union{Nothing,AbstractWorld}, )::Formula return error("Please, provide method interpret(::$(typeof(φ)), ::$(typeof(i)), ::$(typeof(w))).") end diff --git a/src/syntax-utils.jl b/src/syntax-utils.jl index 7b70037c..f9e52e52 100644 --- a/src/syntax-utils.jl +++ b/src/syntax-utils.jl @@ -111,7 +111,7 @@ struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure function LeftmostLinearForm( tree::SyntaxTree, - c::Union{<:SoleLogics.Connective,Nothing} = nothing + c::Union{Nothing,<:SoleLogics.Connective} = nothing ) # Check c correctness; it should not be nothing (thus, auto inferred) if # tree root contains something that is not a connective @@ -636,7 +636,7 @@ function treewalk( rng::AbstractRNG = Random.GLOBAL_RNG, criterion::Function = c->true, returnnode::Bool = false, - transformnode::Union{Function,Nothing} = nothing, + transformnode::Union{Nothing,Function} = nothing, ) chs = children(st) From be68360b7dec0e9077a33d7f7f1dcb73d3d7eb19 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 15 Oct 2024 20:32:14 +0200 Subject: [PATCH 39/90] randformula enriched with a new kwarg: alphabet_sample_kwargs (which is still not used) --- src/generation/docstrings.jl | 3 +++ src/generation/formula.jl | 11 ++--------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index 82ef2d99..99be7b74 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -182,6 +182,7 @@ randformula_docstring = """ modaldepth::Integer=height, atompicker::Union{Nothing,Function,AbstractWeights,AbstractVector{<:Real}}=randatom, opweights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing, + alphabet_sample_kwargs::Union{Nothing,AbstractVector}=nothing, kwargs... ) @@ -199,6 +200,8 @@ Return a pseudo-randomic [`SyntaxTree`](@ref). Base.rand or StatsBase.sample; - `opweights::AbstractWeights`: operators are sampled with probabilities proportional to this vector vector (see [`AbstractWeights`](@ref) of StatsBase package). +- `alphabet_sample_kwargs::AbstractVector`: pool of atoms to pull from if the given alphabet + is not finite. # Examples diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 44b28347..914747a6 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -193,13 +193,6 @@ end -# TODO -# - make rng first (optional) argument of randformula (see above) -# - in randformula, keyword argument alphabet_sample_kwargs that are unpacked upon sampling atoms, as in: Base.rand(rng, a; alphabet_sample_kwargs...). This would allow to sample from infinite alphabets, so when this parameter, !isfinite(alphabet) is allowed! - -# TODO @Mauro implement this method. - - """$(randformula_docstring)""" @__rng_dispatch function randformula( rng::Union{Integer,AbstractRNG}, @@ -210,7 +203,7 @@ end modaldepth::Integer=height, atompicker::Union{Nothing,Function,AbstractWeights,AbstractVector{<:Real}}=randatom, opweights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing, - alphabet_sample_kwargs::Union{Nothing,AbstractVector}, + alphabet_sample_kwargs::Union{Nothing,AbstractVector}=nothing, kwargs... ) rng = initrng(rng) @@ -279,7 +272,7 @@ end end # if the alphabet is not iterable, this function should not work. - if !isfinite(alphabet) + if !isfinite(alphabet) && isnothing(alphabet_sample_kwargs) @warn "Attempting to generate random formulas from " * "(infinite) alphabet of type $(typeof(alphabet))!" end From b3c45ac3b3fb8fd30d237fe03f39e81e6eee455f Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 15 Oct 2024 20:46:08 +0200 Subject: [PATCH 40/90] randbaseformula docstring --- src/generation/docstrings.jl | 17 ++++++++++++++++ src/generation/formula.jl | 38 ++++++++++++------------------------ 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index 99be7b74..3c1229a0 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -229,3 +229,20 @@ generated [`SyntaxTree`](@ref). See also [`AbstractGrammar`](@ref), [`randformula(::Integer, ::Union{AbstractVector,AbstractAlphabet}, ::AbstractVector)`](@ref). """ + +randbaseformula_docstring = """ + function randbaseformula( + rng::Union{Integer,AbstractRNG}, + height::Integer, + alphabet::Union{AbstractVector,AbstractAlphabet}, + operators::AbstractVector{<:Operator}, + args...; + kwargs... + )::AnchoredFormula + +Similar to `randformula`, but generates an [`AnchoredFormula`](@ref) anchored to the logic +that includes [`Atom`](@ref)s in `alphabet` and [`Operator`](@ref)s in `operators`. + +See also [`AnchoredFormula`](@ref), [`Atom`](@ref), [`Operator`](@ref), +[`randformula(::Integer, ::Union{AbstractVector,AbstractAlphabet}, ::AbstractVector)`](@ref) +""" diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 914747a6..67531a23 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -301,41 +301,29 @@ end end -function randbaseformula( - height::Integer, - g::AbstractGrammar; - kwargs... -)::AnchoredFormula - _alphabet = alphabet(g) - _operators = operators(g) - baseformula( - randformula(height, _alphabet, _operators; kwargs...); - alphabet = _alphabet, - additional_operators = _operators - ) -end - -"""""" -function randbaseformula( +"""$(randbaseformula_docstring)""" +@__rng_dispatch function randbaseformula( + rng::Union{Integer,AbstractRNG}, height::Integer, - alphabet, - operators::AbstractVector{<:Operator}; + alphabet::Union{AbstractVector,AbstractAlphabet}, + operators::AbstractVector{<:Operator}, + args...; kwargs... -)::AnchoredFormula +) alphabet = convert(AbstractAlphabet, alphabet) baseformula( - randformula(height, alphabet, operators; kwargs...); - alphabet = alphabet, - additional_operators = operators, + randformula(height, alphabet, operators, args...; kwargs...); + alphabet=alphabet, + additional_operators=operators, ) end -function randbaseformula( +@__rng_dispatch function randbaseformula( + rng::Union{Integer,AbstractRNG}, height::Integer, g::AbstractGrammar, args...; - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, kwargs... )::AnchoredFormula - randbaseformula(height, alphabet(g), operators(g), args...; rng=rng, kwargs...) + randbaseformula(rng, height, alphabet(g), operators(g), args...; kwargs...) end From 62c5af84c3b001e4f532466db6fc0081110b80e1 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 15 Oct 2024 23:02:17 +0200 Subject: [PATCH 41/90] Tests refactoring. generator/formula.jl dispatch ambiguity resolved with Base.rand. Now, tests/random.jl is splitted in tests/generator/formula.jl and tests/generator/models.jl --- src/generation/formula.jl | 17 ++++-- test/generation/formula.jl | 118 +++++++++++++++++++++++++++++++++++++ test/generation/models.jl | 0 test/random.jl | 84 -------------------------- test/runtests.jl | 4 +- 5 files changed, 134 insertions(+), 89 deletions(-) create mode 100644 test/generation/formula.jl create mode 100644 test/generation/models.jl delete mode 100644 test/random.jl diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 67531a23..3c46eb68 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -79,7 +79,7 @@ end """$(rand_abstractalphabet_docstring)""" -@__rng_dispatch function Base.rand( +@__rng_dispatch function SoleLogics.rand( rng::Union{Integer,AbstractRNG}, a::AbstractAlphabet, args...; @@ -87,7 +87,14 @@ end ) randatom(initrng(rng), a, args...; kwargs...) end - +function rand(rng::Random.AbstractRNG, a::SoleLogics.AbstractAlphabet, args...; kwargs...) + # This is needed to avoid a dispatch ambiguity caused by @__rng_dispatch: + # rand(rng::Union{Integer, Random.AbstractRNG}, a::SoleLogics.AbstractAlphabet, args...; kwargs...) + # @ SoleLogics ~/.julia/fork/SoleLogics.jl/src/generation/formula.jl:82 + # rand(rng::Random.AbstractRNG, X) + # @ Random ~/.julia/julia_exec/julia-1.9.0/share/julia/stdlib/v1.9/Random/src/Random.jl:256 + randatom(rng, a, args...; kwargs...) +end """$(rand_abstractlogic_docstring)""" @__rng_dispatch function Base.rand( @@ -167,13 +174,15 @@ end """$(sample_lao_docstring)""" @__rng_dispatch function StatsBase.sample( rng::Union{Integer,AbstractRNG}, + height::Integer, l::AbstractLogic, atomweights::AbstractWeights, opweights::AbstractWeights, args...; kwargs... ) - StatsBase.sample(init(rng), grammar(l), atomweights, opweights, args...; kwargs...) + StatsBase.sample( + initrng(rng), height, grammar(l), atomweights, opweights, args...; kwargs...) end """$(sample_hgao_docstring)""" @@ -324,6 +333,6 @@ end g::AbstractGrammar, args...; kwargs... -)::AnchoredFormula +) randbaseformula(rng, height, alphabet(g), operators(g), args...; kwargs...) end diff --git a/test/generation/formula.jl b/test/generation/formula.jl new file mode 100644 index 00000000..56e57a89 --- /dev/null +++ b/test/generation/formula.jl @@ -0,0 +1,118 @@ +using StatsBase +import SoleLogics: arity +using SoleLogics: parsebaseformula +using Random + +@testset "randformula + randbaseformula" begin + +_alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) +_operators = [NEGATION, CONJUNCTION, IMPLICATION] +w = [10,1,1] + +@test_nowarn [randbaseformula(i, _alphabet, _operators) for i in 1:5] +@test_nowarn [randbaseformula(i, _alphabet, _operators, opweights=w) for i in 1:2] +@test_nowarn [randformula(i, _alphabet, _operators, opweights=w) for i in 1:5] + +end + + + +@testset "generation w. custom operators" begin + +TERNOP = SoleLogics.NamedConnective{:⇶}() +SoleLogics.arity(::typeof(TERNOP)) = 3 + +QUATERNOP = SoleLogics.NamedConnective{:⩰}() +SoleLogics.arity(::typeof(QUATERNOP)) = 4 + +_alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) +_operators = [NEGATION, CONJUNCTION, IMPLICATION, + DiamondRelationalConnective(globalrel), BoxRelationalConnective(globalrel)] +w = [5,1,1,1,1,1,1] + +@test all([begin + f = randbaseformula(3, _alphabet, _operators) + s = syntaxstring(f) + s == syntaxstring(parseformula(s)) + end for i in 1:10]) + +@test all([begin + f = randformula(i%5, _alphabet, [_operators..., TERNOP, QUATERNOP]) + s = syntaxstring(f; function_notation = true) + s == syntaxstring( + parseformula( + s, [_operators..., TERNOP, QUATERNOP]; function_notation = true), + function_notation = true) + end for i in 1:10]) + +@test all([begin + f = randbaseformula(i%5, _alphabet, _operators) + s = syntaxstring(f; function_notation = true) + s == syntaxstring(parsebaseformula(s; function_notation = true); + function_notation = true) + end for i in 1:10]) + +end + + + +@testset "Dispatches made with @__rng_dispatch" begin + +my_alph = ExplicitAlphabet(1:5) +my_ops = [∧,¬] +my_grammar = SoleLogics.CompleteFlatGrammar(my_alph, my_ops) +my_logic = propositionallogic(alphabet=my_alph) + + +@test_nowarn randatom(my_alph) +@test randatom(42, my_alph) == Atom(4) + +alph2 = ExplicitAlphabet(6:10) +unionalph = UnionAlphabet([my_alph,alph2]) +@test_nowarn randatom(unionalph) +@test randatom(42, unionalph) == Atom(6) + +_subalphabets_weights_test_dim = 100 +@test count(x -> x<5, + atom.([randatom(unionalph; atompicking_mode=:weighted, subalphabets_weights=[5,1]) + for i in 1:_subalphabets_weights_test_dim]) + ) > convert(Int32, (_subalphabets_weights_test_dim/2)) + +@test_nowarn rand(my_alph) +@test_nowarn rand(42, my_alph) == Atom(4) + +@test_nowarn rand(4) +@test rand(MersenneTwister(42), 2, my_logic) |> syntaxstring == "(1 ∧ 5) ∨ ¬2" + + +@test_nowarn rand(4, my_grammar) +@test_nowarn rand(Random.MersenneTwister(1), 4, my_grammar) + +@test_nowarn sample(my_alph, Weights([1,2,3,4,5])) +@test sample(2, my_alph, Weights([1,2,3,4,5])) == Atom(3) + +@test_nowarn StatsBase.sample(2, my_logic, Weights([1,2,3,4,5]), Weights([1,2])) +@test StatsBase.sample( + MersenneTwister(42), + 2, + my_logic, + Weights([1,2,3,4,5]), + Weights([1,2]) + ) |> syntaxstring == "(1 ∧ 1) ∨ (5 → 2)" + +@test StatsBase.sample(MersenneTwister(42), 2, my_grammar) |> syntaxstring == "¬(1 ∧ 1)" +@test_nowarn StatsBase.sample(2, my_grammar) + +@test_nowarn randformula(4, my_grammar) +@test_nowarn randformula(MersenneTwister(1), 4, my_grammar) +@test_nowarn randformula(4, my_alph, my_ops) +@test_nowarn randformula(MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:5) +@test_throws ArgumentError randformula( + MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:6) + +@test_nowarn randbaseformula(2, my_alph, my_ops) +@test_nowarn randbaseformula(MersenneTwister(42), 2, my_alph, my_ops) + +@test_nowarn randbaseformula(4, my_grammar) +@test_nowarn randbaseformula(MersenneTwister(42), 4, my_grammar) +end diff --git a/test/generation/models.jl b/test/generation/models.jl new file mode 100644 index 00000000..e69de29b diff --git a/test/random.jl b/test/random.jl deleted file mode 100644 index b4f3e1d1..00000000 --- a/test/random.jl +++ /dev/null @@ -1,84 +0,0 @@ -using StatsBase -import SoleLogics: arity -using SoleLogics: parsebaseformula -using Random - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ random logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -@testset "Random" begin - -_alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) -_operators = [NEGATION, CONJUNCTION, IMPLICATION] -w = [10,1,1] - -@test_nowarn [randbaseformula(i, _alphabet, _operators) for i in 1:15] -@test_nowarn [randbaseformula(i, _alphabet, _operators, opweights=w) for i in 1:2] -@test_nowarn [randformula(i, _alphabet, _operators, opweights=w) for i in 1:10] - -end - -@testset "Random+Parsing" begin - -TERNOP = SoleLogics.NamedConnective{:⇶}() -SoleLogics.arity(::typeof(TERNOP)) = 3 - -QUATERNOP = SoleLogics.NamedConnective{:⩰}() -SoleLogics.arity(::typeof(QUATERNOP)) = 4 - -_alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) -_operators = [NEGATION, CONJUNCTION, IMPLICATION, - DiamondRelationalConnective(globalrel), BoxRelationalConnective(globalrel)] -w = [5,1,1,1,1,1,1] - -@test all([begin - f = randbaseformula(3, _alphabet, _operators) - s = syntaxstring(f) - s == syntaxstring(parseformula(s)) - end for i in 1:1000]) - -@test all([begin - f = randformula(i%5, _alphabet, [_operators..., TERNOP, QUATERNOP]) - # "function_notation = true" is essential in each parsing and string conversion - # to represent ternary operators (or generally operators whose arity is > 2). - s = syntaxstring(f; function_notation = true) - s == syntaxstring( - parseformula( - s, [_operators..., TERNOP, QUATERNOP]; function_notation = true), - function_notation = true) - end for i in 1:1000]) - -@test all([begin - f = randbaseformula(i%5, _alphabet, _operators) - s = syntaxstring(f; function_notation = true) - s == syntaxstring(parsebaseformula(s; function_notation = true); - function_notation = true) - end for i in 1:1000]) - -end - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ random interfaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -using Random - -alph = ExplicitAlphabet(1:5) -ops = [∧,¬] -g = SoleLogics.CompleteFlatGrammar(alph, ops) - -@test_nowarn Base.rand(4, g) -@test_nowarn Base.rand(Random.MersenneTwister(1), 4, g) -@test_nowarn randbaseformula(4, g) -@test_nowarn randbaseformula(4, g; rng = Random.MersenneTwister(1)) - -@test_nowarn StatsBase.sample(4, g) -@test_nowarn StatsBase.sample(Random.MersenneTwister(1), 4, g) - -@test_nowarn randformula(4, g) -@test_nowarn randformula(Random.MersenneTwister(1), 4, g) -@test_nowarn randformula(4, alph, ops) -@test_nowarn randformula(Random.MersenneTwister(1), 4, alph, ops; atompicker = 1:5) -@test_throws ArgumentError randformula(Random.MersenneTwister(1), 4, alph, ops; atompicker = 1:6) - -@test_nowarn StatsBase.sample(4, g, Weights([1:natoms(alphabet(g))]...)) -@test_nowarn StatsBase.sample(4, g, Weights([1,1,1,1,100])) -@test_throws MethodError StatsBase.sample(4, g, [1,1,1,1,100]) -@test_throws MethodError StatsBase.sample(Random.MersenneTwister(1), 4, g, 1:2) diff --git a/test/runtests.jl b/test/runtests.jl index c61d4f53..89b3f6d0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -15,7 +15,6 @@ println("Julia version: ", VERSION) test_suites = [ ("Core", ["core.jl",]), ("Parse", ["parse.jl",]), - ("Random", ["random.jl",]), ("Normalize", ["normalize.jl",]), ("Syntax Utils", ["syntax-utils.jl",]), @@ -29,6 +28,9 @@ test_suites = [ ("Algebras: frames", ["algebras/frames.jl",]), ("Algebras: relations", ["algebras/relations.jl",]), + ("Generation: formula", ["generation/formula.jl",]), + ("Generation: models", ["generation/models.jl",]), + ("Kripke word", ["kripke-word.jl",]), ("Kripke image", ["kripke-image.jl",]), From dd73f63f7f4b2a99b22af367e280651be8c91e0a Mon Sep 17 00:00:00 2001 From: Michele Ghiotti Date: Tue, 15 Oct 2024 23:12:09 +0200 Subject: [PATCH 42/90] Added documentation to haskey and inlinedisplay functions for AbstractAssignment and AbstractAssignment itself. --- src/SoleLogics.jl | 3 ++- src/types/propositional-logic.jl | 46 +++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index e5214dd4..23a22241 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -67,11 +67,12 @@ export BooleanAlgebra export BaseLogic -include("utils.jl") +include("utils/synctactical.jl") ############################################################################################ export propositionallogic +export inlinedisplay export TruthDict, DefaultedTruthDict export truth_table diff --git a/src/types/propositional-logic.jl b/src/types/propositional-logic.jl index e4439d68..6a0f7452 100644 --- a/src/types/propositional-logic.jl +++ b/src/types/propositional-logic.jl @@ -1,7 +1,10 @@ +"""Placeholder to indicate a vector of propositional logical operators, i.e. ¬, ∧, ∨, →.""" const BASE_PROPOSITIONAL_CONNECTIVES = BASE_CONNECTIVES + +"""Types associated with propositional logical operators.""" const BasePropositionalConnectives = Union{typeof.(BASE_PROPOSITIONAL_CONNECTIVES)...} -# A propositional logic based on the base propositional operators +"""A propositional logic based on the base propositional operators.""" const BasePropositionalLogic = AbstractLogic{G,A} where { ALP, G<:AbstractGrammar{ALP,<:BasePropositionalConnectives}, @@ -60,6 +63,11 @@ end Abstract type for assigments, that is, interpretations of propositional logic, encoding mappings from `Atom`s to `Truth` values. +# Interface +- `Base.haskey(i::AbstractAssignment, ::AbstractAtom)::Bool` +- `inlinedisplay(i::AbstractAssignment)` +- `interpret(a::AbstractAtom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf` + See also [`AbstractInterpretation`](@ref). """ abstract type AbstractAssignment <: AbstractInterpretation end @@ -67,19 +75,51 @@ abstract type AbstractAssignment <: AbstractInterpretation end """ Base.haskey(i::AbstractAssignment, ::AbstractAtom)::Bool + Base.haskey(i::AbstractAssignment, v)::Bool + Return whether an assigment has a truth value for a given atom. +If any object is passed, it is wrapped in an atom and then checked. -See also [`AbstractInterpretation`](@ref). +# Examples + +```julia-repl +julia> haskey(TruthDict(["a" => true, "b" => false, "c" => true]), Atom("a")) +true + +julia> haskey(TruthDict(1:4, false), 3) +true + +julia> haskey(TruthDict(1:4, false), 8) +false +``` + +See also [`AbstractInterpretation`](@ref), [`AbstractAtom`](@ref), [`TruthDict`](@ref). """ function Base.haskey(i::AbstractAssignment, ::AbstractAtom)::Bool return error("Please, provide method Base.haskey(::$(typeof(i)), ::AbstractAtom)::Bool.") end -# Helper function Base.haskey(i::AbstractAssignment, v)::Bool Base.haskey(i, Atom(v)) end +""" + inlinedisplay(i::AbstractAssignment) + +Provides a string representation of an AbstractAssignment. + +# Examples + +```julia-repl +julia> SoleLogics.inlinedisplay(TruthDict(["a" => true, "b" => false, "c" => true])) +"TruthDict([c => ⊤, b => ⊥, a => ⊤])" + +julia> SoleLogics.inlinedisplay(TruthDict(1:4, false)) +"TruthDict([4 => ⊥, 2 => ⊥, 3 => ⊥, 1 => ⊥])" +``` + +See also [`TruthDict`](@ref). +""" function inlinedisplay(i::AbstractAssignment) return error("Please, provide method inlinedisplay(::$(typeof(i)))::String.") end From 7395da320ae11c716a0720b90fb863e7c259f9c0 Mon Sep 17 00:00:00 2001 From: edo-007 Date: Wed, 16 Oct 2024 00:41:42 +0200 Subject: [PATCH 43/90] + utils/syntactical.jl & removed <:AbstractAlphabet from utils/syntactical.jl --- src/SoleLogics.jl | 2 +- src/types/docstrings.jl | 9 + src/types/syntactical.jl | 34 +- src/utils.jl | 931 -------------------------------------- src/utils/syntactical.jl | 950 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 976 insertions(+), 950 deletions(-) delete mode 100644 src/utils.jl create mode 100644 src/utils/syntactical.jl diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 737260a1..b5527337 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -67,7 +67,7 @@ export BooleanAlgebra export BaseLogic -include("utils.jl") +include("utils/syntactical.jl") ############################################################################################ diff --git a/src/types/docstrings.jl b/src/types/docstrings.jl index f94f61db..ba770a2a 100644 --- a/src/types/docstrings.jl +++ b/src/types/docstrings.jl @@ -259,6 +259,15 @@ thanks to the following two methods that were defined in SoleLogics: See also [`Formula`](@ref), [`Connective`](@ref). """ +doc_tree = """ + tree(φ::Formula)::SyntaxTree + +Return the `SyntaxTree` representation of a formula; +note that this is equivalent to `Base.convert(SyntaxTree, φ)`. + +See also [`Formula`](@ref), [`SyntaxTree`](@ref). +""" + # TODO this piece of doc will be useful when we define simplify(φ), to explain # the differences between the two # !!! info diff --git a/src/types/syntactical.jl b/src/types/syntactical.jl index 9f715f78..b3803944 100644 --- a/src/types/syntactical.jl +++ b/src/types/syntactical.jl @@ -14,6 +14,7 @@ Also: const Operator = Union{Connective,Truth} const SyntaxToken = Union{Connective,SyntaxLeaf} + =# include("docstrings.jl") @@ -171,6 +172,7 @@ It can be parsed from its [`syntaxstring`](@ref) representation via [`parseformu # 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}` @@ -189,18 +191,23 @@ See also [`tree`](@ref), [`SyntaxStructure`](@ref), [`SyntaxLeaf`](@ref). """ abstract type Formula <: Syntactical end -""" - tree(φ::Formula)::SyntaxTree - -Return the `SyntaxTree` representation of a formula; -note that this is equivalent to `Base.convert(SyntaxTree, φ)`. - -See also [`Formula`](@ref), [`SyntaxTree`](@ref). -""" +"""$(doc_tree)""" function tree(φ::Formula) return error("Please, provide method tree(::$(typeof(φ)))::SyntaxTree.") end +"""$(doc_composeformulas)""" +function composeformulas(c::Connective, φs::NTuple{N,F})::F where {N,F<:Formula} + return error("Please, provide method " * + "composeformulas(c::Connective, φs::NTuple{N,$(F)}) where {N}.") +end + +# Helper (?) +# Note: don't type the output as F +function composeformulas(c::Connective, φs::Vararg{Formula,N}) where {N} + return composeformulas(c, φs) +end + """ height(φ::Formula)::Int @@ -261,6 +268,7 @@ function noperators(φ::Formula)::Int return noperators(tree(φ)); end + function Base.isequal(φ1::Formula, φ2::Formula) Base.isequal(tree(φ1), tree(φ2)) end @@ -271,17 +279,7 @@ function syntaxstring(φ::Formula; kwargs...) syntaxstring(tree(φ); kwargs...) end -"""$(doc_composeformulas)""" -function composeformulas(c::Connective, φs::NTuple{N,F})::F where {N,F<:Formula} - return error("Please, provide method " * - "composeformulas(c::Connective, φs::NTuple{N,$(F)}) where {N}.") -end -# Helper (?) -# Note: don't type the output as F -function composeformulas(c::Connective, φs::Vararg{Formula,N}) where {N} - return composeformulas(c, φs) -end ############################################################################################ #### SyntaxStructure ############################################################### diff --git a/src/utils.jl b/src/utils.jl deleted file mode 100644 index 817c5d21..00000000 --- a/src/utils.jl +++ /dev/null @@ -1,931 +0,0 @@ -#= - Syntactical Type Hierarchy - - Syntactical - ├── Formula - │ ├── SyntaxStructure - │ │ ├── SyntaxTree - │ │ │ ├── SyntaxLeaf - │ │ │ │ ├── AbstractAtom - │ │ │ │ │ └── Atom (e.g., p) - │ │ │ │ └── Truth - │ │ │ │ ├── BooleanTruth (⊤ and ⊥) - │ │ │ │ └── ... - │ │ │ └── AbstractSyntaxBranch - │ │ │ │ └── SyntaxBranch (e.g., p ∧ q) - │ │ ├── LinearForm - │ │ │ └── LeftmostLinearForm (e.g., conjunctions, disjunctions, DNFs, CNFs) - │ │ ├── Literal (e.g., p, ¬p) - │ │ └── ... - │ ├── TruthTable - │ ├── AnchoredFormula - │ └── ... - └── Connective - ├── NamedConnective (e.g., ∧, ∨, →, ¬, □, ◊) - ├── AbstractRelationalConnective - │ ├── DiamondRelationalConnective (e.g., ⟨G⟩) - │ ├── BoxRelationalConnective (e.g., [G]) - │ └── ... - └── ... -=# - -############################################################################################ -#### Atom ################################################################################## -############################################################################################ - -""" - struct Atom{V} <: AbstractAtom - value::V - end - -Simplest atom implementation, wrapping a `value`. - -See also [`AbstractAtom`](@ref), [`value`](@ref), [`check`](@ref), -[`SyntaxToken`](@ref). -""" -struct Atom{V} <: AbstractAtom - value::V - - function Atom{V}(value::V) where {V} - @assert !(value isa Union{Formula, Connective}) "Illegal nesting. "* - "Cannot instantiate Atom with value of type $(typeof(value))" - new{V}(value) - end - function Atom(value::V) where {V} - Atom{V}(value) - end - function Atom{V}(p::Atom) where {V} - Atom{V}(value(p)) - end - function Atom(p::Atom) - p - end -end - -valuetype(::Atom{V}) where {V} = V -valuetype(::Type{Atom{V}}) where {V} = V - -value(p::Atom) = p.value - -dual(p::Atom) = Atom(dual(value(p))) -hasdual(p::Atom) = hasdual(value(p)) -hasdual(value) = false -dual(value) = error("Please, provide method SoleLogics.dual(::$(typeof(value))).") # TODO explain why? - -Base.convert(::Type{A}, p::Atom) where {A <: Atom} = A(p) -Base.convert(::Type{A}, a) where {A <: Atom} = A(a) - -Base.isequal(a::Atom, b::Atom) = Base.isequal(value(a), value(b)) # Needed to avoid infinite recursion -Base.isequal(a::Atom, b) = Base.isequal(value(a), b) -Base.isequal(a, b::Atom) = Base.isequal(a, value(b)) -Base.isequal(a::Atom, b::SyntaxTree) = (a == b) # Needed for resolving ambiguities -Base.isequal(a::SyntaxTree, b::Atom) = (a == b) # Needed for resolving ambiguities -Base.hash(a::Atom) = Base.hash(value(a)) - -syntaxstring(a::Atom; kwargs...)::String = syntaxstring(value(a); kwargs...) - -syntaxstring(value; kwargs...) = string(value) - -############################################################################################ -#### SyntaxBranch ########################################################################## -############################################################################################ - -""" - struct 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-repl -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`](@ref), [`children`](@ref), -[`arity`](@ref), -[`Connective`](@ref), -[`height`](@ref), -[`atoms`](@ref), [`natoms`](@ref), -[`operators`](@ref), [`noperators`](@ref), -[`tokens`](@ref), [`ntokens`](@ref), -""" -struct SyntaxBranch <: AbstractSyntaxBranch - - # The syntax token at the current node - token::Connective - - # The child nodes of the current node - children::NTuple{N, SyntaxTree} where {N} - - function _aritycheck(N, token, children) - @assert arity(token)==N "Cannot instantiate SyntaxBranch with token "* - "$(token) of arity $(arity(token)) and $(N) children." - return nothing - end - - function SyntaxBranch( - token::Connective, - children::NTuple{N, SyntaxTree} = (), - ) where {N} - _aritycheck(N, token, children) - return new(token, children) - end - - # Helpers - function SyntaxBranch(token::Connective, children...) - return SyntaxBranch(token, children) - end -end - -children(φ::SyntaxBranch) = φ.children -token(φ::SyntaxBranch) = φ.token - -################################################################################ -################################################################################ - -""" - collatetruth(c::Connective, ts::NTuple{N,T where T<:Truth})::Truth where {N} - -Return the truth value for a composed formula `c(t1, ..., tN)`, given the `N` -with t1, ..., tN being `Truth` values. - -See also [`simplify`](@ref), [`Connective`](@ref), [`Truth`](@ref). -""" -function collatetruth( - c::Connective, - ts::NTuple{N, T where T <: Truth}, -)::Truth where {N} - if arity(c) != length(ts) - return error("Cannot collate $(length(ts)) truth values for " * - "connective $(typeof(c)) with arity $(arity(c))).") - else - return error("Please, provide method collatetruth(::$(typeof(c)), " * - "::NTuple{$(arity(c)),$(T)}).") - end -end - -# Helper (so that collatetruth work for all operators) -collatetruth(t::Truth, ::Tuple{}) = t - -# With generic formulas, it composes formula -""" - simplify(c::Connective, ts::NTuple{N,F where F<:Formula})::Truth where {N} - -Return a formula with the same semantics of a composed formula `c(φ1, ..., φN)`, -given the `N` -immediate sub-formulas. - -See also [`collatetruth`](@ref), [`Connective`](@ref), [`Formula`](@ref). -""" -function simplify(c::Connective, φs::NTuple{N, T where T <: Formula}) where {N} - c(φs) -end - -function simplify(c::Connective, φs::NTuple{N, T where T <: Truth}) where {N} - collatetruth(c, φs) -end - -############################################################################################ -##################################### BASE CONNECTIVES ##################################### -############################################################################################ - -""" - struct NamedConnective{Symbol} <: Connective end - -A singleton type for representing connectives defined by a name or a symbol. - -# Examples -The AND connective (i.e., the logical conjunction) is defined as the subtype: - - const CONJUNCTION = NamedConnective{:∧}() - const ∧ = CONJUNCTION - arity(::typeof(∧)) = 2 - -See also [`NEGATION`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref), -[`IMPLICATION`](@ref), [`Connective`](@ref). -""" -struct NamedConnective{Symbol} <: Connective end - -name(::NamedConnective{S}) where {S} = S - -Base.show(io::IO, c::NamedConnective) = print(io, "$(syntaxstring(c))") - -syntaxstring(c::NamedConnective; kwargs...) = string(name(c)) - -function precedence(c::NamedConnective) - op = SoleLogics.name(c) - # Using default Base.operator_precedence is risky. For example, - # Base.isoperator(:(¬)) is true, but Base.operator_precedence(:(¬)) is 0. - # See Base.operator_precedence documentation. - if !Base.isoperator(op) || Base.operator_precedence(op) == 0 - error("Please, provide method SoleLogics.precedence(::$(typeof(c))).") - else - Base.operator_precedence(op) - end -end - -function associativity(c::NamedConnective) - op = SoleLogics.name(c) - # Base.isoperator(:(++)) is true, but Base.operator_precedence(:(++)) is :none - if !Base.isoperator(op) || !(Base.operator_associativity(op) in [:left, :right]) - error("Please, provide method SoleLogics.associativity(::$(typeof(c))).") - else - Base.operator_associativity(op) - end -end - -doc_NEGATION = """ - const NEGATION = NamedConnective{:¬}() - const ¬ = NEGATION - arity(::typeof(¬)) = 1 - -Logical negation (also referred to as complement). -It can be typed by `\\neg`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_NEGATION)""" -const NEGATION = NamedConnective{:¬}() -"""$(doc_NEGATION)""" -const ¬ = NEGATION -arity(::typeof(¬)) = 1 - -# ¬ is a risky symbol, since by default it's precedence is defaulted to 0 by julia. -# Because of this, we override Base.operator_precedence. -precedence(::typeof(¬)) = Base.operator_precedence(:∧) + 1 - -doc_CONJUNCTION = """ - const CONJUNCTION = NamedConnective{:∧}() - const ∧ = CONJUNCTION - arity(::typeof(∧)) = 2 - -Logical conjunction. -It can be typed by `\\wedge`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_CONJUNCTION)""" -const CONJUNCTION = NamedConnective{:∧}() -"""$(doc_CONJUNCTION)""" -const ∧ = CONJUNCTION -arity(::typeof(∧)) = 2 - -doc_DISJUNCTION = """ - const DISJUNCTION = NamedConnective{:∨}() - const ∨ = DISJUNCTION - arity(::typeof(∨)) = 2 - -Logical disjunction. -It can be typed by `\\vee`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_DISJUNCTION)""" -const DISJUNCTION = NamedConnective{:∨}() -"""$(doc_DISJUNCTION)""" -const ∨ = DISJUNCTION -arity(::typeof(∨)) = 2 - -doc_IMPLICATION = """ - const IMPLICATION = NamedConnective{:→}() - const → = IMPLICATION - arity(::typeof(→)) = 2 - -Logical implication. -It can be typed by `\\to`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_IMPLICATION)""" -const IMPLICATION = NamedConnective{:→}() -"""$(doc_IMPLICATION)""" -const → = IMPLICATION -arity(::typeof(→)) = 2 - -iscommutative(::typeof(∧)) = true -iscommutative(::typeof(→)) = false -iscommutative(::typeof(∨)) = true - -hasdual(::typeof(∧)) = true -dual(c::typeof(∧)) = typeof(∨) -hasdual(::typeof(∨)) = true -dual(c::typeof(∨)) = typeof(∧) - -############################################################################################ -###################################### BOOLEAN ALGEBRA ##################################### -############################################################################################ - -""" - struct BooleanTruth <: Truth - flag::Bool - end - -Structure for representing the Boolean truth values ⊤ and ⊥. -It wraps a flag which takes value `true` for ⊤ ([`TOP`](@ref)), -and `false` for ⊥ ([`BOT`](@ref)) - -See also [`BooleanAlgebra`](@ref). -""" -struct BooleanTruth <: Truth - flag::Bool -end - -istop(t::BooleanTruth) = t.flag -isbot(t::BooleanTruth) = !istop(t) - -syntaxstring(t::BooleanTruth; kwargs...) = istop(t) ? "⊤" : "⊥" - -function Base.show(io::IO, φ::BooleanTruth) - print(io, "$(syntaxstring(φ))") -end - -doc_TOP = """ - const TOP = BooleanTruth(true) - const ⊤ = TOP - -Canonical truth operator representing the value `true`. -It can be typed by `\\top`. - -See also [`BOT`](@ref), [`Truth`](@ref). -""" -"""$(doc_TOP)""" -const TOP = BooleanTruth(true) -"""$(doc_TOP)""" -const ⊤ = TOP - -doc_BOTTOM = """ - const BOT = BooleanTruth(false) - const ⊥ = BOT - -Canonical truth operator representing the value `false`. -It can be typed by `\\bot`. - -See also [`TOP`](@ref), [`Truth`](@ref). -""" -"""$(doc_BOTTOM)""" -const BOT = BooleanTruth(false) -"""$(doc_BOTTOM)""" -const ⊥ = BOT - -# NOTE: it could be useful to provide a macro to easily create -# a new set of Truth types. In particular, a new subtree of types must be planted -# as children of Truth, and new promotion rules are to be defined like below. -Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:BooleanTruth}) = BooleanTruth - -function Base.convert(::Type{BooleanTruth}, t::Bool)::BooleanTruth - return (t ? TOP : BOT) -end -function Base.convert(::Type{BooleanTruth}, t::Integer)::BooleanTruth - if isone(t) - return TOP - elseif iszero(t) - return BOT - else - return error("Cannot interpret Integer value $t as BooleanTruth.") - end -end - -Base.convert(::Type{Truth}, t::Bool) = Base.convert(BooleanTruth, t) -Base.convert(::Type{Truth}, t::Integer) = Base.convert(BooleanTruth, t) - -# NOTE: are these useful? -hasdual(::BooleanTruth) = true -dual(c::BooleanTruth) = BooleanTruth(!istop(c)) - -precedes(t1::BooleanTruth, t2::BooleanTruth) = istop(t1) < istop(t2) -truthmeet(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t1 : t2 -truthjoin(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t2 : t1 - -""" - struct BooleanAlgebra <: AbstractAlgebra{Bool} end - -A [Boolean algebra](https://en.wikipedia.org/wiki/Boolean_algebra), defined on the values -TOP (representing *truth*) and BOT (for bottom, representing *falsehood*). -For this algebra, the basic operators negation, -conjunction and disjunction (stylized as ¬, ∧, ∨) can be defined as the complement, minimum -and maximum, of the integer cast of `true` and `false`, respectively. - -See also [`Truth`](@ref). -""" -struct BooleanAlgebra <: AbstractAlgebra{BooleanTruth} end - -domain(::BooleanAlgebra) = [TOP, BOT] - -top(::BooleanAlgebra) = TOP -bot(::BooleanAlgebra) = BOT - -############################################################################################ - -# Standard semantics for NOT, AND, OR, IMPLIES -collatetruth(::typeof(¬), (ts,)::Tuple{BooleanTruth}) = istop(ts) ? BOT : TOP -function collatetruth(::typeof(∧), (t1, t2)::NTuple{N, T where T <: BooleanTruth}) where {N} - truthmeet(t1, t2) -end -function collatetruth(::typeof(∨), (t1, t2)::NTuple{N, T where T <: BooleanTruth}) where {N} - truthjoin(t1, t2) -end - -# Incomplete information -function simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth, BooleanTruth}) - istop(t1) && istop(t2) ? TOP : BOT -end -simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth, Formula}) = istop(t1) ? t2 : t1 -simplify(::typeof(∧), (t1, t2)::Tuple{Formula, BooleanTruth}) = istop(t2) ? t1 : t2 - -function simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth, BooleanTruth}) - isbot(t1) && isbot(t2) ? BOT : TOP -end -simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth, Formula}) = isbot(t1) ? t2 : t1 -simplify(::typeof(∨), (t1, t2)::Tuple{Formula, BooleanTruth}) = isbot(t2) ? t1 : t2 - -# The IMPLIES operator, →, falls back to using ¬ and ∨ -function collatetruth(::typeof(→), (t1, t2)::NTuple{2, BooleanTruth}) - return collatetruth(∨, (collatetruth(¬, (t1,)), t2)) -end - -############################################################################################ - -# With dense, discrete algebras, floats can be used. -# These are sketches for a few ideas. Note that truth values should be wrapped into Truth substructures: -# istop(ts::AbstractFloat)::Bool = isone(ts) -# isbot(ts::AbstractFloat)::Bool = iszero(ts) - -# # TODO idea: use full range for numbers! -# # istop(ts::AbstractFloat)::Bool = ts == typemax(typeof(ts)) -# # isbot(ts::AbstractFloat)::Bool = ts == typemin(typeof(ts)) -# istop(ts::Integer)::Bool = ts == typemax(typeof(ts)) -# isbot(ts::Integer)::Bool = ts == typemin(typeof(ts)) - -# TODO: -# struct DiscreteChainAlgebra{T} <: AbstractAlgebra{T} domain::Vector{T} end -# struct DenseChainAlgebra{T<:AbstractFloat} <: AbstractAlgebra{T} end - -# TODO: -# struct HeytingNode{T} end -# struct HeytingAlgebra{T} <: AbstractAlgebra{HeytingNode{T}} ... end - -############################################################################################ -########################################### LOGIC ########################################## -############################################################################################ - -""" - struct BaseLogic{G<:AbstractGrammar,A<:AbstractAlgebra} <: AbstractLogic{G,A} - grammar::G - algebra::A - end - -A basic logic based on a grammar and an algebra, where both the grammar and the algebra -are instantiated. - -See also [`grammar`](@ref), [`algebra`](@ref), -[`AbstractGrammar`](@ref), [`AbstractAlgebra`](@ref), [`AbstractLogic`](@ref). -""" -struct BaseLogic{G <: AbstractGrammar, A <: AbstractAlgebra} <: AbstractLogic{G, A} - grammar::G - algebra::A - - function BaseLogic{G, A}( - grammar::G = BASE_GRAMMAR, - algebra::A = BooleanAlgebra(), - ) where {G <: AbstractGrammar, A <: AbstractAlgebra} - # @assert all([goeswith(c, algebra) for c in operators(grammar)]) "Cannot instantiate BaseLogic{$(G),$(A)}: operators $(operators(grammar)[[goeswith(c, algebra) for c in operators(grammar)]]) cannot be interpreted on $(algebra)." # requires `goeswith` trait - return new{G, A}(grammar, algebra) - end - - function BaseLogic{G}( - grammar::G = BASE_GRAMMAR, - algebra::A = BooleanAlgebra(), - ) where {G <: AbstractGrammar, A <: AbstractAlgebra} - return BaseLogic{G, A}(grammar, algebra) - end - - function BaseLogic( - grammar::G = BASE_GRAMMAR, - algebra::A = BooleanAlgebra(), - ) where {G <: AbstractGrammar, A <: AbstractAlgebra} - return BaseLogic{G, A}(grammar, algebra) - end -end - -grammar(l::BaseLogic) = l.grammar -algebra(l::BaseLogic) = l.algebra - -function Base.isequal(a::BaseLogic, b::BaseLogic) - return Base.isequal(grammar(a), grammar(b)) && Base.isequal(algebra(a), algebra(b)) -end - -Base.hash(a::BaseLogic) = Base.hash(algebra(a), Base.hash(grammar(a))) - -function Base.show( - io::IO, l::BaseLogic{G, A},) where {G <: AbstractGrammar, A <: AbstractAlgebra} - if G <: CompleteFlatGrammar - print(io, - "BaseLogic with:\n\t- operators = [$(join(syntaxstring.(operators(l)), ", "))];\n\t- alphabet: $(alphabet(l));\n\t- algebra: $(algebra(l)).",) - else - print(io, - "BaseLogic{$(G),$(A)}(\n\t- grammar: $(grammar(l));\n\t- algebra: $(algebra(l))\n)",) - end -end - - - - -############################################################################################ -#### ExplicitAlphabet ###################################################################### -############################################################################################ - -""" - struct ExplicitAlphabet{V} <: AbstractAlphabet{V} - atoms::Vector{Atom{V}} - end - -An alphabet wrapping atoms in a (finite) `Vector`. - -See also [`AbstractAlphabet`](@ref), [`atoms`](@ref). -""" -struct ExplicitAlphabet{V} <: AbstractAlphabet{V} - atoms::Vector{Atom{V}} - - function ExplicitAlphabet{V}(atoms) where {V} - return new{V}(collect(atoms)) - end - - function ExplicitAlphabet(atoms::AbstractVector{Atom{V}}) where {V} - return ExplicitAlphabet{V}(collect(atoms)) - end - - function ExplicitAlphabet(atoms::AbstractVector{V}) where {V} - return ExplicitAlphabet{V}(Atom.(collect(atoms))) - end -end -atoms(a::ExplicitAlphabet) = a.atoms -natoms(a::ExplicitAlphabet) = length(atoms(a)) - -function Base.convert(::Type{AbstractAlphabet}, alphabet::Vector{<:Atom}) - ExplicitAlphabet(alphabet) -end - -############################################################################################ -#### AlphabetOfAny ######################################################################### -############################################################################################ - -""" - struct AlphabetOfAny{V} <: AbstractAlphabet{V} end - -An implicit, infinite alphabet that includes all atoms with values of a subtype of V. - -See also [`AbstractAlphabet`](@ref). -""" -struct AlphabetOfAny{V} <: AbstractAlphabet{V} end -Base.isfinite(::Type{<:AlphabetOfAny}) = false -Base.in(::Atom{PV}, ::AlphabetOfAny{VV}) where {PV, VV} = (PV <: VV) - -############################################################################################ -#### UnionAlphabet ######################################################################### -############################################################################################ - -# Finite alphabet of conditions induced from a set of metaconditions -""" -Alphabet given by the *union* of a number of (sub-)alphabets. - -See also -[`UnboundedScalarAlphabet`](@ref), -[`ScalarCondition`](@ref), -[`ScalarMetaCondition`](@ref). -""" - -struct UnionAlphabet{C, A <: AbstractAlphabet{C}} <: AbstractAlphabet{C} - subalphabets::Vector{A} -end - -subalphabets(a::UnionAlphabet) = a.subalphabets -nsubalphabets(a::UnionAlphabet) = length(subalphabets(a)) - -function Base.show(io::IO, a::UnionAlphabet) - println(io, "$(typeof(a)):") - for sa in subalphabets(a) - Base.show(io, sa) - end -end - -function atoms(a::UnionAlphabet) - return Iterators.flatten(Iterators.map(atoms, subalphabets(a))) -end - -natoms(a::UnionAlphabet) = sum(natoms, subalphabets(a)) - -function Base.in(p::Atom, a::UnionAlphabet) - return any(sa -> Base.in(p, sa), subalphabets(a)) -end - -""" - randatom( - rng::Union{Integer,AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol=:uniform, - subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing - )::Atom - -Sample an atom from a `UnionAlphabet`. By default, the sampling is uniform with respect to the atoms. -However, 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. - -See also [`UnionAlphabet`](@ref). -""" -function randatom( - rng::Union{Integer, AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol = :uniform, - subalphabets_weights::Union{AbstractWeights, AbstractVector{<:Real}, Nothing} = nothing, -)::Atom - - # @show a - @assert atompicking_mode in [:uniform, :uniform_subalphabets, :weighted] "Value for `atompicking_mode` not..." - rng = initrng(rng) - alphs = subalphabets(a) - - if atompicking_mode == :weighted - if isnothing(subalphabets_weights) - error("`:weighted` picking_mode requires weights in `subalphabets_weights` ") - end - @assert length(subalphabets_weights)==length(alphs) "Mismatching numbers of alphabets "* - "($(length(alphs))) and weights ($(length(subalphabets_weights)))." - subalphabets_weights = StatsBase.weights(subalphabets_weights) - pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) - else - subalphabets_weights = begin - # This atomatically excludes subalphabets with empty threshold vector - if atompicking_mode == :uniform_subalphabets - # set the weight of the empty alphabets to zero - weights = Weights(ones(Int, length(alphs))) - weights[natoms.(alphs) == 0] .= 0 - elseif atompicking_mode == :uniform - weights = Weights(natoms.(alphs)) - end - weights - end - pickedalphabet = sample(rng, alphs, subalphabets_weights) - end - # @show a - # @show subalphabets_weights - # @show pickedalphabet - return randatom(rng, pickedalphabet) -end - -############################################################################################ -#### CompleteFlatGrammar ################################################################### -############################################################################################ - -""" - struct CompleteFlatGrammar{V<:AbstractAlphabet,O<:Operator} <: AbstractGrammar{V,O} - alphabet::V - operators::Vector{<:O} - end - -V grammar of all well-formed formulas obtained by the arity-complying composition -of atoms of an alphabet of type `V`, and all operators in `operators`. -With n operators, this grammar has exactly n+1 production rules. -For example, with `operators = [∧,∨]`, the grammar (in Backus-Naur form) is: - - φ ::= p | φ ∧ φ | φ ∨ φ - -with p ∈ alphabet. Note: it is *flat* in the sense that all rules substitute the same -(unique and starting) non-terminal symbol φ. - -See also [`AbstractGrammar`](@ref), [`Operator`](@ref), [`alphabet`](@ref), -[`formulas`](@ref), [`connectives`](@ref), [`operators`](@ref), [`leaves`](@ref). -""" -struct CompleteFlatGrammar{V <: AbstractAlphabet, O <: Operator} <: AbstractGrammar{V, O} - alphabet::V - operators::Vector{<:O} - - function CompleteFlatGrammar{V, O}( - alphabet::V, - operators::Vector{<:O}, - ) where {V <: AbstractAlphabet, O <: Operator} - return new{V, O}(alphabet, operators) - end - - function CompleteFlatGrammar{V}( - alphabet::V, - operators::Vector{<:Operator}, - ) where {V <: AbstractAlphabet} - return new{V, Union{typeof.(operators)...}}( - alphabet, - Vector{Union{typeof.(operators)...}}(operators), - ) - end - - function CompleteFlatGrammar( - alphabet::V, - operators::Vector{<:Operator}, - ) where {V <: AbstractAlphabet} - return new{V, Union{typeof.(operators)...}}( - alphabet, - Vector{Union{typeof.(operators)...}}(operators), - ) - end -end - -alphabet(g::CompleteFlatGrammar) = g.alphabet -operators(g::CompleteFlatGrammar) = g.operators - -""" - connectives(g::AbstractGrammar) - -List all connectives appearing in a grammar. - -See also [`Connective`](@ref), [`nconnectives`](@ref). -""" -function connectives(g::AbstractGrammar)::AbstractVector{Connective} - return filter(!isnullary, operators(g)) -end - -""" - leaves(g::AbstractGrammar) - -List all leaves appearing in a grammar. - -See also [`SyntaxLeaf`](@ref), [`nleaves`](@ref). -""" -function leaves(g::AbstractGrammar) - return [atoms(alphabet(g))..., filter(isnullary, operators(g))...] -end - -# V complete grammar includes any *safe* syntax tree that can be built with -# the grammar token types. -function Base.in(φ::SyntaxTree, g::CompleteFlatGrammar)::Bool - return if token(φ) isa Atom - token(φ) in alphabet(g) - elseif token(φ) isa Operator - if operatorstype(φ) <: operatorstype(g) - true - else - all([Base.in(c, g) for c in children(φ)]) - end - else - false - end -end - -""" - formulas( - g::CompleteFlatGrammar{V,O} where {V,O}; - maxdepth::Integer, - nformulas::Union{Nothing,Integer} = nothing - )::Vector{SyntaxBranch} - -Generate all formulas whose `SyntaxBranch`s that are not taller than a given `maxdepth`. - -See also [`AbstractGrammar`](@ref), [`SyntaxBranch`](@ref). -""" -function formulas( - g::CompleteFlatGrammar{V, O} where {V, O}; - maxdepth::Integer, - nformulas::Union{Nothing, Integer} = nothing, -)::Vector{SyntaxTree} - @assert maxdepth >= 0 - @assert isnothing(nformulas) || nformulas > 0 - # With increasing `depth`, accumulate all formulas of length `depth` by combining all - # formulas of `depth-1` using all non-terminal symbols. - # Stop as soon as `maxdepth` is reached or `nformulas` have been generated. - depth = 0 - cur_formulas = Vector{SyntaxTree}(leaves(g)) - all_formulas = SyntaxTree[cur_formulas...] - while depth < maxdepth && (isnothing(nformulas) || length(all_formulas) < nformulas) - _nformulas = length(all_formulas) - cur_formulas = [] - for op in connectives(g) - for children in Iterators.product(fill(all_formulas, arity(op))...) - if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) - break - end - push!(cur_formulas, SyntaxTree(op, Tuple(children))) - end - if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) - break - end - end - append!(all_formulas, cur_formulas) - depth += 1 - end - return all_formulas -end - -# This dispatches are needed, since ambiguities might arise when choosing between -# in(φ::SyntaxTree, g::SoleLogics.CompleteFlatGrammar) and -# in(p::Atom, g::SoleLogics.AbstractGrammar) -Base.in(p::Atom, g::CompleteFlatGrammar) = Base.in(p, alphabet(g)) -Base.in(op::Truth, g::CompleteFlatGrammar) = (op <: operatorstype(g)) - -############################################################################################ -########################################### BASE ########################################### -############################################################################################ - -# This can be useful for standard phrasing of propositional formulas with string atoms. - -""" - const BASE_CONNECTIVES = [¬, ∧, ∨, →] - -Basic logical operators. - -See also [`NEGATION`](@ref), -[`CONJUNCTION`](@ref), -[`DISJUNCTION`](@ref), -[`IMPLICATION`](@ref), -[`Connective`](@ref). -""" -const BASE_CONNECTIVES = [¬, ∧, ∨, →] -const BaseConnectives = Union{typeof.(BASE_CONNECTIVES)...} - -const BASE_ALPHABET = AlphabetOfAny{String}() - -const BASE_GRAMMAR = CompleteFlatGrammar(BASE_ALPHABET, BASE_CONNECTIVES) -const BASE_ALGEBRA = BooleanAlgebra() - -const BASE_LOGIC = BaseLogic(BASE_GRAMMAR, BASE_ALGEBRA) - -function _baselogic(; - alphabet::Union{Nothing, Vector, AbstractAlphabet} = nothing, - operators::Union{Nothing, Vector{<:Operator}} = nothing, - grammar::Union{Nothing, AbstractGrammar} = nothing, - algebra::Union{Nothing, AbstractAlgebra} = nothing, - default_operators::Vector{<:Operator}, - logictypename::String, -) - if !(isnothing(grammar) || (isnothing(alphabet) && isnothing(operators))) - error("Cannot instantiate $(logictypename) by specifing a grammar " * - "together with argument(s): " * - join( - [ - (!isnothing(alphabet) ? ["alphabet"] : [])..., - (!isnothing(operators) ? ["operators"] : [])..., - (!isnothing(grammar) ? ["grammar"] : [])..., - ], - ", ",) * ".") - end - grammar = begin - if isnothing(grammar) - # @show alphabet - # @show operators - # @show BASE_GRAMMAR - # if isnothing(alphabet) && isnothing(operators) - # BASE_GRAMMAR - # else - alphabet = isnothing(alphabet) ? BASE_ALPHABET : alphabet - operators = begin - if isnothing(operators) - default_operators - else - if length(setdiff(operators, default_operators)) > 0 - @warn "Instantiating $(logictypename) with operators not in " * - "$(default_operators): " * - join(", ", setdiff(operators, default_operators)) * "." - end - operators - end - end - if alphabet isa Vector - alphabet = ExplicitAlphabet(map(Atom, alphabet)) - end - CompleteFlatGrammar(alphabet, operators) - # end - else - @assert isnothing(alphabet) && isnothing(operators) - grammar - end - end - - algebra = isnothing(algebra) ? BASE_ALGEBRA : algebra - - return BaseLogic(grammar, algebra) -end - diff --git a/src/utils/syntactical.jl b/src/utils/syntactical.jl new file mode 100644 index 00000000..fe2e22d6 --- /dev/null +++ b/src/utils/syntactical.jl @@ -0,0 +1,950 @@ +#= + Syntactical Type Hierarchy + + Syntactical + ├── Formula + │ ├── SyntaxStructure + │ │ ├── SyntaxTree + │ │ │ ├── SyntaxLeaf + │ │ │ │ ├── AbstractAtom + │ │ │ │ │ └── Atom (e.g., p) + │ │ │ │ └── Truth + │ │ │ │ ├── BooleanTruth (⊤ and ⊥) + │ │ │ │ └── ... + │ │ │ └── AbstractSyntaxBranch + │ │ │ │ └── SyntaxBranch (e.g., p ∧ q) + │ │ ├── LinearForm + │ │ │ └── LeftmostLinearForm (e.g., conjunctions, disjunctions, DNFs, CNFs) + │ │ ├── Literal (e.g., p, ¬p) + │ │ └── ... + │ ├── TruthTable + │ ├── AnchoredFormula + │ └── ... + └── Connective + ├── NamedConnective (e.g., ∧, ∨, →, ¬, □, ◊) + ├── AbstractRelationalConnective + │ ├── DiamondRelationalConnective (e.g., ⟨G⟩) + │ ├── BoxRelationalConnective (e.g., [G]) + │ └── ... + └── ... +=# + +############################################################################################ +#### Atom ################################################################################## +############################################################################################ + +""" + struct Atom{V} <: AbstractAtom + value::V + end + +Simplest atom implementation, wrapping a `value`. + +See also [`AbstractAtom`](@ref), [`value`](@ref), [`check`](@ref), +[`SyntaxToken`](@ref). +""" +struct Atom{V} <: AbstractAtom + value::V + + function Atom{V}(value::V) where {V} + # @assert !(value isa Union{Formula, Connective}) "Illegal nesting... " + if value isa Union{Formula, Connective} + thow("Illegal nesting. "* + "Cannot instantiate Atom with value of type $(typeof(value))" + ) + end + new{V}(value) + end + function Atom(value::V) where {V} + Atom{V}(value) + end + function Atom{V}(p::Atom) where {V} + Atom{V}(value(p)) + end + function Atom(p::Atom) + p + end +end + +valuetype(::Atom{V}) where {V} = V +valuetype(::Type{Atom{V}}) where {V} = V + +value(p::Atom) = p.value + +dual(p::Atom) = Atom(dual(value(p))) +hasdual(p::Atom) = hasdual(value(p)) +hasdual(value) = false +dual(value) = error("Please, provide method SoleLogics.dual(::$(typeof(value))).") # TODO explain why? + +Base.convert(::Type{A}, p::Atom) where {A <: Atom} = A(p) +Base.convert(::Type{A}, a) where {A <: Atom} = A(a) + +Base.isequal(a::Atom, b::Atom) = Base.isequal(value(a), value(b)) # Needed to avoid infinite recursion +Base.isequal(a::Atom, b) = Base.isequal(value(a), b) +Base.isequal(a, b::Atom) = Base.isequal(a, value(b)) +Base.isequal(a::Atom, b::SyntaxTree) = (a == b) # Needed for resolving ambiguities +Base.isequal(a::SyntaxTree, b::Atom) = (a == b) # Needed for resolving ambiguities +Base.hash(a::Atom) = Base.hash(value(a)) + +syntaxstring(a::Atom; kwargs...)::String = syntaxstring(value(a); kwargs...) + +syntaxstring(value; kwargs...) = string(value) + +############################################################################################ +#### SyntaxBranch ########################################################################## +############################################################################################ + +""" + struct 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-repl +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`](@ref), [`children`](@ref), +[`arity`](@ref), +[`Connective`](@ref), +[`height`](@ref), +[`atoms`](@ref), [`natoms`](@ref), +[`operators`](@ref), [`noperators`](@ref), +[`tokens`](@ref), [`ntokens`](@ref), +""" +struct SyntaxBranch <: AbstractSyntaxBranch + + # The syntax token at the current node + token::Connective + + # The child nodes of the current node + children::NTuple{N, SyntaxTree} where {N} + + function _aritycheck(N, token, children) + if arity(token) != N + throw( + "Cannot instantiate SyntaxBranch with token "* + "$(token) of arity $(arity(token)) and $(N) children." + ) + end + return nothing + end + + function SyntaxBranch( + token::Connective, + children::NTuple{N, SyntaxTree} = (), + ) where {N} + _aritycheck(N, token, children) + return new(token, children) + end + + # Helpers + function SyntaxBranch(token::Connective, children...) + return SyntaxBranch(token, children) + end +end + +children(φ::SyntaxBranch) = φ.children +token(φ::SyntaxBranch) = φ.token + +################################################################################ +################################################################################ + +""" + collatetruth(c::Connective, ts::NTuple{N,T where T<:Truth})::Truth where {N} + +Return the truth value for a composed formula `c(t1, ..., tN)`, given the `N` +with t1, ..., tN being `Truth` values. + +See also [`simplify`](@ref), [`Connective`](@ref), [`Truth`](@ref). +""" +function collatetruth( + c::Connective, + ts::NTuple{N, T where T <: Truth}, +)::Truth where {N} + if arity(c) != length(ts) + return error("Cannot collate $(length(ts)) truth values for " * + "connective $(typeof(c)) with arity $(arity(c))).") + else + return error("Please, provide method collatetruth(::$(typeof(c)), " * + "::NTuple{$(arity(c)),$(T)}).") + end +end + +# Helper (so that collatetruth work for all operators) +collatetruth(t::Truth, ::Tuple{}) = t + +# With generic formulas, it composes formula +""" + simplify(c::Connective, ts::NTuple{N,F where F<:Formula})::Truth where {N} + +Return a formula with the same semantics of a composed formula `c(φ1, ..., φN)`, +given the `N` +immediate sub-formulas. + +See also [`collatetruth`](@ref), [`Connective`](@ref), [`Formula`](@ref). +""" +function simplify(c::Connective, φs::NTuple{N, T where T <: Formula}) where {N} + c(φs) +end + +function simplify(c::Connective, φs::NTuple{N, T where T <: Truth}) where {N} + collatetruth(c, φs) +end + +############################################################################################ +##################################### BASE CONNECTIVES ##################################### +############################################################################################ + +""" + struct NamedConnective{Symbol} <: Connective end + +A singleton type for representing connectives defined by a name or a symbol. + +# Examples +The AND connective (i.e., the logical conjunction) is defined as the subtype: + + const CONJUNCTION = NamedConnective{:∧}() + const ∧ = CONJUNCTION + arity(::typeof(∧)) = 2 + +See also [`NEGATION`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref), +[`IMPLICATION`](@ref), [`Connective`](@ref). +""" +struct NamedConnective{Symbol} <: Connective end + +name(::NamedConnective{S}) where {S} = S + +Base.show(io::IO, c::NamedConnective) = print(io, "$(syntaxstring(c))") + +syntaxstring(c::NamedConnective; kwargs...) = string(name(c)) + +function precedence(c::NamedConnective) + op = SoleLogics.name(c) + # Using default Base.operator_precedence is risky. For example, + # Base.isoperator(:(¬)) is true, but Base.operator_precedence(:(¬)) is 0. + # See Base.operator_precedence documentation. + if !Base.isoperator(op) || Base.operator_precedence(op) == 0 + error("Please, provide method SoleLogics.precedence(::$(typeof(c))).") + else + Base.operator_precedence(op) + end +end + +function associativity(c::NamedConnective) + op = SoleLogics.name(c) + # Base.isoperator(:(++)) is true, but Base.operator_precedence(:(++)) is :none + if !Base.isoperator(op) || !(Base.operator_associativity(op) in [:left, :right]) + error("Please, provide method SoleLogics.associativity(::$(typeof(c))).") + else + Base.operator_associativity(op) + end +end + +doc_NEGATION = """ + const NEGATION = NamedConnective{:¬}() + const ¬ = NEGATION + arity(::typeof(¬)) = 1 + +Logical negation (also referred to as complement). +It can be typed by `\\neg`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_NEGATION)""" +const NEGATION = NamedConnective{:¬}() +"""$(doc_NEGATION)""" +const ¬ = NEGATION +arity(::typeof(¬)) = 1 + +# ¬ is a risky symbol, since by default it's precedence is defaulted to 0 by julia. +# Because of this, we override Base.operator_precedence. +precedence(::typeof(¬)) = Base.operator_precedence(:∧) + 1 + +doc_CONJUNCTION = """ + const CONJUNCTION = NamedConnective{:∧}() + const ∧ = CONJUNCTION + arity(::typeof(∧)) = 2 + +Logical conjunction. +It can be typed by `\\wedge`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_CONJUNCTION)""" +const CONJUNCTION = NamedConnective{:∧}() +"""$(doc_CONJUNCTION)""" +const ∧ = CONJUNCTION +arity(::typeof(∧)) = 2 + +doc_DISJUNCTION = """ + const DISJUNCTION = NamedConnective{:∨}() + const ∨ = DISJUNCTION + arity(::typeof(∨)) = 2 + +Logical disjunction. +It can be typed by `\\vee`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_DISJUNCTION)""" +const DISJUNCTION = NamedConnective{:∨}() +"""$(doc_DISJUNCTION)""" +const ∨ = DISJUNCTION +arity(::typeof(∨)) = 2 + +doc_IMPLICATION = """ + const IMPLICATION = NamedConnective{:→}() + const → = IMPLICATION + arity(::typeof(→)) = 2 + +Logical implication. +It can be typed by `\\to`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_IMPLICATION)""" +const IMPLICATION = NamedConnective{:→}() +"""$(doc_IMPLICATION)""" +const → = IMPLICATION +arity(::typeof(→)) = 2 + +iscommutative(::typeof(∧)) = true +iscommutative(::typeof(→)) = false +iscommutative(::typeof(∨)) = true + +hasdual(::typeof(∧)) = true +dual(c::typeof(∧)) = typeof(∨) +hasdual(::typeof(∨)) = true +dual(c::typeof(∨)) = typeof(∧) + +############################################################################################ +###################################### BOOLEAN ALGEBRA ##################################### +############################################################################################ + +""" + struct BooleanTruth <: Truth + flag::Bool + end + +Structure for representing the Boolean truth values ⊤ and ⊥. +It wraps a flag which takes value `true` for ⊤ ([`TOP`](@ref)), +and `false` for ⊥ ([`BOT`](@ref)) + +See also [`BooleanAlgebra`](@ref). +""" +struct BooleanTruth <: Truth + flag::Bool +end + +istop(t::BooleanTruth) = t.flag +isbot(t::BooleanTruth) = !istop(t) + +syntaxstring(t::BooleanTruth; kwargs...) = istop(t) ? "⊤" : "⊥" + +function Base.show(io::IO, φ::BooleanTruth) + print(io, "$(syntaxstring(φ))") +end + +doc_TOP = """ + const TOP = BooleanTruth(true) + const ⊤ = TOP + +Canonical truth operator representing the value `true`. +It can be typed by `\\top`. + +See also [`BOT`](@ref), [`Truth`](@ref). +""" +"""$(doc_TOP)""" +const TOP = BooleanTruth(true) +"""$(doc_TOP)""" +const ⊤ = TOP + +doc_BOTTOM = """ + const BOT = BooleanTruth(false) + const ⊥ = BOT + +Canonical truth operator representing the value `false`. +It can be typed by `\\bot`. + +See also [`TOP`](@ref), [`Truth`](@ref). +""" +"""$(doc_BOTTOM)""" +const BOT = BooleanTruth(false) +"""$(doc_BOTTOM)""" +const ⊥ = BOT + +# NOTE: it could be useful to provide a macro to easily create +# a new set of Truth types. In particular, a new subtree of types must be planted +# as children of Truth, and new promotion rules are to be defined like below. +Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:BooleanTruth}) = BooleanTruth + +function Base.convert(::Type{BooleanTruth}, t::Bool)::BooleanTruth + return (t ? TOP : BOT) +end +function Base.convert(::Type{BooleanTruth}, t::Integer)::BooleanTruth + if isone(t) + return TOP + elseif iszero(t) + return BOT + else + return error("Cannot interpret Integer value $t as BooleanTruth.") + end +end + +Base.convert(::Type{Truth}, t::Bool) = Base.convert(BooleanTruth, t) +Base.convert(::Type{Truth}, t::Integer) = Base.convert(BooleanTruth, t) + +# NOTE: are these useful? +hasdual(::BooleanTruth) = true +dual(c::BooleanTruth) = BooleanTruth(!istop(c)) + +precedes(t1::BooleanTruth, t2::BooleanTruth) = istop(t1) < istop(t2) +truthmeet(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t1 : t2 +truthjoin(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t2 : t1 + + + +# """ +# struct BooleanAlgebra <: AbstractAlgebra{Bool} end + +# A [Boolean algebra](https://en.wikipedia.org/wiki/Boolean_algebra), defined on the values +# TOP (representing *truth*) and BOT (for bottom, representing *falsehood*). +# For this algebra, the basic operators negation, +# conjunction and disjunction (stylized as ¬, ∧, ∨) can be defined as the complement, minimum +# and maximum, of the integer cast of `true` and `false`, respectively. + +# See also [`Truth`](@ref). +# """ +# struct BooleanAlgebra <: AbstractAlgebra{BooleanTruth} end + +# domain(::BooleanAlgebra) = [TOP, BOT] + +# top(::BooleanAlgebra) = TOP +# bot(::BooleanAlgebra) = BOT + +# ############################################################################################ + +# # Standard semantics for NOT, AND, OR, IMPLIES +# collatetruth(::typeof(¬), (ts,)::Tuple{BooleanTruth}) = istop(ts) ? BOT : TOP +# function collatetruth(::typeof(∧), (t1, t2)::NTuple{N, T where T <: BooleanTruth}) where {N} +# truthmeet(t1, t2) +# end +# function collatetruth(::typeof(∨), (t1, t2)::NTuple{N, T where T <: BooleanTruth}) where {N} +# truthjoin(t1, t2) +# end + +# # Incomplete information +# function simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth, BooleanTruth}) +# istop(t1) && istop(t2) ? TOP : BOT +# end +# simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth, Formula}) = istop(t1) ? t2 : t1 +# simplify(::typeof(∧), (t1, t2)::Tuple{Formula, BooleanTruth}) = istop(t2) ? t1 : t2 + +# function simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth, BooleanTruth}) +# isbot(t1) && isbot(t2) ? BOT : TOP +# end +# simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth, Formula}) = isbot(t1) ? t2 : t1 +# simplify(::typeof(∨), (t1, t2)::Tuple{Formula, BooleanTruth}) = isbot(t2) ? t1 : t2 + +# # The IMPLIES operator, →, falls back to using ¬ and ∨ +# function collatetruth(::typeof(→), (t1, t2)::NTuple{2, BooleanTruth}) +# return collatetruth(∨, (collatetruth(¬, (t1,)), t2)) +# end + +############################################################################################ + +# With dense, discrete algebras, floats can be used. +# These are sketches for a few ideas. Note that truth values should be wrapped into Truth substructures: +# istop(ts::AbstractFloat)::Bool = isone(ts) +# isbot(ts::AbstractFloat)::Bool = iszero(ts) + +# # TODO idea: use full range for numbers! +# # istop(ts::AbstractFloat)::Bool = ts == typemax(typeof(ts)) +# # isbot(ts::AbstractFloat)::Bool = ts == typemin(typeof(ts)) +# istop(ts::Integer)::Bool = ts == typemax(typeof(ts)) +# isbot(ts::Integer)::Bool = ts == typemin(typeof(ts)) + +# TODO: +# struct DiscreteChainAlgebra{T} <: AbstractAlgebra{T} domain::Vector{T} end +# struct DenseChainAlgebra{T<:AbstractFloat} <: AbstractAlgebra{T} end + +# TODO: +# struct HeytingNode{T} end +# struct HeytingAlgebra{T} <: AbstractAlgebra{HeytingNode{T}} ... end + +############################################################################################ +########################################### LOGIC ########################################## +############################################################################################ + +# """ +# struct BaseLogic{G<:AbstractGrammar,A<:AbstractAlgebra} <: AbstractLogic{G,A} +# grammar::G +# algebra::A +# end + +# A basic logic based on a grammar and an algebra, where both the grammar and the algebra +# are instantiated. + +# See also [`grammar`](@ref), [`algebra`](@ref), +# [`AbstractGrammar`](@ref), [`AbstractAlgebra`](@ref), [`AbstractLogic`](@ref). +# """ +# struct BaseLogic{G <: AbstractGrammar, A <: AbstractAlgebra} <: AbstractLogic{G, A} +# grammar::G +# algebra::A + +# function BaseLogic{G, A}( +# grammar::G = BASE_GRAMMAR, +# algebra::A = BooleanAlgebra(), +# ) where {G <: AbstractGrammar, A <: AbstractAlgebra} +# # @assert all([goeswith(c, algebra) for c in operators(grammar)]) "Cannot instantiate BaseLogic{$(G),$(A)}: operators $(operators(grammar)[[goeswith(c, algebra) for c in operators(grammar)]]) cannot be interpreted on $(algebra)." # requires `goeswith` trait +# return new{G, A}(grammar, algebra) +# end + +# function BaseLogic{G}( +# grammar::G = BASE_GRAMMAR, +# algebra::A = BooleanAlgebra(), +# ) where {G <: AbstractGrammar, A <: AbstractAlgebra} +# return BaseLogic{G, A}(grammar, algebra) +# end + +# function BaseLogic( +# grammar::G = BASE_GRAMMAR, +# algebra::A = BooleanAlgebra(), +# ) where {G <: AbstractGrammar, A <: AbstractAlgebra} +# return BaseLogic{G, A}(grammar, algebra) +# end +# end + +# grammar(l::BaseLogic) = l.grammar +# algebra(l::BaseLogic) = l.algebra + +# function Base.isequal(a::BaseLogic, b::BaseLogic) +# return Base.isequal(grammar(a), grammar(b)) && Base.isequal(algebra(a), algebra(b)) +# end + +# Base.hash(a::BaseLogic) = Base.hash(algebra(a), Base.hash(grammar(a))) + +# function Base.show( +# io::IO, l::BaseLogic{G, A},) where {G <: AbstractGrammar, A <: AbstractAlgebra} +# if G <: CompleteFlatGrammar +# print(io, +# "BaseLogic with:\n\t- operators = [$(join(syntaxstring.(operators(l)), ", "))];\n\t- alphabet: $(alphabet(l));\n\t- algebra: $(algebra(l)).",) +# else +# print(io, +# "BaseLogic{$(G),$(A)}(\n\t- grammar: $(grammar(l));\n\t- algebra: $(algebra(l))\n)",) +# end +# end + + +############################################################################################ +########################################### BASE ########################################### +############################################################################################ + +# This can be useful for standard phrasing of propositional formulas with string atoms. + +# """ +# const BASE_CONNECTIVES = [¬, ∧, ∨, →] + +# Basic logical operators. + +# See also [`NEGATION`](@ref), +# [`CONJUNCTION`](@ref), +# [`DISJUNCTION`](@ref), +# [`IMPLICATION`](@ref), +# [`Connective`](@ref). +# """ +# const BASE_CONNECTIVES = [¬, ∧, ∨, →] +# const BaseConnectives = Union{typeof.(BASE_CONNECTIVES)...} + +# const BASE_ALPHABET = AlphabetOfAny{String}() + +# const BASE_GRAMMAR = CompleteFlatGrammar(BASE_ALPHABET, BASE_CONNECTIVES) +# const BASE_ALGEBRA = BooleanAlgebra() + +# const BASE_LOGIC = BaseLogic(BASE_GRAMMAR, BASE_ALGEBRA) + +# function _baselogic(; +# alphabet::Union{Nothing, Vector, AbstractAlphabet} = nothing, +# operators::Union{Nothing, Vector{<:Operator}} = nothing, +# grammar::Union{Nothing, AbstractGrammar} = nothing, +# algebra::Union{Nothing, AbstractAlgebra} = nothing, +# default_operators::Vector{<:Operator}, +# logictypename::String, +# ) +# if !(isnothing(grammar) || (isnothing(alphabet) && isnothing(operators))) +# error("Cannot instantiate $(logictypename) by specifing a grammar " * +# "together with argument(s): " * +# join( +# [ +# (!isnothing(alphabet) ? ["alphabet"] : [])..., +# (!isnothing(operators) ? ["operators"] : [])..., +# (!isnothing(grammar) ? ["grammar"] : [])..., +# ], +# ", ",) * ".") +# end +# grammar = begin +# if isnothing(grammar) +# # @show alphabet +# # @show operators +# # @show BASE_GRAMMAR +# # if isnothing(alphabet) && isnothing(operators) +# # BASE_GRAMMAR +# # else +# alphabet = isnothing(alphabet) ? BASE_ALPHABET : alphabet +# operators = begin +# if isnothing(operators) +# default_operators +# else +# if length(setdiff(operators, default_operators)) > 0 +# @warn "Instantiating $(logictypename) with operators not in " * +# "$(default_operators): " * +# join(", ", setdiff(operators, default_operators)) * "." +# end +# operators +# end +# end +# if alphabet isa Vector +# alphabet = ExplicitAlphabet(map(Atom, alphabet)) +# end +# CompleteFlatGrammar(alphabet, operators) +# # end +# else +# # @assert isnothing(alphabet) && isnothing(operators) +# if !isnothing(alphabet) || !isnothing(operators) +# throw("Errore TODO ...") +# end +# grammar +# end +# end + +# algebra = isnothing(algebra) ? BASE_ALGEBRA : algebra + +# return BaseLogic(grammar, algebra) +# end + +############################################################################################ +#### ExplicitAlphabet ###################################################################### +############################################################################################ + +# """ +# struct ExplicitAlphabet{V} <: AbstractAlphabet{V} +# atoms::Vector{Atom{V}} +# end + +# An alphabet wrapping atoms in a (finite) `Vector`. + +# See also [`AbstractAlphabet`](@ref), [`atoms`](@ref). +# """ +# struct ExplicitAlphabet{V} <: AbstractAlphabet{V} +# atoms::Vector{Atom{V}} + +# function ExplicitAlphabet{V}(atoms) where {V} +# return new{V}(collect(atoms)) +# end + +# function ExplicitAlphabet(atoms::AbstractVector{Atom{V}}) where {V} +# return ExplicitAlphabet{V}(collect(atoms)) +# end + +# function ExplicitAlphabet(atoms::AbstractVector{V}) where {V} +# return ExplicitAlphabet{V}(Atom.(collect(atoms))) +# end +# end +# atoms(a::ExplicitAlphabet) = a.atoms +# natoms(a::ExplicitAlphabet) = length(atoms(a)) + +# function Base.convert(::Type{AbstractAlphabet}, alphabet::Vector{<:Atom}) +# ExplicitAlphabet(alphabet) +# end + +############################################################################################ +#### AlphabetOfAny ######################################################################### +############################################################################################ + +# """ +# struct AlphabetOfAny{V} <: AbstractAlphabet{V} end + +# An implicit, infinite alphabet that includes all atoms with values of a subtype of V. + +# See also [`AbstractAlphabet`](@ref). +# """ +# struct AlphabetOfAny{V} <: AbstractAlphabet{V} end +# Base.isfinite(::Type{<:AlphabetOfAny}) = false +# Base.in(::Atom{PV}, ::AlphabetOfAny{VV}) where {PV, VV} = (PV <: VV) + +############################################################################################ +#### UnionAlphabet ######################################################################### +############################################################################################ + +## Finite alphabet of conditions induced from a set of metaconditions + +# """ +# Alphabet given by the *union* of a number of (sub-)alphabets. + +# See also +# [`UnboundedScalarAlphabet`](@ref), +# [`ScalarCondition`](@ref), +# [`ScalarMetaCondition`](@ref). +# """ + +# struct UnionAlphabet{C, A <: AbstractAlphabet{C}} <: AbstractAlphabet{C} +# subalphabets::Vector{A} +# end + +# subalphabets(a::UnionAlphabet) = a.subalphabets +# nsubalphabets(a::UnionAlphabet) = length(subalphabets(a)) + +# function Base.show(io::IO, a::UnionAlphabet) +# println(io, "$(typeof(a)):") +# for sa in subalphabets(a) +# Base.show(io, sa) +# end +# end + +# function atoms(a::UnionAlphabet) +# return Iterators.flatten(Iterators.map(atoms, subalphabets(a))) +# end + +# natoms(a::UnionAlphabet) = sum(natoms, subalphabets(a)) + +# function Base.in(p::Atom, a::UnionAlphabet) +# return any(sa -> Base.in(p, sa), subalphabets(a)) +# end + +# """ +# randatom( +# rng::Union{Integer,AbstractRNG}, +# a::UnionAlphabet; +# atompicking_mode::Symbol=:uniform, +# subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing +# )::Atom + +# Sample an atom from a `UnionAlphabet`. By default, the sampling is uniform with respect to the atoms. +# However, 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. + +# See also [`UnionAlphabet`](@ref). +# """ +# function randatom( +# rng::Union{Integer, AbstractRNG}, +# a::UnionAlphabet; +# atompicking_mode::Symbol = :uniform, +# subalphabets_weights::Union{AbstractWeights, AbstractVector{<:Real}, Nothing} = nothing, +# )::Atom + +# # if atompicking_mode ∉ [:uniform, :uniform_subalphabets, :weighted] +# if !(atompicking_mode in [:uniform, :uniform_subalphabets, :weighted]) +# throw("Value for `atompicking_mode` not...") +# end +# rng = initrng(rng) +# alphs = subalphabets(a) + +# if atompicking_mode == :weighted +# if isnothing(subalphabets_weights) +# error("`:weighted` picking_mode requires weights in `subalphabets_weights` ") +# end +# if length(subalphabets_weights)!=length(alphs) +# throw( +# "Mismatching numbers of alphabets "* +# "($(length(alphs))) and weights ($(length(subalphabets_weights)))." +# ) +# end +# subalphabets_weights = StatsBase.weights(subalphabets_weights) +# pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) +# else +# subalphabets_weights = begin +# # This atomatically excludes subalphabets with empty threshold vector +# if atompicking_mode == :uniform_subalphabets +# # set the weight of the empty alphabets to zero +# weights = Weights(ones(Int, length(alphs))) +# weights[natoms.(alphs) == 0] .= 0 +# elseif atompicking_mode == :uniform +# weights = Weights(natoms.(alphs)) +# end +# weights +# end +# pickedalphabet = sample(rng, alphs, subalphabets_weights) +# end + +# return randatom(rng, pickedalphabet) +# end + +############################################################################################ +#### CompleteFlatGrammar ################################################################### +############################################################################################ + +# """ +# struct CompleteFlatGrammar{V<:AbstractAlphabet,O<:Operator} <: AbstractGrammar{V,O} +# alphabet::V +# operators::Vector{<:O} +# end + +# V grammar of all well-formed formulas obtained by the arity-complying composition +# of atoms of an alphabet of type `V`, and all operators in `operators`. +# With n operators, this grammar has exactly n+1 production rules. +# For example, with `operators = [∧,∨]`, the grammar (in Backus-Naur form) is: + +# φ ::= p | φ ∧ φ | φ ∨ φ + +# with p ∈ alphabet. Note: it is *flat* in the sense that all rules substitute the same +# (unique and starting) non-terminal symbol φ. + +# See also [`AbstractGrammar`](@ref), [`Operator`](@ref), [`alphabet`](@ref), +# [`formulas`](@ref), [`connectives`](@ref), [`operators`](@ref), [`leaves`](@ref). +# """ +# struct CompleteFlatGrammar{V <: AbstractAlphabet, O <: Operator} <: AbstractGrammar{V, O} +# alphabet::V +# operators::Vector{<:O} + +# function CompleteFlatGrammar{V, O}( +# alphabet::V, +# operators::Vector{<:O}, +# ) where {V <: AbstractAlphabet, O <: Operator} +# return new{V, O}(alphabet, operators) +# end + +# function CompleteFlatGrammar{V}( +# alphabet::V, +# operators::Vector{<:Operator}, +# ) where {V <: AbstractAlphabet} +# return new{V, Union{typeof.(operators)...}}( +# alphabet, +# Vector{Union{typeof.(operators)...}}(operators), +# ) +# end + +# function CompleteFlatGrammar( +# alphabet::V, +# operators::Vector{<:Operator}, +# ) where {V <: AbstractAlphabet} +# return new{V, Union{typeof.(operators)...}}( +# alphabet, +# Vector{Union{typeof.(operators)...}}(operators), +# ) +# end +# end + +# alphabet(g::CompleteFlatGrammar) = g.alphabet +# operators(g::CompleteFlatGrammar) = g.operators + +# """ +# connectives(g::AbstractGrammar) + +# List all connectives appearing in a grammar. + +# See also [`Connective`](@ref), [`nconnectives`](@ref). +# """ +# function connectives(g::AbstractGrammar)::AbstractVector{Connective} +# return filter(!isnullary, operators(g)) +# end + +# """ +# leaves(g::AbstractGrammar) + +# List all leaves appearing in a grammar. + +# See also [`SyntaxLeaf`](@ref), [`nleaves`](@ref). +# """ +# function leaves(g::AbstractGrammar) +# return [atoms(alphabet(g))..., filter(isnullary, operators(g))...] +# end + +# # V complete grammar includes any *safe* syntax tree that can be built with +# # the grammar token types. +# function Base.in(φ::SyntaxTree, g::CompleteFlatGrammar)::Bool +# return if token(φ) isa Atom +# token(φ) in alphabet(g) +# elseif token(φ) isa Operator +# if operatorstype(φ) <: operatorstype(g) +# true +# else +# all([Base.in(c, g) for c in children(φ)]) +# end +# else +# false +# end +# end + +# """ +# formulas( +# g::CompleteFlatGrammar{V,O} where {V,O}; +# maxdepth::Integer, +# nformulas::Union{Nothing,Integer} = nothing +# )::Vector{SyntaxBranch} + +# Generate all formulas whose `SyntaxBranch`s that are not taller than a given `maxdepth`. + +# See also [`AbstractGrammar`](@ref), [`SyntaxBranch`](@ref). +# """ +# function formulas( +# g::CompleteFlatGrammar{V, O} where {V, O}; +# maxdepth::Integer, +# nformulas::Union{Nothing, Integer} = nothing, +# )::Vector{SyntaxTree} + +# maxdepth >= 0 || throw("maxdepth must be greater than or equal to 0, but got $maxdepth") + +# if !isnothing(nformulas) +# nformulas > 0 || throw("nformulas must be greater than 0, but got $nformulas") +# end +# # With increasing `depth`, accumulate all formulas of length `depth` by combining all +# # formulas of `depth-1` using all non-terminal symbols. +# # Stop as soon as `maxdepth` is reached or `nformulas` have been generated. +# depth = 0 +# cur_formulas = Vector{SyntaxTree}(leaves(g)) +# all_formulas = SyntaxTree[cur_formulas...] +# while depth < maxdepth && (isnothing(nformulas) || length(all_formulas) < nformulas) +# _nformulas = length(all_formulas) +# cur_formulas = [] +# for op in connectives(g) +# for children in Iterators.product(fill(all_formulas, arity(op))...) +# if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) +# break +# end +# push!(cur_formulas, SyntaxTree(op, Tuple(children))) +# end +# if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) +# break +# end +# end +# append!(all_formulas, cur_formulas) +# depth += 1 +# end +# return all_formulas +# end + +# # This dispatches are needed, since ambiguities might arise when choosing between +# # in(φ::SyntaxTree, g::SoleLogics.CompleteFlatGrammar) and +# # in(p::Atom, g::SoleLogics.AbstractGrammar) +# Base.in(p::Atom, g::CompleteFlatGrammar) = Base.in(p, alphabet(g)) +# Base.in(op::Truth, g::CompleteFlatGrammar) = (op <: operatorstype(g)) From 0702d1ea9fe04f0101820e746e604196919a49d3 Mon Sep 17 00:00:00 2001 From: Michele Ghiotti Date: Wed, 16 Oct 2024 09:37:59 +0200 Subject: [PATCH 44/90] Extract utilities in utils/propositional-logic.jl, extended documentation for interpret function --- src/types/propositional-logic.jl | 35 ++++++++++++++++--- src/utils/propositional-logic.jl | 58 ++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/types/propositional-logic.jl b/src/types/propositional-logic.jl index 6a0f7452..ae1c0c21 100644 --- a/src/types/propositional-logic.jl +++ b/src/types/propositional-logic.jl @@ -65,7 +65,7 @@ encoding mappings from `Atom`s to `Truth` values. # Interface - `Base.haskey(i::AbstractAssignment, ::AbstractAtom)::Bool` -- `inlinedisplay(i::AbstractAssignment)` +- `inlinedisplay(i::AbstractAssignment)::String` - `interpret(a::AbstractAtom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf` See also [`AbstractInterpretation`](@ref). @@ -74,7 +74,6 @@ abstract type AbstractAssignment <: AbstractInterpretation end """ Base.haskey(i::AbstractAssignment, ::AbstractAtom)::Bool - Base.haskey(i::AbstractAssignment, v)::Bool Return whether an assigment has a truth value for a given atom. @@ -106,7 +105,7 @@ end """ inlinedisplay(i::AbstractAssignment) -Provides a string representation of an AbstractAssignment. +Provides a string representation of an assignment. # Examples @@ -124,7 +123,35 @@ function inlinedisplay(i::AbstractAssignment) return error("Please, provide method inlinedisplay(::$(typeof(i)))::String.") end -# When interpreting a single atom, if the lookup fails, then return the atom itself +""" + interpret(a::AbstractAtom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf + +Return the value corresponding to the atom contained in the assignment. +When interpreting a single atom, if the lookup fails, then return the atom itself. + +# Implementation + +If you pass a DefaultedTruthDict as assignment and the atom is not present in the dictionary, +then the default dictionary value will be returned and not the atom itself. + +Here is an example of this. +```julia-repl +julia> interpret(Atom(5), DefaultedTruthDict(string.(1:4), false)) +⊥ +``` + +# Examples + +```julia-repl +julia>interpret(Atom("a"), TruthDict(["a" => true, "b" => false, "c" => true])) +⊤ + +julia>interpret(Atom(3), TruthDict(1:4, false)) +⊥ +``` + +See also [`TruthDict`](@ref), [`Atom`](@ref). +""" function interpret(a::AbstractAtom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf if Base.haskey(i, a) Base.getindex(i, a, args...; kwargs...) diff --git a/src/utils/propositional-logic.jl b/src/utils/propositional-logic.jl index ec5defd8..0276b8ab 100644 --- a/src/utils/propositional-logic.jl +++ b/src/utils/propositional-logic.jl @@ -1,3 +1,32 @@ +############################################################################################ +####################################### Utilities ########################################## +############################################################################################ + +""" + _hpretty_table(io::IO, keys::Any, values::Any) + +Recreate horizontal pretty table formatting. +The keys represent the header of the table and the values the first row of the table. +""" +function _hpretty_table(io::IO, keys::Any, values::Any) + # Prepare columns names + _keys = map(x -> x isa Atom ? value(x) : x, collect(keys)) + header = (_keys, string.(nameof.(typeof.(_keys)))) + + try + # Try to draw a complete table + data = hcat([x for x in values]...) + pretty_table(io, data; header=header) + catch e + if e isa DimensionMismatch + # If it is not possible to draw a complete table, throw a custom error. + error("Some syntax structures are not resolved with all the interpretations ") + else + throw(e) + end + end +end + ############################################################################################ #### Implementations ####################################################################### ############################################################################################ @@ -53,7 +82,8 @@ true See also [`DefaultedTruthDict`](@ref), -[`AbstractAssignment`](@ref), [`AbstractInterpretation`](@ref). +[`AbstractAssignment`](@ref), +[`AbstractInterpretation`](@ref). """ struct TruthDict{D<:AbstractDict} <: AbstractAssignment @@ -114,26 +144,6 @@ function inlinedisplay(i::TruthDict) "TruthDict([$(join(["$(syntaxstring(a)) => $t" for (a,t) in i.truth], ", "))])" end -# Utility function to represent pretty tables horizontally -function _hpretty_table(io::IO, keys::Any, values::Any) - # Prepare columns names - _keys = map(x -> x isa Atom ? value(x) : x, collect(keys)) - header = (_keys, string.(nameof.(typeof.(_keys)))) - - try - # Try to draw a complete table - data = hcat([x for x in values]...) - pretty_table(io, data; header=header) - catch e - if e isa DimensionMismatch - # If it is not possible to draw a complete table, throw a custom error. - @error "Some syntax structures are not resolved with all the interpretations " - else - throw(e) - end - end -end - function Base.show( io::IO, i::TruthDict, @@ -256,7 +266,7 @@ function interpret(a::Atom, i::DefaultedTruthDict, args...; kwargs...) return Base.haskey(i.truth, a) ? Base.getindex(i.truth, a) : i.default_truth end -function inlinedisplay(i::DefaultedTruthDict) +function inlinedisplay(i::DefaultedTruthDict)::String "DefaultedTruthDict([$(join(["$(syntaxstring(a)) => $t" for (a,t) in i.truth], ", "))], $(i.default_truth))" end @@ -276,7 +286,6 @@ end Base.values, ) - ############################################################################################ """ @@ -285,8 +294,7 @@ end Dictionary which associates an [`AbstractAssignment`](@ref)s to the truth value of the assignment itself on a [`SyntaxStructure`](@ref). -See also [`AbstractAssignment`](@ref), [`SyntaxStructure`](@ref), -[`Truth`](@ref). +See also [`AbstractAssignment`](@ref), [`SyntaxStructure`](@ref), [`Truth`](@ref). """ struct TruthTable{ A, From e05d491366e78620dfba1381e132be6a3615422e Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 16 Oct 2024 15:01:43 +0200 Subject: [PATCH 45/90] generation/docstrings.jl and generation/utils.jl included in SoleLogics.jl, before both generation/formula.jl and generation/models.jl --- src/SoleLogics.jl | 6 +++++- src/generation/docstrings.jl | 6 ++++++ src/generation/formula.jl | 3 --- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index c6e5dd1b..6b659706 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -132,9 +132,13 @@ include("parse.jl") ############################################################################################ +# these first files are included here to avoid repeated inclusions in those below; +# "generation" could become a SoleLogics submodule. +include("generation/docstrings.jl") +include("generation/utils.jl") + export randatom export randformula, randbaseformula - include("generation/formula.jl") export randframe, randmodel diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index 3c1229a0..891064e7 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -1,3 +1,5 @@ +#formula.jl docstrings + randatom_docstring = """ randatom( [rng::Union{Random.AbstractRNG,Integer},] @@ -246,3 +248,7 @@ that includes [`Atom`](@ref)s in `alphabet` and [`Operator`](@ref)s in `operator See also [`AnchoredFormula`](@ref), [`Atom`](@ref), [`Operator`](@ref), [`randformula(::Integer, ::Union{AbstractVector,AbstractAlphabet}, ::AbstractVector)`](@ref) """ + + + +# models.jl docstrings diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 3c46eb68..66078bc1 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -4,9 +4,6 @@ using StatsBase import Base: rand import StatsBase: sample -include("docstrings.jl") -include("utils.jl") - """$(randatom_docstring)""" @__rng_dispatch function randatom( rng::Union{Random.AbstractRNG,Integer}, From 188cb24cb0db1fd869783ac770c19d5f46ccd52a Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 16 Oct 2024 15:26:17 +0200 Subject: [PATCH 46/90] generation/models.jl refactoring (now, only 2 dispatches exists thank to @__rng_dispatch). New docstrings in src/generation. new tests for `randframe` and `randmodel` --- src/generation/docstrings.jl | 72 ++++++++++++++++++++++++++++++++++++ src/generation/models.jl | 61 ++++-------------------------- test/generation/models.jl | 11 ++++++ 3 files changed, 90 insertions(+), 54 deletions(-) diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index 891064e7..a1a17cfd 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -252,3 +252,75 @@ See also [`AnchoredFormula`](@ref), [`Atom`](@ref), [`Operator`](@ref), # models.jl docstrings + +randframe_docstring = """ + function randframe( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + nworlds::Int64, + nedges::Int64, + end + +Return a random Kripke Frame, which is a directed graph interpreted as a +[`SoleLogics.ExplicitCrispUniModalFrame`](@ref). +The underlying graph is generated using [`Graphs.SimpleGraphs.SimpleDiGraph`](@ref). + +# Arguments +- `rng::Union{Intger,AbstractRNG}=Random.GLOBAL_RNG`: random number generator; +- `nworlds::Int64`: number of worlds (nodes) in the frame (numbered from `1` to `nworld` + included). +- `nedges::Int64`: number of relations (edges) in the frame; + +# Examples +julia> randframe(Random.MersenneTwister(42),5,10) +SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}} with +- worlds = ["1", "2", "3", "4", "5"] +- accessibles = + 1 -> [2, 3, 5] + 2 -> [1, 4, 5] + 3 -> [] + 4 -> [1, 2] + 5 -> [1, 2] + +See also [`SoleLogics.ExplicitCrispUniModalFrame`](@ref), [`SyntaxLeaf`](@ref), +[`Graphs.SimpleGraphs.SimpleDiGraph`](@ref). +""" + +randmodel_docstring = """ + function randmodel( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + nworlds::Int64, + nedges::Int64, + facts::Vector{SyntaxLeaf}; + truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} + ) + +# Arguments +- `rng::Union{Intger,AbstractRNG}=Random.GLOBAL_RNG`: random number generator; +- `nworlds::Int64`: number of worlds (nodes) in the frame (numbered from `1` to `nworld` + included). +- `nedges::Int64`: number of relations (edges) in the frame; +- `facts::Int64`: vector of generic [`SyntaxLeaf`](@ref), representing facts to which a certain + valuation function can associate a [`Truth`](@ref) value; +- `truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}}`: [`Truth`](@ref) values to + be associated for each element of `facts`. + +# Examples +julia> randmodel(Random.MersenneTwister(42),5,10, [Atom("s"), Atom("p")], BooleanAlgebra()) +KripkeStructure{SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}}, Dict{SoleLogics.World{Int64}, TruthDict{Dict{Atom{String}, BooleanTruth}}}} with +- frame = SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}} with +- worlds = ["1", "2", "3", "4", "5"] +- accessibles = + 1 -> [2, 3, 5] + 2 -> [1, 4, 5] + 3 -> [] + 4 -> [1, 2] + 5 -> [1, 2] +- valuations = + 1 -> TruthDict([s => ⊥, p => ⊤]) + 2 -> TruthDict([s => ⊥, p => ⊥]) + 3 -> TruthDict([s => ⊥, p => ⊥]) + 4 -> TruthDict([s => ⊤, p => ⊤]) + 5 -> TruthDict([s => ⊤, p => ⊤]) + +See also [`AbstractAlgebra`](@ref), [`SyntaxLeaf`](@ref), [`Truth`](@ref). +""" diff --git a/src/generation/models.jl b/src/generation/models.jl index f99bec80..37e33500 100644 --- a/src/generation/models.jl +++ b/src/generation/models.jl @@ -1,37 +1,8 @@ using Graphs using Random -""" - function randframe( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] - nworlds::Int64, - nedges::Int64, - facts::Vector{SyntaxLeaf} - end - -Return a random Kripke Frame, which is a directed graph interpreted as a -[`SoleLogics.ExplicitCrispUniModalFrame`](@ref). The underlying graph is generated using -[`Graphs.SimpleGraphs.SimpleDiGraph`](@ref). - -# Arguments: -* `rng` is a random number generator, or the seed used to create one; -* `nworld` is the number of worlds (nodes) in the frame. Worlds are numbered from `1` - to `nworld` included. -* `nedges` is the number of relations (edges) in the frame; -* `facts` is a vector of generic [`SyntaxLeaf`](@ref). - -See also [`SyntaxLeaf`](@ref), [`Graphs.SimpleGraphs.SimpleDiGraph`](@ref), -[`SoleLogics.ExplicitCrispUniModalFrame`](@ref). -""" -function randframe( - nworlds::Int64, - nedges::Int64; - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG -) - randframe(rng, nworlds, nedges) -end - -function randframe( +"""$(randframe_docstring)""" +@__rng_dispatch function randframe( rng::Union{Integer,AbstractRNG}, nworlds::Int64, nedges::Int64 @@ -41,35 +12,17 @@ function randframe( return SoleLogics.ExplicitCrispUniModalFrame(worlds, graph) end -""" - function randmodel( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] - nworlds::Int64, - nedges::Int64, - facts::Vector{SyntaxLeaf}; - truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} = BooleanAlgebra(); - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG - ) -""" -function randmodel( +"""$(randmodel_docstring)""" +@__rng_dispatch function randmodel( + rng::Union{Integer,AbstractRNG}, nworlds::Int64, nedges::Int64, facts::Vector{<:SyntaxLeaf}, - truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} = BooleanAlgebra(); - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG + truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} ) truthvalues = inittruthvalues(truthvalues) - randmodel(initrng(rng), nworlds, nedges, facts, truthvalues) -end - -function randmodel( - rng::AbstractRNG, - nworlds::Int64, - nedges::Int64, - facts::Vector{<:SyntaxLeaf}, - truthvalues::AbstractVector{<:Truth} -) fr = randframe(rng, nworlds, nedges) + valuation = Dict( [w => TruthDict([f => rand(truthvalues) for f in facts]) for w in fr.worlds] ) diff --git a/test/generation/models.jl b/test/generation/models.jl index e69de29b..74f8ca20 100644 --- a/test/generation/models.jl +++ b/test/generation/models.jl @@ -0,0 +1,11 @@ +using Random + +@testset "randframe + randmodel" begin + +@test_nowarn randframe(42, 10, 20) +@test_nowarn randframe(Random.MersenneTwister(42), 10, 20) + +@test_nowarn randmodel(42, 5, 10, [Atom("s"), Atom("p")], BooleanAlgebra()) +@test_nowarn randmodel(42, 5, 10, [Atom("s"), Atom("p")], BooleanAlgebra()) + +end From 35a2d96d2a370ebb0f4870600afcb63ecf96716b Mon Sep 17 00:00:00 2001 From: Michele Ghiotti Date: Wed, 16 Oct 2024 15:37:10 +0200 Subject: [PATCH 47/90] added documentation for propositional-logic structs and for AbstractDict and AbstractVector functions --- src/utils/propositional-logic.jl | 150 ++++++++++++++++++++++++++++--- 1 file changed, 139 insertions(+), 11 deletions(-) diff --git a/src/utils/propositional-logic.jl b/src/utils/propositional-logic.jl index 0276b8ab..f00157f4 100644 --- a/src/utils/propositional-logic.jl +++ b/src/utils/propositional-logic.jl @@ -28,7 +28,7 @@ function _hpretty_table(io::IO, keys::Any, values::Any) end ############################################################################################ -#### Implementations ####################################################################### +####################################### TruthDict ########################################## ############################################################################################ """ @@ -168,6 +168,8 @@ end Base.keys, Base.values, ) +############################################################################################ +################################## DefaultedTruthDict ###################################### ############################################################################################ """ @@ -286,8 +288,11 @@ end Base.values, ) +############################################################################################ +###################################### TruthTable ########################################## ############################################################################################ +# TODO: it is necessary? """ struct TruthTable{A,T<:Truth} @@ -305,9 +310,15 @@ end ############################################################################################ -# Helpers: -# we let any AbstractDict and AbstractVector be used as an interpretation when model checking. +""" + 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`](@ref). +""" function check( φ::Formula, i::Union{AbstractDict,AbstractVector}, @@ -316,14 +327,131 @@ function check( check(φ, convert(AbstractInterpretation, i), args...) end -# A dictionary is interpreted as the map from atoms to `Truth` values +############################################################################################# +###################################### AbstractDict ######################################### +############################################################################################# + +""" + convert(::Type{AbstractInterpretation}, i::AbstractDict) + +Convert a dictionary (with keys and values) in a TruthDict. +In this case, a dictionary is interpreted as the map from atoms to `Truth` values. + +# Examples + +```julia-repl +julia> convert(AbstractInterpretation, Dict([1 => ⊤, 2 => ⊥])) +TruthDict with values: +┌───────┬───────┐ +│ 2 │ 1 │ +│ Int64 │ Int64 │ +├───────┼───────┤ +│ ⊥ │ ⊤ │ +└───────┴───────┘ +``` + +!!! warning + For a proper functioning, the values contained in the dictionary and + associated with the keys must be Boolean values. If this were not the + case, this method could not be used. + +See also [`AbstractInterpretation`](@ref), [`TruthDict`](@ref). +""" convert(::Type{AbstractInterpretation}, i::AbstractDict) = TruthDict(i) -# Base.getindex(i::AbstractDict, a::Atom) = i[value(a)] -Base.haskey(a::Atom, i::AbstractDict) = (value(a) in keys(i)) -check(a::Atom, i::AbstractDict) = Base.getindex(i, a) +#Base.getindex(i::AbstractDict, a::Atom) = i[value(a)] + +""" + Base.haskey(a::Atom, i::AbstractDict)::Bool + +Checks whether an atom is contained in any dictionary. + +# Examples + +```julia-repl +julia> haskey(Atom(1), Dict([1 => ⊤, 2 => ⊥])) +true + +julia> haskey(Atom(3), Dict([1 => ⊤, 2 => ⊥])) +false +``` + +See also [`TruthDict`](@ref). +""" +Base.haskey(a::Atom, i::AbstractDict)::Bool = (value(a) in keys(i)) + +""" + check(a::Atom, i::AbstractDict) + +Returns the Boolean value corresponding to the atom passed as parameter. + +# Examples + +```julia-repl +julia> check(Atom(1), Dict([1 => ⊤, 2 => ⊥])) +true + +julia> check(Atom(3), Dict([1 => ⊤, 2 => ⊥])) +false +``` + +See also [`Atom`](@ref). +""" +check(a::Atom, i::AbstractDict) = haskey(a,i) ? Base.getindex(i, value(a)) : nothing + +############################################################################################# +##################################### AbstractVector######################################### +############################################################################################# + +""" + convert(::Type{AbstractInterpretation}, i::AbstractVector) + +Converts any vector to a dictionary with all ⊤ and ⊥ default value. +In this case, a vector is interpreted as the set of true atoms. + +# Examples + +```julia-repl +julia> convert(AbstractInterpretation, [1,2,3]) +DefaultedTruthDict with default truth `⊥` and values: +┌───────┬───────┬───────┐ +│ 2 │ 3 │ 1 │ +│ Int64 │ Int64 │ Int64 │ +├───────┼───────┼───────┤ +│ ⊤ │ ⊤ │ ⊤ │ +└───────┴───────┴───────┘ + +julia> convert(SoleLogics.AbstractInterpretation, ["a","b"]) +DefaultedTruthDict with default truth `⊥` and values: +┌────────┬────────┐ +│ b │ a │ +│ String │ String │ +├────────┼────────┤ +│ ⊤ │ ⊤ │ +└────────┴────────┘ +``` -# A vector is interpreted as the set of true atoms +See also [`AbstractInterpretation`](@ref), [`TruthDict`](@ref). +""" convert(::Type{AbstractInterpretation}, i::AbstractVector) = DefaultedTruthDict(i, ⊥) -# Base.getindex(i::AbstractVector, a::Atom) = (value(a) in i) -# Base.in(a::Atom, i::AbstractVector) = true -check(a::Atom, i::AbstractVector) = (a in i) +#Base.getindex(i::AbstractVector, a::Atom) = (value(a) in i) +#Base.in(a::Atom, i::AbstractVector) = true + +""" + check(a::Atom, i::AbstractVector) + +Returns a truth value indicating whether or not that atom +is contained in the passed vector. + +# Examples + +```julia-repl +julia> check(Atom(1), [1,2,4]) +true + +julia> check(Atom(5), [2,3,4]) +false +``` + +See also [`Atom`](@ref). +""" +check(a::Atom, i::AbstractVector) = (value(a) in i) From 5259523ac6de1ea7cb3ed406d3d896856ba74a1d Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 16 Oct 2024 19:23:49 +0200 Subject: [PATCH 48/90] increasing code coverage (more tests, especially @test_throws) --- src/generation/docstrings.jl | 4 ++-- src/generation/formula.jl | 16 ++++++---------- test/generation/formula.jl | 30 ++++++++++++++++++++++++++++-- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index a1a17cfd..0314486e 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -179,7 +179,7 @@ randformula_docstring = """ [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] height::Integer, alphabet::Union{AbstractVector,AbstractAlphabet}, - operators::AbstractVector, + operators::AbstractVector{<:Operator}, args...; modaldepth::Integer=height, atompicker::Union{Nothing,Function,AbstractWeights,AbstractVector{<:Real}}=randatom, @@ -194,7 +194,7 @@ Return a pseudo-randomic [`SyntaxTree`](@ref). - `rng::Union{Intger,AbstractRNG}=Random.GLOBAL_RNG`: random number generator; - `height::Integer`: height of the generated structure; - `alphabet::AbstractAlphabet`: collection from which atoms are chosen randomly; -- `operators::AbstractVector`: vector from which legal operators are chosen. +- `operators::AbstractVector{<:Operator}`: vector from which legal operators are chosen. # Keyword Arguments - `modaldepth::Integer`: maximum modal depth; diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 66078bc1..0acaa3a7 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -130,7 +130,7 @@ end if !(connectives isa AbstractVector{<:Connective} || !(truthvalues isa AbstractVector{<:Truth}) ) - thorw(ArgumentError("Unexpected connectives and truth values: " * + throw(ArgumentError("Unexpected connectives and truth values: " * "$(connectives) and $(truthvalues).")) end @@ -147,7 +147,7 @@ end ops = vcat(ops, truthvalues) end - randformula(height, ops, atoms, args...; rng=initrng(rng), kwargs...) + randformula(height, atoms, ops, args...; kwargs...) end @@ -204,7 +204,7 @@ end rng::Union{Integer,AbstractRNG}, height::Integer, alphabet::Union{AbstractVector,AbstractAlphabet}, - operators::AbstractVector, + operators::AbstractVector{<:Operator}, args...; modaldepth::Integer=height, atompicker::Union{Nothing,Function,AbstractWeights,AbstractVector{<:Real}}=randatom, @@ -260,13 +260,9 @@ end else # sample operator and generate children # (modal connectives only if modaldepth is set > 0) - ops, ops_w = begin - if modaldepth > 0 - operators, opweights - else - operators[nonmodal_operators], opweights[nonmodal_operators] - end - end + ops, ops_w = (modaldepth > 0) ? + (operators, opweights) : + operators[nonmodal_operators], opweights[nonmodal_operators] # op = rand(rng, ops) op = sample(rng, ops, ops_w) diff --git a/test/generation/formula.jl b/test/generation/formula.jl index 56e57a89..ad175fb9 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -1,8 +1,9 @@ using StatsBase -import SoleLogics: arity using SoleLogics: parsebaseformula using Random +import SoleLogics: arity, syntaxstring + @testset "randformula + randbaseformula" begin _alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) @@ -67,6 +68,9 @@ my_logic = propositionallogic(alphabet=my_alph) @test_nowarn randatom(my_alph) @test randatom(42, my_alph) == Atom(4) +non_finite_alph = my_alph = AlphabetOfAny{Atom{String}}() +@test_throws Exception randatom(42, non_finite_alph) + alph2 = ExplicitAlphabet(6:10) unionalph = UnionAlphabet([my_alph,alph2]) @test_nowarn randatom(unionalph) @@ -78,18 +82,40 @@ _subalphabets_weights_test_dim = 100 for i in 1:_subalphabets_weights_test_dim]) ) > convert(Int32, (_subalphabets_weights_test_dim/2)) +@test_throws UndefVarError randatom(unionalph; atompicking_mode=:invalid_mode) +@test_throws ArgumentError randatom(unionalph; atompicking_mode=:weighted) +@test_throws ArgumentError randatom( + unionalph; atompicking_mode=:weighted, subalphabets_weights=[1]) +@test_nowarn randatom(unionalph; atompicking_mode=:uniform_subalphabets) + @test_nowarn rand(my_alph) @test_nowarn rand(42, my_alph) == Atom(4) @test_nowarn rand(4) @test rand(MersenneTwister(42), 2, my_logic) |> syntaxstring == "(1 ∧ 5) ∨ ¬2" - @test_nowarn rand(4, my_grammar) @test_nowarn rand(Random.MersenneTwister(1), 4, my_grammar) +@test_nowarn rand(Random.MersenneTwister(42), 4, my_alph, [CONJUNCTION, DISJUNCTION]) +@test_throws ArgumentError rand( + Random.MersenneTwister(42), 4, my_alph, [TOP]; truthvalues=[TOP]) + +# Testing rand edge case: truth values common ancestor is Truth; +# here, we make a custom Truth +struct MyTruth <: Truth + val::Integer +end +const MyTruthTOP = MyTruth(5); +syntaxstring(mt::MyTruth) = mt.val +Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:MyTruth}) = Truth + +@test_throws ArgumentError rand( + 42, 4, my_alph, [CONJUNCTION]; truthvalues=Truth[TOP,MyTruthTOP]); + @test_nowarn sample(my_alph, Weights([1,2,3,4,5])) @test sample(2, my_alph, Weights([1,2,3,4,5])) == Atom(3) +@test_throws Exception sample(2, non_finite_alph, Weights([1,2,3,4,5])) @test_nowarn StatsBase.sample(2, my_logic, Weights([1,2,3,4,5]), Weights([1,2])) @test StatsBase.sample( From a52a5d8f5ab51a8f8f4971436706d1d6b08ca204 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 16 Oct 2024 19:24:15 +0200 Subject: [PATCH 49/90] typecheck removed in randformula (check if all elements in `operators` are of type Operator). `operators` type is already specified in function signature --- src/generation/formula.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 0acaa3a7..dd5c6994 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -214,10 +214,6 @@ end ) rng = initrng(rng) alphabet = convert(AbstractAlphabet, alphabet) - if !(all(x->x isa Operator, operators)) - throw(ArgumentError("Unexpected object(s) passed as" * - " operator:" * " $(filter(x->!(x isa Operator), operators))")) - end if (isnothing(opweights)) opweights = StatsBase.uweights(length(operators)) From 6a40e91c70eacb7ffa7873384ae0ecb6ce9f2a13 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 16 Oct 2024 19:36:44 +0200 Subject: [PATCH 50/90] tests fixed (broken because of one missing pair of brackets and one assignment) --- src/generation/formula.jl | 2 +- test/generation/formula.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/generation/formula.jl b/src/generation/formula.jl index dd5c6994..fb40f575 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -258,7 +258,7 @@ end # (modal connectives only if modaldepth is set > 0) ops, ops_w = (modaldepth > 0) ? (operators, opweights) : - operators[nonmodal_operators], opweights[nonmodal_operators] + (operators[nonmodal_operators], opweights[nonmodal_operators]) # op = rand(rng, ops) op = sample(rng, ops, ops_w) diff --git a/test/generation/formula.jl b/test/generation/formula.jl index ad175fb9..6e6a5464 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -64,11 +64,10 @@ my_ops = [∧,¬] my_grammar = SoleLogics.CompleteFlatGrammar(my_alph, my_ops) my_logic = propositionallogic(alphabet=my_alph) - @test_nowarn randatom(my_alph) @test randatom(42, my_alph) == Atom(4) -non_finite_alph = my_alph = AlphabetOfAny{Atom{String}}() +non_finite_alph = AlphabetOfAny{Atom{String}}() @test_throws Exception randatom(42, non_finite_alph) alph2 = ExplicitAlphabet(6:10) @@ -135,6 +134,7 @@ Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:MyTruth}) = Truth @test_nowarn randformula(MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:5) @test_throws ArgumentError randformula( MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:6) +@test_nowarn randformula(MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:5) @test_nowarn randbaseformula(2, my_alph, my_ops) @test_nowarn randbaseformula(MersenneTwister(42), 2, my_alph, my_ops) From 6c13046c037e570c9c4753f8395a1debc68e4a17 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 16 Oct 2024 19:41:59 +0200 Subject: [PATCH 51/90] randformula atompicker docstring enriched --- src/generation/docstrings.jl | 10 ++++++---- test/generation/formula.jl | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index 0314486e..8b582093 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -198,10 +198,12 @@ Return a pseudo-randomic [`SyntaxTree`](@ref). # Keyword Arguments - `modaldepth::Integer`: maximum modal depth; -- `atompicker::Function`: method used to pick a random element. For example, this could be - Base.rand or StatsBase.sample; -- `opweights::AbstractWeights`: operators are sampled with probabilities proportional to - this vector vector (see [`AbstractWeights`](@ref) of StatsBase package). +- `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 of `StatsBase.AbstractWeights`; +- `opweights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}`: operators are sampled + with probabilities proportional to this vector (see [`AbstractWeights`](@ref) of + StatsBase package). - `alphabet_sample_kwargs::AbstractVector`: pool of atoms to pull from if the given alphabet is not finite. diff --git a/test/generation/formula.jl b/test/generation/formula.jl index 6e6a5464..31fe3caf 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -131,6 +131,8 @@ Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:MyTruth}) = Truth @test_nowarn randformula(4, my_grammar) @test_nowarn randformula(MersenneTwister(1), 4, my_grammar) @test_nowarn randformula(4, my_alph, my_ops) +@test_throws ArgumentError randformula(4, my_alph, my_ops; atompicker=[1,2]) + @test_nowarn randformula(MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:5) @test_throws ArgumentError randformula( MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:6) From 95663e4836ec32cb560c5f24c21e1b2369100adb Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 16 Oct 2024 19:44:47 +0200 Subject: [PATCH 52/90] @warn changed to throw(ArgumentError(...)) in randformula --- src/generation/formula.jl | 4 ++-- test/generation/formula.jl | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/generation/formula.jl b/src/generation/formula.jl index fb40f575..5941cb5c 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -271,8 +271,8 @@ end # if the alphabet is not iterable, this function should not work. if !isfinite(alphabet) && isnothing(alphabet_sample_kwargs) - @warn "Attempting to generate random formulas from " * - "(infinite) alphabet of type $(typeof(alphabet))!" + throw(ArgumentError("Attempting to generate random formulas from " * + "(infinite) alphabet of type $(typeof(alphabet))!")) end return _randformula(rng, height, modaldepth) diff --git a/test/generation/formula.jl b/test/generation/formula.jl index 31fe3caf..95da164c 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -132,6 +132,7 @@ Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:MyTruth}) = Truth @test_nowarn randformula(MersenneTwister(1), 4, my_grammar) @test_nowarn randformula(4, my_alph, my_ops) @test_throws ArgumentError randformula(4, my_alph, my_ops; atompicker=[1,2]) +@test_throws ArgumentError randformula(4, non_finite_alph, my_ops) @test_nowarn randformula(MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:5) @test_throws ArgumentError randformula( From 9e0ea19d2140168986ac754371f71bd20e17f110 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 16 Oct 2024 19:48:15 +0200 Subject: [PATCH 53/90] randformula ambiguous dispatch removed (arguments were 2 integers: one seed and height.) --- src/generation/formula.jl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 5941cb5c..ae790a01 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -289,16 +289,6 @@ end randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) end -@__rng_dispatch function randformula( - rng::Union{Integer,AbstractRNG}, - height::Integer, - args...; - kwargs... -) - randformula(initrng(rng), height, args...; kwargs...) -end - - """$(randbaseformula_docstring)""" @__rng_dispatch function randbaseformula( rng::Union{Integer,AbstractRNG}, From 11210102504b381f278d475dae85d101cc70d120 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 16 Oct 2024 20:06:19 +0200 Subject: [PATCH 54/90] code coverage increased for generation/utils.jl --- src/generation/utils.jl | 13 +++++++------ test/generation/formula.jl | 11 ++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/generation/utils.jl b/src/generation/utils.jl index e899d4a2..2c8471b0 100644 --- a/src/generation/utils.jl +++ b/src/generation/utils.jl @@ -22,12 +22,13 @@ macro __rng_dispatch(ex) # The first argument one must be an Union{Random.AbstractRNG,Integer}, # otherwise, this macro makes no sense. - quote - if !isa($fargs[2], Union{Random.AbstractRNG,Integer}) - throw(ArgumentError("Expected function's first argument to be of type " * - "Union{Random.AbstractRNG,Integer}.")) - end - end + # TODO - this check should be made out of quote environment + # quote + # if !isa($(esc(fargs[2])), Union{Random.AbstractRNG,Integer}) + # throw(ArgumentError("Expected function's first argument to be of type " * + # "Union{Random.AbstractRNG,Integer}.")) + # end + # end # At this point, fargs is shaped similar to: # diff --git a/test/generation/formula.jl b/test/generation/formula.jl index 95da164c..ddd202bb 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -1,5 +1,5 @@ using StatsBase -using SoleLogics: parsebaseformula +using SoleLogics: parsebaseformula, @__rng_dispatch using Random import SoleLogics: arity, syntaxstring @@ -145,3 +145,12 @@ Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:MyTruth}) = Truth @test_nowarn randbaseformula(4, my_grammar) @test_nowarn randbaseformula(MersenneTwister(42), 4, my_grammar) end + + + +@testset "@__rng_dispatch" begin + +@test_throws LoadError @eval @__rng_dispatch 1+1 +@test_throws LoadError @eval @__rng_dispatch function foo() end + +end From 3d13e7db639fb1516f574165cb907cfe4c4d65cb Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Thu, 17 Oct 2024 00:35:29 +0200 Subject: [PATCH 55/90] removing @testset from generation/formula.jl causes the tests to work --- test/generation/formula.jl | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/generation/formula.jl b/test/generation/formula.jl index ddd202bb..e8340f05 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -4,7 +4,7 @@ using Random import SoleLogics: arity, syntaxstring -@testset "randformula + randbaseformula" begin +# @testset "randformula + randbaseformula" begin _alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) _operators = [NEGATION, CONJUNCTION, IMPLICATION] @@ -14,11 +14,11 @@ w = [10,1,1] @test_nowarn [randbaseformula(i, _alphabet, _operators, opweights=w) for i in 1:2] @test_nowarn [randformula(i, _alphabet, _operators, opweights=w) for i in 1:5] -end +# end # endof test set -@testset "generation w. custom operators" begin +# @testset "generation w. custom operators" begin TERNOP = SoleLogics.NamedConnective{:⇶}() SoleLogics.arity(::typeof(TERNOP)) = 3 @@ -53,11 +53,11 @@ w = [5,1,1,1,1,1,1] function_notation = true) end for i in 1:10]) -end +# end # endof test set -@testset "Dispatches made with @__rng_dispatch" begin +# @testset "Dispatches made with @__rng_dispatch" begin my_alph = ExplicitAlphabet(1:5) my_ops = [∧,¬] @@ -105,7 +105,7 @@ _subalphabets_weights_test_dim = 100 struct MyTruth <: Truth val::Integer end -const MyTruthTOP = MyTruth(5); +MyTruthTOP = MyTruth(5); syntaxstring(mt::MyTruth) = mt.val Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:MyTruth}) = Truth @@ -144,13 +144,14 @@ Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:MyTruth}) = Truth @test_nowarn randbaseformula(4, my_grammar) @test_nowarn randbaseformula(MersenneTwister(42), 4, my_grammar) -end +# end # endof test set -@testset "@__rng_dispatch" begin + +# @testset "@__rng_dispatch" begin @test_throws LoadError @eval @__rng_dispatch 1+1 @test_throws LoadError @eval @__rng_dispatch function foo() end -end +# end # endof test set From 613fd1887701850cdd42b684a72fb4cc8b574049 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Thu, 17 Oct 2024 00:43:46 +0200 Subject: [PATCH 56/90] atom -> value, casting Syntactical -> Operator in normalize.jl, to fix a test involving `randformula`. --- test/generation/formula.jl | 2 +- test/normalize.jl | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test/generation/formula.jl b/test/generation/formula.jl index e8340f05..39c8fc0f 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -77,7 +77,7 @@ unionalph = UnionAlphabet([my_alph,alph2]) _subalphabets_weights_test_dim = 100 @test count(x -> x<5, - atom.([randatom(unionalph; atompicking_mode=:weighted, subalphabets_weights=[5,1]) + value.([randatom(unionalph; atompicking_mode=:weighted, subalphabets_weights=[5,1]) for i in 1:_subalphabets_weights_test_dim]) ) > convert(Int32, (_subalphabets_weights_test_dim/2)) diff --git a/test/normalize.jl b/test/normalize.jl index fd5343ee..fd5fa3f4 100644 --- a/test/normalize.jl +++ b/test/normalize.jl @@ -51,7 +51,13 @@ check(φ, K1, w0) N = 200 for K in [K0, K1] for i in 1:N - _ops = rand([SoleLogics.BASE_MODAL_CONNECTIVES, union(SoleLogics.BASE_MODAL_CONNECTIVES, [⊤, ⊥])]) + _ops = Vector{Operator}([ + rand([ + SoleLogics.BASE_MODAL_CONNECTIVES, + union(SoleLogics.BASE_MODAL_CONNECTIVES, + [⊤, ⊥]) + ])... + ]) _φ = randformula(MersenneTwister(i), 3, alph_vector, _ops) _nφ = normalize(_φ) # @show syntaxstring(φ) From 4ae962fb0359560292865669bfda75181556d649 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Thu, 17 Oct 2024 00:50:00 +0200 Subject: [PATCH 57/90] revert of the last commit (value -> atom). A warning message tricked me: "`atom(p::Proposition)` is deprecated, use `value(p)` instead." --- test/generation/formula.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/generation/formula.jl b/test/generation/formula.jl index 39c8fc0f..e8340f05 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -77,7 +77,7 @@ unionalph = UnionAlphabet([my_alph,alph2]) _subalphabets_weights_test_dim = 100 @test count(x -> x<5, - value.([randatom(unionalph; atompicking_mode=:weighted, subalphabets_weights=[5,1]) + atom.([randatom(unionalph; atompicking_mode=:weighted, subalphabets_weights=[5,1]) for i in 1:_subalphabets_weights_test_dim]) ) > convert(Int32, (_subalphabets_weights_test_dim/2)) From 0d6cc6503c551acc33ef2d64a7cad33fa2c5677c Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Thu, 17 Oct 2024 10:20:09 +0200 Subject: [PATCH 58/90] one minor TODO and added ```julia-repl``` to randframe and randmodel --- src/generation/docstrings.jl | 5 ++++- src/generation/formula.jl | 1 + test/generation/formula.jl | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl index 8b582093..fbbaecb1 100644 --- a/src/generation/docstrings.jl +++ b/src/generation/docstrings.jl @@ -208,7 +208,6 @@ Return a pseudo-randomic [`SyntaxTree`](@ref). is not finite. # Examples - ```julia-repl julia> syntaxstring(randformula(4, ExplicitAlphabet([1,2]), [NEGATION, CONJUNCTION, IMPLICATION])) "¬((¬(¬(2))) → ((1 → 2) → (1 → 2)))" @@ -273,6 +272,7 @@ The underlying graph is generated using [`Graphs.SimpleGraphs.SimpleDiGraph`](@r - `nedges::Int64`: number of relations (edges) in the frame; # Examples +```julia-repl julia> randframe(Random.MersenneTwister(42),5,10) SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}} with - worlds = ["1", "2", "3", "4", "5"] @@ -282,6 +282,7 @@ SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, Graphs.SimpleGrap 3 -> [] 4 -> [1, 2] 5 -> [1, 2] +``` See also [`SoleLogics.ExplicitCrispUniModalFrame`](@ref), [`SyntaxLeaf`](@ref), [`Graphs.SimpleGraphs.SimpleDiGraph`](@ref). @@ -307,6 +308,7 @@ randmodel_docstring = """ be associated for each element of `facts`. # Examples +```julia-repl julia> randmodel(Random.MersenneTwister(42),5,10, [Atom("s"), Atom("p")], BooleanAlgebra()) KripkeStructure{SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}}, Dict{SoleLogics.World{Int64}, TruthDict{Dict{Atom{String}, BooleanTruth}}}} with - frame = SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}} with @@ -323,6 +325,7 @@ KripkeStructure{SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, G 3 -> TruthDict([s => ⊥, p => ⊥]) 4 -> TruthDict([s => ⊤, p => ⊤]) 5 -> TruthDict([s => ⊤, p => ⊤]) +``` See also [`AbstractAlgebra`](@ref), [`SyntaxLeaf`](@ref), [`Truth`](@ref). """ diff --git a/src/generation/formula.jl b/src/generation/formula.jl index ae790a01..5278ba2e 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -4,6 +4,7 @@ using StatsBase import Base: rand import StatsBase: sample +# TODO - Move in src/utils.jl (keep here the implementations for struct) """$(randatom_docstring)""" @__rng_dispatch function randatom( rng::Union{Random.AbstractRNG,Integer}, diff --git a/test/generation/formula.jl b/test/generation/formula.jl index e8340f05..b52cd0cd 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -107,7 +107,7 @@ struct MyTruth <: Truth end MyTruthTOP = MyTruth(5); syntaxstring(mt::MyTruth) = mt.val -Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:MyTruth}) = Truth +# Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:MyTruth}) = Truth @test_throws ArgumentError rand( 42, 4, my_alph, [CONJUNCTION]; truthvalues=Truth[TOP,MyTruthTOP]); From 59bb72ad8a1c18a49a120da247b81ca4325a9cd3 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Thu, 17 Oct 2024 15:33:45 +0200 Subject: [PATCH 59/90] Merge with Mauro rand --- src/SoleLogics.jl | 17 +- src/generation/docstrings.jl | 314 +++++++++++++ src/generation/formula.jl | 320 +++++++++++++ src/generation/models.jl | 31 ++ src/generation/utils.jl | 55 +++ src/modal-logic.jl | 2 +- src/old-code/install.jl | 29 -- src/random.jl | 470 -------------------- src/syntax-utils.jl | 4 +- src/types/logic.jl | 25 +- src/utils.jl | 57 --- src/{old-code => utils}/anchored-formula.jl | 44 +- src/utils/parse.jl | 20 +- test/generation/formula.jl | 157 +++++++ test/generation/models.jl | 11 + test/normalize.jl | 8 +- test/random.jl | 82 ---- test/runtests.jl | 4 +- 18 files changed, 927 insertions(+), 723 deletions(-) create mode 100644 src/generation/docstrings.jl create mode 100644 src/generation/formula.jl create mode 100644 src/generation/models.jl create mode 100644 src/generation/utils.jl delete mode 100644 src/old-code/install.jl delete mode 100644 src/random.jl rename src/{old-code => utils}/anchored-formula.jl (91%) create mode 100644 test/generation/formula.jl create mode 100644 test/generation/models.jl delete mode 100644 test/random.jl diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index a8967a02..29fd10fb 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -75,6 +75,8 @@ export BaseLogic include("utils.jl") +include("utils/anchored-formula.jl") + ############################################################################################ export propositionallogic @@ -132,14 +134,25 @@ include("types/interpretation-sets.jl") include("utils/interpretation-sets.jl") +############################################################################################ + +export parseformula + include("utils/parse.jl") ############################################################################################ +# these first files are included here to avoid repeated inclusions in those below; +# "generation" could become a SoleLogics submodule. +include("generation/docstrings.jl") +include("generation/utils.jl") + +export randatom export randformula -export randframe, randmodel +include("generation/formula.jl") -include("random.jl") +export randframe, randmodel +include("generation/models.jl") ############################################################################################ diff --git a/src/generation/docstrings.jl b/src/generation/docstrings.jl new file mode 100644 index 00000000..573cc01d --- /dev/null +++ b/src/generation/docstrings.jl @@ -0,0 +1,314 @@ +#formula.jl docstrings + +randatom_docstring = """ + randatom( + [rng::Union{Random.AbstractRNG,Integer},] + a::AbstractAlphabet, + args...; + kwargs... + ) + +Randomly generate an [`Atom`](@ref) from a *finite* [`AbstractAlphabet`](@ref) according to +a uniform distribution. + +# Examples +```julia-repl +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`](@ref), [`AbstractAlphabet`](@ref). +""" + +randatom_unionalphabet_docstring = """ + 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-repl +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`](@ref). +""" + +rand_abstractalphabet_docstring = """ + Base.rand( + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + alphabet::AbstractAlphabet, + args...; + kwargs... + )::Atom + +Synonym for [`randatom(::AbstractAlphabet)`](@ref). + +Randomly generate an [`Atom`](@ref) from a *finite* [`AbstractAlphabet`](@ref) according to +a uniform distribution. + +See also [`AbstractAlphabet`], [`randatom(::AbstractAlphabet)`](@ref). +""" + +rand_abstractlogic_docstring = """ + function Base.rand( + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + height::Integer, + l::AbstractLogic, + args...; + kwargs... + ) + +Generate a random formula of height `height` and belonging to logic `l`. + +See also [`AbstractLogic`](@ref). +""" + +rand_completeflatgrammar_docstring = """ + Base.rand( + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + height::Integer, + g::CompleteFlatGrammar, + args... + )::Formula + +Generate a random formula of height `height`, honoring the grammar `g`. + +See also [`CompleteFlatGrammar`](@ref). +""" + +rand_granular_docstring = """ + Base.rand( + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + height::Integer, + connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, + atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, + truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}}=nothing, + args...; + rng::AbstractRNG = Random.GLOBAL_RNG, + kwargs... + )::Formula + +See also [`AbstractAlphabet`](@ref), [`AbstractAlgebra`](@ref), [`Connective`](@ref), +[`Operator`](@ref). +""" + +sample_aw_docstring = """ + function StatsBase.sample( + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + alphabet::AbstractAlphabet, + weights::AbstractWeights, + args...; + kwargs... + ) + +Sample an [`Atom`](@ref) from an `alphabet`, with probabilities proportional to the weights +given in `weights`. + +See also [`AbstractAlphabet`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref). +""" + +sample_lao_docstring = """ + function StatsBase.sample( + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + l::AbstractLogic, + weights::AbstractWeights, + args...; + kwargs... + ) + +Sample from the [`grammar`](@ref) of logic `l`, with probabilities proportional to the +weights given in `weights`. + +See also [`AbstractLogic`](@ref), [`AbstractWeights`](@ref), +[`grammar(::AbstractLogic{G}) where {G}`](@ref). +""" + +sample_hgao_docstring = """ + function StatsBase.sample( + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + height::Integer, + g::AbstractGrammar, + atomweights::Union{Nothing,AbstractWeights}=nothing, + opweights::Union{Nothing,AbstractWeights}=nothing, + args...; + kwargs... + ) + +Sample a formula from grammar `g`. +[`Atom`](@ref)s and [`Operator`](@ref)s sampling probabilities are proportional +respectively to `atomweights` and `opweights`. + +See also [`AbstractGrammar`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref), +[`Operator`](@ref). +""" + +randformula_docstring = """ + function randformula( + [T::Type{<:Formula}=SyntaxTree,] + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + height::Integer, + alphabet::Union{AbstractVector,AbstractAlphabet}, + operators::AbstractVector{<:Operator}, + args...; + modaldepth::Integer=height, + 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`](@ref). + +# Arguments +- `rng::Union{Intger,AbstractRNG}=Random.GLOBAL_RNG`: random number generator; +- `height::Integer`: 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 +- `modaldepth::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 of `StatsBase.AbstractWeights`; +- `opweights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}`: operators are sampled + with probabilities proportional to this vector (see [`AbstractWeights`](@ref) of + StatsBase package). +- `alphabet_sample_kwargs::AbstractVector`: pool of atoms to pull from if the given alphabet + is not finite. + +# Examples +```julia-repl +julia> syntaxstring(randformula(4, ExplicitAlphabet([1,2]), [NEGATION, CONJUNCTION, IMPLICATION])) +"¬((¬(¬(2))) → ((1 → 2) → (1 → 2)))" +``` + +See also [`AbstractAlphabet`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref), +[`Operator`](@ref), [`SyntaxBranch`](@ref), [`SyntaxTree`](@ref). +""" + +randformula_hg_docstring = """ + function randformula( + [T::Type{<:Formula}=SyntaxTree,] + [rng::Union{Integer,AbstractRNG}=Random.GLOBAL_RNG,] + height::Integer, + [g::AbstractGrammar,] + args...; + kwargs... + ) + +Fallback to `randformula`, specifying only the `height` (possibly also a `grammar`) of the +generated [`SyntaxTree`](@ref). + +See also [`AbstractGrammar`](@ref), +[`randformula(::Integer, ::Union{AbstractVector,AbstractAlphabet}, ::AbstractVector)`](@ref). +""" + +# models.jl docstrings + +randframe_docstring = """ + function randframe( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + nworlds::Integer, + nedges::Integer, + end + +Return a random Kripke Frame, which is a directed graph interpreted as a +[`SoleLogics.ExplicitCrispUniModalFrame`](@ref). +The underlying graph is generated using [`Graphs.SimpleGraphs.SimpleDiGraph`](@ref). + +# Arguments +- `rng::Union{Intger,AbstractRNG}=Random.GLOBAL_RNG`: random number generator; +- `nworlds::Int64`: number of worlds (nodes) in the frame (numbered from `1` to `nworld` + included). +- `nedges::Int64`: number of relations (edges) in the frame; + +# Examples +```julia-repl +julia> randframe(Random.MersenneTwister(42),5,10) +SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}} with +- worlds = ["1", "2", "3", "4", "5"] +- accessibles = + 1 -> [2, 3, 5] + 2 -> [1, 4, 5] + 3 -> [] + 4 -> [1, 2] + 5 -> [1, 2] +``` + +See also [`SoleLogics.ExplicitCrispUniModalFrame`](@ref), [`SyntaxLeaf`](@ref), +[`Graphs.SimpleGraphs.SimpleDiGraph`](@ref). +""" + +randmodel_docstring = """ + function randmodel( + [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] + nworlds::Integer, + nedges::Integer, + facts::Vector{SyntaxLeaf}; + truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} + ) + +# Arguments +- `rng::Union{Intger,AbstractRNG}=Random.GLOBAL_RNG`: random number generator; +- `nworlds::Int64`: number of worlds (nodes) in the frame (numbered from `1` to `nworld` + included). +- `nedges::Int64`: number of relations (edges) in the frame; +- `facts::Int64`: vector of generic [`SyntaxLeaf`](@ref), representing facts to which a certain + valuation function can associate a [`Truth`](@ref) value; +- `truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}}`: [`Truth`](@ref) values to + be associated for each element of `facts`. + +# Examples +```julia-repl +julia> randmodel(Random.MersenneTwister(42),5,10, [Atom("s"), Atom("p")], BooleanAlgebra()) +KripkeStructure{SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}}, Dict{SoleLogics.World{Int64}, TruthDict{Dict{Atom{String}, BooleanTruth}}}} with +- frame = SoleLogics.ExplicitCrispUniModalFrame{SoleLogics.World{Int64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}} with +- worlds = ["1", "2", "3", "4", "5"] +- accessibles = + 1 -> [2, 3, 5] + 2 -> [1, 4, 5] + 3 -> [] + 4 -> [1, 2] + 5 -> [1, 2] +- valuations = + 1 -> TruthDict([s => ⊥, p => ⊤]) + 2 -> TruthDict([s => ⊥, p => ⊥]) + 3 -> TruthDict([s => ⊥, p => ⊥]) + 4 -> TruthDict([s => ⊤, p => ⊤]) + 5 -> TruthDict([s => ⊤, p => ⊤]) +``` + +See also [`AbstractAlgebra`](@ref), [`SyntaxLeaf`](@ref), [`Truth`](@ref). +""" diff --git a/src/generation/formula.jl b/src/generation/formula.jl new file mode 100644 index 00000000..f4876c54 --- /dev/null +++ b/src/generation/formula.jl @@ -0,0 +1,320 @@ +using Random +using StatsBase + +import Base: rand +import StatsBase: sample + +# TODO - Move in src/utils.jl (keep here the implementations for struct) +"""$(randatom_docstring)""" +@__rng_dispatch function randatom( + rng::Union{Random.AbstractRNG,Integer}, + a::AbstractAlphabet, + args...; + kwargs... +) + if isfinite(a) + # commented because otherwise this is getting spammed + # @warn "Consider implementing a specific `randatom` dispatch for your alphabet " * + # "type ($(typeof(a))) to increase performances." + + return Base.rand(initrng(rng), atoms(a), args...; kwargs...) + else + error("Please provide method randatom(rng::$(typeof(rng)), " * + "alphabet::$(typeof(a)), args...; kwargs...)") + end +end + +"""$(randatom_unionalphabet_docstring)""" +@__rng_dispatch function randatom( + rng::Union{Integer,AbstractRNG}, + a::UnionAlphabet, + args...; + atompicking_mode::Symbol=:uniform, + subalphabets_weights::Union{ + Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing, + kwargs... +) + _atompicking_modes = [:uniform, :uniform_subalphabets, :weighted] + if !(atompicking_mode in _atompicking_modes) + throw(ArgumentError("Invalid value for `atompicking_mode` ($(atompicking_mode))." * + "Chosee between $(atompicking_modes).")) + end + + rng = initrng(rng) + alphs = subalphabets(a) + + if atompicking_mode == :weighted + if isnothing(subalphabets_weights) + throw(ArgumentError("`:weighted` picking_mode requires weights in " * + "`subalphabets_weights` ")) + end + + if length(subalphabets_weights) != length(alphs) + throw(ArgumentError("Mismatching numbers of alphabets " * + "($(length(alphs))) and weights ($(length(subalphabets_weights))).")) + end + + subalphabets_weights = StatsBase.weights(subalphabets_weights) + pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) + else + subalphabets_weights = begin + # this atomatically excludes subalphabets with empty threshold vector + if atompicking_mode == :uniform_subalphabets + # set the weight of the empty alphabets to zero + weights = Weights(ones(Int, length(alphs))) + weights[natoms.(alphs) .== 0] .= 0 + elseif atompicking_mode == :uniform + weights = Weights(natoms.(alphs)) + end + weights + end + pickedalphabet = sample(rng, alphs, subalphabets_weights) + end + + return randatom(rng, pickedalphabet) +end + + + +"""$(rand_abstractalphabet_docstring)""" +@__rng_dispatch function SoleLogics.rand( + rng::Union{Integer,AbstractRNG}, + a::AbstractAlphabet, + args...; + kwargs... +) + randatom(initrng(rng), a, args...; kwargs...) +end +function rand(rng::Random.AbstractRNG, a::SoleLogics.AbstractAlphabet, args...; kwargs...) + # This is needed to avoid a dispatch ambiguity caused by @__rng_dispatch: + # rand(rng::Union{Integer, Random.AbstractRNG}, a::SoleLogics.AbstractAlphabet, args...; kwargs...) + # @ SoleLogics ~/.julia/fork/SoleLogics.jl/src/generation/formula.jl:82 + # rand(rng::Random.AbstractRNG, X) + # @ Random ~/.julia/julia_exec/julia-1.9.0/share/julia/stdlib/v1.9/Random/src/Random.jl:256 + randatom(rng, a, args...; kwargs...) +end + +"""$(rand_abstractlogic_docstring)""" +@__rng_dispatch function Base.rand( + rng::Union{Integer,AbstractRNG}, + height::Integer, + l::AbstractLogic, + args...; + kwargs... +) + Base.rand(initrng(rng), height, grammar(l), args...; kwargs...) +end + +"""$(rand_completeflatgrammar_docstring)""" +@__rng_dispatch function Base.rand( + rng::Union{Integer,AbstractRNG}, + height::Integer, + g::CompleteFlatGrammar, + args...; + kwargs... +) + randformula(initrng(rng), height, alphabet(g), operators(g), args...; kwargs...) +end + +"""$(rand_granular_docstring)""" +@__rng_dispatch function Base.rand( + rng::Union{Integer,AbstractRNG}, + height::Integer, + atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, + connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, + args...; + truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}}=nothing, + kwargs... +) + # If Truth's are specified as `operators`, then they cannot be simultaneously + # provided as `truthvalues` + if !(connectives isa AbstractVector{<:Connective} || + !(truthvalues isa AbstractVector{<:Truth}) + ) + throw(ArgumentError("Unexpected connectives and truth values: " * + "$(connectives) and $(truthvalues).")) + end + + atoms = atoms isa AbstractAlphabet ? SoleLogics.atoms(atoms) : atoms + ops = connectives + if !isnothing(truthvalues) + truthvalues = inittruthvalues(truthvalues) + if typejoin(typeof.(truthvalues)...) == Truth + throw(ArgumentError("Truth values " * + "$(truthvalues) must belong to the same algebra " * + "(and have a common supertype that is not Truth).")) + end + + ops = vcat(ops, truthvalues) + end + + randformula(height, atoms, ops, args...; kwargs...) +end + + + +"""$(sample_aw_docstring)""" +@__rng_dispatch function StatsBase.sample( + rng::Union{Integer,AbstractRNG}, + alphabet::AbstractAlphabet, + weights::AbstractWeights, + args...; + kwargs... +) + if isfinite(alphabet) + StatsBase.sample(initrng(rng), atoms(alphabet), weights, args...; kwargs...) + else + error("Please, provide method StatsBase.sample(rng::AbstractRNG, " * + "alphabet::$(typeof(alphabet)), args...; kwargs...).") + end +end + +"""$(sample_lao_docstring)""" +@__rng_dispatch function StatsBase.sample( + rng::Union{Integer,AbstractRNG}, + height::Integer, + l::AbstractLogic, + atomweights::AbstractWeights, + opweights::AbstractWeights, + args...; + kwargs... +) + StatsBase.sample( + initrng(rng), height, grammar(l), atomweights, opweights, args...; kwargs...) +end + +"""$(sample_hgao_docstring)""" +@__rng_dispatch function StatsBase.sample( + rng::Union{Integer,AbstractRNG}, + height::Integer, + g::AbstractGrammar, + args...; + atomweights::Union{Nothing,AbstractWeights}=nothing, + opweights::Union{Nothing,AbstractWeights}=nothing, + kwargs... +) + randformula( + initrng(rng), height, alphabet(g), operators(g), args...; + atompicker = atomweights, opweights = opweights, kwargs...) +end + + + +"""$(randformula_docstring)""" +@__rng_dispatch function randformula( + rng::Union{Integer,AbstractRNG}, + height::Integer, + alphabet::Union{AbstractVector,AbstractAlphabet}, + operators::AbstractVector{<:Operator}, + args...; + modaldepth::Integer=height, + atompicker::Union{Nothing,Function,AbstractWeights,AbstractVector{<:Real}}=randatom, + opweights::Union{Nothing,AbstractWeights,AbstractVector{<:Real}}=nothing, + alphabet_sample_kwargs::Union{Nothing,AbstractVector}=nothing, + kwargs... +) + rng = initrng(rng) + alphabet = convert(AbstractAlphabet, alphabet) + + if (isnothing(opweights)) + opweights = StatsBase.uweights(length(operators)) + elseif (opweights isa AbstractVector) + if !(length(opweights) == length(operators)) + throw(ArgumentError("Mismatching numbers of operators " * + "($(length(operators))) and opweights ($(length(opweights))).")) + end + + opweights = StatsBase.weights(opweights) + end + + if (isnothing(atompicker)) + atompicker = StatsBase.uweights(natoms(alphabet)) + elseif (atompicker isa AbstractVector) + if !(length(atompicker) == natoms(alphabet)) + throw(ArgumentError("Mismatching numbers of atoms " * + "($(natoms(alphabet))) and atompicker ($(length(atompicker))).")) + end + + atompicker = StatsBase.weights(atompicker) + end + + if !(atompicker isa Function) + atomweights = atompicker + atompicker = (rng, dom)->StatsBase.sample(rng, dom, atomweights) + end + + nonmodal_operators = findall(!ismodal, operators) + + # recursive call + function _randformula( + rng::AbstractRNG, + height::Integer, + modaldepth::Integer; + )::SyntaxTree + + if height == 0 + return atompicker(rng, alphabet) + else + # sample operator and generate children + # (modal connectives only if modaldepth is set > 0) + ops, ops_w = (modaldepth > 0) ? + (operators, opweights) : + (operators[nonmodal_operators], opweights[nonmodal_operators]) + + # op = rand(rng, ops) + op = sample(rng, ops, ops_w) + ch = Tuple([ + _randformula(rng, height-1, modaldepth-(ismodal(op) ? 1 : 0)) + for _ in 1:arity(op)]) + return SyntaxTree(op, ch) + end + end + + # if the alphabet is not iterable, this function should not work. + if !isfinite(alphabet) && isnothing(alphabet_sample_kwargs) + throw(ArgumentError("Attempting to generate random formulas from " * + "(infinite) alphabet of type $(typeof(alphabet))!")) + end + + return _randformula(rng, height, modaldepth) +end + +"""$(randformula_hg_docstring)""" +@__rng_dispatch function randformula( + rng::Union{Integer,AbstractRNG}, + height::Integer, + g::AbstractGrammar, + args...; + kwargs... +) + randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) +end + +"""$(randformula_docstring)""" +@__rng_dispatch function randformula( + rng::Union{Integer,AbstractRNG}, + T::Type{AnchoredFormula}, + height::Integer, + alphabet::Union{AbstractVector,AbstractAlphabet}, + operators::AbstractVector{<:Operator}, + args...; + kwargs... +) + alphabet = convert(AbstractAlphabet, alphabet) + baseformula( + randformula(height, alphabet, operators, args...; kwargs...); + alphabet=alphabet, + additional_operators=operators, + ) +end + +@__rng_dispatch function randformula( + rng::Union{Integer,AbstractRNG}, + T::Type{AnchoredFormula}, + height::Integer, + g::AbstractGrammar, + args...; + kwargs... +) + randformula(rng, T, height, alphabet(g), operators(g), args...; kwargs...) +end diff --git a/src/generation/models.jl b/src/generation/models.jl new file mode 100644 index 00000000..b5a96d38 --- /dev/null +++ b/src/generation/models.jl @@ -0,0 +1,31 @@ +using Graphs +using Random + +"""$(randframe_docstring)""" +@__rng_dispatch function randframe( + rng::Union{Integer,AbstractRNG}, + nworlds::Integer, + nedges::Integer, +) + worlds = World.(1:nworlds) + graph = Graphs.SimpleDiGraph(nworlds, nedges, rng=initrng(rng)) + return SoleLogics.ExplicitCrispUniModalFrame(worlds, graph) +end + +"""$(randmodel_docstring)""" +@__rng_dispatch function randmodel( + rng::Union{Integer,AbstractRNG}, + nworlds::Integer, + nedges::Integer, + facts::Vector{<:SyntaxLeaf}, + truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} +) + truthvalues = inittruthvalues(truthvalues) + fr = randframe(rng, nworlds, nedges) + + valuation = Dict( + [w => TruthDict([f => rand(truthvalues) for f in facts]) for w in fr.worlds] + ) + + return KripkeStructure(fr, valuation) +end diff --git a/src/generation/utils.jl b/src/generation/utils.jl new file mode 100644 index 00000000..2c8471b0 --- /dev/null +++ b/src/generation/utils.jl @@ -0,0 +1,55 @@ +# Given a function whose first argument is of type Union{Random.AbstractRNG,Integer}, +# write an identical dispatch defaulting that field to Random.GLOBAL_RNG. +# +# This is useful to adhere to Base.rand methods, where the RNG is not a kwarg and it's +# placed as the first argument. +# +# At the moment, this macro does not handle functions giving a return type hint. +macro __rng_dispatch(ex) + if ex.head != :function + throw(ArgumentError("Expected a function definition")) + end + + fsignature = ex.args[1] + fname = fsignature.args[1] + fargs = fsignature.args[2:end] + + # Later, fargs is sliced from 3rd index to end + if length(fargs) <= 2 + throw(ArgumentError("Expected function's argument to be atleast 2, the first of " * + "which of type Union{Random.AbstractRNG,Integer}.")) + end + + # The first argument one must be an Union{Random.AbstractRNG,Integer}, + # otherwise, this macro makes no sense. + # TODO - this check should be made out of quote environment + # quote + # if !isa($(esc(fargs[2])), Union{Random.AbstractRNG,Integer}) + # throw(ArgumentError("Expected function's first argument to be of type " * + # "Union{Random.AbstractRNG,Integer}.")) + # end + # end + + # At this point, fargs is shaped similar to: + # + # Any[ + # :($(Expr(:parameters, :(kwargs...)))), + # :(rng::Union{Random.AbstractRNG, Integer}), + # :(a::AbstractAlphabet), :(args...) + # ] + # + # We would like to remove both kwargs... and `rng`. + # kwargs are reinserted manually later (just writing $(newargs...) gives problems). + newargs = Any[fargs[3:end]...] + + # Define both dispatches; + # the names are the same, and the new dispatches (the one without rng) + # also gets the docstring written just before the macro invocation. + quote + Core.@__doc__ function $(esc(fname))($(newargs...); kwargs...) + $(esc(fname))(Random.GLOBAL_RNG, $(newargs...); kwargs...) + end + + $(esc(ex)) + end +end diff --git a/src/modal-logic.jl b/src/modal-logic.jl index 707aac74..df6204e8 100644 --- a/src/modal-logic.jl +++ b/src/modal-logic.jl @@ -645,7 +645,7 @@ end function interpret( φ::Formula, i::AbstractKripkeStructure, - w::Union{AbstractWorld,Nothing}, + w::Union{Nothing,AbstractWorld}, )::Formula return error("Please, provide method interpret(::$(typeof(φ)), ::$(typeof(i)), ::$(typeof(w))).") end diff --git a/src/old-code/install.jl b/src/old-code/install.jl deleted file mode 100644 index 221ad9df..00000000 --- a/src/old-code/install.jl +++ /dev/null @@ -1,29 +0,0 @@ -# This file can be used to automatically resolve dependencies -# involving unregistered packages. -# To do so, simply call `install` one time for each package -# respecting the correct dependency order. - -using Pkg -Pkg.activate(".") - -# Remove the specified package (do not abort if it is already removed) and reinstall it. -function install(package::String, url::String, rev::String) - printstyled(stdout, "\nRemoving: $package\n", color=:green) - try - Pkg.rm(package) - catch e - println(); showerror(stdout, e); println() - end - - printstyled(stdout, "\nFetching: $url at branch $rev\n", color=:green) - try - Pkg.add(url=url, rev=rev) - printstyled(stdout, "\nPackage $package instantiated correctly\n", color=:green) - catch e - println(); showerror(stdout, e); println() - end -end - -install("SoleBase", "https://github.com/aclai-lab/SoleBase.jl", "dev-v0.9.1") - -Pkg.instantiate() diff --git a/src/random.jl b/src/random.jl deleted file mode 100644 index 51c7f5a5..00000000 --- a/src/random.jl +++ /dev/null @@ -1,470 +0,0 @@ -using Graphs -using Random -using StatsBase - -import Random: rand -import StatsBase: sample - -#= ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Formulas ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ =# - -doc_rand = """ - Base.rand( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - alphabet::AbstractAlphabet, - args...; - kwargs... - )::Atom - - Base.rand( - [rng::AbstractRNG = Random.GLOBAL_RNG], - height::Integer, - l::AbstractLogic, - args...; - kwargs... - )::Formula - - Base.rand( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - height::Integer, - g::CompleteFlatGrammar, - args... - )::Formula - - Base.rand( - height::Integer, - connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, - atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, - truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, - args...; - rng::AbstractRNG = Random.GLOBAL_RNG, - kwargs... - )::Formula - -Randomly generate an [`Atom`](@ref) from an [`AbstractAlphabet`](@ref) according to a -uniform distribution. If a [`CompleteFlatGrammar`](@ref) is provided together with an -`height` a [`Formula`](@ref) could also be generated. - -# Implementation -If the `alphabet` is finite, the function defaults to `rand(rng, atoms(alphabet))`; -otherwise, it must be implemented, and additional keyword arguments should be provided -in order to limit the (otherwise infinite) sampling domain. - -See also -[`AbstractAlphabet`](@ref), [`Atom`](@ref), [`CompleteFlatGrammar`](@ref), -[`Formula`](@ref), [`randformula`](@ref). -""" - -"""$(doc_rand)""" -function Base.rand(a::AbstractAlphabet, args...; kwargs...) - Base.rand(Random.GLOBAL_RNG, a, args...; kwargs...) -end - -function Base.rand( - rng::AbstractRNG, - a::AbstractAlphabet, - args...; - kwargs... -) - randatom(rng, a, args...; kwargs...) -end - -function Base.rand(height::Integer, l::AbstractLogic, args...; kwargs...) - Base.rand(Random.GLOBAL_RNG, height, l, args...; kwargs...) -end - -function Base.rand( - rng::AbstractRNG, - height::Integer, - l::AbstractLogic, - args...; - kwargs... -) - Base.rand(rng, grammar(l), args...; kwargs...) -end - -# For the case of a CompleteFlatGrammar, the alphabet and the operators suffice. -function Base.rand( - height::Integer, - g::CompleteFlatGrammar, - args... -) - Base.rand(Random.GLOBAL_RNG, height, g, args...) -end - -function Base.rand( - rng::AbstractRNG, - height::Integer, - g::CompleteFlatGrammar, - args...; - kwargs... -) - randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) -end - -function Base.rand( - height::Integer, - atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, - connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, - truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, - args...; - kwargs... -) - Base.rand(Random.GLOBAL_RNG, height, atoms, connectives, truthvalues, args...; - kwargs...) -end - -function Base.rand( - rng::AbstractRNG, - height::Integer, - atoms::Union{AbstractVector{<:Atom},AbstractAlphabet}, - connectives::Union{AbstractVector{<:Operator},AbstractVector{<:Connective}}, - truthvalues::Union{Nothing,AbstractAlgebra,AbstractVector{<:Truth}} = nothing, - args...; - kwargs... -) - # If Truth's are specified as `operators`, then they cannot be simultaneously - # provided as `truthvalues` - @assert (connectives isa AbstractVector{<:Connective} || - !(truthvalues isa AbstractVector{<:Truth}) - ) "Unexpected connectives and truth values: $(connectives) and $(truthvalues)." - - atoms = atoms isa AbstractAlphabet ? SoleLogics.atoms(atoms) : atoms - ops = connectives - if !isnothing(truthvalues) - truthvalues = inittruthvalues(truthvalues) - @assert typejoin(typeof.(truthvalues)...) != Truth "Truth values " * - "$(truthvalues) must belong to the same algebra " * - "(and have a common supertype that is not Truth)." - ops = vcat(ops, truthvalues) - end - - randformula(height, ops, atoms, args...; rng=rng, kwargs...) -end - -doc_sample = """ - function StatsBase.sample( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - alphabet::AbstractAlphabet, - weights::AbstractWeights, - args...; - kwargs... - ) - - function StatsBase.sample( - rng::AbstractRNG, - l::AbstractLogic, - weights::AbstractWeights, - args...; - kwargs... - ) - - StatsBase.sample( - [rng::AbstractRNG = Random.GLOBAL_RNG,] - height::Integer, - g::AbstractGrammar, - [opweights::Union{Nothing,AbstractWeights} = nothing,] - args...; - kwargs... - )::Formula - -Randomly sample an [`Atom`](@ref) from an `alphabet`, or a logic formula of given `height` -from a grammar `g`. -Sampling is weighted, thus, for example, if the first weight in `weights` is higher than -the others, then the first atom in the alphabet is selected more frequently. - -See also [`AbstractAlphabet`](@ref), [`AbstractWeights`](@ref), [`Atom`](@ref). -""" -function StatsBase.sample( - alphabet::AbstractAlphabet, - weights::AbstractWeights, - args...; - kwargs... -)::Atom - StatsBase.sample(Random.GLOBAL_RNG, alphabet, weights, args...; kwargs...) -end - -function StatsBase.sample( - rng::AbstractRNG, - alphabet::AbstractAlphabet, - weights::AbstractWeights, - args...; - kwargs... -)::Atom - if isfinite(alphabet) - StatsBase.sample(rng, atoms(alphabet), weights, args...; kwargs...) - else - error("Please, provide method StatsBase.sample(rng::AbstractRNG, " * - "alphabet::$(typeof(alphabet)), args...; kwargs...).") - end -end - -function StatsBase.sample( - l::AbstractLogic, - atomweights::AbstractWeights, - opweights::AbstractWeights, - args...; - kwargs... -) - StatsBase.sample(Random.GLOBAL_RNG, l, atomweights, opweights, args...; kwargs...) -end - -function StatsBase.sample( - rng::AbstractRNG, - l::AbstractLogic, - atomweights::AbstractWeights, - opweights::AbstractWeights, - args...; - kwargs... -) - StatsBase.sample(init(rng), grammar(l), atomweights, opweights, args...; kwargs...) -end - -"""$(doc_sample)""" -function StatsBase.sample( - height::Integer, - g::AbstractGrammar, - atomweights::Union{Nothing,AbstractWeights} = nothing, - opweights::Union{Nothing,AbstractWeights} = nothing, - args...; - kwargs... -) - StatsBase.sample(Random.GLOBAL_RNG, height, g, atomweights, opweights, args...; - kwargs...) -end - -"""$(doc_sample)""" -function StatsBase.sample( - rng::AbstractRNG, - height::Integer, - g::AbstractGrammar, - atomweights::Union{Nothing,AbstractWeights} = nothing, - opweights::Union{Nothing,AbstractWeights} = nothing, - args...; - kwargs... -) - randformula( - rng, height, alphabet(g), operators(g), args...; - # atompicker=(rng,dom)->StatsBase.sample(rng, dom, atomweights), kwargs...) - atompicker = atomweights, opweights = opweights, kwargs...) -end - -#= ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CompleteFlatGrammar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ =# - -# TODO -# - make rng first (optional) argument of randformula (see above) -# - in randformula, keyword argument alphabet_sample_kwargs that are unpacked upon sampling atoms, as in: Base.rand(rng, a; alphabet_sample_kwargs...). This would allow to sample from infinite alphabets, so when this parameter, !isfinite(alphabet) is allowed! - -# TODO @Mauro implement this method. -doc_randformula = """ - randformula( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, ] - height::Integer, - alphabet, - operators::AbstractVector; - kwargs... - )::SyntaxTree - - randformula( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, ] - height::Integer, - g::AbstractGrammar; - kwargs... - )::SyntaxTree - -Return a pseudo-randomic `SyntaxTree`. - -# Arguments -- `rng::Union{Intger,AbstractRNG} = Random.GLOBAL_RNG`: random number generator; -- `height::Integer`: height of the generated structure; -- `alphabet::AbstractAlphabet`: collection from which atoms are chosen randomly; -- `operators::AbstractVector`: vector from which legal operators are chosen; -- `g::AbstractGrammar`: alternative to passing alphabet and operators separately. (TODO explain?) - -# Keyword Arguments -- `modaldepth::Integer`: maximum modal depth -- `atompicker::Function`: method used to pick a random element. For example, this could be - Base.rand or StatsBase.sample. -- `opweights::AbstractWeights`: weight vector over the set of operators (see `StatsBase`). - -# Examples - -```julia-repl -julia> syntaxstring(randformula(4, ExplicitAlphabet([1,2]), [NEGATION, CONJUNCTION, IMPLICATION])) -"¬((¬(¬(2))) → ((1 → 2) → (1 → 2)))" -``` - -See also [`AbstractAlphabet`](@ref), [`SyntaxBranch`](@ref). -""" - -"""$(doc_randformula)""" -function randformula( - rng::Union{Integer,AbstractRNG}, - height::Integer, - alphabet::Union{AbstractVector,AbstractAlphabet}, - operators::AbstractVector; - modaldepth::Integer = height, - atompicker::Union{Function,AbstractWeights,AbstractVector{<:Real},Nothing} = randatom, - opweights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing -)::SyntaxTree - - rng = initrng(rng) - alphabet = convert(AbstractAlphabet, alphabet) - @assert all(x->x isa Operator, operators) "Unexpected object(s) passed as" * - " operator:" * " $(filter(x->!(x isa Operator), operators))" - - if (isnothing(opweights)) - opweights = StatsBase.uweights(length(operators)) - elseif (opweights isa AbstractVector) - @assert length(opweights) == length(operators) "Mismatching numbers of operators " * - "($(length(operators))) and opweights ($(length(opweights)))." - opweights = StatsBase.weights(opweights) - end - - if (isnothing(atompicker)) - atompicker = StatsBase.uweights(natoms(alphabet)) - elseif (atompicker isa AbstractVector) - @assert length(atompicker) == natoms(alphabet) "Mismatching numbers of atoms " * - "($(natoms(alphabet))) and atompicker ($(length(atompicker)))." - atompicker = StatsBase.weights(atompicker) - end - - if !(atompicker isa Function) - atomweights = atompicker - atompicker = (rng, dom)->StatsBase.sample(rng, dom, atomweights) - end - - nonmodal_operators = findall(!ismodal, operators) - - # recursive call - function _randformula( - rng::AbstractRNG, - height::Integer, - modaldepth::Integer; - )::SyntaxTree - - if height == 0 - return atompicker(rng, alphabet) - else - # Sample operator and generate children (modal connectives only if modaldepth > 0) - ops, ops_w = begin - if modaldepth > 0 - operators, opweights - else - operators[nonmodal_operators], opweights[nonmodal_operators] - end - end - - # op = rand(rng, ops) - op = sample(rng, ops, ops_w) - ch = Tuple([ - _randformula(rng, height-1, modaldepth-(ismodal(op) ? 1 : 0)) - for _ in 1:arity(op)]) - return SyntaxTree(op, ch) - end - end - - # If the alphabet is not iterable, this function should not work. - if !isfinite(alphabet) - @warn "Attempting to generate random formulas from " * - "(infinite) alphabet of type $(typeof(alphabet))!" - end - - return _randformula(rng, height, modaldepth) -end - -function randformula( - rng::AbstractRNG, - height::Integer, - g::AbstractGrammar, - args...; - kwargs... -) - randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) -end - -# Helper -function randformula( - height::Integer, - args...; - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, - kwargs... -) - randformula(initrng(rng), height, args...; kwargs...) -end - -#= ~~~~~~~~~~~~~~~~~~~~~~~~~~ Kripke Models generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ =# -""" - function randframe( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] - nworlds::Int, - nedges::Int, - facts::Vector{SyntaxLeaf} - end - -Return a random Kripke Frame, which is a directed graph interpreted as a -[`SoleLogics.ExplicitCrispUniModalFrame`](@ref). The underlying graph is generated using -[`Graphs.SimpleGraphs.SimpleDiGraph`](@ref). - -# Arguments: -* `rng` is a random number generator, or the seed used to create one; -* `nworld` is the number of worlds (nodes) in the frame. Worlds are numbered from `1` - to `nworld` included. -* `nedges` is the number of relations (edges) in the frame; -* `facts` is a vector of generic [`SyntaxLeaf`](@ref). - -See also [`SyntaxLeaf`](@ref), [`Graphs.SimpleGraphs.SimpleDiGraph`](@ref), -[`SoleLogics.ExplicitCrispUniModalFrame`](@ref). -""" -function randframe( - nworlds::Int, - nedges::Int; - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG -) - randframe(rng, nworlds, nedges) -end - -function randframe( - rng::Union{Integer,AbstractRNG}, - nworlds::Int, - nedges::Int -) - worlds = World.(1:nworlds) - graph = Graphs.SimpleDiGraph(nworlds, nedges, rng=initrng(rng)) - return SoleLogics.ExplicitCrispUniModalFrame(worlds, graph) -end - -""" - function randmodel( - [rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG,] - nworlds::Int, - nedges::Int, - facts::Vector{SyntaxLeaf}; - truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} = BooleanAlgebra(); - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG - ) -""" -function randmodel( - nworlds::Int, - nedges::Int, - facts::Vector{<:SyntaxLeaf}, - truthvalues::Union{AbstractAlgebra,AbstractVector{<:Truth}} = BooleanAlgebra(); - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG -) - truthvalues = inittruthvalues(truthvalues) - randmodel(initrng(rng), nworlds, nedges, facts, truthvalues) -end - -function randmodel( - rng::AbstractRNG, - nworlds::Int, - nedges::Int, - facts::Vector{<:SyntaxLeaf}, - truthvalues::AbstractVector{<:Truth} -) - fr = randframe(rng, nworlds, nedges) - valuation = Dict( - [w => TruthDict([f => rand(truthvalues) for f in facts]) for w in fr.worlds] - ) - - return KripkeStructure(fr, valuation) -end diff --git a/src/syntax-utils.jl b/src/syntax-utils.jl index 7b70037c..f9e52e52 100644 --- a/src/syntax-utils.jl +++ b/src/syntax-utils.jl @@ -111,7 +111,7 @@ struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure function LeftmostLinearForm( tree::SyntaxTree, - c::Union{<:SoleLogics.Connective,Nothing} = nothing + c::Union{Nothing,<:SoleLogics.Connective} = nothing ) # Check c correctness; it should not be nothing (thus, auto inferred) if # tree root contains something that is not a connective @@ -636,7 +636,7 @@ function treewalk( rng::AbstractRNG = Random.GLOBAL_RNG, criterion::Function = c->true, returnnode::Bool = false, - transformnode::Union{Function,Nothing} = nothing, + transformnode::Union{Nothing,Function} = nothing, ) chs = children(st) diff --git a/src/types/logic.jl b/src/types/logic.jl index b160e4e5..c01d54e3 100644 --- a/src/types/logic.jl +++ b/src/types/logic.jl @@ -36,7 +36,7 @@ true ``` # Interface -- `atoms(a::AbstractAlphabet)::Bool` +- `atoms(a::AbstractAlphabet)::AbstractVector` - `Base.isfinite(::Type{<:AbstractAlphabet})::Bool` - `randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...)::AbstractAtom` @@ -146,29 +146,6 @@ function natoms(a::AbstractAlphabet)::Integer end end -""" - randatom(a::AbstractAlphabet, args...; kwargs...) - randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...) - -Return a random atom from a *finite* alphabet. - -See also [`natoms`](@ref), [`AbstractAlphabet`](@ref). -""" -function randatom(a::AbstractAlphabet, args...; kwargs...) - randatom(Random.GLOBAL_RNG, a, args...; kwargs...) -end - -function randatom(rng::Union{Random.AbstractRNG, Integer}, a::AbstractAlphabet, args...; kwargs...) - if isfinite(a) - # TODO: note that `atoms(a)` can lead to brutal reduction in performance, - # if one forgets to implement specific methods for `randatom` for custom alphabets! - return Base.rand(rng, atoms(a), args...; kwargs...) - else - error("Please provide method randatom(rng::$(typeof(rng)), " * - "alphabet::$(typeof(a)), args...; kwargs...)") - end -end - # Helper function Base.length(a::AbstractAlphabet) @warn "Please use `natoms` instead of `Base.length` with alphabets." diff --git a/src/utils.jl b/src/utils.jl index a2076a91..0bf0792d 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -643,62 +643,6 @@ function Base.in(p::Atom, a::UnionAlphabet) return any(sa -> Base.in(p, sa), subalphabets(a)) end -""" - randatom( - rng::Union{Integer,AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol=:uniform, - subalphabets_weights::Union{AbstractWeights,AbstractVector{<:Real},Nothing} = nothing - )::Atom - -Sample an atom from a `UnionAlphabet`. By default, the sampling is uniform with respect to the atoms. -However, 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. - -See also [`UnionAlphabet`](@ref). -""" -function randatom( - rng::Union{Integer, AbstractRNG}, - a::UnionAlphabet; - atompicking_mode::Symbol = :uniform, - subalphabets_weights::Union{AbstractWeights, AbstractVector{<:Real}, Nothing} = nothing, -)::Atom - - # @show a - @assert atompicking_mode in [:uniform, :uniform_subalphabets, :weighted] "Value for `atompicking_mode` not..." - rng = initrng(rng) - alphs = subalphabets(a) - - if atompicking_mode == :weighted - if isnothing(subalphabets_weights) - error("`:weighted` picking_mode requires weights in `subalphabets_weights` ") - end - @assert length(subalphabets_weights)==length(alphs) "Mismatching numbers of alphabets "* - "($(length(alphs))) and weights ($(length(subalphabets_weights)))." - subalphabets_weights = StatsBase.weights(subalphabets_weights) - pickedalphabet = StatsBase.sample(rng, alphs, subalphabets_weights) - else - subalphabets_weights = begin - # This atomatically excludes subalphabets with empty threshold vector - if atompicking_mode == :uniform_subalphabets - # set the weight of the empty alphabets to zero - weights = Weights(ones(Int, length(alphs))) - weights[natoms.(alphs) == 0] .= 0 - elseif atompicking_mode == :uniform - weights = Weights(natoms.(alphs)) - end - weights - end - pickedalphabet = sample(rng, alphs, subalphabets_weights) - end - # @show a - # @show subalphabets_weights - # @show pickedalphabet - return randatom(rng, pickedalphabet) -end - ############################################################################################ #### CompleteFlatGrammar ################################################################### ############################################################################################ @@ -927,4 +871,3 @@ function _baselogic(; return BaseLogic(grammar, algebra) end - diff --git a/src/old-code/anchored-formula.jl b/src/utils/anchored-formula.jl similarity index 91% rename from src/old-code/anchored-formula.jl rename to src/utils/anchored-formula.jl index 0bc22f82..5fe24e9b 100644 --- a/src/old-code/anchored-formula.jl +++ b/src/utils/anchored-formula.jl @@ -243,7 +243,7 @@ end """ parseformula( - ::Type{AnchoredFormula}, + T::Type{AnchoredFormula}, expr::String, additional_operators::Union{Nothing,Vector{<:Operator}} = nothing; operators::Union{Nothing,Vector{<:Operator}}, @@ -296,44 +296,4 @@ function parseformula( kwargs..., ) AnchoredFormula(logic, parseformula(SyntaxTree, expr, operators(logic); kwargs...)) -end - -function randformula( - height::Integer, - g::AbstractGrammar; - kwargs... -)::AnchoredFormula - _alphabet = alphabet(g) - _operators = operators(g) - baseformula( - randformula(height, _alphabet, _operators; kwargs...); - alphabet = _alphabet, - additional_operators = _operators - ) -end - -function randformula( - ::Type{AnchoredFormula}, - height::Integer, - alphabet, - operators::AbstractVector{<:Operator}; - kwargs... -)::AnchoredFormula - alphabet = convert(AbstractAlphabet, alphabet) - baseformula( - randformula(height, alphabet, operators; kwargs...); - alphabet = alphabet, - additional_operators = operators, - ) -end - -function randformula( - T::Type{AnchoredFormula}, - height::Integer, - g::AbstractGrammar, - args...; - rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG, - kwargs... -)::AnchoredFormula - randformula(T, height, alphabet(g), operators(g), args...; rng=rng, kwargs...) -end +end \ No newline at end of file diff --git a/src/utils/parse.jl b/src/utils/parse.jl index 390aba2a..33838984 100644 --- a/src/utils/parse.jl +++ b/src/utils/parse.jl @@ -23,25 +23,21 @@ const BASE_PARSABLE_CONNECTIVES = [ ] |> unique doc_parseformula = """ - parseformula(expr::String, additional_operators = nothing; kwargs...) - parseformula( - F::Type{<:SyntaxTree}, + [F::Type{<:Formula}=SyntaxTree], expr::String, additional_operators::Union{Nothing,AbstractVector} = nothing; - function_notation::Bool = false, - atom_parser::Base.Callable = Atom{String}, - additional_whitespaces::Vector{Char} = Char[], - opening_parenthesis::String = $(repr(DEFAULT_OPENING_PARENTHESIS)), - closing_parenthesis::String = $(repr(DEFAULT_CLOSING_PARENTHESIS)), - arg_delim::String = $(repr(DEFAULT_ARG_DELIM)) + kwargs... )::F - parseformula(F::Type{<:Formula}, expr::String, additional_operators = nothing; kwargs...) - parseformula(F::Type{<:SyntaxTree}, expr::String, logic::AbstractLogic; kwargs...) + parseformula( + F::Type{<:SyntaxTree}, + expr::String, + logic::AbstractLogic; + kwargs... + )::F Parse a formula of type `F` from a string expression (its [`syntaxstring`](@ref)). -When `F` is not specified, it defaults to `SyntaxTree`. By default, this function is only able to parse operators in [`SoleLogics.BASE_PARSABLE_CONNECTIVES`](@ref) (e.g., diff --git a/test/generation/formula.jl b/test/generation/formula.jl new file mode 100644 index 00000000..8a115bab --- /dev/null +++ b/test/generation/formula.jl @@ -0,0 +1,157 @@ +using StatsBase +using SoleLogics: parseformula, @__rng_dispatch +using Random + +import SoleLogics: arity, syntaxstring + +# @testset "randformula" begin + +_alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) +_operators = [NEGATION, CONJUNCTION, IMPLICATION] +w = [10,1,1] + +@test_nowarn [randformula(i, _alphabet, _operators) for i in 1:5] +@test_nowarn [randformula(i, _alphabet, _operators, opweights=w) for i in 1:2] +@test_nowarn [randformula(i, _alphabet, _operators, opweights=w) for i in 1:5] + +# end # endof test set + + + +# @testset "generation w. custom operators" begin + +TERNOP = SoleLogics.NamedConnective{:⇶}() +SoleLogics.arity(::typeof(TERNOP)) = 3 + +QUATERNOP = SoleLogics.NamedConnective{:⩰}() +SoleLogics.arity(::typeof(QUATERNOP)) = 4 + +_alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) +_operators = [NEGATION, CONJUNCTION, IMPLICATION, + DiamondRelationalConnective(globalrel), BoxRelationalConnective(globalrel)] +w = [5,1,1,1,1,1,1] + +@test all([begin + f = randformula(3, _alphabet, _operators) + s = syntaxstring(f) + s == syntaxstring(parseformula(s)) + end for i in 1:10]) + +@test all([begin + f = randformula(i%5, _alphabet, [_operators..., TERNOP, QUATERNOP]) + s = syntaxstring(f; function_notation = true) + s == syntaxstring( + parseformula( + s, [_operators..., TERNOP, QUATERNOP]; function_notation = true), + function_notation = true) + end for i in 1:10]) + +# @test all([begin +# f = randformula(i%5, _alphabet, _operators) +# s = syntaxstring(f; function_notation = true) +# s == syntaxstring(parseformula(SoleLogics.AnchoredFormula, s; function_notation = true); +# function_notation = true) +# end for i in 1:10]) + +# end # endof test set + + + +# @testset "Dispatches made with @__rng_dispatch" begin + +my_alph = ExplicitAlphabet(1:5) +my_ops = [∧,¬] +my_grammar = SoleLogics.CompleteFlatGrammar(my_alph, my_ops) +my_logic = propositionallogic(alphabet=my_alph) + +@test_nowarn randatom(my_alph) +@test randatom(42, my_alph) == Atom(4) + +non_finite_alph = AlphabetOfAny{Atom{String}}() +@test_throws Exception randatom(42, non_finite_alph) + +alph2 = ExplicitAlphabet(6:10) +unionalph = UnionAlphabet([my_alph,alph2]) +@test_nowarn randatom(unionalph) +@test randatom(42, unionalph) == Atom(6) + +_subalphabets_weights_test_dim = 100 +@test count(x -> x<5, + SoleLogics.value.([randatom(unionalph; atompicking_mode=:weighted, subalphabets_weights=[5,1]) + for i in 1:_subalphabets_weights_test_dim]) + ) > convert(Int32, (_subalphabets_weights_test_dim/2)) + +@test_throws UndefVarError randatom(unionalph; atompicking_mode=:invalid_mode) +@test_throws ArgumentError randatom(unionalph; atompicking_mode=:weighted) +@test_throws ArgumentError randatom( + unionalph; atompicking_mode=:weighted, subalphabets_weights=[1]) +@test_nowarn randatom(unionalph; atompicking_mode=:uniform_subalphabets) + +@test_nowarn rand(my_alph) +@test_nowarn rand(42, my_alph) == Atom(4) + +@test_nowarn rand(4) +@test rand(MersenneTwister(42), 2, my_logic) |> syntaxstring == "(1 ∧ 5) ∨ ¬2" + +@test_nowarn rand(4, my_grammar) +@test_nowarn rand(Random.MersenneTwister(1), 4, my_grammar) + +@test_nowarn rand(Random.MersenneTwister(42), 4, my_alph, [CONJUNCTION, DISJUNCTION]) +@test_throws ArgumentError rand( + Random.MersenneTwister(42), 4, my_alph, [TOP]; truthvalues=[TOP]) + +# Testing rand edge case: truth values common ancestor is Truth; +# here, we make a custom Truth +struct MyTruth <: Truth + val::Integer +end +MyTruthTOP = MyTruth(5); +syntaxstring(mt::MyTruth) = mt.val +# Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:MyTruth}) = Truth + +@test_throws ArgumentError rand( + 42, 4, my_alph, [CONJUNCTION]; truthvalues=Truth[TOP,MyTruthTOP]); + +@test_nowarn sample(my_alph, Weights([1,2,3,4,5])) +@test sample(2, my_alph, Weights([1,2,3,4,5])) == Atom(3) +@test_throws Exception sample(2, non_finite_alph, Weights([1,2,3,4,5])) + +@test_nowarn StatsBase.sample(2, my_logic, Weights([1,2,3,4,5]), Weights([1,2])) +@test StatsBase.sample( + MersenneTwister(42), + 2, + my_logic, + Weights([1,2,3,4,5]), + Weights([1,2]) + ) |> syntaxstring == "(1 ∧ 1) ∨ (5 → 2)" + +@test StatsBase.sample(MersenneTwister(42), 2, my_grammar) |> syntaxstring == "¬(1 ∧ 1)" +@test_nowarn StatsBase.sample(2, my_grammar) + +@test_nowarn randformula(4, my_grammar) +@test_nowarn randformula(MersenneTwister(1), 4, my_grammar) +@test_nowarn randformula(4, my_alph, my_ops) +@test_throws ArgumentError randformula(4, my_alph, my_ops; atompicker=[1,2]) +@test_throws ArgumentError randformula(4, non_finite_alph, my_ops) + +@test_nowarn randformula(MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:5) +@test_throws ArgumentError randformula( + MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:6) +@test_nowarn randformula(MersenneTwister(1), 4, my_alph, my_ops; atompicker = 1:5) + +@test_nowarn randformula(2, my_alph, my_ops) +@test_nowarn randformula(MersenneTwister(42), 2, my_alph, my_ops) + +@test_nowarn randformula(4, my_grammar) +@test_nowarn randformula(MersenneTwister(42), 4, my_grammar) + +# end # endof test set + + + +# @testset "@__rng_dispatch" begin + +@test_throws LoadError @eval @__rng_dispatch 1+1 +@test_throws LoadError @eval @__rng_dispatch function foo() end + +# end # endof test set diff --git a/test/generation/models.jl b/test/generation/models.jl new file mode 100644 index 00000000..74f8ca20 --- /dev/null +++ b/test/generation/models.jl @@ -0,0 +1,11 @@ +using Random + +@testset "randframe + randmodel" begin + +@test_nowarn randframe(42, 10, 20) +@test_nowarn randframe(Random.MersenneTwister(42), 10, 20) + +@test_nowarn randmodel(42, 5, 10, [Atom("s"), Atom("p")], BooleanAlgebra()) +@test_nowarn randmodel(42, 5, 10, [Atom("s"), Atom("p")], BooleanAlgebra()) + +end diff --git a/test/normalize.jl b/test/normalize.jl index fd5343ee..fd5fa3f4 100644 --- a/test/normalize.jl +++ b/test/normalize.jl @@ -51,7 +51,13 @@ check(φ, K1, w0) N = 200 for K in [K0, K1] for i in 1:N - _ops = rand([SoleLogics.BASE_MODAL_CONNECTIVES, union(SoleLogics.BASE_MODAL_CONNECTIVES, [⊤, ⊥])]) + _ops = Vector{Operator}([ + rand([ + SoleLogics.BASE_MODAL_CONNECTIVES, + union(SoleLogics.BASE_MODAL_CONNECTIVES, + [⊤, ⊥]) + ])... + ]) _φ = randformula(MersenneTwister(i), 3, alph_vector, _ops) _nφ = normalize(_φ) # @show syntaxstring(φ) diff --git a/test/random.jl b/test/random.jl deleted file mode 100644 index 712a392c..00000000 --- a/test/random.jl +++ /dev/null @@ -1,82 +0,0 @@ -using StatsBase -import SoleLogics: arity -using Random - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ random logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -@testset "Random" begin - -_alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) -_operators = [NEGATION, CONJUNCTION, IMPLICATION] -w = [10,1,1] - -@test_nowarn [randformula(i, _alphabet, _operators) for i in 1:15] -@test_nowarn [randformula(i, _alphabet, _operators, opweights=w) for i in 1:10] - -end - -@testset "Random+Parsing" begin - -TERNOP = SoleLogics.NamedConnective{:⇶}() -SoleLogics.arity(::typeof(TERNOP)) = 3 - -QUATERNOP = SoleLogics.NamedConnective{:⩰}() -SoleLogics.arity(::typeof(QUATERNOP)) = 4 - -_alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) -_operators = [NEGATION, CONJUNCTION, IMPLICATION, - DiamondRelationalConnective(globalrel), BoxRelationalConnective(globalrel)] -w = [5,1,1,1,1,1,1] - -@test all([begin - f = randformula(3, _alphabet, _operators) - s = syntaxstring(f) - s == syntaxstring(parseformula(s)) - end for i in 1:1000]) - -@test all([begin - f = randformula(i%5, _alphabet, [_operators..., TERNOP, QUATERNOP]) - # "function_notation = true" is essential in each parsing and string conversion - # to represent ternary operators (or generally operators whose arity is > 2). - s = syntaxstring(f; function_notation = true) - s == syntaxstring( - parseformula( - s, [_operators..., TERNOP, QUATERNOP]; function_notation = true), - function_notation = true) - end for i in 1:1000]) - -# @test all([begin -# f = randformula(i%5, _alphabet, _operators) -# s = syntaxstring(f; function_notation = true) -# s == syntaxstring(parseformula(AnchoredFormula, s; function_notation = true); -# function_notation = true) -# end for i in 1:1000]) - -end - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ random interfaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -using Random - -alph = ExplicitAlphabet(1:5) -ops = [∧,¬] -g = SoleLogics.CompleteFlatGrammar(alph, ops) - -@test_nowarn Base.rand(4, g) -@test_nowarn Base.rand(Random.MersenneTwister(1), 4, g) -@test_nowarn randformula(4, g) -@test_nowarn randformula(4, g; rng = Random.MersenneTwister(1)) - -@test_nowarn StatsBase.sample(4, g) -@test_nowarn StatsBase.sample(Random.MersenneTwister(1), 4, g) - -@test_nowarn randformula(4, g) -@test_nowarn randformula(Random.MersenneTwister(1), 4, g) -@test_nowarn randformula(4, alph, ops) -@test_nowarn randformula(Random.MersenneTwister(1), 4, alph, ops; atompicker = 1:5) -@test_throws AssertionError randformula(Random.MersenneTwister(1), 4, alph, ops; atompicker = 1:6) - -@test_nowarn StatsBase.sample(4, g, Weights([1:natoms(alphabet(g))]...)) -@test_nowarn StatsBase.sample(4, g, Weights([1,1,1,1,100])) -@test_throws MethodError StatsBase.sample(4, g, [1,1,1,1,100]) -@test_throws MethodError StatsBase.sample(Random.MersenneTwister(1), 4, g, 1:2) diff --git a/test/runtests.jl b/test/runtests.jl index fdfaba4b..d3a374a0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -15,7 +15,6 @@ println("Julia version: ", VERSION) test_suites = [ ("Core", ["core.jl",]), ("Parse", ["parse.jl",]), - ("Random", ["random.jl",]), ("Normalize", ["normalize.jl",]), ("Syntax Utils", ["syntax-utils.jl",]), @@ -31,6 +30,9 @@ test_suites = [ ("Algebras: frames", ["algebras/frames.jl",]), ("Algebras: relations", ["algebras/relations.jl",]), + ("Generation: formula", ["generation/formula.jl",]), + ("Generation: models", ["generation/models.jl",]), + ("Kripke word", ["kripke-word.jl",]), ("Kripke image", ["kripke-image.jl",]), From 56eb1ce41ceddc7a75847fab96f1ebbb068a0c59 Mon Sep 17 00:00:00 2001 From: Alberto Paparella Date: Mon, 21 Oct 2024 14:47:40 +1000 Subject: [PATCH 60/90] Divided modal-logic.jl between types and utils folders --- src/{ => types}/modal-logic.jl | 609 --------------------------------- src/utils/modal-logic.jl | 607 ++++++++++++++++++++++++++++++++ 2 files changed, 607 insertions(+), 609 deletions(-) rename src/{ => types}/modal-logic.jl (53%) create mode 100644 src/utils/modal-logic.jl diff --git a/src/modal-logic.jl b/src/types/modal-logic.jl similarity index 53% rename from src/modal-logic.jl rename to src/types/modal-logic.jl index df6204e8..238d1557 100644 --- a/src/modal-logic.jl +++ b/src/types/modal-logic.jl @@ -23,27 +23,6 @@ abstract type AbstractWorld end ############################################################################################ -""" - struct World{T} <: AbstractWorld - name::T - end - -A world that is solely identified by its `name`. -This can be useful when instantiating the underlying graph of a modal frame -in an explicit way. - -See also [`OneWorld`](@ref), [`AbstractWorld`](@ref). -""" -struct World{T} <: AbstractWorld - name::T -end - -name(w::World) = w.name - -inlinedisplay(w::World) = string(name(w)) - -include("algebras/worlds.jl") - """ abstract type AbstractFrame{W<:AbstractWorld} end @@ -118,42 +97,6 @@ function accessibles(fr::AbstractUniModalFrame{W}, w::W)::Worlds{W} where {W<:Ab return error("Please, provide method accessibles(fr::$(typeof(f)), w::$(typeof(w)))::Vector{$(W)}.") end -############################################################################################ - -# """ -# TODO Mauro -# Association "(w1,w2) => truth_value". Not recommended in sparse scenarios. -# """ -# struct AdjMatUniModalFrame{W<:AbstractWorld,T<:Truth} <: AbstractUniModalFrame{W} -# adjacents::NamedMatrix{T,Matrix{T},Tuple{OrderedDict{W,Int64},OrderedDict{W,Int64}}} -# end -# Upon construction, check that the type is not "OneWorld" -# end -# Add an example in the above docstring for accessibles -# accessibles(...) = ... - -# TODO move truth value out of frame (frame is passive, perhaps it is relations that have a truth value) -""" -TODO -""" -struct ExplicitCrispUniModalFrame{ - W<:AbstractWorld, - G<:Graphs.SimpleGraphs.AbstractSimpleGraph, -} <: AbstractUniModalFrame{W} - worlds::Worlds{W} - graph::G -end -accessibles(fr::ExplicitCrispUniModalFrame, w::AbstractWorld) = fr.worlds[neighbors(fr.graph, findfirst(==(w), fr.worlds))] -allworlds(fr::ExplicitCrispUniModalFrame) = fr.worlds -nworlds(fr::ExplicitCrispUniModalFrame) = length(fr.worlds) - -function Base.show(io::IO, fr::ExplicitCrispUniModalFrame) - println(io, "$(typeof(fr)) with") - println(io, "- worlds = $(inlinedisplay.(fr.worlds))") - maxl = maximum(length.(inlinedisplay.(fr.worlds))) - println(io, "- accessibles = \n$(join(["\t$(rpad(inlinedisplay(w), maxl)) -> [$(join(inlinedisplay.(accessibles(fr, w)), ", "))]" for w in fr.worlds], "\n"))") -end - ############################################################################################ #### Multi-modal logic ##################################################################### ############################################################################################ @@ -240,7 +183,6 @@ return the converse relation (type) of a given relation (type). See also [`issymmetric`](@ref), [`isreflexive`](@ref), [`istransitive`](@ref), [`AbstractRelation`](@ref). """ - """$(doc_conv_rel)""" function hasconverse(r::AbstractRelation)::Bool return false @@ -251,7 +193,6 @@ function converse(r::AbstractRelation)::AbstractRelation return error("Please, provide method converse(::$(typeof(r))).") end - """ istoone(r::AbstractRelation) = false @@ -307,124 +248,6 @@ isgrounding(::AbstractRelation) = false ############################################################################################ ############################################################################################ -############################################################################################ -# Singletons representing natural relations -############################################################################################ - -doc_identityrel = """ - struct IdentityRel <: AbstractRelation end; - const identityrel = IdentityRel(); - -Singleton type for the identity relation. This is a binary relation via which a world -accesses itself. The relation is also symmetric, reflexive and transitive. - -# Examples -```julia-repl -julia> syntaxstring(SoleLogics.identityrel) -"=" - -julia> SoleLogics.converse(identityrel) -IdentityRel() -``` - -See also -[`globalrel`](@ref), -[`AbstractRelation`](@ref), -[`AbstractWorld`](@ref), -[`AbstractFrame`](@ref). -[`AbstractKripkeStructure`](@ref), -""" - -"""$(doc_identityrel)""" -struct IdentityRel <: AbstractRelation end; -"""$(doc_identityrel)""" -const identityrel = IdentityRel(); - -arity(::IdentityRel) = 2 - -syntaxstring(::IdentityRel; kwargs...) = "=" - -hasconverse(::IdentityRel) = true -converse(::IdentityRel) = identityrel -istoone(::IdentityRel) = true -issymmetric(::IdentityRel) = true -isreflexive(::IdentityRel) = true -istransitive(::IdentityRel) = true - -############################################################################################ - -doc_globalrel = """ - struct 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-repl -julia> syntaxstring(SoleLogics.globalrel) -"G" - -julia> SoleLogics.converse(globalrel) -GlobalRel() -``` - -See also -[`identityrel`](@ref), -[`AbstractRelation`](@ref), -[`AbstractWorld`](@ref), -[`AbstractFrame`](@ref). -[`AbstractKripkeStructure`](@ref), -""" - -"""$(doc_globalrel)""" -struct GlobalRel <: AbstractRelation end; -"""$(doc_globalrel)""" -const globalrel = GlobalRel(); - -arity(::GlobalRel) = 2 - -syntaxstring(::GlobalRel; kwargs...) = "G" - -hasconverse(::GlobalRel) = true -converse(::GlobalRel) = globalrel -issymmetric(::GlobalRel) = true -isreflexive(::GlobalRel) = true -istransitive(::GlobalRel) = true -isgrounding(::GlobalRel) = true - -############################################################################################ - -""" -A binary relation via which a world *is accessed* by every other world within the frame. -That is, the binary relation that leads to a world. - -See also -[`identityrel`](@ref), -[`AbstractRelation`](@ref), -[`AbstractWorld`](@ref), -[`AbstractFrame`](@ref). -[`AbstractKripkeStructure`](@ref), -""" -struct AtWorldRelation{W<:AbstractWorld} <: AbstractRelation - w::W -end; - -arity(::AtWorldRelation) = 2 - -syntaxstring(r::AtWorldRelation; kwargs...) = "@($(syntaxstring(r.w)))" - -hasconverse(::AtWorldRelation) = false -issymmetric(::AtWorldRelation) = false -isreflexive(::AtWorldRelation) = false -istransitive(::AtWorldRelation) = true -isgrounding(::AtWorldRelation) = true - -############################################################################################ -############################################################################################ -############################################################################################ - """ abstract type AbstractMultiModalFrame{W<:AbstractWorld} <: AbstractFrame{W} end @@ -440,13 +263,6 @@ See also [`AbstractUniModalFrame`](@ref), [`AbstractFrame`](@ref). """ abstract type AbstractMultiModalFrame{W<:AbstractWorld} <: AbstractFrame{W} end -# Shortcut: when enumerating accessibles through global relation, delegate to `allworlds` -accessibles(fr::AbstractMultiModalFrame, ::GlobalRel) = allworlds(fr) -accessibles(fr::AbstractMultiModalFrame, ::AbstractWorld, r::GlobalRel) = accessibles(fr, r) -accessibles(fr::AbstractMultiModalFrame, w::AbstractWorld, ::IdentityRel) = [w] -accessibles(fr::AbstractMultiModalFrame, w::AbstractWorld, r::AtWorldRelation) = [r.w] - - """ accessibles( fr::AbstractMultiModalFrame{W}, @@ -542,71 +358,6 @@ function accessibles( IterTools.imap(W, _accessibles(fr, w, r)) end -############################################################################################ - -# TODO test -""" - struct WrapperMultiModalFrame{ - W<:AbstractWorld, - D<:AbstractDict{<:AbstractRelation,<:AbstractUniModalFrame{W}} - } <: AbstractMultiModalFrame{W} - frames::D - end - -A multi-modal frame that is the superposition of many uni-modal frames. -It uses a single `AbstractUniModalFrame` for -each of relations. - -See also [`AbstractRelation`](@ref), [`AbstractUniModalFrame`](@ref). -""" -struct WrapperMultiModalFrame{ - W<:AbstractWorld, - D<:AbstractDict{<:AbstractRelation,<:AbstractUniModalFrame{W}} -} <: AbstractMultiModalFrame{W} - frames::D -end -function accessibles( - fr::WrapperMultiModalFrame{W}, - w::W, - r::AbstractRelation, -) where {W<:AbstractWorld} - accessibles(frames[r], w, r) -end -function accessibles( - fr::WrapperMultiModalFrame{W}, - w::W, - r::IdentityRel, -) where {W<:AbstractWorld} - [w] -end -function accessibles( - fr::WrapperMultiModalFrame{W}, - w::W, - r::GlobalRel, -) where {W<:AbstractWorld} - accessibles(fr, r) -end - -# """ -# TODO -# """ -# struct AdjMatCrispMultiModalFrame{ -# W<:AbstractWorld -# } <: AbstractMultiModalFrame{W} -# worlds::Worlds{W} -# adjacents::Vector{W,Dict{R,Vector{W,3}}} -# end -# accessibles(fr::AdjMatMultiModalFrame) = ... - -# allworlds(fr::AdjMatMultiModalFrame) = fr.worlds -# nworlds(fr::AdjMatMultiModalFrame) = length(fr) - - - -include("algebras/relations.jl") - -include("algebras/frames.jl") - ############################################################################################ ############################################################################################ ############################################################################################ @@ -666,9 +417,6 @@ accessibles(i::AbstractKripkeStructure, args...) = accessibles(frame(i), args... allworlds(i::AbstractKripkeStructure, args...) = allworlds(frame(i), args...) nworlds(i::AbstractKripkeStructure) = nworlds(frame(i)) -# TODO explain -struct AnyWorld end - # # General grounding # function check( # φ::SyntaxTree, @@ -834,44 +582,6 @@ function check( return ret end -############################################################################################ - -""" - struct KripkeStructure{ - FR<:AbstractFrame, - MAS<:AbstractDict - } <: AbstractKripkeStructure - frame::FR - assignment::AS - end - -Type for representing -[Kripke structures](https://en.wikipedia.org/wiki/Kripke_structure_(model_checking)). -explicitly; it wraps a `frame`, and an abstract dictionary that assigns an interpretation to -each world. -""" -struct KripkeStructure{ - FR<:AbstractFrame, - MAS<:AbstractDict -} <: AbstractKripkeStructure - frame::FR - assignment::MAS -end - -frame(i::KripkeStructure) = i.frame - -function interpret(a::Atom, i::KripkeStructure, w::W) where {W<:AbstractWorld} - interpret(a, i.assignment[w]) -end - -function Base.show(io::IO, i::KripkeStructure) - println(io, "$(typeof(i)) with") - print(io, "- frame = ") - Base.show(io, frame(i)) - maxl = maximum(length.(inlinedisplay.(allworlds(i)))) - println(io, "- valuations = \n$(join(["\t$(rpad(inlinedisplay(w), maxl)) -> $(inlinedisplay(i.assignment[w]))" for w in allworlds(i)], "\n"))") -end - ############################################################################################ ############################################################################################ ############################################################################################ @@ -923,114 +633,6 @@ isdiamond(C::Type{<:Connective})::Bool = ismodal(C) && !isbox(C) isdiamond(c::Connective)::Bool = isdiamond(typeof(c)) isdiamond(::Truth)::Bool = false -doc_DIAMOND = """ - const DIAMOND = NamedConnective{:◊}() - const ◊ = DIAMOND - ismodal(::typeof(◊)) = true - arity(::typeof(◊)) = 1 - -Logical diamond connective, typically interpreted as the modal existential quantifier. -See [here](https://en.wikipedia.org/wiki/Modal_operator). - -See also [`BOX`](@ref), [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_DIAMOND)""" -const DIAMOND = NamedConnective{:◊}() -"""$(doc_DIAMOND)""" -const ◊ = DIAMOND -ismodal(::Type{typeof(◊)}) = true -isbox(::Type{typeof(◊)}) = false -arity(::typeof(◊)) = 1 -precedence(::typeof(◊)) = precedence(NEGATION) -associativity(::typeof(◊)) = associativity(NEGATION) - -doc_BOX = """ - const BOX = NamedConnective{:□}() - const □ = BOX - arity(::typeof(□)) = 1 - -Logical box connective, typically interpreted as the modal universal quantifier. -See [here](https://en.wikipedia.org/wiki/Modal_operator). - -See also [`DIAMOND`](@ref), [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_BOX)""" -const BOX = NamedConnective{:□}() -"""$(doc_BOX)""" -const □ = BOX -ismodal(::Type{typeof(□)}) = true -isbox(::Type{typeof(□)}) = true -arity(::typeof(□)) = 1 -precedence(::typeof(□)) = precedence(NEGATION) -associativity(::typeof(□)) = associativity(NEGATION) - -hasdual(::typeof(DIAMOND)) = true -dual(::typeof(DIAMOND)) = BOX -hasdual(::typeof(BOX)) = true -dual(::typeof(BOX)) = DIAMOND - -############################################################################################ - -const BASE_MODAL_CONNECTIVES = [BASE_PROPOSITIONAL_CONNECTIVES..., ◊, □] -const BaseModalConnectives = Union{typeof.(BASE_MODAL_CONNECTIVES)...} - -""" - modallogic(; - alphabet = AlphabetOfAny{String}(), - operators = [⊤, ⊥, ¬, ∧, ∨, →, ◊, □], - grammar = CompleteFlatGrammar(AlphabetOfAny{String}(), [⊤, ⊥, ¬, ∧, ∨, →, ◊, □]), - algebra = BooleanAlgebra(), - ) - -Instantiate a [modal logic](https://simple.wikipedia.org/wiki/Modal_logic) -given a grammar and an algebra. Alternatively, an alphabet and a set of operators -can be specified instead of the grammar. - -# Examples -```julia-repl -julia> (¬) isa operatorstype(modallogic()); -true - -julia> (□) isa operatorstype(modallogic()); -true - -julia> (□) isa operatorstype(modallogic(; operators = [¬, ∨])) -┌ Warning: Instantiating modal logic (via `modallogic`) with solely propositional operators (SoleLogics.NamedConnective[¬, ∨]). Consider using propositionallogic instead. -└ @ SoleLogics ~/.julia/dev/SoleLogics/src/modal-logic.jl:642 -false - -julia> modallogic(; alphabet = ["p", "q"]); - -julia> modallogic(; alphabet = ExplicitAlphabet([Atom("p"), Atom("q")])); - -``` - -See also [`propositionallogic`](@ref), [`AbstractAlphabet`](@ref), [`AbstractAlgebra`](@ref). -""" -function modallogic(; - alphabet::Union{Nothing,Vector,AbstractAlphabet} = nothing, - operators::Union{Nothing,Vector{<:Connective}} = nothing, - grammar::Union{Nothing,AbstractGrammar} = nothing, - algebra::Union{Nothing,AbstractAlgebra} = nothing, - default_operators = BASE_MODAL_CONNECTIVES -) - if !isnothing(operators) && length(setdiff(operators, BASE_PROPOSITIONAL_CONNECTIVES)) == 0 - @warn "Instantiating modal logic (via `modallogic`) with solely " * - "propositional operators ($(operators)). Consider using propositionallogic instead." - end - _baselogic( - alphabet = alphabet, - operators = operators, - grammar = grammar, - algebra = algebra; - default_operators = default_operators, - logictypename = "modal logic", - ) -end - -# A modal logic based on the base modal connectives -const BaseModalLogic = AbstractLogic{G,A} where {ALP,G<:AbstractGrammar{ALP,<:BaseModalConnectives},A<:AbstractAlgebra} - ############################################################################################ """ @@ -1079,138 +681,8 @@ function associativity(op::AbstractRelationalConnective) end end -const archetypmodal_relops_docstring = """ - struct DiamondRelationalConnective{R<:AbstractRelation} <: AbstractRelationalConnective{R} end - struct BoxRelationalConnective{R<:AbstractRelation} <: AbstractRelationalConnective{R} end - -Singleton types for relational connectives, typically interpreted as the modal existential -and universal quantifier, respectively. - -Both connectives can be easily instantiated with relation instances, -such as `DiamondRelationalConnective(rel)`, which is a shortcut for -`DiamondRelationalConnective{typeof(rel)}()`. - -# Examples -```julia-repl -julia> syntaxstring(DiamondRelationalConnective(IA_A)) -"⟨A⟩" - -julia> syntaxstring(BoxRelationalConnective(IA_A)) -"[A]" - -julia> @assert DiamondRelationalConnective(IA_A) == SoleLogics.dual(BoxRelationalConnective(IA_A)) - -``` - -See also -[`DiamondRelationalConnective`](@ref), [`BoxRelationalConnective`](@ref), -[`syntaxstring`](@ref), [`dual`](@ref), -[`AbstractKripkeStructure`](@ref), [`AbstractFrame`](@ref). -""" - -"""$(archetypmodal_relops_docstring)""" -struct DiamondRelationalConnective{R<:AbstractRelation} <: AbstractRelationalConnective{R} end -(DiamondRelationalConnective)(r::AbstractRelation) = DiamondRelationalConnective{typeof(r)}() - -"""$(archetypmodal_relops_docstring)""" -struct BoxRelationalConnective{R<:AbstractRelation} <: AbstractRelationalConnective{R} end -(BoxRelationalConnective)(r::AbstractRelation) = BoxRelationalConnective{typeof(r)}() - -ismodal(::Type{<:DiamondRelationalConnective}) = true -ismodal(::Type{<:BoxRelationalConnective}) = true - -isbox(::Type{<:DiamondRelationalConnective}) = false -isbox(::Type{<:BoxRelationalConnective}) = true - -function syntaxstring(op::DiamondRelationalConnective; use_modal_notation = nothing, kwargs...) - if isnothing(use_modal_notation) - return "⟨$(syntaxstring(relation(op); kwargs...))⟩" - elseif use_modal_notation == :superscript - return "◊$(SoleBase.superscript(syntaxstring(relation(op); kwargs...)))" - # elseif use_modal_notation == :subscript - # return "◊$(SoleBase.subscript(syntaxstring(relation(op); kwargs...)))" - else - return error("Unexpected value for parameter `use_modal_notation`. Allowed are: [nothing, :superscript]") - end -end -function syntaxstring(op::BoxRelationalConnective; use_modal_notation = nothing, kwargs...) - if isnothing(use_modal_notation) - return "[$(syntaxstring(relation(op); kwargs...))]" - elseif use_modal_notation == :superscript - return "□$(SoleBase.superscript(syntaxstring(relation(op); kwargs...)))" - # elseif use_modal_notation == :subscript - # return "□$(SoleBase.subscript(syntaxstring(relation(op); kwargs...)))" - else - return error("Unexpected value for parameter `use_modal_notation`. Allowed are: [nothing, :superscript]") - end -end - -hasdual(::DiamondRelationalConnective) = true -dual(op::DiamondRelationalConnective) = BoxRelationalConnective{relationtype(op)}() -hasdual(::BoxRelationalConnective) = true -dual(op::BoxRelationalConnective) = DiamondRelationalConnective{relationtype(op)}() - ############################################################################################ -""" - diamond() = DIAMOND - diamond(r::AbstractRelation) = DiamondRelationalConnective(r) - -Return either the diamond modal connective from unimodal logic (i.e., ◊), or a -a diamond relational connective from a multi-modal logic, wrapping the relation `r`. - -See also [`DiamondRelationalConnective`](@ref), [`diamond`](@ref), [`DIAMOND`](@ref). -""" -function diamond() DIAMOND end -function diamond(r::AbstractRelation) DiamondRelationalConnective(r) end - -""" - box() = BOX - box(r::AbstractRelation) = BoxRelationalConnective(r) - -Return either the box modal connective from unimodal logic (i.e., □), or a -a box relational connective from a multi-modal logic, wrapping the relation `r`. - -See also [`BoxRelationalConnective`](@ref), [`box`](@ref), [`BOX`](@ref). -""" -function box() BOX end -function box(r::AbstractRelation) BoxRelationalConnective(r) end - -globaldiamond = diamond(globalrel) -globalbox = box(globalrel) - -identitydiamond = diamond(identityrel) -identitybox = box(identityrel) - -function diamondsandboxes() - return [diamond(), box()] -end -function diamondsandboxes(r::AbstractRelation) - return [diamond(r), box(r)] -end -function diamondsandboxes(rs::AbstractVector{<:AbstractRelation}) - return Iterators.flatten([diamondsandboxes(r) for r in rs]) |> collect -end - -# Well known connectives -Base.show(io::IO, c::Union{ - typeof(globaldiamond), - typeof(globalbox), - typeof(identitydiamond), - typeof(identitybox) -}) = print(io, "$(syntaxstring(c))") - -const BASE_MULTIMODAL_CONNECTIVES = [BASE_PROPOSITIONAL_CONNECTIVES..., - globaldiamond, - globalbox, - diamond(identityrel), - box(identityrel), -] -const BaseMultiModalConnectives = Union{typeof.(BASE_MULTIMODAL_CONNECTIVES)...} - -############################################################################################ - - """ collateworlds( fr::AbstractFrame{W}, @@ -1238,84 +710,3 @@ function collateworlds( "::$(typeof(op)), ::NTuple{$(arity(op)), $(AbstractWorlds{W})}).") end end - -# I know, these exceed 92 characters. But they look nicer like this!! :D -function collateworlds(fr::AbstractFrame{W}, t::BooleanTruth, ::NTuple{0,<:AbstractWorlds}) where {W<:AbstractWorld} - istop(t) ? allworlds(fr) : W[] -end - -collateworlds(fr::AbstractFrame{W}, ::typeof(¬), (ws,)::NTuple{1,<:AbstractWorlds}) where {W<:AbstractWorld} = setdiff(allworlds(fr), ws) -collateworlds(::AbstractFrame{W}, ::typeof(∧), (ws1, ws2)::NTuple{2,<:AbstractWorlds}) where {W<:AbstractWorld} = intersect(ws1, ws2) -collateworlds(::AbstractFrame{W}, ::typeof(∨), (ws1, ws2)::NTuple{2,<:AbstractWorlds}) where {W<:AbstractWorld} = union(ws1, ws2) -collateworlds(fr::AbstractFrame{W}, ::typeof(→), (ws1, ws2)::NTuple{2,<:AbstractWorlds}) where {W<:AbstractWorld} = union(setdiff(allworlds(fr), ws1), ws2) - -function collateworlds( - fr::AbstractFrame{W}, - op::typeof(◊), - (ws,)::NTuple{1,<:AbstractWorlds}, -) where {W<:AbstractWorld} - filter(w1->intersects(ws, accessibles(fr, w1)), collect(allworlds(fr))) -end - -function collateworlds( - fr::AbstractFrame{W}, - op::typeof(□), - (ws,)::NTuple{1,<:AbstractWorlds}, -) where {W<:AbstractWorld} - filter(w1->issubset(accessibles(fr, w1), ws), collect(allworlds(fr))) -end - -# TODO: use AbstractMultiModalFrame -function collateworlds( - fr::AbstractFrame{W}, - op::DiamondRelationalConnective, - (ws,)::NTuple{1,<:AbstractWorlds}, -) where {W<:AbstractWorld} - r = relation(op) - if r == globalrel - if length(ws) > 0 - collect(allworlds(fr)) - else - W[] - end - else - if hasconverse(r) - # DIAMOND STRATEGY 1 - union(W[], [accessibles(fr, w, converse(r)) for w in ws]...) - else - # DIAMOND STRATEGY 2 - filter(w1->intersects(ws, accessibles(fr, w1, r)), collect(allworlds(fr))) - end - end -end - -# TODO: use AbstractMultiModalFrame -function collateworlds( - fr::AbstractFrame{W}, - op::BoxRelationalConnective, - (ws,)::NTuple{1,<:AbstractWorlds}, -) where {W<:AbstractWorld} - r = relation(op) - if r == globalrel - if length(ws) == nworlds(fr) # Assuming no duplicates - collect(allworlds(fr)) - else - W[] - end - else - if hasconverse(r) - # BOX STRATEGY 1 - negws = setdiff(collect(allworlds(fr)), ws) - negboxws = union(W[], [ - accessibles(fr, w, converse(r)) for w in negws]...) - setdiff(collect(allworlds(fr)), negboxws) - # BOX STRATEGY 3 - # filter(w1->all((w2)->w1 in accessibles(fr, w2, converse(r)), ws), collect(allworlds(fr))) - else - # BOX STRATEGY 2 - filter(w1->issubset(accessibles(fr, w1, r), ws), collect(allworlds(fr))) - end - # Note: this is wrong, as it does not include worlds for which φ is trivially true. - # union(intersect(W[], [accessibles(fr, w, converse(r)) for w in ws]...)) - end -end diff --git a/src/utils/modal-logic.jl b/src/utils/modal-logic.jl new file mode 100644 index 00000000..885f74ec --- /dev/null +++ b/src/utils/modal-logic.jl @@ -0,0 +1,607 @@ +import Base: show +using DataStructures: OrderedDict +using Graphs +using ThreadSafeDicts + +""" + struct World{T} <: AbstractWorld + name::T + end + +A world that is solely identified by its `name`. +This can be useful when instantiating the underlying graph of a modal frame +in an explicit way. + +See also [`OneWorld`](@ref), [`AbstractWorld`](@ref). +""" +struct World{T} <: AbstractWorld + name::T +end + +name(w::World) = w.name + +inlinedisplay(w::World) = string(name(w)) + +include("algebras/worlds.jl") + +############################################################################################ +##################################### Uni-modal logic ###################################### +############################################################################################ + +# """ +# TODO Mauro +# Association "(w1,w2) => truth_value". Not recommended in sparse scenarios. +# """ +# struct AdjMatUniModalFrame{W<:AbstractWorld,T<:Truth} <: AbstractUniModalFrame{W} +# adjacents::NamedMatrix{T,Matrix{T},Tuple{OrderedDict{W,Int64},OrderedDict{W,Int64}}} +# end +# Upon construction, check that the type is not "OneWorld" +# end +# Add an example in the above docstring for accessibles +# accessibles(...) = ... + +# TODO move truth value out of frame (frame is passive, perhaps it is relations that have a truth value) +""" +TODO +""" +struct ExplicitCrispUniModalFrame{ + W<:AbstractWorld, + G<:Graphs.SimpleGraphs.AbstractSimpleGraph, +} <: AbstractUniModalFrame{W} + worlds::Worlds{W} + graph::G +end +accessibles(fr::ExplicitCrispUniModalFrame, w::AbstractWorld) = fr.worlds[neighbors(fr.graph, findfirst(==(w), fr.worlds))] +allworlds(fr::ExplicitCrispUniModalFrame) = fr.worlds +nworlds(fr::ExplicitCrispUniModalFrame) = length(fr.worlds) + +function Base.show(io::IO, fr::ExplicitCrispUniModalFrame) + println(io, "$(typeof(fr)) with") + println(io, "- worlds = $(inlinedisplay.(fr.worlds))") + maxl = maximum(length.(inlinedisplay.(fr.worlds))) + println(io, "- accessibles = \n$(join(["\t$(rpad(inlinedisplay(w), maxl)) -> [$(join(inlinedisplay.(accessibles(fr, w)), ", "))]" for w in fr.worlds], "\n"))") +end + +############################################################################################ +#### Multi-modal logic ##################################################################### +############################################################################################ + +############################################################################################ +# Singletons representing natural relations +############################################################################################ + +doc_identityrel = """ + struct IdentityRel <: AbstractRelation end; + const identityrel = IdentityRel(); + +Singleton type for the identity relation. This is a binary relation via which a world +accesses itself. The relation is also symmetric, reflexive and transitive. + +# Examples +```julia-repl +julia> syntaxstring(SoleLogics.identityrel) +"=" + +julia> SoleLogics.converse(identityrel) +IdentityRel() +``` + +See also +[`globalrel`](@ref), +[`AbstractRelation`](@ref), +[`AbstractWorld`](@ref), +[`AbstractFrame`](@ref). +[`AbstractKripkeStructure`](@ref), +""" + +"""$(doc_identityrel)""" +struct IdentityRel <: AbstractRelation end; +"""$(doc_identityrel)""" +const identityrel = IdentityRel(); + +arity(::IdentityRel) = 2 + +syntaxstring(::IdentityRel; kwargs...) = "=" + +hasconverse(::IdentityRel) = true +converse(::IdentityRel) = identityrel +istoone(::IdentityRel) = true +issymmetric(::IdentityRel) = true +isreflexive(::IdentityRel) = true +istransitive(::IdentityRel) = true + +############################################################################################ + +doc_globalrel = """ + struct 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-repl +julia> syntaxstring(SoleLogics.globalrel) +"G" + +julia> SoleLogics.converse(globalrel) +GlobalRel() +``` + +See also +[`identityrel`](@ref), +[`AbstractRelation`](@ref), +[`AbstractWorld`](@ref), +[`AbstractFrame`](@ref). +[`AbstractKripkeStructure`](@ref), +""" + +"""$(doc_globalrel)""" +struct GlobalRel <: AbstractRelation end; +"""$(doc_globalrel)""" +const globalrel = GlobalRel(); + +arity(::GlobalRel) = 2 + +syntaxstring(::GlobalRel; kwargs...) = "G" + +hasconverse(::GlobalRel) = true +converse(::GlobalRel) = globalrel +issymmetric(::GlobalRel) = true +isreflexive(::GlobalRel) = true +istransitive(::GlobalRel) = true +isgrounding(::GlobalRel) = true + +############################################################################################ + +""" +A binary relation via which a world *is accessed* by every other world within the frame. +That is, the binary relation that leads to a world. + +See also +[`identityrel`](@ref), +[`AbstractRelation`](@ref), +[`AbstractWorld`](@ref), +[`AbstractFrame`](@ref). +[`AbstractKripkeStructure`](@ref), +""" +struct AtWorldRelation{W<:AbstractWorld} <: AbstractRelation + w::W +end; + +arity(::AtWorldRelation) = 2 + +syntaxstring(r::AtWorldRelation; kwargs...) = "@($(syntaxstring(r.w)))" + +hasconverse(::AtWorldRelation) = false +issymmetric(::AtWorldRelation) = false +isreflexive(::AtWorldRelation) = false +istransitive(::AtWorldRelation) = true +isgrounding(::AtWorldRelation) = true + +############################################################################################ + +# Shortcut: when enumerating accessibles through global relation, delegate to `allworlds` +accessibles(fr::AbstractMultiModalFrame, ::GlobalRel) = allworlds(fr) +accessibles(fr::AbstractMultiModalFrame, ::AbstractWorld, r::GlobalRel) = accessibles(fr, r) +accessibles(fr::AbstractMultiModalFrame, w::AbstractWorld, ::IdentityRel) = [w] +accessibles(fr::AbstractMultiModalFrame, w::AbstractWorld, r::AtWorldRelation) = [r.w] + +############################################################################################ + +# TODO test +""" + struct WrapperMultiModalFrame{ + W<:AbstractWorld, + D<:AbstractDict{<:AbstractRelation,<:AbstractUniModalFrame{W}} + } <: AbstractMultiModalFrame{W} + frames::D + end + +A multi-modal frame that is the superposition of many uni-modal frames. +It uses a single `AbstractUniModalFrame` for +each of relations. + +See also [`AbstractRelation`](@ref), [`AbstractUniModalFrame`](@ref). +""" +struct WrapperMultiModalFrame{ + W<:AbstractWorld, + D<:AbstractDict{<:AbstractRelation,<:AbstractUniModalFrame{W}} +} <: AbstractMultiModalFrame{W} + frames::D +end +function accessibles( + fr::WrapperMultiModalFrame{W}, + w::W, + r::AbstractRelation, +) where {W<:AbstractWorld} + accessibles(frames[r], w, r) +end +function accessibles( + fr::WrapperMultiModalFrame{W}, + w::W, + r::IdentityRel, +) where {W<:AbstractWorld} + [w] +end +function accessibles( + fr::WrapperMultiModalFrame{W}, + w::W, + r::GlobalRel, +) where {W<:AbstractWorld} + accessibles(fr, r) +end + +# """ +# TODO +# """ +# struct AdjMatCrispMultiModalFrame{ +# W<:AbstractWorld +# } <: AbstractMultiModalFrame{W} +# worlds::Worlds{W} +# adjacents::Vector{W,Dict{R,Vector{W,3}}} +# end +# accessibles(fr::AdjMatMultiModalFrame) = ... + +# allworlds(fr::AdjMatMultiModalFrame) = fr.worlds +# nworlds(fr::AdjMatMultiModalFrame) = length(fr) + +include("algebras/relations.jl") + +include("algebras/frames.jl") + +# TODO explain +struct AnyWorld end + +""" + struct KripkeStructure{ + FR<:AbstractFrame, + MAS<:AbstractDict + } <: AbstractKripkeStructure + frame::FR + assignment::AS + end + +Type for representing +[Kripke structures](https://en.wikipedia.org/wiki/Kripke_structure_(model_checking)). +explicitly; it wraps a `frame`, and an abstract dictionary that assigns an interpretation to +each world. +""" +struct KripkeStructure{ + FR<:AbstractFrame, + MAS<:AbstractDict +} <: AbstractKripkeStructure + frame::FR + assignment::MAS +end + +frame(i::KripkeStructure) = i.frame + +function interpret(a::Atom, i::KripkeStructure, w::W) where {W<:AbstractWorld} + interpret(a, i.assignment[w]) +end + +function Base.show(io::IO, i::KripkeStructure) + println(io, "$(typeof(i)) with") + print(io, "- frame = ") + Base.show(io, frame(i)) + maxl = maximum(length.(inlinedisplay.(allworlds(i)))) + println(io, "- valuations = \n$(join(["\t$(rpad(inlinedisplay(w), maxl)) -> $(inlinedisplay(i.assignment[w]))" for w in allworlds(i)], "\n"))") +end + +doc_DIAMOND = """ + const DIAMOND = NamedConnective{:◊}() + const ◊ = DIAMOND + ismodal(::typeof(◊)) = true + arity(::typeof(◊)) = 1 + +Logical diamond connective, typically interpreted as the modal existential quantifier. +See [here](https://en.wikipedia.org/wiki/Modal_operator). + +See also [`BOX`](@ref), [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_DIAMOND)""" +const DIAMOND = NamedConnective{:◊}() +"""$(doc_DIAMOND)""" +const ◊ = DIAMOND +ismodal(::Type{typeof(◊)}) = true +isbox(::Type{typeof(◊)}) = false +arity(::typeof(◊)) = 1 +precedence(::typeof(◊)) = precedence(NEGATION) +associativity(::typeof(◊)) = associativity(NEGATION) + +doc_BOX = """ + const BOX = NamedConnective{:□}() + const □ = BOX + arity(::typeof(□)) = 1 + +Logical box connective, typically interpreted as the modal universal quantifier. +See [here](https://en.wikipedia.org/wiki/Modal_operator). + +See also [`DIAMOND`](@ref), [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_BOX)""" +const BOX = NamedConnective{:□}() +"""$(doc_BOX)""" +const □ = BOX +ismodal(::Type{typeof(□)}) = true +isbox(::Type{typeof(□)}) = true +arity(::typeof(□)) = 1 +precedence(::typeof(□)) = precedence(NEGATION) +associativity(::typeof(□)) = associativity(NEGATION) + +hasdual(::typeof(DIAMOND)) = true +dual(::typeof(DIAMOND)) = BOX +hasdual(::typeof(BOX)) = true +dual(::typeof(BOX)) = DIAMOND + +const BASE_MODAL_CONNECTIVES = [BASE_PROPOSITIONAL_CONNECTIVES..., ◊, □] +const BaseModalConnectives = Union{typeof.(BASE_MODAL_CONNECTIVES)...} + +""" + modallogic(; + alphabet = AlphabetOfAny{String}(), + operators = [⊤, ⊥, ¬, ∧, ∨, →, ◊, □], + grammar = CompleteFlatGrammar(AlphabetOfAny{String}(), [⊤, ⊥, ¬, ∧, ∨, →, ◊, □]), + algebra = BooleanAlgebra(), + ) + +Instantiate a [modal logic](https://simple.wikipedia.org/wiki/Modal_logic) +given a grammar and an algebra. Alternatively, an alphabet and a set of operators +can be specified instead of the grammar. + +# Examples +```julia-repl +julia> (¬) isa operatorstype(modallogic()); +true + +julia> (□) isa operatorstype(modallogic()); +true + +julia> (□) isa operatorstype(modallogic(; operators = [¬, ∨])) +┌ Warning: Instantiating modal logic (via `modallogic`) with solely propositional operators (SoleLogics.NamedConnective[¬, ∨]). Consider using propositionallogic instead. +└ @ SoleLogics ~/.julia/dev/SoleLogics/src/modal-logic.jl:642 +false + +julia> modallogic(; alphabet = ["p", "q"]); + +julia> modallogic(; alphabet = ExplicitAlphabet([Atom("p"), Atom("q")])); + +``` + +See also [`propositionallogic`](@ref), [`AbstractAlphabet`](@ref), [`AbstractAlgebra`](@ref). +""" +function modallogic(; + alphabet::Union{Nothing,Vector,AbstractAlphabet} = nothing, + operators::Union{Nothing,Vector{<:Connective}} = nothing, + grammar::Union{Nothing,AbstractGrammar} = nothing, + algebra::Union{Nothing,AbstractAlgebra} = nothing, + default_operators = BASE_MODAL_CONNECTIVES +) + if !isnothing(operators) && length(setdiff(operators, BASE_PROPOSITIONAL_CONNECTIVES)) == 0 + @warn "Instantiating modal logic (via `modallogic`) with solely " * + "propositional operators ($(operators)). Consider using propositionallogic instead." + end + _baselogic( + alphabet = alphabet, + operators = operators, + grammar = grammar, + algebra = algebra; + default_operators = default_operators, + logictypename = "modal logic", + ) +end + +# A modal logic based on the base modal connectives +const BaseModalLogic = AbstractLogic{G,A} where {ALP,G<:AbstractGrammar{ALP,<:BaseModalConnectives},A<:AbstractAlgebra} + +const archetypmodal_relops_docstring = """ + struct DiamondRelationalConnective{R<:AbstractRelation} <: AbstractRelationalConnective{R} end + struct BoxRelationalConnective{R<:AbstractRelation} <: AbstractRelationalConnective{R} end + +Singleton types for relational connectives, typically interpreted as the modal existential +and universal quantifier, respectively. + +Both connectives can be easily instantiated with relation instances, +such as `DiamondRelationalConnective(rel)`, which is a shortcut for +`DiamondRelationalConnective{typeof(rel)}()`. + +# Examples +```julia-repl +julia> syntaxstring(DiamondRelationalConnective(IA_A)) +"⟨A⟩" + +julia> syntaxstring(BoxRelationalConnective(IA_A)) +"[A]" + +julia> @assert DiamondRelationalConnective(IA_A) == SoleLogics.dual(BoxRelationalConnective(IA_A)) + +``` + +See also +[`DiamondRelationalConnective`](@ref), [`BoxRelationalConnective`](@ref), +[`syntaxstring`](@ref), [`dual`](@ref), +[`AbstractKripkeStructure`](@ref), [`AbstractFrame`](@ref). +""" + +"""$(archetypmodal_relops_docstring)""" +struct DiamondRelationalConnective{R<:AbstractRelation} <: AbstractRelationalConnective{R} end +(DiamondRelationalConnective)(r::AbstractRelation) = DiamondRelationalConnective{typeof(r)}() + +"""$(archetypmodal_relops_docstring)""" +struct BoxRelationalConnective{R<:AbstractRelation} <: AbstractRelationalConnective{R} end +(BoxRelationalConnective)(r::AbstractRelation) = BoxRelationalConnective{typeof(r)}() + +ismodal(::Type{<:DiamondRelationalConnective}) = true +ismodal(::Type{<:BoxRelationalConnective}) = true + +isbox(::Type{<:DiamondRelationalConnective}) = false +isbox(::Type{<:BoxRelationalConnective}) = true + +function syntaxstring(op::DiamondRelationalConnective; use_modal_notation = nothing, kwargs...) + if isnothing(use_modal_notation) + return "⟨$(syntaxstring(relation(op); kwargs...))⟩" + elseif use_modal_notation == :superscript + return "◊$(SoleBase.superscript(syntaxstring(relation(op); kwargs...)))" + # elseif use_modal_notation == :subscript + # return "◊$(SoleBase.subscript(syntaxstring(relation(op); kwargs...)))" + else + return error("Unexpected value for parameter `use_modal_notation`. Allowed are: [nothing, :superscript]") + end +end +function syntaxstring(op::BoxRelationalConnective; use_modal_notation = nothing, kwargs...) + if isnothing(use_modal_notation) + return "[$(syntaxstring(relation(op); kwargs...))]" + elseif use_modal_notation == :superscript + return "□$(SoleBase.superscript(syntaxstring(relation(op); kwargs...)))" + # elseif use_modal_notation == :subscript + # return "□$(SoleBase.subscript(syntaxstring(relation(op); kwargs...)))" + else + return error("Unexpected value for parameter `use_modal_notation`. Allowed are: [nothing, :superscript]") + end +end + +hasdual(::DiamondRelationalConnective) = true +dual(op::DiamondRelationalConnective) = BoxRelationalConnective{relationtype(op)}() +hasdual(::BoxRelationalConnective) = true +dual(op::BoxRelationalConnective) = DiamondRelationalConnective{relationtype(op)}() + +############################################################################################ + +""" + diamond() = DIAMOND + diamond(r::AbstractRelation) = DiamondRelationalConnective(r) + +Return either the diamond modal connective from unimodal logic (i.e., ◊), or a +a diamond relational connective from a multi-modal logic, wrapping the relation `r`. + +See also [`DiamondRelationalConnective`](@ref), [`diamond`](@ref), [`DIAMOND`](@ref). +""" +function diamond() DIAMOND end +function diamond(r::AbstractRelation) DiamondRelationalConnective(r) end + +""" + box() = BOX + box(r::AbstractRelation) = BoxRelationalConnective(r) + +Return either the box modal connective from unimodal logic (i.e., □), or a +a box relational connective from a multi-modal logic, wrapping the relation `r`. + +See also [`BoxRelationalConnective`](@ref), [`box`](@ref), [`BOX`](@ref). +""" +function box() BOX end +function box(r::AbstractRelation) BoxRelationalConnective(r) end + +globaldiamond = diamond(globalrel) +globalbox = box(globalrel) + +identitydiamond = diamond(identityrel) +identitybox = box(identityrel) + +function diamondsandboxes() + return [diamond(), box()] +end +function diamondsandboxes(r::AbstractRelation) + return [diamond(r), box(r)] +end +function diamondsandboxes(rs::AbstractVector{<:AbstractRelation}) + return Iterators.flatten([diamondsandboxes(r) for r in rs]) |> collect +end + +# Well known connectives +Base.show(io::IO, c::Union{ + typeof(globaldiamond), + typeof(globalbox), + typeof(identitydiamond), + typeof(identitybox) +}) = print(io, "$(syntaxstring(c))") + +const BASE_MULTIMODAL_CONNECTIVES = [BASE_PROPOSITIONAL_CONNECTIVES..., + globaldiamond, + globalbox, + diamond(identityrel), + box(identityrel), +] +const BaseMultiModalConnectives = Union{typeof.(BASE_MULTIMODAL_CONNECTIVES)...} + +# I know, these exceed 92 characters. But they look nicer like this!! :D +function collateworlds(fr::AbstractFrame{W}, t::BooleanTruth, ::NTuple{0,<:AbstractWorlds}) where {W<:AbstractWorld} + istop(t) ? allworlds(fr) : W[] +end + +collateworlds(fr::AbstractFrame{W}, ::typeof(¬), (ws,)::NTuple{1,<:AbstractWorlds}) where {W<:AbstractWorld} = setdiff(allworlds(fr), ws) +collateworlds(::AbstractFrame{W}, ::typeof(∧), (ws1, ws2)::NTuple{2,<:AbstractWorlds}) where {W<:AbstractWorld} = intersect(ws1, ws2) +collateworlds(::AbstractFrame{W}, ::typeof(∨), (ws1, ws2)::NTuple{2,<:AbstractWorlds}) where {W<:AbstractWorld} = union(ws1, ws2) +collateworlds(fr::AbstractFrame{W}, ::typeof(→), (ws1, ws2)::NTuple{2,<:AbstractWorlds}) where {W<:AbstractWorld} = union(setdiff(allworlds(fr), ws1), ws2) + +function collateworlds( + fr::AbstractFrame{W}, + op::typeof(◊), + (ws,)::NTuple{1,<:AbstractWorlds}, +) where {W<:AbstractWorld} + filter(w1->intersects(ws, accessibles(fr, w1)), collect(allworlds(fr))) +end + +function collateworlds( + fr::AbstractFrame{W}, + op::typeof(□), + (ws,)::NTuple{1,<:AbstractWorlds}, +) where {W<:AbstractWorld} + filter(w1->issubset(accessibles(fr, w1), ws), collect(allworlds(fr))) +end + +# TODO: use AbstractMultiModalFrame +function collateworlds( + fr::AbstractFrame{W}, + op::DiamondRelationalConnective, + (ws,)::NTuple{1,<:AbstractWorlds}, +) where {W<:AbstractWorld} + r = relation(op) + if r == globalrel + if length(ws) > 0 + collect(allworlds(fr)) + else + W[] + end + else + if hasconverse(r) + # DIAMOND STRATEGY 1 + union(W[], [accessibles(fr, w, converse(r)) for w in ws]...) + else + # DIAMOND STRATEGY 2 + filter(w1->intersects(ws, accessibles(fr, w1, r)), collect(allworlds(fr))) + end + end +end + +# TODO: use AbstractMultiModalFrame +function collateworlds( + fr::AbstractFrame{W}, + op::BoxRelationalConnective, + (ws,)::NTuple{1,<:AbstractWorlds}, +) where {W<:AbstractWorld} + r = relation(op) + if r == globalrel + if length(ws) == nworlds(fr) # Assuming no duplicates + collect(allworlds(fr)) + else + W[] + end + else + if hasconverse(r) + # BOX STRATEGY 1 + negws = setdiff(collect(allworlds(fr)), ws) + negboxws = union(W[], [ + accessibles(fr, w, converse(r)) for w in negws]...) + setdiff(collect(allworlds(fr)), negboxws) + # BOX STRATEGY 3 + # filter(w1->all((w2)->w1 in accessibles(fr, w2, converse(r)), ws), collect(allworlds(fr))) + else + # BOX STRATEGY 2 + filter(w1->issubset(accessibles(fr, w1, r), ws), collect(allworlds(fr))) + end + # Note: this is wrong, as it does not include worlds for which φ is trivially true. + # union(intersect(W[], [accessibles(fr, w, converse(r)) for w in ws]...)) + end +end From 7fa4472788c94ae75871cafd049f3b963707b4b3 Mon Sep 17 00:00:00 2001 From: Alberto Paparella Date: Mon, 21 Oct 2024 16:19:02 +1000 Subject: [PATCH 61/90] Divided algebra subfolder in types and utils folders; WARNING: 5 tests fail in test/generation/formula.jl --- Project.toml | 8 +- src/SoleLogics.jl | 4 +- src/types/algebras/frames.jl | 38 ++++ .../frames/full-dimensional-frame/main.jl | 17 ++ src/types/algebras/relations.jl | 9 + .../algebras/relations/IntervalAlgebra.jl | 126 +++++++++++++ src/types/algebras/relations/Point.jl | 10 + src/{ => types}/algebras/relations/RCC.jl | 95 ---------- .../algebras/relations/filtered-relations.jl | 9 + .../relations/geometrical-relations.jl | 4 - src/{ => types}/algebras/worlds.jl | 22 --- .../algebras/worlds/geometrical-worlds.jl | 8 + src/types/modal-logic.jl | 171 +----------------- src/{ => utils}/algebras/frames.jl | 37 ---- .../full-dimensional-frame/Full1DFrame+IA.jl | 0 .../full-dimensional-frame/Full1DFrame+RCC.jl | 0 .../Full1DPointFrame.jl | 0 .../Full2DFrame+IA2D.jl | 0 .../full-dimensional-frame/Full2DFrame+RCC.jl | 0 .../Full2DPointFrame.jl | 0 .../FullDimensionalFrame-filtered.jl | 0 .../dimensional-world-filters.jl | 0 .../frames/full-dimensional-frame/main.jl | 20 +- src/{ => utils}/algebras/relations.jl | 4 - .../algebras/relations/IntervalAlgebra.jl | 131 +------------- .../algebras/relations/IntervalAlgebra2D.jl | 0 src/{ => utils}/algebras/relations/Point.jl | 17 +- src/utils/algebras/relations/RCC.jl | 98 ++++++++++ .../algebras/relations/filtered-relations.jl | 11 -- .../relations/geometrical-relations.jl | 11 ++ src/utils/algebras/worlds.jl | 24 +++ .../algebras/worlds/geometrical-worlds.jl | 9 - src/utils/modal-logic.jl | 165 +++++++++++++++++ 33 files changed, 538 insertions(+), 510 deletions(-) create mode 100644 src/types/algebras/frames.jl create mode 100644 src/types/algebras/frames/full-dimensional-frame/main.jl create mode 100644 src/types/algebras/relations.jl create mode 100644 src/types/algebras/relations/IntervalAlgebra.jl create mode 100644 src/types/algebras/relations/Point.jl rename src/{ => types}/algebras/relations/RCC.jl (58%) create mode 100644 src/types/algebras/relations/filtered-relations.jl rename src/{ => types}/algebras/relations/geometrical-relations.jl (94%) rename src/{ => types}/algebras/worlds.jl (72%) create mode 100644 src/types/algebras/worlds/geometrical-worlds.jl rename src/{ => utils}/algebras/frames.jl (61%) rename src/{ => utils}/algebras/frames/full-dimensional-frame/Full1DFrame+IA.jl (100%) rename src/{ => utils}/algebras/frames/full-dimensional-frame/Full1DFrame+RCC.jl (100%) rename src/{ => utils}/algebras/frames/full-dimensional-frame/Full1DPointFrame.jl (100%) rename src/{ => utils}/algebras/frames/full-dimensional-frame/Full2DFrame+IA2D.jl (100%) rename src/{ => utils}/algebras/frames/full-dimensional-frame/Full2DFrame+RCC.jl (100%) rename src/{ => utils}/algebras/frames/full-dimensional-frame/Full2DPointFrame.jl (100%) rename src/{ => utils}/algebras/frames/full-dimensional-frame/FullDimensionalFrame-filtered.jl (100%) rename src/{ => utils}/algebras/frames/full-dimensional-frame/dimensional-world-filters.jl (100%) rename src/{ => utils}/algebras/frames/full-dimensional-frame/main.jl (90%) rename src/{ => utils}/algebras/relations.jl (96%) rename src/{ => utils}/algebras/relations/IntervalAlgebra.jl (56%) rename src/{ => utils}/algebras/relations/IntervalAlgebra2D.jl (100%) rename src/{ => utils}/algebras/relations/Point.jl (92%) create mode 100644 src/utils/algebras/relations/RCC.jl rename src/{ => utils}/algebras/relations/filtered-relations.jl (92%) create mode 100644 src/utils/algebras/relations/geometrical-relations.jl create mode 100644 src/utils/algebras/worlds.jl rename src/{ => utils}/algebras/worlds/geometrical-worlds.jl (94%) diff --git a/Project.toml b/Project.toml index 9900db6c..0ebca2c7 100644 --- a/Project.toml +++ b/Project.toml @@ -5,12 +5,14 @@ version = "0.9.6" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" Lazy = "50d2b5c4-7a5e-59d5-8109-a42b560f39c0" +PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" @@ -20,12 +22,14 @@ ThreadSafeDicts = "4239201d-c60e-5e0a-9702-85d713665ba7" [compat] AbstractTrees = "0.4" +BenchmarkTools = "1.5.0" DataStructures = "0.18" Dictionaries = "0.3" FunctionWrappers = "1" Graphs = "1.8" IterTools = "1" Lazy = "0.15" +PlutoUI = "0.7.60" PrettyTables = "2.2" Random = "1" Reexport = "1" @@ -36,11 +40,11 @@ ThreadSafeDicts = "0.1.0" julia = "1" [extras] -InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 29fd10fb..a4313b7f 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -112,7 +112,9 @@ export GlobalRel, IdentityRel export globalrel, identityrel -include("modal-logic.jl") +include("types/modal-logic.jl") + +include("utils/modal-logic.jl") ############################################################################################ diff --git a/src/types/algebras/frames.jl b/src/types/algebras/frames.jl new file mode 100644 index 00000000..d796b059 --- /dev/null +++ b/src/types/algebras/frames.jl @@ -0,0 +1,38 @@ +# It is convenient to define methods for `accessibles` that take a world set instead of a +# single world. Generally, this falls back to calling `_accessibles` on each world in +# the set, and returning a constructor of wolds from the union; however, one may provide +# improved implementations for special cases (e.g. ⟨L⟩ of a world set in interval algebra). +function accessibles( + fr::AbstractMultiModalFrame{W}, + S::AbstractWorlds, + r::AbstractRelation, +) where {W<:AbstractWorld} + IterTools.imap(W, + IterTools.distinct( + Iterators.flatten( + (_accessibles(fr, w, r) for w in S) + ) + ) + ) +end + +############################################################################################ + +""" +Return an empty world (e.g., `Interval(-1,0)`). +""" +function emptyworld(fr::AbstractMultiModalFrame) + return error("Please, provide method emptyworld(::$(typeof(fr))).") +end + +""" +Return the world at the *center* of the frame; +note that this does not always exist. +""" +function centralworld(fr::AbstractMultiModalFrame) + return error("Please, provide method centralworld(::$(typeof(fr))).") +end + +############################################################################################ + +include("frames/full-dimensional-frame/main.jl") diff --git a/src/types/algebras/frames/full-dimensional-frame/main.jl b/src/types/algebras/frames/full-dimensional-frame/main.jl new file mode 100644 index 00000000..4e9c3243 --- /dev/null +++ b/src/types/algebras/frames/full-dimensional-frame/main.jl @@ -0,0 +1,17 @@ +""" + abstract type AbstractDimensionalFrame{ + N, + W<:AbstractWorld, + } <: AbstractMultiModalFrame{W} end + +Abstract type for dimensional frames. Given a `N`-dimensional array of size (X, Y, Z, ...) +the corresponding dimensional frame is a graph where each vertex is an +`N`-hyperrectangle (e.g., an Interval/Interval2D) in the space (1:X, 1:Y, 1:Z, ...). + +See also +[`Interval`](@ref), +[`Interval2D`](@ref), +[`IntervalRelation`](@ref), +[`AbstractDimensionalFrame`](@ref), [`AbstractMultiModalFrame`](@ref). +""" +abstract type AbstractDimensionalFrame{N,W<:AbstractWorld} <: AbstractMultiModalFrame{W} end diff --git a/src/types/algebras/relations.jl b/src/types/algebras/relations.jl new file mode 100644 index 00000000..6099d694 --- /dev/null +++ b/src/types/algebras/relations.jl @@ -0,0 +1,9 @@ +export GeometricalRelations, IntervalRelation, RCCRelation + +############################################################################################ + +include("relations/filtered-relations.jl"); + +############################################################################################ + +include("relations/geometrical-relations.jl"); diff --git a/src/types/algebras/relations/IntervalAlgebra.jl b/src/types/algebras/relations/IntervalAlgebra.jl new file mode 100644 index 00000000..49ef5a02 --- /dev/null +++ b/src/types/algebras/relations/IntervalAlgebra.jl @@ -0,0 +1,126 @@ +############################################################################################ +# Allen's Interval Algebra relations +############################################################################################ +doc_IntervalRelation = """ + abstract type IntervalRelation <: GeometricalRelation end + +Abstract type for interval binary relations. +Originally defined by Allen in 1983, +[interval algebra](https://en.wikipedia.org/wiki/Allen%27s_interval_algebra) +comprehends 12 directional relations between intervals, +plus the identity (i.e., `identityrel`). + +The 12 relations are +the 6 relations `after`, `later`, `begins`, `ends`, `during`, `overlaps`, +and their inverses. + +If we consider a reference interval `(x−y)`, we can graphically represent the 6 +base relations by providing an example of a world `(z−t)` that is accessible via each +of them: + +| Relation | Full name | Property | Graphical Representation w.r.t (x−y) | +| :---------- |:------------------ |:-------------------:| :----------------------------------------: | +| | | |`_____x___________________y________________`| +| | | |`_____∣−−−−−−−−−−−−−−−−−−−∣________________`| +| | | |`_____.___________________.________________`| +| | | |`_____.___________________z________t_______`| +| A | After (or meets) | y = z |`_____.___________________∣−−−−−−−−∣_______`| +| | | |`_____.___________________.________________`| +| | | |`_____.___________________.___z_________t__`| +| L | Later | y < z |`_____.___________________.___∣−−−−−−−−−∣__`| +| | | |`_____.___________________.________________`| +| | | |`_____z_____t_____________.________________`| +| B | Begins (or starts) | x = z, t < y |`_____∣−−−−−∣_____________.________________`| +| | | |`_____.___________________.________________`| +| | | |`_____._____________z_____t________________`| +| E | Ends (or finishes) | y = t, x < z |`_____._____________∣−−−−−∣________________`| +| | | |`_____.___________________.________________`| +| | | |`_____.___z________t______.________________`| +| D | During | x < z, t < y |`_____.___∣−−−−−−−−∣______.________________`| +| | | |`_____.___________________.________________`| +| | | |`_____.___________z_______.____t___________`| +| O | Overlaps | x < z < y < t |`_____.___________∣−−−−−−−−−−−−∣___________`| + +Coarser relations can be defined by union of these 12 relations. + +# Examples +```julia-repl +julia> IARelations +12-element Vector{IntervalRelation}: + _IA_A() + _IA_L() + _IA_B() + _IA_E() + _IA_D() + _IA_O() + _IA_Ai() + _IA_Li() + _IA_Bi() + _IA_Ei() + _IA_Di() + _IA_Oi() + +julia> @assert SoleLogics._IA_L() == IA_L + +julia> fr = SoleLogics.FullDimensionalFrame((10,),); + +julia> collect(accessibles(fr, Interval(2,5), IA_L)) +15-element Vector{Interval{Int64}}: + (6−7) + (6−8) + (7−8) + (6−9) + (7−9) + (8−9) + (6−10) + (7−10) + (8−10) + (9−10) + (6−11) + (7−11) + (8−11) + (9−11) + (10−11) + +julia> syntaxstring.(IARelations) +12-element Vector{String}: + "A" + "L" + "B" + "E" + "D" + "O" + "A̅" + "L̅" + "B̅" + "E̅" + "D̅" + "O̅" + +julia> syntaxstring.(IA7Relations) +6-element Vector{String}: + "AO" + "L" + "DBE" + "A̅O̅" + "L̅" + "D̅B̅E̅" + +julia> syntaxstring.(SoleLogics.IA3Relations) +3-element Vector{String}: + "I" + "L" + "L̅" + +``` + +See also [`IARelations`](@ref), +[`IA7Relations`](@ref), [`IA3Relations`](@ref), +[`Interval`](@ref), [`GeometricalRelation`](@ref). +""" + +"""$(doc_IntervalRelation)""" +abstract type IntervalRelation <: GeometricalRelation end + +arity(::IntervalRelation) = 2 +hasconverse(::IntervalRelation) = true diff --git a/src/types/algebras/relations/Point.jl b/src/types/algebras/relations/Point.jl new file mode 100644 index 00000000..9b2fec92 --- /dev/null +++ b/src/types/algebras/relations/Point.jl @@ -0,0 +1,10 @@ +"""1D Point relations""" +abstract type PointRelation <: GeometricalRelation end + +arity(::PointRelation) = 2 + +"""2D Point relations (see [Compass logic](https://ieeexplore.ieee.org/abstract/document/8133753/))""" +abstract type Point2DRelation <: GeometricalRelation end + +arity(::Point2DRelation) = 2 +hasconverse(::Point2DRelation) = true diff --git a/src/algebras/relations/RCC.jl b/src/types/algebras/relations/RCC.jl similarity index 58% rename from src/algebras/relations/RCC.jl rename to src/types/algebras/relations/RCC.jl index 873f2945..985f5c5b 100644 --- a/src/algebras/relations/RCC.jl +++ b/src/types/algebras/relations/RCC.jl @@ -116,98 +116,3 @@ hasconverse(::RCCRelation) = true # Property: all RCC relations are topological istopological(r::RCCRelation) = true - -# Relations for RCC8 -struct _Topo_DC <: RCCRelation end; const Topo_DC = _Topo_DC(); # Disconnected -struct _Topo_EC <: RCCRelation end; const Topo_EC = _Topo_EC(); # Externally connected -struct _Topo_PO <: RCCRelation end; const Topo_PO = _Topo_PO(); # Partially overlapping -struct _Topo_TPP <: RCCRelation end; const Topo_TPP = _Topo_TPP(); # Tangential proper part -struct _Topo_TPPi <: RCCRelation end; const Topo_TPPi = _Topo_TPPi(); # Tangential proper part inverse -struct _Topo_NTPP <: RCCRelation end; const Topo_NTPP = _Topo_NTPP(); # Non-tangential proper part -struct _Topo_NTPPi <: RCCRelation end; const Topo_NTPPi = _Topo_NTPPi(); # Non-tangential proper part inverse - -syntaxstring(::_Topo_DC; kwargs...) = "DC" -syntaxstring(::_Topo_EC; kwargs...) = "EC" -syntaxstring(::_Topo_PO; kwargs...) = "PO" -syntaxstring(::_Topo_TPP; kwargs...) = "TPP" -syntaxstring(::_Topo_TPPi; kwargs...) = "T̅P̅P̅" -syntaxstring(::_Topo_NTPP; kwargs...) = "NTPP" -syntaxstring(::_Topo_NTPPi; kwargs...) = "N̅T̅P̅P̅" - -# Properties -converse(r::_Topo_DC) = Topo_DC -converse(r::_Topo_EC) = Topo_EC -converse(r::_Topo_PO) = Topo_PO -converse(r::_Topo_TPP) = Topo_TPPi -converse(r::_Topo_TPPi) = Topo_TPP -converse(r::_Topo_NTPP) = Topo_NTPPi -converse(r::_Topo_NTPPi) = Topo_NTPP - -issymmetric(r::_Topo_DC) = true -issymmetric(r::_Topo_EC) = true -issymmetric(r::_Topo_PO) = true -istransitive(r::_Topo_NTPP) = true -istransitive(r::_Topo_NTPPi) = true - -############################################################################################ - -# Coarser relations for RCC5 -struct _Topo_DR <: RCCRelation end; const Topo_DR = _Topo_DR(); # Disjointed -struct _Topo_PP <: RCCRelation end; const Topo_PP = _Topo_PP(); # Proper part -struct _Topo_PPi <: RCCRelation end; const Topo_PPi = _Topo_PPi(); # Proper part inverse - -syntaxstring(::_Topo_DR; kwargs...) = "DR" -syntaxstring(::_Topo_PP; kwargs...) = "PP" -syntaxstring(::_Topo_PPi; kwargs...) = "P̅P̅" - -# Properties -converse(r::_Topo_DR) = Topo_DR -converse(r::_Topo_PP) = Topo_PPi -converse(r::_Topo_PPi) = Topo_PP -issymmetric(r::_Topo_DR) = true -istransitive(r::_Topo_PP) = true -istransitive(r::_Topo_PPi) = true -############################################################################################ - -""" - const RCC8Relations = [Topo_DC, Topo_EC, Topo_PO, Topo_TPP, Topo_TPPi, Topo_NTPP, Topo_NTPPi] - -Vector of the 7 relations from RCC8. - -See also -[`RCC5Relations`](@ref), -[`GeometricalRelation`](@ref). -""" -const RCC8Relations = [Topo_DC, Topo_EC, Topo_PO, Topo_TPP, Topo_TPPi, Topo_NTPP, Topo_NTPPi] -RCC8Relation = Union{typeof.(RCC8Relations)...} - -""" - const RCC5Relations = [Topo_DR, Topo_PO, Topo_PP, Topo_PPi] - -Vector of the 4 relations from RCC5. - -See also -[`RCC5Relations`](@ref), -[`GeometricalRelation`](@ref). -""" -const RCC5Relations = [Topo_DR, Topo_PO, Topo_PP, Topo_PPi] -RCC5Relation = Union{typeof.(RCC5Relations)...} - -############################################################################################ - -# It is conveniente to define RCC relations as unions of IA relations -const RCC8RelationFromIA = Union{_Topo_DC,_Topo_EC,_Topo_PO,_Topo_TPP,_Topo_TPPi} - -topo2IARelations(::_Topo_DC) = [IA_L, IA_Li] -topo2IARelations(::_Topo_EC) = [IA_A, IA_Ai] -topo2IARelations(::_Topo_PO) = [IA_O, IA_Oi] -topo2IARelations(::_Topo_TPP) = [IA_B, IA_E] -topo2IARelations(::_Topo_TPPi) = [IA_Bi, IA_Ei] -topo2IARelations(::_Topo_NTPP) = [IA_D] -topo2IARelations(::_Topo_NTPPi) = [IA_Di] - -# TODO RCC5 can be better written as a combination of IA7 relations! -const RCC5RelationFromRCC8 = Union{_Topo_DR,_Topo_PP,_Topo_PPi} -RCC52RCC8Relations(::_Topo_DR) = [Topo_DC, Topo_EC] -RCC52RCC8Relations(::_Topo_PP) = [Topo_TPP, Topo_NTPP] -RCC52RCC8Relations(::_Topo_PPi) = [Topo_TPPi, Topo_NTPPi] diff --git a/src/types/algebras/relations/filtered-relations.jl b/src/types/algebras/relations/filtered-relations.jl new file mode 100644 index 00000000..a5c8e638 --- /dev/null +++ b/src/types/algebras/relations/filtered-relations.jl @@ -0,0 +1,9 @@ +abstract type WorldFilter{W<:AbstractWorld} end + +function filterworlds(wf::WorldFilter, worlds) # ::AbstractArray{W}) where {W<:AbstractWorld} + return error("Please, provide method filterworlds(::$(typeof(wf)), ::$(typeof(worlds))).") +end + +function (wf::WorldFilter)(worlds) # ::AbstractArray{W}) where {W<:AbstractWorld} + return filterworlds(wf, worlds) +end diff --git a/src/algebras/relations/geometrical-relations.jl b/src/types/algebras/relations/geometrical-relations.jl similarity index 94% rename from src/algebras/relations/geometrical-relations.jl rename to src/types/algebras/relations/geometrical-relations.jl index 01ee36f9..c07b7b8a 100644 --- a/src/algebras/relations/geometrical-relations.jl +++ b/src/types/algebras/relations/geometrical-relations.jl @@ -1,4 +1,3 @@ - """ abstract type GeometricalWorld <: AbstractRelation end @@ -31,8 +30,5 @@ include("Point.jl") # 1D Allen relations include("IntervalAlgebra.jl") -# 2D Allen relations -include("IntervalAlgebra2D.jl") - # RCC relations include("RCC.jl") diff --git a/src/algebras/worlds.jl b/src/types/algebras/worlds.jl similarity index 72% rename from src/algebras/worlds.jl rename to src/types/algebras/worlds.jl index f92d4ef5..bba81de2 100644 --- a/src/algebras/worlds.jl +++ b/src/types/algebras/worlds.jl @@ -1,4 +1,3 @@ - """ Some worlds (dimensional worlds) can be interpreted on dimensional data, that is, n-dimensional arrays. The compatibility of a given world with respect of a @@ -34,27 +33,6 @@ goeswithdim(w::AbstractWorld, d) = goeswithdim(typeof(w), d) goeswithdim(W::Type{<:AbstractWorld}, d::Integer) = goeswithdim(W, Val(d)) goeswithdim(::Type{<:AbstractWorld}, ::Val) = false -############################################################################################ -# One unique world (propositional case) -############################################################################################ - -""" - struct OneWorld <: AbstractWorld end - -A singleton world to be used in modal frames with a single, unique world. -This usage effectively simulates a propositional context. -Note that it is compatible with 0-dimensional datasets. - -See also [`Interval`](@ref), [`Interval2D`](@ref), -[`goeswithdim`](@ref), [`AbstractWorld`](@ref). -""" -struct OneWorld <: AbstractWorld end - -inlinedisplay(w::OneWorld) = "−" - -# A propositional world is compatible with 0-dimensional datasets -goeswithdim(::Type{OneWorld}, ::Val{0}) = true - ############################################################################################ worlds_doc = """ diff --git a/src/types/algebras/worlds/geometrical-worlds.jl b/src/types/algebras/worlds/geometrical-worlds.jl new file mode 100644 index 00000000..0dd1772e --- /dev/null +++ b/src/types/algebras/worlds/geometrical-worlds.jl @@ -0,0 +1,8 @@ +""" + abstract type GeometricalWorld <: AbstractWorld end + +Abstract type for worlds with a geometrical interpretation. + +See also [`Point`](@ref), [`Interval`](@ref), [`Interval2D`](@ref), [`AbstractWorld`](@ref). +""" +abstract type GeometricalWorld <: AbstractWorld end diff --git a/src/types/modal-logic.jl b/src/types/modal-logic.jl index 238d1557..403420cf 100644 --- a/src/types/modal-logic.jl +++ b/src/types/modal-logic.jl @@ -73,6 +73,8 @@ function nworlds(fr::AbstractFrame)::Integer return error("Please, provide method nworlds(frame::$(typeof(fr))).") end +include("algebras/worlds.jl") + ############################################################################################ ##################################### Uni-modal logic ###################################### ############################################################################################ @@ -358,6 +360,10 @@ function accessibles( IterTools.imap(W, _accessibles(fr, w, r)) end +include("algebras/relations.jl") + +include("algebras/frames.jl") + ############################################################################################ ############################################################################################ ############################################################################################ @@ -417,171 +423,6 @@ accessibles(i::AbstractKripkeStructure, args...) = accessibles(frame(i), args... allworlds(i::AbstractKripkeStructure, args...) = allworlds(frame(i), args...) nworlds(i::AbstractKripkeStructure) = nworlds(frame(i)) -# # General grounding -# function check( -# φ::SyntaxTree, -# i::AbstractKripkeStructure; -# kwargs... -# ) -# if token(φ) isa Union{DiamondRelationalConnective,BoxRelationalConnective} -# rel = SoleLogics.relation(SoleLogics.token(φ)) -# if rel == tocenterrel -# checkw(first(children(φ)), i, centralworld(frame(i)); kwargs...) -# elseif rel == globalrel -# checkw(first(children(φ)), i, AnyWorld(); kwargs...) -# elseif isgrounding(rel) -# checkw(first(children(φ)), i, accessibles(frame(i), rel); kwargs...) -# else -# error("Unexpected formula: $φ! Perhaps ") -# end -# else -# # checkw(φ, i, nothing; kwargs...) -# error("Unexpected formula: $φ! Perhaps ") -# end -# end - -""" - 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, - )::Bool - -Check a formula on a specific word in a [`KripkeStructure`](@ref). - -# Examples -```julia-repl -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) => 0 -``` - -See also [`SyntaxTree`](@ref), [`AbstractWorld`](@ref), [`KripkeStructure`](@ref). -""" -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 -)::Bool - W = worldtype(i) - - if isnothing(w) - if nworlds(frame(i)) == 1 - w = first(allworlds(frame(i))) - end - end - @assert isgrounded(φ) || !(isnothing(w)) "Please, specify a world in order " * - "to check non-grounded formula: $(syntaxstring(φ))." - - setformula(memo_structure::AbstractDict{<:Formula}, φ::Formula, val) = memo_structure[tree(φ)] = val - readformula(memo_structure::AbstractDict{<:Formula}, φ::Formula) = memo_structure[tree(φ)] - hasformula(memo_structure::AbstractDict{<:Formula}, φ::Formula) = haskey(memo_structure, tree(φ)) - - if perform_normalization - φ = normalize(φ; profile = :modelchecking, allow_atom_flipping = false) - end - - memo_structure = begin - if isnothing(use_memo) - ThreadSafeDict{SyntaxTree,Worlds{W}}() - else - use_memo - end - end - - if !isnothing(memo_max_height) - forget_list = Vector{SyntaxTree}() - end - - fr = frame(i) - - # TODO try lazily - (_f, _c) = filter, collect - # (_f, _c) = Iterators.filter, identity - - if !hasformula(memo_structure, φ) - for ψ in unique(subformulas(φ)) - if !isnothing(memo_max_height) && height(ψ) > memo_max_height - push!(forget_list, ψ) - end - - if !hasformula(memo_structure, ψ) - tok = token(ψ) - - worldset = begin - if tok isa Connective - _c(collateworlds(fr, tok, map(f->readformula(memo_structure, f), children(ψ)))) - elseif tok isa SyntaxLeaf - _f(_w->begin - istop(interpret(tok, i, _w)) - end, _c(allworlds(fr))) - else - error("Unexpected token encountered in check: $(typeof(tok))") - end - end - setformula(memo_structure, ψ, Worlds{W}(worldset)) - end - # @show syntaxstring(ψ), readformula(memo_structure, ψ) - end - end - - if !isnothing(memo_max_height) - for ψ in forget_list - delete!(memo_structure, ψ) - end - end - - ret = begin - if isnothing(w) || w isa AnyWorld - length(readformula(memo_structure, φ)) > 0 - else - w in readformula(memo_structure, φ) - end - end - - return ret -end - ############################################################################################ ############################################################################################ ############################################################################################ diff --git a/src/algebras/frames.jl b/src/utils/algebras/frames.jl similarity index 61% rename from src/algebras/frames.jl rename to src/utils/algebras/frames.jl index 71d88758..b803f530 100644 --- a/src/algebras/frames.jl +++ b/src/utils/algebras/frames.jl @@ -13,43 +13,6 @@ accessibles(fr::AbstractUniModalFrame, ::GlobalRel) = allworlds(fr) ############################################################################################ -# It is convenient to define methods for `accessibles` that take a world set instead of a -# single world. Generally, this falls back to calling `_accessibles` on each world in -# the set, and returning a constructor of wolds from the union; however, one may provide -# improved implementations for special cases (e.g. ⟨L⟩ of a world set in interval algebra). -function accessibles( - fr::AbstractMultiModalFrame{W}, - S::AbstractWorlds, - r::AbstractRelation, -) where {W<:AbstractWorld} - IterTools.imap(W, - IterTools.distinct( - Iterators.flatten( - (_accessibles(fr, w, r) for w in S) - ) - ) - ) -end - -############################################################################################ - -""" -Return an empty world (e.g., `Interval(-1,0)`). -""" -function emptyworld(fr::AbstractMultiModalFrame) - return error("Please, provide method emptyworld(::$(typeof(fr))).") -end - -""" -Return the world at the *center* of the frame; -note that this does not always exist. -""" -function centralworld(fr::AbstractMultiModalFrame) - return error("Please, provide method centralworld(::$(typeof(fr))).") -end - -############################################################################################ - doc_tocenterrel = """ struct ToCenteredRel <: AbstractRelation end; const tocenterrel = ToCenteredRel(); diff --git a/src/algebras/frames/full-dimensional-frame/Full1DFrame+IA.jl b/src/utils/algebras/frames/full-dimensional-frame/Full1DFrame+IA.jl similarity index 100% rename from src/algebras/frames/full-dimensional-frame/Full1DFrame+IA.jl rename to src/utils/algebras/frames/full-dimensional-frame/Full1DFrame+IA.jl diff --git a/src/algebras/frames/full-dimensional-frame/Full1DFrame+RCC.jl b/src/utils/algebras/frames/full-dimensional-frame/Full1DFrame+RCC.jl similarity index 100% rename from src/algebras/frames/full-dimensional-frame/Full1DFrame+RCC.jl rename to src/utils/algebras/frames/full-dimensional-frame/Full1DFrame+RCC.jl diff --git a/src/algebras/frames/full-dimensional-frame/Full1DPointFrame.jl b/src/utils/algebras/frames/full-dimensional-frame/Full1DPointFrame.jl similarity index 100% rename from src/algebras/frames/full-dimensional-frame/Full1DPointFrame.jl rename to src/utils/algebras/frames/full-dimensional-frame/Full1DPointFrame.jl diff --git a/src/algebras/frames/full-dimensional-frame/Full2DFrame+IA2D.jl b/src/utils/algebras/frames/full-dimensional-frame/Full2DFrame+IA2D.jl similarity index 100% rename from src/algebras/frames/full-dimensional-frame/Full2DFrame+IA2D.jl rename to src/utils/algebras/frames/full-dimensional-frame/Full2DFrame+IA2D.jl diff --git a/src/algebras/frames/full-dimensional-frame/Full2DFrame+RCC.jl b/src/utils/algebras/frames/full-dimensional-frame/Full2DFrame+RCC.jl similarity index 100% rename from src/algebras/frames/full-dimensional-frame/Full2DFrame+RCC.jl rename to src/utils/algebras/frames/full-dimensional-frame/Full2DFrame+RCC.jl diff --git a/src/algebras/frames/full-dimensional-frame/Full2DPointFrame.jl b/src/utils/algebras/frames/full-dimensional-frame/Full2DPointFrame.jl similarity index 100% rename from src/algebras/frames/full-dimensional-frame/Full2DPointFrame.jl rename to src/utils/algebras/frames/full-dimensional-frame/Full2DPointFrame.jl diff --git a/src/algebras/frames/full-dimensional-frame/FullDimensionalFrame-filtered.jl b/src/utils/algebras/frames/full-dimensional-frame/FullDimensionalFrame-filtered.jl similarity index 100% rename from src/algebras/frames/full-dimensional-frame/FullDimensionalFrame-filtered.jl rename to src/utils/algebras/frames/full-dimensional-frame/FullDimensionalFrame-filtered.jl diff --git a/src/algebras/frames/full-dimensional-frame/dimensional-world-filters.jl b/src/utils/algebras/frames/full-dimensional-frame/dimensional-world-filters.jl similarity index 100% rename from src/algebras/frames/full-dimensional-frame/dimensional-world-filters.jl rename to src/utils/algebras/frames/full-dimensional-frame/dimensional-world-filters.jl diff --git a/src/algebras/frames/full-dimensional-frame/main.jl b/src/utils/algebras/frames/full-dimensional-frame/main.jl similarity index 90% rename from src/algebras/frames/full-dimensional-frame/main.jl rename to src/utils/algebras/frames/full-dimensional-frame/main.jl index a3713090..b151a87d 100644 --- a/src/algebras/frames/full-dimensional-frame/main.jl +++ b/src/utils/algebras/frames/full-dimensional-frame/main.jl @@ -1,24 +1,6 @@ using IterTools import SoleBase: dimensionality, channelsize -""" - abstract type AbstractDimensionalFrame{ - N, - W<:AbstractWorld, - } <: AbstractMultiModalFrame{W} end - -Abstract type for dimensional frames. Given a `N`-dimensional array of size (X, Y, Z, ...) -the corresponding dimensional frame is a graph where each vertex is an -`N`-hyperrectangle (e.g., an Interval/Interval2D) in the space (1:X, 1:Y, 1:Z, ...). - -See also -[`Interval`](@ref), -[`Interval2D`](@ref), -[`IntervalRelation`](@ref), -[`AbstractDimensionalFrame`](@ref), [`AbstractMultiModalFrame`](@ref). -""" -abstract type AbstractDimensionalFrame{N,W<:AbstractWorld} <: AbstractMultiModalFrame{W} end - """ struct FullDimensionalFrame{N,W<:AbstractWorld} <: AbstractDimensionalFrame{N,W} channelsize::NTuple{N,Int} @@ -172,4 +154,4 @@ include("Full2DFrame+RCC.jl") include("Full2DPointFrame.jl") -include("FullDimensionalFrame-filtered.jl") \ No newline at end of file +include("FullDimensionalFrame-filtered.jl") diff --git a/src/algebras/relations.jl b/src/utils/algebras/relations.jl similarity index 96% rename from src/algebras/relations.jl rename to src/utils/algebras/relations.jl index dc79adbd..242c4d6d 100644 --- a/src/algebras/relations.jl +++ b/src/utils/algebras/relations.jl @@ -1,5 +1,3 @@ -export GeometricalRelations, IntervalRelation, RCCRelation - # Interval algebra relations 1D # IA @@ -51,10 +49,8 @@ name(r::NamedRelation) = r.name ############################################################################################ - include("relations/filtered-relations.jl"); ############################################################################################ - include("relations/geometrical-relations.jl"); diff --git a/src/algebras/relations/IntervalAlgebra.jl b/src/utils/algebras/relations/IntervalAlgebra.jl similarity index 56% rename from src/algebras/relations/IntervalAlgebra.jl rename to src/utils/algebras/relations/IntervalAlgebra.jl index 26a4807a..455e38fe 100644 --- a/src/algebras/relations/IntervalAlgebra.jl +++ b/src/utils/algebras/relations/IntervalAlgebra.jl @@ -1,129 +1,6 @@ ############################################################################################ # Allen's Interval Algebra relations ############################################################################################ -doc_IntervalRelation = """ - abstract type IntervalRelation <: GeometricalRelation end - -Abstract type for interval binary relations. -Originally defined by Allen in 1983, -[interval algebra](https://en.wikipedia.org/wiki/Allen%27s_interval_algebra) -comprehends 12 directional relations between intervals, -plus the identity (i.e., `identityrel`). - -The 12 relations are -the 6 relations `after`, `later`, `begins`, `ends`, `during`, `overlaps`, -and their inverses. - -If we consider a reference interval `(x−y)`, we can graphically represent the 6 -base relations by providing an example of a world `(z−t)` that is accessible via each -of them: - -| Relation | Full name | Property | Graphical Representation w.r.t (x−y) | -| :---------- |:------------------ |:-------------------:| :----------------------------------------: | -| | | |`_____x___________________y________________`| -| | | |`_____∣−−−−−−−−−−−−−−−−−−−∣________________`| -| | | |`_____.___________________.________________`| -| | | |`_____.___________________z________t_______`| -| A | After (or meets) | y = z |`_____.___________________∣−−−−−−−−∣_______`| -| | | |`_____.___________________.________________`| -| | | |`_____.___________________.___z_________t__`| -| L | Later | y < z |`_____.___________________.___∣−−−−−−−−−∣__`| -| | | |`_____.___________________.________________`| -| | | |`_____z_____t_____________.________________`| -| B | Begins (or starts) | x = z, t < y |`_____∣−−−−−∣_____________.________________`| -| | | |`_____.___________________.________________`| -| | | |`_____._____________z_____t________________`| -| E | Ends (or finishes) | y = t, x < z |`_____._____________∣−−−−−∣________________`| -| | | |`_____.___________________.________________`| -| | | |`_____.___z________t______.________________`| -| D | During | x < z, t < y |`_____.___∣−−−−−−−−∣______.________________`| -| | | |`_____.___________________.________________`| -| | | |`_____.___________z_______.____t___________`| -| O | Overlaps | x < z < y < t |`_____.___________∣−−−−−−−−−−−−∣___________`| - -Coarser relations can be defined by union of these 12 relations. - -# Examples -```julia-repl -julia> IARelations -12-element Vector{IntervalRelation}: - _IA_A() - _IA_L() - _IA_B() - _IA_E() - _IA_D() - _IA_O() - _IA_Ai() - _IA_Li() - _IA_Bi() - _IA_Ei() - _IA_Di() - _IA_Oi() - -julia> @assert SoleLogics._IA_L() == IA_L - -julia> fr = SoleLogics.FullDimensionalFrame((10,),); - -julia> collect(accessibles(fr, Interval(2,5), IA_L)) -15-element Vector{Interval{Int64}}: - (6−7) - (6−8) - (7−8) - (6−9) - (7−9) - (8−9) - (6−10) - (7−10) - (8−10) - (9−10) - (6−11) - (7−11) - (8−11) - (9−11) - (10−11) - -julia> syntaxstring.(IARelations) -12-element Vector{String}: - "A" - "L" - "B" - "E" - "D" - "O" - "A̅" - "L̅" - "B̅" - "E̅" - "D̅" - "O̅" - -julia> syntaxstring.(IA7Relations) -6-element Vector{String}: - "AO" - "L" - "DBE" - "A̅O̅" - "L̅" - "D̅B̅E̅" - -julia> syntaxstring.(SoleLogics.IA3Relations) -3-element Vector{String}: - "I" - "L" - "L̅" - -``` - -See also [`IARelations`](@ref), -[`IA7Relations`](@ref), [`IA3Relations`](@ref), -[`Interval`](@ref), [`GeometricalRelation`](@ref). -""" - -"""$(doc_IntervalRelation)""" -abstract type IntervalRelation <: GeometricalRelation end - -arity(::IntervalRelation) = 2 -hasconverse(::IntervalRelation) = true struct _IA_A <: IntervalRelation end; """See [`IntervalRelation`](@ref)""" const IA_A = _IA_A(); # After struct _IA_L <: IntervalRelation end; """See [`IntervalRelation`](@ref)""" const IA_L = _IA_L(); # Later @@ -218,7 +95,7 @@ syntaxstring(::_IA_I; kwargs...) = "I" """ const IARelations = [IA_A, IA_L, IA_B, IA_E, IA_D, IA_O, - IA_Ai, IA_Li, IA_Bi, IA_Ei, IA_Di, IA_Oi] + IA_Ai, IA_Li, IA_Bi, IA_Ei, IA_Di, IA_Oi] Vector of the 12 interval relations from Allen's interval algebra. @@ -227,12 +104,12 @@ See also [`IntervalRelation`](@ref), [`GeometricalRelation`](@ref). """ const IARelations = [IA_A, IA_L, IA_B, IA_E, IA_D, IA_O, - IA_Ai, IA_Li, IA_Bi, IA_Ei, IA_Di, IA_Oi] + IA_Ai, IA_Li, IA_Bi, IA_Ei, IA_Di, IA_Oi] IARelation = Union{typeof.(IARelations)...} """ const IA7Relations = [IA_AorO, IA_L, IA_DorBorE, - IA_AiorOi, IA_Li, IA_DiorBiorEi] + IA_AiorOi, IA_Li, IA_DiorBiorEi] Vector of 7 interval relations from a coarser version of Allen's interval algebra. @@ -241,7 +118,7 @@ See also [`IntervalRelation`](@ref), [`GeometricalRelation`](@ref). """ const IA7Relations = [IA_AorO, IA_L, IA_DorBorE, - IA_AiorOi, IA_Li, IA_DiorBiorEi] + IA_AiorOi, IA_Li, IA_DiorBiorEi] """ const IA3Relations = [IA_I, IA_L, IA_Li] diff --git a/src/algebras/relations/IntervalAlgebra2D.jl b/src/utils/algebras/relations/IntervalAlgebra2D.jl similarity index 100% rename from src/algebras/relations/IntervalAlgebra2D.jl rename to src/utils/algebras/relations/IntervalAlgebra2D.jl diff --git a/src/algebras/relations/Point.jl b/src/utils/algebras/relations/Point.jl similarity index 92% rename from src/algebras/relations/Point.jl rename to src/utils/algebras/relations/Point.jl index 10bcefb4..4f15e525 100644 --- a/src/algebras/relations/Point.jl +++ b/src/utils/algebras/relations/Point.jl @@ -1,9 +1,3 @@ - -"""1D Point relations""" -abstract type PointRelation <: GeometricalRelation end - -arity(::PointRelation) = 2 - """Relation leading to the *minimum* `Point` (i.e., the *least* in the linear order).""" struct _MinRel <: PointRelation end; const MinRel = _MinRel(); # Minimum @@ -12,14 +6,16 @@ struct _MaxRel <: PointRelation end; const MaxRel = _MaxRel(); """Relation leading to the *successor* `Point` (i.e., the *next* in the linear order).""" struct _SuccessorRel <: PointRelation end; const SuccessorRel = _SuccessorRel(); # Successor + """Relation leading to the *predecessor* `Point` (i.e., the *previous* in the linear order).""" struct _PredecessorRel <: PointRelation end; const PredecessorRel = _PredecessorRel(); # Predecessor + """Relation leading to the *greater* `Point`s in the linear order.""" struct _GreaterRel <: PointRelation end; const GreaterRel = _GreaterRel(); # Greater + """Relation leading to the *lesser* `Point`s in the linear order.""" struct _LesserRel <: PointRelation end; const LesserRel = _LesserRel(); # Lesser - hasconverse(::_SuccessorRel) = true converse(::_SuccessorRel) = PredecessorRel hasconverse(::_PredecessorRel) = true @@ -56,13 +52,6 @@ See also """ const PointRelations = [MinRel, MaxRel, SuccessorRel, PredecessorRel, GreaterRel, LesserRel] - -"""2D Point relations (see [Compass logic](https://ieeexplore.ieee.org/abstract/document/8133753/))""" -abstract type Point2DRelation <: GeometricalRelation end - -arity(::Point2DRelation) = 2 -hasconverse(::Point2DRelation) = true - struct _CL_N <: Point2DRelation end; """Relation leading to the closest northern `Point2D`.""" const CL_N = _CL_N(); # North struct _CL_S <: Point2DRelation end; """Relation leading to the closest southern `Point2D`.""" const CL_S = _CL_S(); # South struct _CL_E <: Point2DRelation end; """Relation leading to the closest eastern `Point2D`.""" const CL_E = _CL_E(); # East diff --git a/src/utils/algebras/relations/RCC.jl b/src/utils/algebras/relations/RCC.jl new file mode 100644 index 00000000..206e2e26 --- /dev/null +++ b/src/utils/algebras/relations/RCC.jl @@ -0,0 +1,98 @@ +############################################################################################ +# RCC topological relations +############################################################################################ + +# Relations for RCC8 +struct _Topo_DC <: RCCRelation end; const Topo_DC = _Topo_DC(); # Disconnected +struct _Topo_EC <: RCCRelation end; const Topo_EC = _Topo_EC(); # Externally connected +struct _Topo_PO <: RCCRelation end; const Topo_PO = _Topo_PO(); # Partially overlapping +struct _Topo_TPP <: RCCRelation end; const Topo_TPP = _Topo_TPP(); # Tangential proper part +struct _Topo_TPPi <: RCCRelation end; const Topo_TPPi = _Topo_TPPi(); # Tangential proper part inverse +struct _Topo_NTPP <: RCCRelation end; const Topo_NTPP = _Topo_NTPP(); # Non-tangential proper part +struct _Topo_NTPPi <: RCCRelation end; const Topo_NTPPi = _Topo_NTPPi(); # Non-tangential proper part inverse + +syntaxstring(::_Topo_DC; kwargs...) = "DC" +syntaxstring(::_Topo_EC; kwargs...) = "EC" +syntaxstring(::_Topo_PO; kwargs...) = "PO" +syntaxstring(::_Topo_TPP; kwargs...) = "TPP" +syntaxstring(::_Topo_TPPi; kwargs...) = "T̅P̅P̅" +syntaxstring(::_Topo_NTPP; kwargs...) = "NTPP" +syntaxstring(::_Topo_NTPPi; kwargs...) = "N̅T̅P̅P̅" + +# Properties +converse(r::_Topo_DC) = Topo_DC +converse(r::_Topo_EC) = Topo_EC +converse(r::_Topo_PO) = Topo_PO +converse(r::_Topo_TPP) = Topo_TPPi +converse(r::_Topo_TPPi) = Topo_TPP +converse(r::_Topo_NTPP) = Topo_NTPPi +converse(r::_Topo_NTPPi) = Topo_NTPP + +issymmetric(r::_Topo_DC) = true +issymmetric(r::_Topo_EC) = true +issymmetric(r::_Topo_PO) = true +istransitive(r::_Topo_NTPP) = true +istransitive(r::_Topo_NTPPi) = true + +############################################################################################ + +# Coarser relations for RCC5 +struct _Topo_DR <: RCCRelation end; const Topo_DR = _Topo_DR(); # Disjointed +struct _Topo_PP <: RCCRelation end; const Topo_PP = _Topo_PP(); # Proper part +struct _Topo_PPi <: RCCRelation end; const Topo_PPi = _Topo_PPi(); # Proper part inverse + +syntaxstring(::_Topo_DR; kwargs...) = "DR" +syntaxstring(::_Topo_PP; kwargs...) = "PP" +syntaxstring(::_Topo_PPi; kwargs...) = "P̅P̅" + +# Properties +converse(r::_Topo_DR) = Topo_DR +converse(r::_Topo_PP) = Topo_PPi +converse(r::_Topo_PPi) = Topo_PP +issymmetric(r::_Topo_DR) = true +istransitive(r::_Topo_PP) = true +istransitive(r::_Topo_PPi) = true +############################################################################################ + +""" + const RCC8Relations = [Topo_DC, Topo_EC, Topo_PO, Topo_TPP, Topo_TPPi, Topo_NTPP, Topo_NTPPi] + +Vector of the 7 relations from RCC8. + +See also +[`RCC5Relations`](@ref), +[`GeometricalRelation`](@ref). +""" +const RCC8Relations = [Topo_DC, Topo_EC, Topo_PO, Topo_TPP, Topo_TPPi, Topo_NTPP, Topo_NTPPi] +RCC8Relation = Union{typeof.(RCC8Relations)...} + +""" + const RCC5Relations = [Topo_DR, Topo_PO, Topo_PP, Topo_PPi] + +Vector of the 4 relations from RCC5. + +See also +[`RCC5Relations`](@ref), +[`GeometricalRelation`](@ref). +""" +const RCC5Relations = [Topo_DR, Topo_PO, Topo_PP, Topo_PPi] +RCC5Relation = Union{typeof.(RCC5Relations)...} + +############################################################################################ + +# It is conveniente to define RCC relations as unions of IA relations +const RCC8RelationFromIA = Union{_Topo_DC,_Topo_EC,_Topo_PO,_Topo_TPP,_Topo_TPPi} + +topo2IARelations(::_Topo_DC) = [IA_L, IA_Li] +topo2IARelations(::_Topo_EC) = [IA_A, IA_Ai] +topo2IARelations(::_Topo_PO) = [IA_O, IA_Oi] +topo2IARelations(::_Topo_TPP) = [IA_B, IA_E] +topo2IARelations(::_Topo_TPPi) = [IA_Bi, IA_Ei] +topo2IARelations(::_Topo_NTPP) = [IA_D] +topo2IARelations(::_Topo_NTPPi) = [IA_Di] + +# TODO RCC5 can be better written as a combination of IA7 relations! +const RCC5RelationFromRCC8 = Union{_Topo_DR,_Topo_PP,_Topo_PPi} +RCC52RCC8Relations(::_Topo_DR) = [Topo_DC, Topo_EC] +RCC52RCC8Relations(::_Topo_PP) = [Topo_TPP, Topo_NTPP] +RCC52RCC8Relations(::_Topo_PPi) = [Topo_TPPi, Topo_NTPPi] diff --git a/src/algebras/relations/filtered-relations.jl b/src/utils/algebras/relations/filtered-relations.jl similarity index 92% rename from src/algebras/relations/filtered-relations.jl rename to src/utils/algebras/relations/filtered-relations.jl index 6d6e98a1..37dc8f78 100644 --- a/src/algebras/relations/filtered-relations.jl +++ b/src/utils/algebras/relations/filtered-relations.jl @@ -1,17 +1,6 @@ -abstract type WorldFilter{W<:AbstractWorld} end - -function filterworlds(wf::WorldFilter, worlds) # ::AbstractArray{W}) where {W<:AbstractWorld} - return error("Please, provide method filterworlds(::$(typeof(wf)), ::$(typeof(worlds))).") -end - -function (wf::WorldFilter)(worlds) # ::AbstractArray{W}) where {W<:AbstractWorld} - return filterworlds(wf, worlds) -end - using FunctionWrappers using FunctionWrappers: FunctionWrapper - """ struct FunctionalWorldFilter{W <: AbstractWorld, F <: Function} <: WorldFilter{W} filter::FunctionWrapper{Bool, Tuple{W}} diff --git a/src/utils/algebras/relations/geometrical-relations.jl b/src/utils/algebras/relations/geometrical-relations.jl new file mode 100644 index 00000000..b03b2b3f --- /dev/null +++ b/src/utils/algebras/relations/geometrical-relations.jl @@ -0,0 +1,11 @@ +# Point relations +include("Point.jl") + +# 1D Allen relations +include("IntervalAlgebra.jl") + +# 2D Allen relations +include("IntervalAlgebra2D.jl") + +# RCC relations +include("RCC.jl") diff --git a/src/utils/algebras/worlds.jl b/src/utils/algebras/worlds.jl new file mode 100644 index 00000000..9f0bfe5e --- /dev/null +++ b/src/utils/algebras/worlds.jl @@ -0,0 +1,24 @@ +############################################################################################ +# One unique world (propositional case) +############################################################################################ + +""" + struct OneWorld <: AbstractWorld end + +A singleton world to be used in modal frames with a single, unique world. +This usage effectively simulates a propositional context. +Note that it is compatible with 0-dimensional datasets. + +See also [`Interval`](@ref), [`Interval2D`](@ref), +[`goeswithdim`](@ref), [`AbstractWorld`](@ref). +""" +struct OneWorld <: AbstractWorld end + +inlinedisplay(w::OneWorld) = "−" + +# A propositional world is compatible with 0-dimensional datasets +goeswithdim(::Type{OneWorld}, ::Val{0}) = true + +############################################################################################ + +include("worlds/geometrical-worlds.jl") diff --git a/src/algebras/worlds/geometrical-worlds.jl b/src/utils/algebras/worlds/geometrical-worlds.jl similarity index 94% rename from src/algebras/worlds/geometrical-worlds.jl rename to src/utils/algebras/worlds/geometrical-worlds.jl index 841f895f..12df0a66 100644 --- a/src/algebras/worlds/geometrical-worlds.jl +++ b/src/utils/algebras/worlds/geometrical-worlds.jl @@ -1,14 +1,5 @@ import Base: length -""" - abstract type GeometricalWorld <: AbstractWorld end - -Abstract type for worlds with a geometrical interpretation. - -See also [`Point`](@ref), [`Interval`](@ref), [`Interval2D`](@ref), [`AbstractWorld`](@ref). -""" -abstract type GeometricalWorld <: AbstractWorld end - ############################################################################################ # Point ############################################################################################ diff --git a/src/utils/modal-logic.jl b/src/utils/modal-logic.jl index 885f74ec..c7b695ec 100644 --- a/src/utils/modal-logic.jl +++ b/src/utils/modal-logic.jl @@ -254,6 +254,171 @@ include("algebras/frames.jl") # TODO explain struct AnyWorld end +# # General grounding +# function check( +# φ::SyntaxTree, +# i::AbstractKripkeStructure; +# kwargs... +# ) +# if token(φ) isa Union{DiamondRelationalConnective,BoxRelationalConnective} +# rel = SoleLogics.relation(SoleLogics.token(φ)) +# if rel == tocenterrel +# checkw(first(children(φ)), i, centralworld(frame(i)); kwargs...) +# elseif rel == globalrel +# checkw(first(children(φ)), i, AnyWorld(); kwargs...) +# elseif isgrounding(rel) +# checkw(first(children(φ)), i, accessibles(frame(i), rel); kwargs...) +# else +# error("Unexpected formula: $φ! Perhaps ") +# end +# else +# # checkw(φ, i, nothing; kwargs...) +# error("Unexpected formula: $φ! Perhaps ") +# end +# end + +""" + 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, + )::Bool + +Check a formula on a specific word in a [`KripkeStructure`](@ref). + +# Examples +```julia-repl +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) => 0 +``` + +See also [`SyntaxTree`](@ref), [`AbstractWorld`](@ref), [`KripkeStructure`](@ref). +""" +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 +)::Bool + W = worldtype(i) + + if isnothing(w) + if nworlds(frame(i)) == 1 + w = first(allworlds(frame(i))) + end + end + @assert isgrounded(φ) || !(isnothing(w)) "Please, specify a world in order " * + "to check non-grounded formula: $(syntaxstring(φ))." + + setformula(memo_structure::AbstractDict{<:Formula}, φ::Formula, val) = memo_structure[tree(φ)] = val + readformula(memo_structure::AbstractDict{<:Formula}, φ::Formula) = memo_structure[tree(φ)] + hasformula(memo_structure::AbstractDict{<:Formula}, φ::Formula) = haskey(memo_structure, tree(φ)) + + if perform_normalization + φ = normalize(φ; profile = :modelchecking, allow_atom_flipping = false) + end + + memo_structure = begin + if isnothing(use_memo) + ThreadSafeDict{SyntaxTree,Worlds{W}}() + else + use_memo + end + end + + if !isnothing(memo_max_height) + forget_list = Vector{SyntaxTree}() + end + + fr = frame(i) + + # TODO try lazily + (_f, _c) = filter, collect + # (_f, _c) = Iterators.filter, identity + + if !hasformula(memo_structure, φ) + for ψ in unique(subformulas(φ)) + if !isnothing(memo_max_height) && height(ψ) > memo_max_height + push!(forget_list, ψ) + end + + if !hasformula(memo_structure, ψ) + tok = token(ψ) + + worldset = begin + if tok isa Connective + _c(collateworlds(fr, tok, map(f->readformula(memo_structure, f), children(ψ)))) + elseif tok isa SyntaxLeaf + _f(_w->begin + istop(interpret(tok, i, _w)) + end, _c(allworlds(fr))) + else + error("Unexpected token encountered in check: $(typeof(tok))") + end + end + setformula(memo_structure, ψ, Worlds{W}(worldset)) + end + # @show syntaxstring(ψ), readformula(memo_structure, ψ) + end + end + + if !isnothing(memo_max_height) + for ψ in forget_list + delete!(memo_structure, ψ) + end + end + + ret = begin + if isnothing(w) || w isa AnyWorld + length(readformula(memo_structure, φ)) > 0 + else + w in readformula(memo_structure, φ) + end + end + + return ret +end + """ struct KripkeStructure{ FR<:AbstractFrame, From 6aef556456f1e621fc35e6a5e101c4d542425c55 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:41:31 +0200 Subject: [PATCH 62/90] ToCenteredRel -> ToCenterRel. Rename files. Add a couple of docstrings. This is great! --- src/deprecate.jl | 2 ++ src/types/algebras/frames.jl | 14 ++++++++++--- .../main.jl => dimensional-frame.jl} | 3 ++- src/types/algebras/relations.jl | 1 - .../algebras/relations/filtered-relations.jl | 12 +++++++++++ src/types/algebras/worlds.jl | 2 +- ...etrical-worlds.jl => geometrical-world.jl} | 0 src/types/modal-logic.jl | 7 ++----- src/utils/algebras/frames.jl | 20 +++++++++---------- src/utils/algebras/relations.jl | 6 ++++-- .../algebras/relations/IntervalAlgebra2D.jl | 2 +- src/utils/algebras/relations/Point.jl | 8 ++++---- .../algebras/relations/filtered-relations.jl | 18 +++++++++-------- src/utils/algebras/worlds.jl | 2 +- src/utils/modal-logic.jl | 6 +++--- src/utils/syntactical.jl | 4 ++-- test/core.jl | 4 ++-- test/generation/formula.jl | 16 +++++++-------- test/kripke-image.jl | 2 +- 19 files changed, 76 insertions(+), 53 deletions(-) rename src/types/algebras/frames/{full-dimensional-frame/main.jl => dimensional-frame.jl} (84%) rename src/types/algebras/worlds/{geometrical-worlds.jl => geometrical-world.jl} (100%) diff --git a/src/deprecate.jl b/src/deprecate.jl index 4c3ab66a..c032ac22 100644 --- a/src/deprecate.jl +++ b/src/deprecate.jl @@ -1,6 +1,8 @@ # core.jl export atom, Proposition, NamedOperator +const ToCenteredRel = ToCenterRel + const NamedOperator = NamedConnective const AbstractSyntaxStructure = SyntaxStructure diff --git a/src/types/algebras/frames.jl b/src/types/algebras/frames.jl index d796b059..64997038 100644 --- a/src/types/algebras/frames.jl +++ b/src/types/algebras/frames.jl @@ -19,15 +19,23 @@ end ############################################################################################ """ + emptyworld(fr::AbstractMultiModalFrame) + Return an empty world (e.g., `Interval(-1,0)`). + +See also [`AbstractMultiModalFrame`](@ref). """ function emptyworld(fr::AbstractMultiModalFrame) return error("Please, provide method emptyworld(::$(typeof(fr))).") end """ -Return the world at the *center* of the frame; -note that this does not always exist. + centralworld(fr::AbstractMultiModalFrame) + +Return the world at the *center* of the frame (whenever there exists a definition of +"center" that makes sense). + +See also [`AbstractDimensionalFrame`](@ref), [`AbstractMultiModalFrame`](@ref). """ function centralworld(fr::AbstractMultiModalFrame) return error("Please, provide method centralworld(::$(typeof(fr))).") @@ -35,4 +43,4 @@ end ############################################################################################ -include("frames/full-dimensional-frame/main.jl") +include("frames/dimensional-frame.jl") diff --git a/src/types/algebras/frames/full-dimensional-frame/main.jl b/src/types/algebras/frames/dimensional-frame.jl similarity index 84% rename from src/types/algebras/frames/full-dimensional-frame/main.jl rename to src/types/algebras/frames/dimensional-frame.jl index 4e9c3243..11841ace 100644 --- a/src/types/algebras/frames/full-dimensional-frame/main.jl +++ b/src/types/algebras/frames/dimensional-frame.jl @@ -1,3 +1,4 @@ + """ abstract type AbstractDimensionalFrame{ N, @@ -14,4 +15,4 @@ See also [`IntervalRelation`](@ref), [`AbstractDimensionalFrame`](@ref), [`AbstractMultiModalFrame`](@ref). """ -abstract type AbstractDimensionalFrame{N,W<:AbstractWorld} <: AbstractMultiModalFrame{W} end +abstract type AbstractDimensionalFrame{N, W <: AbstractWorld} <: AbstractMultiModalFrame{W} end diff --git a/src/types/algebras/relations.jl b/src/types/algebras/relations.jl index 6099d694..3c8eac3c 100644 --- a/src/types/algebras/relations.jl +++ b/src/types/algebras/relations.jl @@ -1,4 +1,3 @@ -export GeometricalRelations, IntervalRelation, RCCRelation ############################################################################################ diff --git a/src/types/algebras/relations/filtered-relations.jl b/src/types/algebras/relations/filtered-relations.jl index a5c8e638..9a853675 100644 --- a/src/types/algebras/relations/filtered-relations.jl +++ b/src/types/algebras/relations/filtered-relations.jl @@ -1,5 +1,17 @@ +""" + abstract type WorldFilter{W<:AbstractWorld} end + +An object that selects some worlds. Can be used in filtered relations. + +See also [`filterworlds`](@ref), [`FilteredRelation`](@ref). +""" abstract type WorldFilter{W<:AbstractWorld} end +""" + filterworlds(wf::WorldFilter, worlds) + +Return an iterator to the `worlds` that satisfy the filter. +""" function filterworlds(wf::WorldFilter, worlds) # ::AbstractArray{W}) where {W<:AbstractWorld} return error("Please, provide method filterworlds(::$(typeof(wf)), ::$(typeof(worlds))).") end diff --git a/src/types/algebras/worlds.jl b/src/types/algebras/worlds.jl index bba81de2..c53f7c9c 100644 --- a/src/types/algebras/worlds.jl +++ b/src/types/algebras/worlds.jl @@ -56,4 +56,4 @@ const Worlds{W} = Vector{W} where {W<:AbstractWorld} ############################################################################################ -include("worlds/geometrical-worlds.jl") +include("worlds/geometrical-world.jl") diff --git a/src/types/algebras/worlds/geometrical-worlds.jl b/src/types/algebras/worlds/geometrical-world.jl similarity index 100% rename from src/types/algebras/worlds/geometrical-worlds.jl rename to src/types/algebras/worlds/geometrical-world.jl diff --git a/src/types/modal-logic.jl b/src/types/modal-logic.jl index 403420cf..3f167b80 100644 --- a/src/types/modal-logic.jl +++ b/src/types/modal-logic.jl @@ -1,7 +1,4 @@ import Base: show -using DataStructures: OrderedDict -using Graphs -using ThreadSafeDicts """ abstract type AbstractWorld end @@ -73,8 +70,6 @@ function nworlds(fr::AbstractFrame)::Integer return error("Please, provide method nworlds(frame::$(typeof(fr))).") end -include("algebras/worlds.jl") - ############################################################################################ ##################################### Uni-modal logic ###################################### ############################################################################################ @@ -360,6 +355,8 @@ function accessibles( IterTools.imap(W, _accessibles(fr, w, r)) end +include("algebras/worlds.jl") + include("algebras/relations.jl") include("algebras/frames.jl") diff --git a/src/utils/algebras/frames.jl b/src/utils/algebras/frames.jl index b803f530..59666954 100644 --- a/src/utils/algebras/frames.jl +++ b/src/utils/algebras/frames.jl @@ -14,8 +14,8 @@ accessibles(fr::AbstractUniModalFrame, ::GlobalRel) = allworlds(fr) ############################################################################################ doc_tocenterrel = """ - struct ToCenteredRel <: AbstractRelation end; - const tocenterrel = ToCenteredRel(); + struct ToCenterRel <: AbstractRelation end; + const tocenterrel = ToCenterRel(); Singleton type for a relation that leads to the world at the center of a frame. The relation is transitive. @@ -36,19 +36,19 @@ See also """ """$(doc_tocenterrel)""" -struct ToCenteredRel <: AbstractRelation end; +struct ToCenterRel <: AbstractRelation end; """$(doc_tocenterrel)""" -const tocenterrel = ToCenteredRel(); +const tocenterrel = ToCenterRel(); -accessibles(fr::AbstractMultiModalFrame, ::AbstractWorld, r::ToCenteredRel) = [centralworld(fr)] +accessibles(fr::AbstractMultiModalFrame, ::AbstractWorld, r::ToCenterRel) = [centralworld(fr)] -arity(::ToCenteredRel) = 2 +arity(::ToCenterRel) = 2 -syntaxstring(::ToCenteredRel; kwargs...) = "◉" +syntaxstring(::ToCenterRel; kwargs...) = "◉" -hasconverse(::ToCenteredRel) = false -istransitive(::ToCenteredRel) = true -isgrounding(::ToCenteredRel) = true +hasconverse(::ToCenterRel) = false +istransitive(::ToCenterRel) = true +isgrounding(::ToCenterRel) = true ############################################################################################ diff --git a/src/utils/algebras/relations.jl b/src/utils/algebras/relations.jl index 242c4d6d..45d1f906 100644 --- a/src/utils/algebras/relations.jl +++ b/src/utils/algebras/relations.jl @@ -29,8 +29,8 @@ Type for relations that are solely defined by their name. See also [`AbstractRelation`](@ref), [`AbstractWorld`](@ref), -[`AbstractFrame`](@ref). -[`AbstractKripkeStructure`](@ref), +[`AbstractFrame`](@ref), +[`AbstractKripkeStructure`](@ref). """ struct NamedRelation{T} <: AbstractRelation name::T @@ -53,4 +53,6 @@ include("relations/filtered-relations.jl"); ############################################################################################ +export GeometricalRelations, IntervalRelation, RCCRelation + include("relations/geometrical-relations.jl"); diff --git a/src/utils/algebras/relations/IntervalAlgebra2D.jl b/src/utils/algebras/relations/IntervalAlgebra2D.jl index 350d5dbf..be6da745 100644 --- a/src/utils/algebras/relations/IntervalAlgebra2D.jl +++ b/src/utils/algebras/relations/IntervalAlgebra2D.jl @@ -31,7 +31,7 @@ julia> length(IA2DRelations) ``` See also [`Interval`](@ref), [`Interval2D`](@ref), -[`IntervalRelation`](@ref), [`[`GeometricalRelation`](@ref). +[`IntervalRelation`](@ref), [`GeometricalRelation`](@ref). """ """$(doc_rectangle_rel)""" diff --git a/src/utils/algebras/relations/Point.jl b/src/utils/algebras/relations/Point.jl index 4f15e525..da07580f 100644 --- a/src/utils/algebras/relations/Point.jl +++ b/src/utils/algebras/relations/Point.jl @@ -4,16 +4,16 @@ struct _MinRel <: PointRelation end; const MinRel = _MinRel(); """Relation leading to the *maximum* `Point` (i.e., the *greatest* in the linear order).""" struct _MaxRel <: PointRelation end; const MaxRel = _MaxRel(); # Maximum -"""Relation leading to the *successor* `Point` (i.e., the *next* in the linear order).""" +"""Relation leading to the *successor* `Point` (i.e., the *next* in the linear order). It is displayed as `X` (for "neXt").""" struct _SuccessorRel <: PointRelation end; const SuccessorRel = _SuccessorRel(); # Successor -"""Relation leading to the *predecessor* `Point` (i.e., the *previous* in the linear order).""" +"""Relation leading to the *predecessor* `Point` (i.e., the *previous* in the linear order). It is displayed as `X̅` (for "neXt inverse").""" struct _PredecessorRel <: PointRelation end; const PredecessorRel = _PredecessorRel(); # Predecessor -"""Relation leading to the *greater* `Point`s in the linear order.""" +"""> relation, leading to *greater* `Point`s in the linear order.""" struct _GreaterRel <: PointRelation end; const GreaterRel = _GreaterRel(); # Greater -"""Relation leading to the *lesser* `Point`s in the linear order.""" +"""< relation, leading to *lesser* `Point`s in the linear order.""" struct _LesserRel <: PointRelation end; const LesserRel = _LesserRel(); # Lesser hasconverse(::_SuccessorRel) = true diff --git a/src/utils/algebras/relations/filtered-relations.jl b/src/utils/algebras/relations/filtered-relations.jl index 37dc8f78..3cfdef6c 100644 --- a/src/utils/algebras/relations/filtered-relations.jl +++ b/src/utils/algebras/relations/filtered-relations.jl @@ -6,14 +6,16 @@ using FunctionWrappers: FunctionWrapper filter::FunctionWrapper{Bool, Tuple{W}} end - FunctionalWorldFilter{W, F}(filter::FunctionWrapper{Bool, Tuple{W}}) where {W <: AbstractWorld, F <: Function} - FunctionalWorldFilter(filter::FunctionWrapper{Bool, Tuple{W}}, functiontype::Type{F}) where {W <: AbstractWorld, F <: Function} - FunctionalWorldFilter{W, F}(filter::F) where {W <: AbstractWorld, F <: Function} - FunctionalWorldFilter{W}(filter::F) where {W <: AbstractWorld, F <: Function} - FunctionalWorldFilter(filter::F, worldtype::Type{W}) where {W <: AbstractWorld, F <: Function} - -Please provide a function as filter so that it takes as input an object subtype of -AbstractWorld and it gives as output a Bool. +A world filter based on a function from `AbstractWorld` to `Bool`. + +# Constructors +- `FunctionalWorldFilter{W, F}(filter::FunctionWrapper{Bool, Tuple{W}}) where {W <: AbstractWorld, F <: Function}` +- `FunctionalWorldFilter(filter::FunctionWrapper{Bool, Tuple{W}}, functiontype::Type{F}) where {W <: AbstractWorld, F <: Function}` +- `FunctionalWorldFilter{W, F}(filter::F) where {W <: AbstractWorld, F <: Function}` +- `FunctionalWorldFilter{W}(filter::F) where {W <: AbstractWorld, F <: Function}` +- `FunctionalWorldFilter(filter::F, worldtype::Type{W}) where {W <: AbstractWorld, F <: Function}` + +See also [`filterworlds`](@ref), [`FilteredRelation`](@ref). """ struct FunctionalWorldFilter{W<:AbstractWorld,F<:Function} <: WorldFilter{W} filter::FunctionWrapper{Bool,Tuple{W}} diff --git a/src/utils/algebras/worlds.jl b/src/utils/algebras/worlds.jl index 9f0bfe5e..57ce2773 100644 --- a/src/utils/algebras/worlds.jl +++ b/src/utils/algebras/worlds.jl @@ -1,5 +1,5 @@ ############################################################################################ -# One unique world (propositional case) +# One unique world in a singleton frame (propositional logic) ############################################################################################ """ diff --git a/src/utils/modal-logic.jl b/src/utils/modal-logic.jl index c7b695ec..76c3fb4f 100644 --- a/src/utils/modal-logic.jl +++ b/src/utils/modal-logic.jl @@ -29,7 +29,6 @@ include("algebras/worlds.jl") ############################################################################################ # """ -# TODO Mauro # Association "(w1,w2) => truth_value". Not recommended in sparse scenarios. # """ # struct AdjMatUniModalFrame{W<:AbstractWorld,T<:Truth} <: AbstractUniModalFrame{W} @@ -41,8 +40,9 @@ include("algebras/worlds.jl") # accessibles(...) = ... # TODO move truth value out of frame (frame is passive, perhaps it is relations that have a truth value) + """ -TODO +A unimodal frame given by an `Graphs.SimpleGraphs.AbstractSimpleGraph` on worlds. """ struct ExplicitCrispUniModalFrame{ W<:AbstractWorld, @@ -251,7 +251,7 @@ include("algebras/relations.jl") include("algebras/frames.jl") -# TODO explain +# TODO explain: a constant representing any world (only to be used for checking grounded formulas). struct AnyWorld end # # General grounding diff --git a/src/utils/syntactical.jl b/src/utils/syntactical.jl index eeefdad1..6c02bee7 100644 --- a/src/utils/syntactical.jl +++ b/src/utils/syntactical.jl @@ -47,9 +47,9 @@ struct Atom{V} <: AbstractAtom function Atom{V}(value::V) where {V} if value isa Union{Formula, Connective} - throw("Illegal nesting. " * + throw(ArgumentError("Illegal nesting. " * "Cannot instantiate Atom with value of type $(typeof(value))" - ) + )) end new{V}(value) end diff --git a/test/core.jl b/test/core.jl index 96cf0a0b..da00d7cb 100644 --- a/test/core.jl +++ b/test/core.jl @@ -13,8 +13,8 @@ p1_number = @test_nowarn Atom{Number}(1) p_string = @test_nowarn Atom{String}("1") @test Atom(Atom(1)) == Atom(1) -@test_throws AssertionError Atom(parseformula("¬p")) -@test_throws AssertionError Atom(¬) +@test_throws ArgumentError Atom(parseformula("¬p")) +@test_throws ArgumentError Atom(¬) @test arity(p1) == 0 @test Atom(1.0) != Atom(1) diff --git a/test/generation/formula.jl b/test/generation/formula.jl index 8a115bab..976c6dd3 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -18,13 +18,13 @@ w = [10,1,1] -# @testset "generation w. custom operators" begin +@testset "generation w. custom operators" begin -TERNOP = SoleLogics.NamedConnective{:⇶}() -SoleLogics.arity(::typeof(TERNOP)) = 3 +my_TERNOP = SoleLogics.NamedConnective{:⇶}() +SoleLogics.arity(::typeof(my_TERNOP)) = 3 -QUATERNOP = SoleLogics.NamedConnective{:⩰}() -SoleLogics.arity(::typeof(QUATERNOP)) = 4 +my_QUATERNOP = SoleLogics.NamedConnective{:⩰}() +SoleLogics.arity(::typeof(my_QUATERNOP)) = 4 _alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) _operators = [NEGATION, CONJUNCTION, IMPLICATION, @@ -38,11 +38,11 @@ w = [5,1,1,1,1,1,1] end for i in 1:10]) @test all([begin - f = randformula(i%5, _alphabet, [_operators..., TERNOP, QUATERNOP]) + f = randformula(i%5, _alphabet, [_operators..., my_TERNOP, my_QUATERNOP]) s = syntaxstring(f; function_notation = true) s == syntaxstring( parseformula( - s, [_operators..., TERNOP, QUATERNOP]; function_notation = true), + s, [_operators..., my_TERNOP, my_QUATERNOP]; function_notation = true), function_notation = true) end for i in 1:10]) @@ -53,7 +53,7 @@ w = [5,1,1,1,1,1,1] # function_notation = true) # end for i in 1:10]) -# end # endof test set +end # endof test set diff --git a/test/kripke-image.jl b/test/kripke-image.jl index 83f402ac..8c872a36 100644 --- a/test/kripke-image.jl +++ b/test/kripke-image.jl @@ -13,7 +13,7 @@ function interpret(a::Atom{Int}, i::BWImageKripkeStructure, w::Point{2,Int}) return (valueatpoint >= integervalue) ? TOP : BOT end -frame(i::AbstractKripkeStructure) = FullDimensionalFrame(size(i.image), Point{2,Int}) +frame(i::BWImageKripkeStructure) = FullDimensionalFrame(size(i.image), Point{2, Int}) using SoleLogics: Point2DRelations From 95e7ecbe656f01785f6fb4a0744cffb179eb7855 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:27:54 +0200 Subject: [PATCH 63/90] Minor --- test/kripke-word.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/kripke-word.jl b/test/kripke-word.jl index 64d30e02..2402826e 100644 --- a/test/kripke-word.jl +++ b/test/kripke-word.jl @@ -32,3 +32,7 @@ check(SoleLogics.diamond(SoleLogics.PredecessorRel)(Atom('1')), KripkeWord("0100 check(φ000, KripkeWord("01001")) check(φ000, KripkeWord("0101")) + + + +# check(SoleLogics.diamond(globalrel)(Atom("11") ∧ diamond(IA_A)(Atom("11") ∧ diamond(IA_A)(Atom("11")))), KripkeWord("101100011")) From 4cbfb2dafa0b3b624b03fcd87824f7309c9d8214 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Tue, 22 Oct 2024 00:42:06 +0200 Subject: [PATCH 64/90] Fixing tests broken by 9.11 new RNG algorithm --- test/generation/formula.jl | 42 +++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/test/generation/formula.jl b/test/generation/formula.jl index 976c6dd3..bf8bb6d5 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -1,3 +1,9 @@ +# README: +# Some @test are commented out, in favour of @test_nowarn since: +# "Relying on a specific seed or generated stream of numbers during unit testing is thus +# discouraged - consider testing properties of the methods in question instead." +# (from https://docs.julialang.org/en/v1/stdlib/Random/) + using StatsBase using SoleLogics: parseformula, @__rng_dispatch using Random @@ -65,7 +71,8 @@ my_grammar = SoleLogics.CompleteFlatGrammar(my_alph, my_ops) my_logic = propositionallogic(alphabet=my_alph) @test_nowarn randatom(my_alph) -@test randatom(42, my_alph) == Atom(4) +@test_nowarn randatom(42, my_alph) +# @test randatom(42, my_alph) == Atom(4) non_finite_alph = AlphabetOfAny{Atom{String}}() @test_throws Exception randatom(42, non_finite_alph) @@ -91,7 +98,8 @@ _subalphabets_weights_test_dim = 100 @test_nowarn rand(42, my_alph) == Atom(4) @test_nowarn rand(4) -@test rand(MersenneTwister(42), 2, my_logic) |> syntaxstring == "(1 ∧ 5) ∨ ¬2" +@test_nowarn rand(MersenneTwister(42), 2, my_logic) +# @test rand(MersenneTwister(42), 2, my_logic) |> syntaxstring == "(1 ∧ 5) ∨ ¬2" @test_nowarn rand(4, my_grammar) @test_nowarn rand(Random.MersenneTwister(1), 4, my_grammar) @@ -113,19 +121,29 @@ syntaxstring(mt::MyTruth) = mt.val 42, 4, my_alph, [CONJUNCTION]; truthvalues=Truth[TOP,MyTruthTOP]); @test_nowarn sample(my_alph, Weights([1,2,3,4,5])) -@test sample(2, my_alph, Weights([1,2,3,4,5])) == Atom(3) +@test_nowarn sample(2, my_alph, Weights([1,2,3,4,5])) +# @test sample(2, my_alph, Weights([1,2,3,4,5])) == Atom(3) @test_throws Exception sample(2, non_finite_alph, Weights([1,2,3,4,5])) @test_nowarn StatsBase.sample(2, my_logic, Weights([1,2,3,4,5]), Weights([1,2])) -@test StatsBase.sample( - MersenneTwister(42), - 2, - my_logic, - Weights([1,2,3,4,5]), - Weights([1,2]) - ) |> syntaxstring == "(1 ∧ 1) ∨ (5 → 2)" - -@test StatsBase.sample(MersenneTwister(42), 2, my_grammar) |> syntaxstring == "¬(1 ∧ 1)" +@test_nowarn StatsBase.sample( + MersenneTwister(42), + 2, + my_logic, + Weights([1,2,3,4,5]), + Weights([1,2]) +) + +# @test StatsBase.sample( +# MersenneTwister(42), +# 2, +# my_logic, +# Weights([1,2,3,4,5]), +# Weights([1,2]) +# ) |> syntaxstring == "(1 ∧ 1) ∨ (5 → 2)" + +@test_nowarn StatsBase.sample(MersenneTwister(42), 2, my_grammar) +# @test StatsBase.sample(MersenneTwister(42), 2, my_grammar) |> syntaxstring == "¬(1 ∧ 1)" @test_nowarn StatsBase.sample(2, my_grammar) @test_nowarn randformula(4, my_grammar) From 71e877cb5937061d85ed2003d0ac82081172e974 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:36:43 +0200 Subject: [PATCH 65/90] Fix deps --- Project.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Project.toml b/Project.toml index 0ebca2c7..804601ee 100644 --- a/Project.toml +++ b/Project.toml @@ -5,14 +5,12 @@ version = "0.9.6" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" -BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" Lazy = "50d2b5c4-7a5e-59d5-8109-a42b560f39c0" -PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" @@ -22,14 +20,12 @@ ThreadSafeDicts = "4239201d-c60e-5e0a-9702-85d713665ba7" [compat] AbstractTrees = "0.4" -BenchmarkTools = "1.5.0" DataStructures = "0.18" Dictionaries = "0.3" FunctionWrappers = "1" Graphs = "1.8" IterTools = "1" Lazy = "0.15" -PlutoUI = "0.7.60" PrettyTables = "2.2" Random = "1" Reexport = "1" From 91f568beb71886659b58cffecc3fef3d25b0fb93 Mon Sep 17 00:00:00 2001 From: Michele Ghiotti Date: Wed, 23 Oct 2024 17:09:45 +0200 Subject: [PATCH 66/90] fixed docstring for propositional-logic.jl --- src/deprecate.jl | 20 ++++++ src/types/propositional-logic.jl | 108 ++++++++++--------------------- src/utils/propositional-logic.jl | 107 ++++++++++++++++++++---------- test/propositional-logic.jl | 14 ++++ test/runtests.jl | 2 + 5 files changed, 143 insertions(+), 108 deletions(-) create mode 100644 test/propositional-logic.jl diff --git a/src/deprecate.jl b/src/deprecate.jl index 4c3ab66a..b9da861f 100644 --- a/src/deprecate.jl +++ b/src/deprecate.jl @@ -33,5 +33,25 @@ export global_diamond, global_box const global_diamond = globaldiamond const global_box = globalbox +# propositional-logic.jl +function Base.haskey(i::AbstractAssignment, v)::Bool + Base.haskey(i, Atom(v)) +end + +""" + struct TruthTable{A,T<:Truth} + +Dictionary which associates an [`AbstractAssignment`](@ref)s to the truth value of the +assignment itself on a [`SyntaxStructure`](@ref). + +See also [`AbstractAssignment`](@ref), [`SyntaxStructure`](@ref), [`Truth`](@ref). +""" +struct TruthTable{ + A, + T<:Truth +} <: Formula # TODO is this correct? Remove? + truth::Dict{<:AbstractAssignment,Vector{Pair{SyntaxStructure,T}}} +end + # syntax-utils.jl op(::LeftmostLinearForm{C}) where {C} = C() diff --git a/src/types/propositional-logic.jl b/src/types/propositional-logic.jl index ae1c0c21..31d466dc 100644 --- a/src/types/propositional-logic.jl +++ b/src/types/propositional-logic.jl @@ -1,59 +1,34 @@ -"""Placeholder to indicate a vector of propositional logical operators, i.e. ¬, ∧, ∨, →.""" -const BASE_PROPOSITIONAL_CONNECTIVES = BASE_CONNECTIVES +""" + const BASE_PROPOSITIONAL_CONNECTIVES = BASE_CONNECTIVES -"""Types associated with propositional logical operators.""" -const BasePropositionalConnectives = Union{typeof.(BASE_PROPOSITIONAL_CONNECTIVES)...} +Vector of propositional logical operators, i.e. ¬, ∧, ∨, →. -"""A propositional logic based on the base propositional operators.""" -const BasePropositionalLogic = AbstractLogic{G,A} where { - ALP, - G<:AbstractGrammar{ALP,<:BasePropositionalConnectives}, - A<:AbstractAlgebra - } +See also [`BASE_CONNECTIVES`](@ref). +""" +const BASE_PROPOSITIONAL_CONNECTIVES = BASE_CONNECTIVES """ - propositionallogic(; - alphabet = AlphabetOfAny{String}(), - operators = $(BASE_PROPOSITIONAL_CONNECTIVES), - grammar = CompleteFlatGrammar(AlphabetOfAny{String}(), $(BASE_PROPOSITIONAL_CONNECTIVES)), - algebra = BooleanAlgebra() - ) + const BasePropositionalConnectives = Union{typeof.(BASE_PROPOSITIONAL_CONNECTIVES)...} -Instantiate a [propositional logic](https://simple.wikipedia.org/wiki/Propositional_logic) -given a grammar and an algebra. Alternatively, an alphabet and a set of operators -can be specified instead of the grammar. +Types associated with propositional logical operators. -# Examples -```julia-repl -julia> (¬) isa operatorstype(propositionallogic()) -true +See also [`BASE_PROPOSITIONAL_CONNECTIVES`](@ref), [`BASE_CONNECTIVES`](@ref). +""" +const BasePropositionalConnectives = Union{typeof.(BASE_PROPOSITIONAL_CONNECTIVES)...} -julia> (¬) isa operatorstype(propositionallogic(; operators = [∨])) -false -julia> propositionallogic(; alphabet = ["p", "q"]); +""" + const BasePropositionalLogic = AbstractLogic{G,A} -julia> propositionallogic(; alphabet = ExplicitAlphabet([Atom("p"), Atom("q")])); +A propositional logic based on the base propositional operators. -``` - -See also [`modallogic`](@ref), [`AbstractAlphabet`](@ref), [`AbstractAlgebra`](@ref). +See also [`AbstractLogic`](@ref). """ -function propositionallogic(; - alphabet::Union{Nothing,Vector,AbstractAlphabet} = nothing, - operators::Union{Nothing,Vector{<:Operator}} = nothing, - grammar::Union{Nothing,AbstractGrammar} = nothing, - algebra::Union{Nothing,AbstractAlgebra} = nothing -) - _baselogic( - alphabet = alphabet, - operators = operators, - grammar = grammar, - algebra = algebra; - default_operators = BASE_PROPOSITIONAL_CONNECTIVES, - logictypename = "propositional logic", - ) -end +const BasePropositionalLogic = AbstractLogic{G,A} where { + ALP, + G<:AbstractGrammar{ALP,<:BasePropositionalConnectives}, + A<:AbstractAlgebra + } ############################################################################################ @@ -61,23 +36,22 @@ end abstract type AbstractAssignment <: AbstractInterpretation end Abstract type for assigments, that is, interpretations of propositional logic, -encoding mappings from `Atom`s to `Truth` values. +encoding mappings from [`AbstractAtom`](@ref)s to `Truth` values. # Interface - `Base.haskey(i::AbstractAssignment, ::AbstractAtom)::Bool` - `inlinedisplay(i::AbstractAssignment)::String` - `interpret(a::AbstractAtom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf` -See also [`AbstractInterpretation`](@ref). +See also [`AbstractAssignment`](@ref), [`AbstractAtom`](@ref), [`AbstractInterpretation`](@ref). """ abstract type AbstractAssignment <: AbstractInterpretation end """ Base.haskey(i::AbstractAssignment, ::AbstractAtom)::Bool - Base.haskey(i::AbstractAssignment, v)::Bool -Return whether an assigment has a truth value for a given atom. -If any object is passed, it is wrapped in an atom and then checked. +Return whether an [`AbstractAssignment`](@ref) has a truth value for a given [`Atom`](@ref). +If any object is passed, it is wrapped in an [`Atom`](@ref) and then checked. # Examples @@ -85,23 +59,17 @@ If any object is passed, it is wrapped in an atom and then checked. julia> haskey(TruthDict(["a" => true, "b" => false, "c" => true]), Atom("a")) true -julia> haskey(TruthDict(1:4, false), 3) +julia> haskey(TruthDict(1:4, false), Atom(3)) true - -julia> haskey(TruthDict(1:4, false), 8) -false ``` -See also [`AbstractInterpretation`](@ref), [`AbstractAtom`](@ref), [`TruthDict`](@ref). +See also [`AbstractAssignment`](@ref), [`AbstractInterpretation`](@ref), +[`AbstractAtom`](@ref), [`TruthDict`](@ref), [`Atom`](@ref). """ function Base.haskey(i::AbstractAssignment, ::AbstractAtom)::Bool return error("Please, provide method Base.haskey(::$(typeof(i)), ::AbstractAtom)::Bool.") end -function Base.haskey(i::AbstractAssignment, v)::Bool - Base.haskey(i, Atom(v)) -end - """ inlinedisplay(i::AbstractAssignment) @@ -117,7 +85,7 @@ julia> SoleLogics.inlinedisplay(TruthDict(1:4, false)) "TruthDict([4 => ⊥, 2 => ⊥, 3 => ⊥, 1 => ⊥])" ``` -See also [`TruthDict`](@ref). +See also [`AbstractAssignment`](@ref), [`TruthDict`](@ref). """ function inlinedisplay(i::AbstractAssignment) return error("Please, provide method inlinedisplay(::$(typeof(i)))::String.") @@ -126,19 +94,9 @@ end """ interpret(a::AbstractAtom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf -Return the value corresponding to the atom contained in the assignment. -When interpreting a single atom, if the lookup fails, then return the atom itself. - -# Implementation - -If you pass a DefaultedTruthDict as assignment and the atom is not present in the dictionary, -then the default dictionary value will be returned and not the atom itself. - -Here is an example of this. -```julia-repl -julia> interpret(Atom(5), DefaultedTruthDict(string.(1:4), false)) -⊥ -``` +Return the value corresponding to the [`Atom`](@ref) contained in the +[`AbstractAssignment`](@ref). When interpreting a single atom, if the lookup fails, +then return the atom itself. # Examples @@ -150,7 +108,8 @@ julia>interpret(Atom(3), TruthDict(1:4, false)) ⊥ ``` -See also [`TruthDict`](@ref), [`Atom`](@ref). +See also [`AbstractAssignment`](@ref), [`Atom`](@ref), +[`DefaultedTruthDict`](@ref), [`TruthDict`](@ref). """ function interpret(a::AbstractAtom, i::AbstractAssignment, args...; kwargs...)::SyntaxLeaf if Base.haskey(i, a) @@ -160,6 +119,7 @@ function interpret(a::AbstractAtom, i::AbstractAssignment, args...; kwargs...):: end end +# TODO: do test and look if working # # TODO remove repetition!!! # function interpret( # φ::SyntaxBranch, diff --git a/src/utils/propositional-logic.jl b/src/utils/propositional-logic.jl index f00157f4..ea4d1246 100644 --- a/src/utils/propositional-logic.jl +++ b/src/utils/propositional-logic.jl @@ -27,6 +27,52 @@ function _hpretty_table(io::IO, keys::Any, values::Any) end end +""" + propositionallogic(; + alphabet = AlphabetOfAny{String}(), + operators = $(BASE_PROPOSITIONAL_CONNECTIVES), + grammar = CompleteFlatGrammar(AlphabetOfAny{String}(), $(BASE_PROPOSITIONAL_CONNECTIVES)), + algebra = BooleanAlgebra() + ) + +Instantiate a [propositional logic](https://simple.wikipedia.org/wiki/Propositional_logic) +given a grammar and an algebra. Alternatively, an alphabet and a set of operators +can be specified instead of the grammar. + +# Examples +```julia-repl +julia> (¬) isa operatorstype(propositionallogic()) +true + +julia> (¬) isa operatorstype(propositionallogic(; operators = [∨])) +false + +julia> propositionallogic(; alphabet = ["p", "q"]); + +julia> propositionallogic(; alphabet = ExplicitAlphabet([Atom("p"), Atom("q")])); + +``` + +See also [`modallogic`](@ref), +[`AbstractAlphabet`](@ref), [`AbstractAlgebra`](@ref), [`AlphabetOfAny`](@ref), +[`CompleteFlatGrammar`], [`BooleanAlgebra`](@ref), [`BASE_PROPOSITIONAL_CONNECTIVES`](@ref). +""" +function propositionallogic(; + alphabet::Union{Nothing,Vector,AbstractAlphabet} = nothing, + operators::Union{Nothing,Vector{<:Operator}} = nothing, + grammar::Union{Nothing,AbstractGrammar} = nothing, + algebra::Union{Nothing,AbstractAlgebra} = nothing +) + _baselogic( + alphabet = alphabet, + operators = operators, + grammar = grammar, + algebra = algebra; + default_operators = BASE_PROPOSITIONAL_CONNECTIVES, + logictypename = "propositional logic", + ) +end + ############################################################################################ ####################################### TruthDict ########################################## ############################################################################################ @@ -78,12 +124,12 @@ true If prompted for the value of an unknown atom, this throws an error. If boolean, integer, or float values are specified, they are converted to `Truth` values. - If the structure is initialized as empty, `BooleanTruth` values are assumed. + If the structure is initialized as empty, [`BooleanTruth`](@ref) values are assumed. -See also +See also [`AbstractAssignment`](@ref), +[`AbstractInterpretation`](@ref), [`DefaultedTruthDict`](@ref), -[`AbstractAssignment`](@ref), -[`AbstractInterpretation`](@ref). +[`BooleanTruth`](@ref). """ struct TruthDict{D<:AbstractDict} <: AbstractAssignment @@ -186,6 +232,18 @@ This structure assigns truth values to a set of atoms and, when prompted for the value of an atom that is not in the dictionary, it returns `default_truth`. +# Implementation + +If you use [`interpret`](@ref) function and you pass a [`DefaultedTruthDict`](@ref) as [`AbstractAssignment`](@ref) +and the [`Atom`](@ref) is not present in the dictionary, then the default dictionary value will be +returned and not the [`Atom`](@ref) itself. + +Here is an example of this. +```julia-repl +julia> interpret(Atom(5), DefaultedTruthDict(string.(1:4), false)) +⊥ +``` + # Examples ```julia-repl julia> t1 = DefaultedTruthDict(string.(1:4), false); t1["5"] = false; t1 @@ -205,9 +263,9 @@ false ``` -See also -[`TruthDict`](@ref), -[`AbstractAssignment`](@ref), [`AbstractInterpretation`](@ref). +See also [`AbstractAssignment`](@ref), [`AbstractInterpretation`](@ref), +[`interpret`](@ref), [`Atom`](@ref), +[`TruthDict`](@ref), [`DefaultedTruthDict`](@ref). """ struct DefaultedTruthDict{ D<:AbstractDict, @@ -288,33 +346,14 @@ end Base.values, ) -############################################################################################ -###################################### TruthTable ########################################## -############################################################################################ - -# TODO: it is necessary? -""" - struct TruthTable{A,T<:Truth} - -Dictionary which associates an [`AbstractAssignment`](@ref)s to the truth value of the -assignment itself on a [`SyntaxStructure`](@ref). - -See also [`AbstractAssignment`](@ref), [`SyntaxStructure`](@ref), [`Truth`](@ref). -""" -struct TruthTable{ - A, - T<:Truth -} <: Formula # TODO is this correct? Remove? - truth::Dict{<:AbstractAssignment,Vector{Pair{SyntaxStructure,T}}} -end ############################################################################################ """ - check(φ::Formula,i::Union{AbstractDict,AbstractVector},args...) + 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 +Takes a [`Formula`](@ref) 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`](@ref). @@ -334,7 +373,7 @@ end """ convert(::Type{AbstractInterpretation}, i::AbstractDict) -Convert a dictionary (with keys and values) in a TruthDict. +Convert a dictionary (with keys and values) in a [`TruthDict`](@ref). In this case, a dictionary is interpreted as the map from atoms to `Truth` values. # Examples @@ -399,7 +438,7 @@ See also [`Atom`](@ref). check(a::Atom, i::AbstractDict) = haskey(a,i) ? Base.getindex(i, value(a)) : nothing ############################################################################################# -##################################### AbstractVector######################################### +##################################### AbstractVector ######################################## ############################################################################################# """ @@ -420,7 +459,7 @@ DefaultedTruthDict with default truth `⊥` and values: │ ⊤ │ ⊤ │ ⊤ │ └───────┴───────┴───────┘ -julia> convert(SoleLogics.AbstractInterpretation, ["a","b"]) +julia> convert(AbstractInterpretation, ["a","b"]) DefaultedTruthDict with default truth `⊥` and values: ┌────────┬────────┐ │ b │ a │ @@ -430,7 +469,7 @@ DefaultedTruthDict with default truth `⊥` and values: └────────┴────────┘ ``` -See also [`AbstractInterpretation`](@ref), [`TruthDict`](@ref). +See also [`AbstractInterpretation`](@ref), [`DefaultedTruthDict`](@ref), [`TruthDict`](@ref). """ convert(::Type{AbstractInterpretation}, i::AbstractVector) = DefaultedTruthDict(i, ⊥) #Base.getindex(i::AbstractVector, a::Atom) = (value(a) in i) @@ -439,7 +478,7 @@ convert(::Type{AbstractInterpretation}, i::AbstractVector) = DefaultedTruthDict( """ check(a::Atom, i::AbstractVector) -Returns a truth value indicating whether or not that atom +Returns a truth value indicating whether or not that [`Atom`](@ref) is contained in the passed vector. # Examples diff --git a/test/propositional-logic.jl b/test/propositional-logic.jl new file mode 100644 index 00000000..df423cdd --- /dev/null +++ b/test/propositional-logic.jl @@ -0,0 +1,14 @@ +using Test +using SoleLogics + +@testset "Test Propositional Logic" begin + + # g = SoleLogics.CompleteFlatGrammar(ExplicitAlphabet([Atom("p"), Atom("q")]), SoleLogics.BASE_CONNECTIVES) + # @test propositionallogic(; alphabet = ExplicitAlphabet([Atom("p"), Atom("q")])) == BaseLogic(g, BooleanAlgebra()) + + io = IOBuffer(); + keys = [1,2,3]; + values = [⊤,⊤,⊥]; + _hpretty_table(io,keys,values); + @test String(take!(io)) == "┌───────┬───────┬───────┐\n│ 1 │ 2 │ 3 │\n│ Int64 │ Int64 │ Int64 │\n├───────┼───────┼───────┤\n│ ⊤ │ ⊤ │ ⊥ │\n└───────┴───────┴───────┘\n" + diff --git a/test/runtests.jl b/test/runtests.jl index c61d4f53..d8e361c3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -25,6 +25,8 @@ test_suites = [ ("Operators", ["logics/operators.jl"]), # ("Logics", ["logics/logics.jl"]), + ("Propositional Logic", ["propositional-logic.jl"]), + ("Algebras: worlds", ["algebras/worlds.jl",]), ("Algebras: frames", ["algebras/frames.jl",]), ("Algebras: relations", ["algebras/relations.jl",]), From 5c7fb1173391e2dfa7ead456cce1f461c607b121 Mon Sep 17 00:00:00 2001 From: PasoStudio73 Date: Thu, 24 Oct 2024 11:33:47 +0200 Subject: [PATCH 67/90] syntax-utils --- src/SoleLogics.jl | 2 +- src/syntax-utils.jl | 1091 ------------------------------------- src/utils/syntactical.jl | 610 +++++++++++++++++++++ src/utils/syntax-utils.jl | 483 ++++++++++++++++ 4 files changed, 1094 insertions(+), 1092 deletions(-) delete mode 100644 src/syntax-utils.jl create mode 100644 src/utils/syntax-utils.jl diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 60d376f3..68c6bb67 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -128,7 +128,7 @@ export subformulas, normalize export CNF, DNF, cnf -include("syntax-utils.jl") +include("utils/syntax-utils.jl") ############################################################################################ diff --git a/src/syntax-utils.jl b/src/syntax-utils.jl deleted file mode 100644 index f9e52e52..00000000 --- a/src/syntax-utils.jl +++ /dev/null @@ -1,1091 +0,0 @@ -import Base: show, promote_rule, length, getindex -using SoleBase - -doc_lmlf = """ - struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure - children::Vector{<:SS} - end - -A syntax structure representing the [`foldl`](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) -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-repl -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} children: - p - q - -julia> tree(conj) -SyntaxBranch: p ∧ q - -julia> nconj = NEGATION(conj) -LeftmostLinearForm with connective ¬ and 1 LeftmostConjunctiveForm{Atom{String}} children: - (p) ∧ (q) - -julia> tree(nconj) -SyntaxBranch: ¬(p ∧ q) - -julia> tree(nconj ∧ nconj) -SyntaxBranch: ¬(p ∧ q) ∧ ¬(p ∧ q) -``` -""" - -"""$(doc_lmlf) - -See also [`SyntaxStructure`](@ref), [`SyntaxTree`](@ref), -[`LeftmostConjunctiveForm`](@ref), [`LeftmostDisjunctiveForm`](@ref), -[`Literal`](@ref). -""" -struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure - children::Vector{SS} - - function LeftmostLinearForm{C,SS}( - children::Vector, - ) where {C<:Connective,SS<:SyntaxStructure} - a = arity(C()) # TODO maybe add member connective::C and use that instead of C() - n_children = length(children) - - length(children) > 0 || error("Cannot instantiate LeftmostLinearForm{$(C)} with no children.") - - if a == 1 - n_children == 1 || - error("Mismatching number of children ($n_children) and connective's arity ($a).") - else - h = (n_children-1)/(a-1) - (isinteger(h) && h >= 0) || - # TODO figure out whether the base case n_children = 0 makes sense - error("Mismatching number of children ($n_children) and connective's arity ($a).") - end - - new{C,SS}(children) - end - - function LeftmostLinearForm{C}(children::AbstractVector{SS}) where {C<:Connective,SS<:SyntaxStructure} - # SS = SoleBase._typejoin(typeof.(children)...) - LeftmostLinearForm{C,SS}(children) - end - - # Ugly!! - function LeftmostLinearForm{C}(children::AbstractVector) where {C<:Connective} - SS = SoleBase._typejoin(typeof.(children)...) - LeftmostLinearForm{C,SS}(children) - end - - function LeftmostLinearForm( - C::Type{<:SoleLogics.Connective}, - children::Vector, - ) - LeftmostLinearForm{C}(children) - end - - function LeftmostLinearForm( - op::Connective, - children::Vector, - ) - LeftmostLinearForm(typeof(op), children) - end - - function LeftmostLinearForm( - tree::SyntaxTree, - c::Union{Nothing,<:SoleLogics.Connective} = nothing - ) - # Check c correctness; it should not be nothing (thus, auto inferred) if - # tree root contains something that is not a connective - if (!(token(tree) isa Connective) && !isnothing(c)) - error("Syntax tree cannot be converted to a LeftmostLinearForm. " * - "tree root is $(token(tree)). " * - "Try specifying a connective as a second argument." - ) - end - - if isnothing(c) - c = token(tree) - end - - # Get a vector of `SyntaxTree`s, having `c` as common ancestor, then, - # call LeftmostLinearForm constructor. - _children = SyntaxStructure[] - - function _dig_and_retrieve(tree::SyntaxTree, c::SoleLogics.Connective) - token(tree) != c ? - push!(_children, tree) : # Lexical scope - for chs in children(tree) - _dig_and_retrieve(chs, c) - end - end - _dig_and_retrieve(tree, c) - - LeftmostLinearForm(c, _children) - end - - function LeftmostLinearForm{C}(tree::SyntaxTree) where {C<:Connective} - LeftmostLinearForm(tree, C()) # TODO avoid - end -end - -children(lf::LeftmostLinearForm) = lf.children -connective(::LeftmostLinearForm{C}) where {C} = C() # TODO avoid using C alone, since it may not be a singleton. - -operatortype(::LeftmostLinearForm{C}) where {C} = C -childrentype(::LeftmostLinearForm{C,SS}) where {C,SS} = SS - -nchildren(lf::LeftmostLinearForm) = length(children(lf)) - - -@forward LeftmostLinearForm.children ( - Base.length, - Base.setindex!, - Base.push!, - Base.iterate, Base.IteratorSize, Base.IteratorEltype, - Base.firstindex, Base.lastindex, - Base.keys, Base.values, -) - -# function Base.getindex(lf::LeftmostLinearForm{C,SS}, idxs::AbstractVector) where {C,SS} - # return LeftmostLinearForm{C,SS}(children(lf)[idxs]) -# end -# Base.getindex(lf::LeftmostLinearForm, idx::Integer) = Base.getindex(lf,[idx]) -function Base.getindex(lf::LeftmostLinearForm, idxs::AbstractVector) - return LeftmostLinearForm(children(lf)[idxs]) -end -Base.getindex(lf::LeftmostLinearForm, idx::Integer) = Base.getindex(children(lf),idx) -Base.push!(lf::LeftmostLinearForm, el) = Base.push!(children(lf), el) - -function composeformulas(c::Connective, φs::NTuple{N,LeftmostLinearForm}) where {N} - # @show φs - if all(_c->_c == c, connective.(φs)) # If operator is the same, collapse children TODO and operator is ... associative? - return LeftmostLinearForm(c, collect(Iterators.flatten(children.(φs)))) - # return LeftmostLinearForm(c, reduce(vcat,children.(φs))) - else - return LeftmostLinearForm(c, collect(φs)) - end -end - -# TODO: add parameter remove_redundant_parentheses -# TODO: add parameter parenthesize_atoms -function syntaxstring( - lf::LeftmostLinearForm; - function_notation = false, - kwargs..., -) - if function_notation - syntaxstring(tree(lf); function_notation = function_notation, kwargs...) - else - chs = children(lf) - children_ss = map( - c->syntaxstring(c; kwargs...), - chs - ) - "(" * join(children_ss, ") $(syntaxstring(connective(lf); kwargs...)) (") * ")" - end -end - -function tree(lf::LeftmostLinearForm) - c = connective(lf) - a = arity(c) - chs = children(lf) - - st = begin - if length(chs) == 1 # Only child - if a == 1 - c(tree(first(chs))) - else - tree(first(chs)) - end - else - function _tree(φs::Vector{<:SyntaxTree}) - @assert (length(φs) != 0) "$(φs); $(lf); $(c); $(a)." - return length(φs) == a ? - SyntaxTree(c, φs...) : - SyntaxTree(c, φs[1:(a-1)]..., _tree(φs[a:end])) # Left-most unwinding - end - _tree(tree.(chs)) - end - end - - return st -end - -function Base.show(io::IO, lf::LeftmostLinearForm{C,SS}) where {C,SS} - if lf isa CNF - print(io, "CNF with") - println(io, " $(nconjuncts(lf)) conjuncts:") - L = literaltype(lf) - L <: Literal || println(io, " $(nconjuncts(lf)) and literals of type $(L):") - elseif lf isa DNF - print(io, "DNF with") - println(io, " $(ndisjuncts(lf)) disjuncts:") - L = literaltype(lf) - L <: Literal || println(io, " $(ndisjuncts(lf)) and literals of type $(L):") - else - if lf isa LeftmostConjunctiveForm - print(io, "LeftmostConjunctiveForm with") - elseif lf isa LeftmostDisjunctiveForm - print(io, "LeftmostDisjunctiveForm with") - else - print(io, "LeftmostLinearForm with connective $(syntaxstring(connective(lf))) and") - end - println(io, " $(nchildren(lf)) $((SS == SyntaxStructure ? "" : "$(SS) "))children:") - end - # println(io, "\t$(join(syntaxstring.(children(lf)), " $(syntaxstring(connective(lf))) \n\t"))") - println(io, "\t$(join(syntaxstring.(children(lf)), "\n\t"))") -end - -# TODO fix -Base.promote_rule(::Type{<:LeftmostLinearForm}, ::Type{<:LeftmostLinearForm}) = SyntaxTree -Base.promote_rule(::Type{SS}, ::Type{LF}) where {SS<:SyntaxStructure,LF<:LeftmostLinearForm} = SyntaxTree -Base.promote_rule(::Type{LF}, ::Type{SS}) where {LF<:LeftmostLinearForm,SS<:SyntaxStructure} = SyntaxTree - -function Base.in(tok::SyntaxToken, φ::LeftmostLinearForm)::Bool - return (tok isa Connective && connective(φ) == tok) || - any(c->Base.in(tok, c), children(φ)) -end - -function Base.in(tok::SyntaxLeaf, φ::LeftmostLinearForm{C,<:SyntaxLeaf})::Bool where {C<:Connective} - return Base.in(tok, children(φ)) -end - - -atoms(φ::LeftmostLinearForm) = Iterators.flatten(Iterators.map(atoms, children(φ))) -leaves(φ::LeftmostLinearForm) = Iterators.flatten(Iterators.map(leaves, children(φ))) - -natoms(φ::LeftmostLinearForm) = sum(natoms, children(φ)) -nleaves(φ::LeftmostLinearForm) = sum(nleaves, children(φ)) - -# function tokens(φ::LeftmostLinearForm) -# # return TODO -# end - -function atoms(φ::LeftmostLinearForm{C,<:Atom})::Vector{Atom} where {C<:Connective} - return children(φ) -end - -# function connectives(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} -# # return TODO -# end - -# function operators(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} -# # return TODO -# end - -function leaves(φ::LeftmostLinearForm{C,<:SyntaxLeaf})::SyntaxLeaf where {C<:Connective} - return children(φ) -end - -# function ntokens(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} -# # return TODO -# end - -function natoms(φ::LeftmostLinearForm{C,<:Atom})::Integer where {C<:Connective} - return nchildren(φ) -end - -# function nconnectives(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} -# # return TODO -# end - -# function noperators(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} -# # return TODO -# end - -function nleaves(φ::LeftmostLinearForm{C,<:SyntaxLeaf})::Integer where {C<:Connective} - return nchildren(φ) -end - -Base.promote_rule(::Type{LF}, ::Type{SS}) where {LF<:LeftmostLinearForm,SS<:SyntaxTree} = SyntaxTree -Base.promote_rule(::Type{SS}, ::Type{LF}) where {LF<:LeftmostLinearForm,SS<:SyntaxTree} = SyntaxTree - -############################################################################################ - -# TODO actually: -# const CNF{SS<:SyntaxStructure} = Union{LeftmostLinearForm{typeof(∧),LeftmostLinearForm{typeof(∨),SS}},LeftmostLinearForm{typeof(∨),SS}} -# const DNF{SS<:SyntaxStructure} = Union{LeftmostLinearForm{typeof(∨),LeftmostLinearForm{typeof(∧),SS}},LeftmostLinearForm{typeof(∧),SS}} - -""" - LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} - -Specific instantiation of a [`LeftmostLinearForm`](@ref), where [`Connective`](@ref)s are -all [`CONJUNCTION`](@ref)s. - -See also [`SyntaxStructure`](@ref), [`Connective`](@ref), [`LeftmostLinearForm`](@ref), -[`CONJUNCTION`](@ref). -""" -const LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} - -function check( - φ::LeftmostConjunctiveForm, - args...; - kwargs... -) - return all(ch -> check(ch, args...; kwargs...), children(φ)) -end - -function check( - φ::LeftmostConjunctiveForm, - i::AbstractInterpretation, - args...; - kwargs... -) - return all(ch -> check(ch, i, args...; kwargs...), children(φ)) -end - -""" - LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} - -Specific instantiation of a [`LeftmostLinearForm`](@ref), where [`Connective`](@ref)s are -all [`DISJUNCTION`](@ref)s. - -See also [`SyntaxStructure`](@ref), [`Connective`](@ref), -[`LeftmostLinearForm`](@ref), [`DISJUNCTION`](@ref). -""" -const LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} - -function check( - φ::LeftmostDisjunctiveForm, - args...; - kwargs... -) - return any(ch -> check(ch, args...; kwargs...), children(φ)) -end - -function check( - φ::LeftmostDisjunctiveForm, - i::AbstractInterpretation, - args...; - kwargs... -) - return any(ch -> check(ch, i, args...; kwargs...), children(φ)) -end - -""" - CNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} - -Conjunctive Normal Form of an [`SyntaxStructure`](@ref). - -See also [`SyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), -[`LeftmostDisjunctiveForm`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref). -""" -const CNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} - -function check( - φ::CNF, - args...; - kwargs... -) - return all(ch -> any(grandch -> check(grandch, args...; kwargs...), children(ch)), children(φ)) -end - -function check( - φ::CNF, - i::AbstractInterpretation, - args...; - kwargs... -) - return all(ch -> any(grandch -> check(grandch, i, args...; kwargs...), children(ch)), children(φ)) -end - -""" - DNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostConjunctiveForm{SS}} - -Disjunctive Normal Form of an [`SyntaxStructure`](@ref). - -See also [`SyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), -[`LeftmostDisjunctiveForm`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref). -""" -const DNF{SS<:SyntaxStructure} = LeftmostDisjunctiveForm{LeftmostConjunctiveForm{SS}} - -function check( - φ::DNF, - args...; - kwargs... -) - return any(ch -> all(grandch -> check(grandch, args...; kwargs...), children(ch)), children(φ)) -end - -function check( - φ::DNF, - i::AbstractInterpretation, - args...; - kwargs... -) - return any(ch -> all(grandch -> check(grandch, i, args...; kwargs...), children(ch)), children(φ)) -end - -# Helpers -function CNF(conjuncts::AbstractVector{<:LeftmostDisjunctiveForm}) - SS = Union{childrentype.(conjuncts)...} - return CNF{SS}(conjuncts) -end -function DNF(disjuncts::AbstractVector{<:LeftmostConjunctiveForm}) - SS = Union{childrentype.(disjuncts)...} - return DNF{SS}(disjuncts) -end -CNF(conjuncts::NTuple{N,<:LeftmostDisjunctiveForm}) where {N} = CNF(collect(conjuncts)) -DNF(disjuncts::NTuple{N,<:LeftmostConjunctiveForm}) where {N} = DNF(collect(disjuncts)) -CNF(conjuncts::Vararg{LeftmostDisjunctiveForm}) = CNF(collect(conjuncts)) -DNF(disjuncts::Vararg{LeftmostConjunctiveForm}) = DNF(collect(disjuncts)) -CNF(conjunct::LeftmostDisjunctiveForm) = CNF([conjunct]) -DNF(disjunct::LeftmostConjunctiveForm) = DNF([disjunct]) - -function CNF(φ::SyntaxLeaf) - return LeftmostConjunctiveForm([LeftmostDisjunctiveForm([φ])]) -end - -function DNF(φ::SyntaxLeaf) - return LeftmostDisjunctiveForm([LeftmostConjunctiveForm([φ])]) -end - -function CNF(φ::SyntaxBranch) - return erorr("TODO") -end - -function DNF(φ::SyntaxBranch) - return erorr("TODO") -end - - -literaltype(::CNF{SS}) where {SS<:SyntaxStructure} = SS -literaltype(::DNF{SS}) where {SS<:SyntaxStructure} = SS - -# # TODO maybe not needed? -# Base.promote_rule(::Type{<:LeftmostConjunctiveForm}, ::Type{<:LeftmostConjunctiveForm}) = LeftmostConjunctiveForm -# Base.promote_rule(::Type{<:LeftmostDisjunctiveForm}, ::Type{<:LeftmostDisjunctiveForm}) = LeftmostDisjunctiveForm -# Base.promote_rule(::Type{<:LeftmostConjunctiveForm}, ::Type{<:LeftmostDisjunctiveForm}) = SyntaxTree - -conjuncts(φ::LeftmostConjunctiveForm) = children(φ) -nconjuncts(φ::LeftmostConjunctiveForm) = nchildren(φ) -pushconjunct!(φ::LeftmostLinearForm, el) = Base.push!(children(φ), el) - -disjuncts(φ::LeftmostDisjunctiveForm) = children(φ) -ndisjuncts(φ::LeftmostDisjunctiveForm) = nchildren(φ) -pushdisjunct(φ::LeftmostDisjunctiveForm, el) = Base.push!(children(φ), el) - -# conjuncts(φ::DNF) = map(d->conjuncts(d), disjuncts(φ)) -# nconjuncts(φ::DNF) = map(d->nconjuncts(d), disjuncts(φ)) -# disjuncts(φ::CNF) = map(d->disjuncts(d), conjuncts(φ)) -# ndisjuncts(φ::CNF) = map(d->ndisjuncts(d), conjuncts(φ)) - - -############################################################################################ - -""" - struct Literal{T<:SyntaxLeaf} <: SyntaxStructure - ispos::Bool - prop::T - end - -An atom, or its negation. - -See also [`CNF`](@ref), [`DNF`](@ref), [`SyntaxStructure`](@ref). -""" -struct Literal{T<:SyntaxLeaf} <: SyntaxStructure - ispos::Bool - prop::T - - function Literal{T}( - ispos::Bool, - prop::T, - ) where {T<:SyntaxLeaf} - new{T}(ispos, prop) - end - - function Literal( - ispos::Bool, - prop::T, - ) where {T<:SyntaxLeaf} - Literal{T}(ispos, prop) - end - - function Literal(φ::SyntaxLeaf, flag = true) - return Literal(flag, φ) - end - function Literal(φ::SyntaxBranch, flag = true) - ch = first(children(φ)) - @assert (token(φ) == ¬) "Cannot " * - "construct Literal with formula of type $(typeof(ch))): $(syntaxstring(ch))." - return Literal(ch, !flag) - end -end - -ispos(l::Literal) = l.ispos -prop(l::Literal) = l.prop - -atomstype(::Literal{T}) where {T} = T - -tree(l::Literal) = ispos(l) ? l.prop : ¬(l.prop) - -hasdual(l::Literal) = true -dual(l::Literal) = Literal(!ispos(l), prop(l)) - -function Base.show(io::IO, l::Literal) - println(io, - "Literal{$(atomstype(l))}: " * (ispos(l) ? "" : "¬") * syntaxstring(prop(l)) - ) -end - -############################################################################################ -# CNF conversion -############################################################################################ - -""" - cnf(φ::Formula, literaltype = Literal; kwargs...) - - TODO docstring. Converts to cnf form ([`CNF`](@ref)). - `CNF{literaltype}` - Additional `kwargs` are passed to [`normalize`](@ref) -""" -function cnf(φ::Formula, literaltype = Literal; kwargs...) - return _cnf(normalize(φ; profile = :nnf, kwargs...), literaltype) -end - -function cnf(φ::CNF{T}, literaltype = Literal; kwargs...) where {T<:SyntaxStructure} - if T == literaltype - return φ - else - return cnf(tree(φ), literaltype; kwargs...) - end -end - -function cnf(φ::DNF, args...; kwargs...) - return cnf(tree(φ), args...; kwargs...) -end - - -function _cnf(φ::Formula, literaltype = Literal) - return error("Cannot convert to CNF formula of type $(typeof(φ)): $(syntaxstring(φ))") -end - -function _cnf(φ::SyntaxLeaf, literaltype = Literal) - φ = φ isa literaltype ? φ : literaltype(φ) - return LeftmostConjunctiveForm([LeftmostDisjunctiveForm{literaltype}([φ])]) -end - -function _cnf(φ::SyntaxBranch, literaltype = Literal) - if token(φ) == ∧ - return _cnf(first(children(φ)), literaltype) ∧ _cnf(last(children(φ)), literaltype) - elseif token(φ) == ∨ - conjs = vec([begin - # @show typeof(c1), typeof(c2) - # @show typeof(c1 ∨ c2) - # LeftmostDisjunctiveForm{literaltype}(c1 ∨ c2) - c1 ∨ c2 - end for (c1,c2) in Iterators.product(conjuncts(_cnf(first(children(φ)), literaltype)),conjuncts(_cnf(last(children(φ)), literaltype)))]) - # @show typeof.(conjs) - # conjs = Vector{LeftmostDisjunctiveForm{literaltype}}(conjs) - return LeftmostConjunctiveForm(conjs) - elseif token(φ) == ¬ - φ = φ isa literaltype ? φ : literaltype(φ) - return LeftmostConjunctiveForm([LeftmostDisjunctiveForm{literaltype}([φ])]) - else - return error("Unexpected token $(token)!") - end -end - - - -############################################################################################ - -subtrees(tree::SyntaxTree) = [Iterators.flatten(_subtrees.(children(tree)))...] -_subtrees(tree::SyntaxTree) = [tree, Iterators.flatten(_subtrees.(children(tree)))...] - -# TODO: explain better -# TODO: is this available in AbstractTrees? -""" - treewalk( - 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. -""" -function treewalk( - st::SyntaxTree, - args...; - rng::AbstractRNG = Random.GLOBAL_RNG, - criterion::Function = c->true, - returnnode::Bool = false, - transformnode::Union{Nothing,Function} = nothing, -) - chs = children(st) - - return length(chs) == 0 ? begin - isnothing(transformnode) ? st : transformnode(st, args...) - end : begin - c_chsub = map(c->length(filter(criterion, tokens(c))), chs) - c_father = criterion(token(st)) ? 1 : 0 - - @assert [c_chsub..., c_father] isa AbstractVector{<:Integer} "Not all values " * - "computed as criterion are integers, double check the passed function used for " * - "calculating these; values: $([c_chsub..., c_father])" - - w_nodes = [c_chsub..., c_father]/sum([c_chsub..., c_father]) - idx_randnode = sample(rng, 1:length(w_nodes), Weights(w_nodes)) - - if idx_randnode == length(w_nodes) - isnothing(transformnode) ? st : transformnode(st, args...) - else - returnnode ? - treewalk( - chs[idx_randnode], - args...; - rng=rng, - criterion=criterion, - returnnode=returnnode, - transformnode=transformnode, - ) : - SyntaxTree( - token(st), - ( - chs[1:(idx_randnode-1)]..., - treewalk( - chs[idx_randnode], - args...; - rng=rng, - criterion=criterion, - returnnode=returnnode, - transformnode=transformnode, - ), - chs[(idx_randnode+1):end]... - ) - ) - end - end -end - - - -""" - subformulas(f::Formula; sorted=true) - -Return all sub-formulas (sorted by size when `sorted=true`) -of a given formula. - -# Examples -```julia-repl -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`](@ref)), [`Formula`](@ref). -""" -subformulas(f::Formula, args...; kwargs...) = subformulas(tree(f), args...; kwargs...) -function subformulas(t::SyntaxTree; sorted=true) - # function _subformulas(_t::SyntaxTree) - # SyntaxTree[ - # (map(SyntaxTree, Iterators.flatten(subformulas.(children(_t)))))..., - # _t - # ] - # end - function _subformulas(_t::SyntaxTree) - SyntaxTree[ - (Iterators.flatten(subformulas.(children(_t))))..., - _t - ] - end - ts = _subformulas(t) - if sorted - sort!(ts, by = t -> SoleLogics.height(t)) - end - ts -end - -# TODO move to utils and rename "normalize" -> "transform"/"reshape"/"simplify" -# TODO \to diventano \lor -# TODO explain profile's and other parameters -""" - normalize( - 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, - ) - -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](https://en.wikipedia.org/wiki/Model_checking). -The current implementation assumes the underlying algebra is Boolean! - -# Arguments -- `f::Formula`: when set to `true`, - 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 to `true`, - attempts at reducing the number of negations by appling - some transformation rules - (e.g., [De Morgan's laws](https://en.wikipedia.org/wiki/De_Morgan%27s_laws)). - Note: this assumes an underlying Boolean algebra. -- `allow_atom_flipping::Bool`: when set to `true`, - together with `reduce_negations=true`, this may cause the negation of an atom - to be replaced with the its [`dual`](@ref) atom. - -# Examples -```julia-repl -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`](@ref)), [`Formula`](@ref). -""" -normalize(f::Formula, args...; kwargs...) = normalize(tree(f), args...; kwargs...) -function normalize( - t::LLF; - kwargs... -) where {LLF<:Union{LeftmostConjunctiveForm,LeftmostDisjunctiveForm}} - ch = children(t) - unique!(ch) - ch = normalize.(ch; kwargs...) - unique!(ch) - if connective(t) == (∧) - filter!(c->c != ⊤, ch) - elseif connective(t) == (∨) - filter!(c->c != ⊥, ch) - end - return LeftmostLinearForm(connective(t), ch) -end - -function normalize( - t::SyntaxTree; - profile = :readability, - remove_boxes = nothing, - reduce_negations = nothing, - simplify_constants = nothing, - allow_atom_flipping = nothing, - prefer_implications = nothing, - remove_implications = nothing, - forced_negation_removal = nothing, - remove_identities = nothing, - unify_toones = nothing, - rotate_commutatives = nothing, -) - if profile == :readability - if isnothing(remove_boxes) remove_boxes = false end - if isnothing(reduce_negations) reduce_negations = true end - if isnothing(simplify_constants) simplify_constants = true end - if isnothing(allow_atom_flipping) allow_atom_flipping = false end - if isnothing(prefer_implications) prefer_implications = false end - if isnothing(remove_implications) remove_implications = false end - if isnothing(remove_identities) remove_identities = true end - if isnothing(unify_toones) unify_toones = true end - if isnothing(rotate_commutatives) rotate_commutatives = true end - # TODO leave \to's instead of replacing them with \lor's... - elseif profile == :nnf - if isnothing(remove_boxes) remove_boxes = false end - if isnothing(reduce_negations) reduce_negations = true end - if isnothing(simplify_constants) simplify_constants = false end - if isnothing(allow_atom_flipping) allow_atom_flipping = false end - if isnothing(prefer_implications) prefer_implications = false end - if isnothing(remove_implications) remove_implications = true end - if isnothing(remove_identities) remove_identities = false end - if isnothing(unify_toones) unify_toones = false end - if isnothing(rotate_commutatives) rotate_commutatives = true end - # TODO leave \to's instead of replacing them with \lor's... - elseif profile == :modelchecking - if isnothing(remove_boxes) remove_boxes = true end - if isnothing(reduce_negations) reduce_negations = true end - if isnothing(simplify_constants) simplify_constants = true end - if isnothing(allow_atom_flipping) allow_atom_flipping = false end - if isnothing(prefer_implications) prefer_implications = false end - if isnothing(remove_implications) remove_implications = false end - if isnothing(remove_identities) remove_identities = true end - if isnothing(unify_toones) unify_toones = true end - if isnothing(rotate_commutatives) rotate_commutatives = true end - else - error("Unknown normalization profile: $(repr(profile))") - end - - if isnothing(forced_negation_removal) - if isnothing(allow_atom_flipping) - forced_negation_removal = true - else - forced_negation_removal = false - end - end - - # TODO we're currently assuming Boolean algebra!!! Very wrong assumption... - - _normalize = t->normalize(t; - profile = profile, - remove_boxes = remove_boxes, - reduce_negations = reduce_negations, - simplify_constants = simplify_constants, - allow_atom_flipping = allow_atom_flipping, - prefer_implications = prefer_implications, - remove_implications = remove_implications, - forced_negation_removal = forced_negation_removal, - remove_identities = remove_identities, - unify_toones = unify_toones, - rotate_commutatives = rotate_commutatives - ) - - newt = t - - # Remove modal connectives based on the identity relation - newt = begin - tok, chs = token(newt), children(newt) - if remove_identities && tok isa AbstractRelationalConnective && - relation(tok) == identityrel && arity(tok) == 1 - first(chs) - elseif unify_toones && tok isa AbstractRelationalConnective && - istoone(relation(tok)) && arity(tok) == 1 - diamond(relation(tok))(first(chs)) - else - newt - end - end - - # Simplify - newt = begin - tok, chs = token(newt), children(newt) - if (tok == ¬) && arity(tok) == 1 - child = chs[1] - chtok, grandchildren = token(child), children(child) - if reduce_negations && (chtok == ¬) && arity(chtok) == 1 - _normalize(grandchildren[1]) - elseif reduce_negations && (chtok == ∨) && arity(chtok) == 2 - ∧(_normalize(¬(grandchildren[1])), _normalize(¬(grandchildren[2]))) - # TODO use implication, maybe it's more interpretable? - elseif reduce_negations && (chtok == ∧) && arity(chtok) == 2 - # if prefer_implications - # →(_normalize(grandchildren[1]), _normalize(¬(grandchildren[2]))) - # else - ∨(_normalize(¬(grandchildren[1])), _normalize(¬(grandchildren[2]))) - # end - elseif reduce_negations && (chtok == →) && arity(chtok) == 2 - # _normalize(∨(¬(grandchildren[1]), grandchildren[2])) - ∧(_normalize(grandchildren[1]), _normalize(¬(grandchildren[2]))) - elseif reduce_negations && chtok isa Atom - if allow_atom_flipping && hasdual(chtok) - dual(chtok) - else - ¬(_normalize(child)) - end - # elseif reduce_negations && chtok isa SoleLogics.AbstractRelationalConnective && arity(chtok) == 1 - # dual_op = dual(chtok) - # if remove_boxes && dual_op isa SoleLogics.BoxRelationalConnective - # ¬(_normalize(child)) - # else - # dual_op(_normalize(¬(grandchildren[1]))) - # end - elseif reduce_negations && ismodal(chtok) && arity(chtok) == 1 - dual_op = dual(chtok) - # if remove_boxes && SoleLogics.isbox(dual_op) - # ¬(_normalize(child)) - # else - dual_op(_normalize(¬(grandchildren[1]))) - # end - elseif (reduce_negations || simplify_constants) && chtok == ⊤ && arity(chtok) == 1 - ⊥ - elseif (reduce_negations || simplify_constants) && chtok == ⊥ && arity(chtok) == 1 - ⊤ - elseif !forced_negation_removal - SyntaxTree(tok, _normalize.(chs)) - else - error("Unknown chtok when removing negations: $(chtok) (type = $(typeof(chtok)))") - end - else - SyntaxTree(tok, _normalize.(chs)) - end - end - - # DEBUG: old_newt = newt - # Simplify constants - newt = begin - tok, chs = token(newt), children(newt) - if simplify_constants && tok isa Connective - if (tok == ∨) && arity(tok) == 2 # TODO maybe use istop, isbot? - if token(chs[1]) == ⊥ chs[2] # ⊥ ∨ φ ≡ φ - elseif token(chs[2]) == ⊥ chs[1] # φ ∨ ⊥ ≡ φ - elseif token(chs[1]) == ⊤ ⊤ # ⊤ ∨ φ ≡ ⊤ - elseif token(chs[2]) == ⊤ ⊤ # φ ∨ ⊤ ≡ ⊤ - else newt - end - elseif (tok == ∧) && arity(tok) == 2 - if token(chs[1]) == ⊥ ⊥ # ⊥ ∧ φ ≡ ⊥ - elseif token(chs[2]) == ⊥ ⊥ # φ ∧ ⊥ ≡ ⊥ - elseif token(chs[1]) == ⊤ chs[2] # ⊤ ∧ φ ≡ φ - elseif token(chs[2]) == ⊤ chs[1] # φ ∧ ⊤ ≡ φ - else newt - end - elseif (tok == →) && arity(tok) == 2 - if token(chs[1]) == ⊥ ⊤ # ⊥ → φ ≡ ⊤ - elseif token(chs[2]) == ⊥ _normalize(¬chs[1]) # φ → ⊥ ≡ ¬φ - elseif token(chs[1]) == ⊤ chs[2] # ⊤ → φ ≡ φ - elseif token(chs[2]) == ⊤ ⊤ # φ → ⊤ ≡ ⊤ - else newt - end - elseif (tok == ¬) && arity(tok) == 1 - if token(chs[1]) == ⊤ ⊥ - elseif token(chs[1]) == ⊥ ⊤ - else newt - end - elseif SoleLogics.isbox(tok) && arity(tok) == 1 - if token(chs[1]) == ⊤ ⊤ - else newt - end - elseif SoleLogics.isdiamond(tok) && arity(tok) == 1 - if token(chs[1]) == ⊥ ⊥ - else newt - end - else - newt - end - else - newt - end - end - - # Implication <-> disjunction - newt = begin - tok, chs = token(newt), children(newt) - if prefer_implications && (tok == ∨) - if token(chs[1]) == ¬ - →(_normalize(first(children(chs[1]))), _normalize((chs[2]))) - else - →(_normalize(¬chs[1]), _normalize((chs[2]))) - end - elseif remove_implications && (tok == →) - if token(chs[1]) == ¬ - ∨(_normalize(first(children(chs[1]))), _normalize((chs[2]))) - else - ∨(_normalize(¬chs[1]), _normalize((chs[2]))) - end - else - newt - end - end - # DEBUG: - # "$(syntaxstring(old_newt)) => $(syntaxstring(newt))" |> println - - newt = begin - tok, chs = token(newt), children(newt) - if remove_boxes && tok isa Connective && SoleLogics.isbox(tok) && arity(tok) == 1 - # remove_boxes -> substitute every [X]φ with ¬⟨X⟩¬φ - child = chs[1] - dual_op = dual(tok) - ¬(dual_op(_normalize(¬child))) - # TODO remove - # if relation(tok) == globalrel - # # Special case: [G]φ -> ⟨G⟩φ - # dual_op(_normalize(child)) - # else - # ¬(dual_op(_normalize(¬child))) - # end - else - newt - end - end - - function _isless(st1::SyntaxTree, st2::SyntaxTree) - isless(Base.hash(st1), Base.hash(st2)) - end - - # Rotate commutatives - if rotate_commutatives - newt = begin - tok, chs = token(newt), children(newt) - if tok isa Connective && iscommutative(tok) && arity(tok) > 1 - chs = children(LeftmostLinearForm(newt, tok)) - chs = Vector(sort(collect(_normalize.(chs)), lt=_isless)) - if tok in [∧,∨] # TODO create trait for this behavior: p ∧ p ∧ p ∧ q -> p ∧ q - chs = unique(chs) - end - tree(LeftmostLinearForm(tok, chs)) - else - SyntaxTree(tok, chs) - end - end - end - - return newt -end - -""" - isgrounded(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-repl -julia> f = parseformula("⟨G⟩p → [G]q"); - -julia> syntaxstring(f) -"(⟨G⟩p) → ([G]q)" - -julia> SoleLogics.isgrounded(f) -true -``` - -See also -[`isgrounding`](@ref)), [`SyntaxTree`](@ref)), [`Formula`](@ref). -""" -isgrounded(f::Formula) = isgrounded(tree(f)) -function isgrounded(t::SyntaxTree)::Bool - # (println(token(t)); println(children(t)); true) && - return (token(t) isa SoleLogics.AbstractRelationalConnective && isgrounding(relation(token(t)))) || - # (token(t) in [◊,□]) || - (token(t) isa Connective && all(c->isgrounded(c), children(t))) -end diff --git a/src/utils/syntactical.jl b/src/utils/syntactical.jl index 6c02bee7..9d1ad5a3 100644 --- a/src/utils/syntactical.jl +++ b/src/utils/syntactical.jl @@ -875,3 +875,613 @@ function _baselogic(; return BaseLogic(grammar, algebra) end + +############################################################################################ +# import Base: show, promote_rule, length, getindex +# using SoleBase + +doc_lmlf = """ + struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure + children::Vector{<:SS} + end + +A syntax structure representing the [`foldl`](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) +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-repl +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} children: + p + q + +julia> tree(conj) +SyntaxBranch: p ∧ q + +julia> nconj = NEGATION(conj) +LeftmostLinearForm with connective ¬ and 1 LeftmostConjunctiveForm{Atom{String}} children: + (p) ∧ (q) + +julia> tree(nconj) +SyntaxBranch: ¬(p ∧ q) + +julia> tree(nconj ∧ nconj) +SyntaxBranch: ¬(p ∧ q) ∧ ¬(p ∧ q) +``` +""" + +"""$(doc_lmlf) + +See also [`SyntaxStructure`](@ref), [`SyntaxTree`](@ref), +[`LeftmostConjunctiveForm`](@ref), [`LeftmostDisjunctiveForm`](@ref), +[`Literal`](@ref). +""" +struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure + children::Vector{SS} + + function LeftmostLinearForm{C,SS}( + children::Vector, + ) where {C<:Connective,SS<:SyntaxStructure} + a = arity(C()) # TODO maybe add member connective::C and use that instead of C() + n_children = length(children) + + length(children) > 0 || error("Cannot instantiate LeftmostLinearForm{$(C)} with no children.") + + if a == 1 + n_children == 1 || + error("Mismatching number of children ($n_children) and connective's arity ($a).") + else + h = (n_children-1)/(a-1) + (isinteger(h) && h >= 0) || + # TODO figure out whether the base case n_children = 0 makes sense + error("Mismatching number of children ($n_children) and connective's arity ($a).") + end + + new{C,SS}(children) + end + + function LeftmostLinearForm{C}(children::AbstractVector{SS}) where {C<:Connective,SS<:SyntaxStructure} + # SS = SoleBase._typejoin(typeof.(children)...) + LeftmostLinearForm{C,SS}(children) + end + + # Ugly!! + function LeftmostLinearForm{C}(children::AbstractVector) where {C<:Connective} + SS = SoleBase._typejoin(typeof.(children)...) + LeftmostLinearForm{C,SS}(children) + end + + function LeftmostLinearForm( + C::Type{<:SoleLogics.Connective}, + children::Vector, + ) + LeftmostLinearForm{C}(children) + end + + function LeftmostLinearForm( + op::Connective, + children::Vector, + ) + LeftmostLinearForm(typeof(op), children) + end + + function LeftmostLinearForm( + tree::SyntaxTree, + c::Union{Nothing,<:SoleLogics.Connective} = nothing + ) + # Check c correctness; it should not be nothing (thus, auto inferred) if + # tree root contains something that is not a connective + if (!(token(tree) isa Connective) && !isnothing(c)) + error("Syntax tree cannot be converted to a LeftmostLinearForm. " * + "tree root is $(token(tree)). " * + "Try specifying a connective as a second argument." + ) + end + + if isnothing(c) + c = token(tree) + end + + # Get a vector of `SyntaxTree`s, having `c` as common ancestor, then, + # call LeftmostLinearForm constructor. + _children = SyntaxStructure[] + + function _dig_and_retrieve(tree::SyntaxTree, c::SoleLogics.Connective) + token(tree) != c ? + push!(_children, tree) : # Lexical scope + for chs in children(tree) + _dig_and_retrieve(chs, c) + end + end + _dig_and_retrieve(tree, c) + + LeftmostLinearForm(c, _children) + end + + function LeftmostLinearForm{C}(tree::SyntaxTree) where {C<:Connective} + LeftmostLinearForm(tree, C()) # TODO avoid + end +end + +children(lf::LeftmostLinearForm) = lf.children +connective(::LeftmostLinearForm{C}) where {C} = C() # TODO avoid using C alone, since it may not be a singleton. + +operatortype(::LeftmostLinearForm{C}) where {C} = C +childrentype(::LeftmostLinearForm{C,SS}) where {C,SS} = SS + +nchildren(lf::LeftmostLinearForm) = length(children(lf)) + + +@forward LeftmostLinearForm.children ( + Base.length, + Base.setindex!, + Base.push!, + Base.iterate, Base.IteratorSize, Base.IteratorEltype, + Base.firstindex, Base.lastindex, + Base.keys, Base.values, +) + +# function Base.getindex(lf::LeftmostLinearForm{C,SS}, idxs::AbstractVector) where {C,SS} + # return LeftmostLinearForm{C,SS}(children(lf)[idxs]) +# end +# Base.getindex(lf::LeftmostLinearForm, idx::Integer) = Base.getindex(lf,[idx]) +function Base.getindex(lf::LeftmostLinearForm, idxs::AbstractVector) + return LeftmostLinearForm(children(lf)[idxs]) +end +Base.getindex(lf::LeftmostLinearForm, idx::Integer) = Base.getindex(children(lf),idx) +Base.push!(lf::LeftmostLinearForm, el) = Base.push!(children(lf), el) + +function composeformulas(c::Connective, φs::NTuple{N,LeftmostLinearForm}) where {N} + # @show φs + if all(_c->_c == c, connective.(φs)) # If operator is the same, collapse children TODO and operator is ... associative? + return LeftmostLinearForm(c, collect(Iterators.flatten(children.(φs)))) + # return LeftmostLinearForm(c, reduce(vcat,children.(φs))) + else + return LeftmostLinearForm(c, collect(φs)) + end +end + +# TODO: add parameter remove_redundant_parentheses +# TODO: add parameter parenthesize_atoms +function syntaxstring( + lf::LeftmostLinearForm; + function_notation = false, + kwargs..., +) + if function_notation + syntaxstring(tree(lf); function_notation = function_notation, kwargs...) + else + chs = children(lf) + children_ss = map( + c->syntaxstring(c; kwargs...), + chs + ) + "(" * join(children_ss, ") $(syntaxstring(connective(lf); kwargs...)) (") * ")" + end +end + +function tree(lf::LeftmostLinearForm) + c = connective(lf) + a = arity(c) + chs = children(lf) + + st = begin + if length(chs) == 1 # Only child + if a == 1 + c(tree(first(chs))) + else + tree(first(chs)) + end + else + function _tree(φs::Vector{<:SyntaxTree}) + @assert (length(φs) != 0) "$(φs); $(lf); $(c); $(a)." + return length(φs) == a ? + SyntaxTree(c, φs...) : + SyntaxTree(c, φs[1:(a-1)]..., _tree(φs[a:end])) # Left-most unwinding + end + _tree(tree.(chs)) + end + end + + return st +end + +function Base.show(io::IO, lf::LeftmostLinearForm{C,SS}) where {C,SS} + if lf isa CNF + print(io, "CNF with") + println(io, " $(nconjuncts(lf)) conjuncts:") + L = literaltype(lf) + L <: Literal || println(io, " $(nconjuncts(lf)) and literals of type $(L):") + elseif lf isa DNF + print(io, "DNF with") + println(io, " $(ndisjuncts(lf)) disjuncts:") + L = literaltype(lf) + L <: Literal || println(io, " $(ndisjuncts(lf)) and literals of type $(L):") + else + if lf isa LeftmostConjunctiveForm + print(io, "LeftmostConjunctiveForm with") + elseif lf isa LeftmostDisjunctiveForm + print(io, "LeftmostDisjunctiveForm with") + else + print(io, "LeftmostLinearForm with connective $(syntaxstring(connective(lf))) and") + end + println(io, " $(nchildren(lf)) $((SS == SyntaxStructure ? "" : "$(SS) "))children:") + end + # println(io, "\t$(join(syntaxstring.(children(lf)), " $(syntaxstring(connective(lf))) \n\t"))") + println(io, "\t$(join(syntaxstring.(children(lf)), "\n\t"))") +end + +# TODO fix +Base.promote_rule(::Type{<:LeftmostLinearForm}, ::Type{<:LeftmostLinearForm}) = SyntaxTree +Base.promote_rule(::Type{SS}, ::Type{LF}) where {SS<:SyntaxStructure,LF<:LeftmostLinearForm} = SyntaxTree +Base.promote_rule(::Type{LF}, ::Type{SS}) where {LF<:LeftmostLinearForm,SS<:SyntaxStructure} = SyntaxTree + +function Base.in(tok::SyntaxToken, φ::LeftmostLinearForm)::Bool + return (tok isa Connective && connective(φ) == tok) || + any(c->Base.in(tok, c), children(φ)) +end + +function Base.in(tok::SyntaxLeaf, φ::LeftmostLinearForm{C,<:SyntaxLeaf})::Bool where {C<:Connective} + return Base.in(tok, children(φ)) +end + + +atoms(φ::LeftmostLinearForm) = Iterators.flatten(Iterators.map(atoms, children(φ))) +leaves(φ::LeftmostLinearForm) = Iterators.flatten(Iterators.map(leaves, children(φ))) + +natoms(φ::LeftmostLinearForm) = sum(natoms, children(φ)) +nleaves(φ::LeftmostLinearForm) = sum(nleaves, children(φ)) + +# function tokens(φ::LeftmostLinearForm) +# # return TODO +# end + +function atoms(φ::LeftmostLinearForm{C,<:Atom})::Vector{Atom} where {C<:Connective} + return children(φ) +end + +# function connectives(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} +# # return TODO +# end + +# function operators(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} +# # return TODO +# end + +function leaves(φ::LeftmostLinearForm{C,<:SyntaxLeaf})::SyntaxLeaf where {C<:Connective} + return children(φ) +end + +# function ntokens(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} +# # return TODO +# end + +function natoms(φ::LeftmostLinearForm{C,<:Atom})::Integer where {C<:Connective} + return nchildren(φ) +end + +# function nconnectives(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} +# # return TODO +# end + +# function noperators(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} +# # return TODO +# end + +function nleaves(φ::LeftmostLinearForm{C,<:SyntaxLeaf})::Integer where {C<:Connective} + return nchildren(φ) +end + +Base.promote_rule(::Type{LF}, ::Type{SS}) where {LF<:LeftmostLinearForm,SS<:SyntaxTree} = SyntaxTree +Base.promote_rule(::Type{SS}, ::Type{LF}) where {LF<:LeftmostLinearForm,SS<:SyntaxTree} = SyntaxTree + +############################################################################################ + +# TODO actually: +# const CNF{SS<:SyntaxStructure} = Union{LeftmostLinearForm{typeof(∧),LeftmostLinearForm{typeof(∨),SS}},LeftmostLinearForm{typeof(∨),SS}} +# const DNF{SS<:SyntaxStructure} = Union{LeftmostLinearForm{typeof(∨),LeftmostLinearForm{typeof(∧),SS}},LeftmostLinearForm{typeof(∧),SS}} + +""" + LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} + +Specific instantiation of a [`LeftmostLinearForm`](@ref), where [`Connective`](@ref)s are +all [`CONJUNCTION`](@ref)s. + +See also [`SyntaxStructure`](@ref), [`Connective`](@ref), [`LeftmostLinearForm`](@ref), +[`CONJUNCTION`](@ref). +""" +const LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} + +function check( + φ::LeftmostConjunctiveForm, + args...; + kwargs... +) + return all(ch -> check(ch, args...; kwargs...), children(φ)) +end + +function check( + φ::LeftmostConjunctiveForm, + i::AbstractInterpretation, + args...; + kwargs... +) + return all(ch -> check(ch, i, args...; kwargs...), children(φ)) +end + +""" + LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} + +Specific instantiation of a [`LeftmostLinearForm`](@ref), where [`Connective`](@ref)s are +all [`DISJUNCTION`](@ref)s. + +See also [`SyntaxStructure`](@ref), [`Connective`](@ref), +[`LeftmostLinearForm`](@ref), [`DISJUNCTION`](@ref). +""" +const LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} + +function check( + φ::LeftmostDisjunctiveForm, + args...; + kwargs... +) + return any(ch -> check(ch, args...; kwargs...), children(φ)) +end + +function check( + φ::LeftmostDisjunctiveForm, + i::AbstractInterpretation, + args...; + kwargs... +) + return any(ch -> check(ch, i, args...; kwargs...), children(φ)) +end + +""" + CNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} + +Conjunctive Normal Form of an [`SyntaxStructure`](@ref). + +See also [`SyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), +[`LeftmostDisjunctiveForm`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref). +""" +const CNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} + +function check( + φ::CNF, + args...; + kwargs... +) + return all(ch -> any(grandch -> check(grandch, args...; kwargs...), children(ch)), children(φ)) +end + +function check( + φ::CNF, + i::AbstractInterpretation, + args...; + kwargs... +) + return all(ch -> any(grandch -> check(grandch, i, args...; kwargs...), children(ch)), children(φ)) +end + +""" + DNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostConjunctiveForm{SS}} + +Disjunctive Normal Form of an [`SyntaxStructure`](@ref). + +See also [`SyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), +[`LeftmostDisjunctiveForm`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref). +""" +const DNF{SS<:SyntaxStructure} = LeftmostDisjunctiveForm{LeftmostConjunctiveForm{SS}} + +function check( + φ::DNF, + args...; + kwargs... +) + return any(ch -> all(grandch -> check(grandch, args...; kwargs...), children(ch)), children(φ)) +end + +function check( + φ::DNF, + i::AbstractInterpretation, + args...; + kwargs... +) + return any(ch -> all(grandch -> check(grandch, i, args...; kwargs...), children(ch)), children(φ)) +end + +# Helpers +function CNF(conjuncts::AbstractVector{<:LeftmostDisjunctiveForm}) + SS = Union{childrentype.(conjuncts)...} + return CNF{SS}(conjuncts) +end +function DNF(disjuncts::AbstractVector{<:LeftmostConjunctiveForm}) + SS = Union{childrentype.(disjuncts)...} + return DNF{SS}(disjuncts) +end +CNF(conjuncts::NTuple{N,<:LeftmostDisjunctiveForm}) where {N} = CNF(collect(conjuncts)) +DNF(disjuncts::NTuple{N,<:LeftmostConjunctiveForm}) where {N} = DNF(collect(disjuncts)) +CNF(conjuncts::Vararg{LeftmostDisjunctiveForm}) = CNF(collect(conjuncts)) +DNF(disjuncts::Vararg{LeftmostConjunctiveForm}) = DNF(collect(disjuncts)) +CNF(conjunct::LeftmostDisjunctiveForm) = CNF([conjunct]) +DNF(disjunct::LeftmostConjunctiveForm) = DNF([disjunct]) + +function CNF(φ::SyntaxLeaf) + return LeftmostConjunctiveForm([LeftmostDisjunctiveForm([φ])]) +end + +function DNF(φ::SyntaxLeaf) + return LeftmostDisjunctiveForm([LeftmostConjunctiveForm([φ])]) +end + +function CNF(φ::SyntaxBranch) + return erorr("TODO") +end + +function DNF(φ::SyntaxBranch) + return erorr("TODO") +end + + +literaltype(::CNF{SS}) where {SS<:SyntaxStructure} = SS +literaltype(::DNF{SS}) where {SS<:SyntaxStructure} = SS + +# # TODO maybe not needed? +# Base.promote_rule(::Type{<:LeftmostConjunctiveForm}, ::Type{<:LeftmostConjunctiveForm}) = LeftmostConjunctiveForm +# Base.promote_rule(::Type{<:LeftmostDisjunctiveForm}, ::Type{<:LeftmostDisjunctiveForm}) = LeftmostDisjunctiveForm +# Base.promote_rule(::Type{<:LeftmostConjunctiveForm}, ::Type{<:LeftmostDisjunctiveForm}) = SyntaxTree + +conjuncts(φ::LeftmostConjunctiveForm) = children(φ) +nconjuncts(φ::LeftmostConjunctiveForm) = nchildren(φ) +pushconjunct!(φ::LeftmostLinearForm, el) = Base.push!(children(φ), el) + +disjuncts(φ::LeftmostDisjunctiveForm) = children(φ) +ndisjuncts(φ::LeftmostDisjunctiveForm) = nchildren(φ) +pushdisjunct(φ::LeftmostDisjunctiveForm, el) = Base.push!(children(φ), el) + +# conjuncts(φ::DNF) = map(d->conjuncts(d), disjuncts(φ)) +# nconjuncts(φ::DNF) = map(d->nconjuncts(d), disjuncts(φ)) +# disjuncts(φ::CNF) = map(d->disjuncts(d), conjuncts(φ)) +# ndisjuncts(φ::CNF) = map(d->ndisjuncts(d), conjuncts(φ)) + + +############################################################################################ + +""" + struct Literal{T<:SyntaxLeaf} <: SyntaxStructure + ispos::Bool + prop::T + end + +An atom, or its negation. + +See also [`CNF`](@ref), [`DNF`](@ref), [`SyntaxStructure`](@ref). +""" +struct Literal{T<:SyntaxLeaf} <: SyntaxStructure + ispos::Bool + prop::T + + function Literal{T}( + ispos::Bool, + prop::T, + ) where {T<:SyntaxLeaf} + new{T}(ispos, prop) + end + + function Literal( + ispos::Bool, + prop::T, + ) where {T<:SyntaxLeaf} + Literal{T}(ispos, prop) + end + + function Literal(φ::SyntaxLeaf, flag = true) + return Literal(flag, φ) + end + function Literal(φ::SyntaxBranch, flag = true) + ch = first(children(φ)) + @assert (token(φ) == ¬) "Cannot " * + "construct Literal with formula of type $(typeof(ch))): $(syntaxstring(ch))." + return Literal(ch, !flag) + end +end + +ispos(l::Literal) = l.ispos +prop(l::Literal) = l.prop + +atomstype(::Literal{T}) where {T} = T + +tree(l::Literal) = ispos(l) ? l.prop : ¬(l.prop) + +hasdual(l::Literal) = true +dual(l::Literal) = Literal(!ispos(l), prop(l)) + +function Base.show(io::IO, l::Literal) + println(io, + "Literal{$(atomstype(l))}: " * (ispos(l) ? "" : "¬") * syntaxstring(prop(l)) + ) +end + +############################################################################################ +# CNF conversion +############################################################################################ + +""" + cnf(φ::Formula, literaltype = Literal; kwargs...) + + TODO docstring. Converts to cnf form ([`CNF`](@ref)). + `CNF{literaltype}` + Additional `kwargs` are passed to [`normalize`](@ref) +""" +function cnf(φ::Formula, literaltype = Literal; kwargs...) + return _cnf(normalize(φ; profile = :nnf, kwargs...), literaltype) +end + +function cnf(φ::CNF{T}, literaltype = Literal; kwargs...) where {T<:SyntaxStructure} + if T == literaltype + return φ + else + return cnf(tree(φ), literaltype; kwargs...) + end +end + +function cnf(φ::DNF, args...; kwargs...) + return cnf(tree(φ), args...; kwargs...) +end + + +function _cnf(φ::Formula, literaltype = Literal) + return error("Cannot convert to CNF formula of type $(typeof(φ)): $(syntaxstring(φ))") +end + +function _cnf(φ::SyntaxLeaf, literaltype = Literal) + φ = φ isa literaltype ? φ : literaltype(φ) + return LeftmostConjunctiveForm([LeftmostDisjunctiveForm{literaltype}([φ])]) +end + +function _cnf(φ::SyntaxBranch, literaltype = Literal) + if token(φ) == ∧ + return _cnf(first(children(φ)), literaltype) ∧ _cnf(last(children(φ)), literaltype) + elseif token(φ) == ∨ + conjs = vec([begin + # @show typeof(c1), typeof(c2) + # @show typeof(c1 ∨ c2) + # LeftmostDisjunctiveForm{literaltype}(c1 ∨ c2) + c1 ∨ c2 + end for (c1,c2) in Iterators.product(conjuncts(_cnf(first(children(φ)), literaltype)),conjuncts(_cnf(last(children(φ)), literaltype)))]) + # @show typeof.(conjs) + # conjs = Vector{LeftmostDisjunctiveForm{literaltype}}(conjs) + return LeftmostConjunctiveForm(conjs) + elseif token(φ) == ¬ + φ = φ isa literaltype ? φ : literaltype(φ) + return LeftmostConjunctiveForm([LeftmostDisjunctiveForm{literaltype}([φ])]) + else + return error("Unexpected token $(token)!") + end +end + +############################################################################################ diff --git a/src/utils/syntax-utils.jl b/src/utils/syntax-utils.jl new file mode 100644 index 00000000..54f3901b --- /dev/null +++ b/src/utils/syntax-utils.jl @@ -0,0 +1,483 @@ + +############################################################################################ + +subtrees(tree::SyntaxTree) = [Iterators.flatten(_subtrees.(children(tree)))...] +_subtrees(tree::SyntaxTree) = [tree, Iterators.flatten(_subtrees.(children(tree)))...] + +# TODO: explain better +# TODO: is this available in AbstractTrees? +""" + treewalk( + 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. +""" +function treewalk( + st::SyntaxTree, + args...; + rng::AbstractRNG = Random.GLOBAL_RNG, + criterion::Function = c->true, + returnnode::Bool = false, + transformnode::Union{Nothing,Function} = nothing, +) + chs = children(st) + + return length(chs) == 0 ? begin + isnothing(transformnode) ? st : transformnode(st, args...) + end : begin + c_chsub = map(c->length(filter(criterion, tokens(c))), chs) + c_father = criterion(token(st)) ? 1 : 0 + + @assert [c_chsub..., c_father] isa AbstractVector{<:Integer} "Not all values " * + "computed as criterion are integers, double check the passed function used for " * + "calculating these; values: $([c_chsub..., c_father])" + + w_nodes = [c_chsub..., c_father]/sum([c_chsub..., c_father]) + idx_randnode = sample(rng, 1:length(w_nodes), Weights(w_nodes)) + + if idx_randnode == length(w_nodes) + isnothing(transformnode) ? st : transformnode(st, args...) + else + returnnode ? + treewalk( + chs[idx_randnode], + args...; + rng=rng, + criterion=criterion, + returnnode=returnnode, + transformnode=transformnode, + ) : + SyntaxTree( + token(st), + ( + chs[1:(idx_randnode-1)]..., + treewalk( + chs[idx_randnode], + args...; + rng=rng, + criterion=criterion, + returnnode=returnnode, + transformnode=transformnode, + ), + chs[(idx_randnode+1):end]... + ) + ) + end + end +end + + + +""" + subformulas(f::Formula; sorted=true) + +Return all sub-formulas (sorted by size when `sorted=true`) +of a given formula. + +# Examples +```julia-repl +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`](@ref)), [`Formula`](@ref). +""" +subformulas(f::Formula, args...; kwargs...) = subformulas(tree(f), args...; kwargs...) +function subformulas(t::SyntaxTree; sorted=true) + # function _subformulas(_t::SyntaxTree) + # SyntaxTree[ + # (map(SyntaxTree, Iterators.flatten(subformulas.(children(_t)))))..., + # _t + # ] + # end + function _subformulas(_t::SyntaxTree) + SyntaxTree[ + (Iterators.flatten(subformulas.(children(_t))))..., + _t + ] + end + ts = _subformulas(t) + if sorted + sort!(ts, by = t -> SoleLogics.height(t)) + end + ts +end + +# TODO move to utils and rename "normalize" -> "transform"/"reshape"/"simplify" +# TODO \to diventano \lor +# TODO explain profile's and other parameters +""" + normalize( + 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, + ) + +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](https://en.wikipedia.org/wiki/Model_checking). +The current implementation assumes the underlying algebra is Boolean! + +# Arguments +- `f::Formula`: when set to `true`, + 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 to `true`, + attempts at reducing the number of negations by appling + some transformation rules + (e.g., [De Morgan's laws](https://en.wikipedia.org/wiki/De_Morgan%27s_laws)). + Note: this assumes an underlying Boolean algebra. +- `allow_atom_flipping::Bool`: when set to `true`, + together with `reduce_negations=true`, this may cause the negation of an atom + to be replaced with the its [`dual`](@ref) atom. + +# Examples +```julia-repl +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`](@ref)), [`Formula`](@ref). +""" +normalize(f::Formula, args...; kwargs...) = normalize(tree(f), args...; kwargs...) +function normalize( + t::LLF; + kwargs... +) where {LLF<:Union{LeftmostConjunctiveForm,LeftmostDisjunctiveForm}} + ch = children(t) + unique!(ch) + ch = normalize.(ch; kwargs...) + unique!(ch) + if connective(t) == (∧) + filter!(c->c != ⊤, ch) + elseif connective(t) == (∨) + filter!(c->c != ⊥, ch) + end + return LeftmostLinearForm(connective(t), ch) +end + +function normalize( + t::SyntaxTree; + profile = :readability, + remove_boxes = nothing, + reduce_negations = nothing, + simplify_constants = nothing, + allow_atom_flipping = nothing, + prefer_implications = nothing, + remove_implications = nothing, + forced_negation_removal = nothing, + remove_identities = nothing, + unify_toones = nothing, + rotate_commutatives = nothing, +) + if profile == :readability + if isnothing(remove_boxes) remove_boxes = false end + if isnothing(reduce_negations) reduce_negations = true end + if isnothing(simplify_constants) simplify_constants = true end + if isnothing(allow_atom_flipping) allow_atom_flipping = false end + if isnothing(prefer_implications) prefer_implications = false end + if isnothing(remove_implications) remove_implications = false end + if isnothing(remove_identities) remove_identities = true end + if isnothing(unify_toones) unify_toones = true end + if isnothing(rotate_commutatives) rotate_commutatives = true end + # TODO leave \to's instead of replacing them with \lor's... + elseif profile == :nnf + if isnothing(remove_boxes) remove_boxes = false end + if isnothing(reduce_negations) reduce_negations = true end + if isnothing(simplify_constants) simplify_constants = false end + if isnothing(allow_atom_flipping) allow_atom_flipping = false end + if isnothing(prefer_implications) prefer_implications = false end + if isnothing(remove_implications) remove_implications = true end + if isnothing(remove_identities) remove_identities = false end + if isnothing(unify_toones) unify_toones = false end + if isnothing(rotate_commutatives) rotate_commutatives = true end + # TODO leave \to's instead of replacing them with \lor's... + elseif profile == :modelchecking + if isnothing(remove_boxes) remove_boxes = true end + if isnothing(reduce_negations) reduce_negations = true end + if isnothing(simplify_constants) simplify_constants = true end + if isnothing(allow_atom_flipping) allow_atom_flipping = false end + if isnothing(prefer_implications) prefer_implications = false end + if isnothing(remove_implications) remove_implications = false end + if isnothing(remove_identities) remove_identities = true end + if isnothing(unify_toones) unify_toones = true end + if isnothing(rotate_commutatives) rotate_commutatives = true end + else + error("Unknown normalization profile: $(repr(profile))") + end + + if isnothing(forced_negation_removal) + if isnothing(allow_atom_flipping) + forced_negation_removal = true + else + forced_negation_removal = false + end + end + + # TODO we're currently assuming Boolean algebra!!! Very wrong assumption... + + _normalize = t->normalize(t; + profile = profile, + remove_boxes = remove_boxes, + reduce_negations = reduce_negations, + simplify_constants = simplify_constants, + allow_atom_flipping = allow_atom_flipping, + prefer_implications = prefer_implications, + remove_implications = remove_implications, + forced_negation_removal = forced_negation_removal, + remove_identities = remove_identities, + unify_toones = unify_toones, + rotate_commutatives = rotate_commutatives + ) + + newt = t + + # Remove modal connectives based on the identity relation + newt = begin + tok, chs = token(newt), children(newt) + if remove_identities && tok isa AbstractRelationalConnective && + relation(tok) == identityrel && arity(tok) == 1 + first(chs) + elseif unify_toones && tok isa AbstractRelationalConnective && + istoone(relation(tok)) && arity(tok) == 1 + diamond(relation(tok))(first(chs)) + else + newt + end + end + + # Simplify + newt = begin + tok, chs = token(newt), children(newt) + if (tok == ¬) && arity(tok) == 1 + child = chs[1] + chtok, grandchildren = token(child), children(child) + if reduce_negations && (chtok == ¬) && arity(chtok) == 1 + _normalize(grandchildren[1]) + elseif reduce_negations && (chtok == ∨) && arity(chtok) == 2 + ∧(_normalize(¬(grandchildren[1])), _normalize(¬(grandchildren[2]))) + # TODO use implication, maybe it's more interpretable? + elseif reduce_negations && (chtok == ∧) && arity(chtok) == 2 + # if prefer_implications + # →(_normalize(grandchildren[1]), _normalize(¬(grandchildren[2]))) + # else + ∨(_normalize(¬(grandchildren[1])), _normalize(¬(grandchildren[2]))) + # end + elseif reduce_negations && (chtok == →) && arity(chtok) == 2 + # _normalize(∨(¬(grandchildren[1]), grandchildren[2])) + ∧(_normalize(grandchildren[1]), _normalize(¬(grandchildren[2]))) + elseif reduce_negations && chtok isa Atom + if allow_atom_flipping && hasdual(chtok) + dual(chtok) + else + ¬(_normalize(child)) + end + # elseif reduce_negations && chtok isa SoleLogics.AbstractRelationalConnective && arity(chtok) == 1 + # dual_op = dual(chtok) + # if remove_boxes && dual_op isa SoleLogics.BoxRelationalConnective + # ¬(_normalize(child)) + # else + # dual_op(_normalize(¬(grandchildren[1]))) + # end + elseif reduce_negations && ismodal(chtok) && arity(chtok) == 1 + dual_op = dual(chtok) + # if remove_boxes && SoleLogics.isbox(dual_op) + # ¬(_normalize(child)) + # else + dual_op(_normalize(¬(grandchildren[1]))) + # end + elseif (reduce_negations || simplify_constants) && chtok == ⊤ && arity(chtok) == 1 + ⊥ + elseif (reduce_negations || simplify_constants) && chtok == ⊥ && arity(chtok) == 1 + ⊤ + elseif !forced_negation_removal + SyntaxTree(tok, _normalize.(chs)) + else + error("Unknown chtok when removing negations: $(chtok) (type = $(typeof(chtok)))") + end + else + SyntaxTree(tok, _normalize.(chs)) + end + end + + # DEBUG: old_newt = newt + # Simplify constants + newt = begin + tok, chs = token(newt), children(newt) + if simplify_constants && tok isa Connective + if (tok == ∨) && arity(tok) == 2 # TODO maybe use istop, isbot? + if token(chs[1]) == ⊥ chs[2] # ⊥ ∨ φ ≡ φ + elseif token(chs[2]) == ⊥ chs[1] # φ ∨ ⊥ ≡ φ + elseif token(chs[1]) == ⊤ ⊤ # ⊤ ∨ φ ≡ ⊤ + elseif token(chs[2]) == ⊤ ⊤ # φ ∨ ⊤ ≡ ⊤ + else newt + end + elseif (tok == ∧) && arity(tok) == 2 + if token(chs[1]) == ⊥ ⊥ # ⊥ ∧ φ ≡ ⊥ + elseif token(chs[2]) == ⊥ ⊥ # φ ∧ ⊥ ≡ ⊥ + elseif token(chs[1]) == ⊤ chs[2] # ⊤ ∧ φ ≡ φ + elseif token(chs[2]) == ⊤ chs[1] # φ ∧ ⊤ ≡ φ + else newt + end + elseif (tok == →) && arity(tok) == 2 + if token(chs[1]) == ⊥ ⊤ # ⊥ → φ ≡ ⊤ + elseif token(chs[2]) == ⊥ _normalize(¬chs[1]) # φ → ⊥ ≡ ¬φ + elseif token(chs[1]) == ⊤ chs[2] # ⊤ → φ ≡ φ + elseif token(chs[2]) == ⊤ ⊤ # φ → ⊤ ≡ ⊤ + else newt + end + elseif (tok == ¬) && arity(tok) == 1 + if token(chs[1]) == ⊤ ⊥ + elseif token(chs[1]) == ⊥ ⊤ + else newt + end + elseif SoleLogics.isbox(tok) && arity(tok) == 1 + if token(chs[1]) == ⊤ ⊤ + else newt + end + elseif SoleLogics.isdiamond(tok) && arity(tok) == 1 + if token(chs[1]) == ⊥ ⊥ + else newt + end + else + newt + end + else + newt + end + end + + # Implication <-> disjunction + newt = begin + tok, chs = token(newt), children(newt) + if prefer_implications && (tok == ∨) + if token(chs[1]) == ¬ + →(_normalize(first(children(chs[1]))), _normalize((chs[2]))) + else + →(_normalize(¬chs[1]), _normalize((chs[2]))) + end + elseif remove_implications && (tok == →) + if token(chs[1]) == ¬ + ∨(_normalize(first(children(chs[1]))), _normalize((chs[2]))) + else + ∨(_normalize(¬chs[1]), _normalize((chs[2]))) + end + else + newt + end + end + # DEBUG: + # "$(syntaxstring(old_newt)) => $(syntaxstring(newt))" |> println + + newt = begin + tok, chs = token(newt), children(newt) + if remove_boxes && tok isa Connective && SoleLogics.isbox(tok) && arity(tok) == 1 + # remove_boxes -> substitute every [X]φ with ¬⟨X⟩¬φ + child = chs[1] + dual_op = dual(tok) + ¬(dual_op(_normalize(¬child))) + # TODO remove + # if relation(tok) == globalrel + # # Special case: [G]φ -> ⟨G⟩φ + # dual_op(_normalize(child)) + # else + # ¬(dual_op(_normalize(¬child))) + # end + else + newt + end + end + + function _isless(st1::SyntaxTree, st2::SyntaxTree) + isless(Base.hash(st1), Base.hash(st2)) + end + + # Rotate commutatives + if rotate_commutatives + newt = begin + tok, chs = token(newt), children(newt) + if tok isa Connective && iscommutative(tok) && arity(tok) > 1 + chs = children(LeftmostLinearForm(newt, tok)) + chs = Vector(sort(collect(_normalize.(chs)), lt=_isless)) + if tok in [∧,∨] # TODO create trait for this behavior: p ∧ p ∧ p ∧ q -> p ∧ q + chs = unique(chs) + end + tree(LeftmostLinearForm(tok, chs)) + else + SyntaxTree(tok, chs) + end + end + end + + return newt +end + +""" + isgrounded(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-repl +julia> f = parseformula("⟨G⟩p → [G]q"); + +julia> syntaxstring(f) +"(⟨G⟩p) → ([G]q)" + +julia> SoleLogics.isgrounded(f) +true +``` + +See also +[`isgrounding`](@ref)), [`SyntaxTree`](@ref)), [`Formula`](@ref). +""" +isgrounded(f::Formula) = isgrounded(tree(f)) +function isgrounded(t::SyntaxTree)::Bool + # (println(token(t)); println(children(t)); true) && + return (token(t) isa SoleLogics.AbstractRelationalConnective && isgrounding(relation(token(t)))) || + # (token(t) in [◊,□]) || + (token(t) isa Connective && all(c->isgrounded(c), children(t))) +end From a53931ee79139a104d10c692f8d6518634b951b2 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:40:24 +0200 Subject: [PATCH 68/90] Fix doc --- docs/Manifest.toml | 174 +++++++++++++++-------------------- docs/make.jl | 6 ++ docs/src/base-logic.md | 3 +- docs/src/getting-started.md | 3 +- docs/src/more-on-formulas.md | 2 +- src/generation/formula.jl | 51 +++++++--- src/types/syntactical.jl | 2 +- 7 files changed, 124 insertions(+), 117 deletions(-) diff --git a/docs/Manifest.toml b/docs/Manifest.toml index b17a6e68..82c89d78 100644 --- a/docs/Manifest.toml +++ b/docs/Manifest.toml @@ -1,8 +1,8 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.9.4" +julia_version = "1.10.5" manifest_format = "2.0" -project_hash = "c381fc514da0d250e7ae0e321593f7b9fe01ae6d" +project_hash = "249cfa71630805782c75f79c26f4be6f24605297" [[deps.ANSIColoredPrinters]] git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" @@ -20,9 +20,9 @@ version = "1.1.1" [[deps.ArnoldiMethod]] deps = ["LinearAlgebra", "Random", "StaticArrays"] -git-tree-sha1 = "62e51b39331de8911e4a7ff6f5aaf38a5f4cc0ae" +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" uuid = "ec485272-7323-5ecc-a04f-4719b315124d" -version = "0.2.0" +version = "0.4.0" [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" @@ -48,23 +48,17 @@ version = "0.10.8" SentinelArrays = "91c51154-3ec4-41a3-a24f-3f23e20d615c" StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" -[[deps.CodeTracking]] -deps = ["InteractiveUtils", "UUIDs"] -git-tree-sha1 = "c0216e792f518b39b22212127d4a84dc31e4e386" -uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" -version = "1.3.5" - [[deps.CodecZlib]] deps = ["TranscodingStreams", "Zlib_jll"] -git-tree-sha1 = "59939d8a997469ee05c4b4944560a820f9ba0d73" +git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" -version = "0.7.4" +version = "0.7.6" [[deps.Compat]] deps = ["TOML", "UUIDs"] -git-tree-sha1 = "c955881e3c981181362ae4088b35995446298b80" +git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.14.0" +version = "4.16.0" weakdeps = ["Dates", "LinearAlgebra"] [deps.Compat.extensions] @@ -73,7 +67,7 @@ weakdeps = ["Dates", "LinearAlgebra"] [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.0.5+0" +version = "1.1.1+0" [[deps.Crayons]] git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15" @@ -87,9 +81,9 @@ version = "1.16.0" [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "0f4b5d62a88d8f59003e43c25a8a90de9eb76317" +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.18" +version = "0.18.20" [[deps.DataValueInterfaces]] git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" @@ -118,9 +112,9 @@ version = "0.9.3" [[deps.Documenter]] deps = ["ANSIColoredPrinters", "AbstractTrees", "Base64", "CodecZlib", "Dates", "DocStringExtensions", "Downloads", "Git", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "MarkdownAST", "Pkg", "PrecompileTools", "REPL", "RegistryInstances", "SHA", "TOML", "Test", "Unicode"] -git-tree-sha1 = "4a40af50e8b24333b9ec6892546d9ca5724228eb" +git-tree-sha1 = "5a1ee886566f2fa9318df1273d8b778b9d42712d" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "1.3.0" +version = "1.7.0" [[deps.Downloads]] deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] @@ -129,18 +123,18 @@ version = "1.6.0" [[deps.Expat_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "4558ab818dcceaab612d1bb8c19cee87eda2b83c" +git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7" uuid = "2e619515-83b5-522b-bb60-26c02a35a201" -version = "2.5.0+0" +version = "2.6.2+0" [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" [[deps.FillArrays]] -deps = ["LinearAlgebra", "Random"] -git-tree-sha1 = "5b93957f6dcd33fc343044af3d48c215be2562f1" +deps = ["LinearAlgebra"] +git-tree-sha1 = "6a70198746448456524cb442b8af316927ff3e1a" uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" -version = "1.9.3" +version = "1.13.0" [deps.FillArrays.extensions] FillArraysPDMatsExt = "PDMats" @@ -169,21 +163,21 @@ version = "1.3.1" [[deps.Git_jll]] deps = ["Artifacts", "Expat_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Libiconv_jll", "OpenSSL_jll", "PCRE2_jll", "Zlib_jll"] -git-tree-sha1 = "12945451c5d0e2d0dca0724c3a8d6448b46bbdf9" +git-tree-sha1 = "ea372033d09e4552a04fd38361cd019f9003f4f4" uuid = "f8c6e375-362e-5223-8a59-34ff63f689eb" -version = "2.44.0+1" +version = "2.46.2+0" [[deps.Graphs]] deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "899050ace26649433ef1af25bc17a815b3db52b7" +git-tree-sha1 = "1dc470db8b1131cfc7fb4c115de89fe391b9e780" uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.9.0" +version = "1.12.0" [[deps.IOCapture]] deps = ["Logging", "Random"] -git-tree-sha1 = "8b72179abc660bfab5e28472e019392b97d0985c" +git-tree-sha1 = "b6d6bfdd7ce25b0f9b2f6b3dd56b2673a66c8770" uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" -version = "0.2.4" +version = "0.2.5" [[deps.Indexing]] git-tree-sha1 = "ce1566720fd6b19ff3411404d4b977acd4814f9f" @@ -191,9 +185,9 @@ uuid = "313cdc1a-70c2-5d6a-ae34-0150d3930a38" version = "1.1.1" [[deps.Inflate]] -git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381" +git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" -version = "0.1.4" +version = "0.1.5" [[deps.InteractiveUtils]] deps = ["Markdown"] @@ -216,9 +210,9 @@ version = "1.0.0" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] -git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.5.0" +version = "1.6.1" [[deps.JSON]] deps = ["Dates", "Mmap", "Parsers", "Unicode"] @@ -226,16 +220,10 @@ git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" version = "0.21.4" -[[deps.JuliaInterpreter]] -deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"] -git-tree-sha1 = "7b762d81887160169ddfc93a47e5fd7a6a3e78ef" -uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" -version = "0.9.29" - [[deps.LaTeXStrings]] -git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec" +git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c" uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" -version = "1.3.1" +version = "1.4.0" [[deps.LazilyInitializedFields]] git-tree-sha1 = "8f7f3cabab0fd1800699663533b6d5cb3fc0e612" @@ -259,9 +247,14 @@ uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" version = "8.4.0+0" [[deps.LibGit2]] -deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.6.4+0" + [[deps.LibSSH2_jll]] deps = ["Artifacts", "Libdl", "MbedTLS_jll"] uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" @@ -282,9 +275,9 @@ uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [[deps.LogExpFunctions]] deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "18144f3e9cbe9b15b070288eef858f71b291ce37" +git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea" uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.27" +version = "0.3.28" [deps.LogExpFunctions.extensions] LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" @@ -299,12 +292,6 @@ version = "0.3.27" [[deps.Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" -[[deps.LoweredCodeUtils]] -deps = ["JuliaInterpreter"] -git-tree-sha1 = "31e27f0b0bf0df3e3e951bfcc43fe8c730a219f6" -uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" -version = "2.4.5" - [[deps.MacroTools]] deps = ["Markdown", "Random"] git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df" @@ -324,20 +311,20 @@ version = "0.1.2" [[deps.MbedTLS_jll]] deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.2+0" +version = "2.28.2+1" [[deps.Missings]] deps = ["DataAPI"] -git-tree-sha1 = "f66bdc5de519e8f8ae43bdc598782d35a25b1272" +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" -version = "1.1.0" +version = "1.2.0" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" [[deps.MozillaCACerts_jll]] uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2022.10.11" +version = "2023.1.10" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" @@ -346,13 +333,13 @@ version = "1.2.0" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.21+4" +version = "0.3.23+4" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "60e3045590bd104a16fefb12836c00c0ef8c7f8c" +git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.13+0" +version = "3.0.15+1" [[deps.OrderedCollections]] git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" @@ -362,7 +349,7 @@ version = "1.6.3" [[deps.PCRE2_jll]] deps = ["Artifacts", "Libdl"] uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" -version = "10.42.0+0" +version = "10.42.0+1" [[deps.Parsers]] deps = ["Dates", "PrecompileTools", "UUIDs"] @@ -373,13 +360,13 @@ version = "2.8.1" [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -version = "1.9.2" +version = "1.10.0" [[deps.PrecompileTools]] deps = ["Preferences"] -git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.2.0" +version = "1.2.1" [[deps.Preferences]] deps = ["TOML"] @@ -389,9 +376,9 @@ version = "1.4.3" [[deps.PrettyTables]] deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "Reexport", "StringManipulation", "Tables"] -git-tree-sha1 = "88b895d13d53b5577fd53379d913b9ab9ac82660" +git-tree-sha1 = "1101cd475833706e4d0e7b122218257178f48f34" uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -version = "2.3.1" +version = "2.4.0" [[deps.Printf]] deps = ["Unicode"] @@ -402,7 +389,7 @@ deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.Random]] -deps = ["SHA", "Serialization"] +deps = ["SHA"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[deps.Reexport]] @@ -422,12 +409,6 @@ git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" uuid = "ae029012-a4dd-5104-9daa-d747884805df" version = "1.3.0" -[[deps.Revise]] -deps = ["CodeTracking", "Distributed", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Pkg", "REPL", "Requires", "UUIDs", "Unicode"] -git-tree-sha1 = "12aa2d7593df490c407a3bbd8b86b8b515017f3e" -uuid = "295af30f-e4ad-537b-8983-00126c2a3abe" -version = "3.5.14" - [[deps.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" version = "0.7.0" @@ -450,13 +431,13 @@ uuid = "6462fe0b-24de-5631-8697-dd941f90decc" [[deps.SoleBase]] deps = ["CategoricalArrays", "FillArrays", "IterTools", "Logging", "Random", "StatsBase"] -git-tree-sha1 = "878891f576af95083687f1039f33beb40d990fcf" +git-tree-sha1 = "ec78e6ef042c7060c2f26f72f7b8b2199510c465" uuid = "4475fa32-7023-44a0-aa70-4813b230e492" -version = "0.12.1" +version = "0.12.4" [[deps.SoleLogics]] -deps = ["AbstractTrees", "DataStructures", "Dictionaries", "FunctionWrappers", "Graphs", "IterTools", "Lazy", "PrettyTables", "Random", "Reexport", "Revise", "SoleBase", "StatsBase", "ThreadSafeDicts"] -git-tree-sha1 = "629e1c1c4b46e82b4ea8435833a8bdd457b0aec1" +deps = ["AbstractTrees", "DataStructures", "Dictionaries", "FunctionWrappers", "Graphs", "IterTools", "Lazy", "PrettyTables", "Random", "Reexport", "SoleBase", "StatsBase", "ThreadSafeDicts"] +git-tree-sha1 = "958e6cce545b79bb243283455a7453c88a77381e" repo-rev = "dev" repo-url = ".." uuid = "b002da8f-3cb3-4d91-bbe3-2953433912b5" @@ -471,12 +452,13 @@ version = "1.2.1" [[deps.SparseArrays]] deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.10.0" [[deps.StaticArrays]] deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "bf074c045d3d5ffd956fa0a461da38a44685d6b2" +git-tree-sha1 = "777657803913ffc7e8cc20f0fd04b634f871af8f" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.3" +version = "1.9.8" [deps.StaticArrays.extensions] StaticArraysChainRulesCoreExt = "ChainRulesCore" @@ -487,14 +469,14 @@ version = "1.9.3" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [[deps.StaticArraysCore]] -git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" -version = "1.4.2" +version = "1.4.3" [[deps.Statistics]] deps = ["LinearAlgebra", "SparseArrays"] uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -version = "1.9.0" +version = "1.10.0" [[deps.StatsAPI]] deps = ["LinearAlgebra"] @@ -504,20 +486,20 @@ version = "1.7.0" [[deps.StatsBase]] deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "1d77abd07f617c4868c33d4f5b9e1dbb2643c9cf" +git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21" uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.34.2" +version = "0.34.3" [[deps.StringManipulation]] deps = ["PrecompileTools"] -git-tree-sha1 = "a04cabe79c5f01f4d723cc6704070ada0b9d46d5" +git-tree-sha1 = "a6b1675a536c5ad1a60e5a5153e1fee12eb146e3" uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" -version = "0.3.4" +version = "0.4.0" [[deps.SuiteSparse_jll]] -deps = ["Artifacts", "Libdl", "Pkg", "libblastrampoline_jll"] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "5.10.1+6" +version = "7.2.1+1" [[deps.TOML]] deps = ["Dates"] @@ -531,10 +513,10 @@ uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" version = "1.0.1" [[deps.Tables]] -deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits"] -git-tree-sha1 = "cb76cf677714c095e535e3501ac7954732aeea2d" +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] +git-tree-sha1 = "598cd7c1f68d1e205689b1c2fe65a9f85846f297" uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "1.11.1" +version = "1.12.0" [[deps.Tar]] deps = ["ArgTools", "SHA"] @@ -551,13 +533,9 @@ uuid = "4239201d-c60e-5e0a-9702-85d713665ba7" version = "0.1.6" [[deps.TranscodingStreams]] -git-tree-sha1 = "3caa21522e7efac1ba21834a03734c57b4611c7e" +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" -version = "0.10.4" -weakdeps = ["Random", "Test"] - - [deps.TranscodingStreams.extensions] - TestExt = ["Test", "Random"] +version = "0.11.3" [[deps.UUIDs]] deps = ["Random", "SHA"] @@ -569,12 +547,12 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.13+0" +version = "1.2.13+1" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+0" +version = "5.11.0+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] @@ -584,4 +562,4 @@ version = "1.52.0+1" [[deps.p7zip_jll]] deps = ["Artifacts", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.4.0+0" +version = "17.4.0+2" diff --git a/docs/make.jl b/docs/make.jl index 1bca5c3a..9dd6a4b2 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,8 +2,14 @@ using SoleLogics using SoleLogics.ManyValuedLogics using Documenter + + + + DocMeta.setdocmeta!(SoleLogics, :DocTestSetup, :(using SoleLogics); recursive = true) + + makedocs(; modules = [SoleLogics, SoleLogics.ManyValuedLogics], authors = "Mauro Milella, Giovanni Pagliarini, Alberto Paparella, Eduard I. Stan", diff --git a/docs/src/base-logic.md b/docs/src/base-logic.md index 53bfcce7..34fb8994 100644 --- a/docs/src/base-logic.md +++ b/docs/src/base-logic.md @@ -48,9 +48,8 @@ Recalling the type hierarchy presented in [man-core](@ref), it is here enriched AbstractAlphabet{V} Base.isfinite(::Type{<:AbstractAlphabet}) atoms(a::AbstractAlphabet) +natoms(a::AbstractAlphabet) Base.in(p::Atom, a::AbstractAlphabet) -Base.length(a::AbstractAlphabet) -Base.iterate(a::AbstractAlphabet) ExplicitAlphabet{V} AlphabetOfAny{V} diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index 7ece2f35..31ebddf2 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -49,7 +49,7 @@ If the definition above overwhelms you, don't worry: it will be clearer later. F 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`](@ref). We will also understand how to define our own custom connectives. ```@docs -arity(φ::SyntaxTree) +arity(φ::Connective) ``` The vast majority of data structures involved in encoding a logical formula, are children of the [`Formula`](@ref) 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`](@ref) abstract type. @@ -79,7 +79,6 @@ SyntaxStructure SyntaxTree children(φ::SyntaxTree) token(φ::SyntaxTree) -arity(φ::SyntaxTree) SyntaxLeaf SyntaxToken diff --git a/docs/src/more-on-formulas.md b/docs/src/more-on-formulas.md index 4826acb9..128fd376 100644 --- a/docs/src/more-on-formulas.md +++ b/docs/src/more-on-formulas.md @@ -55,7 +55,7 @@ Base.rand(alphabet::AbstractAlphabet, args...; kwargs...) SoleLogics.randatom StatsBase.sample(alphabet::AbstractAlphabet, weights::AbstractWeights, args...; kwargs...) -randformula(height::Integer, alphabet, operators::AbstractVector; rng::Union{Integer,AbstractRNG} = Random.GLOBAL_RNG) +randformula ``` ## Parsing diff --git a/src/generation/formula.jl b/src/generation/formula.jl index f4876c54..08b486d2 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -6,6 +6,39 @@ import StatsBase: sample # TODO - Move in src/utils.jl (keep here the implementations for struct) """$(randatom_docstring)""" +function randatom end + +"""$(randatom_unionalphabet_docstring)""" +function randatom end + +"""$(randformula_hg_docstring)""" +function randformula end + +"""$(randformula_docstring)""" +function randformula end + +"""$(rand_abstractlogic_docstring)""" +function Base.rand end + +"""$(rand_abstractalphabet_docstring)""" +function Base.rand end + +"""$(rand_completeflatgrammar_docstring)""" +function Base.rand end + +"""$(rand_granular_docstring)""" +function Base.rand end + +"""$(sample_aw_docstring)""" +function StatsBase.sample end + +"""$(sample_lao_docstring)""" +function StatsBase.sample end + +"""$(sample_hgao_docstring)""" +function StatsBase.sample end + + @__rng_dispatch function randatom( rng::Union{Random.AbstractRNG,Integer}, a::AbstractAlphabet, @@ -24,7 +57,6 @@ import StatsBase: sample end end -"""$(randatom_unionalphabet_docstring)""" @__rng_dispatch function randatom( rng::Union{Integer,AbstractRNG}, a::UnionAlphabet, @@ -76,7 +108,6 @@ end -"""$(rand_abstractalphabet_docstring)""" @__rng_dispatch function SoleLogics.rand( rng::Union{Integer,AbstractRNG}, a::AbstractAlphabet, @@ -94,7 +125,6 @@ function rand(rng::Random.AbstractRNG, a::SoleLogics.AbstractAlphabet, args...; randatom(rng, a, args...; kwargs...) end -"""$(rand_abstractlogic_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -105,7 +135,7 @@ end Base.rand(initrng(rng), height, grammar(l), args...; kwargs...) end -"""$(rand_completeflatgrammar_docstring)""" + @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -116,7 +146,6 @@ end randformula(initrng(rng), height, alphabet(g), operators(g), args...; kwargs...) end -"""$(rand_granular_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -153,7 +182,6 @@ end -"""$(sample_aw_docstring)""" @__rng_dispatch function StatsBase.sample( rng::Union{Integer,AbstractRNG}, alphabet::AbstractAlphabet, @@ -169,7 +197,6 @@ end end end -"""$(sample_lao_docstring)""" @__rng_dispatch function StatsBase.sample( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -183,8 +210,7 @@ end initrng(rng), height, grammar(l), atomweights, opweights, args...; kwargs...) end -"""$(sample_hgao_docstring)""" -@__rng_dispatch function StatsBase.sample( + rng::Union{Integer,AbstractRNG}, height::Integer, g::AbstractGrammar, @@ -200,8 +226,7 @@ end -"""$(randformula_docstring)""" -@__rng_dispatch function randformula( + rng::Union{Integer,AbstractRNG}, height::Integer, alphabet::Union{AbstractVector,AbstractAlphabet}, @@ -279,7 +304,8 @@ end return _randformula(rng, height, modaldepth) end -"""$(randformula_hg_docstring)""" + + @__rng_dispatch function randformula( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -290,7 +316,6 @@ end randformula(rng, height, alphabet(g), operators(g), args...; kwargs...) end -"""$(randformula_docstring)""" @__rng_dispatch function randformula( rng::Union{Integer,AbstractRNG}, T::Type{AnchoredFormula}, diff --git a/src/types/syntactical.jl b/src/types/syntactical.jl index b3803944..2739b84d 100644 --- a/src/types/syntactical.jl +++ b/src/types/syntactical.jl @@ -61,7 +61,7 @@ for example, CONJUNCTION, DISJUNCTION, NEGATION and IMPLICATION (stylized as ∧ - `precedence(::Connective)::Int` - `associativity(::Connective)::Symbol` - `collatetruth(::Connective, ::NTuple{N,Truth})::Truth` -- `simplify(::Connective, ::NTuple{N,Truth})::SyntaxTree` +- `simplify(::Connective, ::NTuple{N,SyntaxTree})::SyntaxTree` - `dual(s::Connective)::Connective` - `hasdual(s::Connective)::Bool` - See also [`Syntactical`](@ref) From 5df8e64ca3c9d8940e84feeee6c5b6483d1872c1 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:30:15 +0200 Subject: [PATCH 69/90] Fix CI --- .cirrus.yml | 2 +- .github/workflows/ci.yml | 4 ++-- src/generation/formula.jl | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 0a3671d0..7b73dd3f 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,5 +1,5 @@ freebsd_instance: - image_family: freebsd-14-0 + image_family: freebsd-14-1 task: name: FreeBSD artifacts_cache: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ee86811..d4db6e9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 - - name: Set up Julia 1.9.0 + - name: Set up Julia 1.9 uses: julia-actions/setup-julia@v1 with: - version: "1.9.0" + version: "1.9" - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 08b486d2..306e5529 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -18,25 +18,25 @@ function randformula end function randformula end """$(rand_abstractlogic_docstring)""" -function Base.rand end +function rand end """$(rand_abstractalphabet_docstring)""" -function Base.rand end +function rand end """$(rand_completeflatgrammar_docstring)""" -function Base.rand end +function rand end """$(rand_granular_docstring)""" -function Base.rand end +function rand end """$(sample_aw_docstring)""" -function StatsBase.sample end +function sample end """$(sample_lao_docstring)""" -function StatsBase.sample end +function sample end """$(sample_hgao_docstring)""" -function StatsBase.sample end +function sample end @__rng_dispatch function randatom( From 02186944dbe45e766438dd9ada616d845ca91bb4 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:32:14 +0200 Subject: [PATCH 70/90] Fix --- src/generation/formula.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 306e5529..52eb30a2 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -210,7 +210,7 @@ end initrng(rng), height, grammar(l), atomweights, opweights, args...; kwargs...) end - +@__rng_dispatch function StatsBase.sample( rng::Union{Integer,AbstractRNG}, height::Integer, g::AbstractGrammar, @@ -227,6 +227,7 @@ end +@__rng_dispatch function StatsBase.sample( rng::Union{Integer,AbstractRNG}, height::Integer, alphabet::Union{AbstractVector,AbstractAlphabet}, From e59bdad48d96dc8ebd2eab05aaa1ed8481a5293b Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:24:27 +0200 Subject: [PATCH 71/90] Fix docstrings in formula generation --- src/generation/formula.jl | 49 +++++++++------------------------------ 1 file changed, 11 insertions(+), 38 deletions(-) diff --git a/src/generation/formula.jl b/src/generation/formula.jl index 52eb30a2..6c7cd5d8 100644 --- a/src/generation/formula.jl +++ b/src/generation/formula.jl @@ -6,39 +6,6 @@ import StatsBase: sample # TODO - Move in src/utils.jl (keep here the implementations for struct) """$(randatom_docstring)""" -function randatom end - -"""$(randatom_unionalphabet_docstring)""" -function randatom end - -"""$(randformula_hg_docstring)""" -function randformula end - -"""$(randformula_docstring)""" -function randformula end - -"""$(rand_abstractlogic_docstring)""" -function rand end - -"""$(rand_abstractalphabet_docstring)""" -function rand end - -"""$(rand_completeflatgrammar_docstring)""" -function rand end - -"""$(rand_granular_docstring)""" -function rand end - -"""$(sample_aw_docstring)""" -function sample end - -"""$(sample_lao_docstring)""" -function sample end - -"""$(sample_hgao_docstring)""" -function sample end - - @__rng_dispatch function randatom( rng::Union{Random.AbstractRNG,Integer}, a::AbstractAlphabet, @@ -57,6 +24,7 @@ function sample end end end +"""$(randatom_unionalphabet_docstring)""" @__rng_dispatch function randatom( rng::Union{Integer,AbstractRNG}, a::UnionAlphabet, @@ -108,6 +76,7 @@ end +"""$(rand_abstractalphabet_docstring)""" @__rng_dispatch function SoleLogics.rand( rng::Union{Integer,AbstractRNG}, a::AbstractAlphabet, @@ -125,6 +94,7 @@ function rand(rng::Random.AbstractRNG, a::SoleLogics.AbstractAlphabet, args...; randatom(rng, a, args...; kwargs...) end +"""$(rand_abstractlogic_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -135,7 +105,7 @@ end Base.rand(initrng(rng), height, grammar(l), args...; kwargs...) end - +"""$(rand_completeflatgrammar_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -146,6 +116,7 @@ end randformula(initrng(rng), height, alphabet(g), operators(g), args...; kwargs...) end +"""$(rand_granular_docstring)""" @__rng_dispatch function Base.rand( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -182,6 +153,7 @@ end +"""$(sample_aw_docstring)""" @__rng_dispatch function StatsBase.sample( rng::Union{Integer,AbstractRNG}, alphabet::AbstractAlphabet, @@ -197,6 +169,7 @@ end end end +"""$(sample_lao_docstring)""" @__rng_dispatch function StatsBase.sample( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -210,6 +183,7 @@ end initrng(rng), height, grammar(l), atomweights, opweights, args...; kwargs...) end +"""$(sample_hgao_docstring)""" @__rng_dispatch function StatsBase.sample( rng::Union{Integer,AbstractRNG}, height::Integer, @@ -226,8 +200,8 @@ end - -@__rng_dispatch function StatsBase.sample( +"""$(randformula_docstring)""" +@__rng_dispatch function randformula( rng::Union{Integer,AbstractRNG}, height::Integer, alphabet::Union{AbstractVector,AbstractAlphabet}, @@ -305,8 +279,7 @@ end return _randformula(rng, height, modaldepth) end - - +"""$(randformula_hg_docstring)""" @__rng_dispatch function randformula( rng::Union{Integer,AbstractRNG}, height::Integer, From 31961d322d997ccc4bee123048fdedb61506c53c Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:29:37 +0200 Subject: [PATCH 72/90] bump ver --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 07631399..fb9d50dd 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SoleLogics" uuid = "b002da8f-3cb3-4d91-bbe3-2953433912b5" authors = ["Mauro MILELLA", "Giovanni PAGLIARINI", "Edoardo PONSANESI", "Alberto PAPARELLA", "Eduard I. STAN"] -version = "0.9.5" +version = "0.10.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 0594235624b7d48628c03dbb3f543652984fe8bf Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:51:35 +0200 Subject: [PATCH 73/90] Minor --- src/types/logic.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/types/logic.jl b/src/types/logic.jl index c01d54e3..55fe6125 100644 --- a/src/types/logic.jl +++ b/src/types/logic.jl @@ -146,11 +146,7 @@ function natoms(a::AbstractAlphabet)::Integer end end -# Helper -function Base.length(a::AbstractAlphabet) - @warn "Please use `natoms` instead of `Base.length` with alphabets." - return natoms(a) -end +# [Iteration interface](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-iteration) utils """ Base.iterate(a::AbstractAlphabet) @@ -175,11 +171,14 @@ function Base.iterate(a::AbstractAlphabet, state) end end -# [Iteration interface](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-iteration) util. function Base.IteratorSize(::Type{V}) where {V<:AbstractAlphabet} return Base.isfinite(V) ? Base.HasLength() : Base.IsInfinite() end +function Base.length(a::AbstractAlphabet) + return natoms(a) +end + ############################################################################################ #### AbstractGrammar ####################################################################### ############################################################################################ From 9e5e0e51228fa798aca8b32207ddc16997408012 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:25:46 +0100 Subject: [PATCH 74/90] Renamed syntax-utils into tools and created syntactical-romal-forms --- src/SoleLogics.jl | 49 +- src/utils/syntactical-normal-forms.jl | 609 +++++++++++++++++++++++ src/utils/syntactical.jl | 610 ------------------------ src/utils/{syntax-utils.jl => tools.jl} | 0 test/generation/formula.jl | 4 +- 5 files changed, 633 insertions(+), 639 deletions(-) create mode 100644 src/utils/syntactical-normal-forms.jl rename src/utils/{syntax-utils.jl => tools.jl} (100%) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 68c6bb67..0c172ffe 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -12,7 +12,6 @@ using Lazy using SoleBase using SoleBase: initrng -############################################################################################ export iscrisp, isfinite, isnullary, isunary, isbinary @@ -22,44 +21,42 @@ export Syntactical, Connective, export Operator, SyntaxToken -export syntaxstring +export tree, syntaxstring export arity, valuetype, tokentype, tokenstype, atomstype, operatorstype, truthtype, associativity, precedence -# export value # TODO remove. The name is too generic, and it clashes, e.g., with JuMP.value. export token, children, formulas -export tree export tokens, ntokens, atoms, natoms, truths, ntruths, leaves, nleaves, connectives, nconnectives, operators, noperators, height -export composeformulas + + export composeformulas include("types/syntactical.jl") -############################################################################################ export parseformula include("types/parse.jl") -############################################################################################ + export interpret, check include("types/interpretation.jl") -############################################################################################ +include("types/interpretation-sets.jl") -export AlphabetOfAny, ExplicitAlphabet, UnionAlphabet -export alphabet, alphabets +export AlphabetOfAny, ExplicitAlphabet, UnionAlphabet +export alphabet, subalphabets export domain, top, bot, grammar, algebra, logic include("types/logic.jl") -############################################################################################ + export TOP, ⊤ export BOT, ⊥ @@ -77,7 +74,7 @@ include("utils/syntactical.jl") include("utils/anchored-formula.jl") -############################################################################################ + export propositionallogic @@ -86,7 +83,7 @@ export truth_table include("propositional-logic.jl") -############################################################################################ + export accessibles export ismodal, modallogic @@ -116,11 +113,11 @@ include("types/modal-logic.jl") include("utils/modal-logic.jl") -############################################################################################ + include("many-valued-logics/ManyValuedLogics.jl") -############################################################################################ + export LeftmostLinearForm, LeftmostConjunctiveForm, LeftmostDisjunctiveForm, Literal @@ -128,21 +125,19 @@ export subformulas, normalize export CNF, DNF, cnf -include("utils/syntax-utils.jl") +include("utils/syntactical-normal-forms.jl") + +include("utils/tools.jl") -############################################################################################ -include("types/interpretation-sets.jl") include("utils/interpretation-sets.jl") -############################################################################################ -export parseformula include("utils/parse.jl") -############################################################################################ + # these first files are included here to avoid repeated inclusions in those below; # "generation" could become a SoleLogics submodule. @@ -156,26 +151,26 @@ include("generation/formula.jl") export randframe, randmodel include("generation/models.jl") -############################################################################################ + # export AnchoredFormula # include("anchored-formula.jl") -############################################################################################ + export @atoms, @synexpr include("ui.jl") -############################################################################################ + include("experimentals.jl") -############################################################################################ + include("deprecate.jl") -############################################################################################ + # Fast isempty(intersect(u, v)) function intersects(u, v) for x in u @@ -203,6 +198,6 @@ function displaysyntaxvector(a, maxnum = 8; quotes = true) "$(eltype(a))[$(join(els, ", "))]" end -############################################################################################ + end diff --git a/src/utils/syntactical-normal-forms.jl b/src/utils/syntactical-normal-forms.jl new file mode 100644 index 00000000..225deb49 --- /dev/null +++ b/src/utils/syntactical-normal-forms.jl @@ -0,0 +1,609 @@ + +############################################################################################ +# import Base: show, promote_rule, length, getindex +# using SoleBase + +doc_lmlf = """ + struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure + children::Vector{<:SS} + end + +A syntax structure representing the [`foldl`](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) +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-repl +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} children: + p + q + +julia> tree(conj) +SyntaxBranch: p ∧ q + +julia> nconj = NEGATION(conj) +LeftmostLinearForm with connective ¬ and 1 LeftmostConjunctiveForm{Atom{String}} children: + (p) ∧ (q) + +julia> tree(nconj) +SyntaxBranch: ¬(p ∧ q) + +julia> tree(nconj ∧ nconj) +SyntaxBranch: ¬(p ∧ q) ∧ ¬(p ∧ q) +``` +""" + +"""$(doc_lmlf) + +See also [`SyntaxStructure`](@ref), [`SyntaxTree`](@ref), +[`LeftmostConjunctiveForm`](@ref), [`LeftmostDisjunctiveForm`](@ref), +[`Literal`](@ref). +""" +struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure + children::Vector{SS} + + function LeftmostLinearForm{C,SS}( + children::Vector, + ) where {C<:Connective,SS<:SyntaxStructure} + a = arity(C()) # TODO maybe add member connective::C and use that instead of C() + n_children = length(children) + + length(children) > 0 || error("Cannot instantiate LeftmostLinearForm{$(C)} with no children.") + + if a == 1 + n_children == 1 || + error("Mismatching number of children ($n_children) and connective's arity ($a).") + else + h = (n_children-1)/(a-1) + (isinteger(h) && h >= 0) || + # TODO figure out whether the base case n_children = 0 makes sense + error("Mismatching number of children ($n_children) and connective's arity ($a).") + end + + new{C,SS}(children) + end + + function LeftmostLinearForm{C}(children::AbstractVector{SS}) where {C<:Connective,SS<:SyntaxStructure} + # SS = SoleBase._typejoin(typeof.(children)...) + LeftmostLinearForm{C,SS}(children) + end + + # Ugly!! + function LeftmostLinearForm{C}(children::AbstractVector) where {C<:Connective} + SS = SoleBase._typejoin(typeof.(children)...) + LeftmostLinearForm{C,SS}(children) + end + + function LeftmostLinearForm( + C::Type{<:SoleLogics.Connective}, + children::Vector, + ) + LeftmostLinearForm{C}(children) + end + + function LeftmostLinearForm( + op::Connective, + children::Vector, + ) + LeftmostLinearForm(typeof(op), children) + end + + function LeftmostLinearForm( + tree::SyntaxTree, + c::Union{Nothing,<:SoleLogics.Connective} = nothing + ) + # Check c correctness; it should not be nothing (thus, auto inferred) if + # tree root contains something that is not a connective + if (!(token(tree) isa Connective) && !isnothing(c)) + error("Syntax tree cannot be converted to a LeftmostLinearForm. " * + "tree root is $(token(tree)). " * + "Try specifying a connective as a second argument." + ) + end + + if isnothing(c) + c = token(tree) + end + + # Get a vector of `SyntaxTree`s, having `c` as common ancestor, then, + # call LeftmostLinearForm constructor. + _children = SyntaxStructure[] + + function _dig_and_retrieve(tree::SyntaxTree, c::SoleLogics.Connective) + token(tree) != c ? + push!(_children, tree) : # Lexical scope + for chs in children(tree) + _dig_and_retrieve(chs, c) + end + end + _dig_and_retrieve(tree, c) + + LeftmostLinearForm(c, _children) + end + + function LeftmostLinearForm{C}(tree::SyntaxTree) where {C<:Connective} + LeftmostLinearForm(tree, C()) # TODO avoid + end +end + +children(lf::LeftmostLinearForm) = lf.children +connective(::LeftmostLinearForm{C}) where {C} = C() # TODO avoid using C alone, since it may not be a singleton. + +operatortype(::LeftmostLinearForm{C}) where {C} = C +childrentype(::LeftmostLinearForm{C,SS}) where {C,SS} = SS + +nchildren(lf::LeftmostLinearForm) = length(children(lf)) + + +@forward LeftmostLinearForm.children ( + Base.length, + Base.setindex!, + Base.push!, + Base.iterate, Base.IteratorSize, Base.IteratorEltype, + Base.firstindex, Base.lastindex, + Base.keys, Base.values, +) + +# function Base.getindex(lf::LeftmostLinearForm{C,SS}, idxs::AbstractVector) where {C,SS} + # return LeftmostLinearForm{C,SS}(children(lf)[idxs]) +# end +# Base.getindex(lf::LeftmostLinearForm, idx::Integer) = Base.getindex(lf,[idx]) +function Base.getindex(lf::LeftmostLinearForm, idxs::AbstractVector) + return LeftmostLinearForm(children(lf)[idxs]) +end +Base.getindex(lf::LeftmostLinearForm, idx::Integer) = Base.getindex(children(lf),idx) +Base.push!(lf::LeftmostLinearForm, el) = Base.push!(children(lf), el) + +function composeformulas(c::Connective, φs::NTuple{N,LeftmostLinearForm}) where {N} + # @show φs + if all(_c->_c == c, connective.(φs)) # If operator is the same, collapse children TODO and operator is ... associative? + return LeftmostLinearForm(c, collect(Iterators.flatten(children.(φs)))) + # return LeftmostLinearForm(c, reduce(vcat,children.(φs))) + else + return LeftmostLinearForm(c, collect(φs)) + end +end + +# TODO: add parameter remove_redundant_parentheses +# TODO: add parameter parenthesize_atoms +function syntaxstring( + lf::LeftmostLinearForm; + function_notation = false, + kwargs..., +) + if function_notation + syntaxstring(tree(lf); function_notation = function_notation, kwargs...) + else + chs = children(lf) + children_ss = map( + c->syntaxstring(c; kwargs...), + chs + ) + "(" * join(children_ss, ") $(syntaxstring(connective(lf); kwargs...)) (") * ")" + end +end + +function tree(lf::LeftmostLinearForm) + c = connective(lf) + a = arity(c) + chs = children(lf) + + st = begin + if length(chs) == 1 # Only child + if a == 1 + c(tree(first(chs))) + else + tree(first(chs)) + end + else + function _tree(φs::Vector{<:SyntaxTree}) + @assert (length(φs) != 0) "$(φs); $(lf); $(c); $(a)." + return length(φs) == a ? + SyntaxTree(c, φs...) : + SyntaxTree(c, φs[1:(a-1)]..., _tree(φs[a:end])) # Left-most unwinding + end + _tree(tree.(chs)) + end + end + + return st +end + +function Base.show(io::IO, lf::LeftmostLinearForm{C,SS}) where {C,SS} + if lf isa CNF + print(io, "CNF with") + println(io, " $(nconjuncts(lf)) conjuncts:") + L = literaltype(lf) + L <: Literal || println(io, " $(nconjuncts(lf)) and literals of type $(L):") + elseif lf isa DNF + print(io, "DNF with") + println(io, " $(ndisjuncts(lf)) disjuncts:") + L = literaltype(lf) + L <: Literal || println(io, " $(ndisjuncts(lf)) and literals of type $(L):") + else + if lf isa LeftmostConjunctiveForm + print(io, "LeftmostConjunctiveForm with") + elseif lf isa LeftmostDisjunctiveForm + print(io, "LeftmostDisjunctiveForm with") + else + print(io, "LeftmostLinearForm with connective $(syntaxstring(connective(lf))) and") + end + println(io, " $(nchildren(lf)) $((SS == SyntaxStructure ? "" : "$(SS) "))children:") + end + # println(io, "\t$(join(syntaxstring.(children(lf)), " $(syntaxstring(connective(lf))) \n\t"))") + println(io, "\t$(join(syntaxstring.(children(lf)), "\n\t"))") +end + +# TODO fix +Base.promote_rule(::Type{<:LeftmostLinearForm}, ::Type{<:LeftmostLinearForm}) = SyntaxTree +Base.promote_rule(::Type{SS}, ::Type{LF}) where {SS<:SyntaxStructure,LF<:LeftmostLinearForm} = SyntaxTree +Base.promote_rule(::Type{LF}, ::Type{SS}) where {LF<:LeftmostLinearForm,SS<:SyntaxStructure} = SyntaxTree + +function Base.in(tok::SyntaxToken, φ::LeftmostLinearForm)::Bool + return (tok isa Connective && connective(φ) == tok) || + any(c->Base.in(tok, c), children(φ)) +end + +function Base.in(tok::SyntaxLeaf, φ::LeftmostLinearForm{C,<:SyntaxLeaf})::Bool where {C<:Connective} + return Base.in(tok, children(φ)) +end + + +atoms(φ::LeftmostLinearForm) = Iterators.flatten(Iterators.map(atoms, children(φ))) +leaves(φ::LeftmostLinearForm) = Iterators.flatten(Iterators.map(leaves, children(φ))) + +natoms(φ::LeftmostLinearForm) = sum(natoms, children(φ)) +nleaves(φ::LeftmostLinearForm) = sum(nleaves, children(φ)) + +# function tokens(φ::LeftmostLinearForm) +# # return TODO +# end + +function atoms(φ::LeftmostLinearForm{C,<:Atom})::Vector{Atom} where {C<:Connective} + return children(φ) +end + +# function connectives(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} +# # return TODO +# end + +# function operators(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} +# # return TODO +# end + +function leaves(φ::LeftmostLinearForm{C,<:SyntaxLeaf})::SyntaxLeaf where {C<:Connective} + return children(φ) +end + +# function ntokens(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} +# # return TODO +# end + +function natoms(φ::LeftmostLinearForm{C,<:Atom})::Integer where {C<:Connective} + return nchildren(φ) +end + +# function nconnectives(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} +# # return TODO +# end + +# function noperators(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} +# # return TODO +# end + +function nleaves(φ::LeftmostLinearForm{C,<:SyntaxLeaf})::Integer where {C<:Connective} + return nchildren(φ) +end + +Base.promote_rule(::Type{LF}, ::Type{SS}) where {LF<:LeftmostLinearForm,SS<:SyntaxTree} = SyntaxTree +Base.promote_rule(::Type{SS}, ::Type{LF}) where {LF<:LeftmostLinearForm,SS<:SyntaxTree} = SyntaxTree + +############################################################################################ + +# TODO actually: +# const CNF{SS<:SyntaxStructure} = Union{LeftmostLinearForm{typeof(∧),LeftmostLinearForm{typeof(∨),SS}},LeftmostLinearForm{typeof(∨),SS}} +# const DNF{SS<:SyntaxStructure} = Union{LeftmostLinearForm{typeof(∨),LeftmostLinearForm{typeof(∧),SS}},LeftmostLinearForm{typeof(∧),SS}} + +""" + LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} + +Specific instantiation of a [`LeftmostLinearForm`](@ref), where [`Connective`](@ref)s are +all [`CONJUNCTION`](@ref)s. + +See also [`SyntaxStructure`](@ref), [`Connective`](@ref), [`LeftmostLinearForm`](@ref), +[`CONJUNCTION`](@ref). +""" +const LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} + +function check( + φ::LeftmostConjunctiveForm, + args...; + kwargs... +) + return all(ch -> check(ch, args...; kwargs...), children(φ)) +end + +function check( + φ::LeftmostConjunctiveForm, + i::AbstractInterpretation, + args...; + kwargs... +) + return all(ch -> check(ch, i, args...; kwargs...), children(φ)) +end + +""" + LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} + +Specific instantiation of a [`LeftmostLinearForm`](@ref), where [`Connective`](@ref)s are +all [`DISJUNCTION`](@ref)s. + +See also [`SyntaxStructure`](@ref), [`Connective`](@ref), +[`LeftmostLinearForm`](@ref), [`DISJUNCTION`](@ref). +""" +const LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} + +function check( + φ::LeftmostDisjunctiveForm, + args...; + kwargs... +) + return any(ch -> check(ch, args...; kwargs...), children(φ)) +end + +function check( + φ::LeftmostDisjunctiveForm, + i::AbstractInterpretation, + args...; + kwargs... +) + return any(ch -> check(ch, i, args...; kwargs...), children(φ)) +end + +""" + CNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} + +Conjunctive Normal Form of an [`SyntaxStructure`](@ref). + +See also [`SyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), +[`LeftmostDisjunctiveForm`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref). +""" +const CNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} + +function check( + φ::CNF, + args...; + kwargs... +) + return all(ch -> any(grandch -> check(grandch, args...; kwargs...), children(ch)), children(φ)) +end + +function check( + φ::CNF, + i::AbstractInterpretation, + args...; + kwargs... +) + return all(ch -> any(grandch -> check(grandch, i, args...; kwargs...), children(ch)), children(φ)) +end + +""" + DNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostConjunctiveForm{SS}} + +Disjunctive Normal Form of an [`SyntaxStructure`](@ref). + +See also [`SyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), +[`LeftmostDisjunctiveForm`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref). +""" +const DNF{SS<:SyntaxStructure} = LeftmostDisjunctiveForm{LeftmostConjunctiveForm{SS}} + +function check( + φ::DNF, + args...; + kwargs... +) + return any(ch -> all(grandch -> check(grandch, args...; kwargs...), children(ch)), children(φ)) +end + +function check( + φ::DNF, + i::AbstractInterpretation, + args...; + kwargs... +) + return any(ch -> all(grandch -> check(grandch, i, args...; kwargs...), children(ch)), children(φ)) +end + +# Helpers +function CNF(conjuncts::AbstractVector{<:LeftmostDisjunctiveForm}) + SS = Union{childrentype.(conjuncts)...} + return CNF{SS}(conjuncts) +end +function DNF(disjuncts::AbstractVector{<:LeftmostConjunctiveForm}) + SS = Union{childrentype.(disjuncts)...} + return DNF{SS}(disjuncts) +end +CNF(conjuncts::NTuple{N,<:LeftmostDisjunctiveForm}) where {N} = CNF(collect(conjuncts)) +DNF(disjuncts::NTuple{N,<:LeftmostConjunctiveForm}) where {N} = DNF(collect(disjuncts)) +CNF(conjuncts::Vararg{LeftmostDisjunctiveForm}) = CNF(collect(conjuncts)) +DNF(disjuncts::Vararg{LeftmostConjunctiveForm}) = DNF(collect(disjuncts)) +CNF(conjunct::LeftmostDisjunctiveForm) = CNF([conjunct]) +DNF(disjunct::LeftmostConjunctiveForm) = DNF([disjunct]) + +function CNF(φ::SyntaxLeaf) + return LeftmostConjunctiveForm([LeftmostDisjunctiveForm([φ])]) +end + +function DNF(φ::SyntaxLeaf) + return LeftmostDisjunctiveForm([LeftmostConjunctiveForm([φ])]) +end + +function CNF(φ::SyntaxBranch) + return erorr("TODO") +end + +function DNF(φ::SyntaxBranch) + return erorr("TODO") +end + + +literaltype(::CNF{SS}) where {SS<:SyntaxStructure} = SS +literaltype(::DNF{SS}) where {SS<:SyntaxStructure} = SS + +# # TODO maybe not needed? +# Base.promote_rule(::Type{<:LeftmostConjunctiveForm}, ::Type{<:LeftmostConjunctiveForm}) = LeftmostConjunctiveForm +# Base.promote_rule(::Type{<:LeftmostDisjunctiveForm}, ::Type{<:LeftmostDisjunctiveForm}) = LeftmostDisjunctiveForm +# Base.promote_rule(::Type{<:LeftmostConjunctiveForm}, ::Type{<:LeftmostDisjunctiveForm}) = SyntaxTree + +conjuncts(φ::LeftmostConjunctiveForm) = children(φ) +nconjuncts(φ::LeftmostConjunctiveForm) = nchildren(φ) +pushconjunct!(φ::LeftmostLinearForm, el) = Base.push!(children(φ), el) + +disjuncts(φ::LeftmostDisjunctiveForm) = children(φ) +ndisjuncts(φ::LeftmostDisjunctiveForm) = nchildren(φ) +pushdisjunct(φ::LeftmostDisjunctiveForm, el) = Base.push!(children(φ), el) + +# conjuncts(φ::DNF) = map(d->conjuncts(d), disjuncts(φ)) +# nconjuncts(φ::DNF) = map(d->nconjuncts(d), disjuncts(φ)) +# disjuncts(φ::CNF) = map(d->disjuncts(d), conjuncts(φ)) +# ndisjuncts(φ::CNF) = map(d->ndisjuncts(d), conjuncts(φ)) + + +############################################################################################ + +""" + struct Literal{T<:SyntaxLeaf} <: SyntaxStructure + ispos::Bool + prop::T + end + +An atom, or its negation. + +See also [`CNF`](@ref), [`DNF`](@ref), [`SyntaxStructure`](@ref). +""" +struct Literal{T<:SyntaxLeaf} <: SyntaxStructure + ispos::Bool + prop::T + + function Literal{T}( + ispos::Bool, + prop::T, + ) where {T<:SyntaxLeaf} + new{T}(ispos, prop) + end + + function Literal( + ispos::Bool, + prop::T, + ) where {T<:SyntaxLeaf} + Literal{T}(ispos, prop) + end + + function Literal(φ::SyntaxLeaf, flag = true) + return Literal(flag, φ) + end + function Literal(φ::SyntaxBranch, flag = true) + ch = first(children(φ)) + @assert (token(φ) == ¬) "Cannot " * + "construct Literal with formula of type $(typeof(ch))): $(syntaxstring(ch))." + return Literal(ch, !flag) + end +end + +ispos(l::Literal) = l.ispos +prop(l::Literal) = l.prop + +atomstype(::Literal{T}) where {T} = T + +tree(l::Literal) = ispos(l) ? l.prop : ¬(l.prop) + +hasdual(l::Literal) = true +dual(l::Literal) = Literal(!ispos(l), prop(l)) + +function Base.show(io::IO, l::Literal) + println(io, + "Literal{$(atomstype(l))}: " * (ispos(l) ? "" : "¬") * syntaxstring(prop(l)) + ) +end + +############################################################################################ +# CNF conversion +############################################################################################ + +""" + cnf(φ::Formula, literaltype = Literal; kwargs...) + + TODO docstring. Converts to cnf form ([`CNF`](@ref)). + `CNF{literaltype}` + Additional `kwargs` are passed to [`normalize`](@ref) +""" +function cnf(φ::Formula, literaltype = Literal; kwargs...) + return _cnf(normalize(φ; profile = :nnf, kwargs...), literaltype) end + +function cnf(φ::CNF{T}, literaltype = Literal; kwargs...) where {T<:SyntaxStructure} + if T == literaltype + return φ + else + return cnf(tree(φ), literaltype; kwargs...) + end +end + +function cnf(φ::DNF, args...; kwargs...) + return cnf(tree(φ), args...; kwargs...) +end + + +function _cnf(φ::Formula, literaltype = Literal) + return error("Cannot convert to CNF formula of type $(typeof(φ)): $(syntaxstring(φ))") +end + +function _cnf(φ::SyntaxLeaf, literaltype = Literal) + φ = φ isa literaltype ? φ : literaltype(φ) + return LeftmostConjunctiveForm([LeftmostDisjunctiveForm{literaltype}([φ])]) +end + +function _cnf(φ::SyntaxBranch, literaltype = Literal) + if token(φ) == ∧ + return _cnf(first(children(φ)), literaltype) ∧ _cnf(last(children(φ)), literaltype) + elseif token(φ) == ∨ + conjs = vec([begin + # @show typeof(c1), typeof(c2) + # @show typeof(c1 ∨ c2) + # LeftmostDisjunctiveForm{literaltype}(c1 ∨ c2) + c1 ∨ c2 + end for (c1,c2) in Iterators.product(conjuncts(_cnf(first(children(φ)), literaltype)),conjuncts(_cnf(last(children(φ)), literaltype)))]) + # @show typeof.(conjs) + # conjs = Vector{LeftmostDisjunctiveForm{literaltype}}(conjs) + return LeftmostConjunctiveForm(conjs) + elseif token(φ) == ¬ + φ = φ isa literaltype ? φ : literaltype(φ) + return LeftmostConjunctiveForm([LeftmostDisjunctiveForm{literaltype}([φ])]) + else + return error("Unexpected token $(token)!") + end +end + +############################################################################################ diff --git a/src/utils/syntactical.jl b/src/utils/syntactical.jl index 9d1ad5a3..6c02bee7 100644 --- a/src/utils/syntactical.jl +++ b/src/utils/syntactical.jl @@ -875,613 +875,3 @@ function _baselogic(; return BaseLogic(grammar, algebra) end - -############################################################################################ -# import Base: show, promote_rule, length, getindex -# using SoleBase - -doc_lmlf = """ - struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure - children::Vector{<:SS} - end - -A syntax structure representing the [`foldl`](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) -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-repl -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} children: - p - q - -julia> tree(conj) -SyntaxBranch: p ∧ q - -julia> nconj = NEGATION(conj) -LeftmostLinearForm with connective ¬ and 1 LeftmostConjunctiveForm{Atom{String}} children: - (p) ∧ (q) - -julia> tree(nconj) -SyntaxBranch: ¬(p ∧ q) - -julia> tree(nconj ∧ nconj) -SyntaxBranch: ¬(p ∧ q) ∧ ¬(p ∧ q) -``` -""" - -"""$(doc_lmlf) - -See also [`SyntaxStructure`](@ref), [`SyntaxTree`](@ref), -[`LeftmostConjunctiveForm`](@ref), [`LeftmostDisjunctiveForm`](@ref), -[`Literal`](@ref). -""" -struct LeftmostLinearForm{C<:Connective,SS<:SyntaxStructure} <: SyntaxStructure - children::Vector{SS} - - function LeftmostLinearForm{C,SS}( - children::Vector, - ) where {C<:Connective,SS<:SyntaxStructure} - a = arity(C()) # TODO maybe add member connective::C and use that instead of C() - n_children = length(children) - - length(children) > 0 || error("Cannot instantiate LeftmostLinearForm{$(C)} with no children.") - - if a == 1 - n_children == 1 || - error("Mismatching number of children ($n_children) and connective's arity ($a).") - else - h = (n_children-1)/(a-1) - (isinteger(h) && h >= 0) || - # TODO figure out whether the base case n_children = 0 makes sense - error("Mismatching number of children ($n_children) and connective's arity ($a).") - end - - new{C,SS}(children) - end - - function LeftmostLinearForm{C}(children::AbstractVector{SS}) where {C<:Connective,SS<:SyntaxStructure} - # SS = SoleBase._typejoin(typeof.(children)...) - LeftmostLinearForm{C,SS}(children) - end - - # Ugly!! - function LeftmostLinearForm{C}(children::AbstractVector) where {C<:Connective} - SS = SoleBase._typejoin(typeof.(children)...) - LeftmostLinearForm{C,SS}(children) - end - - function LeftmostLinearForm( - C::Type{<:SoleLogics.Connective}, - children::Vector, - ) - LeftmostLinearForm{C}(children) - end - - function LeftmostLinearForm( - op::Connective, - children::Vector, - ) - LeftmostLinearForm(typeof(op), children) - end - - function LeftmostLinearForm( - tree::SyntaxTree, - c::Union{Nothing,<:SoleLogics.Connective} = nothing - ) - # Check c correctness; it should not be nothing (thus, auto inferred) if - # tree root contains something that is not a connective - if (!(token(tree) isa Connective) && !isnothing(c)) - error("Syntax tree cannot be converted to a LeftmostLinearForm. " * - "tree root is $(token(tree)). " * - "Try specifying a connective as a second argument." - ) - end - - if isnothing(c) - c = token(tree) - end - - # Get a vector of `SyntaxTree`s, having `c` as common ancestor, then, - # call LeftmostLinearForm constructor. - _children = SyntaxStructure[] - - function _dig_and_retrieve(tree::SyntaxTree, c::SoleLogics.Connective) - token(tree) != c ? - push!(_children, tree) : # Lexical scope - for chs in children(tree) - _dig_and_retrieve(chs, c) - end - end - _dig_and_retrieve(tree, c) - - LeftmostLinearForm(c, _children) - end - - function LeftmostLinearForm{C}(tree::SyntaxTree) where {C<:Connective} - LeftmostLinearForm(tree, C()) # TODO avoid - end -end - -children(lf::LeftmostLinearForm) = lf.children -connective(::LeftmostLinearForm{C}) where {C} = C() # TODO avoid using C alone, since it may not be a singleton. - -operatortype(::LeftmostLinearForm{C}) where {C} = C -childrentype(::LeftmostLinearForm{C,SS}) where {C,SS} = SS - -nchildren(lf::LeftmostLinearForm) = length(children(lf)) - - -@forward LeftmostLinearForm.children ( - Base.length, - Base.setindex!, - Base.push!, - Base.iterate, Base.IteratorSize, Base.IteratorEltype, - Base.firstindex, Base.lastindex, - Base.keys, Base.values, -) - -# function Base.getindex(lf::LeftmostLinearForm{C,SS}, idxs::AbstractVector) where {C,SS} - # return LeftmostLinearForm{C,SS}(children(lf)[idxs]) -# end -# Base.getindex(lf::LeftmostLinearForm, idx::Integer) = Base.getindex(lf,[idx]) -function Base.getindex(lf::LeftmostLinearForm, idxs::AbstractVector) - return LeftmostLinearForm(children(lf)[idxs]) -end -Base.getindex(lf::LeftmostLinearForm, idx::Integer) = Base.getindex(children(lf),idx) -Base.push!(lf::LeftmostLinearForm, el) = Base.push!(children(lf), el) - -function composeformulas(c::Connective, φs::NTuple{N,LeftmostLinearForm}) where {N} - # @show φs - if all(_c->_c == c, connective.(φs)) # If operator is the same, collapse children TODO and operator is ... associative? - return LeftmostLinearForm(c, collect(Iterators.flatten(children.(φs)))) - # return LeftmostLinearForm(c, reduce(vcat,children.(φs))) - else - return LeftmostLinearForm(c, collect(φs)) - end -end - -# TODO: add parameter remove_redundant_parentheses -# TODO: add parameter parenthesize_atoms -function syntaxstring( - lf::LeftmostLinearForm; - function_notation = false, - kwargs..., -) - if function_notation - syntaxstring(tree(lf); function_notation = function_notation, kwargs...) - else - chs = children(lf) - children_ss = map( - c->syntaxstring(c; kwargs...), - chs - ) - "(" * join(children_ss, ") $(syntaxstring(connective(lf); kwargs...)) (") * ")" - end -end - -function tree(lf::LeftmostLinearForm) - c = connective(lf) - a = arity(c) - chs = children(lf) - - st = begin - if length(chs) == 1 # Only child - if a == 1 - c(tree(first(chs))) - else - tree(first(chs)) - end - else - function _tree(φs::Vector{<:SyntaxTree}) - @assert (length(φs) != 0) "$(φs); $(lf); $(c); $(a)." - return length(φs) == a ? - SyntaxTree(c, φs...) : - SyntaxTree(c, φs[1:(a-1)]..., _tree(φs[a:end])) # Left-most unwinding - end - _tree(tree.(chs)) - end - end - - return st -end - -function Base.show(io::IO, lf::LeftmostLinearForm{C,SS}) where {C,SS} - if lf isa CNF - print(io, "CNF with") - println(io, " $(nconjuncts(lf)) conjuncts:") - L = literaltype(lf) - L <: Literal || println(io, " $(nconjuncts(lf)) and literals of type $(L):") - elseif lf isa DNF - print(io, "DNF with") - println(io, " $(ndisjuncts(lf)) disjuncts:") - L = literaltype(lf) - L <: Literal || println(io, " $(ndisjuncts(lf)) and literals of type $(L):") - else - if lf isa LeftmostConjunctiveForm - print(io, "LeftmostConjunctiveForm with") - elseif lf isa LeftmostDisjunctiveForm - print(io, "LeftmostDisjunctiveForm with") - else - print(io, "LeftmostLinearForm with connective $(syntaxstring(connective(lf))) and") - end - println(io, " $(nchildren(lf)) $((SS == SyntaxStructure ? "" : "$(SS) "))children:") - end - # println(io, "\t$(join(syntaxstring.(children(lf)), " $(syntaxstring(connective(lf))) \n\t"))") - println(io, "\t$(join(syntaxstring.(children(lf)), "\n\t"))") -end - -# TODO fix -Base.promote_rule(::Type{<:LeftmostLinearForm}, ::Type{<:LeftmostLinearForm}) = SyntaxTree -Base.promote_rule(::Type{SS}, ::Type{LF}) where {SS<:SyntaxStructure,LF<:LeftmostLinearForm} = SyntaxTree -Base.promote_rule(::Type{LF}, ::Type{SS}) where {LF<:LeftmostLinearForm,SS<:SyntaxStructure} = SyntaxTree - -function Base.in(tok::SyntaxToken, φ::LeftmostLinearForm)::Bool - return (tok isa Connective && connective(φ) == tok) || - any(c->Base.in(tok, c), children(φ)) -end - -function Base.in(tok::SyntaxLeaf, φ::LeftmostLinearForm{C,<:SyntaxLeaf})::Bool where {C<:Connective} - return Base.in(tok, children(φ)) -end - - -atoms(φ::LeftmostLinearForm) = Iterators.flatten(Iterators.map(atoms, children(φ))) -leaves(φ::LeftmostLinearForm) = Iterators.flatten(Iterators.map(leaves, children(φ))) - -natoms(φ::LeftmostLinearForm) = sum(natoms, children(φ)) -nleaves(φ::LeftmostLinearForm) = sum(nleaves, children(φ)) - -# function tokens(φ::LeftmostLinearForm) -# # return TODO -# end - -function atoms(φ::LeftmostLinearForm{C,<:Atom})::Vector{Atom} where {C<:Connective} - return children(φ) -end - -# function connectives(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} -# # return TODO -# end - -# function operators(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} -# # return TODO -# end - -function leaves(φ::LeftmostLinearForm{C,<:SyntaxLeaf})::SyntaxLeaf where {C<:Connective} - return children(φ) -end - -# function ntokens(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} -# # return TODO -# end - -function natoms(φ::LeftmostLinearForm{C,<:Atom})::Integer where {C<:Connective} - return nchildren(φ) -end - -# function nconnectives(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} -# # return TODO -# end - -# function noperators(φ::LeftmostLinearForm{C,<:Atom})::Bool where {C<:Connective} -# # return TODO -# end - -function nleaves(φ::LeftmostLinearForm{C,<:SyntaxLeaf})::Integer where {C<:Connective} - return nchildren(φ) -end - -Base.promote_rule(::Type{LF}, ::Type{SS}) where {LF<:LeftmostLinearForm,SS<:SyntaxTree} = SyntaxTree -Base.promote_rule(::Type{SS}, ::Type{LF}) where {LF<:LeftmostLinearForm,SS<:SyntaxTree} = SyntaxTree - -############################################################################################ - -# TODO actually: -# const CNF{SS<:SyntaxStructure} = Union{LeftmostLinearForm{typeof(∧),LeftmostLinearForm{typeof(∨),SS}},LeftmostLinearForm{typeof(∨),SS}} -# const DNF{SS<:SyntaxStructure} = Union{LeftmostLinearForm{typeof(∨),LeftmostLinearForm{typeof(∧),SS}},LeftmostLinearForm{typeof(∧),SS}} - -""" - LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} - -Specific instantiation of a [`LeftmostLinearForm`](@ref), where [`Connective`](@ref)s are -all [`CONJUNCTION`](@ref)s. - -See also [`SyntaxStructure`](@ref), [`Connective`](@ref), [`LeftmostLinearForm`](@ref), -[`CONJUNCTION`](@ref). -""" -const LeftmostConjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∧),SS} - -function check( - φ::LeftmostConjunctiveForm, - args...; - kwargs... -) - return all(ch -> check(ch, args...; kwargs...), children(φ)) -end - -function check( - φ::LeftmostConjunctiveForm, - i::AbstractInterpretation, - args...; - kwargs... -) - return all(ch -> check(ch, i, args...; kwargs...), children(φ)) -end - -""" - LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} - -Specific instantiation of a [`LeftmostLinearForm`](@ref), where [`Connective`](@ref)s are -all [`DISJUNCTION`](@ref)s. - -See also [`SyntaxStructure`](@ref), [`Connective`](@ref), -[`LeftmostLinearForm`](@ref), [`DISJUNCTION`](@ref). -""" -const LeftmostDisjunctiveForm{SS<:SyntaxStructure} = LeftmostLinearForm{typeof(∨),SS} - -function check( - φ::LeftmostDisjunctiveForm, - args...; - kwargs... -) - return any(ch -> check(ch, args...; kwargs...), children(φ)) -end - -function check( - φ::LeftmostDisjunctiveForm, - i::AbstractInterpretation, - args...; - kwargs... -) - return any(ch -> check(ch, i, args...; kwargs...), children(φ)) -end - -""" - CNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} - -Conjunctive Normal Form of an [`SyntaxStructure`](@ref). - -See also [`SyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), -[`LeftmostDisjunctiveForm`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref). -""" -const CNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostDisjunctiveForm{SS}} - -function check( - φ::CNF, - args...; - kwargs... -) - return all(ch -> any(grandch -> check(grandch, args...; kwargs...), children(ch)), children(φ)) -end - -function check( - φ::CNF, - i::AbstractInterpretation, - args...; - kwargs... -) - return all(ch -> any(grandch -> check(grandch, i, args...; kwargs...), children(ch)), children(φ)) -end - -""" - DNF{SS<:SyntaxStructure} = LeftmostConjunctiveForm{LeftmostConjunctiveForm{SS}} - -Disjunctive Normal Form of an [`SyntaxStructure`](@ref). - -See also [`SyntaxStructure`](@ref), [`LeftmostConjunctiveForm`](@ref), -[`LeftmostDisjunctiveForm`](@ref), [`CONJUNCTION`](@ref), [`DISJUNCTION`](@ref). -""" -const DNF{SS<:SyntaxStructure} = LeftmostDisjunctiveForm{LeftmostConjunctiveForm{SS}} - -function check( - φ::DNF, - args...; - kwargs... -) - return any(ch -> all(grandch -> check(grandch, args...; kwargs...), children(ch)), children(φ)) -end - -function check( - φ::DNF, - i::AbstractInterpretation, - args...; - kwargs... -) - return any(ch -> all(grandch -> check(grandch, i, args...; kwargs...), children(ch)), children(φ)) -end - -# Helpers -function CNF(conjuncts::AbstractVector{<:LeftmostDisjunctiveForm}) - SS = Union{childrentype.(conjuncts)...} - return CNF{SS}(conjuncts) -end -function DNF(disjuncts::AbstractVector{<:LeftmostConjunctiveForm}) - SS = Union{childrentype.(disjuncts)...} - return DNF{SS}(disjuncts) -end -CNF(conjuncts::NTuple{N,<:LeftmostDisjunctiveForm}) where {N} = CNF(collect(conjuncts)) -DNF(disjuncts::NTuple{N,<:LeftmostConjunctiveForm}) where {N} = DNF(collect(disjuncts)) -CNF(conjuncts::Vararg{LeftmostDisjunctiveForm}) = CNF(collect(conjuncts)) -DNF(disjuncts::Vararg{LeftmostConjunctiveForm}) = DNF(collect(disjuncts)) -CNF(conjunct::LeftmostDisjunctiveForm) = CNF([conjunct]) -DNF(disjunct::LeftmostConjunctiveForm) = DNF([disjunct]) - -function CNF(φ::SyntaxLeaf) - return LeftmostConjunctiveForm([LeftmostDisjunctiveForm([φ])]) -end - -function DNF(φ::SyntaxLeaf) - return LeftmostDisjunctiveForm([LeftmostConjunctiveForm([φ])]) -end - -function CNF(φ::SyntaxBranch) - return erorr("TODO") -end - -function DNF(φ::SyntaxBranch) - return erorr("TODO") -end - - -literaltype(::CNF{SS}) where {SS<:SyntaxStructure} = SS -literaltype(::DNF{SS}) where {SS<:SyntaxStructure} = SS - -# # TODO maybe not needed? -# Base.promote_rule(::Type{<:LeftmostConjunctiveForm}, ::Type{<:LeftmostConjunctiveForm}) = LeftmostConjunctiveForm -# Base.promote_rule(::Type{<:LeftmostDisjunctiveForm}, ::Type{<:LeftmostDisjunctiveForm}) = LeftmostDisjunctiveForm -# Base.promote_rule(::Type{<:LeftmostConjunctiveForm}, ::Type{<:LeftmostDisjunctiveForm}) = SyntaxTree - -conjuncts(φ::LeftmostConjunctiveForm) = children(φ) -nconjuncts(φ::LeftmostConjunctiveForm) = nchildren(φ) -pushconjunct!(φ::LeftmostLinearForm, el) = Base.push!(children(φ), el) - -disjuncts(φ::LeftmostDisjunctiveForm) = children(φ) -ndisjuncts(φ::LeftmostDisjunctiveForm) = nchildren(φ) -pushdisjunct(φ::LeftmostDisjunctiveForm, el) = Base.push!(children(φ), el) - -# conjuncts(φ::DNF) = map(d->conjuncts(d), disjuncts(φ)) -# nconjuncts(φ::DNF) = map(d->nconjuncts(d), disjuncts(φ)) -# disjuncts(φ::CNF) = map(d->disjuncts(d), conjuncts(φ)) -# ndisjuncts(φ::CNF) = map(d->ndisjuncts(d), conjuncts(φ)) - - -############################################################################################ - -""" - struct Literal{T<:SyntaxLeaf} <: SyntaxStructure - ispos::Bool - prop::T - end - -An atom, or its negation. - -See also [`CNF`](@ref), [`DNF`](@ref), [`SyntaxStructure`](@ref). -""" -struct Literal{T<:SyntaxLeaf} <: SyntaxStructure - ispos::Bool - prop::T - - function Literal{T}( - ispos::Bool, - prop::T, - ) where {T<:SyntaxLeaf} - new{T}(ispos, prop) - end - - function Literal( - ispos::Bool, - prop::T, - ) where {T<:SyntaxLeaf} - Literal{T}(ispos, prop) - end - - function Literal(φ::SyntaxLeaf, flag = true) - return Literal(flag, φ) - end - function Literal(φ::SyntaxBranch, flag = true) - ch = first(children(φ)) - @assert (token(φ) == ¬) "Cannot " * - "construct Literal with formula of type $(typeof(ch))): $(syntaxstring(ch))." - return Literal(ch, !flag) - end -end - -ispos(l::Literal) = l.ispos -prop(l::Literal) = l.prop - -atomstype(::Literal{T}) where {T} = T - -tree(l::Literal) = ispos(l) ? l.prop : ¬(l.prop) - -hasdual(l::Literal) = true -dual(l::Literal) = Literal(!ispos(l), prop(l)) - -function Base.show(io::IO, l::Literal) - println(io, - "Literal{$(atomstype(l))}: " * (ispos(l) ? "" : "¬") * syntaxstring(prop(l)) - ) -end - -############################################################################################ -# CNF conversion -############################################################################################ - -""" - cnf(φ::Formula, literaltype = Literal; kwargs...) - - TODO docstring. Converts to cnf form ([`CNF`](@ref)). - `CNF{literaltype}` - Additional `kwargs` are passed to [`normalize`](@ref) -""" -function cnf(φ::Formula, literaltype = Literal; kwargs...) - return _cnf(normalize(φ; profile = :nnf, kwargs...), literaltype) -end - -function cnf(φ::CNF{T}, literaltype = Literal; kwargs...) where {T<:SyntaxStructure} - if T == literaltype - return φ - else - return cnf(tree(φ), literaltype; kwargs...) - end -end - -function cnf(φ::DNF, args...; kwargs...) - return cnf(tree(φ), args...; kwargs...) -end - - -function _cnf(φ::Formula, literaltype = Literal) - return error("Cannot convert to CNF formula of type $(typeof(φ)): $(syntaxstring(φ))") -end - -function _cnf(φ::SyntaxLeaf, literaltype = Literal) - φ = φ isa literaltype ? φ : literaltype(φ) - return LeftmostConjunctiveForm([LeftmostDisjunctiveForm{literaltype}([φ])]) -end - -function _cnf(φ::SyntaxBranch, literaltype = Literal) - if token(φ) == ∧ - return _cnf(first(children(φ)), literaltype) ∧ _cnf(last(children(φ)), literaltype) - elseif token(φ) == ∨ - conjs = vec([begin - # @show typeof(c1), typeof(c2) - # @show typeof(c1 ∨ c2) - # LeftmostDisjunctiveForm{literaltype}(c1 ∨ c2) - c1 ∨ c2 - end for (c1,c2) in Iterators.product(conjuncts(_cnf(first(children(φ)), literaltype)),conjuncts(_cnf(last(children(φ)), literaltype)))]) - # @show typeof.(conjs) - # conjs = Vector{LeftmostDisjunctiveForm{literaltype}}(conjs) - return LeftmostConjunctiveForm(conjs) - elseif token(φ) == ¬ - φ = φ isa literaltype ? φ : literaltype(φ) - return LeftmostConjunctiveForm([LeftmostDisjunctiveForm{literaltype}([φ])]) - else - return error("Unexpected token $(token)!") - end -end - -############################################################################################ diff --git a/src/utils/syntax-utils.jl b/src/utils/tools.jl similarity index 100% rename from src/utils/syntax-utils.jl rename to src/utils/tools.jl diff --git a/test/generation/formula.jl b/test/generation/formula.jl index bf8bb6d5..a7b78df0 100644 --- a/test/generation/formula.jl +++ b/test/generation/formula.jl @@ -26,10 +26,10 @@ w = [10,1,1] @testset "generation w. custom operators" begin -my_TERNOP = SoleLogics.NamedConnective{:⇶}() +my_TERNOP = SoleLogics.NamedConnective{:threeway}() SoleLogics.arity(::typeof(my_TERNOP)) = 3 -my_QUATERNOP = SoleLogics.NamedConnective{:⩰}() +my_QUATERNOP = SoleLogics.NamedConnective{:fourway}() SoleLogics.arity(::typeof(my_QUATERNOP)) = 4 _alphabet = ExplicitAlphabet(["p", "q", "r", "s"]) From ef103bf3174fef66d68ae1e6211b50933fbb9c26 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:47:24 +0100 Subject: [PATCH 75/90] Brought back old-code/many-valued-logics --- .../many-valued-logics/flew-algebras.jl | 143 +++ .../many-valued-logics/heyting-algebras.jl | 837 ++++++++++++++++++ 2 files changed, 980 insertions(+) create mode 100644 src/old-code/many-valued-logics/flew-algebras.jl create mode 100644 src/old-code/many-valued-logics/heyting-algebras.jl diff --git a/src/old-code/many-valued-logics/flew-algebras.jl b/src/old-code/many-valued-logics/flew-algebras.jl new file mode 100644 index 00000000..deb92aaa --- /dev/null +++ b/src/old-code/many-valued-logics/flew-algebras.jl @@ -0,0 +1,143 @@ +using ..SoleLogics: AbstractAlgebra +import ..SoleLogics: syntaxstring +import Base: convert + +struct FLewTruth <: Truth + label::String + + function FLewTruth(label::String) + return new(label) + end + + function FLewTruth(t::BooleanTruth) + return convert(FLewTruth, t) + end +end + +syntaxstring(t::FLewTruth; kwargs...) = t.label +convert(::Type{FLewTruth}, t::BooleanTruth) = istop(t) ? FLewTruth("⊤") : FLewTruth("⊥") + +""" + struct FLewAlgebra <: AbstractAlgebra{FLewTruth} + domain::Set{FLewTruth} + jointable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} + meettable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} + monoidtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} + implicationtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} + bot::FLewTruth + top::FLewTruth + end + +An FLew-algebra is an algebra (L, ∨, ∧, ⋅, →, ⊥, ⊤), where + - (L, ∨, ∧, ⊥, ⊤) is a lattice with top element ⊤ and bottom element ⊥ + - (L, ⋅, ⊤) is a commutative monoid + - The residuation property holds: x ⋅ y ≤ z iff x ≤ y → z + +A lattice is an algebraic structure (L, ∨, ∧) consisting of a set L and two binary, +commutative and associative operations ∨ and ∧ on L satisfying the following axiomatic +identities for all elements a, b ∈ L (sometimes called absorption laws): + - a ∨ (a ∧ b) = a + - a ∧ (a ∨ b) = a + +The following two identities are also usally regarded as axioms, even though they follow +from the two absorption laws taken together. These are called idempotent laws: + - a ∨ a = a + - a ∧ a = a + +A bounded lattice is an algebraic structure (L, ∨, ∧, ⊥, ⊤) such that (L, ∨, ∧) is a +lattice, the bottom element ⊥ is the identity element for the join operation ∨, and the top +element ⊤ is the identity element for the meet operation ∧: + - a ∨ ⊥ = a + - a ∧ ⊤ = a + +A monoid (L, ⋅, e) is a set L equipped with a binary operation L × L → L, denoted as ⋅, +satisfying the following axiomatic identities: + - (Associativity) ∀ a, b, c ∈ L, the equation (a ⋅ b) ⋅ c = a ⋅ (b ⋅ c) holds. + - (Identity element) There exists an element e ∈ L such that for every element a ∈ L, the equalities e ⋅ a = a + and a ⋅ e = a hold. + +The identity element of a monoid is unique. + +A commutative monoid is a monoid whose operation is commutative. +""" +struct FLewAlgebra <: AbstractAlgebra{FLewTruth} + domain::Set{FLewTruth} + jointable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} + meettable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} + monoidtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} + implicationtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth} + bot::FLewTruth + top::FLewTruth + + function FLewAlgebra( + domain::Set{FLewTruth}, + jointable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth}, + meettable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth}, + monoidtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth}, + implicationtable::Dict{Tuple{FLewTruth, FLewTruth}, FLewTruth}, + bot::FLewTruth, + top::FLewTruth + ) + for i ∈ domain + for j ∈ domain + @assert (i, j) ∈ keys(jointable) "jointable[($i, $j)] is not defined." + @assert (i, j) ∈ keys(meettable) "meettable[($i, $j)] is not defined." + @assert (i, j) ∈ keys(monoidtable) "monoidtable[($i, $j)] is not defined." + @assert (i, j) ∈ keys(monoidtable) "implicationtable[($i, $j)] is not defined." + end + end + @assert length(jointable) == length(domain)^2 "Found jointable[(i, j)] where i or j ∉ domain." + @assert length(meettable) == length(domain)^2 "Found meettable[(i, j)] where i or j ∉ domain." + @assert length(monoidtable) == length(domain)^2 "Found monoidtable[(i, j)] where i or j ∉ domain." + @assert length(implicationtable) == length(domain)^2 "Found implicationtable[(i, j)] where i or j ∉ domain." + for i ∈ domain + @assert monoidtable[(i, FLewTruth(⊤))] == i "Defined monoid don't satisfy the neutral element rule: monoidtable[($i, ⊤) ≠ $i]." + for j ∈ domain + @assert jointable[(i, j)] == jointable[(j, i)] "Defined join is not commutative: jointable[($i,$j)] ≠ jointable[($j,$i)]." + @assert meettable[(i, j)] == meettable[(j, i)] "Defined meet is not commutative: meettable[($i,$j)] ≠ meettable[($j,$i)]." + @assert monoidtable[(i, j)] == monoidtable[(j, i)] "Defined monoid is not commutative: monoidtable[($i,$j)] ≠ monoidtable[($j,$i)]." + for k ∈ domain + @assert jointable[(jointable[(i, j)], k)] == jointable[(i, jointable[(j, k)])] "Defined join is not associative: jointable[(jointable[(i, j)], k)] ≠ jointable[(i, jointable[(j, k)])]." + @assert meettable[(meettable[(i, j)], k)] == meettable[(i, meettable[(j, k)])] "Defined meet is not associative: meettable[(meettable[(i, j)], k)] ≠ meettable[(i, meettable[(j, k)])]." + @assert monoidtable[(monoidtable[(i,j)], k)] == monoidtable[(i, monoidtable[(j, k)])] "Defined monoid is not associative: monoidtable[(monoidtable[(i,j)], k)] ≠ monoidtable[(i, monoidtable[(j, k)])]." + end + @assert jointable[(i, meettable[(i, j)])] == i "Defined join and meet don't satisfy the asborption law: jointable[($i, meettable[($i, $j)])] ≠ $i." + @assert meettable[(i, jointable[(i, j)])] == i "Defined join and meet don't satisfy the asborption law: meettable[($i, jointable[($i, $j)])] ≠ $i." + end + @assert jointable[(i, i)] == i "Defined join don't satisfy the idempotent law: jointable[($i, $i) ≠ $i]." + @assert meettable[(i, i)] == i "Defined meet don't satisfy the idempotent law: meettable[($i, $i) ≠ $i]." + @assert meettable[(bot, i)] == bot "$bot isn't a valid bottom element: $bot ≰ $i" + @assert jointable[(i, top)] == top "$top isn't a valid top element: $i ≰ $top" + end + return new(domain, meettable, jointable, monoidtable, implicationtable, bot, top) + end +end + +join(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) = a.jointable[(t1, t2)] +meet(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) = a.meettable[(t1, t2)] +monoid(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) = a.monoidtable[(t1, t2)] +implication(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) = a.implicationtable[(t1, t2)] +isbot(a::FLewAlgebra, t::FLewTruth) = t == a.bot +itop(a::FLewAlgebra, t::FLewTruth) = t == a.top + +""" + precedes(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) + +Given an algebraically defined lattice (L, ∨, ∧), one can define a partial ordern ≤ on L by +setting: + - a ≤ b if a = a ∧ b, or + - a ≤ b if b = a ∨ b, + +for all elements a, b ∈ L. The laws of absorption ensure that both definitions are +equivalent: + - a = a ∧ b implies b = b ∨ (b ∧ a) = (a ∧ b) ∨ b = a ∨ b + +and dually for the other direction. +""" +function precedes(a::FLewAlgebra, t1::FLewTruth, t2::FLewTruth) + if meet(a, t1, t2) == t1 + return true + else + return false + end +end diff --git a/src/old-code/many-valued-logics/heyting-algebras.jl b/src/old-code/many-valued-logics/heyting-algebras.jl new file mode 100644 index 00000000..557f2f04 --- /dev/null +++ b/src/old-code/many-valued-logics/heyting-algebras.jl @@ -0,0 +1,837 @@ +using Graphs +using ..SoleLogics: AbstractAlgebra +import ..SoleLogics: istop, simplify + +# Author: alberto-paparella + +############################################################################################ +#### HeytingTruth ########################################################################## +############################################################################################ + +""" + struct HeytingTruth <: Truth + label::String + index::Int + end + +A truth value of a Heyting algebra. +Heyting truth values are represented by a label, and an index corresponding to its +position in the domain vector of the associated algebra. +Values `⊤` and `⊥` always exist with index 1 and 2, respectively. +New values can be easily constructed via the [`@heytingtruths`](@ref) macro. + +See also [`@heytingtruths`](@ref), [`HeytingAlgebra`](@ref), [`Truth`](@ref) +""" +struct HeytingTruth <: Truth + label::String + index::Int # the index of the node in the domain vector: no order is implied! + + function HeytingTruth(label::String, index::Int) + return new(label, index) + end + + # Helper + function HeytingTruth(booleantruth::BooleanTruth) + return convert(HeytingTruth, booleantruth) + end +end + +""" +Return the label of a [`HeytingTruth`](@ref). +""" +label(t::HeytingTruth)::String = t.label + +""" +Return the index of a [`HeytingTruth`](@ref). +""" +index(t::HeytingTruth)::Int = t.index + +istop(t::HeytingTruth) = index(t) == 1 +isbot(t::HeytingTruth) = index(t) == 2 + +syntaxstring(t::HeytingTruth; kwargs...) = label(t) + +convert(::Type{HeytingTruth}, t::HeytingTruth) = t + +function convert(::Type{HeytingTruth}, t::BooleanTruth) + return istop(t) ? HeytingTruth("⊤", 1) : HeytingTruth("⊥", 2) +end + +# Convert an object of type HeytingTruth to an object of type `BooleanTruth` (if possible). +function convert(::Type{BooleanTruth}, t::HeytingTruth) + if istop(t) + return TOP + elseif isbot(t) + return BOT + else + error("Cannot convert HeytingTruth \"" * syntaxstring(t) * "\" to BooleanTruth. " * + "Only ⊤ and ⊥ can be converted to BooleanTruth.") + end +end + +# Helper +function Base.show(io::IO, v::Vector{HeytingTruth}) + print(io, SoleLogics.displaysyntaxvector(v; quotes = false)) +end + +############################################################################################ +#### HeytingAlgebra ######################################################################## +############################################################################################ + +# TODO verify: these may be useful: +# - https://github.com/scheinerman/SimplePosets.jl +# - https://github.com/simonschoelly/SimpleValueGraphs.jl +""" + struct HeytingAlgebra{D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph} + domain::D + graph::G + transitiveclosure::G + end + +A Heyting algebra, represented explicitly +as a domain of truth values, and a graph over them encoding a partial order with +specific constraints (see [here](https://en.wikipedia.org/wiki/Heyting_algebra)). +⊤ and ⊥ are always the first and the second element of each algebra, respectively. +A copy of the graph under transitive closure is also stored for optimization purposes. + +See also [`@heytingalgebra`](@ref), [`HeytingTruth`](@ref) +""" +struct HeytingAlgebra{ + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} <: AbstractAlgebra{HeytingTruth} + domain::D + graph::G # directed graph where (α, β) represents α ≺ β + transitiveclosure::G # transitive closure of the graph (useful for some optimization) + isevaluated::Bool + meettable::Vector{Vector{HeytingTruth}} + jointable::Vector{Vector{HeytingTruth}} + implication::Vector{Vector{HeytingTruth}} + maxmembers::Vector{Vector{HeytingTruth}} + minmembers::Vector{Vector{HeytingTruth}} + + function HeytingAlgebra( + domain::D, + graph::G; + evaluate::Bool + ) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph + } + @assert length(domain) >= 2 "Cannot instantiate `HeytingAlgebra` with domain " * + "of length $(length(domain)). Need to specify at least a top and a bottom " * + "element (to be placed at positions 1 and 2, respectively)." + @assert isbounded(domain, graph) "Tried to define an HeytingAlgebra with a graph " * + "which is not a bounded lattice." + @assert iscomplete(domain, graph) "Tried to define an HeytingAlgebra " * + "with a graph which is not a complete lattice." + if evaluate + tc = transitiveclosure(graph) + meettable = [Vector{HeytingTruth}(undef, length(domain)) for _ in 1:length(domain)] + jointable = [Vector{HeytingTruth}(undef, length(domain)) for _ in 1:length(domain)] + implication = [Vector{HeytingTruth}(undef, length(domain)) for _ in 1:length(domain)] + maxmembers = Vector{Vector{HeytingTruth}}(undef, length(domain)) + minmembers = Vector{Vector{HeytingTruth}}(undef, length(domain)) + for α ∈ domain + for β ∈ domain + meettable[index(α)][index(β)] = greatestlowerbound(domain, tc, α, β) + jointable[index(α)][index(β)] = leastupperbound(domain, tc, α, β) + end + end + for α ∈ domain + for β ∈ domain + η = HeytingTruth(⊥) + for γ ∈ domain + if precedeq(domain, tc, meettable[index(α)][index(γ)], β) + η = jointable[index(η)][index(γ)] + end + end + implication[index(α)][index(β)] = η + end + end + for α ∈ domain + maxmembers[index(α)] = maximalmembers(domain, tc, α) + minmembers[index(α)] = minimalmembers(domain, tc, α) + end + return new{D,G}(domain, graph, tc, true, meettable, jointable, implication, maxmembers, minmembers) + else + return new{D,G}(domain, graph, transitiveclosure(graph), false) + end + end + + function HeytingAlgebra(domain::Vector{HeytingTruth}, relations::Vector{Edge{Int64}}; evaluate::Bool=false) + return HeytingAlgebra(domain, SimpleDiGraph(relations), evaluate=evaluate) + end +end + +domain(h::HeytingAlgebra) = h.domain +top(h::HeytingAlgebra) = h.domain[1] +bot(h::HeytingAlgebra) = h.domain[2] +graph(h::HeytingAlgebra) = h.graph +Graphs.transitiveclosure(h::HeytingAlgebra) = h.transitiveclosure +isevaluated(h::HeytingAlgebra) = h.isevaluated + +meet(h::HeytingAlgebra) = h.meettable +meet(h::HeytingAlgebra, α, β) = meet(h)[index(α)][index(β)] + +join(h::HeytingAlgebra) = h.jointable +join(h::HeytingAlgebra, α, β) = join(h)[index(α)][index(β)] + +implication(h::HeytingAlgebra) = h.implication +implication(h::HeytingAlgebra, α, β) = implication(h)[index(α)][index(β)] + +maxmembers(h::HeytingAlgebra) = h.maxmembers +maxmembers(h::HeytingAlgebra, t::HeytingTruth) = maxmembers(h)[index(t)] + +minmembers(h::HeytingAlgebra) = h.minmembers +minmembers(h::HeytingAlgebra, t::HeytingTruth) = minmembers(h)[index(t)] + +cardinality(h::HeytingAlgebra) = length(domain(h)) +isboolean(h::HeytingAlgebra) = (cardinality(h) == 2) + +function Graphs.has_path( + g::G, + α::HeytingTruth, + β::HeytingTruth +) where { + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + return has_path(g, index(α), index(β)) +end + +function isbounded( + d::D, + g::G +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + for α ∈ d + if !has_path(g, HeytingTruth(⊥), α) || !has_path(g, α, HeytingTruth(⊤)) + return false + end + end + return true +end + +function Graphs.inneighbors( + d::D, + g::G, + t::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + return d[inneighbors(g, index(t))] +end + +function Graphs.inneighbors(h::HeytingAlgebra, t::HeytingTruth) + return domain(h)[inneighbors(graph(h), index(t))] +end + +function Graphs.outneighbors( + d::D, + g::G, + t::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + return d[outneighbors(g, index(t))] +end + +function Graphs.outneighbors(h::HeytingAlgebra, t::HeytingTruth) + return domain(h)[outneighbors(graph(h), index(t))] +end + +# α ≺ β (note: in general, α ⊀ β ≠ α ⪰ β) +function precedes( + d::D, + tc::G, + α::HeytingTruth, + β::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + return β ∈ outneighbors(d, tc, α) +end + +function precedes(h::HeytingAlgebra, α::HeytingTruth, β::HeytingTruth) + return precedes(domain(h), transitiveclosure(h), α, β) +end + +function precedes(h::HeytingAlgebra, α::HeytingTruth, β::BooleanTruth) + return precedes(domain(h), transitiveclosure(h), α, convert(HeytingTruth, β)) +end + +function precedes(h::HeytingAlgebra, α::BooleanTruth, β::HeytingTruth) + return precedes(domain(h), transitiveclosure(h), convert(HeytingTruth, α), β) +end + +function precedes(h::HeytingAlgebra, α::BooleanTruth, β::BooleanTruth) + return precedes( + domain(h), + transitiveclosure(h), + convert(HeytingTruth, α), + convert(HeytingTruth, β) + ) +end + +# α ⪯ β (note: in general, α ⪯̸ β ≠ α ≻ β) +function precedeq( + d::D, + tc::G, + α::HeytingTruth, + β::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + return α == β || precedes(d, tc, α, β) +end + +function precedeq(h::HeytingAlgebra, α::HeytingTruth, β::HeytingTruth) + return α == β || precedes(domain(h), transitiveclosure(h), α, β) +end + +function precedeq(h::HeytingAlgebra, α::HeytingTruth, β::BooleanTruth) + return precedeq(h, α, convert(HeytingTruth, β)) +end + +function precedeq(h::HeytingAlgebra, α::BooleanTruth, β::HeytingTruth) + return precedeq(h, convert(HeytingTruth, α), β) +end + +function precedeq(h::HeytingAlgebra, α::BooleanTruth, β::BooleanTruth) + return α == β || precedes( + domain(h), + transitiveclosure(h), + convert(HeytingTruth, α), + convert(HeytingTruth, β) + ) +end + +# α ≻ β (note: in general, α ⊁ β ≠ α ⪯ β) +function succeedes( + d::D, + tc::G, + α::HeytingTruth, + β::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + return β ∈ inneighbors(d, tc, α) +end + +function succeedes(h::HeytingAlgebra, α::HeytingTruth, β::HeytingTruth) + return succeedes(domain(h), transitiveclosure(h), α, β) +end + +function succeedes(h::HeytingAlgebra, α::HeytingTruth, β::BooleanTruth) + return succeedes(domain(h), transitiveclosure(h), α, convert(HeytingTruth, β)) +end + +function succeedes(h::HeytingAlgebra, α::BooleanTruth, β::HeytingTruth) + return succeedes(domain(h), transitiveclosure(h), convert(HeytingTruth, α), β) +end + +function succeedes(h::HeytingAlgebra, α::BooleanTruth, β::BooleanTruth) + return succeedes( + domain(h), + transitiveclosure(h), + convert(HeytingTruth, α), + convert(HeytingTruth, β) + ) +end + +# α ⪰ β (note: in general, α ⪰̸ β ≠ α ≺ β) +function succeedeq( + d::D, + tc::G, + α::HeytingTruth, + β::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + return α == β || succeedes(d, tc, α, β) +end + +function succeedeq(h::HeytingAlgebra, α::HeytingTruth, β::HeytingTruth) + return α == β || succeedes(domain(h), transitiveclosure(h), α, β) +end + +function succeedeq(h::HeytingAlgebra, α::HeytingTruth, β::BooleanTruth) + return succeedeq(h, α, convert(HeytingTruth, β)) +end + +function succeedeq(h::HeytingAlgebra, α::BooleanTruth, β::HeytingTruth) + return succeedeq(h, convert(HeytingTruth, α), β) +end + +function succeedeq(h::HeytingAlgebra, α::BooleanTruth, β::BooleanTruth) + return α == β || succeedes( + domain(h), + transitiveclosure(h), + convert(HeytingTruth, α), + convert(HeytingTruth, β) + ) +end + +""" +Return all maximal members of h not above t. +""" +function maximalmembers( + d::D, + tc::G, + t::HeytingTruth, + α::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + ismm = true + mm = Set{HeytingTruth}() + for o ∈ outneighbors(d, tc, α) + if !succeedeq(d, tc, o, t) + ismm = false + push!(mm, maximalmembers(d, tc, t, o)...) + end + end + ismm ? HeytingTruth[α] : collect(mm) +end + +function maximalmembers( + d::D, + tc::G, + t::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + if isbot(t) + return HeytingTruth[] + else + return maximalmembers(d, tc, t, HeytingTruth(⊥)) + end +end + +function maximalmembers(h::HeytingAlgebra, t::HeytingTruth) + if isbot(t) + return HeytingTruth[] + elseif isevaluated(h) + return maxmembers(h, t) + else + return maximalmembers(domain(h), transitiveclosure(h), t, HeytingTruth(⊥)) + end +end + +maximalmembers(h::HeytingAlgebra, t::BooleanTruth) = maximalmembers(h, HeytingTruth(t)) + +""" +Return all minimal members of h not below t +""" +function minimalmembers( + d::D, + tc::G, + t::HeytingTruth, + α::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + ismm = true + mm = Set{HeytingTruth}() + for i ∈ inneighbors(d, tc, α) + if !precedeq(d, tc, i, t) + ismm = false + push!(mm, minimalmembers(d, tc, t, i)...) + end + end + ismm ? HeytingTruth[α] : collect(mm) +end + +function minimalmembers( + d::D, + tc::G, + t::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + if istop(t) + return HeytingTruth[] + else + return minimalmembers(d, tc, t, HeytingTruth(⊤)) + end +end + +function minimalmembers(h::HeytingAlgebra, t::HeytingTruth) + if istop(t) + return HeytingTruth[] + elseif isevaluated(h) + return minmembers(h, t) + else + return minimalmembers(domain(h), transitiveclosure(h), t, HeytingTruth(⊤)) + end +end + +minimalmembers(h::HeytingAlgebra, t::BooleanTruth) = minimalmembers(h, HeytingTruth(t)) + +function greatervalues( + d::D, + tc::G, + α::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + return outneighbors(d, tc, α) +end + +function upperbounds( + d::D, + tc::G, + α::HeytingTruth, + β::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + geqα = push!(greatervalues(d, tc, α), α) + geqβ = push!(greatervalues(d, tc, β), β) + return geqα[in.(geqα, Ref(geqβ))] +end + +function isleastupperbound( + d::D, + tc::G, + α::HeytingTruth, + ubs::D +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + for ub ∈ ubs + if !precedeq(d, tc, α, ub) # note: in general, α ⪯̸ β ≠ α ≻ β + return false + end + end + return true +end + +function leastupperbound( + d::D, + tc::G, + α::HeytingTruth, + β::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + ubs = upperbounds(d, tc, α, β) + for ub ∈ ubs + if isleastupperbound(d, tc, ub, ubs) + return ub + end + end + return nothing +end + +function lesservalues( + d::D, + tc::G, + α::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + return inneighbors(d, tc, α) +end + +function lesservalues(h::HeytingAlgebra, t::HeytingTruth) + return lesservalues(domain(h), transitiveclosure(h), t) +end + +function lowerbounds( + d::D, + tc::G, + α::HeytingTruth, + β::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + leqα = push!(lesservalues(d, tc, α), α) + leqβ = push!(lesservalues(d, tc, β), β) + return leqα[in.(leqα, Ref(leqβ))] +end + +function isgreatestlowerbound( + d::D, + tc::G, + α::HeytingTruth, + lbs::D +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + for lb ∈ lbs + if !succeedeq(d, tc, α, lb) # note: in general, α ⪰̸ β ≠ α ≺ β + return false + end + end + return true +end + +function greatestlowerbound( + d::D, + tc::G, + α::HeytingTruth, + β::HeytingTruth +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + lbs = lowerbounds(d, tc, α, β) + for lb ∈ lbs + if isgreatestlowerbound(d, tc, lb, lbs) + return lb + end + end + return nothing +end + +function iscomplete( + d::D, + g::G +) where { + D<:AbstractVector{HeytingTruth}, + G<:Graphs.SimpleGraphs.SimpleDiGraph +} + tc = transitiveclosure(g) + for α ∈ d + for β ∈ d + if α != β && ( + isnothing(leastupperbound(d, tc, α, β)) || + isnothing(greatestlowerbound(d, tc, α, β)) + ) + return false + end + end + end + return true +end + +Graphs.Edge(t::Tuple{HeytingTruth, HeytingTruth}) = Edge(index(t[1]), index(t[2])) + +function Graphs.Edge(t::Tuple{HeytingTruth, BooleanTruth}) + return Edge((t[1], convert(HeytingTruth, t[2]))) +end + +function Graphs.Edge(t::Tuple{BooleanTruth, HeytingTruth}) + return Edge((convert(HeytingTruth, t[1]), t[2])) +end + +function Graphs.Edge(t::Tuple{BooleanTruth, BooleanTruth}) + return Edge((convert(HeytingTruth, t[1]), convert(HeytingTruth, t[2]))) +end + +""" + @heytingtruths(labels...) + +Instantiate a collection of [`HeytingTruth`](@ref)s and return them as a vector. +⊤ and ⊥ already exist as `const`s of type `BooleanTruth` and they are +treated as `HeytingTruth`s with index 1 and 2, respectively. + +!!! info + `HeytingTruth`s instantiated with this macro are defined in the global scope as + constants. + +# Examples +```julia-repl +julia> SoleLogics.@heytingtruths α β +2-element Vector{HeytingTruth}: + HeytingTruth: α + HeytingTruth: β + +julia> α +HeytingTruth: α + +See also [`HeytingTruth`](@ref), [`@heytingalgebra`](@ref) +""" +macro heytingtruths(labels...) + quote + $(map(t -> begin + if !(t[2] in [Symbol(:⊤), Symbol(:⊥)]) + :(const $(t[2]) = $(HeytingTruth(string(t[2]), t[1]+2))) + else + return error("Invalid heyting truth provided: $(t[2]). " * + "Symbols `⊤` and `⊥` are reserved for the top and bottom of the " + * "algebra, and they do not need to be specified.") + end + end, enumerate(labels))...) + HeytingTruth[$(labels...)] + end |> esc +end + +""" + @heytingalgebra(values, relations...) + +Construct a [`HeytingAlgebra`](@ref) +with domain containing `values` and graph represented by the tuples in `relations`, with +each tuple (α, β) representing a direct edge in the graph asserting α ≺ β. + +# Examples +```julia-repl + +julia> myalgebra = SoleLogics.@heytingalgebra (α, β) (⊥, α) (⊥, β) (α, ⊤) (β, ⊤) +HeytingAlgebra(HeytingTruth[⊤, ⊥, α, β], SimpleDiGraph{Int64}(4, [Int64[], [3, 4], [1], [1]] +, [[3, 4], Int64[], [2], [2]])) + +See also [`HeytingTruth`](@ref), [`@heytingalgebra`](@ref) +""" +macro heytingalgebra(values, relations...) + quote + labels = @heytingtruths $(values.args...) + domain = HeytingTruth[convert(HeytingTruth, ⊤), convert(HeytingTruth, ⊥), labels...] + edges = Vector{SoleLogics.Graphs.Edge{Int64}}() + map(e -> push!(edges, SoleLogics.Graphs.Edge(eval(e))), $relations) + HeytingAlgebra(domain, edges) + end |> esc +end + +# Meet (greatest lower bound) between values α and β +function collatetruth( + ::typeof(∧), + (α, β)::NTuple{N, T where T<:HeytingTruth}, + h::HeytingAlgebra +) where { + N +} + if isevaluated(h) + return meet(h, α, β) + else + return greatestlowerbound(domain(h), transitiveclosure(h), α, β) + end +end + +# Join (least upper bound) between values α and β +function collatetruth( + ::typeof(∨), + (α, β)::NTuple{N, T where T<:HeytingTruth}, + h::HeytingAlgebra +) where { + N +} + if isevaluated(h) + return join(h, α, β) + else + return leastupperbound(domain(h), transitiveclosure(h), α, β) + end +end + +# Implication/pseudo-complement α → β = join(γ | meet(α, γ) ⪯ β) +function collatetruth( + ::typeof(→), + (α, β)::NTuple{N, T where T<:HeytingTruth}, + h::HeytingAlgebra +) where { + N +} + if isevaluated(h) + return implication(h, α, β) + else + η = bot(h) + for γ ∈ domain(h) + if precedeq(h, collatetruth(∧, (α, γ), h), β) + η = collatetruth(∨, (η, γ), h) + end + end + return η + end +end + +function collatetruth( + c::Connective, + (α, β)::Tuple{HeytingTruth, BooleanTruth}, + h::HeytingAlgebra +) + return collatetruth(c, (α, convert(HeytingTruth, β)), h) +end + +function collatetruth( + c::Connective, + (α, β)::Tuple{BooleanTruth, HeytingTruth}, + h::HeytingAlgebra +) + return collatetruth(c, (convert(HeytingTruth, α), β), h) +end + +function collatetruth( + c::Connective, + (α, β)::Tuple{BooleanTruth, BooleanTruth}, + h::HeytingAlgebra +) + return collatetruth(c, (convert(HeytingTruth, α), convert(HeytingTruth, β)), h) +end + +function simplify( + c::Connective, + (α, β)::Tuple{HeytingTruth,HeytingTruth}, + h::HeytingAlgebra +) + return collatetruth(c, (α, β), h) +end + + function simplify( + c::Connective, + (α, β)::Tuple{HeytingTruth,BooleanTruth}, + h::HeytingAlgebra +) + return collatetruth(c, (α, convert(HeytingTruth, β)), h) +end + + function simplify( + c::Connective, + (α, β)::Tuple{BooleanTruth,HeytingTruth}, + h::HeytingAlgebra +) + return collatetruth(c, (convert(HeytingTruth, α), β), h) +end + + function simplify( + c::Connective, + (α, β)::Tuple{BooleanTruth,BooleanTruth}, + h::HeytingAlgebra +) + return collatetruth(c, (convert(HeytingTruth, α), convert(HeytingTruth, β)), h) +end + + +function collatetruth(::typeof(¬), (α,)::Tuple{HeytingTruth}, h::HeytingAlgebra) + if isboolean(h) + if istop(α) + return ⊥ + else + return ⊤ + end + else + return error("¬ operation isn't defined outside of BooleanAlgebra") + end +end + +function collatetruth(c::Connective, (α,)::Tuple{BooleanTruth}, h::HeytingAlgebra) + return collatetruth(c, convert(HeytingTruth, α), h) +end + +function simplify(c::Connective, (α,)::Tuple{HeytingTruth}, h::HeytingAlgebra) + return collatetruth(c, (α,), h) +end + + function simplify(c::Connective, (α,)::Tuple{BooleanTruth}, h::HeytingAlgebra) + return simplify(c, (convert(HeytingTruth, α),), h) +end From 1b95e6ca084b88d83e0dd068976195ab1dcf63c4 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:13:15 +0100 Subject: [PATCH 76/90] Fix and add test --- Project.toml | 5 ++++- src/types/interpretation-sets.jl | 4 ++-- test/interpretation-sets.jl | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index c0fd6cf3..6b69432c 100644 --- a/Project.toml +++ b/Project.toml @@ -42,6 +42,9 @@ Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +DecisionTree = "7806a523-6efd-50cb-b5f6-3fa6f1930dbb" +SoleData = "123f1ae1-6307-4526-ab5b-aab3a92a2b8c" [targets] -test = ["Test", "Markdown", "InteractiveUtils", "PlutoUI", "BenchmarkTools"] +test = ["Test", "Markdown", "InteractiveUtils", "PlutoUI", "BenchmarkTools", "DataFrames", "DecisionTree", "SoleData"] diff --git a/src/types/interpretation-sets.jl b/src/types/interpretation-sets.jl index d538928b..22c91d8e 100644 --- a/src/types/interpretation-sets.jl +++ b/src/types/interpretation-sets.jl @@ -59,13 +59,13 @@ truthtype(S::Type{<:AbstractInterpretationSet}) = truthtype(interpretationtype(S truthtype(s::AbstractInterpretationSet) = truthtype(typeof(s)) """ - alphabet(s::AbstractInterpretationSet)::Alphabet + alphabet(s::AbstractInterpretationSet)::AbstractAlphabet Return the propositional alphabet of an interpretation set. See also [`AbstractAlphabet`](@ref), [`AbstractGrammar`](@ref). """ -function alphabet(s::AbstractInterpretationSet)::Alphabet +function alphabet(s::AbstractInterpretationSet)::AbstractAlphabet return error("Please, provide method alphabet(::$(typeof(s))).") end diff --git a/test/interpretation-sets.jl b/test/interpretation-sets.jl index 63bd6c90..260312c1 100644 --- a/test/interpretation-sets.jl +++ b/test/interpretation-sets.jl @@ -12,3 +12,18 @@ s = SoleLogics.InterpretationVector([TruthDict((1,false)), TruthDict((1,true)), @test_nowarn [check(Atom(1), i) for i in SoleLogics.eachinstance(s)] @test [check(Atom(1), i) for i in SoleLogics.eachinstance(s)] == [false, true, true] + +################################################################################ + +using DecisionTree: load_data +using DataFrames +using SoleData + +X, y = load_data("iris") +X = Float64.(X) +X_df = DataFrame(X, :auto) +s = scalarlogiset(X_df) + +myalphabet = @test_nowarn alphabet(s) +a = @test_nowarn atoms(myalphabet)[1] +@test_nowarn [check(a, i) for i in SoleLogics.eachinstance(s)] From 2b6b9f91420c380b2f5d6cbc08b5b3597606526d Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:13:47 +0100 Subject: [PATCH 77/90] Bump ver --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6b69432c..9c815f96 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SoleLogics" uuid = "b002da8f-3cb3-4d91-bbe3-2953433912b5" authors = ["Mauro MILELLA", "Giovanni PAGLIARINI", "Edoardo PONSANESI", "Alberto PAPARELLA", "Eduard I. STAN"] -version = "0.10.0" +version = "0.10.1" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From aa918515a44f01dab0f6f60b6cf9243be113320e Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:53:20 +0100 Subject: [PATCH 78/90] Minor --- src/SoleLogics.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 49599cad..31e16803 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -47,8 +47,6 @@ export interpret, check include("types/interpretation.jl") -include("types/interpretation-sets.jl") - export AlphabetOfAny, ExplicitAlphabet, UnionAlphabet export alphabet, subalphabets @@ -57,6 +55,8 @@ export domain, top, bot, grammar, algebra, logic include("types/logic.jl") +include("types/interpretation-sets.jl") + export TOP, ⊤ export BOT, ⊥ From e65107b6bda124ab451aea1a3ce00b82f1b6e974 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:59:58 +0100 Subject: [PATCH 79/90] Minor fix test --- test/interpretation-sets.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/interpretation-sets.jl b/test/interpretation-sets.jl index 260312c1..179c273f 100644 --- a/test/interpretation-sets.jl +++ b/test/interpretation-sets.jl @@ -22,8 +22,8 @@ using SoleData X, y = load_data("iris") X = Float64.(X) X_df = DataFrame(X, :auto) -s = scalarlogiset(X_df) - +s = scalarlogiset(X_df; allow_propositional = true) myalphabet = @test_nowarn alphabet(s) a = @test_nowarn atoms(myalphabet)[1] @test_nowarn [check(a, i) for i in SoleLogics.eachinstance(s)] + From 1ce9fc75ee58aa04ae25666ee4dc6538e9278220 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:07:17 +0100 Subject: [PATCH 80/90] Fix test --- test/interpretation-sets.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/interpretation-sets.jl b/test/interpretation-sets.jl index 260312c1..92e57f81 100644 --- a/test/interpretation-sets.jl +++ b/test/interpretation-sets.jl @@ -25,5 +25,5 @@ X_df = DataFrame(X, :auto) s = scalarlogiset(X_df) myalphabet = @test_nowarn alphabet(s) -a = @test_nowarn atoms(myalphabet)[1] +a = @test_nowarn first(atoms(myalphabet) @test_nowarn [check(a, i) for i in SoleLogics.eachinstance(s)] From 23bcc618641647ad8c3ec5fdf0e5db3c482777e8 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:12:08 +0100 Subject: [PATCH 81/90] minor --- test/interpretation-sets.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/interpretation-sets.jl b/test/interpretation-sets.jl index 1d92ac62..4cd97bfd 100644 --- a/test/interpretation-sets.jl +++ b/test/interpretation-sets.jl @@ -24,6 +24,6 @@ X = Float64.(X) X_df = DataFrame(X, :auto) s = scalarlogiset(X_df; allow_propositional = true) myalphabet = @test_nowarn alphabet(s) -a = @test_nowarn first(atoms(myalphabet) +a = @test_nowarn first(atoms(myalphabet)) @test_nowarn [check(a, i) for i in SoleLogics.eachinstance(s)] From d81b8c6b8567470d35d756be60b5ce0ec253ab99 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:12:44 +0100 Subject: [PATCH 82/90] bump --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 9c815f96..19735b78 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SoleLogics" uuid = "b002da8f-3cb3-4d91-bbe3-2953433912b5" authors = ["Mauro MILELLA", "Giovanni PAGLIARINI", "Edoardo PONSANESI", "Alberto PAPARELLA", "Eduard I. STAN"] -version = "0.10.1" +version = "0.10.2" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From cd5d899703b2eca03d6a3e9c05dedcb05242feab Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Sun, 3 Nov 2024 13:45:39 +0100 Subject: [PATCH 83/90] Test for added to close a pr; space removed from tests --- src/types/algebras/worlds.jl | 2 +- test/check/propositional.jl | 2 +- test/normalize.jl | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/types/algebras/worlds.jl b/src/types/algebras/worlds.jl index c53f7c9c..108371d6 100644 --- a/src/types/algebras/worlds.jl +++ b/src/types/algebras/worlds.jl @@ -1,6 +1,6 @@ """ Some worlds (dimensional worlds) can be interpreted on dimensional data, -that is, n-dimensional arrays. The compatibility of a given world with respect of a +that is, n-dimensional arrays. The compatibility of a given world with respect of a structure of a given dimensionality must be specified via the following trait: goeswithdim(w::AbstractWorld, d) = goeswithdim(typeof(w), d) diff --git a/test/check/propositional.jl b/test/check/propositional.jl index 858718f9..8fb5fa01 100644 --- a/test/check/propositional.jl +++ b/test/check/propositional.jl @@ -108,7 +108,7 @@ t2 = @test_nowarn TruthDict(Pair{Real,Bool}[1.0 => true, 2 => true, 3 => true]) @test interpret(Atom("r"), DefaultedTruthDict(["p", "q"])) |> isbot -# normalization: negations compression ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# normalization: negations compression ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @test syntaxstring(normalize(parseformula("¬¬ p"))) == "p" @test syntaxstring(normalize(parseformula("¬¬¬ p"))) == "¬p" diff --git a/test/normalize.jl b/test/normalize.jl index fd5fa3f4..77c5ae37 100644 --- a/test/normalize.jl +++ b/test/normalize.jl @@ -72,6 +72,9 @@ end @test_nowarn normalize(parseformula("(⟨G⟩A ∧ ¬⟨G⟩(A ∧ ⟨A̅⟩C))"), prefer_implications = true) @test_nowarn normalize(parseformula("(⟨G⟩A ∧ ¬⟨G⟩(A ∧ ⟨A̅⟩C))"), prefer_implications = true) +@test "(b∧a)∨(d∧c)" |> parseformula |> normalize == + "(d∧c)∨(a∧b)" |> parseformula |> normalize + # φ = parseformula("(¬((q → p) → (q ∨ q))) → ⊤") # [check(φ, K, w; perform_normalization = false) for w in worlds] # φ = parseformula("¬(¬(¬(¬q ∨ p) ∨ (q ∨ q))) ∨ ⊤") From d603e6e9dfd634b67724a238e735c1c21fbf1664 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Wed, 6 Nov 2024 15:46:21 +0100 Subject: [PATCH 84/90] Improve separation --- src/SoleLogics.jl | 7 +- src/types/syntactical.jl | 4 +- src/utils/base-logic.jl | 631 +++++++++++++++++++++++++++++++ src/utils/parse.jl | 2 +- src/utils/propositional-logic.jl | 2 +- src/utils/syntactical.jl | 609 ----------------------------- src/utils/tools.jl | 2 +- test/check/propositional.jl | 8 +- 8 files changed, 645 insertions(+), 620 deletions(-) create mode 100644 src/utils/base-logic.jl diff --git a/src/SoleLogics.jl b/src/SoleLogics.jl index 31e16803..73e52b14 100644 --- a/src/SoleLogics.jl +++ b/src/SoleLogics.jl @@ -17,7 +17,7 @@ export iscrisp, isfinite, isnullary, isunary, isbinary export Syntactical, Connective, Formula, SyntaxStructure, SyntaxTree, SyntaxLeaf, - Atom, Truth, SyntaxBranch + AbstractAtom, Truth export Operator, SyntaxToken @@ -57,6 +57,9 @@ include("types/logic.jl") include("types/interpretation-sets.jl") +export Atom, SyntaxBranch + +include("utils/syntactical.jl") export TOP, ⊤ export BOT, ⊥ @@ -70,7 +73,7 @@ export BooleanAlgebra export BaseLogic -include("utils/syntactical.jl") +include("utils/base-logic.jl") export propositionallogic export inlinedisplay diff --git a/src/types/syntactical.jl b/src/types/syntactical.jl index 2739b84d..df9ba32a 100644 --- a/src/types/syntactical.jl +++ b/src/types/syntactical.jl @@ -604,7 +604,7 @@ function syntaxstring( if !remove_redundant_parentheses true elseif arity(chtok) == 0 - if chtok isa Atom && parenthesize_atoms + if chtok isa AbstractAtom && parenthesize_atoms true else false @@ -683,7 +683,7 @@ function syntaxstring( ch = token(children(φ)[1]) charity = arity(ch) if !function_notation && arity(tok) == 1 && - (charity == 1 || (ch isa Atom && !parenthesize_atoms)) + (charity == 1 || (ch isa AbstractAtom && !parenthesize_atoms)) # When not in function notation, print "¬p" instead of "¬(p)"; # note that "◊((p ∧ q) → s)" must not be simplified as "◊(p ∧ q) → s". lpar, rpar = "", "" diff --git a/src/utils/base-logic.jl b/src/utils/base-logic.jl new file mode 100644 index 00000000..a10f6788 --- /dev/null +++ b/src/utils/base-logic.jl @@ -0,0 +1,631 @@ + +doc_NEGATION = """ + const NEGATION = NamedConnective{:¬}() + const ¬ = NEGATION + arity(::typeof(¬)) = 1 + +Logical negation (also referred to as complement). +It can be typed by `\\neg`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_NEGATION)""" +const NEGATION = NamedConnective{:¬}() +"""$(doc_NEGATION)""" +const ¬ = NEGATION +arity(::typeof(¬)) = 1 + +# ¬ is a risky symbol, since by default it's precedence is defaulted to 0 by julia. +# Because of this, we override Base.operator_precedence. +precedence(::typeof(¬)) = Base.operator_precedence(:∧) + 1 + +doc_CONJUNCTION = """ + const CONJUNCTION = NamedConnective{:∧}() + const ∧ = CONJUNCTION + arity(::typeof(∧)) = 2 + +Logical conjunction. +It can be typed by `\\wedge`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_CONJUNCTION)""" +const CONJUNCTION = NamedConnective{:∧}() +"""$(doc_CONJUNCTION)""" +const ∧ = CONJUNCTION +arity(::typeof(∧)) = 2 + +doc_DISJUNCTION = """ + const DISJUNCTION = NamedConnective{:∨}() + const ∨ = DISJUNCTION + arity(::typeof(∨)) = 2 + +Logical disjunction. +It can be typed by `\\vee`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_DISJUNCTION)""" +const DISJUNCTION = NamedConnective{:∨}() +"""$(doc_DISJUNCTION)""" +const ∨ = DISJUNCTION +arity(::typeof(∨)) = 2 + +doc_IMPLICATION = """ + const IMPLICATION = NamedConnective{:→}() + const → = IMPLICATION + arity(::typeof(→)) = 2 + +Logical implication. +It can be typed by `\\to`. + +See also [`NamedConnective`](@ref), [`Connective`](@ref). +""" +"""$(doc_IMPLICATION)""" +const IMPLICATION = NamedConnective{:→}() +"""$(doc_IMPLICATION)""" +const → = IMPLICATION +arity(::typeof(→)) = 2 + +iscommutative(::typeof(∧)) = true +iscommutative(::typeof(→)) = false +iscommutative(::typeof(∨)) = true + +hasdual(::typeof(∧)) = true +dual(c::typeof(∧)) = typeof(∨) +hasdual(::typeof(∨)) = true +dual(c::typeof(∨)) = typeof(∧) + +############################################################################################ +###################################### BOOLEAN ALGEBRA ##################################### +############################################################################################ + +""" + struct BooleanTruth <: Truth + flag::Bool + end + +Structure for representing the Boolean truth values ⊤ and ⊥. +It wraps a flag which takes value `true` for ⊤ ([`TOP`](@ref)), +and `false` for ⊥ ([`BOT`](@ref)) + +See also [`BooleanAlgebra`](@ref). +""" +struct BooleanTruth <: Truth + flag::Bool +end + +istop(t::BooleanTruth) = t.flag +isbot(t::BooleanTruth) = !istop(t) + +syntaxstring(t::BooleanTruth; kwargs...) = istop(t) ? "⊤" : "⊥" + +function Base.show(io::IO, φ::BooleanTruth) + print(io, "$(syntaxstring(φ))") +end + +doc_TOP = """ + const TOP = BooleanTruth(true) + const ⊤ = TOP + +Canonical truth operator representing the value `true`. +It can be typed by `\\top`. + +See also [`BOT`](@ref), [`Truth`](@ref). +""" +"""$(doc_TOP)""" +const TOP = BooleanTruth(true) +"""$(doc_TOP)""" +const ⊤ = TOP + +doc_BOTTOM = """ + const BOT = BooleanTruth(false) + const ⊥ = BOT + +Canonical truth operator representing the value `false`. +It can be typed by `\\bot`. + +See also [`TOP`](@ref), [`Truth`](@ref). +""" +"""$(doc_BOTTOM)""" +const BOT = BooleanTruth(false) +"""$(doc_BOTTOM)""" +const ⊥ = BOT + +# NOTE: it could be useful to provide a macro to easily create +# a new set of Truth types. In particular, a new subtree of types must be planted +# as children of Truth, and new promotion rules are to be defined like below. +Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:BooleanTruth}) = BooleanTruth + +function Base.convert(::Type{BooleanTruth}, t::Bool)::BooleanTruth + return (t ? TOP : BOT) +end +function Base.convert(::Type{BooleanTruth}, t::Integer)::BooleanTruth + if isone(t) + return TOP + elseif iszero(t) + return BOT + else + return error("Cannot interpret Integer value $t as BooleanTruth.") + end +end + +Base.convert(::Type{Truth}, t::Bool) = Base.convert(BooleanTruth, t) +Base.convert(::Type{Truth}, t::Integer) = Base.convert(BooleanTruth, t) + +# NOTE: are these useful? +hasdual(::BooleanTruth) = true +dual(c::BooleanTruth) = BooleanTruth(!istop(c)) + +precedes(t1::BooleanTruth, t2::BooleanTruth) = istop(t1) < istop(t2) +truthmeet(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t1 : t2 +truthjoin(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t2 : t1 + +""" + struct BooleanAlgebra <: AbstractAlgebra{Bool} end + +A [Boolean algebra](https://en.wikipedia.org/wiki/Boolean_algebra), defined on the values +TOP (representing *truth*) and BOT (for bottom, representing *falsehood*). +For this algebra, the basic operators negation, +conjunction and disjunction (stylized as ¬, ∧, ∨) can be defined as the complement, minimum +and maximum, of the integer cast of `true` and `false`, respectively. + +See also [`Truth`](@ref). +""" +struct BooleanAlgebra <: AbstractAlgebra{BooleanTruth} end + +domain(::BooleanAlgebra) = [TOP, BOT] + +top(::BooleanAlgebra) = TOP +bot(::BooleanAlgebra) = BOT + +############################################################################################ + +# Standard semantics for NOT, AND, OR, IMPLIES +collatetruth(::typeof(¬), (ts,)::Tuple{BooleanTruth}) = istop(ts) ? BOT : TOP +function collatetruth(::typeof(∧), (t1, t2)::NTuple{N, T where T <: BooleanTruth}) where {N} + truthmeet(t1, t2) +end +function collatetruth(::typeof(∨), (t1, t2)::NTuple{N, T where T <: BooleanTruth}) where {N} + truthjoin(t1, t2) +end + +# Incomplete information +function simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth, BooleanTruth}) + istop(t1) && istop(t2) ? TOP : BOT +end +simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth, Formula}) = istop(t1) ? t2 : t1 +simplify(::typeof(∧), (t1, t2)::Tuple{Formula, BooleanTruth}) = istop(t2) ? t1 : t2 + +function simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth, BooleanTruth}) + isbot(t1) && isbot(t2) ? BOT : TOP +end +simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth, Formula}) = isbot(t1) ? t2 : t1 +simplify(::typeof(∨), (t1, t2)::Tuple{Formula, BooleanTruth}) = isbot(t2) ? t1 : t2 + +# The IMPLIES operator, →, falls back to using ¬ and ∨ +function collatetruth(::typeof(→), (t1, t2)::NTuple{2, BooleanTruth}) + return collatetruth(∨, (collatetruth(¬, (t1,)), t2)) +end + +############################################################################################ + +# With dense, discrete algebras, floats can be used. +# These are sketches for a few ideas. Note that truth values should be wrapped into Truth substructures: +# istop(ts::AbstractFloat)::Bool = isone(ts) +# isbot(ts::AbstractFloat)::Bool = iszero(ts) + +# # TODO idea: use full range for numbers! +# # istop(ts::AbstractFloat)::Bool = ts == typemax(typeof(ts)) +# # isbot(ts::AbstractFloat)::Bool = ts == typemin(typeof(ts)) +# istop(ts::Integer)::Bool = ts == typemax(typeof(ts)) +# isbot(ts::Integer)::Bool = ts == typemin(typeof(ts)) + +# TODO: +# struct DiscreteChainAlgebra{T} <: AbstractAlgebra{T} domain::Vector{T} end +# struct DenseChainAlgebra{T<:AbstractFloat} <: AbstractAlgebra{T} end + +# TODO: +# struct HeytingNode{T} end +# struct HeytingAlgebra{T} <: AbstractAlgebra{HeytingNode{T}} ... end + +############################################################################################ +########################################### LOGIC ########################################## +############################################################################################ + +""" + struct BaseLogic{G<:AbstractGrammar,A<:AbstractAlgebra} <: AbstractLogic{G,A} + grammar::G + algebra::A + end + +A basic logic based on a grammar and an algebra, where both the grammar and the algebra +are instantiated. + +See also [`grammar`](@ref), [`algebra`](@ref), +[`AbstractGrammar`](@ref), [`AbstractAlgebra`](@ref), [`AbstractLogic`](@ref). +""" +struct BaseLogic{G <: AbstractGrammar, A <: AbstractAlgebra} <: AbstractLogic{G, A} + grammar::G + algebra::A + + function BaseLogic{G, A}( + grammar::G = BASE_GRAMMAR, + algebra::A = BooleanAlgebra(), + ) where {G <: AbstractGrammar, A <: AbstractAlgebra} + # @assert all([goeswith(c, algebra) for c in operators(grammar)]) "Cannot instantiate BaseLogic{$(G),$(A)}: operators $(operators(grammar)[[goeswith(c, algebra) for c in operators(grammar)]]) cannot be interpreted on $(algebra)." # requires `goeswith` trait + return new{G, A}(grammar, algebra) + end + + function BaseLogic{G}( + grammar::G = BASE_GRAMMAR, + algebra::A = BooleanAlgebra(), + ) where {G <: AbstractGrammar, A <: AbstractAlgebra} + return BaseLogic{G, A}(grammar, algebra) + end + + function BaseLogic( + grammar::G = BASE_GRAMMAR, + algebra::A = BooleanAlgebra(), + ) where {G <: AbstractGrammar, A <: AbstractAlgebra} + return BaseLogic{G, A}(grammar, algebra) + end +end + +grammar(l::BaseLogic) = l.grammar +algebra(l::BaseLogic) = l.algebra + +function Base.isequal(a::BaseLogic, b::BaseLogic) + return Base.isequal(grammar(a), grammar(b)) && Base.isequal(algebra(a), algebra(b)) +end + +Base.hash(a::BaseLogic) = Base.hash(algebra(a), Base.hash(grammar(a))) + +function Base.show( + io::IO, l::BaseLogic{G, A},) where {G <: AbstractGrammar, A <: AbstractAlgebra} + if G <: CompleteFlatGrammar + print(io, + "BaseLogic with:\n\t- operators = [$(join(syntaxstring.(operators(l)), ", "))];\n\t- alphabet: $(alphabet(l));\n\t- algebra: $(algebra(l)).",) + else + print(io, + "BaseLogic{$(G),$(A)}(\n\t- grammar: $(grammar(l));\n\t- algebra: $(algebra(l))\n)",) + end +end + +############################################################################################ +#### EmptyAlphabet ######################################################################### +############################################################################################ + +""" + struct EmptyAlphabet{V} <: AbstractAlphabet{V} + atoms::Vector{Atom{V}} + end + +An alphabet wrapping atoms in a (finite) `Vector`. + +See also [`AbstractAlphabet`](@ref), [`atoms`](@ref). +""" +struct EmptyAlphabet{V} <: AbstractAlphabet{V} end +atoms(a::EmptyAlphabet{V}) where {V} = V[] +natoms(a::EmptyAlphabet) = 0 + +############################################################################################ +#### ExplicitAlphabet ###################################################################### +############################################################################################ + +""" + struct ExplicitAlphabet{V} <: AbstractAlphabet{V} + atoms::Vector{Atom{V}} + end + +An alphabet wrapping atoms in a (finite) `Vector`. + +See also [`AbstractAlphabet`](@ref), [`atoms`](@ref). +""" +struct ExplicitAlphabet{V} <: AbstractAlphabet{V} + atoms::Vector{Atom{V}} + + function ExplicitAlphabet{V}(atoms) where {V} + return new{V}(collect(atoms)) + end + + function ExplicitAlphabet(atoms::AbstractVector{<:Atom{V}}) where {V} + return ExplicitAlphabet{V}(collect(atoms)) + end + + function ExplicitAlphabet(atoms::AbstractVector{<:Atom}) + V = typejoin(valuetype.(atoms)...) + return ExplicitAlphabet{V}(collect(atoms)) + end + + function ExplicitAlphabet(atoms::AbstractVector{V}) where {V} + return ExplicitAlphabet{V}(Atom.(collect(atoms))) + end +end +atoms(a::ExplicitAlphabet) = a.atoms +natoms(a::ExplicitAlphabet) = length(atoms(a)) + +function Base.convert(::Type{AbstractAlphabet}, alphabet::Vector{<:Atom}) + ExplicitAlphabet(alphabet) +end + +############################################################################################ +#### AlphabetOfAny ######################################################################### +############################################################################################ + +""" + struct AlphabetOfAny{V} <: AbstractAlphabet{V} end + +An implicit, infinite alphabet that includes all atoms with values of a subtype of V. + +See also [`AbstractAlphabet`](@ref). +""" +struct AlphabetOfAny{V} <: AbstractAlphabet{V} end +Base.isfinite(::Type{<:AlphabetOfAny}) = false +Base.in(::Atom{PV}, ::AlphabetOfAny{VV}) where {PV, VV} = (PV <: VV) + +############################################################################################ +#### UnionAlphabet ######################################################################### +############################################################################################ + +""" +Alphabet given by the *union* of a number of (sub-)alphabets. + +See also +[`UnboundedScalarAlphabet`](@ref), +[`ScalarCondition`](@ref), +[`ScalarMetaCondition`](@ref). +""" + +struct UnionAlphabet{C, A <: AbstractAlphabet{C}} <: AbstractAlphabet{C} + subalphabets::Vector{A} +end + +subalphabets(a::UnionAlphabet) = a.subalphabets +nsubalphabets(a::UnionAlphabet) = length(subalphabets(a)) + +function Base.show(io::IO, a::UnionAlphabet) + println(io, "$(typeof(a)):") + for sa in subalphabets(a) + Base.show(io, sa) + end +end + +function atoms(a::UnionAlphabet) + return Iterators.flatten(Iterators.map(atoms, subalphabets(a))) +end + +natoms(a::UnionAlphabet) = sum(natoms, subalphabets(a)) + +function Base.in(p::Atom, a::UnionAlphabet) + return any(sa -> Base.in(p, sa), subalphabets(a)) +end + +############################################################################################ +#### CompleteFlatGrammar ################################################################### +############################################################################################ + +""" + struct CompleteFlatGrammar{V<:AbstractAlphabet,O<:Operator} <: AbstractGrammar{V,O} + alphabet::V + operators::Vector{<:O} + end + +V grammar of all well-formed formulas obtained by the arity-complying composition +of atoms of an alphabet of type `V`, and all operators in `operators`. +With n operators, this grammar has exactly n+1 production rules. +For example, with `operators = [∧,∨]`, the grammar (in Backus-Naur form) is: + + φ ::= p | φ ∧ φ | φ ∨ φ + +with p ∈ alphabet. Note: it is *flat* in the sense that all rules substitute the same +(unique and starting) non-terminal symbol φ. + +See also [`AbstractGrammar`](@ref), [`Operator`](@ref), [`alphabet`](@ref), +[`formulas`](@ref), [`connectives`](@ref), [`operators`](@ref), [`leaves`](@ref). +""" +struct CompleteFlatGrammar{V <: AbstractAlphabet, O <: Operator} <: AbstractGrammar{V, O} + alphabet::V + operators::Vector{<:O} + + function CompleteFlatGrammar{V, O}( + alphabet::V, + operators::Vector{<:O}, + ) where {V <: AbstractAlphabet, O <: Operator} + return new{V, O}(alphabet, operators) + end + + function CompleteFlatGrammar{V}( + alphabet::V, + operators::Vector{<:Operator}, + ) where {V <: AbstractAlphabet} + return new{V, Union{typeof.(operators)...}}( + alphabet, + Vector{Union{typeof.(operators)...}}(operators), + ) + end + + function CompleteFlatGrammar( + alphabet::V, + operators::Vector{<:Operator}, + ) where {V <: AbstractAlphabet} + return new{V, Union{typeof.(operators)...}}( + alphabet, + Vector{Union{typeof.(operators)...}}(operators), + ) + end +end + +alphabet(g::CompleteFlatGrammar) = g.alphabet +operators(g::CompleteFlatGrammar) = g.operators + +""" + connectives(g::AbstractGrammar) + +List all connectives appearing in a grammar. + +See also [`Connective`](@ref), [`nconnectives`](@ref). +""" +function connectives(g::AbstractGrammar)::AbstractVector{Connective} + return filter(!isnullary, operators(g)) +end + +""" + leaves(g::AbstractGrammar) + +List all leaves appearing in a grammar. + +See also [`SyntaxLeaf`](@ref), [`nleaves`](@ref). +""" +function leaves(g::AbstractGrammar) + return [atoms(alphabet(g))..., filter(isnullary, operators(g))...] +end + +# V complete grammar includes any *safe* syntax tree that can be built with +# the grammar token types. +function Base.in(φ::SyntaxTree, g::CompleteFlatGrammar)::Bool + return if token(φ) isa AbstractAtom + token(φ) in alphabet(g) + elseif token(φ) isa Operator + if operatorstype(φ) <: operatorstype(g) + true + else + all([Base.in(c, g) for c in children(φ)]) + end + else + false + end +end + +""" + formulas( + g::CompleteFlatGrammar{V,O} where {V,O}; + maxdepth::Integer, + nformulas::Union{Nothing,Integer} = nothing + )::Vector{SyntaxBranch} + +Generate all formulas whose `SyntaxBranch`s that are not taller than a given `maxdepth`. + +See also [`AbstractGrammar`](@ref), [`SyntaxBranch`](@ref). +""" +function formulas( + g::CompleteFlatGrammar{V, O} where {V, O}; + maxdepth::Integer, + nformulas::Union{Nothing, Integer} = nothing, +)::Vector{SyntaxTree} + @assert maxdepth >= 0 + @assert isnothing(nformulas) || nformulas > 0 + # With increasing `depth`, accumulate all formulas of length `depth` by combining all + # formulas of `depth-1` using all non-terminal symbols. + # Stop as soon as `maxdepth` is reached or `nformulas` have been generated. + depth = 0 + cur_formulas = Vector{SyntaxTree}(leaves(g)) + all_formulas = SyntaxTree[cur_formulas...] + while depth < maxdepth && (isnothing(nformulas) || length(all_formulas) < nformulas) + _nformulas = length(all_formulas) + cur_formulas = [] + for op in connectives(g) + for children in Iterators.product(fill(all_formulas, arity(op))...) + if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) + break + end + push!(cur_formulas, SyntaxTree(op, Tuple(children))) + end + if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) + break + end + end + append!(all_formulas, cur_formulas) + depth += 1 + end + return all_formulas +end + +# This dispatches are needed, since ambiguities might arise when choosing between +# in(φ::SyntaxTree, g::SoleLogics.CompleteFlatGrammar) and +# in(p::Atom, g::SoleLogics.AbstractGrammar) +Base.in(p::Atom, g::CompleteFlatGrammar) = Base.in(p, alphabet(g)) +Base.in(op::Truth, g::CompleteFlatGrammar) = (op <: operatorstype(g)) + +############################################################################################ +########################################### BASE ########################################### +############################################################################################ + +# This can be useful for standard phrasing of propositional formulas with string atoms. + +""" + const BASE_CONNECTIVES = [¬, ∧, ∨, →] + +Basic logical operators. + +See also [`NEGATION`](@ref), +[`CONJUNCTION`](@ref), +[`DISJUNCTION`](@ref), +[`IMPLICATION`](@ref), +[`Connective`](@ref). +""" +const BASE_CONNECTIVES = [¬, ∧, ∨, →] +const BaseConnectives = Union{typeof.(BASE_CONNECTIVES)...} + +const BASE_ALPHABET = AlphabetOfAny{String}() + +const BASE_GRAMMAR = CompleteFlatGrammar(BASE_ALPHABET, BASE_CONNECTIVES) +const BASE_ALGEBRA = BooleanAlgebra() + +const BASE_LOGIC = BaseLogic(BASE_GRAMMAR, BASE_ALGEBRA) + +function _baselogic(; + alphabet::Union{Nothing, Vector, AbstractAlphabet} = nothing, + operators::Union{Nothing, Vector{<:Operator}} = nothing, + grammar::Union{Nothing, AbstractGrammar} = nothing, + algebra::Union{Nothing, AbstractAlgebra} = nothing, + default_operators::Vector{<:Operator}, + logictypename::String, +) + if !(isnothing(grammar) || (isnothing(alphabet) && isnothing(operators))) + error("Cannot instantiate $(logictypename) by specifing a grammar " * + "together with argument(s): " * + join( + [ + (!isnothing(alphabet) ? ["alphabet"] : [])..., + (!isnothing(operators) ? ["operators"] : [])..., + (!isnothing(grammar) ? ["grammar"] : [])..., + ], + ", ",) * ".") + end + grammar = begin + if isnothing(grammar) + # @show alphabet + # @show operators + # @show BASE_GRAMMAR + # if isnothing(alphabet) && isnothing(operators) + # BASE_GRAMMAR + # else + alphabet = isnothing(alphabet) ? BASE_ALPHABET : alphabet + operators = begin + if isnothing(operators) + default_operators + else + if length(setdiff(operators, default_operators)) > 0 + @warn "Instantiating $(logictypename) with operators not in " * + "$(default_operators): " * + join(", ", setdiff(operators, default_operators)) * "." + end + operators + end + end + if alphabet isa Vector + alphabet = ExplicitAlphabet(map(Atom, alphabet)) + end + CompleteFlatGrammar(alphabet, operators) + # end + else + @assert isnothing(alphabet) && isnothing(operators) + grammar + end + end + + algebra = isnothing(algebra) ? BASE_ALGEBRA : algebra + + return BaseLogic(grammar, algebra) +end diff --git a/src/utils/parse.jl b/src/utils/parse.jl index 33838984..da4ec6cf 100644 --- a/src/utils/parse.jl +++ b/src/utils/parse.jl @@ -265,7 +265,7 @@ function parseformula( else # If the token is something else -> parse as Atom and push it atom = Atom(atom_parser(st)) - # @assert atom isa Atom string(atom) * + # @assert atom isa AbstractAtom string(atom) * # " is not an atom. Please, provide a valid atom_parser." atom end diff --git a/src/utils/propositional-logic.jl b/src/utils/propositional-logic.jl index ea4d1246..f251fb83 100644 --- a/src/utils/propositional-logic.jl +++ b/src/utils/propositional-logic.jl @@ -10,7 +10,7 @@ The keys represent the header of the table and the values the first row of the t """ function _hpretty_table(io::IO, keys::Any, values::Any) # Prepare columns names - _keys = map(x -> x isa Atom ? value(x) : x, collect(keys)) + _keys = map(x -> x isa AbstractAtom ? value(x) : x, collect(keys)) header = (_keys, string.(nameof.(typeof.(_keys)))) try diff --git a/src/utils/syntactical.jl b/src/utils/syntactical.jl index d9c48f90..029b0fa4 100644 --- a/src/utils/syntactical.jl +++ b/src/utils/syntactical.jl @@ -265,612 +265,3 @@ function associativity(c::NamedConnective) Base.operator_associativity(op) end end - -doc_NEGATION = """ - const NEGATION = NamedConnective{:¬}() - const ¬ = NEGATION - arity(::typeof(¬)) = 1 - -Logical negation (also referred to as complement). -It can be typed by `\\neg`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_NEGATION)""" -const NEGATION = NamedConnective{:¬}() -"""$(doc_NEGATION)""" -const ¬ = NEGATION -arity(::typeof(¬)) = 1 - -# ¬ is a risky symbol, since by default it's precedence is defaulted to 0 by julia. -# Because of this, we override Base.operator_precedence. -precedence(::typeof(¬)) = Base.operator_precedence(:∧) + 1 - -doc_CONJUNCTION = """ - const CONJUNCTION = NamedConnective{:∧}() - const ∧ = CONJUNCTION - arity(::typeof(∧)) = 2 - -Logical conjunction. -It can be typed by `\\wedge`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_CONJUNCTION)""" -const CONJUNCTION = NamedConnective{:∧}() -"""$(doc_CONJUNCTION)""" -const ∧ = CONJUNCTION -arity(::typeof(∧)) = 2 - -doc_DISJUNCTION = """ - const DISJUNCTION = NamedConnective{:∨}() - const ∨ = DISJUNCTION - arity(::typeof(∨)) = 2 - -Logical disjunction. -It can be typed by `\\vee`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_DISJUNCTION)""" -const DISJUNCTION = NamedConnective{:∨}() -"""$(doc_DISJUNCTION)""" -const ∨ = DISJUNCTION -arity(::typeof(∨)) = 2 - -doc_IMPLICATION = """ - const IMPLICATION = NamedConnective{:→}() - const → = IMPLICATION - arity(::typeof(→)) = 2 - -Logical implication. -It can be typed by `\\to`. - -See also [`NamedConnective`](@ref), [`Connective`](@ref). -""" -"""$(doc_IMPLICATION)""" -const IMPLICATION = NamedConnective{:→}() -"""$(doc_IMPLICATION)""" -const → = IMPLICATION -arity(::typeof(→)) = 2 - -iscommutative(::typeof(∧)) = true -iscommutative(::typeof(→)) = false -iscommutative(::typeof(∨)) = true - -hasdual(::typeof(∧)) = true -dual(c::typeof(∧)) = typeof(∨) -hasdual(::typeof(∨)) = true -dual(c::typeof(∨)) = typeof(∧) - -############################################################################################ -###################################### BOOLEAN ALGEBRA ##################################### -############################################################################################ - -""" - struct BooleanTruth <: Truth - flag::Bool - end - -Structure for representing the Boolean truth values ⊤ and ⊥. -It wraps a flag which takes value `true` for ⊤ ([`TOP`](@ref)), -and `false` for ⊥ ([`BOT`](@ref)) - -See also [`BooleanAlgebra`](@ref). -""" -struct BooleanTruth <: Truth - flag::Bool -end - -istop(t::BooleanTruth) = t.flag -isbot(t::BooleanTruth) = !istop(t) - -syntaxstring(t::BooleanTruth; kwargs...) = istop(t) ? "⊤" : "⊥" - -function Base.show(io::IO, φ::BooleanTruth) - print(io, "$(syntaxstring(φ))") -end - -doc_TOP = """ - const TOP = BooleanTruth(true) - const ⊤ = TOP - -Canonical truth operator representing the value `true`. -It can be typed by `\\top`. - -See also [`BOT`](@ref), [`Truth`](@ref). -""" -"""$(doc_TOP)""" -const TOP = BooleanTruth(true) -"""$(doc_TOP)""" -const ⊤ = TOP - -doc_BOTTOM = """ - const BOT = BooleanTruth(false) - const ⊥ = BOT - -Canonical truth operator representing the value `false`. -It can be typed by `\\bot`. - -See also [`TOP`](@ref), [`Truth`](@ref). -""" -"""$(doc_BOTTOM)""" -const BOT = BooleanTruth(false) -"""$(doc_BOTTOM)""" -const ⊥ = BOT - -# NOTE: it could be useful to provide a macro to easily create -# a new set of Truth types. In particular, a new subtree of types must be planted -# as children of Truth, and new promotion rules are to be defined like below. -Base.promote_rule(::Type{<:BooleanTruth}, ::Type{<:BooleanTruth}) = BooleanTruth - -function Base.convert(::Type{BooleanTruth}, t::Bool)::BooleanTruth - return (t ? TOP : BOT) -end -function Base.convert(::Type{BooleanTruth}, t::Integer)::BooleanTruth - if isone(t) - return TOP - elseif iszero(t) - return BOT - else - return error("Cannot interpret Integer value $t as BooleanTruth.") - end -end - -Base.convert(::Type{Truth}, t::Bool) = Base.convert(BooleanTruth, t) -Base.convert(::Type{Truth}, t::Integer) = Base.convert(BooleanTruth, t) - -# NOTE: are these useful? -hasdual(::BooleanTruth) = true -dual(c::BooleanTruth) = BooleanTruth(!istop(c)) - -precedes(t1::BooleanTruth, t2::BooleanTruth) = istop(t1) < istop(t2) -truthmeet(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t1 : t2 -truthjoin(t1::BooleanTruth, t2::BooleanTruth) = precedes(t1, t2) ? t2 : t1 - -""" - struct BooleanAlgebra <: AbstractAlgebra{Bool} end - -A [Boolean algebra](https://en.wikipedia.org/wiki/Boolean_algebra), defined on the values -TOP (representing *truth*) and BOT (for bottom, representing *falsehood*). -For this algebra, the basic operators negation, -conjunction and disjunction (stylized as ¬, ∧, ∨) can be defined as the complement, minimum -and maximum, of the integer cast of `true` and `false`, respectively. - -See also [`Truth`](@ref). -""" -struct BooleanAlgebra <: AbstractAlgebra{BooleanTruth} end - -domain(::BooleanAlgebra) = [TOP, BOT] - -top(::BooleanAlgebra) = TOP -bot(::BooleanAlgebra) = BOT - -############################################################################################ - -# Standard semantics for NOT, AND, OR, IMPLIES -collatetruth(::typeof(¬), (ts,)::Tuple{BooleanTruth}) = istop(ts) ? BOT : TOP -function collatetruth(::typeof(∧), (t1, t2)::NTuple{N, T where T <: BooleanTruth}) where {N} - truthmeet(t1, t2) -end -function collatetruth(::typeof(∨), (t1, t2)::NTuple{N, T where T <: BooleanTruth}) where {N} - truthjoin(t1, t2) -end - -# Incomplete information -function simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth, BooleanTruth}) - istop(t1) && istop(t2) ? TOP : BOT -end -simplify(::typeof(∧), (t1, t2)::Tuple{BooleanTruth, Formula}) = istop(t1) ? t2 : t1 -simplify(::typeof(∧), (t1, t2)::Tuple{Formula, BooleanTruth}) = istop(t2) ? t1 : t2 - -function simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth, BooleanTruth}) - isbot(t1) && isbot(t2) ? BOT : TOP -end -simplify(::typeof(∨), (t1, t2)::Tuple{BooleanTruth, Formula}) = isbot(t1) ? t2 : t1 -simplify(::typeof(∨), (t1, t2)::Tuple{Formula, BooleanTruth}) = isbot(t2) ? t1 : t2 - -# The IMPLIES operator, →, falls back to using ¬ and ∨ -function collatetruth(::typeof(→), (t1, t2)::NTuple{2, BooleanTruth}) - return collatetruth(∨, (collatetruth(¬, (t1,)), t2)) -end - -############################################################################################ - -# With dense, discrete algebras, floats can be used. -# These are sketches for a few ideas. Note that truth values should be wrapped into Truth substructures: -# istop(ts::AbstractFloat)::Bool = isone(ts) -# isbot(ts::AbstractFloat)::Bool = iszero(ts) - -# # TODO idea: use full range for numbers! -# # istop(ts::AbstractFloat)::Bool = ts == typemax(typeof(ts)) -# # isbot(ts::AbstractFloat)::Bool = ts == typemin(typeof(ts)) -# istop(ts::Integer)::Bool = ts == typemax(typeof(ts)) -# isbot(ts::Integer)::Bool = ts == typemin(typeof(ts)) - -# TODO: -# struct DiscreteChainAlgebra{T} <: AbstractAlgebra{T} domain::Vector{T} end -# struct DenseChainAlgebra{T<:AbstractFloat} <: AbstractAlgebra{T} end - -# TODO: -# struct HeytingNode{T} end -# struct HeytingAlgebra{T} <: AbstractAlgebra{HeytingNode{T}} ... end - -############################################################################################ -########################################### LOGIC ########################################## -############################################################################################ - -""" - struct BaseLogic{G<:AbstractGrammar,A<:AbstractAlgebra} <: AbstractLogic{G,A} - grammar::G - algebra::A - end - -A basic logic based on a grammar and an algebra, where both the grammar and the algebra -are instantiated. - -See also [`grammar`](@ref), [`algebra`](@ref), -[`AbstractGrammar`](@ref), [`AbstractAlgebra`](@ref), [`AbstractLogic`](@ref). -""" -struct BaseLogic{G <: AbstractGrammar, A <: AbstractAlgebra} <: AbstractLogic{G, A} - grammar::G - algebra::A - - function BaseLogic{G, A}( - grammar::G = BASE_GRAMMAR, - algebra::A = BooleanAlgebra(), - ) where {G <: AbstractGrammar, A <: AbstractAlgebra} - # @assert all([goeswith(c, algebra) for c in operators(grammar)]) "Cannot instantiate BaseLogic{$(G),$(A)}: operators $(operators(grammar)[[goeswith(c, algebra) for c in operators(grammar)]]) cannot be interpreted on $(algebra)." # requires `goeswith` trait - return new{G, A}(grammar, algebra) - end - - function BaseLogic{G}( - grammar::G = BASE_GRAMMAR, - algebra::A = BooleanAlgebra(), - ) where {G <: AbstractGrammar, A <: AbstractAlgebra} - return BaseLogic{G, A}(grammar, algebra) - end - - function BaseLogic( - grammar::G = BASE_GRAMMAR, - algebra::A = BooleanAlgebra(), - ) where {G <: AbstractGrammar, A <: AbstractAlgebra} - return BaseLogic{G, A}(grammar, algebra) - end -end - -grammar(l::BaseLogic) = l.grammar -algebra(l::BaseLogic) = l.algebra - -function Base.isequal(a::BaseLogic, b::BaseLogic) - return Base.isequal(grammar(a), grammar(b)) && Base.isequal(algebra(a), algebra(b)) -end - -Base.hash(a::BaseLogic) = Base.hash(algebra(a), Base.hash(grammar(a))) - -function Base.show( - io::IO, l::BaseLogic{G, A},) where {G <: AbstractGrammar, A <: AbstractAlgebra} - if G <: CompleteFlatGrammar - print(io, - "BaseLogic with:\n\t- operators = [$(join(syntaxstring.(operators(l)), ", "))];\n\t- alphabet: $(alphabet(l));\n\t- algebra: $(algebra(l)).",) - else - print(io, - "BaseLogic{$(G),$(A)}(\n\t- grammar: $(grammar(l));\n\t- algebra: $(algebra(l))\n)",) - end -end - -############################################################################################ -#### ExplicitAlphabet ###################################################################### -############################################################################################ - -""" - struct ExplicitAlphabet{V} <: AbstractAlphabet{V} - atoms::Vector{Atom{V}} - end - -An alphabet wrapping atoms in a (finite) `Vector`. - -See also [`AbstractAlphabet`](@ref), [`atoms`](@ref). -""" -struct ExplicitAlphabet{V} <: AbstractAlphabet{V} - atoms::Vector{Atom{V}} - - function ExplicitAlphabet{V}(atoms) where {V} - return new{V}(collect(atoms)) - end - - function ExplicitAlphabet(atoms::AbstractVector{Atom{V}}) where {V} - return ExplicitAlphabet{V}(collect(atoms)) - end - - function ExplicitAlphabet(atoms::AbstractVector{V}) where {V} - return ExplicitAlphabet{V}(Atom.(collect(atoms))) - end -end -atoms(a::ExplicitAlphabet) = a.atoms -natoms(a::ExplicitAlphabet) = length(atoms(a)) - -function Base.convert(::Type{AbstractAlphabet}, alphabet::Vector{<:Atom}) - ExplicitAlphabet(alphabet) -end - -############################################################################################ -#### AlphabetOfAny ######################################################################### -############################################################################################ - -""" - struct AlphabetOfAny{V} <: AbstractAlphabet{V} end - -An implicit, infinite alphabet that includes all atoms with values of a subtype of V. - -See also [`AbstractAlphabet`](@ref). -""" -struct AlphabetOfAny{V} <: AbstractAlphabet{V} end -Base.isfinite(::Type{<:AlphabetOfAny}) = false -Base.in(::Atom{PV}, ::AlphabetOfAny{VV}) where {PV, VV} = (PV <: VV) - -############################################################################################ -#### UnionAlphabet ######################################################################### -############################################################################################ - -""" -Alphabet given by the *union* of a number of (sub-)alphabets. - -See also -[`UnboundedScalarAlphabet`](@ref), -[`ScalarCondition`](@ref), -[`ScalarMetaCondition`](@ref). -""" - -struct UnionAlphabet{C, A <: AbstractAlphabet{C}} <: AbstractAlphabet{C} - subalphabets::Vector{A} -end - -subalphabets(a::UnionAlphabet) = a.subalphabets -nsubalphabets(a::UnionAlphabet) = length(subalphabets(a)) - -function Base.show(io::IO, a::UnionAlphabet) - println(io, "$(typeof(a)):") - for sa in subalphabets(a) - Base.show(io, sa) - end -end - -function atoms(a::UnionAlphabet) - return Iterators.flatten(Iterators.map(atoms, subalphabets(a))) -end - -natoms(a::UnionAlphabet) = sum(natoms, subalphabets(a)) - -function Base.in(p::Atom, a::UnionAlphabet) - return any(sa -> Base.in(p, sa), subalphabets(a)) -end - -############################################################################################ -#### CompleteFlatGrammar ################################################################### -############################################################################################ - -""" - struct CompleteFlatGrammar{V<:AbstractAlphabet,O<:Operator} <: AbstractGrammar{V,O} - alphabet::V - operators::Vector{<:O} - end - -V grammar of all well-formed formulas obtained by the arity-complying composition -of atoms of an alphabet of type `V`, and all operators in `operators`. -With n operators, this grammar has exactly n+1 production rules. -For example, with `operators = [∧,∨]`, the grammar (in Backus-Naur form) is: - - φ ::= p | φ ∧ φ | φ ∨ φ - -with p ∈ alphabet. Note: it is *flat* in the sense that all rules substitute the same -(unique and starting) non-terminal symbol φ. - -See also [`AbstractGrammar`](@ref), [`Operator`](@ref), [`alphabet`](@ref), -[`formulas`](@ref), [`connectives`](@ref), [`operators`](@ref), [`leaves`](@ref). -""" -struct CompleteFlatGrammar{V <: AbstractAlphabet, O <: Operator} <: AbstractGrammar{V, O} - alphabet::V - operators::Vector{<:O} - - function CompleteFlatGrammar{V, O}( - alphabet::V, - operators::Vector{<:O}, - ) where {V <: AbstractAlphabet, O <: Operator} - return new{V, O}(alphabet, operators) - end - - function CompleteFlatGrammar{V}( - alphabet::V, - operators::Vector{<:Operator}, - ) where {V <: AbstractAlphabet} - return new{V, Union{typeof.(operators)...}}( - alphabet, - Vector{Union{typeof.(operators)...}}(operators), - ) - end - - function CompleteFlatGrammar( - alphabet::V, - operators::Vector{<:Operator}, - ) where {V <: AbstractAlphabet} - return new{V, Union{typeof.(operators)...}}( - alphabet, - Vector{Union{typeof.(operators)...}}(operators), - ) - end -end - -alphabet(g::CompleteFlatGrammar) = g.alphabet -operators(g::CompleteFlatGrammar) = g.operators - -""" - connectives(g::AbstractGrammar) - -List all connectives appearing in a grammar. - -See also [`Connective`](@ref), [`nconnectives`](@ref). -""" -function connectives(g::AbstractGrammar)::AbstractVector{Connective} - return filter(!isnullary, operators(g)) -end - -""" - leaves(g::AbstractGrammar) - -List all leaves appearing in a grammar. - -See also [`SyntaxLeaf`](@ref), [`nleaves`](@ref). -""" -function leaves(g::AbstractGrammar) - return [atoms(alphabet(g))..., filter(isnullary, operators(g))...] -end - -# V complete grammar includes any *safe* syntax tree that can be built with -# the grammar token types. -function Base.in(φ::SyntaxTree, g::CompleteFlatGrammar)::Bool - return if token(φ) isa Atom - token(φ) in alphabet(g) - elseif token(φ) isa Operator - if operatorstype(φ) <: operatorstype(g) - true - else - all([Base.in(c, g) for c in children(φ)]) - end - else - false - end -end - -""" - formulas( - g::CompleteFlatGrammar{V,O} where {V,O}; - maxdepth::Integer, - nformulas::Union{Nothing,Integer} = nothing - )::Vector{SyntaxBranch} - -Generate all formulas whose `SyntaxBranch`s that are not taller than a given `maxdepth`. - -See also [`AbstractGrammar`](@ref), [`SyntaxBranch`](@ref). -""" -function formulas( - g::CompleteFlatGrammar{V, O} where {V, O}; - maxdepth::Integer, - nformulas::Union{Nothing, Integer} = nothing, -)::Vector{SyntaxTree} - @assert maxdepth >= 0 - @assert isnothing(nformulas) || nformulas > 0 - # With increasing `depth`, accumulate all formulas of length `depth` by combining all - # formulas of `depth-1` using all non-terminal symbols. - # Stop as soon as `maxdepth` is reached or `nformulas` have been generated. - depth = 0 - cur_formulas = Vector{SyntaxTree}(leaves(g)) - all_formulas = SyntaxTree[cur_formulas...] - while depth < maxdepth && (isnothing(nformulas) || length(all_formulas) < nformulas) - _nformulas = length(all_formulas) - cur_formulas = [] - for op in connectives(g) - for children in Iterators.product(fill(all_formulas, arity(op))...) - if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) - break - end - push!(cur_formulas, SyntaxTree(op, Tuple(children))) - end - if !isnothing(nformulas) && nformulas == _nformulas + length(cur_formulas) - break - end - end - append!(all_formulas, cur_formulas) - depth += 1 - end - return all_formulas -end - -# This dispatches are needed, since ambiguities might arise when choosing between -# in(φ::SyntaxTree, g::SoleLogics.CompleteFlatGrammar) and -# in(p::Atom, g::SoleLogics.AbstractGrammar) -Base.in(p::Atom, g::CompleteFlatGrammar) = Base.in(p, alphabet(g)) -Base.in(op::Truth, g::CompleteFlatGrammar) = (op <: operatorstype(g)) - -############################################################################################ -########################################### BASE ########################################### -############################################################################################ - -# This can be useful for standard phrasing of propositional formulas with string atoms. - -""" - const BASE_CONNECTIVES = [¬, ∧, ∨, →] - -Basic logical operators. - -See also [`NEGATION`](@ref), -[`CONJUNCTION`](@ref), -[`DISJUNCTION`](@ref), -[`IMPLICATION`](@ref), -[`Connective`](@ref). -""" -const BASE_CONNECTIVES = [¬, ∧, ∨, →] -const BaseConnectives = Union{typeof.(BASE_CONNECTIVES)...} - -const BASE_ALPHABET = AlphabetOfAny{String}() - -const BASE_GRAMMAR = CompleteFlatGrammar(BASE_ALPHABET, BASE_CONNECTIVES) -const BASE_ALGEBRA = BooleanAlgebra() - -const BASE_LOGIC = BaseLogic(BASE_GRAMMAR, BASE_ALGEBRA) - -function _baselogic(; - alphabet::Union{Nothing, Vector, AbstractAlphabet} = nothing, - operators::Union{Nothing, Vector{<:Operator}} = nothing, - grammar::Union{Nothing, AbstractGrammar} = nothing, - algebra::Union{Nothing, AbstractAlgebra} = nothing, - default_operators::Vector{<:Operator}, - logictypename::String, -) - if !(isnothing(grammar) || (isnothing(alphabet) && isnothing(operators))) - error("Cannot instantiate $(logictypename) by specifing a grammar " * - "together with argument(s): " * - join( - [ - (!isnothing(alphabet) ? ["alphabet"] : [])..., - (!isnothing(operators) ? ["operators"] : [])..., - (!isnothing(grammar) ? ["grammar"] : [])..., - ], - ", ",) * ".") - end - grammar = begin - if isnothing(grammar) - # @show alphabet - # @show operators - # @show BASE_GRAMMAR - # if isnothing(alphabet) && isnothing(operators) - # BASE_GRAMMAR - # else - alphabet = isnothing(alphabet) ? BASE_ALPHABET : alphabet - operators = begin - if isnothing(operators) - default_operators - else - if length(setdiff(operators, default_operators)) > 0 - @warn "Instantiating $(logictypename) with operators not in " * - "$(default_operators): " * - join(", ", setdiff(operators, default_operators)) * "." - end - operators - end - end - if alphabet isa Vector - alphabet = ExplicitAlphabet(map(Atom, alphabet)) - end - CompleteFlatGrammar(alphabet, operators) - # end - else - @assert isnothing(alphabet) && isnothing(operators) - grammar - end - end - - algebra = isnothing(algebra) ? BASE_ALGEBRA : algebra - - return BaseLogic(grammar, algebra) -end diff --git a/src/utils/tools.jl b/src/utils/tools.jl index 54f3901b..2fbc772c 100644 --- a/src/utils/tools.jl +++ b/src/utils/tools.jl @@ -307,7 +307,7 @@ function normalize( elseif reduce_negations && (chtok == →) && arity(chtok) == 2 # _normalize(∨(¬(grandchildren[1]), grandchildren[2])) ∧(_normalize(grandchildren[1]), _normalize(¬(grandchildren[2]))) - elseif reduce_negations && chtok isa Atom + elseif reduce_negations && chtok isa AbstractAtom if allow_atom_flipping && hasdual(chtok) dual(chtok) else diff --git a/test/check/propositional.jl b/test/check/propositional.jl index 8fb5fa01..f6ebfbf1 100644 --- a/test/check/propositional.jl +++ b/test/check/propositional.jl @@ -92,10 +92,10 @@ t2 = @test_nowarn TruthDict(Pair{Real,Bool}[1.0 => true, 2 => true, 3 => true]) @test_throws MethodError interpret("p", TruthDict(["p", "q"])) |> istop @test interpret(Atom("p"), TruthDict(["p", "q"])) |> istop -@test TruthDict(["p", "q"])["r"] isa Atom -@test TruthDict(["p", "q"])[Atom("r")] isa Atom -@test_throws MethodError interpret("r", TruthDict(["p", "q"])) isa Atom -@test interpret(Atom("r"), TruthDict(["p", "q"])) isa Atom +@test TruthDict(["p", "q"])["r"] isa AbstractAtom +@test TruthDict(["p", "q"])[Atom("r")] isa AbstractAtom +@test_throws MethodError interpret("r", TruthDict(["p", "q"])) isa AbstractAtom +@test interpret(Atom("r"), TruthDict(["p", "q"])) isa AbstractAtom @test DefaultedTruthDict(["p", "q"])["p"] |> istop @test DefaultedTruthDict(["p", "q"])[Atom("p")] |> istop From 9b3f4b48d30408c616af5ea62e1a52b1f6c71077 Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 6 Nov 2024 17:42:12 +0100 Subject: [PATCH 85/90] Dimensionality for Point --- src/utils/algebras/worlds/geometrical-worlds.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/utils/algebras/worlds/geometrical-worlds.jl b/src/utils/algebras/worlds/geometrical-worlds.jl index 12df0a66..49e649b2 100644 --- a/src/utils/algebras/worlds/geometrical-worlds.jl +++ b/src/utils/algebras/worlds/geometrical-worlds.jl @@ -1,4 +1,5 @@ import Base: length +import SoleBase: dimensionality ############################################################################################ # Point @@ -27,10 +28,10 @@ struct Point{N,T} <: GeometricalWorld xyz :: NTuple{N,T} # TODO check x<=N but only in debug mode # Point(x) = x<=N ... ? new(x) : error("Cannot instantiate Point(x={$x})") - + # TODO needed? Point(w::Point) = Point(w.xyz) - + Point{N,T}(xyz::NTuple{N,T}) where {N,T} = new{N,T}(xyz) Point{N,T}(xyz::Vararg{T,N}) where {N,T} = Point{N,T}(xyz) Point() = error("Cannot instantiate Point in a 0-dimensional space. " * @@ -51,6 +52,8 @@ X(w::Point) = w[1] Y(w::Point) = w[2] Z(w::Point) = w[3] +dimensionality(::Point{N}) where {N} = N + goeswithdim(::Type{P}, ::Val{N}) where {N,P<:Point{N}} = true # Useful aliases From 7a9f996b58915c8dd305c829cd3ca5391f92313e Mon Sep 17 00:00:00 2001 From: mauro-milella Date: Wed, 6 Nov 2024 17:43:17 +0100 Subject: [PATCH 86/90] Added `dimensionality` for Point type --- src/utils/algebras/worlds/geometrical-worlds.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/algebras/worlds/geometrical-worlds.jl b/src/utils/algebras/worlds/geometrical-worlds.jl index 49e649b2..ee9f81f7 100644 --- a/src/utils/algebras/worlds/geometrical-worlds.jl +++ b/src/utils/algebras/worlds/geometrical-worlds.jl @@ -53,6 +53,7 @@ Y(w::Point) = w[2] Z(w::Point) = w[3] dimensionality(::Point{N}) where {N} = N +dimensionality(::Type{Point{N}}) where {N} = N goeswithdim(::Type{P}, ::Val{N}) where {N,P<:Point{N}} = true From 35ab20c27d66ececc6825183e1f6db3ffbb72f66 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Thu, 7 Nov 2024 22:58:16 +0100 Subject: [PATCH 87/90] Bump --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 19735b78..be030605 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SoleLogics" uuid = "b002da8f-3cb3-4d91-bbe3-2953433912b5" authors = ["Mauro MILELLA", "Giovanni PAGLIARINI", "Edoardo PONSANESI", "Alberto PAPARELLA", "Eduard I. STAN"] -version = "0.10.2" +version = "0.10.3" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From d79b63e0f9ef389020f7e1337c37e1ebdac7d1be Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 8 Nov 2024 16:35:38 +0100 Subject: [PATCH 88/90] Add nworlds for OneWorld frame --- src/utils/algebras/worlds.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/algebras/worlds.jl b/src/utils/algebras/worlds.jl index 57ce2773..742118c7 100644 --- a/src/utils/algebras/worlds.jl +++ b/src/utils/algebras/worlds.jl @@ -19,6 +19,8 @@ inlinedisplay(w::OneWorld) = "−" # A propositional world is compatible with 0-dimensional datasets goeswithdim(::Type{OneWorld}, ::Val{0}) = true +nworlds(::OneWorld) = 1 + ############################################################################################ include("worlds/geometrical-worlds.jl") From f80d9ad309fc6ac180637c5791e295ee4dcd3cc2 Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Fri, 8 Nov 2024 16:36:08 +0100 Subject: [PATCH 89/90] bump --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index be030605..a58dfc18 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SoleLogics" uuid = "b002da8f-3cb3-4d91-bbe3-2953433912b5" authors = ["Mauro MILELLA", "Giovanni PAGLIARINI", "Edoardo PONSANESI", "Alberto PAPARELLA", "Eduard I. STAN"] -version = "0.10.3" +version = "0.10.4" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From ff5a033597ac45ca268243a71fec30052a7acdbb Mon Sep 17 00:00:00 2001 From: giopaglia <24519853+giopaglia@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:57:16 +0100 Subject: [PATCH 90/90] Fix tests --- test/formulas/generation.jl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/formulas/generation.jl b/test/formulas/generation.jl index c9dff866..ebe1172b 100644 --- a/test/formulas/generation.jl +++ b/test/formulas/generation.jl @@ -1,9 +1,9 @@ -# TODO adjust +# TODO @Mauro? # # Pool of valid propositional letters used those -# letters = [SoleLogics.Atom{Int64}(letter) for letter in string.(collect('a':'z'))] +# letters = [SoleLogics.Atom(letter) for letter in string.(collect('a':'z'))] # @testset "Formulas fundamental checks" begin -# function fxtest(h::Integer, letters::Vector{<:AbstractPropositionalLetter}) +# function fxtest(h::Integer, letters::Vector{<:SoleLogics.AbstractAtom}) # formula = gen_formula(h, letters) # # Height check @@ -35,7 +35,7 @@ # @testset "Modal depth regulation" begin # function fxtest_modal( # height::Integer, -# letters::Vector{<:AbstractPropositionalLetter}, +# letters::Vector{<:SoleLogics.AbstractAtom}, # max_modepth::Integer # ) # root = tree(gen_formula(height, letters, max_modepth = max_modepth)) @@ -55,10 +55,11 @@ gr = SoleLogics.CompleteFlatGrammar(alp, ops) using Random +@testset "maxheight parameter" begin rng = MersenneTwister(1) found_not_full = false for i in 1:100 - φ = randformula(rng, 4, SoleLogics.CompleteFlatGrammar(ExplicitAlphabet(@atoms p q r), [∨, ∧, ¬, □]), mode = :exactheight) + φ = randformula(rng, 4, SoleLogics.CompleteFlatGrammar(alp, [∨, ∧, ¬, □]), mode = :exactheight) @test height(φ) == 4 φs = (φ |> subformulas) ls = children.(φs) .|> x->map(height, x) @@ -69,7 +70,7 @@ end rng = MersenneTwister(1) found_shorter_than_maxheight = false for i in 1:100 - maxφ = randformula(4, SoleLogics.CompleteFlatGrammar(ExplicitAlphabet(@atoms p q r), [∨, ∧, ¬, □]), mode = :maxheight) + maxφ = randformula(4, SoleLogics.CompleteFlatGrammar(alp, [∨, ∧, ¬, □]), mode = :maxheight) @test height(maxφ) <= 4 height(maxφ) < 4 && (found_shorter_than_maxheight = true) && break end @@ -79,9 +80,10 @@ end rng = MersenneTwister(1) for i in 1:50 # Full syntax trees - fullφ = randformula(4, SoleLogics.CompleteFlatGrammar(ExplicitAlphabet(@atoms p q r), [∨, ∧, ¬, □]), mode = :full) + fullφ = randformula(4, SoleLogics.CompleteFlatGrammar(alp, [∨, ∧, ¬, □]), mode = :full) @test height(fullφ) == 4 φs = (fullφ |> subformulas) ls = children.(φs) .|> x->map(height, x) @test all(allequal, ls) end +end