//+------------------------------------------------------------------+
//|                       MathEngineAlglib.mqh                       |
//|                       https://www.mql5.com                       |
//+------------------------------------------------------------------+
#property copyright "Olamide Adebayo"
#property link      "https://www.mql5.com"

#include <Math\Alglib\alglib.mqh>
#include "BenchmarkConfig.mqh"
#include "WeightLoader.mqh"

//+------------------------------------------------------------------+
//| CMathEngineAlglib                                                |
//| Engine 2: CMatrixDouble as the weight container, float64.        |
//| The multiply-accumulate is written as plain loops over the       |
//| container indexers, not a GEMM call. That is how most EAs use    |
//| ALGLIB for a small forward pass, and it isolates one question:   |
//| what does the container and indexing cost at equal precision?    |
//+------------------------------------------------------------------+
class CMathEngineAlglib
  {
private:
   CMatrixDouble     m_W1, m_W2, m_W3;
   double            m_b1[], m_b2[], m_b3[];
   bool              m_ready;

//+------------------------------------------------------------------+
//| CMathEngineAlglib::LoadMatrix                                    |
//+------------------------------------------------------------------+
   void              LoadMatrix(CMatrixDouble &dst, double &flat[], const int rows, const int cols)
     {
      dst.Resize(rows, cols);
      for(int r = 0; r < rows; r++)
         for(int c = 0; c < cols; c++)
            dst.Set(r, c, flat[r * cols + c]);
     }

//+------------------------------------------------------------------+
//| CMathEngineAlglib::ForwardLayer                                  |
//+------------------------------------------------------------------+
   void              ForwardLayer(const double &in[], const int in_dim,
                                   CMatrixDouble &W, const double &b[], const int out_dim,
                                   double &out[], const bool relu)
     {
      ArrayResize(out, out_dim);
      for(int o = 0; o < out_dim; o++)
        {
         double acc = b[o];
         for(int i = 0; i < in_dim; i++)
            acc += in[i] * W[i][o];
         out[o] = (relu && acc < 0.0) ? 0.0 : acc;
        }
     }

public:
                     CMathEngineAlglib(void) : m_ready(false) {}

//+------------------------------------------------------------------+
//| CMathEngineAlglib::Init                                          |
//+------------------------------------------------------------------+
   bool              Init(CWeightLoader &loader)
     {
      if(!loader.m_loaded)
         return false;

      LoadMatrix(m_W1, loader.W1, BENCH_INPUT_DIM,   BENCH_HIDDEN1_DIM);
      LoadMatrix(m_W2, loader.W2, BENCH_HIDDEN1_DIM, BENCH_HIDDEN2_DIM);
      LoadMatrix(m_W3, loader.W3, BENCH_HIDDEN2_DIM, BENCH_OUTPUT_DIM);

      ArrayCopy(m_b1, loader.b1);
      ArrayCopy(m_b2, loader.b2);
      ArrayCopy(m_b3, loader.b3);

      m_ready = true;
      return true;
     }

//+------------------------------------------------------------------+
//| CMathEngineAlglib::Predict                                       |
//+------------------------------------------------------------------+
   double            Predict(const double &in_vec[])
     {
      double h1[], h2[], out[];
      ForwardLayer(in_vec, BENCH_INPUT_DIM,   m_W1, m_b1, BENCH_HIDDEN1_DIM, h1, true);
      ForwardLayer(h1,    BENCH_HIDDEN1_DIM, m_W2, m_b2, BENCH_HIDDEN2_DIM, h2, true);
      ForwardLayer(h2,    BENCH_HIDDEN2_DIM, m_W3, m_b3, BENCH_OUTPUT_DIM,  out, false);
      return out[0];
     }

   bool              IsReady(void) const { return m_ready; }
  };
