Mercurial > forge
changeset 6992:45a51449b113 octave-forge
- leasqr.m:
-- Added general inequality constraints (Reference: Bard, Y., 'An
eclectic approach to nonlinear programming', Proc. ANU
Sem. Optimization, Canberra, Austral. Nat. Univ.). Differences from
the reference: adaption to svd-based algorithm, linesearch or
stepwidth adaptions to ensure decrease in objective function
omitted to rather start a new overall cycle with a new epsL, some
performance gains from linear constraints even if general
constraints are specified.
-- Made complementary pivot algorithm configurable (but no alternative
supplied).
- private/__dfdp__.m: Moved code from dfdp.m to here for more common use.
- dfdp.m: Is now an interface to private/__dfdp__.m, functionally
unchanged.
- dcdp.m: New file. Interface to private/__dfdp__.m for passed
functions only of parameters.
- cpiv.m: Renamed to cpiv_bard.m.
author | i7tiol |
---|---|
date | Tue, 13 Apr 2010 12:51:54 +0000 |
parents | 862639e94dbf |
children | 27212aa967b0 |
files | main/optim/INDEX main/optim/inst/cpiv.m main/optim/inst/cpiv_bard.m main/optim/inst/dcdp.m main/optim/inst/dfdp.m main/optim/inst/leasqr.m main/optim/inst/private/__dfdp__.m |
diffstat | 7 files changed, 605 insertions(+), 200 deletions(-) [+] |
line wrap: on
line diff
--- a/main/optim/INDEX Mon Apr 12 12:14:09 2010 +0000 +++ b/main/optim/INDEX Tue Apr 13 12:51:54 2010 +0000 @@ -22,8 +22,11 @@ quadprog= try Yinyu Ye's <a href="http://dollar.biz.uiowa.edu/col/ye/matlab.html">code</a> linprog Numerical derivatives - dfdp cdiff deriv leval + dfdp dcdp cdiff deriv leval numgradient numhessian +Pivoting + cpiv_bard.m + gjp.m Tests test_min_1 test_min_2 test_min_3 test_min_4 test_d2_min_1 test_d2_min_2 test_d2_min_3
--- a/main/optim/inst/cpiv.m Mon Apr 12 12:14:09 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -%% Copyright (C) 2010 Olaf Till -%% -%% This program 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; 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. -%% -%% You should have received a copy of the GNU General Public License -%% along with this program; if not, write to the Free Software -%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307USA - -function [lb, idx] = cpiv (v, m) - - %% [lb, idx] = cpiv (v, m) - %% - %% v: column vector; m: matrix. length (v) must equal rows (m). m must - %% be positive definit, which is not be explicitely checked. Finds - %% column vectors w and l with w == v + m * l, w >= 0, l >= 0, l.' * w - %% == 0. lb: column vector of components of l for which the - %% corresponding components of w are zero; idx: logical index of these - %% components in l. This is called solving the 'complementary pivot - %% problem' (Cottle, R. W. and Dantzig, G. B., 'Complementary pivot - %% theory of mathematical programming', Linear Algebra and Appl. 1, - %% 102--125. References for the current algorithm: Bard, Y.: Nonlinear - %% Parameter Estimation, p. 147--149, Academic Press, New York and - %% London 1974; Bard, Y., 'An eclectic approach to nonlinear - %% programming', Proc. ANU Sem. Optimization, Canberra, Austral. Nat. - %% Univ.). - - n = length (v); - if (n > size (v, 1)) - error ('first argument is no column vector'); % the most typical mistake - end - m = cat (2, m, v); - id = ones (n, 1); - nz = -eps; % This is arbitrary; components of w and -l are regarded as - % non-negative if >= nz. - nl = 100 * n; % maximum number of loop repeats, after that give up - ready = false; - while (~ ready && nl > 0) - [vm, idm] = min (id .* m(:, end)); - if (vm >= nz) - ready = true; - else - id(idm) = -id(idm); - m = gjp (m, idm); - nl = nl - 1; - end - end - if (~ ready) - error ('not successful'); - end - idx = id < 0; - lb = -m(idx, end);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/optim/inst/cpiv_bard.m Tue Apr 13 12:51:54 2010 +0000 @@ -0,0 +1,75 @@ +%% Copyright (C) 2010 Olaf Till +%% +%% This program 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; 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. +%% +%% You should have received a copy of the GNU General Public License +%% along with this program; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307USA + +function [lb, idx, ridx, m] = cpiv_bard (v, m, incl) + + %% [lb, idx, ridx, mv] = cpiv_bard (v, m[, incl]) + %% + %% v: column vector; m: matrix; incl (optional): index. length (v) + %% must equal rows (m). Finds column vectors w and l with w == v + m * + %% l, w >= 0, l >= 0, l.' * w == 0. Chooses idx, w, and l so that + %% l(~idx) == 0, l(idx) == -inv (m(idx, idx)) * v(idx), w(idx) roughly + %% == 0, and w(~idx) == v(~idx) + m(idx, ~idx).' * l(idx). idx indexes + %% at least everything indexed by incl. lb: l(idx) (column vector); + %% idx: logical index, defined above; ridx: ~idx & w roughly == 0; mv: + %% [m, v] after performing a Gauss-Jordan 'sweep' (with gjp.m) on each + %% diagonal element indexed by idx. This is called solving the + %% 'complementary pivot problem' (Cottle, R. W. and Dantzig, G. B., + %% 'Complementary pivot theory of mathematical programming', Linear + %% Algebra and Appl. 1, 102--125. References for the current + %% algorithm: Bard, Y.: Nonlinear Parameter Estimation, p. 147--149, + %% Academic Press, New York and London 1974; Bard, Y., 'An eclectic + %% approach to nonlinear programming', Proc. ANU Sem. Optimization, + %% Canberra, Austral. Nat. Univ.). + + n = length (v); + if (n > size (v, 1)) + error ('first argument is no column vector'); % the most typical mistake + end + if (nargin < 3) + incl = []; + elseif (islogical (incl)) + incl = find (incl); + end + nincl = 1:n; + nincl(incl) = []; + m = cat (2, m, v); + sgn = ones (n, 1); + for id = incl + sgn(id) = -sgn(id); + m = gjp (m, id); + end + nz = eps; % This is arbitrary; components of w and -l are regarded as + % non-negative if >= -nz. + nl = 100 * n; % maximum number of loop repeats, after that give up + ready = false; + while (~ready && nl > 0) + [vm, idm] = min (sgn(nincl) .* m(nincl, end)); + if (vm >= -nz) + ready = true; + else + idm = nincl(idm); + sgn(idm) = -sgn(idm); + m = gjp (m, idm); + nl = nl - 1; + end + end + if (~ready || any (m(incl, end) > nz)) + error ('not successful'); + end + idx = sgn < 0; + lb = -m(idx, end); + ridx = ~idx & abs (m(:, end)) <= nz;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/optim/inst/dcdp.m Tue Apr 13 12:51:54 2010 +0000 @@ -0,0 +1,28 @@ +%% Copyright (C) 2010 Olaf Till <olaf.till@uni-jena.de> +%% +%% This program 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; 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. +%% +%% You should have received a copy of the GNU General Public License +%% along with this program; If not, see <http://www.gnu.org/licenses/>. + +function prt = dcdp (varargin) + + %% function prt = dcdp (f, p, dp, func[, bounds]) + %% + %% This is an interface to __dfdp__.m, similar to dfdp.m, but for + %% functions only of parameters 'p', not of independents 'x'. See + %% dfdp.m. + + if (ischar (varargin{4})) + varargin{4} = str2func (varargin{4}); + end + + prt = __dfdp__ (varargin{:});
--- a/main/optim/inst/dfdp.m Mon Apr 12 12:14:09 2010 +0000 +++ b/main/optim/inst/dfdp.m Tue Apr 13 12:51:54 2010 +0000 @@ -1,89 +1,50 @@ -% Copyright (C) 1992-1994 Richard Shrager -% Copyright (C) 1992-1994 Arthur Jutan -% Copyright (C) 1992-1994 Ray Muzic -% -% This program 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; 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. -% -% You should have received a copy of the GNU General Public License -% along with this program; If not, see <http://www.gnu.org/licenses/>. +%% Copyright (C) 1992-1994 Richard Shrager +%% Copyright (C) 1992-1994 Arthur Jutan +%% Copyright (C) 1992-1994 Ray Muzic +%% Copyright (C) 2010 Olaf Till <olaf.till@uni-jena.de> +%% +%% This program 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; 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. +%% +%% You should have received a copy of the GNU General Public License +%% along with this program; If not, see <http://www.gnu.org/licenses/>. -function prt=dfdp(x,f,p,dp,func,bounds) -% function prt = dfdp (x, f, p, dp, func[, bounds]) -% numerical partial derivatives (Jacobian) df/dp for use with leasqr -% --------INPUT VARIABLES--------- -% x=vec or matrix of indep var(used as arg to func) x=[x0 x1 ....] -% f=func(x,p) vector initialsed by user before each call to dfdp -% p= vec of current parameter values -% dp= fractional increment of p for numerical derivatives -% dp(j)>0 central differences calculated -% dp(j)<0 one sided differences calculated -% dp(j)=0 sets corresponding partials to zero; i.e. holds p(j) fixed -% func=string naming the function (.m) file -% e.g. to calc Jacobian for function expsum prt=dfdp(x,f,p,dp,'expsum') -% bounds=two-column-matrix of lower and upper bounds for parameters -% If no 'bounds' options is specified to leasqr, it will call -% dfdp without the 'bounds' argument. -%----------OUTPUT VARIABLES------- -% prt= Jacobian Matrix prt(i,j)=df(i)/dp(j) -%================================ +function prt = dfdp (varargin) -m=size(x,1); if (m==1), m=size(x,2); end %# PAK: in case #cols > #rows -n=length(p); %dimensions -if (nargin < 6) - bounds = ones (n, 2); - bounds(:, 1) = -Inf; - bounds(:, 2) = Inf; -end -prt=zeros(m,n); % initialise Jacobian to Zero -del = dp .* p; %cal delx=fract(dp)*param value(p) -idx = p == 0; -del(idx) = dp(idx); %if param=0 delx=fraction -idx = dp > 0; -del(idx) = abs (del(idx)); % not for one-sided intervals, changed - % direction of intervals could change - % behavior of optimization without bounds -min_del = min (abs (del), bounds(:, 2) - bounds(:, 1)); - for j=1:n - ps = p; - if (dp(j)~=0) - if (dp(j) < 0) - ps(j) = p(j) + del(j); - if (ps(j) < bounds(j, 1) || ps(j) > bounds(j, 2)) - t_del1 = max (bounds(j, 1) - p(j), - abs (del(j))); % - %non-positive - t_del2 = min (bounds(j, 2) - p(j), abs (del(j))); % - %non-negative - if (- t_del1 > t_del2) - del(j) = t_del1; - else - del(j) = t_del2; - end - ps(j) = p(j) + del(j); - end - prt(:, j) = (feval (func, x, ps) - f) / del(j); - else - if (p(j) - del(j) < bounds(j, 1)) - tp = bounds(j, 1); - ps(j) = tp + min_del(j); - elseif (p(j) + del(j) > bounds(j, 2)) - ps(j) = bounds(j, 2); - tp = ps(j) - min_del(j); - else - ps(j) = p(j) + del(j); - tp = p(j) - del(j); - min_del(j) = 2 * del(j); - end - f1 = feval (func, x, ps); - ps(j) = tp; - prt(:, j) = (f1 - feval (func, x, ps)) / min_del(j); - end - end + %% function prt = dfdp (x, f, p, dp, func[, bounds]) + %% numerical partial derivatives (Jacobian) df/dp for use with leasqr + %% --------INPUT VARIABLES--------- + %% x=vec or matrix of indep var(used as arg to func) x=[x0 x1 ....] + %% f=func(x,p) vector initialsed by user before each call to dfdp + %% p= vec of current parameter values + %% dp= fractional increment of p for numerical derivatives + %% dp(j)>0 central differences calculated + %% dp(j)<0 one sided differences calculated + %% dp(j)=0 sets corresponding partials to zero; i.e. holds p(j) fixed + %% func=function (string or handle) to calculate the Jacobian for, + %% e.g. to calc Jacobian for function expsum prt=dfdp(x,f,p,dp,'expsum') + %% bounds=two-column-matrix of lower and upper bounds for parameters + %% If no 'bounds' options is specified to leasqr, it will call + %% dfdp without the 'bounds' argument. + %%----------OUTPUT VARIABLES------- + %% prt= Jacobian Matrix prt(i,j)=df(i)/dp(j) + %%================================ + + %% This is just an interface. The original code has been moved to + %% __dfdp__.m, which is used with two different interfaces by + %% leasqr.m. + + if (ischar (varargin{5})) + varargin{5} = @ (p) str2func (varargin{5}) (varargin{1}, p); + else + varargin{5} = @ (p) varargin{5} (varargin{1}, p); end + + prt = __dfdp__ (varargin{2:end});
--- a/main/optim/inst/leasqr.m Mon Apr 12 12:14:09 2010 +0000 +++ b/main/optim/inst/leasqr.m Tue Apr 13 12:51:54 2010 +0000 @@ -72,14 +72,48 @@ %% [ie. abs(chg(i))=abs(min([chg(i) %% options.max_fract_change(i)*current param estimate])).], default = %% Inf*ones(). - %% Field 'options.inequc': cell-array containing a matrix (say m) - %% and a column vector (say v), specifying linear inequality - %% constraints of the form `m.' * parameters + v >= 0'. If the - %% constraints are just bounds, it is suggested to specify them in - %% 'options.bounds' instead, since then some sanity tests are - %% performed, and since the function 'dfdp.m' is guarantied not to - %% violate constraints during determination of the numeric gradient - %% only for those constraints specified as 'bounds'. + %% Field 'options.inequc': cell-array containing up to four entries, + %% two entries for linear inequality constraints and/or one or two + %% entries for general inequality constraints. Either linear or + %% general constraints may be the first entries, but the two entries + %% for linear constraints must be adjacent and, if two entries are + %% given for general constraints, they also must be adjacent. The + %% two entries for linear constraints are a matrix (say m) and a + %% vector (say v), specifying linear inequality constraints of the + %% form `m.' * parameters + v >= 0'. If the constraints are just + %% bounds, it is suggested to specify them in 'options.bounds' + %% instead, since then some sanity tests are performed, and since + %% the function 'dfdp.m' is guarantied not to violate constraints + %% during determination of the numeric gradient only for those + %% constraints specified as 'bounds'. The first entry for general + %% constraints must be a differentiable vector valued function (say + %% h), specifying general inequality constraints of the form `h (p[, + %% idx]) >= 0'; p is the column vector of optimized paraters and the + %% optional argument idx is a logical index. h has to return the + %% values of all constraints if idx is not given, and has to return + %% only the indexed constraints if idx is given (so computation of + %% the other constraints can be spared). If a second entry for + %% general constraints is given, it must be a function (say dh) + %% which returnes a matrix whos rows contain the gradients of the + %% constraint function h with respect to the optimized parameters. + %% It has the form jac_h = dh (vh, p, dp, h, idx[, bounds]); p is + %% the column vector of optimized parameters, and idx is a logical + %% index --- only the rows indexed by idx must be returned (so + %% computation of the others can be spared). The other arguments of + %% dh are for the case that dh computes numerical gradients: vh is + %% the column vector of the current values of the constraint + %% function h, with idx already applied. h is a function h (p) to + %% compute the values of the constraints for parameters p, it will + %% return only the values indexed by idx. dp is a suggestion for + %% relative step width, having the same value as the argument 'dp' + %% of leasqr above. If bounds were specified to leasqr, they are + %% provided in the argument bounds of dh, to enable their + %% consideration in determination of numerical gradients. If dh is + %% not specified to leasqr, numerical gradients are computed in the + %% same way as with 'dfdp.m' (see above). If some constraints are + %% linear, they should be specified as linear constraints (or + %% bounds, if applicable) for reasons of performance, even if + %% general constraints are also specified. %% Field 'options.bounds': two-column-matrix, one row for each %% parameter in 'pin'. Each row contains a minimal and maximal value %% for each parameter. Default: [-Inf, Inf] in each row. If this @@ -88,6 +122,9 @@ %% _Warning_: If constraints (or bounds) are set, returned guesses %% of corp, covp, and Z are generally invalid, even if no constraints %% are active for the final parameters. + %% Field 'options.cpiv': Function for complementary pivot algorithm + %% for inequality constraints, default: @ cpiv_bard. No different + %% function is supplied. %% %% OUTPUT VARIABLES %% f = column vector of values computed: f = F(x,p). @@ -161,13 +198,26 @@ %% Modified by Francesco Potort́ %% for use in Octave %% Added linear inequality constraints with quadratic programming to - %% this file and special case bounds to this file and to dfdp.m, Olaf - %% Till 24-Feb-2010 + %% this file and special case bounds to this file and to dfdp.m + %% (24-Feb-2010) and later also general inequality constraints + %% (12-Apr-2010) (Reference: Bard, Y., 'An eclectic approach to + %% nonlinear programming', Proc. ANU Sem. Optimization, Canberra, + %% Austral. Nat. Univ.). Differences from the reference: adaption to + %% svd-based algorithm, linesearch or stepwidth adaptions to ensure + %% decrease in objective function omitted to rather start a new + %% overall cycle with a new epsL, some performance gains from linear + %% constraints even if general constraints are specified. Olaf Till + %% %% References: %% Bard, Nonlinear Parameter Estimation, Academic Press, 1974. %% Draper and Smith, Applied Regression Analysis, John Wiley and Sons, 1981. + %% needed for some anonymous functions + if (exist ('ifelse') ~= 5) + ifelse = @ scalar_ifelse; + end + %% argument processing %% @@ -188,11 +238,16 @@ verbose=0; %This will not tell them the results end - if (nargin <= 8) dFdp='dfdp'; end + if (nargin <= 8) + dFdp = @ dfdp; + elseif (ischar (dFdp)) + dFdp = str2func (dFdp); + end if (nargin <= 7) dp=.001*(pin*0+1); end %DT if (nargin <= 6) wt=ones(length(y),1); end % SMB modification if (nargin <= 5) niter=20; end if (nargin == 4) stol=.0001; end + if (ischar (F)) F = str2func (F); end %% y=y(:); wt=wt(:); pin=pin(:); dp=dp(:); %change all vectors to columns @@ -206,10 +261,13 @@ %% processing of 'options' pprec = zeros (n, 1); maxstep = Inf * ones (n, 1); + have_gencstr = false; % no general constraints + n_gencstr = 0; mc = zeros (n, 0); - vc = zeros (0, 1); + vc = zeros (0, 1); rv = 0; bounds = cat (2, -Inf * ones (n, 1), Inf * ones (n, 1)); - dfdp_cmd = 'feval(dFdp,x,fbest,p,dp,F);'; % will possibly be redefined + dfdp_bounds = {}; + cpiv = @ cpiv_bard; if (nargin > 9) if (ismatrix (options)) % backwards compatibility tp = options; @@ -218,6 +276,16 @@ options.max_fract_change = tp(:, 2); end end + if (isfield (options, 'cpiv') && ~isempty (options.cpiv)) + %% As yet there is only one cpiv function distributed with leasqr, + %% but this may change; cpiv_bard is said to be relatively fast, + %% but may have disadvantages. + if (ischar (options.cpiv)) + cpiv = str2func (options.cpiv); + else + cpiv = options.cpiv; + end + end if (isfield (options, 'fract_prec')) pprec = options.fract_prec; if (rows (pprec) ~= n || columns (pprec) ~= 1) @@ -231,19 +299,59 @@ end end if (isfield (options, 'inequc')) - mc = options.inequc{1}; - vc = options.inequc{2}; + inequc = options.inequc; + if (ismatrix (inequc{1})) + mc = inequc{1}; + vc = inequc{2}; + if (length (inequc) > 2) + have_gencstr = true; + f_gencstr = inequc{3}; + if (length (inequc) > 3) + df_gencstr = inequc{4}; + else + df_gencstr = @ dcdp; + end + end + else + lid = 0; % no linear constraints + have_gencstr = true; + f_gencstr = inequc{1}; + if (length (inequc) > 1) + if (ismatrix (inequc{2})) + lid = 2; + df_gencstr = @ dcdp; + else + df_gencstr = inequc{2}; + if (length (inequc) > 2) + lid = 3; + end + end + else + df_gencstr = @ dcdp; + end + if (lid) + mc = inequc{lid}; + vc = inequc{lid + 1}; + end + end + if (have_gencstr) + if (ischar (f_gencstr)) f_gencstr = str2func (f_gencstr); end + if (ischar (df_gencstr)) df_gencstr = str2func (df_gencstr); end + end [rm, cm] = size (mc); [rv, cv] = size (vc); if (rm ~= n || cm ~= rv || cv ~= 1) - error ('inequality constraints: wrong dimensions'); + error ('linear inequality constraints: wrong dimensions'); end - if (any (mc.' * pin + vc < 0)) + if (have_gencstr) + tp = f_gencstr (pin); + n_gencstr = length (tp); + end + if (any (mc.' * pin + vc < 0) || (have_gencstr && any (tp < 0))) error ('initial parameters violate inequality constraints'); end end if (isfield (options, 'bounds')) - dfdp_cmd = 'feval(dFdp,x,fbest,p,dp,F,bounds);'; bounds = options.bounds; if (rows (bounds) ~= n || columns (bounds) ~= 2) error ('bounds: wrong dimensions'); @@ -268,12 +376,44 @@ pin(idx) = bounds(idx, 2); end tp = eye (n); - lidx = ~ isinf (bounds(:, 1)); - uidx = ~ isinf (bounds(:, 2)); + lidx = ~isinf (bounds(:, 1)); + uidx = ~isinf (bounds(:, 2)); mc = cat (2, mc, tp(:, lidx), - tp(:, uidx)); vc = cat (1, vc, - bounds(lidx, 1), bounds(uidx, 2)); + [rm, cm] = size (mc); + [rv, cv] = size (vc); + dfdp_bounds = {bounds}; end end + nidxl = 1:rv; + nidxh = rv+1:rv+n_gencstr; + if (have_gencstr) + f_cstr = @ (p, idx) ... + cat (1, mc(:, idx(nidxl)).' * p + vc(idx(nidxl), 1), ... + tf_gencstr (p, idx(nidxh), f_gencstr)); + if (strcmp (func2str (df_gencstr), 'dcdp')) + df_cstr = @ (f, p, idx) ... + cat (1, mc(:, idx(nidxl)).', ... + df_gencstr (f(nidxh(idx(nidxh))), p, dp, ... + @ (tp) tf_gencstr (tp, idx(nidxh), ... + f_gencstr), ... + dfdp_bounds{:})); + else + df_cstr = @ (f, p, idx) ... + cat (1, mc(:, idx(nidxl)).', ... + df_gencstr (f(nidxh(idx(nidxh))), p, dp, ... + @ (tp) tf_gencstr (tp, idx(nidxh), ... + f_gencstr), ... + idx(nidxh), ... + dfdp_bounds{:})); + end + if (any (~isinf (maxstep))) + warning ('setting both a maximum fractional step change of parameters and general constraints may result in inefficiency and failure'); + end + else + f_cstr = @ (p, idx) mc(:, idx).' * p + vc(idx, 1); + df_cstr = @ (f, p, idx) mc(:, idx).'; + end if (all (dp == 0)) error ('no free parameters'); @@ -282,7 +422,7 @@ %% set up for iterations %% p = pin; - f=feval(F,x,p); fbest=f; pbest=p; + f = F (x, p); fbest=f; pbest=p; r=wt.*(y-f); if (~isreal (r)) error ('weighted residuals are not real'); end ss = r.' * r; @@ -291,25 +431,43 @@ cvg=0; epsLlast=1; epstab=[.1, 1, 1e2, 1e4, 1e6]; + ac_idx = true (rv + n_gencstr, 1); % all constraints + nc_idx = false (rv + n_gencstr, 1); % non of all constraints + gc_idx = cat (1, false (rv, 1), true (n_gencstr, 1)); % gen. constr. + lc_idx = ~gc_idx; %% for testing %% new_s = false; %% if (isfield (options, 'new_s')) %% new_s = options.new_s; %% end + %% if (isfield (options, 'testing') && options.testing) + %% testing = true; + %% else + %% testing = false; + %% end - nz = eps; % This is arbitrary. Constraint fuction will be regarded as - % <= zero if less than nz. + nz = 20 * eps; % This is arbitrary. Constraint function will be + % regarded as <= zero if less than nz. %% do iterations %% for iter=1:niter - c_act = mc.' * p + vc < nz; % index of active constraints - mca = mc(:, c_act); - vca = vc(c_act); - mcat = mca.'; + %% deb_printf (testing, '\nstart outer iteration\n'); + v_cstr = f_cstr (p, ac_idx); + c_act = v_cstr < nz; % index of active constraints + if (any (c_act)) + if (have_gencstr) + dct = df_cstr (v_cstr, p, ac_idx); + dc = dct.'; + dcat = dct(c_act, :); + else + dcat = df_cstr (v_cstr, p, c_act); + end + dca = dcat.'; + end nrm = zeros (1, n); pprev=pbest; - prt = eval (dfdp_cmd); + prt = dFdp (x, fbest, p, dp, F, dfdp_bounds{:}); r=wt.*(y-fbest); if (~isreal (r)) error ('weighted residuals are not real'); end sprev=sbest; @@ -325,6 +483,7 @@ s=diag(s); g = prt.' * r; for jjj=1:length(epstab) + %% deb_printf (testing, '\nstart inner iteration\n'); epsL = max(epsLlast*epstab(jjj),1e-7); %% printf ('epsL: %e\n', epsL); % for testing @@ -347,40 +506,176 @@ %% end tp1 = (v * (g .* ser)) .* nrm; if (any (c_act)) + %% deb_printf (testing, 'constraints are active:\n'); + %% deb_printf (testing, '%i\n', c_act); %% calculate chg by 'quadratic programming' - idx = ones (1, size (mca, 2)); - nrme = nrm(:, idx); - ser2 = ser .* ser; - tp2 = nrme .* (v * (ser2(:, idx) .* (v.' * (nrme .* mca)))); - [lb, idx] = cpiv (mcat * tp1, mcat * tp2); - chg = tp1 + tp2(:, idx) * lb; - %% collect inactive constraints - mcit = mc(:, ~ c_act).'; - vci = vc(~ c_act); + nrme= diag (nrm); + ser2 = diag (ser .* ser); + mfc1 = nrme * v * ser2 * v.' * nrme; + tp2 = mfc1 * dca; + [lb, bidx, ridx, tbl] = cpiv (dcat * tp1, dcat * tp2); + chg = tp1 + tp2(:, bidx) * lb; + %% indices for different types of constraints + c_inact = ~c_act; % inactive constraints + c_binding = nc_idx; + c_binding(c_act) = bidx; % constraints selected binding + c_unbinding = nc_idx; + c_unbinding(c_act) = ridx; % constraints unselected binding + c_nonbinding = c_act & ~(c_binding | c_unbinding); % constraints + % selected non-binding else %% chg is the Levenberg/Marquardt step chg = tp1; - %% inactive constraints consist of all constraints - mcit = mc.'; - vci = vc; + %% indices for different types of constraints + c_inact = ac_idx; % inactive constraints consist of all + % constraints + c_binding = nc_idx; + c_unbinding = nc_idx; + c_nonbinding = nc_idx; end - %% apply inactive constraints (since this is a Levenberg/Marquardt - %% algorithm, no line-search is performed here) + %% apply constraints to step width (since this is a + %% Levenberg/Marquardt algorithm, no line-search is performed + %% here) + k = 1; + c_tp = c_inact(1:rv); + mcit = mc(:, c_tp).'; + vci = vc(c_tp); hstep = mcit * chg; idx = hstep < 0; if (any (idx)) k = min (1, min (- (vci(idx) + mcit(idx, :) * pprev) ./ ... hstep(idx))); - chg = k * chg; + %% deb_printf (testing, 'stepwidth: linear constraints\n'); + end + if (have_gencstr) + c_tp = gc_idx & (c_nonbinding | c_inact); + if (any (c_tp) && any (f_cstr (pprev + k * chg, c_tp) < 0)) + [k, fval, info] = ... + fzero (@ (x) min (cat (1, ... + f_cstr (pprev + x * chg, c_tp), ... + k - x, ... + ifelse (x < 0, -Inf, Inf))), ... + 0); + if (info ~= 1 || abs (fval) >= nz) + error ('could not find stepwidth to satisfy inactive and non-binding general inequality constraints'); + end + %% deb_printf (testing, 'general constraints limit stepwidth\n'); + end end - %% check the maximal stepwidth and apply as necessary - ochg=chg; - idx = ~isinf(maxstep); - limit = abs(maxstep(idx).*pprev(idx)); - chg(idx) = min(max(chg(idx),-limit),limit); - if (verbose && any(ochg ~= chg)) - disp(['Change in parameter(s): ', ... - sprintf('%d ',find(ochg ~= chg)), 'maximal fractional stepwidth enforced']); + chg = k * chg; + + if (any (gc_idx & c_binding)) % none selected binding => + % none unselected binding + %% deb_printf (testing, 'general binding constraints must be regained:\n'); + %% regain binding constraints and one of the possibly active + %% previously inactive or non-binding constraints + ptp1 = pprev + chg; + + tp = true; + nt_nosuc = true; + lim = 20; + while (nt_nosuc && lim >= 0) + %% deb_printf (testing, 'starting from new value of p in regaining:\n'); + %% deb_printf (testing, '%e\n', ptp1); + %% we keep dp.' * inv (mfc1) * dp minimal in each step of the + %% inner loop; this is both sensible (this metric considers a + %% guess of curvature of sum of squared residuals) and + %% convenient (we have useful matrices available for it) + c_tp0 = c_inact | c_nonbinding; + c_tp1 = c_inact | (gc_idx & c_nonbinding); + btbl = tbl(bidx, bidx); + c_tp2 = c_binding; + if (any (tp)) % if none before, does not get true again + tp = f_cstr (ptp1, c_tp1) < nz; + if (any (tp)) % could be less clumsy, but ml-compatibility.. + %% keep only the first true entry in tp + tp(tp) = logical (cat (1, 1, zeros (sum (tp) - 1, 1))); + %% supplement binding index with one (the first) getting + %% binding in c_tp1 + c_tp2(c_tp1) = tp; + %% gradient of this added constraint + caddt = dct(c_tp2 & ~c_binding, :); + cadd = caddt.'; + C = dct(c_binding, :) * mfc1 * cadd; + Ct = C.'; + G = [btbl, btbl * C; ... + -Ct * btbl, caddt * mfc1 * cadd - Ct * btbl * C]; + btbl = gjp (G, size (G, 1)); + end + end + dcbt = dct(c_tp2, :); + mfc = - mfc1 * dcbt.' * btbl; + %% deb_printf (testing, 'constraints to regain:\n'); + %% deb_printf (testing, '%i\n', c_tp2); + + ptp2 = ptp1; + nt_niter = 100; + while (nt_nosuc && nt_niter >= 0) + hv = f_cstr (ptp2, c_tp2); + if (all (hv < nz)) + nt_nosuc = false; + chg = ptp2 - pprev; + else + ptp2 = ptp2 + mfc * hv; + end + nt_niter = nt_niter - 1; + end + if (nt_nosuc || ... + any (abs (chg) > abs (pprev .* maxstep)) || ... + any (f_cstr (ptp2, c_tp0) < -nz)) + nt_nosuc = true; + ptp1 = (pprev + ptp1) / 2; + if (nt_nosuc) + %% deb_printf (testing, 'regaining did not converge\n'); + else + %% deb_printf (testing, 'regaining violated type 3 and 4\n'); + end + end + if (~nt_nosuc) + tp = f_cstr (ptp2, c_unbinding); + if (any (tp) < 0) % again ml-compatibility clumsyness.. + [discarded, id] = min(tp); + tid = find (ridx); + id = tid(id); + if (abs (tbl(id, id)) < nz) % Bard: not absolute value + [discarded, idm] = max (abs (tbl(bidx, id))); + tid = find (bidx); + idm = tid(idm); + tbl = gjp (tbl, idm); + bidx(idm) = false; + ridx(idm) = true; + end + tbl = gjp (tbl, id); + bidx(id) = true; + ridx(id) = false; + c_binding = nc_idx; + c_binding(c_act) = bidx; + c_unbinding = nc_idx; + c_unbinding(c_act) = ridx; + nt_nosuc = true; + %% deb_printf (testing, 'regaining violated type 2\n'); + end + end + if (~nt_nosuc) + %% deb_printf (testing, 'regaining successful, converged with %i iterations:\n', ... + %% 100 - nt_niter); + %% deb_printf (testing, '%e\n', ptp2); + end + lim = lim - 1; + end + if (nt_nosuc) + error ('could not regain binding constraints'); + end + else + %% check the maximal stepwidth and apply as necessary + ochg=chg; + idx = ~isinf(maxstep); + limit = abs(maxstep(idx).*pprev(idx)); + chg(idx) = min(max(chg(idx),-limit),limit); + if (verbose && any(ochg ~= chg)) + disp(['Change in parameter(s): ', ... + sprintf('%d ',find(ochg ~= chg)), 'maximal fractional stepwidth enforced']); + end end aprec=abs(pprec.*pbest); %--- %% ss=scalar sum of squares=sum((wt.*(y-f))^2). @@ -388,7 +683,7 @@ % function if there is some non-miniscule % change p=chg+pprev; - f=feval(F,x,p); + f = F (x, p); r=wt.*(y-f); if (~isreal (r)) error ('weighted residuals are not real'); @@ -440,7 +735,7 @@ %% CALC VARIANCE COV MATRIX AND CORRELATION MATRIX OF PARAMETERS %% re-evaluate the Jacobian at optimal values - jac = eval (dfdp_cmd); + jac = dFdp (x, fbest, p, dp, F, dfdp_bounds{:}); msk = dp ~= 0; n = sum(msk); % reduce n to equal number of estimated parameters jac = jac(:, msk); % use only fitted parameters @@ -534,7 +829,7 @@ disp(covr) disp(' Correlation Coefficient R^2') disp(r2) - sprintf(' 95%% conf region: F(0.05)(%.0f,%.0f)>= delta_pvec.''*Z*delta_pvec',n,m-n) + sprintf(' 95%% conf region: F(0.05)(%.0f,%.0f)>= delta_pvec.%s*Z*delta_pvec', n, m - n, char (39)) % works with ' and ' Z %% runs test according to Bard. p 201. n1 = sum (resid > 0); @@ -553,6 +848,34 @@ end end +function ret = tf_gencstr (p, idx, f) + + %% necessary since user function f_gencstr might return [] + + ret = f (p, idx); + if (isempty (ret)) + ret = zeros (0, 1); + elseif (size (ret, 2) > 1) + ret = ret(:); + end + +function fval = scalar_ifelse (cond, tval, fval) + + %% needed for some anonymous functions, builtin ifelse only available + %% in Octave > 3.2; we need only the scalar case here + + if (cond) + fval = tval; + end + +function deb_printf (do_printf, varargin) + + %% for testing + + if (do_printf) + printf (varargin{:}) + end + %!demo %! % Define functions %! leasqrfunc = @(x, p) p(1) * exp (-p(2) * x);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/optim/inst/private/__dfdp__.m Tue Apr 13 12:51:54 2010 +0000 @@ -0,0 +1,74 @@ +%% Copyright (C) 1992-1994 Richard Shrager +%% Copyright (C) 1992-1994 Arthur Jutan +%% Copyright (C) 1992-1994 Ray Muzic +%% Copyright (C) 2010 Olaf Till <olaf.till@uni-jena.de> +%% +%% This program 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; 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. +%% +%% You should have received a copy of the GNU General Public License +%% along with this program; If not, see <http://www.gnu.org/licenses/>. + +function prt = __dfdp__ (f, p, dp, func, bounds) + + %% Meant to be called by interfaces 'dfdp.m' and 'dcdp.m', see there. + + m = length (f); + n=length(p); %dimensions + if (nargin < 6) + bounds = ones (n, 2); + bounds(:, 1) = -Inf; + bounds(:, 2) = Inf; + end + prt=zeros(m,n); % initialise Jacobian to Zero + del = dp .* p; %cal delx=fract(dp)*param value(p) + idx = p == 0; + del(idx) = dp(idx); %if param=0 delx=fraction + idx = dp > 0; + del(idx) = abs (del(idx)); % not for one-sided intervals, changed + % direction of intervals could change + % behavior of optimization without bounds + min_del = min (abs (del), bounds(:, 2) - bounds(:, 1)); + for j=1:n + ps = p; + if (dp(j)~=0) + if (dp(j) < 0) + ps(j) = p(j) + del(j); + if (ps(j) < bounds(j, 1) || ps(j) > bounds(j, 2)) + t_del1 = max (bounds(j, 1) - p(j), - abs (del(j))); % + %non-positive + t_del2 = min (bounds(j, 2) - p(j), abs (del(j))); % + %non-negative + if (- t_del1 > t_del2) + del(j) = t_del1; + else + del(j) = t_del2; + end + ps(j) = p(j) + del(j); + end + prt(:, j) = (func (ps) - f) / del(j); + else + if (p(j) - del(j) < bounds(j, 1)) + tp = bounds(j, 1); + ps(j) = tp + min_del(j); + elseif (p(j) + del(j) > bounds(j, 2)) + ps(j) = bounds(j, 2); + tp = ps(j) - min_del(j); + else + ps(j) = p(j) + del(j); + tp = p(j) - del(j); + min_del(j) = 2 * del(j); + end + f1 = func (ps); + ps(j) = tp; + prt(:, j) = (f1 - func (ps)) / min_del(j); + end + end + end