Skip to content

Commit 93b4806

Browse files
authored
support different types in membership functions parameters (#37)
1 parent 3da057e commit 93b4806

File tree

4 files changed

+96
-65
lines changed

4 files changed

+96
-65
lines changed

docs/src/changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
44

55
## Unreleased
66

7+
- ![](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)`.
8+
9+
## v0.1.3 -- 2024-09-18
10+
11+
[view release on GitHub](https://github.com/lucaferranti/FuzzyLogic.jl/releases/tag/v0.1.3)
12+
713
- ![](https://img.shields.io/badge/bugfix-purple.svg) fix bug in Julia code generation of ZShape and SShape mf
814
- ![](https://img.shields.io/badge/bugfix-purple.svg) disallow implicit conversion from interval to float
915
- ![](https://img.shields.io/badge/new%20feature-green.svg) added semi-elliptic and singleton membership functions

src/membership_functions.jl

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
abstract type AbstractPredicate end
33
abstract type AbstractMembershipFunction <: AbstractPredicate end
44

5+
function Base.show(io::IO, mf::MF) where {MF <: AbstractMembershipFunction}
6+
props = [getproperty(mf, x) for x in fieldnames(MF)]
7+
print(io, MF.name.name, "(")
8+
for (i, p) in enumerate(props)
9+
print(io, p)
10+
i < length(props) && print(io, ", ")
11+
end
12+
print(io, ")")
13+
end
14+
515
"""
616
Singleton membership function. Equal to one at a single point and zero elsewhere.
717
@@ -35,13 +45,13 @@ $(TYPEDFIELDS)
3545
mf = GeneralizedBellMF(2, 4, 5)
3646
```
3747
"""
38-
struct GeneralizedBellMF{T <: Real, S <: Real} <: AbstractMembershipFunction
48+
struct GeneralizedBellMF{Ta <: Real, Tb <: Real, Tc <: Real} <: AbstractMembershipFunction
3949
"Width of the curve, the bigger the wider."
40-
a::T
50+
a::Ta
4151
"Slope of the curve, the bigger the steeper."
42-
b::S
52+
b::Tb
4353
"Center of the curve."
44-
c::T
54+
c::Tc
4555
end
4656
(mf::GeneralizedBellMF)(x) = 1 / (1 + abs((x - mf.c) / mf.a)^(2mf.b))
4757

@@ -58,11 +68,11 @@ $(TYPEDFIELDS)
5868
mf = GaussianMF(5.0, 1.5)
5969
```
6070
"""
61-
struct GaussianMF{T <: Real} <: AbstractMembershipFunction
71+
struct GaussianMF{Tm <: Real, Ts <: Real} <: AbstractMembershipFunction
6272
"mean ``μ``."
63-
mu::T
73+
mu::Tm
6474
"standard deviation ``σ``."
65-
sig::T
75+
sig::Ts
6676
end
6777
(mf::GaussianMF)(x) = exp(-(x - mf.mu)^2 / (2mf.sig^2))
6878

@@ -79,13 +89,13 @@ $(TYPEDFIELDS)
7989
mf = TriangularMF(3, 5, 7)
8090
```
8191
"""
82-
struct TriangularMF{T <: Real} <: AbstractMembershipFunction
92+
struct TriangularMF{Ta <: Real, Tb <: Real, Tc <: Real} <: AbstractMembershipFunction
8393
"left foot."
84-
a::T
94+
a::Ta
8595
"peak."
86-
b::T
96+
b::Tb
8797
"right foot."
88-
c::T
98+
c::Tc
8999
end
90100
(mf::TriangularMF)(x) = max(min((x - mf.a) / (mf.b - mf.a), (mf.c - x) / (mf.c - mf.b)), 0)
91101

@@ -102,15 +112,16 @@ $(TYPEDFIELDS)
102112
mf = TrapezoidalMF(1, 3, 7, 9)
103113
```
104114
"""
105-
struct TrapezoidalMF{T <: Real} <: AbstractMembershipFunction
115+
struct TrapezoidalMF{Ta <: Real, Tb <: Real, Tc <: Real, Td <: Real} <:
116+
AbstractMembershipFunction
106117
"left foot."
107-
a::T
118+
a::Ta
108119
"left shoulder."
109-
b::T
120+
b::Tb
110121
"right shoulder."
111-
c::T
122+
c::Tc
112123
"right foot."
113-
d::T
124+
d::Td
114125
end
115126
function (mf::TrapezoidalMF)(x)
116127
return max(min((x - mf.a) / (mf.b - mf.a), 1, (mf.d - x) / (mf.d - mf.c)), 0)
@@ -130,11 +141,11 @@ $(TYPEDFIELDS)
130141
mf = LinearMF(2, 8)
131142
```
132143
"""
133-
struct LinearMF{T <: Real} <: AbstractMembershipFunction
144+
struct LinearMF{Ta <: Real, Tb <: Real} <: AbstractMembershipFunction
134145
"foot."
135-
a::T
146+
a::Ta
136147
"shoulder."
137-
b::T
148+
b::Tb
138149
end
139150
(mf::LinearMF)(x) = max(min((x - mf.a) / (mf.b - mf.a), 1), 0)
140151

@@ -151,11 +162,11 @@ $(TYPEDFIELDS)
151162
mf = SigmoidMF(2, 5)
152163
```
153164
"""
154-
struct SigmoidMF{T <: Real} <: AbstractMembershipFunction
165+
struct SigmoidMF{Ta <: Real, Tc <: Real} <: AbstractMembershipFunction
155166
"parameter controlling the slope of the curve."
156-
a::T
167+
a::Ta
157168
"center of the slope."
158-
c::T
169+
c::Tc
159170
end
160171
(mf::SigmoidMF)(x) = 1 / (1 + exp(-mf.a * (x - mf.c)))
161172

@@ -172,19 +183,22 @@ $(TYPEDFIELDS)
172183
mf = DifferenceSigmoidMF(5, 2, 5, 7)
173184
```
174185
"""
175-
struct DifferenceSigmoidMF{T <: Real} <: AbstractMembershipFunction
186+
struct DifferenceSigmoidMF{Ta1 <: Real, Tc1 <: Real, Ta2 <: Real, Tc2 <: Real} <:
187+
AbstractMembershipFunction
176188
"slope of the first sigmoid."
177-
a1::T
189+
a1::Ta1
178190
"center of the first sigmoid."
179-
c1::T
191+
c1::Tc1
180192
"slope of the second sigmoid."
181-
a2::T
193+
a2::Ta2
182194
"center of the second sigmoid."
183-
c2::T
195+
c2::Tc2
184196
end
185197
function (mf::DifferenceSigmoidMF)(x)
186-
return max(min(1 / (1 + exp(-mf.a1 * (x - mf.c1))) -
187-
1 / (1 + exp(-mf.a2 * (x - mf.c2))), 1), 0)
198+
return max(
199+
min(1 / (1 + exp(-mf.a1 * (x - mf.c1))) -
200+
1 / (1 + exp(-mf.a2 * (x - mf.c2))), 1),
201+
0)
188202
end
189203

190204
"""
@@ -227,11 +241,11 @@ $(TYPEDFIELDS)
227241
mf = SShapeMF(1, 8)
228242
```
229243
"""
230-
struct SShapeMF{T <: Real} <: AbstractMembershipFunction
244+
struct SShapeMF{Ta <: Real, Tb <: Real} <: AbstractMembershipFunction
231245
"foot."
232-
a::T
246+
a::Ta
233247
"shoulder."
234-
b::T
248+
b::Tb
235249
end
236250
function (s::SShapeMF)(x::T) where {T <: Real}
237251
x <= s.a && return zero(float(T))
@@ -253,11 +267,11 @@ $(TYPEDFIELDS)
253267
mf = ZShapeMF(3, 7)
254268
```
255269
"""
256-
struct ZShapeMF{T <: Real} <: AbstractMembershipFunction
270+
struct ZShapeMF{Ta <: Real, Tb <: Real} <: AbstractMembershipFunction
257271
"shoulder."
258-
a::T
272+
a::Ta
259273
"foot."
260-
b::T
274+
b::Tb
261275
end
262276
function (z::ZShapeMF)(x::T) where {T <: Real}
263277
x <= z.a && return one(float(T))
@@ -279,15 +293,16 @@ $(TYPEDFIELDS)
279293
mf = PiShapeMF(1, 4, 5, 10)
280294
```
281295
"""
282-
struct PiShapeMF{T <: Real} <: AbstractMembershipFunction
296+
struct PiShapeMF{Ta <: Real, Tb <: Real, Tc <: Real, Td <: Real} <:
297+
AbstractMembershipFunction
283298
"left foot."
284-
a::T
299+
a::Ta
285300
"left shoulder."
286-
b::T
301+
b::Tb
287302
"right shoulder."
288-
c::T
303+
c::Tc
289304
"right foot."
290-
d::T
305+
d::Td
291306
end
292307
function (p::PiShapeMF)(x::T) where {T <: Real}
293308
(x <= p.a || x >= p.d) && return zero(float(T))
@@ -311,11 +326,11 @@ $(TYPEDFIELDS)
311326
mf = SemiEllipticMF(5.0, 4.0)
312327
```
313328
"""
314-
struct SemiEllipticMF{T <: Real} <: AbstractMembershipFunction
329+
struct SemiEllipticMF{Tcd <: Real, Trd <: Real} <: AbstractMembershipFunction
315330
"center."
316-
cd::T
331+
cd::Tcd
317332
"semi-axis."
318-
rd::T
333+
rd::Trd
319334
end
320335
function (semf::SemiEllipticMF)(x::Real)
321336
cd, rd = semf.cd, semf.rd

src/parser.jl

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,21 @@ tipper
4949
Inputs:
5050
-------
5151
service ∈ [0, 10] with membership functions:
52-
poor = GaussianMF{Float64}(0.0, 1.5)
53-
good = GaussianMF{Float64}(5.0, 1.5)
54-
excellent = GaussianMF{Float64}(10.0, 1.5)
52+
poor = GaussianMF(0.0, 1.5)
53+
good = GaussianMF(5.0, 1.5)
54+
excellent = GaussianMF(10.0, 1.5)
5555
5656
food ∈ [0, 10] with membership functions:
57-
rancid = TrapezoidalMF{Int64}(-2, 0, 1, 3)
58-
delicious = TrapezoidalMF{Int64}(7, 9, 10, 12)
57+
rancid = TrapezoidalMF(-2, 0, 1, 3)
58+
delicious = TrapezoidalMF(7, 9, 10, 12)
5959
6060
6161
Outputs:
6262
--------
6363
tip ∈ [0, 30] with membership functions:
64-
cheap = TriangularMF{Int64}(0, 5, 10)
65-
average = TriangularMF{Int64}(10, 15, 20)
66-
generous = TriangularMF{Int64}(20, 25, 30)
64+
cheap = TriangularMF(0, 5, 10)
65+
average = TriangularMF(10, 15, 20)
66+
generous = TriangularMF(20, 25, 30)
6767
6868
6969
Inference rules:
@@ -128,13 +128,13 @@ tipper
128128
Inputs:
129129
-------
130130
service ∈ [0, 10] with membership functions:
131-
poor = GaussianMF{Float64}(0.0, 1.5)
132-
good = GaussianMF{Float64}(5.0, 1.5)
133-
excellent = GaussianMF{Float64}(10.0, 1.5)
131+
poor = GaussianMF(0.0, 1.5)
132+
good = GaussianMF(5.0, 1.5)
133+
excellent = GaussianMF(10.0, 1.5)
134134
135135
food ∈ [0, 10] with membership functions:
136-
rancid = TrapezoidalMF{Int64}(-2, 0, 1, 3)
137-
delicious = TrapezoidalMF{Int64}(7, 9, 10, 12)
136+
rancid = TrapezoidalMF(-2, 0, 1, 3)
137+
delicious = TrapezoidalMF(7, 9, 10, 12)
138138
139139
140140
Outputs:
@@ -170,7 +170,7 @@ function _fis(ex::Expr, type)
170170
inputs, outputs, opts, rules = parse_body(body, argsin, argsout, type)
171171

172172
fis = :($type(; name = $(QuoteNode(name)), inputs = $inputs,
173-
outputs = $outputs, rules = $rules))
173+
outputs = $outputs, rules = $rules))
174174
append!(fis.args[2].args, opts)
175175
return fis
176176
end
@@ -227,21 +227,23 @@ function parse_body(body, argsin, argsout, type)
227227
end
228228

229229
function parse_line!(inputs, outputs, rules, opts, line, argsin, argsout, type)
230-
if @capture(line, var_:=begin args__ end)
230+
if @capture(line, var_:=begin
231+
args__
232+
end)
231233
var = to_var_name(var)
232234
if var in argsin
233235
push!(inputs.args[2].args, parse_variable(var, args))
234236
elseif var in argsout
235237
# TODO: makes this more scalable
236238
push!(outputs.args[2].args,
237-
type == :SugenoFuzzySystem ? parse_sugeno_output(var, args, argsin) :
238-
parse_variable(var, args))
239+
type == :SugenoFuzzySystem ? parse_sugeno_output(var, args, argsin) :
240+
parse_variable(var, args))
239241
else
240242
throw(ArgumentError("Undefined variable $var"))
241243
end
242244
elseif @capture(line, for i_ in start_:stop_
243-
sts__
244-
end)
245+
sts__
246+
end)
245247
for j in start:stop
246248
for st in sts
247249
ex = MacroTools.postwalk(x -> x == i ? j : x, st)
@@ -282,10 +284,10 @@ function parse_antecedent(ant)
282284
return Expr(:call, :FuzzyOr, parse_antecedent(left), parse_antecedent(right))
283285
elseif @capture(ant, subj_==prop_)
284286
return Expr(:call, :FuzzyRelation, QuoteNode(to_var_name(subj)),
285-
QuoteNode(to_var_name(prop)))
287+
QuoteNode(to_var_name(prop)))
286288
elseif @capture(ant, subj_!=prop_)
287289
return Expr(:call, :FuzzyNegation, QuoteNode(to_var_name(subj)),
288-
QuoteNode(to_var_name(prop)))
290+
QuoteNode(to_var_name(prop)))
289291
else
290292
throw(ArgumentError("Invalid premise $ant"))
291293
end
@@ -295,7 +297,7 @@ function parse_consequents(cons)
295297
newcons = map(cons) do c
296298
@capture(c, subj_==prop_) || throw(ArgumentError("Invalid consequence $c"))
297299
Expr(:call, :FuzzyRelation, QuoteNode(to_var_name(subj)),
298-
QuoteNode(to_var_name(prop)))
300+
QuoteNode(to_var_name(prop)))
299301
end
300302
return Expr(:vect, newcons...)
301303
end

test/test_membership_functions.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,11 @@ end
166166
@test mf(4) == Interval(0.0, 0.0)
167167
@test mf(5) == Interval(0.0, 0.0)
168168
end
169+
170+
@testset "Mixed argument types" begin
171+
mf = TriangularMF(1, 2.0, 3.0f0)
172+
@test typeof(mf) == TriangularMF{Int64, Float64, Float32}
173+
174+
mf = TrapezoidalMF(1, 2.0, 3, 4.0)
175+
@test typeof(mf) == TrapezoidalMF{Int64, Float64, Int64, Float64}
176+
end

0 commit comments

Comments
 (0)