Packages

A toolkit for fuzzy logic, this library includes functions for creating fuzzy variables, sets, rules to create a Fuzzy Logic System (FLS).

Current section

Files

Jump to
flex test engine_adapters mamdani_test.exs
Raw

test/engine_adapters/mamdani_test.exs

defmodule MamdaniTest do
use ExUnit.Case
import Flex.Rule
alias Flex.{EngineAdapter.Mamdani, Rule, Set, System, Variable}
doctest Flex
setup do
t_h = Set.new(tag: "too hot", mf_type: "saturation", mf_params: [-2, 0, -4])
j_r = Set.new(tag: "just right", mf_type: "triangle", mf_params: [-2, 0, 2])
t_c = Set.new(tag: "too cold", mf_type: "shoulder", mf_params: [0, 2, 4])
fuzzy_sets = [t_h, j_r, t_c]
error = Variable.new(tag: "error", fuzzy_sets: fuzzy_sets, type: :antecedent, range: -4..4)
t_h = Set.new(tag: "getting hotter", mf_type: "saturation", mf_params: [-5, 0, -10])
j_r = Set.new(tag: "no change", mf_type: "triangle", mf_params: [-5, 0, 5])
t_c = Set.new(tag: "getting colder", mf_type: "shoulder", mf_params: [0, 5, 10])
fuzzy_sets = [t_h, j_r, t_c]
dt_error =
Variable.new(tag: "dt_error", fuzzy_sets: fuzzy_sets, type: :antecedent, range: -10..10)
t_h = Set.new(tag: "cool", mf_type: "saturation", mf_params: [-50, 0, -100])
j_r = Set.new(tag: "do nothing", mf_type: "triangle", mf_params: [-50, 0, 50])
t_c = Set.new(tag: "heat", mf_type: "shoulder", mf_params: [0, 50, 100])
fuzzy_sets = [t_h, j_r, t_c]
output =
Variable.new(tag: "output", fuzzy_sets: fuzzy_sets, type: :consequent, range: -100..100)
%{ant: [error, dt_error], cons: output}
end
test "Mamdani System test" do
nb = Set.new(tag: "nb", mf_type: "saturation", mf_params: [-5, -2.5, -5])
ns = Set.new(tag: "ns", mf_type: "triangle", mf_params: [-5, -2.5, 0])
ze = Set.new(tag: "ze", mf_type: "triangle", mf_params: [-2.5, 0, 2.5])
ps = Set.new(tag: "ps", mf_type: "triangle", mf_params: [0, 2.5, 5])
pb = Set.new(tag: "pb", mf_type: "shoulder", mf_params: [2.5, 5, 5])
fuzzy_sets = [nb, ns, ze, ps, pb]
error = Variable.new(tag: "error", fuzzy_sets: fuzzy_sets, type: :antecedent, range: -5..5)
nb = Set.new(tag: "nb", mf_type: "saturation", mf_params: [-0.5, -0.25, -0.5])
ns = Set.new(tag: "ns", mf_type: "triangle", mf_params: [-0.5, -0.25, 0])
ze = Set.new(tag: "ze", mf_type: "triangle", mf_params: [-0.25, 0, 0.25])
ps = Set.new(tag: "ps", mf_type: "triangle", mf_params: [0, 0.25, 0.5])
pb = Set.new(tag: "pb", mf_type: "shoulder", mf_params: [0.25, 0.5, 0.50])
fuzzy_sets = [nb, ns, ze, ps, pb]
dt_error =
Variable.new(tag: "dt_error", fuzzy_sets: fuzzy_sets, type: :antecedent, range: [-0.5, 0.5])
output =
Variable.new(tag: "output", fuzzy_sets: fuzzy_sets, type: :consequent, range: [-0.5, 0.5])
r1 = fn [at1, at2, con] ->
(at1 ~> "pb" &&& at2 ~> "ps") >>> con ~> "nb"
end
r2 = fn [at1, at2, con] ->
(at1 ~> "pb" &&& at2 ~> "pb") >>> con ~> "nb"
end
r3 = fn [at1, at2, con] ->
(at1 ~> "ps" &&& at2 ~> "pb") >>> con ~> "nb"
end
r4 = fn [at1, at2, con] ->
(at1 ~> "ze" &&& at2 ~> "pb") >>> con ~> "nb"
end
r5 = fn [at1, at2, con] ->
(at1 ~> "pb" &&& at2 ~> "ns") >>> con ~> "ns"
end
r6 = fn [at1, at2, con] ->
(at1 ~> "pb" &&& at2 ~> "ze") >>> con ~> "ns"
end
r7 = fn [at1, at2, con] ->
(at1 ~> "ps" &&& at2 ~> "ze") >>> con ~> "ns"
end
r8 = fn [at1, at2, con] ->
(at1 ~> "ps" &&& at2 ~> "ps") >>> con ~> "ns"
end
r9 = fn [at1, at2, con] ->
(at1 ~> "ze" &&& at2 ~> "ps") >>> con ~> "ns"
end
r10 = fn [at1, at2, con] ->
(at1 ~> "ns" &&& at2 ~> "pb") >>> con ~> "ns"
end
r11 = fn [at1, at2, con] ->
(at1 ~> "pb" &&& at2 ~> "nb") >>> con ~> "ze"
end
r12 = fn [at1, at2, con] ->
(at1 ~> "ps" &&& at2 ~> "ns") >>> con ~> "ze"
end
r13 = fn [at1, at2, con] ->
(at1 ~> "ze" &&& at2 ~> "ze") >>> con ~> "ze"
end
r14 = fn [at1, at2, con] ->
(at1 ~> "ns" &&& at2 ~> "ps") >>> con ~> "ze"
end
r15 = fn [at1, at2, con] ->
(at1 ~> "nb" &&& at2 ~> "pb") >>> con ~> "ze"
end
r16 = fn [at1, at2, con] ->
(at1 ~> "ps" &&& at2 ~> "nb") >>> con ~> "ps"
end
r17 = fn [at1, at2, con] ->
(at1 ~> "ze" &&& at2 ~> "ns") >>> con ~> "ps"
end
r18 = fn [at1, at2, con] ->
(at1 ~> "ns" &&& at2 ~> "ns") >>> con ~> "ps"
end
r19 = fn [at1, at2, con] ->
(at1 ~> "ns" &&& at2 ~> "ze") >>> con ~> "ps"
end
r20 = fn [at1, at2, con] ->
(at1 ~> "ze" &&& at2 ~> "nb") >>> con ~> "pb"
end
r21 = fn [at1, at2, con] ->
(at1 ~> "ns" &&& at2 ~> "nb") >>> con ~> "pb"
end
r22 = fn [at1, at2, con] ->
(at1 ~> "nb" &&& at2 ~> "nb") >>> con ~> "pb"
end
r23 = fn [at1, at2, con] ->
(at1 ~> "nb" &&& at2 ~> "ns") >>> con ~> "pb"
end
r24 = fn [at1, at2, con] ->
(at1 ~> "nb" &&& at2 ~> "ze") >>> con ~> "pb"
end
r25 = fn [at1, at2, con] ->
(at1 ~> "nb" &&& at2 ~> "ps") >>> con ~> "pb"
end
rule1 =
Rule.new(statement: r1, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule2 =
Rule.new(statement: r2, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule3 =
Rule.new(statement: r3, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule4 =
Rule.new(statement: r4, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule5 =
Rule.new(statement: r5, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule6 =
Rule.new(statement: r6, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule7 =
Rule.new(statement: r7, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule8 =
Rule.new(statement: r8, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule9 =
Rule.new(statement: r9, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule10 =
Rule.new(statement: r10, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule11 =
Rule.new(statement: r11, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule12 =
Rule.new(statement: r12, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule13 =
Rule.new(statement: r13, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule14 =
Rule.new(statement: r14, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule15 =
Rule.new(statement: r15, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule16 =
Rule.new(statement: r16, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule17 =
Rule.new(statement: r17, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule18 =
Rule.new(statement: r18, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule19 =
Rule.new(statement: r19, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule20 =
Rule.new(statement: r20, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule21 =
Rule.new(statement: r21, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule22 =
Rule.new(statement: r22, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule23 =
Rule.new(statement: r23, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule24 =
Rule.new(statement: r24, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rule25 =
Rule.new(statement: r25, consequent: output.tag, antecedent: [error.tag, dt_error.tag])
rules = [
rule1,
rule2,
rule3,
rule4,
rule5,
rule6,
rule7,
rule8,
rule9,
rule10,
rule11,
rule12,
rule13,
rule14,
rule15,
rule16,
rule17,
rule18,
rule19,
rule20,
rule21,
rule22,
rule23,
rule24,
rule25
]
{:ok, s_pid} =
System.start_link(antecedent: [error, dt_error], consequent: output, rules: rules)
assert System.compute(s_pid, [-15, -0.5]) == 0.5
assert System.compute(s_pid, [-15, -0.25]) == 0.5
assert System.compute(s_pid, [-15, 0]) == 0.5
assert System.compute(s_pid, [-15, 0.25]) == 0.5
assert System.compute(s_pid, [-15, 0.5]) == 0
assert System.compute(s_pid, [-7.5, -0.5]) == 0.5
assert System.compute(s_pid, [-7.5, -0.25]) == 0.5
assert System.compute(s_pid, [-7.5, 0]) == 0.5
assert System.compute(s_pid, [-7.5, 0.25]) == 0.5
assert System.compute(s_pid, [-7.5, 0.5]) == 0
assert System.compute(s_pid, [0, -0.5]) == 0.5
assert System.compute(s_pid, [0, -0.25]) == 0.25
assert System.compute(s_pid, [0, 0]) == 0
assert System.compute(s_pid, [0, 0.25]) == -0.25
assert System.compute(s_pid, [0, 0.5]) == -0.5
assert System.compute(s_pid, [7.5, -0.5]) == 0
assert System.compute(s_pid, [7.5, -0.25]) == -0.25
assert System.compute(s_pid, [7.5, 0]) == -0.25
assert System.compute(s_pid, [7.5, 0.25]) == -0.5
assert System.compute(s_pid, [7.5, 0.5]) == -0.5
assert System.compute(s_pid, [15, -0.5]) == 0
assert System.compute(s_pid, [15, -0.25]) == -0.25
assert System.compute(s_pid, [15, 0]) == -0.25
assert System.compute(s_pid, [15, 0.25]) == -0.5
assert System.compute(s_pid, [15, 0.5]) == -0.5
end
test "defuzzification test" do
t_h = Set.new(tag: "too hot", mf_type: "saturation", mf_params: [-50, 0, -100])
j_r = Set.new(tag: "just right", mf_type: "triangle", mf_params: [-50, 0, 50])
t_c = Set.new(tag: "too cold", mf_type: "shoulder", mf_params: [0, 50, 100])
fuzzy_sets = [t_h, j_r, t_c]
error =
Variable.new(tag: "error", fuzzy_sets: fuzzy_sets, type: :consequent, range: -100..100)
error = %{error | rule_output: [0.866, 0.5, 0]}
output = Mamdani.centroid_method(error)
assert Float.floor(output, 1) == -63.4
end
test "Inference engine", %{ant: [error, dt_error], cons: output} do
n_error = Variable.fuzzification(error, -1)
n_dt_error = Variable.fuzzification(dt_error, -2.5)
r1 = fn [at1, at2, con] ->
(at1 ~> "too hot" &&& at2 ~> "getting colder") >>> con ~> "cool"
end
r2 = fn [at1, at2, con] ->
(at1 ~> "too hot" &&& at2 ~> "no change") >>> con ~> "heat"
end
rule1 =
Rule.new(statement: r1, consequent: output.tag, antecedent: [n_error.tag, n_dt_error.tag])
rule2 =
Rule.new(statement: r2, consequent: output.tag, antecedent: [n_error.tag, n_dt_error.tag])
antecedent = %{
n_error.tag => n_error,
n_dt_error.tag => n_dt_error
}
output = Mamdani.inference_engine(antecedent, [rule1, rule2], output)
assert output.mf_values["cool"] == [0]
assert output.mf_values["heat"] == [0.5]
end
test "SISO Controller" do
# Fuzzy Variables + Fuzzy Sets
# Input
nb = Set.new(tag: "mb", mf_type: "saturation", mf_params: [-5, -3, -7])
ns = Set.new(tag: "ms", mf_type: "triangle", mf_params: [-4, -2.375, -0.75])
ze = Set.new(tag: "ze", mf_type: "triangle", mf_params: [-1, 0, 1])
ps = Set.new(tag: "ps", mf_type: "triangle", mf_params: [0.75, 2.375, 4])
pb = Set.new(tag: "pb", mf_type: "shoulder", mf_params: [3, 5, 7])
fuzzy_sets = [nb, ns, ze, ps, pb]
input = Variable.new(tag: "input", fuzzy_sets: fuzzy_sets, type: :antecedent, range: [-7, 7])
# Output
nb = Set.new(tag: "mb", mf_type: "saturation", mf_params: [-750, -500, -1000])
ns = Set.new(tag: "ms", mf_type: "triangle", mf_params: [-625, -375, -125])
ze = Set.new(tag: "ze", mf_type: "triangle", mf_params: [-187.5, 0, 187.5])
ps = Set.new(tag: "ps", mf_type: "triangle", mf_params: [125, 375, 625])
pb = Set.new(tag: "pb", mf_type: "shoulder", mf_params: [500, 750, 1000])
fuzzy_sets = [nb, ns, ze, ps, pb]
output =
Variable.new(tag: "output", fuzzy_sets: fuzzy_sets, type: :consequent, range: [-1000, 1000])
# Rules
r1 = fn [at1, con] -> at1 ~> "mb" >>> con ~> "mb" end
r2 = fn [at1, con] -> at1 ~> "ms" >>> con ~> "mb" end
r3 = fn [at1, con] -> at1 ~> "ze" >>> con ~> "mb" end
r4 = fn [at1, con] -> at1 ~> "ps" >>> con ~> "ms" end
r5 = fn [at1, con] -> at1 ~> "pb" >>> con ~> "ms" end
rule1 = Rule.new(statement: r1, consequent: output.tag, antecedent: [input.tag])
rule2 = Rule.new(statement: r2, consequent: output.tag, antecedent: [input.tag])
rule3 = Rule.new(statement: r3, consequent: output.tag, antecedent: [input.tag])
rule4 = Rule.new(statement: r4, consequent: output.tag, antecedent: [input.tag])
rule5 = Rule.new(statement: r5, consequent: output.tag, antecedent: [input.tag])
rules = [rule1, rule2, rule3, rule4, rule5]
{:ok, s_pid} = System.start_link(antecedent: [input], consequent: output, rules: rules)
assert System.compute(s_pid, [4.1]) == -375.0
end
end