-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtclopt.tcl
1626 lines (1557 loc) · 66.5 KB
/
tclopt.tcl
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package require argparse
package provide tclopt 0.2
interp alias {} dget {} dict get
interp alias {} @ {} lindex
interp alias {} = {} expr
interp alias {} dkeys {} dict keys
interp alias {} dvalues {} dict values
interp alias {} dexist {} dict exists
interp alias {} dcreate {} dict create
interp alias {} dappend {} dict append
namespace eval ::tclopt {
namespace import ::tcl::mathop::*
namespace export Parameter ParameterMpfit Mpfit
# Double precision numeric constants
variable MP_MACHEP0 2.2204460e-16
variable MP_DWARF 2.2250739e-308
variable MP_GIANT 1.7976931e+308
}
proc ::tclopt::list2array {list {type double}} {
# Create and initialize doubleArray object from the list
# list - list of values
# type - type of array, double or int
# Returns: array object
set length [llength $list]
if {$type ni {double int}} {
error "Type '$type' must be int or double"
}
set a [::tclopt::new_${type}Array $length]
for {set i 0} {$i<$length} {incr i} {
set iElem [@ $list $i]
try {
::tclopt::${type}Array_setitem $a $i $iElem
} on error {errmsg erropts} {
if {[dget $erropts -errorcode]=="SWIG TypeError"} {
error "List must contains only $type elements, but get '$iElem'"
} else {
error "Array creation failed with message '$errmsg' and opts '$erropts'"
}
}
}
return $a
}
proc ::tclopt::lists2arrays {varNames lists {type double}} {
# Create and initialize doubleArray objects from lists, and set these objects to variables
# varNames - list of variables names
# lists - list of lists
# type - type of array, double or int
# Returns: variables with doubleArray objects are set in caller's scope
if {$type ni {double int}} {
error "Type '$type' must be int or double"
}
if {[llength $varNames]!=[llength $lists]} {
error "Length of varName list '[llength $varNames]' must be equal to length of lists list '[llength $lists]'"
}
foreach varName $varNames list $lists {
uplevel 1 [list set $varName [::tclopt::list2array $list $type]]
}
return
}
proc ::tclopt::array2list {array length {type double}} {
# Create list from doubleArray object
# array - doubleArray object
# length - number of elements in doubleArray
# type - type of array, double or int
# Returns: list
if {$type ni {double int}} {
error "Type '$type' must be int or double"
}
for {set i 0} {$i<$length} {incr i} {
lappend list [::tclopt::${type}Array_getitem $array $i]
}
return $list
}
proc ::tclopt::arrays2lists {varNames arrays lengths {type double}} {
# Create lists from doubleArray objects, and set these lists to variables
# varNames - list of variables names
# arrays - list of doubleArray
# lengths - list of doubleArray lengths
# type - type of arrays, double or int
# Returns: variables with lists are set in caller's scope
if {$type ni {double int}} {
error "Type '$type' must be int or double"
}
if {[llength $varNames]!=[llength $arrays]} {
error "Length of varName list '[llength $varNames]' must be equal to length of array list '[llength $arrays]'"
} elseif {[llength $varNames]!=[llength $lengths]} {
error "Length of varName list '[llength $varNames]' must be equal to length of lengths list\
'[llength $lengths]'"
}
foreach varName $varNames array $arrays length $lengths {
uplevel 1 [list set $varName [::tclopt::array2list $array $length $type]]
}
return
}
proc ::tclopt::newArrays {varNames lengths {type double}} {
# Creates doubleArray objects, and set these objects to variables
# varNames - list of variables names
# lengths - list of doubleArray's lengths
# type - type of arrays, double or int
# Returns: variables with doubleArray or intArray objects are set in caller's scope
if {$type ni {double int}} {
error "Type '$type' must be int or double"
}
if {[llength $varNames]!=[llength $lengths]} {
error "Length of varName list '[llength $varNames]' must be equal to length of lengths list\
'[llength $lengths]'"
}
foreach varName $varNames length $lengths {
uplevel 1 [list set $varName [::tclopt::new_${type}Array $length]]
}
return
}
proc ::tclopt::newDoubleps {varNames} {
# Creates doubleps objects, and set these objects to variables
# varNames - list of variables names
# Returns: variables with doubleps objects are set in caller's scope
foreach varName $varNames {
uplevel 1 [list set $varName [::tclopt::new_doublep]]
}
return
}
proc ::tclopt::deleteArrays {arrays {type double} } {
# Deletes doubleArray objects
# arrays - list of arrays objects
# type - type of arrays, double or int
if {$type ni {double int}} {
error "Type '$type' must be int or double"
}
foreach array $arrays {
::tclopt::delete_${type}Array $array
}
return
}
proc ::tclopt::deleteDoubleps {args} {
# Deletes doublep objects
# args - list of doublep objects
foreach arg $args {
::tclopt::delete_doublep $arg
}
return
}
### DuplChecker class definition
oo::configurable create ::tclopt::DuplChecker {
self mixin -append oo::abstract
method duplListCheck {list} {
# Checks if list contains duplicates.
# list - list to check
# Returns: 0 if there are no duplicates and 1 if there are.
set itemDup ""
set new {}
foreach item $list {
if {[lsearch $new $item] < 0} {
lappend new $item
} else {
set itemDup $item
break
}
}
return $itemDup
}
}
### Levenberg-Marquardt square-least fitting optimization
oo::configurable create ::tclopt::Parameter {
property name -set {
if {$value==""} {
return -code error "Parameter must have a name, empty string was provided"
} elseif {[regexp {[^A-Za-z0-9_]+} $value]} {
return -code error "Parameter name '$value' is not a valid name"
}
set name $value
} -get {
return $name
}
property fixed -set {
if {[string is boolean -strict $value]} {
set fixed $value
return
} else {
return -code error "fixed property value '$value' must be a boolean type"
}
} -get {
return $fixed
}
property initval -set {
if {[string is double -strict $value]} {
set initval $value
return
} else {
return -code error "Initial value '$value' of parameter must be a double type"
}
} -get {
return $initval
}
property lowlim -set {
if {[string is double -strict $value]} {
set lowlim $value
return
} else {
return -code error "Lower limit value '$value' of parameter must be a double type"
}
} -get {
return $lowlim
}
property uplim -set {
if {[string is double -strict $value]} {
set uplim $value
return
} else {
return -code error "Upper limit value '$value' of parameter must be a double type"
}
} -get {
return $uplim
}
variable name fixed initval lowlim uplim
constructor {name initval args} {
argparse {
{-fixed -boolean}
-lowlim=
-uplim=
}
my configure -fixed $fixed
my configure -initval $initval
my configure -name $name
if {[info exists lowlim]} {
if {$lowlim>$initval} {
return -code error "Initial value must be higher than the lower limit"
}
my configure -lowlim $lowlim
}
if {[info exists uplim]} {
if {[info exists lowlim]} {
if {$lowlim>=$uplim} {
return -code error "Lower limit must be lower than the upper limit"
}
}
if {$uplim<$initval} {
return -code error "Initial value must be lower than the upper limit"
}
my configure -uplim $uplim
}
}
}
oo::configurable create ::tclopt::ParameterMpfit {
superclass ::tclopt::Parameter
property step -set {
if {[string is double -strict $value]} {
if {$value<=0} {
return -code error "Step value '$value' of parameter must be more than zero"
} else {
set step $value
return
}
} else {
return -code error "Step value '$value' of parameter must be a double type"
}
} -get {
return $step
}
property relstep -set {
if {[string is double -strict $value]} {
if {$value<=0} {
return -code error "Relative step value '$value' of parameter must be more than zero"
} else {
set relstep $value
return
}
} else {
return -code error "Relative step value '$value' of parameter must be a double type"
}
} -get {
return $relstep
}
property side -set {
if {$value in {auto right left both an}} {
set side $value
return
} else {
return -code error "Side of derivative selector value '$value' of parameter must be of one of the type\
'auto', 'right', 'left', 'both' or 'an'"
}
} -get {
return $side
}
property debugder -set {
if {[string is boolean -strict $value]} {
set debugder $value
return
} else {
return -code error "debugder property value '$value' must be a boolean type"
}
} -get {
return $debugder
}
property derivreltol -set {
if {[string is double -strict $value]} {
if {$value<0} {
return -code error "derivreltol value '$value' of parameter must be more or equal to zero"
} else {
set derivreltol $value
return
}
} else {
return -code error "derivreltol value '$value' of parameter must be a double type"
}
} -get {
return $derivreltol
}
property derivabstol -set {
if {[string is double -strict $value]} {
if {$value<0} {
return -code error "derivabstol value '$value' of parameter must be more or equal to zero"
} else {
set derivabstol $value
return
}
} else {
return -code error "derivabstol value '$value' of parameter must be a double type"
}
} -get {
return $derivabstol
}
variable name fixed lowlim uplim step relstep side debugder derivreltol derivabstol initval
constructor {name initval args} {
# Creates parameter object for [::tclopt::Mpfit] class.
# name - name of the parameter
# initval - initial value of parameter
# -fixed - specify that parameter is fixed during optimization, optional
# -lowlim - specify lower limit for parameter, must be lower than upper limit if upper limit is provided,
# optional
# -uplim - specify upper limit for parameter, must be higher than lower limit if lower limit is provided,
# optional
# -step - the step size to be used in calculating the numerical derivatives. If set to zero, then the step
# size is computed automatically, optional
# -relstep - the *relative* step size to be used in calculating the numerical derivatives. This number is the
# fractional size of the step, compared to the parameter value. This value supercedes the -step setting.
# If the parameter is zero, then a default step size is chosen.
# -side - the sidedness of the finite difference when computing numerical derivatives. This field can take four
# values: auto : one-sided derivative computed automatically, right : one-sided derivative (f(x+h)-f(x))/h,
# left : one-sided derivative (f(x)-f(x-h))/h, both : two-sided derivative (f(x+h)-f(x-h))/(2*h), an :
# user-computed explicit derivatives, where h is the -step parameter described above. The "automatic"
# one-sided derivative method will chose a direction for the finite difference which does not violate any
# constraints. The other methods do not perform this check. The two-sided method is in principle more precise,
# but requires twice as many function evaluations. Default is auto.
# -debugder - switch to enable console debug logging of user-computed derivatives, as described above. Note that
# when debugging is enabled, then -side should be set to auto, right, left or both, depending on which
# numerical derivative you wish to compare to. Requires -debugreltol and -debugabstol values.
# -debugreltol - relative error that controls printing of derivatives comparison if relative error exceeds this
# value. Requires -debugder and -debugabstol.
# -debugabstol - absolute error that controls printing of derivatives comparison if absolute error exceeds this
# value. Requires -debugder and -debugreltol.
# Synopsis: value value ?-fixed? ?-lowlim value? ?-uplim value? ?-step value? ?-relstep value? ?-side value?
# ?-debugder -debugreltol value -debugabstol value?
#
# Example of building 4 parameters with different constraints:
# ```
# set par0 [::tclopt::ParameterMpfit new a 1.0 -fixed -side both]
# set par1 [::tclopt::ParameterMpfit new b 2.0]
# set par2 [::tclopt::ParameterMpfit new c 0.0 -fixed]
# set par3 [::tclopt::ParameterMpfit new d 0.1 -lowlim -0.3 -uplim 0.2]
# ```
set arguments [argparse -inline {
{-fixed -boolean}
-lowlim=
-uplim=
-step=
-relstep=
{-side= -enum {auto right left both an} -default auto}
{-debugder -boolean -require {debugreltol debugabstol}}
-debugreltol=
-debugabstol=
}]
my configure -side [dget $arguments side]
if {[dexist $arguments step]} {
my configure -step [dget $arguments step]
}
if {[dexist $arguments relstep]} {
my configure -relstep [dget $arguments relstep]
}
my configure -debugder [dget $arguments debugder]
if {[dget $arguments debugder]} {
my configure -derivreltol [dget $arguments debugreltol]
my configure -derivabstol [dget $arguments debugabstol]
}
set params ""
if {[dget $arguments fixed]} {
lappend params -fixed
}
dict for {paramName value} $arguments {
if {$paramName ni {side step relstep debugder debugreltol debugabstol fixed}} {
lappend params -$paramName $value
}
}
next $name $initval {*}$params
}
}
oo::configurable create ::tclopt::Mpfit {
mixin ::tclopt::DuplChecker
property funct -set {
if {$value==""} {
return -code error "Function must have a name, empty string was provided"
} elseif {$value ni [info commands $value]} {
return -code error "Function with name '$value' does not exist"
} else {
set funct $value
}
} -get {
return $funct
}
property m -set {
if {[string is integer -strict $value]} {
if {$value<=0} {
return -code error "Number of values m must be more than 0"
} else {
set m $value
return
}
} else {
return -code error "Number of values m '$value' must be an integer type"
}
} -get {
return $m
}
property ftol -set {
if {[string is double -strict $value]} {
if {$value<=0} {
return -code error "ftol value '$value' must be more than zero"
} else {
set ftol $value
return
}
} else {
return -code error "ftol value '$value' must be a double type"
}
} -get {
return $ftol
}
property xtol -set {
if {[string is double -strict $value]} {
if {$value<=0} {
return -code error "xtol value '$value' must be more than zero"
} else {
set xtol $value
return
}
} else {
return -code error "xtol value '$value' must be a double type"
}
} -get {
return $xtol
}
property gtol -set {
if {[string is double -strict $value]} {
if {$value<=0} {
return -code error "gtol value '$value' must be more than zero"
} else {
set gtol $value
return
}
} else {
return -code error "gtol value '$value' must be a double type"
}
} -get {
return $gtol
}
property stepfactor -set {
if {[string is double -strict $value]} {
if {$value<=0} {
return -code error "stepfactor value '$value' must be more than zero"
} else {
set stepfactor $value
return
}
} else {
return -code error "stepfactor value '$value' must be a double type"
}
} -get {
return $stepfactor
}
property covtol -set {
if {[string is double -strict $value]} {
if {$value<=0} {
return -code error "covtol value '$value' must be more than zero"
} else {
set covtol $value
return
}
} else {
return -code error "covtol value '$value' must be a double type"
}
} -get {
return $covtol
}
property maxiter -set {
if {[string is integer -strict $value]} {
if {$value<0} {
return -code error "Maximum iterations number '$value' must be more than or equal to 0"
} else {
set maxiter $value
return
}
} else {
return -code error "Maximum iterations number '$value' must be an integer type"
}
} -get {
return $maxiter
}
property maxfev -set {
if {[string is integer -strict $value]} {
if {$value<0} {
return -code error "Maximum number of function evaluations '$value' must be more than or equal to 0"
} else {
set maxfev $value
return
}
} else {
return -code error "Maximum number of function evaluations '$value' must be an integer type"
}
} -get {
return $maxfev
}
property epsfcn -set {
if {[string is double -strict $value]} {
if {$value<=0} {
return -code error "epsfcn value '$value' must be more than zero"
} else {
set epsfcn $value
return
}
} else {
return -code error "epsfcn value '$value' must be a double type"
}
} -get {
return $epsfcn
}
property nofinitecheck -set {
if {[string is boolean -strict $value]} {
set nofinitecheck $value
return
} else {
return -code error "nofinitecheck property value '$value' must be a boolean type"
}
} -get {
return $nofinitecheck
}
property pdata
property results
variable funct m ftol xtol gtol stepfactor covtol maxiter maxfev epsfcn nofinitecheck pdata results
variable Pars
method getAllParsNames {} {
# Gets names of all parameters.
# Returns: list of elements names
if {![info exists Pars]} {
return -code error "There are no parameters attached to optimizer"
} else {
return [dkeys $Pars]
}
}
method getAllPars {} {
# Gets names of all parameters.
# Returns: list of elements names
if {![info exists Pars]} {
return -code error "There are no parameters attached to optimizer"
} else {
return $Pars
}
}
method addPars {args} {
foreach arg $args {
set argClass [info object class $arg]
if {$argClass!={::tclopt::ParameterMpfit}} {
return -code error "Only ::tclopt::ParameterMpfit could be added to optimizer, '$argClass' was provided"
}
lappend parsNamesList [$arg configure -name]
}
if {[info exists Pars]} {
lappend parsNamesList {*}[my getAllParsNames]
}
set dup [my duplListCheck $parsNamesList]
if {$dup!=""} {
return -code error "Optimizer already contains parameter with name '$dup'"
}
foreach arg $args {
set parName [$arg configure -name]
dict append Pars $parName $arg
}
return
}
constructor {args} {
# Creates optimization object that does least squares fitting using modified Levenberg-Marquardt algorithm.
# -funct - name of the procedure that should be minimized
# -m - number of data points
# -pdata - list or dictionary that provides private data to funct that is needed to evaluate residuals. Usually
# it contains x and y values lists, but you can provide any data necessary for function residuals evaluation.
# Will be passed upon each function evaluation without modification.
# -ftol - control termination of mpfit. Termination occurs when both the actual and predicted relative
# reductions in the sum of squares are at most ftol. Therefore, ftol measures the relative error desired
# in the sum of squares. Value must be of the type float more than zero, default is 1e-10.
# -xtol - control termination of mpfit. Termination occurs when the relative error between two consecutive
# iterates is at most xtol. Therefore, xtol measures the relative error desired in the approximate solution.
# Value must be of the type float more than zero, default is 1e-10.
# -gtol - control termination of mpfit. Termination occurs when the cosine of the angle between fvec and any
# column of the jacobian is at most gtol in absolute value. Therefore, gtol measures the orthogonality desired
# between the function vector and the columns of the jacobian. Value must be of the type float more than zero,
# default is 1e-10.
# -maxfev - control termination of mpfit. Termination occurs when the number of calls to funct is at least
# maxfev by the end of an iteration. Value must be the positive integer, default is 0. If it equals to 0,
# number of evaluations is not restricted.
# -stepfactor - used in determining the initial step bound. This bound is set to the product of factor and the
# euclidean norm of diag*x if nonzero, or else to factor itself. in most cases factor should lie in the
# interval (.1,100.). 100. is a generally recommended value. Value must be of the type float more than zero,
# default is 100.
# -covtol - range tolerance for covariance calculation. Value must be of the type float more than zero, default
# is 1e-14.
# -maxiter - maximum number of iterations. If maxiter equal to 0, then basic error checking is done, and
# parameter errors/covariances are estimated based on input arameter values, but no fitting iterations are
# done. Value must be the positive integer, default is 200.
# -epsfcn - finite derivative step size. Value must be of the type float more than zero, default is
# 2.2204460e-16.
# -nofinitecheck - enable check for infinite quantities, default is off.
# Returns: object of class
#
# Class uses the Levenberg-Marquardt technique to solve the least-squares problem. In its typical use, it will
# be used to fit a user-supplied function (the "model") to user-supplied data points (the "data") by adjusting a
# set of parameters. mpfit is based upon MINPACK-1 (LMDIF.F) by More' and collaborators.
# The user-supplied function should compute an array of weighted deviations between model and data. In a typical
# scientific problem the residuals should be weighted so that each deviate has a gaussian sigma of 1.0. If x
# represents values of the independent variable, y represents a measurement for each value of x, and err
# represents the error in the measurements, then the deviates could be calculated as follows:
# ```
# for {set i 0} {$i<$m} {incr i} {
# lset deviates $i [expr {([lindex $y $i] - [f [lindex $x $i]])/[lindex $err $i]}]
# }
# ```
# where m is the number of data points, and where f is the function representing the model evaluated at x. If ERR
# are the 1-sigma uncertainties in Y, then the sum of deviates squared will be the total chi-squared value, which
# mpfit will seek to minimize.
# Simple constraints are placed on parameter values by adding objects of class [::tclopt::ParameterMpfit] to
# mpfit with method [:tclopt::Mpfit::addPars], where other parameter-specific options can be set.
# For details of how to specify constraints, please look at the
# description of [::tclopt::parCreate] procedure. Please note, that order in which we attach parameters objects
# is the order in which values will be supplied to minimized function, and the order in which resulted will
# be written to X property of the class.
# Example of user defined function (using linear equation t=a+b*x):
# ```
# proc f {xall pdata args} {
# set x [dget $pdata x]
# set y [dget $pdata y]
# set ey [dget $pdata ey]
# foreach xVal $x yVal $y eyVal $ey {
# set f [= {[@ $xall 0]+[@ $xall 1]*$xVal}]
# lappend fval [= {($yVal-$f)/$eyVal}]
# }
# return [dcreate fvec $fval]
# }
# ```
# where xall is list of initial parameters values, pdata - dictionary that contains x, y and ey lists with
# length m. It returns dictionary with residuals values.
# Alternative form of function f could also provide analytical derivatives:
# ```
# proc quadfunc {xall pdata args} {
# set x [dget $pdata x]
# set y [dget $pdata y]
# set ey [dget $pdata ey]
# foreach xVal $x yVal $y eyVal $ey {
# lappend fvec [= {($yVal-[@ $xall 0]-[@ $xall 1]*$xVal-[@ $xall 2]*$xVal*$xVal)/$eyVal}]
# }
# if {[@ $args 0]!=""} {
# set derivs [@ $args 0]
# foreach deriv $derivs {
# if {$deriv==0} {
# foreach xVal $x yVal $y eyVal $ey {
# lappend dvec [= {-1/$eyVal}]
# }
# }
# if {$deriv==1} {
# foreach xVal $x yVal $y eyVal $ey {
# lappend dvec [= {(-$xVal)/$eyVal}]
# }
# }
# if {$deriv==2} {
# foreach xVal $x yVal $y eyVal $ey {
# lappend dvec [= {(-$xVal*$xVal)/$eyVal}]
# }
# }
# }
# return [dcreate fvec $fvec dvec $dvec]
# } else {
# return [dcreate fvec $fvec]
# }
# }
# ```
# The first element of the `args` list is a list specifying the ordinal numbers of the parameters for which we
# need to calculate the analytical derivative. In this case, the returned `dvec` list contains the derivative at
# each x point for each specified parameter, following the same order as in the input list. For example, if the
# input list is {0, 2} and the number m of x points is 3, the `dvec` list will look like this:
# ```
# ⎛⎛df ⎞ ⎛df ⎞ ⎛df ⎞ ⎛df ⎞ ⎛df ⎞ ⎛df ⎞ ⎞
# ⎜⎜───⎟ ⎜───⎟ ⎜───⎟ ⎜───⎟ ⎜───⎟ ⎜───⎟ ⎟
# ⎜⎝dp0⎠ ⎝dp0⎠ ⎝dp0⎠ ⎝dp2⎠ ⎝dp2⎠ ⎝dp2⎠ ⎟
# ⎝ x0 x1 x2 x0 x1 x2⎠
# ```
#
# Description of keys and data in returned dictionary:
# -bestnorm - final chi^2
# -orignorm - starting value of chi^2
# -status - fitting status code
# -niter - number of iterations
# -nfev - number of function evaluations
# -npar - total number of parameters
# -nfree - number of free parameters
# -npegged - number of pegged parameters
# -nfunc - number of residuals (= num. of data points)
# -resid - list of final residuals
# -xerror - final parameter uncertainties (1-sigma), in the order of elements in `Pars` property dictionary.
# -x - final parameters values list in the order of elements in `Pars` property dictionary.
# -debug - string with derivatives debugging output
# -covar - final parameters covariance matrix.
# You can also access all results by [my configure propertyName] mechanism.
#
# Synopsis: -funct value -m value -pdata value ?-ftol value? ?-xtol value? ?-gtol value? ?-stepfactor value?
# ?-covtol value? ?-maxiter value? ?-maxfev value? ?-epsfcn value? ?-nofinitecheck?
argparse {
{-funct= -required}
{-m= -required}
{-pdata= -default ""}
{-ftol= -default 1e-10}
{-xtol= -default 1e-10}
{-gtol= -default 1e-10}
{-stepfactor= -default 100}
{-covtol= -default 1e-14}
{-maxiter= -default 200}
{-maxfev= -default 0}
{-epsfcn= -default 2.2204460e-16}
{-nofinitecheck -boolean}
}
my configure -funct $funct
my configure -m $m
my configure -pdata $pdata
my configure -ftol $ftol
my configure -gtol $gtol
my configure -xtol $xtol
my configure -stepfactor $stepfactor
my configure -covtol $covtol
my configure -maxiter $maxiter
my configure -maxfev $maxfev
my configure -epsfcn $epsfcn
my configure -nofinitecheck $nofinitecheck
}
method run {} {
# Runs optimization.
# Returns: dictionary containing resulted data
set sideMap [dict create auto 0 right 1 left -1 both 2 an 3]
set one 1.0
set p1 0.1
set p5 0.5
set p25 0.25
set p75 0.75
set p0001 1e-4
set zero 0.0
if {![info exists Pars]} {
return -code error "At least one parameter must be attached to optimizer before call to run"
}
set pars [dvalues [my getAllPars]]
set npar [llength $pars]
set pdata [my configure -pdata]
set funct [my configure -funct]
set mLoc [my configure -m]
foreach par $pars {
lappend xall [$par configure -initval]
}
set ftol [my configure -ftol]
set xtol [my configure -xtol]
set gtol [my configure -gtol]
set stepfactor [my configure -stepfactor]
set covtol [my configure -covtol]
set maxiter [my configure -maxiter]
set maxfev [my configure -maxfev]
set epsfcn [my configure -epsfcn]
set nofinitecheck [my configure -nofinitecheck]
set iflag 0
set qanylim 0
set npegged 0
set nfev 0
set info 0
set fnorm -1.0
set fnorm1 -1.0
set xnorm -1.0
set delta 0.0
set par 0.0
foreach par $pars {
if {[$par configure -fixed]} {
lappend pfixed 1
} else {
lappend pfixed 0
}
if {[catch {$par configure -step}]} {
lappend step 0
} else {
lappend step [$par configure -step]
}
if {[catch {$par configure -relstep}]} {
lappend dstep 0
} else {
lappend dstep [$par configure -relstep]
}
lappend mpside [dict get $sideMap [$par configure -side]]
if {![$par configure -debugder]} {
lappend ddebug 0
lappend ddrtol 0
lappend ddatol 0
} else {
lappend ddebug 1
lappend ddrtol [$par configure -derivreltol]
lappend ddatol [$par configure -derivabstol]
}
}
# Finish up the free parameters
set nfree 0
for {set i 0} {$i<$npar} {incr i} {
lappend ifree 0
}
set j 0
for {set i 0} {$i<$npar} {incr i} {
if {[@ $pfixed $i]==0} {
incr nfree
lset ifree $j $i
incr j
}
}
if {$nfree==0} {
return -code error "All parameters are fixed, optimization is not possible"
}
# allocate lists with zeros
for {set i 0} {$i<$npar} {incr i} {
lappend qllim 0
lappend qulim 0
lappend llim 0
lappend ulim 0
}
for {set i 0} {$i<$nfree} {incr i} {
set par [@ $pars [@ $ifree $i]]
if {[catch {$par configure -lowlim}]} {
lset qllim $i 0
lset llim $i 0
} else {
lset qllim $i 1
lset llim $i [$par configure -lowlim]
}
if {[catch {$par configure -uplim}]} {
lset qulim $i 0
lset ulim $i 0
} else {
lset qulim $i 1
lset ulim $i [$par configure -uplim]
}
if {[@ $qllim $i] || [@ $qulim $i]} {
set qanylim 1
}
}
if {$mLoc<$nfree} {
return -code error "Degree of freedom check failed because of 'm=$mLoc>=n=$nfree'"
}
# allocate temporary storage
for {set i 0} {$i<$npar} {incr i} {
lappend diag 0
}
for {set i 0} {$i<$mLoc} {incr i} {
lappend wa4 0
}
set ldfjac $mLoc
# Evaluate user function with initial parameter values
set fvec [dget [$funct $xall $pdata] fvec]
if {[llength $fvec]!=$mLoc} {
return -code error "Length of list '[llength $fvec]' returned from the function is less than m '$mLoc' value"
}
incr nfev
set fnorm [my Enorm $fvec]
#puts $fnorm
set orignorm [= {$fnorm*$fnorm}]
# Make a new copy
set xnew $xall
# Transfer free parameters to 'x'
for {set i 0} {$i<$nfree} {incr i} {
lappend x [@ $xall [@ $ifree $i]]
}
# Initialize Levelberg-Marquardt parameter and iteration counter
set par 0.0
set iter 1
for {set i 0} {$i<$nfree} {incr i} {
lappend qtf 0
}
# Beginning of the outer loop
while {true} {
# puts "beginning of the outer loop"
for {set i 0} {$i<$nfree} {incr i} {
lset xnew [@ $ifree $i] [@ $x $i]
}
# Calculate the jacobian matrix
set fdjac2Data [my Fdjac2 $funct $mLoc $ifree $nfree $xnew $fvec $ldfjac $epsfcn $pdata $nfev $step\
$dstep $mpside $qulim $ulim $ddebug $ddrtol $ddatol]
set fjac [dget $fdjac2Data fjac]
set nfev [dget $fdjac2Data nfev]
if {[dexist $fdjac2Data debug]} {
lappend debugOutput {*}[dget $fdjac2Data debug]
}
# Determine if any of the parameters are pegged at the limits
if {$qanylim} {
for {set j 0} {$j<$nfree} {incr j} {
set lpegged [= {[@ $qllim $j] && ([@ $x $j]==[@ $llim $j])}]
set upegged [= {[@ $qulim $j] && ([@ $x $j]==[@ $ulim $j])}]
set sum 0
# If the parameter is pegged at a limit, compute the gradient direction
if {$lpegged || $upegged} {
set ij [= {$j*$ldfjac}]
for {set i 0} {$i<$mLoc} {incr i} {
set sum [= {$sum+[@ $fvec $i]*[@ $fjac $ij]}]
incr ij
}
}
# If pegged at lower limit and gradient is toward negative then reset gradient to zero
if {$lpegged && ($sum>0)} {
set ij [= {$j*$ldfjac}]
for {set i 0} {$i<$mLoc} {incr i} {
lset fjac $ij 0
incr ij
}
}
# If pegged at upper limit and gradient is toward positive then reset gradient to zero
if {$upegged && ($sum<0)} {
set ij [= {$j*$ldfjac}]
for {set i 0} {$i<$mLoc} {incr i} {
lset fjac $ij 0
incr ij
}
}
}
}
# Compute the QR factorization of the jacobian
set qfracData [my Qfrac $mLoc $nfree $fjac $ldfjac 1 $nfree]
set ipvt [dget $qfracData ipvt]
set fjac [dget $qfracData a]
set wa1 [dget $qfracData rdiag]
set wa2 [dget $qfracData acnorm]
set wa3 [dget $qfracData wa]
# on the first iteration and if mode is 1, scale according to the norms of the columns of the initial jacobian.
if {$iter==1} {
for {set j 0} {$j<$nfree} {incr j} {
lset diag [@ $ifree $j] [@ $wa2 $j]
if {[@ $wa2 $j]==$zero} {
lset diag [@ $ifree $j] $one
}
}
# on the first iteration, calculate the norm of the scaled x and initialize the step bound delta.
for {set j 0} {$j<$nfree} {incr j} {
lset wa3 $j [= {[@ $diag [@ $ifree $j]]*[@ $x $j]}]
}
set xnorm [my Enorm $wa3]
set delta [= {$stepfactor*$xnorm}]
if {$delta==$zero} {
set delta $stepfactor
}
}
# form (q transpose)*fvec and store the first n components in qtf
for {set i 0} {$i<$mLoc} {incr i} {
lset wa4 $i [@ $fvec $i]
}
set jj 0
for {set j 0} {$j<$nfree} {incr j} {
set temp3 [@ $fjac $jj]
if {$temp3!=$zero} {
set sum $zero