51767 lines
2.1 MiB
Plaintext
51767 lines
2.1 MiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| optimization.mqh |
|
|
//| Copyright 2003-2022 Sergey Bochkanov (ALGLIB project) |
|
|
//| Copyright 2012-2026, MetaQuotes Ltd. |
|
|
//| www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
//| Implementation of ALGLIB library in MetaQuotes Language 5 |
|
|
//| |
|
|
//| The features of the library include: |
|
|
//| - Linear algebra (direct algorithms, EVD, SVD) |
|
|
//| - Solving systems of linear and non-linear equations |
|
|
//| - Interpolation |
|
|
//| - Optimization |
|
|
//| - FFT (Fast Fourier Transform) |
|
|
//| - Numerical integration |
|
|
//| - Linear and nonlinear least-squares fitting |
|
|
//| - Ordinary differential equations |
|
|
//| - Computation of special functions |
|
|
//| - Descriptive statistics and hypothesis testing |
|
|
//| - Data analysis - classification, regression |
|
|
//| - Implementing linear algebra algorithms, interpolation, etc. |
|
|
//| in high-precision arithmetic (using MPFR) |
|
|
//| |
|
|
//| This file is free software; you can redistribute it and/or |
|
|
//| modify it under the terms of the GNU General Public License as |
|
|
//| published by the Free Software Foundation (www.m_fsf.org); either |
|
|
//| version 2 of the License, or (at your option) any later version. |
|
|
//| |
|
|
//| This program is distributed in the hope that it will be useful, |
|
|
//| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
//| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
//| GNU General Public License for more details. |
|
|
//+------------------------------------------------------------------+
|
|
#include "matrix.mqh"
|
|
#include "ap.mqh"
|
|
#include "alglibinternal.mqh"
|
|
#include "linalg.mqh"
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is used to store OptGuard report, i.e. report on |
|
|
//| the properties of the nonlinear function being optimized with |
|
|
//| ALGLIB. |
|
|
//| After you tell your optimizer to activate OptGuardthis technology|
|
|
//| starts to silently monitor function values and gradients / |
|
|
//| Jacobians being passed all around during your optimization |
|
|
//| session. Depending on specific set of checks enabled OptGuard may|
|
|
//| perform additional function evaluations (say, about 3*N |
|
|
//| evaluations if you want to check analytic gradient for errors). |
|
|
//| Upon discovering that something strange happens (function values |
|
|
//| and/or gradient components change too sharply and/or |
|
|
//| unexpectedly) OptGuard sets one of the "suspicion flags" |
|
|
//| (without interrupting optimization session). |
|
|
//| After optimization is done, you can examine OptGuard report. |
|
|
//| Following report fields can be set: |
|
|
//| * nonc0suspected |
|
|
//| * nonc1suspected |
|
|
//| * badgradsuspected |
|
|
//| === WHAT CAN BE DETECTED WITH OptGuard INTEGRITY CHECKER ===== |
|
|
//| Following types of errors in your target function (constraints) |
|
|
//| can be caught: |
|
|
//| a) discontinuous functions ("non-C0" part of the report) |
|
|
//| b) functions with discontinuous derivative ("non-C1" part of |
|
|
//| the report) |
|
|
//| c) errors in the analytic gradient provided by user |
|
|
//| These types of errors result in optimizer stopping well before |
|
|
//| reaching solution (most often - right after encountering |
|
|
//| discontinuity). |
|
|
//| Type A errors are usually coding errors during implementation |
|
|
//| of the target function. Most "normal" problems involve continuous|
|
|
//| functions, and anyway you can't reliably optimize discontinuous |
|
|
//| function. |
|
|
//| Type B errors are either coding errors or (in case code itself is|
|
|
//| correct) evidence of the fact that your problem is an "incorrect"|
|
|
//| one. Most optimizers (except for ones provided by MINNS |
|
|
//| subpackage) do not support nonsmooth problems. |
|
|
//| Type C errors are coding errors which often prevent optimizer |
|
|
//| from making even one step or result in optimizing stopping too |
|
|
//| early, as soon as actual descent direction becomes too different |
|
|
//| from one suggested by user-supplied gradient. |
|
|
//| === WHAT IS REPORTED =========================================== |
|
|
//| Following set of report fields deals with discontinuous target |
|
|
//| functions, ones not belonging to C0 continuity class: |
|
|
//| * nonc0suspected - is a flag which is set upon discovering some|
|
|
//| indication of the discontinuity. If this flag is false, the |
|
|
//| rest of "non-C0" fields should be ignored |
|
|
//| * nonc0fidx - is an index of the function (0 for target |
|
|
//| function, 1 or higher for nonlinear constraints) which is |
|
|
//| suspected of being "non-C0" |
|
|
//| * nonc0lipshitzc - a Lipchitz constant for a function which was|
|
|
//| suspected of being non-continuous. |
|
|
//| * nonc0test0positive - set to indicate specific test which |
|
|
//| detected continuity violation (test #0) |
|
|
//| Following set of report fields deals with discontinuous gradient/|
|
|
//| Jacobian, i.e. with functions violating C1 continuity: |
|
|
//| * nonc1suspected - is a flag which is set upon discovering some|
|
|
//| indication of the discontinuity. If this flag is false, the |
|
|
//| rest of "non-C1" fields should be ignored |
|
|
//| * nonc1fidx - is an index of the function (0 for target |
|
|
//| function, 1 or higher for nonlinear constraints) which is |
|
|
//| suspected of being "non-C1" |
|
|
//| * nonc1lipshitzc - a Lipchitz constant for a function gradient |
|
|
//| which was suspected of being non-smooth. |
|
|
//| * nonc1test0positive - set to indicate specific test which |
|
|
//| detected continuity violation (test #0) |
|
|
//| * nonc1test1positive - set to indicate specific test which |
|
|
//| detected continuity violation (test #1) |
|
|
//| Following set of report fields deals with errors in the gradient:|
|
|
//| * badgradsuspected - is a flad which is set upon discovering an|
|
|
//| error in the analytic gradient supplied by user |
|
|
//| * badgradfidx - index of the function with bad gradient (0 for|
|
|
//| target function, 1 or higher for nonlinear constraints) |
|
|
//| * badgradvidx - index of the variable |
|
|
//| * badgradxbase - location where Jacobian is tested |
|
|
//| * following matrices store user-supplied Jacobian and its |
|
|
//| numerical differentiation version (which is assumed to be |
|
|
//| free from the coding errors), both of them computed near the |
|
|
//| initial point: |
|
|
//| * badgraduser, an array[K,N], analytic Jacobian supplied |
|
|
//| by user |
|
|
//| * badgradnum, an array[K,N], numeric Jacobian computed |
|
|
//| by ALGLIB |
|
|
//| Here K is a total number of nonlinear functions (target + |
|
|
//| nonlinear constraints), N is a variable number. |
|
|
//| The element of badgraduser[] with index [badgradfidx,badgradvidx]|
|
|
//| is assumed to be wrong. |
|
|
//| More detailed error log can be obtained from optimizer by |
|
|
//| explicitly requesting reports for tests C0.0, C1.0, C1.1. |
|
|
//+------------------------------------------------------------------+
|
|
struct COptGuardReport
|
|
{
|
|
bool m_nonc0suspected;
|
|
bool m_nonc0test0positive;
|
|
int m_nonc0fidx;
|
|
double m_nonc0lipschitzc;
|
|
bool m_nonc1suspected;
|
|
bool m_nonc1test0positive;
|
|
bool m_nonc1test1positive;
|
|
int m_nonc1fidx;
|
|
double m_nonc1lipschitzc;
|
|
bool m_badgradsuspected;
|
|
int m_badgradfidx;
|
|
int m_badgradvidx;
|
|
CRowDouble m_badgradxbase;
|
|
CMatrixDouble m_badgraduser;
|
|
CMatrixDouble m_badgradnum;
|
|
//--- constructor / destructor
|
|
COptGuardReport(void);
|
|
~COptGuardReport(void) {}
|
|
//---
|
|
void Copy(const COptGuardReport &obj);
|
|
//--- overloading
|
|
void operator=(const COptGuardReport &obj) { Copy(obj); }
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
COptGuardReport::COptGuardReport(void)
|
|
{
|
|
m_nonc0suspected=false;
|
|
m_nonc0test0positive=false;
|
|
m_nonc0fidx=0;
|
|
m_nonc0lipschitzc=0;
|
|
m_nonc1suspected=false;
|
|
m_nonc1test0positive=false;
|
|
m_nonc1test1positive=false;
|
|
m_nonc1fidx=0;
|
|
m_nonc1lipschitzc=0;
|
|
m_badgradsuspected=false;
|
|
m_badgradfidx=0;
|
|
m_badgradvidx=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void COptGuardReport::Copy(const COptGuardReport &obj)
|
|
{
|
|
m_nonc0suspected=obj.m_nonc0suspected;
|
|
m_nonc0test0positive=obj.m_nonc0test0positive;
|
|
m_nonc0fidx=obj.m_nonc0fidx;
|
|
m_nonc0lipschitzc=obj.m_nonc0lipschitzc;
|
|
m_nonc1suspected=obj.m_nonc1suspected;
|
|
m_nonc1test0positive=obj.m_nonc1test0positive;
|
|
m_nonc1test1positive=obj.m_nonc1test1positive;
|
|
m_nonc1fidx=obj.m_nonc1fidx;
|
|
m_nonc1lipschitzc=obj.m_nonc1lipschitzc;
|
|
m_badgradsuspected=obj.m_badgradsuspected;
|
|
m_badgradfidx=obj.m_badgradfidx;
|
|
m_badgradvidx=obj.m_badgradvidx;
|
|
m_badgradxbase=obj.m_badgradxbase;
|
|
m_badgraduser=obj.m_badgraduser;
|
|
m_badgradnum=obj.m_badgradnum;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is used for detailed reporting about suspected C0 |
|
|
//| continuity violation. |
|
|
//| === WHAT IS TESTED ============================================= |
|
|
//| C0 test studies function values (not gradient!) obtained |
|
|
//| during line searches and monitors estimate of the Lipschitz |
|
|
//| constant. Sudden spikes usually indicate that discontinuity was |
|
|
//| detected. |
|
|
//| === WHAT IS REPORTED =========================================== |
|
|
//| Actually, report retrieval function returns TWO report |
|
|
//| structures: |
|
|
//| * one for most suspicious point found so far (one with highest |
|
|
//| change in the function value), so called "strongest" report |
|
|
//| * another one for most detailed line search (more function |
|
|
//| evaluations = easier to understand what's going on) which |
|
|
//| triggered test #0 criteria, so called "longest" report |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; |
|
|
//| FALSE if test did not notice anything (in the latter cases |
|
|
//| fields below are empty). |
|
|
//| * fidx - is an index of the function (0 for target function, 1 |
|
|
//| or higher for nonlinear constraints) which is suspected of |
|
|
//| being "non-C1" |
|
|
//| * x0[], d[] - arrays of length N which store initial point and |
|
|
//| direction for line search (d[] can be normalized, but does |
|
|
//| not have to) |
|
|
//| * stp[], f[] - arrays of length CNT which store step lengths |
|
|
//| and function values at these points; f[i] is evaluated in |
|
|
//| x0+stp[i]*d. |
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb (usually we |
|
|
//| have stpidxb=stpidxa+3, with most likely position of the |
|
|
//| violation between stpidxa+1 and stpidxa+2. |
|
|
//| You can plot function values stored in stp[] and f[] arrays and |
|
|
//| study behavior of your function by your own eyes, just to be sure|
|
|
//| that test correctly reported C1 violation. |
|
|
//+------------------------------------------------------------------+
|
|
struct COptGuardNonC0Report
|
|
{
|
|
int m_stpidxb;
|
|
int m_cnt;
|
|
int m_stpidxa;
|
|
int m_n;
|
|
int m_fidx;
|
|
bool m_positive;
|
|
CRowDouble m_d;
|
|
CRowDouble m_x0;
|
|
CRowDouble m_stp;
|
|
CRowDouble m_f;
|
|
//--- constructor / destructor
|
|
COptGuardNonC0Report(void);
|
|
~COptGuardNonC0Report(void) {}
|
|
//---
|
|
void Copy(const COptGuardNonC0Report &obj);
|
|
//--- overloading
|
|
void operator=(const COptGuardNonC0Report &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
COptGuardNonC0Report::COptGuardNonC0Report(void)
|
|
{
|
|
m_stpidxb=0;
|
|
m_cnt=0;
|
|
m_stpidxa=0;
|
|
m_n=0;
|
|
m_fidx=0;
|
|
m_positive=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void COptGuardNonC0Report::Copy(const COptGuardNonC0Report &obj)
|
|
{
|
|
m_stpidxb=obj.m_stpidxb;
|
|
m_cnt=obj.m_cnt;
|
|
m_stpidxa=obj.m_stpidxa;
|
|
m_n=obj.m_n;
|
|
m_fidx=obj.m_fidx;
|
|
m_positive=obj.m_positive;
|
|
m_d=obj.m_d;
|
|
m_x0=obj.m_x0;
|
|
m_stp=obj.m_stp;
|
|
m_f=obj.m_f;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is used for detailed reporting about suspected C1 |
|
|
//| continuity violation as flagged by C1 test #0 (OptGuard has |
|
|
//| several tests for C1 continuity, this report is used by #0). |
|
|
//| === WHAT IS TESTED ============================================= |
|
|
//| C1 test #0 studies function values (not gradient!) obtained |
|
|
//| during line searches and monitors behavior of directional |
|
|
//| derivative estimate. This test is less powerful than test #1, but|
|
|
//| it does not depend on gradient values and thus it is more robust |
|
|
//| against artifacts introduced by numerical differentiation. |
|
|
//| === WHAT IS REPORTED =========================================== |
|
|
//| Actually, report retrieval function returns TWO report |
|
|
//| structures: |
|
|
//| * one for most suspicious point found so far (one with highest |
|
|
//| change in the directional derivative), so called "strongest" |
|
|
//| report |
|
|
//| * another one for most detailed line search (more function |
|
|
//| evaluations = easier to understand what's going on) which |
|
|
//| triggered test #0 criteria, so called "longest" report |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; |
|
|
//| FALSE if test did not notice anything (in the latter cases |
|
|
//| fields below are empty). |
|
|
//| * fidx - is an index of the function (0 for target function, |
|
|
//| 1 or higher for nonlinear constraints) which is suspected of |
|
|
//| being "non-C1" |
|
|
//| * x0[], d[] - arrays of length N which store initial point and |
|
|
//| direction for line search (d[] can be normalized, but does |
|
|
//| not have to) |
|
|
//| * stp[], f[] - arrays of length CNT which store step lengths |
|
|
//| and function values at these points; f[i] is evaluated in |
|
|
//| x0+stp[i]*d. |
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb (usually we |
|
|
//| have stpidxb=stpidxa+3, with most likely position of the |
|
|
//| violation between stpidxa+1 and stpidxa+2. |
|
|
//| You can plot function values stored in stp[] and f[] arrays |
|
|
//| and study behavior of your function by your own eyes, just to |
|
|
//| be sure that test correctly reported C1 violation. |
|
|
//+------------------------------------------------------------------+
|
|
struct COptGuardNonC1Test0Report
|
|
{
|
|
int m_cnt;
|
|
int m_fidx;
|
|
int m_n;
|
|
int m_stpidxa;
|
|
int m_stpidxb;
|
|
bool m_positive;
|
|
CRowDouble m_d;
|
|
CRowDouble m_f;
|
|
CRowDouble m_stp;
|
|
CRowDouble m_x0;
|
|
//--- constructor / destructor
|
|
COptGuardNonC1Test0Report(void);
|
|
~COptGuardNonC1Test0Report(void) {}
|
|
//---
|
|
void Copy(const COptGuardNonC1Test0Report &obj);
|
|
//--- overloading
|
|
void operator=(const COptGuardNonC1Test0Report &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
COptGuardNonC1Test0Report::COptGuardNonC1Test0Report(void)
|
|
{
|
|
m_cnt=0;
|
|
m_fidx=0;
|
|
m_n=0;
|
|
m_stpidxa=0;
|
|
m_stpidxb=0;
|
|
m_positive=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void COptGuardNonC1Test0Report::Copy(const COptGuardNonC1Test0Report &obj)
|
|
{
|
|
m_cnt=obj.m_cnt;
|
|
m_fidx=obj.m_fidx;
|
|
m_n=obj.m_n;
|
|
m_stpidxa=obj.m_stpidxa;
|
|
m_stpidxb=obj.m_stpidxb;
|
|
m_positive=obj.m_positive;
|
|
m_d=obj.m_d;
|
|
m_f=obj.m_f;
|
|
m_stp=obj.m_stp;
|
|
m_x0=obj.m_x0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is used for detailed reporting about suspected C1 |
|
|
//| continuity violation as flagged by C1 test #1 (OptGuard has |
|
|
//| several tests for C1 continuity, this report is used by #1). |
|
|
//| === WHAT IS TESTED ============================================= |
|
|
//| C1 test #1 studies individual components of the gradient as |
|
|
//| recorded during line searches. Upon discovering discontinuity in |
|
|
//| the gradient this test records specific component which was |
|
|
//| suspected (or one with highest indication of discontinuity if |
|
|
//| multiple components are suspected). |
|
|
//| When precise analytic gradient is provided this test is more |
|
|
//| powerful than test #0 which works with function values and |
|
|
//| ignores user-provided gradient. However, test #0 becomes |
|
|
//| more powerful when numerical differentiation is employed (in such|
|
|
//| cases test #1 detects higher levels of numerical noise and |
|
|
//| becomes too conservative). |
|
|
//| This test also tells specific components of the gradient which |
|
|
//| violate C1 continuity, which makes it more informative than #0, |
|
|
//| which just tells that continuity is violated. |
|
|
//| === WHAT IS REPORTED =========================================== |
|
|
//| Actually, report retrieval function returns TWO report |
|
|
//| structures: |
|
|
//| * one for most suspicious point found so far (one with highest |
|
|
//| change in the directional derivative), so called "strongest" |
|
|
//| report |
|
|
//| * another one for most detailed line search (more function |
|
|
//| evaluations = easier to understand what's going on) which |
|
|
//| triggered test #1 criteria, so called "longest" report |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; |
|
|
//| FALSE if test did not notice anything (in the latter cases |
|
|
//| fields below are empty). |
|
|
//| * fidx - is an index of the function (0 for target function, |
|
|
//| 1 or higher for nonlinear constraints) which is suspected of |
|
|
//| being "non-C1" |
|
|
//| * vidx - is an index of the variable in [0,N) with nonsmooth |
|
|
//| derivative |
|
|
//| * x0[], d[] - arrays of length N which store initial point and |
|
|
//| direction for line search (d[] can be normalized, but does |
|
|
//| not have to) |
|
|
//| * stp[], g[] - arrays of length CNT which store step lengths |
|
|
//| and gradient values at these points; g[i] is evaluated in |
|
|
//| x0+stp[i]*d and contains vidx-th component of the gradient. |
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb (usually we |
|
|
//| have stpidxb=stpidxa+3, with most likely position of the |
|
|
//| violation between stpidxa+1 and stpidxa+2. |
|
|
//| You can plot function values stored in stp[] and g[] arrays |
|
|
//| and study behavior of your function by your own eyes, just to be |
|
|
//| sure that test correctly reported C1 violation. |
|
|
//+------------------------------------------------------------------+
|
|
struct COptGuardNonC1Test1Report
|
|
{
|
|
int m_cnt;
|
|
int m_fidx;
|
|
int m_n;
|
|
int m_stpidxa;
|
|
int m_stpidxb;
|
|
int m_vidx;
|
|
bool m_positive;
|
|
CRowDouble m_d;
|
|
CRowDouble m_g;
|
|
CRowDouble m_stp;
|
|
CRowDouble m_x0;
|
|
//--- constructor / destructor
|
|
COptGuardNonC1Test1Report(void);
|
|
~COptGuardNonC1Test1Report(void) {}
|
|
//---
|
|
void Copy(const COptGuardNonC1Test1Report &obj);
|
|
//--- overloading
|
|
void operator=(const COptGuardNonC1Test1Report &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
COptGuardNonC1Test1Report::COptGuardNonC1Test1Report(void)
|
|
{
|
|
m_cnt=0;
|
|
m_fidx=0;
|
|
m_n=0;
|
|
m_stpidxa=0;
|
|
m_stpidxb=0;
|
|
m_vidx=0;
|
|
m_positive=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void COptGuardNonC1Test1Report::Copy(const COptGuardNonC1Test1Report &obj)
|
|
{
|
|
m_cnt=obj.m_cnt;
|
|
m_fidx=obj.m_fidx;
|
|
m_n=obj.m_n;
|
|
m_stpidxa=obj.m_stpidxa;
|
|
m_stpidxb=obj.m_stpidxb;
|
|
m_vidx=obj.m_vidx;
|
|
m_positive=obj.m_positive;
|
|
m_d=obj.m_d;
|
|
m_g=obj.m_g;
|
|
m_stp=obj.m_stp;
|
|
m_x0=obj.m_x0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class COptGuardApi
|
|
{
|
|
public:
|
|
static void OptGuardInitInternal(COptGuardReport &rep,int n,int k);
|
|
static void OptGuardExportReport(COptGuardReport &srcrep,int n,int k,bool badgradhasxj,COptGuardReport &dstrep);
|
|
static void SmoothnessMonitorExportC1Test0Report(COptGuardNonC1Test0Report &srcrep,CRowDouble &s,COptGuardNonC1Test0Report &dstrep);
|
|
static void SmoothnessMonitorExportC1Test1Report(COptGuardNonC1Test1Report &srcrep,CRowDouble &s,COptGuardNonC1Test1Report &dstrep);
|
|
static bool OptGuardAllClear(COptGuardReport &rep);
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine initializes "internal" OptGuard report, i.e. one|
|
|
//| intended for internal use by optimizers. |
|
|
//+------------------------------------------------------------------+
|
|
void COptGuardApi::OptGuardInitInternal(COptGuardReport &rep,
|
|
int n,
|
|
int k)
|
|
{
|
|
rep.m_nonc0suspected=false;
|
|
rep.m_nonc0test0positive=false;
|
|
rep.m_nonc0lipschitzc=0;
|
|
rep.m_nonc0fidx=-1;
|
|
rep.m_nonc1suspected=false;
|
|
rep.m_nonc1test0positive=false;
|
|
rep.m_nonc1test1positive=false;
|
|
rep.m_nonc1lipschitzc=0;
|
|
rep.m_nonc1fidx=-1;
|
|
rep.m_badgradsuspected=false;
|
|
rep.m_badgradfidx=-1;
|
|
rep.m_badgradvidx=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine exports report to user-readable representation |
|
|
//| (all arrays are forced to have exactly same size as needed; |
|
|
//| unused arrays are set to zero length). |
|
|
//+------------------------------------------------------------------+
|
|
void COptGuardApi::OptGuardExportReport(COptGuardReport &srcrep,
|
|
int n,
|
|
int k,
|
|
bool badgradhasxj,
|
|
COptGuardReport &dstrep)
|
|
{
|
|
//--- copy
|
|
dstrep.m_nonc0suspected=srcrep.m_nonc0suspected;
|
|
dstrep.m_nonc0test0positive=srcrep.m_nonc0test0positive;
|
|
if(srcrep.m_nonc0suspected)
|
|
{
|
|
dstrep.m_nonc0lipschitzc=srcrep.m_nonc0lipschitzc;
|
|
dstrep.m_nonc0fidx=srcrep.m_nonc0fidx;
|
|
}
|
|
else
|
|
{
|
|
dstrep.m_nonc0lipschitzc=0;
|
|
dstrep.m_nonc0fidx=-1;
|
|
}
|
|
dstrep.m_nonc1suspected=srcrep.m_nonc1suspected;
|
|
dstrep.m_nonc1test0positive=srcrep.m_nonc1test0positive;
|
|
dstrep.m_nonc1test1positive=srcrep.m_nonc1test1positive;
|
|
if(srcrep.m_nonc1suspected)
|
|
{
|
|
dstrep.m_nonc1lipschitzc=srcrep.m_nonc1lipschitzc;
|
|
dstrep.m_nonc1fidx=srcrep.m_nonc1fidx;
|
|
}
|
|
else
|
|
{
|
|
dstrep.m_nonc1lipschitzc=0;
|
|
dstrep.m_nonc1fidx=-1;
|
|
}
|
|
dstrep.m_badgradsuspected=srcrep.m_badgradsuspected;
|
|
if(srcrep.m_badgradsuspected)
|
|
{
|
|
dstrep.m_badgradfidx=srcrep.m_badgradfidx;
|
|
dstrep.m_badgradvidx=srcrep.m_badgradvidx;
|
|
}
|
|
else
|
|
{
|
|
dstrep.m_badgradfidx=-1;
|
|
dstrep.m_badgradvidx=-1;
|
|
}
|
|
if(badgradhasxj)
|
|
{
|
|
dstrep.m_badgradxbase=srcrep.m_badgradxbase;
|
|
dstrep.m_badgraduser=srcrep.m_badgraduser;
|
|
dstrep.m_badgradnum=srcrep.m_badgradnum;
|
|
}
|
|
else
|
|
{
|
|
dstrep.m_badgradxbase.Resize(0);
|
|
dstrep.m_badgraduser.Resize(0,0);
|
|
dstrep.m_badgradnum.Resize(0,0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine exports report to user-readable representation |
|
|
//| (all arrays are forced to have exactly same size as needed; |
|
|
//| unused arrays are set to zero length). |
|
|
//| NOTE: we assume that SrcRep contains scaled X0[] and D[], i.e. |
|
|
//| explicit variable scaling was applied. We need to rescale them |
|
|
//| during export, that's why we need S[] parameter. |
|
|
//+------------------------------------------------------------------+
|
|
void COptGuardApi::SmoothnessMonitorExportC1Test0Report(COptGuardNonC1Test0Report &srcrep,
|
|
CRowDouble &s,
|
|
COptGuardNonC1Test0Report &dstrep)
|
|
{
|
|
dstrep.m_positive=srcrep.m_positive;
|
|
if(srcrep.m_positive)
|
|
{
|
|
dstrep.m_stpidxa=srcrep.m_stpidxa;
|
|
dstrep.m_stpidxb=srcrep.m_stpidxb;
|
|
dstrep.m_fidx=srcrep.m_fidx;
|
|
dstrep.m_cnt=srcrep.m_cnt;
|
|
dstrep.m_n=srcrep.m_n;
|
|
dstrep.m_x0=srcrep.m_x0*s+0;
|
|
dstrep.m_d=srcrep.m_d*s+0;
|
|
dstrep.m_stp=srcrep.m_stp;
|
|
dstrep.m_f=srcrep.m_f;
|
|
}
|
|
else
|
|
{
|
|
dstrep.m_stpidxa=-1;
|
|
dstrep.m_stpidxb=-1;
|
|
dstrep.m_fidx=-1;
|
|
dstrep.m_cnt=0;
|
|
dstrep.m_n=0;
|
|
dstrep.m_x0.Resize(0);
|
|
dstrep.m_d.Resize(0);
|
|
dstrep.m_stp.Resize(0);
|
|
dstrep.m_f.Resize(0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine exports report to user-readable representation |
|
|
//| (all arrays are forced to have exactly same size as needed; |
|
|
//| unused arrays are set to zero length). |
|
|
//| NOTE: we assume that SrcRep contains scaled X0[], D[] and G[], |
|
|
//| i.e. explicit variable scaling was applied. We need to |
|
|
//| rescale them during export, that's why we need S[] |
|
|
//| parameter. |
|
|
//+------------------------------------------------------------------+
|
|
void COptGuardApi::SmoothnessMonitorExportC1Test1Report(COptGuardNonC1Test1Report &srcrep,
|
|
CRowDouble &s,
|
|
COptGuardNonC1Test1Report &dstrep)
|
|
{
|
|
dstrep.m_positive=srcrep.m_positive;
|
|
if(srcrep.m_positive)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(srcrep.m_vidx>=0 && srcrep.m_vidx<srcrep.m_n,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
dstrep.m_stpidxa=srcrep.m_stpidxa;
|
|
dstrep.m_stpidxb=srcrep.m_stpidxb;
|
|
dstrep.m_fidx=srcrep.m_fidx;
|
|
dstrep.m_vidx=srcrep.m_vidx;
|
|
dstrep.m_cnt=srcrep.m_cnt;
|
|
dstrep.m_n=srcrep.m_n;
|
|
dstrep.m_x0=srcrep.m_x0*s+0;
|
|
dstrep.m_d=srcrep.m_d*s+0;
|
|
dstrep.m_stp=srcrep.m_stp;
|
|
dstrep.m_g=srcrep.m_g/s[srcrep.m_vidx]+0;
|
|
dstrep.m_g.Resize(srcrep.m_cnt);
|
|
}
|
|
else
|
|
{
|
|
dstrep.m_stpidxa=-1;
|
|
dstrep.m_stpidxb=-1;
|
|
dstrep.m_fidx=-1;
|
|
dstrep.m_vidx=-1;
|
|
dstrep.m_cnt=0;
|
|
dstrep.m_n=0;
|
|
dstrep.m_x0.Resize(0);
|
|
dstrep.m_d.Resize(0);
|
|
dstrep.m_stp.Resize(0);
|
|
dstrep.m_g.Resize(0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns True when all flags are clear. Intended for easy coding |
|
|
//| of unit tests. |
|
|
//+------------------------------------------------------------------+
|
|
bool COptGuardApi::OptGuardAllClear(COptGuardReport &rep)
|
|
{
|
|
return (!(rep.m_badgradsuspected || rep.m_nonc0suspected || rep.m_nonc1suspected));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is used to store temporary buffers for |
|
|
//| L-BFGS-based preconditioner. |
|
|
//+------------------------------------------------------------------+
|
|
struct CPrecBufLBFGS
|
|
{
|
|
CRowInt m_bufb;
|
|
CRowInt m_idx;
|
|
CRowDouble m_alpha;
|
|
CRowDouble m_bufa;
|
|
CRowDouble m_norms;
|
|
CRowDouble m_rho;
|
|
CMatrixDouble m_yk;
|
|
CRowDouble norms;
|
|
//--- constructor / destructor
|
|
CPrecBufLBFGS(void) {}
|
|
~CPrecBufLBFGS(void) {}
|
|
void Copy(const CPrecBufLBFGS &obj);
|
|
//--- overloading
|
|
void operator=(const CPrecBufLBFGS &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CPrecBufLBFGS::Copy(const CPrecBufLBFGS &obj)
|
|
{
|
|
m_bufb=obj.m_bufb;
|
|
m_idx=obj.m_idx;
|
|
m_alpha=obj.m_alpha;
|
|
m_bufa=obj.m_bufa;
|
|
m_norms=obj.m_norms;
|
|
m_rho=obj.m_rho;
|
|
m_yk=obj.m_yk;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is used to store temporary buffers for LowRank |
|
|
//| preconditioner. |
|
|
//+------------------------------------------------------------------+
|
|
struct CPrecBufLowRank
|
|
{
|
|
int m_k;
|
|
int m_n;
|
|
CRowDouble m_bufc;
|
|
CRowDouble m_d;
|
|
CRowDouble m_tmp;
|
|
CMatrixDouble m_bufw;
|
|
CMatrixDouble m_bufz;
|
|
CMatrixDouble m_v;
|
|
CPrecBufLowRank(void) { m_k=0; m_n=0; }
|
|
~CPrecBufLowRank(void) {}
|
|
void Copy(const CPrecBufLowRank &obj);
|
|
//--- overloading
|
|
void operator=(const CPrecBufLowRank &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CPrecBufLowRank::Copy(const CPrecBufLowRank &obj)
|
|
{
|
|
m_k=obj.m_k;
|
|
m_n=obj.m_n;
|
|
m_bufc=obj.m_bufc;
|
|
m_d=obj.m_d;
|
|
m_tmp=obj.m_tmp;
|
|
m_bufw=obj.m_bufw;
|
|
m_bufz=obj.m_bufz;
|
|
m_v=obj.m_v;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is a smoothness monitor. |
|
|
//+------------------------------------------------------------------+
|
|
struct CSmoothnessMonitor
|
|
{
|
|
RCommState m_probingrcomm;
|
|
RCommState m_rstateg0;
|
|
COptGuardReport m_rep;
|
|
COptGuardNonC1Test1Report m_nonc1test1lngrep;
|
|
COptGuardNonC1Test1Report m_nonc1test1strrep;
|
|
COptGuardNonC1Test0Report m_nonc1test0lngrep;
|
|
COptGuardNonC1Test0Report m_nonc1test0strrep;
|
|
COptGuardNonC0Report m_nonc0lngrep;
|
|
COptGuardNonC0Report m_nonc0strrep;
|
|
int m_enqueuedcnt;
|
|
int m_k;
|
|
int m_n;
|
|
int m_probingnstepsstored;
|
|
int m_probingnvalues;
|
|
int m_sortedcnt;
|
|
double m_nonc0currentrating;
|
|
double m_nonc0lngrating;
|
|
double m_nonc0strrating;
|
|
double m_nonc1currentrating;
|
|
double m_nonc1test0lngrating;
|
|
double m_nonc1test0strrating;
|
|
double m_nonc1test1lngrating;
|
|
double m_nonc1test1strrating;
|
|
double m_probingstepmax;
|
|
double m_probingstepscale;
|
|
double m_probingstp;
|
|
bool m_badgradhasxj;
|
|
bool m_checksmoothness;
|
|
bool m_linesearchspoiled;
|
|
bool m_linesearchstarted;
|
|
bool m_needfij;
|
|
CRowInt m_bufi;
|
|
CRowInt m_sortedidx;
|
|
CRowInt m_tmpidx;
|
|
CRowDouble m_bufr;
|
|
CRowDouble m_dcur;
|
|
CRowDouble m_deltax;
|
|
CRowDouble m_du;
|
|
CRowDouble m_enqueuedfunc;
|
|
CRowDouble m_enqueuedstp;
|
|
CRowDouble m_enqueuedx;
|
|
CRowDouble m_f;
|
|
CRowDouble m_f0;
|
|
CRowDouble m_fbase;
|
|
CRowDouble m_fc;
|
|
CRowDouble m_fi;
|
|
CRowDouble m_fm;
|
|
CRowDouble m_fp;
|
|
CRowDouble m_g;
|
|
CRowDouble m_jc;
|
|
CRowDouble m_jm;
|
|
CRowDouble m_jp;
|
|
CRowDouble m_probingf;
|
|
CRowDouble m_probingsteps;
|
|
CRowDouble m_s;
|
|
CRowDouble m_sortedstp;
|
|
CRowDouble m_stp;
|
|
CRowDouble m_x;
|
|
CRowDouble m_xbase;
|
|
CRowDouble m_xu;
|
|
CMatrixDouble m_enqueuedjac;
|
|
CMatrixDouble m_j;
|
|
CMatrixDouble m_j0;
|
|
CMatrixDouble m_jbasenum;
|
|
CMatrixDouble m_jbaseusr;
|
|
CMatrixDouble m_probingslopes;
|
|
CMatrixDouble m_probingvalues;
|
|
//--- constructor / destructor
|
|
CSmoothnessMonitor(void);
|
|
~CSmoothnessMonitor(void) {}
|
|
void Copy(const CSmoothnessMonitor &obj);
|
|
//--- overloading
|
|
void operator=(const CSmoothnessMonitor &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CSmoothnessMonitor::CSmoothnessMonitor(void)
|
|
{
|
|
m_enqueuedcnt=0;
|
|
m_k=0;
|
|
m_n=0;
|
|
m_probingnstepsstored=0;
|
|
m_probingnvalues=0;
|
|
m_sortedcnt=0;
|
|
m_nonc0currentrating=0;
|
|
m_nonc0lngrating=0;
|
|
m_nonc0strrating=0;
|
|
m_nonc1currentrating=0;
|
|
m_nonc1test0lngrating=0;
|
|
m_nonc1test0strrating=0;
|
|
m_nonc1test1lngrating=0;
|
|
m_nonc1test1strrating=0;
|
|
m_probingstepmax=0;
|
|
m_probingstepscale=0;
|
|
m_probingstp=0;
|
|
m_badgradhasxj=false;
|
|
m_checksmoothness=false;
|
|
m_linesearchspoiled=false;
|
|
m_linesearchstarted=false;
|
|
m_needfij=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSmoothnessMonitor::Copy(const CSmoothnessMonitor &obj)
|
|
{
|
|
m_probingrcomm=obj.m_probingrcomm;
|
|
m_rstateg0=obj.m_rstateg0;
|
|
m_rep=obj.m_rep;
|
|
m_nonc1test1lngrep=obj.m_nonc1test1lngrep;
|
|
m_nonc1test1strrep=obj.m_nonc1test1strrep;
|
|
m_nonc1test0lngrep=obj.m_nonc1test0lngrep;
|
|
m_nonc1test0strrep=obj.m_nonc1test0strrep;
|
|
m_nonc0lngrep=obj.m_nonc0lngrep;
|
|
m_nonc0strrep=obj.m_nonc0strrep;
|
|
m_enqueuedcnt=obj.m_enqueuedcnt;
|
|
m_k=obj.m_k;
|
|
m_n=obj.m_n;
|
|
m_probingnstepsstored=obj.m_probingnstepsstored;
|
|
m_probingnvalues=obj.m_probingnvalues;
|
|
m_sortedcnt=obj.m_sortedcnt;
|
|
m_nonc0currentrating=obj.m_nonc0currentrating;
|
|
m_nonc0lngrating=obj.m_nonc0lngrating;
|
|
m_nonc0strrating=obj.m_nonc0strrating;
|
|
m_nonc1currentrating=obj.m_nonc1currentrating;
|
|
m_nonc1test0lngrating=obj.m_nonc1test0lngrating;
|
|
m_nonc1test0strrating=obj.m_nonc1test0strrating;
|
|
m_nonc1test1lngrating=obj.m_nonc1test1lngrating;
|
|
m_nonc1test1strrating=obj.m_nonc1test1strrating;
|
|
m_probingstepmax=obj.m_probingstepmax;
|
|
m_probingstepscale=obj.m_probingstepscale;
|
|
m_probingstp=obj.m_probingstp;
|
|
m_badgradhasxj=obj.m_badgradhasxj;
|
|
m_checksmoothness=obj.m_checksmoothness;
|
|
m_linesearchspoiled=obj.m_linesearchspoiled;
|
|
m_linesearchstarted=obj.m_linesearchstarted;
|
|
m_needfij=obj.m_needfij;
|
|
m_bufi=obj.m_bufi;
|
|
m_sortedidx=obj.m_sortedidx;
|
|
m_tmpidx=obj.m_tmpidx;
|
|
m_bufr=obj.m_bufr;
|
|
m_dcur=obj.m_dcur;
|
|
m_deltax=obj.m_deltax;
|
|
m_du=obj.m_du;
|
|
m_enqueuedfunc=obj.m_enqueuedfunc;
|
|
m_enqueuedstp=obj.m_enqueuedstp;
|
|
m_enqueuedx=obj.m_enqueuedx;
|
|
m_f=obj.m_f;
|
|
m_f0=obj.m_f0;
|
|
m_fbase=obj.m_fbase;
|
|
m_fc=obj.m_fc;
|
|
m_fi=obj.m_fi;
|
|
m_fm=obj.m_fm;
|
|
m_fp=obj.m_fp;
|
|
m_g=obj.m_g;
|
|
m_jc=obj.m_jc;
|
|
m_jm=obj.m_jm;
|
|
m_jp=obj.m_jp;
|
|
m_probingf=obj.m_probingf;
|
|
m_probingsteps=obj.m_probingsteps;
|
|
m_s=obj.m_s;
|
|
m_sortedstp=obj.m_sortedstp;
|
|
m_stp=obj.m_stp;
|
|
m_x=obj.m_x;
|
|
m_xbase=obj.m_xbase;
|
|
m_xu=obj.m_xu;
|
|
m_enqueuedjac=obj.m_enqueuedjac;
|
|
m_j=obj.m_j;
|
|
m_j0=obj.m_j0;
|
|
m_jbasenum=obj.m_jbasenum;
|
|
m_jbaseusr=obj.m_jbaseusr;
|
|
m_probingslopes=obj.m_probingslopes;
|
|
m_probingvalues=obj.m_probingvalues;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary functions for other classes |
|
|
//+------------------------------------------------------------------+
|
|
class COptServ
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const double m_ognoiselevelf;
|
|
static const double m_ognoiselevelg;
|
|
static const double m_ogminrating0;
|
|
static const double m_ogminrating1;
|
|
|
|
static void CheckBcViolation(bool &HasBndL[],CRowDouble &bndl,bool &HasBndU[],CRowDouble &bndu,CRowDouble &x,int n,CRowDouble &s,bool nonunits,double &bcerr,int &bcidx);
|
|
static void CheckLcViolation(CMatrixDouble &cleic,CRowInt &lcsrcidx,int nec,int nic,CRowDouble &x,int n,double &lcerr,int &lcidx);
|
|
static void CheckNLcViolation(CRowDouble &fi,int ng,int nh,double &nlcerr,int &nlcidx);
|
|
static void UnScaleAndCheckNLcViolation(CRowDouble &fi,CRowDouble &fscales,int ng,int nh,double &nlcerr,int &nlcidx);
|
|
static void TrimPrepare(double f,double &threshold);
|
|
static void TrimFunction(double &f,double &g[],const int n,const double threshold);
|
|
static void TrimFunction(double &f,CRowDouble &g,const int n,const double threshold);
|
|
static bool EnforceBoundaryConstraints(CRowDouble &x,CRowDouble &bl,bool &havebl[],CRowDouble &bu,bool &havebu[],int nmain,int nslack);
|
|
static void ProjectGradientIntoBC(CRowDouble &x,CRowDouble &g,CRowDouble &bl,bool &havebl[],CRowDouble &bu,bool &havebu[],int nmain,int nslack);
|
|
static void CalculateStepBound(CRowDouble &x,CRowDouble &d,double alpha,CRowDouble &bndl,bool &havebndl[],CRowDouble &bndu,bool &havebndu[],int nmain,int nslack,int &variabletofreeze,double &valuetofreeze,double &maxsteplen);
|
|
static int PostProcessBoundedStep(CRowDouble &x,CRowDouble &xprev,CRowDouble &bndl,bool &havebndl[],CRowDouble &bndu,bool &havebndu[],int nmain,int nslack,int variabletofreeze,double valuetofreeze,double steptaken,double maxsteplen);
|
|
static void FilterDirection(CRowDouble &d,CRowDouble &x,CRowDouble &bndl,bool &havebndl[],CRowDouble &bndu,bool &havebndu[],CRowDouble &s,int nmain,int nslack,double droptol);
|
|
static int NumberOfChangedConstraints(CRowDouble &x,CRowDouble &xprev,CRowDouble &bndl,bool &havebndl[],CRowDouble &bndu,bool &havebndu[],int nmain,int nslack);
|
|
static bool FindFeasiblePoint(CRowDouble &x,CRowDouble &bndl,bool &havebndl[],CRowDouble &bndu,bool &havebndu[],int nmain,int nslack,CMatrixDouble &ce,int k,double epsi,int &qpits,int &gpaits);
|
|
static bool DerivativeCheck(double f0,double df0,double f1,double df1,double f,double df,double width);
|
|
static void EstimateParabolicModel(double absasum,double absasum2,double mx,double mb,double md,double d1,double d2,int &d1est,int &d2est);
|
|
static void InexactLBFGSPreconditioner(CRowDouble &s,int n,CRowDouble &d,CRowDouble &c,CMatrixDouble &w,int k,CPrecBufLBFGS &buf);
|
|
static void PrepareLowRankPreconditioner(CRowDouble &d,CRowDouble &c,CMatrixDouble &w,int n,int k,CPrecBufLowRank &buf);
|
|
static void ApplyLowRankPreconditioner(CRowDouble &s,CPrecBufLowRank &buf);
|
|
static void SmoothnessMonitorInit(CSmoothnessMonitor &monitor,CRowDouble &s,int n,int k,bool checksmoothness);
|
|
static void SmoothnessMonitorStartLineSearch(CSmoothnessMonitor &monitor,CRowDouble &x,CRowDouble &fi,CMatrixDouble &jac);
|
|
static void SmoothnessMonitorStartLineSearch1u(CSmoothnessMonitor &monitor,CRowDouble &s,CRowDouble &invs,CRowDouble &x,double f0,CRowDouble &j0);
|
|
static void SmoothnessMonitorEnqueuePoint(CSmoothnessMonitor &monitor,CRowDouble &d,double stp,CRowDouble &x,CRowDouble &fi,CMatrixDouble &jac);
|
|
static void SmoothnessMonitorEnqueuePoint1u(CSmoothnessMonitor &monitor,CRowDouble &s,CRowDouble &invs,CRowDouble &d,double stp,CRowDouble &x,double f0,CRowDouble &j0);
|
|
static void SmoothnessMonitorFinalizeLineSearch(CSmoothnessMonitor &monitor);
|
|
static void SmoothnessMonitorStartProbing(CSmoothnessMonitor &monitor,double stpmax,int nvalues,double stepscale);
|
|
static bool SmoothnessMonitorProbe(CSmoothnessMonitor &monitor);
|
|
static void SmoothnessMonitorTraceProbingResults(CSmoothnessMonitor &monitor);
|
|
static void SmoothnessMonitorTraceStatus(CSmoothnessMonitor &monitor,bool callersuggeststrace);
|
|
static void SmoothnessMonitorExportReport(CSmoothnessMonitor &monitor,COptGuardReport &rep);
|
|
static bool SmoothnessMonitorCheckGradientATX0(CSmoothnessMonitor &monitor,CRowDouble &unscaledx0,CRowDouble &s,CRowDouble &bndl,CRowDouble &bndu,bool hasboxconstraints,double teststep);
|
|
|
|
private:
|
|
static double FeasibilityError(CMatrixDouble &ce,CRowDouble &x,int nmain,int nslack,int k,CRowDouble &m_tmp0);
|
|
static void FeasibilityErrorGrad(CMatrixDouble &ce,CRowDouble &x,int nmain,int nslack,int k,double &err,CRowDouble &grad,CRowDouble &m_tmp0);
|
|
static void TestC0Continuity(double f0,double f1,double f2,double f3,double noise0,double noise1,double noise2,double noise3,double delta0,double delta1,double delta2,bool applyspecialcorrection,double &rating,double &lipschitz);
|
|
static void C1ContinuityTest0(CSmoothnessMonitor &monitor,int funcidx,int stpidx,int sortedcnt);
|
|
static void C1ContinuityTest1(CSmoothnessMonitor &monitor,int funcidx,int stpidx,int sortedcnt);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
const double COptServ::m_ognoiselevelf=1.0E2*CMath::m_machineepsilon;
|
|
const double COptServ::m_ognoiselevelg=1.0E4*CMath::m_machineepsilon;
|
|
const double COptServ::m_ogminrating0=50.0;
|
|
const double COptServ::m_ogminrating1=50.0;
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine checks violation of the box constraints. On |
|
|
//| output it sets bcerr to the maximum scaled violation, bcidx to |
|
|
//| the index of the violating constraint. |
|
|
//| if bcerr=0 (say, if no constraints are violated) then bcidx=-1.|
|
|
//| If nonunits=false then s[] is not referenced at all (assumed |
|
|
//| unit). |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::CheckBcViolation(bool &HasBndL[],
|
|
CRowDouble &bndl,
|
|
bool &HasBndU[],
|
|
CRowDouble &bndu,
|
|
CRowDouble &x,
|
|
int n,
|
|
CRowDouble &s,
|
|
bool nonunits,
|
|
double &bcerr,
|
|
int &bcidx)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
double vs=0;
|
|
double ve=0;
|
|
//--- initialization
|
|
bcerr=0;
|
|
bcidx=-1;
|
|
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Fetch scale
|
|
if(nonunits)
|
|
vs=1/s[i];
|
|
else
|
|
vs=1;
|
|
//--- Check lower bound
|
|
if(HasBndL[i] && x[i]<bndl[i])
|
|
{
|
|
ve=(bndl[i]-x[i])*vs;
|
|
if(ve>bcerr)
|
|
{
|
|
bcerr=ve;
|
|
bcidx=i;
|
|
}
|
|
}
|
|
//--- Check upper bound
|
|
if(HasBndU[i] && x[i]>bndu[i])
|
|
{
|
|
ve=(x[i]-bndu[i])*vs;
|
|
if(ve>bcerr)
|
|
{
|
|
bcerr=ve;
|
|
bcidx=i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine checks violation of the general linear |
|
|
//| constraints. |
|
|
//| Constraints are assumed to be un-normalized and stored in the |
|
|
//| format "NEC equality ones followed by NIC inequality ones". |
|
|
//| On output it sets lcerr to the maximum scaled violation, lcidx to|
|
|
//| the source index of the most violating constraint (row indexes of|
|
|
//| CLEIC are mapped to the indexes of the "original" constraints via|
|
|
//| LCSrcIdx[] array. |
|
|
//| if lcerr=0 (say, if no constraints are violated) then lcidx=-1.|
|
|
//| If nonunits=false then s[] is not referenced at all (assumed |
|
|
//| unit). |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::CheckLcViolation(CMatrixDouble &cleic,
|
|
CRowInt &lcsrcidx,
|
|
int nec,
|
|
int nic,
|
|
CRowDouble &x,
|
|
int n,
|
|
double &lcerr,
|
|
int &lcidx)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double cx=0;
|
|
double cnrm=0;
|
|
double v=0;
|
|
|
|
lcerr=0;
|
|
lcidx=-1;
|
|
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
cx=-cleic.Get(i,n);
|
|
cnrm=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=cleic.Get(i,j);
|
|
cx+=v*x[j];
|
|
cnrm+=v*v;
|
|
}
|
|
cnrm=MathSqrt(cnrm);
|
|
cx=cx/CApServ::Coalesce(cnrm,1);
|
|
if(i<nec)
|
|
{
|
|
cx=MathAbs(cx);
|
|
}
|
|
else
|
|
{
|
|
cx=MathMax(cx,0);
|
|
}
|
|
if(cx>lcerr)
|
|
{
|
|
lcerr=cx;
|
|
lcidx=lcsrcidx[i];
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine checks violation of the nonlinear constraints. |
|
|
//| Fi[0] is the target value (ignored), Fi[1:NG+NH] are values of |
|
|
//| nonlinear constraints. |
|
|
//| On output it sets nlcerr to the scaled violation, nlcidx to the |
|
|
//| index of the most violating constraint in [0,NG+NH-1] range. |
|
|
//| if nlcerr=0 (say, if no constraints are violated) then |
|
|
//| nlcidx=-1. |
|
|
//| If nonunits=false then s[] is not referenced at all (assumed |
|
|
//| unit). |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::CheckNLcViolation(CRowDouble &fi,
|
|
int ng,
|
|
int nh,
|
|
double &nlcerr,
|
|
int &nlcidx)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
double v=0;
|
|
|
|
nlcerr=0;
|
|
nlcidx=-1;
|
|
|
|
for(i=0; i<ng+nh; i++)
|
|
{
|
|
v=fi[i+1];
|
|
if(i<ng)
|
|
v=MathAbs(v);
|
|
else
|
|
v=MathMax(v,0);
|
|
if(v>nlcerr)
|
|
{
|
|
nlcerr=v;
|
|
nlcidx=i;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine is same as CheckNLCViolation, but is works with |
|
|
//| scaled constraints: it assumes that Fi[] were divided by |
|
|
//| FScales[] vector BEFORE passing them to this function. |
|
|
//| The function checks scaled values, but reports unscaled errors. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::UnScaleAndCheckNLcViolation(CRowDouble &fi,
|
|
CRowDouble &fscales,
|
|
int ng,
|
|
int nh,
|
|
double &nlcerr,
|
|
int &nlcidx)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
double v=0;
|
|
|
|
nlcerr=0;
|
|
nlcidx=-1;
|
|
for(i=0; i<ng+nh; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(fscales[i+1]>0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
v=fi[i+1]*fscales[i+1];
|
|
if(i<ng)
|
|
v=MathAbs(v);
|
|
else
|
|
v=MathMax(v,0);
|
|
if(v>nlcerr)
|
|
{
|
|
nlcerr=v;
|
|
nlcidx=i;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine is used to prepare threshold value which will be |
|
|
//| used for trimming of the target function (see comments on |
|
|
//| TrimFunction() for more information). |
|
|
//| This function accepts only one parameter: function value at the |
|
|
//| starting point. It returns threshold which will be used for |
|
|
//| trimming. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::TrimPrepare(double f,double &threshold)
|
|
{
|
|
threshold=10*(MathAbs(f)+1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine is used to "trim" target function, i.e. to do |
|
|
//| following transformation: |
|
|
//| { {F,G} if F<Threshold |
|
|
//| {F_tr, G_tr} = { |
|
|
//| { {Threshold, 0} if F>=Threshold |
|
|
//| Such transformation allows us to solve problems with |
|
|
//| singularities by redefining function in such way that it becomes |
|
|
//| bounded from above. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::TrimFunction(double &f,double &g[],const int n,
|
|
const double threshold)
|
|
{
|
|
//--- check
|
|
if(f>=threshold)
|
|
{
|
|
f=threshold;
|
|
ArrayFill(g,0,n,0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::TrimFunction(double &f,CRowDouble &g,const int n,
|
|
const double threshold)
|
|
{
|
|
//--- check
|
|
if(f>=threshold)
|
|
{
|
|
f=threshold;
|
|
if(g.Size()<=n)
|
|
g=vector<double>::Zeros(n);
|
|
else
|
|
for(int i=0; i<n; i++)
|
|
g.Set(i,0.0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function enforces boundary constraints in the X. |
|
|
//| This function correctly(although a bit inefficient) handles BL[i]|
|
|
//| which are -INF and BU[i] which are +INF. |
|
|
//| We have NMain + NSlack dimensional X, with first NMain components|
|
|
//| bounded by BL/BU, and next NSlack ones bounded by non-negativity |
|
|
//| constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[NMain + NSlack], point |
|
|
//| BL - array[NMain], lower bounds (may contain -INF, when|
|
|
//| bound is not present) |
|
|
//| HaveBL - array[NMain], if HaveBL[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| BU - array[NMain], upper bounds (may contain +INF, when|
|
|
//| bound is not present) |
|
|
//| HaveBU - array[NMain], if HaveBU[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - X with all constraints being enforced |
|
|
//| It returns True when constraints are consistent, |
|
|
//| False - when constraints are inconsistent. |
|
|
//+------------------------------------------------------------------+
|
|
bool COptServ::EnforceBoundaryConstraints(CRowDouble &x,
|
|
CRowDouble &bl,
|
|
bool &havebl[],
|
|
CRowDouble &bu,
|
|
bool &havebu[],
|
|
int nmain,
|
|
int nslack)
|
|
{
|
|
for(int i=0; i<nmain; i++)
|
|
{
|
|
if(havebl[i] && havebu[i] && bl[i]>bu[i])
|
|
return(false);
|
|
//---
|
|
if(havebl[i] && x[i]<bl[i])
|
|
x.Set(i,bl[i]);
|
|
if(havebu[i] && x[i]>bu[i])
|
|
x.Set(i,bu[i]);
|
|
}
|
|
|
|
for(int i=0; i<nslack; i++)
|
|
if(x[nmain+i]<0.0)
|
|
x.Set(nmain+i,0);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function projects gradient into feasible area of boundary |
|
|
//| constrained optimization problem. X can be infeasible with |
|
|
//| respect to boundary constraints. We have NMain + NSlack |
|
|
//| dimensional X, with first NMain components bounded by BL/BU,|
|
|
//| and next NSlack ones bounded by non-negativity constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[NMain + NSlack], point |
|
|
//| G - array[NMain + NSlack], gradient |
|
|
//| BL - lower bounds(may contain -INF, when bound is not |
|
|
//| present) |
|
|
//| HaveBL - if HaveBL[i] is False, then i-th bound is not |
|
|
//| present |
|
|
//| BU - upper bounds(may contain +INF, when bound is not |
|
|
//| present) |
|
|
//| HaveBU - if HaveBU[i] is False, then i-th bound is not |
|
|
//| present |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| G - projection of G. Components of G which satisfy one |
|
|
//| of the following |
|
|
//| (1)(X[I] <= BndL[I]) and (G[I] > 0), OR |
|
|
//| (2)(X[I] >= BndU[I]) and (G[I] < 0) |
|
|
//| are replaced by zeros. |
|
|
//| NOTE 1: this function assumes that constraints are feasible. It |
|
|
//| throws exception otherwise. |
|
|
//| NOTE 2: in fact, projection of ANTI - gradient is calculated, |
|
|
//| because this function trims components of - G which |
|
|
//| points outside of the feasible area. However, working |
|
|
//| with - G is considered confusing, because all |
|
|
//| optimization source work with G. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::ProjectGradientIntoBC(CRowDouble &x,
|
|
CRowDouble &g,
|
|
CRowDouble &bl,
|
|
bool &havebl[],
|
|
CRowDouble &bu,
|
|
bool &havebu[],
|
|
int nmain,
|
|
int nslack)
|
|
{
|
|
for(int i=0; i<nmain; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(!havebl[i] || !havebu[i] || bl[i]<=bu[i],__FUNCTION__+": internal error (infeasible constraints)"))
|
|
return;
|
|
if(havebl[i] && x[i]<=bl[i] && g[i]>0.0)
|
|
g.Set(i,0);
|
|
if(havebu[i] && x[i]>=bu[i] && g[i]<0.0)
|
|
g.Set(i,0);
|
|
}
|
|
for(int i=0; i<nslack; i++)
|
|
if(x[nmain+i]<=0.0 && g[nmain+i]>0.0)
|
|
g.Set(nmain+i,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Given |
|
|
//| a) initial point X0[NMain + NSlack] (feasible with respect |
|
|
//| to bound constraints) |
|
|
//| b) step vector alpha*D[NMain + NSlack] |
|
|
//| c) boundary constraints BndL[NMain], BndU[NMain] |
|
|
//| d) implicit non-negativity constraints for slack variables |
|
|
//| this function calculates bound on the step length |
|
|
//| subject to boundary constraints. |
|
|
//| It returns: |
|
|
//| * MaxStepLen - such step length that X0 + MaxStepLen*alpha*D |
|
|
//| is exactly at the boundary given by constraints |
|
|
//| * VariableToFreeze - index of the constraint to be activated, |
|
|
//| 0 <= VariableToFreeze < NMain + NSlack |
|
|
//| * ValueToFreeze - value of the corresponding constraint. |
|
|
//| Notes: |
|
|
//| * it is possible that several constraints can be activated by |
|
|
//| the step at once. In such cases only one constraint is |
|
|
//| returned. It is caller responsibility to check other |
|
|
//| constraints. This function makes sure that we activate at |
|
|
//| least one constraint, and everything else is the |
|
|
//| responsibility of the caller. |
|
|
//| * steps smaller than MaxStepLen still can activate constraints |
|
|
//| due to numerical errors. Thus purpose of this function is |
|
|
//| not to guard against accidental activation of the |
|
|
//| constraints - quite the reverse, its purpose is to activate |
|
|
//| at least constraint upon performing step which is too long. |
|
|
//| * in case there is no constraints to activate, we return |
|
|
//| negative VariableToFreeze and zero MaxStepLen and |
|
|
//| ValueToFreeze. |
|
|
//| * this function assumes that constraints are consistent; it |
|
|
//| throws exception otherwise. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[NMain + NSlack], point. Must be feasible with|
|
|
//| respect to bound constraints(exception will be |
|
|
//| thrown otherwise) |
|
|
//| D - array[NMain + NSlack], step direction |
|
|
//| alpha - scalar multiplier before D, alpha<>0 |
|
|
//| BndL - lower bounds, array[NMain] (may contain -INF, when|
|
|
//| bound is not present) |
|
|
//| HaveBndL - array[NMain], if HaveBndL[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| BndU - array[NMain], upper bounds (may contain +INF, when|
|
|
//| bound is not present) |
|
|
//| HaveBndU - array[NMain], if HaveBndU[i] is False, then i th |
|
|
//| bound is not present |
|
|
//| NMain - number of main variables |
|
|
//| NSlack - number of slack variables |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| VariableToFreeze: |
|
|
//| * negative value = step is unbounded, ValueToFreeze = 0,|
|
|
//| MaxStepLen = 0. |
|
|
//| * Non-Negative value = at least one constraint, given by |
|
|
//| this parameter, will be activated |
|
|
//| upon performing maximum step. |
|
|
//| ValueToFreeze - value of the variable which will be |
|
|
//| constrained |
|
|
//| MaxStepLen - maximum length of the step. Can be zero when |
|
|
//| step vector looks outside of the feasible area|
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::CalculateStepBound(CRowDouble &x,CRowDouble &d,
|
|
double alpha,CRowDouble &bndl,
|
|
bool &havebndl[],CRowDouble &bndu,
|
|
bool &havebndu[],int nmain,int nslack,
|
|
int &variabletofreeze,
|
|
double &valuetofreeze,
|
|
double &maxsteplen)
|
|
{
|
|
//--- create variables
|
|
double prevmax=0;
|
|
double initval=0;
|
|
//--- initialization
|
|
variabletofreeze=0;
|
|
valuetofreeze=0;
|
|
maxsteplen=0;
|
|
//--- check
|
|
if(!CAp::Assert(alpha!=0.0,__FUNCTION__+": zero alpha"))
|
|
return;
|
|
variabletofreeze=-1;
|
|
initval=CMath::m_maxrealnumber;
|
|
maxsteplen=initval;
|
|
for(int i=0; i<nmain; i++)
|
|
{
|
|
if(havebndl[i] && (alpha*d[i])<0.0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(x[i]>=bndl[i],__FUNCTION__+": infeasible X"))
|
|
return;
|
|
prevmax=maxsteplen;
|
|
maxsteplen=CApServ::SafeMinPosRV(x[i]-bndl[i],-(alpha*d[i]),maxsteplen);
|
|
if(maxsteplen<prevmax)
|
|
{
|
|
variabletofreeze=i;
|
|
valuetofreeze=bndl[i];
|
|
}
|
|
}
|
|
if(havebndu[i] && (alpha*d[i])>0.0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(x[i]<=bndu[i],__FUNCTION__+": infeasible X"))
|
|
return;
|
|
prevmax=maxsteplen;
|
|
maxsteplen=CApServ::SafeMinPosRV(bndu[i]-x[i],alpha*d[i],maxsteplen);
|
|
if(maxsteplen<prevmax)
|
|
{
|
|
variabletofreeze=i;
|
|
valuetofreeze=bndu[i];
|
|
}
|
|
}
|
|
}
|
|
for(int i=0; i<nslack; i++)
|
|
{
|
|
if((alpha*d[nmain+i])<0.0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(x[nmain+i]>=0.0,__FUNCTION__+": infeasible X"))
|
|
return;
|
|
prevmax=maxsteplen;
|
|
maxsteplen=CApServ::SafeMinPosRV(x[nmain+i],-(alpha*d[nmain+i]),maxsteplen);
|
|
if(maxsteplen<prevmax)
|
|
{
|
|
variabletofreeze=nmain+i;
|
|
valuetofreeze=0;
|
|
}
|
|
}
|
|
}
|
|
if(maxsteplen==initval)
|
|
{
|
|
valuetofreeze=0;
|
|
maxsteplen=0;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function postprocesses bounded step by: |
|
|
//| * analysing step length(whether it is equal to MaxStepLen) and |
|
|
//| activating constraint given by VariableToFreeze if needed |
|
|
//| * checking for additional bound constraints to activate |
|
|
//| This function uses final point of the step, quantities calculated|
|
|
//| by the CalculateStepBound() function. As result, it returns |
|
|
//| point which is exactly feasible with respect to boundary |
|
|
//| constraints. |
|
|
//| NOTE 1: this function does NOT handle and check linear equality |
|
|
//| constraints |
|
|
//| NOTE 2: when StepTaken = MaxStepLen we always activate at least |
|
|
//| one constraint |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[NMain + NSlack], final point to postprocess |
|
|
//| XPrev - array[NMain + NSlack], initial point |
|
|
//| BndL - lower bounds, array[NMain] (may contain -INF, when|
|
|
//| bound is not present) |
|
|
//| HaveBndL - array[NMain], if HaveBndL[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| BndU - array[NMain], upper bounds (may contain +INF, when|
|
|
//| bound is not present) |
|
|
//| HaveBndU - array[NMain], if HaveBndU[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| NMain - number of main variables |
|
|
//| NSlack - number of slack variables |
|
|
//| VariableToFreeze - result of CalculateStepBound() |
|
|
//| ValueToFreeze - result of CalculateStepBound() |
|
|
//| StepTaken- actual step length (actual step is equal to the |
|
|
//| possibly non-unit step direction vector times this |
|
|
//| parameter). StepTaken <= MaxStepLen. |
|
|
//| MaxStepLen- result of CalculateStepBound() |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - point bounded with respect to constraints. |
|
|
//| components corresponding to active constraints are |
|
|
//| exactly equal to the boundary values. |
|
|
//| RESULT: |
|
|
//| number of constraints activated in addition to previously |
|
|
//| active ones. |
|
|
//| Constraints which were DEACTIVATED are ignored(do not influence |
|
|
//| function value). |
|
|
//+------------------------------------------------------------------+
|
|
int COptServ::PostProcessBoundedStep(CRowDouble &x,
|
|
CRowDouble &xprev,
|
|
CRowDouble &bndl,
|
|
bool &havebndl[],
|
|
CRowDouble &bndu,
|
|
bool &havebndu[],
|
|
int nmain,
|
|
int nslack,
|
|
int variabletofreeze,
|
|
double valuetofreeze,
|
|
double steptaken,
|
|
double maxsteplen)
|
|
{
|
|
//--- create variables
|
|
int result=0;
|
|
int i=0;
|
|
bool wasactivated;
|
|
//--- check
|
|
if(!CAp::Assert(variabletofreeze<0 || steptaken<=maxsteplen))
|
|
return(result);
|
|
//--- Activate constraints
|
|
if(variabletofreeze>=0 && steptaken==maxsteplen)
|
|
x.Set(variabletofreeze,valuetofreeze);
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
if(havebndl[i] && x[i]<bndl[i])
|
|
x.Set(i,bndl[i]);
|
|
if(havebndu[i] && x[i]>bndu[i])
|
|
x.Set(i,bndu[i]);
|
|
}
|
|
for(i=0; i<nslack; i++)
|
|
if(x[nmain+i]<=0.0)
|
|
x.Set(nmain+i,0);
|
|
//--- Calculate number of constraints being activated
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
wasactivated=(x[i]!=xprev[i] && ((havebndl[i] && x[i]==bndl[i]) || (havebndu[i] && x[i]==bndu[i])));
|
|
wasactivated=wasactivated || variabletofreeze==i;
|
|
if(wasactivated)
|
|
result++;
|
|
}
|
|
for(i=0; i<nslack; i++)
|
|
{
|
|
wasactivated=(x[nmain+i]!=xprev[nmain+i] && x[nmain+i]==0.0);
|
|
wasactivated=wasactivated || variabletofreeze==nmain+i;
|
|
if(wasactivated)
|
|
result++;
|
|
}
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| The purpose of this function is to prevent algorithm from |
|
|
//| "unsticking" from the active bound constraints because of |
|
|
//| numerical noise in the gradient or Hessian. |
|
|
//| It is done by zeroing some components of the search direction D. |
|
|
//| D[i] is zeroed when both(a) and (b) are true: |
|
|
//| a) corresponding X[i] is exactly at the boundary |
|
|
//| b) | D[i]*S[i] | <= DropTol*Sqrt(SUM(D[i] ^ 2 * S[I] ^ 2)) |
|
|
//| D can be step direction, antigradient, gradient, or anything |
|
|
//| similar. Sign of D does not matter, nor matters step length. |
|
|
//| NOTE 1: boundary constraints are expected to be consistent, as |
|
|
//| well as X is expected to be feasible. Exception will be |
|
|
//| thrown otherwise. |
|
|
//| INPUT PARAMETERS: |
|
|
//| D - array[NMain + NSlack], direction |
|
|
//| X - array[NMain + NSlack], current point |
|
|
//| BndL - lower bounds, array[NMain] (may contain -INF, when |
|
|
//| bound is not present) |
|
|
//| HaveBndL - array[NMain], if HaveBndL[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| BndU - array[NMain], upper bounds (may contain +INF, when |
|
|
//| bound is not present) |
|
|
//| HaveBndU - array[NMain], if HaveBndU[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| S - array[NMain + NSlack], scaling of the variables |
|
|
//| NMain - number of main variables |
|
|
//| NSlack - number of slack variables |
|
|
//| DropTol - drop tolerance, >= 0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - point bounded with respect to constraints. |
|
|
//| components corresponding to active constraints are |
|
|
//| exactly equal to the boundary values. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::FilterDirection(CRowDouble &d,
|
|
CRowDouble &x,
|
|
CRowDouble &bndl,
|
|
bool &havebndl[],
|
|
CRowDouble &bndu,
|
|
bool &havebndu[],
|
|
CRowDouble &s,
|
|
int nmain,
|
|
int nslack,
|
|
double droptol)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
double scalednorm=0;
|
|
bool isactive=false;
|
|
|
|
for(i=0; i<nmain+nslack; i++)
|
|
scalednorm+=CMath::Sqr(d[i]*s[i]);
|
|
scalednorm=MathSqrt(scalednorm);
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(!havebndl[i] || x[i]>=bndl[i],__FUNCTION__+": infeasible point"))
|
|
return;
|
|
if(!CAp::Assert(!havebndu[i] || x[i]<=bndu[i],__FUNCTION__+": infeasible point"))
|
|
return;
|
|
isactive=(havebndl[i] && x[i]==bndl[i]) || (havebndu[i] && x[i]==bndu[i]);
|
|
if(isactive && MathAbs(d[i]*s[i])<=(droptol*scalednorm))
|
|
d.Set(i,0.0);
|
|
}
|
|
for(i=0; i<nslack; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(x[nmain+i]>=0.0,__FUNCTION__+": infeasible point"))
|
|
return;
|
|
if(x[nmain+i]==0.0 && MathAbs(d[nmain+i]*s[nmain+i])<=(droptol*scalednorm))
|
|
d.Set(nmain+i,0.0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns number of bound constraints whose State was|
|
|
//| changed (either activated or deactivated) when making step from |
|
|
//| XPrev to X. |
|
|
//| Constraints are considered: |
|
|
//| * active - when we are exactly at the boundary |
|
|
//| * inactive - when we are not at the boundary |
|
|
//| You should note that antigradient direction is NOT taken into |
|
|
//| account when we make decions on the constraint status. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[NMain + NSlack], final point. Must be |
|
|
//| feasible with respect to bound constraints. |
|
|
//| XPrev - array[NMain + NSlack], initial point. Must be |
|
|
//| feasible with respect to bound constraints. |
|
|
//| BndL - lower bounds, array[NMain] (may contain -INF, when |
|
|
//| bound is not present) |
|
|
//| HaveBndL - array[NMain], if HaveBndL[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| BndU - array[NMain], upper bounds (may contain +INF, when |
|
|
//| bound is not present) |
|
|
//| HaveBndU - array[NMain], if HaveBndU[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| NMain - number of main variables |
|
|
//| NSlack - number of slack variables |
|
|
//| RESULT: number of constraints whose State was changed. |
|
|
//+------------------------------------------------------------------+
|
|
int COptServ::NumberOfChangedConstraints(CRowDouble &x,
|
|
CRowDouble &xprev,
|
|
CRowDouble &bndl,
|
|
bool &havebndl[],
|
|
CRowDouble &bndu,
|
|
bool &havebndu[],
|
|
int nmain,
|
|
int nslack)
|
|
{
|
|
//--- create variables
|
|
int result=0;
|
|
int i=0;
|
|
bool statuschanged;
|
|
|
|
for(i=0; i<nmain; i++)
|
|
if(x[i]!=xprev[i])
|
|
{
|
|
statuschanged=false;
|
|
if(havebndl[i] && (x[i]==bndl[i] || xprev[i]==bndl[i]))
|
|
statuschanged=true;
|
|
if(havebndu[i] && (x[i]==bndu[i] || xprev[i]==bndu[i]))
|
|
statuschanged=true;
|
|
if(statuschanged)
|
|
result++;
|
|
}
|
|
|
|
for(i=0; i<nslack; i++)
|
|
if(x[nmain+i]!=xprev[nmain+i] && (x[nmain+i]==0.0 || xprev[nmain+i]==0.0))
|
|
result=result+1;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function finds feasible point of(NMain + NSlack)-dimensional|
|
|
//| problem subject to NMain explicit boundary constraints (some |
|
|
//| constraints can be omitted), NSlack implicit non-negativity |
|
|
//| constraints, K linear equality constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[NMain + NSlack], initial point. |
|
|
//| BndL - lower bounds, array[NMain] (may contain -INF, when |
|
|
//| bound is not present) |
|
|
//| HaveBndL - array[NMain], if HaveBndL[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| BndU - array[NMain], upper bounds (may contain +INF, when |
|
|
//| bound is not present) |
|
|
//| HaveBndU - array[NMain], if HaveBndU[i] is False, then i-th |
|
|
//| bound is not present |
|
|
//| NMain - number of main variables |
|
|
//| NSlack - number of slack variables |
|
|
//| CE - array[K, NMain + NSlack + 1], equality constraints|
|
|
//| CE*x = b. Rows contain constraints, first |
|
|
//| NMain + NSlack columns contain coefficients before |
|
|
//| X[], last column contain right part. |
|
|
//| K - number of linear constraints |
|
|
//| EpsI - infeasibility(error in the right part) allowed in |
|
|
//| the solution |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - feasible point or best infeasible point found |
|
|
//| beforec algorithm termination |
|
|
//| QPIts - number of QP iterations(for debug purposes) |
|
|
//| GPAIts - number of GPA iterations(for debug purposes) |
|
|
//| RESULT: |
|
|
//| True in case X is feasible, |
|
|
//| False - if it is infeasible. |
|
|
//+------------------------------------------------------------------+
|
|
bool COptServ::FindFeasiblePoint(CRowDouble &x,
|
|
CRowDouble &bndl,
|
|
bool &havebndl[],
|
|
CRowDouble &bndu,
|
|
bool &havebndu[],
|
|
int nmain,
|
|
int nslack,
|
|
CMatrixDouble &CE,
|
|
int k,
|
|
double epsi,
|
|
int &qpits,
|
|
int &gpaits)
|
|
{
|
|
//--- create variables
|
|
bool result;
|
|
int i=0;
|
|
int j=0;
|
|
int idx0=0;
|
|
int idx1=0;
|
|
CRowDouble permx;
|
|
CRowDouble xn;
|
|
CRowDouble xa;
|
|
CRowDouble newtonstep;
|
|
CRowDouble g;
|
|
CRowDouble pg;
|
|
CRowDouble tau;
|
|
CRowDouble s;
|
|
double armijostep=0;
|
|
double armijobeststep=0;
|
|
double armijobestfeas=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double mx=0;
|
|
double feaserr=0;
|
|
double feaserr0=0;
|
|
double feaserr1=0;
|
|
double feasold=0;
|
|
double feasnew=0;
|
|
double pgnorm=0;
|
|
double vn=0;
|
|
double vd=0;
|
|
double stp=0;
|
|
int vartofreeze=0;
|
|
double valtofreeze=0;
|
|
double maxsteplen=0;
|
|
bool werechangesinconstraints;
|
|
bool stage1isover;
|
|
bool converged;
|
|
CRowDouble activeconstraints;
|
|
CRowDouble tmpk;
|
|
CRowDouble colnorms;
|
|
int nactive=0;
|
|
int nfree=0;
|
|
CRowInt p1;
|
|
CRowInt p2;
|
|
CApBuff buf;
|
|
int itscount=0;
|
|
int itswithintolerance=0;
|
|
int maxitswithintolerance=0;
|
|
int badits=0;
|
|
int maxbadits=0;
|
|
int gparuns=0;
|
|
int maxarmijoruns=0;
|
|
double infeasibilityincreasetolerance=0;
|
|
CMatrixDouble permce;
|
|
CMatrixDouble q;
|
|
int i_=0;
|
|
//--- initialization
|
|
CMatrixDouble ce=CE;
|
|
qpits=0;
|
|
gpaits=0;
|
|
maxitswithintolerance=3;
|
|
maxbadits=3;
|
|
maxarmijoruns=5;
|
|
qpits=0;
|
|
gpaits=0;
|
|
//--- Initial enforcement of the feasibility with respect to boundary constraints
|
|
//--- NOTE: after this block we assume that boundary constraints are consistent.
|
|
if(!EnforceBoundaryConstraints(x,bndl,havebndl,bndu,havebndu,nmain,nslack))
|
|
return(false);
|
|
//--- No linear constraints, we can exit right now
|
|
if(k==0)
|
|
return(true);
|
|
//--- Scale rows of CE in such way that max(CE[i,0..m_nmain+nslack-1])=1 for any i=0..m_k-1
|
|
for(i=0; i<k; i++)
|
|
{
|
|
v=CAblasF::RMaxAbsR(nmain+nslack,ce,i);
|
|
if(v!=0.0)
|
|
ce.Row(i,ce[i]/v);
|
|
}
|
|
//--- Allocate temporaries
|
|
xn.Resize(nmain+nslack);
|
|
xa.Resize(nmain+nslack);
|
|
permx.Resize(nmain+nslack);
|
|
g.Resize(nmain+nslack);
|
|
pg.Resize(nmain+nslack);
|
|
tmpk.Resize(k);
|
|
permce.Resize(k,nmain+nslack);
|
|
activeconstraints.Resize(nmain+nslack);
|
|
newtonstep.Resize(nmain+nslack);
|
|
s=vector<double>::Ones(nmain+nslack);
|
|
colnorms=(ce*ce+0).Sum(0); //--- squerd summ by columns
|
|
//--- K>0, we have linear equality constraints combined with bound constraints.
|
|
//--- Try to find feasible point as minimizer of the quadratic function
|
|
//--- F(x) = 0.5*||CE*x-b||^2 = 0.5*x'*(CE'*CE)*x - (b'*CE)*x + 0.5*b'*b
|
|
//--- subject to boundary constraints given by BL, BU and non-negativity of
|
|
//--- the slack variables. BTW, we drop constant term because it does not
|
|
//--- actually influences on the solution.
|
|
//--- Below we will assume that K>0.
|
|
itswithintolerance=0;
|
|
badits=0;
|
|
itscount=0;
|
|
while(true)
|
|
{
|
|
//--- Dynamically adjust infeasibility error tolerance
|
|
infeasibilityincreasetolerance=MathMax(CAblasF::RMaxAbsV(nmain+nslack,x),1)*(1000+nmain)*CMath::m_machineepsilon;
|
|
//--- Stage 0: check for exact convergence
|
|
converged=true;
|
|
feaserr=FeasibilityError(ce,x,nmain,nslack,k,tmpk);
|
|
for(i=0; i<k; i++)
|
|
{
|
|
//--- Calculate MX - maximum term in the left part
|
|
//--- Terminate if error in the right part is not greater than 100*Eps*MX.
|
|
//--- IMPORTANT: we must perform check for non-strict inequality, i.e. to use <= instead of <.
|
|
//--- it will allow us to easily handle situations with zero rows of CE.
|
|
//--- NOTE: it is important to calculate feasibility error with dedicated
|
|
//--- function. Once we had a situation when usage of "inline" code
|
|
//--- resulted in different numerical values calculated at different
|
|
//--- parts of program for exactly same X. However, this value is
|
|
//--- essential for algorithm's ability to terminate before entering
|
|
//--- infinite loop, so reproducibility of numerical results is very
|
|
//--- important.
|
|
mx=0;
|
|
v=-ce.Get(i,nmain+nslack);
|
|
for(j=0; j<nmain+nslack; j++)
|
|
{
|
|
mx=MathMax(mx,MathAbs(ce.Get(i,j)*x[j]));
|
|
v+=ce.Get(i,j)*x[j];
|
|
}
|
|
converged=converged && MathAbs(v)<=(100.0*CMath::m_machineepsilon*mx);
|
|
}
|
|
feaserr0=feaserr;
|
|
if(converged)
|
|
return (feaserr<=epsi);
|
|
//--- Stage 1: equality constrained quadratic programming
|
|
//--- * treat active bound constraints as equality ones (constraint is considered
|
|
//--- active when we are at the boundary, independently of the antigradient direction)
|
|
//--- * calculate unrestricted Newton step to point XM (which may be infeasible)
|
|
//--- calculate MaxStepLen = largest step in direction of XM which retains feasibility.
|
|
//--- * perform bounded step from X to XN:
|
|
//--- a) XN=XM (if XM is feasible)
|
|
//--- b) XN=X-MaxStepLen*(XM-X) (otherwise)
|
|
//--- * X := XN
|
|
//--- * if XM (Newton step subject to currently active constraints) was feasible, goto Stage 2
|
|
//--- * repeat Stage 1
|
|
//--- NOTE 1: in order to solve constrained qudratic subproblem we will have to reorder
|
|
//--- variables in such way that ones corresponding to inactive constraints will
|
|
//--- be first, and active ones will be last in the list. CE and X are now
|
|
//--- [ xi ]
|
|
//--- separated into two parts: CE = [CEi CEa], x = [ ], where CEi/Xi correspond
|
|
//--- [ xa ]
|
|
//--- to INACTIVE constraints, and CEa/Xa correspond to the ACTIVE ones.
|
|
//--- Now, instead of F=0.5*x'*(CE'*CE)*x - (b'*CE)*x + 0.5*b'*b, we have
|
|
//--- F(xi) = 0.5*(CEi*xi,CEi*xi) + (CEa*xa-b,CEi*xi) + (0.5*CEa*xa-b,CEa*xa).
|
|
//--- Here xa is considered constant, i.e. we optimize with respect to xi, leaving xa fixed.
|
|
//--- We can solve it by performing SVD of CEi and calculating pseudoinverse of the
|
|
//--- Hessian matrix. Of course, we do NOT calculate pseudoinverse explicitly - we
|
|
//--- just use singular vectors to perform implicit multiplication by it.
|
|
while(true)
|
|
{
|
|
//--- Calculate G - gradient subject to equality constraints,
|
|
//--- multiply it by inverse of the Hessian diagonal to obtain initial
|
|
//--- step vector.
|
|
//--- Bound step subject to constraints which can be activated,
|
|
//--- run Armijo search with increasing step size.
|
|
//--- Search is terminated when feasibility error stops to decrease.
|
|
//--- NOTE: it is important to test for "stops to decrease" instead
|
|
//--- of "starts to increase" in order to correctly handle cases with
|
|
//--- zero CE.
|
|
armijobeststep=0.0;
|
|
FeasibilityErrorGrad(ce,x,nmain,nslack,k,armijobestfeas,g,tmpk);
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
if((havebndl[i] && x[i]==bndl[i]) || (havebndu[i] && x[i]==bndu[i]))
|
|
g.Set(i,0.0);
|
|
}
|
|
for(i=0; i<nslack; i++)
|
|
if(x[nmain+i]==0.0)
|
|
g.Set(nmain+i,0.0);
|
|
v=0.0;
|
|
for(i=0; i<nmain+nslack; i++)
|
|
{
|
|
if(CMath::Sqr(colnorms[i])!=0.0)
|
|
newtonstep.Set(i,-(g[i]/CMath::Sqr(colnorms[i])));
|
|
else
|
|
newtonstep.Set(i,0.0);
|
|
v+=CMath::Sqr(newtonstep[i]);
|
|
}
|
|
if(v==0.0)
|
|
{
|
|
//--- Constrained gradient is zero, QP iterations are over
|
|
break;
|
|
}
|
|
CalculateStepBound(x,newtonstep,1.0,bndl,havebndl,bndu,havebndu,nmain,nslack,vartofreeze,valtofreeze,maxsteplen);
|
|
if(vartofreeze>=0 && maxsteplen==0.0)
|
|
{
|
|
//--- Can not perform step, QP iterations are over
|
|
break;
|
|
}
|
|
if(vartofreeze>=0)
|
|
armijostep=MathMin(1.0,maxsteplen);
|
|
else
|
|
armijostep=1;
|
|
while(true)
|
|
{
|
|
xa=x.ToVector()+newtonstep*armijostep;
|
|
EnforceBoundaryConstraints(xa,bndl,havebndl,bndu,havebndu,nmain,nslack);
|
|
feaserr=FeasibilityError(ce,xa,nmain,nslack,k,tmpk);
|
|
if(feaserr>=armijobestfeas)
|
|
break;
|
|
armijobestfeas=feaserr;
|
|
armijobeststep=armijostep;
|
|
armijostep=2.0*armijostep;
|
|
}
|
|
x+=newtonstep*armijobeststep+0;
|
|
EnforceBoundaryConstraints(x,bndl,havebndl,bndu,havebndu,nmain,nslack);
|
|
//--- Determine number of active and free constraints
|
|
nactive=0;
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
activeconstraints.Set(i,0);
|
|
if(havebndl[i] && x[i]==bndl[i])
|
|
activeconstraints.Set(i,1);
|
|
if(havebndu[i] && x[i]==bndu[i])
|
|
activeconstraints.Set(i,1);
|
|
if(activeconstraints[i]>0.0)
|
|
nactive++;
|
|
}
|
|
for(i=0; i<nslack; i++)
|
|
{
|
|
activeconstraints.Set(nmain+i,0);
|
|
if(x[nmain+i]==0.0)
|
|
activeconstraints.Set(nmain+i,1);
|
|
if(activeconstraints[nmain+i]>0.0)
|
|
nactive++;
|
|
}
|
|
nfree=nmain+nslack-nactive;
|
|
if(nfree==0)
|
|
break;
|
|
qpits++;
|
|
//--- Reorder variables: CE is reordered to PermCE, X is reordered to PermX
|
|
CTSort::TagSortBuf(activeconstraints,nmain+nslack,p1,p2,buf);
|
|
permce=ce;
|
|
permx=x;
|
|
for(j=0; j<nmain+nslack; j++)
|
|
{
|
|
if(p2[j]!=j)
|
|
{
|
|
idx0=p2[j];
|
|
idx1=j;
|
|
permce.SwapCols(idx0,idx1);
|
|
permx.Swap(idx0,idx1);
|
|
}
|
|
}
|
|
//--- Calculate (unprojected) gradient:
|
|
//--- G(xi) = CEi'*(CEi*xi + CEa*xa - b)
|
|
for(i=0; i<nfree; i++)
|
|
g.Set(i,0);
|
|
for(i=0; i<k; i++)
|
|
{
|
|
v=CAblasF::RDotVR(nmain+nslack,permx,permce,i);
|
|
tmpk.Set(i,v-ce.Get(i,nmain+nslack));
|
|
}
|
|
for(i=0; i<k; i++)
|
|
{
|
|
v=tmpk[i];
|
|
for(i_=0; i_<nfree; i_++)
|
|
g.Add(i_,v*permce.Get(i,i_));
|
|
}
|
|
//--- Calculate Newton step using pseudoinverse PermCE:
|
|
//--- F(xi) = 0.5*xi'*H*xi + g'*xi (Taylor decomposition)
|
|
//--- XN = -H^(-1)*g (new point, solution of the QP subproblem)
|
|
//--- H = CEi'*CEi
|
|
//--- H^(-1) can be calculated via QR or LQ decomposition (see below)
|
|
//--- step = -H^(-1)*g
|
|
//--- NOTE: PermCE is destroyed after this block
|
|
newtonstep=vector<double>::Zeros(nmain+nslack);
|
|
if(k<=nfree)
|
|
{
|
|
//--- CEi = L*Q
|
|
//--- H = Q'*L'*L*Q
|
|
//--- inv(H) = Q'*inv(L)*inv(L')*Q
|
|
//--- NOTE: we apply minor regularizing perturbation to diagonal of L,
|
|
//--- which is equal to 10*K*Eps
|
|
COrtFac::RMatrixLQ(permce,k,nfree,tau);
|
|
COrtFac::RMatrixLQUnpackQ(permce,k,nfree,tau,k,q);
|
|
vector<double> diag=permce.Diag();
|
|
v=(MathAbs(diag)).Max();
|
|
if(v!=0.0)
|
|
permce.Diag(diag+10.0*k*CMath::m_machineepsilon*v);
|
|
CAblas::RMatrixGemVect(k,nfree,1.0,q,0,0,0,g,0,0.0,tmpk,0);
|
|
CAblas::RMatrixTrsVect(k,permce,0,0,false,false,1,tmpk,0);
|
|
CAblas::RMatrixTrsVect(k,permce,0,0,false,false,0,tmpk,0);
|
|
CAblas::RMatrixGemVect(nfree,k,-1.0,q,0,0,1,tmpk,0,0.0,newtonstep,0);
|
|
}
|
|
else
|
|
{
|
|
//--- CEi = Q*R
|
|
//--- H = R'*R
|
|
//--- inv(H) = inv(R)*inv(R')
|
|
//--- NOTE: we apply minor regularizing perturbation to diagonal of R,
|
|
//--- which is equal to 10*K*Eps
|
|
COrtFac::RMatrixQR(permce,k,nfree,tau);
|
|
vector<double> diag=permce.Diag();
|
|
diag.Resize(nfree);
|
|
v=(MathAbs(diag)).Max();
|
|
if(v!=0.0)
|
|
for(i=0; i<nfree; i++)
|
|
{
|
|
vv=10*nfree*CMath::m_machineepsilon*v;
|
|
if(diag[i]<0.0)
|
|
vv=-vv;
|
|
permce.Add(i,i,vv);
|
|
}
|
|
for(i_=0; i_<nfree; i_++)
|
|
newtonstep.Set(i_,-g[i_]);
|
|
CAblas::RMatrixTrsVect(nfree,permce,0,0,true,false,1,newtonstep,0);
|
|
CAblas::RMatrixTrsVect(nfree,permce,0,0,true,false,0,newtonstep,0);
|
|
}
|
|
//--- Post-reordering of Newton step
|
|
for(j=nmain+nslack-1; j>=0; j--)
|
|
{
|
|
if(p2[j]!=j)
|
|
{
|
|
idx0=p2[j];
|
|
idx1=j;
|
|
newtonstep.Swap(idx0,idx1);
|
|
}
|
|
}
|
|
//--- NewtonStep contains Newton step subject to active bound constraints.
|
|
//--- Such step leads us to the minimizer of the equality constrained F,
|
|
//--- but such minimizer may be infeasible because some constraints which
|
|
//--- are inactive at the initial point can be violated at the solution.
|
|
//--- Thus, we perform optimization in two stages:
|
|
//--- a) perform bounded Newton step, i.e. step in the Newton direction
|
|
//--- until activation of the first constraint
|
|
//--- b) in case (MaxStepLen>0)and(MaxStepLen<1), perform additional iteration
|
|
//--- of the Armijo line search in the rest of the Newton direction.
|
|
CalculateStepBound(x,newtonstep,1.0,bndl,havebndl,bndu,havebndu,nmain,nslack,vartofreeze,valtofreeze,maxsteplen);
|
|
if(vartofreeze>=0)
|
|
{
|
|
if(maxsteplen==0.0)
|
|
//--- Activation of the constraints prevent us from performing step,
|
|
//--- QP iterations are over
|
|
break;
|
|
v=MathMin(1.0,maxsteplen);
|
|
}
|
|
else
|
|
v=1.0;
|
|
xn=x.ToVector()+newtonstep*v;
|
|
PostProcessBoundedStep(xn,x,bndl,havebndl,bndu,havebndu,nmain,nslack,vartofreeze,valtofreeze,v,maxsteplen);
|
|
if(maxsteplen>0.0 && maxsteplen<1.0)
|
|
{
|
|
//--- Newton step was restricted by activation of the constraints,
|
|
//--- perform Armijo iteration.
|
|
//--- Initial estimate for best step is zero step. We try different
|
|
//--- step sizes, from the 1-MaxStepLen (residual of the full Newton
|
|
//--- step) to progressively smaller and smaller steps.
|
|
armijobeststep=0.0;
|
|
armijobestfeas=FeasibilityError(ce,xn,nmain,nslack,k,tmpk);
|
|
armijostep=1-maxsteplen;
|
|
for(j=0; j<maxarmijoruns; j++)
|
|
{
|
|
xa=xn.ToVector()+newtonstep*armijostep;
|
|
EnforceBoundaryConstraints(xa,bndl,havebndl,bndu,havebndu,nmain,nslack);
|
|
feaserr=FeasibilityError(ce,xa,nmain,nslack,k,tmpk);
|
|
if(feaserr<armijobestfeas)
|
|
{
|
|
armijobestfeas=feaserr;
|
|
armijobeststep=armijostep;
|
|
}
|
|
armijostep=0.5*armijostep;
|
|
}
|
|
xa=xn.ToVector()+newtonstep*armijobeststep;
|
|
EnforceBoundaryConstraints(xa,bndl,havebndl,bndu,havebndu,nmain,nslack);
|
|
}
|
|
else
|
|
{
|
|
//--- Armijo iteration is not performed
|
|
xa=xn;
|
|
}
|
|
stage1isover=(maxsteplen>=1.0 || maxsteplen==0.0);
|
|
//--- Calculate feasibility errors for old and new X.
|
|
//--- These quantinies are used for debugging purposes only.
|
|
//--- However, we can leave them in release code because performance impact is insignificant.
|
|
//--- Update X. Exit if needed.
|
|
feasold=FeasibilityError(ce,x,nmain,nslack,k,tmpk);
|
|
feasnew=FeasibilityError(ce,xa,nmain,nslack,k,tmpk);
|
|
if(feasnew>=(feasold+infeasibilityincreasetolerance))
|
|
break;
|
|
x=xa;
|
|
if(stage1isover)
|
|
break;
|
|
}
|
|
//--- Stage 2: gradient projection algorithm (GPA)
|
|
//--- * calculate feasibility error (with respect to linear equality constraints)
|
|
//--- * calculate gradient G of F, project it into feasible area (G => PG)
|
|
//--- * exit if norm(PG) is exactly zero or feasibility error is smaller than EpsC
|
|
//--- * let XM be exact minimum of F along -PG (XM may be infeasible).
|
|
//--- calculate MaxStepLen = largest step in direction of -PG which retains feasibility.
|
|
//--- * perform bounded step from X to XN:
|
|
//--- a) XN=XM (if XM is feasible)
|
|
//--- b) XN=X-MaxStepLen*PG (otherwise)
|
|
//--- * X := XN
|
|
//--- * stop after specified number of iterations or when no new constraints was activated
|
|
//--- NOTES:
|
|
//--- * grad(F) = (CE'*CE)*x - (b'*CE)^T
|
|
//--- * CE[i] denotes I-th row of CE
|
|
//--- * XM = X+stp*(-PG) where stp=(grad(F(X)),PG)/(CE*PG,CE*PG).
|
|
//--- Here PG is a projected gradient, but in fact it can be arbitrary non-zero
|
|
//--- direction vector - formula for minimum of F along PG still will be correct.
|
|
werechangesinconstraints=false;
|
|
for(gparuns=1; gparuns<=k; gparuns++)
|
|
{
|
|
//--- calculate feasibility error and G
|
|
FeasibilityErrorGrad(ce,x,nmain,nslack,k,feaserr,g,tmpk);
|
|
//--- project G, filter it (strip numerical noise)
|
|
pg=g;
|
|
ProjectGradientIntoBC(x,pg,bndl,havebndl,bndu,havebndu,nmain,nslack);
|
|
FilterDirection(pg,x,bndl,havebndl,bndu,havebndu,s,nmain,nslack,1.0E-9);
|
|
for(i=0; i<nmain+nslack; i++)
|
|
{
|
|
if(CMath::Sqr(colnorms[i])!=0.0)
|
|
pg.Mul(i,1.0/CMath::Sqr(colnorms[i]));
|
|
else
|
|
pg.Set(i,0.0);
|
|
}
|
|
//--- Check GNorm and feasibility.
|
|
//--- Exit when GNorm is exactly zero.
|
|
pgnorm=MathSqrt(pg.Dot(pg));
|
|
if(pgnorm==0.0)
|
|
{
|
|
result=(feaserr<=epsi);
|
|
return(result);
|
|
}
|
|
//--- calculate planned step length
|
|
vn=g.Dot(pg);
|
|
CAblas::RMatrixGemVect(k,nmain+nslack,1.0,ce,0,0,0,pg,0,0.0,tmpk,0);
|
|
vd=tmpk.Dot(tmpk);
|
|
stp=vn/vd;
|
|
//--- Calculate step bound.
|
|
//--- Perform bounded step and post-process it
|
|
CalculateStepBound(x,pg,-1.0,bndl,havebndl,bndu,havebndu,nmain,nslack,vartofreeze,valtofreeze,maxsteplen);
|
|
if(vartofreeze>=0 && maxsteplen==0.0)
|
|
{
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(vartofreeze>=0)
|
|
v=MathMin(stp,maxsteplen);
|
|
else
|
|
v=stp;
|
|
xn=x.ToVector()-pg*v;
|
|
PostProcessBoundedStep(xn,x,bndl,havebndl,bndu,havebndu,nmain,nslack,vartofreeze,valtofreeze,v,maxsteplen);
|
|
//--- update X
|
|
//--- check stopping criteria
|
|
werechangesinconstraints=(werechangesinconstraints || NumberOfChangedConstraints(xn,x,bndl,havebndl,bndu,havebndu,nmain,nslack)>0);
|
|
x=xn;
|
|
gpaits++;
|
|
if(!werechangesinconstraints)
|
|
break;
|
|
}
|
|
//--- Stage 3: decide to stop algorithm or not to stop
|
|
//--- 1. we can stop when last GPA run did NOT changed constraints status.
|
|
//--- It means that we've found final set of the active constraints even
|
|
//--- before GPA made its run. And it means that Newton step moved us to
|
|
//--- the minimum subject to the present constraints.
|
|
//--- Depending on feasibility error, True or False is returned.
|
|
feaserr=FeasibilityError(ce,x,nmain,nslack,k,tmpk);
|
|
feaserr1=feaserr;
|
|
if(feaserr1>=(feaserr0-infeasibilityincreasetolerance))
|
|
badits++;
|
|
else
|
|
badits=0;
|
|
if(feaserr<=epsi)
|
|
itswithintolerance++;
|
|
else
|
|
itswithintolerance=0;
|
|
if((!werechangesinconstraints || itswithintolerance>=maxitswithintolerance) || badits>=maxbadits)
|
|
{
|
|
result=(feaserr<=epsi);
|
|
return(result);
|
|
}
|
|
itscount++;
|
|
}
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function checks that input derivatives are right. First it |
|
|
//| scales parameters DF0 and DF1 from segment [A; B] to [0; 1]. Then|
|
|
//| it builds Hermite spline and derivative of it in 0.5. Search |
|
|
//| scale as Max(DF0, DF1, | F0 - F1 |). Right derivative has |
|
|
//| to satisfy condition: |
|
|
//| | H - F | / S <= 0, 001, | H'-F' | / S <= 0, 001. |
|
|
//| INPUT PARAMETERS: |
|
|
//| F0 - function's value in X-TestStep point; |
|
|
//| DF0 - derivative's value in X-TestStep point; |
|
|
//| F1 - function's value in X+TestStep point; |
|
|
//| DF1 - derivative's value in X+TestStep point; |
|
|
//| F - testing function's value; |
|
|
//| DF - testing derivative's value; |
|
|
//| Width - width of verification segment. |
|
|
//| RESULT: |
|
|
//| If input derivatives is right then function returns true, else |
|
|
//| function returns false. |
|
|
//+------------------------------------------------------------------+
|
|
bool COptServ::DerivativeCheck(double f0,double df0,double f1,
|
|
double df1,double f,double df,
|
|
double width)
|
|
{
|
|
//--- create variables
|
|
double s=0;
|
|
double h=0;
|
|
double dh=0;
|
|
//--- Rescale input data to [0,1]
|
|
df=width*df;
|
|
df0=width*df0;
|
|
df1=width*df1;
|
|
//--- Compute error scale, two sources are used:
|
|
//--- * magnitudes of derivatives and secants
|
|
//--- * magnitudes of input data times sqrt(machine_epsilon)
|
|
s=0.0;
|
|
s=MathMax(s,MathAbs(df0));
|
|
s=MathMax(s,MathAbs(df1));
|
|
s=MathMax(s,MathAbs(f1-f0));
|
|
s=MathMax(s,MathSqrt(CMath::m_machineepsilon)*MathAbs(f0));
|
|
s=MathMax(s,MathSqrt(CMath::m_machineepsilon)*MathAbs(f1));
|
|
//--- Compute H and dH/dX at the middle of interval
|
|
h=0.5*(f0+f1)+0.125*(df0-df1);
|
|
dh=1.5*(f1-f0)-0.250*(df0+df1);
|
|
//--- Check
|
|
if(s!=0.0)
|
|
{
|
|
if((MathAbs(h-f)/s)>0.001 || (MathAbs(dh-df)/s)>0.001)
|
|
return(false);
|
|
}
|
|
else
|
|
{
|
|
if((double)(h-f)!=0.0 || (double)(dh-df)!=0.0)
|
|
return(false);
|
|
}
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Having quadratic target function |
|
|
//| f(x)=0.5*x'*A*x+b'*x+penaltyfactor*0.5*(C*x-b)'*(C*x-b) |
|
|
//| and its parabolic model along direction D |
|
|
//| F(x0 + alpha*D) = D2 * alpha ^ 2 + D1 * alpha |
|
|
//| this function estimates numerical errors in the coefficients of |
|
|
//| the model. |
|
|
//| It is important that this function does NOT calculate D1/D2 - it |
|
|
//| only estimates numerical errors introduced during evaluation and |
|
|
//| compares their magnitudes against magnitudes of numerical errors.|
|
|
//| As result, one of three outcomes is returned for each |
|
|
//| coefficient: |
|
|
//| *"true" coefficient is almost surely positive |
|
|
//| *"true" coefficient is almost surely negative |
|
|
//| * numerical errors in coefficient are so large that it can |
|
|
//| not be reliably distinguished from zero |
|
|
//| INPUT PARAMETERS: |
|
|
//| AbsASum - SUM( | A.Get(i,j) |) |
|
|
//| AbsASum2 - SUM(A.Get(i,j) ^ 2) |
|
|
//| MB - max( | B |) |
|
|
//| MX - max( | X |) |
|
|
//| MD - max( | D |) |
|
|
//| D1 - linear coefficient |
|
|
//| D2 - quadratic coefficient |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D1Est - estimate of D1 sign, accounting for possible |
|
|
//| numerical errors: |
|
|
//| *>0 means "almost surely positive"(D1 > 0 |
|
|
//| and large) |
|
|
//| *<0 means "almost surely negative"(D1 < 0 |
|
|
//| and large) |
|
|
//| *=0 means "pessimistic estimate of numerical|
|
|
//| errors in D1 is larger than magnitude of|
|
|
//| D1 itself; it is impossible to reliably |
|
|
//| distinguish D1 from zero". |
|
|
//| D2Est - estimate of D2 sign, accounting for possible |
|
|
//| numerical errors: |
|
|
//| *>0 means "almost surely positive"(D2 > 0 |
|
|
//| and large) |
|
|
//| *<0 means "almost surely negative"(D2 < 0 |
|
|
//| and large) |
|
|
//| *=0 means "pessimistic estimate of numerical|
|
|
//| errors in D2 is larger than magnitude of|
|
|
//| D2 itself; it is impossible to reliably |
|
|
//| distinguish D2 from zero". |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::EstimateParabolicModel(double absasum,double absasum2,
|
|
double mx,double mb,
|
|
double md,double d1,
|
|
double d2,int &d1est,
|
|
int &d2est)
|
|
{
|
|
//--- create variables
|
|
double d1esterror=0;
|
|
double d2esterror=0;
|
|
double eps=0;
|
|
double e1=0;
|
|
double e2=0;
|
|
d1est=0;
|
|
d2est=0;
|
|
//--- Error estimates:
|
|
//--- * error in D1=d'*(A*x+b) is estimated as
|
|
//--- ED1 = eps*MAX_ABS(D)*(MAX_ABS(X)*ENORM(A)+MAX_ABS(B))
|
|
//--- * error in D2=0.5*d'*A*d is estimated as
|
|
//--- ED2 = eps*MAX_ABS(D)^2*ENORM(A)
|
|
//--- Here ENORM(A) is some pseudo-norm which reflects the way numerical
|
|
//--- error accumulates during addition. Two ways of accumulation are
|
|
//--- possible - worst case (errors always increase) and mean-case (errors
|
|
//--- may cancel each other). We calculate geometrical average of both:
|
|
//--- * ENORM_WORST(A) = SUM(|A[i,j]|) error in N-term sum grows as O(N)
|
|
//--- * ENORM_MEAN(A) = SQRT(SUM(A[i,j]^2)) error in N-term sum grows as O(sqrt(N))
|
|
//--- * ENORM(A) = SQRT(ENORM_WORST(A),ENORM_MEAN(A))
|
|
eps=4*CMath::m_machineepsilon;
|
|
e1=eps*md*(mx*absasum+mb);
|
|
e2=eps*md*(mx*MathSqrt(absasum2)+mb);
|
|
d1esterror=MathSqrt(e1*e2);
|
|
if(MathAbs(d1)<=d1esterror)
|
|
d1est=0;
|
|
else
|
|
d1est=(int)MathSign(d1);
|
|
e1=eps*md*md*absasum;
|
|
e2=eps*md*md*MathSqrt(absasum2);
|
|
d2esterror=MathSqrt(e1*e2);
|
|
if(MathAbs(d2)<=d2esterror)
|
|
d2est=0;
|
|
else
|
|
d2est=(int)MathSign(d2);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates inexact rank - K preconditioner for |
|
|
//| Hessian matrix H = D + W'*C*W, where: |
|
|
//| * H is a Hessian matrix, which is approximated by D / W / C |
|
|
//| * D is a diagonal matrix with positive entries |
|
|
//| * W is a rank - K correction |
|
|
//| * C is a diagonal factor of rank - K correction |
|
|
//| This preconditioner is inexact but fast - it requires O(N*K) time|
|
|
//| to be applied. Its main purpose - to be used in barrier / penalty|
|
|
//| / AUL methods, where ill - conditioning is created by combination|
|
|
//| of two factors: |
|
|
//| * simple bounds on variables => ill - conditioned D |
|
|
//| * general barrier / penalty => correction W with large |
|
|
//| coefficient C(makes problem ill - conditioned) but W itself |
|
|
//| is well conditioned. |
|
|
//| Preconditioner P is calculated by artificially constructing a set|
|
|
//| of BFGS updates which tries to reproduce behavior of H: |
|
|
//| * Sk = Wk(k - th row of W) |
|
|
//| * Yk = (D + Wk'*Ck*Wk)*Sk |
|
|
//| * Yk / Sk are reordered by ascending of C[k] * norm(Wk) ^ 2 |
|
|
//| Here we assume that rows of Wk are orthogonal or nearly |
|
|
//| orthogonal, which allows us to have O(N * K + K^2) update instead|
|
|
//| of O(N * K ^ 2) one. Reordering of updates is essential for |
|
|
//| having good performance on non-orthogonal problems (updates which|
|
|
//| do not add much of curvature are added first, and updates which |
|
|
//| add very large eigenvalues are added last and override effect of |
|
|
//| the first updates). |
|
|
//| On input this function takes direction S and components of H. |
|
|
//| On output it returns inv(H) * S |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::InexactLBFGSPreconditioner(CRowDouble &s,
|
|
int n,
|
|
CRowDouble &d,
|
|
CRowDouble &c,
|
|
CMatrixDouble &w,
|
|
int k,
|
|
CPrecBufLBFGS &buf)
|
|
{
|
|
//--- create variables
|
|
int idx=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double vx=0;
|
|
double vy=0;
|
|
int i_=0;
|
|
//--- allocate
|
|
buf.m_norms.Resize(k);
|
|
buf.m_alpha.Resize(k);
|
|
buf.m_rho.Resize(k);
|
|
buf.m_yk.Resize(k,n);
|
|
buf.m_idx.Resize(k);
|
|
//--- Check inputs
|
|
if(!CAp::Assert(d.Min()>0.0,"InexactLBFGSPreconditioner: D[]<=0"))
|
|
return;
|
|
if(!CAp::Assert(c.Min()>=0.0,"InexactLBFGSPreconditioner: C[]<0"))
|
|
return;
|
|
//--- Reorder linear terms according to increase of second derivative.
|
|
//--- Fill Norms[] array.
|
|
vector<double> temp;
|
|
for(idx=0; idx<k; idx++)
|
|
{
|
|
temp=w[idx];
|
|
temp.Resize(n);
|
|
v=temp.Dot(temp);
|
|
buf.m_norms.Set(idx,v*c[idx]);
|
|
buf.m_idx.Set(idx,idx);
|
|
}
|
|
CTSort::TagSortFastI(buf.m_norms,buf.m_idx,buf.m_bufa,buf.m_bufb,k);
|
|
//--- Apply updates
|
|
for(idx=0; idx<k; idx++)
|
|
{
|
|
//--- Select update to perform (ordered by ascending of second derivative)
|
|
i=buf.m_idx[idx];
|
|
//--- Calculate YK and Rho
|
|
temp=w[i];
|
|
temp.Resize(n);
|
|
v0=temp.Dot(temp);
|
|
v=v0*c[i];
|
|
buf.m_yk.Row(i,(d+v)*temp);
|
|
v=temp.Dot(buf.m_yk[i]+0);
|
|
v1=CAblasF::RDotRR(n,buf.m_yk,i,buf.m_yk,i);
|
|
if(v>0.0 && (v0*v1)>0.0 && (v/MathSqrt(v0*v1))>(n*10.0*CMath::m_machineepsilon))
|
|
buf.m_rho.Set(i,1/v);
|
|
else
|
|
buf.m_rho.Set(i,0.0);
|
|
}
|
|
for(idx=k-1; idx>=0; idx--)
|
|
{
|
|
//--- Select update to perform (ordered by ascending of second derivative)
|
|
i=buf.m_idx[idx];
|
|
temp=w[i];
|
|
temp.Resize(n);
|
|
//--- Calculate Alpha[] according to L-BFGS algorithm
|
|
//--- and update S[]
|
|
v=s.Dot(temp);
|
|
v*=buf.m_rho[i];
|
|
buf.m_alpha.Set(i,v);
|
|
s-=buf.m_yk[i]*v;
|
|
}
|
|
s/=d;
|
|
for(idx=0; idx<k; idx++)
|
|
{
|
|
//--- Select update to perform (ordered by ascending of second derivative)
|
|
i=buf.m_idx[idx];
|
|
temp=w[i];
|
|
temp.Resize(n);
|
|
//--- Calculate Beta according to L-BFGS algorithm
|
|
//--- and update S[]
|
|
v=s.Dot(buf.m_yk[i]+0);
|
|
v=buf.m_alpha[i]-buf.m_rho[i]*v;
|
|
s+=temp*v;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function prepares exact low-rank preconditioner for Hessian |
|
|
//| matrix H = D + W'*C*W, where: |
|
|
//| * H is a Hessian matrix, which is approximated by D / W / C |
|
|
//| * D is a diagonal matrix with positive entries |
|
|
//| * W is a rank - K correction |
|
|
//| * C is a diagonal factor of rank - K correction, positive |
|
|
//| semidefinite |
|
|
//| This preconditioner is exact but relatively slow - it requires |
|
|
//| O(N * K ^ 2) time to be prepared and O(N*K) time to be applied. |
|
|
//| It is calculated with the help of Woodbury matrix identity. |
|
|
//| It should be used as follows: |
|
|
//| * PrepareLowRankPreconditioner() call PREPARES data structure |
|
|
//| * subsequent calls to ApplyLowRankPreconditioner() APPLY |
|
|
//| preconditioner to user - specified search direction. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::PrepareLowRankPreconditioner(CRowDouble &d,
|
|
CRowDouble &c,
|
|
CMatrixDouble &w,
|
|
int n,
|
|
int k,
|
|
CPrecBufLowRank &buf)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
bool b;
|
|
//--- Check inputs
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0"))
|
|
return;
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": N<=0"))
|
|
return;
|
|
if(!CAp::Assert(d.Min()>0.0,__FUNCTION__+": D[]<=0"))
|
|
return;
|
|
if(!CAp::Assert(c.Min()>=0.0,__FUNCTION__+": C[]<0"))
|
|
return;
|
|
//--- Prepare buffer structure; skip zero entries of update.
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_d,n);
|
|
CApServ::RMatrixSetLengthAtLeast(buf.m_v,k,n);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_bufc,k);
|
|
CApServ::RMatrixSetLengthAtLeast(buf.m_bufw,k+1,n);
|
|
buf.m_n=n;
|
|
buf.m_k=0;
|
|
for(i=0; i<k; i++)
|
|
{
|
|
//--- Estimate magnitude of update row; skip zero rows (either W or C are zero)
|
|
vector<double> temp=w[i];
|
|
v=temp.Dot(temp)*c[i];
|
|
if(v==0.0)
|
|
continue;
|
|
//--- check
|
|
if(!CAp::Assert(v>0.0,__FUNCTION__+": internal error"))
|
|
return;
|
|
//--- Copy non-zero update to buffer
|
|
buf.m_bufc.Set(buf.m_k,c[i]);
|
|
buf.m_v.Row(buf.m_k,temp);
|
|
buf.m_bufw.Row(buf.m_k,temp);
|
|
buf.m_k++;
|
|
}
|
|
//--- Reset K (for convenience)
|
|
k=buf.m_k;
|
|
//--- Prepare diagonal factor; quick exit for K=0
|
|
buf.m_d=d.Pow(-1.0)+0;
|
|
if(k==0)
|
|
return;
|
|
//--- Use Woodbury matrix identity
|
|
buf.m_bufz=matrix<double>::Zeros(k,k);
|
|
buf.m_bufc.Resize(k);
|
|
buf.m_bufz.Diag(buf.m_bufc.Pow(-1.0)+0);
|
|
buf.m_bufw.Row(k,d.Pow(-0.5)+0);
|
|
for(i=0; i<k; i++)
|
|
for(j=0; j<n; j++)
|
|
buf.m_bufw.Mul(i,j,buf.m_bufw.Get(k,j));
|
|
CAblas::RMatrixGemm(k,k,n,1.0,buf.m_bufw,0,0,0,buf.m_bufw,0,0,1,1.0,buf.m_bufz,0,0);
|
|
b=CTrFac::SPDMatrixCholeskyRec(buf.m_bufz,0,k,true,buf.m_tmp);
|
|
//--- check
|
|
if(!CAp::Assert(b,__FUNCTION__+": internal error (Cholesky failure)"))
|
|
return;
|
|
CAblas::RMatrixLeftTrsM(k,n,buf.m_bufz,0,0,true,false,1,buf.m_v,0,0);
|
|
for(i=0; i<k; i++)
|
|
for(j=0; j<n; j++)
|
|
buf.m_v.Mul(i,j,buf.m_d[j]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function apply exact low - rank preconditioner prepared by |
|
|
//| PrepareLowRankPreconditioner function (see its comments for more |
|
|
//| information). |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::ApplyLowRankPreconditioner(CRowDouble &s,
|
|
CPrecBufLowRank &buf)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
int n=buf.m_n;
|
|
int k=buf.m_k;
|
|
|
|
buf.m_tmp=vector<double>::Zeros(n);
|
|
|
|
for(i=0; i<n; i++)
|
|
buf.m_tmp.Set(i,buf.m_d[i]*s[i]);
|
|
for(i=0; i<k; i++)
|
|
{
|
|
v=CAblasF::RDotVR(n,s,buf.m_v,i);
|
|
for(j=0; j<n; j++)
|
|
buf.m_tmp.Add(j,-buf.m_v.Get(i,j)*v);
|
|
}
|
|
for(i=0; i<n; i++)
|
|
s.Set(i,buf.m_tmp[i]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine initializes smoothness monitor at the beginning |
|
|
//| of the optimization session. It requires variable scales to be |
|
|
//| passed. |
|
|
//| It is possible to perform "dummy" initialization with N = K = 0. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::SmoothnessMonitorInit(CSmoothnessMonitor &monitor,
|
|
CRowDouble &s,
|
|
int n,
|
|
int k,
|
|
bool checksmoothness)
|
|
{
|
|
monitor.m_n=n;
|
|
monitor.m_k=k;
|
|
monitor.m_checksmoothness=checksmoothness;
|
|
monitor.m_linesearchspoiled=false;
|
|
monitor.m_linesearchstarted=false;
|
|
monitor.m_enqueuedcnt=0;
|
|
monitor.m_sortedcnt=0;
|
|
monitor.m_s=s;
|
|
monitor.m_nonc0currentrating=0.0;
|
|
monitor.m_nonc1currentrating=0.0;
|
|
COptGuardApi::OptGuardInitInternal(monitor.m_rep,n,k);
|
|
monitor.m_nonc0strrating=0.0;
|
|
monitor.m_nonc0lngrating=-CMath::m_maxrealnumber;
|
|
monitor.m_nonc0strrep.m_positive=false;
|
|
monitor.m_nonc0lngrep.m_positive=false;
|
|
monitor.m_nonc1test0strrating=0.0;
|
|
monitor.m_nonc1test0lngrating=-CMath::m_maxrealnumber;
|
|
monitor.m_nonc1test0strrep.m_positive=false;
|
|
monitor.m_nonc1test0lngrep.m_positive=false;
|
|
monitor.m_nonc1test1strrating=0.0;
|
|
monitor.m_nonc1test1lngrating=-CMath::m_maxrealnumber;
|
|
monitor.m_nonc1test1strrep.m_positive=false;
|
|
monitor.m_nonc1test1lngrep.m_positive=false;
|
|
monitor.m_badgradhasxj=false;
|
|
monitor.m_rstateg0.ia.Resize(5);
|
|
monitor.m_rstateg0.ra.Resize(4);
|
|
monitor.m_rstateg0.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine starts line search |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::SmoothnessMonitorStartLineSearch(CSmoothnessMonitor &monitor,
|
|
CRowDouble &x,
|
|
CRowDouble &fi,
|
|
CMatrixDouble &jac)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
int n=monitor.m_n;
|
|
int k=monitor.m_k;
|
|
//--- Skip if inactive or spoiled by NAN
|
|
if(!monitor.m_checksmoothness)
|
|
return;
|
|
v=0;
|
|
for(i=0; i<n; i++)
|
|
v=0.5*v+x[i];
|
|
for(i=0; i<k; i++)
|
|
v=0.5*v+fi[i];
|
|
for(i=0; i<k; i++)
|
|
for(j=0; j<n; j++)
|
|
v=0.5*v+jac.Get(i,j);
|
|
if(!MathIsValidNumber(v))
|
|
{
|
|
monitor.m_linesearchspoiled=true;
|
|
return;
|
|
}
|
|
//--- Finalize previous line search
|
|
if(monitor.m_enqueuedcnt>0)
|
|
SmoothnessMonitorFinalizeLineSearch(monitor);
|
|
//--- Store initial point
|
|
monitor.m_linesearchstarted=true;
|
|
monitor.m_enqueuedcnt=1;
|
|
monitor.m_enqueuedx=x;
|
|
monitor.m_enqueuedfunc=fi;
|
|
monitor.m_enqueuedjac=jac;
|
|
monitor.m_enqueuedstp.Resize(monitor.m_enqueuedcnt);
|
|
monitor.m_enqueuedx.Resize(monitor.m_enqueuedcnt*n);
|
|
monitor.m_enqueuedfunc.Resize(monitor.m_enqueuedcnt*k);
|
|
monitor.m_enqueuedjac.Resize(monitor.m_enqueuedcnt*k,n);
|
|
monitor.m_enqueuedstp.Set(0,0.0);
|
|
//--- Initialize sorted representation
|
|
monitor.m_sortedstp.Resize(1);
|
|
monitor.m_sortedidx.Resize(1);
|
|
monitor.m_sortedstp.Set(0,0.0);
|
|
monitor.m_sortedidx.Set(0,0);
|
|
monitor.m_sortedcnt=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine starts line search for a scalar function - |
|
|
//| convenience wrapper for ....StartLineSearch() with unscaled |
|
|
//| variables. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::SmoothnessMonitorStartLineSearch1u(CSmoothnessMonitor &monitor,
|
|
CRowDouble &s,
|
|
CRowDouble &invs,
|
|
CRowDouble &x,
|
|
double f0,
|
|
CRowDouble &j0)
|
|
{
|
|
//--- create variables
|
|
int n=monitor.m_n;
|
|
int k=monitor.m_k;
|
|
if(!monitor.m_checksmoothness)
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k==1,__FUNCTION__+": K<>1"))
|
|
return;
|
|
|
|
monitor.m_xu=x*invs+0;
|
|
monitor.m_xu.Resize(n);
|
|
monitor.m_f0.Resize(1);
|
|
monitor.m_j0.Resize(1,n);
|
|
monitor.m_j0.Row(0,j0*s+0);
|
|
monitor.m_f0.Set(0,f0);
|
|
SmoothnessMonitorStartLineSearch(monitor,monitor.m_xu,monitor.m_f0,monitor.m_j0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine enqueues one more trial point |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::SmoothnessMonitorEnqueuePoint(CSmoothnessMonitor &monitor,
|
|
CRowDouble &d,
|
|
double stp,
|
|
CRowDouble &x,
|
|
CRowDouble &fi,
|
|
CMatrixDouble &jac)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
int enqueuedcnt=0;
|
|
int sortedcnt=0;
|
|
bool hasduplicates;
|
|
int funcidx=0;
|
|
int stpidx=0;
|
|
double f0=0;
|
|
double f1=0;
|
|
double f2=0;
|
|
double f3=0;
|
|
double f4=0;
|
|
double noise0=0;
|
|
double noise1=0;
|
|
double noise2=0;
|
|
double noise3=0;
|
|
double rating=0;
|
|
double lipschitz=0;
|
|
double nrm=0;
|
|
double lengthrating=0;
|
|
int n=monitor.m_n;
|
|
int k=monitor.m_k;
|
|
//--- Skip if inactive or spoiled by NAN
|
|
if(!monitor.m_checksmoothness || monitor.m_linesearchspoiled || !monitor.m_linesearchstarted)
|
|
return;
|
|
v=stp;
|
|
for(i=0; i<n; i++)
|
|
v=0.5*v+x[i];
|
|
for(i=0; i<n; i++)
|
|
v=0.5*v+d[i];
|
|
for(i=0; i<k; i++)
|
|
v=0.5*v+fi[i];
|
|
for(i=0; i<k; i++)
|
|
for(j=0; j<n; j++)
|
|
v=0.5*v+jac.Get(i,j);
|
|
if(!MathIsValidNumber(v))
|
|
{
|
|
monitor.m_linesearchspoiled=true;
|
|
return;
|
|
}
|
|
//--- Enqueue
|
|
monitor.m_enqueuedcnt++;
|
|
enqueuedcnt=monitor.m_enqueuedcnt;
|
|
monitor.m_dcur=d;
|
|
monitor.m_dcur.Resize(n);
|
|
monitor.m_enqueuedstp.Resize(enqueuedcnt);
|
|
monitor.m_enqueuedx.Resize(enqueuedcnt*n);
|
|
monitor.m_enqueuedfunc.Resize(enqueuedcnt*k);
|
|
monitor.m_enqueuedjac.Resize(enqueuedcnt*k,n);
|
|
monitor.m_enqueuedstp.Set(enqueuedcnt-1,stp);
|
|
for(j=0; j<n; j++)
|
|
monitor.m_enqueuedx.Set((enqueuedcnt-1)*n+j,x[j]);
|
|
for(i=0; i<k; i++)
|
|
monitor.m_enqueuedfunc.Set((enqueuedcnt-1)*k+i,fi[i]);
|
|
for(i=0; i<k; i++)
|
|
for(j=0; j<n; j++)
|
|
monitor.m_enqueuedjac.Set((enqueuedcnt-1)*k+i,j,jac.Get(i,j));
|
|
//--- Update sorted representation: insert to the end, reorder
|
|
sortedcnt=monitor.m_sortedcnt;
|
|
hasduplicates=false;
|
|
for(i=0; i<sortedcnt; i++)
|
|
hasduplicates=(hasduplicates || monitor.m_sortedstp[i]==stp);
|
|
if(!hasduplicates)
|
|
{
|
|
monitor.m_sortedcnt++;
|
|
sortedcnt=monitor.m_sortedcnt;
|
|
monitor.m_sortedstp.Resize(sortedcnt);
|
|
monitor.m_sortedidx.Resize(sortedcnt);
|
|
monitor.m_sortedstp.Set(sortedcnt-1,stp);
|
|
monitor.m_sortedidx.Set(sortedcnt-1,enqueuedcnt-1);
|
|
for(i=sortedcnt-2; i>=0; i--)
|
|
{
|
|
if(monitor.m_sortedstp[i]<=monitor.m_sortedstp[i+1])
|
|
break;
|
|
monitor.m_sortedstp.Swap(i,i+1);
|
|
monitor.m_sortedidx.Swap(i,i+1);
|
|
}
|
|
}
|
|
//--- Scan sorted representation, check for C0 and C1 continuity
|
|
//--- violations.
|
|
monitor.m_f.Resize(sortedcnt);
|
|
monitor.m_g.Resize(sortedcnt*n);
|
|
for(funcidx=0; funcidx<k; funcidx++)
|
|
{
|
|
//--- Fetch current function and its gradient to the contiguous storage
|
|
for(i=0; i<sortedcnt; i++)
|
|
{
|
|
monitor.m_f.Set(i,monitor.m_enqueuedfunc[monitor.m_sortedidx[i]*k+funcidx]);
|
|
for(j=0; j<n; j++)
|
|
monitor.m_g.Set(i*n+j,monitor.m_enqueuedjac.Get(monitor.m_sortedidx[i]*k+funcidx,j));
|
|
}
|
|
//--- Check C0 continuity.
|
|
//--- The basis approach is that we find appropriate candidate point
|
|
//--- (either a local minimum along the line - for target; or an interval
|
|
//--- where function sign is changed - for constraints), calculate left
|
|
//--- and right estimates of the Lipschitz constant (slopes between points
|
|
//--- #0 and #1, #2 and #3), and then calculate slope between points #1 and
|
|
//--- #2 and compare it with left/right estimates.
|
|
//--- The actual approach is a bit more complex to account for different
|
|
//--- sources of numerical noise and different false positive scenarios.
|
|
if(funcidx==0)
|
|
{
|
|
for(stpidx=0; stpidx<sortedcnt-3; stpidx++)
|
|
{
|
|
f0=monitor.m_f[stpidx+0];
|
|
f1=monitor.m_f[stpidx+1];
|
|
f2=monitor.m_f[stpidx+2];
|
|
f3=monitor.m_f[stpidx+3];
|
|
noise0=m_ognoiselevelf*MathMax(MathAbs(f0),1.0);
|
|
noise1=m_ognoiselevelf*MathMax(MathAbs(f1),1.0);
|
|
noise2=m_ognoiselevelf*MathMax(MathAbs(f2),1.0);
|
|
noise3=m_ognoiselevelf*MathMax(MathAbs(f3),1.0);
|
|
if(!(f1<f0+(noise0+noise1) && f1<f2))
|
|
continue;
|
|
TestC0Continuity(f0,f1,f2,f3,noise0,noise1,noise2,noise3,monitor.m_sortedstp[stpidx+1]-monitor.m_sortedstp[stpidx+0],monitor.m_sortedstp[stpidx+2]-monitor.m_sortedstp[stpidx+1],monitor.m_sortedstp[stpidx+3]-monitor.m_sortedstp[stpidx+2],false,rating,lipschitz);
|
|
if(rating>m_ogminrating0)
|
|
{
|
|
//--- Store to total report
|
|
monitor.m_rep.m_nonc0suspected=true;
|
|
monitor.m_rep.m_nonc0test0positive=true;
|
|
if(rating>monitor.m_nonc0currentrating)
|
|
{
|
|
monitor.m_nonc0currentrating=rating;
|
|
monitor.m_rep.m_nonc0lipschitzc=lipschitz;
|
|
monitor.m_rep.m_nonc0fidx=funcidx;
|
|
}
|
|
//--- Store to "strongest" report
|
|
if(rating>monitor.m_nonc0strrating)
|
|
{
|
|
monitor.m_nonc0strrating=rating;
|
|
monitor.m_nonc0strrep.m_positive=true;
|
|
monitor.m_nonc0strrep.m_fidx=funcidx;
|
|
monitor.m_nonc0strrep.m_n=n;
|
|
monitor.m_nonc0strrep.m_cnt=sortedcnt;
|
|
monitor.m_nonc0strrep.m_stpidxa=stpidx+0;
|
|
monitor.m_nonc0strrep.m_stpidxb=stpidx+3;
|
|
monitor.m_nonc0strrep.m_x0.Resize(n);
|
|
monitor.m_nonc0strrep.m_d.Resize(n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
monitor.m_nonc0strrep.m_x0.Set(i,monitor.m_enqueuedx[monitor.m_sortedidx[0]*n+i]);
|
|
monitor.m_nonc0strrep.m_d.Set(i,monitor.m_dcur[i]);
|
|
}
|
|
monitor.m_nonc0strrep.m_stp=monitor.m_sortedstp;
|
|
monitor.m_nonc0strrep.m_f=monitor.m_f;
|
|
monitor.m_nonc0strrep.m_stp.Resize(sortedcnt);
|
|
monitor.m_nonc0strrep.m_f.Resize(sortedcnt);
|
|
}
|
|
//--- Store to "longest" report
|
|
nrm=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
nrm+=CMath::Sqr(monitor.m_enqueuedx[monitor.m_sortedidx[0]*n+i] -
|
|
monitor.m_enqueuedx[monitor.m_sortedidx[sortedcnt-1]*n+i]);
|
|
}
|
|
nrm=MathSqrt(nrm);
|
|
nrm=MathMin(nrm,1.0);
|
|
nrm=CApServ::Coalesce(nrm,CMath::m_machineepsilon);
|
|
lengthrating=sortedcnt+MathLog(nrm)/MathLog(100);
|
|
if(lengthrating>monitor.m_nonc0lngrating)
|
|
{
|
|
monitor.m_nonc0lngrating=lengthrating;
|
|
monitor.m_nonc0lngrep.m_positive=true;
|
|
monitor.m_nonc0lngrep.m_fidx=funcidx;
|
|
monitor.m_nonc0lngrep.m_n=n;
|
|
monitor.m_nonc0lngrep.m_cnt=sortedcnt;
|
|
monitor.m_nonc0lngrep.m_stpidxa=stpidx+0;
|
|
monitor.m_nonc0lngrep.m_stpidxb=stpidx+3;
|
|
monitor.m_nonc0lngrep.m_x0.Resize(n);
|
|
monitor.m_nonc0lngrep.m_d.Resize(n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
monitor.m_nonc0lngrep.m_x0.Set(i,monitor.m_enqueuedx[monitor.m_sortedidx[0]*n+i]);
|
|
monitor.m_nonc0lngrep.m_d.Set(i,monitor.m_dcur[i]);
|
|
}
|
|
monitor.m_nonc0lngrep.m_stp=monitor.m_sortedstp;
|
|
monitor.m_nonc0lngrep.m_f=monitor.m_f;
|
|
monitor.m_nonc0lngrep.m_stp.Resize(sortedcnt);
|
|
monitor.m_nonc0lngrep.m_f.Resize(sortedcnt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- C1 continuity test #0
|
|
for(stpidx=0; stpidx<sortedcnt-6; stpidx++)
|
|
{
|
|
//--- Fetch function values
|
|
f2=monitor.m_f[stpidx+2];
|
|
f3=monitor.m_f[stpidx+3];
|
|
f4=monitor.m_f[stpidx+4];
|
|
noise2=m_ognoiselevelf*MathMax(MathAbs(f2),1.0);
|
|
noise3=m_ognoiselevelf*MathMax(MathAbs(f3),1.0);
|
|
//--- Decide whether we want to test this interval or not; for target
|
|
//--- function we test intervals around minimum, for constraints we
|
|
//--- test intervals of sign change.
|
|
if(funcidx==0)
|
|
{
|
|
//--- Skip if not minimum
|
|
if(!(f3<f2+(noise2+noise3) && f3<f4))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//--- Skip if sign does not change
|
|
if(MathSign(f2*f4)>0)
|
|
continue;
|
|
}
|
|
C1ContinuityTest0(monitor,funcidx,stpidx+0,sortedcnt);
|
|
C1ContinuityTest0(monitor,funcidx,stpidx+1,sortedcnt);
|
|
}
|
|
//--- C1 continuity test #1
|
|
for(stpidx=0; stpidx<sortedcnt-3; stpidx++)
|
|
{
|
|
//--- Fetch function values from the interval being tested
|
|
f0=monitor.m_f[stpidx+0];
|
|
f1=monitor.m_f[stpidx+1];
|
|
f2=monitor.m_f[stpidx+2];
|
|
f3=monitor.m_f[stpidx+3];
|
|
noise0=m_ognoiselevelf*MathMax(MathAbs(f0),1.0);
|
|
noise1=m_ognoiselevelf*MathMax(MathAbs(f1),1.0);
|
|
noise2=m_ognoiselevelf*MathMax(MathAbs(f2),1.0);
|
|
noise3=m_ognoiselevelf*MathMax(MathAbs(f3),1.0);
|
|
//--- Decide whether we want to test this interval or not; for target
|
|
//--- function we test intervals around minimum, for constraints we
|
|
//--- test intervals of sign change.
|
|
if(funcidx==0)
|
|
{
|
|
//--- Skip if not minimum
|
|
if(!(f1<f0+(noise0+noise1) && f2<f3+noise2+noise3))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//--- Skip if sign does not change
|
|
if(MathSign(f0*f3)>0)
|
|
continue;
|
|
}
|
|
C1ContinuityTest1(monitor,funcidx,stpidx,sortedcnt);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine enqueues one more trial point for a task with |
|
|
//| scalar function with unscaled variables - a convenience wrapper |
|
|
//| for more general EnqueuePoint() |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::SmoothnessMonitorEnqueuePoint1u(CSmoothnessMonitor &monitor,
|
|
CRowDouble &s,
|
|
CRowDouble &invs,
|
|
CRowDouble &d,
|
|
double stp,
|
|
CRowDouble &x,
|
|
double f0,
|
|
CRowDouble &j0)
|
|
{
|
|
//--- create variables
|
|
int n=monitor.m_n;
|
|
int k=monitor.m_k;
|
|
if(!monitor.m_checksmoothness)
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k==1,__FUNCTION__+": K<>1"))
|
|
return;
|
|
|
|
monitor.m_xu=x*invs+0;
|
|
monitor.m_du=d*invs+0;
|
|
monitor.m_xu.Resize(n);
|
|
monitor.m_du.Resize(n);
|
|
monitor.m_f0.Resize(1);
|
|
monitor.m_j0.Resize(1,n);
|
|
monitor.m_j0.Row(0,j0*s+0);
|
|
monitor.m_f0.Set(0,f0);
|
|
SmoothnessMonitorEnqueuePoint(monitor,monitor.m_du,stp,monitor.m_xu,monitor.m_f0,monitor.m_j0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine finalizes line search |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::SmoothnessMonitorFinalizeLineSearch(CSmoothnessMonitor &monitor)
|
|
{
|
|
//--- As for now - nothing to be done.
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function starts aggressive probing for a range of step |
|
|
//| lengths [0, StpMax]. |
|
|
//| This function stores NValues values per step, with the first one |
|
|
//| (index 0) value being "primary" one (target function / merit |
|
|
//| function) and the rest being supplementary ones. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::SmoothnessMonitorStartProbing(CSmoothnessMonitor &monitor,
|
|
double stpmax,
|
|
int nvalues,
|
|
double stepscale)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(stpmax) && stpmax>0.0,__FUNCTION__+": StpMax<=0"))
|
|
return;
|
|
if(!CAp::Assert(nvalues>=1,__FUNCTION__+": NValues<1"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(stepscale) && (double)(stepscale)>=0.0,__FUNCTION__+": StepScale<0"))
|
|
return;
|
|
|
|
monitor.m_probingnvalues=nvalues;
|
|
monitor.m_probingnstepsstored=0;
|
|
monitor.m_probingstepmax=stpmax;
|
|
monitor.m_probingstepscale=stepscale;
|
|
monitor.m_probingf.Resize(nvalues);
|
|
monitor.m_probingrcomm.ia.Resize(2+1);
|
|
monitor.m_probingrcomm.ra.Resize(3+1);
|
|
monitor.m_probingrcomm.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs aggressive probing. |
|
|
//| After each call it returns step to evaluate in Monitor.ProbingStp|
|
|
//| Load values being probed into Monitor.ProbingF and continue |
|
|
//| iteration. |
|
|
//| Monitor.ProbingF[0] is a special value which is used to guide |
|
|
//| probing process towards discontinuities and nonsmooth points. |
|
|
//+------------------------------------------------------------------+
|
|
bool COptServ::SmoothnessMonitorProbe(CSmoothnessMonitor &monitor)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int idx=0;
|
|
double vlargest=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(monitor.m_probingrcomm.stage>=0)
|
|
{
|
|
i=monitor.m_probingrcomm.ia[0];
|
|
j=monitor.m_probingrcomm.ia[1];
|
|
idx=monitor.m_probingrcomm.ia[2];
|
|
vlargest=monitor.m_probingrcomm.ra[0];
|
|
v=monitor.m_probingrcomm.ra[1];
|
|
v0=monitor.m_probingrcomm.ra[2];
|
|
v1=monitor.m_probingrcomm.ra[3];
|
|
}
|
|
else
|
|
{
|
|
i=359;
|
|
j=-58;
|
|
idx=-919;
|
|
vlargest=-909;
|
|
v=81;
|
|
v0=255;
|
|
v1=74;
|
|
}
|
|
if(monitor.m_probingrcomm.stage==0)
|
|
{
|
|
monitor.m_probingvalues.Row(monitor.m_probingnstepsstored,monitor.m_probingf);
|
|
monitor.m_probingslopes.Row(monitor.m_probingnstepsstored,vector<double>::Zeros(monitor.m_probingnvalues));
|
|
monitor.m_probingnstepsstored++;
|
|
//--- Resort
|
|
for(j=monitor.m_probingnstepsstored-1; j>=1; j--)
|
|
{
|
|
if(monitor.m_probingsteps[j-1]<=monitor.m_probingsteps[j])
|
|
break;
|
|
monitor.m_probingsteps.Swap(j-1,j);
|
|
monitor.m_probingvalues.SwapRows(j-1,j);
|
|
}
|
|
i++;
|
|
}
|
|
else
|
|
//--- Routine body
|
|
i=0;
|
|
if(i>40)
|
|
return(false);
|
|
//--- Increase storage size
|
|
monitor.m_probingsteps.Resize(monitor.m_probingnstepsstored+1);
|
|
monitor.m_probingvalues.Resize(monitor.m_probingnstepsstored+1,monitor.m_probingnvalues);
|
|
monitor.m_probingslopes.Resize(monitor.m_probingnstepsstored+1,monitor.m_probingnvalues);
|
|
//--- Determine probing step length, save step to the end of the storage
|
|
if(i<=10)
|
|
{
|
|
//--- First 11 steps are performed over equidistant grid
|
|
monitor.m_probingstp=(double)i/10.0*monitor.m_probingstepmax;
|
|
}
|
|
else
|
|
{
|
|
//--- Subsequent steps target either points with maximum change in F[0]
|
|
//--- (search for discontinuity) or maximum change in slope of F[0] (search
|
|
//--- for nonsmoothness)
|
|
//--- check
|
|
if(!CAp::Assert(monitor.m_probingnstepsstored>=3,__FUNCTION__+": critical integrity check failed"))
|
|
return(false);
|
|
if(i%2==0)
|
|
{
|
|
//--- Target interval with maximum change in F[0]
|
|
idx=-1;
|
|
vlargest=0;
|
|
for(j=0; j<=monitor.m_probingnstepsstored-2; j++)
|
|
{
|
|
v=MathAbs(monitor.m_probingvalues.Get(j+1,0)-monitor.m_probingvalues.Get(j,0));
|
|
if(idx<0 || v>vlargest)
|
|
{
|
|
idx=j;
|
|
vlargest=v;
|
|
}
|
|
}
|
|
monitor.m_probingstp=0.5*(monitor.m_probingsteps[idx]+monitor.m_probingsteps[idx+1]);
|
|
}
|
|
else
|
|
{
|
|
//--- Target interval [J,J+2] with maximum change in slope of F[0], select
|
|
//--- subinterval [J,J+1] or [J+1,J+2] with maximum length.
|
|
idx=-1;
|
|
vlargest=0;
|
|
for(j=0; j<=monitor.m_probingnstepsstored-3; j++)
|
|
{
|
|
v0=(monitor.m_probingvalues.Get(j+1,0)-monitor.m_probingvalues.Get(j+0,0))/(monitor.m_probingsteps[j+1]-monitor.m_probingsteps[j+0]+CMath::m_machineepsilon);
|
|
v1=(monitor.m_probingvalues.Get(j+2,0)-monitor.m_probingvalues.Get(j+1,0))/(monitor.m_probingsteps[j+2]-monitor.m_probingsteps[j+1]+CMath::m_machineepsilon);
|
|
v=MathAbs(v0-v1);
|
|
if(idx<0 || v>vlargest)
|
|
{
|
|
idx=j;
|
|
vlargest=v;
|
|
}
|
|
}
|
|
if((double)(monitor.m_probingsteps[idx+2]-monitor.m_probingsteps[idx+1])>(double)(monitor.m_probingsteps[idx+1]-monitor.m_probingsteps[idx+0]))
|
|
{
|
|
monitor.m_probingstp=0.5*(monitor.m_probingsteps[idx+2]+monitor.m_probingsteps[idx+1]);
|
|
}
|
|
else
|
|
{
|
|
monitor.m_probingstp=0.5*(monitor.m_probingsteps[idx+1]+monitor.m_probingsteps[idx+0]);
|
|
}
|
|
}
|
|
}
|
|
monitor.m_probingsteps.Set(monitor.m_probingnstepsstored,monitor.m_probingstp);
|
|
//--- Retrieve user values
|
|
monitor.m_probingrcomm.stage=0;
|
|
//--- Saving State
|
|
monitor.m_probingrcomm.ia.Set(0,i);
|
|
monitor.m_probingrcomm.ia.Set(1,j);
|
|
monitor.m_probingrcomm.ia.Set(2,idx);
|
|
monitor.m_probingrcomm.ra.Set(0,vlargest);
|
|
monitor.m_probingrcomm.ra.Set(1,v);
|
|
monitor.m_probingrcomm.ra.Set(2,v0);
|
|
monitor.m_probingrcomm.ra.Set(3,v1);
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function prints probing results to trace log. |
|
|
//| Tracing is performed using fixed width for all columns, so you |
|
|
//| may print a header before printing trace - and reasonably expect |
|
|
//| that its width will match that of the trace. This function |
|
|
//| promises that it wont change trace output format without |
|
|
//| introducing breaking changes into its signature. |
|
|
//| NOTE: this function ALWAYS tries to print results; it is caller's|
|
|
//| responsibility to decide whether he needs tracing or not. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::SmoothnessMonitorTraceProbingResults(CSmoothnessMonitor &monitor)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double steplen=0;
|
|
//--- Compute slopes
|
|
for(i=0; i<=monitor.m_probingnstepsstored-2; i++)
|
|
{
|
|
for(j=0; j<=monitor.m_probingnvalues-1; j++)
|
|
{
|
|
steplen=(monitor.m_probingsteps[i+1]-monitor.m_probingsteps[i]+100.0*CMath::m_machineepsilon)*(monitor.m_probingstepscale+CMath::m_machineepsilon);
|
|
monitor.m_probingslopes.Set(i,j,(monitor.m_probingvalues.Get(i+1,j)-monitor.m_probingvalues.Get(i,j))/steplen);
|
|
}
|
|
}
|
|
if(monitor.m_probingnstepsstored>=1)
|
|
for(j=0; j<monitor.m_probingnvalues; j++)
|
|
monitor.m_probingslopes.Set(monitor.m_probingnstepsstored-1,j,monitor.m_probingslopes.Get(MathMax(monitor.m_probingnstepsstored-2,0),j));
|
|
//--- Print to trace log
|
|
CAp::Trace("*** ----------");
|
|
for(j=0; j<monitor.m_probingnvalues; j++)
|
|
CAp::Trace("-------------------------");
|
|
CAp::Trace("\n");
|
|
for(i=0; i<monitor.m_probingnstepsstored; i++)
|
|
{
|
|
CAp::Trace(StringFormat("*** | %0.4f |",monitor.m_probingsteps[i]));
|
|
for(j=0; j<monitor.m_probingnvalues; j++)
|
|
CAp::Trace(StringFormat(" %11.3E %10.2E |",monitor.m_probingvalues.Get(i,j) - monitor.m_probingvalues.Get(0,j),monitor.m_probingslopes.Get(i,j)));
|
|
CAp::Trace("\n");
|
|
}
|
|
CAp::Trace("*** ----------");
|
|
for(j=0; j<monitor.m_probingnvalues; j++)
|
|
CAp::Trace("-------------------------");
|
|
CAp::Trace("\n");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine tells monitor to output trace Info. |
|
|
//| If CallerSuggestsTrace = True, monitor ALWAYS prints trace, |
|
|
//| even if no suspicions were raised during optimization. If |
|
|
//| CallerSuggestsTrace = False, the monitor will print trace only |
|
|
//| if: |
|
|
//| * trace was requested by trace tag 'OPTGUARD' AND suspicious |
|
|
//| points were found during optimization |
|
|
//| * trace was requested by trace tag 'OPTGUARD.ALWAYS' - always |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::SmoothnessMonitorTraceStatus(CSmoothnessMonitor &monitor,
|
|
bool callersuggeststrace)
|
|
{
|
|
//--- create variables
|
|
bool needreport;
|
|
bool needxdreport;
|
|
bool suspicionsraised;
|
|
int i=0;
|
|
double slope=0;
|
|
//--- Do we need trace report?
|
|
suspicionsraised=(monitor.m_rep.m_nonc0suspected || monitor.m_rep.m_nonc1suspected || monitor.m_rep.m_badgradsuspected);
|
|
needreport=false;
|
|
needreport=needreport || callersuggeststrace;
|
|
needreport=needreport || CAp::IsTraceEnabled("OPTGUARD.ALWAYS");
|
|
needreport=needreport || (CAp::IsTraceEnabled("OPTGUARD") && suspicionsraised);
|
|
if(!needreport)
|
|
return;
|
|
needxdreport=needreport && CAp::IsTraceEnabled("OPTIMIZERS.X");
|
|
|
|
CAp::Trace("\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
CAp::Trace("//--- OPTGUARD INTEGRITY CHECKER REPORT //\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
if(!suspicionsraised)
|
|
{
|
|
CAp::Trace("> no discontinuity/nonsmoothness/bad-gradient suspicions were raised during optimization\n");
|
|
return;
|
|
}
|
|
if(monitor.m_rep.m_nonc0suspected)
|
|
CAp::Trace("> [WARNING] suspected discontinuity (aka C0-discontinuity)\n");
|
|
if(monitor.m_rep.m_nonc1suspected)
|
|
CAp::Trace("> [WARNING] suspected nonsmoothness (aka C1-discontinuity)\n");
|
|
CAp::Trace("> printing out test reports...\n");
|
|
if(monitor.m_rep.m_nonc0suspected && monitor.m_rep.m_nonc0test0positive)
|
|
{
|
|
CAp::Trace("> printing out discontinuity test #0 report:\n");
|
|
CAp::Trace("*** -------------------------------------------------------\n");
|
|
CAp::Trace("*** | Test #0 for discontinuity was triggered (this test |\n");
|
|
CAp::Trace("*** | analyzes changes in function values). See below for |\n");
|
|
CAp::Trace("*** | detailed Info: |\n");
|
|
CAp::Trace(StringFormat("*** | * function index: %10.m_d",monitor.m_nonc0lngrep.m_fidx));
|
|
if(monitor.m_nonc0lngrep.m_fidx==0)
|
|
CAp::Trace(" (target) |\n");
|
|
else
|
|
CAp::Trace(" (constraint) |\n");
|
|
CAp::Trace(StringFormat("*** | * F() Lipschitz const: %10.2E |\n",monitor.m_rep.m_nonc0lipschitzc));
|
|
CAp::Trace("*** | Printing out log of suspicious line search XK+Stp*D |\n");
|
|
CAp::Trace("*** | Look for abrupt changes in slope. |\n");
|
|
if(!needxdreport)
|
|
{
|
|
CAp::Trace("*** | NOTE: XK and D are not printed by default. If you |\n");
|
|
CAp::Trace("*** | need them,add trace tag OPTIMIZERS.X |\n");
|
|
}
|
|
CAp::Trace("*** -------------------------------------------------------\n");
|
|
CAp::Trace("*** | step along D | delta F | slope |\n");
|
|
CAp::Trace("*** ------------------------------------------------------|\n");
|
|
for(i=0; i<=monitor.m_nonc0lngrep.m_cnt-1; i++)
|
|
{
|
|
slope=monitor.m_nonc0lngrep.m_f[MathMin(i+1,monitor.m_nonc0lngrep.m_cnt-1)]-monitor.m_nonc0lngrep.m_f[i];
|
|
slope=slope/(1.0e-15+monitor.m_nonc0lngrep.m_stp[MathMin(i+1,monitor.m_nonc0lngrep.m_cnt-1)]-monitor.m_nonc0lngrep.m_stp[i]);
|
|
CAp::Trace(StringFormat("*** | %13.5E | %13.5E | %13.5E |",monitor.m_nonc0lngrep.m_stp[i],monitor.m_nonc0lngrep.m_f[i] - monitor.m_nonc0lngrep.m_f[0],slope));
|
|
if(i>=monitor.m_nonc0lngrep.m_stpidxa && i<=monitor.m_nonc0lngrep.m_stpidxb)
|
|
{
|
|
CAp::Trace(" <---");
|
|
}
|
|
CAp::Trace("\n");
|
|
}
|
|
CAp::Trace("*** ------------------------------------------------------|\n");
|
|
if(needxdreport)
|
|
{
|
|
CAp::Trace("*** > printing raw variables\n");
|
|
CAp::Trace("*** XK = ");
|
|
CApServ::TraceVectorUnscaledUnshiftedAutopRec(monitor.m_nonc0lngrep.m_x0,monitor.m_n,monitor.m_s,true,monitor.m_s,false);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("*** D = ");
|
|
CApServ::TraceVectorUnscaledUnshiftedAutopRec(monitor.m_nonc0lngrep.m_d,monitor.m_n,monitor.m_s,true,monitor.m_s,false);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("*** > printing scaled variables (values are divided by user-specified scales)\n");
|
|
CAp::Trace("*** XK = ");
|
|
CApServ::TraceVectorAutopRec(monitor.m_nonc0lngrep.m_x0,0,monitor.m_n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("*** D = ");
|
|
CApServ::TraceVectorAutopRec(monitor.m_nonc0lngrep.m_d,0,monitor.m_n);
|
|
CAp::Trace("\n");
|
|
}
|
|
}
|
|
if(monitor.m_rep.m_nonc1suspected && monitor.m_rep.m_nonc1test0positive)
|
|
{
|
|
CAp::Trace("> printing out nonsmoothness test #0 report:\n");
|
|
CAp::Trace("*** -------------------------------------------------------\n");
|
|
CAp::Trace("*** | Test #0 for nonsmoothness was triggered (this test |\n");
|
|
CAp::Trace("*** | analyzes changes in function values and ignores |\n");
|
|
CAp::Trace("*** | gradient Info). See below for detailed Info: |\n");
|
|
CAp::Trace(StringFormat("*** | * function index: %10.m_d",monitor.m_nonc1test0lngrep.m_fidx));
|
|
if(monitor.m_nonc1test0lngrep.m_fidx==0)
|
|
{
|
|
CAp::Trace(" (target) |\n");
|
|
}
|
|
else
|
|
{
|
|
CAp::Trace(" (constraint) |\n");
|
|
}
|
|
CAp::Trace(StringFormat("*** | * dF/dX Lipschitz const: %10.2E |\n",monitor.m_rep.m_nonc1lipschitzc));
|
|
CAp::Trace("*** | Printing out log of suspicious line search XK+Stp*D |\n");
|
|
CAp::Trace("*** | Look for abrupt changes in slope. |\n");
|
|
if(!needxdreport)
|
|
{
|
|
CAp::Trace("*** | NOTE: XK and D are not printed by default. If you |\n");
|
|
CAp::Trace("*** | need them,add trace tag OPTIMIZERS.X |\n");
|
|
}
|
|
CAp::Trace("*** -------------------------------------------------------\n");
|
|
CAp::Trace("*** | step along D | delta F | slope |\n");
|
|
CAp::Trace("*** ------------------------------------------------------|\n");
|
|
for(i=0; i<=monitor.m_nonc1test0lngrep.m_cnt-1; i++)
|
|
{
|
|
slope=monitor.m_nonc1test0lngrep.m_f[MathMin(i+1,monitor.m_nonc1test0lngrep.m_cnt-1)]-monitor.m_nonc1test0lngrep.m_f[i];
|
|
slope=slope/(1.0e-15+monitor.m_nonc1test0lngrep.m_stp[MathMin(i+1,monitor.m_nonc1test0lngrep.m_cnt-1)]-monitor.m_nonc1test0lngrep.m_stp[i]);
|
|
CAp::Trace(StringFormat("*** | %13.5E | %13.5E | %13.5E |",monitor.m_nonc1test0lngrep.m_stp[i],monitor.m_nonc1test0lngrep.m_f[i] - monitor.m_nonc1test0lngrep.m_f[0],slope));
|
|
if(i>=monitor.m_nonc1test0lngrep.m_stpidxa && i<=monitor.m_nonc1test0lngrep.m_stpidxb)
|
|
{
|
|
CAp::Trace(" <---");
|
|
}
|
|
CAp::Trace("\n");
|
|
}
|
|
CAp::Trace("*** ------------------------------------------------------|\n");
|
|
if(needxdreport)
|
|
{
|
|
CAp::Trace("*** > printing raw variables\n");
|
|
CAp::Trace("*** XK = ");
|
|
CApServ::TraceVectorUnscaledUnshiftedAutopRec(monitor.m_nonc1test0lngrep.m_x0,monitor.m_n,monitor.m_s,true,monitor.m_s,false);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("*** D = ");
|
|
CApServ::TraceVectorUnscaledUnshiftedAutopRec(monitor.m_nonc1test0lngrep.m_d,monitor.m_n,monitor.m_s,true,monitor.m_s,false);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("*** > printing scaled variables (values are divided by user-specified scales)\n");
|
|
CAp::Trace("*** XK = ");
|
|
CApServ::TraceVectorAutopRec(monitor.m_nonc1test0lngrep.m_x0,0,monitor.m_n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("*** D = ");
|
|
CApServ::TraceVectorAutopRec(monitor.m_nonc1test0lngrep.m_d,0,monitor.m_n);
|
|
CAp::Trace("\n");
|
|
}
|
|
}
|
|
if(monitor.m_rep.m_nonc1suspected && monitor.m_rep.m_nonc1test1positive)
|
|
{
|
|
CAp::Trace("> printing out nonsmoothness test #1 report:\n");
|
|
CAp::Trace("*** -------------------------------------------------------\n");
|
|
CAp::Trace("*** | Test #1 for nonsmoothness was triggered (this test |\n");
|
|
CAp::Trace("*** | analyzes changes in gradient components). See below |\n");
|
|
CAp::Trace("*** | for detailed Info: |\n");
|
|
CAp::Trace(StringFormat("*** | * function index: %10.m_d",monitor.m_nonc1test1lngrep.m_fidx));
|
|
if(monitor.m_nonc1test1lngrep.m_fidx==0)
|
|
{
|
|
CAp::Trace(" (target) |\n");
|
|
}
|
|
else
|
|
{
|
|
CAp::Trace(" (constraint) |\n");
|
|
}
|
|
CAp::Trace(StringFormat("*** | * variable index I: %10.m_d |\n",monitor.m_nonc1test1lngrep.m_vidx));
|
|
CAp::Trace(StringFormat("*** | * dF/dX Lipschitz const: %10.2E |\n",monitor.m_rep.m_nonc1lipschitzc));
|
|
CAp::Trace("*** | Printing out log of suspicious line search XK+Stp*D |\n");
|
|
CAp::Trace("*** | Look for abrupt changes in slope. |\n");
|
|
if(!needxdreport)
|
|
{
|
|
CAp::Trace("*** | NOTE: XK and D are not printed by default. If you |\n");
|
|
CAp::Trace("*** | need them,add trace tag OPTIMIZERS.X |\n");
|
|
}
|
|
CAp::Trace("*** -------------------------------------------------------\n");
|
|
CAp::Trace("*** | step along D | delta Gi | slope |\n");
|
|
CAp::Trace("*** ------------------------------------------------------|\n");
|
|
for(i=0; i<monitor.m_nonc1test1lngrep.m_cnt; i++)
|
|
{
|
|
slope=monitor.m_nonc1test1lngrep.m_g[MathMin(i+1,monitor.m_nonc1test1lngrep.m_cnt-1)]-monitor.m_nonc1test1lngrep.m_g[i];
|
|
slope=slope/(1.0e-15+monitor.m_nonc1test1lngrep.m_stp[MathMin(i+1,monitor.m_nonc1test1lngrep.m_cnt-1)]-monitor.m_nonc1test1lngrep.m_stp[i]);
|
|
CAp::Trace(StringFormat("*** | %13.5E | %13.5E | %13.5E |",monitor.m_nonc1test1lngrep.m_stp[i],monitor.m_nonc1test1lngrep.m_g[i] - monitor.m_nonc1test1lngrep.m_g[0],slope));
|
|
if(i>=monitor.m_nonc1test1lngrep.m_stpidxa && i<=monitor.m_nonc1test1lngrep.m_stpidxb)
|
|
{
|
|
CAp::Trace(" <---");
|
|
}
|
|
CAp::Trace("\n");
|
|
}
|
|
CAp::Trace("*** ------------------------------------------------------|\n");
|
|
if(needxdreport)
|
|
{
|
|
CAp::Trace("*** > printing raw variables\n");
|
|
CAp::Trace("*** XK = ");
|
|
CApServ::TraceVectorUnscaledUnshiftedAutopRec(monitor.m_nonc1test1lngrep.m_x0,monitor.m_n,monitor.m_s,true,monitor.m_s,false);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("*** D = ");
|
|
CApServ::TraceVectorUnscaledUnshiftedAutopRec(monitor.m_nonc1test1lngrep.m_d,monitor.m_n,monitor.m_s,true,monitor.m_s,false);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("*** > printing scaled variables (values are divided by user-specified scales)\n");
|
|
CAp::Trace("*** XK = ");
|
|
CApServ::TraceVectorAutopRec(monitor.m_nonc1test1lngrep.m_x0,0,monitor.m_n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("*** D = ");
|
|
CApServ::TraceVectorAutopRec(monitor.m_nonc1test1lngrep.m_d,0,monitor.m_n);
|
|
CAp::Trace("\n");
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine exports report to user - readable representation |
|
|
//| (all arrays are forced to have exactly same size as needed; |
|
|
//| unused arrays are set to zero length). |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::SmoothnessMonitorExportReport(CSmoothnessMonitor &monitor,
|
|
COptGuardReport &rep)
|
|
{
|
|
//--- Finalize last line search, just to be sure
|
|
if(monitor.m_enqueuedcnt>0)
|
|
SmoothnessMonitorFinalizeLineSearch(monitor);
|
|
//--- Export report
|
|
COptGuardApi::OptGuardExportReport(monitor.m_rep,monitor.m_n,monitor.m_k,monitor.m_badgradhasxj,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Check numerical gradient at point X0 (unscaled variables!), with |
|
|
//| optional box constraints [BndL, BndU](if HasBoxConstraints=True) |
|
|
//| and with scale vector S[]. |
|
|
//| Step S[i] * TestStep is performed along I-th variable. |
|
|
//| NeedFiJ rcomm protocol is used to request derivative information.|
|
|
//| Box constraints BndL / BndU are expected to be feasible. It is |
|
|
//| possible to have BndL = BndU. |
|
|
//+------------------------------------------------------------------+
|
|
bool COptServ::SmoothnessMonitorCheckGradientATX0(CSmoothnessMonitor &monitor,
|
|
CRowDouble &unscaledx0,
|
|
CRowDouble &s,
|
|
CRowDouble &bndl,
|
|
CRowDouble &bndu,
|
|
bool hasboxconstraints,
|
|
double teststep)
|
|
{
|
|
//--- create variables
|
|
int n=0;
|
|
int k=0;
|
|
int i=0;
|
|
int j=0;
|
|
int varidx=0;
|
|
double v=0;
|
|
double vp=0;
|
|
double vm=0;
|
|
double vc=0;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(monitor.m_rstateg0.stage>=0)
|
|
{
|
|
n=monitor.m_rstateg0.ia[0];
|
|
k=monitor.m_rstateg0.ia[1];
|
|
i=monitor.m_rstateg0.ia[2];
|
|
j=monitor.m_rstateg0.ia[3];
|
|
varidx=monitor.m_rstateg0.ia[4];
|
|
v=monitor.m_rstateg0.ra[0];
|
|
vp=monitor.m_rstateg0.ra[1];
|
|
vm=monitor.m_rstateg0.ra[2];
|
|
vc=monitor.m_rstateg0.ra[3];
|
|
}
|
|
else
|
|
{
|
|
n=-788;
|
|
k=809;
|
|
i=205;
|
|
j=-838;
|
|
varidx=939;
|
|
v=-526;
|
|
vp=763;
|
|
vm=-541;
|
|
vc=-698;
|
|
}
|
|
//--- select
|
|
switch(monitor.m_rstateg0.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
//--- Routine body
|
|
default:
|
|
n=monitor.m_n;
|
|
k=monitor.m_k;
|
|
monitor.m_needfij=false;
|
|
//--- Quick exit
|
|
if(n<=0 || k<=0 || !MathIsValidNumber(teststep) || teststep==0.0)
|
|
return(false);
|
|
teststep=MathAbs(teststep);
|
|
//--- Allocate storage
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_x,n);
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_fi,k);
|
|
CApServ::RMatrixSetLengthAtLeast(monitor.m_j,k,n);
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_xbase,n);
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_fbase,k);
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_fm,k);
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_fc,k);
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_fp,k);
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_jm,k);
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_jc,k);
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_jp,k);
|
|
CApServ::RMatrixSetLengthAtLeast(monitor.m_jbaseusr,k,n);
|
|
CApServ::RMatrixSetLengthAtLeast(monitor.m_jbasenum,k,n);
|
|
CApServ::RVectorSetLengthAtLeast(monitor.m_rep.m_badgradxbase,n);
|
|
CApServ::RMatrixSetLengthAtLeast(monitor.m_rep.m_badgraduser,k,n);
|
|
CApServ::RMatrixSetLengthAtLeast(monitor.m_rep.m_badgradnum,k,n);
|
|
//--- Set XBase/Jacobian presence flag
|
|
monitor.m_badgradhasxj=true;
|
|
//--- Determine reference point, compute function vector and user-supplied Jacobian
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=unscaledx0[i];
|
|
if((hasboxconstraints && MathIsValidNumber(bndl[i])) && v<bndl[i])
|
|
v=bndl[i];
|
|
if((hasboxconstraints && MathIsValidNumber(bndu[i])) && v>bndu[i])
|
|
v=bndu[i];
|
|
monitor.m_xbase.Set(i,v);
|
|
monitor.m_rep.m_badgradxbase.Set(i,v);
|
|
monitor.m_x.Set(i,v);
|
|
}
|
|
monitor.m_needfij=true;
|
|
monitor.m_rstateg0.stage=0;
|
|
label=-1;
|
|
break;
|
|
}
|
|
//--- main loop
|
|
while(label>=0)
|
|
switch(label)
|
|
{
|
|
case 0:
|
|
monitor.m_needfij=false;
|
|
monitor.m_fbase=monitor.m_fi;
|
|
monitor.m_jbaseusr=monitor.m_j;
|
|
monitor.m_rep.m_badgraduser=monitor.m_j;
|
|
//--- Check Jacobian column by column
|
|
varidx=0;
|
|
case 4:
|
|
if(varidx>n-1)
|
|
{
|
|
label=6;
|
|
break;
|
|
}
|
|
//--- Determine test location.
|
|
v=monitor.m_xbase[varidx];
|
|
vm=v-s[varidx]*teststep;
|
|
vp=v+s[varidx]*teststep;
|
|
if((hasboxconstraints && MathIsValidNumber(bndl[varidx])) && vm<bndl[varidx])
|
|
vm=bndl[varidx];
|
|
if((hasboxconstraints && MathIsValidNumber(bndu[varidx])) && vp>bndu[varidx])
|
|
vp=bndu[varidx];
|
|
vc=vm+(vp-vm)/2;
|
|
//--- Quickly skip fixed variables
|
|
if(vm==vp || vc==vm || vc==vp)
|
|
{
|
|
monitor.m_rep.m_badgradnum.Col(varidx,vector<double>::Zeros(k));
|
|
label=5;
|
|
break;
|
|
}
|
|
//--- Compute F/J at three trial points
|
|
monitor.m_x=monitor.m_xbase;
|
|
monitor.m_x.Set(varidx,vm);
|
|
monitor.m_needfij=true;
|
|
monitor.m_rstateg0.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
monitor.m_needfij=false;
|
|
monitor.m_fm=monitor.m_fi;
|
|
monitor.m_jm=monitor.m_j.Col(varidx)+0;
|
|
monitor.m_x=monitor.m_xbase;
|
|
monitor.m_x.Set(varidx,vc);
|
|
monitor.m_needfij=true;
|
|
monitor.m_rstateg0.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
monitor.m_needfij=false;
|
|
monitor.m_fc=monitor.m_fi;
|
|
monitor.m_jc=monitor.m_j.Col(varidx)+0;
|
|
monitor.m_x=monitor.m_xbase;
|
|
monitor.m_x.Set(varidx,vp);
|
|
monitor.m_needfij=true;
|
|
monitor.m_rstateg0.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
monitor.m_needfij=false;
|
|
monitor.m_fp=monitor.m_fi;
|
|
monitor.m_jp=monitor.m_j.Col(varidx)+0;
|
|
//--- Check derivative
|
|
for(i=0; i<k; i++)
|
|
{
|
|
monitor.m_rep.m_badgradnum.Set(i,varidx,(monitor.m_fp[i]-monitor.m_fm[i])/(vp-vm));
|
|
if(!DerivativeCheck(monitor.m_fm[i],monitor.m_jm[i]*s[varidx],monitor.m_fp[i],monitor.m_jp[i]*s[varidx],monitor.m_fc[i],monitor.m_jc[i]*s[varidx],(vp-vm)/s[varidx]))
|
|
{
|
|
monitor.m_rep.m_badgradsuspected=true;
|
|
monitor.m_rep.m_badgradfidx=i;
|
|
monitor.m_rep.m_badgradvidx=varidx;
|
|
}
|
|
}
|
|
case 5:
|
|
varidx=varidx+1;
|
|
label=4;
|
|
break;
|
|
case 6:
|
|
return(false);
|
|
}
|
|
//--- Saving State
|
|
monitor.m_rstateg0.ia.Set(0,n);
|
|
monitor.m_rstateg0.ia.Set(1,k);
|
|
monitor.m_rstateg0.ia.Set(2,i);
|
|
monitor.m_rstateg0.ia.Set(3,j);
|
|
monitor.m_rstateg0.ia.Set(4,varidx);
|
|
monitor.m_rstateg0.ra.Set(0,v);
|
|
monitor.m_rstateg0.ra.Set(1,vp);
|
|
monitor.m_rstateg0.ra.Set(2,vm);
|
|
monitor.m_rstateg0.ra.Set(3,vc);
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates feasibility error(square root of sum of |
|
|
//| squared errors) for a Kx(NMain + NSlack) system of linear |
|
|
//| equalities. |
|
|
//| INPUT PARAMETERS: |
|
|
//| CE - set of K equality constraints, |
|
|
//| array[K, NMain + NSlack + 1] |
|
|
//| X - candidate point, array [NMain + NSlack] |
|
|
//| NMain - number of primary variables |
|
|
//| NSlack - number of slack variables |
|
|
//| K - number of constraints |
|
|
//| Tmp0 - possible preallocated buffer, automatically resized|
|
|
//| RESULT: Sqrt(SUM(Err ^ 2)) |
|
|
//+------------------------------------------------------------------+
|
|
double COptServ::FeasibilityError(CMatrixDouble &ce,
|
|
CRowDouble &x,
|
|
int nmain,
|
|
int nslack,
|
|
int k,
|
|
CRowDouble &tmp0)
|
|
{
|
|
double result=0;
|
|
|
|
tmp0=ce.Col(nmain+nslack)*(-1.0);
|
|
CAblas::RMatrixGemVect(k,nmain+nslack,1.0,ce,0,0,0,x,0,1.0,tmp0,0);
|
|
result=tmp0.Dot(tmp0);
|
|
result=MathSqrt(result);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates feasibility error(square root of sum of |
|
|
//| squared errors) for a Kx(NMain + NSlack) system of linear |
|
|
//| equalities and error gradient(with respect to x) |
|
|
//| INPUT PARAMETERS: |
|
|
//| CE - set of K equality constraints, |
|
|
//| array[K, NMain + NSlack + 1] |
|
|
//| X - candidate point, array [NMain + NSlack] |
|
|
//| NMain - number of primary variables |
|
|
//| NSlack - number of slack variables |
|
|
//| K - number of constraints |
|
|
//| Grad - preallocated array[NMain + NSlack] |
|
|
//| Tmp0 - possible preallocated buffer, automatically resized|
|
|
//| RESULT: |
|
|
//| Err - Sqrt(SUM(Err ^ 2)) |
|
|
//| Grad - error gradient with respect to X, |
|
|
//| array[NMain + NSlack] |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::FeasibilityErrorGrad(CMatrixDouble &ce,
|
|
CRowDouble &x,
|
|
int nmain,
|
|
int nslack,
|
|
int k,
|
|
double &err,
|
|
CRowDouble &grad,
|
|
CRowDouble &tmp0)
|
|
{
|
|
err=0;
|
|
//--- check
|
|
if(!CAp::Assert(grad.Size()>=nmain+nslack,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
|
|
tmp0.Resize(k);
|
|
CAblas::RMatrixGemVect(k,nmain+nslack,1.0,ce,0,0,0,x,0,0.0,tmp0,0);
|
|
tmp0-=ce.Col(nmain+nslack)+0;
|
|
err=tmp0.Dot(tmp0);
|
|
err=MathSqrt(err);
|
|
CAblas::RMatrixGemVect(nmain+nslack,k,1.0,ce,0,0,1,tmp0,0,0.0,grad,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine checks C0 continuity and returns continuity |
|
|
//| rating (normalized value, with values above 50 - 500 being good |
|
|
//| indication of the discontinuity) and Lipschitz constant. |
|
|
//| An interval between F1 and F2 is tested for(dis) continuity. |
|
|
//| Per - point noise estimates are provided. Delta[i] is a step from|
|
|
//| F[i] to F[i + 1]. |
|
|
//| ApplySpecialCorrection parameter should be set to True if you use|
|
|
//| this function to estimate continuity of the model around minimum;|
|
|
//| it adds special correction which helps to detect "max(0,1/x)" - |
|
|
//| like discontinuities. Without this correction algorithm will |
|
|
//| still work, but will be a bit less powerful. Do not use this |
|
|
//| correction for situations when you want to estimate continuity |
|
|
//| around some non - extremal point - it may result in spurious |
|
|
//| discontinuities being reported. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::TestC0Continuity(double f0,double f1,double f2,
|
|
double f3,double noise0,double noise1,
|
|
double noise2,double noise3,double delta0,
|
|
double delta1,double delta2,
|
|
bool applyspecialcorrection,
|
|
double &rating,double &lipschitz)
|
|
{
|
|
//--- create variables
|
|
double lipschitz01=0;
|
|
double lipschitz12=0;
|
|
double lipschitz23=0;
|
|
rating=0;
|
|
lipschitz=0;
|
|
//--- Compute Lipschitz constant for the interval [0,1],
|
|
//--- add noise correction in order to get increased estimate (makes
|
|
//--- comparison below more conservative).
|
|
lipschitz01=(MathAbs(f1-f0)+(noise0+noise1))/delta0;
|
|
//--- Compute Lipschitz constant for the interval [StpIdx+1,StpIdx+2],
|
|
//--- SUBTRACT noise correction in order to get decreased estimate (makes
|
|
//--- comparison below more conservative).
|
|
lipschitz12=MathMax(MathAbs(f2-f1)-(noise1+noise2),0.0)/delta1;
|
|
//--- Compute Lipschitz constant for the interval [StpIdx+2,StpIdx+3]
|
|
//--- using special algorithm:
|
|
//--- a) if F3<F2-Noise23, Lipschitz constant is assumed to be zero
|
|
//--- b) otherwise, we compute Lipschitz constant as usual,
|
|
//--- with noise correction term being added
|
|
//--- We need case (a) because some kinds of discontinuities
|
|
//--- (like one introduced by max(1/x,0)) manifest themselves
|
|
//--- in a quite special way.
|
|
if(applyspecialcorrection && f3<f2-(noise2+noise3))
|
|
lipschitz23=0;
|
|
else
|
|
lipschitz23=(MathAbs(f3-f2)+(noise2+noise3))/delta2;
|
|
//--- Compute rating (ratio of two Lipschitz constants)
|
|
//--- check
|
|
if(!CAp::Assert(MathMax(lipschitz01,lipschitz23)>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
rating=lipschitz12/MathMax(lipschitz01,lipschitz23);
|
|
lipschitz=lipschitz12;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine checks C1 continuity using test #0(function |
|
|
//| values from the line search log are studied, gradient is not |
|
|
//| used). |
|
|
//| An interval between F[StpIdx + 0] and F[StpIdx + 5] is tested for|
|
|
//| continuity. An normalized error metric (Lipschitz constant growth|
|
|
//| for the derivative) for the interval in question is calculated. |
|
|
//| Values above 50 are a good indication of the discontinuity. |
|
|
//| A six - point algorithm is used for testing, so we expect that |
|
|
//| Monitor.F and Monitor.Stp have enough points for this test. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::C1ContinuityTest0(CSmoothnessMonitor &monitor,
|
|
int funcidx,
|
|
int stpidx,
|
|
int sortedcnt)
|
|
{
|
|
//--- create variables
|
|
double f0=0;
|
|
double f1=0;
|
|
double f2=0;
|
|
double f3=0;
|
|
double f4=0;
|
|
double f5=0;
|
|
double noise0=0;
|
|
double noise1=0;
|
|
double noise2=0;
|
|
double noise3=0;
|
|
double noise4=0;
|
|
double noise5=0;
|
|
double delta0=0;
|
|
double delta1=0;
|
|
double delta2=0;
|
|
double delta3=0;
|
|
double delta4=0;
|
|
double d0=0;
|
|
double d1=0;
|
|
double d2=0;
|
|
double d3=0;
|
|
double newnoise0=0;
|
|
double newnoise1=0;
|
|
double newnoise2=0;
|
|
double newnoise3=0;
|
|
double newdelta0=0;
|
|
double newdelta1=0;
|
|
double newdelta2=0;
|
|
double rating=0;
|
|
double lipschitz=0;
|
|
double lengthrating=0;
|
|
int i=0;
|
|
int n=monitor.m_n;
|
|
double nrm=0;
|
|
//--- check
|
|
if(!CAp::Assert(stpidx+5<sortedcnt,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(monitor.m_sortedstp[0]==0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(monitor.m_sortedstp[sortedcnt-1]>0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Fetch F, noise, Delta's
|
|
f0=monitor.m_f[stpidx+0];
|
|
f1=monitor.m_f[stpidx+1];
|
|
f2=monitor.m_f[stpidx+2];
|
|
f3=monitor.m_f[stpidx+3];
|
|
f4=monitor.m_f[stpidx+4];
|
|
f5=monitor.m_f[stpidx+5];
|
|
noise0=m_ognoiselevelf*MathMax(MathAbs(f0),1.0);
|
|
noise1=m_ognoiselevelf*MathMax(MathAbs(f1),1.0);
|
|
noise2=m_ognoiselevelf*MathMax(MathAbs(f2),1.0);
|
|
noise3=m_ognoiselevelf*MathMax(MathAbs(f3),1.0);
|
|
noise4=m_ognoiselevelf*MathMax(MathAbs(f4),1.0);
|
|
noise5=m_ognoiselevelf*MathMax(MathAbs(f5),1.0);
|
|
delta0=monitor.m_sortedstp[stpidx+1]-monitor.m_sortedstp[stpidx+0];
|
|
delta1=monitor.m_sortedstp[stpidx+2]-monitor.m_sortedstp[stpidx+1];
|
|
delta2=monitor.m_sortedstp[stpidx+3]-monitor.m_sortedstp[stpidx+2];
|
|
delta3=monitor.m_sortedstp[stpidx+4]-monitor.m_sortedstp[stpidx+3];
|
|
delta4=monitor.m_sortedstp[stpidx+5]-monitor.m_sortedstp[stpidx+4];
|
|
//--- Differentiate functions, get derivative values and noise
|
|
//--- estimates at points (0+1)/2, (1+2)/2, (3+4)/2, (3+4)/2,
|
|
//--- (4+5)/2. Compute new step values NewDelta[i] and new
|
|
//--- noise estimates.
|
|
d0=(f1-f0)/delta0;
|
|
d1=(f2-f1)/delta1;
|
|
d2=(f4-f3)/delta3;
|
|
d3=(f5-f4)/delta4;
|
|
newnoise0=(noise0+noise1)/delta0;
|
|
newnoise1=(noise1+noise2)/delta1;
|
|
newnoise2=(noise3+noise4)/delta3;
|
|
newnoise3=(noise4+noise5)/delta4;
|
|
newdelta0=0.5*(delta0+delta1);
|
|
newdelta1=0.5*delta1+delta2+0.5*delta3;
|
|
newdelta2=0.5*(delta3+delta4);
|
|
//--- Test with C0 continuity tester. "Special correction" is
|
|
//--- turned off for this test.
|
|
TestC0Continuity(d0,d1,d2,d3,newnoise0,newnoise1,newnoise2,newnoise3,newdelta0,newdelta1,newdelta2,false,rating,lipschitz);
|
|
//--- Store results
|
|
if(rating>m_ogminrating1)
|
|
{
|
|
//--- Store to total report
|
|
monitor.m_rep.m_nonc1test0positive=true;
|
|
if(rating>monitor.m_nonc1currentrating)
|
|
{
|
|
monitor.m_nonc1currentrating=rating;
|
|
monitor.m_rep.m_nonc1suspected=true;
|
|
monitor.m_rep.m_nonc1lipschitzc=lipschitz;
|
|
monitor.m_rep.m_nonc1fidx=funcidx;
|
|
}
|
|
//--- Store to "strongest" report
|
|
if(rating>monitor.m_nonc1test0strrating)
|
|
{
|
|
monitor.m_nonc1test0strrating=rating;
|
|
monitor.m_nonc1test0strrep.m_positive=true;
|
|
monitor.m_nonc1test0strrep.m_fidx=funcidx;
|
|
monitor.m_nonc1test0strrep.m_n=n;
|
|
monitor.m_nonc1test0strrep.m_cnt=sortedcnt;
|
|
monitor.m_nonc1test0strrep.m_stpidxa=stpidx+1;
|
|
monitor.m_nonc1test0strrep.m_stpidxb=stpidx+4;
|
|
monitor.m_nonc1test0strrep.m_d=monitor.m_dcur;
|
|
monitor.m_nonc1test0strrep.m_x0.Resize(n);
|
|
monitor.m_nonc1test0strrep.m_d.Resize(n);
|
|
for(i=0; i<n; i++)
|
|
monitor.m_nonc1test0strrep.m_x0.Set(i,monitor.m_enqueuedx[monitor.m_sortedidx[0]*n+i]);
|
|
monitor.m_nonc1test0strrep.m_stp=monitor.m_sortedstp;
|
|
monitor.m_nonc1test0strrep.m_f=monitor.m_f;
|
|
monitor.m_nonc1test0strrep.m_stp.Resize(sortedcnt);
|
|
monitor.m_nonc1test0strrep.m_f.Resize(sortedcnt);
|
|
}
|
|
//--- Store to "longest" report
|
|
nrm=0;
|
|
for(i=0; i<n; i++)
|
|
nrm+=CMath::Sqr(monitor.m_enqueuedx[monitor.m_sortedidx[0]*n+i]-monitor.m_enqueuedx[monitor.m_sortedidx[sortedcnt-1]*n+i]);
|
|
nrm=MathSqrt(nrm);
|
|
nrm=MathMin(nrm,1.0);
|
|
nrm=CApServ::Coalesce(nrm,CMath::m_machineepsilon);
|
|
lengthrating=sortedcnt+MathLog(nrm)/MathLog(100);
|
|
if(lengthrating>monitor.m_nonc1test0lngrating)
|
|
{
|
|
monitor.m_nonc1test0lngrating=lengthrating;
|
|
monitor.m_nonc1test0lngrep.m_positive=true;
|
|
monitor.m_nonc1test0lngrep.m_fidx=funcidx;
|
|
monitor.m_nonc1test0lngrep.m_n=n;
|
|
monitor.m_nonc1test0lngrep.m_cnt=sortedcnt;
|
|
monitor.m_nonc1test0lngrep.m_stpidxa=stpidx+1;
|
|
monitor.m_nonc1test0lngrep.m_stpidxb=stpidx+4;
|
|
monitor.m_nonc1test0lngrep.m_d=monitor.m_dcur;
|
|
monitor.m_nonc1test0lngrep.m_stp=monitor.m_sortedstp;
|
|
monitor.m_nonc1test0lngrep.m_f=monitor.m_f;
|
|
monitor.m_nonc1test0lngrep.m_x0.Resize(n);
|
|
monitor.m_nonc1test0lngrep.m_d.Resize(n);
|
|
monitor.m_nonc1test0lngrep.m_stp.Resize(sortedcnt);
|
|
monitor.m_nonc1test0lngrep.m_f.Resize(sortedcnt);
|
|
for(i=0; i<n; i++)
|
|
monitor.m_nonc1test0lngrep.m_x0.Set(i,monitor.m_enqueuedx[monitor.m_sortedidx[0]*n+i]);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine checks C1 continuity using test #1(individual |
|
|
//| gradient components from the line search log are studied for |
|
|
//| continuity). |
|
|
//| An interval between F[StpIdx + 0] and F[StpIdx + 3]is tested for|
|
|
//| continuity. An normalized error metric(Lipschitz constant growth |
|
|
//| for the derivative) for the interval in question is calculated. |
|
|
//| Values above 50 are a good indication of the discontinuity. |
|
|
//+------------------------------------------------------------------+
|
|
void COptServ::C1ContinuityTest1(CSmoothnessMonitor &monitor,
|
|
int funcidx,
|
|
int stpidx,
|
|
int sortedcnt)
|
|
{
|
|
//--- create variables
|
|
int n=monitor.m_n;
|
|
int i=0;
|
|
int varidx=0;
|
|
double f0=0;
|
|
double f1=0;
|
|
double f2=0;
|
|
double f3=0;
|
|
double noise0=0;
|
|
double noise1=0;
|
|
double noise2=0;
|
|
double noise3=0;
|
|
double nrm=0;
|
|
double rating=0;
|
|
double lengthrating=0;
|
|
double lipschitz=0;
|
|
//--- check
|
|
if(!CAp::Assert(stpidx+3<sortedcnt,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(monitor.m_sortedstp[0]==0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(monitor.m_sortedstp[sortedcnt-1]>0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Study each component of the gradient in the interval in question
|
|
for(varidx=0; varidx<n; varidx++)
|
|
{
|
|
f0=monitor.m_g[(stpidx+0)*n+varidx];
|
|
f1=monitor.m_g[(stpidx+1)*n+varidx];
|
|
f2=monitor.m_g[(stpidx+2)*n+varidx];
|
|
f3=monitor.m_g[(stpidx+3)*n+varidx];
|
|
noise0=m_ognoiselevelg*MathMax(MathAbs(f0),1.0);
|
|
noise1=m_ognoiselevelg*MathMax(MathAbs(f1),1.0);
|
|
noise2=m_ognoiselevelg*MathMax(MathAbs(f2),1.0);
|
|
noise3=m_ognoiselevelg*MathMax(MathAbs(f3),1.0);
|
|
TestC0Continuity(f0,f1,f2,f3,noise0,noise1,noise2,noise3,monitor.m_sortedstp[stpidx+1]-monitor.m_sortedstp[stpidx+0],monitor.m_sortedstp[stpidx+2]-monitor.m_sortedstp[stpidx+1],monitor.m_sortedstp[stpidx+3]-monitor.m_sortedstp[stpidx+2],false,rating,lipschitz);
|
|
//--- Store results
|
|
if(rating>m_ogminrating1)
|
|
{
|
|
//--- Store to total report
|
|
monitor.m_rep.m_nonc1test1positive=true;
|
|
if(rating>monitor.m_nonc1currentrating)
|
|
{
|
|
monitor.m_nonc1currentrating=rating;
|
|
monitor.m_rep.m_nonc1suspected=true;
|
|
monitor.m_rep.m_nonc1lipschitzc=lipschitz;
|
|
monitor.m_rep.m_nonc1fidx=funcidx;
|
|
}
|
|
//--- Store to "strongest" report
|
|
if(rating>monitor.m_nonc1test1strrating)
|
|
{
|
|
monitor.m_nonc1test1strrating=rating;
|
|
monitor.m_nonc1test1strrep.m_positive=true;
|
|
monitor.m_nonc1test1strrep.m_fidx=funcidx;
|
|
monitor.m_nonc1test1strrep.m_vidx=varidx;
|
|
monitor.m_nonc1test1strrep.m_n=n;
|
|
monitor.m_nonc1test1strrep.m_cnt=sortedcnt;
|
|
monitor.m_nonc1test1strrep.m_stpidxa=stpidx+0;
|
|
monitor.m_nonc1test1strrep.m_stpidxb=stpidx+3;
|
|
monitor.m_nonc1test1strrep.m_d=monitor.m_dcur;
|
|
monitor.m_nonc1test1strrep.m_stp=monitor.m_sortedstp;
|
|
monitor.m_nonc1test1strrep.m_x0.Resize(n);
|
|
monitor.m_nonc1test1strrep.m_d.Resize(n);
|
|
monitor.m_nonc1test1strrep.m_stp.Resize(sortedcnt);
|
|
monitor.m_nonc1test1strrep.m_g.Resize(sortedcnt);
|
|
for(i=0; i<n; i++)
|
|
monitor.m_nonc1test1strrep.m_x0.Set(i,monitor.m_enqueuedx[monitor.m_sortedidx[0]*n+i]);
|
|
for(i=0; i<sortedcnt ; i++)
|
|
monitor.m_nonc1test1strrep.m_g.Set(i,monitor.m_g[i*n+varidx]);
|
|
}
|
|
//--- Store to "longest" report
|
|
nrm=0;
|
|
for(i=0; i<n; i++)
|
|
nrm+=CMath::Sqr(monitor.m_enqueuedx[monitor.m_sortedidx[0]*n+i]-monitor.m_enqueuedx[monitor.m_sortedidx[sortedcnt-1]*n+i]);
|
|
nrm=MathSqrt(nrm);
|
|
nrm=MathMin(nrm,1.0);
|
|
nrm=CApServ::Coalesce(nrm,CMath::m_machineepsilon);
|
|
lengthrating=sortedcnt+MathLog(nrm)/MathLog(100);
|
|
if(lengthrating>monitor.m_nonc1test1lngrating)
|
|
{
|
|
monitor.m_nonc1test1lngrating=lengthrating;
|
|
monitor.m_nonc1test1lngrep.m_positive=true;
|
|
monitor.m_nonc1test1lngrep.m_fidx=funcidx;
|
|
monitor.m_nonc1test1lngrep.m_vidx=varidx;
|
|
monitor.m_nonc1test1lngrep.m_n=n;
|
|
monitor.m_nonc1test1lngrep.m_cnt=sortedcnt;
|
|
monitor.m_nonc1test1lngrep.m_stpidxa=stpidx+0;
|
|
monitor.m_nonc1test1lngrep.m_stpidxb=stpidx+3;
|
|
monitor.m_nonc1test1lngrep.m_d=monitor.m_dcur;
|
|
monitor.m_nonc1test1lngrep.m_stp=monitor.m_sortedstp;
|
|
monitor.m_nonc1test1lngrep.m_x0.Resize(n);
|
|
monitor.m_nonc1test1lngrep.m_d.Resize(n);
|
|
monitor.m_nonc1test1lngrep.m_stp.Resize(sortedcnt);
|
|
monitor.m_nonc1test1lngrep.m_g.Resize(sortedcnt);
|
|
for(i=0; i<n; i++)
|
|
monitor.m_nonc1test1lngrep.m_x0.Set(i,monitor.m_enqueuedx[monitor.m_sortedidx[0]*n+i]);
|
|
for(i=0; i<sortedcnt; i++)
|
|
monitor.m_nonc1test1lngrep.m_g.Set(i,monitor.m_g[i*n+varidx]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores State of the nonlinear CG optimizer. |
|
|
//| You should use ALGLIB functions to work with this object. |
|
|
//+------------------------------------------------------------------+
|
|
class CMinCGState
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_cgtype;
|
|
int m_debugrestartscount;
|
|
int m_k;
|
|
int m_maxits;
|
|
int m_mcinfo;
|
|
int m_mcstage;
|
|
int m_n;
|
|
int m_nfev;
|
|
int m_prectype;
|
|
int m_repiterationscount;
|
|
int m_repnfev;
|
|
int m_repterminationtype;
|
|
int m_rstimer;
|
|
int m_smoothnessguardlevel;
|
|
int m_vcnt;
|
|
double m_betady;
|
|
double m_betahs;
|
|
double m_curstpmax;
|
|
double m_diffstep;
|
|
double m_epsf;
|
|
double m_epsg;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_fbase;
|
|
double m_fm1;
|
|
double m_fm2;
|
|
double m_fold;
|
|
double m_fp1;
|
|
double m_fp2;
|
|
double m_lastgoodstep;
|
|
double m_lastscaledstep;
|
|
double m_stp;
|
|
double m_stpmax;
|
|
double m_suggestedstep;
|
|
double m_teststep;
|
|
double m_trimthreshold;
|
|
bool m_algpowerup;
|
|
bool m_drep;
|
|
bool m_innerresetneeded;
|
|
bool m_lsend;
|
|
bool m_lsstart;
|
|
bool m_needf;
|
|
bool m_needfg;
|
|
bool m_terminationneeded;
|
|
bool m_userterminationneeded;
|
|
bool m_xrep;
|
|
bool m_xupdated;
|
|
//--- objects
|
|
RCommState m_rstate;
|
|
CSmoothnessMonitor m_smonitor;
|
|
CLinMinState m_lstate;
|
|
//--- arrays
|
|
CRowDouble m_d;
|
|
CRowDouble m_diagh;
|
|
CRowDouble m_diaghl2;
|
|
CRowDouble m_dk;
|
|
CRowDouble m_dn;
|
|
CRowDouble m_g;
|
|
CRowDouble m_invs;
|
|
CRowDouble m_lastscaleused;
|
|
CRowDouble m_s;
|
|
CRowDouble m_work0;
|
|
CRowDouble m_work1;
|
|
CRowDouble m_x;
|
|
CRowDouble m_xbase;
|
|
CRowDouble m_xk;
|
|
CRowDouble m_xn;
|
|
CRowDouble m_yk;
|
|
//--- matrix
|
|
CMatrixDouble m_vcorr;
|
|
//--- constructor, destructor
|
|
CMinCGState(void);
|
|
~CMinCGState(void) {}
|
|
//--- copy
|
|
void Copy(const CMinCGState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinCGState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinCGState::CMinCGState(void)
|
|
{
|
|
m_cgtype=0;
|
|
m_debugrestartscount=0;
|
|
m_k=0;
|
|
m_maxits=0;
|
|
m_mcinfo=0;
|
|
m_mcstage=0;
|
|
m_n=0;
|
|
m_nfev=0;
|
|
m_prectype=0;
|
|
m_repiterationscount=0;
|
|
m_repnfev=0;
|
|
m_repterminationtype=0;
|
|
m_rstimer=0;
|
|
m_smoothnessguardlevel=0;
|
|
m_vcnt=0;
|
|
m_betady=0;
|
|
m_betahs=0;
|
|
m_curstpmax=0;
|
|
m_diffstep=0;
|
|
m_epsf=0;
|
|
m_epsg=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_fbase=0;
|
|
m_fm1=0;
|
|
m_fm2=0;
|
|
m_fold=0;
|
|
m_fp1=0;
|
|
m_fp2=0;
|
|
m_lastgoodstep=0;
|
|
m_lastscaledstep=0;
|
|
m_stp=0;
|
|
m_stpmax=0;
|
|
m_suggestedstep=0;
|
|
m_teststep=0;
|
|
m_trimthreshold=0;
|
|
m_algpowerup=false;
|
|
m_drep=false;
|
|
m_innerresetneeded=false;
|
|
m_lsend=false;
|
|
m_lsstart=false;
|
|
m_needf=false;
|
|
m_needfg=false;
|
|
m_terminationneeded=false;
|
|
m_userterminationneeded=false;
|
|
m_xrep=false;
|
|
m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCGState::Copy(const CMinCGState &obj)
|
|
{
|
|
m_cgtype=obj.m_cgtype;
|
|
m_debugrestartscount=obj.m_debugrestartscount;
|
|
m_k=obj.m_k;
|
|
m_maxits=obj.m_maxits;
|
|
m_mcinfo=obj.m_mcinfo;
|
|
m_mcstage=obj.m_mcstage;
|
|
m_n=obj.m_n;
|
|
m_nfev=obj.m_nfev;
|
|
m_prectype=obj.m_prectype;
|
|
m_repiterationscount=obj.m_repiterationscount;
|
|
m_repnfev=obj.m_repnfev;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_rstimer=obj.m_rstimer;
|
|
m_smoothnessguardlevel=obj.m_smoothnessguardlevel;
|
|
m_vcnt=obj.m_vcnt;
|
|
m_betady=obj.m_betady;
|
|
m_betahs=obj.m_betahs;
|
|
m_curstpmax=obj.m_curstpmax;
|
|
m_diffstep=obj.m_diffstep;
|
|
m_epsf=obj.m_epsf;
|
|
m_epsg=obj.m_epsg;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_fbase=obj.m_fbase;
|
|
m_fm1=obj.m_fm1;
|
|
m_fm2=obj.m_fm2;
|
|
m_fold=obj.m_fold;
|
|
m_fp1=obj.m_fp1;
|
|
m_fp2=obj.m_fp2;
|
|
m_lastgoodstep=obj.m_lastgoodstep;
|
|
m_lastscaledstep=obj.m_lastscaledstep;
|
|
m_stp=obj.m_stp;
|
|
m_stpmax=obj.m_stpmax;
|
|
m_suggestedstep=obj.m_suggestedstep;
|
|
m_teststep=obj.m_teststep;
|
|
m_trimthreshold=obj.m_trimthreshold;
|
|
m_algpowerup=obj.m_algpowerup;
|
|
m_drep=obj.m_drep;
|
|
m_innerresetneeded=obj.m_innerresetneeded;
|
|
m_lsend=obj.m_lsend;
|
|
m_lsstart=obj.m_lsstart;
|
|
m_needf=obj.m_needf;
|
|
m_needfg=obj.m_needfg;
|
|
m_terminationneeded=obj.m_terminationneeded;
|
|
m_userterminationneeded=obj.m_userterminationneeded;
|
|
m_xrep=obj.m_xrep;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_rstate=obj.m_rstate;
|
|
m_smonitor=obj.m_smonitor;
|
|
m_d=obj.m_d;
|
|
m_diagh=obj.m_diagh;
|
|
m_diaghl2=obj.m_diaghl2;
|
|
m_dk=obj.m_dk;
|
|
m_dn=obj.m_dn;
|
|
m_g=obj.m_g;
|
|
m_invs=obj.m_invs;
|
|
m_lastscaleused=obj.m_lastscaleused;
|
|
m_s=obj.m_s;
|
|
m_work0=obj.m_work0;
|
|
m_work1=obj.m_work1;
|
|
m_x=obj.m_x;
|
|
m_xbase=obj.m_xbase;
|
|
m_xk=obj.m_xk;
|
|
m_xn=obj.m_xn;
|
|
m_yk=obj.m_yk;
|
|
m_vcorr=obj.m_vcorr;
|
|
m_lstate=obj.m_lstate;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores State of the nonlinear CG optimizer. |
|
|
//| You should use ALGLIB functions to work with this object. |
|
|
//+------------------------------------------------------------------+
|
|
class CMinCGStateShell
|
|
{
|
|
private:
|
|
CMinCGState m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinCGStateShell(void) {}
|
|
CMinCGStateShell(CMinCGState &obj) { m_innerobj.Copy(obj); }
|
|
~CMinCGStateShell(void) {}
|
|
//--- methods
|
|
bool GetNeedF(void);
|
|
void SetNeedF(const bool b);
|
|
bool GetNeedFG(void);
|
|
void SetNeedFG(const bool b);
|
|
bool GetXUpdated(void);
|
|
void SetXUpdated(const bool b);
|
|
double GetF(void);
|
|
void SetF(const double d);
|
|
CMinCGState *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needf |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinCGStateShell::GetNeedF(void)
|
|
{
|
|
return(m_innerobj.m_needf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needf |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCGStateShell::SetNeedF(const bool b)
|
|
{
|
|
m_innerobj.m_needf=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinCGStateShell::GetNeedFG(void)
|
|
{
|
|
return(m_innerobj.m_needfg);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCGStateShell::SetNeedFG(const bool b)
|
|
{
|
|
m_innerobj.m_needfg=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinCGStateShell::GetXUpdated(void)
|
|
{
|
|
return(m_innerobj.m_xupdated);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCGStateShell::SetXUpdated(const bool b)
|
|
{
|
|
m_innerobj.m_xupdated=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
double CMinCGStateShell::GetF(void)
|
|
{
|
|
return(m_innerobj.m_f);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCGStateShell::SetF(const double d)
|
|
{
|
|
m_innerobj.m_f=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinCGState *CMinCGStateShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary class for CMinCG |
|
|
//+------------------------------------------------------------------+
|
|
class CMinCGReport
|
|
{
|
|
public:
|
|
int m_iterationscount;
|
|
int m_nfev;
|
|
int m_terminationtype;
|
|
//--- constructor, destructor
|
|
CMinCGReport(void) { ZeroMemory(this); }
|
|
~CMinCGReport(void) {}
|
|
//--- copy
|
|
void Copy(const CMinCGReport &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCGReport::Copy(const CMinCGReport &obj)
|
|
{
|
|
//--- copy variables
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_nfev=obj.m_nfev;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This class is a shell for class CMinCGReport |
|
|
//+------------------------------------------------------------------+
|
|
class CMinCGReportShell
|
|
{
|
|
private:
|
|
CMinCGReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinCGReportShell(void) {}
|
|
CMinCGReportShell(CMinCGReport &obj) { m_innerobj.Copy(obj); }
|
|
~CMinCGReportShell(void) {}
|
|
//--- methods
|
|
int GetIterationsCount(void);
|
|
void SetIterationsCount(const int i);
|
|
int GetNFev(void);
|
|
void SetNFev(const int i);
|
|
int GetTerminationType(void);
|
|
void SetTerminationType(const int i);
|
|
CMinCGReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable iterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CMinCGReportShell::GetIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_iterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable iterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCGReportShell::SetIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_iterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable nfev |
|
|
//+------------------------------------------------------------------+
|
|
int CMinCGReportShell::GetNFev(void)
|
|
{
|
|
return(m_innerobj.m_nfev);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable nfev |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCGReportShell::SetNFev(const int i)
|
|
{
|
|
m_innerobj.m_nfev=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
int CMinCGReportShell::GetTerminationType(void)
|
|
{
|
|
return(m_innerobj.m_terminationtype);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCGReportShell::SetTerminationType(const int i)
|
|
{
|
|
m_innerobj.m_terminationtype=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinCGReport *CMinCGReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Conjugate gradient optimizer |
|
|
//+------------------------------------------------------------------+
|
|
class CMinCG
|
|
{
|
|
public:
|
|
//--- class constants
|
|
static const int m_rscountdownlen;
|
|
static const double m_gtol;
|
|
|
|
//--- public methods
|
|
static void MinCGCreate(const int n,double &x[],CMinCGState &State);
|
|
static void MinCGCreate(const int n,CRowDouble &x,CMinCGState &State);
|
|
static void MinCGCreateF(const int n,double &x[],const double diffstep,CMinCGState &State);
|
|
static void MinCGCreateF(const int n,CRowDouble &x,const double diffstep,CMinCGState &State);
|
|
static void MinCGSetCond(CMinCGState &State,const double epsg,const double epsf,double epsx,const int m_maxits);
|
|
static void MinCGSetScale(CMinCGState &State,double &s[]);
|
|
static void MinCGSetScale(CMinCGState &State,CRowDouble &s);
|
|
static void MinCGSetXRep(CMinCGState &State,const bool needxrep);
|
|
static void MinCGSetDRep(CMinCGState &State,const bool needdrep);
|
|
static void MinCGSetCGType(CMinCGState &State,int cgtype);
|
|
static void MinCGSetStpMax(CMinCGState &State,const double stpmax);
|
|
static void MinCGSuggestStep(CMinCGState &State,const double stp);
|
|
static double MinCGLastGoodStep(CMinCGState &State);
|
|
static void MinCGSetPrecDefault(CMinCGState &State);
|
|
static void MinCGSetPrecDiag(CMinCGState &State,double &d[]);
|
|
static void MinCGSetPrecDiag(CMinCGState &State,CRowDouble &d);
|
|
static void MinCGSetPrecScale(CMinCGState &State);
|
|
static void MinCGOptGuardGradient(CMinCGState &State,double teststep);
|
|
static void MinCGOptGuardSmoothness(CMinCGState &State,int level);
|
|
static void MinCGOptGuardResults(CMinCGState &State,COptGuardReport &rep);
|
|
static void MinCGOptGuardNonC1Test0Results(CMinCGState &State,COptGuardNonC1Test0Report &strrep,COptGuardNonC1Test0Report &lngrep);
|
|
static void MinCGOptGuardNonC1Test1Results(CMinCGState &State,COptGuardNonC1Test1Report &strrep,COptGuardNonC1Test1Report &lngrep);
|
|
static void MinCGResults(CMinCGState &State,double &x[],CMinCGReport &rep);
|
|
static void MinCGResults(CMinCGState &State,CRowDouble &x,CMinCGReport &rep);
|
|
static void MinCGResultsBuf(CMinCGState &State,double &x[],CMinCGReport &rep);
|
|
static void MinCGResultsBuf(CMinCGState &State,CRowDouble &x,CMinCGReport &rep);
|
|
static void MinCGRestartFrom(CMinCGState &State,double &x[]);
|
|
static void MinCGRestartFrom(CMinCGState &State,CRowDouble &x);
|
|
static void MinCGRequestTermination(CMinCGState &State);
|
|
static void MinCGSetPrecDiagFast(CMinCGState &State,double &d[]);
|
|
static void MinCGSetPrecDiagFast(CMinCGState &State,CRowDouble &d);
|
|
static void MinCGSetPrecLowRankFast(CMinCGState &State,double &d1[],double &c[],CMatrixDouble &v,const int vcnt);
|
|
static void MinCGSetPrecLowRankFast(CMinCGState &State,CRowDouble &d1,CRowDouble &c,CMatrixDouble &v,const int vcnt);
|
|
static void MinCGSetPrecVarPart(CMinCGState &State,double &d2[]);
|
|
static void MinCGSetPrecVarPart(CMinCGState &State,CRowDouble &d2);
|
|
static bool MinCGIteration(CMinCGState &State);
|
|
|
|
private:
|
|
static void ClearRequestFields(CMinCGState &State);
|
|
static void PreconditionedMultiply(CMinCGState &State,CRowDouble &x,CRowDouble &work0,CRowDouble &work1);
|
|
static double PreconditionedMultiply2(CMinCGState &State,CRowDouble &x,CRowDouble &y,CRowDouble &work0,CRowDouble &work1);
|
|
static void MinCGInitInternal(const int n,const double diffstep,CMinCGState &State);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constants |
|
|
//+------------------------------------------------------------------+
|
|
const int CMinCG::m_rscountdownlen=10;
|
|
const double CMinCG::m_gtol=0.3;
|
|
//+------------------------------------------------------------------+
|
|
//| NONLINEAR CONJUGATE GRADIENT METHOD |
|
|
//| DESCRIPTION: |
|
|
//| The subroutine minimizes function F(x) of N arguments by using |
|
|
//| one of the nonlinear conjugate gradient methods. |
|
|
//| These CG methods are globally convergent (even on non-convex |
|
|
//| functions) as long as grad(f) is Lipschitz continuous in a some |
|
|
//| neighborhood of the L = { x : f(x)<=f(x0) }. |
|
|
//| REQUIREMENTS: |
|
|
//| Algorithm will request following information during its |
|
|
//| operation: |
|
|
//| * function value F and its gradient G (simultaneously) at given |
|
|
//| point X |
|
|
//| USAGE: |
|
|
//| 1. User initializes algorithm State with MinCGCreate() call |
|
|
//| 2. User tunes m_solver parameters with MinCGSetCond(), |
|
|
//| MinCGSetStpMax() and other functions |
|
|
//| 3. User calls MinCGOptimize() function which takes algorithm |
|
|
//| State and pointer (delegate, etc.) to callback function which |
|
|
//| calculates F/G. |
|
|
//| 4. User calls MinCGResults() to get solution |
|
|
//| 5. Optionally, user may call MinCGRestartFrom() to solve another |
|
|
//| problem with same N but another starting point and/or another |
|
|
//| function. MinCGRestartFrom() allows to reuse already |
|
|
//| initialized structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N>0: |
|
|
//| * if given, only leading N elements of X are used|
|
|
//| * if not given, automatically determined from |
|
|
//| size of X |
|
|
//| X - starting point, array[0..m_n-1]. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGCreate(const int n,double &x[],CMinCGState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N too small!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- function call
|
|
MinCGInitInternal(n,0.0,State);
|
|
//--- function call
|
|
MinCGRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGCreate(const int n,CRowDouble &x,CMinCGState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N too small!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- function call
|
|
MinCGInitInternal(n,0.0,State);
|
|
//--- function call
|
|
MinCGRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| The subroutine is finite difference variant of MinCGCreate(). |
|
|
//| It uses finite differences in order to differentiate target |
|
|
//| function. |
|
|
//| Description below contains information which is specific to this |
|
|
//| function only. We recommend to read comments on MinCGCreate() in |
|
|
//| order to get more information about creation of CG optimizer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N>0: |
|
|
//| * if given, only leading N elements of X are |
|
|
//| used |
|
|
//| * if not given, automatically determined from |
|
|
//| size of X |
|
|
//| X - starting point, array[0..m_n-1]. |
|
|
//| DiffStep- differentiation step, >0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NOTES: |
|
|
//| 1. algorithm uses 4-point central formula for differentiation. |
|
|
//| 2. differentiation step along I-th axis is equal to |
|
|
//| DiffStep*S[I] where S[] is scaling vector which can be set by |
|
|
//| MinCGSetScale() call. |
|
|
//| 3. we recommend you to use moderate values of differentiation |
|
|
//| step. Too large step will result in too large truncation |
|
|
//| errors, while too small step will result in too large |
|
|
//| numerical errors. 1.0E-6 can be good value to start with. |
|
|
//| 4. Numerical differentiation is very inefficient - one gradient |
|
|
//| calculation needs 4*N function evaluations. This function will|
|
|
//| work for any N - either small (1...10), moderate (10...100) or|
|
|
//| large (100...). However, performance penalty will be too |
|
|
//| severe for any N's except for small ones. |
|
|
//| We should also say that code which relies on numerical |
|
|
//| differentiation is less robust and precise. L-BFGS needs |
|
|
//| exact gradient values. Imprecise gradient may slow down |
|
|
//| convergence, especially on highly nonlinear problems. |
|
|
//| Thus we recommend to use this function for fast prototyping |
|
|
//| on small- dimensional problems only, and to implement |
|
|
//| analytical gradient as soon as possible. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGCreateF(const int n,double &x[],const double diffstep,
|
|
CMinCGState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N too small!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(diffstep),__FUNCTION__+": DiffStep is infinite or NaN!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep is non-positive!"))
|
|
return;
|
|
//--- function call
|
|
MinCGInitInternal(n,diffstep,State);
|
|
//--- function call
|
|
MinCGRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGCreateF(const int n,CRowDouble &x,const double diffstep,
|
|
CMinCGState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N too small!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(diffstep),__FUNCTION__+": DiffStep is infinite or NaN!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep is non-positive!"))
|
|
return;
|
|
//--- function call
|
|
MinCGInitInternal(n,diffstep,State);
|
|
//--- function call
|
|
MinCGRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping conditions for CG optimization |
|
|
//| algorithm. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsG - >=0 |
|
|
//| The subroutine finishes its work if the condition|
|
|
//| |v|<EpsG is satisfied, where: |
|
|
//| * |.| means Euclidian norm |
|
|
//| * v - scaled gradient vector, v[i]=g[i]*s[i] |
|
|
//| * g - gradient |
|
|
//| * s - scaling coefficients set by MinCGSetScale()|
|
|
//| EpsF - >=0 |
|
|
//| The subroutine finishes its work if on k+1-th |
|
|
//| iteration the condition |F(k+1)-F(k)| <= |
|
|
//| <= EpsF*max{|F(k)|,|F(k+1)|,1} is satisfied. |
|
|
//| EpsX - >=0 |
|
|
//| The subroutine finishes its work if on k+1-th |
|
|
//| iteration the condition |v|<=EpsX is fulfilled, |
|
|
//| where: |
|
|
//| * |.| means Euclidian norm |
|
|
//| * v - scaled step vector, v[i]=dx[i]/s[i] |
|
|
//| * dx - ste pvector, dx=X(k+1)-X(k) |
|
|
//| * s - scaling coefficients set by MinCGSetScale()|
|
|
//| MaxIts - maximum number of iterations. If MaxIts=0, the |
|
|
//| number of iterations is unlimited. |
|
|
//| Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will|
|
|
//| lead to automatic stopping criterion selection (small EpsX). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetCond(CMinCGState &State,const double epsg,
|
|
const double epsf,double epsx,const int m_maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsg),__FUNCTION__+": EpsG is not finite number!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsg>=0.0,__FUNCTION__+": negative EpsG!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsf),__FUNCTION__+": EpsF is not finite number!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsf>=0.0,__FUNCTION__+": negative EpsF!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsx),__FUNCTION__+": EpsX is not finite number!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m_maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
//--- check
|
|
if(epsg==0.0 && epsf==0.0 && epsx==0.0 && m_maxits==0)
|
|
epsx=1.0E-6;
|
|
//--- change values
|
|
State.m_epsg=epsg;
|
|
State.m_epsf=epsf;
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=m_maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients for CG optimizer. |
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions (step size and gradient are scaled before comparison |
|
|
//| with tolerances). Scale of the I-th variable is a translation |
|
|
//| invariant measure of: |
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| Scaling is also used by finite difference variant of CG |
|
|
//| optimizer - step along I-th axis is equal to DiffStep*S[I]. |
|
|
//| In most optimizers (and in the CG too) scaling is NOT a form of |
|
|
//| preconditioning. It just affects stopping conditions. You should |
|
|
//| set preconditioner by separate call to one of the |
|
|
//| MinCGSetPrec...() functions. |
|
|
//| There is special preconditioning mode, however, which uses |
|
|
//| scaling coefficients to form diagonal preconditioning matrix. |
|
|
//| You can turn this mode on, if you want. But you should understand|
|
|
//| that scaling is not the same thing as preconditioning - these are|
|
|
//| two different, although related forms of tuning m_solver. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients |
|
|
//| S[i] may be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetScale(CMinCGState &State,double &s[])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(s)>=State.m_n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
State.m_s.Set(i,MathAbs(s[i]));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetScale(CMinCGState &State,CRowDouble &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(s)>=State.m_n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
}
|
|
|
|
State.m_s=s.Abs()+0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function turns on/off reporting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NeedXRep- whether iteration reports are needed or not |
|
|
//| If NeedXRep is True, algorithm will call rep() callback function |
|
|
//| if it is provided to MinCGOptimize(). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetXRep(CMinCGState &State,const bool needxrep)
|
|
{
|
|
State.m_xrep=needxrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function turns on/off line search reports. |
|
|
//| These reports are described in more details in developer-only |
|
|
//| comments on CMinCGState &object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NeedDRep- whether line search reports are needed or not |
|
|
//| This function is intended for private use only. Turning it on |
|
|
//| artificially may cause program failure. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetDRep(CMinCGState &State,const bool needdrep)
|
|
{
|
|
State.m_drep=needdrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets CG algorithm. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| CGType - algorithm type: |
|
|
//| * -1 automatic selection of the best |
|
|
//| algorithm |
|
|
//| * 0 DY (Dai and Yuan) algorithm |
|
|
//| * 1 Hybrid DY-HS algorithm |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetCGType(CMinCGState &State,int cgtype)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(cgtype>=-1 && cgtype<=1,__FUNCTION__+": incorrect CGType!"))
|
|
return;
|
|
//--- check
|
|
if(cgtype==-1)
|
|
cgtype=1;
|
|
//--- change value
|
|
State.m_cgtype=cgtype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets maximum step length |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| StpMax - maximum step length, >=0. Set StpMax to 0.0, if |
|
|
//| you don't want to limit step length. |
|
|
//| Use this subroutine when you optimize target function which |
|
|
//| contains exp() or other fast growing functions, and optimization |
|
|
//| algorithm makes too large steps which leads to overflow. This |
|
|
//| function allows us to reject steps that are too large (and |
|
|
//| therefore expose us to the possible overflow) without actually |
|
|
//| calculating function value at the x+stp*d. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetStpMax(CMinCGState &State,const double stpmax)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(stpmax),__FUNCTION__+": StpMax is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(stpmax>=0.0,__FUNCTION__+": StpMax<0!"))
|
|
return;
|
|
//--- change value
|
|
State.m_stpmax=stpmax;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function allows to suggest initial step length to the CG |
|
|
//| algorithm. |
|
|
//| Suggested step length is used as starting point for the line |
|
|
//| search. It can be useful when you have badly scaled problem, i.e.|
|
|
//| when ||grad|| (which is used as initial estimate for the first |
|
|
//| step) is many orders of magnitude different from the desired |
|
|
//| step. |
|
|
//| Line search may fail on such problems without good estimate of |
|
|
//| initial step length. Imagine, for example, problem with |
|
|
//| ||grad||=10^50 and desired step equal to 0.1 Line search |
|
|
//| function will use 10^50 as initial step, then it will decrease |
|
|
//| step length by 2 (up to 20 attempts) and will get 10^44, which is|
|
|
//| still too large. |
|
|
//| This function allows us to tell than line search should be |
|
|
//| started from some moderate step length, like 1.0, so algorithm |
|
|
//| will be able to detect desired step length in a several searches.|
|
|
//| Default behavior (when no step is suggested) is to use |
|
|
//| preconditioner, if it is available, to generate initial estimate |
|
|
//| of step length. |
|
|
//| This function influences only first iteration of algorithm. It |
|
|
//| should be called between MinCGCreate/MinCGRestartFrom() call and |
|
|
//| MinCGOptimize call. Suggested step is ignored if you have |
|
|
//| preconditioner. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure used to store algorithm State. |
|
|
//| Stp - initial estimate of the step length. |
|
|
//| Can be zero (no estimate). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSuggestStep(CMinCGState &State,const double stp)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(stp),__FUNCTION__+": Stp is infinite or NAN"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(stp>=0.0,__FUNCTION__+": Stp<0"))
|
|
return;
|
|
//--- change value
|
|
State.m_suggestedstep=stp;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This developer-only function allows to retrieve unscaled (!) |
|
|
//| length of last good step (i.e. step which resulted in sufficient |
|
|
//| decrease of target function). |
|
|
//| It can be used in for solution of sequential optimization |
|
|
//| subproblems, where MinCGSuggestStep() is called with length of |
|
|
//| previous step as parameter. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure used to store algorithm State. |
|
|
//| RESULT: |
|
|
//| length of last good step being accepted |
|
|
//| NOTE: result of this function is undefined if you called it |
|
|
//| before |
|
|
//+------------------------------------------------------------------+
|
|
double CMinCG::MinCGLastGoodStep(CMinCGState &State)
|
|
{
|
|
return(State.m_lastgoodstep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: preconditioning is turned |
|
|
//| off. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NOTE: you can change preconditioner "on the fly", during |
|
|
//| algorithm iterations. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetPrecDefault(CMinCGState &State)
|
|
{
|
|
//--- change values
|
|
State.m_prectype=0;
|
|
State.m_innerresetneeded=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: diagonal of approximate |
|
|
//| Hessian is used. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| D - diagonal of the approximate Hessian, |
|
|
//| array[0..m_n-1], (if larger, only leading N |
|
|
//| elements are used). |
|
|
//| NOTE: you can change preconditioner "on the fly", during |
|
|
//| algorithm iterations. |
|
|
//| NOTE 2: D[i] should be positive. Exception will be thrown |
|
|
//| otherwise. |
|
|
//| NOTE 3: you should pass diagonal of approximate Hessian - NOT |
|
|
//| ITS INVERSE. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetPrecDiag(CMinCGState &State,double &d[])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(d)>=State.m_n,__FUNCTION__+": D is too short"))
|
|
return;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(d[i]),__FUNCTION__+": D contains infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(d[i]>0.0,__FUNCTION__+": D contains non-positive elements"))
|
|
return;
|
|
}
|
|
//--- function call
|
|
MinCGSetPrecDiagFast(State,d);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetPrecDiag(CMinCGState &State,CRowDouble &d)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(d)>=State.m_n,__FUNCTION__+": D is too short"))
|
|
return;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(d[i]),__FUNCTION__+": D contains infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(d[i]>0.0,__FUNCTION__+": D contains non-positive elements"))
|
|
return;
|
|
}
|
|
//--- function call
|
|
MinCGSetPrecDiagFast(State,d);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: scale-based diagonal |
|
|
//| preconditioning. |
|
|
//| This preconditioning mode can be useful when you don't have |
|
|
//| approximate diagonal of Hessian, but you know that your variables|
|
|
//| are badly scaled (for example, one variable is in [1,10], and |
|
|
//| another in [1000,100000]), and most part of the ill-conditioning |
|
|
//| comes from different scales of vars. |
|
|
//| In this case simple scale-based preconditioner, |
|
|
//| with H.Set(i, 1/(s[i]^2), can greatly improve convergence. |
|
|
//| IMPRTANT: you should set scale of your variables with |
|
|
//| MinCGSetScale() call (before or after MinCGSetPrecScale() call). |
|
|
//| Without knowledge of the scale of your variables scale-based |
|
|
//| preconditioner will be just unit matrix. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NOTE: you can change preconditioner "on the fly", during |
|
|
//| algorithm iterations. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetPrecScale(CMinCGState &State)
|
|
{
|
|
//--- change values
|
|
State.m_prectype=3;
|
|
State.m_innerresetneeded=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function activates/deactivates verification of the |
|
|
//| user-supplied analytic gradient. |
|
|
//| Upon activation of this option OptGuard integrity checker |
|
|
//| performs numerical differentiation of your target function at |
|
|
//| the initial point (note: future versions may also perform check|
|
|
//| at the final point) and compares numerical gradient with analytic|
|
|
//| one provided by you. |
|
|
//| If difference is too large, an error flag is set and optimization|
|
|
//| session continues. After optimization session is over, you can |
|
|
//| retrieve the report which stores both gradients and specific |
|
|
//| components highlighted as suspicious by the OptGuard. |
|
|
//| The primary OptGuard report can be retrieved with |
|
|
//| MinCGOptGuardResults(). |
|
|
//| IMPORTANT: gradient check is a high-overhead option which will |
|
|
//| cost you about 3*N additional function evaluations. In|
|
|
//| many cases it may cost as much as the rest of the |
|
|
//| optimization session. |
|
|
//| YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO |
|
|
//| CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY. |
|
|
//| NOTE: unlike previous incarnation of the gradient checking code, |
|
|
//| OptGuard does NOT interrupt optimization even if it |
|
|
//| discovers bad gradient. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure used to store algorithm State |
|
|
//| TestStep - verification step used for numerical |
|
|
//| differentiation: |
|
|
//| * TestStep=0 turns verification off |
|
|
//| * TestStep>0 activates verification |
|
|
//| You should carefully choose TestStep. Value which |
|
|
//| is too large (so large that function behavior is|
|
|
//| non-cubic at this scale) will lead to false alarms.|
|
|
//| Too short step will result in rounding errors |
|
|
//| dominating numerical derivative. |
|
|
//| You may use different step for different parameters|
|
|
//| by means of setting scale with MinCGSetScale(). |
|
|
//| === EXPLANATION ================================================ |
|
|
//| In order to verify gradient algorithm performs following steps: |
|
|
//| * two trial steps are made to X[i]-TestStep*S[i] and |
|
|
//| X[i]+TestStep*S[i], where X[i] is i-th component of the |
|
|
//| initial point and S[i] is a scale of i-th parameter |
|
|
//| * F(X) is evaluated at these trial points |
|
|
//| * we perform one more evaluation in the middle point of the |
|
|
//| interval |
|
|
//| * we build cubic model using function values and derivatives|
|
|
//| at trial points and we compare its prediction with actual |
|
|
//| value in the middle point |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGOptGuardGradient(CMinCGState &State,
|
|
double teststep)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(teststep),__FUNCTION__+": TestStep contains NaN or INF"))
|
|
return;
|
|
if(!CAp::Assert(teststep>=0.0,__FUNCTION__+": invalid argument TestStep(TestStep<0)"))
|
|
return;
|
|
|
|
State.m_teststep=teststep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function activates/deactivates nonsmoothness monitoring |
|
|
//| option of the OptGuard integrity checker. Smoothness monitor |
|
|
//| silently observes solution process and tries to detect ill-posed |
|
|
//| problems, i.e. ones with: |
|
|
//| a) discontinuous target function(non - C0) |
|
|
//| b) nonsmooth target function(non - C1) |
|
|
//| Smoothness monitoring does NOT interrupt optimization even if it|
|
|
//| suspects that your problem is nonsmooth. It just sets |
|
|
//| corresponding flags in the OptGuard report which can be retrieved|
|
|
//| after optimization is over. |
|
|
//| Smoothness monitoring is a moderate overhead option which often |
|
|
//| adds less than 1 % to the optimizer running time. Thus, you can |
|
|
//| use it even for large scale problems. |
|
|
//| NOTE: OptGuard does NOT guarantee that it will always detect |
|
|
//| C0 / C1 continuity violations. |
|
|
//| First, minor errors are hard to catch-say, a 0.0001 difference in|
|
|
//| the model values at two sides of the gap may be due to |
|
|
//| discontinuity of the model - or simply because the model has |
|
|
//| changed. |
|
|
//| Second, C1 - violations are especially difficult to detect |
|
|
//| in a noninvasive way. The optimizer usually performs very short |
|
|
//| steps near the nonsmoothness, and differentiation usually |
|
|
//| introduces a lot of numerical noise. It is hard to tell whether |
|
|
//| some tiny discontinuity in the slope is due to real nonsmoothness|
|
|
//| or just due to numerical noise alone. |
|
|
//| Our top priority was to avoid false positives, so in some rare |
|
|
//| cases minor errors may went unnoticed(however, in most cases they|
|
|
//| can be spotted with restart from different initial point). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| Level - monitoring level: |
|
|
//| * 0 - monitoring is disabled |
|
|
//| * 1 - noninvasive low - overhead monitoring; |
|
|
//| function values and / or gradients are |
|
|
//| recorded, but OptGuard does not try to |
|
|
//| perform additional evaluations in order |
|
|
//| to get more information about suspicious |
|
|
//| locations. |
|
|
//| === EXPLANATION ================================================ |
|
|
//| One major source of headache during optimization is the |
|
|
//| possibility of the coding errors in the target function / |
|
|
//| constraints (or their gradients). Such errors most often manifest|
|
|
//| themselves as discontinuity or nonsmoothness of the target / |
|
|
//| constraints. |
|
|
//| Another frequent situation is when you try to optimize something |
|
|
//| involving lots of min() and max() operations, i.e. nonsmooth |
|
|
//| target. Although not a coding error, it is nonsmoothness anyway -|
|
|
//| and smooth optimizers usually stop right after encountering |
|
|
//| nonsmoothness, well before reaching solution. |
|
|
//| OptGuard integrity checker helps you to catch such situations: |
|
|
//| it monitors function values / gradients being passed to the |
|
|
//| optimizer and tries to errors. Upon discovering suspicious |
|
|
//| pair of points it raises appropriate flag (and allows you to |
|
|
//| continue optimization). When optimization is done, you can study |
|
|
//| OptGuard result. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGOptGuardSmoothness(CMinCGState &State,int level)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(level==0 || level==1,__FUNCTION__+": unexpected value of level parameter"))
|
|
return;
|
|
|
|
State.m_smoothnessguardlevel=level;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Results of OptGuard integrity check, should be called after |
|
|
//| optimization session is over. |
|
|
//| === PRIMARY REPORT ============================================= |
|
|
//| OptGuard performs several checks which are intended to catch |
|
|
//| common errors in the implementation of nonlinear function / |
|
|
//| gradient: |
|
|
//| * incorrect analytic gradient |
|
|
//| * discontinuous(non - C0) target functions (constraints) |
|
|
//| * nonsmooth(non - C1) target functions (constraints) |
|
|
//| Each of these checks is activated with appropriate function: |
|
|
//| * MinCGOptGuardGradient() for gradient verification |
|
|
//| * MinCGOptGuardSmoothness() for C0 / C1 checks |
|
|
//| Following flags are set when these errors are suspected: |
|
|
//| * rep.badgradsuspected, and additionally: |
|
|
//| * rep.badgradvidx for specific variable (gradient element) |
|
|
//| suspected |
|
|
//| * rep.badgradxbase, a point where gradient is tested |
|
|
//| * rep.badgraduser, user - provided gradient (stored as 2D |
|
|
//| matrix with single row in order to make |
|
|
//| report structure compatible with more|
|
|
//| complex optimizers like MinNLC or MinLM) |
|
|
//| * rep.badgradnum, reference gradient obtained via numerical|
|
|
//| differentiation(stored as 2D matrix with|
|
|
//| single row in order to make report |
|
|
//| structure compatible with more complex |
|
|
//| optimizers like MinNLC or MinLM) |
|
|
//| * rep.nonc0suspected |
|
|
//| * rep.nonc1suspected |
|
|
//| === ADDITIONAL REPORTS / LOGS ================================== |
|
|
//| Several different tests are performed to catch C0 / C1 errors, |
|
|
//| you can find out specific test signaled error by looking to: |
|
|
//| * rep.nonc0test0positive, for non - C0 test #0 |
|
|
//| * rep.nonc1test0positive, for non - C1 test #0 |
|
|
//| * rep.nonc1test1positive, for non - C1 test #1 |
|
|
//| Additional information (including line search logs) can be |
|
|
//| obtained by means of: |
|
|
//| * MinCGOptGuardNonC1Test0Results() |
|
|
//| * MinCGOptGuardNonC1Test1Results() |
|
|
//| which return detailed error reports, specific points where |
|
|
//| discontinuities were found, and so on. |
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - generic OptGuard report; more detailed reports |
|
|
//| can be retrieved with other functions. |
|
|
//| NOTE: false negatives(nonsmooth problems are not identified as |
|
|
//| nonsmooth ones) are possible although unlikely. |
|
|
//| The reason is that you need to make several evaluations |
|
|
//| around nonsmoothness in order to accumulate enough information |
|
|
//| about function curvature. Say, if you start right from the |
|
|
//| nonsmooth point, optimizer simply won't get enough data to |
|
|
//| understand what is going wrong before it terminates due to abrupt|
|
|
//| changes in the derivative. It is also possible that "unlucky"|
|
|
//| step will move us to the termination too quickly. |
|
|
//| Our current approach is to have less than 0.1 % false negatives|
|
|
//| in our test examples (measured with multiple restarts from random|
|
|
//| points), and to have exactly 0 % false positives. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGOptGuardResults(CMinCGState &State,COptGuardReport &rep)
|
|
{
|
|
COptServ::SmoothnessMonitorExportReport(State.m_smonitor,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Detailed results of the OptGuard integrity check for |
|
|
//| nonsmoothness test #0 |
|
|
//| Nonsmoothness (non-C1) test #0 studies function values (not |
|
|
//| gradient!) obtained during line searches and monitors behavior |
|
|
//| of the directional derivative estimate. |
|
|
//| This test is less powerful than test #1, but it does not depend|
|
|
//| on the gradient values and thus it is more robust against |
|
|
//| artifacts introduced by numerical differentiation. |
|
|
//| Two reports are returned: |
|
|
//| *a "strongest" one, corresponding to line search which had |
|
|
//| highest value of the nonsmoothness indicator |
|
|
//| *a "longest" one, corresponding to line search which had more |
|
|
//| function evaluations, and thus is more detailed |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; |
|
|
//| FALSE if test did not notice anything (in the |
|
|
//| latter cases fields below are empty). |
|
|
//| * x0[], d[] - arrays of length N which store initial point and |
|
|
//| direction for line search(d[] can be normalized, |
|
|
//| but does not have to) |
|
|
//| * stp[], f[]- arrays of length CNT which store step lengths |
|
|
//| and function values at these points; f[i] is |
|
|
//| evaluated in x0 + stp[i]*d. |
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb |
|
|
//| (usually we have stpidxb = stpidxa + 3, with |
|
|
//| most likely position of the violation between |
|
|
//| stpidxa + 1 and stpidxa + 2. |
|
|
//| ================================================================ |
|
|
//| = SHORTLY SPEAKING: build a 2D plot of(stp, f) and look at it - |
|
|
//| = you will see where C1 continuity is violated.|
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| StrRep - C1 test #0 "strong" report |
|
|
//| LngRep - C1 test #0 "long" report |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGOptGuardNonC1Test0Results(CMinCGState &State,
|
|
COptGuardNonC1Test0Report &strrep,
|
|
COptGuardNonC1Test0Report &lngrep)
|
|
{
|
|
COptGuardApi::SmoothnessMonitorExportC1Test0Report(State.m_smonitor.m_nonc1test0strrep,State.m_lastscaleused,strrep);
|
|
COptGuardApi::SmoothnessMonitorExportC1Test0Report(State.m_smonitor.m_nonc1test0lngrep,State.m_lastscaleused,lngrep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Detailed results of the OptGuard integrity check for |
|
|
//| nonsmoothness test #1 |
|
|
//| Nonsmoothness (non-C1) test #0 studies function values (not |
|
|
//| gradient!) obtained during line searches and monitors behavior |
|
|
//| of the directional derivative estimate. |
|
|
//| This test is less powerful than test #1, but it does not depend|
|
|
//| on the gradient values and thus it is more robust against |
|
|
//| artifacts introduced by numerical differentiation. |
|
|
//| Two reports are returned: |
|
|
//| *a "strongest" one, corresponding to line search which had |
|
|
//| highest value of the nonsmoothness indicator |
|
|
//| *a "longest" one, corresponding to line search which had more |
|
|
//| function evaluations, and thus is more detailed |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; |
|
|
//| FALSE if test did not notice anything (in the |
|
|
//| latter cases fields below are empty). |
|
|
//| * x0[], d[] - arrays of length N which store initial point and |
|
|
//| direction for line search(d[] can be normalized, |
|
|
//| but does not have to) |
|
|
//| * stp[], f[]- arrays of length CNT which store step lengths |
|
|
//| and function values at these points; f[i] is |
|
|
//| evaluated in x0 + stp[i]*d. |
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb |
|
|
//| (usually we have stpidxb = stpidxa + 3, with |
|
|
//| most likely position of the violation between |
|
|
//| stpidxa + 1 and stpidxa + 2. |
|
|
//| ================================================================ |
|
|
//| = SHORTLY SPEAKING: build a 2D plot of(stp, f) and look at it - |
|
|
//| = you will see where C1 continuity is violated.|
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| StrRep - C1 test #1 "strong" report |
|
|
//| LngRep - C1 test #1 "long" report |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGOptGuardNonC1Test1Results(CMinCGState &State,
|
|
COptGuardNonC1Test1Report &strrep,
|
|
COptGuardNonC1Test1Report &lngrep)
|
|
{
|
|
COptGuardApi::SmoothnessMonitorExportC1Test1Report(State.m_smonitor.m_nonc1test1strrep,State.m_lastscaleused,strrep);
|
|
COptGuardApi::SmoothnessMonitorExportC1Test1Report(State.m_smonitor.m_nonc1test1lngrep,State.m_lastscaleused,lngrep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Conjugate gradient results |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[0..m_n-1], solution |
|
|
//| Rep - optimization report: |
|
|
//| * Rep.TerminationType completetion code: |
|
|
//| * 1 relative function improvement is no |
|
|
//| more than EpsF. |
|
|
//| * 2 relative step is no more than EpsX. |
|
|
//| * 4 gradient norm is no more than EpsG |
|
|
//| * 5 MaxIts steps was taken |
|
|
//| * 7 stopping conditions are too |
|
|
//| stringent, further improvement is |
|
|
//| impossible, we return best X found |
|
|
//| so far |
|
|
//| * 8 terminated by user |
|
|
//| * Rep.IterationsCount contains iterations count |
|
|
//| * NFEV countains number of function calculations |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGResults(CMinCGState &State,double &x[],CMinCGReport &rep)
|
|
{
|
|
//--- reset memory
|
|
ArrayResize(x,0);
|
|
//--- function call
|
|
MinCGResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGResults(CMinCGState &State,CRowDouble &x,CMinCGReport &rep)
|
|
{
|
|
//--- reset memory
|
|
x.Resize(0);
|
|
//--- function call
|
|
MinCGResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Conjugate gradient results |
|
|
//| Buffered implementation of MinCGResults(), which uses |
|
|
//| pre-allocated buffer to store X[]. If buffer size is too small, |
|
|
//| it resizes buffer.It is intended to be used in the inner cycles |
|
|
//| of performance critical algorithms where array reallocation |
|
|
//| penalty is too large to be ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGResultsBuf(CMinCGState &State,double &x[],CMinCGReport &rep)
|
|
{
|
|
//--- create a variable
|
|
int i_=0;
|
|
//--- copy
|
|
State.m_xn.ToArray(x);
|
|
//--- change values
|
|
rep.m_iterationscount=State.m_repiterationscount;
|
|
rep.m_nfev=State.m_repnfev;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGResultsBuf(CMinCGState &State,CRowDouble &x,CMinCGReport &rep)
|
|
{
|
|
//--- copy
|
|
x=State.m_xn;
|
|
//--- change values
|
|
rep.m_iterationscount=State.m_repiterationscount;
|
|
rep.m_nfev=State.m_repnfev;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine restarts CG algorithm from new point. All |
|
|
//| optimization parameters are left unchanged. |
|
|
//| This function allows to solve multiple optimization problems |
|
|
//| (which must have same number of dimensions) without object |
|
|
//| reallocation penalty. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure used to store algorithm State. |
|
|
//| X - new starting point. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGRestartFrom(CMinCGState &State,double &x[])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=State.m_n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,State.m_n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- copy
|
|
State.m_xbase=x;
|
|
State.m_xbase.Resize(State.m_n);
|
|
//--- function call
|
|
MinCGSuggestStep(State,0.0);
|
|
//--- allocation
|
|
State.m_rstate.ia.Resize(2);
|
|
State.m_rstate.ra.Resize(3);
|
|
//--- change value
|
|
State.m_rstate.stage=-1;
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGRestartFrom(CMinCGState &State,CRowDouble &x)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=State.m_n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,State.m_n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- copy
|
|
State.m_xbase=x;
|
|
State.m_xbase.Resize(State.m_n);
|
|
//--- function call
|
|
MinCGSuggestStep(State,0.0);
|
|
//--- allocation
|
|
State.m_rstate.ia.Resize(2);
|
|
State.m_rstate.ra.Resize(3);
|
|
//--- change value
|
|
State.m_rstate.stage=-1;
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine submits request for termination of running |
|
|
//| optimizer. It should be called from user-supplied callback when |
|
|
//| user decides that it is time to "smoothly" terminate |
|
|
//| optimization process. As result, optimizer stops at point which |
|
|
//| was "current accepted" when termination request was submitted and|
|
|
//| returns error code 8 (successful termination). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - optimizer structure |
|
|
//| NOTE: after request for termination optimizer may perform |
|
|
//| several additional calls to user-supplied callbacks. It |
|
|
//| does NOT guarantee to stop immediately - it just guarantees|
|
|
//| that these additional calls will be discarded later. |
|
|
//| NOTE: calling this function on optimizer which is NOT running |
|
|
//| will have no effect. |
|
|
//| NOTE: multiple calls to this function are possible. First call is|
|
|
//| counted, subsequent calls are silently ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGRequestTermination(CMinCGState &State)
|
|
{
|
|
State.m_userterminationneeded=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Faster version of MinCGSetPrecDiag(), for time-critical parts of |
|
|
//| code, without safety checks. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetPrecDiagFast(CMinCGState &State,double &d[])
|
|
{
|
|
//--- change values
|
|
State.m_prectype=2;
|
|
State.m_vcnt=0;
|
|
State.m_innerresetneeded=true;
|
|
//--- copy
|
|
State.m_diagh=d;
|
|
State.m_diaghl2=vector<double>::Zeros(State.m_n);
|
|
State.m_diagh.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetPrecDiagFast(CMinCGState &State,CRowDouble &d)
|
|
{
|
|
//--- change values
|
|
State.m_prectype=2;
|
|
State.m_vcnt=0;
|
|
State.m_innerresetneeded=true;
|
|
//--- copy
|
|
State.m_diagh=d;
|
|
State.m_diaghl2=vector<double>::Zeros(State.m_n);
|
|
State.m_diagh.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets low-rank preconditioner for Hessian matrix |
|
|
//| H=D+V'*C*V, where: |
|
|
//| * H is a Hessian matrix, which is approximated by D/V/C |
|
|
//| * D=D1+D2 is a diagonal matrix, which includes two positive |
|
|
//| definite terms: |
|
|
//| * constant term D1 (is not updated or infrequently updated) |
|
|
//| * variable term D2 (can be cheaply updated from iteration to |
|
|
//| iteration) |
|
|
//| * V is a low-rank correction |
|
|
//| * C is a diagonal factor of low-rank correction |
|
|
//| Preconditioner P is calculated using approximate Woodburry |
|
|
//| formula: |
|
|
//| P = D^(-1) - D^(-1)*V'*(C^(-1)+V*D1^(-1)*V')^(-1)*V*D^(-1) |
|
|
//| = D^(-1) - D^(-1)*VC'*VC*D^(-1), |
|
|
//| where |
|
|
//| VC = sqrt(B)*V |
|
|
//| B = (C^(-1)+V*D1^(-1)*V')^(-1) |
|
|
//| Note that B is calculated using constant term (D1) only, which |
|
|
//| allows us to update D2 without recalculation of B or VC. Such |
|
|
//| preconditioner is exact when D2 is zero. When D2 is non-zero, it |
|
|
//| is only approximation, but very good and cheap one. |
|
|
//| This function accepts D1, V, C. |
|
|
//| D2 is set to zero by default. |
|
|
//| Cost of this update is O(N*VCnt*VCnt), but D2 can be updated in |
|
|
//| just O(N) by MinCGSetPrecVarPart. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetPrecLowRankFast(CMinCGState &State,double &d1[],
|
|
double &c[],CMatrixDouble &v,
|
|
const int vcnt)
|
|
{
|
|
CRowDouble D=d1;
|
|
CRowDouble C=c;
|
|
MinCGSetPrecLowRankFast(State,D,C,v,vcnt);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetPrecLowRankFast(CMinCGState &State,CRowDouble &d1,
|
|
CRowDouble &c,CMatrixDouble &v,
|
|
const int vcnt)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int i_=0;
|
|
int j=0;
|
|
int k=0;
|
|
int n=0;
|
|
double t=0;
|
|
//--- create matrix
|
|
CMatrixDouble b;
|
|
//--- check
|
|
if(vcnt==0)
|
|
{
|
|
//--- function call
|
|
MinCGSetPrecDiagFast(State,d1);
|
|
return;
|
|
}
|
|
//--- initialization
|
|
n=State.m_n;
|
|
b=matrix<double>::Zeros(vcnt,vcnt);
|
|
//--- function call
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_vcorr,vcnt,n);
|
|
State.m_prectype=2;
|
|
State.m_vcnt=vcnt;
|
|
State.m_innerresetneeded=true;
|
|
//--- copy
|
|
State.m_diagh=d1;
|
|
State.m_diaghl2=vector<double>::Zeros(n);
|
|
State.m_diagh.Resize(n);
|
|
//--- calculation
|
|
for(i=0; i<vcnt; i++)
|
|
{
|
|
vector<double> temp=v[i]/d1.ToVector();
|
|
for(j=i; j<vcnt; j++)
|
|
{
|
|
t=temp.Dot(v[j]+0);
|
|
b.Set(i,j,t);
|
|
}
|
|
b.Set(i,i,b.Get(i,i)+1.0/c[i]);
|
|
}
|
|
//--- check
|
|
if(!CTrFac::SPDMatrixCholeskyRec(b,0,vcnt,true,State.m_work0))
|
|
{
|
|
State.m_vcnt=0;
|
|
return;
|
|
}
|
|
//--- calculation
|
|
State.m_vcorr=v;
|
|
for(i=0; i<vcnt; i++)
|
|
{
|
|
//--- change values
|
|
for(j=0; j<i; j++)
|
|
{
|
|
t=b.Get(j,i);
|
|
State.m_vcorr.Row(i,State.m_vcorr[i]-State.m_vcorr[j]*t);
|
|
}
|
|
t=1.0/b.Get(i,i);
|
|
//--- change values
|
|
State.m_vcorr.Row(i,State.m_vcorr[i]*t);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function updates variable part (diagonal matrix D2) |
|
|
//| of low-rank preconditioner. |
|
|
//| This update is very cheap and takes just O(N) time. |
|
|
//| It has no effect with default preconditioner. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetPrecVarPart(CMinCGState &State,double &d2[])
|
|
{
|
|
//--- copy
|
|
State.m_diaghl2=d2;
|
|
State.m_diaghl2.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGSetPrecVarPart(CMinCGState &State,CRowDouble &d2)
|
|
{
|
|
//--- copy
|
|
State.m_diaghl2=d2;
|
|
State.m_diaghl2.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Clears request fileds (to be sure that we don't forgot to clear |
|
|
//| something) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::ClearRequestFields(CMinCGState &State)
|
|
{
|
|
//--- change values
|
|
State.m_needf=false;
|
|
State.m_needfg=false;
|
|
State.m_xupdated=false;
|
|
State.m_lsstart=false;
|
|
State.m_lsend=false;
|
|
State.m_algpowerup=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates preconditioned product H^(-1)*x and |
|
|
//| stores result back into X. Work0[] and Work1[] are used as |
|
|
//| temporaries (size must be at least N; this function doesn't |
|
|
//| allocate arrays). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::PreconditionedMultiply(CMinCGState &State,CRowDouble &x,
|
|
CRowDouble &work0,CRowDouble &work1)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int n=0;
|
|
int vcnt=0;
|
|
double v=0;
|
|
int i_=0;
|
|
//--- initialization
|
|
n=State.m_n;
|
|
vcnt=State.m_vcnt;
|
|
//--- check
|
|
if(State.m_prectype==0)
|
|
return;
|
|
//--- check
|
|
if(State.m_prectype==3)
|
|
{
|
|
x*=State.m_s.Pow(2)+0;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_prectype==2,__FUNCTION__+": internal error (unexpected PrecType)"))
|
|
return;
|
|
//--- handle part common for VCnt=0 and VCnt<>0
|
|
x/=State.m_diagh.ToVector()+State.m_diaghl2.ToVector();
|
|
//--- if VCnt>0
|
|
if(vcnt>0)
|
|
{
|
|
//--- calculation work0
|
|
for(i=0; i<vcnt; i++)
|
|
work0.Set(i,CAblasF::RDotVR(n,x,State.m_vcorr,i));
|
|
//--- calculation work1
|
|
State.m_work1=vector<double>::Zeros(n);
|
|
for(i=0; i<vcnt; i++)
|
|
{
|
|
v=work0[i];
|
|
for(i_=0; i_<=n-1; i_++)
|
|
State.m_work1.Add(i_,v*State.m_vcorr.Get(i,i_));
|
|
}
|
|
//--- change x
|
|
x-=State.m_work1.ToVector()/(State.m_diagh+State.m_diaghl2);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates preconditioned product x'*H^(-1)*y. |
|
|
//| Work0[] and Work1[] are used as temporaries (size must be at |
|
|
//| least N; this function doesn't allocate arrays). |
|
|
//+------------------------------------------------------------------+
|
|
double CMinCG::PreconditionedMultiply2(CMinCGState &State,CRowDouble &x,
|
|
CRowDouble &y,CRowDouble &work0,
|
|
CRowDouble &work1)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int i=0;
|
|
int n=0;
|
|
int vcnt=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
int i_=0;
|
|
//--- initialization
|
|
n=State.m_n;
|
|
vcnt=State.m_vcnt;
|
|
//--- no preconditioning
|
|
if(State.m_prectype==0)
|
|
{
|
|
result=x.Dot(y);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//--- check
|
|
if(State.m_prectype==3)
|
|
{
|
|
result=x.Dot(y.ToVector()*State.m_s.Pow(2.0));
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_prectype==2,__FUNCTION__+": internal error (unexpected PrecType)"))
|
|
return(EMPTY_VALUE);
|
|
//--- low rank preconditioning
|
|
vector<double> temp=State.m_diagh+State.m_diaghl2;
|
|
result=x.Dot(y.ToVector()/temp);
|
|
//--- check
|
|
if(vcnt>0)
|
|
{
|
|
//--- prepare arrays
|
|
work0=x.ToVector()/temp;
|
|
work1=y.ToVector()/temp;
|
|
for(i=0; i<=vcnt-1; i++)
|
|
{
|
|
//--- calculation
|
|
v0=CAblasF::RDotVR(n,work0,State.m_vcorr,i);
|
|
v1=CAblasF::RDotVR(n,work1,State.m_vcorr,i);
|
|
//--- get result
|
|
result-=v0*v1;
|
|
}
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal initialization subroutine |
|
|
//+------------------------------------------------------------------+
|
|
void CMinCG::MinCGInitInternal(const int n,const double diffstep,
|
|
CMinCGState &State)
|
|
{
|
|
//--- initialization
|
|
State.m_teststep=0;
|
|
State.m_smoothnessguardlevel=0;
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,0,0,false);
|
|
State.m_n=n;
|
|
State.m_diffstep=diffstep;
|
|
State.m_lastgoodstep=0;
|
|
//--- function call
|
|
MinCGSetCond(State,0,0,0,0);
|
|
//--- function call
|
|
MinCGSetXRep(State,false);
|
|
//--- function call
|
|
MinCGSetDRep(State,false);
|
|
//--- function call
|
|
MinCGSetStpMax(State,0);
|
|
//--- function call
|
|
MinCGSetCGType(State,-1);
|
|
//--- function call
|
|
MinCGSetPrecDefault(State);
|
|
//--- allocation
|
|
State.m_xk.Resize(n);
|
|
State.m_dk.Resize(n);
|
|
State.m_xn.Resize(n);
|
|
State.m_dn.Resize(n);
|
|
State.m_x.Resize(n);
|
|
State.m_d.Resize(n);
|
|
State.m_g.Resize(n);
|
|
State.m_work0.Resize(n);
|
|
State.m_work1.Resize(n);
|
|
State.m_yk.Resize(n);
|
|
State.m_xbase.Resize(n);
|
|
State.m_s=vector<double>::Ones(n);
|
|
State.m_invs=vector<double>::Ones(n);
|
|
State.m_lastscaleused=vector<double>::Ones(n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| NOTES: |
|
|
//| 1. This function has two different implementations: one which |
|
|
//| uses exact (analytical) user-supplied gradient, and one which|
|
|
//| uses function value only and numerically differentiates |
|
|
//| function in order to obtain gradient. |
|
|
//| Depending on the specific function used to create optimizer |
|
|
//| object (either MinCGCreate() for analytical gradient or |
|
|
//| MinCGCreateF() for numerical differentiation) you should |
|
|
//| choose appropriate variant of MinCGOptimize() - one which |
|
|
//| accepts function AND gradient or one which accepts function |
|
|
//| ONLY. |
|
|
//| Be careful to choose variant of MinCGOptimize() which |
|
|
//| corresponds to your optimization scheme! Table below lists |
|
|
//| different combinations of callback (function/gradient) passed |
|
|
//| to MinCGOptimize() and specific function used to create |
|
|
//| optimizer. |
|
|
//| | USER PASSED TO MinCGOptimize() |
|
|
//| CREATED WITH | function only | function and gradient |
|
|
//| ------------------------------------------------------------ |
|
|
//| MinCGCreateF() | work FAIL |
|
|
//| MinCGCreate() | FAIL work |
|
|
//| Here "FAIL" denotes inappropriate combinations of optimizer |
|
|
//| creation function and MinCGOptimize() version. Attemps to use |
|
|
//| such combination (for example, to create optimizer with |
|
|
//| MinCGCreateF() and to pass gradient information to |
|
|
//| MinCGOptimize()) will lead to exception being thrown. Either |
|
|
//| you did not pass gradient when it WAS needed or you passed |
|
|
//| gradient when it was NOT needed. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinCG::MinCGIteration(CMinCGState &State)
|
|
{
|
|
//--- create variables
|
|
int n=0;
|
|
int i=0;
|
|
double betak=0;
|
|
double v=0;
|
|
double vv=0;
|
|
int i_=0;
|
|
int label=-1;
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
//
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
n=State.m_rstate.ia[0];
|
|
i=State.m_rstate.ia[1];
|
|
betak=State.m_rstate.ra[0];
|
|
v=State.m_rstate.ra[1];
|
|
vv=State.m_rstate.ra[2];
|
|
}
|
|
else
|
|
{
|
|
n=359;
|
|
i=-58;
|
|
betak=-919;
|
|
v=-909;
|
|
vv=81;
|
|
}
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
case 4:
|
|
label=4;
|
|
break;
|
|
case 5:
|
|
label=5;
|
|
break;
|
|
case 6:
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
label=7;
|
|
break;
|
|
case 8:
|
|
label=8;
|
|
break;
|
|
case 9:
|
|
label=9;
|
|
break;
|
|
case 10:
|
|
label=10;
|
|
break;
|
|
case 11:
|
|
label=11;
|
|
break;
|
|
case 12:
|
|
label=12;
|
|
break;
|
|
case 13:
|
|
label=13;
|
|
break;
|
|
case 14:
|
|
label=14;
|
|
break;
|
|
case 15:
|
|
label=15;
|
|
break;
|
|
case 16:
|
|
label=16;
|
|
break;
|
|
case 17:
|
|
label=17;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
//--- Prepare
|
|
n=State.m_n;
|
|
State.m_terminationneeded=false;
|
|
State.m_userterminationneeded=false;
|
|
State.m_repterminationtype=0;
|
|
State.m_repiterationscount=0;
|
|
State.m_repnfev=0;
|
|
State.m_debugrestartscount=0;
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,n,1,State.m_smoothnessguardlevel>0);
|
|
State.m_lastscaleused=State.m_s;
|
|
State.m_invs=State.m_s.Pow(-1.0)+0;
|
|
//--- Check, that transferred derivative value is right
|
|
ClearRequestFields(State);
|
|
if(!(State.m_diffstep==0.0 && State.m_teststep>0.0))
|
|
label=18;
|
|
else
|
|
label=20;
|
|
break;
|
|
}
|
|
//--- Main loop
|
|
while(label>=0)
|
|
switch(label)
|
|
{
|
|
case 20:
|
|
if(!COptServ::SmoothnessMonitorCheckGradientATX0(State.m_smonitor,State.m_xbase,State.m_s,State.m_s,State.m_s,false,State.m_teststep))
|
|
{
|
|
label=21;
|
|
break;
|
|
}
|
|
State.m_x=State.m_smonitor.m_x;
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
State.m_needfg=false;
|
|
State.m_smonitor.m_fi.Set(0,State.m_f);
|
|
State.m_smonitor.m_j.Row(0,State.m_g);
|
|
label=20;
|
|
break;
|
|
case 21:
|
|
case 18:
|
|
//--- Preparations continue:
|
|
//--- * set XK
|
|
//--- * calculate F/G
|
|
//--- * set DK to -G
|
|
//--- * powerup algo (it may change preconditioner)
|
|
//--- * apply preconditioner to DK
|
|
//--- * report update of X
|
|
//--- * check stopping conditions for G
|
|
State.m_x=State.m_xbase;
|
|
State.m_xk=State.m_x;
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=22;
|
|
break;
|
|
}
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_needfg=false;
|
|
label=23;
|
|
break;
|
|
case 22:
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_fbase=State.m_f;
|
|
i=0;
|
|
case 24:
|
|
if(i>n-1)
|
|
{
|
|
label=26;
|
|
break;
|
|
}
|
|
v=State.m_x[i];
|
|
State.m_x.Set(i,v-State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_fm2=State.m_f;
|
|
State.m_x.Set(i,v-0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=4;
|
|
label=-1;
|
|
break;
|
|
case 4:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,v+0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=5;
|
|
label=-1;
|
|
break;
|
|
case 5:
|
|
State.m_fp1=State.m_f;
|
|
State.m_x.Set(i,v+State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=6;
|
|
label=-1;
|
|
break;
|
|
case 6:
|
|
State.m_fp2=State.m_f;
|
|
State.m_x.Set(i,v);
|
|
State.m_g.Set(i,(8*(State.m_fp1-State.m_fm1)-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[i]));
|
|
i++;
|
|
label=24;
|
|
break;
|
|
case 26:
|
|
State.m_f=State.m_fbase;
|
|
State.m_needf=false;
|
|
case 23:
|
|
if(!State.m_drep)
|
|
{
|
|
label=27;
|
|
break;
|
|
}
|
|
//--- Report algorithm powerup (if needed)
|
|
ClearRequestFields(State);
|
|
State.m_algpowerup=true;
|
|
State.m_rstate.stage=7;
|
|
label=-1;
|
|
break;
|
|
case 7:
|
|
State.m_algpowerup=false;
|
|
case 27:
|
|
COptServ::TrimPrepare(State.m_f,State.m_trimthreshold);
|
|
State.m_dk=State.m_g.ToVector()*(-1.0);
|
|
PreconditionedMultiply(State,State.m_dk,State.m_work0,State.m_work1);
|
|
if(!State.m_xrep)
|
|
{
|
|
label=29;
|
|
break;
|
|
}
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=8;
|
|
label=-1;
|
|
break;
|
|
case 8:
|
|
State.m_xupdated=false;
|
|
case 29:
|
|
if(State.m_terminationneeded || State.m_userterminationneeded)
|
|
{
|
|
//--- Combined termination point for "internal" termination by TerminationNeeded flag
|
|
//--- and for "user" termination by MinCGRequestTermination() (UserTerminationNeeded flag).
|
|
//--- In this location rules for both of methods are same, thus only one exit point is needed.
|
|
State.m_xn=State.m_xk;
|
|
State.m_repterminationtype=8;
|
|
return(false);
|
|
}
|
|
v=MathPow(State.m_g*State.m_s+0,2.0).Sum();
|
|
if(MathSqrt(v)<=State.m_epsg)
|
|
{
|
|
State.m_xn=State.m_xk;
|
|
State.m_repterminationtype=4;
|
|
return(false);
|
|
}
|
|
State.m_repnfev=1;
|
|
State.m_k=0;
|
|
State.m_fold=State.m_f;
|
|
//--- Choose initial step.
|
|
//--- Apply preconditioner, if we have something other than default.
|
|
if(State.m_prectype==2 || State.m_prectype==3)
|
|
{
|
|
//--- because we use preconditioner, step length must be equal
|
|
//--- to the norm of DK
|
|
v=State.m_dk.Dot(State.m_dk);
|
|
State.m_lastgoodstep=MathSqrt(v);
|
|
}
|
|
else
|
|
{
|
|
//--- No preconditioner is used, we try to use suggested step
|
|
if(State.m_suggestedstep>0.0)
|
|
State.m_lastgoodstep=State.m_suggestedstep;
|
|
else
|
|
State.m_lastgoodstep=1.0;
|
|
}
|
|
//--- Main cycle
|
|
State.m_rstimer=m_rscountdownlen;
|
|
case 31:
|
|
//--- * clear reset flag
|
|
//--- * clear termination flag
|
|
//--- * store G[k] for later calculation of Y[k]
|
|
//--- * prepare starting point and direction and step length for line search
|
|
State.m_innerresetneeded=false;
|
|
State.m_terminationneeded=false;
|
|
State.m_yk=State.m_g*(-1.0)+0;
|
|
State.m_d=State.m_dk;
|
|
State.m_x=State.m_xk;
|
|
State.m_mcstage=0;
|
|
State.m_stp=1.0;
|
|
CLinMin::LinMinNormalized(State.m_d,State.m_stp,n);
|
|
if(State.m_lastgoodstep!=0.0)
|
|
State.m_stp=State.m_lastgoodstep;
|
|
State.m_curstpmax=State.m_stpmax;
|
|
//--- Report beginning of line search (if needed)
|
|
//--- Terminate algorithm, if user request was detected
|
|
if(!State.m_drep)
|
|
{
|
|
label=33;
|
|
break;
|
|
}
|
|
ClearRequestFields(State);
|
|
State.m_lsstart=true;
|
|
State.m_rstate.stage=9;
|
|
label=-1;
|
|
break;
|
|
case 9:
|
|
State.m_lsstart=false;
|
|
case 33:
|
|
if(State.m_terminationneeded)
|
|
{
|
|
State.m_xn=State.m_x;
|
|
State.m_repterminationtype=8;
|
|
return(false);
|
|
}
|
|
//--- Minimization along D
|
|
COptServ::SmoothnessMonitorStartLineSearch1u(State.m_smonitor,State.m_s,State.m_invs,State.m_x,State.m_f,State.m_g);
|
|
CLinMin::MCSrch(n,State.m_x,State.m_f,State.m_g,State.m_d,State.m_stp,State.m_curstpmax,m_gtol,State.m_mcinfo,State.m_nfev,State.m_work0,State.m_lstate,State.m_mcstage);
|
|
case 35:
|
|
if(State.m_mcstage==0)
|
|
{
|
|
label=36;
|
|
break;
|
|
}
|
|
//--- Calculate function/gradient using either
|
|
//--- analytical gradient supplied by user
|
|
//--- or finite difference approximation.
|
|
//--- "Trim" function in order to handle near-singularity points.
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=37;
|
|
break;
|
|
}
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=10;
|
|
label=-1;
|
|
break;
|
|
case 10:
|
|
State.m_needfg=false;
|
|
label=38;
|
|
break;
|
|
case 37:
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=11;
|
|
label=-1;
|
|
break;
|
|
case 11:
|
|
State.m_fbase=State.m_f;
|
|
i=0;
|
|
case 39:
|
|
if(i>n-1)
|
|
{
|
|
label=41;
|
|
break;
|
|
}
|
|
v=State.m_x[i];
|
|
State.m_x.Set(i,v-State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=12;
|
|
label=-1;
|
|
break;
|
|
case 12:
|
|
State.m_fm2=State.m_f;
|
|
State.m_x.Set(i,v-0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=13;
|
|
label=-1;
|
|
break;
|
|
case 13:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,v+0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=14;
|
|
label=-1;
|
|
break;
|
|
case 14:
|
|
State.m_fp1=State.m_f;
|
|
State.m_x.Set(i,v+State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=15;
|
|
label=-1;
|
|
break;
|
|
case 15:
|
|
State.m_fp2=State.m_f;
|
|
State.m_x.Set(i,v);
|
|
State.m_g.Set(i,(8*(State.m_fp1-State.m_fm1)-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[i]));
|
|
i++;
|
|
label=39;
|
|
break;
|
|
case 41:
|
|
State.m_f=State.m_fbase;
|
|
State.m_needf=false;
|
|
case 38:
|
|
COptServ::SmoothnessMonitorEnqueuePoint1u(State.m_smonitor,State.m_s,State.m_invs,State.m_d,State.m_stp,State.m_x,State.m_f,State.m_g);
|
|
COptServ::TrimFunction(State.m_f,State.m_g,n,State.m_trimthreshold);
|
|
//--- Call MCSRCH again
|
|
CLinMin::MCSrch(n,State.m_x,State.m_f,State.m_g,State.m_d,State.m_stp,State.m_curstpmax,m_gtol,State.m_mcinfo,State.m_nfev,State.m_work0,State.m_lstate,State.m_mcstage);
|
|
label=35;
|
|
break;
|
|
case 36:
|
|
COptServ::SmoothnessMonitorFinalizeLineSearch(State.m_smonitor);
|
|
//---*terminate algorithm if "user" request for detected
|
|
//--- * report end of line search
|
|
//--- * store current point to XN
|
|
//--- * report iteration
|
|
//---*terminate algorithm if "internal" request was detected
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
State.m_xn=State.m_xk;
|
|
State.m_repterminationtype=8;
|
|
return(false);
|
|
}
|
|
if(!State.m_drep)
|
|
{
|
|
label=42;
|
|
break;
|
|
}
|
|
//--- Report end of line search (if needed)
|
|
ClearRequestFields(State);
|
|
State.m_lsend=true;
|
|
State.m_rstate.stage=16;
|
|
label=-1;
|
|
break;
|
|
case 16:
|
|
State.m_lsend=false;
|
|
case 42:
|
|
State.m_xn=State.m_x;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=44;
|
|
break;
|
|
}
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=17;
|
|
label=-1;
|
|
break;
|
|
case 17:
|
|
State.m_xupdated=false;
|
|
case 44:
|
|
if(State.m_terminationneeded)
|
|
{
|
|
State.m_xn=State.m_x;
|
|
State.m_repterminationtype=8;
|
|
return(false);
|
|
}
|
|
//--- Line search is finished.
|
|
//--- * calculate BetaK
|
|
//--- * calculate DN
|
|
//--- * update timers
|
|
//--- * calculate step length:
|
|
//--- * LastScaledStep is ALWAYS calculated because it is used in the stopping criteria
|
|
//--- * LastGoodStep is updated only when MCINFO is equal to 1 (Wolfe conditions hold).
|
|
//--- See below for more explanation.
|
|
if(State.m_mcinfo==1 && !State.m_innerresetneeded)
|
|
{
|
|
//--- Standard Wolfe conditions hold
|
|
//--- Calculate Y[K] and D[K]'*Y[K]
|
|
State.m_yk+=State.m_g;
|
|
vv=State.m_yk.Dot(State.m_dk);
|
|
//--- Calculate BetaK according to DY formula
|
|
v=PreconditionedMultiply2(State,State.m_g,State.m_g,State.m_work0,State.m_work1);
|
|
State.m_betady=v/vv;
|
|
//--- Calculate BetaK according to HS formula
|
|
v=PreconditionedMultiply2(State,State.m_g,State.m_yk,State.m_work0,State.m_work1);
|
|
State.m_betahs=v/vv;
|
|
//--- Choose BetaK
|
|
if(State.m_cgtype==0)
|
|
betak=State.m_betady;
|
|
if(State.m_cgtype==1)
|
|
betak=MathMax(0,MathMin(State.m_betady,State.m_betahs));
|
|
}
|
|
else
|
|
{
|
|
//--- Something is wrong (may be function is too wild or too flat)
|
|
//--- or we just have to restart algo.
|
|
//--- We'll set BetaK=0, which will restart CG algorithm.
|
|
//--- We can stop later (during normal checks) if stopping conditions are met.
|
|
//
|
|
betak=0;
|
|
State.m_debugrestartscount++;
|
|
}
|
|
if(State.m_repiterationscount>0 && State.m_repiterationscount%(3+n)==0)
|
|
{
|
|
//--- clear Beta every N iterations
|
|
betak=0;
|
|
}
|
|
if(State.m_mcinfo==1 || State.m_mcinfo==5)
|
|
State.m_rstimer=m_rscountdownlen;
|
|
else
|
|
State.m_rstimer--;
|
|
State.m_dn=State.m_g*(-1.0)+0;
|
|
PreconditionedMultiply(State,State.m_dn,State.m_work0,State.m_work1);
|
|
State.m_dn+=State.m_dk.ToVector()*betak;
|
|
State.m_lastscaledstep=MathPow(State.m_d.ToVector()/State.m_s.ToVector(),2.0).Sum();
|
|
State.m_lastscaledstep=State.m_stp*MathSqrt(State.m_lastscaledstep);
|
|
if(State.m_mcinfo==1)
|
|
{
|
|
//--- Step is good (Wolfe conditions hold), update LastGoodStep.
|
|
//--- This check for MCINFO=1 is essential because sometimes in the
|
|
//--- constrained optimization setting we may take very short steps
|
|
//--- (like 1E-15) because we were very close to boundary of the
|
|
//--- feasible area. Such short step does not mean that we've converged
|
|
//--- to the solution - it was so short because we were close to the
|
|
//--- boundary and there was a limit on step length.
|
|
//--- So having such short step is quite normal situation. However, we
|
|
//--- should NOT start next iteration from step whose initial length is
|
|
//--- estimated as 1E-15 because it may lead to the failure of the
|
|
//--- linear minimizer (step is too short, function does not changes,
|
|
//--- line search stagnates).
|
|
State.m_lastgoodstep=CAblasF::RDotV2(n,State.m_d);
|
|
State.m_lastgoodstep=State.m_stp*MathSqrt(State.m_lastgoodstep);
|
|
}
|
|
//--- Update information.
|
|
//--- Check stopping conditions.
|
|
v=MathPow(State.m_g.ToVector()*State.m_s.ToVector(),2.0).Sum();
|
|
if(!MathIsValidNumber(v) || !MathIsValidNumber(State.m_f))
|
|
{
|
|
//--- Abnormal termination - infinities in function/gradient
|
|
State.m_repterminationtype=-8;
|
|
return(false);
|
|
}
|
|
State.m_repnfev+=State.m_nfev;
|
|
State.m_repiterationscount++;
|
|
if(State.m_repiterationscount>=State.m_maxits && State.m_maxits>0)
|
|
{
|
|
//--- Too many iterations
|
|
State.m_repterminationtype=5;
|
|
return(false);
|
|
}
|
|
if(MathSqrt(v)<=State.m_epsg)
|
|
{
|
|
//--- Gradient is small enough
|
|
State.m_repterminationtype=4;
|
|
return(false);
|
|
}
|
|
if(!State.m_innerresetneeded)
|
|
{
|
|
//--- These conditions are checked only when no inner reset was requested by user
|
|
if((double)(State.m_fold-State.m_f)<=(double)(State.m_epsf*MathMax(MathAbs(State.m_fold),MathMax(MathAbs(State.m_f),1.0))))
|
|
{
|
|
//--- F(k+1)-F(k) is small enough
|
|
State.m_repterminationtype=1;
|
|
return(false);
|
|
}
|
|
if(State.m_lastscaledstep<=State.m_epsx)
|
|
{
|
|
//--- X(k+1)-X(k) is small enough
|
|
State.m_repterminationtype=2;
|
|
return(false);
|
|
}
|
|
}
|
|
if(State.m_rstimer<=0)
|
|
{
|
|
//--- Too many subsequent restarts
|
|
State.m_repterminationtype=7;
|
|
return(false);
|
|
}
|
|
//--- Shift Xk/Dk, update other information
|
|
State.m_xk=State.m_xn;
|
|
State.m_dk=State.m_dn;
|
|
State.m_fold=State.m_f;
|
|
State.m_k++;
|
|
label=31;
|
|
break;
|
|
case 32:
|
|
return(false);
|
|
}
|
|
//--- Saving State
|
|
State.m_rstate.ia.Set(0,n);
|
|
State.m_rstate.ia.Set(1,i);
|
|
State.m_rstate.ra.Set(0,betak);
|
|
State.m_rstate.ra.Set(1,v);
|
|
State.m_rstate.ra.Set(2,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is a SNNLS(Specialized Non-Negative Least Squares)|
|
|
//| m_solver. |
|
|
//| It solves problems of the form | A*x - b | ^ 2 => min subject to |
|
|
//| non-negativity constraints on SOME components of x, with |
|
|
//| structured A(first NS columns are just unit matrix, next ND |
|
|
//| columns store dense part). |
|
|
//| This m_solver is suited for solution of many sequential NNLS |
|
|
//| subproblems - it keeps track of previously allocated memory and |
|
|
//| reuses it as much as possible. |
|
|
//+------------------------------------------------------------------+
|
|
struct CSNNLSSolver
|
|
{
|
|
int m_debugmaxinnerits;
|
|
int m_nd;
|
|
int m_nr;
|
|
int m_ns;
|
|
double m_debugflops;
|
|
bool m_nnc[];
|
|
CRowInt m_rdtmprowmap;
|
|
CRowDouble m_b;
|
|
CRowDouble m_cb;
|
|
CRowDouble m_cborg;
|
|
CRowDouble m_crb;
|
|
CRowDouble m_cx;
|
|
CRowDouble m_d;
|
|
CRowDouble m_diagaa;
|
|
CRowDouble m_dx;
|
|
CRowDouble m_g;
|
|
CRowDouble m_r;
|
|
CRowDouble m_regdiag;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CRowDouble m_tmp2;
|
|
CRowDouble m_tmpcholesky;
|
|
CRowDouble m_trdd;
|
|
CRowDouble m_xn;
|
|
CRowDouble m_xp;
|
|
CMatrixDouble m_densea;
|
|
CMatrixDouble m_tmpca;
|
|
CMatrixDouble m_tmplq;
|
|
CMatrixDouble m_trda;
|
|
CSNNLSSolver(void);
|
|
~CSNNLSSolver(void) {}
|
|
void Copy(const CSNNLSSolver &obj);
|
|
//--- overloading
|
|
void operator=(const CSNNLSSolver &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CSNNLSSolver::CSNNLSSolver(void)
|
|
{
|
|
m_debugmaxinnerits=0;
|
|
m_nd=0;
|
|
m_nr=0;
|
|
m_ns=0;
|
|
m_debugflops=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSNNLSSolver::Copy(const CSNNLSSolver &obj)
|
|
{
|
|
m_debugmaxinnerits=obj.m_debugmaxinnerits;
|
|
m_nd=obj.m_nd;
|
|
m_nr=obj.m_nr;
|
|
m_ns=obj.m_ns;
|
|
m_debugflops=obj.m_debugflops;
|
|
ArrayCopy(m_nnc,obj.m_nnc);
|
|
m_rdtmprowmap=obj.m_rdtmprowmap;
|
|
m_b=obj.m_b;
|
|
m_cb=obj.m_cb;
|
|
m_cborg=obj.m_cborg;
|
|
m_crb=obj.m_crb;
|
|
m_cx=obj.m_cx;
|
|
m_d=obj.m_d;
|
|
m_diagaa=obj.m_diagaa;
|
|
m_dx=obj.m_dx;
|
|
m_g=obj.m_g;
|
|
m_r=obj.m_r;
|
|
m_regdiag=obj.m_regdiag;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_tmp2=obj.m_tmp2;
|
|
m_tmpcholesky=obj.m_tmpcholesky;
|
|
m_trdd=obj.m_trdd;
|
|
m_xn=obj.m_xn;
|
|
m_xp=obj.m_xp;
|
|
m_densea=obj.m_densea;
|
|
m_tmpca=obj.m_tmpca;
|
|
m_tmplq=obj.m_tmplq;
|
|
m_trda=obj.m_trda;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Specialized Non-Negative Least Squares |
|
|
//+------------------------------------------------------------------+
|
|
class CSNNLS
|
|
{
|
|
public:
|
|
static void SNNLSInit(int nsmax,int ndmax,int nrmax,CSNNLSSolver &s);
|
|
static void SNNLSSetProblem(CSNNLSSolver &s,CMatrixDouble &a,CRowDouble &b,int ns,int nd,int nr);
|
|
static void SNNLSDropNNC(CSNNLSSolver &s,int idx);
|
|
static void SNNLSSolve(CSNNLSSolver &s,CRowDouble &x);
|
|
|
|
private:
|
|
static void FuncGradU(CSNNLSSolver &s,CRowDouble &x,CRowDouble &r,CRowDouble &g,double &f);
|
|
static void Func(CSNNLSSolver &s,CRowDouble &x,double &f);
|
|
static void TRDPrepare(CSNNLSSolver &s,CRowDouble &x,CRowDouble &diag,double lambdav,CRowDouble &trdd,CMatrixDouble &trda,CRowDouble &m_tmp0,CRowDouble &m_tmp1,CRowDouble &tmp2,CMatrixDouble &tmplq);
|
|
static void TRDSolve(CRowDouble &trdd,CMatrixDouble &trda,int ns,int nd,CRowDouble &d);
|
|
static void TRDFixVariable(CRowDouble &trdd,CMatrixDouble &trda,int ns,int nd,int idx,CRowDouble &tmp);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine is used to initialize SNNLS m_solver. |
|
|
//| By default, empty NNLS problem is produced, but we allocated |
|
|
//| enough space to store problems with NSMax + NDMax columns and |
|
|
//| NRMax rows. It is good place to provide algorithm with initial |
|
|
//| estimate of the space requirements, although you may |
|
|
//| underestimate problem size or even pass zero estimates - in this |
|
|
//| case buffer variables will be resized automatically when you set |
|
|
//| NNLS problem. |
|
|
//| Previously allocated buffer variables are reused as much as |
|
|
//| possible. This function does not clear structure completely, it |
|
|
//| tries to preserve as much dynamically allocated memory as |
|
|
//| possible. |
|
|
//+------------------------------------------------------------------+
|
|
void CSNNLS::SNNLSInit(int nsmax,
|
|
int ndmax,
|
|
int nrmax,
|
|
CSNNLSSolver &s)
|
|
{
|
|
s.m_ns=0;
|
|
s.m_nd=0;
|
|
s.m_nr=0;
|
|
s.m_debugflops=0.0;
|
|
s.m_debugmaxinnerits=0;
|
|
s.m_densea.Resize(nrmax,ndmax);
|
|
s.m_tmpca.Resize(nrmax,ndmax);
|
|
s.m_b.Resize(nrmax);
|
|
ArrayResize(s.m_nnc,nsmax+ndmax);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine is used to set NNLS problem: |
|
|
//| ([ 1 | ] [ ] [ ]) ^ 2 |
|
|
//| ([ 1 | ] [ ] [ ]) |
|
|
//| min([ 1 | Ad ] * [ x ] - [ b ]) s.m_t. x >= 0 |
|
|
//| ([ | ] [ ] [ ]) |
|
|
//| ([ | ] [ ] [ ]) |
|
|
//| where: |
|
|
//| * identity matrix has NS*NS size(NS <= NR, NS can be zero) |
|
|
//| * dense matrix Ad has NR*ND size |
|
|
//| * b is NR * 1 vector |
|
|
//| * x is(NS + ND) * 1 vector |
|
|
//| * all elements of x are Non - Negative(this constraint can |
|
|
//| be removed later by calling SNNLSDropNNC() function) |
|
|
//| Previously allocated buffer variables are reused as much as |
|
|
//| possible. After you set problem, you can solve it with |
|
|
//| SNNLSSolve(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SNNLS m_solver, must be initialized with SNNLSInit() |
|
|
//| call |
|
|
//| A - array[NR, ND], dense part of the system |
|
|
//| B - array[NR], right part |
|
|
//| NS - size of the sparse part of the system, 0<=NS<=NR |
|
|
//| ND - size of the dense part of the system, ND >= 0 |
|
|
//| NR - rows count, NR > 0 |
|
|
//| NOTE 1: You can have NS + ND = 0, m_solver will correctly accept |
|
|
//| such combination and return empty array as problem |
|
|
//| solution. |
|
|
//+------------------------------------------------------------------+
|
|
void CSNNLS::SNNLSSetProblem(CSNNLSSolver &s,
|
|
CMatrixDouble &a,
|
|
CRowDouble &b,
|
|
int ns,
|
|
int nd,
|
|
int nr)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nd>=0,__FUNCTION__+": ND<0"))
|
|
return;
|
|
if(!CAp::Assert(ns>=0,__FUNCTION__+": NS<0"))
|
|
return;
|
|
if(!CAp::Assert(nr>0,__FUNCTION__+": NR<=0"))
|
|
return;
|
|
if(!CAp::Assert(ns<=nr,__FUNCTION__+": NS>NR"))
|
|
return;
|
|
if(!CAp::Assert(a.Rows()>=nr || nd==0,__FUNCTION__+": rows(A)<NR"))
|
|
return;
|
|
if(!CAp::Assert(a.Cols()>=nd,__FUNCTION__+": cols(A)<ND"))
|
|
return;
|
|
if(!CAp::Assert(b.Size()>=nr,__FUNCTION__+": length(B)<NR"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(a,nr,nd),__FUNCTION__+": A contains INF/NAN"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(b,nr),__FUNCTION__+": B contains INF/NAN"))
|
|
return;
|
|
//--- Copy problem
|
|
s.m_ns=ns;
|
|
s.m_nd=nd;
|
|
s.m_nr=nr;
|
|
if(nd>0)
|
|
{
|
|
s.m_densea=a;
|
|
s.m_densea.Resize(nr,nd);
|
|
}
|
|
s.m_b=b;
|
|
s.m_b.Resize(nr);
|
|
ArrayResize(s.m_nnc,ns+nd);
|
|
ArrayInitialize(s.m_nnc,true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine drops non-negativity constraint from the problem|
|
|
//| set by SNNLSSetProblem() call. This function must be called AFTER|
|
|
//| problem is set, because each SetProblem() call resets constraints|
|
|
//| to their default State (all constraints are present). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SNNLS m_solver, must be initialized with SNNLSInit() |
|
|
//| call, problem must be set with SNNLSSetProblem() |
|
|
//| call. |
|
|
//| Idx - constraint index, 0 <= IDX < NS + ND |
|
|
//+------------------------------------------------------------------+
|
|
void CSNNLS::SNNLSDropNNC(CSNNLSSolver &s,int idx)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(idx>=0,__FUNCTION__+": Idx<0"))
|
|
return;
|
|
if(!CAp::Assert(idx<s.m_ns+s.m_nd,__FUNCTION__+": Idx>=NS+ND"))
|
|
return;
|
|
|
|
s.m_nnc[idx]=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine is used to solve NNLS problem. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SNNLS m_solver, must be initialized with SNNLSInit() |
|
|
//| call and problem must be set up with |
|
|
//| SNNLSSetProblem() call. |
|
|
//| X - possibly preallocated buffer, automatically resized|
|
|
//| if needed |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[NS + ND], solution |
|
|
//| NOTE: |
|
|
//| 1. You can have NS + ND = 0, m_solver will correctly accept such |
|
|
//| combination and return empty array as problem solution. |
|
|
//| 2. Internal field S.DebugFLOPS contains rough estimate of FLOPs|
|
|
//| used to solve problem. It can be used for debugging |
|
|
//| purposes. This field is real - valued. |
|
|
//+------------------------------------------------------------------+
|
|
void CSNNLS::SNNLSSolve(CSNNLSSolver &s,CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int ns=s.m_ns;
|
|
int nd=s.m_nd;
|
|
int nr=s.m_nr;
|
|
bool wasactivation=false;
|
|
double lambdav=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v=0;
|
|
int outerits=0;
|
|
int innerits=0;
|
|
int maxouterits=0;
|
|
double xtol=0;
|
|
double kicklength=0;
|
|
bool kickneeded=false;
|
|
double f0=0;
|
|
double f1=0;
|
|
double dnrm=0;
|
|
int actidx=0;
|
|
double stp=0;
|
|
double stpmax=0;
|
|
//--- Prepare
|
|
s.m_debugflops=0.0;
|
|
//--- Handle special cases:
|
|
//--- * NS+ND=0
|
|
//--- * ND=0
|
|
if(ns+nd==0)
|
|
return;
|
|
if(nd==0)
|
|
{
|
|
x=vector<double>::Zeros(ns);
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
if(s.m_nnc[i])
|
|
x.Set(i,MathMax(s.m_b[i],0.0));
|
|
else
|
|
x.Set(i,s.m_b[i]);
|
|
}
|
|
return;
|
|
}
|
|
//--- Main cycle of BLEIC-SNNLS algorithm.
|
|
//--- Below we assume that ND>0.
|
|
x=vector<double>::Zeros(ns+nd);
|
|
s.m_xn.Resize(ns+nd);
|
|
s.m_xp.Resize(ns+nd);
|
|
s.m_g.Resize(ns+nd);
|
|
s.m_d.Resize(ns+nd);
|
|
s.m_r.Resize(nr);
|
|
s.m_diagaa.Resize(nd);
|
|
s.m_regdiag=vector<double>::Ones(ns+nd);
|
|
s.m_dx.Resize(ns+nd);
|
|
lambdav=1.0E6*CMath::m_machineepsilon;
|
|
maxouterits=10;
|
|
outerits=0;
|
|
innerits=0;
|
|
xtol=1.0E3*CMath::m_machineepsilon;
|
|
kicklength=MathSqrt(CMath::m_minrealnumber);
|
|
while(true)
|
|
{
|
|
//--- Initial check for correctness of X
|
|
for(i=0; i<ns+nd; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(!s.m_nnc[i] || x[i]>=0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
}
|
|
//--- Calculate gradient G and constrained descent direction D
|
|
FuncGradU(s,x,s.m_r,s.m_g,f0);
|
|
for(i=0; i<ns+nd; i++)
|
|
{
|
|
if(s.m_nnc[i] && x[i]==0.0 && s.m_g[i]>0.0)
|
|
s.m_d.Set(i,0.0);
|
|
else
|
|
s.m_d.Set(i,-s.m_g[i]);
|
|
}
|
|
//--- Decide whether we need "kick" stage: special stage
|
|
//--- that moves us away from boundary constraints which are
|
|
//--- not strictly active (i.e. such constraints that x[i]=0.0 and d[i]>0).
|
|
//--- If we need kick stage, we make a kick - and restart iteration.
|
|
//--- If not, after this block we can rely on the fact that
|
|
//--- for all x[i]=0.0 we have d[i]=0.0
|
|
//--- NOTE: we do not increase outer iterations counter here
|
|
kickneeded=false;
|
|
for(i=0; i<ns+nd; i++)
|
|
{
|
|
if(s.m_nnc[i] && x[i]==0.0 && s.m_d[i]>0.0)
|
|
kickneeded=true;
|
|
}
|
|
if(kickneeded)
|
|
{
|
|
//--- Perform kick.
|
|
//--- Restart.
|
|
//--- Do not increase iterations counter.
|
|
for(i=0; i<ns+nd; i++)
|
|
{
|
|
if(x[i]==0.0 && s.m_d[i]>0.0)
|
|
x.Add(i,kicklength);
|
|
}
|
|
continue;
|
|
}
|
|
//--- Newton phase
|
|
//--- Reduce problem to constrained triangular form and perform Newton
|
|
//--- steps with quick activation of constrants (triangular form is
|
|
//--- updated in order to handle changed constraints).
|
|
s.m_xp=x;
|
|
TRDPrepare(s,x,s.m_regdiag,lambdav,s.m_trdd,s.m_trda,s.m_tmp0,s.m_tmp1,s.m_tmp2,s.m_tmplq);
|
|
while(true)
|
|
{
|
|
//--- Skip if debug limit on inner iterations count is turned on.
|
|
if(s.m_debugmaxinnerits>0 && innerits>=s.m_debugmaxinnerits)
|
|
break;
|
|
//--- Prepare step vector.
|
|
FuncGradU(s,x,s.m_r,s.m_g,f0);
|
|
for(i=0; i<ns+nd; i++)
|
|
{
|
|
if(s.m_nnc[i] && x[i]==0.0)
|
|
s.m_d.Set(i,0.0);
|
|
else
|
|
s.m_d.Set(i,-s.m_g[i]);
|
|
}
|
|
TRDSolve(s.m_trdd,s.m_trda,ns,nd,s.m_d);
|
|
//--- Perform unconstrained trial step and compare function values.
|
|
for(i=0; i<ns+nd; i++)
|
|
{
|
|
s.m_xn.Set(i,x[i]+s.m_d[i]);
|
|
}
|
|
Func(s,s.m_xn,f1);
|
|
if(f1>=f0)
|
|
break;
|
|
//--- Calculate length of D, maximum step and component which is
|
|
//--- activated by this step. Break if D is exactly zero.
|
|
dnrm=CAblasF::RDotV2(ns+nd,s.m_d);
|
|
dnrm=MathSqrt(dnrm);
|
|
actidx=-1;
|
|
stpmax=1.0E50;
|
|
for(i=0; i<ns+nd; i++)
|
|
{
|
|
if(s.m_nnc[i] && s.m_d[i]<0.0)
|
|
{
|
|
v=stpmax;
|
|
stpmax=CApServ::SafeMinPosRV(x[i],-s.m_d[i],stpmax);
|
|
if(stpmax<v)
|
|
actidx=i;
|
|
}
|
|
}
|
|
if(dnrm==0.0)
|
|
break;
|
|
//--- Perform constrained step and update X
|
|
//--- and triangular model.
|
|
stp=MathMin(1.0,stpmax);
|
|
for(i=0; i<ns+nd; i++)
|
|
{
|
|
v=x[i]+stp*s.m_d[i];
|
|
if(s.m_nnc[i])
|
|
v=MathMax(v,0.0);
|
|
s.m_xn.Set(i,v);
|
|
}
|
|
if(stp==stpmax && actidx>=0)
|
|
s.m_xn.Set(actidx,0.0);
|
|
wasactivation=false;
|
|
for(i=0; i<ns+nd; i++)
|
|
{
|
|
if(s.m_xn[i]==0.0 && x[i]!=0.0)
|
|
{
|
|
wasactivation=true;
|
|
TRDFixVariable(s.m_trdd,s.m_trda,ns,nd,i,s.m_tmpcholesky);
|
|
}
|
|
}
|
|
for(i=0; i<ns+nd; i++)
|
|
x.Set(i,s.m_xn[i]);
|
|
//--- Increment iterations counter.
|
|
//--- Terminate if no constraint was activated.
|
|
innerits++;
|
|
if(!wasactivation)
|
|
break;
|
|
}
|
|
//--- Update outer iterations counter.
|
|
//--- Break if necessary:
|
|
//--- * maximum number of outer iterations performed
|
|
//--- * relative change in X is small enough
|
|
outerits++;
|
|
if(outerits>=maxouterits)
|
|
break;
|
|
v=0;
|
|
for(i=0; i<ns+nd; i++)
|
|
{
|
|
v0=MathAbs(s.m_xp[i]);
|
|
v1=MathAbs(x[i]);
|
|
if(v0!=0.0 || v1!=0.0)
|
|
v=MathMax(v,MathAbs(x[i]-s.m_xp[i])/MathMax(v0,v1));
|
|
}
|
|
if(v<=xtol)
|
|
break;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates: |
|
|
//| * residual vector R = A * x - b |
|
|
//| * unconstrained gradient vector G |
|
|
//| * function value F = 0.5 * | R | ^ 2 |
|
|
//| R and G must have at least N elements. |
|
|
//+------------------------------------------------------------------+
|
|
void CSNNLS::FuncGradU(CSNNLSSolver &s,
|
|
CRowDouble &x,
|
|
CRowDouble &r,
|
|
CRowDouble &g,
|
|
double &f)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int nr=s.m_nr;
|
|
int nd=s.m_nd;
|
|
int ns=s.m_ns;
|
|
double v=0;
|
|
int i_=0;
|
|
int i1_=0;
|
|
//--- initialization
|
|
f=0;
|
|
|
|
for(i=0; i<nr; i++)
|
|
{
|
|
i1_=ns;
|
|
v=0.0;
|
|
for(i_=0; i_<nd; i_++)
|
|
v+=s.m_densea.Get(i,i_)*x[i_+i1_];
|
|
if(i<ns)
|
|
v+= x[i];
|
|
v-=s.m_b[i];
|
|
r.Set(i,v);
|
|
f+=0.5*v*v;
|
|
}
|
|
for(i=0; i<ns; i++)
|
|
g.Set(i,r[i]);
|
|
for(i=ns; i<ns+nd; i++)
|
|
g.Set(i,0.0);
|
|
for(i=0; i<nr; i++)
|
|
{
|
|
v=r[i];
|
|
i1_=-ns;
|
|
for(i_=ns; i_<ns+nd; i_++)
|
|
g.Add(i_,v*s.m_densea.Get(i,i_+i1_));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates function value F = 0.5 * | R | ^ 2 at X.|
|
|
//+------------------------------------------------------------------+
|
|
void CSNNLS::Func(CSNNLSSolver &s,CRowDouble &x,double &f)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int nr=s.m_nr;
|
|
int nd=s.m_nd;
|
|
int ns=s.m_ns;
|
|
double v=0;
|
|
int i_=0;
|
|
int i1_=0;
|
|
//--- initialization
|
|
f=0;
|
|
|
|
for(i=0; i<nr; i++)
|
|
{
|
|
i1_=ns;
|
|
v=0.0;
|
|
for(i_=0; i_<nd; i_++)
|
|
v+=s.m_densea.Get(i,i_)*x[i_+i1_];
|
|
if(i<ns)
|
|
v+= x[i];
|
|
v-=s.m_b[i];
|
|
f+=0.5*v*v;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CSNNLS::TRDPrepare(CSNNLSSolver &s,CRowDouble &x,
|
|
CRowDouble &diag,double lambdav,
|
|
CRowDouble &trdd,CMatrixDouble &trda,
|
|
CRowDouble &tmp0,CRowDouble &tmp1,
|
|
CRowDouble &tmp2,CMatrixDouble &tmplq)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int nr=s.m_nr;
|
|
int nd=s.m_nd;
|
|
int ns=s.m_ns;
|
|
double v=0;
|
|
double cs=0;
|
|
double sn=0;
|
|
double r=0;
|
|
//--- Triangular reduction
|
|
trdd.Resize(ns);
|
|
trda.Resize(ns+nd,nd);
|
|
tmplq.Resize(nd,nr+nd);
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
//--- Apply rotation to I-th row and corresponding row of
|
|
//--- regularizer. Here V is diagonal element of I-th row,
|
|
//--- which is set to 1.0 or 0.0 depending on variable
|
|
//--- status (constrained or not).
|
|
v=1.0;
|
|
if(s.m_nnc[i] && x[i]==0.0)
|
|
v=0.0;
|
|
CRotations::GenerateRotation(v,lambdav,cs,sn,r);
|
|
trdd.Set(i,cs*v+sn*lambdav);
|
|
for(j=0; j<nd; j++)
|
|
{
|
|
v=s.m_densea.Get(i,j);
|
|
trda.Set(i,j,cs*v);
|
|
tmplq.Set(j,i,-(sn*v));
|
|
}
|
|
}
|
|
for(j=0; j<nd; j++)
|
|
for(i=ns; i<nr; i++)
|
|
tmplq.Set(j,i,s.m_densea.Get(i,j));
|
|
|
|
for(j=0; j<nd; j++)
|
|
{
|
|
if(s.m_nnc[ns+j] && x[ns+j]==0.0)
|
|
{
|
|
//--- Variable is constrained, entire row is set to zero.
|
|
for(i=0; i<nr; i++)
|
|
tmplq.Set(j,i,0.0);
|
|
for(i=0; i<ns; i++)
|
|
trda.Set(i,j,0.0);
|
|
}
|
|
}
|
|
for(i=0; i<nd; i++)
|
|
{
|
|
for(j=0; j<nd; j++)
|
|
tmplq.Set(j,nr+i,0.0);
|
|
tmplq.Set(i,nr+i,lambdav*diag[i]);
|
|
}
|
|
tmp0=vector<double>::Zeros(nr+nd+1);
|
|
tmp1=vector<double>::Zeros(nr+nd+1);
|
|
tmp2=vector<double>::Zeros(nr+nd+1);
|
|
COrtFac::RMatrixLQBaseCase(tmplq,nd,nr+nd,tmp0,tmp1,tmp2);
|
|
for(i=0; i<nd; i++)
|
|
{
|
|
if(tmplq.Get(i,i)<0.0)
|
|
for(j=i; j<nd; j++)
|
|
tmplq.Mul(j,i,-1.0);
|
|
}
|
|
for(i=0; i<nd; i++)
|
|
{
|
|
for(j=0; j<=i; j++)
|
|
trda.Set(ns+j,i,tmplq.Get(i,j));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CSNNLS::TRDSolve(CRowDouble &trdd,CMatrixDouble &trda,
|
|
int ns,int nd,CRowDouble &d)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
//--- Solve U'*y=d first.
|
|
//--- This section includes two parts:
|
|
//--- * solve diagonal part of U'
|
|
//--- * solve dense part of U'
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
d.Mul(i,1.0/trdd[i]);
|
|
v=d[i];
|
|
for(j=0; j<nd; j++)
|
|
d.Add(ns+j,- v*trda.Get(i,j));
|
|
}
|
|
for(i=0; i<nd; i++)
|
|
{
|
|
d.Mul(ns+i,1.0/trda.Get(ns+i,i));
|
|
v=d[ns+i];
|
|
for(j=i+1; j<nd; j++)
|
|
d.Add(ns+j,- v*trda.Get(ns+i,j));
|
|
}
|
|
//--- Solve U*x=y then.
|
|
//--- This section includes two parts:
|
|
//--- * solve trailing triangular part of U
|
|
//--- * solve combination of diagonal and dense parts of U
|
|
for(i=nd-1; i>=0; i--)
|
|
{
|
|
v=0.0;
|
|
for(j=i+1; j<nd; j++)
|
|
v+= trda.Get(ns+i,j)*d[ns+j];
|
|
d.Set(ns+i,(d[ns+i]-v)/trda.Get(ns+i,i));
|
|
}
|
|
for(i=ns-1; i>=0; i--)
|
|
{
|
|
v=0.0;
|
|
for(j=0; j<nd; j++)
|
|
v+= trda.Get(i,j)*d[ns+j];
|
|
d.Set(i,(d[i]-v)/trdd[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CSNNLS::TRDFixVariable(CRowDouble &trdd,CMatrixDouble &trda,
|
|
int ns,int nd,int idx,CRowDouble &tmp)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double cs=0;
|
|
double sn=0;
|
|
double r=0;
|
|
double v=0;
|
|
double vv=0;
|
|
//--- check
|
|
if(!CAp::Assert(ns>=0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(nd>=0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(ns+nd>0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(idx>=0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(idx<ns+nd,__FUNCTION__+": integrity error"))
|
|
return;
|
|
tmp.Resize(nd);
|
|
//--- Depending on variable index, two situations are possible
|
|
if(idx<ns)
|
|
{
|
|
//--- We fix variable in the diagonal part of the model. It means
|
|
//--- that prior to fixing we have:
|
|
//--- ( | )
|
|
//--- ( D | )
|
|
//--- ( | )
|
|
//--- (-----| A )
|
|
//--- ( |0 )
|
|
//--- ( |00 )
|
|
//--- ( |000 )
|
|
//--- ( |0000 )
|
|
//--- ( |00000)
|
|
//--- then we replace idx-th column by zeros:
|
|
//--- (D 0 | )
|
|
//--- ( 0 | )
|
|
//--- ( 0 D| )
|
|
//--- (-----| A )
|
|
//--- ( | )
|
|
//--- ( | )
|
|
//--- ( | )
|
|
//--- and append row with unit element to bottom, in order to
|
|
//--- regularize problem
|
|
//--- (D 0 | )
|
|
//--- ( 0 | )
|
|
//--- ( 0 D| )
|
|
//--- (-----| A )
|
|
//--- ( | )
|
|
//--- ( | )
|
|
//--- ( | )
|
|
//--- (00100|00000) <- appended
|
|
//--- and then we nullify this row by applying rotations:
|
|
//--- (D 0 | )
|
|
//--- ( 0 | ) <- first rotation is applied here
|
|
//--- ( 0 D| )
|
|
//--- (-----| A ) <- subsequent rotations are applied to this row and rows below
|
|
//--- ( | )
|
|
//--- ( | )
|
|
//--- ( | )
|
|
//--- ( 0 | 0 ) <- as result, row becomes zero
|
|
//--- and triangular structure is preserved
|
|
if(nd==0)
|
|
{
|
|
//--- Quick exit for empty dense part
|
|
trdd.Set(idx,1.0);
|
|
return;
|
|
}
|
|
for(j=0; j<nd; j++)
|
|
{
|
|
//--- Apply first rotation
|
|
tmp.Set(j,trda.Get(idx,j));
|
|
trda.Set(idx,j,0.0);
|
|
}
|
|
trdd.Set(idx,1.0);
|
|
for(i=0; i<nd; i++)
|
|
{
|
|
if(tmp[i]!=0.0)
|
|
{
|
|
//--- Apply subsequent rotations with bottom triangular part of A
|
|
CRotations::GenerateRotation(trda.Get(ns+i,i),tmp[i],cs,sn,r);
|
|
for(j=i; j<nd; j++)
|
|
{
|
|
v=trda.Get(ns+i,j);
|
|
vv=tmp[j];
|
|
trda.Set(ns+i,j,v*cs+vv*sn);
|
|
tmp.Set(j,vv*cs-v*sn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- We fix variable in the dense part of the model. It means
|
|
//--- that prior to fixing we have:
|
|
//--- ( | )
|
|
//--- ( D | )
|
|
//--- ( | )
|
|
//--- (-----| A )
|
|
//--- ( |0 )
|
|
//--- ( |00 )
|
|
//--- ( |000 )
|
|
//--- ( |0000 )
|
|
//--- ( |00000)
|
|
//--- then we replace idx-th column by zeros:
|
|
//--- ( | 0 )
|
|
//--- ( D | 0 )
|
|
//--- ( | 0 )
|
|
//--- (-----|A 0 A)
|
|
//--- ( | 0 )
|
|
//--- ( | 0 )
|
|
//--- ( | 0 )
|
|
//--- and append row with unit element to bottom, in order to
|
|
//--- regularize problem
|
|
//--- ( | 0 )
|
|
//--- ( D | 0 )
|
|
//--- ( | 0 )
|
|
//--- (-----|A 0 A)
|
|
//--- ( | 0 )
|
|
//--- ( | 0 )
|
|
//--- ( | 0 )
|
|
//--- (00000|00100) <- appended
|
|
//--- and then we nullify this row by applying rotations:
|
|
//--- (D 0 | )
|
|
//--- ( 0 | )
|
|
//--- ( 0 D| )
|
|
//--- (-----| A )
|
|
//--- ( | )
|
|
//--- ( | ) <- first rotation is applied here
|
|
//--- ( | ) <- subsequent rotations are applied to rows below
|
|
//--- ( 0 | 0 ) <- as result, row becomes zero
|
|
//--- and triangular structure is preserved.
|
|
k=idx-ns;
|
|
for(i=0; i<ns+nd; i++)
|
|
trda.Set(i,k,0.0);
|
|
for(j=k+1; j<nd; j++)
|
|
{
|
|
//--- Apply first rotation
|
|
tmp.Set(j,trda.Get(idx,j));
|
|
trda.Set(idx,j,0.0);
|
|
}
|
|
trda.Set(idx,k,1.0);
|
|
for(i=k+1; i<nd; i++)
|
|
{
|
|
if(tmp[i]!=0.0)
|
|
{
|
|
//--- Apply subsequent rotations with bottom triangular part of A
|
|
CRotations::GenerateRotation(trda.Get(ns+i,i),tmp[i],cs,sn,r);
|
|
for(j=i; j<nd; j++)
|
|
{
|
|
v=trda.Get(ns+i,j);
|
|
vv=tmp[j];
|
|
trda.Set(ns+i,j,v*cs+vv*sn);
|
|
tmp.Set(j,vv*cs-v*sn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure describes set of linear constraints (boundary and |
|
|
//| general ones) which can be active and inactive. It also has |
|
|
//| functionality to work with current point and current gradient |
|
|
//| (determine active constraints, move current point, project |
|
|
//| gradient into constrained subspace, perform constrained |
|
|
//| preconditioning and so on. |
|
|
//| This structure is intended to be used by constrained optimizers |
|
|
//| for management of all constraint - related functionality. |
|
|
//| External code may access following internal fields of the |
|
|
//| structure: |
|
|
//| XC - stores current point, array[N]. can be accessed |
|
|
//| only in optimization mode |
|
|
//| CStatus - active set, array[N + NEC + NIC]: |
|
|
//| * CStatus[I]>0 I-Th constraint is in the active set|
|
|
//| * CStatus[I]=0 I-Th constraint is at the boundary, |
|
|
//| but inactive |
|
|
//| * CStatus[I]<0 I-Th constraint is far from the |
|
|
//| boundary (and inactive) |
|
|
//| * elements from 0 to N-1 correspond to boundary |
|
|
//| constraints |
|
|
//| * elements from N to N + NEC + NIC - 1 correspond |
|
|
//| to linear constraints |
|
|
//| * elements from N to N + NEC - 1 are always + 1 |
|
|
//| FeasInitPt - SASStartOptimization() sets this flag to True if, |
|
|
//| after enforcement of box constraints, initial point|
|
|
//| was feasible w.m_r.m_t. general linear constraints. |
|
|
//| This field can be used by unit test to validate |
|
|
//| some details of initial point calculation algorithm|
|
|
//| SparseBatch - indices of box constraints which are NOT included|
|
|
//| into dense batch(were activated at the beginning of|
|
|
//| the orthogonalization); SparseBatchSize elements |
|
|
//| are stored. |
|
|
//| SparseBatchSize - size of sparse batcj |
|
|
//| PDenseBatch, |
|
|
//| IDenseBatch, |
|
|
//| SDenseBatch - after call to SASRebuildBasis() these matrices |
|
|
//| store active constraints(except for ones included |
|
|
//| into sparse batch), reorthogonalized with respect |
|
|
//| to some inner product: |
|
|
//| a) for "P" batch - one given by preconditioner |
|
|
//| matrix (inverse Hessian) |
|
|
//| b) for "S" one - one given by square of the |
|
|
//| scale matrix |
|
|
//| c) for "I" one - traditional dot product |
|
|
//| array[DenseBatchSize, N + 1] |
|
|
//| All three matrices are linearly equivalent to each other. |
|
|
//| IMPORTANT: you have to call SASRebuildBasis() before accessing |
|
|
//| these arrays in order to make sure that they are up to|
|
|
//| date. |
|
|
//| DenseBatchSize - dense batch size(PBasis / SBasis / IBasis) |
|
|
//+------------------------------------------------------------------+
|
|
struct CSActiveSet
|
|
{
|
|
int m_algostate;
|
|
int m_basisage;
|
|
int m_densebatchsize;
|
|
int m_n;
|
|
int m_nec;
|
|
int m_nic;
|
|
int m_sparsebatchsize;
|
|
bool m_basisisready;
|
|
bool m_constraintschanged;
|
|
bool m_feasinitpt;
|
|
bool m_HasBndL[];
|
|
bool m_HasBndU[];
|
|
bool m_hasxc;
|
|
bool m_mtnew[];
|
|
bool m_rctmpisequality[];
|
|
CSNNLSSolver m_solver;
|
|
CRowInt m_cstatus;
|
|
CRowInt m_mtas;
|
|
CRowInt m_rctmpconstraintidx;
|
|
CRowInt m_sparsebatch;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_cdtmp;
|
|
CRowDouble m_corrtmp;
|
|
CRowDouble m_h;
|
|
CRowDouble m_mtx;
|
|
CRowDouble m_rctmpg;
|
|
CRowDouble m_rctmplambdas;
|
|
CRowDouble m_rctmprightpart;
|
|
CRowDouble m_rctmps;
|
|
CRowDouble m_s;
|
|
CRowDouble m_scntmp;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmpci;
|
|
CRowDouble m_tmpcp;
|
|
CRowDouble m_tmpcs;
|
|
CRowDouble m_tmpfeas;
|
|
CRowDouble m_tmpnormestimates;
|
|
CRowDouble m_tmpprodp;
|
|
CRowDouble m_tmpprods;
|
|
CRowDouble m_tmpreciph;
|
|
CRowDouble m_unitdiagonal;
|
|
CRowDouble m_xc;
|
|
CMatrixDouble m_cleic;
|
|
CMatrixDouble m_idensebatch;
|
|
CMatrixDouble m_pdensebatch;
|
|
CMatrixDouble m_rctmpdense0;
|
|
CMatrixDouble m_rctmpdense1;
|
|
CMatrixDouble m_sdensebatch;
|
|
CMatrixDouble m_tmpbasis;
|
|
CMatrixDouble m_tmpm0;
|
|
//--- constructor / destructor
|
|
CSActiveSet(void);
|
|
~CSActiveSet(void) {}
|
|
void Copy(const CSActiveSet &obj);
|
|
//--- overloading
|
|
void operator=(const CSActiveSet &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
CSActiveSet::CSActiveSet(void)
|
|
{
|
|
m_algostate=0;
|
|
m_basisage=0;
|
|
m_densebatchsize=0;
|
|
m_n=0;
|
|
m_nec=0;
|
|
m_nic=0;
|
|
m_sparsebatchsize=0;
|
|
m_basisisready=false;
|
|
m_constraintschanged=false;
|
|
m_feasinitpt=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSet::Copy(const CSActiveSet &obj)
|
|
{
|
|
m_algostate=obj.m_algostate;
|
|
m_basisage=obj.m_basisage;
|
|
m_densebatchsize=obj.m_densebatchsize;
|
|
m_n=obj.m_n;
|
|
m_nec=obj.m_nec;
|
|
m_nic=obj.m_nic;
|
|
m_sparsebatchsize=obj.m_sparsebatchsize;
|
|
m_basisisready=obj.m_basisisready;
|
|
m_constraintschanged=obj.m_constraintschanged;
|
|
m_feasinitpt=obj.m_feasinitpt;
|
|
ArrayCopy(m_HasBndL,obj.m_HasBndL);
|
|
ArrayCopy(m_HasBndU,obj.m_HasBndU);
|
|
m_hasxc=obj.m_hasxc;
|
|
ArrayCopy(m_mtnew,obj.m_mtnew);
|
|
ArrayCopy(m_rctmpisequality,obj.m_rctmpisequality);
|
|
m_solver=obj.m_solver;
|
|
m_cstatus=obj.m_cstatus;
|
|
m_mtas=obj.m_mtas;
|
|
m_rctmpconstraintidx=obj.m_rctmpconstraintidx;
|
|
m_sparsebatch=obj.m_sparsebatch;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_cdtmp=obj.m_cdtmp;
|
|
m_corrtmp=obj.m_corrtmp;
|
|
m_h=obj.m_h;
|
|
m_mtx=obj.m_mtx;
|
|
m_rctmpg=obj.m_rctmpg;
|
|
m_rctmplambdas=obj.m_rctmplambdas;
|
|
m_rctmprightpart=obj.m_rctmprightpart;
|
|
m_rctmps=obj.m_rctmps;
|
|
m_s=obj.m_s;
|
|
m_scntmp=obj.m_scntmp;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmpci=obj.m_tmpci;
|
|
m_tmpcp=obj.m_tmpcp;
|
|
m_tmpcs=obj.m_tmpcs;
|
|
m_tmpfeas=obj.m_tmpfeas;
|
|
m_tmpnormestimates=obj.m_tmpnormestimates;
|
|
m_tmpprodp=obj.m_tmpprodp;
|
|
m_tmpprods=obj.m_tmpprods;
|
|
m_tmpreciph=obj.m_tmpreciph;
|
|
m_unitdiagonal=obj.m_unitdiagonal;
|
|
m_xc=obj.m_xc;
|
|
m_cleic=obj.m_cleic;
|
|
m_idensebatch=obj.m_idensebatch;
|
|
m_pdensebatch=obj.m_pdensebatch;
|
|
m_rctmpdense0=obj.m_rctmpdense0;
|
|
m_rctmpdense1=obj.m_rctmpdense1;
|
|
m_sdensebatch=obj.m_sdensebatch;
|
|
m_tmpbasis=obj.m_tmpbasis;
|
|
m_tmpm0=obj.m_tmpm0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CSActiveSets
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const int m_maxbasisage ;
|
|
static const double m_maxbasisdecay;
|
|
static const double m_minnormseparation;
|
|
|
|
static void SASInit(int n,CSActiveSet &s);
|
|
static void SASSetScale(CSActiveSet &State,CRowDouble &s);
|
|
static void SASSetPrecDiag(CSActiveSet &State,CRowDouble &d);
|
|
static void SASSetBC(CSActiveSet &State,CRowDouble &bndl,CRowDouble &bndu);
|
|
static void SASSetLC(CSActiveSet &State,CMatrixDouble &c,CRowInt &ct,int k);
|
|
static void SASSetLCX(CSActiveSet &State,CMatrixDouble &cleic,int nec,int nic);
|
|
static bool SASStartOptimization(CSActiveSet &State,CRowDouble &x);
|
|
static void SASExploreDirection(CSActiveSet &State,CRowDouble &d,double &stpmax,int &cidx,double &vval);
|
|
static int SASMoveTo(CSActiveSet &State,CRowDouble &xn,bool needact,int cidx,double cval);
|
|
static void SASImmediateActivation(CSActiveSet &State,int cidx,double cval);
|
|
static void SASConstrainedDescent(CSActiveSet &State,CRowDouble &g,CRowDouble &d);
|
|
static void SASConstrainedDescentPrec(CSActiveSet &State,CRowDouble &g,CRowDouble &d);
|
|
static void SASConstrainedDirection(CSActiveSet &State,CRowDouble &d);
|
|
static void SASConstrainedDirectionPrec(CSActiveSet &State,CRowDouble &d);
|
|
static void SASCorrection(CSActiveSet &State,CRowDouble &x,double &penalty);
|
|
static double SASActiveLCPenalty1(CSActiveSet &State,CRowDouble &x);
|
|
static double SASScaledConstrainedNorm(CSActiveSet &State,CRowDouble &d);
|
|
static void SASStopOptimization(CSActiveSet &State);
|
|
static void SASReactivateConstraints(CSActiveSet &State,CRowDouble &gc);
|
|
static void SASReactivateConstraintsPrec(CSActiveSet &State,CRowDouble &gc);
|
|
static void SASRebuildBasis(CSActiveSet &State);
|
|
static void SASAppendToBasis(CSActiveSet &State,bool &newentries[]);
|
|
|
|
private:
|
|
static void ConstrainedDescent(CSActiveSet &State,CRowDouble &g,CRowDouble &h,CMatrixDouble &ha,bool normalize,CRowDouble &d);
|
|
static void ReactivateConstraints(CSActiveSet &State,CRowDouble &gc,CRowDouble &h);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const int CSActiveSets::m_maxbasisage=5;
|
|
const double CSActiveSets::m_maxbasisdecay=0.01;
|
|
const double CSActiveSets::m_minnormseparation=0.25;
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine is used to initialize active set. By default, |
|
|
//| empty N-variable model with no constraints is generated. |
|
|
//| Previously allocated buffer variables are reused as much as |
|
|
//| possible. |
|
|
//| Two use cases for this object are described below. |
|
|
//| CASE 1 - STEEPEST DESCENT : |
|
|
//| SASInit() |
|
|
//| repeat: |
|
|
//| SASReactivateConstraints() |
|
|
//| SASDescentDirection() |
|
|
//| SASExploreDirection() |
|
|
//| SASMoveTo() |
|
|
//| until convergence |
|
|
//| CASE 1 - PRECONDITIONED STEEPEST DESCENT: |
|
|
//| SASInit() |
|
|
//| repeat: |
|
|
//| SASReactivateConstraintsPrec() |
|
|
//| SASDescentDirectionPrec() |
|
|
//| SASExploreDirection() |
|
|
//| SASMoveTo() |
|
|
//| until convergence |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASInit(int n,CSActiveSet &s)
|
|
{
|
|
s.m_n=n;
|
|
s.m_algostate=0;
|
|
//--- Constraints
|
|
s.m_constraintschanged=true;
|
|
s.m_nec=0;
|
|
s.m_nic=0;
|
|
s.m_bndl=vector<double>::Full(n,AL_NEGINF);
|
|
s.m_bndu=vector<double>::Full(n,AL_POSINF);
|
|
ArrayResize(s.m_HasBndL,n);
|
|
ArrayResize(s.m_HasBndU,n);
|
|
ArrayInitialize(s.m_HasBndL,false);
|
|
ArrayInitialize(s.m_HasBndU,false);
|
|
//--- current point, scale
|
|
s.m_hasxc=false;
|
|
s.m_xc=vector<double>::Zeros(n);
|
|
s.m_s=vector<double>::Ones(n);
|
|
s.m_h=s.m_s;
|
|
//--- Other
|
|
s.m_unitdiagonal=s.m_s;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients for SAS object. |
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions (step size and gradient are scaled before comparison |
|
|
//| with tolerances). Scale of the I-Th variable is a translation |
|
|
//| invariant measure of: |
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| During orthogonalization phase, scale is used to calculate drop |
|
|
//| tolerances (whether vector is significantly non-zero or not). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients S[i] may |
|
|
//| be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASSetScale(CSActiveSet &State,CRowDouble &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==0,__FUNCTION__+": you may change scale only in modification mode"))
|
|
return;
|
|
if(!CAp::Assert(s.Size()>=State.m_n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
}
|
|
|
|
State.m_s=s.Abs()+0;
|
|
State.m_s.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: diagonal of approximate |
|
|
//| Hessian is used. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| D - diagonal of the approximate Hessian, array[0..m_n-1],|
|
|
//| (if larger, only leading N elements are used). |
|
|
//| NOTE 1: D[i] should be positive. Exception will be thrown |
|
|
//| otherwise. |
|
|
//| NOTE 2: you should pass diagonal of approximate Hessian - NOT |
|
|
//| ITS INVERSE. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASSetPrecDiag(CSActiveSet &State,CRowDouble &d)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==0,__FUNCTION__+": you may change preconditioner only in modification mode"))
|
|
return;
|
|
if(!CAp::Assert(d.Size()>=State.m_n,__FUNCTION__+": D is too short"))
|
|
return;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(d[i]),__FUNCTION__+": D contains infinite or NAN elements"))
|
|
return;
|
|
if(!CAp::Assert(d[i]>0.0,__FUNCTION__+": D contains non-positive elements"))
|
|
return;
|
|
}
|
|
|
|
State.m_h=d;
|
|
State.m_h.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets / changes boundary constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bounds, array[N]. If some(all) variables are |
|
|
//| unbounded, you may specify very small number or |
|
|
//| -INF. |
|
|
//| BndU - upper bounds, array[N]. If some(all) variables are |
|
|
//| unbounded, you may specify very large number or |
|
|
//| +INF. |
|
|
//| NOTE 1: it is possible to specify BndL.Set(i, BndU[i]. In this case|
|
|
//| I-th variable will be "frozen" at X[i]=BndL[i]=BndU[i]. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASSetBC(CSActiveSet &State,CRowDouble &bndl,
|
|
CRowDouble &bndu)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==0,__FUNCTION__+": you may change constraints only in modification mode"))
|
|
return;
|
|
|
|
int n=State.m_n;
|
|
if(!CAp::Assert(bndl.Size()>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
if(!CAp::Assert(bndu.Size()>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(bndl[i]) || AL_NEGINF==bndl[i],__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu[i]) || AL_POSINF==bndu[i],__FUNCTION__+": BndL contains NAN or -INF"))
|
|
return;
|
|
State.m_bndl.Set(i,bndl[i]);
|
|
State.m_HasBndL[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_bndu.Set(i,bndu[i]);
|
|
State.m_HasBndU[i]=MathIsValidNumber(bndu[i]);
|
|
}
|
|
|
|
State.m_constraintschanged=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets linear constraints for SAS object. |
|
|
//| Linear constraints are inactive by default(after initial |
|
|
//| creation). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - SAS structure |
|
|
//| C - linear constraints, array[K, N + 1]. Each row of C |
|
|
//| represents one constraint, either equality or |
|
|
//| inequality (see below): |
|
|
//| * first N elements correspond to coefficients, |
|
|
//| * last element corresponds to the right part. |
|
|
//| All elements of C(including right part) must be |
|
|
//| finite. |
|
|
//| CT - type of constraints, array[K]: |
|
|
//| * if CT[i] > 0, then I-Th constraint is |
|
|
//| C[i, *] * x >= C[i, n + 1] |
|
|
//| * if CT.Set(i, 0, then I-Th constraint is |
|
|
//| C[i, *] * x = C[i, n + 1] |
|
|
//| * if CT[i] < 0, then I-Th constraint is |
|
|
//| C[i, *] * x <= C[i, n + 1] |
|
|
//| K - number of equality / inequality constraints, K >= 0|
|
|
//| NOTE 1: linear(non - bound) constraints are satisfied only |
|
|
//| approximately: |
|
|
//| * there always exists some minor violation(about Epsilon |
|
|
//| in magnitude) due to rounding errors |
|
|
//| * numerical differentiation, if used, may lead to |
|
|
//| function evaluations outside of the feasible area, |
|
|
//| because algorithm does NOT change numerical |
|
|
//| differentiation formula according to linear |
|
|
//| constraints. If you want constraints to be satisfied |
|
|
//| exactly, try to reformulate your problem in such manner|
|
|
//| that all constraints will become boundary ones (this |
|
|
//| kind of constraints is always satisfied exactly, both |
|
|
//| in the final solution and in all intermediate points). |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASSetLC(CSActiveSet &State,CMatrixDouble &c,
|
|
CRowInt &ct,
|
|
int k)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==0,__FUNCTION__+": you may change constraints only in modification mode"))
|
|
return;
|
|
|
|
int n=State.m_n;
|
|
//--- First, check for errors in the inputs
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(!CAp::Assert(c.Cols()>=n+1 || k==0,__FUNCTION__+": Cols(C)<N+1"))
|
|
return;
|
|
if(!CAp::Assert(c.Rows()>=k,__FUNCTION__+": Rows(C)<K"))
|
|
return;
|
|
if(!CAp::Assert(ct.Size()>=k,__FUNCTION__+": Length(CT)<K"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(c,k,n+1),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
//--- Handle zero K
|
|
if(k==0)
|
|
{
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
State.m_constraintschanged=true;
|
|
return;
|
|
}
|
|
//--- Equality constraints are stored first, in the upper
|
|
//--- NEC rows of State.CLEIC matrix. Inequality constraints
|
|
//--- are stored in the next NIC rows.
|
|
//--- NOTE: we convert inequality constraints to the form
|
|
//--- A*x<=b before copying them.
|
|
State.m_cleic.Resize(k,n+1);
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
if(ct[i]==0)
|
|
{
|
|
State.m_cleic.Row(State.m_nec,c[i]+0);
|
|
State.m_nec++;
|
|
}
|
|
}
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
if(ct[i]!=0)
|
|
{
|
|
if(ct[i]>0)
|
|
State.m_cleic.Row(State.m_nec+State.m_nic,c[i]*(-1.0));
|
|
else
|
|
State.m_cleic.Row(State.m_nec+State.m_nic,c[i]+0);
|
|
State.m_nic++;
|
|
}
|
|
}
|
|
//--- Mark State as changed
|
|
State.m_constraintschanged=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Another variation of SASSetLC(), which accepts linear constraints|
|
|
//| using another representation. |
|
|
//| Linear constraints are inactive by default(after initial |
|
|
//| creation). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - SAS structure |
|
|
//| CLEIC - linear constraints, array[NEC + NIC, N + 1]. Each |
|
|
//| row of C represents one constraint: |
|
|
//| * first N elements correspond to coefficients, |
|
|
//| * last element corresponds to the right part. First|
|
|
//| NEC rows store equality constraints, next NIC - |
|
|
//| are inequality ones. All elements of C(including |
|
|
//| right part) must be finite. |
|
|
//| NEC - number of equality constraints, NEC >= 0 |
|
|
//| NIC - number of inequality constraints, NIC >= 0 |
|
|
//| NOTE 1: linear(non - bound) constraints are satisfied only |
|
|
//| approximately: |
|
|
//| * there always exists some minor violation(about Epsilon |
|
|
//| in magnitude) due to rounding errors |
|
|
//| * numerical differentiation, if used, may lead to |
|
|
//| function evaluations outside of the feasible area, |
|
|
//| because algorithm does NOT change numerical |
|
|
//| differentiation formula according to linear constraints|
|
|
//| If you want constraints to be satisfied exactly, try |
|
|
//| to reformulate your problem in such manner that all |
|
|
//| constraints will become boundary ones (this kind of |
|
|
//| constraints is always satisfied exactly, both in the |
|
|
//| final solution and in all intermediate points). |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASSetLCX(CSActiveSet &State,CMatrixDouble &cleic,
|
|
int nec,int nic)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==0,__FUNCTION__+": you may change constraints only in modification mode"))
|
|
return;
|
|
|
|
int n=State.m_n;
|
|
//--- First, check for errors in the inputs
|
|
if(!CAp::Assert(nec>=0,__FUNCTION__+": NEC<0"))
|
|
return;
|
|
if(!CAp::Assert(nic>=0,__FUNCTION__+": NIC<0"))
|
|
return;
|
|
if(!CAp::Assert(cleic.Cols()>=n+1 || nec+nic==0,__FUNCTION__+": Cols(CLEIC)<N+1"))
|
|
return;
|
|
if(!CAp::Assert(cleic.Rows()>=nec+nic,__FUNCTION__+": Rows(CLEIC)<NEC+NIC"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(cleic,nec+nic,n+1),__FUNCTION__+": CLEIC contains infinite or NaN values!"))
|
|
return;
|
|
//--- Store constraints
|
|
State.m_cleic=cleic;
|
|
State.m_cleic.Resize(nec+nic,n+1);
|
|
State.m_nec=nec;
|
|
State.m_nic=nic;
|
|
//--- Mark State as changed
|
|
State.m_constraintschanged=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine turns on optimization mode: |
|
|
//| 1. feasibility in X is enforced(in case X=S.XC and constraints |
|
|
//| have not changed, algorithm just uses X without any |
|
|
//| modifications at all) |
|
|
//| 2. constraints are marked as "candidate" or "inactive" |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| X - initial point(candidate), array[N]. It is expected |
|
|
//| that X contains only finite values(we do not check |
|
|
//| it). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - State is changed |
|
|
//| X - initial point can be changed to enforce feasibility|
|
|
//| RESULT: |
|
|
//| True in case feasible point was found(mode was changed |
|
|
//| to "optimization") |
|
|
//| False in case no feasible point was found(mode was not changed)|
|
|
//+------------------------------------------------------------------+
|
|
bool CSActiveSets::SASStartOptimization(CSActiveSet &State,
|
|
CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double vv=0;
|
|
double vc=0;
|
|
double vx=0;
|
|
int i_=0;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==0,__FUNCTION__+": already in optimization mode"))
|
|
return(false);
|
|
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
//--- Enforce feasibility and calculate set of "candidate"/"active" constraints.
|
|
//--- Always active equality constraints are marked as "active", all other constraints
|
|
//--- are marked as "candidate".
|
|
State.m_cstatus.Resize(n+nec+nic);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i])
|
|
if(State.m_bndl[i]>State.m_bndu[i])
|
|
return(result);
|
|
}
|
|
State.m_xc=x;
|
|
if(State.m_nec+State.m_nic>0)
|
|
{
|
|
//--- General linear constraints are present.
|
|
//--- Try to use fast code for feasible initial point with modest
|
|
//--- memory requirements.
|
|
State.m_tmp0=x;
|
|
State.m_cstatus.Fill(-1,0,n);
|
|
State.m_feasinitpt=true;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if((State.m_HasBndL[i] && State.m_HasBndU[i]) && State.m_bndl[i]==State.m_bndu[i])
|
|
{
|
|
State.m_tmp0.Set(i,State.m_bndl[i]);
|
|
State.m_cstatus.Set(i,1);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndL[i] && State.m_tmp0[i]<=State.m_bndl[i])
|
|
{
|
|
State.m_cstatus.Set(i,0);
|
|
State.m_tmp0.Set(i,State.m_bndl[i]);
|
|
}
|
|
if(State.m_HasBndU[i] && State.m_tmp0[i]>=State.m_bndu[i])
|
|
{
|
|
State.m_cstatus.Set(i,0);
|
|
State.m_tmp0.Set(i,State.m_bndu[i]);
|
|
}
|
|
}
|
|
for(i=0; i<State.m_nec+State.m_nic; i++)
|
|
{
|
|
v=-State.m_cleic.Get(i,n);
|
|
v0=0;
|
|
v1=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
vx=State.m_tmp0[j]/State.m_s[j];
|
|
vc=State.m_cleic.Get(i,j)*State.m_s[j];
|
|
v+= vx*vc;
|
|
v0+=CMath::Sqr(vx);
|
|
v1+=CMath::Sqr(vc);
|
|
}
|
|
vv=MathSqrt(v0)*MathSqrt(v1)*1000*CMath::m_machineepsilon;
|
|
if(i<State.m_nec)
|
|
{
|
|
State.m_cstatus.Set(n+i,1);
|
|
State.m_feasinitpt=State.m_feasinitpt && MathAbs(v)<vv;
|
|
}
|
|
else
|
|
{
|
|
State.m_feasinitpt=State.m_feasinitpt && v<vv;
|
|
if(v<(-vv))
|
|
State.m_cstatus.Set(n+i,-1);
|
|
else
|
|
State.m_cstatus.Set(n+i,0);
|
|
}
|
|
}
|
|
if(State.m_feasinitpt)
|
|
{
|
|
State.m_xc=State.m_tmp0;
|
|
}
|
|
//--- Fast code failed? Use general code with ~(N+NIC)^2 memory requirements
|
|
if(!State.m_feasinitpt)
|
|
{
|
|
State.m_tmp0.Resize(n);
|
|
State.m_tmpfeas.Resize(n+State.m_nic);
|
|
State.m_tmpm0.Resize(State.m_nec+State.m_nic,n+State.m_nic+1);
|
|
for(i=0; i<State.m_nec+State.m_nic; i++)
|
|
{
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_tmpm0.Set(i,i_,State.m_cleic.Get(i,i_));
|
|
for(j=n; j<n+State.m_nic; j++)
|
|
State.m_tmpm0.Set(i,j,0);
|
|
if(i>=State.m_nec)
|
|
State.m_tmpm0.Set(i,n+i-State.m_nec,1.0);
|
|
State.m_tmpm0.Set(i,n+State.m_nic,State.m_cleic.Get(i,n));
|
|
}
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_tmpfeas.Set(i_,State.m_xc[i_]);
|
|
for(i=0; i<State.m_nic; i++)
|
|
{
|
|
v=CAblasF::RDotVR(n,State.m_xc,State.m_cleic,i+State.m_nec);
|
|
State.m_tmpfeas.Set(i+n,MathMax(State.m_cleic.Get(i+State.m_nec,n)-v,0.0));
|
|
}
|
|
if(!COptServ::FindFeasiblePoint(State.m_tmpfeas,State.m_bndl,State.m_HasBndL,State.m_bndu,State.m_HasBndU,n,State.m_nic,State.m_tmpm0,State.m_nec+State.m_nic,1.0E-6,i,j))
|
|
return(result);
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_xc.Set(i_,State.m_tmpfeas[i_]);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if((State.m_HasBndL[i] && State.m_HasBndU[i]) && State.m_bndl[i]==State.m_bndu[i])
|
|
{
|
|
State.m_cstatus.Set(i,1);
|
|
continue;
|
|
}
|
|
if((State.m_HasBndL[i] && State.m_xc[i]==State.m_bndl[i]) || (State.m_HasBndU[i] && State.m_xc[i]==State.m_bndu[i]))
|
|
{
|
|
State.m_cstatus.Set(i,0);
|
|
continue;
|
|
}
|
|
State.m_cstatus.Set(i,-1);
|
|
}
|
|
for(i=0; i<State.m_nec; i++)
|
|
State.m_cstatus.Set(n+i,1);
|
|
for(i=0; i<State.m_nic; i++)
|
|
{
|
|
if(State.m_tmpfeas[n+i]==0.0)
|
|
State.m_cstatus.Set(n+State.m_nec+i,0);
|
|
else
|
|
State.m_cstatus.Set(n+State.m_nec+i,-1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Only box constraints are present, quick code can be used
|
|
for(i=0; i<n; i++)
|
|
{
|
|
State.m_cstatus.Set(i,-1);
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i] && State.m_bndl[i]==State.m_bndu[i])
|
|
{
|
|
State.m_cstatus.Set(i,1);
|
|
State.m_xc.Set(i,State.m_bndl[i]);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndL[i] && State.m_xc[i]<=State.m_bndl[i])
|
|
{
|
|
State.m_xc.Set(i,State.m_bndl[i]);
|
|
State.m_cstatus.Set(i,0);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndU[i] && State.m_xc[i]>=State.m_bndu[i])
|
|
{
|
|
State.m_xc.Set(i,State.m_bndu[i]);
|
|
State.m_cstatus.Set(i,0);
|
|
continue;
|
|
}
|
|
}
|
|
State.m_feasinitpt=true;
|
|
}
|
|
//--- Change State, allocate temporaries
|
|
result=true;
|
|
State.m_algostate=1;
|
|
State.m_basisisready=false;
|
|
State.m_hasxc=true;
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function explores search direction and calculates bound for |
|
|
//| step as well as information for activation of constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - SAS structure which stores current point and all |
|
|
//| other active set related information |
|
|
//| D - descent direction to explore |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| StpMax - upper limit on step length imposed by yet inactive |
|
|
//| constraints. Can be zero in case some constraints |
|
|
//| can be activated by zero step. Equal to some large |
|
|
//| value in case step is unlimited. |
|
|
//| CIdx - -1 for unlimited step, in [0, N + NEC + NIC) in |
|
|
//| case of limited step. |
|
|
//| VVal - value which is assigned to X[CIdx] during |
|
|
//| activation. For CIdx<0 or CIdx >= N some dummy |
|
|
//| value is assigned to this parameter. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASExploreDirection(CSActiveSet &State,CRowDouble &d,
|
|
double &stpmax,int &cidx,
|
|
double &vval)
|
|
{
|
|
//--- create variables
|
|
int n=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int i=0;
|
|
double prevmax=0;
|
|
double vc=0;
|
|
double vd=0;
|
|
int i_=0;
|
|
|
|
stpmax=0;
|
|
cidx=0;
|
|
vval=0;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": is not in optimization mode"))
|
|
return;
|
|
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
cidx=-1;
|
|
vval=0;
|
|
stpmax=1.0E50;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_cstatus[i]<=0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(!State.m_HasBndL[i] || State.m_xc[i]>=State.m_bndl[i],__FUNCTION__+": internal error - infeasible X"))
|
|
return;
|
|
if(!CAp::Assert(!State.m_HasBndU[i] || State.m_xc[i]<=State.m_bndu[i],__FUNCTION__+": internal error - infeasible X"))
|
|
return;
|
|
if(State.m_HasBndL[i] && d[i]<0.0)
|
|
{
|
|
prevmax=stpmax;
|
|
stpmax=CApServ::SafeMinPosRV(State.m_xc[i]-State.m_bndl[i],-d[i],stpmax);
|
|
if(stpmax<prevmax)
|
|
{
|
|
cidx=i;
|
|
vval=State.m_bndl[i];
|
|
}
|
|
}
|
|
if(State.m_HasBndU[i] && d[i]>0.0)
|
|
{
|
|
prevmax=stpmax;
|
|
stpmax=CApServ::SafeMinPosRV(State.m_bndu[i]-State.m_xc[i],d[i],stpmax);
|
|
if(stpmax<prevmax)
|
|
{
|
|
cidx=i;
|
|
vval=State.m_bndu[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for(i=nec; i<nec+nic; i++)
|
|
{
|
|
if(State.m_cstatus[n+i]<=0)
|
|
{
|
|
vc=0.0;
|
|
for(i_=0; i_<n; i_++)
|
|
{
|
|
vc+=State.m_cleic.Get(i,i_)*State.m_xc[i_];
|
|
}
|
|
vc=vc-State.m_cleic.Get(i,n);
|
|
vd=0.0;
|
|
for(i_=0; i_<n; i_++)
|
|
vd+=State.m_cleic.Get(i,i_)*d[i_];
|
|
if(vd<=0.0)
|
|
continue;
|
|
if(vc<0.0)
|
|
{
|
|
//--- XC is strictly feasible with respect to I-th constraint,
|
|
//--- we can perform non-zero step because there is non-zero distance
|
|
//--- between XC and bound.
|
|
prevmax=stpmax;
|
|
stpmax=CApServ::SafeMinPosRV(-vc,vd,stpmax);
|
|
if(stpmax<prevmax)
|
|
cidx=n+i;
|
|
}
|
|
else
|
|
{
|
|
//--- XC is at the boundary (or slightly beyond it), and step vector
|
|
//--- points beyond the boundary.
|
|
//--- The only thing we can do is to perform zero step and activate
|
|
//--- I-th constraint.
|
|
stpmax=0;
|
|
cidx=n+i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine moves current point to XN, which can be: |
|
|
//| a) point in the direction previously explored with |
|
|
//| SASExploreDirection() function (in this case NeedAct/CIdx/ |
|
|
//| CVal are used) |
|
|
//| b) point in arbitrary direction, not necessarily previously |
|
|
//| checked with SASExploreDirection() function. |
|
|
//| Step may activate one constraint. It is assumed than XN is |
|
|
//| approximately feasible(small error as large as several ulps |
|
|
//| is possible). Strict feasibility with respect to bound |
|
|
//| constraints is enforced during activation, feasibility with |
|
|
//| respect to general linear constraints is not enforced. |
|
|
//| This function activates boundary constraints, such that both is |
|
|
//| True: |
|
|
//| 1) XC[I] is not at the boundary |
|
|
//| 2) XN[I] is at the boundary or beyond it |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| XN - new point. |
|
|
//| NeedAct - True in case one constraint needs activation |
|
|
//| CIdx - index of constraint, in [0, N + NEC + NIC). Ignored|
|
|
//| if NeedAct is false. This value is calculated by |
|
|
//| SASExploreDirection(). |
|
|
//| CVal - for CIdx in [0, N) this field stores value which is|
|
|
//| assigned to XC[CIdx] during activation. CVal is |
|
|
//| ignored in other cases. This value is calculated by|
|
|
//| SASExploreDirection(). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - current point and list of active constraints are |
|
|
//| changed. |
|
|
//| RESULT: |
|
|
//| > 0, in case at least one inactive non - candidate constraint |
|
|
//| was activated |
|
|
//| =0, in case only "candidate" constraints were activated |
|
|
//| < 0, in case no constraints were activated by the step |
|
|
//| NOTE: in general case State.XC<>XN because activation of |
|
|
//| constraints may slightly change current point(to enforce |
|
|
//| feasibility). |
|
|
//+------------------------------------------------------------------+
|
|
int CSActiveSets::SASMoveTo(CSActiveSet &State,CRowDouble &xn,
|
|
bool needact,int cidx,double cval)
|
|
{
|
|
//--- create variables
|
|
int result=0;
|
|
int n=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int i=0;
|
|
bool wasactivation;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": is not in optimization mode"))
|
|
return(result);
|
|
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
//--- Save previous State, update current point
|
|
State.m_mtx.Resize(n);
|
|
State.m_mtas.Resize(n+nec+nic);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
State.m_mtx.Set(i,State.m_xc[i]);
|
|
State.m_xc.Set(i,xn[i]);
|
|
}
|
|
for(i=0; i<n+nec+nic ; i++)
|
|
State.m_mtas.Set(i,State.m_cstatus[i]);
|
|
//--- Activate constraints
|
|
ArrayResize(State.m_mtnew,n+nec+nic);
|
|
ArrayInitialize(State.m_mtnew,false);
|
|
wasactivation=false;
|
|
if(needact)
|
|
{
|
|
//--- Activation
|
|
//--- check
|
|
if(!CAp::Assert(cidx>=0 && cidx<n+nec+nic,__FUNCTION__+": incorrect CIdx"))
|
|
return(result);
|
|
if(cidx<n)
|
|
{
|
|
//--- CIdx in [0,N-1] means that bound constraint was activated.
|
|
//--- We activate it explicitly to avoid situation when roundoff-error
|
|
//--- prevents us from moving EXACTLY to x=CVal.
|
|
State.m_xc.Set(cidx,cval);
|
|
}
|
|
State.m_cstatus.Set(cidx,1);
|
|
State.m_mtnew[cidx]=true;
|
|
wasactivation=true;
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Post-check (some constraints may be activated because of numerical errors)
|
|
if((State.m_HasBndL[i] && State.m_xc[i]<=State.m_bndl[i]) && State.m_xc[i]!=State.m_mtx[i])
|
|
{
|
|
State.m_xc.Set(i,State.m_bndl[i]);
|
|
State.m_cstatus.Set(i,1);
|
|
State.m_mtnew[i]=true;
|
|
wasactivation=true;
|
|
}
|
|
if((State.m_HasBndU[i] && State.m_xc[i]>=State.m_bndu[i]) && State.m_xc[i]!=State.m_mtx[i])
|
|
{
|
|
State.m_xc.Set(i,State.m_bndu[i]);
|
|
State.m_cstatus.Set(i,1);
|
|
State.m_mtnew[i]=true;
|
|
wasactivation=true;
|
|
}
|
|
}
|
|
//--- Determine return status:
|
|
//--- * -1 in case no constraints were activated
|
|
//---* 0 in case only "candidate" constraints were activated
|
|
//---*+1 in case at least one "non-candidate" constraint was activated
|
|
if(wasactivation)
|
|
{
|
|
//--- Step activated one/several constraints, but sometimes it is spurious
|
|
//--- activation - RecalculateConstraints() tells us that constraint is
|
|
//--- inactive (negative Largrange multiplier), but step activates it
|
|
//--- because of numerical noise.
|
|
//--- This block of code checks whether step activated truly new constraints
|
|
//--- (ones which were not in the active set at the solution):
|
|
//--- * for non-boundary constraint it is enough to check that previous value
|
|
//--- of CStatus[i] is negative (=far from boundary), and new one is
|
|
//--- positive (=we are at the boundary, constraint is activated).
|
|
//--- * for boundary constraints previous criterion won't work. Each variable
|
|
//--- has two constraints, and simply checking their status is not enough -
|
|
//--- we have to correctly identify cases when we leave one boundary
|
|
//--- (PrevActiveSet[i]=0) and move to another boundary (CStatus[i]>0).
|
|
//--- Such cases can be identified if we compare previous X with new X.
|
|
//--- In case only "candidate" constraints were activated, result variable
|
|
//--- is set to 0. In case at least one new constraint was activated, result
|
|
//--- is set to 1.
|
|
result=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_cstatus[i]>0 && State.m_xc[i]!=State.m_mtx[i])
|
|
result=1;
|
|
}
|
|
for(i=n; i<n+State.m_nec+State.m_nic; i++)
|
|
{
|
|
if(State.m_mtas[i]<0 && State.m_cstatus[i]>0)
|
|
result=1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- No activation, return -1
|
|
result=-1;
|
|
}
|
|
//--- Update basis
|
|
SASAppendToBasis(State,State.m_mtnew);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine performs immediate activation of one constraint: |
|
|
//| *"immediate" means that we do not have to move to activate it |
|
|
//| * in case boundary constraint is activated, we enforce current |
|
|
//| point to be exactly at the boundary |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| CIdx - index of constraint, in [0, N + NEC + NIC). This |
|
|
//| value is calculated by SASExploreDirection(). |
|
|
//| CVal - for CIdx in [0, N) this field stores value which is|
|
|
//| assigned to XC[CIdx] during activation. CVal is |
|
|
//| ignored in other cases. This value is calculated |
|
|
//| by SASExploreDirection(). |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASImmediateActivation(CSActiveSet &State,int cidx,
|
|
double cval)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": is not in optimization mode"))
|
|
return;
|
|
|
|
if(cidx<State.m_n)
|
|
State.m_xc.Set(cidx,cval);
|
|
State.m_cstatus.Set(cidx,1);
|
|
CApServ::BVectorSetLengthAtLeast(State.m_mtnew,State.m_n+State.m_nec+State.m_nic);
|
|
ArrayInitialize(State.m_mtnew,false);
|
|
State.m_mtnew[cidx]=true;
|
|
SASAppendToBasis(State,State.m_mtnew);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates descent direction subject to current |
|
|
//| active set. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| G - array[N], gradient |
|
|
//| D - possibly prealocated buffer; automatically resized |
|
|
//| if needed. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D - descent direction projected onto current active set|
|
|
//| Components of D which correspond to active boundary|
|
|
//| constraints are forced to be exactly zero. In case |
|
|
//| D is non-zero, it is normalized to have unit norm. |
|
|
//| NOTE: in case active set has N active constraints (or more), |
|
|
//| descent direction is forced to be exactly zero. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASConstrainedDescent(CSActiveSet &State,
|
|
CRowDouble &g,
|
|
CRowDouble &d)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": is not in optimization mode"))
|
|
return;
|
|
//--- function call
|
|
SASRebuildBasis(State);
|
|
ConstrainedDescent(State,g,State.m_unitdiagonal,State.m_idensebatch,true,d);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates preconditioned descent direction |
|
|
//| subject to current active set. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| G - array[N], gradient |
|
|
//| D - possibly prealocated buffer; automatically resized |
|
|
//| if needed. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D - descent direction projected onto current active set|
|
|
//| Components of D which correspond to active boundary|
|
|
//| constraints are forced to be exactly zero. In case |
|
|
//| D is non-zero, it is normalized to have unit norm. |
|
|
//| NOTE: in case active set has N active constraints (or more), |
|
|
//| descent direction is forced to be exactly zero. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASConstrainedDescentPrec(CSActiveSet &State,
|
|
CRowDouble &g,
|
|
CRowDouble &d)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": is not in optimization mode"))
|
|
return;
|
|
//--- function call
|
|
SASRebuildBasis(State);
|
|
ConstrainedDescent(State,g,State.m_h,State.m_pdensebatch,true,d);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates projection of direction vector to |
|
|
//| current active set. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| D - array[N], direction |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D - direction projected onto current active set. |
|
|
//| Components of D which correspond to active boundary|
|
|
//| constraints are forced to be exactly zero. |
|
|
//| NOTE: in case active set has N active constraints (or more), |
|
|
//| descent direction is forced to be exactly zero. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASConstrainedDirection(CSActiveSet &State,
|
|
CRowDouble &d)
|
|
{
|
|
int i=0;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": is not in optimization mode"))
|
|
return;
|
|
//--- function call
|
|
SASRebuildBasis(State);
|
|
ConstrainedDescent(State,d,State.m_unitdiagonal,State.m_idensebatch,false,State.m_cdtmp);
|
|
//--- copy
|
|
d=State.m_cdtmp;
|
|
d*=(-1.0);
|
|
d.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates product of direction vector and |
|
|
//| preconditioner multiplied subject to current active set. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| D - array[N], direction |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D - preconditioned direction projected onto current |
|
|
//| active set. Components of D which correspond to |
|
|
//| active boundary constraints are forced to be |
|
|
//| exactly zero. |
|
|
//| NOTE: in case active set has N active constraints (or more), |
|
|
//| descent direction is forced to be exactly zero. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASConstrainedDirectionPrec(CSActiveSet &State,
|
|
CRowDouble &d)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": is not in optimization mode"))
|
|
return;
|
|
//--- function call
|
|
SASRebuildBasis(State);
|
|
ConstrainedDescent(State,d,State.m_h,State.m_pdensebatch,false,State.m_cdtmp);
|
|
//--- copy
|
|
d=State.m_cdtmp;
|
|
d*=(-1.0);
|
|
d.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine performs correction of some (possibly infeasible)|
|
|
//| point with respect to a) current active set, b) all boundary |
|
|
//| constraints, both active and inactive: |
|
|
//| 0) we calculate L1 penalty term for violation of active linear |
|
|
//| constraints (one which is returned by SASActiveLCPenalty1() |
|
|
//| function). |
|
|
//| 1) first, it performs projection(orthogonal with respect |
|
|
//| to scale matrix S) of X into current active set: X -> X1. |
|
|
//| 2) next, we perform projection with respect to ALL boundary |
|
|
//| constraints which are violated at X1: X1 -> X2. |
|
|
//| 3) X is replaced by X2. |
|
|
//| The idea is that this function can preserve and enforce |
|
|
//| feasibility during optimization, and additional penalty parameter|
|
|
//| can be used to prevent algo from leaving feasible set because of |
|
|
//| rounding errors. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| X - array[N], candidate point |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - "improved" candidate point: |
|
|
//| a) feasible with respect to all boundary constraints |
|
|
//| b) feasibility with respect to active set is retained |
|
|
//| at good level. |
|
|
//| Penalty - penalty term, which can be added to function value |
|
|
//| if user wants to penalize violation of constraints |
|
|
//| (recommended). |
|
|
//| NOTE: this function is not intended to find exact projection |
|
|
//| (i.e. best approximation) of X into feasible set. It just |
|
|
//| improves situation a bit. |
|
|
//| Regular use of this function will help you to retain feasibility |
|
|
//| - if you already have something to start with and constrain your |
|
|
//| steps is such way that the only source of infeasibility are |
|
|
//| roundoff errors. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASCorrection(CSActiveSet &State,CRowDouble &x,
|
|
double &penalty)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int n=0;
|
|
double v=0;
|
|
int i_=0;
|
|
penalty=0;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": is not in optimization mode"))
|
|
return;
|
|
//--- function call
|
|
SASRebuildBasis(State);
|
|
n=State.m_n;
|
|
//--- Calculate penalty term.
|
|
penalty=SASActiveLCPenalty1(State,x);
|
|
//--- Perform projection 1.
|
|
//--- This projecton is given by:
|
|
//--- x_proj = x - S*S*As'*(As*x-b)
|
|
//--- where x is original x before projection, S is a scale matrix,
|
|
//--- As is a matrix of equality constraints (active set) which were
|
|
//--- orthogonalized with respect to inner product given by S (i.e. we
|
|
//--- have As*S*S'*As'=I), b is a right part of the orthogonalized
|
|
//--- constraints.
|
|
//--- NOTE: you can verify that x_proj is strictly feasible w.m_r.m_t.
|
|
//--- active set by multiplying it by As - you will get
|
|
//--- As*x_proj = As*x - As*x + b = b.
|
|
//--- This formula for projection can be obtained by solving
|
|
//--- following minimization problem.
|
|
//--- min ||inv(S)*(x_proj-x)||^2 s.m_t. As*x_proj=b
|
|
//--- NOTE: we apply sparse batch by examining CStatus[]; it is guaranteed
|
|
//--- to contain sparse batch, but avoids roundoff errors associated
|
|
//--- with the fact that some box constraints were moved to sparse
|
|
//--- storage
|
|
State.m_corrtmp=x;
|
|
State.m_corrtmp.Resize(n);
|
|
for(i=0; i<State.m_densebatchsize; i++)
|
|
{
|
|
v=-State.m_sdensebatch.Get(i,n);
|
|
for(j=0; j<n; j++)
|
|
v+= State.m_sdensebatch.Get(i,j)*State.m_corrtmp[j];
|
|
for(j=0; j<n; j++)
|
|
State.m_corrtmp.Add(j,- v*State.m_sdensebatch.Get(i,j)*CMath::Sqr(State.m_s[j]));
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_cstatus[i]>0)
|
|
State.m_corrtmp.Set(i,State.m_xc[i]);
|
|
}
|
|
//--- Perform projection 2
|
|
for(i=0; i<n; i++)
|
|
{
|
|
x.Set(i,State.m_corrtmp[i]);
|
|
if(State.m_HasBndL[i] && x[i]<State.m_bndl[i])
|
|
x.Set(i,State.m_bndl[i]);
|
|
if(State.m_HasBndU[i] && x[i]>State.m_bndu[i])
|
|
x.Set(i,State.m_bndu[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine returns L1 penalty for violation of active |
|
|
//| general linear constraints(violation of boundary or inactive |
|
|
//| linear constraints is not added to penalty). |
|
|
//| Penalty term is equal to: |
|
|
//| Penalty = SUM(Abs((C_i * x - R_i) / Alpha_i)) |
|
|
//| Here: |
|
|
//| * summation is performed for I = 0...NEC + NIC-1, |
|
|
//| CStatus[N+I] > 0 (only for rows of CLEIC which are in active |
|
|
//| set) |
|
|
//| * C_i is I-Th row of CLEIC |
|
|
//| * R_i is corresponding right part |
|
|
//| * S is a scale matrix |
|
|
//| * Alpha_i = || S * C_i || - is a scaling coefficient which |
|
|
//| "normalizes" I-Th summation term according to its scale. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| X - array[N], candidate point |
|
|
//+------------------------------------------------------------------+
|
|
double CSActiveSets::SASActiveLCPenalty1(CSActiveSet &State,
|
|
CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int i=0;
|
|
int j=0;
|
|
int n=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
double v=0;
|
|
double alpha=0;
|
|
double p=0;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": is not in optimization mode"))
|
|
return(result);
|
|
//--- function call
|
|
SASRebuildBasis(State);
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
//--- Calculate penalty term.
|
|
result=0;
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
if(State.m_cstatus[n+i]>0)
|
|
{
|
|
alpha=0;
|
|
p=-State.m_cleic.Get(i,n);
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=State.m_cleic.Get(i,j);
|
|
p=p+v*x[j];
|
|
alpha+=CMath::Sqr(v*State.m_s[j]);
|
|
}
|
|
alpha=MathSqrt(alpha);
|
|
if(alpha!=0.0)
|
|
result+=MathAbs(p/alpha);
|
|
}
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates scaled norm of vector after projection|
|
|
//| onto subspace of active constraints. Most often this function is |
|
|
//| used to test stopping conditions. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| D - vector whose norm is calculated |
|
|
//| RESULT: |
|
|
//| Vector norm (after projection and scaling) |
|
|
//| NOTE: projection is performed first, scaling is performed after |
|
|
//| projection |
|
|
//| NOTE: if we have N active constraints, zero value(exact zero) |
|
|
//| is returned |
|
|
//+------------------------------------------------------------------+
|
|
double CSActiveSets::SASScaledConstrainedNorm(CSActiveSet &State,
|
|
CRowDouble &d)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int i=0;
|
|
int n=0;
|
|
double v=0;
|
|
int i_=0;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": is not in optimization mode"))
|
|
return(result);
|
|
|
|
n=State.m_n;
|
|
//--- Prepare basis (if needed)
|
|
SASRebuildBasis(State);
|
|
//--- Calculate descent direction
|
|
if(State.m_sparsebatchsize+State.m_densebatchsize>=n)
|
|
{
|
|
//--- Quick exit if number of active constraints is N or larger
|
|
result=0.0;
|
|
return(result);
|
|
}
|
|
State.m_scntmp=d;
|
|
State.m_scntmp.Resize(n);
|
|
for(i=0; i<=State.m_densebatchsize-1; i++)
|
|
{
|
|
v=0.0;
|
|
for(i_=0; i_<n; i_++)
|
|
v+=State.m_idensebatch.Get(i,i_)*State.m_scntmp[i_];
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_scntmp.Add(i_,- v*State.m_idensebatch.Get(i,i_));
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_cstatus[i]>0)
|
|
State.m_scntmp.Set(i,0);
|
|
}
|
|
v=0.0;
|
|
for(i=0; i<n; i++)
|
|
v+= CMath::Sqr(State.m_s[i]*State.m_scntmp[i]);
|
|
result=MathSqrt(v);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine turns off optimization mode. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - active set object |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - State is changed |
|
|
//| NOTE: this function can be called many times for optimizer which |
|
|
//| was already stopped. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASStopOptimization(CSActiveSet &State)
|
|
{
|
|
State.m_algostate=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function recalculates constraints - activates and |
|
|
//| deactivates them according to gradient value at current point. |
|
|
//| Algorithm assumes that we want to make steepest descent step from|
|
|
//| current point; constraints are activated and deactivated in such |
|
|
//| way that we won't violate any constraint by steepest descent step|
|
|
//| After call to this function active set is ready to try steepest |
|
|
//| descent step (SASDescentDirection-SASExploreDirection-SASMoveTo).|
|
|
//| Only already "active" and "candidate" elements of ActiveSet are |
|
|
//| examined; constraints which are not active are not examined. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - active set object |
|
|
//| GC - array[N], gradient at XC |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - active set object, with new set of constraint |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASReactivateConstraints(CSActiveSet &State,
|
|
CRowDouble &gc)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": must be in optimization mode"))
|
|
return;
|
|
//--- function call
|
|
ReactivateConstraints(State,gc,State.m_unitdiagonal);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function recalculates constraints - activates and |
|
|
//| deactivates them according to gradient value at current point. |
|
|
//| Algorithm assumes that we want to make Quasi - Newton step from |
|
|
//| current point with diagonal Quasi - Newton matrix H. Constraints |
|
|
//| are activated and deactivated in such way that we won't violate |
|
|
//| any constraint by step. |
|
|
//| After call to this function active set is ready to try |
|
|
//| preconditioned steepest descent step (SASDescentDirection - |
|
|
//| SASExploreDirection - SASMoveTo). |
|
|
//| Only already "active" and "candidate" elements of ActiveSet are |
|
|
//| examined; constraints which are not active are not examined. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - active set object |
|
|
//| GC - array[N], gradient at XC |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - active set object, with new set of constraint |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASReactivateConstraintsPrec(CSActiveSet &State,
|
|
CRowDouble &gc)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": must be in optimization mode"))
|
|
return;
|
|
//--- function call
|
|
ReactivateConstraints(State,gc,State.m_h);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds three orthonormal basises for current active|
|
|
//| set: |
|
|
//| * P - orthogonal one, which is orthogonalized with inner |
|
|
//| product (x, y) = x'*P*y, where P=inv(H) is current |
|
|
//| preconditioner |
|
|
//| * S - orthogonal one, which is orthogonalized with inner |
|
|
//| product (x, y) = x'*S'*S * y, where S is diagonal |
|
|
//| scaling matrix |
|
|
//| * I - orthogonal one, which is orthogonalized with standard |
|
|
//| dot product |
|
|
//| NOTE: all sets of orthogonal vectors are guaranteed to have |
|
|
//| same size. P - orthogonal basis is built first, |
|
|
//| I / S - orthogonal basises are forced to have same number |
|
|
//| of vectors as P - orthogonal one(padded by zero vectors |
|
|
//| if needed). |
|
|
//| NOTE: this function tracks changes in active set; first call |
|
|
//| will result in reorthogonalization |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - active set object |
|
|
//| H - diagonal preconditioner, H[i] > 0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - active set object with new basis |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASRebuildBasis(CSActiveSet &State)
|
|
{
|
|
//--- create variables
|
|
int n=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int i=0;
|
|
int j=0;
|
|
bool hasactivelin=false;
|
|
int candidatescnt=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double vmax=0;
|
|
int kmax=0;
|
|
int i_=0;
|
|
|
|
if(State.m_basisisready)
|
|
return;
|
|
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
State.m_tmp0.Resize(n);
|
|
State.m_tmpprodp.Resize(n);
|
|
State.m_tmpprods.Resize(n);
|
|
State.m_tmpcp.Resize(n+1);
|
|
State.m_tmpcs.Resize(n+1);
|
|
State.m_tmpci.Resize(n+1);
|
|
State.m_tmpbasis.Resize(nec+nic,n+1);
|
|
State.m_pdensebatch.Resize(nec+nic,n+1);
|
|
State.m_idensebatch.Resize(nec+nic,n+1);
|
|
State.m_sdensebatch.Resize(nec+nic,n+1);
|
|
State.m_sparsebatch.Resize(n);
|
|
State.m_sparsebatchsize=0;
|
|
State.m_densebatchsize=0;
|
|
State.m_basisage=0;
|
|
State.m_basisisready=true;
|
|
//--- Determine number of active boundary and non-boundary
|
|
//--- constraints, move them to TmpBasis. Quick exit if no
|
|
//--- non-boundary constraints were detected.
|
|
hasactivelin=false;
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
if(State.m_cstatus[n+i]>0)
|
|
hasactivelin=true;
|
|
}
|
|
for(j=0; j<n; j++)
|
|
{
|
|
if(State.m_cstatus[j]>0)
|
|
{
|
|
State.m_sparsebatch.Set(State.m_sparsebatchsize,j);
|
|
State.m_sparsebatchsize++;
|
|
}
|
|
}
|
|
if(!hasactivelin)
|
|
return;
|
|
//--- Prepare precomputed values
|
|
State.m_tmpreciph=State.m_h.Pow(-1.0)+0;
|
|
State.m_tmpreciph.Resize(n);
|
|
//--- Prepare initial candidate set:
|
|
//--- * select active constraints
|
|
//--- * normalize (inner product is given by preconditioner)
|
|
//--- * orthogonalize with respect to active box constraints
|
|
//--- * copy normalized/orthogonalized candidates to PBasis/SBasis/IBasis
|
|
candidatescnt=0;
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
if(State.m_cstatus[n+i]>0)
|
|
{
|
|
State.m_tmpbasis.Row(candidatescnt,State.m_cleic[i]+0);
|
|
candidatescnt++;
|
|
}
|
|
}
|
|
for(i=0; i<candidatescnt; i++)
|
|
{
|
|
v=0.0;
|
|
for(j=0; j<n; j++)
|
|
v+=CMath::Sqr(State.m_tmpbasis.Get(i,j))*State.m_tmpreciph[j];
|
|
if(v>0.0)
|
|
{
|
|
v=1/MathSqrt(v);
|
|
State.m_tmpbasis.Row(i,State.m_tmpbasis[i]*v);
|
|
}
|
|
}
|
|
for(j=0; j<n; j++)
|
|
{
|
|
if(State.m_cstatus[j]>0)
|
|
{
|
|
for(i=0; i<candidatescnt; i++)
|
|
{
|
|
State.m_tmpbasis.Add(i,n,-State.m_tmpbasis.Get(i,j)*State.m_xc[j]);
|
|
State.m_tmpbasis.Set(i,j,0.0);
|
|
}
|
|
}
|
|
}
|
|
for(i=0; i<candidatescnt; i++)
|
|
State.m_pdensebatch.Row(i,State.m_tmpbasis[i]+0);
|
|
State.m_sdensebatch=State.m_pdensebatch;
|
|
State.m_idensebatch=State.m_pdensebatch;
|
|
//--- Perform orthogonalization of general linear constraints with respect
|
|
//--- to each other (constraints in P/S/IBasis are already normalized w.m_r.m_t.
|
|
//--- box constraints). During this process we select strictly active constraints
|
|
//--- from the candidate set, and drop ones which were detected as redundant
|
|
//--- during orthogonalization.
|
|
//--- Orthogonalization is performed with the help of Gram-Schmidt process.
|
|
//--- Due to accumulation of round-off errors it is beneficial to perform
|
|
//--- pivoting, i.e. to select candidate vector with largest norm at each
|
|
//--- step.
|
|
//--- First (basic) version of the algorithm is:
|
|
//--- 0. split all constraints into two sets: basis ones (initially empty)
|
|
//--- and candidate ones (all constraints)
|
|
//--- 1. fill PBasis with H-normalized candidate constraints, fill
|
|
//--- corresponding entries of S/IBasis with corresponding
|
|
//--- (non-normalized) constraints
|
|
//--- 2. select row of PBasis with largest norm, move it (and its S/IBasis
|
|
//--- counterparts) to the beginning of the candidate set, H-normalize
|
|
//--- this row (rows of S/IBasis are normalized using corresponding norms).
|
|
//--- Stop if largest row is nearly (or exactly) zero.
|
|
//--- 3. orthogonalize remaining rows of P/S/IBasis with respect to
|
|
//--- one chosen at step (2). It can be done efficiently using
|
|
//--- combination of DGEMV/DGER BLAS calls.
|
|
//--- 4. increase basis size by one, decrease candidate set size by one,
|
|
//--- goto (2)
|
|
//--- However, naive implementation of the algorithm above spends significant
|
|
//--- amount of time in step (2) - selection of row with largest H-norm. Step
|
|
//--- (3) can be efficiently implemented with optimized BLAS, but we have no
|
|
//--- optimized BLAS kernels for step(2). And because step (3) changes row norms,
|
|
//--- step (2) have to be re-calculated every time, which is quite slow.
|
|
//--- We can save significant amount of calculations by noticing that:
|
|
//--- * step (3) DECREASES row norms, but never increases it
|
|
//--- * we can maintain upper bounds for row H-norms is a separate array,
|
|
//--- use them for initial evaluation of best candidates, and update them
|
|
//--- after we find some promising row (all bounds are invalidated after
|
|
//--- step 3, but their old values still carry some information)
|
|
//--- * it is beneficial re-evaluate bounds only for rows which are
|
|
//--- significantly (at least few percents) larger than best one found so far
|
|
//--- * because rows are initially normalized, initial values for upper bounds
|
|
//--- can be set to 1.0
|
|
if(!CAp::Assert(State.m_densebatchsize==0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(m_minnormseparation>0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
State.m_tmpnormestimates=vector<double>::Ones(candidatescnt);
|
|
while(State.m_sparsebatchsize+State.m_densebatchsize<n)
|
|
{
|
|
//--- No candidates? We are done!
|
|
if(candidatescnt==0)
|
|
break;
|
|
//--- Find largest vector
|
|
vmax=0;
|
|
kmax=-1;
|
|
for(i=State.m_densebatchsize; i<State.m_densebatchsize+candidatescnt; i++)
|
|
{
|
|
//--- Use upper bound for row norm for initial evaluation.
|
|
//--- Skip rows whose upper bound is less than best candidate
|
|
//--- found so far.
|
|
//--- NOTE: in fact, we may skip rows whose upper bound is
|
|
//--- marginally higher than that of best candidate.
|
|
//--- No need to perform costly re-evaluation in order
|
|
//--- to get just few percents of improvement.
|
|
if(State.m_tmpnormestimates[i]<(vmax*(1+m_minnormseparation)))
|
|
continue;
|
|
//--- OK, upper bound is large enough... lets perform full
|
|
//--- re-evaluation and update of the estimate.
|
|
v=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
vv=State.m_pdensebatch.Get(i,j);
|
|
v+= vv*vv*State.m_tmpreciph[j];
|
|
}
|
|
v=MathSqrt(v);
|
|
State.m_tmpnormestimates.Set(i,v);
|
|
//--- Now compare with best candidate so far
|
|
if(v>vmax)
|
|
{
|
|
vmax=v;
|
|
kmax=i;
|
|
}
|
|
}
|
|
if(vmax<(1.0E4*CMath::m_machineepsilon) || kmax<0)
|
|
{
|
|
//--- All candidates are either zero or too small (after orthogonalization)
|
|
candidatescnt=0;
|
|
break;
|
|
}
|
|
//--- Candidate is selected for inclusion into basis set.
|
|
//--- Move candidate row to the beginning of candidate array (which is
|
|
//--- right past the end of the approved basis). Normalize (for P-basis
|
|
//--- we perform preconditioner-based normalization, for S-basis - scale
|
|
//--- based, for I-basis - identity based).
|
|
State.m_pdensebatch.SwapRows(State.m_densebatchsize,kmax);
|
|
State.m_sdensebatch.SwapRows(State.m_densebatchsize,kmax);
|
|
State.m_idensebatch.SwapRows(State.m_densebatchsize,kmax);
|
|
State.m_tmpnormestimates.Swap(State.m_densebatchsize,kmax);
|
|
v=1/vmax;
|
|
State.m_pdensebatch.Row(State.m_densebatchsize,State.m_pdensebatch[State.m_densebatchsize]*v);
|
|
v=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
vv=State.m_sdensebatch.Get(State.m_densebatchsize,j)*State.m_s[j];
|
|
v+= vv*vv;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(v>0.0,__FUNCTION__+": integrity check failed,SNorm=0"))
|
|
return;
|
|
v=1/MathSqrt(v);
|
|
State.m_sdensebatch.Row(State.m_densebatchsize,State.m_sdensebatch[State.m_densebatchsize]*v);
|
|
v=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
vv=State.m_idensebatch.Get(State.m_densebatchsize,j);
|
|
v+= vv*vv;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(v>0.0,__FUNCTION__+": integrity check failed,INorm=0"))
|
|
return;
|
|
v=1/MathSqrt(v);
|
|
State.m_idensebatch.Row(State.m_densebatchsize,State.m_idensebatch[State.m_densebatchsize]*v);
|
|
//--- Reorthogonalize other candidates with respect to candidate #0:
|
|
//--- * calculate projections en masse with GEMV()
|
|
//--- * subtract projections with GER()
|
|
State.m_tmp0.Resize(candidatescnt-1);
|
|
for(j=0; j<n; j++)
|
|
{
|
|
State.m_tmpprodp.Set(j,State.m_pdensebatch.Get(State.m_densebatchsize,j)/State.m_h[j]);
|
|
State.m_tmpprods.Set(j,State.m_sdensebatch.Get(State.m_densebatchsize,j)*CMath::Sqr(State.m_s[j]));
|
|
}
|
|
State.m_tmpcp=State.m_pdensebatch[State.m_densebatchsize]+0;
|
|
State.m_tmpcs=State.m_sdensebatch[State.m_densebatchsize]+0;
|
|
State.m_tmpci=State.m_idensebatch[State.m_densebatchsize]+0;
|
|
CAblas::RMatrixGemVect(candidatescnt-1,n,1.0,State.m_pdensebatch,State.m_densebatchsize+1,0,0,State.m_tmpprodp,0,0.0,State.m_tmp0,0);
|
|
CAblas::RMatrixGer(candidatescnt-1,n+1,State.m_pdensebatch,State.m_densebatchsize+1,0,-1.0,State.m_tmp0,0,State.m_tmpcp,0);
|
|
CAblas::RMatrixGemVect(candidatescnt-1,n,1.0,State.m_sdensebatch,State.m_densebatchsize+1,0,0,State.m_tmpprods,0,0.0,State.m_tmp0,0);
|
|
CAblas::RMatrixGer(candidatescnt-1,n+1,State.m_sdensebatch,State.m_densebatchsize+1,0,-1.0,State.m_tmp0,0,State.m_tmpcs,0);
|
|
CAblas::RMatrixGemVect(candidatescnt-1,n,1.0,State.m_idensebatch,State.m_densebatchsize+1,0,0,State.m_tmpci,0,0.0,State.m_tmp0,0);
|
|
CAblas::RMatrixGer(candidatescnt-1,n+1,State.m_idensebatch,State.m_densebatchsize+1,0,-1.0,State.m_tmp0,0,State.m_tmpci,0);
|
|
//--- Increase basis, decrease candidates count
|
|
State.m_densebatchsize++;
|
|
candidatescnt--;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function appends new constraints(if possible; sometimes it |
|
|
//| isn't!) to three orthonormal basises for current active set: |
|
|
//| * P - orthogonal one, which is orthogonalized with inner |
|
|
//| product (x, y) = x'*P*y, where P=inv(H) is current |
|
|
//| preconditioner |
|
|
//| * S - orthogonal one, which is orthogonalized with inner |
|
|
//| product (x, y) = x'*S'*S*y, where S is diagonal scaling |
|
|
//| matrix |
|
|
//| * I - orthogonal one, which is orthogonalized with standard |
|
|
//| dot product |
|
|
//| NOTE: all sets of orthogonal vectors are guaranteed to have same |
|
|
//| size. P - orthogonal basis is built first, I/S - orthogonal|
|
|
//| basises are forced to have same number of vectors as |
|
|
//| P - orthogonal one(padded by zero vectors if needed). |
|
|
//| NOTE: this function may fail to update basis without full |
|
|
//| recalculation; in such case it will set BasisIsReady to |
|
|
//| False and silently return; if it succeeds, it will increase|
|
|
//| BasisSize. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - active set object |
|
|
//| NewEntries - array[N + NEC + NIC], indexes of constraints |
|
|
//| being added are marked as True; it is |
|
|
//| responsibility of the caller to specify only |
|
|
//| those constraints which were previously |
|
|
//| inactive; when some constraint is already in the|
|
|
//| active set, algorithm behavior is undefined. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - active set object with new basis |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::SASAppendToBasis(CSActiveSet &State,
|
|
bool &newentries[])
|
|
{
|
|
//--- create variables
|
|
int n=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int i=0;
|
|
int j=0;
|
|
int t=0;
|
|
int nact=0;
|
|
double v=0;
|
|
double vp=0;
|
|
double vs=0;
|
|
double vi=0;
|
|
double initnormp=0;
|
|
double projnormp=0;
|
|
double projnorms=0;
|
|
double projnormi=0;
|
|
int i_=0;
|
|
//--- check
|
|
if(!State.m_basisisready)
|
|
return;
|
|
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
//--- Count number of constraints to activate;
|
|
//--- perform integrity check.
|
|
nact=0;
|
|
for(i=0; i<n; i++)
|
|
if(newentries[i])
|
|
nact++;
|
|
for(i=n; i<n+nec; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(!newentries[i],__FUNCTION__+": integrity check failed (appendtobasis.0)"))
|
|
return;
|
|
}
|
|
for(i=n+nec; i<n+nec+nic; i++)
|
|
{
|
|
if(newentries[i])
|
|
nact++;
|
|
}
|
|
if(nact+State.m_basisage>m_maxbasisage)
|
|
{
|
|
State.m_basisisready=false;
|
|
return;
|
|
}
|
|
//--- Resize basis matrices if needed
|
|
State.m_pdensebatch.Resize(State.m_densebatchsize+nact,n+1);
|
|
State.m_sdensebatch.Resize(State.m_densebatchsize+nact,n+1);
|
|
State.m_idensebatch.Resize(State.m_densebatchsize+nact,n+1);
|
|
//--- Try adding recommended entries to basis.
|
|
//--- If reorthogonalization removes too much of candidate constraint,
|
|
//--- we will invalidate basis and try to rebuild it from scratch.
|
|
State.m_tmp0.Resize(n+1);
|
|
State.m_tmpcp.Resize(n+1);
|
|
State.m_tmpcs.Resize(n+1);
|
|
State.m_tmpci.Resize(n+1);
|
|
State.m_tmpprodp.Resize(n);
|
|
State.m_tmpprods.Resize(n);
|
|
for(t=0; t<n+nec+nic; t++)
|
|
{
|
|
if(newentries[t])
|
|
{
|
|
//--- Basis is full? Quick skip!
|
|
if(State.m_sparsebatchsize+State.m_densebatchsize>=n)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_sparsebatchsize+State.m_densebatchsize==n,__FUNCTION__+": integrity check failed (SASAppendToBasis)"))
|
|
return;
|
|
break;
|
|
}
|
|
//--- Copy constraint to temporary storage.
|
|
if(t<n)
|
|
{
|
|
//--- Copy box constraint
|
|
State.m_tmp0=vector<double>::Zeros(n+1);
|
|
State.m_tmp0.Set(t,1.0);
|
|
State.m_tmp0.Set(n,State.m_xc[t]);
|
|
}
|
|
else
|
|
{
|
|
//--- Copy general linear constraint
|
|
State.m_tmp0=State.m_cleic[t-n]+0;
|
|
}
|
|
//--- Calculate initial norm (preconditioner is used for norm calculation).
|
|
initnormp=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=State.m_tmp0[j];
|
|
initnormp=initnormp+v*v/State.m_h[j];
|
|
}
|
|
initnormp=MathSqrt(initnormp);
|
|
if(initnormp==0.0)
|
|
{
|
|
//--- Well, it is not expected. Let's just rebuild basis
|
|
//--- from scratch and forget about this strange situation...
|
|
State.m_basisisready=false;
|
|
return;
|
|
}
|
|
//--- Orthogonalize Tmp0 w.m_r.m_t. sparse batch (box constraints stored in sparse storage).
|
|
//--- Copy to TmpCP/TmpCS/TmpCI (P for preconditioner-based inner product
|
|
//--- used for orthogonalization, S for scale-based orthogonalization,
|
|
//--- I for "traditional" inner product used for Gram-Schmidt orthogonalization).
|
|
for(i=0; i<State.m_sparsebatchsize; i++)
|
|
{
|
|
j=State.m_sparsebatch[i];
|
|
State.m_tmp0.Add(n,- State.m_tmp0[j]*State.m_xc[j]);
|
|
State.m_tmp0.Set(j,0.0);
|
|
}
|
|
State.m_tmpcp=State.m_tmp0;
|
|
State.m_tmpcs=State.m_tmp0;
|
|
State.m_tmpci=State.m_tmp0;
|
|
//--- Orthogonalize TmpCP/S/I with respect to active linear constraints from dense batch.
|
|
//--- Corresponding norm (preconditioner, scale, identity) is used in each case.
|
|
State.m_tmpprodp=State.m_h.Pow(-1.0)+0;
|
|
State.m_tmpprods=State.m_s.Pow(2)+0;
|
|
for(i=0; i<State.m_densebatchsize; i++)
|
|
{
|
|
vp=0;
|
|
vs=0;
|
|
vi=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
vp+=State.m_pdensebatch.Get(i,j)*State.m_tmpcp[j]*State.m_tmpprodp[j];
|
|
vs+=State.m_sdensebatch.Get(i,j)*State.m_tmpcs[j]*State.m_tmpprods[j];
|
|
vi+=State.m_idensebatch.Get(i,j)*State.m_tmpci[j];
|
|
}
|
|
State.m_tmpcp-=State.m_pdensebatch[i]*vp;
|
|
State.m_tmpcs-=State.m_sdensebatch[i]*vs;
|
|
State.m_tmpci-=State.m_idensebatch[i]*vi;
|
|
}
|
|
projnormp=0.0;
|
|
projnorms=0.0;
|
|
projnormi=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
projnormp+=CMath::Sqr(State.m_tmpcp[j])/State.m_h[j];
|
|
projnorms+=CMath::Sqr(State.m_tmpcs[j])*CMath::Sqr(State.m_s[j]);
|
|
projnormi+=CMath::Sqr(State.m_tmpci[j]);
|
|
}
|
|
projnormp=MathSqrt(projnormp);
|
|
projnorms=MathSqrt(projnorms);
|
|
projnormi=MathSqrt(projnormi);
|
|
if(projnormp<=(m_maxbasisdecay*initnormp))
|
|
{
|
|
State.m_basisisready=false;
|
|
return;
|
|
//--- Nearly zero row, skip
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(projnormp>0.0,__FUNCTION__+": integrity check failed,ProjNormP=0"))
|
|
return;
|
|
if(!CAp::Assert(projnorms>0.0,__FUNCTION__+": integrity check failed,ProjNormS=0"))
|
|
return;
|
|
if(!CAp::Assert(projnormi>0.0,__FUNCTION__+": integrity check failed,ProjNormI=0"))
|
|
return;
|
|
v=1/projnormp;
|
|
State.m_pdensebatch.Row(State.m_densebatchsize,State.m_tmpcp*v+0);
|
|
v=1/projnorms;
|
|
State.m_sdensebatch.Row(State.m_densebatchsize,State.m_tmpcs*v+0);
|
|
v=1/projnormi;
|
|
State.m_idensebatch.Row(State.m_densebatchsize,State.m_tmpci*v+0);
|
|
//--- Increase set size
|
|
State.m_densebatchsize++;
|
|
State.m_basisage++;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates preconditioned descent direction |
|
|
//| subject to current active set. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - active set object |
|
|
//| G - array[N], gradient |
|
|
//| H - array[N], Hessian matrix |
|
|
//| HA - active constraints orthogonalized in such way that |
|
|
//| HA*inv(H)*HA'= I. |
|
|
//| Normalize- whether we need normalized descent or not |
|
|
//| D - possibly preallocated buffer; automatically resized|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D - descent direction projected onto current active set|
|
|
//| Components of D which correspond to active boundary|
|
|
//| constraints are forced to be exactly zero. In case |
|
|
//| D is non-zero and Normalize is True, it is |
|
|
//| normalized to have unit norm. |
|
|
//| NOTE: if we have N active constraints, D is explicitly set to |
|
|
//| zero. |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::ConstrainedDescent(CSActiveSet &State,
|
|
CRowDouble &g,
|
|
CRowDouble &h,
|
|
CMatrixDouble &ha,
|
|
bool normalize,
|
|
CRowDouble &d)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int n=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": internal error in ConstrainedDescent() - not in optimization mode"))
|
|
return;
|
|
if(!CAp::Assert(State.m_basisisready,__FUNCTION__+": internal error in ConstrainedDescent() - no basis"))
|
|
return;
|
|
|
|
n=State.m_n;
|
|
//--- Calculate preconditioned constrained descent direction:
|
|
//--- d := -inv(H)*( g - HA'*(HA*inv(H)*g) )
|
|
//--- Formula above always gives direction which is orthogonal to rows of HA.
|
|
//--- You can verify it by multiplication of both sides by HA[i] (I-th row),
|
|
//--- taking into account that HA*inv(H)*HA'= I (by definition of HA - it is
|
|
//--- orthogonal basis with inner product given by inv(H)).
|
|
d=g;
|
|
d.Resize(n);
|
|
for(i=0; i<State.m_densebatchsize; i++)
|
|
{
|
|
vector<double> row=ha[i];
|
|
row.Resize(n);
|
|
v=d.Dot(row/h.ToVector());
|
|
d-=row*v;
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_cstatus[i]>0)
|
|
d.Set(i,0);
|
|
}
|
|
d/=h.ToVector()*(-1);
|
|
v=MathSqrt(CAblasF::RDotV2(n,d));
|
|
if(State.m_sparsebatchsize+State.m_densebatchsize>=n)
|
|
{
|
|
v=0;
|
|
d=vector<double>::Zeros(n);
|
|
}
|
|
if(normalize && v>0.0)
|
|
d/=v;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function recalculates constraints - activates and |
|
|
//| deactivates them according to gradient value at current point. |
|
|
//| Algorithm assumes that we want to make Quasi - Newton step |
|
|
//| from current point with diagonal Quasi - Newton matrix H. |
|
|
//| Constraints are activated and deactivated in such way that we |
|
|
//| won't violate any constraint by step. |
|
|
//| Only already "active" and "candidate" elements of ActiveSet are |
|
|
//| examined; constraints which are not active are not examined. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - active set object |
|
|
//| GC - array[N], gradient at XC |
|
|
//| H - array[N], Hessian matrix |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - active set object, with new set of constraint |
|
|
//+------------------------------------------------------------------+
|
|
void CSActiveSets::ReactivateConstraints(CSActiveSet &State,
|
|
CRowDouble &gc,
|
|
CRowDouble &h)
|
|
{
|
|
//--- return result
|
|
int n=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int i=0;
|
|
int j=0;
|
|
int idx0=0;
|
|
int idx1=0;
|
|
double v=0;
|
|
int nactivebnd=0;
|
|
int nactivelin=0;
|
|
int nactiveconstraints=0;
|
|
double rowscale=0;
|
|
int i_=0;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algostate==1,__FUNCTION__+": must be in optimization mode"))
|
|
return;
|
|
//--- Prepare
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
State.m_basisisready=false;
|
|
//--- Handle important special case - no linear constraints,
|
|
//--- only boundary constraints are present
|
|
if(nec+nic==0)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i] && State.m_bndl[i]==State.m_bndu[i])
|
|
{
|
|
State.m_cstatus.Set(i,1);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndL[i] && State.m_xc[i]==State.m_bndl[i] && gc[i]>=0.0)
|
|
{
|
|
State.m_cstatus.Set(i,1);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndU[i] && State.m_xc[i]==State.m_bndu[i] && gc[i]<=0.0)
|
|
{
|
|
State.m_cstatus.Set(i,1);
|
|
continue;
|
|
}
|
|
State.m_cstatus.Set(i,-1);
|
|
}
|
|
return;
|
|
}
|
|
//--- General case.
|
|
//--- Calculate descent direction
|
|
State.m_rctmpg=gc*(-1.0)+0;
|
|
//--- Allocate temporaries.
|
|
State.m_rctmpg.Resize(n);
|
|
State.m_rctmprightpart.Resize(n);
|
|
State.m_rctmps.Resize(n);
|
|
State.m_rctmpdense0.Resize(n,nec+nic);
|
|
State.m_rctmpdense1.Resize(n,nec+nic);
|
|
State.m_rctmpconstraintidx.Resize(n+nec+nic);
|
|
CApServ::BVectorSetLengthAtLeast(State.m_rctmpisequality,n+nec+nic);
|
|
//--- Determine candidates to the active set.
|
|
//--- After this block constraints become either "inactive" (CStatus[i]<0)
|
|
//--- or "candidates" (CStatus[i]=0). Previously active constraints always
|
|
//--- become "candidates".
|
|
State.m_cstatus.Fill(-1,0,n);
|
|
for(i=n; i<=n+nec+nic-1; i++)
|
|
{
|
|
if(State.m_cstatus[i]>0)
|
|
State.m_cstatus.Set(i,0);
|
|
else
|
|
State.m_cstatus.Set(i,-1);
|
|
}
|
|
nactiveconstraints=0;
|
|
nactivebnd=0;
|
|
nactivelin=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Activate boundary constraints:
|
|
//--- * copy constraint index to RCTmpConstraintIdx
|
|
//---*set corresponding element of CStatus[] to "candidate"
|
|
//--- * fill RCTmpS by either +1 (lower bound) or -1 (upper bound)
|
|
//--- * set RCTmpIsEquality to False (BndL<BndU) or True (BndL=BndU)
|
|
//--- * increase counters
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i] && State.m_bndl[i]==State.m_bndu[i])
|
|
{
|
|
//--- Equality constraint is activated
|
|
State.m_rctmpconstraintidx.Set(nactiveconstraints,i);
|
|
State.m_cstatus.Set(i,0);
|
|
State.m_rctmps.Set(i,1.0);
|
|
State.m_rctmpisequality[nactiveconstraints]=true;
|
|
nactiveconstraints++;
|
|
nactivebnd++;
|
|
continue;
|
|
}
|
|
if(State.m_HasBndL[i] && State.m_xc[i]==State.m_bndl[i])
|
|
{
|
|
//--- Lower bound is activated
|
|
State.m_rctmpconstraintidx.Set(nactiveconstraints,i);
|
|
State.m_cstatus.Set(i,0);
|
|
State.m_rctmps.Set(i,-1.0);
|
|
State.m_rctmpisequality[nactiveconstraints]=false;
|
|
nactiveconstraints++;
|
|
nactivebnd++;
|
|
continue;
|
|
}
|
|
if(State.m_HasBndU[i] && State.m_xc[i]==State.m_bndu[i])
|
|
{
|
|
//--- Upper bound is activated
|
|
State.m_cstatus.Set(i,0);
|
|
State.m_rctmpconstraintidx.Set(nactiveconstraints,i);
|
|
State.m_rctmps.Set(i,1.0);
|
|
State.m_rctmpisequality[nactiveconstraints]=false;
|
|
nactiveconstraints++;
|
|
nactivebnd++;
|
|
continue;
|
|
}
|
|
}
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
if(i>=nec && State.m_cstatus[n+i]<0)
|
|
{
|
|
//--- Inequality constraints are skipped if both (a) constraint was
|
|
//--- not active, and (b) we are too far away from the boundary.
|
|
rowscale=0.0;
|
|
v=-State.m_cleic.Get(i,n);
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v+= State.m_cleic.Get(i,j)*State.m_xc[j];
|
|
rowscale=MathMax(rowscale,MathAbs(State.m_cleic.Get(i,j)*State.m_s[j]));
|
|
}
|
|
if(v<=(-1.0E5*CMath::m_machineepsilon*rowscale))
|
|
{
|
|
//--- NOTE: it is important to check for non-strict inequality
|
|
//--- because we have to correctly handle zero constraint
|
|
//--- 0*x<=0
|
|
continue;
|
|
}
|
|
}
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_rctmpdense0.Set(i_,nactivelin,State.m_cleic.Get(i,i_));
|
|
State.m_cstatus.Set(n+i,0);
|
|
State.m_rctmpconstraintidx.Set(nactiveconstraints,n+i);
|
|
State.m_rctmpisequality[nactiveconstraints]=i<nec;
|
|
nactiveconstraints++;
|
|
nactivelin++;
|
|
}
|
|
//--- Skip if no "candidate" constraints was found
|
|
if(nactiveconstraints==0)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i] && State.m_bndl[i]==State.m_bndu[i])
|
|
{
|
|
State.m_cstatus.Set(i,1);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndL[i] && State.m_xc[i]==State.m_bndl[i] && gc[i]>=0.0)
|
|
{
|
|
State.m_cstatus.Set(i,1);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndU[i] && State.m_xc[i]==State.m_bndu[i] && gc[i]<=0.0)
|
|
{
|
|
State.m_cstatus.Set(i,1);
|
|
continue;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
//--- General case.
|
|
//--- APPROACH TO CONSTRAINTS ACTIVATION/DEACTIVATION
|
|
//--- We have NActiveConstraints "candidates": NActiveBnd boundary candidates,
|
|
//--- NActiveLin linear candidates. Indexes of boundary constraints are stored
|
|
//--- in RCTmpConstraintIdx[0:NActiveBnd-1], indexes of linear ones are stored
|
|
//--- in RCTmpConstraintIdx[NActiveBnd:NActiveBnd+NActiveLin-1]. Some of the
|
|
//--- constraints are equality ones, some are inequality - as specified by
|
|
//--- RCTmpIsEquality[i].
|
|
//--- Now we have to determine active subset of "candidates" set. In order to
|
|
//--- do so we solve following constrained minimization problem:
|
|
//--- ( )^2
|
|
//--- min ( SUM(lambda[i]*A[i]) + G )
|
|
//--- ( )
|
|
//--- Here:
|
|
//--- * G is a gradient (column vector)
|
|
//--- * A[i] is a column vector, linear (left) part of I-th constraint.
|
|
//--- I=0..NActiveConstraints-1, first NActiveBnd elements of A are just
|
|
//--- subset of identity matrix (boundary constraints), next NActiveLin
|
|
//--- elements are subset of rows of the matrix of general linear constraints.
|
|
//--- * lambda[i] is a Lagrange multiplier corresponding to I-th constraint
|
|
//--- NOTE: for preconditioned setting A is replaced by A*H^(-0.5), G is
|
|
//--- replaced by G*H^(-0.5). We apply this scaling at the last stage,
|
|
//--- before passing data to NNLS m_solver.
|
|
//--- Minimization is performed subject to non-negativity constraints on
|
|
//--- lambda[i] corresponding to inequality constraints. Inequality constraints
|
|
//--- which correspond to non-zero lambda are activated, equality constraints
|
|
//--- are always considered active.
|
|
//--- Informally speaking, we "decompose" descent direction -G and represent
|
|
//--- it as sum of constraint vectors and "residual" part (which is equal to
|
|
//--- the actual descent direction subject to constraints).
|
|
//--- SOLUTION OF THE NNLS PROBLEM
|
|
//--- We solve this optimization problem with Non-Negative Least Squares m_solver,
|
|
//--- which can efficiently solve least squares problems of the form
|
|
//--- ( [ I | AU ] )^2
|
|
//--- min ( [ | ]*x-b ) s.m_t. non-negativity constraints on some x[i]
|
|
//--- ( [ 0 | AL ] )
|
|
//--- In order to use this m_solver we have to rearrange rows of A[] and G in
|
|
//--- such way that first NActiveBnd columns of A store identity matrix (before
|
|
//--- sorting non-zero elements are randomly distributed in the first NActiveBnd
|
|
//--- columns of A, during sorting we move them to first NActiveBnd rows).
|
|
//--- Then we create instance of NNLS m_solver (we reuse instance left from the
|
|
//--- previous run of the optimization problem) and solve NNLS problem.
|
|
idx0=0;
|
|
idx1=nactivebnd;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_cstatus[i]>=0)
|
|
{
|
|
v=1/MathSqrt(h[i]);
|
|
for(j=0; j<=nactivelin-1; j++)
|
|
State.m_rctmpdense1.Set(idx0,j,State.m_rctmpdense0.Get(i,j)/State.m_rctmps[i]*v);
|
|
State.m_rctmprightpart.Set(idx0,State.m_rctmpg[i]/State.m_rctmps[i]*v);
|
|
idx0++;
|
|
}
|
|
else
|
|
{
|
|
v=1/MathSqrt(h[i]);
|
|
for(j=0; j<nactivelin; j++)
|
|
State.m_rctmpdense1.Set(idx1,j,State.m_rctmpdense0.Get(i,j)*v);
|
|
State.m_rctmprightpart.Set(idx1,State.m_rctmpg[i]*v);
|
|
idx1++;
|
|
}
|
|
}
|
|
CSNNLS::SNNLSInit(n,MathMin(nec+nic,n),n,State.m_solver);
|
|
CSNNLS::SNNLSSetProblem(State.m_solver,State.m_rctmpdense1,State.m_rctmprightpart,nactivebnd,nactiveconstraints-nactivebnd,n);
|
|
for(i=0; i<=nactiveconstraints-1; i++)
|
|
{
|
|
if(State.m_rctmpisequality[i])
|
|
CSNNLS::SNNLSDropNNC(State.m_solver,i);
|
|
}
|
|
CSNNLS::SNNLSSolve(State.m_solver,State.m_rctmplambdas);
|
|
//--- After solution of the problem we activate equality constraints (always active)
|
|
//--- and inequality constraints with non-zero Lagrange multipliers. Then we reorthogonalize
|
|
//--- active constraints.
|
|
State.m_cstatus.Fill(-1,0,n+nec+nic);
|
|
for(i=0; i<nactiveconstraints; i++)
|
|
{
|
|
if(State.m_rctmpisequality[i] || State.m_rctmplambdas[i]>0.0)
|
|
State.m_cstatus.Set(State.m_rctmpconstraintidx[i],1);
|
|
else
|
|
State.m_cstatus.Set(State.m_rctmpconstraintidx[i],0);
|
|
}
|
|
SASRebuildBasis(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores nonlinear optimizer State. |
|
|
//| You should use functions provided by MinBLEIC subpackage to work |
|
|
//| with this object |
|
|
//+------------------------------------------------------------------+
|
|
class CMinBLEICState
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_bufsize;
|
|
int m_cidx;
|
|
int m_maxits;
|
|
int m_mcstage;
|
|
int m_nec;
|
|
int m_nfev;
|
|
int m_nic;
|
|
int m_nmain;
|
|
int m_nonmonotoniccnt;
|
|
int m_nslack;
|
|
int m_prectype;
|
|
int m_repdebugfeasgpaits;
|
|
int m_repdebugfeasqpits;
|
|
int m_repinneriterationscount;
|
|
int m_repnfev;
|
|
int m_repouteriterationscount;
|
|
int m_repterminationtype;
|
|
int m_repvaridx;
|
|
int m_smoothnessguardlevel;
|
|
double m_activationstep;
|
|
double m_curstpmax;
|
|
double m_cval;
|
|
double m_diffstep;
|
|
double m_epsf;
|
|
double m_epsg;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_fbase;
|
|
double m_fc;
|
|
double m_fm1;
|
|
double m_fm2;
|
|
double m_fn;
|
|
double m_fp1;
|
|
double m_fp2;
|
|
double m_fp;
|
|
double m_gm1;
|
|
double m_gp1;
|
|
double m_lastgoodstep;
|
|
double m_lastscaledgoodstep;
|
|
double m_maxscaledgrad;
|
|
double m_repdebugdx;
|
|
double m_repdebugeqerr;
|
|
double m_repdebugff;
|
|
double m_repdebugfs;
|
|
double m_stp;
|
|
double m_stpmax;
|
|
double m_teststep;
|
|
double m_trimthreshold;
|
|
double m_xm1;
|
|
double m_xp1;
|
|
bool m_boundedstep;
|
|
bool m_drep;
|
|
bool m_lsstart;
|
|
bool m_needf;
|
|
bool m_needfg;
|
|
bool m_steepestdescentstep;
|
|
bool m_userterminationneeded;
|
|
bool m_xrep;
|
|
bool m_xupdated;
|
|
//--- objects
|
|
RCommState m_rstate;
|
|
CSmoothnessMonitor m_smonitor;
|
|
CSNNLSSolver m_solver;
|
|
CSActiveSet m_sas;
|
|
CLinMinState m_lstate;
|
|
//--- arrays
|
|
bool m_HasBndL[];
|
|
bool m_HasBndU[];
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_bufrho;
|
|
CRowDouble m_buftheta;
|
|
CRowDouble m_cgc;
|
|
CRowDouble m_cgn;
|
|
CRowDouble m_d;
|
|
CRowDouble m_diagh;
|
|
CRowDouble m_g;
|
|
CRowDouble m_invs;
|
|
CRowDouble m_lastscaleused;
|
|
CRowDouble m_s;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmpprec;
|
|
CRowDouble m_ugc;
|
|
CRowDouble m_ugn;
|
|
CRowDouble m_work;
|
|
CRowDouble m_x;
|
|
CRowDouble m_xn;
|
|
CRowDouble m_xp;
|
|
CRowDouble m_xstart;
|
|
//--- matrix
|
|
CMatrixDouble m_bufsk;
|
|
CMatrixDouble m_bufyk;
|
|
CMatrixDouble m_cleic;
|
|
//--- constructor, destructor
|
|
CMinBLEICState(void);
|
|
~CMinBLEICState(void) {}
|
|
//--- copy
|
|
void Copy(const CMinBLEICState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinBLEICState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinBLEICState::CMinBLEICState(void)
|
|
{
|
|
m_bufsize=0;
|
|
m_cidx=0;
|
|
m_maxits=0;
|
|
m_mcstage=0;
|
|
m_nec=0;
|
|
m_nfev=0;
|
|
m_nic=0;
|
|
m_nmain=0;
|
|
m_nonmonotoniccnt=0;
|
|
m_nslack=0;
|
|
m_prectype=0;
|
|
m_repdebugfeasgpaits=0;
|
|
m_repdebugfeasqpits=0;
|
|
m_repinneriterationscount=0;
|
|
m_repnfev=0;
|
|
m_repouteriterationscount=0;
|
|
m_repterminationtype=0;
|
|
m_repvaridx=0;
|
|
m_smoothnessguardlevel=0;
|
|
m_activationstep=0;
|
|
m_curstpmax=0;
|
|
m_cval=0;
|
|
m_diffstep=0;
|
|
m_epsf=0;
|
|
m_epsg=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_fbase=0;
|
|
m_fc=0;
|
|
m_fm1=0;
|
|
m_fm2=0;
|
|
m_fn=0;
|
|
m_fp1=0;
|
|
m_fp2=0;
|
|
m_fp=0;
|
|
m_gm1=0;
|
|
m_gp1=0;
|
|
m_lastgoodstep=0;
|
|
m_lastscaledgoodstep=0;
|
|
m_maxscaledgrad=0;
|
|
m_repdebugdx=0;
|
|
m_repdebugeqerr=0;
|
|
m_repdebugff=0;
|
|
m_repdebugfs=0;
|
|
m_stp=0;
|
|
m_stpmax=0;
|
|
m_teststep=0;
|
|
m_trimthreshold=0;
|
|
m_xm1=0;
|
|
m_xp1=0;
|
|
m_boundedstep=false;
|
|
m_drep=false;
|
|
m_lsstart=false;
|
|
m_needf=false;
|
|
m_needfg=false;
|
|
m_steepestdescentstep=false;
|
|
m_userterminationneeded=false;
|
|
m_xrep=false;
|
|
m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICState::Copy(const CMinBLEICState &obj)
|
|
{
|
|
m_bufsize=obj.m_bufsize;
|
|
m_cidx=obj.m_cidx;
|
|
m_maxits=obj.m_maxits;
|
|
m_mcstage=obj.m_mcstage;
|
|
m_nec=obj.m_nec;
|
|
m_nfev=obj.m_nfev;
|
|
m_nic=obj.m_nic;
|
|
m_nmain=obj.m_nmain;
|
|
m_nonmonotoniccnt=obj.m_nonmonotoniccnt;
|
|
m_nslack=obj.m_nslack;
|
|
m_prectype=obj.m_prectype;
|
|
m_repdebugfeasgpaits=obj.m_repdebugfeasgpaits;
|
|
m_repdebugfeasqpits=obj.m_repdebugfeasqpits;
|
|
m_repinneriterationscount=obj.m_repinneriterationscount;
|
|
m_repnfev=obj.m_repnfev;
|
|
m_repouteriterationscount=obj.m_repouteriterationscount;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_repvaridx=obj.m_repvaridx;
|
|
m_smoothnessguardlevel=obj.m_smoothnessguardlevel;
|
|
m_activationstep=obj.m_activationstep;
|
|
m_curstpmax=obj.m_curstpmax;
|
|
m_cval=obj.m_cval;
|
|
m_diffstep=obj.m_diffstep;
|
|
m_epsf=obj.m_epsf;
|
|
m_epsg=obj.m_epsg;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_fbase=obj.m_fbase;
|
|
m_fc=obj.m_fc;
|
|
m_fm1=obj.m_fm1;
|
|
m_fm2=obj.m_fm2;
|
|
m_fn=obj.m_fn;
|
|
m_fp1=obj.m_fp1;
|
|
m_fp2=obj.m_fp2;
|
|
m_fp=obj.m_fp;
|
|
m_gm1=obj.m_gm1;
|
|
m_gp1=obj.m_gp1;
|
|
m_lastgoodstep=obj.m_lastgoodstep;
|
|
m_lastscaledgoodstep=obj.m_lastscaledgoodstep;
|
|
m_maxscaledgrad=obj.m_maxscaledgrad;
|
|
m_repdebugdx=obj.m_repdebugdx;
|
|
m_repdebugeqerr=obj.m_repdebugeqerr;
|
|
m_repdebugff=obj.m_repdebugff;
|
|
m_repdebugfs=obj.m_repdebugfs;
|
|
m_stp=obj.m_stp;
|
|
m_stpmax=obj.m_stpmax;
|
|
m_teststep=obj.m_teststep;
|
|
m_trimthreshold=obj.m_trimthreshold;
|
|
m_xm1=obj.m_xm1;
|
|
m_xp1=obj.m_xp1;
|
|
m_boundedstep=obj.m_boundedstep;
|
|
m_drep=obj.m_drep;
|
|
ArrayCopy(m_HasBndL,obj.m_HasBndL);
|
|
ArrayCopy(m_HasBndU,obj.m_HasBndU);
|
|
m_lsstart=obj.m_lsstart;
|
|
m_needf=obj.m_needf;
|
|
m_needfg=obj.m_needfg;
|
|
m_steepestdescentstep=obj.m_steepestdescentstep;
|
|
m_userterminationneeded=obj.m_userterminationneeded;
|
|
m_xrep=obj.m_xrep;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_rstate=obj.m_rstate;
|
|
m_smonitor=obj.m_smonitor;
|
|
m_solver=obj.m_solver;
|
|
m_sas=obj.m_sas;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_bufrho=obj.m_bufrho;
|
|
m_buftheta=obj.m_buftheta;
|
|
m_cgc=obj.m_cgc;
|
|
m_cgn=obj.m_cgn;
|
|
m_d=obj.m_d;
|
|
m_diagh=obj.m_diagh;
|
|
m_g=obj.m_g;
|
|
m_invs=obj.m_invs;
|
|
m_lastscaleused=obj.m_lastscaleused;
|
|
m_s=obj.m_s;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmpprec=obj.m_tmpprec;
|
|
m_ugc=obj.m_ugc;
|
|
m_ugn=obj.m_ugn;
|
|
m_work=obj.m_work;
|
|
m_x=obj.m_x;
|
|
m_xn=obj.m_xn;
|
|
m_xp=obj.m_xp;
|
|
m_xstart=obj.m_xstart;
|
|
m_bufsk=obj.m_bufsk;
|
|
m_bufyk=obj.m_bufyk;
|
|
m_cleic=obj.m_cleic;
|
|
m_lstate=obj.m_lstate;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores nonlinear optimizer State. |
|
|
//| You should use functions provided by MinBLEIC subpackage to work |
|
|
//| with this object |
|
|
//+------------------------------------------------------------------+
|
|
class CMinBLEICStateShell
|
|
{
|
|
private:
|
|
CMinBLEICState m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinBLEICStateShell(void) {}
|
|
CMinBLEICStateShell(CMinBLEICState &obj) { m_innerobj.Copy(obj); }
|
|
~CMinBLEICStateShell(void) {}
|
|
//--- methods
|
|
bool GetNeedF(void);
|
|
void SetNeedF(const bool b);
|
|
bool GetNeedFG(void);
|
|
void SetNeedFG(const bool b);
|
|
bool GetXUpdated(void);
|
|
void SetXUpdated(const bool b);
|
|
double GetF(void);
|
|
void SetF(const double d);
|
|
CMinBLEICState *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needf |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinBLEICStateShell::GetNeedF(void)
|
|
{
|
|
return(m_innerobj.m_needf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needf |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICStateShell::SetNeedF(const bool b)
|
|
{
|
|
m_innerobj.m_needf=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinBLEICStateShell::GetNeedFG(void)
|
|
{
|
|
return(m_innerobj.m_needfg);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICStateShell::SetNeedFG(const bool b)
|
|
{
|
|
m_innerobj.m_needfg=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinBLEICStateShell::GetXUpdated(void)
|
|
{
|
|
return(m_innerobj.m_xupdated);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICStateShell::SetXUpdated(const bool b)
|
|
{
|
|
m_innerobj.m_xupdated=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
double CMinBLEICStateShell::GetF(void)
|
|
{
|
|
return(m_innerobj.m_f);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICStateShell::SetF(const double d)
|
|
{
|
|
m_innerobj.m_f=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinBLEICState *CMinBLEICStateShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure stores optimization report: |
|
|
//| * InnerIterationsCount number of inner iterations |
|
|
//| * OuterIterationsCount number of outer iterations |
|
|
//| * NFEV number of gradient evaluations |
|
|
//| * TerminationType termination type (see below) |
|
|
//| TERMINATION CODES |
|
|
//| TerminationType field contains completion code,which can be: |
|
|
//| -10 unsupported combination of algorithm Settings: |
|
|
//| 1) StpMax is set to non-zero value, |
|
|
//| AND 2) non-default preconditioner is used. |
|
|
//| You can't use both features at the same moment, |
|
|
//| so you have to choose one of them (and to turn |
|
|
//| off another one). |
|
|
//| -3 inconsistent constraints. Feasible point is |
|
|
//| either nonexistent or too hard to find. Try to |
|
|
//| restart optimizer with better initial |
|
|
//| approximation |
|
|
//| 4 conditions on constraints are fulfilled |
|
|
//| with error less than or equal to EpsC |
|
|
//| 5 MaxIts steps was taken |
|
|
//| 7 stopping conditions are too stringent, |
|
|
//| further improvement is impossible, |
|
|
//| X contains best point found so far. |
|
|
//| ADDITIONAL FIELDS |
|
|
//| There are additional fields which can be used for debugging: |
|
|
//| * DebugEqErr error in the equality constraints |
|
|
//| (2-norm) |
|
|
//| * DebugFS f,calculated at projection of initial|
|
|
//| point to the feasible set |
|
|
//| * DebugFF f,calculated at the final point |
|
|
//| * DebugDX |X_start-X_final| |
|
|
//+------------------------------------------------------------------+
|
|
class CMinBLEICReport
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_debugfeasgpaits;
|
|
int m_debugfeasqpits;
|
|
int m_inneriterationscount;
|
|
int m_iterationscount;
|
|
int m_nfev;
|
|
int m_outeriterationscount;
|
|
int m_terminationtype;
|
|
int m_varidx;
|
|
double m_debugdx;
|
|
double m_debugeqerr;
|
|
double m_debugff;
|
|
double m_debugfs;
|
|
//--- constructor, destructor
|
|
CMinBLEICReport(void) { ZeroMemory(this); }
|
|
~CMinBLEICReport(void) {}
|
|
//--- copy
|
|
void Copy(const CMinBLEICReport &obj);
|
|
//--- overloading
|
|
void operator=(const CMinBLEICReport &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICReport::Copy(const CMinBLEICReport &obj)
|
|
{
|
|
//--- copy variables
|
|
m_debugfeasgpaits=obj.m_debugfeasgpaits;
|
|
m_debugfeasqpits=obj.m_debugfeasqpits;
|
|
m_inneriterationscount=obj.m_inneriterationscount;
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_nfev=obj.m_nfev;
|
|
m_outeriterationscount=obj.m_outeriterationscount;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_varidx=obj.m_varidx;
|
|
m_debugdx=obj.m_debugdx;
|
|
m_debugeqerr=obj.m_debugeqerr;
|
|
m_debugff=obj.m_debugff;
|
|
m_debugfs=obj.m_debugfs;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure stores optimization report: |
|
|
//| * InnerIterationsCount number of inner iterations |
|
|
//| * OuterIterationsCount number of outer iterations |
|
|
//| * NFEV number of gradient evaluations |
|
|
//| * TerminationType termination type (see below) |
|
|
//| TERMINATION CODES |
|
|
//| TerminationType field contains completion code,which can be: |
|
|
//| -10 unsupported combination of algorithm Settings: |
|
|
//| 1) StpMax is set to non-zero value, |
|
|
//| AND 2) non-default preconditioner is used. |
|
|
//| You can't use both features at the same moment, |
|
|
//| so you have to choose one of them (and to turn |
|
|
//| off another one). |
|
|
//| -3 inconsistent constraints. Feasible point is |
|
|
//| either nonexistent or too hard to find. Try to |
|
|
//| restart optimizer with better initial |
|
|
//| approximation |
|
|
//| 4 conditions on constraints are fulfilled |
|
|
//| with error less than or equal to EpsC |
|
|
//| 5 MaxIts steps was taken |
|
|
//| 7 stopping conditions are too stringent, |
|
|
//| further improvement is impossible, |
|
|
//| X contains best point found so far. |
|
|
//| ADDITIONAL FIELDS |
|
|
//| There are additional fields which can be used for debugging: |
|
|
//| * DebugEqErr error in the equality constraints |
|
|
//| (2-norm) |
|
|
//| * DebugFS f,calculated at projection of initial|
|
|
//| point to the feasible set |
|
|
//| * DebugFF f,calculated at the final point |
|
|
//| * DebugDX |X_start-X_final| |
|
|
//+------------------------------------------------------------------+
|
|
class CMinBLEICReportShell
|
|
{
|
|
private:
|
|
CMinBLEICReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinBLEICReportShell(void) {}
|
|
CMinBLEICReportShell(CMinBLEICReport &obj) { m_innerobj.Copy(obj); }
|
|
~CMinBLEICReportShell(void) {}
|
|
//--- methods
|
|
int GetInnerIterationsCount(void);
|
|
void SetInnerIterationsCount(const int i);
|
|
int GetOuterIterationsCount(void);
|
|
void SetOuterIterationsCount(const int i);
|
|
int GetNFev(void);
|
|
void SetNFev(const int i);
|
|
int GetTerminationType(void);
|
|
void SetTerminationType(const int i);
|
|
double GetDebugEqErr(void);
|
|
void SetDebugEqErr(const double d);
|
|
double GetDebugFS(void);
|
|
void SetDebugFS(const double d);
|
|
double GetDebugFF(void);
|
|
void SetDebugFF(const double d);
|
|
double GetDebugDX(void);
|
|
void SetDebugDX(const double d);
|
|
CMinBLEICReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable inneriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CMinBLEICReportShell::GetInnerIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_inneriterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable inneriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICReportShell::SetInnerIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_inneriterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable outeriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CMinBLEICReportShell::GetOuterIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_outeriterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable outeriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICReportShell::SetOuterIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_outeriterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable nfev |
|
|
//+------------------------------------------------------------------+
|
|
int CMinBLEICReportShell::GetNFev(void)
|
|
{
|
|
return(m_innerobj.m_nfev);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable nfev |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICReportShell::SetNFev(const int i)
|
|
{
|
|
m_innerobj.m_nfev=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
int CMinBLEICReportShell::GetTerminationType(void)
|
|
{
|
|
return(m_innerobj.m_terminationtype);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICReportShell::SetTerminationType(const int i)
|
|
{
|
|
m_innerobj.m_terminationtype=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable debugeqerr |
|
|
//+------------------------------------------------------------------+
|
|
double CMinBLEICReportShell::GetDebugEqErr(void)
|
|
{
|
|
return(m_innerobj.m_debugeqerr);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable debugeqerr |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICReportShell::SetDebugEqErr(const double d)
|
|
{
|
|
m_innerobj.m_debugeqerr=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable debugfs |
|
|
//+------------------------------------------------------------------+
|
|
double CMinBLEICReportShell::GetDebugFS(void)
|
|
{
|
|
return(m_innerobj.m_debugfs);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable debugfs |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICReportShell::SetDebugFS(const double d)
|
|
{
|
|
m_innerobj.m_debugfs=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable debugff |
|
|
//+------------------------------------------------------------------+
|
|
double CMinBLEICReportShell::GetDebugFF(void)
|
|
{
|
|
return(m_innerobj.m_debugff);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable debugff |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICReportShell::SetDebugFF(const double d)
|
|
{
|
|
m_innerobj.m_debugff=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable debugdx |
|
|
//+------------------------------------------------------------------+
|
|
double CMinBLEICReportShell::GetDebugDX(void)
|
|
{
|
|
return(m_innerobj.m_debugdx);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable debugdx |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEICReportShell::SetDebugDX(const double d)
|
|
{
|
|
m_innerobj.m_debugdx=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinBLEICReport *CMinBLEICReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Bound constrained optimization with additional linear equality |
|
|
//| and inequality constraints |
|
|
//+------------------------------------------------------------------+
|
|
class CMinBLEIC
|
|
{
|
|
public:
|
|
//--- class constants
|
|
static const double m_maxnonmonotoniclen;
|
|
static const double m_gtol;
|
|
static const double m_initialdecay;
|
|
static const double m_mindecay;
|
|
static const double m_decaycorrection;
|
|
static const double m_penaltyfactor;
|
|
|
|
//--- public methods
|
|
static void MinBLEICCreate(const int n,double &x[],CMinBLEICState &State);
|
|
static void MinBLEICCreate(const int n,CRowDouble &x,CMinBLEICState &State);
|
|
static void MinBLEICCreateF(const int n,double &x[],const double diffstep,CMinBLEICState &State);
|
|
static void MinBLEICCreateF(const int n,CRowDouble &x,const double diffstep,CMinBLEICState &State);
|
|
static void MinBLEICSetBC(CMinBLEICState &State,double &bndl[],double &bndu[]);
|
|
static void MinBLEICSetBC(CMinBLEICState &State,CRowDouble &bndl,CRowDouble &bndu);
|
|
static void MinBLEICSetLC(CMinBLEICState &State,CMatrixDouble &c,int &ct[],const int k);
|
|
static void MinBLEICSetLC(CMinBLEICState &State,CMatrixDouble &c,CRowInt &ct,const int k);
|
|
static void MinBLEICSetInnerCond(CMinBLEICState &State,const double epsg,const double epsf,const double epsx);
|
|
static void MinBLEICSetOuterCond(CMinBLEICState &State,const double epsx,const double epsi);
|
|
static void MinBLEICSetCond(CMinBLEICState &State,double epsg,double epsf,double epsx,int m_maxits);
|
|
static void MinBLEICSetScale(CMinBLEICState &State,double &s[]);
|
|
static void MinBLEICSetScale(CMinBLEICState &State,CRowDouble &s);
|
|
static void MinBLEICSetPrecDefault(CMinBLEICState &State);
|
|
static void MinBLEICSetPrecDiag(CMinBLEICState &State,double &d[]);
|
|
static void MinBLEICSetPrecDiag(CMinBLEICState &State,CRowDouble &d);
|
|
static void MinBLEICSetPrecScale(CMinBLEICState &State);
|
|
static void MinBLEICSetMaxIts(CMinBLEICState &State,const int m_maxits);
|
|
static void MinBLEICSetXRep(CMinBLEICState &State,const bool needxrep);
|
|
static void MinBLEICSetDRep(CMinBLEICState &State,bool needdrep);
|
|
static void MinBLEICSetStpMax(CMinBLEICState &State,const double stpmax);
|
|
static void MinBLEICOptGuardGradient(CMinBLEICState &State,double &teststep);
|
|
static void MinBLEICOptGuardSmoothness(CMinBLEICState &State,int level);
|
|
static void MinBLEICOptGuardResults(CMinBLEICState &State,COptGuardReport &rep);
|
|
static void MinBLEICOptGuardNonC1Test0Results(CMinBLEICState &State,COptGuardNonC1Test0Report &strrep,COptGuardNonC1Test0Report &lngrep);
|
|
static void MinBLEICOptGuardNonC1Test1Results(CMinBLEICState &State,COptGuardNonC1Test1Report &strrep,COptGuardNonC1Test1Report &lngrep);
|
|
static void MinBLEICResults(CMinBLEICState &State,double &x[],CMinBLEICReport &rep);
|
|
static void MinBLEICResults(CMinBLEICState &State,CRowDouble &x,CMinBLEICReport &rep);
|
|
static void MinBLEICResultsBuf(CMinBLEICState &State,double &x[],CMinBLEICReport &rep);
|
|
static void MinBLEICResultsBuf(CMinBLEICState &State,CRowDouble &x,CMinBLEICReport &rep);
|
|
static void MinBLEICRestartFrom(CMinBLEICState &State,double &x[]);
|
|
static void MinBLEICRestartFrom(CMinBLEICState &State,CRowDouble &x);
|
|
static void MinBLEICRequestTermination(CMinBLEICState &State);
|
|
static void MinBLEICEmergencyTermination(CMinBLEICState &State);
|
|
static bool MinBLEICIteration(CMinBLEICState &State);
|
|
|
|
private:
|
|
static void ClearRequestFields(CMinBLEICState &State);
|
|
static void MinBLEICInitInternal(const int n,CRowDouble &x,const double diffstep,CMinBLEICState &State);
|
|
static void UpdateEstimateOfGoodStep(double &estimate,double newstep);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CMinBLEIC::m_maxnonmonotoniclen=1.0E-7;
|
|
const double CMinBLEIC::m_gtol=0.4;
|
|
const double CMinBLEIC::m_initialdecay=0.5;
|
|
const double CMinBLEIC::m_mindecay=0.1;
|
|
const double CMinBLEIC::m_decaycorrection=0.8;
|
|
const double CMinBLEIC::m_penaltyfactor=100;
|
|
//+------------------------------------------------------------------+
|
|
//| BOUND CONSTRAINED OPTIMIZATION |
|
|
//| WITH ADDITIONAL LINEAR EQUALITY AND INEQUALITY CONSTRAINTS|
|
|
//| DESCRIPTION: |
|
|
//| The subroutine minimizes function F(x) of N arguments subject to |
|
|
//| any combination of: |
|
|
//| * bound constraints |
|
|
//| * linear inequality constraints |
|
|
//| * linear equality constraints |
|
|
//| REQUIREMENTS: |
|
|
//| * user must provide function value and gradient |
|
|
//| * starting point X0 must be feasible or |
|
|
//| not too far away from the feasible set |
|
|
//| * grad(f) must be Lipschitz continuous on a level set: |
|
|
//| L = { x : f(x)<=f(x0) } |
|
|
//| * function must be defined everywhere on the feasible set F |
|
|
//| USAGE: |
|
|
//| Constrained optimization if far more complex than the |
|
|
//| unconstrained one. Here we give very brief outline of the BLEIC |
|
|
//| optimizer. We strongly recommend you to read examples in the |
|
|
//| ALGLIB Reference Manual and to read ALGLIB User Guide on |
|
|
//| optimization, which is available at |
|
|
//| http://www.alglib.net/optimization/ |
|
|
//| 1. User initializes algorithm State with MinBLEICCreate() call |
|
|
//| 2. USer adds boundary and/or linear constraints by calling |
|
|
//| MinBLEICSetBC() and MinBLEICSetLC() functions. |
|
|
//| 3. User sets stopping conditions for underlying unconstrained |
|
|
//| m_solver with MinBLEICSetInnerCond() call. |
|
|
//| This function controls accuracy of underlying optimization |
|
|
//| algorithm. |
|
|
//| 4. User sets stopping conditions for outer iteration by calling |
|
|
//| MinBLEICSetOuterCond() function. |
|
|
//| This function controls handling of boundary and inequality |
|
|
//| constraints. |
|
|
//| 5. Additionally, user may set limit on number of internal |
|
|
//| iterations by MinBLEICSetMaxIts() call. |
|
|
//| This function allows to prevent algorithm from looping |
|
|
//| forever. |
|
|
//| 6. User calls MinBLEICOptimize() function which takes algorithm |
|
|
//| State and pointer (delegate, etc.) to callback function |
|
|
//| which calculates F/G. |
|
|
//| 7. User calls MinBLEICResults() to get solution |
|
|
//| 8. Optionally user may call MinBLEICRestartFrom() to solve |
|
|
//| another problem with same N but another starting point. |
|
|
//| MinBLEICRestartFrom() allows to reuse already initialized |
|
|
//| structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N>0: |
|
|
//| * if given, only leading N elements of X are |
|
|
//| used |
|
|
//| * if not given, automatically determined from |
|
|
//| size ofX |
|
|
//| X - starting point, array[N]: |
|
|
//| * it is better to set X to a feasible point |
|
|
//| * but X can be infeasible, in which case |
|
|
//| algorithm will try to find feasible point |
|
|
//| first, using X as initial approximation. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICCreate(const int n,double &x[],CMinBLEICState &State)
|
|
{
|
|
CRowDouble X=x;
|
|
MinBLEICCreate(n,X,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICCreate(const int n,CRowDouble &x,CMinBLEICState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- function call
|
|
MinBLEICInitInternal(n,x,0.0,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| The subroutine is finite difference variant of MinBLEICCreate(). |
|
|
//| It uses finite differences in order to differentiate target |
|
|
//| function. |
|
|
//| Description below contains information which is specific to this |
|
|
//| function only. We recommend to read comments on MinBLEICCreate() |
|
|
//| in order to get more information about creation of BLEIC |
|
|
//| optimizer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N>0: |
|
|
//| * if given, only leading N elements of X are used|
|
|
//| * if not given, automatically determined from |
|
|
//| size of X |
|
|
//| X - starting point, array[0..m_n-1]. |
|
|
//| DiffStep- differentiation step, >0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NOTES: |
|
|
//| 1. algorithm uses 4-point central formula for differentiation. |
|
|
//| 2. differentiation step along I-th axis is equal to DiffStep*S[I]|
|
|
//| where S[] is scaling vector which can be set by |
|
|
//| MinBLEICSetScale() call. |
|
|
//| 3. we recommend you to use moderate values of differentiation |
|
|
//| step. Too large step will result in too large truncation |
|
|
//| errors, while too small step will result in too large |
|
|
//| numerical errors. 1.0E-6 can be good value to start with. |
|
|
//| 4. Numerical differentiation is very inefficient - one gradient |
|
|
//| calculation needs 4*N function evaluations. This function will|
|
|
//| work for any N - either small (1...10), moderate (10...100) or|
|
|
//| large (100...). However, performance penalty will be too |
|
|
//| severe for any N's except for small ones. |
|
|
//| We should also say that code which relies on numerical |
|
|
//| differentiation is less robust and precise. CG needs exact |
|
|
//| gradient values. Imprecise gradient may slow down convergence,|
|
|
//| especially on highly nonlinear problems. |
|
|
//| Thus we recommend to use this function for fast prototyping on|
|
|
//| small - dimensional problems only, and to implement analytical|
|
|
//| gradient as soon as possible. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICCreateF(const int n,double &x[],
|
|
const double diffstep,
|
|
CMinBLEICState &State)
|
|
{
|
|
CRowDouble X=x;
|
|
MinBLEICCreateF(n,X,diffstep,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICCreateF(const int n,CRowDouble &x,
|
|
const double diffstep,
|
|
CMinBLEICState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(x.Size()>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(diffstep),__FUNCTION__+": DiffStep is infinite or NaN!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep is non-positive!"))
|
|
return;
|
|
//--- function call
|
|
MinBLEICInitInternal(n,x,diffstep,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets boundary constraints for BLEIC optimizer. |
|
|
//| Boundary constraints are inactive by default (after initial |
|
|
//| creation). They are preserved after algorithm restart with |
|
|
//| MinBLEICRestartFrom(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bounds, array[N]. |
|
|
//| If some (all) variables are unbounded, you may |
|
|
//| specify very small number or -INF. |
|
|
//| BndU - upper bounds, array[N]. |
|
|
//| If some (all) variables are unbounded, you may |
|
|
//| specify very large number or +INF. |
|
|
//| NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case |
|
|
//| I-th variable will be "frozen" at X[i]=BndL[i]=BndU[i]. |
|
|
//| NOTE 2: this m_solver has following useful properties: |
|
|
//| * bound constraints are always satisfied exactly |
|
|
//| * function is evaluated only INSIDE area specified by bound |
|
|
//| constraints, even when numerical differentiation is used |
|
|
//| (algorithm adjusts nodes according to boundary constraints) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetBC(CMinBLEICState &State,double &bndl[],
|
|
double &bndu[])
|
|
{
|
|
CRowDouble BndL=bndl;
|
|
CRowDouble BndU=bndu;
|
|
MinBLEICSetBC(State,BndL,BndU);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetBC(CMinBLEICState &State,CRowDouble &bndl,
|
|
CRowDouble &bndu)
|
|
{
|
|
int n=State.m_nmain;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndu)>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndl[i]) || AL_NEGINF==bndl[i],__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndu[i]) || AL_POSINF==bndu[i],__FUNCTION__+": BndU contains NAN or -INF"))
|
|
return;
|
|
//--- change values
|
|
State.m_bndl.Set(i,bndl[i]);
|
|
State.m_bndu.Set(i,bndu[i]);
|
|
State.m_HasBndL[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_HasBndU[i]=MathIsValidNumber(bndu[i]);
|
|
}
|
|
CSActiveSets::SASSetBC(State.m_sas,bndl,bndu);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets linear constraints for BLEIC optimizer. |
|
|
//| Linear constraints are inactive by default (after initial |
|
|
//| creation). They are preserved after algorithm restart with |
|
|
//| MinBLEICRestartFrom(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with |
|
|
//| MinBLEICCreate call. |
|
|
//| C - linear constraints, array[K,N+1]. |
|
|
//| Each row of C represents one constraint, either |
|
|
//| equality or inequality (see below): |
|
|
//| * first N elements correspond to coefficients, |
|
|
//| * last element corresponds to the right part. |
|
|
//| All elements of C (including right part) must be |
|
|
//| finite. |
|
|
//| CT - type of constraints, array[K]: |
|
|
//| * if CT[i]>0, then I-th constraint is |
|
|
//| C[i,*]*x >= C[i,n+1] |
|
|
//| * if CT[i]=0, then I-th constraint is |
|
|
//| C[i,*]*x = C[i,n+1] |
|
|
//| * if CT[i]<0, then I-th constraint is |
|
|
//| C[i,*]*x <= C[i,n+1] |
|
|
//| K - number of equality/inequality constraints, K>=0: |
|
|
//| * if given, only leading K elements of C/CT are |
|
|
//| used |
|
|
//| * if not given, automatically determined from |
|
|
//| sizes of C/CT |
|
|
//| NOTE 1: linear (non-bound) constraints are satisfied only |
|
|
//| approximately: |
|
|
//| * there always exists some minor violation (about Epsilon in |
|
|
//| magnitude) due to rounding errors |
|
|
//| * numerical differentiation, if used, may lead to function |
|
|
//| evaluations outside of the feasible area, because algorithm |
|
|
//| does NOT change numerical differentiation formula according to |
|
|
//| linear constraints. |
|
|
//| If you want constraints to be satisfied exactly, try to |
|
|
//| reformulate your problem in such manner that all constraints will|
|
|
//| become boundary ones (this kind of constraints is always |
|
|
//| satisfied exactly, both in the final solution and in all |
|
|
//| intermediate points). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetLC(CMinBLEICState &State,CMatrixDouble &c,
|
|
int &ct[],const int k)
|
|
{
|
|
CRowInt CT=ct;
|
|
MinBLEICSetLC(State,c,CT,k);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetLC(CMinBLEICState &State,CMatrixDouble &c,
|
|
CRowInt &ct,const int k)
|
|
{
|
|
//--- create variables
|
|
int nmain=State.m_nmain;
|
|
int i=0;
|
|
//--- First,check for errors in the inputs
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(c)>=nmain+1 || k==0,__FUNCTION__+": Cols(C)<N+1"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(c)>=k,__FUNCTION__+": Rows(C)<K"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(ct)>=k,__FUNCTION__+": Length(CT)<K"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(c,k,nmain+1),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
//-- Handle zero K
|
|
if(k==0)
|
|
{
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
CSActiveSets::SASSetLC(State.m_sas,c,ct,0);
|
|
return;
|
|
}
|
|
//-- Equality constraints are stored first, in the upper
|
|
//-- NEC rows of State.CLEIC matrix. Inequality constraints
|
|
//-- are stored in the next NIC rows.
|
|
//-- NOTE: we convert inequality constraints to the form
|
|
//-- A*x<=b before copying them.
|
|
State.m_cleic.Resize(k,nmain+1);
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
for(i=0; i<k; i++)
|
|
{
|
|
if(ct[i]==0)
|
|
{
|
|
State.m_cleic.Row(State.m_nec,c[i]+0);
|
|
State.m_nec++;
|
|
}
|
|
}
|
|
for(i=0; i<k; i++)
|
|
{
|
|
if(ct[i]!=0)
|
|
{
|
|
if(ct[i]>0)
|
|
State.m_cleic.Row(State.m_nec+State.m_nic,c[i]*(-1.0));
|
|
else
|
|
State.m_cleic.Row(State.m_nec+State.m_nic,c[i]+0);
|
|
State.m_nic++;
|
|
}
|
|
}
|
|
//-- Normalize rows of State.CLEIC: each row must have unit norm.
|
|
//-- Norm is calculated using first N elements (i.e. right part is
|
|
//-- not counted when we calculate norm).
|
|
for(i=0; i<k; i++)
|
|
{
|
|
double v=CAblasF::RDotRR(nmain,State.m_cleic,i,State.m_cleic,i);
|
|
if(v==0.0)
|
|
continue;
|
|
v=1/MathSqrt(v);
|
|
State.m_cleic.Row(i,State.m_cleic[i]*v);
|
|
}
|
|
CSActiveSets::SASSetLC(State.m_sas,c,ct,k);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping conditions for the underlying |
|
|
//| nonlinear CG optimizer. It controls overall accuracy of solution.|
|
|
//| These conditions should be strict enough in order for algorithm |
|
|
//| to converge. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsG - >=0 |
|
|
//| The subroutine finishes its work if the condition|
|
|
//| |v|<EpsG is satisfied, where: |
|
|
//| * |.| means Euclidian norm |
|
|
//| * v - scaled gradient vector, v[i]=g[i]*s[i] |
|
|
//| * g - gradient |
|
|
//| * s - scaling coefficients set by |
|
|
//| MinBLEICSetScale() |
|
|
//| EpsF - >=0 |
|
|
//| The subroutine finishes its work if on k+1-th |
|
|
//| iteration the condition |F(k+1)-F(k)| <= |
|
|
//| <= EpsF*max{|F(k)|,|F(k+1)|,1} is satisfied. |
|
|
//| EpsX - >=0 |
|
|
//| The subroutine finishes its work if on k+1-th |
|
|
//| iteration the condition |v|<=EpsX is fulfilled, |
|
|
//| where: |
|
|
//| * |.| means Euclidian norm |
|
|
//| * v - scaled step vector, v[i]=dx[i]/s[i] |
|
|
//| * dx - ste pvector, dx=X(k+1)-X(k) |
|
|
//| * s - scaling coefficients set by |
|
|
//| MinBLEICSetScale() |
|
|
//| Passing EpsG=0, EpsF=0 and EpsX=0 (simultaneously) will lead to |
|
|
//| automatic stopping criterion selection. |
|
|
//| These conditions are used to terminate inner iterations. However,|
|
|
//| you need to tune termination conditions for outer iterations too.|
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetInnerCond(CMinBLEICState &State,const double epsg,
|
|
const double epsf,const double epsx)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsg),__FUNCTION__+": EpsG is not finite number"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsg>=0.0,__FUNCTION__+": negative EpsG"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsf),__FUNCTION__+": EpsF is not finite number"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsf>=0.0,__FUNCTION__+": negative EpsF"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsx),__FUNCTION__+": EpsX is not finite number"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX"))
|
|
return;
|
|
//--- change values
|
|
State.m_epsg=epsg;
|
|
State.m_epsf=epsf;
|
|
State.m_epsx=epsx;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping conditions for outer iteration of |
|
|
//| BLEIC algo. |
|
|
//| These conditions control accuracy of constraint handling and |
|
|
//| amount of infeasibility allowed in the solution. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsX - >0, stopping condition on outer iteration step |
|
|
//| length |
|
|
//| EpsI - >0, stopping condition on infeasibility |
|
|
//| Both EpsX and EpsI must be non-zero. |
|
|
//| MEANING OF EpsX |
|
|
//| EpsX is a stopping condition for outer iterations. Algorithm will|
|
|
//| stop when solution of the current modified subproblem will be |
|
|
//| within EpsX (using 2-norm) of the previous solution. |
|
|
//| MEANING OF EpsI |
|
|
//| EpsI controls feasibility properties - algorithm won't stop until|
|
|
//| all inequality constraints will be satisfied with error (distance|
|
|
//| from current point to the feasible area) at most EpsI. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetOuterCond(CMinBLEICState &State,const double epsx,
|
|
const double epsi)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsx),__FUNCTION__+": EpsX is not finite number"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": non-positive EpsX"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsi),__FUNCTION__+": EpsI is not finite number"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((double)(epsi)>0.0,__FUNCTION__+": non-positive EpsI"))
|
|
return;
|
|
//--- change values
|
|
State.m_epsx=epsx;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping conditions for the optimizer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsG - >=0, The subroutine finishes its work if the |
|
|
//| condition |v|<EpsG is satisfied, where: |
|
|
//| * |.| means Euclidian norm |
|
|
//| * v - scaled gradient vector, v[i]=g[i]*s[i] |
|
|
//| * g - gradient |
|
|
//| * s - scaling coefficients set by |
|
|
//| MinBLEICSetScale() |
|
|
//| EpsF - >=0, The subroutine finishes its work if on k+1-th |
|
|
//| iteration the condition |
|
|
//| |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} |
|
|
//| is satisfied. |
|
|
//| EpsX - >=0, The subroutine finishes its work if on k+1-th |
|
|
//| iteration the condition |v|<=EpsX is fulfilled|
|
|
//| where: |
|
|
//| * |.| means Euclidian norm |
|
|
//| * v - scaled step vector, v[i]=dx[i]/s[i] |
|
|
//| * dx - step vector, dx=X(k+1)-X(k) |
|
|
//| * s - scaling coefficients set by |
|
|
//| MinBLEICSetScale() |
|
|
//| MaxIts - maximum number of iterations. If MaxIts=0, the |
|
|
//| number of iterations is unlimited. |
|
|
//| Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) |
|
|
//| will lead to automatic stopping criterion selection. |
|
|
//| NOTE: when SetCond() called with non-zero MaxIts, BLEIC solver |
|
|
//| may perform slightly more than MaxIts iterations. I.e., |
|
|
//| MaxIts sets non-strict limit on iterations count. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetCond(CMinBLEICState &State,
|
|
double epsg,
|
|
double epsf,
|
|
double epsx,
|
|
int m_maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(epsg),__FUNCTION__+": EpsG is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsg>=0.0,__FUNCTION__+": negative EpsG"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsf),__FUNCTION__+": EpsF is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsf>=0.0,__FUNCTION__+": negative EpsF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsx),__FUNCTION__+": EpsX is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX"))
|
|
return;
|
|
if(!CAp::Assert(m_maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
if(epsg==0.0 && epsf==0.0 && epsx==0.0 && m_maxits==0)
|
|
epsx=1.0E-6;
|
|
State.m_epsg=epsg;
|
|
State.m_epsf=epsf;
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=m_maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients for BLEIC optimizer. |
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions (step size and gradient are scaled before comparison |
|
|
//| with tolerances). Scale of the I-th variable is a translation |
|
|
//| invariant measure of: |
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| Scaling is also used by finite difference variant of the |
|
|
//| optimizer - step along I-th axis is equal to DiffStep*S[I]. |
|
|
//| In most optimizers (and in the BLEIC too) scaling is NOT a form |
|
|
//| of preconditioning. It just affects stopping conditions. You |
|
|
//| should set preconditioner by separate call to one of the |
|
|
//| MinBLEICSetPrec...() functions. |
|
|
//| There is a special preconditioning mode, however, which uses |
|
|
//| scaling coefficients to form diagonal preconditioning matrix. |
|
|
//| You can turn this mode on, if you want. But you should understand|
|
|
//| that scaling is not the same thing as preconditioning - these are|
|
|
//| two different, although related forms of tuning m_solver. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients |
|
|
//| S[i] may be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetScale(CMinBLEICState &State,double &s[])
|
|
{
|
|
CRowDouble Scale=s;
|
|
MinBLEICSetScale(State,Scale);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetScale(CMinBLEICState &State,CRowDouble &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.Size()>=State.m_nmain,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
for(int i=0; i<State.m_nmain; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
State.m_s.Set(i,MathAbs(s[i]));
|
|
}
|
|
CSActiveSets::SASSetScale(State.m_sas,s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: preconditioning is turned |
|
|
//| off. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetPrecDefault(CMinBLEICState &State)
|
|
{
|
|
State.m_prectype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: diagonal of approximate |
|
|
//| Hessian is used. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| D - diagonal of the approximate Hessian, |
|
|
//| array[0..m_n-1], (if larger, only leading N |
|
|
//| elements are used). |
|
|
//| NOTE 1: D[i] should be positive. Exception will be thrown |
|
|
//| otherwise. |
|
|
//| NOTE 2: you should pass diagonal of approximate Hessian - NOT |
|
|
//| ITS INVERSE. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetPrecDiag(CMinBLEICState &State,double &d[])
|
|
{
|
|
CRowDouble D=d;
|
|
CMinBLEIC::MinBLEICSetPrecDiag(State,D);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetPrecDiag(CMinBLEICState &State,CRowDouble &d)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(d)>=State.m_nmain,__FUNCTION__+": D is too short"))
|
|
return;
|
|
for(int i=0; i<=State.m_nmain-1; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(d[i]),__FUNCTION__+": D contains infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(d[i]>0.0,__FUNCTION__+": D contains non-positive elements"))
|
|
return;
|
|
}
|
|
//--- function call
|
|
CApServ::RVectorSetLengthAtLeast(State.m_diagh,State.m_nmain);
|
|
State.m_prectype=2;
|
|
//--- copy
|
|
State.m_diagh=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: scale-based diagonal |
|
|
//| preconditioning. |
|
|
//| This preconditioning mode can be useful when you don't have |
|
|
//| approximate diagonal of Hessian, but you know that your variables|
|
|
//| are badly scaled (for example, one variable is in [1,10], and |
|
|
//| another in [1000,100000]), and most part of the ill-conditioning |
|
|
//| comes from different scales of vars. |
|
|
//| In this case simple scale-based preconditioner, with H.Set(i, |
|
|
//| = 1/(s[i]^2), can greatly improve convergence. |
|
|
//| IMPRTANT: you should set scale of your variables with |
|
|
//| MinBLEICSetScale() call (before or after MinBLEICSetPrecScale() |
|
|
//| call). Without knowledge of the scale of your variables |
|
|
//| scale-based preconditioner will be just unit matrix. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetPrecScale(CMinBLEICState &State)
|
|
{
|
|
State.m_prectype=3;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function allows to stop algorithm after specified number of |
|
|
//| inner iterations. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| MaxIts - maximum number of inner iterations. |
|
|
//| If MaxIts=0, the number of iterations is |
|
|
//| unlimited. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetMaxIts(CMinBLEICState &State,
|
|
const int m_maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(m_maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
//--- change value
|
|
State.m_maxits=m_maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function turns on/off reporting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NeedXRep- whether iteration reports are needed or not |
|
|
//| If NeedXRep is True, algorithm will call rep() callback function |
|
|
//| if it is provided to MinBLEICOptimize(). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetXRep(CMinBLEICState &State,
|
|
const bool needxrep)
|
|
{
|
|
State.m_xrep=needxrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function turns on/off line search reports. |
|
|
//| These reports are described in more details in developer-only |
|
|
//| comments on MinBLEICState object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NeedDRep - whether line search reports are needed or not |
|
|
//| This function is intended for private use only. Turning it on |
|
|
//| artificially may cause program failure. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetDRep(CMinBLEICState &State,
|
|
bool needdrep)
|
|
{
|
|
State.m_drep=needdrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets maximum step length |
|
|
//| IMPORTANT: this feature is hard to combine with preconditioning. |
|
|
//| You can't set upper limit on step length, when you solve |
|
|
//| optimization problem with linear (non-boundary) constraints AND |
|
|
//| preconditioner turned on. |
|
|
//| When non-boundary constraints are present, you have to either a) |
|
|
//| use preconditioner, or b) use upper limit on step length. YOU |
|
|
//| CAN'T USE BOTH! In this case algorithm will terminate with |
|
|
//| appropriate error code. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| StpMax - maximum step length, >=0. Set StpMax to 0.0, if |
|
|
//| you don't want to limit step length. |
|
|
//| Use this subroutine when you optimize target function which |
|
|
//| contains exp() or other fast growing functions, and optimization |
|
|
//| algorithm makes too large steps which lead to overflow. This |
|
|
//| function allows us to reject steps that are too large (and |
|
|
//| therefore expose us to the possible overflow) without actually |
|
|
//| calculating function value at the x+stp*d. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICSetStpMax(CMinBLEICState &State,
|
|
const double stpmax)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(stpmax),__FUNCTION__+": StpMax is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(stpmax>=0.0,__FUNCTION__+": StpMax<0!"))
|
|
return;
|
|
//--- change value
|
|
State.m_stpmax=stpmax;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function activates/deactivates verification of the user- |
|
|
//| supplied analytic gradient. |
|
|
//| Upon activation of this option OptGuard integrity checker |
|
|
//| performs numerical differentiation of your target function at the|
|
|
//| initial point (note: future versions may also perform check at |
|
|
//| the final point) and compares numerical gradient with analytic |
|
|
//| one provided by you. |
|
|
//| If difference is too large, an error flag is set and optimization|
|
|
//| session continues. After optimization session is over, you can |
|
|
//| retrieve the report which stores both gradients and specific |
|
|
//| components highlighted as suspicious by the OptGuard. |
|
|
//| The primary OptGuard report can be retrieved with |
|
|
//| MinBLEICOptGuardResults(). |
|
|
//| IMPORTANT: gradient check is a high-overhead option which will |
|
|
//| cost you about 3*N additional function evaluations. |
|
|
//| In many cases it may cost as much as the rest of the |
|
|
//| optimization session. |
|
|
//| YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO |
|
|
//| CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY. |
|
|
//| NOTE: unlike previous incarnation of the gradient checking code, |
|
|
//| OptGuard does NOT interrupt optimization even if it |
|
|
//| discovers bad gradient. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure used to store algorithm State |
|
|
//| TestStep - verification step used for numerical |
|
|
//| differentiation: |
|
|
//| * TestStep=0 turns verification off |
|
|
//| * TestStep>0 activates verification |
|
|
//| You should carefully choose TestStep. Value |
|
|
//| which is too large (so large that function |
|
|
//| behavior is non-cubic at this scale) will lead |
|
|
//| to false alarms. Too short step will result in |
|
|
//| rounding errors dominating numerical derivative.|
|
|
//| You may use different step for different parameters by means of |
|
|
//| setting scale with MinBLEICSetScale(). |
|
|
//| === EXPLANATION ================================================ |
|
|
//| In order to verify gradient algorithm performs following steps: |
|
|
//| * two trial steps are made to X[i]-TestStep*S[i] and |
|
|
//| X[i]+TestStep*S[i], where X[i] is i-th component of the |
|
|
//| initial point and S[i] is a scale of i-th parameter |
|
|
//| * F(X) is evaluated at these trial points |
|
|
//| * we perform one more evaluation in the middle point of the |
|
|
//| interval |
|
|
//| * we build cubic model using function values and derivatives at|
|
|
//| trial points and we compare its prediction with actual value |
|
|
//| in the middle point |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICOptGuardGradient(CMinBLEICState &State,
|
|
double &teststep)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(teststep),__FUNCTION__+": TestStep contains NaN or INF"))
|
|
return;
|
|
if(!CAp::Assert(teststep>=0.0,__FUNCTION__+": invalid argument TestStep(TestStep<0)"))
|
|
return;
|
|
State.m_teststep=teststep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function activates/deactivates nonsmoothness monitoring |
|
|
//| option of the OptGuard integrity checker. Smoothness monitor |
|
|
//| silently observes solution process and tries to detect ill-posed |
|
|
//| problems, i.e. ones with: |
|
|
//| a) discontinuous target function (non-C0) |
|
|
//| b) nonsmooth target function (non-C1) |
|
|
//| Smoothness monitoring does NOT interrupt optimization even if it |
|
|
//| suspects that your problem is nonsmooth. It just sets |
|
|
//| corresponding flags in the OptGuard report which can be retrieved|
|
|
//| after optimization is over. |
|
|
//| Smoothness monitoring is a moderate overhead option which often |
|
|
//| adds less than 1% to the optimizer running time. Thus, you can |
|
|
//| use it even for large scale problems. |
|
|
//| NOTE: OptGuard does NOT guarantee that it will always detect |
|
|
//| C0/C1 continuity violations. |
|
|
//| First, minor errors are hard to catch - say, a 0.0001 difference |
|
|
//| in the model values at two sides of the gap may be due |
|
|
//| to discontinuity of the model - or simply because the model has |
|
|
//| changed. |
|
|
//| Second, C1-violations are especially difficult to detect in a |
|
|
//| noninvasive way. The optimizer usually performs very short steps |
|
|
//| near the nonsmoothness, and differentiation usually introduces a |
|
|
//| lot of numerical noise. It is hard to tell whether some tiny |
|
|
//| discontinuity in the slope is due to real nonsmoothness or just |
|
|
//| due to numerical noise alone. |
|
|
//| Our top priority was to avoid false positives, so in some rare |
|
|
//| cases minor errors may went unnoticed (however, in most cases |
|
|
//| they can be spotted with restart from different initial point). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| Level - monitoring level: |
|
|
//| * 0 - monitoring is disabled |
|
|
//| * 1 - noninvasive low-overhead monitoring; function|
|
|
//| values and/or gradients are recorded, but |
|
|
//| OptGuard does not try to perform additional |
|
|
//| evaluations in order to get more information |
|
|
//| about suspicious locations. |
|
|
//| === EXPLANATION ================================================ |
|
|
//| One major source of headache during optimization is the |
|
|
//| possibility of the coding errors in the target function / |
|
|
//| constraints (or their gradients). Such errors most often manifest|
|
|
//| themselves as discontinuity or nonsmoothness of the target / |
|
|
//| constraints. |
|
|
//| Another frequent situation is when you try to optimize something |
|
|
//| involving lots of min() and max() operations, i.e. nonsmooth |
|
|
//| target. Although not a coding error, it is nonsmoothness anyway -|
|
|
//| and smooth optimizers usually stop right after encountering |
|
|
//| nonsmoothness, well before reaching solution. |
|
|
//| OptGuard integrity checker helps you to catch such situations: it|
|
|
//| monitors function values/gradients being passed to the optimizer |
|
|
//| and tries to errors. Upon discovering suspicious pair of points |
|
|
//| it raises appropriate flag (and allows you to continue |
|
|
//| optimization). When optimization is done, you can study OptGuard |
|
|
//| result. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICOptGuardSmoothness(CMinBLEICState &State,
|
|
int level)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(level==0 || level==1,__FUNCTION__+": unexpected value of level parameter"))
|
|
return;
|
|
State.m_smoothnessguardlevel=level;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Results of OptGuard integrity check, should be called after |
|
|
//| optimization session is over. |
|
|
//| === PRIMARY REPORT ============================================= |
|
|
//| OptGuard performs several checks which are intended to catch |
|
|
//| common errors in the implementation of nonlinear function / |
|
|
//| gradient: |
|
|
//| * incorrect analytic gradient |
|
|
//| * discontinuous (non-C0) target functions (constraints) |
|
|
//| * nonsmooth (non-C1) target functions (constraints) |
|
|
//| Each of these checks is activated with appropriate function: |
|
|
//| * MinBLEICOptGuardGradient() for gradient verification |
|
|
//| * MinBLEICOptGuardSmoothness() for C0/C1 checks |
|
|
//| Following flags are set when these errors are suspected: |
|
|
//| * rep.badgradsuspected, and additionally: |
|
|
//| * rep.badgradvidx for specific variable (gradient element) |
|
|
//| suspected |
|
|
//| * rep.badgradxbase, a point where gradient is tested |
|
|
//| * rep.badgraduser, user-provided gradient (stored as 2D |
|
|
//| matrix with single row in order to make report structure |
|
|
//| compatible with more complex optimizers like MinNLC or |
|
|
//| MinLM) |
|
|
//| * rep.badgradnum, reference gradient obtained via numerical |
|
|
//| differentiation (stored as 2D matrix with single row in |
|
|
//| order to make report structure compatible with more |
|
|
//| complex optimizers like MinNLC or MinLM) |
|
|
//| * rep.nonc0suspected |
|
|
//| * rep.nonc1suspected |
|
|
//| === ADDITIONAL REPORTS/LOGS ==================================== |
|
|
//| Several different tests are performed to catch C0/C1 errors, you |
|
|
//| can find out specific test signaled error by looking to: |
|
|
//| * rep.nonc0test0positive, for non-C0 test #0 |
|
|
//| * rep.nonc1test0positive, for non-C1 test #0 |
|
|
//| * rep.nonc1test1positive, for non-C1 test #1 |
|
|
//| Additional information (including line search logs) can be |
|
|
//| obtained by means of: |
|
|
//| * MinBLEICOptGuardNonC1Test0Results() |
|
|
//| * MinBLEICOptGuardNonC1Test1Results() |
|
|
//| which return detailed error reports, specific points where |
|
|
//| discontinuities were found, and so on. |
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - generic OptGuard report; more detailed reports |
|
|
//| can be retrieved with other functions. |
|
|
//| NOTE: false negatives (nonsmooth problems are not identified as |
|
|
//| nonsmooth ones) are possible although unlikely. |
|
|
//| The reason is that you need to make several evaluations around |
|
|
//| nonsmoothness in order to accumulate enough information about |
|
|
//| function curvature. Say, if you start right from the nonsmooth |
|
|
//| point, optimizer simply won't get enough data to understand what |
|
|
//| is going wrong before it terminates due to abrupt changes in the |
|
|
//| derivative. It is also possible that "unlucky" step will move us |
|
|
//| to the termination too quickly. |
|
|
//| Our current approach is to have less than 0.1% false negatives in|
|
|
//| our test examples (measured with multiple restarts from random |
|
|
//| points), and to have exactly 0% false positives. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICOptGuardResults(CMinBLEICState &State,
|
|
COptGuardReport &rep)
|
|
{
|
|
COptServ::SmoothnessMonitorExportReport(State.m_smonitor,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Detailed results of the OptGuard integrity check for |
|
|
//| nonsmoothness test #0 |
|
|
//| Nonsmoothness (non-C1) test #0 studies function values (not |
|
|
//| gradient!) obtained during line searches and monitors behavior |
|
|
//| of the directional derivative estimate. |
|
|
//| This test is less powerful than test #1, but it does not depend |
|
|
//| on the gradient values and thus it is more robust against |
|
|
//| artifacts introduced by numerical differentiation. |
|
|
//| Two reports are returned: |
|
|
//| *a "strongest" one, corresponding to line search which had |
|
|
//| highest value of the nonsmoothness indicator |
|
|
//| *a "longest" one, corresponding to line search which had more |
|
|
//| function evaluations, and thus is more detailed |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; |
|
|
//| FALSE if test did not notice anything |
|
|
//| (in the latter cases fields below are empty). |
|
|
//| * x0[], d[] - arrays of length N which store initial point and |
|
|
//| direction for line search (d[] can be normalized,|
|
|
//| but does not have to) |
|
|
//| * stp[], f[]- arrays of length CNT which store step lengths and|
|
|
//| function values at these points; f[i] is |
|
|
//| evaluated in x0+stp[i]*d. |
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb |
|
|
//| (usually we have stpidxb=stpidxa+3, with most |
|
|
//| likely position of the violation between |
|
|
//| stpidxa+1 and stpidxa+2. |
|
|
//| ================================================================ |
|
|
//| = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it - |
|
|
//| = you will see where C1 continuity is violated.|
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| StrRep - C1 test #0 "strong" report |
|
|
//| LngRep - C1 test #0 "long" report |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICOptGuardNonC1Test0Results(CMinBLEICState &State,
|
|
COptGuardNonC1Test0Report &strrep,
|
|
COptGuardNonC1Test0Report &lngrep)
|
|
{
|
|
COptGuardApi::SmoothnessMonitorExportC1Test0Report(State.m_smonitor.m_nonc1test0strrep,State.m_lastscaleused,strrep);
|
|
COptGuardApi::SmoothnessMonitorExportC1Test0Report(State.m_smonitor.m_nonc1test0lngrep,State.m_lastscaleused,lngrep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Detailed results of the OptGuard integrity check for |
|
|
//| nonsmoothness test #1 |
|
|
//| Nonsmoothness (non-C1) test #1 studies individual components of |
|
|
//| the gradient computed during line search. |
|
|
//| When precise analytic gradient is provided this test is more |
|
|
//| powerful than test #0 which works with function values and |
|
|
//| ignores user-provided gradient. However, test #0 becomes more |
|
|
//| powerful when numerical differentiation is employed (in such |
|
|
//| cases test #1 detects higher levels of numerical noise and |
|
|
//| becomes too conservative). |
|
|
//| This test also tells specific components of the gradient which |
|
|
//| violate C1 continuity, which makes it more informative than #0, |
|
|
//| which just tells that continuity is violated. |
|
|
//| Two reports are returned: |
|
|
//| *a "strongest" one, corresponding to line search which had |
|
|
//| highest value of the nonsmoothness indicator |
|
|
//| *a "longest" one, corresponding to line search which had more |
|
|
//| function evaluations, and thus is more detailed |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; |
|
|
//| FALSE if test did not notice anything |
|
|
//| (in the latter cases fields below are empty).|
|
|
//| * vidx - is an index of the variable in [0,N) with |
|
|
//| nonsmooth derivative |
|
|
//| * x0[], d[] - arrays of length N which store initial point and|
|
|
//| direction for line search (d[] can be normalized|
|
|
//| but does not have to) |
|
|
//| * stp[], g[]- arrays of length CNT which store step lengths |
|
|
//| and gradient values at these points; g[i] is |
|
|
//| evaluated in x0+stp[i]*d and contains vidx-th |
|
|
//| component of the gradient. |
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb |
|
|
//| (usually we have stpidxb=stpidxa+3, with most |
|
|
//| likely position of the violation between |
|
|
//| stpidxa+1 and stpidxa+2. |
|
|
//| ================================================================ |
|
|
//| = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it - |
|
|
//| = you will see where C1 continuity is violated.|
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| StrRep - C1 test #1 "strong" report |
|
|
//| LngRep - C1 test #1 "long" report |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICOptGuardNonC1Test1Results(CMinBLEICState &State,
|
|
COptGuardNonC1Test1Report &strrep,
|
|
COptGuardNonC1Test1Report &lngrep)
|
|
{
|
|
COptGuardApi::SmoothnessMonitorExportC1Test1Report(State.m_smonitor.m_nonc1test1strrep,State.m_lastscaleused,strrep);
|
|
COptGuardApi::SmoothnessMonitorExportC1Test1Report(State.m_smonitor.m_nonc1test1lngrep,State.m_lastscaleused,lngrep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| BLEIC results |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[0..m_n-1], solution |
|
|
//| Rep - optimization report. You should check Rep. |
|
|
//| TerminationType in order to distinguish |
|
|
//| successful termination from unsuccessful one. |
|
|
//| More information about fields of this structure |
|
|
//| can be found in the comments on MinBLEICReport |
|
|
//| datatype. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICResults(CMinBLEICState &State,double &x[],
|
|
CMinBLEICReport &rep)
|
|
{
|
|
//--- reset memory
|
|
ArrayResize(x,0);
|
|
//--- function call
|
|
MinBLEICResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICResults(CMinBLEICState &State,CRowDouble &x,
|
|
CMinBLEICReport &rep)
|
|
{
|
|
//--- reset memory
|
|
x.Resize(0);
|
|
//--- function call
|
|
MinBLEICResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| BLEIC results |
|
|
//| Buffered implementation of MinBLEICResults() which uses |
|
|
//| pre-allocated buffer to store X[]. If buffer size is too small, |
|
|
//| it resizes buffer. It is intended to be used in the inner cycles |
|
|
//| of performance critical algorithms where array reallocation |
|
|
//| penalty is too large to be ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICResultsBuf(CMinBLEICState &State,double &x[],
|
|
CMinBLEICReport &rep)
|
|
{
|
|
CRowDouble X;
|
|
MinBLEICResultsBuf(State,X,rep);
|
|
X.ToArray(x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICResultsBuf(CMinBLEICState &State,CRowDouble &x,
|
|
CMinBLEICReport &rep)
|
|
{
|
|
//--- change values
|
|
rep.m_iterationscount=State.m_repinneriterationscount;
|
|
rep.m_inneriterationscount=State.m_repinneriterationscount;
|
|
rep.m_outeriterationscount=State.m_repouteriterationscount;
|
|
rep.m_nfev=State.m_repnfev;
|
|
rep.m_varidx=State.m_repvaridx;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
//--- check
|
|
if(State.m_repterminationtype>0)
|
|
x=State.m_sas.m_xc;
|
|
else
|
|
x=vector<double>::Full(State.m_nmain,AL_NaN);
|
|
//--- change values
|
|
rep.m_debugeqerr=State.m_repdebugeqerr;
|
|
rep.m_debugfs=State.m_repdebugfs;
|
|
rep.m_debugff=State.m_repdebugff;
|
|
rep.m_debugdx=State.m_repdebugdx;
|
|
rep.m_debugfeasqpits=State.m_repdebugfeasqpits;
|
|
rep.m_debugfeasgpaits=State.m_repdebugfeasgpaits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine restarts algorithm from new point. |
|
|
//| All optimization parameters (including constraints) are left |
|
|
//| unchanged. |
|
|
//| This function allows to solve multiple optimization problems |
|
|
//| (which must have same number of dimensions) without object |
|
|
//| reallocation penalty. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with |
|
|
//| MinBLEICCreate call. |
|
|
//| X - new starting point. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICRestartFrom(CMinBLEICState &State,double &x[])
|
|
{
|
|
CRowDouble X=x;
|
|
MinBLEICRestartFrom(State,X);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICRestartFrom(CMinBLEICState &State,CRowDouble &x)
|
|
{
|
|
int n=State.m_nmain;
|
|
//--- First,check for errors in the inputs
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- Set XC
|
|
State.m_xstart=x;
|
|
State.m_xstart.Resize(n);
|
|
//--- prepare RComm facilities
|
|
State.m_rstate.ia.Resize(7);
|
|
ArrayResize(State.m_rstate.ba,1);
|
|
State.m_rstate.ra.Resize(6);
|
|
State.m_rstate.stage=-1;
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
CSActiveSets::SASStopOptimization(State.m_sas);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine submits request for termination of running |
|
|
//| optimizer. It should be called from user-supplied callback when |
|
|
//| user decides that it is time to "smoothly" terminate optimization|
|
|
//| process. As result, optimizer stops at point which was "current |
|
|
//| accepted" when termination request was submitted and returns |
|
|
//| error code 8 (successful termination). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - optimizer structure |
|
|
//| NOTE: after request for termination optimizer may perform |
|
|
//| several additional calls to user-supplied callbacks. It |
|
|
//| does NOT guarantee to stop immediately - it just guarantees|
|
|
//| that these additional calls will be discarded later. |
|
|
//| NOTE: calling this function on optimizer which is NOT running |
|
|
//| will have no effect. |
|
|
//| NOTE: multiple calls to this function are possible. First call is|
|
|
//| counted, subsequent calls are silently ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICRequestTermination(CMinBLEICState &State)
|
|
{
|
|
State.m_userterminationneeded=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine finalizes internal structures after emergency |
|
|
//| termination from State.LSStart report (see comments on |
|
|
//| MinBLEICState for more information). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure after exit from LSStart report |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICEmergencyTermination(CMinBLEICState &State)
|
|
{
|
|
CSActiveSets::SASStopOptimization(State.m_sas);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Clears request fileds (to be sure that we don't forget to clear |
|
|
//| something) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::ClearRequestFields(CMinBLEICState &State)
|
|
{
|
|
//--- change values
|
|
State.m_needf=false;
|
|
State.m_needfg=false;
|
|
State.m_xupdated=false;
|
|
State.m_lsstart=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal initialization subroutine |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::MinBLEICInitInternal(const int n,CRowDouble &x,
|
|
const double diffstep,
|
|
CMinBLEICState &State)
|
|
{
|
|
//--- create matrix
|
|
CMatrixDouble c;
|
|
//--- create array
|
|
CRowInt ct;
|
|
//--- initialization
|
|
State.m_teststep=0;
|
|
State.m_smoothnessguardlevel=0;
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,0,0,false);
|
|
State.m_nmain=n;
|
|
State.m_diffstep=diffstep;
|
|
CSActiveSets::SASInit(n,State.m_sas);
|
|
//--- allocation
|
|
ArrayResize(State.m_HasBndL,n);
|
|
ArrayResize(State.m_HasBndU,n);
|
|
State.m_bndl=vector<double>::Full(n,AL_NEGINF);
|
|
State.m_bndu=vector<double>::Full(n,AL_POSINF);
|
|
State.m_xstart.Resize(n);
|
|
State.m_cgc.Resize(n);
|
|
State.m_ugc.Resize(n);
|
|
State.m_xn.Resize(n);
|
|
State.m_cgn.Resize(n);
|
|
State.m_ugn.Resize(n);
|
|
State.m_xp.Resize(n);
|
|
State.m_d.Resize(n);
|
|
State.m_s=vector<double>::Ones(n);
|
|
State.m_invs=vector<double>::Ones(n);
|
|
State.m_lastscaleused=vector<double>::Ones(n);
|
|
State.m_x.Resize(n);
|
|
State.m_g.Resize(n);
|
|
State.m_work.Resize(n);
|
|
ArrayInitialize(State.m_HasBndL,false);
|
|
ArrayInitialize(State.m_HasBndU,false);
|
|
//--- function call
|
|
MinBLEICSetLC(State,c,ct,0);
|
|
//--- function call
|
|
MinBLEICSetCond(State,0.0,0.0,0.0,0);
|
|
//--- function call
|
|
MinBLEICSetXRep(State,false);
|
|
//--- function call
|
|
MinBLEICSetDRep(State,false);
|
|
//--- function call
|
|
MinBLEICSetStpMax(State,0.0);
|
|
//--- function call
|
|
MinBLEICSetPrecDefault(State);
|
|
//--- function call
|
|
MinBLEICRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| NOTES: |
|
|
//| 1. This function has two different implementations: one which |
|
|
//| uses exact (analytical) user-supplied gradient, and one which |
|
|
//| uses function value only and numerically differentiates |
|
|
//| function in order to obtain gradient. |
|
|
//| Depending on the specific function used to create optimizer |
|
|
//| object (either MinBLEICCreate() for analytical gradient or |
|
|
//| MinBLEICCreateF() for numerical differentiation) you should |
|
|
//| choose appropriate variant of MinBLEICOptimize() - one which |
|
|
//| accepts function AND gradient or one which accepts function |
|
|
//| ONLY. |
|
|
//| Be careful to choose variant of MinBLEICOptimize() which |
|
|
//| corresponds to your optimization scheme! Table below lists |
|
|
//| different combinations of callback (function/gradient) passed |
|
|
//| to MinBLEICOptimize() and specific function used to create |
|
|
//| optimizer. |
|
|
//| | USER PASSED TO MinBLEICOptimize() |
|
|
//| CREATED WITH | function only | function and gradient |
|
|
//| ------------------------------------------------------------ |
|
|
//| MinBLEICCreateF() | work FAIL |
|
|
//| MinBLEICCreate() | FAIL work |
|
|
//| Here "FAIL" denotes inappropriate combinations of optimizer |
|
|
//| creation function and MinBLEICOptimize() version. Attemps to |
|
|
//| use such combination (for example, to create optimizer with |
|
|
//| MinBLEICCreateF() and to pass gradient information to |
|
|
//| MinCGOptimize()) will lead to exception being thrown. Either |
|
|
//| you did not pass gradient when it WAS needed or you passed |
|
|
//| gradient when it was NOT needed. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinBLEIC::MinBLEICIteration(CMinBLEICState &State)
|
|
{
|
|
//--- create variables
|
|
int n=0;
|
|
int m=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double v0=0;
|
|
bool b=false;
|
|
int mcinfo=0;
|
|
int actstatus=0;
|
|
int itidx=0;
|
|
double penalty=0;
|
|
double ginit=0;
|
|
double gdecay=0;
|
|
int i_=0;
|
|
int label=-1;
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
n=State.m_rstate.ia[0];
|
|
m=State.m_rstate.ia[1];
|
|
i=State.m_rstate.ia[2];
|
|
j=State.m_rstate.ia[3];
|
|
mcinfo=State.m_rstate.ia[4];
|
|
actstatus=State.m_rstate.ia[5];
|
|
itidx=State.m_rstate.ia[6];
|
|
b=State.m_rstate.ba[0];
|
|
v=State.m_rstate.ra[0];
|
|
vv=State.m_rstate.ra[1];
|
|
v0=State.m_rstate.ra[2];
|
|
penalty=State.m_rstate.ra[3];
|
|
ginit=State.m_rstate.ra[4];
|
|
gdecay=State.m_rstate.ra[5];
|
|
}
|
|
else
|
|
{
|
|
n=359;
|
|
m=-58;
|
|
i=-919;
|
|
j=-909;
|
|
mcinfo=81;
|
|
actstatus=255;
|
|
itidx=74;
|
|
b=false;
|
|
v=809;
|
|
vv=205;
|
|
v0=-838;
|
|
penalty=939;
|
|
ginit=-526;
|
|
gdecay=763;
|
|
}
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
case 4:
|
|
label=4;
|
|
break;
|
|
case 5:
|
|
label=5;
|
|
break;
|
|
case 6:
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
label=7;
|
|
break;
|
|
case 8:
|
|
label=8;
|
|
break;
|
|
case 9:
|
|
label=9;
|
|
break;
|
|
case 10:
|
|
label=10;
|
|
break;
|
|
case 11:
|
|
label=11;
|
|
break;
|
|
case 12:
|
|
label=12;
|
|
break;
|
|
case 13:
|
|
label=13;
|
|
break;
|
|
case 14:
|
|
label=14;
|
|
break;
|
|
case 15:
|
|
label=15;
|
|
break;
|
|
case 16:
|
|
label=16;
|
|
break;
|
|
case 17:
|
|
label=17;
|
|
break;
|
|
case 18:
|
|
label=18;
|
|
break;
|
|
case 19:
|
|
label=19;
|
|
break;
|
|
case 20:
|
|
label=20;
|
|
break;
|
|
case 21:
|
|
label=21;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
//--- Algorithm parameters:
|
|
//--- * M number of L-BFGS corrections.
|
|
//--- This coefficient remains fixed during iterations.
|
|
//--- * GDecay desired decrease of constrained gradient during L-BFGS iterations.
|
|
//--- This coefficient is decreased after each L-BFGS round until
|
|
//--- it reaches minimum decay.
|
|
m=MathMin(5,State.m_nmain);
|
|
gdecay=m_initialdecay;
|
|
//--- Init
|
|
n=State.m_nmain;
|
|
State.m_steepestdescentstep=false;
|
|
State.m_userterminationneeded=false;
|
|
State.m_repterminationtype=0;
|
|
State.m_repinneriterationscount=0;
|
|
State.m_repouteriterationscount=0;
|
|
State.m_repnfev=0;
|
|
State.m_repvaridx=-1;
|
|
State.m_repdebugeqerr=0.0;
|
|
State.m_repdebugfs=AL_NaN;
|
|
State.m_repdebugff=AL_NaN;
|
|
State.m_repdebugdx=AL_NaN;
|
|
if(State.m_stpmax!=0.0 && State.m_prectype!=0)
|
|
{
|
|
State.m_repterminationtype=-10;
|
|
return(false);
|
|
}
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_bufyk,m+1,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_bufsk,m+1,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_bufrho,m);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_buftheta,m);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_tmp0,n);
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,n,1,State.m_smoothnessguardlevel>0);
|
|
State.m_lastscaleused=State.m_s;
|
|
State.m_invs=vector<double>::Ones(n)/State.m_s.ToVector();
|
|
//--- Check analytic derivative
|
|
ClearRequestFields(State);
|
|
if(!(State.m_diffstep==0.0 && State.m_teststep>0.0))
|
|
{
|
|
label=22;
|
|
break;
|
|
}
|
|
label=24;
|
|
break;
|
|
}
|
|
//--- main loop
|
|
while(label>=0)
|
|
switch(label)
|
|
{
|
|
case 24:
|
|
if(!COptServ::SmoothnessMonitorCheckGradientATX0(State.m_smonitor,State.m_xstart,State.m_s,State.m_bndl,State.m_bndu,true,State.m_teststep))
|
|
{
|
|
label=25;
|
|
break;
|
|
}
|
|
State.m_x=State.m_smonitor.m_x;
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
State.m_needfg=false;
|
|
State.m_smonitor.m_fi.Set(0,State.m_f);
|
|
State.m_smonitor.m_j.Row(0,State.m_g);
|
|
label=24;
|
|
break;
|
|
case 25:
|
|
case 22:
|
|
//--- Fill TmpPrec with current preconditioner
|
|
switch(State.m_prectype)
|
|
{
|
|
case 2:
|
|
State.m_tmpprec=State.m_diagh;
|
|
break;
|
|
case 3:
|
|
State.m_tmpprec=State.m_s.Pow(-2.0)+0;
|
|
break;
|
|
default:
|
|
State.m_tmpprec=vector<double>::Ones(n);
|
|
}
|
|
CSActiveSets::SASSetPrecDiag(State.m_sas,State.m_tmpprec);
|
|
//--- Start optimization
|
|
if(!CSActiveSets::SASStartOptimization(State.m_sas,State.m_xstart))
|
|
{
|
|
State.m_repterminationtype=-3;
|
|
return(false);
|
|
}
|
|
//--- Main cycle of BLEIC-PG algorithm
|
|
State.m_repterminationtype=0;
|
|
State.m_lastgoodstep=0;
|
|
State.m_lastscaledgoodstep=0;
|
|
State.m_maxscaledgrad=0;
|
|
State.m_nonmonotoniccnt=(int)MathRound(1.5*(n+State.m_nic))+5;
|
|
State.m_x=State.m_sas.m_xc;
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=26;
|
|
break;
|
|
}
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_needfg=false;
|
|
label=27;
|
|
break;
|
|
case 26:
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_needf=false;
|
|
case 27:
|
|
State.m_fc=State.m_f;
|
|
COptServ::TrimPrepare(State.m_f,State.m_trimthreshold);
|
|
State.m_repnfev++;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=28;
|
|
break;
|
|
}
|
|
//--- Report current point
|
|
State.m_x=State.m_sas.m_xc;
|
|
State.m_f=State.m_fc;
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_xupdated=false;
|
|
case 28:
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
CSActiveSets::SASStopOptimization(State.m_sas);
|
|
State.m_repterminationtype=8;
|
|
return(false);
|
|
}
|
|
case 30:
|
|
//--- Preparations
|
|
//--- (a) calculate unconstrained gradient
|
|
//--- (b) determine initial active set
|
|
//--- (c) update MaxScaledGrad
|
|
//--- (d) check F/G for NAN/INF, abnormally terminate algorithm if needed
|
|
State.m_x=State.m_sas.m_xc;
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=32;
|
|
break;
|
|
}
|
|
//--- Analytic gradient
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=4;
|
|
label=-1;
|
|
break;
|
|
case 4:
|
|
State.m_needfg=false;
|
|
label=33;
|
|
break;
|
|
case 32:
|
|
//--- Numerical differentiation
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=5;
|
|
label=-1;
|
|
break;
|
|
case 5:
|
|
State.m_fbase=State.m_f;
|
|
i=0;
|
|
case 34:
|
|
if(i>n-1)
|
|
{
|
|
label=36;
|
|
break;
|
|
}
|
|
v=State.m_x[i];
|
|
b=false;
|
|
if(State.m_HasBndL[i])
|
|
b=b || (v-State.m_diffstep*State.m_s[i])<State.m_bndl[i];
|
|
if(State.m_HasBndU[i])
|
|
b=b || (v+State.m_diffstep*State.m_s[i])>State.m_bndu[i];
|
|
if(b)
|
|
{
|
|
label=37;
|
|
break;
|
|
}
|
|
State.m_x.Set(i,v-State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=6;
|
|
label=-1;
|
|
break;
|
|
case 6:
|
|
State.m_fm2=State.m_f;
|
|
State.m_x.Set(i,v-0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=7;
|
|
label=-1;
|
|
break;
|
|
case 7:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,v+0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=8;
|
|
label=-1;
|
|
break;
|
|
case 8:
|
|
State.m_fp1=State.m_f;
|
|
State.m_x.Set(i,v+State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=9;
|
|
label=-1;
|
|
break;
|
|
case 9:
|
|
State.m_fp2=State.m_f;
|
|
State.m_g.Set(i,(8*(State.m_fp1-State.m_fm1)-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[i]));
|
|
label=38;
|
|
break;
|
|
case 37:
|
|
State.m_xm1=v-State.m_diffstep*State.m_s[i];
|
|
State.m_xp1=v+State.m_diffstep*State.m_s[i];
|
|
if(State.m_HasBndL[i] && State.m_xm1<State.m_bndl[i])
|
|
State.m_xm1=State.m_bndl[i];
|
|
if(State.m_HasBndU[i] && State.m_xp1>State.m_bndu[i])
|
|
State.m_xp1=State.m_bndu[i];
|
|
State.m_x.Set(i,State.m_xm1);
|
|
State.m_rstate.stage=10;
|
|
label=-1;
|
|
break;
|
|
case 10:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,State.m_xp1);
|
|
State.m_rstate.stage=11;
|
|
label=-1;
|
|
break;
|
|
case 11:
|
|
State.m_fp1=State.m_f;
|
|
if(State.m_xm1!=State.m_xp1)
|
|
State.m_g.Set(i,(State.m_fp1-State.m_fm1)/(State.m_xp1-State.m_xm1));
|
|
else
|
|
State.m_g.Set(i,0);
|
|
case 38:
|
|
State.m_x.Set(i,v);
|
|
i++;
|
|
label=34;
|
|
break;
|
|
case 36:
|
|
State.m_f=State.m_fbase;
|
|
State.m_needf=false;
|
|
case 33:
|
|
State.m_fc=State.m_f;
|
|
State.m_ugc=State.m_g;
|
|
State.m_cgc=State.m_g;
|
|
CSActiveSets::SASReactivateConstraintsPrec(State.m_sas,State.m_ugc);
|
|
CSActiveSets::SASConstrainedDirection(State.m_sas,State.m_cgc);
|
|
ginit=MathPow(State.m_cgc*State.m_s+0,2).Sum();
|
|
ginit=MathSqrt(ginit);
|
|
State.m_maxscaledgrad=MathMax(State.m_maxscaledgrad,ginit);
|
|
if(!MathIsValidNumber(ginit) || !MathIsValidNumber(State.m_fc))
|
|
{
|
|
//--- Abnormal termination - infinities in function/gradient
|
|
CSActiveSets::SASStopOptimization(State.m_sas);
|
|
State.m_repterminationtype=-8;
|
|
return(false);
|
|
}
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
CSActiveSets::SASStopOptimization(State.m_sas);
|
|
State.m_repterminationtype=8;
|
|
return(false);
|
|
}
|
|
//--- LBFGS stage:
|
|
//--- * during LBFGS iterations we activate new constraints, but never
|
|
//--- deactivate already active ones.
|
|
//--- * we perform at most N iterations of LBFGS before re-evaluating
|
|
//--- active set and restarting LBFGS.
|
|
//--- * first iteration of LBFGS is a special - it is performed with
|
|
//--- minimum set of active constraints, algorithm termination can
|
|
//--- be performed only at this State.m_ We call this iteration
|
|
//--- "steepest descent step".
|
|
//--- About termination:
|
|
//--- * LBFGS iterations can be terminated because of two reasons:
|
|
//--- *"termination" - non-zero termination code in RepTerminationType,
|
|
//--- which means that optimization is done
|
|
//--- *"restart" - zero RepTerminationType, which means that we
|
|
//--- have to re-evaluate active set and resume LBFGS stage.
|
|
//---*one more option is "refresh" - to continue LBFGS iterations,
|
|
//--- but with all BFGS updates (Sk/Yk pairs) being dropped;
|
|
//--- it happens after changes in active set
|
|
State.m_bufsize=0;
|
|
State.m_steepestdescentstep=true;
|
|
itidx=-1;
|
|
case 39:
|
|
if(itidx>=n-1)
|
|
{
|
|
label=40;
|
|
break;
|
|
}
|
|
//--- Increment iterations counter
|
|
//--- NOTE: we have strong reasons to use such complex scheme
|
|
//--- instead of just for() loop - this counter may be
|
|
//--- decreased at some occasions to perform "restart"
|
|
//--- of an iteration.
|
|
itidx++;
|
|
//--- At the beginning of each iteration:
|
|
//--- * SAS.XC stores current point
|
|
//--- * FC stores current function value
|
|
//--- * UGC stores current unconstrained gradient
|
|
//--- * CGC stores current constrained gradient
|
|
//--- * D stores constrained step direction (calculated at this block)
|
|
//--- Check gradient-based stopping criteria
|
|
//--- This stopping condition is tested only for step which is the
|
|
//--- first step of LBFGS (subsequent steps may accumulate active
|
|
//--- constraints thus they should NOT be used for stopping - gradient
|
|
//--- may be small when constrained, but these constraints may be
|
|
//--- deactivated by the subsequent steps)
|
|
if(State.m_steepestdescentstep &&
|
|
CSActiveSets::SASScaledConstrainedNorm(State.m_sas,State.m_ugc)<=State.m_epsg)
|
|
{
|
|
//--- Gradient is small enough.
|
|
//--- Optimization is terminated
|
|
State.m_repterminationtype=4;
|
|
label=40;
|
|
break;
|
|
}
|
|
//--- 1. Calculate search direction D according to L-BFGS algorithm
|
|
//--- using constrained preconditioner to perform inner multiplication.
|
|
//--- 2. Evaluate scaled length of direction D; restart LBFGS if D is zero
|
|
//--- (it may be possible that we found minimum, but it is also possible
|
|
//--- that some constraints need deactivation)
|
|
//--- 3. If D is non-zero, try to use previous scaled step length as initial estimate for new step.
|
|
State.m_work=State.m_cgc;
|
|
for(i=State.m_bufsize-1; i>=0; i--)
|
|
{
|
|
v=CAblasF::RDotVR(n,State.m_work,State.m_bufsk,i);
|
|
State.m_buftheta.Set(i,v);
|
|
vv=v*State.m_bufrho[i];
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_work.Add(i_,- State.m_bufyk.Get(i,i_)*vv);
|
|
}
|
|
CSActiveSets::SASConstrainedDirectionPrec(State.m_sas,State.m_work);
|
|
for(i=0; i<State.m_bufsize; i++)
|
|
{
|
|
v=CAblasF::RDotVR(n,State.m_work,State.m_bufyk,i);
|
|
vv=State.m_bufrho[i]*(-v+State.m_buftheta[i]);
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_work.Add(i_,State.m_bufsk.Get(i,i_)*vv);
|
|
}
|
|
CSActiveSets::SASConstrainedDirection(State.m_sas,State.m_work);
|
|
State.m_d=State.m_work.ToVector()*(-1.0);
|
|
v=MathPow(State.m_d/State.m_s+0,2.0).Sum();
|
|
v=MathSqrt(v);
|
|
if(v==0.0)
|
|
{
|
|
//--- Search direction is zero.
|
|
//--- If we perform "steepest descent step", algorithm is terminated.
|
|
//--- Otherwise we just restart LBFGS.
|
|
if(State.m_steepestdescentstep)
|
|
State.m_repterminationtype=4;
|
|
label=40;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(v>0.0,__FUNCTION__+": internal error"))
|
|
return(false);
|
|
if(State.m_lastscaledgoodstep>0.0 && v>0.0)
|
|
State.m_stp=State.m_lastscaledgoodstep/v;
|
|
else
|
|
State.m_stp=1.0/v;
|
|
//--- Calculate bound on step length.
|
|
//--- Step direction is stored
|
|
CSActiveSets::SASExploreDirection(State.m_sas,State.m_d,State.m_curstpmax,State.m_cidx,State.m_cval);
|
|
State.m_activationstep=State.m_curstpmax;
|
|
if(State.m_cidx>=0 && State.m_activationstep==0.0)
|
|
{
|
|
//--- We are exactly at the boundary, immediate activation
|
|
//--- of constraint is required. LBFGS stage is continued
|
|
//--- with "refreshed" model.
|
|
//--- ! IMPORTANT: we do not clear SteepestDescent flag here,
|
|
//--- ! it is very important for correct stopping
|
|
//--- ! of algorithm.
|
|
//--- ! IMPORTANT: we decrease iteration counter in order to
|
|
//--- preserve computational budget for iterations.
|
|
CSActiveSets::SASImmediateActivation(State.m_sas,State.m_cidx,State.m_cval);
|
|
State.m_bufsize=0;
|
|
itidx=itidx-1;
|
|
label=39;
|
|
break;
|
|
}
|
|
if(State.m_stpmax>0.0)
|
|
{
|
|
v=CAblasF::RDotV2(n,State.m_d);
|
|
v=MathSqrt(v);
|
|
if(v>0.0)
|
|
State.m_curstpmax=MathMin(State.m_curstpmax,State.m_stpmax/v);
|
|
}
|
|
//--- Report beginning of line search (if requested by caller).
|
|
//--- See description of the MinBLEICState for more information
|
|
//--- about fields accessible to caller.
|
|
//--- Caller may do following:
|
|
//--- * change State.Stp and load better initial estimate of
|
|
//--- the step length.
|
|
//--- Caller may not terminate algorithm.
|
|
if(!State.m_drep)
|
|
{
|
|
label=41;
|
|
break;
|
|
}
|
|
ClearRequestFields(State);
|
|
State.m_lsstart=true;
|
|
State.m_boundedstep=State.m_cidx>=0;
|
|
State.m_x=State.m_sas.m_xc;
|
|
State.m_rstate.stage=12;
|
|
label=-1;
|
|
break;
|
|
case 12:
|
|
State.m_lsstart=false;
|
|
case 41:
|
|
//--- Minimize F(x+alpha*d)
|
|
State.m_xn=State.m_sas.m_xc;
|
|
State.m_cgn=State.m_cgc;
|
|
State.m_ugn=State.m_ugc;
|
|
State.m_fn=State.m_fc;
|
|
State.m_mcstage=0;
|
|
COptServ::SmoothnessMonitorStartLineSearch1u(State.m_smonitor,State.m_s,State.m_invs,State.m_xn,State.m_fn,State.m_ugn);
|
|
CLinMin::MCSrch(n,State.m_xn,State.m_fn,State.m_ugn,State.m_d,State.m_stp,State.m_curstpmax,m_gtol,mcinfo,State.m_nfev,State.m_work,State.m_lstate,State.m_mcstage);
|
|
case 43:
|
|
if(State.m_mcstage==0)
|
|
{
|
|
label=44;
|
|
break;
|
|
}
|
|
//--- Perform correction (constraints are enforced)
|
|
//--- Copy XN to X
|
|
CSActiveSets::SASCorrection(State.m_sas,State.m_xn,penalty);
|
|
State.m_x=State.m_xn;
|
|
//--- Gradient, either user-provided or numerical differentiation
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=45;
|
|
break;
|
|
}
|
|
//--- Analytic gradient
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=13;
|
|
label=-1;
|
|
break;
|
|
case 13:
|
|
State.m_needfg=false;
|
|
State.m_repnfev++;
|
|
label=46;
|
|
break;
|
|
case 45:
|
|
//--- Numerical differentiation
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=14;
|
|
label=-1;
|
|
break;
|
|
case 14:
|
|
State.m_fbase=State.m_f;
|
|
i=0;
|
|
case 47:
|
|
if(i>n-1)
|
|
{
|
|
label=49;
|
|
break;
|
|
}
|
|
v=State.m_x[i];
|
|
b=false;
|
|
if(State.m_HasBndL[i])
|
|
b=b || (v-State.m_diffstep*State.m_s[i])<State.m_bndl[i];
|
|
if(State.m_HasBndU[i])
|
|
b=b || (v+State.m_diffstep*State.m_s[i])>State.m_bndu[i];
|
|
if(b)
|
|
{
|
|
label=50;
|
|
break;
|
|
}
|
|
State.m_x.Set(i,v-State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=15;
|
|
label=-1;
|
|
break;
|
|
case 15:
|
|
State.m_fm2=State.m_f;
|
|
State.m_x.Set(i,v-0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=16;
|
|
label=-1;
|
|
break;
|
|
case 16:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,v+0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=17;
|
|
label=-1;
|
|
break;
|
|
case 17:
|
|
State.m_fp1=State.m_f;
|
|
State.m_x.Set(i,v+State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=18;
|
|
label=-1;
|
|
break;
|
|
case 18:
|
|
State.m_fp2=State.m_f;
|
|
State.m_g.Set(i,(8*(State.m_fp1-State.m_fm1)-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[i]));
|
|
State.m_repnfev+=4;
|
|
label=51;
|
|
break;
|
|
case 50:
|
|
State.m_xm1=v-State.m_diffstep*State.m_s[i];
|
|
State.m_xp1=v+State.m_diffstep*State.m_s[i];
|
|
if(State.m_HasBndL[i] && State.m_xm1<State.m_bndl[i])
|
|
State.m_xm1=State.m_bndl[i];
|
|
if(State.m_HasBndU[i] && State.m_xp1>State.m_bndu[i])
|
|
State.m_xp1=State.m_bndu[i];
|
|
State.m_x.Set(i,State.m_xm1);
|
|
State.m_rstate.stage=19;
|
|
label=-1;
|
|
break;
|
|
case 19:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,State.m_xp1);
|
|
State.m_rstate.stage=20;
|
|
label=-1;
|
|
break;
|
|
case 20:
|
|
State.m_fp1=State.m_f;
|
|
if(State.m_xm1!=State.m_xp1)
|
|
State.m_g.Set(i,(State.m_fp1-State.m_fm1)/(State.m_xp1-State.m_xm1));
|
|
else
|
|
State.m_g.Set(i,0);
|
|
State.m_repnfev+=2;
|
|
case 51:
|
|
State.m_x.Set(i,v);
|
|
i++;
|
|
label=47;
|
|
break;
|
|
case 49:
|
|
State.m_f=State.m_fbase;
|
|
State.m_needf=false;
|
|
case 46:
|
|
//--- Back to MCSRCH
|
|
//--- NOTE: penalty term from correction is added to FN in order
|
|
//--- to penalize increase in infeasibility.
|
|
COptServ::SmoothnessMonitorEnqueuePoint1u(State.m_smonitor,State.m_s,State.m_invs,State.m_d,State.m_stp,State.m_x,State.m_f,State.m_g);
|
|
State.m_fn=State.m_f+m_penaltyfactor*State.m_maxscaledgrad*penalty;
|
|
State.m_cgn=State.m_g;
|
|
State.m_ugn=State.m_g;
|
|
CSActiveSets::SASConstrainedDirection(State.m_sas,State.m_cgn);
|
|
COptServ::TrimFunction(State.m_fn,State.m_cgn,n,State.m_trimthreshold);
|
|
CLinMin::MCSrch(n,State.m_xn,State.m_fn,State.m_ugn,State.m_d,State.m_stp,State.m_curstpmax,m_gtol,mcinfo,State.m_nfev,State.m_work,State.m_lstate,State.m_mcstage);
|
|
label=43;
|
|
break;
|
|
case 44:
|
|
State.m_bufsk.Row(State.m_bufsize,State.m_xn-State.m_sas.m_xc+0);
|
|
State.m_bufyk.Row(State.m_bufsize,State.m_cgn-State.m_cgc+0);
|
|
COptServ::SmoothnessMonitorFinalizeLineSearch(State.m_smonitor);
|
|
//--- Check for presence of NAN/INF in function/gradient
|
|
v=State.m_fn;
|
|
for(i=0; i<n; i++)
|
|
v=0.1*v+State.m_ugn[i];
|
|
if(!MathIsValidNumber(v))
|
|
{
|
|
//--- Abnormal termination - infinities in function/gradient
|
|
State.m_repterminationtype=-8;
|
|
label=40;
|
|
break;
|
|
}
|
|
//--- Handle possible failure of the line search or request for termination
|
|
if(mcinfo!=1 && mcinfo!=5)
|
|
{
|
|
//--- We can not find step which decreases function value. We have
|
|
//--- two possibilities:
|
|
//--- (a) numerical properties of the function do not allow us to
|
|
//--- find good step.
|
|
//--- (b) we are close to activation of some constraint, and it is
|
|
//--- so close that step which activates it leads to change in
|
|
//--- target function which is smaller than numerical noise.
|
|
//--- Optimization algorithm must be able to handle case (b), because
|
|
//--- inability to handle it will cause failure when algorithm
|
|
//--- started very close to boundary of the feasible area.
|
|
//--- In order to correctly handle such cases we allow limited amount
|
|
//--- of small steps which increase function value.
|
|
v=MathPow(State.m_d/State.m_s*State.m_curstpmax,2.0).Sum();
|
|
v=MathSqrt(v);
|
|
b=false;
|
|
if(State.m_cidx>=0 && v<=m_maxnonmonotoniclen && State.m_nonmonotoniccnt>0)
|
|
{
|
|
//--- We try to enforce non-monotonic step:
|
|
//--- * Stp := CurStpMax
|
|
//--- * MCINFO := 5
|
|
//--- * XN := XC+CurStpMax*D
|
|
//--- * non-monotonic counter is decreased
|
|
//--- NOTE: UGN/CGN are not updated because step is so short that we assume that
|
|
//--- GN is approximately equal to GC.
|
|
//--- NOTE: prior to enforcing such step we check that it does not increase infeasibility
|
|
//--- of constraints beyond tolerable level
|
|
v=State.m_curstpmax;
|
|
State.m_tmp0=State.m_sas.m_xc.ToVector()+State.m_d*v;
|
|
State.m_stp=State.m_curstpmax;
|
|
mcinfo=5;
|
|
State.m_xn=State.m_tmp0;
|
|
State.m_nonmonotoniccnt--;
|
|
b=true;
|
|
}
|
|
if(!b)
|
|
{
|
|
//--- Numerical properties of the function do not allow
|
|
//--- us to solve problem. Here we have two possibilities:
|
|
//---*if it is "steepest descent" step, we can terminate
|
|
//--- algorithm because we are close to minimum
|
|
//---*if it is NOT "steepest descent" step, we should restart
|
|
//--- LBFGS iterations.
|
|
if(State.m_steepestdescentstep)
|
|
{
|
|
//--- Algorithm is terminated
|
|
State.m_repterminationtype=7;
|
|
label=40;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
//--- Re-evaluate active set and restart LBFGS
|
|
label=40;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
label=40;
|
|
break;
|
|
}
|
|
//--- Current point is updated:
|
|
//--- * move XC/FC/GC to XP/FP/GP
|
|
//--- * change current point remembered by SAS structure
|
|
//--- * move XN/FN/GN to XC/FC/GC
|
|
//--- * report current point and update iterations counter
|
|
//--- * if MCINFO=1, push new pair SK/YK to LBFGS buffer
|
|
State.m_fp=State.m_fc;
|
|
State.m_xp=State.m_sas.m_xc;
|
|
State.m_fc=State.m_fn;
|
|
State.m_cgc=State.m_cgn;
|
|
State.m_ugc=State.m_ugn;
|
|
actstatus=CSActiveSets::SASMoveTo(State.m_sas,State.m_xn,State.m_cidx>=0 && State.m_stp>=State.m_activationstep,State.m_cidx,State.m_cval);
|
|
if(!State.m_xrep)
|
|
{
|
|
label=52;
|
|
break;
|
|
}
|
|
State.m_x=State.m_sas.m_xc;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=21;
|
|
label=-1;
|
|
break;
|
|
case 21:
|
|
State.m_xupdated=false;
|
|
case 52:
|
|
State.m_repinneriterationscount++;
|
|
if(mcinfo==1)
|
|
{
|
|
//--- Accept new LBFGS update given by Sk,Yk
|
|
if(State.m_bufsize==m)
|
|
{
|
|
//--- Buffer is full, shift contents by one row
|
|
for(i=0; i<State.m_bufsize; i++)
|
|
{
|
|
State.m_bufsk.Row(i,State.m_bufsk,i+1);
|
|
State.m_bufyk.Row(i,State.m_bufyk,i+1);
|
|
}
|
|
for(i=0; i<State.m_bufsize-1; i++)
|
|
{
|
|
State.m_bufrho.Set(i,State.m_bufrho[i+1]);
|
|
State.m_buftheta.Set(i,State.m_buftheta[i+1]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Buffer is not full, increase buffer size by 1
|
|
State.m_bufsize++;
|
|
}
|
|
v=CAblasF::RDotRR(n,State.m_bufyk,State.m_bufsize-1,State.m_bufsk,State.m_bufsize-1);
|
|
vv=CAblasF::RDotRR(n,State.m_bufyk,State.m_bufsize-1,State.m_bufyk,State.m_bufsize-1);
|
|
if(v==0.0 || vv==0.0)
|
|
{
|
|
//--- Strange internal error in LBFGS - either YK=0
|
|
//--- (which should not have been) or (SK,YK)=0 (again,
|
|
//--- unexpected). It should not take place because
|
|
//--- MCINFO=1, which signals "good" step. But just
|
|
//--- to be sure we have special branch of code which
|
|
//--- restarts LBFGS
|
|
label=40;
|
|
break;
|
|
}
|
|
State.m_bufrho.Set(State.m_bufsize-1,1/v);
|
|
//--- check
|
|
if(!CAp::Assert(State.m_bufsize<=m,__FUNCTION__+": internal error"))
|
|
return(false);
|
|
//--- Update length of the good step
|
|
vector<double> temp=State.m_sas.m_xc-State.m_xp;
|
|
v=MathPow(temp/State.m_s.ToVector(),2.0).Sum();
|
|
vv=temp.Dot(temp);
|
|
State.m_lastgoodstep=MathSqrt(vv);
|
|
UpdateEstimateOfGoodStep(State.m_lastscaledgoodstep,MathSqrt(v));
|
|
}
|
|
//--- Check stopping criteria
|
|
//--- Step size and function-based stopping criteria are tested only
|
|
//--- for step which satisfies Wolfe conditions and is the first step of
|
|
//--- LBFGS (subsequent steps may accumulate active constraints thus
|
|
//--- they should NOT be used for stopping; step size or function change
|
|
//--- may be small when constrained, but these constraints may be
|
|
//--- deactivated by the subsequent steps).
|
|
//--- MaxIts-based stopping condition is checked for all kinds of steps.
|
|
if(mcinfo==1 && State.m_steepestdescentstep)
|
|
{
|
|
//--- Step is small enough
|
|
v=MathPow((State.m_sas.m_xc-State.m_xp)/State.m_s.ToVector(),2.0).Sum();
|
|
v=MathSqrt(v);
|
|
if(v<=State.m_epsx)
|
|
{
|
|
State.m_repterminationtype=2;
|
|
label=40;
|
|
break;
|
|
}
|
|
//--- Function change is small enough
|
|
if(MathAbs(State.m_fp-State.m_fc)<=(State.m_epsf*MathMax(MathAbs(State.m_fc),MathMax(MathAbs(State.m_fp),1.0))))
|
|
{
|
|
State.m_repterminationtype=1;
|
|
label=40;
|
|
break;
|
|
}
|
|
}
|
|
if(State.m_maxits>0 && State.m_repinneriterationscount>=State.m_maxits)
|
|
{
|
|
State.m_repterminationtype=5;
|
|
label=40;
|
|
break;
|
|
}
|
|
//--- Clear "steepest descent" flag.
|
|
State.m_steepestdescentstep=false;
|
|
//--- Smooth reset (LBFGS memory model is refreshed) or hard restart:
|
|
//--- * LBFGS model is refreshed, if line search was performed with activation of constraints
|
|
//--- * algorithm is restarted if scaled gradient decreased below GDecay
|
|
if(actstatus>=0)
|
|
{
|
|
State.m_bufsize=0;
|
|
label=39;
|
|
break;
|
|
}
|
|
v=MathPow(State.m_cgc*State.m_s+0,2.0).Sum();
|
|
if(MathSqrt(v)<(gdecay*ginit))
|
|
{
|
|
label=40;
|
|
break;
|
|
}
|
|
label=39;
|
|
break;
|
|
case 40:
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
State.m_repterminationtype=8;
|
|
label=31;
|
|
break;
|
|
}
|
|
if(State.m_repterminationtype!=0)
|
|
{
|
|
//--- Algorithm terminated
|
|
label=31;
|
|
break;
|
|
}
|
|
//--- Decrease decay coefficient. Subsequent L-BFGS stages will
|
|
//--- have more stringent stopping criteria.
|
|
gdecay=MathMax(gdecay*m_decaycorrection,m_mindecay);
|
|
label=30;
|
|
break;
|
|
case 31:
|
|
CSActiveSets::SASStopOptimization(State.m_sas);
|
|
State.m_repouteriterationscount=1;
|
|
return(false);
|
|
}
|
|
//-- Saving State
|
|
State.m_rstate.ba[0]=b;
|
|
State.m_rstate.ia.Set(0,n);
|
|
State.m_rstate.ia.Set(1,m);
|
|
State.m_rstate.ia.Set(2,i);
|
|
State.m_rstate.ia.Set(3,j);
|
|
State.m_rstate.ia.Set(4,mcinfo);
|
|
State.m_rstate.ia.Set(5,actstatus);
|
|
State.m_rstate.ia.Set(6,itidx);
|
|
State.m_rstate.ra.Set(0,v);
|
|
State.m_rstate.ra.Set(1,vv);
|
|
State.m_rstate.ra.Set(2,v0);
|
|
State.m_rstate.ra.Set(3,penalty);
|
|
State.m_rstate.ra.Set(4,ginit);
|
|
State.m_rstate.ra.Set(5,gdecay);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine updates estimate of the good step length given: |
|
|
//| 1) previous estimate |
|
|
//| 2) new length of the good step |
|
|
//| It makes sure that estimate does not change too rapidly - ratio |
|
|
//| of new and old estimates will be at least 0.01, at most 100.0 |
|
|
//| In case previous estimate of good step is zero (no estimate), |
|
|
//| new estimate is used unconditionally. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBLEIC::UpdateEstimateOfGoodStep(double &estimate,
|
|
double newstep)
|
|
{
|
|
//--- check
|
|
if(estimate==0.0)
|
|
{
|
|
estimate=newstep;
|
|
return;
|
|
}
|
|
if(newstep<(estimate*0.01))
|
|
{
|
|
estimate=estimate*0.01;
|
|
return;
|
|
}
|
|
if(newstep>(estimate*100))
|
|
{
|
|
estimate=estimate*100;
|
|
return;
|
|
}
|
|
estimate=newstep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary class for CMinLBFGS |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLBFGSState
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_k;
|
|
int m_m;
|
|
int m_maxits;
|
|
int m_mcstage;
|
|
int m_n;
|
|
int m_nfev;
|
|
int m_p;
|
|
int m_preck;
|
|
int m_prectype;
|
|
int m_q;
|
|
int m_repiterationscount;
|
|
int m_repnfev;
|
|
int m_repterminationtype;
|
|
int m_smoothnessguardlevel;
|
|
double m_diffstep;
|
|
double m_epsf;
|
|
double m_epsg;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_fbase;
|
|
double m_fm1;
|
|
double m_fm2;
|
|
double m_fold;
|
|
double m_fp1;
|
|
double m_fp2;
|
|
double m_gammak;
|
|
double m_stp;
|
|
double m_stpmax;
|
|
double m_teststep;
|
|
double m_trimthreshold;
|
|
bool m_needf;
|
|
bool m_needfg;
|
|
bool m_userterminationneeded;
|
|
bool m_xrep;
|
|
bool m_xupdated;
|
|
RCommState m_rstate;
|
|
CSmoothnessMonitor m_smonitor;
|
|
CRowDouble m_autobuf;
|
|
CRowDouble m_d;
|
|
CRowDouble m_diagh;
|
|
CRowDouble m_g;
|
|
CRowDouble m_invs;
|
|
CRowDouble m_lastscaleused;
|
|
CRowDouble m_precc;
|
|
CRowDouble m_precd;
|
|
CRowDouble m_rho;
|
|
CRowDouble m_s;
|
|
CRowDouble m_theta;
|
|
CRowDouble m_work;
|
|
CRowDouble m_x;
|
|
CRowDouble m_xbase;
|
|
CRowDouble m_xp;
|
|
CPrecBufLowRank m_lowrankbuf;
|
|
CPrecBufLBFGS m_precbuf;
|
|
CMatrixDouble m_denseh;
|
|
CMatrixDouble m_precw;
|
|
CMatrixDouble m_sk;
|
|
CMatrixDouble m_yk;
|
|
CLinMinState m_lstate;
|
|
//--- constructor, destructor
|
|
CMinLBFGSState(void);
|
|
~CMinLBFGSState(void) {}
|
|
//--- copy
|
|
void Copy(CMinLBFGSState &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinLBFGSState::CMinLBFGSState(void)
|
|
{
|
|
m_k=0;
|
|
m_m=0;
|
|
m_maxits=0;
|
|
m_mcstage=0;
|
|
m_n=0;
|
|
m_nfev=0;
|
|
m_p=0;
|
|
m_preck=0;
|
|
m_prectype=0;
|
|
m_q=0;
|
|
m_repiterationscount=0;
|
|
m_repnfev=0;
|
|
m_repterminationtype=0;
|
|
m_smoothnessguardlevel=0;
|
|
m_diffstep=0;
|
|
m_epsf=0;
|
|
m_epsg=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_fbase=0;
|
|
m_fm1=0;
|
|
m_fm2=0;
|
|
m_fold=0;
|
|
m_fp1=0;
|
|
m_fp2=0;
|
|
m_gammak=0;
|
|
m_stp=0;
|
|
m_stpmax=0;
|
|
m_teststep=0;
|
|
m_trimthreshold=0;
|
|
m_needf=false;
|
|
m_needfg=false;
|
|
m_userterminationneeded=false;
|
|
m_xrep=false;
|
|
m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGSState::Copy(CMinLBFGSState &obj)
|
|
{
|
|
//--- copy variables
|
|
m_k=obj.m_k;
|
|
m_m=obj.m_m;
|
|
m_maxits=obj.m_maxits;
|
|
m_mcstage=obj.m_mcstage;
|
|
m_n=obj.m_n;
|
|
m_nfev=obj.m_nfev;
|
|
m_p=obj.m_p;
|
|
m_preck=obj.m_preck;
|
|
m_prectype=obj.m_prectype;
|
|
m_q=obj.m_q;
|
|
m_repiterationscount=obj.m_repiterationscount;
|
|
m_repnfev=obj.m_repnfev;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_smoothnessguardlevel=obj.m_smoothnessguardlevel;
|
|
m_diffstep=obj.m_diffstep;
|
|
m_epsf=obj.m_epsf;
|
|
m_epsg=obj.m_epsg;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_fbase=obj.m_fbase;
|
|
m_fm1=obj.m_fm1;
|
|
m_fm2=obj.m_fm2;
|
|
m_fold=obj.m_fold;
|
|
m_fp1=obj.m_fp1;
|
|
m_fp2=obj.m_fp2;
|
|
m_gammak=obj.m_gammak;
|
|
m_stp=obj.m_stp;
|
|
m_stpmax=obj.m_stpmax;
|
|
m_teststep=obj.m_teststep;
|
|
m_trimthreshold=obj.m_trimthreshold;
|
|
m_needf=obj.m_needf;
|
|
m_needfg=obj.m_needfg;
|
|
m_userterminationneeded=obj.m_userterminationneeded;
|
|
m_xrep=obj.m_xrep;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_rstate=obj.m_rstate;
|
|
m_smonitor=obj.m_smonitor;
|
|
m_autobuf=obj.m_autobuf;
|
|
m_d=obj.m_d;
|
|
m_diagh=obj.m_diagh;
|
|
m_g=obj.m_g;
|
|
m_invs=obj.m_invs;
|
|
m_lastscaleused=obj.m_lastscaleused;
|
|
m_precc=obj.m_precc;
|
|
m_precd=obj.m_precd;
|
|
m_rho=obj.m_rho;
|
|
m_s=obj.m_s;
|
|
m_theta=obj.m_theta;
|
|
m_work=obj.m_work;
|
|
m_x=obj.m_x;
|
|
m_xbase=obj.m_xbase;
|
|
m_xp=obj.m_xp;
|
|
m_lowrankbuf=obj.m_lowrankbuf;
|
|
m_precbuf=obj.m_precbuf;
|
|
m_denseh=obj.m_denseh;
|
|
m_precw=obj.m_precw;
|
|
m_sk=obj.m_sk;
|
|
m_yk=obj.m_yk;
|
|
m_lstate=obj.m_lstate;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This class is a shell for class CMinLBFGSState |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLBFGSStateShell
|
|
{
|
|
private:
|
|
CMinLBFGSState m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinLBFGSStateShell(void) {}
|
|
CMinLBFGSStateShell(CMinLBFGSState &obj) { m_innerobj.Copy(obj); }
|
|
~CMinLBFGSStateShell(void) {}
|
|
//--- methods
|
|
bool GetNeedF(void);
|
|
void SetNeedF(const bool b);
|
|
bool GetNeedFG(void);
|
|
void SetNeedFG(const bool b);
|
|
bool GetXUpdated(void);
|
|
void SetXUpdated(const bool b);
|
|
double GetF(void);
|
|
void SetF(const double d);
|
|
CMinLBFGSState *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needf |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLBFGSStateShell::GetNeedF(void)
|
|
{
|
|
return(m_innerobj.m_needf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needf |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGSStateShell::SetNeedF(const bool b)
|
|
{
|
|
m_innerobj.m_needf=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLBFGSStateShell::GetNeedFG(void)
|
|
{
|
|
return(m_innerobj.m_needfg);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGSStateShell::SetNeedFG(const bool b)
|
|
{
|
|
m_innerobj.m_needfg=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLBFGSStateShell::GetXUpdated(void)
|
|
{
|
|
return(m_innerobj.m_xupdated);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGSStateShell::SetXUpdated(const bool b)
|
|
{
|
|
m_innerobj.m_xupdated=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
double CMinLBFGSStateShell::GetF(void)
|
|
{
|
|
return(m_innerobj.m_f);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGSStateShell::SetF(const double d)
|
|
{
|
|
m_innerobj.m_f=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinLBFGSState *CMinLBFGSStateShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary class for CMinLFBFGS |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLBFGSReport
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_iterationscount;
|
|
int m_nfev;
|
|
int m_terminationtype;
|
|
//--- constructor, destructor
|
|
CMinLBFGSReport(void) { ZeroMemory(this); }
|
|
~CMinLBFGSReport(void) {}
|
|
//--- copy
|
|
void Copy(CMinLBFGSReport &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGSReport::Copy(CMinLBFGSReport &obj)
|
|
{
|
|
//--- copy variables
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_nfev=obj.m_nfev;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This class is a shell for class CMinLBFGSReport |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLBFGSReportShell
|
|
{
|
|
private:
|
|
CMinLBFGSReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinLBFGSReportShell(void) {}
|
|
CMinLBFGSReportShell(CMinLBFGSReport &obj) { m_innerobj.Copy(obj); }
|
|
~CMinLBFGSReportShell(void) {}
|
|
//--- methods
|
|
int GetIterationsCount(void);
|
|
void SetIterationsCount(const int i);
|
|
int GetNFev(void);
|
|
void SetNFev(const int i);
|
|
int GetTerminationType(void);
|
|
void SetTerminationType(const int i);
|
|
CMinLBFGSReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable iterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLBFGSReportShell::GetIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_iterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable iterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGSReportShell::SetIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_iterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable nfev |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLBFGSReportShell::GetNFev(void)
|
|
{
|
|
return(m_innerobj.m_nfev);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable nfev |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGSReportShell::SetNFev(const int i)
|
|
{
|
|
m_innerobj.m_nfev=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLBFGSReportShell::GetTerminationType(void)
|
|
{
|
|
return(m_innerobj.m_terminationtype);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGSReportShell::SetTerminationType(const int i)
|
|
{
|
|
m_innerobj.m_terminationtype=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinLBFGSReport *CMinLBFGSReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Limited memory BFGS method for large scale optimization |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLBFGS
|
|
{
|
|
public:
|
|
//--- constant
|
|
static const double m_gtol;
|
|
//--- public methods
|
|
static void MinLBFGSCreate(const int n,const int m,double &x[],CMinLBFGSState &State);
|
|
static void MinLBFGSCreate(const int n,const int m,CRowDouble &x,CMinLBFGSState &State);
|
|
static void MinLBFGSCreateF(const int n,const int m,double &x[],const double diffstep,CMinLBFGSState &State);
|
|
static void MinLBFGSSetCond(CMinLBFGSState &State,const double epsg,const double epsf,double epsx,const int m_maxits);
|
|
static void MinLBFGSSetXRep(CMinLBFGSState &State,const bool needxrep);
|
|
static void MinLBFGSSetStpMax(CMinLBFGSState &State,const double stpmax);
|
|
static void MinLBFGSSetScale(CMinLBFGSState &State,double &s[]);
|
|
static void MinLBFGSCreateX(const int n,const int m,double &x[],int flags,const double diffstep,CMinLBFGSState &State);
|
|
static void MinLBFGSCreateX(const int n,const int m,CRowDouble &x,int flags,const double diffstep,CMinLBFGSState &State);
|
|
static void MinLBFGSSetPrecDefault(CMinLBFGSState &State);
|
|
static void MinLBFGSSetPrecCholesky(CMinLBFGSState &State,CMatrixDouble &p,const bool IsUpper);
|
|
static void MinLBFGSSetPrecDiag(CMinLBFGSState &State,double &d[]);
|
|
static void MinLBFGSSetPrecScale(CMinLBFGSState &State);
|
|
static void MinLBFGSSetPrecRankKLBFGSFast(CMinLBFGSState &State,CRowDouble &d,CRowDouble &c,CMatrixDouble &w,int cnt);
|
|
static void MinLBFGSSetPrecLowRankExact(CMinLBFGSState &State,CRowDouble &d,CRowDouble &c,CMatrixDouble &w,int cnt);
|
|
static void MinLBFGSResults(CMinLBFGSState &State,double &x[],CMinLBFGSReport &rep);
|
|
static void MinLBFGSResults(CMinLBFGSState &State,CRowDouble &x,CMinLBFGSReport &rep);
|
|
static void MinLBFGSResultsBuf(CMinLBFGSState &State,double &x[],CMinLBFGSReport &rep);
|
|
static void MinLBFGSResultsBuf(CMinLBFGSState &State,CRowDouble &x,CMinLBFGSReport &rep);
|
|
static void MinLBFGSRestartFrom(CMinLBFGSState &State,double &x[]);
|
|
static void MinLBFGSRestartFrom(CMinLBFGSState &State,CRowDouble &x);
|
|
static void MinLBFGSRequestTermination(CMinLBFGSState &State);
|
|
|
|
static bool MinLBFGSIteration(CMinLBFGSState &State);
|
|
|
|
private:
|
|
static void ClearRequestFields(CMinLBFGSState &State);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CMinLBFGS::m_gtol=0.4;
|
|
//+------------------------------------------------------------------+
|
|
//| LIMITED MEMORY BFGS METHOD FOR LARGE SCALE OPTIMIZATION |
|
|
//| DESCRIPTION: |
|
|
//| The subroutine minimizes function F(x) of N arguments by using a |
|
|
//| quasi - Newton method (LBFGS scheme) which is optimized to use a |
|
|
//| minimum amount of memory. |
|
|
//| The subroutine generates the approximation of an inverse Hessian |
|
|
//| matrix by using information about the last M steps of the |
|
|
//| algorithm (instead of N). It lessens a required amount of memory |
|
|
//| from a value of order N^2 to a value of order 2*N*M. |
|
|
//| REQUIREMENTS: |
|
|
//| Algorithm will request following information during its |
|
|
//| operation: |
|
|
//| * function value F and its gradient G (simultaneously) at given |
|
|
//| point X |
|
|
//| USAGE: |
|
|
//| 1. User initializes algorithm State with MinLBFGSCreate() call |
|
|
//| 2. User tunes m_solver parameters with MinLBFGSSetCond() |
|
|
//| MinLBFGSSetStpMax() and other functions |
|
|
//| 3. User calls MinLBFGSOptimize() function which takes algorithm |
|
|
//| State and pointer (delegate, etc.) to callback function which |
|
|
//| calculates F/G. |
|
|
//| 4. User calls MinLBFGSResults() to get solution |
|
|
//| 5. Optionally user may call MinLBFGSRestartFrom() to solve |
|
|
//| another problem with same N/M but another starting point |
|
|
//| and/or another function. MinLBFGSRestartFrom() allows to reuse|
|
|
//| already initialized structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension. N>0 |
|
|
//| M - number of corrections in the BFGS scheme of |
|
|
//| Hessian approximation update. Recommended value: |
|
|
//| 3<=M<=7. The smaller value causes worse |
|
|
//| convergence, the bigger will not cause a |
|
|
//| considerably better convergence, but will cause |
|
|
//| a fall in the performance. M<=N. |
|
|
//| X - initial solution approximation, array[0..m_n-1]. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NOTES: |
|
|
//| 1. you may tune stopping conditions with MinLBFGSSetCond() |
|
|
//| function |
|
|
//| 2. if target function contains exp() or other fast growing |
|
|
//| functions, and optimization algorithm makes too large steps |
|
|
//| which leads to overflow, use MinLBFGSSetStpMax() function to |
|
|
//| bound algorithm's steps. However, L-BFGS rarely needs such a |
|
|
//| tuning. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSCreate(const int n,const int m,double &x[],
|
|
CMinLBFGSState &State)
|
|
{
|
|
CRowDouble X=x;
|
|
MinLBFGSCreate(n,m,X,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSCreate(const int n,const int m,CRowDouble &x,
|
|
CMinLBFGSState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m<=n,__FUNCTION__+": M>N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- function call
|
|
MinLBFGSCreateX(n,m,x,0,0.0,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| The subroutine is finite difference variant of MinLBFGSCreate(). |
|
|
//| It uses finite differences in order to differentiate target |
|
|
//| function. |
|
|
//| Description below contains information which is specific to this |
|
|
//| function only. We recommend to read comments on MinLBFGSCreate() |
|
|
//| in order to get more information about creation of LBFGS |
|
|
//| optimizer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N>0: |
|
|
//| * if given, only leading N elements of X are used|
|
|
//| * if not given, automatically determined from |
|
|
//| size of X |
|
|
//| M - number of corrections in the BFGS scheme of |
|
|
//| Hessian approximation update. Recommended value: |
|
|
//| 3<=M<=7. The smaller value causes worse |
|
|
//| convergence, the bigger will not cause a |
|
|
//| considerably better convergence, but will cause a|
|
|
//| fall in the performance. M<=N. |
|
|
//| X - starting point, array[0..m_n-1]. |
|
|
//| DiffStep- differentiation step, >0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NOTES: |
|
|
//| 1. algorithm uses 4-point central formula for differentiation. |
|
|
//| 2. differentiation step along I-th axis is equal to DiffStep*S[I]|
|
|
//| where S[] is scaling vector which can be set by |
|
|
//| MinLBFGSSetScale() call. |
|
|
//| 3. we recommend you to use moderate values of differentiation |
|
|
//| step. Too large step will result in too large truncation |
|
|
//| errors, while too small step will result in too large |
|
|
//| numerical errors. 1.0E-6 can be good value to start with. |
|
|
//| 4. Numerical differentiation is very inefficient - one gradient |
|
|
//| calculation needs 4*N function evaluations. This function will|
|
|
//| work for any N - either small (1...10), moderate (10...100) or|
|
|
//| large (100...). However, performance penalty will be too |
|
|
//| severe for any N's except for small ones. |
|
|
//| We should also say that code which relies on numerical |
|
|
//| differentiation is less robust and precise. LBFGS needs exact |
|
|
//| gradient values. Imprecise gradient may slow down convergence,|
|
|
//| especially on highly nonlinear problems. |
|
|
//| Thus we recommend to use this function for fast prototyping on|
|
|
//| small- dimensional problems only, and to implement analytical |
|
|
//| gradient as soon as possible. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSCreateF(const int n,const int m,double &x[],
|
|
const double diffstep,CMinLBFGSState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N too small!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m<=n,__FUNCTION__+": M>N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(diffstep),__FUNCTION__+": DiffStep is infinite or NaN!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep is non-positive!"))
|
|
return;
|
|
//--- function call
|
|
MinLBFGSCreateX(n,m,x,0,diffstep,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping conditions for L-BFGS optimization |
|
|
//| algorithm. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsG - >=0 |
|
|
//| The subroutine finishes its work if the condition|
|
|
//| |v|<EpsG is satisfied, where: |
|
|
//| * |.| means Euclidian norm |
|
|
//| * v - scaled gradient vector, v[i]=g[i]*s[i] |
|
|
//| * g - gradient |
|
|
//| * s - scaling coefficients set by |
|
|
//| MinLBFGSSetScale() |
|
|
//| EpsF - >=0 |
|
|
//| The subroutine finishes its work if on k+1-th |
|
|
//| iteration the condition |F(k+1)-F(k)| <= |
|
|
//| <= EpsF*max{|F(k)|,|F(k+1)|,1} is satisfied. |
|
|
//| EpsX - >=0 |
|
|
//| The subroutine finishes its work if on k+1-th |
|
|
//| iteration the condition |v|<=EpsX is fulfilled, |
|
|
//| where: |
|
|
//| * |.| means Euclidian norm |
|
|
//| * v - scaled step vector, v[i]=dx[i]/s[i] |
|
|
//| * dx - ste pvector, dx=X(k+1)-X(k) |
|
|
//| * s - scaling coefficients set by |
|
|
//| MinLBFGSSetScale() |
|
|
//| MaxIts - maximum number of iterations. If MaxIts=0, the |
|
|
//| number of iterations is unlimited. |
|
|
//| Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will|
|
|
//| lead to automatic stopping criterion selection (small EpsX). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSSetCond(CMinLBFGSState &State,const double epsg,
|
|
const double epsf,double epsx,
|
|
const int m_maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsg),__FUNCTION__+": EpsG is not finite number!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsg>=0.0,__FUNCTION__+": negative EpsG!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsf),__FUNCTION__+": EpsF is not finite number!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsf>=0.0,__FUNCTION__+": negative EpsF!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsx),__FUNCTION__+": EpsX is not finite number!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m_maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
//--- check
|
|
if(((epsg==0.0 && epsf==0.0) && epsx==0.0) && m_maxits==0)
|
|
epsx=1.0E-6;
|
|
//--- change values
|
|
State.m_epsg=epsg;
|
|
State.m_epsf=epsf;
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=m_maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function turns on/off reporting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NeedXRep- whether iteration reports are needed or not |
|
|
//| If NeedXRep is True, algorithm will call rep() callback function |
|
|
//| if it is provided to MinLBFGSOptimize(). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSSetXRep(CMinLBFGSState &State,const bool needxrep)
|
|
{
|
|
State.m_xrep=needxrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets maximum step length |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| StpMax - maximum step length, >=0. Set StpMax to 0.0 |
|
|
//| (default), if you don't want to limit step |
|
|
//| length. |
|
|
//| Use this subroutine when you optimize target function which |
|
|
//| contains exp() or other fast growing functions, and optimization |
|
|
//| algorithm makes too large steps which leads to overflow. This |
|
|
//| function allows us to reject steps that are too large (and |
|
|
//| therefore expose us to the possible overflow) without actually |
|
|
//| calculating function value at the x+stp*d. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSSetStpMax(CMinLBFGSState &State,
|
|
const double stpmax)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(stpmax),__FUNCTION__+": StpMax is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(stpmax>=0.0,__FUNCTION__+": StpMax<0!"))
|
|
return;
|
|
//--- change value
|
|
State.m_stpmax=stpmax;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients for LBFGS optimizer. |
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions (step size and gradient are scaled before comparison |
|
|
//| with tolerances). Scale of the I-th variable is a translation |
|
|
//| invariant measure of: |
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| Scaling is also used by finite difference variant of the |
|
|
//| optimizer - step along I-th axis is equal to DiffStep*S[I]. |
|
|
//| In most optimizers (and in the LBFGS too) scaling is NOT a form |
|
|
//| of preconditioning. It just affects stopping conditions. You |
|
|
//| should set preconditioner by separate call to one of the |
|
|
//| MinLBFGSSetPrec...() functions. |
|
|
//| There is special preconditioning mode, however, which uses |
|
|
//| scaling coefficients to form diagonal preconditioning matrix. |
|
|
//| You can turn this mode on, if you want. But you should |
|
|
//| understand that scaling is not the same thing as |
|
|
//| preconditioning - these are two different, although related |
|
|
//| forms of tuning m_solver. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients |
|
|
//| S[i] may be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSSetScale(CMinLBFGSState &State,double &s[])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(s)>=State.m_n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
State.m_s.Set(i,MathAbs(s[i]));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Extended subroutine for internal use only. |
|
|
//| Accepts additional parameters: |
|
|
//| Flags - additional Settings: |
|
|
//| * Flags = 0 means no additional Settings |
|
|
//| *Flags=1 "do not allocate memory". used when |
|
|
//| solving a many subsequent tasks with |
|
|
//| same N/M values. First call MUST be |
|
|
//| without this flag bit set, subsequent|
|
|
//| calls of MinLBFGS with same |
|
|
//| MinLBFGSState structure can set Flags|
|
|
//| to 1. |
|
|
//| DiffStep - numerical differentiation step |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSCreateX(const int n,const int m,double &x[],
|
|
int flags,const double diffstep,
|
|
CMinLBFGSState &State)
|
|
{
|
|
CRowDouble X=x;
|
|
MinLBFGSCreateX(n,m,X,flags,diffstep,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSCreateX(const int n,const int m,CRowDouble &x,
|
|
int flags,const double diffstep,
|
|
CMinLBFGSState &State)
|
|
{
|
|
//--- create variable
|
|
bool allocatemem;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N too small!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M too small!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m<=n,__FUNCTION__+": M too large!"))
|
|
return;
|
|
//--- Initialize
|
|
State.m_diffstep=diffstep;
|
|
State.m_n=n;
|
|
State.m_m=m;
|
|
allocatemem=flags%2==0;
|
|
flags=flags/2;
|
|
//--- check
|
|
if(allocatemem)
|
|
{
|
|
//--- allocation
|
|
State.m_rho.Resize(m);
|
|
State.m_theta.Resize(m);
|
|
State.m_yk.Resize(m,n);
|
|
State.m_sk.Resize(m,n);
|
|
State.m_d.Resize(n);
|
|
State.m_xp.Resize(n);
|
|
State.m_x.Resize(n);
|
|
State.m_xbase.Resize(n);
|
|
State.m_g.Resize(n);
|
|
State.m_work.Resize(n);
|
|
}
|
|
//--- function call
|
|
MinLBFGSSetCond(State,0,0,0,0);
|
|
//--- function call
|
|
MinLBFGSSetXRep(State,false);
|
|
//--- function call
|
|
MinLBFGSSetStpMax(State,0);
|
|
//--- function call
|
|
MinLBFGSRestartFrom(State,x);
|
|
//--- change values
|
|
State.m_s=vector<double>::Ones(n);
|
|
State.m_invs=vector<double>::Ones(n);
|
|
State.m_lastscaleused=vector<double>::Ones(n);
|
|
State.m_prectype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: default preconditioner |
|
|
//| (simple scaling, same for all elements of X) is used. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NOTE: you can change preconditioner "on the fly", during |
|
|
//| algorithm iterations. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSSetPrecDefault(CMinLBFGSState &State)
|
|
{
|
|
State.m_prectype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: Cholesky factorization of |
|
|
//| approximate Hessian is used. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| P - triangular preconditioner, Cholesky factorization|
|
|
//| of the approximate Hessian. array[0..m_n-1,0..m_n-1],|
|
|
//| (if larger, only leading N elements are used). |
|
|
//| IsUpper - whether upper or lower triangle of P is given |
|
|
//| (other triangle is not referenced) |
|
|
//| After call to this function preconditioner is changed to P (P is |
|
|
//| copied into the internal buffer). |
|
|
//| NOTE: you can change preconditioner "on the fly", during |
|
|
//| algorithm iterations. |
|
|
//| NOTE 2: P should be nonsingular. Exception will be thrown |
|
|
//| otherwise. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSSetPrecCholesky(CMinLBFGSState &State,
|
|
CMatrixDouble &p,
|
|
const bool IsUpper)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteRTrMatrix(p,State.m_n,IsUpper),__FUNCTION__+": P contains infinite or NAN values!"))
|
|
return;
|
|
//--- initialization
|
|
double mx=0;
|
|
for(int i=0; i<State.m_n; i++)
|
|
mx=MathMax(mx,MathAbs(p[i][i]));
|
|
//--- check
|
|
if(!CAp::Assert(mx>0.0,__FUNCTION__+": P is strictly singular!"))
|
|
return;
|
|
//--- check
|
|
if((int)CAp::Rows(State.m_denseh)<State.m_n || (int)CAp::Cols(State.m_denseh)<State.m_n)
|
|
State.m_denseh.Resize(State.m_n,State.m_n);
|
|
//--- initialization
|
|
State.m_prectype=1;
|
|
//--- check
|
|
if(IsUpper)
|
|
CAblas::RMatrixCopy(State.m_n,State.m_n,p,0,0,State.m_denseh,0,0);
|
|
else
|
|
CAblas::RMatrixTranspose(State.m_n,State.m_n,p,0,0,State.m_denseh,0,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: diagonal of approximate |
|
|
//| Hessian is used. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| D - diagonal of the approximate Hessian, |
|
|
//| array[0..m_n-1], (if larger, only leading N |
|
|
//| elements are used). |
|
|
//| NOTE: you can change preconditioner "on the fly", during |
|
|
//| algorithm iterations. |
|
|
//| NOTE 2: D[i] should be positive. Exception will be thrown |
|
|
//| otherwise. |
|
|
//| NOTE 3: you should pass diagonal of approximate Hessian - NOT |
|
|
//| ITS INVERSE. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSSetPrecDiag(CMinLBFGSState &State,double &d[])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(d)>=State.m_n,__FUNCTION__+": D is too short"))
|
|
return;
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(d[i]),__FUNCTION__+": D contains infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(d[i]>0.0,__FUNCTION__+": D contains non-positive elements"))
|
|
return;
|
|
}
|
|
//--- change values
|
|
State.m_prectype=2;
|
|
State.m_diagh=d;
|
|
State.m_diagh.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: scale-based diagonal |
|
|
//| preconditioning. |
|
|
//| This preconditioning mode can be useful when you don't have |
|
|
//| approximate diagonal of Hessian, but you know that your variables|
|
|
//| are badly scaled (for example, one variable is in [1,10], and |
|
|
//| another in [1000,100000]), and most part of the ill-conditioning |
|
|
//| comes from different scales of vars. |
|
|
//| In this case simple scale-based preconditioner, with H.Set(i, |
|
|
//| = 1/(s[i]^2), can greatly improve convergence. |
|
|
//| IMPRTANT: you should set scale of your variables with |
|
|
//| MinLBFGSSetScale() call (before or after MinLBFGSSetPrecScale() |
|
|
//| call). Without knowledge of the scale of your variables |
|
|
//| scale-based preconditioner will be just unit matrix. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSSetPrecScale(CMinLBFGSState &State)
|
|
{
|
|
State.m_prectype=3;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets low-rank preconditioner for Hessian matrix |
|
|
//| H=D+W'*C*W, |
|
|
//| where: |
|
|
//| * H is a Hessian matrix, which is approximated by D/W/C |
|
|
//| * D is a NxN diagonal positive definite matrix |
|
|
//| * W is a KxN low-rank correction |
|
|
//| * C is a KxK positive definite diagonal factor of low-rank |
|
|
//| correction |
|
|
//| This preconditioner is inexact but fast - it requires O(N*K) time|
|
|
//| to be applied. Preconditioner P is calculated by artificially |
|
|
//| constructing a set of BFGS updates which tries to reproduce |
|
|
//| behavior of H: |
|
|
//| * Sk = Wk (k-th row of W) |
|
|
//| * Yk = (D+Wk'*Ck*Wk)*Sk |
|
|
//| * Yk/Sk are reordered by ascending of C[k]*norm(Wk)^2 |
|
|
//| Here we assume that rows of Wk are orthogonal or nearly |
|
|
//| orthogonal, which allows us to have O(N*K+K^2) update instead |
|
|
//| of O(N*K^2) one. Reordering of updates is essential for having |
|
|
//| good performance on non-orthogonal problems (updates which do not|
|
|
//| add much of curvature are added first, and updates which add very|
|
|
//| large eigenvalues are added last and override effect of the first|
|
|
//| updates). |
|
|
//| In practice, this preconditioner is perfect when ortogonal |
|
|
//| correction is applied; on non-orthogonal problems sometimes it |
|
|
//| allows to achieve 5x speedup (when compared to non-preconditioned|
|
|
//| solver). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSSetPrecRankKLBFGSFast(CMinLBFGSState &State,
|
|
CRowDouble &d,CRowDouble &c,CMatrixDouble &w,int cnt)
|
|
{
|
|
int n=State.m_n;
|
|
State.m_prectype=4;
|
|
State.m_preck=cnt;
|
|
//--- copy
|
|
State.m_precd=d;
|
|
State.m_precc=c;
|
|
State.m_precw=w;
|
|
State.m_precc.Resize(cnt);
|
|
State.m_precd.Resize(n);
|
|
State.m_precw.Resize(cnt,n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets exact low-rank preconditioner for Hessian |
|
|
//| matrix H=D+W'*C*W, where: |
|
|
//| * H is a Hessian matrix, which is approximated by D/W/C |
|
|
//| * D is a NxN diagonal positive definite matrix |
|
|
//| * W is a KxN low-rank correction |
|
|
//| * C is a KxK semidefinite diagonal factor of low-rank |
|
|
//| correction |
|
|
//| This preconditioner is exact but slow - it requires O(N*K^2) time|
|
|
//| to be built and O(N*K) time to be applied. Woodbury matrix |
|
|
//| identity is used to build inverse matrix. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSSetPrecLowRankExact(CMinLBFGSState &State,
|
|
CRowDouble &d,
|
|
CRowDouble &c,
|
|
CMatrixDouble &w,
|
|
int cnt)
|
|
{
|
|
State.m_prectype=5;
|
|
COptServ::PrepareLowRankPreconditioner(d,c,w,State.m_n,cnt,State.m_lowrankbuf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| L-BFGS algorithm results |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[0..m_n-1], solution |
|
|
//| Rep - optimization report: |
|
|
//| * Rep.TerminationType completetion code: |
|
|
//| * -2 rounding errors prevent further |
|
|
//| improvement. X contains best point |
|
|
//| found. |
|
|
//| * -1 incorrect parameters were specified |
|
|
//| * 1 relative function improvement is no |
|
|
//| more than EpsF. |
|
|
//| * 2 relative step is no more than EpsX. |
|
|
//| * 4 gradient norm is no more than EpsG |
|
|
//| * 5 MaxIts steps was taken |
|
|
//| * 7 stopping conditions are too |
|
|
//| stringent, further improvement is |
|
|
//| impossible |
|
|
//| * Rep.IterationsCount contains iterations count |
|
|
//| * NFEV countains number of function calculations |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSResults(CMinLBFGSState &State,double &x[],
|
|
CMinLBFGSReport &rep)
|
|
{
|
|
//--- reset memory
|
|
ArrayResize(x,0);
|
|
//--- function call
|
|
MinLBFGSResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSResults(CMinLBFGSState &State,CRowDouble &x,
|
|
CMinLBFGSReport &rep)
|
|
{
|
|
//--- reset memory
|
|
x.Resize(0);
|
|
//--- function call
|
|
MinLBFGSResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| L-BFGS algorithm results |
|
|
//| Buffered implementation of MinLBFGSResults which uses |
|
|
//| pre-allocated buffer to store X[]. If buffer size is too small, |
|
|
//| it resizes buffer. It is intended to be used in the inner cycles |
|
|
//| of performance critical algorithms where array reallocation |
|
|
//| penalty is too large to be ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSResultsBuf(CMinLBFGSState &State,double &x[],
|
|
CMinLBFGSReport &rep)
|
|
{
|
|
CRowDouble X;
|
|
MinLBFGSResultsBuf(State,X,rep);
|
|
X.ToArray(x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSResultsBuf(CMinLBFGSState &State,CRowDouble &x,
|
|
CMinLBFGSReport &rep)
|
|
{
|
|
//--- copy
|
|
x=State.m_x;
|
|
//--- change values
|
|
rep.m_iterationscount=State.m_repiterationscount;
|
|
rep.m_nfev=State.m_repnfev;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine restarts LBFGS algorithm from new point. All |
|
|
//| optimization parameters are left unchanged. |
|
|
//| This function allows to solve multiple optimization problems |
|
|
//| (which must have same number of dimensions) without object |
|
|
//| reallocation penalty. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure used to store algorithm State |
|
|
//| X - new starting point. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSRestartFrom(CMinLBFGSState &State,double &x[])
|
|
{
|
|
CRowDouble X=x;
|
|
MinLBFGSRestartFrom(State,X);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSRestartFrom(CMinLBFGSState &State,CRowDouble &x)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=State.m_n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,State.m_n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- copy
|
|
State.m_xbase=x;
|
|
//--- allocation
|
|
State.m_rstate.ia.Resize(6);
|
|
State.m_rstate.ra.Resize(2);
|
|
State.m_rstate.stage=-1;
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine submits request for termination of running |
|
|
//| optimizer. It should be called from user-supplied callback when |
|
|
//| user decides that it is time to "smoothly" terminate optimization|
|
|
//| process. As result, optimizer stops at point which was "current |
|
|
//| accepted" when termination request was submitted and returns |
|
|
//| error code 8 (successful termination). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - optimizer structure |
|
|
//| NOTE: after request for termination optimizer may perform several|
|
|
//| additional calls to user-supplied callbacks. It does NOT |
|
|
//| guarantee to stop immediately - it just guarantees that |
|
|
//| these additional calls will be discarded later. |
|
|
//| NOTE: calling this function on optimizer which is NOT running |
|
|
//| will have no effect. |
|
|
//| NOTE: multiple calls to this function are possible. First call is|
|
|
//| counted, subsequent calls are silently ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::MinLBFGSRequestTermination(CMinLBFGSState &State)
|
|
{
|
|
State.m_userterminationneeded=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Clears request fileds (to be sure that we don't forgot to clear |
|
|
//| something) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLBFGS::ClearRequestFields(CMinLBFGSState &State)
|
|
{
|
|
//--- change values
|
|
State.m_needf=false;
|
|
State.m_needfg=false;
|
|
State.m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| NOTES: |
|
|
//| 1. This function has two different implementations: one which |
|
|
//| uses exact (analytical) user-supplied gradient, and one which |
|
|
//| uses function value only and numerically differentiates |
|
|
//| function in order to obtain gradient. |
|
|
//| Depending on the specific function used to create optimizer |
|
|
//| object (either MinLBFGSCreate() for analytical gradient or |
|
|
//| MinLBFGSCreateF() for numerical differentiation) you should |
|
|
//| choose appropriate variant of MinLBFGSOptimize() - one which |
|
|
//| accepts function AND gradient or one which accepts function |
|
|
//| ONLY. |
|
|
//| Be careful to choose variant of MinLBFGSOptimize() which |
|
|
//| corresponds to your optimization scheme! Table below lists |
|
|
//| different combinations of callback (function/gradient) passed |
|
|
//| to MinLBFGSOptimize() and specific function used to create |
|
|
//| optimizer. |
|
|
//| | USER PASSED TO MinLBFGSOptimize() |
|
|
//| CREATED WITH | function only | function and gradient |
|
|
//| ------------------------------------------------------------ |
|
|
//| MinLBFGSCreateF() | work FAIL |
|
|
//| MinLBFGSCreate() | FAIL work |
|
|
//| Here "FAIL" denotes inappropriate combinations of optimizer |
|
|
//| creation function and MinLBFGSOptimize() version. Attemps to |
|
|
//| use such combination (for example, to create optimizer with |
|
|
//| MinLBFGSCreateF() and to pass gradient information to |
|
|
//| MinCGOptimize()) will lead to exception being thrown. Either |
|
|
//| you did not pass gradient when it WAS needed or you passed |
|
|
//| gradient when it was NOT needed. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLBFGS::MinLBFGSIteration(CMinLBFGSState &State)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=0;
|
|
int m=0;
|
|
int i=0;
|
|
int j=0;
|
|
int ic=0;
|
|
int mcinfo=0;
|
|
double v=0;
|
|
double vv=0;
|
|
int i_=0;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
n=State.m_rstate.ia[0];
|
|
m=State.m_rstate.ia[1];
|
|
i=State.m_rstate.ia[2];
|
|
j=State.m_rstate.ia[3];
|
|
ic=State.m_rstate.ia[4];
|
|
mcinfo=State.m_rstate.ia[5];
|
|
v=State.m_rstate.ra[0];
|
|
vv=State.m_rstate.ra[1];
|
|
}
|
|
else
|
|
{
|
|
n=359;
|
|
m=-58;
|
|
i=-919;
|
|
j=-909;
|
|
ic=81;
|
|
mcinfo=255;
|
|
v=74;
|
|
vv=-788;
|
|
}
|
|
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
case 4:
|
|
label=4;
|
|
break;
|
|
case 5:
|
|
label=5;
|
|
break;
|
|
case 6:
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
label=7;
|
|
break;
|
|
case 8:
|
|
label=8;
|
|
break;
|
|
case 9:
|
|
label=9;
|
|
break;
|
|
case 10:
|
|
label=10;
|
|
break;
|
|
case 11:
|
|
label=11;
|
|
break;
|
|
case 12:
|
|
label=12;
|
|
break;
|
|
case 13:
|
|
label=13;
|
|
break;
|
|
case 14:
|
|
label=14;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
//--- Unload frequently used variables from State structure
|
|
//--- (just for typing convinience)
|
|
n=State.m_n;
|
|
m=State.m_m;
|
|
//--- Init
|
|
State.m_userterminationneeded=false;
|
|
State.m_repterminationtype=0;
|
|
State.m_repiterationscount=0;
|
|
State.m_repnfev=0;
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,n,1,State.m_smoothnessguardlevel>0);
|
|
State.m_invs.Resize(n);
|
|
State.m_lastscaleused=State.m_s;
|
|
State.m_invs=State.m_s.Pow(-1)+0;
|
|
//--- Check, that transferred derivative value is right
|
|
State.m_stp=0;
|
|
ClearRequestFields(State);
|
|
if(!(State.m_diffstep==0.0 && State.m_teststep>0.0))
|
|
{
|
|
label=15;
|
|
break;
|
|
}
|
|
label=17;
|
|
break;
|
|
}
|
|
//--- main loop
|
|
while(label>=0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 17:
|
|
if(!COptServ::SmoothnessMonitorCheckGradientATX0(State.m_smonitor,State.m_xbase,State.m_s,State.m_s,State.m_s,false,State.m_teststep))
|
|
{
|
|
label=18;
|
|
break;
|
|
}
|
|
State.m_x=State.m_smonitor.m_x;
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
State.m_needfg=false;
|
|
State.m_smonitor.m_fi.Set(0,State.m_f);
|
|
State.m_smonitor.m_j.Row(0,State.m_g);
|
|
label=17;
|
|
break;
|
|
case 18:
|
|
case 15:
|
|
//--- Calculate F/G at the initial point
|
|
State.m_x=State.m_xbase;
|
|
State.m_stp=0;
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=19;
|
|
break;
|
|
}
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_needfg=false;
|
|
label=20;
|
|
break;
|
|
case 19:
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_fbase=State.m_f;
|
|
i=0;
|
|
case 21:
|
|
if(i>n-1)
|
|
{
|
|
label=23;
|
|
break;
|
|
}
|
|
v=State.m_x[i];
|
|
State.m_x.Set(i,v-State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_fm2=State.m_f;
|
|
State.m_x.Set(i,v-0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=4;
|
|
label=-1;
|
|
break;
|
|
case 4:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,v+0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=5;
|
|
label=-1;
|
|
break;
|
|
case 5:
|
|
State.m_fp1=State.m_f;
|
|
State.m_x.Set(i,v+State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=6;
|
|
label=-1;
|
|
break;
|
|
case 6:
|
|
State.m_fp2=State.m_f;
|
|
State.m_x.Set(i,v);
|
|
State.m_g.Set(i,(8*(State.m_fp1-State.m_fm1)-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[i]));
|
|
i++;
|
|
label=21;
|
|
break;
|
|
case 23:
|
|
State.m_f=State.m_fbase;
|
|
State.m_needf=false;
|
|
case 20:
|
|
COptServ::TrimPrepare(State.m_f,State.m_trimthreshold);
|
|
if(!State.m_xrep)
|
|
{
|
|
label=24;
|
|
break;
|
|
}
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=7;
|
|
label=-1;
|
|
break;
|
|
case 7:
|
|
State.m_xupdated=false;
|
|
case 24:
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
State.m_repterminationtype=8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
State.m_repnfev=1;
|
|
State.m_fold=State.m_f;
|
|
v=MathPow(State.m_g*State.m_s+0,2.0).Sum();
|
|
if(MathSqrt(v)<=State.m_epsg)
|
|
{
|
|
State.m_repterminationtype=4;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
//--- Choose initial step and direction.
|
|
//--- Apply preconditioner, if we have something other than default.
|
|
State.m_d=State.m_g.ToVector()*(-1.0);
|
|
switch(State.m_prectype)
|
|
{
|
|
case 0:
|
|
//--- Default preconditioner is used, but we can't use it before iterations will start
|
|
v=State.m_g.Dot(State.m_g);
|
|
v=MathSqrt(v);
|
|
if(State.m_stpmax==0.0)
|
|
State.m_stp=MathMin(1.0/v,1);
|
|
else
|
|
State.m_stp=MathMin(1.0/v,State.m_stpmax);
|
|
break;
|
|
case 1:
|
|
//--- Cholesky preconditioner is used
|
|
CFbls::FblsCholeskySolve(State.m_denseh,1.0,n,true,State.m_d,State.m_autobuf);
|
|
State.m_stp=1;
|
|
break;
|
|
case 2:
|
|
//--- diagonal approximation is used
|
|
State.m_d/=State.m_diagh;
|
|
State.m_stp=1;
|
|
break;
|
|
case 3:
|
|
//--- scale-based preconditioner is used
|
|
State.m_d*=State.m_s.Pow(2.0)+0;
|
|
State.m_stp=1;
|
|
break;
|
|
case 4:
|
|
//--- rank-k BFGS-based preconditioner is used
|
|
COptServ::InexactLBFGSPreconditioner(State.m_d,n,State.m_precd,State.m_precc,State.m_precw,State.m_preck,State.m_precbuf);
|
|
State.m_stp=1;
|
|
break;
|
|
case 5:
|
|
//--- exact low-rank preconditioner is used
|
|
COptServ::ApplyLowRankPreconditioner(State.m_d,State.m_lowrankbuf);
|
|
State.m_stp=1;
|
|
break;
|
|
}
|
|
//--- Main cycle
|
|
State.m_k=0;
|
|
case 26:
|
|
//--- Main cycle: prepare to 1-D line search
|
|
State.m_p=State.m_k%m;
|
|
State.m_q=MathMin(State.m_k,m-1);
|
|
//--- Store X[k], G[k]
|
|
State.m_xp=State.m_x;
|
|
State.m_sk.Row(State.m_p,State.m_x*(-1)+0);
|
|
State.m_yk.Row(State.m_p,State.m_g*(-1)+0);
|
|
//--- Minimize F(x+alpha*d)
|
|
//--- Calculate S[k], Y[k]
|
|
State.m_mcstage=0;
|
|
if(State.m_k!=0)
|
|
State.m_stp=1.0;
|
|
CLinMin::LinMinNormalized(State.m_d,State.m_stp,n);
|
|
COptServ::SmoothnessMonitorStartLineSearch1u(State.m_smonitor,State.m_s,State.m_invs,State.m_x,State.m_f,State.m_g);
|
|
CLinMin::MCSrch(n,State.m_x,State.m_f,State.m_g,State.m_d,State.m_stp,State.m_stpmax,m_gtol,mcinfo,State.m_nfev,State.m_work,State.m_lstate,State.m_mcstage);
|
|
case 28:
|
|
if(State.m_mcstage==0)
|
|
{
|
|
label=29;
|
|
break;
|
|
}
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=30;
|
|
break;
|
|
}
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=8;
|
|
label=-1;
|
|
break;
|
|
case 8:
|
|
State.m_needfg=false;
|
|
label=31;
|
|
break;
|
|
case 30:
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=9;
|
|
label=-1;
|
|
break;
|
|
case 9:
|
|
State.m_fbase=State.m_f;
|
|
i=0;
|
|
case 32:
|
|
if(i>n-1)
|
|
{
|
|
label=34;
|
|
break;
|
|
}
|
|
v=State.m_x[i];
|
|
State.m_x.Set(i,v-State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=10;
|
|
label=-1;
|
|
break;
|
|
case 10:
|
|
State.m_fm2=State.m_f;
|
|
State.m_x.Set(i,v-0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=11;
|
|
label=-1;
|
|
break;
|
|
case 11:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,v+0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=12;
|
|
label=-1;
|
|
break;
|
|
case 12:
|
|
State.m_fp1=State.m_f;
|
|
State.m_x.Set(i,v+State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=13;
|
|
label=-1;
|
|
break;
|
|
case 13:
|
|
State.m_fp2=State.m_f;
|
|
State.m_x.Set(i,v);
|
|
State.m_g.Set(i,(8*(State.m_fp1-State.m_fm1)-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[i]));
|
|
i++;
|
|
label=32;
|
|
break;
|
|
case 34:
|
|
State.m_f=State.m_fbase;
|
|
State.m_needf=false;
|
|
case 31:
|
|
COptServ::SmoothnessMonitorEnqueuePoint1u(State.m_smonitor,State.m_s,State.m_invs,State.m_d,State.m_stp,State.m_x,State.m_f,State.m_g);
|
|
COptServ::TrimFunction(State.m_f,State.m_g,n,State.m_trimthreshold);
|
|
CLinMin::MCSrch(n,State.m_x,State.m_f,State.m_g,State.m_d,State.m_stp,State.m_stpmax,m_gtol,mcinfo,State.m_nfev,State.m_work,State.m_lstate,State.m_mcstage);
|
|
label=28;
|
|
break;
|
|
case 29:
|
|
COptServ::SmoothnessMonitorFinalizeLineSearch(State.m_smonitor);
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination.
|
|
//--- Restore previous point and return.
|
|
State.m_x=State.m_xp;
|
|
State.m_repterminationtype=8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(!State.m_xrep)
|
|
{
|
|
label=35;
|
|
break;
|
|
}
|
|
//--- report
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=14;
|
|
label=-1;
|
|
break;
|
|
case 14:
|
|
State.m_xupdated=false;
|
|
case 35:
|
|
State.m_repnfev=State.m_repnfev+State.m_nfev;
|
|
State.m_repiterationscount=State.m_repiterationscount+1;
|
|
State.m_sk.Row(State.m_p,State.m_x.ToVector()+State.m_sk[State.m_p]);
|
|
State.m_yk.Row(State.m_p,State.m_g.ToVector()+State.m_yk[State.m_p]);
|
|
//--- Stopping conditions
|
|
v=MathPow(State.m_g*State.m_s+0,2.0).Sum();
|
|
if(!MathIsValidNumber(v) || !MathIsValidNumber(State.m_f))
|
|
{
|
|
//--- Abnormal termination - infinities in function/gradient
|
|
State.m_repterminationtype=-8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(State.m_repiterationscount>=State.m_maxits && State.m_maxits>0)
|
|
{
|
|
//--- Too many iterations
|
|
State.m_repterminationtype=5;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(MathSqrt(v)<=State.m_epsg)
|
|
{
|
|
//--- Gradient is small enough
|
|
State.m_repterminationtype=4;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if((State.m_fold-State.m_f)<=(State.m_epsf*MathMax(MathAbs(State.m_fold),MathMax(MathAbs(State.m_f),1.0))))
|
|
{
|
|
//--- F(k+1)-F(k) is small enough
|
|
State.m_repterminationtype=1;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
v=MathPow(State.m_sk[State.m_p]/State.m_s.ToVector(),2.0).Sum();
|
|
if(MathSqrt(v)<=State.m_epsx)
|
|
{
|
|
//--- X(k+1)-X(k) is small enough
|
|
State.m_repterminationtype=2;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
//--- If Wolfe conditions are satisfied, we can update
|
|
//--- limited memory model.
|
|
//--- However, if conditions are not satisfied (NFEV limit is met,
|
|
//--- function is too wild, ...), we'll skip L-BFGS update
|
|
if(mcinfo!=1)
|
|
{
|
|
//--- Skip update.
|
|
//--- In such cases we'll initialize search direction by
|
|
//--- antigradient vector, because it leads to more
|
|
//--- transparent code with less number of special cases
|
|
State.m_fold=State.m_f;
|
|
State.m_d=State.m_g.ToVector()*(-1.0);
|
|
}
|
|
else
|
|
{
|
|
//--- Calculate Rho[k], GammaK
|
|
v=0.0;
|
|
for(i_=0; i_<n; i_++)
|
|
v+=State.m_yk.Get(State.m_p,i_)*State.m_sk.Get(State.m_p,i_);
|
|
vv=0.0;
|
|
for(i_=0; i_<n; i_++)
|
|
vv+=State.m_yk.Get(State.m_p,i_)*State.m_yk.Get(State.m_p,i_);
|
|
if(v==0.0 || vv==0.0)
|
|
{
|
|
//--- Rounding errors make further iterations impossible.
|
|
State.m_repterminationtype=-2;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
State.m_rho.Set(State.m_p,1/v);
|
|
State.m_gammak=v/vv;
|
|
//--- Calculate d(k+1) = -H(k+1)*g(k+1)
|
|
//--- for I:=K downto K-Q do
|
|
//--- V = s(i)^T * work(iteration:I)
|
|
//--- theta(i) = V
|
|
//--- work(iteration:I+1) = work(iteration:I) - V*Rho(i)*y(i)
|
|
//--- work(last iteration) = H0*work(last iteration) - preconditioner
|
|
//--- for I:=K-Q to K do
|
|
//--- V = y(i)^T*work(iteration:I)
|
|
//--- work(iteration:I+1) = work(iteration:I) +(-V+theta(i))*Rho(i)*s(i)
|
|
//--- NOW WORK CONTAINS d(k+1)
|
|
State.m_work=State.m_g;
|
|
for(i=State.m_k; i>=State.m_k-State.m_q; i--)
|
|
{
|
|
ic=i%m;
|
|
v=State.m_work.DotR(State.m_sk,ic);
|
|
State.m_theta.Set(ic,v);
|
|
vv=v*State.m_rho[ic];
|
|
State.m_work-=State.m_yk[ic]*vv;
|
|
}
|
|
switch(State.m_prectype)
|
|
{
|
|
case 0:
|
|
//--- Simple preconditioner is used
|
|
v=State.m_gammak;
|
|
State.m_work*=v;
|
|
break;
|
|
case 1:
|
|
//--- Cholesky preconditioner is used
|
|
CFbls::FblsCholeskySolve(State.m_denseh,1,n,true,State.m_work,State.m_autobuf);
|
|
break;
|
|
case 2:
|
|
//--- diagonal approximation is used
|
|
State.m_work/=State.m_diagh;
|
|
break;
|
|
case 3:
|
|
//--- scale-based preconditioner is used
|
|
State.m_work*=State.m_s.Pow(2.0)+0;
|
|
break;
|
|
case 4:
|
|
//--- Rank-K BFGS-based preconditioner is used
|
|
COptServ::InexactLBFGSPreconditioner(State.m_work,n,State.m_precd,State.m_precc,State.m_precw,State.m_preck,State.m_precbuf);
|
|
break;
|
|
case 5:
|
|
//--- Exact low-rank preconditioner is used
|
|
COptServ::ApplyLowRankPreconditioner(State.m_work,State.m_lowrankbuf);
|
|
break;
|
|
}
|
|
for(i=State.m_k-State.m_q; i<=State.m_k; i++)
|
|
{
|
|
ic=i%m;
|
|
v=State.m_work.DotR(State.m_yk,ic);
|
|
vv=State.m_rho[ic]*(-v+State.m_theta[ic]);
|
|
State.m_work+=State.m_sk[ic]*vv;
|
|
}
|
|
State.m_d=State.m_work.ToVector()*(-1.0);
|
|
//--- Next step
|
|
State.m_fold=State.m_f;
|
|
State.m_k++;
|
|
}
|
|
label=26;
|
|
break;
|
|
case 27:
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
//--- Saving State
|
|
result=true;
|
|
State.m_rstate.ia.Set(0,n);
|
|
State.m_rstate.ia.Set(1,m);
|
|
State.m_rstate.ia.Set(2,i);
|
|
State.m_rstate.ia.Set(3,j);
|
|
State.m_rstate.ia.Set(4,ic);
|
|
State.m_rstate.ia.Set(5,mcinfo);
|
|
State.m_rstate.ra.Set(0,v);
|
|
State.m_rstate.ra.Set(1,vv);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Variables of IPM method(primal and dual, slacks) |
|
|
//+------------------------------------------------------------------+
|
|
struct CVIPMVars
|
|
{
|
|
int m_m;
|
|
int m_n;
|
|
CRowDouble m_g;
|
|
CRowDouble m_p;
|
|
CRowDouble m_q;
|
|
CRowDouble m_s;
|
|
CRowDouble m_t;
|
|
CRowDouble m_v;
|
|
CRowDouble m_w;
|
|
CRowDouble m_x;
|
|
CRowDouble m_y;
|
|
CRowDouble m_z;
|
|
//--- constructor / destructor
|
|
CVIPMVars(void) { m_m=0; m_n=0; }
|
|
~CVIPMVars(void) {}
|
|
//---
|
|
void Copy(const CVIPMVars &obj);
|
|
//--- overloading
|
|
void operator=(const CVIPMVars &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMVars::Copy(const CVIPMVars &obj)
|
|
{
|
|
m_m=obj.m_m;
|
|
m_n=obj.m_n;
|
|
m_g=obj.m_g;
|
|
m_p=obj.m_p;
|
|
m_q=obj.m_q;
|
|
m_s=obj.m_s;
|
|
m_t=obj.m_t;
|
|
m_v=obj.m_v;
|
|
m_w=obj.m_w;
|
|
m_x=obj.m_x;
|
|
m_y=obj.m_y;
|
|
m_z=obj.m_z;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Reduced(M + N)*(M + N) KKT system stored in sparse format |
|
|
//+------------------------------------------------------------------+
|
|
struct CVIPMReducedSparseSystem
|
|
{
|
|
int m_ntotal;
|
|
bool m_isdiagonal[];
|
|
CSparseMatrix m_rawsystem;
|
|
CSpCholAnalysis m_analysis;
|
|
CRowInt m_coldegrees;
|
|
CRowInt m_priorities;
|
|
CRowInt m_rowdegrees;
|
|
CRowDouble m_effectivediag;
|
|
//--- constructor / destructor
|
|
CVIPMReducedSparseSystem(void) { m_ntotal=0; }
|
|
~CVIPMReducedSparseSystem(void) {}
|
|
void Copy(const CVIPMReducedSparseSystem &obj);
|
|
//--- overloading
|
|
void operator=(const CVIPMReducedSparseSystem &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMReducedSparseSystem::Copy(const CVIPMReducedSparseSystem &obj)
|
|
{
|
|
m_ntotal=obj.m_ntotal;
|
|
ArrayCopy(m_isdiagonal,obj.m_isdiagonal);
|
|
m_rawsystem=obj.m_rawsystem;
|
|
m_analysis=obj.m_analysis;
|
|
m_coldegrees=obj.m_coldegrees;
|
|
m_priorities=obj.m_priorities;
|
|
m_rowdegrees=obj.m_rowdegrees;
|
|
m_effectivediag=obj.m_effectivediag;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Right - hand side for KKT system: |
|
|
//| * Rho corresponds to Ax - w = b |
|
|
//| * Nu corresponds to x - g = l |
|
|
//| * Tau corresponds to x + t = u |
|
|
//| * Alpha corresponds to w + p = r |
|
|
//| * Sigma corresponds to A'y+z-s-Hx=c |
|
|
//| * Beta corresponds to y + q - v = 0 |
|
|
//| * GammaZ, GammaS, GammaQ, GammaW correspond to complementarity |
|
|
//| conditions |
|
|
//+------------------------------------------------------------------+
|
|
struct CVIPMRightHandSide
|
|
{
|
|
CRowDouble m_alpha;
|
|
CRowDouble m_beta;
|
|
CRowDouble m_gammaq;
|
|
CRowDouble m_gammas;
|
|
CRowDouble m_gammaw;
|
|
CRowDouble m_gammaz;
|
|
CRowDouble m_nu;
|
|
CRowDouble m_rho;
|
|
CRowDouble m_sigma;
|
|
CRowDouble m_tau;
|
|
//--- constructor / destructor
|
|
CVIPMRightHandSide(void) {}
|
|
~CVIPMRightHandSide(void) {}
|
|
//---
|
|
void Copy(const CVIPMRightHandSide &obj);
|
|
//--- overloading
|
|
void operator=(const CVIPMRightHandSide &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMRightHandSide::Copy(const CVIPMRightHandSide &obj)
|
|
{
|
|
m_alpha=obj.m_alpha;
|
|
m_beta=obj.m_beta;
|
|
m_gammaq=obj.m_gammaq;
|
|
m_gammas=obj.m_gammas;
|
|
m_gammaw=obj.m_gammaw;
|
|
m_gammaz=obj.m_gammaz;
|
|
m_nu=obj.m_nu;
|
|
m_rho=obj.m_rho;
|
|
m_sigma=obj.m_sigma;
|
|
m_tau=obj.m_tau;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| VIPM State |
|
|
//+------------------------------------------------------------------+
|
|
struct CVIPMState
|
|
{
|
|
int m_cntgz;
|
|
int m_cntpq;
|
|
int m_cntts;
|
|
int m_cntwv;
|
|
int m_factorizationtype;
|
|
int m_hkind;
|
|
int m_mdense;
|
|
int m_msparse;
|
|
int m_n;
|
|
int m_nmain;
|
|
int m_repiterationscount;
|
|
int m_repncholesky;
|
|
double m_epsd;
|
|
double m_epsgap;
|
|
double m_epsp;
|
|
double m_targetscale;
|
|
bool m_aflips[];
|
|
bool m_dodetailedtrace;
|
|
bool m_dotrace;
|
|
bool m_factorizationpoweredup;
|
|
bool m_factorizationpresent;
|
|
bool m_HasBndL[];
|
|
bool m_HasBndU[];
|
|
bool m_hasgz[];
|
|
bool m_haspq[];
|
|
bool m_hasr[];
|
|
bool m_hasts[];
|
|
bool m_haswv[];
|
|
bool m_isdiagonalh;
|
|
bool m_isfrozen[];
|
|
bool m_islinear;
|
|
bool m_slacksforequalityconstraints;
|
|
CSparseMatrix m_combinedaslack;
|
|
CSparseMatrix m_sparseafull;
|
|
CSparseMatrix m_sparseamain;
|
|
CSparseMatrix m_sparseh;
|
|
CSparseMatrix m_tmpsparse0;
|
|
CVIPMVars m_best;
|
|
CVIPMVars m_current;
|
|
CVIPMVars m_deltaaff;
|
|
CVIPMVars m_deltacorr;
|
|
CVIPMVars m_trial;
|
|
CVIPMVars m_zerovars;
|
|
CVIPMRightHandSide m_rhs;
|
|
CVIPMReducedSparseSystem m_reducedsparsesystem;
|
|
CRowInt m_tmpi;
|
|
CRowDouble m_ascales;
|
|
CRowDouble m_b;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_c;
|
|
CRowDouble m_deltaxy;
|
|
CRowDouble m_diagddr;
|
|
CRowDouble m_diagde;
|
|
CRowDouble m_diagder;
|
|
CRowDouble m_diagdq;
|
|
CRowDouble m_diagdqi;
|
|
CRowDouble m_diagdqiri;
|
|
CRowDouble m_diagds;
|
|
CRowDouble m_diagdsi;
|
|
CRowDouble m_diagdsiri;
|
|
CRowDouble m_diagdw;
|
|
CRowDouble m_diagdwi;
|
|
CRowDouble m_diagdwir;
|
|
CRowDouble m_diagdz;
|
|
CRowDouble m_diagdzi;
|
|
CRowDouble m_diagdziri;
|
|
CRowDouble m_diagr;
|
|
CRowDouble m_dummyr;
|
|
CRowDouble m_factinvregdzrz;
|
|
CRowDouble m_factregdhrh;
|
|
CRowDouble m_factregewave;
|
|
CRowDouble m_facttmpdiag;
|
|
CRowDouble m_invscl;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CRowDouble m_r;
|
|
CRowDouble m_rawbndl;
|
|
CRowDouble m_rawbndu;
|
|
CRowDouble m_rhsalphacap;
|
|
CRowDouble m_rhsbetacap;
|
|
CRowDouble m_rhsnucap;
|
|
CRowDouble m_rhstaucap;
|
|
CRowDouble m_scl;
|
|
CRowDouble m_tmp2;
|
|
CRowDouble m_tmpaty;
|
|
CRowDouble m_tmpax;
|
|
CRowDouble m_tmphx;
|
|
CRowDouble m_tmplaggrad;
|
|
CRowDouble m_tmpy;
|
|
CRowDouble m_xorigin;
|
|
CMatrixDouble m_denseafull;
|
|
CMatrixDouble m_denseamain;
|
|
CMatrixDouble m_denseh;
|
|
CMatrixDouble m_factdensehaug;
|
|
CMatrixDouble m_tmpr2;
|
|
//--- constructor / destructor
|
|
CVIPMState(void);
|
|
~CVIPMState(void) {}
|
|
//---
|
|
void Copy(const CVIPMState &obj);
|
|
//--- overloading
|
|
void operator=(const CVIPMState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CVIPMState::CVIPMState(void)
|
|
{
|
|
m_cntgz=0;
|
|
m_cntpq=0;
|
|
m_cntts=0;
|
|
m_cntwv=0;
|
|
m_factorizationtype=0;
|
|
m_hkind=0;
|
|
m_mdense=0;
|
|
m_msparse=0;
|
|
m_n=0;
|
|
m_nmain=0;
|
|
m_repiterationscount=0;
|
|
m_repncholesky=0;
|
|
m_epsd=0;
|
|
m_epsgap=0;
|
|
m_epsp=0;
|
|
m_targetscale=0;
|
|
m_dodetailedtrace=false;
|
|
m_dotrace=false;
|
|
m_factorizationpoweredup=false;
|
|
m_factorizationpresent=false;
|
|
m_isdiagonalh=false;
|
|
m_islinear=false;
|
|
m_slacksforequalityconstraints=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMState::Copy(const CVIPMState &obj)
|
|
{
|
|
m_cntgz=obj.m_cntgz;
|
|
m_cntpq=obj.m_cntpq;
|
|
m_cntts=obj.m_cntts;
|
|
m_cntwv=obj.m_cntwv;
|
|
m_factorizationtype=obj.m_factorizationtype;
|
|
m_hkind=obj.m_hkind;
|
|
m_mdense=obj.m_mdense;
|
|
m_msparse=obj.m_msparse;
|
|
m_n=obj.m_n;
|
|
m_nmain=obj.m_nmain;
|
|
m_repiterationscount=obj.m_repiterationscount;
|
|
m_repncholesky=obj.m_repncholesky;
|
|
m_epsd=obj.m_epsd;
|
|
m_epsgap=obj.m_epsgap;
|
|
m_epsp=obj.m_epsp;
|
|
m_targetscale=obj.m_targetscale;
|
|
ArrayCopy(m_aflips,obj.m_aflips);
|
|
m_dodetailedtrace=obj.m_dodetailedtrace;
|
|
m_dotrace=obj.m_dotrace;
|
|
m_factorizationpoweredup=obj.m_factorizationpoweredup;
|
|
m_factorizationpresent=obj.m_factorizationpresent;
|
|
ArrayCopy(m_HasBndL,obj.m_HasBndL);
|
|
ArrayCopy(m_HasBndU,obj.m_HasBndU);
|
|
ArrayCopy(m_hasgz,obj.m_hasgz);
|
|
ArrayCopy(m_haspq,obj.m_haspq);
|
|
ArrayCopy(m_hasr,obj.m_hasr);
|
|
ArrayCopy(m_hasts,obj.m_hasts);
|
|
ArrayCopy(m_haswv,obj.m_haswv);
|
|
m_isdiagonalh=obj.m_isdiagonalh;
|
|
ArrayCopy(m_isfrozen,obj.m_isfrozen);
|
|
m_islinear=obj.m_islinear;
|
|
m_slacksforequalityconstraints=obj.m_slacksforequalityconstraints;
|
|
m_combinedaslack=obj.m_combinedaslack;
|
|
m_sparseafull=obj.m_sparseafull;
|
|
m_sparseamain=obj.m_sparseamain;
|
|
m_sparseh=obj.m_sparseh;
|
|
m_tmpsparse0=obj.m_tmpsparse0;
|
|
m_best=obj.m_best;
|
|
m_current=obj.m_current;
|
|
m_deltaaff=obj.m_deltaaff;
|
|
m_deltacorr=obj.m_deltacorr;
|
|
m_trial=obj.m_trial;
|
|
m_zerovars=obj.m_zerovars;
|
|
m_rhs=obj.m_rhs;
|
|
m_reducedsparsesystem=obj.m_reducedsparsesystem;
|
|
m_tmpi=obj.m_tmpi;
|
|
m_ascales=obj.m_ascales;
|
|
m_b=obj.m_b;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_c=obj.m_c;
|
|
m_deltaxy=obj.m_deltaxy;
|
|
m_diagddr=obj.m_diagddr;
|
|
m_diagde=obj.m_diagde;
|
|
m_diagder=obj.m_diagder;
|
|
m_diagdq=obj.m_diagdq;
|
|
m_diagdqi=obj.m_diagdqi;
|
|
m_diagdqiri=obj.m_diagdqiri;
|
|
m_diagds=obj.m_diagds;
|
|
m_diagdsi=obj.m_diagdsi;
|
|
m_diagdsiri=obj.m_diagdsiri;
|
|
m_diagdw=obj.m_diagdw;
|
|
m_diagdwi=obj.m_diagdwi;
|
|
m_diagdwir=obj.m_diagdwir;
|
|
m_diagdz=obj.m_diagdz;
|
|
m_diagdzi=obj.m_diagdzi;
|
|
m_diagdziri=obj.m_diagdziri;
|
|
m_diagr=obj.m_diagr;
|
|
m_dummyr=obj.m_dummyr;
|
|
m_factinvregdzrz=obj.m_factinvregdzrz;
|
|
m_factregdhrh=obj.m_factregdhrh;
|
|
m_factregewave=obj.m_factregewave;
|
|
m_facttmpdiag=obj.m_facttmpdiag;
|
|
m_invscl=obj.m_invscl;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_r=obj.m_r;
|
|
m_rawbndl=obj.m_rawbndl;
|
|
m_rawbndu=obj.m_rawbndu;
|
|
m_rhsalphacap=obj.m_rhsalphacap;
|
|
m_rhsbetacap=obj.m_rhsbetacap;
|
|
m_rhsnucap=obj.m_rhsnucap;
|
|
m_rhstaucap=obj.m_rhstaucap;
|
|
m_scl=obj.m_scl;
|
|
m_tmp2=obj.m_tmp2;
|
|
m_tmpaty=obj.m_tmpaty;
|
|
m_tmpax=obj.m_tmpax;
|
|
m_tmphx=obj.m_tmphx;
|
|
m_tmplaggrad=obj.m_tmplaggrad;
|
|
m_tmpy=obj.m_tmpy;
|
|
m_xorigin=obj.m_xorigin;
|
|
m_denseafull=obj.m_denseafull;
|
|
m_denseamain=obj.m_denseamain;
|
|
m_denseh=obj.m_denseh;
|
|
m_factdensehaug=obj.m_factdensehaug;
|
|
m_tmpr2=obj.m_tmpr2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CVIPMSolver
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const double m_muquasidense;
|
|
static const int m_maxipmits;
|
|
static const double m_initslackval;
|
|
static const double m_steplengthdecay;
|
|
static const double m_stagnationdelta;
|
|
static const double m_primalinfeasible1;
|
|
static const double m_dualinfeasible1;
|
|
static const double m_bigy;
|
|
static const double m_ygrowth;
|
|
static const int m_itersfortoostringentcond;
|
|
static const int m_minitersbeforedroppingbounds;
|
|
static const int m_minitersbeforeinfeasible;
|
|
static const int m_minitersbeforestagnation;
|
|
static const int m_minitersbeforeeworststagnation;
|
|
static const int m_primalstagnationlen;
|
|
static const int m_dualstagnationlen;
|
|
static const double m_bigconstrxtol;
|
|
static const double m_bigconstrmag;
|
|
static const double m_minitersbeforesafeguards;
|
|
static const double m_badsteplength;
|
|
|
|
static void VIPMInitDense(CVIPMState &State,CRowDouble &s,CRowDouble &xorigin,int n);
|
|
static void VIPMInitDenseWithSlacks(CVIPMState &State,CRowDouble &s,CRowDouble &xorigin,int nmain,int n);
|
|
static void VIPMInitSparse(CVIPMState &State,CRowDouble &s,CRowDouble &xorigin,int n);
|
|
static void VIPMSetQuadraticLinear(CVIPMState &State,CMatrixDouble &denseh,CSparseMatrix &sparseh,int hkind,bool IsUpper,CRowDouble &c);
|
|
static void VIPMSetConstraints(CVIPMState &State,CRowDouble &bndl,CRowDouble &bndu,CSparseMatrix &sparsea,int msparse,CMatrixDouble &densea,int mdense,CRowDouble &cl,CRowDouble &cu);
|
|
static void VIPMSetCond(CVIPMState &State,double epsp,double epsd,double epsgap);
|
|
static void VIPMOptimize(CVIPMState &State,bool dropbigbounds,CRowDouble &xs,CRowDouble &lagbc,CRowDouble &laglc,int &terminationtype);
|
|
|
|
private:
|
|
static void VARSInitByZero(CVIPMVars &vstate,int n,int m);
|
|
static void VARSInitFrom(CVIPMVars &vstate,CVIPMVars &vsrc);
|
|
static void VarsAddStep(CVIPMVars &vstate,CVIPMVars &vdir,double stpp,double stpd);
|
|
static double VarsComputeComplementarityGap(CVIPMVars &vstate);
|
|
static double VARSComputeMu(CVIPMState &State,CVIPMVars &vstate);
|
|
static void ReducedSystemInit(CVIPMReducedSparseSystem &s,CVIPMState &solver);
|
|
static bool ReducedSystemFactorizeWithAddEnd(CVIPMReducedSparseSystem &s,CRowDouble &d,double modeps,double badchol,double &sumsq,double &errsq);
|
|
static void ReducedSystemSolve(CVIPMReducedSparseSystem &s,CRowDouble &b);
|
|
static void VIPMInit(CVIPMState &State,CRowDouble &s,CRowDouble &xorigin,int n,int nmain,int ftype);
|
|
static double VIPMTarget(CVIPMState &State,CRowDouble &x);
|
|
static void MultiplyGEAX(CVIPMState &State,double alpha,CRowDouble &x,int offsx,double beta,CRowDouble &y,int offsax);
|
|
static void MultiplyGEATX(CVIPMState &State,double alpha,CRowDouble &x,int offsx,double beta,CRowDouble &y,int offsy);
|
|
static void MultiplyHX(CVIPMState &State,CRowDouble &x,CRowDouble &hx);
|
|
static void VIPMMultiply(CVIPMState &State,CRowDouble &x,CRowDouble &y,CRowDouble &hx,CRowDouble &ax,CRowDouble &aty);
|
|
static void VIPMPowerUp(CVIPMState &State,double regfree);
|
|
static bool VIPMFactorize(CVIPMState &State,double alpha0,CRowDouble &d,double beta0,CRowDouble &e,double alpha11,double beta11,double modeps,double dampeps);
|
|
static void SolveReducedKKTSystem(CVIPMState &State,CRowDouble &deltaxy);
|
|
static bool VIPMPrecomputeNewtonFactorization(CVIPMState &State,CVIPMVars &v0,double regeps,double modeps,double dampeps,double dampfree);
|
|
static void SolveKKTSystem(CVIPMState &State,CVIPMRightHandSide &rhs,CVIPMVars &sol);
|
|
static bool VIPMComputeStepDirection(CVIPMState &State,CVIPMVars &v0,double muestimate,CVIPMVars &vdestimate,CVIPMVars &vdresult,double reg,bool isdampepslarge);
|
|
static void VIPMComputeStepLength(CVIPMState &State,CVIPMVars &v0,CVIPMVars &vs,double stepdecay,double &alphap,double &alphad);
|
|
static void VIPMPerformStep(CVIPMState &State,double alphap,double alphad);
|
|
static void ComputeErrors(CVIPMState &State,double &errp2,double &errd2,double &errpinf,double &errdinf,double &egap);
|
|
static void RunIntegrityChecks(CVIPMState &State,CVIPMVars &v0,CVIPMVars &vd,double alphap,double alphad);
|
|
static void TraceProgress(CVIPMState &State,double mu,double muaff,double sigma,double alphap,double alphad);
|
|
static void RHSCompute(CVIPMState &State,CVIPMVars &v0,double muestimate,CVIPMVars &direstimate,CVIPMRightHandSide &rhs,double reg);
|
|
static void RHSSubtract(CVIPMState &State,CVIPMRightHandSide &rhs,CVIPMVars &v0,CVIPMVars &vdcandidate,double reg);
|
|
static double RHSPrimal2(CVIPMRightHandSide &rhs);
|
|
static double RHSDual2(CVIPMRightHandSide &rhs);
|
|
static double RHSPrimalInf(CVIPMRightHandSide &rhs);
|
|
static double RHSDualInf(CVIPMRightHandSide &rhs);
|
|
static double RHSCompl2(CVIPMRightHandSide &rhs);
|
|
static double MinNZ(CRowDouble &x,int n);
|
|
static double MinProdNZ(CRowDouble &x,CRowDouble &y,int n);
|
|
static double MaxProdNZ(CRowDouble &x,CRowDouble &y,int n);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CVIPMSolver::m_muquasidense=2.0;
|
|
const int CVIPMSolver::m_maxipmits=200;
|
|
const double CVIPMSolver::m_initslackval=100.0;
|
|
const double CVIPMSolver::m_steplengthdecay=0.95;
|
|
const double CVIPMSolver::m_stagnationdelta=0.99999;
|
|
const double CVIPMSolver::m_primalinfeasible1=1.0E-3;
|
|
const double CVIPMSolver::m_dualinfeasible1=1.0E-3;
|
|
const double CVIPMSolver::m_bigy=1.0E8;
|
|
const double CVIPMSolver::m_ygrowth=1.0E6;
|
|
const int CVIPMSolver::m_itersfortoostringentcond=25;
|
|
const int CVIPMSolver::m_minitersbeforedroppingbounds=3;
|
|
const int CVIPMSolver::m_minitersbeforeinfeasible=3;
|
|
const int CVIPMSolver::m_minitersbeforestagnation=5;
|
|
const int CVIPMSolver::m_minitersbeforeeworststagnation=50;
|
|
const int CVIPMSolver::m_primalstagnationlen=5;
|
|
const int CVIPMSolver::m_dualstagnationlen=7;
|
|
const double CVIPMSolver::m_bigconstrxtol=1.0E-5;
|
|
const double CVIPMSolver::m_bigconstrmag=1.0E3;
|
|
const double CVIPMSolver::m_minitersbeforesafeguards=5;
|
|
const double CVIPMSolver::m_badsteplength=1.0E-3;
|
|
//+------------------------------------------------------------------+
|
|
//| Initializes QP - IPM State and prepares it to receive quadratic/ |
|
|
//| linear terms and constraints. |
|
|
//| The solver is configured to work internally with dense NxN |
|
|
//| factorization, no matter what exactly is passed - dense or sparse|
|
|
//| matrices. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - solver State to be configured; previously allocated|
|
|
//| memory is reused as much as possible |
|
|
//| S - scale vector, array[N]: |
|
|
//| * I-th element contains scale of I-th variable, |
|
|
//| * S[I] > 0 |
|
|
//| XOrigin - origin term, array[N]. Can be zero. The solver |
|
|
//| solves problem of the form |
|
|
//| > |
|
|
//| > min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin)) |
|
|
//| > |
|
|
//| The terms A and b(as well as constraints) will be specified later|
|
|
//| with separate calls. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMInitDense(CVIPMState &State,
|
|
CRowDouble &s,
|
|
CRowDouble &xorigin,
|
|
int n)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(s,n),__FUNCTION__+": S contains infinite or NaN elements"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(xorigin,n),__FUNCTION__+": XOrigin contains infinite or NaN elements"))
|
|
return;
|
|
VIPMInit(State,s,xorigin,n,n,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initializes QP-IPM State and prepares it to receive quadratic/ |
|
|
//| linear terms and constraints. |
|
|
//| The solver is configured to work internally with dense NxN |
|
|
//| problem divided into two distinct parts-"main" and slack one: |
|
|
//| * dense quadratic term is a NMain*NMain matrix(NMain <= N),|
|
|
//| quadratic coefficients are zero for variables outside of |
|
|
//| [0, NMain) range) |
|
|
//| * linear term is general vector of length N |
|
|
//| * linear constraints have special structure for variable with |
|
|
//| indexes in [NMain, N) range: at most one element per column |
|
|
//| can be nonzero. |
|
|
//| This mode is intended for problems arising during SL1QP nonlinear|
|
|
//| programming. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - m_solver State to be configured; previously |
|
|
//| allocated memory is reused as much as possible |
|
|
//| S - scale vector, array[N]: |
|
|
//| * I-th element contains scale of I-th variable, |
|
|
//| * S[I] > 0 |
|
|
//| XOrigin - origin term, array[N]. Can be zero. The solver |
|
|
//| solves problem of the form |
|
|
//| > |
|
|
//| > min(0.5 * (x - x_origin)'*A*(x-x_origin)+b' * (x - x_origin))|
|
|
//| > |
|
|
//| The terms A and b(as well as constraints) will be specified later|
|
|
//| with separate calls. |
|
|
//| NMain - number of "main" variables, 1 <= NMain <= N |
|
|
//| N - total number of variables including slack ones |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMInitDenseWithSlacks(CVIPMState &State,
|
|
CRowDouble &s,
|
|
CRowDouble &xorigin,
|
|
int nmain,
|
|
int n)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nmain>=1,__FUNCTION__+": NMain<1"))
|
|
return;
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(nmain<=n,__FUNCTION__+": NMain>N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(s,n),__FUNCTION__+": S contains infinite or NaN elements"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(xorigin,n),__FUNCTION__+": XOrigin contains infinite or NaN elements"))
|
|
return;
|
|
VIPMInit(State,s,xorigin,n,nmain,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initializes QP-IPM State and prepares it to receive quadratic/ |
|
|
//| linear terms and constraints. |
|
|
//| The solver is configured to work internally with sparse |
|
|
//| (N + M)x(N + M) factorization no matter what exactly is passed - |
|
|
//| dense or sparse matrices. Dense quadratic term will be sparsified|
|
|
//| prior to storage. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - solver State to be configured; previously allocated|
|
|
//| memory is reused as much as possible |
|
|
//| S - scale vector, array[N]: |
|
|
//| * I-th element contains scale of I-th variable, |
|
|
//| * S[I] > 0 |
|
|
//| XOrigin - origin term, array[N]. Can be zero. The solver |
|
|
//| solves problem of the form |
|
|
//| > |
|
|
//| > min(0.5*(x - x_origin)'*A*(x-x_origin)+b'*(x - x_origin)) |
|
|
//| > |
|
|
//| The terms A and b(as well as constraints) will be specified later|
|
|
//| with separate calls. |
|
|
//| N - total number of variables, N >= 1 |
|
|
//| This optimization mode assumes that no slack variables is present|
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMInitSparse(CVIPMState &State,
|
|
CRowDouble &s,
|
|
CRowDouble &xorigin,
|
|
int n)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(s,n),__FUNCTION__+": S contains infinite or NaN elements"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(xorigin,n),__FUNCTION__+": XOrigin contains infinite or NaN elements"))
|
|
return;
|
|
VIPMInit(State,s,xorigin,n,n,1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets linear / quadratic terms for QP - IPM m_solver |
|
|
//| If you initialized m_solver with VIMPInitDenseWithSlacks(), NMain|
|
|
//| below is a number of non-slack variables. In other cases, |
|
|
//| NMain = N. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - instance initialized with one of the initialization|
|
|
//| functions |
|
|
//| DenseH - if HKind = 0: array[NMain, NMain], dense quadratic |
|
|
//| term (either upper or lower triangle) |
|
|
//| SparseH - if HKind = 1: array[NMain, NMain], sparse quadratic|
|
|
//| term (either upper or lower triangle) |
|
|
//| HKind - 0 or 1, quadratic term format |
|
|
//| IsUpper - whether dense / sparse H contains lower or upper |
|
|
//| triangle of the quadratic term |
|
|
//| C - array[N], linear term |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMSetQuadraticLinear(CVIPMState &State,
|
|
CMatrixDouble &denseh,
|
|
CSparseMatrix &sparseh,
|
|
int hkind,
|
|
bool IsUpper,
|
|
CRowDouble &c)
|
|
{
|
|
//--- create variables
|
|
int nmain=State.m_nmain;
|
|
int n=State.m_n;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double v=0;
|
|
double vv=0;
|
|
int nnz=0;
|
|
int offs=0;
|
|
//--- check
|
|
if(!CAp::Assert(hkind==0 || hkind==1,__FUNCTION__+": incorrect HKind"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(c,n),__FUNCTION__+": C contains infinite or NaN elements"))
|
|
return;
|
|
if(!CAp::Assert(State.m_factorizationtype==0 || State.m_factorizationtype==1,__FUNCTION__+": unexpected factorization type"))
|
|
return;
|
|
//--- Set problem Info, reset factorization flag
|
|
State.m_islinear=false;
|
|
State.m_factorizationpresent=false;
|
|
State.m_factorizationpoweredup=false;
|
|
//--- Linear term
|
|
State.m_c=c;
|
|
State.m_c.Resize(n);
|
|
//--- Quadratic term and normalization
|
|
//--- NOTE: we perform integrity check for inifinities/NANs by
|
|
//--- computing sum of all matrix elements and checking its
|
|
//--- value for being finite. It is a bit faster than checking
|
|
//--- each element individually.
|
|
State.m_hkind=-1;
|
|
State.m_targetscale=1.0;
|
|
switch(State.m_factorizationtype)
|
|
{
|
|
case 0:
|
|
//--- Quadratic term is stored in dense format: either copy dense
|
|
//--- term of densify sparse one
|
|
State.m_hkind=0;
|
|
switch(hkind)
|
|
{
|
|
case 0:
|
|
//--- Copy dense quadratic term
|
|
if(IsUpper)
|
|
State.m_denseh=denseh.Transpose()+0;
|
|
else
|
|
State.m_denseh=denseh;
|
|
State.m_denseh.Resize(nmain,nmain);
|
|
break;
|
|
case 1:
|
|
//--- Extract sparse quadratic term
|
|
if(!CAp::Assert(sparseh.m_MatrixType==1,__FUNCTION__+": unexpected sparse matrix format"))
|
|
return;
|
|
if(!CAp::Assert(sparseh.m_M==nmain,__FUNCTION__+": unexpected sparse matrix size"))
|
|
return;
|
|
if(!CAp::Assert(sparseh.m_N==nmain,__FUNCTION__+": unexpected sparse matrix size"))
|
|
return;
|
|
State.m_denseh.Resize(nmain,nmain);
|
|
for(i=0; i<nmain; i++)
|
|
for(j=0; j<=i; j++)
|
|
State.m_denseh.Set(i,j,0);
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
//--- diagonal element
|
|
if(sparseh.m_DIdx[i]!=sparseh.m_UIdx[i])
|
|
State.m_denseh.Set(i,i,sparseh.m_Vals[sparseh.m_DIdx[i]]);
|
|
//--- Off-diagonal elements
|
|
if(IsUpper)
|
|
{
|
|
//--- superdiagonal elements are moved to subdiagonal part
|
|
j0=sparseh.m_UIdx[i];
|
|
j1=sparseh.m_RIdx[i+1];
|
|
for(j=j0; j<j1; j++)
|
|
State.m_denseh.Set(sparseh.m_Idx[j],i,sparseh.m_Vals[j]);
|
|
}
|
|
else
|
|
{
|
|
//--- subdiagonal elements are moved to subdiagonal part
|
|
j0=sparseh.m_RIdx[i];
|
|
j1=sparseh.m_DIdx[i];
|
|
for(j=j0; j<j1; j++)
|
|
State.m_denseh.Set(i,sparseh.m_Idx[j],sparseh.m_Vals[j]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
vv=0;
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
for(j=0; j<=i; j++)
|
|
vv+=State.m_denseh.Get(i,j);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(vv),__FUNCTION__+": DenseH contains infinite or NaN values!"))
|
|
return;
|
|
CLPQPServ::ScaleDenseQPInplace(State.m_denseh,false,nmain,State.m_c,n,State.m_scl);
|
|
State.m_targetscale=CLPQPServ::NormalizeDenseQPInplace(State.m_denseh,false,nmain,State.m_c,n);
|
|
State.m_isdiagonalh=false;
|
|
break;
|
|
case 1:
|
|
//--- check
|
|
if(!CAp::Assert(nmain==n,__FUNCTION__+": critical integrity check failed,NMain!=N"))
|
|
return;
|
|
//--- Quadratic term is stored in sparse format: either sparsify dense
|
|
//--- term or copy the sparse one
|
|
State.m_hkind=1;
|
|
State.m_sparseh.m_MatrixType=1;
|
|
State.m_sparseh.m_M=n;
|
|
State.m_sparseh.m_N=n;
|
|
switch(hkind)
|
|
{
|
|
case 0:
|
|
//--- Sparsify dense term
|
|
nnz=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
nnz++;
|
|
if(IsUpper)
|
|
{
|
|
j0=i+1;
|
|
j1=n-1;
|
|
}
|
|
else
|
|
{
|
|
j0=0;
|
|
j1=i-1;
|
|
}
|
|
for(j=j0; j<=j1; j++)
|
|
if(denseh.Get(i,j)!=0)
|
|
nnz++;
|
|
}
|
|
State.m_sparseh.m_RIdx.Resize(n+1);
|
|
State.m_sparseh.m_Idx.Resize(nnz);
|
|
State.m_sparseh.m_Vals.Resize(nnz);
|
|
State.m_sparseh.m_RIdx.Set(0,0);
|
|
offs=0;
|
|
vv=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Off-diagonal elements are copied only when nonzero
|
|
if(!IsUpper)
|
|
{
|
|
for(j=0; j<=i-1; j++)
|
|
{
|
|
if(denseh.Get(i,j)!=0)
|
|
{
|
|
v=denseh.Get(i,j);
|
|
State.m_sparseh.m_Idx.Set(offs,j);
|
|
State.m_sparseh.m_Vals.Set(offs,v);
|
|
vv+=v;
|
|
offs++;
|
|
}
|
|
}
|
|
}
|
|
//--- Diagonal element is always copied
|
|
v=denseh.Get(i,i);
|
|
State.m_sparseh.m_Idx.Set(offs,i);
|
|
State.m_sparseh.m_Vals.Set(offs,v);
|
|
vv+=v;
|
|
offs++;
|
|
//--- Off-diagonal elements are copied only when nonzero
|
|
if(IsUpper)
|
|
{
|
|
for(j=i+1; j<n; j++)
|
|
{
|
|
v=denseh.Get(i,j);
|
|
if(v!=0)
|
|
{
|
|
State.m_sparseh.m_Idx.Set(offs,j);
|
|
State.m_sparseh.m_Vals.Set(offs,v);
|
|
vv+=v;
|
|
offs++;
|
|
}
|
|
}
|
|
}
|
|
//--- Finalize row
|
|
State.m_sparseh.m_RIdx.Set(i+1,offs);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(vv),__FUNCTION__+": DenseH contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(offs==nnz,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
CSparse::SparseCreateCRSInplace(State.m_sparseh);
|
|
break;
|
|
case 1:
|
|
//--- Copy sparse quadratic term, but make sure that we have diagonal elements
|
|
//--- present (we add diagonal if it is not present)
|
|
if(!CAp::Assert(sparseh.m_MatrixType==1,__FUNCTION__+": unexpected sparse matrix format"))
|
|
return;
|
|
if(!CAp::Assert(sparseh.m_M==n,__FUNCTION__+": unexpected sparse matrix size"))
|
|
return;
|
|
if(!CAp::Assert(sparseh.m_N==n,__FUNCTION__+": unexpected sparse matrix size"))
|
|
return;
|
|
State.m_sparseh.m_RIdx.Resize(n+1);
|
|
State.m_sparseh.m_Idx.Resize(sparseh.m_RIdx[n]+n);
|
|
State.m_sparseh.m_Vals.Resize(sparseh.m_RIdx[n]+n);
|
|
State.m_sparseh.m_RIdx.Set(0,0);
|
|
offs=0;
|
|
vv=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Copy subdiagonal elements (if needed)
|
|
if(!IsUpper)
|
|
{
|
|
j0=sparseh.m_RIdx[i];
|
|
j1=sparseh.m_DIdx[i]-1;
|
|
for(k=j0; k<=j1; k++)
|
|
{
|
|
v=sparseh.m_Vals[k];
|
|
State.m_sparseh.m_Idx.Set(offs,sparseh.m_Idx[k]);
|
|
State.m_sparseh.m_Vals.Set(offs,v);
|
|
vv+=v;
|
|
offs++;
|
|
}
|
|
}
|
|
//--- Diagonal element is always copied
|
|
v=0;
|
|
if(sparseh.m_UIdx[i]!=sparseh.m_DIdx[i])
|
|
v=sparseh.m_Vals[sparseh.m_DIdx[i]];
|
|
State.m_sparseh.m_Idx.Set(offs,i);
|
|
State.m_sparseh.m_Vals.Set(offs,v);
|
|
vv+=v;
|
|
offs++;
|
|
//--- Copy superdiagonal elements (if needed)
|
|
if(IsUpper)
|
|
{
|
|
j0=sparseh.m_UIdx[i];
|
|
j1=sparseh.m_RIdx[i+1]-1;
|
|
for(k=j0; k<=j1; k++)
|
|
{
|
|
v=sparseh.m_Vals[k];
|
|
State.m_sparseh.m_Idx.Set(offs,sparseh.m_Idx[k]);
|
|
State.m_sparseh.m_Vals.Set(offs,v);
|
|
vv+=v;
|
|
offs++;
|
|
}
|
|
}
|
|
//--- Finalize row
|
|
State.m_sparseh.m_RIdx.Set(i+1,offs);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(vv),__FUNCTION__+": SparseH contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(offs<=State.m_sparseh.m_Vals.Size() && offs<=State.m_sparseh.m_Idx.Size(),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
CSparse::SparseCreateCRSInplace(State.m_sparseh);
|
|
if(IsUpper)
|
|
{
|
|
CSparse::SparseCopyTransposeCRSBuf(State.m_sparseh,State.m_tmpsparse0);
|
|
CSparse::SparseCopyBuf(State.m_tmpsparse0,State.m_sparseh);
|
|
}
|
|
break;
|
|
}
|
|
CLPQPServ::ScaleSparseQPInplace(State.m_scl,n,State.m_sparseh,State.m_c);
|
|
State.m_targetscale=CLPQPServ::NormalizeSparseQPInplace(State.m_sparseh,false,State.m_c,n);
|
|
State.m_isdiagonalh=State.m_sparseh.m_RIdx[n]==n;
|
|
break;
|
|
}
|
|
CAp::Assert(State.m_hkind>=0,__FUNCTION__+": integrity check failed");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets constraints for QP - IPM m_solver |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - instance initialized with one of the initialization|
|
|
//| functions |
|
|
//| BndL, BndU - lower and upper bound. BndL[] can be - INF, |
|
|
//| BndU[] can be + INF. |
|
|
//| SparseA - sparse constraint matrix, CRS format |
|
|
//| MSparse - number of sparse constraints |
|
|
//| DenseA - array[MDense, N], dense part of the constraints |
|
|
//| MDense - number of dense constraints |
|
|
//| CL, CU - lower and upper bounds for constraints, first |
|
|
//| MSparse are bounds for sparse part, following MDense ones are |
|
|
//| bounds for dense part, MSparse + MDense in total. |
|
|
//| - INF <= CL[I] <= CU[I] <= +INF. |
|
|
//| This function throws exception if constraints have inconsistent |
|
|
//| bounds, i.e. either BndL[I] > BndU[I] or CL[I] > CU[I]. In all |
|
|
//| other cases it succeeds. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMSetConstraints(CVIPMState &State,
|
|
CRowDouble &bndl,
|
|
CRowDouble &bndu,
|
|
CSparseMatrix &sparsea,
|
|
int msparse,
|
|
CMatrixDouble &densea,
|
|
int mdense,
|
|
CRowDouble &cl,
|
|
CRowDouble &cu)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int nmain=State.m_nmain;
|
|
int nslack=n-nmain;
|
|
int m=0;
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int k=0;
|
|
int offsmain=0;
|
|
int offscombined=0;
|
|
double vs=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(mdense>=0,__FUNCTION__+": MDense<0"))
|
|
return;
|
|
if(!CAp::Assert(msparse>=0,__FUNCTION__+": MSparse<0"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(densea,mdense,n),__FUNCTION__+": DenseA contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(msparse==0 || sparsea.m_MatrixType==1,__FUNCTION__+": non-CRS constraint matrix!"))
|
|
return;
|
|
if(!CAp::Assert(msparse==0 || (sparsea.m_M==msparse && sparsea.m_N==n),__FUNCTION__+": constraint matrix has incorrect size"))
|
|
return;
|
|
if(!CAp::Assert(cl.Size()>=mdense+msparse,__FUNCTION__+": CL is too short!"))
|
|
return;
|
|
if(!CAp::Assert(cu.Size()>=mdense+msparse,__FUNCTION__+": CU is too short!"))
|
|
return;
|
|
//--- Reset factorization flag
|
|
State.m_factorizationpresent=false;
|
|
State.m_factorizationpoweredup=false;
|
|
//--- Box constraints
|
|
State.m_bndl.Resize(n);
|
|
State.m_bndu.Resize(n);
|
|
State.m_rawbndl.Resize(n);
|
|
State.m_rawbndu.Resize(n);
|
|
ArrayResize(State.m_HasBndL,n);
|
|
ArrayResize(State.m_HasBndU,n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
State.m_HasBndL[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_HasBndU[i]=MathIsValidNumber(bndu[i]);
|
|
//--- check
|
|
if(!CAp::Assert(!(State.m_HasBndL[i] && State.m_HasBndU[i] && bndl[i]>bndu[i]),__FUNCTION__+": inconsistent range for box constraints"))
|
|
return;
|
|
State.m_bndl.Set(i,bndl[i]);
|
|
State.m_bndu.Set(i,bndu[i]);
|
|
State.m_rawbndl.Set(i,bndl[i]);
|
|
State.m_rawbndu.Set(i,bndu[i]);
|
|
}
|
|
CLPQPServ::ScaleShiftBCInplace(State.m_scl,State.m_xorigin,State.m_bndl,State.m_bndu,n);
|
|
//--- Linear constraints (full matrices)
|
|
m=mdense+msparse;
|
|
State.m_b.Resize(m);
|
|
State.m_r.Resize(m);
|
|
State.m_ascales.Resize(m);
|
|
ArrayResize(State.m_aflips,m);
|
|
ArrayResize(State.m_hasr,m);
|
|
if(msparse>0)
|
|
CSparse::SparseCopyToCRSBuf(sparsea,State.m_sparseafull);
|
|
if(mdense>0)
|
|
State.m_denseafull=densea;
|
|
State.m_denseafull.Resize(mdense,n);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(cl[i]) || AL_NEGINF==cl[i],__FUNCTION__+": CL is not finite number or -INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(cu[i]) || AL_POSINF==cu[i],__FUNCTION__+": CU is not finite number or +INF"))
|
|
return;
|
|
//--- Store range
|
|
if(MathIsValidNumber(cl[i]) || MathIsValidNumber(cu[i]))
|
|
{
|
|
//--- Non-degenerate constraint, at least one of bounds is present
|
|
if(MathIsValidNumber(cl[i]))
|
|
{
|
|
if(!CAp::Assert(!MathIsValidNumber(cu[i]) || cu[i]>=cl[i],__FUNCTION__+": inconsistent range (right-hand side) for linear constraint"))
|
|
return;
|
|
if(MathIsValidNumber(cu[i]))
|
|
{
|
|
//--- We have both CL and CU, i.e. CL <= A*x <= CU.
|
|
//--- It can be either equality constraint (no slacks) or range constraint
|
|
//--- (two pairs of slacks variables).
|
|
//--- Try to arrange things in such a way that |CU|>=|CL| (it can be done
|
|
//--- by multiplication by -1 and boundaries swap).
|
|
//--- Having |CU|>=|CL| will allow us to drop huge irrelevant bound CU,
|
|
//--- if we find it irrelevant during computations. Due to limitations
|
|
//--- of our slack variable substitution, it can be done only for CU.
|
|
if(MathAbs(cu[i])>=MathAbs(cl[i]))
|
|
{
|
|
State.m_b.Set(i,cl[i]);
|
|
State.m_r.Set(i,cu[i]-cl[i]);
|
|
State.m_hasr[i]=true;
|
|
State.m_aflips[i]=false;
|
|
vs=1;
|
|
}
|
|
else
|
|
{
|
|
State.m_b.Set(i,-cu[i]);
|
|
State.m_r.Set(i,cu[i]-cl[i]);
|
|
State.m_hasr[i]=true;
|
|
State.m_aflips[i]=true;
|
|
vs=-1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Only lower bound: CL <= A*x.
|
|
//--- One pair of slack variables added.
|
|
State.m_b.Set(i,cl[i]);
|
|
State.m_r.Set(i,AL_POSINF);
|
|
State.m_hasr[i]=false;
|
|
State.m_aflips[i]=false;
|
|
vs=1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Only upper bound: A*x <= CU
|
|
//--- One pair of slack variables added.
|
|
State.m_b.Set(i,-cu[i]);
|
|
State.m_r.Set(i,AL_POSINF);
|
|
State.m_hasr[i]=false;
|
|
State.m_aflips[i]=true;
|
|
vs=-1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Degenerate constraint -inf <= Ax <= +inf.
|
|
//--- Generate dummy formulation.
|
|
State.m_b.Set(i,-1);
|
|
State.m_r.Set(i,2);
|
|
State.m_hasr[i]=true;
|
|
State.m_aflips[i]=false;
|
|
vs=0;
|
|
}
|
|
//--- Store matrix row and its scaling coefficient
|
|
if(i<msparse)
|
|
{
|
|
j0=State.m_sparseafull.m_RIdx[i];
|
|
j1=State.m_sparseafull.m_RIdx[i+1];
|
|
for(j=j0; j<j1; j++)
|
|
State.m_sparseafull.m_Vals.Mul(j,vs);
|
|
}
|
|
else
|
|
State.m_denseafull.Row(i-msparse,State.m_denseafull[i-msparse]*vs);
|
|
State.m_ascales.Set(i,vs);
|
|
}
|
|
CLPQPServ::ScaleShiftMixedBRLCInplace(State.m_scl,State.m_xorigin,n,State.m_sparseafull,msparse,State.m_denseafull,mdense,State.m_b,State.m_r);
|
|
CLPQPServ::NormalizeMixedBRLCInplace(State.m_sparseafull,msparse,State.m_denseafull,mdense,State.m_b,State.m_r,n,true,State.m_tmp0,true);
|
|
State.m_ascales*=State.m_tmp0;
|
|
State.m_mdense=mdense;
|
|
State.m_msparse=msparse;
|
|
//--- Separate main and slack parts of the constraint matrices
|
|
State.m_tmpi.Resize(nslack);
|
|
State.m_tmpi.Fill(0);
|
|
State.m_combinedaslack.m_M=mdense+msparse;
|
|
State.m_combinedaslack.m_N=nslack;
|
|
State.m_combinedaslack.m_RIdx.Resize(mdense+msparse+1);
|
|
State.m_combinedaslack.m_Idx.Resize(nslack);
|
|
State.m_combinedaslack.m_Vals.Resize(nslack);
|
|
State.m_combinedaslack.m_RIdx.Set(0,0);
|
|
State.m_sparseamain.m_M=msparse;
|
|
State.m_sparseamain.m_N=nmain;
|
|
if(msparse>0)
|
|
{
|
|
State.m_sparseamain.m_RIdx.Resize(msparse+1);
|
|
State.m_sparseamain.m_Idx.Resize(sparsea.m_RIdx[msparse]);
|
|
State.m_sparseamain.m_Vals.Resize(sparsea.m_RIdx[msparse]);
|
|
State.m_sparseamain.m_RIdx.Set(0,0);
|
|
for(i=0; i<msparse; i++)
|
|
{
|
|
offsmain=State.m_sparseamain.m_RIdx[i];
|
|
offscombined=State.m_combinedaslack.m_RIdx[i];
|
|
j0=State.m_sparseafull.m_RIdx[i];
|
|
j1=State.m_sparseafull.m_RIdx[i+1];
|
|
for(j=j0; j<j1; j++)
|
|
{
|
|
v=State.m_sparseafull.m_Vals[j];
|
|
k=State.m_sparseafull.m_Idx[j];
|
|
if(k<nmain)
|
|
{
|
|
State.m_sparseamain.m_Idx.Set(offsmain,k);
|
|
State.m_sparseamain.m_Vals.Set(offsmain,v);
|
|
offsmain++;
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_tmpi[k-nmain]==0,__FUNCTION__+": slack column contains more than one nonzero element"))
|
|
return;
|
|
State.m_combinedaslack.m_Idx.Set(offscombined,k-nmain);
|
|
State.m_combinedaslack.m_Vals.Set(offscombined,v);
|
|
State.m_tmpi.Add(k-nmain,1);
|
|
offscombined++;
|
|
}
|
|
}
|
|
State.m_sparseamain.m_RIdx.Set(i+1,offsmain);
|
|
State.m_combinedaslack.m_RIdx.Set(i+1,offscombined);
|
|
}
|
|
}
|
|
CSparse::SparseCreateCRSInplace(State.m_sparseamain);
|
|
if(mdense>0)
|
|
{
|
|
State.m_denseamain=State.m_denseafull;
|
|
State.m_denseamain.Resize(mdense,nmain);
|
|
for(i=0; i<mdense; i++)
|
|
{
|
|
offscombined=State.m_combinedaslack.m_RIdx[msparse+i];
|
|
for(k=nmain; k<n; k++)
|
|
{
|
|
if(State.m_denseafull.Get(i,k)!=0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_tmpi[k-nmain]==0,__FUNCTION__+": slack column contains more than one nonzero element"))
|
|
return;
|
|
State.m_combinedaslack.m_Idx.Set(offscombined,k-nmain);
|
|
State.m_combinedaslack.m_Vals.Set(offscombined,State.m_denseafull.Get(i,k));
|
|
State.m_tmpi.Add(k-nmain,1);
|
|
offscombined++;
|
|
}
|
|
}
|
|
State.m_combinedaslack.m_RIdx.Set(msparse+i+1,offscombined);
|
|
}
|
|
}
|
|
CSparse::SparseCreateCRSInplace(State.m_combinedaslack);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets stopping criteria for QP - IPM m_solver. |
|
|
//| You can set all epsilon-values to one small value, about 1.0E-6. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - instance initialized with one of the |
|
|
//| initialization functions |
|
|
//| EpsP - maximum primal error allowed in the solution, |
|
|
//| EpsP >= 0. Zero will be automatically replaced |
|
|
//| by recommended default value, which is equal |
|
|
//| to 10 * Sqrt(Epsilon) in the current version |
|
|
//| EpsD - maximum dual error allowed in the solution, |
|
|
//| EpsP >= 0. Zero will be automatically replaced |
|
|
//| by recommended default value, which is equal |
|
|
//| to 10 * Sqrt(Epsilon) in the current version |
|
|
//| EpsGap - maximum duality gap allowed in the solution, |
|
|
//| EpsP >= 0. Zero will be automatically replaced |
|
|
//| by recommended default value, which is equal |
|
|
//| to 10 * Sqrt(Epsilon) in the current version |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMSetCond(CVIPMState &State,
|
|
double epsp,
|
|
double epsd,
|
|
double epsgap)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(epsp) && epsp>=0.0,__FUNCTION__+": EpsP is infinite or negative"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsd) && epsd>=0.0,__FUNCTION__+": EpsD is infinite or negative"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsgap) && epsgap>=0.0,__FUNCTION__+": EpsP is infinite or negative"))
|
|
return;
|
|
|
|
double sml=MathSqrt(CMath::m_machineepsilon);
|
|
State.m_epsp=CApServ::Coalesce(epsp,sml);
|
|
State.m_epsd=CApServ::Coalesce(epsd,sml);
|
|
State.m_epsgap=CApServ::Coalesce(epsgap,sml);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Solve QP problem. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - solver instance |
|
|
//| DropBigBounds - If True, algorithm may drop box and linear |
|
|
//| constraints with huge bound values that destabilize|
|
|
//| algorithm. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XS - array[N], solution |
|
|
//| LagBC - array[N], Lagrange multipliers for box constraints |
|
|
//| LagLC - array[M], Lagrange multipliers for linear |
|
|
//| constraints |
|
|
//| TerminationType - completion code, positive values for success,|
|
|
//| negative for failures(XS constrains best point |
|
|
//| found so far): |
|
|
//| * -2 the task is either unbounded or infeasible; |
|
|
//| the IPM solver has difficulty distinguishing |
|
|
//| between these two. |
|
|
//| * +1 stopping criteria are met |
|
|
//| * +7 stopping criteria are too stringent |
|
|
//| RESULT: |
|
|
//| This function ALWAYS returns something meaningful in XS, LagBC,|
|
|
//| LagLC - either solution or the best point so far, even for |
|
|
//| negative TerminationType. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMOptimize(CVIPMState &State,
|
|
bool dropbigbounds,
|
|
CRowDouble &xs,
|
|
CRowDouble &lagbc,
|
|
CRowDouble &laglc,
|
|
int &terminationtype)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int i=0;
|
|
int iteridx=0;
|
|
double mu=0;
|
|
double muaff=0;
|
|
double sigma=0;
|
|
double alphaaffp=0;
|
|
double alphaaffd=0;
|
|
double alphap=0;
|
|
double alphad=0;
|
|
int primalstagnationcnt=0;
|
|
int dualstagnationcnt=0;
|
|
double regeps=0;
|
|
double dampeps=0;
|
|
double safedampeps=0;
|
|
double modeps=0;
|
|
double maxdampeps=0;
|
|
double regfree=0;
|
|
double dampfree=0;
|
|
int droppedbounds=0;
|
|
double primalxscale=0;
|
|
double errp2=0;
|
|
double errd2=0;
|
|
double errpinf=0;
|
|
double errdinf=0;
|
|
double preverrp2=0;
|
|
double preverrd2=0;
|
|
double errgap=0;
|
|
double eprimal=0;
|
|
double edual=0;
|
|
double egap=0;
|
|
double mumin=0;
|
|
double mustop=0;
|
|
double y0nrm=0;
|
|
double bady=0;
|
|
double mxprimal=0;
|
|
double mxdeltaprimal=0;
|
|
int bestiteridx=0;
|
|
double besterr=0;
|
|
double bestegap=0;
|
|
double besteprimal=0;
|
|
double bestedual=0;
|
|
bool loadbest=false;
|
|
|
|
terminationtype=0;
|
|
State.m_dotrace=CAp::IsTraceEnabled("IPM");
|
|
State.m_dodetailedtrace=State.m_dotrace && CAp::IsTraceEnabled("IPM.DETAILED");
|
|
//--- Prepare outputs
|
|
xs=vector<double>::Zeros(n);
|
|
lagbc=vector<double>::Zeros(n);
|
|
laglc=vector<double>::Zeros(m);
|
|
//--- Some integrity checks:
|
|
//--- * we need PrimalStagnationLen<DualStagnationLen in order to be able to correctly
|
|
//--- detect infeasible instances (stagnated dual error is present in both infeasible
|
|
//--- and unbounded instances, so we should check for primal stagnation a few iters
|
|
//--- before checking for dual stagnation)
|
|
if(!CAp::Assert(m_primalstagnationlen<m_dualstagnationlen,__FUNCTION__+": critical integrity failure - incorrect configuration parameters"))
|
|
return;
|
|
//--- Trace output (if needed)
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("\n\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
CAp::Trace("//--- IPM SOLVER STARTED //\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
}
|
|
//--- Prepare regularization coefficients:
|
|
//--- * RegEps - one that is applied to initial (5N+5M)x(5N+5M) KKT system. This one has to be
|
|
//--- small because it perturbs solution returned by the algorithm. Essential in order to
|
|
//--- avoid stalling at extremely large points.
|
|
//--- * ModEps - small modification applied to LDLT decomposition in order to preserve sign
|
|
//--- of diagonal elements
|
|
//--- * DampEps - damping coefficient for damped Newton step. Comes along with SafeDampEps
|
|
//--- (threshold value when some safeguards are turned off in order to preserve convergence
|
|
//--- speed) and MaxDampEps - threshold value when we consider problem overregularized and stop.
|
|
//--- * DampFree - additional damping coefficient for free variables
|
|
regfree=MathPow(CMath::m_machineepsilon,0.75);
|
|
dampfree=0;
|
|
regeps=100*CMath::m_machineepsilon;
|
|
modeps=(100+MathSqrt(n))*CMath::m_machineepsilon;
|
|
dampeps=(100+MathSqrt(n))*CMath::m_machineepsilon;
|
|
safedampeps=MathSqrt(CMath::m_machineepsilon);
|
|
maxdampeps=MathSqrt(MathSqrt(CMath::m_machineepsilon));
|
|
//--- Set up initial State
|
|
State.m_repiterationscount=0;
|
|
State.m_repncholesky=0;
|
|
mustop=(100+MathSqrt(n))*CMath::m_machineepsilon;
|
|
mumin=0.01*mustop;
|
|
VIPMPowerUp(State,regfree);
|
|
VARSInitFrom(State.m_best,State.m_current);
|
|
VARSInitByZero(State.m_zerovars,n,m);
|
|
VARSInitByZero(State.m_deltaaff,n,m);
|
|
VARSInitByZero(State.m_deltacorr,n,m);
|
|
bestiteridx=-1;
|
|
besterr=CMath::m_maxrealnumber;
|
|
bestegap=CMath::m_maxrealnumber;
|
|
besteprimal=CMath::m_maxrealnumber;
|
|
bestedual=CMath::m_maxrealnumber;
|
|
TraceProgress(State,0.0,0.0,0.0,0.0,0.0);
|
|
y0nrm=0;
|
|
y0nrm=MathMax(y0nrm,CAblasF::RMaxAbsV(m,State.m_current.m_y));
|
|
y0nrm=MathMax(y0nrm,CAblasF::RMaxAbsV(m,State.m_current.m_v));
|
|
y0nrm=MathMax(y0nrm,CAblasF::RMaxAbsV(m,State.m_current.m_q));
|
|
y0nrm=MathMax(y0nrm,CAblasF::RMaxAbsV(n,State.m_current.m_z));
|
|
y0nrm=MathMax(y0nrm,CAblasF::RMaxAbsV(n,State.m_current.m_s));
|
|
//--- Start iteration
|
|
loadbest=true;
|
|
primalstagnationcnt=0;
|
|
dualstagnationcnt=0;
|
|
terminationtype=7;
|
|
errp2=CMath::m_maxrealnumber;
|
|
errd2=CMath::m_maxrealnumber;
|
|
for(iteridx=0; iteridx<m_maxipmits; iteridx++)
|
|
{
|
|
//--- Trace beginning
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("=== PREDICTOR-CORRECTOR STEP %2d ====================================================================\n",iteridx));
|
|
//--- Check regularization status, terminate if overregularized
|
|
if(dampeps>=maxdampeps)
|
|
{
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> tried to increase regularization parameter,but it is too large\n");
|
|
CAp::Trace("> it is likely that stopping conditions are too stringent,stopping at the best point found so far\n");
|
|
}
|
|
terminationtype=7;
|
|
break;
|
|
}
|
|
//--- Precompute factorization
|
|
//--- NOTE: we use "m_solver" regularization coefficient at this moment
|
|
if(!VIPMPrecomputeNewtonFactorization(State,State.m_current,regeps,modeps,dampeps,dampfree))
|
|
{
|
|
//--- KKT factorization failed.
|
|
//--- Increase regularization parameter and skip this iteration.
|
|
dampeps*=10;
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> LDLT factorization failed due to rounding errors\n");
|
|
CAp::Trace(StringFormat("> increasing damping coefficient to %.2E,skipping iteration\n",dampeps));
|
|
}
|
|
continue;
|
|
}
|
|
//--- Compute Mu
|
|
mu=VARSComputeMu(State,State.m_current);
|
|
//--- Compute affine scaling step for Mehrotra's predictor-corrector algorithm
|
|
if(!VIPMComputeStepDirection(State,State.m_current,0.0,State.m_zerovars,State.m_deltaaff,regeps,dampeps>=safedampeps))
|
|
{
|
|
//--- Affine scaling step failed due to numerical errors.
|
|
//--- Increase regularization parameter and skip this iteration.
|
|
dampeps*=10;
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> affine scaling step failed to decrease residual due to rounding errors\n");
|
|
CAp::Trace(StringFormat("> increasing damping coefficient to %.2E,skipping iteration\n",dampeps));
|
|
}
|
|
continue;
|
|
}
|
|
VIPMComputeStepLength(State,State.m_current,State.m_deltaaff,m_steplengthdecay,alphaaffp,alphaaffd);
|
|
//--- Compute MuAff and centering parameter
|
|
VARSInitFrom(State.m_trial,State.m_current);
|
|
VarsAddStep(State.m_trial,State.m_deltaaff,alphaaffp,alphaaffd);
|
|
muaff=VARSComputeMu(State,State.m_trial);
|
|
sigma=MathMin(MathPow((muaff+mumin)/(mu+mumin),3),1.0);
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(sigma) && sigma<=1.0,__FUNCTION__+": critical integrity check failed for Sigma (infinite or greater than 1)"))
|
|
return;
|
|
//--- Compute corrector step
|
|
if(!VIPMComputeStepDirection(State,State.m_current,sigma*mu+mumin,State.m_deltaaff,State.m_deltacorr,regeps,dampeps>=safedampeps))
|
|
{
|
|
//--- Affine scaling step failed due to numerical errors.
|
|
//--- Increase regularization parameter and skip this iteration.
|
|
dampeps*=10;
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> corrector step failed to decrease residual due to rounding errors\n");
|
|
CAp::Trace(StringFormat("> increasing damping coefficient to %.2E,skipping iteration\n",dampeps));
|
|
}
|
|
continue;
|
|
}
|
|
VIPMComputeStepLength(State,State.m_current,State.m_deltacorr,m_steplengthdecay,alphap,alphad);
|
|
if((double)(iteridx)>=m_minitersbeforesafeguards && (alphap<=m_badsteplength || alphad<=m_badsteplength))
|
|
{
|
|
//--- Affine scaling step failed due to numerical errors.
|
|
//--- Increase regularization parameter and skip this iteration.
|
|
dampeps*=10;
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> step length is too short,suspecting rounding errors\n");
|
|
CAp::Trace(StringFormat("> increasing damping coefficient to %.2E,skipping iteration\n",dampeps));
|
|
}
|
|
continue;
|
|
}
|
|
//--- Perform a step
|
|
RunIntegrityChecks(State,State.m_current,State.m_deltacorr,alphap,alphad);
|
|
VIPMPerformStep(State,alphap,alphad);
|
|
TraceProgress(State,mu,muaff,sigma,alphap,alphad);
|
|
//--- Check for excessive bounds (one that are so large that they are both irrelevant
|
|
//--- and destabilizing due to their magnitude)
|
|
if(dropbigbounds && iteridx>=m_minitersbeforedroppingbounds)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert((10.0*m_bigconstrmag)<=(1.0/m_bigconstrxtol),__FUNCTION__+": integrity check failed (incorrect BigConstr Settings)"))
|
|
return;
|
|
droppedbounds=0;
|
|
//--- Determine variable and step scales.
|
|
//--- Both quantities are bounded from below by 1.0
|
|
mxprimal=1.0;
|
|
mxprimal=MathMax(mxprimal,CAblasF::RMaxAbsV(n,State.m_current.m_x));
|
|
mxprimal=MathMax(mxprimal,CAblasF::RMaxAbsV(n,State.m_current.m_g));
|
|
mxprimal=MathMax(mxprimal,CAblasF::RMaxAbsV(n,State.m_current.m_t));
|
|
mxprimal=MathMax(mxprimal,CAblasF::RMaxAbsV(m,State.m_current.m_w));
|
|
mxprimal=MathMax(mxprimal,CAblasF::RMaxAbsV(m,State.m_current.m_p));
|
|
mxdeltaprimal=1.0;
|
|
mxdeltaprimal=MathMax(mxdeltaprimal,alphap*CAblasF::RMaxAbsV(n,State.m_deltacorr.m_x));
|
|
mxdeltaprimal=MathMax(mxdeltaprimal,alphap*CAblasF::RMaxAbsV(n,State.m_deltacorr.m_g));
|
|
mxdeltaprimal=MathMax(mxdeltaprimal,alphap*CAblasF::RMaxAbsV(n,State.m_deltacorr.m_t));
|
|
mxdeltaprimal=MathMax(mxdeltaprimal,alphap*CAblasF::RMaxAbsV(m,State.m_deltacorr.m_w));
|
|
mxdeltaprimal=MathMax(mxdeltaprimal,alphap*CAblasF::RMaxAbsV(m,State.m_deltacorr.m_p));
|
|
//--- If changes in primal variables are small enough, try dropping too large bounds
|
|
if(mxdeltaprimal<(mxprimal*m_bigconstrxtol))
|
|
{
|
|
//--- Drop irrelevant box constraints
|
|
primalxscale=1.0;
|
|
primalxscale=MathMax(primalxscale,CAblasF::RMaxAbsV(n,State.m_current.m_x));
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if((State.m_HasBndL[i] && State.m_hasgz[i]) && MathAbs(State.m_bndl[i])>(m_bigconstrmag*primalxscale))
|
|
{
|
|
State.m_hasgz[i]=false;
|
|
State.m_current.m_g.Set(i,0);
|
|
State.m_current.m_z.Set(i,0);
|
|
State.m_cntgz--;
|
|
droppedbounds++;
|
|
}
|
|
if((State.m_HasBndU[i] && State.m_hasts[i]) && MathAbs(State.m_bndu[i])>(m_bigconstrmag*primalxscale))
|
|
{
|
|
State.m_hasts[i]=false;
|
|
State.m_current.m_t.Set(i,0);
|
|
State.m_current.m_s.Set(i,0);
|
|
State.m_cntts--;
|
|
droppedbounds++;
|
|
}
|
|
}
|
|
//--- Drop irrelevant linear constraints. Due to specifics of the m_solver
|
|
//--- we can drop only right part part of b<=Ax<=b+r.
|
|
//--- We can't drop b<=A from b<=A<=b+r because it impossible with our choice of
|
|
//--- slack variables. Usually we do not need to do so because we reorder constraints
|
|
//--- during initialization in such a way that |b+r|>|b| and because typical
|
|
//--- applications do not have excessively large lower AND upper bound (user may
|
|
//--- specify large value for 'absent' bound, but usually he does not mark both bounds as absent).
|
|
MultiplyGEAX(State,1.0,State.m_current.m_x,0,0.0,State.m_tmpax,0);
|
|
primalxscale=1.0;
|
|
primalxscale=MathMax(primalxscale,CAblasF::RMaxAbsV(n,State.m_current.m_x));
|
|
primalxscale=MathMax(primalxscale,CAblasF::RMaxAbsV(m,State.m_tmpax));
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(State.m_hasr[i] && State.m_haspq[i] && MathAbs(State.m_b[i]+State.m_r[i])>(m_bigconstrmag*primalxscale) && MathAbs(State.m_b[i])<(m_bigconstrmag*primalxscale))
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_haswv[i] && State.m_haspq[i],__FUNCTION__+": unexpected integrity check failure (4y64)"))
|
|
return;
|
|
State.m_haspq[i]=false;
|
|
State.m_current.m_p.Set(i,0);
|
|
State.m_current.m_q.Set(i,0);
|
|
State.m_cntpq--;
|
|
droppedbounds++;
|
|
}
|
|
}
|
|
//--- Trace output
|
|
if(droppedbounds>0)
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("[NOTICE] detected %d irrelevant constraints with huge bounds,X converged to values well below them,dropping...\n",droppedbounds));
|
|
}
|
|
}
|
|
//--- Check stopping criteria
|
|
//--- * primal and dual stagnation are checked only when following criteria are met:
|
|
//--- 1) Mu is smaller than 1 (we already converged close enough)
|
|
//--- 2) we performed more than MinItersBeforeStagnation iterations
|
|
preverrp2=errp2;
|
|
preverrd2=errd2;
|
|
ComputeErrors(State,errp2,errd2,errpinf,errdinf,errgap);
|
|
mu=VARSComputeMu(State,State.m_current);
|
|
egap=errgap;
|
|
eprimal=errpinf;
|
|
edual=errdinf;
|
|
if(MathMax(egap,MathMax(eprimal,edual))<besterr)
|
|
{
|
|
//--- Save best point found so far
|
|
VARSInitFrom(State.m_best,State.m_current);
|
|
bestiteridx=iteridx;
|
|
besterr=MathMax(egap,MathMax(eprimal,edual));
|
|
bestegap=egap;
|
|
besteprimal=eprimal;
|
|
bestedual=edual;
|
|
}
|
|
if(bestiteridx>0 && iteridx>bestiteridx+m_minitersbeforeeworststagnation)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("> worst of primal/dual/gap errors stagnated for %d its,stopping at the best point found so far\n",m_minitersbeforeeworststagnation));
|
|
break;
|
|
}
|
|
if(egap<=State.m_epsgap && errp2>=(m_stagnationdelta*preverrp2) && errpinf>=m_primalinfeasible1 && iteridx>=m_minitersbeforestagnation)
|
|
{
|
|
primalstagnationcnt++;
|
|
if(primalstagnationcnt>=m_primalstagnationlen)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("> primal error stagnated for %d its,stopping at the best point found so far\n",m_primalstagnationlen));
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
primalstagnationcnt=0;
|
|
if(egap<=State.m_epsgap && errd2>=(m_stagnationdelta*preverrd2) && errdinf>=m_dualinfeasible1 && iteridx>=m_minitersbeforestagnation)
|
|
{
|
|
dualstagnationcnt++;
|
|
if(dualstagnationcnt>=m_dualstagnationlen)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("> dual error stagnated for %d its,stopping at the best point found so far\n",m_dualstagnationlen));
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
dualstagnationcnt=0;
|
|
if(mu<=mustop && iteridx>=m_itersfortoostringentcond)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> stopping conditions are too stringent,stopping at the best point found so far\n");
|
|
terminationtype=7;
|
|
break;
|
|
}
|
|
if(egap<=State.m_epsgap && eprimal<=State.m_epsp && edual<=State.m_epsd)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> stopping criteria are met\n");
|
|
terminationtype=1;
|
|
loadbest=false;
|
|
break;
|
|
}
|
|
bady=m_bigy;
|
|
bady=MathMax(bady,m_ygrowth*y0nrm);
|
|
bady=MathMax(bady,m_ygrowth*CAblasF::RMaxAbsV(n,State.m_current.m_x));
|
|
bady=MathMax(bady,m_ygrowth*CAblasF::RMaxAbsV(n,State.m_current.m_g));
|
|
bady=MathMax(bady,m_ygrowth*CAblasF::RMaxAbsV(n,State.m_current.m_t));
|
|
bady=MathMax(bady,m_ygrowth*CAblasF::RMaxAbsV(m,State.m_current.m_w));
|
|
bady=MathMax(bady,m_ygrowth*CAblasF::RMaxAbsV(m,State.m_current.m_p));
|
|
if(CAblasF::RMaxAbsV(m,State.m_current.m_y)>=bady && iteridx>=m_minitersbeforeinfeasible)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("> |Y| increased beyond %.1E,stopping at the best point found so far\n",bady));
|
|
break;
|
|
}
|
|
}
|
|
//--- Load best point, perform some checks
|
|
if(loadbest)
|
|
{
|
|
//--- Load best point
|
|
//--- NOTE: TouchReal() is used to avoid spurious compiler warnings about 'set but unused'
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("> the best point so far is one from iteration %d\n",bestiteridx));
|
|
VARSInitFrom(State.m_current,State.m_best);
|
|
//--- If no error flags were set yet, check solution quality
|
|
bady=m_bigy;
|
|
bady=MathMax(bady,m_ygrowth*y0nrm);
|
|
bady=MathMax(bady,m_ygrowth*CAblasF::RMaxAbsV(n,State.m_current.m_x));
|
|
bady=MathMax(bady,m_ygrowth*CAblasF::RMaxAbsV(n,State.m_current.m_g));
|
|
bady=MathMax(bady,m_ygrowth*CAblasF::RMaxAbsV(n,State.m_current.m_t));
|
|
bady=MathMax(bady,m_ygrowth*CAblasF::RMaxAbsV(m,State.m_current.m_w));
|
|
bady=MathMax(bady,m_ygrowth*CAblasF::RMaxAbsV(m,State.m_current.m_p));
|
|
if(terminationtype>0 && CAblasF::RMaxAbsV(m,State.m_current.m_y)>=bady)
|
|
{
|
|
terminationtype=-2;
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("> |Y| increased beyond %.1E,declaring infeasibility/unboundedness\n",bady));
|
|
}
|
|
if(terminationtype>0 && besteprimal>=m_primalinfeasible1)
|
|
{
|
|
terminationtype=-2;
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> primal error at the best point is too high,declaring infeasibility/unboundedness\n");
|
|
}
|
|
if(terminationtype>0 && bestedual>=m_dualinfeasible1)
|
|
{
|
|
terminationtype=-2;
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> dual error at the best point is too high,declaring infeasibility/unboundedness\n");
|
|
}
|
|
}
|
|
//--- Output
|
|
MultiplyHX(State,State.m_current.m_x,State.m_tmp0);
|
|
CAblasF::RAddV(n,1.0,State.m_c,State.m_tmp0);
|
|
MultiplyGEATX(State,-1.0,State.m_current.m_y,0,1.0,State.m_tmp0,0);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_isfrozen[i])
|
|
{
|
|
//--- I-th variable is frozen, use its frozen value.
|
|
//--- By the definition, I-th Lagrangian multiplier is an I-th component of Lagrangian gradient
|
|
xs.Set(i,State.m_current.m_x[i]);
|
|
lagbc.Set(i,-State.m_tmp0[i]);
|
|
}
|
|
else
|
|
{
|
|
xs.Set(i,State.m_current.m_x[i]);
|
|
lagbc.Set(i,0.0);
|
|
if(State.m_hasgz[i])
|
|
lagbc.Add(i,- State.m_current.m_z[i]);
|
|
if(State.m_hasts[i])
|
|
lagbc.Add(i,State.m_current.m_s[i]);
|
|
}
|
|
}
|
|
laglc=State.m_current.m_y*(-1.0)+0;
|
|
//--- Unscale point and Lagrange multipliers
|
|
CLPQPServ::UnscaleUnshiftPointBC(State.m_scl,State.m_xorigin,State.m_rawbndl,State.m_rawbndu,State.m_bndl,State.m_bndu,State.m_HasBndL,State.m_HasBndU,xs,n);
|
|
lagbc*=State.m_scl.Pow(-1.0)*State.m_targetscale;
|
|
for(i=0; i<m; i++)
|
|
laglc.Mul(i,State.m_targetscale/CApServ::Coalesce(State.m_ascales[i],1.0));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Allocates place for variables of IPM and fills by zeros. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VARSInitByZero(CVIPMVars &vstate,
|
|
int n,
|
|
int m)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(m>=0,__FUNCTION__+": M<0"))
|
|
return;
|
|
vstate.m_n=n;
|
|
vstate.m_m=m;
|
|
vstate.m_x=vector<double>::Zeros(n);
|
|
vstate.m_g=vector<double>::Zeros(n);
|
|
vstate.m_t=vector<double>::Zeros(n);
|
|
vstate.m_z=vector<double>::Zeros(n);
|
|
vstate.m_s=vector<double>::Zeros(n);
|
|
vstate.m_y=vector<double>::Zeros(m);
|
|
vstate.m_w=vector<double>::Zeros(m);
|
|
vstate.m_p=vector<double>::Zeros(m);
|
|
vstate.m_v=vector<double>::Zeros(m);
|
|
vstate.m_q=vector<double>::Zeros(m);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Allocates place for variables of IPM and fills them by values of |
|
|
//| the source |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VARSInitFrom(CVIPMVars &vstate,
|
|
CVIPMVars &vsrc)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(vsrc.m_n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(vsrc.m_m>=0,__FUNCTION__+": M<0"))
|
|
return;
|
|
//--- copy
|
|
vstate=vsrc;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Adds to variables direction vector times step length. Different |
|
|
//| lengths are used for primal and dual steps. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VarsAddStep(CVIPMVars &vstate,
|
|
CVIPMVars &vdir,
|
|
double stpp,
|
|
double stpd)
|
|
{
|
|
//--- create variables
|
|
int n=vstate.m_n;
|
|
int m=vstate.m_m;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(m>=0,__FUNCTION__+": M<0"))
|
|
return;
|
|
if(!CAp::Assert(n==vdir.m_n,__FUNCTION__+": sizes mismatch"))
|
|
return;
|
|
if(!CAp::Assert(m==vdir.m_m,__FUNCTION__+": sizes mismatch"))
|
|
return;
|
|
|
|
vstate.m_x+=vdir.m_x*stpp+0;
|
|
vstate.m_g+=vdir.m_g*stpp+0;
|
|
vstate.m_t+=vdir.m_t*stpp+0;
|
|
vstate.m_z+=vdir.m_z*stpd+0;
|
|
vstate.m_s+=vdir.m_s*stpd+0;
|
|
vstate.m_w+=vdir.m_w*stpp+0;
|
|
vstate.m_p+=vdir.m_p*stpp+0;
|
|
vstate.m_y+=vdir.m_y*stpd+0;
|
|
vstate.m_v+=vdir.m_v*stpd+0;
|
|
vstate.m_q+=vdir.m_q*stpd+0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes complementarity gap |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::VarsComputeComplementarityGap(CVIPMVars &vstate)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int n=vstate.m_n;
|
|
int m=vstate.m_m;
|
|
|
|
result=CAblasF::RDotV(n,vstate.m_z,vstate.m_g)+CAblasF::RDotV(n,vstate.m_s,vstate.m_t);
|
|
result+=CAblasF::RDotV(m,vstate.m_v,vstate.m_w)+CAblasF::RDotV(m,vstate.m_p,vstate.m_q);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes empirical value of the barrier parameter Mu |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::VARSComputeMu(CVIPMState &State,CVIPMVars &vstate)
|
|
{
|
|
//--- create variable
|
|
double result=0;
|
|
|
|
result=CAblasF::RDotV(vstate.m_n,vstate.m_z,vstate.m_g)+CAblasF::RDotV(vstate.m_n,vstate.m_s,vstate.m_t);
|
|
result+=CAblasF::RDotV(vstate.m_m,vstate.m_v,vstate.m_w)+CAblasF::RDotV(vstate.m_m,vstate.m_p,vstate.m_q);
|
|
result/=CApServ::Coalesce(State.m_cntgz+State.m_cntts+State.m_cntwv+State.m_cntpq,1);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initializes reduced sparse system. |
|
|
//| Works only for sparse IPM. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::ReducedSystemInit(CVIPMReducedSparseSystem &s,
|
|
CVIPMState &solver)
|
|
{
|
|
//--- create variables
|
|
int ntotal=0;
|
|
int nnzmax=0;
|
|
int factldlt=0;
|
|
int permpriorityamd=0;
|
|
int offs=0;
|
|
int rowoffs=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
int sumdeg=0;
|
|
int colthreshold=0;
|
|
int rowthreshold=0;
|
|
int eligiblecols=0;
|
|
int eligiblerows=0;
|
|
//--- check
|
|
if(!CAp::Assert(solver.m_factorizationtype==1,__FUNCTION__+": unexpected factorization type"))
|
|
return;
|
|
if(!CAp::Assert(solver.m_hkind==1,__FUNCTION__+": unexpected HKind"))
|
|
return;
|
|
ntotal=solver.m_n+solver.m_mdense+solver.m_msparse;
|
|
s.m_ntotal=ntotal;
|
|
s.m_effectivediag.Resize(ntotal);
|
|
//--- Determine maximum amount of memory required to store sparse matrices
|
|
nnzmax=solver.m_sparseh.m_RIdx[solver.m_n];
|
|
if(solver.m_msparse>0)
|
|
nnzmax+=solver.m_sparseafull.m_RIdx[solver.m_msparse];
|
|
if(solver.m_mdense>0)
|
|
nnzmax+=solver.m_n*solver.m_mdense;
|
|
nnzmax+=ntotal;
|
|
//--- Prepare strictly lower triangle of template KKT matrix (KKT system without D and E
|
|
//--- terms being added to diagonals)
|
|
s.m_rawsystem.m_M=ntotal;
|
|
s.m_rawsystem.m_N=ntotal;
|
|
s.m_rawsystem.m_Idx.Resize(nnzmax);
|
|
s.m_rawsystem.m_Vals.Resize(nnzmax);
|
|
s.m_rawsystem.m_RIdx.Resize(ntotal+1);
|
|
s.m_rawsystem.m_RIdx.Set(0,0);
|
|
//if(solver.m_dodetailedtrace)
|
|
//--- CSparse::SparseTrace(s.m_rawsystem);
|
|
offs=0;
|
|
rowoffs=0;
|
|
sumdeg=0;
|
|
CAblasF::ISetAllocV(solver.m_n,0,s.m_coldegrees);
|
|
CAblasF::ISetAllocV(solver.m_msparse+solver.m_mdense,0,s.m_rowdegrees);
|
|
CAblasF::BSetAllocV(solver.m_n,true,s.m_isdiagonal);
|
|
for(i=0; i<solver.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(solver.m_sparseh.m_DIdx[i]+1==solver.m_sparseh.m_UIdx[i],__FUNCTION__+": critical integrity check failed for diagonal of H"))
|
|
return;
|
|
if(!solver.m_isfrozen[i])
|
|
{
|
|
//--- Entire row is not frozen, but some of its entries can be.
|
|
//--- Output non-frozen offdiagonal entries.
|
|
k0=solver.m_sparseh.m_RIdx[i];
|
|
k1=solver.m_sparseh.m_DIdx[i];
|
|
for(k=k0; k<k1; k++)
|
|
{
|
|
j=solver.m_sparseh.m_Idx[k];
|
|
if(!solver.m_isfrozen[j])
|
|
{
|
|
s.m_rawsystem.m_Idx.Set(offs,j);
|
|
s.m_rawsystem.m_Vals.Set(offs,-solver.m_sparseh.m_Vals[k]);
|
|
s.m_isdiagonal[i]=false;
|
|
s.m_isdiagonal[j]=false;
|
|
offs++;
|
|
}
|
|
}
|
|
//--- Output diagonal entry (it is always not frozen)
|
|
s.m_rawsystem.m_Idx.Set(offs,i);
|
|
s.m_rawsystem.m_Vals.Set(offs,-solver.m_sparseh.m_Vals[solver.m_sparseh.m_DIdx[i]]);
|
|
offs++;
|
|
}
|
|
else
|
|
{
|
|
//--- Entire row is frozen, output just -1
|
|
s.m_rawsystem.m_Idx.Set(offs,i);
|
|
s.m_rawsystem.m_Vals.Set(offs,-1.0);
|
|
offs++;
|
|
}
|
|
rowoffs++;
|
|
s.m_rawsystem.m_RIdx.Set(rowoffs,offs);
|
|
//if(solver.m_dodetailedtrace)
|
|
//--- CSparse::SparseTrace(s.m_rawsystem);
|
|
}
|
|
for(i=0; i<solver.m_msparse; i++)
|
|
{
|
|
k0=solver.m_sparseafull.m_RIdx[i];
|
|
k1=solver.m_sparseafull.m_RIdx[i+1];
|
|
for(k=k0; k<k1; k++)
|
|
{
|
|
j=solver.m_sparseafull.m_Idx[k];
|
|
if(!solver.m_isfrozen[j])
|
|
{
|
|
s.m_rawsystem.m_Idx.Set(offs,j);
|
|
s.m_rawsystem.m_Vals.Set(offs,solver.m_sparseafull.m_Vals[k]);
|
|
s.m_rowdegrees.Add(i,1);
|
|
s.m_coldegrees.Add(j,1);
|
|
sumdeg++;
|
|
offs++;
|
|
}
|
|
}
|
|
s.m_rawsystem.m_Idx.Set(offs,rowoffs);
|
|
s.m_rawsystem.m_Vals.Set(offs,0.0);
|
|
offs++;
|
|
rowoffs++;
|
|
s.m_rawsystem.m_RIdx.Set(rowoffs,offs);
|
|
//if(solver.m_dodetailedtrace)
|
|
//--- CSparse::SparseTrace(s.m_rawsystem);
|
|
}
|
|
for(i=0; i<solver.m_mdense; i++)
|
|
{
|
|
for(k=0; k<solver.m_n; k++)
|
|
{
|
|
if(solver.m_denseafull.Get(i,k)!=0.0 && !solver.m_isfrozen[k])
|
|
{
|
|
s.m_rawsystem.m_Idx.Set(offs,k);
|
|
s.m_rawsystem.m_Vals.Set(offs,solver.m_denseafull.Get(i,k));
|
|
s.m_rowdegrees.Add(solver.m_msparse+i,1);
|
|
s.m_coldegrees.Add(k,1);
|
|
sumdeg++;
|
|
offs++;
|
|
}
|
|
}
|
|
s.m_rawsystem.m_Idx.Set(offs,rowoffs);
|
|
s.m_rawsystem.m_Vals.Set(offs,0.0);
|
|
offs++;
|
|
rowoffs++;
|
|
s.m_rawsystem.m_RIdx.Set(rowoffs,offs);
|
|
//if(solver.m_dodetailedtrace)
|
|
//--- CSparse::SparseTrace(s.m_rawsystem);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(rowoffs==ntotal,__FUNCTION__+": critical integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(offs<=nnzmax,__FUNCTION__+": critical integrity check failed"))
|
|
return;
|
|
CSparse::SparseCreateCRSInplace(s.m_rawsystem);
|
|
//if(solver.m_dodetailedtrace)
|
|
//--- CSparse::SparseTrace(s.m_rawsystem);
|
|
//--- Prepare reordering
|
|
colthreshold=(int)MathRound(m_muquasidense*sumdeg/solver.m_n)+2;
|
|
rowthreshold=(int)MathRound(m_muquasidense*sumdeg/(solver.m_msparse+solver.m_mdense+1))+2;
|
|
eligiblecols=0;
|
|
eligiblerows=0;
|
|
CAblasF::ISetAllocV(ntotal,0,s.m_priorities);
|
|
for(i=0; i<solver.m_n; i++)
|
|
{
|
|
if(s.m_isdiagonal[i] && s.m_coldegrees[i]<=colthreshold)
|
|
eligiblecols++;
|
|
}
|
|
for(i=0; i<(solver.m_mdense+solver.m_msparse); i++)
|
|
{
|
|
if(s.m_rowdegrees[i]<=rowthreshold)
|
|
eligiblerows++;
|
|
}
|
|
if(solver.m_dotrace)
|
|
CAp::Trace("> initializing KKT system; no priority ordering being applied\n");
|
|
//--- Perform factorization analysis using sparsity pattern (but not numerical values)
|
|
factldlt=1;
|
|
permpriorityamd=3;
|
|
if(!CSpChol::SpSymmAnalyze(s.m_rawsystem,s.m_priorities,factldlt,permpriorityamd,s.m_analysis))
|
|
CAp::Assert(false,__FUNCTION__+": critical integrity check failed,symbolically degenerate KKT system encountered");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes factorization of A + D, where A is internally stored |
|
|
//| KKT matrix and D is user-supplied diagonal term.The factorization|
|
|
//| is stored internally and should never be accessed directly. |
|
|
//| ModEps and BadChol are user supplied tolerances for modified |
|
|
//| Cholesky / LDLT. |
|
|
//| Returns True on success, False on LDLT failure. |
|
|
//| On success outputs diagonal reproduction error ErrSq, and sum of |
|
|
//| squared diagonal elements SumSq |
|
|
//+------------------------------------------------------------------+
|
|
bool CVIPMSolver::ReducedSystemFactorizeWithAddEnd(CVIPMReducedSparseSystem &s,
|
|
CRowDouble &d,
|
|
double modeps,
|
|
double badchol,
|
|
double &sumsq,
|
|
double &errsq)
|
|
{
|
|
//--- create variables
|
|
bool result=true;
|
|
int ntotal=s.m_ntotal;
|
|
sumsq=0;
|
|
errsq=0;
|
|
|
|
for(int i=0; i<ntotal; i++)
|
|
s.m_effectivediag.Set(i,s.m_rawsystem.m_Vals[s.m_rawsystem.m_DIdx[i]]+d[i]);
|
|
CSpChol::SpSymmReloadDiagonal(s.m_analysis,s.m_effectivediag);
|
|
CSpChol::SpSymmSetModificationStrategy(s.m_analysis,1,modeps,badchol,0.0,0.0);
|
|
if(CSpChol::SpSymmFactorize(s.m_analysis))
|
|
CSpChol::SpSymmDiagErr(s.m_analysis,sumsq,errsq);
|
|
else
|
|
{
|
|
sumsq=0;
|
|
errsq=0;
|
|
result=false;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Solve reduced KKT system, replacing right part by its solution. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::ReducedSystemSolve(CVIPMReducedSparseSystem &s,
|
|
CRowDouble &b)
|
|
{
|
|
CSpChol::SpSymmSolve(s.m_analysis,b);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initializes QP-IPM State and prepares it to receive quadratic / |
|
|
//| linear terms and constraints. |
|
|
//| The solver is configured to work internally with factorization |
|
|
//| FType |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - solver State to be configured; previously |
|
|
//| allocated memory is reused as much as possible |
|
|
//| S - scale vector, array[N]: |
|
|
//| * I-th element contains scale of I-th variable, |
|
|
//| * S[I] > 0 |
|
|
//| XOrigin - origin term, array[N]. Can be zero. The solver |
|
|
//| solves problem of the form |
|
|
//| > |
|
|
//| > min(0.5 * (x-x_origin)'*A*(x-x_origin)+b' * (x-x_origin)) |
|
|
//| > |
|
|
//| The terms A and b (as well as constraints) will be specified |
|
|
//| later with separate calls. |
|
|
//| FType - factorization type: |
|
|
//| * 0 for dense NxN factorization (normal equations) |
|
|
//| * 1 for sparse(N + M) |
|
|
//| x(N + M) factorization |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMInit(CVIPMState &State,
|
|
CRowDouble &s,
|
|
CRowDouble &xorigin,
|
|
int n,
|
|
int nmain,
|
|
int ftype)
|
|
{
|
|
//--- create variables
|
|
int nslack=n-nmain;
|
|
int i=0;
|
|
int j=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(s,n),__FUNCTION__+": S contains infinite or NaN elements"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(xorigin,n),__FUNCTION__+": XOrigin contains infinite or NaN elements"))
|
|
return;
|
|
if(!CAp::Assert(ftype==0 || ftype==1,__FUNCTION__+": unexpected FType"))
|
|
return;
|
|
if(!CAp::Assert(nmain>=1,__FUNCTION__+": NMain<1"))
|
|
return;
|
|
if(!CAp::Assert(nmain<=n,__FUNCTION__+": NMain>N"))
|
|
return;
|
|
//--- Problem metrics, Settings and type
|
|
State.m_n=n;
|
|
State.m_nmain=nmain;
|
|
State.m_islinear=true;
|
|
State.m_factorizationtype=ftype;
|
|
State.m_factorizationpresent=false;
|
|
State.m_factorizationpoweredup=false;
|
|
State.m_slacksforequalityconstraints=true;
|
|
VIPMSetCond(State,0.0,0.0,0.0);
|
|
//--- Reports
|
|
State.m_repiterationscount=0;
|
|
State.m_repncholesky=0;
|
|
//--- Trace
|
|
State.m_dotrace=false;
|
|
State.m_dodetailedtrace=false;
|
|
//--- Scale and origin
|
|
State.m_scl.Resize(n);
|
|
State.m_invscl.Resize(n);
|
|
State.m_xorigin.Resize(n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s[i]>0.0,__FUNCTION__+": S[i] is non-positive"))
|
|
return;
|
|
State.m_scl.Set(i,s[i]);
|
|
State.m_invscl.Set(i,1/s[i]);
|
|
State.m_xorigin.Set(i,xorigin[i]);
|
|
}
|
|
State.m_targetscale=1.0;
|
|
//--- Linear and quadratic terms - default value
|
|
State.m_c=vector<double>::Zeros(n);
|
|
State.m_hkind=-1;
|
|
switch(ftype)
|
|
{
|
|
case 0:
|
|
//--- Dense quadratic term
|
|
State.m_denseh.Resize(nmain,nmain);
|
|
for(i=0; i<nmain; i++)
|
|
for(j=0; j<=i; j++)
|
|
State.m_denseh.Set(i,j,0);
|
|
State.m_hkind=0;
|
|
State.m_isdiagonalh=false;
|
|
break;
|
|
case 1:
|
|
//--- Sparse quadratic term
|
|
State.m_sparseh.m_MatrixType=1;
|
|
State.m_sparseh.m_M=n;
|
|
State.m_sparseh.m_N=n;
|
|
State.m_sparseh.m_NInitialized=n;
|
|
State.m_sparseh.m_Idx.Resize(n);
|
|
State.m_sparseh.m_Vals=vector<double>::Zeros(n);
|
|
State.m_sparseh.m_RIdx.Resize(n+1);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
State.m_sparseh.m_Idx.Set(i,i);
|
|
State.m_sparseh.m_RIdx.Set(i,i);
|
|
}
|
|
State.m_sparseh.m_RIdx.Set(n,n);
|
|
CSparse::SparseCreateCRSInplace(State.m_sparseh);
|
|
State.m_hkind=1;
|
|
State.m_isdiagonalh=true;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hkind>=0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Box constraints - default values
|
|
State.m_bndl=vector<double>::Full(n,AL_NEGINF);
|
|
State.m_bndu=vector<double>::Full(n,AL_POSINF);
|
|
CApServ::BVectorSetLengthAtLeast(State.m_HasBndL,n);
|
|
CApServ::BVectorSetLengthAtLeast(State.m_HasBndU,n);
|
|
ArrayInitialize(State.m_HasBndL,false);
|
|
ArrayInitialize(State.m_HasBndU,false);
|
|
//--- Linear constraints - empty
|
|
State.m_mdense=0;
|
|
State.m_msparse=0;
|
|
State.m_combinedaslack.m_M=0;
|
|
State.m_combinedaslack.m_N=nslack;
|
|
State.m_sparseamain.m_M=0;
|
|
State.m_sparseamain.m_N=nmain;
|
|
CSparse::SparseCreateCRSInplace(State.m_sparseamain);
|
|
CSparse::SparseCreateCRSInplace(State.m_combinedaslack);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes target function 0.5 * x'*H*x+c'*x |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::VIPMTarget(CVIPMState &State,
|
|
CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int n=State.m_n;
|
|
int nmain=State.m_nmain;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hkind==0 || State.m_hkind==1,__FUNCTION__+": unexpected HKind"))
|
|
return(result);
|
|
switch(State.m_hkind)
|
|
{
|
|
//--- Dense
|
|
case 0:
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
for(j=0; j<=i-1; j++)
|
|
result+=x[i]*State.m_denseh.Get(i,j)*x[j];
|
|
result +=+0.5*CMath::Sqr(x[i])*State.m_denseh.Get(i,i);
|
|
}
|
|
for(i=0; i<n; i++)
|
|
result+=State.m_c[i]*x[i];
|
|
break;
|
|
//--- Sparse
|
|
case 1:
|
|
for(i=0; i<n; i++)
|
|
{
|
|
result+=State.m_c[i]*x[i];
|
|
j0=State.m_sparseh.m_RIdx[i];
|
|
j1=State.m_sparseh.m_DIdx[i]-1;
|
|
for(k=j0; k<=j1; k++)
|
|
{
|
|
v=State.m_sparseh.m_Vals[k];
|
|
j=State.m_sparseh.m_Idx[k];
|
|
result+=v*x[i]*x[j];
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_sparseh.m_UIdx[i]!=State.m_sparseh.m_DIdx[i],__FUNCTION__+": sparse diagonal not found"))
|
|
return(0);
|
|
v=State.m_sparseh.m_Vals[State.m_sparseh.m_DIdx[i]];
|
|
result+=0.5*v*CMath::Sqr(x[i]);
|
|
}
|
|
break;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes Y := alpha * A * x + beta * Y |
|
|
//| where A is constraint matrix, |
|
|
//| X is user - specified source, |
|
|
//| Y is target. |
|
|
//| Beta can be zero(in this case original contents of Y is ignored).|
|
|
//| If Beta is nonzero, we expect that Y contains preallocated array.|
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::MultiplyGEAX(CVIPMState &State,
|
|
double alpha,
|
|
CRowDouble &x,
|
|
int offsx,
|
|
double beta,
|
|
CRowDouble &y,
|
|
int offsax)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int mdense=State.m_mdense;
|
|
int msparse=State.m_msparse;
|
|
|
|
if(beta==0.0)
|
|
y.Resize(offsax+m);
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(y.Size()>=offsax+m,__FUNCTION__+": Y is too short"))
|
|
return;
|
|
}
|
|
if(msparse>0)
|
|
CSparse::SparseGemV(State.m_sparseafull,alpha,0,x,offsx,beta,y,offsax);
|
|
if(mdense>0)
|
|
CAblas::RMatrixGemVect(mdense,n,alpha,State.m_denseafull,0,0,0,x,offsx,beta,y,offsax+msparse);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes Y := alpha * A'*x + beta*Y |
|
|
//| where A is constraint matrix, |
|
|
//| X is user - specified source, |
|
|
//| Y is target. |
|
|
//| Beta can be zero, in this case we automatically reallocate target|
|
|
//| if it is too short (but do NOT reallocate it if its size is large|
|
|
//| enough). If Beta is nonzero, we expect that Y contains |
|
|
//| preallocated array. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::MultiplyGEATX(CVIPMState &State,
|
|
double alpha,
|
|
CRowDouble &x,
|
|
int offsx,
|
|
double beta,
|
|
CRowDouble &y,
|
|
int offsy)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int mdense=State.m_mdense;
|
|
int msparse=State.m_msparse;
|
|
|
|
if(beta==0.0)
|
|
{
|
|
y.Resize(offsy+n);
|
|
CAblasF::RSetVX(n,0.0,y,offsy);
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(y.Size()>=offsy+n,__FUNCTION__+": Y is too short"))
|
|
return;
|
|
CAblasF::RMulVX(n,beta,y,offsy);
|
|
}
|
|
if(msparse>0)
|
|
CSparse::SparseGemV(State.m_sparseafull,alpha,1,x,offsx,1.0,y,offsy);
|
|
if(mdense>0)
|
|
CAblas::RMatrixGemVect(n,mdense,alpha,State.m_denseafull,0,0,1,x,offsx+msparse,1.0,y,offsy);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes H*x, does not support advanced functionality of GEAX / |
|
|
//| GEATX |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::MultiplyHX(CVIPMState &State,
|
|
CRowDouble &x,
|
|
CRowDouble &hx)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int nmain=State.m_nmain;
|
|
int i=0;
|
|
|
|
hx.Resize(n);
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hkind==0 || State.m_hkind==1,__FUNCTION__+": unexpected HKind"))
|
|
return;
|
|
switch(State.m_hkind)
|
|
{
|
|
case 0:
|
|
CAblas::RMatrixSymVect(nmain,1.0,State.m_denseh,0,0,false,x,0,0.0,hx,0);
|
|
for(i=nmain; i<n; i++)
|
|
hx.Set(i,0);
|
|
for(i=0; i<n; i++)
|
|
hx.Add(i,x[i]*State.m_diagr[i]);
|
|
break;
|
|
case 1:
|
|
//--- check
|
|
if(!CAp::Assert(State.m_sparseh.m_N==n && State.m_sparseh.m_M==n,__FUNCTION__+": sparse H has incorrect size"))
|
|
return;
|
|
if(State.m_isdiagonalh)
|
|
{
|
|
//--- H is known to be diagonal, much faster code can be used
|
|
CAblasF::RCopyV(n,State.m_diagr,hx);
|
|
CAblasF::RAddV(n,1.0,State.m_sparseh.m_Vals,hx);
|
|
CAblasF::RMergeMulV(n,x,hx);
|
|
}
|
|
else
|
|
{
|
|
//--- H is a general sparse matrix, use generic sparse matrix-vector multiply
|
|
CSparse::SparseSMV(State.m_sparseh,false,x,hx);
|
|
for(i=0; i<n; i++)
|
|
hx.Add(i,x[i]*State.m_diagr[i]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes products H*x, A*x, A^T*y |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMMultiply(CVIPMState &State,
|
|
CRowDouble &x,
|
|
CRowDouble &y,
|
|
CRowDouble &hx,
|
|
CRowDouble &ax,
|
|
CRowDouble &aty)
|
|
{
|
|
MultiplyGEAX(State,1.0,x,0,0.0,ax,0);
|
|
MultiplyGEATX(State,1.0,y,0,0.0,aty,0);
|
|
MultiplyHX(State,x,hx);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function "powers up" factorization, i.e. prepares some |
|
|
//| important temporaries. It should be called once prior to the |
|
|
//| first call to VIPMInitialPoint() or VIPMFactorize(). |
|
|
//| Parameters: |
|
|
//| RegFree - regularization for free variables; good value |
|
|
//| sqrt(MachineEpsilon) |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMPowerUp(CVIPMState &State,
|
|
double regfree)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int i=0;
|
|
double v=0;
|
|
double vrhs=0;
|
|
double priorcoeff=0;
|
|
double initprimslack=0;
|
|
double initdualslack=0;
|
|
double maxinitialnoncentrality=0;
|
|
double maxinitialimbalance=0;
|
|
double mu0=0;
|
|
double mumin=0;
|
|
bool success=false;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_factorizationtype==0 || State.m_factorizationtype==1,__FUNCTION__+": unexpected factorization type"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(!State.m_factorizationpoweredup,__FUNCTION__+": repeated call"))
|
|
return;
|
|
maxinitialnoncentrality=1.0E-6;
|
|
maxinitialimbalance=1.0E-6;
|
|
//--- Set up information about presence of slack variables.
|
|
//--- Decide which components of X should be frozen.
|
|
//--- Compute diagonal regularization matrix R.
|
|
CAblasF::BCopyAllocV(n,State.m_HasBndL,State.m_hasgz);
|
|
CAblasF::BCopyAllocV(n,State.m_HasBndU,State.m_hasts);
|
|
CAblasF::BSetAllocV(n,false,State.m_isfrozen);
|
|
CAblasF::RSetAllocV(n,0.0,State.m_diagr);
|
|
VARSInitByZero(State.m_current,n,m);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i] && State.m_bndl[i]==State.m_bndu[i])
|
|
{
|
|
State.m_isfrozen[i]=true;
|
|
State.m_hasgz[i]=false;
|
|
State.m_hasts[i]=false;
|
|
State.m_current.m_x.Set(i,State.m_bndl[i]);
|
|
}
|
|
if(!State.m_HasBndL[i] && !State.m_HasBndU[i])
|
|
State.m_diagr.Set(i,regfree);
|
|
}
|
|
CAblasF::BAllocV(m,State.m_haspq);
|
|
CAblasF::BAllocV(m,State.m_haswv);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
State.m_haswv[i]=(State.m_slacksforequalityconstraints || !State.m_hasr[i] || State.m_r[i]>0.0);
|
|
State.m_haspq[i]=(State.m_hasr[i] && State.m_haswv[i]);
|
|
}
|
|
State.m_cntgz=0;
|
|
State.m_cntts=0;
|
|
State.m_cntwv=0;
|
|
State.m_cntpq=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_hasgz[i])
|
|
State.m_cntgz++;
|
|
if(State.m_hasts[i])
|
|
State.m_cntts++;
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(State.m_haswv[i])
|
|
State.m_cntwv++;
|
|
if(State.m_haspq[i])
|
|
State.m_cntpq++;
|
|
}
|
|
//--- Special initialization for sparse version
|
|
if(State.m_factorizationtype==1)
|
|
ReducedSystemInit(State.m_reducedsparsesystem,State);
|
|
State.m_factorizationpoweredup=true;
|
|
//--- Set up initial values of primal and dual variables X and Y by solving
|
|
//--- modified KKT system which tries to enforce linear constraints (ignoring
|
|
//--- box constraints for a while) subject to minimization of additional prior
|
|
//--- term which moves solution towards some interior point.
|
|
//--- Here we expect that State.Current.X contains zeros in non-fixed variables
|
|
//--- and their fixed values for fixed ones.
|
|
priorcoeff=1.0;
|
|
success=VIPMFactorize(State,0.0,State.m_diagddr,0.0,State.m_diagder,priorcoeff,priorcoeff,CMath::m_machineepsilon,CMath::m_machineepsilon);
|
|
//--- check
|
|
if(!CAp::Assert(success,__FUNCTION__+": impossible failure of LDLT factorization"))
|
|
return;
|
|
MultiplyHX(State,State.m_current.m_x,State.m_tmp0);
|
|
MultiplyGEAX(State,1.0,State.m_current.m_x,0,0.0,State.m_tmp1,0);
|
|
CAblasF::RAllocV(n+m,State.m_deltaxy);
|
|
for(i=0; i<n; i++)
|
|
State.m_deltaxy.Set(i,State.m_c[i]+State.m_tmp0[i]);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
//--- We need to specify target right-hand sides for constraints.
|
|
//--- Ether zero, b or b+r is used (depending on presence of r and
|
|
//--- magnitudes of b and b+r, and subject to current State of frozen
|
|
//--- variables).
|
|
vrhs=State.m_b[i]-State.m_tmp1[i];
|
|
if(State.m_hasr[i])
|
|
{
|
|
//--- Range constraint b<=Ax<=b+r
|
|
if(vrhs>=0.0)
|
|
{
|
|
//--- 0<=b<=b+r, select target at lower bound
|
|
v=vrhs;
|
|
}
|
|
else
|
|
{
|
|
//--- b<=0, b+r can have any sign.
|
|
//--- Select zero target if possible, if not - one with smallest absolute value.
|
|
v=MathMin(vrhs+State.m_r[i],0.0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Single-sided constraint Ax>=b.
|
|
//--- Select zero target if possible, if not - one with smallest absolute value.
|
|
v=MathMax(vrhs,0.0);
|
|
}
|
|
State.m_deltaxy.Set(n+i,v);
|
|
}
|
|
SolveReducedKKTSystem(State,State.m_deltaxy);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!State.m_isfrozen[i])
|
|
State.m_current.m_x.Set(i,State.m_deltaxy[i]);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
State.m_current.m_y.Set(i,State.m_deltaxy[n+i]);
|
|
//--- Set up slacks according to our own heuristic
|
|
initprimslack=MathMax(m_initslackval,CAblasF::RMaxAbsV(n,State.m_current.m_x));
|
|
initdualslack=MathMax(m_initslackval,CAblasF::RMaxAbsV(m,State.m_current.m_y));
|
|
MultiplyGEAX(State,1.0,State.m_current.m_x,0,0.0,State.m_tmpax,0);
|
|
mu0=1.0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_hasgz[i])
|
|
{
|
|
State.m_current.m_g.Set(i,MathMax(MathAbs(State.m_current.m_x[i]-State.m_bndl[i]),initprimslack));
|
|
State.m_current.m_z.Set(i,MathMax(State.m_current.m_g[i]*maxinitialimbalance,initdualslack));
|
|
mu0=MathMax(mu0,State.m_current.m_g[i]*State.m_current.m_z[i]);
|
|
}
|
|
if(State.m_hasts[i])
|
|
{
|
|
State.m_current.m_t.Set(i,MathMax(MathAbs(State.m_current.m_x[i]-State.m_bndu[i]),initprimslack));
|
|
State.m_current.m_s.Set(i,MathMax(State.m_current.m_t[i]*maxinitialimbalance,initdualslack));
|
|
mu0=MathMax(mu0,State.m_current.m_t[i]*State.m_current.m_s[i]);
|
|
}
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(State.m_haswv[i])
|
|
{
|
|
State.m_current.m_w.Set(i,MathMax(MathAbs(State.m_tmpax[i]-State.m_b[i]),initprimslack));
|
|
State.m_current.m_v.Set(i,MathMax(State.m_current.m_w[i]*maxinitialimbalance,MathMax(MathAbs(State.m_current.m_y[i]),m_initslackval)));
|
|
mu0=MathMax(mu0,State.m_current.m_w[i]*State.m_current.m_v[i]);
|
|
}
|
|
if(State.m_haspq[i])
|
|
{
|
|
State.m_current.m_p.Set(i,MathMax(MathAbs(State.m_r[i]-State.m_current.m_w[i]),initprimslack));
|
|
State.m_current.m_q.Set(i,MathMax(State.m_current.m_p[i]*maxinitialimbalance,MathMax(MathAbs(State.m_current.m_y[i]),m_initslackval)));
|
|
mu0=MathMax(mu0,State.m_current.m_p[i]*State.m_current.m_q[i]);
|
|
}
|
|
}
|
|
//--- Additional shift to ensure that initial point is not too non-centered
|
|
mumin=mu0*maxinitialnoncentrality;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_hasgz[i] && (State.m_current.m_g[i]*State.m_current.m_z[i])<mumin)
|
|
{
|
|
v=MathSqrt(mumin/(State.m_current.m_g[i]*State.m_current.m_z[i]));
|
|
State.m_current.m_g.Mul(i,v);
|
|
State.m_current.m_z.Mul(i,v);
|
|
}
|
|
if(State.m_hasts[i] && (double)(State.m_current.m_t[i]*State.m_current.m_s[i])<mumin)
|
|
{
|
|
v=MathSqrt(mumin/(State.m_current.m_t[i]*State.m_current.m_s[i]));
|
|
State.m_current.m_t.Mul(i,v);
|
|
State.m_current.m_s.Mul(i,v);
|
|
}
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(State.m_haswv[i] && (State.m_current.m_w[i]*State.m_current.m_v[i])<mumin)
|
|
{
|
|
v=MathSqrt(mumin/(State.m_current.m_w[i]*State.m_current.m_v[i]));
|
|
State.m_current.m_w.Mul(i,v);
|
|
State.m_current.m_v.Mul(i,v);
|
|
}
|
|
if(State.m_haspq[i] && (State.m_current.m_p[i]*State.m_current.m_q[i])<mumin)
|
|
{
|
|
v=MathSqrt(mumin/(State.m_current.m_p[i]*State.m_current.m_q[i]));
|
|
State.m_current.m_p.Mul(i,v);
|
|
State.m_current.m_q.Mul(i,v);
|
|
}
|
|
}
|
|
//--- Almost done
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> initial point was generated\n");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs factorization of modified KKT system |
|
|
//| ( | ) |
|
|
//| (-(H + alpha0 * D + alpha1*I) | A^T ) |
|
|
//| ( | ) |
|
|
//| (---------------------------- | -------------------) |
|
|
//| ( | ) |
|
|
//| ( A | beta0 * E + beta1*I) |
|
|
//| ( | ) |
|
|
//| where: |
|
|
//| * H is an NxN quadratic term |
|
|
//| * A is an MxN matrix of linear constraint |
|
|
//| * alpha0, alpha1, beta0, beta1 are nonnegative scalars |
|
|
//| * D and E are diagonal matrices with nonnegative entries |
|
|
//| (which are ignored if alpha0 and beta0 are zero - arrays |
|
|
//| are not referenced at all) |
|
|
//| * I is an NxN or MxM identity matrix |
|
|
//| Additionally, regularizing term |
|
|
//| ( | ) |
|
|
//| ( -reg * I | ) |
|
|
//| ( | ) |
|
|
//| (---------- | ----------) |
|
|
//| ( | ) |
|
|
//| ( | +reg*I ) |
|
|
//| ( | ) |
|
|
//| is added to the entire KKT system prior to factorization in order|
|
|
//| to improve its numerical stability. |
|
|
//| Returns True on success, False on falure of factorization (it is |
|
|
//| recommended to increase regularization parameter and try one more|
|
|
//| time). |
|
|
//+------------------------------------------------------------------+
|
|
bool CVIPMSolver::VIPMFactorize(CVIPMState &State,
|
|
double alpha0,
|
|
CRowDouble &d,
|
|
double beta0,
|
|
CRowDouble &e,
|
|
double alpha11,
|
|
double beta11,
|
|
double modeps,
|
|
double dampeps)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int nmain=State.m_nmain;
|
|
int nslack=n-nmain;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int mdense=State.m_mdense;
|
|
int msparse=State.m_msparse;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
int ka=0;
|
|
int kb=0;
|
|
int ja=0;
|
|
int jb=0;
|
|
double va=0;
|
|
double vb=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double badchol=1.0E50;
|
|
double sumsq=0;
|
|
double errsq=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(alpha0) && alpha0>=0.0,__FUNCTION__+": Alpha0 is infinite or negative"))
|
|
return(false);
|
|
if(!CAp::Assert(MathIsValidNumber(alpha11) && alpha11>=0.0,__FUNCTION__+": Alpha1 is infinite or negative"))
|
|
return(false);
|
|
if(!CAp::Assert(MathIsValidNumber(beta0) && beta0>=0.0,__FUNCTION__+": Beta0 is infinite or negative"))
|
|
return(false);
|
|
if(!CAp::Assert(MathIsValidNumber(beta11) && beta11>=0.0,__FUNCTION__+": Beta1 is infinite or negative"))
|
|
return(false);
|
|
if(!CAp::Assert(State.m_factorizationtype==0 || State.m_factorizationtype==1,__FUNCTION__+": unexpected factorization type"))
|
|
return(false);
|
|
if(!CAp::Assert(State.m_factorizationpoweredup,__FUNCTION__+": critical integrity check failed (no powerup stage)"))
|
|
return(false);
|
|
|
|
State.m_factorizationpresent=false;
|
|
//--- Dense NxN normal equations approach
|
|
if(State.m_factorizationtype==0)
|
|
{
|
|
//--- A problem formulation with possible slacks.
|
|
//--- === A FORMULATION WITHOUT FROZEN VARIABLES ===
|
|
//--- We have to solve following system:
|
|
//--- [ -(H+Dh+Rh) Ah' ] [ Xh ] [ Bh ]
|
|
//--- [ -(Dz+Rz) Az' ] [ Xz ] = [ Bz ]
|
|
//--- [ Ah Az E ] [ Y ] [ By ]
|
|
//--- with Xh being NMain-dimensional vector, Xz being NSlack-dimensional vector, constraint
|
|
//--- matrix A being divided into non-slack and slack parts Ah and Az (and Ah, in turn, being
|
|
//--- divided into sparse and dense parts), Rh and Rz being diagonal regularization matrix,
|
|
//--- Y being M-dimensional vector.
|
|
//--- NOTE: due to definition of slack variables following holds: for any diagonal matrix W
|
|
//--- a product Az*W*Az' is a diagonal matrix.
|
|
//--- From the second line we get
|
|
//--- Xz = inv(Dz+Rz)*Az'*y - inv(Dz+Rz)*Bz
|
|
//--- = inv(Dz+Rz)*Az'*y - BzWave
|
|
//--- Using this value for Zx, third line gives us
|
|
//--- Y = inv(E+Az*inv(Dz+Rz)*Az')*(By+Az*BzWave-Ah*Xh)
|
|
//--- = inv(EWave)*(ByWave-Ah*Xh)
|
|
//--- with EWave = E+Az*inv(Dz+Rz)*Az' and ByWave = By+Az*BzWave
|
|
//--- Finally, first line gives us
|
|
//--- Xh = -inv(H+Dh+Rh+Ah'*inv(EWave)*Ah)*(Bh-Ah'*inv(EWave)*ByWave)
|
|
//--- = -inv(HWave)*BhWave
|
|
//--- with HWave = H+Dh+Rh+Ah'*inv(EWave)*Ah and BhWave = Bh-Ah'*inv(EWave)*ByWave
|
|
//--- In order to prepare factorization we need to compute:
|
|
//--- (a) diagonal matrices Dh, Rh, Dz and Rz (and precomputed inverse of Dz+Rz)
|
|
//--- (b) EWave
|
|
//--- (c) HWave
|
|
//--- === SPECIAL HANDLING OF FROZEN VARIABLES ===
|
|
//--- Frozen variables result in zero steps, i.e. zero components of Xh and Xz.
|
|
//--- It could be implemented by explicit modification of KKT system (zeroing out
|
|
//--- columns/rows of KKT matrix, rows of right part, putting 1's to diagonal).
|
|
//--- However, it is possible to do without actually modifying quadratic term and
|
|
//--- constraints:
|
|
//--- * freezing elements of Xz can be implemented by zeroing out corresponding
|
|
//--- columns of inv(Dz+Rz) because Az always appears in computations along with diagonal Dz+Rz.
|
|
//--- * freezing elements of Xh is a bit more complex - it needs:
|
|
//--- * zeroing out columns/rows of HWave and setting up unit diagonal prior to solving for Xh
|
|
//--- * explicitly zeroing out computed elements of Xh prior to computing Y and Xz
|
|
State.m_factregdhrh.Resize(nmain);
|
|
State.m_factinvregdzrz.Resize(nslack);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=0;
|
|
if(alpha0>0)
|
|
v+=alpha0*d[i];
|
|
if(alpha11>0)
|
|
v+=alpha11;
|
|
v+=State.m_diagr[i]+dampeps;
|
|
//--- check
|
|
if(!CAp::Assert(v>0,__FUNCTION__+": integrity check failed,degenerate diagonal matrix"))
|
|
return(false);
|
|
if(i>=nmain)
|
|
{
|
|
if(!State.m_isfrozen[i])
|
|
State.m_factinvregdzrz.Set(i-nmain,1/v);
|
|
else
|
|
State.m_factinvregdzrz.Set(i-nmain,0.0);
|
|
}
|
|
else
|
|
State.m_factregdhrh.Set(i,v);
|
|
}
|
|
//--- Now we are ready to compute EWave
|
|
State.m_factregewave.Resize(m);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
//--- Compute diagonal element of E
|
|
v=0;
|
|
if(beta0>0)
|
|
v+=beta0*e[i];
|
|
if(beta11>0)
|
|
v+=beta11;
|
|
v+=dampeps;
|
|
//--- check
|
|
if(!CAp::Assert(v>0,__FUNCTION__+": integrity check failed,degenerate diagonal matrix"))
|
|
return(false);
|
|
//--- Compute diagonal modification Az*inv(Dz)*Az'
|
|
k0=State.m_combinedaslack.m_RIdx[i];
|
|
k1=State.m_combinedaslack.m_RIdx[i+1];
|
|
for(k=k0; k<k1; k++)
|
|
{
|
|
vv=State.m_combinedaslack.m_Vals[k];
|
|
v+=vv*vv*State.m_factinvregdzrz[State.m_combinedaslack.m_Idx[k]];
|
|
}
|
|
//--- Save EWave
|
|
State.m_factregewave.Set(i,v);
|
|
}
|
|
//--- Now we are ready to compute HWave:
|
|
//--- * store H
|
|
//--- * add Dh
|
|
//--- * add Ah'*inv(EWave)*Ah
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hkind==0,__FUNCTION__+": unexpected HKind"))
|
|
return(false);
|
|
State.m_factdensehaug=State.m_denseh;
|
|
State.m_factdensehaug.Resize(nmain,nmain);
|
|
for(i=0; i<nmain; i++)
|
|
State.m_factdensehaug.Add(i,i,State.m_factregdhrh[i]);
|
|
if(msparse>0)
|
|
{
|
|
//--- Handle sparse part of Ah in Ah'*inv(EWave)*Ah
|
|
for(i=0; i<msparse; i++)
|
|
{
|
|
v=1.0/State.m_factregewave[i];
|
|
k0=State.m_sparseamain.m_RIdx[i];
|
|
k1=State.m_sparseamain.m_RIdx[i+1];
|
|
for(ka=k0; ka<k1; ka++)
|
|
{
|
|
ja=State.m_sparseamain.m_Idx[ka];
|
|
va=State.m_sparseamain.m_Vals[ka];
|
|
for(kb=k0; kb<=ka; kb++)
|
|
{
|
|
jb=State.m_sparseamain.m_Idx[kb];
|
|
vb=State.m_sparseamain.m_Vals[kb];
|
|
State.m_factdensehaug.Add(ja,jb,v*va*vb);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(mdense>0)
|
|
{
|
|
//--- Handle dense part of Ah in Ah'*inv(EWave)*Ah
|
|
State.m_tmpr2=State.m_denseamain;
|
|
State.m_tmpr2.Resize(mdense,nmain);
|
|
for(i=0; i<mdense; i++)
|
|
{
|
|
v=1.0/MathSqrt(State.m_factregewave[msparse+i]);
|
|
for(j=0; j<nmain; j++)
|
|
State.m_tmpr2.Mul(i,j,v);
|
|
}
|
|
CAblas::RMatrixSyrk(nmain,mdense,1.0,State.m_tmpr2,0,0,2,1.0,State.m_factdensehaug,0,0,false);
|
|
}
|
|
//--- Zero out rows/cols of HWave corresponding to frozen variables, set up unit diagonal
|
|
State.m_tmp0=vector<double>::Ones(nmain);
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
if(State.m_isfrozen[i])
|
|
{
|
|
State.m_tmp0.Set(i,0.0);
|
|
//--- Entire row is nullified except for diagonal element
|
|
CAblasF::RSetR(i+1,0.0,State.m_factdensehaug,i);
|
|
State.m_factdensehaug.Set(i,i,1.0);
|
|
}
|
|
else
|
|
{
|
|
//--- Only some components are nullified
|
|
CAblasF::RMergeMulVR(i+1,State.m_tmp0,State.m_factdensehaug,i);
|
|
}
|
|
}
|
|
//--- Compute Cholesky factorization of HWave
|
|
if(!CTrFac::SPDMatrixCholesky(State.m_factdensehaug,nmain,false))
|
|
return(false);
|
|
v=(State.m_factdensehaug.Diag(0)+0).Sum();
|
|
if(!MathIsValidNumber(v) || v>badchol)
|
|
return(false);
|
|
State.m_factorizationpresent=true;
|
|
}
|
|
//--- Sparse (M+N)x(M+N) factorization
|
|
if(State.m_factorizationtype==1)
|
|
{
|
|
//--- Generate reduced KKT matrix
|
|
State.m_facttmpdiag.Resize(n+m);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
vv=0;
|
|
if(alpha0>0)
|
|
vv+=alpha0*d[i];
|
|
if(alpha11>0)
|
|
vv+=alpha11;
|
|
vv+=State.m_diagr[i]+dampeps;
|
|
State.m_facttmpdiag.Set(i,-vv);
|
|
//--- check
|
|
if(!CAp::Assert(vv>0,__FUNCTION__+": integrity check failed,degenerate diagonal matrix"))
|
|
return(false);
|
|
}
|
|
for(i=0; i<msparse+mdense; i++)
|
|
{
|
|
vv=0;
|
|
if(beta0>0)
|
|
vv+=beta0*e[i];
|
|
if(beta11>0)
|
|
vv+=beta11;
|
|
vv+=dampeps;
|
|
State.m_facttmpdiag.Set(n+i,vv);
|
|
//--- check
|
|
if(!CAp::Assert(vv>0,__FUNCTION__+": integrity check failed,degenerate diagonal matrix"))
|
|
return(false);
|
|
}
|
|
//--- Perform factorization
|
|
//--- Perform additional integrity check: LDLT should reproduce diagonal of initial KKT system with good precision
|
|
if(!ReducedSystemFactorizeWithAddEnd(State.m_reducedsparsesystem,State.m_facttmpdiag,modeps,badchol,sumsq,errsq))
|
|
{
|
|
return(false);
|
|
}
|
|
if(MathSqrt(errsq/(1+sumsq))>MathSqrt(CMath::m_machineepsilon))
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("LDLT-diag-err= %.3E (diagonal reproduction error)\n",MathSqrt(errsq / (1 + sumsq))));
|
|
return(false);
|
|
}
|
|
State.m_factorizationpresent=true;
|
|
//--- Trace
|
|
if(State.m_dotrace)
|
|
{
|
|
CSpChol::SpSymmExtract(State.m_reducedsparsesystem.m_analysis,State.m_tmpsparse0,State.m_tmp0,State.m_tmpi);
|
|
CAp::Trace("--- sparse KKT factorization report ----------------------------------------------------------------\n");
|
|
CAp::Trace("> diagonal terms D and E\n");
|
|
if(alpha0!=0.0)
|
|
{
|
|
v=MathAbs(d[0]);
|
|
vv=MathAbs(d[0]);
|
|
for(i=1; i<n; i++)
|
|
{
|
|
v=MathMin(v,MathAbs(d[i]));
|
|
vv=MathMax(vv,MathAbs(d[i]));
|
|
}
|
|
CAp::Trace(StringFormat("diagD = %.3E (min) ... %.3E (max)\n",v,vv));
|
|
}
|
|
if(m>0 && beta0!=0.0)
|
|
{
|
|
v=MathAbs(e[0]);
|
|
vv=MathAbs(e[0]);
|
|
for(i=1; i<m; i++)
|
|
{
|
|
v=MathMin(v,MathAbs(e[i]));
|
|
vv=MathMax(vv,MathAbs(e[i]));
|
|
}
|
|
CAp::Trace(StringFormat("diagE = %.3E (min) ... %.3E (max)\n",v,vv));
|
|
}
|
|
CAp::Trace("> LDLT factorization of entire KKT matrix\n");
|
|
v=MathAbs(State.m_tmp0[0]);
|
|
vv=MathAbs(State.m_tmp0[0]);
|
|
for(i=1; i<State.m_tmpsparse0.m_N; i++)
|
|
{
|
|
v=MathMax(v,MathAbs(State.m_tmp0[i]));
|
|
vv=MathMin(vv,MathAbs(State.m_tmp0[i]));
|
|
}
|
|
CAp::Trace(StringFormat("|D| = %.3E (min) ... %.3E (max)\n",vv,v));
|
|
v=0.0;
|
|
for(i=0; i<State.m_tmpsparse0.m_N; i++)
|
|
{
|
|
k0=State.m_tmpsparse0.m_RIdx[i];
|
|
k1=State.m_tmpsparse0.m_DIdx[i];
|
|
for(k=k0; k<=k1; k++)
|
|
v=MathMax(v,MathAbs(State.m_tmpsparse0.m_Vals[k]));
|
|
}
|
|
CAp::Trace(StringFormat("max(|L|) = %.3E\n",v));
|
|
CAp::Trace(StringFormat("diag-err = %.3E (diagonal reproduction error)\n",MathSqrt(errsq / (1 + sumsq))));
|
|
}
|
|
}
|
|
//--- Done, integrity control
|
|
//--- check
|
|
if(!CAp::Assert(State.m_factorizationpresent,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
State.m_repncholesky++;
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| A low - level function which solves KKT system whose regularized |
|
|
//| (!) factorization was prepared by VIPMFactorize(). No iterative |
|
|
//| refinement is performed. |
|
|
//| On input, right - hand - side is stored in DeltaXY; on output, |
|
|
//| solution replaces DeltaXY. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::SolveReducedKKTSystem(CVIPMState &State,
|
|
CRowDouble &deltaxy)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int nmain=State.m_nmain;
|
|
int nslack=n-nmain;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int mdense=State.m_mdense;
|
|
int msparse=State.m_msparse;
|
|
int i=0;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_factorizationpresent,__FUNCTION__+": integrity check failed - factorization is not present"))
|
|
return;
|
|
if(!CAp::Assert(State.m_factorizationtype==0 || State.m_factorizationtype==1,__FUNCTION__+": unexpected factorization type"))
|
|
return;
|
|
//--- Dense solving
|
|
switch(State.m_factorizationtype)
|
|
{
|
|
case 0:
|
|
//--- Compute
|
|
//--- BzWave = inv(Dz+Rz)*Bz
|
|
//--- ByWave = By+Az*BzWave
|
|
//--- BhWave = Bh-Ah'*inv(EWave)*ByWave
|
|
for(i=0; i<nslack; i++)
|
|
deltaxy.Mul(nmain+i,State.m_factinvregdzrz[i]);
|
|
CSparse::SparseGemV(State.m_combinedaslack,1.0,0,deltaxy,nmain,1.0,deltaxy,n);
|
|
State.m_tmp1.Resize(m);
|
|
for(i=0; i<m; i++)
|
|
State.m_tmp1.Set(i,deltaxy[n+i]/State.m_factregewave[i]);
|
|
CSparse::SparseGemV(State.m_sparseamain,-1.0,1,State.m_tmp1,0,1.0,deltaxy,0);
|
|
CAblas::RMatrixGemVect(nmain,mdense,-1.0,State.m_denseamain,0,0,1,State.m_tmp1,msparse,1.0,deltaxy,0);
|
|
//--- Compute Xh = -inv(HWave)*BhWave.
|
|
//--- Zero out components corresponding to frozen variables.
|
|
for(i=0; i<nmain; i++)
|
|
deltaxy.Mul(i,-1);
|
|
CAblas::RMatrixTrsVect(nmain,State.m_factdensehaug,0,0,false,false,0,deltaxy,0);
|
|
CAblas::RMatrixTrsVect(nmain,State.m_factdensehaug,0,0,false,false,1,deltaxy,0);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_isfrozen[i])
|
|
deltaxy.Set(i,0);
|
|
}
|
|
//--- Compute Y = inv(EWave)*(ByWave-Ah*Xh)
|
|
CSparse::SparseGemV(State.m_sparseamain,-1.0,0,deltaxy,0,1.0,deltaxy,n);
|
|
CAblas::RMatrixGemVect(mdense,nmain,-1.0,State.m_denseamain,0,0,0,deltaxy,0,1.0,deltaxy,n+msparse);
|
|
for(i=0; i<m; i++)
|
|
deltaxy.Mul(n+i,1.0/State.m_factregewave[i]);
|
|
//--- Compute Xz = -(BzWave - inv(Dz+Rz)*Az'*y)
|
|
State.m_tmp0.Resize(nslack);
|
|
for(i=0; i<nslack; i++)
|
|
State.m_tmp0.Set(i,0);
|
|
CSparse::SparseGemV(State.m_combinedaslack,1.0,1,deltaxy,n,1.0,State.m_tmp0,0);
|
|
for(i=0; i<nslack; i++)
|
|
deltaxy.Set(nmain+i,-(deltaxy[nmain+i]-State.m_factinvregdzrz[i]*State.m_tmp0[i]));
|
|
//--- Done
|
|
break;
|
|
//--- Sparse solving
|
|
case 1:
|
|
ReducedSystemSolve(State.m_reducedsparsesystem,deltaxy);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_isfrozen[i])
|
|
deltaxy.Set(i,0);
|
|
}
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed - unexpected factorization");
|
|
break;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Generates precomputed temporary vectors and KKT factorization at |
|
|
//| the beginning of the current iteration. |
|
|
//| This function uses representation of KKT system inspired by |
|
|
//| Vanderbei slack variable approach, but with additional |
|
|
//| regularization being applied all along computations. |
|
|
//| On successful factorization returns True; on failure returns |
|
|
//| False - it is recommended to increase regularization parameter |
|
|
//| and try one more time. |
|
|
//| --- DESCRIPTION ------------------------------------------------ |
|
|
//| Initial KKT system proposed by Vanderbei has following structure:|
|
|
//| (1) -DS*deltaT-I*deltaS = -mu/T+s+DELTAT*DELTAS/T = -GammaS |
|
|
//| (2) -DZ*deltaG-I*deltaZ = -mu/G+z+DELTAG*DELTAZ/G = -GammaZ |
|
|
//| (3) -DQ*deltaP-I*deltaQ = -mu/P+q+DELTAP*DELTAQ/P = -GammaQ |
|
|
//| (4) -DW*deltaV-I*deltaW = -mu/V+w+DELTAV*DELTAW/V = -GammaW |
|
|
//| (5) - I*deltaY-I*deltaQ+I*deltaV = y-q+v = Beta |
|
|
//| (6) -H*deltaX+A'*deltaY+I*deltaZ-I*deltaS=c-A'*y-z+s+H*x=Sigma |
|
|
//| (7) A * deltaX - I * deltaW = b - A * x + w = Rho |
|
|
//| (8) I * deltaX - I * deltaG = l - x + g = Nu |
|
|
//| (9) -I * deltaX - I * deltaT = -u + x + t = -Tau |
|
|
//| (10) -I * deltaW - I * deltaP = -r + w + p = -Alpha |
|
|
//| where: |
|
|
//| DS = diag(S / T) |
|
|
//| DZ = diag(Z / G) |
|
|
//| DQ = diag(Q / P) |
|
|
//| DW = diag(W / V) |
|
|
//| This linear system is actually symmetric indefinite one, that can|
|
|
//| be regularized by modifying equations (5), (6), (7), (8), (9), |
|
|
//| (10): |
|
|
//| (5) -I*deltaY-I*deltaQ+I*deltaV-REG*deltaW = y+q-v+REG*w = Beta|
|
|
//| (6) -(H+REG)*deltaX+A'*deltaY+I*deltaZ-I*deltaS = |
|
|
//| c-A'*y-z+s+(H+REG)*x = Sigma |
|
|
//| (7) A*deltaX-I*deltaW+REG*deltaY = b-A*x+w-REG*y = Rho |
|
|
//| (8) I*deltaX-I*deltaG+REG*deltaZ = l-x+g-REG*z = Nu |
|
|
//| (9) -I*deltaX-I*deltaT+REG*deltaS = -u+x+t-REG*s = -Tau |
|
|
//| (10)-I*deltaW-I*deltaP+REG*deltaQ = -r+w+p-REG*q = -Alpha |
|
|
//| NOTE: regularizing equations (5)-(10) seems to be beneficial |
|
|
//| because their coefficients are well-normalized, usually |
|
|
//| having unit scale. Contrary to that, equations (1)-(4) are |
|
|
//| wildly nonnormalized, and regularization ruins algorithm |
|
|
//| convergence. |
|
|
//| From(1), (2), (3) and (4) we obtain |
|
|
//| deltaT = (GammaS - I * deltaS) / DS |
|
|
//| deltaG = (GammaZ - I * deltaZ) / DZ |
|
|
//| deltaP = (GammaQ - I * deltaQ) / DQ |
|
|
//| deltaV = (GammaW - I * deltaW) / DW |
|
|
//| and substitute them to equations to obtain |
|
|
//| (5) -I*deltaY-I*deltaQ-(inv(DW)+REG)*deltaW = |
|
|
//| Beta - inv(DW) * GammaW = BetaCap |
|
|
//| (8) I*deltaX+(inv(DZ)+REG)*deltaZ = Nu+inv(DZ)*GammaZ = NuCap |
|
|
//| (9) I*deltaX+(inv(DS)+REG)*deltaS=-(Tau-inv(DS)*GammaS)=-TauCap|
|
|
//| (10) -I * deltaW + (inv(DQ) + REG) * deltaQ = |
|
|
//| -(Alpha - inv(DQ) * GammaQ) = -AlphaCap |
|
|
//| (6) A'*deltaY + I*deltaZ - I*deltaS - (H+REG)*deltaX = |
|
|
//| c-A' * y - z + s + (H + REG) * x = Sigma |
|
|
//| (7) REG*deltaY+A*deltaX-I*deltaW = b-A*x+w-REG*y = Rho |
|
|
//| then, we obtain(here IRI stands for Invert - Regularize - Invert)|
|
|
//| DQIRI = inv(inv(DQ) + REG) |
|
|
//| DZIRI = inv(inv(DZ) + REG) |
|
|
//| DSIRI = inv(inv(DS) + REG) |
|
|
//| deltaQ = (I * deltaW - AlphaCap) * DQIRI |
|
|
//| deltaZ = (NuCap - I * deltaX) * DZIRI |
|
|
//| deltaS = (I * deltaX - TauCap) * DSIRI |
|
|
//| DWIR = inv(DW) + REG |
|
|
//| and after substitution |
|
|
//| (5) -I*deltaY-(DQIRI+DWIR)*deltaW = BetaCap-DQIRI*AlphaCap |
|
|
//| (6) A'*deltaY - (H+REG+DSIRI+DZIRI)*deltaX = |
|
|
//| Sigma-DSIRI*TauCap-DZIRI*NuCap |
|
|
//| (7) REG * deltaY + A * deltaX - I * deltaW = Rho |
|
|
//| finally, we obtain |
|
|
//| DE = inv(DQIRI + DWIR) |
|
|
//| DER = DE + REG |
|
|
//| DDR = DSIRI + DZIRI + REG |
|
|
//| deltaW = -(BetaCap - DQIRI * AlphaCap + I * deltaY) * DE |
|
|
//| and after substitution |
|
|
//| (6) - (H + DDR) * deltaX + A'*deltaY = |
|
|
//| Sigma-DSIRI*TauCap-DZIRI*NuCap |
|
|
//| (7) A*deltaX + DER*deltaY = Rho-DE*(BetaCap-DQIRI*AlphaCap) |
|
|
//+------------------------------------------------------------------+
|
|
bool CVIPMSolver::VIPMPrecomputeNewtonFactorization(CVIPMState &State,
|
|
CVIPMVars &v0,
|
|
double regeps,
|
|
double modeps,
|
|
double dampeps,
|
|
double dampfree)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int i=0;
|
|
|
|
State.m_diagdz=vector<double>::Zeros(n);
|
|
State.m_diagdzi=vector<double>::Zeros(n);
|
|
State.m_diagdziri=vector<double>::Zeros(n);
|
|
State.m_diagds=vector<double>::Zeros(n);
|
|
State.m_diagdsi=vector<double>::Zeros(n);
|
|
State.m_diagdsiri=vector<double>::Zeros(n);
|
|
State.m_diagdw=vector<double>::Zeros(m);
|
|
State.m_diagdwi=vector<double>::Zeros(m);
|
|
State.m_diagdwir=vector<double>::Zeros(m);
|
|
State.m_diagdq=vector<double>::Zeros(m);
|
|
State.m_diagdqi=vector<double>::Zeros(m);
|
|
State.m_diagdqiri=vector<double>::Zeros(m);
|
|
State.m_diagddr.Resize(n);
|
|
State.m_diagde.Resize(m);
|
|
State.m_diagder.Resize(m);
|
|
//--- Handle temporary matrices arising due to box constraints
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Lower bound: G*inv(Z) and Z*inv(G)
|
|
if(State.m_hasgz[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(v0.m_g[i]>0.0 && v0.m_z[i]>0.0,__FUNCTION__+": integrity failure - G[i]<=0 or Z[i]<=0"))
|
|
return(false);
|
|
State.m_diagdz.Set(i,v0.m_z[i]/v0.m_g[i]);
|
|
State.m_diagdzi.Set(i,1.0/State.m_diagdz[i]);
|
|
State.m_diagdziri.Set(i,1.0/(State.m_diagdzi[i]+regeps));
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(v0.m_g[i]==0.0 && v0.m_z[i]==0.0,__FUNCTION__+": integrity failure - G[i]<>0 or Z[i]<>0 for absent lower bound"))
|
|
return(false);
|
|
}
|
|
//--- Upper bound: T*inv(S) and S*inv(T)
|
|
if(State.m_hasts[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(v0.m_t[i]>0.0 && v0.m_s[i]>0.0,__FUNCTION__+": integrity failure - T[i]<=0 or S[i]<=0"))
|
|
return(false);
|
|
State.m_diagds.Set(i,v0.m_s[i]/v0.m_t[i]);
|
|
State.m_diagdsi.Set(i,1/State.m_diagds[i]);
|
|
State.m_diagdsiri.Set(i,1/(State.m_diagdsi[i]+regeps));
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(v0.m_t[i]==0.0 && v0.m_s[i]==0.0,__FUNCTION__+": integrity failure - T[i]<>0 or S[i]<>0 for absent upper bound"))
|
|
return(false);
|
|
}
|
|
//--- Diagonal term D
|
|
State.m_diagddr.Set(i,State.m_diagdziri[i]+State.m_diagdsiri[i]+regeps);
|
|
if(!State.m_hasgz[i] && !State.m_hasts[i])
|
|
State.m_diagddr.Add(i,dampfree);
|
|
}
|
|
//--- Handle temporary matrices arising due to linear constraints: with lower bound B[]
|
|
//--- or with lower and upper bounds.
|
|
for(i=0; i<m; i++)
|
|
{
|
|
//--- Lower bound
|
|
if(State.m_haswv[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(v0.m_v[i]>0.0 && v0.m_w[i]>0.0,__FUNCTION__+": integrity failure - V[i]<=0 or W[i]<=0"))
|
|
return(false);
|
|
State.m_diagdw.Set(i,v0.m_w[i]/v0.m_v[i]);
|
|
State.m_diagdwi.Set(i,1/State.m_diagdw[i]);
|
|
State.m_diagdwir.Set(i,State.m_diagdwi[i]+regeps);
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(v0.m_v[i]==0.0 && v0.m_w[i]==0.0,__FUNCTION__+": integrity failure - V[i]<>0 or W[i]<>0 for linear equality constraint"))
|
|
return(false);
|
|
}
|
|
//--- Upper bound
|
|
if(State.m_haspq[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(v0.m_p[i]>0.0 && v0.m_q[i]>0.0,__FUNCTION__+": integrity failure - P[i]<=0 or Q[i]<=0"))
|
|
return(false);
|
|
State.m_diagdq.Set(i,v0.m_q[i]/v0.m_p[i]);
|
|
State.m_diagdqi.Set(i,1/State.m_diagdq[i]);
|
|
State.m_diagdqiri.Set(i,1/(State.m_diagdqi[i]+regeps));
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(v0.m_p[i]==0.0 && v0.m_q[i]==0.0,__FUNCTION__+": integrity failure - P[i]<>0 or Q[i]<>0 for absent linear constraint"))
|
|
return(false);
|
|
}
|
|
//--- Diagonal term E
|
|
if(State.m_haswv[i] || State.m_haspq[i])
|
|
State.m_diagde.Set(i,1/(State.m_diagdwir[i]+State.m_diagdqiri[i]));
|
|
else
|
|
State.m_diagde.Set(i,0.0);
|
|
State.m_diagder.Set(i,State.m_diagde[i]+regeps);
|
|
}
|
|
//--- Perform factorization
|
|
result=VIPMFactorize(State,1.0,State.m_diagddr,1.0,State.m_diagder,0.0,0.0,modeps,dampeps);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Solves KKT system stored in VIPMState with user - passed RHS. |
|
|
//| Sol must be preallocated VIPMVars object whose initial values are|
|
|
//| ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::SolveKKTSystem(CVIPMState &State,
|
|
CVIPMRightHandSide &rhs,
|
|
CVIPMVars &sol)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int i=0;
|
|
//--- Compute elimination temporaries
|
|
//--- RhsAlphaCap = RhsAlpha - InvDQ*GammaQ
|
|
//--- RhsNuCap = RhsNu + InvDZ*GammaZ
|
|
//--- RhsTauCap = RhsTau - InvDS*GammaS
|
|
//--- RhsBetaCap = RhsBeta - InvDW*GammaW
|
|
State.m_rhsnucap.Resize(n);
|
|
State.m_rhstaucap.Resize(n);
|
|
State.m_rhsbetacap.Resize(m);
|
|
State.m_rhsalphacap.Resize(m);
|
|
CAblasF::RCopyNegMulAddV(m,State.m_diagdqi,rhs.m_gammaq,rhs.m_alpha,State.m_rhsalphacap);
|
|
CAblasF::RCopyMulAddV(n,State.m_diagdzi,rhs.m_gammaz,rhs.m_nu,State.m_rhsnucap);
|
|
CAblasF::RCopyNegMulAddV(n,State.m_diagdsi,rhs.m_gammas,rhs.m_tau,State.m_rhstaucap);
|
|
CAblasF::RCopyNegMulAddV(m,State.m_diagdwi,rhs.m_gammaw,rhs.m_beta,State.m_rhsbetacap);
|
|
//--- Solve reduced KKT system
|
|
State.m_deltaxy.Resize(n+m);
|
|
for(i=0; i<n; i++)
|
|
State.m_deltaxy.Set(i,rhs.m_sigma[i]-State.m_diagdziri[i]*State.m_rhsnucap[i]-State.m_diagdsiri[i]*State.m_rhstaucap[i]);
|
|
for(i=0; i<m; i++)
|
|
State.m_deltaxy.Set(n+i,rhs.m_rho[i]-State.m_diagde[i]*(State.m_rhsbetacap[i]-State.m_diagdqiri[i]*State.m_rhsalphacap[i]));
|
|
SolveReducedKKTSystem(State,State.m_deltaxy);
|
|
//--- Perform backsubstitution
|
|
for(i=0; i<n; i++)
|
|
{
|
|
sol.m_x.Set(i,State.m_deltaxy[i]);
|
|
sol.m_s.Set(i,State.m_diagdsiri[i]*(sol.m_x[i]-State.m_rhstaucap[i]));
|
|
sol.m_z.Set(i,State.m_diagdziri[i]*(State.m_rhsnucap[i]-sol.m_x[i]));
|
|
sol.m_g.Set(i,State.m_diagdzi[i]*(rhs.m_gammaz[i]-sol.m_z[i]));
|
|
sol.m_t.Set(i,State.m_diagdsi[i]*(rhs.m_gammas[i]-sol.m_s[i]));
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
sol.m_y.Set(i,State.m_deltaxy[n+i]);
|
|
sol.m_w.Set(i,-(State.m_diagde[i]*(State.m_rhsbetacap[i]-State.m_diagdqiri[i]*State.m_rhsalphacap[i]+sol.m_y[i])));
|
|
sol.m_q.Set(i,State.m_diagdqiri[i]*(sol.m_w[i]-State.m_rhsalphacap[i]));
|
|
sol.m_v.Set(i,State.m_diagdwi[i]*(rhs.m_gammaw[i]-sol.m_w[i]));
|
|
sol.m_p.Set(i,State.m_diagdqi[i]*(rhs.m_gammaq[i]-sol.m_q[i]));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Compute VIPM step by solving KKT system. |
|
|
//| VDResult must be preallocated VIPMVars object whose initial |
|
|
//| values are ignored. |
|
|
//| Returns False on failure to compute step direction with |
|
|
//| reasonable accuracy (it is advised to terminate iterations |
|
|
//| immediately). |
|
|
//+------------------------------------------------------------------+
|
|
bool CVIPMSolver::VIPMComputeStepDirection(CVIPMState &State,
|
|
CVIPMVars &v0,
|
|
double muestimate,
|
|
CVIPMVars &vdestimate,
|
|
CVIPMVars &vdresult,
|
|
double reg,
|
|
bool isdampepslarge)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
double vrhsprim2=0;
|
|
double vrhsdual2=0;
|
|
double vrhscmpl2=0;
|
|
double vresprim2=0;
|
|
double vresdual2=0;
|
|
double vrescmpl2=0;
|
|
double vrhspriminf=0;
|
|
double vrhsdualinf=0;
|
|
double vrespriminf=0;
|
|
double vresdualinf=0;
|
|
double badres=1.01;
|
|
double verybadres=1.0E3;
|
|
double residualgrowth=0;
|
|
bool primaldestabilized=false;
|
|
bool dualdestabilized=false;
|
|
//--- Initial m_solver report
|
|
if(State.m_dotrace)
|
|
CAp::Trace("--- detailed KKT solver report ---------------------------------------------------------------------\n");
|
|
//--- Solve KKT system with right-hand sides coming from primal, dual
|
|
//--- and complementary slackness conditions. Analyze solution,
|
|
//--- terminate immediately if primal/dual residuals are way too high.
|
|
RHSCompute(State,v0,muestimate,vdestimate,State.m_rhs,reg);
|
|
vrhsprim2=RHSPrimal2(State.m_rhs);
|
|
vrhsdual2=RHSDual2(State.m_rhs);
|
|
vrhscmpl2=RHSCompl2(State.m_rhs);
|
|
vrhspriminf=RHSPrimalInf(State.m_rhs);
|
|
vrhsdualinf=RHSDualInf(State.m_rhs);
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> primal/dual/complementarity right-hand-side\n");
|
|
CAp::Trace(StringFormat("rhs-prim = %.3E (2-norm)\n",MathSqrt(vrhsprim2)));
|
|
CAp::Trace(StringFormat("rhs-dual = %.3E (2-norm)\n",MathSqrt(vrhsdual2)));
|
|
CAp::Trace(StringFormat("rhs-cmpl = %.3E (2-norm)\n",MathSqrt(vrhscmpl2)));
|
|
}
|
|
SolveKKTSystem(State,State.m_rhs,vdresult);
|
|
RHSSubtract(State,State.m_rhs,v0,vdresult,reg);
|
|
vresprim2=RHSPrimal2(State.m_rhs);
|
|
vresdual2=RHSDual2(State.m_rhs);
|
|
vrescmpl2=RHSCompl2(State.m_rhs);
|
|
vrespriminf=RHSPrimalInf(State.m_rhs);
|
|
vresdualinf=RHSDualInf(State.m_rhs);
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> primal/dual/complementarity residuals compared with RHS\n");
|
|
CAp::Trace(StringFormat("res/rhs prim = %.3E\n",MathSqrt(vresprim2 / CApServ::Coalesce(vrhsprim2,1))));
|
|
CAp::Trace(StringFormat("res/rhs dual = %.3E\n",MathSqrt(vresdual2 / CApServ::Coalesce(vrhsdual2,1))));
|
|
CAp::Trace(StringFormat("res/rhs cmpl = %.3E\n",MathSqrt(vrescmpl2 / CApServ::Coalesce(vrhscmpl2,1))));
|
|
CAp::Trace(StringFormat("res/rhs all = %.3E\n",MathSqrt((vresprim2 + vresdual2 + vrescmpl2) / CApServ::Coalesce(vrhsprim2 + vrhsdual2 + vrhscmpl2,1))));
|
|
}
|
|
primaldestabilized=(vrhspriminf<=State.m_epsp && vrespriminf>=MathMax(verybadres*vrhspriminf,State.m_epsp));
|
|
dualdestabilized=(vrhsdualinf<=State.m_epsd && vresdualinf>=MathMax(verybadres*vrhsdualinf,State.m_epsd));
|
|
residualgrowth=MathSqrt((vresprim2+vresdual2+vrescmpl2)/CApServ::Coalesce(vrhsprim2+vrhsdual2+vrhscmpl2,1));
|
|
if((primaldestabilized || dualdestabilized) && residualgrowth>(0.01*MathSqrt(CMath::m_machineepsilon)) && !isdampepslarge)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> primal/dual residual growth is too high,signaling presence of numerical errors\n");
|
|
return(false);
|
|
}
|
|
if(residualgrowth>badres)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> total residual is too high,signaling presence of numerical errors\n");
|
|
return(false);
|
|
}
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function estimates primal and dual step lengths (subject to |
|
|
//| step decay parameter, which should be in [0, 1] range). |
|
|
//| Current version returns same step lengths for primal and dual |
|
|
//| steps. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - solver State |
|
|
//| V0 - current point (we ignore one stored in |
|
|
//| State.Current) |
|
|
//| VS - step direction |
|
|
//| StepDecay- decay parameter, the step is multiplied by this |
|
|
//| coefficient. 1.0 corresponds to full step length |
|
|
//| being returned. Values in (0, 1] range. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| AlphaP - primal step(after applying decay coefficient) |
|
|
//| AlphaD - dual step(after applying decay coefficient) |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMComputeStepLength(CVIPMState &State,
|
|
CVIPMVars &v0,
|
|
CVIPMVars &vs,
|
|
double stepdecay,
|
|
double &alphap,
|
|
double &alphad)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int i=0;
|
|
double alpha=0;
|
|
alphap=0;
|
|
alphad=0;
|
|
//--- check
|
|
if(!CAp::Assert(n==v0.m_n && m==v0.m_m,__FUNCTION__+": sizes mismatch"))
|
|
return;
|
|
alphap=1;
|
|
alphad=1;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Primal
|
|
if(vs.m_g[i]<0.0)
|
|
alphap=CApServ::SafeMinPosRV(v0.m_g[i],-vs.m_g[i],alphap);
|
|
if(vs.m_t[i]<0.0)
|
|
alphap=CApServ::SafeMinPosRV(v0.m_t[i],-vs.m_t[i],alphap);
|
|
//--- Dual
|
|
if(vs.m_z[i]<0.0)
|
|
alphad=CApServ::SafeMinPosRV(v0.m_z[i],-vs.m_z[i],alphad);
|
|
if(vs.m_s[i]<0.0)
|
|
alphad=CApServ::SafeMinPosRV(v0.m_s[i],-vs.m_s[i],alphad);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
//--- Primal
|
|
if(vs.m_w[i]<0.0)
|
|
alphap=CApServ::SafeMinPosRV(v0.m_w[i],-vs.m_w[i],alphap);
|
|
if(vs.m_p[i]<0.0)
|
|
alphap=CApServ::SafeMinPosRV(v0.m_p[i],-vs.m_p[i],alphap);
|
|
//--- Dual
|
|
if(vs.m_v[i]<0.0)
|
|
alphad=CApServ::SafeMinPosRV(v0.m_v[i],-vs.m_v[i],alphad);
|
|
if(vs.m_q[i]<0.0)
|
|
alphad=CApServ::SafeMinPosRV(v0.m_q[i],-vs.m_q[i],alphad);
|
|
}
|
|
//--- Because we may solve QP problem, step length has to be same for primal and dual variables
|
|
alpha=MathMin(alphap,alphad);
|
|
//--- Apply decay
|
|
alphap=stepdecay*alpha;
|
|
alphad=stepdecay*alpha;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs IPM step, updates iteration counts and |
|
|
//| performs following additional checks: |
|
|
//| * it monitors status of box/linear constraints and smoothly |
|
|
//| drops ones with too large bounds (a variable or linear sum is|
|
|
//| well below constraint bound for several iterations) |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - m_solver State |
|
|
//| AlphaP - primal step to perform |
|
|
//| AlphaD - dual step to perform |
|
|
//| OUTPUT PARAMETERS: |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::VIPMPerformStep(CVIPMState &State,
|
|
double alphap,
|
|
double alphad)
|
|
{
|
|
//--- Perform step
|
|
VarsAddStep(State.m_current,State.m_deltacorr,alphap,alphad);
|
|
//--- Update iterations count
|
|
State.m_repiterationscount++;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Compute primal / dual errors and complementarity gap |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::ComputeErrors(CVIPMState &State,
|
|
double &errp2,
|
|
double &errd2,
|
|
double &errpinf,
|
|
double &errdinf,
|
|
double &egap)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int i=0;
|
|
int cntp2=0;
|
|
int cntd2=0;
|
|
double v=0;
|
|
|
|
errp2=0;
|
|
errd2=0;
|
|
errpinf=0;
|
|
errdinf=0;
|
|
egap=0;
|
|
//--- Compute primal and dual infeasibilities
|
|
VIPMMultiply(State,State.m_current.m_x,State.m_current.m_y,State.m_tmphx,State.m_tmpax,State.m_tmpaty);
|
|
cntp2=0;
|
|
errp2=0;
|
|
errpinf=0;
|
|
for(i=0; i<m; i++)
|
|
{
|
|
v=State.m_tmpax[i]-State.m_current.m_w[i]-State.m_b[i];
|
|
errp2=errp2+v*v;
|
|
errpinf=MathMax(errpinf,MathAbs(v));
|
|
cntp2++;
|
|
if(State.m_haspq[i])
|
|
{
|
|
v=State.m_current.m_w[i]+State.m_current.m_p[i]-State.m_r[i];
|
|
errp2=errp2+v*v;
|
|
errpinf=MathMax(errpinf,MathAbs(v));
|
|
cntp2++;
|
|
}
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_hasgz[i])
|
|
{
|
|
v=State.m_current.m_x[i]-State.m_current.m_g[i]-State.m_bndl[i];
|
|
errp2+=v*v;
|
|
errpinf=MathMax(errpinf,MathAbs(v));
|
|
cntp2++;
|
|
}
|
|
if(State.m_hasts[i])
|
|
{
|
|
v=State.m_current.m_x[i]+State.m_current.m_t[i]-State.m_bndu[i];
|
|
errp2+=v*v;
|
|
errpinf=MathMax(errpinf,MathAbs(v));
|
|
cntp2++;
|
|
}
|
|
}
|
|
errp2=MathSqrt(errp2/CApServ::Coalesce(cntp2,1));
|
|
cntd2=0;
|
|
errd2=0;
|
|
errdinf=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!State.m_isfrozen[i])
|
|
{
|
|
v=State.m_tmphx[i]+State.m_c[i]-State.m_tmpaty[i];
|
|
if(State.m_hasgz[i])
|
|
v-=State.m_current.m_z[i];
|
|
if(State.m_hasts[i])
|
|
v+=State.m_current.m_s[i];
|
|
errd2+=v*v;
|
|
errdinf=MathMax(errdinf,MathAbs(v));
|
|
cntd2++;
|
|
}
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
v=0;
|
|
if(State.m_haswv[i])
|
|
v=State.m_current.m_y[i]-State.m_current.m_v[i];
|
|
if(State.m_haspq[i])
|
|
v+=State.m_current.m_q[i];
|
|
errd2+=v*v;
|
|
errdinf=MathMax(errdinf,MathAbs(v));
|
|
if(State.m_haswv[i] || State.m_haspq[i])
|
|
cntd2++;
|
|
}
|
|
errd2=MathSqrt(errd2/CApServ::Coalesce(cntd2,1));
|
|
egap=VarsComputeComplementarityGap(State.m_current)/(1.0+MathAbs(VIPMTarget(State,State.m_current.m_x)));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Performs integrity checks for current point and step |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::RunIntegrityChecks(CVIPMState &State,
|
|
CVIPMVars &v0,
|
|
CVIPMVars &vd,
|
|
double alphap,
|
|
double alphad)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int i=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(alphap) && alphap>=0.0,__FUNCTION__+": bad AlphaP"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(alphad) && alphad>=0.0,__FUNCTION__+": bad AlphaD"))
|
|
return;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_hasgz[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(!State.m_isfrozen[i],__FUNCTION__+": integrity failure - X[I] is frozen"))
|
|
return;
|
|
if(!CAp::Assert(v0.m_g[i]>0.0 && v0.m_z[i]>0.0,__FUNCTION__+": integrity failure - G[i]<=0 or Z[i]<=0"))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(v0.m_g[i]==0.0 && v0.m_z[i]==0.0,__FUNCTION__+": integrity failure - G[i]<>0 or Z[i]<>0 for absent lower bound"))
|
|
return;
|
|
if(!CAp::Assert(vd.m_g[i]==0.0 && vd.m_z[i]==0.0,__FUNCTION__+": integrity failure - G[i]<>0 or Z[i]<>0 for absent lower bound"))
|
|
return;
|
|
}
|
|
if(State.m_hasts[i])
|
|
{
|
|
if(!CAp::Assert(!State.m_isfrozen[i],__FUNCTION__+": integrity failure - X[I] is frozen"))
|
|
return;
|
|
if(!CAp::Assert(v0.m_t[i]>0.0 && v0.m_s[i]>0.0,__FUNCTION__+": integrity failure - T[i]<=0 or S[i]<=0"))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if(!CAp::Assert(v0.m_t[i]==0.0 && v0.m_s[i]==0.0,__FUNCTION__+": integrity failure - T[i]<>0 or S[i]<>0 for absent upper bound"))
|
|
return;
|
|
if(!CAp::Assert(vd.m_t[i]==0.0 && vd.m_s[i]==0.0,__FUNCTION__+": integrity failure - T[i]<>0 or S[i]<>0 for absent upper bound"))
|
|
return;
|
|
}
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(!CAp::Assert(State.m_haswv[i] || !State.m_haspq[i],__FUNCTION__+": inconsistent HasWV/HasPQ"))
|
|
return;
|
|
if(State.m_haswv[i])
|
|
{
|
|
if(!CAp::Assert(v0.m_v[i]>0.0 && v0.m_w[i]>0.0,__FUNCTION__+": integrity failure - V[i]<=0 or W[i]<=0"))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if(!CAp::Assert(v0.m_v[i]==0.0 && v0.m_w[i]==0.0,__FUNCTION__+": integrity failure - V[i]<>0 or W[i]<>0 for linear equality constraint"))
|
|
return;
|
|
if(!CAp::Assert(vd.m_v[i]==0.0 && vd.m_w[i]==0.0,__FUNCTION__+": integrity failure - V[i]<>0 or W[i]<>0 for linear equality constraint"))
|
|
return;
|
|
}
|
|
if(State.m_haspq[i])
|
|
{
|
|
if(!CAp::Assert(v0.m_p[i]>0.0 && v0.m_q[i]>0.0,__FUNCTION__+": integrity failure - P[i]<=0 or Q[i]<=0"))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if(!CAp::Assert(v0.m_p[i]==0.0 && v0.m_q[i]==0.0,__FUNCTION__+": integrity failure - P[i]<>0 or Q[i]<>0 for absent range of linear constraint"))
|
|
return;
|
|
if(!CAp::Assert(vd.m_p[i]==0.0 && vd.m_q[i]==0.0,__FUNCTION__+": integrity failure - P[i]<>0 or Q[i]<>0 for absent range of linear constraint"))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Evaluate progress so far, outputs trace data, if requested to do |
|
|
//| so. |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::TraceProgress(CVIPMState &State,
|
|
double mu,
|
|
double muaff,
|
|
double sigma,
|
|
double alphap,
|
|
double alphad)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int i=0;
|
|
double v=0;
|
|
double errp2=0;
|
|
double errd2=0;
|
|
double errpinf=0;
|
|
double errdinf=0;
|
|
double errgap=0;
|
|
|
|
if(!State.m_dotrace)
|
|
return;
|
|
//--- Print high-level information
|
|
ComputeErrors(State,errp2,errd2,errpinf,errdinf,errgap);
|
|
CAp::Trace("--- step report ------------------------------------------------------------------------------------\n");
|
|
CAp::Trace("> step information\n");
|
|
CAp::Trace(StringFormat("mu_init = %.3E (at the beginning)\n",mu));
|
|
CAp::Trace(StringFormat("mu_aff = %.3E (by affine scaling step)\n",muaff));
|
|
CAp::Trace(StringFormat("sigma = %.3E (centering parameter)\n",sigma));
|
|
CAp::Trace(StringFormat("alphaP = %.3E (primal step)\n",alphap));
|
|
CAp::Trace(StringFormat("alphaD = %.3E (dual step)\n",alphad));
|
|
CAp::Trace(StringFormat("mu_cur = %.3E (after the step)\n",VARSComputeMu(State,State.m_current)));
|
|
CAp::Trace("> errors\n");
|
|
CAp::Trace(StringFormat("errP = %.3E (primal infeasibility,inf-norm)\n",errpinf));
|
|
CAp::Trace(StringFormat("errD = %.3E (dual infeasibility, inf-norm)\n",errdinf));
|
|
CAp::Trace(StringFormat("errGap = %.3E (complementarity gap)\n",errgap));
|
|
CAp::Trace("> current point information (inf-norm)\n");
|
|
CAp::Trace(StringFormat("|X|=%8.1E,|G|=%8.1E,|T|=%8.1E,|W|=%8.1E,|P|=%8.1E\n",CAblasF::RMaxAbsV(n,State.m_current.m_x),CAblasF::RMaxAbsV(n,State.m_current.m_g),CAblasF::RMaxAbsV(n,State.m_current.m_t),CAblasF::RMaxAbsV(m,State.m_current.m_w),CAblasF::RMaxAbsV(m,State.m_current.m_p)));
|
|
CAp::Trace(StringFormat("|Y|=%8.1E,|Z|=%8.1E,|S|=%8.1E,|V|=%8.1E,|Q|=%8.1E\n",CAblasF::RMaxAbsV(m,State.m_current.m_y),CAblasF::RMaxAbsV(n,State.m_current.m_z),CAblasF::RMaxAbsV(n,State.m_current.m_s),CAblasF::RMaxAbsV(m,State.m_current.m_v),CAblasF::RMaxAbsV(m,State.m_current.m_q)));
|
|
//--- Print variable stats, if required
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("--- variable statistics ----------------------------------------------------------------------------\n");
|
|
CAp::Trace("> smallest values for nonnegative vars\n");
|
|
CAp::Trace(StringFormat("primal: minG=%8.1E minT=%8.1E minW=%8.1E minP=%8.1E\n",MinNZ(State.m_current.m_g,n),MinNZ(State.m_current.m_t,n),MinNZ(State.m_current.m_w,m),MinNZ(State.m_current.m_p,m)));
|
|
CAp::Trace(StringFormat("dual: minZ=%8.1E minS=%8.1E minV=%8.1E minQ=%8.1E\n",MinNZ(State.m_current.m_z,n),MinNZ(State.m_current.m_s,n),MinNZ(State.m_current.m_v,m),MinNZ(State.m_current.m_q,m)));
|
|
CAp::Trace("> min and max complementary slackness\n");
|
|
CAp::Trace(StringFormat("min: GZ=%8.1E TS=%8.1E WV=%8.1E PQ=%8.1E\n",MinProdNZ(State.m_current.m_g,State.m_current.m_z,n),MinProdNZ(State.m_current.m_t,State.m_current.m_s,n),MinProdNZ(State.m_current.m_w,State.m_current.m_v,m),MinProdNZ(State.m_current.m_p,State.m_current.m_q,m)));
|
|
CAp::Trace(StringFormat("max: GZ=%8.1E TS=%8.1E WV=%8.1E PQ=%8.1E\n",MaxProdNZ(State.m_current.m_g,State.m_current.m_z,n),MaxProdNZ(State.m_current.m_t,State.m_current.m_s,n),MaxProdNZ(State.m_current.m_w,State.m_current.m_v,m),MaxProdNZ(State.m_current.m_p,State.m_current.m_q,m)));
|
|
}
|
|
//--- Detailed output (all variables values, not suited for high-dimensional problems)
|
|
if(State.m_dodetailedtrace)
|
|
{
|
|
VIPMMultiply(State,State.m_current.m_x,State.m_current.m_y,State.m_tmphx,State.m_tmpax,State.m_tmpaty);
|
|
State.m_tmplaggrad=vector<double>::Zeros(n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!State.m_isfrozen[i])
|
|
{
|
|
v=State.m_tmphx[i]+State.m_c[i]-State.m_tmpaty[i];
|
|
if(State.m_hasgz[i])
|
|
v-=State.m_current.m_z[i];
|
|
if(State.m_hasts[i])
|
|
v+=State.m_current.m_s[i];
|
|
State.m_tmplaggrad.Set(i,v);
|
|
}
|
|
}
|
|
CAp::Trace("--- printing raw data (prior to applying variable scales and Shifting by XOrigin) ------------------\n");
|
|
CAp::Trace("X (raw) = ");
|
|
CApServ::TraceVectorUnscaledUnshiftedAutopRec(State.m_current.m_x,n,State.m_scl,true,State.m_xorigin,true);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("--- printing scaled data (after applying variable scales and Shifting by XOrigin) ------------------\n");
|
|
CAp::Trace("> reporting X,Lagrangian gradient\n");
|
|
CAp::Trace("Xnew = ");
|
|
CApServ::TraceVectorAutopRec(State.m_current.m_x,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("Lag-grad = ");
|
|
CApServ::TraceVectorAutopRec(State.m_tmplaggrad,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("--- printing new point -----------------------------------------------------------------------------\n");
|
|
CAp::Trace("> primal slacks and dual multipliers for box constraints\n");
|
|
CAp::Trace("G (L prim slck) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_current.m_g,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("Z (L dual mult) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_current.m_z,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("T (U prim slck) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_current.m_t,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("S (U dual mult) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_current.m_s,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("> primal slacks and dual multipliers for linear constraints,B/R stand for B<=Ax<=B+R\n");
|
|
CAp::Trace("Y (lag mult) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_current.m_y,0,m);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("W (B prim slck) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_current.m_w,0,m);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("V (B dual mult) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_current.m_v,0,m);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("P (R prim slck) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_current.m_p,0,m);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("Q (R dual mult) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_current.m_q,0,m);
|
|
CAp::Trace("\n");
|
|
}
|
|
CAp::Trace("\n");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Compute right - hand side for KKT system. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - IPM State |
|
|
//| V0 - current point(used to compute RHS) |
|
|
//| MuEstimate - estimate of Mu(can be zero) |
|
|
//| DirEstimate - estimate of delta's (can be zero) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rhs - RHS |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::RHSCompute(CVIPMState &State,
|
|
CVIPMVars &v0,
|
|
double muestimate,
|
|
CVIPMVars &direstimate,
|
|
CVIPMRightHandSide &rhs,
|
|
double reg)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int i=0;
|
|
//--- Allocate
|
|
rhs.m_sigma.Resize(n);
|
|
rhs.m_nu.Resize(n);
|
|
rhs.m_tau.Resize(n);
|
|
rhs.m_gammaz.Resize(n);
|
|
rhs.m_gammas.Resize(n);
|
|
rhs.m_gammaw.Resize(m);
|
|
rhs.m_gammaq.Resize(m);
|
|
rhs.m_beta=vector<double>::Zeros(m);
|
|
rhs.m_rho=vector<double>::Zeros(m);
|
|
rhs.m_alpha=vector<double>::Zeros(m);
|
|
//--- Compute products H*x, A*x, A^T*y
|
|
//--- We compute these products in one location for the sake of simplicity.
|
|
VIPMMultiply(State,v0.m_x,v0.m_y,State.m_tmphx,State.m_tmpax,State.m_tmpaty);
|
|
//--- Compute right-hand side:
|
|
//--- Rho = b - A*x + w
|
|
//--- Nu = l - x + g
|
|
//--- Tau = u - x - t
|
|
//--- Alpha = r - w - p
|
|
//--- Sigma = c - A^T*y - z + s + (H+REG)*x
|
|
//--- Beta = y + q - v
|
|
for(i=0; i<m; i++)
|
|
{
|
|
rhs.m_rho.Set(i,State.m_b[i]-State.m_tmpax[i]-reg*v0.m_y[i]);
|
|
if(State.m_haswv[i])
|
|
{
|
|
//--- Inequality/range constraint
|
|
rhs.m_rho.Add(i,v0.m_w[i]);
|
|
}
|
|
else
|
|
{
|
|
//--- Equality constraint without slack variables, W[i]=0
|
|
if(!CAp::Assert(v0.m_w[i]==0.0,__FUNCTION__+": W[i]<>0 for linear equality constraint"))
|
|
return;
|
|
}
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_hasgz[i])
|
|
{
|
|
//--- Lower bound is present
|
|
rhs.m_nu.Set(i,State.m_bndl[i]-v0.m_x[i]+v0.m_g[i]-reg*v0.m_z[i]);
|
|
}
|
|
else
|
|
{
|
|
//--- Lower bound is absent, g.Set(i, 0
|
|
if(!CAp::Assert(v0.m_g[i]==0.0,__FUNCTION__+": G[i]<>0 for absent constraint"))
|
|
return;
|
|
rhs.m_nu.Set(i,0);
|
|
}
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_hasts[i])
|
|
{
|
|
//--- Upper bound is present
|
|
rhs.m_tau.Set(i,State.m_bndu[i]-v0.m_x[i]-v0.m_t[i]+reg*v0.m_s[i]);
|
|
}
|
|
else
|
|
{
|
|
//--- Upper bound is absent, t.Set(i, 0
|
|
if(!CAp::Assert(v0.m_t[i]==0.0,__FUNCTION__+": T[i]<>0 for absent constraint"))
|
|
return;
|
|
rhs.m_tau.Set(i,0);
|
|
}
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(State.m_haspq[i])
|
|
rhs.m_alpha.Set(i,State.m_r[i]-v0.m_w[i]-v0.m_p[i]+reg*v0.m_q[i]);
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!State.m_isfrozen[i])
|
|
{
|
|
rhs.m_sigma.Set(i,State.m_c[i]-State.m_tmpaty[i]+State.m_tmphx[i]+reg*v0.m_x[i]);
|
|
if(State.m_hasgz[i])
|
|
rhs.m_sigma.Add(i,-v0.m_z[i]);
|
|
if(State.m_hasts[i])
|
|
rhs.m_sigma.Add(i,v0.m_s[i]);
|
|
}
|
|
else
|
|
rhs.m_sigma.Set(i,0);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(State.m_haswv[i])
|
|
rhs.m_beta.Add(i,v0.m_y[i]-v0.m_v[i]+reg*v0.m_w[i]);
|
|
if(State.m_haspq[i])
|
|
rhs.m_beta.Add(i,v0.m_q[i]);
|
|
}
|
|
//--- Compute right-hand side:
|
|
//--- GammaZ = mu*inv(G)*e - z - inv(G)*DELTAG*deltaZ
|
|
//--- GammaW = mu*inv(V)*e - w - inv(V)*DELTAV*deltaW
|
|
//--- GammaS = mu*inv(T)*e - s - inv(T)*DELTAT*deltaS
|
|
//--- GammaQ = mu*inv(P)*e - q - inv(P)*DELTAP*deltaQ
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_hasgz[i])
|
|
{
|
|
if(!CAp::Assert(v0.m_g[i]>0.0,__FUNCTION__+": G[i]<=0"))
|
|
return;
|
|
rhs.m_gammaz.Set(i,muestimate/v0.m_g[i]-v0.m_z[i]-direstimate.m_g[i]*direstimate.m_z[i]/v0.m_g[i]);
|
|
}
|
|
else
|
|
{
|
|
if(!CAp::Assert(v0.m_g[i]==0.0,__FUNCTION__+": G[i]<>0 for absent constraint"))
|
|
return;
|
|
if(!CAp::Assert(v0.m_z[i]==0.0,__FUNCTION__+": Z[i]<>0 for absent constraint"))
|
|
return;
|
|
rhs.m_gammaz.Set(i,0);
|
|
}
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(State.m_haswv[i])
|
|
{
|
|
//--- Inequality/range constraint
|
|
if(!CAp::Assert(v0.m_v[i]>0.0,__FUNCTION__+": V[i]<=0"))
|
|
return;
|
|
rhs.m_gammaw.Set(i,muestimate/v0.m_v[i]-v0.m_w[i]-direstimate.m_v[i]*direstimate.m_w[i]/v0.m_v[i]);
|
|
}
|
|
else
|
|
{
|
|
//--- Equality constraint
|
|
if(!CAp::Assert(v0.m_v[i]==0.0,__FUNCTION__+": V[i]<>0 for equality constraint"))
|
|
return;
|
|
if(!CAp::Assert(v0.m_w[i]==0.0,__FUNCTION__+": W[i]<>0 for equality constraint"))
|
|
return;
|
|
rhs.m_gammaw.Set(i,0);
|
|
}
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_hasts[i])
|
|
{
|
|
//--- Upper bound is present
|
|
if(!CAp::Assert(v0.m_t[i]>0.0,__FUNCTION__+": T[i]<=0"))
|
|
return;
|
|
rhs.m_gammas.Set(i,muestimate/v0.m_t[i]-v0.m_s[i]-direstimate.m_t[i]*direstimate.m_s[i]/v0.m_t[i]);
|
|
}
|
|
else
|
|
{
|
|
//--- Upper bound is absent
|
|
if(!CAp::Assert(v0.m_t[i]==0.0,__FUNCTION__+": T[i]<>0 for absent constraint"))
|
|
return;
|
|
if(!CAp::Assert(v0.m_s[i]==0.0,__FUNCTION__+": S[i]<>0 for absent constraint"))
|
|
return;
|
|
rhs.m_gammas.Set(i,0);
|
|
}
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(State.m_haspq[i])
|
|
{
|
|
if(!CAp::Assert(v0.m_p[i]>0.0,__FUNCTION__+": P[i]<=0"))
|
|
return;
|
|
rhs.m_gammaq.Set(i,muestimate/v0.m_p[i]-v0.m_q[i]-direstimate.m_p[i]*direstimate.m_q[i]/v0.m_p[i]);
|
|
}
|
|
else
|
|
{
|
|
if(!CAp::Assert(v0.m_p[i]==0.0,__FUNCTION__+": P[i]<>0 for absent range"))
|
|
return;
|
|
if(!CAp::Assert(v0.m_q[i]==0.0,__FUNCTION__+": Q[i]<>0 for absent range"))
|
|
return;
|
|
rhs.m_gammaq.Set(i,0);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Subtracts KKT*cand from already computed RHS. |
|
|
//| A pair of RHSCompute/RHSSubtract calls results in residual being |
|
|
//| loaded into the RHS structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - IPM State |
|
|
//| V0 - current point(used to compute RHS) |
|
|
//| MuEstimate - estimate of Mu(can be zero) |
|
|
//| DirEstimate - estimate of delta's (can be zero) |
|
|
//| ResidualFrom- whether we want to compute RHS or residual |
|
|
//| computed using VDCandidate |
|
|
//| VDCandidate - solution candidate |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rhs - either RHS or residual RHS - KKT*Cand |
|
|
//+------------------------------------------------------------------+
|
|
void CVIPMSolver::RHSSubtract(CVIPMState &State,
|
|
CVIPMRightHandSide &rhs,
|
|
CVIPMVars &v0,
|
|
CVIPMVars &vdcandidate,
|
|
double reg)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
int i=0;
|
|
|
|
VIPMMultiply(State,vdcandidate.m_x,vdcandidate.m_y,State.m_tmphx,State.m_tmpax,State.m_tmpaty);
|
|
//--- Residual for Rho, Nu, Tau, Alpha, Sigma, Beta
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(State.m_haswv[i])
|
|
{
|
|
rhs.m_rho.Add(i,-(State.m_tmpax[i]-vdcandidate.m_w[i]+reg*vdcandidate.m_y[i]));
|
|
rhs.m_beta.Add(i,-(-vdcandidate.m_y[i]+vdcandidate.m_v[i]-reg*vdcandidate.m_w[i]));
|
|
rhs.m_gammaw.Add(i,-(v0.m_w[i]/v0.m_v[i]*vdcandidate.m_v[i]+vdcandidate.m_w[i]));
|
|
}
|
|
else
|
|
rhs.m_rho.Add(i,-(State.m_tmpax[i]+reg*vdcandidate.m_y[i]));
|
|
//---
|
|
if(State.m_haspq[i])
|
|
{
|
|
rhs.m_alpha.Add(i,-(vdcandidate.m_w[i]+vdcandidate.m_p[i]-reg*vdcandidate.m_q[i]));
|
|
rhs.m_beta.Add(i,vdcandidate.m_q[i]);
|
|
rhs.m_gammaq.Add(i,-(v0.m_q[i]/v0.m_p[i]*vdcandidate.m_p[i]+vdcandidate.m_q[i]));
|
|
}
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Residual for GammaZ, GammaW, GammaS, GammaQ
|
|
if(State.m_hasgz[i])
|
|
{
|
|
rhs.m_nu.Add(i,-(vdcandidate.m_x[i]-vdcandidate.m_g[i]+reg*vdcandidate.m_z[i]));
|
|
rhs.m_gammaz.Add(i,-(v0.m_z[i]/v0.m_g[i]*vdcandidate.m_g[i]+vdcandidate.m_z[i]));
|
|
}
|
|
if(State.m_hasts[i])
|
|
{
|
|
rhs.m_tau.Add(i,-(vdcandidate.m_x[i]+vdcandidate.m_t[i]-reg*vdcandidate.m_s[i]));
|
|
rhs.m_gammas.Add(i,-(v0.m_s[i]/v0.m_t[i]*vdcandidate.m_t[i]+vdcandidate.m_s[i]));
|
|
}
|
|
//---
|
|
if(!State.m_isfrozen[i])
|
|
{
|
|
rhs.m_sigma.Add(i,-(State.m_tmpaty[i]-State.m_tmphx[i]-reg*vdcandidate.m_x[i]));
|
|
if(State.m_hasgz[i])
|
|
rhs.m_sigma.Add(i,-vdcandidate.m_z[i]);
|
|
if(State.m_hasts[i])
|
|
rhs.m_sigma.Add(i,+ vdcandidate.m_s[i]);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes sum of squared primal terms of RHS |
|
|
//| INPUT PARAMETERS: |
|
|
//| Rhs - RHS structure |
|
|
//| N, M - problem metrics |
|
|
//| RESULT: |
|
|
//| sum(sqr()) computed over primal terms(Rho, Nu, Tau, Alpha) |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::RHSPrimal2(CVIPMRightHandSide &rhs)
|
|
{
|
|
double result=rhs.m_rho.Dot(rhs.m_rho);
|
|
result+=rhs.m_nu.Dot(rhs.m_nu);
|
|
result+=rhs.m_tau.Dot(rhs.m_tau);
|
|
result+=rhs.m_alpha.Dot(rhs.m_alpha);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes sum of squared dual terms of RHS |
|
|
//| INPUT PARAMETERS: |
|
|
//| Rhs - RHS structure |
|
|
//| N, M - problem metrics |
|
|
//| RESULT: |
|
|
//| sum(sqr()) computed over dual terms(Sigma, Beta) |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::RHSDual2(CVIPMRightHandSide &rhs)
|
|
{
|
|
double result=rhs.m_sigma.Dot(rhs.m_sigma);
|
|
result+=rhs.m_beta.Dot(rhs.m_beta);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes inf - norm of primal terms of RHS |
|
|
//| INPUT PARAMETERS: |
|
|
//| Rhs - RHS structure |
|
|
//| N, M - problem metrics |
|
|
//| RESULT: |
|
|
//| max(abs()) computed over primal terms(Rho, Nu, Tau, Alpha) |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::RHSPrimalInf(CVIPMRightHandSide &rhs)
|
|
{
|
|
double result=MathMax((rhs.m_rho.Abs()+0).Max(),(rhs.m_nu.Abs()+0).Max());
|
|
result=MathMax(result,(rhs.m_tau.Abs()+0).Max());
|
|
result=MathMax(result,(rhs.m_alpha.Abs()+0).Max());
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes inf - norm of dual terms of RHS |
|
|
//| INPUT PARAMETERS: |
|
|
//| Rhs - RHS structure |
|
|
//| N, M - problem metrics |
|
|
//| RESULT: |
|
|
//| max(abs()) computed over dual terms(Sigma, Beta) |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::RHSDualInf(CVIPMRightHandSide &rhs)
|
|
{
|
|
double result=MathMax((rhs.m_sigma.Abs()+0).Max(),(rhs.m_beta.Abs()+0).Max());
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes maximum over complementarity slackness terms of RHS |
|
|
//| INPUT PARAMETERS: |
|
|
//| Rhs - RHS structure |
|
|
//| N, M - problem metrics |
|
|
//| RESULT: |
|
|
//| max(abs()) computed over complementarity terms |
|
|
//| (GammaZ, GammaS, GammaW, GammaQ) |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::RHSCompl2(CVIPMRightHandSide &rhs)
|
|
{
|
|
double result=rhs.m_gammaz.Dot(rhs.m_gammaz);
|
|
result+=rhs.m_gammas.Dot(rhs.m_gammas);
|
|
result+=rhs.m_gammaw.Dot(rhs.m_gammaw);
|
|
result+=rhs.m_gammaq.Dot(rhs.m_gammaq);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes minimum nonzero value of the vector. Returns 0 if all |
|
|
//| components are nonpositive. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - vector |
|
|
//| N - length |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::MinNZ(CRowDouble &x,
|
|
int n)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
bool nz=false;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(x[i]>0.0)
|
|
{
|
|
if(!nz)
|
|
{
|
|
result=x[i];
|
|
nz=true;
|
|
}
|
|
else
|
|
result=MathMin(result,x[i]);
|
|
}
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes minimum product of nonzero components. |
|
|
//| Returns 0 if all components are nonpositive. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - vector |
|
|
//| Y - vector |
|
|
//| N - length |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::MinProdNZ(CRowDouble &x,
|
|
CRowDouble &y,
|
|
int n)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
bool nz=false;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(x[i]>0.0 && y[i]>0.0)
|
|
{
|
|
if(!nz)
|
|
{
|
|
result=x[i]*y[i];
|
|
nz=true;
|
|
}
|
|
else
|
|
result=MathMin(result,x[i]*y[i]);
|
|
}
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes maximum product of nonzero components. |
|
|
//| Returns 0 if all components are nonpositive. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - vector |
|
|
//| Y - vector |
|
|
//| N - length |
|
|
//+------------------------------------------------------------------+
|
|
double CVIPMSolver::MaxProdNZ(CRowDouble &x,
|
|
CRowDouble &y,
|
|
int n)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
bool nz=false;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(x[i]>0.0 && y[i]>0.0)
|
|
{
|
|
if(!nz)
|
|
{
|
|
result=x[i]*y[i];
|
|
nz=true;
|
|
}
|
|
else
|
|
result=MathMax(result,x[i]*y[i]);
|
|
}
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure describes convex quadratic model of the form: |
|
|
//|f(x)=0.5*(Alpha*x'*A*x+Tau*x'*D*x)+0.5*Theta*(Q*x-r)'*(Q*x-r)+b'*x|
|
|
//| where: |
|
|
//| * Alpha>=0, Tau>=0, Theta>=0, Alpha+Tau>0. |
|
|
//| * A is NxN matrix, Q is NxK matrix (N>=1, K>=0), b is Nx1 |
|
|
//| vector, D is NxN diagonal matrix. |
|
|
//| *"main" quadratic term Alpha*A+Lambda*D is symmetric positive |
|
|
//| definite |
|
|
//| Structure may contain optional equality constraints of the form |
|
|
//| x[i]=x0[i], in this case functions provided by this unit |
|
|
//| calculate Newton step subject to these equality constraints. |
|
|
//+------------------------------------------------------------------+
|
|
struct CConvexQuadraticModel
|
|
{
|
|
int m_ecakind;
|
|
int m_k;
|
|
int m_n;
|
|
int m_nfree;
|
|
double m_alpha;
|
|
double m_ec;
|
|
double m_tau;
|
|
double m_theta;
|
|
double m_tk0;
|
|
double m_tq0;
|
|
bool m_activeset[];
|
|
bool m_isactivesetchanged;
|
|
bool m_islineartermchanged;
|
|
bool m_ismaintermchanged;
|
|
bool m_issecondarytermchanged;
|
|
CRowDouble m_b;
|
|
CRowDouble m_d;
|
|
CRowDouble m_eb;
|
|
CRowDouble m_ecadiag;
|
|
CRowDouble m_r;
|
|
CRowDouble m_tb;
|
|
CRowDouble m_tk1;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CRowDouble m_tmpg;
|
|
CRowDouble m_tq1;
|
|
CRowDouble m_tq2diag;
|
|
CRowDouble m_txc;
|
|
CRowDouble m_xc;
|
|
CMatrixDouble m_a;
|
|
CMatrixDouble m_ecadense;
|
|
CMatrixDouble m_eccm;
|
|
CMatrixDouble m_eq;
|
|
CMatrixDouble m_q;
|
|
CMatrixDouble m_tk2;
|
|
CMatrixDouble m_tmp2;
|
|
CMatrixDouble m_tq2dense;
|
|
//--- constructor / destructor
|
|
CConvexQuadraticModel(void);
|
|
~CConvexQuadraticModel(void) {}
|
|
//---
|
|
void Copy(const CConvexQuadraticModel &obj);
|
|
//--- overloading
|
|
void operator=(const CConvexQuadraticModel &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CConvexQuadraticModel::CConvexQuadraticModel(void)
|
|
{
|
|
m_ecakind=0;
|
|
m_k=0;
|
|
m_n=0;
|
|
m_nfree=0;
|
|
m_alpha=0;
|
|
m_ec=0;
|
|
m_tau=0;
|
|
m_theta=0;
|
|
m_tk0=0;
|
|
m_tq0=0;
|
|
m_isactivesetchanged=false;
|
|
m_islineartermchanged=false;
|
|
m_ismaintermchanged=false;
|
|
m_issecondarytermchanged=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CConvexQuadraticModel::Copy(const CConvexQuadraticModel &obj)
|
|
{
|
|
m_ecakind=obj.m_ecakind;
|
|
m_k=obj.m_k;
|
|
m_n=obj.m_n;
|
|
m_nfree=obj.m_nfree;
|
|
m_alpha=obj.m_alpha;
|
|
m_ec=obj.m_ec;
|
|
m_tau=obj.m_tau;
|
|
m_theta=obj.m_theta;
|
|
m_tk0=obj.m_tk0;
|
|
m_tq0=obj.m_tq0;
|
|
ArrayCopy(m_activeset,obj.m_activeset);
|
|
m_isactivesetchanged=obj.m_isactivesetchanged;
|
|
m_islineartermchanged=obj.m_islineartermchanged;
|
|
m_ismaintermchanged=obj.m_ismaintermchanged;
|
|
m_issecondarytermchanged=obj.m_issecondarytermchanged;
|
|
m_b=obj.m_b;
|
|
m_d=obj.m_d;
|
|
m_eb=obj.m_eb;
|
|
m_ecadiag=obj.m_ecadiag;
|
|
m_r=obj.m_r;
|
|
m_tb=obj.m_tb;
|
|
m_tk1=obj.m_tk1;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_tmpg=obj.m_tmpg;
|
|
m_tq1=obj.m_tq1;
|
|
m_tq2diag=obj.m_tq2diag;
|
|
m_txc=obj.m_txc;
|
|
m_xc=obj.m_xc;
|
|
m_a=obj.m_a;
|
|
m_ecadense=obj.m_ecadense;
|
|
m_eccm=obj.m_eccm;
|
|
m_eq=obj.m_eq;
|
|
m_q=obj.m_q;
|
|
m_tk2=obj.m_tk2;
|
|
m_tmp2=obj.m_tmp2;
|
|
m_tq2dense=obj.m_tq2dense;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Convex Quadratic Models |
|
|
//+------------------------------------------------------------------+
|
|
class CCQModels
|
|
{
|
|
public:
|
|
static const int m_newtonrefinementits;
|
|
|
|
static void CQMInit(int n,CConvexQuadraticModel &s);
|
|
static void CQMSetA(CConvexQuadraticModel &s,CMatrixDouble &a,bool IsUpper,double alpha);
|
|
static void CQMGetA(CConvexQuadraticModel &s,CMatrixDouble &a);
|
|
static void CQMRewriteDenseDiagonal(CConvexQuadraticModel &s,CRowDouble &z);
|
|
static void CQMSetD(CConvexQuadraticModel &s,CRowDouble &d,double tau);
|
|
static void CQMDropA(CConvexQuadraticModel &s);
|
|
static void CQMSetB(CConvexQuadraticModel &s,CRowDouble &b);
|
|
static void CQMSetQ(CConvexQuadraticModel &s,CMatrixDouble &q,CRowDouble &r,int k,double theta);
|
|
static void CQMSetActiveSet(CConvexQuadraticModel &s,CRowDouble &x,bool &activeset[]);
|
|
static double CQMEval(CConvexQuadraticModel &s,CRowDouble &x);
|
|
static void CQMEvalX(CConvexQuadraticModel &s,CRowDouble &x,double &r,double &noise);
|
|
static void CQMGradUnconstrained(CConvexQuadraticModel &s,CRowDouble &x,CRowDouble &g);
|
|
static double CQMXTADX2(CConvexQuadraticModel &s,CRowDouble &x,CRowDouble &tmp);
|
|
static void CQMADX(CConvexQuadraticModel &s,CRowDouble &x,CRowDouble &y);
|
|
static bool CQMConstrainedOptimum(CConvexQuadraticModel &s,CRowDouble &x);
|
|
static void CQMScaleVector(CConvexQuadraticModel &s,CRowDouble &x);
|
|
static void CQMGetDiagA(CConvexQuadraticModel &s,CRowDouble &x);
|
|
static double CQMDebugConstrainedEvalT(CConvexQuadraticModel &s,CRowDouble &x);
|
|
static double CQMDebugConstrainedEvalE(CConvexQuadraticModel &s,CRowDouble &x);
|
|
|
|
private:
|
|
static bool CQMRebuild(CConvexQuadraticModel &s);
|
|
static void CQMSolveEA(CConvexQuadraticModel &s,CRowDouble &x,CRowDouble &tmp);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
const int CCQModels::m_newtonrefinementits=3;
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine is used to initialize CQM. By default, empty NxN |
|
|
//| model is generated, with Alpha=Lambda=Theta=0.0 and zero b. |
|
|
//| Previously allocated buffer variables are reused as much as |
|
|
//| possible. |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMInit(int n,CConvexQuadraticModel &s)
|
|
{
|
|
//--- initialization
|
|
s.m_n=n;
|
|
s.m_k=0;
|
|
s.m_nfree=n;
|
|
s.m_ecakind=-1;
|
|
s.m_alpha=0.0;
|
|
s.m_tau=0.0;
|
|
s.m_theta=0.0;
|
|
s.m_ismaintermchanged=true;
|
|
s.m_issecondarytermchanged=true;
|
|
s.m_islineartermchanged=true;
|
|
s.m_isactivesetchanged=true;
|
|
ArrayResize(s.m_activeset,n);
|
|
s.m_xc=vector<double>::Zeros(n);
|
|
s.m_eb=vector<double>::Zeros(n);
|
|
s.m_tq1=vector<double>::Zeros(n);
|
|
s.m_txc=vector<double>::Zeros(n);
|
|
s.m_tb=vector<double>::Zeros(n);
|
|
s.m_b=vector<double>::Zeros(n);
|
|
s.m_tk1=vector<double>::Zeros(n);
|
|
ArrayInitialize(s.m_activeset,false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine changes main quadratic term of the model. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| A - NxN matrix, only upper or lower triangle is |
|
|
//| referenced |
|
|
//| IsUpper - True, when matrix is stored in upper triangle |
|
|
//| Alpha - multiplier; when Alpha=0, A is not referenced at |
|
|
//| all |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMSetA(CConvexQuadraticModel &s,
|
|
CMatrixDouble &a,
|
|
bool IsUpper,
|
|
double alpha)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(alpha) && alpha>=0.0,__FUNCTION__+": Alpha<0 or is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(alpha==0.0 || CApServ::IsFiniteRTrMatrix(a,s.m_n,IsUpper),__FUNCTION__+": A is not finite NxN matrix"))
|
|
return;
|
|
s.m_alpha=alpha;
|
|
if(alpha>0.0)
|
|
{
|
|
s.m_a.Resize(s.m_n,s.m_n);
|
|
s.m_ecadense.Resize(s.m_n,s.m_n);
|
|
s.m_tq2dense.Resize(s.m_n,s.m_n);
|
|
for(i=0; i<s.m_n; i++)
|
|
{
|
|
for(j=i; j<s.m_n; j++)
|
|
{
|
|
if(IsUpper)
|
|
v=a.Get(i,j);
|
|
else
|
|
v=a.Get(j,i);
|
|
s.m_a.Set(i,j,v);
|
|
s.m_a.Set(j,i,v);
|
|
}
|
|
}
|
|
}
|
|
s.m_ismaintermchanged=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine changes main quadratic term of the model. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| A - possibly preallocated buffer |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| A - NxN matrix, full matrix is returned. Zero matrix is|
|
|
//| returned if model is empty. |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMGetA(CConvexQuadraticModel &s,
|
|
CMatrixDouble &a)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
//--- copy
|
|
if(s.m_alpha>0.0)
|
|
{
|
|
v=s.m_alpha;
|
|
a=s.m_a*v+0;
|
|
}
|
|
else
|
|
{
|
|
a=matrix<double>::Zeros(s.m_n,s.m_n);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine rewrites diagonal of the main quadratic term of |
|
|
//| the model (dense A) by vector Z/Alpha (current value of the Alpha|
|
|
//| coefficient is used). |
|
|
//| IMPORTANT: in case model has no dense quadratic term, this |
|
|
//| function allocates N*N dense matrix of zeros, and |
|
|
//| fills its diagonal by non-zero values. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| Z - new diagonal, array[N] |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMRewriteDenseDiagonal(CConvexQuadraticModel &s,
|
|
CRowDouble &z)
|
|
{
|
|
if(s.m_alpha==0.0)
|
|
{
|
|
s.m_a=matrix<double>::Zeros(s.m_n,s.m_n);
|
|
s.m_ecadense.Resize(s.m_n,s.m_n);
|
|
s.m_tq2dense.Resize(s.m_n,s.m_n);
|
|
s.m_alpha=1.0;
|
|
}
|
|
vector<double> diag=z/s.m_alpha;
|
|
diag.Resize(s.m_n);
|
|
s.m_a.Diag(diag);
|
|
s.m_ismaintermchanged=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine changes diagonal quadratic term of the model. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| D - array[N], semidefinite diagonal matrix |
|
|
//| Tau - multiplier; when Tau=0, D is not referenced at all |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMSetD(CConvexQuadraticModel &s,CRowDouble &d,double tau)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(tau) && tau>=0.0,__FUNCTION__+": Tau<0 or is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(tau==0.0 || CApServ::IsFiniteVector(d,s.m_n),__FUNCTION__+": D is not finite Nx1 vector"))
|
|
return;
|
|
s.m_tau=tau;
|
|
if(tau>0.0)
|
|
{
|
|
s.m_d=d;
|
|
s.m_d.Resize(s.m_n);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_d.Min()>=0.0,__FUNCTION__+": D[i]<0"))
|
|
return;
|
|
s.m_ecadiag=vector<double>::Zeros(s.m_n);
|
|
s.m_tq2diag=vector<double>::Zeros(s.m_n);
|
|
}
|
|
s.m_ismaintermchanged=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine drops main quadratic term A from the model. It is|
|
|
//| same as call to CQMSetA() with zero A, but gives better |
|
|
//| performance because algorithm knows that matrix is zero and can |
|
|
//| optimize subsequent calculations. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMDropA(CConvexQuadraticModel &s)
|
|
{
|
|
s.m_alpha=0.0;
|
|
s.m_ismaintermchanged=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine changes linear term of the model |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMSetB(CConvexQuadraticModel &s,CRowDouble &b)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(b,s.m_n),__FUNCTION__+": B is not finite vector"))
|
|
return;
|
|
|
|
s.m_b=b;
|
|
s.m_b.Resize(s.m_n);
|
|
s.m_islineartermchanged=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine changes linear term of the model |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMSetQ(CConvexQuadraticModel &s,
|
|
CMatrixDouble &q,
|
|
CRowDouble &r,
|
|
int k,
|
|
double theta)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(!CAp::Assert(k==0 || theta==0.0 || CApServ::IsFiniteMatrix(q,k,s.m_n),__FUNCTION__+": Q is not finite matrix"))
|
|
return;
|
|
if(!CAp::Assert(k==0 || theta==0.0 || CApServ::IsFiniteVector(r,k),__FUNCTION__+": R is not finite vector"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(theta) && theta>=0.0,__FUNCTION__+": Theta<0 or is not finite number"))
|
|
return;
|
|
//--- degenerate case: K=0 or Theta=0
|
|
if(k==0 || theta==0.0)
|
|
{
|
|
s.m_k=0;
|
|
s.m_theta=0;
|
|
s.m_issecondarytermchanged=true;
|
|
return;
|
|
}
|
|
//--- General case: both Theta>0 and K>0
|
|
s.m_k=k;
|
|
s.m_theta=theta;
|
|
s.m_q=q;
|
|
s.m_r=r;
|
|
s.m_q.Resize(k,s.m_n);
|
|
s.m_r.Resize(k);
|
|
CApServ::RMatrixSetLengthAtLeast(s.m_eq,k,s.m_n);
|
|
CApServ::RMatrixSetLengthAtLeast(s.m_eccm,k,k);
|
|
CApServ::RMatrixSetLengthAtLeast(s.m_tk2,k,s.m_n);
|
|
s.m_issecondarytermchanged=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine changes active set |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| X - array[N], constraint values |
|
|
//| ActiveSet- array[N], active set. If ActiveSet[I] = True, |
|
|
//| then I-th variables is constrained to X[I]. |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMSetActiveSet(CConvexQuadraticModel &s,CRowDouble &x,
|
|
bool &activeset[])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(x.Size()>=s.m_n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(ArraySize(activeset)>=s.m_n,__FUNCTION__+": Length(ActiveSet)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<s.m_n; i++)
|
|
{
|
|
s.m_isactivesetchanged=s.m_isactivesetchanged || (s.m_activeset[i] && !activeset[i]);
|
|
s.m_isactivesetchanged=s.m_isactivesetchanged || (activeset[i] && !s.m_activeset[i]);
|
|
s.m_activeset[i]=activeset[i];
|
|
if(activeset[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x[i]),__FUNCTION__+": X[] contains infinite constraints"))
|
|
return;
|
|
s.m_isactivesetchanged=s.m_isactivesetchanged || (s.m_xc[i]!=x[i]);
|
|
s.m_xc.Set(i,x[i]);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine evaluates model at X. Active constraints are |
|
|
//| ignored. |
|
|
//+------------------------------------------------------------------+
|
|
double CCQModels::CQMEval(CConvexQuadraticModel &s,CRowDouble &X)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int n=s.m_n;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(X,n),__FUNCTION__+": X is not finite vector"))
|
|
return(result);
|
|
//--- main quadratic term
|
|
CRowDouble x=X;
|
|
x.Resize(n);
|
|
if(s.m_alpha>0.0)
|
|
result=x.Dot(x.MatMul(s.m_a)*(s.m_alpha*0.5));
|
|
if(s.m_tau>0.0)
|
|
result+=s.m_tau*s.m_d.Dot(x*x/2.0);
|
|
//--- secondary quadratic term
|
|
if(s.m_theta>0.0)
|
|
{
|
|
for(int i=0; i<s.m_k; i++)
|
|
{
|
|
v=x.Dot(s.m_q[i]+0);
|
|
result+=0.5*s.m_theta*CMath::Sqr(v-s.m_r[i]);
|
|
}
|
|
}
|
|
//--- linear term
|
|
result+=x.Dot(s.m_b);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine evaluates model at X. Active constraints are |
|
|
//| ignored. |
|
|
//| It returns: |
|
|
//| R - model value |
|
|
//| Noise - estimate of the numerical noise in data |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMEvalX(CConvexQuadraticModel &s,
|
|
CRowDouble &x,
|
|
double &r,
|
|
double &noise)
|
|
{
|
|
//--- create variables
|
|
int n=s.m_n;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double v2=0;
|
|
double mxq=0;
|
|
double eps=2*CMath::m_machineepsilon;
|
|
//--- initialization
|
|
r=0;
|
|
noise=0;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X is not finite vector"))
|
|
return;
|
|
//--- Main quadratic term.
|
|
//--- Noise from the main quadratic term is equal to the
|
|
//--- maximum summand in the term.
|
|
if(s.m_alpha>0.0)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=s.m_alpha*0.5*x[i]*s.m_a.Get(i,j)*x[j];
|
|
r+=v;
|
|
noise=MathMax(noise,eps*MathAbs(v));
|
|
}
|
|
}
|
|
if(s.m_tau>0.0)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=0.5*CMath::Sqr(x[i])*s.m_tau*s.m_d[i];
|
|
r+=v;
|
|
noise=MathMax(noise,eps*MathAbs(v));
|
|
}
|
|
}
|
|
//--- secondary quadratic term
|
|
//--- Noise from the secondary quadratic term is estimated as follows:
|
|
//--- * noise in qi*x-r[i] is estimated as
|
|
//--- Eps*MXQ = Eps*max(|r[i]|, |q[i,j]*x[j]|)
|
|
//--- * noise in (qi*x-r[i])^2 is estimated as
|
|
//--- NOISE = (|qi*x-r[i]|+Eps*MXQ)^2-(|qi*x-r[i]|)^2
|
|
//--- = Eps*MXQ*(2*|qi*x-r[i]|+Eps*MXQ)
|
|
if(s.m_theta>0.0)
|
|
{
|
|
for(i=0; i<s.m_k; i++)
|
|
{
|
|
v=0.0;
|
|
mxq=MathAbs(s.m_r[i]);
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v2=s.m_q.Get(i,j)*x[j];
|
|
v+=v2;
|
|
mxq=MathMax(mxq,MathAbs(v2));
|
|
}
|
|
r+=0.5*s.m_theta*CMath::Sqr(v-s.m_r[i]);
|
|
noise=MathMax(noise,eps*mxq*(2*MathAbs(v-s.m_r[i])+eps*mxq));
|
|
}
|
|
}
|
|
//--- linear term
|
|
for(i=0; i<s.m_n; i++)
|
|
{
|
|
r+=x[i]
|
|
*s.m_b[i];
|
|
noise=MathMax(noise,eps*MathAbs(x[i]*s.m_b[i]));
|
|
}
|
|
//--- Final update of the noise
|
|
noise=n*noise;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine evaluates gradient of the model; active |
|
|
//| constraints are ignored. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - convex model |
|
|
//| X - point, array[N] |
|
|
//| G - possibly preallocated buffer; resized, if too small|
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMGradUnconstrained(CConvexQuadraticModel &s,
|
|
CRowDouble &x,
|
|
CRowDouble &g)
|
|
{
|
|
//--- create variables
|
|
int n=s.m_n;
|
|
int i=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X is not finite vector"))
|
|
return;
|
|
g=vector<double>::Zeros(n);
|
|
//--- main quadratic term
|
|
if(s.m_alpha>0.0)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=x.Dot(s.m_a[i]*s.m_alpha);
|
|
g.Add(i,v);
|
|
}
|
|
}
|
|
if(s.m_tau>0.0)
|
|
g+=x*s.m_d*s.m_tau;
|
|
//--- secondary quadratic term
|
|
if(s.m_theta>0.0)
|
|
{
|
|
for(i=0; i<s.m_k; i++)
|
|
{
|
|
v=x.Dot(s.m_q[i]+0);
|
|
v=s.m_theta*(v-s.m_r[i]);
|
|
g+=s.m_q[i]*v;
|
|
}
|
|
}
|
|
//--- linear term
|
|
g+=s.m_b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine evaluates x'*(0.5*alpha*A+tau*D)*x |
|
|
//| NOTE: Tmp[] must be preallocated array whose length is at least N|
|
|
//+------------------------------------------------------------------+
|
|
double CCQModels::CQMXTADX2(CConvexQuadraticModel &s,
|
|
CRowDouble &x,
|
|
CRowDouble &tmp)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int n=s.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X is not finite vector"))
|
|
return(0.0);
|
|
if(!CAp::Assert(tmp.Size()>=n,__FUNCTION__+": Length(Tmp)<N"))
|
|
return(0.0);
|
|
//--- main quadratic term
|
|
if(s.m_alpha>0.0)
|
|
result+=s.m_alpha*0.5*CAblas::RMatrixSyvMVect(n,s.m_a,0,0,true,x,0,tmp);
|
|
if(s.m_tau>0.0)
|
|
{
|
|
result+=s.m_d.Dot(x*x/2.0)*s.m_tau;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine evaluates(0.5 * alpha * A + tau*D)*x |
|
|
//| Y is automatically resized if needed |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMADX(CConvexQuadraticModel &s,
|
|
CRowDouble &x,
|
|
CRowDouble &y)
|
|
{
|
|
int n=s.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X is not finite vector"))
|
|
return;
|
|
y=vector<double>::Zeros(n);
|
|
//--- main quadratic term
|
|
if(s.m_alpha>0.0)
|
|
CAblas::RMatrixSymVect(n,s.m_alpha,s.m_a,0,0,true,x,0,1.0,y,0);
|
|
if(s.m_tau>0.0)
|
|
y+=x*s.m_d*s.m_tau;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine finds optimum of the model. It returns False on |
|
|
//| failure (indefinite / semidefinite matrix). Optimum is found |
|
|
//| subject to active constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| X - possibly preallocated buffer; automatically |
|
|
//| resized, if too small enough. |
|
|
//+------------------------------------------------------------------+
|
|
bool CCQModels::CQMConstrainedOptimum(CConvexQuadraticModel &s,
|
|
CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
int n=0;
|
|
int nfree=0;
|
|
int k=0;
|
|
int i=0;
|
|
double v=0;
|
|
int cidx0=0;
|
|
int itidx=0;
|
|
int i_=0;
|
|
//--- Rebuild internal structures
|
|
if(!CQMRebuild(s))
|
|
return(false);
|
|
|
|
n=s.m_n;
|
|
k=s.m_k;
|
|
nfree=s.m_nfree;
|
|
//--- Calculate initial point for the iterative refinement:
|
|
//--- * free components are set to zero
|
|
//--- * constrained components are set to their constrained values
|
|
x=vector<double>::Zeros(n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(s.m_activeset[i])
|
|
x.Set(i,s.m_xc[i]);
|
|
}
|
|
//--- Iterative refinement.
|
|
//--- In an ideal world without numerical errors it would be enough
|
|
//--- to make just one Newton step from initial point:
|
|
//--- x_new = -H^(-1)*grad(x=0)
|
|
//--- However, roundoff errors can significantly deteriorate quality
|
|
//--- of the solution. So we have to recalculate gradient and to
|
|
//--- perform Newton steps several times.
|
|
//--- Below we perform fixed number of Newton iterations.
|
|
for(itidx=0; itidx<m_newtonrefinementits; itidx++)
|
|
{
|
|
//--- Calculate gradient at the current point.
|
|
//--- Move free components of the gradient in the beginning.
|
|
CQMGradUnconstrained(s,x,s.m_tmpg);
|
|
cidx0=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!s.m_activeset[i])
|
|
{
|
|
s.m_tmpg.Set(cidx0,s.m_tmpg[i]);
|
|
cidx0++;
|
|
}
|
|
}
|
|
//--- Free components of the extrema are calculated in the first NFree elements of TXC.
|
|
//--- First, we have to calculate original Newton step, without rank-K perturbations
|
|
for(i_=0; i_<nfree; i_++)
|
|
s.m_txc.Set(i_,-s.m_tmpg[i_]);
|
|
CQMSolveEA(s,s.m_txc,s.m_tmp0);
|
|
//--- Then, we account for rank-K correction.
|
|
//--- Woodbury matrix identity is used.
|
|
if(s.m_k>0 && s.m_theta>0.0)
|
|
{
|
|
s.m_tmp0=vector<double>::Zeros(MathMax(nfree,k));
|
|
s.m_tmp1=vector<double>::Zeros(MathMax(nfree,k));
|
|
for(i_=0; i_<nfree; i_++)
|
|
s.m_tmp1.Set(i_,-s.m_tmpg[i_]);
|
|
CQMSolveEA(s,s.m_tmp1,s.m_tmp0);
|
|
for(i=0; i<k; i++)
|
|
{
|
|
v=0.0;
|
|
for(i_=0; i_<nfree; i_++)
|
|
v+=s.m_eq.Get(i,i_)*s.m_tmp1[i_];
|
|
s.m_tmp0.Set(i,v);
|
|
}
|
|
CFbls::FblsCholeskySolve(s.m_eccm,1.0,k,true,s.m_tmp0,s.m_tmp1);
|
|
for(i=0; i<nfree; i++)
|
|
s.m_tmp1.Set(i,0.0);
|
|
for(i=0; i<k; i++)
|
|
{
|
|
v=s.m_tmp0[i];
|
|
for(i_=0; i_<nfree; i_++)
|
|
s.m_tmp1.Add(i_,v*s.m_eq.Get(i,i_));
|
|
}
|
|
CQMSolveEA(s,s.m_tmp1,s.m_tmp0);
|
|
for(i_=0; i_<nfree; i_++)
|
|
s.m_txc.Add(i_,-s.m_tmp1[i_]);
|
|
}
|
|
//--- Unpack components from TXC into X. We pass through all
|
|
//--- free components of X and add our step.
|
|
cidx0=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!s.m_activeset[i])
|
|
{
|
|
x.Add(i,s.m_txc[cidx0]);
|
|
cidx0++;
|
|
}
|
|
}
|
|
}
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function scales vector by multiplying it by inverse of the|
|
|
//| diagonal of the Hessian matrix. It should be used to accelerate |
|
|
//| steepest descent phase of the QP m_solver. |
|
|
//| Although it is called "scale-grad", it can be called for any |
|
|
//| vector, whether it is gradient, anti-gradient, or just some |
|
|
//| vector. |
|
|
//| This function does NOT takes into account current set of |
|
|
//| constraints, it just performs matrix-vector multiplication |
|
|
//| without taking into account constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| X - vector to scale |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - scaled vector |
|
|
//| NOTE: when called for non-SPD matrices, it silently skips |
|
|
//| components of X which correspond to zero or negative |
|
|
//| diagonal elements. |
|
|
//| NOTE: this function uses diagonals of A and D; it ignores |
|
|
//| Q-rank-K term of the quadratic model. |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMScaleVector(CConvexQuadraticModel &s,CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
int n=s.m_n;
|
|
double v=0;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
v=0.0;
|
|
if(s.m_alpha>0.0)
|
|
v+=s.m_a.Get(i,i);
|
|
if(s.m_tau>0.0)
|
|
v+=s.m_d[i];
|
|
if(v>0.0)
|
|
x.Mul(i,1.0/v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns diagonal of the A - term. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D - diagonal of the A( or zero) |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMGetDiagA(CConvexQuadraticModel &s,CRowDouble &x)
|
|
{
|
|
int n=s.m_n;
|
|
|
|
if(s.m_alpha>0)
|
|
x=s.m_a.Diag(0)+0;
|
|
else
|
|
x=vector<double>::Zeros(n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calls CQMRebuild() and evaluates model at X |
|
|
//| subject to active constraints. |
|
|
//| It is intended for debug purposes only, because it evaluates |
|
|
//| model by means of temporaries, which were calculated by |
|
|
//| CQMRebuild(). The only purpose of this function is to check |
|
|
//| correctness of CQMRebuild() by comparing results of this function|
|
|
//| with ones obtained by CQMEval(), which is used as reference point|
|
|
//| The idea is that significant deviation in results of these two |
|
|
//| functions is evidence of some error in the CQMRebuild(). |
|
|
//| NOTE: suffix T denotes that temporaries marked by T-prefix are |
|
|
//| used. There is one more variant of this function, which |
|
|
//| uses "effective" model built by CQMRebuild(). |
|
|
//| NOTE2: in case CQMRebuild() fails(due to model non-convexity), |
|
|
//| this function returns NAN. |
|
|
//+------------------------------------------------------------------+
|
|
double CCQModels::CQMDebugConstrainedEvalT(CConvexQuadraticModel &s,
|
|
CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
double result=0.0;
|
|
int n=s.m_n;
|
|
int nfree=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X is not finite vector"))
|
|
return(result);
|
|
if(!CQMRebuild(s))
|
|
return(AL_NaN);
|
|
|
|
nfree=s.m_nfree;
|
|
//--- Reorder variables
|
|
j=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!s.m_activeset[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(j<nfree,__FUNCTION__+": internal error"))
|
|
return(AL_NaN);
|
|
s.m_txc.Set(j,x[i]);
|
|
j++;
|
|
}
|
|
}
|
|
//--- TQ2, TQ1, TQ0
|
|
if(s.m_alpha>0.0)
|
|
{
|
|
//--- Dense TQ2
|
|
for(i=0; i<nfree; i++)
|
|
for(j=0; j<nfree; j++)
|
|
result+=0.5*s.m_txc[i]*s.m_tq2dense.Get(i,j)*s.m_txc[j];
|
|
}
|
|
else
|
|
{
|
|
//--- Diagonal TQ2
|
|
for(i=0; i<nfree; i++)
|
|
result+=0.5*s.m_tq2diag[i]*CMath::Sqr(s.m_txc[i]);
|
|
}
|
|
result+=s.m_tq1.Dot(s.m_txc);
|
|
result=result+s.m_tq0;
|
|
//--- TK2, TK1, TK0
|
|
if(s.m_k>0 && s.m_theta>0.0)
|
|
{
|
|
for(i=0; i<s.m_k; i++)
|
|
{
|
|
v=CAblasF::RDotVR(nfree,s.m_txc,s.m_tk2,i);
|
|
result=result+0.5*CMath::Sqr(v);
|
|
}
|
|
result+=s.m_tk1.Dot(s.m_txc);
|
|
result=result+s.m_tk0;
|
|
}
|
|
//--- TB (Bf and Bc parts)
|
|
result+=s.m_tb.Dot(s.m_txc);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calls CQMRebuild() and evaluates model at X |
|
|
//| subject to active constraints. |
|
|
//| It is intended for debug purposes only, because it evaluates |
|
|
//| model by means of "effective" matrices built by CQMRebuild(). The|
|
|
//| only purpose of this function is to check correctness of |
|
|
//| CQMRebuild() by comparing results of this function with ones |
|
|
//| obtained by CQMEval(), which is used as reference point. The idea|
|
|
//| is that significant deviation in results of these two functions |
|
|
//| is evidence of some error in the CQMRebuild(). |
|
|
//| NOTE: suffix E denotes that effective matrices. There is one more|
|
|
//| variant of this function, which uses temporary matrices |
|
|
//| built by CQMRebuild(). |
|
|
//| NOTE2: in case CQMRebuild() fails(due to model non-convexity), |
|
|
//| this function returns NAN. |
|
|
//+------------------------------------------------------------------+
|
|
double CCQModels::CQMDebugConstrainedEvalE(CConvexQuadraticModel &s,
|
|
CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int n=s.m_n;
|
|
int nfree=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X is not finite vector"))
|
|
return(AL_NaN);
|
|
if(!CQMRebuild(s))
|
|
return(AL_NaN);
|
|
|
|
nfree=s.m_nfree;
|
|
//--- Reorder variables
|
|
j=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!s.m_activeset[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(j<nfree,__FUNCTION__+": internal error"))
|
|
return(AL_NaN);
|
|
s.m_txc.Set(j,x[i]);
|
|
j++;
|
|
}
|
|
}
|
|
//--- ECA
|
|
//--- check
|
|
if(!CAp::Assert(s.m_ecakind==0 || s.m_ecakind==1 || (s.m_ecakind==-1 && nfree==0),__FUNCTION__+": unexpected ECAKind"))
|
|
return(AL_NaN);
|
|
if(s.m_ecakind==0)
|
|
{
|
|
//--- Dense ECA
|
|
for(i=0; i<nfree; i++)
|
|
{
|
|
v=0.0;
|
|
for(j=i; j<nfree; j++)
|
|
v+= s.m_ecadense.Get(i,j)*s.m_txc[j];
|
|
result+=0.5*CMath::Sqr(v);
|
|
}
|
|
}
|
|
if(s.m_ecakind==1)
|
|
{
|
|
//--- Diagonal ECA
|
|
for(i=0; i<nfree; i++)
|
|
result+=0.5*CMath::Sqr(s.m_ecadiag[i]*s.m_txc[i]);
|
|
}
|
|
//--- EQ
|
|
for(i=0; i<s.m_k; i++)
|
|
{
|
|
v=0.0;
|
|
for(j=0; j<nfree; j++)
|
|
v+=s.m_eq.Get(i,j)*s.m_txc[j];
|
|
result+=0.5*CMath::Sqr(v);
|
|
}
|
|
//--- EB
|
|
for(i=0; i<nfree; i++)
|
|
result+=s.m_eb[i]*s.m_txc[i];
|
|
//--- EC
|
|
result+=s.m_ec;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal function, rebuilds "effective" model subject to |
|
|
//| constraints. Returns False on failure (non-SPD main quadratic |
|
|
//| term) |
|
|
//+------------------------------------------------------------------+
|
|
bool CCQModels::CQMRebuild(CConvexQuadraticModel &s)
|
|
{
|
|
//--- create variables
|
|
bool result=true;
|
|
int n=s.m_n;
|
|
int k=s.m_k;
|
|
int nfree=0;
|
|
int i=0;
|
|
int j=0;
|
|
int ridx0=0;
|
|
int ridx1=0;
|
|
int cidx0=0;
|
|
int cidx1=0;
|
|
double v=0;
|
|
int i_=0;
|
|
//--- Non-SPD model, quick exit
|
|
if(s.m_alpha==0.0 && s.m_tau==0.0)
|
|
return(false);
|
|
//--- Determine number of free variables.
|
|
//--- Fill TXC - array whose last N-NFree elements store constraints.
|
|
if(s.m_isactivesetchanged)
|
|
{
|
|
s.m_nfree=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!s.m_activeset[i])
|
|
s.m_nfree++;
|
|
}
|
|
j=s.m_nfree;
|
|
for(i=0; i<n; i++)
|
|
if(s.m_activeset[i])
|
|
{
|
|
s.m_txc.Set(j,s.m_xc[i]);
|
|
j++;
|
|
}
|
|
}
|
|
nfree=s.m_nfree;
|
|
//--- Re-evaluate TQ2/TQ1/TQ0, if needed
|
|
if(s.m_isactivesetchanged || s.m_ismaintermchanged)
|
|
{
|
|
//--- Handle cases Alpha>0 and Alpha=0 separately:
|
|
//--- * in the first case we have dense matrix
|
|
//--- * in the second one we have diagonal matrix, which can be
|
|
//--- handled more efficiently
|
|
if(s.m_alpha>0.0)
|
|
{
|
|
//--- Alpha>0, dense QP
|
|
//--- Split variables into two groups - free (F) and constrained (C). Reorder
|
|
//--- variables in such way that free vars come first, constrained are last:
|
|
//--- x = [xf, xc].
|
|
//--- Main quadratic term x'*(alpha*A+tau*D)*x now splits into quadratic part,
|
|
//--- linear part and constant part:
|
|
//--- ( alpha*Aff+tau*Df alpha*Afc ) ( xf )
|
|
//--- 0.5*( xf' xc' )*( )*( ) =
|
|
//--- ( alpha*Acf alpha*Acc+tau*Dc ) ( xc )
|
|
//--- = 0.5*xf'*(alpha*Aff+tau*Df)*xf + (alpha*Afc*xc)'*xf + 0.5*xc'(alpha*Acc+tau*Dc)*xc
|
|
//--- We store these parts into temporary variables:
|
|
//--- * alpha*Aff+tau*Df, alpha*Afc, alpha*Acc+tau*Dc are stored into upper
|
|
//--- triangle of TQ2
|
|
//--- * alpha*Afc*xc is stored into TQ1
|
|
//--- * 0.5*xc'(alpha*Acc+tau*Dc)*xc is stored into TQ0
|
|
//--- Below comes first part of the work - generation of TQ2:
|
|
//--- * we pass through rows of A and copy I-th row into upper block (Aff/Afc) or
|
|
//--- lower one (Acf/Acc) of TQ2, depending on presence of X[i] in the active set.
|
|
//--- RIdx0 variable contains current position for insertion into upper block,
|
|
//--- RIdx1 contains current position for insertion into lower one.
|
|
//--- * within each row, we copy J-th element into left half (Aff/Acf) or right
|
|
//--- one (Afc/Acc), depending on presence of X[j] in the active set. CIdx0
|
|
//--- contains current position for insertion into left block, CIdx1 contains
|
|
//--- position for insertion into right one.
|
|
//--- * during copying, we multiply elements by alpha and add diagonal matrix D.
|
|
ridx0=0;
|
|
ridx1=s.m_nfree;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
cidx0=0;
|
|
cidx1=s.m_nfree;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
if(!s.m_activeset[i] && !s.m_activeset[j])
|
|
{
|
|
//--- Element belongs to Aff
|
|
v=s.m_alpha*s.m_a.Get(i,j);
|
|
if(i==j && s.m_tau>0.0)
|
|
v+=s.m_tau*s.m_d[i];
|
|
s.m_tq2dense.Set(ridx0,cidx0,v);
|
|
}
|
|
if(!s.m_activeset[i] && s.m_activeset[j])
|
|
{
|
|
//--- Element belongs to Afc
|
|
s.m_tq2dense.Set(ridx0,cidx1,s.m_alpha*s.m_a.Get(i,j));
|
|
}
|
|
if(s.m_activeset[i] && !s.m_activeset[j])
|
|
{
|
|
//--- Element belongs to Acf
|
|
s.m_tq2dense.Set(ridx1,cidx0,s.m_alpha*s.m_a.Get(i,j));
|
|
}
|
|
if(s.m_activeset[i] && s.m_activeset[j])
|
|
{
|
|
//--- Element belongs to Acc
|
|
v=s.m_alpha*s.m_a.Get(i,j);
|
|
if(i==j && s.m_tau>0.0)
|
|
v+=s.m_tau*s.m_d[i];
|
|
s.m_tq2dense.Set(ridx1,cidx1,v);
|
|
}
|
|
if(s.m_activeset[j])
|
|
cidx1++;
|
|
else
|
|
cidx0++;
|
|
}
|
|
if(s.m_activeset[i])
|
|
ridx1++;
|
|
else
|
|
ridx0++;
|
|
}
|
|
//--- Now we have TQ2, and we can evaluate TQ1.
|
|
//--- In the special case when we have Alpha=0, NFree=0 or NFree=N,
|
|
//--- TQ1 is filled by zeros.
|
|
s.m_tq1=vector<double>::Zeros(n);
|
|
if(s.m_nfree>0 && s.m_nfree<n)
|
|
CAblas::RMatrixMVect(s.m_nfree,n-s.m_nfree,s.m_tq2dense,0,s.m_nfree,0,s.m_txc,s.m_nfree,s.m_tq1,0);
|
|
//--- And finally, we evaluate TQ0.
|
|
v=0.0;
|
|
for(i=s.m_nfree; i<n; i++)
|
|
for(j=s.m_nfree; j<n; j++)
|
|
v+= 0.5*s.m_txc[i]*s.m_tq2dense.Get(i,j)*s.m_txc[j];
|
|
s.m_tq0=v;
|
|
}
|
|
else
|
|
{
|
|
//--- Alpha=0, diagonal QP
|
|
//--- Split variables into two groups - free (F) and constrained (C). Reorder
|
|
//--- variables in such way that free vars come first, constrained are last:
|
|
//--- x = [xf, xc].
|
|
//--- Main quadratic term x'*(tau*D)*x now splits into quadratic and constant
|
|
//--- parts:
|
|
//--- ( tau*Df ) ( xf )
|
|
//--- 0.5*( xf' xc' )*( )*( ) =
|
|
//--- ( tau*Dc ) ( xc )
|
|
//--- = 0.5*xf'*(tau*Df)*xf + 0.5*xc'(tau*Dc)*xc
|
|
//--- We store these parts into temporary variables:
|
|
//--- * tau*Df is stored in TQ2Diag
|
|
//--- * 0.5*xc'(tau*Dc)*xc is stored into TQ0
|
|
s.m_tq0=0.0;
|
|
ridx0=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!s.m_activeset[i])
|
|
{
|
|
s.m_tq2diag.Set(ridx0,s.m_tau*s.m_d[i]);
|
|
ridx0++;
|
|
}
|
|
else
|
|
s.m_tq0+=0.5*s.m_tau*s.m_d[i]*CMath::Sqr(s.m_xc[i]);
|
|
}
|
|
s.m_tq1=vector<double>::Zeros(n);
|
|
}
|
|
}
|
|
//--- Re-evaluate TK2/TK1/TK0, if needed
|
|
if(s.m_isactivesetchanged || s.m_issecondarytermchanged)
|
|
{
|
|
//
|
|
//--- Split variables into two groups - free (F) and constrained (C). Reorder
|
|
//--- variables in such way that free vars come first, constrained are last:
|
|
//--- x = [xf, xc].
|
|
//
|
|
//--- Secondary term theta*(Q*x-r)'*(Q*x-r) now splits into quadratic part,
|
|
//--- linear part and constant part:
|
|
//--- ( ( xf ) )' ( ( xf ) )
|
|
//--- 0.5*theta*( (Qf Qc)'*( ) - r ) * ( (Qf Qc)'*( ) - r ) =
|
|
//--- ( ( xc ) ) ( ( xc ) )
|
|
//
|
|
//--- = 0.5*theta*xf'*(Qf'*Qf)*xf + theta*((Qc*xc-r)'*Qf)*xf +
|
|
//--- + theta*(-r'*(Qc*xc-r)-0.5*r'*r+0.5*xc'*Qc'*Qc*xc)
|
|
//
|
|
//--- We store these parts into temporary variables:
|
|
//--- * sqrt(theta)*Qf is stored into TK2
|
|
//--- * theta*((Qc*xc-r)'*Qf) is stored into TK1
|
|
//--- * theta*(-r'*(Qc*xc-r)-0.5*r'*r+0.5*xc'*Qc'*Qc*xc) is stored into TK0
|
|
//
|
|
//--- We use several other temporaries to store intermediate results:
|
|
//--- * Tmp0 - to store Qc*xc-r
|
|
//--- * Tmp1 - to store Qc*xc
|
|
//
|
|
//--- Generation of TK2/TK1/TK0 is performed as follows:
|
|
//--- * we fill TK2/TK1/TK0 (to handle K=0 or Theta=0)
|
|
//--- * other steps are performed only for K>0 and Theta>0
|
|
//--- * we pass through columns of Q and copy I-th column into left block (Qf) or
|
|
//--- right one (Qc) of TK2, depending on presence of X[i] in the active set.
|
|
//--- CIdx0 variable contains current position for insertion into upper block,
|
|
//--- CIdx1 contains current position for insertion into lower one.
|
|
//--- * we calculate Qc*xc-r and store it into Tmp0
|
|
//--- * we calculate TK0 and TK1
|
|
//--- * we multiply leading part of TK2 which stores Qf by sqrt(theta)
|
|
//--- it is important to perform this step AFTER calculation of TK0 and TK1,
|
|
//--- because we need original (non-modified) Qf to calculate TK0 and TK1.
|
|
//
|
|
s.m_tk2=matrix<double>::Zeros(k,n);
|
|
s.m_tk1=vector<double>::Zeros(n);
|
|
s.m_tk0=0.0;
|
|
if(s.m_k>0 && s.m_theta>0.0)
|
|
{
|
|
//
|
|
//--- Split Q into Qf and Qc
|
|
//--- Calculate Qc*xc-r, store in Tmp0
|
|
//
|
|
s.m_tmp0.Resize(k);
|
|
s.m_tmp1=vector<double>::Zeros(k);
|
|
cidx0=0;
|
|
cidx1=nfree;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
if(s.m_activeset[j])
|
|
{
|
|
s.m_tk2.Col(cidx1,s.m_q.Col(j)+0);
|
|
s.m_tmp1+=s.m_q.Col(j)*s.m_txc[cidx1];
|
|
cidx1++;
|
|
}
|
|
else
|
|
{
|
|
s.m_tk2.Col(cidx0,s.m_q.Col(j)+0);
|
|
cidx0++;
|
|
}
|
|
}
|
|
s.m_tmp0=s.m_tmp1-s.m_r+0;
|
|
//--- Calculate TK0
|
|
v=0.0;
|
|
for(i=0; i<k; i++)
|
|
v+= s.m_theta*(0.5*CMath::Sqr(s.m_tmp1[i])-s.m_r[i]*s.m_tmp0[i]-0.5*CMath::Sqr(s.m_r[i]));
|
|
s.m_tk0=v;
|
|
//--- Calculate TK1
|
|
if(nfree>0)
|
|
{
|
|
for(i=0; i<k; i++)
|
|
{
|
|
v=s.m_theta*s.m_tmp0[i];
|
|
for(i_=0; i_<nfree; i_++)
|
|
s.m_tk1.Add(i_,v*s.m_tk2.Get(i,i_));
|
|
}
|
|
}
|
|
//--- Calculate TK2
|
|
if(nfree>0)
|
|
{
|
|
v=MathSqrt(s.m_theta);
|
|
for(i=0; i<k; i++)
|
|
{
|
|
for(i_=0; i_<nfree; i_++)
|
|
s.m_tk2.Mul(i,i_,v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- Re-evaluate TB
|
|
if(s.m_isactivesetchanged || s.m_islineartermchanged)
|
|
{
|
|
ridx0=0;
|
|
ridx1=nfree;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(s.m_activeset[i])
|
|
{
|
|
s.m_tb.Set(ridx1,s.m_b[i]);
|
|
ridx1++;
|
|
}
|
|
else
|
|
{
|
|
s.m_tb.Set(ridx0,s.m_b[i]);
|
|
ridx0++;
|
|
}
|
|
}
|
|
}
|
|
//--- Compose ECA: either dense ECA or diagonal ECA
|
|
if((s.m_isactivesetchanged || s.m_ismaintermchanged) && nfree>0)
|
|
{
|
|
if(s.m_alpha>0.0)
|
|
{
|
|
//--- Dense ECA
|
|
s.m_ecakind=0;
|
|
for(i=0; i<nfree; i++)
|
|
{
|
|
for(j=i; j<nfree; j++)
|
|
s.m_ecadense.Set(i,j,s.m_tq2dense.Get(i,j));
|
|
}
|
|
if(!CTrFac::SPDMatrixCholeskyRec(s.m_ecadense,0,nfree,true,s.m_tmp0))
|
|
{
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Diagonal ECA
|
|
s.m_ecakind=1;
|
|
for(i=0; i<nfree; i++)
|
|
{
|
|
if(s.m_tq2diag[i]<0.0)
|
|
{
|
|
result=false;
|
|
return(result);
|
|
}
|
|
s.m_ecadiag.Set(i,MathSqrt(s.m_tq2diag[i]));
|
|
}
|
|
}
|
|
}
|
|
//--- Compose EQ
|
|
if(s.m_isactivesetchanged || s.m_issecondarytermchanged)
|
|
{
|
|
for(i=0; i<k; i++)
|
|
for(j=0; j<nfree; j++)
|
|
s.m_eq.Set(i,j,s.m_tk2.Get(i,j));
|
|
}
|
|
//--- Calculate ECCM
|
|
if((s.m_isactivesetchanged || s.m_ismaintermchanged || s.m_issecondarytermchanged) && s.m_k>0 && s.m_theta>0.0 && nfree>0)
|
|
{
|
|
//--- Calculate ECCM-Cholesky factor of the "effective" capacitance
|
|
//--- matrix CM = I + EQ*inv(EffectiveA)*EQ'.
|
|
//--- We calculate CM as follows:
|
|
//--- CM = I + EQ*inv(EffectiveA)*EQ'
|
|
//--- = I + EQ*ECA^(-1)*ECA^(-T)*EQ'
|
|
//--- = I + (EQ*ECA^(-1))*(EQ*ECA^(-1))'
|
|
//--- Then we perform Cholesky decomposition of CM.
|
|
s.m_tmp2.Resize(k,n);
|
|
CAblas::RMatrixCopy(k,nfree,s.m_eq,0,0,s.m_tmp2,0,0);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_ecakind==0 || s.m_ecakind==1,__FUNCTION__+": unexpected ECAKind"))
|
|
return(false);
|
|
if(s.m_ecakind==0)
|
|
CAblas::RMatrixRightTrsM(k,nfree,s.m_ecadense,0,0,true,false,0,s.m_tmp2,0,0);
|
|
if(s.m_ecakind==1)
|
|
{
|
|
for(i=0; i<k; i++)
|
|
for(j=0; j<nfree; j++)
|
|
s.m_tmp2.Mul(i,j,1.0/s.m_ecadiag[j]);
|
|
}
|
|
s.m_eccm=matrix<double>::Identity(k,k);
|
|
CAblas::RMatrixSyrk(k,nfree,1.0,s.m_tmp2,0,0,0,1.0,s.m_eccm,0,0,true);
|
|
if(!CTrFac::SPDMatrixCholeskyRec(s.m_eccm,0,k,true,s.m_tmp0))
|
|
{
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
//--- Compose EB and EC
|
|
//--- NOTE: because these quantities are cheap to compute, we do not
|
|
//--- use caching here.
|
|
for(i=0; i<nfree; i++)
|
|
s.m_eb.Set(i,s.m_tq1[i]+s.m_tk1[i]+s.m_tb[i]);
|
|
s.m_ec=s.m_tq0+s.m_tk0;
|
|
for(i=nfree; i<n; i++)
|
|
s.m_ec+=s.m_tb[i]*s.m_txc[i];
|
|
//--- Change cache status - everything is cached
|
|
s.m_ismaintermchanged=false;
|
|
s.m_issecondarytermchanged=false;
|
|
s.m_islineartermchanged=false;
|
|
s.m_isactivesetchanged=false;
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal function, solves system Effective_A*x = b. |
|
|
//| It should be called after successful completion of CQMRebuild(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - quadratic model, after call to CQMRebuild() |
|
|
//| X - right part B, array[S.NFree] |
|
|
//| Tmp - temporary array, automatically reallocated if |
|
|
//| needed |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - solution, array[S.NFree] |
|
|
//| NOTE: when called with zero S.NFree, returns silently |
|
|
//| NOTE: this function assumes that EA is non - degenerate |
|
|
//+------------------------------------------------------------------+
|
|
void CCQModels::CQMSolveEA(CConvexQuadraticModel &s,CRowDouble &x,
|
|
CRowDouble &tmp)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_ecakind==0 || s.m_ecakind==1 || (s.m_ecakind==-1 && s.m_nfree==0),__FUNCTION__+": unexpected ECAKind"))
|
|
return;
|
|
|
|
if(s.m_ecakind==0)
|
|
{
|
|
//--- Dense ECA, use FBLSCholeskySolve() dense m_solver.
|
|
CFbls::FblsCholeskySolve(s.m_ecadense,1.0,s.m_nfree,true,x,tmp);
|
|
}
|
|
|
|
if(s.m_ecakind==1)
|
|
{
|
|
//--- Diagonal ECA
|
|
for(int i=0; i<s.m_nfree; i++)
|
|
x.Mul(i,1.0/CMath::Sqr(s.m_ecadiag[i]));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores Settings for QQP m_solver. |
|
|
//| It must be initialized with QQPLoadDefaults(). |
|
|
//| After initialization you may change Settings. |
|
|
//+------------------------------------------------------------------+
|
|
struct CQQPSettings
|
|
{
|
|
int m_cgmaxits;
|
|
int m_cgminits;
|
|
int m_cnmaxupdates;
|
|
int m_maxouterits;
|
|
int m_sparsesolver;
|
|
double m_epsf;
|
|
double m_epsg;
|
|
double m_epsx;
|
|
bool m_cgphase;
|
|
bool m_cnphase;
|
|
//--- constructor / destructor
|
|
CQQPSettings(void) { ZeroMemory(this); }
|
|
~CQQPSettings(void) {}
|
|
void Copy(const CQQPSettings &obj);
|
|
//--- overloading
|
|
void operator=(const CQQPSettings &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CQQPSettings::Copy(const CQQPSettings &obj)
|
|
{
|
|
m_cgmaxits=obj.m_cgmaxits;
|
|
m_cgminits=obj.m_cgminits;
|
|
m_cnmaxupdates=obj.m_cnmaxupdates;
|
|
m_maxouterits=obj.m_maxouterits;
|
|
m_sparsesolver=obj.m_sparsesolver;
|
|
m_epsf=obj.m_epsf;
|
|
m_epsg=obj.m_epsg;
|
|
m_epsx=obj.m_epsx;
|
|
m_cgphase=obj.m_cgphase;
|
|
m_cnphase=obj.m_cnphase;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries used by QuickQP m_solver. |
|
|
//+------------------------------------------------------------------+
|
|
struct CQQPBuffers
|
|
{
|
|
int m_akind;
|
|
int m_cnmodelage;
|
|
int m_n;
|
|
int m_nfree;
|
|
int m_repinneriterationscount;
|
|
int m_repncholesky;
|
|
int m_repncupdates;
|
|
int m_repouteriterationscount;
|
|
double m_absamax;
|
|
double m_absasum;
|
|
double m_absasum2;
|
|
bool m_activated[];
|
|
bool m_havebndl[];
|
|
bool m_havebndu[];
|
|
bool m_sparseupper;
|
|
bool m_tmpcnb[];
|
|
CSparseMatrix m_sparsea;
|
|
CSparseMatrix m_sparsecca;
|
|
CSparseBuffers m_sbuf;
|
|
CSActiveSet m_sas;
|
|
CRowInt m_tmpcni;
|
|
CRowInt m_yidx;
|
|
CRowDouble m_b;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_cgc;
|
|
CRowDouble m_cgp;
|
|
CRowDouble m_dc;
|
|
CRowDouble m_dp;
|
|
CRowDouble m_gc;
|
|
CRowDouble m_regdiag;
|
|
CRowDouble m_regx0;
|
|
CRowDouble m_stpbuf;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CRowDouble m_tmpcn;
|
|
CRowDouble m_xf;
|
|
CRowDouble m_xp;
|
|
CRowDouble m_xs;
|
|
CMatrixDouble m_densea;
|
|
CMatrixDouble m_densez;
|
|
//--- constructor / destructor
|
|
CQQPBuffers(void);
|
|
~CQQPBuffers(void) {}
|
|
//---
|
|
void Copy(const CQQPBuffers &obj);
|
|
//--- overloading
|
|
void operator=(const CQQPBuffers &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CQQPBuffers::CQQPBuffers(void)
|
|
{
|
|
m_akind=0;
|
|
m_cnmodelage=0;
|
|
m_n=0;
|
|
m_nfree=0;
|
|
m_repinneriterationscount=0;
|
|
m_repncholesky=0;
|
|
m_repncupdates=0;
|
|
m_repouteriterationscount=0;
|
|
m_absamax=0;
|
|
m_absasum=0;
|
|
m_absasum2=0;
|
|
m_sparseupper=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CQQPBuffers::Copy(const CQQPBuffers &obj)
|
|
{
|
|
m_akind=obj.m_akind;
|
|
m_cnmodelage=obj.m_cnmodelage;
|
|
m_n=obj.m_n;
|
|
m_nfree=obj.m_nfree;
|
|
m_repinneriterationscount=obj.m_repinneriterationscount;
|
|
m_repncholesky=obj.m_repncholesky;
|
|
m_repncupdates=obj.m_repncupdates;
|
|
m_repouteriterationscount=obj.m_repouteriterationscount;
|
|
m_absamax=obj.m_absamax;
|
|
m_absasum=obj.m_absasum;
|
|
m_absasum2=obj.m_absasum2;
|
|
ArrayCopy(m_activated,obj.m_activated);
|
|
ArrayCopy(m_havebndl,obj.m_havebndl);
|
|
ArrayCopy(m_havebndu,obj.m_havebndu);
|
|
m_sparseupper=obj.m_sparseupper;
|
|
ArrayCopy(m_tmpcnb,obj.m_tmpcnb);
|
|
m_sparsea=obj.m_sparsea;
|
|
m_sparsecca=obj.m_sparsecca;
|
|
m_sbuf=obj.m_sbuf;
|
|
m_sas=obj.m_sas;
|
|
m_tmpcni=obj.m_tmpcni;
|
|
m_yidx=obj.m_yidx;
|
|
m_b=obj.m_b;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_cgc=obj.m_cgc;
|
|
m_cgp=obj.m_cgp;
|
|
m_dc=obj.m_dc;
|
|
m_dp=obj.m_dp;
|
|
m_gc=obj.m_gc;
|
|
m_regdiag=obj.m_regdiag;
|
|
m_regx0=obj.m_regx0;
|
|
m_stpbuf=obj.m_stpbuf;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_tmpcn=obj.m_tmpcn;
|
|
m_xf=obj.m_xf;
|
|
m_xp=obj.m_xp;
|
|
m_xs=obj.m_xs;
|
|
m_densea=obj.m_densea;
|
|
m_densez=obj.m_densez;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| QQP m_solver |
|
|
//+------------------------------------------------------------------+
|
|
class CQQPSolver
|
|
{
|
|
public:
|
|
static const int m_quickqprestartcg;
|
|
static const double m_regz;
|
|
|
|
static void QQPLoadDefaults(int n,CQQPSettings &s);
|
|
static void QQPCopySettings(CQQPSettings &src,CQQPSettings &dst);
|
|
static void QQPPreAllocateGrowDense(CQQPBuffers &sstate,int nexpected,int ngrowto);
|
|
static void QQPOptimize(CConvexQuadraticModel &cqmac,CSparseMatrix &sparseac,CMatrixDouble &denseac,int akind,bool IsUpper,CRowDouble &bc,CRowDouble &bndlc,CRowDouble &bnduc,CRowDouble &sc,CRowDouble &xoriginc,int nc,CQQPSettings &Settings,CQQPBuffers &sstate,CRowDouble &xs,int &terminationtype);
|
|
|
|
private:
|
|
static double ProjectedTargetFunction(CQQPBuffers &sstate,CRowDouble &x,CRowDouble &d,double stp,CRowDouble &m_tmp0,CRowDouble &m_tmp1);
|
|
static void TargetGradient(CQQPBuffers &sstate,CRowDouble &x,CRowDouble &g);
|
|
static void QuadraticModel(CQQPBuffers &sstate,CRowDouble &x,CRowDouble &d,CRowDouble &g,double &d1,int &d1est,double &d2,int &d2est,CRowDouble &m_tmp0);
|
|
static void FindBestStepAndMove(CQQPBuffers &sstate,CSActiveSet &sas,CRowDouble &d,double stp,bool needact,int cidx,double cval,CRowDouble &addsteps,int addstepscnt,bool &activated[],CRowDouble &m_tmp0,CRowDouble &m_tmp1);
|
|
static bool CNewtonBuild(CQQPBuffers &sstate,int sparsesolver,int &ncholesky);
|
|
static bool CNewtonUpdate(CQQPBuffers &sstate,CQQPSettings &Settings,int &ncupdates);
|
|
static bool CNewtonStep(CQQPBuffers &sstate,CQQPSettings &Settings,CRowDouble &gc);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
const int CQQPSolver::m_quickqprestartcg=50;
|
|
const double CQQPSolver::m_regz=1.0E-9;
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes QQPSettings structure with default |
|
|
//| Settings. |
|
|
//| Newly created structure MUST be initialized by default |
|
|
//| Settings - or by copy of the already initialized structure. |
|
|
//+------------------------------------------------------------------+
|
|
void CQQPSolver::QQPLoadDefaults(int n,CQQPSettings &s)
|
|
{
|
|
s.m_epsg=0.0;
|
|
s.m_epsf=0.0;
|
|
s.m_epsx=1.0E-6;
|
|
s.m_maxouterits=0;
|
|
s.m_cgphase=true;
|
|
s.m_cnphase=true;
|
|
s.m_cgminits=5;
|
|
s.m_cgmaxits=MathMax(s.m_cgminits,(int)MathRound(1+0.33*n));
|
|
s.m_sparsesolver=0;
|
|
s.m_cnmaxupdates=(int)MathRound(1+0.1*n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes QQPSettings structure with copy of |
|
|
//| another, already initialized structure. |
|
|
//+------------------------------------------------------------------+
|
|
void CQQPSolver::QQPCopySettings(CQQPSettings &src,CQQPSettings &dst)
|
|
{
|
|
dst.m_epsg=src.m_epsg;
|
|
dst.m_epsf=src.m_epsf;
|
|
dst.m_epsx=src.m_epsx;
|
|
dst.m_maxouterits=src.m_maxouterits;
|
|
dst.m_cgphase=src.m_cgphase;
|
|
dst.m_cnphase=src.m_cnphase;
|
|
dst.m_cgminits=src.m_cgminits;
|
|
dst.m_cgmaxits=src.m_cgmaxits;
|
|
dst.m_sparsesolver=src.m_sparsesolver;
|
|
dst.m_cnmaxupdates=src.m_cnmaxupdates;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs preallocation of internal 2D matrices. If |
|
|
//| matrix size is less than expected, we grow to some larger value |
|
|
//| (specified by user). |
|
|
//| It can be useful in cases when we solve many subsequent QP |
|
|
//| problems with increasing sizes - helps to avoid multiple |
|
|
//| allocations. |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - object which stores temporaries: |
|
|
//| * uninitialized object is automatically |
|
|
//| initialized |
|
|
//| * previously allocated memory is reused as much |
|
|
//| as possible |
|
|
//| NExpected - if internal buffers have size enough for |
|
|
//| NExpected, no preallocation happens. If size is |
|
|
//| less than NExpected, buffers are preallocated up|
|
|
//| to NGrowTo*NGrowTo |
|
|
//| NGrowTo - new size |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| SState - temporary buffers, some of them are preallocated|
|
|
//+------------------------------------------------------------------+
|
|
void CQQPSolver::QQPPreAllocateGrowDense(CQQPBuffers &sstate,
|
|
int nexpected,
|
|
int ngrowto)
|
|
{
|
|
if(sstate.m_densea.Rows()<nexpected || sstate.m_densea.Cols()<nexpected)
|
|
sstate.m_densea.Resize(ngrowto,ngrowto);
|
|
if(sstate.m_densez.Rows()<nexpected || sstate.m_densez.Cols()<nexpected)
|
|
sstate.m_densez.Resize(ngrowto,ngrowto);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function runs QQP m_solver; it returns after optimization |
|
|
//| process was completed. Following QP problem is solved: |
|
|
//| min(0.5 * (x - x_origin)'*A*(x-x_origin)+b' * (x - x_origin)) |
|
|
//| subject to boundary constraints. |
|
|
//| IMPORTANT: UNLIKE MANY OTHER SOLVERS, THIS FUNCTION DOES NOT |
|
|
//| REQUIRE YOU TO INITIALIZE STATE OBJECT. IT CAN BE |
|
|
//| AUTOMATICALLY INITIALIZED DURING SOLUTION PROCESS. |
|
|
//| INPUT PARAMETERS: |
|
|
//| AC - for dense problems given by CQM model(AKind = 0) |
|
|
//| A - term of CQM object contains system matrix. Other |
|
|
//| terms are unspecified and should not be referenced.|
|
|
//| SparseAC - for sparse problems(AKind = 1) |
|
|
//| DenseAC - for traditional dense matrices(AKind = 2) |
|
|
//| AKind - matrix term to use: |
|
|
//| * 0 for dense CQM(CQMAC) |
|
|
//| * 1 for sparse matrix(SparseAC) |
|
|
//| * 2 for dense matrix(DenseAC) |
|
|
//| IsUpper - which triangle of SparseAC / DenseAC stores matrix-|
|
|
//| upper or lower one (for dense matrices this |
|
|
//| parameter is not actual). |
|
|
//| BC - linear term, array[NC] |
|
|
//| BndLC - lower bound, array[NC] |
|
|
//| BndUC - upper bound, array[NC] |
|
|
//| SC - scale vector, array[NC]: |
|
|
//| * I-Th element contains scale of I-Th variable, |
|
|
//| * SC[I] > 0 |
|
|
//| XOriginC - origin term, array[NC]. Can be zero. |
|
|
//| NC - number of variables in the original formulation |
|
|
//| (no slack variables). |
|
|
//| CLEICC - linear equality / inequality constraints. Present |
|
|
//| version of this function does NOT provide publicly |
|
|
//| available support for linear constraints. This |
|
|
//| feature will be introduced in the future versions |
|
|
//| of the function. |
|
|
//| NEC, NIC - number of equality / inequality constraints. MUST |
|
|
//| BE ZERO IN THE CURRENT VERSION!!! |
|
|
//| Settings - QQPSettings object initialized by one of the |
|
|
//| initialization functions. |
|
|
//| SState - object which stores temporaries: |
|
|
//| * uninitialized object is automatically initialized|
|
|
//| * previously allocated memory is reused as much as |
|
|
//| possible |
|
|
//| XS - initial point, array[NC] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XS - last point |
|
|
//| TerminationType - termination type: |
|
|
//| * |
|
|
//| * |
|
|
//| * |
|
|
//+------------------------------------------------------------------+
|
|
void CQQPSolver::QQPOptimize(CConvexQuadraticModel &cqmac,
|
|
CSparseMatrix &sparseac,
|
|
CMatrixDouble &denseac,
|
|
int akind,
|
|
bool IsUpper,
|
|
CRowDouble &bc,
|
|
CRowDouble &bndlc,
|
|
CRowDouble &bnduc,
|
|
CRowDouble &sc,
|
|
CRowDouble &xoriginc,
|
|
int nc,
|
|
CQQPSettings &Settings,
|
|
CQQPBuffers &sstate,
|
|
CRowDouble &xs,
|
|
int &terminationtype)
|
|
{
|
|
//--- create variables
|
|
int n=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double d2=0;
|
|
double d1=0;
|
|
int d1est=0;
|
|
int d2est=0;
|
|
bool needact;
|
|
double reststp=0;
|
|
double fullstp=0;
|
|
double stpmax=0;
|
|
double stp=0;
|
|
int stpcnt=0;
|
|
int cidx=0;
|
|
double cval=0;
|
|
int cgcnt=0;
|
|
int cgmax=0;
|
|
int newtcnt=0;
|
|
int sparsesolver=0;
|
|
double beta=0;
|
|
bool b;
|
|
double fprev=0;
|
|
double fcur=0;
|
|
bool problemsolved;
|
|
bool isconstrained;
|
|
double f0=0;
|
|
double f1=0;
|
|
int i_=0;
|
|
|
|
terminationtype=0;
|
|
//--- Primary checks
|
|
if(!CAp::Assert(akind==0 || akind==1 || akind==2,__FUNCTION__+": incorrect AKind"))
|
|
return;
|
|
|
|
sstate.m_n=nc;
|
|
n=sstate.m_n;
|
|
terminationtype=0;
|
|
sstate.m_repinneriterationscount=0;
|
|
sstate.m_repouteriterationscount=0;
|
|
sstate.m_repncholesky=0;
|
|
sstate.m_repncupdates=0;
|
|
//--- Several checks
|
|
//--- * matrix size
|
|
//--- * scale vector
|
|
//--- * consistency of bound constraints
|
|
//--- * consistency of Settings
|
|
if(akind==1)
|
|
{
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(sparseac)==n,__FUNCTION__+": rows(SparseAC)<>N"))
|
|
return;
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(sparseac)==n,__FUNCTION__+": cols(SparseAC)<>N"))
|
|
return;
|
|
}
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(sc,n) && sc.Min()>0.0,__FUNCTION__+": incorrect scale"))
|
|
return;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(MathIsValidNumber(bndlc[i]) && MathIsValidNumber(bnduc[i]))
|
|
if(bndlc[i]>bnduc[i])
|
|
{
|
|
terminationtype=-3;
|
|
return;
|
|
}
|
|
}
|
|
if(!CAp::Assert(Settings.m_cgphase || Settings.m_cnphase,__FUNCTION__+": both phases (CG and Newton) are inactive"))
|
|
return;
|
|
//--- Allocate data structures
|
|
CApServ::BVectorSetLengthAtLeast(sstate.m_havebndl,n);
|
|
CApServ::BVectorSetLengthAtLeast(sstate.m_havebndu,n);
|
|
sstate.m_bndl.Resize(n);
|
|
sstate.m_bndu.Resize(n);
|
|
sstate.m_xs.Resize(n);
|
|
sstate.m_xf.Resize(n);
|
|
sstate.m_xp.Resize(n);
|
|
sstate.m_gc.Resize(n);
|
|
sstate.m_cgc.Resize(n);
|
|
sstate.m_cgp.Resize(n);
|
|
sstate.m_dc.Resize(n);
|
|
sstate.m_dp.Resize(n);
|
|
sstate.m_tmp0.Resize(n);
|
|
sstate.m_tmp1.Resize(n);
|
|
sstate.m_stpbuf.Resize(15);
|
|
CSActiveSets::SASInit(n,sstate.m_sas);
|
|
//--- Scale/shift problem coefficients:
|
|
//--- min { 0.5*(x-x0)'*A*(x-x0) + b'*(x-x0) }
|
|
//--- becomes (after transformation "x = S*y+x0")
|
|
//--- min { 0.5*y'*(S*A*S)*y + (S*b)'*y
|
|
//--- Modified A_mod=S*A*S and b_mod=S*(b+A*x0) are
|
|
//--- stored into SState.DenseA and SState.B.
|
|
sstate.m_b=sc*bc+0;
|
|
sstate.m_b.Resize(n);
|
|
sstate.m_akind=-99;
|
|
if(akind==0)
|
|
{
|
|
//--- Dense QP problem - just copy and scale.
|
|
sstate.m_densea.Resize(n,n);
|
|
CCQModels::CQMGetA(cqmac,sstate.m_densea);
|
|
sstate.m_akind=0;
|
|
sstate.m_absamax=0;
|
|
sstate.m_absasum=0;
|
|
sstate.m_absasum2=0;
|
|
for(i=0; i<n; i++)
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=sc[i]*sstate.m_densea.Get(i,j)*sc[j];
|
|
vv=MathAbs(v);
|
|
sstate.m_densea.Set(i,j,v);
|
|
sstate.m_absamax=MathMax(sstate.m_absamax,vv);
|
|
sstate.m_absasum=sstate.m_absasum+vv;
|
|
sstate.m_absasum2=sstate.m_absasum2+vv*vv;
|
|
}
|
|
}
|
|
if(akind==1)
|
|
{
|
|
//--- Sparse QP problem - a bit tricky. Depending on format of the
|
|
//--- input we use different strategies for copying matrix:
|
|
//--- * SKS matrices are copied to SKS format
|
|
//--- * anything else is copied to CRS format
|
|
CSparse::SparseCopyToSKSBuf(sparseac,sstate.m_sparsea);
|
|
if(IsUpper)
|
|
CSparse::SparseTransposeSKS(sstate.m_sparsea);
|
|
sstate.m_akind=1;
|
|
sstate.m_sparseupper=false;
|
|
sstate.m_absamax=0;
|
|
sstate.m_absasum=0;
|
|
sstate.m_absasum2=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
k=sstate.m_sparsea.m_RIdx[i];
|
|
for(j=i-sstate.m_sparsea.m_DIdx[i]; j<=i; j++)
|
|
{
|
|
v=sc[i]*sstate.m_sparsea.m_Vals[k]*sc[j];
|
|
vv=MathAbs(v);
|
|
sstate.m_sparsea.m_Vals.Set(k,v);
|
|
if(i==j)
|
|
{
|
|
//--- Diagonal terms are counted only once
|
|
sstate.m_absamax=MathMax(sstate.m_absamax,vv);
|
|
sstate.m_absasum=sstate.m_absasum+vv;
|
|
sstate.m_absasum2=sstate.m_absasum2+vv*vv;
|
|
}
|
|
else
|
|
{
|
|
//--- Offdiagonal terms are counted twice
|
|
sstate.m_absamax=MathMax(sstate.m_absamax,vv);
|
|
sstate.m_absasum=sstate.m_absasum+2*vv;
|
|
sstate.m_absasum2=sstate.m_absasum2+2*vv*vv;
|
|
}
|
|
k++;
|
|
}
|
|
}
|
|
}
|
|
if(akind==2)
|
|
{
|
|
//--- Dense QP problem - just copy and scale.
|
|
sstate.m_densea.Resize(n,n);
|
|
sstate.m_akind=0;
|
|
sstate.m_absamax=0;
|
|
sstate.m_absasum=0;
|
|
sstate.m_absasum2=0;
|
|
if(IsUpper)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=i; j<n; j++)
|
|
{
|
|
v=sc[i]*denseac.Get(i,j)*sc[j];
|
|
vv=MathAbs(v);
|
|
sstate.m_densea.Set(i,j,v);
|
|
sstate.m_densea.Set(j,i,v);
|
|
if((double)(i)==v)
|
|
k=1;
|
|
else
|
|
k=2;
|
|
sstate.m_absamax=MathMax(sstate.m_absamax,vv);
|
|
sstate.m_absasum+=vv*k;
|
|
sstate.m_absasum2+=vv*vv*k;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<=i; j++)
|
|
{
|
|
v=sc[i]*denseac.Get(i,j)*sc[j];
|
|
vv=MathAbs(v);
|
|
sstate.m_densea.Set(i,j,v);
|
|
sstate.m_densea.Set(j,i,v);
|
|
if((double)(i)==v)
|
|
k=1;
|
|
else
|
|
k=2;
|
|
sstate.m_absamax=MathMax(sstate.m_absamax,vv);
|
|
sstate.m_absasum=sstate.m_absasum+vv*k;
|
|
sstate.m_absasum2=sstate.m_absasum2+vv*vv*k;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(sstate.m_akind>=0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Load box constraints into State structure.
|
|
//--- We apply transformation to variables: y=(x-x_origin)/s,
|
|
//--- each of the constraints is appropriately shifted/scaled.
|
|
for(i=0; i<n; i++)
|
|
{
|
|
sstate.m_havebndl[i]=MathIsValidNumber(bndlc[i]);
|
|
if(sstate.m_havebndl[i])
|
|
sstate.m_bndl.Set(i,(bndlc[i]-xoriginc[i])/sc[i]);
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(AL_NEGINF==bndlc[i],__FUNCTION__+": incorrect lower bound"))
|
|
return;
|
|
sstate.m_bndl.Set(i,AL_NEGINF);
|
|
}
|
|
sstate.m_havebndu[i]=MathIsValidNumber(bnduc[i]);
|
|
if(sstate.m_havebndu[i])
|
|
sstate.m_bndu.Set(i,(bnduc[i]-xoriginc[i])/sc[i]);
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(AL_POSINF==bnduc[i],__FUNCTION__+": incorrect upper bound"))
|
|
return;
|
|
sstate.m_bndu.Set(i,AL_POSINF);
|
|
}
|
|
}
|
|
//--- Process initial point:
|
|
//--- * set it to XS-XOriginC
|
|
//--- * make sure that boundary constraints are preserved by transformation
|
|
for(i=0; i<n; i++)
|
|
{
|
|
sstate.m_xs.Set(i,(xs[i]-xoriginc[i])/sc[i]);
|
|
if(sstate.m_havebndl[i] && sstate.m_xs[i]<sstate.m_bndl[i])
|
|
sstate.m_xs.Set(i,sstate.m_bndl[i]);
|
|
if(sstate.m_havebndu[i] && sstate.m_xs[i]>sstate.m_bndu[i])
|
|
sstate.m_xs.Set(i,sstate.m_bndu[i]);
|
|
if(sstate.m_havebndl[i] && xs[i]==bndlc[i])
|
|
sstate.m_xs.Set(i,sstate.m_bndl[i]);
|
|
if(sstate.m_havebndu[i] && xs[i]==bnduc[i])
|
|
sstate.m_xs.Set(i,sstate.m_bndu[i]);
|
|
}
|
|
//--- Select sparse direct m_solver
|
|
if(akind==1)
|
|
{
|
|
sparsesolver=Settings.m_sparsesolver;
|
|
if(sparsesolver==0)
|
|
sparsesolver=1;
|
|
if(CSparse::SparseIsSKS(sstate.m_sparsea))
|
|
sparsesolver=2;
|
|
sparsesolver=2;
|
|
//--- check
|
|
if(!CAp::Assert(sparsesolver==1 || sparsesolver==2,__FUNCTION__+": incorrect SparseSolver"))
|
|
return;
|
|
}
|
|
else
|
|
sparsesolver=0;
|
|
//--- For unconstrained problems - try to use fast approach which requires
|
|
//--- just one unregularized Cholesky decomposition for solution. If it fails,
|
|
//--- switch to general QQP code.
|
|
problemsolved=false;
|
|
isconstrained=false;
|
|
for(i=0; i<n; i++)
|
|
isconstrained=(isconstrained || sstate.m_havebndl[i] || sstate.m_havebndu[i]);
|
|
if(!isconstrained && Settings.m_cnphase && akind==0)
|
|
{
|
|
sstate.m_densez=sstate.m_densea;
|
|
sstate.m_densez.Resize(n,n);
|
|
sstate.m_tmpcn.Resize(n);
|
|
sstate.m_repncholesky++;
|
|
if(CTrFac::SPDMatrixCholeskyRec(sstate.m_densez,0,n,true,sstate.m_tmpcn))
|
|
{
|
|
sstate.m_xf=sstate.m_xs;
|
|
sstate.m_dc=vector<double>::Zeros(n);
|
|
f0=ProjectedTargetFunction(sstate,sstate.m_xf,sstate.m_dc,0.0,sstate.m_tmpcn,sstate.m_tmp1);
|
|
for(k=0; k<=3; k++)
|
|
{
|
|
CAblas::RMatrixMVect(n,n,sstate.m_densea,0,0,0,sstate.m_xf,0,sstate.m_gc,0);
|
|
sstate.m_gc+=sstate.m_b;
|
|
sstate.m_dc=sstate.m_gc;
|
|
sstate.m_dc*=(-1.0);
|
|
CFbls::FblsCholeskySolve(sstate.m_densez,1.0,n,true,sstate.m_dc,sstate.m_tmpcn);
|
|
f1=ProjectedTargetFunction(sstate,sstate.m_xf,sstate.m_dc,1.0,sstate.m_tmpcn,sstate.m_tmp1);
|
|
if(f1>=f0)
|
|
break;
|
|
sstate.m_xf+=sstate.m_dc;
|
|
f0=f1;
|
|
}
|
|
terminationtype=2;
|
|
problemsolved=true;
|
|
}
|
|
}
|
|
//--- Attempt to solve problem with fast approach failed, use generic QQP
|
|
if(!problemsolved)
|
|
{
|
|
//--- Prepare "active set" structure
|
|
CSActiveSets::SASSetBC(sstate.m_sas,sstate.m_bndl,sstate.m_bndu);
|
|
if(!CSActiveSets::SASStartOptimization(sstate.m_sas,sstate.m_xs))
|
|
{
|
|
terminationtype=-3;
|
|
return;
|
|
}
|
|
//--- Main loop.
|
|
//--- Following variables are used:
|
|
//--- * GC stores current gradient (unconstrained)
|
|
//--- * CGC stores current gradient (constrained)
|
|
//--- * DC stores current search direction
|
|
//--- * CGP stores constrained gradient at previous point
|
|
//--- (zero on initial entry)
|
|
//--- * DP stores previous search direction
|
|
//--- (zero on initial entry)
|
|
cgmax=Settings.m_cgminits;
|
|
sstate.m_repinneriterationscount=0;
|
|
sstate.m_repouteriterationscount=0;
|
|
while(true)
|
|
{
|
|
if(Settings.m_maxouterits>0 && sstate.m_repouteriterationscount>=Settings.m_maxouterits)
|
|
{
|
|
terminationtype=5;
|
|
break;
|
|
}
|
|
if(sstate.m_repouteriterationscount>0)
|
|
{
|
|
//--- Check EpsF- and EpsX-based stopping criteria.
|
|
//--- Because problem was already scaled, we do not scale step before checking its length.
|
|
//--- NOTE: these checks are performed only after at least one outer iteration was made.
|
|
if(Settings.m_epsf>0.0)
|
|
{
|
|
//--- NOTE 1: here we rely on the fact that ProjectedTargetFunction() ignore D when Stp=0
|
|
//--- NOTE 2: code below handles situation when update increases function value instead
|
|
//--- of decreasing it.
|
|
fprev=ProjectedTargetFunction(sstate,sstate.m_xp,sstate.m_dc,0.0,sstate.m_tmp0,sstate.m_tmp1);
|
|
fcur=ProjectedTargetFunction(sstate,sstate.m_sas.m_xc,sstate.m_dc,0.0,sstate.m_tmp0,sstate.m_tmp1);
|
|
if((fprev-fcur)<=(Settings.m_epsf*MathMax(MathAbs(fprev),MathMax(MathAbs(fcur),1.0))))
|
|
{
|
|
terminationtype=1;
|
|
break;
|
|
}
|
|
}
|
|
if(Settings.m_epsx>0.0)
|
|
{
|
|
v=0.0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v+= CMath::Sqr(sstate.m_xp[i]-sstate.m_sas.m_xc[i]);
|
|
}
|
|
if(MathSqrt(v)<=Settings.m_epsx)
|
|
{
|
|
terminationtype=2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
sstate.m_repouteriterationscount++;
|
|
sstate.m_xp=sstate.m_sas.m_xc;
|
|
if(!Settings.m_cgphase)
|
|
cgmax=0;
|
|
sstate.m_cgp=vector<double>::Zeros(n);
|
|
sstate.m_dp=vector<double>::Zeros(n);
|
|
for(cgcnt=0; cgcnt<=cgmax-1; cgcnt++)
|
|
{
|
|
//--- Calculate unconstrained gradient GC for "extended" QP problem
|
|
//--- Determine active set, current constrained gradient CGC.
|
|
//--- Check gradient-based stopping condition.
|
|
//
|
|
//--- NOTE: because problem was scaled, we do not have to apply scaling
|
|
//--- to gradient before checking stopping condition.
|
|
TargetGradient(sstate,sstate.m_sas.m_xc,sstate.m_gc);
|
|
CSActiveSets::SASReactivateConstraints(sstate.m_sas,sstate.m_gc);
|
|
sstate.m_cgc=sstate.m_gc;
|
|
CSActiveSets::SASConstrainedDirection(sstate.m_sas,sstate.m_cgc);
|
|
v=CAblasF::RDotV2(n,sstate.m_cgc);
|
|
if(MathSqrt(v)<=Settings.m_epsg)
|
|
{
|
|
terminationtype=4;
|
|
break;
|
|
}
|
|
//--- Prepare search direction DC and explore it.
|
|
//--- We try to use CGP/DP to prepare conjugate gradient step,
|
|
//--- but we resort to steepest descent step (Beta=0) in case
|
|
//--- we are at I-th boundary, but DP[I]<>0.
|
|
//--- Such approach allows us to ALWAYS have feasible DC, with
|
|
//--- guaranteed compatibility with both feasible area and current
|
|
//--- active set.
|
|
//--- Automatic CG reset performed every time DP is incompatible
|
|
//--- with current active set and/or feasible area. We also
|
|
//--- perform reset every QuickQPRestartCG iterations.
|
|
sstate.m_dc=sstate.m_cgc;
|
|
sstate.m_dc*=(-1.0);
|
|
v=0.0;
|
|
vv=0.0;
|
|
b=false;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v+= sstate.m_cgc[i]*sstate.m_cgc[i];
|
|
vv+=sstate.m_cgp[i]*sstate.m_cgp[i];
|
|
b=b || (sstate.m_havebndl[i] && sstate.m_sas.m_xc[i]==sstate.m_bndl[i] && sstate.m_dp[i]!=0.0);
|
|
b=b || (sstate.m_havebndu[i] && sstate.m_sas.m_xc[i]==sstate.m_bndu[i] && sstate.m_dp[i]!=0.0);
|
|
}
|
|
b=b || vv==0.0;
|
|
b=b || cgcnt%m_quickqprestartcg==0;
|
|
if(!b)
|
|
beta=v/vv;
|
|
else
|
|
beta=0.0;
|
|
sstate.m_dc+=sstate.m_dp*beta+0;
|
|
CSActiveSets::SASConstrainedDirection(sstate.m_sas,sstate.m_dc);
|
|
CSActiveSets::SASExploreDirection(sstate.m_sas,sstate.m_dc,stpmax,cidx,cval);
|
|
//--- Build quadratic model of F along descent direction:
|
|
//--- F(xc+alpha*D) = D2*alpha^2 + D1*alpha
|
|
//--- Terminate algorithm if needed.
|
|
//--- NOTE: we do not maintain constant term D0
|
|
QuadraticModel(sstate,sstate.m_sas.m_xc,sstate.m_dc,sstate.m_gc,d1,d1est,d2,d2est,sstate.m_tmp0);
|
|
if(d1==0.0 && d2==0.0)
|
|
{
|
|
//--- D1 and D2 are exactly zero, success.
|
|
//--- After this if-then we assume that D is non-zero.
|
|
terminationtype=4;
|
|
break;
|
|
}
|
|
if(d1est>=0)
|
|
{
|
|
//--- Numerical noise is too large, it means that we are close
|
|
//--- to minimum - and that further improvement is impossible.
|
|
//--- After this if-then we assume that D1 is definitely negative
|
|
//--- (even under presence of numerical errors).
|
|
terminationtype=7;
|
|
break;
|
|
}
|
|
if(d2est<=0 && cidx<0)
|
|
{
|
|
//--- Function is unbounded from below:
|
|
//--- * D1<0 (verified by previous block)
|
|
//--- * D2Est<=0, which means that either D2<0 - or it can not
|
|
//--- be reliably distinguished from zero.
|
|
//--- * step is unconstrained
|
|
//--- If these conditions are true, we abnormally terminate QP
|
|
//--- algorithm with return code -4
|
|
terminationtype=-4;
|
|
break;
|
|
}
|
|
//--- Perform step along DC.
|
|
//--- In this block of code we maintain two step length:
|
|
//--- * RestStp - restricted step, maximum step length along DC which does
|
|
//--- not violate constraints
|
|
//--- * FullStp - step length along DC which minimizes quadratic function
|
|
//--- without taking constraints into account. If problem is
|
|
//--- unbounded from below without constraints, FullStp is
|
|
//--- forced to be RestStp.
|
|
//--- So, if function is convex (D2>0):
|
|
//--- * FullStp = -D1/(2*D2)
|
|
//--- * RestStp = restricted FullStp
|
|
//--- * 0<=RestStp<=FullStp
|
|
//--- If function is non-convex, but bounded from below under constraints:
|
|
//--- * RestStp = step length subject to constraints
|
|
//--- * FullStp = RestStp
|
|
//--- After RestStp and FullStp are initialized, we generate several trial
|
|
//--- steps which are different multiples of RestStp and FullStp.
|
|
if(d2est>0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(d1<0.0,__FUNCTION__+": internal error"))
|
|
return;
|
|
fullstp=-(d1/(2*d2));
|
|
needact=(fullstp>=stpmax);
|
|
if(needact)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(sstate.m_stpbuf.Size()>=3,__FUNCTION__+": StpBuf overflow"))
|
|
return;
|
|
reststp=stpmax;
|
|
stp=reststp;
|
|
sstate.m_stpbuf.Set(0,reststp*4);
|
|
sstate.m_stpbuf.Set(1,fullstp);
|
|
sstate.m_stpbuf.Set(2,fullstp/4);
|
|
stpcnt=3;
|
|
}
|
|
else
|
|
{
|
|
reststp=fullstp;
|
|
stp=fullstp;
|
|
stpcnt=0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(cidx>=0,__FUNCTION__+": internal error"))
|
|
return;
|
|
if(!CAp::Assert(sstate.m_stpbuf.Size()>=2,__FUNCTION__+": StpBuf overflow"))
|
|
return;
|
|
reststp=stpmax;
|
|
fullstp=stpmax;
|
|
stp=reststp;
|
|
needact=true;
|
|
sstate.m_stpbuf.Set(0,4*reststp);
|
|
stpcnt=1;
|
|
}
|
|
FindBestStepAndMove(sstate,sstate.m_sas,sstate.m_dc,stp,needact,cidx,cval,sstate.m_stpbuf,stpcnt,sstate.m_activated,sstate.m_tmp0,sstate.m_tmp1);
|
|
//--- Update CG information.
|
|
sstate.m_dp=sstate.m_dc;
|
|
sstate.m_cgp=sstate.m_cgc;
|
|
//--- Update iterations counter
|
|
sstate.m_repinneriterationscount++;
|
|
}
|
|
if(terminationtype!=0)
|
|
break;
|
|
cgmax=Settings.m_cgmaxits;
|
|
//--- Generate YIdx - reordering of variables for constrained Newton phase.
|
|
//--- Free variables come first, fixed are last ones.
|
|
newtcnt=0;
|
|
while(true)
|
|
{
|
|
//--- Skip iteration if constrained Newton is turned off.
|
|
if(!Settings.m_cnphase)
|
|
break;
|
|
//--- At the first iteration - build Cholesky decomposition of Hessian.
|
|
//--- At subsequent iterations - refine Hessian by adding new constraints.
|
|
//--- Loop is terminated in following cases:
|
|
//--- * Hessian is not positive definite subject to current constraints
|
|
//--- (termination during initial decomposition)
|
|
//--- * there were no new constraints being activated
|
|
//--- (termination during update)
|
|
//--- * all constraints were activated during last step
|
|
//--- (termination during update)
|
|
//--- * CNMaxUpdates were performed on matrix
|
|
//--- (termination during update)
|
|
if(newtcnt==0)
|
|
{
|
|
//--- Perform initial Newton step. If Cholesky decomposition fails,
|
|
//--- increase number of CG iterations to CGMaxIts - it should help
|
|
//--- us to find set of constraints which will make matrix positive
|
|
//--- definite.
|
|
b=CNewtonBuild(sstate,sparsesolver,sstate.m_repncholesky);
|
|
if(b)
|
|
cgmax=Settings.m_cgminits;
|
|
}
|
|
else
|
|
b=CNewtonUpdate(sstate,Settings,sstate.m_repncupdates);
|
|
if(!b)
|
|
break;
|
|
newtcnt++;
|
|
//--- Calculate gradient GC.
|
|
TargetGradient(sstate,sstate.m_sas.m_xc,sstate.m_gc);
|
|
//--- Bound-constrained Newton step
|
|
sstate.m_dc=sstate.m_gc;
|
|
if(!CNewtonStep(sstate,Settings,sstate.m_dc))
|
|
break;
|
|
QuadraticModel(sstate,sstate.m_sas.m_xc,sstate.m_dc,sstate.m_gc,d1,d1est,d2,d2est,sstate.m_tmp0);
|
|
if(d1est>=0)
|
|
{
|
|
//--- We are close to minimum, derivative is nearly zero, break Newton iteration
|
|
break;
|
|
}
|
|
if(d2est>0)
|
|
{
|
|
//--- Positive definite matrix, we can perform Newton step
|
|
//--- check
|
|
if(!CAp::Assert(d1<0.0,__FUNCTION__+": internal error"))
|
|
return;
|
|
fullstp=-(d1/(2*d2));
|
|
CSActiveSets::SASExploreDirection(sstate.m_sas,sstate.m_dc,stpmax,cidx,cval);
|
|
needact=(fullstp>=stpmax);
|
|
if(needact)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(sstate.m_stpbuf.Size()>=3,__FUNCTION__+": StpBuf overflow"))
|
|
return;
|
|
reststp=stpmax;
|
|
stp=reststp;
|
|
sstate.m_stpbuf.Set(0,reststp*4);
|
|
sstate.m_stpbuf.Set(1,fullstp);
|
|
sstate.m_stpbuf.Set(2,fullstp/4);
|
|
stpcnt=3;
|
|
}
|
|
else
|
|
{
|
|
reststp=fullstp;
|
|
stp=fullstp;
|
|
stpcnt=0;
|
|
}
|
|
FindBestStepAndMove(sstate,sstate.m_sas,sstate.m_dc,stp,needact,cidx,cval,sstate.m_stpbuf,stpcnt,sstate.m_activated,sstate.m_tmp0,sstate.m_tmp1);
|
|
}
|
|
else
|
|
{
|
|
//--- Matrix is semi-definite or indefinite, but regularized
|
|
//--- Cholesky succeeded and gave us descent direction in DC.
|
|
//--- We will investigate it and try to perform descent step:
|
|
//--- * first, we explore direction:
|
|
//--- * if it is unbounded, we stop algorithm with
|
|
//--- appropriate termination code -4.
|
|
//--- * if StpMax=0, we break Newton phase and return to
|
|
//--- CG phase - constraint geometry is complicated near
|
|
//--- current point, so it is better to use simpler algo.
|
|
//--- * second, we check that bounded step decreases function;
|
|
//--- if not, we again skip to CG phase
|
|
//--- * finally, we use FindBestStep...() function to choose
|
|
//--- between bounded step and projection of full-length step
|
|
//--- (latter may give additional decrease in
|
|
CSActiveSets::SASExploreDirection(sstate.m_sas,sstate.m_dc,stpmax,cidx,cval);
|
|
if(cidx<0)
|
|
{
|
|
//--- Function is unbounded from below:
|
|
//--- * D1<0 (verified by previous block)
|
|
//--- * D2Est<=0, which means that either D2<0 - or it can not
|
|
//--- be reliably distinguished from zero.
|
|
//--- * step is unconstrained
|
|
//--- If these conditions are true, we abnormally terminate QP
|
|
//--- algorithm with return code -4
|
|
terminationtype=-4;
|
|
break;
|
|
}
|
|
if(stpmax==0.0)
|
|
{
|
|
//--- Resort to CG phase.
|
|
//--- Increase number of CG iterations.
|
|
cgmax=Settings.m_cgmaxits;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(stpmax>0.0,__FUNCTION__+": internal error"))
|
|
return;
|
|
f0=ProjectedTargetFunction(sstate,sstate.m_sas.m_xc,sstate.m_dc,0.0,sstate.m_tmp0,sstate.m_tmp1);
|
|
f1=ProjectedTargetFunction(sstate,sstate.m_sas.m_xc,sstate.m_dc,stpmax,sstate.m_tmp0,sstate.m_tmp1);
|
|
if(f1>=f0)
|
|
{
|
|
//--- Descent direction does not actually decrease function value.
|
|
//--- Resort to CG phase
|
|
//--- Increase number of CG iterations.
|
|
cgmax=Settings.m_cgmaxits;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(sstate.m_stpbuf.Size()>=3,__FUNCTION__+": StpBuf overflow"))
|
|
return;
|
|
reststp=stpmax;
|
|
stp=reststp;
|
|
sstate.m_stpbuf.Set(0,reststp*4);
|
|
sstate.m_stpbuf.Set(1,1.00);
|
|
sstate.m_stpbuf.Set(2,0.25);
|
|
stpcnt=3;
|
|
FindBestStepAndMove(sstate,sstate.m_sas,sstate.m_dc,stp,true,cidx,cval,sstate.m_stpbuf,stpcnt,sstate.m_activated,sstate.m_tmp0,sstate.m_tmp1);
|
|
}
|
|
}
|
|
if(terminationtype!=0)
|
|
break;
|
|
}
|
|
CSActiveSets::SASStopOptimization(sstate.m_sas);
|
|
sstate.m_xf=sstate.m_sas.m_xc;
|
|
}
|
|
//--- Stop optimization and unpack results.
|
|
//--- Add XOriginC to XS and make sure that boundary constraints are
|
|
//--- both (a) satisfied, (b) preserved. Former means that "shifted"
|
|
//--- point is feasible, while latter means that point which was exactly
|
|
//--- at the boundary before shift will be exactly at the boundary
|
|
//--- after shift.
|
|
for(i=0; i<n; i++)
|
|
{
|
|
xs.Set(i,sc[i]*sstate.m_xf[i]+xoriginc[i]);
|
|
if(sstate.m_havebndl[i] && xs[i]<bndlc[i])
|
|
xs.Set(i,bndlc[i]);
|
|
if(sstate.m_havebndu[i] && xs[i]>bnduc[i])
|
|
xs.Set(i,bnduc[i]);
|
|
if(sstate.m_havebndl[i] && sstate.m_xf[i]==sstate.m_bndl[i])
|
|
xs.Set(i,bndlc[i]);
|
|
if(sstate.m_havebndu[i] && sstate.m_xf[i]==sstate.m_bndu[i])
|
|
xs.Set(i,bnduc[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Target function at point PROJ(X + Stp*D), where PROJ(.) is a |
|
|
//| projection into feasible set. |
|
|
//| NOTE: if Stp = 0, D is not referenced at all. Thus, there is no |
|
|
//| need to fill it by some meaningful values for Stp = 0. |
|
|
//| This subroutine uses temporary buffers Tmp0 / 1, which are |
|
|
//| automatically resized if needed. |
|
|
//+------------------------------------------------------------------+
|
|
double CQQPSolver::ProjectedTargetFunction(CQQPBuffers &sstate,
|
|
CRowDouble &x,
|
|
CRowDouble &d,
|
|
double stp,
|
|
CRowDouble &m_tmp0,
|
|
CRowDouble &m_tmp1)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int n=sstate.m_n;
|
|
double v=0;
|
|
|
|
m_tmp0.Resize(n);
|
|
m_tmp1.Resize(n);
|
|
//--- Calculate projected point
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(stp!=0.0)
|
|
v=x[i]+stp*d[i];
|
|
else
|
|
v=x[i];
|
|
if(sstate.m_havebndl[i] && v<sstate.m_bndl[i])
|
|
v=sstate.m_bndl[i];
|
|
if(sstate.m_havebndu[i] && v>sstate.m_bndu[i])
|
|
v=sstate.m_bndu[i];
|
|
m_tmp0.Set(i,v);
|
|
}
|
|
//--- Function value at the Tmp0:
|
|
//--- f(x) = 0.5*x'*A*x + b'*x
|
|
result=sstate.m_b.Dot(m_tmp0);
|
|
if(sstate.m_akind==0)
|
|
{
|
|
//--- Dense matrix A
|
|
result+=0.5*CAblas::RMatrixSyvMVect(n,sstate.m_densea,0,0,true,m_tmp0,0,m_tmp1);
|
|
}
|
|
else
|
|
{
|
|
//--- sparse matrix A
|
|
//--- check
|
|
if(!CAp::Assert(sstate.m_akind==1,__FUNCTION__+": unexpected AKind in ProjectedTargetFunction"))
|
|
return(0.0);
|
|
result+=0.5*CSparse::SparseVSMV(sstate.m_sparsea,sstate.m_sparseupper,m_tmp0);
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Gradient of the target function: |
|
|
//| f(x) = 0.5 * x'*A*x + b'*x |
|
|
//| which is equal to grad = A * x + b |
|
|
//| Here: |
|
|
//| * x is array[N] |
|
|
//| * A is array[N, N] |
|
|
//| * b is array[N] |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - structure which stores function terms(not modified)|
|
|
//| X - location |
|
|
//| G - possibly preallocated buffer |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| G - array[N], gradient |
|
|
//+------------------------------------------------------------------+
|
|
void CQQPSolver::TargetGradient(CQQPBuffers &sstate,
|
|
CRowDouble &x,
|
|
CRowDouble &g)
|
|
{
|
|
int n=sstate.m_n;
|
|
g.Resize(n);
|
|
|
|
if(sstate.m_akind==0)
|
|
{
|
|
//--- Dense matrix A
|
|
CAblas::RMatrixSymVect(n,1.0,sstate.m_densea,0,0,true,x,0,0.0,g,0);
|
|
}
|
|
else
|
|
{
|
|
//--- Sparse matrix A
|
|
//--- check
|
|
if(!CAp::Assert(sstate.m_akind==1,__FUNCTION__+": unexpected AKind in TargetGradient"))
|
|
return;
|
|
CSparse::SparseSMV(sstate.m_sparsea,sstate.m_sparseupper,x,g);
|
|
}
|
|
|
|
g+=sstate.m_b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| First and second derivatives of the "extended" target function |
|
|
//| along specified direction. Target function is called "extended" |
|
|
//| because of additional slack variables and has form: |
|
|
//| f(x)=0.5*x'*A*x+b'*x+penaltyfactor*0.5*(C*x-b)'*(C*x-b) |
|
|
//| with gradient grad = A * x + b + penaltyfactor * C'*(C*x-b) |
|
|
//| Quadratic model has form |
|
|
//| F(x0 + alpha*D) = D2 * alpha ^ 2 + D1 * alpha |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - structure which is used to obtain quadratic term |
|
|
//| of the model |
|
|
//| X - current point, array[N] |
|
|
//| D - direction across which derivatives are calculated, |
|
|
//| array[N] |
|
|
//| G - gradient at current point (pre-calculated by |
|
|
//| caller), array[N] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D1 - linear coefficient |
|
|
//| D1Est - estimate of D1 sign, accounting for possible |
|
|
//| numerical errors: |
|
|
//| *>0 means "almost surely positive" |
|
|
//| *<0 means "almost surely negative" |
|
|
//| *=0 means "pessimistic estimate of numerical |
|
|
//| errors in D1 is larger than magnitude of |
|
|
//| D1 itself; it is impossible to reliably |
|
|
//| distinguish D1 from zero". |
|
|
//| D2 - quadratic coefficient |
|
|
//| D2Est - estimate of D2 sign, accounting for possible |
|
|
//| numerical errors: |
|
|
//| *>0 means "almost surely positive" |
|
|
//| *<0 means "almost surely negative" |
|
|
//| *=0 means "pessimistic estimate of numerical |
|
|
//| errors in D2 is larger than magnitude of |
|
|
//| D2 itself; it is impossible to reliably |
|
|
//| distinguish D2 from zero". |
|
|
//+------------------------------------------------------------------+
|
|
void CQQPSolver::QuadraticModel(CQQPBuffers &sstate,CRowDouble &x,
|
|
CRowDouble &d,CRowDouble &g,
|
|
double &d1,int &d1est,double &d2,
|
|
int &d2est,CRowDouble &m_tmp0)
|
|
{
|
|
//--- create variables
|
|
int n=sstate.m_n;
|
|
double v=0;
|
|
double mx=(x.Abs()+0).Max();
|
|
double md=(d.Abs()+0).Max();
|
|
double mb=(sstate.m_b.Abs()+0).Max();
|
|
|
|
d1=0;
|
|
d1est=0;
|
|
d2=0;
|
|
d2est=0;
|
|
//--- Maximums
|
|
//--- D2
|
|
if(sstate.m_akind==0)
|
|
{
|
|
//--- Dense matrix A
|
|
d2=0.5*CAblas::RMatrixSyvMVect(n,sstate.m_densea,0,0,true,d,0,m_tmp0);
|
|
}
|
|
else
|
|
{
|
|
//--- Sparse matrix A
|
|
//--- check
|
|
if(!CAp::Assert(sstate.m_akind==1,__FUNCTION__+": unexpected AKind in TargetGradient"))
|
|
return;
|
|
d2=0.5*CSparse::SparseVSMV(sstate.m_sparsea,sstate.m_sparseupper,d);
|
|
}
|
|
v=d.Dot(g);
|
|
d1=v;
|
|
//--- Error estimates
|
|
COptServ::EstimateParabolicModel(sstate.m_absasum,sstate.m_absasum2,mx,mb,md,d1,d2,d1est,d2est);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function accepts quadratic model of the form |
|
|
//| f(x) = 0.5*x'*A*x+b'*x+penaltyfactor*0.5*(C*x-b)'*(C*x-b) |
|
|
//| and list of possible steps along direction D. It chooses best |
|
|
//| step (one which achieves minimum value of the target function) |
|
|
//| and moves current point (given by SAS object) to the new location|
|
|
//| Step is bounded subject to boundary constraints. |
|
|
//| Candidate steps are divided into two groups: |
|
|
//| *"default" step, which is always performed when no candidate |
|
|
//| steps LONGER THAN THE DEFAULT ONE is given. This candidate |
|
|
//| MUST reduce target function value; it is responsibility of |
|
|
//| caller to provide default candidate which reduces target |
|
|
//| function. |
|
|
//| *"additional candidates", which may be shorter or longer than |
|
|
//| the default step. Candidates which are shorter that the |
|
|
//| default step are ignored; candidates which are longer than |
|
|
//| the "default" step are tested. |
|
|
//| The idea is that we ALWAYS try "default" step, and it is |
|
|
//| responsibility of the caller to provide us with something which |
|
|
//| is worth trying. This step may activate some constraint - that's |
|
|
//| why we stopped at "default" step size. However, we may also try |
|
|
//| longer steps which may activate additional constraints and |
|
|
//| further reduce function value. |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - structure which stores model |
|
|
//| SAS - active set structure which stores current point in |
|
|
//| SAS.XC |
|
|
//| D - direction for step |
|
|
//| Stp - step length for "default" candidate |
|
|
//| NeedAct - whether default candidate activates some constraint|
|
|
//| if NeedAct is True, constraint given by CIdc/CVal |
|
|
//| is GUARANTEED to be activated in the final point. |
|
|
//| CIdx - if NeedAct is True, stores index of the constraint |
|
|
//| to activate |
|
|
//| CVal - if NeedAct is True, stores constrained value; |
|
|
//| SAS.XC[CIdx] is forced to be equal to CVal. |
|
|
//| AddSteps - array[AddStepsCnt] of additional steps: |
|
|
//| * AddSteps[] <= Stp are ignored |
|
|
//| * AddSteps[] > Stp are tried |
|
|
//| Activated - possibly preallocated buffer; previously allocated |
|
|
//| memory will be reused. |
|
|
//| Tmp0 / 1 - possibly preallocated buffers; previously allocated|
|
|
//| memory will be reused. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| SAS - SAS.XC is set to new point; if there was a |
|
|
//| constraint specified by NeedAct/CIdx/CVal, it will |
|
|
//| be activated (other constraints may be activated |
|
|
//| too, but this one is guaranteed to be active in the|
|
|
//| final point). |
|
|
//| Activated - elements of this array are set to True, if I-Th |
|
|
//| constraint as inactive at previous point, but |
|
|
//| become active in the new one. |
|
|
//| Situations when we deactivate xi >= 0 and activate xi <= 1 are |
|
|
//| considered as activation of previously inactive constraint |
|
|
//+------------------------------------------------------------------+
|
|
void CQQPSolver::FindBestStepAndMove(CQQPBuffers &sstate,
|
|
CSActiveSet &sas,
|
|
CRowDouble &d,
|
|
double stp,
|
|
bool needact,
|
|
int cidx,
|
|
double cval,
|
|
CRowDouble &addsteps,
|
|
int addstepscnt,
|
|
bool &activated[],
|
|
CRowDouble &m_tmp0,
|
|
CRowDouble &m_tmp1)
|
|
{
|
|
//--- create variables
|
|
int n=sstate.m_n;
|
|
int i=0;
|
|
int k=0;
|
|
double v=0;
|
|
double stpbest=0;
|
|
double fbest=0;
|
|
double fcand=0;
|
|
|
|
m_tmp0.Resize(n);
|
|
CApServ::BVectorSetLengthAtLeast(activated,n);
|
|
//--- Calculate initial step, store to Tmp0
|
|
//--- NOTE: Tmp0 is guaranteed to be feasible w.m_r.m_t. boundary constraints
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=sas.m_xc[i]+stp*d[i];
|
|
if(sstate.m_havebndl[i] && v<sstate.m_bndl[i])
|
|
v=sstate.m_bndl[i];
|
|
if(sstate.m_havebndu[i] && v>sstate.m_bndu[i])
|
|
v=sstate.m_bndu[i];
|
|
m_tmp0.Set(i,v);
|
|
}
|
|
if(needact)
|
|
m_tmp0.Set(cidx,cval);
|
|
//--- Try additional steps, if AddStepsCnt>0
|
|
if(addstepscnt>0)
|
|
{
|
|
//--- Find best step
|
|
stpbest=stp;
|
|
fbest=ProjectedTargetFunction(sstate,sas.m_xc,d,stpbest,m_tmp0,m_tmp1);
|
|
for(k=0; k<=addstepscnt-1; k++)
|
|
{
|
|
if(addsteps[k]>stp)
|
|
{
|
|
fcand=ProjectedTargetFunction(sstate,sas.m_xc,d,addsteps[k],m_tmp0,m_tmp1);
|
|
if(fcand<fbest)
|
|
{
|
|
fbest=fcand;
|
|
stpbest=addsteps[k];
|
|
}
|
|
}
|
|
}
|
|
//--- Prepare best step
|
|
//--- NOTE: because only AddSteps[]>Stp were checked,
|
|
//--- this step will activate constraint CIdx.
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=sas.m_xc[i]+stpbest*d[i];
|
|
if(sstate.m_havebndl[i] && v<sstate.m_bndl[i])
|
|
v=sstate.m_bndl[i];
|
|
if(sstate.m_havebndu[i] && v>sstate.m_bndu[i])
|
|
v=sstate.m_bndu[i];
|
|
m_tmp0.Set(i,v);
|
|
}
|
|
if(needact)
|
|
m_tmp0.Set(cidx,cval);
|
|
}
|
|
//--- Fill Activated array by information about activated constraints.
|
|
//--- Perform step
|
|
for(i=0; i<n; i++)
|
|
{
|
|
activated[i]=false;
|
|
v=m_tmp0[i];
|
|
if(v==sas.m_xc[i])
|
|
continue;
|
|
if(sstate.m_havebndl[i] && v==sstate.m_bndl[i])
|
|
activated[i]=true;
|
|
if(sstate.m_havebndu[i] && v==sstate.m_bndu[i])
|
|
activated[i]=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
CSActiveSets::SASMoveTo(sas,m_tmp0,needact,cidx,cval);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function prepares data for constrained Newton step for |
|
|
//| penalized quadratic model of the form |
|
|
//| f(x) = 0.5*x'*A*x+b'*x+penaltyfactor*0.5*(C*x-b)'*(C*x-b) |
|
|
//| where A can be dense or sparse, and model is considered subject |
|
|
//| to equality constraints specified by SState.SAS.XC object. |
|
|
//| Constraint is considered active if XC[i] is exactly BndL[i] or |
|
|
//| BndU[i], i.e. we ignore internal list of constraints monitored |
|
|
//| by SAS object. Our own set of constraints includes all |
|
|
//| constraints stored by SAS, but also may include some constraints |
|
|
//| which are inactive in SAS. |
|
|
//| "Preparation" means that Cholesky decomposition of the effective |
|
|
//| system matrix is performed, and we can perform constrained Newton|
|
|
//| step. |
|
|
//| This function works as black box. It uses fields of SState which |
|
|
//| are marked as "Variables for constrained Newton phase", and only |
|
|
//| this function and its friends know about these variables. |
|
|
//| Everyone else should use: |
|
|
//| * CNewtonBuild() to prepare initial Cholesky decomposition for|
|
|
//| step |
|
|
//| * CNewtonStep() to perform constrained Newton step |
|
|
//| * CNewtonUpdate() to update Cholesky matrix after point was |
|
|
//| moved and constraints were updated. In some |
|
|
//| cases it is possible to efficiently real - |
|
|
//| calculate Cholesky decomposition if you know |
|
|
//| which constraints were activated. If |
|
|
//| efficient real - calculation is impossible, |
|
|
//| this function returns False. |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - structure which stores model and temporaries |
|
|
//| for CN phase; in particular, SAS.XC stores |
|
|
//| current point. |
|
|
//| SparseSolver - which sparse m_solver to use for sparse model; |
|
|
//| ignored for dense QP. Can be: |
|
|
//| * 2 - SKS - based Cholesky |
|
|
//| NCholesky - counter which is incremented after Cholesky |
|
|
//| (successful or failed one) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| NCholesky - possibly updated counter |
|
|
//| RESULT: |
|
|
//| True, if Cholesky decomposition was successfully performed. |
|
|
//| False, if a) matrix was semi - definite or indefinite, or |
|
|
//| b) particular combination of matrix type(sparse) |
|
|
//| and constraints (general linear) is not supported.|
|
|
//| NOTE: this function may routinely return False, for indefinite |
|
|
//| matrices or for sparse problems with general linear |
|
|
//| constraints. You should be able to handle such situations. |
|
|
//+------------------------------------------------------------------+
|
|
bool CQQPSolver::CNewtonBuild(CQQPBuffers &sstate,int sparsesolver,
|
|
int &ncholesky)
|
|
{
|
|
//--- create variables
|
|
int n=sstate.m_n;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
bool b;
|
|
int ridx0=0;
|
|
int ridx1=0;
|
|
int nfree=0;
|
|
int i_=0;
|
|
//--- 1. Set CNModelAge to zero
|
|
//--- 2. Generate YIdx - reordering of variables such that free variables
|
|
//--- come first and are ordered by ascending, fixed are last ones and
|
|
//--- have no particular ordering.
|
|
//--- This step is same for dense and sparse problems.
|
|
sstate.m_cnmodelage=0;
|
|
sstate.m_yidx.Resize(n);
|
|
ridx0=0;
|
|
ridx1=n-1;
|
|
sstate.m_yidx.Fill(-1,0,n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(!sstate.m_havebndl[i] || sstate.m_sas.m_xc[i]>=sstate.m_bndl[i],__FUNCTION__+": internal error"))
|
|
return(false);
|
|
if(!CAp::Assert(!sstate.m_havebndu[i] || sstate.m_sas.m_xc[i]<=sstate.m_bndu[i],__FUNCTION__+": internal error"))
|
|
return(false);
|
|
b=false;
|
|
b=b || (sstate.m_havebndl[i] && sstate.m_sas.m_xc[i]==sstate.m_bndl[i]);
|
|
b=b || (sstate.m_havebndu[i] && sstate.m_sas.m_xc[i]==sstate.m_bndu[i]);
|
|
if(b)
|
|
{
|
|
sstate.m_yidx.Set(ridx1,i);
|
|
ridx1--;
|
|
}
|
|
else
|
|
{
|
|
sstate.m_yidx.Set(ridx0,i);
|
|
ridx0++;
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(ridx0==ridx1+1,__FUNCTION__+": internal error"))
|
|
return(false);
|
|
nfree=ridx0;
|
|
sstate.m_nfree=nfree;
|
|
if(nfree==0)
|
|
return(false);
|
|
//--- Constrained Newton matrix: dense version
|
|
if(sstate.m_akind==0)
|
|
{
|
|
sstate.m_densez=sstate.m_densea;
|
|
sstate.m_densez.Resize(n,n);
|
|
sstate.m_tmpcn.Resize(n);
|
|
for(i=1; i<nfree; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(sstate.m_yidx[i]>sstate.m_yidx[i-1],__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
}
|
|
for(i=0; i<nfree; i++)
|
|
{
|
|
k=sstate.m_yidx[i];
|
|
for(j=i; j<nfree; j++)
|
|
sstate.m_densez.Set(i,j,sstate.m_densez.Get(k,sstate.m_yidx[j]));
|
|
}
|
|
sstate.m_regdiag.Resize(n);
|
|
for(i=0; i<nfree; i++)
|
|
{
|
|
v=0.0;
|
|
for(j=0; j<i; j++)
|
|
v+= MathAbs(sstate.m_densez.Get(j,i));
|
|
for(j=i; j<nfree; j++)
|
|
v+= MathAbs(sstate.m_densez.Get(i,j));
|
|
if(v==0.0)
|
|
v=1.0;
|
|
sstate.m_regdiag.Set(i,m_regz*v);
|
|
}
|
|
for(i=0; i<nfree; i++)
|
|
sstate.m_densez.Add(i,i,sstate.m_regdiag[i]);
|
|
ncholesky++;
|
|
if(!CTrFac::SPDMatrixCholeskyRec(sstate.m_densez,0,nfree,true,sstate.m_tmpcn))
|
|
return(false);
|
|
for(i=nfree-1; i>=0; i--)
|
|
{
|
|
for(i_=i; i_<nfree; i_++)
|
|
sstate.m_tmpcn.Set(i_,sstate.m_densez.Get(i,i_));
|
|
k=sstate.m_yidx[i];
|
|
for(j=k; j<n; j++)
|
|
sstate.m_densez.Set(k,j,0);
|
|
for(j=i; j<nfree; j++)
|
|
sstate.m_densez.Set(k,sstate.m_yidx[j],sstate.m_tmpcn[j]);
|
|
}
|
|
for(i=nfree; i<n; i++)
|
|
{
|
|
k=sstate.m_yidx[i];
|
|
sstate.m_densez.Set(k,k,1.0);
|
|
for(j=k+1; j<n; j++)
|
|
sstate.m_densez.Set(k,j,0);
|
|
}
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//--- Constrained Newton matrix: sparse version
|
|
if(sstate.m_akind==1)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(sparsesolver==2,__FUNCTION__+": internal error"))
|
|
return(false);
|
|
//--- Copy sparse A to Z and fill rows/columns corresponding to active
|
|
//--- constraints by zeros. Diagonal elements corresponding to active
|
|
//--- constraints are filled by unit values.
|
|
CSparse::SparseCopyToSKSBuf(sstate.m_sparsea,sstate.m_sparsecca);
|
|
sstate.m_tmpcn=vector<double>::Zeros(n);
|
|
for(i=nfree; i<n; i++)
|
|
sstate.m_tmpcn.Set(sstate.m_yidx[i],1);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
k=sstate.m_sparsecca.m_RIdx[i];
|
|
for(j=i-sstate.m_sparsecca.m_DIdx[i]; j<=i; j++)
|
|
{
|
|
if(sstate.m_tmpcn[i]!=0.0 || sstate.m_tmpcn[j]!=0.0)
|
|
{
|
|
//--- I-th or J-th variable is in active set (constrained)
|
|
if(i==j)
|
|
sstate.m_sparsecca.m_Vals.Set(k,1.0);
|
|
else
|
|
sstate.m_sparsecca.m_Vals.Set(k,0.0);
|
|
}
|
|
k++;
|
|
}
|
|
}
|
|
//--- Perform sparse Cholesky
|
|
ncholesky++;
|
|
if(!CTrFac::SparseCholeskySkyLine(sstate.m_sparsecca,n,sstate.m_sparseupper))
|
|
return(false);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//--- Unexpected :)
|
|
CAp::Assert(false,__FUNCTION__+": internal error");
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function updates equality-constrained Cholesky matrix after |
|
|
//| activation of the new equality constraints. Matrix being updated |
|
|
//| is quadratic term of the function below |
|
|
//| f(x) = 0.5*x'*A*x+b'*x+penaltyfactor*0.5*(C*x-b)'*(C*x-b) |
|
|
//| where A can be dense or CSparse:: |
|
|
//| This function uses YIdx[] array(set by CNewtonBuild() function) |
|
|
//| to distinguish between active and inactive constraints. |
|
|
//| This function works as black box. It uses fields of SState which |
|
|
//| are marked as "Variables for constrained Newton phase", and only |
|
|
//| this function and its friends know about these variables. |
|
|
//| Everyone else should use: |
|
|
//| * CNewtonBuild() to prepare initial Cholesky decomposition for|
|
|
//| step |
|
|
//| * CNewtonStep() to perform constrained Newton step |
|
|
//| * CNewtonUpdate() to update Cholesky matrix after point was |
|
|
//| moved and constraints were updated. In some |
|
|
//| cases it is possible to efficiently real - |
|
|
//| calculate Cholesky decomposition if you know |
|
|
//| which constraints were activated. If |
|
|
//| efficient real - calculation is impossible, |
|
|
//| this function returns False. |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - structure which stores model and temporaries for|
|
|
//| CN phase; in particular, SAS.XC stores current |
|
|
//| point. |
|
|
//| Settings - QQPSettings object which was initialized by |
|
|
//| appropriate construction function. |
|
|
//| NCUpdates - counter which is incremented after each update |
|
|
//| (one update means one variable being fixed) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| NCUpdates - possibly updated counter |
|
|
//| RESULT: |
|
|
//| True, if Cholesky decomposition was successfully performed. |
|
|
//| False, if a) model age was too high, or b) particular |
|
|
//| combination of matrix type(sparse) and constraints|
|
|
//| (general linear) is not supported |
|
|
//| NOTE: this function may routinely return False. You should be |
|
|
//| able to handle such situations. |
|
|
//+------------------------------------------------------------------+
|
|
bool CQQPSolver::CNewtonUpdate(CQQPBuffers &sstate,CQQPSettings &Settings,
|
|
int &ncupdates)
|
|
{
|
|
//--- return result
|
|
int n=sstate.m_n;
|
|
int nfree=sstate.m_nfree;
|
|
int ntofix=0;
|
|
bool b;
|
|
int ridx0=0;
|
|
int ridx1=0;
|
|
int i=0;
|
|
int k=0;
|
|
//--- Cholesky updates for sparse problems are not supported
|
|
if(sstate.m_akind==1)
|
|
return(false);
|
|
//--- Determine variables to fix and move them to YIdx[NFree-NToFix:NFree-1]
|
|
//--- Exit if CNModelAge increased too much.
|
|
sstate.m_tmpcni.Resize(n);
|
|
ridx0=0;
|
|
ridx1=nfree-1;
|
|
for(i=0; i<nfree; i++)
|
|
sstate.m_tmpcni.Set(i,-1);
|
|
for(k=0; k<nfree; k++)
|
|
{
|
|
i=sstate.m_yidx[k];
|
|
//--- check
|
|
if(!CAp::Assert(!sstate.m_havebndl[i] || sstate.m_sas.m_xc[i]>=sstate.m_bndl[i],__FUNCTION__+": internal error"))
|
|
return(false);
|
|
if(!CAp::Assert(!sstate.m_havebndu[i] || sstate.m_sas.m_xc[i]<=sstate.m_bndu[i],__FUNCTION__+": internal error"))
|
|
return(false);
|
|
b=false;
|
|
b=b || (sstate.m_havebndl[i] && sstate.m_sas.m_xc[i]==sstate.m_bndl[i]);
|
|
b=b || (sstate.m_havebndu[i] && sstate.m_sas.m_xc[i]==sstate.m_bndu[i]);
|
|
if(b)
|
|
{
|
|
sstate.m_tmpcni.Set(ridx1,i);
|
|
ridx1--;
|
|
}
|
|
else
|
|
{
|
|
sstate.m_tmpcni.Set(ridx0,i);
|
|
ridx0++;
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(ridx0==ridx1+1,__FUNCTION__+": internal error"))
|
|
return(false);
|
|
ntofix=nfree-ridx0;
|
|
if(ntofix==0 || ntofix==nfree)
|
|
return(false);
|
|
if(sstate.m_cnmodelage+ntofix>Settings.m_cnmaxupdates)
|
|
return(false);
|
|
for(i=0; i<nfree; i++)
|
|
{
|
|
sstate.m_yidx.Set(i,sstate.m_tmpcni[i]);
|
|
}
|
|
//--- Constrained Newton matrix: dense version.
|
|
if(sstate.m_akind==0)
|
|
{
|
|
//--- Update Cholesky matrix with SPDMatrixCholeskyUpdateFixBuf()
|
|
CApServ::BVectorSetLengthAtLeast(sstate.m_tmpcnb,n);
|
|
ArrayInitialize(sstate.m_tmpcnb,false);
|
|
for(i=nfree-ntofix; i<nfree; i++)
|
|
sstate.m_tmpcnb[sstate.m_yidx[i]]=true;
|
|
CTrFac::SPDMatrixCholeskyUpdateFixBuf(sstate.m_densez,n,true,sstate.m_tmpcnb,sstate.m_tmpcn);
|
|
//--- Update information stored in State and exit
|
|
sstate.m_nfree=nfree-ntofix;
|
|
sstate.m_cnmodelage=sstate.m_cnmodelage+ntofix;
|
|
ncupdates=ncupdates+ntofix;
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//--- Unexpected :)
|
|
CAp::Assert(false,__FUNCTION__+": internal error");
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function prepares equality - constrained Newton step using |
|
|
//| previously calculated constrained Cholesky matrix of the problem |
|
|
//| f(x) = 0.5*x'*A*x+b'*x+penaltyfactor*0.5*(C*x-b)'*(C*x-b) |
|
|
//| where A can be dense or CSparse:: |
|
|
//| As input, this function accepts gradient at the current location.|
|
|
//| As output, it returns step vector (replaces gradient). |
|
|
//| This function works as black box. It uses fields of SState which |
|
|
//| are marked as "Variables for constrained Newton phase", and only |
|
|
//| this function and its friends know about these variables. |
|
|
//| Everyone else should use: |
|
|
//| * CNewtonBuild() to prepare initial Cholesky decomposition for|
|
|
//| step |
|
|
//| * CNewtonStep() to perform constrained Newton step |
|
|
//| * CNewtonUpdate() to update Cholesky matrix after point was |
|
|
//| moved and constraints were updated. In some |
|
|
//| cases it is possible to efficiently real - |
|
|
//| calculate Cholesky decomposition if you know |
|
|
//| which constraints were activated. If |
|
|
//| efficient real - calculation is impossible, |
|
|
//| this function returns False. |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - structure which stores model and temporaries for|
|
|
//| CN phase; in particular, SAS.XC stores current |
|
|
//| point. |
|
|
//| Settings - QQPSettings object which was initialized by |
|
|
//| appropriate construction function. |
|
|
//| GC - array[N], gradient of the target function |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| GC - array[N], step vector(on success) |
|
|
//| RESULT: |
|
|
//| True, if step was successfully calculated. |
|
|
//| False, if step calculation failed: |
|
|
//| a) gradient was exactly zero, |
|
|
//| b) gradient norm was smaller than EpsG (stopping |
|
|
//| condition) |
|
|
//| c) all variables were equality - constrained |
|
|
//| NOTE: this function may routinely return False. You should be |
|
|
//| able to handle such situations. |
|
|
//+------------------------------------------------------------------+
|
|
bool CQQPSolver::CNewtonStep(CQQPBuffers &sstate,CQQPSettings &Settings,
|
|
CRowDouble &gc)
|
|
{
|
|
//--- create variables
|
|
int n=sstate.m_n;
|
|
int nfree=sstate.m_nfree;
|
|
double v=0;
|
|
|
|
for(int i=nfree; i<n; i++)
|
|
gc.Set(sstate.m_yidx[i],0.0);
|
|
v=gc.Dot(gc);
|
|
if(MathSqrt(v)<=Settings.m_epsg)
|
|
return(false);
|
|
|
|
gc*=(-1.0);
|
|
if(sstate.m_akind==0)
|
|
{
|
|
//--- Dense Newton step.
|
|
//--- Use straightforward Cholesky m_solver.
|
|
CFbls::FblsCholeskySolve(sstate.m_densez,1.0,n,true,gc,sstate.m_tmpcn);
|
|
return(true);
|
|
}
|
|
if(sstate.m_akind==1)
|
|
{
|
|
//--- Sparse Newton step.
|
|
//--- We have T*T' = L*L' = U'*U (depending on specific triangle stored in SparseCCA).
|
|
if(sstate.m_sparseupper)
|
|
{
|
|
CSparse::SparseTRSV(sstate.m_sparsecca,sstate.m_sparseupper,false,1,gc);
|
|
CSparse::SparseTRSV(sstate.m_sparsecca,sstate.m_sparseupper,false,0,gc);
|
|
}
|
|
else
|
|
{
|
|
CSparse::SparseTRSV(sstate.m_sparsecca,sstate.m_sparseupper,false,0,gc);
|
|
CSparse::SparseTRSV(sstate.m_sparsecca,sstate.m_sparseupper,false,1,gc);
|
|
}
|
|
return(true);
|
|
}
|
|
//--- return result
|
|
CAp::Assert(false,__FUNCTION__+": internal error");
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores Settings for DENSE - AUL m_solver. |
|
|
//| It must be initialized with QPDENSEAULLoadDefaults(). |
|
|
//| After initialization you may change Settings. |
|
|
//+------------------------------------------------------------------+
|
|
struct CQPDenseAULSettings
|
|
{
|
|
int m_outerits;
|
|
double m_epsx;
|
|
double m_rho;
|
|
//--- constructor / destructor
|
|
CQPDenseAULSettings(void) { ZeroMemory(this); }
|
|
~CQPDenseAULSettings(void) {}
|
|
//---
|
|
void Copy(const CQPDenseAULSettings &obj);
|
|
//--- overloading
|
|
void operator=(const CQPDenseAULSettings &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CQPDenseAULSettings::Copy(const CQPDenseAULSettings &obj)
|
|
{
|
|
m_outerits=obj.m_outerits;
|
|
m_epsx=obj.m_epsx;
|
|
m_rho=obj.m_rho;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries used by Dense - AUL m_solver. |
|
|
//+------------------------------------------------------------------+
|
|
struct CQPDenseAULBuffers
|
|
{
|
|
int m_repinneriterationscount;
|
|
int m_repncholesky;
|
|
int m_repnmv;
|
|
int m_repnwrk0;
|
|
int m_repnwrk1;
|
|
int m_repnwrkchanges;
|
|
int m_repnwrkf;
|
|
int m_repouteriterationscount;
|
|
bool m_sclsfthasbndl[];
|
|
bool m_sclsfthasbndu[];
|
|
CSparseMatrix m_dummysparse;
|
|
CRowInt m_cidx;
|
|
CRowInt m_nicnact;
|
|
CRowDouble m_cscales;
|
|
CRowDouble m_d;
|
|
CRowDouble m_deltax;
|
|
CRowDouble m_exb;
|
|
CRowDouble m_exbndl;
|
|
CRowDouble m_exbndu;
|
|
CRowDouble m_exscale;
|
|
CRowDouble m_exxc;
|
|
CRowDouble m_exxorigin;
|
|
CRowDouble m_modelg;
|
|
CRowDouble m_nicerr;
|
|
CRowDouble m_nulc;
|
|
CRowDouble m_nulcest;
|
|
CRowDouble m_qrrightpart;
|
|
CRowDouble m_qrsv0;
|
|
CRowDouble m_qrsvx1;
|
|
CRowDouble m_qrtau;
|
|
CRowDouble m_sclsftb;
|
|
CRowDouble m_sclsftbndl;
|
|
CRowDouble m_sclsftbndu;
|
|
CRowDouble m_sclsftxc;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmpg;
|
|
CQQPSettings m_qqpsettingsuser;
|
|
CQQPBuffers m_qqpbuf;
|
|
CMatrixDouble m_exa;
|
|
CMatrixDouble m_qrkkt;
|
|
CMatrixDouble m_sclsfta;
|
|
CMatrixDouble m_sclsftcleic;
|
|
CMatrixDouble m_tmp2;
|
|
CConvexQuadraticModel m_dummycqm;
|
|
//--- constructor / destructor
|
|
CQPDenseAULBuffers(void);
|
|
~CQPDenseAULBuffers(void) {}
|
|
//---
|
|
void Copy(const CQPDenseAULBuffers &obj);
|
|
//--- overloading
|
|
void operator=(const CQPDenseAULBuffers &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CQPDenseAULBuffers::CQPDenseAULBuffers(void)
|
|
{
|
|
m_repinneriterationscount=0;
|
|
m_repncholesky=0;
|
|
m_repnmv=0;
|
|
m_repnwrk0=0;
|
|
m_repnwrk1=0;
|
|
m_repnwrkchanges=0;
|
|
m_repnwrkf=0;
|
|
m_repouteriterationscount=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CQPDenseAULBuffers::Copy(const CQPDenseAULBuffers &obj)
|
|
{
|
|
m_repinneriterationscount=obj.m_repinneriterationscount;
|
|
m_repncholesky=obj.m_repncholesky;
|
|
m_repnmv=obj.m_repnmv;
|
|
m_repnwrk0=obj.m_repnwrk0;
|
|
m_repnwrk1=obj.m_repnwrk1;
|
|
m_repnwrkchanges=obj.m_repnwrkchanges;
|
|
m_repnwrkf=obj.m_repnwrkf;
|
|
m_repouteriterationscount=obj.m_repouteriterationscount;
|
|
ArrayCopy(m_sclsfthasbndl,obj.m_sclsfthasbndl);
|
|
ArrayCopy(m_sclsfthasbndu,obj.m_sclsfthasbndu);
|
|
m_dummysparse=obj.m_dummysparse;
|
|
m_cidx=obj.m_cidx;
|
|
m_nicnact=obj.m_nicnact;
|
|
m_cscales=obj.m_cscales;
|
|
m_d=obj.m_d;
|
|
m_deltax=obj.m_deltax;
|
|
m_exb=obj.m_exb;
|
|
m_exbndl=obj.m_exbndl;
|
|
m_exbndu=obj.m_exbndu;
|
|
m_exscale=obj.m_exscale;
|
|
m_exxc=obj.m_exxc;
|
|
m_exxorigin=obj.m_exxorigin;
|
|
m_modelg=obj.m_modelg;
|
|
m_nicerr=obj.m_nicerr;
|
|
m_nulc=obj.m_nulc;
|
|
m_nulcest=obj.m_nulcest;
|
|
m_qrrightpart=obj.m_qrrightpart;
|
|
m_qrsv0=obj.m_qrsv0;
|
|
m_qrsvx1=obj.m_qrsvx1;
|
|
m_qrtau=obj.m_qrtau;
|
|
m_sclsftb=obj.m_sclsftb;
|
|
m_sclsftbndl=obj.m_sclsftbndl;
|
|
m_sclsftbndu=obj.m_sclsftbndu;
|
|
m_sclsftxc=obj.m_sclsftxc;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmpg=obj.m_tmpg;
|
|
m_qqpsettingsuser=obj.m_qqpsettingsuser;
|
|
m_qqpbuf=obj.m_qqpbuf;
|
|
m_exa=obj.m_exa;
|
|
m_qrkkt=obj.m_qrkkt;
|
|
m_sclsfta=obj.m_sclsfta;
|
|
m_sclsftcleic=obj.m_sclsftcleic;
|
|
m_tmp2=obj.m_tmp2;
|
|
m_dummycqm=obj.m_dummycqm;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CQPDenseAULSolver
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const double m_evictionlevel;
|
|
static const double m_expansionratio;
|
|
|
|
static void QPDenseAULLoadDefaults(int nmain,CQPDenseAULSettings &s);
|
|
static void QPDenseAULOptimize(CConvexQuadraticModel &a,CSparseMatrix &sparsea,int akind,bool sparseaupper,CRowDouble &b,CRowDouble &bndl,CRowDouble &bndu,CRowDouble &s,CRowDouble &xorigin,int nn,CMatrixDouble &cleic,int dnec,int dnic,CSparseMatrix &scleic,int snec,int snic,bool renormlc,CQPDenseAULSettings &Settings,CQPDenseAULBuffers &State,CRowDouble &xs,CRowDouble &lagbc,CRowDouble &laglc,int &terminationtype);
|
|
|
|
private:
|
|
static void GenerateExModel(CMatrixDouble &sclsfta,CRowDouble &sclsftb,int nmain,CRowDouble &sclsftbndl,bool &sclsfthasbndl[],CRowDouble &sclsftbndu,bool &sclsfthasbndu[],CMatrixDouble &sclsftcleic,int sclsftnec,int sclsftnic,CRowDouble &nulc,double rho,CMatrixDouble &exa,CRowDouble &exb,CRowDouble &exbndl,CRowDouble &exbndu,CMatrixDouble &tmp2);
|
|
static void GenerateExInitialPoint(CRowDouble &sclsftxc,int nmain,int nslack,CRowDouble &exxc);
|
|
static void UpdateLagrangeMultipliers(CMatrixDouble &sclsfta,CRowDouble &sclsftb,int nmain,CRowDouble &sclsftbndl,bool &sclsfthasbndl[],CRowDouble &sclsftbndu,bool &sclsfthasbndu[],CMatrixDouble &sclsftcleic,int sclsftnec,int sclsftnic,CRowDouble &exxc,CRowDouble &nulcest,CQPDenseAULBuffers &buffers);
|
|
static void ScaleShiftOriginalPproblem(CConvexQuadraticModel &a,CSparseMatrix &sparsea,int akind,bool sparseaupper,CRowDouble &b,CRowDouble &bndl,CRowDouble &bndu,CRowDouble &s,CRowDouble &xorigin,int nmain,CMatrixDouble &cleic,int dnec,int dnic,CSparseMatrix &scleic,int snec,int snic,bool renormlc,CQPDenseAULBuffers &State,CRowDouble &xs);
|
|
|
|
static double NormalizeQuadraticTerm(CMatrixDouble &a,CRowDouble &b,int n,CMatrixDouble &cleic,int nec,int nic,bool usecleic,CMatrixDouble &tmp2);
|
|
static void SelectInitialWorkingSet(CMatrixDouble &a,int nmain,CMatrixDouble &cleic,int nec,int nic,CRowDouble &m_tmp0,CMatrixDouble &tmp2,int &nicwork,bool &allowwseviction);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
const double CQPDenseAULSolver::m_evictionlevel=-0.01;
|
|
const double CQPDenseAULSolver::m_expansionratio=0.20;
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes QPDENSEAULSettings structure with |
|
|
//| default Settings. |
|
|
//| Newly created structure MUST be initialized by default Settings -|
|
|
//| or by copy of the already initialized structure. |
|
|
//+------------------------------------------------------------------+
|
|
void CQPDenseAULSolver::QPDenseAULLoadDefaults(int nmain,
|
|
CQPDenseAULSettings &s)
|
|
{
|
|
s.m_epsx=1.0E-6;
|
|
s.m_outerits=5;
|
|
s.m_rho=100.0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function runs Dense-AUL m_solver; it returns after |
|
|
//| optimization process was completed. Following QP problem is |
|
|
//| solved: |
|
|
//| min(0.5 * (x - x_origin)'*A*(x-x_origin)+b' * (x - x_origin)) |
|
|
//| subject to combination of box and general linear dense / sparse |
|
|
//| constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| DenseA - for dense problems(AKind = 0), A-term of CQM |
|
|
//| object contains system matrix. Other terms are |
|
|
//| unspecified and should not be referenced. |
|
|
//| SparseA - for sparse problems(AKind = 1), CRS format |
|
|
//| AKind - sparse matrix format: |
|
|
//| * 0 for dense matrix |
|
|
//| * 1 for sparse matrix |
|
|
//| SparseUpper - which triangle of SparseAC stores matrix - upper|
|
|
//| or lower one (for dense matrices this parameter |
|
|
//| is not actual). |
|
|
//| B - linear term, array[N] |
|
|
//| BndL - lower bound, array[N] |
|
|
//| BndU - upper bound, array[N] |
|
|
//| S - scale vector, array[NC]: |
|
|
//| * I-Th element contains scale of I-Th variable, |
|
|
//| * SC[I] > 0 |
|
|
//| XOrigin - origin term, array[NC]. Can be zero. |
|
|
//| N - number of variables in the original formulation|
|
|
//| (no slack variables). |
|
|
//| CLEIC - dense linear equality / inequality constraints. |
|
|
//| Equality constraints come first. |
|
|
//| NEC, NIC - number of dense equality/inequality constraints.|
|
|
//| SCLEIC - sparse linear equality / inequality constraints.|
|
|
//| Equality constraints come first. |
|
|
//| SNEC, SNIC - number of sparse equality/inequality constraints|
|
|
//| RenormLC - whether constraints should be renormalized |
|
|
//| (recommended) or used "as is". |
|
|
//| Settings - QPDENSEAULSettings object initialized by one of |
|
|
//| the initialization functions. |
|
|
//| State - object which stores temporaries |
|
|
//| XS - initial point, array[NC] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XS - last point |
|
|
//| TerminationType - termination type: |
|
|
//| * |
|
|
//| * |
|
|
//| * |
|
|
//+------------------------------------------------------------------+
|
|
void CQPDenseAULSolver::QPDenseAULOptimize(CConvexQuadraticModel &a,
|
|
CSparseMatrix &sparsea,
|
|
int akind,bool sparseaupper,
|
|
CRowDouble &b,
|
|
CRowDouble &bndl,
|
|
CRowDouble &bndu,
|
|
CRowDouble &s,
|
|
CRowDouble &xorigin,
|
|
int nn,CMatrixDouble &cleic,
|
|
int dnec,int dnic,
|
|
CSparseMatrix &scleic,
|
|
int snec,int snic,bool renormlc,
|
|
CQPDenseAULSettings &Settings,
|
|
CQPDenseAULBuffers &State,
|
|
CRowDouble &xs,CRowDouble &lagbc,
|
|
CRowDouble &laglc,
|
|
int &terminationtype)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double rho=Settings.m_rho;
|
|
double epsx=Settings.m_epsx;
|
|
int outeridx=0;
|
|
int nmain=nn;
|
|
int nslack=dnic+snic;
|
|
int ntotal=nmain+nslack;
|
|
int nectotal=dnec+snec;
|
|
int nictotal=dnic+snic;
|
|
int ktotal=dnec+dnic+snec+snic;
|
|
double maxrho=1.0E12;
|
|
double feaserr=0;
|
|
double feaserrprev=0;
|
|
double requestedfeasdecrease=0.33;
|
|
int goodcounter=0;
|
|
int stagnationcounter=0;
|
|
int nicwork=0;
|
|
int kwork=0;
|
|
int nwork=0;
|
|
bool allowwseviction;
|
|
bool workingsetextended;
|
|
double targetscale=0;
|
|
int i_=0;
|
|
|
|
terminationtype=0;
|
|
if(epsx<=0.0)
|
|
epsx=1.0E-9;
|
|
//--- Integrity checks
|
|
if(snec+snic>0)
|
|
{
|
|
if(!CAp::Assert(scleic.m_MatrixType==1,__FUNCTION__+": unexpected sparse matrix format"))
|
|
return;
|
|
if(!CAp::Assert(scleic.m_M==snec+snic,__FUNCTION__+": unexpected sparse matrix size"))
|
|
return;
|
|
if(!CAp::Assert(scleic.m_N==nmain+1,__FUNCTION__+": unexpected sparse matrix size"))
|
|
return;
|
|
}
|
|
//--- Prepare
|
|
State.m_repinneriterationscount=0;
|
|
State.m_repouteriterationscount=0;
|
|
State.m_repncholesky=0;
|
|
State.m_repnmv=0;
|
|
State.m_repnwrkchanges=0;
|
|
State.m_repnwrk0=0;
|
|
State.m_repnwrk1=0;
|
|
State.m_repnwrkf=0;
|
|
terminationtype=0;
|
|
State.m_cidx.Resize(ktotal);
|
|
State.m_nulc.Resize(ktotal);
|
|
State.m_nulcest.Resize(ktotal);
|
|
State.m_exb.Resize(ntotal);
|
|
State.m_exxc.Resize(ntotal);
|
|
State.m_exxorigin.Resize(ntotal);
|
|
State.m_exbndl.Resize(ntotal);
|
|
State.m_exbndu.Resize(ntotal);
|
|
State.m_exscale.Resize(ntotal);
|
|
State.m_tmp0.Resize(ntotal);
|
|
State.m_nicerr.Resize(nictotal);
|
|
State.m_nicnact.Resize(nictotal);
|
|
//--- Allocate Lagrange multipliers, fill by default values (zeros)
|
|
lagbc=vector<double>::Zeros(nmain);
|
|
laglc=vector<double>::Zeros(ktotal);
|
|
//--- Prepare scaled/shifted model in dense format - input parameters
|
|
//--- are converted and stored in State.SclSftA/B/HasBndL/HasBndU/BndL/BndU/CLEIC/XC/CScales
|
|
ScaleShiftOriginalPproblem(a,sparsea,akind,sparseaupper,b,bndl,bndu,s,xorigin,nmain,cleic,dnec,dnic,scleic,snec,snic,renormlc,State,xs);
|
|
//--- Normalize model in such way that norm(A)~1 (very roughly)
|
|
//--- We have two lower bounds for sigma_max(A):
|
|
//--- * first estimate is provided by Frobenius norm, it is equal to ANorm/NMain
|
|
//--- * second estimate is provided by max(CAC)
|
|
//--- We select largest one of these estimates, because using just one
|
|
//--- of them is prone to different failure modes. Then, we divide A and B
|
|
//--- by this estimate.
|
|
targetscale=NormalizeQuadraticTerm(State.m_sclsfta,State.m_sclsftb,nmain,State.m_sclsftcleic,nectotal,nictotal,renormlc,State.m_tmp2);
|
|
//--- Select working set of inequality constraints.
|
|
//--- Although it is possible to process all inequality constraints
|
|
//--- at once, in one large batch, some QP problems have NIC>>N constraints,
|
|
//--- but only minor fraction of them is inactive in the solution.
|
|
//--- Because algorithm running time is O((N+NEC+NIC)^3), we can
|
|
//--- save a lot of time if we process only those inequality constraints
|
|
//--- which need activation. Generally, NEC<N, and only O(N) inequality
|
|
//--- constraints are active in the solution.
|
|
//--- We can do so by solving problem first without general inequality
|
|
//--- constraints at all (box and general equality constraints are added),
|
|
//--- and by iteratively adding more and more inequality constraints in
|
|
//--- order to get feasible solution. Such set of inequality constraints
|
|
//--- is called "working set".
|
|
//--- NOTE: such approach works reliably only for convex QP problems; non-convex
|
|
//--- QP problem can be unbounded when some constraints are dropped.
|
|
//--- NOTE: we can also remove some constraints from working set, but eviction
|
|
//--- can be performed only limited amount of times (at most once); if
|
|
//--- constraint is added to working set second time, it is never removed.
|
|
//--- NOTE: we do not perform constraint eviction on non-convex problems
|
|
SelectInitialWorkingSet(State.m_sclsfta,nmain,State.m_sclsftcleic,nectotal,nictotal,State.m_tmp0,State.m_tmp2,nicwork,allowwseviction);
|
|
kwork=nectotal+nicwork;
|
|
nwork=nmain+nicwork;
|
|
State.m_repnwrk0=nicwork;
|
|
for(i=0; i<nicwork; i++)
|
|
State.m_nicnact.Set(i,1);
|
|
for(i=nicwork; i<=nictotal-1; i++)
|
|
State.m_nicnact.Set(i,0);
|
|
//--- Perform outer iteration
|
|
for(i=0; i<ktotal; i++)
|
|
{
|
|
State.m_cidx.Set(i,i);
|
|
State.m_nulc.Set(i,0);
|
|
}
|
|
for(i=0; i<ntotal; i++)
|
|
{
|
|
State.m_exscale.Set(i,1.0);
|
|
State.m_exxorigin.Set(i,0.0);
|
|
}
|
|
GenerateExInitialPoint(State.m_sclsftxc,nmain,nslack,State.m_exxc);
|
|
goodcounter=0;
|
|
stagnationcounter=0;
|
|
feaserr=CMath::m_maxrealnumber;
|
|
for(outeridx=0; outeridx<Settings.m_outerits; outeridx++)
|
|
{
|
|
//--- Repeat loop until working set stabilizes.
|
|
do
|
|
{
|
|
//--- Preallocate space for ExA and for QQP m_solver; we do not allocate
|
|
//--- array[NTotal,NTotal] from the start because NTotal can be much
|
|
//--- larger than NMain for problems with large amount of inequality
|
|
//--- constraints, and we usually need NWork=O(NMain).
|
|
//--- NOTE: for the sake of simplicity, 1-dimensional arrays were
|
|
//--- preallocated to the maximum size required (NTotal).
|
|
if(State.m_exa.Rows()<nwork || State.m_exa.Cols()<nwork)
|
|
{
|
|
i=nwork+nwork/3+1;
|
|
State.m_exa.Resize(i,i);
|
|
}
|
|
CQQPSolver::QQPPreAllocateGrowDense(State.m_qqpbuf,nwork,i);
|
|
//--- Generate penalized quadratic model
|
|
GenerateExModel(State.m_sclsfta,State.m_sclsftb,nmain,State.m_sclsftbndl,State.m_sclsfthasbndl,State.m_sclsftbndu,State.m_sclsfthasbndu,State.m_sclsftcleic,nectotal,nicwork,State.m_nulc,rho,State.m_exa,State.m_exb,State.m_exbndl,State.m_exbndu,State.m_tmp2);
|
|
//--- Solve extended QP problem subject to current working set of general
|
|
//--- inequality constraints.
|
|
CQQPSolver::QQPLoadDefaults(nwork,State.m_qqpsettingsuser);
|
|
State.m_qqpsettingsuser.m_maxouterits=50;
|
|
State.m_qqpsettingsuser.m_epsg=0.0;
|
|
State.m_qqpsettingsuser.m_epsf=0.0;
|
|
State.m_qqpsettingsuser.m_epsx=0.01*epsx;
|
|
State.m_qqpsettingsuser.m_cnphase=true;
|
|
CQQPSolver::QQPOptimize(State.m_dummycqm,State.m_dummysparse,State.m_exa,2,true,State.m_exb,State.m_exbndl,State.m_exbndu,State.m_exscale,State.m_exxorigin,nwork,State.m_qqpsettingsuser,State.m_qqpbuf,State.m_exxc,k);
|
|
State.m_repncholesky+=State.m_qqpbuf.m_repncholesky;
|
|
//--- Evaluate violation of constraints
|
|
for(i=0; i<nictotal; i++)
|
|
{
|
|
v=0.0;
|
|
for(i_=0; i_<nmain; i_++)
|
|
v+=State.m_sclsftcleic.Get(nectotal+i,i_)*State.m_exxc[i_];
|
|
v-=State.m_sclsftcleic.Get(nectotal+i,nmain);
|
|
State.m_nicerr.Set(i,v);
|
|
}
|
|
//--- Working set expansion:
|
|
//--- * select limited amount of most violated constraints
|
|
//--- * perform permutation of non-work constraints such that
|
|
//--- candidate constraint is first the list (update XC and NuLC)
|
|
//--- * increase working set size by 1
|
|
//--- * increase activation count for new constraint by 1 (this count
|
|
//--- is used later by working set eviction phase)
|
|
//--- * repeat
|
|
//--- NOTE: we use selection sort algorithm because its O(NAdded*NWork) cost
|
|
//--- is still comparable to the cost of constraints evaluation
|
|
workingsetextended=false;
|
|
i=0;
|
|
while((double)(i)<(1+m_expansionratio*nmain) && nicwork<nictotal)
|
|
{
|
|
//--- Select most violated constraint
|
|
k=nicwork;
|
|
for(j=nicwork; j<nictotal; j++)
|
|
{
|
|
if(State.m_nicerr[j]>State.m_nicerr[k])
|
|
k=j;
|
|
}
|
|
//--- If violation is positive, add it
|
|
if(State.m_nicerr[k]>0.0)
|
|
{
|
|
State.m_sclsftcleic.SwapRows(nectotal+nicwork,nectotal+k);
|
|
State.m_nicerr.Swap(nicwork,k);
|
|
State.m_nicnact.Swap(nicwork,k);
|
|
State.m_cidx.Swap(nectotal+nicwork,nectotal+k);
|
|
State.m_cscales.Swap(nectotal+nicwork,nectotal+k);
|
|
State.m_exxc.Set(nmain+nicwork,0.0);
|
|
State.m_nulc.Set(nectotal+nicwork,0.0);
|
|
State.m_nicnact.Add(nicwork,1);
|
|
nicwork++;
|
|
nwork++;
|
|
kwork++;
|
|
i++;
|
|
workingsetextended=true;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
//--- Working set eviction:
|
|
//--- * select constraints which are (1) far away from the
|
|
//--- boundary, AND (2) has less than two activation attempts
|
|
//--- (if constraint is regularly activated/deactivated, we keep
|
|
//--- it in the working set no matter what)
|
|
//--- * remove such constraints from the working set one by one
|
|
if(allowwseviction)
|
|
{
|
|
for(k=nicwork-1; k>=0; k--)
|
|
{
|
|
if(State.m_nicerr[k]<m_evictionlevel && State.m_nicnact[k]<=1)
|
|
{
|
|
State.m_sclsftcleic.SwapRows(nectotal+nicwork-1,nectotal+k);
|
|
State.m_cidx.Swap(nectotal+nicwork-1,nectotal+k);
|
|
State.m_cscales.Swap(nectotal+nicwork-1,nectotal+k);
|
|
State.m_nicerr.Swap(nicwork-1,k);
|
|
State.m_nicnact.Swap(nicwork-1,k);
|
|
State.m_exxc.Swap(nmain+nicwork-1,nmain+k);
|
|
State.m_nulc.Swap(nectotal+nicwork-1,nectotal+k);
|
|
nicwork--;
|
|
nwork--;
|
|
kwork--;
|
|
}
|
|
}
|
|
}
|
|
//--- Report working set statistics
|
|
if(State.m_repnwrk1==0)
|
|
State.m_repnwrk1=nicwork;
|
|
State.m_repnwrkf=nicwork;
|
|
if(workingsetextended)
|
|
State.m_repnwrkchanges++;
|
|
}
|
|
while(workingsetextended);
|
|
//--- Estimate Lagrange multipliers using alternative algorithm
|
|
for(i_=0; i_<kwork; i_++)
|
|
State.m_nulcest.Set(i_,State.m_nulc[i_]);
|
|
UpdateLagrangeMultipliers(State.m_sclsfta,State.m_sclsftb,nmain,State.m_sclsftbndl,State.m_sclsfthasbndl,State.m_sclsftbndu,State.m_sclsfthasbndu,State.m_sclsftcleic,nectotal,nicwork,State.m_exxc,State.m_nulcest,State);
|
|
//--- Update XC and Lagrange multipliers
|
|
feaserrprev=feaserr;
|
|
feaserr=0;
|
|
for(i=0; i<kwork; i++)
|
|
{
|
|
//--- Calculate I-th feasibility error in V using formula for distance
|
|
//--- between point and line (here we calculate actual distance between
|
|
//--- XN and hyperplane Ci'*XN=Bi, which is different from error Ci'*XN-Bi).
|
|
v=0;
|
|
vv=0;
|
|
for(j=0; j<nmain; j++)
|
|
{
|
|
v+= State.m_sclsftcleic.Get(i,j)*State.m_exxc[j];
|
|
vv+=CMath::Sqr(State.m_sclsftcleic.Get(i,j));
|
|
}
|
|
if(i>=nectotal)
|
|
{
|
|
v+= State.m_exxc[nmain+(i-nectotal)];
|
|
vv++;
|
|
}
|
|
v-=State.m_sclsftcleic.Get(i,nmain);
|
|
vv=CApServ::Coalesce(vv,1);
|
|
v=v/MathSqrt(vv);
|
|
//--- Calculate magnitude of Lagrangian update (and Lagrangian parameters themselves)
|
|
feaserr+=CMath::Sqr(v);
|
|
State.m_nulc.Set(i,State.m_nulcest[i]);
|
|
}
|
|
feaserr=MathSqrt(feaserr);
|
|
if(feaserr<epsx)
|
|
goodcounter++;
|
|
else
|
|
goodcounter=0;
|
|
if(feaserr>(feaserrprev*requestedfeasdecrease))
|
|
stagnationcounter++;
|
|
else
|
|
stagnationcounter=0;
|
|
if(goodcounter>=2)
|
|
break;
|
|
if(stagnationcounter>=2)
|
|
rho=MathMin(rho*10.0,maxrho);
|
|
else
|
|
rho=MathMin(rho*1.41,maxrho);
|
|
}
|
|
//--- Convert Lagrange multipliers from internal format to one expected
|
|
//--- by caller:
|
|
//--- * reorder multipliers for linear constraints
|
|
//--- * compute residual from gradient+linearconstraints
|
|
//--- * compute multipliers for box constraints from residual
|
|
//--- * rescale everything
|
|
for(i=0; i<nectotal+nicwork; i++)
|
|
if(State.m_cscales[i]!=0)
|
|
laglc.Set(State.m_cidx[i],-(State.m_nulc[i]*targetscale/State.m_cscales[i]));
|
|
else
|
|
laglc.Set(State.m_cidx[i],AL_NaN);
|
|
State.m_tmpg.Resize(nmain);
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
v=State.m_sclsftb[i];
|
|
for(j=0; j<nmain; j++)
|
|
v+= State.m_sclsfta.Get(i,j)*State.m_exxc[j];
|
|
State.m_tmpg.Set(i,v);
|
|
}
|
|
CAblas::RMatrixGemVect(nmain,nectotal+nicwork,-1.0,State.m_sclsftcleic,0,0,1,State.m_nulc,0,1.0,State.m_tmpg,0);
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
if((State.m_sclsfthasbndl[i] && State.m_exxc[i]==State.m_sclsftbndl[i]) || (State.m_sclsfthasbndu[i] && State.m_exxc[i]==State.m_sclsftbndu[i]))
|
|
lagbc.Set(i,-State.m_tmpg[i]);
|
|
}
|
|
for(i=0; i<nmain; i++)
|
|
lagbc.Mul(i,targetscale/s[i]);
|
|
//--- Unpack results.
|
|
//--- Add XOrigin to XC and make sure that boundary constraints are
|
|
//--- satisfied.
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
//--- Unscale/unshift
|
|
xs.Set(i,s[i]*State.m_exxc[i]+xorigin[i]);
|
|
//--- Make sure that point is feasible w.m_r.m_t. box constraints.
|
|
//--- Enforce box constraints which were active in the scaled/shifted solution.
|
|
if(State.m_sclsfthasbndl[i])
|
|
{
|
|
if(xs[i]<bndl[i])
|
|
xs.Set(i,bndl[i]);
|
|
if(State.m_exxc[i]==State.m_sclsftbndl[i])
|
|
xs.Set(i,bndl[i]);
|
|
}
|
|
if(State.m_sclsfthasbndu[i])
|
|
{
|
|
if(xs[i]>bndu[i])
|
|
xs.Set(i,bndu[i]);
|
|
if(State.m_exxc[i]==State.m_sclsftbndu[i])
|
|
xs.Set(i,bndu[i]);
|
|
}
|
|
}
|
|
terminationtype=2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates box - constrained QP problem, which is |
|
|
//| penalized and augmented formulation of original linearly |
|
|
//| constrained problem |
|
|
//+------------------------------------------------------------------+
|
|
void CQPDenseAULSolver::GenerateExModel(CMatrixDouble &sclsfta,
|
|
CRowDouble &sclsftb,
|
|
int nmain,
|
|
CRowDouble &sclsftbndl,
|
|
bool &sclsfthasbndl[],
|
|
CRowDouble &sclsftbndu,
|
|
bool &sclsfthasbndu[],
|
|
CMatrixDouble &sclsftcleic,
|
|
int sclsftnec,
|
|
int sclsftnic,
|
|
CRowDouble &nulc,
|
|
double rho,
|
|
CMatrixDouble &exa,
|
|
CRowDouble &exb,
|
|
CRowDouble &exbndl,
|
|
CRowDouble &exbndu,
|
|
CMatrixDouble &tmp2)
|
|
{
|
|
//--- create variables
|
|
int nslack=sclsftnic;
|
|
int ntotal=nmain+nslack;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
int i_=0;
|
|
//--- Integrity check for properly preallocated storage
|
|
if(!CAp::Assert(exa.Rows()>=ntotal && exa.Cols()>=ntotal,__FUNCTION__+" - integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(exb.Size()>=ntotal && exbndl.Size()>=ntotal && exbndu.Size()>=ntotal,__FUNCTION__+" - integrity check failed"))
|
|
return;
|
|
//--- Primary quadratic term
|
|
for(i=0; i<ntotal; i++)
|
|
for(j=i; j<ntotal; j++)
|
|
exa.Set(i,j,0);
|
|
for(i=0; i<nmain; i++)
|
|
for(j=i; j<nmain; j++)
|
|
exa.Set(i,j,sclsfta.Get(i,j));
|
|
//--- Primary linear term
|
|
for(i=0; i<ntotal; i++)
|
|
exb.Set(i,0);
|
|
for(i=0; i<nmain; i++)
|
|
exb.Set(i,sclsftb[i]);
|
|
//--- Box constraints - move primary, add slack
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
if(sclsfthasbndl[i])
|
|
exbndl.Set(i,sclsftbndl[i]);
|
|
else
|
|
exbndl.Set(i,AL_NEGINF);
|
|
if(sclsfthasbndu[i])
|
|
exbndu.Set(i,sclsftbndu[i]);
|
|
else
|
|
exbndu.Set(i,AL_POSINF);
|
|
}
|
|
for(i=nmain; i<ntotal; i++)
|
|
{
|
|
exbndl.Set(i,0);
|
|
exbndu.Set(i,AL_POSINF);
|
|
}
|
|
//--- Handle equality constraints:
|
|
//--- * modify quadratic term
|
|
//--- * modify linear term
|
|
//--- * add Lagrangian term
|
|
tmp2.Resize(sclsftnec+sclsftnic,ntotal);
|
|
for(i=0; i<sclsftnec+sclsftnic; i++)
|
|
{
|
|
//--- Given constraint row ci and right hand side ri,
|
|
//--- I-th quadratic constraint adds penalty term
|
|
//--- 0.5*Rho*(ci'*x-ri)^2 =
|
|
//--- = 0.5*Rho*(ci'*x-ri)^T*(ci'*x-ri) =
|
|
//--- = 0.5*Rho*(x'*ci-ri')*(ci'*x-ri) =
|
|
//--- = 0.5*Rho*(x'*ci*ci'*x - ri'*ci'*x - x'*ci*ri + ri'*ri )
|
|
//--- = 0.5*Rho*(x'*(ci*ci')*x - 2*ri*(ci'*x) + ri^2 )
|
|
//--- Thus, quadratic term is updated by
|
|
//--- 0.5*Rho*(ci*ci')
|
|
//--- (with actual update to ExA being performed without 0.5
|
|
//--- multiplier because entire matrix is post-multipliead by 0.5)
|
|
//--- and linear term receives update
|
|
//--- -Rho*ri*ci
|
|
//--- Similaryly, lagrangian term is -NUi*(ci'*x-ri),
|
|
//--- so linear term is updated by
|
|
//--- -NUi*ci
|
|
//--- Because our model does not take into account constant term,
|
|
//--- we calculate just quadratic and linear terms.
|
|
for(i_=0; i_<nmain; i_++)
|
|
tmp2.Set(i,i_,sclsftcleic.Get(i,i_));
|
|
for(j=nmain; j<ntotal; j++)
|
|
tmp2.Set(i,j,0);
|
|
if(i>=sclsftnec)
|
|
tmp2.Set(i,nmain+i-sclsftnec,1.0);
|
|
v=-(rho*sclsftcleic.Get(i,nmain));
|
|
for(i_=0; i_<ntotal; i_++)
|
|
exb.Add(i_,v*tmp2.Get(i,i_));
|
|
v=-nulc[i];
|
|
for(i_=0; i_<ntotal; i_++)
|
|
exb.Add(i_,v*tmp2.Get(i,i_));
|
|
}
|
|
CAblas::RMatrixSyrk(ntotal,sclsftnec+sclsftnic,rho,tmp2,0,0,2,1.0,exa,0,0,true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates initial point for "extended" box - |
|
|
//| constrained QP problem. |
|
|
//+------------------------------------------------------------------+
|
|
void CQPDenseAULSolver::GenerateExInitialPoint(CRowDouble &sclsftxc,
|
|
int nmain,
|
|
int nslack,
|
|
CRowDouble &exxc)
|
|
{
|
|
int ntotal=nmain+nslack;
|
|
|
|
exxc=sclsftxc;
|
|
exxc.Resize(ntotal);
|
|
for(int i=nmain; i<ntotal; i++)
|
|
exxc.Set(i,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function estimates Lagrange multipliers for scaled - shifted|
|
|
//| QP problem (here "scaled-shifted" means that we performed |
|
|
//| variable scaling and subtracted origin) given by quadratic |
|
|
//| term A, linear term B, box constraints and linear constraint |
|
|
//| matrix. |
|
|
//| It is assumed that all linear constraints are equality ones, |
|
|
//| with first NEC ones being constraints without slack variables, |
|
|
//| and next NIC ones having slack variables. The only inequality |
|
|
//| constraints we have are box ones, with first NMain ones being |
|
|
//| "general" box constraints, and next NIC ones being non-negativity|
|
|
//| constraints(not specified explicitly). |
|
|
//| We also make use of the current point XC, which is used to |
|
|
//| determine active box constraints. |
|
|
//| Actual QP problem size is NMain + NIC, but some parameters have |
|
|
//| lower dimensionality. |
|
|
//| Parameters sizes are: |
|
|
//| * A is assumed to be array[NMain, NMain] |
|
|
//| * B is assumed to be array[NMain] |
|
|
//| * BndL, BndU are array[NMain] |
|
|
//| * CLEIC is array[NEC + NIC, NMain + 1] (last item in a row |
|
|
//| containts right part) |
|
|
//| * ExXC is array[NMain + NIC], holds current point |
|
|
//| * NuLCEst is array[NEC + NIC], holds initial values of Lagrange|
|
|
//| coeffs |
|
|
//| On exit NuLCEst is updated with new estimate of Lagrange |
|
|
//| multipliers. |
|
|
//+------------------------------------------------------------------+
|
|
void CQPDenseAULSolver::UpdateLagrangeMultipliers(CMatrixDouble &sclsfta,
|
|
CRowDouble &sclsftb,
|
|
int nmain,
|
|
CRowDouble &sclsftbndl,
|
|
bool &sclsfthasbndl[],
|
|
CRowDouble &sclsftbndu,
|
|
bool &sclsfthasbndu[],
|
|
CMatrixDouble &sclsftcleic,
|
|
int sclsftnec,
|
|
int sclsftnic,
|
|
CRowDouble &exxc,
|
|
CRowDouble &nulcest,
|
|
CQPDenseAULBuffers &buffers)
|
|
{
|
|
//--- create variables
|
|
int nslack=sclsftnic;
|
|
int ntotal=nmain+nslack;
|
|
int ktotal=sclsftnec+sclsftnic;
|
|
int nqrrows=0;
|
|
int nqrcols=0;
|
|
int i=0;
|
|
int j=0;
|
|
double lambdareg=0;
|
|
double mxdiag=0;
|
|
double v=0;
|
|
bool isactive;
|
|
int i_=0;
|
|
//--- Given current point ExXC, we can determine active and inactive
|
|
//--- constraints. After we drop inactive inequality constraints, we
|
|
//--- have equality-only constrained QP problem, with mix of general
|
|
//--- linear equality constraints and "simple" constraints Xi=Ci.
|
|
//--- Problem min(0.5*x'*A*x + b'*x) s.m_t. C*x=d (general linear
|
|
//--- constraints) can be solved by explicitly writing out Lagrange
|
|
//--- equations:
|
|
//--- [ A C' ] [ X ] [ -b]
|
|
//--- [ ] [ ] = [ ]
|
|
//--- [ C ] [ L ] [ d ]
|
|
//--- or
|
|
//--- [ X ]
|
|
//--- A1* [ ] = b1
|
|
//--- [ L ]
|
|
//--- where X stands for solution itself, and L stands for Lagrange
|
|
//--- multipliers. It can be easily solved with direct linear m_solver.
|
|
//--- However, such formulation does not account for "simple" equality
|
|
//--- constraints on variables. It is possible to include "simple"
|
|
//--- constraints into "general" ones (i.e. append (0 ... 0 -1 0 ... 0)'
|
|
//--- to the constraint matrix), but it will increase problem
|
|
//--- size.
|
|
//--- Another approach is to use initial values of X and L (X0 and L0)
|
|
//--- as starting point, and to solve for "offset" from (X0, L0):
|
|
//--- [ X0+X1 ]
|
|
//--- A1*[ ] = b1
|
|
//--- [ L0+L1 ]
|
|
//--- or
|
|
//--- [ X1 ] [ X0 ]
|
|
//--- A1*[ ] = b1 - A1*[ ]
|
|
//--- [ L1 ] [ L0 ]
|
|
//--- In such formulation components of X1 which correspond to active
|
|
//--- constraints on variables are "frozen" at value 0 (because we have
|
|
//--- equality constraint, offset from constrained value have to be zero).
|
|
//--- Thus, we can rewrite corresponding columns of A1 with zeros - and
|
|
//--- use this space to store (0 ... 0 -1 0 ... 0)', which is used to
|
|
//--- account for Lagrange multipliers for "simple" constraints.
|
|
nqrcols=ntotal+ktotal;
|
|
nqrrows=nqrcols;
|
|
buffers.m_qrsv0=exxc;
|
|
buffers.m_qrsv0.Resize(nqrcols);
|
|
buffers.m_qrsvx1.Resize(nqrcols);
|
|
for(i=0; i<ktotal; i++)
|
|
buffers.m_qrsv0.Set(ntotal+i,nulcest[i]);
|
|
lambdareg=1.0E-8;
|
|
while(true)
|
|
{
|
|
//--- Initialize matrix A1 and right part b1 with zeros
|
|
buffers.m_qrkkt=matrix<double>::Zeros(nqrcols+nqrcols,nqrcols+1);
|
|
buffers.m_qrrightpart=vector<double>::Zeros(nqrcols+nqrcols);
|
|
//--- Append quadratic term (note: we implicitly add NSlack zeros to
|
|
//--- A and b).
|
|
mxdiag=0;
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
for(j=0; j<nmain; j++)
|
|
buffers.m_qrkkt.Set(i,j,sclsfta.Get(i,j));
|
|
buffers.m_qrrightpart.Set(i,-sclsftb[i]);
|
|
mxdiag=MathMax(mxdiag,MathAbs(sclsfta.Get(i,i)));
|
|
}
|
|
mxdiag=CApServ::Coalesce(mxdiag,1);
|
|
//--- Append general linear constraints
|
|
for(i=0; i<ktotal; i++)
|
|
{
|
|
for(j=0; j<nmain; j++)
|
|
{
|
|
buffers.m_qrkkt.Set(ntotal+i,j,-sclsftcleic.Get(i,j));
|
|
buffers.m_qrkkt.Set(j,ntotal+i,-sclsftcleic.Get(i,j));
|
|
}
|
|
if(i>=sclsftnec)
|
|
{
|
|
buffers.m_qrkkt.Set(ntotal+i,nmain+(i-sclsftnec),-1);
|
|
buffers.m_qrkkt.Set(nmain+(i-sclsftnec),ntotal+i,-1);
|
|
}
|
|
buffers.m_qrrightpart.Set(ntotal+i,-sclsftcleic.Get(i,nmain));
|
|
}
|
|
//--- Append regularizer to the bottom of the matrix
|
|
//--- (it will be factored in during QR decomposition)
|
|
if(lambdareg>0.0)
|
|
{
|
|
nqrrows=nqrcols+nqrcols;
|
|
for(i=0; i<nqrcols; i++)
|
|
buffers.m_qrkkt.Set(nqrcols+i,i,lambdareg*mxdiag);
|
|
}
|
|
//--- Subtract reference point (X0,L0) from the system
|
|
for(i=0; i<nqrcols; i++)
|
|
{
|
|
v=0.0;
|
|
for(i_=0; i_<nqrcols; i_++)
|
|
v+=buffers.m_qrkkt.Get(i,i_)*buffers.m_qrsv0[i_];
|
|
buffers.m_qrrightpart.Add(i,-v);
|
|
}
|
|
//--- Handle active "simple" equality constraints
|
|
for(i=0; i<ntotal; i++)
|
|
{
|
|
isactive=false;
|
|
if(i<nmain && ((sclsfthasbndl[i] && exxc[i]==sclsftbndl[i]) || (sclsfthasbndu[i] && exxc[i]==sclsftbndu[i])))
|
|
isactive=true;
|
|
if(i>=nmain && exxc[i]==0.0)
|
|
isactive=true;
|
|
if(!isactive)
|
|
continue;
|
|
for(j=0; j<nqrrows; j++)
|
|
buffers.m_qrkkt.Set(j,i,0);
|
|
buffers.m_qrkkt.Set(i,i,-1);
|
|
}
|
|
//--- Solve via QR decomposition:
|
|
//--- * append right part to the system matrix
|
|
//--- * perform QR decomposition of the extended matrix (right part is implicitly
|
|
//--- multiplied by Q during decomposition; believe me, it works!)
|
|
//--- * check condition number, increase regularization value if necessary and retry
|
|
//--- * solve triangular system, break iteration
|
|
for(i=0; i<nqrrows; i++)
|
|
buffers.m_qrkkt.Set(i,nqrcols,buffers.m_qrrightpart[i]);
|
|
COrtFac::RMatrixQR(buffers.m_qrkkt,nqrrows,nqrcols+1,buffers.m_qrtau);
|
|
if(CRCond::RMatrixTrRCond1(buffers.m_qrkkt,nqrcols,true,false)<=(1000.0*CMath::m_machineepsilon))
|
|
{
|
|
lambdareg=CApServ::Coalesce(10*lambdareg,1.0E-13);
|
|
continue;
|
|
}
|
|
for(i=nqrcols-1; i>=0; i--)
|
|
{
|
|
v=buffers.m_qrkkt.Get(i,nqrcols);
|
|
for(j=i+1; j<nqrcols; j++)
|
|
v-=buffers.m_qrkkt.Get(i,j)*buffers.m_qrsvx1[j];
|
|
buffers.m_qrsvx1.Set(i,v/buffers.m_qrkkt.Get(i,i));
|
|
}
|
|
break;
|
|
}
|
|
//--- Update Lagrange coefficients
|
|
for(i=0; i<ktotal; i++)
|
|
nulcest.Set(i,buffers.m_qrsv0[ntotal+i]+buffers.m_qrsvx1[ntotal+i]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates scaled (by S) and shifted (by XC) |
|
|
//| reformulation of the original problem. |
|
|
//| INPUT PARAMETERS: |
|
|
//| DenseA - for dense problems(AKind = 0), A - term of CQM |
|
|
//| object contains system matrix. Other terms are |
|
|
//| unspecified and should not be referenced. |
|
|
//| SparseA - for sparse problems(AKind = 1), CRS format |
|
|
//| AKind - sparse matrix format: |
|
|
//| * 0 for dense matrix |
|
|
//| * 1 for sparse matrix |
|
|
//| SparseUpper - which triangle of SparseAC stores matrix - upper|
|
|
//| or lower one (for dense matrices this parameter |
|
|
//| is not actual). |
|
|
//| B - linear term, array[N] |
|
|
//| BndL - lower bound, array[N] |
|
|
//| BndU - upper bound, array[N] |
|
|
//| S - scale vector, array[NC]: |
|
|
//| * I-Th element contains scale of I-Th variable, |
|
|
//| * SC[I] > 0 |
|
|
//| XOrigin - origin term, array[NC]. Can be zero. |
|
|
//| N - number of variables in the original formulation |
|
|
//| (no slack variables). |
|
|
//| CLEIC - dense linear equality/inequality constraints. |
|
|
//| Equality constraints come first. |
|
|
//| NEC, NIC - number of dense equality/inequality constraints.|
|
|
//| SCLEIC - sparse linear equality / inequality constraints.|
|
|
//| Equality constraints come first. |
|
|
//| SNEC, SNIC - number of sparse equality/inequality constraints|
|
|
//| RenormLC - whether constraints should be renormalized |
|
|
//| (recommended) or used "as is". |
|
|
//| Settings - QPDENSEAULSettings object initialized by one of |
|
|
//| the initialization functions. |
|
|
//| State - object which stores temporaries |
|
|
//| XS - initial point, array[NC] |
|
|
//| On output, following fields of the State structure are modified: |
|
|
//| * SclSftA - array[NMain, NMain], quadratic term, both |
|
|
//| triangles |
|
|
//| * SclSftB - array[NMain], linear term |
|
|
//| * SclSftXC - array[NMain], initial point |
|
|
//| * SclSftHasBndL, |
|
|
//| SclSftHasBndU, |
|
|
//| SclSftBndL, |
|
|
//| SclSftBndU - array[NMain], lower / upper bounds |
|
|
//| * SclSftCLEIC- array[KTotal, NMain + 1], general linear |
|
|
//| constraints |
|
|
//| NOTE: State.Tmp2 is used to store temporary array[NMain, NMain] |
|
|
//+------------------------------------------------------------------+
|
|
void CQPDenseAULSolver::ScaleShiftOriginalPproblem(CConvexQuadraticModel &a,
|
|
CSparseMatrix &sparsea,
|
|
int akind,
|
|
bool sparseaupper,
|
|
CRowDouble &b,
|
|
CRowDouble &bndl,
|
|
CRowDouble &bndu,
|
|
CRowDouble &s,
|
|
CRowDouble &xorigin,
|
|
int nmain,
|
|
CMatrixDouble &cleic,
|
|
int dnec,
|
|
int dnic,
|
|
CSparseMatrix &scleic,
|
|
int snec,
|
|
int snic,
|
|
bool renormlc,
|
|
CQPDenseAULBuffers &State,
|
|
CRowDouble &xs)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double v=0;
|
|
double vv=0;
|
|
int ktotal=0;
|
|
//--- check
|
|
if(!CAp::Assert(akind==0 || akind==1,__FUNCTION__+": unexpected AKind"))
|
|
return;
|
|
|
|
ktotal=dnec+dnic+snec+snic;
|
|
CApServ::BVectorSetLengthAtLeast(State.m_sclsfthasbndl,nmain);
|
|
CApServ::BVectorSetLengthAtLeast(State.m_sclsfthasbndu,nmain);
|
|
State.m_sclsfta=matrix<double>::Zeros(nmain,nmain);
|
|
State.m_sclsftb.Resize(nmain);
|
|
State.m_sclsftxc.Resize(nmain);
|
|
State.m_sclsftbndl.Resize(nmain);
|
|
State.m_sclsftbndu.Resize(nmain);
|
|
State.m_sclsftcleic.Resize(ktotal,nmain+1);
|
|
State.m_cscales.Resize(ktotal);
|
|
if(akind==0)
|
|
{
|
|
//--- Extract dense A and scale
|
|
CCQModels::CQMGetA(a,State.m_tmp2);
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
for(j=i; j<nmain; j++)
|
|
{
|
|
v=State.m_tmp2.Get(i,j)*s[i]*s[j];
|
|
State.m_sclsfta.Set(i,j,v);
|
|
State.m_sclsfta.Set(j,i,v);
|
|
}
|
|
}
|
|
}
|
|
if(akind==1)
|
|
{
|
|
//--- Extract sparse A and scale
|
|
//--- check
|
|
if(!CAp::Assert(sparsea.m_MatrixType==1,__FUNCTION__+": unexpected sparse matrix format"))
|
|
return;
|
|
if(!CAp::Assert(sparsea.m_M==nmain,__FUNCTION__+": unexpected sparse matrix size"))
|
|
return;
|
|
if(!CAp::Assert(sparsea.m_N==nmain,__FUNCTION__+": unexpected sparse matrix size"))
|
|
return;
|
|
if(sparseaupper)
|
|
{
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
if(sparsea.m_DIdx[i]!=sparsea.m_UIdx[i])
|
|
State.m_sclsfta.Set(i,i,sparsea.m_Vals[sparsea.m_DIdx[i]]*s[i]*s[i]);
|
|
j0=sparsea.m_UIdx[i];
|
|
j1=sparsea.m_RIdx[i+1]-1;
|
|
for(j=j0; j<=j1; j++)
|
|
{
|
|
k=sparsea.m_Idx[j];
|
|
v=sparsea.m_Vals[j]*s[i]*s[k];
|
|
State.m_sclsfta.Set(i,k,v);
|
|
State.m_sclsfta.Set(k,i,v);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
if(sparsea.m_DIdx[i]!=sparsea.m_UIdx[i])
|
|
State.m_sclsfta.Set(i,i,sparsea.m_Vals[sparsea.m_DIdx[i]]*s[i]*s[i]);
|
|
j0=sparsea.m_RIdx[i];
|
|
j1=sparsea.m_DIdx[i]-1;
|
|
for(j=j0; j<=j1; j++)
|
|
{
|
|
k=sparsea.m_Idx[j];
|
|
v=sparsea.m_Vals[j]*s[i]*s[k];
|
|
State.m_sclsfta.Set(i,k,v);
|
|
State.m_sclsfta.Set(k,i,v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
State.m_sclsfthasbndl[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_sclsfthasbndu[i]=MathIsValidNumber(bndu[i]);
|
|
State.m_sclsftb.Set(i,b[i]*s[i]);
|
|
State.m_sclsftxc.Set(i,(xs[i]-xorigin[i])/s[i]);
|
|
State.m_sclsftbndl.Set(i,bndl[i]);
|
|
State.m_sclsftbndu.Set(i,bndu[i]);
|
|
}
|
|
CLPQPServ::ScaleShiftBCInplace(s,xorigin,State.m_sclsftbndl,State.m_sclsftbndu,nmain);
|
|
for(i=0; i<=ktotal-1; i++)
|
|
for(j=0; j<=nmain; j++)
|
|
State.m_sclsftcleic.Set(i,j,0);
|
|
for(i=0; i<dnec; i++)
|
|
{
|
|
for(j=0; j<nmain; j++)
|
|
{
|
|
v=cleic.Get(i,j)*s[j];
|
|
State.m_sclsftcleic.Set(i,j,v);
|
|
}
|
|
State.m_sclsftcleic.Set(i,nmain,cleic.Get(i,nmain));
|
|
}
|
|
for(i=0; i<dnic; i++)
|
|
{
|
|
for(j=0; j<nmain; j++)
|
|
{
|
|
v=cleic.Get(dnec+i,j)*s[j];
|
|
State.m_sclsftcleic.Set(dnec+snec+i,j,v);
|
|
}
|
|
State.m_sclsftcleic.Set(dnec+snec+i,nmain,cleic.Get(dnec+i,nmain));
|
|
}
|
|
for(i=0; i<snec; i++)
|
|
{
|
|
//--- Because constraints are sparse, everything is a bit tricky -
|
|
//--- it is possible that N-th element of the row is zero and not
|
|
//--- stored; it is also possible that entire row is empty.
|
|
j0=scleic.m_RIdx[i];
|
|
j1=scleic.m_RIdx[i+1]-1;
|
|
if(j1>=j0 && scleic.m_Idx[j1]==nmain)
|
|
{
|
|
State.m_sclsftcleic.Set(dnec+i,nmain,scleic.m_Vals[j1]);
|
|
j1--;
|
|
}
|
|
for(j=j0; j<=j1; j++)
|
|
{
|
|
k=scleic.m_Idx[j];
|
|
v=scleic.m_Vals[j]*s[k];
|
|
State.m_sclsftcleic.Set(dnec+i,k,v);
|
|
}
|
|
}
|
|
for(i=0; i<snic; i++)
|
|
{
|
|
//--- Because constraints are sparse, everything is a bit tricky -
|
|
//--- it is possible that N-th element of the row is zero and not
|
|
//--- stored; it is also possible that entire row is empty.
|
|
j0=scleic.m_RIdx[snec+i];
|
|
j1=scleic.m_RIdx[snec+i+1]-1;
|
|
if(j1>=j0 && scleic.m_Idx[j1]==nmain)
|
|
{
|
|
State.m_sclsftcleic.Set(dnec+snec+dnic+i,nmain,scleic.m_Vals[j1]);
|
|
j1--;
|
|
}
|
|
for(j=j0; j<=j1; j++)
|
|
{
|
|
k=scleic.m_Idx[j];
|
|
v=scleic.m_Vals[j]*s[k];
|
|
State.m_sclsftcleic.Set(dnec+snec+dnic+i,k,v);
|
|
}
|
|
}
|
|
if(renormlc && ktotal>0)
|
|
{
|
|
//--- Normalize linear constraints in such way that they have unit norm
|
|
//--- (after variable scaling)
|
|
for(i=0; i<ktotal; i++)
|
|
{
|
|
vv=0.0;
|
|
for(j=0; j<nmain; j++)
|
|
{
|
|
v=State.m_sclsftcleic.Get(i,j);
|
|
vv+=v*v;
|
|
}
|
|
vv=MathSqrt(vv);
|
|
State.m_cscales.Set(i,vv);
|
|
if(vv>0.0)
|
|
{
|
|
vv=1/vv;
|
|
for(j=0; j<=nmain; j++)
|
|
State.m_sclsftcleic.Mul(i,j,vv);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Load unit scales
|
|
for(i=0; i<ktotal; i++)
|
|
State.m_cscales.Set(i,1.0);
|
|
}
|
|
for(i=0; i<ktotal; i++)
|
|
{
|
|
//--- Apply XOrigin
|
|
v=0.0;
|
|
for(j=0; j<nmain; j++)
|
|
v+= State.m_sclsftcleic.Get(i,j)*(xorigin[j]/s[j]);
|
|
State.m_sclsftcleic.Add(i,nmain,-v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Normalize model in such way that norm(A)~1(very roughly) |
|
|
//| We have two lower bounds for sigma_max(A): |
|
|
//| * first estimate is provided by Frobenius norm, it is equal |
|
|
//| to ANorm / NMain |
|
|
//| * second estimate is provided by max(CAC) |
|
|
//| We select largest one of these estimates, because using just one |
|
|
//| of them is prone to different failure modes. Then, we divide A |
|
|
//| and B by this estimate. |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - array[N, N], quadratic term, full triangle is given|
|
|
//| B - array[N], linear term |
|
|
//| N - problem size |
|
|
//| CLEIC - array[NEC + NIC, N + 1], linear equality/inequality|
|
|
//| constraints |
|
|
//| NEC - number of equality constraints |
|
|
//| NIC - number of inequality constraints |
|
|
//| UseCLEIC - additional normalization of A in such way that |
|
|
//| CLEIC * A * CLEIC'~1: |
|
|
//| * if False, CLEIC is ignored |
|
|
//| * if True, CLEIC rows MUST have unit norm (we |
|
|
//| check it) |
|
|
//| Tmp2 - additional buffer, possibly preallocated |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| A, B - appropriately rescaled by 1 / SCL |
|
|
//| RESULT: multiplier SCL |
|
|
//+------------------------------------------------------------------+
|
|
double CQPDenseAULSolver::NormalizeQuadraticTerm(CMatrixDouble &a,
|
|
CRowDouble &b,
|
|
int n,
|
|
CMatrixDouble &cleic,
|
|
int nec,
|
|
int nic,
|
|
bool usecleic,
|
|
CMatrixDouble &tmp2)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int i=0;
|
|
int j=0;
|
|
double anorm=0;
|
|
double maxcac=0;
|
|
double v=0;
|
|
double vv=0;
|
|
int nmain=n;
|
|
int ktotal=nec+nic;
|
|
|
|
for(i=0; i<nmain; i++)
|
|
for(j=0; j<nmain; j++)
|
|
anorm=anorm+CMath::Sqr(a.Get(i,j));
|
|
anorm=MathSqrt(anorm);
|
|
if(usecleic && ktotal>0)
|
|
{
|
|
//--- Calculate max(|diag(C*A*C')|), where C is constraint matrix
|
|
tmp2.Resize(ktotal,nmain);
|
|
CAblas::RMatrixGemm(ktotal,nmain,nmain,1.0,cleic,0,0,0,a,0,0,0,0.0,tmp2,0,0);
|
|
maxcac=0.0;
|
|
for(i=0; i<ktotal; i++)
|
|
{
|
|
v=0;
|
|
vv=0;
|
|
for(j=0; j<nmain; j++)
|
|
{
|
|
v+= tmp2.Get(i,j)*cleic.Get(i,j);
|
|
vv+=CMath::Sqr(cleic.Get(i,j));
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(MathAbs(vv-1)<1.0E-9 || vv==0.0,__FUNCTION__+": integrity check failed"))
|
|
return(result);
|
|
maxcac=MathMax(maxcac,MathAbs(v));
|
|
}
|
|
}
|
|
else
|
|
maxcac=0;
|
|
result=CApServ::Coalesce(MathMax(maxcac,anorm/nmain),1);
|
|
v=1/result;
|
|
for(i=0; i<nmain; i++)
|
|
for(j=0; j<nmain; j++)
|
|
a.Mul(i,j,v);
|
|
for(i=0; i<nmain; i++)
|
|
b.Mul(i,v);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function selects initial working set of general inequality |
|
|
//| constraints for QP problem: |
|
|
//| * for non - convex QP problems - NICWork = NIC is returned |
|
|
//| * otherwise - NICWork = 0 is returned (we |
|
|
//| have to determine working set |
|
|
//| iteratively) |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - array[NMain], quadratic term, full matrix is |
|
|
//| stored |
|
|
//| NMain - number of variables in the "original" QP problem|
|
|
//| CLEIC - array[NEC + NIC, NMain + 1], constraint matrix |
|
|
//| NEC - number of equality constraints |
|
|
//| NIC - number of inequality constraints |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| NICWork - recommended size of working set; in current |
|
|
//| version either all (NICWork = NIC) or none |
|
|
//| (NICWork = 0) constraints are included. |
|
|
//| AllowWSEviction - whether problem properties allow eviction |
|
|
//| of constraints from working set or not. |
|
|
//| Non-convex problems do not allow eviction, |
|
|
//| convex ones do. |
|
|
//+------------------------------------------------------------------+
|
|
void CQPDenseAULSolver::SelectInitialWorkingSet(CMatrixDouble &a,
|
|
int nmain,
|
|
CMatrixDouble &cleic,
|
|
int nec,
|
|
int nic,
|
|
CRowDouble &m_tmp0,
|
|
CMatrixDouble &tmp2,
|
|
int &nicwork,
|
|
bool &allowwseviction)
|
|
{
|
|
nicwork=0;
|
|
allowwseviction=false;
|
|
tmp2=a;
|
|
tmp2.Resize(nmain,nmain);
|
|
m_tmp0.Resize(nmain);
|
|
|
|
if(!CTrFac::SPDMatrixCholeskyRec(tmp2,0,nmain,true,m_tmp0))
|
|
{
|
|
//--- Matrix is indefinite.
|
|
//--- We have to select full working set, otherwise algorithm may fail
|
|
//--- because problem with reduced working set can be unbounded from below.
|
|
nicwork=nic;
|
|
allowwseviction=false;
|
|
}
|
|
else
|
|
{
|
|
//--- Positive definite matrix.
|
|
//--- We can select zero initial working set and expand it later.
|
|
nicwork=0;
|
|
allowwseviction=true;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores Settings for QPBLEIC m_solver. |
|
|
//| It must be initialized with QPBLEICLoadDefaults(). |
|
|
//| After initialization you may change Settings. |
|
|
//+------------------------------------------------------------------+
|
|
struct CQPBLEICSettings
|
|
{
|
|
int m_maxits;
|
|
double m_epsf;
|
|
double m_epsg;
|
|
double m_epsx;
|
|
//--- constructor / destructor
|
|
CQPBLEICSettings(void) { ZeroMemory(this); }
|
|
~CQPBLEICSettings(void) {}
|
|
//--- copy
|
|
void Copy(const CQPBLEICSettings &obj);
|
|
//--- overloading
|
|
void operator=(const CQPBLEICSettings &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CQPBLEICSettings::Copy(const CQPBLEICSettings &obj)
|
|
{
|
|
m_maxits=obj.m_maxits;
|
|
m_epsf=obj.m_epsf;
|
|
m_epsg=obj.m_epsg;
|
|
m_epsx=obj.m_epsx;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries used by QuickQP m_solver. |
|
|
//+------------------------------------------------------------------+
|
|
struct CQPBLEICbuffers
|
|
{
|
|
int m_repinneriterationscount;
|
|
int m_repouteriterationscount;
|
|
CRowInt m_tmpi;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CMinBLEICState m_solver;
|
|
CMinBLEICReport m_solverrep;
|
|
//--- constructor / destructor
|
|
CQPBLEICbuffers(void);
|
|
~CQPBLEICbuffers(void) {}
|
|
//--- copy
|
|
void Copy(const CQPBLEICbuffers &obj);
|
|
//--- overloading
|
|
void operator=(const CQPBLEICbuffers &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CQPBLEICbuffers::CQPBLEICbuffers(void)
|
|
{
|
|
m_repinneriterationscount=0;
|
|
m_repouteriterationscount=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CQPBLEICbuffers::Copy(const CQPBLEICbuffers &obj)
|
|
{
|
|
m_repinneriterationscount=obj.m_repinneriterationscount;
|
|
m_repouteriterationscount=obj.m_repouteriterationscount;
|
|
m_tmpi=obj.m_tmpi;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_solver=obj.m_solver;
|
|
m_solverrep=obj.m_solverrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CQPBLEICSolver
|
|
{
|
|
public:
|
|
static void QPBLEICLoadDefaults(int nmain,CQPBLEICSettings &s);
|
|
static void QPBLEICCopySettings(CQPBLEICSettings &src,CQPBLEICSettings &dst);
|
|
static void QPBLEICOptimize(CConvexQuadraticModel &a,CSparseMatrix &sparsea,int akind,bool sparseaupper,double absasum,double absasum2,CRowDouble &b,CRowDouble &bndl,CRowDouble &bndu,CRowDouble &s,CRowDouble &xorigin,int n,CMatrixDouble &cleic,int nec,int nic,CQPBLEICSettings &Settings,CQPBLEICbuffers &sstate,bool &firstcall,CRowDouble &xs,int &terminationtype);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes QPBLEICSettings structure with default |
|
|
//| Settings. |
|
|
//| Newly created structure MUST be initialized by default Settings -|
|
|
//| or by copy of the already initialized structure. |
|
|
//+------------------------------------------------------------------+
|
|
void CQPBLEICSolver::QPBLEICLoadDefaults(int nmain,
|
|
CQPBLEICSettings &s)
|
|
{
|
|
s.m_epsg=0.0;
|
|
s.m_epsf=0.0;
|
|
s.m_epsx=1.0E-6;
|
|
s.m_maxits=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes QPBLEICSettings structure with copy |
|
|
//| of another, already initialized structure. |
|
|
//+------------------------------------------------------------------+
|
|
void CQPBLEICSolver::QPBLEICCopySettings(CQPBLEICSettings &src,
|
|
CQPBLEICSettings &dst)
|
|
{
|
|
dst.m_epsg=src.m_epsg;
|
|
dst.m_epsf=src.m_epsf;
|
|
dst.m_epsx=src.m_epsx;
|
|
dst.m_maxits=src.m_maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function runs QPBLEIC m_solver; it returns after |
|
|
//| optimization process was completed. Following QP problem is |
|
|
//| solved: |
|
|
//| min(0.5 * (x - x_origin)'*A*(x-x_origin)+b' * (x - x_origin)) |
|
|
//| subject to boundary constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| AC - for dense problems(AKind = 0), A - term of CQM |
|
|
//| object contains system matrix. Other terms are |
|
|
//| unspecified and should not be referenced. |
|
|
//| SparseAC - for sparse problems(AKind = 1) |
|
|
//| AKind - sparse matrix format: |
|
|
//| * 0 for dense matrix |
|
|
//| * 1 for sparse matrix |
|
|
//| SparseUpper - which triangle of SparseAC stores matrix - upper |
|
|
//| or lower one (for dense matrices this parameter is |
|
|
//| not actual). |
|
|
//| AbsASum - SUM( | A.Get(i,j) |) |
|
|
//| AbsASum2 - SUM(A.Get(i,j) ^ 2) |
|
|
//| BC - linear term, array[NC] |
|
|
//| BndLC - lower bound, array[NC] |
|
|
//| BndUC - upper bound, array[NC] |
|
|
//| SC - scale vector, array[NC]: |
|
|
//| * I-th element contains scale of I-th variable, |
|
|
//| * SC[I] > 0 |
|
|
//| XOriginC - origin term, array[NC]. Can be zero. |
|
|
//| NC - number of variables in the original formulation |
|
|
//| (no slack variables). |
|
|
//| CLEICC - linear equality / inequality constraints. Present |
|
|
//| version of this function does NOT provide publicly|
|
|
//| available support for linear constraints. This |
|
|
//| feature will be introduced in the future versions |
|
|
//| of the function. |
|
|
//| NEC, NIC - number of equality/inequality constraints. MUST BE |
|
|
//| ZERO IN THE CURRENT VERSION!!! |
|
|
//| Settings - QPBLEICSettings object initialized by one of the |
|
|
//| initialization functions. |
|
|
//| SState - object which stores temporaries: |
|
|
//| * if uninitialized object was passed, FirstCall |
|
|
//| parameter MUST be set to True; object will be |
|
|
//| automatically initialized by the function, and |
|
|
//| FirstCall will be set to False. |
|
|
//| * if FirstCall = False, it is assumed that this |
|
|
//| parameter was already initialized by previous |
|
|
//| call to this function with same problem |
|
|
//| dimensions(variable count N). |
|
|
//| FirstCall- whether it is first call of this function for this |
|
|
//| specific instance of SState, with this number of |
|
|
//| variables N specified. |
|
|
//| XS - initial point, array[NC] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XS - last point |
|
|
//| FirstCall- uncondtionally set to False |
|
|
//| TerminationType - termination type: |
|
|
//| * |
|
|
//| * |
|
|
//| * |
|
|
//+------------------------------------------------------------------+
|
|
void CQPBLEICSolver::QPBLEICOptimize(CConvexQuadraticModel &a,
|
|
CSparseMatrix &sparsea,
|
|
int akind,bool sparseaupper,
|
|
double absasum,double absasum2,
|
|
CRowDouble &b,CRowDouble &bndl,
|
|
CRowDouble &bndu,CRowDouble &s,
|
|
CRowDouble &xorigin,int n,
|
|
CMatrixDouble &cleic,int nec,
|
|
int nic,CQPBLEICSettings &Settings,
|
|
CQPBLEICbuffers &sstate,
|
|
bool &firstcall,CRowDouble &xs,
|
|
int &terminationtype)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
double d2=0;
|
|
double d1=0;
|
|
double d0=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double md=0;
|
|
double mx=0;
|
|
double mb=0;
|
|
int d1est=0;
|
|
int d2est=0;
|
|
int i_=0;
|
|
terminationtype=0;
|
|
//--- check
|
|
if(!CAp::Assert(akind==0 || akind==1,__FUNCTION__+": unexpected AKind"))
|
|
return;
|
|
|
|
sstate.m_repinneriterationscount=0;
|
|
sstate.m_repouteriterationscount=0;
|
|
terminationtype=0;
|
|
//--- Prepare m_solver object, if needed
|
|
if(firstcall)
|
|
{
|
|
CMinBLEIC::MinBLEICCreate(n,xs,sstate.m_solver);
|
|
firstcall=false;
|
|
}
|
|
//--- Prepare max(|B|)
|
|
mb=0.0;
|
|
for(i=0; i<n; i++)
|
|
mb=MathMax(mb,MathAbs(b[i]));
|
|
//--- Temporaries
|
|
sstate.m_tmpi.Resize(nec+nic);
|
|
sstate.m_tmp0.Resize(n);
|
|
sstate.m_tmp1.Resize(n);
|
|
sstate.m_tmpi.Fill(0,0,nec);
|
|
sstate.m_tmpi.Fill(-1,nec,nic);
|
|
CMinBLEIC::MinBLEICSetLC(sstate.m_solver,cleic,sstate.m_tmpi,nec+nic);
|
|
CMinBLEIC::MinBLEICSetBC(sstate.m_solver,bndl,bndu);
|
|
CMinBLEIC::MinBLEICSetDRep(sstate.m_solver,true);
|
|
CMinBLEIC::MinBLEICSetCond(sstate.m_solver,CMath::m_minrealnumber,0.0,0.0,Settings.m_maxits);
|
|
CMinBLEIC::MinBLEICSetScale(sstate.m_solver,s);
|
|
CMinBLEIC::MinBLEICSetPrecScale(sstate.m_solver);
|
|
CMinBLEIC::MinBLEICRestartFrom(sstate.m_solver,xs);
|
|
while(CMinBLEIC::MinBLEICIteration(sstate.m_solver))
|
|
{
|
|
//--- Line search started
|
|
if(sstate.m_solver.m_lsstart)
|
|
{
|
|
//--- Iteration counters:
|
|
//--- * inner iterations count is increased on every line search
|
|
//--- * outer iterations count is increased only at steepest descent line search
|
|
sstate.m_repinneriterationscount++;
|
|
if(sstate.m_solver.m_steepestdescentstep)
|
|
sstate.m_repouteriterationscount++;
|
|
//--- Build quadratic model of F along descent direction:
|
|
//--- F(x+alpha*d) = D2*alpha^2 + D1*alpha + D0
|
|
//--- Calculate estimates of linear and quadratic term
|
|
//--- (term magnitude is compared with magnitude of numerical errors)
|
|
d0=sstate.m_solver.m_f;
|
|
d1=sstate.m_solver.m_d.Dot(sstate.m_solver.m_g);
|
|
d2=0;
|
|
switch(akind)
|
|
{
|
|
case 0:
|
|
d2=CCQModels::CQMXTADX2(a,sstate.m_solver.m_d,sstate.m_tmp0);
|
|
break;
|
|
case 1:
|
|
CSparse::SparseSMV(sparsea,sparseaupper,sstate.m_solver.m_d,sstate.m_tmp0);
|
|
d2=sstate.m_solver.m_d.Dot(sstate.m_tmp0);
|
|
d2/=2;
|
|
break;
|
|
}
|
|
mx=0.0;
|
|
md=0.0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
mx=MathMax(mx,MathAbs(sstate.m_solver.m_x[i]));
|
|
md=MathMax(md,MathAbs(sstate.m_solver.m_d[i]));
|
|
}
|
|
COptServ::EstimateParabolicModel(absasum,absasum2,mx,mb,md,d1,d2,d1est,d2est);
|
|
//--- Tests for "normal" convergence.
|
|
//--- This line search may be started from steepest descent
|
|
//--- stage (stage 2) or from L-BFGS stage (stage 3) of the
|
|
//--- BLEIC algorithm. Depending on stage type, different
|
|
//--- checks are performed.
|
|
//--- Say, L-BFGS stage is an equality-constrained refinement
|
|
//--- stage of BLEIC. This stage refines current iterate
|
|
//--- under "frozen" equality constraints. We can terminate
|
|
//--- iterations at this stage only when we encounter
|
|
//--- unconstrained direction of negative curvature. In all
|
|
//--- other cases (say, when constrained gradient is zero)
|
|
//--- we should not terminate algorithm because everything may
|
|
//--- change after de-activating presently active constraints.
|
|
//--- Tests for convergence are performed only at "steepest descent" stage
|
|
//--- of the BLEIC algorithm, and only when function is non-concave
|
|
//--- (D2 is positive or approximately zero) along direction D.
|
|
//--- NOTE: we do not test iteration count (MaxIts) here, because
|
|
//--- this stopping condition is tested by BLEIC itself.
|
|
if(sstate.m_solver.m_steepestdescentstep && d2est>=0)
|
|
{
|
|
if(d1est>=0)
|
|
{
|
|
//--- "Emergency" stopping condition: D is non-descent direction.
|
|
//--- Sometimes it is possible because of numerical noise in the
|
|
//--- target function.
|
|
terminationtype=4;
|
|
xs=sstate.m_solver.m_x;
|
|
break;
|
|
}
|
|
if(d2est>0)
|
|
{
|
|
//--- Stopping condition #4 - gradient norm is small:
|
|
//--- 1. rescale State.Solver.D and State.Solver.G according to
|
|
//--- current scaling, store results to Tmp0 and Tmp1.
|
|
//--- 2. Normalize Tmp0 (scaled direction vector).
|
|
//--- 3. compute directional derivative (in scaled variables),
|
|
//--- which is equal to DOTPRODUCT(Tmp0,Tmp1).
|
|
sstate.m_tmp0=sstate.m_solver.m_d/s+0;
|
|
sstate.m_tmp1=sstate.m_solver.m_g*s+0;
|
|
v=sstate.m_tmp0.Dot(sstate.m_tmp0);
|
|
//--- check
|
|
if(!CAp::Assert(v>0.0,__FUNCTION__+": inernal errror (scaled direction is zero)"))
|
|
return;
|
|
v=1/MathSqrt(v);
|
|
sstate.m_tmp0*=v;
|
|
v=sstate.m_tmp0.Dot(sstate.m_tmp1);
|
|
if(MathAbs(v)<=Settings.m_epsg)
|
|
{
|
|
terminationtype=4;
|
|
xs=sstate.m_solver.m_x;
|
|
break;
|
|
}
|
|
//--- Stopping condition #1 - relative function improvement is small:
|
|
//--- 1. calculate steepest descent step: V = -D1/(2*D2)
|
|
//--- 2. calculate function change: V1= D2*V^2 + D1*V
|
|
//--- 3. stop if function change is small enough
|
|
v=-(d1/(2*d2));
|
|
v1=d2*v*v+d1*v;
|
|
if(MathAbs(v1)<=Settings.m_epsf*MathMax(d0,1.0))
|
|
{
|
|
terminationtype=1;
|
|
xs=sstate.m_solver.m_x;
|
|
break;
|
|
}
|
|
//--- Stopping condition #2 - scaled step is small:
|
|
//--- 1. calculate step multiplier V0 (step itself is D*V0)
|
|
//--- 2. calculate scaled step length V
|
|
//--- 3. stop if step is small enough
|
|
v0=-(d1/(2*d2));
|
|
v=0;
|
|
for(i=0; i<n; i++)
|
|
v+=CMath::Sqr(v0*sstate.m_solver.m_d[i]/s[i]);
|
|
if(MathSqrt(v)<=Settings.m_epsx)
|
|
{
|
|
terminationtype=2;
|
|
xs=sstate.m_solver.m_x;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//--- Test for unconstrained direction of negative curvature
|
|
if((d2est<0 || (d2est==0 && d1est<0)) && !sstate.m_solver.m_boundedstep)
|
|
{
|
|
//--- Function is unbounded from below:
|
|
//--- * function will decrease along D, i.e. either:
|
|
//--- * D2<0
|
|
//--- * D2=0 and D1<0
|
|
//--- * step is unconstrained
|
|
//--- If these conditions are true, we abnormally terminate QP
|
|
//--- algorithm with return code -4 (we can do so at any stage
|
|
//--- of BLEIC - whether it is L-BFGS or steepest descent one).
|
|
terminationtype=-4;
|
|
xs=sstate.m_solver.m_x;
|
|
break;
|
|
}
|
|
//--- Suggest new step (only if D1 is negative far away from zero,
|
|
//--- D2 is positive far away from zero).
|
|
if(d1est<0 && d2est>0)
|
|
sstate.m_solver.m_stp=CApServ::SafeMinPosRV(-d1,2*d2,sstate.m_solver.m_curstpmax);
|
|
}
|
|
//--- Gradient evaluation
|
|
if(sstate.m_solver.m_needfg)
|
|
{
|
|
sstate.m_tmp0=sstate.m_solver.m_x-xorigin+0;
|
|
switch(akind)
|
|
{
|
|
case 0:
|
|
CCQModels::CQMADX(a,sstate.m_tmp0,sstate.m_tmp1);
|
|
break;
|
|
case 1:
|
|
CSparse::SparseSMV(sparsea,sparseaupper,sstate.m_tmp0,sstate.m_tmp1);
|
|
break;
|
|
}
|
|
v0=sstate.m_tmp0.Dot(sstate.m_tmp1);
|
|
v1=sstate.m_tmp0.Dot(b);
|
|
sstate.m_solver.m_f=0.5*v0+v1;
|
|
sstate.m_solver.m_g=sstate.m_tmp1+b+0;
|
|
}
|
|
}
|
|
if(terminationtype==0)
|
|
{
|
|
//--- BLEIC optimizer was terminated by one of its inner stopping
|
|
//--- conditions. Usually it is iteration counter (if such
|
|
//--- stopping condition was specified by user).
|
|
CMinBLEIC::MinBLEICResultsBuf(sstate.m_solver,xs,sstate.m_solverrep);
|
|
terminationtype=sstate.m_solverrep.m_terminationtype;
|
|
}
|
|
else
|
|
{
|
|
//--- BLEIC optimizer was terminated in "emergency" mode by QP
|
|
//--- m_solver.
|
|
//--- NOTE: such termination is "emergency" only when viewed from
|
|
//--- BLEIC's position. QP m_solver sees such termination as
|
|
//--- routine one, triggered by QP's stopping criteria.
|
|
CMinBLEIC::MinBLEICEmergencyTermination(sstate.m_solver);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores nonlinear optimizer State. |
|
|
//| You should use functions provided by MinQP subpackage to work |
|
|
//| with this object |
|
|
//+------------------------------------------------------------------+
|
|
class CMinQPState
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_akind;
|
|
int m_algokind;
|
|
int m_mdense;
|
|
int m_msparse;
|
|
int m_n;
|
|
int m_repinneriterationscount;
|
|
int m_repncholesky;
|
|
int m_repnmv;
|
|
int m_repouteriterationscount;
|
|
int m_repterminationtype;
|
|
int m_stype;
|
|
double m_absamax;
|
|
double m_absasum2;
|
|
double m_absasum;
|
|
double m_veps;
|
|
bool m_dbgskipconstraintnormalization;
|
|
bool m_havex;
|
|
bool m_qpbleicfirstcall;
|
|
bool m_sparseaupper;
|
|
//--- objects
|
|
CVIPMState m_vsolver;
|
|
CQQPSettings m_qqpsettingsuser;
|
|
CQQPBuffers m_qqpbuf;
|
|
CQPDenseAULSettings m_qpdenseaulsettingsuser;
|
|
CQPDenseAULBuffers m_qpdenseaulbuf;
|
|
CQPBLEICbuffers m_qpbleicbuf;
|
|
CQPBLEICSettings m_qpbleicsettingsuser;
|
|
CConvexQuadraticModel m_a;
|
|
//--- arrays
|
|
bool m_havebndl[];
|
|
bool m_havebndu[];
|
|
CRowInt m_elagidx;
|
|
CRowDouble m_b;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_cl;
|
|
CRowDouble m_cu;
|
|
CRowDouble m_effectives;
|
|
CRowDouble m_elaglc;
|
|
CRowDouble m_elagmlt;
|
|
CRowDouble m_replagbc;
|
|
CRowDouble m_replaglc;
|
|
CRowDouble m_s;
|
|
CRowDouble m_startx;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_wrkbndl;
|
|
CRowDouble m_wrkbndu;
|
|
CRowDouble m_wrkcl;
|
|
CRowDouble m_wrkcu;
|
|
CRowDouble m_xorigin;
|
|
CRowDouble m_xs;
|
|
//--- matrix
|
|
CSparseMatrix m_dummysparse;
|
|
CSparseMatrix m_sparsea;
|
|
CSparseMatrix m_sparsec;
|
|
CSparseMatrix m_wrksparsec;
|
|
CMatrixDouble m_densec;
|
|
CMatrixDouble m_dummyr2;
|
|
CMatrixDouble m_ecleic;
|
|
CMatrixDouble m_tmpr2;
|
|
CMatrixDouble m_wrkdensec;
|
|
//--- constructor, destructor
|
|
CMinQPState(void);
|
|
~CMinQPState(void) {}
|
|
//--- copy
|
|
void Copy(const CMinQPState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinQPState &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinQPState::CMinQPState(void)
|
|
{
|
|
m_akind=0;
|
|
m_algokind=0;
|
|
m_mdense=0;
|
|
m_msparse=0;
|
|
m_n=0;
|
|
m_repinneriterationscount=0;
|
|
m_repncholesky=0;
|
|
m_repnmv=0;
|
|
m_repouteriterationscount=0;
|
|
m_repterminationtype=0;
|
|
m_stype=0;
|
|
m_absamax=0;
|
|
m_absasum2=0;
|
|
m_absasum=0;
|
|
m_veps=0;
|
|
m_dbgskipconstraintnormalization=false;
|
|
m_havex=false;
|
|
m_qpbleicfirstcall=false;
|
|
m_sparseaupper=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQPState::Copy(const CMinQPState &obj)
|
|
{
|
|
m_akind=obj.m_akind;
|
|
m_algokind=obj.m_algokind;
|
|
m_mdense=obj.m_mdense;
|
|
m_msparse=obj.m_msparse;
|
|
m_n=obj.m_n;
|
|
m_repinneriterationscount=obj.m_repinneriterationscount;
|
|
m_repncholesky=obj.m_repncholesky;
|
|
m_repnmv=obj.m_repnmv;
|
|
m_repouteriterationscount=obj.m_repouteriterationscount;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_stype=obj.m_stype;
|
|
m_absamax=obj.m_absamax;
|
|
m_absasum2=obj.m_absasum2;
|
|
m_absasum=obj.m_absasum;
|
|
m_veps=obj.m_veps;
|
|
m_dbgskipconstraintnormalization=obj.m_dbgskipconstraintnormalization;
|
|
ArrayCopy(m_havebndl,obj.m_havebndl);
|
|
ArrayCopy(m_havebndu,obj.m_havebndu);
|
|
m_havex=obj.m_havex;
|
|
m_qpbleicfirstcall=obj.m_qpbleicfirstcall;
|
|
m_sparseaupper=obj.m_sparseaupper;
|
|
m_vsolver=obj.m_vsolver;
|
|
m_dummysparse=obj.m_dummysparse;
|
|
m_sparsea=obj.m_sparsea;
|
|
m_sparsec=obj.m_sparsec;
|
|
m_wrksparsec=obj.m_wrksparsec;
|
|
m_elagidx=obj.m_elagidx;
|
|
m_b=obj.m_b;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_cl=obj.m_cl;
|
|
m_cu=obj.m_cu;
|
|
m_effectives=obj.m_effectives;
|
|
m_elaglc=obj.m_elaglc;
|
|
m_elagmlt=obj.m_elagmlt;
|
|
m_replagbc=obj.m_replagbc;
|
|
m_replaglc=obj.m_replaglc;
|
|
m_s=obj.m_s;
|
|
m_startx=obj.m_startx;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_wrkbndl=obj.m_wrkbndl;
|
|
m_wrkbndu=obj.m_wrkbndu;
|
|
m_wrkcl=obj.m_wrkcl;
|
|
m_wrkcu=obj.m_wrkcu;
|
|
m_xorigin=obj.m_xorigin;
|
|
m_xs=obj.m_xs;
|
|
m_qqpsettingsuser=obj.m_qqpsettingsuser;
|
|
m_qqpbuf=obj.m_qqpbuf;
|
|
m_qpdenseaulsettingsuser=obj.m_qpdenseaulsettingsuser;
|
|
m_qpdenseaulbuf=obj.m_qpdenseaulbuf;
|
|
m_qpbleicbuf=obj.m_qpbleicbuf;
|
|
m_qpbleicsettingsuser=obj.m_qpbleicsettingsuser;
|
|
m_densec=obj.m_densec;
|
|
m_dummyr2=obj.m_dummyr2;
|
|
m_ecleic=obj.m_ecleic;
|
|
m_tmpr2=obj.m_tmpr2;
|
|
m_wrkdensec=obj.m_wrkdensec;
|
|
m_a=obj.m_a;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores nonlinear optimizer State. |
|
|
//| You should use functions provided by MinQP subpackage to work |
|
|
//| with this object |
|
|
//+------------------------------------------------------------------+
|
|
class CMinQPStateShell
|
|
{
|
|
private:
|
|
CMinQPState m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinQPStateShell(void) {}
|
|
CMinQPStateShell(CMinQPState &obj) { m_innerobj.Copy(obj); }
|
|
~CMinQPStateShell(void) {}
|
|
//--- method
|
|
CMinQPState *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This structure stores optimization report: |
|
|
//| * InnerIterationsCount number of inner iterations |
|
|
//| * OuterIterationsCount number of outer iterations |
|
|
//| * NCholesky number of Cholesky decomposition |
|
|
//| * NMV number of matrix-vector products |
|
|
//| (only products calculated as part of |
|
|
//| iterative process are counted) |
|
|
//| * TerminationType completion code (see below) |
|
|
//| Completion codes: |
|
|
//| * -5 inappropriate m_solver was used: |
|
|
//| * Cholesky m_solver for semidefinite or indefinite problems|
|
|
//| * Cholesky m_solver for problems with non-boundary |
|
|
//| constraints |
|
|
//| * -3 inconsistent constraints (or, maybe, feasible point is |
|
|
//| too hard to find). If you are sure that constraints are |
|
|
//| feasible, try to restart optimizer with better initial |
|
|
//| approximation. |
|
|
//| * 4 successful completion |
|
|
//| * 5 MaxIts steps was taken |
|
|
//| * 7 stopping conditions are too stringent, |
|
|
//| further improvement is impossible, |
|
|
//| X contains best point found so far. |
|
|
//+------------------------------------------------------------------+
|
|
class CMinQPReport
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_inneriterationscount;
|
|
int m_outeriterationscount;
|
|
int m_nmv;
|
|
int m_ncholesky;
|
|
int m_terminationtype;
|
|
//--- arrays
|
|
CRowDouble m_lagbc;
|
|
CRowDouble m_laglc;
|
|
//--- constructor, destructor
|
|
CMinQPReport(void);
|
|
~CMinQPReport(void) {}
|
|
//--- copy
|
|
void Copy(const CMinQPReport &obj);
|
|
//--- overloading
|
|
void operator=(const CMinQPReport &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinQPReport::CMinQPReport(void)
|
|
{
|
|
m_inneriterationscount=0;
|
|
m_outeriterationscount=0;
|
|
m_nmv=0;
|
|
m_ncholesky=0;
|
|
m_terminationtype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQPReport::Copy(const CMinQPReport &obj)
|
|
{
|
|
//--- copy variables
|
|
m_inneriterationscount=obj.m_inneriterationscount;
|
|
m_outeriterationscount=obj.m_outeriterationscount;
|
|
m_nmv=obj.m_nmv;
|
|
m_ncholesky=obj.m_ncholesky;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
//--- copy arrays
|
|
m_lagbc=obj.m_lagbc;
|
|
m_laglc=obj.m_laglc;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure stores optimization report: |
|
|
//| * InnerIterationsCount number of inner iterations |
|
|
//| * OuterIterationsCount number of outer iterations |
|
|
//| * NCholesky number of Cholesky decomposition |
|
|
//| * NMV number of matrix-vector products |
|
|
//| (only products calculated as part of |
|
|
//| iterative process are counted) |
|
|
//| * TerminationType completion code (see below) |
|
|
//| Completion codes: |
|
|
//| * -5 inappropriate m_solver was used: |
|
|
//| * Cholesky m_solver for semidefinite or indefinite problems|
|
|
//| * Cholesky m_solver for problems with non-boundary |
|
|
//| constraints |
|
|
//| * -3 inconsistent constraints (or, maybe, feasible point is |
|
|
//| too hard to find). If you are sure that constraints are |
|
|
//| feasible, try to restart optimizer with better initial |
|
|
//| approximation. |
|
|
//| * 4 successful completion |
|
|
//| * 5 MaxIts steps was taken |
|
|
//| * 7 stopping conditions are too stringent, |
|
|
//| further improvement is impossible, |
|
|
//| X contains best point found so far. |
|
|
//+------------------------------------------------------------------+
|
|
class CMinQPReportShell
|
|
{
|
|
private:
|
|
CMinQPReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinQPReportShell(void) {}
|
|
CMinQPReportShell(CMinQPReport &obj) { m_innerobj.Copy(obj); }
|
|
~CMinQPReportShell(void) {}
|
|
//--- methods
|
|
int GetInnerIterationsCount(void);
|
|
void SetInnerIterationsCount(const int i);
|
|
int GetOuterIterationsCount(void);
|
|
void SetOuterIterationsCount(const int i);
|
|
int GetNMV(void);
|
|
void SetNMV(const int i);
|
|
int GetNCholesky(void);
|
|
void SetNCholesky(const int i);
|
|
int GetTerminationType(void);
|
|
void SetTerminationType(const int i);
|
|
CMinQPReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable inneriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CMinQPReportShell::GetInnerIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_inneriterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable inneriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQPReportShell::SetInnerIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_inneriterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable outeriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CMinQPReportShell::GetOuterIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_outeriterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable outeriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQPReportShell::SetOuterIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_outeriterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable nmv |
|
|
//+------------------------------------------------------------------+
|
|
int CMinQPReportShell::GetNMV(void)
|
|
{
|
|
return(m_innerobj.m_nmv);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable nmv |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQPReportShell::SetNMV(const int i)
|
|
{
|
|
m_innerobj.m_nmv=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable ncholesky |
|
|
//+------------------------------------------------------------------+
|
|
int CMinQPReportShell::GetNCholesky(void)
|
|
{
|
|
return(m_innerobj.m_ncholesky);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable ncholesky |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQPReportShell::SetNCholesky(const int i)
|
|
{
|
|
m_innerobj.m_ncholesky=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
int CMinQPReportShell::GetTerminationType(void)
|
|
{
|
|
return(m_innerobj.m_terminationtype);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQPReportShell::SetTerminationType(const int i)
|
|
{
|
|
m_innerobj.m_terminationtype=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinQPReport* CMinQPReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Constrained quadratic programming |
|
|
//+------------------------------------------------------------------+
|
|
class CMinQP
|
|
{
|
|
public:
|
|
static void MinQPCreate(const int n,CMinQPState &State);
|
|
static void MinQPSetLinearTerm(CMinQPState &State,double &b[]);
|
|
static void MinQPSetLinearTerm(CMinQPState &State,CRowDouble &b);
|
|
static void MinQPSetQuadraticTerm(CMinQPState &State,CMatrixDouble &a,const bool IsUpper);
|
|
static void MinQPSetQuadraticTermSparse(CMinQPState &State,CSparseMatrix &a,bool IsUpper);
|
|
static void MinQPSetStartingPoint(CMinQPState &State,double &x[]);
|
|
static void MinQPSetStartingPoint(CMinQPState &State,CRowDouble &x);
|
|
static void MinQPSetOrigin(CMinQPState &State,double &xorigin[]);
|
|
static void MinQPSetOrigin(CMinQPState &State,CRowDouble &xorigin);
|
|
static void MinQPSetScale(CMinQPState &State,CRowDouble &s);
|
|
static void MinQPSetScaleAutoDiag(CMinQPState &State);
|
|
static void MinQPSetAlgoBLEIC(CMinQPState &State,double epsg,double epsf,double epsx,int m_maxits);
|
|
static void MinQPSetAlgoDenseAUL(CMinQPState &State,double epsx,double rho,int itscnt);
|
|
static void MinQPSetAlgoDenseIPM(CMinQPState &State,double eps);
|
|
static void MinQPSetAlgoSparseIPM(CMinQPState &State,double eps);
|
|
static void MinQPSetAlgoQuickQP(CMinQPState &State,double epsg,double epsf,double epsx,int maxouterits,bool usenewton);
|
|
static void MinQPSetAlgoCholesky(CMinQPState &State);
|
|
static void MinQPSetBC(CMinQPState &State,double &bndl[],double &bndu[]);
|
|
static void MinQPSetBC(CMinQPState &State,CRowDouble &bndl,CRowDouble &bndu);
|
|
static void MinQPSetBCAll(CMinQPState &State,double bndl,double bndu);
|
|
static void MinQPSetBCI(CMinQPState &State,int i,double bndl,double bndu);
|
|
static void MinQPSetLC(CMinQPState &State,CMatrixDouble &c,CRowInt &ct,int k);
|
|
static void MinQPSetLCSparse(CMinQPState &State,CSparseMatrix &c,CRowInt &ct,int k);
|
|
static void MinQPSetLCMixed(CMinQPState &State,CSparseMatrix &sparsec,CRowInt &sparsect,int sparsek,CMatrixDouble &densec,CRowInt &densect,int densek);
|
|
static void MinQPSetLCMixedLegacy(CMinQPState &State,CMatrixDouble &densec,CRowInt &densect,int densek,CSparseMatrix &sparsec,CRowInt &sparsect,int sparsek);
|
|
static void MinQPSetLC2Dense(CMinQPState &State,CMatrixDouble &a,CRowDouble &al,CRowDouble &au,int k);
|
|
static void MinQPSetLC2(CMinQPState &State,CSparseMatrix &a,CRowDouble &al,CRowDouble &au,int k);
|
|
static void MinQPSetLC2Mixed(CMinQPState &State,CSparseMatrix &sparsea,int ksparse,CMatrixDouble &densea,int kdense,CRowDouble &al,CRowDouble &au);
|
|
static void MinQPAddLC2Dense(CMinQPState &State,CRowDouble &a,double al,double au);
|
|
static void MinQPAddLC2(CMinQPState &State,CRowInt &idxa,CRowDouble &vala,int nnz,double al,double au);
|
|
static void MinQPAddLC2SparseFromDense(CMinQPState &State,CRowDouble &da,double al,double au);
|
|
static void MinQPOptimize(CMinQPState &State);
|
|
static void MinQPResults(CMinQPState &State,double &x[],CMinQPReport &rep);
|
|
static void MinQPResults(CMinQPState &State,CRowDouble &x,CMinQPReport &rep);
|
|
static void MinQPResultsBuf(CMinQPState &State,double &x[],CMinQPReport &rep);
|
|
static void MinQPResultsBuf(CMinQPState &State,CRowDouble &x,CMinQPReport &rep);
|
|
static void MinQPSetLinearTermFast(CMinQPState &State,double &b[]);
|
|
static void MinQPSetLinearTermFast(CMinQPState &State,CRowDouble &b);
|
|
static void MinQPSetQuadraticTermFast(CMinQPState &State,CMatrixDouble &a,const bool IsUpper,const double s);
|
|
static void MinQPRewriteDiagonal(CMinQPState &State,double &s[]);
|
|
static void MinQPRewriteDiagonal(CMinQPState &State,CRowDouble &s);
|
|
static void MinQPSetStartingPointFast(CMinQPState &State,double &x[]);
|
|
static void MinQPSetStartingPointFast(CMinQPState &State,CRowDouble &x);
|
|
static void MinQPSetOriginFast(CMinQPState &State,double &xorigin[]);
|
|
static void MinQPSetOriginFast(CMinQPState &State,CRowDouble &xorigin);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| CONSTRAINED QUADRATIC PROGRAMMING |
|
|
//| The subroutine creates QP optimizer. After initial creation, it |
|
|
//| contains default optimization problem with zero quadratic and |
|
|
//| linear terms and no constraints. You should set quadratic/linear |
|
|
//| terms with calls to functions provided by MinQP subpackage. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem size |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - optimizer with zero quadratic/linear terms |
|
|
//| and no constraints |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPCreate(const int n,CMinQPState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
//--- initialize QP m_solver
|
|
State.m_n=n;
|
|
State.m_mdense=0;
|
|
State.m_msparse=0;
|
|
State.m_repterminationtype=0;
|
|
State.m_absamax=1;
|
|
State.m_absasum=1;
|
|
State.m_absasum2=1;
|
|
State.m_akind=0;
|
|
State.m_sparseaupper=false;
|
|
CCQModels::CQMInit(n,State.m_a);
|
|
//--- allocation
|
|
ArrayResize(State.m_havebndl,n);
|
|
ArrayResize(State.m_havebndu,n);
|
|
State.m_b=vector<double>::Zeros(n);
|
|
State.m_bndl=vector<double>::Full(n,AL_NEGINF);
|
|
State.m_bndu=vector<double>::Full(n,AL_POSINF);
|
|
State.m_s=vector<double>::Ones(n);
|
|
State.m_startx=vector<double>::Zeros(n);
|
|
State.m_xorigin=vector<double>::Zeros(n);
|
|
State.m_xs.Resize(n);
|
|
State.m_replagbc=vector<double>::Zeros(n);
|
|
//--- initialization
|
|
ArrayInitialize(State.m_havebndl,false);
|
|
ArrayInitialize(State.m_havebndu,false);
|
|
State.m_stype=0;
|
|
State.m_havex=false;
|
|
//--- function call
|
|
MinQPSetAlgoBLEIC(State,0.0,0.0,0.0,0);
|
|
CQQPSolver::QQPLoadDefaults(n,State.m_qqpsettingsuser);
|
|
CQPBLEICSolver::QPBLEICLoadDefaults(n,State.m_qpbleicsettingsuser);
|
|
CQPDenseAULSolver::QPDenseAULLoadDefaults(n,State.m_qpdenseaulsettingsuser);
|
|
State.m_qpbleicfirstcall=true;
|
|
State.m_dbgskipconstraintnormalization=false;
|
|
State.m_veps=0.0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets linear term for QP m_solver. |
|
|
//| By default, linear term is zero. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| B - linear term, array[N]. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLinearTerm(CMinQPState &State,double &b[])
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(b)>=n,__FUNCTION__+": Length(B)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(b,n),__FUNCTION__+": B contains infinite or NaN elements"))
|
|
return;
|
|
//--- function call
|
|
MinQPSetLinearTermFast(State,b);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLinearTerm(CMinQPState &State,CRowDouble &b)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(b)>=n,__FUNCTION__+": Length(B)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(b,n),__FUNCTION__+": B contains infinite or NaN elements"))
|
|
return;
|
|
//--- function call
|
|
MinQPSetLinearTermFast(State,b);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets quadratic term for QP m_solver. |
|
|
//| By default quadratic term is zero. |
|
|
//| IMPORTANT: this m_solver minimizes following function: |
|
|
//| f(x) = 0.5*x'*A*x + b'*x. |
|
|
//| Note that quadratic term has 0.5 before it. So if you want to |
|
|
//| minimize |
|
|
//| f(x) = x^2 + x |
|
|
//| you should rewrite your problem as follows: |
|
|
//| f(x) = 0.5*(2*x^2) + x |
|
|
//| and your matrix A will be equal to [[2.0]], not to [[1.0]] |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| A - matrix, array[N,N] |
|
|
//| IsUpper - (optional) storage type: |
|
|
//| * if True, symmetric matrix A is given by its |
|
|
//| upper triangle, and the lower triangle isn?t |
|
|
//| used |
|
|
//| * if False, symmetric matrix A is given by its |
|
|
//| lower triangle, and the upper triangle isn?t |
|
|
//| used |
|
|
//| * if not given, both lower and upper triangles |
|
|
//| must be filled. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetQuadraticTerm(CMinQPState &State,CMatrixDouble &a,
|
|
const bool IsUpper)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(a)>=n,__FUNCTION__+": Rows(A)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(a)>=n,__FUNCTION__+": Cols(A)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteRTrMatrix(a,n,IsUpper),__FUNCTION__+": A contains infinite or NaN elements"))
|
|
return;
|
|
//--- function call
|
|
MinQPSetQuadraticTermFast(State,a,IsUpper,0.0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets sparse quadratic term for QP solver. By |
|
|
//| default, quadratic term is zero. This function overrides previous|
|
|
//| calls to MinQPSetQuadraticTerm() or MinQPSetQuadraticTermSparse()|
|
|
//| NOTE: dense solvers like DENSE-AUL-QP or DENSE-IPM-QP will |
|
|
//| convert this matrix to dense storage anyway. |
|
|
//| IMPORTANT: This solver minimizes following function: |
|
|
//| f(x) = 0.5*x'*A*x + b'*x. |
|
|
//| Note that quadratic term has 0.5 before it. So if you |
|
|
//| want to minimize |
|
|
//| f(x) = x^2 + x |
|
|
//| you should rewrite your problem as follows: |
|
|
//| f(x) = 0.5*(2*x^2) + x |
|
|
//| and your matrix A will be equal to [[2.0]], not to |
|
|
//| [[1.0]] |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| A - matrix, array[N,N] |
|
|
//| IsUpper - (optional) storage type: |
|
|
//| * if True, symmetric matrix A is given by its upper|
|
|
//| triangle, and the lower triangle isn't used |
|
|
//| * if False, symmetric matrix A is given by its |
|
|
//| lower triangle, and the upper triangle isn't used|
|
|
//| * if not given, both lower and upper triangles must|
|
|
//| be filled. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetQuadraticTermSparse(CMinQPState &State,
|
|
CSparseMatrix &a,
|
|
bool IsUpper)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int t0=0;
|
|
int t1=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(a)==n,__FUNCTION__+": Rows(A)<>N"))
|
|
return;
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(a)==n,__FUNCTION__+": Cols(A)<>N"))
|
|
return;
|
|
//--- function call
|
|
CSparse::SparseCopyToCRSBuf(a,State.m_sparsea);
|
|
State.m_sparseaupper=IsUpper;
|
|
State.m_akind=1;
|
|
//-- Estimate norm of A
|
|
//-- (it will be used later in the quadratic penalty function)
|
|
State.m_absamax=0;
|
|
State.m_absasum=0;
|
|
State.m_absasum2=0;
|
|
t0=0;
|
|
t1=0;
|
|
while(CSparse::SparseEnumerate(a,t0,t1,i,j,v))
|
|
{
|
|
if(i==j)
|
|
{
|
|
//-- Diagonal terms are counted only once
|
|
State.m_absamax=MathMax(State.m_absamax,v);
|
|
State.m_absasum=State.m_absasum+v;
|
|
State.m_absasum2=State.m_absasum2+v*v;
|
|
}
|
|
if((j>i && IsUpper) || (j<i && !IsUpper))
|
|
{
|
|
//-- Offdiagonal terms are counted twice
|
|
State.m_absamax=MathMax(State.m_absamax,v);
|
|
State.m_absasum=State.m_absasum+2*v;
|
|
State.m_absasum2=State.m_absasum2+2*v*v;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets starting point for QP m_solver. It is useful to |
|
|
//| have good initial approximation to the solution, because it will |
|
|
//| increase speed of convergence and identification of active |
|
|
//| constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| X - starting point, array[N]. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetStartingPoint(CMinQPState &State,double &x[])
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(B)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN elements"))
|
|
return;
|
|
//--- function call
|
|
MinQPSetStartingPointFast(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetStartingPoint(CMinQPState &State,CRowDouble &x)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(B)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN elements"))
|
|
return;
|
|
//--- function call
|
|
MinQPSetStartingPointFast(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets origin for QP m_solver. By default, following |
|
|
//| QP program is solved: |
|
|
//| min(0.5*x'*A*x+b'*x) |
|
|
//| This function allows to solve different problem: |
|
|
//| min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin)) |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| XOrigin - origin, array[N]. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetOrigin(CMinQPState &State,double &xorigin[])
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(xorigin)>=n,__FUNCTION__+": Length(B)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(xorigin,n),__FUNCTION__+": B contains infinite or NaN elements"))
|
|
return;
|
|
//--- function call
|
|
MinQPSetOriginFast(State,xorigin);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetOrigin(CMinQPState &State,CRowDouble &xorigin)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(xorigin)>=n,__FUNCTION__+": Length(B)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(xorigin,n),__FUNCTION__+": B contains infinite or NaN elements"))
|
|
return;
|
|
//--- function call
|
|
MinQPSetOriginFast(State,xorigin);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients. |
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions (step size and gradient are scaled before comparison |
|
|
//| with tolerances) and as preconditioner. |
|
|
//| Scale of the I-th variable is a translation invariant measure of:|
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| If you do not know how to choose scales of your variables, you |
|
|
//| can: |
|
|
//| * read www.alglib.net/optimization/scaling.php article |
|
|
//| * use minqpsetscaleautodiag(), which calculates scale using |
|
|
//| diagonal ofthe quadratic term: S is set to 1/sqrt(diag(A)), |
|
|
//| which works well sometimes. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients S[i] may |
|
|
//| be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetScale(CMinQPState &State,
|
|
CRowDouble &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.Size()>=State.m_n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
}
|
|
//--- copy
|
|
State.m_s=s.Abs()+0;
|
|
State.m_stype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets automatic evaluation of variable scaling. |
|
|
//| IMPORTANT: this function works only for matrices with positive |
|
|
//| diagonal elements! Zero or negative elements will |
|
|
//| result in -9 error code being returned. Specify scale |
|
|
//| vector manually with MinQPSetScale() in such cases. |
|
|
//| ALGLIB optimizers use scaling matrices to test |
|
|
//| stopping conditions (step size and gradient are scaled|
|
|
//| before comparison with tolerances) and as |
|
|
//| preconditioner. |
|
|
//| The best way to set scaling is to manually specify variable |
|
|
//| scales. However, sometimes you just need quick-and-dirty |
|
|
//| solution - either when you perform fast prototyping, or when you |
|
|
//| know your problem well and you are 100 % sure that this quick |
|
|
//| solution is robust enough in your case. |
|
|
//| One such solution is to evaluate scale of I-th variable |
|
|
//| as 1 / Sqrt(A[i, i]), where A[i, i] is an I-th diagonal |
|
|
//| element of the quadratic term. |
|
|
//| Such approach works well sometimes, but you have to be careful |
|
|
//| here. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetScaleAutoDiag(CMinQPState &State)
|
|
{
|
|
State.m_stype=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells solver to use BLEIC-based algorithm and sets |
|
|
//| stopping criteria for the algorithm. |
|
|
//| This algorithm is intended for large-scale problems, possibly |
|
|
//| nonconvex, with small number of general linear constraints. |
|
|
//| Feasible initial point is essential for good performance. |
|
|
//| IMPORTANT: when DENSE-IPM (or DENSE-AUL for nonconvex problems) |
|
|
//| solvers are applicable, their performance is much |
|
|
//| better than that of BLEIC-QP. |
|
|
//| We recommend you to use BLEIC only when other solvers |
|
|
//| can not be used. |
|
|
//| ALGORITHM FEATURES: |
|
|
//| * supports dense and sparse QP problems |
|
|
//| * supports box and general linear equality / inequality |
|
|
//| constraints |
|
|
//| * can solve all types of problems (convex, semidefinite, |
|
|
//| nonconvex) as long as they are bounded from below under |
|
|
//| constraints. |
|
|
//| Say, it is possible to solve "min{-x^2} subject to -1<=x<=+1". |
|
|
//| Of course, global minimum is found only for positive definite and|
|
|
//| semidefinite problems. As for indefinite ones-only local minimum |
|
|
//| is found. |
|
|
//| ALGORITHM OUTLINE : |
|
|
//| * BLEIC-QP solver is just a driver function for MinBLEIC |
|
|
//| solver; it solves quadratic programming problem as general |
|
|
//| linearly constrained optimization problem, which is solved |
|
|
//| by means of BLEIC solver(part of ALGLIB, active set method). |
|
|
//| ALGORITHM LIMITATIONS: |
|
|
//| * This algorithm is inefficient on problems with hundreds and |
|
|
//| thousands of general inequality constraints and infeasible |
|
|
//| initial point. Initial feasibility detection stage may take |
|
|
//| too long on such constraint sets. Consider using DENSE-IPM |
|
|
//| or DENSE-AUL instead. |
|
|
//| * unlike QuickQP solver, this algorithm does not perform Newton|
|
|
//| steps and does not use Level 3 BLAS. Being general-purpose |
|
|
//| active set method, it can activate constraints only |
|
|
//| one-by-one. Thus, its performance is lower than that of |
|
|
//| QuickQP. |
|
|
//| * its precision is also a bit inferior to that of QuickQP. |
|
|
//| BLEIC-QP performs only LBFGS steps(no Newton steps), which |
|
|
//| are good at detecting neighborhood of the solution, buy needs|
|
|
//| many iterations to find solution with more than 6 digits of |
|
|
//| precision. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsG - >= 0, The subroutine finishes its work if the |
|
|
//| condition | v | < EpsG is satisfied, where: |
|
|
//| * | . | means Euclidian norm |
|
|
//| * v - scaled constrained gradient vector, |
|
|
//| v[i] = g[i] * s[i] |
|
|
//| * g - gradient |
|
|
//| * s - scaling coefficients set by |
|
|
//| MinQPSetScale() |
|
|
//| EpsF - >= 0, The subroutine finishes its work if |
|
|
//| exploratory steepest descent step on k+1-th |
|
|
//| iteration satisfies following condition: |
|
|
//| |F(k+1) - F(k)| <= EpsF * max{ |F(k)|, |F(k+1)|, 1}|
|
|
//| EpsX - >= 0, The subroutine finishes its work if |
|
|
//| exploratory steepest descent step on k+1-th |
|
|
//| iteration satisfies following condition: |
|
|
//| * | . | means Euclidian norm |
|
|
//| * v - scaled step vector, v[i] = dx[i] / s[i] |
|
|
//| * dx - step vector, dx = X(k + 1) - X(k) |
|
|
//| * s - scaling coefficients set by |
|
|
//| MinQPSetScale() |
|
|
//| MaxIts - maximum number of iterations. If MaxIts = 0, the |
|
|
//| number of iterations is unlimited. |
|
|
//| NOTE: this algorithm uses LBFGS iterations, which are relatively |
|
|
//| cheap, but improve function value only a bit. So you will |
|
|
//| need many iterations to converge - from 0.1 * N to 10 * N, |
|
|
//| depending on problem's condition number. |
|
|
//| IT IS VERY IMPORTANT TO CALL MinQPSetScale() WHEN YOU USE THIS |
|
|
//| ALGORITHM BECAUSE ITS STOPPING CRITERIA ARE SCALE - DEPENDENT! |
|
|
//| Passing EpsG = 0, EpsF = 0 and EpsX = 0 and MaxIts = 0 |
|
|
//| (simultaneously) will lead to automatic stopping criterion |
|
|
//| selection (presently it is small step length, but it may change |
|
|
//| in the future versions of ALGLIB). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetAlgoBLEIC(CMinQPState &State,
|
|
double epsg,
|
|
double epsf,
|
|
double epsx,
|
|
int m_maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(epsg),__FUNCTION__+": EpsG is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsg>=0.0,__FUNCTION__+": negative EpsG"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsf),__FUNCTION__+": EpsF is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsf>=0.0,__FUNCTION__+": negative EpsF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsx),__FUNCTION__+": EpsX is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX"))
|
|
return;
|
|
if(!CAp::Assert(m_maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
|
|
State.m_algokind=2;
|
|
if(epsg==0.0 && epsf==0.0 && epsx==0.0 && m_maxits==0)
|
|
epsx=1.0E-6;
|
|
State.m_qpbleicsettingsuser.m_epsg=epsg;
|
|
State.m_qpbleicsettingsuser.m_epsf=epsf;
|
|
State.m_qpbleicsettingsuser.m_epsx=epsx;
|
|
State.m_qpbleicsettingsuser.m_maxits=m_maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells QP solver to use DENSE-AUL algorithm and sets|
|
|
//| stopping criteria for the algorithm. |
|
|
//| This algorithm is intended for non-convex problems with moderate |
|
|
//| (up to several thousands) variable count and arbitrary number of |
|
|
//| constraints which are either(a) effectively convexified under |
|
|
//| constraints or (b) have unique solution even with nonconvex |
|
|
//| target. |
|
|
//| IMPORTANT: when DENSE-IPM solver is applicable, its performance |
|
|
//| is usually much better than that of DENSE-AUL. We |
|
|
//| recommend you to use DENSE-AUL only when other solvers|
|
|
//| can not be used. |
|
|
//| ALGORITHM FEATURES: |
|
|
//| * supports box and dense / sparse general linear equality / |
|
|
//| inequality constraints |
|
|
//| * convergence is theoretically proved for positive - definite |
|
|
//| (convex) QP problems. Semidefinite and non-convex problems |
|
|
//| can be solved as long as they are bounded from below under |
|
|
//| constraints, although without theoretical guarantees. |
|
|
//| ALGORITHM OUTLINE: |
|
|
//| * this algorithm is an augmented Lagrangian method with dense |
|
|
//| preconditioner(hence its name). |
|
|
//| * it performs several outer iterations in order to refine |
|
|
//| values of the Lagrange multipliers. Single outer iteration is|
|
|
//| a solution of some optimization problem: first it performs |
|
|
//| dense Cholesky factorization of the Hessian in order to build|
|
|
//| preconditioner (adaptive regularization is applied to enforce|
|
|
//| positive definiteness), and then it uses L-BFGS optimizer to |
|
|
//| solve optimization problem. |
|
|
//| * typically you need about 5-10 outer iterations to converge |
|
|
//| to solution |
|
|
//| ALGORITHM LIMITATIONS: |
|
|
//| * because dense Cholesky driver is used, this algorithm has |
|
|
//| O(N^2) memory requirements and O(OuterIterations*N^3) minimum|
|
|
//| running time. From the practical point of view, it limits its|
|
|
//| applicability by several thousands of variables. |
|
|
//| From the other side, variables count is the most limiting factor,|
|
|
//| and dependence on constraint count is much more lower. Assuming |
|
|
//| that constraint matrix is sparse, it may handle tens of thousands|
|
|
//| of general linear constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsX - >= 0, stopping criteria for inner optimizer. Inner |
|
|
//| iterations are stopped when step length (with|
|
|
//| variable scaling being applied) is less than |
|
|
//| EpsX. See MinQPSetScale() for more |
|
|
//| information on variable scaling. |
|
|
//| Rho - penalty coefficient, Rho > 0: |
|
|
//| * large enough that algorithm converges with |
|
|
//| desired precision. |
|
|
//| * not TOO large to prevent ill-conditioning |
|
|
//| * recommended values are 100, 1000 or 10000 |
|
|
//| ItsCnt - number of outer iterations: |
|
|
//| * recommended values: 10 - 15 (although in most |
|
|
//| cases it converges within 5 iterations, you may |
|
|
//| need a few more to be sure). |
|
|
//| * ItsCnt = 0 means that small number of outer |
|
|
//| iterations is automatically chosen (10 iterations|
|
|
//| in current version). |
|
|
//| * ItsCnt = 1 means that AUL algorithm performs just|
|
|
//| as usual penalty method. |
|
|
//| * ItsCnt > 1 means that AUL algorithm performs |
|
|
//| specified number of outer iterations |
|
|
//| IT IS VERY IMPORTANT TO CALL MinQPSetScale() WHEN YOU USE THIS |
|
|
//| ALGORITHM BECAUSE ITS CONVERGENCE PROPERTIES AND STOPPING |
|
|
//| CRITERIA ARE SCALE - DEPENDENT! |
|
|
//| NOTE: Passing EpsX = 0 will lead to automatic step length |
|
|
//| selection (specific step length chosen may change in the |
|
|
//| future versions of ALGLIB, so it is better to specify step |
|
|
//| length explicitly). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetAlgoDenseAUL(CMinQPState &State,
|
|
double epsx,
|
|
double rho,
|
|
int itscnt)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(epsx),__FUNCTION__+": EpsX is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(rho),__FUNCTION__+": Rho is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(rho>0.0,__FUNCTION__+": non-positive Rho"))
|
|
return;
|
|
if(!CAp::Assert(itscnt>=0,__FUNCTION__+": negative ItsCnt!"))
|
|
return;
|
|
|
|
State.m_algokind=4;
|
|
if(epsx==0.0)
|
|
epsx=1.0E-8;
|
|
if(itscnt==0)
|
|
itscnt=10;
|
|
State.m_qpdenseaulsettingsuser.m_epsx=epsx;
|
|
State.m_qpdenseaulsettingsuser.m_outerits=itscnt;
|
|
State.m_qpdenseaulsettingsuser.m_rho=rho;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells QP solver to use DENSE-IPM QP algorithm and |
|
|
//| sets stopping criteria for the algorithm. |
|
|
//| This algorithm is intended for convex and semidefinite problems |
|
|
//| with moderate (up to several thousands) variable count and |
|
|
//| arbitrary number of constraints. |
|
|
//| IMPORTANT: this algorithm won't work for nonconvex problems, use |
|
|
//| DENSE-AUL or BLEIC-QP instead. If you try to run |
|
|
//| DENSE-IPM on problem with indefinite matrix (matrix |
|
|
//| having at least one negative eigenvalue) then |
|
|
//| depending on circumstances it may either(a) stall at |
|
|
//| some arbitrary point, or (b) throw exception on |
|
|
//| failure of Cholesky decomposition. |
|
|
//| ALGORITHM FEATURES: |
|
|
//| * supports box and dense / sparse general linear equality / |
|
|
//| inequality constraints |
|
|
//| ALGORITHM OUTLINE: |
|
|
//| * this algorithm is our implementation of interior point method|
|
|
//| as formulated by R.J.Vanderbei, with minor modifications to |
|
|
//| the algorithm (damped Newton directions are extensively used)|
|
|
//| * like all interior point methods, this algorithm tends to |
|
|
//| converge in roughly same number of iterations (between 15 and|
|
|
//| 50) independently from the problem dimensionality |
|
|
//| ALGORITHM LIMITATIONS: |
|
|
//| * because dense Cholesky driver is used, for N-dimensional |
|
|
//| problem with M dense constaints this algorithm has |
|
|
//| O(N^2 + N*M) memory requirements and O(N^3 + N*M^2) running |
|
|
//| time. Having sparse constraints with Z nonzeros per row |
|
|
//| relaxes storage and running time down to O(N^2 + M*Z) and |
|
|
//| O(N^3 + N*Z^2) From the practical point of view, it limits |
|
|
//| its applicability by several thousands of variables. From the|
|
|
//| other side, variables count is the most limiting factor, and |
|
|
//| dependence on constraint count is much more lower. Assuming |
|
|
//| that constraint matrix is sparse, it may handle tens of |
|
|
//| thousands of general linear constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| Eps - >= 0, stopping criteria. The algorithm stops when |
|
|
//| primal and dual infeasiblities as well as |
|
|
//| complementarity gap are less than Eps. |
|
|
//| IT IS VERY IMPORTANT TO CALL minqpsetscale() WHEN YOU USE THIS |
|
|
//| ALGORITHM BECAUSE ITS CONVERGENCE PROPERTIES AND STOPPING |
|
|
//| CRITERIA ARE SCALE - DEPENDENT! |
|
|
//| NOTE: Passing EpsX = 0 will lead to automatic selection of small |
|
|
//| epsilon. |
|
|
//| ===== TRACING IPM SOLVER ======================================= |
|
|
//| IPM solver supports advanced tracing capabilities. You can trace |
|
|
//| algorithm output by specifying following trace symbols (case- |
|
|
//| insensitive) by means of trace_file() call: |
|
|
//| * 'IPM' - for basic trace of algorithm steps and decisions|
|
|
//| Only short scalars(function values and deltas) |
|
|
//| are printed. N-dimensional quantities like |
|
|
//| search directions are NOT printed. |
|
|
//| * 'IPM.DETAILED' - for output of points being visited and |
|
|
//| search directions. This symbol also implicitly |
|
|
//| defines 'IPM'. You can control output format by |
|
|
//| additionally specifying: |
|
|
//| * nothing to output in 6-digit exponential |
|
|
//| format |
|
|
//| * 'PREC.E15' to output in 15-digit exponential |
|
|
//| format |
|
|
//| * 'PREC.F6' to output in 6-digit fixed-point |
|
|
//| format |
|
|
//| By default trace is disabled and adds no overhead to the |
|
|
//| optimization process. However, specifying any of the symbols adds|
|
|
//| some formatting and output - related overhead. |
|
|
//| You may specify multiple symbols by separating them with commas: |
|
|
//| > |
|
|
//| >CAlglib::Trace_File("IPM,PREC.F6", "path/to/trace.log") |
|
|
//| > |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetAlgoDenseIPM(CMinQPState &State,
|
|
double eps)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(eps),__FUNCTION__+": Eps is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(eps>=0.0,__FUNCTION__+": negative Eps"))
|
|
return;
|
|
//--- change value
|
|
State.m_algokind=5;
|
|
State.m_veps=eps;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells QP solver to use SPARSE-IPM QP algorithm |
|
|
//| and sets stopping criteria for the algorithm. |
|
|
//| This algorithm is intended for convex and semidefinite |
|
|
//| problems with large variable and constraint count and sparse |
|
|
//| quadratic term and constraints. It is possible to have some |
|
|
//| limited set of dense linear constraints - they will be handled |
|
|
//| separately by dense BLAS - but the more dense constraints you |
|
|
//| have, the more time solver needs. |
|
|
//| IMPORTANT: internally this solver performs large and sparse(N+M) |
|
|
//| x(N+M) triangular factorization. So it expects both |
|
|
//| quadratic term and constraints to be highly CSparse |
|
|
//| However, its running time is influenced by BOTH fill|
|
|
//| factor and sparsity pattern. |
|
|
//| Generally we expect that no more than few nonzero elements per |
|
|
//| row are present. However different sparsity patterns may result |
|
|
//| in completely different running times even given same fill |
|
|
//| factor. |
|
|
//| In many cases this algorithm outperforms DENSE-IPM by order of |
|
|
//| magnitude. However, in some cases you may get better results |
|
|
//| with DENSE-IPM even when solving sparse task. |
|
|
//| IMPORTANT: this algorithm won't work for nonconvex problems, use |
|
|
//| DENSE-AUL or BLEIC - QP instead. If you try to run |
|
|
//| DENSE-IPM on problem with indefinite matrix (matrix |
|
|
//| having at least one negative eigenvalue) then |
|
|
//| depending on circumstances it may either(a) stall at |
|
|
//| some arbitrary point, or (b) throw exception on |
|
|
//| failure of Cholesky decomposition. |
|
|
//| ALGORITHM FEATURES: |
|
|
//| * supports box and dense/sparse general linear equality/ |
|
|
//| inequality constraints |
|
|
//| * specializes on large-scale sparse problems |
|
|
//| ALGORITHM OUTLINE: |
|
|
//| * this algorithm is our implementation of interior point |
|
|
//| method as formulated by R.J.Vanderbei, with minor |
|
|
//| modifications to the algorithm (damped Newton directions are|
|
|
//| extensively used) |
|
|
//| * like all interior point methods, this algorithm tends to |
|
|
//| converge in roughly same number of iterations(between 15 and |
|
|
//| 50) independently from the problem dimensionality |
|
|
//| ALGORITHM LIMITATIONS: |
|
|
//| * this algorithm may handle moderate number of dense |
|
|
//| constraints, usually no more than a thousand of dense ones |
|
|
//| without losing its efficiency. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| Eps - >= 0, stopping criteria. The algorithm stops when |
|
|
//| primal and dual infeasiblities as well as |
|
|
//| complementarity gap are less than Eps. |
|
|
//| IT IS VERY IMPORTANT TO CALL minqpsetscale() WHEN YOU USE THIS |
|
|
//| ALGORITHM BECAUSE ITS CONVERGENCE PROPERTIES AND STOPPING |
|
|
//| CRITERIA ARE SCALE - DEPENDENT! |
|
|
//| NOTE: Passing EpsX = 0 will lead to automatic selection of small |
|
|
//| epsilon. |
|
|
//| ===== TRACING IPM SOLVER ======================================= |
|
|
//| IPM solver supports advanced tracing capabilities. You can trace |
|
|
//| algorithm output by specifying following trace symbols |
|
|
//| (case-insensitive) by means of trace_file() call: |
|
|
//| * 'IPM' - for basic trace of algorithm steps and decisions. |
|
|
//| Only short scalars(function values and deltas) are |
|
|
//| printed. N-dimensional quantities like search |
|
|
//| directions are NOT printed. |
|
|
//| * 'IPM.DETAILED' - for output of points being visited and |
|
|
//| search directions. This symbol also implicitly |
|
|
//| defines 'IPM'. You can control output format by |
|
|
//| additionally specifying: |
|
|
//| * nothing to output in 6-digit exponential |
|
|
//| format |
|
|
//| * 'PREC.E15' to output in 15-digit exponential |
|
|
//| format |
|
|
//| * 'PREC.F6' to output in 6-digit fixed-point |
|
|
//| format |
|
|
//| By default trace is disabled and adds no overhead to the |
|
|
//| optimization process. However, specifying any of the symbols adds|
|
|
//| some formatting and output - related overhead. |
|
|
//| You may specify multiple symbols by separating them with commas: |
|
|
//| > |
|
|
//| >CAlglib::Trace_File("IPM,PREC.F6", "path/to/trace.log") |
|
|
//| > |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetAlgoSparseIPM(CMinQPState &State,
|
|
double eps)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(eps),__FUNCTION__+": Eps is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(eps>=0.0,__FUNCTION__+": negative Eps"))
|
|
return;
|
|
|
|
State.m_algokind=6;
|
|
State.m_veps=eps;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells solver to use QuickQP algorithm: special |
|
|
//| extra-fast algorithm for problems with box-only constrants. It |
|
|
//| may solve non-convex problems as long as they are bounded from |
|
|
//| below under constraints. |
|
|
//| ALGORITHM FEATURES: |
|
|
//| * several times faster than DENSE-IPM when running on box-only |
|
|
//| problem |
|
|
//| * utilizes accelerated methods for activation of constraints. |
|
|
//| * supports dense and sparse QP problems |
|
|
//| * supports ONLY box constraints; general linear constraints are|
|
|
//| NOT supported by this solver |
|
|
//| * can solve all types of problems (convex, semidefinite, |
|
|
//| nonconvex) as long as they are bounded from below under |
|
|
//| constraints. Say, it is possible to solve "min{-x^2} subject |
|
|
//| to -1<=x<=+1". In convex/semidefinite case global minimum is|
|
|
//| returned, in nonconvex case-algorithm returns one of the |
|
|
//| local minimums. |
|
|
//| ALGORITHM OUTLINE: |
|
|
//| * algorithm performs two kinds of iterations: constrained CG |
|
|
//| iterations and constrained Newton iterations |
|
|
//| * initially it performs small number of constrained CG |
|
|
//| iterations, which can efficiently activate/deactivate |
|
|
//| multiple constraints |
|
|
//| * after CG phase algorithm tries to calculate Cholesky |
|
|
//| decomposition and to perform several constrained Newton |
|
|
//| steps. If Cholesky decomposition failed(matrix is |
|
|
//| indefinite even under constraints), we perform more CG |
|
|
//| iterations until we converge to such set of constraints that |
|
|
//| system matrix becomes positive definite. Constrained Newton |
|
|
//| steps greatly increase convergence speed and precision. |
|
|
//| * algorithm interleaves CG and Newton iterations which allows |
|
|
//| to handle indefinite matrices (CG phase) and quickly converge|
|
|
//| after final set of constraints is found (Newton phase). |
|
|
//| Combination of CG and Newton phases is called "outer |
|
|
//| iteration". |
|
|
//| * it is possible to turn off Newton phase (beneficial for |
|
|
//| semidefinite problems - Cholesky decomposition will fail too |
|
|
//| often) |
|
|
//| ALGORITHM LIMITATIONS: |
|
|
//| * algorithm does not support general linear constraints; only|
|
|
//| box ones are supported |
|
|
//| * Cholesky decomposition for sparse problems is performed with |
|
|
//| Skyline Cholesky solver, which is intended for low-profile |
|
|
//| matrices. No profile-reducing reordering of variables is |
|
|
//| performed in this version of ALGLIB. |
|
|
//| * problems with near-zero negative eigenvalues (or exacty zero |
|
|
//| ones) may experience about 2-3x performance penalty. The |
|
|
//| reason is that Cholesky decomposition can not be performed |
|
|
//| until we identify directions of zero and negative curvature |
|
|
//| and activate corresponding boundary constraints- but we need |
|
|
//| a lot of trial and errors because these directions are hard |
|
|
//| to notice in the matrix spectrum. In this case you may turn |
|
|
//| off Newton phase of algorithm. Large negative eigenvalues |
|
|
//| are not an issue, so highly non-convex problems can be |
|
|
//| solved very efficiently. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsG - >= 0. The subroutine finishes its work if the |
|
|
//| condition |v| < EpsG is satisfied, where: |
|
|
//| * | . | means Euclidian norm |
|
|
//| * v - scaled constrained gradient vector, |
|
|
//| v[i] = g[i] * s[i] |
|
|
//| * g - gradient |
|
|
//| * s - scaling coefficients set by MinQPSetScale() |
|
|
//| EpsF - >= 0. The subroutine finishes its work if |
|
|
//| exploratory steepest descent step on k+1-th |
|
|
//| iteration satisfies following condition: |
|
|
//| |F(k+1) - F(k)| <= EpsF*max{|F(k)|, |F(k+1)|, 1} |
|
|
//| EpsX - >= 0. The subroutine finishes its work if |
|
|
//| exploratory steepest descent step on k+1-th |
|
|
//| iteration satisfies following condition: |
|
|
//| * | . | means Euclidian norm |
|
|
//| * v - scaled step vector, v[i] = dx[i] / s[i] |
|
|
//| * dx - step vector, dx = X(k + 1) - X(k) |
|
|
//| * s - scaling coefficients set by MinQPSetScale() |
|
|
//| MaxOuterIts - maximum number of OUTER iterations. One outer |
|
|
//| iteration includes some amount of CG iterations |
|
|
//| (from 5 to ~N) and one or several (usually small |
|
|
//| amount) Newton steps. Thus, one outer iteration has|
|
|
//| high cost, but can greatly reduce funcation value. |
|
|
//| Use 0 if you do not want to limit number of outer |
|
|
//| iterations. |
|
|
//| UseNewton - use Newton phase or not: |
|
|
//| * Newton phase improves performance of positive |
|
|
//| definite dense problems (about 2 times |
|
|
//| improvement can be observed) |
|
|
//| * can result in some performance penalty on |
|
|
//| semidefinite or slightly negative definite |
|
|
//| problems - each Newton phase will bring no |
|
|
//| improvement (Cholesky failure), but still will |
|
|
//| require computational time. |
|
|
//| * if you doubt, you can turn off this phase - |
|
|
//| optimizer will retain its most of its high speed.|
|
|
//| IT IS VERY IMPORTANT TO CALL MinQPSetScale() WHEN YOU USE THIS |
|
|
//| ALGORITHM BECAUSE ITS STOPPING CRITERIA ARE SCALE - DEPENDENT! |
|
|
//| Passing EpsG = 0, EpsF = 0 and EpsX = 0 and MaxIts = 0 |
|
|
//| (simultaneously) will lead to automatic stopping criterion |
|
|
//| selection (presently it is small step length, but it may change |
|
|
//| in the future versions of ALGLIB). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetAlgoQuickQP(CMinQPState &State,
|
|
double epsg,
|
|
double epsf,
|
|
double epsx,
|
|
int maxouterits,
|
|
bool usenewton)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(epsg),__FUNCTION__+": EpsG is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsg>=0.0,__FUNCTION__+": negative EpsG"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsf),__FUNCTION__+": EpsF is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsf>=0.0,__FUNCTION__+": negative EpsF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsx),__FUNCTION__+": EpsX is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX"))
|
|
return;
|
|
if(!CAp::Assert(maxouterits>=0,__FUNCTION__+": negative MaxOuterIts!"))
|
|
return;
|
|
|
|
State.m_algokind=3;
|
|
if(epsg==0.0 && epsf==0.0 && epsx==0.0 && maxouterits==0)
|
|
epsx=1.0E-6;
|
|
|
|
State.m_qqpsettingsuser.m_maxouterits=maxouterits;
|
|
State.m_qqpsettingsuser.m_epsg=epsg;
|
|
State.m_qqpsettingsuser.m_epsf=epsf;
|
|
State.m_qqpsettingsuser.m_epsx=epsx;
|
|
State.m_qqpsettingsuser.m_cnphase=usenewton;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets boundary constraints for QP m_solver |
|
|
//| Boundary constraints are inactive by default (after initial |
|
|
//| creation). After being set, they are preserved until explicitly |
|
|
//| turned off with another SetBC() call. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bounds, array[N]. |
|
|
//| If some (all) variables are unbounded, you may |
|
|
//| specify very small number or -INF (latter is |
|
|
//| recommended because it will allow m_solver to use |
|
|
//| better algorithm). |
|
|
//| BndU - upper bounds, array[N]. |
|
|
//| If some (all) variables are unbounded, you may |
|
|
//| specify very large number or +INF (latter is |
|
|
//| recommended because it will allow m_solver to use |
|
|
//| better algorithm). |
|
|
//| NOTE: it is possible to specify BndL[i]=BndU[i]. In this case |
|
|
//| I-th variable will be "frozen" at X[i]=BndL[i]=BndU[i]. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetBC(CMinQPState &State,double &bndl[],double &bndu[])
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndu)>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
//--- change values
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndl[i]) || AL_NEGINF==bndl[i],__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndu[i]) || AL_POSINF==bndu[i],__FUNCTION__+": BndU contains NAN or -INF"))
|
|
return;
|
|
//--- change values
|
|
State.m_havebndl[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_havebndu[i]=MathIsValidNumber(bndu[i]);
|
|
}
|
|
State.m_bndl=bndl;
|
|
State.m_bndu=bndu;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetBC(CMinQPState &State,CRowDouble &bndl,CRowDouble &bndu)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndu)>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
//--- change values
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndl[i]) || AL_NEGINF==bndl[i],__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndu[i]) || AL_POSINF==bndu[i],__FUNCTION__+": BndU contains NAN or -INF"))
|
|
return;
|
|
//--- change values
|
|
State.m_havebndl[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_havebndu[i]=MathIsValidNumber(bndu[i]);
|
|
}
|
|
State.m_bndl=bndl;
|
|
State.m_bndu=bndu;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets box constraints for QP solver (all variables |
|
|
//| at once, same constraints for all variables) |
|
|
//| Box constraints are inactive by default (after initial creation)|
|
|
//| After being set, they are preserved until explicitly overwritten |
|
|
//| with another MinQPSetBC() or MinQPSetBCAll() call, or partially |
|
|
//| overwritten with MinQPSetBCI() call. |
|
|
//| Following types of constraints are supported: |
|
|
//| DESCRIPTION CONSTRAINT HOW TO SPECIFY |
|
|
//| fixed variable x[i]=Bnd BndL=BndU |
|
|
//| lower bound BndL<=x[i] BndU=+INF |
|
|
//| upper bound x[i]<=BndU BndL=-INF |
|
|
//| range BndL<=x[i]<=BndU ... |
|
|
//| free variable - BndL=-INF, BndU+INF|
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bound, same for all variables |
|
|
//| BndU - upper bound, same for all variables |
|
|
//| NOTE: infinite values can be specified by means of AL_NEGINF and |
|
|
//| AL_POSINF. |
|
|
//| NOTE: you may replace infinities by very small/very large values,|
|
|
//| but it is not recommended because large numbers may |
|
|
//| introduce large numerical errors in the algorithm. |
|
|
//| NOTE: BndL>BndU will result in QP problem being recognized as |
|
|
//| infeasible. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetBCAll(CMinQPState &State,
|
|
double bndl,
|
|
double bndu)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(bndl) || AL_NEGINF==bndl,__FUNCTION__+": BndL is NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu) || AL_POSINF==bndu,__FUNCTION__+": BndU is NAN or -INF"))
|
|
return;
|
|
|
|
State.m_bndl.Fill(bndl);
|
|
State.m_bndu.Fill(bndu);
|
|
ArrayInitialize(State.m_havebndl,MathIsValidNumber(bndl));
|
|
ArrayInitialize(State.m_havebndu,MathIsValidNumber(bndu));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets box constraints for I-th variable (other |
|
|
//| variables are not modified). |
|
|
//| Following types of constraints are supported: |
|
|
//| DESCRIPTION CONSTRAINT HOW TO SPECIFY |
|
|
//| fixed variable x[i] = Bnd BndL = BndU |
|
|
//| lower bound BndL <= x[i] BndU = +INF |
|
|
//| upper bound x[i] <= BndU BndL = -INF |
|
|
//| range BndL <= x[i] <= BndU ... |
|
|
//| free variable - BndL -INF, BndU +INF|
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bound |
|
|
//| BndU - upper bound |
|
|
//| NOTE: infinite values can be specified by means of AL_POSINF and |
|
|
//| AL_POSINF. |
|
|
//| NOTE: you may replace infinities by very small/very large values,|
|
|
//| but it is not recommended because large numbers may |
|
|
//| introduce large numerical errors in the algorithm. |
|
|
//| NOTE: BndL>BndU will result in QP problem being recognized as |
|
|
//| infeasible. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetBCI(CMinQPState &State,
|
|
int i,
|
|
double bndl,
|
|
double bndu)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(i>=0 && i<State.m_n,__FUNCTION__+": I is outside of [0,N)"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndl) || AL_NEGINF==bndl,__FUNCTION__+": BndL is NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu) || AL_POSINF==bndu,__FUNCTION__+": BndU is NAN or -INF"))
|
|
return;
|
|
//--- change values
|
|
State.m_bndl.Set(i,bndl);
|
|
State.m_bndu.Set(i,bndu);
|
|
State.m_havebndl[i]=MathIsValidNumber(bndl);
|
|
State.m_havebndu[i]=MathIsValidNumber(bndu);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets dense linear constraints for QP optimizer. |
|
|
//| This function overrides results of previous calls to MinQPSetLC()|
|
|
//| MinQPSetLCSparse() and MinQPSetLCMixed(). After call to this |
|
|
//| function all non-box constraints are dropped, and you have only |
|
|
//| those constraints which were specified in the present call. |
|
|
//| If you want to specify mixed(with dense and sparse terms) linear |
|
|
//| constraints, you should call MinQPSetLCMixed(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinQPCreate |
|
|
//| call. |
|
|
//| C - linear constraints, array[K, N + 1]. Each row of C |
|
|
//| represents one constraint, either equality or |
|
|
//| inequality (see below): |
|
|
//| * first N elements correspond to coefficients, |
|
|
//| * last element corresponds to the right part. |
|
|
//| All elements of C(including right part) must be |
|
|
//| finite. |
|
|
//| CT - type of constraints, array[K]: |
|
|
//| * if CT[i] > 0, then I-th constraint is |
|
|
//| C[i, *] * x >= C[i, n + 1] |
|
|
//| * if CT[i] = 0, then I-th constraint is |
|
|
//| C[i, *] * x = C[i, n + 1] |
|
|
//| * if CT[i] < 0, then I-th constraint is |
|
|
//| C[i, *] * x <= C[i, n + 1] |
|
|
//| K - number of equality/inequality constraints, K >= 0: |
|
|
//| * if given, only leading K elements of C/CT are |
|
|
//| used |
|
|
//| * if not given, automatically determined from sizes|
|
|
//| of C/CT |
|
|
//| NOTE 1: linear (non-bound) constraints are satisfied only |
|
|
//| approximately - there always exists some violation due |
|
|
//| to numerical errors and algorithmic limitations |
|
|
//| (BLEIC-QP solver is most precise, AUL-QP solver is less |
|
|
//| precise). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLC(CMinQPState &State,
|
|
CMatrixDouble &c,
|
|
CRowInt &ct,
|
|
int k)
|
|
{
|
|
CSparseMatrix dummyc;
|
|
CRowInt dummyct;
|
|
MinQPSetLCMixed(State,dummyc,dummyct,0,c,ct,k);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets sparse linear constraints for QP optimizer. |
|
|
//| This function overrides results of previous calls to MinQPSetLC()|
|
|
//| MinQPSetLCSparse() and MinQPSetLCMixed(). After call to this |
|
|
//| function all non-box constraints are dropped, and you have only |
|
|
//| those constraints which were specified in the present call. |
|
|
//| If you want to specify mixed(with dense and sparse terms) linear |
|
|
//| constraints, you should call MinQPSetLCMixed(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinQPCreate |
|
|
//| call. |
|
|
//| C - linear constraints, sparse matrix with dimensions |
|
|
//| at least [K, N + 1]. If matrix has larger size, |
|
|
//| only leading Kx(N + 1) rectangle is used. Each row |
|
|
//| of C represents one constraint, either equality or |
|
|
//| inequality(see below) : |
|
|
//| * first N elements correspond to coefficients, |
|
|
//| * last element corresponds to the right part. |
|
|
//| All elements of C(including right part) must be |
|
|
//| finite. |
|
|
//| CT - type of constraints, array[K]: |
|
|
//| * if CT[i] > 0, then I-th constraint is |
|
|
//| C[i, *] * x >= C[i, n + 1] |
|
|
//| * if CT[i] = 0, then I-th constraint is |
|
|
//| C[i, *] * x = C[i, n + 1] |
|
|
//| * if CT[i] < 0, then I-th constraint is |
|
|
//| C[i, *] * x <= C[i, n + 1] |
|
|
//| K - number of equality/inequality constraints, K >= 0: |
|
|
//| * if given, only leading K elements of C/CT are |
|
|
//| used |
|
|
//| * if not given, automatically determined from sizes|
|
|
//| of C/CT |
|
|
//| NOTE 1: linear (non-bound) constraints are satisfied only |
|
|
//| approximately - there always exists some violation due |
|
|
//| to numerical errors and algorithmic limitations |
|
|
//| (BLEIC-QP solver is most precise, AUL-QP solver is less |
|
|
//| precise). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLCSparse(CMinQPState &State,
|
|
CSparseMatrix &c,
|
|
CRowInt &ct,
|
|
int k)
|
|
{
|
|
CMatrixDouble dummyc;
|
|
CRowInt dummyct;
|
|
MinQPSetLCMixed(State,c,ct,k,dummyc,dummyct,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets mixed linear constraints, which include a set |
|
|
//| of dense rows, and a set of sparse rows. |
|
|
//| This function overrides results of previous calls to MinQPSetLC()|
|
|
//| MinQPSetLCSparse() and MinQPSetLCMixed(). After call to this |
|
|
//| function all non-box constraints are dropped, and you have only |
|
|
//| those constraints which were specified in the present call. |
|
|
//| If you want to specify mixed(with dense and sparse terms) linear |
|
|
//| constraints, you should call MinQPSetLCMixed(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinQPCreate |
|
|
//| call. |
|
|
//| SparseC - linear constraints, sparse matrix with dimensions |
|
|
//| EXACTLY EQUAL TO [SparseK, N + 1]. Each row of C |
|
|
//| represents one constraint, either equality or |
|
|
//| inequality (see below): |
|
|
//| * first N elements correspond to coefficients, |
|
|
//| * last element corresponds to the right part. |
|
|
//| All elements of C(including right part) must be |
|
|
//| finite. |
|
|
//| SparseCT - type of sparse constraints, array[K]: |
|
|
//| * if SparseCT[i] > 0, then I-th constraint is |
|
|
//| SparseC[i, *] * x >= SparseC[i, n + 1] |
|
|
//| * if SparseCT[i] = 0, then I-th constraint is |
|
|
//| SparseC[i, *] * x = SparseC[i, n + 1] |
|
|
//| * if SparseCT[i] < 0, then I-th constraint is |
|
|
//| SparseC[i, *] * x <= SparseC[i, n + 1] |
|
|
//| SparseK - number of sparse equality/inequality constraints, |
|
|
//| K >= 0 |
|
|
//| DenseC - dense linear constraints, array[K, N + 1]. Each row|
|
|
//| of DenseC represents one constraint, either |
|
|
//| equality or inequality(see below): |
|
|
//| * first N elements correspond to coefficients, |
|
|
//| * last element corresponds to the right part. |
|
|
//| All elements of DenseC (including right part) must |
|
|
//| be finite. |
|
|
//| DenseCT - type of constraints, array[K]: |
|
|
//| * if DenseCT[i] > 0, then I-th constraint is |
|
|
//| DenseC[i, *] * x >= DenseC[i, n + 1] |
|
|
//| * if DenseCT[i] = 0, then I-th constraint is |
|
|
//| DenseC[i, *] * x = DenseC[i, n + 1] |
|
|
//| * if DenseCT[i] < 0, then I-th constraint is |
|
|
//| DenseC[i, *] * x <= DenseC[i, n + 1] |
|
|
//| DenseK - number of equality/inequality constraints, |
|
|
//| DenseK >= 0 |
|
|
//| NOTE 1: linear(non-box) constraints are satisfied only |
|
|
//| approximately - there always exists some violation due |
|
|
//| to numerical errors and algorithmic limitations |
|
|
//| (BLEIC-QP solver is most precise, AUL-QP solver is less |
|
|
//| precise). |
|
|
//| NOTE 2: due to backward compatibility reasons SparseC can be |
|
|
//| larger than [SparseK, N + 1]. In this case only leading |
|
|
//| [SparseK, N+1] submatrix will be used. However, the rest |
|
|
//| of ALGLIB has more strict requirements on the input size,|
|
|
//| so we recommend you to pass sparse term whose size |
|
|
//| exactly matches algorithm expectations. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLCMixed(CMinQPState &State,CSparseMatrix &sparsec,
|
|
CRowInt &sparsect,int sparsek,
|
|
CMatrixDouble &densec,CRowInt &densect,
|
|
int densek)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
double v=0;
|
|
CRowInt srcidx;
|
|
CRowInt dstidx;
|
|
CRowDouble s;
|
|
CRowInt rs;
|
|
CRowInt eoffs;
|
|
CRowInt roffs;
|
|
CRowDouble v2;
|
|
CRowInt eidx;
|
|
CRowDouble eval;
|
|
int t0=0;
|
|
int t1=0;
|
|
int nnz=0;
|
|
//-- First, check for errors in the inputs
|
|
if(!CAp::Assert(densek>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(!CAp::Assert(densek==0 || densec.Cols()>n,__FUNCTION__+": Cols(C)<N+1"))
|
|
return;
|
|
if(!CAp::Assert(densec.Rows()>=densek,__FUNCTION__+": Rows(DenseC)<DenseK"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(densect)>=densek,__FUNCTION__+": Length(DenseCT)<DenseK"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(densec,densek,n+1),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(sparsek>=0,__FUNCTION__+": SparseK<0"))
|
|
return;
|
|
if(!CAp::Assert(sparsek==0 || CSparse::SparseGetNCols(sparsec)>=n+1,__FUNCTION__+": Cols(SparseC)<N+1"))
|
|
return;
|
|
if(!CAp::Assert(sparsek==0 || CSparse::SparseGetNRows(sparsec)>=sparsek,__FUNCTION__+": Rows(SparseC)<SparseK"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(sparsect)>=sparsek,__FUNCTION__+": Length(SparseCT)<SparseK"))
|
|
return;
|
|
//-- Allocate place for Lagrange multipliers, fill by zero
|
|
State.m_replaglc=vector<double>::Zeros(densek+sparsek);
|
|
//-- Init
|
|
State.m_cl=vector<double>::Zeros(densek+sparsek);
|
|
State.m_cu=vector<double>::Zeros(densek+sparsek);
|
|
State.m_mdense=densek;
|
|
State.m_msparse=sparsek;
|
|
if(sparsek>0)
|
|
{
|
|
//-- Evaluate row sizes for new storage
|
|
rs.Resize(sparsek);
|
|
rs.Fill(0);
|
|
t0=0;
|
|
t1=0;
|
|
nnz=0;
|
|
while(CSparse::SparseEnumerate(sparsec,t0,t1,i,j,v))
|
|
{
|
|
if(i>sparsek-1 || j>n-1)
|
|
continue;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": C contains infinite or NAN values"))
|
|
return;
|
|
nnz++;
|
|
rs.Add(i,1);
|
|
}
|
|
//-- Prepare new sparse CRS storage, copy leading SparseK*N submatrix into the storage
|
|
for(i=0; i<sparsek; i++)
|
|
{
|
|
State.m_cl.Set(i,0);
|
|
State.m_cu.Set(i,0);
|
|
}
|
|
State.m_sparsec.m_M=sparsek;
|
|
State.m_sparsec.m_N=n;
|
|
State.m_sparsec.m_RIdx.Resize(sparsek+1);
|
|
State.m_sparsec.m_Idx.Resize(nnz);
|
|
State.m_sparsec.m_Vals.Resize(nnz);
|
|
eoffs.Resize(sparsek+1);
|
|
State.m_sparsec.m_RIdx.Set(0,0);
|
|
eoffs.Set(0,0);
|
|
for(i=1; i<=sparsek; i++)
|
|
{
|
|
State.m_sparsec.m_RIdx.Set(i,State.m_sparsec.m_RIdx[i-1]+rs[i-1]);
|
|
eoffs.Set(i,State.m_sparsec.m_RIdx[i]);
|
|
}
|
|
t0=0;
|
|
t1=0;
|
|
while(CSparse::SparseEnumerate(sparsec,t0,t1,i,j,v))
|
|
{
|
|
if(i>sparsek-1 || j>n)
|
|
continue;
|
|
if(j<n)
|
|
{
|
|
//-- Copy left part of constraint
|
|
j0=eoffs[i];
|
|
State.m_sparsec.m_Idx.Set(j0,j);
|
|
State.m_sparsec.m_Vals.Set(j0,v);
|
|
eoffs.Set(i,j0+1);
|
|
}
|
|
else
|
|
{
|
|
//-- Handle right part of the constraint
|
|
State.m_cl.Set(i,v);
|
|
State.m_cu.Set(i,v);
|
|
}
|
|
}
|
|
for(i=0; i<sparsek; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(eoffs[i]==State.m_sparsec.m_RIdx[i+1],__FUNCTION__+": critical integrity check failed (sparse copying)"))
|
|
return;
|
|
}
|
|
CSparse::SparseCreateCRSInplace(State.m_sparsec);
|
|
for(i=0; i<sparsek; i++)
|
|
{
|
|
if(sparsect[i]>0)
|
|
State.m_cu.Set(i,AL_POSINF);
|
|
if(sparsect[i]<0)
|
|
State.m_cl.Set(i,AL_NEGINF);
|
|
}
|
|
}
|
|
if(densek>0)
|
|
{
|
|
//-- Copy dense constraints
|
|
State.m_densec=densec;
|
|
State.m_densec.Resize(densek,n);
|
|
for(i=0; i<densek; i++)
|
|
{
|
|
if(densect[i]>0)
|
|
{
|
|
State.m_cl.Set(sparsek+i,densec.Get(i,n));
|
|
State.m_cu.Set(sparsek+i,AL_POSINF);
|
|
continue;
|
|
}
|
|
if(densect[i]<0)
|
|
{
|
|
State.m_cl.Set(sparsek+i,AL_NEGINF);
|
|
State.m_cu.Set(sparsek+i,densec.Get(i,n));
|
|
continue;
|
|
}
|
|
State.m_cl.Set(sparsek+i,densec.Get(i,n));
|
|
State.m_cu.Set(sparsek+i,densec.Get(i,n));
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function provides legacy API for specification of mixed |
|
|
//| dense / sparse linear constraints. |
|
|
//| New conventions used by ALGLIB since release 3.16.0 State that |
|
|
//| set of sparse constraints comes first, followed by set of |
|
|
//| dense ones. This convention is essential when you talk about |
|
|
//| things like order of Lagrange multipliers. |
|
|
//| However, legacy API accepted mixed constraints in reverse order. |
|
|
//| This function is here to simplify situation with code relying on |
|
|
//| legacy API. It simply accepts constraints in one order (old) and |
|
|
//| passes them to new API, now in correct order. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLCMixedLegacy(CMinQPState &State,
|
|
CMatrixDouble &densec,
|
|
CRowInt &densect,
|
|
int densek,
|
|
CSparseMatrix &sparsec,
|
|
CRowInt &sparsect,
|
|
int sparsek)
|
|
{
|
|
MinQPSetLCMixed(State,sparsec,sparsect,sparsek,densec,densect,densek);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets two-sided linear constraints AL <= A*x <= AU |
|
|
//| with dense constraint matrix A. |
|
|
//| NOTE: knowing that constraint matrix is dense helps some QP |
|
|
//| solvers (especially modern IPM method) to utilize efficient dense|
|
|
//| Level 3 BLAS for dense parts of the problem. If your problem has |
|
|
//| both dense and sparse constraints, you can use MinQPSetLC2Mixed()|
|
|
//| function, which will result in dense algebra being applied to |
|
|
//| dense terms, and sparse sparse linear algebra applied to sparse |
|
|
//| terms. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinQPCreate() |
|
|
//| call. |
|
|
//| A - linear constraints, array[K, N]. Each row of A |
|
|
//| represents one constraint. One-sided inequality |
|
|
//| constraints, two-sided inequality constraints, |
|
|
//| equality constraints are supported (see below) |
|
|
//| AL, AU - lower and upper bounds, array[K]; |
|
|
//| * AL[i] = AU[i] => equality constraint Ai * x |
|
|
//| * AL[i]<AU.Set(i,> two-sided constraint |
|
|
//| AL[i] <= Ai*x <= AU[i] |
|
|
//| * AL[i] = -INF => one-sided constraint |
|
|
//| Ai*x <= AU[i] |
|
|
//| * AU[i] = +INF => one-sided constraint |
|
|
//| AL[i] <= Ai*x |
|
|
//| * AL[i] = -INF, AU[i] = +INF => constraint is |
|
|
//| ignored |
|
|
//| K - number of equality/inequality constraints, K >= 0; |
|
|
//| if not given, inferred from sizes of A, AL, AU. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLC2Dense(CMinQPState &State,CMatrixDouble &a,
|
|
CRowDouble &al,CRowDouble &au,int k)
|
|
{
|
|
MinQPSetLC2Mixed(State,State.m_dummysparse,0,a,k,al,au);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets two-sided linear constraints AL <= A*x <= AU |
|
|
//| with sparse constraining matrix A. Recommended for large-scale |
|
|
//| problems. |
|
|
//| This function overwrites linear (non-box) constraints set by |
|
|
//| previous calls(if such calls were made). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinQPCreate() |
|
|
//| call. |
|
|
//| A - sparse matrix with size [K, N](exactly!). Each row |
|
|
//| of A represents one general linear constraint. A |
|
|
//| can be stored in any sparse storage format. |
|
|
//| AL, AU - lower and upper bounds, array[K]; |
|
|
//| * AL[i] = AU[i] => equality constraint Ai*x |
|
|
//| * AL[i]<AU.Set(i,> two-sided constraint |
|
|
//| AL[i] <= Ai*x <= AU[i] |
|
|
//| * AL[i] = -INF => one-sided constraint |
|
|
//| Ai*x <= AU[i] |
|
|
//| * AU[i] = +INF => one-sided constraint |
|
|
//| AL[i] <= Ai*x |
|
|
//| * AL[i] = -INF, AU[i] = +INF => constraint is |
|
|
//| ignored |
|
|
//| K - number of equality/inequality constraints, K >= 0. |
|
|
//| If K = 0 is specified, A, AL, AU are ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLC2(CMinQPState &State,CSparseMatrix &a,
|
|
CRowDouble &al,CRowDouble &au,int k)
|
|
{
|
|
MinQPSetLC2Mixed(State,a,k,State.m_dummyr2,0,al,au);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets two-sided linear constraints AL <= A*x <= AU |
|
|
//| with mixed constraining matrix A including sparse part (first |
|
|
//| SparseK rows) and dense part(last DenseK rows). Recommended for |
|
|
//| large-scale problems. |
|
|
//| This function overwrites linear (non-box) constraints set by |
|
|
//| previous calls (if such calls were made). |
|
|
//| This function may be useful if constraint matrix includes large |
|
|
//| number of both types of rows - dense and CSparse If you have just|
|
|
//| a few sparse rows, you may represent them in dense format without|
|
|
//| losing performance. Similarly, if you have just a few dense rows,|
|
|
//| you may store them in sparse format with almost same performance.|
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinQPCreate() |
|
|
//| call. |
|
|
//| SparseA - sparse matrix with size [K, N](exactly!). Each row |
|
|
//| of A represents one general linear constraint. A |
|
|
//| can be stored in any sparse storage format. |
|
|
//| SparseK - number of sparse constraints, SparseK >= 0 |
|
|
//| DenseA - linear constraints, array[K, N], set of dense |
|
|
//| constraints. Each row of A represents one general |
|
|
//| linear constraint. |
|
|
//| DenseK - number of dense constraints, DenseK >= 0 |
|
|
//| AL, AU - lower and upper bounds, array[SparseK + DenseK], |
|
|
//| with former SparseK elements corresponding to |
|
|
//| sparse constraints, and latter DenseK elements |
|
|
//| corresponding to dense constraints; |
|
|
//| * AL[i] = AU[i] => equality constraint Ai*x |
|
|
//| * AL[i]<AU.Set(i,> two-sided constraint |
|
|
//| AL[i] <= Ai*x <= AU[i] |
|
|
//| * AL[i] = -INF => one-sided constraint |
|
|
//| Ai*x <= AU[i] |
|
|
//| * AU[i] = +INF => one-sided constraint |
|
|
//| AL[i] <= Ai*x |
|
|
//| * AL[i] = -INF, AU[i] = +INF => constraint is |
|
|
//| ignored |
|
|
//| K - number of equality/inequality constraints, K >= 0.|
|
|
//| If K = 0 is specified, A, AL, AU are ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLC2Mixed(CMinQPState &State,CSparseMatrix &sparsea,
|
|
int ksparse,CMatrixDouble &densea,
|
|
int kdense,CRowDouble &al,CRowDouble &au)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=kdense+ksparse;
|
|
//-- Check input arguments
|
|
if(!CAp::Assert(ksparse>=0,__FUNCTION__+": KSparse<0"))
|
|
return;
|
|
if(!CAp::Assert(ksparse==0 || CSparse::SparseGetNCols(sparsea)==n,__FUNCTION__+": Cols(SparseA)<>N"))
|
|
return;
|
|
if(!CAp::Assert(ksparse==0 || CSparse::SparseGetNRows(sparsea)==ksparse,__FUNCTION__+": Rows(SparseA)<>K"))
|
|
return;
|
|
if(!CAp::Assert(kdense>=0,__FUNCTION__+": KDense<0"))
|
|
return;
|
|
if(!CAp::Assert(kdense==0 || CAp::Cols(densea)>=n,__FUNCTION__+": Cols(DenseA)<N"))
|
|
return;
|
|
if(!CAp::Assert(kdense==0 || CAp::Rows(densea)>=kdense,__FUNCTION__+": Rows(DenseA)<K"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(densea,kdense,n),__FUNCTION__+": DenseA contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(al)>=kdense+ksparse,__FUNCTION__+": Length(AL)<K"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(au)>=kdense+ksparse,__FUNCTION__+": Length(AU)<K"))
|
|
return;
|
|
|
|
for(int i=0; i<m; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(al[i]) || IsNegInf(al[i]),__FUNCTION__+": AL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(au[i]) || IsPosInf(au[i]),__FUNCTION__+": AU contains NAN or -INF"))
|
|
return;
|
|
}
|
|
//-- Allocate place for Lagrange multipliers, fill by zero
|
|
State.m_replaglc=vector<double>::Zeros(kdense+ksparse);
|
|
//-- Quick exit if needed
|
|
if(m==0)
|
|
{
|
|
State.m_mdense=0;
|
|
State.m_msparse=0;
|
|
return;
|
|
}
|
|
//-- Prepare
|
|
State.m_cl=al;
|
|
State.m_cu=au;
|
|
State.m_cl.Resize(m);
|
|
State.m_cu.Resize(m);
|
|
State.m_mdense=kdense;
|
|
State.m_msparse=ksparse;
|
|
//-- Copy dense and sparse terms
|
|
if(ksparse>0)
|
|
{
|
|
CSparse::SparseCopyToCRSBuf(sparsea,State.m_sparsec);
|
|
}
|
|
if(kdense>0)
|
|
{
|
|
State.m_densec=densea;
|
|
State.m_densec.Resize(kdense,n);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function appends two-sided linear constraint AL <= A*x <= AU|
|
|
//| to the matrix of currently present dense constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinQPCreate() |
|
|
//| call. |
|
|
//| A - linear constraint coefficient, array[N], right side|
|
|
//| is NOT included. |
|
|
//| AL, AU - lower and upper bounds; |
|
|
//| * AL = AU => equality constraint Ai*x |
|
|
//| * AL < AU => two-sided constraint |
|
|
//| AL <= Ai*x <= AU |
|
|
//| * AL = -INF => one-sided constraint |
|
|
//| Ai*x <= AU |
|
|
//| * AU = +INF => one-sided constraint |
|
|
//| AL <= Ai*x |
|
|
//| * AL = -INF, AU = +INF => constraint is |
|
|
//| ignored |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPAddLC2Dense(CMinQPState &State,
|
|
CRowDouble &a,
|
|
double al,
|
|
double au)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(a)>=n,__FUNCTION__+": Length(A)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(a,n),__FUNCTION__+": A contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(al) || IsNegInf(al),__FUNCTION__+": AL is NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(au) || IsPosInf(au),__FUNCTION__+": AU is NAN or -INF"))
|
|
return;
|
|
|
|
State.m_cl.Resize(State.m_msparse+State.m_mdense+1);
|
|
State.m_cu.Resize(State.m_msparse+State.m_mdense+1);
|
|
State.m_replaglc.Resize(State.m_msparse+State.m_mdense+1);
|
|
State.m_densec.Resize(State.m_mdense+1,n);
|
|
State.m_densec.Row(State.m_mdense,a);
|
|
State.m_cl.Set(State.m_msparse+State.m_mdense,al);
|
|
State.m_cu.Set(State.m_msparse+State.m_mdense,au);
|
|
State.m_replaglc.Set(State.m_msparse+State.m_mdense,0.0);
|
|
State.m_mdense++;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function appends two-sided linear constraint AL <= A*x <= AU|
|
|
//| to the list of currently present sparse constraints. |
|
|
//| Constraint is passed in compressed format: as list of non-zero |
|
|
//| entries of coefficient vector A. Such approach is more efficient |
|
|
//| than dense storage for highly sparse constraint vectors. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinQPCreate() |
|
|
//| call. |
|
|
//| IdxA - array[NNZ], indexes of non-zero elements of A: |
|
|
//| * can be unsorted |
|
|
//| * can include duplicate indexes (corresponding |
|
|
//| entries of ValA[] will be summed) |
|
|
//| ValA - array[NNZ], values of non-zero elements of A |
|
|
//| NNZ - number of non-zero coefficients in A |
|
|
//| AL, AU - lower and upper bounds; |
|
|
//| * AL = AU => equality constraint A*x |
|
|
//| * AL<AU => two-sided constraint AL <= A*x <= AU |
|
|
//| * AL = -INF => one-sided constraint A*x <= AU |
|
|
//| * AU = +INF => one-sided constraint AL <= A*x |
|
|
//| * AL = -INF, AU = +INF => constraint is ignored |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPAddLC2(CMinQPState &State,CRowInt &idxa,
|
|
CRowDouble &vala,int nnz,double al,
|
|
double au)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int offs=0;
|
|
int offsdst=0;
|
|
int didx=0;
|
|
int uidx=0;
|
|
//-- Check inputs
|
|
if(!CAp::Assert(nnz>=0,__FUNCTION__+": NNZ<0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(idxa)>=nnz,__FUNCTION__+": Length(IdxA)<NNZ"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(vala)>=nnz,__FUNCTION__+": Length(ValA)<NNZ"))
|
|
return;
|
|
for(i=0; i<nnz; i++)
|
|
if(!CAp::Assert(idxa[i]>=0 && idxa[i]<n,__FUNCTION__+": IdxA contains indexes outside of [0,N) range"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(vala,nnz),__FUNCTION__+": ValA contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(al) || IsNegInf(al),__FUNCTION__+": AL is NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(au) || IsPosInf(au),__FUNCTION__+": AU is NAN or -INF"))
|
|
return;
|
|
//-- If M=0, it means that A is uninitialized.
|
|
//-- Prepare sparse matrix structure
|
|
if(State.m_msparse==0)
|
|
{
|
|
State.m_sparsec.m_MatrixType=1;
|
|
State.m_sparsec.m_M=0;
|
|
State.m_sparsec.m_N=n;
|
|
State.m_sparsec.m_NInitialized=0;
|
|
State.m_sparsec.m_RIdx.Resize(1);
|
|
State.m_sparsec.m_RIdx.Set(0,0);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_sparsec.m_MatrixType==1 && State.m_sparsec.m_M==State.m_msparse,__FUNCTION__+": integrity check failed!"))
|
|
return;
|
|
//-- Reallocate inequality bounds
|
|
State.m_cl.Resize(State.m_msparse+State.m_mdense+1);
|
|
State.m_cu.Resize(State.m_msparse+State.m_mdense+1);
|
|
State.m_replaglc.Resize(State.m_msparse+State.m_mdense+1);
|
|
for(i=State.m_msparse+State.m_mdense; i>=State.m_msparse+1; i--)
|
|
{
|
|
State.m_cl.Set(i,State.m_cl[i-1]);
|
|
State.m_cu.Set(i,State.m_cu[i-1]);
|
|
State.m_replaglc.Set(i,State.m_replaglc[i-1]);
|
|
}
|
|
State.m_cl.Set(State.m_msparse,al);
|
|
State.m_cu.Set(State.m_msparse,au);
|
|
State.m_replaglc.Set(State.m_msparse,0.0);
|
|
//-- Reallocate sparse storage
|
|
offs=State.m_sparsec.m_RIdx[State.m_msparse];
|
|
State.m_sparsec.m_Idx.Resize(offs+nnz);
|
|
State.m_sparsec.m_Vals.Resize(offs+nnz);
|
|
State.m_sparsec.m_DIdx.Resize(State.m_msparse+1);
|
|
State.m_sparsec.m_UIdx.Resize(State.m_msparse+1);
|
|
State.m_sparsec.m_RIdx.Resize(State.m_msparse+2);
|
|
//-- If NNZ=0, perform quick and simple row append.
|
|
if(nnz==0)
|
|
{
|
|
State.m_sparsec.m_DIdx.Set(State.m_msparse,State.m_sparsec.m_RIdx[State.m_msparse]);
|
|
State.m_sparsec.m_UIdx.Set(State.m_msparse,State.m_sparsec.m_RIdx[State.m_msparse]);
|
|
State.m_sparsec.m_RIdx.Set(State.m_msparse+1,State.m_sparsec.m_RIdx[State.m_msparse]);
|
|
State.m_sparsec.m_M++;
|
|
State.m_msparse++;
|
|
return;
|
|
}
|
|
//-- Now we are sure that SparseC contains properly initialized sparse
|
|
//-- matrix (or some appropriate dummy for M=0) and we have NNZ>0
|
|
//-- (no need to care about degenerate cases).
|
|
//-- Append rows to SparseC:
|
|
//-- * append data
|
|
//-- * sort in place
|
|
//-- * merge duplicate indexes
|
|
//-- * compute DIdx and UIdx
|
|
for(i=0; i<nnz; i++)
|
|
{
|
|
State.m_sparsec.m_Idx.Set(offs+i,idxa[i]);
|
|
State.m_sparsec.m_Vals.Set(offs+i,vala[i]);
|
|
}
|
|
CTSort::TagSortMiddleIR(State.m_sparsec.m_Idx,State.m_sparsec.m_Vals,offs,nnz);
|
|
offsdst=offs;
|
|
for(i=1; i<nnz; i++)
|
|
{
|
|
if(State.m_sparsec.m_Idx[offsdst]!=State.m_sparsec.m_Idx[offs+i])
|
|
{
|
|
offsdst++;
|
|
State.m_sparsec.m_Idx.Set(offsdst,State.m_sparsec.m_Idx[offs+i]);
|
|
State.m_sparsec.m_Vals.Set(offsdst,State.m_sparsec.m_Vals[offs+i]);
|
|
}
|
|
else
|
|
State.m_sparsec.m_Vals.Add(offsdst,State.m_sparsec.m_Vals[offs+i]);
|
|
}
|
|
nnz=offsdst-offs+1;
|
|
uidx=-1;
|
|
didx=-1;
|
|
for(j=offs; j<=offsdst; j++)
|
|
{
|
|
k=State.m_sparsec.m_Idx[j];
|
|
if(k==State.m_msparse)
|
|
didx=j;
|
|
else
|
|
if(k>State.m_msparse && uidx==-1)
|
|
{
|
|
uidx=j;
|
|
break;
|
|
}
|
|
}
|
|
if(uidx==-1)
|
|
uidx=offsdst+1;
|
|
if(didx==-1)
|
|
didx=uidx;
|
|
State.m_sparsec.m_DIdx.Set(State.m_msparse,didx);
|
|
State.m_sparsec.m_UIdx.Set(State.m_msparse,uidx);
|
|
State.m_sparsec.m_RIdx.Set(State.m_msparse+1,offsdst+1);
|
|
State.m_sparsec.m_NInitialized=State.m_sparsec.m_RIdx[State.m_msparse+1];
|
|
State.m_sparsec.m_M++;
|
|
State.m_msparse++;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function appends two-sided linear constraint AL <= A*x <= AU|
|
|
//| to the list of currently present sparse constraints. |
|
|
//| Constraint vector A is passed as a dense array which is |
|
|
//| internally sparsified by this function. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinQPCreate() |
|
|
//| call. |
|
|
//| DA - array[N], constraint vector |
|
|
//| AL, AU - lower and upper bounds; |
|
|
//| * AL = AU => equality constraint A*x |
|
|
//| * AL < AU => two-sided constraint AL<=A*x<=AU |
|
|
//| * AL = -INF => one-sided constraint A*x <= AU |
|
|
//| * AU = +INF => one-sided constraint AL <= A*x |
|
|
//| * AL = -INF, AU = +INF => constraint is ignored |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPAddLC2SparseFromDense(CMinQPState &State,
|
|
CRowDouble &da,
|
|
double al,
|
|
double au)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int nzi=0;
|
|
int offs=0;
|
|
int nnz=0;
|
|
int didx=0;
|
|
int uidx=0;
|
|
//-- Check inputs
|
|
if(!CAp::Assert(CAp::Len(da)>=n,__FUNCTION__+": Length(DA)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(da,n),__FUNCTION__+": DA contains infinities/NANs"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(al) || IsNegInf(al),__FUNCTION__+": AL is NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(au) || IsPosInf(au),__FUNCTION__+": AU is NAN or -INF"))
|
|
return;
|
|
//-- If M=0, it means that A is uninitialized.
|
|
//-- Prepare sparse matrix structure
|
|
if(State.m_msparse==0)
|
|
{
|
|
State.m_sparsec.m_MatrixType=1;
|
|
State.m_sparsec.m_M=0;
|
|
State.m_sparsec.m_N=n;
|
|
State.m_sparsec.m_NInitialized=0;
|
|
State.m_sparsec.m_RIdx.Resize(1);
|
|
State.m_sparsec.m_RIdx.Set(0,0);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_sparsec.m_MatrixType==1 && State.m_sparsec.m_M==State.m_msparse,__FUNCTION__+": integrity check failed!"))
|
|
return;
|
|
//-- Reallocate inequality bounds
|
|
State.m_cl.Resize(State.m_msparse+State.m_mdense+1);
|
|
State.m_cu.Resize(State.m_msparse+State.m_mdense+1);
|
|
State.m_replaglc.Resize(State.m_msparse+State.m_mdense+1);
|
|
for(i=State.m_msparse+State.m_mdense; i>=State.m_msparse+1; i--)
|
|
{
|
|
State.m_cl.Set(i,State.m_cl[i-1]);
|
|
State.m_cu.Set(i,State.m_cu[i-1]);
|
|
State.m_replaglc.Set(i,State.m_replaglc[i-1]);
|
|
}
|
|
State.m_cl.Set(State.m_msparse,al);
|
|
State.m_cu.Set(State.m_msparse,au);
|
|
State.m_replaglc.Set(State.m_msparse,0.0);
|
|
//-- Determine nonzeros count.
|
|
//-- Reallocate sparse storage.
|
|
nnz=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!(da[i]==0.0))
|
|
nnz++;
|
|
}
|
|
offs=State.m_sparsec.m_RIdx[State.m_msparse];
|
|
State.m_sparsec.m_Idx.Resize(offs+nnz);
|
|
State.m_sparsec.m_Vals.Resize(offs+nnz);
|
|
State.m_sparsec.m_DIdx.Resize(State.m_msparse+1);
|
|
State.m_sparsec.m_UIdx.Resize(State.m_msparse+1);
|
|
State.m_sparsec.m_RIdx.Resize(State.m_msparse+2);
|
|
//-- If NNZ=0, perform quick and simple row append.
|
|
if(nnz==0)
|
|
{
|
|
State.m_sparsec.m_DIdx.Set(State.m_msparse,State.m_sparsec.m_RIdx[State.m_msparse]);
|
|
State.m_sparsec.m_UIdx.Set(State.m_msparse,State.m_sparsec.m_RIdx[State.m_msparse]);
|
|
State.m_sparsec.m_RIdx.Set(State.m_msparse+1,State.m_sparsec.m_RIdx[State.m_msparse]);
|
|
State.m_sparsec.m_M++;
|
|
State.m_msparse++;
|
|
return;
|
|
}
|
|
//-- Now we are sure that SparseC contains properly initialized sparse
|
|
//-- matrix (or some appropriate dummy for M=0) and we have NNZ>0
|
|
//-- (no need to care about degenerate cases).
|
|
//-- Append rows to SparseC:
|
|
//-- * append data
|
|
//-- * compute DIdx and UIdx
|
|
nzi=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!(da[i]==0.0))
|
|
{
|
|
State.m_sparsec.m_Idx.Set(offs+nzi,i);
|
|
State.m_sparsec.m_Vals.Set(offs+nzi,da[i]);
|
|
nzi++;
|
|
}
|
|
}
|
|
uidx=-1;
|
|
didx=-1;
|
|
for(j=offs; j<=offs+nnz-1; j++)
|
|
{
|
|
k=State.m_sparsec.m_Idx[j];
|
|
if(k==State.m_msparse)
|
|
didx=j;
|
|
else
|
|
{
|
|
if(k>State.m_msparse && uidx==-1)
|
|
{
|
|
uidx=j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(uidx==-1)
|
|
{
|
|
uidx=offs+nnz;
|
|
}
|
|
if(didx==-1)
|
|
{
|
|
didx=uidx;
|
|
}
|
|
State.m_sparsec.m_DIdx.Set(State.m_msparse,didx);
|
|
State.m_sparsec.m_UIdx.Set(State.m_msparse,uidx);
|
|
State.m_sparsec.m_RIdx.Set(State.m_msparse+1,offs+nnz);
|
|
State.m_sparsec.m_NInitialized=State.m_sparsec.m_RIdx[State.m_msparse+1];
|
|
State.m_sparsec.m_M++;
|
|
State.m_msparse++;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves quadratic programming problem. |
|
|
//| You should call it after setting m_solver options with |
|
|
//| MinQPSet...() calls. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| You should use MinQPResults() function to access results after |
|
|
//| calls to this function. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPOptimize(CMinQPState &State)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int k=0;
|
|
int nbc=0;
|
|
int nlc=0;
|
|
int nactive=0;
|
|
int nfree=0;
|
|
int neq=0;
|
|
int nineq=0;
|
|
int curecpos=0;
|
|
int curicpos=neq;
|
|
double f=0;
|
|
double fprev=0;
|
|
double v=0;
|
|
double x=0;
|
|
int i_=0;
|
|
//--- initialization
|
|
int n=State.m_n;
|
|
int m=State.m_mdense+State.m_msparse;
|
|
State.m_repterminationtype=-5;
|
|
State.m_repinneriterationscount=0;
|
|
State.m_repouteriterationscount=0;
|
|
State.m_repncholesky=0;
|
|
State.m_repnmv=0;
|
|
//-- Zero-fill Lagrange multipliers (their initial value)
|
|
State.m_replagbc=vector<double>::Zeros(n);
|
|
State.m_replaglc=vector<double>::Zeros(m);
|
|
//-- Initial point:
|
|
//-- * if we have starting point in StartX, we just have to bound it
|
|
//-- * if we do not have StartX, deduce initial point from boundary constraints
|
|
if(State.m_havex)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
x=State.m_startx[i];
|
|
if(State.m_havebndl[i] && x<State.m_bndl[i])
|
|
State.m_xs.Set(i,State.m_bndl[i]);
|
|
else
|
|
if(State.m_havebndu[i] && x>State.m_bndu[i])
|
|
State.m_xs.Set(i,State.m_bndu[i]);
|
|
else
|
|
State.m_xs.Set(i,x);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_havebndl[i] && State.m_havebndu[i])
|
|
{
|
|
State.m_xs.Set(i,0.5*(State.m_bndl[i]+State.m_bndu[i]));
|
|
continue;
|
|
}
|
|
if(State.m_havebndl[i])
|
|
{
|
|
State.m_xs.Set(i,State.m_bndl[i]);
|
|
continue;
|
|
}
|
|
if(State.m_havebndu[i])
|
|
{
|
|
State.m_xs.Set(i,State.m_bndu[i]);
|
|
continue;
|
|
}
|
|
State.m_xs.Set(i,0);
|
|
}
|
|
}
|
|
//--- check correctness of constraints
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(State.m_havebndl[i] && State.m_havebndu[i])
|
|
{
|
|
//--- check
|
|
if(State.m_bndl[i]>State.m_bndu[i])
|
|
{
|
|
State.m_repterminationtype=-3;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//--- count number of bound and linear constraints
|
|
nbc=0;
|
|
nlc=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(State.m_havebndl[i])
|
|
nbc++;
|
|
//--- check
|
|
if(State.m_havebndu[i])
|
|
nbc++;
|
|
}
|
|
//-- Effective scale
|
|
State.m_effectives=vector<double>::Zeros(n);
|
|
switch(State.m_stype)
|
|
{
|
|
case 0:
|
|
//-- User scale (or default one)
|
|
State.m_effectives=State.m_s;
|
|
break;
|
|
case 1:
|
|
//-- Diagonal is used for scaling:
|
|
//-- * unpack
|
|
//-- * convert to scale, return error on failure
|
|
if(State.m_akind==0)
|
|
{
|
|
//-- Unpack CQM structure
|
|
CCQModels::CQMGetDiagA(State.m_a,State.m_effectives);
|
|
}
|
|
else
|
|
{
|
|
if(State.m_akind==1)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
State.m_effectives.Set(i,CSparse::SparseGet(State.m_sparsea,i,i));
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(false,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
}
|
|
}
|
|
//---
|
|
if(State.m_effectives.Min()<=0.0)
|
|
{
|
|
State.m_repterminationtype=-9;
|
|
return;
|
|
}
|
|
State.m_effectives=State.m_effectives.Pow(-0.5)+0;
|
|
break;
|
|
default:
|
|
//--- check
|
|
if(!CAp::Assert(false,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
}
|
|
//-- Solvers which can not handle new two-sided constraints need them to be
|
|
//-- converted into legacy equality/inequality one-sided format
|
|
if(State.m_algokind==2 || State.m_algokind==4)
|
|
{
|
|
//-- Scan constraint left/right sides, count equality ones and one/two-sided inequality ones
|
|
neq=0;
|
|
nineq=0;
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(MathIsValidNumber(State.m_cl[i]) && MathIsValidNumber(State.m_cu[i]) && State.m_cl[i]==State.m_cu[i])
|
|
{
|
|
neq++;
|
|
continue;
|
|
}
|
|
if(MathIsValidNumber(State.m_cl[i]))
|
|
nineq++;
|
|
if(MathIsValidNumber(State.m_cu[i]))
|
|
nineq++;
|
|
}
|
|
//-- Perform conversion
|
|
State.m_ecleic=matrix<double>::Zeros(neq+nineq,n+1);
|
|
State.m_elagmlt=vector<double>::Zeros(neq+nineq);
|
|
State.m_elagidx.Resize(neq+nineq);
|
|
State.m_elagidx.Fill(0);
|
|
curecpos=0;
|
|
curicpos=neq;
|
|
for(i=0; i<m ; i++)
|
|
{
|
|
if(MathIsValidNumber(State.m_cl[i]) && MathIsValidNumber(State.m_cu[i]) && State.m_cl[i]==State.m_cu[i])
|
|
{
|
|
//-- Offload equality constraint
|
|
if(i<State.m_msparse)
|
|
{
|
|
State.m_ecleic.Row(curecpos,vector<double>::Zeros(n+1));
|
|
j0=State.m_sparsec.m_RIdx[i];
|
|
j1=State.m_sparsec.m_RIdx[i+1]-1;
|
|
for(j=j0; j<=j1; j++)
|
|
State.m_ecleic.Set(curecpos,State.m_sparsec.m_Idx[j],State.m_sparsec.m_Vals[j]);
|
|
}
|
|
else
|
|
{
|
|
for(j=0; j<n; j++)
|
|
State.m_ecleic.Set(curecpos,j,State.m_densec.Get(i-State.m_msparse,j));
|
|
}
|
|
State.m_ecleic.Set(curecpos,n,State.m_cu[i]);
|
|
State.m_elagidx.Set(curecpos,i);
|
|
State.m_elagmlt.Set(curecpos,1.0);
|
|
curecpos++;
|
|
continue;
|
|
}
|
|
if(MathIsValidNumber(State.m_cl[i]))
|
|
{
|
|
//-- Offload inequality constraint of the form CL<=C*x, convert it to -C*x<=-CL
|
|
if(i<State.m_msparse)
|
|
{
|
|
State.m_ecleic.Row(curicpos,vector<double>::Zeros(n+1));
|
|
j0=State.m_sparsec.m_RIdx[i];
|
|
j1=State.m_sparsec.m_RIdx[i+1]-1;
|
|
for(j=j0; j<=j1; j++)
|
|
State.m_ecleic.Set(curicpos,State.m_sparsec.m_Idx[j],-State.m_sparsec.m_Vals[j]);
|
|
}
|
|
else
|
|
{
|
|
for(j=0; j<n; j++)
|
|
State.m_ecleic.Set(curicpos,j,-State.m_densec.Get(i-State.m_msparse,j));
|
|
}
|
|
State.m_ecleic.Set(curicpos,n,-State.m_cl[i]);
|
|
State.m_elagidx.Set(curicpos,i);
|
|
State.m_elagmlt.Set(curicpos,-1.0);
|
|
curicpos++;
|
|
}
|
|
if(MathIsValidNumber(State.m_cu[i]))
|
|
{
|
|
//-- Offload inequality constraint of the form C*x<=CU
|
|
if(i<State.m_msparse)
|
|
{
|
|
State.m_ecleic.Row(curicpos,vector<double>::Zeros(n+1));
|
|
j0=State.m_sparsec.m_RIdx[i];
|
|
j1=State.m_sparsec.m_RIdx[i+1]-1;
|
|
for(j=j0; j<=j1; j++)
|
|
State.m_ecleic.Set(curicpos,State.m_sparsec.m_Idx[j],State.m_sparsec.m_Vals[j]);
|
|
}
|
|
else
|
|
{
|
|
for(j=0; j<n; j++)
|
|
State.m_ecleic.Set(curicpos,j,State.m_densec.Get(i-State.m_msparse,j));
|
|
}
|
|
State.m_ecleic.Set(curicpos,n,State.m_cu[i]);
|
|
State.m_elagidx.Set(curicpos,i);
|
|
State.m_elagmlt.Set(curicpos,1.0);
|
|
curicpos++;
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(curecpos==neq && curicpos==neq+nineq,__FUNCTION__+": critical integrity check failed (ECLEIC conversion)"))
|
|
return;
|
|
//-- Run solvers
|
|
if(State.m_algokind==2)
|
|
{
|
|
CQPBLEICSolver::QPBLEICOptimize(State.m_a,State.m_sparsea,State.m_akind,State.m_sparseaupper,State.m_absasum,State.m_absasum2,State.m_b,State.m_bndl,State.m_bndu,State.m_effectives,State.m_xorigin,n,State.m_ecleic,neq,nineq,State.m_qpbleicsettingsuser,State.m_qpbleicbuf,State.m_qpbleicfirstcall,State.m_xs,State.m_repterminationtype);
|
|
State.m_repinneriterationscount=State.m_qpbleicbuf.m_repinneriterationscount;
|
|
State.m_repouteriterationscount=State.m_qpbleicbuf.m_repouteriterationscount;
|
|
return;
|
|
}
|
|
if(State.m_algokind==4)
|
|
{
|
|
CQPDenseAULSolver::QPDenseAULOptimize(State.m_a,State.m_sparsea,State.m_akind,State.m_sparseaupper,State.m_b,State.m_bndl,State.m_bndu,State.m_effectives,State.m_xorigin,n,State.m_ecleic,neq,nineq,State.m_dummysparse,0,0,!State.m_dbgskipconstraintnormalization,State.m_qpdenseaulsettingsuser,State.m_qpdenseaulbuf,State.m_xs,State.m_replagbc,State.m_elaglc,State.m_repterminationtype);
|
|
for(i=0; i<neq+nineq; i++)
|
|
State.m_replaglc.Add(State.m_elagidx[i],State.m_elaglc[i]*State.m_elagmlt[i]);
|
|
State.m_repinneriterationscount=State.m_qpdenseaulbuf.m_repinneriterationscount;
|
|
State.m_repouteriterationscount=State.m_qpdenseaulbuf.m_repouteriterationscount;
|
|
State.m_repncholesky=State.m_qpdenseaulbuf.m_repncholesky;
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(false,__FUNCTION__+": integrity check failed - unknown solver"))
|
|
return;
|
|
}
|
|
//-- QuickQP solver
|
|
if(State.m_algokind==3)
|
|
{
|
|
if(State.m_mdense+State.m_msparse>0)
|
|
{
|
|
State.m_repterminationtype=-5;
|
|
return;
|
|
}
|
|
CQQPSolver::QQPOptimize(State.m_a,State.m_sparsea,State.m_dummyr2,State.m_akind,State.m_sparseaupper,State.m_b,State.m_bndl,State.m_bndu,State.m_effectives,State.m_xorigin,n,State.m_qqpsettingsuser,State.m_qqpbuf,State.m_xs,State.m_repterminationtype);
|
|
State.m_repinneriterationscount=State.m_qqpbuf.m_repinneriterationscount;
|
|
State.m_repouteriterationscount=State.m_qqpbuf.m_repouteriterationscount;
|
|
State.m_repncholesky=State.m_qqpbuf.m_repncholesky;
|
|
return;
|
|
}
|
|
//-- QP-DENSE-IPM and QP-SPARSE-IPM solvers
|
|
if(State.m_algokind==5 || State.m_algokind==6)
|
|
{
|
|
//-- Prepare working versions of constraints; these versions may be modified
|
|
//-- when we detect that some bounds are irrelevant.
|
|
State.m_wrkbndl=State.m_bndl;
|
|
State.m_wrkbndu=State.m_bndu;
|
|
if(State.m_msparse>0)
|
|
CSparse::SparseCopyBuf(State.m_sparsec,State.m_wrksparsec);
|
|
if(State.m_mdense>0)
|
|
CAblasF::RCopyAllocM(State.m_mdense,n,State.m_densec,State.m_wrkdensec);
|
|
CAblasF::RCopyAllocV(m,State.m_cl,State.m_wrkcl);
|
|
CAblasF::RCopyAllocV(m,State.m_cu,State.m_wrkcu);
|
|
//-- Solve
|
|
//--- check
|
|
if(!CAp::Assert(State.m_akind==0 || State.m_akind==1,__FUNCTION__+": unexpected AKind"))
|
|
return;
|
|
if(State.m_akind==0)
|
|
CCQModels::CQMGetA(State.m_a,State.m_tmpr2);
|
|
if(State.m_algokind==5)
|
|
CVIPMSolver::VIPMInitDense(State.m_vsolver,State.m_effectives,State.m_xorigin,n);
|
|
if(State.m_algokind==6)
|
|
CVIPMSolver::VIPMInitSparse(State.m_vsolver,State.m_effectives,State.m_xorigin,n);
|
|
CVIPMSolver::VIPMSetQuadraticLinear(State.m_vsolver,State.m_tmpr2,State.m_sparsea,State.m_akind,State.m_sparseaupper,State.m_b);
|
|
CVIPMSolver::VIPMSetConstraints(State.m_vsolver,State.m_wrkbndl,State.m_wrkbndu,State.m_wrksparsec,State.m_msparse,State.m_wrkdensec,State.m_mdense,State.m_wrkcl,State.m_wrkcu);
|
|
CVIPMSolver::VIPMSetCond(State.m_vsolver,State.m_veps,State.m_veps,State.m_veps);
|
|
CVIPMSolver::VIPMOptimize(State.m_vsolver,true,State.m_xs,State.m_replagbc,State.m_replaglc,State.m_repterminationtype);
|
|
State.m_repinneriterationscount=State.m_vsolver.m_repiterationscount;
|
|
State.m_repouteriterationscount=State.m_repinneriterationscount;
|
|
State.m_repncholesky=State.m_vsolver.m_repncholesky;
|
|
return;
|
|
}
|
|
//-- Integrity check failed - unknown solver
|
|
if(!CAp::Assert(false,__FUNCTION__+": integrity check failed - unknown solver"))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| QP solver results |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[0..m_n-1], solution |
|
|
//| Rep - optimization report. You should check Rep. |
|
|
//| TerminationType, which contains completion code, |
|
|
//| and you may check another fields which contain |
|
|
//| another information about algorithm functioning. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPResults(CMinQPState &State,double &x[],CMinQPReport &rep)
|
|
{
|
|
//--- reset memory
|
|
ArrayResize(x,0);
|
|
//--- function call
|
|
MinQPResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPResults(CMinQPState &State,CRowDouble &x,CMinQPReport &rep)
|
|
{
|
|
//--- reset memory
|
|
x.Resize(0);
|
|
//--- function call
|
|
MinQPResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| QP results |
|
|
//| Buffered implementation of MinQPResults() which uses |
|
|
//| pre-allocated buffer to store X[]. If buffer size is too small, |
|
|
//| it resizes buffer. It is intended to be used in the inner cycles |
|
|
//| of performance critical algorithms where array reallocation |
|
|
//| penalty is too large to be ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPResultsBuf(CMinQPState &State,double &x[],
|
|
CMinQPReport &rep)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_xs.Size()>=State.m_n,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(State.m_replagbc.Size()>=State.m_n,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(State.m_replaglc.Size()>=State.m_mdense+State.m_msparse,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- copy
|
|
State.m_xs.ToArray(x);
|
|
rep.m_lagbc=State.m_replagbc;
|
|
rep.m_laglc=State.m_replaglc;
|
|
|
|
ArrayResize(x,State.m_n);
|
|
rep.m_lagbc.Resize(State.m_n);
|
|
rep.m_laglc.Resize(State.m_mdense+State.m_msparse);
|
|
//--- change values
|
|
rep.m_inneriterationscount=State.m_repinneriterationscount;
|
|
rep.m_outeriterationscount=State.m_repouteriterationscount;
|
|
rep.m_nmv=State.m_repnmv;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPResultsBuf(CMinQPState &State,CRowDouble &x,
|
|
CMinQPReport &rep)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_xs.Size()>=State.m_n,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(State.m_replagbc.Size()>=State.m_n,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(State.m_replaglc.Size()>=State.m_mdense+State.m_msparse,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- copy
|
|
x=State.m_xs;
|
|
rep.m_lagbc=State.m_replagbc;
|
|
rep.m_laglc=State.m_replaglc;
|
|
|
|
x.Resize(State.m_n);
|
|
rep.m_lagbc.Resize(State.m_n);
|
|
rep.m_laglc.Resize(State.m_mdense+State.m_msparse);
|
|
//--- change values
|
|
rep.m_inneriterationscount=State.m_repinneriterationscount;
|
|
rep.m_outeriterationscount=State.m_repouteriterationscount;
|
|
rep.m_nmv=State.m_repnmv;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fast version of MinQPSetLinearTerm(), which doesn't check its |
|
|
//| arguments. For internal use only. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLinearTermFast(CMinQPState &State,double &b[])
|
|
{
|
|
//--- initialization
|
|
State.m_b=b;
|
|
State.m_b.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetLinearTermFast(CMinQPState &State,CRowDouble &b)
|
|
{
|
|
//--- initialization
|
|
State.m_b=b;
|
|
State.m_b.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fast version of MinQPSetQuadraticTerm(), which doesn't check its |
|
|
//| arguments. |
|
|
//| It accepts additional parameter - shift S, which allows to |
|
|
//| "shift" matrix A by adding s*I to A. S must be positive (although|
|
|
//| it is not checked). |
|
|
//| For internal use only. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetQuadraticTermFast(CMinQPState &State,CMatrixDouble &a,
|
|
const bool IsUpper,const double s)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
State.m_akind=0;
|
|
//--- function call
|
|
CCQModels::CQMSetA(State.m_a,a,IsUpper,1.0);
|
|
if(s>0.0)
|
|
{
|
|
State.m_tmp0=a.Diag()+s;
|
|
State.m_tmp0.Resize(n);
|
|
CCQModels::CQMRewriteDenseDiagonal(State.m_a,State.m_tmp0);
|
|
}
|
|
//-- Estimate norm of A
|
|
//-- (it will be used later in the quadratic penalty function)
|
|
State.m_absamax=0;
|
|
State.m_absasum=0;
|
|
State.m_absasum2=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(IsUpper)
|
|
{
|
|
j0=i;
|
|
j1=n-1;
|
|
}
|
|
else
|
|
{
|
|
j0=0;
|
|
j1=i;
|
|
}
|
|
for(j=j0; j<=j1; j++)
|
|
{
|
|
v=MathAbs(a.Get(i,j));
|
|
State.m_absamax=MathMax(State.m_absamax,v);
|
|
State.m_absasum=State.m_absasum+v;
|
|
State.m_absasum2=State.m_absasum2+v*v;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Interna lfunction which allows to rewrite diagonal of quadratic |
|
|
//| term. For internal use only. |
|
|
//| This function can be used only when you have dense A and already |
|
|
//| made MinQPSetQuadraticTerm(Fast) call. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPRewriteDiagonal(CMinQPState &State,double &s[])
|
|
{
|
|
CRowDouble S=s;
|
|
MinQPRewriteDiagonal(State,S);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPRewriteDiagonal(CMinQPState &State,CRowDouble &s)
|
|
{
|
|
CCQModels::CQMRewriteDenseDiagonal(State.m_a,s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fast version of MinQPSetStartingPoint(), which doesn't check its |
|
|
//| arguments. For internal use only. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetStartingPointFast(CMinQPState &State,double &x[])
|
|
{
|
|
//--- initialization
|
|
State.m_startx=x;
|
|
State.m_startx.Resize(State.m_n);
|
|
State.m_havex=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetStartingPointFast(CMinQPState &State,CRowDouble &x)
|
|
{
|
|
//--- initialization
|
|
State.m_startx=x;
|
|
State.m_startx.Resize(State.m_n);
|
|
State.m_havex=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fast version of MinQPSetOrigin(), which doesn't check its |
|
|
//| arguments. For internal use only. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetOriginFast(CMinQPState &State,double &xorigin[])
|
|
{
|
|
//--- initialization
|
|
State.m_xorigin=xorigin;
|
|
State.m_xorigin.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinQP::MinQPSetOriginFast(CMinQPState &State,CRowDouble &xorigin)
|
|
{
|
|
//--- initialization
|
|
State.m_xorigin=xorigin;
|
|
State.m_xorigin.Resize(State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Step finder for Levenberg-Marquardt optimizer. |
|
|
//| Internal object used by MinLM unit. |
|
|
//| This structure should be initialized with MinLMStepFinderInit(). |
|
|
//| Each step search session should start with MinLMStepFinderStart()|
|
|
//| call, followed by a sequence of MinLMStepFinderIteration() calls.|
|
|
//+------------------------------------------------------------------+
|
|
struct CMinLMStepFinder
|
|
{
|
|
int m_m;
|
|
int m_maxmodelage;
|
|
int m_modelage;
|
|
int m_n;
|
|
double m_actualdecrease;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_fbase;
|
|
double m_predicteddecrease;
|
|
double m_stpmax;
|
|
bool m_hasfi;
|
|
bool m_havebndl[];
|
|
bool m_havebndu[];
|
|
bool m_needf;
|
|
bool m_needfi;
|
|
RCommState m_rstate;
|
|
CSparseMatrix m_tmpsp;
|
|
CRowInt m_tmpct;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_choleskybuf;
|
|
CRowDouble m_fi;
|
|
CRowDouble m_fibase;
|
|
CRowDouble m_modeldiag;
|
|
CRowDouble m_s;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_x;
|
|
CRowDouble m_xbase;
|
|
CRowDouble m_xdir;
|
|
CMinQPState m_qpstate;
|
|
CMinQPReport m_qprep;
|
|
//--- constructor / destructor
|
|
CMinLMStepFinder(void);
|
|
~CMinLMStepFinder(void) {}
|
|
//---
|
|
void Copy(const CMinLMStepFinder &obj);
|
|
//--- overloading
|
|
void operator=(const CMinLMStepFinder &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinLMStepFinder::CMinLMStepFinder(void)
|
|
{
|
|
m_m=0;
|
|
m_maxmodelage=0;
|
|
m_modelage=0;
|
|
m_n=0;
|
|
m_actualdecrease=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_fbase=0;
|
|
m_predicteddecrease=0;
|
|
m_stpmax=0;
|
|
m_hasfi=false;
|
|
m_needf=false;
|
|
m_needfi=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMStepFinder::Copy(const CMinLMStepFinder &obj)
|
|
{
|
|
m_m=obj.m_m;
|
|
m_maxmodelage=obj.m_maxmodelage;
|
|
m_modelage=obj.m_modelage;
|
|
m_n=obj.m_n;
|
|
m_actualdecrease=obj.m_actualdecrease;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_fbase=obj.m_fbase;
|
|
m_predicteddecrease=obj.m_predicteddecrease;
|
|
m_stpmax=obj.m_stpmax;
|
|
m_hasfi=obj.m_hasfi;
|
|
ArrayCopy(m_havebndl,obj.m_havebndl);
|
|
ArrayCopy(m_havebndu,obj.m_havebndu);
|
|
m_needf=obj.m_needf;
|
|
m_needfi=obj.m_needfi;
|
|
m_rstate=obj.m_rstate;
|
|
m_tmpsp=obj.m_tmpsp;
|
|
m_tmpct=obj.m_tmpct;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_choleskybuf=obj.m_choleskybuf;
|
|
m_fi=obj.m_fi;
|
|
m_fibase=obj.m_fibase;
|
|
m_modeldiag=obj.m_modeldiag;
|
|
m_s=obj.m_s;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_x=obj.m_x;
|
|
m_xbase=obj.m_xbase;
|
|
m_xdir=obj.m_xdir;
|
|
m_qpstate=obj.m_qpstate;
|
|
m_qprep=obj.m_qprep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Levenberg-Marquardt optimizer. |
|
|
//| This structure should be created using one of the |
|
|
//| MinLMCreate() functions. You should not access its fields |
|
|
//| directly; use ALGLIB functions to work with it. |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLMState
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_algomode;
|
|
int m_m;
|
|
int m_maxits;
|
|
int m_maxmodelage;
|
|
int m_modelage;
|
|
int m_n;
|
|
int m_nec;
|
|
int m_nic;
|
|
int m_repiterationscount;
|
|
int m_repncholesky;
|
|
int m_repnfunc;
|
|
int m_repngrad;
|
|
int m_repnhess;
|
|
int m_repnjac;
|
|
int m_repterminationtype;
|
|
double m_actualdecrease;
|
|
double m_diffstep;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_fbase;
|
|
double m_lambdav;
|
|
double m_nu;
|
|
double m_predicteddecrease;
|
|
double m_stpmax;
|
|
double m_teststep;
|
|
double m_xm1;
|
|
double m_xp1;
|
|
bool m_deltafready;
|
|
bool m_deltaxready;
|
|
bool m_hasf;
|
|
bool m_hasfi;
|
|
bool m_hasg;
|
|
bool m_makeadditers;
|
|
bool m_needf;
|
|
bool m_needfg;
|
|
bool m_needfgh;
|
|
bool m_needfi;
|
|
bool m_needfij;
|
|
bool m_userterminationneeded;
|
|
bool m_xrep;
|
|
bool m_xupdated;
|
|
//--- objects
|
|
RCommState m_rstate;
|
|
CSmoothnessMonitor m_smonitor;
|
|
CMinQPState m_qpstate;
|
|
CMinQPReport m_qprep;
|
|
CMinLMStepFinder m_finderstate;
|
|
CMinLBFGSState m_internalstate;
|
|
CMinLBFGSReport m_internalrep;
|
|
//--- arrays
|
|
bool m_havebndl[];
|
|
bool m_havebndu[];
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_choleskybuf;
|
|
CRowDouble m_deltaf;
|
|
CRowDouble m_deltax;
|
|
CRowDouble m_fc1;
|
|
CRowDouble m_fi;
|
|
CRowDouble m_fibase;
|
|
CRowDouble m_fm1;
|
|
CRowDouble m_fp1;
|
|
CRowDouble m_g;
|
|
CRowDouble m_gbase;
|
|
CRowDouble m_gc1;
|
|
CRowDouble m_gm1;
|
|
CRowDouble m_gp1;
|
|
CRowDouble m_lastscaleused;
|
|
CRowDouble m_s;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_x;
|
|
CRowDouble m_xbase;
|
|
CRowDouble m_xdir;
|
|
CRowDouble m_xnew;
|
|
//--- matrix
|
|
CMatrixDouble m_cleic;
|
|
CMatrixDouble m_h;
|
|
CMatrixDouble m_j;
|
|
CMatrixDouble m_quadraticmodel;
|
|
//--- constructor, destructor
|
|
CMinLMState(void);
|
|
~CMinLMState(void) {}
|
|
//--- copy
|
|
void Copy(const CMinLMState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinLMState &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinLMState::CMinLMState(void)
|
|
{
|
|
m_algomode=0;
|
|
m_m=0;
|
|
m_maxits=0;
|
|
m_maxmodelage=0;
|
|
m_modelage=0;
|
|
m_n=0;
|
|
m_nec=0;
|
|
m_nic=0;
|
|
m_repiterationscount=0;
|
|
m_repncholesky=0;
|
|
m_repnfunc=0;
|
|
m_repngrad=0;
|
|
m_repnhess=0;
|
|
m_repnjac=0;
|
|
m_repterminationtype=0;
|
|
m_actualdecrease=0;
|
|
m_diffstep=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_fbase=0;
|
|
m_lambdav=0;
|
|
m_nu=0;
|
|
m_predicteddecrease=0;
|
|
m_stpmax=0;
|
|
m_teststep=0;
|
|
m_xm1=0;
|
|
m_xp1=0;
|
|
m_deltafready=false;
|
|
m_deltaxready=false;
|
|
m_hasf=false;
|
|
m_hasfi=false;
|
|
m_hasg=false;
|
|
m_makeadditers=false;
|
|
m_needf=false;
|
|
m_needfg=false;
|
|
m_needfgh=false;
|
|
m_needfi=false;
|
|
m_needfij=false;
|
|
m_userterminationneeded=false;
|
|
m_xrep=false;
|
|
m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMState::Copy(const CMinLMState &obj)
|
|
{
|
|
m_algomode=obj.m_algomode;
|
|
m_m=obj.m_m;
|
|
m_maxits=obj.m_maxits;
|
|
m_maxmodelage=obj.m_maxmodelage;
|
|
m_modelage=obj.m_modelage;
|
|
m_n=obj.m_n;
|
|
m_nec=obj.m_nec;
|
|
m_nic=obj.m_nic;
|
|
m_repiterationscount=obj.m_repiterationscount;
|
|
m_repncholesky=obj.m_repncholesky;
|
|
m_repnfunc=obj.m_repnfunc;
|
|
m_repngrad=obj.m_repngrad;
|
|
m_repnhess=obj.m_repnhess;
|
|
m_repnjac=obj.m_repnjac;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_actualdecrease=obj.m_actualdecrease;
|
|
m_diffstep=obj.m_diffstep;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_fbase=obj.m_fbase;
|
|
m_lambdav=obj.m_lambdav;
|
|
m_nu=obj.m_nu;
|
|
m_predicteddecrease=obj.m_predicteddecrease;
|
|
m_stpmax=obj.m_stpmax;
|
|
m_teststep=obj.m_teststep;
|
|
m_xm1=obj.m_xm1;
|
|
m_xp1=obj.m_xp1;
|
|
m_deltafready=obj.m_deltafready;
|
|
m_deltaxready=obj.m_deltaxready;
|
|
m_hasf=obj.m_hasf;
|
|
m_hasfi=obj.m_hasfi;
|
|
m_hasg=obj.m_hasg;
|
|
ArrayCopy(m_havebndl,obj.m_havebndl);
|
|
ArrayCopy(m_havebndu,obj.m_havebndu);
|
|
m_makeadditers=obj.m_makeadditers;
|
|
m_needf=obj.m_needf;
|
|
m_needfg=obj.m_needfg;
|
|
m_needfgh=obj.m_needfgh;
|
|
m_needfi=obj.m_needfi;
|
|
m_needfij=obj.m_needfij;
|
|
m_userterminationneeded=obj.m_userterminationneeded;
|
|
m_xrep=obj.m_xrep;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_rstate=obj.m_rstate;
|
|
m_smonitor=obj.m_smonitor;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_choleskybuf=obj.m_choleskybuf;
|
|
m_deltaf=obj.m_deltaf;
|
|
m_deltax=obj.m_deltax;
|
|
m_fc1=obj.m_fc1;
|
|
m_fi=obj.m_fi;
|
|
m_fibase=obj.m_fibase;
|
|
m_fm1=obj.m_fm1;
|
|
m_fp1=obj.m_fp1;
|
|
m_g=obj.m_g;
|
|
m_gbase=obj.m_gbase;
|
|
m_gc1=obj.m_gc1;
|
|
m_gm1=obj.m_gm1;
|
|
m_gp1=obj.m_gp1;
|
|
m_lastscaleused=obj.m_lastscaleused;
|
|
m_s=obj.m_s;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_x=obj.m_x;
|
|
m_xbase=obj.m_xbase;
|
|
m_xdir=obj.m_xdir;
|
|
m_xnew=obj.m_xnew;
|
|
m_qpstate=obj.m_qpstate;
|
|
m_qprep=obj.m_qprep;
|
|
m_finderstate=obj.m_finderstate;
|
|
m_internalstate=obj.m_internalstate;
|
|
m_internalrep=obj.m_internalrep;
|
|
m_cleic=obj.m_cleic;
|
|
m_h=obj.m_h;
|
|
m_j=obj.m_j;
|
|
m_quadraticmodel=obj.m_quadraticmodel;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Levenberg-Marquardt optimizer. |
|
|
//| This structure should be created using one of the |
|
|
//| MinLMCreate() functions. You should not access its fields |
|
|
//| directly; use ALGLIB functions to work with it. |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLMStateShell
|
|
{
|
|
private:
|
|
CMinLMState m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinLMStateShell(void) {}
|
|
CMinLMStateShell(CMinLMState &obj) { m_innerobj.Copy(obj); }
|
|
~CMinLMStateShell(void) {}
|
|
//--- methods
|
|
bool GetNeedF(void);
|
|
void SetNeedF(const bool b);
|
|
bool GetNeedFG(void);
|
|
void SetNeedFG(const bool b);
|
|
bool GetNeedFGH(void);
|
|
void SetNeedFGH(const bool b);
|
|
bool GetNeedFI(void);
|
|
void SetNeedFI(const bool b);
|
|
bool GetNeedFIJ(void);
|
|
void SetNeedFIJ(const bool b);
|
|
bool GetXUpdated(void);
|
|
void SetXUpdated(const bool b);
|
|
double GetF(void);
|
|
void SetF(const double d);
|
|
CMinLMState *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needf |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLMStateShell::GetNeedF(void)
|
|
{
|
|
return(m_innerobj.m_needf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needf |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMStateShell::SetNeedF(const bool b)
|
|
{
|
|
m_innerobj.m_needf=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLMStateShell::GetNeedFG(void)
|
|
{
|
|
return(m_innerobj.m_needfg);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMStateShell::SetNeedFG(const bool b)
|
|
{
|
|
m_innerobj.m_needfg=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needfgh |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLMStateShell::GetNeedFGH(void)
|
|
{
|
|
return(m_innerobj.m_needfgh);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needfgh |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMStateShell::SetNeedFGH(const bool b)
|
|
{
|
|
m_innerobj.m_needfgh=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needfi |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLMStateShell::GetNeedFI(void)
|
|
{
|
|
return(m_innerobj.m_needfi);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needfi |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMStateShell::SetNeedFI(const bool b)
|
|
{
|
|
m_innerobj.m_needfi=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needfij |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLMStateShell::GetNeedFIJ(void)
|
|
{
|
|
return(m_innerobj.m_needfij);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needfij |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMStateShell::SetNeedFIJ(const bool b)
|
|
{
|
|
m_innerobj.m_needfij=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLMStateShell::GetXUpdated(void)
|
|
{
|
|
return(m_innerobj.m_xupdated);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMStateShell::SetXUpdated(const bool b)
|
|
{
|
|
m_innerobj.m_xupdated=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
double CMinLMStateShell::GetF(void)
|
|
{
|
|
return(m_innerobj.m_f);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMStateShell::SetF(const double d)
|
|
{
|
|
m_innerobj.m_f=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinLMState* CMinLMStateShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Optimization report, filled by MinLMResults() function |
|
|
//| FIELDS: |
|
|
//| * TerminationType, completetion code: |
|
|
//| * -9 derivative correctness check failed; |
|
|
//| see Rep.WrongNum, Rep.WrongI, Rep.WrongJ for |
|
|
//| more information. |
|
|
//| * 1 relative function improvement is no more than |
|
|
//| EpsF. |
|
|
//| * 2 relative step is no more than EpsX. |
|
|
//| * 4 gradient is no more than EpsG. |
|
|
//| * 5 MaxIts steps was taken |
|
|
//| * 7 stopping conditions are too stringent, |
|
|
//| further improvement is impossible |
|
|
//| * IterationsCount, contains iterations count |
|
|
//| * NFunc, number of function calculations |
|
|
//| * NJac, number of Jacobi matrix calculations |
|
|
//| * NGrad, number of gradient calculations |
|
|
//| * NHess, number of Hessian calculations |
|
|
//| * NCholesky, number of Cholesky decomposition calculations |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLMReport
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_iterationscount;
|
|
int m_terminationtype;
|
|
int m_nfunc;
|
|
int m_njac;
|
|
int m_ngrad;
|
|
int m_nhess;
|
|
int m_ncholesky;
|
|
//--- constructor, destructor
|
|
CMinLMReport(void) { ZeroMemory(this); }
|
|
~CMinLMReport(void) {}
|
|
//--- copy
|
|
void Copy(const CMinLMReport &obj);
|
|
//--- overloading
|
|
void operator=(const CMinLMReport &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMReport::Copy(const CMinLMReport &obj)
|
|
{
|
|
//--- copy variables
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_nfunc=obj.m_nfunc;
|
|
m_njac=obj.m_njac;
|
|
m_ngrad=obj.m_ngrad;
|
|
m_nhess=obj.m_nhess;
|
|
m_ncholesky=obj.m_ncholesky;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Optimization report, filled by MinLMResults() function |
|
|
//| FIELDS: |
|
|
//| * TerminationType, completetion code: |
|
|
//| * -9 derivative correctness check failed; |
|
|
//| see Rep.WrongNum, Rep.WrongI, Rep.WrongJ for |
|
|
//| more information. |
|
|
//| * 1 relative function improvement is no more than |
|
|
//| EpsF. |
|
|
//| * 2 relative step is no more than EpsX. |
|
|
//| * 4 gradient is no more than EpsG. |
|
|
//| * 5 MaxIts steps was taken |
|
|
//| * 7 stopping conditions are too stringent, |
|
|
//| further improvement is impossible |
|
|
//| * IterationsCount, contains iterations count |
|
|
//| * NFunc, number of function calculations |
|
|
//| * NJac, number of Jacobi matrix calculations |
|
|
//| * NGrad, number of gradient calculations |
|
|
//| * NHess, number of Hessian calculations |
|
|
//| * NCholesky, number of Cholesky decomposition calculations |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLMReportShell
|
|
{
|
|
private:
|
|
CMinLMReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinLMReportShell(void) {}
|
|
CMinLMReportShell(CMinLMReport &obj) { m_innerobj.Copy(obj); }
|
|
~CMinLMReportShell(void) {}
|
|
//--- methods
|
|
int GetIterationsCount(void);
|
|
void SetIterationsCount(const int i);
|
|
int GetTerminationType(void);
|
|
void SetTerminationType(const int i);
|
|
int GetNFunc(void);
|
|
void SetNFunc(const int i);
|
|
int GetNJAC(void);
|
|
void SetNJAC(const int i);
|
|
int GetNGrad(void);
|
|
void SetNGrad(const int i);
|
|
int GetNHess(void);
|
|
void SetNHess(const int i);
|
|
int GetNCholesky(void);
|
|
void SetNCholesky(const int i);
|
|
CMinLMReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable iterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLMReportShell::GetIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_iterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable iterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMReportShell::SetIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_iterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLMReportShell::GetTerminationType(void)
|
|
{
|
|
return(m_innerobj.m_terminationtype);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMReportShell::SetTerminationType(const int i)
|
|
{
|
|
m_innerobj.m_terminationtype=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable nfunc |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLMReportShell::GetNFunc(void)
|
|
{
|
|
return(m_innerobj.m_nfunc);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable nfunc |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMReportShell::SetNFunc(const int i)
|
|
{
|
|
m_innerobj.m_nfunc=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable njac |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLMReportShell::GetNJAC(void)
|
|
{
|
|
return(m_innerobj.m_njac);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable njac |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMReportShell::SetNJAC(const int i)
|
|
{
|
|
m_innerobj.m_njac=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable ngrad |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLMReportShell::GetNGrad(void)
|
|
{
|
|
return(m_innerobj.m_ngrad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable ngrad |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMReportShell::SetNGrad(const int i)
|
|
{
|
|
m_innerobj.m_ngrad=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable nhess |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLMReportShell::GetNHess(void)
|
|
{
|
|
return(m_innerobj.m_nhess);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable nhess |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMReportShell::SetNHess(const int i)
|
|
{
|
|
m_innerobj.m_nhess=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable ncholesky |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLMReportShell::GetNCholesky(void)
|
|
{
|
|
return(m_innerobj.m_ncholesky);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable ncholesky |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLMReportShell::SetNCholesky(const int i)
|
|
{
|
|
m_innerobj.m_ncholesky=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinLMReport *CMinLMReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Levenberg-Marquardt method |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLM
|
|
{
|
|
public:
|
|
//--- class constants
|
|
static const double m_lambdaup;
|
|
static const double m_lambdadown;
|
|
static const double m_suspiciousnu;
|
|
static const int m_smallmodelage;
|
|
static const int m_additers;
|
|
//--- public methods
|
|
static void MinLMCreateVJ(const int n,const int m,double &x[],CMinLMState &State);
|
|
static void MinLMCreateVJ(const int n,const int m,CRowDouble &x,CMinLMState &State);
|
|
static void MinLMCreateV(const int n,const int m,double &x[],const double diffstep,CMinLMState &State);
|
|
static void MinLMCreateV(const int n,const int m,CRowDouble &x,const double diffstep,CMinLMState &State);
|
|
static void MinLMCreateFGH(const int n,double &x[],CMinLMState &State);
|
|
static void MinLMCreateFGH(const int n,CRowDouble &x,CMinLMState &State);
|
|
static void MinLMSetCond(CMinLMState &State,double epsx,const int m_maxits);
|
|
static void MinLMSetXRep(CMinLMState &State,const bool needxrep);
|
|
static void MinLMSetStpMax(CMinLMState &State,const double stpmax);
|
|
static void MinLMSetScale(CMinLMState &State,double &s[]);
|
|
static void MinLMSetScale(CMinLMState &State,CRowDouble &s);
|
|
static void MinLMSetBC(CMinLMState &State,double &bndl[],double &bndu[]);
|
|
static void MinLMSetBC(CMinLMState &State,CRowDouble &bndl,CRowDouble &bndu);
|
|
static void MinLMSetLC(CMinLMState &State,CMatrixDouble &c,CRowInt &ct,int k);
|
|
static void MinLMSetAccType(CMinLMState &State,int acctype);
|
|
static void MinLMOptGuardGradient(CMinLMState &State,double teststep);
|
|
static void MinLMOptGuardResults(CMinLMState &State,COptGuardReport &rep);
|
|
static void MinLMResults(CMinLMState &State,double &x[],CMinLMReport &rep);
|
|
static void MinLMResults(CMinLMState &State,CRowDouble &x,CMinLMReport &rep);
|
|
static void MinLMResultsBuf(CMinLMState &State,double &x[],CMinLMReport &rep);
|
|
static void MinLMResultsBuf(CMinLMState &State,CRowDouble &x,CMinLMReport &rep);
|
|
static void MinLMRestartFrom(CMinLMState &State,double &x[]);
|
|
static void MinLMRestartFrom(CMinLMState &State,CRowDouble &x);
|
|
static void MinLMRequestTermination(CMinLMState &State);
|
|
static void MinLMCreateVGJ(const int n,const int m,double &x[],CMinLMState &State);
|
|
static void MinLMCreateVGJ(const int n,const int m,CRowDouble &x,CMinLMState &State);
|
|
static void MinLMCreateFGJ(const int n,const int m,double &x[],CMinLMState &State);
|
|
static void MinLMCreateFGJ(const int n,const int m,CRowDouble &x,CMinLMState &State);
|
|
static void MinLMCreateFJ(const int n,const int m,double &x[],CMinLMState &State);
|
|
static void MinLMCreateFJ(const int n,const int m,CRowDouble &x,CMinLMState &State);
|
|
static bool MinLMIteration(CMinLMState &State);
|
|
|
|
private:
|
|
static void LMPRepare(const int n,const int m,bool havegrad,CMinLMState &State);
|
|
static void ClearRequestFields(CMinLMState &State);
|
|
static bool IncreaseLambda(double &lambdav,double &nu);
|
|
static void DecreaseLambda(double &lambdav,double &nu);
|
|
static int CheckDecrease(CMatrixDouble &quadraticmodel,CRowDouble &gbase,double fbase,int n,CRowDouble &deltax,double fnew,double &lambdav,double &nu);
|
|
static bool MinLMStepFinderInit(CMinLMStepFinder &State,int n,int m,int maxmodelage,bool hasfi,CRowDouble &xbase,CRowDouble &bndl,CRowDouble &bndu,CMatrixDouble &cleic,int nec,int nic,CRowDouble &s,double stpmax,double epsx);
|
|
static void MinLMStepFinderStart(CMinLMStepFinder &State,CMatrixDouble &quadraticmodel,CRowDouble &gbase,double fbase,CRowDouble &xbase,CRowDouble &fibase,int modelage);
|
|
static bool MinLMStepFinderIteration(CMinLMStepFinder &State,double &lambdav,double &nu,CRowDouble &xnew,CRowDouble &deltax,bool &deltaxready,CRowDouble &deltaf,bool &deltafready,int &iflag,double &fnew,int &ncholesky);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CMinLM::m_lambdaup=2.0;
|
|
const double CMinLM::m_lambdadown=0.33;
|
|
const double CMinLM::m_suspiciousnu=16;
|
|
const int CMinLM::m_smallmodelage=3;
|
|
const int CMinLM::m_additers=5;
|
|
//+------------------------------------------------------------------+
|
|
//| IMPROVED LEVENBERG-MARQUARDT METHOD FOR |
|
|
//| NON-LINEAR LEAST SQUARES OPTIMIZATION |
|
|
//| DESCRIPTION: |
|
|
//| This function is used to find minimum of function which is |
|
|
//| represented as sum of squares: |
|
|
//| F(x) = f[0]^2(x[0],...,x[n-1]) + ... + |
|
|
//| + f[m-1]^2(x[0],...,x[n-1]) |
|
|
//| using value of function vector f[] and Jacobian of f[]. |
|
|
//| REQUIREMENTS: |
|
|
//| This algorithm will request following information during its |
|
|
//| operation: |
|
|
//| * function vector f[] at given point X |
|
|
//| * function vector f[] and Jacobian of f[] (simultaneously) at |
|
|
//| given point |
|
|
//| There are several overloaded versions of MinLMOptimize() |
|
|
//| function which correspond to different LM-like optimization |
|
|
//| algorithms provided by this unit. You should choose version which|
|
|
//| accepts fvec() and jac() callbacks. First one is used to |
|
|
//| calculate f[] at given point, second one calculates f[] and |
|
|
//| Jacobian df[i]/dx[j]. |
|
|
//| You can try to initialize MinLMState structure with VJ function |
|
|
//| and then use incorrect version of MinLMOptimize() (for example,|
|
|
//| version which works with general form function and does not |
|
|
//| provide Jacobian), but it will lead to exception being thrown |
|
|
//| after first attempt to calculate Jacobian. |
|
|
//| USAGE: |
|
|
//| 1. User initializes algorithm State with MinLMCreateVJ() call |
|
|
//| 2. User tunes m_solver parameters with MinLMSetCond(), |
|
|
//| MinLMSetStpMax() and other functions |
|
|
//| 3. User calls MinLMOptimize() function which takes algorithm |
|
|
//| State and callback functions. |
|
|
//| 4. User calls MinLMResults() to get solution |
|
|
//| 5. Optionally, user may call MinLMRestartFrom() to solve another |
|
|
//| problem with same N/M but another starting point and/or |
|
|
//| another function. MinLMRestartFrom() allows to reuse already |
|
|
//| initialized structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - dimension, N>1 |
|
|
//| * if given, only leading N elements of X are |
|
|
//| used |
|
|
//| * if not given, automatically determined from |
|
|
//| size of X |
|
|
//| M - number of functions f[i] |
|
|
//| X - initial solution, array[0..m_n-1] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NOTES: |
|
|
//| 1. you may tune stopping conditions with MinLMSetCond() function |
|
|
//| 2. if target function contains exp() or other fast growing |
|
|
//| functions, and optimization algorithm makes too large steps |
|
|
//| which leads to overflow, use MinLMSetStpMax() function to |
|
|
//| bound algorithm's steps. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateVJ(const int n,const int m,double &x[],
|
|
CMinLMState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialize,check parameters
|
|
State.m_teststep=0;
|
|
State.m_n=n;
|
|
State.m_m=m;
|
|
State.m_algomode=1;
|
|
State.m_hasf=false;
|
|
State.m_hasfi=true;
|
|
State.m_hasg=false;
|
|
//--- second stage of initialization
|
|
LMPRepare(n,m,false,State);
|
|
MinLMSetAccType(State,0);
|
|
MinLMSetCond(State,0,0);
|
|
MinLMSetXRep(State,false);
|
|
MinLMSetStpMax(State,0);
|
|
MinLMRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateVJ(const int n,const int m,CRowDouble &x,
|
|
CMinLMState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialize,check parameters
|
|
State.m_teststep=0;
|
|
State.m_n=n;
|
|
State.m_m=m;
|
|
State.m_algomode=1;
|
|
State.m_hasf=false;
|
|
State.m_hasfi=true;
|
|
State.m_hasg=false;
|
|
//--- second stage of initialization
|
|
LMPRepare(n,m,false,State);
|
|
MinLMSetAccType(State,0);
|
|
MinLMSetCond(State,0,0);
|
|
MinLMSetXRep(State,false);
|
|
MinLMSetStpMax(State,0);
|
|
MinLMRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| IMPROVED LEVENBERG-MARQUARDT METHOD FOR |
|
|
//| NON-LINEAR LEAST SQUARES OPTIMIZATION |
|
|
//| DESCRIPTION: |
|
|
//| This function is used to find minimum of function which is |
|
|
//| represented as sum of squares: |
|
|
//| F(x) = f[0]^2(x[0],...,x[n-1]) + ... + |
|
|
//| + f[m-1]^2(x[0],...,x[n-1]) |
|
|
//| using value of function vector f[] only. Finite differences are |
|
|
//| used to calculate Jacobian. |
|
|
//| REQUIREMENTS: |
|
|
//| This algorithm will request following information during its |
|
|
//| operation: |
|
|
//| * function vector f[] at given point X |
|
|
//| There are several overloaded versions of MinLMOptimize() function|
|
|
//| which correspond to different LM-like optimization algorithms |
|
|
//| provided by this unit. You should choose version which accepts |
|
|
//| fvec() callback. |
|
|
//| You can try to initialize MinLMState structure with VJ function |
|
|
//| and then use incorrect version of MinLMOptimize() (for example, |
|
|
//| version which works with general form function and does not |
|
|
//| accept function vector), but it will lead to exception being |
|
|
//| thrown after first attempt to calculate Jacobian. |
|
|
//| USAGE: |
|
|
//| 1. User initializes algorithm State with MinLMCreateV() call |
|
|
//| 2. User tunes m_solver parameters with MinLMSetCond(), |
|
|
//| MinLMSetStpMax() and other functions |
|
|
//| 3. User calls MinLMOptimize() function which takes algorithm |
|
|
//| State and callback functions. |
|
|
//| 4. User calls MinLMResults() to get solution |
|
|
//| 5. Optionally, user may call MinLMRestartFrom() to solve another |
|
|
//| problem with same N/M but another starting point and/or |
|
|
//| another function. MinLMRestartFrom() allows to reuse already |
|
|
//| initialized structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - dimension, N>1 |
|
|
//| * if given, only leading N elements of X are |
|
|
//| used |
|
|
//| * if not given, automatically determined from |
|
|
//| size of X |
|
|
//| M - number of functions f[i] |
|
|
//| X - initial solution, array[0..m_n-1] |
|
|
//| DiffStep- differentiation step, >0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| See also MinLMIteration, MinLMResults. |
|
|
//| NOTES: |
|
|
//| 1. you may tune stopping conditions with MinLMSetCond() function |
|
|
//| 2. if target function contains exp() or other fast growing |
|
|
//| functions, and optimization algorithm makes too large steps |
|
|
//| which leads to overflow, use MinLMSetStpMax() function to |
|
|
//| bound algorithm's steps. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateV(const int n,const int m,double &x[],
|
|
const double diffstep,CMinLMState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(diffstep),__FUNCTION__+": DiffStep is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialize
|
|
State.m_teststep=0;
|
|
State.m_n=n;
|
|
State.m_m=m;
|
|
State.m_algomode=0;
|
|
State.m_hasf=false;
|
|
State.m_hasfi=true;
|
|
State.m_hasg=false;
|
|
State.m_diffstep=diffstep;
|
|
//--- second stage of initialization
|
|
LMPRepare(n,m,false,State);
|
|
MinLMSetAccType(State,1);
|
|
MinLMSetCond(State,0,0);
|
|
MinLMSetXRep(State,false);
|
|
MinLMSetStpMax(State,0);
|
|
MinLMRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateV(const int n,const int m,CRowDouble &x,
|
|
const double diffstep,CMinLMState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(diffstep),__FUNCTION__+": DiffStep is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialize
|
|
State.m_teststep=0;
|
|
State.m_n=n;
|
|
State.m_m=m;
|
|
State.m_algomode=0;
|
|
State.m_hasf=false;
|
|
State.m_hasfi=true;
|
|
State.m_hasg=false;
|
|
State.m_diffstep=diffstep;
|
|
//--- second stage of initialization
|
|
LMPRepare(n,m,false,State);
|
|
MinLMSetAccType(State,1);
|
|
MinLMSetCond(State,0,0);
|
|
MinLMSetXRep(State,false);
|
|
MinLMSetStpMax(State,0);
|
|
MinLMRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| LEVENBERG-MARQUARDT-LIKE METHOD FOR NON-LINEAR OPTIMIZATION |
|
|
//| DESCRIPTION: |
|
|
//| This function is used to find minimum of general form (not |
|
|
//| "sum-of-squares") function |
|
|
//| F = F(x[0], ..., x[n-1]) |
|
|
//| using its gradient and Hessian. Levenberg-Marquardt modification |
|
|
//| with L-BFGS pre-optimization and internal pre-conditioned L-BFGS |
|
|
//| optimization after each Levenberg-Marquardt step is used. |
|
|
//| REQUIREMENTS: |
|
|
//| This algorithm will request following information during its |
|
|
//| operation: |
|
|
//| * function value F at given point X |
|
|
//| * F and gradient G (simultaneously) at given point X |
|
|
//| * F, G and Hessian H (simultaneously) at given point X |
|
|
//| There are several overloaded versions of MinLMOptimize() |
|
|
//| function which correspond to different LM-like optimization |
|
|
//| algorithms provided by this unit. You should choose version which|
|
|
//| accepts func(), grad() and hess() function pointers. First |
|
|
//| pointer is used to calculate F at given point, second one |
|
|
//| calculates F(x) and grad F(x), third one calculates F(x), grad |
|
|
//| F(x), hess F(x). |
|
|
//| You can try to initialize MinLMState structure with FGH-function |
|
|
//| and then use incorrect version of MinLMOptimize() (for example, |
|
|
//| version which does not provide Hessian matrix), but it will lead |
|
|
//| to exception being thrown after first attempt to calculate |
|
|
//| Hessian. |
|
|
//| USAGE: |
|
|
//| 1. User initializes algorithm State with MinLMCreateFGH() call |
|
|
//| 2. User tunes m_solver parameters with MinLMSetCond(), |
|
|
//| MinLMSetStpMax() and other functions |
|
|
//| 3. User calls MinLMOptimize() function which takes algorithm |
|
|
//| State and pointers (delegates, etc.) to callback functions. |
|
|
//| 4. User calls MinLMResults() to get solution |
|
|
//| 5. Optionally, user may call MinLMRestartFrom() to solve another |
|
|
//| problem with same N but another starting point and/or another |
|
|
//| function. MinLMRestartFrom() allows to reuse already |
|
|
//| initialized structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - dimension, N>1 |
|
|
//| * if given, only leading N elements of X are |
|
|
//| used |
|
|
//| * if not given, automatically determined from |
|
|
//| size of X |
|
|
//| X - initial solution, array[0..m_n-1] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NOTES: |
|
|
//| 1. you may tune stopping conditions with MinLMSetCond() function |
|
|
//| 2. if target function contains exp() or other fast growing |
|
|
//| functions, and optimization algorithm makes too large steps |
|
|
//| which leads to overflow, use MinLMSetStpMax() function to |
|
|
//| bound algorithm's steps. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateFGH(const int n,double &x[],CMinLMState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialize
|
|
State.m_teststep=0;
|
|
State.m_n=n;
|
|
State.m_m=0;
|
|
State.m_algomode=2;
|
|
State.m_hasf=true;
|
|
State.m_hasfi=false;
|
|
State.m_hasg=true;
|
|
//--- init2
|
|
LMPRepare(n,0,true,State);
|
|
MinLMSetAccType(State,2);
|
|
MinLMSetCond(State,0,0);
|
|
MinLMSetXRep(State,false);
|
|
MinLMSetStpMax(State,0);
|
|
MinLMRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateFGH(const int n,CRowDouble &x,CMinLMState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialize
|
|
State.m_teststep=0;
|
|
State.m_n=n;
|
|
State.m_m=0;
|
|
State.m_algomode=2;
|
|
State.m_hasf=true;
|
|
State.m_hasfi=false;
|
|
State.m_hasg=true;
|
|
//--- init2
|
|
LMPRepare(n,0,true,State);
|
|
MinLMSetAccType(State,2);
|
|
MinLMSetCond(State,0,0);
|
|
MinLMSetXRep(State,false);
|
|
MinLMSetStpMax(State,0);
|
|
MinLMRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping conditions for Levenberg-Marquardt |
|
|
//| optimization algorithm. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsX - >=0 |
|
|
//| The subroutine finishes its work if on k+1-th |
|
|
//| iteration the condition |v|<=EpsX is fulfilled, |
|
|
//| where: |
|
|
//| * |.| means Euclidian norm |
|
|
//| * v - scaled step vector, v[i]=dx[i]/s[i] |
|
|
//| * dx - ste pvector, dx=X(k+1)-X(k) |
|
|
//| * s - scaling coefficients set by MinLMSetScale()|
|
|
//| MaxIts - maximum number of iterations. If MaxIts=0, the |
|
|
//| number of iterations is unlimited. Only |
|
|
//| Levenberg-Marquardt iterations are counted |
|
|
//| (L-BFGS/CG iterations are NOT counted because |
|
|
//| their cost is very low compared to that of LM). |
|
|
//| Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will|
|
|
//| lead to automatic stopping criterion selection (small EpsX). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMSetCond(CMinLMState &State,double epsx,
|
|
const int m_maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsx),__FUNCTION__+": EpsX is not finite number!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m_maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
//--- check
|
|
if(epsx==0.0 && m_maxits==0)
|
|
epsx=1.0E-9;
|
|
//--- change values
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=m_maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function turns on/off reporting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NeedXRep- whether iteration reports are needed or not |
|
|
//| If NeedXRep is True, algorithm will call rep() callback function |
|
|
//| if it is provided to MinLMOptimize(). Both Levenberg-Marquardt |
|
|
//| and internal L-BFGS iterations are reported. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMSetXRep(CMinLMState &State,const bool needxrep)
|
|
{
|
|
State.m_xrep=needxrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets maximum step length |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| StpMax - maximum step length, >=0. Set StpMax to 0.0, if |
|
|
//| you don't want to limit step length. |
|
|
//| Use this subroutine when you optimize target function which |
|
|
//| contains exp() or other fast growing functions, and optimization |
|
|
//| algorithm makes too large steps which leads to overflow. This |
|
|
//| function allows us to reject steps that are too large (and |
|
|
//| therefore expose us to the possible overflow) without actually |
|
|
//| calculating function value at the x+stp*d. |
|
|
//| NOTE: non-zero StpMax leads to moderate performance degradation |
|
|
//| because intermediate step of preconditioned L-BFGS optimization |
|
|
//| is incompatible with limits on step size. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMSetStpMax(CMinLMState &State,const double stpmax)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(stpmax),__FUNCTION__+": StpMax is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(stpmax>=0.0,__FUNCTION__+": StpMax<0!"))
|
|
return;
|
|
//--- change value
|
|
State.m_stpmax=stpmax;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients for LM optimizer. |
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions (step size and gradient are scaled before comparison |
|
|
//| with tolerances). Scale of the I-th variable is a translation |
|
|
//| invariant measure of: |
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| Generally, scale is NOT considered to be a form of |
|
|
//| preconditioner. But LM optimizer is unique in that it uses |
|
|
//| scaling matrix both in the stopping condition tests and as |
|
|
//| Marquardt damping factor. |
|
|
//| Proper scaling is very important for the algorithm performance. |
|
|
//| It is less important for the quality of results, but still has |
|
|
//| some influence (it is easier to converge when variables are |
|
|
//| properly scaled, so premature stopping is possible when very |
|
|
//| badly scalled variables are combined with relaxed stopping |
|
|
//| conditions). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients |
|
|
//| S[i] may be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMSetScale(CMinLMState &State,double &s[])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(s)>=State.m_n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
//--- change value
|
|
State.m_s.Set(i,MathAbs(s[i]));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMSetScale(CMinLMState &State,CRowDouble &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(s)>=State.m_n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
//--- change value
|
|
State.m_s.Set(i,MathAbs(s[i]));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets boundary constraints for LM optimizer |
|
|
//| Boundary constraints are inactive by default (after initial |
|
|
//| creation). They are preserved until explicitly turned off with |
|
|
//| another SetBC() call. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bounds, array[N]. |
|
|
//| If some (all) variables are unbounded, you may |
|
|
//| specify very small number or -INF (latter is |
|
|
//| recommended because it will allow m_solver to use |
|
|
//| better algorithm). |
|
|
//| BndU - upper bounds, array[N]. |
|
|
//| If some (all) variables are unbounded, you may |
|
|
//| specify very large number or +INF (latter is |
|
|
//| recommended because it will allow m_solver to use |
|
|
//| better algorithm). |
|
|
//| NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case |
|
|
//| I-th variable will be "frozen" at X[i]=BndL[i]=BndU[i]. |
|
|
//| NOTE 2: this m_solver has following useful properties: |
|
|
//| * bound constraints are always satisfied exactly |
|
|
//| * function is evaluated only INSIDE area specified by bound |
|
|
//| constraints or at its boundary |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMSetBC(CMinLMState &State,double &bndl[],double &bndu[])
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndu)>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndl[i]) || AL_NEGINF==bndl[i],"MinLMSetBC: BndL contains NAN or +INF"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndu[i]) || AL_POSINF==bndu[i],"MinLMSetBC: BndU contains NAN or -INF"))
|
|
return;
|
|
//--- change values
|
|
State.m_bndl.Set(i,bndl[i]);
|
|
State.m_bndu.Set(i,bndu[i]);
|
|
State.m_havebndl[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_havebndu[i]=MathIsValidNumber(bndu[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMSetBC(CMinLMState &State,CRowDouble &bndl,
|
|
CRowDouble &bndu)
|
|
{
|
|
//--- initialization
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndu)>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndl[i]) || AL_NEGINF==bndl[i],"MinLMSetBC: BndL contains NAN or +INF"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndu[i]) || AL_POSINF==bndu[i],"MinLMSetBC: BndU contains NAN or -INF"))
|
|
return;
|
|
//--- change values
|
|
State.m_bndl.Set(i,bndl[i]);
|
|
State.m_bndu.Set(i,bndu[i]);
|
|
State.m_havebndl[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_havebndu[i]=MathIsValidNumber(bndu[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets general linear constraints for LM optimizer |
|
|
//| Linear constraints are inactive by default (after initial |
|
|
//| creation). They are preserved until explicitly turned off with |
|
|
//| another minlmsetlc() call. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| C - linear constraints, array[K,N+1]. |
|
|
//| Each row of C represents one constraint, either |
|
|
//| equality or inequality (see below): |
|
|
//| * first N elements correspond to coefficients, |
|
|
//| * last element corresponds to the right part. |
|
|
//| All elements of C (including right part) must be |
|
|
//| finite. |
|
|
//| CT - type of constraints, array[K]: |
|
|
//| * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1] |
|
|
//| * if CT[i]=0, then I-th constraint is C[i,*]*x = C[i,n+1] |
|
|
//| * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1] |
|
|
//| K - number of equality/inequality constraints, K>=0: |
|
|
//| * if given, only leading K elements of C/CT are used |
|
|
//| * if not given, automatically determined from sizes of C/CT |
|
|
//| IMPORTANT: if you have linear constraints, it is strongly |
|
|
//| recommended to set scale of variables with |
|
|
//| MinLMSetScale(). QP solver which is used to calculate |
|
|
//| linearly constrained steps heavily relies on good |
|
|
//| scaling of input problems. |
|
|
//| IMPORTANT: solvers created with MinLMCreateFGH() do not support |
|
|
//| linear constraints. |
|
|
//| NOTE: linear (non-bound) constraints are satisfied only |
|
|
//| approximately - there always exists some violation due to |
|
|
//| numerical errors and algorithmic limitations. |
|
|
//| NOTE: general linear constraints add significant overhead to |
|
|
//| solution process. Although solver performs roughly same |
|
|
//| amount of iterations (when compared with similar box-only |
|
|
//| constrained problem), each iteration now involves solution |
|
|
//| of linearly constrained QP subproblem, which requires ~3-5 |
|
|
//| times more Cholesky decompositions. Thus, if you can |
|
|
//| reformulate your problem in such way this it has only box |
|
|
//| constraints, it may be beneficial to do so. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMSetLC(CMinLMState &State,CMatrixDouble &c,
|
|
CRowInt &ct,int k)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int i=0;
|
|
int i_=0;
|
|
//--- First, check for errors in the inputs
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(!CAp::Assert(c.Cols()>=n+1 || k==0,__FUNCTION__+": Cols(C)<N+1"))
|
|
return;
|
|
if(!CAp::Assert(c.Rows()>=k,__FUNCTION__+": Rows(C)<K"))
|
|
return;
|
|
if(!CAp::Assert(ct.Size()>=k,__FUNCTION__+": Length(CT)<K"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(c,k,n+1),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
//--- Handle zero K
|
|
if(k==0)
|
|
{
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
return;
|
|
}
|
|
//--- Equality constraints are stored first, in the upper
|
|
//--- NEC rows of State.CLEIC matrix. Inequality constraints
|
|
//--- are stored in the next NIC rows.
|
|
//--- NOTE: we convert inequality constraints to the form
|
|
//--- A*x<=b before copying them.
|
|
State.m_cleic.Resize(k,n+1);
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
for(i=0; i<k; i++)
|
|
{
|
|
if(ct[i]==0)
|
|
{
|
|
State.m_cleic.Row(State.m_nec,c[i]+0);
|
|
State.m_nec++;
|
|
}
|
|
}
|
|
for(i=0; i<k; i++)
|
|
{
|
|
if(ct[i]==0)
|
|
continue;
|
|
if(ct[i]>0)
|
|
State.m_cleic.Row(State.m_nec+State.m_nic,c[i]*(-1.0));
|
|
else
|
|
State.m_cleic.Row(State.m_nec+State.m_nic,c[i]+0);
|
|
State.m_nic++;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is used to change acceleration Settings |
|
|
//| You can choose between three acceleration strategies: |
|
|
//| * AccType=0, no acceleration. |
|
|
//| * AccType=1, secant updates are used to update quadratic model |
|
|
//| after each iteration. After fixed number of iterations (or |
|
|
//| after model breakdown) we recalculate quadratic model using |
|
|
//| analytic Jacobian or finite differences. Number of secant-based|
|
|
//| iterations depends on optimization Settings: about 3 |
|
|
//| iterations - when we have analytic Jacobian, up to 2*N |
|
|
//| iterations - when we use finite differences to calculate |
|
|
//| Jacobian. |
|
|
//| AccType=1 is recommended when Jacobian calculation cost is |
|
|
//| prohibitive high (several Mx1 function vector calculations |
|
|
//| followed by several NxN Cholesky factorizations are faster than |
|
|
//| calculation of one M*N Jacobian). It should also be used when we|
|
|
//| have no Jacobian, because finite difference approximation takes |
|
|
//| too much time to compute. |
|
|
//| Table below list optimization protocols (XYZ protocol corresponds|
|
|
//| to MinLMCreateXYZ) and acceleration types they support (and use |
|
|
//| by default). |
|
|
//| ACCELERATION TYPES SUPPORTED BY OPTIMIZATION PROTOCOLS: |
|
|
//| protocol 0 1 comment |
|
|
//| V + + |
|
|
//| VJ + + |
|
|
//| FGH + |
|
|
//| DAFAULT VALUES: |
|
|
//| protocol 0 1 comment |
|
|
//| V x without acceleration it is so slooooooooow |
|
|
//| VJ x |
|
|
//| FGH x |
|
|
//| NOTE: this function should be called before optimization. |
|
|
//| Attempt to call it during algorithm iterations may result in |
|
|
//| unexpected behavior. |
|
|
//| NOTE: attempt to call this function with unsupported |
|
|
//| protocol/acceleration combination will result in exception being |
|
|
//| thrown. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMSetAccType(CMinLMState &State,int acctype)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert((acctype==0 || acctype==1) || acctype==2,__FUNCTION__+": incorrect AccType!"))
|
|
return;
|
|
//--- check
|
|
if(acctype==2)
|
|
acctype=0;
|
|
//--- check
|
|
if(acctype==0)
|
|
{
|
|
State.m_maxmodelage=0;
|
|
State.m_makeadditers=false;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- check
|
|
if(acctype==1)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hasfi,__FUNCTION__+": AccType=1 is incompatible with current protocol!"))
|
|
return;
|
|
//--- check
|
|
if(State.m_algomode==0)
|
|
State.m_maxmodelage=2*State.m_n;
|
|
else
|
|
State.m_maxmodelage=m_smallmodelage;
|
|
State.m_makeadditers=false;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function activates/deactivates verification of the |
|
|
//| user-supplied analytic Jacobian. |
|
|
//| Upon activation of this option OptGuard integrity checker |
|
|
//| performs numerical differentiation of your target function vector|
|
|
//| at the initial point (note: future versions may also perform |
|
|
//| check at the final point) and compares numerical Jacobian with|
|
|
//| analytic one provided by you. |
|
|
//| If difference is too large, an error flag is set and optimization|
|
|
//| session continues. After optimization session is over, you can |
|
|
//| retrieve the report which stores both Jacobians, and specific|
|
|
//| components highlighted as suspicious by the OptGuard. |
|
|
//| The OptGuard report can be retrieved with MinLMOptGuardResults().|
|
|
//| IMPORTANT: gradient check is a high-overhead option which will |
|
|
//| cost you about 3*N additional function evaluations. In|
|
|
//| many cases it may cost as much as the rest of the |
|
|
//| optimization session. |
|
|
//| YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO |
|
|
//| CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY. |
|
|
//| NOTE: unlike previous incarnation of the gradient checking code, |
|
|
//| OptGuard does NOT interrupt optimization even if it |
|
|
//| discovers bad gradient. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure used to store algorithm State |
|
|
//| TestStep - verification step used for numerical |
|
|
//| differentiation: |
|
|
//| * TestStep=0 turns verification off |
|
|
//| * TestStep>0 activates verification. You should |
|
|
//| carefully choose TestStep. Value which is too |
|
|
//| large (so large that function behavior is non-|
|
|
//| cubic at this scale) will lead to false alarms. |
|
|
//| Too short step will result in rounding errors |
|
|
//| dominating numerical derivative. |
|
|
//| You may use different step for different parameters by means of |
|
|
//| setting scale with MinLMSetScale(). |
|
|
//| === EXPLANATION ================================================ |
|
|
//| In order to verify gradient algorithm performs following steps: |
|
|
//| * two trial steps are made to X[i]-TestStep*S[i] and |
|
|
//| X[i]+TestStep*S[i], where X[i] is i-th component of the |
|
|
//| initial point and S[i] is a scale of i-th parameter |
|
|
//| * F(X) is evaluated at these trial points |
|
|
//| * we perform one more evaluation in the middle point of the |
|
|
//| interval |
|
|
//| * we build cubic model using function values and derivatives|
|
|
//| at trial points and we compare its prediction with actual |
|
|
//| value in the middle point |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMOptGuardGradient(CMinLMState &State,
|
|
double teststep)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(teststep),__FUNCTION__+": TestStep contains NaN or INF"))
|
|
return;
|
|
if(!CAp::Assert(teststep>=0.0,__FUNCTION__+": invalid argument TestStep(TestStep<0)"))
|
|
return;
|
|
State.m_teststep=teststep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Results of OptGuard integrity check, should be called after |
|
|
//| optimization session is over. |
|
|
//| OptGuard checks analytic Jacobian against reference value |
|
|
//| obtained by numerical differentiation with user-specified step. |
|
|
//| NOTE: other optimizers perform additional OptGuard checks for |
|
|
//| things like C0/C1-continuity violations. However, LM |
|
|
//| optimizer can check only for incorrect Jacobian. |
|
|
//| The reason is that unlike line search methods LM optimizer does |
|
|
//| not perform extensive evaluations along the line. Thus, we simply|
|
|
//| do not have enough data to catch C0/C1-violations. |
|
|
//| This check is activated with MinLMOptGuardGradient() function. |
|
|
//| Following flags are set when these errors are suspected: |
|
|
//| * rep.badgradsuspected, and additionally: |
|
|
//| * rep.badgradfidx for specific function (Jacobian row) |
|
|
//| suspected |
|
|
//| * rep.badgradvidx for specific variable (Jacobian column) |
|
|
//| suspected |
|
|
//| * rep.badgradxbase, a point where gradient/Jacobian is |
|
|
//| tested |
|
|
//| * rep.badgraduser, user-provided gradient/Jacobian |
|
|
//| * rep.badgradnum, reference gradient/Jacobian obtained via |
|
|
//| numerical differentiation |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - OptGuard report |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMOptGuardResults(CMinLMState &State,
|
|
COptGuardReport &rep)
|
|
{
|
|
COptServ::SmoothnessMonitorExportReport(State.m_smonitor,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Levenberg-Marquardt algorithm results |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[0..m_n-1], solution |
|
|
//| Rep - optimization report; |
|
|
//| see comments for this structure for more Info. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMResults(CMinLMState &State,double &x[],
|
|
CMinLMReport &rep)
|
|
{
|
|
//--- reset memory
|
|
ArrayResize(x,0);
|
|
//--- function call
|
|
MinLMResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMResults(CMinLMState &State,CRowDouble &x,
|
|
CMinLMReport &rep)
|
|
{
|
|
//--- reset memory
|
|
x.Resize(0);
|
|
//--- function call
|
|
MinLMResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Levenberg-Marquardt algorithm results |
|
|
//| Buffered implementation of MinLMResults(), which uses |
|
|
//| pre-allocated buffer to store X[]. If buffer size is too small, |
|
|
//| it resizes buffer. It is intended to be used in the inner cycles |
|
|
//| of performance critical algorithms where array reallocation |
|
|
//| penalty is too large to be ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMResultsBuf(CMinLMState &State,double &x[],
|
|
CMinLMReport &rep)
|
|
{
|
|
//--- copy
|
|
State.m_x.ToArray(x);
|
|
//--- change values
|
|
rep.m_iterationscount=State.m_repiterationscount;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
rep.m_nfunc=State.m_repnfunc;
|
|
rep.m_njac=State.m_repnjac;
|
|
rep.m_ngrad=State.m_repngrad;
|
|
rep.m_nhess=State.m_repnhess;
|
|
rep.m_ncholesky=State.m_repncholesky;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMResultsBuf(CMinLMState &State,CRowDouble &x,
|
|
CMinLMReport &rep)
|
|
{
|
|
//--- copy
|
|
x=State.m_x;
|
|
//--- change values
|
|
rep.m_iterationscount=State.m_repiterationscount;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
rep.m_nfunc=State.m_repnfunc;
|
|
rep.m_njac=State.m_repnjac;
|
|
rep.m_ngrad=State.m_repngrad;
|
|
rep.m_nhess=State.m_repnhess;
|
|
rep.m_ncholesky=State.m_repncholesky;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine restarts LM algorithm from new point. All |
|
|
//| optimization parameters are left unchanged. |
|
|
//| This function allows to solve multiple optimization problems |
|
|
//| (which must have same number of dimensions) without object |
|
|
//| reallocation penalty. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure used for reverse communication |
|
|
//| previously allocated with MinLMCreateXXX call. |
|
|
//| X - new starting point. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMRestartFrom(CMinLMState &State,double &x[])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=State.m_n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,State.m_n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- copy
|
|
State.m_xbase=x;
|
|
State.m_xbase.Resize(State.m_n);
|
|
//--- allocation
|
|
State.m_rstate.ia.Resize(5);
|
|
ArrayResizeAL(State.m_rstate.ba,1);
|
|
State.m_rstate.ra.Resize(4);
|
|
State.m_rstate.stage=-1;
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMRestartFrom(CMinLMState &State,CRowDouble &x)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=State.m_n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,State.m_n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- copy
|
|
State.m_xbase=x;
|
|
State.m_xbase.Resize(State.m_n);
|
|
//--- allocation
|
|
State.m_rstate.ia.Resize(5);
|
|
ArrayResizeAL(State.m_rstate.ba,1);
|
|
State.m_rstate.ra.Resize(4);
|
|
State.m_rstate.stage=-1;
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine submits request for termination of running |
|
|
//| optimizer. It should be called from user-supplied callback when |
|
|
//| user decides that it is time to "smoothly" terminate optimization|
|
|
//| process. As result, optimizer stops at point which was "current |
|
|
//| accepted" when termination request was submitted and returns |
|
|
//| error code 8 (successful termination). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - optimizer structure |
|
|
//| NOTE: after request for termination optimizer may perform several|
|
|
//| additional calls to user-supplied callbacks. It does NOT |
|
|
//| guarantee to stop immediately - it just guarantees that |
|
|
//| these additional calls will be discarded later. |
|
|
//| NOTE: calling this function on optimizer which is NOT running |
|
|
//| will have no effect. |
|
|
//| NOTE: multiple calls to this function are possible. First call is|
|
|
//| counted, subsequent calls are silently ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMRequestTermination(CMinLMState &State)
|
|
{
|
|
State.m_userterminationneeded=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is obsolete function. |
|
|
//| Since ALGLIB 3.3 it is equivalent to MinLMCreateVJ(). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateVGJ(const int n,const int m,double &x[],
|
|
CMinLMState &State)
|
|
{
|
|
MinLMCreateVJ(n,m,x,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateVGJ(const int n,const int m,CRowDouble &x,
|
|
CMinLMState &State)
|
|
{
|
|
MinLMCreateVJ(n,m,x,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is obsolete function. |
|
|
//| Since ALGLIB 3.3 it is equivalent to MinLMCreateFJ(). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateFGJ(const int n,const int m,double &x[],
|
|
CMinLMState &State)
|
|
{
|
|
MinLMCreateFJ(n,m,x,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateFGJ(const int n,const int m,CRowDouble &x,
|
|
CMinLMState &State)
|
|
{
|
|
MinLMCreateFJ(n,m,x,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is considered obsolete since ALGLIB 3.1.0 and is |
|
|
//| present for backward compatibility only. We recommend to use |
|
|
//| MinLMCreateVJ, which provides similar, but more consistent and |
|
|
//| feature-rich interface. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateFJ(const int n,const int m,double &x[],
|
|
CMinLMState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialize
|
|
State.m_teststep=0;
|
|
State.m_n=n;
|
|
State.m_m=m;
|
|
State.m_algomode=1;
|
|
State.m_hasf=true;
|
|
State.m_hasfi=false;
|
|
State.m_hasg=false;
|
|
//--- init 2
|
|
LMPRepare(n,m,true,State);
|
|
//--- function call
|
|
MinLMSetAccType(State,0);
|
|
//--- function call
|
|
MinLMSetCond(State,0,0);
|
|
//--- function call
|
|
MinLMSetXRep(State,false);
|
|
//--- function call
|
|
MinLMSetStpMax(State,0);
|
|
//--- function call
|
|
MinLMRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMCreateFJ(const int n,const int m,CRowDouble &x,
|
|
CMinLMState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialize
|
|
State.m_teststep=0;
|
|
State.m_n=n;
|
|
State.m_m=m;
|
|
State.m_algomode=1;
|
|
State.m_hasf=true;
|
|
State.m_hasfi=false;
|
|
State.m_hasg=false;
|
|
//--- init 2
|
|
LMPRepare(n,m,true,State);
|
|
//--- function call
|
|
MinLMSetAccType(State,0);
|
|
//--- function call
|
|
MinLMSetCond(State,0,0);
|
|
//--- function call
|
|
MinLMSetXRep(State,false);
|
|
//--- function call
|
|
MinLMSetStpMax(State,0);
|
|
//--- function call
|
|
MinLMRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Prepare internal structures (except for RComm). |
|
|
//| Note: M must be zero for FGH mode, non-zero for V/VJ/FJ/FGJ mode.|
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::LMPRepare(const int n,const int m,bool havegrad,
|
|
CMinLMState &State)
|
|
{
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,0,0,false);
|
|
if(n<=0 || m<0)
|
|
return;
|
|
|
|
if(havegrad)
|
|
State.m_g.Resize(n);
|
|
|
|
if(m!=0)
|
|
{
|
|
State.m_j.Resize(m,n);
|
|
State.m_fi.Resize(m);
|
|
State.m_fibase.Resize(m);
|
|
State.m_deltaf.Resize(m);
|
|
State.m_fm1.Resize(m);
|
|
State.m_fp1.Resize(m);
|
|
State.m_fc1.Resize(m);
|
|
State.m_gm1.Resize(m);
|
|
State.m_gp1.Resize(m);
|
|
State.m_gc1.Resize(m);
|
|
}
|
|
else
|
|
State.m_h.Resize(n,n);
|
|
|
|
State.m_x=vector<double>::Zeros(n);
|
|
State.m_deltax.Resize(n);
|
|
State.m_quadraticmodel.Resize(n,n);
|
|
State.m_xbase.Resize(n);
|
|
State.m_gbase.Resize(n);
|
|
State.m_xdir.Resize(n);
|
|
State.m_tmp0.Resize(n);
|
|
//--- prepare internal L-BFGS
|
|
CMinLBFGS::MinLBFGSCreate(n,MathMin(m_additers,n),State.m_x,State.m_internalstate);
|
|
CMinLBFGS::MinLBFGSSetCond(State.m_internalstate,0.0,0.0,0.0,MathMin(m_additers,n));
|
|
//--- Prepare internal QP solver
|
|
CMinQP::MinQPCreate(n,State.m_qpstate);
|
|
CMinQP::MinQPSetAlgoQuickQP(State.m_qpstate,0.0,0.0,CApServ::Coalesce(0.01*State.m_epsx,1.0E-12),10,true);
|
|
//--- Prepare boundary constraints
|
|
State.m_bndl=vector<double>::Full(n,AL_NEGINF);
|
|
State.m_bndu=vector<double>::Full(n,AL_POSINF);
|
|
ArrayResize(State.m_havebndl,n);
|
|
ArrayResize(State.m_havebndu,n);
|
|
ArrayInitialize(State.m_havebndl,false);
|
|
ArrayInitialize(State.m_havebndu,false);
|
|
//--- Prepare scaling matrix
|
|
State.m_s=vector<double>::Ones(n);
|
|
State.m_lastscaleused=vector<double>::Ones(n);
|
|
//--- Prepare linear constraints
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Clears request fileds (to be sure that we don't forgot to clear |
|
|
//| something) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::ClearRequestFields(CMinLMState &State)
|
|
{
|
|
//--- change values
|
|
State.m_needf=false;
|
|
State.m_needfg=false;
|
|
State.m_needfgh=false;
|
|
State.m_needfij=false;
|
|
State.m_needfi=false;
|
|
State.m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Increases lambda, returns False when there is a danger of |
|
|
//| overflow |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLM::IncreaseLambda(double &lambdav,double &nu)
|
|
{
|
|
//--- create variables
|
|
double lnlambda=MathLog(lambdav);
|
|
double lnlambdaup=MathLog(m_lambdaup);
|
|
double lnnu=MathLog(nu);
|
|
double lnmax=MathLog(CMath::m_maxrealnumber);
|
|
//--- check
|
|
if(lnlambda+lnlambdaup+lnnu>0.25*lnmax)
|
|
return(false);
|
|
//--- check
|
|
if(lnnu+MathLog(2)>lnmax)
|
|
return(false);
|
|
//--- change values
|
|
lambdav=lambdav*m_lambdaup*nu;
|
|
nu=nu*2;
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Decreases lambda, but leaves it unchanged when there is danger of|
|
|
//| underflow. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::DecreaseLambda(double &lambdav,double &nu)
|
|
{
|
|
//--- initialization
|
|
nu=1;
|
|
//--- check
|
|
if(MathLog(lambdav)+MathLog(m_lambdadown)<MathLog(CMath::m_minrealnumber))
|
|
lambdav=CMath::m_minrealnumber;
|
|
else
|
|
lambdav=lambdav*m_lambdadown;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function compares actual decrease vs predicted decrease and |
|
|
//| updates LambdaV/Nu accordingly. |
|
|
//| INPUT PARAMETERS: |
|
|
//| QuadraticModel - array[N,N], full Hessian matrix of quadratic |
|
|
//| model at deltaX=0 |
|
|
//| GBase - array[N], gradient at deltaX=0 |
|
|
//| FBase - F(deltaX=0) |
|
|
//| N - size |
|
|
//| DeltaX - step vector |
|
|
//| FNew - new function value |
|
|
//| LambdaV - lambda-value, updated on exit |
|
|
//| Nu - Nu-multiplier, updated on exit |
|
|
//| On exit it returns: |
|
|
//| * Result=0 - if we have to continue iterations |
|
|
//| * Result<>0 - if termination with completion code Result is|
|
|
//| requested |
|
|
//+------------------------------------------------------------------+
|
|
int CMinLM::CheckDecrease(CMatrixDouble &quadraticmodel,
|
|
CRowDouble &gbase,
|
|
double fbase,
|
|
int n,
|
|
CRowDouble &deltax,
|
|
double fnew,
|
|
double &lambdav,
|
|
double &nu)
|
|
{
|
|
//--- create variables
|
|
int result=0;
|
|
int i=0;
|
|
double v=0;
|
|
double t=0;
|
|
double predicteddecrease=0;
|
|
double actualdecrease=0;
|
|
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=deltax.Dot(quadraticmodel[i]+0);
|
|
t+=deltax[i]*(gbase[i]+0.5 *v);
|
|
}
|
|
predicteddecrease=-t;
|
|
actualdecrease=-(fnew-fbase);
|
|
if(predicteddecrease<=0.0)
|
|
{
|
|
result=7;
|
|
return(result);
|
|
}
|
|
v=actualdecrease/predicteddecrease;
|
|
if(v<0.1)
|
|
if(!IncreaseLambda(lambdav,nu))
|
|
{
|
|
//--- Lambda is too large, we have to break iterations.
|
|
result=7;
|
|
return(result);
|
|
}
|
|
if(v>0.5)
|
|
DecreaseLambda(lambdav,nu);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes step finder object with problem |
|
|
//| statement; model parameters specified during this call should not|
|
|
//| (and can not) change during object lifetime (although it is |
|
|
//| possible to re-initialize object with different Settings). |
|
|
//| This function reuses internally allocated objects as much as |
|
|
//| possible. |
|
|
//| In addition to initializing step finder, this function enforces |
|
|
//| feasibility in initial point X passed to this function. It is |
|
|
//| important that LM iteration starts from feasible point and |
|
|
//| performs feasible steps; |
|
|
//| RETURN VALUE: |
|
|
//| True for successful initialization |
|
|
//| False for inconsistent constraints; you should not use step |
|
|
//| finder if it returned False. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLM::MinLMStepFinderInit(CMinLMStepFinder &State,
|
|
int n,
|
|
int m,
|
|
int maxmodelage,
|
|
bool hasfi,
|
|
CRowDouble &xbase,
|
|
CRowDouble &bndl,
|
|
CRowDouble &bndu,
|
|
CMatrixDouble &cleic,
|
|
int nec,
|
|
int nic,
|
|
CRowDouble &s,
|
|
double stpmax,
|
|
double epsx)
|
|
{
|
|
//--- create variable
|
|
int i=0;
|
|
//--- initialization
|
|
State.m_n=n;
|
|
State.m_m=m;
|
|
State.m_maxmodelage=maxmodelage;
|
|
State.m_hasfi=hasfi;
|
|
State.m_stpmax=stpmax;
|
|
State.m_epsx=epsx;
|
|
//--- Allocate temporaries, create QP solver, select QP algorithm
|
|
ArrayResize(State.m_havebndl,n);
|
|
ArrayResize(State.m_havebndu,n);
|
|
State.m_bndl.Resize(n);
|
|
State.m_bndu.Resize(n);
|
|
State.m_s=s;
|
|
State.m_x.Resize(n);
|
|
State.m_xbase.Resize(n);
|
|
State.m_tmp0.Resize(n);
|
|
State.m_modeldiag.Resize(n);
|
|
State.m_tmpct.Resize(nec+nic);
|
|
State.m_xdir.Resize(n);
|
|
if(hasfi)
|
|
{
|
|
State.m_fi.Resize(m);
|
|
State.m_fibase.Resize(m);
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(bndl[i]) || IsNegInf(bndl[i]),__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
if(!CAp::Assert(MathIsValidNumber(bndu[i]) || IsPosInf(bndu[i]),__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
State.m_havebndl[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_havebndu[i]=MathIsValidNumber(bndu[i]);
|
|
State.m_bndl.Set(i,bndl[i]);
|
|
State.m_bndu.Set(i,bndu[i]);
|
|
}
|
|
State.m_tmpct.Fill(0,0,nec);
|
|
State.m_tmpct.Fill(-1,nec,nic);
|
|
CMinQP::MinQPCreate(n,State.m_qpstate);
|
|
if(nec+nic==0)
|
|
CMinQP::MinQPSetAlgoQuickQP(State.m_qpstate,0.0,0.0,CApServ::Coalesce(0.01*epsx,1.0E-12),10,true);
|
|
else
|
|
CMinQP::MinQPSetAlgoDenseAUL(State.m_qpstate,CApServ::Coalesce(0.01*epsx,1.0E-12),100,10);
|
|
CMinQP::MinQPSetBC(State.m_qpstate,bndl,bndu);
|
|
CMinQP::MinQPSetLC(State.m_qpstate,cleic,State.m_tmpct,nec+nic);
|
|
CMinQP::MinQPSetScale(State.m_qpstate,s);
|
|
//--- Check feasibility of constraints:
|
|
//--- * check/enforce box constraints (straightforward)
|
|
//--- * prepare QP subproblem which return us a feasible point
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_havebndl[i] && State.m_havebndu[i] && State.m_bndl[i]>State.m_bndu[i])
|
|
return(false);
|
|
|
|
if(State.m_havebndl[i] && xbase[i]<State.m_bndl[i])
|
|
xbase.Set(i,State.m_bndl[i]);
|
|
if(State.m_havebndu[i] && xbase[i]>State.m_bndu[i])
|
|
xbase.Set(i,State.m_bndu[i]);
|
|
}
|
|
if(nec+nic>0)
|
|
{
|
|
//--- Well, we have linear constraints... let's use heavy machinery.
|
|
//--- We will modify QP solver State below, but everything will be
|
|
//--- restored in MinLMStepFinderStart().
|
|
CSparse::SparseCreate(n,n,n,State.m_tmpsp);
|
|
for(i=0; i<n; i++)
|
|
CSparse::SparseSet(State.m_tmpsp,i,i,0.5);
|
|
State.m_tmp0=vector<double>::Zeros(n);
|
|
CMinQP::MinQPSetStartingPointFast(State.m_qpstate,xbase);
|
|
CMinQP::MinQPSetOriginFast(State.m_qpstate,xbase);
|
|
CMinQP::MinQPSetLinearTermFast(State.m_qpstate,State.m_tmp0);
|
|
CMinQP::MinQPSetQuadraticTermSparse(State.m_qpstate,State.m_tmpsp,true);
|
|
CMinQP::MinQPOptimize(State.m_qpstate);
|
|
CMinQP::MinQPResultsBuf(State.m_qpstate,xbase,State.m_qprep);
|
|
}
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function prepares LM step search session. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLM::MinLMStepFinderStart(CMinLMStepFinder &State,
|
|
CMatrixDouble &quadraticmodel,
|
|
CRowDouble &gbase,
|
|
double fbase,
|
|
CRowDouble &xbase,
|
|
CRowDouble &fibase,
|
|
int modelage)
|
|
{
|
|
int n=State.m_n;
|
|
//--- allocated
|
|
State.m_rstate.ia.Resize(3);
|
|
ArrayResize(State.m_rstate.ba,1);
|
|
State.m_rstate.ra.Resize(1);
|
|
//--- initialization
|
|
State.m_rstate.stage=-1;
|
|
State.m_modelage=modelage;
|
|
State.m_fbase=fbase;
|
|
if(State.m_hasfi)
|
|
{
|
|
State.m_fibase=fibase;
|
|
}
|
|
State.m_xbase=xbase;
|
|
State.m_modeldiag=quadraticmodel.Diag()+0;
|
|
CMinQP::MinQPSetStartingPointFast(State.m_qpstate,xbase);
|
|
CMinQP::MinQPSetOriginFast(State.m_qpstate,xbase);
|
|
CMinQP::MinQPSetLinearTermFast(State.m_qpstate,gbase);
|
|
CMinQP::MinQPSetQuadraticTermFast(State.m_qpstate,quadraticmodel,true,0.0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function runs LM step search session. |
|
|
//| Find value of Levenberg-Marquardt damping parameter which: |
|
|
//| * leads to positive definite damped model |
|
|
//| * within bounds specified by StpMax |
|
|
//| * generates step which decreases function value |
|
|
//| After this block IFlag is set to: |
|
|
//| * -8, if infinities/NANs were detected in function values/ |
|
|
//| gradient |
|
|
//| * -3, if constraints are infeasible |
|
|
//| * -2, if model update is needed (either Lambda growth is too |
|
|
//| large or step is too short, but we can't rely on model |
|
|
//| and stop iterations) |
|
|
//| * -1, if model is fresh, Lambda have grown too large, |
|
|
//| termination is needed |
|
|
//| * 0, if everything is OK, continue iterations |
|
|
//| * >0 - successful completion (step size is small enough) |
|
|
//| State.Nu can have any value on enter, but after exit it is set |
|
|
//| to 1.0 |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLM::MinLMStepFinderIteration(CMinLMStepFinder &State,
|
|
double &lambdav,
|
|
double &nu,
|
|
CRowDouble &xnew,
|
|
CRowDouble &deltax,
|
|
bool &deltaxready,
|
|
CRowDouble &deltaf,
|
|
bool &deltafready,
|
|
int &iflag,
|
|
double &fnew,
|
|
int &ncholesky)
|
|
{
|
|
int i=0;
|
|
bool bflag=false;
|
|
double v=0;
|
|
int n=0;
|
|
int m=0;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
i=State.m_rstate.ia[0];
|
|
n=State.m_rstate.ia[1];
|
|
m=State.m_rstate.ia[2];
|
|
bflag=State.m_rstate.ba[0];
|
|
v=State.m_rstate.ra[0];
|
|
}
|
|
else
|
|
{
|
|
i=-838;
|
|
n=939;
|
|
m=-526;
|
|
bflag=true;
|
|
v=-541;
|
|
}
|
|
//--- check
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
State.m_needfi=false;
|
|
v=State.m_fi.Dot(State.m_fi);
|
|
fnew=v;
|
|
deltaf=State.m_fi-State.m_fibase+0;
|
|
deltafready=true;
|
|
if(!MathIsValidNumber(fnew))
|
|
{
|
|
//--- Integrity check failed, break!
|
|
iflag=-8;
|
|
nu=1;
|
|
return(false);
|
|
}
|
|
if(fnew>=State.m_fbase)
|
|
{
|
|
//--- Increase lambda and continue
|
|
if(!IncreaseLambda(lambdav,nu))
|
|
{
|
|
iflag=-1;
|
|
nu=1;
|
|
return(false);
|
|
}
|
|
break;
|
|
}
|
|
//--- We've found our step!
|
|
iflag=0;
|
|
nu=1;
|
|
return(false);
|
|
break;
|
|
case 1:
|
|
State.m_needf=false;
|
|
fnew=State.m_f;
|
|
if(!MathIsValidNumber(fnew))
|
|
{
|
|
//--- Integrity check failed, break!
|
|
iflag=-8;
|
|
nu=1;
|
|
return(false);
|
|
}
|
|
if(fnew>=State.m_fbase)
|
|
{
|
|
//--- Increase lambda and continue
|
|
if(!IncreaseLambda(lambdav,nu))
|
|
{
|
|
iflag=-1;
|
|
nu=1;
|
|
return(false);
|
|
}
|
|
break;
|
|
}
|
|
//--- We've found our step!
|
|
iflag=0;
|
|
nu=1;
|
|
return(false);
|
|
break;
|
|
default:
|
|
iflag=-99;
|
|
n=State.m_n;
|
|
m=State.m_m;
|
|
break;
|
|
}
|
|
//--- Routine body
|
|
while(true)
|
|
{
|
|
deltaxready=false;
|
|
deltafready=false;
|
|
//--- Do we need model update?
|
|
if(State.m_modelage>0 && nu>=m_suspiciousnu)
|
|
{
|
|
iflag=-2;
|
|
nu=1;
|
|
return(false);
|
|
}
|
|
//
|
|
//--- Setup quadratic solver and solve quadratic programming problem.
|
|
//--- After problem is solved we'll try to bound step by StpMax
|
|
//--- (Lambda will be increased if step size is too large).
|
|
//
|
|
//--- We use BFlag variable to indicate that we have to increase Lambda.
|
|
//--- If it is False, we will try to increase Lambda and move to new iteration.
|
|
//
|
|
bflag=true;
|
|
State.m_tmp0=State.m_modeldiag.ToVector()+State.m_s.Pow(-2)*lambdav;
|
|
CMinQP::MinQPRewriteDiagonal(State.m_qpstate,State.m_tmp0);
|
|
CMinQP::MinQPOptimize(State.m_qpstate);
|
|
CMinQP::MinQPResultsBuf(State.m_qpstate,xnew,State.m_qprep);
|
|
ncholesky+=State.m_qprep.m_ncholesky;
|
|
if(State.m_qprep.m_terminationtype==-3)
|
|
{
|
|
//--- Infeasible constraints
|
|
iflag=-3;
|
|
nu=1;
|
|
return(false);
|
|
}
|
|
if(State.m_qprep.m_terminationtype==-4 || State.m_qprep.m_terminationtype==-5)
|
|
{
|
|
//--- Unconstrained direction of negative curvature was detected
|
|
if(!IncreaseLambda(lambdav,nu))
|
|
{
|
|
iflag=-1;
|
|
nu=1;
|
|
return(false);
|
|
}
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_qprep.m_terminationtype>0,__FUNCTION__+": unexpected completion code from QP solver"))
|
|
return(false);
|
|
State.m_xdir=xnew-State.m_xbase+0;
|
|
v=MathPow(State.m_xdir/State.m_s+0,2).Sum();
|
|
if(MathIsValidNumber(v))
|
|
{
|
|
v=MathSqrt(v);
|
|
if(State.m_stpmax>0.0 && v>State.m_stpmax)
|
|
bflag=false;
|
|
}
|
|
else
|
|
bflag=false;
|
|
if(!bflag)
|
|
{
|
|
//--- Solution failed:
|
|
//--- try to increase lambda to make matrix positive definite and continue.
|
|
if(!IncreaseLambda(lambdav,nu))
|
|
{
|
|
iflag=-1;
|
|
nu=1;
|
|
return(false);
|
|
}
|
|
continue;
|
|
}
|
|
//--- Step in State.XDir and it is bounded by StpMax.
|
|
//--- We should check stopping conditions on step size here.
|
|
//--- DeltaX, which is used for secant updates, is initialized here.
|
|
//--- This code is a bit tricky because sometimes XDir<>0, but
|
|
//--- it is so small that XDir+XBase==XBase (in finite precision
|
|
//--- arithmetics). So we set DeltaX to XBase, then
|
|
//--- add XDir, and then subtract XBase to get exact value of
|
|
//--- DeltaX.
|
|
//--- Step length is estimated using DeltaX.
|
|
//--- NOTE: stopping conditions are tested
|
|
//--- for fresh models only (ModelAge=0)
|
|
deltax=xnew-State.m_xbase+0;
|
|
deltaxready=true;
|
|
v=MathPow(deltax/State.m_s+0,2.0).Sum();
|
|
v=MathSqrt(v);
|
|
if(v<=State.m_epsx)
|
|
{
|
|
if(State.m_modelage==0)
|
|
{
|
|
//--- Step is too short, model is fresh and we can rely on it.
|
|
//--- Terminating.
|
|
iflag=2;
|
|
nu=1;
|
|
return(false);
|
|
}
|
|
else
|
|
{
|
|
//--- Step is suspiciously short, but model is not fresh
|
|
//--- and we can't rely on it.
|
|
iflag=-2;
|
|
nu=1;
|
|
return(false);
|
|
}
|
|
}
|
|
//--- Let's evaluate new step:
|
|
//--- a) if we have Fi vector, we evaluate it using rcomm, and
|
|
//--- then we manually calculate State.F as sum of squares of Fi[]
|
|
//--- b) if we have F value, we just evaluate it through rcomm interface
|
|
//--- We prefer (a) because we may need Fi vector for additional
|
|
//--- iterations
|
|
State.m_x=xnew;
|
|
State.m_needf=false;
|
|
State.m_needfi=false;
|
|
if(!State.m_hasfi)
|
|
{
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=1;
|
|
}
|
|
else
|
|
{
|
|
State.m_needfi=true;
|
|
State.m_rstate.stage=0;
|
|
}
|
|
break;
|
|
}
|
|
//--- Saving State
|
|
State.m_rstate.ba[0]=bflag;
|
|
State.m_rstate.ia.Set(0,i);
|
|
State.m_rstate.ia.Set(1,n);
|
|
State.m_rstate.ia.Set(2,m);
|
|
State.m_rstate.ra.Set(0,v);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| NOTES: |
|
|
//| 1. Depending on function used to create State structure, this |
|
|
//| algorithm may accept Jacobian and/or Hessian and/or gradient. |
|
|
//| According to the said above, there ase several versions of |
|
|
//| this function, which accept different sets of callbacks. |
|
|
//| This flexibility opens way to subtle errors - you may create |
|
|
//| State with MinLMCreateFGH() (optimization using Hessian), but |
|
|
//| call function which does not accept Hessian. So when |
|
|
//| algorithm will request Hessian, there will be no callback to |
|
|
//| call. In this case exception will be thrown. |
|
|
//| Be careful to avoid such errors because there is no way to |
|
|
//| find them at compile time - you can see them at runtime only. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinLM::MinLMIteration(CMinLMState &State)
|
|
{
|
|
//--- create variables
|
|
int n=0;
|
|
int m=0;
|
|
bool bflag=false;
|
|
int iflag=0;
|
|
double v=0;
|
|
double s=0;
|
|
double t=0;
|
|
double fnew=0;
|
|
int i=0;
|
|
int k=0;
|
|
int i_=0;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
n=State.m_rstate.ia[0];
|
|
m=State.m_rstate.ia[1];
|
|
iflag=State.m_rstate.ia[2];
|
|
i=State.m_rstate.ia[3];
|
|
k=State.m_rstate.ia[4];
|
|
bflag=State.m_rstate.ba[0];
|
|
v=State.m_rstate.ra[0];
|
|
s=State.m_rstate.ra[1];
|
|
t=State.m_rstate.ra[2];
|
|
fnew=State.m_rstate.ra[3];
|
|
}
|
|
else
|
|
{
|
|
n=359;
|
|
m=-58;
|
|
iflag=-919;
|
|
i=-909;
|
|
k=81;
|
|
bflag=true;
|
|
v=74;
|
|
s=-788;
|
|
t=809;
|
|
fnew=205;
|
|
}
|
|
//--- check stage
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
case 4:
|
|
label=4;
|
|
break;
|
|
case 5:
|
|
label=5;
|
|
break;
|
|
case 6:
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
label=7;
|
|
break;
|
|
case 8:
|
|
label=8;
|
|
break;
|
|
case 9:
|
|
label=9;
|
|
break;
|
|
case 10:
|
|
label=10;
|
|
break;
|
|
case 11:
|
|
label=11;
|
|
break;
|
|
case 12:
|
|
label=12;
|
|
break;
|
|
case 13:
|
|
label=13;
|
|
break;
|
|
case 14:
|
|
label=14;
|
|
break;
|
|
case 15:
|
|
label=15;
|
|
break;
|
|
case 16:
|
|
label=16;
|
|
break;
|
|
case 17:
|
|
label=17;
|
|
break;
|
|
case 18:
|
|
label=18;
|
|
break;
|
|
case 19:
|
|
label=19;
|
|
break;
|
|
case 20:
|
|
label=20;
|
|
break;
|
|
case 21:
|
|
label=21;
|
|
break;
|
|
case 22:
|
|
label=22;
|
|
break;
|
|
case 23:
|
|
label=23;
|
|
break;
|
|
case 24:
|
|
label=24;
|
|
break;
|
|
case 25:
|
|
label=25;
|
|
break;
|
|
case 26:
|
|
label=26;
|
|
break;
|
|
case 27:
|
|
label=27;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
//--- prepare
|
|
n=State.m_n;
|
|
m=State.m_m;
|
|
State.m_repiterationscount=0;
|
|
State.m_repterminationtype=0;
|
|
State.m_repnfunc=0;
|
|
State.m_repnjac=0;
|
|
State.m_repngrad=0;
|
|
State.m_repnhess=0;
|
|
State.m_repncholesky=0;
|
|
State.m_userterminationneeded=false;
|
|
if(m>0)
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,n,m,false);
|
|
State.m_lastscaleused=State.m_s;
|
|
//--- Prepare LM step finder and enforce/check feasibility of constraints
|
|
if(!MinLMStepFinderInit(State.m_finderstate,n,m,State.m_maxmodelage,State.m_hasfi,State.m_xbase,State.m_bndl,State.m_bndu,State.m_cleic,State.m_nec,State.m_nic,State.m_s,State.m_stpmax,State.m_epsx))
|
|
{
|
|
State.m_repterminationtype=-3;
|
|
return(false);
|
|
}
|
|
//--- set constraints for obsolete QP solver
|
|
CMinQP::MinQPSetBC(State.m_qpstate,State.m_bndl,State.m_bndu);
|
|
//--- Check correctness of the analytic Jacobian
|
|
ClearRequestFields(State);
|
|
if(!(State.m_algomode==1 && State.m_teststep>0.0))
|
|
{
|
|
label=28;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
label=30;
|
|
break;
|
|
}
|
|
//--- main loop
|
|
while(label>=0)
|
|
switch(label)
|
|
{
|
|
case 30:
|
|
if(!COptServ::SmoothnessMonitorCheckGradientATX0(State.m_smonitor,State.m_xbase,State.m_s,State.m_bndl,State.m_bndu,true,State.m_teststep))
|
|
{
|
|
label=31;
|
|
break;
|
|
}
|
|
State.m_x=State.m_smonitor.m_x;
|
|
State.m_needfij=true;
|
|
State.m_rstate.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
State.m_needfij=false;
|
|
State.m_smonitor.m_fi=State.m_fi;
|
|
State.m_smonitor.m_j=State.m_j;
|
|
label=30;
|
|
break;
|
|
case 31:
|
|
case 28:
|
|
//--- Initial report of current point
|
|
//--- Note 1: we rewrite State.X twice because
|
|
//--- user may accidentally change it after first call.
|
|
//--- Note 2: we set NeedF or NeedFI depending on what
|
|
//--- information about function we have.
|
|
if(!State.m_xrep)
|
|
{
|
|
label=32;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
if(!State.m_hasf)
|
|
{
|
|
label=34;
|
|
break;
|
|
}
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_needf=false;
|
|
label=35;
|
|
break;
|
|
case 34:
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hasfi,__FUNCTION__+": internal error 2!"))
|
|
return(false);
|
|
State.m_needfi=true;
|
|
State.m_rstate.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_needfi=false;
|
|
v=CAblasF::RDotV2(m,State.m_fi);
|
|
State.m_f=v;
|
|
case 35:
|
|
State.m_repnfunc=State.m_repnfunc+1;
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_xupdated=false;
|
|
case 32:
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
State.m_x=State.m_xbase;
|
|
State.m_repterminationtype=8;
|
|
return(false);
|
|
}
|
|
//--- Prepare control variables
|
|
State.m_nu=1;
|
|
State.m_lambdav=-CMath::m_maxrealnumber;
|
|
State.m_modelage=State.m_maxmodelage+1;
|
|
State.m_deltaxready=false;
|
|
State.m_deltafready=false;
|
|
if(State.m_algomode==2)
|
|
{
|
|
label=36;
|
|
break;
|
|
}
|
|
//--- Jacobian-based optimization mode
|
|
//--- Main cycle.
|
|
//--- We move through it until either:
|
|
//--- * one of the stopping conditions is met
|
|
//--- * we decide that stopping conditions are too stringent
|
|
//--- and break from cycle
|
|
case 38:
|
|
//--- First, we have to prepare quadratic model for our function.
|
|
//--- We use BFlag to ensure that model is prepared;
|
|
//--- if it is false at the end of this block, something went wrong.
|
|
//--- We may either calculate brand new model or update old one.
|
|
//--- Before this block we have:
|
|
//--- * State.XBase - current position.
|
|
//--- * State.DeltaX - if DeltaXReady is True
|
|
//--- * State.DeltaF - if DeltaFReady is True
|
|
//--- After this block is over, we will have:
|
|
//--- * State.XBase - base point (unchanged)
|
|
//--- * State.FBase - F(XBase)
|
|
//--- * State.GBase - linear term
|
|
//--- * State.QuadraticModel - quadratic term
|
|
//--- * State.LambdaV - current estimate for lambda
|
|
//--- We also clear DeltaXReady/DeltaFReady flags
|
|
//--- after initialization is done.
|
|
//--- check
|
|
if(!CAp::Assert(State.m_algomode==0 || State.m_algomode==1,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
if(!(State.m_modelage>State.m_maxmodelage || !(State.m_deltaxready && State.m_deltafready)))
|
|
{
|
|
label=40;
|
|
break;
|
|
}
|
|
//--- Refresh model (using either finite differences or analytic Jacobian)
|
|
if(State.m_algomode!=0)
|
|
{
|
|
label=42;
|
|
break;
|
|
}
|
|
//--- Optimization using F values only.
|
|
//--- Use finite differences to estimate Jacobian.
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hasfi,__FUNCTION__+": internal error when estimating Jacobian (no f[])"))
|
|
return(false);
|
|
k=0;
|
|
case 44:
|
|
if(k>n-1)
|
|
{
|
|
label=46;
|
|
break;
|
|
}
|
|
//--- We guard X[k] from leaving [BndL,BndU].
|
|
//--- In case BndL=BndU, we assume that derivative in this direction is zero.
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,-State.m_s[k]*State.m_diffstep);
|
|
if(State.m_havebndl[k])
|
|
State.m_x.Set(k,MathMax(State.m_x[k],State.m_bndl[k]));
|
|
if(State.m_havebndu[k])
|
|
State.m_x.Set(k,MathMin(State.m_x[k],State.m_bndu[k]));
|
|
State.m_xm1=State.m_x[k];
|
|
ClearRequestFields(State);
|
|
State.m_needfi=true;
|
|
State.m_rstate.stage=4;
|
|
label=-1;
|
|
break;
|
|
case 4:
|
|
State.m_repnfunc++;
|
|
State.m_fm1=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,State.m_s[k]*State.m_diffstep);
|
|
if(State.m_havebndl[k])
|
|
State.m_x.Set(k,MathMax(State.m_x[k],State.m_bndl[k]));
|
|
if(State.m_havebndu[k])
|
|
State.m_x.Set(k,MathMin(State.m_x[k],State.m_bndu[k]));
|
|
State.m_xp1=State.m_x[k];
|
|
ClearRequestFields(State);
|
|
State.m_needfi=true;
|
|
State.m_rstate.stage=5;
|
|
label=-1;
|
|
break;
|
|
case 5:
|
|
State.m_repnfunc++;
|
|
State.m_fp1=State.m_fi;
|
|
v=State.m_xp1-State.m_xm1;
|
|
if(v!=0.0)
|
|
{
|
|
v=1/v;
|
|
State.m_j.Col(k,(State.m_fp1-State.m_fm1)*v);
|
|
}
|
|
else
|
|
State.m_j.Col(k,vector<double>::Zeros(m));
|
|
k++;
|
|
label=44;
|
|
break;
|
|
case 46:
|
|
//--- Calculate F(XBase)
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
State.m_needfi=true;
|
|
State.m_rstate.stage=6;
|
|
label=-1;
|
|
break;
|
|
case 6:
|
|
State.m_needfi=false;
|
|
State.m_repnfunc++;
|
|
State.m_repnjac++;
|
|
//--- New model
|
|
State.m_modelage=0;
|
|
label=41;
|
|
break;
|
|
case 42:
|
|
//--- Obtain f[] and Jacobian
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
State.m_needfij=true;
|
|
State.m_rstate.stage=7;
|
|
label=-1;
|
|
break;
|
|
case 7:
|
|
State.m_needfij=false;
|
|
State.m_repnfunc++;
|
|
State.m_repnjac++;
|
|
//--- New model
|
|
State.m_modelage=0;
|
|
case 43:
|
|
label=41;
|
|
break;
|
|
case 40:
|
|
//--- State.J contains Jacobian or its current approximation;
|
|
//--- refresh it using secant updates:
|
|
//--- f(x0+dx) = f(x0) + J*dx,
|
|
//--- J_new = J_old + u*h'
|
|
//--- h = x_new-x_old
|
|
//--- u = (f_new - f_old - J_old*h)/(h'h)
|
|
//--- We can explicitly generate h and u, but it is
|
|
//--- preferential to do in-place calculations. Only
|
|
//--- I-th row of J_old is needed to calculate u[I],
|
|
//--- so we can update J row by row in one pass.
|
|
//--- NOTE: we expect that State.XBase contains new point,
|
|
//--- State.FBase contains old point, State.DeltaX and
|
|
//--- State.DeltaY contain updates from last step.
|
|
//--- check
|
|
if(!CAp::Assert(State.m_deltaxready && State.m_deltafready,__FUNCTION__+": uninitialized DeltaX/DeltaF"))
|
|
return(false);
|
|
t=CAblasF::RDotV2(n,State.m_deltax);
|
|
//--- check
|
|
if(!CAp::Assert(t!=0.0,__FUNCTION__+": internal error (T=0)"))
|
|
return(false);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
v=CAblasF::RDotVR(n,State.m_deltax,State.m_j,i);
|
|
v=(State.m_deltaf[i]-v)/t;
|
|
State.m_j.Row(i,State.m_j[i]+State.m_deltax*v);
|
|
}
|
|
State.m_fi=State.m_fibase+State.m_deltaf+0;
|
|
//--- Increase model age
|
|
State.m_modelage++;
|
|
case 41:
|
|
CAblas::RMatrixGemm(n,n,m,2.0,State.m_j,0,0,1,State.m_j,0,0,0,0.0,State.m_quadraticmodel,0,0);
|
|
CAblas::RMatrixMVect(n,m,State.m_j,0,0,1,State.m_fi,0,State.m_gbase,0);
|
|
State.m_gbase*=2.0;
|
|
v=CAblasF::RDotV2(m,State.m_fi);
|
|
State.m_fbase=v;
|
|
State.m_fibase=State.m_fi;
|
|
State.m_deltaxready=false;
|
|
State.m_deltafready=false;
|
|
//--- Perform integrity check (presense of NAN/INF)
|
|
for(i=0; i<n; i++)
|
|
v=0.1*v+State.m_gbase[i];
|
|
if(!CMath::IsFinite(v))
|
|
{
|
|
//--- Break!
|
|
State.m_repterminationtype=-8;
|
|
return(false);
|
|
}
|
|
//--- If Lambda is not initialized, initialize it using quadratic model
|
|
if(State.m_lambdav<0.0)
|
|
{
|
|
State.m_lambdav=0;
|
|
for(i=0; i<n; i++)
|
|
State.m_lambdav=MathMax(State.m_lambdav,MathAbs(State.m_quadraticmodel.Get(i,i))*CMath::Sqr(State.m_s[i]));
|
|
State.m_lambdav=0.001*State.m_lambdav;
|
|
if(State.m_lambdav==0.0)
|
|
State.m_lambdav=1;
|
|
}
|
|
//--- Find value of Levenberg-Marquardt damping parameter which:
|
|
//--- * leads to positive definite damped model
|
|
//--- * within bounds specified by StpMax
|
|
//--- * generates step which decreases function value
|
|
//--- After this block IFlag is set to:
|
|
//--- * -8, if internal integrity control detected NAN/INF in function values
|
|
//--- * -3, if constraints are infeasible
|
|
//--- * -2, if model update is needed (either Lambda growth is too large
|
|
//--- or step is too short, but we can't rely on model and stop iterations)
|
|
//--- * -1, if model is fresh, Lambda have grown too large, termination is needed
|
|
//--- * 0, if everything is OK, continue iterations
|
|
//--- * >0, successful termination, step is less than EpsX
|
|
//--- State.Nu can have any value on enter, but after exit it is set to 1.0
|
|
iflag=-99;
|
|
MinLMStepFinderStart(State.m_finderstate,State.m_quadraticmodel,State.m_gbase,State.m_fbase,State.m_xbase,State.m_fibase,State.m_modelage);
|
|
case 47:
|
|
if(!MinLMStepFinderIteration(State.m_finderstate,State.m_lambdav,State.m_nu,State.m_xnew,State.m_deltax,State.m_deltaxready,State.m_deltaf,State.m_deltafready,iflag,fnew,State.m_repncholesky))
|
|
{
|
|
label=48;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hasfi || State.m_hasf,__FUNCTION__+": internal error 2!"))
|
|
return(false);
|
|
State.m_repnfunc++;
|
|
ClearRequestFields(State);
|
|
if(!State.m_finderstate.m_needfi)
|
|
{
|
|
label=49;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hasfi,__FUNCTION__+": internal error 2!"))
|
|
return(false);
|
|
State.m_x=State.m_finderstate.m_x;
|
|
State.m_needfi=true;
|
|
State.m_rstate.stage=8;
|
|
label=-1;
|
|
break;
|
|
case 8:
|
|
State.m_needfi=false;
|
|
State.m_finderstate.m_fi=State.m_fi;
|
|
label=47;
|
|
break;
|
|
case 49:
|
|
if(!State.m_finderstate.m_needf)
|
|
{
|
|
label=51;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hasf,__FUNCTION__+": internal error 2!"))
|
|
return(false);
|
|
State.m_x=State.m_finderstate.m_x;
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=9;
|
|
label=-1;
|
|
break;
|
|
case 9:
|
|
State.m_needf=false;
|
|
State.m_finderstate.m_f=State.m_f;
|
|
label=47;
|
|
break;
|
|
case 51:
|
|
CAp::Assert(false,__FUNCTION__+": internal error 2!");
|
|
return(false);
|
|
case 48:
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
State.m_x=State.m_xbase;
|
|
State.m_repterminationtype=8;
|
|
return(false);
|
|
}
|
|
State.m_nu=1;
|
|
//--- check
|
|
if(!CAp::Assert((iflag>=-3 && iflag<=0) || iflag==-8 || iflag>0,__FUNCTION__+": internal integrity check failed!"))
|
|
return(false);
|
|
if(iflag==-3)
|
|
{
|
|
State.m_repterminationtype=-3;
|
|
return(false);
|
|
}
|
|
if(iflag==-2)
|
|
{
|
|
State.m_modelage=State.m_maxmodelage+1;
|
|
label=38;
|
|
break;
|
|
}
|
|
if(iflag!=-1)
|
|
{
|
|
label=53;
|
|
break;
|
|
}
|
|
//--- Stopping conditions are too stringent
|
|
State.m_repterminationtype=7;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=55;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
State.m_f=State.m_fbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=10;
|
|
label=-1;
|
|
break;
|
|
case 10:
|
|
State.m_xupdated=false;
|
|
case 55:
|
|
return(false);
|
|
case 53:
|
|
if(!(iflag==-8 || iflag>0))
|
|
{
|
|
label=57;
|
|
break;
|
|
}
|
|
//--- Either:
|
|
//--- * Integrity check failed - infinities or NANs
|
|
//--- * successful termination (step size is small enough)
|
|
State.m_repterminationtype=iflag;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=59;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
State.m_f=State.m_fbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=11;
|
|
label=-1;
|
|
break;
|
|
case 11:
|
|
State.m_xupdated=false;
|
|
case 59:
|
|
return(false);
|
|
case 57:
|
|
State.m_f=fnew;
|
|
//--- Levenberg-Marquardt step is ready.
|
|
//--- Compare predicted vs. actual decrease and decide what to do with lambda.
|
|
//--- NOTE: we expect that State.DeltaX contains direction of step,
|
|
//--- State.F contains function value at new point.
|
|
//--- check
|
|
if(!CAp::Assert(State.m_deltaxready,__FUNCTION__+": deltaX is not ready"))
|
|
return(false);
|
|
iflag=CheckDecrease(State.m_quadraticmodel,State.m_gbase,State.m_fbase,n,State.m_deltax,State.m_f,State.m_lambdav,State.m_nu);
|
|
if(iflag==0)
|
|
{
|
|
label=61;
|
|
break;
|
|
}
|
|
State.m_repterminationtype=iflag;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=63;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
State.m_f=State.m_fbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=12;
|
|
label=-1;
|
|
break;
|
|
case 12:
|
|
State.m_xupdated=false;
|
|
case 63:
|
|
return(false);
|
|
case 61:
|
|
//--- Accept step, report it and
|
|
//--- test stopping conditions on iterations count and function decrease.
|
|
//--- NOTE: we expect that State.DeltaX contains direction of step,
|
|
//--- State.F contains function value at new point.
|
|
//--- NOTE2: we should update XBase ONLY. In the beginning of the next
|
|
//--- iteration we expect that State.FIBase is NOT updated and
|
|
//--- contains old value of a function vector.
|
|
State.m_xbase=State.m_xnew;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=65;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=13;
|
|
label=-1;
|
|
break;
|
|
case 13:
|
|
State.m_xupdated=false;
|
|
case 65:
|
|
State.m_repiterationscount++;
|
|
if(State.m_repiterationscount>=State.m_maxits && State.m_maxits>0)
|
|
State.m_repterminationtype=5;
|
|
if(State.m_repterminationtype<=0)
|
|
{
|
|
label=67;
|
|
break;
|
|
}
|
|
if(!State.m_xrep)
|
|
{
|
|
label=69;
|
|
break;
|
|
}
|
|
//--- Report: XBase contains new point, F contains function value at new point
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=14;
|
|
label=-1;
|
|
break;
|
|
case 14:
|
|
State.m_xupdated=false;
|
|
case 69:
|
|
return(false);
|
|
case 67:
|
|
State.m_modelage=State.m_modelage+1;
|
|
label=38;
|
|
break;
|
|
case 39:
|
|
//--- Lambda is too large, we have to break iterations.
|
|
State.m_repterminationtype=7;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=71;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
State.m_f=State.m_fbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=15;
|
|
label=-1;
|
|
break;
|
|
case 15:
|
|
State.m_xupdated=false;
|
|
case 71:
|
|
label=37;
|
|
break;
|
|
case 36:
|
|
//--- Legacy Hessian-based mode
|
|
//--- Main cycle.
|
|
//--- We move through it until either:
|
|
//--- * one of the stopping conditions is met
|
|
//--- * we decide that stopping conditions are too stringent
|
|
//--- and break from cycle
|
|
if(State.m_nec+State.m_nic>0)
|
|
{
|
|
//--- FGH solver does not support general linear constraints
|
|
State.m_repterminationtype=-5;
|
|
return(false);
|
|
}
|
|
case 73:
|
|
//--- First, we have to prepare quadratic model for our function.
|
|
//--- We use BFlag to ensure that model is prepared;
|
|
//--- if it is false at the end of this block, something went wrong.
|
|
//--- We may either calculate brand new model or update old one.
|
|
//--- Before this block we have:
|
|
//--- * State.XBase - current position.
|
|
//--- * State.DeltaX - if DeltaXReady is True
|
|
//--- * State.DeltaF - if DeltaFReady is True
|
|
//--- After this block is over, we will have:
|
|
//--- * State.XBase - base point (unchanged)
|
|
//--- * State.FBase - F(XBase)
|
|
//--- * State.GBase - linear term
|
|
//--- * State.QuadraticModel - quadratic term
|
|
//--- * State.LambdaV - current estimate for lambda
|
|
//--- We also clear DeltaXReady/DeltaFReady flags
|
|
//--- after initialization is done.
|
|
bflag=false;
|
|
if(!(State.m_algomode==0 || State.m_algomode==1))
|
|
{
|
|
label=75;
|
|
break;
|
|
}
|
|
//--- Calculate f[] and Jacobian
|
|
if(!(State.m_modelage>State.m_maxmodelage || !(State.m_deltaxready && State.m_deltafready)))
|
|
{
|
|
label=77;
|
|
break;
|
|
}
|
|
//--- Refresh model (using either finite differences or analytic Jacobian)
|
|
if(State.m_algomode!=0)
|
|
{
|
|
label=79;
|
|
break;
|
|
}
|
|
//--- Optimization using F values only.
|
|
//--- Use finite differences to estimate Jacobian.
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hasfi,__FUNCTION__+": internal error when estimating Jacobian (no f[])"))
|
|
return(false);
|
|
k=0;
|
|
case 81:
|
|
if(k>n-1)
|
|
{
|
|
label=83;
|
|
break;
|
|
}
|
|
//--- We guard X[k] from leaving [BndL,BndU].
|
|
//--- In case BndL=BndU, we assume that derivative in this direction is zero.
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,-State.m_s[k]*State.m_diffstep);
|
|
if(State.m_havebndl[k])
|
|
State.m_x.Set(k,MathMax(State.m_x[k],State.m_bndl[k]));
|
|
if(State.m_havebndu[k])
|
|
State.m_x.Set(k,MathMin(State.m_x[k],State.m_bndu[k]));
|
|
State.m_xm1=State.m_x[k];
|
|
ClearRequestFields(State);
|
|
State.m_needfi=true;
|
|
State.m_rstate.stage=16;
|
|
label=-1;
|
|
break;
|
|
case 16:
|
|
State.m_repnfunc++;
|
|
State.m_fm1=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,State.m_s[k]*State.m_diffstep);
|
|
if(State.m_havebndl[k])
|
|
State.m_x.Set(k,MathMax(State.m_x[k],State.m_bndl[k]));
|
|
if(State.m_havebndu[k])
|
|
State.m_x.Set(k,MathMin(State.m_x[k],State.m_bndu[k]));
|
|
State.m_xp1=State.m_x[k];
|
|
ClearRequestFields(State);
|
|
State.m_needfi=true;
|
|
State.m_rstate.stage=17;
|
|
label=-1;
|
|
break;
|
|
case 17:
|
|
State.m_repnfunc++;
|
|
State.m_fp1=State.m_fi;
|
|
v=State.m_xp1-State.m_xm1;
|
|
if(v!=0.0)
|
|
{
|
|
v=1/v;
|
|
State.m_j.Col(k,(State.m_fp1-State.m_fm1)*v);
|
|
}
|
|
else
|
|
State.m_j.Col(k,vector<double>::Zeros(m));
|
|
k++;
|
|
label=81;
|
|
break;
|
|
case 83:
|
|
//--- Calculate F(XBase)
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
State.m_needfi=true;
|
|
State.m_rstate.stage=18;
|
|
label=-1;
|
|
break;
|
|
case 18:
|
|
State.m_needfi=false;
|
|
State.m_repnfunc++;
|
|
State.m_repnjac++;
|
|
//--- New model
|
|
State.m_modelage=0;
|
|
label=80;
|
|
break;
|
|
case 79:
|
|
//--- Obtain f[] and Jacobian
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
State.m_needfij=true;
|
|
State.m_rstate.stage=19;
|
|
label=-1;
|
|
break;
|
|
case 19:
|
|
State.m_needfij=false;
|
|
State.m_repnfunc++;
|
|
State.m_repnjac++;
|
|
//--- New model
|
|
State.m_modelage=0;
|
|
case 80:
|
|
label=78;
|
|
break;
|
|
case 77:
|
|
//--- State.J contains Jacobian or its current approximation;
|
|
//--- refresh it using secant updates:
|
|
//--- f(x0+dx) = f(x0) + J*dx,
|
|
//--- J_new = J_old + u*h'
|
|
//--- h = x_new-x_old
|
|
//--- u = (f_new - f_old - J_old*h)/(h'h)
|
|
//--- We can explicitly generate h and u, but it is
|
|
//--- preferential to do in-place calculations. Only
|
|
//--- I-th row of J_old is needed to calculate u[I],
|
|
//--- so we can update J row by row in one pass.
|
|
//--- NOTE: we expect that State.XBase contains new point,
|
|
//--- State.FBase contains old point, State.DeltaX and
|
|
//--- State.DeltaY contain updates from last step.
|
|
//--- check
|
|
if(!CAp::Assert(State.m_deltaxready && State.m_deltafready,__FUNCTION__+": uninitialized DeltaX/DeltaF"))
|
|
return(false);
|
|
t=CAblasF::RDotV2(n,State.m_deltax);
|
|
//--- check
|
|
if(!CAp::Assert(t!=0.0,__FUNCTION__+": internal error (T=0)"))
|
|
return(false);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
v=CAblasF::RDotVR(n,State.m_deltax,State.m_j,i);
|
|
v=(State.m_deltaf[i]-v)/t;
|
|
State.m_j.Row(i,State.m_j[i]+State.m_deltax*v);
|
|
}
|
|
State.m_fi=State.m_fibase+State.m_deltaf+0;
|
|
//--- Increase model age
|
|
State.m_modelage++;
|
|
case 78:
|
|
//--- Generate quadratic model:
|
|
//--- f(xbase+dx) =
|
|
//--- = (f0 + J*dx)'(f0 + J*dx)
|
|
//--- = f0^2 + dx'J'f0 + f0*J*dx + dx'J'J*dx
|
|
//--- = f0^2 + 2*f0*J*dx + dx'J'J*dx
|
|
//--- Note that we calculate 2*(J'J) instead of J'J because
|
|
//--- our quadratic model is based on Tailor decomposition,
|
|
//--- i.e. it has 0.5 before quadratic term.
|
|
CAblas::RMatrixGemm(n,n,m,2.0,State.m_j,0,0,1,State.m_j,0,0,0,0.0,State.m_quadraticmodel,0,0);
|
|
CAblas::RMatrixMVect(n,m,State.m_j,0,0,1,State.m_fi,0,State.m_gbase,0);
|
|
State.m_gbase*=2;
|
|
v=CAblasF::RDotV2(m,State.m_fi);
|
|
State.m_fbase=v;
|
|
State.m_fibase=State.m_fi;
|
|
//--- set control variables
|
|
bflag=true;
|
|
case 75:
|
|
if(State.m_algomode!=2)
|
|
{
|
|
label=84;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(!State.m_hasfi,__FUNCTION__+": internal error (HasFI is True in Hessian-based mode)"))
|
|
return(false);
|
|
//--- Obtain F, G, H
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
State.m_needfgh=true;
|
|
State.m_rstate.stage=20;
|
|
label=-1;
|
|
break;
|
|
case 20:
|
|
State.m_needfgh=false;
|
|
State.m_repnfunc++;
|
|
State.m_repngrad++;
|
|
State.m_repnhess++;
|
|
CAblas::RMatrixCopy(n,n,State.m_h,0,0,State.m_quadraticmodel,0,0);
|
|
State.m_gbase=State.m_g;
|
|
State.m_fbase=State.m_f;
|
|
//--- set control variables
|
|
bflag=true;
|
|
State.m_modelage=0;
|
|
case 84:
|
|
//--- check
|
|
if(!CAp::Assert(bflag,__FUNCTION__+": internal integrity check failed!"))
|
|
return(false);
|
|
State.m_deltaxready=false;
|
|
State.m_deltafready=false;
|
|
//--- Perform integrity check (presense of NAN/INF)
|
|
v=State.m_fbase;
|
|
for(i=0; i<n; i++)
|
|
v=0.1*v+State.m_gbase[i];
|
|
if(!CMath::IsFinite(v))
|
|
{
|
|
//--- Break!
|
|
State.m_repterminationtype=-8;
|
|
return(false);
|
|
}
|
|
//--- If Lambda is not initialized, initialize it using quadratic model
|
|
if(State.m_lambdav<0.0)
|
|
{
|
|
State.m_lambdav=0;
|
|
for(i=0; i<n; i++)
|
|
State.m_lambdav=MathMax(State.m_lambdav,MathAbs(State.m_quadraticmodel.Get(i,i))*CMath::Sqr(State.m_s[i]));
|
|
State.m_lambdav=0.001*State.m_lambdav;
|
|
if(State.m_lambdav==0.0)
|
|
State.m_lambdav=1;
|
|
}
|
|
//--- Find value of Levenberg-Marquardt damping parameter which:
|
|
//--- * leads to positive definite damped model
|
|
//--- * within bounds specified by StpMax
|
|
//--- * generates step which decreases function value
|
|
//--- After this block IFlag is set to:
|
|
//--- * -3, if constraints are infeasible
|
|
//--- * -2, if model update is needed (either Lambda growth is too large
|
|
//--- or step is too short, but we can't rely on model and stop iterations)
|
|
//--- * -1, if model is fresh, Lambda have grown too large, termination is needed
|
|
//--- * 0, if everything is OK, continue iterations
|
|
//--- State.Nu can have any value on enter, but after exit it is set to 1.0
|
|
iflag=-99;
|
|
case 86:
|
|
//--- Do we need model update?
|
|
if(State.m_modelage>0 && State.m_nu>=m_suspiciousnu)
|
|
{
|
|
iflag=-2;
|
|
label=87;
|
|
break;
|
|
}
|
|
//--- Setup quadratic solver and solve quadratic programming problem.
|
|
//--- After problem is solved we'll try to bound step by StpMax
|
|
//--- (Lambda will be increased if step size is too large).
|
|
//--- We use BFlag variable to indicate that we have to increase Lambda.
|
|
//--- If it is False, we will try to increase Lambda and move to new iteration.
|
|
bflag=true;
|
|
CMinQP::MinQPSetStartingPointFast(State.m_qpstate,State.m_xbase);
|
|
CMinQP::MinQPSetOriginFast(State.m_qpstate,State.m_xbase);
|
|
CMinQP::MinQPSetLinearTermFast(State.m_qpstate,State.m_gbase);
|
|
CMinQP::MinQPSetQuadraticTermFast(State.m_qpstate,State.m_quadraticmodel,true,0.0);
|
|
State.m_tmp0=State.m_quadraticmodel.Diag()+State.m_s.Pow(-2.0)*State.m_lambdav;
|
|
CMinQP::MinQPRewriteDiagonal(State.m_qpstate,State.m_tmp0);
|
|
CMinQP::MinQPOptimize(State.m_qpstate);
|
|
CMinQP::MinQPResultsBuf(State.m_qpstate,State.m_xdir,State.m_qprep);
|
|
if(State.m_qprep.m_terminationtype>0)
|
|
{
|
|
//--- successful solution of QP problem
|
|
State.m_xdir-=State.m_xbase;
|
|
v=CAblasF::RDotV2(n,State.m_xdir);
|
|
if(CMath::IsFinite(v))
|
|
{
|
|
v=MathSqrt(v);
|
|
if(State.m_stpmax>0.0 && v>State.m_stpmax)
|
|
bflag=false;
|
|
}
|
|
else
|
|
bflag=false;
|
|
}
|
|
else
|
|
{
|
|
//--- Either problem is non-convex (increase LambdaV) or constraints are inconsistent
|
|
//--- check
|
|
if(!CAp::Assert(State.m_qprep.m_terminationtype==-3 || State.m_qprep.m_terminationtype==-4 || State.m_qprep.m_terminationtype==-5,__FUNCTION__+": unexpected completion code from QP solver"))
|
|
return(false);
|
|
if(State.m_qprep.m_terminationtype==-3)
|
|
{
|
|
iflag=-3;
|
|
label=87;
|
|
break;
|
|
}
|
|
bflag=false;
|
|
}
|
|
if(!bflag)
|
|
{
|
|
//--- Solution failed:
|
|
//--- try to increase lambda to make matrix positive definite and continue.
|
|
if(!IncreaseLambda(State.m_lambdav,State.m_nu))
|
|
{
|
|
iflag=-1;
|
|
label=87;
|
|
break;
|
|
}
|
|
label=86;
|
|
break;
|
|
}
|
|
//--- Step in State.XDir and it is bounded by StpMax.
|
|
//--- We should check stopping conditions on step size here.
|
|
//--- DeltaX, which is used for secant updates, is initialized here.
|
|
//--- This code is a bit tricky because sometimes XDir<>0, but
|
|
//--- it is so small that XDir+XBase==XBase (in finite precision
|
|
//--- arithmetics). So we set DeltaX to XBase, then
|
|
//--- add XDir, and then subtract XBase to get exact value of
|
|
//--- DeltaX.
|
|
//--- Step length is estimated using DeltaX.
|
|
//--- NOTE: stopping conditions are tested
|
|
//--- for fresh models only (ModelAge=0)
|
|
State.m_deltax=State.m_xdir;
|
|
State.m_deltaxready=true;
|
|
v=MathPow(State.m_deltax/State.m_s+0,2.0).Sum();
|
|
v=MathSqrt(v);
|
|
if(v>State.m_epsx)
|
|
{
|
|
label=88;
|
|
break;
|
|
}
|
|
if(State.m_modelage!=0)
|
|
{
|
|
label=90;
|
|
break;
|
|
}
|
|
//--- Step is too short, model is fresh and we can rely on it.
|
|
//--- Terminating.
|
|
State.m_repterminationtype=2;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=92;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
State.m_f=State.m_fbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=21;
|
|
label=-1;
|
|
break;
|
|
case 21:
|
|
State.m_xupdated=false;
|
|
case 92:
|
|
return(false);
|
|
case 90:
|
|
//--- Step is suspiciously short, but model is not fresh
|
|
//--- and we can't rely on it.
|
|
iflag=-2;
|
|
label=87;
|
|
break;
|
|
case 91:
|
|
case 88:
|
|
//--- Let's evaluate new step:
|
|
//--- a) if we have Fi vector, we evaluate it using rcomm, and
|
|
//--- then we manually calculate State.F as sum of squares of Fi[]
|
|
//--- b) if we have F value, we just evaluate it through rcomm interface
|
|
//--- We prefer (a) because we may need Fi vector for additional
|
|
//--- iterations
|
|
//--- check
|
|
if(!CAp::Assert(State.m_hasfi || State.m_hasf,__FUNCTION__+": internal error 2!"))
|
|
return(false);
|
|
State.m_x=State.m_xbase+State.m_xdir+0;
|
|
ClearRequestFields(State);
|
|
if(!State.m_hasfi)
|
|
{
|
|
label=94;
|
|
break;
|
|
}
|
|
State.m_needfi=true;
|
|
State.m_rstate.stage=22;
|
|
label=-1;
|
|
break;
|
|
case 22:
|
|
State.m_needfi=false;
|
|
v=CAblasF::RDotV2(m,State.m_fi);
|
|
State.m_f=v;
|
|
State.m_deltaf=State.m_fi-State.m_fibase+0;
|
|
State.m_deltafready=true;
|
|
label=95;
|
|
break;
|
|
case 94:
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=23;
|
|
label=-1;
|
|
break;
|
|
case 23:
|
|
State.m_needf=false;
|
|
case 95:
|
|
State.m_repnfunc++;
|
|
if(!CMath::IsFinite(State.m_f))
|
|
{
|
|
//--- Integrity check failed, break!
|
|
State.m_repterminationtype=-8;
|
|
return(false);
|
|
}
|
|
if(State.m_f>=State.m_fbase)
|
|
{
|
|
//--- Increase lambda and continue
|
|
if(!IncreaseLambda(State.m_lambdav,State.m_nu))
|
|
{
|
|
iflag=-1;
|
|
label=87;
|
|
break;
|
|
}
|
|
label=86;
|
|
break;
|
|
}
|
|
//--- We've found our step!
|
|
iflag=0;
|
|
case 87:
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
State.m_x=State.m_xbase;
|
|
State.m_repterminationtype=8;
|
|
return(false);
|
|
}
|
|
State.m_nu=1;
|
|
//--- check
|
|
if(!CAp::Assert(iflag>=-3 && iflag<=0,__FUNCTION__+": internal integrity check failed!"))
|
|
return(false);
|
|
if(iflag==-3)
|
|
{
|
|
State.m_repterminationtype=-3;
|
|
return(false);
|
|
}
|
|
if(iflag==-2)
|
|
{
|
|
State.m_modelage=State.m_maxmodelage+1;
|
|
label=73;
|
|
break;
|
|
}
|
|
if(iflag==-1)
|
|
{
|
|
label=74;
|
|
break;
|
|
}
|
|
//--- Levenberg-Marquardt step is ready.
|
|
//--- Compare predicted vs. actual decrease and decide what to do with lambda.
|
|
//--- NOTE: we expect that State.DeltaX contains direction of step,
|
|
//--- State.F contains function value at new point.
|
|
//--- check
|
|
if(!CAp::Assert(State.m_deltaxready,__FUNCTION__+": deltaX is not ready"))
|
|
return(false);
|
|
t=0;
|
|
for(i=0; i<=n-1; i++)
|
|
{
|
|
v=CAblasF::RDotVR(n,State.m_deltax,State.m_quadraticmodel,i);
|
|
t+=State.m_deltax[i]*(State.m_gbase[i]+0.5*v);
|
|
}
|
|
State.m_predicteddecrease=-t;
|
|
State.m_actualdecrease=-(State.m_f-State.m_fbase);
|
|
if(State.m_predicteddecrease<=0.0)
|
|
{
|
|
label=74;
|
|
break;
|
|
}
|
|
v=State.m_actualdecrease/State.m_predicteddecrease;
|
|
if(v>=0.1)
|
|
{
|
|
label=96;
|
|
break;
|
|
}
|
|
if(IncreaseLambda(State.m_lambdav,State.m_nu))
|
|
{
|
|
label=98;
|
|
break;
|
|
}
|
|
//--- Lambda is too large, we have to break iterations.
|
|
State.m_repterminationtype=7;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=100;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
State.m_f=State.m_fbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=24;
|
|
label=-1;
|
|
break;
|
|
case 24:
|
|
State.m_xupdated=false;
|
|
case 100:
|
|
return(false);
|
|
case 98:
|
|
case 96:
|
|
if(v>0.5)
|
|
DecreaseLambda(State.m_lambdav,State.m_nu);
|
|
//--- Accept step, report it and
|
|
//--- test stopping conditions on iterations count and function decrease.
|
|
//--- NOTE: we expect that State.DeltaX contains direction of step,
|
|
//--- State.F contains function value at new point.
|
|
//--- NOTE2: we should update XBase ONLY. In the beginning of the next
|
|
//--- iteration we expect that State.FIBase is NOT updated and
|
|
//--- contains old value of a function vector.
|
|
State.m_xbase+=State.m_deltax;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=102;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=25;
|
|
label=-1;
|
|
break;
|
|
case 25:
|
|
State.m_xupdated=false;
|
|
case 102:
|
|
State.m_repiterationscount++;
|
|
if(State.m_repiterationscount>=State.m_maxits && State.m_maxits>0)
|
|
State.m_repterminationtype=5;
|
|
if(State.m_repterminationtype<=0)
|
|
{
|
|
label=104;
|
|
break;
|
|
}
|
|
if(!State.m_xrep)
|
|
{
|
|
label=106;
|
|
break;
|
|
}
|
|
//--- Report: XBase contains new point, F contains function value at new point
|
|
State.m_x=State.m_xbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=26;
|
|
label=-1;
|
|
break;
|
|
case 26:
|
|
State.m_xupdated=false;
|
|
case 106:
|
|
return(false);
|
|
case 104:
|
|
State.m_modelage++;
|
|
label=73;
|
|
break;
|
|
case 74:
|
|
//--- Lambda is too large, we have to break iterations.
|
|
State.m_repterminationtype=7;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=108;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
State.m_f=State.m_fbase;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=27;
|
|
label=-1;
|
|
break;
|
|
case 27:
|
|
State.m_xupdated=false;
|
|
case 108:
|
|
case 37:
|
|
return(false);
|
|
}
|
|
//--- Saving State
|
|
State.m_rstate.ba[0]=bflag;
|
|
State.m_rstate.ia.Set(0,n);
|
|
State.m_rstate.ia.Set(1,m);
|
|
State.m_rstate.ia.Set(2,iflag);
|
|
State.m_rstate.ia.Set(3,i);
|
|
State.m_rstate.ia.Set(4,k);
|
|
State.m_rstate.ra.Set(0,v);
|
|
State.m_rstate.ra.Set(1,s);
|
|
State.m_rstate.ra.Set(2,t);
|
|
State.m_rstate.ra.Set(3,fnew);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary class for CMinComp |
|
|
//+------------------------------------------------------------------+
|
|
class CMinASAState
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_n;
|
|
double m_epsg;
|
|
double m_epsf;
|
|
double m_epsx;
|
|
int m_maxits;
|
|
bool m_xrep;
|
|
double m_stpmax;
|
|
int m_cgtype;
|
|
int m_k;
|
|
int m_nfev;
|
|
int m_mcstage;
|
|
int m_curalgo;
|
|
int m_acount;
|
|
double m_mu;
|
|
double m_finit;
|
|
double m_dginit;
|
|
double m_fold;
|
|
double m_stp;
|
|
double m_laststep;
|
|
double m_f;
|
|
bool m_needfg;
|
|
bool m_xupdated;
|
|
RCommState m_rstate;
|
|
int m_repiterationscount;
|
|
int m_repnfev;
|
|
int m_repterminationtype;
|
|
int m_debugrestartscount;
|
|
CLinMinState m_lstate;
|
|
double m_betahs;
|
|
double m_betady;
|
|
//--- arrays
|
|
double m_bndl[];
|
|
double m_bndu[];
|
|
double m_ak[];
|
|
double m_xk[];
|
|
double m_dk[];
|
|
double m_an[];
|
|
double m_xn[];
|
|
double m_dn[];
|
|
double m_d[];
|
|
double m_work[];
|
|
double m_yk[];
|
|
double m_gc[];
|
|
double m_x[];
|
|
double m_g[];
|
|
//--- constructor, destructor
|
|
CMinASAState(void);
|
|
~CMinASAState(void) {}
|
|
//--- copy
|
|
void Copy(CMinASAState &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinASAState::CMinASAState(void)
|
|
{
|
|
m_n=0;
|
|
m_epsg=0;
|
|
m_epsf=0;
|
|
m_epsx=0;
|
|
m_maxits=0;
|
|
m_xrep=false;
|
|
m_stpmax=0;
|
|
m_cgtype=0;
|
|
m_k=0;
|
|
m_nfev=0;
|
|
m_mcstage=0;
|
|
m_curalgo=0;
|
|
m_acount=0;
|
|
m_mu=0;
|
|
m_finit=0;
|
|
m_dginit=0;
|
|
m_fold=0;
|
|
m_stp=0;
|
|
m_laststep=0;
|
|
m_f=0;
|
|
m_needfg=false;
|
|
m_xupdated=false;
|
|
m_repiterationscount=0;
|
|
m_repnfev=0;
|
|
m_repterminationtype=0;
|
|
m_debugrestartscount=0;
|
|
m_betahs=0;
|
|
m_betady=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinASAState::Copy(CMinASAState &obj)
|
|
{
|
|
//--- copy variables
|
|
m_n=obj.m_n;
|
|
m_epsg=obj.m_epsg;
|
|
m_epsf=obj.m_epsf;
|
|
m_epsx=obj.m_epsx;
|
|
m_maxits=obj.m_maxits;
|
|
m_xrep=obj.m_xrep;
|
|
m_stpmax=obj.m_stpmax;
|
|
m_cgtype=obj.m_cgtype;
|
|
m_k=obj.m_k;
|
|
m_nfev=obj.m_nfev;
|
|
m_mcstage=obj.m_mcstage;
|
|
m_curalgo=obj.m_curalgo;
|
|
m_acount=obj.m_acount;
|
|
m_mu=obj.m_mu;
|
|
m_finit=obj.m_finit;
|
|
m_dginit=obj.m_dginit;
|
|
m_fold=obj.m_fold;
|
|
m_stp=obj.m_stp;
|
|
m_laststep=obj.m_laststep;
|
|
m_f=obj.m_f;
|
|
m_needfg=obj.m_needfg;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_repiterationscount=obj.m_repiterationscount;
|
|
m_repnfev=obj.m_repnfev;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_debugrestartscount=obj.m_debugrestartscount;
|
|
m_betahs=obj.m_betahs;
|
|
m_betady=obj.m_betady;
|
|
m_rstate.Copy(obj.m_rstate);
|
|
m_lstate.Copy(obj.m_lstate);
|
|
//--- copy arrays
|
|
ArrayCopy(m_bndl,obj.m_bndl);
|
|
ArrayCopy(m_bndu,obj.m_bndu);
|
|
ArrayCopy(m_ak,obj.m_ak);
|
|
ArrayCopy(m_xk,obj.m_xk);
|
|
ArrayCopy(m_dk,obj.m_dk);
|
|
ArrayCopy(m_an,obj.m_an);
|
|
ArrayCopy(m_xn,obj.m_xn);
|
|
ArrayCopy(m_dn,obj.m_dn);
|
|
ArrayCopy(m_d,obj.m_d);
|
|
ArrayCopy(m_work,obj.m_work);
|
|
ArrayCopy(m_yk,obj.m_yk);
|
|
ArrayCopy(m_gc,obj.m_gc);
|
|
ArrayCopy(m_x,obj.m_x);
|
|
ArrayCopy(m_g,obj.m_g);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This class is a shell for class CMinASAState |
|
|
//+------------------------------------------------------------------+
|
|
class CMinASAStateShell
|
|
{
|
|
private:
|
|
CMinASAState m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinASAStateShell(void) {}
|
|
CMinASAStateShell(CMinASAState &obj) { m_innerobj.Copy(obj); }
|
|
~CMinASAStateShell(void) {}
|
|
//--- methods
|
|
bool GetNeedFG(void);
|
|
void SetNeedFG(const bool b);
|
|
bool GetXUpdated(void);
|
|
void SetXUpdated(const bool b);
|
|
double GetF(void);
|
|
void SetF(const double d);
|
|
CMinASAState *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinASAStateShell::GetNeedFG(void)
|
|
{
|
|
return(m_innerobj.m_needfg);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
void CMinASAStateShell::SetNeedFG(const bool b)
|
|
{
|
|
m_innerobj.m_needfg=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinASAStateShell::GetXUpdated(void)
|
|
{
|
|
return(m_innerobj.m_xupdated);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
void CMinASAStateShell::SetXUpdated(const bool b)
|
|
{
|
|
m_innerobj.m_xupdated=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
double CMinASAStateShell::GetF(void)
|
|
{
|
|
return(m_innerobj.m_f);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
void CMinASAStateShell::SetF(const double d)
|
|
{
|
|
m_innerobj.m_f=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinASAState *CMinASAStateShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary class for CMinComp |
|
|
//+------------------------------------------------------------------+
|
|
class CMinASAReport
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_iterationscount;
|
|
int m_nfev;
|
|
int m_terminationtype;
|
|
int m_activeconstraints;
|
|
//--- constructor, destructor
|
|
CMinASAReport(void) { ZeroMemory(this); }
|
|
~CMinASAReport(void) {}
|
|
//--- copy
|
|
void Copy(CMinASAReport &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinASAReport::Copy(CMinASAReport &obj)
|
|
{
|
|
//--- copy variables
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_nfev=obj.m_nfev;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_activeconstraints=obj.m_activeconstraints;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This class is a shell for class CMinASAReport |
|
|
//+------------------------------------------------------------------+
|
|
class CMinASAReportShell
|
|
{
|
|
private:
|
|
CMinASAReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMinASAReportShell(void) {}
|
|
CMinASAReportShell(CMinASAReport &obj) { m_innerobj.Copy(obj); }
|
|
~CMinASAReportShell(void) {}
|
|
//--- methods
|
|
int GetIterationsCount(void);
|
|
void SetIterationsCount(const int i);
|
|
int GetNFev(void);
|
|
void SetNFev(const int i);
|
|
int GetTerminationType(void);
|
|
void SetTerminationType(const int i);
|
|
int GetActiveConstraints(void);
|
|
void SetActiveConstraints(const int i);
|
|
CMinASAReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable iterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CMinASAReportShell::GetIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_iterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable iterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CMinASAReportShell::SetIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_iterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable nfev |
|
|
//+------------------------------------------------------------------+
|
|
int CMinASAReportShell::GetNFev(void)
|
|
{
|
|
return(m_innerobj.m_nfev);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable nfev |
|
|
//+------------------------------------------------------------------+
|
|
void CMinASAReportShell::SetNFev(const int i)
|
|
{
|
|
m_innerobj.m_nfev=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
int CMinASAReportShell::GetTerminationType(void)
|
|
{
|
|
return(m_innerobj.m_terminationtype);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
void CMinASAReportShell::SetTerminationType(const int i)
|
|
{
|
|
m_innerobj.m_terminationtype=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable activeconstraints |
|
|
//+------------------------------------------------------------------+
|
|
int CMinASAReportShell::GetActiveConstraints(void)
|
|
{
|
|
return(m_innerobj.m_activeconstraints);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable activeconstraints |
|
|
//+------------------------------------------------------------------+
|
|
void CMinASAReportShell::SetActiveConstraints(const int i)
|
|
{
|
|
m_innerobj.m_activeconstraints=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMinASAReport *CMinASAReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Backward compatibility functions |
|
|
//+------------------------------------------------------------------+
|
|
class CMinComp
|
|
{
|
|
public:
|
|
//--- class constants
|
|
static const int m_n1;
|
|
static const int m_n2;
|
|
static const double m_stpmin;
|
|
static const double m_gtol;
|
|
static const double m_gpaftol;
|
|
static const double m_gpadecay;
|
|
static const double m_asarho;
|
|
//--- public methods
|
|
static void MinLBFGSSetDefaultPreconditioner(CMinLBFGSState &State);
|
|
static void MinLBFGSSetCholeskyPreconditioner(CMinLBFGSState &State,CMatrixDouble &p,const bool IsUpper);
|
|
static void MinBLEICSetBarrierWidth(CMinBLEICState &State,const double mu);
|
|
static void MinBLEICSetBarrierDecay(CMinBLEICState &State,const double mudecay);
|
|
static void MinASACreate(const int n,double &x[],double &bndl[],double &bndu[],CMinASAState &State);
|
|
static void MinASASetCond(CMinASAState &State,const double epsg,const double epsf,double epsx,const int m_maxits);
|
|
static void MinASASetXRep(CMinASAState &State,const bool needxrep);
|
|
static void MinASASetAlgorithm(CMinASAState &State,int algotype);
|
|
static void MinASASetStpMax(CMinASAState &State,const double stpmax);
|
|
static void MinASAResults(CMinASAState &State,double &x[],CMinASAReport &rep);
|
|
static void MinASAResultsBuf(CMinASAState &State,double &x[],CMinASAReport &rep);
|
|
static void MinASARestartFrom(CMinASAState &State,double &x[],double &bndl[],double &bndu[]);
|
|
static bool MinASAIteration(CMinASAState &State);
|
|
|
|
private:
|
|
//--- private methods
|
|
static double ASABoundedAntigradNorm(CMinASAState &State);
|
|
static double ASAGINorm(CMinASAState &State);
|
|
static double ASAD1Norm(CMinASAState &State);
|
|
static bool ASAUIsEmpty(CMinASAState &State);
|
|
static void ClearRequestFields(CMinASAState &State);
|
|
//--- auxiliary functions for MinASAIteration
|
|
static void Func_lbl_rcomm(CMinASAState &State,int n,int i,int mcinfo,int diffcnt,bool b,bool stepfound,double betak,double v,double vv);
|
|
static bool Func_lbl_15(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_17(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_19(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_21(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_24(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_26(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_27(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_29(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_31(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_35(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_39(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_43(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_49(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_51(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_52(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_53(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_55(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_59(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_63(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
static bool Func_lbl_65(CMinASAState &State,int &n,int &i,int &mcinfo,int &diffcnt,bool &b,bool &stepfound,double &betak,double &v,double &vv);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constants |
|
|
//+------------------------------------------------------------------+
|
|
const int CMinComp::m_n1=2;
|
|
const int CMinComp::m_n2=2;
|
|
const double CMinComp::m_stpmin=1.0E-300;
|
|
const double CMinComp::m_gtol=0.3;
|
|
const double CMinComp::m_gpaftol=0.0001;
|
|
const double CMinComp::m_gpadecay=0.5;
|
|
const double CMinComp::m_asarho=0.5;
|
|
//+------------------------------------------------------------------+
|
|
//| Obsolete function, use MinLBFGSSetPrecDefault() instead. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinLBFGSSetDefaultPreconditioner(CMinLBFGSState &State)
|
|
{
|
|
CMinLBFGS::MinLBFGSSetPrecDefault(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Obsolete function, use MinLBFGSSetCholeskyPreconditioner() |
|
|
//| instead. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinLBFGSSetCholeskyPreconditioner(CMinLBFGSState &State,
|
|
CMatrixDouble &p,
|
|
const bool IsUpper)
|
|
{
|
|
CMinLBFGS::MinLBFGSSetPrecCholesky(State,p,IsUpper);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is obsolete function which was used by previous version of |
|
|
//| the BLEIC optimizer. It does nothing in the current version of |
|
|
//| BLEIC. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinBLEICSetBarrierWidth(CMinBLEICState &State,
|
|
const double mu)
|
|
{
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is obsolete function which was used by previous version of |
|
|
//| the BLEIC optimizer. It does nothing in the current version of |
|
|
//| BLEIC. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinBLEICSetBarrierDecay(CMinBLEICState &State,
|
|
const double mudecay)
|
|
{
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Obsolete optimization algorithm. |
|
|
//| Was replaced by MinBLEIC subpackage. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinASACreate(const int n,double &x[],double &bndl[],
|
|
double &bndu[],CMinASAState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N too small!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=n,__FUNCTION__+": Length(BndL)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(bndl,n),__FUNCTION__+": BndL contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndu)>=n,__FUNCTION__+": Length(BndU)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(bndu,n),__FUNCTION__+": BndU contains infinite or NaN values!"))
|
|
return;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(bndl[i]<=bndu[i],__FUNCTION__+": inconsistent bounds!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(bndl[i]<=x[i],__FUNCTION__+": infeasible X!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(x[i]<=bndu[i],__FUNCTION__+": infeasible X!"))
|
|
return;
|
|
}
|
|
//--- Initialize
|
|
State.m_n=n;
|
|
MinASASetCond(State,0,0,0,0);
|
|
MinASASetXRep(State,false);
|
|
MinASASetStpMax(State,0);
|
|
MinASASetAlgorithm(State,-1);
|
|
//--- allocation
|
|
ArrayResize(State.m_bndl,n);
|
|
ArrayResize(State.m_bndu,n);
|
|
ArrayResize(State.m_ak,n);
|
|
ArrayResize(State.m_xk,n);
|
|
ArrayResize(State.m_dk,n);
|
|
ArrayResize(State.m_an,n);
|
|
ArrayResize(State.m_xn,n);
|
|
ArrayResize(State.m_dn,n);
|
|
ArrayResize(State.m_x,n);
|
|
ArrayResize(State.m_d,n);
|
|
ArrayResize(State.m_g,n);
|
|
ArrayResize(State.m_gc,n);
|
|
ArrayResize(State.m_work,n);
|
|
ArrayResize(State.m_yk,n);
|
|
//--- function call
|
|
MinASARestartFrom(State,x,bndl,bndu);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Obsolete optimization algorithm. |
|
|
//| Was replaced by MinBLEIC subpackage. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinASASetCond(CMinASAState &State,const double epsg,
|
|
const double epsf,double epsx,
|
|
const int m_maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsg),__FUNCTION__+": EpsG is not finite number!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsg>=0.0,__FUNCTION__+": negative EpsG!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsf),__FUNCTION__+": EpsF is not finite number!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsf>=0.0,__FUNCTION__+": negative EpsF!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsx),__FUNCTION__+": EpsX is not finite number!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m_maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
//--- check
|
|
if(((epsg==0.0 && epsf==0.0) && epsx==0.0) && m_maxits==0)
|
|
epsx=1.0E-6;
|
|
//--- change values
|
|
State.m_epsg=epsg;
|
|
State.m_epsf=epsf;
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=m_maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Obsolete optimization algorithm. |
|
|
//| Was replaced by MinBLEIC subpackage. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinASASetXRep(CMinASAState &State,const bool needxrep)
|
|
{
|
|
State.m_xrep=needxrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Obsolete optimization algorithm. |
|
|
//| Was replaced by MinBLEIC subpackage. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinASASetAlgorithm(CMinASAState &State,int algotype)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(algotype>=-1 && algotype<=1,__FUNCTION__+": incorrect AlgoType!"))
|
|
return;
|
|
//--- check
|
|
if(algotype==-1)
|
|
algotype=1;
|
|
//--- change value
|
|
State.m_cgtype=algotype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Obsolete optimization algorithm. |
|
|
//| Was replaced by MinBLEIC subpackage. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinASASetStpMax(CMinASAState &State,const double stpmax)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(stpmax),__FUNCTION__+": StpMax is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(stpmax>=0.0,__FUNCTION__+": StpMax<0!"))
|
|
return;
|
|
//--- change value
|
|
State.m_stpmax=stpmax;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Obsolete optimization algorithm. |
|
|
//| Was replaced by MinBLEIC subpackage. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinASAResults(CMinASAState &State,double &x[],CMinASAReport &rep)
|
|
{
|
|
//--- reset memory
|
|
ArrayResize(x,0);
|
|
//--- function call
|
|
MinASAResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Obsolete optimization algorithm. |
|
|
//| Was replaced by MinBLEIC subpackage. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinASAResultsBuf(CMinASAState &State,double &x[],
|
|
CMinASAReport &rep)
|
|
{
|
|
//--- check
|
|
if(CAp::Len(x)<State.m_n)
|
|
ArrayResize(x,State.m_n);
|
|
//--- copy
|
|
for(int i_=0; i_<State.m_n; i_++)
|
|
x[i_]=State.m_x[i_];
|
|
//--- change values
|
|
rep.m_iterationscount=State.m_repiterationscount;
|
|
rep.m_nfev=State.m_repnfev;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
rep.m_activeconstraints=0;
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(State.m_ak[i]==0.0)
|
|
rep.m_activeconstraints=rep.m_activeconstraints+1;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Obsolete optimization algorithm. |
|
|
//| Was replaced by MinBLEIC subpackage. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::MinASARestartFrom(CMinASAState &State,double &x[],double &bndl[],double &bndu[])
|
|
{
|
|
//--- create a variable
|
|
int i_=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=State.m_n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,State.m_n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=State.m_n,__FUNCTION__+": Length(BndL)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(bndl,State.m_n),__FUNCTION__+": BndL contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndu)>=State.m_n,__FUNCTION__+": Length(BndU)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(bndu,State.m_n),__FUNCTION__+": BndU contains infinite or NaN values!"))
|
|
return;
|
|
//--- copy
|
|
for(i_=0; i_<State.m_n; i_++)
|
|
State.m_x[i_]=x[i_];
|
|
for(i_=0; i_<State.m_n; i_++)
|
|
State.m_bndl[i_]=bndl[i_];
|
|
for(i_=0; i_<State.m_n; i_++)
|
|
State.m_bndu[i_]=bndu[i_];
|
|
State.m_laststep=0;
|
|
//--- allocation
|
|
State.m_rstate.ia.Resize(4);
|
|
ArrayResizeAL(State.m_rstate.ba,2);
|
|
State.m_rstate.ra.Resize(3);
|
|
State.m_rstate.stage=-1;
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns norm of bounded anti-gradient. |
|
|
//| Bounded antigradient is a vector obtained from anti-gradient by |
|
|
//| zeroing components which point outwards: |
|
|
//| result = norm(v) |
|
|
//| v[i]=0 if ((-g[i]<0)and(x[i]=bndl[i])) or |
|
|
//| ((-g[i]>0)and(x[i]=bndu[i])) |
|
|
//| v[i]=-g[i] otherwise |
|
|
//| This function may be used to check a stopping criterion. |
|
|
//+------------------------------------------------------------------+
|
|
double CMinComp::ASABoundedAntigradNorm(CMinASAState &State)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
double v=0;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
v=-State.m_g[i];
|
|
//--- check
|
|
if(State.m_x[i]==State.m_bndl[i] && -State.m_g[i]<0.0)
|
|
v=0;
|
|
//--- check
|
|
if(State.m_x[i]==State.m_bndu[i] && -State.m_g[i]>0.0)
|
|
v=0;
|
|
result=result+CMath::Sqr(v);
|
|
}
|
|
//--- return result
|
|
return(MathSqrt(result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns norm of GI(x). |
|
|
//| GI(x) is a gradient vector whose components associated with |
|
|
//| active constraints are zeroed. It differs from bounded |
|
|
//| anti-gradient because components of GI(x) are zeroed |
|
|
//| independently of sign(g[i]), and anti-gradient's components are |
|
|
//| zeroed with respect to both constraint and sign. |
|
|
//+------------------------------------------------------------------+
|
|
double CMinComp::ASAGINorm(CMinASAState &State)
|
|
{
|
|
double result=0;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(State.m_x[i]!=State.m_bndl[i] && State.m_x[i]!=State.m_bndu[i])
|
|
result=result+CMath::Sqr(State.m_g[i]);
|
|
}
|
|
//--- return result
|
|
return(MathSqrt(result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns norm(D1(State.X)) |
|
|
//| For a meaning of D1 see 'NEW ACTIVE SET ALGORITHM FOR BOX |
|
|
//| CONSTRAINED OPTIMIZATION' by WILLIAM W. HAGER AND HONGCHAO ZHANG.|
|
|
//+------------------------------------------------------------------+
|
|
double CMinComp::ASAD1Norm(CMinASAState &State)
|
|
{
|
|
double result=0;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
result=result+CMath::Sqr(CApServ::BoundVal(State.m_x[i]-State.m_g[i],State.m_bndl[i],State.m_bndu[i])-State.m_x[i]);
|
|
//--- return result
|
|
return(MathSqrt(result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns True, if U set is empty. |
|
|
//| * State.X is used as point, |
|
|
//| * State.G - as gradient, |
|
|
//| * D is calculated within function (because State.D may have |
|
|
//| different meaning depending on current optimization algorithm) |
|
|
//| For a meaning of U see 'NEW ACTIVE SET ALGORITHM FOR BOX |
|
|
//| CONSTRAINED OPTIMIZATION' by WILLIAM W. HAGER AND HONGCHAO ZHANG.|
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::ASAUIsEmpty(CMinASAState &State)
|
|
{
|
|
//--- create variables
|
|
double d=ASAD1Norm(State);
|
|
double d2=MathSqrt(d);
|
|
double d32=d*d2;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
//--- check
|
|
if(MathAbs(State.m_g[i])>=d2 && MathMin(State.m_x[i]-State.m_bndl[i],State.m_bndu[i]-State.m_x[i])>=d32)
|
|
return(false);
|
|
}
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Clears request fileds (to be sure that we don't forgot to clear |
|
|
//| something) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::ClearRequestFields(CMinASAState &State)
|
|
{
|
|
//--- change values
|
|
State.m_needfg=false;
|
|
State.m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::MinASAIteration(CMinASAState &State)
|
|
{
|
|
//--- create variables
|
|
int n=0;
|
|
int i=0;
|
|
double betak=0;
|
|
double v=0;
|
|
double vv=0;
|
|
int mcinfo=0;
|
|
bool b;
|
|
bool stepfound;
|
|
int diffcnt=0;
|
|
int i_=0;
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
//--- initialization
|
|
n=State.m_rstate.ia[0];
|
|
i=State.m_rstate.ia[1];
|
|
mcinfo=State.m_rstate.ia[2];
|
|
diffcnt=State.m_rstate.ia[3];
|
|
b=State.m_rstate.ba[0];
|
|
stepfound=State.m_rstate.ba[1];
|
|
betak=State.m_rstate.ra[0];
|
|
v=State.m_rstate.ra[1];
|
|
vv=State.m_rstate.ra[2];
|
|
}
|
|
else
|
|
{
|
|
//--- initialization
|
|
n=-983;
|
|
i=-989;
|
|
mcinfo=-834;
|
|
diffcnt=900;
|
|
b=true;
|
|
stepfound=false;
|
|
betak=214;
|
|
v=-338;
|
|
vv=-686;
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==0)
|
|
{
|
|
//--- change value
|
|
State.m_needfg=false;
|
|
//--- check
|
|
if(!State.m_xrep)
|
|
return(Func_lbl_15(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- progress report
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=1;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==1)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- function call, return result
|
|
return(Func_lbl_15(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==2)
|
|
{
|
|
//--- change values
|
|
State.m_needfg=false;
|
|
State.m_repnfev=State.m_repnfev+1;
|
|
stepfound=State.m_f<=State.m_finit+m_gpaftol*State.m_dginit;
|
|
//--- function call, return result
|
|
return(Func_lbl_24(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==3)
|
|
{
|
|
//--- change values
|
|
State.m_needfg=false;
|
|
State.m_repnfev=State.m_repnfev+1;
|
|
//--- check
|
|
if(State.m_stp<=m_stpmin)
|
|
{
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_xn[i_]=State.m_x[i_];
|
|
//--- function call, return result
|
|
return(Func_lbl_26(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- check
|
|
if(State.m_f<=State.m_finit+State.m_stp*m_gpaftol*State.m_dginit)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_xn[i_]=State.m_x[i_];
|
|
//--- function call, return result
|
|
return(Func_lbl_26(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- change value
|
|
State.m_stp=State.m_stp*m_gpadecay;
|
|
//--- function call, return result
|
|
return(Func_lbl_27(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==4)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- function call, return result
|
|
return(Func_lbl_29(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==5)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- return result
|
|
return(false);
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==6)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- return result
|
|
return(false);
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==7)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- return result
|
|
return(false);
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==8)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- return result
|
|
return(false);
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==9)
|
|
{
|
|
//--- change value
|
|
State.m_needfg=false;
|
|
//--- postprocess data: zero components of G corresponding to
|
|
//--- the active constraints
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(State.m_x[i]==State.m_bndl[i] || State.m_x[i]==State.m_bndu[i])
|
|
State.m_gc[i]=0;
|
|
else
|
|
State.m_gc[i]=State.m_g[i];
|
|
}
|
|
CLinMin::MCSrch(n,State.m_xn,State.m_f,State.m_gc,State.m_d,State.m_stp,State.m_stpmax,m_gtol,mcinfo,State.m_nfev,State.m_work,State.m_lstate,State.m_mcstage);
|
|
//--- function call, return result
|
|
return(Func_lbl_51(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==10)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- function call, return result
|
|
return(Func_lbl_53(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==11)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- return result
|
|
return(false);
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==12)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- return result
|
|
return(false);
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==13)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- return result
|
|
return(false);
|
|
}
|
|
//--- check
|
|
if(State.m_rstate.stage==14)
|
|
{
|
|
//--- change value
|
|
State.m_xupdated=false;
|
|
//--- return result
|
|
return(false);
|
|
}
|
|
//--- Routine body
|
|
//--- Prepare
|
|
n=State.m_n;
|
|
State.m_repterminationtype=0;
|
|
State.m_repiterationscount=0;
|
|
State.m_repnfev=0;
|
|
State.m_debugrestartscount=0;
|
|
State.m_cgtype=1;
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_xk[i_]=State.m_x[i_];
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(State.m_xk[i]==State.m_bndl[i] || State.m_xk[i]==State.m_bndu[i])
|
|
State.m_ak[i]=0;
|
|
else
|
|
State.m_ak[i]=1;
|
|
}
|
|
//--- change values
|
|
State.m_mu=0.1;
|
|
State.m_curalgo=0;
|
|
//--- Calculate F/G,initialize algorithm
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=0;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinComp::Func_lbl_rcomm(CMinASAState &State,int n,int i,
|
|
int mcinfo,int diffcnt,bool b,
|
|
bool stepfound,double betak,
|
|
double v,double vv)
|
|
{
|
|
//--- save
|
|
State.m_rstate.ia.Set(0,n);
|
|
State.m_rstate.ia.Set(1,i);
|
|
State.m_rstate.ia.Set(2,mcinfo);
|
|
State.m_rstate.ia.Set(3,diffcnt);
|
|
State.m_rstate.ba[0]=b;
|
|
State.m_rstate.ba[1]=stepfound;
|
|
State.m_rstate.ra.Set(0,betak);
|
|
State.m_rstate.ra.Set(1,v);
|
|
State.m_rstate.ra.Set(2,vv);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_15(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- check
|
|
if(ASABoundedAntigradNorm(State)<=State.m_epsg)
|
|
{
|
|
State.m_repterminationtype=4;
|
|
//--- return result
|
|
return(false);
|
|
}
|
|
State.m_repnfev=State.m_repnfev+1;
|
|
//--- Main cycle
|
|
//--- At the beginning of new iteration:
|
|
//--- * CurAlgo stores current algorithm selector
|
|
//--- * State.XK,State.F and State.G store current X/F/G
|
|
//--- * State.AK stores current set of active constraints
|
|
return(Func_lbl_17(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_17(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- GPA algorithm
|
|
if(State.m_curalgo!=0)
|
|
return(Func_lbl_19(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- change values
|
|
State.m_k=0;
|
|
State.m_acount=0;
|
|
//--- function call, return result
|
|
return(Func_lbl_21(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_19(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- CG algorithm
|
|
if(State.m_curalgo!=1)
|
|
return(Func_lbl_17(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- first,check that there are non-active constraints.
|
|
//--- move to GPA algorithm,if all constraints are active
|
|
b=true;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(State.m_ak[i]!=0.0)
|
|
{
|
|
b=false;
|
|
break;
|
|
}
|
|
}
|
|
//--- check
|
|
if(b)
|
|
{
|
|
State.m_curalgo=0;
|
|
//--- function call, return result
|
|
return(Func_lbl_17(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- CG iterations
|
|
State.m_fold=State.m_f;
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_xk[i_]=State.m_x[i_];
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- change values
|
|
State.m_dk[i]=-(State.m_g[i]*State.m_ak[i]);
|
|
State.m_gc[i]=State.m_g[i]*State.m_ak[i];
|
|
}
|
|
//--- function call, return result
|
|
return(Func_lbl_49(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_21(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- Determine Dk=proj(xk - gk)-xk
|
|
for(i=0; i<n; i++)
|
|
State.m_d[i]=CApServ::BoundVal(State.m_xk[i]-State.m_g[i],State.m_bndl[i],State.m_bndu[i])-State.m_xk[i];
|
|
//--- Armijo line search.
|
|
//--- * exact search with alpha=1 is tried first,
|
|
//--- 'exact' means that we evaluate f() EXACTLY at
|
|
//--- bound(x-g,bndl,bndu),without intermediate floating
|
|
//--- point operations.
|
|
//--- * alpha<1 are tried if explicit search wasn't successful
|
|
//--- Result is placed into XN.
|
|
//--- Two types of search are needed because we can't
|
|
//--- just use second type with alpha=1 because in finite
|
|
//--- precision arithmetics (x1-x0)+x0 may differ from x1.
|
|
//--- So while x1 is correctly bounded (it lie EXACTLY on
|
|
//--- boundary,if it is active),(x1-x0)+x0 may be
|
|
//--- not bounded.
|
|
v=0.0;
|
|
for(int i_=0; i_<n; i_++)
|
|
v+=State.m_d[i_]*State.m_g[i_];
|
|
//--- change values
|
|
State.m_dginit=v;
|
|
State.m_finit=State.m_f;
|
|
//--- check
|
|
if(!(ASAD1Norm(State)<=State.m_stpmax || State.m_stpmax==0.0))
|
|
{
|
|
stepfound=false;
|
|
//--- function call, return result
|
|
return(Func_lbl_24(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- Try alpha=1 step first
|
|
for(i=0; i<n; i++)
|
|
State.m_x[i]=CApServ::BoundVal(State.m_xk[i]-State.m_g[i],State.m_bndl[i],State.m_bndu[i]);
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=2;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_24(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- check
|
|
if(!stepfound)
|
|
{
|
|
//--- alpha=1 is too large,try smaller values
|
|
State.m_stp=1;
|
|
//--- function call
|
|
CLinMin::LinMinNormalized(State.m_d,State.m_stp,n);
|
|
//--- change values
|
|
State.m_dginit=State.m_dginit/State.m_stp;
|
|
State.m_stp=m_gpadecay*State.m_stp;
|
|
//--- check
|
|
if(State.m_stpmax>0.0)
|
|
State.m_stp=MathMin(State.m_stp,State.m_stpmax);
|
|
//--- function call, return result
|
|
return(Func_lbl_27(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- we are at the boundary(ies)
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_xn[i_]=State.m_x[i_];
|
|
State.m_stp=1;
|
|
//--- function call, return result
|
|
return(Func_lbl_26(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_26(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
State.m_repiterationscount=State.m_repiterationscount+1;
|
|
//--- check
|
|
if(!State.m_xrep)
|
|
return(Func_lbl_29(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- progress report
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=4;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_27(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
v=State.m_stp;
|
|
//--- copy
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_x[i_]=State.m_xk[i_];
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_x[i_]=State.m_x[i_]+v*State.m_d[i_];
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=3;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_29(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- Calculate new set of active constraints.
|
|
//--- Reset counter if active set was changed.
|
|
//--- Prepare for the new iteration
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(State.m_xn[i]==State.m_bndl[i] || State.m_xn[i]==State.m_bndu[i])
|
|
State.m_an[i]=0;
|
|
else
|
|
State.m_an[i]=1;
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(State.m_ak[i]!=State.m_an[i])
|
|
{
|
|
State.m_acount=-1;
|
|
break;
|
|
}
|
|
}
|
|
State.m_acount=State.m_acount+1;
|
|
//--- copy
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_xk[i_]=State.m_xn[i_];
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_ak[i_]=State.m_an[i_];
|
|
//--- Stopping conditions
|
|
if(!(State.m_repiterationscount>=State.m_maxits && State.m_maxits>0))
|
|
return(Func_lbl_31(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- Too many iterations
|
|
State.m_repterminationtype=5;
|
|
//--- check
|
|
if(!State.m_xrep)
|
|
return(false);
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=5;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_31(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- check
|
|
if(ASABoundedAntigradNorm(State)>State.m_epsg)
|
|
return(Func_lbl_35(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- Gradient is small enough
|
|
State.m_repterminationtype=4;
|
|
//--- check
|
|
if(!State.m_xrep)
|
|
return(false);
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=6;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_35(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- change value
|
|
v=0.0;
|
|
for(int i_=0; i_<n; i_++)
|
|
v+=State.m_d[i_]*State.m_d[i_];
|
|
//--- check
|
|
if(MathSqrt(v)*State.m_stp>State.m_epsx)
|
|
return(Func_lbl_39(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- Step size is too small,no further improvement is
|
|
//--- possible
|
|
State.m_repterminationtype=2;
|
|
//--- check
|
|
if(!State.m_xrep)
|
|
return(false);
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=7;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_39(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- check
|
|
if(State.m_finit-State.m_f>State.m_epsf*MathMax(MathAbs(State.m_finit),MathMax(MathAbs(State.m_f),1.0)))
|
|
return(Func_lbl_43(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- F(k+1)-F(k) is small enough
|
|
State.m_repterminationtype=1;
|
|
//--- check
|
|
if(!State.m_xrep)
|
|
return(false);
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=8;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_43(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- Decide - should we switch algorithm or not
|
|
if(ASAUIsEmpty(State))
|
|
{
|
|
//--- check
|
|
if(ASAGINorm(State)>=State.m_mu*ASAD1Norm(State))
|
|
{
|
|
State.m_curalgo=1;
|
|
//--- function call, return result
|
|
return(Func_lbl_19(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
else
|
|
State.m_mu=State.m_mu*m_asarho;
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(State.m_acount==m_n1)
|
|
{
|
|
//--- check
|
|
if(ASAGINorm(State)>=State.m_mu*ASAD1Norm(State))
|
|
{
|
|
State.m_curalgo=1;
|
|
//--- function call, return result
|
|
return(Func_lbl_19(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
}
|
|
}
|
|
//--- Next iteration
|
|
State.m_k=State.m_k+1;
|
|
//--- function call, return result
|
|
return(Func_lbl_21(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_49(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- Store G[k] for later calculation of Y[k]
|
|
for(i=0; i<n; i++)
|
|
State.m_yk[i]=-State.m_gc[i];
|
|
//--- Make a CG step in direction given by DK[]:
|
|
//--- * calculate step. Step projection into feasible set
|
|
//--- is used. It has several benefits: a) step may be
|
|
//--- found with usual line search,b) multiple constraints
|
|
//--- may be activated with one step,c) activated constraints
|
|
//--- are detected in a natural way - just compare x[i] with
|
|
//--- bounds
|
|
//--- * update active set,set B to True,if there
|
|
//--- were changes in the set.
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_d[i_]=State.m_dk[i_];
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_xn[i_]=State.m_xk[i_];
|
|
//--- change values
|
|
State.m_mcstage=0;
|
|
State.m_stp=1;
|
|
//--- function call
|
|
CLinMin::LinMinNormalized(State.m_d,State.m_stp,n);
|
|
//--- check
|
|
if(State.m_laststep!=0.0)
|
|
State.m_stp=State.m_laststep;
|
|
//--- function call
|
|
CLinMin::MCSrch(n,State.m_xn,State.m_f,State.m_gc,State.m_d,State.m_stp,State.m_stpmax,m_gtol,mcinfo,State.m_nfev,State.m_work,State.m_lstate,State.m_mcstage);
|
|
//--- function call, return result
|
|
return(Func_lbl_51(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_51(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- check
|
|
if(State.m_mcstage==0)
|
|
return(Func_lbl_52(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- preprocess data: bound State.XN so it belongs to the
|
|
//--- feasible set and store it in the State.X
|
|
for(i=0; i<n; i++)
|
|
State.m_x[i]=CApServ::BoundVal(State.m_xn[i],State.m_bndl[i],State.m_bndu[i]);
|
|
//--- RComm
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=9;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_52(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
diffcnt=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- XN contains unprojected result,project it,
|
|
//--- save copy to X (will be used for progress reporting)
|
|
State.m_xn[i]=CApServ::BoundVal(State.m_xn[i],State.m_bndl[i],State.m_bndu[i]);
|
|
//--- update active set
|
|
if(State.m_xn[i]==State.m_bndl[i] || State.m_xn[i]==State.m_bndu[i])
|
|
State.m_an[i]=0;
|
|
else
|
|
State.m_an[i]=1;
|
|
//--- check
|
|
if(State.m_an[i]!=State.m_ak[i])
|
|
diffcnt=diffcnt+1;
|
|
State.m_ak[i]=State.m_an[i];
|
|
}
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_xk[i_]=State.m_xn[i_];
|
|
//--- change values
|
|
State.m_repnfev=State.m_repnfev+State.m_nfev;
|
|
State.m_repiterationscount=State.m_repiterationscount+1;
|
|
//--- check
|
|
if(!State.m_xrep)
|
|
return(Func_lbl_53(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- progress report
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=10;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_53(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- Update Info about step length
|
|
v=0.0;
|
|
for(int i_=0; i_<n; i_++)
|
|
v+=State.m_d[i_]*State.m_d[i_];
|
|
State.m_laststep=MathSqrt(v)*State.m_stp;
|
|
//--- Check stopping conditions.
|
|
if(ASABoundedAntigradNorm(State)>State.m_epsg)
|
|
return(Func_lbl_55(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- Gradient is small enough
|
|
State.m_repterminationtype=4;
|
|
//--- check
|
|
if(!State.m_xrep)
|
|
return(false);
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=11;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_55(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- check
|
|
if(!(State.m_repiterationscount>=State.m_maxits && State.m_maxits>0))
|
|
return(Func_lbl_59(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- Too many iterations
|
|
State.m_repterminationtype=5;
|
|
//--- check
|
|
if(!State.m_xrep)
|
|
return(false);
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=12;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_59(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- check
|
|
if(!(ASAGINorm(State)>=State.m_mu*ASAD1Norm(State) && diffcnt==0))
|
|
return(Func_lbl_63(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- These conditions (EpsF/EpsX) are explicitly or implicitly
|
|
//--- related to the current step size and influenced
|
|
//--- by changes in the active constraints.
|
|
//--- For these reasons they are checked only when we don't
|
|
//--- want to 'unstick' at the end of the iteration and there
|
|
//--- were no changes in the active set.
|
|
//--- NOTE: consition |G|>=Mu*|D1| must be exactly opposite
|
|
//--- to the condition used to switch back to GPA. At least
|
|
//--- one inequality must be strict,otherwise infinite cycle
|
|
//--- may occur when |G|=Mu*|D1| (we DON'T test stopping
|
|
//--- conditions and we DON'T switch to GPA,so we cycle
|
|
//--- indefinitely).
|
|
if(State.m_fold-State.m_f>State.m_epsf*MathMax(MathAbs(State.m_fold),MathMax(MathAbs(State.m_f),1.0)))
|
|
return(Func_lbl_65(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- F(k+1)-F(k) is small enough
|
|
State.m_repterminationtype=1;
|
|
if(!State.m_xrep)
|
|
return(false);
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=13;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_63(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- Check conditions for switching
|
|
if(ASAGINorm(State)<State.m_mu*ASAD1Norm(State))
|
|
{
|
|
State.m_curalgo=0;
|
|
//--- function call, return result
|
|
return(Func_lbl_17(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- check
|
|
if(diffcnt>0)
|
|
{
|
|
//--- check
|
|
if(ASAUIsEmpty(State) || diffcnt>=m_n2)
|
|
State.m_curalgo=1;
|
|
else
|
|
State.m_curalgo=0;
|
|
//--- function call, return result
|
|
return(Func_lbl_17(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//--- Calculate D(k+1)
|
|
//--- Line search may result in:
|
|
//--- * maximum feasible step being taken (already processed)
|
|
//--- * point satisfying Wolfe conditions
|
|
//--- * some kind of error (CG is restarted by assigning 0.0 to Beta)
|
|
if(mcinfo==1)
|
|
{
|
|
//--- Standard Wolfe conditions are satisfied:
|
|
//--- * calculate Y[K] and BetaK
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_yk[i_]=State.m_yk[i_]+State.m_gc[i_];
|
|
//--- change value
|
|
vv=0.0;
|
|
for(int i_=0; i_<n; i_++)
|
|
vv+=State.m_yk[i_]*State.m_dk[i_];
|
|
//--- change value
|
|
v=0.0;
|
|
for(int i_=0; i_<n; i_++)
|
|
v+=State.m_gc[i_]*State.m_gc[i_];
|
|
State.m_betady=v/vv;
|
|
//--- change value
|
|
v=0.0;
|
|
for(int i_=0; i_<n; i_++)
|
|
v+=State.m_gc[i_]*State.m_yk[i_];
|
|
State.m_betahs=v/vv;
|
|
//--- check
|
|
if(State.m_cgtype==0)
|
|
betak=State.m_betady;
|
|
//--- check
|
|
if(State.m_cgtype==1)
|
|
betak=MathMax(0,MathMin(State.m_betady,State.m_betahs));
|
|
}
|
|
else
|
|
{
|
|
//--- Something is wrong (may be function is too wild or too flat).
|
|
//--- We'll set BetaK=0,which will restart CG algorithm.
|
|
//--- We can stop later (during normal checks) if stopping conditions are met.
|
|
betak=0;
|
|
State.m_debugrestartscount=State.m_debugrestartscount+1;
|
|
}
|
|
//--- change values
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_dn[i_]=-State.m_gc[i_];
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_dn[i_]=State.m_dn[i_]+betak*State.m_dk[i_];
|
|
for(int i_=0; i_<n; i_++)
|
|
State.m_dk[i_]=State.m_dn[i_];
|
|
//--- update other information
|
|
State.m_fold=State.m_f;
|
|
State.m_k=State.m_k+1;
|
|
//--- function call, return result
|
|
return(Func_lbl_49(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MinASAIteration. Is a product to get rid |
|
|
//| of the operator unconditional jump goto. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinComp::Func_lbl_65(CMinASAState &State,int &n,int &i,
|
|
int &mcinfo,int &diffcnt,bool &b,
|
|
bool &stepfound,double &betak,
|
|
double &v,double &vv)
|
|
{
|
|
//--- check
|
|
if(State.m_laststep>State.m_epsx)
|
|
return(Func_lbl_63(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv));
|
|
//--- X(k+1)-X(k) is small enough
|
|
State.m_repterminationtype=2;
|
|
//--- check
|
|
if(!State.m_xrep)
|
|
return(false);
|
|
//--- function call
|
|
ClearRequestFields(State);
|
|
//--- change values
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=14;
|
|
//--- Saving State
|
|
Func_lbl_rcomm(State,n,i,mcinfo,diffcnt,b,stepfound,betak,v,vv);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CLPQPServ
|
|
{
|
|
public:
|
|
static void ScaleShiftBCInplace(CRowDouble &s,CRowDouble &xorigin,CRowDouble &bndl,CRowDouble &bndu,int n);
|
|
static void ScaleShiftDenseBRLCInplace(CRowDouble &s,CRowDouble &xorigin,int n,CMatrixDouble &densea,CRowDouble &ab,CRowDouble &ar,int m);
|
|
static void ScaleShiftMixedBRLCInplace(CRowDouble &s,CRowDouble &xorigin,int n,CSparseMatrix &sparsea,int msparse,CMatrixDouble &densea,int mdense,CRowDouble &ab,CRowDouble &ar);
|
|
static void ScaleDenseQPInplace(CMatrixDouble &densea,bool IsUpper,int nmain,CRowDouble &denseb,int ntotal,CRowDouble &s);
|
|
static void ScaleSparseQPInplace(CRowDouble &s,int n,CSparseMatrix &sparsea,CRowDouble &denseb);
|
|
static void NormalizeDenseBRLCInplace(CMatrixDouble &densea,CRowDouble &ab,CRowDouble &ar,int n,int m,CRowDouble &rownorms,bool neednorms);
|
|
static void NormalizeMixedBRLCInplace(CSparseMatrix &sparsea,int msparse,CMatrixDouble &densea,int mdense,CRowDouble &ab,CRowDouble &ar,int n,bool limitedamplification,CRowDouble &rownorms,bool neednorms);
|
|
static double NormalizeDenseQPInplace(CMatrixDouble &densea,bool IsUpper,int nmain,CRowDouble &denseb,int ntotal);
|
|
static double NormalizeSparseQPInplace(CSparseMatrix &sparsea,bool IsUpper,CRowDouble &denseb,int n);
|
|
static void UnscaleUnshiftPointBC(CRowDouble &s,CRowDouble &xorigin,CRowDouble &rawbndl,CRowDouble &rawbndu,CRowDouble &sclsftbndl,CRowDouble &sclsftbndu,bool &HasBndL[],bool &HasBndU[],CRowDouble &x,int n);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates scaled (by S) and shifted (by XC) |
|
|
//| reformulation of the box constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - scale vector, array[N]: |
|
|
//| * I-th element contains scale of I-th variable, |
|
|
//| * SC[I] > 0 |
|
|
//| XOrigin - origin term, array[N]. Can be zero. |
|
|
//| BndL - raw lower bounds, array[N] |
|
|
//| BndU - raw upper bounds, array[N] |
|
|
//| N - number of variables. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| BndL - replaced by scaled / shifted lower bounds, array[N]|
|
|
//| BndU - replaced by scaled / shifted upper bounds, array[N]|
|
|
//+------------------------------------------------------------------+
|
|
void CLPQPServ::ScaleShiftBCInplace(CRowDouble &s,
|
|
CRowDouble &xorigin,
|
|
CRowDouble &bndl,
|
|
CRowDouble &bndu,
|
|
int n)
|
|
{
|
|
//--- create variables
|
|
bool HasBndL=false;
|
|
bool HasBndU=false;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(s[i]) && s[i]>0.0,__FUNCTION__+": S[i] is nonpositive"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndl[i]) || CInfOrNaN::NegativeInfinity()==bndl[i],__FUNCTION__+": BndL[i] is +INF or NAN"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu[i]) || CInfOrNaN::PositiveInfinity()==bndu[i],__FUNCTION__+": BndU[i] is -INF or NAN"))
|
|
return;
|
|
//---
|
|
HasBndL=MathIsValidNumber(bndl[i]);
|
|
HasBndU=MathIsValidNumber(bndu[i]);
|
|
if(HasBndL && HasBndU && bndl[i]==bndu[i])
|
|
{
|
|
//--- Make sure that BndL[I]=BndU[I] bit-to-bit
|
|
//--- even with CRAZY optimizing compiler.
|
|
bndu.Set(i,(bndu[i]-xorigin[i])/s[i]);
|
|
bndl.Set(i,bndu[i]);
|
|
continue;
|
|
}
|
|
if(HasBndL)
|
|
{
|
|
bndl.Set(i,(bndl[i]-xorigin[i])/s[i]);
|
|
}
|
|
if(HasBndU)
|
|
{
|
|
bndu.Set(i,(bndu[i]-xorigin[i])/s[i]);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates scaled (by S) and shifted (by XC) |
|
|
//| reformulation of two-sided "lower-bound/range" constraints stored|
|
|
//| in dense format. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - scale vector, array[N]: |
|
|
//| * I-th element contains scale of I-th variable, |
|
|
//| * SC[I] > 0 |
|
|
//| XOrigin - origin term, array[N]. Can be zero. |
|
|
//| N - number of variables. |
|
|
//| DenseA - array[M, N], constraint matrix |
|
|
//| AB - lower bounds for constraints, always present and |
|
|
//| finite, array[M] |
|
|
//| AR - ranges for constraints, can be zero(equality |
|
|
//| constraint), positive(range constraint) or + INF |
|
|
//| (lower bound constraint), array[M] |
|
|
//| M - constraint count, M >= 0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| DenseA - replaced by scaled / shifted constraints, |
|
|
//| array[M, N] |
|
|
//| AB - replaced by scaled / shifted lower bounds, array[M]|
|
|
//| AR - replaced by scaled / shifted ranges, array[M] |
|
|
//+------------------------------------------------------------------+
|
|
void CLPQPServ::ScaleShiftDenseBRLCInplace(CRowDouble &s,
|
|
CRowDouble &xorigin,
|
|
int n,
|
|
CMatrixDouble &densea,
|
|
CRowDouble &ab,
|
|
CRowDouble &ar,
|
|
int m)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double vv=0;
|
|
|
|
for(int i=0; i<m; i++)
|
|
{
|
|
//--- Scale/shift constraint; shift its lower bound
|
|
//--- NOTE: range is NOT scaled or shifted
|
|
v=0.0;
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
vv=densea.Get(i,j);
|
|
v+= vv*xorigin[j];
|
|
densea.Set(i,j,vv*s[j]);
|
|
}
|
|
ab.Add(i,-v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates scaled (by S) and shifted (by XC) |
|
|
//| reformulation of two-sided "lower-bound/range" constraints stored|
|
|
//| in dense format. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - scale vector, array[N]: |
|
|
//| * I-th element contains scale of I-th variable, |
|
|
//| * SC[I] > 0 |
|
|
//| XOrigin - origin term, array[N]. Can be zero. |
|
|
//| N - number of variables. |
|
|
//| SparseA - sparse MSparse*N constraint matrix in CRS format; |
|
|
//| ignored if MSparse = 0. |
|
|
//| MSparse - dense constraint count, MSparse >= 0 |
|
|
//| DenseA - array[MDense, N], constraint matrix; ignored if |
|
|
//| MDense = 0. |
|
|
//| MDense - dense constraint count, MDense >= 0 |
|
|
//| AB - lower bounds for constraints, always present and |
|
|
//| finite, array[MSparse + MDense] |
|
|
//| AR - ranges for constraints, can be zero(equality |
|
|
//| constraint), positive(range constraint) or + INF |
|
|
//| (lower bound constraint), array[MSparse + MDense] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| DenseA - replaced by scaled / shifted constraints, |
|
|
//| array[MDense, N] |
|
|
//| SparseA - replaced by scaled / shifted constraints, |
|
|
//| array[MSparse, N] |
|
|
//| AB - replaced by scaled / shifted lower bounds, |
|
|
//| array[MDense + MSparse] |
|
|
//| AR - replaced by scaled / shifted ranges, |
|
|
//| array[MDense + MSparse] |
|
|
//+------------------------------------------------------------------+
|
|
void CLPQPServ::ScaleShiftMixedBRLCInplace(CRowDouble &s,
|
|
CRowDouble &xorigin,
|
|
int n,
|
|
CSparseMatrix &sparsea,
|
|
int msparse,
|
|
CMatrixDouble &densea,
|
|
int mdense,
|
|
CRowDouble &ab,
|
|
CRowDouble &ar)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
double v=0;
|
|
double vv=0;
|
|
//--- check
|
|
if(!CAp::Assert(msparse==0 || (sparsea.m_MatrixType==1 && sparsea.m_M==msparse && sparsea.m_N==n),__FUNCTION__+": non-CRS sparse constraint matrix!"))
|
|
return;
|
|
|
|
for(i=0; i<msparse; i++)
|
|
{
|
|
//--- Scale/shift constraint; shift its lower bound
|
|
//--- NOTE: range is NOT scaled or shifted
|
|
v=0.0;
|
|
k0=sparsea.m_RIdx[i];
|
|
k1=sparsea.m_RIdx[i+1];
|
|
for(k=k0; k<k1; k++)
|
|
{
|
|
j=sparsea.m_Idx[k];
|
|
vv=sparsea.m_Vals[k];
|
|
v+= vv*xorigin[j];
|
|
sparsea.m_Vals.Set(k,vv*s[j]);
|
|
}
|
|
ab.Add(i,-v);
|
|
}
|
|
for(i=0; i<mdense; i++)
|
|
{
|
|
//--- Scale/shift constraint; shift its lower bound
|
|
//--- NOTE: range is NOT scaled or shifted
|
|
v=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
vv=densea.Get(i,j);
|
|
v+= vv*xorigin[j];
|
|
densea.Set(i,j,vv*s[j]);
|
|
}
|
|
ab.Add(msparse+i,- v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates scaled (by S) reformulation of dense |
|
|
//| quadratic and linear terms in QP problem. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - number of variables. |
|
|
//| DenseA - array[NMain, NMain], quadratic term |
|
|
//| IsUpper - whether upper or lower triangle is present |
|
|
//| NMain - number of nonslack vars, 1 <= NMain <= NTotal |
|
|
//| DenseB - array[NTotal], linear term |
|
|
//| NTotal - total number of variables, NTotal >= 1 |
|
|
//| S - scale vector, array[NTotal]: |
|
|
//| * I-th element contains scale of I-th variable, |
|
|
//| * SC[I] > 0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| DenseA - replaced by scaled term, array[N, N] |
|
|
//| DenseB - replaced by scaled term, array[N] |
|
|
//+------------------------------------------------------------------+
|
|
void CLPQPServ::ScaleDenseQPInplace(CMatrixDouble &densea,
|
|
bool IsUpper,
|
|
int nmain,
|
|
CRowDouble &denseb,
|
|
int ntotal,
|
|
CRowDouble &s)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double si=0;
|
|
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
si=s[i];
|
|
if(IsUpper)
|
|
{
|
|
j0=i;
|
|
j1=nmain-1;
|
|
}
|
|
else
|
|
{
|
|
j0=0;
|
|
j1=i;
|
|
}
|
|
for(j=j0; j<=j1; j++)
|
|
densea.Mul(i,j,si*s[j]);
|
|
}
|
|
for(i=0; i<ntotal; i++)
|
|
denseb.Mul(i,s[i]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates scaled (by S) reformulation of sparse |
|
|
//| quadratic and linear terms in QP problem. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - scale vector, array[N]: |
|
|
//| * I-th element contains scale of I-th variable, |
|
|
//| * SC[I] > 0 |
|
|
//| N - number of variables. |
|
|
//| SparseA - NxN CSparseMatrix in CRS format(any triangle can |
|
|
//| be present, we will scale everything) |
|
|
//| DenseB - array[N], linear term |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| SparseA - replaced by scaled term |
|
|
//| DenseB - replaced by scaled term |
|
|
//+------------------------------------------------------------------+
|
|
void CLPQPServ::ScaleSparseQPInplace(CRowDouble &s,
|
|
int n,
|
|
CSparseMatrix &sparsea,
|
|
CRowDouble &denseb)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
int k=0;
|
|
double si=0;
|
|
//--- check
|
|
if(!CAp::Assert(sparsea.m_MatrixType==1 && sparsea.m_M==n && sparsea.m_N==n,__FUNCTION__+": SparseA in unexpected format"))
|
|
return;
|
|
|
|
for(i=0; i<n; i++)
|
|
{
|
|
si=s[i];
|
|
k0=sparsea.m_RIdx[i];
|
|
k1=sparsea.m_RIdx[i+1];
|
|
for(k=k0; k<k1; k++)
|
|
sparsea.m_Vals.Mul(k,si*s[sparsea.m_Idx[k]]);
|
|
denseb.Mul(i,si);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function normalizes two-sided "lower-bound/range" |
|
|
//| constraints stored in dense format in such a way that L2 norms |
|
|
//| of rows (right hand side NOT included) become equal to 1.0. |
|
|
//| Exactly zero rows are handled correctly. |
|
|
//| INPUT PARAMETERS: |
|
|
//| DenseA - array[M, N], constraint matrix |
|
|
//| AB - lower bounds for constraints, always present and |
|
|
//| finite, array[M] |
|
|
//| AR - ranges for constraints, can be zero (equality |
|
|
//| constraint), positive(range constraint) or + INF |
|
|
//| (lower bound constraint), array[M] |
|
|
//| N - number of variables, N >= 1. |
|
|
//| M - constraint count, M >= 0 |
|
|
//| NeedNorms- whether we need row norms or not |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| DenseA - replaced by normalized constraints, array[M, N] |
|
|
//| AB - replaced by normalized lower bounds, array[M] |
|
|
//| AR - replaced by normalized ranges, array[M] |
|
|
//| RowNorms - if NeedNorms is true, leading M elements (resized |
|
|
//| if length is less than M) are filled by row norms |
|
|
//| before normalization was performed. |
|
|
//+------------------------------------------------------------------+
|
|
void CLPQPServ::NormalizeDenseBRLCInplace(CMatrixDouble &densea,
|
|
CRowDouble &ab,
|
|
CRowDouble &ar,
|
|
int n,
|
|
int m,
|
|
CRowDouble &rownorms,
|
|
bool neednorms)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
|
|
if(neednorms)
|
|
rownorms.Resize(m);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
vv=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=densea.Get(i,j);
|
|
vv+=v*v;
|
|
}
|
|
vv=MathSqrt(vv);
|
|
if(neednorms)
|
|
rownorms.Set(i,vv);
|
|
if(vv>0.0)
|
|
{
|
|
vv=1/vv;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
densea.Mul(i,j,vv);
|
|
}
|
|
ab.Mul(i,vv);
|
|
if(MathIsValidNumber(ar[i]))
|
|
ar.Mul(i,vv);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function normalizes two-sided "lower-bound/range" |
|
|
//| constraints stored in dense format in such a way that L2 norms |
|
|
//| of rows (right hand side NOT included) become equal to 1.0. |
|
|
//| Exactly zero rows are handled correctly. |
|
|
//| INPUT PARAMETERS: |
|
|
//| SparseA - sparse MSparse*N constraint matrix in CRS format; |
|
|
//| ignored if MSparse = 0. |
|
|
//| MSparse - dense constraint count, MSparse >= 0 |
|
|
//| DenseA - array[MDense, N], constraint matrix; ignored if |
|
|
//| MDense = 0. |
|
|
//| MDense - dense constraint count, MDense >= 0 |
|
|
//| AB - lower bounds for constraints, always present and |
|
|
//| finite, array[MSparse + MDense] |
|
|
//| AR - ranges for constraints, can be zero(equality |
|
|
//| constraint), positive(range constraint) or + INF |
|
|
//| (lower bound constraint), array[MSparse + MDense] |
|
|
//| N - number of variables, N >= 1. |
|
|
//| LimitedAmplification - whether row amplification is limited or|
|
|
//| not: |
|
|
//| * if False, rows with small norms(less than 1.0) |
|
|
//| are always normalized |
|
|
//| * if True, we do not increase individual row norms |
|
|
//| during normalization - only decrease. However, |
|
|
//| we may apply one amplification rount to entire |
|
|
//| constraint matrix, i.e. amplify all rows by same |
|
|
//| coefficient. As result, we do not overamplify |
|
|
//| any single row, but still make sure than entire |
|
|
//| problem is well scaled. If True, only large rows |
|
|
//| are normalized. |
|
|
//| NeedNorms- whether we need row norms or not |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| DenseA - replaced by normalized constraints, array[M, N] |
|
|
//| AB - replaced by normalized lower bounds, array[M] |
|
|
//| AR - replaced by normalized ranges, array[M] |
|
|
//| RowNorms - if NeedNorms is true, leading M elements(resized |
|
|
//| if length is less than M) are filled by row norms |
|
|
//| before normalization was performed. |
|
|
//+------------------------------------------------------------------+
|
|
void CLPQPServ::NormalizeMixedBRLCInplace(CSparseMatrix &sparsea,
|
|
int msparse,
|
|
CMatrixDouble &densea,
|
|
int mdense,
|
|
CRowDouble &ab,
|
|
CRowDouble &ar,
|
|
int n,
|
|
bool limitedamplification,
|
|
CRowDouble &rownorms,
|
|
bool neednorms)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double maxnrm2=0;
|
|
//--- check
|
|
if(!CAp::Assert(msparse==0 || (sparsea.m_MatrixType==1 && sparsea.m_M==msparse && sparsea.m_N==n),__FUNCTION__+": non-CRS sparse constraint matrix!"))
|
|
return;
|
|
if(neednorms)
|
|
rownorms.Resize(mdense+msparse);
|
|
//--- First round of normalization - normalize row 2-norms subject to limited amplification status
|
|
maxnrm2=0;
|
|
for(i=0; i<msparse; i++)
|
|
{
|
|
vv=0.0;
|
|
k0=sparsea.m_RIdx[i];
|
|
k1=sparsea.m_RIdx[i+1];
|
|
for(k=k0; k<k1; k++)
|
|
{
|
|
v=sparsea.m_Vals[k];
|
|
vv+=v*v;
|
|
}
|
|
vv=MathSqrt(vv);
|
|
maxnrm2=MathMax(maxnrm2,vv);
|
|
if(limitedamplification)
|
|
vv=MathMax(vv,1.0);
|
|
if(neednorms)
|
|
rownorms.Set(i,vv);
|
|
if(vv>0.0)
|
|
{
|
|
vv=1/vv;
|
|
for(k=k0; k<k1; k++)
|
|
sparsea.m_Vals.Mul(k,vv);
|
|
ab.Mul(i,vv);
|
|
if(MathIsValidNumber(ar[i]))
|
|
ar.Mul(i,vv);
|
|
}
|
|
}
|
|
for(i=0; i<mdense; i++)
|
|
{
|
|
vv=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=densea.Get(i,j);
|
|
vv+=v*v;
|
|
}
|
|
vv=MathSqrt(vv);
|
|
maxnrm2=MathMax(maxnrm2,vv);
|
|
if(limitedamplification)
|
|
vv=MathMax(vv,1.0);
|
|
if(neednorms)
|
|
rownorms.Set(msparse+i,vv);
|
|
if(vv>0.0)
|
|
{
|
|
vv=1/vv;
|
|
for(j=0; j<n; j++)
|
|
densea.Mul(i,j,vv);
|
|
ab.Mul(msparse+i,vv);
|
|
if(MathIsValidNumber(ar[msparse+i]))
|
|
ar.Mul(msparse+i,vv);
|
|
}
|
|
}
|
|
//--- If amplification was limited, perform second round of normalization
|
|
if(limitedamplification && maxnrm2<1.0 && maxnrm2>0.0)
|
|
{
|
|
if(neednorms)
|
|
CAblasF::RMulV(mdense+msparse,maxnrm2,rownorms);
|
|
vv=1/maxnrm2;
|
|
for(i=0; i<msparse; i++)
|
|
{
|
|
k0=sparsea.m_RIdx[i];
|
|
k1=sparsea.m_RIdx[i+1];
|
|
for(k=k0; k<k1; k++)
|
|
sparsea.m_Vals.Mul(k,vv);
|
|
ab.Mul(i,vv);
|
|
if(MathIsValidNumber(ar[i]))
|
|
ar.Mul(i,vv);
|
|
}
|
|
for(i=0; i<mdense; i++)
|
|
{
|
|
CAblasF::RMulR(n,vv,densea,i);
|
|
ab.Mul(msparse+i,vv);
|
|
if(MathIsValidNumber(ar[msparse+i]))
|
|
ar.Mul(msparse+i,vv);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function normalizes dense QP problem in such a way that |
|
|
//| maximum over its linear/quadratic coefficients max(max(A),max(B))|
|
|
//| becomes equal to 1.0. |
|
|
//| NOTE: completely zero A and B are handled correctly. |
|
|
//| INPUT PARAMETERS: |
|
|
//| DenseA - array[NMain, NMain], quadratic term |
|
|
//| IsUpper - whether upper or lower triangle is present |
|
|
//| NMain - number of nonslack vars, 1 <= NMain <= NTotal |
|
|
//| DenseB - array[NTotal], linear term |
|
|
//| NTotal - total number of variables. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| DenseA - replaced by normalized term |
|
|
//| DenseB - replaced by normalized term |
|
|
//| RESULT: max(max(A), max(B)) is returned |
|
|
//+------------------------------------------------------------------+
|
|
double CLPQPServ::NormalizeDenseQPInplace(CMatrixDouble &densea,
|
|
bool IsUpper,
|
|
int nmain,
|
|
CRowDouble &denseb,
|
|
int ntotal)
|
|
{
|
|
//--- return result
|
|
double result=0;
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double mx=0;
|
|
double v=0;
|
|
mx=0;
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
if(IsUpper)
|
|
{
|
|
j0=i;
|
|
j1=nmain-1;
|
|
}
|
|
else
|
|
{
|
|
j0=0;
|
|
j1=i;
|
|
}
|
|
for(j=j0; j<=j1; j++)
|
|
mx=MathMax(mx,MathAbs(densea.Get(i,j)));
|
|
}
|
|
for(i=0; i<ntotal; i++)
|
|
mx=MathMax(mx,MathAbs(denseb[i]));
|
|
result=mx;
|
|
if(mx==0.0)
|
|
return(result);
|
|
v=1/mx;
|
|
for(i=0; i<nmain; i++)
|
|
{
|
|
if(IsUpper)
|
|
{
|
|
j0=i;
|
|
j1=nmain-1;
|
|
}
|
|
else
|
|
{
|
|
j0=0;
|
|
j1=i;
|
|
}
|
|
for(j=j0; j<=j1; j++)
|
|
densea.Mul(i,j,v);
|
|
}
|
|
for(i=0; i<ntotal; i++)
|
|
denseb.Mul(i,v);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function normalizes sparse QP problem in such a way that |
|
|
//| maximum over its linear/quadratic coefficients max(max(A),max(B))|
|
|
//| becomes equal to 1.0. |
|
|
//| NOTE: completely zero A and B are handled correctly. |
|
|
//| INPUT PARAMETERS: |
|
|
//| SparseA - Sparse NxN matrix, either upper or lower triangle, |
|
|
//| diagonal MUST be present |
|
|
//| IsUpper - which triangle is present(other one is ignored) |
|
|
//| DenseB - array[N], linear term |
|
|
//| N - number of variables. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| DenseA - replaced by normalized term, array[N, N] |
|
|
//| DenseB - replaced by normalized term, array[N] |
|
|
//| RESULT: max(max(A), max(B)) is returned |
|
|
//+------------------------------------------------------------------+
|
|
double CLPQPServ::NormalizeSparseQPInplace(CSparseMatrix &sparsea,
|
|
bool IsUpper,
|
|
CRowDouble &denseb,
|
|
int n)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int i=0;
|
|
int k=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
double mx=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(sparsea.m_MatrixType==1 && sparsea.m_M==n && sparsea.m_N==n,__FUNCTION__+": SparseA in unexpected format"))
|
|
return(result);
|
|
mx=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(sparsea.m_DIdx[i]+1==sparsea.m_UIdx[i],__FUNCTION__+": critical integrity check failed,sparse diagonal not found"))
|
|
return(result);
|
|
if(IsUpper)
|
|
{
|
|
k0=sparsea.m_DIdx[i];
|
|
k1=sparsea.m_RIdx[i+1]-1;
|
|
}
|
|
else
|
|
{
|
|
k0=sparsea.m_RIdx[i];
|
|
k1=sparsea.m_DIdx[i];
|
|
}
|
|
for(k=k0; k<=k1; k++)
|
|
mx=MathMax(mx,MathAbs(sparsea.m_Vals[k]));
|
|
mx=MathMax(mx,MathAbs(denseb[i]));
|
|
}
|
|
result=mx;
|
|
if(mx==0.0)
|
|
return(result);
|
|
v=1/mx;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
k0=sparsea.m_RIdx[i];
|
|
k1=sparsea.m_RIdx[i+1]-1;
|
|
for(k=k0; k<=k1; k++)
|
|
sparsea.m_Vals.Mul(k,v);
|
|
denseb.Mul(i,v);
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs transformation of X from scaled / shifted |
|
|
//| coordinates to unscaled/unshifted ones, paying special attention |
|
|
//| to box constraints: |
|
|
//| * points which were exactly at the boundary before scaling will|
|
|
//| be mapped to corresponding boundary after scaling |
|
|
//| * in any case, unscaled box constraints will be satisfied |
|
|
//+------------------------------------------------------------------+
|
|
void CLPQPServ::UnscaleUnshiftPointBC(CRowDouble &s,
|
|
CRowDouble &xorigin,
|
|
CRowDouble &rawbndl,
|
|
CRowDouble &rawbndu,
|
|
CRowDouble &sclsftbndl,
|
|
CRowDouble &sclsftbndu,
|
|
bool &HasBndL[],
|
|
bool &HasBndU[],
|
|
CRowDouble &x,
|
|
int n)
|
|
{
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(HasBndL[i] && x[i]<=sclsftbndl[i])
|
|
{
|
|
x.Set(i,rawbndl[i]);
|
|
continue;
|
|
}
|
|
if(HasBndU[i] && x[i]>=sclsftbndu[i])
|
|
{
|
|
x.Set(i,rawbndu[i]);
|
|
continue;
|
|
}
|
|
x.Set(i,x[i]*s[i]+xorigin[i]);
|
|
if(HasBndL[i] && x[i]<=rawbndl[i])
|
|
x.Set(i,rawbndl[i]);
|
|
if(HasBndU[i] && x[i]>=rawbndu[i])
|
|
x.Set(i,rawbndu[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries of SQP subsolver. |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSQPSubSolver
|
|
{
|
|
int m_activesetsize;
|
|
int m_algokind;
|
|
bool m_hasal[];
|
|
bool m_hasau[];
|
|
bool m_HasBndL[];
|
|
bool m_HasBndU[];
|
|
CVIPMState m_ipmsolver;
|
|
CSparseMatrix m_sparsedummy;
|
|
CSparseMatrix m_sparseefflc;
|
|
CSparseMatrix m_sparserawlc;
|
|
CRowInt m_activeidx;
|
|
CRowDouble m_activerhs;
|
|
CRowDouble m_cural;
|
|
CRowDouble m_curau;
|
|
CRowDouble m_curb;
|
|
CRowDouble m_curbndl;
|
|
CRowDouble m_curbndu;
|
|
CRowDouble m_d0;
|
|
CRowDouble m_sk;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CRowDouble m_tmp2;
|
|
CRowDouble m_yk;
|
|
CMatrixDouble m_activea;
|
|
CMatrixDouble m_densedummy;
|
|
CMatrixDouble m_h;
|
|
//---
|
|
CMinSQPSubSolver(void);
|
|
~CMinSQPSubSolver(void) {}
|
|
//---
|
|
void Copy(const CMinSQPSubSolver &obj);
|
|
//--- overloading
|
|
void operator=(const CMinSQPSubSolver &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinSQPSubSolver::CMinSQPSubSolver(void)
|
|
{
|
|
m_activesetsize=0;
|
|
m_algokind=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinSQPSubSolver::Copy(const CMinSQPSubSolver &obj)
|
|
{
|
|
m_activesetsize=obj.m_activesetsize;
|
|
m_algokind=obj.m_algokind;
|
|
ArrayCopy(m_hasal,obj.m_hasal);
|
|
ArrayCopy(m_hasau,obj.m_hasau);
|
|
ArrayCopy(m_HasBndL,obj.m_HasBndL);
|
|
ArrayCopy(m_HasBndU,obj.m_HasBndU);
|
|
m_ipmsolver=obj.m_ipmsolver;
|
|
m_sparsedummy=obj.m_sparsedummy;
|
|
m_sparseefflc=obj.m_sparseefflc;
|
|
m_sparserawlc=obj.m_sparserawlc;
|
|
m_activeidx=obj.m_activeidx;
|
|
m_activerhs=obj.m_activerhs;
|
|
m_cural=obj.m_cural;
|
|
m_curau=obj.m_curau;
|
|
m_curb=obj.m_curb;
|
|
m_curbndl=obj.m_curbndl;
|
|
m_curbndu=obj.m_curbndu;
|
|
m_d0=obj.m_d0;
|
|
m_sk=obj.m_sk;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_tmp2=obj.m_tmp2;
|
|
m_yk=obj.m_yk;
|
|
m_activea=obj.m_activea;
|
|
m_densedummy=obj.m_densedummy;
|
|
m_h=obj.m_h;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries for LagrangianFG() function |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSQPTmpLagrangian
|
|
{
|
|
CRowDouble m_sclagtmp0;
|
|
CRowDouble m_sclagtmp1;
|
|
|
|
CMinSQPTmpLagrangian(void) {}
|
|
~CMinSQPTmpLagrangian(void) {}
|
|
void Copy(const CMinSQPTmpLagrangian &obj)
|
|
{
|
|
m_sclagtmp0=obj.m_sclagtmp0;
|
|
m_sclagtmp1=obj.m_sclagtmp1;
|
|
}
|
|
//--- overloading
|
|
void operator=(const CMinSQPTmpLagrangian &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries for LagrangianFG() function |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSQPTmpMerit
|
|
{
|
|
public:
|
|
CRowDouble m_mftmp0;
|
|
|
|
CMinSQPTmpMerit(void) {}
|
|
~CMinSQPTmpMerit(void) {}
|
|
|
|
void Copy(const CMinSQPTmpMerit &obj) { m_mftmp0=obj.m_mftmp0; }
|
|
//--- overloading
|
|
void operator=(const CMinSQPTmpMerit &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries of Phase 1 (merit function |
|
|
//| optimization). |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSQPMeritPhaseState
|
|
{
|
|
int m_n;
|
|
int m_nec;
|
|
int m_nic;
|
|
int m_nlec;
|
|
int m_nlic;
|
|
int m_status;
|
|
bool m_increasebigc;
|
|
RCommState m_rmeritphasestate;
|
|
CRowDouble m_d;
|
|
CRowDouble m_dummylagmult;
|
|
CRowDouble m_dx;
|
|
CRowDouble m_lagmult;
|
|
CRowDouble m_penalties;
|
|
CRowDouble m_stepkfi;
|
|
CRowDouble m_stepkfic;
|
|
CRowDouble m_stepkfin;
|
|
CRowDouble m_stepklaggrad;
|
|
CRowDouble m_stepknlaggrad;
|
|
CRowDouble m_stepkx;
|
|
CRowDouble m_stepkxc;
|
|
CRowDouble m_stepkxn;
|
|
CMinSQPTmpMerit m_tmpmerit;
|
|
CMinSQPTmpLagrangian m_tmplagrangianfg;
|
|
CMatrixDouble m_stepkj;
|
|
CMatrixDouble m_stepkjc;
|
|
CMatrixDouble m_stepkjn;
|
|
|
|
CMinSQPMeritPhaseState(void);
|
|
~CMinSQPMeritPhaseState(void) {}
|
|
|
|
void Copy(const CMinSQPMeritPhaseState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinSQPMeritPhaseState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinSQPMeritPhaseState::CMinSQPMeritPhaseState(void)
|
|
{
|
|
m_n=0;
|
|
m_nec=0;
|
|
m_nic=0;
|
|
m_nlec=0;
|
|
m_nlic=0;
|
|
m_status=0;
|
|
m_increasebigc=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinSQPMeritPhaseState::Copy(const CMinSQPMeritPhaseState &obj)
|
|
{
|
|
m_n=obj.m_n;
|
|
m_nec=obj.m_nec;
|
|
m_nic=obj.m_nic;
|
|
m_nlec=obj.m_nlec;
|
|
m_nlic=obj.m_nlic;
|
|
m_status=obj.m_status;
|
|
m_increasebigc=obj.m_increasebigc;
|
|
m_rmeritphasestate=obj.m_rmeritphasestate;
|
|
m_d=obj.m_d;
|
|
m_dummylagmult=obj.m_dummylagmult;
|
|
m_dx=obj.m_dx;
|
|
m_lagmult=obj.m_lagmult;
|
|
m_penalties=obj.m_penalties;
|
|
m_stepkfi=obj.m_stepkfi;
|
|
m_stepkfic=obj.m_stepkfic;
|
|
m_stepkfin=obj.m_stepkfin;
|
|
m_stepklaggrad=obj.m_stepklaggrad;
|
|
m_stepknlaggrad=obj.m_stepknlaggrad;
|
|
m_stepkx=obj.m_stepkx;
|
|
m_stepkxc=obj.m_stepkxc;
|
|
m_stepkxn=obj.m_stepkxn;
|
|
m_tmpmerit=obj.m_tmpmerit;
|
|
m_tmplagrangianfg=obj.m_tmplagrangianfg;
|
|
m_stepkj=obj.m_stepkj;
|
|
m_stepkjc=obj.m_stepkjc;
|
|
m_stepkjn=obj.m_stepkjn;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries of SQP solver. |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSQPState
|
|
{
|
|
int m_fstagnationcnt;
|
|
int m_maxits;
|
|
int m_n;
|
|
int m_nec;
|
|
int m_nic;
|
|
int m_nlec;
|
|
int m_nlic;
|
|
int m_repbcidx;
|
|
int m_repiterationscount;
|
|
int m_replcidx;
|
|
int m_repnlcidx;
|
|
int m_repsimplexiterations1;
|
|
int m_repsimplexiterations2;
|
|
int m_repsimplexiterations3;
|
|
int m_repsimplexiterations;
|
|
int m_repterminationtype;
|
|
int m_trustradstagnationcnt;
|
|
double m_bigc;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_repbcerr;
|
|
double m_replcerr;
|
|
double m_repnlcerr;
|
|
double m_trustrad;
|
|
bool m_HasBndL[];
|
|
bool m_HasBndU[];
|
|
bool m_haslagmult;
|
|
bool m_needfij;
|
|
bool m_xupdated;
|
|
RCommState m_rstate;
|
|
CRowInt m_lcsrcidx;
|
|
CRowDouble m_backupfi;
|
|
CRowDouble m_backupx;
|
|
CRowDouble m_dummylagmult;
|
|
CRowDouble m_fi;
|
|
CRowDouble m_fscales;
|
|
CRowDouble m_meritlagmult;
|
|
CRowDouble m_s;
|
|
CRowDouble m_scaledbndl;
|
|
CRowDouble m_scaledbndu;
|
|
CRowDouble m_step0fi;
|
|
CRowDouble m_step0x;
|
|
CRowDouble m_stepkfi;
|
|
CRowDouble m_stepkx;
|
|
CRowDouble m_tracegamma;
|
|
CRowDouble m_x;
|
|
CMinSQPTmpMerit m_tmpmerit;
|
|
CMinSQPSubSolver m_subsolver;
|
|
CMinSQPMeritPhaseState m_meritstate;
|
|
CMatrixDouble m_abslagmemory;
|
|
CMatrixDouble m_j;
|
|
CMatrixDouble m_scaledcleic;
|
|
CMatrixDouble m_step0j;
|
|
CMatrixDouble m_stepkj;
|
|
//---
|
|
CMinSQPState(void);
|
|
~CMinSQPState(void) {}
|
|
//---
|
|
void Copy(const CMinSQPState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinSQPState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
CMinSQPState::CMinSQPState(void)
|
|
{
|
|
m_fstagnationcnt=0;
|
|
m_maxits=0;
|
|
m_n=0;
|
|
m_nec=0;
|
|
m_nic=0;
|
|
m_nlec=0;
|
|
m_nlic=0;
|
|
m_repbcidx=0;
|
|
m_repiterationscount=0;
|
|
m_replcidx=0;
|
|
m_repnlcidx=0;
|
|
m_repsimplexiterations1=0;
|
|
m_repsimplexiterations2=0;
|
|
m_repsimplexiterations3=0;
|
|
m_repsimplexiterations=0;
|
|
m_repterminationtype=0;
|
|
m_trustradstagnationcnt=0;
|
|
m_bigc=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_repbcerr=0;
|
|
m_replcerr=0;
|
|
m_repnlcerr=0;
|
|
m_trustrad=0;
|
|
m_haslagmult=false;
|
|
m_needfij=false;
|
|
m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinSQPState::Copy(const CMinSQPState &obj)
|
|
{
|
|
m_fstagnationcnt=obj.m_fstagnationcnt;
|
|
m_maxits=obj.m_maxits;
|
|
m_n=obj.m_n;
|
|
m_nec=obj.m_nec;
|
|
m_nic=obj.m_nic;
|
|
m_nlec=obj.m_nlec;
|
|
m_nlic=obj.m_nlic;
|
|
m_repbcidx=obj.m_repbcidx;
|
|
m_repiterationscount=obj.m_repiterationscount;
|
|
m_replcidx=obj.m_replcidx;
|
|
m_repnlcidx=obj.m_repnlcidx;
|
|
m_repsimplexiterations1=obj.m_repsimplexiterations1;
|
|
m_repsimplexiterations2=obj.m_repsimplexiterations2;
|
|
m_repsimplexiterations3=obj.m_repsimplexiterations3;
|
|
m_repsimplexiterations=obj.m_repsimplexiterations;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_trustradstagnationcnt=obj.m_trustradstagnationcnt;
|
|
m_bigc=obj.m_bigc;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_repbcerr=obj.m_repbcerr;
|
|
m_replcerr=obj.m_replcerr;
|
|
m_repnlcerr=obj.m_repnlcerr;
|
|
m_trustrad=obj.m_trustrad;
|
|
ArrayCopy(m_HasBndL,obj.m_HasBndL);
|
|
ArrayCopy(m_HasBndU,obj.m_HasBndU);
|
|
m_haslagmult=obj.m_haslagmult;
|
|
m_needfij=obj.m_needfij;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_rstate=obj.m_rstate;
|
|
m_lcsrcidx=obj.m_lcsrcidx;
|
|
m_backupfi=obj.m_backupfi;
|
|
m_backupx=obj.m_backupx;
|
|
m_dummylagmult=obj.m_dummylagmult;
|
|
m_fi=obj.m_fi;
|
|
m_fscales=obj.m_fscales;
|
|
m_meritlagmult=obj.m_meritlagmult;
|
|
m_s=obj.m_s;
|
|
m_scaledbndl=obj.m_scaledbndl;
|
|
m_scaledbndu=obj.m_scaledbndu;
|
|
m_step0fi=obj.m_step0fi;
|
|
m_step0x=obj.m_step0x;
|
|
m_stepkfi=obj.m_stepkfi;
|
|
m_stepkx=obj.m_stepkx;
|
|
m_tracegamma=obj.m_tracegamma;
|
|
m_x=obj.m_x;
|
|
m_tmpmerit=obj.m_tmpmerit;
|
|
m_subsolver=obj.m_subsolver;
|
|
m_meritstate=obj.m_meritstate;
|
|
m_abslagmemory=obj.m_abslagmemory;
|
|
m_j=obj.m_j;
|
|
m_scaledcleic=obj.m_scaledcleic;
|
|
m_step0j=obj.m_step0j;
|
|
m_stepkj=obj.m_stepkj;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CNLCSQP
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const int m_fstagnationlimit;
|
|
static const int m_penaltymemlen;
|
|
static const int m_trustradstagnationlimit;
|
|
static const double m_augmentationfactor;
|
|
static const double m_inittrustrad;
|
|
static const double m_maxbigc;
|
|
static const double m_maxtrustraddecay;
|
|
static const double m_maxtrustradgrowth;
|
|
static const double m_meritfunctionbase;
|
|
static const double m_meritfunctiongain;
|
|
static const double m_sqpbigscale;
|
|
static const double m_sqpdeltadecrease;
|
|
static const double m_sqpdeltaincrease;
|
|
static const double m_sqpsmallscale;
|
|
static const double m_stagnationepsf;
|
|
|
|
static void MinSQPInitBuf(CRowDouble &bndl,CRowDouble &bndu,CRowDouble &s,CRowDouble &x0,int n,CMatrixDouble &cleic,CRowInt &lcsrcidx,int nec,int nic,int nlec,int nlic,double epsx,int m_maxits,CMinSQPState &State);
|
|
static bool MinSQPIteration(CMinSQPState &State,CSmoothnessMonitor &smonitor,bool userterminationneeded);
|
|
|
|
private:
|
|
static void InitQPSubSolver(CMinSQPState &sstate,CMinSQPSubSolver &subsolver);
|
|
static void QPSubSolverSetAlgoIPM(CMinSQPSubSolver &subsolver);
|
|
static bool QPSubProblemUpdateHessian(CMinSQPState &sstate,CMinSQPSubSolver &subsolver,CRowDouble &x0,CRowDouble &g0,CRowDouble &x1,CRowDouble &g1);
|
|
static void FASSolve(CMinSQPSubSolver &subsolver,CRowDouble &d0,CMatrixDouble &h,int nq,CRowDouble &b,int n,CRowDouble &bndl,CRowDouble &bndu,CSparseMatrix &a,int m,CRowDouble &al,CRowDouble &au,double trustrad,int &terminationtype,CRowDouble &d,CRowDouble &lagmult);
|
|
static bool QPSubproblemSolve(CMinSQPState &State,CMinSQPSubSolver &subsolver,CRowDouble &x,CRowDouble &fi,CMatrixDouble &jac,CRowDouble &d,CRowDouble &lagmult,int &terminationtype);
|
|
static void MeritPhaseInit(CMinSQPMeritPhaseState &meritstate,CRowDouble &curx,CRowDouble &curfi,CMatrixDouble &curj,int n,int nec,int nic,int nlec,int nlic,CMatrixDouble &abslagmemory,int memlen);
|
|
static bool MeritPhaseIteration(CMinSQPState &State,CMinSQPMeritPhaseState &meritstate,CSmoothnessMonitor &smonitor,bool userterminationneeded);
|
|
static void MeritPhaseResults(CMinSQPMeritPhaseState &meritstate,CRowDouble &curx,CRowDouble &curfi,CMatrixDouble &curj,CRowDouble &lagmult,bool &increasebigc,int &status);
|
|
static void SQPSendX(CMinSQPState &State,CRowDouble &xs);
|
|
static bool SQPRetrieveFIJ(CMinSQPState &State,CRowDouble &fis,CMatrixDouble &js);
|
|
static void SQPCopyState(CMinSQPState &State,CRowDouble &x0,CRowDouble &fi0,CMatrixDouble &j0,CRowDouble &x1,CRowDouble &fi1,CMatrixDouble &j1);
|
|
static void LagrangianFG(CMinSQPState &State,CRowDouble &x,double trustrad,CRowDouble &fi,CMatrixDouble &j,CRowDouble &lagmult,CMinSQPTmpLagrangian &tmp,double &f,CRowDouble &g);
|
|
static double MeritFunction(CMinSQPState &State,CRowDouble &x,CRowDouble &fi,CRowDouble &lagmult,CRowDouble &penalties,CMinSQPTmpMerit &tmp);
|
|
static double RawLagrangian(CMinSQPState &State,CRowDouble &x,CRowDouble &fi,CRowDouble &lagmult,CRowDouble &penalties,CMinSQPTmpMerit &tmp);
|
|
static void MeritFunctionAndRawLagrangian(CMinSQPState &State,CRowDouble &x,CRowDouble &fi,CRowDouble &lagmult,CRowDouble &penalties,CMinSQPTmpMerit &tmp,double &meritf,double &rawlag);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CNLCSQP::m_sqpdeltadecrease=0.20;
|
|
const double CNLCSQP::m_sqpdeltaincrease=0.80;
|
|
const double CNLCSQP::m_maxtrustraddecay=0.1;
|
|
const double CNLCSQP::m_maxtrustradgrowth=1.333;
|
|
const double CNLCSQP::m_maxbigc=1.0E5;
|
|
const double CNLCSQP::m_meritfunctionbase=0.0;
|
|
const double CNLCSQP::m_meritfunctiongain=2.0;
|
|
const double CNLCSQP::m_augmentationfactor=10.0;
|
|
const double CNLCSQP::m_inittrustrad=0.1;
|
|
const double CNLCSQP::m_stagnationepsf=1.0E-12;
|
|
const int CNLCSQP::m_fstagnationlimit=20;
|
|
const int CNLCSQP::m_trustradstagnationlimit=10;
|
|
const double CNLCSQP::m_sqpbigscale=5.0;
|
|
const double CNLCSQP::m_sqpsmallscale=0.2;
|
|
const int CNLCSQP::m_penaltymemlen=5;
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSQP::MinSQPInitBuf(CRowDouble &bndl,CRowDouble &bndu,
|
|
CRowDouble &s,CRowDouble &x0,int n,
|
|
CMatrixDouble &cleic,CRowInt &lcsrcidx,
|
|
int nec,int nic,int nlec,int nlic,
|
|
double epsx,int m_maxits,CMinSQPState &State)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
|
|
State.m_n=n;
|
|
State.m_nec=nec;
|
|
State.m_nic=nic;
|
|
State.m_nlec=nlec;
|
|
State.m_nlic=nlic;
|
|
//--- Prepare RCOMM State
|
|
State.m_rstate.ia.Resize(10);
|
|
ArrayResize(State.m_rstate.ba,4);
|
|
State.m_rstate.ra.Resize(7);
|
|
State.m_rstate.stage=-1;
|
|
State.m_needfij=false;
|
|
State.m_xupdated=false;
|
|
State.m_x.Resize(n);
|
|
State.m_fi.Resize(1+nlec+nlic);
|
|
State.m_j.Resize(1+nlec+nlic,n);
|
|
//--- Allocate memory.
|
|
ArrayResize(State.m_HasBndL,n);
|
|
ArrayResize(State.m_HasBndU,n);
|
|
State.m_s.Resize(n);
|
|
State.m_step0x.Resize(n);
|
|
State.m_stepkx.Resize(n);
|
|
State.m_backupx.Resize(n);
|
|
State.m_step0fi.Resize(1+nlec+nlic);
|
|
State.m_stepkfi.Resize(1+nlec+nlic);
|
|
State.m_backupfi.Resize(1+nlec+nlic);
|
|
State.m_step0j.Resize(1+nlec+nlic,n);
|
|
State.m_stepkj.Resize(1+nlec+nlic,n);
|
|
State.m_fscales.Resize(1+nlec+nlic);
|
|
State.m_tracegamma.Resize(1+nlec+nlic);
|
|
State.m_dummylagmult.Resize(nec+nic+nlec+nlic);
|
|
State.m_scaledbndl.Resize(n);
|
|
State.m_scaledbndu.Resize(n);
|
|
State.m_scaledcleic.Resize(nec+nic,n+1);
|
|
State.m_lcsrcidx.Resize(nec+nic);
|
|
State.m_meritlagmult.Resize(nec+nic+nlec+nlic);
|
|
State.m_abslagmemory=matrix<double>::Zeros(m_penaltymemlen,nec+nic+nlec+nlic);
|
|
//--- Prepare scaled problem
|
|
for(i=0; i<n; i++)
|
|
{
|
|
State.m_HasBndL[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_HasBndU[i]=MathIsValidNumber(bndu[i]);
|
|
if(State.m_HasBndL[i])
|
|
State.m_scaledbndl.Set(i,bndl[i]/s[i]);
|
|
if(State.m_HasBndU[i])
|
|
State.m_scaledbndu.Set(i,bndu[i]/s[i]);
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(bndl[i]<=bndu[i],__FUNCTION__+": integrity check failed,box constraints are inconsistent"))
|
|
return;
|
|
}
|
|
State.m_step0x.Set(i,x0[i]/s[i]);
|
|
State.m_s.Set(i,s[i]);
|
|
}
|
|
//--- Permutation
|
|
State.m_lcsrcidx=lcsrcidx;
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
//--- Scale and normalize linear constraints
|
|
vv=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=cleic.Get(i,j)*s[j];
|
|
State.m_scaledcleic.Set(i,j,v);
|
|
vv+=v*v;
|
|
}
|
|
vv=MathSqrt(vv);
|
|
State.m_scaledcleic.Set(i,n,cleic.Get(i,n));
|
|
if(vv>0.0)
|
|
State.m_scaledcleic.Row(i,State.m_scaledcleic[i]/vv);
|
|
}
|
|
//--- Initial enforcement of box constraints
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i])
|
|
State.m_step0x.Set(i,MathMax(State.m_step0x[i],State.m_scaledbndl[i]));
|
|
if(State.m_HasBndU[i])
|
|
State.m_step0x.Set(i,MathMin(State.m_step0x[i],State.m_scaledbndu[i]));
|
|
}
|
|
//--- Stopping criteria
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=m_maxits;
|
|
//--- Report fields
|
|
State.m_repsimplexiterations=0;
|
|
State.m_repsimplexiterations1=0;
|
|
State.m_repsimplexiterations2=0;
|
|
State.m_repsimplexiterations3=0;
|
|
State.m_repterminationtype=0;
|
|
State.m_repbcerr=0;
|
|
State.m_repbcidx=-1;
|
|
State.m_replcerr=0;
|
|
State.m_replcidx=-1;
|
|
State.m_repnlcerr=0;
|
|
State.m_repnlcidx=-1;
|
|
State.m_repiterationscount=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(m_sqpdeltadecrease<m_sqpdeltaincrease,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs actual processing for SQP algorithm. It |
|
|
//| expects that caller redirects its reverse communication requests |
|
|
//| NeedFiJ/XUpdated to external user who will provide analytic |
|
|
//| derivative (or handle reports about progress). |
|
|
//| In case external user does not have analytic derivative, it is |
|
|
//| responsibility of caller to intercept NeedFiJ request and replace|
|
|
//| it with appropriate numerical differentiation scheme. |
|
|
//| Results are stored: |
|
|
//| * point - in State.StepKX |
|
|
//| IMPORTANT: this function works with scaled problem formulation; |
|
|
//| it is responsibility of the caller to UnScale request |
|
|
//| and scale Jacobian. |
|
|
//| NOTE: SMonitor is expected to be correctly initialized smoothness|
|
|
//| monitor. |
|
|
//+------------------------------------------------------------------+
|
|
bool CNLCSQP::MinSQPIteration(CMinSQPState &State,
|
|
CSmoothnessMonitor &smonitor,
|
|
bool userterminationneeded)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=0;
|
|
int nslack=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int nlec=0;
|
|
int nlic=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double mx=0;
|
|
int status=0;
|
|
double deltamax=0;
|
|
double multiplyby=0;
|
|
double setscaleto=0;
|
|
double prevtrustrad=0;
|
|
int subiterationidx=0;
|
|
bool trustradstagnated=false;
|
|
bool dotrace=false;
|
|
bool dodetailedtrace=false;
|
|
bool increasebigc=false;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
n=State.m_rstate.ia[0];
|
|
nslack=State.m_rstate.ia[1];
|
|
nec=State.m_rstate.ia[2];
|
|
nic=State.m_rstate.ia[3];
|
|
nlec=State.m_rstate.ia[4];
|
|
nlic=State.m_rstate.ia[5];
|
|
i=State.m_rstate.ia[6];
|
|
j=State.m_rstate.ia[7];
|
|
status=State.m_rstate.ia[8];
|
|
subiterationidx=State.m_rstate.ia[9];
|
|
trustradstagnated=State.m_rstate.ba[0];
|
|
dotrace=State.m_rstate.ba[1];
|
|
dodetailedtrace=State.m_rstate.ba[2];
|
|
increasebigc=State.m_rstate.ba[3];
|
|
v=State.m_rstate.ra[0];
|
|
vv=State.m_rstate.ra[1];
|
|
mx=State.m_rstate.ra[2];
|
|
deltamax=State.m_rstate.ra[3];
|
|
multiplyby=State.m_rstate.ra[4];
|
|
setscaleto=State.m_rstate.ra[5];
|
|
prevtrustrad=State.m_rstate.ra[6];
|
|
}
|
|
else
|
|
{
|
|
n=359;
|
|
nslack=-58;
|
|
nec=-919;
|
|
nic=-909;
|
|
nlec=81;
|
|
nlic=255;
|
|
i=74;
|
|
j=-788;
|
|
status=809;
|
|
subiterationidx=205;
|
|
trustradstagnated=false;
|
|
dotrace=true;
|
|
dodetailedtrace=false;
|
|
increasebigc=true;
|
|
v=-541;
|
|
vv=-698;
|
|
mx=-900;
|
|
deltamax=-318;
|
|
multiplyby=-940;
|
|
setscaleto=1016;
|
|
prevtrustrad=-229;
|
|
}
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
State.m_needfij=false;
|
|
if(!SQPRetrieveFIJ(State,State.m_step0fi,State.m_step0j))
|
|
{
|
|
//--- Failed to retrieve function/Jaconian, infinities detected!
|
|
State.m_stepkx=State.m_step0x;
|
|
State.m_repterminationtype=-8;
|
|
return(false);
|
|
}
|
|
SQPCopyState(State,State.m_step0x,State.m_step0fi,State.m_step0j,State.m_stepkx,State.m_stepkfi,State.m_stepkj);
|
|
SQPSendX(State,State.m_stepkx);
|
|
State.m_f=State.m_stepkfi[0]*State.m_fscales[0];
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=1;
|
|
break;
|
|
case 1:
|
|
State.m_xupdated=false;
|
|
COptServ::CheckLcViolation(State.m_scaledcleic,State.m_lcsrcidx,nec,nic,State.m_stepkx,n,State.m_replcerr,State.m_replcidx);
|
|
COptServ::UnScaleAndCheckNLcViolation(State.m_stepkfi,State.m_fscales,nlec,nlic,State.m_repnlcerr,State.m_repnlcidx);
|
|
//--- Trace output (if needed)
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace("\n\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
CAp::Trace("//--- SQP SOLVER STARTED //\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
}
|
|
//--- Perform outer (NLC) iterations
|
|
State.m_bigc=500;
|
|
InitQPSubSolver(State,State.m_subsolver);
|
|
label=3;
|
|
break;
|
|
case 2:
|
|
label=5;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
nlec=State.m_nlec;
|
|
nlic=State.m_nlic;
|
|
nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
dotrace=CAp::IsTraceEnabled("SQP");
|
|
dodetailedtrace=dotrace && CAp::IsTraceEnabled("SQP.DETAILED");
|
|
//--- Prepare rcomm interface
|
|
State.m_needfij=false;
|
|
State.m_xupdated=false;
|
|
//--- Initialize algorithm data:
|
|
//---*Lagrangian and "Big C" estimates
|
|
//--- * trust region
|
|
//--- * initial function scales (vector of 1's)
|
|
//--- * current approximation of the Hessian matrix H (unit matrix)
|
|
//--- * initial linearized constraints
|
|
//--- * initial violation of linear/nonlinear constraints
|
|
State.m_fstagnationcnt=0;
|
|
State.m_trustradstagnationcnt=0;
|
|
State.m_trustrad=m_inittrustrad;
|
|
State.m_fscales.Fill(1);
|
|
State.m_tracegamma.Fill(0);
|
|
State.m_haslagmult=false;
|
|
//--- Avoid spurious warnings about possibly uninitialized vars
|
|
status=0;
|
|
//--- Evaluate function vector and Jacobian at Step0X, send first location report.
|
|
//--- Compute initial violation of constraints.
|
|
SQPSendX(State,State.m_step0x);
|
|
State.m_needfij=true;
|
|
State.m_rstate.stage=0;
|
|
break;
|
|
}
|
|
|
|
while(label>0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 3:
|
|
//--- Before beginning new outer iteration:
|
|
//--- * renormalize target function and/or constraints, if some of them have too large magnitudes
|
|
//--- * save initial point for the outer iteration
|
|
for(i=0; i<=nlec+nlic; i++)
|
|
{
|
|
//--- Determine (a) multiplicative coefficient applied to function value
|
|
//--- and Jacobian row, and (b) new value of the function scale.
|
|
mx=MathAbs(State.m_stepkj[i]+0).Max();
|
|
multiplyby=1.0;
|
|
setscaleto=State.m_fscales[i];
|
|
if(mx>=m_sqpbigscale)
|
|
{
|
|
multiplyby=1/mx;
|
|
setscaleto=State.m_fscales[i]*mx;
|
|
}
|
|
if(mx<=m_sqpsmallscale && State.m_fscales[i]>1.0)
|
|
{
|
|
if((State.m_fscales[i]*mx)>1.0)
|
|
{
|
|
multiplyby=1/mx;
|
|
setscaleto=State.m_fscales[i]*mx;
|
|
}
|
|
else
|
|
{
|
|
multiplyby=State.m_fscales[i];
|
|
setscaleto=1.0;
|
|
}
|
|
}
|
|
if(multiplyby!=1.0)
|
|
{
|
|
//--- Function #I needs renormalization:
|
|
//--- * update function vector element and Jacobian matrix row
|
|
//--- * update FScales[] and TraceGamma[] arrays
|
|
State.m_stepkfi.Mul(i,multiplyby);
|
|
State.m_stepkj.Row(i,State.m_stepkj[i]*multiplyby);
|
|
State.m_fscales.Set(i,setscaleto);
|
|
State.m_tracegamma.Mul(i,multiplyby);
|
|
}
|
|
}
|
|
//--- Trace output (if needed)
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace(StringFormat("\n=== OUTER ITERATION %5d STARTED ==================================================================\n",State.m_repiterationscount));
|
|
if(dodetailedtrace)
|
|
{
|
|
CAp::Trace("> printing raw data (prior to applying variable and function scales)\n");
|
|
CAp::Trace("X (raw) = ");
|
|
CApServ::TraceVectorUnscaledUnshiftedAutopRec(State.m_step0x,n,State.m_s,true,State.m_s,false);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("> printing scaled data (after applying variable and function scales)\n");
|
|
CAp::Trace("X (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_step0x,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("FScales = ");
|
|
CApServ::TraceVectorAutopRec(State.m_fscales,0,1+nlec+nlic);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("GammaScl = ");
|
|
CApServ::TraceVectorAutopRec(State.m_tracegamma,0,1+nlec+nlic);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("Fi (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_stepkfi,0,1+nlec+nlic);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("|Ji| (scaled) = ");
|
|
CApServ::TraceRowNrm1AutopRec(State.m_stepkj,0,1+nlec+nlic,0,n);
|
|
CAp::Trace("\n");
|
|
}
|
|
mx=0;
|
|
for(i=1; i<=nlec; i++)
|
|
mx=MathMax(mx,MathAbs(State.m_stepkfi[i]));
|
|
for(i=nlec+1; i<=nlec+nlic; i++)
|
|
mx=MathMax(mx,State.m_stepkfi[i]);
|
|
CAp::Trace(StringFormat("trustRad = %.3E\n",State.m_trustrad));
|
|
CAp::Trace(StringFormat("lin.violation = %.3E (scaled violation of linear constraints)\n",State.m_replcerr));
|
|
CAp::Trace(StringFormat("nlc.violation = %.3E (scaled violation of nonlinear constraints)\n",mx));
|
|
CAp::Trace(StringFormat("gamma0 = %.3E (Hessian 2-norm estimate for target)\n",State.m_tracegamma[0]));
|
|
j=(int)State.m_tracegamma.ArgMax();
|
|
CAp::Trace(StringFormat("gammaMax = %.3E (maximum over Hessian 2-norm estimates for target/constraints)\n",State.m_tracegamma[j]));
|
|
CAp::Trace(StringFormat("arg(gammaMax) = %d (function index; 0 for target,>0 for nonlinear constraints)\n",j));
|
|
}
|
|
//--- PHASE 2
|
|
//--- This phase is a primary part of the algorithm which is responsible for its
|
|
//--- convergence properties.
|
|
//--- It solves QP subproblem with possible activation and deactivation of constraints
|
|
//--- and then starts backtracking (step length is bounded by 1.0) merit function search
|
|
//--- (with second-order correction to deal with Maratos effect) on the direction produced
|
|
//--- by QP subproblem.
|
|
//--- This phase is everything we need to in order to have convergence; however,
|
|
//--- it has one performance-related issue: using "general" interior point QP solver
|
|
//--- results in slow solution times. Fast equality-constrained phase is essential for
|
|
//--- the quick convergence.
|
|
QPSubSolverSetAlgoIPM(State.m_subsolver);
|
|
SQPCopyState(State,State.m_stepkx,State.m_stepkfi,State.m_stepkj,State.m_step0x,State.m_step0fi,State.m_step0j);
|
|
MeritPhaseInit(State.m_meritstate,State.m_stepkx,State.m_stepkfi,State.m_stepkj,n,nec,nic,nlec,nlic,State.m_abslagmemory,m_penaltymemlen);
|
|
case 5:
|
|
if(MeritPhaseIteration(State,State.m_meritstate,smonitor,userterminationneeded))
|
|
{
|
|
State.m_rstate.stage=2;
|
|
label=-1;
|
|
break;
|
|
}
|
|
case 6:
|
|
MeritPhaseResults(State.m_meritstate,State.m_stepkx,State.m_stepkfi,State.m_stepkj,State.m_meritlagmult,increasebigc,status);
|
|
if(status==0)
|
|
{
|
|
label=4;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(status>0,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
State.m_haslagmult=true;
|
|
State.m_abslagmemory.DeleteRow(m_penaltymemlen);
|
|
State.m_abslagmemory.InsertRow(0);
|
|
State.m_abslagmemory.Row(0,State.m_meritlagmult.Abs()+0);
|
|
//--- Caller requested to update BigC - L1 penalty coefficient for linearized constraint violation
|
|
if(increasebigc)
|
|
State.m_bigc=MathMin(10*State.m_bigc,m_maxbigc);
|
|
//--- Update trust region.
|
|
//--- NOTE: when trust region radius remains fixed for a long time it may mean that we
|
|
//--- stagnated in eternal loop. In such cases we decrease it slightly in order
|
|
//--- to break possible loop. If such decrease was unnecessary, it may be easily
|
|
//--- fixed within few iterations.
|
|
deltamax=(MathAbs(State.m_step0x-State.m_stepkx+0)/State.m_trustrad).Max();
|
|
trustradstagnated=false;
|
|
State.m_trustradstagnationcnt++;
|
|
prevtrustrad=State.m_trustrad;
|
|
if(deltamax<=m_sqpdeltadecrease)
|
|
State.m_trustrad=State.m_trustrad*MathMax(deltamax/m_sqpdeltadecrease,m_maxtrustraddecay);
|
|
if(deltamax>=m_sqpdeltaincrease)
|
|
State.m_trustrad=State.m_trustrad*MathMin(deltamax/m_sqpdeltaincrease,m_maxtrustradgrowth);
|
|
if(State.m_trustrad<(0.99*prevtrustrad) || State.m_trustrad>(1.01*prevtrustrad))
|
|
State.m_trustradstagnationcnt=0;
|
|
if(State.m_trustradstagnationcnt>=m_trustradstagnationlimit)
|
|
{
|
|
State.m_trustrad=0.5*State.m_trustrad;
|
|
State.m_trustradstagnationcnt=0;
|
|
trustradstagnated=true;
|
|
}
|
|
//--- Trace
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace("\n--- outer iteration ends ---------------------------------------------------------------------------\n");
|
|
CAp::Trace(StringFormat("deltaMax = %.3f (ratio of step length to trust radius)\n",deltamax));
|
|
CAp::Trace(StringFormat("newTrustRad = %.3E",State.m_trustrad));
|
|
if(!trustradstagnated)
|
|
{
|
|
if(State.m_trustrad>(double)(prevtrustrad))
|
|
CAp::Trace(",trust radius increased");
|
|
if(State.m_trustrad<(double)(prevtrustrad))
|
|
CAp::Trace(",trust radius decreased");
|
|
}
|
|
else
|
|
CAp::Trace(StringFormat(",trust radius forcibly decreased due to stagnation for %d iterations",m_trustradstagnationlimit));
|
|
CAp::Trace("\n");
|
|
if(increasebigc)
|
|
CAp::Trace(StringFormat("BigC = %.3E (short step was performed,but some constraints are still infeasible - increasing)\n",State.m_bigc));
|
|
}
|
|
//--- Advance outer iteration counter, test stopping criteria
|
|
State.m_repiterationscount++;
|
|
if(MathAbs(State.m_stepkfi[0]-State.m_step0fi[0])<=(m_stagnationepsf*MathAbs(State.m_step0fi[0])))
|
|
State.m_fstagnationcnt++;
|
|
else
|
|
State.m_fstagnationcnt=0;
|
|
if(State.m_trustrad<=State.m_epsx)
|
|
{
|
|
State.m_repterminationtype=2;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stopping condition met: trust radius is smaller than %.3E\n",State.m_epsx));
|
|
label=4;
|
|
break;
|
|
}
|
|
if(State.m_maxits>0 && State.m_repiterationscount>=State.m_maxits)
|
|
{
|
|
State.m_repterminationtype=5;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stopping condition met: %d iterations performed\n",State.m_repiterationscount));
|
|
label=4;
|
|
break;
|
|
}
|
|
if(State.m_fstagnationcnt>=m_fstagnationlimit)
|
|
{
|
|
State.m_repterminationtype=7;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stopping criteria are too stringent: F stagnated for %d its,stopping\n",State.m_fstagnationcnt));
|
|
label=4;
|
|
break;
|
|
}
|
|
label=3;
|
|
break;
|
|
case 4:
|
|
COptServ::SmoothnessMonitorTraceStatus(smonitor,dotrace);
|
|
return(false);
|
|
break;
|
|
}
|
|
}
|
|
//--- Saving State
|
|
State.m_rstate.ba[0]=trustradstagnated;
|
|
State.m_rstate.ba[1]=dotrace;
|
|
State.m_rstate.ba[2]=dodetailedtrace;
|
|
State.m_rstate.ba[3]=increasebigc;
|
|
State.m_rstate.ia.Set(0,n);
|
|
State.m_rstate.ia.Set(1,nslack);
|
|
State.m_rstate.ia.Set(2,nec);
|
|
State.m_rstate.ia.Set(3,nic);
|
|
State.m_rstate.ia.Set(4,nlec);
|
|
State.m_rstate.ia.Set(5,nlic);
|
|
State.m_rstate.ia.Set(6,i);
|
|
State.m_rstate.ia.Set(7,j);
|
|
State.m_rstate.ia.Set(8,status);
|
|
State.m_rstate.ia.Set(9,subiterationidx);
|
|
State.m_rstate.ra.Set(0,v);
|
|
State.m_rstate.ra.Set(1,vv);
|
|
State.m_rstate.ra.Set(2,mx);
|
|
State.m_rstate.ra.Set(3,deltamax);
|
|
State.m_rstate.ra.Set(4,multiplyby);
|
|
State.m_rstate.ra.Set(5,setscaleto);
|
|
State.m_rstate.ra.Set(6,prevtrustrad);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes SQP subproblem. |
|
|
//| Should be called once in the beginning of the optimization. |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - solver State |
|
|
//| Subsolver - SQP subproblem to initialize |
|
|
//| RETURN VALUE: |
|
|
//| True on success |
|
|
//| False on failure of the QP solver (unexpected... but possible |
|
|
//| due to numerical errors) |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSQP::InitQPSubSolver(CMinSQPState &sstate,
|
|
CMinSQPSubSolver &subsolver)
|
|
{
|
|
//--- create variables
|
|
int n=sstate.m_n;
|
|
int nec=sstate.m_nec;
|
|
int nic=sstate.m_nic;
|
|
int nlec=sstate.m_nlec;
|
|
int nlic=sstate.m_nlic;
|
|
int nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
int lccnt=nec+nic+nlec+nlic;
|
|
int nnz=0;
|
|
int offs=0;
|
|
//--- Allocate temporaries
|
|
subsolver.m_cural.Resize(lccnt);
|
|
subsolver.m_curau.Resize(lccnt);
|
|
subsolver.m_curbndl.Resize(nslack);
|
|
subsolver.m_curbndu.Resize(nslack);
|
|
subsolver.m_curb.Resize(nslack);
|
|
subsolver.m_sk.Resize(n);
|
|
subsolver.m_yk.Resize(n);
|
|
//--- Initial State
|
|
subsolver.m_algokind=0;
|
|
subsolver.m_h=matrix<double>::Identity(n,n);
|
|
//--- Linear constraints do not change across subiterations, that's
|
|
//--- why we allocate storage for them at the start of the program.
|
|
//--- A full set of "raw" constraints is stored; later we will filter
|
|
//--- out inequality ones which are inactive anywhere in the current
|
|
//--- trust region.
|
|
//--- NOTE: because sparserawlc object stores only linear constraint
|
|
//--- (linearizations of nonlinear ones are not stored) we
|
|
//--- allocate only minimum necessary space.
|
|
nnz=sstate.m_scaledcleic.Compare(matrix<double>::Zeros(nec+nic,n));
|
|
subsolver.m_sparserawlc.m_RIdx.Resize(nec+nic+1);
|
|
subsolver.m_sparserawlc.m_Vals.Resize(nnz);
|
|
subsolver.m_sparserawlc.m_Idx.Resize(nnz);
|
|
subsolver.m_sparserawlc.m_DIdx.Resize(nec+nic);
|
|
subsolver.m_sparserawlc.m_UIdx.Resize(nec+nic);
|
|
offs=0;
|
|
subsolver.m_sparserawlc.m_RIdx.Set(0,0);
|
|
for(int i=0; i<nec+nic; i++)
|
|
{
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
if(sstate.m_scaledcleic.Get(i,j)!=0.0)
|
|
{
|
|
//--- Primary part of the matrix
|
|
subsolver.m_sparserawlc.m_Vals.Set(offs,sstate.m_scaledcleic.Get(i,j));
|
|
subsolver.m_sparserawlc.m_Idx.Set(offs,j);
|
|
offs++;
|
|
}
|
|
}
|
|
subsolver.m_sparserawlc.m_RIdx.Set(i+1,offs);
|
|
}
|
|
subsolver.m_sparserawlc.m_MatrixType=1;
|
|
subsolver.m_sparserawlc.m_NInitialized=subsolver.m_sparserawlc.m_RIdx[nec+nic];
|
|
subsolver.m_sparserawlc.m_M=nec+nic;
|
|
subsolver.m_sparserawlc.m_N=n;
|
|
CSparse::SparseInitDUIdx(subsolver.m_sparserawlc);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets subsolver algorithm to interior point |
|
|
//| method(IPM) |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSQP::QPSubSolverSetAlgoIPM(CMinSQPSubSolver &subsolver)
|
|
{
|
|
subsolver.m_algokind=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Updates Hessian estimate, uses regularized formula which prevents|
|
|
//| Hessian eigenvalues from decreasing below sqrt(Eps) and rejects |
|
|
//| updates larger than 1/sqrt(Eps) in magnitude. |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - solver State |
|
|
//| Subsolver - SQP subproblem to initialize |
|
|
//| X0, G0 - point #0 and gradient at #0, array[N] |
|
|
//| X1, G1 - point #1 and gradient at #1, array[N] |
|
|
//+------------------------------------------------------------------+
|
|
bool CNLCSQP::QPSubProblemUpdateHessian(CMinSQPState &sstate,
|
|
CMinSQPSubSolver &subsolver,
|
|
CRowDouble &x0,
|
|
CRowDouble &g0,
|
|
CRowDouble &x1,
|
|
CRowDouble &g1)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int i=0;
|
|
int n=0;
|
|
double shs=0;
|
|
double rawsy=0;
|
|
double sy=0;
|
|
double snrm2=0;
|
|
double ynrm2=0;
|
|
double v2=0;
|
|
double gk=0;
|
|
double sk=0;
|
|
double yk=0;
|
|
double mxs=0;
|
|
double mxy=0;
|
|
double mxhs=0;
|
|
double reg=0;
|
|
double big=0;
|
|
double growth=0;
|
|
double eigold=0;
|
|
double eignew=0;
|
|
double eigcorrection=0;
|
|
//--- Algorithm parameters
|
|
reg=100*MathSqrt(CMath::m_machineepsilon);
|
|
big=1/reg;
|
|
growth=100.0;
|
|
//--- Proceed
|
|
result=false;
|
|
n=sstate.m_n;
|
|
subsolver.m_tmp0.Resize(n);
|
|
rawsy=0;
|
|
sy=0;
|
|
snrm2=0;
|
|
ynrm2=0;
|
|
v2=0;
|
|
mxs=0;
|
|
mxy=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Fetch components
|
|
sk=x1[i]-x0[i];
|
|
yk=g1[i]-g0[i];
|
|
gk=g0[i];
|
|
//--- Compute raw (S,Y) without regularization (to be used later
|
|
//--- during comparison with zero)
|
|
rawsy+=sk*yk;
|
|
//--- Convexify Y
|
|
yk+=reg*sk;
|
|
//--- Compute various coefficients using regularized values
|
|
sy+=sk*yk;
|
|
snrm2+=sk*sk;
|
|
ynrm2+=yk*yk;
|
|
v2+=gk*gk;
|
|
mxs=MathMax(mxs,MathAbs(sk));
|
|
mxy=MathMax(mxy,MathAbs(yk));
|
|
subsolver.m_sk.Set(i,sk);
|
|
subsolver.m_yk.Set(i,yk);
|
|
}
|
|
shs=CAblas::RMatrixSyvMVect(n,subsolver.m_h,0,0,true,subsolver.m_sk,0,subsolver.m_tmp0);
|
|
CAblas::RMatrixGemVect(n,n,1.0,subsolver.m_h,0,0,0,subsolver.m_sk,0,0.0,subsolver.m_tmp0,0);
|
|
mxhs=(subsolver.m_tmp0.Abs()+0).Max();
|
|
//--- Skip updates if (Sk,Yk)<=0 or Sk*H*Sk<=0
|
|
//--- NOTE: we use 0.5*(SY+RawSY) in place of (Sk,Yk) which allows us to have slight
|
|
//--- nonconvexity due to numerical noise.
|
|
if((0.5*(sy+rawsy))<=0.0 || shs<=0.0 || snrm2<=0.0)
|
|
return(result);
|
|
//--- check
|
|
if(!CAp::Assert(sy>0.0,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
//--- Skip updates with too short steps
|
|
//--- NOTE: may prevent us from updating Hessian near the solution
|
|
if(mxs<=CApServ::Coalesce(sstate.m_epsx,MathSqrt(CMath::m_machineepsilon)))
|
|
return(result);
|
|
//--- Too large Hessian updates sometimes may come from noisy or nonsmooth problems.
|
|
//--- Skip updates with max(Yk)^2/(Yk,Sk)>=BIG or max(H*Sk)^2/(Sk*H*Sk)>=BIG
|
|
if((CMath::Sqr(mxy)/sy)>=big)
|
|
return(result);
|
|
if((CMath::Sqr(mxhs)/shs)>=big)
|
|
return(result);
|
|
//--- Compare eigenvalues of H: old one removed by update, and new one.
|
|
//--- We require that new eigenvalue is not much larger/smaller than the old one.
|
|
//--- In order to enforce this condition we compute correction coefficient and
|
|
//--- multiply one of the rank-1 updates by this coefficient.
|
|
eigold=shs/snrm2;
|
|
eignew=ynrm2/sy;
|
|
eigcorrection=1.0;
|
|
if(eignew>(eigold*growth))
|
|
eigcorrection=1/(eignew/(eigold*growth));
|
|
if(eignew<(eigold/growth))
|
|
eigcorrection=1/(eignew/(eigold/growth));
|
|
//--- Update Hessian
|
|
CAblas::RMatrixGer(n,n,subsolver.m_h,0,0,-(1/shs),subsolver.m_tmp0,0,subsolver.m_tmp0,0);
|
|
CAblas::RMatrixGer(n,n,subsolver.m_h,0,0,eigcorrection*(1/sy),subsolver.m_yk,0,subsolver.m_yk,0);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves QP subproblem given by initial point X, |
|
|
//| function vector Fi and Jacobian Jac, and returns estimates of |
|
|
//| Lagrangian multipliers and search direction D[]. |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSQP::FASSolve(CMinSQPSubSolver &subsolver,
|
|
CRowDouble &d0,
|
|
CMatrixDouble &h,
|
|
int nq,
|
|
CRowDouble &b,
|
|
int n,
|
|
CRowDouble &bndl,
|
|
CRowDouble &bndu,
|
|
CSparseMatrix &a,
|
|
int m,
|
|
CRowDouble &al,
|
|
CRowDouble &au,
|
|
double trustrad,
|
|
int &terminationtype,
|
|
CRowDouble &d,
|
|
CRowDouble &lagmult)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
terminationtype=1;
|
|
//--- Initial point, integrity check for constraints
|
|
ArrayResize(subsolver.m_HasBndL,n);
|
|
ArrayResize(subsolver.m_HasBndU,n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
subsolver.m_HasBndL[i]=MathIsValidNumber(bndl[i]);
|
|
subsolver.m_HasBndU[i]=MathIsValidNumber(bndu[i]);
|
|
//--- check
|
|
if(!CAp::Assert(!subsolver.m_HasBndL[i] || bndl[i]<=d0[i],__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(!subsolver.m_HasBndU[i] || bndu[i]>=(double)(d0[i]),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
d.Set(i,d0[i]);
|
|
}
|
|
ArrayResize(subsolver.m_hasal,m);
|
|
ArrayResize(subsolver.m_hasau,m);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
subsolver.m_hasal[i]=MathIsValidNumber(al[i]);
|
|
subsolver.m_hasau[i]=MathIsValidNumber(au[i]);
|
|
if(subsolver.m_hasal[i] && subsolver.m_hasau[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(al[i]<=au[i],__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
}
|
|
}
|
|
subsolver.m_activea.Resize(n,n);
|
|
subsolver.m_activerhs.Resize(n);
|
|
subsolver.m_activeidx.Resize(n);
|
|
subsolver.m_activesetsize=0;
|
|
//--- Activate equality constraints (at most N)
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(subsolver.m_hasal[i] && subsolver.m_hasau[i] && al[i]==au[i])
|
|
{
|
|
//--- Stop if full set of constraints is activated
|
|
if(subsolver.m_activesetsize>=n)
|
|
break;
|
|
}
|
|
}
|
|
subsolver.m_tmp0=vector<double>::Full(n,trustrad);
|
|
subsolver.m_tmp1=vector<double>::Zeros(n);
|
|
CVIPMSolver::VIPMInitDenseWithSlacks(subsolver.m_ipmsolver,subsolver.m_tmp0,subsolver.m_tmp1,nq,n);
|
|
CVIPMSolver::VIPMSetQuadraticLinear(subsolver.m_ipmsolver,h,subsolver.m_sparsedummy,0,true,b);
|
|
CVIPMSolver::VIPMSetConstraints(subsolver.m_ipmsolver,bndl,bndu,a,m,subsolver.m_densedummy,0,al,au);
|
|
CVIPMSolver::VIPMOptimize(subsolver.m_ipmsolver,false,subsolver.m_tmp0,subsolver.m_tmp1,subsolver.m_tmp2,terminationtype);
|
|
if(terminationtype<=0)
|
|
return;
|
|
d=subsolver.m_tmp0;
|
|
lagmult=subsolver.m_tmp2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves QP subproblem given by initial point X, |
|
|
//| function vector Fi and Jacobian Jac, and returns estimates of |
|
|
//| Lagrangian multipliers and search direction D[]. |
|
|
//+------------------------------------------------------------------+
|
|
bool CNLCSQP::QPSubproblemSolve(CMinSQPState &State,
|
|
CMinSQPSubSolver &subsolver,
|
|
CRowDouble &x,
|
|
CRowDouble &fi,
|
|
CMatrixDouble &jac,
|
|
CRowDouble &d,
|
|
CRowDouble &lagmult,
|
|
int &terminationtype)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=State.m_n;
|
|
int nec=State.m_nec;
|
|
int nic=State.m_nic;
|
|
int nlec=State.m_nlec;
|
|
int nlic=State.m_nlic;
|
|
int nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
int lccnt=nec+nic+nlec+nlic;
|
|
//--- Locations of slack variables
|
|
int offsslackec=n;
|
|
int offsslacknlec=n+2*nec;
|
|
int offsslackic=n+2*nec+2*nlec;
|
|
int offsslacknlic=n+2*(nec+nlec)+nic;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double vright=0;
|
|
double vmax=0;
|
|
int offs=0;
|
|
int nnz=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
terminationtype=0;
|
|
//--- Prepare temporary structures
|
|
subsolver.m_cural.Resize(lccnt);
|
|
subsolver.m_curau.Resize(lccnt);
|
|
subsolver.m_d0=vector<double>::Zeros(nslack);
|
|
//--- Prepare default solution: all zeros
|
|
result=true;
|
|
terminationtype=0;
|
|
d.Fill(0.0);
|
|
lagmult.Fill(0);
|
|
//--- Linear term B
|
|
//--- NOTE: elements [N,NSlack) are equal to bigC + perturbation to improve numeric properties of QP problem
|
|
subsolver.m_curb=jac[0]+0;
|
|
subsolver.m_curb.Resize(n);
|
|
v=subsolver.m_curb.Dot(subsolver.m_curb);
|
|
v=CApServ::Coalesce(MathSqrt(v),1.0);
|
|
subsolver.m_curb.Resize(nslack);
|
|
for(i=n; i<nslack; i++)
|
|
subsolver.m_curb.Set(i,(State.m_bigc+1.0/(1+i))*v);
|
|
//--- Trust radius constraints for primary variables
|
|
for(i=0; i<n; i++)
|
|
{
|
|
subsolver.m_curbndl.Set(i,-State.m_trustrad);
|
|
subsolver.m_curbndu.Set(i,State.m_trustrad);
|
|
if(State.m_HasBndL[i])
|
|
subsolver.m_curbndl.Set(i,MathMax(subsolver.m_curbndl[i],State.m_scaledbndl[i]-x[i]));
|
|
if(State.m_HasBndU[i])
|
|
subsolver.m_curbndu.Set(i,MathMin(subsolver.m_curbndu[i],State.m_scaledbndu[i]-x[i]));
|
|
}
|
|
//--- Prepare storage for "effective" constraining matrix
|
|
nnz=subsolver.m_sparserawlc.m_RIdx[nec+nic];
|
|
for(i=0; i<nlec+nlic; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
if(jac.Get(1+i,j)!=0.0)
|
|
nnz++;
|
|
}
|
|
nnz=nnz+2*nec+nic;
|
|
nnz=nnz+2*nlec+nlic;
|
|
subsolver.m_sparseefflc.m_RIdx.Resize(lccnt+1);
|
|
subsolver.m_sparseefflc.m_Vals.Resize(nnz);
|
|
subsolver.m_sparseefflc.m_Idx.Resize(nnz);
|
|
subsolver.m_sparseefflc.m_DIdx.Resize(lccnt);
|
|
subsolver.m_sparseefflc.m_UIdx.Resize(lccnt);
|
|
subsolver.m_sparseefflc.m_M=0;
|
|
subsolver.m_sparseefflc.m_N=nslack;
|
|
subsolver.m_sparseefflc.m_MatrixType=1;
|
|
//--- Append linear equality/inequality constraints
|
|
//--- Scan sparsified linear constraints stored in sparserawlc[], skip ones
|
|
//--- which are inactive anywhere in the trust region.
|
|
subsolver.m_tmp0=x;
|
|
subsolver.m_tmp0.Resize(nslack);
|
|
for(i=n; i<nslack; i++)
|
|
subsolver.m_tmp0.Set(i,0);
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
//--- Calculate:
|
|
//--- * VRight - product of X[] (extended with zeros up to NSlack elements)
|
|
//--- and AR[i] - Ith row of sparserawlc matrix.
|
|
//--- * VMax - maximum value of X*ARi computed over trust region
|
|
vright=0;
|
|
vmax=0;
|
|
j0=subsolver.m_sparserawlc.m_RIdx[i];
|
|
j1=subsolver.m_sparserawlc.m_RIdx[i+1];
|
|
for(k=j0; k<j1; k++)
|
|
{
|
|
j=subsolver.m_sparserawlc.m_Idx[k];
|
|
v=subsolver.m_tmp0[j];
|
|
vv=subsolver.m_sparserawlc.m_Vals[k];
|
|
vright+=vv*v;
|
|
if(vv>=0)
|
|
vmax+=vv*(v+subsolver.m_curbndu[j]);
|
|
else
|
|
vmax+=vv*(v+subsolver.m_curbndl[j]);
|
|
}
|
|
//--- If constraint is an inequality one and guaranteed to be inactive
|
|
//--- within trust region, it is skipped (row itself is retained but
|
|
//--- filled by zeros).
|
|
if(i>=nec && vmax<=State.m_scaledcleic.Get(i,n))
|
|
{
|
|
offs=subsolver.m_sparseefflc.m_RIdx[i];
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,-1);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,offsslackic+(i-nec));
|
|
subsolver.m_sparseefflc.m_RIdx.Set(i+1,offs+1);
|
|
subsolver.m_cural.Set(i,0.0);
|
|
subsolver.m_curau.Set(i,0.0);
|
|
subsolver.m_curbndl.Set(offsslackic+(i-nec),0);
|
|
subsolver.m_curbndu.Set(offsslackic+(i-nec),0);
|
|
continue;
|
|
}
|
|
//--- Start working on row I
|
|
offs=subsolver.m_sparseefflc.m_RIdx[i];
|
|
//--- Copy constraint from sparserawlc[] to sparseefflc[]
|
|
j0=subsolver.m_sparserawlc.m_RIdx[i];
|
|
j1=subsolver.m_sparserawlc.m_RIdx[i+1];
|
|
for(k=j0; k<j1; k++)
|
|
{
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,subsolver.m_sparserawlc.m_Idx[k]);
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,subsolver.m_sparserawlc.m_Vals[k]);
|
|
offs++;
|
|
}
|
|
//--- Set up slack variables
|
|
if(i<nec)
|
|
{
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,-1);
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs+1,1);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,offsslackec+2*i);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs+1,offsslackec+2*i+1);
|
|
offs+=2;
|
|
}
|
|
else
|
|
{
|
|
//--- Slack variables for inequality constraints
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,-1);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,offsslackic+(i-nec));
|
|
offs++;
|
|
}
|
|
//--- Finalize row
|
|
subsolver.m_sparseefflc.m_RIdx.Set(i+1,offs);
|
|
//--- Set up bounds and slack part of D0.
|
|
//--- NOTE: bounds for equality and inequality constraints are
|
|
//--- handled differently
|
|
v=vright-State.m_scaledcleic.Get(i,n);
|
|
if(i<nec)
|
|
{
|
|
subsolver.m_cural.Set(i,-v);
|
|
subsolver.m_curau.Set(i,-v);
|
|
subsolver.m_curbndl.Set(offsslackec+2*i,0);
|
|
subsolver.m_curbndl.Set(offsslackec+2*i+1,0);
|
|
subsolver.m_curbndu.Set(offsslackec+2*i,MathAbs(v));
|
|
subsolver.m_curbndu.Set(offsslackec+2*i+1,MathAbs(v));
|
|
if(v>=0.0)
|
|
{
|
|
subsolver.m_d0.Set(offsslackec+2*i+0,MathAbs(v));
|
|
subsolver.m_d0.Set(offsslackec+2*i+1,0);
|
|
}
|
|
else
|
|
{
|
|
subsolver.m_d0.Set(offsslackec+2*i+0,0);
|
|
subsolver.m_d0.Set(offsslackec+2*i+1,MathAbs(v));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
subsolver.m_cural.Set(i,AL_NEGINF);
|
|
subsolver.m_curau.Set(i,-v);
|
|
subsolver.m_curbndl.Set(offsslackic+(i-nec),0);
|
|
subsolver.m_curbndu.Set(offsslackic+(i-nec),MathMax(v,0));
|
|
subsolver.m_d0.Set(offsslackic+(i-nec),MathMax(v,0));
|
|
}
|
|
}
|
|
subsolver.m_sparseefflc.m_M+=(nec+nic);
|
|
//--- Append nonlinear equality/inequality constraints
|
|
for(i=0; i<nlec+nlic; i++)
|
|
{
|
|
//--- Calculate scale coefficient
|
|
vv=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=jac.Get(1+i,j);
|
|
vv+=v*v;
|
|
}
|
|
vv=1/CApServ::Coalesce(MathSqrt(vv),1);
|
|
//--- Copy scaled row
|
|
offs=subsolver.m_sparseefflc.m_RIdx[subsolver.m_sparseefflc.m_M+i];
|
|
for(j=0; j<n; j++)
|
|
{
|
|
if(jac.Get(1+i,j)!=0.0)
|
|
{
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,vv*jac.Get(1+i,j));
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,j);
|
|
offs++;
|
|
}
|
|
}
|
|
if(i<nlec)
|
|
{
|
|
//--- Add slack terms for equality constraints
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,-1);
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs+1,1);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,offsslacknlec+2*i);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs+1,offsslacknlec+2*i+1);
|
|
offs+=2;
|
|
}
|
|
else
|
|
{
|
|
//--- Add slack terms for inequality constraints
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,-1);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,offsslacknlic+(i-nlec));
|
|
offs++;
|
|
}
|
|
subsolver.m_sparseefflc.m_RIdx.Set(subsolver.m_sparseefflc.m_M+i+1,offs);
|
|
//--- Set box constraints on slack variables and bounds on linear equality/inequality constraints
|
|
v=vv*fi[1+i];
|
|
if(i<nlec)
|
|
{
|
|
//--- Equality constraint
|
|
subsolver.m_cural.Set(subsolver.m_sparseefflc.m_M+i,-v);
|
|
subsolver.m_curau.Set(subsolver.m_sparseefflc.m_M+i,-v);
|
|
subsolver.m_curbndl.Set(offsslacknlec+2*i,0);
|
|
subsolver.m_curbndl.Set(offsslacknlec+2*i+1,0);
|
|
subsolver.m_curbndu.Set(offsslacknlec+2*i,MathAbs(v));
|
|
subsolver.m_curbndu.Set(offsslacknlec+2*i+1,MathAbs(v));
|
|
if(v>=0.0)
|
|
{
|
|
subsolver.m_d0.Set(offsslacknlec+2*i,MathAbs(v));
|
|
subsolver.m_d0.Set(offsslacknlec+2*i+1,0);
|
|
}
|
|
else
|
|
{
|
|
subsolver.m_d0.Set(offsslacknlec+2*i,0);
|
|
subsolver.m_d0.Set(offsslacknlec+2*i+1,MathAbs(v));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Inequality constraint
|
|
subsolver.m_cural.Set(subsolver.m_sparseefflc.m_M+i,AL_NEGINF);
|
|
subsolver.m_curau.Set(subsolver.m_sparseefflc.m_M+i,-v);
|
|
subsolver.m_curbndl.Set(offsslacknlic+(i-nlec),0);
|
|
subsolver.m_curbndu.Set(offsslacknlic+(i-nlec),MathMax(v,0));
|
|
subsolver.m_d0.Set(offsslacknlic+(i-nlec),MathMax(v,0));
|
|
}
|
|
}
|
|
subsolver.m_sparseefflc.m_M+=(nlec+nlic);
|
|
//--- Finalize sparse matrix structure
|
|
//--- check
|
|
if(!CAp::Assert(subsolver.m_sparseefflc.m_RIdx[subsolver.m_sparseefflc.m_M]<=subsolver.m_sparseefflc.m_Idx.Size(),__FUNCTION__+": critical integrity check failed"))
|
|
return(false);
|
|
if(!CAp::Assert(subsolver.m_sparseefflc.m_RIdx[subsolver.m_sparseefflc.m_M]<=subsolver.m_sparseefflc.m_Vals.Size(),__FUNCTION__+": critical integrity check failed"))
|
|
return(false);
|
|
subsolver.m_sparseefflc.m_NInitialized=subsolver.m_sparseefflc.m_RIdx[subsolver.m_sparseefflc.m_M];
|
|
CSparse::SparseInitDUIdx(subsolver.m_sparseefflc);
|
|
//--- Solve quadratic program
|
|
switch(subsolver.m_algokind)
|
|
{
|
|
case 0:
|
|
//--- Use dense IPM.
|
|
//--- We always treat its result as a valid solution, even for TerminationType<=0.
|
|
//--- In case anything is wrong with solution vector, we will detect it during line
|
|
//--- search phase (merit function does not increase).
|
|
//--- NOTE: because we cleaned up constraints that are DEFINITELY inactive within
|
|
//--- trust region, we do not have to worry about StopOnExcessiveBounds option.
|
|
subsolver.m_tmp0=vector<double>::Full(nslack,State.m_trustrad);
|
|
subsolver.m_tmp1=vector<double>::Zeros(nslack);
|
|
CVIPMSolver::VIPMInitDenseWithSlacks(subsolver.m_ipmsolver,subsolver.m_tmp0,subsolver.m_tmp1,n,nslack);
|
|
CVIPMSolver::VIPMSetQuadraticLinear(subsolver.m_ipmsolver,subsolver.m_h,subsolver.m_sparsedummy,0,true,subsolver.m_curb);
|
|
CVIPMSolver::VIPMSetConstraints(subsolver.m_ipmsolver,subsolver.m_curbndl,subsolver.m_curbndu,subsolver.m_sparseefflc,subsolver.m_sparseefflc.m_M,subsolver.m_densedummy,0,subsolver.m_cural,subsolver.m_curau);
|
|
CVIPMSolver::VIPMOptimize(subsolver.m_ipmsolver,false,subsolver.m_tmp0,subsolver.m_tmp1,subsolver.m_tmp2,terminationtype);
|
|
d=subsolver.m_tmp0;
|
|
lagmult=subsolver.m_tmp2;
|
|
break;
|
|
case 1:
|
|
//--- Use fast active set
|
|
FASSolve(subsolver,subsolver.m_d0,subsolver.m_h,n,subsolver.m_curb,nslack,subsolver.m_curbndl,subsolver.m_curbndu,subsolver.m_sparseefflc,subsolver.m_sparseefflc.m_M,subsolver.m_cural,subsolver.m_curau,State.m_trustrad,terminationtype,d,lagmult);
|
|
if(terminationtype<=0)
|
|
{
|
|
//--- QP solver failed due to numerical errors; exit
|
|
result=false;
|
|
}
|
|
break;
|
|
default:
|
|
//--- Unexpected
|
|
CAp::Assert(false,__FUNCTION__+": unexpected subsolver type");
|
|
result=false;
|
|
break;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes MeritPhase temporaries. It should be |
|
|
//| called before beginning of each new iteration. You may call it |
|
|
//| multiple times for the same instance of MeritPhase temporaries. |
|
|
//| INPUT PARAMETERS: |
|
|
//| MeritState - instance to be initialized. |
|
|
//| N - problem dimensionality |
|
|
//| NEC, NIC - linear equality / inequality constraint count |
|
|
//| NLEC, NLIC - nonlinear equality / inequality constraint count|
|
|
//| AbsLagMemory - array[MemLen, NEC + NIC + NLEC + NLIC], stores |
|
|
//| absolute values of Lagrange multipliers for the |
|
|
//| last MemLen iterations |
|
|
//| MemLen - memory length |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| MeritState - instance being initialized |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSQP::MeritPhaseInit(CMinSQPMeritPhaseState &meritstate,
|
|
CRowDouble &curx,
|
|
CRowDouble &curfi,
|
|
CMatrixDouble &curj,
|
|
int n,
|
|
int nec,
|
|
int nic,
|
|
int nlec,
|
|
int nlic,
|
|
CMatrixDouble &abslagmemory,
|
|
int memlen)
|
|
{
|
|
int nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
|
|
meritstate.m_n=n;
|
|
meritstate.m_nec=nec;
|
|
meritstate.m_nic=nic;
|
|
meritstate.m_nlec=nlec;
|
|
meritstate.m_nlic=nlic;
|
|
meritstate.m_penalties=vector<double>::Zeros(nec+nic+nlec+nlic);
|
|
for(int i=0; i<memlen; i++)
|
|
CAblasF::RMergeMaxRV(nec+nic+nlec+nlic,abslagmemory,i,meritstate.m_penalties);
|
|
meritstate.m_stepkx=curx;
|
|
meritstate.m_stepkfi=curfi;
|
|
meritstate.m_stepkj=curj;
|
|
meritstate.m_stepkx.Resize(n);
|
|
meritstate.m_stepkfi.Resize(1+nlec+nlic);
|
|
meritstate.m_stepkj.Resize(1+nlec+nlic,n);
|
|
meritstate.m_d.Resize(nslack);
|
|
meritstate.m_dx.Resize(nslack);
|
|
meritstate.m_stepkxc.Resize(n);
|
|
meritstate.m_stepkxn.Resize(n);
|
|
meritstate.m_stepkfic.Resize(1+nlec+nlic);
|
|
meritstate.m_stepkfin.Resize(1+nlec+nlic);
|
|
meritstate.m_stepkj.Resize(1+nlec+nlic,n);
|
|
meritstate.m_stepkjc.Resize(1+nlec+nlic,n);
|
|
meritstate.m_stepkjn.Resize(1+nlec+nlic,n);
|
|
meritstate.m_stepklaggrad.Resize(n);
|
|
meritstate.m_stepknlaggrad.Resize(n);
|
|
meritstate.m_lagmult.Resize(nec+nic+nlec+nlic);
|
|
meritstate.m_dummylagmult.Resize(nec+nic+nlec+nlic);
|
|
meritstate.m_rmeritphasestate.ia.Resize(8);
|
|
ArrayResize(meritstate.m_rmeritphasestate.ba,4);
|
|
meritstate.m_rmeritphasestate.ra.Resize(12);
|
|
meritstate.m_rmeritphasestate.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tries to perform either phase #1 or phase #3 step. |
|
|
//| Former corresponds to linear model step (without conjugacy |
|
|
//| constraints) with correction for nonlinearity ("second order |
|
|
//| correction"). Such correction helps to overcome Maratos effect |
|
|
//| (a tendency of L1 penalized merit functions to reject nonzero |
|
|
//| steps). |
|
|
//| Latter is a step using linear model with no second order |
|
|
//| correction. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - SQP solver State |
|
|
//| SMonitor - smoothness monitor |
|
|
//| UserTerminationNeeded - True if user requested termination |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - RepTerminationType is set to current termination |
|
|
//| code (if Status = 0). |
|
|
//| Status - when reverse communication is done, Status is set |
|
|
//| to: |
|
|
//| * positive value, if we can proceed to the next |
|
|
//| stage of the outer iteration |
|
|
//| * zero, if algorithm is terminated |
|
|
//| (RepTerminationType is set to appropriate value) |
|
|
//+------------------------------------------------------------------+
|
|
bool CNLCSQP::MeritPhaseIteration(CMinSQPState &State,
|
|
CMinSQPMeritPhaseState &meritstate,
|
|
CSmoothnessMonitor &smonitor,
|
|
bool userterminationneeded)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=0;
|
|
int nslack=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int nlec=0;
|
|
int nlic=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double mx=0;
|
|
double f0=0;
|
|
double f1=0;
|
|
double nu=0;
|
|
double localstp=0;
|
|
double tol=0;
|
|
double stepklagval=0;
|
|
double stepknlagval=0;
|
|
bool hessianupdateperformed=false;
|
|
bool dotrace=false;
|
|
bool doprobing=false;
|
|
bool dotracexd=false;
|
|
double stp=0;
|
|
double expandedrad=0;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(meritstate.m_rmeritphasestate.stage>=0)
|
|
{
|
|
n=meritstate.m_rmeritphasestate.ia[0];
|
|
nslack=meritstate.m_rmeritphasestate.ia[1];
|
|
nec=meritstate.m_rmeritphasestate.ia[2];
|
|
nic=meritstate.m_rmeritphasestate.ia[3];
|
|
nlec=meritstate.m_rmeritphasestate.ia[4];
|
|
nlic=meritstate.m_rmeritphasestate.ia[5];
|
|
i=meritstate.m_rmeritphasestate.ia[6];
|
|
j=meritstate.m_rmeritphasestate.ia[7];
|
|
hessianupdateperformed=meritstate.m_rmeritphasestate.ba[0];
|
|
dotrace=meritstate.m_rmeritphasestate.ba[1];
|
|
doprobing=meritstate.m_rmeritphasestate.ba[2];
|
|
dotracexd=meritstate.m_rmeritphasestate.ba[3];
|
|
v=meritstate.m_rmeritphasestate.ra[0];
|
|
vv=meritstate.m_rmeritphasestate.ra[1];
|
|
mx=meritstate.m_rmeritphasestate.ra[2];
|
|
f0=meritstate.m_rmeritphasestate.ra[3];
|
|
f1=meritstate.m_rmeritphasestate.ra[4];
|
|
nu=meritstate.m_rmeritphasestate.ra[5];
|
|
localstp=meritstate.m_rmeritphasestate.ra[6];
|
|
tol=meritstate.m_rmeritphasestate.ra[7];
|
|
stepklagval=meritstate.m_rmeritphasestate.ra[8];
|
|
stepknlagval=meritstate.m_rmeritphasestate.ra[9];
|
|
stp=meritstate.m_rmeritphasestate.ra[10];
|
|
expandedrad=meritstate.m_rmeritphasestate.ra[11];
|
|
}
|
|
else
|
|
{
|
|
n=-536;
|
|
nslack=487;
|
|
nec=-115;
|
|
nic=886;
|
|
nlec=346;
|
|
nlic=-722;
|
|
i=-413;
|
|
j=-461;
|
|
hessianupdateperformed=true;
|
|
dotrace=true;
|
|
doprobing=false;
|
|
dotracexd=false;
|
|
v=306;
|
|
vv=-1011;
|
|
mx=951;
|
|
f0=-463;
|
|
f1=88;
|
|
nu=-861;
|
|
localstp=-678;
|
|
tol=-731;
|
|
stepklagval=-675;
|
|
stepknlagval=-763;
|
|
stp=-233;
|
|
expandedrad=-936;
|
|
}
|
|
switch(meritstate.m_rmeritphasestate.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
nlec=State.m_nlec;
|
|
nlic=State.m_nlic;
|
|
nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
dotrace=CAp::IsTraceEnabled("SQP");
|
|
dotracexd=dotrace && CAp::IsTraceEnabled("SQP.DETAILED");
|
|
doprobing=CAp::IsTraceEnabled("SQP.PROBING");
|
|
//--- check
|
|
if(!CAp::Assert(meritstate.m_lagmult.Size()>=nec+nic+nlec+nlic,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
//--- Report iteration beginning
|
|
if(dotrace)
|
|
CAp::Trace("\n--- quadratic step ---------------------------------------------------------------------------------\n");
|
|
//--- Default decision is to continue algorithm
|
|
meritstate.m_status=1;
|
|
meritstate.m_increasebigc=false;
|
|
stp=0;
|
|
//--- Determine step direction using initial quadratic model.
|
|
//--- Update penalties vector with current Lagrange multipliers.
|
|
if(!QPSubproblemSolve(State,State.m_subsolver,meritstate.m_stepkx,meritstate.m_stepkfi,meritstate.m_stepkj,meritstate.m_d,meritstate.m_lagmult,j))
|
|
{
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> [WARNING] QP subproblem failed with TerminationType=%d\n",j));
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> QP subproblem solved with TerminationType=%d\n",j));
|
|
for(i=0; i<=nec+nic+nlec+nlic-1; i++)
|
|
meritstate.m_penalties.Set(i,MathMax(meritstate.m_penalties[i],MathAbs(meritstate.m_lagmult[i])));
|
|
//--- Perform merit function line search.
|
|
//--- First, we try unit step. If it does not decrease merit function,
|
|
//--- a second-order correction is tried (helps to combat Maratos effect).
|
|
localstp=1.0;
|
|
f0=MeritFunction(State,meritstate.m_stepkx,meritstate.m_stepkfi,meritstate.m_lagmult,meritstate.m_penalties,meritstate.m_tmpmerit);
|
|
for(i=0; i<n; i++)
|
|
meritstate.m_stepkxn.Set(i,meritstate.m_stepkx[i]+meritstate.m_d[i]*localstp);
|
|
SQPSendX(State,meritstate.m_stepkxn);
|
|
State.m_needfij=true;
|
|
meritstate.m_rmeritphasestate.stage=0;
|
|
label=-1;
|
|
break;
|
|
}
|
|
//--- Main loop
|
|
while(label>=0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 0:
|
|
State.m_needfij=false;
|
|
if(!SQPRetrieveFIJ(State,meritstate.m_stepkfin,meritstate.m_stepkjn))
|
|
{
|
|
//--- Failed to retrieve func/Jac, infinities detected
|
|
State.m_repterminationtype=-8;
|
|
meritstate.m_status=0;
|
|
if(dotrace)
|
|
CAp::Trace("[ERROR] infinities in target/constraints are detected\n");
|
|
return(false);
|
|
}
|
|
f1=MeritFunction(State,meritstate.m_stepkxn,meritstate.m_stepkfin,meritstate.m_lagmult,meritstate.m_penalties,meritstate.m_tmpmerit);
|
|
if(f1<f0)
|
|
{
|
|
label=4;
|
|
break;
|
|
}
|
|
//--- Full step increases merit function. Let's compute second order
|
|
//--- correction to the constraint model and recompute trial step D:
|
|
//--- * use original model of the target
|
|
//--- * extrapolate model of nonlinear constraints at StepKX+D back to origin
|
|
if(dotrace)
|
|
CAp::Trace("> preparing second-order correction\n");
|
|
meritstate.m_stepkfic.Set(0,meritstate.m_stepkfi[0]);
|
|
meritstate.m_stepkjc=meritstate.m_stepkj;
|
|
for(i=1; i<=nlec+nlic; i++)
|
|
{
|
|
v=CAblasF::RDotVR(n,meritstate.m_d,meritstate.m_stepkj,i);
|
|
meritstate.m_stepkjc.Row(i,meritstate.m_stepkj,i);
|
|
meritstate.m_stepkfic.Set(i,meritstate.m_stepkfin[i]-v);
|
|
}
|
|
if(!QPSubproblemSolve(State,State.m_subsolver,meritstate.m_stepkx,meritstate.m_stepkfic,meritstate.m_stepkjc,meritstate.m_dx,meritstate.m_dummylagmult,j))
|
|
{
|
|
if(dotrace)
|
|
CAp::Trace("> [WARNING] second-order QP subproblem failed\n");
|
|
return(false);
|
|
}
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> second-order QP subproblem solved with TerminationType=%d\n",j));
|
|
meritstate.m_d=meritstate.m_dx;
|
|
//--- Perform line search, we again try full step (maybe it will work after SOC)
|
|
localstp=1.0;
|
|
nu=0.5;
|
|
f1=f0;
|
|
COptServ::SmoothnessMonitorStartLineSearch(smonitor,meritstate.m_stepkx,meritstate.m_stepkfi,meritstate.m_stepkj);
|
|
case 6:
|
|
for(i=0; i<n; i++)
|
|
meritstate.m_stepkxn.Set(i,meritstate.m_stepkx[i]+meritstate.m_d[i]*localstp);
|
|
SQPSendX(State,meritstate.m_stepkxn);
|
|
State.m_needfij=true;
|
|
meritstate.m_rmeritphasestate.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_needfij=false;
|
|
if(!SQPRetrieveFIJ(State,meritstate.m_stepkfin,meritstate.m_stepkjn))
|
|
{
|
|
//--- Failed to retrieve func/Jac, infinities detected
|
|
State.m_repterminationtype=-8;
|
|
meritstate.m_status=0;
|
|
if(dotrace)
|
|
CAp::Trace("[ERROR] infinities in target/constraints are detected\n");
|
|
return(false);
|
|
}
|
|
COptServ::SmoothnessMonitorEnqueuePoint(smonitor,meritstate.m_d,localstp,meritstate.m_stepkxn,meritstate.m_stepkfin,meritstate.m_stepkjn);
|
|
f1=MeritFunction(State,meritstate.m_stepkxn,meritstate.m_stepkfin,meritstate.m_lagmult,meritstate.m_penalties,meritstate.m_tmpmerit);
|
|
if(f1<f0)
|
|
{
|
|
//--- Step is found!
|
|
label=7;
|
|
break;
|
|
}
|
|
if(localstp<0.001)
|
|
{
|
|
//--- Step is shorter than 0.001 times current search direction,
|
|
//--- it means that no good step can be found.
|
|
localstp=0;
|
|
SQPCopyState(State,meritstate.m_stepkx,meritstate.m_stepkfi,meritstate.m_stepkj,meritstate.m_stepkxn,meritstate.m_stepkfin,meritstate.m_stepkjn);
|
|
label=7;
|
|
break;
|
|
}
|
|
localstp=nu*localstp;
|
|
nu=MathMax(0.1,0.5*nu);
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
COptServ::SmoothnessMonitorFinalizeLineSearch(smonitor);
|
|
case 4:
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i])
|
|
meritstate.m_stepkxn.Set(i,MathMax(meritstate.m_stepkxn[i],State.m_scaledbndl[i]));
|
|
if(State.m_HasBndU[i])
|
|
meritstate.m_stepkxn.Set(i,MathMin(meritstate.m_stepkxn[i],State.m_scaledbndu[i]));
|
|
}
|
|
if(userterminationneeded)
|
|
{
|
|
//--- User requested termination, break before we move to new point
|
|
State.m_repterminationtype=8;
|
|
meritstate.m_status=0;
|
|
if(dotrace)
|
|
CAp::Trace("> user requested termination\n");
|
|
return(false);
|
|
}
|
|
LagrangianFG(State,meritstate.m_stepkx,State.m_trustrad,meritstate.m_stepkfi,meritstate.m_stepkj,meritstate.m_lagmult,meritstate.m_tmplagrangianfg,stepklagval,meritstate.m_stepklaggrad);
|
|
LagrangianFG(State,meritstate.m_stepkxn,State.m_trustrad,meritstate.m_stepkfin,meritstate.m_stepkjn,meritstate.m_lagmult,meritstate.m_tmplagrangianfg,stepknlagval,meritstate.m_stepknlaggrad);
|
|
//--- Decide whether we want to request increase BigC (a constraint enforcing multiplier for L1 penalized
|
|
//--- QP subproblem) or not.
|
|
//--- An increase is NOT needed if at least one of the following holds:
|
|
//--- * present value of BigC is already nearly maximum
|
|
//--- * a long step was performed
|
|
//--- * any single constraint can be made feasible within box with radius slightly larger max|D|
|
|
//--- Thus, BigC is requested to be increased if a short step was made, but there are some
|
|
//--- constraints that are infeasible within max|D|-sized box
|
|
if(CAblasF::RMaxAbsV(n,meritstate.m_d)<(0.9*State.m_trustrad) && State.m_bigc<(0.9*m_maxbigc))
|
|
{
|
|
expandedrad=1.1*CAblasF::RMaxAbsV(n,meritstate.m_d);
|
|
tol=MathMax(MathSqrt(CMath::m_machineepsilon)*State.m_trustrad,1000*CMath::m_machineepsilon);
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
v=0;
|
|
vv=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v+=State.m_scaledcleic.Get(i,j)*State.m_stepkx[j];
|
|
vv+=MathAbs(State.m_scaledcleic.Get(i,j)*expandedrad);
|
|
}
|
|
v-=State.m_scaledcleic.Get(i,n);
|
|
if(i>=nec)
|
|
v=MathMax(v,0.0);
|
|
meritstate.m_increasebigc=meritstate.m_increasebigc || MathAbs(v)>(vv+tol);
|
|
}
|
|
for(i=1; i<=nlec+nlic; i++)
|
|
{
|
|
v=State.m_stepkfi[i];
|
|
vv=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
vv+=MathAbs(State.m_stepkj.Get(i,j)*expandedrad);
|
|
}
|
|
if(i>=nlec+1)
|
|
v=MathMax(v,0.0);
|
|
meritstate.m_increasebigc=meritstate.m_increasebigc || MathAbs(v)>(vv+tol);
|
|
}
|
|
}
|
|
//--- Trace
|
|
if(!dotrace)
|
|
{
|
|
label=8;
|
|
break;
|
|
}
|
|
//--- Perform agressive probing of the search direction - additional function evaluations
|
|
//--- which help us to determine possible discontinuity and nonsmoothness of the problem
|
|
if(!doprobing)
|
|
{
|
|
label=10;
|
|
break;
|
|
}
|
|
COptServ::SmoothnessMonitorStartProbing(smonitor,1.0,2,State.m_trustrad);
|
|
COptServ::SmoothnessMonitorStartLineSearch(smonitor,meritstate.m_stepkx,meritstate.m_stepkfi,meritstate.m_stepkj);
|
|
case 12:
|
|
if(!COptServ::SmoothnessMonitorProbe(smonitor))
|
|
{
|
|
label=13;
|
|
break;
|
|
}
|
|
for(j=0; j<n; j++)
|
|
{
|
|
meritstate.m_stepkxc.Set(j,meritstate.m_stepkx[j]+meritstate.m_d[j]*smonitor.m_probingstp);
|
|
if(State.m_HasBndL[j])
|
|
meritstate.m_stepkxc.Set(j,MathMax(meritstate.m_stepkxc[j],State.m_scaledbndl[j]));
|
|
if(State.m_HasBndU[j])
|
|
meritstate.m_stepkxc.Set(j,MathMin(meritstate.m_stepkxc[j],State.m_scaledbndu[j]));
|
|
}
|
|
SQPSendX(State,meritstate.m_stepkxc);
|
|
State.m_needfij=true;
|
|
meritstate.m_rmeritphasestate.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_needfij=false;
|
|
if(!SQPRetrieveFIJ(State,meritstate.m_stepkfic,meritstate.m_stepkjc))
|
|
{
|
|
label=13;
|
|
break;
|
|
}
|
|
smonitor.m_probingf.Set(0,RawLagrangian(State,meritstate.m_stepkxc,meritstate.m_stepkfic,meritstate.m_lagmult,meritstate.m_penalties,meritstate.m_tmpmerit));
|
|
smonitor.m_probingf.Set(1,meritstate.m_stepkfic[0]);
|
|
COptServ::SmoothnessMonitorEnqueuePoint(smonitor,meritstate.m_d,smonitor.m_probingstp,meritstate.m_stepkxc,meritstate.m_stepkfic,meritstate.m_stepkjc);
|
|
label=12;
|
|
break;
|
|
case 13:
|
|
COptServ::SmoothnessMonitorFinalizeLineSearch(smonitor);
|
|
CAp::Trace("*** ------------------------------------------------------------\n");
|
|
CAp::Trace("*** | probing search direction suggested by QP subproblem |\n");
|
|
CAp::Trace("*** ------------------------------------------------------------\n");
|
|
CAp::Trace("*** | Step | Lagrangian (unaugmentd)| Target function |\n");
|
|
CAp::Trace("*** |along D| must be smooth | must be smooth |\n");
|
|
CAp::Trace("*** | | function | slope | function | slope |\n");
|
|
COptServ::SmoothnessMonitorTraceProbingResults(smonitor);
|
|
case 10:
|
|
//--- Update debug curvature information - TraceGamma[]
|
|
v=0;
|
|
mx=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
vv=meritstate.m_stepkxn[j]-meritstate.m_stepkx[j];
|
|
mx=MathMax(mx,MathAbs(vv));
|
|
v+=vv*vv;
|
|
}
|
|
if(v>0.0)
|
|
{
|
|
//--- Step is long enough, update curvature information (used for debugging)
|
|
for(i=0; i<=nlec+nlic; i++)
|
|
{
|
|
vv=0;
|
|
for(j=0; j<n; j++)
|
|
vv+=(meritstate.m_stepkjn.Get(i,j)-meritstate.m_stepkj.Get(i,j))*(meritstate.m_stepkxn[j]-meritstate.m_stepkx[j]);
|
|
State.m_tracegamma.Set(i,MathMax(State.m_tracegamma[i],MathAbs(vv/(v+100*n*CMath::m_machineepsilon*CMath::m_machineepsilon))));
|
|
}
|
|
}
|
|
//--- Output other information
|
|
mx=(meritstate.m_d.Abs()/State.m_trustrad).Max();
|
|
if(localstp>0.0)
|
|
CAp::Trace("> nonzero linear step was performed\n");
|
|
else
|
|
CAp::Trace("> zero linear step was performed\n");
|
|
CAp::Trace(StringFormat("max(|Di|)/TrustRad = %.6f\n",mx));
|
|
CAp::Trace(StringFormat("stp = %.6f\n",localstp));
|
|
if(dotracexd)
|
|
{
|
|
CAp::Trace("X0 (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(meritstate.m_stepkx,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("D (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(meritstate.m_d,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("X1 (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(meritstate.m_stepkxn,0,n);
|
|
CAp::Trace("\n");
|
|
}
|
|
CAp::Trace(StringFormat("meritF: %14.6E -> %14.6E (delta=%11.3E)\n",f0,f1,f1 - f0));
|
|
CAp::Trace(StringFormat("scaled-targetF: %14.6E -> %14.6E (delta=%11.3E)\n",meritstate.m_stepkfi[0],meritstate.m_stepkfin[0],meritstate.m_stepkfin[0] - meritstate.m_stepkfi[0]));
|
|
CAp::Trace("> evaluating possible Hessian update\n");
|
|
v=(meritstate.m_stepkxn-meritstate.m_stepkx+0).Dot(meritstate.m_stepknlaggrad-meritstate.m_stepklaggrad+0);
|
|
CAp::Trace(StringFormat("(Sk,Yk) = %.3E\n",v));
|
|
v=MathPow(meritstate.m_stepkxn-meritstate.m_stepkx+0,2.0).Sum();
|
|
CAp::Trace(StringFormat("(Sk,Sk) = %.3E\n",v));
|
|
v=MathPow(meritstate.m_stepknlaggrad-meritstate.m_stepklaggrad+0,2.0).Sum();
|
|
CAp::Trace(StringFormat("(Yk,Yk) = %.3E\n",v));
|
|
v=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v+=CMath::Sqr(meritstate.m_stepkxn[i]-meritstate.m_stepkx[i])
|
|
*State.m_subsolver.m_h.Get(i,i);
|
|
for(j=i+1; j<n; j++)
|
|
v+=2*(meritstate.m_stepkxn[i]-meritstate.m_stepkx[i])*State.m_subsolver.m_h.Get(i,j)*(meritstate.m_stepkxn[j]-meritstate.m_stepkx[j]);
|
|
}
|
|
CAp::Trace(StringFormat("Sk*Bk*Sk = %.3E\n",v));
|
|
v=State.m_subsolver.m_h.Get(0,0);
|
|
for(i=1; i<n; i++)
|
|
v=MathMin(v,State.m_subsolver.m_h.Get(i,i));
|
|
CAp::Trace(StringFormat("mindiag(Bk) = %.3E\n",v));
|
|
v=State.m_subsolver.m_h.Get(0,0);
|
|
for(i=1; i<n; i++)
|
|
v=MathMax(v,State.m_subsolver.m_h.Get(i,i));
|
|
CAp::Trace(StringFormat("maxdiag(Bk) = %.3E\n",v));
|
|
case 8:
|
|
//--- Perform Hessian update
|
|
hessianupdateperformed=false;
|
|
if(localstp>0.0)
|
|
hessianupdateperformed=QPSubProblemUpdateHessian(State,State.m_subsolver,meritstate.m_stepkx,meritstate.m_stepklaggrad,meritstate.m_stepkxn,meritstate.m_stepknlaggrad);
|
|
if(dotrace)
|
|
{
|
|
if(hessianupdateperformed)
|
|
{
|
|
CAp::Trace("> Hessian updated\n");
|
|
v=State.m_subsolver.m_h.Get(0,0);
|
|
for(i=0; i<n; i++)
|
|
v=MathMin(v,State.m_subsolver.m_h.Get(i,i));
|
|
CAp::Trace(StringFormat("mindiag(Bk) = %.3E\n",v));
|
|
v=State.m_subsolver.m_h.Get(0,0);
|
|
for(i=0; i<n; i++)
|
|
v=MathMax(v,State.m_subsolver.m_h.Get(i,i));
|
|
CAp::Trace(StringFormat("maxdiag(Bk) = %.3E\n",v));
|
|
}
|
|
else
|
|
CAp::Trace("> skipping Hessian update\n");
|
|
}
|
|
//--- Move to new point
|
|
stp=localstp;
|
|
SQPCopyState(State,meritstate.m_stepkxn,meritstate.m_stepkfin,meritstate.m_stepkjn,meritstate.m_stepkx,meritstate.m_stepkfi,meritstate.m_stepkj);
|
|
if(localstp<=0.0)
|
|
{
|
|
label=14;
|
|
break;
|
|
}
|
|
//--- Report one more inner iteration
|
|
SQPSendX(State,meritstate.m_stepkx);
|
|
State.m_f=meritstate.m_stepkfi[0]*State.m_fscales[0];
|
|
State.m_xupdated=true;
|
|
meritstate.m_rmeritphasestate.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_xupdated=false;
|
|
//--- Update constraint violations
|
|
COptServ::CheckLcViolation(State.m_scaledcleic,State.m_lcsrcidx,nec,nic,meritstate.m_stepkx,n,State.m_replcerr,State.m_replcidx);
|
|
COptServ::UnScaleAndCheckNLcViolation(meritstate.m_stepkfi,State.m_fscales,nlec,nlic,State.m_repnlcerr,State.m_repnlcidx);
|
|
case 14:
|
|
return(false);
|
|
}
|
|
}
|
|
//--- Saving State
|
|
result=true;
|
|
meritstate.m_rmeritphasestate.ba[0]=hessianupdateperformed;
|
|
meritstate.m_rmeritphasestate.ba[1]=dotrace;
|
|
meritstate.m_rmeritphasestate.ba[2]=doprobing;
|
|
meritstate.m_rmeritphasestate.ba[3]=dotracexd;
|
|
meritstate.m_rmeritphasestate.ia.Set(0,n);
|
|
meritstate.m_rmeritphasestate.ia.Set(1,nslack);
|
|
meritstate.m_rmeritphasestate.ia.Set(2,nec);
|
|
meritstate.m_rmeritphasestate.ia.Set(3,nic);
|
|
meritstate.m_rmeritphasestate.ia.Set(4,nlec);
|
|
meritstate.m_rmeritphasestate.ia.Set(5,nlic);
|
|
meritstate.m_rmeritphasestate.ia.Set(6,i);
|
|
meritstate.m_rmeritphasestate.ia.Set(7,j);
|
|
meritstate.m_rmeritphasestate.ra.Set(0,v);
|
|
meritstate.m_rmeritphasestate.ra.Set(1,vv);
|
|
meritstate.m_rmeritphasestate.ra.Set(2,mx);
|
|
meritstate.m_rmeritphasestate.ra.Set(3,f0);
|
|
meritstate.m_rmeritphasestate.ra.Set(4,f1);
|
|
meritstate.m_rmeritphasestate.ra.Set(5,nu);
|
|
meritstate.m_rmeritphasestate.ra.Set(6,localstp);
|
|
meritstate.m_rmeritphasestate.ra.Set(7,tol);
|
|
meritstate.m_rmeritphasestate.ra.Set(8,stepklagval);
|
|
meritstate.m_rmeritphasestate.ra.Set(9,stepknlagval);
|
|
meritstate.m_rmeritphasestate.ra.Set(10,stp);
|
|
meritstate.m_rmeritphasestate.ra.Set(11,expandedrad);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes MeritPhase temporaries. It should be |
|
|
//| called before beginning of each new iteration. You may call it |
|
|
//| multiple times for the same instance of MeritPhase temporaries. |
|
|
//| INPUT PARAMETERS: |
|
|
//| MeritState - instance to be initialized. |
|
|
//| N - problem dimensionality |
|
|
//| NEC, NIC - linear equality / inequality constraint count |
|
|
//| NLEC, NLIC - nonlinear equality / inequality constraint count|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| IncreaseBigC - whether increasing BigC is suggested (we |
|
|
//| detected infeasible constraints that are NOT |
|
|
//| improved) or not. |
|
|
//| MeritState - instance being initialized |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSQP::MeritPhaseResults(CMinSQPMeritPhaseState &meritstate,
|
|
CRowDouble &curx,
|
|
CRowDouble &curfi,
|
|
CMatrixDouble &curj,
|
|
CRowDouble &lagmult,
|
|
bool &increasebigc,
|
|
int &status)
|
|
{
|
|
//--- copy State
|
|
increasebigc=meritstate.m_increasebigc;
|
|
status=meritstate.m_status;
|
|
curx=meritstate.m_stepkx;
|
|
curfi=meritstate.m_stepkfi;
|
|
curj=meritstate.m_stepkj;
|
|
lagmult=meritstate.m_lagmult;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copies X to State.X |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSQP::SQPSendX(CMinSQPState &State,
|
|
CRowDouble &xs)
|
|
{
|
|
int n=State.m_n;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && xs[i]<=State.m_scaledbndl[i])
|
|
{
|
|
State.m_x.Set(i,State.m_scaledbndl[i]);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndU[i] && xs[i]>=State.m_scaledbndu[i])
|
|
{
|
|
State.m_x.Set(i,State.m_scaledbndu[i]);
|
|
continue;
|
|
}
|
|
State.m_x.Set(i,xs[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Retrieves F-vector and scaled Jacobian, copies them to FiS and JS|
|
|
//| Returns: |
|
|
//| True on success, |
|
|
//| False on failure(when F or J are not finite numbers). |
|
|
//+------------------------------------------------------------------+
|
|
bool CNLCSQP::SQPRetrieveFIJ(CMinSQPState &State,
|
|
CRowDouble &fis,
|
|
CMatrixDouble &js)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=State.m_n;
|
|
int nlec=State.m_nlec;
|
|
int nlic=State.m_nlic;
|
|
double v=0;
|
|
double vv=0;
|
|
//--- main loop
|
|
v=0;
|
|
for(int i=0; i<=nlec+nlic; i++)
|
|
{
|
|
vv=1/State.m_fscales[i];
|
|
fis.Set(i,vv*State.m_fi[i]);
|
|
v=0.1*v+fis[i];
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
js.Set(i,j,vv*State.m_j.Get(i,j));
|
|
v=0.1*v+js.Get(i,j);
|
|
}
|
|
}
|
|
result=MathIsValidNumber(v);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copies State (X point, Fi vector, J jacobian) to preallocated |
|
|
//| storage. |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSQP::SQPCopyState(CMinSQPState &State,
|
|
CRowDouble &x0,
|
|
CRowDouble &fi0,
|
|
CMatrixDouble &j0,
|
|
CRowDouble &x1,
|
|
CRowDouble &fi1,
|
|
CMatrixDouble &j1)
|
|
{
|
|
x1=x0;
|
|
fi1=fi0;
|
|
j1=j0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates Lagrangian of the problem (in scaled |
|
|
//| variables): its value and gradient. |
|
|
//| Additionally it also estimates violation of linear constraints at|
|
|
//| the point as well as index of the most violated constraint |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSQP::LagrangianFG(CMinSQPState &State,
|
|
CRowDouble &x,
|
|
double trustrad,
|
|
CRowDouble &fi,
|
|
CMatrixDouble &j,
|
|
CRowDouble &lagmult,
|
|
CMinSQPTmpLagrangian &tmp,
|
|
double &f,
|
|
CRowDouble &g)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int nec=State.m_nec;
|
|
int nic=State.m_nic;
|
|
int nlec=State.m_nlec;
|
|
int nlic=State.m_nlic;
|
|
int i=0;
|
|
double v=0;
|
|
double vlag=0;
|
|
double vact=0;
|
|
double vd=0;
|
|
bool usesparsegemv=false;
|
|
//--- Target function
|
|
f=fi[0];
|
|
g=j[0]+0;
|
|
//--- Lagrangian terms for linear constraints, constraint violations
|
|
if(nec+nic>0)
|
|
{
|
|
usesparsegemv=State.m_subsolver.m_sparserawlc.m_RIdx[nec+nic]<CApServ::SparseLevel2Density()*n*(nec+nic);
|
|
tmp.m_sclagtmp0.Resize(MathMax(nec+nic,n));
|
|
tmp.m_sclagtmp1.Resize(MathMax(nec+nic,n));
|
|
if(usesparsegemv)
|
|
CSparse::SparseMV(State.m_subsolver.m_sparserawlc,x,tmp.m_sclagtmp0);
|
|
else
|
|
CAblas::RMatrixGemVect(nec+nic,n,1.0,State.m_scaledcleic,0,0,0,x,0,0.0,tmp.m_sclagtmp0,0);
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
//--- Prepare
|
|
v=tmp.m_sclagtmp0[i]-State.m_scaledcleic.Get(i,n);
|
|
vlag=lagmult[i];
|
|
tmp.m_sclagtmp1.Set(i,0);
|
|
//--- Primary Lagrangian term
|
|
vact=v;
|
|
vd=1;
|
|
f+=vlag*vact;
|
|
tmp.m_sclagtmp1.Add(i,vlag*vd);
|
|
//--- Quadratic augmentation term
|
|
if(i<nec || v>0)
|
|
vact=v;
|
|
else
|
|
vact=0;
|
|
f+=0.5*m_augmentationfactor*vact*vact;
|
|
tmp.m_sclagtmp1.Add(i,m_augmentationfactor*vact);
|
|
}
|
|
if(usesparsegemv)
|
|
{
|
|
CSparse::SparseMTV(State.m_subsolver.m_sparserawlc,tmp.m_sclagtmp1,tmp.m_sclagtmp0);
|
|
g+=tmp.m_sclagtmp0;
|
|
}
|
|
else
|
|
CAblas::RMatrixGemVect(n,nec+nic,1.0,State.m_scaledcleic,0,0,1,tmp.m_sclagtmp1,0,1.0,g,0);
|
|
}
|
|
//--- Lagrangian terms for nonlinear constraints
|
|
tmp.m_sclagtmp1=vector<double>::Zeros(nlec+nlic);
|
|
for(i=0; i<nlec+nlic; i++)
|
|
{
|
|
v=fi[1+i];
|
|
vlag=lagmult[nec+nic+i];
|
|
//--- Lagrangian term
|
|
vact=v;
|
|
vd=1;
|
|
f+=vlag*vact;
|
|
tmp.m_sclagtmp1.Add(i,vlag*vd);
|
|
//--- Augmentation term
|
|
if(i<nlec || v>0)
|
|
vact=v;
|
|
else
|
|
vact=0;
|
|
f+=0.5*m_augmentationfactor*vact*vact;
|
|
tmp.m_sclagtmp1.Add(i,m_augmentationfactor*vact);
|
|
}
|
|
CAblas::RMatrixGemVect(n,nlec+nlic,1.0,j,1,0,1,tmp.m_sclagtmp1,0,1.0,g,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates L1 - penalized merit function |
|
|
//+------------------------------------------------------------------+
|
|
double CNLCSQP::MeritFunction(CMinSQPState &State,
|
|
CRowDouble &x,
|
|
CRowDouble &fi,
|
|
CRowDouble &lagmult,
|
|
CRowDouble &penalties,
|
|
CMinSQPTmpMerit &tmp)
|
|
{
|
|
//--- create variables
|
|
double tmp0=0;
|
|
double tmp1=0;
|
|
//--- function call
|
|
MeritFunctionAndRawLagrangian(State,x,fi,lagmult,penalties,tmp,tmp0,tmp1);
|
|
//--- return result
|
|
return(tmp0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates raw (unaugmented and smooth) Lagrangian |
|
|
//+------------------------------------------------------------------+
|
|
double CNLCSQP::RawLagrangian(CMinSQPState &State,
|
|
CRowDouble &x,
|
|
CRowDouble &fi,
|
|
CRowDouble &lagmult,
|
|
CRowDouble &penalties,
|
|
CMinSQPTmpMerit &tmp)
|
|
{
|
|
//--- create variables
|
|
double tmp0=0;
|
|
double tmp1=0;
|
|
//--- function call
|
|
MeritFunctionAndRawLagrangian(State,x,fi,lagmult,penalties,tmp,tmp0,tmp1);
|
|
//--- return result
|
|
return(tmp1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates L1-penalized merit function and raw |
|
|
//| (smooth and un-augmented) Lagrangian |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSQP::MeritFunctionAndRawLagrangian(CMinSQPState &State,
|
|
CRowDouble &x,
|
|
CRowDouble &fi,
|
|
CRowDouble &lagmult,
|
|
CRowDouble &penalties,
|
|
CMinSQPTmpMerit &tmp,
|
|
double &meritf,
|
|
double &rawlag)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int nec=State.m_nec;
|
|
int nic=State.m_nic;
|
|
int nlec=State.m_nlec;
|
|
int nlic=State.m_nlic;
|
|
int i=0;
|
|
double v=0;
|
|
//--- Merit function and Lagrangian: primary term
|
|
meritf=fi[0];
|
|
rawlag=fi[0];
|
|
//--- Merit function: augmentation and penalty for linear constraints
|
|
tmp.m_mftmp0.Resize(nec+nic);
|
|
CAblas::RMatrixGemVect(nec+nic,n,1.0,State.m_scaledcleic,0,0,0,x,0,0.0,tmp.m_mftmp0,0);
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
v=tmp.m_mftmp0[i]-State.m_scaledcleic.Get(i,n);
|
|
if(i<nec)
|
|
{
|
|
//--- Merit function: augmentation term + L1 penalty term
|
|
meritf+=0.5*m_augmentationfactor*v*v;
|
|
meritf+=m_meritfunctionbase*MathAbs(v)+m_meritfunctiongain*MathAbs(1+penalties[i])*MathAbs(v);
|
|
//--- Raw Lagrangian
|
|
rawlag+=lagmult[i]*v;
|
|
}
|
|
else
|
|
{
|
|
//--- Merit function: augmentation term + L1 penalty term
|
|
meritf+=0.5*m_augmentationfactor*CMath::Sqr(MathMax(v,0));
|
|
meritf+=m_meritfunctionbase*MathMax(v,0)+m_meritfunctiongain*MathAbs(1+penalties[i])*MathMax(v,0);
|
|
//--- Raw Lagrangian
|
|
rawlag+=lagmult[i]*v;
|
|
}
|
|
}
|
|
//--- Merit function: augmentation and penalty for nonlinear constraints
|
|
for(i=0; i<nlec+nlic; i++)
|
|
{
|
|
v=fi[1+i];
|
|
if(i<nlec)
|
|
{
|
|
//--- Merit function: augmentation term + L1 penalty term
|
|
meritf+=0.5*m_augmentationfactor*v*v;
|
|
meritf+=m_meritfunctionbase*MathAbs(v)+m_meritfunctiongain*MathAbs(1+penalties[nec+nic+i])*MathAbs(v);
|
|
//--- Raw Lagrangian
|
|
rawlag+=lagmult[nec+nic+i]*v;
|
|
}
|
|
else
|
|
{
|
|
//--- Merit function: augmentation term + L1 penalty term
|
|
meritf+=0.5*m_augmentationfactor*CMath::Sqr(MathMax(v,0));
|
|
meritf+=m_meritfunctionbase*MathMax(v,0)+m_meritfunctiongain*MathAbs(1+penalties[nec+nic+i])*MathMax(v,0);
|
|
//--- Raw Lagrangian
|
|
rawlag+=lagmult[nec+nic+i]*v;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores transformation used to convert solution |
|
|
//| (primal and dual) to original variables. It is also used |
|
|
//| to store temporaries. |
|
|
//+------------------------------------------------------------------+
|
|
struct CPresolveInfo
|
|
{
|
|
int m_newm;
|
|
int m_newn;
|
|
int m_oldm;
|
|
int m_oldn;
|
|
double m_costscale;
|
|
CSparseMatrix m_sparsea;
|
|
CRowDouble m_al;
|
|
CRowDouble m_au;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_c;
|
|
CRowDouble m_colscales;
|
|
CRowDouble m_rawbndl;
|
|
CRowDouble m_rawbndu;
|
|
CRowDouble m_rowscales;
|
|
//--- constructor / destructor
|
|
CPresolveInfo(void);
|
|
~CPresolveInfo(void) {}
|
|
//---
|
|
void Copy(const CPresolveInfo &obj);
|
|
//--- overloading
|
|
void operator=(const CPresolveInfo &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CPresolveInfo::CPresolveInfo(void)
|
|
{
|
|
m_newm=0;
|
|
m_newn=0;
|
|
m_oldm=0;
|
|
m_oldn=0;
|
|
m_costscale=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CPresolveInfo::Copy(const CPresolveInfo &obj)
|
|
{
|
|
m_newm=obj.m_newm;
|
|
m_newn=obj.m_newn;
|
|
m_oldm=obj.m_oldm;
|
|
m_oldn=obj.m_oldn;
|
|
m_costscale=obj.m_costscale;
|
|
m_sparsea=obj.m_sparsea;
|
|
m_al=obj.m_al;
|
|
m_au=obj.m_au;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_c=obj.m_c;
|
|
m_colscales=obj.m_colscales;
|
|
m_rawbndl=obj.m_rawbndl;
|
|
m_rawbndu=obj.m_rawbndu;
|
|
m_rowscales=obj.m_rowscales;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CLPQPPresolve
|
|
{
|
|
public:
|
|
static void PresolveNoneScaleUser(CRowDouble &s,CRowDouble &c,CRowDouble &bndl,CRowDouble &bndu,int n,CSparseMatrix &sparsea,CRowDouble &al,CRowDouble &au,int k,CPresolveInfo &Info);
|
|
static void PresolveBwd(CPresolveInfo &Info,CRowDouble &x,CRowInt &stats,CRowDouble &lagbc,CRowDouble &laglc);
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| No presolve, just user-supplied scaling + constraint and cost |
|
|
//| vector normalization. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - array[N], user - supplied scale vector, S[I] > 0 |
|
|
//| C - array[N], costs |
|
|
//| BndL - array[N], lower bounds (may contain - INF) |
|
|
//| BndU - array[N], upper bounds (may contain + INF) |
|
|
//| N - variable count, N > 0 |
|
|
//| SparseA - matrix[K, N], sparse constraints |
|
|
//| AL - array[K], lower constraint bounds (may contain |
|
|
//| -INF) |
|
|
//| AU - array[K], upper constraint bounds (may contain |
|
|
//| +INF) |
|
|
//| K - constraint count, K >= 0 |
|
|
//| Info - presolve Info structure; temporaries allocated |
|
|
//| during previous calls may be reused by this |
|
|
//| function. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info - contains transformed C, BndL, bndU, SparseA, AL, |
|
|
//| AU and information necessary to perform backward |
|
|
//| transformation. |
|
|
//| Following fields can be acessed: |
|
|
//| * Info.NewN > 0 for transformed problem size |
|
|
//| * Info.NewM >= 0 for transformed constraint |
|
|
//| count |
|
|
//| * always: Info.C, Info.BndL, Info.BndU - |
|
|
//| array[NewN] |
|
|
//| * for Info.NewM > 0: Info.SparseA, Info.AL, |
|
|
//| Info.AU |
|
|
//| NOTE: this routine does not reallocate arrays if NNew <= NOld |
|
|
//| and/or KNew <= KOld. |
|
|
//+------------------------------------------------------------------+
|
|
void CLPQPPresolve::PresolveNoneScaleUser(CRowDouble &s,
|
|
CRowDouble &c,
|
|
CRowDouble &bndl,
|
|
CRowDouble &bndu,
|
|
int n,
|
|
CSparseMatrix &sparsea,
|
|
CRowDouble &al,
|
|
CRowDouble &au,
|
|
int k,
|
|
CPresolveInfo &Info)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double v=0;
|
|
double avgln=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(bndl.Size()>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
if(!CAp::Assert(bndu.Size()>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
if(!CAp::Assert(s.Size()>=n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(s,n),__FUNCTION__+": S contains infinite or NaN elements"))
|
|
return;
|
|
if(!CAp::Assert(c.Size()>=n,__FUNCTION__+": Length(C)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(c,n),__FUNCTION__+": C contains infinite or NaN elements"))
|
|
return;
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(!CAp::Assert(k==0 || CSparse::SparseIsCRS(sparsea),__FUNCTION__+": A is not CRS"))
|
|
return;
|
|
if(!CAp::Assert(k==0 || sparsea.m_M==k,__FUNCTION__+": rows(A)<>K"))
|
|
return;
|
|
if(!CAp::Assert(k==0 || sparsea.m_N==n,__FUNCTION__+": cols(A)<>N"))
|
|
return;
|
|
//--- Save original problem formulation
|
|
Info.m_newn=n;
|
|
Info.m_oldn=n;
|
|
Info.m_newm=k;
|
|
Info.m_oldm=k;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s[i]>0,__FUNCTION__+": S<=0"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndl[i]) || IsNegInf(bndl[i]),__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu[i]) || IsPosInf(bndu[i]),__FUNCTION__+": BndU contains NAN or -INF"))
|
|
return;
|
|
}
|
|
Info.m_colscales=s;
|
|
Info.m_rawbndl=bndl;
|
|
Info.m_rawbndu=bndu;
|
|
Info.m_rawbndl.Resize(n);
|
|
Info.m_rawbndu.Resize(n);
|
|
Info.m_colscales.Resize(n);
|
|
//--- Scale cost and box constraints
|
|
Info.m_c=c*s+0;
|
|
Info.m_bndl=bndl/s+0;
|
|
Info.m_bndu=bndu/s+0;
|
|
Info.m_c.Resize(n);
|
|
Info.m_bndl.Resize(n);
|
|
Info.m_bndu.Resize(n);
|
|
avgln=0;
|
|
for(i=0; i<n; i++)
|
|
avgln+=MathLog(1+MathAbs(Info.m_c[i]));
|
|
Info.m_costscale=MathExp(avgln/n);
|
|
Info.m_c/=Info.m_costscale;
|
|
//--- Quick exit if no linear constraints is present
|
|
if(k==0)
|
|
return;
|
|
//--- Scale constraint matrix
|
|
Info.m_al=al;
|
|
Info.m_au=au;
|
|
CSparse::SparseCopyBuf(sparsea,Info.m_sparsea);
|
|
Info.m_rowscales=vector<double>::Zeros(k);
|
|
for(i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(Info.m_al[i]) || IsNegInf(Info.m_al[i]),__FUNCTION__+": AL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(Info.m_au[i]) || IsPosInf(Info.m_au[i]),__FUNCTION__+": AU contains NAN or -INF"))
|
|
return;
|
|
j0=Info.m_sparsea.m_RIdx[i];
|
|
j1=Info.m_sparsea.m_RIdx[i+1];
|
|
for(j=j0; j<j1; j++)
|
|
{
|
|
v=s[Info.m_sparsea.m_Idx[j]]*Info.m_sparsea.m_Vals[j];
|
|
Info.m_sparsea.m_Vals.Set(j,v);
|
|
Info.m_rowscales.Set(i,MathMax(Info.m_rowscales[i],v));
|
|
}
|
|
Info.m_rowscales.Set(i,MathMax(Info.m_rowscales[i],1.0));
|
|
v=1/Info.m_rowscales[i];
|
|
for(j=j0; j<j1; j++)
|
|
Info.m_sparsea.m_Vals.Mul(j,v);
|
|
Info.m_al.Mul(i,v);
|
|
Info.m_au.Mul(i,v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Backward transformation which extracts original solution from |
|
|
//| that of the converted problem. |
|
|
//| Below NNew/KNew correspond to transformed problem size (as |
|
|
//| returned by the presolve routine) and NOld/KOld correspond to |
|
|
//| original problem size (as specified by caller). We expect that |
|
|
//| caller knows these sizes, so this routine does not report them. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Info - presolve Info structure |
|
|
//| X - array[NNew], transformed solution(primal variables)|
|
|
//| Stats - array[NNew + MNew], transformed constraint status |
|
|
//| (negative - at lower bound, positive - at upper |
|
|
//| bound, zero - inactive). |
|
|
//| LagBC - array[NNew], transformed Lagrange multipliers |
|
|
//| LagLC - array[KNew], transformed Lagrange multipliers |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[NOld], original solution(primal variables) |
|
|
//| Stats - array[NOld + MOld], original constraint status |
|
|
//| LagBC - array[NOld], Lagrange multipliers |
|
|
//| LagLC - array[KOld], Lagrange multipliers |
|
|
//| NOTE: this routine does not reallocate arrays if NOld <= NNew |
|
|
//| and / or KOld <= KNew. |
|
|
//+------------------------------------------------------------------+
|
|
void CLPQPPresolve::PresolveBwd(CPresolveInfo &Info,
|
|
CRowDouble &x,
|
|
CRowInt &stats,
|
|
CRowDouble &lagbc,
|
|
CRowDouble &laglc)
|
|
{
|
|
//--- create variables
|
|
int n=Info.m_oldn;
|
|
int m=Info.m_oldm;
|
|
int i=0;
|
|
//--- check
|
|
if(!CAp::Assert(Info.m_oldn==Info.m_newn,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(Info.m_oldm==Info.m_newm,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(stats[i]<0)
|
|
{
|
|
x.Set(i,Info.m_rawbndl[i]);
|
|
continue;
|
|
}
|
|
if(stats[i]>0)
|
|
{
|
|
x.Set(i,Info.m_rawbndu[i]);
|
|
continue;
|
|
}
|
|
x.Mul(i,Info.m_colscales[i]);
|
|
if(MathIsValidNumber(Info.m_rawbndl[i]))
|
|
x.Set(i,MathMax(x[i],Info.m_rawbndl[i]));
|
|
if(MathIsValidNumber(Info.m_rawbndu[i]))
|
|
x.Set(i,MathMin(x[i],Info.m_rawbndu[i]));
|
|
}
|
|
for(i=0; i<n; i++)
|
|
lagbc.Mul(i,Info.m_costscale/Info.m_colscales[i]) ;
|
|
for(i=0; i<m; i++)
|
|
laglc.Mul(i,Info.m_costscale/Info.m_rowscales[i]) ;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores Settings for dual simplex solver |
|
|
//+------------------------------------------------------------------+
|
|
struct CDualSimplexSettings
|
|
{
|
|
int m_maxtrfage;
|
|
int m_pricing;
|
|
int m_ratiotest;
|
|
int m_shifting;
|
|
int m_trftype;
|
|
double m_dtolabs;
|
|
double m_perturbmag;
|
|
double m_pivottol;
|
|
double m_xtolabs;
|
|
double m_xtolrelabs;
|
|
//--- constructor / destructor
|
|
CDualSimplexSettings(void) { ZeroMemory(this); }
|
|
~CDualSimplexSettings(void) {}
|
|
//---
|
|
void Copy(const CDualSimplexSettings &obj);
|
|
//--- overloading
|
|
void operator=(const CDualSimplexSettings &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CDualSimplexSettings::Copy(const CDualSimplexSettings &obj)
|
|
{
|
|
m_maxtrfage=obj.m_maxtrfage;
|
|
m_pricing=obj.m_pricing;
|
|
m_ratiotest=obj.m_ratiotest;
|
|
m_shifting=obj.m_shifting;
|
|
m_trftype=obj.m_trftype;
|
|
m_dtolabs=obj.m_dtolabs;
|
|
m_perturbmag=obj.m_perturbmag;
|
|
m_pivottol=obj.m_pivottol;
|
|
m_xtolabs=obj.m_xtolabs;
|
|
m_xtolrelabs=obj.m_xtolrelabs;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| A vector that is stored in dual dense / sparse format. |
|
|
//+------------------------------------------------------------------+
|
|
struct CDSSVector
|
|
{
|
|
int m_k;
|
|
int m_n;
|
|
CRowInt m_idx;
|
|
CRowDouble m_dense;
|
|
CRowDouble m_vals;
|
|
//--- constructor / destructor
|
|
CDSSVector(void) { m_k=0; m_n=0; }
|
|
~CDSSVector(void) {}
|
|
//---
|
|
void Copy(const CDSSVector &obj);
|
|
//--- overloading
|
|
void operator=(const CDSSVector &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CDSSVector::Copy(const CDSSVector &obj)
|
|
{
|
|
m_k=obj.m_k;
|
|
m_n=obj.m_n;
|
|
m_idx=obj.m_idx;
|
|
m_dense=obj.m_dense;
|
|
m_vals=obj.m_vals;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores basis and its triangular factorization for |
|
|
//| DualSimplexState |
|
|
//+------------------------------------------------------------------+
|
|
struct CDualSimplexBasis
|
|
{
|
|
int m_m;
|
|
int m_ns;
|
|
int m_statfact;
|
|
int m_statupdt;
|
|
int m_trfage;
|
|
int m_trftype;
|
|
double m_eminu;
|
|
double m_statoffdiag;
|
|
bool m_dsevalid;
|
|
bool m_isbasic[];
|
|
bool m_isvalidtrf;
|
|
CSparseMatrix m_sparsel;
|
|
CSparseMatrix m_sparselu1;
|
|
CSparseMatrix m_sparselu2;
|
|
CSparseMatrix m_sparseludbg;
|
|
CSparseMatrix m_sparseu;
|
|
CSparseMatrix m_sparseut;
|
|
CSLUV2Buffer m_lubuf2;
|
|
CRowInt m_colpermbwd;
|
|
CRowInt m_densep2;
|
|
CRowInt m_densep2c;
|
|
CRowInt m_dk;
|
|
CRowInt m_idx;
|
|
CRowInt m_nidx;
|
|
CRowInt m_nrs;
|
|
CRowInt m_rk;
|
|
CRowInt m_rowpermbwd;
|
|
CRowInt m_tcinvidx;
|
|
CRowInt m_tmpi;
|
|
CRowInt m_utmpi;
|
|
CRowDouble m_densemu;
|
|
CRowDouble m_densepfieta;
|
|
CRowDouble m_dseweights;
|
|
CRowDouble m_utmp0;
|
|
CRowDouble m_wtmp0;
|
|
CRowDouble m_wtmp1;
|
|
CRowDouble m_wtmp2;
|
|
CMatrixDouble m_denselu2;
|
|
CMatrixDouble m_denselu;
|
|
//--- constructor / destructor
|
|
CDualSimplexBasis(void);
|
|
~CDualSimplexBasis(void) {}
|
|
//---
|
|
void Copy(const CDualSimplexBasis &obj);
|
|
//--- overloading
|
|
void operator=(const CDualSimplexBasis &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CDualSimplexBasis::CDualSimplexBasis(void)
|
|
{
|
|
m_m=0;
|
|
m_ns=0;
|
|
m_statfact=0;
|
|
m_statupdt=0;
|
|
m_trfage=0;
|
|
m_trftype=0;
|
|
m_eminu=0;
|
|
m_statoffdiag=0;
|
|
m_dsevalid=false;
|
|
m_isvalidtrf=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CDualSimplexBasis::Copy(const CDualSimplexBasis &obj)
|
|
{
|
|
m_m=obj.m_m;
|
|
m_ns=obj.m_ns;
|
|
m_statfact=obj.m_statfact;
|
|
m_statupdt=obj.m_statupdt;
|
|
m_trfage=obj.m_trfage;
|
|
m_trftype=obj.m_trftype;
|
|
m_eminu=obj.m_eminu;
|
|
m_statoffdiag=obj.m_statoffdiag;
|
|
m_dsevalid=obj.m_dsevalid;
|
|
ArrayCopy(m_isbasic,obj.m_isbasic);
|
|
m_isvalidtrf=obj.m_isvalidtrf;
|
|
m_sparsel=obj.m_sparsel;
|
|
m_sparselu1=obj.m_sparselu1;
|
|
m_sparselu2=obj.m_sparselu2;
|
|
m_sparseludbg=obj.m_sparseludbg;
|
|
m_sparseu=obj.m_sparseu;
|
|
m_sparseut=obj.m_sparseut;
|
|
m_lubuf2=obj.m_lubuf2;
|
|
m_colpermbwd=obj.m_colpermbwd;
|
|
m_densep2=obj.m_densep2;
|
|
m_densep2c=obj.m_densep2c;
|
|
m_dk=obj.m_dk;
|
|
m_idx=obj.m_idx;
|
|
m_nidx=obj.m_nidx;
|
|
m_nrs=obj.m_nrs;
|
|
m_rk=obj.m_rk;
|
|
m_rowpermbwd=obj.m_rowpermbwd;
|
|
m_tcinvidx=obj.m_tcinvidx;
|
|
m_tmpi=obj.m_tmpi;
|
|
m_utmpi=obj.m_utmpi;
|
|
m_densemu=obj.m_densemu;
|
|
m_densepfieta=obj.m_densepfieta;
|
|
m_dseweights=obj.m_dseweights;
|
|
m_utmp0=obj.m_utmp0;
|
|
m_wtmp0=obj.m_wtmp0;
|
|
m_wtmp1=obj.m_wtmp1;
|
|
m_wtmp2=obj.m_wtmp2;
|
|
m_denselu2=obj.m_denselu2;
|
|
m_denselu=obj.m_denselu;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores subproblem for DualSimplexState object |
|
|
//+------------------------------------------------------------------+
|
|
struct CDualSimplexSubproblem
|
|
{
|
|
int m_m;
|
|
int m_ns;
|
|
int m_state;
|
|
CRowInt m_bndt;
|
|
CRowInt m_bndtb;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndlb;
|
|
CRowDouble m_bndtollb;
|
|
CRowDouble m_bndtolub;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_bndub;
|
|
CRowDouble m_d;
|
|
CRowDouble m_effc;
|
|
CRowDouble m_rawc;
|
|
CRowDouble m_xa;
|
|
CRowDouble m_xb;
|
|
//--- constructor / destructor
|
|
CDualSimplexSubproblem(void) { m_m=0; m_ns=0; m_state=0; }
|
|
~CDualSimplexSubproblem(void) {}
|
|
//---
|
|
void Copy(const CDualSimplexSubproblem &obj);
|
|
//--- overloading
|
|
void operator=(const CDualSimplexSubproblem &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CDualSimplexSubproblem::Copy(const CDualSimplexSubproblem &obj)
|
|
{
|
|
m_m=obj.m_m;
|
|
m_ns=obj.m_ns;
|
|
m_state=obj.m_state;
|
|
m_bndt=obj.m_bndt;
|
|
m_bndtb=obj.m_bndtb;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndlb=obj.m_bndlb;
|
|
m_bndtollb=obj.m_bndtollb;
|
|
m_bndtolub=obj.m_bndtolub;
|
|
m_bndu=obj.m_bndu;
|
|
m_bndub=obj.m_bndub;
|
|
m_d=obj.m_d;
|
|
m_effc=obj.m_effc;
|
|
m_rawc=obj.m_rawc;
|
|
m_xa=obj.m_xa;
|
|
m_xb=obj.m_xb;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores State of the DSS solver. |
|
|
//| MUST be initialized with DSSInit(). |
|
|
//+------------------------------------------------------------------+
|
|
struct CDualSimplexState
|
|
{
|
|
int m_m;
|
|
int m_ns;
|
|
int m_possibleflipscnt;
|
|
int m_repdualbtrantime;
|
|
int m_repdualftrantime;
|
|
int m_repdualpivotrowtime;
|
|
int m_repdualpricingtime;
|
|
int m_repdualratiotesttime;
|
|
int m_repdualupdatesteptime;
|
|
int m_repfilldensemucnt;
|
|
int m_repfillpivotrowcnt;
|
|
int m_repfillrhorcnt;
|
|
int m_repiterationscount1;
|
|
int m_repiterationscount2;
|
|
int m_repiterationscount3;
|
|
int m_repiterationscount;
|
|
int m_repphase1time;
|
|
int m_repphase2time;
|
|
int m_repphase3time;
|
|
int m_repterminationtype;
|
|
double m_repfilldensemu;
|
|
double m_repfillpivotrow;
|
|
double m_repfillrhor;
|
|
bool m_dodetailedtrace;
|
|
bool m_dotimers;
|
|
bool m_dotrace;
|
|
CSparseMatrix m_a;
|
|
CSparseMatrix m_at;
|
|
CRowInt m_eligiblealphar;
|
|
CRowInt m_harrisset;
|
|
CRowInt m_possibleflips;
|
|
CRowInt m_repstats;
|
|
CRowInt m_ustmpi;
|
|
CRowDouble m_alphaq;
|
|
CRowDouble m_alphaqim;
|
|
CRowDouble m_btrantmp0;
|
|
CRowDouble m_btrantmp1;
|
|
CRowDouble m_btrantmp2;
|
|
CRowDouble m_dfctmp0;
|
|
CRowDouble m_dfctmp1;
|
|
CRowDouble m_dfctmp2;
|
|
CRowDouble m_ftrantmp0;
|
|
CRowDouble m_ftrantmp1;
|
|
CRowDouble m_rawbndl;
|
|
CRowDouble m_rawbndu;
|
|
CRowDouble m_replagbc;
|
|
CRowDouble m_replaglc;
|
|
CRowDouble m_repx;
|
|
CRowDouble m_rowscales;
|
|
CRowDouble m_tau;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CRowDouble m_tmp2;
|
|
CDualSimplexSubproblem m_phase1;
|
|
CDualSimplexSubproblem m_phase3;
|
|
CDualSimplexSubproblem m_primary;
|
|
CDualSimplexBasis m_basis;
|
|
CDSSVector m_alphar;
|
|
CDSSVector m_rhor;
|
|
CApBuff m_xydsbuf;
|
|
//--- constructor / destructor
|
|
CDualSimplexState(void);
|
|
~CDualSimplexState(void) {}
|
|
//---
|
|
void Copy(const CDualSimplexState &obj);
|
|
//--- overloading
|
|
void operator=(const CDualSimplexState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CDualSimplexState::CDualSimplexState(void)
|
|
{
|
|
m_m=0;
|
|
m_ns=0;
|
|
m_possibleflipscnt=0;
|
|
m_repdualbtrantime=0;
|
|
m_repdualftrantime=0;
|
|
m_repdualpivotrowtime=0;
|
|
m_repdualpricingtime=0;
|
|
m_repdualratiotesttime=0;
|
|
m_repdualupdatesteptime=0;
|
|
m_repfilldensemucnt=0;
|
|
m_repfillpivotrowcnt=0;
|
|
m_repfillrhorcnt=0;
|
|
m_repiterationscount1=0;
|
|
m_repiterationscount2=0;
|
|
m_repiterationscount3=0;
|
|
m_repiterationscount=0;
|
|
m_repphase1time=0;
|
|
m_repphase2time=0;
|
|
m_repphase3time=0;
|
|
m_repterminationtype=0;
|
|
m_repfilldensemu=0;
|
|
m_repfillpivotrow=0;
|
|
m_repfillrhor=0;
|
|
m_dodetailedtrace=false;
|
|
m_dotimers=false;
|
|
m_dotrace=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CDualSimplexState::Copy(const CDualSimplexState &obj)
|
|
{
|
|
m_m=obj.m_m;
|
|
m_ns=obj.m_ns;
|
|
m_possibleflipscnt=obj.m_possibleflipscnt;
|
|
m_repdualbtrantime=obj.m_repdualbtrantime;
|
|
m_repdualftrantime=obj.m_repdualftrantime;
|
|
m_repdualpivotrowtime=obj.m_repdualpivotrowtime;
|
|
m_repdualpricingtime=obj.m_repdualpricingtime;
|
|
m_repdualratiotesttime=obj.m_repdualratiotesttime;
|
|
m_repdualupdatesteptime=obj.m_repdualupdatesteptime;
|
|
m_repfilldensemucnt=obj.m_repfilldensemucnt;
|
|
m_repfillpivotrowcnt=obj.m_repfillpivotrowcnt;
|
|
m_repfillrhorcnt=obj.m_repfillrhorcnt;
|
|
m_repiterationscount1=obj.m_repiterationscount1;
|
|
m_repiterationscount2=obj.m_repiterationscount2;
|
|
m_repiterationscount3=obj.m_repiterationscount3;
|
|
m_repiterationscount=obj.m_repiterationscount;
|
|
m_repphase1time=obj.m_repphase1time;
|
|
m_repphase2time=obj.m_repphase2time;
|
|
m_repphase3time=obj.m_repphase3time;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_repfilldensemu=obj.m_repfilldensemu;
|
|
m_repfillpivotrow=obj.m_repfillpivotrow;
|
|
m_repfillrhor=obj.m_repfillrhor;
|
|
m_dodetailedtrace=obj.m_dodetailedtrace;
|
|
m_dotimers=obj.m_dotimers;
|
|
m_dotrace=obj.m_dotrace;
|
|
m_a=obj.m_a;
|
|
m_at=obj.m_at;
|
|
m_eligiblealphar=obj.m_eligiblealphar;
|
|
m_harrisset=obj.m_harrisset;
|
|
m_possibleflips=obj.m_possibleflips;
|
|
m_repstats=obj.m_repstats;
|
|
m_ustmpi=obj.m_ustmpi;
|
|
m_alphaq=obj.m_alphaq;
|
|
m_alphaqim=obj.m_alphaqim;
|
|
m_btrantmp0=obj.m_btrantmp0;
|
|
m_btrantmp1=obj.m_btrantmp1;
|
|
m_btrantmp2=obj.m_btrantmp2;
|
|
m_dfctmp0=obj.m_dfctmp0;
|
|
m_dfctmp1=obj.m_dfctmp1;
|
|
m_dfctmp2=obj.m_dfctmp2;
|
|
m_ftrantmp0=obj.m_ftrantmp0;
|
|
m_ftrantmp1=obj.m_ftrantmp1;
|
|
m_rawbndl=obj.m_rawbndl;
|
|
m_rawbndu=obj.m_rawbndu;
|
|
m_replagbc=obj.m_replagbc;
|
|
m_replaglc=obj.m_replaglc;
|
|
m_repx=obj.m_repx;
|
|
m_rowscales=obj.m_rowscales;
|
|
m_tau=obj.m_tau;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_tmp2=obj.m_tmp2;
|
|
m_phase1=obj.m_phase1;
|
|
m_phase3=obj.m_phase3;
|
|
m_primary=obj.m_primary;
|
|
m_basis=obj.m_basis;
|
|
m_alphar=obj.m_alphar;
|
|
m_rhor=obj.m_rhor;
|
|
m_xydsbuf=obj.m_xydsbuf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CRevisedDualSimplex
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const int m_maxforcedrestarts;
|
|
static const int m_safetrfage;
|
|
static const int m_defaultmaxtrfage;
|
|
static const double m_minbeta;
|
|
static const double m_maxudecay;
|
|
static const double m_shiftlen;
|
|
static const double m_alphatrigger;
|
|
static const double m_alphatrigger2;
|
|
static const int m_ssinvalid;
|
|
static const int m_ssvalidxn;
|
|
static const int m_ssvalid;
|
|
static const int m_ccfixed;
|
|
static const int m_cclower;
|
|
static const int m_ccupper;
|
|
static const int m_ccrange;
|
|
static const int m_ccfree;
|
|
static const int m_ccinfeasible;
|
|
|
|
static void DSSSettingsInit(CDualSimplexSettings &Settings);
|
|
static void DSSInit(int n,CDualSimplexState &s);
|
|
static void DSSSetProblem(CDualSimplexState &State,CRowDouble &c,CRowDouble &bndl,CRowDouble &bndu,CMatrixDouble &densea,CSparseMatrix &sparsea,int akind,CRowDouble &al,CRowDouble &au,int k,CDualSimplexBasis &proposedbasis,int basisinittype,CDualSimplexSettings &Settings);
|
|
static void DSSExportBasis(CDualSimplexState &State,CDualSimplexBasis &basis);
|
|
static void DSSOptimize(CDualSimplexState &State,CDualSimplexSettings &Settings);
|
|
|
|
private:
|
|
static void SubproblemInit(int n,CDualSimplexSubproblem &s);
|
|
static void SubproblemInitPhase1(CDualSimplexSubproblem &s0,CDualSimplexBasis &basis,CDualSimplexSubproblem &s1);
|
|
static void SubproblemInitPhase3(CDualSimplexSubproblem &s0,CDualSimplexSubproblem &s1);
|
|
static void SubproblemInferInitialXN(CDualSimplexState &State,CDualSimplexSubproblem &s);
|
|
static void SubproblemHandleXNUpdate(CDualSimplexState &State,CDualSimplexSubproblem &s);
|
|
static double InitialDualFeasibilityCorrection(CDualSimplexState &State,CDualSimplexSubproblem &s,CDualSimplexSettings &Settings);
|
|
static void Shifting(CDualSimplexState &State,CDualSimplexSubproblem &s,CDSSVector &alphar,double delta,int q,double alpharpiv,double &thetad,CDualSimplexSettings &Settings);
|
|
static void PricingStep(CDualSimplexState &State,CDualSimplexSubproblem &s,bool phase1pricing,int &p,int &r,double &delta,CDualSimplexSettings &Settings);
|
|
static void BTranStep(CDualSimplexState &State,CDualSimplexSubproblem &s,int r,CDSSVector &rhor,CDualSimplexSettings &Settings);
|
|
static void PivotRowStep(CDualSimplexState &State,CDualSimplexSubproblem &s,CDSSVector &rhor,CDSSVector &alphar,CDualSimplexSettings &Settings);
|
|
static void FTranStep(CDualSimplexState &State,CDualSimplexSubproblem &s,CDSSVector &rhor,int q,CRowDouble &alphaq,CRowDouble &alphaqim,CRowDouble &tau,CDualSimplexSettings &Settings);
|
|
static void RatioTest(CDualSimplexState &State,CDualSimplexSubproblem &s,CDSSVector &alphar,double delta,int p,int &q,double &alpharpiv,double &thetad,CRowInt &possibleflips,int &possibleflipscnt,CDualSimplexSettings &Settings);
|
|
static void UpdateStep(CDualSimplexState &State,CDualSimplexSubproblem &s,int p,int q,int r,double delta,double alphapiv,double thetap,double thetad,CRowDouble &alphaq,CRowDouble &alphaqim,CDSSVector &alphar,CRowDouble &tau,CRowInt &possiblealpharflips,int possiblealpharflipscnt,CDualSimplexSettings &Settings);
|
|
static bool RefactorizationRequired(CDualSimplexState &State,CDualSimplexSubproblem &s,int q,double alpharpiv,int r,double alphaqpiv);
|
|
static void CacheBoundInfo(CDualSimplexSubproblem &s,int i,int k,CDualSimplexSettings &Settings);
|
|
static void SolveSubproblemDual(CDualSimplexState &State,CDualSimplexSubproblem &s,bool IsPhase1,CDualSimplexSettings &Settings,int &Info);
|
|
static void SolveSubproblemPrimal(CDualSimplexState &State,CDualSimplexSubproblem &s,CDualSimplexSettings &Settings,int &Info);
|
|
static void InvokePhase1(CDualSimplexState &State,CDualSimplexSettings &Settings);
|
|
static void DSSOptimizeWrk(CDualSimplexState &State,CDualSimplexSettings &Settings);
|
|
static void SolveBoxOnly(CDualSimplexState &State);
|
|
static void SetZeroXYStats(CDualSimplexState &State);
|
|
static void BasisInit(int ns,int m,CDualSimplexBasis &s);
|
|
static void BasisClearStats(CDualSimplexBasis &s);
|
|
static bool BasisTryResize(CDualSimplexBasis &s,int newm,CSparseMatrix &at,CDualSimplexSettings &Settings);
|
|
static double BasisMinimumDiagonalElement(CDualSimplexBasis &s);
|
|
static void BasisExportTo(CDualSimplexBasis &s0,CDualSimplexBasis &s1);
|
|
static bool BasisTryImportFrom(CDualSimplexBasis &s0,CDualSimplexBasis &s1,CSparseMatrix &at,CDualSimplexSettings &Settings);
|
|
static void BasisFreshTrf(CDualSimplexBasis &s,CSparseMatrix &at,CDualSimplexSettings &Settings);
|
|
static double BasisFreshTRFUnsafe(CDualSimplexBasis &s,CSparseMatrix &at,CDualSimplexSettings &Settings);
|
|
static void BasisRequestWeights(CDualSimplexBasis &s,CDualSimplexSettings &Settings);
|
|
static void BasisUpdateTrf(CDualSimplexBasis &s,CSparseMatrix &at,int p,int q,CRowDouble &alphaq,CRowDouble &alphaqim,int r,CRowDouble &tau,CDualSimplexSettings &Settings);
|
|
static void BasisSolve(CDualSimplexBasis &s,CRowDouble &r,CRowDouble &x,CRowDouble &tmpx);
|
|
static void BasisSolveX(CDualSimplexBasis &s,CRowDouble &r,CRowDouble &x,CRowDouble &xim,bool needintermediate,CRowDouble &tx);
|
|
static void BasisSolveT(CDualSimplexBasis &s,CRowDouble &r,CRowDouble &x,CRowDouble &tx);
|
|
static void ComputeAnXn(CDualSimplexState &State,CDualSimplexSubproblem &subproblem,CRowDouble &x,CRowDouble &y);
|
|
static void ComputeAnTV(CDualSimplexState &State,CRowDouble &y,CRowDouble &r);
|
|
static bool HasBndL(CDualSimplexSubproblem &subproblem,int i);
|
|
static bool HasBndU(CDualSimplexSubproblem &subproblem,int i);
|
|
static bool IsFree(CDualSimplexSubproblem &subproblem,int i);
|
|
static void DowngradeState(CDualSimplexSubproblem &subproblem,int s);
|
|
static double DualFeasibilityError(CDualSimplexState &State,CDualSimplexSubproblem &s);
|
|
static bool IsDualFeasible(CDualSimplexState &State,CDualSimplexSubproblem &s,CDualSimplexSettings &Settings);
|
|
static void PivotToBWD(CRowInt &p,int m,CRowInt &bwd);
|
|
static void InverseCyclicPermutation(CRowInt &bwd,int m,int d,CRowInt &tmpi);
|
|
static void OffloadBasicComponents(CDualSimplexSubproblem &s,CDualSimplexBasis &basis,CDualSimplexSettings &Settings);
|
|
static void RecombineBasicNonBasicX(CDualSimplexSubproblem &s,CDualSimplexBasis &basis);
|
|
static void SetXYDStats(CDualSimplexState &State,CDualSimplexSubproblem &s,CDualSimplexBasis &basis,CApBuff &buffers,CRowDouble &x,CRowDouble &lagbc,CRowDouble &laglc,CRowInt &stats);
|
|
static void DVAlloc(CDSSVector &x,int n);
|
|
static void DVInit(CDSSVector &x,int n);
|
|
static void DVDenseToSparse(CDSSVector &x);
|
|
static void DVSparseToDense(CDSSVector &x);
|
|
static double SparsitYOf(CRowDouble &x,int n);
|
|
static void UpdateAvgCounter(double v,double &acc,int &cnt);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const int CRevisedDualSimplex::m_maxforcedrestarts=1;
|
|
const int CRevisedDualSimplex::m_safetrfage=5;
|
|
const int CRevisedDualSimplex::m_defaultmaxtrfage=100;
|
|
const double CRevisedDualSimplex::m_minbeta=1.0E-4;
|
|
const double CRevisedDualSimplex::m_maxudecay=0.001;
|
|
const double CRevisedDualSimplex::m_shiftlen=1.0E-12;
|
|
const double CRevisedDualSimplex::m_alphatrigger=1.0E8*CMath::m_machineepsilon;
|
|
const double CRevisedDualSimplex::m_alphatrigger2=0.001;
|
|
const int CRevisedDualSimplex::m_ssinvalid=0;
|
|
const int CRevisedDualSimplex::m_ssvalidxn=1;
|
|
const int CRevisedDualSimplex::m_ssvalid=2;
|
|
const int CRevisedDualSimplex::m_ccfixed=0;
|
|
const int CRevisedDualSimplex::m_cclower=1;
|
|
const int CRevisedDualSimplex::m_ccupper=2;
|
|
const int CRevisedDualSimplex::m_ccrange=3;
|
|
const int CRevisedDualSimplex::m_ccfree=4;
|
|
const int CRevisedDualSimplex::m_ccinfeasible=5;
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DSSSettingsInit(CDualSimplexSettings &Settings)
|
|
{
|
|
Settings.m_xtolabs=1.0E-6;
|
|
Settings.m_dtolabs=1.0E-6;
|
|
Settings.m_xtolrelabs=0.01;
|
|
Settings.m_pivottol=10*MathSqrt(CMath::m_machineepsilon);
|
|
Settings.m_perturbmag=10*Settings.m_pivottol;
|
|
Settings.m_maxtrfage=m_defaultmaxtrfage;
|
|
Settings.m_trftype=3;
|
|
Settings.m_ratiotest=1;
|
|
Settings.m_pricing=1;
|
|
Settings.m_shifting=2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes DSS structure. Previously allocated |
|
|
//| memory is reused as much as possible. |
|
|
//| Default State of the problem is zero cost vector, all variables |
|
|
//| are fixed at zero. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DSSInit(int n,CDualSimplexState &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0"))
|
|
return;
|
|
|
|
s.m_ns=n;
|
|
s.m_m=0;
|
|
s.m_rawbndl=vector<double>::Zeros(n);
|
|
s.m_rawbndu=vector<double>::Zeros(n);
|
|
SubproblemInit(n,s.m_primary);
|
|
BasisInit(n,0,s.m_basis);
|
|
s.m_repx=vector<double>::Zeros(n);
|
|
s.m_replagbc.Resize(n);
|
|
s.m_repstats.Resize(n);
|
|
s.m_repstats.Fill(1);
|
|
s.m_dotrace=false;
|
|
s.m_dodetailedtrace=false;
|
|
s.m_dotimers=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function specifies LP problem |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinLPCreate() |
|
|
//| call. |
|
|
//| BndL - lower bounds, array[N]. |
|
|
//| BndU - upper bounds, array[N]. |
|
|
//| DenseA - dense array[K, N], dense linear constraints (not |
|
|
//| supported in present version) |
|
|
//| SparseA - sparse linear constraints, sparsematrix[K, N] in |
|
|
//| CRS format |
|
|
//| AKind - type of A: 0 for dense, 1 for sparse |
|
|
//| AL, AU - lower and upper bounds, array[K] |
|
|
//| K - number of equality/inequality constraints, K >= 0. |
|
|
//| ProposedBasis - basis to import from (if BasisType = 2) |
|
|
//| BasisInitType - what to do with basis: |
|
|
//| * 0 - set new basis to all-logicals |
|
|
//| * 1 - try to reuse previous basis as much as |
|
|
//| possible |
|
|
//| * 2 - try to import basis from ProposedBasis |
|
|
//| Settings - algorithm Settings |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DSSSetProblem(CDualSimplexState &State,
|
|
CRowDouble &c,
|
|
CRowDouble &bndl,
|
|
CRowDouble &bndu,
|
|
CMatrixDouble &densea,
|
|
CSparseMatrix &sparsea,
|
|
int akind,
|
|
CRowDouble &al,
|
|
CRowDouble &au,
|
|
int k,
|
|
CDualSimplexBasis &proposedbasis,
|
|
int basisinittype,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int ns=State.m_primary.m_ns;
|
|
int oldm=State.m_primary.m_m;
|
|
int i=0;
|
|
int j=0;
|
|
int jj=0;
|
|
int offs=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
bool processed=false;
|
|
bool basisinitialized=false;
|
|
double v=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(bndl.Size()>=ns,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
if(!CAp::Assert(bndu.Size()>=ns,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
if(!CAp::Assert(c.Size()>=ns,__FUNCTION__+": Length(C)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(c,ns),__FUNCTION__+": C contains infinite or NaN elements"))
|
|
return;
|
|
if(!CAp::Assert(akind==0 || akind==1,__FUNCTION__+": incorrect AKind"))
|
|
return;
|
|
if(!CAp::Assert(basisinittype==0 || basisinittype==1 || basisinittype==2,__FUNCTION__+": incorrect BasisInitType"))
|
|
return;
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(k>0 && akind==1)
|
|
{
|
|
if(!CAp::Assert(sparsea.m_M==k,__FUNCTION__+": rows(A)<>K"))
|
|
return;
|
|
if(!CAp::Assert(sparsea.m_N==ns,__FUNCTION__+": cols(A)<>N"))
|
|
return;
|
|
}
|
|
//--- Downgrade State
|
|
DowngradeState(State.m_primary,m_ssinvalid);
|
|
//--- Reallocate storage
|
|
State.m_primary.m_bndl.Resize(ns+k);
|
|
State.m_primary.m_bndu.Resize(ns+k);
|
|
State.m_primary.m_bndt.Resize(ns+k);
|
|
State.m_primary.m_effc=vector<double>::Zeros(ns+k);
|
|
State.m_primary.m_rawc=vector<double>::Zeros(ns+k);
|
|
State.m_primary.m_xa.Resize(ns+k);
|
|
State.m_primary.m_d.Resize(ns+k);
|
|
State.m_primary.m_xb.Resize(k);
|
|
State.m_primary.m_bndlb.Resize(k);
|
|
State.m_primary.m_bndub.Resize(k);
|
|
State.m_primary.m_bndtb.Resize(k);
|
|
State.m_primary.m_bndtollb.Resize(k);
|
|
State.m_primary.m_bndtolub.Resize(k);
|
|
//--- Save original problem formulation
|
|
State.m_ns=ns;
|
|
State.m_m=k;
|
|
State.m_rawbndl=bndl;
|
|
State.m_rawbndu=bndu;
|
|
//--- Setup cost, scale and box constraints
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
State.m_primary.m_rawc.Set(i,c[i]);
|
|
State.m_primary.m_effc.Set(i,c[i]);
|
|
}
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(bndl[i]) || IsNegInf(bndl[i]),__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu[i]) || IsPosInf(bndu[i]),__FUNCTION__+": BndU contains NAN or -INF"))
|
|
return;
|
|
State.m_primary.m_bndl.Set(i,bndl[i]);
|
|
State.m_primary.m_bndu.Set(i,bndu[i]);
|
|
//--- Set bound type
|
|
if(MathIsValidNumber(bndl[i]) && MathIsValidNumber(bndu[i]))
|
|
{
|
|
if(bndl[i]>bndu[i])
|
|
State.m_primary.m_bndt.Set(i,m_ccinfeasible);
|
|
if(bndl[i]<bndu[i])
|
|
State.m_primary.m_bndt.Set(i,m_ccrange);
|
|
if(bndl[i]==bndu[i])
|
|
State.m_primary.m_bndt.Set(i,m_ccfixed);
|
|
continue;
|
|
}
|
|
if(MathIsValidNumber(bndl[i]) && !MathIsValidNumber(bndu[i]))
|
|
{
|
|
State.m_primary.m_bndt.Set(i,m_cclower);
|
|
continue;
|
|
}
|
|
if(!MathIsValidNumber(bndl[i]) && MathIsValidNumber(bndu[i]))
|
|
{
|
|
State.m_primary.m_bndt.Set(i,m_ccupper);
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(IsNegInf(bndl[i]) && IsPosInf(bndu[i]),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
State.m_primary.m_bndt.Set(i,m_ccfree);
|
|
}
|
|
//--- Quick exit if no linear constraints is present
|
|
if(k==0)
|
|
{
|
|
State.m_primary.m_m=0;
|
|
BasisInit(State.m_primary.m_ns,State.m_primary.m_m,State.m_basis);
|
|
return;
|
|
}
|
|
//--- Extend A with structural terms and transpose it:
|
|
//--- * allocate place for A^T extended with logical part.
|
|
//--- * copy with transposition
|
|
//--- * perform integrity check for array sizes
|
|
//--- * manually append new items
|
|
//--- * update DIdx/UIdx
|
|
processed=false;
|
|
State.m_primary.m_m=k;
|
|
if(akind==0)
|
|
{
|
|
CAp::Assert(false,__FUNCTION__+": does not support dense inputs yet");
|
|
return;
|
|
}
|
|
if(akind==1)
|
|
{
|
|
//--- Transpose constraints matrix, apply column and row scaling.
|
|
//--- Extend it with identity submatrix.
|
|
//--- NOTE: in order to improve stability of LU factorization we
|
|
//--- normalize rows using 2-norm, not INF-norm. Having rows
|
|
//--- normalized with 2-norm makes every element less than
|
|
//--- 1.0 in magnitude, which allows us later to move logical
|
|
//--- columns to the beginning of LU factors without loosing
|
|
//--- stability.
|
|
State.m_at.m_Vals.Resize(sparsea.m_RIdx[k]+k);
|
|
State.m_at.m_Idx.Resize(sparsea.m_RIdx[k]+k);
|
|
State.m_at.m_RIdx.Resize(ns+k+1);
|
|
State.m_at.m_DIdx.Resize(ns+k);
|
|
State.m_at.m_UIdx.Resize(ns+k);
|
|
CSparse::SparseCopyTransposeCRSBuf(sparsea,State.m_at);
|
|
State.m_rowscales=vector<double>::Zeros(k);
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
j0=State.m_at.m_RIdx[i];
|
|
j1=State.m_at.m_RIdx[i+1];
|
|
for(j=j0; j<j1; j++)
|
|
{
|
|
v=State.m_at.m_Vals[j];
|
|
jj=State.m_at.m_Idx[j];
|
|
State.m_rowscales.Set(jj,State.m_rowscales[jj]+v*v);
|
|
}
|
|
}
|
|
for(i=0; i<k; i++)
|
|
State.m_rowscales.Set(i,CApServ::Coalesce(MathSqrt(State.m_rowscales[i]),1));
|
|
State.m_tmp0=State.m_rowscales.Pow(-1.0)+0;
|
|
State.m_tmp0.Resize(k);
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
j0=State.m_at.m_RIdx[i];
|
|
j1=State.m_at.m_RIdx[i+1];
|
|
for(j=j0; j<j1; j++)
|
|
State.m_at.m_Vals.Mul(j,State.m_tmp0[State.m_at.m_Idx[j]]);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_at.m_Vals.Size()>=sparsea.m_RIdx[k]+k,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(State.m_at.m_Idx.Size()>=sparsea.m_RIdx[k]+k,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(State.m_at.m_RIdx.Size()>=ns+k+1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(State.m_at.m_DIdx.Size()>=ns+k,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(State.m_at.m_UIdx.Size()>=ns+k,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
offs=State.m_at.m_RIdx[ns];
|
|
for(i=0; i<k; i++)
|
|
{
|
|
State.m_at.m_Vals.Set(offs+i,-1.0);
|
|
State.m_at.m_Idx.Set(offs+i,i);
|
|
State.m_at.m_RIdx.Set(ns+i+1,State.m_at.m_RIdx[ns+i]+1);
|
|
State.m_at.m_NInitialized++;
|
|
}
|
|
State.m_at.m_M+=k;
|
|
CSparse::SparseInitDUIdx(State.m_at);
|
|
CSparse::SparseCopyTransposeCRSBuf(State.m_at,State.m_a);
|
|
processed=true;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(processed,__FUNCTION__+": integrity check failed (akind)"))
|
|
return;
|
|
//--- Copy AL, AU to BndL/BndT
|
|
for(i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(al[i]) || IsNegInf(al[i]),__FUNCTION__+": AL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(au[i]) || IsPosInf(au[i]),__FUNCTION__+": AU contains NAN or -INF"))
|
|
return;
|
|
State.m_primary.m_bndl.Set(ns+i,al[i]/State.m_rowscales[i]);
|
|
State.m_primary.m_bndu.Set(ns+i,au[i]/State.m_rowscales[i]);
|
|
//--- Set bound type
|
|
if(MathIsValidNumber(al[i]) && MathIsValidNumber(au[i]))
|
|
{
|
|
if(al[i]>au[i])
|
|
State.m_primary.m_bndt.Set(ns+i,m_ccinfeasible);
|
|
if(al[i]<au[i])
|
|
State.m_primary.m_bndt.Set(ns+i,m_ccrange);
|
|
if(al[i]==au[i])
|
|
State.m_primary.m_bndt.Set(ns+i,m_ccfixed);
|
|
continue;
|
|
}
|
|
if(MathIsValidNumber(al[i]) && !MathIsValidNumber(au[i]))
|
|
{
|
|
State.m_primary.m_bndt.Set(ns+i,m_cclower);
|
|
continue;
|
|
}
|
|
if(!MathIsValidNumber(al[i]) && MathIsValidNumber(au[i]))
|
|
{
|
|
State.m_primary.m_bndt.Set(ns+i,m_ccupper);
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(IsNegInf(al[i]) && IsPosInf(au[i]),__FUNCTION__+": integrity check faoled"))
|
|
return;
|
|
State.m_primary.m_bndt.Set(ns+i,m_ccfree);
|
|
}
|
|
//--- Depending on BasisInitType either start from all-logical basis
|
|
//--- or try to reuse already existing basis.
|
|
//--- NOTE: current version does not support basis shrinkage, only
|
|
//--- growing basis can be reused.
|
|
basisinitialized=false;
|
|
if(basisinittype==2)
|
|
{
|
|
//--- Import basis from one proposed by caller
|
|
//--- check
|
|
if(!CAp::Assert(proposedbasis.m_ns==State.m_primary.m_ns,__FUNCTION__+": unable to import basis,sizes do not match"))
|
|
return;
|
|
if(!CAp::Assert(proposedbasis.m_m==State.m_primary.m_m,__FUNCTION__+": unable to import basis,sizes do not match"))
|
|
return;
|
|
basisinitialized=BasisTryImportFrom(State.m_basis,proposedbasis,State.m_at,Settings);
|
|
}
|
|
if(basisinittype==1 && State.m_primary.m_m>=oldm)
|
|
{
|
|
//--- New rows were added, try to reuse previous basis
|
|
for(i=oldm; i<State.m_primary.m_m; i++)
|
|
{
|
|
State.m_primary.m_rawc.Set(ns+i,0.0);
|
|
State.m_primary.m_effc.Set(ns+i,0.0);
|
|
State.m_primary.m_xa.Set(ns+i,0.0);
|
|
State.m_primary.m_d.Set(ns+i,0.0);
|
|
}
|
|
basisinitialized=BasisTryResize(State.m_basis,State.m_primary.m_m,State.m_at,Settings);
|
|
}
|
|
if(!basisinitialized)
|
|
{
|
|
//--- Straightforward code for all-logicals basis
|
|
for(i=0; i<k; i++)
|
|
{
|
|
State.m_primary.m_rawc.Set(ns+i,0.0);
|
|
State.m_primary.m_effc.Set(ns+i,0.0);
|
|
State.m_primary.m_xa.Set(ns+i,0.0);
|
|
State.m_primary.m_d.Set(ns+i,0.0);
|
|
}
|
|
BasisInit(State.m_primary.m_ns,State.m_primary.m_m,State.m_basis);
|
|
BasisFreshTrf(State.m_basis,State.m_at,Settings);
|
|
}
|
|
State.m_replaglc.Resize(State.m_primary.m_m);
|
|
State.m_repstats.Resize(State.m_primary.m_ns+State.m_primary.m_m);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function exports basis from the primary (phase II) |
|
|
//| subproblem. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Basis - current basis exported (no factorization, only set |
|
|
//| of basis / nonbasic variables) |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DSSExportBasis(CDualSimplexState &State,
|
|
CDualSimplexBasis &basis)
|
|
{
|
|
BasisExportTo(State.m_basis,basis);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves LP problem with dual simplex solver. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - State |
|
|
//| Solution results can be found in fields of State which are |
|
|
//| explicitly declared as accessible by external code. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DSSOptimize(CDualSimplexState &State,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int nx=State.m_primary.m_ns+State.m_primary.m_m;
|
|
int i=0;
|
|
double v=0;
|
|
int cnt1=0;
|
|
int cnt2=0;
|
|
int cntfx=0;
|
|
int cntfr=0;
|
|
int cntif=0;
|
|
int ttotal=0;
|
|
//--- Trace Settings
|
|
State.m_dotrace=CAp::IsTraceEnabled("DSS");
|
|
State.m_dodetailedtrace=State.m_dotrace && CAp::IsTraceEnabled("DSS.DETAILED");
|
|
State.m_dotimers=CAp::IsTraceEnabled("TIMERS.DSS");
|
|
//--- Init report fields
|
|
State.m_repiterationscount=0;
|
|
State.m_repiterationscount1=0;
|
|
State.m_repiterationscount2=0;
|
|
State.m_repiterationscount3=0;
|
|
State.m_repterminationtype=1;
|
|
State.m_repphase1time=0;
|
|
State.m_repphase2time=0;
|
|
State.m_repphase3time=0;
|
|
State.m_repdualpricingtime=0;
|
|
State.m_repdualbtrantime=0;
|
|
State.m_repdualpivotrowtime=0;
|
|
State.m_repdualratiotesttime=0;
|
|
State.m_repdualftrantime=0;
|
|
State.m_repdualupdatesteptime=0;
|
|
State.m_repfillpivotrow=0;
|
|
State.m_repfillpivotrowcnt=0;
|
|
State.m_repfillrhor=0;
|
|
State.m_repfillrhorcnt=0;
|
|
State.m_repfilldensemu=0;
|
|
State.m_repfilldensemucnt=0;
|
|
BasisClearStats(State.m_basis);
|
|
//--- Setup timer (if needed)
|
|
if(State.m_dotimers)
|
|
ttotal=(int)(int)(GetTickCount()/10000);
|
|
//--- Trace output (if needed)
|
|
if(State.m_dotrace || State.m_dotimers)
|
|
{
|
|
CAp::Trace("\n\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
CAp::Trace("//--- DUAL SIMPLEX SOLVER STARTED //\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
CAp::Trace("> problem size:\n");
|
|
CAp::Trace(StringFormat("N = %12d (variables)\n",State.m_primary.m_ns));
|
|
CAp::Trace(StringFormat("M = %12d (constraints)\n",State.m_primary.m_m));
|
|
}
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> variable stats:\n");
|
|
cnt1=0;
|
|
cnt2=0;
|
|
cntfx=0;
|
|
cntfr=0;
|
|
cntif=0;
|
|
for(i=0; i<State.m_primary.m_ns; i++)
|
|
{
|
|
if(State.m_primary.m_bndt[i]==m_cclower || State.m_primary.m_bndt[i]==m_ccupper)
|
|
cnt1++;
|
|
if(State.m_primary.m_bndt[i]==m_ccrange)
|
|
cnt2++;
|
|
if(State.m_primary.m_bndt[i]==m_ccfixed)
|
|
cntfx++;
|
|
if(State.m_primary.m_bndt[i]==m_ccfree)
|
|
cntfr++;
|
|
if(State.m_primary.m_bndt[i]==m_ccinfeasible)
|
|
cntif++;
|
|
}
|
|
CAp::Trace(StringFormat("UBnd/LBnd = %12d\n",cnt1));
|
|
CAp::Trace(StringFormat("Range = %12d\n",cnt2));
|
|
CAp::Trace(StringFormat("Fixed = %12d\n",cntfx));
|
|
CAp::Trace(StringFormat("Free = %12d\n",cntfr));
|
|
CAp::Trace(StringFormat("Infeas = %12d\n",cntif));
|
|
CAp::Trace("> constraint stats:\n");
|
|
cnt1=0;
|
|
cnt2=0;
|
|
cntfx=0;
|
|
cntfr=0;
|
|
cntif=0;
|
|
for(i=State.m_primary.m_ns-1; i<nx; i++)
|
|
{
|
|
if(State.m_primary.m_bndt[i]==m_cclower || State.m_primary.m_bndt[i]==m_ccupper)
|
|
cnt1++;
|
|
if(State.m_primary.m_bndt[i]==m_ccrange)
|
|
cnt2++;
|
|
if(State.m_primary.m_bndt[i]==m_ccfixed)
|
|
cntfx++;
|
|
if(State.m_primary.m_bndt[i]==m_ccfree)
|
|
cntfr++;
|
|
if(State.m_primary.m_bndt[i]==m_ccinfeasible)
|
|
cntif++;
|
|
}
|
|
CAp::Trace(StringFormat("ubnd/lbnd = %12d\n",cnt1));
|
|
CAp::Trace(StringFormat("range = %12d\n",cnt2));
|
|
CAp::Trace(StringFormat("fixed = %12d\n",cntfx));
|
|
CAp::Trace(StringFormat("free = %12d\n",cntfr));
|
|
CAp::Trace(StringFormat("infeas = %12d\n",cntif));
|
|
v=0;
|
|
for(i=0; i<State.m_primary.m_ns; i++)
|
|
if(MathIsValidNumber(State.m_primary.m_bndl[i]))
|
|
v=MathMax(v,MathAbs(State.m_primary.m_bndl[i]));
|
|
CAp::Trace(StringFormat("|BndL| = %.3E\n",v));
|
|
v=0;
|
|
for(i=0; i<State.m_primary.m_ns; i++)
|
|
if(MathIsValidNumber(State.m_primary.m_bndu[i]))
|
|
v=MathMax(v,MathAbs(State.m_primary.m_bndu[i]));
|
|
CAp::Trace(StringFormat("|BndU| = %.3E\n",v));
|
|
v=0;
|
|
for(i=State.m_primary.m_ns; i<nx; i++)
|
|
if(MathIsValidNumber(State.m_primary.m_bndl[i]))
|
|
v=MathMax(v,MathAbs(State.m_primary.m_bndl[i]));
|
|
CAp::Trace(StringFormat("|AL| = %.3E\n",v));
|
|
v=0;
|
|
for(i=State.m_primary.m_ns; i<nx; i++)
|
|
if(MathIsValidNumber(State.m_primary.m_bndu[i]))
|
|
v=MathMax(v,MathAbs(State.m_primary.m_bndu[i]));
|
|
CAp::Trace(StringFormat("|AU| = %.3E\n",v));
|
|
}
|
|
//--- Call actual workhorse function
|
|
DSSOptimizeWrk(State,Settings);
|
|
//--- Print reports
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("\n");
|
|
CAp::Trace("****************************************************************************************************\n");
|
|
CAp::Trace("* PRINTING ITERATION STATISTICS *\n");
|
|
CAp::Trace("****************************************************************************************************\n");
|
|
CAp::Trace("> iteration counts:\n");
|
|
CAp::Trace(StringFormat("Phase 1 = %12d\n",State.m_repiterationscount1));
|
|
CAp::Trace(StringFormat("Phase 2 = %12d\n",State.m_repiterationscount2));
|
|
CAp::Trace(StringFormat("Phase 3 = %12d\n",State.m_repiterationscount3));
|
|
CAp::Trace("> factorization statistics:\n");
|
|
CAp::Trace(StringFormat("FactCnt = %12d (LU factorizations)\n",State.m_basis.m_statfact));
|
|
CAp::Trace(StringFormat("UpdtCnt = %12d (LU updates)\n",State.m_basis.m_statupdt));
|
|
CAp::Trace(StringFormat("RefactPeriod= %12.1f (average refactorization interval)\n",(State.m_basis.m_statfact + State.m_basis.m_statupdt) / CApServ::Coalesce(State.m_basis.m_statfact,1)));
|
|
CAp::Trace(StringFormat("LU-NZR = %12.1f (average LU nonzeros per row)\n",State.m_basis.m_statoffdiag / (CApServ::Coalesce(State.m_m,1)*CApServ::Coalesce(State.m_basis.m_statfact + State.m_basis.m_statupdt,1))));
|
|
CAp::Trace("> sparsity counters (average fill factors):\n");
|
|
if(State.m_dodetailedtrace)
|
|
{
|
|
CAp::Trace(StringFormat("RhoR = %12.4f (BTran result)\n",State.m_repfillrhor / CApServ::Coalesce(State.m_repfillrhorcnt,1)));
|
|
CAp::Trace(StringFormat("AlphaR = %12.4f (pivot row)\n",State.m_repfillpivotrow / CApServ::Coalesce(State.m_repfillpivotrowcnt,1)));
|
|
if(State.m_basis.m_trftype==3)
|
|
CAp::Trace(StringFormat("Mu = %12.4f (Forest-Tomlin factor)\n",State.m_repfilldensemu / CApServ::Coalesce(State.m_repfilldensemucnt,1)));
|
|
}
|
|
else
|
|
CAp::Trace("...skipped,need DUALSIMPLEX.DETAILED trace tag\n");
|
|
}
|
|
if(State.m_dotimers)
|
|
{
|
|
ttotal=(int)(int)(GetTickCount()/10000)-ttotal;
|
|
CAp::Trace("\n");
|
|
CAp::Trace("****************************************************************************************************\n");
|
|
CAp::Trace("* PRINTING DUAL SIMPLEX TIMERS *\n");
|
|
CAp::Trace("****************************************************************************************************\n");
|
|
CAp::Trace("> total time:\n");
|
|
CAp::Trace(StringFormat("Time = %12d ms\n",ttotal));
|
|
CAp::Trace("> time by phase:\n");
|
|
CAp::Trace(StringFormat("Phase 1 = %12d ms\n",State.m_repphase1time));
|
|
CAp::Trace(StringFormat("Phase 2 = %12d ms\n",State.m_repphase2time));
|
|
CAp::Trace(StringFormat("Phase 3 = %12d ms\n",State.m_repphase3time));
|
|
CAp::Trace("> time by step (dual phases 1 and 2):\n");
|
|
CAp::Trace(StringFormat("Pricing = %12d ms\n",State.m_repdualpricingtime));
|
|
CAp::Trace(StringFormat("BTran = %12d ms\n",State.m_repdualbtrantime));
|
|
CAp::Trace(StringFormat("PivotRow = %12d ms\n",State.m_repdualpivotrowtime));
|
|
CAp::Trace(StringFormat(__FUNCTION__+" = %12d ms\n",State.m_repdualratiotesttime));
|
|
CAp::Trace(StringFormat(__FUNCTION__+" = %12d ms\n",State.m_repdualftrantime));
|
|
CAp::Trace(StringFormat("Update = %12d ms\n",State.m_repdualupdatesteptime));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes subproblem structure. Previously |
|
|
//| allocated memory is reused as much as possible. |
|
|
//| Default State of the problem is zero cost vector, all variables |
|
|
//| are fixed at zero, linear constraint matrix is zero. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::SubproblemInit(int n,CDualSimplexSubproblem &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0"))
|
|
return;
|
|
|
|
s.m_ns=n;
|
|
s.m_m=0;
|
|
s.m_state=m_ssinvalid;
|
|
s.m_xb.Resize(0);
|
|
s.m_xa=vector<double>::Zeros(n);
|
|
s.m_d=vector<double>::Zeros(n);
|
|
s.m_rawc=vector<double>::Zeros(n);
|
|
s.m_effc=vector<double>::Zeros(n);
|
|
s.m_bndl=vector<double>::Zeros(n);
|
|
s.m_bndu=vector<double>::Zeros(n);
|
|
s.m_bndt.Resize(n);
|
|
s.m_bndt.Fill(m_ccfixed);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes phase #1 subproblem which minimizes sum|
|
|
//| of dual infeasibilities. It is required that total count of |
|
|
//| non-boxed non-fixed variables is at least M. |
|
|
//| It splits out basic components of XA[] to XB[] |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::SubproblemInitPhase1(CDualSimplexSubproblem &s0,
|
|
CDualSimplexBasis &basis,
|
|
CDualSimplexSubproblem &s1)
|
|
{
|
|
//--- copy
|
|
s1=s0;
|
|
|
|
for(int i=0; i<s1.m_ns+s1.m_m; i++)
|
|
{
|
|
if(s1.m_bndt[i]==m_cclower)
|
|
{
|
|
s1.m_bndt.Set(i,m_ccrange);
|
|
s1.m_bndl.Set(i,0);
|
|
s1.m_bndu.Set(i,1);
|
|
s1.m_xa.Set(i,0);
|
|
continue;
|
|
}
|
|
if(s1.m_bndt[i]==m_ccupper)
|
|
{
|
|
s1.m_bndt.Set(i,m_ccrange);
|
|
s1.m_bndl.Set(i,-1);
|
|
s1.m_bndu.Set(i,0);
|
|
s1.m_xa.Set(i,0);
|
|
continue;
|
|
}
|
|
if(s1.m_bndt[i]==m_ccfree)
|
|
{
|
|
s1.m_bndt.Set(i,m_ccrange);
|
|
s1.m_bndl.Set(i,-1);
|
|
s1.m_bndu.Set(i,1);
|
|
if(s1.m_effc[i]>=0.0)
|
|
s1.m_xa.Set(i,-1);
|
|
else
|
|
s1.m_xa.Set(i,1);
|
|
continue;
|
|
}
|
|
s1.m_bndt.Set(i,m_ccfixed);
|
|
s1.m_bndl.Set(i,0);
|
|
s1.m_bndu.Set(i,0);
|
|
s1.m_xa.Set(i,0);
|
|
}
|
|
s1.m_state=m_ssvalidxn;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes phase #3 subproblem which applies |
|
|
//| primal simplex method to the result of the phase #2. |
|
|
//| It also performs modification of the subproblem in order to |
|
|
//| ensure that initial point is primal feasible. |
|
|
//| NOTE: this function expects that all components (basic and |
|
|
//| nonbasic ones) are stored in XA[] |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::SubproblemInitPhase3(CDualSimplexSubproblem &s0,
|
|
CDualSimplexSubproblem &s1)
|
|
{
|
|
s1=s0;
|
|
s1.m_state=m_ssvalidxn;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function infers nonbasic variables of X using sign of |
|
|
//| effective C[]. |
|
|
//| Only non - basic components of XN are changed; everything else |
|
|
//| is NOT updated. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::SubproblemInferInitialXN(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int ii=0;
|
|
int bndt=0;
|
|
|
|
for(ii=0; ii<s.m_ns; ii++)
|
|
{
|
|
i=State.m_basis.m_nidx[ii];
|
|
bndt=s.m_bndt[i];
|
|
if(bndt==m_ccfixed || bndt==m_ccrange)
|
|
{
|
|
if(s.m_effc[i]>=0)
|
|
s.m_xa.Set(i,s.m_bndl[i]);
|
|
else
|
|
s.m_xa.Set(i,s.m_bndu[i]);
|
|
continue;
|
|
}
|
|
if(bndt==m_cclower)
|
|
{
|
|
s.m_xa.Set(i,s.m_bndl[i]);
|
|
continue;
|
|
}
|
|
if(bndt==m_ccupper)
|
|
{
|
|
s.m_xa.Set(i,s.m_bndu[i]);
|
|
continue;
|
|
}
|
|
if(bndt==m_ccfree)
|
|
{
|
|
s.m_xa.Set(i,0.0);
|
|
continue;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed (infeasible constraint)");
|
|
return;
|
|
}
|
|
s.m_state=m_ssvalidxn;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function infers basic variables of X using values of |
|
|
//| non-basic vars and updates reduced cost vector D and target |
|
|
//| function Z. Sets State age to zero. |
|
|
//| D[] is allocated during computations. |
|
|
//| Temporary vectors Tmp0 and Tmp1 are used(reallocated as needed). |
|
|
//| NOTE: this function expects that both nonbasic and basic |
|
|
//| components are stored in XA[]. XB[] array is not referenced|
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::SubproblemHandleXNUpdate(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s)
|
|
{
|
|
//--- create variables
|
|
int nn=s.m_ns;
|
|
int m=s.m_m;
|
|
int i=0;
|
|
int j=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_state>=m_ssvalidxn,__FUNCTION__+": integrity check failed (XN is not valid)"))
|
|
return;
|
|
//--- Compute nonbasic components
|
|
ComputeAnXn(State,s,s.m_xa,State.m_tmp0);
|
|
BasisSolve(State.m_basis,State.m_tmp0,State.m_tmp1,State.m_tmp2);
|
|
for(i=0; i<m; i++)
|
|
s.m_xa.Set(State.m_basis.m_idx[i],-State.m_tmp1[i]);
|
|
//--- Compute D
|
|
for(i=0; i<m; i++)
|
|
State.m_tmp0.Set(i,s.m_effc[State.m_basis.m_idx[i]]);
|
|
BasisSolveT(State.m_basis,State.m_tmp0,State.m_tmp1,State.m_tmp2);
|
|
ComputeAnTV(State,State.m_tmp1,s.m_d);
|
|
for(i=0; i<nn; i++)
|
|
{
|
|
j=State.m_basis.m_nidx[i];
|
|
s.m_d.Set(j,s.m_effc[j]-s.m_d[j]);
|
|
}
|
|
//--- Update State validity/age
|
|
s.m_state=m_ssvalid;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs initial dual feasibility correction on the|
|
|
//| subproblem. It assumes that problem State is at least ssValidXN. |
|
|
//| After call to this function the problem State is set to ssValid. |
|
|
//| This function returns dual feasibility error after dual |
|
|
//| feasibility correction. |
|
|
//| NOTE: this function expects that both nonbasic and basic |
|
|
//| components are stored in XA[]. XB[] array is not referenced|
|
|
//+------------------------------------------------------------------+
|
|
double CRevisedDualSimplex::InitialDualFeasibilityCorrection(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int nn=s.m_ns;
|
|
int m=s.m_m;
|
|
double result=0;
|
|
CRowDouble dummy;
|
|
int ii=0;
|
|
int i=0;
|
|
int j=0;
|
|
bool flipped=false;
|
|
double v=0;
|
|
double dj=0;
|
|
double xj=0;
|
|
int bndt=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_state>=m_ssvalidxn,__FUNCTION__+": XN is invalid"))
|
|
return(result);
|
|
//--- Prepare
|
|
State.m_dfctmp0.Resize(m);
|
|
State.m_dfctmp1.Resize(m);
|
|
//--- Recompute D[] using fresh factorization
|
|
BasisFreshTrf(State.m_basis,State.m_at,Settings);
|
|
for(i=0; i<m; i++)
|
|
State.m_dfctmp0.Set(i,s.m_effc[State.m_basis.m_idx[i]]);
|
|
BasisSolveT(State.m_basis,State.m_dfctmp0,State.m_dfctmp1,State.m_dfctmp2);
|
|
ComputeAnTV(State,State.m_dfctmp1,s.m_d);
|
|
for(i=0; i<nn; i++)
|
|
{
|
|
j=State.m_basis.m_nidx[i];
|
|
s.m_d.Set(j,s.m_effc[j]-s.m_d[j]);
|
|
}
|
|
//--- Perform flips for dual-infeasible boxed variables
|
|
result=0;
|
|
flipped=false;
|
|
for(ii=0; ii<nn; ii++)
|
|
{
|
|
j=State.m_basis.m_nidx[ii];
|
|
bndt=s.m_bndt[j];
|
|
//--- Boxed variables, perform DFC
|
|
if(bndt==m_ccrange)
|
|
{
|
|
dj=s.m_d[j];
|
|
xj=s.m_xa[j];
|
|
if(xj==s.m_bndl[j] && dj<0)
|
|
{
|
|
s.m_xa.Set(j,s.m_bndu[j]);
|
|
flipped=true;
|
|
continue;
|
|
}
|
|
if(xj==s.m_bndu[j] && dj>0)
|
|
{
|
|
s.m_xa.Set(j,s.m_bndl[j]);
|
|
flipped=true;
|
|
continue;
|
|
}
|
|
continue;
|
|
}
|
|
//--- Non-boxed variables, compute dual feasibility error
|
|
if(bndt==m_ccfixed)
|
|
continue;
|
|
if(bndt==m_cclower)
|
|
{
|
|
v=-s.m_d[j];
|
|
if(v>result)
|
|
result=v;
|
|
continue;
|
|
}
|
|
if(bndt==m_ccupper)
|
|
{
|
|
v=s.m_d[j];
|
|
if(v>result)
|
|
result=v;
|
|
continue;
|
|
}
|
|
if(bndt==m_ccfree)
|
|
{
|
|
result=MathMax(result,MathAbs(s.m_d[j]));
|
|
continue;
|
|
}
|
|
}
|
|
//--- Recompute basic components of X[]
|
|
if(flipped || s.m_state<m_ssvalid)
|
|
{
|
|
ComputeAnXn(State,s,s.m_xa,State.m_dfctmp0);
|
|
BasisSolve(State.m_basis,State.m_dfctmp0,State.m_dfctmp1,State.m_dfctmp2);
|
|
for(i=0; i<m; i++)
|
|
s.m_xa.Set(State.m_basis.m_idx[i],-State.m_dfctmp1[i]);
|
|
}
|
|
//--- Update State validity/age
|
|
s.m_state=m_ssvalid;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs Shifting using current algorithm as |
|
|
//| specified by Settings.m_shifting. |
|
|
//| It accepts following parameters: |
|
|
//| * AlphaR - pivot row |
|
|
//| * Delta - delta from pricing step |
|
|
//| * Q - variable selected by ratio test |
|
|
//| * AlphaRPiv - pivot element, Q-th element of AlphaR (because |
|
|
//| alphaR is stored in compressed format, we can't |
|
|
//| extract it easily) |
|
|
//| * ThetaD - dual step length |
|
|
//| If no shifts are necessary, it silently returns. If shifts are |
|
|
//| necessary, it modifies ThetaD, S.D, S.EffC according to Shifting |
|
|
//| algorithm. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::Shifting(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
CDSSVector &alphar,
|
|
double delta,
|
|
int q,
|
|
double alpharpiv,
|
|
double &thetad,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int dir=0;
|
|
double sft=0;
|
|
int ii=0;
|
|
int j=0;
|
|
int bndt=0;
|
|
|
|
if(q<0)
|
|
return;
|
|
|
|
switch(Settings.m_shifting)
|
|
{
|
|
//--- No shifts
|
|
case 0:
|
|
break;
|
|
//--- EXPAND with ThetaD=0
|
|
case 1:
|
|
dir=(int)MathSign(delta);
|
|
if((thetad*dir)>=0.0)
|
|
break;
|
|
s.m_effc.Add(q,- s.m_d[q]);
|
|
s.m_d.Set(q,0);
|
|
thetad=0;
|
|
break;
|
|
//--- EXPAND with ThetaD=ShiftLen
|
|
case 2:
|
|
dir=(int)MathSign(delta);
|
|
if((thetad*dir)>0.0)
|
|
break;
|
|
//--- Ensure that non-zero step is performed
|
|
thetad=dir*m_shiftlen;
|
|
//--- Shift Q-th coefficient
|
|
sft=thetad*(dir*alpharpiv)-s.m_d[q];
|
|
s.m_effc.Add(q,sft);
|
|
s.m_d.Add(q,sft);
|
|
//--- Shift other coefficients
|
|
for(ii=0; ii<alphar.m_k; ii++)
|
|
{
|
|
j=alphar.m_idx[ii];
|
|
bndt=s.m_bndt[j];
|
|
if((j==q || bndt==m_ccfixed) || bndt==m_ccfree)
|
|
continue;
|
|
sft=thetad*(dir*alphar.m_vals[ii])-s.m_d[j];
|
|
//--- Handle variables at lower bound
|
|
if(bndt==m_cclower || (bndt==m_ccrange && s.m_xa[j]==s.m_bndl[j]))
|
|
{
|
|
sft-=Settings.m_dtolabs;
|
|
if(sft>0)
|
|
{
|
|
s.m_effc.Add(j,sft);
|
|
s.m_d.Add(j,sft);
|
|
}
|
|
continue;
|
|
}
|
|
if(bndt==m_ccupper || (bndt==m_ccrange && s.m_xa[j]==s.m_bndu[j]))
|
|
{
|
|
sft+=Settings.m_dtolabs;
|
|
if(sft<0)
|
|
{
|
|
s.m_effc.Add(j,sft);
|
|
s.m_d.Add(j,sft);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
//--- Done
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": unexpected Shifting type");
|
|
break;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs pricing step |
|
|
//| Additional parameters: |
|
|
//| * Phase1Pricing - if True, then special Phase #1 restriction|
|
|
//| is applied to leaving variables: only |
|
|
//| those are eligible which will move to zero|
|
|
//| bound after basis change. |
|
|
//| This trick allows to accelerate and stabilize phase #1. See |
|
|
//| Robert Fourer, 'Notes on the dual simplex method', draft report, |
|
|
//| 1994, for more Info. |
|
|
//| Returns: |
|
|
//| * leaving variable index P |
|
|
//| * its index R in the basis, in [0, M) range |
|
|
//| * Delta - difference between variable value and corresponding |
|
|
//| bound |
|
|
//| NOTE: this function expects that basic components are stored in |
|
|
//| XB[]; corresponding entries of XA[] are ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::PricingStep(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
bool phase1pricing,
|
|
int &p,
|
|
int &r,
|
|
double &delta,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
int i=0;
|
|
int bi=0;
|
|
double v=0;
|
|
double vtarget=0;
|
|
double xbi=0;
|
|
double bndl=0;
|
|
double bndu=0;
|
|
double vdiff=0;
|
|
double vtest=0;
|
|
double invw=0;
|
|
int bndt=0;
|
|
bool hasboth=false;
|
|
bool hasl=false;
|
|
bool hasu=false;
|
|
int t0=0;
|
|
|
|
p=0;
|
|
r=0;
|
|
delta=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(s.m_state==m_ssvalid,__FUNCTION__+": invalid X"))
|
|
return;
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0"))
|
|
return;
|
|
//--- Timers
|
|
t0=0;
|
|
if(State.m_dotimers)
|
|
t0=(int)(GetTickCount()/10000);
|
|
//--- Pricing
|
|
if(Settings.m_pricing==0)
|
|
{
|
|
//--- "Most infeasible" pricing
|
|
p=-1;
|
|
r=-1;
|
|
delta=0;
|
|
vtarget=0;
|
|
for(i=0; i<m; i++)
|
|
{
|
|
bndt=s.m_bndtb[i];
|
|
hasboth=bndt==3 || bndt==0;
|
|
hasl=hasboth || bndt==1;
|
|
hasu=hasboth || bndt==2;
|
|
xbi=s.m_xb[i];
|
|
if(hasl)
|
|
{
|
|
bndl=s.m_bndlb[i];
|
|
vdiff=xbi-bndl;
|
|
v=-vdiff;
|
|
if(v>s.m_bndtollb[i] && v>vtarget)
|
|
{
|
|
//--- Special phase 1 pricing: do not choose variables which move to non-zero bound
|
|
if(phase1pricing && !(bndl==0.0))
|
|
continue;
|
|
//--- Proceed as usual
|
|
p=State.m_basis.m_idx[i];
|
|
r=i;
|
|
delta=vdiff;
|
|
vtarget=v;
|
|
continue;
|
|
}
|
|
}
|
|
if(hasu)
|
|
{
|
|
bndu=s.m_bndub[i];
|
|
vdiff=xbi-bndu;
|
|
v=vdiff;
|
|
if(v>s.m_bndtolub[i] && v>vtarget)
|
|
{
|
|
//--- Special phase 1 pricing: do not choose variables which move to non-zero bound
|
|
if(phase1pricing && !(bndu==0.0))
|
|
continue;
|
|
//--- Proceed as usual
|
|
p=State.m_basis.m_idx[i];
|
|
r=i;
|
|
delta=vdiff;
|
|
vtarget=v;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
//--- Trace/profile
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> pricing: most infeasible variable removed\n");
|
|
CAp::Trace(StringFormat("P = %12d (R=%d)\n",p,r));
|
|
CAp::Trace(StringFormat("Delta = %12.3E\n",delta));
|
|
}
|
|
if(State.m_dotimers)
|
|
State.m_repdualpricingtime+=(int)(GetTickCount()/10000)-t0;
|
|
//--- Done
|
|
return;
|
|
}
|
|
if(Settings.m_pricing==-1 || Settings.m_pricing==1)
|
|
{
|
|
//--- Dual steepest edge pricing
|
|
BasisRequestWeights(State.m_basis,Settings);
|
|
p=-1;
|
|
r=-1;
|
|
delta=0;
|
|
vtarget=0;
|
|
for(i=0; i<m; i++)
|
|
{
|
|
bi=State.m_basis.m_idx[i];
|
|
bndt=s.m_bndtb[i];
|
|
hasboth=bndt==3 || bndt==0;
|
|
hasl=hasboth || bndt==1;
|
|
hasu=hasboth || bndt==2;
|
|
xbi=s.m_xb[i];
|
|
invw=1/State.m_basis.m_dseweights[i];
|
|
if(hasl)
|
|
{
|
|
bndl=s.m_bndlb[i];
|
|
vdiff=xbi-bndl;
|
|
vtest=vdiff*vdiff*invw;
|
|
if(vdiff<-s.m_bndtollb[i] && (p<0 || vtest>vtarget))
|
|
{
|
|
//--- Special phase 1 pricing: do not choose variables which move to non-zero bound
|
|
if(phase1pricing && !(bndl==0.0))
|
|
continue;
|
|
//--- Proceed as usual
|
|
p=bi;
|
|
r=i;
|
|
delta=vdiff;
|
|
vtarget=vtest;
|
|
continue;
|
|
}
|
|
}
|
|
if(hasu)
|
|
{
|
|
bndu=s.m_bndub[i];
|
|
vdiff=xbi-bndu;
|
|
vtest=vdiff*vdiff*invw;
|
|
if(vdiff>s.m_bndtolub[i] && (p<0 || vtest>vtarget))
|
|
{
|
|
//--- Special phase 1 pricing: do not choose variables which move to non-zero bound
|
|
if(phase1pricing && !(bndu==0.0))
|
|
continue;
|
|
//--- Proceed as usual
|
|
p=bi;
|
|
r=i;
|
|
delta=vdiff;
|
|
vtarget=vtest;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
//--- Trace/profile
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> dual steepest edge pricing: leaving variable found\n");
|
|
CAp::Trace(StringFormat("P = %12d (variable index)\n",p));
|
|
CAp::Trace(StringFormat("R = %12d (variable index in basis)\n",r));
|
|
CAp::Trace(StringFormat("Delta = %12.3E (primal infeasibility removed)\n",delta));
|
|
}
|
|
if(State.m_dotimers)
|
|
State.m_repdualpricingtime+=(int)(GetTickCount()/10000)-t0;
|
|
//--- Done
|
|
return;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__+": unknown pricing type");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs BTran step |
|
|
//| Accepts: |
|
|
//| * R, index of the leaving variable in the basis, in [0, M) |
|
|
//| range |
|
|
//| Returns: |
|
|
//| * RhoR, array[M], BTran result |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::BTranStep(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
int r,
|
|
CDSSVector &rhor,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
int t0=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0"))
|
|
return;
|
|
//--- Timers
|
|
if(State.m_dotimers)
|
|
t0=(int)(GetTickCount()/10000);
|
|
//--- BTran
|
|
State.m_btrantmp0=vector<double>::Zeros(m);
|
|
State.m_btrantmp1.Resize(m);
|
|
State.m_btrantmp2.Resize(m);
|
|
State.m_btrantmp0.Set(r,1);
|
|
DVAlloc(rhor,m);
|
|
BasisSolveT(State.m_basis,State.m_btrantmp0,rhor.m_dense,State.m_btrantmp1);
|
|
DVDenseToSparse(rhor);
|
|
//--- Timers
|
|
if(State.m_dotimers)
|
|
State.m_repdualbtrantime+=((int)(GetTickCount()/10000)-t0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs PivotRow step |
|
|
//| Accepts: |
|
|
//| * RhoR, BTRan result |
|
|
//| Returns: |
|
|
//| * AlphaR, array[N + M], pivot row |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::PivotRowStep(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
CDSSVector &rhor,
|
|
CDSSVector &alphar,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
int ns=s.m_ns;
|
|
int nx=s.m_ns+s.m_m;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int jj=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int alphark=0;
|
|
double v=0;
|
|
int t0=0;
|
|
double avgcolwise=0;
|
|
double avgrowwise=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0"))
|
|
return;
|
|
//--- Timers
|
|
if(State.m_dotimers)
|
|
t0=(int)(GetTickCount()/10000);
|
|
//--- Determine operation counts for columnwise and rowwise approaches
|
|
avgrowwise=rhor.m_k*((double)State.m_at.m_RIdx[nx]/(double)m);
|
|
avgcolwise=ns*((double)State.m_at.m_RIdx[nx]/(double)nx);
|
|
//--- Pivot row
|
|
if(avgrowwise<avgcolwise)
|
|
{
|
|
//--- Use rowwise algorithm
|
|
DVInit(alphar,nx);
|
|
for(i=0; i<rhor.m_k; i++)
|
|
{
|
|
k=rhor.m_idx[i];
|
|
v=rhor.m_vals[i];
|
|
j0=State.m_a.m_RIdx[k];
|
|
j1=State.m_a.m_RIdx[k+1];
|
|
for(j=j0; j<j1; j++)
|
|
{
|
|
jj=State.m_a.m_Idx[j];
|
|
alphar.m_dense.Add(jj,v*State.m_a.m_Vals[j]);
|
|
}
|
|
}
|
|
alphark=0;
|
|
for(i=0; i<nx; i++)
|
|
{
|
|
if(!State.m_basis.m_isbasic[i])
|
|
{
|
|
//--- Fetch nonbasic nonzeros to sparse part
|
|
v=alphar.m_dense[i];
|
|
if(v!=0.0)
|
|
{
|
|
alphar.m_idx.Set(alphark,i);
|
|
alphar.m_vals.Set(alphark,v);
|
|
alphark++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Enforce condition that basic elements of AlphaR are exactly zero
|
|
alphar.m_dense.Set(i,0);
|
|
}
|
|
}
|
|
alphar.m_k=alphark;
|
|
}
|
|
else
|
|
{
|
|
//--- Use colwise algorithm
|
|
DVAlloc(alphar,nx);
|
|
alphark=0;
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
k=State.m_basis.m_nidx[i];
|
|
j0=State.m_at.m_RIdx[k];
|
|
j1=State.m_at.m_RIdx[k+1];
|
|
v=0;
|
|
for(j=j0; j<j1; j++)
|
|
{
|
|
v+=State.m_at.m_Vals[j]*rhor.m_dense[State.m_at.m_Idx[j]];
|
|
}
|
|
if(v!=0.0)
|
|
{
|
|
alphar.m_idx.Set(alphark,k);
|
|
alphar.m_vals.Set(alphark,v);
|
|
alphark++;
|
|
}
|
|
}
|
|
alphar.m_k=alphark;
|
|
DVSparseToDense(alphar);
|
|
}
|
|
//--- Timers and tracing
|
|
if(State.m_dodetailedtrace)
|
|
{
|
|
UpdateAvgCounter(rhor.m_k/CApServ::Coalesce(rhor.m_n,1),State.m_repfillrhor,State.m_repfillrhorcnt);
|
|
UpdateAvgCounter(alphar.m_k/CApServ::Coalesce(alphar.m_n,1),State.m_repfillpivotrow,State.m_repfillpivotrowcnt);
|
|
}
|
|
if(State.m_dotimers)
|
|
State.m_repdualpivotrowtime+=((int)(GetTickCount()/10000)-t0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs FTran step |
|
|
//| Accepts: |
|
|
//| * RhoR, array[M] |
|
|
//| * Q, index of the entering variable, in [0, NX) range |
|
|
//| Returns: |
|
|
//| * AlphaQ, array[M], FTran result |
|
|
//| * AlphaQim, array[M], intermediate FTran result used by Forest-|
|
|
//| Tomlin update |
|
|
//| * Tau, array[M], used to compute DSE temporaries |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::FTranStep(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
CDSSVector &rhor,
|
|
int q,
|
|
CRowDouble &alphaq,
|
|
CRowDouble &alphaqim,
|
|
CRowDouble &tau,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int t0=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0"))
|
|
return;
|
|
//--- Timers
|
|
if(State.m_dotimers)
|
|
t0=(int)(GetTickCount()/10000);
|
|
//--- FTran
|
|
State.m_ftrantmp0=vector<double>::Zeros(m);
|
|
j0=State.m_at.m_RIdx[q];
|
|
j1=State.m_at.m_RIdx[q+1];
|
|
for(j=j0; j<j1; j++)
|
|
State.m_ftrantmp0.Set(State.m_at.m_Idx[j],State.m_at.m_Vals[j]);
|
|
BasisSolveX(State.m_basis,State.m_ftrantmp0,alphaq,alphaqim,true,State.m_ftrantmp1);
|
|
//--- check
|
|
if(!CAp::Assert(Settings.m_pricing==-1 || Settings.m_pricing==0 || Settings.m_pricing==1,__FUNCTION__+": unexpected Settings.Pricing"))
|
|
return;
|
|
if(Settings.m_pricing==1)
|
|
BasisSolve(State.m_basis,rhor.m_dense,tau,State.m_ftrantmp1);
|
|
//--- Timers
|
|
if(State.m_dotimers)
|
|
State.m_repdualftrantime+=((int)(GetTickCount()/10000)-t0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs ratio test, either simple one or BFRT. |
|
|
//| It accepts following parameters: |
|
|
//| * AlphaR - pivot row |
|
|
//| * Delta - delta from pricing step |
|
|
//| * P - index of leaving variable from pricing step |
|
|
//| It returns following results: |
|
|
//| * Q - non-negative value for success, negative for |
|
|
//| primal infeasible problem |
|
|
//| * AlphaRPiv - AlphaR[Q] (due to AlphaR being stored in sparse |
|
|
//| format this value is difficult to extract by |
|
|
//| index Q). |
|
|
//| * ThetaD - dual step length |
|
|
//| * PossibleFlips[PossibleFlipsCnt] - for possible flip indexes |
|
|
//| (for BFRT this set coincides with actual flips, |
|
|
//| but stabilizing BFRT is a bit more complex - |
|
|
//| some variables in PossibleFlips[] may need |
|
|
//| flipping and some not) |
|
|
//| Internally it uses following fields of State for temporaries: |
|
|
//| * EligibleAlphaR |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::RatioTest(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
CDSSVector &alphar,
|
|
double delta,
|
|
int p,
|
|
int &q,
|
|
double &alpharpiv,
|
|
double &thetad,
|
|
CRowInt &possibleflips,
|
|
int &possibleflipscnt,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int nx=s.m_ns+s.m_m;
|
|
int j=0;
|
|
int nj=0;
|
|
int dir=0;
|
|
double vx=0;
|
|
double vp=0;
|
|
int ej=0;
|
|
double alpharej=0;
|
|
double vtarget=0;
|
|
double vtest=0;
|
|
int eligiblecnt=0;
|
|
int originaleligiblecnt=0;
|
|
int bndt=0;
|
|
double alphawaver=0;
|
|
double adelta=0;
|
|
int idx=0;
|
|
double vtheta=0;
|
|
int t0=0;
|
|
|
|
q=0;
|
|
alpharpiv=0;
|
|
thetad=0;
|
|
//--- check
|
|
if(!CAp::Assert(delta!=0.0,__FUNCTION__+": zero delta"))
|
|
return;
|
|
if(!CAp::Assert(s.m_state==m_ssvalid,__FUNCTION__+": invalid X"))
|
|
return;
|
|
//--- Timers
|
|
if(State.m_dotimers)
|
|
t0=(int)(GetTickCount()/10000);
|
|
//--- Clear output
|
|
q=-1;
|
|
alpharpiv=0;
|
|
thetad=0;
|
|
possibleflipscnt=0;
|
|
//--- Prepare temporaries
|
|
//--- Scaled tolerances are used to test AlphaWaveR for positivity/negativity,
|
|
//--- scale of I-th tolerance is calculated as ratio of ColScale[I] and ColScale[P].
|
|
dir=(int)MathSign(delta);
|
|
possibleflips.Resize(nx);
|
|
//--- Prepare set of eligible variables
|
|
//--- NOTE: free variables are immediately chosen at this stage
|
|
State.m_eligiblealphar.Resize(alphar.m_k);
|
|
eligiblecnt=0;
|
|
for(j=0; j<alphar.m_k; j++)
|
|
{
|
|
nj=alphar.m_idx[j];
|
|
bndt=s.m_bndt[nj];
|
|
//--- Handle fixed and free variables: fixed ones are not eligible,
|
|
//--- free non-basic variables are always and immediately eligible
|
|
if(bndt==m_ccfixed)
|
|
continue;
|
|
if(bndt==m_ccfree)
|
|
{
|
|
q=nj;
|
|
thetad=0;
|
|
alpharpiv=alphar.m_vals[j];
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> ratio test: quick exit,found free nonbasic variable\n");
|
|
CAp::Trace(StringFormat("Q = %12d (variable selected)\n",q));
|
|
CAp::Trace(StringFormat("ThetaD = %12.3E (dual step length)\n",thetad));
|
|
}
|
|
if(State.m_dotimers)
|
|
State.m_repdualratiotesttime+=((int)(GetTickCount()/10000)-t0);
|
|
return;
|
|
}
|
|
//--- Handle lower/upper/range constraints
|
|
vx=s.m_xa[nj];
|
|
vp=Settings.m_pivottol;
|
|
alphawaver=dir*alphar.m_vals[j];
|
|
if(bndt==m_cclower || (bndt==m_ccrange && vx==s.m_bndl[nj]))
|
|
{
|
|
if(alphawaver>vp)
|
|
{
|
|
State.m_eligiblealphar.Set(eligiblecnt,j);
|
|
eligiblecnt++;
|
|
continue;
|
|
}
|
|
}
|
|
if(bndt==m_ccupper || (bndt==m_ccrange && vx==s.m_bndu[nj]))
|
|
{
|
|
if(alphawaver<-vp)
|
|
{
|
|
State.m_eligiblealphar.Set(eligiblecnt,j);
|
|
eligiblecnt++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
originaleligiblecnt=eligiblecnt;
|
|
//--- Simple ratio test.
|
|
if(Settings.m_ratiotest==0)
|
|
{
|
|
//--- Ratio test
|
|
vtarget=0;
|
|
for(j=0; j<eligiblecnt; j++)
|
|
{
|
|
ej=State.m_eligiblealphar[j];
|
|
nj=alphar.m_idx[ej];
|
|
alpharej=alphar.m_vals[ej];
|
|
//--- More general case
|
|
alphawaver=dir*alpharej;
|
|
vtest=s.m_d[nj]/alphawaver;
|
|
if(q<0 || vtest<vtarget)
|
|
{
|
|
q=nj;
|
|
alpharpiv=alpharej;
|
|
vtarget=vtest;
|
|
thetad=s.m_d[nj]/alpharej;
|
|
}
|
|
}
|
|
Shifting(State,s,alphar,delta,q,alpharpiv,thetad,Settings);
|
|
//--- Trace
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> dual ratio test:\n");
|
|
CAp::Trace(StringFormat("|E| = %12d (eligible set size)\n",originaleligiblecnt));
|
|
CAp::Trace(StringFormat("Q = %12d (variable selected)\n",q));
|
|
CAp::Trace(StringFormat("ThetaD = %12.3E (dual step length)\n",thetad));
|
|
}
|
|
if(State.m_dotimers)
|
|
State.m_repdualratiotesttime+=((int)(GetTickCount()/10000)-t0);
|
|
//--- Done
|
|
return;
|
|
}
|
|
//--- Bounds flipping ratio test
|
|
if(Settings.m_ratiotest==1)
|
|
{
|
|
adelta=MathAbs(delta);
|
|
//--- Quick exit
|
|
if(eligiblecnt==0)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> ratio test: quick exit,no eligible variables\n");
|
|
return;
|
|
}
|
|
//--- BFRT
|
|
while(eligiblecnt>0)
|
|
{
|
|
//--- Find Q satisfying BFRT criteria
|
|
idx=-1;
|
|
q=-1;
|
|
alpharpiv=0;
|
|
vtarget=0;
|
|
for(j=0; j<eligiblecnt; j++)
|
|
{
|
|
ej=State.m_eligiblealphar[j];
|
|
nj=alphar.m_idx[ej];
|
|
alpharej=alphar.m_vals[ej];
|
|
vtheta=s.m_d[nj]/alpharej;
|
|
vtest=dir*vtheta;
|
|
if(q<0 || vtest<vtarget)
|
|
{
|
|
q=nj;
|
|
alpharpiv=alpharej;
|
|
vtarget=vtest;
|
|
thetad=vtheta;
|
|
idx=j;
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(q>=0,__FUNCTION__+": integrity check failed (BFRT)"))
|
|
return;
|
|
//--- BFRT mini-iterations will be terminated upon discovery
|
|
//--- of non-boxed variable or upon exhausting of eligible set.
|
|
if(s.m_bndt[q]!=m_ccrange)
|
|
break;
|
|
if(eligiblecnt==1)
|
|
break;
|
|
//--- Update and test ADelta. Break BFRT mini-iterations once
|
|
//--- we get negative slope.
|
|
adelta-=(s.m_bndu[q]-s.m_bndl[q])*MathAbs(alpharpiv);
|
|
if(adelta<=0.0)
|
|
break;
|
|
//--- Update eligible set, record flip
|
|
possibleflips.Set(possibleflipscnt,State.m_eligiblealphar[idx]);
|
|
possibleflipscnt++;
|
|
State.m_eligiblealphar.Set(idx,State.m_eligiblealphar[eligiblecnt-1]);
|
|
eligiblecnt--;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(q>=0,__FUNCTION__+": unexpected failure"))
|
|
return;
|
|
thetad=s.m_d[q]/alpharpiv;
|
|
Shifting(State,s,alphar,delta,q,alpharpiv,thetad,Settings);
|
|
//--- Trace
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> dual bounds flipping ratio test:\n");
|
|
CAp::Trace(StringFormat("|E| = %12d (eligible set size)\n",originaleligiblecnt));
|
|
CAp::Trace(StringFormat("Q = %12d (variable selected)\n",q));
|
|
CAp::Trace(StringFormat("ThetaD = %12.3E (dual step length)\n",thetad));
|
|
CAp::Trace(StringFormat("Flips = %12d (possible bound flips)\n",State.m_possibleflipscnt));
|
|
}
|
|
if(State.m_dotimers)
|
|
State.m_repdualratiotesttime+=((int)(GetTickCount()/10000)-t0);
|
|
//--- Done
|
|
return;
|
|
}
|
|
//--- Unknown test type
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed,unknown test type");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs update of XB, XN, D and Z during final |
|
|
//| step of revised dual simplex method. |
|
|
//| It also updates basis cache of the subproblem (s.bcache field). |
|
|
//| Depending on Settings.m_ratiotest, following operations are |
|
|
//| performed: |
|
|
//| * Settings.m_ratiotest = 0 -> simple update is performed |
|
|
//| * Settings.m_ratiotest = 1 -> bounds flipping ratio test update|
|
|
//| is performed |
|
|
//| * Settings.m_ratiotest = 2 -> stabilizing bounds flipping ratio|
|
|
//| test update is performed |
|
|
//| It accepts following parameters: |
|
|
//| * P - index of leaving variable from pricing step |
|
|
//| * Q - index of entering variable. |
|
|
//| * R - index of leaving variable in AlphaQ |
|
|
//| * Delta - delta from pricing step |
|
|
//| * AlphaPiv - pivot element (in absence of numerical rounding it|
|
|
//| is AlphaR[Q] = AlphaQ[R]) |
|
|
//| * ThetaP - primal step length |
|
|
//| * ThetaD - dual step length |
|
|
//| * AlphaQ - pivot column |
|
|
//| * AlphaQim - intermediate result from Ftran for AlphaQ, used |
|
|
//| for Forest-Tomlin update, not referenced when other|
|
|
//| update scheme is set |
|
|
//| * AlphaR - pivot row |
|
|
//| * Tau - tau-vector for DSE pricing (ignored if simple |
|
|
//| pricing is used) |
|
|
//| * PossibleAlphaRFlips, PossibleAlphaRFlipsCnt - outputs of the |
|
|
//| RatioTest() information about possible variable |
|
|
//| flips - indexes of AlphaR positions which are |
|
|
//| considered for flipping due to BFRT (however, we |
|
|
//| have to check residual costs before actually |
|
|
//| flipping variables - it is possible that some |
|
|
//| variables in this set actually do not need flipping|
|
|
//| It performs following operations: |
|
|
//| * basis update |
|
|
//| * update of XB / BndTB / BndLB / BndUB[] and XA[] (basic and |
|
|
//| nonbasic components), D |
|
|
//| * update of pricing weights |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::UpdateStep(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
int p,
|
|
int q,
|
|
int r,
|
|
double delta,
|
|
double alphapiv,
|
|
double thetap,
|
|
double thetad,
|
|
CRowDouble &alphaq,
|
|
CRowDouble &alphaqim,
|
|
CDSSVector &alphar,
|
|
CRowDouble &tau,
|
|
CRowInt &possiblealpharflips,
|
|
int possiblealpharflipscnt,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int nx=s.m_ns+s.m_m;
|
|
int m=s.m_m;
|
|
int ii=0;
|
|
int j=0;
|
|
int k=0;
|
|
int aj=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
double bndl=0;
|
|
double bndu=0;
|
|
bool flipped=false;
|
|
double flip=0;
|
|
double dj=0;
|
|
int dir=0;
|
|
int idx=0;
|
|
int actualflipscnt=0;
|
|
int t0=0;
|
|
int alpharlen=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(Settings.m_ratiotest==0 || Settings.m_ratiotest==1 || Settings.m_ratiotest==2,__FUNCTION__+": invalid X"))
|
|
return;
|
|
if(!CAp::Assert(s.m_state==m_ssvalid,__FUNCTION__+": invalid X"))
|
|
return;
|
|
if(!CAp::Assert(p>=0 && q>=0,__FUNCTION__+": invalid P/Q"))
|
|
return;
|
|
if(!CAp::Assert(delta!=0.0,__FUNCTION__+": Delta=0"))
|
|
return;
|
|
if(!CAp::Assert(alphapiv!=0.0,__FUNCTION__+": AlphaPiv=0"))
|
|
return;
|
|
//--- Timers
|
|
if(State.m_dotimers)
|
|
t0=(int)(GetTickCount()/10000);
|
|
//--- Prepare
|
|
dir=(int)MathSign(delta);
|
|
alpharlen=alphar.m_k;
|
|
flip=0;
|
|
State.m_tmp0=vector<double>::Zeros(m);
|
|
State.m_ustmpi.Resize(nx);
|
|
actualflipscnt=0;
|
|
//--- Evaluate and update non-basic elements of D
|
|
for(ii=0; ii<alpharlen; ii++)
|
|
{
|
|
j=alphar.m_idx[ii];
|
|
s.m_d.Add(j,- thetad*alphar.m_vals[ii]);
|
|
}
|
|
for(ii=0; ii<possiblealpharflipscnt; ii++)
|
|
{
|
|
aj=possiblealpharflips[ii];
|
|
j=alphar.m_idx[aj];
|
|
dj=s.m_d[j];
|
|
bndl=s.m_bndl[j];
|
|
bndu=s.m_bndu[j];
|
|
flipped=false;
|
|
if(s.m_xa[j]==bndl && dj<0)
|
|
{
|
|
flip=bndu-bndl;
|
|
flipped=true;
|
|
}
|
|
else
|
|
{
|
|
if(s.m_xa[j]==bndu && dj>0)
|
|
{
|
|
flip=bndl-bndu;
|
|
flipped=true;
|
|
}
|
|
}
|
|
if(flipped)
|
|
{
|
|
delta-=dir*(bndu-bndl)*MathAbs(alphar.m_vals[aj]);
|
|
State.m_ustmpi.Set(actualflipscnt,j);
|
|
actualflipscnt++;
|
|
k0=State.m_at.m_RIdx[j];
|
|
k1=State.m_at.m_RIdx[j+1];
|
|
for(k=k0; k<k1; k++)
|
|
{
|
|
idx=State.m_at.m_Idx[k];
|
|
State.m_tmp0.Add(idx,flip*State.m_at.m_Vals[k]);
|
|
}
|
|
}
|
|
}
|
|
s.m_d.Set(p,-thetad);
|
|
s.m_d.Set(q,0.0);
|
|
//--- Apply BFRT update (aka long dual step) or simple ratio update
|
|
if(actualflipscnt>0)
|
|
{
|
|
thetap=delta/alphapiv;
|
|
k0=State.m_at.m_RIdx[q];
|
|
k1=State.m_at.m_RIdx[q+1]-1;
|
|
for(k=k0; k<=k1; k++)
|
|
{
|
|
idx=State.m_at.m_Idx[k];
|
|
State.m_tmp0.Add(idx,thetap*State.m_at.m_Vals[k]);
|
|
}
|
|
BasisSolve(State.m_basis,State.m_tmp0,State.m_tmp1,State.m_tmp2);
|
|
for(j=0; j<m; j++)
|
|
s.m_xb.Add(j,-State.m_tmp1[j]);
|
|
for(ii=0; ii<=actualflipscnt-1; ii++)
|
|
{
|
|
j=State.m_ustmpi[ii];
|
|
if(s.m_xa[j]==s.m_bndl[j])
|
|
s.m_xa.Set(j,s.m_bndu[j]);
|
|
else
|
|
s.m_xa.Set(j,s.m_bndl[j]);
|
|
}
|
|
s.m_xb.Set(r,s.m_xa[q]+thetap);
|
|
if(dir<0)
|
|
s.m_xa.Set(p,s.m_bndl[p]);
|
|
else
|
|
s.m_xa.Set(p,s.m_bndu[p]);
|
|
}
|
|
else
|
|
{
|
|
for(j=0; j<m; j++)
|
|
s.m_xb.Add(j,-thetap*alphaq[j]);
|
|
s.m_xb.Set(r,s.m_xa[q]+thetap);
|
|
if(dir<0)
|
|
s.m_xa.Set(p,s.m_bndl[p]);
|
|
else
|
|
s.m_xa.Set(p,s.m_bndu[p]);
|
|
}
|
|
//--- Update basis
|
|
BasisUpdateTrf(State.m_basis,State.m_at,p,q,alphaq,alphaqim,r,tau,Settings);
|
|
//--- Update cached variables
|
|
CacheBoundInfo(s,r,q,Settings);
|
|
//--- Tracing and timers
|
|
if(State.m_dodetailedtrace)
|
|
{
|
|
if(State.m_basis.m_trftype==3)
|
|
UpdateAvgCounter(SparsitYOf(State.m_basis.m_densemu,State.m_basis.m_trfage*m),State.m_repfilldensemu,State.m_repfilldensemucnt);
|
|
}
|
|
if(State.m_dotimers)
|
|
{
|
|
State.m_repdualupdatesteptime+=((int)(GetTickCount()/10000)-t0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs several checks for accumulation of errors |
|
|
//| during factorization update. It returns True if refactorization |
|
|
//| is advised. |
|
|
//+------------------------------------------------------------------+
|
|
bool CRevisedDualSimplex::RefactorizationRequired(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
int q,
|
|
double alpharpiv,
|
|
int r,
|
|
double alphaqpiv)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int m=s.m_m;
|
|
double mx=0;
|
|
double v=0;
|
|
//--- Quick exit
|
|
if(State.m_basis.m_trfage<=m_safetrfage)
|
|
return(result);
|
|
//--- Compare Q-th entry of the pivot row AlphaR with R-th entry of the AlphaQ;
|
|
//--- ideally, both should match exactly. The difference is a rough estimate
|
|
//--- of the magnitude of the numerical errors.
|
|
mx=(State.m_alphaq.Abs()+0).Max();
|
|
result=result || MathAbs(alphaqpiv-alpharpiv)>(m_alphatrigger*(1.0+mx));
|
|
result=result || MathAbs(alphaqpiv-alpharpiv)>(m_alphatrigger2*MathAbs(alpharpiv));
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function caches information for I-th column of the basis, |
|
|
//| which is assumed to store variable K: |
|
|
//| * lower bound in S.BndLB[I] = S.BndL[K] |
|
|
//| * upper bound in S.BndUB[I] = S.BndU[K] |
|
|
//| * bound type in S.BndTB[I] = S.BndT[K] |
|
|
//| * lower bound primal error tolerance in S.BndTolLB[I] |
|
|
//| (nonnegative) |
|
|
//| * upper bound primal error tolerance in S.BndTolLB[I] |
|
|
//| (nonnegative). |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::CacheBoundInfo(CDualSimplexSubproblem &s,
|
|
int i,
|
|
int k,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
s.m_bndlb.Set(i,s.m_bndl[k]);
|
|
s.m_bndub.Set(i,s.m_bndu[k]);
|
|
s.m_bndtb.Set(i,s.m_bndt[k]);
|
|
s.m_bndtollb.Set(i,Settings.m_xtolabs+Settings.m_xtolrelabs*Settings.m_xtolabs*MathAbs(s.m_bndlb[i]));
|
|
s.m_bndtolub.Set(i,Settings.m_xtolabs+Settings.m_xtolrelabs*Settings.m_xtolabs*MathAbs(s.m_bndub[i]));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs actual solution of dual simplex subproblem|
|
|
//| (either primary one or phase 1 one). |
|
|
//| A problem with following properties is expected: |
|
|
//| * M > 0 |
|
|
//| * feasible box constraints |
|
|
//| * dual feasible initial basis |
|
|
//| * actual initial point XC and target value Z |
|
|
//| * actual reduced cost vector D |
|
|
//| * pricing weights being set to 1.0 or copied from previous |
|
|
//| problem |
|
|
//| Returns: |
|
|
//| * Info = +1 for success, -3 for infeasible |
|
|
//| * IterationsCount is increased by amount of iterations |
|
|
//| performed |
|
|
//| NOTE: this function internally uses separate storage of basic and|
|
|
//| nonbasic components; however, all inputs and outputs use |
|
|
//| single array S.XA[] to store both basic and nonbasic |
|
|
//| variables. It transparently splits variables on input and |
|
|
//| recombines them on output. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::SolveSubproblemDual(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
bool IsPhase1,
|
|
CDualSimplexSettings &Settings,
|
|
int &Info)
|
|
{
|
|
//--- create variables
|
|
int nx=s.m_ns+s.m_m;
|
|
int m=s.m_m;
|
|
int i=0;
|
|
int p=0;
|
|
int r=0;
|
|
int q=0;
|
|
double alpharpiv=0;
|
|
double alphaqpiv=0;
|
|
double thetad=0;
|
|
double thetap=0;
|
|
double delta=0;
|
|
int forcedrestarts=0;
|
|
|
|
Info=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(s.m_state==m_ssvalid,__FUNCTION__+": X is not valid"))
|
|
return;
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0"))
|
|
return;
|
|
for(i=0; i<nx; i++)
|
|
if(!CAp::Assert(s.m_bndt[i]!=m_ccinfeasible,__FUNCTION__+": infeasible box constraints"))
|
|
return;
|
|
if(!CAp::Assert(IsDualFeasible(State,s,Settings),__FUNCTION__+": dual infeasible initial basis"))
|
|
return;
|
|
//--- Actual processing
|
|
OffloadBasicComponents(s,State.m_basis,Settings);
|
|
Info=0;
|
|
State.m_tmp0.Resize(m);
|
|
while(true)
|
|
{
|
|
//--- Iteration report
|
|
if(State.m_dotrace)
|
|
{
|
|
i=State.m_repiterationscount2;
|
|
if(IsPhase1)
|
|
i=State.m_repiterationscount1;
|
|
CAp::Trace(StringFormat("=== ITERATION %5d STARTED ========================================================================\n",i));
|
|
}
|
|
//--- Pricing
|
|
PricingStep(State,s,IsPhase1,p,r,delta,Settings);
|
|
if(delta==0.0)
|
|
{
|
|
//--- Solved! Feasible and bounded!
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> pricing: feasible point found\n");
|
|
RecombineBasicNonBasicX(s,State.m_basis);
|
|
Info=1;
|
|
return;
|
|
}
|
|
//--- BTran
|
|
BTranStep(State,s,r,State.m_rhor,Settings);
|
|
//--- Pivot row
|
|
PivotRowStep(State,s,State.m_rhor,State.m_alphar,Settings);
|
|
//--- Ratio test
|
|
RatioTest(State,s,State.m_alphar,delta,p,q,alpharpiv,thetad,State.m_possibleflips,State.m_possibleflipscnt,Settings);
|
|
if(q<0)
|
|
{
|
|
//--- Do we have fresh factorization and State? If not,
|
|
//--- refresh them prior to declaring that we have no solution.
|
|
if(State.m_basis.m_trfage>0 && forcedrestarts<m_maxforcedrestarts)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("> ratio test: failed,basis is old (age=%d),forcing restart (%d of %d)\n",State.m_basis.m_trfage,forcedrestarts,m_maxforcedrestarts - 1));
|
|
BasisFreshTrf(State.m_basis,State.m_at,Settings);
|
|
SubproblemHandleXNUpdate(State,s);
|
|
OffloadBasicComponents(s,State.m_basis,Settings);
|
|
forcedrestarts++;
|
|
continue;
|
|
}
|
|
//--- Dual unbounded, primal infeasible
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> ratio test: failed,results are accepted\n");
|
|
RecombineBasicNonBasicX(s,State.m_basis);
|
|
Info=-3;
|
|
return;
|
|
}
|
|
thetap=delta/alpharpiv;
|
|
//--- FTran, including additional FTran for DSE weights (if needed)
|
|
//--- NOTE: AlphaQim is filled by intermediate FTran result which is useful
|
|
//--- for Forest-Tomlin update scheme. If not Forest-Tomlin update is
|
|
//--- used, then it is not set.
|
|
FTranStep(State,s,State.m_rhor,q,State.m_alphaq,State.m_alphaqim,State.m_tau,Settings);
|
|
alphaqpiv=State.m_alphaq[r];
|
|
//--- Check numerical accuracy, trigger refactorization if needed
|
|
if(RefactorizationRequired(State,s,q,alpharpiv,r,alphaqpiv))
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> refactorization test: numerical errors are too large,forcing refactorization and restart\n");
|
|
BasisFreshTrf(State.m_basis,State.m_at,Settings);
|
|
SubproblemHandleXNUpdate(State,s);
|
|
OffloadBasicComponents(s,State.m_basis,Settings);
|
|
continue;
|
|
}
|
|
//--- Basis change and update
|
|
UpdateStep(State,s,p,q,r,delta,alpharpiv,thetap,thetad,State.m_alphaq,State.m_alphaqim,State.m_alphar,State.m_tau,State.m_possibleflips,State.m_possibleflipscnt,Settings);
|
|
State.m_repiterationscount++;
|
|
if(IsPhase1)
|
|
State.m_repiterationscount1++;
|
|
else
|
|
State.m_repiterationscount2++;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves simplex subproblem using primal simplex |
|
|
//| method. |
|
|
//| A problem with following properties is expected: |
|
|
//| * M > 0 |
|
|
//| * feasible box constraints |
|
|
//| * primal feasible initial basis |
|
|
//| * actual initial point XC and target value Z |
|
|
//| * actual reduced cost vector D |
|
|
//| * pricing weights being set to 1.0 or copied from previous |
|
|
//| problem |
|
|
//| Returns: |
|
|
//| * Info = +1 for success, -3 for infeasible |
|
|
//| * IterationsCount is increased by amount of iterations |
|
|
//| performed |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::SolveSubproblemPrimal(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
CDualSimplexSettings &Settings,
|
|
int &Info)
|
|
{
|
|
//--- create variables
|
|
int nn=s.m_ns;
|
|
int nx=s.m_ns+s.m_m;
|
|
int m=s.m_m;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vmax=0;
|
|
int bi=0;
|
|
double dj=0;
|
|
int bndt=0;
|
|
int q=0;
|
|
int p=0;
|
|
int r=0;
|
|
int dir=0;
|
|
double lim=0;
|
|
bool haslim=false;
|
|
double thetap=0;
|
|
double xbnd=0;
|
|
double flip=0;
|
|
int canddir=0;
|
|
double candlim=0;
|
|
double candflip=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double alphawave=0;
|
|
double vp=0;
|
|
double vb=0;
|
|
double vx=0;
|
|
double vtest=0;
|
|
double vv=0;
|
|
|
|
Info=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(s.m_state==m_ssvalid,__FUNCTION__+": X is not valid"))
|
|
return;
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0"))
|
|
return;
|
|
for(i=0; i<nx; i++)
|
|
if(!CAp::Assert(s.m_bndt[i]!=m_ccinfeasible,__FUNCTION__+": infeasible box constraints"))
|
|
return;
|
|
//--- Actual processing
|
|
Info=1;
|
|
State.m_tmp0.Resize(m);
|
|
while(true)
|
|
{
|
|
//--- Iteration report
|
|
if(State.m_dotrace)
|
|
{
|
|
i=State.m_repiterationscount3;
|
|
CAp::Trace(StringFormat("=== ITERATION %5d STARTED ========================================================================\n",i));
|
|
}
|
|
//--- Primal simplex pricing step: we implement the very basic version
|
|
//--- of the pricing step because it is expected that primal simplex method
|
|
//--- is used just to apply quick correction after removal of the perturbation.
|
|
q=-1;
|
|
vmax=0;
|
|
dir=0;
|
|
lim=CMath::m_maxrealnumber;
|
|
haslim=false;
|
|
flip=0;
|
|
canddir=0;
|
|
for(i=0; i<nn; i++)
|
|
{
|
|
j=State.m_basis.m_nidx[i];
|
|
dj=s.m_d[j];
|
|
bndt=s.m_bndt[j];
|
|
if(bndt==m_ccfixed)
|
|
continue;
|
|
if(bndt==m_ccrange)
|
|
{
|
|
v=0;
|
|
candlim=s.m_bndu[j]-s.m_bndl[j];
|
|
candflip=0;
|
|
if(s.m_xa[j]==s.m_bndl[j])
|
|
{
|
|
v=-dj;
|
|
canddir=1;
|
|
candflip=s.m_bndu[j];
|
|
}
|
|
if(s.m_xa[j]==s.m_bndu[j])
|
|
{
|
|
v=dj;
|
|
canddir=-1;
|
|
candflip=s.m_bndl[j];
|
|
}
|
|
if(v>vmax)
|
|
{
|
|
vmax=v;
|
|
dir=canddir;
|
|
lim=candlim;
|
|
haslim=true;
|
|
flip=candflip;
|
|
q=j;
|
|
}
|
|
continue;
|
|
}
|
|
v=0;
|
|
canddir=0;
|
|
if(bndt==m_cclower)
|
|
{
|
|
v=-dj;
|
|
canddir=1;
|
|
}
|
|
if(bndt==m_ccupper)
|
|
{
|
|
v=dj;
|
|
canddir=-1;
|
|
}
|
|
if(bndt==m_ccfree)
|
|
{
|
|
v=MathAbs(dj);
|
|
canddir=-(int)MathSign(dj);
|
|
}
|
|
if(v>vmax)
|
|
{
|
|
vmax=v;
|
|
dir=canddir;
|
|
lim=CMath::m_maxrealnumber;
|
|
haslim=false;
|
|
q=j;
|
|
}
|
|
continue;
|
|
}
|
|
if(vmax<=Settings.m_dtolabs)
|
|
{
|
|
//--- Solved: primal and dual feasible!
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> primal pricing: feasible point found\n");
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(q>=0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> primal pricing: found entering variable\n");
|
|
CAp::Trace(StringFormat("Q = %12d (variable selected)\n",q));
|
|
CAp::Trace(StringFormat("|D| = %12.3E (dual infeasibility)\n",vmax));
|
|
}
|
|
//--- FTran and textbook ratio test (again, we expect primal phase to terminate quickly)
|
|
//--- NOTE: AlphaQim is filled by intermediate FTran result which is useful
|
|
//--- for Forest-Tomlin update scheme. If not Forest-Tomlin update is
|
|
//--- used, then it is not set.
|
|
State.m_tmp0.Fill(0);
|
|
j0=State.m_at.m_RIdx[q];
|
|
j1=State.m_at.m_RIdx[q+1];
|
|
for(j=j0; j<j1; j++)
|
|
State.m_tmp0.Set(State.m_at.m_Idx[j],State.m_at.m_Vals[j]);
|
|
BasisSolveX(State.m_basis,State.m_tmp0,State.m_alphaq,State.m_alphaqim,true,State.m_tmp2);
|
|
vp=Settings.m_pivottol;
|
|
p=-1;
|
|
r=-1;
|
|
thetap=0;
|
|
xbnd=0;
|
|
for(i=0; i<m; i++)
|
|
{
|
|
bi=State.m_basis.m_idx[i];
|
|
alphawave=-(dir*State.m_alphaq[i]);
|
|
vx=s.m_xa[bi];
|
|
if(alphawave<-vp && HasBndL(s,bi))
|
|
{
|
|
vb=s.m_bndl[bi];
|
|
if(vx<=vb)
|
|
{
|
|
//--- X[Bi] is already out of bounds due to rounding errors, perform Shifting
|
|
vb=vx-m_shiftlen;
|
|
s.m_bndl.Set(bi,vx);
|
|
}
|
|
vtest=(vb-vx)/alphawave;
|
|
if(p<0 || vtest<thetap)
|
|
{
|
|
p=bi;
|
|
r=i;
|
|
thetap=vtest;
|
|
xbnd=vb;
|
|
}
|
|
}
|
|
if(alphawave>vp && HasBndU(s,bi))
|
|
{
|
|
vb=s.m_bndu[bi];
|
|
if(vx>=vb)
|
|
{
|
|
//--- X[Bi] is already out of bounds due to rounding errors, perform Shifting
|
|
vb=vx+m_shiftlen;
|
|
s.m_bndu.Set(bi,vb);
|
|
}
|
|
vtest=(vb-vx)/alphawave;
|
|
if(p<0 || vtest<thetap)
|
|
{
|
|
p=bi;
|
|
r=i;
|
|
thetap=vtest;
|
|
xbnd=vb;
|
|
}
|
|
}
|
|
}
|
|
if(p<0 && !haslim)
|
|
{
|
|
//--- Primal unbounded
|
|
Info=-4;
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> primal ratio test: dual infeasible,primal unbounded\n");
|
|
return;
|
|
}
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> primal ratio test: found leaving variable\n");
|
|
CAp::Trace(StringFormat("P = %12d (variable index)\n",p));
|
|
CAp::Trace(StringFormat("R = %12d (variable index in basis)\n",r));
|
|
CAp::Trace(StringFormat("ThetaP = %12.3E (primal step length)\n",thetap));
|
|
}
|
|
//--- Update step
|
|
if(p>=0 && (!haslim || thetap<lim))
|
|
{
|
|
//--- One of the basic variables hit the boundary and become non-basic.
|
|
//--- Perform update:
|
|
//--- * update basic elements of X[] (X[p] is explicitly set to the
|
|
//--- boundary value) and X[q]
|
|
//--- * update target value Z
|
|
//--- * update factorization
|
|
//--- * update D[]
|
|
State.m_tmp0.Resize(m);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
bi=State.m_basis.m_idx[i];
|
|
vv=thetap*(dir*State.m_alphaq[i]);
|
|
s.m_xa.Add(bi,-vv);
|
|
}
|
|
s.m_xa.Set(p,xbnd);
|
|
s.m_xa.Add(q,dir*thetap);
|
|
State.m_tmp0.Fill(0);
|
|
BasisUpdateTrf(State.m_basis,State.m_at,p,q,State.m_alphaq,State.m_alphaqim,r,State.m_tmp0,Settings);
|
|
for(i=0; i<m; i++)
|
|
State.m_tmp0.Set(i,s.m_effc[State.m_basis.m_idx[i]]);
|
|
BasisSolveT(State.m_basis,State.m_tmp0,State.m_tmp1,State.m_tmp2);
|
|
ComputeAnTV(State,State.m_tmp1,s.m_d);
|
|
for(i=0; i<nn; i++)
|
|
{
|
|
j=State.m_basis.m_nidx[i];
|
|
s.m_d.Set(j,s.m_effc[j]-s.m_d[j]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Basis does not change because Qth variable flips from one bound
|
|
//--- to another one long before we encounter the boundary
|
|
s.m_xa.Set(q,flip);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
bi=State.m_basis.m_idx[i];
|
|
vv=lim*(dir*State.m_alphaq[i]);
|
|
s.m_xa.Add(bi,-vv);
|
|
}
|
|
}
|
|
State.m_repiterationscount++;
|
|
State.m_repiterationscount3++;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function estimates feasibility properties of the current |
|
|
//| basis and invokes phase 1 if necessary. |
|
|
//| A problem with following properties is expected: |
|
|
//| * M > 0 |
|
|
//| * feasible box constraints |
|
|
//| * some initial basis (can be dual infeasible) with actual |
|
|
//| factorization |
|
|
//| * actual initial point XC and target value Z |
|
|
//| * actual reduced cost vector D |
|
|
//| It returns: |
|
|
//| * +1 if dual feasible basis was found |
|
|
//| * -4 if problem is dual infeasible |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::InvokePhase1(CDualSimplexState &State,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int m=State.m_primary.m_m;
|
|
double dualerr=0;
|
|
|
|
State.m_repterminationtype=0;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(State.m_primary.m_state==m_ssvalid,__FUNCTION__+": invalid primary X"))
|
|
return;
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0"))
|
|
return;
|
|
//--- Is it dual feasible from the very beginning (or maybe after initial DFC)?
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> performing initial dual feasibility correction...\n");
|
|
dualerr=InitialDualFeasibilityCorrection(State,State.m_primary,Settings);
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("> initial dual feasibility correction done\ndualErr = %.3E\n",dualerr));
|
|
if(dualerr<=Settings.m_dtolabs)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> solution is dual feasible,phase 1 is done\n");
|
|
State.m_repterminationtype=1;
|
|
return;
|
|
}
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("> solution is not dual feasible,proceeding to full-scale phase 1\n");
|
|
CAp::Trace("\n");
|
|
CAp::Trace("****************************************************************************************************\n");
|
|
CAp::Trace("* PHASE 1 OF DUAL SIMPLEX SOLVER *\n");
|
|
CAp::Trace("****************************************************************************************************\n");
|
|
}
|
|
//--- Solve phase #1 subproblem
|
|
SubproblemInitPhase1(State.m_primary,State.m_basis,State.m_phase1);
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> performing phase 1 dual feasibility correction...\n");
|
|
dualerr=InitialDualFeasibilityCorrection(State,State.m_phase1,Settings);
|
|
if(State.m_dotrace)
|
|
CAp::Trace(StringFormat("> phase 1 dual feasibility correction done\ndualErr = %.3E\n",dualerr));
|
|
SolveSubproblemDual(State,State.m_phase1,true,Settings,State.m_repterminationtype);
|
|
//--- check
|
|
if(!CAp::Assert(State.m_repterminationtype>0,__FUNCTION__+": unexpected failure of phase #1"))
|
|
return;
|
|
State.m_repterminationtype=1;
|
|
//--- Setup initial basis for phase #2 using solution of phase #1
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> setting up phase 2 initial solution\n");
|
|
SubproblemInferInitialXN(State,State.m_primary);
|
|
dualerr=InitialDualFeasibilityCorrection(State,State.m_primary,Settings);
|
|
if(dualerr>Settings.m_dtolabs)
|
|
{
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> initial dual feasibility correction failed! terminating...\n");
|
|
State.m_repterminationtype=-4;
|
|
return;
|
|
}
|
|
State.m_repterminationtype=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs actual solution. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - State |
|
|
//| Solution results can be found in fields of State which are |
|
|
//| explicitly declared as accessible by external code. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DSSOptimizeWrk(CDualSimplexState &State,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int nx=State.m_primary.m_ns+State.m_primary.m_m;
|
|
int m=State.m_primary.m_m;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
CHighQualityRandState rs;
|
|
int t0=0;
|
|
//--- Handle case when M=0; after this block we assume that M>0.
|
|
if(m==0)
|
|
{
|
|
//--- Trace
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> box-only LP problem,quick solution\n");
|
|
//--- Solve
|
|
SolveBoxOnly(State);
|
|
return;
|
|
}
|
|
//--- Most basic check for correctness of box and/or linear constraints
|
|
for(j=0; j<nx; j++)
|
|
{
|
|
if(State.m_primary.m_bndt[j]==m_ccinfeasible)
|
|
{
|
|
//--- Set error flag and generate some point to return
|
|
if(State.m_dotrace)
|
|
CAp::Trace("[WARNING] infeasible box constraint (or range constraint with AL>AU) found,terminating\n");
|
|
State.m_repterminationtype=-3;
|
|
SetZeroXYStats(State);
|
|
return;
|
|
}
|
|
}
|
|
//--- Initialization:
|
|
//--- * initial perturbed C[]
|
|
CHighQualityRand::HQRndSeed(7456,2355,rs);
|
|
for(i=0; i<nx; i++)
|
|
{
|
|
if(!IsFree(State.m_primary,i))
|
|
{
|
|
//--- apply perturbation
|
|
v=Settings.m_perturbmag*(1+MathAbs(State.m_primary.m_rawc[i]))*(1+CHighQualityRand::HQRndUniformR(rs));
|
|
if(!HasBndL(State.m_primary,i))
|
|
v=-v;
|
|
State.m_primary.m_effc.Set(i,State.m_primary.m_rawc[i]+v);
|
|
}
|
|
}
|
|
//--- Solve phase 1 subproblem, then perturbed subproblem
|
|
BasisFreshTrf(State.m_basis,State.m_at,Settings);
|
|
if(State.m_primary.m_state==m_ssinvalid)
|
|
SubproblemInferInitialXN(State,State.m_primary);
|
|
if(State.m_primary.m_state==m_ssvalidxn)
|
|
SubproblemHandleXNUpdate(State,State.m_primary);
|
|
//--- check
|
|
if(!CAp::Assert(State.m_primary.m_state==m_ssvalid,__FUNCTION__+": integrity check failed (init)"))
|
|
return;
|
|
if(State.m_dotimers)
|
|
t0=(int)(GetTickCount()/10000);
|
|
InvokePhase1(State,Settings);
|
|
if(State.m_dotimers)
|
|
State.m_repphase1time=(int)(GetTickCount()/10000)-t0;
|
|
if(State.m_repterminationtype<=0)
|
|
{
|
|
//--- Primal unbounded, dual infeasible
|
|
if(!CAp::Assert(State.m_repterminationtype==-4,__FUNCTION__+": integrity check for InvokePhase1() result failed"))
|
|
return;
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> the problem is dual infeasible,primal unbounded\n> done\n");
|
|
SetXYDStats(State,State.m_primary,State.m_basis,State.m_xydsbuf,State.m_repx,State.m_replagbc,State.m_replaglc,State.m_repstats);
|
|
return;
|
|
}
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("\n");
|
|
CAp::Trace("****************************************************************************************************\n");
|
|
CAp::Trace("* PHASE 2 OF DUAL SIMPLEX SOLVER *\n");
|
|
CAp::Trace("****************************************************************************************************\n");
|
|
}
|
|
if(State.m_dotimers)
|
|
t0=(int)(GetTickCount()/10000);
|
|
SolveSubproblemDual(State,State.m_primary,false,Settings,State.m_repterminationtype);
|
|
if(State.m_dotimers)
|
|
State.m_repphase2time=(int)(GetTickCount()/10000)-t0;
|
|
if(State.m_repterminationtype<=0)
|
|
{
|
|
//--- Primal infeasible
|
|
if(!CAp::Assert(State.m_repterminationtype==-3,__FUNCTION__+": integrity check for SolveSubproblemDual() result failed"))
|
|
return;
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> the problem is primal infeasible\n> done\n");
|
|
SetXYDStats(State,State.m_primary,State.m_basis,State.m_xydsbuf,State.m_repx,State.m_replagbc,State.m_replaglc,State.m_repstats);
|
|
return;
|
|
}
|
|
//--- Remove perturbation from the cost vector,
|
|
//--- then use primal simplex to enforce dual feasibility
|
|
//--- after removal of the perturbation (if necessary).
|
|
if(State.m_dotrace)
|
|
{
|
|
CAp::Trace("\n");
|
|
CAp::Trace("****************************************************************************************************\n");
|
|
CAp::Trace("* PHASE 3 OF DUAL SIMPLEX SOLVER (perturbation removed from cost vector) *\n");
|
|
CAp::Trace("****************************************************************************************************\n");
|
|
}
|
|
if(State.m_dotimers)
|
|
t0=(int)(GetTickCount()/10000);
|
|
SubproblemInitPhase3(State.m_primary,State.m_phase3);
|
|
State.m_phase3.m_effc=State.m_primary.m_rawc;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_phase3.m_state>=m_ssvalidxn,__FUNCTION__+": integrity check failed (remove perturbation)"))
|
|
return;
|
|
SubproblemHandleXNUpdate(State,State.m_phase3);
|
|
SolveSubproblemPrimal(State,State.m_phase3,Settings,State.m_repterminationtype);
|
|
if(State.m_dotimers)
|
|
State.m_repphase3time=(int)(GetTickCount()/10000)-t0;
|
|
if(State.m_repterminationtype<=0)
|
|
{
|
|
//--- Dual infeasible, primal unbounded
|
|
//--- check
|
|
if(!CAp::Assert(State.m_repterminationtype==-4,__FUNCTION__+": integrity check for SolveSubproblemPrimal() result failed"))
|
|
return;
|
|
if(State.m_dotrace)
|
|
CAp::Trace("> the problem is primal unbounded\n> done\n");
|
|
SetXYDStats(State,State.m_phase3,State.m_basis,State.m_xydsbuf,State.m_repx,State.m_replagbc,State.m_replaglc,State.m_repstats);
|
|
return;
|
|
}
|
|
State.m_primary.m_xa=State.m_phase3.m_xa;
|
|
for(i=0; i<nx; i++)
|
|
{
|
|
if(HasBndL(State.m_primary,i))
|
|
State.m_primary.m_xa.Set(i,MathMax(State.m_primary.m_xa[i],State.m_primary.m_bndl[i]));
|
|
if(HasBndU(State.m_primary,i))
|
|
State.m_primary.m_xa.Set(i,MathMin(State.m_primary.m_xa[i],State.m_primary.m_bndu[i]));
|
|
}
|
|
//--- Primal and dual feasible, problem solved
|
|
State.m_repterminationtype=1;
|
|
SetXYDStats(State,State.m_primary,State.m_basis,State.m_xydsbuf,State.m_repx,State.m_replagbc,State.m_replaglc,State.m_repstats);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Box - constrained solver; sets State.RepX, State.RepStats and |
|
|
//| State.RepTerminationType, does not change other fields. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::SolveBoxOnly(CDualSimplexState &State)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int ns=State.m_primary.m_ns;
|
|
//--- check
|
|
if(!CAp::Assert(State.m_primary.m_m==0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
State.m_replagbc=vector<double>::Zeros(ns);
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
//--- Handle infeasible variable
|
|
if(State.m_primary.m_bndt[i]==m_ccinfeasible)
|
|
{
|
|
State.m_repterminationtype=-3;
|
|
State.m_repx.Set(i,0.5*(State.m_primary.m_bndl[i]+State.m_primary.m_bndu[i]));
|
|
State.m_repstats.Set(i,0);
|
|
continue;
|
|
}
|
|
//--- Handle fixed variable
|
|
if(State.m_primary.m_bndt[i]==m_ccfixed)
|
|
{
|
|
State.m_repx.Set(i,State.m_primary.m_bndl[i]);
|
|
State.m_repstats.Set(i,-1);
|
|
State.m_replagbc.Set(i,-State.m_primary.m_rawc[i]);
|
|
continue;
|
|
}
|
|
//--- Handle non-zero cost component
|
|
if((double)(State.m_primary.m_rawc[i])>0.0)
|
|
{
|
|
if(State.m_primary.m_bndt[i]!=m_ccrange && State.m_primary.m_bndt[i]!=m_cclower)
|
|
{
|
|
if(State.m_repterminationtype>0)
|
|
State.m_repterminationtype=-4;
|
|
if(State.m_primary.m_bndt[i]==m_ccupper)
|
|
{
|
|
State.m_repx.Set(i,State.m_primary.m_bndu[i]);
|
|
State.m_repstats.Set(i,1);
|
|
}
|
|
else
|
|
{
|
|
State.m_repx.Set(i,0);
|
|
State.m_repstats.Set(i,0);
|
|
}
|
|
State.m_replagbc.Set(i,0);
|
|
}
|
|
else
|
|
{
|
|
State.m_repx.Set(i,State.m_primary.m_bndl[i]);
|
|
State.m_repstats.Set(i,-1);
|
|
State.m_replagbc.Set(i,-State.m_primary.m_rawc[i]);
|
|
}
|
|
continue;
|
|
}
|
|
if(State.m_primary.m_rawc[i]<0.0)
|
|
{
|
|
if(State.m_primary.m_bndt[i]!=m_ccrange && State.m_primary.m_bndt[i]!=m_ccupper)
|
|
{
|
|
if(State.m_repterminationtype>0)
|
|
State.m_repterminationtype=-4;
|
|
if(State.m_primary.m_bndt[i]==m_cclower)
|
|
{
|
|
State.m_repx.Set(i,State.m_primary.m_bndl[i]);
|
|
State.m_repstats.Set(i,-1);
|
|
}
|
|
else
|
|
{
|
|
State.m_repx.Set(i,0);
|
|
State.m_repstats.Set(i,0);
|
|
}
|
|
State.m_replagbc.Set(i,0);
|
|
}
|
|
else
|
|
{
|
|
State.m_repx.Set(i,State.m_primary.m_bndu[i]);
|
|
State.m_repstats.Set(i,1);
|
|
State.m_replagbc.Set(i,-State.m_primary.m_rawc[i]);
|
|
}
|
|
continue;
|
|
}
|
|
//--- Handle non-free variable with zero cost component
|
|
if(State.m_primary.m_bndt[i]==m_ccupper || State.m_primary.m_bndt[i]==m_ccrange)
|
|
{
|
|
State.m_repx.Set(i,State.m_primary.m_bndu[i]);
|
|
State.m_repstats.Set(i,1);
|
|
State.m_replagbc.Set(i,0);
|
|
continue;
|
|
}
|
|
if(State.m_primary.m_bndt[i]==m_cclower)
|
|
{
|
|
State.m_repx.Set(i,State.m_primary.m_bndl[i]);
|
|
State.m_repstats.Set(i,-1);
|
|
State.m_replagbc.Set(i,0);
|
|
continue;
|
|
}
|
|
//--- Free variable, zero cost component
|
|
//--- check
|
|
if(!CAp::Assert(State.m_primary.m_bndt[i]==m_ccfree,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
State.m_repx.Set(i,0);
|
|
State.m_repstats.Set(i,0);
|
|
State.m_replagbc.Set(i,0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Zero - fill RepX, RepLagBC, RepLagLC, RepStats. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::SetZeroXYStats(CDualSimplexState &State)
|
|
{
|
|
State.m_repx=vector<double>::Zeros(State.m_primary.m_ns);
|
|
State.m_replagbc=vector<double>::Zeros(State.m_primary.m_ns);
|
|
State.m_replaglc=vector<double>::Zeros(State.m_primary.m_m);
|
|
State.m_repstats.Resize(State.m_primary.m_ns+State.m_primary.m_m);
|
|
State.m_repstats.Fill(0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes basis structure; no triangular |
|
|
//| factorization is prepared yet. Previously allocated memory is |
|
|
//| reused. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::BasisInit(int ns,int m,CDualSimplexBasis &s)
|
|
{
|
|
s.m_ns=ns;
|
|
s.m_m=m;
|
|
s.m_idx.Resize(m);
|
|
s.m_nidx.Resize(ns);
|
|
|
|
ArrayResize(s.m_isbasic,ns+m);
|
|
ArrayFill(s.m_isbasic,0,ns,false);
|
|
ArrayFill(s.m_isbasic,ns,m,true);
|
|
|
|
for(int i=0; i<ns; i++)
|
|
s.m_nidx.Set(i,i);
|
|
for(int i=0; i<m; i++)
|
|
s.m_idx.Set(i,ns+i);
|
|
s.m_trftype=3;
|
|
s.m_trfage=0;
|
|
s.m_isvalidtrf=false;
|
|
s.m_dseweights=vector<double>::Ones(m);
|
|
s.m_dsevalid=false;
|
|
BasisClearStats(s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function clears internal performance counters of the basis |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::BasisClearStats(CDualSimplexBasis &s)
|
|
{
|
|
s.m_statfact=0;
|
|
s.m_statupdt=0;
|
|
s.m_statoffdiag=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function resizes basis. It is assumed that constraint matrix|
|
|
//| is completely overwritten by new one, but both matrices are |
|
|
//| similar enough so we can reuse previous basis. |
|
|
//| Dual steepest edge weights are invalidated by this function. |
|
|
//| This function: |
|
|
//| * tries to resize basis |
|
|
//| * if possible, returns True and valid basis with valid |
|
|
//| factorization |
|
|
//| * if resize is impossible (or abandoned due to stability |
|
|
//| reasons), it returns False and basis object is left in the |
|
|
//| invalid State(you have to reinitialize it by all-logicals |
|
|
//| basis) |
|
|
//| Following types of resize are supported: |
|
|
//| * new basis size is larger than previous one => logical |
|
|
//| elements are added to the new basis |
|
|
//| * basis sizes match => no operation is performed |
|
|
//| * new basis size is zero => basis is set to zero |
|
|
//| This function: |
|
|
//| * requires valid triangular factorization at S on entry |
|
|
//| * replaces it by another, valid factorization |
|
|
//| * checks that new factorization deviates from the previous one |
|
|
//| not too much by comparing magnitudes of min[abs(u_ii)] in |
|
|
//| both factorization (sharp decrease results in attempt to |
|
|
//| resize being abandoned |
|
|
//| IMPORTANT: if smooth resize is not possible, this function throws|
|
|
//| an exception! It is responsibility of the caller to |
|
|
//| check that smooth resize is possible |
|
|
//+------------------------------------------------------------------+
|
|
bool CRevisedDualSimplex::BasisTryResize(CDualSimplexBasis &s,
|
|
int newm,
|
|
CSparseMatrix &at,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int ns=s.m_ns;
|
|
int oldm=s.m_m;
|
|
double oldminu=0;
|
|
double newminu=0;
|
|
//--- Quick exit strategies
|
|
if(newm==0)
|
|
{
|
|
BasisInit(ns,0,s);
|
|
return(true);
|
|
}
|
|
//--- Same size or larger
|
|
if(newm>=oldm)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_isvalidtrf || oldm==0,__FUNCTION__+": needs valid TRF in S"))
|
|
return(false);
|
|
//--- Save information about matrix conditioning
|
|
oldminu=BasisMinimumDiagonalElement(s);
|
|
//--- Growth if needed
|
|
s.m_m=newm;
|
|
s.m_idx.Resize(newm);
|
|
ArrayResize(s.m_isbasic,ns+newm);
|
|
for(int i=oldm; i<newm; i++)
|
|
{
|
|
s.m_idx.Set(i,ns+i);
|
|
s.m_isbasic[ns+i]=true;
|
|
}
|
|
//--- DSE weights are invalid and filled by 1.0
|
|
s.m_dseweights=vector<double>::Ones(newm);
|
|
s.m_dsevalid=false;
|
|
//--- Invalidate TRF.
|
|
//--- Try to refactorize.
|
|
s.m_isvalidtrf=false;
|
|
newminu=BasisFreshTRFUnsafe(s,at,Settings);
|
|
result=newminu>=(m_maxudecay*oldminu);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//--- unexpected branch
|
|
CAp::Assert(false,__FUNCTION__+": unexpected branch");
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns minimum diagonal element of S. Result = 1 |
|
|
//| is returned for M = 0. |
|
|
//+------------------------------------------------------------------+
|
|
double CRevisedDualSimplex::BasisMinimumDiagonalElement(CDualSimplexBasis &s)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
double v=0;
|
|
double vv=0;
|
|
//--- Quick exit
|
|
if(m==0)
|
|
return(1);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_trftype==0 || s.m_trftype==1 || s.m_trftype==2 || s.m_trftype==3,__FUNCTION__+": unexpected TRF type"))
|
|
return(0);
|
|
if(!CAp::Assert(s.m_isvalidtrf,__FUNCTION__+": TRF is invalid"))
|
|
return(0);
|
|
v=CMath::m_maxrealnumber;
|
|
for(int i=0; i<m; i++)
|
|
{
|
|
vv=0;
|
|
if(s.m_trftype==0 || s.m_trftype==1)
|
|
vv=s.m_denselu.Get(i,i);
|
|
if(s.m_trftype==2 || s.m_trftype==3)
|
|
vv=CSparse::SparseGetDiagonal(s.m_sparseu,i);
|
|
if(vv<0)
|
|
vv=-vv;
|
|
if(vv<v)
|
|
v=vv;
|
|
}
|
|
//--- return result
|
|
return(v);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function exports division of variables into basic / nonbasic|
|
|
//| ones; only basic / nonbasic sets are exported - triangular |
|
|
//| factorization is NOT exported; however, valid triangular |
|
|
//| factorization IS required in order to perform exporting. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::BasisExportTo(CDualSimplexBasis &s0,
|
|
CDualSimplexBasis &s1)
|
|
{
|
|
//--- copy
|
|
s1.m_ns=s0.m_ns;
|
|
s1.m_m=s0.m_m;
|
|
s1.m_idx=s0.m_idx;
|
|
s1.m_nidx=s0.m_nidx;
|
|
ArrayCopy(s1.m_isbasic,s0.m_isbasic);
|
|
s1.m_isvalidtrf=false;
|
|
s1.m_trftype=-1;
|
|
s1.m_dsevalid=false;
|
|
if(s0.m_m>0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s0.m_isvalidtrf,"BasisExport: valid factorization is required for source basis"))
|
|
return;
|
|
s1.m_eminu=BasisMinimumDiagonalElement(s0);
|
|
}
|
|
else
|
|
s1.m_eminu=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function imports from S1 to S0 a division of variables into |
|
|
//| basic / nonbasic ones; only basic / nonbasic sets are imported. |
|
|
//| Triangular factorization is not imported; however, this function |
|
|
//| checks that new factorization deviates from the previous one not |
|
|
//| too much by comparing magnitudes of min[abs(u_ii)] in both |
|
|
//| factorization (basis being imported stores statistics about U). |
|
|
//| Sharp decrease of diagonal elements means that we have too |
|
|
//| unstable situation which results in import being abandoned. In |
|
|
//| this case False is returned, and the basis S0 is left in the |
|
|
//| indeterminate invalid State(you have to reinitialize it by |
|
|
//| all-logicals). |
|
|
//| IMPORTANT: if metrics of S0 and S1 do not match, an exception |
|
|
//| will be generated. |
|
|
//+------------------------------------------------------------------+
|
|
bool CRevisedDualSimplex::BasisTryImportFrom(CDualSimplexBasis &s0,
|
|
CDualSimplexBasis &s1,
|
|
CSparseMatrix &at,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
double newminu=0;
|
|
//--- check
|
|
if(!CAp::Assert(s0.m_ns==s1.m_ns,__FUNCTION__+": structural variable counts do not match"))
|
|
return(false);
|
|
BasisClearStats(s0);
|
|
s0.m_m=s1.m_m;
|
|
s0.m_idx=s1.m_idx;
|
|
s0.m_nidx=s1.m_nidx;
|
|
ArrayCopy(s0.m_isbasic,s1.m_isbasic);
|
|
s0.m_isvalidtrf=false;
|
|
s0.m_dseweights=vector<double>::Ones(s1.m_m);
|
|
s0.m_dsevalid=false;
|
|
newminu=BasisFreshTRFUnsafe(s0,at,Settings);
|
|
result=newminu>=(m_maxudecay*s1.m_eminu);
|
|
if(!result)
|
|
{
|
|
s0.m_isvalidtrf=false;
|
|
s0.m_trftype=-1;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function computes fresh triangular factorization. |
|
|
//| If TRF of age 0 (fresh) is already present, no new factorization |
|
|
//| is calculated. If factorization has exactly zero element along |
|
|
//| diagonal, this function generates exception. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::BasisFreshTrf(CDualSimplexBasis &s,
|
|
CSparseMatrix &at,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
double v=BasisFreshTRFUnsafe(s,at,Settings);
|
|
CAp::Assert(v>0.0,__FUNCTION__+": degeneracy of B is detected");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function computes fresh triangular factorization. |
|
|
//| If TRF of age 0 (fresh) is already present, no new factorization |
|
|
//| is calculated. |
|
|
//| It returns min[abs(u[i, i])] which can be used to determine |
|
|
//| whether factorization is degenerate or not (it will|
|
|
//| factorize anything, the question is whether it is |
|
|
//| possible to use factorization) |
|
|
//+------------------------------------------------------------------+
|
|
double CRevisedDualSimplex::BasisFreshTRFUnsafe(CDualSimplexBasis &s,
|
|
CSparseMatrix &at,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
int ns=s.m_ns;
|
|
double result=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int k1=0;
|
|
int nzl=0;
|
|
int nzu=0;
|
|
int nlogical=0;
|
|
int nstructural=0;
|
|
int offs=0;
|
|
int offs1=0;
|
|
int offs2=0;
|
|
//--- Compare TRF type with one required by Settings, invalidation and refresh otherwise
|
|
if(s.m_trftype!=Settings.m_trftype)
|
|
{
|
|
s.m_trftype=Settings.m_trftype;
|
|
s.m_isvalidtrf=false;
|
|
result=BasisFreshTRFUnsafe(s,at,Settings);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//--- Is it valid and fresh?
|
|
if(s.m_isvalidtrf && s.m_trfage==0)
|
|
{
|
|
result=BasisMinimumDiagonalElement(s);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//--- Dense TRF
|
|
if(s.m_trftype==0 || s.m_trftype==1)
|
|
{
|
|
s.m_colpermbwd.Resize(m);
|
|
for(i=0; i<m; i++)
|
|
s.m_colpermbwd.Set(i,i);
|
|
s.m_denselu=matrix<double>::Zeros(m,m);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
j0=at.m_RIdx[s.m_idx[i]];
|
|
j1=at.m_RIdx[s.m_idx[i]+1];
|
|
for(j=j0; j<j1; j++)
|
|
s.m_denselu.Set(i,at.m_Idx[j],at.m_Vals[j]);
|
|
}
|
|
CTrFac::RMatrixLU(s.m_denselu,m,m,s.m_tmpi);
|
|
PivotToBWD(s.m_tmpi,m,s.m_rowpermbwd);
|
|
s.m_isvalidtrf=true;
|
|
s.m_trfage=0;
|
|
s.m_statfact++;
|
|
s.m_statoffdiag+=CMath::Sqr(m-1);
|
|
result=BasisMinimumDiagonalElement(s);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//--- Sparse TRF (with either PFI or Forest-Tomlin)
|
|
if(s.m_trftype==2 || s.m_trftype==3)
|
|
{
|
|
//--- Determine permutation which moves logical variables
|
|
//--- to the beginning.
|
|
//--- NOTE: this reordering results in stable factorization
|
|
//--- because we prenormalized constraints with 2-norm,
|
|
//--- all elements in the logical columns are less than
|
|
//--- 1.0 in magnitude.
|
|
//--- After this block is done we have following arrays:
|
|
//--- * tCInvIdx[j], which is an inverse of ColPermBwf[]
|
|
s.m_tcinvidx.Resize(m);
|
|
s.m_rowpermbwd.Resize(m);
|
|
s.m_colpermbwd.Resize(m);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
s.m_tcinvidx.Set(i,i);
|
|
s.m_rowpermbwd.Set(i,i);
|
|
s.m_colpermbwd.Set(i,i);
|
|
}
|
|
nlogical=0;
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(s.m_idx[i]>=ns)
|
|
{
|
|
s.m_rowpermbwd.Swap(nlogical,i);
|
|
j1=s.m_tcinvidx[s.m_idx[i]-ns];
|
|
s.m_colpermbwd.Swap(j1,nlogical);
|
|
s.m_tcinvidx.Set(s.m_colpermbwd[nlogical],nlogical);
|
|
s.m_tcinvidx.Set(s.m_colpermbwd[j1],j1);
|
|
nlogical++;
|
|
}
|
|
}
|
|
CTSort::SortMiddleI(s.m_colpermbwd,nlogical,m-nlogical);
|
|
for(i=0; i<m; i++)
|
|
s.m_tcinvidx.Set(s.m_colpermbwd[i],i);
|
|
nstructural=m-nlogical;
|
|
//--- Prepare SparseLU1 to receive factored out logical part of the matrix
|
|
//--- and SparseLU2 to receive structural part of the matrix.
|
|
s.m_sparselu1.m_RIdx.Resize(nstructural+1);
|
|
s.m_sparselu1.m_DIdx.Resize(nstructural);
|
|
s.m_sparselu1.m_UIdx.Resize(nstructural);
|
|
s.m_sparselu1.m_MatrixType=1;
|
|
s.m_sparselu1.m_M=nstructural;
|
|
s.m_sparselu1.m_N=nlogical;
|
|
s.m_sparselu1.m_RIdx.Set(0,0);
|
|
s.m_sparselu2.m_RIdx.Resize(nstructural+1);
|
|
s.m_sparselu2.m_DIdx.Resize(nstructural);
|
|
s.m_sparselu2.m_UIdx.Resize(nstructural);
|
|
s.m_sparselu2.m_MatrixType=1;
|
|
s.m_sparselu2.m_M=nstructural;
|
|
s.m_sparselu2.m_N=nstructural;
|
|
s.m_sparselu2.m_RIdx.Set(0,0);
|
|
//--- Reorder array, perform LU factorization
|
|
for(k=0; k<nstructural; k++)
|
|
{
|
|
//--- Make sure SparseLU1 and SparseLU2 have enough place.
|
|
offs1=s.m_sparselu1.m_RIdx[k];
|
|
offs2=s.m_sparselu2.m_RIdx[k];
|
|
s.m_sparselu1.m_Idx.Resize(offs1+m);
|
|
s.m_sparselu1.m_Vals.Resize(offs1+m);
|
|
s.m_sparselu2.m_Idx.Resize(offs2+m);
|
|
s.m_sparselu2.m_Vals.Resize(offs2+m);
|
|
//--- Extract K-th row of the SparseLU1/2 (I-th row of the original matrix)
|
|
i=s.m_rowpermbwd[k+nlogical];
|
|
j0=at.m_RIdx[s.m_idx[i]];
|
|
j1=at.m_RIdx[s.m_idx[i]+1];
|
|
for(j=j0; j<j1; j++)
|
|
{
|
|
k1=s.m_tcinvidx[at.m_Idx[j]];
|
|
if(k1<nlogical)
|
|
{
|
|
//--- Append element to SparseLU1
|
|
s.m_sparselu1.m_Idx.Set(offs1,k1);
|
|
s.m_sparselu1.m_Vals.Set(offs1,at.m_Vals[j]);
|
|
offs1++;
|
|
}
|
|
else
|
|
{
|
|
//--- Append element to SparseLU2
|
|
s.m_sparselu2.m_Idx.Set(offs2,k1-nlogical);
|
|
s.m_sparselu2.m_Vals.Set(offs2,at.m_Vals[j]);
|
|
offs2++;
|
|
}
|
|
}
|
|
//--- Elements added to the last row of LU1 can be unordered,
|
|
//--- so it needs resorting.
|
|
//--- LU2 does NOT need resorting because trailing NStructural
|
|
//--- elements of permutation were post-sorted to produce
|
|
//--- already sorted results.
|
|
CTSort::TagSortMiddleIR(s.m_sparselu1.m_Idx,s.m_sparselu1.m_Vals,s.m_sparselu1.m_RIdx[k],offs1-s.m_sparselu1.m_RIdx[k]);
|
|
s.m_sparselu1.m_RIdx.Set(k+1,offs1);
|
|
s.m_sparselu2.m_RIdx.Set(k+1,offs2);
|
|
}
|
|
s.m_sparselu1.m_NInitialized=s.m_sparselu1.m_RIdx[nstructural];
|
|
s.m_sparselu2.m_NInitialized=s.m_sparselu2.m_RIdx[nstructural];
|
|
CSparse::SparseInitDUIdx(s.m_sparselu1);
|
|
CSparse::SparseInitDUIdx(s.m_sparselu2);
|
|
if(nstructural>0)
|
|
{
|
|
CSpTrf::SpTrfLU(s.m_sparselu2,2,s.m_densep2,s.m_densep2c,s.m_lubuf2);
|
|
for(i=0; i<nstructural; i++)
|
|
{
|
|
s.m_rowpermbwd.Swap(i+nlogical,s.m_densep2[i]+nlogical);
|
|
s.m_colpermbwd.Swap(i+nlogical,s.m_densep2c[i]+nlogical);
|
|
}
|
|
//--- Process L factor:
|
|
//--- 1. count number of non-zeros in the L factor,
|
|
//--- 2. fill NLogical*NLogical leading block
|
|
//--- 3. NStructural*M bottom block
|
|
nzl=nlogical;
|
|
for(i=0; i<nstructural; i++)
|
|
{
|
|
k=s.m_lubuf2.m_RowPermRawIdx[i];
|
|
nzl+=(s.m_sparselu1.m_RIdx[k+1]-s.m_sparselu1.m_RIdx[k]);
|
|
nzl+=1+(s.m_sparselu2.m_DIdx[i]-s.m_sparselu2.m_RIdx[i]);
|
|
}
|
|
s.m_sparsel.m_Vals.Resize(nzl);
|
|
s.m_sparsel.m_Idx.Resize(nzl);
|
|
s.m_sparsel.m_RIdx.Resize(m+1);
|
|
s.m_sparsel.m_DIdx.Resize(m);
|
|
s.m_sparsel.m_UIdx.Resize(m);
|
|
s.m_sparsel.m_MatrixType=1;
|
|
s.m_sparsel.m_M=m;
|
|
s.m_sparsel.m_N=m;
|
|
s.m_sparsel.m_NInitialized=nzl;
|
|
s.m_sparsel.m_RIdx.Set(0,0);
|
|
for(i=0; i<nlogical; i++)
|
|
{
|
|
s.m_sparsel.m_Idx.Set(i,i);
|
|
s.m_sparsel.m_Vals.Set(i,1.0);
|
|
s.m_sparsel.m_RIdx.Set(i+1,i+1);
|
|
}
|
|
for(i=0; i<nstructural; i++)
|
|
{
|
|
offs=s.m_sparsel.m_RIdx[nlogical+i];
|
|
k=s.m_lubuf2.m_RowPermRawIdx[i];
|
|
j0=s.m_sparselu1.m_RIdx[k];
|
|
j1=s.m_sparselu1.m_RIdx[k+1];
|
|
for(j=j0; j<j1; j++)
|
|
{
|
|
s.m_sparsel.m_Idx.Set(offs,s.m_sparselu1.m_Idx[j]);
|
|
s.m_sparsel.m_Vals.Set(offs,-s.m_sparselu1.m_Vals[j]);
|
|
offs++;
|
|
}
|
|
j0=s.m_sparselu2.m_RIdx[i];
|
|
j1=s.m_sparselu2.m_DIdx[i];
|
|
for(j=j0; j<j1; j++)
|
|
{
|
|
s.m_sparsel.m_Idx.Set(offs,nlogical+s.m_sparselu2.m_Idx[j]);
|
|
s.m_sparsel.m_Vals.Set(offs,s.m_sparselu2.m_Vals[j]);
|
|
offs++;
|
|
}
|
|
s.m_sparsel.m_Idx.Set(offs,nlogical+i);
|
|
s.m_sparsel.m_Vals.Set(offs,1.0);
|
|
offs++;
|
|
s.m_sparsel.m_RIdx.Set(nlogical+i+1,offs);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(s.m_sparsel.m_NInitialized==s.m_sparsel.m_RIdx[m],__FUNCTION__+": integrity check failed"))
|
|
return(0);
|
|
CSparse::SparseInitDUIdx(s.m_sparsel);
|
|
//--- Process U factor:
|
|
//--- 1. count number of non-zeros in the U factor,
|
|
//--- 2. fill NLogical*NLogical leading block
|
|
//--- 3. NStructural*NStructural bottom block
|
|
nzu=nlogical;
|
|
for(i=0; i<=nstructural-1; i++)
|
|
nzu+=1+(s.m_sparselu2.m_RIdx[i+1]-s.m_sparselu2.m_UIdx[i]);
|
|
s.m_sparseu.m_Vals=vector<double>::Full(nzu,-1.0);
|
|
s.m_sparseu.m_Idx.Resize(nzu);
|
|
s.m_sparseu.m_RIdx.Resize(m+1);
|
|
s.m_sparseu.m_DIdx.Resize(m);
|
|
s.m_sparseu.m_UIdx.Resize(m);
|
|
s.m_sparseu.m_MatrixType=1;
|
|
s.m_sparseu.m_M=m;
|
|
s.m_sparseu.m_N=m;
|
|
s.m_sparseu.m_NInitialized=nzu;
|
|
s.m_sparseu.m_RIdx.Set(0,0);
|
|
for(i=0; i<nlogical; i++)
|
|
{
|
|
s.m_sparseu.m_Idx.Set(i,i);
|
|
s.m_sparseu.m_RIdx.Set(i+1,i+1);
|
|
}
|
|
for(i=0; i<nstructural; i++)
|
|
{
|
|
offs=s.m_sparseu.m_RIdx[nlogical+i];
|
|
s.m_sparseu.m_Idx.Set(offs,nlogical+i);
|
|
j=s.m_sparselu2.m_DIdx[i];
|
|
if(j<s.m_sparselu2.m_UIdx[i])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_sparselu2.m_Idx[j]==i,__FUNCTION__+": integrity check failed"))
|
|
return(0);
|
|
s.m_sparseu.m_Vals.Set(offs,s.m_sparselu2.m_Vals[j]);
|
|
}
|
|
else
|
|
{
|
|
s.m_sparseu.m_Vals.Set(offs,0);
|
|
}
|
|
offs++;
|
|
j0=s.m_sparselu2.m_UIdx[i];
|
|
j1=s.m_sparselu2.m_RIdx[i+1];
|
|
for(j=j0; j<j1; j++)
|
|
{
|
|
s.m_sparseu.m_Idx.Set(offs,nlogical+s.m_sparselu2.m_Idx[j]);
|
|
s.m_sparseu.m_Vals.Set(offs,s.m_sparselu2.m_Vals[j]);
|
|
offs++;
|
|
}
|
|
s.m_sparseu.m_RIdx.Set(nlogical+i+1,offs);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(s.m_sparseu.m_NInitialized==s.m_sparseu.m_RIdx[m],__FUNCTION__+": integrity check failed"))
|
|
return(0);
|
|
CSparse::SparseInitDUIdx(s.m_sparseu);
|
|
}
|
|
else
|
|
{
|
|
s.m_nrs.Resize(m);
|
|
for(i=0; i<m; i++)
|
|
s.m_nrs.Set(i,1);
|
|
CSparse::SparseCreateCRSBuf(m,m,s.m_nrs,s.m_sparsel);
|
|
for(i=0; i<nlogical; i++)
|
|
CSparse::SparseSet(s.m_sparsel,i,i,1.0);
|
|
CSparse::SparseCreateCRSBuf(m,m,s.m_nrs,s.m_sparseu);
|
|
for(i=0; i<nlogical; i++)
|
|
CSparse::SparseSet(s.m_sparseu,i,i,-1.0);
|
|
}
|
|
CSparse::SparseCopyTransposeCRSBuf(s.m_sparseu,s.m_sparseut);
|
|
s.m_isvalidtrf=true;
|
|
s.m_trfage=0;
|
|
s.m_statfact=s.m_statfact+1;
|
|
s.m_statoffdiag+=(s.m_sparsel.m_RIdx[m]-m)+(s.m_sparseu.m_RIdx[m]-m);
|
|
result=BasisMinimumDiagonalElement(s);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//---unexpected TRF type
|
|
CAp::Assert(false,__FUNCTION__+": unexpected TRF type");
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function fills S.DSEWeights by actual weights according to |
|
|
//| current Settings and sets validity flag. |
|
|
//| Basis object MUST store valid triangular factorization, otherwise|
|
|
//| this function throws an exception. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::BasisRequestWeights(CDualSimplexBasis &s,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
int ns=s.m_ns;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
//--- check
|
|
if(!CAp::Assert(Settings.m_pricing==-1 || Settings.m_pricing==0 || Settings.m_pricing==1,__FUNCTION__+": unknown pricing type"))
|
|
return;
|
|
if(!CAp::Assert(s.m_isvalidtrf,__FUNCTION__+": factorization is not computed prior to calling this function"))
|
|
return;
|
|
//--- If weights are valid, return immediately
|
|
if(s.m_dsevalid)
|
|
return;
|
|
//--- Compute weights from scratch
|
|
if(Settings.m_pricing==-1 || Settings.m_pricing==1)
|
|
{
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(s.m_idx[i]<ns)
|
|
{
|
|
//--- Structural variable, DSE weight is computed by definition
|
|
s.m_wtmp0=vector<double>::Zeros(m);
|
|
s.m_wtmp1.Resize(m);
|
|
s.m_wtmp0.Set(i,1);
|
|
BasisSolveT(s,s.m_wtmp0,s.m_wtmp1,s.m_wtmp2);
|
|
v=s.m_wtmp1.Dot(s.m_wtmp1);
|
|
s.m_dseweights.Set(i,v);
|
|
}
|
|
else
|
|
{
|
|
//--- Logical variable, weight can be set to 1.0
|
|
s.m_dseweights.Set(i,1.0);
|
|
}
|
|
}
|
|
s.m_dsevalid=true;
|
|
return;
|
|
}
|
|
//--- Compute weights from scratch
|
|
if(Settings.m_pricing==0)
|
|
{
|
|
s.m_dseweights=vector<double>::Ones(m);
|
|
s.m_dsevalid=true;
|
|
}
|
|
else
|
|
CAp::Assert(false,__FUNCTION__+": unexpected pricing type");
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| This function updates triangular factorization by adding Q to |
|
|
//| basis and removing P from basis. It also updates index tables |
|
|
//| IsBasic[], BasicIdx[], Basis.NIdx[]. |
|
|
//| AlphaQim contains intermediate result from Ftran for AlphaQ, it |
|
|
//| is used by Forest - Tomlin update scheme. If other update is |
|
|
//| used, it is not referenced at all. |
|
|
//| X[], D[], Z are NOT recomputed. |
|
|
//| Tau is used if Settings.Pricing = 1, ignored otherwise. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::BasisUpdateTrf(CDualSimplexBasis &s,
|
|
CSparseMatrix &at,
|
|
int p,
|
|
int q,
|
|
CRowDouble &alphaq,
|
|
CRowDouble &alphaqim,
|
|
int r,
|
|
CRowDouble &tau,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
int nn=s.m_ns;
|
|
int i=0;
|
|
int j=0;
|
|
bool processed=false;
|
|
double invaq=0;
|
|
int dstoffs=0;
|
|
int srcoffs=0;
|
|
int srcidx=0;
|
|
double srcval=0;
|
|
double vcorner=0;
|
|
int idxd=0;
|
|
double v=0;
|
|
//--- Update index tables
|
|
s.m_isbasic[p]=false;
|
|
s.m_isbasic[q]=true;
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(s.m_idx[i]==p)
|
|
{
|
|
s.m_idx.Set(i,q);
|
|
break;
|
|
}
|
|
}
|
|
for(i=0; i<nn; i++)
|
|
{
|
|
if(s.m_nidx[i]==q)
|
|
{
|
|
s.m_nidx.Set(i,p);
|
|
break;
|
|
}
|
|
}
|
|
//--- Update dense factorization
|
|
if(s.m_trftype!=Settings.m_trftype || s.m_trftype==0 || !s.m_isvalidtrf || s.m_trfage>=Settings.m_maxtrfage)
|
|
{
|
|
//--- Complete refresh is needed for factorization
|
|
s.m_isvalidtrf=false;
|
|
BasisFreshTrf(s,at,Settings);
|
|
}
|
|
else
|
|
{
|
|
processed=false;
|
|
if(s.m_trftype==0 || s.m_trftype==1 || s.m_trftype==2)
|
|
{
|
|
//--- Dense/sparse factorizations with dense PFI
|
|
//--- check
|
|
if(!CAp::Assert(alphaq[r]!=0.0,__FUNCTION__+": integrity check failed,AlphaQ[R]=0"))
|
|
return;
|
|
s.m_densepfieta.Resize((s.m_trfage+1)*m);
|
|
s.m_rk.Resize(s.m_trfage+1);
|
|
s.m_rk.Set(s.m_trfage,r);
|
|
invaq=1.0/alphaq[r];
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(i!=r)
|
|
s.m_densepfieta.Set(s.m_trfage*m+i,-(alphaq[i]*invaq));
|
|
else
|
|
s.m_densepfieta.Set(s.m_trfage*m+i,invaq);
|
|
}
|
|
s.m_trfage++;
|
|
s.m_statupdt++;
|
|
s.m_statoffdiag+=CMath::Sqr(m-1);
|
|
processed=true;
|
|
}
|
|
if(s.m_trftype==3)
|
|
{
|
|
//--- Sparse factorization with Forest-Tomlin update
|
|
//--- check
|
|
if(!CAp::Assert(alphaq[r]!=0.0,__FUNCTION__+": integrity check failed,AlphaQ[R]=0"))
|
|
return;
|
|
s.m_densemu.Resize((s.m_trfage+1)*m);
|
|
s.m_rk.Resize(s.m_trfage+1);
|
|
s.m_dk.Resize(s.m_trfage+1);
|
|
s.m_utmp0=vector<double>::Zeros(m);
|
|
//--- Determine D - index of row being overwritten by Forest-Tomlin update
|
|
idxd=-1;
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(s.m_rowpermbwd[i]==r)
|
|
{
|
|
idxd=i;
|
|
break;
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(idxd>=0,__FUNCTION__+": unexpected integrity check failure"))
|
|
return;
|
|
s.m_rk.Set(s.m_trfage,r);
|
|
s.m_dk.Set(s.m_trfage,idxd);
|
|
//--- Modify L with permutation which moves D-th row/column to the end:
|
|
//--- * rows 0...D-1 are left intact
|
|
//--- * rows D+1...M-1 are moved one position up, with columns 0..D-1
|
|
//--- retained as is, and columns D+1...M-1 being moved one position left.
|
|
//--- * last row is filled by permutation/modification of AlphaQim
|
|
//--- Determine FT update coefficients in the process.
|
|
s.m_sparsel.m_Idx.Resize(s.m_sparsel.m_RIdx[m]+m);
|
|
s.m_sparsel.m_Vals.Resize(s.m_sparsel.m_RIdx[m]+m);
|
|
for(i=idxd+1; i<m; i++)
|
|
{
|
|
j=s.m_sparsel.m_RIdx[i+1]-1;
|
|
if(s.m_sparsel.m_Idx[j]!=i || s.m_sparsel.m_Vals[j]!=1)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(false,__FUNCTION__+": integrity check failed for sparse L"))
|
|
return;
|
|
}
|
|
dstoffs=s.m_sparsel.m_RIdx[i-1];
|
|
srcoffs=s.m_sparsel.m_RIdx[i];
|
|
//--- Read first element in the row (it has at least one - unit diagonal)
|
|
srcidx=s.m_sparsel.m_Idx[srcoffs];
|
|
srcval=s.m_sparsel.m_Vals[srcoffs];
|
|
//--- Read/write columns 0...D-1
|
|
while(srcidx<idxd)
|
|
{
|
|
s.m_sparsel.m_Idx.Set(dstoffs,srcidx);
|
|
s.m_sparsel.m_Vals.Set(dstoffs,srcval);
|
|
dstoffs++;
|
|
srcoffs++;
|
|
srcidx=s.m_sparsel.m_Idx[srcoffs];
|
|
srcval=s.m_sparsel.m_Vals[srcoffs];
|
|
}
|
|
//--- If we have non-zero element in column D, use it as
|
|
//--- right-hand side of intermediate linear system which
|
|
//--- is used to determine coefficients of update matrix.
|
|
if(srcidx==idxd)
|
|
{
|
|
s.m_utmp0.Set(i-1,srcval);
|
|
srcoffs++;
|
|
srcidx=s.m_sparsel.m_Idx[srcoffs];
|
|
srcval=s.m_sparsel.m_Vals[srcoffs];
|
|
}
|
|
//--- Process columns D+1...I-1
|
|
v=s.m_utmp0[i-1];
|
|
while(srcidx<i)
|
|
{
|
|
s.m_sparsel.m_Idx.Set(dstoffs,srcidx-1);
|
|
s.m_sparsel.m_Vals.Set(dstoffs,srcval);
|
|
v-=srcval*s.m_utmp0[srcidx-1];
|
|
dstoffs++;
|
|
srcoffs++;
|
|
srcidx=s.m_sparsel.m_Idx[srcoffs];
|
|
srcval=s.m_sparsel.m_Vals[srcoffs];
|
|
}
|
|
s.m_utmp0.Set(i-1,v);
|
|
//--- Write out unit diagonal, finalize row
|
|
s.m_sparsel.m_Idx.Set(dstoffs,i-1);
|
|
s.m_sparsel.m_Vals.Set(dstoffs,1);
|
|
dstoffs++;
|
|
s.m_sparsel.m_RIdx.Set(i,dstoffs);
|
|
}
|
|
s.m_utmp0.Set(m-1,1);
|
|
dstoffs=s.m_sparsel.m_RIdx[m-1];
|
|
for(j=0; j<=idxd-1; j++)
|
|
{
|
|
v=alphaqim[j];
|
|
if(v!=0)
|
|
{
|
|
s.m_sparsel.m_Idx.Set(dstoffs,j);
|
|
s.m_sparsel.m_Vals.Set(dstoffs,v);
|
|
dstoffs++;
|
|
}
|
|
}
|
|
vcorner=alphaqim[idxd];
|
|
for(j=idxd+1; j<m; j++)
|
|
{
|
|
v=alphaqim[j];
|
|
if(v!=0)
|
|
{
|
|
s.m_sparsel.m_Idx.Set(dstoffs,j-1);
|
|
s.m_sparsel.m_Vals.Set(dstoffs,v);
|
|
dstoffs++;
|
|
vcorner-=v*s.m_utmp0[j-1];
|
|
}
|
|
}
|
|
s.m_sparsel.m_Idx.Set(dstoffs,m-1);
|
|
s.m_sparsel.m_Vals.Set(dstoffs,1);
|
|
dstoffs++;
|
|
s.m_sparsel.m_RIdx.Set(m,dstoffs);
|
|
s.m_sparsel.m_NInitialized=s.m_sparsel.m_RIdx[m];
|
|
for(i=0; i<m; i++)
|
|
{
|
|
j=s.m_sparsel.m_RIdx[i+1];
|
|
s.m_sparsel.m_DIdx.Set(i,j-1);
|
|
s.m_sparsel.m_UIdx.Set(i,j);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(vcorner!=0,__FUNCTION__+": corner element is zero,degeneracy detected"))
|
|
return;
|
|
v=1/vcorner;
|
|
for(i=0; i<m-1; i++)
|
|
s.m_densemu.Set(s.m_trfage*m+i,-(s.m_utmp0[i]*v));
|
|
s.m_densemu.Set(s.m_trfage*m+m-1,v);
|
|
//--- Multiply row permutation matrix by cyclic permutation applied to L
|
|
InverseCyclicPermutation(s.m_rowpermbwd,m,idxd,s.m_utmpi);
|
|
//--- Done
|
|
s.m_trfage++;
|
|
s.m_statupdt++;
|
|
s.m_statoffdiag+=(s.m_sparsel.m_RIdx[m]-m)+(s.m_sparseu.m_RIdx[m]-m);
|
|
processed=true;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(processed,__FUNCTION__+": unexpected TRF type"))
|
|
return;
|
|
}
|
|
//--- Update pricing weights
|
|
//--- check
|
|
if(!CAp::Assert(Settings.m_pricing==-1 || Settings.m_pricing==0 || Settings.m_pricing==1,__FUNCTION__+": unexpected Settings.Pricing"))
|
|
return;
|
|
processed=false;
|
|
switch(Settings.m_pricing)
|
|
{
|
|
case -1:
|
|
//--- Weights are recomputed from scratch at every step.
|
|
//--- VERY, VERY time consuming, used only for debug purposes.
|
|
s.m_dsevalid=false;
|
|
BasisRequestWeights(s,Settings);
|
|
processed=true;
|
|
break;
|
|
case 0:
|
|
//--- Weights are filled by 1.0
|
|
if(!s.m_dsevalid)
|
|
{
|
|
s.m_dseweights.Fill(1.0);
|
|
s.m_dsevalid=true;
|
|
}
|
|
processed=true;
|
|
break;
|
|
case 1:
|
|
//--- Weights are computed using DSE update formula.
|
|
if(s.m_dsevalid)
|
|
{
|
|
//--- Compute using update formula
|
|
for(i=0; i<m; i++)
|
|
if(i!=r)
|
|
{
|
|
s.m_dseweights.Add(i,-2*(alphaq[i]/alphaq[r])*tau[i]+s.m_dseweights[r]*CMath::Sqr(alphaq[i]/alphaq[r]));
|
|
s.m_dseweights.Set(i,MathMax(s.m_dseweights[i],m_minbeta));
|
|
}
|
|
s.m_dseweights.Mul(r,1.0/CMath::Sqr(alphaq[r]));
|
|
}
|
|
else
|
|
{
|
|
//--- No prior values, compute from scratch (usually it is done only once)
|
|
BasisRequestWeights(s,Settings);
|
|
}
|
|
processed=true;
|
|
break;
|
|
}
|
|
CAp::Assert(processed,__FUNCTION__+": unexpected pricing type");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function computes solution to B*x = r. |
|
|
//| Output array is reallocated if needed. Temporary array TmpX[] is |
|
|
//| used and reallocated if necessary. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::BasisSolve(CDualSimplexBasis &s,
|
|
CRowDouble &r,
|
|
CRowDouble &x,
|
|
CRowDouble &tmpx)
|
|
{
|
|
BasisSolveX(s,r,x,x,false,tmpx);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function computes solution to B*x = r. It also additionally |
|
|
//| outputs intermediate result of multiplication by |
|
|
//| inv(DS) * inv(U) * inv(colPerm), a value essential for |
|
|
//| Forest - Tomlin update. |
|
|
//| Output arrays are reallocated if needed. Temporary array TX[] can|
|
|
//| be used / reallocated. |
|
|
//| If NeedIntermediate is False or Forest - Tomlin updates are not |
|
|
//| used, then Xim[] is not referenced at all. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::BasisSolveX(CDualSimplexBasis &s,
|
|
CRowDouble &r,
|
|
CRowDouble &x,
|
|
CRowDouble &xim,
|
|
bool needintermediate,
|
|
CRowDouble &tx)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
int i=0;
|
|
int d=0;
|
|
int k=0;
|
|
double v=0;
|
|
double vd=0;
|
|
double vv=0;
|
|
bool processed=false;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_isvalidtrf,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
|
|
tx.Resize(m);
|
|
//--- Dense/sparse factorizations with dense PFI
|
|
//--- NOTE: although we solve B*x=r, internally we store factorization of B^T
|
|
if((s.m_trftype==0 || s.m_trftype==1) || s.m_trftype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_trfage==0 || s.m_trftype!=0,__FUNCTION__+": integrity check failed TrfAge vs TrfType"))
|
|
return;
|
|
x.Resize(m);
|
|
for(i=0; i<m; i++)
|
|
x.Set(i,r[s.m_colpermbwd[i]]);
|
|
if(s.m_trftype==0 || s.m_trftype==1)
|
|
{
|
|
//--- Dense TRF
|
|
CAblas::RMatrixTrsVect(m,s.m_denselu,0,0,true,false,1,x,0);
|
|
CAblas::RMatrixTrsVect(m,s.m_denselu,0,0,false,true,1,x,0);
|
|
}
|
|
else
|
|
{
|
|
//--- Sparse TRF
|
|
CSparse::SparseTRSV(s.m_sparseu,true,false,1,x);
|
|
CSparse::SparseTRSV(s.m_sparsel,false,false,1,x);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
tx.Set(s.m_rowpermbwd[i],x[i]);
|
|
x=tx;
|
|
for(k=0; k<s.m_trfage; k++)
|
|
{
|
|
v=x[s.m_rk[k]];
|
|
for(i=0; i<m; i++)
|
|
x.Add(i,s.m_densepfieta[k*m+i]*v);
|
|
x.Add(s.m_rk[k],-v);
|
|
}
|
|
processed=true;
|
|
}
|
|
//--- Sparse factorization with Forest-Tomlin update
|
|
//--- NOTE: although we solve B*x=r, internally we store factorization of B^T
|
|
if(s.m_trftype==3)
|
|
{
|
|
x.Resize(m);
|
|
for(i=0; i<m; i++)
|
|
x.Set(i,r[s.m_colpermbwd[i]]);
|
|
CSparse::SparseTRSV(s.m_sparseu,true,false,1,x);
|
|
for(k=0; k<s.m_trfage; k++)
|
|
{
|
|
//--- The code below is an amalgamation of two parts:
|
|
//--- cyclic permutation
|
|
//--- V:=X[D];
|
|
//--- for I:=D to M-2 do
|
|
//--- X[I]:=X[I+1];
|
|
//--- X[M-1]:=V;
|
|
//--- and triangular factor
|
|
//--- V:=0;
|
|
//--- for I:=D to M-1 do
|
|
//--- V:=V+X[I]*S.DenseMu[K*M+I];
|
|
//--- X[M-1]:=V;
|
|
d=s.m_dk[k];
|
|
vv=0;
|
|
vd=x[d];
|
|
for(i=d; i<m-1; i++)
|
|
{
|
|
v=x[i+1];
|
|
x.Set(i,v);
|
|
vv+=v*s.m_densemu[k*m+i];
|
|
}
|
|
x.Set(m-1,vv+vd*s.m_densemu[k*m+m-1]);
|
|
}
|
|
if(needintermediate)
|
|
xim=x;
|
|
CSparse::SparseTRSV(s.m_sparsel,false,false,1,x);
|
|
for(i=0; i<m; i++)
|
|
tx.Set(s.m_rowpermbwd[i],x[i]);
|
|
x=tx;
|
|
processed=true;
|
|
}
|
|
//--- Integrity check
|
|
if(!CAp::Assert(processed,__FUNCTION__+": unsupported TRF type"))
|
|
return;
|
|
v=x.Sum();
|
|
CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": integrity check failed (degeneracy in B?)");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function computes solution to(B^T)*x = r. |
|
|
//| Output array is reallocated if needed. TX[] temporary is |
|
|
//| reallocated if needed |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::BasisSolveT(CDualSimplexBasis &s,
|
|
CRowDouble &r,
|
|
CRowDouble &x,
|
|
CRowDouble &tx)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
int i=0;
|
|
int d=0;
|
|
int k=0;
|
|
double v=0;
|
|
double vm=0;
|
|
bool processed=false;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_isvalidtrf,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
|
|
tx.Resize(m);
|
|
//--- Dense factorizations
|
|
if(s.m_trftype==0 || s.m_trftype==1 || s.m_trftype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_trfage==0 || s.m_trftype!=0,__FUNCTION__+": integrity check failed TrfAge vs TrfType"))
|
|
return;
|
|
x=r;
|
|
x.Resize(m);
|
|
for(k=s.m_trfage-1; k>=0; k--)
|
|
{
|
|
v=0;
|
|
for(i=0; i<m; i++)
|
|
v+=s.m_densepfieta[k*m+i]*x[i];
|
|
x.Set(s.m_rk[k],v);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
tx.Set(i,x[s.m_rowpermbwd[i]]);
|
|
x=tx;
|
|
if(s.m_trftype==0 || s.m_trftype==1)
|
|
{
|
|
//--- Dense TRF
|
|
CAblas::RMatrixTrsVect(m,s.m_denselu,0,0,false,true,0,x,0);
|
|
CAblas::RMatrixTrsVect(m,s.m_denselu,0,0,true,false,0,x,0);
|
|
}
|
|
else
|
|
{
|
|
//--- Sparse TRF
|
|
CSparse::SparseTRSV(s.m_sparsel,false,false,0,x);
|
|
CSparse::SparseTRSV(s.m_sparseu,true,false,0,x);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
tx.Set(s.m_colpermbwd[i],x[i]);
|
|
x=tx;
|
|
processed=true;
|
|
}
|
|
//--- Sparse factorization with Forest-Tomlin update
|
|
if(s.m_trftype==3)
|
|
{
|
|
x=r;
|
|
x.Resize(m);
|
|
for(i=0; i<m; i++)
|
|
tx.Set(i,x[s.m_rowpermbwd[i]]);
|
|
x=tx;
|
|
CSparse::SparseTRSV(s.m_sparsel,false,false,0,x);
|
|
for(k=s.m_trfage-1; k>=0; k--)
|
|
{
|
|
//--- The code below is an amalgamation of two parts:
|
|
//--- triangular factor
|
|
//--- V:=X[M-1];
|
|
//--- for I:=D to M-2 do
|
|
//--- X[I]:=X[I]+S.DenseMu[K*M+I]*V;
|
|
//--- X[M-1]:=S.DenseMu[K*M+(M-1)]*V;
|
|
//--- inverse of cyclic permutation
|
|
//--- V:=X[M-1];
|
|
//--- for I:=M-1 downto D+1 do
|
|
//--- X[I]:=X[I-1];
|
|
//--- X[D]:=V;
|
|
d=s.m_dk[k];
|
|
vm=x[m-1];
|
|
v=s.m_densemu[k*m+(m-1)]*vm;
|
|
if(vm!=0)
|
|
{
|
|
//--- X[M-1] is non-zero, apply update
|
|
for(i=m-2; i>=d; i--)
|
|
x.Set(i+1,x[i]+s.m_densemu[k*m+i]*vm);
|
|
}
|
|
else
|
|
{
|
|
//--- X[M-1] is zero, just cyclic permutation
|
|
for(i=m-2; i>=d; i--)
|
|
x.Set(i+1,x[i]);
|
|
}
|
|
x.Set(d,v);
|
|
}
|
|
CSparse::SparseTRSV(s.m_sparseut,false,false,1,x);
|
|
for(i=0; i<m; i++)
|
|
tx.Set(s.m_colpermbwd[i],x[i]);
|
|
x=tx;
|
|
processed=true;
|
|
}
|
|
//--- Integrity check
|
|
if(!CAp::Assert(processed,__FUNCTION__+": unsupported TRF type"))
|
|
return;
|
|
v=x.Sum();
|
|
CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": integrity check failed (degeneracy in B?)");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function computes product AN*XN, where AN is a non-basic |
|
|
//| subset of columns of A, and XN is a non - basic subset of columns|
|
|
//| of X. |
|
|
//| Output array is reallocated if its size is too small. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::ComputeAnXn(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &subproblem,
|
|
CRowDouble &x,
|
|
CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int nx=subproblem.m_ns+subproblem.m_m;
|
|
int m=subproblem.m_m;
|
|
int nn=nx-m;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double v=0;
|
|
//--- Integrity check
|
|
if(!CAp::Assert(subproblem.m_state>=m_ssvalidxn,"ComputeANXN: XN is invalid"))
|
|
return;
|
|
//--- Compute
|
|
y=vector<double>::Zeros(m);
|
|
for(i=0; i<nn; i++)
|
|
{
|
|
j0=State.m_at.m_RIdx[State.m_basis.m_nidx[i]];
|
|
j1=State.m_at.m_RIdx[State.m_basis.m_nidx[i]+1];
|
|
v=x[State.m_basis.m_nidx[i]];
|
|
for(j=j0; j<j1; j++)
|
|
{
|
|
k=State.m_at.m_Idx[j];
|
|
y.Add(k,v*State.m_at.m_Vals[j]);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function computes product(AN^T)*y, where AN is a non-basic |
|
|
//| subset of columns of A, and y is some vector. |
|
|
//| Output array is set to full NX - sized length, with basic |
|
|
//| components of the output being set to zeros. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::ComputeAnTV(CDualSimplexState &State,
|
|
CRowDouble &y,
|
|
CRowDouble &r)
|
|
{
|
|
//--- create variables
|
|
int nx=State.m_ns+State.m_m;
|
|
int m=State.m_m;
|
|
int nn=nx-m;
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double v=0;
|
|
//--- Allocate output, set to zero
|
|
r=vector<double>::Zeros(nx);
|
|
for(i=0; i<nn; i++)
|
|
{
|
|
j0=State.m_at.m_RIdx[State.m_basis.m_nidx[i]];
|
|
j1=State.m_at.m_RIdx[State.m_basis.m_nidx[i]+1];
|
|
v=0;
|
|
for(j=j0; j<j1; j++)
|
|
v+=State.m_at.m_Vals[j]*y[State.m_at.m_Idx[j]];
|
|
r.Set(State.m_basis.m_nidx[i],v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns True if I-th lower bound is present |
|
|
//+------------------------------------------------------------------+
|
|
bool CRevisedDualSimplex::HasBndL(CDualSimplexSubproblem &subproblem,
|
|
int i)
|
|
{
|
|
int k=subproblem.m_bndt[i];
|
|
//--- check
|
|
if(k==0 || k==1 || k==3)
|
|
return(true);
|
|
if(k==2 || k==4)
|
|
return(false);
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns True if I-th upper bound is present |
|
|
//+------------------------------------------------------------------+
|
|
bool CRevisedDualSimplex::HasBndU(CDualSimplexSubproblem &subproblem,
|
|
int i)
|
|
{
|
|
int k=subproblem.m_bndt[i];
|
|
//--- check
|
|
if(k==0 || k==2 || k==3)
|
|
return(true);
|
|
if(k==1 || k==4)
|
|
return(false);
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns True if I-th variable if free |
|
|
//+------------------------------------------------------------------+
|
|
bool CRevisedDualSimplex::IsFree(CDualSimplexSubproblem &subproblem,
|
|
int i)
|
|
{
|
|
int k=subproblem.m_bndt[i];
|
|
//--- check
|
|
if(k==0 || k==1 || k==2 || k==3)
|
|
return(false);
|
|
if(k==4)
|
|
return(true);
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Downgrades problem State to the specified one(if status is lower |
|
|
//| than one specified by user, nothing is changed) |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DowngradeState(CDualSimplexSubproblem &subproblem,
|
|
int s)
|
|
{
|
|
subproblem.m_state=MathMin(subproblem.m_state,s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns maximum dual infeasibility(only non - basic variables are|
|
|
//| checked, we assume that basic variables are good enough). |
|
|
//+------------------------------------------------------------------+
|
|
double CRevisedDualSimplex::DualFeasibilityError(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s)
|
|
{
|
|
//--- create variables
|
|
int nn=s.m_ns;
|
|
double result=0;
|
|
int i=0;
|
|
int j=0;
|
|
int bndt=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_state==m_ssvalid,__FUNCTION__+": invalid X"))
|
|
return(0);
|
|
|
|
for(i=0; i<nn; i++)
|
|
{
|
|
j=State.m_basis.m_nidx[i];
|
|
bndt=s.m_bndt[j];
|
|
if(bndt==m_ccfixed)
|
|
continue;
|
|
if(bndt==m_ccrange)
|
|
{
|
|
if(s.m_xa[j]==s.m_bndl[j])
|
|
{
|
|
result=MathMax(result,-s.m_d[j]);
|
|
continue;
|
|
}
|
|
if(s.m_xa[j]==s.m_bndu[j])
|
|
{
|
|
result=MathMax(result,s.m_d[j]);
|
|
continue;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
return(0);
|
|
}
|
|
if(bndt==m_cclower)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_xa[j]==s.m_bndl[j],__FUNCTION__+": integrity check failed"))
|
|
return(0);
|
|
result=MathMax(result,-s.m_d[j]);
|
|
continue;
|
|
}
|
|
if(bndt==m_ccupper)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_xa[j]==s.m_bndu[j],__FUNCTION__+": integrity check failed"))
|
|
return(0);
|
|
result=MathMax(result,s.m_d[j]);
|
|
continue;
|
|
}
|
|
if(bndt==m_ccfree)
|
|
{
|
|
result=MathMax(result,MathAbs(s.m_d[j]));
|
|
continue;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed (infeasible constraint)");
|
|
return(0);
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns True for dual feasible basis(some minor dual feasibility |
|
|
//| error is allowed), False otherwise |
|
|
//+------------------------------------------------------------------+
|
|
bool CRevisedDualSimplex::IsDualFeasible(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
return (DualFeasibilityError(State,s)<=Settings.m_dtolabs);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Transforms sequence of pivot permutations P0*P1*...*Pm to |
|
|
//| forward / backward permutation representation. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::PivotToBWD(CRowInt &p,
|
|
int m,
|
|
CRowInt &bwd)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int k=0;
|
|
int t=0;
|
|
|
|
bwd.Resize(m);
|
|
|
|
for(i=0; i<m; i++)
|
|
bwd.Set(i,i);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
k=p[i];
|
|
if(k!=i)
|
|
bwd.Swap(i,k);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Applies inverse cyclic permutation of [D, M - 1) (element D is |
|
|
//| moved to the end, the rest of elements is shifted one position |
|
|
//| backward) to the already existing permutation. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::InverseCyclicPermutation(CRowInt &bwd,
|
|
int m,
|
|
int d,
|
|
CRowInt &tmpi)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int k=0;
|
|
//--- update Bwd[]
|
|
k=bwd[d];
|
|
for(i=d; i<m-1; i++)
|
|
bwd.Set(i,bwd[i+1]);
|
|
bwd.Set(m-1,k);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Offloads basic components of X[], BndT[], BndL[], BndU[] to |
|
|
//| XB / BndTB / BndLB / BndUB. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::OffloadBasicComponents(CDualSimplexSubproblem &s,
|
|
CDualSimplexBasis &basis,
|
|
CDualSimplexSettings &Settings)
|
|
{
|
|
int m=basis.m_m;
|
|
|
|
for(int i=0; i<m; i++)
|
|
{
|
|
s.m_xb.Set(i,s.m_xa[basis.m_idx[i]]);
|
|
CacheBoundInfo(s,i,basis.m_idx[i],Settings);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recombines basic and non - basic components in X[] |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::RecombineBasicNonBasicX(CDualSimplexSubproblem &s,
|
|
CDualSimplexBasis &basis)
|
|
{
|
|
int m=basis.m_m;
|
|
//--- copy
|
|
for(int i=0; i<m; i++)
|
|
s.m_xa.Set(basis.m_idx[i],s.m_xb[i]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes Stats array |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - problem, contains current solution at S.XA |
|
|
//| Basis - basis |
|
|
//| X - possibly preallocated output buffer |
|
|
//| LagBC - possibly preallocated output buffer |
|
|
//| LagLC - possibly preallocated output buffer |
|
|
//| Stats - possibly preallocated output buffer |
|
|
//| Buffers - temporary buffers |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[NS], solution |
|
|
//| LagBC - array[NS], Lagrange multipliers for box constraints|
|
|
//| LagLC - array[M], Lagrange multipliers for linear |
|
|
//| constraints |
|
|
//| Stats - array[NS + M], primary / slack variable stats: |
|
|
//| * -1 = variable at lower bound |
|
|
//| * +1 = variable at upper bound |
|
|
//| * 0 = basic or free (possibly nonbasic) |
|
|
//| variable fixed variables may be set |
|
|
//| to + 1 or - 1 |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::SetXYDStats(CDualSimplexState &State,
|
|
CDualSimplexSubproblem &s,
|
|
CDualSimplexBasis &basis,
|
|
CApBuff &buffers,
|
|
CRowDouble &x,
|
|
CRowDouble &lagbc,
|
|
CRowDouble &laglc,
|
|
CRowInt &stats)
|
|
{
|
|
//--- create variables
|
|
int m=s.m_m;
|
|
int ns=s.m_ns;
|
|
int nx=s.m_ns+s.m_m;
|
|
int i=0;
|
|
int j=0;
|
|
//--- Prepare
|
|
x.Resize(ns);
|
|
laglc.Resize(m);
|
|
stats.Resize(nx);
|
|
lagbc=vector<double>::Zeros(ns);
|
|
//--- Compute Y (in Buffers.RA1) and D (in Buffers.RA3)
|
|
buffers.m_ra0.Resize(m);
|
|
buffers.m_ra1.Resize(m);
|
|
buffers.m_ra3.Resize(nx);
|
|
for(i=0; i<m; i++)
|
|
buffers.m_ra0.Set(i,s.m_rawc[basis.m_idx[i]]);
|
|
BasisSolveT(basis,buffers.m_ra0,buffers.m_ra1,buffers.m_ra2);
|
|
ComputeAnTV(State,buffers.m_ra1,buffers.m_ra3);
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
j=State.m_basis.m_nidx[i];
|
|
buffers.m_ra3.Set(j,State.m_primary.m_rawc[j]-buffers.m_ra3[j]);
|
|
if(j<ns)
|
|
lagbc.Set(j,-buffers.m_ra3[j]);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
buffers.m_ra3.Set(State.m_basis.m_idx[i],0);
|
|
//--- Compute X, Y, Stats
|
|
x=s.m_xa;
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
if(MathIsValidNumber(State.m_rawbndl[i]))
|
|
x.Set(i,MathMax(x[i],State.m_rawbndl[i]));
|
|
if(MathIsValidNumber(State.m_rawbndu[i]))
|
|
x.Set(i,MathMin(x[i],State.m_rawbndu[i]));
|
|
}
|
|
for(i=0; i<ns; i++)
|
|
{
|
|
if(basis.m_isbasic[i])
|
|
{
|
|
lagbc.Set(i,0);
|
|
continue;
|
|
}
|
|
if(s.m_bndt[i]==m_ccfixed)
|
|
continue;
|
|
if(HasBndL(s,i) && s.m_xa[i]==s.m_bndl[i])
|
|
{
|
|
lagbc.Set(i,MathMin(lagbc[i],0.0));
|
|
continue;
|
|
}
|
|
if(HasBndU(s,i) && s.m_xa[i]==s.m_bndu[i])
|
|
{
|
|
lagbc.Set(i,MathMax(lagbc[i],0.0));
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(!HasBndL(s,i) && !HasBndU(s,i),__FUNCTION__+": integrity check failed (zetta5)"))
|
|
return;
|
|
lagbc.Set(i,0);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
laglc.Set(i,-(buffers.m_ra1[i]/State.m_rowscales[i]));
|
|
for(i=0; i<nx; i++)
|
|
{
|
|
if(basis.m_isbasic[i])
|
|
{
|
|
stats.Set(i,0);
|
|
continue;
|
|
}
|
|
if(HasBndL(s,i) && s.m_xa[i]==s.m_bndl[i])
|
|
{
|
|
stats.Set(i,-1);
|
|
continue;
|
|
}
|
|
if(HasBndU(s,i) && s.m_xa[i]==s.m_bndu[i])
|
|
{
|
|
stats.Set(i,1);
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(!HasBndL(s,i) && !HasBndU(s,i),__FUNCTION__+": integrity check failed (zetta5)"))
|
|
return;
|
|
stats.Set(i,0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initializes vector, sets all internal arrays to length N (so that|
|
|
//| we may store any vector without reallocation). Previously |
|
|
//| allocated memory is reused as much as possible. |
|
|
//| No zero - filling is performed, X.K is undefined. Only X.N is set|
|
|
//| INPUT PARAMETERS: |
|
|
//| X - temporary buffers |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - preallocated vector, X.N = N, contents undefined|
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DVAlloc(CDSSVector &x,int n)
|
|
{
|
|
x.m_idx.Resize(n);
|
|
x.m_vals.Resize(n);
|
|
x.m_dense.Resize(n);
|
|
x.m_n=n;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initializes vector, sets all internal arrays to length N and |
|
|
//| zero - fills them. Previously allocated memory is reused as much |
|
|
//| as possible. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - temporary buffers |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - preallocated vector: |
|
|
//| * X.N = N |
|
|
//| * X.K = 0 |
|
|
//| * X.Dense is zero - filled. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DVInit(CDSSVector &x,int n)
|
|
{
|
|
x.m_idx.Resize(n);
|
|
x.m_vals.Resize(n);
|
|
x.m_dense=vector<double>::Zeros(n);
|
|
x.m_n=n;
|
|
x.m_k=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copies dense part to sparse one. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - allocated vector; dense part must be valid |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - both dense and sparse parts are valid. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DVDenseToSparse(CDSSVector &x)
|
|
{
|
|
//--- create variables
|
|
int n=x.m_n;
|
|
int k=0;
|
|
double v=0;
|
|
|
|
x.m_idx.Resize(n);
|
|
x.m_vals.Resize(n);
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
v=x.m_dense[i];
|
|
if(v!=0.0)
|
|
{
|
|
x.m_idx.Set(k,i);
|
|
x.m_vals.Set(k,v);
|
|
k++;
|
|
}
|
|
}
|
|
x.m_k=k;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copies sparse part to dense one. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - allocated vector; sparse part must be valid |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - both dense and sparse parts are valid. |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::DVSparseToDense(CDSSVector &x)
|
|
{
|
|
//--- create variables
|
|
int n=x.m_n;
|
|
int k=x.m_k;
|
|
|
|
x.m_dense=vector<double>::Zeros(n);
|
|
for(int i=0; i<k; i++)
|
|
x.m_dense.Set(x.m_idx[i],x.m_vals[i]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
double CRevisedDualSimplex::SparsitYOf(CRowDouble &x,int n)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int i=0;
|
|
int k=0;
|
|
double mx=1.0;
|
|
//--- Quick exit
|
|
if(n<=1)
|
|
return(0);
|
|
|
|
for(i=0; i<n; i++)
|
|
mx=MathMax(mx,MathAbs(x[i]));
|
|
mx=1.0E5*CMath::m_machineepsilon*mx;
|
|
k=0;
|
|
for(i=0; i<n; i++)
|
|
if(MathAbs(x[i])>mx)
|
|
k++;
|
|
|
|
result=(double)k/(double)n;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRevisedDualSimplex::UpdateAvgCounter(double v,
|
|
double &acc,
|
|
int &cnt)
|
|
{
|
|
acc=acc+v;
|
|
cnt=cnt+1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores linear solver State. |
|
|
//| You should use functions provided by MinLP subpackage to work |
|
|
//| with this object |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinLPState
|
|
{
|
|
int m_algokind;
|
|
int m_m;
|
|
int m_n;
|
|
int m_repiterationscount;
|
|
int m_repm;
|
|
int m_repn;
|
|
int m_repterminationtype;
|
|
double m_dsseps;
|
|
double m_ipmeps;
|
|
double m_ipmlambda;
|
|
double m_repdualerror;
|
|
double m_repf;
|
|
double m_repprimalerror;
|
|
double m_repslackerror;
|
|
CVIPMState m_ipm;
|
|
CSparseMatrix m_a;
|
|
CSparseMatrix m_ipmquadratic;
|
|
CRowInt m_adddtmpi;
|
|
CRowInt m_cs;
|
|
CRowDouble m_adddtmpr;
|
|
CRowDouble m_al;
|
|
CRowDouble m_au;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_c;
|
|
CRowDouble m_lagbc;
|
|
CRowDouble m_laglc;
|
|
CRowDouble m_s;
|
|
CRowDouble m_tmpax;
|
|
CRowDouble m_tmpg;
|
|
CRowDouble m_units;
|
|
CRowDouble m_xs;
|
|
CRowDouble m_zeroorigin;
|
|
CPresolveInfo m_presolver;
|
|
CDualSimplexState m_dss;
|
|
//--- constructor / destructor
|
|
CMinLPState(void);
|
|
~CMinLPState(void) {}
|
|
//---
|
|
void Copy(const CMinLPState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinLPState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinLPState::CMinLPState(void)
|
|
{
|
|
m_algokind=0;
|
|
m_m=0;
|
|
m_n=0;
|
|
m_repiterationscount=0;
|
|
m_repm=0;
|
|
m_repn=0;
|
|
m_repterminationtype=0;
|
|
m_dsseps=0;
|
|
m_ipmeps=0;
|
|
m_ipmlambda=0;
|
|
m_repdualerror=0;
|
|
m_repf=0;
|
|
m_repprimalerror=0;
|
|
m_repslackerror=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLPState::Copy(const CMinLPState &obj)
|
|
{
|
|
m_algokind=obj.m_algokind;
|
|
m_m=obj.m_m;
|
|
m_n=obj.m_n;
|
|
m_repiterationscount=obj.m_repiterationscount;
|
|
m_repm=obj.m_repm;
|
|
m_repn=obj.m_repn;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_dsseps=obj.m_dsseps;
|
|
m_ipmeps=obj.m_ipmeps;
|
|
m_ipmlambda=obj.m_ipmlambda;
|
|
m_repdualerror=obj.m_repdualerror;
|
|
m_repf=obj.m_repf;
|
|
m_repprimalerror=obj.m_repprimalerror;
|
|
m_repslackerror=obj.m_repslackerror;
|
|
m_ipm=obj.m_ipm;
|
|
m_a=obj.m_a;
|
|
m_ipmquadratic=obj.m_ipmquadratic;
|
|
m_adddtmpi=obj.m_adddtmpi;
|
|
m_cs=obj.m_cs;
|
|
m_adddtmpr=obj.m_adddtmpr;
|
|
m_al=obj.m_al;
|
|
m_au=obj.m_au;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_c=obj.m_c;
|
|
m_lagbc=obj.m_lagbc;
|
|
m_laglc=obj.m_laglc;
|
|
m_s=obj.m_s;
|
|
m_tmpax=obj.m_tmpax;
|
|
m_tmpg=obj.m_tmpg;
|
|
m_units=obj.m_units;
|
|
m_xs=obj.m_xs;
|
|
m_zeroorigin=obj.m_zeroorigin;
|
|
m_presolver=obj.m_presolver;
|
|
m_dss=obj.m_dss;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure stores optimization report: |
|
|
//| * f target function value |
|
|
//| * lagbc Lagrange coefficients for box constraints |
|
|
//| * laglc Lagrange coefficients for linear constraints |
|
|
//| * y dual variables |
|
|
//| * stats array[N + M], statuses of box(N) and linear(M) |
|
|
//| constraints. This array is filled only by DSS |
|
|
//| algorithm because IPM always stops at INTERIOR |
|
|
//| point: |
|
|
//| * stats[i] > 0 => constraint at upper bound |
|
|
//| (also used for free |
|
|
//| non-basic variables set |
|
|
//| to zero) |
|
|
//| * stats[i] < 0 => constraint at lower bound |
|
|
//| * stats[i] = 0 => constraint is inactive, |
|
|
//| basic variable |
|
|
//| * primalerror primal feasibility error |
|
|
//| * dualerror dual feasibility error |
|
|
//| * slackerror complementary slackness error |
|
|
//| * iterationscount iteration count |
|
|
//| * terminationtype completion code(see below) |
|
|
//| COMPLETION CODES: |
|
|
//| * -4 LP problem is primal unbounded(dual infeasible) |
|
|
//| * -3 LP problem is primal infeasible(dual unbounded) |
|
|
//| * 1..4 successful completion |
|
|
//| * 5 MaxIts steps was taken |
|
|
//| * 7 stopping conditions are too stringent, further |
|
|
//| improvement is impossible, X contains best point|
|
|
//| found so far. |
|
|
//| LAGRANGE COEFFICIENTS: |
|
|
//| Positive Lagrange coefficient means that constraint is at its |
|
|
//| upper bound. Negative coefficient means that constraint is at |
|
|
//| its lower bound. It is expected that at solution the dual |
|
|
//| feasibility condition holds: |
|
|
//| C+SUM(Ei*LagBC[i], i=0..m_n-1)+SUM(Ai*LagLC[i], i=0..m_m-1) ~ 0 |
|
|
//| where: |
|
|
//| * C is a cost vector(linear term) |
|
|
//| * Ei is a vector with 1.0 at position I and 0 in other |
|
|
//| positions |
|
|
//| * Ai is an I-th row of linear constraint matrix |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinLPReport
|
|
{
|
|
int m_iterationscount;
|
|
int m_terminationtype;
|
|
double m_dualerror;
|
|
double m_f;
|
|
double m_primalerror;
|
|
double m_slackerror;
|
|
CRowInt m_stats;
|
|
CRowDouble m_lagbc;
|
|
CRowDouble m_laglc;
|
|
CRowDouble m_y;
|
|
//--- constructor / destructor
|
|
CMinLPReport(void);
|
|
~CMinLPReport(void) {}
|
|
void Copy(const CMinLPReport &obj);
|
|
//--- overloading
|
|
void operator=(const CMinLPReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinLPReport::CMinLPReport(void)
|
|
{
|
|
m_iterationscount=0;
|
|
m_terminationtype=0;
|
|
m_dualerror=0;
|
|
m_f=0;
|
|
m_primalerror=0;
|
|
m_slackerror=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLPReport::Copy(const CMinLPReport &obj)
|
|
{
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_dualerror=obj.m_dualerror;
|
|
m_f=obj.m_f;
|
|
m_primalerror=obj.m_primalerror;
|
|
m_slackerror=obj.m_slackerror;
|
|
m_stats=obj.m_stats;
|
|
m_lagbc=obj.m_lagbc;
|
|
m_laglc=obj.m_laglc;
|
|
m_y=obj.m_y;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CMinLP
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const int m_alllogicalsbasis;
|
|
|
|
static void MinLPCreate(int n,CMinLPState &State);
|
|
static void MinLPSetAlgoDSS(CMinLPState &State,double eps);
|
|
static void MinLPSetAlgoIPM(CMinLPState &State,double eps);
|
|
static void MinLPSetCost(CMinLPState &State,CRowDouble &c);
|
|
static void MinLPSetScale(CMinLPState &State,CRowDouble &s);
|
|
static void MinLPSetBC(CMinLPState &State,CRowDouble &bndl,CRowDouble &bndu);
|
|
static void MinLPSetBCAll(CMinLPState &State,double bndl,double bndu);
|
|
static void MinLPSetBCi(CMinLPState &State,int i,double bndl,double bndu);
|
|
static void MinLPSetLC(CMinLPState &State,CMatrixDouble &a,CRowInt &ct,int k);
|
|
static void MinLPSetLC2Dense(CMinLPState &State,CMatrixDouble &a,CRowDouble &al,CRowDouble &au,int k);
|
|
static void MinLPSetLC2(CMinLPState &State,CSparseMatrix &a,CRowDouble &al,CRowDouble &au,int k);
|
|
static void MinLPAddLC2Dense(CMinLPState &State,CRowDouble &a,double al,double au);
|
|
static void MinLPAddLC2(CMinLPState &State,CRowInt &idxa,CRowDouble &vala,int nnz,double al,double au);
|
|
static void MinLPOptimize(CMinLPState &State);
|
|
static void MinLPResults(CMinLPState &State,CRowDouble &x,CMinLPReport &rep);
|
|
static void MinLPResultsBuf(CMinLPState &State,CRowDouble &x,CMinLPReport &rep);
|
|
|
|
private:
|
|
static void ClearReportFields(CMinLPState &State);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const int CMinLP::m_alllogicalsbasis=0;
|
|
//+------------------------------------------------------------------+
|
|
//| LINEAR PROGRAMMING |
|
|
//| The subroutine creates LP solver. After initial creation it |
|
|
//| contains default optimization problem with zero cost vector and |
|
|
//| all variables being fixed to zero values and no constraints. |
|
|
//| In order to actually solve something you should: |
|
|
//| * set cost vector with MinLPSetCost() |
|
|
//| * set variable bounds with MinLPSetBC() or MinLPSetBCAll() |
|
|
//| * specify constraint matrix with one of the following |
|
|
//| functions: |
|
|
//| [*] MinLPSetLC() for dense one-sided constraints |
|
|
//| [*] MinLPSetLC2Dense() for dense two-sided constraints |
|
|
//| [*] MinLPSetLC2() for sparse two-sided constraints |
|
|
//| [*] MinLPAddLC2Dense() to add one dense row to constraint |
|
|
//| matrix |
|
|
//| [*] MinLPAddLC2() to add one row to constraint matrix |
|
|
//| (compressed format) |
|
|
//| * call MinLPOptimize() to run the solver and MinLPResults() to |
|
|
//| get the solution vector and additional information. |
|
|
//| By default, LP solver uses best algorithm available. As of ALGLIB|
|
|
//| 3.17, sparse interior point (barrier) solver is used. Future |
|
|
//| releases of ALGLIB may introduce other solvers. |
|
|
//| User may choose specific LP algorithm by calling: |
|
|
//| * MinLPSetAlgoDSS() for revised dual simplex method with DSE |
|
|
//| pricing and bounds flipping ratio test (aka long dual step). |
|
|
//| Large - scale sparse LU solverwith Forest - Tomlin update is |
|
|
//| used internally as linear algebra driver. |
|
|
//| * MinLPSetAlgoIPM() for sparse interior point method |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem size |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - optimizer in the default State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPCreate(int n,CMinLPState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
//--- Initialize
|
|
State.m_n=n;
|
|
State.m_m=0;
|
|
MinLPSetAlgoIPM(State,0.0);
|
|
State.m_ipmlambda=0;
|
|
State.m_c=vector<double>::Zeros(n);
|
|
State.m_s=vector<double>::Ones(n);
|
|
State.m_bndl=vector<double>::Zeros(n);
|
|
State.m_bndu=vector<double>::Zeros(n);
|
|
State.m_xs=vector<double>::Ones(n);
|
|
ClearReportFields(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets LP algorithm to revised dual simplex method. |
|
|
//| ALGLIB implementation of dual simplex method supports advanced |
|
|
//| performance and stability improvements like DSE pricing, bounds |
|
|
//| flipping ratio test (aka long dual step), Forest - Tomlin update,|
|
|
//| Shifting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - optimizer |
|
|
//| Eps - stopping condition, Eps >= 0: |
|
|
//| * should be small number about 1E-6 or 1E-7. |
|
|
//| * zero value means that solver automatically |
|
|
//| selects good value (can be different in |
|
|
//| different ALGLIB versions) |
|
|
//| * default value is zero |
|
|
//| Algorithm stops when relative error is less than Eps. |
|
|
//| ===== TRACING DSS SOLVER ======================================= |
|
|
//| DSS solver supports advanced tracing capabilities. You can trace |
|
|
//| algorithm output by specifying following trace symbols |
|
|
//| (case-insensitive) by means of trace_file() call: |
|
|
//| * 'DSS' - for basic trace of algorithm steps and decisions|
|
|
//| Only short scalars (function values and deltas) |
|
|
//| are printed. N-dimensional quantities like |
|
|
//| search directions are NOT printed. |
|
|
//| * 'DSS.DETAILED' - for output of points being visited and |
|
|
//| search directions. |
|
|
//| This symbol also implicitly defines 'DSS'. You can control output|
|
|
//| format by additionally specifying: |
|
|
//| * nothing to output in 6-digit exponential format |
|
|
//| * 'PREC.E15' to output in 15-digit exponential format |
|
|
//| * 'PREC.F6' to output in 6-digit fixed - point format |
|
|
//| By default trace is disabled and adds no overhead to the |
|
|
//| optimization process. However, specifying any of the symbols |
|
|
//| adds some formatting and output - related overhead. |
|
|
//| You may specify multiple symbols by separating them with commas: |
|
|
//| > |
|
|
//| >CAlglib::Trace_File("DSS,PREC.F6", "path/to/trace.log") |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPSetAlgoDSS(CMinLPState &State,
|
|
double eps)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(eps),__FUNCTION__+": Eps is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(eps>=0.0,__FUNCTION__+": Eps<0"))
|
|
return;
|
|
State.m_algokind=1;
|
|
if(eps==0.0)
|
|
eps=1.0E-6;
|
|
State.m_dsseps=eps;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets LP algorithm to sparse interior point method. |
|
|
//| ALGORITHM INFORMATION: |
|
|
//| * this algorithm is our implementation of interior point |
|
|
//| method as formulated by R.J.Vanderbei, with minor |
|
|
//| modifications to the algorithm (damped Newton directions are |
|
|
//| extensively used) |
|
|
//| * like all interior point methods, this algorithm tends to |
|
|
//| converge in roughly same number of iterations (between 15 |
|
|
//| and 50) independently from the problem dimensionality |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - optimizer |
|
|
//| Eps - stopping condition, Eps >= 0: |
|
|
//| * should be small number about 1E-7 or 1E-8. |
|
|
//| * zero value means that solver automatically |
|
|
//| selects good value (can be different in different|
|
|
//| ALGLIB versions) |
|
|
//| * default value is zero |
|
|
//| Algorithm stops when primal error AND dual error AND |
|
|
//| duality gap are less than Eps. |
|
|
//| ===== TRACING IPM SOLVER ======================================= |
|
|
//| IPM solver supports advanced tracing capabilities. You can trace |
|
|
//| algorithm output by specifying following trace symbols |
|
|
//| (case-insensitive) by means of trace_file() call: |
|
|
//| * 'IPM' - for basic trace of algorithm steps and |
|
|
//| decisions. Only short scalars (function |
|
|
//| values and deltas) are printed. N-dimensional|
|
|
//| quantities like search directions are NOT |
|
|
//| printed. |
|
|
//| * 'IPM.DETAILED' - for output of points being visited and |
|
|
//| search directions |
|
|
//| This symbol also implicitly defines 'IPM'. You can output format |
|
|
//| by additionally specifying: |
|
|
//| * nothing to output in 6-digit exponential format |
|
|
//| * 'PREC.E15' to output in 15 - digit exponential format |
|
|
//| * 'PREC.F6' to output in 6-digit fixed-point format |
|
|
//| By default trace is disabled and adds no overhead to the |
|
|
//| optimization process. However, specifying any of the symbols |
|
|
//| adds some formatting and output - related overhead. |
|
|
//| You may specify multiple symbols by separating them with commas: |
|
|
//| > |
|
|
//| >CAlglib::Trace_File("IPM,PREC.F6", "path/to/trace.log") |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPSetAlgoIPM(CMinLPState &State,double eps)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(eps),__FUNCTION__+": Eps is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(eps>=0.0,__FUNCTION__+": Eps<0"))
|
|
return;
|
|
State.m_algokind=2;
|
|
State.m_ipmeps=eps;
|
|
State.m_ipmlambda=0.0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets cost term for LP solver. |
|
|
//| By default, cost term is zero. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| C - cost term, array[N]. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPSetCost(CMinLPState &State,CRowDouble &c)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(c.Size()>=n,__FUNCTION__+": Length(C)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(c,n),__FUNCTION__+": C contains infinite or NaN elements"))
|
|
return;
|
|
State.m_c=c;
|
|
State.m_c.Resize(n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients. |
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions and as preconditioner. |
|
|
//| Scale of the I-th variable is a translation invariant measure of:|
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients S[i] may |
|
|
//| be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPSetScale(CMinLPState &State,CRowDouble &s)
|
|
{
|
|
if(!CAp::Assert(CAp::Len(s)>=State.m_n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
}
|
|
State.m_s=s.Abs()+0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets box constraints for LP solver (all variables |
|
|
//| at once, different constraints for different variables). |
|
|
//| The default State of constraints is to have all variables fixed |
|
|
//| at zero. You have to overwrite it by your own constraint vector. |
|
|
//| Constraint status is preserved until constraints are explicitly |
|
|
//| overwritten with another MinLPSetBC() call, overwritten with |
|
|
//| MinLPSetBCAll(), or partially overwritten with minlmsetbci() call|
|
|
//| Following types of constraints are supported: |
|
|
//| DESCRIPTION CONSTRAINT HOW TO SPECIFY |
|
|
//| fixed variable x[i] = Bnd[i] BndL[i] = BndU[i]|
|
|
//| lower bound BndL[i] <= x[i] BndU[i] = +INF |
|
|
//| upper bound x[i] <= BndU[i] BndL[i] = -INF |
|
|
//| range BndL[i] <= x[i] <= BndU[i] ... |
|
|
//| free variable - BndL[I] = -INF, BndU[I] + INF|
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bounds, array[N]. |
|
|
//| BndU - upper bounds, array[N]. |
|
|
//| NOTE: infinite values can be specified by means of AL_POSINF and |
|
|
//| AL_NEGINF |
|
|
//| NOTE: you may replace infinities by very small/very large values,|
|
|
//| but it is not recommended because large numbers may |
|
|
//| introduce large numerical errors in the algorithm. |
|
|
//| NOTE: if constraints for all variables are same you may use |
|
|
//| MinLPSetBCAll() which allows to specify constraints without|
|
|
//| using arrays. |
|
|
//| NOTE: BndL > BndU will result in LP problem being recognized as |
|
|
//| infeasible. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPSetBC(CMinLPState &State,CRowDouble &bndl,CRowDouble &bndu)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(bndu)>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(bndl[i]) || IsNegInf(bndl[i]),__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu[i]) || IsPosInf(bndu[i]),__FUNCTION__+": BndU contains NAN or -INF"))
|
|
return;
|
|
}
|
|
State.m_bndl=bndl;
|
|
State.m_bndu=bndu;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets box constraints for LP solver(all variables at|
|
|
//| once, same constraints for all variables) |
|
|
//| The default State of constraints is to have all variables fixed |
|
|
//| at zero. You have to overwrite it by your own constraint vector. |
|
|
//| Constraint status is preserved until constraints are explicitly |
|
|
//| overwritten with another MinLPSetBC() call or partially |
|
|
//| overwritten with MinLPSetBCAll(). |
|
|
//| Following types of constraints are supported: |
|
|
//| DESCRIPTION CONSTRAINT HOW TO SPECIFY |
|
|
//| fixed variable x[i] = Bnd[i] BndL[i] = BndU[i]|
|
|
//| lower bound BndL[i] <= x[i] BndU[i] = +INF |
|
|
//| upper bound x[i] <= BndU[i] BndL[i] = -INF |
|
|
//| range BndL[i] <= x[i] <= BndU[i] ... |
|
|
//| free variable - BndL[I] = -INF, BndU[I] + INF|
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bound, same for all variables |
|
|
//| BndU - upper bound, same for all variables |
|
|
//| NOTE: infinite values can be specified by means of AL_POSINF and |
|
|
//| AL_NEGINF |
|
|
//| NOTE: you may replace infinities by very small/very large values,|
|
|
//| but it is not recommended because large numbers may |
|
|
//| introduce large numerical errors in the algorithm. |
|
|
//| NOTE: MinLPSetBC() can be used to specify different constraints |
|
|
//| for different variables. |
|
|
//| NOTE: BndL > BndU will result in LP problem being recognized as |
|
|
//| infeasible. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPSetBCAll(CMinLPState &State,double bndl,double bndu)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(bndl) || IsNegInf(bndl),__FUNCTION__+": BndL is NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu) || IsPosInf(bndu),__FUNCTION__+": BndU is NAN or -INF"))
|
|
return;
|
|
|
|
State.m_bndl.Fill(bndl);
|
|
State.m_bndu.Fill(bndu);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets box constraints for I-th variable (other |
|
|
//| variables are not modified). |
|
|
//| The default State of constraints is to have all variables fixed |
|
|
//| at zero. You have to overwrite it by your own constraint vector. |
|
|
//| Following types of constraints are supported: |
|
|
//| DESCRIPTION CONSTRAINT HOW TO SPECIFY |
|
|
//| fixed variable x[i] = Bnd[i] BndL[i] = BndU[i]|
|
|
//| lower bound BndL[i] <= x[i] BndU[i] = +INF |
|
|
//| upper bound x[i] <= BndU[i] BndL[i] = -INF |
|
|
//| range BndL[i] <= x[i] <= BndU[i] ... |
|
|
//| free variable - BndL[I] = -INF, BndU[I] + INF|
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| I - variable index, in [0, N) |
|
|
//| BndL - lower bound for I-th variable |
|
|
//| BndU - upper bound for I-th variable |
|
|
//| NOTE: infinite values can be specified by means of AL_POSINF and |
|
|
//| AL_NEGINF |
|
|
//| NOTE: you may replace infinities by very small/very large values,|
|
|
//| but it is not recommended because large numbers may |
|
|
//| introduce large numerical errors in the algorithm. |
|
|
//| NOTE: MinLPSetBC() can be used to specify different constraints |
|
|
//| for different variables. |
|
|
//| NOTE: BndL > BndU will result in LP problem being recognized as |
|
|
//| infeasible. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPSetBCi(CMinLPState &State,int i,double bndl,double bndu)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(i>=0 && i<n,__FUNCTION__+": I is outside of [0,N)"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndl) || IsNegInf(bndl),__FUNCTION__+": BndL is NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu) || IsPosInf(bndu),__FUNCTION__+": BndU is NAN or -INF"))
|
|
return;
|
|
|
|
State.m_bndl.Set(i,bndl);
|
|
State.m_bndu.Set(i,bndu);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets one-sided linear constraints A*x ~ AU, where |
|
|
//| "~" can be a mix of "<=", "=" and ">=". |
|
|
//| IMPORTANT: this function is provided here for compatibility with |
|
|
//| the rest of ALGLIB optimizers which accept constraints|
|
|
//| in format like this one. Many real-life problems |
|
|
//| feature two-sided constraints like a0 <= a*x <= a1. It|
|
|
//| is really inefficient to add them as a pair of |
|
|
//| one-sided constraints. |
|
|
//| Use MinLPSetLC2Dense(), MinLPSetLC2(), MinLPAddLC2() (or its |
|
|
//| sparse version) wherever possible. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinLPCreate() |
|
|
//| call. |
|
|
//| A - linear constraints, array[K, N + 1]. Each row of A |
|
|
//| represents one constraint, with first N elements |
|
|
//| being linear coefficients, and last element being |
|
|
//| right side. |
|
|
//| CT - constraint types, array[K]: |
|
|
//| * if CT[i] > 0, then I-th constraint is |
|
|
//| A[i, *]*x >= A[i, n] |
|
|
//| * if CT[i] = 0, then I-th constraint is |
|
|
//| A[i, *] * x = A[i, n] |
|
|
//| * if CT[i] < 0, then I-th constraint is |
|
|
//| A[i, *] * x <= A[i, n] |
|
|
//| K - number of equality/inequality constraints, K >= 0; |
|
|
//| if not given, inferred from sizes of A and CT. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPSetLC(CMinLPState &State,CMatrixDouble &a,CRowInt &ct,
|
|
int k)
|
|
{
|
|
//--- create variables
|
|
CRowDouble al;
|
|
CRowDouble au;
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(!CAp::Assert(k==0 || CAp::Cols(a)>=n+1,__FUNCTION__+": Cols(A)<N+1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(a)>=k,__FUNCTION__+": Rows(A)<K"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(ct)>=k,__FUNCTION__+": Length(CT)<K"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(a,k,n+1),__FUNCTION__+": A contains infinite or NaN values!"))
|
|
return;
|
|
//--- Handle zero K
|
|
if(k==0)
|
|
{
|
|
State.m_m=0;
|
|
return;
|
|
}
|
|
//--- Convert constraints to two-sided storage format, call another function
|
|
al.Resize(k);
|
|
au.Resize(k);
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
if(ct[i]>0)
|
|
{
|
|
al.Set(i,a.Get(i,n));
|
|
au.Set(i,AL_POSINF);
|
|
continue;
|
|
}
|
|
if(ct[i]<0)
|
|
{
|
|
al.Set(i,AL_NEGINF);
|
|
au.Set(i,a.Get(i,n));
|
|
continue;
|
|
}
|
|
al.Set(i,a.Get(i,n));
|
|
au.Set(i,a.Get(i,n));
|
|
}
|
|
MinLPSetLC2Dense(State,a,al,au,k);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets two-sided linear constraints AL <= A*x <= AU. |
|
|
//| This version accepts dense matrix as input; internally LP solver|
|
|
//| uses sparse storage anyway (most LP problems are sparse), but for|
|
|
//| your convenience it may accept dense inputs. This function |
|
|
//| overwrites linear constraints set by previous calls (if such |
|
|
//| calls were made). |
|
|
//| We recommend you to use sparse version of this function unless |
|
|
//| you solve small-scale LP problem (less than few hundreds of |
|
|
//| variables). |
|
|
//| NOTE: there also exist several versions of this function: |
|
|
//| * one-sided dense version which accepts constraints in the same|
|
|
//| format as one used by QP and NLP solvers |
|
|
//| * two-sided sparse version which accepts sparse matrix |
|
|
//| * two-sided dense version which allows you to add constraints |
|
|
//| row by row |
|
|
//| * two-sided sparse version which allows you to add constraints |
|
|
//| row by row |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinLPCreate() |
|
|
//| call. |
|
|
//| A - linear constraints, array[K, N]. Each row of A |
|
|
//| represents one constraint. One-sided inequality |
|
|
//| constraints, two-sided inequality constraints, |
|
|
//| equality constraints are supported (see below) |
|
|
//| AL, AU - lower and upper bounds, array[K]; |
|
|
//| * AL[i] = AU[i] => equality constraint Ai * x |
|
|
//| * AL[i]<AU[i] => two-sided constraint |
|
|
//| AL[i] <= Ai*x <= AU[i] |
|
|
//| * AL[i] = -INF => one-sided constraint |
|
|
//| Ai*x <= AU[i] |
|
|
//| * AU[i] = +INF => one-sided constraint |
|
|
//| AL[i] <= Ai*x |
|
|
//| * AL[i] = -INF, AU[i] = +INF => constraint is |
|
|
//| ignored |
|
|
//| K - number of equality/inequality constraints, K >= 0; |
|
|
//| if not given, inferred from sizes of A, AL, AU. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPSetLC2Dense(CMinLPState &State,CMatrixDouble &a,
|
|
CRowDouble &al,CRowDouble &au,int k)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int i=0;
|
|
int j=0;
|
|
int nz=0;
|
|
CRowInt nrs;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(!CAp::Assert(k==0 || CAp::Cols(a)>=n,__FUNCTION__+": Cols(A)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(a)>=k,__FUNCTION__+": Rows(A)<K"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(al)>=k,__FUNCTION__+": Length(AL)<K"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(au)>=k,__FUNCTION__+": Length(AU)<K"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(a,k,n),__FUNCTION__+": A contains infinite or NaN values!"))
|
|
return;
|
|
//--- Count actual (different from -INF<=A*x<=+INF) constraints;
|
|
//--- count non-zero elements in each row.
|
|
nrs.Resize(k);
|
|
State.m_m=k;
|
|
if(State.m_m==0)
|
|
return;
|
|
for(i=0; i<k; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(al[i]) || IsNegInf(al[i]),__FUNCTION__+": AL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(au[i]) || IsPosInf(au[i]),__FUNCTION__+": AU contains NAN or -INF"))
|
|
return;
|
|
nz=0;
|
|
for(j=0; j<n; j++)
|
|
if(a.Get(i,j)!=0.0)
|
|
nz++;
|
|
nrs.Set(i,nz);
|
|
}
|
|
//--- Allocate storage, copy
|
|
State.m_al=al;
|
|
State.m_au=au;
|
|
CSparse::SparseCreateCRSBuf(State.m_m,n,nrs,State.m_a);
|
|
for(i=0; i<k; i++)
|
|
for(j=0; j<n; j++)
|
|
if(a.Get(i,j)!=0.0)
|
|
CSparse::SparseSet(State.m_a,i,j,a.Get(i,j));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets two-sided linear constraints AL <= A*x <= AU |
|
|
//| with sparse constraining matrix A. Recommended for large-scale |
|
|
//| problems. |
|
|
//| This function overwrites linear (non-box) constraints set by |
|
|
//| previous calls (if such calls were made). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinLPCreate() |
|
|
//| call. |
|
|
//| A - sparse matrix with size [K, N] (exactly!). Each row|
|
|
//| of A represents one general linear constraint. A |
|
|
//| can be stored in any sparse storage format. |
|
|
//| AL, AU - lower and upper bounds, array[K]; |
|
|
//| * AL.Set(i, AU.Set(i,> equality constraint Ai*x |
|
|
//| * AL[i]<AU.Set(i,> two-sided constraint |
|
|
//| AL[i] <= Ai*x <= AU[i] |
|
|
//| * AL[i] = -INF => one-sided constraint |
|
|
//| Ai*x <= AU[i] |
|
|
//| * AU[i] = +INF => one-sided constraint |
|
|
//| AL[i] <= Ai*x |
|
|
//| * AL.Set(i, -INF, AU.Set(i, +INF => constraint is |
|
|
//| ignored |
|
|
//| K - number of equality/inequality constraints, K >= 0. |
|
|
//| If K = 0 is specified, A, AL, AU are ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPSetLC2(CMinLPState &State,CSparseMatrix &a,
|
|
CRowDouble &al,CRowDouble &au,int k)
|
|
{
|
|
int n=State.m_n;
|
|
//--- Quick exit
|
|
if(k==0)
|
|
{
|
|
State.m_m=0;
|
|
return;
|
|
}
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(k>0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(a)==n,__FUNCTION__+": Cols(A)<>N"))
|
|
return;
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(a)==k,__FUNCTION__+": Rows(A)<>K"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(al)>=k,__FUNCTION__+": Length(AL)<K"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(au)>=k,__FUNCTION__+": Length(AU)<K"))
|
|
return;
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(al[i]) || IsNegInf(al[i]),__FUNCTION__+": AL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(au[i]) || IsPosInf(au[i]),__FUNCTION__+": AU contains NAN or -INF"))
|
|
return;
|
|
}
|
|
//--- Copy
|
|
State.m_m=k;
|
|
CSparse::SparseCopyToCRSBuf(a,State.m_a);
|
|
State.m_al=al;
|
|
State.m_au=au;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function appends two-sided linear constraint AL <= A*x <= AU|
|
|
//| to the list of currently present constraints. |
|
|
//| This version accepts dense constraint vector as input, but |
|
|
//| sparsifies it for internal storage and processing. Thus, time to |
|
|
//| add one constraint in is O(N) - we have to scan entire array of |
|
|
//| length N. Sparse version of this function is order of magnitude |
|
|
//| faster for constraints with just a few nonzeros per row. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinLPCreate() |
|
|
//| call. |
|
|
//| A - linear constraint coefficient, array[N], right side|
|
|
//| is NOT included. |
|
|
//| AL, AU - lower and upper bounds; |
|
|
//| * AL = AU => equality constraint Ai*x |
|
|
//| * AL<AU => two-sided constraint |
|
|
//| AL <= A*x <= AU |
|
|
//| * AL = -INF => one-sided constraint Ai*x <= AU |
|
|
//| * AU = +INF => one-sided constraint AL <= Ai*x |
|
|
//| * AL = -INF, AU = +INF => constraint is ignored |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPAddLC2Dense(CMinLPState &State,CRowDouble &a,
|
|
double al,double au)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int nnz=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(a)>=n,__FUNCTION__+": Length(A)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(a,n),__FUNCTION__+": A contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(al) || IsNegInf(al),__FUNCTION__+": AL is NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(au) || IsPosInf(au),__FUNCTION__+": AU is NAN or -INF"))
|
|
return;
|
|
CApServ::IVectorSetLengthAtLeast(State.m_adddtmpi,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_adddtmpr,n);
|
|
nnz=0;
|
|
for(int i=0; i<n; i++)
|
|
if(a[i]!=0.0)
|
|
{
|
|
State.m_adddtmpi.Set(nnz,i);
|
|
State.m_adddtmpr.Set(nnz,a[i]);
|
|
nnz++;
|
|
}
|
|
//--- function call
|
|
MinLPAddLC2(State,State.m_adddtmpi,State.m_adddtmpr,nnz,al,au);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function appends two-sided linear constraint AL <= A*x <= AU|
|
|
//| to the list of currently present constraints. |
|
|
//| Constraint is passed in compressed format: as list of non-zero |
|
|
//| entries of coefficient vector A. Such approach is more efficient |
|
|
//| than dense storage for highly sparse constraint vectors. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinLPCreate() |
|
|
//| call. |
|
|
//| IdxA - array[NNZ], indexes of non-zero elements of A: |
|
|
//| * can be unsorted |
|
|
//| * can include duplicate indexes (corresponding |
|
|
//| entries of ValA[] will be summed) |
|
|
//| ValA - array[NNZ], values of non-zero elements of A |
|
|
//| NNZ - number of non-zero coefficients in A |
|
|
//| AL, AU - lower and upper bounds; |
|
|
//| * AL = AU => equality constraint A*x |
|
|
//| * AL<AU => two-sided constraint AL <= A*x <= AU |
|
|
//| * AL = -INF => one-sided constraint A*x <= AU |
|
|
//| * AU = +INF => one-sided constraint AL <= A*x |
|
|
//| * AL = -INF, AU = +INF => constraint is ignored |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPAddLC2(CMinLPState &State,CRowInt &idxa,
|
|
CRowDouble &vala,int nnz,double al,double au)
|
|
{
|
|
//--- create variables
|
|
int m=State.m_m;
|
|
int n=State.m_n;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int offs=0;
|
|
int offsdst=0;
|
|
int didx=0;
|
|
int uidx=0;
|
|
//--- Check inputs
|
|
if(!CAp::Assert(nnz>=0,__FUNCTION__+": NNZ<0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(idxa)>=nnz,__FUNCTION__+": Length(IdxA)<NNZ"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(vala)>=nnz,__FUNCTION__+": Length(ValA)<NNZ"))
|
|
return;
|
|
for(i=0; i<nnz; i++)
|
|
if(!CAp::Assert(idxa[i]>=0 && idxa[i]<n,__FUNCTION__+": IdxA contains indexes outside of [0,N) range"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(vala,nnz),__FUNCTION__+": ValA contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(al) || IsNegInf(al),__FUNCTION__+": AL is NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(au) || IsPosInf(au),__FUNCTION__+": AU is NAN or -INF"))
|
|
return;
|
|
//--- If M=0, it means that A is uninitialized.
|
|
//--- Prepare sparse matrix structure
|
|
if(m==0)
|
|
{
|
|
State.m_a.m_MatrixType=1;
|
|
State.m_a.m_M=0;
|
|
State.m_a.m_N=n;
|
|
State.m_a.m_NInitialized=0;
|
|
CApServ::IVectorSetLengthAtLeast(State.m_a.m_RIdx,1);
|
|
State.m_a.m_RIdx.Set(0,0);
|
|
}
|
|
//--- Reallocate storage
|
|
offs=State.m_a.m_RIdx[m];
|
|
State.m_a.m_Idx.Resize(offs+nnz);
|
|
State.m_a.m_Vals.Resize(offs+nnz);
|
|
State.m_a.m_DIdx.Resize(m+1);
|
|
State.m_a.m_UIdx.Resize(m+1);
|
|
State.m_a.m_RIdx.Resize(m+2);
|
|
State.m_al.Resize(m+1);
|
|
State.m_au.Resize(m+1);
|
|
//--- If NNZ=0, perform quick and simple row append.
|
|
if(nnz==0)
|
|
{
|
|
State.m_a.m_DIdx.Set(m,State.m_a.m_RIdx[m]);
|
|
State.m_a.m_UIdx.Set(m,State.m_a.m_RIdx[m]);
|
|
State.m_a.m_RIdx.Set(m+1,State.m_a.m_RIdx[m]);
|
|
State.m_al.Set(m,al);
|
|
State.m_au.Set(m,au);
|
|
State.m_a.m_M=m+1;
|
|
State.m_m=m+1;
|
|
return;
|
|
}
|
|
//--- Now we are sure that A contains properly initialized sparse
|
|
//--- matrix (or some appropriate dummy for M=0) and we have NNZ>0
|
|
//--- (no need to care about degenerate cases).
|
|
//--- Append rows to A:
|
|
//--- * append data
|
|
//--- * sort in place
|
|
//--- * merge duplicate indexes
|
|
//--- * compute DIdx and UIdx
|
|
for(i=0; i<nnz; i++)
|
|
{
|
|
State.m_a.m_Idx.Set(offs+i,idxa[i]);
|
|
State.m_a.m_Vals.Set(offs+i,vala[i]);
|
|
}
|
|
CTSort::TagSortMiddleIR(State.m_a.m_Idx,State.m_a.m_Vals,offs,nnz);
|
|
offsdst=offs;
|
|
for(i=1; i<nnz; i++)
|
|
{
|
|
if(State.m_a.m_Idx[offsdst]!=State.m_a.m_Idx[offs+i])
|
|
{
|
|
offsdst++;
|
|
State.m_a.m_Idx.Set(offsdst,State.m_a.m_Idx[offs+i]);
|
|
State.m_a.m_Vals.Set(offsdst,State.m_a.m_Vals[offs+i]);
|
|
}
|
|
else
|
|
State.m_a.m_Vals.Add(offsdst,State.m_a.m_Vals[offs+i]);
|
|
}
|
|
nnz=offsdst-offs+1;
|
|
uidx=-1;
|
|
didx=-1;
|
|
for(j=offs; j<=offsdst; j++)
|
|
{
|
|
k=State.m_a.m_Idx[j];
|
|
if(k==m)
|
|
didx=j;
|
|
else
|
|
if(k>m && uidx==-1)
|
|
{
|
|
uidx=j;
|
|
break;
|
|
}
|
|
}
|
|
if(uidx==-1)
|
|
uidx=offsdst+1;
|
|
if(didx==-1)
|
|
didx=uidx;
|
|
State.m_a.m_DIdx.Set(m,didx);
|
|
State.m_a.m_UIdx.Set(m,uidx);
|
|
State.m_a.m_RIdx.Set(m+1,offsdst+1);
|
|
State.m_a.m_M=m+1;
|
|
State.m_a.m_NInitialized+=nnz;
|
|
State.m_al.Set(m,al);
|
|
State.m_au.Set(m,au);
|
|
State.m_m=m+1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves LP problem. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| You should use MinLPResults() function to access results after |
|
|
//| calls to this function. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPOptimize(CMinLPState &State)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int m=State.m_m;
|
|
int i=0;
|
|
double v=0;
|
|
bool badconstr=false;
|
|
CDualSimplexSettings Settings;
|
|
CMatrixDouble dummy;
|
|
CDualSimplexBasis dummybasis;
|
|
|
|
ClearReportFields(State);
|
|
//--- Most basic check for correctness of constraints
|
|
badconstr=false;
|
|
for(i=0; i<n; i++)
|
|
if((MathIsValidNumber(State.m_bndl[i]) && MathIsValidNumber(State.m_bndu[i])) && State.m_bndl[i]>State.m_bndu[i])
|
|
badconstr=true;
|
|
for(i=0; i<m; i++)
|
|
if((MathIsValidNumber(State.m_al[i]) && MathIsValidNumber(State.m_au[i])) && State.m_al[i]>State.m_au[i])
|
|
badconstr=true;
|
|
if(badconstr)
|
|
{
|
|
State.m_repterminationtype=-3;
|
|
State.m_repn=n;
|
|
State.m_repm=m;
|
|
CAblasF::RSetAllocV(n,0.0,State.m_xs);
|
|
CAblasF::RSetAllocV(n,0.0,State.m_lagbc);
|
|
CAblasF::RSetAllocV(m,0.0,State.m_laglc);
|
|
CAblasF::ISetAllocV(n+m,0,State.m_cs);
|
|
State.m_repf=0;
|
|
State.m_repprimalerror=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(MathIsValidNumber(State.m_bndl[i]))
|
|
State.m_repprimalerror=MathMax(State.m_repprimalerror,State.m_bndl[i]-0);
|
|
if(MathIsValidNumber(State.m_bndu[i]))
|
|
State.m_repprimalerror=MathMax(State.m_repprimalerror,0-State.m_bndu[i]);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(MathIsValidNumber(State.m_al[i]))
|
|
State.m_repprimalerror=MathMax(State.m_repprimalerror,State.m_al[i]-0);
|
|
if(MathIsValidNumber(State.m_au[i]))
|
|
State.m_repprimalerror=MathMax(State.m_repprimalerror,0-State.m_au[i]);
|
|
}
|
|
State.m_repdualerror=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
State.m_repdualerror=MathMax(State.m_repdualerror,MathAbs(State.m_c[i]));
|
|
}
|
|
State.m_repslackerror=0;
|
|
return;
|
|
}
|
|
//--- Call current solver
|
|
if(State.m_algokind==1 || State.m_algokind==2)
|
|
{
|
|
//--- Call the solver
|
|
if(State.m_algokind==1)
|
|
{
|
|
//--- Dual simplex method with presolve
|
|
CLPQPPresolve::PresolveNoneScaleUser(State.m_s,State.m_c,State.m_bndl,State.m_bndu,n,State.m_a,State.m_al,State.m_au,m,State.m_presolver);
|
|
CRevisedDualSimplex::DSSSettingsInit(Settings);
|
|
Settings.m_xtolabs=State.m_dsseps;
|
|
Settings.m_dtolabs=State.m_dsseps;
|
|
CRevisedDualSimplex::DSSInit(State.m_presolver.m_newn,State.m_dss);
|
|
CRevisedDualSimplex::DSSSetProblem(State.m_dss,State.m_presolver.m_c,State.m_presolver.m_bndl,State.m_presolver.m_bndu,dummy,State.m_presolver.m_sparsea,1,State.m_presolver.m_al,State.m_presolver.m_au,State.m_presolver.m_newm,dummybasis,m_alllogicalsbasis,Settings);
|
|
CRevisedDualSimplex::DSSOptimize(State.m_dss,Settings);
|
|
//--- Export results, convert from presolve
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xs,State.m_presolver.m_newn);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_lagbc,State.m_presolver.m_newn);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_laglc,State.m_presolver.m_newm);
|
|
CApServ::IVectorSetLengthAtLeast(State.m_cs,State.m_presolver.m_newn+State.m_presolver.m_newm);
|
|
State.m_xs=State.m_dss.m_repx;
|
|
State.m_lagbc=State.m_dss.m_replagbc;
|
|
State.m_laglc=State.m_dss.m_replaglc;
|
|
State.m_cs=State.m_dss.m_repstats;
|
|
State.m_repiterationscount=State.m_dss.m_repiterationscount;
|
|
State.m_repterminationtype=State.m_dss.m_repterminationtype;
|
|
CLPQPPresolve::PresolveBwd(State.m_presolver,State.m_xs,State.m_cs,State.m_lagbc,State.m_laglc);
|
|
State.m_repn=n;
|
|
State.m_repm=m;
|
|
}
|
|
if(State.m_algokind==2)
|
|
{
|
|
//--- Interior point method with presolve
|
|
CLPQPPresolve::PresolveNoneScaleUser(State.m_s,State.m_c,State.m_bndl,State.m_bndu,n,State.m_a,State.m_al,State.m_au,m,State.m_presolver);
|
|
CAblasF::RSetAllocV(State.m_presolver.m_newn,1.0,State.m_units);
|
|
CAblasF::RSetAllocV(State.m_presolver.m_newn,0.0,State.m_zeroorigin);
|
|
CSparse::SparseCreateSKSBandBuf(State.m_presolver.m_newn,State.m_presolver.m_newn,0,State.m_ipmquadratic);
|
|
for(i=0; i<State.m_presolver.m_newn; i++)
|
|
CSparse::SparseSet(State.m_ipmquadratic,i,i,State.m_ipmlambda);
|
|
CSparse::SparseConvertToCRS(State.m_ipmquadratic);
|
|
CVIPMSolver::VIPMInitSparse(State.m_ipm,State.m_units,State.m_zeroorigin,State.m_presolver.m_newn);
|
|
CVIPMSolver::VIPMSetQuadraticLinear(State.m_ipm,dummy,State.m_ipmquadratic,1,false,State.m_presolver.m_c);
|
|
CVIPMSolver::VIPMSetConstraints(State.m_ipm,State.m_presolver.m_bndl,State.m_presolver.m_bndu,State.m_presolver.m_sparsea,State.m_presolver.m_newm,dummy,0,State.m_presolver.m_al,State.m_presolver.m_au);
|
|
CVIPMSolver::VIPMSetCond(State.m_ipm,State.m_ipmeps,State.m_ipmeps,State.m_ipmeps);
|
|
CVIPMSolver::VIPMOptimize(State.m_ipm,true,State.m_xs,State.m_lagbc,State.m_laglc,State.m_repterminationtype);
|
|
//--- Export results, convert from presolve
|
|
CAblasF::ISetAllocV(State.m_presolver.m_newn+State.m_presolver.m_newm,0,State.m_cs);
|
|
CLPQPPresolve::PresolveBwd(State.m_presolver,State.m_xs,State.m_cs,State.m_lagbc,State.m_laglc);
|
|
State.m_repn=n;
|
|
State.m_repm=m;
|
|
State.m_repiterationscount=State.m_ipm.m_repiterationscount;
|
|
}
|
|
//--- Compute F, primal and dual errors
|
|
State.m_repf=CAblasF::RDotV(n,State.m_xs,State.m_c);
|
|
State.m_repprimalerror=0;
|
|
State.m_repdualerror=0;
|
|
State.m_repslackerror=0;
|
|
State.m_tmpg=State.m_c;
|
|
if(m>0)
|
|
{
|
|
CSparse::SparseMV(State.m_a,State.m_xs,State.m_tmpax);
|
|
CSparse::SparseGemV(State.m_a,1.0,1,State.m_laglc,0,1.0,State.m_tmpg,0);
|
|
}
|
|
CAblasF::RAddV(n,1.0,State.m_lagbc,State.m_tmpg);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(MathIsValidNumber(State.m_bndl[i]))
|
|
{
|
|
State.m_repprimalerror=MathMax(State.m_repprimalerror,State.m_bndl[i]-State.m_xs[i]);
|
|
State.m_repslackerror=MathMax(State.m_repslackerror,MathMax(State.m_xs[i]-State.m_bndl[i],0.0)*MathMax(-State.m_lagbc[i],0.0));
|
|
}
|
|
if(MathIsValidNumber(State.m_bndu[i]))
|
|
{
|
|
State.m_repprimalerror=MathMax(State.m_repprimalerror,State.m_xs[i]-State.m_bndu[i]);
|
|
State.m_repslackerror=MathMax(State.m_repslackerror,MathMax(State.m_bndu[i]-State.m_xs[i],0.0)*MathMax(State.m_lagbc[i],0.0));
|
|
}
|
|
State.m_repdualerror=MathMax(State.m_repdualerror,MathAbs(State.m_tmpg[i]));
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
v=State.m_tmpax[i];
|
|
if(MathIsValidNumber(State.m_al[i]))
|
|
{
|
|
State.m_repprimalerror=MathMax(State.m_repprimalerror,State.m_al[i]-v);
|
|
State.m_repslackerror=MathMax(State.m_repslackerror,MathMax(v-State.m_al[i],0.0)*MathMax(-State.m_laglc[i],0.0));
|
|
}
|
|
if(MathIsValidNumber(State.m_au[i]))
|
|
{
|
|
State.m_repprimalerror=MathMax(State.m_repprimalerror,v-State.m_au[i]);
|
|
State.m_repslackerror=MathMax(State.m_repslackerror,MathMax(State.m_au[i]-v,0.0)*MathMax(State.m_laglc[i],0.0));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
//--- Integrity check failed - unknown solver
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed - unknown solver");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| LP solver results |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[N], solution (on failure: last trial point) |
|
|
//| Rep - optimization report. You should check |
|
|
//| Rep.TerminationType, which contains completion |
|
|
//| code, and you may check another fields which |
|
|
//| contain another information about algorithm |
|
|
//| functioning. |
|
|
//| Failure codes returned by algorithm are: |
|
|
//| * -4 LP problem is primal unbounded(dual infeasible) |
|
|
//| * -3 LP problem is primal infeasible(dual unbounded) |
|
|
//| * -2 IPM solver detected that problem is either infeasible |
|
|
//| or unbounded |
|
|
//| Success codes: |
|
|
//| * 1..4 successful completion |
|
|
//| * 5 MaxIts steps was taken |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPResults(CMinLPState &State,CRowDouble &x,
|
|
CMinLPReport &rep)
|
|
{
|
|
x.Resize(0);
|
|
MinLPResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| LP results |
|
|
//| Buffered implementation of MinLPResults() which uses |
|
|
//| pre-allocated buffer to store X[]. If buffer size is too small, |
|
|
//| it resizes buffer. It is intended to be used in the inner cycles |
|
|
//| of performance critical algorithms where array reallocation |
|
|
//| penalty is too large to be ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::MinLPResultsBuf(CMinLPState &State,
|
|
CRowDouble &x,CMinLPReport &rep)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int repn=0;
|
|
int repm=0;
|
|
repn=State.m_repn;
|
|
repm=State.m_repm;
|
|
rep.m_y.Resize(repm);
|
|
rep.m_stats.Resize(repn+repm);
|
|
rep.m_f=State.m_repf;
|
|
rep.m_primalerror=State.m_repprimalerror;
|
|
rep.m_dualerror=State.m_repdualerror;
|
|
rep.m_slackerror=State.m_repslackerror;
|
|
rep.m_iterationscount=State.m_repiterationscount;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
rep.m_laglc=State.m_laglc;
|
|
rep.m_lagbc=State.m_lagbc;
|
|
x=State.m_xs;
|
|
rep.m_y=rep.m_laglc.ToVector()*(-1.0);
|
|
State.m_cs=rep.m_stats;
|
|
if(CAp::Len(x)!=repn)
|
|
x.Resize(repn);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Clear report fields prior to the optimization. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinLP::ClearReportFields(CMinLPState &State)
|
|
{
|
|
State.m_repf=0.0;
|
|
State.m_repprimalerror=0.0;
|
|
State.m_repdualerror=0.0;
|
|
State.m_repiterationscount=0;
|
|
State.m_repterminationtype=0;
|
|
State.m_repn=0;
|
|
State.m_repm=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries of SLP subsolver. |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSLPSubsolver
|
|
{
|
|
int m_curdcnt;
|
|
int m_hessiantype;
|
|
bool m_basispresent;
|
|
CSparseMatrix m_sparsedummy;
|
|
CSparseMatrix m_sparseefflc;
|
|
CSparseMatrix m_sparserawlc;
|
|
CRowInt m_cs;
|
|
CRowDouble m_cural;
|
|
CRowDouble m_curau;
|
|
CRowDouble m_curb;
|
|
CRowDouble m_curbndl;
|
|
CRowDouble m_curbndu;
|
|
CRowDouble m_lagbc;
|
|
CRowDouble m_laglc;
|
|
CRowDouble m_sk;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CRowDouble m_xs;
|
|
CRowDouble m_yk;
|
|
CPresolveInfo m_presolver;
|
|
CMatrixDouble m_curd;
|
|
CMatrixDouble m_curhd;
|
|
CMatrixDouble m_densedummy;
|
|
CMatrixDouble m_h;
|
|
CDualSimplexState m_dss;
|
|
CDualSimplexSettings m_dsssettings;
|
|
CDualSimplexBasis m_lastbasis;
|
|
//--- constructor / destructor
|
|
CMinSLPSubsolver(void);
|
|
~CMinSLPSubsolver(void) {}
|
|
void Copy(const CMinSLPSubsolver &obj);
|
|
//--- overloading
|
|
void operator=(const CMinSLPSubsolver &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinSLPSubsolver::CMinSLPSubsolver(void)
|
|
{
|
|
m_curdcnt=0;
|
|
m_hessiantype=0;
|
|
m_basispresent=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinSLPSubsolver::Copy(const CMinSLPSubsolver &obj)
|
|
{
|
|
m_curdcnt=obj.m_curdcnt;
|
|
m_hessiantype=obj.m_hessiantype;
|
|
m_basispresent=obj.m_basispresent;
|
|
m_sparsedummy=obj.m_sparsedummy;
|
|
m_sparseefflc=obj.m_sparseefflc;
|
|
m_sparserawlc=obj.m_sparserawlc;
|
|
m_cs=obj.m_cs;
|
|
m_cural=obj.m_cural;
|
|
m_curau=obj.m_curau;
|
|
m_curb=obj.m_curb;
|
|
m_curbndl=obj.m_curbndl;
|
|
m_curbndu=obj.m_curbndu;
|
|
m_lagbc=obj.m_lagbc;
|
|
m_laglc=obj.m_laglc;
|
|
m_sk=obj.m_sk;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_xs=obj.m_xs;
|
|
m_yk=obj.m_yk;
|
|
m_presolver=obj.m_presolver;
|
|
m_curd=obj.m_curd;
|
|
m_curhd=obj.m_curhd;
|
|
m_densedummy=obj.m_densedummy;
|
|
m_h=obj.m_h;
|
|
m_dss=obj.m_dss;
|
|
m_dsssettings=obj.m_dsssettings;
|
|
m_lastbasis=obj.m_lastbasis;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries for LagrangianFG() function |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSLPTmpLagrangian
|
|
{
|
|
CRowDouble m_sclagtmp0;
|
|
CRowDouble m_sclagtmp1;
|
|
//--- constructor / destructor
|
|
CMinSLPTmpLagrangian(void) {}
|
|
~CMinSLPTmpLagrangian(void) {}
|
|
void Copy(const CMinSLPTmpLagrangian &obj);
|
|
//--- overloading
|
|
void operator=(const CMinSLPTmpLagrangian &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinSLPTmpLagrangian::Copy(const CMinSLPTmpLagrangian &obj)
|
|
{
|
|
m_sclagtmp0=obj.m_sclagtmp0;
|
|
m_sclagtmp1=obj.m_sclagtmp1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries for LagrangianFG() function |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSLPTmpMerit
|
|
{
|
|
CRowDouble m_mftmp0;
|
|
|
|
CMinSLPTmpMerit(void) {}
|
|
~CMinSLPTmpMerit(void) {}
|
|
|
|
void Copy(const CMinSLPTmpMerit &obj);
|
|
//--- overloading
|
|
void operator=(const CMinSLPTmpMerit &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinSLPTmpMerit::Copy(const CMinSLPTmpMerit &obj)
|
|
{
|
|
m_mftmp0=obj.m_mftmp0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries of Phase13 SLP subsolver. |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSLPPhase13State
|
|
{
|
|
bool m_usecorrection;
|
|
RCommState m_rphase13state;
|
|
CRowDouble m_d;
|
|
CRowDouble m_dummylagmult;
|
|
CRowDouble m_dx;
|
|
CRowDouble m_stepkfic;
|
|
CRowDouble m_stepkfin;
|
|
CRowDouble m_stepkxc;
|
|
CRowDouble m_stepkxn;
|
|
CMinSLPTmpMerit m_tmpmerit;
|
|
CMatrixDouble m_stepkjc;
|
|
CMatrixDouble m_stepkjn;
|
|
//--- constructor / destructor
|
|
CMinSLPPhase13State(void) { m_usecorrection=false; }
|
|
~CMinSLPPhase13State(void) {}
|
|
//---
|
|
void Copy(const CMinSLPPhase13State &obj);
|
|
//--- overloading
|
|
void operator=(const CMinSLPPhase13State &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinSLPPhase13State::Copy(const CMinSLPPhase13State &obj)
|
|
{
|
|
m_usecorrection=obj.m_usecorrection;
|
|
m_rphase13state=obj.m_rphase13state;
|
|
m_d=obj.m_d;
|
|
m_dummylagmult=obj.m_dummylagmult;
|
|
m_dx=obj.m_dx;
|
|
m_stepkfic=obj.m_stepkfic;
|
|
m_stepkfin=obj.m_stepkfin;
|
|
m_stepkxc=obj.m_stepkxc;
|
|
m_stepkxn=obj.m_stepkxn;
|
|
m_tmpmerit=obj.m_tmpmerit;
|
|
m_stepkjc=obj.m_stepkjc;
|
|
m_stepkjn=obj.m_stepkjn;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries of Phase13 SLP subsolver. |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSLPPhase2State
|
|
{
|
|
int m_lastlcidx;
|
|
int m_lastnlcidx;
|
|
double m_lastlcerr;
|
|
double m_lastnlcerr;
|
|
RCommState m_rphase2state;
|
|
CRowDouble m_d;
|
|
CRowDouble m_meritlagmult;
|
|
CRowDouble m_stepkfic;
|
|
CRowDouble m_stepkfin;
|
|
CRowDouble m_stepklaggrad;
|
|
CRowDouble m_stepknlaggrad;
|
|
CRowDouble m_stepknlagmult;
|
|
CRowDouble m_stepkxc;
|
|
CRowDouble m_stepkxn;
|
|
CRowDouble m_tmp0;
|
|
CMinSLPTmpMerit m_tmpmerit;
|
|
CMinSLPTmpLagrangian m_tmplagrangianfg;
|
|
CMatrixDouble m_stepkjc;
|
|
CMatrixDouble m_stepkjn;
|
|
CLinMinState m_mcstate;
|
|
//--- constructor / destructor
|
|
CMinSLPPhase2State(void);
|
|
~CMinSLPPhase2State(void) {}
|
|
void Copy(const CMinSLPPhase2State &obj);
|
|
//--- overloading
|
|
void operator=(const CMinSLPPhase2State &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinSLPPhase2State::CMinSLPPhase2State(void)
|
|
{
|
|
m_lastlcidx=0;
|
|
m_lastnlcidx=0;
|
|
m_lastlcerr=0;
|
|
m_lastnlcerr=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinSLPPhase2State::Copy(const CMinSLPPhase2State &obj)
|
|
{
|
|
m_lastlcidx=obj.m_lastlcidx;
|
|
m_lastnlcidx=obj.m_lastnlcidx;
|
|
m_lastlcerr=obj.m_lastlcerr;
|
|
m_lastnlcerr=obj.m_lastnlcerr;
|
|
m_rphase2state=obj.m_rphase2state;
|
|
m_d=obj.m_d;
|
|
m_meritlagmult=obj.m_meritlagmult;
|
|
m_stepkfic=obj.m_stepkfic;
|
|
m_stepkfin=obj.m_stepkfin;
|
|
m_stepklaggrad=obj.m_stepklaggrad;
|
|
m_stepknlaggrad=obj.m_stepknlaggrad;
|
|
m_stepknlagmult=obj.m_stepknlagmult;
|
|
m_stepkxc=obj.m_stepkxc;
|
|
m_stepkxn=obj.m_stepkxn;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmpmerit=obj.m_tmpmerit;
|
|
m_tmplagrangianfg=obj.m_tmplagrangianfg;
|
|
m_stepkjc=obj.m_stepkjc;
|
|
m_stepkjn=obj.m_stepkjn;
|
|
m_mcstate=obj.m_mcstate;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries of SLP solver. |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinSLPState
|
|
{
|
|
int m_fstagnationcnt;
|
|
int m_hessiantype;
|
|
int m_historylen;
|
|
int m_lpfailurecnt;
|
|
int m_maxits;
|
|
int m_n;
|
|
int m_nec;
|
|
int m_nic;
|
|
int m_nlec;
|
|
int m_nlic;
|
|
int m_repbcidx;
|
|
int m_repinneriterationscount;
|
|
int m_replcidx;
|
|
int m_repnlcidx;
|
|
int m_repouteriterationscount;
|
|
int m_repsimplexiterations1;
|
|
int m_repsimplexiterations2;
|
|
int m_repsimplexiterations3;
|
|
int m_repsimplexiterations;
|
|
int m_repterminationtype;
|
|
double m_bigc;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_repbcerr;
|
|
double m_replcerr;
|
|
double m_repnlcerr;
|
|
double m_trustrad;
|
|
bool m_HasBndL[];
|
|
bool m_HasBndU[];
|
|
bool m_needfij;
|
|
bool m_xupdated;
|
|
RCommState m_rstate;
|
|
CRowInt m_lcsrcidx;
|
|
CRowDouble m_backupfi;
|
|
CRowDouble m_backupx;
|
|
CRowDouble m_dummylagmult;
|
|
CRowDouble m_fi;
|
|
CRowDouble m_fscales;
|
|
CRowDouble m_maxlaghistory;
|
|
CRowDouble m_meritfunctionhistory;
|
|
CRowDouble m_meritlagmult;
|
|
CRowDouble m_s;
|
|
CRowDouble m_scaledbndl;
|
|
CRowDouble m_scaledbndu;
|
|
CRowDouble m_step0fi;
|
|
CRowDouble m_step0x;
|
|
CRowDouble m_stepkfi;
|
|
CRowDouble m_stepkx;
|
|
CRowDouble m_x;
|
|
CMinSLPTmpMerit m_tmpmerit;
|
|
CMinSLPSubsolver m_subsolver;
|
|
CMinSLPPhase2State m_state2;
|
|
CMinSLPPhase13State m_state13;
|
|
CMatrixDouble m_backupj;
|
|
CMatrixDouble m_j;
|
|
CMatrixDouble m_scaledcleic;
|
|
CMatrixDouble m_step0j;
|
|
CMatrixDouble m_stepkj;
|
|
//--- constructor / destructor
|
|
CMinSLPState(void);
|
|
~CMinSLPState(void) {}
|
|
//---
|
|
void Copy(const CMinSLPState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinSLPState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinSLPState::CMinSLPState(void)
|
|
{
|
|
m_fstagnationcnt=0;
|
|
m_hessiantype=0;
|
|
m_historylen=0;
|
|
m_lpfailurecnt=0;
|
|
m_maxits=0;
|
|
m_n=0;
|
|
m_nec=0;
|
|
m_nic=0;
|
|
m_nlec=0;
|
|
m_nlic=0;
|
|
m_repbcidx=0;
|
|
m_repinneriterationscount=0;
|
|
m_replcidx=0;
|
|
m_repnlcidx=0;
|
|
m_repouteriterationscount=0;
|
|
m_repsimplexiterations1=0;
|
|
m_repsimplexiterations2=0;
|
|
m_repsimplexiterations3=0;
|
|
m_repsimplexiterations=0;
|
|
m_repterminationtype=0;
|
|
m_bigc=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_repbcerr=0;
|
|
m_replcerr=0;
|
|
m_repnlcerr=0;
|
|
m_trustrad=0;
|
|
m_needfij=false;
|
|
m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinSLPState::Copy(const CMinSLPState &obj)
|
|
{
|
|
m_fstagnationcnt=obj.m_fstagnationcnt;
|
|
m_hessiantype=obj.m_hessiantype;
|
|
m_historylen=obj.m_historylen;
|
|
m_lpfailurecnt=obj.m_lpfailurecnt;
|
|
m_maxits=obj.m_maxits;
|
|
m_n=obj.m_n;
|
|
m_nec=obj.m_nec;
|
|
m_nic=obj.m_nic;
|
|
m_nlec=obj.m_nlec;
|
|
m_nlic=obj.m_nlic;
|
|
m_repbcidx=obj.m_repbcidx;
|
|
m_repinneriterationscount=obj.m_repinneriterationscount;
|
|
m_replcidx=obj.m_replcidx;
|
|
m_repnlcidx=obj.m_repnlcidx;
|
|
m_repouteriterationscount=obj.m_repouteriterationscount;
|
|
m_repsimplexiterations1=obj.m_repsimplexiterations1;
|
|
m_repsimplexiterations2=obj.m_repsimplexiterations2;
|
|
m_repsimplexiterations3=obj.m_repsimplexiterations3;
|
|
m_repsimplexiterations=obj.m_repsimplexiterations;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_bigc=obj.m_bigc;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_repbcerr=obj.m_repbcerr;
|
|
m_replcerr=obj.m_replcerr;
|
|
m_repnlcerr=obj.m_repnlcerr;
|
|
m_trustrad=obj.m_trustrad;
|
|
ArrayCopy(m_HasBndL,obj.m_HasBndL);
|
|
ArrayCopy(m_HasBndU,obj.m_HasBndU);
|
|
m_needfij=obj.m_needfij;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_rstate=obj.m_rstate;
|
|
m_lcsrcidx=obj.m_lcsrcidx;
|
|
m_backupfi=obj.m_backupfi;
|
|
m_backupx=obj.m_backupx;
|
|
m_dummylagmult=obj.m_dummylagmult;
|
|
m_fi=obj.m_fi;
|
|
m_fscales=obj.m_fscales;
|
|
m_maxlaghistory=obj.m_maxlaghistory;
|
|
m_meritfunctionhistory=obj.m_meritfunctionhistory;
|
|
m_meritlagmult=obj.m_meritlagmult;
|
|
m_s=obj.m_s;
|
|
m_scaledbndl=obj.m_scaledbndl;
|
|
m_scaledbndu=obj.m_scaledbndu;
|
|
m_step0fi=obj.m_step0fi;
|
|
m_step0x=obj.m_step0x;
|
|
m_stepkfi=obj.m_stepkfi;
|
|
m_stepkx=obj.m_stepkx;
|
|
m_x=obj.m_x;
|
|
m_tmpmerit=obj.m_tmpmerit;
|
|
m_subsolver=obj.m_subsolver;
|
|
m_state2=obj.m_state2;
|
|
m_state13=obj.m_state13;
|
|
m_backupj=obj.m_backupj;
|
|
m_j=obj.m_j;
|
|
m_scaledcleic=obj.m_scaledcleic;
|
|
m_step0j=obj.m_step0j;
|
|
m_stepkj=obj.m_stepkj;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CNLCSLP
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const double m_slpstpclosetozero;
|
|
static const double m_slpdeltadecrease;
|
|
static const double m_slpdeltaincrease;
|
|
static const double m_slpstpclosetoone;
|
|
static const double m_maxtrustraddecay;
|
|
static const double m_maxtrustradgrowth;
|
|
static const double m_slpgtol;
|
|
static const double m_initbigc;
|
|
static const double m_maxbigc;
|
|
static const double m_bfgstol;
|
|
static const double m_defaultl1penalty;
|
|
static const double m_meritfunctionbase;
|
|
static const double m_meritfunctiongain;
|
|
static const double m_inequalitydampingfactor;
|
|
static const double m_augmentationfactor;
|
|
static const double m_inittrustrad;
|
|
static const double m_stagnationepsf;
|
|
static const int m_lpfailureslimit;
|
|
static const int m_fstagnationlimit;
|
|
static const int m_nondescentlimit;
|
|
static const int m_nonmonotonicphase2limit;
|
|
static const double m_slpbigscale;
|
|
static const double m_slpsmallscale;
|
|
static const double m_defaultmaglagdecay;
|
|
|
|
static void MinSLPInitBuf(CRowDouble &bndl,CRowDouble &bndu,CRowDouble &s,CRowDouble &x0,int n,CMatrixDouble &cleic,CRowInt &lcsrcidx,int nec,int nic,int nlec,int nlic,double epsx,int m_maxits,CMinSLPState &State);
|
|
static bool MinSLPIteration(CMinSLPState &State,CSmoothnessMonitor &smonitor,bool userterminationneeded);
|
|
|
|
private:
|
|
static void InitLPSubsolver(CMinSLPState &sstate,CMinSLPSubsolver &subsolver,int hessiantype);
|
|
static void LPSubproblemRestart(CMinSLPState &sstate,CMinSLPSubsolver &subsolver);
|
|
static void LPSubproblemUpdateHessian(CMinSLPState &sstate,CMinSLPSubsolver &subsolver,CRowDouble &x0,CRowDouble &g0,CRowDouble &x1,CRowDouble &g1);
|
|
static bool LPSubproblemSolve(CMinSLPState &State,CMinSLPSubsolver &subsolver,CRowDouble &x,CRowDouble &fi,CMatrixDouble &jac,int innerk,CRowDouble &d,CRowDouble &lagmult);
|
|
static void LPSubproblemAppendConjugacyConstraint(CMinSLPState &State,CMinSLPSubsolver &subsolver,CRowDouble &d);
|
|
static void Phase13Init(CMinSLPPhase13State &state13,int n,int nec,int nic,int nlec,int nlic,bool usecorrection);
|
|
static bool Phase13Iteration(CMinSLPState &State,CMinSLPPhase13State &state13,CSmoothnessMonitor &smonitor,bool userterminationneeded,CRowDouble &curx,CRowDouble &curfi,CMatrixDouble &curj,CRowDouble &lagmult,int &status,double &dnrm,double &stp);
|
|
static void Phase2Init(CMinSLPPhase2State &state2,int n,int nec,int nic,int nlec,int nlic,CRowDouble &meritlagmult);
|
|
static bool Phase2Iteration(CMinSLPState &State,CMinSLPPhase2State &state2,CSmoothnessMonitor &smonitor,bool userterminationneeded,CRowDouble &curx,CRowDouble &curfi,CMatrixDouble &curj,CRowDouble &lagmult,double &gammamax,int &status);
|
|
static void SLPSendX(CMinSLPState &State,CRowDouble &xs);
|
|
static bool SLPRetrieveFIJ(CMinSLPState &State,CRowDouble &fis,CMatrixDouble &js);
|
|
static void SLPCopyState(CMinSLPState &State,CRowDouble &x0,CRowDouble &fi0,CMatrixDouble &j0,CRowDouble &x1,CRowDouble &fi1,CMatrixDouble &j1);
|
|
static void LagrangianFG(CMinSLPState &State,CRowDouble &x,double trustrad,CRowDouble &fi,CMatrixDouble &j,CRowDouble &lagmult,CMinSLPTmpLagrangian &tmp,double &f,CRowDouble &g,double &lcerr,int &lcidx,double &nlcerr,int &nlcidx);
|
|
static double MeritFunction(CMinSLPState &State,CRowDouble &x,CRowDouble &fi,CRowDouble &lagmult,double mu,CMinSLPTmpMerit &tmp);
|
|
static double RawLagrangian(CMinSLPState &State,CRowDouble &x,CRowDouble &fi,CRowDouble &lagmult,CMinSLPTmpMerit &tmp);
|
|
static void MeritFunctionAndRawLagrangian(CMinSLPState &State,CRowDouble &x,CRowDouble &fi,CRowDouble &lagmult,double mu,CMinSLPTmpMerit &tmp,double &meritf,double &rawlag);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CNLCSLP::m_slpstpclosetozero=0.001;
|
|
const double CNLCSLP::m_slpdeltadecrease=0.20;
|
|
const double CNLCSLP::m_slpdeltaincrease=0.80;
|
|
const double CNLCSLP::m_slpstpclosetoone=0.95;
|
|
const double CNLCSLP::m_maxtrustraddecay=0.1;
|
|
const double CNLCSLP::m_maxtrustradgrowth=1.333;
|
|
const double CNLCSLP::m_slpgtol=0.4;
|
|
const double CNLCSLP::m_initbigc=500.0;
|
|
const double CNLCSLP::m_maxbigc=1.0E5;
|
|
const double CNLCSLP::m_bfgstol=1.0E-5;
|
|
const double CNLCSLP::m_defaultl1penalty=0.1;
|
|
const double CNLCSLP::m_meritfunctionbase=0.0;
|
|
const double CNLCSLP::m_meritfunctiongain=2.0;
|
|
const double CNLCSLP::m_inequalitydampingfactor=10.0;
|
|
const double CNLCSLP::m_augmentationfactor=10.0;
|
|
const double CNLCSLP::m_inittrustrad=0.1;
|
|
const double CNLCSLP::m_stagnationepsf=1.0E-12;
|
|
const int CNLCSLP::m_lpfailureslimit=20;
|
|
const int CNLCSLP::m_fstagnationlimit=20;
|
|
const int CNLCSLP::m_nondescentlimit=99999;
|
|
const int CNLCSLP::m_nonmonotonicphase2limit=5;
|
|
const double CNLCSLP::m_slpbigscale=5.0;
|
|
const double CNLCSLP::m_slpsmallscale=0.2;
|
|
const double CNLCSLP::m_defaultmaglagdecay=0.85;
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::MinSLPInitBuf(CRowDouble &bndl,
|
|
CRowDouble &bndu,
|
|
CRowDouble &s,
|
|
CRowDouble &x0,
|
|
int n,
|
|
CMatrixDouble &cleic,
|
|
CRowInt &lcsrcidx,
|
|
int nec,
|
|
int nic,
|
|
int nlec,
|
|
int nlic,
|
|
double epsx,
|
|
int m_maxits,
|
|
CMinSLPState &State)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
|
|
State.m_n=n;
|
|
State.m_nec=nec;
|
|
State.m_nic=nic;
|
|
State.m_nlec=nlec;
|
|
State.m_nlic=nlic;
|
|
//--- Settings
|
|
State.m_hessiantype=2;
|
|
//--- Prepare RCOMM State
|
|
State.m_rstate.ia.Resize(10);
|
|
ArrayResize(State.m_rstate.ba,4);
|
|
State.m_rstate.ra.Resize(17);
|
|
State.m_rstate.stage=-1;
|
|
State.m_needfij=false;
|
|
State.m_xupdated=false;
|
|
State.m_x.Resize(n);
|
|
State.m_fi.Resize(1+nlec+nlic);
|
|
State.m_j.Resize(1+nlec+nlic,n);
|
|
//--- Allocate memory.
|
|
CApServ::RVectorSetLengthAtLeast(State.m_s,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_step0x,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_stepkx,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_backupx,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_step0fi,1+nlec+nlic);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_stepkfi,1+nlec+nlic);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_backupfi,1+nlec+nlic);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_step0j,1+nlec+nlic,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_stepkj,1+nlec+nlic,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_backupj,1+nlec+nlic,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fscales,1+nlec+nlic);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_meritlagmult,nec+nic+nlec+nlic);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_dummylagmult,nec+nic+nlec+nlic);
|
|
ArrayResize(State.m_HasBndL,n);
|
|
ArrayResize(State.m_HasBndU,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_scaledbndl,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_scaledbndu,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_scaledcleic,nec+nic,n+1);
|
|
CApServ::IVectorSetLengthAtLeast(State.m_lcsrcidx,nec+nic);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_meritfunctionhistory,m_nonmonotonicphase2limit+1);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_maxlaghistory,m_nonmonotonicphase2limit+1);
|
|
//--- Prepare scaled problem
|
|
for(i=0; i<n; i++)
|
|
{
|
|
State.m_HasBndL[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_HasBndU[i]=MathIsValidNumber(bndu[i]);
|
|
if(State.m_HasBndL[i])
|
|
State.m_scaledbndl.Set(i,bndl[i]/s[i]);
|
|
if(State.m_HasBndU[i])
|
|
State.m_scaledbndu.Set(i,bndu[i]/s[i]);
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i])
|
|
if(!CAp::Assert(bndl[i]<=bndu[i],__FUNCTION__+": integrity check failed,box constraints are inconsistent"))
|
|
return;
|
|
State.m_step0x.Set(i,x0[i]/s[i]);
|
|
State.m_s.Set(i,s[i]);
|
|
}
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
//--- Permutation
|
|
State.m_lcsrcidx.Set(i,lcsrcidx[i]);
|
|
//--- Scale and normalize linear constraints
|
|
vv=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=cleic.Get(i,j)*s[j];
|
|
State.m_scaledcleic.Set(i,j,v);
|
|
vv+=v*v;
|
|
}
|
|
vv=MathSqrt(vv);
|
|
State.m_scaledcleic.Set(i,n,cleic.Get(i,n));
|
|
if(vv>0.0)
|
|
State.m_scaledcleic.Row(i,State.m_scaledcleic[i]/vv);
|
|
}
|
|
//--- Initial enforcement of box constraints
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i])
|
|
State.m_step0x.Set(i,MathMax(State.m_step0x[i],State.m_scaledbndl[i]));
|
|
if(State.m_HasBndU[i])
|
|
State.m_step0x.Set(i,MathMin(State.m_step0x[i],State.m_scaledbndu[i]));
|
|
}
|
|
//--- Stopping criteria
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=m_maxits;
|
|
//--- Report fields
|
|
State.m_repsimplexiterations=0;
|
|
State.m_repsimplexiterations1=0;
|
|
State.m_repsimplexiterations2=0;
|
|
State.m_repsimplexiterations3=0;
|
|
State.m_repterminationtype=0;
|
|
State.m_repbcerr=0;
|
|
State.m_repbcidx=-1;
|
|
State.m_replcerr=0;
|
|
State.m_replcidx=-1;
|
|
State.m_repnlcerr=0;
|
|
State.m_repnlcidx=-1;
|
|
State.m_repinneriterationscount=0;
|
|
State.m_repouteriterationscount=0;
|
|
//--- Integrity checks:
|
|
//--- * it is important that significant step length is large enough that
|
|
//--- we do not decrease trust regiod radius; it should also be small,
|
|
//--- so we won't treat large steps as insignificant
|
|
if(!CAp::Assert(m_slpstpclosetozero<m_slpdeltadecrease,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(m_slpdeltadecrease<m_slpdeltaincrease,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(m_slpdeltaincrease<m_slpstpclosetoone,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs actual processing for SLP algorithm. It |
|
|
//| expects that caller redirects its reverse communication requests |
|
|
//| NeedFiJ / XUpdated to external user who will provide analytic |
|
|
//| derivative (or handle reports about progress). |
|
|
//| In case external user does not have analytic derivative, it is |
|
|
//| responsibility of caller to intercept NeedFiJ request and replace|
|
|
//| it with appropriate numerical differentiation scheme. |
|
|
//| Results are stored: |
|
|
//| * point - in State.StepKX |
|
|
//| IMPORTANT: this function works with scaled problem formulation; |
|
|
//| it is responsibility of the caller to UnScale request |
|
|
//| and scale Jacobian. |
|
|
//| NOTE: SMonitor is expected to be correctly initialized smoothness|
|
|
//| monitor. |
|
|
//+------------------------------------------------------------------+
|
|
bool CNLCSLP::MinSLPIteration(CMinSLPState &State,
|
|
CSmoothnessMonitor &smonitor,
|
|
bool userterminationneeded)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=0;
|
|
int nslack=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int nlec=0;
|
|
int nlic=0;
|
|
int i=0;
|
|
int j=0;
|
|
int innerk=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double mx=0;
|
|
bool lpstagesuccess=false;
|
|
double gammamax=0;
|
|
double f1=0;
|
|
double f2=0;
|
|
int status=0;
|
|
double stp=0;
|
|
double deltamax=0;
|
|
double multiplyby=0;
|
|
double setscaleto=0;
|
|
double prevtrustrad=0;
|
|
bool increasebigc=false;
|
|
double d1nrm=0;
|
|
double mu=0;
|
|
double expandedrad=0;
|
|
double tol=0;
|
|
bool dotrace=false;
|
|
bool dodetailedtrace=false;
|
|
double maxlag=0;
|
|
double maxhist=0;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
n=State.m_rstate.ia[0];
|
|
nslack=State.m_rstate.ia[1];
|
|
nec=State.m_rstate.ia[2];
|
|
nic=State.m_rstate.ia[3];
|
|
nlec=State.m_rstate.ia[4];
|
|
nlic=State.m_rstate.ia[5];
|
|
i=State.m_rstate.ia[6];
|
|
j=State.m_rstate.ia[7];
|
|
innerk=State.m_rstate.ia[8];
|
|
status=State.m_rstate.ia[9];
|
|
lpstagesuccess=State.m_rstate.ba[0];
|
|
increasebigc=State.m_rstate.ba[1];
|
|
dotrace=State.m_rstate.ba[2];
|
|
dodetailedtrace=State.m_rstate.ba[3];
|
|
v=State.m_rstate.ra[0];
|
|
vv=State.m_rstate.ra[1];
|
|
mx=State.m_rstate.ra[2];
|
|
gammamax=State.m_rstate.ra[3];
|
|
f1=State.m_rstate.ra[4];
|
|
f2=State.m_rstate.ra[5];
|
|
stp=State.m_rstate.ra[6];
|
|
deltamax=State.m_rstate.ra[7];
|
|
multiplyby=State.m_rstate.ra[8];
|
|
setscaleto=State.m_rstate.ra[9];
|
|
prevtrustrad=State.m_rstate.ra[10];
|
|
d1nrm=State.m_rstate.ra[11];
|
|
mu=State.m_rstate.ra[12];
|
|
expandedrad=State.m_rstate.ra[13];
|
|
tol=State.m_rstate.ra[14];
|
|
maxlag=State.m_rstate.ra[15];
|
|
maxhist=State.m_rstate.ra[16];
|
|
}
|
|
else
|
|
{
|
|
n=359;
|
|
nslack=-58;
|
|
nec=-919;
|
|
nic=-909;
|
|
nlec=81;
|
|
nlic=255;
|
|
i=74;
|
|
j=-788;
|
|
innerk=809;
|
|
status=205;
|
|
lpstagesuccess=false;
|
|
increasebigc=true;
|
|
dotrace=false;
|
|
dodetailedtrace=true;
|
|
v=-541;
|
|
vv=-698;
|
|
mx=-900;
|
|
gammamax=-318;
|
|
f1=-940;
|
|
f2=1016;
|
|
stp=-229;
|
|
deltamax=-536;
|
|
multiplyby=487;
|
|
setscaleto=-115;
|
|
prevtrustrad=886;
|
|
d1nrm=346;
|
|
mu=-722;
|
|
expandedrad=-413;
|
|
tol=-461;
|
|
maxlag=927;
|
|
maxhist=201;
|
|
}
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
case 4:
|
|
label=4;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
nlec=State.m_nlec;
|
|
nlic=State.m_nlic;
|
|
nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
dotrace=CAp::IsTraceEnabled("SLP");
|
|
dodetailedtrace=dotrace && CAp::IsTraceEnabled("SLP.DETAILED");
|
|
//--- Prepare rcomm interface
|
|
State.m_needfij=false;
|
|
State.m_xupdated=false;
|
|
//--- Initialize algorithm data:
|
|
//---*Lagrangian and "Big C" estimates
|
|
//--- * trust region
|
|
//--- * initial function scales (vector of 1's)
|
|
//--- * current approximation of the Hessian matrix H (unit matrix)
|
|
//--- * initial linearized constraints
|
|
//--- * initial violation of linear/nonlinear constraints
|
|
State.m_lpfailurecnt=0;
|
|
State.m_fstagnationcnt=0;
|
|
State.m_bigc=m_initbigc;
|
|
State.m_trustrad=m_inittrustrad;
|
|
State.m_fscales.Fill(1.0);
|
|
State.m_meritfunctionhistory.Fill(CMath::m_maxrealnumber);
|
|
State.m_maxlaghistory.Fill(0.0);
|
|
State.m_historylen=0;
|
|
gammamax=0.0;
|
|
//--- Avoid spurious warnings about possibly uninitialized vars
|
|
status=0;
|
|
stp=0;
|
|
//--- Evaluate function vector and Jacobian at Step0X, send first location report.
|
|
//--- Compute initial violation of constraints.
|
|
SLPSendX(State,State.m_step0x);
|
|
State.m_needfij=true;
|
|
State.m_rstate.stage=0;
|
|
label=-1;
|
|
break;
|
|
}
|
|
//--- main loop
|
|
while(label>=0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 0:
|
|
State.m_needfij=false;
|
|
if(!SLPRetrieveFIJ(State,State.m_step0fi,State.m_step0j))
|
|
{
|
|
//--- Failed to retrieve function/Jaconian, infinities detected!
|
|
State.m_stepkx=State.m_step0x;
|
|
State.m_repterminationtype=-8;
|
|
return(false);
|
|
}
|
|
SLPCopyState(State,State.m_step0x,State.m_step0fi,State.m_step0j,State.m_stepkx,State.m_stepkfi,State.m_stepkj);
|
|
SLPSendX(State,State.m_stepkx);
|
|
State.m_f=State.m_stepkfi[0]*State.m_fscales[0];
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_xupdated=false;
|
|
COptServ::CheckLcViolation(State.m_scaledcleic,State.m_lcsrcidx,nec,nic,State.m_stepkx,n,State.m_replcerr,State.m_replcidx);
|
|
COptServ::UnScaleAndCheckNLcViolation(State.m_stepkfi,State.m_fscales,nlec,nlic,State.m_repnlcerr,State.m_repnlcidx);
|
|
//--- Trace output (if needed)
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
CAp::Trace("//--- SLP SOLVER STARTED //\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
}
|
|
//--- Perform outer (NLC) iterations
|
|
InitLPSubsolver(State,State.m_subsolver,State.m_hessiantype);
|
|
case 5:
|
|
//--- Before beginning new outer iteration:
|
|
//--- * renormalize target function and/or constraints, if some of them have too large magnitudes
|
|
//--- * save initial point for the outer iteration
|
|
for(i=0; i<=nlec+nlic; i++)
|
|
{
|
|
//--- Determine (a) multiplicative coefficient applied to function value
|
|
//--- and Jacobian row, and (b) new value of the function scale.
|
|
mx=0;
|
|
for(j=0; j<n; j++)
|
|
mx=MathMax(mx,MathAbs(State.m_stepkj.Get(i,j)));
|
|
multiplyby=1.0;
|
|
setscaleto=State.m_fscales[i];
|
|
if(mx>=m_slpbigscale)
|
|
{
|
|
multiplyby=1/mx;
|
|
setscaleto=State.m_fscales[i]*mx;
|
|
}
|
|
if(mx<=m_slpsmallscale && State.m_fscales[i]>1.0)
|
|
{
|
|
if((State.m_fscales[i]*mx)>1.0)
|
|
{
|
|
multiplyby=1/mx;
|
|
setscaleto=State.m_fscales[i]*mx;
|
|
}
|
|
else
|
|
{
|
|
multiplyby=State.m_fscales[i];
|
|
setscaleto=1.0;
|
|
}
|
|
}
|
|
if(multiplyby!=1.0)
|
|
{
|
|
//--- Function #I needs renormalization:
|
|
//--- * update function vector element and Jacobian matrix row
|
|
//--- * update FScales[] array
|
|
State.m_stepkfi.Mul(i,multiplyby);
|
|
State.m_stepkj.Row(i,State.m_stepkj[i]*multiplyby);
|
|
State.m_fscales.Set(i,setscaleto);
|
|
}
|
|
}
|
|
//--- Save initial point for the outer iteration
|
|
SLPCopyState(State,State.m_stepkx,State.m_stepkfi,State.m_stepkj,State.m_step0x,State.m_step0fi,State.m_step0j);
|
|
//--- Trace output (if needed)
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace(StringFormat("\n=== OUTER ITERATION %5d STARTED ==================================================================\n",State.m_repouteriterationscount));
|
|
if(dodetailedtrace)
|
|
{
|
|
CAp::Trace("> printing raw data (prior to applying variable and function scales)\n");
|
|
CAp::Trace("X (raw) = ");
|
|
CApServ::TraceVectoRunScaledUnshiftedAutopRec(State.m_step0x,n,State.m_s,true,State.m_s,false);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("> printing scaled data (after applying variable and function scales)\n");
|
|
CAp::Trace("X (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_step0x,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("FScales = ");
|
|
CApServ::TraceVectorAutopRec(State.m_fscales,0,1+nlec+nlic);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("Fi (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_stepkfi,0,1+nlec+nlic);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("|Ji| (scaled) = ");
|
|
CApServ::TraceRowNrm1AutopRec(State.m_stepkj,0,1+nlec+nlic,0,n);
|
|
CAp::Trace("\n");
|
|
}
|
|
mx=0;
|
|
for(i=1; i<=nlec; i++)
|
|
mx=MathMax(mx,MathAbs(State.m_stepkfi[i]));
|
|
for(i=nlec+1; i<=nlec+nlic; i++)
|
|
mx=MathMax(mx,State.m_stepkfi[i]);
|
|
CAp::Trace(StringFormat("trustRad = %.3E\n",State.m_trustrad));
|
|
CAp::Trace(StringFormat("lin.violation = %.3E (scaled violation of linear constraints)\n",State.m_replcerr));
|
|
CAp::Trace(StringFormat("nlc.violation = %.3E (scaled violation of nonlinear constraints)\n",mx));
|
|
CAp::Trace(StringFormat("gammaMax = %.3E\n",gammamax));
|
|
CAp::Trace(StringFormat("max|LagMult| = %.3E (maximum over %d last iterations)\n",CAblasF::RMaxAbsV(State.m_historylen,State.m_maxlaghistory),State.m_historylen));
|
|
}
|
|
//--- PHASE 1:
|
|
//--- * perform step using linear model with second order correction
|
|
//---*compute "reference" Lagrange multipliers
|
|
//--- * compute merit function at the end of the phase 1 and push it to the history queue
|
|
//--- NOTE: a second order correction helps to overcome Maratos effect - a tendency
|
|
//--- of L1 penalized merit function to reject nonzero steps along steepest
|
|
//--- descent direction.
|
|
//--- The idea (explained in more details in the Phase13Iteration() body)
|
|
//--- is to perform one look-ahead step and use updated constraint values
|
|
//--- back at the initial point.
|
|
Phase13Init(State.m_state13,n,nec,nic,nlec,nlic,false);
|
|
case 2:
|
|
case 7:
|
|
if(!Phase13Iteration(State,State.m_state13,smonitor,userterminationneeded,State.m_stepkx,State.m_stepkfi,State.m_stepkj,State.m_meritlagmult,status,d1nrm,stp))
|
|
{
|
|
label=8;
|
|
break;
|
|
}
|
|
State.m_rstate.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 8:
|
|
if(status<0)
|
|
{
|
|
label=5;
|
|
break;
|
|
}
|
|
if(status==0)
|
|
{
|
|
label=6;
|
|
break;
|
|
}
|
|
maxlag=CAblasF::RMaxAbsV(nec+nic+nlec+nlic,State.m_meritlagmult);
|
|
maxhist=CAblasF::RMaxAbsV(State.m_historylen,State.m_maxlaghistory);
|
|
mu=CApServ::Coalesce(MathMax(maxhist,maxlag),m_defaultl1penalty);
|
|
for(i=State.m_historylen; i>=1; i--)
|
|
{
|
|
State.m_meritfunctionhistory.Set(i,State.m_meritfunctionhistory[i-1]);
|
|
State.m_maxlaghistory.Set(i,State.m_maxlaghistory[i-1]);
|
|
}
|
|
State.m_meritfunctionhistory.Set(0,MeritFunction(State,State.m_stepkx,State.m_stepkfi,State.m_meritlagmult,mu,State.m_tmpmerit));
|
|
State.m_maxlaghistory.Set(0,CApServ::Coalesce(maxlag,m_defaultmaglagdecay*maxhist));
|
|
State.m_historylen=MathMin(State.m_historylen+1,m_nonmonotonicphase2limit);
|
|
//--- Decide whether we need to increase BigC (penalty for the constraint violation that
|
|
//--- is used by the linear subsolver) or not. BigC is increased if all of the following
|
|
//--- holds true:
|
|
//--- * BigC can be increased (it is below upper limit)
|
|
//--- * a short step was performed (shorter than the current trust region)
|
|
//--- * at least one of the constraints is infeasible within current trust region
|
|
if((d1nrm*stp)<(0.99*State.m_trustrad) && State.m_bigc<(double)(0.9*m_maxbigc))
|
|
{
|
|
increasebigc=false;
|
|
expandedrad=1.1*State.m_trustrad;
|
|
tol=MathMax(MathSqrt(CMath::m_machineepsilon)*State.m_trustrad,1000*CMath::m_machineepsilon);
|
|
for(i=0; i<=nec+nic-1; i++)
|
|
{
|
|
v=0;
|
|
vv=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v+=State.m_scaledcleic.Get(i,j)*State.m_stepkx[j];
|
|
vv+=MathAbs(State.m_scaledcleic.Get(i,j)*expandedrad);
|
|
}
|
|
v-=State.m_scaledcleic.Get(i,n);
|
|
if(i>=nec)
|
|
v=MathMax(v,0.0);
|
|
increasebigc=increasebigc || MathAbs(v)>(vv+tol);
|
|
}
|
|
for(i=1; i<=nlec+nlic; i++)
|
|
{
|
|
v=State.m_stepkfi[i];
|
|
vv=0;
|
|
for(j=0; j<n; j++)
|
|
vv+=MathAbs(State.m_stepkj.Get(i,j)*expandedrad);
|
|
if(i>=nlec+1)
|
|
v=MathMax(v,0.0);
|
|
increasebigc=increasebigc || MathAbs(v)>(vv+tol);
|
|
}
|
|
if(increasebigc)
|
|
{
|
|
State.m_bigc=MathMin(10*State.m_bigc,m_maxbigc);
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("BigC = %.3E (trust radius is small,but some constraints are still infeasible - increasing constraint violation penalty)\n",State.m_bigc));
|
|
}
|
|
}
|
|
//--- PHASE 2: conjugate subiterations
|
|
//--- If step with second order correction is shorter than 1.0, it means
|
|
//--- that target is sufficiently nonlinear to use advanced iterations.
|
|
//--- * perform inner LP subiterations with additional conjugacy constraints
|
|
//--- * check changes in merit function, discard iteration results if merit function increased
|
|
if(stp>=(double)(m_slpstpclosetoone))
|
|
{
|
|
label=9;
|
|
break;
|
|
}
|
|
if(dotrace)
|
|
CAp::Trace("> linear model produced short step,starting conjugate-gradient-like phase\n");
|
|
SLPCopyState(State,State.m_stepkx,State.m_stepkfi,State.m_stepkj,State.m_backupx,State.m_backupfi,State.m_backupj);
|
|
//--- LP subiterations
|
|
Phase2Init(State.m_state2,n,nec,nic,nlec,nlic,State.m_meritlagmult);
|
|
case 3:
|
|
case 11:
|
|
if(Phase2Iteration(State,State.m_state2,smonitor,userterminationneeded,State.m_stepkx,State.m_stepkfi,State.m_stepkj,State.m_dummylagmult,gammamax,status))
|
|
{
|
|
State.m_rstate.stage=3;
|
|
label=-1;
|
|
break;
|
|
}
|
|
case 12:
|
|
if(status==0)
|
|
{
|
|
//--- Save progress so far and stop
|
|
label=6;
|
|
break;
|
|
}
|
|
//--- Evaluating step
|
|
//--- This step is essential because previous step (which minimizes Lagrangian) may fail
|
|
//--- to produce descent direction for L1-penalized merit function and will increase it
|
|
//--- instead of decreasing.
|
|
//--- During evaluation we compare merit function at new location with maximum computed
|
|
//--- over last NonmonotonicPhase2Limit+1 previous ones (as suggested in 'A Sequential
|
|
//--- Quadratic Programming Algorithm with Non-Monotone Line Search' by Yu-Hong Dai).
|
|
//--- Settings NonmonotonicPhase2Limit to 0 will result in strictly monotonic line search,
|
|
//--- whilst having nonzero limits means that we perform more robust nonmonotonic search.
|
|
if(!CAp::Assert(State.m_historylen>=1,__FUNCTION__+": integrity check 6559 failed"))
|
|
return(false);
|
|
f1=State.m_meritfunctionhistory[0];
|
|
for(i=1; i<=State.m_historylen; i++)
|
|
f1=MathMax(f1,State.m_meritfunctionhistory[i]);
|
|
f2=MeritFunction(State,State.m_stepkx,State.m_stepkfi,State.m_meritlagmult,mu,State.m_tmpmerit);
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace(StringFormat("> evaluating changes in merit function (max over last %d values is used for reference):\n",m_nonmonotonicphase2limit + 1));
|
|
CAp::Trace(StringFormat("meritF: %14.6E -> %14.6E (delta=%11.3E)\n",f1,f2,f2 - f1));
|
|
}
|
|
if(f2<f1)
|
|
{
|
|
label=13;
|
|
break;
|
|
}
|
|
//--- Merit function does not decrease, discard phase results and report is as one
|
|
//--- more "fake" inner iteration.
|
|
//--- NOTE: it is important that F2=F1 is considered as "does not decrease"
|
|
if(dotrace)
|
|
CAp::Trace("> CG-like phase increased merit function,completely discarding phase (happens sometimes,but not too often)\n");
|
|
SLPCopyState(State,State.m_backupx,State.m_backupfi,State.m_backupj,State.m_stepkx,State.m_stepkfi,State.m_stepkj);
|
|
State.m_repinneriterationscount++;
|
|
SLPSendX(State,State.m_stepkx);
|
|
State.m_f=State.m_stepkfi[0]*State.m_fscales[0];
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=4;
|
|
label=-1;
|
|
break;
|
|
case 4:
|
|
State.m_xupdated=false;
|
|
COptServ::CheckLcViolation(State.m_scaledcleic,State.m_lcsrcidx,nec,nic,State.m_stepkx,n,State.m_replcerr,State.m_replcidx);
|
|
COptServ::UnScaleAndCheckNLcViolation(State.m_stepkfi,State.m_fscales,nlec,nlic,State.m_repnlcerr,State.m_repnlcidx);
|
|
label=10;
|
|
break;
|
|
case 13:
|
|
//--- Merit function decreased, accept phase
|
|
State.m_meritfunctionhistory.Set(0,f2);
|
|
if(dotrace)
|
|
CAp::Trace("> CG-like phase decreased merit function,CG-like step accepted\n");
|
|
case 14:
|
|
label=10;
|
|
break;
|
|
case 9:
|
|
//--- No phase #2
|
|
if(dotrace)
|
|
{
|
|
if(stp>0.0)
|
|
CAp::Trace("> linear model produced long step,no need to start CG-like iterations\n");
|
|
else
|
|
CAp::Trace("> linear model produced zero step,maybe trust radius is too large\n");
|
|
}
|
|
case 10:
|
|
//--- Update trust region
|
|
prevtrustrad=State.m_trustrad;
|
|
deltamax=0;
|
|
for(i=0; i<n; i++)
|
|
deltamax=MathMax(deltamax,MathAbs(State.m_step0x[i]-State.m_stepkx[i])/State.m_trustrad);
|
|
if(deltamax<=m_slpdeltadecrease)
|
|
State.m_trustrad=State.m_trustrad*MathMax(deltamax/m_slpdeltadecrease,m_maxtrustraddecay);
|
|
if(deltamax>=m_slpdeltaincrease)
|
|
State.m_trustrad=State.m_trustrad*MathMin(deltamax/m_slpdeltaincrease,m_maxtrustradgrowth);
|
|
//--- Trace
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace("\n--- outer iteration ends ---------------------------------------------------------------------------\n");
|
|
CAp::Trace(StringFormat("deltaMax = %.3f (ratio of step length to trust radius)\n",deltamax));
|
|
CAp::Trace(StringFormat("newTrustRad = %.3E",State.m_trustrad));
|
|
if(State.m_trustrad>prevtrustrad)
|
|
CAp::Trace(",trust radius increased");
|
|
if(State.m_trustrad<prevtrustrad)
|
|
CAp::Trace(",trust radius decreased");
|
|
CAp::Trace("\n");
|
|
}
|
|
//--- Advance outer iteration counter, test stopping criteria
|
|
State.m_repouteriterationscount++;
|
|
if(MathAbs(State.m_stepkfi[0]-State.m_step0fi[0])<=(m_stagnationepsf*MathAbs(State.m_step0fi[0])))
|
|
State.m_fstagnationcnt++;
|
|
else
|
|
State.m_fstagnationcnt=0;
|
|
if(State.m_trustrad<=State.m_epsx)
|
|
{
|
|
State.m_repterminationtype=2;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stopping condition met: trust radius is smaller than %.3E\n",State.m_epsx));
|
|
label=6;
|
|
break;
|
|
}
|
|
if(State.m_maxits>0 && State.m_repinneriterationscount>=State.m_maxits)
|
|
{
|
|
State.m_repterminationtype=5;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stopping condition met: %d iterations performed\n",State.m_repinneriterationscount));
|
|
label=6;
|
|
break;
|
|
}
|
|
if(State.m_fstagnationcnt>=m_fstagnationlimit)
|
|
{
|
|
State.m_repterminationtype=7;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stopping criteria are too stringent: F stagnated for %d its,stopping\n",State.m_fstagnationcnt));
|
|
label=6;
|
|
break;
|
|
}
|
|
label=5;
|
|
break;
|
|
case 6:
|
|
COptServ::SmoothnessMonitorTraceStatus(smonitor,dotrace);
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
//--- Saving State
|
|
result=true;
|
|
State.m_rstate.ba[0]=lpstagesuccess;
|
|
State.m_rstate.ba[1]=increasebigc;
|
|
State.m_rstate.ba[2]=dotrace;
|
|
State.m_rstate.ba[3]=dodetailedtrace;
|
|
State.m_rstate.ia.Set(0,n);
|
|
State.m_rstate.ia.Set(1,nslack);
|
|
State.m_rstate.ia.Set(2,nec);
|
|
State.m_rstate.ia.Set(3,nic);
|
|
State.m_rstate.ia.Set(4,nlec);
|
|
State.m_rstate.ia.Set(5,nlic);
|
|
State.m_rstate.ia.Set(6,i);
|
|
State.m_rstate.ia.Set(7,j);
|
|
State.m_rstate.ia.Set(8,innerk);
|
|
State.m_rstate.ia.Set(9,status);
|
|
State.m_rstate.ra.Set(0,v);
|
|
State.m_rstate.ra.Set(1,vv);
|
|
State.m_rstate.ra.Set(2,mx);
|
|
State.m_rstate.ra.Set(3,gammamax);
|
|
State.m_rstate.ra.Set(4,f1);
|
|
State.m_rstate.ra.Set(5,f2);
|
|
State.m_rstate.ra.Set(6,stp);
|
|
State.m_rstate.ra.Set(7,deltamax);
|
|
State.m_rstate.ra.Set(8,multiplyby);
|
|
State.m_rstate.ra.Set(9,setscaleto);
|
|
State.m_rstate.ra.Set(10,prevtrustrad);
|
|
State.m_rstate.ra.Set(11,d1nrm);
|
|
State.m_rstate.ra.Set(12,mu);
|
|
State.m_rstate.ra.Set(13,expandedrad);
|
|
State.m_rstate.ra.Set(14,tol);
|
|
State.m_rstate.ra.Set(15,maxlag);
|
|
State.m_rstate.ra.Set(16,maxhist);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes SLP subproblem. |
|
|
//| Should be called once in the beginning of the optimization. |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - solver State |
|
|
//| Subsolver - SLP subproblem to initialize |
|
|
//| HessianType - 0 for identity Hessian, 1 for BFGS update |
|
|
//| RETURN VALUE: |
|
|
//| True on success |
|
|
//| False on failure of the LP solver (unexpected... but possible |
|
|
//| due to numerical errors) |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::InitLPSubsolver(CMinSLPState &sstate,
|
|
CMinSLPSubsolver &subsolver,
|
|
int hessiantype)
|
|
{
|
|
//--- create variables
|
|
int n=sstate.m_n;
|
|
int nec=sstate.m_nec;
|
|
int nic=sstate.m_nic;
|
|
int nlec=sstate.m_nlec;
|
|
int nlic=sstate.m_nlic;
|
|
int nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
int lccnt=nec+nic+nlec+nlic;
|
|
int nnz=0;
|
|
int offs=0;
|
|
int i=0;
|
|
int j=0;
|
|
//--- Create simplex solver.
|
|
//--- NOTE: we disable DSE pricing because it interferes with our
|
|
//--- warm-start strategy.
|
|
CRevisedDualSimplex::DSSSettingsInit(subsolver.m_dsssettings);
|
|
subsolver.m_dsssettings.m_pricing=0;
|
|
//--- Allocate temporaries
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_cural,lccnt+n);
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_curau,lccnt+n);
|
|
CApServ::RMatrixSetLengthAtLeast(subsolver.m_curd,n,n);
|
|
CApServ::RMatrixSetLengthAtLeast(subsolver.m_curhd,n,n);
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_curbndl,nslack);
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_curbndu,nslack);
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_curb,nslack);
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_sk,n);
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_yk,n);
|
|
//--- Initial State
|
|
subsolver.m_basispresent=false;
|
|
subsolver.m_curdcnt=0;
|
|
subsolver.m_hessiantype=hessiantype;
|
|
if(hessiantype==1 || hessiantype==2)
|
|
{
|
|
//--- Prepare Hessian matrix
|
|
subsolver.m_h=matrix<double>::Identity(n,n);
|
|
}
|
|
//--- Linear constraints do not change across subiterations, that's
|
|
//--- why we allocate storage for them at the start of the program.
|
|
//--- A full set of "raw" constraints is stored; later we will filter
|
|
//--- out inequality ones which are inactive anywhere in the current
|
|
//--- trust region.
|
|
//--- NOTE: because sparserawlc object stores only linear constraint
|
|
//--- (linearizations of nonlinear ones are not stored) we
|
|
//--- allocate only minimum necessary space.
|
|
nnz=0;
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
if(sstate.m_scaledcleic.Get(i,j)!=0.0)
|
|
nnz++;
|
|
}
|
|
CApServ::IVectorSetLengthAtLeast(subsolver.m_sparserawlc.m_RIdx,nec+nic+1);
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_sparserawlc.m_Vals,nnz);
|
|
CApServ::IVectorSetLengthAtLeast(subsolver.m_sparserawlc.m_Idx,nnz);
|
|
CApServ::IVectorSetLengthAtLeast(subsolver.m_sparserawlc.m_DIdx,nec+nic);
|
|
CApServ::IVectorSetLengthAtLeast(subsolver.m_sparserawlc.m_UIdx,nec+nic);
|
|
offs=0;
|
|
subsolver.m_sparserawlc.m_RIdx.Set(0,0);
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
if(sstate.m_scaledcleic.Get(i,j)!=0.0)
|
|
{
|
|
//--- Primary part of the matrix
|
|
subsolver.m_sparserawlc.m_Vals.Set(offs,sstate.m_scaledcleic.Get(i,j));
|
|
subsolver.m_sparserawlc.m_Idx.Set(offs,j);
|
|
offs++;
|
|
}
|
|
subsolver.m_sparserawlc.m_RIdx.Set(i+1,offs);
|
|
}
|
|
subsolver.m_sparserawlc.m_MatrixType=1;
|
|
subsolver.m_sparserawlc.m_NInitialized=subsolver.m_sparserawlc.m_RIdx[nec+nic];
|
|
subsolver.m_sparserawlc.m_M=nec+nic;
|
|
subsolver.m_sparserawlc.m_N=n;
|
|
CSparse::SparseInitDUIdx(subsolver.m_sparserawlc);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Restarts LP subproblem (cleans the matrix of internally stored |
|
|
//| directions) |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - solver State |
|
|
//| Subsolver - SLP subproblem to initialize |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::LPSubproblemRestart(CMinSLPState &sstate,
|
|
CMinSLPSubsolver &subsolver)
|
|
{
|
|
subsolver.m_curdcnt=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Updates Hessian estimate |
|
|
//| INPUT PARAMETERS: |
|
|
//| SState - solver State |
|
|
//| Subsolver - SLP subproblem to initialize |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::LPSubproblemUpdateHessian(CMinSLPState &sstate,
|
|
CMinSLPSubsolver &subsolver,
|
|
CRowDouble &x0,
|
|
CRowDouble &g0,
|
|
CRowDouble &x1,
|
|
CRowDouble &g1)
|
|
{
|
|
//--- create variables
|
|
int n=sstate.m_n;
|
|
double vv=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
double gk=0;
|
|
double sk=0;
|
|
double yk=0;
|
|
//--- check
|
|
if(subsolver.m_hessiantype==1 || subsolver.m_hessiantype==2)
|
|
{
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_tmp0,n);
|
|
v=0;
|
|
v0=0;
|
|
v1=0;
|
|
v2=0;
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
sk=x1[i]-x0[i];
|
|
yk=g1[i]-g0[i];
|
|
gk=g0[i];
|
|
v+=sk*yk;
|
|
v0=v0+sk*sk;
|
|
v1=v1+yk*yk;
|
|
v2=v2+gk*gk;
|
|
subsolver.m_sk.Set(i,sk);
|
|
subsolver.m_yk.Set(i,yk);
|
|
}
|
|
if(MathSqrt(v0)>MathMax(sstate.m_epsx,m_bfgstol) && MathSqrt(v1)>(m_bfgstol*MathSqrt(v2)) && v>m_bfgstol*MathSqrt(v0)*MathSqrt(v1))
|
|
{
|
|
//--- Update Hessian if following criteria hold:
|
|
//--- * MCINFO=1 (good step)
|
|
//--- * step length is large enough
|
|
//--- * |Yk| is large enough when compared with |G|
|
|
//--- * (Sk,Yk) is large enough when compared with |S| and |G|
|
|
vv=CAblas::RMatrixSyvMVect(n,subsolver.m_h,0,0,true,subsolver.m_sk,0,subsolver.m_tmp0);
|
|
CAblas::RMatrixGemVect(n,n,1.0,subsolver.m_h,0,0,0,subsolver.m_sk,0,0.0,subsolver.m_tmp0,0);
|
|
CAblas::RMatrixGer(n,n,subsolver.m_h,0,0,1/v,subsolver.m_yk,0,subsolver.m_yk,0);
|
|
CAblas::RMatrixGer(n,n,subsolver.m_h,0,0,-(1/vv),subsolver.m_tmp0,0,subsolver.m_tmp0,0);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves LP subproblem given by initial point X, |
|
|
//| function vector Fi and Jacobian Jac, and returns estimates of |
|
|
//| Lagrangian multipliers and search direction D[]. |
|
|
//| This function does NOT append search direction D to conjugacy |
|
|
//| constraints, you have to use |
|
|
//| LPSubproblemAppendConjugacyConstraint(). |
|
|
//+------------------------------------------------------------------+
|
|
bool CNLCSLP::LPSubproblemSolve(CMinSLPState &State,
|
|
CMinSLPSubsolver &subsolver,
|
|
CRowDouble &x,
|
|
CRowDouble &fi,
|
|
CMatrixDouble &jac,
|
|
int innerk,
|
|
CRowDouble &d,
|
|
CRowDouble &lagmult)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=State.m_n;
|
|
int nec=State.m_nec;
|
|
int nic=State.m_nic;
|
|
int nlec=State.m_nlec;
|
|
int nlic=State.m_nlic;
|
|
int nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
int lccnt=nec+nic+nlec+nlic;
|
|
//--- Locations of slack variables
|
|
int offsslackec=n;
|
|
int offsslacknlec=n+2*nec;
|
|
int offsslackic=n+2*nec+2*nlec;
|
|
int offsslacknlic=n+2*(nec+nlec)+nic;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double vright=0;
|
|
double vmax=0;
|
|
int basisinittype=0;
|
|
int offs=0;
|
|
int nnz=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
//--- Prepare temporary structures
|
|
subsolver.m_cural.Resize(lccnt+subsolver.m_curdcnt);
|
|
subsolver.m_curau.Resize(lccnt+subsolver.m_curdcnt);
|
|
//--- Prepare default solution: all zeros
|
|
result=true;
|
|
d.Fill(0.0);
|
|
lagmult.Fill(0);
|
|
//--- Linear term B
|
|
//--- NOTE: elements [N,NSlack) are equal to bigC + perturbation to improve numeric properties of LP problem
|
|
for(i=0; i<n; i++)
|
|
subsolver.m_curb.Set(i,jac.Get(0,i));
|
|
v=0;
|
|
for(i=0; i<n; i++)
|
|
v+=CMath::Sqr(jac.Get(0,i));
|
|
v=CApServ::Coalesce(MathSqrt(v),1.0);
|
|
for(i=n; i<nslack; i++)
|
|
subsolver.m_curb.Set(i,(State.m_bigc+1.0/(1+i))*v);
|
|
//--- Trust radius constraints for primary variables
|
|
for(i=0; i<n; i++)
|
|
{
|
|
subsolver.m_curbndl.Set(i,-State.m_trustrad);
|
|
subsolver.m_curbndu.Set(i,State.m_trustrad);
|
|
if(State.m_HasBndL[i])
|
|
subsolver.m_curbndl.Set(i,MathMax(subsolver.m_curbndl[i],State.m_scaledbndl[i]-x[i]));
|
|
if(State.m_HasBndU[i])
|
|
subsolver.m_curbndu.Set(i,MathMin(subsolver.m_curbndu[i],State.m_scaledbndu[i]-x[i]));
|
|
}
|
|
//--- Prepare storage for "effective" constraining matrix
|
|
nnz=subsolver.m_sparserawlc.m_RIdx[nec+nic];
|
|
for(i=0; i<nlec+nlic; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
if(jac.Get(1+i,j)!=0.0)
|
|
nnz++;
|
|
}
|
|
nnz=nnz+2*nec+nic;
|
|
nnz=nnz+2*nlec+nlic;
|
|
nnz=nnz+subsolver.m_curdcnt*n;
|
|
subsolver.m_sparseefflc.m_RIdx.Resize(lccnt+n+1);
|
|
subsolver.m_sparseefflc.m_Vals.Resize(nnz);
|
|
subsolver.m_sparseefflc.m_Idx.Resize(nnz);
|
|
CApServ::IVectorSetLengthAtLeast(subsolver.m_sparseefflc.m_DIdx,lccnt+n);
|
|
CApServ::IVectorSetLengthAtLeast(subsolver.m_sparseefflc.m_UIdx,lccnt+n);
|
|
subsolver.m_sparseefflc.m_M=0;
|
|
subsolver.m_sparseefflc.m_N=nslack;
|
|
subsolver.m_sparseefflc.m_MatrixType=1;
|
|
//--- Append linear equality/inequality constraints
|
|
//--- Scan sparsified linear constraints stored in sparserawlc[], skip ones
|
|
//--- which are inactive anywhere in the trust region.
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_tmp0,nslack);
|
|
for(i=0; i<n; i++)
|
|
subsolver.m_tmp0.Set(i,x[i]);
|
|
for(i=n; i<nslack; i++)
|
|
subsolver.m_tmp0.Set(i,0);
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
//--- Calculate:
|
|
//--- * VRight - product of X[] (extended with zeros up to NSlack elements)
|
|
//--- and AR[i] - Ith row of sparserawlc matrix.
|
|
//--- * VMax - maximum value of X*ARi computed over trust region
|
|
vright=0;
|
|
vmax=0;
|
|
j0=subsolver.m_sparserawlc.m_RIdx[i];
|
|
j1=subsolver.m_sparserawlc.m_RIdx[i+1];
|
|
for(k=j0; k<j1; k++)
|
|
{
|
|
j=subsolver.m_sparserawlc.m_Idx[k];
|
|
v=subsolver.m_tmp0[j];
|
|
vv=subsolver.m_sparserawlc.m_Vals[k];
|
|
vright=vright+vv*v;
|
|
if(vv>=0)
|
|
vmax+=vv*(v+subsolver.m_curbndu[j]);
|
|
else
|
|
vmax+=vv*(v+subsolver.m_curbndl[j]);
|
|
}
|
|
//--- If constraint is an inequality one and guaranteed to be inactive
|
|
//--- within trust region, it is skipped (row itself is retained but
|
|
//--- filled by zeros).
|
|
if(i>=nec && vmax<=State.m_scaledcleic.Get(i,n))
|
|
{
|
|
offs=subsolver.m_sparseefflc.m_RIdx[i];
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,-1);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,offsslackic+(i-nec));
|
|
subsolver.m_sparseefflc.m_RIdx.Set(i+1,offs+1);
|
|
subsolver.m_cural.Set(i,0.0);
|
|
subsolver.m_curau.Set(i,0.0);
|
|
subsolver.m_curbndl.Set(offsslackic+(i-nec),0);
|
|
subsolver.m_curbndu.Set(offsslackic+(i-nec),0);
|
|
continue;
|
|
}
|
|
//--- Start working on row I
|
|
offs=subsolver.m_sparseefflc.m_RIdx[i];
|
|
//--- Copy constraint from sparserawlc[] to sparseefflc[]
|
|
j0=subsolver.m_sparserawlc.m_RIdx[i];
|
|
j1=subsolver.m_sparserawlc.m_RIdx[i+1];
|
|
for(k=j0; k<j1; k++)
|
|
{
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,subsolver.m_sparserawlc.m_Idx[k]);
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,subsolver.m_sparserawlc.m_Vals[k]);
|
|
offs++;
|
|
}
|
|
//--- Set up slack variables
|
|
if(i<nec)
|
|
{
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,-1);
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs+1,1);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,offsslackec+2*i);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs+1,offsslackec+2*i+1);
|
|
offs+=2;
|
|
}
|
|
else
|
|
{
|
|
//--- Slack variables for inequality constraints
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,-1);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,offsslackic+(i-nec));
|
|
offs++;
|
|
}
|
|
//--- Finalize row
|
|
subsolver.m_sparseefflc.m_RIdx.Set(i+1,offs);
|
|
//--- Set up bounds.
|
|
//--- NOTE: bounds for equality and inequality constraints are
|
|
//--- handled differently
|
|
v=vright-State.m_scaledcleic.Get(i,n);
|
|
if(i<nec)
|
|
{
|
|
subsolver.m_cural.Set(i,-v);
|
|
subsolver.m_curau.Set(i,-v);
|
|
subsolver.m_curbndl.Set(offsslackec+2*i,0);
|
|
subsolver.m_curbndl.Set(offsslackec+2*i+1,0);
|
|
subsolver.m_curbndu.Set(offsslackec+2*i,MathAbs(v));
|
|
subsolver.m_curbndu.Set(offsslackec+2*i+1,MathAbs(v));
|
|
}
|
|
else
|
|
{
|
|
subsolver.m_cural.Set(i,AL_NEGINF);
|
|
subsolver.m_curau.Set(i,-v);
|
|
subsolver.m_curbndl.Set(offsslackic+(i-nec),0);
|
|
subsolver.m_curbndu.Set(offsslackic+(i-nec),MathMax(v,0));
|
|
}
|
|
}
|
|
subsolver.m_sparseefflc.m_M+=(nec+nic);
|
|
//--- Append nonlinear equality/inequality constraints
|
|
for(i=0; i<nlec+nlic; i++)
|
|
{
|
|
//--- Calculate scale coefficient
|
|
vv=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=jac.Get(1+i,j);
|
|
vv+=v*v;
|
|
}
|
|
vv=1/CApServ::Coalesce(MathSqrt(vv),1);
|
|
//--- Copy scaled row
|
|
offs=subsolver.m_sparseefflc.m_RIdx[subsolver.m_sparseefflc.m_M+i];
|
|
for(j=0; j<n; j++)
|
|
{
|
|
if(jac.Get(1+i,j)!=0.0)
|
|
{
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,vv*jac.Get(1+i,j));
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,j);
|
|
offs++;
|
|
}
|
|
}
|
|
if(i<nlec)
|
|
{
|
|
//--- Add slack terms for equality constraints
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,-1);
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs+1,1);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,offsslacknlec+2*i);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs+1,offsslacknlec+2*i+1);
|
|
offs+=2;
|
|
}
|
|
else
|
|
{
|
|
//--- Add slack terms for inequality constraints
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,-1);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,offsslacknlic+(i-nlec));
|
|
offs++;
|
|
}
|
|
subsolver.m_sparseefflc.m_RIdx.Set(subsolver.m_sparseefflc.m_M+i+1,offs);
|
|
//--- Set box constraints on slack variables and bounds on linear equality/inequality constraints
|
|
v=vv*fi[1+i];
|
|
if(i<nlec)
|
|
{
|
|
//--- Equality constraint
|
|
subsolver.m_cural.Set(subsolver.m_sparseefflc.m_M+i,-v);
|
|
subsolver.m_curau.Set(subsolver.m_sparseefflc.m_M+i,-v);
|
|
subsolver.m_curbndl.Set(offsslacknlec+2*i,0);
|
|
subsolver.m_curbndl.Set(offsslacknlec+2*i+1,0);
|
|
subsolver.m_curbndu.Set(offsslacknlec+2*i,MathAbs(v));
|
|
subsolver.m_curbndu.Set(offsslacknlec+2*i+1,MathAbs(v));
|
|
}
|
|
else
|
|
{
|
|
//--- Inequality constraint
|
|
subsolver.m_cural.Set(subsolver.m_sparseefflc.m_M+i,AL_NEGINF);
|
|
subsolver.m_curau.Set(subsolver.m_sparseefflc.m_M+i,-v);
|
|
subsolver.m_curbndl.Set(offsslacknlic+(i-nlec),0);
|
|
subsolver.m_curbndu.Set(offsslacknlic+(i-nlec),MathMax(v,0));
|
|
}
|
|
}
|
|
subsolver.m_sparseefflc.m_M+=(nlec+nlic);
|
|
//--- Append conjugacy constraints
|
|
for(i=0; i<subsolver.m_curdcnt; i++)
|
|
{
|
|
//--- Copy N elements of CurHD
|
|
//--- NOTE: we expect product of D and H to be dense, so we copy all N elements
|
|
v=0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
vv=subsolver.m_curhd.Get(i,j);
|
|
v+=vv*vv;
|
|
}
|
|
v=1.0/CApServ::Coalesce(MathSqrt(v),1.0);
|
|
offs=subsolver.m_sparseefflc.m_RIdx[subsolver.m_sparseefflc.m_M];
|
|
for(j=0; j<n; j++)
|
|
{
|
|
vv=subsolver.m_curhd.Get(i,j);
|
|
subsolver.m_sparseefflc.m_Vals.Set(offs,v*vv);
|
|
subsolver.m_sparseefflc.m_Idx.Set(offs,j);
|
|
offs++;
|
|
}
|
|
subsolver.m_sparseefflc.m_RIdx.Set(subsolver.m_sparseefflc.m_M+1,offs);
|
|
//--- Set bounds on linear constraints
|
|
subsolver.m_cural.Set(subsolver.m_sparseefflc.m_M,0);
|
|
subsolver.m_curau.Set(subsolver.m_sparseefflc.m_M,0);
|
|
//--- Increase row count
|
|
subsolver.m_sparseefflc.m_M ++;
|
|
}
|
|
//--- Finalize sparse matrix structure
|
|
if(!CAp::Assert(subsolver.m_sparseefflc.m_RIdx[subsolver.m_sparseefflc.m_M]<=CAp::Len(subsolver.m_sparseefflc.m_Idx),"LPSubproblemSolve: critical integrity check failed"))
|
|
return(false);
|
|
if(!CAp::Assert(subsolver.m_sparseefflc.m_RIdx[subsolver.m_sparseefflc.m_M]<=CAp::Len(subsolver.m_sparseefflc.m_Vals),"LPSubproblemSolve: critical integrity check failed"))
|
|
return(false);
|
|
subsolver.m_sparseefflc.m_NInitialized=subsolver.m_sparseefflc.m_RIdx[subsolver.m_sparseefflc.m_M];
|
|
CSparse::SparseInitDUIdx(subsolver.m_sparseefflc);
|
|
//--- Choose dual simplex method basis initialization type
|
|
if(innerk==1 && subsolver.m_basispresent)
|
|
basisinittype=2;
|
|
else
|
|
basisinittype=1;
|
|
//--- Solve linear program
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_tmp0,nslack);
|
|
subsolver.m_tmp0.Fill(State.m_trustrad);
|
|
CLPQPPresolve::PresolveNoneScaleUser(subsolver.m_tmp0,subsolver.m_curb,subsolver.m_curbndl,subsolver.m_curbndu,nslack,subsolver.m_sparseefflc,subsolver.m_cural,subsolver.m_curau,subsolver.m_sparseefflc.m_M,subsolver.m_presolver);
|
|
CRevisedDualSimplex::DSSInit(subsolver.m_presolver.m_newn,subsolver.m_dss);
|
|
CRevisedDualSimplex::DSSSetProblem(subsolver.m_dss,subsolver.m_presolver.m_c,subsolver.m_presolver.m_bndl,subsolver.m_presolver.m_bndu,subsolver.m_densedummy,subsolver.m_presolver.m_sparsea,1,subsolver.m_presolver.m_al,subsolver.m_presolver.m_au,subsolver.m_presolver.m_newm,subsolver.m_lastbasis,basisinittype,subsolver.m_dsssettings);
|
|
CRevisedDualSimplex::DSSOptimize(subsolver.m_dss,subsolver.m_dsssettings);
|
|
subsolver.m_xs=subsolver.m_dss.m_repx;
|
|
subsolver.m_lagbc=subsolver.m_dss.m_replagbc;
|
|
subsolver.m_laglc=subsolver.m_dss.m_replaglc;
|
|
subsolver.m_cs=subsolver.m_dss.m_repstats;
|
|
CLPQPPresolve::PresolveBwd(subsolver.m_presolver,subsolver.m_xs,subsolver.m_cs,subsolver.m_lagbc,subsolver.m_laglc);
|
|
State.m_repsimplexiterations+=subsolver.m_dss.m_repiterationscount;
|
|
State.m_repsimplexiterations1+=subsolver.m_dss.m_repiterationscount1;
|
|
State.m_repsimplexiterations2+=subsolver.m_dss.m_repiterationscount2;
|
|
State.m_repsimplexiterations3+=subsolver.m_dss.m_repiterationscount3;
|
|
if(subsolver.m_dss.m_repterminationtype<=0)
|
|
{
|
|
//--- LP solver failed due to numerical errors; exit
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(innerk==1)
|
|
{
|
|
//--- Store basis
|
|
CRevisedDualSimplex::DSSExportBasis(subsolver.m_dss,subsolver.m_lastbasis);
|
|
subsolver.m_basispresent=true;
|
|
}
|
|
//--- Extract direction D[] and Lagrange multipliers
|
|
d=subsolver.m_xs;
|
|
lagmult=subsolver.m_laglc;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function appends last search direction D to conjugacy |
|
|
//| constraints of the LP subproblem. |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::LPSubproblemAppendConjugacyConstraint(CMinSLPState &State,
|
|
CMinSLPSubsolver &subsolver,
|
|
CRowDouble &d)
|
|
{
|
|
int n=State.m_n;
|
|
//--- Update matrix of products H*Dprev
|
|
if(!CAp::Assert(subsolver.m_curdcnt<CAp::Rows(subsolver.m_curd),__FUNCTION__+": CurD is too small"))
|
|
return;
|
|
//--- Store direction and default conjugacy constraint d'*I*Dprev=0
|
|
subsolver.m_curd.Row(subsolver.m_curdcnt,d);
|
|
subsolver.m_curhd.Row(subsolver.m_curdcnt,d);
|
|
subsolver.m_curdcnt++;
|
|
if(State.m_hessiantype==1)
|
|
{
|
|
//--- Conjugacy constraint d*H*Dprev=0, full recomputation of (H*Dprev)
|
|
CAblas::RMatrixGemm(subsolver.m_curdcnt,n,n,1.0,subsolver.m_curd,0,0,0,subsolver.m_h,0,0,0,0.0,subsolver.m_curhd,0,0);
|
|
}
|
|
if(State.m_hessiantype==2)
|
|
{
|
|
//--- Conjugacy constraint d*H*Dprev=0, only last row of (H*Dprev) is recomputed
|
|
CApServ::RVectorSetLengthAtLeast(subsolver.m_tmp0,n);
|
|
CAblas::RMatrixGemVect(n,n,1.0,subsolver.m_h,0,0,0,d,0,0.0,subsolver.m_tmp0,0);
|
|
subsolver.m_curhd.Row(subsolver.m_curdcnt-1,subsolver.m_tmp0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes Phase13 temporaries. It should be |
|
|
//| called before beginning of each new iteration. You may call it |
|
|
//| multiple times for the same instance of Phase13 temporaries. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State13 - instance to be initialized. |
|
|
//| N - problem dimensionality |
|
|
//| NEC, NIC - linear equality / inequality constraint count |
|
|
//| NLEC, NLIC - nonlinear equality / inequality constraint count|
|
|
//| UseCorrection - True if we want to perform second order |
|
|
//| correction |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State13 - instance being initialized |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::Phase13Init(CMinSLPPhase13State &state13,
|
|
int n,
|
|
int nec,
|
|
int nic,
|
|
int nlec,
|
|
int nlic,
|
|
bool usecorrection)
|
|
{
|
|
//--- create variables
|
|
int nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
state13.m_usecorrection=usecorrection;
|
|
//--- allocate
|
|
CApServ::RVectorSetLengthAtLeast(state13.m_d,nslack);
|
|
CApServ::RVectorSetLengthAtLeast(state13.m_dx,nslack);
|
|
CApServ::RVectorSetLengthAtLeast(state13.m_stepkxc,n);
|
|
CApServ::RVectorSetLengthAtLeast(state13.m_stepkxn,n);
|
|
CApServ::RVectorSetLengthAtLeast(state13.m_stepkfic,1+nlec+nlic);
|
|
CApServ::RVectorSetLengthAtLeast(state13.m_stepkfin,1+nlec+nlic);
|
|
CApServ::RMatrixSetLengthAtLeast(state13.m_stepkjc,1+nlec+nlic,n);
|
|
CApServ::RMatrixSetLengthAtLeast(state13.m_stepkjn,1+nlec+nlic,n);
|
|
CApServ::RVectorSetLengthAtLeast(state13.m_dummylagmult,nec+nic+nlec+nlic);
|
|
state13.m_rphase13state.ia.Resize(8+1);
|
|
ArrayResize(state13.m_rphase13state.ba,2+1);
|
|
state13.m_rphase13state.ra.Resize(6+1);
|
|
state13.m_rphase13state.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tries to perform either phase #1 or phase #3 step. |
|
|
//| Former corresponds to linear model step (without conjugacy |
|
|
//| constraints) with correction for nonlinearity ("second order |
|
|
//| correction"). Such correction helps to overcome Maratos effect |
|
|
//| (a tendency of L1 penalized merit functions to reject nonzero |
|
|
//| steps). |
|
|
//| Latter is a step using linear model with no second order |
|
|
//| correction. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - SLP solver State |
|
|
//| SMonitor - smoothness monitor |
|
|
//| UserTerminationNeeded - True if user requested termination |
|
|
//| CurX - current point, array[N] |
|
|
//| CurFi - function vector at CurX, array[1 + NLEC + NLIC] |
|
|
//| CurJ - Jacobian at CurX, array[1 + NLEC + NLIC, N] |
|
|
//| LagMult - array[NEC + NIC + NLEC + NLIC], contents ignored |
|
|
//| on input. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - RepTerminationType is set to current termination |
|
|
//| code (if Status = 0). |
|
|
//| CurX - advanced to new point |
|
|
//| CurFi - updated with function vector at CurX[] |
|
|
//| CurJ - updated with Jacobian at CurX[] |
|
|
//| LagMult - filled with current Lagrange multipliers |
|
|
//| Status - when reverse communication is done, Status is set |
|
|
//| to: |
|
|
//| * negative value, if we have to restart outer |
|
|
//| iteration |
|
|
//| * positive value, if we can proceed to the next |
|
|
//| stage of the outer iteration |
|
|
//| * zero, if algorithm is terminated |
|
|
//| (RepTerminationType is set to appropriate value) |
|
|
//| DNrm - inf-norm of the proposed step vector D |
|
|
//| Stp - step length(multiplier for D), in [0, 1] |
|
|
//+------------------------------------------------------------------+
|
|
bool CNLCSLP::Phase13Iteration(CMinSLPState &State,
|
|
CMinSLPPhase13State &state13,
|
|
CSmoothnessMonitor &smonitor,
|
|
bool userterminationneeded,
|
|
CRowDouble &curx,
|
|
CRowDouble &curfi,
|
|
CMatrixDouble &curj,
|
|
CRowDouble &lagmult,
|
|
int &status,
|
|
double &dnrm,
|
|
double &stp)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=0;
|
|
int nslack=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int nlec=0;
|
|
int nlic=0;
|
|
int innerk=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double mx=0;
|
|
double f0=0;
|
|
double f1=0;
|
|
double nu=0;
|
|
double localstp=0;
|
|
double mu=0;
|
|
bool dotrace=false;
|
|
bool doprobing=false;
|
|
bool dotracexd=false;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(state13.m_rphase13state.stage>=0)
|
|
{
|
|
n=state13.m_rphase13state.ia[0];
|
|
nslack=state13.m_rphase13state.ia[1];
|
|
nec=state13.m_rphase13state.ia[2];
|
|
nic=state13.m_rphase13state.ia[3];
|
|
nlec=state13.m_rphase13state.ia[4];
|
|
nlic=state13.m_rphase13state.ia[5];
|
|
innerk=state13.m_rphase13state.ia[6];
|
|
i=state13.m_rphase13state.ia[7];
|
|
j=state13.m_rphase13state.ia[8];
|
|
dotrace=state13.m_rphase13state.ba[0];
|
|
doprobing=state13.m_rphase13state.ba[1];
|
|
dotracexd=state13.m_rphase13state.ba[2];
|
|
v=state13.m_rphase13state.ra[0];
|
|
mx=state13.m_rphase13state.ra[1];
|
|
f0=state13.m_rphase13state.ra[2];
|
|
f1=state13.m_rphase13state.ra[3];
|
|
nu=state13.m_rphase13state.ra[4];
|
|
localstp=state13.m_rphase13state.ra[5];
|
|
mu=state13.m_rphase13state.ra[6];
|
|
}
|
|
else
|
|
{
|
|
n=922;
|
|
nslack=-154;
|
|
nec=306;
|
|
nic=-1011;
|
|
nlec=951;
|
|
nlic=-463;
|
|
innerk=88;
|
|
i=-861;
|
|
j=-678;
|
|
dotrace=true;
|
|
doprobing=true;
|
|
dotracexd=true;
|
|
v=-233;
|
|
mx=-936;
|
|
f0=-279;
|
|
f1=94;
|
|
nu=-812;
|
|
localstp=427;
|
|
mu=178;
|
|
}
|
|
switch(state13.m_rphase13state.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
nlec=State.m_nlec;
|
|
nlic=State.m_nlic;
|
|
nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
innerk=1;
|
|
dotrace=CAp::IsTraceEnabled("SLP");
|
|
dotracexd=dotrace && CAp::IsTraceEnabled("SLP.DETAILED");
|
|
doprobing=CAp::IsTraceEnabled("SLP.PROBING");
|
|
if(!CAp::Assert(CAp::Len(lagmult)>=nec+nic+nlec+nlic,"Phase13Iteration: integrity check failed"))
|
|
return(false);
|
|
//--- Report iteration beginning
|
|
if(dotrace)
|
|
{
|
|
if(state13.m_usecorrection)
|
|
CAp::Trace("\n--- linear step with second-order correction -------------------------------------------------------\n");
|
|
else
|
|
CAp::Trace("\n--- linear step without second-order correction ----------------------------------------------------\n");
|
|
}
|
|
//--- Default decision is to continue algorithm
|
|
status=1;
|
|
stp=0;
|
|
dnrm=0;
|
|
//--- Determine step direction using linearized model with no conjugacy terms
|
|
LPSubproblemRestart(State,State.m_subsolver);
|
|
if(!LPSubproblemSolve(State,State.m_subsolver,curx,curfi,curj,innerk,state13.m_d,lagmult))
|
|
{
|
|
if(dotrace)
|
|
CAp::Trace("> [WARNING] initial phase #1 LP subproblem failed\n");
|
|
//--- Increase failures counter.
|
|
//--- Stop after too many subsequent failures
|
|
State.m_lpfailurecnt++;
|
|
if(State.m_lpfailurecnt>=m_lpfailureslimit)
|
|
{
|
|
State.m_repterminationtype=7;
|
|
status=0;
|
|
if(dotrace)
|
|
CAp::Trace("> stopping condition met: too many phase #1 LP failures\n");
|
|
result=false;
|
|
return(result);
|
|
}
|
|
//--- Can not solve LP subproblem, decrease trust radius
|
|
State.m_trustrad=0.5*State.m_trustrad;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> trust radius was decreased to {0,0:E4}\n",State.m_trustrad));
|
|
if(State.m_trustrad<State.m_epsx)
|
|
{
|
|
State.m_repterminationtype=2;
|
|
status=0;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stopping condition met: trust radius is smaller than %.3E\n",State.m_epsx));
|
|
}
|
|
else
|
|
status=-1;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
mu=MathMax(CAblasF::RMaxAbsV(State.m_historylen,State.m_maxlaghistory),CAblasF::RMaxAbsV(nec+nic+nlec+nlic,lagmult));
|
|
mu=CApServ::Coalesce(mu,m_defaultl1penalty);
|
|
//--- Compute second order correction if required. The issue we address here
|
|
//--- is a tendency of L1 penalized function to reject steps built using simple
|
|
//--- linearized model when nonlinear constraints change faster than the target.
|
|
//--- The idea is that we perform trial step (stp=1) using simple linearized model,
|
|
//--- compute constraint vector at the new trial point - and use these updated
|
|
//--- constraint linearizations back at the initial point.
|
|
if(!state13.m_usecorrection)
|
|
{
|
|
label=4;
|
|
break;
|
|
}
|
|
//--- Perform trial step using vector D to StepKXC
|
|
state13.m_stepkxc=curx+state13.m_d+0;
|
|
SLPSendX(State,state13.m_stepkxc);
|
|
State.m_needfij=true;
|
|
state13.m_rphase13state.stage=0;
|
|
label=-1;
|
|
break;
|
|
}
|
|
while(label>=0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 0:
|
|
State.m_needfij=false;
|
|
if(!SLPRetrieveFIJ(State,state13.m_stepkfic,state13.m_stepkjc))
|
|
{
|
|
//--- Failed to retrieve func/Jac, infinities detected
|
|
State.m_repterminationtype=-8;
|
|
status=0;
|
|
if(dotrace)
|
|
CAp::Trace("[ERROR] infinities in target/constraints are detected\n");
|
|
result=false;
|
|
return(result);
|
|
}
|
|
//--- Move back to point CurX[], restore original linearization of the target
|
|
state13.m_stepkfic.Set(0,curfi[0]);
|
|
state13.m_stepkxc=curx;
|
|
state13.m_stepkjc.Row(0,curj[0]+0);
|
|
//--- Extrapolate linearization of nonlinear constraints back to origin
|
|
for(i=1; i<=nlec+nlic; i++)
|
|
{
|
|
v=state13.m_d.Dot(state13.m_stepkjc[i]+0);
|
|
state13.m_stepkfic.Add(i,-v);
|
|
}
|
|
//--- Solve linearized problem one more time, now with new linearization of constraints
|
|
//--- (but still old linearization of the target), obtain DX
|
|
//--- NOTE: because LPSubproblemRestart() call resets set of conjugate constraints, we
|
|
//--- have to re-add it after solve.
|
|
LPSubproblemRestart(State,State.m_subsolver);
|
|
if(!LPSubproblemSolve(State,State.m_subsolver,state13.m_stepkxc,state13.m_stepkfic,state13.m_stepkjc,innerk,state13.m_dx,state13.m_dummylagmult))
|
|
{
|
|
//--- Second LP subproblem failed.
|
|
//--- Noncritical failure, can be ignored,
|
|
if(dotrace)
|
|
CAp::Trace("> [WARNING] second phase #1 LP subproblem failed\n");
|
|
if(dotrace)
|
|
CAp::Trace("> using step without second order correction\n");
|
|
}
|
|
else
|
|
{
|
|
//--- Set D to new direction
|
|
state13.m_d=state13.m_dx;
|
|
}
|
|
case 4:
|
|
//--- Now we have search direction in D:
|
|
//--- * compute DNrm
|
|
//--- * append D to the list of the conjugacy constraints, so next time when we use the solver we will
|
|
//--- automatically produce conjugate direction
|
|
dnrm=CAblasF::RMaxAbsV(n,state13.m_d);
|
|
LPSubproblemAppendConjugacyConstraint(State,State.m_subsolver,state13.m_d);
|
|
//--- Perform merit function backtracking line search, with trial point being
|
|
//--- computed as XN = XK + Stp*D, with Stp in [0,1]
|
|
//--- NOTE: we use MeritLagMult - Lagrange multipliers computed for initial,
|
|
//--- uncorrected task - for the merit function model.
|
|
//--- Using DummyLagMult can destabilize algorithm.
|
|
localstp=1.0;
|
|
nu=0.5;
|
|
f0=MeritFunction(State,curx,curfi,lagmult,mu,state13.m_tmpmerit);
|
|
f1=f0;
|
|
COptServ::SmoothnessMonitorStartLineSearch(smonitor,curx,curfi,curj);
|
|
case 6:
|
|
for(i=0; i<n; i++)
|
|
state13.m_stepkxn.Set(i,curx[i]+state13.m_d[i]*localstp);
|
|
SLPSendX(State,state13.m_stepkxn);
|
|
State.m_needfij=true;
|
|
state13.m_rphase13state.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_needfij=false;
|
|
if(!SLPRetrieveFIJ(State,state13.m_stepkfin,state13.m_stepkjn))
|
|
{
|
|
//--- Failed to retrieve func/Jac, infinities detected
|
|
State.m_repterminationtype=-8;
|
|
status=0;
|
|
if(dotrace)
|
|
CAp::Trace("[ERROR] infinities in target/constraints are detected\n");
|
|
result=false;
|
|
return(result);
|
|
}
|
|
COptServ::SmoothnessMonitorEnqueuePoint(smonitor,state13.m_d,localstp,state13.m_stepkxn,state13.m_stepkfin,state13.m_stepkjn);
|
|
f1=MeritFunction(State,state13.m_stepkxn,state13.m_stepkfin,lagmult,mu,state13.m_tmpmerit);
|
|
if(f1<f0)
|
|
{
|
|
//--- Step is found!
|
|
label=7;
|
|
break;
|
|
}
|
|
if(localstp<0.001)
|
|
{
|
|
//--- Step is shorter than 0.001 times current search direction,
|
|
//--- it means that no good step can be found.
|
|
localstp=0;
|
|
SLPCopyState(State,curx,curfi,curj,state13.m_stepkxn,state13.m_stepkfin,state13.m_stepkjn);
|
|
label=7;
|
|
break;
|
|
}
|
|
localstp=nu*localstp;
|
|
nu=MathMax(0.1,0.5*nu);
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
COptServ::SmoothnessMonitorFinalizeLineSearch(smonitor);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i])
|
|
state13.m_stepkxn.Set(i,MathMax(state13.m_stepkxn[i],State.m_scaledbndl[i]));
|
|
if(State.m_HasBndU[i])
|
|
state13.m_stepkxn.Set(i,MathMin(state13.m_stepkxn[i],State.m_scaledbndu[i]));
|
|
}
|
|
if(userterminationneeded)
|
|
{
|
|
//--- User requested termination, break before we move to new point
|
|
State.m_repterminationtype=8;
|
|
status=0;
|
|
if(dotrace)
|
|
CAp::Trace("> user requested termination\n");
|
|
result=false;
|
|
return(result);
|
|
}
|
|
//--- Trace
|
|
if(!dotrace)
|
|
{
|
|
label=8;
|
|
break;
|
|
}
|
|
if(!doprobing)
|
|
{
|
|
label=10;
|
|
break;
|
|
}
|
|
COptServ::SmoothnessMonitorStartProbing(smonitor,1.0,2,State.m_trustrad);
|
|
case 12:
|
|
if(!COptServ::SmoothnessMonitorProbe(smonitor))
|
|
{
|
|
label=13;
|
|
break;
|
|
}
|
|
for(j=0; j<n; j++)
|
|
{
|
|
state13.m_stepkxc.Set(j,curx[j]+state13.m_d[j]*smonitor.m_probingstp);
|
|
if(State.m_HasBndL[j])
|
|
state13.m_stepkxc.Set(j,MathMax(state13.m_stepkxc[j],State.m_scaledbndl[j]));
|
|
if(State.m_HasBndU[j])
|
|
state13.m_stepkxc.Set(j,MathMin(state13.m_stepkxc[j],State.m_scaledbndu[j]));
|
|
}
|
|
SLPSendX(State,state13.m_stepkxc);
|
|
State.m_needfij=true;
|
|
state13.m_rphase13state.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_needfij=false;
|
|
if(!SLPRetrieveFIJ(State,state13.m_stepkfic,state13.m_stepkjc))
|
|
{
|
|
label=13;
|
|
break;
|
|
}
|
|
smonitor.m_probingf.Set(0,RawLagrangian(State,state13.m_stepkxc,state13.m_stepkfic,lagmult,state13.m_tmpmerit));
|
|
smonitor.m_probingf.Set(1,state13.m_stepkfic[0]);
|
|
label=12;
|
|
break;
|
|
case 13:
|
|
CAp::Trace("*** ------------------------------------------------------------\n");
|
|
CAp::Trace("*** | probing search direction suggested by LP subproblem |\n");
|
|
CAp::Trace("*** ------------------------------------------------------------\n");
|
|
CAp::Trace("*** | Step | Lagrangian (unaugmentd)| Target function |\n");
|
|
CAp::Trace("*** |along D| must be smooth | must be smooth |\n");
|
|
CAp::Trace("*** | | function | slope | function | slope |\n");
|
|
COptServ::SmoothnessMonitorTraceProbingResults(smonitor);
|
|
case 10:
|
|
mx=0;
|
|
for(i=0; i<n; i++)
|
|
mx=MathMax(mx,MathAbs(state13.m_d[i])/State.m_trustrad);
|
|
if(localstp>0.0)
|
|
CAp::Trace("> nonzero linear step was performed\n");
|
|
else
|
|
CAp::Trace("> zero linear step was performed\n");
|
|
CAp::Trace(StringFormat("max(|Di|)/TrustRad = %.6f\n",mx));
|
|
CAp::Trace(StringFormat("stp = %.6f\n",localstp));
|
|
if(dotracexd)
|
|
{
|
|
CAp::Trace("X0 (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(curx,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("D (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(state13.m_d,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("X1 (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(state13.m_stepkxn,0,n);
|
|
CAp::Trace("\n");
|
|
}
|
|
CAp::Trace(StringFormat("meritF: %14.6E -> %14.6E (delta=%11.3E)\n",f0,f1,f1 - f0));
|
|
CAp::Trace(StringFormat("scaled-targetF: %14.6E -> %14.6E (delta=%11.3E)\n",curfi[0],state13.m_stepkfin[0],state13.m_stepkfin[0] - curfi[0]));
|
|
case 8:
|
|
//--- Move to new point
|
|
stp=localstp;
|
|
SLPCopyState(State,state13.m_stepkxn,state13.m_stepkfin,state13.m_stepkjn,curx,curfi,curj);
|
|
if(localstp<=0.0)
|
|
{
|
|
label=14;
|
|
break;
|
|
}
|
|
//--- Report one more inner iteration
|
|
State.m_repinneriterationscount++;
|
|
SLPSendX(State,curx);
|
|
State.m_f=curfi[0]*State.m_fscales[0];
|
|
State.m_xupdated=true;
|
|
state13.m_rphase13state.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_xupdated=false;
|
|
//--- Update constraint violations
|
|
COptServ::CheckLcViolation(State.m_scaledcleic,State.m_lcsrcidx,nec,nic,curx,n,State.m_replcerr,State.m_replcidx);
|
|
COptServ::UnScaleAndCheckNLcViolation(curfi,State.m_fscales,nlec,nlic,State.m_repnlcerr,State.m_repnlcidx);
|
|
case 14:
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
//--- Saving State
|
|
result=true;
|
|
state13.m_rphase13state.ba[0]=dotrace;
|
|
state13.m_rphase13state.ba[1]=doprobing;
|
|
state13.m_rphase13state.ba[2]=dotracexd;
|
|
state13.m_rphase13state.ia.Set(0,n);
|
|
state13.m_rphase13state.ia.Set(1,nslack);
|
|
state13.m_rphase13state.ia.Set(2,nec);
|
|
state13.m_rphase13state.ia.Set(3,nic);
|
|
state13.m_rphase13state.ia.Set(4,nlec);
|
|
state13.m_rphase13state.ia.Set(5,nlic);
|
|
state13.m_rphase13state.ia.Set(6,innerk);
|
|
state13.m_rphase13state.ia.Set(7,i);
|
|
state13.m_rphase13state.ia.Set(8,j);
|
|
state13.m_rphase13state.ra.Set(0,v);
|
|
state13.m_rphase13state.ra.Set(1,mx);
|
|
state13.m_rphase13state.ra.Set(2,f0);
|
|
state13.m_rphase13state.ra.Set(3,f1);
|
|
state13.m_rphase13state.ra.Set(4,nu);
|
|
state13.m_rphase13state.ra.Set(5,localstp);
|
|
state13.m_rphase13state.ra.Set(6,mu);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes Phase2 temporaries. It should be called|
|
|
//| before beginning of each new iteration. You may call it multiple |
|
|
//| times for the same instance of Phase2 temporaries. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State2 - instance to be initialized. |
|
|
//| N - problem dimensionality |
|
|
//| NEC, NIC - linear equality / inequality constraint count |
|
|
//| NLEC, NLIC - nonlinear equality / inequality constraint count|
|
|
//| MeritLagMult - Lagrange multiplier estimates used by merit |
|
|
//| function (we could use ones computed during |
|
|
//| phase #2, but these may differ from ones |
|
|
//| computed initially at the beginning of the outer|
|
|
//| iteration, so it may confuse algorithm) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State2 - instance being initialized |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::Phase2Init(CMinSLPPhase2State &state2,
|
|
int n,
|
|
int nec,
|
|
int nic,
|
|
int nlec,
|
|
int nlic,
|
|
CRowDouble &meritlagmult)
|
|
{
|
|
int nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
//--- allocate
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_d,nslack);
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_tmp0,nslack);
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_stepkxn,n);
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_stepkxc,n);
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_stepkfin,1+nlec+nlic);
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_stepkfic,1+nlec+nlic);
|
|
CApServ::RMatrixSetLengthAtLeast(state2.m_stepkjn,1+nlec+nlic,n);
|
|
CApServ::RMatrixSetLengthAtLeast(state2.m_stepkjc,1+nlec+nlic,n);
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_stepklaggrad,n);
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_stepknlaggrad,n);
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_stepknlagmult,nec+nic+nlec+nlic);
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_meritlagmult,nec+nic+nlec+nlic);
|
|
state2.m_meritlagmult=meritlagmult;
|
|
state2.m_rphase2state.ia.Resize(12+1);
|
|
ArrayResize(state2.m_rphase2state.ba,3);
|
|
state2.m_rphase2state.ra.Resize(10);
|
|
state2.m_rphase2state.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tries to perform phase #2 iterations. |
|
|
//| Phase #2 is a sequence of linearized steps minimizing |
|
|
//| L2-penalized Lagrangian performed with successively increasing |
|
|
//| set of conjugacy constraints (which make algorithm behavior |
|
|
//| similar to that of CG). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - SLP solver State |
|
|
//| SMonitor - smoothness monitor |
|
|
//| UserTerminationNeeded - True if user requested termination |
|
|
//| CurX - current point, array[N] |
|
|
//| CurFi - function vector at CurX, array[1 + NLEC + NLIC] |
|
|
//| CurJ - Jacobian at CurX, array[1 + NLEC + NLIC, N] |
|
|
//| LagMult - array[NEC + NIC + NLEC + NLIC], contents ignored |
|
|
//| on input. |
|
|
//| GammaMax - current estimate of the Hessian norm |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - RepTerminationType is set to current termination |
|
|
//| code (if Status = 0). |
|
|
//| CurX - advanced to new point |
|
|
//| CurFi - updated with function vector at CurX[] |
|
|
//| CurJ - updated with Jacobian at CurX[] |
|
|
//| LagMult - filled with current Lagrange multipliers |
|
|
//| GammaMax - updated estimate of the Hessian norm |
|
|
//| Status - when reverse communication is done, Status is set |
|
|
//| to: |
|
|
//| * negative value, if we have to restart outer |
|
|
//| iteration |
|
|
//| * positive value, if we can proceed to the next |
|
|
//| stage of the outer iteration |
|
|
//| * zero, if algorithm is terminated |
|
|
//| (RepTerminationType is set to appropriate value) |
|
|
//+------------------------------------------------------------------+
|
|
bool CNLCSLP::Phase2Iteration(CMinSLPState &State,
|
|
CMinSLPPhase2State &state2,
|
|
CSmoothnessMonitor &smonitor,
|
|
bool userterminationneeded,
|
|
CRowDouble &curx,
|
|
CRowDouble &curfi,
|
|
CMatrixDouble &curj,
|
|
CRowDouble &lagmult,
|
|
double &gammamax,
|
|
int &status)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=0;
|
|
int nslack=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int nlec=0;
|
|
int nlic=0;
|
|
double stp=0;
|
|
int mcinfo=0;
|
|
int mcnfev=0;
|
|
int mcstage=0;
|
|
int i=0;
|
|
int j=0;
|
|
int innerk=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double mx=0;
|
|
int nondescentcnt=0;
|
|
double stepklagval=0;
|
|
double stepknlagval=0;
|
|
double gammaprev=0;
|
|
double f0=0;
|
|
double f1=0;
|
|
double mu=0;
|
|
bool dotrace=false;
|
|
bool doprobing=false;
|
|
bool dotracexd=false;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(state2.m_rphase2state.stage>=0)
|
|
{
|
|
n=state2.m_rphase2state.ia[0];
|
|
nslack=state2.m_rphase2state.ia[1];
|
|
nec=state2.m_rphase2state.ia[2];
|
|
nic=state2.m_rphase2state.ia[3];
|
|
nlec=state2.m_rphase2state.ia[4];
|
|
nlic=state2.m_rphase2state.ia[5];
|
|
mcinfo=state2.m_rphase2state.ia[6];
|
|
mcnfev=state2.m_rphase2state.ia[7];
|
|
mcstage=state2.m_rphase2state.ia[8];
|
|
i=state2.m_rphase2state.ia[9];
|
|
j=state2.m_rphase2state.ia[10];
|
|
innerk=state2.m_rphase2state.ia[11];
|
|
nondescentcnt=state2.m_rphase2state.ia[12];
|
|
dotrace=state2.m_rphase2state.ba[0];
|
|
doprobing=state2.m_rphase2state.ba[1];
|
|
dotracexd=state2.m_rphase2state.ba[2];
|
|
stp=state2.m_rphase2state.ra[0];
|
|
v=state2.m_rphase2state.ra[1];
|
|
vv=state2.m_rphase2state.ra[2];
|
|
mx=state2.m_rphase2state.ra[3];
|
|
stepklagval=state2.m_rphase2state.ra[4];
|
|
stepknlagval=state2.m_rphase2state.ra[5];
|
|
gammaprev=state2.m_rphase2state.ra[6];
|
|
f0=state2.m_rphase2state.ra[7];
|
|
f1=state2.m_rphase2state.ra[8];
|
|
mu=state2.m_rphase2state.ra[9];
|
|
}
|
|
else
|
|
{
|
|
n=-819;
|
|
nslack=-826;
|
|
nec=667;
|
|
nic=692;
|
|
nlec=84;
|
|
nlic=529;
|
|
mcinfo=14;
|
|
mcnfev=386;
|
|
mcstage=-908;
|
|
i=577;
|
|
j=289;
|
|
innerk=317;
|
|
nondescentcnt=476;
|
|
dotrace=true;
|
|
doprobing=false;
|
|
dotracexd=true;
|
|
stp=-962;
|
|
v=161;
|
|
vv=-447;
|
|
mx=-799;
|
|
stepklagval=508;
|
|
stepknlagval=-153;
|
|
gammaprev=-450;
|
|
f0=769;
|
|
f1=638;
|
|
mu=-361;
|
|
}
|
|
switch(state2.m_rphase2state.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
nlec=State.m_nlec;
|
|
nlic=State.m_nlic;
|
|
nslack=n+2*(nec+nlec)+(nic+nlic);
|
|
dotrace=CAp::IsTraceEnabled("SLP");
|
|
dotracexd=dotrace && CAp::IsTraceEnabled("SLP.DETAILED");
|
|
doprobing=CAp::IsTraceEnabled("SLP.PROBING");
|
|
if(!CAp::Assert(CAp::Len(lagmult)>=nec+nic+nlec+nlic,"Phase13Iteration: integrity check failed"))
|
|
return(false);
|
|
//--- Report iteration beginning
|
|
if(dotrace)
|
|
CAp::Trace("\n--- linear step with conjugate constraints (CG-like convergence) -----------------------------------\n");
|
|
//--- The default decision is to continue iterations
|
|
status=1;
|
|
//--- Perform inner LP subiterations.
|
|
//--- During this process we maintain information about several points:
|
|
//---*point #0, initial one, with "step0" prefix
|
|
//---*point #K, last one of current LP session, with "stepk" prefix
|
|
//--- * additionally we have point #KN, current candidate during line search at step K.
|
|
//--- For each point we store:
|
|
//--- * location X (scaled coordinates)
|
|
//--- * function vector Fi (target function + nonlinear constraints)
|
|
//--- * scaled Jacobian J
|
|
mu=MathMax(CAblasF::RMaxAbsV(State.m_historylen,State.m_maxlaghistory),CAblasF::RMaxAbsV(nec+nic+nlec+nlic,State.m_meritlagmult));
|
|
mu=CApServ::Coalesce(mu,m_defaultl1penalty);
|
|
nondescentcnt=0;
|
|
LPSubproblemRestart(State,State.m_subsolver);
|
|
innerk=1;
|
|
label=3;
|
|
break;
|
|
}
|
|
while(label>=0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 3:
|
|
if(innerk>n)
|
|
{
|
|
label=5;
|
|
break;
|
|
}
|
|
//--- Formulate LP subproblem and solve it
|
|
if(!LPSubproblemSolve(State,State.m_subsolver,curx,curfi,curj,innerk,state2.m_d,lagmult))
|
|
{
|
|
//--- LP solver failed due to numerical errors; exit.
|
|
//--- It may happen when we solve problem with LOTS of conjugacy constraints.
|
|
if(innerk==1)
|
|
{
|
|
//--- The very first iteration failed, really strange.
|
|
if(dotrace)
|
|
CAp::Trace("[WARNING] the very first LP subproblem failed to produce descent direction\n");
|
|
}
|
|
else
|
|
{
|
|
//--- Quite a normal, the problem is overconstrained by conjugacy constraints now
|
|
if(dotrace)
|
|
CAp::Trace("> LP subproblem is overconstrained (happens after too many iterations),time to stop\n");
|
|
}
|
|
result=false;
|
|
return(result);
|
|
}
|
|
mx=0;
|
|
for(i=0; i<n; i++)
|
|
mx=MathMax(mx,MathAbs(state2.m_d[i])/State.m_trustrad);
|
|
if(mx==0.0)
|
|
{
|
|
//--- Nearly-zero direction is suggested (maybe we arrived exactly to the solution), stop iterations
|
|
status=1;
|
|
SLPCopyState(State,curx,curfi,curj,state2.m_stepkxn,state2.m_stepkfin,state2.m_stepkjn);
|
|
if(dotrace)
|
|
CAp::Trace("> LP subproblem suggested nearly zero step\n");
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("max(|Di|)/TrustRad = %.6f\n",mx));
|
|
if(dotrace)
|
|
CAp::Trace("> stopping CG-like iterations\n");
|
|
result=false;
|
|
return(result);
|
|
}
|
|
LPSubproblemAppendConjugacyConstraint(State,State.m_subsolver,state2.m_d);
|
|
//--- Perform line search to minimize Lagrangian along D.
|
|
//--- Post-normalize StepKXN with respect to box constraints.
|
|
//--- MCSRCH can fail in the following cases:
|
|
//--- * rounding errors prevent optimization
|
|
//--- * non-descent direction is specified (MCINFO=0 is returned)
|
|
//--- In the latter case we proceed to minimization of merit function.
|
|
//--- NOTE: constraint violation reports are updated during Lagrangian computation
|
|
state2.m_lastlcerr=0;
|
|
state2.m_lastlcidx=-1;
|
|
state2.m_lastnlcerr=0;
|
|
state2.m_lastnlcidx=-1;
|
|
CApServ::RVectorSetLengthAtLeast(state2.m_tmp0,n);
|
|
LagrangianFG(State,curx,State.m_trustrad,curfi,curj,lagmult,state2.m_tmplagrangianfg,stepklagval,state2.m_stepklaggrad,state2.m_lastlcerr,state2.m_lastlcidx,state2.m_lastnlcerr,state2.m_lastnlcidx);
|
|
SLPCopyState(State,curx,curfi,curj,state2.m_stepkxn,state2.m_stepkfin,state2.m_stepkjn);
|
|
state2.m_stepknlaggrad=state2.m_stepklaggrad;
|
|
v=CAblasF::RDotV(n,state2.m_d,state2.m_stepklaggrad);
|
|
if(v>=0.0)
|
|
{
|
|
//--- Non-descent direction D was specified; it may happen because LP subproblem favors
|
|
//--- directions which decrease L1 penalty and default augmentation of Lagrangian involves
|
|
//--- only L2 term.
|
|
//--- Append direction to the conjugacy constraints and retry direction generation.
|
|
//--- We make several retries with conjugate directions before giving up.
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> LP subproblem suggested nondescent step,skipping it (dLag=%.3E)\n",v));
|
|
nondescentcnt++;
|
|
if(m_nondescentlimit>0 && nondescentcnt>m_nondescentlimit)
|
|
{
|
|
if(dotrace)
|
|
CAp::Trace("> too many nondescent steps,stopping CG-like iterations\n");
|
|
status=1;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
label=4;
|
|
break;
|
|
}
|
|
COptServ::SmoothnessMonitorStartLineSearch(smonitor,curx,curfi,curj);
|
|
stepknlagval=stepklagval;
|
|
mcnfev=0;
|
|
mcstage=0;
|
|
stp=1.0;
|
|
CLinMin::MCSrch(n,state2.m_stepkxn,stepknlagval,state2.m_stepknlaggrad,state2.m_d,stp,1.0,m_slpgtol,mcinfo,mcnfev,state2.m_tmp0,state2.m_mcstate,mcstage);
|
|
case 6:
|
|
if(mcstage==0)
|
|
{
|
|
label=7;
|
|
break;
|
|
}
|
|
SLPSendX(State,state2.m_stepkxn);
|
|
State.m_needfij=true;
|
|
state2.m_rphase2state.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
State.m_needfij=false;
|
|
if(!SLPRetrieveFIJ(State,state2.m_stepkfin,state2.m_stepkjn))
|
|
{
|
|
//--- Failed to retrieve func/Jac, infinities detected
|
|
status=0;
|
|
State.m_repterminationtype=-8;
|
|
if(dotrace)
|
|
CAp::Trace("[ERROR] infinities in target/constraints are detected\n");
|
|
result=false;
|
|
return(result);
|
|
}
|
|
COptServ::SmoothnessMonitorEnqueuePoint(smonitor,state2.m_d,stp,state2.m_stepkxn,state2.m_stepkfin,state2.m_stepkjn);
|
|
LagrangianFG(State,state2.m_stepkxn,State.m_trustrad,state2.m_stepkfin,state2.m_stepkjn,lagmult,state2.m_tmplagrangianfg,stepknlagval,state2.m_stepknlaggrad,state2.m_lastlcerr,state2.m_lastlcidx,state2.m_lastnlcerr,state2.m_lastnlcidx);
|
|
CLinMin::MCSrch(n,state2.m_stepkxn,stepknlagval,state2.m_stepknlaggrad,state2.m_d,stp,1.0,m_slpgtol,mcinfo,mcnfev,state2.m_tmp0,state2.m_mcstate,mcstage);
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
COptServ::SmoothnessMonitorFinalizeLineSearch(smonitor);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i])
|
|
state2.m_stepkxn.Set(i,MathMax(state2.m_stepkxn[i],State.m_scaledbndl[i]));
|
|
if(State.m_HasBndU[i])
|
|
state2.m_stepkxn.Set(i,MathMin(state2.m_stepkxn[i],State.m_scaledbndu[i]));
|
|
}
|
|
if(mcinfo<=0)
|
|
{
|
|
//--- Line search failed miserably, terminate
|
|
status=1;
|
|
if(innerk==1)
|
|
{
|
|
//--- The very first iteration failed, really strange.
|
|
//--- Let's decrease trust radius and try one more time.
|
|
State.m_trustrad=0.5*State.m_trustrad;
|
|
if(dotrace)
|
|
CAp::Trace("> line search failed miserably for unknown reason,decreasing trust radius\n");
|
|
if(State.m_trustrad<State.m_epsx)
|
|
{
|
|
State.m_repterminationtype=2;
|
|
status=0;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stopping condition met: trust radius is smaller than %.3E\n",State.m_epsx));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Well, it can be normal
|
|
if(dotrace)
|
|
CAp::Trace("> line search failed miserably for unknown reason,proceeding further\n");
|
|
}
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(mcinfo==1)
|
|
LPSubproblemUpdateHessian(State,State.m_subsolver,curx,state2.m_stepklaggrad,state2.m_stepkxn,state2.m_stepknlaggrad);
|
|
//--- Update GammaMax - estimate of the function Hessian norm
|
|
v=0;
|
|
vv=0;
|
|
mx=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
mx=MathMax(mx,MathAbs(state2.m_stepkxn[i]-curx[i]));
|
|
v+=CMath::Sqr(state2.m_stepkxn[i]-curx[i]);
|
|
vv+=(state2.m_stepkjn.Get(0,i)-curj.Get(0,i))*(state2.m_stepkxn[i]-curx[i]);
|
|
}
|
|
gammaprev=gammamax;
|
|
if(mx>m_bfgstol)
|
|
gammamax=MathMax(gammamax,MathAbs(vv/v));
|
|
//--- Trace
|
|
if(!dotrace)
|
|
{
|
|
label=8;
|
|
break;
|
|
}
|
|
if(!doprobing)
|
|
{
|
|
label=10;
|
|
break;
|
|
}
|
|
COptServ::SmoothnessMonitorStartProbing(smonitor,1.0,2,State.m_trustrad);
|
|
case 12:
|
|
if(!COptServ::SmoothnessMonitorProbe(smonitor))
|
|
{
|
|
label=13;
|
|
break;
|
|
}
|
|
for(j=0; j<n; j++)
|
|
{
|
|
state2.m_stepkxc.Set(j,curx[j]+state2.m_d[j]*smonitor.m_probingstp);
|
|
if(State.m_HasBndL[j])
|
|
state2.m_stepkxc.Set(j,MathMax(state2.m_stepkxc[j],State.m_scaledbndl[j]));
|
|
if(State.m_HasBndU[j])
|
|
state2.m_stepkxc.Set(j,MathMin(state2.m_stepkxc[j],State.m_scaledbndu[j]));
|
|
}
|
|
SLPSendX(State,state2.m_stepkxc);
|
|
State.m_needfij=true;
|
|
state2.m_rphase2state.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_needfij=false;
|
|
if(!SLPRetrieveFIJ(State,state2.m_stepkfic,state2.m_stepkjc))
|
|
{
|
|
label=13;
|
|
break;
|
|
}
|
|
smonitor.m_probingf.Set(0,RawLagrangian(State,state2.m_stepkxc,state2.m_stepkfic,lagmult,state2.m_tmpmerit));
|
|
smonitor.m_probingf.Set(1,state2.m_stepkfic[0]);
|
|
label=12;
|
|
break;
|
|
case 13:
|
|
CAp::Trace("*** ------------------------------------------------------------\n");
|
|
CAp::Trace("*** | probing search direction suggested by LP subproblem |\n");
|
|
CAp::Trace("*** ------------------------------------------------------------\n");
|
|
CAp::Trace("*** | Step | Lagrangian (unaugmentd)| Target function |\n");
|
|
CAp::Trace("*** |along D| must be smooth | must be smooth |\n");
|
|
CAp::Trace("*** | | function | slope | function | slope |\n");
|
|
COptServ::SmoothnessMonitorTraceProbingResults(smonitor);
|
|
case 10:
|
|
mx=0;
|
|
for(i=0; i<n; i++)
|
|
mx=MathMax(mx,MathAbs(state2.m_d[i])/State.m_trustrad);
|
|
f0=MeritFunction(State,curx,curfi,state2.m_meritlagmult,mu,state2.m_tmpmerit);
|
|
f1=MeritFunction(State,state2.m_stepkxn,state2.m_stepkfin,state2.m_meritlagmult,mu,state2.m_tmpmerit);
|
|
CAp::Trace("> LP subproblem produced good direction,minimization was performed\n");
|
|
CAp::Trace(StringFormat("max(|Di|)/TrustRad = %.6f\n",mx));
|
|
CAp::Trace(StringFormat("stp = %.6f\n",stp));
|
|
if(dotracexd)
|
|
{
|
|
CAp::Trace("X0 = ");
|
|
CApServ::TraceVectorAutopRec(curx,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("D = ");
|
|
CApServ::TraceVectorAutopRec(state2.m_d,0,n);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("X1 = X0 + stp*D\n");
|
|
CAp::Trace(" = ");
|
|
CApServ::TraceVectorAutopRec(state2.m_stepkxn,0,n);
|
|
CAp::Trace("\n");
|
|
}
|
|
CAp::Trace(StringFormat("meritF: %14.6E -> %14.6E (delta=%11.3E)\n",f0,f1,f1 - f0));
|
|
CAp::Trace(StringFormat("scaled-targetF: %14.6E -> %14.6E (delta=%11.3E)\n",curfi[0],state2.m_stepkfin[0],state2.m_stepkfin[0] - curfi[0]));
|
|
CAp::Trace(StringFormat("aug.Lagrangian: %14.6E -> %14.6E (delta=%11.3E)\n",stepklagval,stepknlagval,stepknlagval - stepklagval));
|
|
if(gammamax>gammaprev)
|
|
CAp::Trace(StringFormat("|H| = %.3E (Hessian norm increased)\n",gammamax));
|
|
case 8:
|
|
//--- Check status of the termination request
|
|
//--- Update current point
|
|
//--- Update constraint status.
|
|
//--- Report iteration.
|
|
if(userterminationneeded)
|
|
{
|
|
//--- User requested termination, break before we move to new point
|
|
status=0;
|
|
State.m_repterminationtype=8;
|
|
if(dotrace)
|
|
CAp::Trace("# user requested termination\n");
|
|
result=false;
|
|
return(result);
|
|
}
|
|
SLPCopyState(State,state2.m_stepkxn,state2.m_stepkfin,state2.m_stepkjn,curx,curfi,curj);
|
|
State.m_replcerr=state2.m_lastlcerr;
|
|
State.m_replcidx=state2.m_lastlcidx;
|
|
State.m_repnlcerr=state2.m_lastnlcerr;
|
|
State.m_repnlcidx=state2.m_lastnlcidx;
|
|
State.m_repinneriterationscount++;
|
|
SLPSendX(State,curx);
|
|
State.m_f=curfi[0]*State.m_fscales[0];
|
|
State.m_xupdated=true;
|
|
state2.m_rphase2state.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_xupdated=false;
|
|
//--- Terminate inner LP subiterations
|
|
if(State.m_maxits>0 && State.m_repinneriterationscount>=State.m_maxits)
|
|
{
|
|
//--- Iteration limit exhausted
|
|
status=1;
|
|
if(dotrace)
|
|
CAp::Trace("# stopping criteria met (MaxIts iterations performed)\n");
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(stp>=m_slpstpclosetoone)
|
|
{
|
|
//--- Step is close to 1.0, either of two is likely:
|
|
//--- * we move through nearly linear region of F()
|
|
//--- * we try to enforce some strongly violated constraint
|
|
//--- In any case, authors of the original algorithm recommend to break inner LP
|
|
//--- iteration and proceed to test of sufficient decrease of merit function.
|
|
status=1;
|
|
if(dotrace)
|
|
CAp::Trace("> step is close to 1,stopping iterations\n");
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if((mcinfo!=1 && mcinfo!=3) && mcinfo!=5)
|
|
{
|
|
//--- Line search ended with "bad" MCINFO
|
|
//--- (neither sufficient decrease, neither maximum step);
|
|
//--- terminate.
|
|
status=1;
|
|
if(dotrace)
|
|
CAp::Trace("> line search ended with bad MCINFO,no more CG-like iterations\n");
|
|
result=false;
|
|
return(result);
|
|
}
|
|
case 4:
|
|
innerk++;
|
|
label=3;
|
|
break;
|
|
case 5:
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
//--- Saving State
|
|
result=true;
|
|
state2.m_rphase2state.ba[0]=dotrace;
|
|
state2.m_rphase2state.ba[1]=doprobing;
|
|
state2.m_rphase2state.ba[2]=dotracexd;
|
|
state2.m_rphase2state.ia.Set(0,n);
|
|
state2.m_rphase2state.ia.Set(1,nslack);
|
|
state2.m_rphase2state.ia.Set(2,nec);
|
|
state2.m_rphase2state.ia.Set(3,nic);
|
|
state2.m_rphase2state.ia.Set(4,nlec);
|
|
state2.m_rphase2state.ia.Set(5,nlic);
|
|
state2.m_rphase2state.ia.Set(6,mcinfo);
|
|
state2.m_rphase2state.ia.Set(7,mcnfev);
|
|
state2.m_rphase2state.ia.Set(8,mcstage);
|
|
state2.m_rphase2state.ia.Set(9,i);
|
|
state2.m_rphase2state.ia.Set(10,j);
|
|
state2.m_rphase2state.ia.Set(11,innerk);
|
|
state2.m_rphase2state.ia.Set(12,nondescentcnt);
|
|
state2.m_rphase2state.ra.Set(0,stp);
|
|
state2.m_rphase2state.ra.Set(1,v);
|
|
state2.m_rphase2state.ra.Set(2,vv);
|
|
state2.m_rphase2state.ra.Set(3,mx);
|
|
state2.m_rphase2state.ra.Set(4,stepklagval);
|
|
state2.m_rphase2state.ra.Set(5,stepknlagval);
|
|
state2.m_rphase2state.ra.Set(6,gammaprev);
|
|
state2.m_rphase2state.ra.Set(7,f0);
|
|
state2.m_rphase2state.ra.Set(8,f1);
|
|
state2.m_rphase2state.ra.Set(9,mu);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copies X to State.X |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::SLPSendX(CMinSLPState &State,CRowDouble &xs)
|
|
{
|
|
int n=State.m_n;
|
|
//--- copy
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && xs[i]<=State.m_scaledbndl[i])
|
|
{
|
|
State.m_x.Set(i,State.m_scaledbndl[i]);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndU[i] && xs[i]>=State.m_scaledbndu[i])
|
|
{
|
|
State.m_x.Set(i,State.m_scaledbndu[i]);
|
|
continue;
|
|
}
|
|
State.m_x.Set(i,xs[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Retrieves F-vector and scaled Jacobian, copies them to FiS and JS|
|
|
//| Returns True on success, False on failure(when F or J are not |
|
|
//| finite numbers). |
|
|
//+------------------------------------------------------------------+
|
|
bool CNLCSLP::SLPRetrieveFIJ(CMinSLPState &State,
|
|
CRowDouble &fis,
|
|
CMatrixDouble &js)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=State.m_n;
|
|
int nlec=State.m_nlec;
|
|
int nlic=State.m_nlic;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
|
|
for(i=0; i<=nlec+nlic; i++)
|
|
{
|
|
vv=1/State.m_fscales[i];
|
|
fis.Set(i,vv*State.m_fi[i]);
|
|
v=0.1*v+fis[i];
|
|
for(j=0; j<n; j++)
|
|
{
|
|
js.Set(i,j,vv*State.m_j.Get(i,j));
|
|
v=0.1*v+js.Get(i,j);
|
|
}
|
|
}
|
|
result=MathIsValidNumber(v);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copies State (X point, Fi vector, J jacobian) to preallocated |
|
|
//| storage. |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::SLPCopyState(CMinSLPState &State,
|
|
CRowDouble &x0,
|
|
CRowDouble &fi0,
|
|
CMatrixDouble &j0,
|
|
CRowDouble &x1,
|
|
CRowDouble &fi1,
|
|
CMatrixDouble &j1)
|
|
{
|
|
x1=x0;
|
|
fi1=fi0;
|
|
j1=j0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates Lagrangian of the problem (in scaled |
|
|
//| variables): its value and gradient. |
|
|
//| Additionally it also estimates violation of linear constraints at|
|
|
//| the point as well as index of the most violated constraint |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::LagrangianFG(CMinSLPState &State,
|
|
CRowDouble &x,
|
|
double trustrad,
|
|
CRowDouble &fi,
|
|
CMatrixDouble &j,
|
|
CRowDouble &lagmult,
|
|
CMinSLPTmpLagrangian &tmp,
|
|
double &f,
|
|
CRowDouble &g,
|
|
double &lcerr,
|
|
int &lcidx,
|
|
double &nlcerr,
|
|
int &nlcidx)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int nec=State.m_nec;
|
|
int nic=State.m_nic;
|
|
int nlec=State.m_nlec;
|
|
int nlic=State.m_nlic;
|
|
int i=0;
|
|
double v=0;
|
|
double vlag=0;
|
|
double vact=0;
|
|
double vd=0;
|
|
double vviolate=0;
|
|
bool usesparsegemv=false;
|
|
double dampingfactor=m_inequalitydampingfactor/trustrad;
|
|
|
|
f=0;
|
|
//--- Prepare constraint violation report
|
|
lcerr=0;
|
|
lcidx=-1;
|
|
nlcerr=0;
|
|
nlcidx=-1;
|
|
//--- Target function
|
|
f=fi[0];
|
|
g=j[0]+0;
|
|
//--- Lagrangian terms for linear constraints, constraint violations
|
|
if(nec+nic>0)
|
|
{
|
|
usesparsegemv=State.m_subsolver.m_sparserawlc.m_RIdx[nec+nic]<CApServ::SparseLevel2Density()*n*(nec+nic);
|
|
CApServ::RVectorSetLengthAtLeast(tmp.m_sclagtmp0,MathMax(nec+nic,n));
|
|
CApServ::RVectorSetLengthAtLeast(tmp.m_sclagtmp1,MathMax(nec+nic,n));
|
|
if(usesparsegemv)
|
|
CSparse::SparseMV(State.m_subsolver.m_sparserawlc,x,tmp.m_sclagtmp0);
|
|
else
|
|
CAblas::RMatrixGemVect(nec+nic,n,1.0,State.m_scaledcleic,0,0,0,x,0,0.0,tmp.m_sclagtmp0,0);
|
|
for(i=0; i<=nec+nic-1; i++)
|
|
{
|
|
//--- Estimate constraint value at the point, update violation Info
|
|
//--- NOTE: here we expect that scaledCLEIC[] has normalized rows
|
|
v=tmp.m_sclagtmp0[i]-State.m_scaledcleic.Get(i,n);
|
|
if(i<nec || v>0)
|
|
{
|
|
//--- Either equality constraint or violated inequality one.
|
|
//--- Update violation report.
|
|
vviolate=MathAbs(v);
|
|
if(vviolate>lcerr)
|
|
{
|
|
lcerr=vviolate;
|
|
lcidx=State.m_lcsrcidx[i];
|
|
}
|
|
}
|
|
//--- Prepare
|
|
vlag=lagmult[i];
|
|
tmp.m_sclagtmp1.Set(i,0);
|
|
//--- Primary Lagrangian term
|
|
if(i<nec || v>0)
|
|
{
|
|
vact=v;
|
|
vd=1;
|
|
}
|
|
else
|
|
{
|
|
vd=1/(1-dampingfactor*v);
|
|
vact=v*vd;
|
|
vd=vd*vd;
|
|
}
|
|
f+=vlag*vact;
|
|
tmp.m_sclagtmp1.Add(i,vlag*vd);
|
|
//--- Quadratic augmentation term
|
|
if(i<nec || v>0)
|
|
vact=v;
|
|
else
|
|
vact=0;
|
|
f+=0.5*m_augmentationfactor*vact*vact;
|
|
tmp.m_sclagtmp1.Add(i,m_augmentationfactor*vact);
|
|
}
|
|
if(usesparsegemv)
|
|
{
|
|
CSparse::SparseMTV(State.m_subsolver.m_sparserawlc,tmp.m_sclagtmp1,tmp.m_sclagtmp0);
|
|
g+=tmp.m_sclagtmp0;
|
|
}
|
|
else
|
|
CAblas::RMatrixGemVect(n,nec+nic,1.0,State.m_scaledcleic,0,0,1,tmp.m_sclagtmp1,0,1.0,g,0);
|
|
}
|
|
//--- Lagrangian terms for nonlinear constraints
|
|
tmp.m_sclagtmp1=vector<double>::Zeros(nlec+nlic);
|
|
for(i=0; i<nlec+nlic; i++)
|
|
{
|
|
v=fi[1+i];
|
|
if(i<nlec || v>0)
|
|
{
|
|
//--- Either equality constraint or violated inequality one.
|
|
//--- Update violation report.
|
|
vviolate=MathAbs(v)*State.m_fscales[1+i];
|
|
if(vviolate>nlcerr)
|
|
{
|
|
nlcerr=vviolate;
|
|
nlcidx=i;
|
|
}
|
|
}
|
|
vlag=lagmult[nec+nic+i];
|
|
//--- Lagrangian term
|
|
if(i<nlec || v>0)
|
|
{
|
|
vact=v;
|
|
vd=1;
|
|
}
|
|
else
|
|
{
|
|
vd=1/(1-dampingfactor*v);
|
|
vact=v*vd;
|
|
vd=vd*vd;
|
|
}
|
|
f+=vlag*vact;
|
|
tmp.m_sclagtmp1.Add(i,vlag*vd);
|
|
//--- Augmentation term
|
|
if(i<nlec || v>0)
|
|
vact=v;
|
|
else
|
|
vact=0;
|
|
f+=0.5*m_augmentationfactor*vact*vact;
|
|
tmp.m_sclagtmp1.Add(i,m_augmentationfactor*vact);
|
|
}
|
|
CAblas::RMatrixGemVect(n,nlec+nlic,1.0,j,1,0,1,tmp.m_sclagtmp1,0,1.0,g,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates L1 - penalized merit function |
|
|
//+------------------------------------------------------------------+
|
|
double CNLCSLP::MeritFunction(CMinSLPState &State,
|
|
CRowDouble &x,
|
|
CRowDouble &fi,
|
|
CRowDouble &lagmult,
|
|
double mu,
|
|
CMinSLPTmpMerit &tmp)
|
|
{
|
|
//--- create variables
|
|
double tmp0=0;
|
|
double tmp1=0;
|
|
//--- function call
|
|
MeritFunctionAndRawLagrangian(State,x,fi,lagmult,mu,tmp,tmp0,tmp1);
|
|
//--- return result
|
|
return(tmp0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates raw(unaugmented and smooth) Lagrangian |
|
|
//+------------------------------------------------------------------+
|
|
double CNLCSLP::RawLagrangian(CMinSLPState &State,
|
|
CRowDouble &x,
|
|
CRowDouble &fi,
|
|
CRowDouble &lagmult,
|
|
CMinSLPTmpMerit &tmp)
|
|
{
|
|
//--- create variables
|
|
double tmp0=0;
|
|
double tmp1=0;
|
|
//--- function call
|
|
MeritFunctionAndRawLagrangian(State,x,fi,lagmult,0.0,tmp,tmp0,tmp1);
|
|
//--- return result
|
|
return(tmp1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates L1-penalized merit function and raw |
|
|
//| (smooth and un-augmented) Lagrangian |
|
|
//+------------------------------------------------------------------+
|
|
void CNLCSLP::MeritFunctionAndRawLagrangian(CMinSLPState &State,
|
|
CRowDouble &x,
|
|
CRowDouble &fi,
|
|
CRowDouble &lagmult,
|
|
double mu,
|
|
CMinSLPTmpMerit &tmp,
|
|
double &meritf,
|
|
double &rawlag)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int nec=State.m_nec;
|
|
int nic=State.m_nic;
|
|
int nlec=State.m_nlec;
|
|
int nlic=State.m_nlic;
|
|
int i=0;
|
|
double v=0;
|
|
|
|
meritf=0;
|
|
rawlag=0;
|
|
//--- Merit function and Lagrangian: primary term
|
|
meritf=fi[0];
|
|
rawlag=fi[0];
|
|
//--- Merit function: augmentation and penalty for linear constraints
|
|
CApServ::RVectorSetLengthAtLeast(tmp.m_mftmp0,nec+nic);
|
|
CAblas::RMatrixGemVect(nec+nic,n,1.0,State.m_scaledcleic,0,0,0,x,0,0.0,tmp.m_mftmp0,0);
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
v=tmp.m_mftmp0[i]-State.m_scaledcleic.Get(i,n);
|
|
if(i<nec)
|
|
{
|
|
//--- Merit function: augmentation term + L1 penalty term
|
|
meritf+=m_meritfunctionbase*MathAbs(v)+m_meritfunctiongain*mu*MathAbs(v);
|
|
//--- Raw Lagrangian
|
|
rawlag+=lagmult[i]*v;
|
|
}
|
|
else
|
|
{
|
|
//--- Merit function: augmentation term + L1 penalty term
|
|
meritf+=m_meritfunctionbase*MathMax(v,0)+m_meritfunctiongain*mu*MathMax(v,0);
|
|
//--- Raw Lagrangian
|
|
rawlag+=lagmult[i]*v;
|
|
}
|
|
}
|
|
//--- Merit function: augmentation and penalty for nonlinear constraints
|
|
for(i=0; i<nlec+nlic; i++)
|
|
{
|
|
v=fi[1+i];
|
|
if(i<nlec)
|
|
{
|
|
//--- Merit function: augmentation term + L1 penalty term
|
|
meritf+=(m_meritfunctionbase+m_meritfunctiongain*mu)*MathAbs(v);
|
|
//--- Raw Lagrangian
|
|
rawlag+=lagmult[nec+nic+i]*v;
|
|
}
|
|
else
|
|
{
|
|
//--- Merit function: augmentation term + L1 penalty term
|
|
meritf+=(m_meritfunctionbase+m_meritfunctiongain*mu)*MathMax(v,0);
|
|
//--- Raw Lagrangian
|
|
rawlag+=lagmult[nec+nic+i]*v;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores nonlinear optimizer State. |
|
|
//| You should use functions provided by MinNLC subpackage to work |
|
|
//| with this object |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinNLCState
|
|
{
|
|
int m_aulitscnt;
|
|
int m_maxits;
|
|
int m_n;
|
|
int m_nec;
|
|
int m_ng;
|
|
int m_nh;
|
|
int m_nic;
|
|
int m_prectype;
|
|
int m_repbcidx;
|
|
int m_repdbgphase0its;
|
|
int m_repinneriterationscount;
|
|
int m_replcidx;
|
|
int m_repnfev;
|
|
int m_repnlcidx;
|
|
int m_repouteriterationscount;
|
|
int m_repterminationtype;
|
|
int m_smoothnessguardlevel;
|
|
int m_solvertype;
|
|
int m_updatefreq;
|
|
double m_diffstep;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_gammak;
|
|
double m_initialinequalitymultiplier;
|
|
double m_repbcerr;
|
|
double m_replcerr;
|
|
double m_repnlcerr;
|
|
double m_rho;
|
|
double m_stabilizingpoint;
|
|
double m_stpmax;
|
|
double m_teststep;
|
|
bool m_HasBndL[];
|
|
bool m_HasBndU[];
|
|
bool m_needfi;
|
|
bool m_needfij;
|
|
bool m_userterminationneeded;
|
|
bool m_xkpresent;
|
|
bool m_xrep;
|
|
bool m_xupdated;
|
|
RCommState m_rstate;
|
|
RCommState m_rstateaul;
|
|
RCommState m_rstateslp;
|
|
CSmoothnessMonitor m_smonitor;
|
|
CRowInt m_lcsrcidx;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_bufc;
|
|
CRowDouble m_bufd;
|
|
CRowDouble m_dfbase;
|
|
CRowDouble m_dfm1;
|
|
CRowDouble m_dfp1;
|
|
CRowDouble m_fbase;
|
|
CRowDouble m_fi;
|
|
CRowDouble m_fm1;
|
|
CRowDouble m_fm2;
|
|
CRowDouble m_fp1;
|
|
CRowDouble m_fp2;
|
|
CRowDouble m_gk1;
|
|
CRowDouble m_gk;
|
|
CRowDouble m_lastscaleused;
|
|
CRowDouble m_nubc;
|
|
CRowDouble m_nulc;
|
|
CRowDouble m_nunlc;
|
|
CRowDouble m_s;
|
|
CRowDouble m_scaledbndl;
|
|
CRowDouble m_scaledbndu;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_x;
|
|
CRowDouble m_xbase;
|
|
CRowDouble m_xc;
|
|
CRowDouble m_xk1;
|
|
CRowDouble m_xk;
|
|
CRowDouble m_xstart;
|
|
CMinSQPState m_sqpsolverstate;
|
|
CMinSLPState m_slpsolverstate;
|
|
CMinLBFGSState m_auloptimizer;
|
|
CMinLBFGSReport m_aulreport;
|
|
CMatrixDouble m_bufw;
|
|
CMatrixDouble m_bufz;
|
|
CMatrixDouble m_cleic;
|
|
CMatrixDouble m_j;
|
|
CMatrixDouble m_scaledcleic;
|
|
//--- constructor / destructor
|
|
CMinNLCState(void);
|
|
~CMinNLCState(void) {}
|
|
//---
|
|
void Copy(const CMinNLCState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinNLCState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinNLCState::CMinNLCState(void)
|
|
{
|
|
m_aulitscnt=0;
|
|
m_maxits=0;
|
|
m_n=0;
|
|
m_nec=0;
|
|
m_ng=0;
|
|
m_nh=0;
|
|
m_nic=0;
|
|
m_prectype=0;
|
|
m_repbcidx=0;
|
|
m_repdbgphase0its=0;
|
|
m_repinneriterationscount=0;
|
|
m_replcidx=0;
|
|
m_repnfev=0;
|
|
m_repnlcidx=0;
|
|
m_repouteriterationscount=0;
|
|
m_repterminationtype=0;
|
|
m_smoothnessguardlevel=0;
|
|
m_solvertype=0;
|
|
m_updatefreq=0;
|
|
m_diffstep=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_gammak=0;
|
|
m_initialinequalitymultiplier=0;
|
|
m_repbcerr=0;
|
|
m_replcerr=0;
|
|
m_repnlcerr=0;
|
|
m_rho=0;
|
|
m_stabilizingpoint=0;
|
|
m_stpmax=0;
|
|
m_teststep=0;
|
|
m_needfi=false;
|
|
m_needfij=false;
|
|
m_userterminationneeded=false;
|
|
m_xkpresent=false;
|
|
m_xrep=false;
|
|
m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLCState::Copy(const CMinNLCState &obj)
|
|
{
|
|
m_aulitscnt=obj.m_aulitscnt;
|
|
m_maxits=obj.m_maxits;
|
|
m_n=obj.m_n;
|
|
m_nec=obj.m_nec;
|
|
m_ng=obj.m_ng;
|
|
m_nh=obj.m_nh;
|
|
m_nic=obj.m_nic;
|
|
m_prectype=obj.m_prectype;
|
|
m_repbcidx=obj.m_repbcidx;
|
|
m_repdbgphase0its=obj.m_repdbgphase0its;
|
|
m_repinneriterationscount=obj.m_repinneriterationscount;
|
|
m_replcidx=obj.m_replcidx;
|
|
m_repnfev=obj.m_repnfev;
|
|
m_repnlcidx=obj.m_repnlcidx;
|
|
m_repouteriterationscount=obj.m_repouteriterationscount;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_smoothnessguardlevel=obj.m_smoothnessguardlevel;
|
|
m_solvertype=obj.m_solvertype;
|
|
m_updatefreq=obj.m_updatefreq;
|
|
m_diffstep=obj.m_diffstep;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_gammak=obj.m_gammak;
|
|
m_initialinequalitymultiplier=obj.m_initialinequalitymultiplier;
|
|
m_repbcerr=obj.m_repbcerr;
|
|
m_replcerr=obj.m_replcerr;
|
|
m_repnlcerr=obj.m_repnlcerr;
|
|
m_rho=obj.m_rho;
|
|
m_stabilizingpoint=obj.m_stabilizingpoint;
|
|
m_stpmax=obj.m_stpmax;
|
|
m_teststep=obj.m_teststep;
|
|
ArrayCopy(m_HasBndL,obj.m_HasBndL);
|
|
ArrayCopy(m_HasBndU,obj.m_HasBndU);
|
|
m_needfi=obj.m_needfi;
|
|
m_needfij=obj.m_needfij;
|
|
m_userterminationneeded=obj.m_userterminationneeded;
|
|
m_xkpresent=obj.m_xkpresent;
|
|
m_xrep=obj.m_xrep;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_rstate=obj.m_rstate;
|
|
m_rstateaul=obj.m_rstateaul;
|
|
m_rstateslp=obj.m_rstateslp;
|
|
m_smonitor=obj.m_smonitor;
|
|
m_lcsrcidx=obj.m_lcsrcidx;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_bufc=obj.m_bufc;
|
|
m_bufd=obj.m_bufd;
|
|
m_dfbase=obj.m_dfbase;
|
|
m_dfm1=obj.m_dfm1;
|
|
m_dfp1=obj.m_dfp1;
|
|
m_fbase=obj.m_fbase;
|
|
m_fi=obj.m_fi;
|
|
m_fm1=obj.m_fm1;
|
|
m_fm2=obj.m_fm2;
|
|
m_fp1=obj.m_fp1;
|
|
m_fp2=obj.m_fp2;
|
|
m_gk1=obj.m_gk1;
|
|
m_gk=obj.m_gk;
|
|
m_lastscaleused=obj.m_lastscaleused;
|
|
m_nubc=obj.m_nubc;
|
|
m_nulc=obj.m_nulc;
|
|
m_nunlc=obj.m_nunlc;
|
|
m_s=obj.m_s;
|
|
m_scaledbndl=obj.m_scaledbndl;
|
|
m_scaledbndu=obj.m_scaledbndu;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_x=obj.m_x;
|
|
m_xbase=obj.m_xbase;
|
|
m_xc=obj.m_xc;
|
|
m_xk1=obj.m_xk1;
|
|
m_xk=obj.m_xk;
|
|
m_xstart=obj.m_xstart;
|
|
m_sqpsolverstate=obj.m_sqpsolverstate;
|
|
m_slpsolverstate=obj.m_slpsolverstate;
|
|
m_auloptimizer=obj.m_auloptimizer;
|
|
m_aulreport=obj.m_aulreport;
|
|
m_bufw=obj.m_bufw;
|
|
m_bufz=obj.m_bufz;
|
|
m_cleic=obj.m_cleic;
|
|
m_j=obj.m_j;
|
|
m_scaledcleic=obj.m_scaledcleic;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| These fields store optimization report: |
|
|
//| * iterationscount total number of inner iterations |
|
|
//| * nfev number of gradient evaluations |
|
|
//| * terminationtype termination type(see below) |
|
|
//| Scaled constraint violations are reported: |
|
|
//| * bcerr maximum violation of the box constraints |
|
|
//| * bcidx index of the most violated box constraint|
|
|
//| (or -1, if all box constraints are |
|
|
//| satisfied or there is no box constraint) |
|
|
//| * lcerr maximum violation of the linear |
|
|
//| constraints, computed as maximum scaled |
|
|
//| distance between final point and |
|
|
//| constraint boundary. |
|
|
//| * lcidx index of the most violated linear |
|
|
//| constraint (or -1, if all constraints are |
|
|
//| satisfied or there is no general linear |
|
|
//| constraints) |
|
|
//| * nlcerr maximum violation of the nonlinear |
|
|
//| constraints |
|
|
//| * nlcidx index of the most violated nonlinear |
|
|
//| constraint (or -1, if all constraints are |
|
|
//| satisfied or there is no nonlinear |
|
|
//| constraints) |
|
|
//| Violations of box constraints are scaled on per-component basis |
|
|
//| according to the scale vector s[] as specified by |
|
|
//| MinNLCSetScale(). Violations of the general linear constraints |
|
|
//| are also computed using user - supplied variable scaling. |
|
|
//| Violations of nonlinear constraints are computed "as is" |
|
|
//| TERMINATION CODES |
|
|
//| TerminationType field contains completion code, which can be |
|
|
//| either: |
|
|
//| === FAILURE CODE === |
|
|
//| -8 internal integrity control detected infinite or NAN |
|
|
//| values in function / gradient. Abnormal termination |
|
|
//| signaled. |
|
|
//| -3 box constraints are infeasible. |
|
|
//| Note: infeasibility of non-box constraints does NOT trigger |
|
|
//| emergency completion; you have to examine bcerr/lcerr/ |
|
|
//| nlcerr to detect possibly inconsistent constraints. |
|
|
//| === SUCCESS CODE === |
|
|
//| 2 relative step is no more than EpsX. |
|
|
//| 5 MaxIts steps was taken |
|
|
//| 7 stopping conditions are too stringent, further |
|
|
//| improvement is impossible, X contains best point |
|
|
//| found so far. |
|
|
//| 8 user requested algorithm termination via |
|
|
//| MinNLCRequestTermination(), last accepted point is |
|
|
//| returned |
|
|
//| Other fields of this structure are not documented and should not |
|
|
//| be used! |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinNLCReport
|
|
{
|
|
int m_bcidx;
|
|
int m_dbgphase0its;
|
|
int m_iterationscount;
|
|
int m_lcidx;
|
|
int m_nfev;
|
|
int m_nlcidx;
|
|
int m_terminationtype;
|
|
double m_bcerr;
|
|
double m_lcerr;
|
|
double m_nlcerr;
|
|
//--- constructor / destructor
|
|
CMinNLCReport(void) { ZeroMemory(this); }
|
|
~CMinNLCReport(void) {}
|
|
//---
|
|
void Copy(const CMinNLCReport &obj);
|
|
//--- overloading
|
|
void operator=(const CMinNLCReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLCReport::Copy(const CMinNLCReport &obj)
|
|
{
|
|
m_bcidx=obj.m_bcidx;
|
|
m_dbgphase0its=obj.m_dbgphase0its;
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_lcidx=obj.m_lcidx;
|
|
m_nfev=obj.m_nfev;
|
|
m_nlcidx=obj.m_nlcidx;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_bcerr=obj.m_bcerr;
|
|
m_lcerr=obj.m_lcerr;
|
|
m_nlcerr=obj.m_nlcerr;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CMinNLC
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const int m_lbfgsfactor;
|
|
static const double m_aulmaxgrowth;
|
|
static const double m_maxlagmult;
|
|
static const double m_hessesttol;
|
|
static const double m_initgamma;
|
|
static const double m_regprec;
|
|
|
|
static void MinNLCCreate(int n,CRowDouble &x,CMinNLCState &State);
|
|
static void MinNLCCreateF(int n,CRowDouble &x,double diffstep,CMinNLCState &State);
|
|
static void MinNLCSetBC(CMinNLCState &State,CRowDouble &bndl,CRowDouble &bndu);
|
|
static void MinNLCSetLC(CMinNLCState &State,CMatrixDouble &c,CRowInt &ct,int k);
|
|
static void MinNLCSetNLC(CMinNLCState &State,int nlec,int nlic);
|
|
static void MinNLCSetCond(CMinNLCState &State,double epsx,int m_maxits);
|
|
static void MinNLCSetScale(CMinNLCState &State,CRowDouble &s);
|
|
static void MinNLCSetPrecInexact(CMinNLCState &State);
|
|
static void MinNLCSetPrecExactLowRank(CMinNLCState &State,int updatefreq);
|
|
static void MinNLCSetPrecExactRobust(CMinNLCState &State,int updatefreq);
|
|
static void MinNLCSetPrecNone(CMinNLCState &State);
|
|
static void MinNLCSetSTPMax(CMinNLCState &State,double stpmax);
|
|
static void MinNLCSetAlgoAUL(CMinNLCState &State,double rho,int itscnt);
|
|
static void MinNLCSetAlgoSLP(CMinNLCState &State);
|
|
static void MinNLCSetAlgoSQP(CMinNLCState &State);
|
|
static void MinNLCSetXRep(CMinNLCState &State,bool needxrep);
|
|
static bool MinNLCIteration(CMinNLCState &State);
|
|
static void MinNLCOptGuardGradient(CMinNLCState &State,double teststep);
|
|
static void MinNLCOptGuardSmoothness(CMinNLCState &State,int level);
|
|
static void MinNLCOptGuardResults(CMinNLCState &State,COptGuardReport &rep);
|
|
static void MinNLCOptGuardNonC1Test0Results(CMinNLCState &State,COptGuardNonC1Test0Report &strrep,COptGuardNonC1Test0Report &lngrep);
|
|
static void MinNLCOptGuardNonC1Test1Results(CMinNLCState &State,COptGuardNonC1Test1Report &strrep,COptGuardNonC1Test1Report &lngrep);
|
|
static void MinNLCResults(CMinNLCState &State,CRowDouble &x,CMinNLCReport &rep);
|
|
static void MinNLCResultsBuf(CMinNLCState &State,CRowDouble &x,CMinNLCReport &rep);
|
|
static void MinNLCRequestTermination(CMinNLCState &State);
|
|
static void MinNLCRestartFrom(CMinNLCState &State,CRowDouble &x);
|
|
static void MinNLCEqualityPenaltyFunction(double alpha,double &f,double &df,double &d2f);
|
|
static void MinNLCInequalityPenaltyFunction(double alpha,double stabilizingpoint,double &f,double &df,double &d2f);
|
|
static void MinNLCInequalityShiftFunction(double alpha,double &f,double &df,double &d2f);
|
|
|
|
private:
|
|
static void ClearRequestFields(CMinNLCState &State);
|
|
static void MinNLCInitInternal(int n,CRowDouble &x,double diffstep,CMinNLCState &State);
|
|
static void ClearPreconditioner(CMinLBFGSState &auloptimizer);
|
|
static void UpdatePreconditioner(int prectype,int updatefreq,int &preccounter,CMinLBFGSState &auloptimizer,CRowDouble &x,double rho,double gammak,CRowDouble &bndl,bool &HasBndL[],CRowDouble &bndu,bool &HasBndU[],CRowDouble &nubc,CMatrixDouble &cleic,
|
|
CRowDouble &nulc,CRowDouble &fi,CMatrixDouble &jac,CRowDouble &nunlc,CRowDouble &bufd,CRowDouble &bufc,CMatrixDouble &bufw,CMatrixDouble &bufz,CRowDouble &tmp0,int n,int nec,int nic,int ng,int nh);
|
|
static void PenaltyBC(CRowDouble &x,CRowDouble &bndl,bool &HasBndL[],CRowDouble &bndu,bool &HasBndU[],CRowDouble &nubc,int n,double rho,double stabilizingpoint,double &f,CRowDouble &g);
|
|
static void PenaltyLC(CRowDouble &x,CMatrixDouble &cleic,CRowDouble &nulc,int n,int nec,int nic,double rho,double stabilizingpoint,double &f,CRowDouble &g);
|
|
static void PenaltyNLC(CRowDouble &fi,CMatrixDouble &j,CRowDouble &nunlc,int n,int ng,int nh,double rho,double stabilizingpoint,double &f,CRowDouble &g);
|
|
static bool AULIteration(CMinNLCState &State,CSmoothnessMonitor &smonitor);
|
|
static void UnScale(CMinNLCState &State,CRowDouble &xs,CRowDouble &scaledbndl,CRowDouble &scaledbndu,CRowDouble &xu);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CMinNLC::m_aulmaxgrowth=10.0;
|
|
const double CMinNLC::m_maxlagmult=1.0E7;
|
|
const int CMinNLC::m_lbfgsfactor=10;
|
|
const double CMinNLC::m_hessesttol=1.0E-6;
|
|
const double CMinNLC::m_initgamma=1.0E-6;
|
|
const double CMinNLC::m_regprec=1.0E-6;
|
|
//+------------------------------------------------------------------+
|
|
//| NONLINEARLY CONSTRAINED OPTIMIZATION WITH PRECONDITIONED |
|
|
//| AUGMENTED LAGRANGIAN ALGORITHM |
|
|
//| DESCRIPTION: |
|
|
//| The subroutine minimizes function F(x) of N arguments subject to |
|
|
//| any combination of: |
|
|
//| * bound constraints |
|
|
//| * linear inequality constraints |
|
|
//| * linear equality constraints |
|
|
//| * nonlinear equality constraints Gi(x) = 0 |
|
|
//| * nonlinear inequality constraints Hi(x) <= 0 |
|
|
//| REQUIREMENTS: |
|
|
//| * user must provide function value and gradient for F(), H(), |
|
|
//| G() |
|
|
//| * starting point X0 must be feasible or not too far away from |
|
|
//| the feasible set |
|
|
//| * F(), G(), H() are continuously differentiable on the |
|
|
//| feasible set and its neighborhood |
|
|
//| * nonlinear constraints G() and H() must have non-zero |
|
|
//| gradient at G(x) = 0 and at H(x) = 0. Say, constraint like |
|
|
//| x^2 >= 1 is supported, but x^2 >= 0 is NOT supported. |
|
|
//| USAGE: |
|
|
//| Constrained optimization if far more complex than the |
|
|
//| unconstrained one. Nonlinearly constrained optimization is one |
|
|
//| of the most esoteric numerical procedures. |
|
|
//| Here we give very brief outline of the MinNLC optimizer. We |
|
|
//| strongly recommend you to study examples in the ALGLIB Reference |
|
|
//| Manual and to read ALGLIB User Guide on optimization, which is |
|
|
//| available at http://www.alglib.net/optimization/ |
|
|
//| 1. User initializes algorithm State with MinNLCCreate() call |
|
|
//| and chooses what NLC solver to use. There is some solver |
|
|
//| which is used by default, with default Settings, but you |
|
|
//| should NOT rely on default choice. It may change in future |
|
|
//| releases of ALGLIB without notice, and no one can guarantee |
|
|
//| that new solver will be able to solve your problem with |
|
|
//| default Settings. |
|
|
//| From the other side, if you choose solver explicitly, you can be |
|
|
//| pretty sure that it will work with new ALGLIB releases. |
|
|
//| In the current release following solvers can be used: |
|
|
//| * SQP solver, recommended for medium-scale problems (less than|
|
|
//| thousand of variables) with hard-to-evaluate target |
|
|
//| functions. Requires less function evaluations than other |
|
|
//| solvers but each step involves solution of QP subproblem, |
|
|
//| so running time may be higher than that of AUL (another |
|
|
//| recommended option). Activated with MinNLCSetAlgoSQP() |
|
|
//| function. |
|
|
//| * AUL solver with dense preconditioner, recommended for |
|
|
//| large-scale problems or for problems with cheap target |
|
|
//| function. Needs more function evaluations that SQP (about |
|
|
//| 5x - 10x times more), but its iterations are much |
|
|
//| cheaper that that of SQP. Activated with MinNLCSetAlgoAUL() |
|
|
//| function. |
|
|
//| * SLP solver, successive linear programming. The slowest one, |
|
|
//| requires more target function evaluations that SQP and AUL. |
|
|
//| However, it is somewhat more robust in tricky cases, so |
|
|
//| it can be used as a backup plan. Activated with |
|
|
//| MinNLCSetAlgoSLP() function. |
|
|
//| 2. [optional] user activates OptGuard integrity checker which |
|
|
//| tries to detect possible errors in the user - supplied |
|
|
//| callbacks: |
|
|
//| * discontinuity/nonsmoothness of the target/nonlinear |
|
|
//| constraints |
|
|
//| * errors in the analytic gradient provided by user. |
|
|
//| This feature is essential for early prototyping stages because it|
|
|
//| helps to catch common coding and problem statement errors. |
|
|
//| OptGuard can be activated with following functions (one per each |
|
|
//| check performed): |
|
|
//| * MinNLCOptGuardSmoothness() |
|
|
//| * MinNLCOptGuardGradient() |
|
|
//| 3. User adds boundary and/or linear and/or nonlinear |
|
|
//| constraints by means of calling one of the following |
|
|
//| functions: |
|
|
//| a) MinNLCSetBC() for boundary constraints |
|
|
//| b) MinNLCSetLC() for linear constraints |
|
|
//| c) MinNLCSetNLC() for nonlinear constraints |
|
|
//| You may combine(a), (b) and (c) in one optimization problem. |
|
|
//| 4. User sets scale of the variables with MinNLCSetScale() |
|
|
//| function. It is VERY important to set scale of the |
|
|
//| variables, because nonlinearly constrained problems are |
|
|
//| hard to solve when variables are badly scaled. |
|
|
//| 5. User sets stopping conditions with MinNLCSetCond(). If |
|
|
//| NLC solver uses inner/outer iteration layout, this |
|
|
//| function sets stopping conditions for INNER iterations. |
|
|
//| 6. Finally, user calls MinNLCOptimize() function which takes |
|
|
//| algorithm State and pointer (delegate, etc.) to callback |
|
|
//| function which calculates F / G / H. |
|
|
//| 7. User calls MinNLCResults() to get solution; additionally you|
|
|
//| can retrieve OptGuard report with MinNLCOptGuardResults(), |
|
|
//| and get detailed report about purported errors in the target|
|
|
//| function with: |
|
|
//| * MinNLCOptGuardNonC1Test0Results() |
|
|
//| * MinNLCOptGuardNonC1Test1Results() |
|
|
//| 8. Optionally user may call MinNLCRestartFrom() to solve |
|
|
//| another problem with same N but another starting point. |
|
|
//| MinNLCRestartFrom() allows to reuse already initialized |
|
|
//| structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N > 0 : |
|
|
//| * if given, only leading N elements of X are used |
|
|
//| * if not given, automatically determined from size |
|
|
//| of X |
|
|
//| X - starting point, array[N]: |
|
|
//| * it is better to set X to a feasible point |
|
|
//| * but X can be infeasible, in which case algorithm |
|
|
//| will try to find feasible point first, using X as|
|
|
//| initial approximation. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCCreate(int n,CRowDouble &x,CMinNLCState &State)
|
|
{
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
MinNLCInitInternal(n,x,0.0,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine is a finite difference variant of MinNLCCreate().|
|
|
//| It uses finite differences in order to differentiate target |
|
|
//| function. |
|
|
//| Description below contains information which is specific to this |
|
|
//| function only. We recommend to read comments on MinNLCCreate() |
|
|
//| in order to get more information about creation of NLC optimizer.|
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N > 0: |
|
|
//| * if given, only leading N elements of X are used |
|
|
//| * if not given, automatically determined from size |
|
|
//| of X |
|
|
//| X - starting point, array[N]: |
|
|
//| * it is better to set X to a feasible point |
|
|
//| * but X can be infeasible, in which case algorithm |
|
|
//| will try to find feasible point first, using X as|
|
|
//| initial approximation. |
|
|
//| DiffStep - differentiation step, > 0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| NOTES: |
|
|
//| 1. algorithm uses 4-point central formula for differentiation. |
|
|
//| 2. differentiation step along I-th axis is equal to |
|
|
//| DiffStep*S[I] where S[] is scaling vector which can be set |
|
|
//| by MinNLCSetScale() call. |
|
|
//| 3. we recommend you to use moderate values of differentiation |
|
|
//| step. Too large step will result in too large TRUNCATION |
|
|
//| errors, while too small step will result in too large |
|
|
//| NUMERICAL errors. 1.0E-4 can be good value to start from. |
|
|
//| 4. Numerical differentiation is very inefficient - one gradient|
|
|
//| calculation needs 4 * N function evaluations. This function |
|
|
//| will work for any N - either small(1...10), moderate |
|
|
//| (10...100) or large(100...). However, performance penalty |
|
|
//| will be too severe for any N's except for small ones. We |
|
|
//| should also say that code which relies on numerical |
|
|
//| differentiation is less robust and precise. Imprecise |
|
|
//| gradient may slow down convergence, especially on highly |
|
|
//| nonlinear problems. Thus we recommend to use this function |
|
|
//| for fast prototyping on small-dimensional problems only, |
|
|
//| and to implement analytical gradient as soon as possible. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCCreateF(int n,
|
|
CRowDouble &x,
|
|
double diffstep,
|
|
CMinNLCState &State)
|
|
{
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(diffstep),__FUNCTION__+": DiffStep is infinite or NaN!"))
|
|
return;
|
|
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep is non-positive!"))
|
|
return;
|
|
//--- function call
|
|
MinNLCInitInternal(n,x,diffstep,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets boundary constraints for NLC optimizer. |
|
|
//| Boundary constraints are inactive by default (after initial |
|
|
//| creation). They are preserved after algorithm restart with |
|
|
//| MinNLCRestartFrom(). |
|
|
//| You may combine boundary constraints with general linear ones -|
|
|
//| and with nonlinear ones! Boundary constraints are handled more |
|
|
//| efficiently than other types. Thus, if your problem has mixed |
|
|
//| constraints, you may explicitly specify some of them as boundary |
|
|
//| and save some time / space. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bounds, array[N]. If some (all) variables |
|
|
//| are unbounded, you may specify very small number|
|
|
//| or -INF. |
|
|
//| BndU - upper bounds, array[N]. If some (all) variables |
|
|
//| are unbounded, you may specify very large number|
|
|
//| or +INF. |
|
|
//| NOTE 1: it is possible to specify BndL.Set(i, BndU[i]. In this |
|
|
//| case I-th variable will be "frozen" at |
|
|
//| X.Set(i, BndL.Set(i, BndU[i]. |
|
|
//| NOTE 2: when you solve your problem with augmented Lagrangian |
|
|
//| solver, boundary constraints are satisfied only |
|
|
//| approximately! It is possible that algorithm will |
|
|
//| evaluate function outside of feasible area! |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetBC(CMinNLCState &State,
|
|
CRowDouble &bndl,
|
|
CRowDouble &bndu)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(bndu)>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(bndl[i]) || IsNegInf(bndl[i]),__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu[i]) || IsPosInf(bndu[i]),__FUNCTION__+": BndL contains NAN or -INF"))
|
|
return;
|
|
State.m_bndl.Set(i,bndl[i]);
|
|
State.m_HasBndL[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_bndu.Set(i,bndu[i]);
|
|
State.m_HasBndU[i]=MathIsValidNumber(bndu[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets linear constraints for MinNLC optimizer. |
|
|
//| Linear constraints are inactive by default (after initial |
|
|
//| creation). They are preserved after algorithm restart with |
|
|
//| MinNLCRestartFrom(). |
|
|
//| You may combine linear constraints with boundary ones - and with |
|
|
//| nonlinear ones! If your problem has mixed constraints, you may |
|
|
//| explicitly specify some of them as linear. It may help optimizer |
|
|
//| to handle them more efficiently. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinNLCCreate |
|
|
//| call. |
|
|
//| C - linear constraints, array[K, N + 1]. Each row of C |
|
|
//| represents one constraint, either equality or |
|
|
//| inequality (see below): |
|
|
//| * first N elements correspond to coefficients, |
|
|
//| * last element corresponds to the right part. |
|
|
//| All elements of C (including right part) must be |
|
|
//| finite. |
|
|
//| CT - type of constraints, array[K]: |
|
|
//| * if CT[i] > 0, then I-th constraint is |
|
|
//| C[i, *] * x >= C[i, n + 1] |
|
|
//| * if CT[i] = 0, then I-th constraint is |
|
|
//| C[i, *] * x = C[i, n + 1] |
|
|
//| * if CT[i] < 0, then I-th constraint is |
|
|
//| C[i, *] * x <= C[i, n + 1] |
|
|
//| K - number of equality/inequality constraints, K >= 0: |
|
|
//| * if given, only leading K elements of C/CT are |
|
|
//| used |
|
|
//| * if not given, automatically determined from sizes|
|
|
//| of C/CT |
|
|
//| NOTE 1: when you solve your problem with augmented Lagrangian |
|
|
//| solver, linear constraints are satisfied only |
|
|
//| approximately! It is possible that algorithm will |
|
|
//| evaluate function outside of feasible area! |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetLC(CMinNLCState &State,
|
|
CMatrixDouble &c,
|
|
CRowInt &ct,
|
|
int k)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int i=0;
|
|
//--- First, check for errors in the inputs
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Cols(c)>=n+1 || k==0,__FUNCTION__+": Cols(C)<N+1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(c)>=k,__FUNCTION__+": Rows(C)<K"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(ct)>=k,__FUNCTION__+": Length(CT)<K"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(c,k,n+1),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
//--- Handle zero K
|
|
if(k==0)
|
|
{
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
return;
|
|
}
|
|
//--- Equality constraints are stored first, in the upper
|
|
//--- NEC rows of State.CLEIC matrix. Inequality constraints
|
|
//--- are stored in the next NIC rows.
|
|
//--- NOTE: we convert inequality constraints to the form
|
|
//--- A*x<=b before copying them.
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_cleic,k,n+1);
|
|
CApServ::IVectorSetLengthAtLeast(State.m_lcsrcidx,k);
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
for(i=0; i<k; i++)
|
|
{
|
|
if(ct[i]==0)
|
|
{
|
|
State.m_cleic.Row(State.m_nec,c,i);
|
|
State.m_lcsrcidx.Set(State.m_nec,i);
|
|
State.m_nec=State.m_nec+1;
|
|
}
|
|
}
|
|
for(i=0; i<k; i++)
|
|
{
|
|
if(ct[i]!=0)
|
|
{
|
|
if(ct[i]>0)
|
|
State.m_cleic.Row(State.m_nec+State.m_nic,c[i]*(-1.0));
|
|
else
|
|
State.m_cleic.Row(State.m_nec+State.m_nic,c,i);
|
|
State.m_lcsrcidx.Set(State.m_nec+State.m_nic,i);
|
|
State.m_nic++;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets nonlinear constraints for MinNLC optimizer. |
|
|
//| In fact, this function sets NUMBER of nonlinear constraints. |
|
|
//| Constraints itself (constraint functions) are passed to |
|
|
//| MinNLCOptimize() method. This method requires user-defined vector|
|
|
//| function F[] and its Jacobian J[], where: |
|
|
//| * first component of F[] and first row of Jacobian J[] |
|
|
//| corresponds to function being minimized |
|
|
//| * next NLEC components of F[] (and rows of J) correspond to |
|
|
//| nonlinear equality constraints G_i(x) = 0 |
|
|
//| * next NLIC components of F[] (and rows of J) correspond to |
|
|
//| nonlinear inequality constraints H_i(x) <= 0 |
|
|
//| NOTE: you may combine nonlinear constraints with linear/boundary |
|
|
//| ones. If your problem has mixed constraints, you may |
|
|
//| explicitly specify some of them as linear ones. It may help|
|
|
//| optimizer to handle them more efficiently. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinNLCCreate|
|
|
//| call. |
|
|
//| NLEC - number of Non-Linear Equality Constraints(NLEC),|
|
|
//| >= 0 |
|
|
//| NLIC - number of Non-Linear Inquality Constraints(NLIC)|
|
|
//| >= 0 |
|
|
//| NOTE 1: when you solve your problem with augmented Lagrangian |
|
|
//| solver, nonlinear constraints are satisfied only |
|
|
//| approximately! It is possible that algorithm will |
|
|
//| evaluate function outside of feasible area! |
|
|
//| NOTE 2: algorithm scales variables according to scale specified |
|
|
//| by MinNLCSetScale() function, so it can handle problems |
|
|
//| with badly scaled variables (as long as we KNOW their |
|
|
//| scales). |
|
|
//| However, there is no way to automatically scale nonlinear |
|
|
//| constraints Gi(x) and Hi(x). Inappropriate scaling of Gi/Hi may |
|
|
//| ruin convergence. Solving problem with constraint "1000*G0(x)=0" |
|
|
//| is NOT same as solving it with constraint "0.001*G0(x)=0". |
|
|
//| It means that YOU are the one who is responsible for correct |
|
|
//| scaling of nonlinear constraints Gi(x) and Hi(x). We recommend |
|
|
//| you to scale nonlinear constraints in such way that I-th |
|
|
//| component of dG/dX (or dH/dx) has approximately unit magnitude |
|
|
//| (for problems with unit scale) or has magnitude approximately |
|
|
//| equal to 1/S[i] (where S is a scale set by MinNLCSetScale() |
|
|
//| function). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetNLC(CMinNLCState &State,int nlec,int nlic)
|
|
{
|
|
if(!CAp::Assert(nlec>=0,__FUNCTION__+": NLEC<0"))
|
|
return;
|
|
if(!CAp::Assert(nlic>=0,__FUNCTION__+": NLIC<0"))
|
|
return;
|
|
|
|
State.m_ng=nlec;
|
|
State.m_nh=nlic;
|
|
State.m_fi.Resize(1+State.m_ng+State.m_nh);
|
|
State.m_j.Resize(1+State.m_ng+State.m_nh,State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping conditions for inner iterations of |
|
|
//| optimizer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsX - >= 0, The subroutine finishes its work if on k+1-th|
|
|
//| iteration the condition |v| <= EpsX is fulfilled, |
|
|
//| where: |
|
|
//| * | . | means Euclidian norm |
|
|
//| * v - scaled step vector, v[i] = dx[i] / s[i] |
|
|
//| * dx - step vector, dx = X(k + 1) - X(k) |
|
|
//| * s - scaling coefficients set by MinNLCSetScale() |
|
|
//| MaxIts - maximum number of iterations. If MaxIts = 0, the |
|
|
//| number of iterations is unlimited. |
|
|
//| Passing EpsX = 0 and MaxIts = 0(simultaneously) will lead to |
|
|
//| automatic selection of the stopping condition. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetCond(CMinNLCState &State,
|
|
double epsx,
|
|
int m_maxits)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(epsx),__FUNCTION__+": EpsX is not finite number"))
|
|
return;
|
|
if(!CAp::Assert((double)(epsx)>=0.0,__FUNCTION__+": negative EpsX"))
|
|
return;
|
|
if(!CAp::Assert(m_maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
|
|
if(epsx==0.0 && m_maxits==0)
|
|
epsx=1.0E-8;
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=m_maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients for NLC optimizer. |
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions (step size and gradient are scaled before comparison |
|
|
//| with tolerances). Scale of the I-th variable is a translation |
|
|
//| invariant measure of: |
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| Scaling is also used by finite difference variant of the |
|
|
//| optimizer - step along I-th axis is equal to DiffStep*S[I]. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients S[i] may |
|
|
//| be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetScale(CMinNLCState &State,CRowDouble &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(s)>=State.m_n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
}
|
|
State.m_s=s.Abs()+0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets preconditioner to "inexact LBFGS-based" mode. |
|
|
//| Preconditioning is very important for convergence of Augmented |
|
|
//| Lagrangian algorithm because presence of penalty term makes |
|
|
//| problem ill-conditioned. Difference between performance of |
|
|
//| preconditioned and unpreconditioned methods can be as large |
|
|
//| as 100x! |
|
|
//| MinNLC optimizer may use following preconditioners, each with its|
|
|
//| own benefits and drawbacks: |
|
|
//| a) inexact LBFGS-based, with O(N * K) evaluation time |
|
|
//| b) exact low rank one, with O(N * K ^ 2) evaluation time |
|
|
//| c) exact robust one, with O(N ^ 3 + K * N ^ 2) evaluation |
|
|
//| time where K is a total number of general linear and |
|
|
//| nonlinear constraints (box ones are not counted). |
|
|
//| Inexact LBFGS-based preconditioner uses L-BFGS formula combined |
|
|
//| with orthogonality assumption to perform very fast updates. For a|
|
|
//| N-dimensional problem with K general linear or nonlinear |
|
|
//| constraints (boundary ones are not counted) it has O(N * K) cost|
|
|
//| per iteration. This preconditioner has best quality (less |
|
|
//| iterations) when general linear and nonlinear constraints |
|
|
//| are orthogonal to each other (orthogonality with respect to |
|
|
//| boundary constraints is not required). Number of iterations |
|
|
//| increases when constraints are non - orthogonal, because |
|
|
//| algorithm assumes orthogonality, but still it is better than no |
|
|
//| preconditioner at all. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetPrecInexact(CMinNLCState &State)
|
|
{
|
|
State.m_updatefreq=0;
|
|
State.m_prectype=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets preconditioner to "exact low rank" mode. |
|
|
//| Preconditioning is very important for convergence of Augmented |
|
|
//| Lagrangian algorithm because presence of penalty term makes |
|
|
//| problem ill-conditioned. Difference between performance of |
|
|
//| preconditioned and unpreconditioned methods can be as large |
|
|
//| as 100x! |
|
|
//| MinNLC optimizer may use following preconditioners, each with its|
|
|
//| own benefits and drawbacks: |
|
|
//| a) inexact LBFGS-based, with O(N * K) evaluation time |
|
|
//| b) exact low rank one, with O(N * K ^ 2) evaluation time |
|
|
//| c) exact robust one, with O(N ^ 3 + K * N ^ 2) evaluation |
|
|
//| time where K is a total number of general linear and |
|
|
//| nonlinear constraints (box ones are not counted). |
|
|
//| It also provides special unpreconditioned mode of operation which|
|
|
//| can be used for test purposes. Comments below discuss low rank |
|
|
//| preconditioner. |
|
|
//| Exact low-rank preconditioner uses Woodbury matrix identity to |
|
|
//| build quadratic model of the penalized function. It has following|
|
|
//| features: |
|
|
//| * no special assumptions about orthogonality of constraints |
|
|
//| * preconditioner evaluation is optimized for K << N. Its cost |
|
|
//| is O(N * K ^ 2), so it may become prohibitively slow for |
|
|
//| K >= N. |
|
|
//| * finally, stability of the process is guaranteed only for |
|
|
//| K << N. Woodbury update often fail for K >= N due to |
|
|
//| degeneracy of intermediate matrices. |
|
|
//| That's why we recommend to use "exact robust" preconditioner for |
|
|
//| such cases. |
|
|
//| RECOMMENDATIONS: |
|
|
//| We recommend to choose between "exact low rank" and "exact |
|
|
//| robust" preconditioners, with "low rank" version being chosen |
|
|
//| when you know in advance that total count of non-box constraints |
|
|
//| won't exceed N, and "robust" version being chosen when you need |
|
|
//| bulletproof solution. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| UpdateFreq - update frequency. Preconditioner is rebuilt |
|
|
//| after every UpdateFreq iterations. Recommended |
|
|
//| value : 10 or higher. Zero value means that good|
|
|
//| default value will be used. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetPrecExactLowRank(CMinNLCState &State,
|
|
int updatefreq)
|
|
{
|
|
if(!CAp::Assert(updatefreq>=0,__FUNCTION__+": UpdateFreq<0"))
|
|
return;
|
|
|
|
if(updatefreq==0)
|
|
updatefreq=10;
|
|
State.m_prectype=2;
|
|
State.m_updatefreq=updatefreq;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets preconditioner to "exact robust" mode. |
|
|
//| Preconditioning is very important for convergence of Augmented |
|
|
//| Lagrangian algorithm because presence of penalty term makes |
|
|
//| problem ill-conditioned. Difference between performance of |
|
|
//| preconditioned and unpreconditioned methods can be as large |
|
|
//| as 100x! |
|
|
//| MinNLC optimizer may use following preconditioners, each with its|
|
|
//| own benefits and drawbacks: |
|
|
//| a) inexact LBFGS-based, with O(N * K) evaluation time |
|
|
//| b) exact low rank one, with O(N * K ^ 2) evaluation time |
|
|
//| c) exact robust one, with O(N ^ 3 + K * N ^ 2) evaluation |
|
|
//| time where K is a total number of general linear and |
|
|
//| nonlinear constraints (box ones are not counted). |
|
|
//| It also provides special unpreconditioned mode of operation which|
|
|
//| can be used for test purposes. Comments below discuss robust |
|
|
//| preconditioner. |
|
|
//| Exact robust preconditioner uses Cholesky decomposition to invert|
|
|
//| approximate Hessian matrix H = D + W'*C*W (where D stands for |
|
|
//| diagonal terms of Hessian, combined result of initial scaling |
|
|
//| matrix and penalty from box constraints; W stands for general |
|
|
//| linear constraints and linearization of nonlinear ones; C stands |
|
|
//| for diagonal matrix of penalty coefficients). |
|
|
//| This preconditioner has following features: |
|
|
//| * no special assumptions about constraint structure |
|
|
//| *preconditioner is optimized for stability; unlike "exact |
|
|
//| low rank" version which fails for K >= N, this one works well|
|
|
//| for any value of K. |
|
|
//| * the only drawback is that is takes O(N ^ 3 + K * N ^ 2) time |
|
|
//| to build it. No economical Woodbury update is applied even |
|
|
//| when it makes sense, thus there are exist situations (K << N)|
|
|
//| when "exact low rank" preconditioner outperforms this one. |
|
|
//| RECOMMENDATIONS: |
|
|
//| We recommend to choose between "exact low rank" and "exact |
|
|
//| robust" preconditioners, with "low rank" version being chosen |
|
|
//| when you know in advance that total count of non-box constraints |
|
|
//| won't exceed N, and "robust" version being chosen when you need |
|
|
//| bulletproof solution. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| UpdateFreq - update frequency. Preconditioner is rebuilt |
|
|
//| after every UpdateFreq iterations. Recommended |
|
|
//| value: 10 or higher. Zero value means that good |
|
|
//| default value will be used. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetPrecExactRobust(CMinNLCState &State,
|
|
int updatefreq)
|
|
{
|
|
if(!CAp::Assert(updatefreq>=0,__FUNCTION__+": UpdateFreq<0"))
|
|
return;
|
|
|
|
if(updatefreq==0)
|
|
updatefreq=10;
|
|
State.m_prectype=3;
|
|
State.m_updatefreq=updatefreq;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets preconditioner to "turned off" mode. |
|
|
//| Preconditioning is very important for convergence of Augmented |
|
|
//| Lagrangian algorithm because presence of penalty term makes |
|
|
//| problem ill-conditioned. Difference between performance of |
|
|
//| preconditioned and unpreconditioned methods can be as large |
|
|
//| as 100x! |
|
|
//| MinNLC optimizer may utilize two preconditioners, each with its |
|
|
//| own benefits and drawbacks: |
|
|
//| a) inexact LBFGS-based, and b) exact low rank one. |
|
|
//| It also provides special unpreconditioned mode of operation which|
|
|
//| can be used for test purposes. |
|
|
//| This function activates this test mode. Do not use it in |
|
|
//| production code to solve real-life problems. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetPrecNone(CMinNLCState &State)
|
|
{
|
|
State.m_updatefreq=0;
|
|
State.m_prectype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets maximum step length (after scaling of step |
|
|
//| vector with respect to variable scales specified by |
|
|
//| MinNLCSetScale() call). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| StpMax - maximum step length, >= 0. Set StpMax to 0.0 |
|
|
//| (default), if you don't want to limit step |
|
|
//| length. |
|
|
//| Use this subroutine when you optimize target function which |
|
|
//| contains exp() or other fast growing functions, and optimization |
|
|
//| algorithm makes too large steps which leads to overflow. This |
|
|
//| function allows us to reject steps that are too large (and |
|
|
//| therefore expose us to the possible overflow) without actually |
|
|
//| calculating function value at the x + stp*d. |
|
|
//| NOTE: different solvers employed by MinNLC optimizer use |
|
|
//| different norms for step; AUL solver uses 2-norm, whilst |
|
|
//| SLP solver uses INF-norm. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetSTPMax(CMinNLCState &State,double stpmax)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(stpmax),__FUNCTION__+": StpMax is not finite!"))
|
|
return;
|
|
if(!CAp::Assert((double)(stpmax)>=0.0,__FUNCTION__+": StpMax<0!"))
|
|
return;
|
|
|
|
State.m_stpmax=stpmax;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells MinNLC unit to use Augmented Lagrangian |
|
|
//| algorithm for nonlinearly constrained optimization. This |
|
|
//| algorithm is a slight modification of one described in |
|
|
//| "A Modified Barrier-Augmented Lagrangian Method for Constrained |
|
|
//| Minimization(1999)" by D.GOLDFARB, R.POLYAK, K. SCHEINBERG, |
|
|
//| I.YUZEFOVICH. |
|
|
//| AUL solver can be significantly faster than SQP on easy problems |
|
|
//| due to cheaper iterations, although it needs more function |
|
|
//| evaluations. |
|
|
//| Augmented Lagrangian algorithm works by converting problem of |
|
|
//| minimizing F(x) subject to equality/inequality constraints to |
|
|
//| unconstrained problem of the form |
|
|
//| min[ f(x) + |
|
|
//| + Rho * PENALTY_EQ(x) + SHIFT_EQ(x, Nu1) + |
|
|
//| + Rho * PENALTY_INEQ(x) + SHIFT_INEQ(x, Nu2) ] |
|
|
//| where: |
|
|
//| * Rho is a fixed penalization coefficient |
|
|
//| * PENALTY_EQ(x) is a penalty term, which is used to |
|
|
//| APPROXIMATELY enforce equality constraints |
|
|
//| *SHIFT_EQ(x) is a special "shift" term which is used to |
|
|
//| "fine-tune" equality constraints, greatly increasing |
|
|
//| precision |
|
|
//| * PENALTY_INEQ(x) is a penalty term which is used to |
|
|
//| approximately enforce inequality constraints |
|
|
//| *SHIFT_INEQ(x) is a special "shift" term which is used to |
|
|
//| "fine-tune" inequality constraints, greatly increasing |
|
|
//| precision |
|
|
//| * Nu1/Nu2 are vectors of Lagrange coefficients which are fine- |
|
|
//| tuned during outer iterations of algorithm |
|
|
//| This version of AUL algorithm uses preconditioner, which |
|
|
//| greatly accelerates convergence. Because this algorithm is |
|
|
//| similar to penalty methods, it may perform steps into infeasible |
|
|
//| area. All kinds of constraints (boundary, linear and nonlinear |
|
|
//| ones) may be violated in intermediate points - and in the |
|
|
//| solution. However, properly configured AUL method is |
|
|
//| significantly better at handling constraints than barrier and/or |
|
|
//| penalty methods. |
|
|
//| The very basic outline of algorithm is given below: |
|
|
//| 1) first outer iteration is performed with "default" values of |
|
|
//| Lagrange multipliers Nu1/Nu2. Solution quality is low |
|
|
//| (candidate point can be too far away from true solution; |
|
|
//| large violation of constraints is possible) and is |
|
|
//| comparable with that of penalty methods. |
|
|
//| 2) subsequent outer iterations refine Lagrange multipliers and |
|
|
//| improve quality of the solution. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| Rho - penalty coefficient, Rho > 0: |
|
|
//| * large enough that algorithm converges with |
|
|
//| desired precision. Minimum value is |
|
|
//| 10 * max(S'*diag(H)*S), where S is a scale matrix|
|
|
//| (set by MinNLCSetScale) and H is a Hessian of the|
|
|
//| function being minimized. If you can not easily |
|
|
//| estimate Hessian norm, see our recommendations |
|
|
//| below. |
|
|
//| * not TOO large to prevent ill - conditioning |
|
|
//| * for unit - scale problems(variables and Hessian |
|
|
//| have unit magnitude), Rho = 100 or Rho = 1000 can|
|
|
//| be used. |
|
|
//| * it is important to note that Rho is internally |
|
|
//| multiplied by scaling matrix, i.e. optimum value |
|
|
//| of Rho depends on scale of variables specified |
|
|
//| by MinNLCSetScale(). |
|
|
//| ItsCnt - number of outer iterations: |
|
|
//| * ItsCnt = 0 means that small number of outer |
|
|
//| iterations is automatically chosen (10 iterations|
|
|
//| in current version). |
|
|
//| * ItsCnt = 1 means that AUL algorithm performs just|
|
|
//| as usual barrier method. |
|
|
//| * ItsCnt > 1 means that AUL algorithm performs |
|
|
//| specified number of outer iterations |
|
|
//| HOW TO CHOOSE PARAMETERS |
|
|
//| Nonlinear optimization is a tricky area and Augmented Lagrangian |
|
|
//| algorithm is sometimes hard to tune. Good values of Rho and |
|
|
//| ItsCnt are problem - specific. In order to help you we prepared |
|
|
//| following set of recommendations: |
|
|
//| * for unit-scale problems (variables and Hessian have unit |
|
|
//| magnitude), Rho = 100 or Rho = 1000 can be used. |
|
|
//| * start from some small value of Rho and solve problem with |
|
|
//| just one outer iteration (ItcCnt = 1). In this case algorithm|
|
|
//| behaves like penalty method. Increase Rho in 2x or 10x steps |
|
|
//| until you see that one outer iteration returns point which is|
|
|
//| "rough approximation to solution". |
|
|
//| It is very important to have Rho so large that penalty term |
|
|
//| becomes constraining i.e. modified function becomes highly convex|
|
|
//| in constrained directions. |
|
|
//| From the other side, too large Rho may prevent you from |
|
|
//| converging to the solution. You can diagnose it by studying |
|
|
//| number of inner iterations performed by algorithm: too few (5-10 |
|
|
//| on 1000-dimensional problem) or too many (orders of magnitude |
|
|
//| more than dimensionality) usually means that Rho is too large. |
|
|
//| * with just one outer iteration you usually have low-quality |
|
|
//| solution. Some constraints can be violated with very large |
|
|
//| margin, while other ones (which are NOT violated in the true |
|
|
//| solution) can push final point too far in the inner area of |
|
|
//| the feasible set. |
|
|
//| For example, if you have constraint x0 >= 0 and true solution |
|
|
//| x0=1, then merely a presence of "x0>=0" will introduce a bias |
|
|
//| towards larger values of x0. Say, algorithm may stop at x0 = 1.5 |
|
|
//| instead of 1.0. |
|
|
//| * after you found good Rho, you may increase number of outer |
|
|
//| iterations. ItsCnt = 10 is a good value. Subsequent outer |
|
|
//| iteration will refine values of Lagrange multipliers. |
|
|
//| Constraints which were violated will be enforced, inactive |
|
|
//| constraints will be dropped(corresponding multipliers will be|
|
|
//| decreased). Ideally, you should see 10-1000x improvement in |
|
|
//| constraint handling(constraint violation is reduced). |
|
|
//| * if you see that algorithm converges to vicinity of solution, |
|
|
//| but additional outer iterations do not refine solution, it |
|
|
//| may mean that algorithm is unstable - it wanders around true |
|
|
//| solution, but can not approach it. Sometimes algorithm may be|
|
|
//| stabilized by increasing Rho one more time, making it 5x or |
|
|
//| 10x larger. |
|
|
//| SCALING OF CONSTRAINTS [IMPORTANT] |
|
|
//| AUL optimizer scales variables according to scale specified by |
|
|
//| MinNLCSetScale() function, so it can handle problems with badly |
|
|
//| scaled variables (as long as we KNOW their scales). However, |
|
|
//| because function being optimized is a mix of original function |
|
|
//| and constraint - dependent penalty functions, it is important to |
|
|
//| rescale both variables AND constraints. |
|
|
//| Say, if you minimize f(x) = x^2 subject to 1000000*x >= 0, then |
|
|
//| you have constraint whose scale is different from that of target |
|
|
//| function (another example is 0.000001*x >= 0). It is also |
|
|
//| possible to have constraints whose scales are misaligned: |
|
|
//| 1000000*x0 >= 0, 0.000001*x1 <= 0. Inappropriate scaling may ruin|
|
|
//| convergence because minimizing x^2 subject to x >= 0 is NOT same |
|
|
//| as minimizing it subject to 1000000*x >= 0. |
|
|
//| Because we know coefficients of boundary/linear constraints, we |
|
|
//| can automatically rescale and normalize them. However, there is |
|
|
//| no way to automatically rescale nonlinear constraints Gi(x) and |
|
|
//| Hi(x) - they are black boxes. |
|
|
//| It means that YOU are the one who is responsible for correct |
|
|
//| scaling of nonlinear constraints Gi(x) and Hi(x). We recommend |
|
|
//| you to rescale nonlinear constraints in such way that I-th |
|
|
//| component of dG/dX (or dH/dx) has magnitude approximately equal |
|
|
//| to 1/S[i] (where S is a scale set by MinNLCSetScale() function). |
|
|
//| WHAT IF IT DOES NOT CONVERGE? |
|
|
//| It is possible that AUL algorithm fails to converge to precise |
|
|
//| values of Lagrange multipliers. It stops somewhere around true |
|
|
//| solution, but candidate point is still too far from solution, |
|
|
//| and some constraints are violated. Such kind of failure is |
|
|
//| specific for Lagrangian algorithms - technically, they stop at |
|
|
//| some point, but this point is not constrained solution. |
|
|
//| There are exist several reasons why algorithm may fail to |
|
|
//| converge: |
|
|
//| a) too loose stopping criteria for inner iteration |
|
|
//| b) degenerate, redundant constraints |
|
|
//| c) target function has unconstrained extremum exactly at the |
|
|
//| boundary of some constraint |
|
|
//| d) numerical noise in the target function |
|
|
//| In all these cases algorithm is unstable - each outer iteration |
|
|
//| results in large and almost random step which improves handling |
|
|
//| of some constraints, but violates other ones (ideally outer |
|
|
//| iterations should form a sequence of progressively decreasing |
|
|
//| steps towards solution). |
|
|
//| First reason possible is that too loose stopping criteria for |
|
|
//| inner iteration were specified. Augmented Lagrangian algorithm |
|
|
//| solves a sequence of intermediate problems, and requries each of |
|
|
//| them to be solved with high precision. Insufficient precision |
|
|
//| results in incorrect update of Lagrange multipliers. |
|
|
//| Another reason is that you may have specified degenerate |
|
|
//| constraints: say, some constraint was repeated twice. In most |
|
|
//| cases AUL algorithm gracefully handles such situations, but |
|
|
//| sometimes it may spend too much time figuring out subtle |
|
|
//| degeneracies in constraint matrix. |
|
|
//| Third reason is tricky and hard to diagnose. Consider situation |
|
|
//| when you minimize f = x^2 subject to constraint x >= 0. |
|
|
//| Unconstrained extremum is located exactly at the boundary of |
|
|
//| constrained area. In this case algorithm will tend to oscillate |
|
|
//| between negative and positive x. Each time it stops at x<0 it |
|
|
//| "reinforces" constraint x >= 0, and each time it is bounced to |
|
|
//| x>0 it "relaxes" constraint( and is attracted to x < 0). |
|
|
//| Such situation sometimes happens in problems with hidden |
|
|
//| symetries. Algorithm is got caught in a loop with Lagrange |
|
|
//| multipliers being continuously increased / decreased. Luckily, |
|
|
//| such loop forms after at least three iterations, so this problem|
|
|
//| can be solved by DECREASING number of outer iterations down |
|
|
//| to 1-2 and increasing penalty coefficient Rho as much as possible|
|
|
//| Final reason is numerical noise. AUL algorithm is robust against |
|
|
//| moderate noise (more robust than, say, active set methods), but |
|
|
//| large noise may destabilize algorithm. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetAlgoAUL(CMinNLCState &State,double rho,int itscnt)
|
|
{
|
|
if(!CAp::Assert(itscnt>=0,__FUNCTION__+": negative ItsCnt"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(rho),__FUNCTION__+": Rho is not finite"))
|
|
return;
|
|
if(!CAp::Assert(rho>0.0,__FUNCTION__+": Rho<=0"))
|
|
return;
|
|
|
|
if(itscnt==0)
|
|
itscnt=10;
|
|
State.m_aulitscnt=itscnt;
|
|
State.m_rho=rho;
|
|
State.m_solvertype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells MinNLC optimizer to use SLP (Successive |
|
|
//| Linear Programming) algorithm for nonlinearly constrained |
|
|
//| optimization. This algorithm is a slight modification of one |
|
|
//| described in "A Linear programming - based optimization algorithm|
|
|
//| for solving nonlinear programming problems" (2010) by Claus Still|
|
|
//| and Tapio Westerlund. |
|
|
//| This solver is the slowest one in ALGLIB, it requires more target|
|
|
//| function evaluations that SQP and AUL. However it is somewhat |
|
|
//| more robust in tricky cases, so it can be used as a backup plan. |
|
|
//| We recommend to use this algo when SQP/AUL do not work (does not |
|
|
//| return the solution you expect). If trying different approach |
|
|
//| gives same results, then MAYBE something is wrong with your |
|
|
//| optimization problem. |
|
|
//| Despite its name ("linear" = "first order method") this algorithm|
|
|
//| performs steps similar to that of conjugate gradients method; |
|
|
//| internally it uses orthogonality/conjugacy requirement for |
|
|
//| subsequent steps which makes it closer to second order methods in|
|
|
//| terms of convergence speed. |
|
|
//| Convergence is proved for the following case: |
|
|
//| * function and constraints are continuously differentiable (C1|
|
|
//| class) |
|
|
//| * extended Mangasarian-Fromovitz constraint qualification |
|
|
//| (EMFCQ) holds; in the context of this algorithm EMFCQ means |
|
|
//| that one can, for any infeasible point, find a search |
|
|
//| direction such that the constraint infeasibilities are |
|
|
//| reduced. |
|
|
//| This algorithm has following nice properties: |
|
|
//| * no parameters to tune |
|
|
//| * no convexity requirements for target function or constraints|
|
|
//| * initial point can be infeasible |
|
|
//| * algorithm respects box constraints in all intermediate |
|
|
//| points (it does not even evaluate function outside of box |
|
|
//| constrained area) |
|
|
//| * once linear constraints are enforced, algorithm will not |
|
|
//| violate them |
|
|
//| * no such guarantees can be provided for nonlinear |
|
|
//| constraints, but once nonlinear constraints are enforced, |
|
|
//| algorithm will try to respect them as much as possible |
|
|
//| * numerical differentiation does not violate box constraints |
|
|
//| (although general linear and nonlinear ones can be violated |
|
|
//| during differentiation) |
|
|
//| * from our experience, this algorithm is somewhat more robust |
|
|
//| in really difficult cases |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| ===== TRACING SLP SOLVER ======================================= |
|
|
//| SLP solver supports advanced tracing capabilities. You can trace |
|
|
//| algorithm output by specifying following trace symbols (case- |
|
|
//| insensitive) by means of trace_file() call: |
|
|
//| * 'SLP' - for basic trace of algorithm steps and decisions. |
|
|
//| Only short scalars(function values and deltas) are |
|
|
//| printed. |
|
|
//| N-dimensional quantities like search directions are|
|
|
//| NOT printed. |
|
|
//| It also prints OptGuard integrity checker report |
|
|
//| when nonsmoothness of target / constraints is |
|
|
//| suspected. |
|
|
//| * 'SLP.DETAILED' - for output of points being visited and |
|
|
//| search directions. |
|
|
//| This symbol also implicitly defines 'SLP'. You can |
|
|
//| control output format by additionally specifying: |
|
|
//| * nothing to output in 6-digit exponential format |
|
|
//| * 'PREC.E15' to output in 15-digit exponential |
|
|
//| format |
|
|
//| * 'PREC.F6' to output in 6-digit fixed-point format|
|
|
//| * 'SLP.PROBING' - to let algorithm insert additional function |
|
|
//| evaluations before line search in order to build |
|
|
//| human-readable chart of the raw Lagrangian |
|
|
//| (~40 additional function evaluations is performed |
|
|
//| for each line search). This symbol also implicitly |
|
|
//| defines 'SLP'. Definition of this symbol also |
|
|
//| automatically activates OptGuard smoothness monitor|
|
|
//| * 'OPTGUARD' - for report of smoothness/continuity violations |
|
|
//| in target and/or constraints. This kind of |
|
|
//| reporting is included in 'SLP', but it comes with |
|
|
//| lots of additional Info. If you need just |
|
|
//| smoothness monitoring, specify this setting. |
|
|
//| NOTE: this tag merely directs OptGuard output to log file. Even |
|
|
//| if you specify it, you still have to configure OptGuard by |
|
|
//| calling MinNLCOptGuard...() family of functions. |
|
|
//| By default trace is disabled and adds no overhead to the |
|
|
//| optimization process. However, specifying any of the symbols adds|
|
|
//| some formatting and output - related overhead. Specifying |
|
|
//| 'SLP.PROBING' adds even larger overhead due to additional |
|
|
//| function evaluations being performed. |
|
|
//| You may specify multiple symbols by separating them with commas: |
|
|
//| > |
|
|
//| >CAlglib::Trace_File("SLP,SLP.PROBING,PREC.F6", |
|
|
//| "path/to/trace.log") |
|
|
//| > |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetAlgoSLP(CMinNLCState &State)
|
|
{
|
|
State.m_solvertype=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells MinNLC optimizer to use SQP (Successive |
|
|
//| Quadratic Programming) algorithm for nonlinearly constrained |
|
|
//| optimization. |
|
|
//| This algorithm needs order of magnitude (5x-10x) less function |
|
|
//| evaluations than AUL solver, but has higher overhead because each|
|
|
//| iteration involves solution of quadratic programming problem. |
|
|
//| Convergence is proved for the following case: |
|
|
//| * function and constraints are continuously differentiable |
|
|
//| (C1 class) |
|
|
//| This algorithm has following nice properties: |
|
|
//| * no parameters to tune |
|
|
//| * no convexity requirements for target function or constraints|
|
|
//| * initial point can be infeasible |
|
|
//| * algorithm respects box constraints in all intermediate |
|
|
//| points (it does not even evaluate function outside of box |
|
|
//| constrained area) |
|
|
//| * once linear constraints are enforced, algorithm will not |
|
|
//| violate them |
|
|
//| * no such guarantees can be provided for nonlinear |
|
|
//| constraints, but once nonlinear constraints are enforced, |
|
|
//| algorithm will try to respect them as much as possible |
|
|
//| * numerical differentiation does not violate box constraints |
|
|
//| (although general linear and nonlinear ones can be violated |
|
|
//| during differentiation) |
|
|
//| We recommend this algorithm as a default option for medium scale |
|
|
//| problems (less than thousand of variables) or problems with |
|
|
//| target function being hard to evaluate. |
|
|
//| For large-scale problems or ones with very cheap target function|
|
|
//| AUL solver can be better option. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| ===== INTERACTION WITH OPTGUARD ================================ |
|
|
//| OptGuard integrity checker allows us to catch problems like |
|
|
//| errors in gradients and discontinuity/nonsmoothness of the |
|
|
//| target/constraints. The latter kind of problems can be detected |
|
|
//| by looking upon line searches performed during optimization and |
|
|
//| searching for signs of nonsmoothness. |
|
|
//| The problem with SQP is that it is too good for OptGuard to work-|
|
|
//| it does not perform line searches. It typically needs 1-2 |
|
|
//| function evaluations per step, and it is not enough for OptGuard |
|
|
//| to detect nonsmoothness. |
|
|
//| So, if you suspect that your problem is nonsmooth and if you want|
|
|
//| to confirm or deny it, we recommend you to either: |
|
|
//| * use AUL or SLP solvers, which can detect nonsmoothness of |
|
|
//| the problem |
|
|
//| * or, alternatively, activate 'SQP.PROBING' trace tag that |
|
|
//| will insert additional function evaluations (~40 per line |
|
|
//| step) that will help OptGuard integrity checker to study |
|
|
//| properties of your problem |
|
|
//| ===== TRACING SQP SOLVER ======================================= |
|
|
//| SQP solver supports advanced tracing capabilities. You can trace |
|
|
//| algorithm output by specifying following trace symbols (case- |
|
|
//| insensitive) by means of trace_file() call: |
|
|
//| * 'SQP' - for basic trace of algorithm steps and |
|
|
//| decisions. Only short scalars (function values |
|
|
//| and deltas) are printed. |
|
|
//| N-dimensional quantities like search directions |
|
|
//| are NOT printed. |
|
|
//| It also prints OptGuard integrity checker report|
|
|
//| when nonsmoothness of target/constraints is |
|
|
//| suspected. |
|
|
//| * 'SQP.DETAILED' - for output of points being visited and |
|
|
//| search directions. This symbol also implicitly |
|
|
//| defines 'SQP'. You can control output format by |
|
|
//| additionally specifying: |
|
|
//| * nothing to output in 6-digit exponential |
|
|
//| format |
|
|
//| * 'PREC.E15' to output in 15-digit exponential |
|
|
//| format |
|
|
//| * 'PREC.F6' to output in 6-digit fixed-point |
|
|
//| format |
|
|
//| * 'SQP.PROBING' - to let algorithm insert additional function |
|
|
//| evaluations before line search in order to build|
|
|
//| human-readable chart of the raw Lagrangian (~40 |
|
|
//| additional function evaluations is performed for|
|
|
//| each line search). This symbol also implicitly |
|
|
//| defines 'SQP' and activates OptGuard integrity |
|
|
//| checker which detects continuity and smoothness |
|
|
//| violations. An OptGuard log is printed at the |
|
|
//| end of the file. |
|
|
//| By default trace is disabled and adds no overhead to the |
|
|
//| optimization process. However, specifying any of the symbols adds|
|
|
//| some formatting and output-related overhead. Specifying |
|
|
//| 'SQP.PROBING' adds even larger overhead due to additional |
|
|
//| function evaluations being performed. |
|
|
//| You may specify multiple symbols by separating them with commas: |
|
|
//| > |
|
|
//| >CAlglib::Trace_File("SQP,SQP.PROBING,PREC.F6", |
|
|
//| "path/to/trace.log") |
|
|
//| > |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetAlgoSQP(CMinNLCState &State)
|
|
{
|
|
State.m_solvertype=2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function turns on / off reporting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NeedXRep - whether iteration reports are needed or not |
|
|
//| If NeedXRep is True, algorithm will call rep() callback function |
|
|
//| if it is provided to MinNLCOptimize(). |
|
|
//| NOTE: algorithm passes two parameters to rep() callback - |
|
|
//| current point and penalized function value at current |
|
|
//| point. Important - function value which is returned is |
|
|
//| NOT function being minimized. It is sum of the value of |
|
|
//| the function being minimized - and penalty term. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCSetXRep(CMinNLCState &State,
|
|
bool needxrep)
|
|
{
|
|
State.m_xrep=needxrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| NOTES: |
|
|
//| 1. This function has two different implementations: one which |
|
|
//| uses exact (analytical) user-supplied Jacobian, and one |
|
|
//| which uses only function vector and numerically |
|
|
//| differentiates function in order to obtain gradient. |
|
|
//| Depending on the specific function used to create optimizer |
|
|
//| object you should choose appropriate variant of MinNLCOptimize()-|
|
|
//| one which accepts function AND Jacobian or one which accepts ONLY|
|
|
//| function. |
|
|
//| Be careful to choose variant of MinNLCOptimize() which |
|
|
//| corresponds to your optimization scheme! Table below lists |
|
|
//| different combinations of callback (function/gradient) passed to |
|
|
//| MinNLCOptimize() and specific function used to create optimizer. |
|
|
//| | USER PASSED TO MinNLCOptimize() |
|
|
//| CREATED WITH | function only | function and gradient |
|
|
//| ------------------------------------------------------------ |
|
|
//| MinNLCCreateF() | works FAILS |
|
|
//| MinNLCCreate() | FAILS works |
|
|
//| Here "FAILS" denotes inappropriate combinations of optimizer |
|
|
//| creation function and MinNLCOptimize() version. Attemps to use |
|
|
//| such combination will lead to exception. Either you did not pass |
|
|
//| gradient when it WAS needed or you passed gradient when it was |
|
|
//| NOT needed. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinNLC::MinNLCIteration(CMinNLCState &State)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int i=0;
|
|
int k=0;
|
|
int n=0;
|
|
int ng=0;
|
|
int nh=0;
|
|
double vleft=0;
|
|
double vright=0;
|
|
bool b=false;
|
|
int i_=0;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
i=State.m_rstate.ia[0];
|
|
k=State.m_rstate.ia[1];
|
|
n=State.m_rstate.ia[2];
|
|
ng=State.m_rstate.ia[3];
|
|
nh=State.m_rstate.ia[4];
|
|
b=State.m_rstate.ba[0];
|
|
vleft=State.m_rstate.ra[0];
|
|
vright=State.m_rstate.ra[1];
|
|
}
|
|
else
|
|
{
|
|
i=359;
|
|
k=-58;
|
|
n=-919;
|
|
ng=-909;
|
|
nh=81;
|
|
b=true;
|
|
vleft=74;
|
|
vright=-788;
|
|
}
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
case 4:
|
|
label=4;
|
|
break;
|
|
case 5:
|
|
label=5;
|
|
break;
|
|
case 6:
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
label=7;
|
|
break;
|
|
case 8:
|
|
label=8;
|
|
break;
|
|
case 9:
|
|
label=9;
|
|
break;
|
|
case 10:
|
|
label=10;
|
|
break;
|
|
case 11:
|
|
label=11;
|
|
break;
|
|
case 12:
|
|
label=12;
|
|
break;
|
|
case 13:
|
|
label=13;
|
|
break;
|
|
case 14:
|
|
label=14;
|
|
break;
|
|
case 15:
|
|
label=15;
|
|
break;
|
|
case 16:
|
|
label=16;
|
|
break;
|
|
case 17:
|
|
label=17;
|
|
break;
|
|
case 18:
|
|
label=18;
|
|
break;
|
|
case 19:
|
|
label=19;
|
|
break;
|
|
case 20:
|
|
label=20;
|
|
break;
|
|
case 21:
|
|
label=21;
|
|
break;
|
|
case 22:
|
|
label=22;
|
|
break;
|
|
case 23:
|
|
label=23;
|
|
break;
|
|
case 24:
|
|
label=24;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
//--- Init
|
|
State.m_userterminationneeded=false;
|
|
State.m_repterminationtype=0;
|
|
State.m_repinneriterationscount=0;
|
|
State.m_repouteriterationscount=0;
|
|
State.m_repnfev=0;
|
|
State.m_repdbgphase0its=0;
|
|
State.m_repbcerr=0;
|
|
State.m_repbcidx=-1;
|
|
State.m_replcerr=0;
|
|
State.m_replcidx=-1;
|
|
State.m_repnlcerr=0;
|
|
State.m_repnlcidx=-1;
|
|
n=State.m_n;
|
|
ng=State.m_ng;
|
|
nh=State.m_nh;
|
|
ClearRequestFields(State);
|
|
if(!CAp::Assert(State.m_smoothnessguardlevel==0 || State.m_smoothnessguardlevel==1,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
b=State.m_smoothnessguardlevel>0;
|
|
b=b || (State.m_solvertype==1 && CAp::IsTraceEnabled("SLP.PROBING"));
|
|
b=b || (State.m_solvertype==2 && CAp::IsTraceEnabled("SQP.PROBING"));
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,n,1+ng+nh,b);
|
|
State.m_lastscaleused=State.m_s;
|
|
//--- Check correctness of box constraints
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i])
|
|
{
|
|
if(State.m_bndl[i]>State.m_bndu[i])
|
|
{
|
|
State.m_repterminationtype=-3;
|
|
State.m_repbcerr=State.m_bndl[i]-State.m_bndu[i];
|
|
State.m_repbcidx=i;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
}
|
|
//--- Test gradient
|
|
if(!(State.m_diffstep==0.0 && State.m_teststep>0.0))
|
|
label=25;
|
|
else
|
|
label=27;
|
|
break;
|
|
}
|
|
//---main loop
|
|
while(label>=0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 27:
|
|
if(!COptServ::SmoothnessMonitorCheckGradientATX0(State.m_smonitor,State.m_xstart,State.m_s,State.m_bndl,State.m_bndu,true,State.m_teststep))
|
|
{
|
|
label=28;
|
|
break;
|
|
}
|
|
State.m_x=State.m_smonitor.m_x;
|
|
State.m_needfij=true;
|
|
State.m_rstate.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
State.m_needfij=false;
|
|
State.m_smonitor.m_fi=State.m_fi;
|
|
State.m_smonitor.m_j=State.m_j;
|
|
label=27;
|
|
break;
|
|
case 28:
|
|
case 25:
|
|
//--- AUL solver
|
|
if(State.m_solvertype!=0)
|
|
{
|
|
label=29;
|
|
break;
|
|
}
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xbase,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fbase,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fm2,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fm1,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fp1,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fp2,1+ng+nh);
|
|
}
|
|
State.m_rstateaul.ia.Resize(9);
|
|
State.m_rstateaul.ra.Resize(8);
|
|
State.m_rstateaul.stage=-1;
|
|
case 6:
|
|
case 31:
|
|
if(!AULIteration(State,State.m_smonitor))
|
|
{
|
|
label=32;
|
|
break;
|
|
}
|
|
//--- Numerical differentiation (if needed) - intercept NeedFiJ
|
|
//--- request and replace it by sequence of NeedFi requests
|
|
if(!(State.m_diffstep!=0.0 && State.m_needfij))
|
|
{
|
|
label=33;
|
|
break;
|
|
}
|
|
State.m_needfij=false;
|
|
State.m_needfi=true;
|
|
State.m_xbase=State.m_x;
|
|
k=0;
|
|
case 35:
|
|
if(k>n-1)
|
|
{
|
|
label=37;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,- State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_fm2=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,- 0.5*State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_fm1=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,0.5*State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_fp1=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=4;
|
|
label=-1;
|
|
break;
|
|
case 4:
|
|
State.m_fp2=State.m_fi;
|
|
State.m_j.Col(k,((State.m_fp1-State.m_fm1)*8-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[k]));
|
|
k++;
|
|
label=35;
|
|
break;
|
|
case 37:
|
|
State.m_x=State.m_xbase;
|
|
State.m_rstate.stage=5;
|
|
label=-1;
|
|
break;
|
|
case 5:
|
|
//--- Restore previous values of fields and continue
|
|
State.m_needfi=false;
|
|
State.m_needfij=true;
|
|
label=31;
|
|
break;
|
|
case 33:
|
|
//--- Forward request to caller
|
|
State.m_rstate.stage=6;
|
|
label=-1;
|
|
break;
|
|
case 32:
|
|
result=false;
|
|
return(result);
|
|
case 29:
|
|
//--- SLP solver
|
|
if(State.m_solvertype!=1)
|
|
{
|
|
label=38;
|
|
break;
|
|
}
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xbase,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fbase,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fm2,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fm1,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fp1,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fp2,1+ng+nh);
|
|
}
|
|
CNLCSLP::MinSLPInitBuf(State.m_bndl,State.m_bndu,State.m_s,State.m_xstart,n,State.m_cleic,State.m_lcsrcidx,State.m_nec,State.m_nic,State.m_ng,State.m_nh,State.m_epsx,State.m_maxits,State.m_slpsolverstate);
|
|
case 40:
|
|
if(!CNLCSLP::MinSLPIteration(State.m_slpsolverstate,State.m_smonitor,State.m_userterminationneeded))
|
|
{
|
|
label=41;
|
|
break;
|
|
}
|
|
//--- Forward request to caller
|
|
if(!State.m_slpsolverstate.m_needfij)
|
|
{
|
|
label=42;
|
|
break;
|
|
}
|
|
//--- Evaluate target function/Jacobian
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=44;
|
|
break;
|
|
}
|
|
//--- Analytic Jacobian is provided
|
|
UnScale(State,State.m_slpsolverstate.m_x,State.m_slpsolverstate.m_scaledbndl,State.m_slpsolverstate.m_scaledbndu,State.m_x);
|
|
State.m_needfij=true;
|
|
State.m_rstate.stage=7;
|
|
label=-1;
|
|
break;
|
|
case 7:
|
|
State.m_needfij=false;
|
|
State.m_slpsolverstate.m_fi=State.m_fi;
|
|
for(i=0; i<=ng+nh; i++)
|
|
State.m_slpsolverstate.m_j.Row(i,State.m_s.ToVector()*State.m_j[i]);
|
|
k=n;
|
|
label=45;
|
|
break;
|
|
case 44:
|
|
//--- Numerical differentiation
|
|
State.m_needfij=false;
|
|
State.m_needfi=true;
|
|
UnScale(State,State.m_slpsolverstate.m_x,State.m_slpsolverstate.m_scaledbndl,State.m_slpsolverstate.m_scaledbndu,State.m_xbase);
|
|
k=0;
|
|
case 46:
|
|
if(k>n-1)
|
|
{
|
|
label=48;
|
|
break;
|
|
}
|
|
vleft=State.m_xbase[k]-State.m_s[k]*State.m_diffstep;
|
|
vright=State.m_xbase[k]+State.m_s[k]*State.m_diffstep;
|
|
if(!((State.m_HasBndL[k] && (double)(vleft)<(double)(State.m_bndl[k])) || (State.m_HasBndU[k] && (double)(vright)>(double)(State.m_bndu[k]))))
|
|
{
|
|
label=49;
|
|
break;
|
|
}
|
|
//--- Box constraint is violated by 4-point centered formula, use 2-point uncentered one
|
|
if(State.m_HasBndL[k] && vleft<State.m_bndl[k])
|
|
vleft=State.m_bndl[k];
|
|
if(State.m_HasBndU[k] && vright>State.m_bndu[k])
|
|
vright=State.m_bndu[k];
|
|
if(!CAp::Assert(vleft<=vright,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
if(vleft==vright)
|
|
{
|
|
//--- Fixed variable
|
|
State.m_j.Col(k,vector<double>::Zeros(ng+nh+1));
|
|
label=47;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Set(k,vleft);
|
|
State.m_rstate.stage=8;
|
|
label=-1;
|
|
break;
|
|
case 8:
|
|
State.m_fm1=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Set(k,vright);
|
|
State.m_rstate.stage=9;
|
|
label=-1;
|
|
break;
|
|
case 9:
|
|
State.m_fp1=State.m_fi;
|
|
State.m_j.Col(k,(State.m_fp1-State.m_fm1)/(vright-vleft));
|
|
label=50;
|
|
break;
|
|
case 49:
|
|
//--- 4-point centered formula does not violate box constraints
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,-State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=10;
|
|
label=-1;
|
|
break;
|
|
case 10:
|
|
State.m_fm2=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,-0.5*State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=11;
|
|
label=-1;
|
|
break;
|
|
case 11:
|
|
State.m_fm1=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,0.5*State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=12;
|
|
label=-1;
|
|
break;
|
|
case 12:
|
|
State.m_fp1=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=13;
|
|
label=-1;
|
|
break;
|
|
case 13:
|
|
State.m_fp2=State.m_fi;
|
|
State.m_j.Col(k,((State.m_fp1-State.m_fm1)*8-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[k]));
|
|
case 50:
|
|
case 47:
|
|
k++;
|
|
label=46;
|
|
break;
|
|
case 48:
|
|
State.m_x=State.m_xbase;
|
|
State.m_rstate.stage=14;
|
|
label=-1;
|
|
break;
|
|
case 14:
|
|
State.m_needfi=false;
|
|
State.m_needfij=true;
|
|
State.m_slpsolverstate.m_fi=State.m_fi;
|
|
for(i=0; i<=ng+nh; i++)
|
|
State.m_slpsolverstate.m_j.Row(i,State.m_s.ToVector()*State.m_j[i]);
|
|
k=n;
|
|
case 45:
|
|
State.m_repnfev++;
|
|
label=40;
|
|
break;
|
|
case 42:
|
|
if(!State.m_slpsolverstate.m_xupdated)
|
|
{
|
|
label=51;
|
|
break;
|
|
}
|
|
//--- Report current point
|
|
if(!State.m_xrep)
|
|
{
|
|
label=40;
|
|
break;
|
|
}
|
|
UnScale(State,State.m_slpsolverstate.m_x,State.m_slpsolverstate.m_scaledbndl,State.m_slpsolverstate.m_scaledbndu,State.m_x);
|
|
State.m_f=State.m_slpsolverstate.m_f;
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=15;
|
|
label=-1;
|
|
break;
|
|
case 15:
|
|
State.m_xupdated=false;
|
|
case 53:
|
|
label=40;
|
|
break;
|
|
case 51:
|
|
if(!CAp::Assert(State.m_slpsolverstate.m_needfij,__FUNCTION__+":SLP:request"))
|
|
return(false);
|
|
label=40;
|
|
break;
|
|
case 41:
|
|
State.m_repterminationtype=State.m_slpsolverstate.m_repterminationtype;
|
|
State.m_repouteriterationscount=State.m_slpsolverstate.m_repouteriterationscount;
|
|
State.m_repinneriterationscount=State.m_slpsolverstate.m_repinneriterationscount;
|
|
State.m_repbcerr=State.m_slpsolverstate.m_repbcerr;
|
|
State.m_repbcidx=State.m_slpsolverstate.m_repbcidx;
|
|
State.m_replcerr=State.m_slpsolverstate.m_replcerr;
|
|
State.m_replcidx=State.m_slpsolverstate.m_replcidx;
|
|
State.m_repnlcerr=State.m_slpsolverstate.m_repnlcerr;
|
|
State.m_repnlcidx=State.m_slpsolverstate.m_repnlcidx;
|
|
UnScale(State,State.m_slpsolverstate.m_stepkx,State.m_slpsolverstate.m_scaledbndl,State.m_slpsolverstate.m_scaledbndu,State.m_xc);
|
|
result=false;
|
|
return(result);
|
|
case 38:
|
|
//--- SQP solver
|
|
if(State.m_solvertype!=2)
|
|
{
|
|
label=55;
|
|
break;
|
|
}
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xbase,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fbase,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fm2,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fm1,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fp1,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fp2,1+ng+nh);
|
|
}
|
|
CNLCSQP::MinSQPInitBuf(State.m_bndl,State.m_bndu,State.m_s,State.m_xstart,n,State.m_cleic,State.m_lcsrcidx,State.m_nec,State.m_nic,State.m_ng,State.m_nh,State.m_epsx,State.m_maxits,State.m_sqpsolverstate);
|
|
case 57:
|
|
if(!CNLCSQP::MinSQPIteration(State.m_sqpsolverstate,State.m_smonitor,State.m_userterminationneeded))
|
|
{
|
|
label=58;
|
|
break;
|
|
}
|
|
//--- Forward request to caller
|
|
if(!State.m_sqpsolverstate.m_needfij)
|
|
{
|
|
label=59;
|
|
break;
|
|
}
|
|
//--- Evaluate target function/Jacobian
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=61;
|
|
break;
|
|
}
|
|
//--- Analytic Jacobian is provided
|
|
UnScale(State,State.m_sqpsolverstate.m_x,State.m_sqpsolverstate.m_scaledbndl,State.m_sqpsolverstate.m_scaledbndu,State.m_x);
|
|
State.m_needfij=true;
|
|
State.m_rstate.stage=16;
|
|
label=-1;
|
|
break;
|
|
case 16:
|
|
State.m_needfij=false;
|
|
State.m_sqpsolverstate.m_fi=State.m_fi;
|
|
for(k=0; k<n; k++)
|
|
State.m_sqpsolverstate.m_j.Col(k,State.m_j.Col(k)*State.m_s[k]);
|
|
label=62;
|
|
break;
|
|
case 61:
|
|
//--- Numerical differentiation
|
|
State.m_needfij=false;
|
|
State.m_needfi=true;
|
|
UnScale(State,State.m_sqpsolverstate.m_x,State.m_sqpsolverstate.m_scaledbndl,State.m_sqpsolverstate.m_scaledbndu,State.m_xbase);
|
|
k=0;
|
|
case 63:
|
|
if(k>n-1)
|
|
{
|
|
label=65;
|
|
break;
|
|
}
|
|
vleft=State.m_xbase[k]-State.m_s[k]*State.m_diffstep;
|
|
vright=State.m_xbase[k]+State.m_s[k]*State.m_diffstep;
|
|
if(!((State.m_HasBndL[k] && vleft<State.m_bndl[k]) || (State.m_HasBndU[k] && vright>State.m_bndu[k])))
|
|
{
|
|
label=66;
|
|
break;
|
|
}
|
|
//--- Box constraint is violated by 4-point centered formula, use 2-point uncentered one
|
|
if(State.m_HasBndL[k] && vleft<State.m_bndl[k])
|
|
vleft=State.m_bndl[k];
|
|
if(State.m_HasBndU[k] && vright>State.m_bndu[k])
|
|
vright=State.m_bndu[k];
|
|
if(!CAp::Assert(vleft<=vright,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
if(vleft==vright)
|
|
{
|
|
//--- Fixed variable
|
|
State.m_j.Col(k,vector<double>::Zeros(ng+nh+1));
|
|
label=64;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Set(k,vleft);
|
|
State.m_rstate.stage=17;
|
|
label=-1;
|
|
break;
|
|
case 17:
|
|
State.m_fm1=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Set(k,vright);
|
|
State.m_rstate.stage=18;
|
|
label=-1;
|
|
break;
|
|
case 18:
|
|
State.m_fp1=State.m_fi;
|
|
State.m_j.Col(k,(State.m_fp1-State.m_fm1)/(vright-vleft));
|
|
label=67;
|
|
break;
|
|
case 66:
|
|
//--- 4-point centered formula does not violate box constraints
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,-State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=19;
|
|
label=-1;
|
|
break;
|
|
case 19:
|
|
State.m_fm2=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,-0.5*State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=20;
|
|
label=-1;
|
|
break;
|
|
case 20:
|
|
State.m_fm1=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,0.5*State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=21;
|
|
label=-1;
|
|
break;
|
|
case 21:
|
|
State.m_fp1=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Add(k,State.m_s[k]*State.m_diffstep);
|
|
State.m_rstate.stage=22;
|
|
label=-1;
|
|
break;
|
|
case 22:
|
|
State.m_fp2=State.m_fi;
|
|
State.m_j.Col(k,((State.m_fp1-State.m_fm1)*8-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[k]));
|
|
case 67:
|
|
case 64:
|
|
k++;
|
|
label=63;
|
|
break;
|
|
case 65:
|
|
State.m_x=State.m_xbase;
|
|
State.m_rstate.stage=23;
|
|
label=-1;
|
|
break;
|
|
case 23:
|
|
State.m_needfi=false;
|
|
State.m_needfij=true;
|
|
State.m_sqpsolverstate.m_fi=State.m_fi;
|
|
for(k=0; k<n; k++)
|
|
State.m_sqpsolverstate.m_j.Col(k,State.m_j.Col(k)*State.m_s[k]);
|
|
case 62:
|
|
State.m_repnfev++;
|
|
label=57;
|
|
break;
|
|
case 59:
|
|
if(!State.m_sqpsolverstate.m_xupdated)
|
|
{
|
|
label=68;
|
|
break;
|
|
}
|
|
//--- Report current point
|
|
if(!State.m_xrep)
|
|
{
|
|
label=57;
|
|
break;
|
|
}
|
|
UnScale(State,State.m_sqpsolverstate.m_x,State.m_sqpsolverstate.m_scaledbndl,State.m_sqpsolverstate.m_scaledbndu,State.m_x);
|
|
State.m_f=State.m_sqpsolverstate.m_f;
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=24;
|
|
label=-1;
|
|
break;
|
|
case 24:
|
|
State.m_xupdated=false;
|
|
case 70:
|
|
label=57;
|
|
break;
|
|
case 68:
|
|
if(!CAp::Assert(State.m_sqpsolverstate.m_needfij,__FUNCTION__+":SQP:request"))
|
|
return(false);
|
|
label=57;
|
|
break;
|
|
case 58:
|
|
State.m_repterminationtype=State.m_sqpsolverstate.m_repterminationtype;
|
|
State.m_repouteriterationscount=State.m_sqpsolverstate.m_repiterationscount;
|
|
State.m_repinneriterationscount=State.m_sqpsolverstate.m_repiterationscount;
|
|
State.m_repbcerr=State.m_sqpsolverstate.m_repbcerr;
|
|
State.m_repbcidx=State.m_sqpsolverstate.m_repbcidx;
|
|
State.m_replcerr=State.m_sqpsolverstate.m_replcerr;
|
|
State.m_replcidx=State.m_sqpsolverstate.m_replcidx;
|
|
State.m_repnlcerr=State.m_sqpsolverstate.m_repnlcerr;
|
|
State.m_repnlcidx=State.m_sqpsolverstate.m_repnlcidx;
|
|
UnScale(State,State.m_sqpsolverstate.m_stepkx,State.m_sqpsolverstate.m_scaledbndl,State.m_sqpsolverstate.m_scaledbndu,State.m_xc);
|
|
result=false;
|
|
return(result);
|
|
case 55:
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
//--- Saving State
|
|
result=true;
|
|
State.m_rstate.ba[0]=b;
|
|
State.m_rstate.ia.Set(0,i);
|
|
State.m_rstate.ia.Set(1,k);
|
|
State.m_rstate.ia.Set(2,n);
|
|
State.m_rstate.ia.Set(3,ng);
|
|
State.m_rstate.ia.Set(4,nh);
|
|
State.m_rstate.ra.Set(0,vleft);
|
|
State.m_rstate.ra.Set(1,vright);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function activates/deactivates verification of the user- |
|
|
//| supplied analytic gradient/Jacobian. |
|
|
//| Upon activation of this option OptGuard integrity checker |
|
|
//| performs numerical differentiation of your target function |
|
|
//| (constraints) at the initial point (note: future versions may |
|
|
//| also perform check at the final point) and compares numerical |
|
|
//| gradient/Jacobian with analytic one provided by you. |
|
|
//| If difference is too large, an error flag is set and optimization|
|
|
//| session continues. After optimization session is over, you can |
|
|
//| retrieve the report which stores both gradients/Jacobians, and |
|
|
//| specific components highlighted as suspicious by the OptGuard. |
|
|
//| The primary OptGuard report can be retrieved with |
|
|
//| MinNLCOptGuardResults(). |
|
|
//| IMPORTANT: gradient check is a high-overhead option which will |
|
|
//| cost you about 3*N additional function evaluations. |
|
|
//| In many cases it may cost as much as the rest of the |
|
|
//| optimization session. |
|
|
//| YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO |
|
|
//| CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY. |
|
|
//| NOTE: unlike previous incarnation of the gradient checking code, |
|
|
//| OptGuard does NOT interrupt optimization even if it |
|
|
//| discovers bad gradient. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure used to store algorithm State |
|
|
//| TestStep - verification step used for numerical |
|
|
//| differentiation: |
|
|
//| * TestStep = 0 turns verification off |
|
|
//| * TestStep > 0 activates verification |
|
|
//| You should carefully choose TestStep. Value |
|
|
//| which is too large (so large that function |
|
|
//| behavior is non- cubic at this scale) will |
|
|
//| lead to false alarms. Too short step will |
|
|
//| result in rounding errors dominating numerical |
|
|
//| derivative. |
|
|
//| You may use different step for different |
|
|
//| parameters by means of setting scale with |
|
|
//| MinNLCSetScale(). |
|
|
//| === EXPLANATION ================================================ |
|
|
//| In order to verify gradient algorithm performs following steps: |
|
|
//| * two trial steps are made to |
|
|
//| X[i] - TestStep * S[i] and X[i] + TestStep * S[i], |
|
|
//| where X[i] is i-th component of the initial point and S[i] |
|
|
//| is a scale of i-th parameter |
|
|
//| * F(X) is evaluated at these trial points |
|
|
//| * we perform one more evaluation in the middle point of the |
|
|
//| interval |
|
|
//| * we build cubic model using function values and derivatives |
|
|
//| at trial points and we compare its prediction with actual |
|
|
//| value in the middle point |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCOptGuardGradient(CMinNLCState &State,
|
|
double teststep)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(teststep),__FUNCTION__+": TestStep contains NaN or INF"))
|
|
return;
|
|
if(!CAp::Assert(teststep>=0.0,__FUNCTION__+": invalid argument TestStep(TestStep<0)"))
|
|
return;
|
|
|
|
State.m_teststep=teststep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function activates/deactivates nonsmoothness monitoring |
|
|
//| option of the OptGuard integrity checker. Smoothness monitor |
|
|
//| silently observes solution process and tries to detect ill-posed |
|
|
//| problems, i.e. ones with: |
|
|
//| a) discontinuous target function(non-C0) and/or constraints |
|
|
//| b) nonsmooth target function(non-C1) and/or constraints |
|
|
//| Smoothness monitoring does NOT interrupt optimization even if it |
|
|
//| suspects that your problem is nonsmooth. It just sets |
|
|
//| corresponding flags in the OptGuard report which can be retrieved|
|
|
//| after optimization is over. |
|
|
//| Smoothness monitoring is a moderate overhead option which often |
|
|
//| adds less than 1% to the optimizer running time. Thus, you can |
|
|
//| use it even for large scale problems. |
|
|
//| NOTE: OptGuard does NOT guarantee that it will always detect |
|
|
//| C0/C1 continuity violations. |
|
|
//| First, minor errors are hard to catch-say, a 0.0001 difference |
|
|
//| in the model values at two sides of the gap may be due to |
|
|
//| discontinuity of the model - or simply because the model has |
|
|
//| changed. |
|
|
//| Second, C1-violations are especially difficult to detect in a |
|
|
//| noninvasive way. The optimizer usually performs very short steps |
|
|
//| near the nonsmoothness, and differentiation usually introduces |
|
|
//| a lot of numerical noise. It is hard to tell whether some tiny |
|
|
//| discontinuity in the slope is due to real nonsmoothness or just |
|
|
//| due to numerical noise alone. |
|
|
//| Our top priority was to avoid false positives, so in some rare |
|
|
//| cases minor errors may went unnoticed (however, in most cases |
|
|
//| they can be spotted with restart from different initial point). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| level - monitoring level: |
|
|
//| * 0 - monitoring is disabled |
|
|
//| * 1 - noninvasive low - overhead monitoring; |
|
|
//| function values and/or gradients are |
|
|
//| recorded, but OptGuard does not try to |
|
|
//| perform additional evaluations in order |
|
|
//| to get more information about suspicious |
|
|
//| locations. |
|
|
//| This kind of monitoring does not work well with SQP because SQP |
|
|
//| solver needs just 1-2 function evaluations per step, which is not|
|
|
//| enough for OptGuard to make any conclusions. |
|
|
//| === EXPLANATION ================================================ |
|
|
//| One major source of headache during optimization is the |
|
|
//| possibility of the coding errors in the target function/ |
|
|
//| constraints (or their gradients). Such errors most often |
|
|
//| manifest themselves as discontinuity or nonsmoothness of the |
|
|
//| target/constraints. |
|
|
//| Another frequent situation is when you try to optimize something |
|
|
//| involving lots of min() and max() operations, i.e. nonsmooth |
|
|
//| target. Although not a coding error, it is nonsmoothness anyway -|
|
|
//| and smooth optimizers usually stop right after encountering |
|
|
//| nonsmoothness, well before reaching solution. |
|
|
//| OptGuard integrity checker helps you to catch such situations: |
|
|
//| it monitors function values/gradients being passed to the |
|
|
//| optimizer and tries to errors. Upon discovering suspicious pair |
|
|
//| of points it raises appropriate flag (and allows you to continue |
|
|
//| optimization). When optimization is done, you can study OptGuard |
|
|
//| result. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCOptGuardSmoothness(CMinNLCState &State,int level)
|
|
{
|
|
if(!CAp::Assert(level==0 || level==1,__FUNCTION__+": unexpected value of level parameter"))
|
|
return;
|
|
State.m_smoothnessguardlevel=level;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Results of OptGuard integrity check, should be called after |
|
|
//| optimization session is over. |
|
|
//| === PRIMARY REPORT ============================================= |
|
|
//| OptGuard performs several checks which are intended to catch |
|
|
//| common errors in the implementation of nonlinear function/ |
|
|
//| gradient: |
|
|
//| * incorrect analytic gradient |
|
|
//| * discontinuous (non-C0) target functions (constraints) |
|
|
//| * nonsmooth (non-C1) target functions (constraints) |
|
|
//| Each of these checks is activated with appropriate function: |
|
|
//| * MinNLCOptGuardGradient() for gradient verification |
|
|
//| * MinNLCOptGuardSmoothness() for C0/C1 checks |
|
|
//| Following flags are set when these errors are suspected: |
|
|
//| * rep.badgradsuspected, and additionally: |
|
|
//| * rep.badgradfidx for specific function (Jacobian row) |
|
|
//| suspected |
|
|
//| * rep.badgradvidx for specific variable (Jacobian column)|
|
|
//| suspected |
|
|
//| * rep.badgradxbase, a point where gradient/Jacobian is |
|
|
//| tested |
|
|
//| * rep.badgraduser, user-provided gradient/Jacobian |
|
|
//| * rep.badgradnum, reference gradient/Jacobian obtained |
|
|
//| via numerical differentiation |
|
|
//| * rep.nonc0suspected, and additionally: |
|
|
//| * rep.nonc0fidx - an index of specific function violating|
|
|
//| C0 continuity |
|
|
//| * rep.nonc1suspected, and additionally |
|
|
//| * rep.nonc1fidx - an index of specific function violating|
|
|
//| C1 continuity |
|
|
//| Here function index 0 means target function, index 1 or higher |
|
|
//| denotes nonlinear constraints. |
|
|
//| === ADDITIONAL REPORTS / LOGS ================================== |
|
|
//| Several different tests are performed to catch C0/C1 errors, you |
|
|
//| can find out specific test signaled error by looking to: |
|
|
//| * rep.nonc0test0positive, for non-C0 test #0 |
|
|
//| * rep.nonc1test0positive, for non-C1 test #0 |
|
|
//| * rep.nonc1test1positive, for non-C1 test #1 |
|
|
//| Additional information (including line search logs) can be |
|
|
//| obtained by means of: |
|
|
//| * MinNLCOptGuardNonC1Test0Results() |
|
|
//| * MinNLCOptGuardNonC1Test1Results() |
|
|
//| which return detailed error reports, specific points where |
|
|
//| discontinuities were found, and so on. |
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| rep - generic OptGuard report; more detailed reports can|
|
|
//| be retrieved with other functions. |
|
|
//| NOTE: false negatives (nonsmooth problems are not identified |
|
|
//| as nonsmooth ones) are possible although unlikely. |
|
|
//| The reason is that you need to make several evaluations around |
|
|
//| nonsmoothness in order to accumulate enough information about |
|
|
//| function curvature. Say, if you start right from the nonsmooth |
|
|
//| point, optimizer simply won't get enough data to understand what |
|
|
//| is going wrong before it terminates due to abrupt changes in the |
|
|
//| derivative. It is also possible that "unlucky" step will move us |
|
|
//| to the termination too quickly. |
|
|
//| Our current approach is to have less than 0.1 % false negatives |
|
|
//| in our test examples (measured with multiple restarts from random|
|
|
//| points), and to have exactly 0 % false positives. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCOptGuardResults(CMinNLCState &State,COptGuardReport &rep)
|
|
{
|
|
COptServ::SmoothnessMonitorExportReport(State.m_smonitor,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Detailed results of the OptGuard integrity check for |
|
|
//| nonsmoothness test #0 |
|
|
//| Nonsmoothness(non-C1) test #0 studies function values (not |
|
|
//| gradient!) obtained during line searches and monitors behavior |
|
|
//| of the directional derivative estimate. |
|
|
//| This test is less powerful than test #1, but it does not depend |
|
|
//| on the gradient values and thus it is more robust against |
|
|
//| artifacts introduced by numerical differentiation. |
|
|
//| Two reports are returned: |
|
|
//| *a "strongest" one, corresponding to line search which had |
|
|
//| highest value of the nonsmoothness indicator |
|
|
//| *a "longest" one, corresponding to line search which had more |
|
|
//| function evaluations, and thus is more detailed |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; |
|
|
//| FALSE if test did not notice anything (in the |
|
|
//| latter cases fields below are empty). |
|
|
//| * fidx-is an index of the function (0 for target function, 1 or|
|
|
//| higher for nonlinear constraints) which is |
|
|
//| suspected of being "non-C1" |
|
|
//| * x0[], d[] - arrays of length N which store initial point and |
|
|
//| direction for line search (d[] can be normalized, |
|
|
//| but does not have to) |
|
|
//| * stp[], f[] - arrays of length CNT which store step lengths |
|
|
//| and function values at these points; f[i] is |
|
|
//| evaluated in x0 + stp[i]*d. |
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb |
|
|
//| (usually we have stpidxb = stpidxa + 3, with most |
|
|
//| likely position of the violation between |
|
|
//| stpidxa + 1 and stpidxa + 2. |
|
|
//| ================================================================ |
|
|
//| = SHORTLY SPEAKING: build a 2D plot of(stp, f) and look at it - |
|
|
//| = you will see where C1 continuity is violated.|
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| strrep - C1 test #0 "strong" report |
|
|
//| lngrep - C1 test #0 "long" report |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCOptGuardNonC1Test0Results(CMinNLCState &State,
|
|
COptGuardNonC1Test0Report &strrep,
|
|
COptGuardNonC1Test0Report &lngrep)
|
|
{
|
|
COptGuardApi::SmoothnessMonitorExportC1Test0Report(State.m_smonitor.m_nonc1test0strrep,State.m_lastscaleused,strrep);
|
|
COptGuardApi::SmoothnessMonitorExportC1Test0Report(State.m_smonitor.m_nonc1test0lngrep,State.m_lastscaleused,lngrep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Detailed results of the OptGuard integrity check for |
|
|
//| nonsmoothness test #1 |
|
|
//| Nonsmoothness(non-C1) test #1 studies individual components of |
|
|
//| the gradient computed during line search. |
|
|
//| When precise analytic gradient is provided this test is more |
|
|
//| powerful than test #0 which works with function values and |
|
|
//| ignores user-provided gradient. However, test #0 becomes more |
|
|
//| powerful when numerical differentiation is employed (in such |
|
|
//| cases test #1 detects higher levels of numerical noise and |
|
|
//| becomes too conservative). |
|
|
//| This test also tells specific components of the gradient which |
|
|
//| violate C1 continuity, which makes it more informative than #0, |
|
|
//| which just tells that continuity is violated. |
|
|
//| Two reports are returned: |
|
|
//| *a "strongest" one, corresponding to line search which had |
|
|
//| highest value of the nonsmoothness indicator |
|
|
//| *a "longest" one, corresponding to line search which had more |
|
|
//| function evaluations, and thus is more detailed |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; FALSE |
|
|
//| if test did not notice anything(in the latter cases fields |
|
|
//| below are empty). |
|
|
//| * fidx-is an index of the function(0 for target function, 1 or |
|
|
//| higher for nonlinear constraints) which is suspected of |
|
|
//| being "non-C1" |
|
|
//| * vidx - is an index of the variable in [0, N) with nonsmooth |
|
|
//| derivative |
|
|
//| * x0[], d[] - arrays of length N which store initial point and |
|
|
//| direction for line search(d[] can be normalized, but does not|
|
|
//| have to) |
|
|
//| * stp[], g[]-arrays of length CNT which store step lengths and |
|
|
//| gradient values at these points; g[i] is evaluated in |
|
|
//| x0 + stp[i]*d and contains vidx-th component of the gradient.|
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb (usually we |
|
|
//| have stpidxb = stpidxa + 3, with most likely position of the |
|
|
//| violation between stpidxa + 1 and stpidxa + 2. |
|
|
//| ================================================================ |
|
|
//| = SHORTLY SPEAKING: build a 2D plot of (stp, f) and look at it - |
|
|
//| = you will see where C1 continuity is violated.|
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| strrep - C1 test #1 "strong" report |
|
|
//| lngrep - C1 test #1 "long" report |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCOptGuardNonC1Test1Results(CMinNLCState &State,
|
|
COptGuardNonC1Test1Report &strrep,
|
|
COptGuardNonC1Test1Report &lngrep)
|
|
{
|
|
COptGuardApi::SmoothnessMonitorExportC1Test1Report(State.m_smonitor.m_nonc1test1strrep,State.m_lastscaleused,strrep);
|
|
COptGuardApi::SmoothnessMonitorExportC1Test1Report(State.m_smonitor.m_nonc1test1lngrep,State.m_lastscaleused,lngrep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| MinNLC results: the solution found, completion codes and |
|
|
//| additional information. |
|
|
//| If you activated OptGuard integrity checking functionality and |
|
|
//| want to get OptGuard report, it can be retrieved with: |
|
|
//| * MinNLCOptGuardResults() - for a primary report about(a) |
|
|
//| suspected C0/C1 continuity |
|
|
//| violations and (b) errors in the |
|
|
//| analytic gradient. |
|
|
//| * MinNLCOptGuardNonC1Test0Results() - for C1 continuity |
|
|
//| violation test #0, detailed line |
|
|
//| search log |
|
|
//| * MinNLCOptGuardNonC1Test1Results() - for C1 continuity |
|
|
//| violation test #1, detailed line |
|
|
//| search log |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[0..N - 1], solution |
|
|
//| Rep - optimization report, contains information about |
|
|
//| completion code, constraint violation at the |
|
|
//| solution and so on. |
|
|
//| You should check rep.m_terminationtype in order to distinguish|
|
|
//| successful termination from unsuccessful one: |
|
|
//| === FAILURE CODES === |
|
|
//| * -8 internal integrity control detected infinite or NAN |
|
|
//| values in function/gradient. Abnormal termination |
|
|
//| signalled. |
|
|
//| * -3 box constraints are infeasible. |
|
|
//| Note: infeasibility of non-box constraints does NOT trigger |
|
|
//| emergency completion; you have to examine rep.m_bcerr/ |
|
|
//| rep.m_lcerr/rep.m_nlcerr to detect possibly inconsistent |
|
|
//| constraints. |
|
|
//| === SUCCESS CODES === |
|
|
//| * 2 scaled step is no more than EpsX. |
|
|
//| * 5 MaxIts steps were taken. |
|
|
//| * 8 user requested algorithm termination via |
|
|
//| MinNLCRequestTermination(), last accepted point is |
|
|
//| returned. |
|
|
//| More information about fields of this structure can be found in |
|
|
//| the comments on CMinNLCReport datatype. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCResults(CMinNLCState &State,CRowDouble &x,
|
|
CMinNLCReport &rep)
|
|
{
|
|
x.Resize(0);
|
|
MinNLCResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| NLC results |
|
|
//| Buffered implementation of MinNLCResults() which uses pre- |
|
|
//| allocated buffer to store X[]. If buffer size is too small, it |
|
|
//| resizes buffer. It is intended to be used in the inner cycles of |
|
|
//| performance critical algorithms where array reallocation penalty |
|
|
//| is too large to be ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCResultsBuf(CMinNLCState &State,
|
|
CRowDouble &x,
|
|
CMinNLCReport &rep)
|
|
{
|
|
//--- copy
|
|
rep.m_iterationscount=State.m_repinneriterationscount;
|
|
rep.m_nfev=State.m_repnfev;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
rep.m_bcerr=State.m_repbcerr;
|
|
rep.m_bcidx=State.m_repbcidx;
|
|
rep.m_lcerr=State.m_replcerr;
|
|
rep.m_lcidx=State.m_replcidx;
|
|
rep.m_nlcerr=State.m_repnlcerr;
|
|
rep.m_nlcidx=State.m_repnlcidx;
|
|
rep.m_dbgphase0its=State.m_repdbgphase0its;
|
|
|
|
if(State.m_repterminationtype>0)
|
|
x=State.m_xc;
|
|
else
|
|
x=vector<double>::Full(State.m_n,AL_NaN);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine submits request for termination of running |
|
|
//| optimizer. It should be called from user - supplied callback |
|
|
//| when user decides that it is time to "smoothly" terminate |
|
|
//| optimization process. As result, optimizer stops at point which |
|
|
//| was "current accepted" when termination request was submitted |
|
|
//| and returns error code 8(successful termination). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - optimizer structure |
|
|
//| NOTE: after request for termination optimizer may perform |
|
|
//| several additional calls to user-supplied callbacks. |
|
|
//| It does NOT guarantee to stop immediately - it just |
|
|
//| guarantees that these additional calls will be discarded |
|
|
//| later. |
|
|
//| NOTE: calling this function on optimizer which is NOT running |
|
|
//| will have no effect. |
|
|
//| NOTE: multiple calls to this function are possible. First call |
|
|
//| is counted, subsequent calls are silently ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCRequestTermination(CMinNLCState &State)
|
|
{
|
|
State.m_userterminationneeded=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine restarts algorithm from new point. |
|
|
//| All optimization parameters (including constraints) are left |
|
|
//| unchanged. |
|
|
//| This function allows to solve multiple optimization problems |
|
|
//| (which must have same number of dimensions) without object |
|
|
//| reallocation penalty. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinNLCCreate |
|
|
//| call. |
|
|
//| X - new starting point. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCRestartFrom(CMinNLCState &State,CRowDouble &x)
|
|
{
|
|
int n=State.m_n;
|
|
//--- First, check for errors in the inputs
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- Set XC
|
|
State.m_xstart=x;
|
|
//--- prepare RComm facilities
|
|
State.m_rstate.ia.Resize(5);
|
|
ArrayResize(State.m_rstate.ba,1);
|
|
State.m_rstate.ra.Resize(2);
|
|
State.m_rstate.stage=-1;
|
|
ClearRequestFields(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Penalty function for equality constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Alpha - function argument. Penalty function becomes large |
|
|
//| when Alpha approaches - 1 or + 1. It is defined for|
|
|
//| Alpha <= -1 or Alpha >= +1 - in this case infinite |
|
|
//| value is returned. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F - depending on Alpha: |
|
|
//| * for Alpha in (-1 + eps, +1 - eps), F = F(Alpha) |
|
|
//| * for Alpha outside of interval, F is some very |
|
|
//| large number |
|
|
//| DF - depending on Alpha: |
|
|
//| * for Alpha in (-1 + eps, +1 - eps), |
|
|
//| DF = dF(Alpha) / dAlpha, exact numerical |
|
|
//| derivative. |
|
|
//| * otherwise, it is zero |
|
|
//| D2F - second derivative |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCEqualityPenaltyFunction(double alpha,double &f,
|
|
double &df,double &d2f)
|
|
{
|
|
f=0;
|
|
df=0;
|
|
d2f=0;
|
|
f=0.5*alpha*alpha;
|
|
df=alpha;
|
|
d2f=1.0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//|"Penalty" function for inequality constraints, which is multiplied|
|
|
//| by penalty coefficient Rho. |
|
|
//| "Penalty" function plays only supplementary role - it helps to |
|
|
//| stabilize algorithm when solving non - convex problems. Because |
|
|
//| it is multiplied by fixed and large Rho - not Lagrange multiplier|
|
|
//| Nu which may become arbitrarily small! - it enforces convexity of|
|
|
//| the problem behind the boundary of the feasible area. |
|
|
//| This function is zero at the feasible area and in the close |
|
|
//| neighborhood, it becomes non-zero only at some distance (scaling |
|
|
//| is essential!) and grows quadratically. |
|
|
//| Penalty function must enter augmented Lagrangian as Rho * PENALTY|
|
|
//| (x - lowerbound) with corresponding changes being made for upper |
|
|
//| bound or other kinds of constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Alpha - function argument. Typically, if we have active |
|
|
//| constraint with precise Lagrange multiplier, we |
|
|
//| have Alpha around 1. Large positive Alpha's |
|
|
//| correspond to inner area of the feasible set. |
|
|
//| Alpha < 1 corresponds to outer area of the feasible|
|
|
//| set. |
|
|
//| StabilizingPoint - point where F becomes non-zero. Must be |
|
|
//| negative value, at least -1, large values(hundreds)|
|
|
//| are possible. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F - F(Alpha) |
|
|
//| DF - DF = dF(Alpha) / dAlpha, exact derivative |
|
|
//| D2F - second derivative |
|
|
//| NOTE: it is important to have significantly non-zero |
|
|
//| StabilizingPoint, because when it is large, shift term |
|
|
//| does not interfere with Lagrange multipliers converging |
|
|
//| to their final values. Thus, convergence of such modified|
|
|
//| AUL algorithm is still guaranteed by same set of theorems|
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCInequalityPenaltyFunction(double alpha,
|
|
double stabilizingpoint,
|
|
double &f,
|
|
double &df,
|
|
double &d2f)
|
|
{
|
|
if(alpha>=stabilizingpoint)
|
|
{
|
|
f=0.0;
|
|
df=0.0;
|
|
d2f=0.0;
|
|
}
|
|
else
|
|
{
|
|
alpha=alpha-stabilizingpoint;
|
|
f=0.5*alpha*alpha;
|
|
df=alpha;
|
|
d2f=1.0;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| "Shift" function for inequality constraints, which is multiplied |
|
|
//| by corresponding Lagrange multiplier. |
|
|
//| "Shift" function is a main factor which enforces inequality |
|
|
//| constraints. Inequality penalty function plays only supplementary|
|
|
//| role - it prevents accidental step deep into infeasible area when|
|
|
//| working with non-convex problems (read comments on corresponding |
|
|
//| function for more information). |
|
|
//| Shift function must enter augmented Lagrangian as |
|
|
//| Nu / Rho * SHIFT((x - lowerbound)*Rho + 1) |
|
|
//| with corresponding changes being made for upper bound or other |
|
|
//| kinds of constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Alpha - function argument. Typically, if we have active |
|
|
//| constraint with precise Lagrange multiplier, we |
|
|
//| have Alpha around 1. Large positive Alpha's |
|
|
//| correspond to inner area of the feasible set. |
|
|
//| Alpha < 1 corresponds to outer area of the feasible|
|
|
//| set. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F - F(Alpha) |
|
|
//| DF - DF = dF(Alpha) / dAlpha, exact derivative |
|
|
//| D2F - second derivative |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCInequalityShiftFunction(double alpha,double &f,
|
|
double &df,double &d2f)
|
|
{
|
|
f=0;
|
|
df=0;
|
|
d2f=0;
|
|
|
|
if(alpha>=0.5)
|
|
{
|
|
f=-MathLog(alpha);
|
|
df=-(1/alpha);
|
|
d2f=1/(alpha*alpha);
|
|
}
|
|
else
|
|
{
|
|
f=2*alpha*alpha-4*alpha+(MathLog(2)+1.5);
|
|
df=4*alpha-4;
|
|
d2f=4;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Clears request fileds (to be sure that we don't forget to clear |
|
|
//| something) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::ClearRequestFields(CMinNLCState &State)
|
|
{
|
|
State.m_needfi=false;
|
|
State.m_needfij=false;
|
|
State.m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal initialization subroutine. |
|
|
//| Sets default NLC solver with default criteria. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::MinNLCInitInternal(int n,CRowDouble &x,double diffstep,
|
|
CMinNLCState &State)
|
|
{
|
|
//--- create variables
|
|
CMatrixDouble c;
|
|
CRowInt ct;
|
|
//--- Default params
|
|
State.m_stabilizingpoint=-2.0;
|
|
State.m_initialinequalitymultiplier=1.0;
|
|
//--- Smoothness monitor, default init
|
|
State.m_teststep=0;
|
|
State.m_smoothnessguardlevel=0;
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,0,0,false);
|
|
//--- Initialize other params
|
|
State.m_n=n;
|
|
State.m_diffstep=diffstep;
|
|
State.m_userterminationneeded=false;
|
|
State.m_xstart=x;
|
|
State.m_xc=x;
|
|
State.m_bndl=vector<double>::Full(n,AL_NEGINF);
|
|
ArrayResize(State.m_HasBndL,n);
|
|
State.m_bndu=vector<double>::Full(n,AL_POSINF);
|
|
ArrayResize(State.m_HasBndU,n);
|
|
State.m_s=vector<double>::Ones(n);
|
|
State.m_lastscaleused=vector<double>::Ones(n);
|
|
State.m_xstart.Resize(n);
|
|
State.m_xc.Resize(n);
|
|
State.m_x.Resize(n);
|
|
ArrayInitialize(State.m_HasBndL,false);
|
|
ArrayInitialize(State.m_HasBndU,false);
|
|
MinNLCSetLC(State,c,ct,0);
|
|
MinNLCSetNLC(State,0,0);
|
|
MinNLCSetCond(State,0.0,0);
|
|
MinNLCSetXRep(State,false);
|
|
MinNLCSetAlgoSQP(State);
|
|
MinNLCSetPrecExactRobust(State,0);
|
|
MinNLCSetSTPMax(State,0.0);
|
|
CMinLBFGS::MinLBFGSCreate(n,MathMin(m_lbfgsfactor,n),x,State.m_auloptimizer);
|
|
MinNLCRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function clears preconditioner for L-BFGS optimizer (sets it|
|
|
//| do default State); |
|
|
//| Parameters: |
|
|
//| AULOptimizer - optimizer to tune |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::ClearPreconditioner(CMinLBFGSState &auloptimizer)
|
|
{
|
|
CMinLBFGS::MinLBFGSSetPrecDefault(auloptimizer);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function updates preconditioner for L-BFGS optimizer. |
|
|
//| Parameters: |
|
|
//| PrecType - preconditioner type: |
|
|
//| * 0 for unpreconditioned iterations |
|
|
//| * 1 for inexact LBFGS |
|
|
//| * 2 for exact low rank preconditioner update |
|
|
//| after each UpdateFreq its |
|
|
//| * 3 for exact robust preconditioner update after|
|
|
//| each UpdateFreq its |
|
|
//| UpdateFreq - update frequency |
|
|
//| PrecCounter - iterations counter, must be zero on the first |
|
|
//| call, automatically increased by this function. |
|
|
//| This counter is used to implement |
|
|
//| "update-once-in-X-iterations" scheme. |
|
|
//| AULOptimizer - optimizer to tune |
|
|
//| X - current point |
|
|
//| Rho - penalty term |
|
|
//| GammaK - current estimate of Hessian norm (used for |
|
|
//| initialization of preconditioner). Can be zero, |
|
|
//| in which case Hessian is assumed to be unit. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::UpdatePreconditioner(int prectype,int updatefreq,
|
|
int &preccounter,
|
|
CMinLBFGSState &auloptimizer,
|
|
CRowDouble &x,double rho,
|
|
double gammak,CRowDouble &bndl,
|
|
bool &HasBndL[],CRowDouble &bndu,
|
|
bool &HasBndU[],CRowDouble &nubc,
|
|
CMatrixDouble &cleic,
|
|
CRowDouble &nulc,CRowDouble &fi,
|
|
CMatrixDouble &jac,
|
|
CRowDouble &nunlc,CRowDouble &bufd,
|
|
CRowDouble &bufc,
|
|
CMatrixDouble &bufw,
|
|
CMatrixDouble &bufz,
|
|
CRowDouble &tmp0,
|
|
int n,int nec,int nic,
|
|
int ng,int nh)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
double p=0;
|
|
double dp=0;
|
|
double d2p=0;
|
|
bool bflag=false;
|
|
int i_=0;
|
|
//--- check
|
|
if(!CAp::Assert(rho>0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
CApServ::RVectorSetLengthAtLeast(bufd,n);
|
|
CApServ::RVectorSetLengthAtLeast(bufc,nec+nic+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(tmp0,n);
|
|
//--- Preconditioner before update from barrier/penalty functions
|
|
if(gammak==0.0)
|
|
gammak=1;
|
|
bufd.Fill(gammak);
|
|
//--- Update diagonal Hessian using nonlinearity from boundary constraints:
|
|
//--- * penalty term from equality constraints
|
|
//--- * shift term from inequality constraints
|
|
//--- NOTE: penalty term for inequality constraints is ignored because it
|
|
//--- is large only in exceptional cases.
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if((HasBndL[i] && HasBndU[i]) && bndl[i]==bndu[i])
|
|
{
|
|
MinNLCEqualityPenaltyFunction((x[i]-bndl[i])*rho,p,dp,d2p);
|
|
bufd.Add(i,d2p*rho);
|
|
continue;
|
|
}
|
|
if(HasBndL[i])
|
|
{
|
|
MinNLCInequalityShiftFunction((x[i]-bndl[i])*rho+1,p,dp,d2p);
|
|
bufd.Add(i,nubc[2*i+0]*d2p*rho);
|
|
}
|
|
if(HasBndU[i])
|
|
{
|
|
MinNLCInequalityShiftFunction((bndu[i]-x[i])*rho+1,p,dp,d2p);
|
|
bufd.Add(i,nubc[2*i+1]*d2p*rho);
|
|
}
|
|
}
|
|
//--- Process linear constraints
|
|
bufw=cleic;
|
|
bufw.Resize(nec+nic+ng+nh,n);
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
v=x.DotR(bufw,i)-cleic.Get(i,n);
|
|
if(i<nec)
|
|
{
|
|
//--- Equality constraint
|
|
MinNLCEqualityPenaltyFunction(v*rho,p,dp,d2p);
|
|
bufc.Set(i,d2p*rho);
|
|
}
|
|
else
|
|
{
|
|
//--- Inequality constraint
|
|
MinNLCInequalityShiftFunction(-(v*rho)+1,p,dp,d2p);
|
|
bufc.Set(i,nulc[i]*d2p*rho);
|
|
}
|
|
}
|
|
//--- Process nonlinear constraints
|
|
for(i=0; i<ng+nh; i++)
|
|
{
|
|
bufw.Row(nec+nic+i,jac,1+i);
|
|
v=fi[1+i];
|
|
if(i<ng)
|
|
{
|
|
//--- Equality constraint
|
|
MinNLCEqualityPenaltyFunction(v*rho,p,dp,d2p);
|
|
bufc.Set(nec+nic+i,d2p*rho);
|
|
}
|
|
else
|
|
{
|
|
//--- Inequality constraint
|
|
MinNLCInequalityShiftFunction(-(v*rho)+1,p,dp,d2p);
|
|
bufc.Set(nec+nic+i,nunlc[i]*d2p*rho);
|
|
}
|
|
}
|
|
//--- Add regularizer (large Rho often result in nearly-degenerate matrices;
|
|
//--- sometimes Cholesky decomposition fails without regularization).
|
|
//--- We use RegPrec*diag(W'*W) as preconditioner.
|
|
k=nec+nic+ng+nh;
|
|
tmp0.Fill(0.0);
|
|
for(i=0; i<k; i++)
|
|
{
|
|
v=bufc[i];
|
|
for(j=0; j<n; j++)
|
|
tmp0.Add(j,v*CMath::Sqr(bufw.Get(i,j)));
|
|
}
|
|
for(j=0; j<=n-1; j++)
|
|
bufd.Add(j,tmp0[j]*m_regprec);
|
|
//--- Apply preconditioner
|
|
switch(prectype)
|
|
{
|
|
case 1:
|
|
CMinLBFGS::MinLBFGSSetPrecRankKLBFGSFast(auloptimizer,bufd,bufc,bufw,nec+nic+ng+nh);
|
|
break;
|
|
case 2:
|
|
if(preccounter%updatefreq==0)
|
|
CMinLBFGS::MinLBFGSSetPrecLowRankExact(auloptimizer,bufd,bufc,bufw,nec+nic+ng+nh);
|
|
break;
|
|
case 3:
|
|
if(preccounter%updatefreq==0)
|
|
{
|
|
//--- Generate full NxN dense Hessian
|
|
bufz=matrix<double>::Zeros(n,n);
|
|
bufz.Diag(bufd);
|
|
if(nec+nic+ng+nh>0)
|
|
{
|
|
for(i=0; i<nec+nic+ng+nh; i++)
|
|
{
|
|
if(!CAp::Assert(bufc[i]>=0.0,__FUNCTION__+": updatepreconditioner() integrity failure"))
|
|
return;
|
|
v=MathSqrt(bufc[i]);
|
|
bufw.Row(i,bufw[i]*v);
|
|
}
|
|
CAblas::RMatrixSyrk(n,nec+nic+ng+nh,1.0,bufw,0,0,2,1.0,bufz,0,0,true);
|
|
}
|
|
//--- Evaluate Cholesky decomposition, set preconditioner
|
|
bflag=CTrFac::SPDMatrixCholeskyRec(bufz,0,n,true,bufd);
|
|
if(!CAp::Assert(bflag,__FUNCTION__+": updatepreconditioner() failure,Cholesky failed"))
|
|
return;
|
|
CMinLBFGS::MinLBFGSSetPrecCholesky(auloptimizer,bufz,true);
|
|
}
|
|
break;
|
|
}
|
|
preccounter++;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine adds penalty from boundary constraints to target |
|
|
//| function and its gradient. Penalty function is one which is used |
|
|
//| for main AUL cycle - with Lagrange multipliers and infinite at |
|
|
//| the barrier and beyond. |
|
|
//| Parameters: |
|
|
//| X[] - current point |
|
|
//| BndL[], BndU[] - boundary constraints |
|
|
//| HasBndL[], HasBndU[] - I-th element is True if corresponding |
|
|
//| constraint is present |
|
|
//| NuBC[] - Lagrange multipliers corresponding to |
|
|
//| constraints |
|
|
//| Rho - penalty term |
|
|
//| StabilizingPoint - branch point for inequality stabilizing term|
|
|
//| F - function value to modify |
|
|
//| G - gradient to modify |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::PenaltyBC(CRowDouble &x,CRowDouble &bndl,bool &HasBndL[],
|
|
CRowDouble &bndu,bool &HasBndU[],CRowDouble &nubc,
|
|
int n,double rho,double stabilizingpoint,
|
|
double &f,CRowDouble &g)
|
|
{
|
|
//--- create variables
|
|
double p=0;
|
|
double dp=0;
|
|
double d2p=0;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if((HasBndL[i] && HasBndU[i]) && bndl[i]==bndu[i])
|
|
{
|
|
//--- I-th boundary constraint is of equality-type
|
|
MinNLCEqualityPenaltyFunction((x[i]-bndl[i])*rho,p,dp,d2p);
|
|
f=f+p/rho-nubc[2*i+0]*(x[i]-bndl[i]);
|
|
g.Add(i,dp-nubc[2*i+0]);
|
|
continue;
|
|
}
|
|
if(HasBndL[i])
|
|
{
|
|
//--- Handle lower bound
|
|
MinNLCInequalityPenaltyFunction(x[i]-bndl[i],stabilizingpoint,p,dp,d2p);
|
|
f=f+rho*p;
|
|
g.Add(i,rho*dp);
|
|
MinNLCInequalityShiftFunction((x[i]-bndl[i])*rho+1,p,dp,d2p);
|
|
f=f+p/rho*nubc[2*i+0];
|
|
g.Add(i,dp*nubc[2*i+0]);
|
|
}
|
|
if(HasBndU[i])
|
|
{
|
|
//--- Handle upper bound
|
|
MinNLCInequalityPenaltyFunction(bndu[i]-x[i],stabilizingpoint,p,dp,d2p);
|
|
f=f+rho*p;
|
|
g.Add(i,-rho*dp);
|
|
MinNLCInequalityShiftFunction((bndu[i]-x[i])*rho+1,p,dp,d2p);
|
|
f=f+p/rho*nubc[2*i+1];
|
|
g.Add(i,-dp*nubc[2*i+1]);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine adds penalty from linear constraints to target |
|
|
//| function and its gradient. Penalty function is one which is used |
|
|
//| for main AUL cycle - with Lagrange multipliers and infinite at |
|
|
//| the barrier and beyond. |
|
|
//| Parameters: |
|
|
//| X[] - current point |
|
|
//| CLEIC[] - constraints matrix, first NEC rows are equality |
|
|
//| ones, next NIC rows are inequality ones. |
|
|
//| array[NEC + NIC, N + 1] |
|
|
//| NuLC[] - Lagrange multipliers corresponding to |
|
|
//| constraints, array[NEC + NIC] |
|
|
//| N - dimensionalty |
|
|
//| NEC - number of equality constraints |
|
|
//| NIC - number of inequality constraints. |
|
|
//| Rho - penalty term |
|
|
//| StabilizingPoint - branch point for inequality stabilizing term|
|
|
//| F - function value to modify |
|
|
//| G - gradient to modify |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::PenaltyLC(CRowDouble &x,CMatrixDouble &cleic,
|
|
CRowDouble &nulc,int n,int nec,int nic,
|
|
double rho,double stabilizingpoint,
|
|
double &f,CRowDouble &g)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double p=0;
|
|
double dp=0;
|
|
double d2p=0;
|
|
double fupd=0;
|
|
double gupd=0;
|
|
int i_=0;
|
|
|
|
for(int i=0; i<nec+nic; i++)
|
|
{
|
|
v=-cleic.Get(i,n);
|
|
for(i_=0; i_<n; i_++)
|
|
v+=cleic.Get(i,i_)*x[i_];
|
|
fupd=0;
|
|
gupd=0;
|
|
if(i<nec)
|
|
{
|
|
//--- Equality constraint
|
|
MinNLCEqualityPenaltyFunction(v*rho,p,dp,d2p);
|
|
fupd=fupd+p/rho;
|
|
gupd=gupd+dp;
|
|
fupd=fupd-nulc[i]*v;
|
|
gupd=gupd-nulc[i];
|
|
}
|
|
else
|
|
{
|
|
//--- Inequality constraint
|
|
MinNLCInequalityPenaltyFunction(-v,stabilizingpoint,p,dp,d2p);
|
|
fupd=fupd+p*rho;
|
|
gupd=gupd-dp*rho;
|
|
MinNLCInequalityShiftFunction(-(v*rho)+1,p,dp,d2p);
|
|
fupd=fupd+p/rho*nulc[i];
|
|
gupd=gupd-dp*nulc[i];
|
|
}
|
|
f=f+fupd;
|
|
for(i_=0; i_<n; i_++)
|
|
g.Add(i_,gupd*cleic.Get(i,i_));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine adds penalty from nonlinear constraints to target|
|
|
//| function and its gradient. Penalty function is one which is used |
|
|
//| for main AUL cycle - with Lagrange multipliers and infinite at |
|
|
//| the barrier and beyond. |
|
|
//| Parameters: |
|
|
//| Fi[] - function vector: |
|
|
//| * 1 component for function being minimized |
|
|
//| * NG components for equality constraints |
|
|
//| G_i(x) = 0 |
|
|
//| * NH components for inequality constraints |
|
|
//| H_i(x) <= 0 |
|
|
//| J[] - Jacobian matrix, array[1 + NG + NH, N] |
|
|
//| NuNLC[] - Lagrange multipliers corresponding to |
|
|
//| constraints, array[NG + NH] |
|
|
//| N - number of dimensions |
|
|
//| NG - number of equality constraints |
|
|
//| NH - number of inequality constraints |
|
|
//| Rho - penalty term |
|
|
//| StabilizingPoint - branch point for inequality stabilizing term |
|
|
//| F - function value to modify |
|
|
//| G - gradient to modify |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::PenaltyNLC(CRowDouble &fi,CMatrixDouble &j,
|
|
CRowDouble &nunlc,int n,int ng,int nh,
|
|
double rho,double stabilizingpoint,
|
|
double &f,CRowDouble &g)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double p=0;
|
|
double dp=0;
|
|
double d2p=0;
|
|
double fupd=0;
|
|
double gupd=0;
|
|
int i_=0;
|
|
//--- IMPORTANT: loop starts from 1, not zero!
|
|
for(int i=1; i<=ng+nh; i++)
|
|
{
|
|
v=fi[i];
|
|
fupd=0;
|
|
gupd=0;
|
|
if(i<=ng)
|
|
{
|
|
//--- Equality constraint
|
|
MinNLCEqualityPenaltyFunction(v*rho,p,dp,d2p);
|
|
fupd=fupd+p/rho;
|
|
gupd=gupd+dp;
|
|
fupd=fupd-nunlc[i-1]*v;
|
|
gupd=gupd-nunlc[i-1];
|
|
}
|
|
else
|
|
{
|
|
//--- Inequality constraint
|
|
MinNLCInequalityPenaltyFunction(-v,stabilizingpoint,p,dp,d2p);
|
|
fupd=fupd+p*rho;
|
|
gupd=gupd-dp*rho;
|
|
MinNLCInequalityShiftFunction(-(v*rho)+1,p,dp,d2p);
|
|
fupd=fupd+p/rho*nunlc[i-1];
|
|
gupd=gupd-dp*nunlc[i-1];
|
|
}
|
|
f+=fupd;
|
|
for(i_=0; i_<n; i_++)
|
|
g.Add(i_,gupd*j.Get(i,i_));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs actual processing for AUL algorithm. It |
|
|
//| expects that caller redirects its reverse communication requests |
|
|
//| NeedFiJ / XUpdated to external user who will provide analytic |
|
|
//| derivative (or handle reports about progress). |
|
|
//| In case external user does not have analytic derivative, it is |
|
|
//| responsibility of caller to intercept NeedFiJ request and replace|
|
|
//| it with appropriate numerical differentiation scheme. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinNLC::AULIteration(CMinNLCState &State,CSmoothnessMonitor &smonitor)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int ng=0;
|
|
int nh=0;
|
|
int i=0;
|
|
int j=0;
|
|
int outerit=0;
|
|
int preccounter=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double p=0;
|
|
double dp=0;
|
|
double d2p=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
int i_=0;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstateaul.stage>=0)
|
|
{
|
|
n=State.m_rstateaul.ia[0];
|
|
nec=State.m_rstateaul.ia[1];
|
|
nic=State.m_rstateaul.ia[2];
|
|
ng=State.m_rstateaul.ia[3];
|
|
nh=State.m_rstateaul.ia[4];
|
|
i=State.m_rstateaul.ia[5];
|
|
j=State.m_rstateaul.ia[6];
|
|
outerit=State.m_rstateaul.ia[7];
|
|
preccounter=State.m_rstateaul.ia[8];
|
|
v=State.m_rstateaul.ra[0];
|
|
vv=State.m_rstateaul.ra[1];
|
|
p=State.m_rstateaul.ra[2];
|
|
dp=State.m_rstateaul.ra[3];
|
|
d2p=State.m_rstateaul.ra[4];
|
|
v0=State.m_rstateaul.ra[5];
|
|
v1=State.m_rstateaul.ra[6];
|
|
v2=State.m_rstateaul.ra[7];
|
|
}
|
|
else
|
|
{
|
|
n=809;
|
|
nec=205;
|
|
nic=-838;
|
|
ng=939;
|
|
nh=-526;
|
|
i=763;
|
|
j=-541;
|
|
outerit=-698;
|
|
preccounter=-900;
|
|
v=-318;
|
|
vv=-940;
|
|
p=1016;
|
|
dp=-229;
|
|
d2p=-536;
|
|
v0=487;
|
|
v1=-115;
|
|
v2=886;
|
|
}
|
|
|
|
switch(State.m_rstateaul.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
if(!CAp::Assert(State.m_solvertype==0,__FUNCTION__+": internal error"))
|
|
return(false);
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
ng=State.m_ng;
|
|
nh=State.m_nh;
|
|
//--- Prepare scaled problem
|
|
CApServ::RVectorSetLengthAtLeast(State.m_scaledbndl,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_scaledbndu,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_scaledcleic,nec+nic,n+1);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i])
|
|
State.m_scaledbndl.Set(i,State.m_bndl[i]/State.m_s[i]);
|
|
if(State.m_HasBndU[i])
|
|
State.m_scaledbndu.Set(i,State.m_bndu[i]/State.m_s[i]);
|
|
State.m_xc.Set(i,State.m_xstart[i]/State.m_s[i]);
|
|
}
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
//--- Scale and normalize linear constraints
|
|
vv=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=State.m_cleic.Get(i,j)*State.m_s[j];
|
|
State.m_scaledcleic.Set(i,j,v);
|
|
vv+=v*v;
|
|
}
|
|
vv=MathSqrt(vv);
|
|
State.m_scaledcleic.Set(i,n,State.m_cleic.Get(i,n));
|
|
if(vv>0.0)
|
|
{
|
|
for(j=0; j<=n; j++)
|
|
State.m_scaledcleic.Set(i,j,State.m_scaledcleic.Get(i,j)/vv);
|
|
}
|
|
}
|
|
//--- Prepare stopping criteria
|
|
CMinLBFGS::MinLBFGSSetCond(State.m_auloptimizer,0,0,State.m_epsx,State.m_maxits);
|
|
CMinLBFGS::MinLBFGSSetStpMax(State.m_auloptimizer,State.m_stpmax);
|
|
//--- Main AUL cycle:
|
|
//--- * prepare Lagrange multipliers NuNB/NuLC
|
|
//--- * set GammaK (current estimate of Hessian norm) to InitGamma and XKPresent to False
|
|
State.m_nubc=vector<double>::Zeros(2*n);
|
|
State.m_nulc=vector<double>::Zeros(nec+nic);
|
|
State.m_nunlc=vector<double>::Zeros(ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xk,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_gk,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xk1,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_gk1,n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i] && State.m_bndl[i]==State.m_bndu[i])
|
|
continue;
|
|
if(State.m_HasBndL[i])
|
|
State.m_nubc.Set(2*i,State.m_initialinequalitymultiplier);
|
|
if(State.m_HasBndU[i])
|
|
State.m_nubc.Set(2*i+1,State.m_initialinequalitymultiplier);
|
|
}
|
|
for(i=0; i<nic; i++)
|
|
State.m_nulc.Set(nec+i,State.m_initialinequalitymultiplier);
|
|
for(i=0; i<nh; i++)
|
|
State.m_nunlc.Set(ng+i,State.m_initialinequalitymultiplier);
|
|
State.m_gammak=m_initgamma;
|
|
State.m_xkpresent=false;
|
|
if(!CAp::Assert(State.m_aulitscnt>0,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
ClearPreconditioner(State.m_auloptimizer);
|
|
outerit=0;
|
|
label=3;
|
|
break;
|
|
}
|
|
//--- main loop
|
|
while(label>=0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 3:
|
|
if(outerit>State.m_aulitscnt-1)
|
|
{
|
|
label=5;
|
|
break;
|
|
}
|
|
//--- Optimize with current Lagrange multipliers
|
|
//--- NOTE: this code expects and checks that line search ends in the
|
|
//--- point which is used as beginning for the next search. Such
|
|
//--- guarantee is given by MCSRCH function. L-BFGS optimizer
|
|
//--- does not formally guarantee it, but it follows same rule.
|
|
//--- Below we a) rely on such property of the optimizer, and b)
|
|
//--- assert that it is true, in order to fail loudly if it is
|
|
//--- not true.
|
|
//--- NOTE: security check for NAN/INF in F/G is responsibility of
|
|
//--- LBFGS optimizer. AUL optimizer checks for NAN/INF only
|
|
//--- when we update Lagrange multipliers.
|
|
preccounter=0;
|
|
CMinLBFGS::MinLBFGSSetXRep(State.m_auloptimizer,true);
|
|
CMinLBFGS::MinLBFGSRestartFrom(State.m_auloptimizer,State.m_xc);
|
|
case 6:
|
|
if(!CMinLBFGS::MinLBFGSIteration(State.m_auloptimizer))
|
|
{
|
|
label=7;
|
|
break;
|
|
}
|
|
if(!State.m_auloptimizer.m_needfg)
|
|
{
|
|
label=8;
|
|
break;
|
|
}
|
|
//--- Un-scale X, evaluate F/G/H, re-scale Jacobian
|
|
for(i=0; i<n; i++)
|
|
State.m_x.Set(i,State.m_auloptimizer.m_x[i]*State.m_s[i]);
|
|
State.m_needfij=true;
|
|
State.m_rstateaul.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
State.m_needfij=false;
|
|
for(i=0; i<=ng+nh; i++)
|
|
for(j=0; j<n; j++)
|
|
State.m_j.Mul(i,j,State.m_s[j]);
|
|
//--- Store data for estimation of Hessian norm:
|
|
//--- * current point (re-scaled)
|
|
State.m_xk1=State.m_auloptimizer.m_x;
|
|
State.m_gk1=State.m_j[0]+0;
|
|
//--- Function being optimized
|
|
State.m_auloptimizer.m_f=State.m_fi[0];
|
|
State.m_auloptimizer.m_g=State.m_j[0]+0;
|
|
//--- Send information to OptGuard monitor
|
|
COptServ::SmoothnessMonitorEnqueuePoint(smonitor,State.m_auloptimizer.m_d,State.m_auloptimizer.m_stp,State.m_auloptimizer.m_x,State.m_fi,State.m_j);
|
|
//--- Penalty for violation of boundary/linear/nonlinear constraints
|
|
PenaltyBC(State.m_auloptimizer.m_x,State.m_scaledbndl,State.m_HasBndL,State.m_scaledbndu,State.m_HasBndU,State.m_nubc,n,State.m_rho,State.m_stabilizingpoint,State.m_auloptimizer.m_f,State.m_auloptimizer.m_g);
|
|
PenaltyLC(State.m_auloptimizer.m_x,State.m_scaledcleic,State.m_nulc,n,nec,nic,State.m_rho,State.m_stabilizingpoint,State.m_auloptimizer.m_f,State.m_auloptimizer.m_g);
|
|
PenaltyNLC(State.m_fi,State.m_j,State.m_nunlc,n,ng,nh,State.m_rho,State.m_stabilizingpoint,State.m_auloptimizer.m_f,State.m_auloptimizer.m_g);
|
|
//--- Forward termination request if needed
|
|
if(State.m_userterminationneeded)
|
|
CMinLBFGS::MinLBFGSRequestTermination(State.m_auloptimizer);
|
|
//--- To optimizer
|
|
label=6;
|
|
break;
|
|
case 8:
|
|
if(!State.m_auloptimizer.m_xupdated)
|
|
{
|
|
label=10;
|
|
break;
|
|
}
|
|
//--- Report current point (if needed)
|
|
if(!State.m_xrep)
|
|
{
|
|
label=12;
|
|
break;
|
|
}
|
|
State.m_x=State.m_auloptimizer.m_x*State.m_s+0;
|
|
State.m_f=State.m_auloptimizer.m_f;
|
|
State.m_xupdated=true;
|
|
State.m_rstateaul.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_xupdated=false;
|
|
case 12:
|
|
//--- Send information to OptGuard monitor
|
|
COptServ::SmoothnessMonitorFinalizeLineSearch(smonitor);
|
|
COptServ::SmoothnessMonitorStartLineSearch(smonitor,State.m_auloptimizer.m_x,State.m_fi,State.m_j);
|
|
//--- Forward termination request if needed
|
|
if(State.m_userterminationneeded)
|
|
CMinLBFGS::MinLBFGSRequestTermination(State.m_auloptimizer);
|
|
//--- Update constraints violation
|
|
COptServ::CheckBcViolation(State.m_HasBndL,State.m_scaledbndl,State.m_HasBndU,State.m_scaledbndu,State.m_auloptimizer.m_x,n,State.m_s,false,State.m_repbcerr,State.m_repbcidx);
|
|
COptServ::CheckLcViolation(State.m_scaledcleic,State.m_lcsrcidx,nec,nic,State.m_auloptimizer.m_x,n,State.m_replcerr,State.m_replcidx);
|
|
COptServ::CheckNLcViolation(State.m_fi,ng,nh,State.m_repnlcerr,State.m_repnlcidx);
|
|
//--- Update GammaK
|
|
if(State.m_xkpresent)
|
|
{
|
|
//--- XK/GK store beginning of current line search, and XK1/GK1
|
|
//--- store data for the end of the line search:
|
|
//--- * first, we Assert() that XK1 (last point where function
|
|
//--- was evaluated) is same as AULOptimizer.X (what is
|
|
//--- reported by RComm interface
|
|
//--- * calculate step length V2.
|
|
//--- If V2>HessEstTol, then:
|
|
//--- * calculate V0 - directional derivative at XK,
|
|
//--- and V1 - directional derivative at XK1
|
|
//--- * set GammaK to Max(GammaK, |V1-V0|/V2)
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!CAp::Assert(MathAbs(State.m_auloptimizer.m_x[i]-State.m_xk1[i])<=(100*CMath::m_machineepsilon) || !(MathIsValidNumber(State.m_auloptimizer.m_x[i]) && MathIsValidNumber(State.m_xk1[i])),__FUNCTION__+": integrity check failed,unexpected behavior of LBFGS optimizer"))
|
|
return(false);
|
|
}
|
|
v2=0.0;
|
|
for(i=0; i<n; i++)
|
|
v2+=CMath::Sqr(State.m_xk[i]-State.m_xk1[i]);
|
|
v2=MathSqrt(v2);
|
|
if(v2>m_hessesttol)
|
|
{
|
|
v0=0.0;
|
|
v1=0.0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=(State.m_xk[i]-State.m_xk1[i])/v2;
|
|
v0+=State.m_gk[i]*v;
|
|
v1+=State.m_gk1[i]*v;
|
|
}
|
|
State.m_gammak=MathMax(State.m_gammak,MathAbs(v1-v0)/v2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Beginning of the first line search, XK is not yet initialized.
|
|
State.m_xk=State.m_xk1;
|
|
State.m_gk=State.m_gk1;
|
|
State.m_xkpresent=true;
|
|
}
|
|
//--- Update preconsitioner using current GammaK
|
|
UpdatePreconditioner(State.m_prectype,State.m_updatefreq,preccounter,State.m_auloptimizer,State.m_auloptimizer.m_x,State.m_rho,State.m_gammak,State.m_scaledbndl,State.m_HasBndL,State.m_scaledbndu,State.m_HasBndU,State.m_nubc,State.m_scaledcleic,State.m_nulc,State.m_fi,State.m_j,State.m_nunlc,State.m_bufd,State.m_bufc,State.m_bufw,State.m_bufz,State.m_tmp0,n,nec,nic,ng,nh);
|
|
label=6;
|
|
break;
|
|
case 10:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
return(false);
|
|
case 7:
|
|
CMinLBFGS::MinLBFGSResultsBuf(State.m_auloptimizer,State.m_xc,State.m_aulreport);
|
|
State.m_repinneriterationscount+=State.m_aulreport.m_iterationscount;
|
|
State.m_repnfev+=State.m_aulreport.m_nfev;
|
|
State.m_repterminationtype=State.m_aulreport.m_terminationtype;
|
|
State.m_repouteriterationscount++;
|
|
if(State.m_repterminationtype<=0 || State.m_repterminationtype==8)
|
|
{
|
|
label=5;
|
|
break;
|
|
}
|
|
//--- 1. Evaluate F/J
|
|
//--- 2. Check for NAN/INF in F/J: we just calculate sum of their
|
|
//--- components, it should be enough to reduce vector/matrix to
|
|
//--- just one value which either "normal" (all summands were "normal")
|
|
//--- or NAN/INF (at least one summand was NAN/INF).
|
|
//--- 3. Update Lagrange multipliers
|
|
State.m_x=State.m_xc*State.m_s+0;
|
|
State.m_needfij=true;
|
|
State.m_rstateaul.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_needfij=false;
|
|
v=0.0;
|
|
for(i=0; i<=ng+nh; i++)
|
|
{
|
|
v=0.1*v+State.m_fi[i];
|
|
for(j=0; j<n; j++)
|
|
v=0.1*v+State.m_j.Get(i,j);
|
|
}
|
|
if(!MathIsValidNumber(v))
|
|
{
|
|
//--- Abnormal termination - infinities in function/gradient
|
|
State.m_repterminationtype=-8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
for(i=0; i<=ng+nh; i++)
|
|
State.m_j.Row(i,State.m_s.ToVector()*State.m_j[i]);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Process coefficients corresponding to equality-type
|
|
//--- constraints.
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i] && State.m_bndl[i]==State.m_bndu[i])
|
|
{
|
|
MinNLCEqualityPenaltyFunction((State.m_xc[i]-State.m_scaledbndl[i])*State.m_rho,p,dp,d2p);
|
|
State.m_nubc.Set(2*i,CApServ::BoundVal(State.m_nubc[2*i]-dp,-m_maxlagmult,m_maxlagmult));
|
|
continue;
|
|
}
|
|
//--- Process coefficients corresponding to inequality-type
|
|
//--- constraints. These coefficients have limited growth/decay
|
|
//--- per iteration which helps to stabilize algorithm.
|
|
if(!CAp::Assert(m_aulmaxgrowth>1.0,__FUNCTION__+": integrity error"))
|
|
return(false);
|
|
if(State.m_HasBndL[i])
|
|
{
|
|
MinNLCInequalityShiftFunction((State.m_xc[i]-State.m_scaledbndl[i])*State.m_rho+1,p,dp,d2p);
|
|
v=MathAbs(dp);
|
|
v=MathMin(v,m_aulmaxgrowth);
|
|
v=MathMax(v,1/m_aulmaxgrowth);
|
|
State.m_nubc.Set(2*i,CApServ::BoundVal(State.m_nubc[2*i]*v,-m_maxlagmult,m_maxlagmult));
|
|
}
|
|
if(State.m_HasBndU[i])
|
|
{
|
|
MinNLCInequalityShiftFunction((State.m_scaledbndu[i]-State.m_xc[i])*State.m_rho+1,p,dp,d2p);
|
|
v=MathAbs(dp);
|
|
v=MathMin(v,m_aulmaxgrowth);
|
|
v=MathMax(v,1/m_aulmaxgrowth);
|
|
State.m_nubc.Set(2*i+1,CApServ::BoundVal(State.m_nubc[2*i+1]*v,-m_maxlagmult,m_maxlagmult));
|
|
}
|
|
}
|
|
for(i=0; i<=nec+nic-1; i++)
|
|
{
|
|
v=-State.m_scaledcleic.Get(i,n);
|
|
for(i_=0; i_<n; i_++)
|
|
v+=State.m_scaledcleic.Get(i,i_)*State.m_xc[i_];
|
|
if(i<nec)
|
|
{
|
|
MinNLCEqualityPenaltyFunction(v*State.m_rho,p,dp,d2p);
|
|
State.m_nulc.Set(i,CApServ::BoundVal(State.m_nulc[i]-dp,-m_maxlagmult,m_maxlagmult));
|
|
}
|
|
else
|
|
{
|
|
MinNLCInequalityShiftFunction(-(v*State.m_rho)+1,p,dp,d2p);
|
|
v=MathAbs(dp);
|
|
v=MathMin(v,m_aulmaxgrowth);
|
|
v=MathMax(v,1/m_aulmaxgrowth);
|
|
State.m_nulc.Set(i,CApServ::BoundVal(State.m_nulc[i]*v,-m_maxlagmult,m_maxlagmult));
|
|
}
|
|
}
|
|
for(i=1; i<=ng+nh; i++)
|
|
{
|
|
//--- NOTE: loop index must start from 1, not zero!
|
|
v=State.m_fi[i];
|
|
if(i<=ng)
|
|
{
|
|
MinNLCEqualityPenaltyFunction(v*State.m_rho,p,dp,d2p);
|
|
State.m_nunlc.Set(i-1,CApServ::BoundVal(State.m_nunlc[i-1]-dp,-m_maxlagmult,m_maxlagmult));
|
|
}
|
|
else
|
|
{
|
|
MinNLCInequalityShiftFunction(-(v*State.m_rho)+1,p,dp,d2p);
|
|
v=MathAbs(dp);
|
|
v=MathMin(v,m_aulmaxgrowth);
|
|
v=MathMax(v,1/m_aulmaxgrowth);
|
|
State.m_nunlc.Set(i-1,CApServ::BoundVal(State.m_nunlc[i-1]*v,-m_maxlagmult,m_maxlagmult));
|
|
}
|
|
}
|
|
outerit++;
|
|
label=3;
|
|
break;
|
|
case 5:
|
|
for(i=0; i<n; i++)
|
|
State.m_xc.Set(i,State.m_xc[i]*State.m_s[i]);
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
//--- Saving State
|
|
result=true;
|
|
State.m_rstateaul.ia.Set(0,n);
|
|
State.m_rstateaul.ia.Set(1,nec);
|
|
State.m_rstateaul.ia.Set(2,nic);
|
|
State.m_rstateaul.ia.Set(3,ng);
|
|
State.m_rstateaul.ia.Set(4,nh);
|
|
State.m_rstateaul.ia.Set(5,i);
|
|
State.m_rstateaul.ia.Set(6,j);
|
|
State.m_rstateaul.ia.Set(7,outerit);
|
|
State.m_rstateaul.ia.Set(8,preccounter);
|
|
State.m_rstateaul.ra.Set(0,v);
|
|
State.m_rstateaul.ra.Set(1,vv);
|
|
State.m_rstateaul.ra.Set(2,p);
|
|
State.m_rstateaul.ra.Set(3,dp);
|
|
State.m_rstateaul.ra.Set(4,d2p);
|
|
State.m_rstateaul.ra.Set(5,v0);
|
|
State.m_rstateaul.ra.Set(6,v1);
|
|
State.m_rstateaul.ra.Set(7,v2);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Unscales X (converts from scaled variables to original ones), |
|
|
//| paying special attention to box constraints (output is always |
|
|
//| feasible; active constraints are mapped to active ones). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNLC::UnScale(CMinNLCState &State,CRowDouble &xs,
|
|
CRowDouble &scaledbndl,CRowDouble &scaledbndu,
|
|
CRowDouble &xu)
|
|
{
|
|
int n=State.m_n;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && xs[i]<=scaledbndl[i])
|
|
{
|
|
xu.Set(i,State.m_bndl[i]);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndU[i] && xs[i]>=scaledbndu[i])
|
|
{
|
|
xu.Set(i,State.m_bndu[i]);
|
|
continue;
|
|
}
|
|
xu.Set(i,xs[i]*State.m_s[i]);
|
|
if(State.m_HasBndL[i] && xu[i]<State.m_bndl[i])
|
|
xu.Set(i,State.m_bndl[i]);
|
|
if(State.m_HasBndU[i] && xu[i]>State.m_bndu[i])
|
|
xu.Set(i,State.m_bndu[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores temporaries for internal QP solver. |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinNSQP
|
|
{
|
|
double m_fc;
|
|
double m_fn;
|
|
bool m_tmpb[];
|
|
CSNNLSSolver m_nnls;
|
|
CRowInt m_tmpidx;
|
|
CRowDouble m_d;
|
|
CRowDouble m_gc;
|
|
CRowDouble m_invutc;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmpc;
|
|
CRowDouble m_tmpd;
|
|
CRowDouble m_tmplambdas;
|
|
CRowDouble m_x0;
|
|
CRowDouble m_xc;
|
|
CRowDouble m_xn;
|
|
CMatrixDouble m_ch;
|
|
CMatrixDouble m_rk;
|
|
CMatrixDouble m_tmpc2;
|
|
CMatrixDouble m_uh;
|
|
//--- constructor / destructor
|
|
CMinNSQP(void) { m_fc=0; m_fn=0; }
|
|
~CMinNSQP(void) {}
|
|
//---
|
|
void Copy(const CMinNSQP &obj);
|
|
//--- overloading
|
|
void operator=(const CMinNSQP &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNSQP::Copy(const CMinNSQP &obj)
|
|
{
|
|
m_fc=obj.m_fc;
|
|
m_fn=obj.m_fn;
|
|
ArrayCopy(m_tmpb,obj.m_tmpb);
|
|
m_nnls=obj.m_nnls;
|
|
m_tmpidx=obj.m_tmpidx;
|
|
m_d=obj.m_d;
|
|
m_gc=obj.m_gc;
|
|
m_invutc=obj.m_invutc;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmpc=obj.m_tmpc;
|
|
m_tmpd=obj.m_tmpd;
|
|
m_tmplambdas=obj.m_tmplambdas;
|
|
m_x0=obj.m_x0;
|
|
m_xc=obj.m_xc;
|
|
m_xn=obj.m_xn;
|
|
m_ch=obj.m_ch;
|
|
m_rk=obj.m_rk;
|
|
m_tmpc2=obj.m_tmpc2;
|
|
m_uh=obj.m_uh;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores nonlinear optimizer State. |
|
|
//| You should use functions provided by MinNS subpackage to work |
|
|
//| with this object |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinNSState
|
|
{
|
|
int m_agsmaxbacktrack;
|
|
int m_agsmaxbacktracknonfull;
|
|
int m_agsmaxraddecays;
|
|
int m_agsminupdate;
|
|
int m_agssamplesize;
|
|
int m_agsshortlimit;
|
|
int m_dbgncholesky;
|
|
int m_maxits;
|
|
int m_n;
|
|
int m_nec;
|
|
int m_ng;
|
|
int m_nh;
|
|
int m_nic;
|
|
int m_repfuncidx;
|
|
int m_repinneriterationscount;
|
|
int m_repnfev;
|
|
int m_repouteriterationscount;
|
|
int m_repterminationtype;
|
|
int m_repvaridx;
|
|
int m_solvertype;
|
|
double m_agsalphadecay;
|
|
double m_agsdecrease;
|
|
double m_agsinitstp;
|
|
double m_agspenaltyincrease;
|
|
double m_agspenaltylevel;
|
|
double m_agsraddecay;
|
|
double m_agsradius;
|
|
double m_agsrhononlinear;
|
|
double m_agsshortf;
|
|
double m_agsshortstpabs;
|
|
double m_agsshortstprel;
|
|
double m_agsstattold;
|
|
double m_diffstep;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_meritf;
|
|
double m_rawf;
|
|
double m_replcerr;
|
|
double m_repnlcerr;
|
|
double m_rholinear;
|
|
bool m_HasBndL[];
|
|
bool m_HasBndU[];
|
|
bool m_needfi;
|
|
bool m_needfij;
|
|
bool m_userterminationneeded;
|
|
bool m_xrep;
|
|
bool m_xupdated;
|
|
RCommState m_rstate;
|
|
RCommState m_rstateags;
|
|
CRowInt m_tmp3;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_colmax;
|
|
CRowDouble m_d;
|
|
CRowDouble m_diagh;
|
|
CRowDouble m_fbase;
|
|
CRowDouble m_fi;
|
|
CRowDouble m_fm;
|
|
CRowDouble m_fp;
|
|
CRowDouble m_meritg;
|
|
CRowDouble m_rawg;
|
|
CRowDouble m_s;
|
|
CRowDouble m_samplef;
|
|
CRowDouble m_scaledbndl;
|
|
CRowDouble m_scaledbndu;
|
|
CRowDouble m_signmax;
|
|
CRowDouble m_signmin;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CRowDouble m_x;
|
|
CRowDouble m_xbase;
|
|
CRowDouble m_xc;
|
|
CRowDouble m_xn;
|
|
CRowDouble m_xscaled;
|
|
CRowDouble m_xstart;
|
|
CMinNSQP m_nsqp;
|
|
CMatrixDouble m_cleic;
|
|
CMatrixDouble m_j;
|
|
CMatrixDouble m_samplegm;
|
|
CMatrixDouble m_samplegmbc;
|
|
CMatrixDouble m_samplex;
|
|
CMatrixDouble m_scaledcleic;
|
|
CMatrixDouble m_tmp2;
|
|
CHighQualityRandState m_agsrs;
|
|
//--- constructor / destructor
|
|
CMinNSState(void);
|
|
~CMinNSState(void) {}
|
|
//---
|
|
void Copy(const CMinNSState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinNSState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinNSState::CMinNSState(void)
|
|
{
|
|
m_agsmaxbacktrack=0;
|
|
m_agsmaxbacktracknonfull=0;
|
|
m_agsmaxraddecays=0;
|
|
m_agsminupdate=0;
|
|
m_agssamplesize=0;
|
|
m_agsshortlimit=0;
|
|
m_dbgncholesky=0;
|
|
m_maxits=0;
|
|
m_n=0;
|
|
m_nec=0;
|
|
m_ng=0;
|
|
m_nh=0;
|
|
m_nic=0;
|
|
m_repfuncidx=0;
|
|
m_repinneriterationscount=0;
|
|
m_repnfev=0;
|
|
m_repouteriterationscount=0;
|
|
m_repterminationtype=0;
|
|
m_repvaridx=0;
|
|
m_solvertype=0;
|
|
m_agsalphadecay=0;
|
|
m_agsdecrease=0;
|
|
m_agsinitstp=0;
|
|
m_agspenaltyincrease=0;
|
|
m_agspenaltylevel=0;
|
|
m_agsraddecay=0;
|
|
m_agsradius=0;
|
|
m_agsrhononlinear=0;
|
|
m_agsshortf=0;
|
|
m_agsshortstpabs=0;
|
|
m_agsshortstprel=0;
|
|
m_agsstattold=0;
|
|
m_diffstep=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_meritf=0;
|
|
m_rawf=0;
|
|
m_replcerr=0;
|
|
m_repnlcerr=0;
|
|
m_rholinear=0;
|
|
m_needfi=false;
|
|
m_needfij=false;
|
|
m_userterminationneeded=false;
|
|
m_xrep=false;
|
|
m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNSState::Copy(const CMinNSState &obj)
|
|
{
|
|
m_agsmaxbacktrack=obj.m_agsmaxbacktrack;
|
|
m_agsmaxbacktracknonfull=obj.m_agsmaxbacktracknonfull;
|
|
m_agsmaxraddecays=obj.m_agsmaxraddecays;
|
|
m_agsminupdate=obj.m_agsminupdate;
|
|
m_agssamplesize=obj.m_agssamplesize;
|
|
m_agsshortlimit=obj.m_agsshortlimit;
|
|
m_dbgncholesky=obj.m_dbgncholesky;
|
|
m_maxits=obj.m_maxits;
|
|
m_n=obj.m_n;
|
|
m_nec=obj.m_nec;
|
|
m_ng=obj.m_ng;
|
|
m_nh=obj.m_nh;
|
|
m_nic=obj.m_nic;
|
|
m_repfuncidx=obj.m_repfuncidx;
|
|
m_repinneriterationscount=obj.m_repinneriterationscount;
|
|
m_repnfev=obj.m_repnfev;
|
|
m_repouteriterationscount=obj.m_repouteriterationscount;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_repvaridx=obj.m_repvaridx;
|
|
m_solvertype=obj.m_solvertype;
|
|
m_agsalphadecay=obj.m_agsalphadecay;
|
|
m_agsdecrease=obj.m_agsdecrease;
|
|
m_agsinitstp=obj.m_agsinitstp;
|
|
m_agspenaltyincrease=obj.m_agspenaltyincrease;
|
|
m_agspenaltylevel=obj.m_agspenaltylevel;
|
|
m_agsraddecay=obj.m_agsraddecay;
|
|
m_agsradius=obj.m_agsradius;
|
|
m_agsrhononlinear=obj.m_agsrhononlinear;
|
|
m_agsshortf=obj.m_agsshortf;
|
|
m_agsshortstpabs=obj.m_agsshortstpabs;
|
|
m_agsshortstprel=obj.m_agsshortstprel;
|
|
m_agsstattold=obj.m_agsstattold;
|
|
m_diffstep=obj.m_diffstep;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_meritf=obj.m_meritf;
|
|
m_rawf=obj.m_rawf;
|
|
m_replcerr=obj.m_replcerr;
|
|
m_repnlcerr=obj.m_repnlcerr;
|
|
m_rholinear=obj.m_rholinear;
|
|
ArrayCopy(m_HasBndL,obj.m_HasBndL);
|
|
ArrayCopy(m_HasBndU,obj.m_HasBndU);
|
|
m_needfi=obj.m_needfi;
|
|
m_needfij=obj.m_needfij;
|
|
m_userterminationneeded=obj.m_userterminationneeded;
|
|
m_xrep=obj.m_xrep;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_rstate=obj.m_rstate;
|
|
m_rstateags=obj.m_rstateags;
|
|
m_tmp3=obj.m_tmp3;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_colmax=obj.m_colmax;
|
|
m_d=obj.m_d;
|
|
m_diagh=obj.m_diagh;
|
|
m_fbase=obj.m_fbase;
|
|
m_fi=obj.m_fi;
|
|
m_fm=obj.m_fm;
|
|
m_fp=obj.m_fp;
|
|
m_meritg=obj.m_meritg;
|
|
m_rawg=obj.m_rawg;
|
|
m_s=obj.m_s;
|
|
m_samplef=obj.m_samplef;
|
|
m_scaledbndl=obj.m_scaledbndl;
|
|
m_scaledbndu=obj.m_scaledbndu;
|
|
m_signmax=obj.m_signmax;
|
|
m_signmin=obj.m_signmin;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_x=obj.m_x;
|
|
m_xbase=obj.m_xbase;
|
|
m_xc=obj.m_xc;
|
|
m_xn=obj.m_xn;
|
|
m_xscaled=obj.m_xscaled;
|
|
m_xstart=obj.m_xstart;
|
|
m_nsqp=obj.m_nsqp;
|
|
m_cleic=obj.m_cleic;
|
|
m_j=obj.m_j;
|
|
m_samplegm=obj.m_samplegm;
|
|
m_samplegmbc=obj.m_samplegmbc;
|
|
m_samplex=obj.m_samplex;
|
|
m_scaledcleic=obj.m_scaledcleic;
|
|
m_tmp2=obj.m_tmp2;
|
|
m_agsrs=obj.m_agsrs;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure stores optimization report: |
|
|
//| * IterationsCount total number of inner iterations |
|
|
//| * NFEV number of gradient evaluations |
|
|
//| * TerminationType termination type(see below) |
|
|
//| * CErr maximum violation of all types of |
|
|
//| constraints |
|
|
//| * LCErr maximum violation of linear |
|
|
//| constraints |
|
|
//| * NLCErr maximum violation of nonlinear |
|
|
//| constraints |
|
|
//| TERMINATION CODES |
|
|
//| TerminationType field contains completion code, which can be: |
|
|
//| -8 internal integrity control detected infinite or NAN |
|
|
//| values in function / gradient. Abnormal termination |
|
|
//| signalled. |
|
|
//| -3 box constraints are inconsistent |
|
|
//| -1 inconsistent parameters were passed: |
|
|
//| * penalty parameter for MinNSSetAlgoAGS() is zero, but|
|
|
//| we have nonlinear constraints set by MinNSSetNLC() |
|
|
//| 2 sampling radius decreased below epsx |
|
|
//| 5 MaxIts steps was taken |
|
|
//| 7 stopping conditions are too stringent, further |
|
|
//| improvement is impossible, X contains best point |
|
|
//| found so far. |
|
|
//| 8 User requested termination via |
|
|
//| MinNSRequestTermination() |
|
|
//| Other fields of this structure are not documented and should not |
|
|
//| be used! |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinNSReport
|
|
{
|
|
int m_funcidx;
|
|
int m_iterationscount;
|
|
int m_nfev;
|
|
int m_terminationtype;
|
|
int m_varidx;
|
|
double m_cerr;
|
|
double m_lcerr;
|
|
double m_nlcerr;
|
|
//--- constructor / destructor
|
|
CMinNSReport(void) { ZeroMemory(this); }
|
|
~CMinNSReport(void) {}
|
|
//---
|
|
void Copy(const CMinNSReport &obj);
|
|
//--- overloading
|
|
void operator=(const CMinNSReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNSReport::Copy(const CMinNSReport &obj)
|
|
{
|
|
m_funcidx=obj.m_funcidx;
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_nfev=obj.m_nfev;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_varidx=obj.m_varidx;
|
|
m_cerr=obj.m_cerr;
|
|
m_lcerr=obj.m_lcerr;
|
|
m_nlcerr=obj.m_nlcerr;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CMinNS
|
|
{
|
|
public:
|
|
static void MinNSCreate(int n,CRowDouble &x,CMinNSState &State);
|
|
static void MinNSCreateF(int n,CRowDouble &x,double diffstep,CMinNSState &State);
|
|
static void MinNSSetBC(CMinNSState &State,CRowDouble &bndl,CRowDouble &bndu);
|
|
static void MinNSSetLC(CMinNSState &State,CMatrixDouble &c,CRowInt &ct,int k);
|
|
static void MinNSSetNLC(CMinNSState &State,int nlec,int nlic);
|
|
static void MinNSSetCond(CMinNSState &State,double epsx,int m_maxits);
|
|
static void MinNSSetScale(CMinNSState &State,CRowDouble &s);
|
|
static void MinNSSetAlgoAGS(CMinNSState &State,double radius,double penalty);
|
|
static void MinNSSetXRep(CMinNSState &State,bool needxrep);
|
|
static void MinNSRequestTermination(CMinNSState &State);
|
|
static bool MinNSIteration(CMinNSState &State);
|
|
static void MinNSResults(CMinNSState &State,CRowDouble &x,CMinNSReport &rep);
|
|
static void MinNSResultsBuf(CMinNSState &State,CRowDouble &x,CMinNSReport &rep);
|
|
static void MinNSRestartFrom(CMinNSState &State,CRowDouble &x);
|
|
|
|
private:
|
|
static void ClearRequestFields(CMinNSState &State);
|
|
static void MinNSInitInternal(int n,CRowDouble &x,double diffstep,CMinNSState &State);
|
|
static bool AGSIteration(CMinNSState &State);
|
|
static void UnscalePointBC(CMinNSState &State,CRowDouble &x);
|
|
static void SolveQP(CMatrixDouble &sampleg,CRowDouble &diagh,int nsample,int nvars,CRowDouble &coeffs,int &dbgncholesky,CMinNSQP &State);
|
|
static void QPCalculateGradFunc(CMatrixDouble &sampleg,CRowDouble &diagh,int nsample,int nvars,CRowDouble &coeffs,CRowDouble &g,double &f,CRowDouble &tmp);
|
|
static void QPCalculateFunc(CMatrixDouble &sampleg,CRowDouble &diagh,int nsample,int nvars,CRowDouble &coeffs,double &f,CRowDouble &tmp);
|
|
static void QPSolveU(CMatrixDouble &a,int n,CRowDouble &x);
|
|
static void QPSolveUT(CMatrixDouble &a,int n,CRowDouble &x);
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| NONSMOOTH NONCONVEX OPTIMIZATION |
|
|
//| SUBJECT TO BOX / LINEAR / NONLINEAR - NONSMOOTH CONSTRAINTS |
|
|
//| DESCRIPTION: |
|
|
//| The subroutine minimizes function F(x) of N arguments subject to|
|
|
//| any combination of: |
|
|
//| * bound constraints |
|
|
//| * linear inequality constraints |
|
|
//| * linear equality constraints |
|
|
//| * nonlinear equality constraints Gi(x) = 0 |
|
|
//| * nonlinear inequality constraints Hi(x) <= 0 |
|
|
//| IMPORTANT: see MinNSSetAlgoAGS for important information on |
|
|
//| performance restrictions of AGS solver. |
|
|
//| REQUIREMENTS: |
|
|
//| * starting point X0 must be feasible or not too far away from |
|
|
//| the feasible set |
|
|
//| * F(), G(), H() are continuous, locally Lipschitz and |
|
|
//| continuously (but not necessarily twice) differentiable in an|
|
|
//| open dense subset of R^N. |
|
|
//| Functions F(), G() and H() may be nonsmooth and non-convex. |
|
|
//| Informally speaking, it means that functions are composed of |
|
|
//| large differentiable "patches" with nonsmoothness having place |
|
|
//| only at the boundaries between these "patches". Most real-life |
|
|
//| nonsmooth functions satisfy these requirements. Say, anything |
|
|
//| which involves finite number of abs(), min() and max() is very |
|
|
//| likely to pass the test. Say, it is possible to optimize anything|
|
|
//| of the following: |
|
|
//| * f = abs(x0) + 2 * abs(x1) |
|
|
//| * f = max(x0, x1) |
|
|
//| * f = sin(max(x0, x1) + abs(x2)) |
|
|
//| * for nonlinearly constrained problems: F() must be bounded |
|
|
//| from below without nonlinear constraints (this requirement |
|
|
//| is due to the fact that, contrary to box and linear |
|
|
//| constraints, nonlinear ones require special handling). |
|
|
//| * user must provide function value and gradient for F(), H(), |
|
|
//| G() at all points where function / gradient can be calculated|
|
|
//| If optimizer requires value exactly at the boundary between |
|
|
//| "patches"(say, at x = 0 for f = abs(x)), where gradient is |
|
|
//| not defined, user may resolve tie arbitrarily (in our case - |
|
|
//| return +1 or -1 at its discretion). |
|
|
//| * NS solver supports numerical differentiation, i.e. it may |
|
|
//| differentiate your function for you, but it results in 2N |
|
|
//| increase of function evaluations. Not recommended unless you |
|
|
//| solve really small problems. See MinNSCreateF() for more |
|
|
//| information on this functionality. |
|
|
//| USAGE: |
|
|
//| 1. User initializes algorithm State with MinNSCreate() call and |
|
|
//| chooses what NLC solver to use. There is some solver which is |
|
|
//| used by default, with default Settings, but you should NOT |
|
|
//| rely on default choice. It may change in future releases of |
|
|
//| ALGLIB without notice, and no one can guarantee that new |
|
|
//| solver will be able to solve your problem with default |
|
|
//| Settings. |
|
|
//| From the other side, if you choose solver explicitly, you can be |
|
|
//| pretty sure that it will work with new ALGLIB releases. |
|
|
//| In the current release following solvers can be used: |
|
|
//| * AGS solver (activated with MinNSSetAlgoAGS() function) |
|
|
//| 2. User adds boundary and/or linear and/or nonlinear constraints |
|
|
//| by means of calling one of the following functions: |
|
|
//| a) MinNSSetBC() for boundary constraints |
|
|
//| b) MinNSSetLC() for linear constraints |
|
|
//| c) MinNSSetNLC() for nonlinear constraints |
|
|
//| You may combine(a), (b) and (c) in one optimization problem. |
|
|
//| 3. User sets scale of the variables with MinNSSetScale() function|
|
|
//| It is VERY important to set scale of the variables, because |
|
|
//| nonlinearly constrained problems are hard to solve when |
|
|
//| variables are badly scaled. |
|
|
//| 4. User sets stopping conditions with MinNSSetCond(). |
|
|
//| 5. Finally, user calls MinNSOptimize() function which takes |
|
|
//| algorithm State and pointer (delegate, etc) to callback |
|
|
//| function which calculates F / G / H. |
|
|
//| 6. User calls MinNSResults() to get solution |
|
|
//| 7. Optionally user may call MinNSRestartFrom() to solve another |
|
|
//| problem with same N but another starting point. |
|
|
//| MinNSRestartFrom() allows to reuse already initialized |
|
|
//| structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N > 0: |
|
|
//| * if given, only leading N elements of X are used |
|
|
//| * if not given, automatically determined from size |
|
|
//| of X |
|
|
//| X - starting point, array[N]: |
|
|
//| * it is better to set X to a feasible point |
|
|
//| * but X can be infeasible, in which case algorithm |
|
|
//| will try to find feasible point first, using X as|
|
|
//| initial approximation. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| NOTE: MinNSCreateF() function may be used if you do not have |
|
|
//| analytic gradient. This function creates solver which |
|
|
//| uses numerical differentiation with user-specified step. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSCreate(int n,CRowDouble &x,CMinNSState &State)
|
|
{
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
MinNSInitInternal(n,x,0.0,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Version of MinNSCreateF() which uses numerical differentiation. |
|
|
//| I.e., you do not have to calculate derivatives yourself. However,|
|
|
//| this version needs 2N times more function evaluations. |
|
|
//| 2-point differentiation formula is used, because more precise |
|
|
//| 4-point formula is unstable when used on non - smooth functions. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N > 0: |
|
|
//| * if given, only leading N elements of X are used |
|
|
//| * if not given, automatically determined from size |
|
|
//| of X |
|
|
//| X - starting point, array[N]: |
|
|
//| * it is better to set X to a feasible point |
|
|
//| * but X can be infeasible, in which case algorithm |
|
|
//| will try to find feasible point first, using X as|
|
|
//| initial approximation. |
|
|
//| DiffStep - differentiation step, DiffStep > 0. Algorithm |
|
|
//| performs numerical differentiation with step for|
|
|
//| I-th variable being equal to DiffStep*S[I] (here |
|
|
//| S[] is a scale vector, set by MinNSSetScale() |
|
|
//| function). Do not use too small steps, because |
|
|
//| it may lead to catastrophic cancellation during |
|
|
//| intermediate calculations. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSCreateF(int n,CRowDouble &x,double diffstep,
|
|
CMinNSState &State)
|
|
{
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(diffstep),__FUNCTION__+": DiffStep is infinite or NaN!"))
|
|
return;
|
|
if(!CAp::Assert((double)(diffstep)>0.0,__FUNCTION__+": DiffStep is non-positive!"))
|
|
return;
|
|
|
|
MinNSInitInternal(n,x,diffstep,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets boundary constraints. |
|
|
//| Boundary constraints are inactive by default (after initial |
|
|
//| creation). They are preserved after algorithm restart with |
|
|
//| MinNSRestartFrom(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bounds, array[N]. If some (all) variables |
|
|
//| are unbounded, you may specify very small number |
|
|
//| or -INF. |
|
|
//| BndU - upper bounds, array[N]. If some (all) variables are|
|
|
//| unbounded, you may specify very large number |
|
|
//| or +INF. |
|
|
//| NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case |
|
|
//| I-th variable will be "frozen" at X[i]=BndL[i]=BndU[i]. |
|
|
//| NOTE 2: AGS solver has following useful properties: |
|
|
//| * bound constraints are always satisfied exactly |
|
|
//| * function is evaluated only INSIDE area specified by |
|
|
//| bound constraints, even when numerical |
|
|
//| differentiation is used(algorithm adjusts nodes |
|
|
//| according to boundary constraints) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSSetBC(CMinNSState &State,CRowDouble &bndl,CRowDouble &bndu)
|
|
{
|
|
int n=State.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(bndu)>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(bndl[i]) || IsNegInf(bndl[i]),__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu[i]) || IsPosInf(bndu[i]),__FUNCTION__+": BndL contains NAN or -INF"))
|
|
return;
|
|
State.m_bndl.Set(i,bndl[i]);
|
|
State.m_HasBndL[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_bndu.Set(i,bndu[i]);
|
|
State.m_HasBndU[i]=MathIsValidNumber(bndu[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets linear constraints. |
|
|
//| Linear constraints are inactive by default (after initial |
|
|
//| creation). They are preserved after algorithm restart with |
|
|
//| MinNSRestartFrom(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinNSCreate() |
|
|
//| call. |
|
|
//| C - linear constraints, array[K, N + 1]. Each row of C |
|
|
//| represents one constraint, either equality or |
|
|
//| inequality(see below): |
|
|
//| * first N elements correspond to coefficients, |
|
|
//| * last element corresponds to the right part. |
|
|
//| All elements of C (including right part) must be |
|
|
//| finite. |
|
|
//| CT - type of constraints, array[K]: |
|
|
//| * if CT[i] > 0, then I-th constraint is |
|
|
//| C[i, *] * x >= C[i, n + 1] |
|
|
//| * if CT[i] = 0, then I-th constraint is |
|
|
//| C[i, *] * x = C[i, n + 1] |
|
|
//| * if CT[i] < 0, then I-th constraint is |
|
|
//| C[i, *] * x <= C[i, n + 1] |
|
|
//| K - number of equality / inequality constraints, K>=0: |
|
|
//| * if given, only leading K elements of C/CT are |
|
|
//| used |
|
|
//| * if not given, automatically determined from sizes|
|
|
//| of C/CT |
|
|
//| NOTE: linear (non-bound) constraints are satisfied only |
|
|
//| approximately: |
|
|
//| * there always exists some minor violation(about current |
|
|
//| sampling radius in magnitude during optimization, about|
|
|
//| EpsX in the solution) due to use of penalty method to |
|
|
//| handle constraints. |
|
|
//| * numerical differentiation, if used, may lead to |
|
|
//| function evaluations outside of the feasible area, |
|
|
//| because algorithm does NOT change numerical |
|
|
//| differentiation formula according to linear constraints|
|
|
//| If you want constraints to be satisfied exactly, try to |
|
|
//| reformulate your problem in such manner that all constraints will|
|
|
//| become boundary ones (this kind of constraints is always |
|
|
//| satisfied exactly, both in the final solution and in all |
|
|
//| intermediate points). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSSetLC(CMinNSState &State,CMatrixDouble &c,
|
|
CRowInt &ct,int k)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_n;
|
|
int i=0;
|
|
//--- First, check for errors in the inputs
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Cols(c)>=n+1 || k==0,__FUNCTION__+": Cols(C)<N+1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(c)>=k,__FUNCTION__+": Rows(C)<K"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(ct)>=k,__FUNCTION__+": Length(CT)<K"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(c,k,n+1),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
//--- Handle zero K
|
|
if(k==0)
|
|
{
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
return;
|
|
}
|
|
//--- Equality constraints are stored first, in the upper
|
|
//--- NEC rows of State.CLEIC matrix. Inequality constraints
|
|
//--- are stored in the next NIC rows.
|
|
//--- NOTE: we convert inequality constraints to the form
|
|
//--- A*x<=b before copying them.
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_cleic,k,n+1);
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
for(i=0; i<k; i++)
|
|
{
|
|
if(ct[i]==0)
|
|
{
|
|
State.m_cleic.Row(State.m_nec,c,i);
|
|
State.m_nec ++;
|
|
}
|
|
}
|
|
for(i=0; i<k; i++)
|
|
{
|
|
if(ct[i]!=0)
|
|
{
|
|
if(ct[i]>0)
|
|
State.m_cleic.Row(State.m_nec+State.m_nic,c[i]*(-1.0));
|
|
else
|
|
State.m_cleic.Row(State.m_nec+State.m_nic,c,i);
|
|
State.m_nic++;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets nonlinear constraints. |
|
|
//| In fact, this function sets NUMBER of nonlinear constraints. |
|
|
//| Constraints itself (constraint functions) are passed to |
|
|
//| MinNSOptimize() method. This method requires user-defined vector |
|
|
//| function F[] and its Jacobian J[], where: |
|
|
//| * first component of F[] and first row of Jacobian J[] |
|
|
//| correspond to function being minimized |
|
|
//| * next NLEC components of F[] (and rows of J) correspond to |
|
|
//| nonlinear equality constraints G_i(x) = 0 |
|
|
//| * next NLIC components of F[] (and rows of J) correspond to |
|
|
//| nonlinear inequality constraints H_i(x) <= 0 |
|
|
//| NOTE: you may combine nonlinear constraints with linear/boundary |
|
|
//| ones. If your problem has mixed constraints, you may |
|
|
//| explicitly specify some of them as linear ones. It may help|
|
|
//| optimizer to handle them more efficiently. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with |
|
|
//| MinNSCreate() call. |
|
|
//| NLEC - number of Non-Linear Equality Constraints(NLEC),|
|
|
//| >= 0 |
|
|
//| NLIC - number of Non-Linear Inquality Constraints(NLIC)|
|
|
//| >= 0 |
|
|
//| NOTE 1: nonlinear constraints are satisfied only approximately! |
|
|
//| It is possible that algorithm will evaluate function |
|
|
//| outside of the feasible area! |
|
|
//| NOTE 2: algorithm scales variables according to scale specified |
|
|
//| by MinNSSetScale() function, so it can handle problems |
|
|
//| with badly scaled variables (as long as we KNOW their |
|
|
//| scales). |
|
|
//| However, there is no way to automatically scale nonlinear |
|
|
//| constraints Gi(x) and Hi(x). Inappropriate scaling of Gi/Hi may |
|
|
//| ruin convergence. Solving problem with constraint "1000*G0(x)=0" |
|
|
//| is NOT same as solving it with constraint "0.001*G0(x)=0". |
|
|
//| It means that YOU are the one who is responsible for correct |
|
|
//| scaling of nonlinear constraints Gi(x) and Hi(x). We recommend |
|
|
//| you to scale nonlinear constraints in such way that I-th |
|
|
//| component of dG/dX (or dH/dx) has approximately unit magnitude |
|
|
//| (for problems with unit scale) or has magnitude approximately |
|
|
//| equal to 1/S[i] (where S is a scale set by MinNSSetScale() |
|
|
//| function). |
|
|
//| NOTE 3: nonlinear constraints are always hard to handle, no |
|
|
//| matter what algorithm you try to use. Even basic box/ |
|
|
//| linear constraints modify function curvature by adding |
|
|
//| valleys and ridges. However, nonlinear constraints add |
|
|
//| valleys which are very hard to follow due to their |
|
|
//| "curved" nature. |
|
|
//| It means that optimization with single nonlinear constraint may |
|
|
//| be significantly slower than optimization with multiple linear |
|
|
//| ones. It is normal situation, and we recommend you to carefully |
|
|
//| choose Rho parameter of MinNSSetAlgoAGS(), because too large |
|
|
//| value may slow down convergence. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSSetNLC(CMinNSState &State,int nlec,int nlic)
|
|
{
|
|
if(!CAp::Assert(nlec>=0,__FUNCTION__+": NLEC<0"))
|
|
return;
|
|
if(!CAp::Assert(nlic>=0,__FUNCTION__+": NLIC<0"))
|
|
return;
|
|
|
|
State.m_ng=nlec;
|
|
State.m_nh=nlic;
|
|
State.m_fi.Resize(1+State.m_ng+State.m_nh);
|
|
State.m_j.Resize(1+State.m_ng+State.m_nh,State.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping conditions for iterations of |
|
|
//| optimizer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsX - >= 0, The AGS solver finishes its work if on |
|
|
//| k+1-th iteration sampling radius decreases |
|
|
//| below EpsX. |
|
|
//| MaxIts - maximum number of iterations. If MaxIts = 0, |
|
|
//| the number of iterations is unlimited. |
|
|
//| Passing EpsX = 0 and MaxIts = 0 (simultaneously) will lead to |
|
|
//| automatic stopping criterion selection. We do not recommend you |
|
|
//| to rely on default choice in production code. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSSetCond(CMinNSState &State,double epsx,int m_maxits)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(epsx),__FUNCTION__+": EpsX is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX"))
|
|
return;
|
|
if(!CAp::Assert(m_maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
|
|
if(epsx==0.0 && m_maxits==0)
|
|
epsx=1.0E-6;
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=m_maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients for NLC optimizer. |
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions (step size and gradient are scaled before comparison |
|
|
//| with tolerances). Scale of the I-th variable is a translation |
|
|
//| invariant measure of: |
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| Scaling is also used by finite difference variant of the |
|
|
//| optimizer - step along I-th axis is equal to DiffStep*S[I]. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients S[i] |
|
|
//| may be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSSetScale(CMinNSState &State,CRowDouble &s)
|
|
{
|
|
if(!CAp::Assert(CAp::Len(s)>=State.m_n,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
State.m_s.Set(i,MathAbs(s[i]));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells MinNS unit to use AGS (adaptive gradient |
|
|
//| sampling) algorithm for nonsmooth constrained optimization. This |
|
|
//| algorithm is a slight modification of one described in "An |
|
|
//| Adaptive Gradient Sampling Algorithm for Nonsmooth Optimization" |
|
|
//| by Frank E. Curtisy and Xiaocun Quez. |
|
|
//| This optimizer has following benefits and drawbacks: |
|
|
//| + robustness; it can be used with nonsmooth and nonconvex |
|
|
//| functions. |
|
|
//| + relatively easy tuning; most of the metaparameters are easy |
|
|
//| to select. |
|
|
//| - it has convergence of steepest descent, slower than CG/LBFGS.|
|
|
//| - each iteration involves evaluation of ~2N gradient values and|
|
|
//| solution of 2Nx2N quadratic programming problem, which limits|
|
|
//| applicability of algorithm by small-scale problems (up |
|
|
//| to 50 - 100). |
|
|
//| IMPORTANT: this algorithm has convergence guarantees, i.e. it |
|
|
//| will steadily move towards some stationary point of |
|
|
//| the function. |
|
|
//| However, "stationary point" does not always mean "solution". |
|
|
//| Nonsmooth problems often have "flat spots", i.e. areas where |
|
|
//| function do not change at all. Such "flat spots" are stationary |
|
|
//| points by definition, and algorithm may be caught here. |
|
|
//| Nonsmooth CONVEX tasks are not prone to this problem. Say, if |
|
|
//| your function has form f() = MAX(f0, f1, ...), and f_i are convex|
|
|
//| then f() is convex too and you have guaranteed convergence to |
|
|
//| solution. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| Radius - initial sampling radius, >= 0. Internally |
|
|
//| multiplied by vector of per-variable scales |
|
|
//| specified by MinNSSetScale(). |
|
|
//| You should select relatively large sampling |
|
|
//| radius, roughly proportional to scaled length |
|
|
//| of the first steps of the algorithm. Something |
|
|
//| close to 0.1 in magnitude should be good for |
|
|
//| most problems. |
|
|
//| AGS solver can automatically decrease radius, |
|
|
//| so too large radius is not a problem (assuming |
|
|
//| that you won't choose so large radius that |
|
|
//| algorithm will sample function in too far away |
|
|
//| points, where gradient value is irrelevant). |
|
|
//| Too small radius won't cause algorithm to fail, |
|
|
//| but it may slow down algorithm (it may have to |
|
|
//| perform too short steps). |
|
|
//| Penalty - penalty coefficient for nonlinear constraints: |
|
|
//| * for problem with nonlinear constraints should |
|
|
//| be some problem - specific positive value, |
|
|
//| large enough that penalty term changes shape |
|
|
//| of the function. Starting from some problem - |
|
|
//| specific value penalty coefficient becomes |
|
|
//| large enough to exactly enforce nonlinear |
|
|
//| constraints; larger values do not improve |
|
|
//| precision. Increasing it too much may slow |
|
|
//| down convergence, so you should choose it |
|
|
//| carefully. |
|
|
//| * can be zero for problems WITHOUT nonlinear |
|
|
//| constraints (i.e. for unconstrained ones or |
|
|
//| ones with just box or linear constraints) |
|
|
//| * if you specify zero value for problem with at |
|
|
//| least one nonlinear constraint, algorithm will|
|
|
//| terminate with error code - 1. |
|
|
//| ALGORITHM OUTLINE |
|
|
//| The very basic outline of unconstrained AGS algorithm is given |
|
|
//| below: |
|
|
//| 0. If sampling radius is below EpsX or we performed more then |
|
|
//| MaxIts iterations - STOP. |
|
|
//| 1. sample O(N) gradient values at random locations around current|
|
|
//| point; informally speaking, this sample is an implicit |
|
|
//| piecewise linear model of the function, although algorithm |
|
|
//| formulation does not mention that explicitly |
|
|
//| 2. solve quadratic programming problem in order to find descent |
|
|
//| direction |
|
|
//| 3. if QP solver tells us that we are near solution, decrease |
|
|
//| sampling radius and move to(0) |
|
|
//| 4. perform backtracking line search |
|
|
//| 5. after moving to new point, goto(0) |
|
|
//| Constraint handling details: |
|
|
//| * box constraints are handled exactly by algorithm |
|
|
//| * linear/nonlinear constraints are handled by adding L1 |
|
|
//| penalty. Because our solver can handle nonsmoothness, we can |
|
|
//| use L1 penalty function, which is an exact one (i.e. exact |
|
|
//| solution is returned under such penalty). |
|
|
//| * penalty coefficient for linear constraints is chosen |
|
|
//| automatically; however, penalty coefficient for nonlinear |
|
|
//| constraints must be specified by user. |
|
|
//| ===== TRACING AGS SOLVER ======================================= |
|
|
//| AGS solver supports advanced tracing capabilities. You can trace |
|
|
//| algorithm output by specifying following trace symbols (case- |
|
|
//| insensitive) by means of trace_file() call: |
|
|
//| * 'AGS' - for basic trace of algorithm steps and |
|
|
//| decisions. Only short scalars (function |
|
|
//| values and deltas) are printed. N-dimensional|
|
|
//| quantities like search directions are NOT |
|
|
//| printed. |
|
|
//| * 'AGS.DETAILED' - for output of points being visited and |
|
|
//| search directions. This symbol also |
|
|
//| implicitly defines 'AGS'. You can control |
|
|
//| output format by additionally specifying: |
|
|
//| * nothing to output in 6-digit exponential |
|
|
//| format |
|
|
//| * 'PREC.E15' to output in 15-digit |
|
|
//| exponential format |
|
|
//| * 'PREC.F6' to output in 6-digit fixed-point |
|
|
//| format |
|
|
//| * 'AGS.DETAILED.SAMPLE' - for output of points being visited, |
|
|
//| search directions and gradient sample. May |
|
|
//| take a LOT of space, do not use it on |
|
|
//| problems with more that several tens of vars.|
|
|
//| This symbol also implicitly defines 'AGS' and|
|
|
//| 'AGS.DETAILED'. |
|
|
//| By default trace is disabled and adds no overhead to the |
|
|
//| optimization process. However, specifying any of the symbols adds|
|
|
//| some formatting and output-related overhead. |
|
|
//| You may specify multiple symbols by separating them with commas: |
|
|
//| > |
|
|
//| >CAlglib::Trace_File("AGS,PREC.F6", "path/to/trace.log") |
|
|
//| > |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSSetAlgoAGS(CMinNSState &State,double radius,double penalty)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(radius),__FUNCTION__+": Radius is not finite"))
|
|
return;
|
|
if(!CAp::Assert(radius>0.0,__FUNCTION__+": Radius<=0"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(penalty),__FUNCTION__+": Penalty is not finite"))
|
|
return;
|
|
if(!CAp::Assert(penalty>=0.0,__FUNCTION__+": Penalty<0"))
|
|
return;
|
|
|
|
State.m_agsrhononlinear=penalty;
|
|
State.m_agsradius=radius;
|
|
State.m_solvertype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function turns on / off reporting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NeedXRep - whether iteration reports are needed or not |
|
|
//| If NeedXRep is True, algorithm will call rep() callback function |
|
|
//| if it is provided to MinNSOptimize(). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSSetXRep(CMinNSState &State,bool needxrep)
|
|
{
|
|
State.m_xrep=needxrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine submits request for termination of running |
|
|
//| optimizer. It should be called from user-supplied callback when |
|
|
//| user decides that it is time to "smoothly" terminate optimization|
|
|
//| process. As result, optimizer stops at point which was "current |
|
|
//| accepted" when termination request was submitted and returns |
|
|
//| error code 8 (successful termination). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - optimizer structure |
|
|
//| NOTE: after request for termination optimizer may perform several|
|
|
//| additional calls to user-supplied callbacks. It does NOT |
|
|
//| guarantee to stop immediately - it just guarantees that |
|
|
//| these additional calls will be discarded later. |
|
|
//| NOTE: calling this function on optimizer which is NOT running |
|
|
//| will have no effect. |
|
|
//| NOTE: multiple calls to this function are possible. First call is|
|
|
//| counted, subsequent calls are silently ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSRequestTermination(CMinNSState &State)
|
|
{
|
|
State.m_userterminationneeded=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| NOTES: |
|
|
//| 1. This function has two different implementations: one which |
|
|
//| uses exact (analytical) user-supplied Jacobian, and one which |
|
|
//| uses only function vector and numerically differentiates |
|
|
//| function in order to obtain gradient. |
|
|
//| Depending on the specific function used to create optimizer |
|
|
//| object you should choose appropriate variant of |
|
|
//| MinNSOptimize() - one which accepts function AND Jacobian or |
|
|
//| one which accepts ONLY function. |
|
|
//| Be careful to choose variant of MinNSOptimize() which |
|
|
//| corresponds to your optimization scheme! Table below lists |
|
|
//| different combinations of callback (function/gradient) passed |
|
|
//| to MinNSOptimize() and specific function used to create |
|
|
//| optimizer. |
|
|
//| | USER PASSED TO MinNSOptimize() |
|
|
//| CREATED WITH | function only | function and gradient |
|
|
//| ------------------------------------------------------------ |
|
|
//| MinNSCreateF() | works FAILS |
|
|
//| MinNSCreate() | FAILS works |
|
|
//| Here "FAILS" denotes inappropriate combinations of optimizer |
|
|
//| creation function and MinNSOptimize() version. Attemps to use |
|
|
//| such combination will lead to exception. Either you did not pass |
|
|
//| gradient when it WAS needed or you passed gradient when it was |
|
|
//| NOT needed. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinNS::MinNSIteration(CMinNSState &State)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int n=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int ng=0;
|
|
int nh=0;
|
|
double v=0;
|
|
double xp=0;
|
|
double xm=0;
|
|
int i_=0;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
i=State.m_rstate.ia[0];
|
|
j=State.m_rstate.ia[1];
|
|
k=State.m_rstate.ia[2];
|
|
n=State.m_rstate.ia[3];
|
|
nec=State.m_rstate.ia[4];
|
|
nic=State.m_rstate.ia[5];
|
|
ng=State.m_rstate.ia[6];
|
|
nh=State.m_rstate.ia[7];
|
|
v=State.m_rstate.ra[0];
|
|
xp=State.m_rstate.ra[1];
|
|
xm=State.m_rstate.ra[2];
|
|
}
|
|
else
|
|
{
|
|
i=359;
|
|
j=-58;
|
|
k=-919;
|
|
n=-909;
|
|
nec=81;
|
|
nic=255;
|
|
ng=74;
|
|
nh=-788;
|
|
v=809;
|
|
xp=205;
|
|
xm=-838;
|
|
}
|
|
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
//--- Init
|
|
State.m_replcerr=0.0;
|
|
State.m_repnlcerr=0.0;
|
|
State.m_repterminationtype=0;
|
|
State.m_repinneriterationscount=0;
|
|
State.m_repouteriterationscount=0;
|
|
State.m_repnfev=0;
|
|
State.m_repvaridx=0;
|
|
State.m_repfuncidx=0;
|
|
State.m_userterminationneeded=false;
|
|
State.m_dbgncholesky=0;
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
ng=State.m_ng;
|
|
nh=State.m_nh;
|
|
ClearRequestFields(State);
|
|
//--- AGS solver
|
|
if(State.m_solvertype!=0)
|
|
{
|
|
label=4;
|
|
break;
|
|
}
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xbase,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fbase,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fm,1+ng+nh);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_fp,1+ng+nh);
|
|
}
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xscaled,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_rawg,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_meritg,n);
|
|
State.m_rstateags.ia.Resize(14);
|
|
ArrayResize(State.m_rstateags.ba,6);
|
|
State.m_rstateags.ra.Resize(11);
|
|
State.m_rstateags.stage=-1;
|
|
label=6;
|
|
break;
|
|
}
|
|
//--- main loop
|
|
while(label>=0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 6:
|
|
if(!AGSIteration(State))
|
|
{
|
|
label=7;
|
|
break;
|
|
}
|
|
State.m_xscaled=State.m_x;
|
|
UnscalePointBC(State,State.m_x);
|
|
//--- Numerical differentiation (if needed) - intercept NeedFiJ
|
|
//--- request and replace it by sequence of NeedFi requests
|
|
if(!(State.m_diffstep!=0.0 && State.m_needfij))
|
|
{
|
|
label=8;
|
|
break;
|
|
}
|
|
State.m_needfij=false;
|
|
State.m_needfi=true;
|
|
State.m_xbase=State.m_x;
|
|
State.m_rstate.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
State.m_fbase=State.m_fi;
|
|
State.m_repnfev++;
|
|
k=0;
|
|
case 10:
|
|
if(k>n-1)
|
|
{
|
|
label=12;
|
|
break;
|
|
}
|
|
v=State.m_xbase[k];
|
|
xm=v-State.m_diffstep*State.m_s[k];
|
|
xp=v+State.m_diffstep*State.m_s[k];
|
|
if(State.m_HasBndL[k] && xm<State.m_bndl[k])
|
|
xm=State.m_bndl[k];
|
|
if(State.m_HasBndU[k] && xp>State.m_bndu[k])
|
|
xp=State.m_bndu[k];
|
|
if(!CAp::Assert(xm<=xp,__FUNCTION__+": integrity check failed (3y634)"))
|
|
return(false);
|
|
if(xm==xp)
|
|
{
|
|
label=13;
|
|
break;
|
|
}
|
|
//--- Compute F(XM) and F(XP)
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Set(k,xm);
|
|
State.m_rstate.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_fm=State.m_fi;
|
|
State.m_x=State.m_xbase;
|
|
State.m_x.Set(k,xp);
|
|
State.m_rstate.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_fp=State.m_fi;
|
|
//--- Compute subgradient at XBase
|
|
CAblasF::RCopyMulVC(1+ng+nh,1/(xp-xm),State.m_fp,State.m_j,k);
|
|
CAblasF::RAddVC(1+ng+nh,-(1/(xp-xm)),State.m_fm,State.m_j,k);
|
|
State.m_repnfev+=2;
|
|
label=14;
|
|
break;
|
|
case 13:
|
|
CAblasF::RSetC(1+ng+nh,0.0,State.m_j,k);
|
|
case 14:
|
|
k++;
|
|
label=10;
|
|
break;
|
|
case 12:
|
|
//--- Restore previous values of fields and continue
|
|
State.m_x=State.m_xscaled;
|
|
State.m_fi=State.m_fbase;
|
|
State.m_needfi=false;
|
|
State.m_needfij=true;
|
|
label=9;
|
|
break;
|
|
case 8:
|
|
//--- Forward request to caller
|
|
State.m_rstate.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_repnfev++;
|
|
State.m_x=State.m_xscaled;
|
|
case 9:
|
|
//--- Postprocess Jacobian: scale and produce 'raw' and 'merit' functions
|
|
for(i=0; i<=ng+nh; i++)
|
|
CAblasF::RMergeMulVR(n,State.m_s,State.m_j,i);
|
|
State.m_rawf=State.m_fi[0];
|
|
State.m_meritf=State.m_fi[0];
|
|
State.m_rawg=State.m_j[0]+0;
|
|
State.m_meritg=State.m_j[0]+0;
|
|
for(i=0; i<=nec+nic-1; i++)
|
|
{
|
|
v=CAblasF::RDotVR(n,State.m_x,State.m_scaledcleic,i)-State.m_scaledcleic.Get(i,n);
|
|
if(i>=nec && v<0.0)
|
|
continue;
|
|
State.m_meritf+=State.m_rholinear*MathAbs(v);
|
|
CAblasF::RAddRV(n,State.m_rholinear*MathSign(v),State.m_scaledcleic,i,State.m_meritg);
|
|
}
|
|
for(i=1; i<=ng+nh; i++)
|
|
{
|
|
v=State.m_fi[i];
|
|
if(i<=ng && v==0.0)
|
|
continue;
|
|
if(i>ng && v<=0.0)
|
|
continue;
|
|
State.m_meritf+=State.m_agsrhononlinear*MathAbs(v);
|
|
CAblasF::RAddRV(n,State.m_agsrhononlinear*MathSign(v),State.m_j,i,State.m_meritg);
|
|
}
|
|
//--- Done
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
case 4:
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
//--- Saving State
|
|
result=true;
|
|
State.m_rstate.ia.Set(0,i);
|
|
State.m_rstate.ia.Set(1,j);
|
|
State.m_rstate.ia.Set(2,k);
|
|
State.m_rstate.ia.Set(3,n);
|
|
State.m_rstate.ia.Set(4,nec);
|
|
State.m_rstate.ia.Set(5,nic);
|
|
State.m_rstate.ia.Set(6,ng);
|
|
State.m_rstate.ia.Set(7,nh);
|
|
State.m_rstate.ra.Set(0,v);
|
|
State.m_rstate.ra.Set(1,xp);
|
|
State.m_rstate.ra.Set(2,xm);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| MinNS results |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[0..N - 1], solution |
|
|
//| Rep - optimization report. You should check |
|
|
//| Rep.TerminationType in order to distinguish |
|
|
//| successful termination from unsuccessful one: |
|
|
//| * -8 internal integrity control detected infinite or NAN |
|
|
//| values in function/gradient. Abnormal termination |
|
|
//| signalled. |
|
|
//| * -3 box constraints are inconsistent |
|
|
//| * -1 inconsistent parameters were passed: |
|
|
//| * penalty parameter for MinNSSetAlgoAGS() is zero, but|
|
|
//| we have nonlinear constraints set by MinNSSetNLC() |
|
|
//| * 2 sampling radius decreased below epsx |
|
|
//| * 7 stopping conditions are too stringent, further |
|
|
//| improvement is impossible, X contains best point found|
|
|
//| so far. |
|
|
//| * 8 User requested termination via |
|
|
//| MinNSRequestTermination() |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSResults(CMinNSState &State,CRowDouble &x,CMinNSReport &rep)
|
|
{
|
|
x.Resize(0);
|
|
MinNSResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Buffered implementation of MinNSResults() which uses pre- |
|
|
//| allocated buffer to store X[]. If buffer size is too small, it |
|
|
//| resizes buffer. It is intended to be used in the inner cycles of |
|
|
//| performance critical algorithms where array reallocation penalty |
|
|
//| is too large to be ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSResultsBuf(CMinNSState &State,CRowDouble &x,CMinNSReport &rep)
|
|
{
|
|
//--- copy
|
|
rep.m_iterationscount=State.m_repinneriterationscount;
|
|
rep.m_nfev=State.m_repnfev;
|
|
rep.m_varidx=State.m_repvaridx;
|
|
rep.m_funcidx=State.m_repfuncidx;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
rep.m_cerr=MathMax(State.m_replcerr,State.m_repnlcerr);
|
|
rep.m_lcerr=State.m_replcerr;
|
|
rep.m_nlcerr=State.m_repnlcerr;
|
|
|
|
if(State.m_repterminationtype>0)
|
|
x=State.m_xc;
|
|
else
|
|
x=vector<double>::Full(State.m_n,AL_NaN);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine restarts algorithm from new point. |
|
|
//| All optimization parameters (including constraints) are left |
|
|
//| unchanged. |
|
|
//| This function allows to solve multiple optimization problems |
|
|
//| (which must have same number of dimensions) without object |
|
|
//| reallocation penalty. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with |
|
|
//| MinNSCreate() call. |
|
|
//| X - new starting point. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSRestartFrom(CMinNSState &State,CRowDouble &x)
|
|
{
|
|
int n=State.m_n;
|
|
//--- First, check for errors in the inputs
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- Set XC
|
|
State.m_xstart=x;
|
|
//--- prepare RComm facilities
|
|
State.m_rstate.ia.Resize(8);
|
|
State.m_rstate.ra.Resize(3);
|
|
State.m_rstate.stage=-1;
|
|
ClearRequestFields(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Clears request fileds (to be sure that we don't forget to clear |
|
|
//| something) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::ClearRequestFields(CMinNSState &State)
|
|
{
|
|
State.m_needfi=false;
|
|
State.m_needfij=false;
|
|
State.m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal initialization subroutine. |
|
|
//| Sets default NLC solver with default criteria. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::MinNSInitInternal(int n,CRowDouble &x,double diffstep,
|
|
CMinNSState &State)
|
|
{
|
|
//--- create variables
|
|
CMatrixDouble c;
|
|
CRowInt ct;
|
|
|
|
State.m_agsinitstp=0.2;
|
|
State.m_agsstattold=MathSqrt(CMath::m_machineepsilon);
|
|
State.m_agsshortstpabs=1.0E-10;
|
|
State.m_agsshortstprel=0.75;
|
|
State.m_agsshortf=10*CMath::m_machineepsilon;
|
|
State.m_agsrhononlinear=0.0;
|
|
State.m_agsraddecay=0.2;
|
|
State.m_agsalphadecay=0.5;
|
|
State.m_agsdecrease=0.1;
|
|
State.m_agsmaxraddecays=50;
|
|
State.m_agsmaxbacktrack=20;
|
|
State.m_agsmaxbacktracknonfull=8;
|
|
State.m_agspenaltylevel=50.0;
|
|
State.m_agspenaltyincrease=100.0;
|
|
State.m_agsminupdate=MathMax(5,n/2);
|
|
State.m_agssamplesize=MathMax(2*n+1,State.m_agsminupdate+1);
|
|
State.m_agsshortlimit=4+State.m_agssamplesize/State.m_agsminupdate;
|
|
//--- Initialize other params
|
|
State.m_n=n;
|
|
State.m_diffstep=diffstep;
|
|
State.m_xstart=x;
|
|
State.m_xc=x;
|
|
State.m_bndl=vector<double>::Full(n,AL_NEGINF);
|
|
ArrayResize(State.m_HasBndL,n);
|
|
State.m_bndu=vector<double>::Full(n,AL_POSINF);
|
|
ArrayResize(State.m_HasBndU,n);
|
|
State.m_s=vector<double>::Ones(n);
|
|
State.m_xstart.Resize(n);
|
|
State.m_xc.Resize(n);
|
|
State.m_xn.Resize(n);
|
|
State.m_d.Resize(n);
|
|
State.m_x.Resize(n);
|
|
ArrayInitialize(State.m_HasBndL,false);
|
|
ArrayInitialize(State.m_HasBndU,false);
|
|
MinNSSetLC(State,c,ct,0);
|
|
MinNSSetNLC(State,0,0);
|
|
MinNSSetCond(State,0.0,0);
|
|
MinNSSetXRep(State,false);
|
|
MinNSSetAlgoAGS(State,0.1,1000.0);
|
|
MinNSRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs actual processing for AUL algorith. It |
|
|
//| expects that caller redirects its reverse communication requests |
|
|
//| NeedFiJ / XUpdated to external user who will provide analytic |
|
|
//| derivative (or handle reports about progress). |
|
|
//| In case external user does not have analytic derivative, it is |
|
|
//| responsibility of caller to intercept NeedFiJ request and replace|
|
|
//| it with appropriate numerical differentiation scheme. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinNS::AGSIteration(CMinNSState &State)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int n=0;
|
|
int nec=0;
|
|
int nic=0;
|
|
int ng=0;
|
|
int nh=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double radius0=0;
|
|
double radius=0;
|
|
int radiusdecays=0;
|
|
double alpha=0;
|
|
double recommendedstep=0;
|
|
double dhd=0;
|
|
double dnrminf=0;
|
|
double v=0;
|
|
double vv=0;
|
|
int maxsamplesize=0;
|
|
int cursamplesize=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
bool b=false;
|
|
bool alphadecreased=false;
|
|
int shortstepscnt=0;
|
|
int backtrackits=0;
|
|
int maxbacktrackits=0;
|
|
bool fullsample=false;
|
|
double currentf0=0;
|
|
bool dotrace=false;
|
|
bool dodetailedtrace=false;
|
|
bool dotracesample=false;
|
|
int i_=0;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstateags.stage>=0)
|
|
{
|
|
n=State.m_rstateags.ia[0];
|
|
nec=State.m_rstateags.ia[1];
|
|
nic=State.m_rstateags.ia[2];
|
|
ng=State.m_rstateags.ia[3];
|
|
nh=State.m_rstateags.ia[4];
|
|
i=State.m_rstateags.ia[5];
|
|
j=State.m_rstateags.ia[6];
|
|
k=State.m_rstateags.ia[7];
|
|
radiusdecays=State.m_rstateags.ia[8];
|
|
maxsamplesize=State.m_rstateags.ia[9];
|
|
cursamplesize=State.m_rstateags.ia[10];
|
|
shortstepscnt=State.m_rstateags.ia[11];
|
|
backtrackits=State.m_rstateags.ia[12];
|
|
maxbacktrackits=State.m_rstateags.ia[13];
|
|
b=State.m_rstateags.ba[0];
|
|
alphadecreased=State.m_rstateags.ba[1];
|
|
fullsample=State.m_rstateags.ba[2];
|
|
dotrace=State.m_rstateags.ba[3];
|
|
dodetailedtrace=State.m_rstateags.ba[4];
|
|
dotracesample=State.m_rstateags.ba[5];
|
|
radius0=State.m_rstateags.ra[0];
|
|
radius=State.m_rstateags.ra[1];
|
|
alpha=State.m_rstateags.ra[2];
|
|
recommendedstep=State.m_rstateags.ra[3];
|
|
dhd=State.m_rstateags.ra[4];
|
|
dnrminf=State.m_rstateags.ra[5];
|
|
v=State.m_rstateags.ra[6];
|
|
vv=State.m_rstateags.ra[7];
|
|
v0=State.m_rstateags.ra[8];
|
|
v1=State.m_rstateags.ra[9];
|
|
currentf0=State.m_rstateags.ra[10];
|
|
}
|
|
else
|
|
{
|
|
n=939;
|
|
nec=-526;
|
|
nic=763;
|
|
ng=-541;
|
|
nh=-698;
|
|
i=-900;
|
|
j=-318;
|
|
k=-940;
|
|
radiusdecays=1016;
|
|
maxsamplesize=-229;
|
|
cursamplesize=-536;
|
|
shortstepscnt=487;
|
|
backtrackits=-115;
|
|
maxbacktrackits=886;
|
|
b=false;
|
|
alphadecreased=false;
|
|
fullsample=true;
|
|
dotrace=true;
|
|
dodetailedtrace=true;
|
|
dotracesample=true;
|
|
radius0=922;
|
|
radius=-154;
|
|
alpha=306;
|
|
recommendedstep=-1011;
|
|
dhd=951;
|
|
dnrminf=-463;
|
|
v=88;
|
|
vv=-861;
|
|
v0=-678;
|
|
v1=-731;
|
|
currentf0=-675;
|
|
}
|
|
|
|
switch(State.m_rstateags.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
if(!CAp::Assert(State.m_solvertype==0,__FUNCTION__+": internal error"))
|
|
return(false);
|
|
n=State.m_n;
|
|
nec=State.m_nec;
|
|
nic=State.m_nic;
|
|
ng=State.m_ng;
|
|
nh=State.m_nh;
|
|
dotrace=CAp::IsTraceEnabled("AGS");
|
|
dodetailedtrace=dotrace && CAp::IsTraceEnabled("AGS.DETAILED");
|
|
dotracesample=dodetailedtrace && CAp::IsTraceEnabled("AGS.DETAILED.SAMPLE");
|
|
//--- Trace output (if needed)
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
CAp::Trace("//--- AGS SOLVER STARTED //\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
}
|
|
//--- Check consistency of parameters
|
|
if(ng+nh>0 && State.m_agsrhononlinear==0.0)
|
|
{
|
|
if(dotrace)
|
|
CAp::Trace("> inconsistent parameters detected,stopping\n\n");
|
|
State.m_repterminationtype=-1;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
//--- Allocate arrays.
|
|
CApServ::RVectorSetLengthAtLeast(State.m_colmax,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_diagh,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_signmin,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_signmax,n);
|
|
maxsamplesize=State.m_agssamplesize;
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_samplex,maxsamplesize+1,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_samplegm,maxsamplesize+1,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_samplegmbc,maxsamplesize+1,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_samplef,maxsamplesize+1);
|
|
//--- Prepare optimizer
|
|
State.m_tmp0=vector<double>::Zeros(maxsamplesize);
|
|
State.m_tmp1=vector<double>::Full(maxsamplesize,AL_POSINF);
|
|
CApServ::IVectorSetLengthAtLeast(State.m_tmp3,1);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_tmp2,1,maxsamplesize+1);
|
|
//--- Prepare RNG, seed it with fixed values so
|
|
//--- that each run on same problem yeilds same results
|
|
CHighQualityRand::HQRndSeed(7235,98532,State.m_agsrs);
|
|
//--- Prepare initial point subject to current bound constraints and
|
|
//--- perform scaling of bound constraints, linear constraints, point itself
|
|
CApServ::RVectorSetLengthAtLeast(State.m_scaledbndl,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_scaledbndu,n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Check and scale constraints
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i] && State.m_bndu[i]<State.m_bndl[i])
|
|
{
|
|
if(dotrace)
|
|
CAp::Trace("> inconsistent box constraints detected,stopping\n\n");
|
|
State.m_repterminationtype=-3;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(State.m_HasBndL[i])
|
|
State.m_scaledbndl.Set(i,State.m_bndl[i]/State.m_s[i]);
|
|
else
|
|
State.m_scaledbndl.Set(i,AL_NEGINF);
|
|
if(State.m_HasBndU[i])
|
|
State.m_scaledbndu.Set(i,State.m_bndu[i]/State.m_s[i]);
|
|
else
|
|
State.m_scaledbndu.Set(i,AL_POSINF);
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i])
|
|
{
|
|
if(!CAp::Assert(State.m_scaledbndl[i]<=State.m_scaledbndu[i],__FUNCTION__+": integrity check failed (dfdf)"))
|
|
return(false);
|
|
}
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i] && State.m_bndl[i]==State.m_bndu[i])
|
|
{
|
|
if(!CAp::Assert(State.m_scaledbndl[i]==State.m_scaledbndu[i],__FUNCTION__+": integrity check failed (dsgh)"))
|
|
return(false);
|
|
}
|
|
//--- Scale and constrain point
|
|
State.m_xc.Set(i,State.m_xstart[i]);
|
|
if(State.m_HasBndL[i] && State.m_xc[i]<=State.m_bndl[i])
|
|
{
|
|
State.m_xc.Set(i,State.m_scaledbndl[i]);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndU[i] && State.m_xc[i]>=State.m_bndu[i])
|
|
{
|
|
State.m_xc.Set(i,State.m_scaledbndu[i]);
|
|
continue;
|
|
}
|
|
State.m_xc.Mul(i,1.0/State.m_s[i]);
|
|
if(State.m_HasBndL[i] && State.m_xc[i]<=State.m_scaledbndl[i])
|
|
State.m_xc.Set(i,State.m_scaledbndl[i]);
|
|
if(State.m_HasBndU[i] && State.m_xc[i]>=State.m_scaledbndu[i])
|
|
State.m_xc.Set(i,State.m_scaledbndu[i]);
|
|
}
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_scaledcleic,nec+nic,n+1);
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
//--- Scale and normalize linear constraints
|
|
vv=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=State.m_cleic.Get(i,j)*State.m_s[j];
|
|
State.m_scaledcleic.Set(i,j,v);
|
|
vv+=v*v;
|
|
}
|
|
vv=MathSqrt(vv);
|
|
State.m_scaledcleic.Set(i,n,State.m_cleic.Get(i,n));
|
|
if(vv>0.0)
|
|
State.m_scaledcleic.Row(i,State.m_scaledcleic[i]/vv);
|
|
}
|
|
//--- Main cycle
|
|
//--- We maintain several variables during iteration:
|
|
//--- * RecommendedStep- current estimate of recommended step length;
|
|
//--- must be Radius0 on first entry
|
|
//--- * Radius - current sampling radius
|
|
//--- * CurSampleSize - current sample size (may change in future versions)
|
|
//--- * FullSample - whether we have full sample, or only partial one
|
|
//--- * RadiusDecays - total number of decreases performed for sampling radius
|
|
radius=State.m_agsradius;
|
|
radius0=radius;
|
|
recommendedstep=MathMin(radius0,State.m_agsinitstp);
|
|
cursamplesize=1;
|
|
radiusdecays=0;
|
|
shortstepscnt=0;
|
|
fullsample=false;
|
|
State.m_rholinear=0.0;
|
|
label=4;
|
|
break;
|
|
}
|
|
//--- main loop
|
|
while(label>=0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 4:
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("\n=== ITERATION %5d STARTED ========================================================================\n",State.m_repinneriterationscount));
|
|
//--- First phase of iteration - central point:
|
|
//--- 1. evaluate function at central point - first entry in sample.
|
|
//--- Its status is ignored, it is always recalculated.
|
|
//--- 2. report point and check gradient/function value for NAN/INF
|
|
//--- 3. check penalty coefficients for linear terms; increase them
|
|
//--- if directional derivative of function being optimized (not
|
|
//--- merit function!) is larger than derivative of penalty.
|
|
//--- 4. update report on constraint violation
|
|
cursamplesize=MathMax(cursamplesize,1);
|
|
State.m_samplex.Row(0,State.m_xc);
|
|
State.m_x=State.m_xc;
|
|
ClearRequestFields(State);
|
|
State.m_needfij=true;
|
|
State.m_rstateags.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
State.m_needfij=false;
|
|
currentf0=State.m_rawf;
|
|
State.m_replcerr=0.0;
|
|
for(i=0; i<nec+nic; i++)
|
|
{
|
|
v=-State.m_scaledcleic.Get(i,n);
|
|
for(j=0; j<n; j++)
|
|
v+=State.m_scaledcleic.Get(i,j)*State.m_xc[j];
|
|
if(i>=nec && v<=0.0)
|
|
continue;
|
|
State.m_replcerr=MathMax(State.m_replcerr,MathAbs(v));
|
|
}
|
|
State.m_repnlcerr=0.0;
|
|
for(i=1; i<=ng+nh; i++)
|
|
{
|
|
v=State.m_fi[i];
|
|
if(i>ng && v<=0.0)
|
|
continue;
|
|
State.m_repnlcerr=MathMax(State.m_repnlcerr,MathAbs(v));
|
|
}
|
|
State.m_samplef.Set(0,State.m_meritf);
|
|
CAblasF::RCopyVR(n,State.m_meritg,State.m_samplegm,0);
|
|
if(!State.m_xrep)
|
|
{
|
|
label=6;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xc;
|
|
State.m_f=currentf0;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstateags.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_xupdated=false;
|
|
case 6:
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
if(dotrace)
|
|
CAp::Trace("> termination requested by user\n\n");
|
|
State.m_repterminationtype=8;
|
|
label=5;
|
|
break;
|
|
}
|
|
v=0;
|
|
for(i=0; i<n; i++)
|
|
v+=CMath::Sqr(State.m_samplegm.Get(0,i));
|
|
if(!MathIsValidNumber(v) || !MathIsValidNumber(State.m_samplef[0]))
|
|
{
|
|
//--- Abnormal termination - infinities in function/gradient
|
|
if(dotrace)
|
|
CAp::Trace("> NAN/INF detected in function/gradient,termination\n\n");
|
|
State.m_repterminationtype=-8;
|
|
label=5;
|
|
break;
|
|
}
|
|
if(!CAp::Assert(State.m_agspenaltylevel>1.0,__FUNCTION__+": integrity error"))
|
|
return(false);
|
|
if(!CAp::Assert(State.m_agspenaltyincrease>State.m_agspenaltylevel,__FUNCTION__+": integrity error"))
|
|
return(false);
|
|
if(MathSqrt(CAblasF::RDotV2(n,State.m_rawg))*State.m_agspenaltylevel>State.m_rholinear)
|
|
{
|
|
State.m_rholinear=MathSqrt(CAblasF::RDotV2(n,State.m_rawg))*State.m_agspenaltyincrease;
|
|
if(dotrace)
|
|
CAp::Trace("> penalty parameter needs increase,iteration restarted\n\n");
|
|
cursamplesize=0;
|
|
label=4;
|
|
break;
|
|
}
|
|
//--- Trace if needed
|
|
if(dotrace)
|
|
{
|
|
if(dodetailedtrace)
|
|
{
|
|
CAp::Trace("> printing raw data (prior to applying variable and function scales)\n");
|
|
CAp::Trace("X (raw) = ");
|
|
CApServ::TraceVectoRunScaledUnshiftedAutopRec(State.m_xc,n,State.m_s,true,State.m_s,false);
|
|
CAp::Trace("\n");
|
|
CAp::Trace("> printing scaled data (after applying variable and function scales)\n");
|
|
CAp::Trace("X (scaled) = ");
|
|
CApServ::TraceVectorAutopRec(State.m_xc,0,n);
|
|
CAp::Trace("\n");
|
|
}
|
|
CAp::Trace(StringFormat("sampleRad = %.3E\n",radius));
|
|
CAp::Trace(StringFormat("lin.violation = %.3E (scaled violation of linear constraints)\n",State.m_replcerr));
|
|
CAp::Trace(StringFormat("nlc.violation = %.3E (scaled violation of nonlinear constraints)\n",State.m_repnlcerr));
|
|
CAp::Trace(StringFormat("targetF = %.3E (target function)\n",currentf0));
|
|
CAp::Trace(StringFormat("meritF = %.3E (merit function)\n",State.m_samplef[0]));
|
|
CAp::Trace(StringFormat("Rho linear = %.3E\n",State.m_rholinear));
|
|
CAp::Trace(StringFormat("Rho nonlinear = %.3E\n",State.m_agsrhononlinear));
|
|
CAp::Trace("----------------------------------------------------------------------------------------------------\n");
|
|
}
|
|
//--- Check stopping conditions.
|
|
if(radiusdecays>=State.m_agsmaxraddecays)
|
|
{
|
|
//--- Too many attempts to decrease radius
|
|
if(dotrace)
|
|
CAp::Trace("> stopping condition met: too many attempts to decrease radius\n\n");
|
|
State.m_repterminationtype=7;
|
|
label=5;
|
|
break;
|
|
}
|
|
if(State.m_repinneriterationscount>=State.m_maxits && State.m_maxits>0)
|
|
{
|
|
//--- Too many iterations
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stopping condition met: %d iterations performed\n\n",State.m_repinneriterationscount));
|
|
State.m_repterminationtype=5;
|
|
label=5;
|
|
break;
|
|
}
|
|
if(radius<=(State.m_epsx*State.m_agsraddecay))
|
|
{
|
|
//--- Radius is smaller than required step tolerance multiplied by radius decay.
|
|
//--- Additional decay is required in order to make sure that optimization session
|
|
//--- with radius equal to EpsX was successfully done.
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stopping condition met: sampling radius is smaller than %.3E\n\n",State.m_epsx));
|
|
State.m_repterminationtype=2;
|
|
label=5;
|
|
break;
|
|
}
|
|
//--- Update sample:
|
|
//--- 1. invalidate entries which are too far away from XC
|
|
//--- and move all valid entries to beginning of the sample.
|
|
//--- 2. add new entries until we have AGSSampleSize
|
|
//--- items in our sample. We remove oldest entries from
|
|
//--- sample until we have enough place to add at least
|
|
//--- AGSMinUpdate items.
|
|
//--- 3. prepare "modified" gradient sample with respect to
|
|
//--- boundary constraints.
|
|
if(!CAp::Assert(cursamplesize>=1,__FUNCTION__+": integrity check failed (2367)"))
|
|
return(false);
|
|
k=1;
|
|
for(i=1; i<cursamplesize; i++)
|
|
{
|
|
//--- If entry is outside of Radius-ball around XC, discard it.
|
|
v=0.0;
|
|
for(j=0; j<n; j++)
|
|
v=MathMax(v,MathAbs(State.m_samplex.Get(i,j)-State.m_xc[j]));
|
|
if(v>radius)
|
|
continue;
|
|
//--- Move to the beginning
|
|
CAblasF::RCopyRR(n,State.m_samplex,i,State.m_samplex,k);
|
|
CAblasF::RCopyRR(n,State.m_samplegm,i,State.m_samplegm,k);
|
|
State.m_samplef.Set(k,State.m_samplef[i]);
|
|
k++;
|
|
}
|
|
cursamplesize=k;
|
|
if(State.m_agssamplesize-cursamplesize<State.m_agsminupdate)
|
|
{
|
|
//--- Remove oldest entries
|
|
k=State.m_agsminupdate-(State.m_agssamplesize-cursamplesize);
|
|
if(!CAp::Assert(k<=cursamplesize-1,__FUNCTION__+": integrity check failed (2662)"))
|
|
return(false);
|
|
for(i=1; i<cursamplesize-k; i++)
|
|
{
|
|
CAblasF::RCopyRR(n,State.m_samplex,i+k,State.m_samplex,i);
|
|
CAblasF::RCopyRR(n,State.m_samplegm,i+k,State.m_samplegm,i);
|
|
State.m_samplef.Set(i,State.m_samplef[i+k]);
|
|
}
|
|
cursamplesize=cursamplesize-k;
|
|
}
|
|
k=0;
|
|
i=cursamplesize;
|
|
case 8:
|
|
if(i>MathMin(cursamplesize+State.m_agsminupdate,State.m_agssamplesize)-1)
|
|
{
|
|
label=10;
|
|
break;
|
|
}
|
|
for(j=0; j<n; j++)
|
|
{
|
|
//--- Undistorted position
|
|
State.m_samplex.Set(i,j,State.m_xc[j]);
|
|
//--- Do not apply distortion if the variable is fixed
|
|
if(State.m_HasBndL[j] && State.m_HasBndU[j] && State.m_scaledbndl[j]==State.m_scaledbndu[j])
|
|
continue;
|
|
//--- Apply distortion
|
|
if(CHighQualityRand::HQRndUniformR(State.m_agsrs)>=0.5)
|
|
{
|
|
//--- Sample at the left side with 50% probability
|
|
v0=State.m_samplex.Get(i,j)-radius;
|
|
v1=State.m_samplex.Get(i,j);
|
|
if(State.m_HasBndL[j])
|
|
v0=MathMax(State.m_scaledbndl[j],v0);
|
|
}
|
|
else
|
|
{
|
|
//--- Sample at the right side with 50% probability
|
|
v0=State.m_samplex.Get(i,j);
|
|
v1=State.m_samplex.Get(i,j)+radius;
|
|
if(State.m_HasBndU[j])
|
|
v1=MathMin(State.m_scaledbndu[j],v1);
|
|
}
|
|
if(!CAp::Assert(v1>=v0,__FUNCTION__+": integrity check failed (9743)"))
|
|
return(false);
|
|
State.m_samplex.Set(i,j,CApServ::BoundVal(v0+(v1-v0)*CHighQualityRand::HQRndUniformR(State.m_agsrs),v0,v1));
|
|
}
|
|
State.m_x=State.m_samplex[i]+0;
|
|
ClearRequestFields(State);
|
|
State.m_needfij=true;
|
|
State.m_rstateags.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_needfij=false;
|
|
State.m_samplef.Set(i,State.m_meritf);
|
|
CAblasF::RCopyVR(n,State.m_meritg,State.m_samplegm,i);
|
|
k++;
|
|
i++;
|
|
label=8;
|
|
break;
|
|
case 10:
|
|
cursamplesize+=k;
|
|
fullsample=cursamplesize==State.m_agssamplesize;
|
|
for(j=0; j<cursamplesize; j++)
|
|
{
|
|
//--- For J-th element in gradient sample, process all of its components
|
|
//--- and modify them according to status of box constraints
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!CAp::Assert(!State.m_HasBndL[i] || State.m_xc[i]>=State.m_scaledbndl[i],__FUNCTION__+": integrity error"))
|
|
return(false);
|
|
if(!CAp::Assert(!State.m_HasBndU[i] || State.m_xc[i]<=State.m_scaledbndu[i],__FUNCTION__+": integrity error"))
|
|
return(false);
|
|
State.m_samplegmbc.Set(j,i,State.m_samplegm.Get(j,i));
|
|
if(State.m_HasBndL[i] && State.m_HasBndU[i] && State.m_scaledbndl[i]==State.m_scaledbndu[i])
|
|
{
|
|
//--- I-th box constraint is of equality type (lower bound matches upper one).
|
|
//--- Simplest case, always active.
|
|
State.m_samplegmbc.Set(j,i,0.0);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndL[i] && State.m_xc[i]==State.m_scaledbndl[i])
|
|
{
|
|
//--- We are at lower bound: activate/deactivate constraint depending on gradient at XC
|
|
if(State.m_samplegm.Get(0,i)>=0.0)
|
|
State.m_samplegmbc.Set(j,i,0.0);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndU[i] && State.m_xc[i]==State.m_scaledbndu[i])
|
|
{
|
|
//--- We are at upper bound: activate/deactivate constraint depending on gradient at XC
|
|
if(State.m_samplegm.Get(0,i)<=0.0)
|
|
State.m_samplegmbc.Set(j,i,0.0);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if(dotracesample)
|
|
{
|
|
CAp::Trace("> gradient sample\n");
|
|
for(i=0; i<=cursamplesize-1; i++)
|
|
{
|
|
CAp::Trace("SampleGrad[] = ");
|
|
CApServ::TraceRowAutopRec(State.m_samplegmbc,i,0,n);
|
|
CAp::Trace("\n");
|
|
}
|
|
}
|
|
//--- Calculate diagonal Hessian.
|
|
//--- This Hessian serves two purposes:
|
|
//--- * first, it improves performance of gradient descent step
|
|
//--- * second, it improves condition number of QP subproblem
|
|
//--- solved to determine step
|
|
//--- The idea is that for each variable we check whether sample
|
|
//--- includes entries with alternating sign of gradient:
|
|
//--- * if gradients with different signs are present, Hessian
|
|
//--- component is set to M/R, where M is a maximum magnitude
|
|
//--- of corresponding gradient component, R is a sampling radius.
|
|
//--- Note that sign=0 and sign=1 are treated as different ones
|
|
//--- * if all gradients have same sign, Hessian component is
|
|
//--- set to M/R0, where R0 is initial sampling radius.
|
|
State.m_colmax.Fill(0.0);
|
|
State.m_signmin.Fill(1);
|
|
State.m_signmax.Fill(-1);
|
|
for(i=0; i<cursamplesize; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=State.m_samplegmbc.Get(i,j);
|
|
State.m_colmax.Set(j,MathMax(State.m_colmax[j],MathAbs(v)));
|
|
State.m_signmin.Set(j,MathMin(State.m_signmin[j],MathSign(v)));
|
|
State.m_signmax.Set(j,MathMax(State.m_signmax[j],MathSign(v)));
|
|
}
|
|
}
|
|
for(j=0; j<n; j++)
|
|
{
|
|
if(State.m_signmin[j]!=State.m_signmax[j])
|
|
{
|
|
//--- Alternating signs of gradient - step is proportional to current sampling radius
|
|
if(!CAp::Assert(State.m_colmax[j]!=0.0,__FUNCTION__+": integrity check failed (2975)"))
|
|
return(false);
|
|
if(!CAp::Assert(radius!=0.0,__FUNCTION__+": integrity check failed (8473)"))
|
|
return(false);
|
|
State.m_diagh.Set(j,State.m_colmax[j]/radius);
|
|
continue;
|
|
}
|
|
if(State.m_colmax[j]!=0.0)
|
|
{
|
|
//--- Non-alternating sign of gradient, but non-zero.
|
|
//--- Step is proportional to recommended step
|
|
if(!CAp::Assert(recommendedstep!=0.0,__FUNCTION__+": integrity check failed (3274)"))
|
|
return(false);
|
|
State.m_diagh.Set(j,State.m_colmax[j]/recommendedstep);
|
|
continue;
|
|
}
|
|
State.m_diagh.Set(j,1);
|
|
}
|
|
if(dodetailedtrace)
|
|
{
|
|
CAp::Trace("> diagonal quasi-Hessian\n");
|
|
CAp::Trace("H = ");
|
|
CApServ::TraceVectorAutopRec(State.m_diagh,0,n);
|
|
CAp::Trace("\n");
|
|
}
|
|
//--- PROJECTION PHASE
|
|
//--- We project zero vector on convex hull of gradient sample.
|
|
//--- If projection is small enough, we decrease radius and restart.
|
|
//--- Otherwise, this phase returns search direction in State.D.
|
|
//--- NOTE: because we use iterative solver, it may have trouble
|
|
//--- dealing with ill-conditioned problems. So we also employ
|
|
//--- second, backup test for stationarity - when too many
|
|
//--- subsequent backtracking searches resulted in short steps.
|
|
SolveQP(State.m_samplegmbc,State.m_diagh,cursamplesize,n,State.m_tmp0,State.m_dbgncholesky,State.m_nsqp);
|
|
State.m_d.Fill(0.0);
|
|
for(i=0; i<cursamplesize; i++)
|
|
{
|
|
v=State.m_tmp0[i];
|
|
for(i_=0; i_<n; i_++)
|
|
State.m_d.Add(i_,State.m_samplegmbc.Get(i,i_)*v);
|
|
}
|
|
v=0.0;
|
|
for(j=0; j<n; j++)
|
|
v=MathMax(v,MathAbs(State.m_d[j]/CApServ::Coalesce(State.m_colmax[j],1.0)));
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> stationarity test:\n|proj(0)| = %.3E (projection of zero vector on convex hull of gradient sample)\n",v));
|
|
if(v<=State.m_agsstattold)
|
|
{
|
|
//--- Stationarity test succeeded.
|
|
//--- Decrease radius and restart.
|
|
//--- NOTE: we also clear ShortStepsCnt on restart
|
|
if(dotrace)
|
|
CAp::Trace("> stationarity test satisfied,decreasing radius\n");
|
|
radius*=State.m_agsraddecay;
|
|
shortstepscnt=0;
|
|
radiusdecays++;
|
|
State.m_repinneriterationscount++;
|
|
label=4;
|
|
break;
|
|
}
|
|
for(i=0; i<n; i++)
|
|
State.m_d.Mul(i,(-1.0)/State.m_diagh[i]);
|
|
//--- Perform backtracking line search.
|
|
//--- Update initial step length depending on search results.
|
|
//--- Here we assume that D is non-zero.
|
|
//--- NOTE: if AGSShortLimit subsequent line searches resulted
|
|
//--- in steps shorter than AGSStatTolStp, we decrease radius.
|
|
dhd=0;
|
|
for(i=0; i<n; i++)
|
|
dhd+=State.m_diagh[i]*CMath::Sqr(State.m_d[i]);
|
|
dnrminf=CAblasF::RMaxAbsV(n,State.m_d);
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace(StringFormat("> search direction is ready:\n|D| = %.3E (inf-norm)\n(D,grad) = %.3E\n",dnrminf,CAblasF::RDotVR(n,State.m_d,State.m_samplegmbc,0)));
|
|
if(dodetailedtrace)
|
|
{
|
|
CAp::Trace("D = ");
|
|
CApServ::TraceVectorAutopRec(State.m_d,0,n);
|
|
CAp::Trace("\n");
|
|
}
|
|
}
|
|
if(!CAp::Assert(dnrminf>0.0,__FUNCTION__+": integrity error (2752)"))
|
|
return(false);
|
|
alpha=recommendedstep/dnrminf;
|
|
alphadecreased=false;
|
|
backtrackits=0;
|
|
if(fullsample)
|
|
maxbacktrackits=State.m_agsmaxbacktrack;
|
|
else
|
|
maxbacktrackits=State.m_agsmaxbacktracknonfull;
|
|
case 11:
|
|
//--- Prepare XN and evaluate merit function at XN
|
|
State.m_xn=State.m_xc.ToVector()+State.m_d*alpha;
|
|
COptServ::EnforceBoundaryConstraints(State.m_xn,State.m_scaledbndl,State.m_HasBndL,State.m_scaledbndu,State.m_HasBndU,n,0);
|
|
State.m_samplex.Row(maxsamplesize,State.m_xn);
|
|
State.m_x=State.m_xn;
|
|
ClearRequestFields(State);
|
|
State.m_needfij=true;
|
|
State.m_rstateags.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_needfij=false;
|
|
State.m_samplef.Set(maxsamplesize,State.m_meritf);
|
|
State.m_samplegm.Row(maxsamplesize,State.m_meritg);
|
|
//--- Check sufficient decrease condition
|
|
if(!CAp::Assert(dnrminf>0.0,__FUNCTION__+": integrity error (9642)"))
|
|
return(false);
|
|
if(State.m_samplef[maxsamplesize]<=(State.m_samplef[0]-alpha*State.m_agsdecrease*dhd))
|
|
{
|
|
label=12;
|
|
break;
|
|
}
|
|
//--- Decrease Alpha
|
|
alpha*=State.m_agsalphadecay;
|
|
alphadecreased=true;
|
|
//--- Update and check iterations counter.
|
|
backtrackits++;
|
|
if(backtrackits>=maxbacktrackits)
|
|
{
|
|
//--- Too many backtracking searches performed without success.
|
|
//--- Terminate iterations.
|
|
alpha=0.0;
|
|
alphadecreased=true;
|
|
State.m_xn=State.m_xc;
|
|
label=12;
|
|
break;
|
|
}
|
|
label=11;
|
|
break;
|
|
case 12:
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> backtracking line search finished:\nstp = %.3E\n",alpha));
|
|
if((alpha*dnrminf)<=State.m_agsshortstpabs || (alpha*dnrminf)<=(State.m_agsshortstprel*radius) || MathAbs(State.m_samplef[0]-State.m_samplef[maxsamplesize])<=State.m_agsshortf)
|
|
shortstepscnt++;
|
|
else
|
|
shortstepscnt=0;
|
|
if(shortstepscnt>=State.m_agsshortlimit)
|
|
{
|
|
//--- Too many subsequent short steps.
|
|
//--- It may be possible that optimizer is unable to find out
|
|
//--- that we have to decrease radius because of ill-conditioned
|
|
//--- gradients.
|
|
//--- Decrease radius and restart.
|
|
if(dotrace)
|
|
CAp::Trace("> too many subsequent short steps,decreasing radius\n");
|
|
radius*=State.m_agsraddecay;
|
|
shortstepscnt=0;
|
|
radiusdecays++;
|
|
State.m_repinneriterationscount++;
|
|
label=4;
|
|
break;
|
|
}
|
|
if(!alphadecreased)
|
|
recommendedstep*=2.0;
|
|
if(alphadecreased && fullsample)
|
|
recommendedstep*=0.5;
|
|
//--- Next iteration
|
|
State.m_xc=State.m_xn;
|
|
State.m_repinneriterationscount++;
|
|
label=4;
|
|
break;
|
|
case 5:
|
|
//--- Convert back from scaled to unscaled representation
|
|
UnscalePointBC(State,State.m_xc);
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
//--- Saving State
|
|
result=true;
|
|
State.m_rstateags.ba[0]=b;
|
|
State.m_rstateags.ba[1]=alphadecreased;
|
|
State.m_rstateags.ba[2]=fullsample;
|
|
State.m_rstateags.ba[3]=dotrace;
|
|
State.m_rstateags.ba[4]=dodetailedtrace;
|
|
State.m_rstateags.ba[5]=dotracesample;
|
|
State.m_rstateags.ia.Set(0,n);
|
|
State.m_rstateags.ia.Set(1,nec);
|
|
State.m_rstateags.ia.Set(2,nic);
|
|
State.m_rstateags.ia.Set(3,ng);
|
|
State.m_rstateags.ia.Set(4,nh);
|
|
State.m_rstateags.ia.Set(5,i);
|
|
State.m_rstateags.ia.Set(6,j);
|
|
State.m_rstateags.ia.Set(7,k);
|
|
State.m_rstateags.ia.Set(8,radiusdecays);
|
|
State.m_rstateags.ia.Set(9,maxsamplesize);
|
|
State.m_rstateags.ia.Set(10,cursamplesize);
|
|
State.m_rstateags.ia.Set(11,shortstepscnt);
|
|
State.m_rstateags.ia.Set(12,backtrackits);
|
|
State.m_rstateags.ia.Set(13,maxbacktrackits);
|
|
State.m_rstateags.ra.Set(0,radius0);
|
|
State.m_rstateags.ra.Set(1,radius);
|
|
State.m_rstateags.ra.Set(2,alpha);
|
|
State.m_rstateags.ra.Set(3,recommendedstep);
|
|
State.m_rstateags.ra.Set(4,dhd);
|
|
State.m_rstateags.ra.Set(5,dnrminf);
|
|
State.m_rstateags.ra.Set(6,v);
|
|
State.m_rstateags.ra.Set(7,vv);
|
|
State.m_rstateags.ra.Set(8,v0);
|
|
State.m_rstateags.ra.Set(9,v1);
|
|
State.m_rstateags.ra.Set(10,currentf0);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs transformation of X from scaled |
|
|
//| coordinates to unscaled ones, paying special attention to box |
|
|
//| constraints: |
|
|
//| * points which were exactly at the boundary before scaling will|
|
|
//| be mapped to corresponding boundary after scaling |
|
|
//| * in any case, unscaled box constraints will be satisfied |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::UnscalePointBC(CMinNSState &State,CRowDouble &x)
|
|
{
|
|
for(int i=0; i<State.m_n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && x[i]<=State.m_scaledbndl[i])
|
|
{
|
|
x.Set(i,State.m_bndl[i]);
|
|
continue;
|
|
}
|
|
if(State.m_HasBndU[i] && x[i]>=State.m_scaledbndu[i])
|
|
{
|
|
x.Set(i,State.m_bndu[i]);
|
|
continue;
|
|
}
|
|
x.Mul(i,State.m_s[i]);
|
|
if(State.m_HasBndL[i] && x[i]<=State.m_bndl[i])
|
|
x.Set(i,State.m_bndl[i]);
|
|
if(State.m_HasBndU[i] && x[i]>=State.m_bndu[i])
|
|
x.Set(i,State.m_bndu[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves QP problem of the form |
|
|
//| [ ] |
|
|
//| min [0.5 * c'*(G*inv(H)*G')*c ] s.t. c[i] >= 0, SUM(c[i]) = 1.0 |
|
|
//| [ ] |
|
|
//| where G is stored in SampleG[] array, diagonal H is stored in |
|
|
//| DiagH[]. |
|
|
//| DbgNCholesky is incremented every time we perform Cholesky |
|
|
//| decomposition. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::SolveQP(CMatrixDouble &sampleg,CRowDouble &diagh,
|
|
int nsample,int nvars,CRowDouble &coeffs,
|
|
int &dbgncholesky,CMinNSQP &State)
|
|
{
|
|
//--- create variables
|
|
int n=nsample;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
double vv=0;
|
|
int idx0=0;
|
|
int idx1=0;
|
|
int ncandbnd=0;
|
|
int innerits=0;
|
|
int outerits=0;
|
|
double dnrm=0;
|
|
double stp=0;
|
|
double stpmax=0;
|
|
int actidx=0;
|
|
double dtol=0;
|
|
bool kickneeded=false;
|
|
double kicklength=0;
|
|
double lambdav=0;
|
|
double maxdiag=0;
|
|
bool wasactivation=false;
|
|
bool werechanges=false;
|
|
int termcnt=0;
|
|
int i_=0;
|
|
//--- Allocate arrays, prepare data
|
|
coeffs=vector<double>::Full(n,1.0/(double)n);
|
|
State.m_xc=vector<double>::Full(n,1.0/(double)n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xn,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_x0,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_gc,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_d,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_uh,n,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_ch,n,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_rk,nsample,nvars);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_invutc,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_tmp0,n);
|
|
ArrayResize(State.m_tmpb,n);
|
|
for(i=0; i<nsample; i++)
|
|
{
|
|
for(j=0; j<nvars; j++)
|
|
State.m_rk.Set(i,j,sampleg.Get(i,j)/MathSqrt(diagh[j]));
|
|
}
|
|
CAblas::RMatrixSyrk(nsample,nvars,1.0,State.m_rk,0,0,0,0.0,State.m_uh,0,0,true);
|
|
maxdiag=(State.m_uh.Diag()+0).Max();
|
|
maxdiag=CApServ::Coalesce(maxdiag,1.0);
|
|
//--- Main cycle:
|
|
innerits=0;
|
|
outerits=0;
|
|
dtol=1.0E5*CMath::m_machineepsilon;
|
|
kicklength=CMath::m_machineepsilon;
|
|
lambdav=1.0E5*CMath::m_machineepsilon;
|
|
termcnt=0;
|
|
while(true)
|
|
{
|
|
//--- Save current point to X0
|
|
State.m_x0=State.m_xc;
|
|
//--- Calculate gradient at initial point, solve NNLS problem
|
|
//--- to determine descent direction D subject to constraints.
|
|
//--- In order to do so we solve following constrained
|
|
//--- minimization problem:
|
|
//--- ( )^2
|
|
//--- min ( SUM(lambda[i]*A[i]) + G )
|
|
//--- ( )
|
|
//--- Here:
|
|
//--- * G is a gradient (column vector)
|
|
//--- * A[i] is a column vector of I-th constraint
|
|
//--- * lambda[i] is a Lagrange multiplier corresponding to I-th constraint
|
|
//--- NOTE: all A[i] except for last one have only one element being set,
|
|
//--- so we rely on sparse capabilities of NNLS solver. However,
|
|
//--- in order to use these capabilities we have to reorder variables
|
|
//--- in such way that sparse ones come first.
|
|
//--- After finding lambda[] coefficients, we can find constrained descent
|
|
//--- direction by subtracting lambda[i]*A[i] from D=-G. We make use of the
|
|
//--- fact that first NCandBnd columns are just columns of identity matrix,
|
|
//--- so we can perform exact projection by explicitly setting elements of D
|
|
//--- to zeros.
|
|
QPCalculateGradFunc(sampleg,diagh,nsample,nvars,State.m_xc,State.m_gc,State.m_fc,State.m_tmp0);
|
|
CApServ::IVectorSetLengthAtLeast(State.m_tmpidx,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_tmpd,n);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_tmpc2,n,1);
|
|
idx0=0;
|
|
ncandbnd=0;
|
|
for(i=0; i<n; i++)
|
|
if(State.m_xc[i]==0.0)
|
|
ncandbnd++;
|
|
idx1=ncandbnd;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_xc[i]==0.0)
|
|
{
|
|
//--- Candidate for activation of boundary constraint,
|
|
//--- comes first.
|
|
//--- NOTE: multiplication by -1 is due to the fact that
|
|
//--- it is lower bound, and has specific direction
|
|
//--- of constraint gradient.
|
|
State.m_tmpidx.Set(idx0,i);
|
|
State.m_tmpd.Set(idx0,State.m_gc[i]);
|
|
State.m_tmpc2.Set(idx0,0,-1.0);
|
|
idx0++;
|
|
}
|
|
else
|
|
{
|
|
//--- We are far away from boundary.
|
|
State.m_tmpidx.Set(idx1,i);
|
|
State.m_tmpd.Set(idx1,State.m_gc[i]);
|
|
State.m_tmpc2.Set(idx1,0,-1.0);
|
|
idx1++;
|
|
}
|
|
}
|
|
if(!CAp::Assert(idx0==ncandbnd,__FUNCTION__+": integrity check failed (2346)"))
|
|
return;
|
|
if(!CAp::Assert(idx1==n,__FUNCTION__+": integrity check failed (4535)"))
|
|
return;
|
|
CSNNLS::SNNLSInit(n,1,n,State.m_nnls);
|
|
CSNNLS::SNNLSSetProblem(State.m_nnls,State.m_tmpc2,State.m_tmpd,ncandbnd,1,n);
|
|
CSNNLS::SNNLSDropNNC(State.m_nnls,ncandbnd);
|
|
CSNNLS::SNNLSSolve(State.m_nnls,State.m_tmplambdas);
|
|
State.m_d=State.m_gc*(-1.0)-State.m_tmplambdas[ncandbnd];
|
|
for(i=0; i<ncandbnd; i++)
|
|
{
|
|
if(State.m_tmplambdas[i]>0.0)
|
|
State.m_d.Set(State.m_tmpidx[i],0.0);
|
|
}
|
|
//--- Additional stage to "polish" D (improve situation
|
|
//--- with sum-to-one constraint and boundary constraints)
|
|
//--- and to perform additional integrity check.
|
|
//--- After this stage we are pretty sure that:
|
|
//--- * if x[i]=0.0, then d[i]>=0.0
|
|
//--- * if d[i]<0.0, then x[i]>0.0
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_xc[i]==0.0 && State.m_d[i]<0.0)
|
|
State.m_d.Set(i,0.0);
|
|
}
|
|
//--- Decide whether we need "kick" stage: special stage
|
|
//--- that moves us away from boundary constraints which are
|
|
//--- not strictly active (i.e. such constraints that x[i]=0.0 and d[i]>0).
|
|
//--- If we need kick stage, we make a kick - and restart iteration.
|
|
//--- If not, after this block we can rely on the fact that
|
|
//--- for all x[i]=0.0 we have d[i]=0.0
|
|
kickneeded=false;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_xc[i]==0.0 && State.m_d[i]>0.0)
|
|
kickneeded=true;
|
|
}
|
|
if(kickneeded)
|
|
{
|
|
//--- Perform kick.
|
|
//--- Restart.
|
|
//--- Do not increase outer iterations counter.
|
|
v=0.0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_xc[i]==0.0 && State.m_d[i]>0.0)
|
|
State.m_xc.Set(i,kicklength);
|
|
v+=State.m_xc[i];
|
|
}
|
|
if(!CAp::Assert(v>0.0,__FUNCTION__+": integrity check failed (2572)"))
|
|
return;
|
|
State.m_xc/=v;
|
|
innerits++;
|
|
continue;
|
|
}
|
|
//--- Calculate Cholesky decomposition of constrained Hessian
|
|
//--- for Newton phase.
|
|
while(true)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Diagonal element
|
|
if(State.m_xc[i]>0.0)
|
|
State.m_ch.Set(i,i,State.m_uh.Get(i,i)+lambdav*maxdiag);
|
|
else
|
|
State.m_ch.Set(i,i,1.0);
|
|
//--- Offdiagonal elements
|
|
for(j=i+1; j<n; j++)
|
|
{
|
|
if(State.m_xc[i]>0.0 && State.m_xc[j]>0.0)
|
|
State.m_ch.Set(i,j,State.m_uh.Get(i,j));
|
|
else
|
|
State.m_ch.Set(i,j,0.0);
|
|
}
|
|
}
|
|
dbgncholesky++;
|
|
if(!CTrFac::SPDMatrixCholeskyRec(State.m_ch,0,n,true,State.m_tmp0))
|
|
{
|
|
//--- Cholesky decomposition failed.
|
|
//--- Increase LambdaV and repeat iteration.
|
|
//--- Do not increase outer iterations counter.
|
|
lambdav*=10;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
//--- Newton phase
|
|
while(true)
|
|
{
|
|
//--- Calculate constrained (equality and sum-to-one) descent direction D.
|
|
//--- Here we use Sherman-Morrison update to calculate direction subject to
|
|
//--- sum-to-one constraint.
|
|
QPCalculateGradFunc(sampleg,diagh,nsample,nvars,State.m_xc,State.m_gc,State.m_fc,State.m_tmp0);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_xc[i]>0.0)
|
|
{
|
|
State.m_invutc.Set(i,1.0);
|
|
State.m_d.Set(i,-State.m_gc[i]);
|
|
}
|
|
else
|
|
{
|
|
State.m_invutc.Set(i,0.0);
|
|
State.m_d.Set(i,0.0);
|
|
}
|
|
}
|
|
QPSolveUT(State.m_ch,n,State.m_invutc);
|
|
QPSolveUT(State.m_ch,n,State.m_d);
|
|
v=CAblasF::RDotV(n,State.m_invutc,State.m_d);
|
|
vv=CAblasF::RDotV2(n,State.m_invutc);
|
|
for(i=0; i<n; i++)
|
|
State.m_d.Add(i,-State.m_invutc[i]*v/vv);
|
|
QPSolveU(State.m_ch,n,State.m_d);
|
|
v=State.m_d.Sum();
|
|
k=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_d[i]!=0.0)
|
|
k++;
|
|
}
|
|
if(k>0 && v>0.0)
|
|
{
|
|
vv=v/k;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_d[i]!=0.0)
|
|
State.m_d.Add(i,-vv);
|
|
}
|
|
}
|
|
//--- Calculate length of D, maximum step and component which is
|
|
//--- activated by this step.
|
|
//--- Break if D is exactly zero. We do not break here if DNrm is
|
|
//--- small - this check is performed later. It is important to
|
|
//--- perform last step with nearly-zero D, it allows us to have
|
|
//--- extra-precision in solution which is often needed for convergence
|
|
//--- of AGS algorithm.
|
|
dnrm=State.m_d.Dot(State.m_d);
|
|
dnrm=MathSqrt(dnrm);
|
|
actidx=-1;
|
|
stpmax=1.0E50;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_d[i]<0.0)
|
|
{
|
|
v=stpmax;
|
|
stpmax=CApServ::SafeMinPosRV(State.m_xc[i],-State.m_d[i],stpmax);
|
|
if(stpmax<v)
|
|
actidx=i;
|
|
}
|
|
}
|
|
if(dnrm==0.0)
|
|
break;
|
|
//--- Calculate trial function value at unconstrained full step.
|
|
//--- If trial value is greater or equal to FC, terminate iterations.
|
|
State.m_xn=State.m_xc+State.m_d+0;
|
|
QPCalculateFunc(sampleg,diagh,nsample,nvars,State.m_xn,State.m_fn,State.m_tmp0);
|
|
if(State.m_fn>=State.m_fc)
|
|
break;
|
|
//--- Perform step
|
|
//--- Update Hessian
|
|
//--- Update XC
|
|
//--- Break if:
|
|
//--- a) no constraint was activated
|
|
//--- b) norm of D is small enough
|
|
stp=MathMin(1.0,stpmax);
|
|
for(i=0; i<n; i++)
|
|
State.m_xn.Set(i,MathMax(State.m_xc[i]+stp*State.m_d[i],0.0));
|
|
if(stp==stpmax && actidx>=0)
|
|
State.m_xn.Set(actidx,0.0);
|
|
wasactivation=false;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
State.m_tmpb[i]=(State.m_xn[i]==0.0 && State.m_xc[i]!=0.0);
|
|
wasactivation=wasactivation || State.m_tmpb[i];
|
|
}
|
|
State.m_xc=State.m_xn;
|
|
if(!wasactivation)
|
|
break;
|
|
if(dnrm<=dtol)
|
|
break;
|
|
CTrFac::SPDMatrixCholeskyUpdateFixBuf(State.m_ch,n,true,State.m_tmpb,State.m_tmp0);
|
|
}
|
|
//--- Compare status of boundary constraints - if nothing changed during
|
|
//--- last outer iteration, TermCnt is increased. Otherwise it is reset
|
|
//--- to zero.
|
|
//--- When TermCnt is large enough, we terminate algorithm.
|
|
werechanges=false;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
werechanges=werechanges || MathSign(State.m_x0[i])!=MathSign(State.m_xc[i]);
|
|
}
|
|
if(!werechanges)
|
|
termcnt++;
|
|
else
|
|
termcnt=0;
|
|
if(termcnt>=2)
|
|
break;
|
|
//--- Increase number of outer iterations.
|
|
//--- Break if we performed too many.
|
|
outerits++;
|
|
if(outerits==10)
|
|
break;
|
|
}
|
|
//--- Store result
|
|
coeffs=State.m_xc;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Function / gradient calculation for QP solver. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::QPCalculateGradFunc(CMatrixDouble &sampleg,CRowDouble &diagh,
|
|
int nsample,int nvars,CRowDouble &coeffs,
|
|
CRowDouble &g,double &f,CRowDouble &tmp)
|
|
{
|
|
//--- create variables
|
|
vector<double> c=coeffs.ToVector();
|
|
vector<double> d=diagh.ToVector();
|
|
matrix<double> s=sampleg.ToMatrix();
|
|
|
|
c.Resize(nsample);
|
|
d.Resize(nvars);
|
|
s.Resize(nsample,nvars);
|
|
f=0;
|
|
//--- Calculate GS*p
|
|
tmp=c.MatMul(s);
|
|
//--- Calculate F
|
|
f=0.5*(tmp.Pow(2.0)/d).Sum();
|
|
//--- Multiply by inverse Hessian
|
|
tmp/=d;
|
|
//--- Function gradient
|
|
g=tmp.MatMul(s.Transpose())+0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Function calculation for QP solver. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::QPCalculateFunc(CMatrixDouble &sampleg,CRowDouble &diagh,
|
|
int nsample,int nvars,CRowDouble &coeffs,
|
|
double &f,CRowDouble &tmp)
|
|
{
|
|
//--- create variables
|
|
vector<double> c=coeffs.ToVector();
|
|
vector<double> d=diagh.ToVector();
|
|
matrix<double> s=sampleg.ToMatrix();
|
|
|
|
c.Resize(nsample);
|
|
d.Resize(nvars);
|
|
s.Resize(nsample,nvars);
|
|
f=0;
|
|
//--- Calculate GS*p
|
|
tmp=c.MatMul(s);
|
|
//--- Calculate F
|
|
f=0.5*(tmp.Pow(2.0)/d).Sum();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Triangular solver for QP solver. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::QPSolveU(CMatrixDouble &a,int n,CRowDouble &x)
|
|
{
|
|
double v=0;
|
|
//--- A^(-1)*X
|
|
for(int i=n-1; i>=0; i--)
|
|
{
|
|
v=x[i];
|
|
for(int j=i+1; j<n; j++)
|
|
v-=a.Get(i,j)*x[j];
|
|
x.Set(i,v/a.Get(i,i));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Triangular solver for QP solver. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinNS::QPSolveUT(CMatrixDouble &a,int n,CRowDouble &x)
|
|
{
|
|
double v=0;
|
|
//--- A^(-T)*X
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
x.Set(i,x[i]/a.Get(i,i));
|
|
v=x[i];
|
|
for(int j=i+1; j<n; j++)
|
|
x.Add(j,-a.Get(i,j)*v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores nonlinear optimizer State.m_ |
|
|
//| You should use functions provided by MinBC subpackage to work |
|
|
//| with this object |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinBCState
|
|
{
|
|
int m_bufsize;
|
|
int m_maxits;
|
|
int m_mcstage;
|
|
int m_nfev;
|
|
int m_nmain;
|
|
int m_nonmonotoniccnt;
|
|
int m_prectype;
|
|
int m_repiterationscount;
|
|
int m_repnfev;
|
|
int m_repterminationtype;
|
|
int m_repvaridx;
|
|
int m_smoothnessguardlevel;
|
|
double m_curstpmax;
|
|
double m_diffstep;
|
|
double m_epsf;
|
|
double m_epsg;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_fbase;
|
|
double m_fc;
|
|
double m_fm1;
|
|
double m_fm2;
|
|
double m_fn;
|
|
double m_fp1;
|
|
double m_fp2;
|
|
double m_fp;
|
|
double m_gm1;
|
|
double m_gp1;
|
|
double m_lastscaledgoodstep;
|
|
double m_stp;
|
|
double m_stpmax;
|
|
double m_teststep;
|
|
double m_trimthreshold;
|
|
double m_xm1;
|
|
double m_xp1;
|
|
bool m_HasBndL[];
|
|
bool m_HasBndU[];
|
|
bool m_needf;
|
|
bool m_needfg;
|
|
bool m_userterminationneeded;
|
|
bool m_xrep;
|
|
bool m_xupdated;
|
|
RCommState m_rstate;
|
|
CSmoothnessMonitor m_smonitor;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_bufrho;
|
|
CRowDouble m_buftheta;
|
|
CRowDouble m_cgc;
|
|
CRowDouble m_cgn;
|
|
CRowDouble m_d;
|
|
CRowDouble m_diagh;
|
|
CRowDouble m_g;
|
|
CRowDouble m_invs;
|
|
CRowDouble m_lastscaleused;
|
|
CRowDouble m_s;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmpprec;
|
|
CRowDouble m_ugc;
|
|
CRowDouble m_ugn;
|
|
CRowDouble m_work;
|
|
CRowDouble m_x;
|
|
CRowDouble m_xc;
|
|
CRowDouble m_xn;
|
|
CRowDouble m_xp;
|
|
CRowDouble m_xstart;
|
|
CMatrixDouble m_bufsk;
|
|
CMatrixDouble m_bufyk;
|
|
CLinMinState m_lstate;
|
|
//--- constructor / destructor
|
|
CMinBCState(void);
|
|
~CMinBCState(void) {}
|
|
//---
|
|
void Copy(const CMinBCState &obj);
|
|
//--- overloading
|
|
void operator=(const CMinBCState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMinBCState::CMinBCState(void)
|
|
{
|
|
m_bufsize=0;
|
|
m_maxits=0;
|
|
m_mcstage=0;
|
|
m_nfev=0;
|
|
m_nmain=0;
|
|
m_nonmonotoniccnt=0;
|
|
m_prectype=0;
|
|
m_repiterationscount=0;
|
|
m_repnfev=0;
|
|
m_repterminationtype=0;
|
|
m_repvaridx=0;
|
|
m_smoothnessguardlevel=0;
|
|
m_curstpmax=0;
|
|
m_diffstep=0;
|
|
m_epsf=0;
|
|
m_epsg=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_fbase=0;
|
|
m_fc=0;
|
|
m_fm1=0;
|
|
m_fm2=0;
|
|
m_fn=0;
|
|
m_fp1=0;
|
|
m_fp2=0;
|
|
m_fp=0;
|
|
m_gm1=0;
|
|
m_gp1=0;
|
|
m_lastscaledgoodstep=0;
|
|
m_stp=0;
|
|
m_stpmax=0;
|
|
m_teststep=0;
|
|
m_trimthreshold=0;
|
|
m_xm1=0;
|
|
m_xp1=0;
|
|
m_needf=false;
|
|
m_needfg=false;
|
|
m_userterminationneeded=false;
|
|
m_xrep=false;
|
|
m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBCState::Copy(const CMinBCState &obj)
|
|
{
|
|
m_bufsize=obj.m_bufsize;
|
|
m_maxits=obj.m_maxits;
|
|
m_mcstage=obj.m_mcstage;
|
|
m_nfev=obj.m_nfev;
|
|
m_nmain=obj.m_nmain;
|
|
m_nonmonotoniccnt=obj.m_nonmonotoniccnt;
|
|
m_prectype=obj.m_prectype;
|
|
m_repiterationscount=obj.m_repiterationscount;
|
|
m_repnfev=obj.m_repnfev;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_repvaridx=obj.m_repvaridx;
|
|
m_smoothnessguardlevel=obj.m_smoothnessguardlevel;
|
|
m_curstpmax=obj.m_curstpmax;
|
|
m_diffstep=obj.m_diffstep;
|
|
m_epsf=obj.m_epsf;
|
|
m_epsg=obj.m_epsg;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_fbase=obj.m_fbase;
|
|
m_fc=obj.m_fc;
|
|
m_fm1=obj.m_fm1;
|
|
m_fm2=obj.m_fm2;
|
|
m_fn=obj.m_fn;
|
|
m_fp1=obj.m_fp1;
|
|
m_fp2=obj.m_fp2;
|
|
m_fp=obj.m_fp;
|
|
m_gm1=obj.m_gm1;
|
|
m_gp1=obj.m_gp1;
|
|
m_lastscaledgoodstep=obj.m_lastscaledgoodstep;
|
|
m_stp=obj.m_stp;
|
|
m_stpmax=obj.m_stpmax;
|
|
m_teststep=obj.m_teststep;
|
|
m_trimthreshold=obj.m_trimthreshold;
|
|
m_xm1=obj.m_xm1;
|
|
m_xp1=obj.m_xp1;
|
|
ArrayCopy(m_HasBndL,obj.m_HasBndL);
|
|
ArrayCopy(m_HasBndU,obj.m_HasBndU);
|
|
m_needf=obj.m_needf;
|
|
m_needfg=obj.m_needfg;
|
|
m_userterminationneeded=obj.m_userterminationneeded;
|
|
m_xrep=obj.m_xrep;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_rstate=obj.m_rstate;
|
|
m_smonitor=obj.m_smonitor;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_bufrho=obj.m_bufrho;
|
|
m_buftheta=obj.m_buftheta;
|
|
m_cgc=obj.m_cgc;
|
|
m_cgn=obj.m_cgn;
|
|
m_d=obj.m_d;
|
|
m_diagh=obj.m_diagh;
|
|
m_g=obj.m_g;
|
|
m_invs=obj.m_invs;
|
|
m_lastscaleused=obj.m_lastscaleused;
|
|
m_s=obj.m_s;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmpprec=obj.m_tmpprec;
|
|
m_ugc=obj.m_ugc;
|
|
m_ugn=obj.m_ugn;
|
|
m_work=obj.m_work;
|
|
m_x=obj.m_x;
|
|
m_xc=obj.m_xc;
|
|
m_xn=obj.m_xn;
|
|
m_xp=obj.m_xp;
|
|
m_xstart=obj.m_xstart;
|
|
m_bufsk=obj.m_bufsk;
|
|
m_bufyk=obj.m_bufyk;
|
|
m_lstate=obj.m_lstate;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure stores optimization report: |
|
|
//| * iterationscount number of iterations |
|
|
//| * nfev number of gradient evaluations |
|
|
//| * terminationtype termination type (see below) |
|
|
//| TERMINATION CODES |
|
|
//| Terminationtype field contains completion code, which can be: |
|
|
//| -8 internal integrity control detected infinite or NAN |
|
|
//| values in function/gradient. Abnormal termination |
|
|
//| signalled. |
|
|
//| -3 inconsistent constraints. |
|
|
//| 1 relative function improvement is no more than EpsF. |
|
|
//| 2 relative step is no more than EpsX. |
|
|
//| 4 gradient norm is no more than EpsG |
|
|
//| 5 MaxIts steps was taken |
|
|
//| 7 stopping conditions are too stringent, further |
|
|
//| improvement is impossible, X contains best point |
|
|
//| found so far. |
|
|
//| 8 terminated by user who called |
|
|
//| MinBCRequestTermination(). X contains point which was |
|
|
//| "current accepted" when termination request was |
|
|
//| submitted. |
|
|
//+------------------------------------------------------------------+
|
|
struct CMinBCReport
|
|
{
|
|
int m_iterationscount;
|
|
int m_nfev;
|
|
int m_terminationtype;
|
|
int m_varidx;
|
|
//--- constructor / destructor
|
|
CMinBCReport(void) { ZeroMemory(this); }
|
|
~CMinBCReport(void) {}
|
|
void Copy(const CMinBCReport &obj);
|
|
//--- overloading
|
|
void operator=(const CMinBCReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBCReport::Copy(const CMinBCReport &obj)
|
|
{
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_nfev=obj.m_nfev;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_varidx=obj.m_varidx;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CMinBC
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const double m_gtol;
|
|
static const double m_maxnonmonotoniclen;
|
|
static const double m_initialdecay;
|
|
static const double m_mindecay;
|
|
static const double m_decaycorrection;
|
|
|
|
static void MinBCCreate(int n,CRowDouble &x,CMinBCState &State);
|
|
static void MinBCCreateF(int n,CRowDouble &x,double diffstep,CMinBCState &State);
|
|
static void MinBCSetBC(CMinBCState &State,CRowDouble &bndl,CRowDouble &bndu);
|
|
static void MinBCSetCond(CMinBCState &State,double epsg,double epsf,double epsx,int maxits);
|
|
static void MinBCSetScale(CMinBCState &State,CRowDouble &s);
|
|
static void MinBCSetPrecDefault(CMinBCState &State);
|
|
static void MinBCSetPrecDiag(CMinBCState &State,CRowDouble &d);
|
|
static void MinBCSetPrecScale(CMinBCState &State);
|
|
static void MinBCSetXRep(CMinBCState &State,bool needxrep);
|
|
static void MinBCSetStpMax(CMinBCState &State,double stpmax);
|
|
static bool MinBCIteration(CMinBCState &State);
|
|
static void MinBCOptGuardGradient(CMinBCState &State,double teststep);
|
|
static void MinBCOptGuardSmoothness(CMinBCState &State,int level);
|
|
static void MinBCOptGuardResults(CMinBCState &State,COptGuardReport &rep);
|
|
static void MinBCOptGuardNonC1Test0Results(CMinBCState &State,COptGuardNonC1Test0Report &strrep,COptGuardNonC1Test0Report &lngrep);
|
|
static void MinBCOptGuardNonC1Test1Results(CMinBCState &State,COptGuardNonC1Test1Report &strrep,COptGuardNonC1Test1Report &lngrep);
|
|
static void MinBCResults(CMinBCState &State,CRowDouble &x,CMinBCReport &rep);
|
|
static void MinBCResultsBuf(CMinBCState &State,CRowDouble &x,CMinBCReport &rep);
|
|
static void MinBCRestartFrom(CMinBCState &State,CRowDouble &x);
|
|
static void MinBCRequestTermination(CMinBCState &State);
|
|
|
|
protected:
|
|
static void ClearRequestFields(CMinBCState &State);
|
|
static void MinBCInitInternal(int n,CRowDouble &x,double diffstep,CMinBCState &State);
|
|
static void UpdateEstimateOfGoodStep(double &estimate,double newstep);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
const double CMinBC::m_gtol=0.4;
|
|
const double CMinBC::m_maxnonmonotoniclen=1.0E-5;
|
|
const double CMinBC::m_initialdecay=0.5;
|
|
const double CMinBC::m_mindecay=0.1;
|
|
const double CMinBC::m_decaycorrection=0.8;
|
|
//+------------------------------------------------------------------+
|
|
//| BOX CONSTRAINED OPTIMIZATION WITH FAST ACTIVATION OF MULTIPLE BOX|
|
|
//| CONSTRAINTS |
|
|
//| DESCRIPTION: |
|
|
//| The subroutine minimizes function F(x) of N arguments subject to |
|
|
//| box constraints (with some of box constraints actually being |
|
|
//| equality ones). |
|
|
//| This optimizer uses algorithm similar to that of MinBLEIC |
|
|
//| (optimizer with general linear constraints), but presence of |
|
|
//| box-only constraints allows us to use faster constraint |
|
|
//| activation strategies. On large-scale problems, with multiple |
|
|
//| constraints active at the solution, this optimizer can be several|
|
|
//| times faster than BLEIC. |
|
|
//| REQUIREMENTS: |
|
|
//| * user must provide function value and gradient |
|
|
//| * starting point X0 must be feasible or not too far away from |
|
|
//| the feasible set |
|
|
//| * grad(f) must be Lipschitz continuous on a level set: |
|
|
//| L = { x : f(x) <= f(x0) } |
|
|
//| * function must be defined everywhere on the feasible set F |
|
|
//| USAGE: |
|
|
//| Constrained optimization if far more complex than the |
|
|
//| unconstrained one. Here we give very brief outline of the BC |
|
|
//| optimizer. We strongly recommend you to read examples in the |
|
|
//| ALGLIB Reference Manual and to read ALGLIB User Guide on |
|
|
//| optimization, which is available at |
|
|
//| http://www.alglib.net/optimization/ |
|
|
//| 1. User initializes algorithm State with MinBCCreate() call |
|
|
//| 2. USer adds box constraints by calling MinBCSetBC() function. |
|
|
//| 3. User sets stopping conditions with MinBCSetCond(). |
|
|
//| 4. User calls MinBCOptimize() function which takes algorithm |
|
|
//| State and pointer (delegate, etc.) to callback function which |
|
|
//| calculates F / G. |
|
|
//| 5. User calls MinBCResults() to get solution |
|
|
//| 6. Optionally user may call MinBCRestartFrom() to solve another |
|
|
//| problem with same N but another starting point. |
|
|
//| MinBCRestartFrom() allows to reuse already initialized |
|
|
//| structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N > 0: |
|
|
//| * if given, only leading N elements of X are used |
|
|
//| * if not given, automatically determined from size |
|
|
//| ofX |
|
|
//| X - starting point, array[N]: |
|
|
//| * it is better to set X to a feasible point |
|
|
//| * but X can be infeasible, in which case algorithm |
|
|
//| will try to find feasible point first, using X as|
|
|
//| initial approximation. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCCreate(int n,CRowDouble &x,CMinBCState &State)
|
|
{
|
|
CMatrixDouble c;
|
|
CRowInt ct;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
|
|
MinBCInitInternal(n,x,0.0,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| The subroutine is finite difference variant of MinBCCreate(). It |
|
|
//| uses finite differences in order to differentiate target function|
|
|
//| Description below contains information which is specific to this|
|
|
//| function only. We recommend to read comments on MinBCCreate() in |
|
|
//| order to get more information about creation of BC optimizer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| N - problem dimension, N > 0: |
|
|
//| * if given, only leading N elements of X are used |
|
|
//| * if not given, automatically determined from size |
|
|
//| of X |
|
|
//| X - starting point, array[0..N - 1]. |
|
|
//| DiffStep - differentiation step, > 0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NOTES: |
|
|
//| 1. algorithm uses 4-point central formula for differentiation. |
|
|
//| 2. differentiation step along I-th axis is equal to |
|
|
//| DiffStep*S[I] where S[] is scaling vector which can be set |
|
|
//| by MinBCSetScale() call. |
|
|
//| 3. we recommend you to use moderate values of differentiation |
|
|
//| step. Too large step will result in too large truncation |
|
|
//| errors, while too small step will result in too large |
|
|
//| numerical errors. 1.0E-6 can be good value to start with. |
|
|
//| 4. Numerical differentiation is very inefficient - one gradient |
|
|
//| calculation needs 4*N function evaluations. This function will|
|
|
//| work for any N - either small(1...10), moderate(10...100) or |
|
|
//| large(100...). However, performance penalty will be too severe|
|
|
//| for any N's except for small ones. |
|
|
//| We should also say that code which relies on numerical |
|
|
//| differentiation is less robust and precise. CG needs exact |
|
|
//| gradient values. Imprecise gradient may slow down convergence,|
|
|
//| especially on highly nonlinear problems. |
|
|
//| Thus we recommend to use this function for fast prototyping on|
|
|
//| small-dimensional problems only, and to implement analytical |
|
|
//| gradient as soon as possible. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCCreateF(int n,CRowDouble &x,double diffstep,
|
|
CMinBCState &State)
|
|
{
|
|
//--- create variables
|
|
CMatrixDouble c;
|
|
CRowInt ct;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(diffstep),__FUNCTION__+": DiffStep is infinite or NaN!"))
|
|
return;
|
|
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep is non-positive!"))
|
|
return;
|
|
|
|
MinBCInitInternal(n,x,diffstep,State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets boundary constraints for BC optimizer. |
|
|
//| Boundary constraints are inactive by default (after initial |
|
|
//| creation). They are preserved after algorithm restart with |
|
|
//| MinBCRestartFrom(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bounds, array[N]. If some (all) variables |
|
|
//| are unbounded, you may specify very small number|
|
|
//| or -INF. |
|
|
//| BndU - upper bounds, array[N]. If some (all) variables |
|
|
//| are unbounded, you may specify very large number|
|
|
//| or +INF. |
|
|
//| NOTE 1: it is possible to specify BndL[i] = BndU[i]. In this case|
|
|
//| I-th variable will be "frozen" at X[i] = BndL[i]=BndU[i].|
|
|
//| NOTE 2: this solver has following useful properties: |
|
|
//| * bound constraints are always satisfied exactly |
|
|
//| * function is evaluated only INSIDE area specified by |
|
|
//| bound constraints, even when numerical differentiation |
|
|
//| is used (algorithm adjusts nodes according to boundary |
|
|
//| constraints) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCSetBC(CMinBCState &State,CRowDouble &bndl,CRowDouble &bndu)
|
|
{
|
|
int n=State.m_nmain;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=n,__FUNCTION__+": Length(BndL)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(bndu)>=n,__FUNCTION__+": Length(BndU)<N"))
|
|
return;
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(bndl[i]) || IsNegInf(bndl[i]),__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bndu[i]) || IsPosInf(bndu[i]),__FUNCTION__+": BndL contains NAN or -INF"))
|
|
return;
|
|
State.m_bndl.Set(i,bndl[i]);
|
|
State.m_bndu.Set(i,bndu[i]);
|
|
State.m_HasBndL[i]=MathIsValidNumber(bndl[i]);
|
|
State.m_HasBndU[i]=MathIsValidNumber(bndu[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping conditions for the optimizer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsG - >= 0. The subroutine finishes its work if the |
|
|
//| condition | v | < EpsG is satisfied, where: |
|
|
//| * | . | means Euclidian norm |
|
|
//| * v - scaled gradient vector, v[i] = g[i] * s[i]|
|
|
//| * g - gradient |
|
|
//| * s - scaling coefficients set by MinBCSetScale |
|
|
//| EpsF - >= 0. The subroutine finishes its work if on |
|
|
//| k+1-th iteration the condition |
|
|
//| | F(k + 1) - F(k) | <= EpsF * max{ | F(k) |, | F(k + 1) |, 1} |
|
|
//| is satisfied. |
|
|
//| EpsX - >= 0. The subroutine finishes its work if on |
|
|
//| k+1-th iteration the condition | v | <= EpsX is |
|
|
//| fulfilled, where: |
|
|
//| * | . | means Euclidian norm |
|
|
//| * v - scaled step vector, v[i] = dx[i] / s[i] |
|
|
//| * dx - step vector, dx = X(k + 1) - X(k) |
|
|
//| * s - scaling coefficients set by MinBCSetScale |
|
|
//| MaxIts - maximum number of iterations. If MaxIts = 0, the|
|
|
//| number of iterations is unlimited. |
|
|
//| Passing EpsG = 0, EpsF = 0 and EpsX = 0 and MaxIts = 0 |
|
|
//| (simultaneously) will lead to automatic stopping criterion |
|
|
//| selection. |
|
|
//| NOTE: when SetCond() called with non-zero MaxIts, BC solver may |
|
|
//| perform slightly more than MaxIts iterations. I.e., MaxIts |
|
|
//| sets non-strict limit on iterations count. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCSetCond(CMinBCState &State,double epsg,double epsf,
|
|
double epsx,int maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(epsg),__FUNCTION__+": EpsG is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsg>=0.0,__FUNCTION__+": negative EpsG"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsf),__FUNCTION__+": EpsF is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsf>=0.0,__FUNCTION__+": negative EpsF"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsx),__FUNCTION__+": EpsX is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(epsx>=0.0,__FUNCTION__+": negative EpsX"))
|
|
return;
|
|
if(!CAp::Assert(maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
|
|
if(epsg==0.0 && epsf==0.0 && epsx==0.0 && maxits==0)
|
|
epsx=1.0E-6;
|
|
State.m_epsg=epsg;
|
|
State.m_epsf=epsf;
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients for BC optimizer. |
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions (step size and gradient are scaled before comparison |
|
|
//| with tolerances). Scale of the I-th variable is a translation |
|
|
//| invariant measure of: |
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| Scaling is also used by finite difference variant of the |
|
|
//| optimizer - step along I-th axis is equal to DiffStep*S[I]. |
|
|
//| In most optimizers (and in the BC too) scaling is NOT a form of |
|
|
//| preconditioning. It just affects stopping conditions. You should |
|
|
//| set preconditioner by separate call to one of the MinBCSetPrec...|
|
|
//| functions. |
|
|
//| There is a special preconditioning mode, however, which uses |
|
|
//| scaling coefficients to form diagonal preconditioning matrix. You|
|
|
//| can turn this mode on, if you want. But you should understand |
|
|
//| that scaling is not the same thing as preconditioning - these are|
|
|
//| two different, although related forms of tuning solver. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients S[i] may|
|
|
//| be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCSetScale(CMinBCState &State,CRowDouble &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(s)>=State.m_nmain,__FUNCTION__+": Length(S)<N"))
|
|
return;
|
|
for(int i=0; i<State.m_nmain; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
if(!CAp::Assert(s[i]!=0.0,__FUNCTION__+": S contains zero elements"))
|
|
return;
|
|
State.m_s.Set(i,MathAbs(s[i]));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: preconditioning is turned off|
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCSetPrecDefault(CMinBCState &State)
|
|
{
|
|
State.m_prectype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: diagonal of approximate |
|
|
//| Hessian is used. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| D - diagonal of the approximate Hessian, |
|
|
//| array[0..N - 1], (if larger, only leading N |
|
|
//| elements are used). |
|
|
//| NOTE 1: D[i] should be positive. Exception will be thrown |
|
|
//| otherwise. |
|
|
//| NOTE 2: you should pass diagonal of approximate Hessian - NOT ITS|
|
|
//| INVERSE. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCSetPrecDiag(CMinBCState &State,CRowDouble &d)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(d)>=State.m_nmain,__FUNCTION__+": D is too short"))
|
|
return;
|
|
for(int i=0; i<State.m_nmain; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(d[i]),__FUNCTION__+": D contains infinite or NAN elements"))
|
|
return;
|
|
if(!CAp::Assert(d[i]>0.0,__FUNCTION__+": D contains non-positive elements"))
|
|
return;
|
|
}
|
|
State.m_prectype=2;
|
|
State.m_diagh=d;
|
|
State.m_diagh.Resize(State.m_nmain);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modification of the preconditioner: scale - based diagonal |
|
|
//| preconditioning. |
|
|
//| This preconditioning mode can be useful when you don't have |
|
|
//| approximate diagonal of Hessian, but you know that your variables|
|
|
//| are badly scaled (for example, one variable is in [1, 10], and |
|
|
//| another in [1000, 100000]), and most part of the ill-conditioning|
|
|
//| comes from different scales of vars. |
|
|
//| In this case simple scale-based preconditioner, with |
|
|
//| H[i] = 1/(s[i]^2), can greatly improve convergence. |
|
|
//| IMPRTANT: you should set scale of your variables with |
|
|
//| MinBCSetScale() call (before or after |
|
|
//| MinBCSetPrecScale() call). Without knowledge of the |
|
|
//| scale of your variables scale-based preconditioner |
|
|
//| will be just unit matrix. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCSetPrecScale(CMinBCState &State)
|
|
{
|
|
State.m_prectype=3;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function turns on / off reporting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NeedXRep - whether iteration reports are needed or not |
|
|
//| If NeedXRep is True, algorithm will call rep() callback function |
|
|
//| if it is provided to MinBCOptimize(). |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCSetXRep(CMinBCState &State,bool needxrep)
|
|
{
|
|
State.m_xrep=needxrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets maximum step length |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| StpMax - maximum step length, >= 0. Set StpMax to 0.0, |
|
|
//| if you don't want to limit step length. |
|
|
//| Use this subroutine when you optimize target function which |
|
|
//| contains exp() or other fast growing functions, and optimization |
|
|
//| algorithm makes too large steps which lead to overflow. This |
|
|
//| function allows us to reject steps that are too large (and |
|
|
//| therefore expose us to the possible overflow) without actually |
|
|
//| calculating function value at the x + stp*d. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCSetStpMax(CMinBCState &State,double stpmax)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(stpmax),__FUNCTION__+": StpMax is not finite!"))
|
|
return;
|
|
if(!CAp::Assert(stpmax>=0.0,__FUNCTION__+": StpMax<0!"))
|
|
return;
|
|
|
|
State.m_stpmax=stpmax;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| NOTES: 1. This function has two different implementations: one |
|
|
//| which uses exact (analytical) user-supplied gradient, |
|
|
//| and one which uses function value only and numerically |
|
|
//| differentiates function in order to obtain gradient. |
|
|
//| Depending on the specific function used to create optimizer |
|
|
//| object (either MinBCCreate() for analytical gradient or |
|
|
//| MinBCCreateF() for numerical differentiation) you should choose |
|
|
//| appropriate variant of MinBCOptimize() - one which accepts |
|
|
//| function AND gradient or one which accepts function ONLY. |
|
|
//| Be careful to choose variant of MinBCOptimize() which corresponds|
|
|
//| to your optimization scheme! Table below lists different |
|
|
//| combinations of callback (function/gradient) passed to |
|
|
//| MinBCOptimize() and specific function used to create optimizer. |
|
|
//| | USER PASSED TO MinBCOptimize() |
|
|
//| CREATED WITH | function only | function and gradient |
|
|
//| ------------------------------------------------------------ |
|
|
//| MinBCCreateF() | works FAILS |
|
|
//| MinBCCreate() | FAILS works |
|
|
//| Here "FAIL" denotes inappropriate combinations of optimizer |
|
|
//| creation function and MinBCOptimize() version. Attemps to use |
|
|
//| such combination (for example, to create optimizer with |
|
|
//| MinBCCreateF() and to pass gradient information to MinCGOptimize)|
|
|
//| will lead to exception being thrown. Either you did not pass |
|
|
//| gradient when it WAS needed or you passed gradient when it was |
|
|
//| NOT needed. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMinBC::MinBCIteration(CMinBCState &State)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
int freezeidx=0;
|
|
double freezeval=0;
|
|
double scaleddnorm=0;
|
|
int n=0;
|
|
int m=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double v0=0;
|
|
bool b=false;
|
|
int mcinfo=0;
|
|
int itidx=0;
|
|
double ginit=0;
|
|
double gdecay=0;
|
|
bool activationstatus=false;
|
|
double activationstep=0;
|
|
int i_=0;
|
|
int label=-1;
|
|
vector<double> temp_v;
|
|
//--- Reverse communication preparations
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
freezeidx=State.m_rstate.ia[0];
|
|
n=State.m_rstate.ia[1];
|
|
m=State.m_rstate.ia[2];
|
|
i=State.m_rstate.ia[3];
|
|
j=State.m_rstate.ia[4];
|
|
mcinfo=State.m_rstate.ia[5];
|
|
itidx=State.m_rstate.ia[6];
|
|
b=State.m_rstate.ba[0];
|
|
activationstatus=State.m_rstate.ba[1];
|
|
freezeval=State.m_rstate.ra[0];
|
|
scaleddnorm=State.m_rstate.ra[1];
|
|
v=State.m_rstate.ra[2];
|
|
vv=State.m_rstate.ra[3];
|
|
v0=State.m_rstate.ra[4];
|
|
ginit=State.m_rstate.ra[5];
|
|
gdecay=State.m_rstate.ra[6];
|
|
activationstep=State.m_rstate.ra[7];
|
|
}
|
|
else
|
|
{
|
|
freezeidx=359;
|
|
n=-58;
|
|
m=-919;
|
|
i=-909;
|
|
j=81;
|
|
mcinfo=255;
|
|
itidx=74;
|
|
b=false;
|
|
activationstatus=true;
|
|
freezeval=205;
|
|
scaleddnorm=-838;
|
|
v=939;
|
|
vv=-526;
|
|
v0=763;
|
|
ginit=-541;
|
|
gdecay=-698;
|
|
activationstep=-900;
|
|
}
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
case 4:
|
|
label=4;
|
|
break;
|
|
case 5:
|
|
label=5;
|
|
break;
|
|
case 6:
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
label=7;
|
|
break;
|
|
case 8:
|
|
label=8;
|
|
break;
|
|
case 9:
|
|
label=9;
|
|
break;
|
|
case 10:
|
|
label=10;
|
|
break;
|
|
case 11:
|
|
label=11;
|
|
break;
|
|
case 12:
|
|
label=12;
|
|
break;
|
|
case 13:
|
|
label=13;
|
|
break;
|
|
case 14:
|
|
label=14;
|
|
break;
|
|
case 15:
|
|
label=15;
|
|
break;
|
|
case 16:
|
|
label=16;
|
|
break;
|
|
case 17:
|
|
label=17;
|
|
break;
|
|
case 18:
|
|
label=18;
|
|
break;
|
|
case 19:
|
|
label=19;
|
|
break;
|
|
case 20:
|
|
label=20;
|
|
break;
|
|
case 21:
|
|
label=21;
|
|
break;
|
|
case 22:
|
|
label=22;
|
|
break;
|
|
case 23:
|
|
label=23;
|
|
break;
|
|
case 24:
|
|
label=24;
|
|
break;
|
|
case 25:
|
|
label=25;
|
|
break;
|
|
case 26:
|
|
label=26;
|
|
break;
|
|
case 27:
|
|
label=27;
|
|
break;
|
|
case 28:
|
|
label=28;
|
|
break;
|
|
case 29:
|
|
label=29;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
//--- Algorithm parameters:
|
|
//--- * M number of L-BFGS corrections.
|
|
//--- This coefficient remains fixed during iterations.
|
|
//--- * GDecay desired decrease of constrained gradient during L-BFGS iterations.
|
|
//--- This coefficient is decreased after each L-BFGS round until
|
|
//--- it reaches minimum decay.
|
|
m=MathMin(5,State.m_nmain);
|
|
gdecay=m_initialdecay;
|
|
//--- Init
|
|
n=State.m_nmain;
|
|
State.m_xc=State.m_xstart;
|
|
if(!COptServ::EnforceBoundaryConstraints(State.m_xc,State.m_bndl,State.m_HasBndL,State.m_bndu,State.m_HasBndU,n,0))
|
|
{
|
|
//--- Inconsistent constraints
|
|
State.m_repterminationtype=-3;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
State.m_userterminationneeded=false;
|
|
State.m_repterminationtype=0;
|
|
State.m_repiterationscount=0;
|
|
State.m_repnfev=0;
|
|
State.m_repvaridx=-1;
|
|
State.m_bufyk=matrix<double>::Zeros(m+1,n);
|
|
State.m_bufsk=matrix<double>::Zeros(m+1,n);
|
|
State.m_bufrho=vector<double>::Zeros(m);
|
|
State.m_buftheta=vector<double>::Zeros(m);
|
|
State.m_tmp0=vector<double>::Zeros(n);
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,n,1,State.m_smoothnessguardlevel>0);
|
|
State.m_lastscaleused=State.m_s;
|
|
State.m_invs=State.m_s.Pow(-1)+0;
|
|
//--- Fill TmpPrec with current preconditioner
|
|
State.m_tmpprec=vector<double>::Ones(n);
|
|
switch(State.m_prectype)
|
|
{
|
|
case 2:
|
|
State.m_tmpprec=State.m_diagh.Pow(-1)+0;
|
|
break;
|
|
case 3:
|
|
State.m_tmpprec=State.m_s.Pow(2)+0;
|
|
break;
|
|
}
|
|
//--- Check correctness of user-supplied gradient
|
|
ClearRequestFields(State);
|
|
if(!(State.m_diffstep==0.0 && State.m_teststep>0.0))
|
|
label=30;
|
|
else
|
|
label=32;
|
|
break;
|
|
}
|
|
//--- main loop
|
|
while(label>=0)
|
|
{
|
|
switch(label)
|
|
{
|
|
case 32:
|
|
if(!COptServ::SmoothnessMonitorCheckGradientATX0(State.m_smonitor,State.m_xc,State.m_s,State.m_bndl,State.m_bndu,true,State.m_teststep))
|
|
{
|
|
label=33;
|
|
break;
|
|
}
|
|
State.m_x=State.m_smonitor.m_x;
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
State.m_needfg=false;
|
|
State.m_smonitor.m_fi.Set(0,State.m_f);
|
|
State.m_smonitor.m_j.Row(0,State.m_g);
|
|
label=32;
|
|
break;
|
|
case 33:
|
|
case 30:
|
|
//--- Main cycle of BC-PG algorithm
|
|
State.m_repterminationtype=0;
|
|
State.m_lastscaledgoodstep=0;
|
|
State.m_nonmonotoniccnt=(int)MathRound(1.5*n)+5;
|
|
State.m_x=State.m_xc;
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=34;
|
|
break;
|
|
}
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
State.m_needfg=false;
|
|
label=35;
|
|
break;
|
|
case 34:
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_needf=false;
|
|
case 35:
|
|
State.m_fc=State.m_f;
|
|
COptServ::TrimPrepare(State.m_f,State.m_trimthreshold);
|
|
State.m_repnfev++;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=36;
|
|
break;
|
|
}
|
|
//--- Report current point
|
|
State.m_x=State.m_xc;
|
|
State.m_f=State.m_fc;
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_xupdated=false;
|
|
case 36:
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
State.m_repterminationtype=8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
case 38:
|
|
//--- Steepest descent phase
|
|
//--- (a) calculate unconstrained gradient
|
|
//--- (b) check F/G for NAN/INF, abnormally terminate algorithm if needed
|
|
//--- (c) perform one steepest descent step, activating only those constraints
|
|
//--- which prevent us from moving outside of box-constrained area
|
|
State.m_x=State.m_xc;
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=40;
|
|
break;
|
|
}
|
|
//--- Analytic gradient
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=4;
|
|
label=-1;
|
|
break;
|
|
case 4:
|
|
State.m_needfg=false;
|
|
label=41;
|
|
break;
|
|
case 40:
|
|
//--- Numerical differentiation
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=5;
|
|
label=-1;
|
|
break;
|
|
case 5:
|
|
State.m_fbase=State.m_f;
|
|
i=0;
|
|
case 42:
|
|
if(i>n-1)
|
|
{
|
|
label=44;
|
|
break;
|
|
}
|
|
v=State.m_x[i];
|
|
b=false;
|
|
if(State.m_HasBndL[i])
|
|
b=b || (v-State.m_diffstep*State.m_s[i])<State.m_bndl[i];
|
|
if(State.m_HasBndU[i])
|
|
b=b || (v+State.m_diffstep*State.m_s[i])>State.m_bndu[i];
|
|
if(b)
|
|
{
|
|
label=45;
|
|
break;
|
|
}
|
|
State.m_x.Set(i,v-State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=6;
|
|
label=-1;
|
|
break;
|
|
case 6:
|
|
State.m_fm2=State.m_f;
|
|
State.m_x.Set(i,v-0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=7;
|
|
label=-1;
|
|
break;
|
|
case 7:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,v+0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=8;
|
|
label=-1;
|
|
break;
|
|
case 8:
|
|
State.m_fp1=State.m_f;
|
|
State.m_x.Set(i,v+State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=9;
|
|
label=-1;
|
|
break;
|
|
case 9:
|
|
State.m_fp2=State.m_f;
|
|
State.m_g.Set(i,(8*(State.m_fp1-State.m_fm1)-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[i]));
|
|
label=46;
|
|
break;
|
|
case 45:
|
|
State.m_xm1=v-State.m_diffstep*State.m_s[i];
|
|
State.m_xp1=v+State.m_diffstep*State.m_s[i];
|
|
if(State.m_HasBndL[i] && State.m_xm1<State.m_bndl[i])
|
|
State.m_xm1=State.m_bndl[i];
|
|
if(State.m_HasBndU[i] && State.m_xp1>State.m_bndu[i])
|
|
State.m_xp1=State.m_bndu[i];
|
|
State.m_x.Set(i,State.m_xm1);
|
|
State.m_rstate.stage=10;
|
|
label=-1;
|
|
break;
|
|
case 10:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,State.m_xp1);
|
|
State.m_rstate.stage=11;
|
|
label=-1;
|
|
break;
|
|
case 11:
|
|
State.m_fp1=State.m_f;
|
|
if(State.m_xm1!=State.m_xp1)
|
|
State.m_g.Set(i,(State.m_fp1-State.m_fm1)/(State.m_xp1-State.m_xm1));
|
|
else
|
|
State.m_g.Set(i,0);
|
|
case 46:
|
|
State.m_x.Set(i,v);
|
|
i++;
|
|
label=42;
|
|
break;
|
|
case 44:
|
|
State.m_f=State.m_fbase;
|
|
State.m_needf=false;
|
|
case 41:
|
|
State.m_fc=State.m_f;
|
|
State.m_ugc=State.m_g;
|
|
State.m_cgc=State.m_g;
|
|
COptServ::ProjectGradientIntoBC(State.m_xc,State.m_cgc,State.m_bndl,State.m_HasBndL,State.m_bndu,State.m_HasBndU,n,0);
|
|
ginit=MathPow(State.m_cgc.ToVector()*State.m_s.ToVector(),2.0).Sum();
|
|
ginit=MathSqrt(ginit);
|
|
if(!MathIsValidNumber(ginit) || !MathIsValidNumber(State.m_fc))
|
|
{
|
|
//--- Abnormal termination - infinities in function/gradient
|
|
State.m_repterminationtype=-8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
State.m_repterminationtype=8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(ginit<=State.m_epsg)
|
|
{
|
|
//--- Gradient is small enough.
|
|
//--- Optimization is terminated
|
|
State.m_repterminationtype=4;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
State.m_d=(State.m_tmpprec*State.m_cgc)*(-1.0);
|
|
scaleddnorm= MathPow(State.m_d.ToVector()/State.m_s.ToVector(),2.0).Sum();
|
|
scaleddnorm=MathSqrt(scaleddnorm);
|
|
//--- check
|
|
if(!CAp::Assert(scaleddnorm>0.0,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
if(State.m_lastscaledgoodstep>0.0)
|
|
State.m_stp=State.m_lastscaledgoodstep/scaleddnorm;
|
|
else
|
|
State.m_stp=1.0/scaleddnorm;
|
|
COptServ::CalculateStepBound(State.m_xc,State.m_d,1.0,State.m_bndl,State.m_HasBndL,State.m_bndu,State.m_HasBndU,n,0,freezeidx,freezeval,State.m_curstpmax);
|
|
activationstep=State.m_curstpmax;
|
|
if(freezeidx<0 || State.m_curstpmax>1.0E50)
|
|
State.m_curstpmax=1.0E50;
|
|
if(State.m_stpmax>0.0)
|
|
State.m_curstpmax=MathMin(State.m_curstpmax,State.m_stpmax/scaleddnorm);
|
|
State.m_xn=State.m_xc;
|
|
State.m_cgn=State.m_cgc;
|
|
State.m_ugn=State.m_ugc;
|
|
State.m_fn=State.m_fc;
|
|
State.m_mcstage=0;
|
|
COptServ::SmoothnessMonitorStartLineSearch1u(State.m_smonitor,State.m_s,State.m_invs,State.m_xn,State.m_fn,State.m_ugn);
|
|
CLinMin::MCSrch(n,State.m_xn,State.m_fn,State.m_cgn,State.m_d,State.m_stp,State.m_curstpmax,m_gtol,mcinfo,State.m_nfev,State.m_work,State.m_lstate,State.m_mcstage);
|
|
case 47:
|
|
if(State.m_mcstage==0)
|
|
{
|
|
label=48;
|
|
break;
|
|
}
|
|
//--- Copy XN to X, perform on-the-fly correction w.r.t box
|
|
//--- constraints (projection onto feasible set).
|
|
State.m_x=State.m_xn;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_xn[i]<State.m_bndl[i])
|
|
State.m_x.Set(i,State.m_bndl[i]);
|
|
if(State.m_HasBndU[i] && State.m_xn[i]>State.m_bndu[i])
|
|
State.m_x.Set(i,State.m_bndu[i]);
|
|
}
|
|
//--- Gradient, either user-provided or numerical differentiation
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=49;
|
|
break;
|
|
}
|
|
//--- Analytic gradient
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=12;
|
|
label=-1;
|
|
break;
|
|
case 12:
|
|
State.m_needfg=false;
|
|
State.m_repnfev=State.m_repnfev+1;
|
|
label=50;
|
|
break;
|
|
case 49:
|
|
//--- Numerical differentiation
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=13;
|
|
label=-1;
|
|
break;
|
|
case 13:
|
|
State.m_fbase=State.m_f;
|
|
i=0;
|
|
case 51:
|
|
if(i>n-1)
|
|
{
|
|
label=53;
|
|
break;
|
|
}
|
|
v=State.m_x[i];
|
|
b=false;
|
|
if(State.m_HasBndL[i])
|
|
b=b || (v-State.m_diffstep*State.m_s[i])<State.m_bndl[i];
|
|
if(State.m_HasBndU[i])
|
|
b=b || (v+State.m_diffstep*State.m_s[i])>(State.m_bndu[i]);
|
|
if(b)
|
|
{
|
|
label=54;
|
|
break;
|
|
}
|
|
State.m_x.Set(i,v-State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=14;
|
|
label=-1;
|
|
break;
|
|
case 14:
|
|
State.m_fm2=State.m_f;
|
|
State.m_x.Set(i,v-0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=15;
|
|
label=-1;
|
|
break;
|
|
case 15:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,v+0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=16;
|
|
label=-1;
|
|
break;
|
|
case 16:
|
|
State.m_fp1=State.m_f;
|
|
State.m_x.Set(i,v+State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=17;
|
|
label=-1;
|
|
break;
|
|
case 17:
|
|
State.m_fp2=State.m_f;
|
|
State.m_g.Set(i,(8*(State.m_fp1-State.m_fm1)-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[i]));
|
|
State.m_repnfev+=4;
|
|
label=55;
|
|
break;
|
|
case 54:
|
|
State.m_xm1=v-State.m_diffstep*State.m_s[i];
|
|
State.m_xp1=v+State.m_diffstep*State.m_s[i];
|
|
if(State.m_HasBndL[i] && State.m_xm1<State.m_bndl[i])
|
|
State.m_xm1=State.m_bndl[i];
|
|
if(State.m_HasBndU[i] && State.m_xp1>State.m_bndu[i])
|
|
State.m_xp1=State.m_bndu[i];
|
|
State.m_x.Set(i,State.m_xm1);
|
|
State.m_rstate.stage=18;
|
|
label=-1;
|
|
break;
|
|
case 18:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,State.m_xp1);
|
|
State.m_rstate.stage=19;
|
|
label=-1;
|
|
break;
|
|
case 19:
|
|
State.m_fp1=State.m_f;
|
|
if(State.m_xm1!=State.m_xp1)
|
|
State.m_g.Set(i,(State.m_fp1-State.m_fm1)/(State.m_xp1-State.m_xm1));
|
|
else
|
|
State.m_g.Set(i,0);
|
|
State.m_repnfev+=2;
|
|
case 55:
|
|
State.m_x.Set(i,v);
|
|
i++;
|
|
label=51;
|
|
break;
|
|
case 53:
|
|
State.m_f=State.m_fbase;
|
|
State.m_needf=false;
|
|
case 50:
|
|
//--- Back to MCSRCH
|
|
COptServ::SmoothnessMonitorEnqueuePoint1u(State.m_smonitor,State.m_s,State.m_invs,State.m_d,State.m_stp,State.m_x,State.m_f,State.m_g);
|
|
COptServ::TrimFunction(State.m_f,State.m_g,n,State.m_trimthreshold);
|
|
State.m_fn=State.m_f;
|
|
State.m_cgn=State.m_g;
|
|
State.m_ugn=State.m_g;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_d[i]==0.0)
|
|
State.m_cgn.Set(i,0);
|
|
}
|
|
CLinMin::MCSrch(n,State.m_xn,State.m_fn,State.m_cgn,State.m_d,State.m_stp,State.m_curstpmax,m_gtol,mcinfo,State.m_nfev,State.m_work,State.m_lstate,State.m_mcstage);
|
|
label=47;
|
|
break;
|
|
case 48:
|
|
COptServ::SmoothnessMonitorFinalizeLineSearch(State.m_smonitor);
|
|
v=State.m_fn;
|
|
for(i=0; i<n; i++)
|
|
v=0.1*v+State.m_ugn[i];
|
|
if(!MathIsValidNumber(v))
|
|
{
|
|
//--- Abnormal termination - infinities in function/gradient
|
|
State.m_repterminationtype=-8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(mcinfo!=1 && mcinfo!=5)
|
|
{
|
|
//--- We can not find step which decreases function value. We have
|
|
//--- two possibilities:
|
|
//--- (a) numerical properties of the function do not allow us to
|
|
//--- find good step.
|
|
//--- (b) we are close to activation of some constraint, and it is
|
|
//--- so close that step which activates it leads to change in
|
|
//--- target function which is smaller than numerical noise.
|
|
//--- Optimization algorithm must be able to handle case (b), because
|
|
//--- inability to handle it will cause failure when algorithm
|
|
//--- started very close to boundary of the feasible area.
|
|
//--- In order to correctly handle such cases we allow limited amount
|
|
//--- of small steps which increase function value.
|
|
if(freezeidx>=0 && (scaleddnorm*State.m_curstpmax)<=m_maxnonmonotoniclen && State.m_nonmonotoniccnt>0)
|
|
{
|
|
//--- We enforce non-monotonic step:
|
|
//--- * Stp := CurStpMax
|
|
//--- * MCINFO := 5
|
|
//--- * XN := XC+CurStpMax*D
|
|
//--- * non-monotonic counter is decreased
|
|
//--- NOTE: UGN/CGN are not updated because step is so short that we assume that
|
|
//--- GN is approximately equal to GC.
|
|
State.m_stp=State.m_curstpmax;
|
|
mcinfo=5;
|
|
v=State.m_curstpmax;
|
|
State.m_xn=State.m_xc.ToVector()+State.m_d.ToVector()*v;
|
|
State.m_nonmonotoniccnt--;
|
|
}
|
|
else
|
|
{
|
|
//--- Numerical properties of the function does not allow
|
|
//--- us to solve problem. Algorithm is terminated
|
|
State.m_repterminationtype=7;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
State.m_repterminationtype=8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(mcinfo!=5 || (double)State.m_stp==(double)State.m_curstpmax,__FUNCTION__+": integrity check failed"))
|
|
return(false);
|
|
COptServ::PostProcessBoundedStep(State.m_xn,State.m_xc,State.m_bndl,State.m_HasBndL,State.m_bndu,State.m_HasBndU,n,0,freezeidx,freezeval,State.m_stp,activationstep);
|
|
State.m_fp=State.m_fc;
|
|
State.m_fc=State.m_fn;
|
|
State.m_xp=State.m_xc;
|
|
State.m_xc=State.m_xn;
|
|
State.m_cgc=State.m_cgn;
|
|
State.m_ugc=State.m_ugn;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=56;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xc;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=20;
|
|
label=-1;
|
|
break;
|
|
case 20:
|
|
State.m_xupdated=false;
|
|
case 56:
|
|
State.m_repiterationscount++;
|
|
if(mcinfo==1)
|
|
{
|
|
v=0;
|
|
for(i=0; i<n; i++)
|
|
v=v+CMath::Sqr((State.m_xc[i]-State.m_xp[i])/State.m_s[i]);
|
|
v=MathSqrt(v);
|
|
if(v<=State.m_epsx)
|
|
{
|
|
//--- Step is small enough
|
|
State.m_repterminationtype=2;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(MathAbs(State.m_fp-State.m_fc)<=(State.m_epsf*MathMax(MathAbs(State.m_fc),MathMax(MathAbs(State.m_fp),1.0))))
|
|
{
|
|
//--- Function change is small enough
|
|
State.m_repterminationtype=1;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
if(State.m_maxits>0 && State.m_repiterationscount>=State.m_maxits)
|
|
{
|
|
//--- Iteration counter exceeded limit
|
|
State.m_repterminationtype=5;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
//--- LBFGS stage:
|
|
//--- * during LBFGS iterations we activate new constraints, but never
|
|
//--- deactivate already active ones.
|
|
//--- * we perform at most N iterations of LBFGS before re-evaluating
|
|
//--- active set and restarting LBFGS.
|
|
//--- About termination:
|
|
//--- * LBFGS iterations can be terminated because of two reasons:
|
|
//--- *"termination" - non-zero termination code in RepTerminationType,
|
|
//--- which means that optimization is done
|
|
//--- *"restart" - zero RepTerminationType, which means that we
|
|
//--- have to re-evaluate active set and resume LBFGS stage.
|
|
//---*one more option is "refresh" - to continue LBFGS iterations,
|
|
//--- but with all BFGS updates (Sk/Yk pairs) being dropped;
|
|
//--- it happens after changes in active set
|
|
ginit=0.0;
|
|
State.m_cgc=State.m_ugc;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_xc[i]==State.m_bndl[i])
|
|
State.m_cgc.Set(i,0);
|
|
if(State.m_HasBndU[i] && State.m_xc[i]==State.m_bndu[i])
|
|
State.m_cgc.Set(i,0);
|
|
ginit+=CMath::Sqr(State.m_cgc[i]*State.m_s[i]);
|
|
}
|
|
ginit=MathSqrt(ginit);
|
|
State.m_bufsize=0;
|
|
itidx=0;
|
|
case 58:
|
|
if(itidx>n-1)
|
|
{
|
|
label=60;
|
|
break;
|
|
}
|
|
//--- At the beginning of each iteration:
|
|
//--- * XC stores current point
|
|
//--- * FC stores current function value
|
|
//--- * UGC stores current unconstrained gradient
|
|
//--- * CGC stores current constrained gradient
|
|
//--- * D stores constrained step direction (calculated at this block)
|
|
//--- 1. Calculate search direction D according to L-BFGS algorithm
|
|
//--- using constrained preconditioner to perform inner multiplication.
|
|
//--- 2. Evaluate scaled length of direction D; restart LBFGS if D is zero
|
|
//--- (it may be possible that we found minimum, but it is also possible
|
|
//--- that some constraints need deactivation)
|
|
//--- 3. If D is non-zero, try to use previous scaled step length as initial estimate for new step.
|
|
//--- 4. Calculate bound on step length.
|
|
State.m_work=State.m_cgc;
|
|
for(i=State.m_bufsize-1; i>=0; i--)
|
|
{
|
|
v=State.m_work.DotR(State.m_bufsk,i);
|
|
State.m_buftheta.Set(i,v);
|
|
vv=v*State.m_bufrho[i];
|
|
State.m_work-=State.m_bufyk[i]*vv;
|
|
}
|
|
State.m_work*=State.m_tmpprec;
|
|
for(i=0; i<State.m_bufsize; i++)
|
|
{
|
|
v=State.m_work.DotR(State.m_bufyk,i);
|
|
vv=State.m_bufrho[i]*(-v+State.m_buftheta[i]);
|
|
State.m_work+=State.m_bufsk[i]*vv;
|
|
}
|
|
State.m_d=State.m_work.ToVector()*(-1);
|
|
b=false;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
b=b || ((State.m_HasBndL[i] && State.m_xc[i]==State.m_bndl[i]) && State.m_d[i]!=0.0);
|
|
b=b || ((State.m_HasBndU[i] && State.m_xc[i]==State.m_bndu[i]) && State.m_d[i]!=0.0);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(!b,__FUNCTION__+": integrity check failed (q)"))
|
|
return(false);
|
|
scaleddnorm=0;
|
|
for(i=0; i<n; i++)
|
|
scaleddnorm=scaleddnorm+CMath::Sqr(State.m_d[i]/State.m_s[i]);
|
|
scaleddnorm=MathSqrt(scaleddnorm);
|
|
if(scaleddnorm==0.0)
|
|
{
|
|
//--- Search direction is zero.
|
|
//--- Skip back to steepest descent phase.
|
|
label=60;
|
|
break;
|
|
}
|
|
if(State.m_lastscaledgoodstep>0.0)
|
|
State.m_stp=State.m_lastscaledgoodstep/scaleddnorm;
|
|
else
|
|
State.m_stp=1.0/scaleddnorm;
|
|
State.m_curstpmax=1.0E50;
|
|
if(State.m_stpmax>0.0)
|
|
State.m_curstpmax=MathMin(State.m_curstpmax,State.m_stpmax/scaleddnorm);
|
|
//--- Minimize G(t) = F(CONSTRAIN(XC + t*D)), with t being scalar, XC and D being vectors.
|
|
State.m_xn=State.m_xc;
|
|
State.m_cgn=State.m_cgc;
|
|
State.m_ugn=State.m_ugc;
|
|
State.m_fn=State.m_fc;
|
|
State.m_mcstage=0;
|
|
COptServ::SmoothnessMonitorStartLineSearch1u(State.m_smonitor,State.m_s,State.m_invs,State.m_xn,State.m_fn,State.m_ugn);
|
|
CLinMin::MCSrch(n,State.m_xn,State.m_fn,State.m_cgn,State.m_d,State.m_stp,State.m_curstpmax,m_gtol,mcinfo,State.m_nfev,State.m_work,State.m_lstate,State.m_mcstage);
|
|
case 61:
|
|
if(State.m_mcstage==0)
|
|
{
|
|
label=62;
|
|
break;
|
|
}
|
|
//--- Copy XN to X, perform on-the-fly correction w.r.t box
|
|
//--- constraints (projection onto feasible set).
|
|
State.m_x=State.m_xn;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_xn[i]<=State.m_bndl[i])
|
|
State.m_x.Set(i,State.m_bndl[i]);
|
|
if(State.m_HasBndU[i] && State.m_xn[i]>=State.m_bndu[i])
|
|
State.m_x.Set(i,State.m_bndu[i]);
|
|
}
|
|
//--- Gradient, either user-provided or numerical differentiation
|
|
ClearRequestFields(State);
|
|
if(State.m_diffstep!=0.0)
|
|
{
|
|
label=63;
|
|
break;
|
|
}
|
|
//--- Analytic gradient
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=21;
|
|
label=-1;
|
|
break;
|
|
case 21:
|
|
State.m_needfg=false;
|
|
State.m_repnfev ++;
|
|
label=64;
|
|
break;
|
|
case 63:
|
|
//--- Numerical differentiation
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=22;
|
|
label=-1;
|
|
break;
|
|
case 22:
|
|
State.m_fbase=State.m_f;
|
|
i=0;
|
|
case 65:
|
|
if(i>n-1)
|
|
{
|
|
label=67;
|
|
break;
|
|
}
|
|
v=State.m_x[i];
|
|
b=false;
|
|
if(State.m_HasBndL[i])
|
|
b=b || (v-State.m_diffstep*State.m_s[i])<State.m_bndl[i];
|
|
if(State.m_HasBndU[i])
|
|
b=b || (v+State.m_diffstep*State.m_s[i])>State.m_bndu[i];
|
|
if(b)
|
|
{
|
|
label=68;
|
|
break;
|
|
}
|
|
State.m_x.Set(i,v-State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=23;
|
|
label=-1;
|
|
break;
|
|
case 23:
|
|
State.m_fm2=State.m_f;
|
|
State.m_x.Set(i,v-0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=24;
|
|
label=-1;
|
|
break;
|
|
case 24:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,v+0.5*State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=25;
|
|
label=-1;
|
|
break;
|
|
case 25:
|
|
State.m_fp1=State.m_f;
|
|
State.m_x.Set(i,v+State.m_diffstep*State.m_s[i]);
|
|
State.m_rstate.stage=26;
|
|
label=-1;
|
|
break;
|
|
case 26:
|
|
State.m_fp2=State.m_f;
|
|
State.m_g.Set(i,(8*(State.m_fp1-State.m_fm1)-(State.m_fp2-State.m_fm2))/(6*State.m_diffstep*State.m_s[i]));
|
|
State.m_repnfev+=4;
|
|
label=69;
|
|
break;
|
|
case 68:
|
|
State.m_xm1=v-State.m_diffstep*State.m_s[i];
|
|
State.m_xp1=v+State.m_diffstep*State.m_s[i];
|
|
if(State.m_HasBndL[i] && State.m_xm1<State.m_bndl[i])
|
|
State.m_xm1=State.m_bndl[i];
|
|
if(State.m_HasBndU[i] && State.m_xp1>State.m_bndu[i])
|
|
State.m_xp1=State.m_bndu[i];
|
|
State.m_x.Set(i,State.m_xm1);
|
|
State.m_rstate.stage=27;
|
|
label=-1;
|
|
break;
|
|
case 27:
|
|
State.m_fm1=State.m_f;
|
|
State.m_x.Set(i,State.m_xp1);
|
|
State.m_rstate.stage=28;
|
|
label=-1;
|
|
break;
|
|
case 28:
|
|
State.m_fp1=State.m_f;
|
|
if(State.m_xm1!=State.m_xp1)
|
|
State.m_g.Set(i,(State.m_fp1-State.m_fm1)/(State.m_xp1-State.m_xm1));
|
|
else
|
|
State.m_g.Set(i,0);
|
|
State.m_repnfev+=2;
|
|
case 69:
|
|
State.m_x.Set(i,v);
|
|
i+=1;
|
|
label=65;
|
|
break;
|
|
case 67:
|
|
State.m_f=State.m_fbase;
|
|
State.m_needf=false;
|
|
case 64:
|
|
//--- Back to MCSRCH
|
|
COptServ::SmoothnessMonitorEnqueuePoint1u(State.m_smonitor,State.m_s,State.m_invs,State.m_d,State.m_stp,State.m_x,State.m_f,State.m_g);
|
|
COptServ::TrimFunction(State.m_f,State.m_g,n,State.m_trimthreshold);
|
|
State.m_fn=State.m_f;
|
|
State.m_ugn=State.m_g;
|
|
State.m_cgn=State.m_g;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_xn[i]<=State.m_bndl[i])
|
|
State.m_cgn.Set(i,0);
|
|
if(State.m_HasBndU[i] && State.m_xn[i]>=State.m_bndu[i])
|
|
State.m_cgn.Set(i,0);
|
|
}
|
|
CLinMin::MCSrch(n,State.m_xn,State.m_fn,State.m_cgn,State.m_d,State.m_stp,State.m_curstpmax,m_gtol,mcinfo,State.m_nfev,State.m_work,State.m_lstate,State.m_mcstage);
|
|
label=61;
|
|
break;
|
|
case 62:
|
|
COptServ::SmoothnessMonitorFinalizeLineSearch(State.m_smonitor);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_xn[i]<=State.m_bndl[i])
|
|
State.m_xn.Set(i,State.m_bndl[i]);
|
|
if(State.m_HasBndU[i] && State.m_xn[i]>=State.m_bndu[i])
|
|
State.m_xn.Set(i,State.m_bndu[i]);
|
|
}
|
|
State.m_bufsk.Row(State.m_bufsize,State.m_xn.ToVector()-State.m_xc.ToVector());
|
|
State.m_bufyk.Row(State.m_bufsize,State.m_cgn.ToVector()-State.m_cgc.ToVector());
|
|
//--- Handle special situations:
|
|
//--- * check for presence of NAN/INF in function/gradient
|
|
//--- * handle failure of line search
|
|
v=State.m_fn;
|
|
for(i=0; i<n; i++)
|
|
v=0.1*v+State.m_ugn[i];
|
|
if(!MathIsValidNumber(v))
|
|
{
|
|
//--- Abnormal termination - infinities in function/gradient
|
|
State.m_repterminationtype=-8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(State.m_userterminationneeded)
|
|
{
|
|
//--- User requested termination
|
|
State.m_repterminationtype=8;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
if(mcinfo!=1)
|
|
{
|
|
//--- Terminate LBFGS phase
|
|
label=60;
|
|
break;
|
|
}
|
|
//--- Current point is updated:
|
|
//--- * move XC/FC/GC to XP/FP/GP
|
|
//--- * move XN/FN/GN to XC/FC/GC
|
|
//--- * report current point and update iterations counter
|
|
//--- * push new pair SK/YK to LBFGS buffer
|
|
//--- * update length of the good step
|
|
activationstatus=false;
|
|
for(i=0; (i<n && !activationstatus); i++)
|
|
{
|
|
if(State.m_HasBndL[i] && State.m_xn[i]==State.m_bndl[i] && State.m_xn[i]!=State.m_xc[i])
|
|
activationstatus=true;
|
|
if(State.m_HasBndU[i] && State.m_xn[i]==State.m_bndu[i] && State.m_xn[i]!=State.m_xc[i])
|
|
activationstatus=true;
|
|
}
|
|
State.m_fp=State.m_fc;
|
|
State.m_fc=State.m_fn;
|
|
State.m_xp=State.m_xc;
|
|
State.m_xc=State.m_xn;
|
|
State.m_cgc=State.m_cgn;
|
|
State.m_ugc=State.m_ugn;
|
|
if(!State.m_xrep)
|
|
{
|
|
label=70;
|
|
break;
|
|
}
|
|
State.m_x=State.m_xc;
|
|
ClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=29;
|
|
label=-1;
|
|
break;
|
|
case 29:
|
|
State.m_xupdated=false;
|
|
case 70:
|
|
State.m_repiterationscount=State.m_repiterationscount+1;
|
|
if(State.m_bufsize==m)
|
|
{
|
|
//--- Buffer is full, shift contents by one row
|
|
for(i=0; i<State.m_bufsize; i++)
|
|
{
|
|
State.m_bufsk.Row(i,State.m_bufsk,i+1);
|
|
State.m_bufyk.Row(i,State.m_bufyk,i+1);
|
|
}
|
|
for(i=0; i<State.m_bufsize-1; i++)
|
|
{
|
|
State.m_bufrho.Set(i,State.m_bufrho[i+1]);
|
|
State.m_buftheta.Set(i,State.m_buftheta[i+1]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Buffer is not full, increase buffer size by 1
|
|
State.m_bufsize++;
|
|
}
|
|
v=(State.m_bufyk[State.m_bufsize-1]*State.m_bufsk[State.m_bufsize-1]).Sum();
|
|
vv=(State.m_bufyk[State.m_bufsize-1]*State.m_bufyk[State.m_bufsize-1]).Sum();
|
|
if(v==0.0 || vv==0.0)
|
|
{
|
|
//--- Strange internal error in LBFGS - either YK=0
|
|
//--- (which should not have been) or (SK,YK)=0 (again,
|
|
//--- unexpected). It should not take place because
|
|
//--- MCINFO=1, which signals "good" step. But just
|
|
//--- to be sure we have special branch of code which
|
|
//--- restarts LBFGS
|
|
label=60;
|
|
break;
|
|
}
|
|
State.m_bufrho.Set(State.m_bufsize-1,1/v);
|
|
//--- check
|
|
if(!CAp::Assert(State.m_bufsize<=m,__FUNCTION__+": internal error"))
|
|
return(false);
|
|
temp_v=State.m_xc-State.m_xp;
|
|
v=MathPow(temp_v/State.m_s.ToVector(),2.0).Sum();
|
|
vv=temp_v.Dot(temp_v);
|
|
UpdateEstimateOfGoodStep(State.m_lastscaledgoodstep,MathSqrt(v));
|
|
//--- Check MaxIts-based stopping condition.
|
|
if(State.m_maxits>0 && State.m_repiterationscount>=State.m_maxits)
|
|
{
|
|
State.m_repterminationtype=5;
|
|
result=false;
|
|
return(result);
|
|
}
|
|
//--- Smooth reset (LBFGS memory model is refreshed) or hard restart:
|
|
//--- * LBFGS model is refreshed, if line search was performed with activation of constraints
|
|
//--- * algorithm is restarted if scaled gradient decreased below GDecay
|
|
if(activationstatus)
|
|
{
|
|
State.m_bufsize=0;
|
|
label=59;
|
|
break;
|
|
}
|
|
temp_v=State.m_cgc*State.m_s;
|
|
v=temp_v.Dot(temp_v);
|
|
if(MathSqrt(v)<(gdecay*ginit))
|
|
{
|
|
label=60;
|
|
break;
|
|
}
|
|
case 59:
|
|
itidx++;
|
|
label=58;
|
|
break;
|
|
case 60:
|
|
//--- Decrease decay coefficient. Subsequent L-BFGS stages will
|
|
//--- have more stringent stopping criteria.
|
|
gdecay=MathMax(gdecay*m_decaycorrection,m_mindecay);
|
|
label=38;
|
|
break;
|
|
case 39:
|
|
result=false;
|
|
return(result);
|
|
}
|
|
}
|
|
//--- Saving State
|
|
result=true;
|
|
State.m_rstate.ba[0]=b;
|
|
State.m_rstate.ba[1]=activationstatus;
|
|
State.m_rstate.ia.Set(0,freezeidx);
|
|
State.m_rstate.ia.Set(1,n);
|
|
State.m_rstate.ia.Set(2,m);
|
|
State.m_rstate.ia.Set(3,i);
|
|
State.m_rstate.ia.Set(4,j);
|
|
State.m_rstate.ia.Set(5,mcinfo);
|
|
State.m_rstate.ia.Set(6,itidx);
|
|
State.m_rstate.ra.Set(0,freezeval);
|
|
State.m_rstate.ra.Set(1,scaleddnorm);
|
|
State.m_rstate.ra.Set(2,v);
|
|
State.m_rstate.ra.Set(3,vv);
|
|
State.m_rstate.ra.Set(4,v0);
|
|
State.m_rstate.ra.Set(5,ginit);
|
|
State.m_rstate.ra.Set(6,gdecay);
|
|
State.m_rstate.ra.Set(7,activationstep);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function activates / deactivates verification of the user - |
|
|
//| supplied analytic gradient. |
|
|
//| Upon activation of this option OptGuard integrity checker |
|
|
//| performs numerical differentiation of your target function at |
|
|
//| the initial point (note: future versions may also perform check |
|
|
//| at the final point) and compares numerical gradient with analytic|
|
|
//| one provided by you. |
|
|
//| If difference is too large, an error flag is set and optimization|
|
|
//| session continues. After optimization session is over, you can |
|
|
//| retrieve the report which stores both gradients and specific |
|
|
//| components highlighted as suspicious by the OptGuard. |
|
|
//| The primary OptGuard report can be retrieved with |
|
|
//| MinBCOptGuardResults(). |
|
|
//| IMPORTANT: gradient check is a high - overhead option which will |
|
|
//| cost you about 3*N additional function evaluations. In many cases|
|
|
//| it may cost as much as the rest of the optimization session. |
|
|
//| YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO |
|
|
//| CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY. |
|
|
//| NOTE: unlike previous incarnation of the gradient checking code, |
|
|
//| OptGuard does NOT interrupt optimization even if it |
|
|
//| discovers bad gradient. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure used to store algorithm State |
|
|
//| TestStep - verification step used for numerical |
|
|
//| differentiation: |
|
|
//| * TestStep = 0 turns verification off |
|
|
//| * TestStep > 0 activates verification |
|
|
//| You should carefully choose TestStep. Value |
|
|
//| which is too large (so large that function |
|
|
//| behavior is non-cubic at this scale) will lead |
|
|
//| to false alarms. Too short step will result in |
|
|
//| rounding errors dominating numerical derivative.|
|
|
//| You may use different step for different parameters by means of |
|
|
//| setting scale with MinBCSetScale(). |
|
|
//| === EXPLANATION ================================================ |
|
|
//| In order to verify gradient algorithm performs following steps: |
|
|
//| * two trial steps are made to X[i] - TestStep * S[i] and |
|
|
//| X[i] + TestStep * S[i], where X[i] is i-th component of the |
|
|
//| initial point and S[i] is a scale of i-th parameter |
|
|
//| * F(X) is evaluated at these trial points |
|
|
//| * we perform one more evaluation in the middle point of the |
|
|
//| interval |
|
|
//| * we build cubic model using function values and derivatives |
|
|
//| at trial points and we compare its prediction with actual |
|
|
//| value in the middle point |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCOptGuardGradient(CMinBCState &State,double teststep)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(teststep),__FUNCTION__+": TestStep contains NaN or INF"))
|
|
return;
|
|
if(!CAp::Assert(teststep>=0.0,__FUNCTION__+": invalid argument TestStep(TestStep<0)"))
|
|
return;
|
|
|
|
State.m_teststep=teststep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function activates / deactivates nonsmoothness monitoring |
|
|
//| option of the OptGuard integrity checker. Smoothness monitor |
|
|
//| silently observes solution process and tries to detect ill-posed |
|
|
//| problems, i.e. ones with: |
|
|
//| a) discontinuous target function(non - C0) |
|
|
//| b) nonsmooth target function(non - C1) |
|
|
//| Smoothness monitoring does NOT interrupt optimization even if it|
|
|
//| suspects that your problem is nonsmooth. It just sets |
|
|
//| corresponding flags in the OptGuard report which can be retrieved|
|
|
//| after optimization is over. |
|
|
//| Smoothness monitoring is a moderate overhead option which often |
|
|
//| adds less than 1 % to the optimizer running time. Thus, you can |
|
|
//| use it even for large scale problems. |
|
|
//| NOTE: OptGuard does NOT guarantee that it will always detect |
|
|
//| C0 / C1 continuity violations. |
|
|
//| First, minor errors are hard to catch - say, a 0.0001 difference|
|
|
//| in the model values at two sides of the gap may be due to |
|
|
//| discontinuity of the model - or simply because the model has |
|
|
//| changed. |
|
|
//| Second, C1 - violations are especially difficult to detect |
|
|
//| in a noninvasive way. The optimizer usually performs very |
|
|
//| short steps near the nonsmoothness, and differentiation usually|
|
|
//| introduces a lot of numerical noise. It is hard to tell |
|
|
//| whether some tiny discontinuity in the slope is due to real |
|
|
//| nonsmoothness or just due to numerical noise alone. |
|
|
//| Our top priority was to avoid false positives, so in some rare |
|
|
//| cases minor errors may went unnoticed(however, in most cases they|
|
|
//| can be spotted with restart from different initial point). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| Level - monitoring level: |
|
|
//| * 0 - monitoring is disabled |
|
|
//| * 1 - noninvasive low - overhead monitoring; |
|
|
//| function values and / or gradients are recorded,|
|
|
//| but OptGuard does not try to perform additional |
|
|
//| evaluations in order to get more information |
|
|
//| about suspicious locations. |
|
|
//| === EXPLANATION ================================================ |
|
|
//| One major source of headache during optimization is the |
|
|
//| possibility of the coding errors in the target function / |
|
|
//| constraints (or their gradients). Such errors most often |
|
|
//| manifest themselves as discontinuity or nonsmoothness of the|
|
|
//| target / constraints. |
|
|
//| Another frequent situation is when you try to optimize something |
|
|
//| involving lots of min() and max() operations, i.e. nonsmooth |
|
|
//| target. Although not a coding error, it is nonsmoothness anyway-|
|
|
//| and smooth optimizers usually stop right after encountering |
|
|
//| nonsmoothness, well before reaching solution. |
|
|
//| OptGuard integrity checker helps you to catch such situations: |
|
|
//| it monitors function values / gradients being passed to the |
|
|
//| optimizer and tries to errors. Upon discovering suspicious pair |
|
|
//| of points it raises appropriate flag (and allows you to continue|
|
|
//| optimization). When optimization is done, you can study OptGuard |
|
|
//| result. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCOptGuardSmoothness(CMinBCState &State,int level)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(level==0 || level==1,__FUNCTION__+": unexpected value of level parameter"))
|
|
return;
|
|
State.m_smoothnessguardlevel=level;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Results of OptGuard integrity check, should be called after |
|
|
//| optimization session is over. |
|
|
//| === PRIMARY REPORT ============================================= |
|
|
//| OptGuard performs several checks which are intended to catch |
|
|
//| common errors in the implementation of nonlinear function / |
|
|
//| gradient: |
|
|
//| * incorrect analytic gradient |
|
|
//| * discontinuous(non - C0) target functions(constraints) |
|
|
//| * nonsmooth(non - C1) target functions(constraints) |
|
|
//| Each of these checks is activated with appropriate function: |
|
|
//| * MinBCOptGuardGradient() for gradient verification |
|
|
//| * MinBCOptGuardSmoothness() for C0 / C1 checks |
|
|
//| Following flags are set when these errors are suspected: |
|
|
//| * rep.badgradsuspected, and additionally: |
|
|
//| * rep.badgradvidx for specific variable (gradient element) |
|
|
//| suspected |
|
|
//| * rep.badgradxbase, a point where gradient is tested |
|
|
//| * rep.badgraduser, user - provided gradient(stored as 2D |
|
|
//| matrix with single row in order to make report structure |
|
|
//| compatible with more complex optimizers like MinNLC or |
|
|
//| MinLM) |
|
|
//| * rep.badgradnum, reference gradient obtained via |
|
|
//| numerical differentiation (stored as 2D matrix with single|
|
|
//| row in order to make report structure compatible with more|
|
|
//| complex optimizers like MinNLC or MinLM) |
|
|
//| * rep.nonc0suspected |
|
|
//| * rep.nonc1suspected |
|
|
//| === ADDITIONAL REPORTS / LOGS ================================== |
|
|
//| Several different tests are performed to catch C0 / C1 errors, |
|
|
//| you can find out specific test signaled error by looking to: |
|
|
//| * rep.nonc0test0positive, for non - C0 test #0 |
|
|
//| * rep.nonc1test0positive, for non - C1 test #0 |
|
|
//| * rep.nonc1test1positive, for non - C1 test #1 |
|
|
//| Additional information (including line search logs) can be |
|
|
//| obtained by means of: |
|
|
//| * MinBCOptGuardNonC1Test0Results() |
|
|
//| * MinBCOptGuardNonC1Test1Results() |
|
|
//| which return detailed error reports, specific points where |
|
|
//| discontinuities were found, and so on. |
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - generic OptGuard report; more detailed reports |
|
|
//| can be retrieved with other functions. |
|
|
//| NOTE: false negatives (nonsmooth problems are not identified as |
|
|
//| nonsmooth ones) are possible although unlikely. |
|
|
//| The reason is that you need to make several evaluations |
|
|
//| around nonsmoothness in order to accumulate enough information |
|
|
//| about function curvature. Say, if you start right from the |
|
|
//| nonsmooth point, optimizer simply won't get enough data to |
|
|
//| understand what is going wrong before it terminates due to abrupt|
|
|
//| changes in the derivative. It is also possible that "unlucky"|
|
|
//| step will move us to the termination too quickly. |
|
|
//| Our current approach is to have less than 0.1 % false negatives|
|
|
//| in our test examples(measured with multiple restarts from |
|
|
//| random points), and to have exactly 0 % false positives. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCOptGuardResults(CMinBCState &State,COptGuardReport &rep)
|
|
{
|
|
COptServ::SmoothnessMonitorExportReport(State.m_smonitor,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//|Detailed results of the OptGuard integrity check for nonsmoothness|
|
|
//| test #0 |
|
|
//| Nonsmoothness (non - C1) test #0 studies function values (not |
|
|
//| gradient!) obtained during line searches and monitors behavior |
|
|
//| of the directional derivative estimate. |
|
|
//| This test is less powerful than test #1, but it does not depend|
|
|
//| on the gradient values and thus it is more robust against |
|
|
//| artifacts introduced by numerical differentiation. |
|
|
//| Two reports are returned: |
|
|
//| *a "strongest" one, corresponding to line search which |
|
|
//| had highest value of the nonsmoothness indicator |
|
|
//| *a "longest" one, corresponding to line search which had more |
|
|
//| function evaluations, and thus is more detailed |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; |
|
|
//| FALSE if test did not notice anything (in the|
|
|
//| latter cases fields below are empty). |
|
|
//| * x0[], d[] - arrays of length N which store initial point and |
|
|
//| direction for line search (d[] can be normalized,|
|
|
//| but does not have to) |
|
|
//| * stp[], f[] - arrays of length CNT which store step lengths |
|
|
//| and function values at these points; f[i] is |
|
|
//| evaluated in x0 + stp[i]*d. |
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb |
|
|
//| (usually we have stpidxb = stpidxa + 3, with |
|
|
//| most likely position of the violation between |
|
|
//| stpidxa + 1 and stpidxa + 2. |
|
|
//| ================================================================ |
|
|
//| = SHORTLY SPEAKING: build a 2D plot of (stp, f) and look at it - |
|
|
//| = you will see where C1 continuity is violated.|
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| strrep - C1 test #0 "strong" report |
|
|
//| lngrep - C1 test #0 "long" report |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCOptGuardNonC1Test0Results(CMinBCState &State,
|
|
COptGuardNonC1Test0Report &strrep,
|
|
COptGuardNonC1Test0Report &lngrep)
|
|
{
|
|
COptGuardApi::SmoothnessMonitorExportC1Test0Report(State.m_smonitor.m_nonc1test0strrep,State.m_lastscaleused,strrep);
|
|
COptGuardApi::SmoothnessMonitorExportC1Test0Report(State.m_smonitor.m_nonc1test0lngrep,State.m_lastscaleused,lngrep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Detailed results of the OptGuard integrity check for |
|
|
//| nonsmoothness test #1 |
|
|
//| Nonsmoothness (non-C1) test #1 studies individual components of |
|
|
//| the gradient computed during line search. |
|
|
//| When precise analytic gradient is provided this test is more |
|
|
//| powerful than test #0 which works with function values and |
|
|
//| ignores user-provided gradient. However, test #0 becomes more |
|
|
//| powerful when numerical differentiation is employed (in such |
|
|
//| cases test #1 detects higher levels of numerical noise and |
|
|
//| becomes too conservative). |
|
|
//| This test also tells specific components of the gradient which |
|
|
//| violate C1 continuity, which makes it more informative than #0, |
|
|
//| which just tells that continuity is violated. |
|
|
//| Two reports are returned: |
|
|
//| *a "strongest" one, corresponding to line search which had |
|
|
//| highest value of the nonsmoothness indicator |
|
|
//| *a "longest" one, corresponding to line search which had more |
|
|
//| function evaluations, and thus is more detailed |
|
|
//| In both cases following fields are returned: |
|
|
//| * positive - is TRUE when test flagged suspicious point; |
|
|
//| FALSE if test did not notice anything (in the |
|
|
//| latter cases fields below are empty). |
|
|
//| * vidx - is an index of the variable in [0, N) with nonsmooth |
|
|
//| derivative |
|
|
//| * x0[], d[] - arrays of length N which store initial point and|
|
|
//| direction for line search(d[] can be normalized,|
|
|
//| but does not have to) |
|
|
//| * stp[], g[] - arrays of length CNT which store step lengths |
|
|
//| and gradient values at these points; g[i] is |
|
|
//| evaluated in x0 + stp[i]*d and contains vidx-th |
|
|
//| component of the gradient. |
|
|
//| * stpidxa, stpidxb - we suspect that function violates C1 |
|
|
//| continuity between steps #stpidxa and #stpidxb |
|
|
//| (usually we have stpidxb = stpidxa + 3, with |
|
|
//| most likely position of the violation between |
|
|
//| stpidxa + 1 and stpidxa + 2. |
|
|
//| ================================================================ |
|
|
//| = SHORTLY SPEAKING: build a 2D plot of (stp, f) and look at it - |
|
|
//| = you will see where C1 continuity is violated.|
|
|
//| ================================================================ |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| strrep - C1 test #1 "strong" report |
|
|
//| lngrep - C1 test #1 "long" report |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCOptGuardNonC1Test1Results(CMinBCState &State,
|
|
COptGuardNonC1Test1Report &strrep,
|
|
COptGuardNonC1Test1Report &lngrep)
|
|
{
|
|
COptGuardApi::SmoothnessMonitorExportC1Test1Report(State.m_smonitor.m_nonc1test1strrep,State.m_lastscaleused,strrep);
|
|
COptGuardApi::SmoothnessMonitorExportC1Test1Report(State.m_smonitor.m_nonc1test1lngrep,State.m_lastscaleused,lngrep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| BC results |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array[0..N - 1], solution |
|
|
//| Rep - optimization report. You should check |
|
|
//| Rep.TerminationType in order to distinguish |
|
|
//| successful termination from unsuccessful one: |
|
|
//| * -8 internal integrity control detected infinite or |
|
|
//| NAN values in function / gradient. Abnormal |
|
|
//| termination signalled. |
|
|
//| * -3 inconsistent constraints. |
|
|
//| * 1 relative function improvement is no more than EpsF.|
|
|
//| * 2 scaled step is no more than EpsX. |
|
|
//| * 4 scaled gradient norm is no more than EpsG. |
|
|
//| * 5 MaxIts steps was taken |
|
|
//| * 8 terminated by user who called |
|
|
//| MinBCRequestTermination(). |
|
|
//| X contains point which was "current accepted" when termination |
|
|
//| request was submitted. More information about fields of this |
|
|
//| structure can be found in the comments on MinBCReport datatype. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCResults(CMinBCState &State,
|
|
CRowDouble &x,
|
|
CMinBCReport &rep)
|
|
{
|
|
x.Resize(0);
|
|
MinBCResultsBuf(State,x,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| BC results |
|
|
//| Buffered implementation of MinBCResults() which uses pre - |
|
|
//| allocated buffer to store X[]. If buffer size is too small, it |
|
|
//| resizes buffer. It is intended to be used in the inner cycles of |
|
|
//| performance critical algorithms where array reallocation penalty |
|
|
//| is too large to be ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCResultsBuf(CMinBCState &State,
|
|
CRowDouble &x,
|
|
CMinBCReport &rep)
|
|
{
|
|
//--- copy data
|
|
rep.m_iterationscount=State.m_repiterationscount;
|
|
rep.m_nfev=State.m_repnfev;
|
|
rep.m_varidx=State.m_repvaridx;
|
|
rep.m_terminationtype=State.m_repterminationtype;
|
|
|
|
if(State.m_repterminationtype>0)
|
|
x=State.m_xc;
|
|
else
|
|
x=vector<double>::Full(State.m_nmain,AL_NaN);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine restarts algorithm from new point. |
|
|
//| All optimization parameters (including constraints) are left |
|
|
//| unchanged. |
|
|
//| This function allows to solve multiple optimization problems |
|
|
//| (which must have same number of dimensions) without object |
|
|
//| reallocation penalty. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure previously allocated with MinBCCreate |
|
|
//| call. |
|
|
//| X - new starting point. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCRestartFrom(CMinBCState &State,CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
int n=State.m_nmain;
|
|
//--- First, check for errors in the inputs
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- Set XC
|
|
State.m_xstart=x;
|
|
//--- prepare RComm facilities
|
|
CAblasF::ISetAllocV(7,0,State.m_rstate.ia);
|
|
CAblasF::BSetAllocV(2,false,State.m_rstate.ba);
|
|
CAblasF::RSetAllocV(8,0,State.m_rstate.ra);
|
|
State.m_rstate.stage=-1;
|
|
ClearRequestFields(State);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine submits request for termination of running |
|
|
//| optimizer. It should be called from user-supplied callback when |
|
|
//| user decides that it is time to "smoothly" terminate optimization|
|
|
//| process. As result, optimizer stops at point which was "current |
|
|
//| accepted" when termination request was submitted and returns |
|
|
//| error code 8 (successful termination). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - optimizer structure |
|
|
//| NOTE: after request for termination optimizer may perform several|
|
|
//| additional calls to user-supplied callbacks. It does NOT |
|
|
//| guarantee to stop immediately - it just guarantees that |
|
|
//| these additional calls will be discarded later. |
|
|
//| NOTE: calling this function on optimizer which is NOT running |
|
|
//| will have no effect. |
|
|
//| NOTE: multiple calls to this function are possible. First call is|
|
|
//| counted, subsequent calls are silently ignored. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCRequestTermination(CMinBCState &State)
|
|
{
|
|
State.m_userterminationneeded=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Clears request fileds (to be sure that we don't forget to clear |
|
|
//| something) |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::ClearRequestFields(CMinBCState &State)
|
|
{
|
|
State.m_needf=false;
|
|
State.m_needfg=false;
|
|
State.m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal initialization subroutine. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::MinBCInitInternal(int n,CRowDouble &x,double diffstep,
|
|
CMinBCState &State)
|
|
{
|
|
//--- create variables
|
|
CMatrixDouble c;
|
|
CRowInt ct;
|
|
//--- Initialize
|
|
State.m_teststep=0;
|
|
State.m_smoothnessguardlevel=0;
|
|
COptServ::SmoothnessMonitorInit(State.m_smonitor,State.m_s,0,0,false);
|
|
State.m_nmain=n;
|
|
State.m_diffstep=diffstep;
|
|
CApServ::BVectorSetLengthAtLeast(State.m_HasBndL,n);
|
|
CApServ::BVectorSetLengthAtLeast(State.m_HasBndU,n);
|
|
State.m_bndl.Resize(n);
|
|
State.m_bndu.Resize(n);
|
|
State.m_xstart.Resize(n);
|
|
State.m_xc.Resize(n);
|
|
State.m_cgc.Resize(n);
|
|
State.m_ugc.Resize(n);
|
|
State.m_xn.Resize(n);
|
|
State.m_cgn.Resize(n);
|
|
State.m_ugn.Resize(n);
|
|
State.m_xp.Resize(n);
|
|
State.m_d.Resize(n);
|
|
State.m_s.Resize(n);
|
|
State.m_invs.Resize(n);
|
|
State.m_lastscaleused.Resize(n);
|
|
State.m_x.Resize(n);
|
|
State.m_g.Resize(n);
|
|
State.m_work.Resize(n);
|
|
State.m_bndl.Fill(AL_NEGINF);
|
|
ArrayInitialize(State.m_HasBndL,false);
|
|
State.m_bndu.Fill(AL_POSINF);
|
|
ArrayInitialize(State.m_HasBndU,false);
|
|
State.m_s.Fill(1.0);
|
|
State.m_invs.Fill(1.0);
|
|
State.m_lastscaleused.Fill(1.0);
|
|
MinBCSetCond(State,0.0,0.0,0.0,0);
|
|
MinBCSetXRep(State,false);
|
|
MinBCSetStpMax(State,0.0);
|
|
MinBCSetPrecDefault(State);
|
|
MinBCRestartFrom(State,x);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine updates estimate of the good step length given: |
|
|
//| 1) previous estimate |
|
|
//| 2) new length of the good step |
|
|
//| It makes sure that estimate does not change too rapidly - ratio |
|
|
//| of new and old estimates will be at least 0.01, at most 100.0 |
|
|
//| In case previous estimate of good step is zero (no estimate), new|
|
|
//| estimate is used unconditionally. |
|
|
//+------------------------------------------------------------------+
|
|
void CMinBC::UpdateEstimateOfGoodStep(double &estimate,double newstep)
|
|
{
|
|
if(estimate==0.0)
|
|
{
|
|
estimate=newstep;
|
|
return;
|
|
}
|
|
if(newstep<(estimate*0.01))
|
|
{
|
|
estimate*=0.01;
|
|
return;
|
|
}
|
|
if(newstep>estimate*100)
|
|
{
|
|
estimate*=100;
|
|
return;
|
|
}
|
|
estimate=newstep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is a test problem class intended for internal performance |
|
|
//| tests. |
|
|
//| Never use it directly in your projects. |
|
|
//+------------------------------------------------------------------+
|
|
struct CLPTestProblem
|
|
{
|
|
int m_m;
|
|
int m_n;
|
|
double m_targetf;
|
|
bool m_hasknowntarget;
|
|
CSparseMatrix m_a;
|
|
CRowDouble m_al;
|
|
CRowDouble m_au;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_c;
|
|
CRowDouble m_s;
|
|
//--- constructor / destructor
|
|
CLPTestProblem(void);
|
|
~CLPTestProblem(void) {}
|
|
//---
|
|
void Copy(const CLPTestProblem &obj);
|
|
//--- overloading
|
|
void operator=(const CLPTestProblem &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CLPTestProblem::CLPTestProblem(void)
|
|
{
|
|
m_m=0;
|
|
m_n=0;
|
|
m_targetf=0;
|
|
m_hasknowntarget=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CLPTestProblem::Copy(const CLPTestProblem &obj)
|
|
{
|
|
m_m=obj.m_m;
|
|
m_n=obj.m_n;
|
|
m_targetf=obj.m_targetf;
|
|
m_hasknowntarget=obj.m_hasknowntarget;
|
|
m_a=obj.m_a;
|
|
m_al=obj.m_al;
|
|
m_au=obj.m_au;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_c=obj.m_c;
|
|
m_s=obj.m_s;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class COPTS
|
|
{
|
|
public:
|
|
static void LPTestProblemCreate(int n,bool hasknowntarget,double targetf,CLPTestProblem &p);
|
|
static bool LPTestProblemHasKnownTarget(CLPTestProblem &p);
|
|
static double LPTestProblemGetTargetF(CLPTestProblem &p);
|
|
static int LPTestProblemGetN(CLPTestProblem &p);
|
|
static int LPTestProblemGetM(CLPTestProblem &p);
|
|
static void LPTestProblemSetScale(CLPTestProblem &p,CRowDouble &s);
|
|
static void LPTestProblemSetCost(CLPTestProblem &p,CRowDouble &c);
|
|
static void LPTestProblemSetBC(CLPTestProblem &p,CRowDouble &bndl,CRowDouble &bndu);
|
|
static void LPTestProblemSetLC2(CLPTestProblem &p,CSparseMatrix &a,CRowDouble &al,CRowDouble &au,int m);
|
|
static void LPTestProblemAlloc(CSerializer &s,CLPTestProblem &p);
|
|
static void LPTestProblemSerialize(CSerializer &s,CLPTestProblem &p);
|
|
static void LPTestProblemUnserialize(CSerializer &s,CLPTestProblem &p);
|
|
static void XDBGMinLPCreateFromTestProblem(CLPTestProblem &p,CMinLPState &State);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize test LP problem. |
|
|
//| This function is intended for internal use by ALGLIB. |
|
|
//+------------------------------------------------------------------+
|
|
void COPTS::LPTestProblemCreate(int n,bool hasknowntarget,
|
|
double targetf,CLPTestProblem &p)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
|
|
p.m_n=n;
|
|
p.m_hasknowntarget=hasknowntarget;
|
|
if(hasknowntarget)
|
|
p.m_targetf=targetf;
|
|
else
|
|
p.m_targetf=AL_NaN;
|
|
p.m_s=vector<double>::Ones(n);
|
|
p.m_c=vector<double>::Zeros(n);
|
|
p.m_bndl=vector<double>::Zeros(n);
|
|
p.m_bndu=vector<double>::Zeros(n);
|
|
p.m_m=0;
|
|
p.m_al.Resize(0);
|
|
p.m_au.Resize(0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Query test problem info |
|
|
//| This function is intended for internal use by ALGLIB. |
|
|
//+------------------------------------------------------------------+
|
|
bool COPTS::LPTestProblemHasKnownTarget(CLPTestProblem &p)
|
|
{
|
|
return(p.m_hasknowntarget);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Query test problem info |
|
|
//| This function is intended for internal use by ALGLIB. |
|
|
//+------------------------------------------------------------------+
|
|
double COPTS::LPTestProblemGetTargetF(CLPTestProblem &p)
|
|
{
|
|
return(p.m_targetf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Query test problem info |
|
|
//| This function is intended for internal use by ALGLIB. |
|
|
//+------------------------------------------------------------------+
|
|
int COPTS::LPTestProblemGetN(CLPTestProblem &p)
|
|
{
|
|
return(p.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Query test problem info |
|
|
//| This function is intended for internal use by ALGLIB. |
|
|
//+------------------------------------------------------------------+
|
|
int COPTS::LPTestProblemGetM(CLPTestProblem &p)
|
|
{
|
|
return(p.m_m);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Set scale for test LP problem |
|
|
//| This function is intended for internal use by ALGLIB. |
|
|
//+------------------------------------------------------------------+
|
|
void COPTS::LPTestProblemSetScale(CLPTestProblem &p,CRowDouble &s)
|
|
{
|
|
p.m_s=s;
|
|
p.m_s.Resize(p.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Set cost for test LP problem |
|
|
//| This function is intended for internal use by ALGLIB. |
|
|
//+------------------------------------------------------------------+
|
|
void COPTS::LPTestProblemSetCost(CLPTestProblem &p,CRowDouble &c)
|
|
{
|
|
p.m_c=c;
|
|
p.m_c.Resize(p.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Set box constraints for test LP problem |
|
|
//| This function is intended for internal use by ALGLIB. |
|
|
//+------------------------------------------------------------------+
|
|
void COPTS::LPTestProblemSetBC(CLPTestProblem &p,CRowDouble &bndl,
|
|
CRowDouble &bndu)
|
|
{
|
|
p.m_bndl=bndl;
|
|
p.m_bndl.Resize(p.m_n);
|
|
p.m_bndu=bndu;
|
|
p.m_bndu.Resize(p.m_n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Set box constraints for test LP problem |
|
|
//| This function is intended for internal use by ALGLIB. |
|
|
//+------------------------------------------------------------------+
|
|
void COPTS::LPTestProblemSetLC2(CLPTestProblem &p,CSparseMatrix &a,
|
|
CRowDouble &al,
|
|
CRowDouble &au,
|
|
int m)
|
|
{
|
|
//--- quick exit
|
|
if(m<=0)
|
|
{
|
|
p.m_m=0;
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(a)==m,__FUNCTION__+": rows(A)<>M"))
|
|
return;
|
|
p.m_m=m;
|
|
CSparse::SparseCopyToCRS(a,p.m_a);
|
|
p.m_al=al;
|
|
p.m_au=au;
|
|
p.m_al.Resize(m);
|
|
p.m_au.Resize(m);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
void COPTS::LPTestProblemAlloc(CSerializer &s,CLPTestProblem &p)
|
|
{
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
CApServ::AllocRealArray(s,p.m_s,p.m_n);
|
|
CApServ::AllocRealArray(s,p.m_c,p.m_n);
|
|
CApServ::AllocRealArray(s,p.m_bndl,p.m_n);
|
|
CApServ::AllocRealArray(s,p.m_bndu,p.m_n);
|
|
s.Alloc_Entry();
|
|
if(p.m_m>0)
|
|
{
|
|
CSparse::SparseAlloc(s,p.m_a);
|
|
CApServ::AllocRealArray(s,p.m_al,p.m_m);
|
|
CApServ::AllocRealArray(s,p.m_au,p.m_m);
|
|
}
|
|
s.Alloc_Entry();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
void COPTS::LPTestProblemSerialize(CSerializer &s,CLPTestProblem &p)
|
|
{
|
|
s.Serialize_Int(CSCodes::GetLpTestSerializationCode());
|
|
s.Serialize_Int(0);
|
|
s.Serialize_Int(p.m_n);
|
|
s.Serialize_Bool(p.m_hasknowntarget);
|
|
s.Serialize_Double(p.m_targetf);
|
|
CApServ::SerializeRealArray(s,p.m_s,p.m_n);
|
|
CApServ::SerializeRealArray(s,p.m_c,p.m_n);
|
|
CApServ::SerializeRealArray(s,p.m_bndl,p.m_n);
|
|
CApServ::SerializeRealArray(s,p.m_bndu,p.m_n);
|
|
s.Serialize_Int(p.m_m);
|
|
if(p.m_m>0)
|
|
{
|
|
CSparse::SparseSerialize(s,p.m_a);
|
|
CApServ::SerializeRealArray(s,p.m_al,p.m_m);
|
|
CApServ::SerializeRealArray(s,p.m_au,p.m_m);
|
|
}
|
|
s.Serialize_Int(872);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
void COPTS::LPTestProblemUnserialize(CSerializer &s,CLPTestProblem &p)
|
|
{
|
|
int k=s.Unserialize_Int();
|
|
//--- check
|
|
if(!CAp::Assert(k==CSCodes::GetLpTestSerializationCode(),__FUNCTION__+": stream header corrupted"))
|
|
return;
|
|
k=s.Unserialize_Int();
|
|
//--- check
|
|
if(!CAp::Assert(k==0,__FUNCTION__+": stream header corrupted"))
|
|
return;
|
|
p.m_n=s.Unserialize_Int();
|
|
p.m_hasknowntarget=s.Unserialize_Bool();
|
|
p.m_targetf=s.Unserialize_Double();
|
|
CApServ::UnserializeRealArray(s,p.m_s);
|
|
CApServ::UnserializeRealArray(s,p.m_c);
|
|
CApServ::UnserializeRealArray(s,p.m_bndl);
|
|
CApServ::UnserializeRealArray(s,p.m_bndu);
|
|
p.m_m=s.Unserialize_Int();
|
|
if(p.m_m>0)
|
|
{
|
|
CSparse::SparseUnserialize(s,p.m_a);
|
|
CApServ::UnserializeRealArray(s,p.m_al);
|
|
CApServ::UnserializeRealArray(s,p.m_au);
|
|
}
|
|
k=s.Unserialize_Int();
|
|
//--- check
|
|
if(!CAp::Assert(k==872,__FUNCTION__+": end-of-stream marker not found"))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is internal function intended to be used only by ALGLIB |
|
|
//| itself. Although for technical reasons it is made publicly |
|
|
//| available (and has its own manual entry), you should never call |
|
|
//| it. |
|
|
//+------------------------------------------------------------------+
|
|
void COPTS::XDBGMinLPCreateFromTestProblem(CLPTestProblem &p,
|
|
CMinLPState &State)
|
|
{
|
|
CMinLP::MinLPCreate(p.m_n,State);
|
|
CMinLP::MinLPSetScale(State,p.m_s);
|
|
CMinLP::MinLPSetCost(State,p.m_c);
|
|
CMinLP::MinLPSetBC(State,p.m_bndl,p.m_bndu);
|
|
CMinLP::MinLPSetLC2(State,p.m_a,p.m_al,p.m_au,p.m_m);
|
|
}
|
|
//+------------------------------------------------------------------+
|