Skip to content

Commit 0fd0eca

Browse files
authored
matlab parser (#15)
* matlab parser initial implementation * add documentation * negated rules, sugeno and more tests and docs * update patch version and changelog * typo fix
1 parent b44d246 commit 0fd0eca

File tree

9 files changed

+345
-11
lines changed

9 files changed

+345
-11
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "FuzzyLogic"
22
uuid = "271df9f8-4390-4196-9d4f-bdd0b67035b3"
33
authors = ["Luca Ferranti"]
4-
version = "0.1.0"
4+
version = "0.1.1"
55

66
[deps]
77
Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"

docs/src/api/readwrite.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,10 @@ readfis
1919
parse_fcl
2020
@fcl_str
2121
```
22+
23+
## Parse Matlab
24+
25+
```@docs
26+
parse_matlabfis
27+
@matlabfis_str
28+
```

docs/src/changelog.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22

33
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
44

5-
## unreleased
5+
## v0.1.1 -- 2023-02-25
66

7-
- ![][badge-feature] Added fuzzy c-means
8-
- ![][badge-enhancement] added support Lukasiewicz, drastic, nilpotent and Hamacher T-norms and corresponding S-norms.
9-
- ![][badge-enhancement] dont build anonymous functions during mamdani inference, but evaluate output directly. Now defuzzifiers don't take a function as input, but an array.
10-
- ![][badge-feature] added piecewise linear membership functions
11-
![][badge-feature] added parser for Fuzzy Control Language.
12-
## v0.1.0 -- 2023-01-10
7+
[view release on GitHub](https://github.com/lucaferranti/FuzzyLogic.jl/releases/tag/v0.1.1)
8+
9+
- ![](https://img.shields.io/badge/new%20feature-green.svg) Added fuzzy c-means
10+
- ![](https://img.shields.io/badge/enhancement-blue.svg) added Lukasiewicz, drastic, nilpotent and Hamacher T-norms and corresponding S-norms.
11+
- ![](https://img.shields.io/badge/enhancement-blue.svg) dont build anonymous functions during mamdani inference, but evaluate output directly. Now defuzzifiers don't take a function as input, but an array.
12+
- ![](https://img.shields.io/badge/enhancement-blue.svg) added piecewise linear membership function
13+
- ![](https://img.shields.io/badge/new%20feature-green.svg) added parser for Fuzzy Control Language and matlab fis.
1314

14-
[view release on GitHub](https://github.com/lucaferranti/FuzzyLogic.jl/releases/tag/v0.1.0)
15+
## v0.1.0 -- 2023-01-10
1516

1617
**initial public release**
1718

src/FuzzyLogic.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export DifferenceSigmoidMF, LinearMF, GeneralizedBellMF, GaussianMF, ProductSigm
2929
## parsers
3030

3131
include("parsers/fcl.jl")
32-
32+
include("parsers/matlab_fis.jl")
3333
@reexport using .FCLParser
34+
@reexport using .MatlabParser
3435

3536
end

src/parsers/matlab_fis.jl

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
module MatlabParser
2+
3+
using Dictionaries
4+
using ..FuzzyLogic
5+
using ..FuzzyLogic: FuzzyAnd, FuzzyOr, FuzzyRule, FuzzyRelation, FuzzyNegation, Domain,
6+
Variable, memberships, AbstractMembershipFunction
7+
8+
export parse_matlabfis, @matlabfis_str
9+
10+
const MATLAB_JULIA = Dict("'mamdani'" => MamdaniFuzzySystem,
11+
"'sugeno'" => SugenoFuzzySystem,
12+
"and'min'" => MinAnd(), "and'prod'" => ProdAnd(),
13+
"or'max'" => MaxOr(), "or'probor'" => ProbSumOr(),
14+
"imp'min'" => MinImplication(), "imp'prod'" => ProdImplication(),
15+
"agg'max'" => MaxAggregator(),
16+
"agg'probor'" => ProbSumAggregator(),
17+
"'centroid'" => CentroidDefuzzifier(),
18+
"'bisector'" => BisectorDefuzzifier(),
19+
"'trapmf'" => TrapezoidalMF,
20+
"'trimf'" => TriangularMF,
21+
"'gaussmf'" => GaussianMF,
22+
"'gbellmf'" => GeneralizedBellMF,
23+
"'sigmf'" => SigmoidMF,
24+
"'dsigmf'" => DifferenceSigmoidMF,
25+
"'psigmf'" => ProductSigmoidMF,
26+
"'zmf'" => ZShapeMF,
27+
"'smf'" => SShapeMF,
28+
"'pimf'" => PiShapeMF,
29+
"'linzmf'" => LinearMF,
30+
"'linsmf'" => LinearMF,
31+
"'constant'" => ConstantSugenoOutput,
32+
"'linear'" => LinearSugenoOutput)
33+
34+
# Handle special cases where FuzzyLogic.jl and matlab dont store parameters the same way.
35+
function preprocess_params(mftype, mfparams; inputs = nothing)
36+
mftype in ("'gaussmf'", "'linzmf'") && return reverse(mfparams)
37+
mftype == "'linear'" &&
38+
return [Dictionary(inputs, mfparams[1:(end - 1)]), mfparams[end]]
39+
mfparams
40+
end
41+
42+
function parse_mf(line::AbstractString; inputs = nothing)
43+
mfname, mftype, mfparams = split(line, r"[:,]")
44+
mfname = Symbol(mfname[2:(end - 1)])
45+
mfparams = parse.(Float64, split(mfparams[2:(end - 1)]))
46+
mfparams = preprocess_params(mftype, mfparams; inputs)
47+
mfname, MATLAB_JULIA[mftype](mfparams...)
48+
end
49+
50+
function parse_var(var; inputs = nothing)
51+
dom = Domain(parse.(Float64, split(var["Range"][2:(end - 1)]))...)
52+
name = Symbol(var["Name"][2:(end - 1)])
53+
mfs = map(1:parse(Int, var["NumMFs"])) do i
54+
mfname, mf = parse_mf(var["MF$i"]; inputs)
55+
mfname => mf
56+
end |> dictionary
57+
name, Variable(dom, mfs)
58+
end
59+
60+
function parse_rule(line, inputnames, outputnames, inputmfs, outputmfs)
61+
ants, cons, op = split(line, r"[,:] ")
62+
antsidx = filter!(!iszero, parse.(Int, split(ants)))
63+
considx = filter!(!iszero, parse.(Int, split(cons)[1:length(outputnames)]))
64+
# TODO: weighted rules
65+
op = op == "1" ? FuzzyAnd : FuzzyOr
66+
length(antsidx) == 1 && (op = identity)
67+
ant = mapreduce(op, enumerate(antsidx)) do (var, mf)
68+
if mf > 0
69+
FuzzyRelation(inputnames[var], inputmfs[var][mf])
70+
else
71+
FuzzyNegation(inputnames[var], inputmfs[var][-mf])
72+
end
73+
end
74+
con = map(enumerate(considx)) do (var, mf)
75+
FuzzyRelation(outputnames[var], outputmfs[var][mf])
76+
end
77+
FuzzyRule(ant, con)
78+
end
79+
80+
function parse_rules(lines, inputs, outputs)
81+
inputnames = collect(keys(inputs))
82+
outputnames = collect(keys(outputs))
83+
inputmfs = collect.(keys.(memberships.(collect(inputs))))
84+
outputmfs = collect.(keys.(memberships.(collect(outputs))))
85+
FuzzyRule[parse_rule(line, inputnames, outputnames, inputmfs, outputmfs)
86+
for line in lines]
87+
end
88+
89+
"""
90+
parse_matlabfis(s::AbstractString)
91+
92+
Parse a fuzzy inference system from a string in Matlab FIS format.
93+
"""
94+
function parse_matlabfis(s::AbstractString)
95+
lines = strip.(split(s, "\n"))
96+
key = ""
97+
fis = Dict()
98+
for line in lines
99+
if occursin(r"\[[a-zA-Z0-9_]+\]", line)
100+
key = line
101+
fis[key] = ifelse(key == "[Rules]", [], Dict())
102+
elseif !isempty(line)
103+
if key != "[Rules]"
104+
k, v = split(line, "=")
105+
fis[key][k] = v
106+
else
107+
push!(fis[key], line)
108+
end
109+
end
110+
end
111+
sysinfo = fis["[System]"]
112+
inputs = Dictionary{Symbol, Variable}()
113+
for i in 1:parse(Int, sysinfo["NumInputs"])
114+
varname, var = parse_var(fis["[Input$i]"])
115+
insert!(inputs, varname, var)
116+
end
117+
outputs = Dictionary{Symbol, Variable}()
118+
for i in 1:parse(Int, sysinfo["NumOutputs"])
119+
varname, var = parse_var(fis["[Output$i]"]; inputs = collect(keys(inputs)))
120+
insert!(outputs, varname, var)
121+
end
122+
rules = parse_rules(fis["[Rules]"], inputs, outputs)
123+
opts = (; name = Symbol(sysinfo["Name"][2:(end - 1)]), inputs = inputs,
124+
outputs = outputs, rules = rules,
125+
and = MATLAB_JULIA["and" * sysinfo["AndMethod"]],
126+
or = MATLAB_JULIA["or" * sysinfo["OrMethod"]])
127+
128+
if sysinfo["Type"] == "'mamdani'"
129+
opts = (; opts..., implication = MATLAB_JULIA["imp" * sysinfo["ImpMethod"]],
130+
aggregator = MATLAB_JULIA["agg" * sysinfo["AggMethod"]],
131+
defuzzifier = MATLAB_JULIA[sysinfo["DefuzzMethod"]])
132+
end
133+
134+
MATLAB_JULIA[sysinfo["Type"]](; opts...)
135+
end
136+
137+
"""
138+
String macro to parse Matlab fis formats. See [`parse_matlabfis`](@ref) for more details.
139+
"""
140+
macro matlabfis_str(s::AbstractString)
141+
parse_matlabfis(s)
142+
end
143+
144+
end

src/readwrite.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ Read a fuzzy system from a file using a specified format.
88
99
Supported formats are
1010
11-
- `:fcl` (corresponding file extension `.fcl`)
11+
- `:fcl` -- Fuzzy Control Language (corresponding file extension `.fcl`)
12+
- `:matlab` -- Matlab fis (corresponding file extension `.fis`)
1213
"""
1314
function readfis(file::String, fmt::Union{Symbol, Nothing} = nothing)::AbstractFuzzySystem
1415
if isnothing(fmt)
1516
ex = split(file, ".")[end]
1617
fmt = if ex == "fcl"
1718
:fcl
19+
elseif ex == "fis"
20+
:matlab
1821
else
1922
throw(ArgumentError("Unrecognized extension $ex."))
2023
end
@@ -23,6 +26,8 @@ function readfis(file::String, fmt::Union{Symbol, Nothing} = nothing)::AbstractF
2326
s = read(file, String)
2427
if fmt === :fcl
2528
parse_fcl(s)
29+
elseif fmt === :matlab
30+
parse_matlabfis(s)
2631
else
2732
throw(ArgumentError("Unknown format $fmt."))
2833
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ testfiles = [
88
"test_plotting.jl",
99
"test_genfis.jl",
1010
"test_parsers/test_fcl.jl",
11+
"test_parsers/test_matlab.jl",
1112
"test_aqua.jl",
1213
"test_doctests.jl",
1314
]

test/test_parsers/data/tipper.fis

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[System]
2+
Name='tipper'
3+
Type='mamdani'
4+
NumInputs=2
5+
NumOutputs=1
6+
NumRules=3
7+
AndMethod='min'
8+
OrMethod='max'
9+
ImpMethod='min'
10+
AggMethod='max'
11+
DefuzzMethod='centroid'
12+
13+
[Input1]
14+
Name='service'
15+
Range=[0 10]
16+
NumMFs=3
17+
MF1='poor':'gaussmf',[1.5 0]
18+
MF2='good':'gaussmf',[1.5 5]
19+
MF3='excellent':'gaussmf',[1.5 10]
20+
21+
[Input2]
22+
Name='food'
23+
Range=[0 10]
24+
NumMFs=2
25+
MF1='rancid':'trapmf',[-2 0 1 3]
26+
MF2='delicious':'trapmf',[7 9 10 12]
27+
28+
[Output1]
29+
Name='tip'
30+
Range=[0 30]
31+
NumMFs=3
32+
MF1='cheap':'trimf',[0 5 10]
33+
MF2='average':'trimf',[10 15 20]
34+
MF3='generous':'trimf',[20 25 30]
35+
36+
[Rules]
37+
1 1, 1 (1) : 2
38+
2 0, 2 (1) : 1
39+
3 2, 3 (1) : 2

0 commit comments

Comments
 (0)