Fugologic is a basic implementation of a fuzzy logic system.
Define fuzzy values, create the rules and evaluate them.
- Describe complex rules manually or using a builder
- Describe simple rules using specific methods (like the fuzzy associative matrix)
For example, implement the following rules
| HP/FP => Act | Very low HP | Low HP | Medium HP | High HP | Very high HP |
|---|---|---|---|---|---|
| Very weak FP | Retreat! | Retreat! | Defend | Defend | Defend |
| Weak FP | Retreat! | Defend | Defend | Attack | Attack |
| Medium FP | Retreat! | Defend | Attack | Attack | Full attack! |
| High FP | Retreat! | Defend | Attack | Attack | Full attack! |
| Very high FP | Defend | Attack | Attack | Full attack! | Full attack! |
// Create a crisp set, fuzzy sets and a fuzzy value
// Input HP [0 ; 100]
crispHP, _ := crisp.NewSetN(0, 100, 1000)
fsHP, _ := fuzzy.NewIDSets(map[id.ID]fuzzy.SetBuilder{
"Very low HP": fuzzy.StepDown{A: 0, B: 20},
"Low HP": fuzzy.Trapezoid{A: 0, B: 20, C: 40, D: 60},
"Medium HP": fuzzy.Triangular{A: 40, B: 50, C: 60},
"High HP": fuzzy.Trapezoid{A: 40, B: 60, C: 80, D: 100},
"Very high HP": fuzzy.StepUp{A: 80, B: 100},
})
fvHP, _ := fuzzy.NewIDVal("HP", crispHP, fsHP)
// Create other fuzzy values the same way
// * fvFP [0 ; 100]: "Very weak FP", "Weak FP", "Medium FP", "High FP", "Very high FP"
// * fvAct [-10 ; 10]: "Retreat!", "Defend", "Attack", "Full attack!"
// Express all rules using the "fuzzy associative matrix"
// if <HP> and <FP> then <Act>
bld := builder.Mamdani().FuzzyAssoMatrix()
_ = bld.
Asso(fvHP, fvFP, fvAct).
Matrix(
[]id.ID{"Very low HP", "Low HP", "Medium HP", "High HP", "Very high HP"},
map[id.ID][]id.ID{
"Very weak FP": {"Retreat!", "Retreat!", "Defend", "Defend", "Defend"},
"Weak FP": {"Retreat!", "Defend", "Defend", "Attack", "Attack"},
"Medium FP": {"Retreat!", "Defend", "Attack", "Attack", "Full attack!"},
"High FP": {"Retreat!", "Defend", "Attack", "Attack", "Full attack!"},
"Very high FP": {"Defend", "Attack", "Attack", "Full attack!", "Full attack!"},
},
)
// Create an engine and evaluate it
engine, _ := bld.Engine()
result, _ := engine.Evaluate(fuzzy.DataInput{
fvHP: 75,
fvFP: 30,
})
// Manage output
return result[fvAct]For more examples, see /fugologic/example/
Defuzzification requires a crisp interval of discrete values.
It is defined as crisp.Set (x min, x max, dx)
// Each values from 0.0 to 0.3 every 0.1 => [0.0, 0.1, 0.2, 0.3]
set, err := crisp.NewSet(0.0, 0.3, 0.1)
if err != nil{
return err
}It can also be defined with n values (x min, x max, n values)
// 4 values in [0.0 ; 0.3]
set, err := crisp.NewSetN(0.0, 0.3, 4)
if err != nil{
return err
}A membership function is defined as a fuzzy.Set.
Several methods are proposed, like :
| method | description | shape |
|---|---|---|
Gauss |
Gaussian | ▁/⁀\▁ |
Gbell |
Generalized bell-shaped | ▁/⁀\▁ |
Trapezoid |
Trapezoïdal | ▁/▔\▁ |
Triangular |
Triangular | ▁/\▁ |
StepUp |
Step up (S shape) | ▁/▔ |
StepDown |
Step down (Z shape) | ▔\▁ |
Sigmoid |
Sigmoïdal (S or Z shape) | ▁/▔ or ▔\▁ |
Initialise a builder and call New to get the fuzzy.Set and check for errors
set, err := Triangular{A: 1, B: 2, C: 3}.New()
if err != nil {
return err
}Fuzzy values and fuzzy sets are defined as :
fuzzy.IDVal: a fuzzy value that contains,- an identifier
id.ID - a
crisp.Setinterval of values (required for defuzzification) - a list of
fuzzy.IDSet; and each on contains,- an identifier
id.ID - a membership method
fuzzy.Set - its
fuzzy.IDValparent
- an identifier
- an identifier
Notes : every identifier shall be unique in a fuzzy.Engine
First, create a fuzzy value and link it to a list of fuzzy sets.
Ensure that the crisp interval of the fuzzy value covers all the fuzzy sets intervals.
// Fuzzy sets "a1", "a2"
// Use the builder or create them manually
fsA, _ := fuzzy.NewIDSets(map[id.ID]fuzzy.SetBuilder{
"a1": fuzzy.Triangular{-3, -1, 1},
"a2": fuzzy.Trapezoid{-1, 1, 3, 5},
})
// Fuzzy value "a"
crispA, _ := crisp.NewSet(-3, 5, 0.1)
fvA, _ := fuzzy.NewIDVal("a", crispA, fsA)
// Retrieve fuzzy sets using their ids
fsA1 := fvA.Get("a1")
fsA2 := fvA.Get("a2")
// Or fetch a fuzzy set and its presence
// - fsUnknown is empty
// - ok is false
fsUnknown, ok := fvA.Fetch("unknown")Create other inputs and outputs the same way.
A rule is defined with 3 components :
expression: connects several fuzzy sets togetherimplication: defines an implication methodconsequence: defines several fuzzy sets as the outputs
rule = <expression> <implication> <consequence>
rule = A1 and B1 then C1, D1
The rule builder is optional but helps creating simple rules, and then, an engine.
Use a predefined configuration, and then create a builder
cfg := builder.Mamdani() // Predefined configuration
bld := cfg.FuzzyLogic() // Rule builder using the predefined configurationOr create a custom configuration, and then create a builder
cfg := builder.Config{
Optr: fuzzy.OperatorZadeh{},
Impl: fuzzy.ImplicationMin,
Agg: fuzzy.AggregationUnion,
Defuzz: fuzzy.DefuzzificationCentroid,
}
bld := cfg.FuzzyLogic()Or use your configuration to created the wanted builder
bld := builder.NewFuzzyLogic(
fuzzy.OperatorZadeh{},
fuzzy.ImplicationMin,
fuzzy.AggregationUnion,
fuzzy.DefuzzificationCentroid,
)Details of the configuration parameters
| type | example | description |
|---|---|---|
fuzzy.Operator |
connect several rule premises together to create an expression | |
OperatorZadeh |
Zadeh And, Or, XOr connectors |
|
OperatorHyperbolic |
Hyperbolic And, Or, XOr connectors |
|
fuzzy.Implication |
propagates the expression results into consequences | |
ImplicationMin |
Mamdani implication minimum | |
ImplicationProd |
Sugeno implication product | |
fuzzy.Aggregation |
merges all coherent implications | |
AggregationUnion |
union | |
AggregationIntersection |
intersection | |
fuzzy.Defuzzification |
extracts one value from the aggregated results | |
DefuzzificationCentroid |
centroïd: center of gravity | |
DefuzzificationBisector |
bisector: position under the curve where the areas on both sides are equal | |
DefuzzificationSmallestOfMaxs |
if several y maximums are found, get the one with the smallest x |
|
DefuzzificationMiddleOfMaxs |
if several y maximums are found, get the point at the middle of the smallest and the largest x |
|
DefuzzificationLargestOfMaxs |
if several y maximums are found, get the one with the largest x |
Select the input fuzzy.IDSet and link them using a fuzzy.Operator.
Simplest case : the expression has only one premise and no connector (directly use the fuzzy set)
// A1
exp := fsA1An expression is a flat list of several fuzzy.IDSet linked with the same fuzzy.Connector.
For example : A1 and B1 not C1.
// Using a builder
// A1 and B1 and C1
exp := bld.If(fsA1).And(fsB1).And(fsC1)Or in a more explicit way
// Using explicit syntax
// A1 and B1 and C1
exp := fuzzy.NewExpression([]fuzzy.Premise{fsA1, fsB1, fsC1}, fuzzy.OperatorZadeh{}.And)At last, an expression can be more complex like (A1 and B1 and C1) not-or (D1 and E1).
Note : an expression can be complemented using the Not function
// Using a builder
expABC := bld.If(fsA1).And(fsB1).And(fsC1) // A1 and B1 and C1
expDE := bld.If(fsD1).And(fsE1) // D1 and E1
exp := expABC.Or(expDE).Not() // (A1 and B1 and C1) not-or (D1 and E1)Or in a more explicit way
// Using explicit syntax
expABC := fuzzy.NewExpression([]fuzzy.Premise{fsA1, fsB1, fsC1}, fuzzy.OperatorZadeh{}.And) // A1 and B1 and C1
expDE := fuzzy.NewExpression([]fuzzy.Premise{fsD1, fsE1}, fuzzy.OperatorZadeh{}.And) // D1 and E1
exp := fuzzy.NewExpression([]fuzzy.Premise{expABC, expDE}, fuzzy.OperatorZadeh{}.Or).Not() // (A1 and B1 and C1) not-or (D1 and E1)An implication links the input expression and the ouput consequences (using a fuzzy.Implication)
A consequence is just a list of fuzzy.IDSet.
Combine several items previously seen to describe the rules.
This method can be used to easily generate rules manually. Connectors can be explicitely choosen, unlike for the first method.
Note : create a list of rules to use it afterwards.
// Using explicit syntax, the rule has to be part of a list
rules := []fuzzy.Rule{
// A1 and B1 => C1
fuzzy.NewRule(
fuzzy.NewExpression([]fuzzy.Premise{fsA1, fsB1}, fuzzy.OperatorZadeh{}.And), // expression
fuzzy.ImplicationMin, // implication
[]fuzzy.IDSet{fsC1}, // consequence
),
// Describe other rules the same way, for example:
// * A1 and B2 => C2
// * A2 and B1 => C1
// * A2 and B2 => C2
}This method is useful when describing rules directly in the code (using a builder)
Note : the builder that creates a rule stores it.
// Using a builder, the rule is stored in the builder
bld := Mamdani().FuzzyLogic()
// A1 and B1 => C1
bld.If(fsA1).And(fsB1).Then(fsC1)
// Describe other rules the same way
// ...This method allows compact description of all rules using a fuzzy associative matrix
Notes :
- it can only be used to express rules like
if <a> and <b> then <c>in a tabular form - the first operand describe the columns values
- the second operand describes the rows values
- the last operand if the result of value of the row #i and the column #j ; an empty identifier means no rule
Eg.: express all rules using the following tabular form
- if
a1andb1thenc1 - if
a1andb2thenc2 - ...
| a/b => c | a1 | a2 | a3 |
|---|---|---|---|
| b1 | c1 | c2 | c3 |
| b2 | c2 | c3 | c4 |
| b3 | c3 | c4 | c5 |
| b4 | c4 | c5 | c6 |
| b5 | c5 | c6 | c7 |
// Express all rules using a "fuzzy associative matrix"
bld := builder.Mamdani().FuzzyAssoMatrix()
err := bld.
Asso(fvA, fvB, fvC).
Matrix(
[]id.ID{"a1", "a2", "a3"},
map[id.ID][]id.ID{
"b1": {"c1", "c2", "c3"},
"b2": {"c2", "c3", "c4"},
"b3": {"c3", "c4", "c5"},
"b4": {"c4", "c5", "c6"},
"b5": {"c5", "c6", "c7"},
},
)
if err != nil {
return err
}A fuzzy.Engine evaluates a list of fuzzy.Rule, applies a fuzzy.Aggregation to get a fuzzy result, and extracts one crisp value for each output using a fuzzy.Defuzzification method.
If the defined rules contains an error, the engine builder will return it.
Create an engine from the builder
// Using a builder
engine, err := bld.Engine()
if err != nil {
return err
}Or create an engine manually with custom methods
// Using explicit syntax
engine, err := fuzzy.NewEngine(rules, AggregationUnion, DefuzzificationCentroid)
if err != nil {
return err
}
// Limit number of workers
engine.WithMaxWorkers(runtime.NumCPU())Then, launch the evaluation process by setting a new crisp input value for each fuzzy.IDVal of the engine.
The result contains a crisp value for each fuzzy output value defined.
// Evaluate all the rules of the engine
result, err := engine.Evaluate(fuzzy.DataInput{
fvA: 1,
fvB: 0.05,
})
if err != nil {
return err
}
// result = fuzzy.DataOutput{
// fvC: <crisp result>,
// }A system is an ordered list of engines. An output of an engine can be linked to the input of another engine.
When creating a system, some contraints are checked, like:
- all identifiers shall be unique
- an output shall only be produced once
- loops are forbidden : an output cannot be linked to an input of a previous engine
// Create engines
engine1, _ := fuzzy.NewEngine(rules1, AggregationUnion, DefuzzificationCentroid)
engine2, _ := fuzzy.NewEngine(rules2, AggregationUnion, DefuzzificationProd)
// Create and evaluate the system
system, err := fuzzy.NewSystem([]Engine{engine1, engine2})
if err != nil {
return err
}Then, launch the evaluation process by setting a new input value for each fuzzy.IDVal of the system.
The result contains a crisp value for each fuzzy.IDVal output value defined.
// Evaluation of the rules of each engines
result, err := system.Evaluate(fuzzy.DataInput{
fvA: 1,
fvB: 0.05,
})
if err != nil {
return err
}
// result = fuzzy.DataOutput{
// fvC: <crisp result>,
// }Classes used to describe and evaluate a simple fuzzy system
classDiagram
class Premise {
<<interface>>
+ Evaluate()
}
class Expression {
+ Evaluate()
}
class Engine {
+ Evaluate()
}
class IDSet {
+ Evaluate()
}
class Defuzzer {
+ Defuzz()
}
class DataInput {
- value()
}
class System {
+ Evaluate()
}
class Rule {
- evaluate()
}
System --> "*" Engine
DataInput --> "1" IDSet
Defuzzer --> "1" IDSet : results
IDSet "1" <--> "*" IDVal
Premise "*" <-- Expression : premises
Expression --|> Premise
IDSet --|> Premise
DataInput <.. Premise
Engine --> "*" Rule : rules
Engine --> "1" Defuzzer : defuzzer
Engine ..> DataInput
Engine ..> DataOutput
Builder --> "*" Rule : rules
Builder ..> Engine
Rule --> "*" Premise : inputs
Rule --> "*" IDSet : outputs