
#
# $Log: not supported by cvs2svn $
# Revision 1.13  2005/11/02 20:34:15  soliday
# Changed APScavput from using putw to putq.
#
# Revision 1.12  2005/04/19 17:43:49  shang
# added APSComputeMoments proc to calculate the statisitcs values of a list of values
#
# Revision 1.11  2005/02/25 16:47:48  soliday
# Added APSMathStats because there is a bug in the Tcllib math extension
# stats command when the sum of the numbers is zero. This results in a
# divide by zero error.
#
# Revision 1.10  2005/02/21 22:52:26  soliday
# Fixed a potential divide by zero problem with the despike option.
#
# Revision 1.9  2005/02/14 21:11:14  soliday
# Fixed a problem with APScavget when using the despike option with more than
# one PV. It was using the same variable in an outer loop and an inner loop
# when it should have been using a different variable name to avoid a conflict.
#
# Revision 1.8  2004/12/15 20:34:01  soliday
# Ported the new -despike option from cavget to APScavget.
#
# Revision 1.7  2004/03/11 23:10:01  soliday
# Fixed issue with APScavput when trying to write to a readonly PV.
# It now acts the same as cavput does.
#
# Revision 1.6  2004/03/10 22:54:57  soliday
# -floatformat option was incorrectly coded as -formatfloat
#
# Revision 1.5  2004/02/03 18:52:35  soliday
# Added APScavput which is a direct replacement for cavput with the added
# benefit of only having to connect to the PVs once per application.
#
# Revision 1.4  2004/02/02 17:36:19  soliday
# Added APScavget which is direct replacement for cavget with the added
# benefit of only having to connect to the PVs once per application.
#
# Revision 1.3  2003/10/30 17:24:52  shang
# added returning error message if "sdds load" fails in APScagetTextFromWaveform proc
#
# Revision 1.2  2002/08/13 18:54:37  soliday
# Added the Help functions.
#
# Revision 1.1  2002/07/09 22:55:32  borland
# Added cautils.tcl
#
#

proc APSMathStats {val1 val2 args} {
     set sum [ expr { $val1+$val2 } ]
     set N [ expr { [ llength $args ] + 2 } ]
     foreach val $args {
        set sum [ expr { $sum+$val } ]
     }
     set mean [ expr { double($sum)/$N } ]
     set sigma_sq 0
     foreach val [ concat $val1 $val2 $args ] {
        set sigma_sq [ expr { $sigma_sq+pow(($val-$mean),2) } ]
     }
     set sigma_sq [ expr { $sigma_sq/($N-1) } ] 
     set sigma [ expr { sqrt($sigma_sq) } ]
     if {$mean == 0} {
        set cov 99e99
     } else {
        set cov [ expr { ($sigma/$mean)*100 } ]
     }
     return [ list $mean $sigma $cov ]
}

# get text string stored in an EPICS character waveform

proc APScagetTextFromWaveformHelp {} {
    return "Usage: APScagetTextFromWaveform \n\[-pvName <string>\]"
}

proc APScagetTextFromWaveform {args} {
    set pvName ""
    if {[APSStrictParseArguments {pvName}] || ![string length $pvName]} {
        return -code error "APScaputTextToWaveform: invalid arguments"
    }
    set tmpFile /tmp/[APSTmpString]
    if [catch {exec sddswget -pvnames=$pvName $tmpFile} result] {
        file delete -force $tmpFile
        return -code error "APScaputTextToWaveform: $result"
    }
    if {[catch {sdds load $tmpFile data} result]} {
        file delete -force $tmpFile
        return -code error "APScaputTextToWaveform: $result"
    }
    for {set i 0} {$i < 255} {incr i} {
        lappend charNum [format %c $i]
    }
    set i 0
    foreach c [lindex $data(Column.Waveform) 0] {
        if {[lsearch -exact $charNum $c] == -1} {
            break
        }
        incr i
    }
    incr i -1
    set text [string trim [string trim [join [lrange [lindex $data(Column.Waveform) 0] 0 $i] ""] \000]]
    return $text
}

# put text string to an EPICS character waveform

proc APScaputTextToWaveformHelp {} {
    return "Usage: APScaputTextToWaveform \n\[-pvName <string>\]\n\[-text <string>\]"
}
proc APScaputTextToWaveform {args} {
    set pvName ""
    set text ""
    if {[APSStrictParseArguments {pvName text}] || ![string length $pvName]} {
        return -code error "APScaputTextToWaveform: invalid arguments"
    }

    # determine the length of the waveform
    if [catch {exec caget -t $pvName} waveformData] {
        return -code error "APScaputTextToWaveform: $waveformData"
    }
    set wlength [string trim [lindex [split [string trim $waveformData]] 0]]

    # build a list of the characters in the output waveform, padded with blanks to
    # the full length
    set tlength [string length $text]
    if $wlength<$tlength {
        set tlength $wlength
    }
    for {set i 0} {$i<$tlength} {incr i} {
        lappend datalist [string index $text $i]
    }
    for {} {$i<$wlength} {incr i} {
        lappend datalist ""
    }

    # create SDDS file to use with sddswput
    set data(ColumnNames) "Waveform" 
    set data(ColumnInfo.Waveform) "type SDDS_CHARACTER"
    set data(ParameterNames) "WaveformPV"
    set data(ParameterNames.WaveformPV) "type SDDS_STRING"
    set data(Parameter.WaveformPV) [list $pvName]
    set data(Column.Waveform) [list $datalist]
    set tmpFile /tmp/[APSTmpString]
    if {[catch {sdds save $tmpFile data} results]} {
        file delete -force $tmpFile
	return -code error "APScaputTextToWaveform: $results"
    }

    # set the waveform 
    if {[catch {exec sddswput $tmpFile} results]} {
        file delete -force $tmpFile
	return -code error "APScaputTextToWaveform: $results"
    }
    file delete -force $tmpFile
}

