Skip to content

support different types in membership functions parameters #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## Unreleased

- ![](https://img.shields.io/badge/BREAKING-red.svg) use a different type parameter for each field of membership functions. This allows membership functions to support mixed-type inputs like `GaussianMF(0, 0.1)`.

## v0.1.3 -- 2024-09-18

[view release on GitHub](https://github.com/lucaferranti/FuzzyLogic.jl/releases/tag/v0.1.3)

- ![](https://img.shields.io/badge/bugfix-purple.svg) fix bug in Julia code generation of ZShape and SShape mf
- ![](https://img.shields.io/badge/bugfix-purple.svg) disallow implicit conversion from interval to float
- ![](https://img.shields.io/badge/new%20feature-green.svg) added semi-elliptic and singleton membership functions
Expand Down
101 changes: 58 additions & 43 deletions src/membership_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
abstract type AbstractPredicate end
abstract type AbstractMembershipFunction <: AbstractPredicate end

function Base.show(io::IO, mf::MF) where {MF <: AbstractMembershipFunction}
props = [getproperty(mf, x) for x in fieldnames(MF)]
print(io, MF.name.name, "(")
for (i, p) in enumerate(props)
print(io, p)
i < length(props) && print(io, ", ")
end
print(io, ")")
end

"""
Singleton membership function. Equal to one at a single point and zero elsewhere.

Expand Down Expand Up @@ -35,13 +45,13 @@ $(TYPEDFIELDS)
mf = GeneralizedBellMF(2, 4, 5)
```
"""
struct GeneralizedBellMF{T <: Real, S <: Real} <: AbstractMembershipFunction
struct GeneralizedBellMF{Ta <: Real, Tb <: Real, Tc <: Real} <: AbstractMembershipFunction
"Width of the curve, the bigger the wider."
a::T
a::Ta
"Slope of the curve, the bigger the steeper."
b::S
b::Tb
"Center of the curve."
c::T
c::Tc
end
(mf::GeneralizedBellMF)(x) = 1 / (1 + abs((x - mf.c) / mf.a)^(2mf.b))

Expand All @@ -58,11 +68,11 @@ $(TYPEDFIELDS)
mf = GaussianMF(5.0, 1.5)
```
"""
struct GaussianMF{T <: Real} <: AbstractMembershipFunction
struct GaussianMF{Tm <: Real, Ts <: Real} <: AbstractMembershipFunction
"mean ``μ``."
mu::T
mu::Tm
"standard deviation ``σ``."
sig::T
sig::Ts
end
(mf::GaussianMF)(x) = exp(-(x - mf.mu)^2 / (2mf.sig^2))

Expand All @@ -79,13 +89,13 @@ $(TYPEDFIELDS)
mf = TriangularMF(3, 5, 7)
```
"""
struct TriangularMF{T <: Real} <: AbstractMembershipFunction
struct TriangularMF{Ta <: Real, Tb <: Real, Tc <: Real} <: AbstractMembershipFunction
"left foot."
a::T
a::Ta
"peak."
b::T
b::Tb
"right foot."
c::T
c::Tc
end
(mf::TriangularMF)(x) = max(min((x - mf.a) / (mf.b - mf.a), (mf.c - x) / (mf.c - mf.b)), 0)

Expand All @@ -102,15 +112,16 @@ $(TYPEDFIELDS)
mf = TrapezoidalMF(1, 3, 7, 9)
```
"""
struct TrapezoidalMF{T <: Real} <: AbstractMembershipFunction
struct TrapezoidalMF{Ta <: Real, Tb <: Real, Tc <: Real, Td <: Real} <:
AbstractMembershipFunction
"left foot."
a::T
a::Ta
"left shoulder."
b::T
b::Tb
"right shoulder."
c::T
c::Tc
"right foot."
d::T
d::Td
end
function (mf::TrapezoidalMF)(x)
return max(min((x - mf.a) / (mf.b - mf.a), 1, (mf.d - x) / (mf.d - mf.c)), 0)
Expand All @@ -130,11 +141,11 @@ $(TYPEDFIELDS)
mf = LinearMF(2, 8)
```
"""
struct LinearMF{T <: Real} <: AbstractMembershipFunction
struct LinearMF{Ta <: Real, Tb <: Real} <: AbstractMembershipFunction
"foot."
a::T
a::Ta
"shoulder."
b::T
b::Tb
end
(mf::LinearMF)(x) = max(min((x - mf.a) / (mf.b - mf.a), 1), 0)

Expand All @@ -151,11 +162,11 @@ $(TYPEDFIELDS)
mf = SigmoidMF(2, 5)
```
"""
struct SigmoidMF{T <: Real} <: AbstractMembershipFunction
struct SigmoidMF{Ta <: Real, Tc <: Real} <: AbstractMembershipFunction
"parameter controlling the slope of the curve."
a::T
a::Ta
"center of the slope."
c::T
c::Tc
end
(mf::SigmoidMF)(x) = 1 / (1 + exp(-mf.a * (x - mf.c)))

Expand All @@ -172,19 +183,22 @@ $(TYPEDFIELDS)
mf = DifferenceSigmoidMF(5, 2, 5, 7)
```
"""
struct DifferenceSigmoidMF{T <: Real} <: AbstractMembershipFunction
struct DifferenceSigmoidMF{Ta1 <: Real, Tc1 <: Real, Ta2 <: Real, Tc2 <: Real} <:
AbstractMembershipFunction
"slope of the first sigmoid."
a1::T
a1::Ta1
"center of the first sigmoid."
c1::T
c1::Tc1
"slope of the second sigmoid."
a2::T
a2::Ta2
"center of the second sigmoid."
c2::T
c2::Tc2
end
function (mf::DifferenceSigmoidMF)(x)
return max(min(1 / (1 + exp(-mf.a1 * (x - mf.c1))) -
1 / (1 + exp(-mf.a2 * (x - mf.c2))), 1), 0)
return max(
min(1 / (1 + exp(-mf.a1 * (x - mf.c1))) -
1 / (1 + exp(-mf.a2 * (x - mf.c2))), 1),
0)
end

"""
Expand Down Expand Up @@ -227,11 +241,11 @@ $(TYPEDFIELDS)
mf = SShapeMF(1, 8)
```
"""
struct SShapeMF{T <: Real} <: AbstractMembershipFunction
struct SShapeMF{Ta <: Real, Tb <: Real} <: AbstractMembershipFunction
"foot."
a::T
a::Ta
"shoulder."
b::T
b::Tb
end
function (s::SShapeMF)(x::T) where {T <: Real}
x <= s.a && return zero(float(T))
Expand All @@ -253,11 +267,11 @@ $(TYPEDFIELDS)
mf = ZShapeMF(3, 7)
```
"""
struct ZShapeMF{T <: Real} <: AbstractMembershipFunction
struct ZShapeMF{Ta <: Real, Tb <: Real} <: AbstractMembershipFunction
"shoulder."
a::T
a::Ta
"foot."
b::T
b::Tb
end
function (z::ZShapeMF)(x::T) where {T <: Real}
x <= z.a && return one(float(T))
Expand All @@ -279,15 +293,16 @@ $(TYPEDFIELDS)
mf = PiShapeMF(1, 4, 5, 10)
```
"""
struct PiShapeMF{T <: Real} <: AbstractMembershipFunction
struct PiShapeMF{Ta <: Real, Tb <: Real, Tc <: Real, Td <: Real} <:
AbstractMembershipFunction
"left foot."
a::T
a::Ta
"left shoulder."
b::T
b::Tb
"right shoulder."
c::T
c::Tc
"right foot."
d::T
d::Td
end
function (p::PiShapeMF)(x::T) where {T <: Real}
(x <= p.a || x >= p.d) && return zero(float(T))
Expand All @@ -311,11 +326,11 @@ $(TYPEDFIELDS)
mf = SemiEllipticMF(5.0, 4.0)
```
"""
struct SemiEllipticMF{T <: Real} <: AbstractMembershipFunction
struct SemiEllipticMF{Tcd <: Real, Trd <: Real} <: AbstractMembershipFunction
"center."
cd::T
cd::Tcd
"semi-axis."
rd::T
rd::Trd
end
function (semf::SemiEllipticMF)(x::Real)
cd, rd = semf.cd, semf.rd
Expand Down
46 changes: 24 additions & 22 deletions src/parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,21 @@ tipper
Inputs:
-------
service ∈ [0, 10] with membership functions:
poor = GaussianMF{Float64}(0.0, 1.5)
good = GaussianMF{Float64}(5.0, 1.5)
excellent = GaussianMF{Float64}(10.0, 1.5)
poor = GaussianMF(0.0, 1.5)
good = GaussianMF(5.0, 1.5)
excellent = GaussianMF(10.0, 1.5)

food ∈ [0, 10] with membership functions:
rancid = TrapezoidalMF{Int64}(-2, 0, 1, 3)
delicious = TrapezoidalMF{Int64}(7, 9, 10, 12)
rancid = TrapezoidalMF(-2, 0, 1, 3)
delicious = TrapezoidalMF(7, 9, 10, 12)


Outputs:
--------
tip ∈ [0, 30] with membership functions:
cheap = TriangularMF{Int64}(0, 5, 10)
average = TriangularMF{Int64}(10, 15, 20)
generous = TriangularMF{Int64}(20, 25, 30)
cheap = TriangularMF(0, 5, 10)
average = TriangularMF(10, 15, 20)
generous = TriangularMF(20, 25, 30)


Inference rules:
Expand Down Expand Up @@ -128,13 +128,13 @@ tipper
Inputs:
-------
service ∈ [0, 10] with membership functions:
poor = GaussianMF{Float64}(0.0, 1.5)
good = GaussianMF{Float64}(5.0, 1.5)
excellent = GaussianMF{Float64}(10.0, 1.5)
poor = GaussianMF(0.0, 1.5)
good = GaussianMF(5.0, 1.5)
excellent = GaussianMF(10.0, 1.5)

food ∈ [0, 10] with membership functions:
rancid = TrapezoidalMF{Int64}(-2, 0, 1, 3)
delicious = TrapezoidalMF{Int64}(7, 9, 10, 12)
rancid = TrapezoidalMF(-2, 0, 1, 3)
delicious = TrapezoidalMF(7, 9, 10, 12)


Outputs:
Expand Down Expand Up @@ -170,7 +170,7 @@ function _fis(ex::Expr, type)
inputs, outputs, opts, rules = parse_body(body, argsin, argsout, type)

fis = :($type(; name = $(QuoteNode(name)), inputs = $inputs,
outputs = $outputs, rules = $rules))
outputs = $outputs, rules = $rules))
append!(fis.args[2].args, opts)
return fis
end
Expand Down Expand Up @@ -227,21 +227,23 @@ function parse_body(body, argsin, argsout, type)
end

function parse_line!(inputs, outputs, rules, opts, line, argsin, argsout, type)
if @capture(line, var_:=begin args__ end)
if @capture(line, var_:=begin
args__
end)
var = to_var_name(var)
if var in argsin
push!(inputs.args[2].args, parse_variable(var, args))
elseif var in argsout
# TODO: makes this more scalable
push!(outputs.args[2].args,
type == :SugenoFuzzySystem ? parse_sugeno_output(var, args, argsin) :
parse_variable(var, args))
type == :SugenoFuzzySystem ? parse_sugeno_output(var, args, argsin) :
parse_variable(var, args))
else
throw(ArgumentError("Undefined variable $var"))
end
elseif @capture(line, for i_ in start_:stop_
sts__
end)
sts__
end)
for j in start:stop
for st in sts
ex = MacroTools.postwalk(x -> x == i ? j : x, st)
Expand Down Expand Up @@ -282,10 +284,10 @@ function parse_antecedent(ant)
return Expr(:call, :FuzzyOr, parse_antecedent(left), parse_antecedent(right))
elseif @capture(ant, subj_==prop_)
return Expr(:call, :FuzzyRelation, QuoteNode(to_var_name(subj)),
QuoteNode(to_var_name(prop)))
QuoteNode(to_var_name(prop)))
elseif @capture(ant, subj_!=prop_)
return Expr(:call, :FuzzyNegation, QuoteNode(to_var_name(subj)),
QuoteNode(to_var_name(prop)))
QuoteNode(to_var_name(prop)))
else
throw(ArgumentError("Invalid premise $ant"))
end
Expand All @@ -295,7 +297,7 @@ function parse_consequents(cons)
newcons = map(cons) do c
@capture(c, subj_==prop_) || throw(ArgumentError("Invalid consequence $c"))
Expr(:call, :FuzzyRelation, QuoteNode(to_var_name(subj)),
QuoteNode(to_var_name(prop)))
QuoteNode(to_var_name(prop)))
end
return Expr(:vect, newcons...)
end
Expand Down
8 changes: 8 additions & 0 deletions test/test_membership_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,11 @@ end
@test mf(4) == Interval(0.0, 0.0)
@test mf(5) == Interval(0.0, 0.0)
end

@testset "Mixed argument types" begin
mf = TriangularMF(1, 2.0, 3.0f0)
@test typeof(mf) == TriangularMF{Int64, Float64, Float32}

mf = TrapezoidalMF(1, 2.0, 3, 4.0)
@test typeof(mf) == TrapezoidalMF{Int64, Float64, Int64, Float64}
end
Loading