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]