6if [[ ! $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.
 
   27typeset -a    CHI2                                # internal chi2 values; index [1] corresponds to best value
 
   28typeset -a    GRADIENT                            # derivative of chi2 per unit step size of each fit parameter
 
   29let          "EPSILON               =  5.0E-4"    # default maximal distance to minimum
 
   30let          "NUMBER_OF_ITERATIONS  =  1000"      # default maximum number of iterations
 
   31typeset -A    PARAMETERS                          # fit parameters
 
   33if [[ -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]