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

#include "BenchmarkConfig.mqh"
#include "WeightLoader.mqh"

//+------------------------------------------------------------------+
//| CMathEngineNative                                                |
//| Engine 1: built-in matrix/vector types, float64 end to end.      |
//| The other two engines are judged against this one for speed.     |
//+------------------------------------------------------------------+
class CMathEngineNative
  {
private:
   matrix<double>    m_W1, m_W2, m_W3;
   vector<double>    m_b1, m_b2, m_b3;
   bool              m_ready;

//+------------------------------------------------------------------+
//| CMathEngineNative::LoadMatrix                                    |
//+------------------------------------------------------------------+
   // matrix::Assign(array) does not keep the shape set by Init() -- it
   // silently collapses the matrix to one row of rows*cols columns, so
   // every W[i][o] lookup past row 0 goes out of range. Filling each
   // element directly is the only reshape-proof way to load these.
   void              LoadMatrix(matrix<double> &dst, double &flat[], const int rows, const int cols)
     {
      dst.Init(rows, cols);
      for(int r = 0; r < rows; r++)
         for(int c = 0; c < cols; c++)
            dst[r][c] = flat[r * cols + c];
     }

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

//+------------------------------------------------------------------+
//| CMathEngineNative::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);

      // vectors have no 2D shape to lose, so Assign() is safe here
      m_b1.Init(BENCH_HIDDEN1_DIM); m_b1.Assign(loader.b1);
      m_b2.Init(BENCH_HIDDEN2_DIM); m_b2.Assign(loader.b2);
      m_b3.Init(BENCH_OUTPUT_DIM);  m_b3.Assign(loader.b3);

      m_ready = true;
      return true;
     }

//+------------------------------------------------------------------+
//| CMathEngineNative::Layer                                         |
//+------------------------------------------------------------------+
   // one dense layer: out = relu(in * W + b), read straight off the
   // matrix/vector containers -- vector*matrix has no operator here
   void              Layer(const vector<double> &in, const matrix<double> &W,
                            const vector<double> &b, const bool relu, vector<double> &out)
     {
      ulong out_dim = b.Size();
      ulong in_dim  = in.Size();
      out.Init(out_dim);
      for(ulong o = 0; o < out_dim; o++)
        {
         double acc = b[o];
         for(ulong i = 0; i < in_dim; i++)
            acc += in[i] * W[i][o];
         out[o] = (relu && acc < 0.0) ? 0.0 : acc;
        }
     }

//+------------------------------------------------------------------+
//| CMathEngineNative::Predict                                       |
//+------------------------------------------------------------------+
   double            Predict(const vector<double> &in_vec)
     {
      vector<double> h1, h2, out;
      Layer(in_vec, m_W1, m_b1, true,  h1);
      Layer(h1,    m_W2, m_b2, true,  h2);
      Layer(h2,    m_W3, m_b3, false, out);
      return out[0];
     }

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