557 lines
22 KiB
MQL5
557 lines
22 KiB
MQL5
|
//+------------------------------------------------------------------+
|
||
|
//| mandanifuzzysystem.mqh |
|
||
|
//| Copyright 2000-2025, MetaQuotes Ltd. |
|
||
|
//| https://www.mql5.com |
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Implementation of Fuzzy library in MetaQuotes Language 5 |
|
||
|
//| |
|
||
|
//| The features of the library include: |
|
||
|
//| - Create Mamdani fuzzy model |
|
||
|
//| - Create Sugeno fuzzy model |
|
||
|
//| - Normal membership function |
|
||
|
//| - Triangular membership function |
|
||
|
//| - Trapezoidal membership function |
|
||
|
//| - Constant membership function |
|
||
|
//| - Defuzzification method of center of gravity (COG) |
|
||
|
//| - Defuzzification method of bisector of area (BOA) |
|
||
|
//| - Defuzzification method of mean of maxima (MeOM) |
|
||
|
//| |
|
||
|
//| This file is free software; you can redistribute it and/or |
|
||
|
//| modify it under the terms of the GNU General Public License as |
|
||
|
//| published by the Free Software Foundation (www.fsf.org); either |
|
||
|
//| version 2 of the License, or (at your option) any later version. |
|
||
|
//| |
|
||
|
//| This program is distributed in the hope that it will be useful, |
|
||
|
//| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||
|
//| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||
|
//| GNU General Public License for more details. |
|
||
|
//+------------------------------------------------------------------+
|
||
|
#include <Arrays\List.mqh>
|
||
|
#include <Arrays\ArrayDouble.mqh>
|
||
|
#include "GenericFuzzySystem.mqh"
|
||
|
#include "InferenceMethod.mqh"
|
||
|
#include "RuleParser.mqh"
|
||
|
#include "FuzzyRule.mqh"
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Purpose: Creating Mamdani fuzzy system |
|
||
|
//+------------------------------------------------------------------+
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Mamdani fuzzy inference system |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CMamdaniFuzzySystem : public CGenericFuzzySystem
|
||
|
{
|
||
|
private:
|
||
|
CList* m_output; // List of fuzzy variable
|
||
|
CList* m_rules; // List of Mamdani fuzzy rule
|
||
|
ImplicationMethod m_impl_method; // Implication method
|
||
|
AggregationMethod m_aggr_method; // Aggregation method
|
||
|
DefuzzificationMethod m_defuzz_method; // Defuzzification method
|
||
|
|
||
|
public:
|
||
|
CMamdaniFuzzySystem(void);
|
||
|
~CMamdaniFuzzySystem(void);
|
||
|
//--- method gets the output linguistic variables
|
||
|
CList* Output(void) { return(m_output); }
|
||
|
//--- method gets the fuzzy rule
|
||
|
CList* Rules(void) { return(m_rules); }
|
||
|
//--- methods gets or sets the implication method
|
||
|
ImplicationMethod GetImplicationMethod(void) const { return (m_impl_method); }
|
||
|
void SetImplicationMethod(ImplicationMethod value) { m_impl_method=value; }
|
||
|
//--- methods gets or sets the aggregation method
|
||
|
AggregationMethod GetAggregationMethod(void) const { return (m_aggr_method); }
|
||
|
void SetAggregationMethod(AggregationMethod value) { m_aggr_method=value; }
|
||
|
//--- methods gets or sets the defuzzification method
|
||
|
DefuzzificationMethod GetDefuzzificationMethod(void) const { return (m_defuzz_method); }
|
||
|
void SetDefuzzificationMethod(DefuzzificationMethod value) { m_defuzz_method=value; }
|
||
|
//--- maethod gets the variable by name
|
||
|
CFuzzyVariable* OutputByName(const string name);
|
||
|
//--- create a new rule
|
||
|
CMamdaniFuzzyRule* EmptyRule(void);
|
||
|
//--- parse rule
|
||
|
CMamdaniFuzzyRule* ParseRule(const string rule);
|
||
|
//--- method for calculate result
|
||
|
CList* Calculate(CList *inputValues);
|
||
|
CList* EvaluateConditions(CList *fuzzifiedInput);
|
||
|
CList* Implicate(CList *conditions);
|
||
|
CList* Aggregate(CList *conclusions);
|
||
|
CList* Defuzzify(CList *fuzzyResult);
|
||
|
double Defuzzify(IMembershipFunction *mf,const double min,const double max);
|
||
|
};
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Constructor without parameters |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CMamdaniFuzzySystem::CMamdaniFuzzySystem(void)
|
||
|
{
|
||
|
m_output = new CList;
|
||
|
m_rules = new CList;
|
||
|
m_impl_method = MinIpm; // Implication method default is Min
|
||
|
m_aggr_method = MaxAgg; // Aggregation method default is Max
|
||
|
m_defuzz_method = CentroidDef; // Defuzzification method default is Centroid
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Destructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CMamdaniFuzzySystem::~CMamdaniFuzzySystem(void)
|
||
|
{
|
||
|
delete m_output;
|
||
|
delete m_rules;
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Get output linguistic variable by its name |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CFuzzyVariable *CMamdaniFuzzySystem::OutputByName(const string name)
|
||
|
{
|
||
|
for(int i=0; i<m_output.Total(); i++)
|
||
|
{
|
||
|
CFuzzyVariable *var=m_output.GetNodeAtIndex(i);
|
||
|
if(var.Name()==name)
|
||
|
{
|
||
|
//--- return varriable
|
||
|
return (var);
|
||
|
}
|
||
|
}
|
||
|
Print("Variable with that name is not found");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Create new empty rule |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CMamdaniFuzzyRule *CMamdaniFuzzySystem::EmptyRule()
|
||
|
{
|
||
|
//--- return empty rule
|
||
|
return new CMamdaniFuzzyRule();
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Parse rule from the string |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CMamdaniFuzzyRule *CMamdaniFuzzySystem::ParseRule(const string rule)
|
||
|
{
|
||
|
//--- return Mamdani fuzzy rule
|
||
|
return CRuleParser::Parse(rule, EmptyRule(), Input(), Output());
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate output values |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CList *CMamdaniFuzzySystem::Calculate(CList *inputValues)
|
||
|
{
|
||
|
//--- There should be one rule as minimum
|
||
|
if(m_rules.Total()==0)
|
||
|
{
|
||
|
Print("There should be one rule as minimum.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
//--- Fuzzification step
|
||
|
CList *fuzzifiedInput=Fuzzify(inputValues);
|
||
|
//--- Evaluate the conditions
|
||
|
CList *evaluatedConditions=EvaluateConditions(fuzzifiedInput);
|
||
|
//--- Do implication for each rule
|
||
|
CList *implicatedConclusions=Implicate(evaluatedConditions);
|
||
|
//--- Aggrerate the results
|
||
|
CList *fuzzyResult=Aggregate(implicatedConclusions);
|
||
|
//--- Defuzzify the result
|
||
|
CList *result=Defuzzify(fuzzyResult);
|
||
|
//---
|
||
|
delete fuzzyResult;
|
||
|
for(int i=0; i<implicatedConclusions.Total(); i++)
|
||
|
{
|
||
|
CDictionary_Obj_Obj *pair=implicatedConclusions.GetNodeAtIndex(i);
|
||
|
CCompositeMembershipFunction *composite=pair.Value();
|
||
|
delete composite.MembershipFunctions().GetNodeAtIndex(0);
|
||
|
delete composite;
|
||
|
}
|
||
|
delete implicatedConclusions;
|
||
|
delete evaluatedConditions;
|
||
|
for(int i=0; i<fuzzifiedInput.Total(); i++)
|
||
|
{
|
||
|
CDictionary_Obj_Obj *pair=fuzzifiedInput.GetNodeAtIndex(i);
|
||
|
delete pair.Value();
|
||
|
}
|
||
|
delete fuzzifiedInput;
|
||
|
//--- return result
|
||
|
return (result);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Evaluate conditions |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CList *CMamdaniFuzzySystem::EvaluateConditions(CList *fuzzifiedInput)
|
||
|
{
|
||
|
CList *result=new CList;
|
||
|
for(int i=0; i<Rules().Total(); i++)
|
||
|
{
|
||
|
CDictionary_Obj_Double *p_rd=new CDictionary_Obj_Double;
|
||
|
CMamdaniFuzzyRule *rule=Rules().GetNodeAtIndex(i);
|
||
|
p_rd.SetAll(rule,EvaluateCondition(rule.Condition(),fuzzifiedInput));
|
||
|
result.Add(p_rd);
|
||
|
}
|
||
|
//--- return result
|
||
|
return (result);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Implicate rule results |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CList *CMamdaniFuzzySystem::Implicate(CList *conditions)
|
||
|
{
|
||
|
CList *conclusions=new CList;
|
||
|
for(int i=0; i<conditions.Total(); i++)
|
||
|
{
|
||
|
CDictionary_Obj_Double *p_rd=conditions.GetNodeAtIndex(i);
|
||
|
CMamdaniFuzzyRule *rule=p_rd.Key();
|
||
|
MfCompositionType compType;
|
||
|
switch(m_impl_method)
|
||
|
{
|
||
|
case MinIpm :
|
||
|
{
|
||
|
compType=MinMF;
|
||
|
break;
|
||
|
}
|
||
|
case ProductionImp :
|
||
|
{
|
||
|
compType=ProdMF;
|
||
|
break;
|
||
|
}
|
||
|
default :
|
||
|
{
|
||
|
Print("Internal error.");
|
||
|
//---
|
||
|
return (NULL);
|
||
|
}
|
||
|
}
|
||
|
CFuzzyTerm *val=rule.Conclusion().Term();
|
||
|
IMembershipFunction *first_fun=new CConstantMembershipFunction(p_rd.Value());
|
||
|
IMembershipFunction *second_fun=val.MembershipFunction();
|
||
|
CCompositeMembershipFunction *resultMF=new CCompositeMembershipFunction(compType,first_fun,second_fun);
|
||
|
CDictionary_Obj_Obj *p_rf=new CDictionary_Obj_Obj;
|
||
|
p_rf.SetAll(rule,resultMF);
|
||
|
conclusions.Add(p_rf);
|
||
|
}
|
||
|
//--- return conclusions
|
||
|
return (conclusions);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Aggregate results |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CList *CMamdaniFuzzySystem::Aggregate(CList *conclusions)
|
||
|
{
|
||
|
CList *fuzzyResult=new CList;
|
||
|
for(int i=0; i<Output().Total(); i++)
|
||
|
{
|
||
|
CFuzzyVariable *var=Output().GetNodeAtIndex(i);
|
||
|
CList *mfList=new CList;
|
||
|
for(int j=0; j<conclusions.Total(); j++)
|
||
|
{
|
||
|
CDictionary_Obj_Obj *p_rf=conclusions.GetNodeAtIndex(j);
|
||
|
CMamdaniFuzzyRule *rule=p_rf.Key();
|
||
|
if(rule.Conclusion().Var()==var)
|
||
|
{
|
||
|
mfList.Add(p_rf.Value());
|
||
|
}
|
||
|
}
|
||
|
MfCompositionType composType;
|
||
|
switch(m_aggr_method)
|
||
|
{
|
||
|
case MaxAgg:
|
||
|
composType=MaxMF;
|
||
|
break;
|
||
|
case SumAgg:
|
||
|
composType=SumMF;
|
||
|
break;
|
||
|
default:
|
||
|
{
|
||
|
Print("Internal exception.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
}
|
||
|
CDictionary_Obj_Obj *p_vf=new CDictionary_Obj_Obj;
|
||
|
CCompositeMembershipFunction *func=new CCompositeMembershipFunction(composType,mfList);
|
||
|
p_vf.SetAll(var,func);
|
||
|
fuzzyResult.Add(p_vf);
|
||
|
}
|
||
|
//--- return result
|
||
|
return (fuzzyResult);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate crisp result for each rule |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CList *CMamdaniFuzzySystem::Defuzzify(CList *fuzzyResult)
|
||
|
{
|
||
|
CList *crispResult=new CList;
|
||
|
for(int i=0; i<fuzzyResult.Total(); i++)
|
||
|
{
|
||
|
CDictionary_Obj_Double *p_vd=new CDictionary_Obj_Double;
|
||
|
CDictionary_Obj_Obj *p_vf=fuzzyResult.GetNodeAtIndex(i);
|
||
|
CFuzzyVariable *var=p_vf.Key();
|
||
|
p_vd.SetAll(var,Defuzzify(p_vf.Value(),var.Min(),var.Max()));
|
||
|
crispResult.Add(p_vd);
|
||
|
}
|
||
|
//--- return result
|
||
|
return (crispResult);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Helpers |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CMamdaniFuzzySystem::Defuzzify(IMembershipFunction *mf,const double min,const double max)
|
||
|
{
|
||
|
if(m_defuzz_method==CentroidDef)
|
||
|
{
|
||
|
int k=50; // The function is divided into "k" steps
|
||
|
double step=(max-min)/k; // Calculate the step function
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate a center of gravity as integral |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double ptLeft=0.0;
|
||
|
double ptCenter= 0.0;
|
||
|
double ptRight = 0.0;
|
||
|
double valLeft=0.0;
|
||
|
double valCenter= 0.0;
|
||
|
double valRight = 0.0;
|
||
|
double val2Left=0.0;
|
||
|
double val2Center= 0.0;
|
||
|
double val2Right = 0.0;
|
||
|
double numerator=0.0;
|
||
|
double denominator=0.0;
|
||
|
for(int i=0; i<k; i++)
|
||
|
{
|
||
|
if(i==0)
|
||
|
{
|
||
|
ptRight=min;
|
||
|
valRight=mf.GetValue(ptRight);
|
||
|
val2Right=ptRight*valRight;
|
||
|
}
|
||
|
ptLeft=ptRight;
|
||
|
ptCenter= min+step *((double)i+0.5);
|
||
|
ptRight = min+step *(i+1);
|
||
|
valLeft=valRight;
|
||
|
valCenter= mf.GetValue(ptCenter);
|
||
|
valRight = mf.GetValue(ptRight);
|
||
|
val2Left=val2Right;
|
||
|
val2Center= ptCenter * valCenter;
|
||
|
val2Right = ptRight * valRight;
|
||
|
numerator+=step *(val2Left+4*val2Center+val2Right)/3.0;
|
||
|
denominator+=step *(valLeft+4*valCenter+valRight)/3.0;
|
||
|
}
|
||
|
delete mf;
|
||
|
if(denominator!=0)
|
||
|
{
|
||
|
//--- return result
|
||
|
return (numerator / denominator);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//--- return NAN
|
||
|
return (MathLog(-1));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
if(m_defuzz_method==BisectorDef)
|
||
|
{
|
||
|
//+-------------------------------------------------------------------------------------+
|
||
|
//| The method Bisector consists in finding the point on the abscissa, |
|
||
|
//| which divides the area under the curve of the membership function in two equal parts|
|
||
|
//+-------------------------------------------------------------------------------------+
|
||
|
double Area=0.0; // The area under the function
|
||
|
int k=50; // The function is divided into "k" steps
|
||
|
double now=min; // The current position
|
||
|
for(int i=0; i<k; i++)
|
||
|
{
|
||
|
Area+=mf.GetValue(now);
|
||
|
now=now+(max-min)/k;
|
||
|
}
|
||
|
now=min;
|
||
|
double halfArea=fabs(Area/2-mf.GetValue(min));
|
||
|
Area=0.0;
|
||
|
while(true)
|
||
|
{
|
||
|
Area+=mf.GetValue(now);
|
||
|
if(Area>=halfArea)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
now=now+(max-min)/k;
|
||
|
}
|
||
|
delete mf;
|
||
|
//--- return result
|
||
|
return (now);
|
||
|
}
|
||
|
else
|
||
|
if(m_defuzz_method==AverageMaximumDef)
|
||
|
{
|
||
|
//+------------------------------------------------------------------------------------------+
|
||
|
//| AverageMaximum method is the arithmetic mean of all the maxima of the membership function|
|
||
|
//+------------------------------------------------------------------------------------------+
|
||
|
double sum_max=0; // Sum of local maxima
|
||
|
double count_max=0; // Count of local maxima
|
||
|
int k=50; // The function is divided into "k" steps
|
||
|
double now=min; // The current position
|
||
|
double step=(max-min)/k; // Calculate the step function
|
||
|
for(int i=1; i<k; i++)
|
||
|
{
|
||
|
double point_1 = mf.GetValue(now);
|
||
|
double point_0 = mf.GetValue(now - step);
|
||
|
double point_2 = mf.GetValue(now + step);
|
||
|
//--- check the first element
|
||
|
if(i==1)
|
||
|
{
|
||
|
if(mf.GetValue(min)>mf.GetValue(min+step))
|
||
|
{
|
||
|
sum_max+=mf.GetValue(min);
|
||
|
count_max++;
|
||
|
}
|
||
|
}
|
||
|
//--- check the second element
|
||
|
if(i==k-1)
|
||
|
{
|
||
|
if(mf.GetValue(max)>mf.GetValue(max-step))
|
||
|
{
|
||
|
sum_max+=mf.GetValue(max);
|
||
|
count_max++;
|
||
|
}
|
||
|
}
|
||
|
//--- check all the other elements
|
||
|
if((point_1>point_0) && (point_1>point_2))
|
||
|
{
|
||
|
sum_max+=point_1;
|
||
|
count_max++;
|
||
|
}
|
||
|
}
|
||
|
if(count_max==0)
|
||
|
{
|
||
|
delete mf;
|
||
|
//--- return result
|
||
|
return (0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delete mf;
|
||
|
//--- return result
|
||
|
return (sum_max/count_max);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
if(m_defuzz_method==LargestMaximumDef)
|
||
|
{
|
||
|
CArrayDouble *local_max=new CArrayDouble; // Array of all local maximum
|
||
|
double result; // Result of defuzzification method
|
||
|
int k=50; // The function is divided into "k" steps
|
||
|
double now=min; // The current position
|
||
|
double step=(max-min)/k; // Calculate the step function
|
||
|
for(int i=1; i<k; i++)
|
||
|
{
|
||
|
double point_1 = mf.GetValue(now);
|
||
|
double point_0 = mf.GetValue(now - step);
|
||
|
double point_2 = mf.GetValue(now + step);
|
||
|
//--- check the first element
|
||
|
if(i==1)
|
||
|
{
|
||
|
if(mf.GetValue(min)>mf.GetValue(min+step))
|
||
|
{
|
||
|
local_max.Add(mf.GetValue(min));
|
||
|
}
|
||
|
}
|
||
|
//--- check the second element
|
||
|
if(i==k-1)
|
||
|
{
|
||
|
if(mf.GetValue(max)>mf.GetValue(max-step))
|
||
|
{
|
||
|
local_max.Add(mf.GetValue(max));
|
||
|
}
|
||
|
}
|
||
|
//--- check all the other elements
|
||
|
if((point_1>point_0) && (point_1>point_2))
|
||
|
{
|
||
|
local_max.Add(point_1);
|
||
|
}
|
||
|
now+=step;
|
||
|
}
|
||
|
result=local_max.At(0);
|
||
|
for(int i=0; i<local_max.Total(); i++)
|
||
|
{
|
||
|
if(result<=local_max.At(i))
|
||
|
{
|
||
|
result=local_max.At(i);
|
||
|
}
|
||
|
}
|
||
|
now=min;
|
||
|
while(true)
|
||
|
{
|
||
|
if(mf.GetValue(now)==result)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
now+=step;
|
||
|
}
|
||
|
delete local_max;
|
||
|
delete mf;
|
||
|
//--- return result
|
||
|
return (now);
|
||
|
}
|
||
|
else
|
||
|
if(m_defuzz_method==SmallestMaximumDef)
|
||
|
{
|
||
|
CArrayDouble *local_max=new CArrayDouble; // Array of all local maximum
|
||
|
double result; // Result of defuzzification method
|
||
|
int k=50; // The function is divided into "k" steps
|
||
|
double now=min; // The current position
|
||
|
double step=(max-min)/k; // Calculate the step function
|
||
|
for(int i=1; i<k; i++)
|
||
|
{
|
||
|
double point_1 = mf.GetValue(now);
|
||
|
double point_0 = mf.GetValue(now - step);
|
||
|
double point_2 = mf.GetValue(now + step);
|
||
|
//--- check the first element
|
||
|
if(i==1)
|
||
|
{
|
||
|
if(mf.GetValue(min)>mf.GetValue(min+step))
|
||
|
{
|
||
|
local_max.Add(mf.GetValue(min));
|
||
|
}
|
||
|
}
|
||
|
//--- check the second element
|
||
|
if(i==k-1)
|
||
|
{
|
||
|
if(mf.GetValue(max)>mf.GetValue(max-step))
|
||
|
{
|
||
|
local_max.Add(mf.GetValue(max));
|
||
|
}
|
||
|
}
|
||
|
//--- check all the other elements
|
||
|
if((point_1>point_0) && (point_1>point_2))
|
||
|
{
|
||
|
local_max.Add(point_1);
|
||
|
}
|
||
|
now+=step;
|
||
|
}
|
||
|
result=local_max.At(0);
|
||
|
for(int i=0; i<local_max.Total(); i++)
|
||
|
{
|
||
|
if(result>=local_max.At(i))
|
||
|
{
|
||
|
result=local_max.At(i);
|
||
|
}
|
||
|
}
|
||
|
now=min;
|
||
|
while(true)
|
||
|
{
|
||
|
if(mf.GetValue(now)==result)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
now+=step;
|
||
|
}
|
||
|
delete local_max;
|
||
|
delete mf;
|
||
|
//--- return result
|
||
|
return (now);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Print("Internal exception.");
|
||
|
delete mf;
|
||
|
//--- return
|
||
|
return (0);
|
||
|
}
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|