//+------------------------------------------------------------------+
//|                                           mandanifuzzysystem.mqh |
//|                             Copyright 2000-2024, 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);
                 }
  }
//+------------------------------------------------------------------+
