//+------------------------------------------------------------------+
//|                                                   NetGenetic.mqh |
//|                                                   Copyright DNG® |
//|                                https://www.mql5.com/ru/users/dng |
//+------------------------------------------------------------------+
#property copyright "Copyright DNG®"
#property link      "https://www.mql5.com/ru/users/dng"
#property version   "1.00"
#include "..\NeuroNet_DNG\NeuroNet.mqh"
//---
#define MaxMutation     0.5f
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CNetGenetic : public CNet
  {
protected:
   uint              i_PopulationSize;
   vectorf           v_Probability;
   vectorf           v_Rewards;
   matrixf           m_Weights;
   matrixf           m_WeightsConv;

   //---
   virtual bool      CreatePopulation(void);
   virtual int       GetAction(CBufferFloat * probability);
   virtual bool      GetWeights(uint layer);
   float             NextGenerationWeight(matrixf &array, uint shift, vectorf &probability);
   float             GenerateWeight(uint total);

public:
                     CNetGenetic();
                    ~CNetGenetic();
   //---
   virtual bool              Create(CArrayObj *Description, uint population_size);
   virtual bool              SetPopulationSize(uint size);
   virtual bool              feedForward(CArrayFloat *inputVals, int window = 1, bool tem = true);
   virtual bool              Rewards(CArrayFloat *rewards);
   virtual bool              NextGeneration(float quantile, float mutation, float &average, float &mamximum);
   virtual bool              Load(string file_name, uint population_size, bool common = true);
   virtual bool              SaveModel(string file_name, int model, bool common = true);
   //---
   virtual bool              CopyModel(CArrayLayer *source, uint model);
   virtual bool              Detach(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CNetGenetic::CNetGenetic() :  i_PopulationSize(100)
  {
   SetPopulationSize(i_PopulationSize);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CNetGenetic::~CNetGenetic()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::Create(CArrayObj *Description, uint population_size)
  {
   if(CheckPointer(Description) == POINTER_INVALID)
      return false;
//---
   if(!SetPopulationSize(population_size))
      return false;
   CNet::Create(Description);
   return CreatePopulation();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::SetPopulationSize(uint size)
  {
   i_PopulationSize = size;
   v_Probability = vectorf::Zeros(i_PopulationSize);
   v_Rewards = vectorf::Zeros(i_PopulationSize);
//---
   return true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::Load(string file_name, uint population_size, bool common = true)
  {
   float temp;
   datetime dt;
   if(!CNet::Load(file_name, temp, temp, temp, dt, common))
      return false;
//---
   if(!SetPopulationSize(population_size))
      return false;
   return CreatePopulation();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::CreatePopulation(void)
  {
   if(!layers || layers.Total() < 2)
      return false;
//---
   CLayer *layer = layers.At(0);
   if(!layer || !layer.At(0))
      return false;
//---
   CNeuronBaseOCL *neuron_ocl = layer.At(0);
   int prev_count = neuron_ocl.Neurons();
   for(int i = 1; i < layers.Total(); i++)
     {
      layer = layers.At(i);
      if(!layer || !layer.At(0))
         return false;
      //---
      neuron_ocl = layer.At(0);
      CLayerDescription *desc = neuron_ocl.GetLayerInfo();
      int outputs = neuron_ocl.getConnections();
      //---
      for(uint n = layer.Total(); n < i_PopulationSize; n++)
        {
         CNeuronConvOCL *neuron_conv_ocl = NULL;
         CNeuronProofOCL *neuron_proof_ocl = NULL;
         CNeuronAttentionOCL *neuron_attention_ocl = NULL;
         CNeuronMLMHAttentionOCL *neuron_mlattention_ocl = NULL;
         CNeuronDropoutOCL *dropout = NULL;
         CNeuronBatchNormOCL *batch = NULL;
         CVAE *vae = NULL;
         CNeuronLSTMOCL *lstm = NULL;
         switch(layer.At(0).Type())
           {
            case defNeuron:
            case defNeuronBaseOCL:
               neuron_ocl = new CNeuronBaseOCL();
               if(CheckPointer(neuron_ocl) == POINTER_INVALID)
                  return false;
               if(!neuron_ocl.Init(outputs, n, opencl, desc.count, desc.optimization, desc.batch))
                 {
                  delete neuron_ocl;
                  return false;
                 }
               neuron_ocl.SetActivationFunction(desc.activation);
               if(!layer.Add(neuron_ocl))
                 {
                  delete neuron_ocl;
                  return false;
                 }
               neuron_ocl = NULL;
               break;
            //---
            case defNeuronConvOCL:
               neuron_conv_ocl = new CNeuronConvOCL();
               if(CheckPointer(neuron_conv_ocl) == POINTER_INVALID)
                  return false;
               if(!neuron_conv_ocl.Init(outputs, n, opencl, desc.window, desc.step, desc.window_out, desc.count, desc.optimization, desc.batch))
                 {
                  delete neuron_conv_ocl;
                  return false;
                 }
               neuron_conv_ocl.SetActivationFunction(desc.activation);
               if(!layer.Add(neuron_conv_ocl))
                 {
                  delete neuron_conv_ocl;
                  return false;
                 }
               neuron_conv_ocl = NULL;
               break;
            //---
            case defNeuronProofOCL:
               neuron_proof_ocl = new CNeuronProofOCL();
               if(!neuron_proof_ocl)
                  return false;
               if(!neuron_proof_ocl.Init(outputs, n, opencl, desc.window, desc.step, desc.count, desc.optimization, desc.batch))
                 {
                  delete neuron_proof_ocl;
                  return false;
                 }
               neuron_proof_ocl.SetActivationFunction(desc.activation);
               if(!layer.Add(neuron_proof_ocl))
                 {
                  delete neuron_proof_ocl;
                  return false;
                 }
               neuron_proof_ocl = NULL;
               break;
            //---
            case defNeuronAttentionOCL:
               neuron_attention_ocl = new CNeuronAttentionOCL();
               if(CheckPointer(neuron_attention_ocl) == POINTER_INVALID)
                  return false;
               if(!neuron_attention_ocl.Init(outputs, n, opencl, desc.window, desc.count, desc.optimization, desc.batch))
                 {
                  delete neuron_attention_ocl;
                  return false;
                 }
               neuron_attention_ocl.SetActivationFunction(desc.activation);
               if(!layer.Add(neuron_attention_ocl))
                 {
                  delete neuron_attention_ocl;
                  return false;
                 }
               neuron_attention_ocl = NULL;
               break;
            //---
            case defNeuronMHAttentionOCL:
               neuron_attention_ocl = new CNeuronMHAttentionOCL();
               if(CheckPointer(neuron_attention_ocl) == POINTER_INVALID)
                  return false;
               if(!neuron_attention_ocl.Init(outputs, n, opencl, desc.window, desc.count, desc.optimization, desc.batch))
                 {
                  delete neuron_attention_ocl;
                  return false;
                 }
               neuron_attention_ocl.SetActivationFunction(desc.activation);
               if(!layer.Add(neuron_attention_ocl))
                 {
                  delete neuron_attention_ocl;
                  return false;
                 }
               neuron_attention_ocl = NULL;
               break;
            //---
            case defNeuronMLMHAttentionOCL:
               neuron_mlattention_ocl = new CNeuronMLMHAttentionOCL();
               if(CheckPointer(neuron_mlattention_ocl) == POINTER_INVALID)
                  return false;
               if(!neuron_mlattention_ocl.Init(outputs, n, opencl, desc.window, desc.window_out, desc.step, desc.count, desc.layers, desc.optimization, desc.batch))
                 {
                  delete neuron_mlattention_ocl;
                  return false;
                 }
               neuron_mlattention_ocl.SetActivationFunction(desc.activation);
               if(!layer.Add(neuron_mlattention_ocl))
                 {
                  delete neuron_mlattention_ocl;
                  return false;
                 }
               neuron_mlattention_ocl = NULL;
               break;
            //---
            case defNeuronDropoutOCL:
               dropout = new CNeuronDropoutOCL();
               if(CheckPointer(dropout) == POINTER_INVALID)
                  return false;
               if(!dropout.Init(outputs, n, opencl, desc.count, desc.probability, desc.optimization, desc.batch))
                 {
                  delete dropout;
                  return false;
                 }
               if(!layer.Add(dropout))
                 {
                  delete dropout;
                  return false;
                 }
               dropout = NULL;
               break;
            //---
            case defNeuronBatchNormOCL:
               batch = new CNeuronBatchNormOCL();
               if(CheckPointer(batch) == POINTER_INVALID)
                  return false;
               if(!batch.Init(outputs, n, opencl, desc.count, desc.batch, desc.optimization))
                 {
                  delete batch;
                  return false;
                 }
               batch.SetActivationFunction(desc.activation);
               if(!layer.Add(batch))
                 {
                  delete batch;
                  return false;
                 }
               batch = NULL;
               break;
            //---
            case defNeuronVAEOCL:
               vae = new CVAE();
               if(!vae)
                  return false;
               if(!vae.Init(outputs, n, opencl, desc.count, desc.optimization, desc.batch))
                 {
                  delete vae;
                  return false;
                 }
               if(!layer.Add(vae))
                 {
                  delete vae;
                  return false;
                 }
               vae = NULL;
               break;
            case defNeuronLSTMOCL:
               lstm = new CNeuronLSTMOCL();
               if(!lstm)
                  return false;
               if(!lstm.Init(outputs, n, opencl, desc.count, desc.optimization, desc.batch))
                 {
                  delete lstm;
                  return false;
                 }
               if(!lstm.SetInputs(prev_count))
                 {
                  delete lstm;
                  return false;
                 }
               if(!layer.Add(lstm))
                 {
                  delete lstm;
                  return false;
                 }
               lstm = NULL;
               break;
            //---
            case defNeuronSoftMaxOCL:
               neuron_ocl = new CNeuronSoftMaxOCL();
               if(!neuron_ocl)
                  return false;
               if(!neuron_ocl.Init(outputs, n, opencl, desc.count, desc.optimization, desc.batch))
                 {
                  delete neuron_ocl;
                  return false;
                 }
               if(!layer.Add(neuron_ocl))
                 {
                  delete neuron_ocl;
                  return false;
                 }
               neuron_ocl = NULL;
               break;
            //---
            default:
               return false;
               break;
           }
        }
      if(layer.Total() > (int)i_PopulationSize)
         layer.Resize(i_PopulationSize);
      delete desc;
     }
//---
   return true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::feedForward(CArrayFloat *inputVals, int window = 1, bool tem = true)
  {
   if(CheckPointer(layers) == POINTER_INVALID || CheckPointer(inputVals) == POINTER_INVALID || layers.Total() <= 1)
      return false;
//---
   CLayer *previous = NULL;
   CLayer *current = layers.At(0);
   int total = MathMin(current.Total(), inputVals.Total());
   CNeuronBase *neuron = NULL;
   if(CheckPointer(opencl) == POINTER_INVALID)
      return false;
   CNeuronBaseOCL *neuron_ocl = current.At(0);
   CBufferFloat *inputs = neuron_ocl.getOutput();
   int total_data = inputVals.Total();
   if(!inputs.Resize(total_data))
      return false;
   for(int d = 0; d < total_data; d++)
     {
      int pos = d;
      int dim = 0;
      if(window > 1)
        {
         dim = d % window;
         pos = (d - dim) / window;
        }
      float value = pos / pow(10000, (2 * dim + 1) / (float)(window + 1));
      value = (float)(tem ? (dim % 2 == 0 ? sin(value) : cos(value)) : 0);
      value += inputVals.At(d);
      if(!inputs.Update(d, value))
         return false;
     }
   if(!inputs.BufferWrite())
      return false;
//---
   CNeuronBaseOCL *current_ocl;
   for(int l = 1; l < layers.Total(); l++)
     {
      previous = current;
      current = layers.At(l);
      if(CheckPointer(current) == POINTER_INVALID)
         return false;
      //---
      for(uint n = 0; n < i_PopulationSize; n++)
        {
         current_ocl = current.At(n);
         if(!current_ocl.FeedForward(previous.At(l == 1 ? 0 : n)))
            return false;
         continue;
        }
     }
//---
   return true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::Rewards(CArrayFloat *rewards)
  {
   if(!rewards || !layers || layers.Total() < 2)
      return false;
//---
   CLayer *output = layers.At(layers.Total() - 1);
   if(!output)
      return false;
//---
   for(int i = 0; i < output.Total(); i++)
     {
      CNeuronBaseOCL *neuron = output.At(i);
      if(!neuron)
         continue;
      int action = GetAction(neuron.getOutput());
      v_Rewards[i] += rewards.At(action >= 0 ? action : rewards.Minimum(0, WHOLE_ARRAY));
     }
//---
   v_Probability = v_Rewards - v_Rewards.Min();
   if(!v_Probability.Clip(0, v_Probability.Max()))
      return false;
   v_Probability = v_Probability / v_Probability.Sum();
//---
   return true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CNetGenetic::GetAction(CBufferFloat * probability)
  {
   vectorf prob;
   if(!probability.GetData(prob))
      return -1;
   prob = prob.CumSum();
   prob = prob / prob.Max();
   int err_code;
   float random = (float)Math::MathRandomNormal(0.5, 0.5, err_code);
   if(random >= 1)
      return (int)prob.Size() - 1;
   for(int i = 0; i < (int)prob.Size(); i++)
      if(random <= prob[i] && prob[i] > 0)
         return i;
//---
   return -1;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::NextGeneration(float quantile, float mutation, float &average, float &maximum)
  {
   maximum = v_Rewards.Max();
   mutation = MathMin(mutation, MaxMutation);
   v_Probability = v_Rewards - v_Rewards.Quantile(quantile);
   if(!v_Probability.Clip(0, v_Probability.Max()))
      return false;
   v_Probability = v_Probability / v_Probability.Sum();
   average = v_Rewards.Average(v_Probability);
//---
   if(!v_Probability.Resize(i_PopulationSize + 1))
      return false;
   v_Probability[i_PopulationSize] = mutation;
   v_Probability = (v_Probability / (1 + mutation)).CumSum();
//---
   for(int l = 1; l < layers.Total(); l++)
     {
      if(!GetWeights(l))
        {
         PrintFormat("Error of load weights from layer %d", l);
         return false;
        }
      CLayer* layer = layers.At(l);
      if(!layer)
         return false;
      for(uint i = 0; i < i_PopulationSize; i++)
        {
         CNeuronBaseOCL* neuron = layer.At(i);
         CBufferFloat* weights = neuron.getWeights();
         if(!!weights)
           {
            for(int w = 0; w < weights.Total(); w++)
               if(!weights.Update(w, NextGenerationWeight(m_Weights, w, v_Probability)))
                 {
                  Print("Error of update weights");
                  return false;
                 }
            weights.BufferWrite();
           }
         if(neuron.Type() != defNeuronConvOCL)
            continue;
         CNeuronConvOCL* temp = neuron;
         weights = temp.GetWeightsConv();
         for(int w = 0; w < weights.Total(); w++)
            if(!weights.Update(w, NextGenerationWeight(m_WeightsConv, w, v_Probability)))
              {
               Print("Error of update weights");
               return false;
              }
         weights.BufferWrite();
        }
     }
   v_Rewards.Fill(0);
//---
   return true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
float CNetGenetic::NextGenerationWeight(matrixf &array, uint shift, vectorf &probability)
  {
   int err_code;
   float random = (float)Math::MathRandomNormal(0.5, 0.5, err_code);
   for(int i = 0; i < (int)probability.Size(); i++)
      if(probability[i] > 0 && random <= probability[i] && i < (int)array.Rows())
        {
         return array[i, shift];
        }
//---
   return GenerateWeight((uint)array.Cols());
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::GetWeights(uint layer)
  {
   if(!layers)
      return false;
   CLayer *current_layer = layers.At(layer);
   if(!current_layer)
      return false;
   int total = current_layer.Total();
   m_Weights = matrixf::Zeros(total, 0);
   m_WeightsConv = matrixf::Zeros(total, 0);
   for(int m = 0; m < total; m++)
     {
      CNeuronBaseOCL *neuron = current_layer.At(m);
      if(!neuron)
         return false;
      vectorf buffer;
      if(!!neuron.getWeights())
        {
         neuron.getWeights().GetData(buffer);
         if(m_Weights.Cols() <= 0)
            if(!m_Weights.Resize(total, buffer.Size()))
               return false;
         if(!m_Weights.Row(buffer, m))
            continue;
        }
      //---
      if(neuron.Type() != defNeuronConvOCL)
         continue;
      CNeuronConvOCL *temp = neuron;
      if(temp.GetWeightsConv().GetData(buffer) <= 0)
         return false;
      if(m_WeightsConv.Cols() != buffer.Size())
         if(!m_WeightsConv.Resize(total, buffer.Size()))
            return false;
      if(!m_WeightsConv.Row(buffer, m))
         return false;
     }
//---
   return true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::SaveModel(string file_name, int model, bool common = true)
  {
   if(model < 0 || model >= (int)i_PopulationSize)
      model = (int)v_Probability.ArgMax();
//---
   CNetGenetic *new_model = new CNetGenetic();
   if(!new_model)
      return false;
   if(!new_model.CopyModel(layers, model))
     {
      new_model.Detach();
      delete new_model;
      return false;
     }
   bool result = new_model.Save(file_name, 0, 0, 0, 0, common);
   new_model.Detach();
   delete new_model;
//---
   return result;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::CopyModel(CArrayLayer *source, uint model)
  {
   if(!source)
      return false;
   if(!layers)
     {
      layers = new CArrayLayer();
      if(!layers)
         return false;
     }
   else
      layers.Clear();
   for(int l = 0; l < source.Total(); l++)
     {
      CLayer* source_layer = source.At(l);
      if(!source_layer)
         return false;
      CLayer* new_layer = new CLayer(source_layer.Outputs(), -1, opencl);
      if(!new_layer)
         return false;
      if(!new_layer.Add(source_layer.At(l == 0 ? 0 : model)))
         return false;
      if(!layers.Add(new_layer))
         return false;
     }
//---
   return true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CNetGenetic::Detach(void)
  {
   if(!layers || layers.Total() <= 0)
      return true;
   int total = layers.Total();
   for(int i = 0; i < total; i++)
     {
      CLayer *layer = layers.At(i);
      if(!layer)
         continue;
      for(int n = layer.Total() - 1; n >= 0; n--)
         CObject *temp = layer.Detach(n);
     }
//---
   return true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
float CNetGenetic::GenerateWeight(uint total)
  {
   xor128;
   float result = (float)rnd_w / UINT_MAX;
   if(result == 0)
      return GenerateWeight(total);
   float k = (float)(1 / sqrt(total + 1));
   result = (2 * result * k - k) * WeightsMultiplier;
//---
   return result;
  }
//+------------------------------------------------------------------+
