6 if [[ ! $ZSH_EVAL_CONTEXT =~ :file$ ]]; then
7 echo "Auxiliary script for running the conjugate gradient fit in zsh."
8 echo "This script should be sourced by the steering script."
12 # This script contains auxiliary functions for running the conjugate gradient fit.
13 # It should be sourced by the steering script.
14 # The steering script should provide for the method getChi2 and set the fit parameters before calling method gradient.
15 # On return, the method getChi2 should set the current value of chi2 to its first positional parameter.
16 # The second positional parameter corresponds to an option that can be used within method getChi2.
17 # The value of the option corresponds to the following cases.
18 # 0 => step wise improvement of the chi2;
19 # 1 => evaluation of the chi2 before the determination of the gradient of the chi2; and
20 # 2 => evaluation of the derivative of the chi2 to each fit parameter.
21 # The fit parameters are defined by the associative array PARAMETERS.
22 # In this, each key corresponds to a valid zsh command that can be used for changing a specific fit parameter.
23 # The command should contain the wild card '%' which is repeatedly replaced by a value, referred to as step size.
24 # The associated value is used to evaluate the derivative of chi2 per unit step size of each fit parameter.
25 # These step sizes should be tuned beforehand to yield similar absolute values of the derivatives.
27 typeset -a CHI2 # internal chi2 values; index [1] corresponds to best value
28 typeset -a GRADIENT # derivative of chi2 per unit step size of each fit parameter
29 let "EPSILON = 5.0E-4" # default maximal distance to minimum
30 let "NUMBER_OF_ITERATIONS = 1000" # default maximum number of iterations
31 typeset -A PARAMETERS # fit parameters
33 if [[ -z "$DEBUG" ]]; then
39 # Auxiliary function to evaluate chi2 and gradient thereof at current position.
41 # The external method getChi2 is used to evaluate the chi2.
42 # The value corresponding to each key in the associative array PARAMETERS
43 # is used as unit step size for the evaluation of the derivative of the chi2.
44 # The results are stored in the internal variables CHI2[1] and GRADIENT, respectively.
54 for KEY in ${(k)PARAMETERS}; do
55 if (( ${#KEY} > $WIDTH )); then
63 for KEY in ${(ko)PARAMETERS}; do
65 BUFFER=(${(s/;/)${(Q)KEY}})
67 VALUE=$PARAMETERS[$KEY]
69 if (( $VALUE != 0.0 )); then
71 for K1 in ${(o)BUFFER}; do
72 eval ${(Q)${(qq)K1//\%/$((+0.5 * $VALUE))}}
77 for K1 in ${(O)BUFFER}; do
78 eval ${(Q)${(qq)K1//\%/$((-0.5 * $VALUE))}}
81 for K1 in ${(o)BUFFER}; do
82 eval ${(Q)${(qq)K1//\%/$((-0.5 * $VALUE))}}
87 for K1 in ${(O)BUFFER}; do
88 eval ${(Q)${(qq)K1//\%/$((+0.5 * $VALUE))}}
91 GRADIENT[$i]=$(($CHI2[2] - $CHI2[3]))
97 if (( $DEBUG >= 3 )); then
98 printf "%-${WIDTH}s %12.5f %12.5f\n" $KEY $PARAMETERS[$KEY] $GRADIENT[$i]
101 let "V += $GRADIENT[$i]*$GRADIENT[$i]"
105 if (( $DEBUG >= 3 )); then
106 printf "%-${WIDTH}s %12s %12.5f\n" "|gradient|" "" $((sqrt($V)))
112 # Move current position along gradient by given scaling factor of the unit step sizes.
114 # \param 1 scaling factor
118 if (( $1 > 0.0 )); then
122 for KEY in ${(ko)PARAMETERS}; do
124 BUFFER=(${(s/;/)${(Q)KEY}})
126 for K1 in ${(o)BUFFER}; do
127 eval ${(Q)${(qq)K1//\%/$(($GRADIENT[$i] * $1 * $PARAMETERS[$KEY]))}}
135 let "i = ${#GRADIENT}"
137 for KEY in ${(kO)PARAMETERS}; do
139 BUFFER=(${(s/;/)${(Q)KEY}})
141 for K1 in ${(O)BUFFER}; do
142 eval ${(Q)${(qq)K1//\%/$(($GRADIENT[$i] * $1 * $PARAMETERS[$KEY]))}}
154 # The algorithm is based on the conjugate gradient method.
155 # The position is moved to the optimal value and the final chi2 is returned.
157 # If the option is one, the step sizes are fine-tuned according to the gradient of the chi2.
158 # The value corresponding to each key in the associative array PARAMETERS is then
159 # multiplied by the corresponding element in GRADIENT and only the sign thereof is maintained.
161 # \param 1 maximum number of extra steps
162 # \param 2 variable containing final chi2 value on return
169 if (( ${#PARAMETERS} == 0 )); then
177 for (( i = 1; i <= ${#GRADIENT}; ++i)); do
178 G[$i]=$((-1.0 * $GRADIENT[$i]))
183 for (( N = 0; $N != $NUMBER_OF_ITERATIONS; N += 1 )); do
185 if (( $DEBUG >= 3 )); then
186 printf "chi2[1] %4d %12.5f\n" $N $CHI2[1]
189 # minimise chi2 in direction of gradient
194 for (( DS = 1.0, M = 0 ; $DS > 1.0e-3; )); do
200 if (( $DEBUG >= 3 )); then
201 printf "chi2[4] %4d %12.5f %12.5e\n" $M $CHI2[4] $DS
204 if (( $CHI2[4] < $CHI2[3] )); then
216 # if chi2 increases, try additional steps first to overcome possible hurdle, then try single step with reduced size
218 if (( $DS == 1.0 )); then
220 if (( $M == 0 )); then
224 if (( $M != $1 )); then
232 for (( ; $M != 0; --M )); do
242 if (( $CHI2[3] < $CHI2[2] )); then
244 # final step based on parabolic interpolation through following points
246 # X1 = -1 * DS -> CHI2[2]
247 # X2 = 0 * DS -> CHI2[3]
248 # X3 = +1 * DS -> CHI2[4]
250 let "F21 = $CHI2[3] - $CHI2[2]" # F(X2) - F(X1)
251 let "F23 = $CHI2[3] - $CHI2[4]" # F(X2) - F(X3)
253 let "XS = 0.5 * ($F21 - $F23) / ($F23 + $F21)"
255 move $((+1.0 * $XS * $DS))
259 if (( $CHI2[4] < $CHI2[3] )); then
265 move $((-1.0 * $XS * $DS))
270 if (( $DEBUG >= 3 )); then
271 printf "chi2[3] %4d %12.5f %12.5e\n" $N $CHI2[3] $DS
284 if (( fabs($CHI2[3] - $CHI2[1]) < $EPSILON * 0.5 * (fabs($CHI2[1]) + fabs($CHI2[3])) )); then
296 for (( i = 1; $i <= ${#GRADIENT}; ++i )); do
297 let "GG += $G[$i]*$G[$i]"
298 let "DGG += ($GRADIENT[$i] + $G[$i]) * $GRADIENT[$i]"
301 if (( $GG == 0.0 )); then
305 let "GAM = $DGG / $GG"
307 for (( i = 1; $i <= ${#GRADIENT}; ++i )); do
308 G[$i]=$((-1.0 * $GRADIENT[$i]))
309 H[$i]=$(($G[$i] + $GAM * $H[$i]))
314 if (( $DEBUG >= 3 )); then
315 printf "chi2[1] %4d %12.5f\n" $N $CHI2[1]
323 # Additional fit function to be called after gradient.
325 # The algorithm is based on sorting the derivatives of the chi2 and
326 # moving the position one-by-one to the optimal value.
327 # The final chi2 is returned.
329 # \param 1 variable containing final chi2 value on return
338 for KEY in ${(k)PARAMETERS}; do
339 if (( ${#KEY} > $WIDTH )); then
340 let "WIDTH = ${#KEY}"
345 for (( N = 0; $N != $NUMBER_OF_ITERATIONS; N += 1 )); do
355 for KEY in ${(ko)PARAMETERS}; do
357 VALUE=$PARAMETERS[$KEY]
359 BUFFER=(${(s/;/)${(Q)KEY}})
361 for K1 in ${(o)BUFFER}; do
362 eval ${(Q)${(qq)K1//\%/$((+1.0 * $VALUE))}}
367 for K1 in ${(O)BUFFER}; do
368 eval ${(Q)${(qq)K1//\%/$((-1.0 * $VALUE))}}
371 for K1 in ${(o)BUFFER}; do
372 eval ${(Q)${(qq)K1//\%/$((-1.0 * $VALUE))}}
377 for K1 in ${(O)BUFFER}; do
378 eval ${(Q)${(qq)K1//\%/$((+1.0 * $VALUE))}}
381 if (( $DEBUG >= 3 )); then
382 printf "%-${WIDTH}s %12.5f %12.5f -> %+9.5f <- %+9.5f\n" $KEY $VALUE $CHI2[1] $(($CHI2[2] - $CHI2[1])) $(($CHI2[3] - $CHI2[1]))
385 # map index to derivative of chi2 and maintain sign of step
387 if (($CHI2[2] < $CHI2[1] && $CHI2[2] < $CHI2[3])); then
389 G[$i]=$(($CHI2[2] - $CHI2[1]))
393 elif (($CHI2[3] < $CHI2[1] && $CHI2[3] < $CHI2[2])); then
395 G[$i]=$(($CHI2[3] - $CHI2[1]))
399 # elif (($CHI2[3] - $CHI2[2] > 0.5*($CHI2[2] + $CHI2[3]) - $CHI2[1])); then
401 # G[$i]=$(($CHI2[2] - $CHI2[1]))
405 # elif (($CHI2[2] - $CHI2[3] > 0.5*($CHI2[2] + $CHI2[3]) - $CHI2[1])); then
407 # G[$i]=$(($CHI2[3] - $CHI2[1]))
416 if (( ${#G} == 0 )); then
421 # sort derivatives of chi2 and optimise position one-by-one
425 for i VALUE in `printf "%d %12.3e\n" ${(kv)G} | sort -g -k2`; do
427 KEY=${${(ko)PARAMETERS}[$i]}
429 BUFFER=(${(s/;/)${(Q)KEY}})
433 let "DS = $GRADIENT[$i] * $PARAMETERS[$KEY]"
435 if (($DEBUG >= 3)); then
436 printf "%-${WIDTH}s %12.5f %12.5f\n" $KEY $DS $G[$i]
441 for (( n = 1; ; ++n )); do
443 for K1 in ${(o)BUFFER}; do
444 eval ${(Q)${(qq)K1//\%/$((+1.0 * $DS))}}
449 if (( $DEBUG >= 3 )); then
450 printf "chi2[4] %4d %12.5f\n" $n $CHI2[4]
453 if (( $CHI2[4] < $CHI2[3] )); then
460 for K1 in ${(O)BUFFER}; do
461 eval ${(Q)${(qq)K1//\%/$((-1.0 * $DS))}}
464 if (( $CHI2[3] < $CHI2[2] )); then
466 # final step based on parabolic interpolation through following points
468 # X1 = -1 * DS -> CHI2[2]
469 # X2 = 0 * DS -> CHI2[3]
470 # X3 = +1 * DS -> CHI2[4]
472 let "F21 = $CHI2[3] - $CHI2[2]" # F(X2) - F(X1)
473 let "F23 = $CHI2[3] - $CHI2[4]" # F(X2) - F(X3)
475 let "XS = 0.5 * ($F21 - $F23) / ($F23 + $F21)"
477 for K1 in ${(o)BUFFER}; do
478 eval ${(Q)${(qq)K1//\%/$((+1.0 * $XS * $DS))}}
483 if (( $CHI2[4] < $CHI2[3] )); then
489 for K1 in ${(O)BUFFER}; do
490 eval ${(Q)${(qq)K1//\%/$((-1.0 * $XS * $DS))}}
495 if (( $DEBUG >= 3 )); then
496 printf "chi2[3] %4d %12.5f\n" $n $CHI2[3]
505 if (( fabs($CHI2[2] - $CHI2[1]) < $EPSILON * 0.5 * (fabs($CHI2[1]) + fabs($CHI2[2])) )); then
513 if (( $DEBUG >= 3 )); then
514 printf "chi2[1] %4d %12.5f\n" $N $CHI2[1]