|
| 1 | + |
| 2 | +module FCLParser |
| 3 | + |
| 4 | +using PEG, Dictionaries |
| 5 | +using ..FuzzyLogic |
| 6 | +using ..FuzzyLogic: Variable, Domain |
| 7 | + |
| 8 | +export parse_fcl, @fcl_str |
| 9 | + |
| 10 | +const FCL_JULIA = Dict("COG" => CentroidDefuzzifier(), |
| 11 | + "COGS" => "COGS", # dummy since hardcoded for sugeno |
| 12 | + "COA" => BisectorDefuzzifier(), |
| 13 | + "ANDMIN" => MinAnd(), |
| 14 | + "ANDPROD" => ProdAnd(), |
| 15 | + "ANDBDIF" => LukasiewiczAnd(), |
| 16 | + "ORMAX" => MaxOr(), |
| 17 | + "ORASUM" => ProbSumOr(), |
| 18 | + "ORBSUM" => BoundedSumOr(), |
| 19 | + "ACTPROD" => ProdImplication(), |
| 20 | + "ACTMIN" => MinImplication()) |
| 21 | + |
| 22 | +function fcl_julia(s::AbstractString) |
| 23 | + haskey(FCL_JULIA, s) ? FCL_JULIA[s] : throw(ArgumentError("Option $s not supported.")) |
| 24 | +end |
| 25 | + |
| 26 | +@rule id = r"[a-zA-Z_]+[a-zA-z0-9_]*"p |> Symbol |
| 27 | +@rule function_block = r"FUNCTION_BLOCK"p & id & var_input_block & var_output_block & |
| 28 | + fuzzify_block[1:end] & defuzzify_block[1:end] & rule_block & |
| 29 | + r"END_FUNCTION_BLOCK"p |> x -> x[2:7] |
| 30 | + |
| 31 | +@rule var_input_block = r"VAR_INPUT"p & var_def[1:end] & r"END_VAR"p |> |
| 32 | + x -> Vector{Symbol}(x[2]) |
| 33 | +@rule var_output_block = r"VAR_OUTPUT"p & var_def[1:end] & r"END_VAR"p |> |
| 34 | + x -> Vector{Symbol}(x[2]) |
| 35 | +@rule var_def = id & r":"p & r"REAL"p & r";"p |> first |
| 36 | + |
| 37 | +@rule fuzzify_block = r"FUZZIFY"p & id & linguistic_term[1:end] & range_term & |
| 38 | + r"END_FUZZIFY"p |> x -> x[2] => Variable(x[4], dictionary(x[3])) |
| 39 | + |
| 40 | +@rule range_term = r"RANGE\s*:=\s*\("p & numeral & r".."p & numeral & r"\)\s*;"p |> |
| 41 | + x -> Domain(x[2], x[4]) |
| 42 | + |
| 43 | +@rule linguistic_term = r"TERM"p & id & r":="p & membership_function & r";"p |> |
| 44 | + x -> x[2] => x[4] |
| 45 | +@rule membership_function = singleton, points |
| 46 | +@rule singleton = r"[+-]?\d+\.?\d*([eE][+-]?\d+)?"p |> |
| 47 | + ConstantSugenoOutput ∘ Base.Fix1(parse, Float64) |
| 48 | + |
| 49 | +@rule numeral = r"[+-]?\d+(\.\d+)?([eE][+-]?\d+)?"p |> Base.Fix1(parse, Float64) |
| 50 | +@rule point = r"\("p & numeral & r","p & numeral & r"\)"p |> x -> tuple(x[2], x[4]) |
| 51 | +@rule points = point[2:end] |> PiecewiseLinearMF ∘ Vector{Tuple{Float64, Float64}} |
| 52 | + |
| 53 | +@rule defuzzify_block = r"DEFUZZIFY"p & id & linguistic_term[1:end] & defuzzify_method & |
| 54 | + range_term & r"END_DEFUZZIFY"p |> |
| 55 | + (x -> (x[2] => Variable(x[5], dictionary(x[3])), x[4])) |
| 56 | + |
| 57 | +@rule defuzzify_method = r"METHOD\s*:"p & (r"COGS"p, r"COG"p, r"COA"p, r"LM"p, r"RM"p) & |
| 58 | + r";"p |> x -> fcl_julia(x[2]) |
| 59 | + |
| 60 | +@rule rule_block = r"RULEBLOCK"p & id & operator_definition & activation_method[:?] & |
| 61 | + rule[1:end] & r"END_RULEBLOCK"p |> |
| 62 | + x -> (x[3], x[4], Vector{FuzzyLogic.FuzzyRule}(x[5])) |
| 63 | + |
| 64 | +@rule or_definition = r"OR"p & r":"p & (r"MAX"p, r"ASUM"p, r"BSUM"p) & r";"p |
| 65 | +@rule and_definition = r"AND"p & r":"p & (r"MIN"p, r"PROD"p, r"BDIF"p) & r";"p |
| 66 | +@rule operator_definition = (and_definition, or_definition) |> |
| 67 | + x -> fcl_julia(join([x[1], x[3]])) |
| 68 | +@rule activation_method = r"ACT"p & r":"p & (r"MIN"p, r"PROD"p) & r";"p |> |
| 69 | + x -> fcl_julia(join([x[1], x[3]])) |
| 70 | + |
| 71 | +@rule rule = r"RULE\s+\d+\s*:\s*IF"p & condition & r"THEN"p & conclusion & r";"p |> |
| 72 | + x -> FuzzyLogic.FuzzyRule(x[2], x[4]) |
| 73 | +@rule relation = negrel, posrel |
| 74 | +@rule posrel = id & r"IS"p & id |> x -> FuzzyLogic.FuzzyRelation(x[1], x[3]) |
| 75 | +@rule negrel = (id & r"IS\s+NOT"p & id |> x -> FuzzyLogic.FuzzyNegation(x[1], x[3])), |
| 76 | + r"NOT\s*\("p & id & r"IS"p & id & r"\)"p |> |
| 77 | + x -> FuzzyLogic.FuzzyNegation(x[2], x[4]) |
| 78 | +@rule conclusion = posrel & (r","p & posrel)[*] |> x -> append!([x[1]], map(last, x[2])) |
| 79 | + |
| 80 | +@rule condition = orcondition |
| 81 | +@rule orcondition = andcondition & (r"OR"p & andcondition)[*] |> |
| 82 | + x -> reduce(FuzzyLogic.FuzzyOr, vcat(x[1], map(last, x[2]))) |
| 83 | +@rule andcondition = term & (r"AND"p & term)[*] |> |
| 84 | + x -> reduce(FuzzyLogic.FuzzyAnd, vcat(x[1], map(last, x[2]))) |
| 85 | +@rule term = relation, r"\("p & condition & r"\)"p |> x -> x[2] |
| 86 | + |
| 87 | +""" |
| 88 | + parse_fcl(s::String)::AbstractFuzzySystem |
| 89 | +
|
| 90 | +Parse a fuzzy inference system from a string representation in Fuzzy Control Language (FCL). |
| 91 | +
|
| 92 | +### Inputs |
| 93 | +
|
| 94 | +- `s::String` -- string describing a fuzzy system in FCL conformant to the IEC 1131-7 standard. |
| 95 | +
|
| 96 | +### Notes |
| 97 | +
|
| 98 | +The parsers can read FCL comformant to IEC 1131-7, with the following remarks: |
| 99 | +
|
| 100 | +- Weighted rules are not supported. |
| 101 | +- Sugeno (system with singleton outputs) shall use COGS as defuzzifier. |
| 102 | +- the `RANGE` keyword is required for both fuzzification and defuzzification blocks. |
| 103 | +- Only the required `MAX` accumulator is supported. |
| 104 | +- Default value for defuzzification not supported. |
| 105 | +- Optional local variables are not supported. |
| 106 | +
|
| 107 | +With the exceptions above, the parser supports all required and optional features of the standard (tables 6.1-1 and 6.1-2). |
| 108 | +In addition, it also supports the following features: |
| 109 | +
|
| 110 | +- Piecewise linear functions can have any number of points. |
| 111 | +- Membership degrees in piecewise linear functions points can be any number between ``0`` and ``1``. |
| 112 | +""" |
| 113 | +function parse_fcl(s::String)::FuzzyLogic.AbstractFuzzySystem |
| 114 | + name, inputs, outputs, inputsmfs, outputsmf, (op, imp, rules) = parse_whole(function_block, |
| 115 | + s) |
| 116 | + varsin = dictionary(inputsmfs) |
| 117 | + @assert sort(collect(keys(varsin)))==sort(inputs) "Mismatch between declared and fuzzified input variables." |
| 118 | + |
| 119 | + varsout = dictionary(first.(outputsmf)) |
| 120 | + @assert sort(collect(keys(varsout)))==sort(outputs) "Mismatch between declared and defuzzified output variables." |
| 121 | + |
| 122 | + @assert all(==(outputsmf[1][2]), last.(outputsmf)) "All output variables should use the same defuzzification method." |
| 123 | + defuzzifier = outputsmf[1][2] |
| 124 | + and, or = ops_pairs(op) |
| 125 | + if defuzzifier == "COGS" # sugeno |
| 126 | + SugenoFuzzySystem(name, varsin, varsout, rules, and, or) |
| 127 | + else # mamdani |
| 128 | + imp = isempty(imp) ? MinImplication() : first(imp) |
| 129 | + MamdaniFuzzySystem(name, varsin, varsout, rules, and, or, imp, MaxAggregator(), |
| 130 | + defuzzifier) |
| 131 | + end |
| 132 | +end |
| 133 | + |
| 134 | +ops_pairs(::MinAnd) = MinAnd(), MaxOr() |
| 135 | +ops_pairs(::ProdAnd) = ProdAnd(), ProbSumOr() |
| 136 | +ops_pairs(::LukasiewiczAnd) = LukasiewiczAnd(), BoundedSumOr() |
| 137 | +ops_pairs(::MaxOr) = MinAnd(), MaxOr() |
| 138 | +ops_pairs(::ProbSumOr) = ProdAnd(), ProbSumOr() |
| 139 | +ops_pairs(::BoundedSumOr) = LukasiewiczAnd(), BoundedSumOr() |
| 140 | + |
| 141 | +""" |
| 142 | +String macro to parse Fuzzy Control Language (FCL). See [`parse_fcl`](@ref) for more details. |
| 143 | +""" |
| 144 | +macro fcl_str(s::AbstractString) |
| 145 | + parse_fcl(s) |
| 146 | +end |
| 147 | + |
| 148 | +end |
0 commit comments