Skip to content

Commit 3e67555

Browse files
authored
type-2 membership functions and sugeno (#18)
* first draft of type-2 mfs and interval types * add docstring and tests for intervals and membership functiosn * test for type-2 sugeno * fix plotting for type-2 fis * test for type-2 mf plotting
1 parent d16a75c commit 3e67555

12 files changed

+203
-9
lines changed

docs/make.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function generate_memberships()
7878
```
7979
8080
""")
81-
docstring = match(r"```julia\nmf = (\w+\(.+\))\n```", string(Docs.doc(mf)))
81+
docstring = match(r"```julia\nmf = (.+)\n```", string(Docs.doc(mf)))
8282
if !isnothing(docstring)
8383
mfex = only(docstring.captures)
8484
write(f, """

src/FuzzyLogic.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module FuzzyLogic
33
using Dictionaries, Reexport
44

55
include("docstrings.jl")
6+
include("intervals.jl")
67
include("membership_functions.jl")
78
include("variables.jl")
89
include("rules.jl")
@@ -16,7 +17,7 @@ include("readwrite.jl")
1617

1718
export DifferenceSigmoidMF, LinearMF, GeneralizedBellMF, GaussianMF, ProductSigmoidMF,
1819
SigmoidMF, TrapezoidalMF, TriangularMF, SShapeMF, ZShapeMF, PiShapeMF,
19-
PiecewiseLinearMF,
20+
PiecewiseLinearMF, WeightedMF, Type2MF, ..,
2021
ProdAnd, MinAnd, LukasiewiczAnd, DrasticAnd, NilpotentAnd, HamacherAnd,
2122
ProbSumOr, MaxOr, BoundedSumOr, DrasticOr, NilpotentOr, EinsteinOr,
2223
MinImplication, ProdImplication,

src/evaluation.jl

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# utilities to evaluate a fuzzy inference system
22

33
function (fr::FuzzyRelation)(fis::AbstractFuzzySystem,
4-
inputs::T)::float(eltype(T)) where {T <: NamedTuple}
4+
inputs::T) where {T <: NamedTuple}
55
memberships(fis.inputs[fr.subj])[fr.prop](inputs[fr.subj])
66
end
77

@@ -29,11 +29,11 @@ end
2929

3030
function (fis::MamdaniFuzzySystem)(inputs::T) where {T <: NamedTuple}
3131
Npoints = fis.defuzzifier.N + 1
32-
S = float(eltype(T))
32+
S = outputtype(typeof(fis.defuzzifier), T)
3333
res = Dictionary{Symbol, Vector{S}}(keys(fis.outputs),
3434
[zeros(S, Npoints) for _ in 1:length(fis.outputs)])
3535
@inbounds for rule in fis.rules
36-
w = rule.antecedent(fis, inputs)::S
36+
w = rule.antecedent(fis, inputs)
3737
for con in rule.consequent
3838
var = fis.outputs[con.subj]
3939
l, h = low(var.domain), high(var.domain)
@@ -43,9 +43,9 @@ function (fis::MamdaniFuzzySystem)(inputs::T) where {T <: NamedTuple}
4343
end
4444
end
4545

46-
Dictionary(keys(fis.outputs), map(zip(res, fis.outputs)) do (y, var)
47-
fis.defuzzifier(y, var.domain)
48-
end)
46+
Dictionary{Symbol, float(S)}(keys(fis.outputs), map(zip(res, fis.outputs)) do (y, var)
47+
fis.defuzzifier(y, var.domain)
48+
end)
4949
end
5050

5151
(fis::MamdaniFuzzySystem)(; inputs...) = fis(values(inputs))
@@ -59,7 +59,7 @@ function (fis::SugenoFuzzySystem)(inputs::T) where {T <: NamedTuple}
5959
zeros(float(eltype(T)), length(fis.outputs)))
6060
weights_sum = zero(S)
6161
for rule in fis.rules
62-
w = scale(rule.antecedent(fis, inputs), rule)::S
62+
w = scale(rule.antecedent(fis, inputs), rule)
6363
weights_sum += w
6464
for con in rule.consequent
6565
res[con.subj] += w * memberships(fis.outputs[con.subj])[con.prop](inputs)
@@ -72,3 +72,6 @@ end
7272
@inline function (fis::SugenoFuzzySystem)(inputs::Union{AbstractVector, Tuple})
7373
fis((; zip(collect(keys(fis.inputs)), inputs)...))
7474
end
75+
76+
outputtype(::Type{T}, S) where {T <: AbstractDefuzzifier} = float(eltype(S))
77+
outputtype(::Type{T}, S) where {T <: Type2Defuzzifier} = Interval{float(eltype(S))}

src/intervals.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
struct Interval{T <: Real}
2+
lo::T
3+
hi::T
4+
end
5+
6+
Base.convert(::Type{Interval{T}}, x::Real) where {T <: Real} = Interval(T(x), T(x))
7+
Base.convert(::Type{T}, x::Interval) where {T <: AbstractFloat} = (x.lo + x.hi) / 2
8+
9+
Base.float(::Type{Interval{T}}) where {T <: Real} = T
10+
11+
Base.:+(a::Interval) = a
12+
Base.:-(a::Interval) = Interval(-a.hi, -a.lo)
13+
Base.:+(a::Interval, b::Interval) = Interval(a.lo + b.lo, a.hi + b.hi)
14+
Base.:-(a::Interval, b::Interval) = Interval(a.lo - b.hi, a.hi - b.lo)
15+
16+
function Base.:*(a::Interval, b::Interval)
17+
Interval(extrema((a.lo * b.lo, a.lo * b.hi, a.hi * b.lo, a.hi * b.hi))...)
18+
end
19+
20+
function Base.:/(a::Interval, b::Interval)
21+
Interval(extrema((a.lo / b.lo, a.lo / b.hi, a.hi / b.lo, a.hi / b.hi))...)
22+
end
23+
Base.min(a::Interval, b::Interval) = Interval(min(a.lo, b.lo), min(a.hi, b.hi))
24+
Base.max(a::Interval, b::Interval) = Interval(max(a.lo, b.lo), max(a.hi, b.hi))
25+
Base.zero(::Type{Interval{T}}) where {T <: Real} = Interval(zero(T), zero(T))
26+
27+
for op in (:+, :-, :*, :/, :min, :max)
28+
@eval Base.$op(a::Interval, b::Real) = $op(a, Interval(b, b))
29+
@eval Base.$op(a::Real, b::Interval) = $op(Interval(a, a), b)
30+
end
31+
32+
function Base.:(a::Interval, b::Interval; kwargs...)
33+
(a.lo, b.lo; kwargs...) && (a.hi, b.hi; kwargs...)
34+
end

src/membership_functions.jl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,51 @@ end
311311

312312
# TODO: more robust soultion for all mfs
313313
Base.:(==)(mf1::PiecewiseLinearMF, mf2::PiecewiseLinearMF) = mf1.points == mf2.points
314+
315+
"""
316+
A membership function scaled by a parameter ``0 ≤ w ≤ 1``.
317+
318+
$(TYPEDFIELDS)
319+
320+
### Example
321+
322+
```julia
323+
mf = 0.5 * TriangularMF(1, 2, 3)
324+
```
325+
"""
326+
struct WeightedMF{MF <: AbstractMembershipFunction, T <: Real} <: AbstractMembershipFunction
327+
"membership function."
328+
mf::MF
329+
"scaling factor."
330+
w::T
331+
end
332+
(wmf::WeightedMF)(x) = wmf.w * wmf.mf(x)
333+
334+
Base.show(io::IO, wmf::WeightedMF) = print(io, wmf.w, wmf.mf)
335+
336+
Base.:*(w::Real, mf::AbstractMembershipFunction) = WeightedMF(mf, w)
337+
Base.:*(mf::AbstractMembershipFunction, w::Real) = WeightedMF(mf, w)
338+
339+
"""
340+
A type-2 membership function.
341+
342+
$(TYPEDFIELDS)
343+
344+
### Example
345+
346+
```julia
347+
mf = 0.7 * TriangularMF(3, 5, 7) .. TriangularMF(1, 5, 9)
348+
```
349+
"""
350+
struct Type2MF{MF1 <: AbstractMembershipFunction, MF2 <: AbstractMembershipFunction} <:
351+
AbstractMembershipFunction
352+
"lower membership function."
353+
lo::MF1
354+
"upper membership function."
355+
hi::MF2
356+
end
357+
(mf2::Type2MF)(x) = Interval(mf2.lo(x), mf2.hi(x))
358+
359+
..(mf1::AbstractMembershipFunction, mf2::AbstractMembershipFunction) = Type2MF(mf1, mf2)
360+
361+
Base.show(io::IO, mf2::Type2MF) = print(io, mf2.lo, " .. ", mf2.hi)

src/options.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,5 @@ function (bd::BisectorDefuzzifier)(y, dom::Domain{T})::float(T) where {T}
202202
end
203203

204204
_trapz(dx, y) = (2sum(y) - first(y) - last(y)) * dx / 2
205+
206+
abstract type Type2Defuzzifier <: AbstractDefuzzifier end

src/plotting.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ using RecipesBase
66
x -> mf(x), low, high
77
end
88

9+
@recipe function f(mf::Type2MF, low::Real, high::Real)
10+
legend --> nothing
11+
fillrange := x -> mf.hi(x)
12+
fillalpha --> 0.25
13+
lw --> 3
14+
x -> mf.lo(x), low, high
15+
end
16+
917
@recipe f(mf::AbstractPredicate, dom::Domain) = mf, low(dom), high(dom)
1018

1119
# plot sugeno membership functions

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using SafeTestsets, Test
22

33
testfiles = [
4+
"test_intervals.jl",
45
"test_membership_functions.jl",
56
"test_settings.jl",
67
"test_parser.jl",

test/test_evaluation.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,32 @@ end
119119
@test fis2(service = 7, food = 8)[:tip]19.639 atol=1e-3
120120
@test fis2((7, 8))[:tip]19.639 atol=1e-3
121121
end
122+
123+
@testset "Type-2 Sugeno" begin
124+
fis = @sugfis function tipper(service, food)::tip
125+
service := begin
126+
domain = 0:10
127+
poor = 0.6 * GaussianMF(0.0, 1.5) .. GaussianMF(0.0, 1.5)
128+
good = 0.6 * GaussianMF(5.0, 1.5) .. GaussianMF(5.0, 1.5)
129+
excellent = 0.9 * GaussianMF(10.0, 1.5) .. GaussianMF(10.0, 1.5)
130+
end
131+
132+
food := begin
133+
domain = 0:10
134+
rancid = 0.8 * TrapezoidalMF(-2, 0, 1, 3) .. TrapezoidalMF(-2, 0, 1, 4)
135+
delicious = 0.8 * TrapezoidalMF(8, 9, 10, 12) .. TrapezoidalMF(7, 9, 10, 12)
136+
end
137+
138+
tip := begin
139+
domain = 0:30
140+
cheap = 5.002
141+
average = 15
142+
generous = 24.998
143+
end
144+
145+
service == poor || food == rancid --> tip == cheap * 0.5
146+
service == good --> tip == average
147+
service == excellent || food == delicious --> tip == generous
148+
end
149+
@test fis(service = 2, food = 3)[:tip]FuzzyLogic.Interval(5.304, 19.792) atol=1e-3
150+
end

test/test_intervals.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using FuzzyLogic, Test
2+
using FuzzyLogic: Interval
3+
4+
@testset "interval operations" begin
5+
a = Interval(0.1, 1.0)
6+
b = Interval(0.2, 0.8)
7+
8+
@test Interval(1.0, 2.0) Interval(nextfloat(1.0), prevfloat(2.0))
9+
@test +a == a
10+
@test -a == Interval(-1.0, -0.1)
11+
@test a + b Interval(0.3, 1.8)
12+
@test a - b Interval(-0.7, 0.8)
13+
@test a * b Interval(0.02, 0.8)
14+
@test a / b Interval(0.125, 5.0)
15+
@test min(a, b) == Interval(0.1, 0.8)
16+
@test max(a, b) == Interval(0.2, 1.0)
17+
@test zero(Interval{Float64}) == Interval(0.0, 0.0)
18+
19+
@test convert(Interval{Float64}, 1.0) == Interval(1.0, 1.0)
20+
@test convert(Float64, Interval(0.0, 1.0)) == 0.5
21+
@test float(Interval{BigFloat}) == BigFloat
22+
end

0 commit comments

Comments
 (0)