//+------------------------------------------------------------------+ //| slspq.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #include "num_diff.mqh" #include //--- #define SLSQP_EPS 2.220446049250313e-16 //--- //+------------------------------------------------------------------+ //| copy from vector to array | //+------------------------------------------------------------------+ template bool Copy(T& dest_array[],vector& src_vec,int count,int dest_start = 0, int src_start = 0) { // Bounds verification checking if requested copy counts exceed dimensions of destination or source if(int(dest_array.Size()) bool Copy(vector& dest_vec, T& src_array[],int count,int dest_start = 0, int src_start = 0) { // Bounds verification matching dynamic vector tracking against plain reference buffer indices if(dest_vec.Size() 1) with their multi-dimensional Jacobians void mf(int mm, double& result[], int n, double& x[],double& gradient[],IObjective* func_data, int &offset) { vector vx = vector::Zeros(n); Copy(vx,x,n); // Use the global helper function to move array inputs into vector format // Joint vector evaluation block mapping multiple outputs and multi-gradient dimensions if(gradient.Size()) { ObjReturn or = func_data.fun_and_grad(vx); Copy(result,or.mf,mm,offset); for(int j = 0; j max_dim) max_dim = c[i].m; return max_dim; } //+------------------------------------------------------------------+ //| evaluates a or all constraints | //+------------------------------------------------------------------+ // Directs single scalar vs multi-dimensional constraint vector metrics reporting to targeted workspace targets void slsqp_eval_constraint(double& result[], double& grad[], slsqp_constraint& c, int n, double& x[], int offset) { if(c.m == 1) result[offset] = c.f(n, x, grad, c.f_data,offset); // Direct scalar assignment mapping else c.mf(c.m, result, n, x, grad, c.f_data,offset); // Structured array segment expansion pass } //+------------------------------------------------------------------+ //| Small utilities | //+------------------------------------------------------------------+ double SLSQP_NaN() { double zero = 0.0; return(MathSqrt(-1.0 + zero)); // NaN sentinel, used exactly like SciPy's convention // for "this bound is absent" (xl[i]/xu[i] = NaN). } //+------------------------------------------------------------------+ //| Is not a number wrapper | //+------------------------------------------------------------------+ bool SLSQP_IsNaN(const double v) { return(!MathIsValidNumber(v)); } //+------------------------------------------------------------------+ //| Apply sign change to input | //+------------------------------------------------------------------+ double SLSQP_CopySign(const double mag, const double sgn) { double a = MathAbs(mag); return(sgn < 0.0 ? -a : a); } //+------------------------------------------------------------------+ //| Hypotenuse computation | //+------------------------------------------------------------------+ double SLSQP_Hypot(const double a, const double b) { double aa = MathAbs(a), ab = MathAbs(b); if(aa == 0.0 && ab == 0.0) return(0.0); if(aa > ab) return(aa*MathSqrt(1.0 + (ab/aa)*(ab/aa))); return(ab*MathSqrt(1.0 + (aa/ab)*(aa/ab))); } //+------------------------------------------------------------------+ //| Level-1 / Level-2 "BLAS" primitives | //| Every array-taking function below uses an (array[], offset) | //| pair in place of a C pointer: arr[off+i] instead of ptr[i]. | //| These are close, deliberately unoptimized transliterations of | //| the reference BLAS algorithms -- validated in C against a full | //| SciPy SLSQP fuzz suite before being ported here. | //+------------------------------------------------------------------+ void SLSQP_daxpy(const int n, const double alpha, const double &x[], const int xo, const int incx, double &y[], const int yo, const int incy) { for(int i = 0; i < n; i++) y[yo + i*incy] += alpha*x[xo + i*incx]; } //+------------------------------------------------------------------+ //|scales vector X(n) by constant da | //+------------------------------------------------------------------+ void SLSQP_dscal(const int n, const double alpha, double &x[], const int xo, const int incx) { for(int i = 0; i < n; i++) x[xo + i*incx] *= alpha; } //+------------------------------------------------------------------+ //|compute the L2 norm of array DX of length N, stride INCX | //+------------------------------------------------------------------+ double SLSQP_dnrm2(const int n, const double &x[], const int xo, const int incx) { double s = 0.0; for(int i = 0; i < n; i++) { double v = x[xo + i*incx]; s += v*v; } return(MathSqrt(s)); } //+------------------------------------------------------------------+ //| dot product dx dot dy | //+------------------------------------------------------------------+ double SLSQP_ddot(const int n, const double &x[], const int xo, const int incx, const double &y[], const int yo, const int incy) { double s = 0.0; for(int i = 0; i < n; i++) s += x[xo + i*incx]*y[yo + i*incy]; return(s); } //+------------------------------------------------------------------------+ //| y = alpha*op(A)*x + beta*y ; A is m x n, column-major, leading dim lda.| //| trans=false -> op(A)=A (y has length m, x has length n) | //| trans=true -> op(A)=A^T (y has length n, x has length m) | //+------------------------------------------------------------------------+ void SLSQP_dgemv(const bool trans, const int m, const int n, const double alpha, const double &a[], const int ao, const int lda, const double &x[], const int xo, const int incx, const double beta, double &y[], const int yo, const int incy) { if(!trans) { for(int i = 0; i < m; i++) { double s = 0.0; for(int j = 0; j < n; j++) s += a[ao + i + j*lda]*x[xo + j*incx]; y[yo + i*incy] = beta*y[yo + i*incy] + alpha*s; } } else { for(int j = 0; j < n; j++) { double s = 0.0; for(int i = 0; i < m; i++) s += a[ao + i + j*lda]*x[xo + i*incx]; y[yo + j*incy] = beta*y[yo + j*incy] + alpha*s; } } } //+-----------------------------------------------------------------------------------------+ //|0-based packed lower-triangular column index (i>=j), matching LAPACK's packed 'L' layout.| //+-----------------------------------------------------------------------------------------+ int SLSQP_LtpIdx(const int i, const int j, const int n) { return(j*n - (j*(j-1))/2 + (i-j)); } //+-----------------------------------------------------------------------------------------+ //|x := op(L)*x, L unit lower-triangular, packed. trans=false -> L*x, trans=true -> L^T*x. | //| (Only uplo='L', diag='U' are ever needed by SLSQP.) | //+-----------------------------------------------------------------------------------------+ void SLSQP_dtpmv(const bool trans, const int n, const double &ap[], const int apo, double &x[], const int xo, const int incx) { if(!trans) { for(int j = n-1; j >= 0; j--) { double temp = x[xo + j*incx]; for(int i = n-1; i > j; i--) x[xo + i*incx] += temp*ap[apo + SLSQP_LtpIdx(i,j,n)]; } } else { for(int j = 0; j < n; j++) { double temp = x[xo + j*incx]; for(int i = j+1; i < n; i++) temp += ap[apo + SLSQP_LtpIdx(i,j,n)]*x[xo + i*incx]; x[xo + j*incx] = temp; } } } //+------------------------------------------------------------------------------+ //|Solve L*x = b in place (unit lower-triangular, packed, forward substitution). | //|(Only uplo='L', trans='N', diag='U' is ever needed by SLSQP.) | //+------------------------------------------------------------------------------+ void SLSQP_dtpsv(const int n, const double &ap[], const int apo, double &x[], const int xo, const int incx) { for(int j = 0; j < n; j++) { double xj = x[xo + j*incx]; if(xj != 0.0) { for(int i = j+1; i < n; i++) x[xo + i*incx] -= xj*ap[apo + SLSQP_LtpIdx(i,j,n)]; } } } //+---------------------------------------------------------------------------------------+ //|Solve op(A)*x = b in place, A upper-triangular n x n, non-unit diag. | //|trans=false -> A*x=b (back substitution), trans=true -> A^T*x=b (forward substitution).| //+---------------------------------------------------------------------------------------+ void SLSQP_dtrsv(const bool trans, const int n, const double &a[], const int ao, const int lda, double &x[], const int xo, const int incx) { if(!trans) { for(int j = n-1; j >= 0; j--) { if(x[xo + j*incx] != 0.0) { x[xo + j*incx] /= a[ao + j + j*lda]; double xj = x[xo + j*incx]; for(int i = 0; i < j; i++) x[xo + i*incx] -= xj*a[ao + i + j*lda]; } } } else { for(int j = 0; j < n; j++) { double s = x[xo + j*incx]; for(int i = 0; i < j; i++) s -= a[ao + i + j*lda]*x[xo + i*incx]; x[xo + j*incx] = s / a[ao + j + j*lda]; } } } //+---------------------------------------------------------------------------------------------------+ //|Solve X*A = alpha*B for X, A n x n upper-triangular (lda), B is m x n (ldb=m), overwritten with X. | //| This is the one specific pattern SLSQP's lsi() needs (BLAS dtrsm("R","U","N","N",...)). | //+---------------------------------------------------------------------------------------------------+ void SLSQP_dtrsm_RUNN(const int m, const int n, const double alpha, const double &a[], const int ao, const int lda, double &b[], const int bo, const int ldb) { for(int i = 0; i < m; i++) for(int j = 0; j < n; j++) b[bo + i + j*ldb] *= alpha; for(int j = 0; j < n; j++) { for(int k = 0; k < j; k++) { double akj = a[ao + k + j*lda]; if(akj != 0.0) { for(int i = 0; i < m; i++) b[bo + i + j*ldb] -= b[bo + i + k*ldb]*akj; } } double ajj = a[ao + j + j*lda]; for(int i = 0; i < m; i++) b[bo + i + j*ldb] /= ajj; } } //+------------------------------------------------------------------+ //| Householder / Givens helpers | //+------------------------------------------------------------------+ // Standard Householder reflector generation: reflects (alpha,x) -> (beta,0,...,0), // beta = -sign(alpha)*hypot(alpha,||x||). Overwrites alpha_arr[alpha_idx] with beta // and x[] with the reflector tail; returns tau via tau_arr[tau_idx]. void SLSQP_dlarfg_std(const int n, double &alpha_arr[], const int alpha_idx, double &x[], const int xo, const int incx, double &tau_arr[], const int tau_idx) { if(n <= 1) { tau_arr[tau_idx] = 0.0; return; } int nm1 = n-1; double xnorm = SLSQP_dnrm2(nm1, x, xo, incx); if(xnorm == 0.0) { tau_arr[tau_idx] = 0.0; return; } double a0 = alpha_arr[alpha_idx]; double beta = -SLSQP_CopySign(SLSQP_Hypot(a0, xnorm), a0); tau_arr[tau_idx] = (beta - a0)/beta; double scale = 1.0/(a0 - beta); for(int i = 0; i < nm1; i++) x[xo + i*incx] *= scale; alpha_arr[alpha_idx] = beta; } //+------------------------------------------------------------------------+ //|"Positive" variant: like dlarfg_std but forces beta >= 0. Used by NNLS. | //+------------------------------------------------------------------------+ void SLSQP_dlarfgp(const int n, double &alpha_arr[], const int alpha_idx, double &x[], const int xo, const int incx, double &tau_arr[], const int tau_idx) { if(n <= 1) { tau_arr[tau_idx] = 0.0; return; } int nm1 = n-1; double xnorm = SLSQP_dnrm2(nm1, x, xo, incx); double a0 = alpha_arr[alpha_idx]; if(xnorm == 0.0 && a0 >= 0.0) { tau_arr[tau_idx] = 0.0; return; } double beta = SLSQP_Hypot(a0, xnorm); // always >= 0 tau_arr[tau_idx] = (beta - a0)/beta; double abdiff = (a0 - beta); double scale = abdiff?1.0/abdiff:double("inf"); for(int i = 0; i < nm1; i++) x[xo + i*incx] *= scale; alpha_arr[alpha_idx] = beta; } //+-------------------------------------------------------------------------------+ //|"Positive" Givens rotation generator: [cs sn;-sn cs]*[f;g] = [r;0], r >= 0. | //| f, g are plain inputs (already-read scalar values); the caller is responsible | //| for writing r back into wherever f came from and zeroing wherever g came from | //| (mirroring exactly how nnls.c uses BLAS's dlartgp). | //+-------------------------------------------------------------------------------+ void SLSQP_dlartgp(const double f, const double g, double &cs, double &sn, double &r) { if(g == 0.0) { cs = 1.0; sn = 0.0; r = f; return; } if(f == 0.0) { cs = 0.0; sn = 1.0; r = g; return; } double rr = SLSQP_Hypot(f,g); r = rr; cs = f/rr; sn = g/rr; } //+----------------------------------------------------------------------------------------+ //|Apply a Householder reflector H = I - tau*v*v^T to C (m x n, leading dim ldc). | //| side_left=true: C := H*C (v has length m). side_left=false: C := C*H (v has length n). | //| v[vo] (with stride incv) must already hold its own explicit leading "1" element | //| (the caller temporarily pokes a 1.0 into the matrix position that represents it -- | //| this mirrors exactly how the reference LAPACK routines use DLARF internally). | //+----------------------------------------------------------------------------------------+ void SLSQP_dlarf(const bool side_left, const int m, const int n, const double &v[], const int vo, const int incv, const double tau, double &c[], const int co, const int ldc, double &work[], const int wo) { if(tau == 0.0) return; if(side_left) { for(int j = 0; j < n; j++) { double s = 0.0; for(int i = 0; i < m; i++) s += c[co + i + j*ldc]*v[vo + i*incv]; work[wo + j] = s; } for(int j = 0; j < n; j++) { double wj = tau*work[wo + j]; for(int i = 0; i < m; i++) c[co + i + j*ldc] -= v[vo + i*incv]*wj; } } else { for(int i = 0; i < m; i++) { double s = 0.0; for(int j = 0; j < n; j++) s += c[co + i + j*ldc]*v[vo + j*incv]; work[wo + i] = s; } for(int i = 0; i < m; i++) { double wi = tau*work[wo + i]; for(int j = 0; j < n; j++) c[co + i + j*ldc] -= wi*v[vo + j*incv]; } } } //+------------------------------------------------------------------+ //| Unblocked Householder QR (dgeqr2) and applying Q/Q^T (dorm2r) | //+------------------------------------------------------------------+ // QR-factorize A (m x n, leading dim lda) in place: R in the upper triangle // (including diagonal), reflectors stored below the diagonal, tau[0..min(m,n)-1]. void SLSQP_dgeqr2(const int m, const int n, double &a[], const int ao, const int lda, double &tau[], const int tauo, double &work[], const int wo) { int k = MathMin(m,n); for(int i = 0; i < k; i++) { int len = m - i; SLSQP_dlarfg_std(len, a, ao + i + i*lda, a, ao + (i+1) + i*lda, 1, tau, tauo + i); if(i < n-1) { double aii = a[ao + i + i*lda]; a[ao + i + i*lda] = 1.0; int mm = m - i, nn = n - i - 1; SLSQP_dlarf(true, mm, nn, a, ao + i + i*lda, 1, tau[tauo+i], a, ao + i + (i+1)*lda, lda, work, wo); a[ao + i + i*lda] = aii; } } } //+--------------------------------------------------------------------------------------------+ //|Apply Q or Q^T (from dgeqr2, side='L' only -- the only case SLSQP needs) to C (m x n, ldc). | //|trans=true -> C := Q^T*C ; trans=false -> C := Q*C. k = number of reflectors used. | //+--------------------------------------------------------------------------------------------+ void SLSQP_dorm2r(const bool trans, const int m, const int n, const int k, double &a[], const int ao, const int lda, double &tau[], const int tauo, double &c[], const int co, const int ldc, double &work[], const int wo) { int start, stop, step; if(trans) { start = 0; // Q^T*C = H(k-1)...H(0)*C -> apply ascending stop = k; step = 1; } else { start = k-1; // Q*C = H(0)...H(k-1)*C -> apply descending stop = -1; step = -1; } for(int i = start; i != stop; i += step) { double aii = a[ao + i + i*lda]; a[ao + i + i*lda] = 1.0; int mm = m - i, nn = n; SLSQP_dlarf(true, mm, nn, a, ao + i + i*lda, 1, tau[tauo+i], c, co + i, ldc, work, wo); a[ao + i + i*lda] = aii; } } //+------------------------------------------------------------------+ //| Unblocked Householder RQ (dgerq2) and applying Q/Q^T (dormr2) | //| RQ decomposition places R as the LAST m columns of the m x n | //| input matrix (upper triangular there); Q is n x n, represented | //| implicitly as a product of m elementary reflectors. | //+------------------------------------------------------------------+ void SLSQP_dgerq2(const int m, const int n, double &a[], const int ao, const int lda, double &tau[], const int tauo, double &work[], const int wo) { int k = MathMin(m,n); for(int idx = 0; idx < k; idx++) { int i = k - 1 - idx; int row = m - k + i; int pcol = n - k + i; // pivot column (rightmost of the swept range) int len = pcol + 1; // x = row 'row', columns 0..pcol-1, stepping across columns => stride = lda SLSQP_dlarfg_std(len, a, ao + row + pcol*lda, a, ao + row + 0*lda, lda, tau, tauo + idx); if(row > 0) { double aii = a[ao + row + pcol*lda]; a[ao + row + pcol*lda] = 1.0; int mm = row, nn = pcol + 1; SLSQP_dlarf(false, mm, nn, a, ao + row + 0*lda, lda, tau[tauo+idx], a, ao, lda, work, wo); a[ao + row + pcol*lda] = aii; } } } //+-----------------------------------------------------------------------------------------------+ //|Apply Q (from dgerq2) to C, side in {L,R}, trans in {N,T}, using k of the reflectors. | //| LAPACK convention: the reflector-storage matrix 'a' is k-by-M when side=L, k-by-N when side=R | //| (M,N = dimensions of C) -- i.e. the "true" column count of 'a' to use when locating each | //| reflector's pivot column depends on which side we're applying from. | //+-----------------------------------------------------------------------------------------------+ void SLSQP_dormr2(const bool side_left, const bool trans, const int m, const int n, const int k, double &a[], const int ao, const int lda, double &tau[], const int tauo, double &c[], const int co, const int ldc, double &work[], const int wo) { int mrq = k; // dgerq2 is always called with its own "m" parameter == k in this codebase bool leftMultQT = side_left && trans; bool rightMultQ = (!side_left) && (!trans); bool useDescending = (leftMultQT || rightMultQ); int ncols_of_a = side_left ? m : n; // LAPACK: A is k-by-M (side=L) or k-by-N (side=R) for(int pass = 0; pass < k; pass++) { int idx = useDescending ? (k-1-pass) : pass; int i = k - 1 - idx; int row = mrq - k + i; int pcol = ncols_of_a - k + i; int len = pcol + 1; double aii = a[ao + row + pcol*lda]; a[ao + row + pcol*lda] = 1.0; if(side_left) { int mm = len, nn = n; SLSQP_dlarf(true, mm, nn, a, ao + row + 0*lda, lda, tau[tauo+idx], c, co, ldc, work, wo); } else { int mm = m, nn = len; SLSQP_dlarf(false, mm, nn, a, ao + row + 0*lda, lda, tau[tauo+idx], c, co, ldc, work, wo); } a[ao + row + pcol*lda] = aii; } } //+------------------------------------------------------------------+ //| dgelsy: rank-revealing least squares via column-pivoted | //| Householder QR (Businger-Golub pivoting). Solves min|Ax-b| for x | //| (n-vector), A is m x n (m>=n expected). Basic (not minimum-norm) | //| solution when rank-deficient -- SLSQP only reaches this routine | //| when there are no inequality/bound constraints at all, and only | //| the rank itself (not the min-norm refinement) affects the mode | //| SLSQP reports in that corner case. | //+------------------------------------------------------------------+ void SLSQP_dgelsy(const int m, const int n, double &a[], const int ao, const int lda, double &b[], const int bo, const double rcond, int &rank) { int pcols[]; ArrayResize(pcols, n); double colnorm[]; ArrayResize(colnorm, n); int k = MathMin(m,n); double tau[]; ArrayResize(tau, MathMax(k,1)); for(int j = 0; j < n; j++) { pcols[j] = j; double s = 0.0; for(int i = 0; i < m; i++) { double v = a[ao+i+j*lda]; s += v*v; } colnorm[j] = s; } double firstdiag = 0.0; for(int kk = 0; kk < k; kk++) { int piv = kk; double best = colnorm[kk]; for(int j = kk+1; j < n; j++) if(colnorm[j] > best) { best = colnorm[j]; piv = j; } if(piv != kk) { for(int i = 0; i < m; i++) { double t = a[ao+i+kk*lda]; a[ao+i+kk*lda] = a[ao+i+piv*lda]; a[ao+i+piv*lda] = t; } double t2 = colnorm[kk]; colnorm[kk] = colnorm[piv]; colnorm[piv] = t2; int ti = pcols[kk]; pcols[kk] = pcols[piv]; pcols[piv] = ti; } int len = m - kk; double work_scratch[]; ArrayResize(work_scratch, MathMax(n,1)); SLSQP_dlarfg_std(len, a, ao+kk+kk*lda, a, ao+(kk+1)+kk*lda, 1, tau, kk); if(kk < n-1) { double aii = a[ao+kk+kk*lda]; a[ao+kk+kk*lda] = 1.0; int mm = m-kk, nn = n-kk-1; SLSQP_dlarf(true, mm, nn, a, ao+kk+kk*lda, 1, tau[kk], a, ao+kk+(kk+1)*lda, lda, work_scratch, 0); a[ao+kk+kk*lda] = aii; } for(int j = kk+1; j < n; j++) { double v = a[ao+kk+j*lda]; colnorm[j] -= v*v; if(colnorm[j] < 0) colnorm[j] = 0; } if(kk == 0) firstdiag = MathAbs(a[ao]); } double thresh = rcond * (firstdiag > 0 ? firstdiag : 1.0); int krank = 0; for(int kk = 0; kk < k; kk++) { if(MathAbs(a[ao+kk+kk*lda]) > thresh) krank++; else break; } rank = krank; // Apply Q^T to b double w1[1]; for(int kk = 0; kk < k; kk++) { double aii = a[ao+kk+kk*lda]; a[ao+kk+kk*lda] = 1.0; int mm = m-kk; SLSQP_dlarf(true, mm, 1, a, ao+kk+kk*lda, 1, tau[kk], b, bo+kk, m, w1, 0); a[ao+kk+kk*lda] = aii; } // Solve R11 (krank x krank) z = b[0:krank]; remaining unknowns = 0 (basic solution) double z[]; ArrayResize(z, n); for(int j = 0; j < n; j++) z[j] = 0.0; for(int j = krank-1; j >= 0; j--) { double s = b[bo+j]; for(int i = j+1; i < krank; i++) s -= a[ao+j+i*lda]*z[i]; z[j] = s / a[ao+j+j*lda]; } for(int j = 0; j < n; j++) b[bo + pcols[j]] = z[j]; } //+------------------------------------------------------------------+ //| NNLS: Lawson-Hanson non-negative least squares (min |Ax-b|, | //| x>=0), via Householder QR + active-set updates. a is m x n, | //| overwritten; b is length m, overwritten; x is length n (output); | //| w, zz are length-n / length-m scratch. All double arrays use the | //| (array[], offset) convention since callers slice them out of a | //| shared scratch buffer; indices is always a dedicated int array | //| (never sliced from a shared buffer anywhere in this codebase). | //| info: 1=success, 2=bad dims, 3=hit maxiter. | //+------------------------------------------------------------------+ void SLSQP_nnls(const int m, const int n, double &a[], const int ao, double &b[], const int bo, double &x[], const int xo, double &w[], const int wo, double &zz[], const int zzo, int &indices[], const int maxiter, double &rnorm, int &info) { int i=0, ii=0, ip=0, indz=0, iteration=0, iz=0, izmax=0; int j=0, jj=0, k=0; double tau=0.0, unorm=0.0, ztest=0.0, alpha=0.0, cc=0.0, ss=0.0, wmax=0.0, T=0.0; double pivot=1.0, pivot2=0.0, tmp=0.0, spacing=0.0; info = 1; if(m <= 0 || n <= 0) { info = 2; return; } for(i = 0; i < n; i++) indices[i] = i; for(i = 0; i < n; i++) x[xo+i] = 0.0; bool terminate = false; while(indz < MathMin(m,n) && !terminate) { for(i = indz; i < n; i++) { j = indices[i]; int tmpint = m - indz; w[wo+j] = SLSQP_ddot(tmpint, a, ao + indz + j*m, 1, b, bo + indz, 1); } bool found_pivot = false; while(!found_pivot && !terminate) { wmax = 0.0; for(k = indz; k < n; k++) { j = indices[k]; if(w[wo+j] > wmax) { wmax = w[wo+j]; izmax = k; } } if(wmax <= 0.0) { terminate = true; break; } iz = izmax; j = indices[iz]; pivot = a[ao + indz + j*m]; int tmpint = m - indz; double tauArr[1]; tauArr[0] = tau; double pivArr[1]; pivArr[0] = pivot; SLSQP_dlarfgp(tmpint, pivArr, 0, a, ao + indz + 1 + j*m, 1, tauArr, 0); pivot = pivArr[0]; tau = tauArr[0]; unorm = (indz > 0 ? SLSQP_dnrm2(indz, a, ao + j*m, 1) : 0.0); spacing = (unorm > 0.0 ? unorm*SLSQP_EPS : 0.0); // approximates nextafter(unorm,2*unorm)-unorm if(MathAbs(pivot) > 100.0*spacing) { for(i = 0; i < m; i++) zz[zzo+i] = b[bo+i]; tmpint = m - indz; pivot2 = a[ao + indz + j*m]; a[ao + indz + j*m] = 1.0; double workv[1]; SLSQP_dlarf(true, tmpint, 1, a, ao + indz + j*m, 1, tau, zz, zzo + indz, tmpint, workv, 0); ztest = zz[zzo+indz] / pivot; if(ztest > 0.0) { found_pivot = true; break; } else { a[ao + indz + j*m] = pivot2; } } w[wo+j] = 0.0; } if(terminate) break; for(i = 0; i < m; i++) b[bo+i] = zz[zzo+i]; indices[iz] = indices[indz]; indices[indz] = j; indz++; if(indz < n) { int tmpint = m - indz + 1; for(k = indz; k < n; k++) { jj = indices[k]; double workv[1]; SLSQP_dlarf(true, tmpint, 1, a, ao + indz - 1 + j*m, 1, tau, a, ao + indz - 1 + jj*m, tmpint, workv, 0); } } a[ao + indz - 1 + j*m] = pivot; if(indz < m) { for(i = indz; i < m; i++) a[ao + j*m + i] = 0.0; } w[wo+j] = 0.0; for(k = 0; k < indz; k++) { ip = indz - 1 - k; if(k != 0) { for(i = 0; i <= ip; i++) zz[zzo+i] = zz[zzo+i] - a[ao + i + jj*m]*zz[zzo+ip+1]; } jj = indices[ip]; zz[zzo+ip] = zz[zzo+ip] / a[ao + ip + jj*m]; } while(true) { iteration++; if(iteration >= maxiter) { info = 3; terminate = true; break; } alpha = 2.0; for(ip = 0; ip < indz; ip++) { k = indices[ip]; if(zz[zzo+ip] <= 0.0) { T = -x[xo+k] / (zz[zzo+ip] - x[xo+k]); if(alpha > T) { alpha = T; jj = ip; } } } if(alpha == 2.0) break; for(ip = 0; ip < indz; ip++) { k = indices[ip]; x[xo+k] = x[xo+k] + alpha*(zz[zzo+ip]-x[xo+k]); } i = indices[jj]; while(true) { x[xo+i] = 0.0; if(jj != indz-1) { jj++; for(j = jj; j < indz; j++) { ii = indices[j]; indices[j-1] = ii; double csv, ssv, rv; SLSQP_dlartgp(a[ao + j-1+ii*m], a[ao + j+ii*m], csv, ssv, rv); cc = csv; ss = ssv; a[ao + j-1+ii*m] = rv; a[ao + j+ii*m] = 0.0; for(k = 0; k < n; k++) { if(k != ii) { tmp = a[ao + j-1+k*m]; a[ao + j-1+k*m] = cc*tmp + ss*a[ao + j+k*m]; a[ao + j+k*m] = -ss*tmp + cc*a[ao + j+k*m]; } } tmp = b[bo+j-1]; b[bo+j-1] = cc*tmp + ss*b[bo+j]; b[bo+j] = -ss*tmp + cc*b[bo+j]; } } indz--; indices[indz] = i; bool nobreak = false; for(jj = 0; jj < indz; jj++) { i = indices[jj]; if(x[xo+i] <= 0.0) { break; } if(jj == indz-1) nobreak = true; } if(nobreak) break; } for(i = 0; i < m; i++) zz[zzo+i] = b[bo+i]; for(k = 0; k < indz; k++) { ip = indz - 1 - k; if(k != 0) { for(i = 0; i <= ip; i++) zz[zzo+i] = zz[zzo+i] - a[ao + i+jj*m]*zz[zzo+ip+1]; } jj = indices[ip]; zz[zzo+ip] = zz[zzo+ip] / a[ao + ip+jj*m]; } } if(terminate) break; for(k = 0; k < indz; k++) { i = indices[k]; x[xo+i] = zz[zzo+k]; } } if(indz < m) { int tmpint = m - indz; rnorm = SLSQP_dnrm2(tmpint, b, bo + indz, 1); } else { for(i = 0; i < n; i++) w[wo+i] = 0.0; rnorm = 0.0; } } //+------------------------------------------------------------------+ //| ldl_update: rank-1 update of a packed lower-triangular LDL' | //| factorization (BFGS Hessian approximation storage): a := LDL' | //| update of a +/- sigma*z*z^T. w is length-n scratch (only used | //| for sigma<0). a holds L off-diagonal, D on the diagonal. | //+------------------------------------------------------------------+ void SLSQP_ldl_update(const int n, double &a[], const int ao, double &z[], const int zo, const double sigma, double &w[], const int wo) { int j, ij = 0; if(sigma == 0.0) return; double alpha, beta, delta, gamma, u, v, tp, t = 1.0/sigma; if(sigma <= 0.0) { for(int i = 0; i < n; i++) w[wo+i] = z[zo+i]; for(int i = 0; i < n; i++) { v = w[wo+i]; t = t + v*v/a[ao+ij]; for(j = i+1; j < n; j++) { ij++; w[wo+j] = w[wo+j] - v*a[ao+ij]; } ij++; } if(t >= 0.0) t = SLSQP_EPS/sigma; for(int i = 0; i < n; i++) { j = n - i - 1; ij -= i + 1; u = w[wo+j]; w[wo+j] = t; t = t - u*u/a[ao+ij]; } } for(int i = 0; i < n; i++) { v = z[zo+i]; delta = v / a[ao+ij]; tp = (sigma < 0.0 ? w[wo+i] : t + delta*v); alpha = tp / t; a[ao+ij] = alpha*a[ao+ij]; if(i == n-1) return; beta = delta / tp; if(alpha <= 4.0) { for(j = i+1; j < n; j++) { ij++; z[zo+j] = z[zo+j] - v*a[ao+ij]; a[ao+ij] = a[ao+ij] + beta*z[zo+j]; } } else { gamma = t / tp; for(j = i+1; j < n; j++) { ij++; u = a[ao+ij]; a[ao+ij] = gamma*u + beta*z[zo+j]; z[zo+j] = z[zo+j] - v*u; } } ij++; t = tp; } } //+------------------------------------------------------------------+ //| ldp: least-distance programming. min |x| s.t. G*x >= h | //| (via the NNLS dual, Lawson & Hanson). | //| g is m x n, h is length m, x is length n (output). | //| buffer must have room for (m+2)*(n+1)+m+n doubles; indices needs | //| length >= n+1. mode: 1=solved, 2=bad dims, 3=nnls iter exceeded, | //| 4=inequalities incompatible/infeasible. | //+------------------------------------------------------------------+ void SLSQP_ldp(const int m, const int n, double &g[], const int go, double &h[], const int ho, double &x[], const int xo, double &buffer[], const int bo, int &indices[], double &xnorm, int &mode) { if(n <= 0) { mode = 2; return; } for(int i = 0; i < n; i++) x[xo+i] = 0.0; if(m == 0) { mode = 1; return; } int a_o = bo; int b_o = bo + m*(n+1); int zz_o = bo + (m+1)*(n+1); int y_o = bo + (m+2)*(n+1); int w_o = bo + (m+2)*(n+1) + m; for(int j = 0; j < m; j++) { for(int i = 0; i < n; i++) buffer[a_o + i + j*(n+1)] = g[go + j + i*m]; buffer[a_o + n + j*(n+1)] = h[ho+j]; } for(int i = 0; i < n; i++) buffer[b_o+i] = 0.0; buffer[b_o+n] = 1.0; double rnorm = 0.0; SLSQP_nnls(n+1, m, buffer, a_o, buffer, b_o, buffer, y_o, buffer, w_o, buffer, zz_o, indices, 3*m, rnorm, mode); if(mode != 1) return; mode = 4; if(rnorm <= 0.0) return; double fac = 1.0 - SLSQP_ddot(m, h, ho, 1, buffer, y_o, 1); if(!((1.0+fac) - 1.0 > 0.0)) return; mode = 1; fac = 1.0/fac; SLSQP_dgemv(true, m, n, fac, g, go, m, buffer, y_o, 1, 0.0, x, xo, 1); xnorm = SLSQP_dnrm2(n, x, xo, 1); for(int i = 0; i < m; i++) buffer[bo+i] = fac*buffer[y_o+i]; } //+-------------------------------------------------------------------+ //| lsi: least-squares with inequality constraints. | //| min |A*x - b| s.t. G*x >= h | //| A is ma x n (overwritten with its QR factors), b is length ma | //| (overwritten), g is mg x n (overwritten), h is length mg | //| (overwritten), x is length n (output). | //| buffer needs room for the QR scratch (>= n) plus ldp's requirement| //| ((mg+2)*(n+1)+2*mg); jw (indices) needs length >= n+1. | //| mode: 1 ok, 2 bad dims, 3 nnls iter exceeded, 4 incompatible, | //| 5 rank-deficient A. | //+-------------------------------------------------------------------+ void SLSQP_lsi(const int ma, const int mg, const int n, double &a[], const int ao, double &b[], const int bo, double &g[], const int go, double &h[], const int ho, double &x[], const int xo, double &buffer[], const int bufo, int &jw[], double &xnorm, int &mode) { int tmp_int = MathMin(ma,n); double work_dummy[1]; SLSQP_dgeqr2(ma, n, a, ao, ma, buffer, bufo, buffer, bufo + tmp_int); SLSQP_dorm2r(true, ma, 1, tmp_int, a, ao, ma, buffer, bufo, b, bo, ma, buffer, bufo + tmp_int); mode = 5; xnorm = 0.0; for(int i = 0; i < tmp_int; i++) if(!(MathAbs(a[ao + i + i*ma]) >= SLSQP_EPS)) return; SLSQP_dtrsm_RUNN(mg, n, 1.0, a, ao, ma, g, go, mg); SLSQP_dgemv(false, mg, n, -1.0, g, go, mg, b, bo, 1, 1.0, h, ho, 1); SLSQP_ldp(mg, n, g, go, h, ho, x, xo, buffer, bufo, jw, xnorm, mode); if(mode != 1) return; SLSQP_daxpy(n, 1.0, b, bo, 1, x, xo, 1); SLSQP_dtrsv(false, n, a, ao, ma, x, xo, 1); tmp_int = ma - n; int btail = (n+1 > ma ? ma : n+1) - 1; double tmp_dbl = SLSQP_dnrm2(tmp_int, b, bo + btail, 1); xnorm = SLSQP_Hypot(xnorm, tmp_dbl); } //+------------------------------------------------------------------+ //| lsei: least-squares with equality AND inequality constraints. | //| min |A*x - b| s.t. E*x = f, G*x >= h | //| a is ma x n, b is length ma, e is me x n, f is length me, | //| g is mg x n, h is length mg (all overwritten); x is length n. | //| buffer needs (mg+2)*(n-me+1) + 3*mg + 2*me + ma + (ma+mg)*(n-me);| //| jw (indices) needs length >= n - me + 1. | //| mode: 1 ok, 2 over-constrained, 4 incompatible, 5 rank-deficient | //| A (in the mg==0 sub-case), 6 rank-deficient E, 7 rank-deficient A| //| (mg==0 case, unsolvable). | //+------------------------------------------------------------------+ void SLSQP_lsei(const int ma, const int me, const int mg, const int n, double &a[], const int ao, double &b[], const int bo, double &e[], const int eo, double &f[], const int fo, double &g[], const int go, double &h[], const int ho, double &x[], const int xo, double &buffer[], const int bufo, int &jw[], double &xnorm, int &mode) { for(int i = 0; i < n; i++) x[xo+i] = 0.0; if(me > n) { mode = 2; return; } int nvars = n - me; int gmults_o = bufo; int emults_o = bufo + mg; int wb_o = bufo + me + mg; int tau_o = bufo + me + mg + ma; int a2_o = bufo + mg + 2*me + ma; int g2_o = bufo + mg + 2*me + ma + ma*nvars; int lsis_o = bufo + mg + 2*me + ma + (ma+mg)*nvars; int lde = (me > 0 ? me : 1); int ldg = (mg > 0 ? mg : 1); SLSQP_dgerq2(me, n, e, eo, lde, buffer, tau_o, buffer, lsis_o); SLSQP_dormr2(false, true, ma, n, me, e, eo, lde, buffer, tau_o, a, ao, ma, buffer, lsis_o); SLSQP_dormr2(false, true, mg, n, me, e, eo, lde, buffer, tau_o, g, go, ldg, buffer, lsis_o); for(int i = 0; i < me; i++) if(!(MathAbs(e[eo + i + (nvars+i)*me]) >= SLSQP_EPS)) { mode = 6; return; } for(int i = 0; i < me; i++) x[xo + nvars+i] = f[fo+i]; SLSQP_dtrsv(false, me, e, eo + nvars*me, lde, x, xo + nvars, 1); mode = 1; for(int i = 0; i < mg; i++) buffer[gmults_o+i] = 0.0; bool skip_rest = (me == n); if(!skip_rest) { for(int i = 0; i < ma; i++) buffer[wb_o+i] = b[bo+i]; SLSQP_dgemv(false, ma, me, -1.0, a, ao + ma*nvars, ma, x, xo + nvars, 1, 1.0, buffer, wb_o, 1); for(int j = 0; j < nvars; j++) { for(int i = 0; i < ma; i++) buffer[a2_o + i + j*ma] = a[ao + i + j*ma]; for(int i = 0; i < mg; i++) buffer[g2_o + i + j*mg] = g[go + i + j*mg]; } if(mg == 0) { int lwork = ma*nvars + 3*nvars + 1; int wborig_o = lsis_o + lwork; for(int i = 0; i < ma; i++) buffer[wborig_o+i] = buffer[wb_o+i]; int krank = 0; double t = MathSqrt(SLSQP_EPS); SLSQP_dgelsy(ma, nvars, buffer, a2_o, ma, buffer, wb_o, t, krank); for(int i = 0; i < nvars; i++) x[xo+i] = buffer[wb_o+i]; SLSQP_dgemv(false, ma, nvars, 1.0, a, ao, ma, x, xo, 1, -1.0, buffer, wborig_o, 1); xnorm = SLSQP_dnrm2(ma, buffer, wborig_o, 1); mode = 7; if(krank < nvars) return; mode = 1; skip_rest = true; } if(!skip_rest) { SLSQP_dgemv(false, mg, me, -1.0, g, go + mg*nvars, ldg, x, xo + nvars, 1, 1.0, h, ho, 1); SLSQP_lsi(ma, mg, nvars, buffer, a2_o, buffer, wb_o, buffer, g2_o, h, ho, x, xo, buffer, lsis_o, jw, xnorm, mode); for(int i = 0; i < mg; i++) buffer[gmults_o+i] = buffer[lsis_o+i]; if(me == 0) return; double t2 = SLSQP_dnrm2(me, x, xo + nvars, 1); xnorm = SLSQP_Hypot(xnorm, t2); if(mode != 1) return; } } // ORIGINAL_BASIS: convert the solution and multipliers back to the original basis. SLSQP_dgemv(false, ma, n, 1.0, a, ao, ma, x, xo, 1, -1.0, b, bo, 1); SLSQP_dgemv(true, ma, me, 1.0, a, ao + nvars*ma, ma, b, bo, 1, 0.0, f, fo, 1); SLSQP_dgemv(true, mg, me, -1.0, g, go + nvars*mg, ldg, buffer, gmults_o, 1, 1.0, f, fo, 1); SLSQP_dormr2(true, true, n, 1, me, e, eo, lde, buffer, tau_o, x, xo, n, buffer, lsis_o); for(int i = 0; i < me; i++) buffer[emults_o+i] = f[fo+i]; SLSQP_dtrsv(true, me, e, eo + (n-me)*me, lde, buffer, emults_o, 1); } //+------------------------------------------------------------------+ //| lsq: forms and solves the SQP sub-problem's quadratic-program | //| direction-finding step as an equivalent LSEI (least squares with | //| equality/inequality constraints) problem, using the packed LDL' | //| BFGS factor Lf as the QP's Hessian square root. | //| | //| Lf : packed LDL' factor of the BFGS Hessian approx (n*(n+1)/2)| //| gradx : gradient of the objective at the current point (n) | //| C : constraint Jacobian, m x n (meq equality rows first) | //| d : constraint values (m) | //| xl,xu : bounds (n; extended to n+1 in-place if augment) | //| x : output search direction (n, or n+1 if augment) | //| y : output Lagrange multipliers (m + 2*n) | //| jw : integer scratch (length >= n - meq + 2) | //| augment: if true, solves the "inconsistent linearization" | //| relaxation with one extra slack variable weighted by | //| aug_weight, per Kraft's SQP paper section 2.2.3. | //| buffer must satisfy the (generous) size formula documented in the | //| SLSQPBody buffer-sizing helper further down this file. | //+------------------------------------------------------------------+ void SLSQP_lsq(int m, const int meq, int n, const bool augment, const double aug_weight, double &Lf[], const int Lfo, double &gradx[], const int gradxo, double &C[], const int Co, double &d[], const int d_o, double &xl[], const int xlo, double &xu[], const int xuo, double &x[], const int xo, double &y[], const int yo, double &buffer[], const int bufo, int &jw[], int &mode) { int orign = n; int mineq = m - meq; double xnorm = 0.0; int cursor = 0; int ld = n; if(augment) { ld = n + 1; x[xo+n] = 1.0; xl[xlo+n] = 0.0; xu[xuo+n] = 1.0; } for(int i = 0; i < (ld+2)*ld; i++) buffer[bufo+i] = 0.0; int wA_o = bufo; int wb_o = bufo + ld*(ld+1); for(int j = 0; j < n; j++) { double diag = MathSqrt(Lf[Lfo+cursor]); cursor++; buffer[wA_o + j + j*ld] = diag; for(int i = j+1; i < n; i++) { buffer[wA_o + j + i*ld] = Lf[Lfo+cursor]*diag; cursor++; } } for(int i = 0; i < n; i++) buffer[wb_o+i] = gradx[gradxo+i]; SLSQP_dtpsv(n, Lf, Lfo, buffer, wb_o, 1); cursor = 0; for(int i = 0; i < n; i++) { buffer[wb_o+i] /= -MathSqrt(Lf[Lfo+cursor]); cursor += n - i; } if(augment) buffer[wA_o + ld*ld - 1] = aug_weight; if(augment) n++; int wE_o = bufo + n*(n+1) + n; int wf_o = bufo + n*(n+1) + n + n*meq; if(meq > 0) { for(int j = 0; j < n-1; j++) for(int i = 0; i < meq; i++) buffer[wE_o + i + j*meq] = C[Co + i + j*m]; if(augment) { for(int i = 0; i < meq; i++) buffer[wE_o + i + (n-1)*meq] = -d[d_o+i]; } else { for(int i = 0; i < meq; i++) buffer[wE_o + i + (n-1)*meq] = C[Co + i + (n-1)*m]; } for(int i = 0; i < meq; i++) buffer[wf_o+i] = -d[d_o+i]; } int wG_o = bufo + n*(n+1) + n + n*meq + meq; int wh_o = bufo + n*(n+1) + n + n*meq + meq + (mineq + 2*n)*ld; for(int i = 0; i < (mineq + 2*n)*(ld+1); i++) buffer[wG_o+i] = 0.0; int nancount = 0; int nrow = mineq; if(m > meq) for(int i = 0; i < mineq; i++) buffer[wh_o+i] = -d[d_o + meq + i]; for(int i = 0; i < n; i++) { if(SLSQP_IsNaN(xl[xlo+i])) nancount++; else { buffer[wh_o+nrow] = xl[xlo+i]; nrow++; } } for(int i = 0; i < n; i++) { if(SLSQP_IsNaN(xu[xuo+i])) nancount++; else { buffer[wh_o+nrow] = -xu[xuo+i]; nrow++; } } int n_wG_rows = mineq + 2*n - nancount; if(m > meq) { for(int j = 0; j < orign; j++) for(int i = 0; i < mineq; i++) buffer[wG_o + i + j*n_wG_rows] = C[Co + meq + i + j*m]; } if(augment) { for(int i = 0; i < mineq; i++) buffer[wG_o + i + orign*n_wG_rows] = MathMax(-d[d_o + meq + i], 0.0); } nrow = mineq; for(int i = 0; i < n; i++) { if(!SLSQP_IsNaN(xl[xlo+i])) { buffer[wG_o + nrow + i*n_wG_rows] = 1.0; nrow++; } } for(int i = 0; i < n; i++) { if(!SLSQP_IsNaN(xu[xuo+i])) { buffer[wG_o + nrow + i*n_wG_rows] = -1.0; nrow++; } } int lsei_scratch_o = wh_o + mineq + 2*n; SLSQP_lsei(ld, meq, n_wG_rows, n, buffer, wA_o, buffer, wb_o, buffer, wE_o, buffer, wf_o, buffer, wG_o, buffer, wh_o, x, xo, buffer, lsei_scratch_o, jw, xnorm, mode); if(mode == 1) { for(int i = 0; i < meq; i++) y[yo+i] = buffer[lsei_scratch_o + i + n_wG_rows]; for(int i = 0; i < mineq; i++) y[yo + meq + i] = buffer[lsei_scratch_o + i]; double nanv = SLSQP_NaN(); for(int i = 0; i < 2*n; i++) y[yo + m + i] = nanv; } for(int i = 0; i < n; i++) { if((!SLSQP_IsNaN(xl[xlo+i])) && (x[xo+i] < xl[xlo+i])) x[xo+i] = xl[xlo+i]; else if((!SLSQP_IsNaN(xu[xuo+i])) && (x[xo+i] > xu[xuo+i])) x[xo+i] = xu[xuo+i]; } } //+------------------------------------------------------------------+ //| SLSQP algorithm state, persisted by the caller across calls to | //| SLSQPBody (this is the reverse-communication "instruction | //| pointer" -- see CSLSQPSolver below for the ready-to-use driver). | //+------------------------------------------------------------------+ struct SSLSQPVars { double acc, alpha, f0, gs, h1, h2, h3, h4, t, t0, tol; int exact, inconsistent, reset, iter, itermax, line, m, meq, mode, n; }; //+------------------------------------------------------------------+ //|Enumeration of states marking sequence of operations for optimizer| //+------------------------------------------------------------------+ enum ESLSQPState { ST_MODE0, ST_RESET_BFGS, ST_ITER_START, ST_LINE_SEARCH, ST_MODE1, ST_LABEL255, ST_MODEM1 }; //+------------------------------------------------------------------+ //| SLSQPBody: the core reverse-communication SLSQP step function, | //| a direct (goto-free) port of SciPy's __slsqp_body. On return, | //| S.mode tells the caller what to do next: | //| S.mode == 1 : evaluate funx and d (constraint values) ONLY | //| at 'sol', then call again with S.mode still 1 | //| S.mode == -1 : evaluate funx, gradx, C (constraint Jacobian) | //| and d at 'sol', then call again with mode -1 | //| S.mode == 0 : converged; 'sol' holds the solution | //| otherwise : terminated abnormally (see docs for mode codes) | //| Before the very first call, set S.mode=0 and evaluate funx, | //| gradx, C, d at the starting point (this is exactly what | //| CSLSQPSolver::Minimize does for you). | //+------------------------------------------------------------------+ void SLSQPBody(SSLSQPVars &S, double &funx, double &gradx[], double &C[], double &d[], double &sol[], double &mult[], double &xl[], double &xu[], double &buffer[], int &indices[]) { int lda = (S.m > 0 ? S.m : 1); int j; double alfmin = 0.1; int n = S.n; int m = S.m; int n1 = n + 1; int n2 = n1*n/2; int bfgs_o = 0; int x0_o = n2; int mu_o = n2 + n; int s_o = n2 + n + m; int u_o = n2 + n + m + n1; int v_o = n2 + n + m + n1 + n1; int lsqbuf_o = n2 + n + m + n1 + n1 + n1; bool badlin = false; ESLSQPState state; if(S.mode == 0) state = ST_MODE0; else if(S.mode == -1) state = ST_MODEM1; else if(S.mode == 1) state = ST_MODE1; else return; while(true) { switch(state) { case ST_MODE0: { S.exact = 0; S.acc = MathAbs(S.acc); S.tol = 10*S.acc; S.iter = 0; S.reset = 0; for(int i = 0; i < n; i++) buffer[s_o+i] = 0.0; for(int i = 0; i < m; i++) buffer[mu_o+i] = 0.0; state = ST_RESET_BFGS; continue; } case ST_RESET_BFGS: { S.reset++; if(S.reset > 5) { state = ST_LABEL255; continue; } for(int i = 0; i < n2; i++) buffer[bfgs_o+i] = 0.0; j = 0; for(int i = 0; i < n; i++) { buffer[bfgs_o+j] = 1.0; j += n - i; } state = ST_ITER_START; continue; } case ST_ITER_START: { S.mode = 9; if(S.iter >= S.itermax) return; S.iter++; for(int i = 0; i < n; i++) { buffer[u_o+i] = -sol[i] + xl[i]; buffer[v_o+i] = -sol[i] + xu[i]; } S.h4 = 1.0; SLSQP_lsq(m, S.meq, n, false, 0.0, buffer, bfgs_o, gradx, 0, C, 0, d, 0, buffer, u_o, buffer, v_o, buffer, s_o, mult, 0, buffer, lsqbuf_o, indices, S.mode); badlin = false; if((S.mode == 6) && (n == S.meq)) S.mode = 4; if(S.mode == 4) { badlin = true; for(int i = 0; i < n; i++) buffer[s_o+i] = 0.0; S.h3 = 0.0; double rho = 100.0; S.inconsistent = 0; while(true) { SLSQP_lsq(m, S.meq, n, true, rho, buffer, bfgs_o, gradx, 0, C, 0, d, 0, buffer, u_o, buffer, v_o, buffer, s_o, mult, 0, buffer, lsqbuf_o, indices, S.mode); S.h4 = 1.0 - buffer[s_o+n]; if(S.mode == 4) { rho *= 10.0; S.inconsistent++; if(S.inconsistent > 5) return; continue; } else if(S.mode != 1) { return; } break; } } else if(S.mode != 1) { return; } for(int i = 0; i < n; i++) buffer[v_o+i] = gradx[i]; SLSQP_dgemv(true, m, n, -1.0, C, 0, lda, mult, 0, 1, 1.0, buffer, v_o, 1); S.f0 = funx; for(int i = 0; i < n; i++) buffer[x0_o+i] = sol[i]; S.gs = SLSQP_ddot(n, gradx, 0, 1, buffer, s_o, 1); S.h1 = MathAbs(S.gs); S.h2 = 0.0; for(int jj = 0; jj < m; jj++) { if(jj < S.meq) S.h3 = d[jj]; else S.h3 = 0.0; S.h2 = S.h2 + MathMax(-d[jj], S.h3); S.h3 = MathAbs(mult[jj]); buffer[mu_o+jj] = MathMax(S.h3, (buffer[mu_o+jj] + S.h3)/2.0); S.h1 = S.h1 + S.h3*MathAbs(d[jj]); } S.mode = 0; if((S.h1 < S.acc) && (S.h2 < S.acc) && (!badlin) && (!SLSQP_IsNaN(funx))) return; S.h1 = 0.0; for(int jj = 0; jj < m; jj++) { if(jj < S.meq) S.h3 = d[jj]; else S.h3 = 0.0; S.h1 += buffer[mu_o+jj]*MathMax(-d[jj], S.h3); } S.t0 = funx + S.h1; S.h3 = S.gs - S.h1*S.h4; S.mode = 8; if(S.h3 >= 0.0) { state = ST_RESET_BFGS; continue; } S.line = 0; S.alpha = 1.0; state = ST_LINE_SEARCH; continue; } case ST_LINE_SEARCH: { S.line++; S.h3 = S.alpha * S.h3; SLSQP_dscal(n, S.alpha, buffer, s_o, 1); for(int i = 0; i < n; i++) sol[i] = buffer[x0_o+i]; SLSQP_daxpy(n, 1.0, buffer, s_o, 1, sol, 0, 1); S.mode = 1; return; } case ST_MODE1: { S.t = funx; for(int jj = 0; jj < m; jj++) { if(jj < S.meq) S.h1 = d[jj]; else S.h1 = 0.0; S.t = S.t + buffer[mu_o+jj]*MathMax(-d[jj], S.h1); } S.h1 = S.t - S.t0; if(!((S.h1 <= (S.h3/10.0)) || (S.line > 10))) { S.alpha = MathMax(S.h3/(2.0*(S.h3-S.h1)), alfmin); state = ST_LINE_SEARCH; continue; } S.h3 = 0.0; for(int jj = 0; jj < m; jj++) { if(jj < S.meq) S.h1 = d[jj]; else S.h1 = 0.0; S.h3 = S.h3 + MathMax(-d[jj], S.h1); } if( ((MathAbs(funx - S.f0) < S.acc) || (SLSQP_dnrm2(n, buffer, s_o, 1) < S.acc)) && (S.h3 < S.acc) && (!badlin) && (!SLSQP_IsNaN(funx)) ) { S.mode = 0; return; } else { S.mode = -1; } return; } case ST_LABEL255: { S.h3 = 0.0; for(int jj = 0; jj < m; jj++) { if(jj < S.meq) S.h1 = d[jj]; else S.h1 = 0.0; S.h3 = S.h3 + MathMax(-d[jj], S.h1); } if(((MathAbs(funx - S.f0) < S.tol) || (SLSQP_dnrm2(n, buffer, s_o, 1) < S.tol)) && (S.h3 < S.tol) && (!badlin) && (!SLSQP_IsNaN(funx)) ) { S.mode = 0; } else { S.mode = 8; } return; } case ST_MODEM1: { for(int i = 0; i < n; i++) buffer[u_o+i] = gradx[i]; SLSQP_dgemv(true, m, n, -1.0, C, 0, lda, mult, 0, 1, 1.0, buffer, u_o, 1); for(int i = 0; i < n; i++) buffer[u_o+i] = buffer[u_o+i] - buffer[v_o+i]; for(int i = 0; i < n; i++) buffer[v_o+i] = buffer[s_o+i]; SLSQP_dtpmv(true, n, buffer, bfgs_o, buffer, v_o, 1); j = 0; for(int i = 0; i < n; i++) { buffer[v_o+i] = buffer[bfgs_o+j]*buffer[v_o+i]; j += n - i; } SLSQP_dtpmv(false, n, buffer, bfgs_o, buffer, v_o, 1); S.h1 = SLSQP_ddot(n, buffer, s_o, 1, buffer, u_o, 1); S.h2 = SLSQP_ddot(n, buffer, s_o, 1, buffer, v_o, 1); S.h3 = 0.2*S.h2; if(S.h1 < S.h3) { S.h4 = (S.h2 - S.h3) / (S.h2 - S.h1); S.h1 = S.h3; double tmp_dbl = 1.0 - S.h4; SLSQP_dscal(n, S.h4, buffer, u_o, 1); SLSQP_daxpy(n, tmp_dbl, buffer, v_o, 1, buffer, u_o, 1); } if((S.h1 == 0.0) || (S.h2 == 0.0)) { state = ST_RESET_BFGS; continue; } SLSQP_ldl_update(n, buffer, bfgs_o, buffer, u_o, 1.0/S.h1, buffer, v_o); SLSQP_ldl_update(n, buffer, bfgs_o, buffer, v_o, -1.0/S.h2, buffer, u_o); state = ST_ITER_START; continue; } } } } //+------------------------------------------------------------------+ //| Scratch-buffer sizing, matching the (additive, deliberately | //| generous) formula documented in SciPy's slsqp.c, using n+1 in | //| place of n throughout so it also covers lsq's internal | //| "inconsistent linearization" augmented sub-problem. | //+------------------------------------------------------------------+ int SLSQP_BufferSize(const int n0, const int m, const int meq) { int n = n0 + 1; int mineq = m - meq; long total = 0; total += n*(n+1)/2 + m + 4*n + 3; // SLSQP total += (long)(n+1)*(n+2) + (long)(n+1)*meq + m + (long)(mineq+2*n+2)*(n+1) + 3*n + 3; // LSQ total += mineq + 2*n + 2 + 2*meq + (n+1) + (long)(mineq+3*n+3)*(n+1-meq); // LSEI total += (long)(mineq+2*n+2+2)*(n+2) + mineq + 2*n + 2; // LDP total += mineq + 2*n + 2; // NNLS total *= 3; // safety margin (validated generously in the C reference tests) return((int)MathMax(total, 64)); } //+------------------------------------------------------------------+ //|Optimization results | //+------------------------------------------------------------------+ // Container structure consolidating final calculation snapshots from a completed optimization run struct OptimizeResult { int return_code; // Solver output code status mapping from the slsqp_result enum set int nfeval; // Total number of objective function evaluation loops executed int niter; // Total mathematical convergence iteration steps consumed vector solution; // Final calculated coordinates vector within the parameter space vector objective_result; // Final minimized objective cost function scalar result packed inside a vector vector objective_gradient; // Calculated local slope vector at the final solution coordinates // Default initialization constructor OptimizeResult(void) { return_code = WRONG_VALUE; // Sentinel flag representing a non-evaluated or initialized result state nfeval = niter = 0; solution = objective_result = objective_gradient = vector::Zeros(0); } // Parameterized instantiation constructor for explicit data binding OptimizeResult(int rc,int feval,int iter,vector &x, vector& f, vector& g) { return_code = rc; nfeval = feval; niter = iter; solution = x; objective_result = f; objective_gradient = g; } // Copy constructor safeguarding native vector deep replication paths OptimizeResult(OptimizeResult& other) { return_code = other.return_code; nfeval = other.nfeval; niter = other.niter; solution = other.solution; objective_result = other.objective_result; objective_gradient = other.objective_gradient; } // Assignment operator overload ensuring thread and allocation safe copying void operator=(OptimizeResult& other) { return_code = other.return_code; nfeval = other.nfeval; niter = other.niter; solution = other.solution; objective_result = other.objective_result; objective_gradient = other.objective_gradient; } }; //+------------------------------------------------------------------+ //| OOP interface for SLSQP minimizer | //+------------------------------------------------------------------+ // Main class orchestration wrapper driving Sequential Least Squares Programming optimization // routines using clean object-oriented control handles. class CSlsqp : public CObject { protected: slsqp_constraint m_eq_constraints[]; // Tracked collection of zero-equality mathematical constraints slsqp_constraint m_ineq_constraints[];// Tracked collection of bounds-inequality mathematical constraints CFunctor* m_obj; // Pointer to objective; int m_n, m_m, m_meq; SSLSQPVars m_S; //-- Interface to internal solver void evaluate(double &x[], double &f, double &grad[], double &c[], double &jac[], const bool need_derivatives) { vector vx = vector::Zeros(m_n); // Initialize MQL5 native vector container matching decision parameters Copy(vx,x,m_n); //--- double empty_array[]; //--- if(need_derivatives) { ObjReturn or = m_obj.fun_and_grad(vx); f = or.f; //--- Copy(grad,or.g,m_n); } else { vector fv = m_obj.objective_function(vx); f = fv[0]; } //--- int ii = 0; for(uint i = 0; iup[i]) { Print(__FUNCTION__ ": Invalid boundary constraints"); return OptimizeResult(); } m_obj = GetPointer(fungrad); m_meq = slsqp_count_constraints(ArraySize(m_eq_constraints),m_eq_constraints); m_m = m_meq + slsqp_count_constraints(ArraySize(m_ineq_constraints),m_ineq_constraints); m_S.mode = 0; m_S.n = m_n; m_S.m = m_m; m_S.meq = m_meq; int m = m_m; double funx = 0.0; double gradx[]; ArrayResize(gradx, n); double C[]; ArrayResize(C, MathMax(m*n,1)); double d[]; ArrayResize(d, MathMax(m,1)); double mult[]; ArrayResize(mult, m + 2*n + 2); ArrayInitialize(mult, 0.0); double xlw[]; ArrayResize(xlw, n+1); double xuw[]; ArrayResize(xuw, n+1); for(int i = 0; i < n; i++) { xlw[i] = low[i]; xuw[i] = up[i]; } int bufsize = SLSQP_BufferSize(m_n, m_m, m_meq); double buffer[]; ArrayResize(buffer, bufsize); ArrayInitialize(buffer, 0.0); int indices[]; ArrayResize(indices, m + 2*n + 2 + 16); double sol[]; ArrayResize(sol, n+1); for(int i = 0; i < n; i++) sol[i] = x0[i]; evaluate(sol, funx, gradx, d, C, true); int iter = 0; int prev_iter = -1; int max_driver_loops = 50*(m_S.itermax + 10); vector grad_v(n); vector fvector(1); //--- if(display_log_info) PrintFormat("%5s %5s %16s %16s","NIT", "FC", "OBJFUN", "GNORM"); //--- while(!IsStopped()) { iter++; if(iter > max_driver_loops) { if(display_log_info && m_S.iter != prev_iter) { vector gnorm; gnorm.Assign(gradx); PrintFormat("%5i %5i % 16.6E % 16.6E", m_S.iter, iter, funx, gnorm.Norm(VECTOR_NORM_P)); } m_S.mode = 9; break; } SLSQPBody(m_S, funx, gradx, C, d, sol, mult, xlw, xuw, buffer, indices); if(m_S.mode == 1) { evaluate(sol, funx, gradx, d, C, false); } else if(m_S.mode == -1) { evaluate(sol, funx, gradx, d, C, true); } else { if(display_log_info && m_S.iter != prev_iter) { vector gnorm; gnorm.Assign(gradx); PrintFormat("%5i %5i % 16.6E % 16.6E", m_S.iter, iter, funx, gnorm.Norm(VECTOR_NORM_P)); } break; } if(display_log_info && m_S.iter != prev_iter) { vector gnorm; gnorm.Assign(gradx); PrintFormat("%5i %5i % 16.6E % 16.6E", m_S.iter, iter, funx, gnorm.Norm(VECTOR_NORM_P)); } prev_iter = m_S.iter; } //--- Copy(x0,sol,n); Copy(grad_v,gradx,n); fvector[0] = funx; if(IsStopped()) m_S.mode = -2; //--- if(display_log_info) { Print("Exit mode: ", GetExitMode(m_S.mode)); Print(" Current function value:", funx); Print(" Iterations:", m_S.iter); Print(" Function evaluations:", m_obj.nfev()); Print(" Gradient evaluations:", m_obj.ngev()); } // Pack successful calculations tracking profiles into result layout container blocks return OptimizeResult(int(m_S.mode),m_S.iter,iter,x0,fvector,grad_v); } }; //+------------------------------------------------------------------+