proc APScavgetHelp {} {
    return {Usage: APScavget 
        [-pvName=<string>[,<string>...]]
        [-range=begin=<int>,end=<int>[,format=string][,interval=<int>]]
        [-floatformat=<printfString>]
        [-delimiter=<string>]
        [-labeled]
        [-noQuotes]
        [-embrace=start=<string>,end=<string>]
        [-cavputForm]
        [-statistics=number=<value>,pause=<value>[,format=[tagvalue][pretty][SDDS,file=<filename>]]]
        [-pendIoTime=<seconds>]
        [-repeat=number=<int>,pause=<seconds>[,average[,sigma]]]
        [-numerical]
        [-errorValue=<string>]
        [-excludeErrors]
        [-despike=[neighbors=<integer>][,passes=<integer>][,averageOf=<integer>][,threshold=<value>]]
        [-showwarnings] [-printErrors]}
}

proc APScavget {args} {
    set timeout 10
    set options(number) 0
    set floatformat "%g"
    set delimiter "\n"
    set labeled 0
    set quotes 1
    set embrace(start) ""
    set embrace(end) ""
    set cavputform 0
    set showwarnings 0
    set errorvalue "?"
    set excludeerrors 0
    set numerical 0
    set repeat(number) 1
    set repeat(last) 0
    set dryrun 0
    set statistics 0
    set repeats 0
    set doDespike 0
    set printErrors 0
    foreach option $args {
        if {[string compare [string index $option 0] "-"] == 0} {
            set i [string first "=" $option]
            if {$i == -1} {
                set options($options(number)) [string range $option 1 end]
                set options($options(number),number) 0
            } else {
                set options($options(number)) [string range $option 1 [expr {$i - 1}]]
                set j 0
                set options($options(number),number) $j
                set option [string range $option [expr {$i + 1}] end]
                set i [string first "," $option]
                set ii [string first "=" $option]
                if {($i == -1) || (($ii > -1) && ($ii < $i))} {
                    set i $ii
                }
                while {$i != -1} {
                    set options($options(number),$j) [string range $option 0 [expr {$i - 1}]]
                    set option [string range $option [expr {$i + 1}] end]
                    incr j
                    set options($options(number),number) $j
                    set i [string first "," $option]
                    set ii [string first "=" $option]
                    if {($i == -1) || (($ii > -1) && ($ii < $i))} {
                        set i $ii
                    }
                }
                set options($options(number),$j) $option
                incr j
                set options($options(number),number) $j
            }
            incr options(number)
        } else {
            puts stderr "Warning: Unknown option: $option"
        }
    }
    set listOption(number) 0
    for {set i 0} {$i < $options(number)} {incr i} {
        if {[string compare -nocase -length [string length $options($i)] $options($i) "list"] == 0} {
            set n 0
            set listOption($listOption(number),number) $n
            for {set j 0} {$j < $options($i,number)} {incr j} {
                set listOption($listOption(number),$n) $options($i,$j)
                incr n
                set listOption($listOption(number),number) $n
            }
            incr listOption(number)
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "range"] == 0} {
            set range(begin) ""
            set range(end) ""
            set range(format) ""
            set range(interval) "1"
            for {set j 0 ; set jj 1} {$j < $options($i,number)} {incr j 2 ; incr jj 2} {
                if {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "begin"] == 0} {
                    set range(begin) $options($i,$jj)
                    if {![string is integer -strict $range(begin)]} {
                        set range(begin) 1
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "end"] == 0} {
                    set range(end) $options($i,$jj)
                    if {![string is integer -strict $range(end)]} {
                        set range(end) 1
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "format"] == 0} {
                    set range(format) $options($i,$jj)
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "interval"] == 0} {
                    set range(interval) $options($i,$jj)
                    if {![string is integer -strict $range(interval)]} {
                        set range(interval) 1
                    }
                }
            }
            if {([string length $range(begin)]) && ([string length $range(end)])} {
                if {($range(interval) > 0) && ($range(begin) > $range(end))} {
                    return -code error "APScavget: invalid -range syntax"
                }
                if {($range(interval) < 0) && ($range(begin) < $range(end))} {
                    return -code error "APScavget: invalid -range syntax"
                }
                if {($range(interval) == 0) && ($range(begin) != $range(end))} {
                    return -code error "APScavget: invalid -range syntax"
                }
                set n 0
                set listOption($listOption(number),number) $n
                for {set j $range(begin)} {$j <= $range(end)} {incr j $range(interval)} {
                    if {[string length $range(format)]} {
                        set listOption($listOption(number),$n) [format $range(format) $j]
                    } else {
                        set listOption($listOption(number),$n) $j
                    }
                    incr n
                    set listOption($listOption(number),number) $n
                }
                incr listOption(number)
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "floatformat"] == 0} {
            if {[info exists options($i,0)]} {
                set floatformat $options($i,0)
            } else {
                return -code error "APScavget: invalid -floatformat syntax"
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "delimiter"] == 0} {
            if {[info exists options($i,0)]} {
                set delimiter $options($i,0)
            } else {
                return -code error "APScavget: invalid -delimiter syntax"
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "labeled"] == 0} {
            set labeled 1
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "noquotes"] == 0} {
            set quotes 0
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "embrace"] == 0} {
            set embrace(start) "\{"
            set embrace(end) "\}"
            for {set j 0 ; set jj 1} {$j < $options($i,number)} {incr j 2 ; incr jj 2} {
                if {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "start"] == 0} {
                    set embrace(start) $options($i,$jj)
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "end"] == 0} {
                    set embrace(end) $options($i,$jj)
                }
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "cavputform"] == 0} {
            set cavputform 1
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "pendiotime"] == 0} {
            if {[info exists options($i,0)]} {
                set timeout $options($i,0)
            } else {
                return -code error "APScavget: invalid -pendIoTime syntax"
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "showwarnings"] == 0} {
            set showwarnings 1
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "errorvalue"] == 0} {
            if {[info exists options($i,0)]} {
                set errorvalue $options($i,0)
            } else {
                return -code error "APScavget: invalid -pendIoTime syntax"
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "excludeerrors"] == 0} {
            set excludeerrors 1
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "numerical"] == 0} {
            set numerical 1
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "dryrun"] == 0} {
            set dryrun 1
        }  elseif {[string compare -nocase -length [string length $options($i)] $options($i) "printErrors"] == 0} {
            set printErrors 1
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "repeat"] == 0} {
            set j 0
            set repeats 1
            set repeat(pause) 1
            set repeat(average) 0
            set repeat(sigma) 0
            while {$j < $options($i,number)} {
                if {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "number"] == 0} {
                    incr j
                    if {![info exists options($i,$j)]} {
                        return -code error "APScavget: invalid -repeat number syntax"
                    }
                    set repeat(number) $options($i,$j)
                    if {([string is double -strict $repeat(number)])} {
                        set repeat(number) [expr {int($repeat(number))}]
                    } else {
                        return -code error "APScavget: invalid -repeat number syntax"
                    }
                    if {($repeat(number) <= 0)} {
                        set repeat(number) 1
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "pause"] == 0} {
                    incr j
                    if {![info exists options($i,$j)]} {
                        return -code error "APScavget: invalid -repeat pause syntax"
                    }
                    set repeat(pause) $options($i,$j)
                    if {(![string is double -strict $repeat(pause)]) || ($repeat(pause) <= 0)} {
                        set repeat(pause) 0
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "average"] == 0} {
                    set repeat(average) 1
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "sigma"] == 0} {
                    set repeat(sigma) 1
                }
                incr j
            }
            if {$doDespike} {
                set repeat(average) 1
                set repeat(sigma) 0
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "statistics"] == 0} {
            set statistics 1
            set stat(pause) 1
            set j 0
            while {$j < $options($i,number)} {
                if {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "number"] == 0} {
                    incr j
                    if {![info exists options($i,$j)]} {
                        return -code error "APScavget: invalid -statistics number syntax"
                    }
                    set stat(number) $options($i,$j)
                    if {([string is double -strict $stat(number)])} {
                        set stat(number) [expr {int($stat(number))}]
                    } else {
                        return -code error "APScavget: invalid -statistics number syntax"
                    }
                    if {($stat(number) <= 0)} {
                        set stat(number) 1
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "pause"] == 0} {
                    incr j
                    if {![info exists options($i,$j)]} {
                        return -code error "APScavget: invalid -statistics pause syntax"
                    }
                    set stat(pause) $options($i,$j)
                    if {(![string is double -strict $stat(pause)]) || ($stat(pause) <= 0)} {
                        set stat(pause) 0
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "format"] == 0} {
                    incr j
                    if {![info exists options($i,$j)]} {
                        return -code error "APScavget: invalid -statistics format syntax"
                    }
                    if {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "tagvalue"] == 0} {
                        set stat(format) "tagvalue"
                    } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "pretty"] == 0} {
                        set stat(format) "pretty"
                    } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "sdds"] == 0} {
                        set stat(format) "sdds"
                        incr j
                        if {![info exists options($i,$j)]} {
                            return -code error "APScavget: invalid -statistics sdds syntax"
                        }
                        if {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "file"] == 0} {
                            incr j
                            if {![info exists options($i,$j)]} {
                                return -code error "APScavget: invalid -statistics file syntax"
                            }
                            set stat(file) $options($i,$j)
                        }
                    }
                }
                incr j
            }
            if {![info exists stat(format)]} {
                return -code error "APScavget: invalid -statistics format syntax"
            }
            if {(![info exists stat(number)]) || ($stat(number) < 1)} {
                return -code error "APScavget: invalid -statistics number syntax"
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "despike"] == 0} {
            set despike(neighbors) 10
            set despike(passes) 2
            set despike(averageOf) 4
            set despike(threshold) 0
            set doDespike 1
            for {set j 0 ; set jj 1} {$j < $options($i,number)} {incr j 2 ; incr jj 2} {
                if {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "neighbors"] == 0} {
                    set despike(neighbors) $options($i,$jj)
                    if {![string is integer -strict $despike(neighbors)]} {
                        set despike(neighbors) 10
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "passes"] == 0} {
                    set despike(passes) $options($i,$jj)
                    if {![string is integer -strict $despike(passes)]} {
                        set despike(passes) 2
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "averageOf"] == 0} {
                    set despike(averageOf) $options($i,$jj)
                    if {![string is integer -strict $despike(averageOf)]} {
                        set despike(averageOf) 4
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "threshold"] == 0} {
                    set despike(threshold) $options($i,$jj)
                    if {![string is integer -strict $despike(threshold)]} {
                        set despike(threshold) 0
                    }
                }
            }
            if {($despike(passes) < 0) || ($despike(averageOf) < 0) || ($despike(threshold) < 0)} {
                return -code error "APScavget: invalid -despike syntax"
            }
            set repeat(average) 1
            set repeat(sigma) 0
        } else {
            puts stderr "Warning: Unknown option: -$options($i)"
        }
    }
    if {$listOption(number)==0} {
        return -code error "APScavget: -list option is missing"
    }
    if {($statistics) && ($repeats)} {
        return -code error "APScavget: -statistics and -repeat options cannot be used together"
    }
    if {($statistics) && ($cavputform)} {
        return -code error "APScavget: -statistics and -cavputform options cannot be used together"
    }
    if ($doDespike) {
        if {($statistics == 0) && ($repeats == 0)} {
            return -code error "APScavget: neither repeat or statistics are given, cannot do despiking!"
        }
        if {($repeats)} {
            if {$despike(neighbors) > $repeat(number)} {
                set despike(neighbors) $repeat(number)
            }
            if {$despike(averageOf) > $repeat(number)} {
                set despike(averageOf) $repeat(number)
            }
        }
        if {($statistics)} {
            if {$despike(neighbors) > $stat(number)} {
                set despike(neighbors) $stat(number)
            }
            if {$despike(averageOf) > $stat(number)} {
                set despike(averageOf) $stat(number)
            }
        }
    }

    if {$statistics} {
        package require math
        set ca(number) $stat(number)
        set ca(pause) $stat(pause)
    } elseif {$repeats} {
        package require math
        set ca(number) $repeat(number)
        set ca(pause) $repeat(pause)
    } else {
        set ca(number) 1
        set ca(pause) 0
    }
    if {$cavputform} {
        set delimiter ","
        set labeled 1
        set quotes 0
        set embrace(start) ""
        set embrace(end) ""
        set repeat(sigma) 0
        set repeat(last) 1
    }

    set num $listOption(0,number)
    for {set i 0} {$i < $listOption(0,number)} {incr i} {
        set listVar($i) $listOption(0,$i)
        set listVar($i,tmp) $listVar($i)
    }
    for {set n 1} {$n < $listOption(number)} {incr n} {
        for {set i 0 ; set k 0} {$i < $num} {incr i} {
            for {set j 0} {$j < $listOption($n,number)} {incr j ; incr k} {
                set listVar($k) "$listVar($i,tmp)$listOption($n,$j)"
            }
        }
        for {set i 0 ; set k 0} {$i < $num} {incr i} {
            for {set j 0} {$j < $listOption($n,number)} {incr j ; incr k} {
                set listVar($k,tmp) $listVar($k)
            }
        }
        set num [expr {$num * $listOption($n,number)}]        
    }
    
    #Find out if the pv command has been switched to error mode
    if {[catch {pv} result]} {
        set pverrormode 1
    } else {
        set pverrormode 0
    }

    set tclVars ""
    set iocVars ""
    for {set i 0} {$i < $num} {incr i} {
        append tclVars "apscavgetput($listVar($i)) "
        append iocVars "$listVar($i) "
    }
    if {$dryrun} {
        set results ""
        foreach iocvar $iocVars {
            lappend results [format "%32s" $iocvar]
        }
        return [join $results \n]
    }
    
    eval global apscavgetput
    #Do not worry about relinking PVs. 
    #The pv command knows if they have already been linked.
    if {$pverrormode} {
        if {[catch {pv link $tclVars $iocVars $timeout} result]} {
            return -code error "APScavget: $result"
        }
    } else {
        if {[pv link $tclVars $iocVars $timeout] == 1} {
            global errorCode
            return -code error "APScavget: $errorCode"
        }
    }
    set endtime [expr {[clock clicks -milliseconds] + $timeout * 1000.0}]
    set unconnected 0
    global apscavgettmpvar
    while {[clock clicks -milliseconds] < $endtime} {
        set unconnected 0
        update
        foreach tclvar $tclVars {
            if {$pverrormode} {
                if {[catch {pv stat $tclvar apscavgettmpvar} result]} {
                    return -code error "APScavget: $result"
                }
            } else {
                if {[pv stat $tclvar apscavgettmpvar] == 1} {
                    global errorCode
                    return -code error "APScavget: $errorCode"
                }
            }
            if {($apscavgettmpvar(state) != "OK") && ($apscavgettmpvar(state) != "WR")} {
                set unconnected 1
                break
            }
        }
        if {$unconnected == 0} {
            break
        }
    }
    if {$unconnected} {
        set pvErrorList ""
        set errorVars ""
        foreach tclvar $tclVars iocvar $iocVars {
            if {$pverrormode} {
                if {[catch {pv stat $tclvar apscavgettmpvar} result]} {
                    return -code error "APScavget: $result"
                }
            } else {
                if {[pv stat $tclvar apscavgettmpvar] == 1} {
                    global errorCode
                    return -code error "APScavget: $errorCode"
                }
            }
            if {($apscavgettmpvar(state) != "OK") && ($apscavgettmpvar(state) != "WR")} {
                append pvErrorList "$iocvar "
                set $tclvar $errorvalue
                append errorVars "1 "
            } else {
                append errorVars "0 "
            }
        }
        if {([string length $pvErrorList]) && ($showwarnings)} {
            puts stderr "Warning: Unable to connect to PVs: $pvErrorList"
        }
    } else {
        foreach tclvar $tclVars {
            append errorVars "0 "
        }
    }
    unset apscavgettmpvar

    for {set loop 1} {$loop <= $ca(number)} {incr loop} {
        if {$pverrormode} {
            if {[catch {pv getw $tclVars $timeout} result]} {
                return -code error "APScavget: $result"
            }
        } else {
            if {[pv getw $tclVars $timeout] == 1} {
                global errorCode
                return -code error "APScavget: $errorCode"
            }
        }
        set i 0
        foreach tclvar $tclVars {
            lappend values($i) [set $tclvar]
            incr i
        }
        if {$repeat(last)} {
            set i 0
            foreach tclvar $tclVars {
                set values($i) [list [set $tclvar]]
                incr i
            }
        }
        if {$ca(number) != $loop} {
            after [expr {int($ca(pause) * 1000)}]
        }
    }

    set results ""

    if {$statistics} {
        if {$stat(format) == "pretty"} {
            lappend results "         ControlName      mean    median     sigma     stDev       MAD       min       max    spread"
            lappend results "----------------------------------------------------------------------------------------------------"
        }
    }    
    set i 0
    set fail 0
    foreach var $tclVars iocvar $iocVars errorvar $errorVars {
        if {($printErrors) && ($errorvar)} {
            lappend results "Error: $iocvar is not connected or invalid channel name."
            set fail 1
            continue
        }
        if {($excludeerrors) && ($errorvar)} {
            incr i
            continue
        }
        if {($numerical) && ($errorvar == 0)} {
            if {$pverrormode} {
                if {[catch {pv info $var choices} choices]} {
                    return -code error "APScavget: $choices"
                }
            } else {
                set choices [pv info $var choices]
            }
            set v ""
            foreach v2 $values($i) {
                set index [lsearch -exact [lindex [lindex $choices 0] 1] $v2]
                if {$index != -1} {
                    set v2 $index
                }
                lappend v $v2
            }
        } else {
            set v $values($i)
        }

        if {($repeats) || ($statistics)} {
            set last 0
            set sum 0
            set sum2 0
            foreach vv $v {
                if {[string is double -strict $vv]} {
                    set sum [expr {$sum + $vv}]
                } else {
                    set last 1
                }
            }
            if {$last} {
                set v [list [lindex $v end]]
                set labels none
            } else {
                if {[llength $v] == 1} {
                    set mean $v
                    set standDev NaN
                    set sigma NaN
                } else {
                    if {$doDespike} {
                        set tempdata $v
                        if {$repeats} {
                            set rows $repeat(number)
                        }
                        if {$statistics} {
                            set rows $stat(number)
                        }
                        set neighbors [expr {int(2 * (($despike(neighbors) + 1) / 2.0))}]
                        set despikeCount 0
                        set passes $despike(passes)
                        unset -nocomplain deltaSum
                        while {$passes > 0} {
                            incr passes -1
                            set despikeCount 0
                            for {set i0 0} {$i0 < $rows} {incr i0 [expr {int($neighbors / 2.0)}]} {
                                set i1 [expr {int($i0 - $neighbors / 2.0)}]
                                set i2 [expr {int($i0 + $neighbors / 2.0)}]
                                if {$i1 < 0} {
                                    set i1 0
                                }
                                if {$i2 >= $rows} {
                                    set i2 [expr {$rows - 1}]
                                }
                                if {[expr {$i2 - $i1}] == 0} {
                                    continue
                                }
                                for {set ii $i1} {$ii <= $i2} {incr ii} {
                                    set deltaSum([expr {$ii - $i1}]) 0
                                    for {set j $i1} {$j <= $i2} {incr j} {
                                        set deltaSum([expr {$ii - $i1}]) [expr {$deltaSum([expr {$ii - $i1}]) + abs([lindex $tempdata $ii] - [lindex $tempdata $j]) }]
                                    }
                                }
                                if {[info exists deltaSum]} {
                                    set n [expr {$i2 - $i1 + 1}]
                                    unset -nocomplain temp
                                    for {set ii 0} {$ii < $n} {incr ii} {
                                        lappend temp $deltaSum($ii)
                                    }
                                    set temp2 [lsort -real $temp]
                                    set imin [lsearch $temp [lindex $temp2 0]]
                                    set imax [lsearch $temp [lindex $temp2 end]]
                                    incr imax $i1
                                    if {($imax < 0) || ($imax > $rows)} {
                                        return -code error "APScavget: index out of range in despikeData\n  imax = ${imax}, rows=${rows}, i1=${i1}, i2=${i2}, neighbors=${neighbors}"
                                    }
                                    if {($despike(threshold) == 0) || ([expr {$despike(threshold) * $neighbors}] < $deltaSum([expr {$imax - $i1}]))} {
                                        set i1a [expr {int($imax - $despike(averageOf) / 2.0)}]
                                        if {$i1a < 0} {
                                            set i1a 0
                                        }
                                        set i2a [expr {int($imax + $despike(averageOf) / 2.0)}]
                                        if {$i2a >= $rows} {
                                            set i2a [expr {$rows - 1}]
                                        }
                                        for {set sum 0 ; set ii $i1a} {$ii <= $i2a} {incr ii} {
                                            if {$ii != $imax} {
                                                set sum [expr {$sum + [lindex $tempdata $ii]}]
                                            }
                                        }
                                        incr despikeCount
                                        if {$i2a != $i1a} {
                                            set tempdata [lreplace $tempdata $imax $imax [expr {1.0 * $sum / ($i2a - $i1a)}]]
                                        }
                                    }
                                } 
                            }
                            if {($despikeCount == 0)} {
                                break
                            }
                        }
                        set v $tempdata
                    }
                    set statslist [eval APSMathStats $v]
                    set mean [lindex $statslist 0]
                    set stddev [lindex $statslist 1]
                    set sigma [eval math::sigma $v]

                    set nums [lsort -real $v]
                    set mid [expr {int([llength $v] / 2)}]
                    if [expr {$n % 2}] {
                        set median [lindex $nums $mid]
                    } else {
                        set a [lindex $nums $mid]
                        set b [lindex $nums [expr {$mid - 1}]]
                        set median [expr {($a + $b) / 2.0}]
                    }
                    set madnums ""
                    foreach vv $v {
                        lappend madnums [expr {abs($vv - $median)}]
                    }
                    set nums2 [lsort -real $madnums]
                    set mid [expr {int([llength $madnums] / 2)}]
                    if [expr {$n % 2}] {
                        set MAD [lindex $nums2 $mid]
                    } else {
                        set a [lindex $nums2 $mid]
                        set b [lindex $nums2 [expr {$mid - 1}]]
                        set MAD [expr {($a + $b) / 2.0}]
                    }
                    set min [lindex $nums 0]
                    set max [lindex $nums end]
                    set spread [expr {$max - $min}]

                }
                if {$statistics} {
                    set v "$mean $median $sigma $stddev $MAD $min $max $spread"
                    set labels "mean median sigma stDev MAD min max spread"
                } else {
                    if {$repeat(average)} {
                        if {$repeat(sigma)} {
                            set v "$mean $sigma"
                        } else {
                            set v $mean
                        }
                    }
                }
            }
        }

        if {$labeled} {
            if {$cavputform} {
                set iv "${iocvar}="
            } else {
                set iv "$iocvar "
            }
        } else {
            set iv ""
        }
        set result ""
        if {$statistics} {
            if {$stat(format) == "pretty"} {
                foreach vv $v {
                    if {([string is integer -strict $vv] == 0) && ([string is double -strict $vv] == 1)} {
                        append result [format %10.4f $vv]
                    } else {
                        if {$quotes} {
                            if {[llength $vv] >= 2} {
                                set vv "\"${vv}\""
                            }
                        }
                        if {[string is double -strict $vv]} {
                            lappend result ${vv}
                        } else {
                            set result ${vv}
                        }
                    }
                }
                lappend results "[format %20s $iocvar]$result"
            } elseif {$stat(format) == "tagvalue"} {
                foreach vv $v label $labels {
                    if {([string is integer -strict $vv] == 0) && ([string is double -strict $vv] == 1)} {
                        append result "$label [format $floatformat $vv] "
                    } else {
                        if {$quotes} {
                            if {[llength $vv] >= 2} {
                                set vv "\"${vv}\""
                            }
                        }
                        if {[string is double -strict $vv]} {
                            lappend result "$label ${vv}"
                        } else {
                            set result ${vv}
                        }
                    }
                }
                lappend results "${embrace(start)}${iv}${result}${embrace(end)}"                
            } elseif {$stat(format) == "sdds"} {
                set data(ColumnNames) "ControlName mean median sigma stDev MAD minimum maximum spread"
                set data(ColumnInfo.mean) "type SDDS_DOUBLE"
                set data(ColumnInfo.median) "type SDDS_DOUBLE"
                set data(ColumnInfo.sigma) "type SDDS_DOUBLE"
                set data(ColumnInfo.stDev) "type SDDS_DOUBLE"
                set data(ColumnInfo.MAD) "type SDDS_DOUBLE"
                set data(ColumnInfo.minimum) "type SDDS_DOUBLE"
                set data(ColumnInfo.maximum) "type SDDS_DOUBLE"
                set data(ColumnInfo.spread) "type SDDS_DOUBLE"
                lappend data(Column.ControlName) $iocvar
                foreach vv $v label "mean median sigma stDev MAD minimum maximum spread" {
                    if {$quotes} {
                        if {[llength $vv] >= 2} {
                            set vv "\"${vv}\""
                        }
                    }
                    if {[string is double -strict $vv]} {
                        lappend data(Column.$label) $vv
                    } else {
                        return -code error "Error, statistical analysis for PVs of non-numeric type is meaningless."
                    }
                }
                set data(Column.ControlName) [list $data(Column.ControlName)]
                set data(Column.mean) [list $data(Column.mean)]
                set data(Column.median) [list $data(Column.median)]
                set data(Column.sigma) [list $data(Column.sigma)]
                set data(Column.stDev) [list $data(Column.stDev)]
                set data(Column.MAD) [list $data(Column.MAD)]
                set data(Column.minimum) [list $data(Column.minimum)]
                set data(Column.maximum) [list $data(Column.maximum)]
                set data(Column.spread) [list $data(Column.spread)]
                sdds save $stat(file) data 
            }
        } else {
            foreach vv $v {
                if {([string is integer -strict $vv] == 0) && ([string is double -strict $vv] == 1)} {
                    lappend result [format $floatformat $vv]
                } else {
                    if {$quotes} {
                        if {[llength $vv] >= 2} {
                            set vv "\"${vv}\""
                        }
                    }
                    if {[string is double -strict $vv]} {
                        lappend result ${vv}
                    } else {
                        set result ${vv}
                    }
                }
            }
            lappend results ${embrace(start)}${iv}${result}${embrace(end)}
        }
        incr i
    }
    if {$fail} {
        if {$statistics} {
            return -code error [join $results \n]
        } else {
            return -code error [join $results $delimiter]
        }
    } else {
        if {$statistics} {
            return -code ok [join $results \n]
        } else {
            return -code ok [join $results $delimiter]
        }
    }
}

