defmodule SystemTest do use ExUnit.Case import Flex.Rule alias Flex.{Set, Variable, Rule, System} doctest Flex # # http://robotics.ee.uwa.edu.au/courses/faulttolerant/notes/FT5.pdf 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) r1 = fn [at1, at2, con] -> (at1 ~> "too hot" &&& at2 ~> "getting colder") >>> con ~> "cool" end r2 = fn [at1, at2, con] -> (at1 ~> "just right" &&& at2 ~> "getting colder") >>> con ~> "heat" end r3 = fn [at1, at2, con] -> (at1 ~> "too cold" &&& at2 ~> "getting colder") >>> con ~> "heat" end r4 = fn [at1, at2, con] -> (at1 ~> "too hot" &&& at2 ~> "no change") >>> con ~> "cool" end r5 = fn [at1, at2, con] -> (at1 ~> "just right" &&& at2 ~> "no change") >>> con ~> "do nothing" end r6 = fn [at1, at2, con] -> (at1 ~> "too cold" &&& at2 ~> "no change") >>> con ~> "heat" end r7 = fn [at1, at2, con] -> (at1 ~> "too hot" &&& at2 ~> "getting hotter") >>> con ~> "cool" end r8 = fn [at1, at2, con] -> (at1 ~> "just right" &&& at2 ~> "getting hotter") >>> con ~> "cool" end r9 = fn [at1, at2, con] -> (at1 ~> "too cold" &&& at2 ~> "getting hotter") >>> con ~> "heat" 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]) rules = [rule1, rule2, rule3, rule4, rule5, rule6, rule7, rule8, rule9] %{ant: [error, dt_error], cons: output, rules: rules, error: error, dt_error: dt_error} end test "Setup for the fuzzy logic system", %{ant: ant, cons: output, rules: rules} do {:ok, s_pid} = System.start_link(antecedent: ant, consequent: output, rules: rules) state = :sys.get_state(s_pid) assert is_map(state.antecedent) assert is_map(state.consequent) assert is_list(state.rules) end test "Compute an output with an input vector", %{ant: ant, cons: output, rules: rules} do {:ok, s_pid} = System.start_link(antecedent: ant, consequent: output, rules: rules) output = System.compute(s_pid, [-1, -2.5]) assert Float.floor(output, 1) == -63.4 end test "Setup with AST", %{ant: ant, cons: output, error: error, dt_error: dt_error} do r1 = {{{{"error", "too hot", "~>"}, {"dt_error", "getting colder", "~>"}, "&&&"}, "output", ">>>"}, "cool", "~>"} r2 = {{{{"error", "just right", "~>"}, {"dt_error", "getting colder", "~>"}, "&&&"}, "output", ">>>"}, "heat", "~>"} r3 = {{{{"error", "too cold", "~>"}, {"dt_error", "getting colder", "~>"}, "&&&"}, "output", ">>>"}, "heat", "~>"} r4 = {{{{"error", "too hot", "~>"}, {"dt_error", "no change", "~>"}, "&&&"}, "output", ">>>"}, "cool", "~>"} r5 = {{{{"error", "just right", "~>"}, {"dt_error", "no change", "~>"}, "&&&"}, "output", ">>>"}, "do nothing", "~>"} r6 = {{{{"error", "too cold", "~>"}, {"dt_error", "no change", "~>"}, "&&&"}, "output", ">>>"}, "heat", "~>"} r7 = {{{{"error", "too hot", "~>"}, {"dt_error", "getting hotter", "~>"}, "&&&"}, "output", ">>>"}, "cool", "~>"} r8 = {{{{"error", "just right", "~>"}, {"dt_error", "getting hotter", "~>"}, "&&&"}, "output", ">>>"}, "cool", "~>"} r9 = {{{{"error", "too cold", "~>"}, {"dt_error", "getting hotter", "~>"}, "&&&"}, "output", ">>>"}, "cool", "~>"} 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]) rules = [rule1, rule2, rule3, rule4, rule5, rule6, rule7, rule8, rule9] {:ok, s_pid} = System.start_link(antecedent: ant, consequent: output, rules: rules) output = System.compute(s_pid, [-1, -2.5]) assert Float.floor(output, 1) == -63.4 end test "System 2 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 end