Current section
Files
Jump to
Current section
Files
lib/engine_adapters/takagi_sugeno.ex
defmodule Flex.EngineAdapter.TakagiSugeno do
@moduledoc """
Takagi-Sugeno-Kang fuzzy inference uses singleton output membership functions that are either constant or a linear function of the input values.
The defuzzification process for a Sugeno system is more computationally efficient compared to that of a Mamdani system,
since it uses a weighted average or weighted sum of a few data points rather than compute a centroid of a two-dimensional area.
"""
alias Flex.{EngineAdapter, EngineAdapter.State, Variable}
@behaviour EngineAdapter
import Flex.Rule, only: [statement: 2, get_rule_parameters: 3]
@impl EngineAdapter
def validation(engine_state, _antecedent, _rules, _consequent),
do: engine_state
@impl EngineAdapter
def fuzzification(%State{input_vector: input_vector} = engine_state, antecedent) do
fuzzy_antecedent = EngineAdapter.default_fuzzification(input_vector, antecedent, %{})
%{engine_state | fuzzy_antecedent: fuzzy_antecedent}
end
@impl EngineAdapter
def inference(
%State{fuzzy_antecedent: fuzzy_antecedent, input_vector: input_vector} = engine_state,
rules,
consequent
) do
fuzzy_consequent =
fuzzy_antecedent
|> inference_engine(rules, consequent)
|> compute_output_level(input_vector)
%{engine_state | fuzzy_consequent: fuzzy_consequent}
end
@impl EngineAdapter
def defuzzification(%State{fuzzy_consequent: fuzzy_consequent} = engine_state) do
%{engine_state | crisp_output: weighted_average_method(fuzzy_consequent)}
end
def inference_engine(_fuzzy_antecedent, [], consequent), do: consequent
def inference_engine(fuzzy_antecedent, [rule | tail], consequent) do
rule_parameters = get_rule_parameters(rule.antecedent, fuzzy_antecedent, []) ++ [consequent]
consequent =
if is_function(rule.statement) do
rule.statement.(rule_parameters)
else
args = Map.merge(fuzzy_antecedent, %{consequent.tag => consequent})
statement(rule.statement, args)
end
inference_engine(fuzzy_antecedent, tail, consequent)
end
defp compute_output_level(cons_var, input_vector) do
rules_output =
Enum.reduce(cons_var.fuzzy_sets, [], fn output_fuzzy_set, acc ->
output_value =
for _ <- cons_var.mf_values[output_fuzzy_set.tag], into: [] do
output_fuzzy_set.mf.(input_vector)
end
acc ++ output_value
end)
%{cons_var | rule_output: rules_output}
end
@doc """
Turns an consequent fuzzy variable (output) from a fuzzy value to a crisp value (weighted average method).
"""
@spec weighted_average_method(Flex.Variable.t()) :: float
def weighted_average_method(%Variable{type: type} = fuzzy_var) when type == :consequent do
fuzzy_var
|> build_fuzzy_sets_strength_list()
|> fuzzy_to_crisp(fuzzy_var.rule_output, 0, 0)
end
defp build_fuzzy_sets_strength_list(%Variable{fuzzy_sets: fuzzy_sets, mf_values: mf_values}) do
Enum.reduce(fuzzy_sets, [], fn fuzzy_set, acc -> acc ++ mf_values[fuzzy_set.tag] end)
end
defp fuzzy_to_crisp([], _input, nom, den), do: nom / den
defp fuzzy_to_crisp([fs_strength | f_tail], [input | i_tail], nom, den) do
nom = nom + fs_strength * input
den = den + fs_strength
fuzzy_to_crisp(f_tail, i_tail, nom, den)
end
end