proc APScavputHelp {} {
    return {Usage: APScavput
[-pvName=<string>[=<value>][,<string>[=<value>]...]]
[-range=begin=<int>,end=<int>[,format=string][,interval=<int>]]
[-pendIoTime=<seconds>]
[-floatformat=<printfString>]
[-deltaMode[=factor=<value>]]
[-ramp=step=<n>,pause=<sec>]
[-numerical]
[-blunderAhead[=silently]]}
}

proc APScavput {args} {
    set timeout 10
    set options(number) 0
    set numerical 0
    set dryrun 0
    set deltamode 0
    set doramp 0
    set blunderAhead 0

    foreach option $args {
        if {[string compare [string index $option 0] "-"] == 0} {
            set i [string first "=" $option]
            if {$i == -1} {
                set options($options(number)) [string range $option 1 end]
                set options($options(number),number) 0
            } else {
                set options($options(number)) [string range $option 1 [expr {$i - 1}]]
                set j 0
                set options($options(number),number) $j
                set options($options(number),$j,sep) "="
                set option [string range $option [expr {$i + 1}] end]
                set i [string first "," $option]
                set ii [string first "=" $option]
                set sep ""
                if {($i == -1) || (($ii > -1) && ($ii < $i))} {
                    set i $ii
                    set sep "="
                } else {
                    set sep ","
                }
                while {$i != -1} {
                    set options($options(number),$j) [string range $option 0 [expr {$i - 1}]]
                    set option [string range $option [expr {$i + 1}] end]
                    incr j
                    set options($options(number),$j,sep) $sep
                    set options($options(number),number) $j
                    set i [string first "," $option]
                    set ii [string first "=" $option]
                    if {($i == -1) || (($ii > -1) && ($ii < $i))} {
                        set i $ii
                        set sep "="
                    } else {
                        set sep ","
                    }
                }
                set options($options(number),$j) $option
                incr j
                set options($options(number),number) $j
            }
            incr options(number)
        } else {
            puts stderr "Warning: Unknown option: $option"
        }
    }

    set listOption(number) 0
    for {set i 0} {$i < $options(number)} {incr i} {
        if {[string compare -nocase -length [string length $options($i)] $options($i) "list"] == 0} {
            set n 0
            set listOption($listOption(number),number) $n
            set nstart $n
            for {set j 0} {$j < $options($i,number)} {incr j} {
                if {($j > 0) && ($options($i,$j,sep) == "=")} {
                    for {set nn $nstart} {$nn < $n} {incr nn} {
                        set listOption($listOption(number),$nn,value) $options($i,$j)
                    }
                    set nstart $n
                } else {
                    set listOption($listOption(number),$n) $options($i,$j)
                    incr n
                    set listOption($listOption(number),number) $n
                }
            }
            incr listOption(number)
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "range"] == 0} {
            set range(begin) ""
            set range(end) ""
            set range(format) ""
            set range(interval) "1"
            for {set j 0 ; set jj 1} {$j < $options($i,number)} {incr j 2 ; incr jj 2} {
                if {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "begin"] == 0} {
                    set range(begin) $options($i,$jj)
                    if {![string is integer -strict $range(begin)]} {
                        set range(begin) 1
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "end"] == 0} {
                    set range(end) $options($i,$jj)
                    if {![string is integer -strict $range(end)]} {
                        set range(end) 1
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "format"] == 0} {
                    set range(format) $options($i,$jj)
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "interval"] == 0} {
                    set range(interval) $options($i,$jj)
                    if {![string is integer -strict $range(interval)]} {
                        set range(interval) 1
                    }
                }
            }
            if {([string length $range(begin)]) && ([string length $range(end)])} {
                if {($range(interval) > 0) && ($range(begin) > $range(end))} {
                    return -code error "APScavput: invalid -range syntax"
                }
                if {($range(interval) < 0) && ($range(begin) < $range(end))} {
                    return -code error "APScavput: invalid -range syntax"
                }
                if {($range(interval) == 0) && ($range(begin) != $range(end))} {
                    return -code error "APScavput: invalid -range syntax"
                }
                set n 0
                set listOption($listOption(number),number) $n
                for {set j $range(begin)} {$j <= $range(end)} {incr j $range(interval)} {
                    if {[string length $range(format)]} {
                        set listOption($listOption(number),$n) [format $range(format) $j]
                    } else {
                        set listOption($listOption(number),$n) $j
                    }
                    incr n
                    set listOption($listOption(number),number) $n
                }
                incr listOption(number)
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "pendiotime"] == 0} {
            if {[info exists options($i,0)]} {
                set timeout $options($i,0)
            } else {
                return -code error "APScavput: invalid -pendIoTime syntax"
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "numerical"] == 0} {
            set numerical 1
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "dryrun"] == 0} {
            set dryrun 1
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "blunderahead"] == 0} {
            set blunderAhead 1
            set blunder(silently) 0
            if {[info exists options($i,0)]} {
                if {[string compare -nocase -length [string length $options($i,0)] $options($i,0) "silently"] == 0} {
                    set blunder(silently) 1
                } else {
                    return -code error "APScavput: invalid -blunderAhead syntax"
                }
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "deltamode"] == 0} {
            set deltamode 1
            set delta(factor) 1
            if {[info exists options($i,0)]} {
                if {[string compare -nocase -length [string length $options($i,0)] $options($i,0) "factor"] == 0} {
                    if {[info exists options($i,1)]} {
                        set delta(factor) $options($i,1)
                    } else {
                        return -code error "APScavput: invalid -deltaMode factor syntax"
                    }
                } else {
                    return -code error "APScavput: invalid -deltaMode syntax"
                }
            }
        } elseif {[string compare -nocase -length [string length $options($i)] $options($i) "ramp"] == 0} {
            set doramp 1
            set ramp(steps) 1
            set ramp(pause) .1
            for {set j 0 ; set jj 1} {$j < $options($i,number)} {incr j 2 ; incr jj 2} {
                if {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "step"] == 0} {
                    set ramp(steps) $options($i,$jj)
                    if {(![string is integer -strict $ramp(steps)]) || ($ramp(steps) <= 0)} {
                        set ramp(steps) 1
                    }
                } elseif {[string compare -nocase -length [string length $options($i,$j)] $options($i,$j) "pause"] == 0} {
                    set ramp(pause) $options($i,$jj)
                    if {![string is double -strict $ramp(pause)]} {
                        set ramp(pause) .1
                    }
                }
            }
        }
    }
    
    set num $listOption(0,number)
    for {set i 0} {$i < $listOption(0,number)} {incr i} {
        set listVar($i) $listOption(0,$i)
        set listVar($i,tmp) $listVar($i)
        if {[info exists listOption(0,$i,value)]} {
            set listVar($i,value) $listOption(0,$i,value)
            set listVar($i,tmp,value) $listVar($i,value)
        } else {
            set listVar($i,value) 0
            set listVar($i,tmp,value) $listVar($i,value)
        }
    }
    for {set n 1} {$n < $listOption(number)} {incr n} {
        for {set i 0 ; set k 0} {$i < $num} {incr i} {
            for {set j 0} {$j < $listOption($n,number)} {incr j ; incr k} {
                set listVar($k) "$listVar($i,tmp)$listOption($n,$j)"
                if {[info exists listOption($n,$j,value)]} {
                    set listVar($k,value) $listOption($n,$j,value)
                } else {
                    set listVar($k,value) $listVar($i,tmp,value) 
                }
                
            }
        }
        for {set i 0 ; set k 0} {$i < $num} {incr i} {
            for {set j 0} {$j < $listOption($n,number)} {incr j ; incr k} {
                set listVar($k,tmp) $listVar($k)
                set listVar($k,tmp,value) $listVar($k,value)
            }
        }
        set num [expr {$num * $listOption($n,number)}]        
    }

    set preValue 0
    for {set i [expr {$num - 1}]} {$i >= 0} {incr i -1} {
        if {[info exists listVar($i,value)]} {
            set preValue $listVar($i,value)
        } else {
            set listVar($i,value) $preValue
        }
    }
    for {set i 0} {$i < $num} {incr i} {
        append tclVars "apscavgetput($listVar($i)) "
        lappend tclVarValues "$listVar($i,value)"
        append iocVars "$listVar($i) "
    }

    if {$dryrun} {
        set results ""
        foreach iocvar $iocVars tclvarvalue $tclVarValues {
            lappend results "[format "%32s" $iocvar] $tclvarvalue"
        }
        return [join $results \n]
    }

    #Find out if the pv command has been switched to error mode
    if {[catch {pv} result]} {
        set pverrormode 1
    } else {
        set pverrormode 0
    }

    eval global apscavgetput

    #Do not worry about relinking PVs. 
    #The pv command knows if they have already been linked.
    if {$pverrormode} {
        if {[catch {pv link $tclVars $iocVars $timeout} result]} {
            return -code error "APScavput: $result"
        }
    } else {
        if {[pv link $tclVars $iocVars $timeout] == 1} {
            global errorCode
            return -code error "APScavput: $errorCode"
        }
    }

    set endtime [expr {[clock clicks -milliseconds] + $timeout * 1000.0}]
    set unconnected 0
    global apscavputtmpvar
    while {[clock clicks -milliseconds] < $endtime} {
        set unconnected 0
        update
        foreach tclvar $tclVars {
            if {$pverrormode} {
                if {[catch {pv stat $tclvar apscavputtmpvar} result]} {
                    return -code error "APScavput: $result"
                }
            } else {
                if {[pv stat $tclvar apscavputtmpvar] == 1} {
                    global errorCode
                    return -code error "APScavput: $errorCode"
                }
            }
            if {($apscavputtmpvar(state) != "OK") && ($apscavputtmpvar(state) != "WR")} {
                set unconnected 1
                break
            }
        }
        if {$unconnected == 0} {
            break
        }
    }

    if {$unconnected} {
        set pvErrorList ""
        set errorVars ""
        foreach tclvar $tclVars iocvar $iocVars {
            if {$pverrormode} {
                if {[catch {pv stat $tclvar apscavputtmpvar} result]} {
                    return -code error "APScavput: $result"
                }
            } else {
                if {[pv stat $tclvar apscavputtmpvar] == 1} {
                    global errorCode
                    return -code error "APScavput: $errorCode"
                }
            }
            if {($apscavputtmpvar(state) != "OK") && ($apscavputtmpvar(state) != "WR")} {
                append pvErrorList "$iocvar "
                append errorVars "1 "
                set $tclvar 0
            } else {
                append errorVars "0 "
            }
        }
        if {$blunderAhead} {
            if {!$blunder(silently)} {
                puts stderr "Warning: Unable to connect to PVs: $pvErrorList"
            }
        } else {
            if {([string length $pvErrorList])} {
                return -code error "Error: Unable to connect to PVs: $pvErrorList"
            }
        }
    } else {
        foreach tclvar $tclVars {
            append errorVars "0 "
        }
    }
    unset apscavputtmpvar

    if {($deltamode) || ($doramp)} {
        if {$pverrormode} {
            if {[catch {pv getw $tclVars $timeout} result]} {
                return -code error "APScavput: $result"
            }
        } else {
            if {[pv getw $tclVars $timeout] == 1} {
                global errorCode
                return -code error "APScavput: $errorCode"
            }
        }
        foreach tclvar $tclVars iocvar $iocVars errorvar $errorVars {
            if {![string is double -strict [set $tclvar]]} {
                return -code error "APScavput: value ([set $tclvar]) for $iocvar is not numerical--can't use -deltaMode or -ramp"
            }
        }
    }

    if {$deltamode} {
        set tmp ""
        foreach tclvar $tclVars tclvarvalue $tclVarValues {
            lappend tmp [expr {[set $tclvar] + $delta(factor) * $tclvarvalue}]
        }
        set tclVarValues $tmp
    }

    set tmp ""
    foreach tclvar $tclVars iocvar $iocVars {
        if {$pverrormode} {
            if {[catch {pv info $tclvar access} access]} {
                return -code error "APScavput: $choices"
            }
        } else {
            set access [pv info $tclvar access]
        }
        if {[lindex [lindex $access 0] 1] != "RW"} {
            lappend tmp $iocvar
        }
    }
    if {[string length $tmp]} {
        if {!$blunderAhead} {
            return -code error "Error: read only access for [join $tmp ,]"
        } else {
            if {!$blunder(silently)} {
                puts stderr "Warning: read only access for [join $tmp ,]"
            }
        }
    }


    if {$doramp} {
        set tclVarsOrig ""
        foreach tclvar $tclVars {
            lappend tclVarsOrig [set $tclvar]
        }
        for {set i 1} {$i <= $ramp(steps)} {incr i} {
            foreach tclvar $tclVars tclvarorig $tclVarsOrig iocvar $iocVars tclvarvalue $tclVarValues {
                set rampDelta [expr {($tclvarvalue - $tclvarorig) / ($ramp(steps) * 1.0)}]
                set $tclvar [expr {$tclvarorig + $i * $rampDelta}]
            }
            if {$pverrormode} {
                if {[catch {pv putq $tclVars $timeout} result]} {
                    return -code error "APScavput: $result"
                }
            } else {
                if {[pv putq $tclVars $timeout] == 1} {
                    global errorCode
                    return -code error "APScavput: $errorCode"
                }
            }
            if {$i < $ramp(steps)} {
                after [expr {int($ramp(pause) * 1000)}]
            }
        }
    } else {
        foreach tclvar $tclVars iocvar $iocVars tclvarvalue $tclVarValues {
            set $tclvar $tclvarvalue
        }
        if {$pverrormode} {
            if {[catch {pv putq $tclVars $timeout} result]} {
                return -code error "APScavput: $result"
            }
        } else {
            if {[pv putq $tclVars $timeout] == 1} {
                global errorCode
                return -code error "APScavput: $errorCode"
            }
        }
    }
}

