//+------------------------------------------------------------------+ //| num_diff.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| directional scheme options | //+------------------------------------------------------------------+ enum ENUM_SCHEME_DIRECTION { SCHEME_1=0,// 1-sided finite difference scheme (Forward or Backward) SCHEME_2 // 2-sided finite difference scheme (Central) }; //+------------------------------------------------------------------+ //| num points of evaluation | //+------------------------------------------------------------------+ enum ENUM_DIFF_POINTS { GRAD_POINT_2=0, // 2-point numerical differentiation (e.g., forward difference) GRAD_POINT_3, // 3-point numerical differentiation (e.g., central/stencil difference) GRAD_POINT_CS, // Complex step derivative estimation method GRAD_POINT_CALLABLE // Exact analytical gradient is available via a callback function }; //+------------------------------------------------------------------+ //| num points of evaluation | //+------------------------------------------------------------------+ enum ENUM_HESS_DIFF_POINTS { HESS_POINT_2=0, // 2-point estimation for Hessian calculation HESS_POINT_3, // 3-point estimation for Hessian calculation HESS_POINT_CS, // Complex step differentiation for Hessian estimation HESS_POINT_HESS_STRATEGY, // Quasi-Newton Hessian update strategy (e.g., BFGS, L-BFGS) HESS_POINT_CALLABLE // Exact analytical Hessian is available via a callback function }; //+------------------------------------------------------------------+ //| struct objective function return | //+------------------------------------------------------------------+ struct ObjReturn { double f; // Scalar value of the objective function: f(x) vector g; // Gradient vector: df/dx vector mf; // Multi-valued objective function return (for vector-valued functions) matrix mg; // Jacobian matrix / Multi-valued gradient response // Default constructor initializing structures to empty/zero values ObjReturn(void) { f = double(0); g = vector::Zeros(0); mf = vector::Zeros(0); mg = matrix::Zeros(0,0); } // Copy constructor ObjReturn(ObjReturn& other) { f = other.f; g = other.g; mf = other.mf; mg = other.mg; } // Assignment operator overload void operator=(ObjReturn& other) { f = other.f; g = other.g; mf = other.mf; mg = other.mg; } }; //+------------------------------------------------------------------+ //| IObjective provides the base interface for any function | //| that will be provided to a minimizer routine | //+------------------------------------------------------------------+ interface IObjective { // Evaluates and returns the vector-valued response of the objective function at point x vector objective_function(vector& x); // Evaluates both the scalar objective value and its gradient at point x ObjReturn fun_and_grad(vector& x); }; //+------------------------------------------------------------------+ //| differentiation options | //+------------------------------------------------------------------+ struct GradDiffOptions { ENUM_DIFF_POINTS method; // Numerical differentiation technique selection vector rel_step; // Relative step size vector for finite differences vector abs_step; // Absolute step size vector for finite differences matrix bounds; // Boundary constraints matrix [N x 2] containing [lower, upper] limits // Default constructor initializing variables to invalid or empty states GradDiffOptions(void) { method = WRONG_VALUE; rel_step = abs_step = vector::Zeros(0); bounds = matrix::Zeros(0,0); } // Parameterized constructor GradDiffOptions(ENUM_DIFF_POINTS m, vector& relstep, vector& absstep, matrix& bnds) { method = m; rel_step = relstep; abs_step = absstep; bounds = bnds; } // Copy constructor GradDiffOptions(GradDiffOptions& other) { method = other.method; rel_step = other.rel_step; abs_step = other.abs_step; bounds = other.bounds; } // Assignment operator overload void operator=(GradDiffOptions& other) { method = other.method; rel_step = other.rel_step; abs_step = other.abs_step; bounds = other.bounds; } }; //+------------------------------------------------------------------+ //| differentiation options | //+------------------------------------------------------------------+ struct HessDiffOptions { bool as_linear_operator; // Flag to treat Hessian operations as matrix-free linear operator mappings ENUM_HESS_DIFF_POINTS method; // Hessian evaluation method selection vector rel_step; // Relative step sizes for Hessian finite differences vector abs_step; // Absolute step sizes for Hessian finite differences // Default constructor HessDiffOptions(void) { method = WRONG_VALUE; as_linear_operator = false; rel_step = abs_step = vector::Zeros(0); } // Parameterized constructor HessDiffOptions(ENUM_HESS_DIFF_POINTS m, vector& relstep, vector& absstep, bool aslinearoperator) { method = m; rel_step = relstep; abs_step = absstep; as_linear_operator = aslinearoperator; } // Copy constructor HessDiffOptions(HessDiffOptions& other) { method = other.method; rel_step = other.rel_step; abs_step = other.abs_step; as_linear_operator = other.as_linear_operator; } // Assignment operator overload void operator=(HessDiffOptions& other) { method = other.method; rel_step = other.rel_step; abs_step = other.abs_step; as_linear_operator = other.as_linear_operator; } }; //+---------------------------------------------------------------------------+ //| function objective representing the objective function and its derivatives.| //+---------------------------------------------------------------------------+ class CFunctor:public IObjective { protected: vector m_xp; // Previous execution parameters state vector vector m_x; // Current state variable parameters vector ulong m_n; // Dimensionality size of the parameter vector matrix m_H; // System Hessian matrix int m_nfev,m_ngev,m_nhev; // Evaluation counters for function, gradient, and Hessian evaluations bool m_fupdated,m_gupdated,m_hupdated; // State dirty flags tracking whether properties require calculation double m_lowest_f,m_f; // Globally tracked minimum objective value discovered, and current value vector m_lowest_x,m_g; // Variable state coordinates yielding m_lowest_f, and current gradient vector bool m_clip; // Enforcement flag to bound variables before function calculations GradDiffOptions m_grad_options; // Operational configuration settings for gradients HessDiffOptions m_hess_options; // Operational configuration settings for Hessians // Internal method to compute and update the objective function value if state changed void update_fun(void) { if(!m_fupdated) { double fx = wrapped_fun(m_x); if(fxbnds[i,1]) x[i] = bnds[i,1]; } } vector copy = x; return orig_fun(copy); } // Interface compliance proxy method accessing calculation engines vector objective_function(vector& x) { vector r(1); r[0] = wrapped_fun(x); return r; } // Evaluates analytical solutions or forwards routines into numerical step approximations for gradients vector wrapped_grad(vector& x,vector& f0) { m_ngev += 1; if(m_clip) { matrix bnds = m_grad_options.bounds; for(ulong i = 0; ibnds[i,1]) x[i] = bnds[i,1]; } } vector copy = x; if(m_grad_options.method == GRAD_POINT_CALLABLE) return grad_fun(copy); IObjective* objective = GetPointer(this); matrix ad = approx_derivative(objective,copy,f0,m_grad_options.method,m_grad_options.rel_step,m_grad_options.abs_step,m_grad_options.bounds); return ad.Row(0); } // Evaluates analytical solutions or forwards routines into numerical step approximations for Hessians matrix wrapped_hess(vector& x, vector& f0) { m_nhev += 1; if(m_clip) { matrix bnds = m_grad_options.bounds; for(ulong i = 0; ibnds[i,1]) x[i] = bnds[i,1]; } } vector copy = x; if(m_hess_options.method == HESS_POINT_CALLABLE) return hess_fun(copy); IObjective* objective = GetPointer(this); return approx_derivative(objective,x,f0,m_grad_options.method,m_grad_options.rel_step,m_grad_options.abs_step,m_grad_options.bounds); } // Data parsing helpers extracting specific constraints vectors vector lower_bounds(void) { return m_grad_options.bounds.Col(0); } vector upper_bounds(void) { return m_grad_options.bounds.Col(1); } vector initial_params(void) { return m_xp; } // Virtual placeholder declarations designed to be overriden by inherited custom user targets virtual double orig_fun(vector& x) { return double("nan"); } virtual vector grad_fun(vector& x) { return vector::Zeros(0); } virtual matrix hess_fun(vector& x) { return matrix::Zeros(0,0); } // Consolidated extraction layer getting simultaneously computed coordinates pairs ObjReturn fun_and_grad(vector& x) { vector dif = MathAbs(m_x - x); if(dif.Sum() >= DBL_EPSILON || dif.HasNan()) update_x(x); update_fun(); update_grad(); ObjReturn out; out.f = m_f; out.g = m_g; return out; } }; //+------------------------------------------------------------------+ //| base class representing function and its derivative | //+------------------------------------------------------------------+ class CConstraints:public IObjective { protected: int m_n,m_m; // m_n: parameter dimension space size, m_m: total quantity of constraint equations bool m_clip,m_initialized; // Status switches managing operation modes and validation configurations GradDiffOptions m_options; // Settings configuration defining differentiation procedures // Evaluates constraint functions wrapping execution boundaries securely vector wrapped_func(vector& x) { if(!m_initialized) return vector::Zeros(0); if(m_clip) { matrix bnds = m_options.bounds; for(ulong i = 0; ibnds[i,1]) x[i] = bnds[i,1]; } } vector copy = x; return orig_fun(copy); } // Computes the constraints Jacobian matrix (numerical or analytical derivations) matrix wrapped_grad(vector& x,vector& f0) { if(!m_initialized) return matrix::Zeros(0,0); if(m_clip) { matrix bnds = m_options.bounds; for(ulong i = 0; ibnds[i,1]) x[i] = bnds[i,1]; } } vector copy = x; if(m_options.method == GRAD_POINT_CALLABLE) return orig_grad(copy); IObjective* fun = (IObjective*)GetPointer(this); matrix ad = approx_derivative(fun,copy,f0,m_options.method,m_options.rel_step,m_options.abs_step,m_options.bounds); return ad; } public: CConstraints(void) { m_n = m_m = 0; m_clip = true; m_initialized = false; m_options.method = GRAD_POINT_2; } ~CConstraints(void) { } // Validates systems shapes and initializes structural memory arrays bool initialize(vector& x) { if((m_m == 0 && m_n) || (int)x.Size()!=m_n) { Print(__FUNCTION__," ","Invalid property dimensions.", " n ", m_n, " vs ", x.Size()); return false; } vector check = orig_fun(x); if(!check.Size() || check.HasNan() || MathClassify(fabs(check.Max())) == FP_INFINITE) { Print(__FUNCTION__," ","Check the implementation of the constraints function."); return false; } if(m_options.method == GRAD_POINT_CALLABLE) { matrix a = orig_grad(x); if(a.Rows()!=m_m || a.HasNan() || MathClassify(fabs(a.Max())) == FP_INFINITE) { Print(__FUNCTION__," ","Check the implementation of the overriden constraints gradient function."); return false; } } if(m_options.bounds.Rows()!=ulong(m_n)) { m_options.bounds.Resize(m_n,2); m_options.bounds.Fill(double("inf")); for(int i = 0; i= 0.) signx0[i]=1.*2-1; // Yields 1.0 else signx0[i] = 0.0*2-1; // Yields -1.0 } double rstep = eps_for_method(method); // Automatically generates fallback absolute scaling matrices if relative instructions are missing if(rel_step.Size()==0) for(ulong i = 0; iub[i]); fitting = int(fabs(h_total[i])<=MathMax(lower_dist[i],upper_dist[i])); if(bool(violated & fitting)) h_adjusted[i]*=-1.; // Flip the direction to stay within bounds forward = int((upper_dist[i] >= lower_dist[i]) & ~fitting); if(forward) h_adjusted[i] = upper_dist[i]/double(num_steps); backward = int((upper_dist[i]=h_total[i]) & (upper_dist[i] >= h_total[i]))); forward = int(((upper_dist[i]>=lower_dist[i]) & ~central)); if(forward) { h_adjusted[i] = MathMin(h[i],0.5*upper_dist[i]/double(num_steps)); one_sided[i] = 1.; // Enforce transformation shift to 1-sided estimation layout } backward = int(((upper_dist[i]ub[i]) { Print(__FUNCTION__, " x0 violates bound constraints "); return matrix::Zeros(0,0); } // Step evaluation logic execution passes vector h; if(!abs_step.Size()) h = compute_absolute_step(rel_step,x0,f0,method); else { h = abs_step; vector signx0 = vector::Zeros(x0.Size()); for(ulong i = 0; i=0.0) signx0[i] = 1.0*2.-1.; else signx0[i] = 0.0*2.-1.; if(((x0[i]+h[i]) - x0[i]) == 0.0) h[i] = eps_for_method(method)*signx0[i]*MathMax(1.,fabs(x0[i])); } } // Schemes alignment corrections based on active borders settings vector use_one_sided; switch(method) { case GRAD_POINT_2: h = adjust_scheme_to_bounds(x0,h,1,SCHEME_1,lb,ub,use_one_sided); break; case GRAD_POINT_3: h = adjust_scheme_to_bounds(x0,h,1,SCHEME_2,lb,ub,use_one_sided); break; case GRAD_POINT_CS: use_one_sided = vector::Zeros(x0.Size()); break; } // Triggers execution calculation pipelines over defined matrices grids return dense_difference(fun,x0,f0,h,use_one_sided,method); } //+------------------------------------------------------------------+