2276 lines
76 KiB
MQL5
2276 lines
76 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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<Object.mqh>
|
||
|
|
//---
|
||
|
|
#define SLSQP_EPS 2.220446049250313e-16
|
||
|
|
//---
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| copy from vector to array |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
template<typename T>
|
||
|
|
bool Copy(T& dest_array[],vector<T>& 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())<fabs(count+dest_start) || int(src_vec.Size())<fabs(count+src_start))
|
||
|
|
{
|
||
|
|
Print(__FUNCTION__, " invalid inputs ");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
//---
|
||
|
|
// Element-by-element buffer assignment loop
|
||
|
|
for(int i = 0; i<(count); ++i)
|
||
|
|
dest_array[dest_start+i] = src_vec[src_start+i];
|
||
|
|
//---
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| copy from array to vector |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
template<typename T>
|
||
|
|
bool Copy(vector<T>& 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()<ulong(count+dest_start) || int(src_array.Size())<fabs(count+src_start))
|
||
|
|
{
|
||
|
|
Print(__FUNCTION__, " invalid inputs ");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
//---
|
||
|
|
// Element-by-element vector insertion loop
|
||
|
|
for(int i = 0; i<(count); ++i)
|
||
|
|
dest_vec[dest_start+i] = src_array[src_start+i];
|
||
|
|
//---
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| constraint |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// Structure defining non-linear constraints for the SLSQP optimizer,
|
||
|
|
// handling both single-valued (scalar) and vector-valued constraints.
|
||
|
|
struct slsqp_constraint
|
||
|
|
{
|
||
|
|
int m; // Number of constraint functions (dimension of the constraint vector)
|
||
|
|
IObjective* f_data; // Pointer to the underlying interface tracking the user's objective/constraint logic
|
||
|
|
|
||
|
|
// Default Constructor
|
||
|
|
slsqp_constraint(void)
|
||
|
|
{
|
||
|
|
m=0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Copy Constructor
|
||
|
|
slsqp_constraint(slsqp_constraint& other)
|
||
|
|
{
|
||
|
|
m = other.m;
|
||
|
|
f_data = other.f_data;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Destructor
|
||
|
|
~slsqp_constraint(void)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
// Assignment Operator Overload
|
||
|
|
void operator=(slsqp_constraint& other)
|
||
|
|
{
|
||
|
|
m = other.m;
|
||
|
|
f_data = other.f_data;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Evaluates a single scalar constraint value and its optional gradients
|
||
|
|
double f(int n, double& x[],double& gradient[],IObjective* func_data, int &offset)
|
||
|
|
{
|
||
|
|
vector vx = vector::Zeros(n); // Initialize MQL5 native vector container matching decision parameters
|
||
|
|
vector out;
|
||
|
|
Copy(vx,x,n); // Convert and pass the primitive array values into the native vector
|
||
|
|
|
||
|
|
// If the optimizer expects gradient evaluation
|
||
|
|
if(gradient.Size())
|
||
|
|
{
|
||
|
|
// Simultaneously calculate the constraint value and its Jacobian/Gradient profile
|
||
|
|
ObjReturn or = func_data.fun_and_grad(vx);
|
||
|
|
|
||
|
|
// Flat-map the matrix gradients into the 1D gradient buffer expected by the solver core
|
||
|
|
for(int j = 0; j<n; ++j)
|
||
|
|
for(int i = 0; i<1; ++i)
|
||
|
|
gradient[offset + (i+j*1)] = or.mg[i,j];
|
||
|
|
out = or.mf;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
// Value-only evaluation mode without executing heavy gradient tracks
|
||
|
|
out = func_data.objective_function(vx);
|
||
|
|
++offset;
|
||
|
|
return out[0]; // Return the singular scalar constraint function result
|
||
|
|
}
|
||
|
|
|
||
|
|
// Evaluates vector-valued multi-constraints (m > 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<n; ++j)
|
||
|
|
{
|
||
|
|
// Map the constraint Jacobian matrix rows into the flattened vector expected by the optimization loop
|
||
|
|
for(int i = 0; i<mm; ++i)
|
||
|
|
gradient[offset + (i+j*mm)] = or.mg[i,j];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Multi-constraint value evaluation without Jacobian configurations
|
||
|
|
vector res = func_data.objective_function(vx);
|
||
|
|
Copy(result,res,mm,offset); // Export local vector allocations back to the flat output array at the designated offset
|
||
|
|
}
|
||
|
|
offset+=mm;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| count the number of constraints |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// Iterates across multiple constraint elements to compute the aggregate constraints dimension sizing
|
||
|
|
int slsqp_count_constraints(int p, slsqp_constraint& c[])
|
||
|
|
{
|
||
|
|
int i, count = 0;
|
||
|
|
for(i = 0; i < p; ++i)
|
||
|
|
count += c[i].m;
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| constraints parsing utility |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// Scans multi-constraint setups to locate the largest single vector dimension block
|
||
|
|
int slsqp_max_constraint_dim(int p, slsqp_constraint& c[])
|
||
|
|
{
|
||
|
|
int i, max_dim = 0;
|
||
|
|
for(i = 0; i < p; ++i)
|
||
|
|
if(c[i].m > 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; i<m_eq_constraints.Size(); ++i)
|
||
|
|
if(need_derivatives)
|
||
|
|
slsqp_eval_constraint(c,jac,m_eq_constraints[i],m_n,x,ii);
|
||
|
|
else
|
||
|
|
slsqp_eval_constraint(c,empty_array,m_eq_constraints[i],m_n,x,ii);
|
||
|
|
//---
|
||
|
|
for(uint i = 0; i<m_ineq_constraints.Size(); ++i)
|
||
|
|
if(need_derivatives)
|
||
|
|
slsqp_eval_constraint(c,jac,m_ineq_constraints[i],m_n,x,ii);
|
||
|
|
else
|
||
|
|
slsqp_eval_constraint(c,empty_array,m_ineq_constraints[i],m_n,x,ii);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
public:
|
||
|
|
CSlsqp(void)
|
||
|
|
{
|
||
|
|
m_n = 0;
|
||
|
|
m_m = 0;
|
||
|
|
m_meq = 0;
|
||
|
|
m_S.acc = 1.e-8;
|
||
|
|
m_S.itermax = 100;
|
||
|
|
m_S.n = m_n;
|
||
|
|
m_S.m = m_m;
|
||
|
|
m_S.meq = m_m;
|
||
|
|
}
|
||
|
|
~CSlsqp(void) {}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// Sets precision/accuracy targets for optimization subproblems
|
||
|
|
void SetAcc(double acc_)
|
||
|
|
{
|
||
|
|
m_S.acc = acc_;
|
||
|
|
}
|
||
|
|
// Sets the upper limit on the allowed number of function evaluation cycles
|
||
|
|
void SetMaxEval(int maxeval)
|
||
|
|
{
|
||
|
|
m_S.itermax = fabs(maxeval);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Configures equality constraints with explicit tolerance parameters
|
||
|
|
bool SetEqualityConstraints(CConstraints &constraints)
|
||
|
|
{
|
||
|
|
if(!m_eq_constraints.Size())
|
||
|
|
if(ArrayResize(m_eq_constraints,1)!=1)
|
||
|
|
{
|
||
|
|
Print(__FUNCTION__,": Error ", GetLastError());
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
m_eq_constraints[0].f_data = GetPointer(constraints);
|
||
|
|
m_eq_constraints[0].m = constraints.numConstraints();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Configures inequality constraints with explicit tolerance parameters
|
||
|
|
bool SetInequalityConstraints(CConstraints &constraints)
|
||
|
|
{
|
||
|
|
if(!m_ineq_constraints.Size())
|
||
|
|
if(ArrayResize(m_ineq_constraints,1)!=1)
|
||
|
|
{
|
||
|
|
Print(__FUNCTION__, ": Error ", GetLastError());
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
m_ineq_constraints[0].f_data = GetPointer(constraints);
|
||
|
|
m_ineq_constraints[0].m = constraints.numConstraints();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get string representation of optimizer's exit code
|
||
|
|
string GetExitMode(const int mode)
|
||
|
|
{
|
||
|
|
string message = "";
|
||
|
|
switch(mode)
|
||
|
|
{
|
||
|
|
case -2:
|
||
|
|
message = "Forced termination by user";
|
||
|
|
break;
|
||
|
|
case -1:
|
||
|
|
message = "Gradient evaluation required (g & a)";
|
||
|
|
break;
|
||
|
|
case 0:
|
||
|
|
message = "Optimization terminated successfully";
|
||
|
|
break;
|
||
|
|
case 1:
|
||
|
|
message = "Function evaluation required (f & c)";
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
message = "More equality constraints than independent variables";
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
message = "More than 3*n iterations in LSQ subproblem";
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
message = "Inequality constraints incompatible";
|
||
|
|
break;
|
||
|
|
case 5:
|
||
|
|
message = "Singular matrix E in LSQ subproblem";
|
||
|
|
break;
|
||
|
|
case 6:
|
||
|
|
message = "Singular matrix C in LSQ subproblem";
|
||
|
|
break;
|
||
|
|
case 7:
|
||
|
|
message = "Rank-deficient equality constraint subproblem HFTI";
|
||
|
|
break;
|
||
|
|
case 8:
|
||
|
|
message = "Positive directional derivative for linesearch";
|
||
|
|
break;
|
||
|
|
case 9:
|
||
|
|
message = "Iteration limit reached";
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
message = NULL;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return message;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Main processing pipeline wrapper driving mathematical optimization mechanics over target parameters
|
||
|
|
OptimizeResult Minimize(CFunctor &fungrad, bool display_log_info = false)
|
||
|
|
{
|
||
|
|
vector x0 = fungrad.initial_params(); // Query starting guess parameters from user optimization configuration class
|
||
|
|
int n = (int)x0.Size();
|
||
|
|
m_n = n;
|
||
|
|
vector x_data = x0;
|
||
|
|
//---
|
||
|
|
vector low = fungrad.lower_bounds();
|
||
|
|
vector up = fungrad.upper_bounds();
|
||
|
|
// Dimension checking to ensure shape match constraints for boundary conditions are satisfied
|
||
|
|
if((low.Size()&&low.Size()!=ulong(n)) || (up.Size()&&up.Size()!=ulong(n)))
|
||
|
|
{
|
||
|
|
Print(__FUNCTION__,": All vector inputs should be the same size if not empty");
|
||
|
|
return OptimizeResult();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify bounds ordering logic
|
||
|
|
for(int i = 0; i<n; ++i)
|
||
|
|
if(low[i]>up[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);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
//+------------------------------------------------------------------+
|