#computes mean, rms, stDev, MAD, max, min, spread, and median
proc APSComputeMoments {args} {
    set valueList ""
    APSStrictParseArguments {valueList} 
    
    set size [llength $valueList]
    if !$size {
        return "mean 0 rms 0 stDev 0 sigma 0 min 0 max 0 spread 0 MAD 0 median 0"
    }
    set valueList [lsort -real $valueList]
    set max [lindex $valueList end]
    set min [lindex $valueList 0]
    set spread [expr $max-$min]
    set n [expr round($size/2.0)]
    set median [lindex $valueList [expr $n-1]]
    set sum 0.0
    set sumSqr 0.0
    for {set i 0} {$i<$size} {incr i} {
        set val [lindex $valueList $i]
        set sum [expr $sum + $val]
        set sumSqr [expr $sumSqr + $val*$val]
    }
    set mean [expr double($sum) / $size]
    set rms [expr sqrt(double($sumSqr)/$size)]
    set sum2 0.0
    set sum 0.0
    for {set i 0} {$i<$size} {incr i} {
        set val [lindex $valueList $i]
        set val1 [expr $val - $mean]
        set sum2 [expr $sum2 + $val1*$val1]
        set sum [expr $sum + abs($val)]
    }
    if ($size>1) {
        set stDev [expr sqrt($sum2/($size-1))]
    } else {
        set stDev 0.0
    }
    set sigma [expr $stDev/sqrt($size)]
    set MAD [expr $sum/$size]
    return "mean $mean rms $rms stDev $stDev sigma $sigma min $min max $max spread $spread MAD $MAD median $median"
}
