-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnmregr.R
347 lines (305 loc) · 10.7 KB
/
nmregr.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# nmregr
#
##
#function [q, info] =
#' Calculates least squares estimates using Nelder Mead's simplex method
#'
#' @param func string with name of user-defined function
#' @param p (k,2)-matrix with p(:,1) initial guesses for parameter values; p(:,2) binaries with yes or no iteration (optional)
#' @param varargin MATLAB idiom...
#'
#' @return [q, info]
#'
#' @importFrom pracma zeros ones
#'
nmregr=function(func, p, varargin){
# created 2001/09/07 by Bas Kooijman; modified 2013/10/04
## Syntax
# [q, info] = <../nmregr.m *nmregr*> (func, p, varargin)
## Description
# Calculates least squares estimates using Nelder Mead's simplex method
#
# Input
#
# * func: string with name of user-defined function
#
# f = func (p, xyw) with
# p: k-vector with parameters; xyw: (n,c)-matrix; f: n-vector
# [f1, f2, ...] = func (p, xyw1, xyw2, ...) with p: k-vector and
# xywi: (ni,k)-matrix; fi: ni-vector with model predictions
# The dependent variable in the output f; For xyw see below.
#
# * p: (k,2)-matrix with
#
# p(:,1) initial guesses for parameter values
# p(:,2) binaries with yes or no iteration (optional)
#
# * xyzi (read as xyw1, xyw2, .. ): (ni,3) matrix with
#
# xywi(:,1) independent variable i
# xywi(:,2) dependent variable i
# xywi(:,3) weight coefficients i (optional)
# xywi(:,>3) data-pont specific information data (optional)
# The number of data matrices xyw1, xyw2, ... is optional but >0
# Default for xywi(:,3) is (number of data points in the set i)^-1
#
# Output
#
# * q: matrix like p, but with least squares estimates
# * info: 1 if convergence has been successful; 0 otherwise
## Remarks
# Calls user-defined function 'func'
# Set options with <nmregr_options.html *nmregr_options*>
# See
# <nrregr.html *nrregr*> for Newton-Raphson method,
# <garegr.html *garegr*> for genetic algorithm,
# <nrregr2.html *nrregr2*> for 2 independent variables, and
# <nmvcregr.html *nmvcregr*> for standard deviations proportional to the mean.
# It is usually a good idea to run <nrregr.html *nrregr*> on the result of nmregr.
## Example of use
# See <../mydata_regr.m *mydata_regr*>
func=match.fun(func)
i = 1 # initiate data set counter
info = 1 # initiate info setting
ci = as.character(i) # character string with value of i
if (!is.list(varargin)){
varargin=list(varargin)
}
nxyw = nrow(summary(varargin)) # number of data sets
while (i <= nxyw){ # loop across data sets
if (i == 1){
listxyw = c('xyw1') # initiate list xyw
# listf = c("f1") # initiate list f
} else{ listxyw = paste(listxyw, paste('xyw', ci,sep=""), sep=',') # append list xyw
# listf = c(listf, paste('f', ci,sep=""))
# paste(listf, paste('f', ci,sep=""), sep=',') # append list f
}
i = i + 1
ci = as.character(i) # character string with value of i
}
# nl = length(listxyw)
# listxyw = listxyw[1:(nl - 1)] # remove last ','
# nl = length(listf)
# listf = listf[1:(nl - 1)] # remove last ','
#
# global_txt = strrep(['global ', listxyw], ',', ' ');
# eval(global_txt); # make data sets global
#
#library(pracma)
N = pracma::zeros(nxyw, 1) # initiate data counter
Y = vector(length=0)
W = vector(length=0) # initiate observations and weight coefficients
for (i in c(1:nxyw)) { # loop across data sets
ci = as.character(i) # character string with value of i
# assing unnamed arguments to xyw1, xyw2, etc
eval(parse(text=paste('xyw', ci, ' = varargin[[', ci,']]', sep="")))
eval(parse(text=paste('N[', ci, '] = nrow(xyw', ci, ')', sep=""))) # number of data points
eval(parse(text=paste('k= ncol(xyw', ci, ')', sep="")))
eval(parse(text=paste('Y = c(Y,xyw', ci, '[,2])', sep=""))) # append dependent variables
if (k > 2) {
eval(parse(text=paste('W = c(W, xyw', ci, '[,3])', sep=""))) # append weight coefficients
} else {W = c(W, (ones(N[i],1)/ N[i]))} # append weight coefficients
}
q = p # copy input parameter matrix to output
info = 1 # convergence has been successful
np = nrow(p) # k: number of parameters
k = ncol(p)
index = c(1:np)
if (k > 1) {
index = index[p[,2]==1] # indices of iterated parameters
}
n_par = max(length(index)) # number of parameters that must be iterated
# set options if necessary
if (!exists('max_step_number')){
nmregr_options(key='max_step_number', 200 * n_par)
}
if (!exists('max_fun_evals')){
nmregr_options(key='max_fun_evals', 200 * n_par);
}
if (!exists('tol_simplex')){
nmregr_options(key='tol_simplex', 1e-4);
}
if (!exists('tol_fun')){
nmregr_options(key='tol_fun', 1e-4)
}
if (!exists('report')){
nmregr_options(key='report', 1)
}
if (is.na(max_step_number)){
nmregr_options(key='max_step_number', 200 * n_par)
}
if (is.na(max_fun_evals)){
nmregr_options(key='max_fun_evals', 200 * n_par)
}
if (is.na(tol_simplex)){
nmregr_options(key='tol_simplex', 1e-4)
}
if (is.na(tol_fun)){
nmregr_options(key='tol_fun', 1e-4)
}
if (is.na(report)){
nmregr_options(key='report', 1)
}
# Initialize parameters
rho = 1
chi = 2
psi = 0.5
sigma = 0.5
onesn = pracma::ones(1, n_par)
two2np1 = 2:(n_par + 1)
one2n = 1:n_par
np1 = n_par + 1
# Set up a simplex near the initial guess.
v = pracma::zeros(n_par, np1)
fv = pracma::zeros(1,np1)
xin = q[index, 1] # Place input guess in the simplex
v[,1] = xin
eval(parse(text=paste('listf = func(q[,1],', listxyw, ')')))
if (nxyw == 1) {
fv[,1] = t(W) %*% (listf[[1]] - Y)^2
} else {fv[,1] = t(W) %*% ((unlist(listf)-Y)^2)}
# Following improvement suggested by L.Pfeffer at Stanford
usual_delta = 0.05 # 5 percent deltas for non-zero terms
zero_term_delta = 0.00025 # Even smaller delta for zero elements of q
for (j in c(1:n_par)){
y = xin
if (y[j] != 0){
y[j] = (1 + usual_delta) * y[j]
} else {y[j] = zero_term_delta}
v[,j+1] = y
q[index,1] = y
eval(parse(text=paste('listf = func(q[,1],', listxyw, ')')))
if (nxyw == 1) {
fv[1, j + 1] = t(W) %*% ((listf[[1]] - Y) ^ 2)
} else {fv[1, j + 1] = t(W) %*% ((unlist(listf) - Y) ^ 2)}
}
# sort so v(1,:) has the lowest function value
j=order(fv)
fv=matrix(fv[1,j], nrow=1, byrow=T)
v = v[,j]
how = 'initial'
itercount = 1
func_evals = n_par + 1
if (report == 1){
print(paste('step ', itercount, ' ssq ', min(fv), '-',
max(fv), ' ', how))
}
info = 1;
# Main algorithm ---------------------------------------------------------------------------------------------------------------------------------------
# Iterate until the diameter of the simplex is less than tol_simplex
# AND the function values differ from the min by less than tol_fun,
# or the max function evaluations are exceeded. (Cannot use OR instead of AND.)
while (func_evals < max_fun_evals && itercount < max_step_number){
if ( max(max(abs(v[,two2np1] - v[,onesn]))) <= tol_simplex && max(abs(fv[1] - fv[two2np1])) <= tol_fun ){
break
}
how = ''
# Compute the reflection point
# xbar = average of the n (NOT n+1) best points
xbar = rowSums(v[,one2n])/ n_par
xr = (1 + rho) * xbar - rho * v[,np1]
q[index,1] = xr
eval(parse(text=paste('listf = func(q[,1],', listxyw, ')')))
if (nxyw == 1){
fxr = t(W) * ((listf[[1]] - Y) ^ 2)
} else { fxr = t(W) %*% ((unlist(listf) - Y) ^ 2) }
func_evals = func_evals + 1
if (fxr < fv[,1]){
# Calculate the expansion point
xe = (1 + rho * chi) * xbar - rho * chi * v[, np1]
q[index,1] = xe
eval(parse(text=paste('listf = func(q[,1],', listxyw, ')')))
if (nxyw == 1){
fxe = t(W) * ((listf[[1]] - Y) ^ 2)
} else {fxe = t(W) %*% ((unlist(listf) - Y) ^2)}
func_evals = func_evals + 1
if (fxe < fxr){
v[,np1] = xe
fv[,np1] = fxe
how = 'expand'
} else {
v[,np1] = xr
fv[,np1] = fxr
how = 'reflect'
}
} else {# fv[,1] <= fxr
if (fxr < fv[,n_par]){
v[,np1] = xr
fv[,np1] = fxr
how = 'reflect'
} else{
# fxr >= fv(:,n_par)
# Perform contraction
if (fxr < fv[,np1]){
# Perform an outside contraction
xc = (1 + psi * rho) * xbar - psi * rho * v[,np1]
q[index,1] = xc
eval(parse(text=paste('listf = func(q[,1],', listxyw, ')')))
if (nxyw == 1){
fxc = t(W) %*% ((listf[[1]] - Y) ^ 2)
} else{fxc = t(W) %*% ((unlist(listf) - Y) ^ 2)}
func_evals = func_evals + 1
if (fxc <= fxr){
v[,np1] = xc
fv[,np1] = fxc
how = 'contract outside'
} else { how = 'shrink' } # perform a shrink
} else {
# Perform an inside contraction
xcc = (1 - psi) * xbar + psi * v[,np1]
q[index,1] = xcc
eval(parse(text=paste('listf = func(q[,1],', listxyw, ')')))
if (nxyw == 1){
fxcc = t(W) %*% ((listf[[1]] - Y) ^ 2)
} else{fxcc = t(W) %*% ((unlist(listf) - Y) ^ 2)}
func_evals = func_evals + 1
if (fxcc < fv[,np1]){
v[,np1] = xcc
fv[,np1] = fxcc
how = 'contract inside'
} else {
# perform a shrink
how = 'shrink'
}
}
if ( how == 'shrink' ){
for (j in two2np1){
v[,j] = v[,1] + sigma * (v[,j] - v[,1])
q[index,1] = v[,j]
eval(parse(text=paste('listf = func(q[,1],', listxyw, ')')))
if (nxyw == 1){
fv[,j] = t(W) %*% ((listf[[1]] - Y) ^ 2)
} else {fv[,j] = t(W) %*% ((unlist(listf) - Y)^2)}
}
func_evals = func_evals + n_par
}
}
}
j=order(fv)
fv=matrix(fv[1,j], nrow=1, byrow=T)
v = v[,j]
itercount = itercount + 1
if (report == 1){
print(paste('step ', itercount, ' ssq ', min(fv), '-', max(fv), ' ', how, '\n'))
}
} # while of main loop
q[index,1] = v[,1]
fval = min(fv)
if (func_evals >= max_fun_evals){
if (report > 0){
print(paste('No convergences with ', max_fun_evals, ' function evaluations'))
}
info = 0
} else if (itercount >= max_step_number ) {
if (report > 0){
print(paste('No convergences with ', max_step_number, ' steps', sep=''))
}
info = 0
} else {
if (report > 0){
print('Successful convergence \n')
}
}
info = 1
return(list(q, info))
}