set apsSRExcitationFileDirectory /home/helios/oagData/sr/magnets
set apsBTSExcitationFileDirectory /home/helios/oagData/bts/magnets

proc APSLatticeToSetpointsQuadSextHelp {} {
    return "Usage: APSLatticeToSetpointsQuadSext\n-input <paramFile>\n-output <setpointFile>\n\[-includeQuadrupoles {0|1}(1)\]\n\[-includeSextupoles {0|1}(1)\]\n\[-energyGeV <value>(6.0)\]\n\nConverts elegant parameter file for quadrupoles and sextupoles to a file that can be restored to power supplies using sddscasr (ControlName, ValueString).\n"
}

proc APSLatticeToSetpointsQuadSext {args} {
    global apsSRExcitationFileDirectory 
    set input ""
    set output ""
    set includeQuadrupoles 1
    set includeSextupoles  1
    set energyGeV 6.0
    if [APSStrictParseArguments {input output includeQuadrupoles includeSextupoles energyGeV}] {
        return -code error "APSLatticeToSetpointsQuadSext: Invalid arguments"
    }
    if ![string length $input] {
        return -code error "APSLatticeToSetpointsQuadSext: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSLatticeToSetpointsQuadSext: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSLatticeToSetpointsQuadSext: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSLatticeToSetpointsQuadSext: in use: $output"
    }

    set typeList {QUAD SEXT}
    set BnLList {B1L B2L}
    set KnList {K1 K2}
    set steererList {Q7 S1}
    set summaryFileList {QuadSummary.sdds SextSummary.sdds}
    set includeList [list $includeQuadrupoles $includeSextupoles]
    set resultList ""

    exec sddsconvert $input $input.bare -delete=param,* -retain=column,ElementName,ElementType,ElementParameter,ParameterValue
    APSAddToTmpFileList -ID APSLatticeToSetpointsQuadSext -fileList $input.bare
    
    set typesDone 0
    foreach type $typeList BnL $BnLList Kn $KnList summaryFile $summaryFileList include $includeList steerer $steererList {
        if !$include continue
        #puts stderr "Working on $type $summaryFile"
        if [catch {exec sddsprocess $input.bare -pipe=out -match=col,ElementType=*${type} -nowarning \
                       | sdds2stream -pipe -rows=bare} count] {
            return -code error "APSLatticeToSetpointsQuadSext (1): $count"
        }
        if $count==0 continue
        #puts stderr "$type $count"
        
        # Get the fractional strength errors
        APSAddToTmpFileList -ID APSLatticeToSetpointsQuadSext -fileList $input.FSE
        if [catch {exec sddsprocess $input.bare $input.FSE -match=col,ElementType=*${type} \
                       -match=col,ElementParameter=FSE -nowarning} result] {
            return -code error "APSLatticeToSetpointsQuadSext (2): $result"
        }
        #puts stderr [exec sddsprintout -col $input.FSE]

        # Get the Kn values
        if [catch {exec sddsprocess $input.bare -pipe=out -match=col,ElementType=*${type} -match=col,ElementParameter=${Kn} -nowarning \
                       = Combine Kn values with the lengths and FSEs = \
                       | sddsxref $input.FSE -pipe -take=ParameterValue -rename=col,ParameterValue=FSE -nowarning -fillin \
                       | sddsprocess -pipe -edit=column,MagnetTypeName,ElementName,Z: -nowarning \
                       | sddsxref ${apsSRExcitationFileDirectory}/$summaryFile -pipe -reuse=row \
                       -match=MagnetTypeName -take=Length,KnSign \
                       | sddsprocess -pipe \
                       "-define=parameter,E,$energyGeV,units=GeV" \
                       "-define=parameter,H,E 1e9 * c_mks /,units=T*m" \
                       "-define=col,Kn,ParameterValue,units=SI" \
                       "-define=col,KnLDesired,Kn Length * 1 FSE + *,units=SI" \
                       "-define=col,BnLDesired,KnLDesired H * KnSign *,units=SI" \
                       -print=column,Filename,${apsSRExcitationFileDirectory}/%sMain/%s.excit,MagnetTypeName,ElementName \
                       -print=column,ControlName,%s:PS:SetCurrentC,ElementName \
                       | sddsinterpset -pipe -belowRange=extrapolate -aboveRange=extrapolate \
                       -order=1 -data=fileColumn=Filename,interpolate=Current,functionOf=${BnL},column=BnLDesired \
                       | sddsprocess -pipe=in $input.${type} -nowarning \
                       "-print=col,ValueString,%21.15le,Current"} result] {
            return -code error "APSLatticeToSetpointsQuadSext (3): $result"
        }
        APSAddToTmpFileList -ID APSLatticeToSetpointsQuadSext -fileList $input.$type
        file delete $input.FSE
        lappend resultList $input.${type}

        # Steering terms
        foreach plane [list H V] {
            eval set ctype [os editstring di/$plane/ $steerer]
            set dataList [exec sddsprocess $apsSRExcitationFileDirectory/correctorLimits.sdds -pipe=out -match=col,CorrectorType=$ctype \
                              | sdds2stream -pipe -column=CurrentLimit,DeflectionLimit]
            set IMax [lindex $dataList 0]
            set KMax [expr [lindex $dataList 1]*6.0/$energyGeV]
            if [catch {exec sddsprocess $input.bare -pipe=out -nowarning \
                           -match=col,ElementName=*$steerer -match=col,ElementParameter=${plane}KICK \
                           -edit=col,ControlName,ElementName,%/:S1/:${plane}1/%/:Q7/:${plane}7/ei/:PS:SetCurrentC/ \
                           "-define=col,Current,ParameterValue $KMax /  $IMax *,units=A" \
                           -print=col,ValueString,%21.15e,Current \
                           | tee $input.$type.$plane \
                           | sdds2stream -rows=bare -pipe } result] {
                return -code error "APSLatticeToSetpointsQuadSext (4): $result"
            }
            if [expr $result>0] {
                lappend resultList $input.$type.$plane
            } else {
                file delete -force $input.$type.$plane
            }
        }
        
        incr typesDone 1
    }
    
    if { $typesDone==0 } {
        # Not necessarily an error
        APSDeleteTmpFileList -ID APSLatticeToSetpointsQuadSext 
       return $typesDone
    }
    eval exec sddscombine $resultList -merge $output -retain=col,ControlName,ValueString,Current,Element*,Parameter*
    eval file delete $resultList
    APSDeleteTmpFileList -ID APSLatticeToSetpointsQuadSext
    return $typesDone
}

proc APSSetpointsToLatticeQuadSextHelp {} {
    return "Usage: APSSetpointsToLatticeQuadSext\n-input <snapshot>\n-output <paramFile>\n\[-energyGeV <value>(6.0)\]\n\nConverts snapshot (ControlName, ValueString) of quadrupole and sextupole currents to an elegant parameter file.\n"
}

proc APSSetpointsToLatticeQuadSext {args} {
    global apsSRExcitationFileDirectory
    set input ""
    set output ""
    set energyGeV 6.0
    if [APSStrictParseArguments {input output energyGeV}] {
        return -code error "APSSetpointsToLatticeQuadSext: Invalid arguments"
    }
    
    if ![string length $input] {
        return -code error "APSSetpointsToLatticeQuadSext: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSSetpointsToLatticeQuadSext: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSSetpointsToLatticeQuadSext: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSSetpointsToLatticeQuadSext: in use: $output"
    }

    set typeList {KQUAD KSEXT}
    set wildcardList {S??\[AB\]:Q\[12367\] S??\[AB\]:S\[123\]}
    set steererList {Q7 S1}
    set summaryFileList {QuadSummary.sdds SextSummary.sdds}
    set multList {1 2}

    set resultList ""
    
    foreach type $typeList wildcard $wildcardList mult $multList summaryFile $summaryFileList steerer $steererList {
        #APSSetVarAndUpdate status "APSSetpointsToLatticeQuadSext $type"
        # Need only the PV name and value
        APSAddToTmpFileList -ID APSSetpointsToLatticeQuadSext -fileList $output.$type.tmp1
        exec sddsconvert $input -pipe=out -retain=col,ControlName,ValueString \
            = Select only the PV names corresponding to the type of magnets = \
            | sddsprocess -pipe=in $output.$type.tmp1  "-match=col,ControlName=${wildcard}:PS:SetCurrentC" -nowarning \
            = Create the element name (used in the lattice) and scan the current = \
            -edit=column,ElementName,ControlName,2S/:/100D -scan=column,Current,ValueString,%le \
            "-edit=col,MagnetTypeName,ElementName,Z:" \
            = Create the name of the excitation file = \
            -print=column,Filename,${apsSRExcitationFileDirectory}/%sMain/%s.excit,MagnetTypeName,ElementName 
            
        if [catch {exec sdds2stream -rows=bare $output.$type.tmp1} count] {
            return -code error "APSSetpointsToLatticeQuadSext: $count"
        }
        if { $count != 0} {
            #APSSetVarAndUpdate status "APSSetpointsToLatticeQuadSext $type $count"
            # Get length, sign for each element 
            APSAddToTmpFileList -ID APSSetpointsToLatticeQuadSext -fileList $output.${type}
            exec sddsxref $output.$type.tmp1 ${apsSRExcitationFileDirectory}/$summaryFile -pipe=out -reuse=row -nowarning \
                -match=MagnetTypeName -take=Length,KnSign \
                | sddsinterpset -pipe -belowRange=extrapolate -aboveRange=extrapolate \
                = Interpolate all the files at the current values found in the input file = \
                -order=1 -data=fileColumn=Filename,interpolate=B${mult}L,functionOf=Current,column=Current \
                | sddsconvert -pipe -rename=col,B${mult}L=BnL \
                | sddsprocess -pipe=in $output.${type} -nowarning \
                = Convert BnL to Kn = \
                "-redefine=parameter,E,$energyGeV,units=GeV" \
                "-redefine=parameter,H,E 1e9 * c_mks /,units=T*m" \
                "-redefine=col,Bn,BnL KnSign * Length /,units=SI" \
                "-redefine=col,n,${mult},type=short" \
                "-redefine=col,ParameterValue,Bn H /,units=SI" \
                "-reprint=col,ElementParameter,K${mult}" \
                -reprint=col,ElementType,$type
            
            # Also set the FSE values to 0 in case these are set to something else in the lattice
            APSAddToTmpFileList -ID APSSetpointsToLatticeQuadSext -fileList $output.${type}.FSE
            exec sddsconvert $output.${type} -pipe=out -retain=col,ElementName \
                | sddsprocess -pipe=in $output.${type}.FSE -nowarning \
                -reprint=col,ElementParameter,FSE -redefine=col,ParameterValue,0 \
                -reprint=col,ElementType,$type
            
            lappend resultList $output.$type $output.$type.FSE
        }

        foreach plane {H V} {
            # Convert steering trims
            eval set ctype [os editstring di/$plane/ $steerer]
            set dataList [exec sddsprocess $apsSRExcitationFileDirectory/correctorLimits.sdds -pipe=out -match=col,CorrectorType=$ctype \
                              | sdds2stream -pipe -column=CurrentLimit,DeflectionLimit]
            set IMax [lindex $dataList 0]
            set KMax [expr [lindex $dataList 1]*6.0/$energyGeV]
            APSAddToTmpFileList -ID APSSetpointsToLatticeQuadSext -fileList $output.${type}.$plane
            if [catch {exec sddsprocess $input $output.$type.$plane -nowarning -match=col,ControlName=S???:$ctype:PS:SetCurrentC \
                           -edit=column,ElementName,ControlName,2S/:/100Da%/:${plane}1/:S1/%/:${plane}7/:Q7/ \
                           -reprint=column,ElementParameter,${plane}KICK \
                           -scan=column,Current,ValueString,%le \
                           "-redefine=column,ParameterValue,Current $IMax / $KMax *" \
                           -reprint=column,ElementType,$type} result] {
                return -code error "APSSetpointsToLatticeQuadSext (4): $result"
            }
            lappend resultList $output.$type.$plane
        }
        file delete $output.$type.tmp1
    }
    #APSSetVarAndUpdate status "$resultList"
    if [llength $resultList]==0 {
        APSDeleteTmpFileList -ID APSSetpointsToLatticeQuadSext
        return 0
    }
    eval exec sddscombine $resultList -pipe=out -merge -retain=col,Element*,Parameter* \
        | sddsprocess -pipe=in $output "-redefine=col,ElementOccurence,1,type=long"
    
    APSDeleteTmpFileList -ID APSSetpointsToLatticeQuadSext
    
    return 1
}

proc APSLatticeToSetpointsTGDHelp {} {
    return "Usage: APSLatticeToSetpointsQuadTGD\n-input <paramFile>\n-output <setpointFile>\n-type {Q4|Q5|Q8|M3|M4}\n\[-energyGeV <value>(6.0)\]\n\nConverts elegant parameter file for a family of transverse-gradient dipoles to a file that can be restored to power supplies using sddscasr (ControlName, ValueString).\n"
}

proc APSLatticeToSetpointsTGD {args} {
    global apsSRExcitationFileDirectory
    set input ""
    set output ""
    set type ""
    set energyGeV 6.0
    if [APSStrictParseArguments {input output type energyGeV}] {
        return -code error "APSLatticeToSetpointsTGD: Invalid arguments"
    }    

    if ![string length $input] {
        return -code error "APSLatticeToSetpointsTGD: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSLatticeToSetpointsTGD: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSLatticeToSetpointsTGD: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSLatticeToSetpointsTGD: in use: $output"
    }
    
    set knownTypeList [list Q4 Q5 Q8 M3 M4]
    if ![string length $type] {
        return -code error "APSLatticeToSetpointsTGD: provide type name, one of [join $knownTypeList ,]"
    }
    if {[lsearch -exact $knownTypeList $type]==-1} {
        return -code error "APSLatticeToSetpointsTGD: unknown magnet type: $type"
    }

    # Collect relevant data into a file with one line per magnet, ensuring that the order of
    # the magnets is the same as in the excitation file that we are going to interpolate
    if [catch {exec sddsprocess $input -pipe=out -match=col,ElementType=C?BEND -match=col,ElementName=*:$type -nowarning \
                   | sdds2stream -pipe -rows=bare} count] {
        return -code error "APSLatticeToSetpointsTGD (0): $count"
    }
    if {$count==0} {
        return 0
    }

    APSAddToTmpFileList -ID APSLatticeToSetpointsTGD -fileList $input.trans
    
    if [catch {exec sddsprocess $input -pipe=out -match=col,ElementType=C?BEND -match=col,ElementName=*:$type -nowarning \
                   = Keep only the fields we need = \
                   | sddsconvert -pipe -retain=col,ElementName,ElementParameter,ParameterValue \
                   = Sort by ElementName, subsort by ElementParameter to prepare for transposition = \
                   | sddssort -pipe -col=ElementName -col=ElementParameter \
                   = Each page needs to pertain to a single element = \
                   | sddsbreak -pipe -change=ElementName \
                   = Need this so we can get the element name back after transposition = \
                   | sddsprocess -pipe -process=ElementName,first,%s0 -nowarning \
                   = Transpose data to make a column corresponding to each ElementParameter = \
                   = E.g., K1, ANGLE, L, ... = \
                   | sddstranspose -pipe -newcol=ElementParameter \
                   = The next four lines produce a table with a column for ElementName as well as desired element parameters = \
                   | sddsconvert -pipe -retain=parameter,ElementName0 -rename=param,ElementName0=ElementName \
                   | sddsexpand -pipe \
                   | sddscollapse -pipe \
                   | sddsconvert -pipe -retain=col,ElementName,ANGLE,K1,K2,L,FSE*,*KICK \
                   = Convert to B0L and B1L values, form name for excitation file = \
                   | sddsprocess -pipe=in $input.trans  -nowarning \
                   "-define=param,H,$energyGeV 1e9 * c_mks /,units=T*m" \
                   = B0L or BigB0 is always positive in the measurement files while B1L or BigB1 is always negative = \
                   "-define=col,BigB0,ANGLE XKICK + H * 1 FSE + FSE_DIPOLE + * abs,units=T*m" \
                   "-define=col,BigB1,K1 H * L * 1 FSE + FSE_QUADRUPOLE + * abs chs,units=T" \
                   "-print=col,GridFilename,${apsSRExcitationFileDirectory}/${type}Main/%s.excit,ElementName"} result] {
        return -code error "APSLatticeToSetpointsTGD (1): $result"
    }

    # Collect all the excitation files for the magnets in question (note that there may not be 40 or 80 magnets
    # in the caller's request, so we can't do this ahead of time). This ensures that the excitation files are
    # in the same order as the lines in the .trans file
    set fileList [exec sdds2stream -column=GridFilename $input.trans]
    APSAddToTmpFileList -ID APSLatticeToSetpointsTGD -fileList $input.excit
    if [catch {eval exec sddscombine [join $fileList  " "] -overwrite $input.excit} result] {
        return -code error "APSLatticeToSetpointsTGD (2): $result"
    }
    
    # Perform grid search and interpolation for each magnet
    APSAddToTmpFileList -ID APSLatticeToSetpointsTGD -fileList $input.interp
    if [catch {exec sddsfindin2dgrid $input.excit -pipe=out \
                   -gridVariables=MainCurrent,TrimCurrent -presorted \
                   -findLocationOf=BigB0,BigB1 -valuesFile=$input.trans -interpolate -mode=onePairPerPage \
                   | sddsxref -pipe=in $input.trans $input.interp -take=*} result] {
        return -code error "APSLatticeToSetpointsTGD (3): $result"
    }        
    
    # Convert to snapshot form
    # - Main current
    APSAddToTmpFileList -ID APSLatticeToSetpointsTGD -fileList [list $input.interp.main $input.interp.trim]
    if [catch {exec sddsprocess $input.interp -pipe=out \
                   -print=col,ControlName,%s:PS:SetCurrentC,ElementName \
                   -print=col,ValueString,%21.15e,MainCurrent \
                   | sddsconvert -pipe=in $input.interp.main \
                   -retain=col,MainCurrent,ElementName,MagnetQrId,ValueString,ControlName \
                   -rename=col,MainCurrent=Current} result] {
        return -code error "APSLatticeToSetpointsTGD (4): $result"
    }
    
    # - Trim current
    if [catch {exec sddsprocess $input.interp -pipe=out \
                   -print=col,ControlName,%sT:PS:SetCurrentC,ElementName \
                   -print=col,ValueString,%21.15e,TrimCurrent \
                   | sddsconvert -pipe=in $input.interp.trim \
                   -retain=col,TrimCurrent,ElementName,MagnetQrId,ValueString,ControlName \
                   -rename=col,TrimCurrent=Current} result] {
        return -code error "APSLatticeToSetpointsTGD (5): $result"
    }        
    
    # - Combined
    if [catch {exec sddscombine $input.interp.main $input.interp.trim -pipe=out -merge \
                   | sddssort -pipe=in $output -column=ElementName} result] {
        return -code error "APSLatticeToSetpointsTGD (6): $result"
    }
    
    APSDeleteTmpFileList -ID APSLatticeToSetpointsTGD
    return 1
}

proc APSSetpointsToLatticeTGDHelp {} {
    return "Usage: APSSetpointsToLatticeTGD\n-input <snapshot>\n-output <paramFile>\n-type {Q4|Q5|Q8|M3|M4}\n\[-energyGeV <value>(6.0)\]\n\nConverts snapshot (ControlName, ValueString) of quadrupole and sextupole currents to an elegant parameter file.\n"
}

proc APSSetpointsToLatticeTGD {args} {
    global apsSRExcitationFileDirectory
    set input ""
    set output ""
    set type ""
    set energyGeV 6.0
    if [APSStrictParseArguments {input output type energyGeV}] {
        return -code error "APSSetpointsToLatticeTGD: Invalid arguments"
    }    

    if ![string length $input] {
        return -code error "APSSetpointsToLatticeTGD: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSSetpointsToLatticeTGD: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSSetpointsToLatticeTGD: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSSetpointsToLatticeTGD: in use: $output"
    }
    
    set knownTypeList [list Q4 Q5 Q8 M3 M4]
    set elementTypeList [list CCBEND CCBEND CSBEND CSBEND CSBEND]
    if ![string length $type] {
        return -code error "APSSetpointsToLatticeTGD: provide type name, one of [join $knownTypeList ,]"
    }
    if {[set index [lsearch -exact $knownTypeList $type]]==-1} {
        return -code error "APSSetpointsToLatticeTGD: unknown magnet type: $type"
    }

    if [catch {exec sddsprocess $input -pipe=out -match=col,ControlName=S???:${type}:PS:SetCurrentC -nowarning \
                   | sdds2stream -pipe -rows=bare} count] {
        return -code error "APSSetpointsToLatticeTGD: $count"
    }
    if {$count==0} {
        return 0
    }

    APSAddToTmpFileList -ID APSSetpointsToLatticeTGD \
        -fileList [list $input.$type.tmp1 $input.$type.tmp2 $input.$type.samplePoints $input.$type.excit $input.$type.values $input.$type.values.B0 \
                      $input.$type.values.FSE $input.$type.values.XKICK $input.$type.values.B1 $input.$type.values.FSEQ]
    
    # Extract main current from the file
    exec sddsconvert $input -pipe=out -retain=col,ControlName,ValueString \
      = Prepare the PV name and value for the main supply = \
      | sddsprocess -pipe=in $input.$type.tmp1 -nowarning \
      "-match=col,ControlName=S???:${type}:PS:SetCurrentC" \
      -edit=column,ElementName,ControlName,2S/:/100D \
      -scan=column,MainCurrent,ValueString,%le
    
    # Extract trim current from the file
    exec sddsconvert $input -pipe=out -retain=col,ControlName,ValueString \
        = Prepare the PV name and value for the trim supply = \
        | sddsprocess -pipe=in $input.$type.tmp2 -nowarning \
        "-match=col,ControlName=S???:${type}T:PS:SetCurrentC" \
        -edit=column,ElementName,ControlName,%/T:PS/:PS/a2S/:/100D \
        -scan=column,TrimCurrent,ValueString,%le
    
    # Combine main and trim setpoint pairs, one per page
    exec sddsxref $input.$type.tmp1 $input.$type.tmp2  -match=ElementName -pipe=out -take=TrimCurrent -nowarning \
        | sddsconvert -pipe -retain=col,TrimCurrent,MainCurrent,ElementName \
        | sddsprocess -pipe -nowarning \
        "-print=col,GridFilename,${apsSRExcitationFileDirectory}/${type}Main/%s.excit,ElementName" \
        | sddssort -pipe=in -column=ElementName $input.$type.samplePoints
    
    file delete $input.$type.tmp1 $input.$type.tmp2
    
    # Collect all the excitation files for the magnets in question (note that there may not be 40 or 80 magnets
    # in the caller's request, so we can't do this ahead of time). This ensures that the excitation files are
    # in the same order as the lines in the .trans file
    set fileList [exec sdds2stream -column=GridFilename $input.$type.samplePoints]
    eval exec sddscombine [join $fileList  " "] -overwrite $input.$type.excit

    # Perform 2d interpolation of the grid data for magnets at the PS setpoints
    exec sddsfindin2dgrid $input.$type.excit -pipe=out \
      = MainCurrent, TrimCurrent are the grid variables and are previously sorted for speed = \
      -gridVariables=MainCurrent,TrimCurrent -presorted \
      = BigB0, BigB1 are functions of MainCurrent, TrimCurrent = \
      -findLocationOf=BigB0,BigB1 \
      = Instead of searching/interpolating for MainCurrent, TrimCurrent that give desired values of = \
      = BigB0, BigB1, we just want to interpolate the grid at specified MainCurrent, TrimCurrent pairs = \
      -inverse -interpolate \
      = There is one sample point in the .samplePoints file per page in the .exit file = \
      -valuesFile=$input.$type.samplePoints -mode=onePairPerPage \
      | sddsxref -pipe=in $input.$type.samplePoints $input.$type.values -take=ElementName
    
    # Convert the B0L deviation from nominal into a dipole fractional strength error
    # Note that B0L = BigB0 is always positive in the measurement files
    exec sddsprocess $input.$type.values -pipe=out \
        "-define=parameter,H,$energyGeV 1e9 * c_mks /,units=T*m" \
        -edit=col,MagnetTypeName,ElementName,Z: \
        | sddsxref ${apsSRExcitationFileDirectory}/BendSummary.sdds -pipe \
        -match=MagnetTypeName -take=Angle,AngleSign -rename=col,Angle=AngleNominal -reuse=row \
        | sddsprocess -pipe=in $input.$type.values.B0 \
        -print=column,ElementParameter,FSE_DIPOLE \
        "-define=column,ParameterValue,BigB0 H / AngleNominal / abs 1 -" \
        -print=column,ElementType,[lindex $elementTypeList $index]

    # Define FSE as 0
    exec sddsprocess $input.$type.values.B0  $input.$type.values.FSE \
        -reprint=col,ElementParameter,FSE -redefine=col,ParameterValue,0
    # Define XKICK as 0
    exec sddsprocess $input.$type.values.B0  $input.$type.values.XKICK \
        -reprint=col,ElementParameter,XKICK -redefine=col,ParameterValue,0
    
    # Convert the B1L value into a K1 value using the effective length
    # Note that B1L = BigB1 is always negative in the measurement files
    exec sddsprocess $input.$type.values -pipe=out \
        "-define=parameter,H,$energyGeV 1e9 * c_mks /,units=T*m" \
        -edit=col,MagnetTypeName,ElementName,Z: \
        | sddsxref ${apsSRExcitationFileDirectory}/BendSummary.sdds -pipe \
        -match=MagnetTypeName -take=LEffective,K1Sign -edit=column,B1*,ei/Nominal/ -reuse=row \
        | sddsprocess -pipe=in $input.$type.values.B1 \
        -print=column,ElementParameter,K1 \
        "-define=column,ParameterValue,BigB1 abs H / LEffective / K1Sign *" \
        -print=column,ElementType,[lindex $elementTypeList $index]

    # Define FSE_QUADRUPOLE as 0
    exec sddsprocess $input.$type.values.B1  $input.$type.values.FSEQ \
        -reprint=col,ElementParameter,FSE_QUADRUPOLE -redefine=col,ParameterValue,0

    # Combine 
    exec sddscombine $input.$type.values.B0 $input.$type.values.FSE $input.$type.values.XKICK $input.$type.values.B1 $input.$type.values.FSEQ \
        -merge -pipe=out -retain=col,Element*,Parameter* \
        | sddsprocess -pipe=in $output "-redefine=col,ElementOccurence,1,type=long" 
    
    # Clean up
    APSDeleteTmpFileList -ID APSSetpointsToLatticeTGD
    
    return 1
}

proc APSLatticeToSetpointsHelp {} {
    return "Usage: APSLatticeToSetpoints \n-input <paramFile>\n-output <setpointFile>\n\[-energyGeV <value>(6.0)\]\n\nConverts elegant parameter file to a file that can be restored to power supplies using sddscasr (ControlName, ValueString).\n"
}

proc APSLatticeToSetpoints {args} {
    set input ""
    set output ""
    set energyGeV 6.0
    if [APSStrictParseArguments {input output energyGeV}] {
        return -code error "APSLatticeToSetpoints: Invalid arguments"
    }
    
    if ![string length $input] {
        return -code error "APSLatticeToSetpoints: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSLatticeToSetpoints: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSLatticeToSetpoints: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSLatticeToSetpoints: in use: $output"
    }
    file delete $output.qs
    if [catch  {APSLatticeToSetpointsQuadSext -input $input -output $output.qs -energyGeV $energyGeV} result] {
        return -code error "APSLatticeToSetpoints (1): $result"
    }
    if [file exists $output.qs] {
        set fileList $output.qs
        APSAddToTmpFileList -ID APSLatticeToSetpoints -fileList $output.qs
    }
    foreach bend {Q4 Q5 Q8 M3 M4} {
        file delete $output.$bend
        APSAddToTmpFileList -ID APSLatticeToSetpoints -fileList $output.$bend
        if [catch {APSLatticeToSetpointsTGD -input $input -output $output.$bend -type $bend} result] {
            return -code error "APSLatticeToSetpoints (2): $result"
        }
        if {$result != 0} {
            lappend fileList $output.$bend
        }
    }
    APSAddToTmpFileList -ID APSLatticeToSetpoints -fileList $output.fhv
    if [catch {APSLatticeToSetpointsFastCorrectors -input $input -output $output.fhv} result] {
        return -code error "APSLatticeToSetpoints (2.1): $result"
    }
    if {$result != 0} {
        lappend fileList $output.fhv
    }
    if [llength $fileList]==0 {
        APSDeleteTmpFileList -ID APSLatticeToSetpoints
        return -code error "APSLatticeToSetpoints: nothing was selected it seems"
    }
    if [catch {eval exec sddscombine $fileList -merge -overwrite -retain=column,ControlName,ValueString $output} result] {
        return -code error "APSLatticeToSetpoints (3): $result"
    }
    APSDeleteTmpFileList -ID APSLatticeToSetpoints
}

proc APSSetpointsToLatticeHelp {} {
    return "Usage: APSSetpointsToLattice\n-input <snapshot>\n-output <paramFile>\n\[-energyGeV <value>(6.0)\]\n\nConverts snapshot (ControlName, ValueString) of magnet currents to an elegant parameter file.\n"
}

proc APSSetpointsToLattice {args} {
    set input ""
    set output ""
    set energyGeV 6.0
    if [APSStrictParseArguments {input output energyGeV}] {
        return -code error "APSSetpointsToLattice: Invalid arguments"
    }
    
    if ![string length $input] {
        return -code error "APSSetpointsToLattice: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSSetpointsToLattice: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSSetpointsToLattice: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSSetpointsToLattice: in use: $output"
    }
    file delete $output.qs
    if [catch {APSSetpointsToLatticeQuadSext -input $input -output $output.qs -energyGeV $energyGeV} result] {
        return -code error "APSSetpointsToLattice (1): $result"
    }
    set fileList ""
    if [file exists $output.qs] {
        set fileList $output.qs
        APSAddToTempFileList -ID APSSetpointsToLattice -fileList $output.qs
    }
    APSAddToTmpFileList -ID APSSetpointsToLattice -fileList $output.fc
    if [catch {APSSetpointsToLatticeFastCorrectors -input $input -output $output.fc} result] {
        return -code error "APSSetpointsToLattice (1.5): $result"
    }
    if {$result != 0} {
        lappend fileList $output.fc
    }
    foreach bend {Q4 Q5 Q8 M3 M4} {
        file delete $output.$bend
        APSAddToTmpFileList -ID APSSetpointsToLattice -fileList $output.$bend
        if [catch {APSSetpointsToLatticeTGD -input $input -output $output.$bend -type $bend} result] {
            return -code error "APSSetpointsToLattice (2): $result"
        }
        if {$result != 0} {
            lappend fileList $output.$bend
        }
    }
    if ![llength $fileList] {
        APSDeleteTmpFileList -ID APSSetpointsToLattice
        return -code error "APSSetpointsToLattice: nothing selected to convert"
    }
    if [catch {eval exec sddscombine $fileList -merge -overwrite $output} result] {
        APSDeleteTmpFileList -ID APSSetpointsToLattice
        return -code error "APSSetpointsToLattice: $result"
    }
    eval file delete $fileList
    APSDeleteTmpFileList -ID APSSetpointsToLattice
}

proc APSUAdjustMagnetSetpoints {args} {
    # Modified from APS-era APsMpAdjustMagnetSetpoints
    # Not used presently, may not work.
    set newSnapFile ""
    set referenceSnapFile ""
    set referenceSCRFile ""
    set adjustmentModel none
    set outputFileDir .
    set outputFileRoot ""
    set statusCallback ""
    set abortVariable ""

    if [APSStrictParseArguments {newSnapFile referenceSnapFile \
                                   referenceSCRFile adjustmentModel \
                                   outputFileDir outputFileRoot \
                                   statusCallback abortVariable}] {
        return -code error "APSUAdjustMagnetSetpoints: Invalid arguments."
    }

    if {$newSnapFile=="" || $referenceSnapFile=="" || \
          $referenceSCRFile=="" || $adjustmentModel=="" || \
          $outputFileDir=="" || $outputFileRoot=="" } {
        return -code error "APSUAdjustMagnetSetpoints: Invalid arguments."
    }
    if ![string compare $adjustmentModel none] {
        return
    }
    if [string length $statusCallback] {
        eval $statusCallback {"Processing $newSnapFile data with files $referenceSnapFile and $referenceSCRFile..."}
    }
    if ![file exists $newSnapFile] {
        return -code error "APSUAdjustMagnetSetpoints: Cannot find file $newSnapFile."
    }
    if ![file exists $referenceSnapFile] {
        return -code error "APSUAdjustMagnetSetpoints: Cannot find  file $referenceSnapFile."
    }
    if ![file exists $referenceSCRFile] {
        return -code error "APSUAdjustMagnetSetpoints: Cannot find  file $referenceSCRFile."
    }
    set tmproot /tmp/[APSTmpString]
    if {$adjustmentModel == "none"} {
        return
    }
    # Keep only magnet settings from SCR file, i.e. those control names
    # that are also contained in the reference snapshot file.
    if [catch {exec sddsselect $referenceSCRFile $referenceSnapFile -pipe=out \
                 -match=ControlName \
                 | sddsprocess -pipe=in ${tmproot}.SCR \
                 -scan=col,SCRSetpoint,ValueString,%lf \
             } result ] {
        return -code error "APSUAdjustMagnetSetpoints(0): $result"
    }
    if [catch {exec sddsprocess $referenceSnapFile ${tmproot}.ref \
                 -scan=col,RefSetpoint,ValueString,%lf \
             } result ] {
        return -code error "APSUAdjustMagnetSetpoints(1): $result"
    }
    switch $adjustmentModel {
        differenceInI {
            set operator1 -
            set operator2 +
        }
        ratioInI {
            set operator1 /
            set operator2 *
        }
    }
    # only adjust quadrupoles
    if [catch {exec sddsprocess $newSnapFile -pipe=out \
                 -scan=col,NewSetpoint,ValueString,%lf \
                 | sddsxref -pipe ${tmproot}.SCR \
                 -match=ControlName -take=SCRSetpoint \
                 | sddsxref -pipe ${tmproot}.ref \
                 -match=ControlName -take=RefSetpoint \
                 | sddsprocess -pipe \
                 "-def=col,IChange,NewSetpoint RefSetpoint $operator1,units=A" \
                 "-def=col,AdjustedSetPoint,SCRSetpoint IChange $operator2,units=A,description=I setpoint after doing $adjustmentModel adjustment model" \
                 | tee $outputFileDir/${outputFileRoot}.all.snp \
                 | sddsprocess -pipe=in $outputFileDir/${outputFileRoot}.Q.snp  \
                 -reprint=col,ValueString,%lf,AdjustedSetPoint \
             } result ] {
        return -code error "APSUAdjustMagnetSetpoints(2): $result"
    }
    if [catch {exec sddsprocess $outputFileDir/${outputFileRoot}.all.snp \
                 $outputFileDir/${outputFileRoot}.S.snp \
                 -match=col,ControlName=*:S* -noWarning \
             } result ] {
        return -code error "APSUAdjustMagnetSetpoints(2): $result"
    }
    if [catch {exec sddscombine $outputFileDir/${outputFileRoot}.Q.snp \
                 $outputFileDir/${outputFileRoot}.S.snp \
                 $outputFileDir/${outputFileRoot}.snp \
                 -merge -overWrite \
             } result ] {
        return -code error "APSUAdjustMagnetSetpoints(2): $result"
    }
    file delete -force $outputFileDir/${outputFileRoot}.Q.snp \
      $outputFileDir/${outputFileRoot}.S.snp

    if [string length $statusCallback] {
        eval $statusCallback "Done."
    }
    return
}

proc APSUAdjustParameterFile {args} {
    # Modified from APS-era APSMpAdjustParameterFile
    # Not used presently, may not work.
    set referenceSCRFile ""
    set xrefFileList ""
    set energy 7
    set refParamFile ""
    set paramFile ""
    set BRhoPVName ""
    set outputParamFile ""
    set statusCallback ""
    if [APSStrictParseArguments {referenceSCRFile xrefFileList energy \
                                   refParamFile paramFile BRhoPVName \
                                   outputParamFile statusCallback}] {
        return -code error "APSUAdjustParameterFile: Invalid arguments."
    }
    if {$referenceSCRFile=="" || $xrefFileList=="" || $refParamFile=="" || \
          $paramFile=="" || $outputParamFile=="" } {
        return -code error "APSUAdjustParameterFile(1): Invalid arguments."
    }
    # convert I_ref to K1_ref, but only for quadrupoles.
    set tmproot [APSTmpString]
    set foundQxrefFile 0
    foreach xref $xrefFileList {
        #determine type of file from name
        if [regexp Q [file tail $xref]] {
            set foundQxrefFile 1
            if [catch {APSUMagnetKnLValues -SCRFile $referenceSCRFile \
                         -xrefFileList $xref \
                         -energy $energy  -BRhoPVName $BRhoPVName \
                         -outputFileDir /tmp \
                         -outputFileRoot ${tmproot} \
                         -statusCallback $statusCallback \
                     } result ] {
                return -code error "APSUAdjustParameterFile(2): $result"
            }
        }
    }
    # if there was no xref file with quadrupoles, then there
    # is no adjustments for this procedure to do.
    if !$foundQxrefFile {
        file copy -force $paramFile $outputParamFile
        if [string length $statusCallback] {
            eval $statusCallback {"Done. No change in parameter file needed."}
        }
        return  
    }
    # determine sum of K1(I_ref) + (K1 - K1_ref) for quads
    # file             column name
    # $tmpfile.KnL     Kn, which is K1(I_ref) above
    # $refParamFile    K1Reference
    # $paramFile       K1
    # Filter the parameter files for the quadrupole K1 ParameterName
    if [string length $statusCallback] {
        eval $statusCallback {"Calculating new K1's..."}
    }
    # note that the skew quads are inadvertedly included here, as
    # there is no easy way to filter them out.
    if [catch {exec sddsprocess $refParamFile /tmp/$tmproot.refK1 \
                 -match=col,ElementParameter=K1,ElementType=*QUAD*,& \
             } result ] {
        return -code error "APSUAdjustParameterFile(3): $result"
    }
    if [catch {exec sddsprocess $paramFile /tmp/$tmproot.K1 \
                 -match=col,ElementParameter=K1,ElementType=*QUAD*,& \
             } result ] {
        return -code error "APSUAdjustParameterFile(4): $result"
    }
    if [catch {exec sddsprocess $paramFile /tmp/$tmproot.other \
                 -match=col,ElementParameter=K1,ElementType=*QUAD*,&,! \
             } result ] {
        return -code error "APSUAdjustParameterFile(5): $result"
    }
    # Something to keep in mind: Since the KnL file /tmp/$tmproot.KnL 
    # doesn't include skew quads the final parameter file 
    # will not have skew quad K1's.
    if [catch {exec sddsxref /tmp/$tmproot.KnL /tmp/$tmproot.refK1  -pipe=out \
                   -match=ElementName -take=ParameterValue \
                 -rename=col,ParameterValue=K1Reference \
                 | sddsxref -pipe /tmp/$tmproot.K1 \
                 -match=ElementName -take=ParameterValue \
                 -rename=col,ParameterValue=K1 \
                 | sddsprocess -pipe=in /tmp/$tmproot.newK1 \
                 "-redef=col,ParameterValue,Kn K1 + K1Reference -" \
             } result ] {
        return -code error "APSUAdjustParameterFile(6): $result"
    }
    if [catch {exec sddscombine /tmp/$tmproot.newK1 /tmp/$tmproot.other \
                 $outputParamFile -overWrite -merge \
             } result ] {
        return -code error "APSUAdjustParameterFile(7): $result"
    }
    if [string length $statusCallback] {
        eval $statusCallback "Done."
    }
}

proc APSLatticeToSetpointsDeltaK1Help {} {
    return "Usage: APSLatticeToSetpointsDeltaK1\n-input <paramFile>\n-output <snapshotFile>\n\[-energyGeV <value>(6.0)\]\n-referenceParameters <paramFile>\n-referenceSnapshot <snapshotFile>\n\[-referenceEnergyGeV <value>(6.0)\]\n\nUses the reference parameters, snapshot, and energy to infer the effective magnetic fields in the magnets, then computes required new currents to give the required changes in K1 values between the input and reference parameters."
}
                                      
proc APSLatticeToSetpointsDeltaK1 {args} {
    set input ""
    set output ""
    set energyGeV 6.0
    set referenceParameters ""
    set referenceSnapshot ""
    set referenceEnergyGeV 6.0
    if [APSStrictParseArguments {input output energyGeV referenceParameters referenceSnapshot referenceEnergyGeV}] {
        return -code error "APSLatticeToSetpointsDeltaK1: Invalid arguments"
    }    

    if ![string length $input] {
        return -code error "APSLatticeToSetpointsDeltaK1: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSLatticeToSetpointsDeltaK1: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSLatticeToSetpointsDeltaK1: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSLatticeToSetpointsDeltaK1: in use: $output"
    }
    if ![string length $referenceParameters] {
        return -code error "APSLatticeToSetpointsDeltaK1: provide reference parameter filename"
    }
    if ![string length $referenceSnapshot] {
        return -code error "APSLatticeToSetpointsDeltaK1: provide reference snapshot filename"
    }
    if ![file exists $referenceParameters] {
        return -code error "APSLatticeToSetpointsDeltaK1: not found: $referenceParameters"
    }
    if ![file exists $referenceSnapshot] {
        return -code error "APSLatticeToSetpointsDeltaK1: not found: $referenceSnapshot"
    }
    
    set tmpRoot /tmp/[APSTmpString]
    APSAddToTmpFileList -ID APSLatticeToSetpointsDeltaK1 \
        -fileList [list $tmpRoot.K1Iref $tmpRoot.K1Eff $tmpRoot.others $tmpRoot.both $tmpRoot.K1Iref~ $tmpRoot.refparam]
    
    # Interpolate the reference currents to get nominal K1 values
    APSSetpointsToLattice -input $referenceSnapshot -output $tmpRoot.K1Iref -energyGeV $referenceEnergyGeV
    exec sddsprocess $tmpRoot.K1Iref -match=col,ElementParameter=K1 -nowarning
    
    exec sddsprocess $referenceParameters $tmpRoot.refparam -nowarning \
        "-match=col,ElementName=*M1*,ElementName=*M2*,|,ElementName=*SQ?,|,ElementName=*S?,|,!,ElementParameter=K1,&" 

    # Check to see if the K1 values are specified as differentials
    set nRows [exec sdds2stream -rows=bare $input]
    set nDiff [exec sddsprocess $input -pipe=out -nowarning -match=col,ParameterMode=differential | sdds2stream -rows=bare -pipe=in]
    if [expr $nDiff==$nRows] {
        # Compute new K1 by offseting from K1(Iref)
        exec sddsprocess $input -pipe=out -nowarning \
          "-match=col,ElementName=*M1*,ElementName=*M2*,|,ElementName=*SQ?,|,ElementName=*S?,|,!,ElementParameter=K1,&" \
          | sddsconvert -pipe -rename=col,ParameterValue=dK1New \
          | sddsxref $tmpRoot.refparam -pipe -match=ElementName -take=ParameterValue -nowarning \
          -rename=col,ParameterValue=K1Ref \
          | sddsxref -pipe $tmpRoot.K1Iref -match=ElementName -take=ParameterValue -rename=col,ParameterValue=K1AtIRef -nowarning \
          | sddsprocess -pipe=in $tmpRoot.K1Eff \
          "-redefine=col,ParameterValue,K1AtIRef dK1New +" -nowarning
    } elseif {$nDiff==0} {
        # Compute change in K1 from reference to new lattice
        # Then compute K1 value by offsetting from the K1(Iref) values
        exec sddsprocess $input -pipe=out -nowarning \
          "-match=col,ElementName=*M1*,ElementName=*M2*,|,ElementName=*SQ?,|,ElementName=*S?,|,!,ElementParameter=K1,&" \
          | sddsconvert -pipe -rename=col,ParameterValue=K1New \
          | sddsxref $tmpRoot.refparam -pipe -match=ElementName -take=ParameterValue -nowarning \
          -rename=col,ParameterValue=K1Ref \
          | sddsxref -pipe $tmpRoot.K1Iref -match=ElementName -take=ParameterValue -rename=col,ParameterValue=K1AtIRef -nowarning \
          | sddsprocess -pipe=in $tmpRoot.K1Eff \
          "-redefine=col,ParameterValue,K1AtIRef K1New K1Ref - +" -nowarning
    } else {
        return -code error "APSLatticeToSetpointsDeltaK1: input file contains a mix of ParameterMode=differential and other modes"
    }

    exec sddsprocess $input $tmpRoot.others -nowarning \
        "-match=col,ElementName=*M1*,ElementName=*M2*,|,ElementName=*SQ?,|,ElementName=*S?,|,!,ElementParameter=K1,&,!" \

    exec sddscombine $tmpRoot.others $tmpRoot.K1Eff $tmpRoot.both -merge 
    
    APSLatticeToSetpoints -input $tmpRoot.both -output $output -energyGeV $energyGeV
    APSDeleteTmpFileList -ID APSLatticeToSetpointsDeltaK1
}

proc APSDeltaK1ToSetpointsHelp {} {
    return "Usage: APSDeltaK1ToSetpoints\n-deltaK1 <value>\n-ElementName <string>\n\[-output <filename>\]\n\[-differential 1\]\n\[-energyGeV <value>(6)\]Computes the current required for a named magnet to change the K1 value by the given amount. If differential=1, returns the change in current not the actual value.\n"
}

proc APSDeltaK1ToSetpoints {args} {
    global OAGGlobal
    set deltaK1 0.0
    set ElementName ""
    set output ""
    set differential 0
    set energyGeV 6.0
    if [catch {APSStrictParseArguments {ElementName deltaK1 output energyGeV differential}} result] {
        return -code error "APSDeltaK1ToSetpoints: $result"
    }
    if ![string length $ElementName] {
        return -code error "APSDeltaK1ToSetpoints: ElementName must not be blank"
    }
    eval set ElementGroup [os editstring Z: $ElementName]
    switch -glob $ElementName {
        *:Q\[45\] {
            set ElementType CCBEND
            set CNList $ElementName:PS:SetCurrentC,${ElementName}T:PS:SetCurrentC
        }
        *:Q8 -
        *:M\[34\] {
            set ElementType CSBEND
            set CNList $ElementName:PS:SetCurrentC,${ElementName}T:PS:SetCurrentC
        }
        *:Q? {
            set ElementType KQUAD
            set CNList $ElementName:PS:SetCurrentC
        }
        *:S? {
            return -code error "APSDeltaK1ToSetpoints: element $ElementName doesn't have K1 value"
        }
        default {
            return -code error "APSDeltaK1ToSetpoints: couldn't identify element type of $ElementName"
        }
    }
    set tmpRoot /tmp/[APSTmpString]
    APSAddToTmpFileList -ID APSDeltaK1ToSetpoints \
        -fileList [list $tmpRoot.req $tmpRoot.snap $tmpRoot.param $tmpRoot.param0 $tmpRoot.param2 $tmpRoot.snap2]

    # Find present setpoints
    if [catch {exec sddsmakedataset $tmpRoot.req -column=ControlName,type=string -data=[join $CNList ,]} result] {
        return -code error "APSDeltaK1ToSetpoints: $result"
    }
    if [catch {exec sddscasr -save  $tmpRoot.req $tmpRoot.snap -pendIoTime=10} result] {
        return -code error "APSDeltaK1ToSetpoints: $result"
    }

    # Convert to K1 etc.
    if [string compare $ElementType KQUAD]==0 {
        if [catch {APSSetpointsToLatticeQuadSext -input $tmpRoot.snap -output $tmpRoot.param -energyGeV $energyGeV} result] {
            return -code error "APSDeltaK1ToSetpoints: $result"
        }
    } else {
        if [catch {APSSetpointsToLatticeTGD -input $tmpRoot.snap -output $tmpRoot.param0 -type $ElementGroup -energyGeV $energyGeV} result] {
            return -code error "APSDeltaK1ToSetpoints: $result"
        }
        # Need a few extras from the reference lattice
        if ![file exists [set ref $OAGGlobal(SRLatticesDirectory)/default/Basic.param]] {
            if ![file exists [set ref $OAGGlobal(SRLatticesDirectory)/default/aps.param]] {
                return -code error "APSDeltaK1ToSetpoints: reference lattice not found"
            }
        }
        if [catch {exec sddsprocess $ref $tmpRoot.extra \
                       -match=col,ElementName=$ElementName \
                       -match=col,ElementParameter=ANGLE,ElementParameter=L,|
            exec sddscombine $tmpRoot.param0  $tmpRoot.extra -merge $tmpRoot.param} result] {
            return -code error "APSDeltaK1ToSetpoints: $result"
        }
    }

    # Change the K1 value only
    if [catch {exec sddsprocess $tmpRoot.param $tmpRoot.param2 \
                   "-redefine=col,ParameterValue,ElementParameter \"K1\" streq ? $deltaK1 : 0 \$ ParameterValue +" } result] {
        return -code error "APSDeltaK1ToSetpoints: $result"
    }

    # Convert back to setpoints
    if [string compare $ElementType KQUAD]==0 {
        if [catch {APSLatticeToSetpointsQuadSext -input $tmpRoot.param2 -output $tmpRoot.snap2 -energyGeV $energyGeV} result] {
            return -code error "APSDeltaK1ToSetpoints: $result"
        }
    } else {
        if [catch {APSLatticeToSetpointsTGD -input $tmpRoot.param2 -output $tmpRoot.snap2 -type $ElementGroup -energyGeV $energyGeV} result] {
            return -code error "APSDeltaK1ToSetpoints: $result"
        }
    }

    if $differential {
        # Convert to delta setpoints
        if [catch {exec sddsxref $tmpRoot.snap2 $tmpRoot.snap -pipe=out -match=ControlName -take=ValueString -rename=col,ValueString=ValueStringRef \
                       | sddsprocess -pipe -scan=column,Value,ValueString,%le -scan=column,ValueRef,ValueStringRef,%le \
                       "-define=col,DeltaValue,Value ValueRef -" \
                       -reprint=column,ValueString,%le,DeltaValue \
                       | sddsconvert -pipe=in $tmpRoot.snap3 -delete=col,Value,ValueRef*,DeltaValue
            file copy -force $tmpRoot.snap3 $tmpRoot.snap2} result] {
            return -code error "APSDeltaK1ToSetpoints: $result"
        }
    }
    
    if [catch {sdds load $tmpRoot.snap2 data} result] {
        return -code error "APSDeltaK1ToSetpoints: $result"
    }

    foreach controlName [lindex $data(Column.ControlName) 0] value [lindex $data(Column.ValueString) 0] {
        lappend returnList $controlName=$value
    }
    if [llength $output] {
        file copy -force $tmpRoot.snap2 $output
    }
    APSDeleteTmpFileList -ID APSDeltaK1ToSetpoints
    return $returnList
}

proc BTSLatticeToSetpointsHelp {} {
    return "Usage: BTSLatticeToSetpoints\n-input <paramFile>\n-output <snapshotFile>\n\[-includeQuadrupoles {0|1}(1)\]\n\[-includeDipoles {0|1}(1)\]\n\[-energyGeV <value>(6.0)\]\n\nConverts an elegant parameter file to setpoints for quadrupoles and/or dipoles in the BTS.\n"
}

proc BTSLatticeToSetpoints {args} {
    APSSetVarAndUpdate status "BTSLatticeToSetpoints $args"
    global apsBTSExcitationFileDirectory 
    set input ""
    set output ""
    set includeQuadrupoles 1
    set includeDipoles 1
    set energyGeV 6.0
    if [APSStrictParseArguments {input output includeQuadrupoles includeDipoles energyGeV}] {
        return -code error "BTSLatticeToSetpoints: Invalid arguments"
    }    
    
    if ![string length $input] {
        return -code error "BTSLatticeToSetpoints: provide input filename"
    }
    if ![string length $output] {
        return -code error "BTSLatticeToSetpoints: provide output filename"
    }
    if ![file exists $input] {
        return -code error "BTSLatticeToSetpoints: not found: $input"
    }
    if [file exists $output] {
        return -code error "BTSLatticeToSetpoints: in use: $output"
    }

    set typeList {BEN QUAD}
    set BnLList {B0L B1L}
    set parameterList {ANGLE K1}
    set factorNameList {1 Length}
    set signNameList {AngleSign KnSign}
    set summaryFileList {BendSummary.sdds QuadSummary.sdds}
    set includeList [list $includeDipoles $includeQuadrupoles]
    set resultList ""

    APSAddToTmpFileList -ID BTSLatticeToSetpoints -fileList $input.bare
    # Select just the columns we need. Select only BTS elements (BTS:AB2 is covered by BTS:AB1).
    exec sddsconvert $input -pipe=out -delete=param,* -retain=column,ElementName,ElementType,ElementParameter,ParameterValue \
        | sddsprocess -pipe=in $input.bare -match=col,ElementName=BTS* -match=col,ElementName=BTS:AB2*,! 
    
    set typesDone 0
    foreach type $typeList BnL $BnLList parameter $parameterList factorName $factorNameList signName $signNameList summaryFile $summaryFileList include $includeList {
        if !$include continue
        if [catch {exec sddsprocess $input.bare -pipe=out -match=col,ElementType=*${type} -nowarning \
                       | sdds2stream -pipe -rows=bare} count] {
            return -code error "BTSLatticeToSetpoints (1): $count"
        }
        if $count==0 continue
        #puts stderr "$type $count"
        
        # Get the fractional strength errors
        APSAddToTmpFileList -ID BTSLatticeToSetpoints -fileList $input.FSE
        if [catch {exec sddsprocess $input.bare $input.FSE -match=col,ElementType=*${type} \
                       -match=col,ElementParameter=FSE -nowarning} result] {
            return -code error "BTSLatticeToSetpoints (2): $result"
        }
        #puts stderr [exec sddsprintout -col $input.FSE]

        # Get or compute the Kn values
        
        if [catch {exec sddsprocess $input.bare -pipe=out -match=col,ElementType=*${type} -match=col,ElementParameter=${parameter} \
                     -nowarning \
                     = Combine Kn values with the lengths and FSEs = \
                     | sddsxref $input.FSE -pipe -take=ParameterValue -rename=col,ParameterValue=FSE \
                     | sddsxref ${apsBTSExcitationFileDirectory}/$summaryFile -pipe -reuse=row \
                     -match=ElementName -take=*Sign,Length,PSName \
                     | sddsprocess -pipe \
                     "-define=parameter,E,$energyGeV,units=GeV" \
                     "-define=parameter,H,E 1e9 * c_mks /,units=T*m" \
                     "-define=col,StrengthParameter,ParameterValue,units=SI" \
                     "-define=col,KnLDesired,StrengthParameter $factorName * 1 FSE + *,units=SI" \
                     "-define=col,BnLDesired,KnLDesired H * $signName *,units=SI" \
                     -print=column,Filename,${apsBTSExcitationFileDirectory}/%s.excit,ElementName \
                     -print=column,ControlName,%s:PS:SetCurrentC,PSName \
                     -reedit=column,ControlName,%/BTS:AB:PS:SetCurrentC/BTS:AB:CurrentAO/ \
                     | sddsinterpset -pipe  -belowRange=extrapolate -aboveRange=extrapolate \
                     -order=1 -data=fileColumn=Filename,interpolate=Current,functionOf=${BnL},column=BnLDesired \
                     | sddssort -pipe -column=PSName \
                     | sddsbreak -pipe -change=PSName \
                     | sddsprocess -pipe -process=ControlName,first,%s -process=Current,ave,%s \
                     | sddscollapse -pipe \
                     | sddsprocess -pipe=in $input.${type} -nowarning \
                     "-print=col,ValueString,%21.15le,Current"} result] {
            return -code error "BTSLatticeToSetpoints (3): $result"
        }
        APSAddToTmpFileList -ID BTSLatticeToSetpoints -fileList $input.$type
        
        file delete $input.FSE
        lappend resultList $input.${type}
        incr typesDone 1
    }
    
    if { $typesDone==0 } {
        # Not necessarily an error
        APSDeleteTmpFileList -ID BTSLatticeToSetpoints 
       return $typesDone
    }
    eval exec sddscombine $resultList -merge $output -retain=col,ControlName,ValueString,Current,Element*,Parameter*
    APSDeleteTmpFileList -ID BTSLatticeToSetpoints
    return $typesDone
}

proc BTSSetpointsToLatticeHelp {} {
    return "Usage: BTSSetpointsToLattice\n-input <snapshotFile>\n-output <paramFile>\n\[-energyGeV <value>(6.0)\]\n\nConverts a snapshot file (ControlName, ValueString) to an elegant parameter file for the BTS.\n"
}

proc BTSSetpointsToLattice {args} {
    global apsBTSExcitationFileDirectory
    set input ""
    set output ""
    set energyGeV 6.0
    if [APSStrictParseArguments {input output energyGeV}] {
        return -code error "BTSSetpointsToLattice: Invalid arguments"
    }
    
    if ![string length $input] {
        return -code error "BTSSetpointsToLattice: provide input filename"
    }
    if ![string length $output] {
        return -code error "BTSSetpointsToLattice: provide output filename"
    }
    if ![file exists $input] {
        return -code error "BTSSetpointsToLattice: not found: $input"
    }
    if [file exists $output] {
        return -code error "BTSSetpointsToLattice: in use: $output"
    }

    set typeList {KQUAD CSBEN}
    set wildcardList {BTS:*Q? BTS:[ABC]B*} 
    set summaryFileList {QuadSummary.sdds BendSummary.sdds}
    set multList {1 0}

    set resultList ""
    
    foreach type $typeList wildcard $wildcardList mult $multList summaryFile $summaryFileList {
        #BTSSetVarAndUpdate status "BTSSetpointsToLattice $type"
        # Need only the PV name and value
        APSAddToTmpFileList -ID BTSSetpointsToLattice -fileList $output.$type.tmp1
        exec sddsconvert $input -pipe=out -retain=col,ControlName,ValueString \
          = Select only the PV names corresponding to the type of magnets = \
          | sddsprocess -pipe=in $output.$type.tmp1  -nowarning \
          "-match=col,ControlName=${wildcard}:PS:SetCurrentC,ControlName=${wildcard}:CurrentAO,|" \
          = Create the power supply name and scan the current = \
          -edit=column,PSName,ControlName,2S/:/100D \
          -scan=column,Current,ValueString,%le
        
        if [catch {exec sdds2stream -rows=bare $output.$type.tmp1} count] {
            return -code error "BTSSetpointsToLattice: $count"
        }

        if { $count != 0} {
            #BTSSetVarAndUpdate status "BTSSetpointsToLattice $type $count"
            # Get length, sign for each element 
            APSAddToTmpFileList -ID BTSSetpointsToLattice -fileList [list $output.${type}.tmp2 $output.${type}]
            if [catch {exec sddsxref ${apsBTSExcitationFileDirectory}/$summaryFile $output.$type.tmp1 -pipe=out -reuse=row -nowarning \
                           -match=PSName -take=Current \
                           | sddsprocess -pipe \
                           -print=col,Filename,${apsBTSExcitationFileDirectory}/%s.excit,ElementName \
                           | sddsinterpset -pipe=in $output.${type}.tmp2  -belowRange=extrapolate -aboveRange=extrapolate \
                           = Interpolate all the files at the current values found in the input file = \
                           -order=1 -data=fileColumn=Filename,interpolate=B${mult}L,functionOf=Current,column=Current} result] {
                return -code error "BTSSetpointsToLattice: $result"
            }

            if $mult==0 {
                # Dipoles
                exec sddsprocess $output.${type}.tmp2 $output.${type} -nowarning \
                    = Convert BnL to angle, then to FSE = \
                    "-redefine=parameter,E,$energyGeV,units=GeV" \
                    "-redefine=parameter,H,E 1e9 * c_mks /,units=T*m" \
                    "-redefine=col,ParameterValue,B0L H / AngleSign * Angle / 1 -" \
                    "-reprint=col,ElementParameter,FSE" \
                    -reprint=col,ElementType,$type
                lappend resultList $output.$type
            } else {
                # Quadrupoles
                exec sddsprocess $output.${type}.tmp2 $output.${type} -nowarning \
                    = Convert BnL to Kn = \
                    "-redefine=parameter,E,$energyGeV,units=GeV" \
                    "-redefine=parameter,H,E 1e9 * c_mks /,units=T*m" \
                    "-redefine=col,Bn,B1L KnSign * Length /,units=SI" \
                    "-redefine=col,ParameterValue,Bn H /,units=SI" \
                    "-reprint=col,ElementParameter,K${mult}" \
                    -reprint=col,ElementType,$type
                # Also set the FSE values to 0 in case these are set to something else in the lattice
                APSAddToTmpFileList -ID BTSSetpointsToLattice -fileList $output.${type}.FSE
                exec sddsconvert $output.${type} -pipe=out -retain=col,ElementName \
                    | sddsprocess -pipe=in $output.${type}.FSE -nowarning \
                    -reprint=col,ElementParameter,FSE -redefine=col,ParameterValue,0 \
                    -reprint=col,ElementType,$type
                lappend resultList $output.$type $output.$type.FSE
            }

            
        }
        file delete $output.$type.tmp1
    }
    #BTSSetVarAndUpdate status "$resultList"
    if [llength $resultList]==0 {
        APSDeleteTmpFileList -ID BTSSetpointsToLattice
        return 0
    }
    eval exec sddscombine $resultList -pipe=out -merge -retain=col,Element*,Parameter* \
        | sddsprocess -pipe=in $output "-redefine=col,ElementOccurence,1,type=long"
    
    APSDeleteTmpFileList -ID BTSSetpointsToLattice
    
    return 1
}

proc APSDeltaK1FileToSetpointsHelp {} {
    return "Usage: APSDeltaK1FileToSetpoints\n-input <deltaParamFile>\n-output <snapshotFile>\n\[-differential 1\]\n\[-energyGeV <value>(6.0)\]\n\nTakes a differential parameter file (ElementName, ElementParameter, ParameterValue) and converts it to a snapshot file (ControlName, ValueString) using the present state as the starting point. If differential=1, the ValueString is the change in current rather than full value."
}

# Compute new setpoints that reflect desired offset in K1's compared to present setpoints

proc APSDeltaK1FileToSetpoints {args} {
    global OAGGlobal
    set input ""
    set output ""
    set energyGeV 6.0
    set differential 0
    set snapshotFile ""
    if [APSStrictParseArguments {input output energyGeV differential snapshotFile}] {
        return -code error "APSDeltaK1FileToSetpoints: Invalid arguments"
    }    
    
    if ![string length $input] {
        return -code error "APSDeltaK1FileToSetpoints: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSDeltaK1FileToSetpoints: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSDeltaK1FileToSetpoints: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSDeltaK1FileToSetpoints: in use: $output"
    }

    set tmpRoot /tmp/[APSTmpString]

    APSAddToTmpFileList -ID APSDeltaK1FileToSetpoints -fileList \
        "$tmpRoot.req1 $tmpRoot.req2 $tmpRoot.refSnap $tmpRoot.refParam $tmpRoot.changedParam $tmpRoot.savedParam $tmpRoot.extra $tmpRoot.newParam"

    # Get the present setpoints
    # - Main supplies
    if [catch {exec sddsprocess $input $tmpRoot.req1 -reprint=col,ControlName,%s:PS:SetCurrentC,ElementName} result] {
        return -code error "APSDeltaK1FileToSetpoints (1): $result"
    }

    # - Trim supplies (if any)
    if [catch {exec sddsprocess $input $tmpRoot.req2 -nowarning -match=col,ElementName=*Q\[458\],ElementName=*M\[34\],| \
                   -reprint=col,ControlName,%sT:PS:SetCurrentC,ElementName} result] {
        return -code error "APSDeltaK1FileToSetpoints (2): $result"
    }
    set nTGDs [exec sdds2stream -rows=bare $tmpRoot.req2]

    if ![string length $snapshotFile] {
    # - Combined
    if [catch {exec sddscombine $tmpRoot.req1 $tmpRoot.req2 -merge -pipe=out -retain=col,ControlName \
        | sddscasr -save -pipe=in $tmpRoot.refSnap} result] {
        return -code error "APSDeltaK1FileToSetpoints (3): $result"
    }
    } else {
	exec sddscombine $tmpRoot.req1 $tmpRoot.req2 $tmpRoot.fullReq -merge -retain=col,ControlName
	exec sddsselect $snapshotFile $tmpRoot.fullReq -pipe=out -match=ControlName \
	    | sddsconvert -pipe=in $tmpRoot.refSnap -retain=col,ControlName,ValueString
	file delete $tmpRoot.fullReq
    }
    
    # Convert present setpoints the present parameter values
    if [catch {APSSetpointsToLattice -input $tmpRoot.refSnap -output $tmpRoot.refParam -energyGeV $energyGeV} result] {
        return -code error "APSDeltaK1FileToSetpoints (4): $result"
    }

    # Add the delta K1 values to the present K1 values
    if [catch {exec sddsprocess $input -pipe=out -match=col,ElementParameter=K1 \
                   | sddsconvert -pipe -rename=column,ParameterValue=DeltaParameterValue \
                   | sddsmxref $tmpRoot.refParam -pipe -match=ElementName,ElementParameter -take=ParameterValue,ElementType \
                   -nowarning \
                   | sddsprocess -pipe=in $tmpRoot.changedParam \
                   "-redefine=col,ParameterValue,ParameterValue DeltaParameterValue +"} result] {
        return -code error "APSDeltaK1FileToSetpoints (5): $result"
    } 

    # Keep the non-K1 values from the parameter file (these are various FSE values)
    if [catch {exec sddsselect $tmpRoot.refParam $input -pipe=out -match=ElementName -reuse=row \
                   | sddsprocess -pipe=in -match=column,ElementParameter=K1,! $tmpRoot.savedParam} result] {
        return -code error "APSDeltaK1FileToSetpoints (6): $result"
    }

    if $nTGDs!=0 {
        # Need a few extras from the reference lattice or APSLatticeToSetpoints will fail
        if ![file exists [set ref $OAGGlobal(SRLatticesDirectory)/default/Basic.param]] {
            if ![file exists [set ref $OAGGlobal(SRLatticesDirectory)/default/aps.param]] {
                return -code error "APSDeltaK1ToSetpoints: reference lattice not found"
            }
        }
        if [catch {exec sddsprocess $ref -pipe=out \
                       -match=col,ElementParameter=ANGLE,ElementParameter=L,| \
                   | sddsselect -pipe=in $input -match=ElementName -reuse=row $tmpRoot.extra} result] {
            return -code error "APSDeltaK1FileToSetpoints (7): $result"
        }
        if [catch {exec sddscombine $tmpRoot.changedParam $tmpRoot.savedParam $tmpRoot.extra -merge $tmpRoot.newParam \
                       -retain=column,ElementName,ElementParameter,ParameterValue,ElementType} result] {
            return -code error "APSDeltaK1FileToSetpoints (8): $result"
        }
    } else {
        if [catch {exec sddscombine $tmpRoot.changedParam $tmpRoot.savedParam -merge $tmpRoot.newParam \
                       -retain=column,ElementName,ElementParameter,ParameterValue,ElementType} result] {
            return -code error "APSDeltaK1FileToSetpoints (9): $result"
        }
    }

    if $differential {
        set tmpOutput $tmpRoot.output
        APSAddToTmpFileList -ID APSDeltaK1FileToSetpoints
    } else {
        set tmpOutput $output
    }
    
    # Convert to current setpoints
    if [catch {APSLatticeToSetpoints -input $tmpRoot.newParam -output $tmpOutput -energyGeV $energyGeV} result] {
        return -code error "APSDeltaK1FileToSetpoints (10): $result"
    }

    if $differential {
        # Convert to delta setpoints
        if [catch {exec sddsxref $tmpOutput $tmpRoot.refSnap -pipe=out -match=ControlName -take=ValueString -rename=col,ValueString=ValueStringRef \
                       | sddsprocess -pipe -scan=column,Value,ValueString,%le -scan=column,ValueRef,ValueStringRef,%le \
                       "-define=col,DeltaValue,Value ValueRef -" \
                       -reprint=column,ValueString,%le,DeltaValue \
                       | sddsconvert -pipe=in $output -delete=col,Value,ValueRef*,DeltaValue} result] {
            return -code error "APSDeltaK1ToSetpoints: $result"
        }
    }
    
    APSDeleteTmpFileList -ID APSDeltaK1FileToSetpoints
}

proc APSDeltaK2ToSetpointsHelp {} {
    return "Usage: APSDeltaK2ToSetpoints\n{-deltaK2 <value> | -fse <value>}\n-ElementName <string>\n\[-output <filename>\]\[-differential 1\]\[-energyGeV <value>(6)\]\nComputes the current required for a named magnet to change the K2 value by the given amount. If differential=1, returns the change in current not the actual value.\n"
}

proc APSDeltaK2ToSetpoints {args} {
    global OAGGlobal
    set deltaK2 0.0
    set fse 0.0
    set ElementName ""
    set output ""
    set differential 0
    set energyGeV 6.0
    if [catch {APSStrictParseArguments {ElementName deltaK2 fse output differential energyGeV}} result] {
        return -code error "APSDeltaK2ToSetpoints: $result"
    }
    if ![string length $ElementName] {
        return -code error "APSDeltaK2ToSetpoints: ElementName must not be blank"
    }
    eval set ElementGroup [os editstring Z: $ElementName]
    switch -glob $ElementName {
        *:S? {
            set ElementType KSEXT
            set CNList $ElementName:PS:SetCurrentC
        }
        default {
            return -code error "APSDeltaK2ToSetpoints: $ElementName doesn't have adjustable sextupole component"
        }
    }
    
    set tmpRoot /tmp/[APSTmpString]
    APSAddToTmpFileList -ID APSDeltaK2ToSetpoints \
        -fileList [list $tmpRoot.req $tmpRoot.snap $tmpRoot.param $tmpRoot.param0 $tmpRoot.param2 $tmpRoot.snap2]

    # Find present setpoint
    if [catch {exec sddsmakedataset $tmpRoot.req -column=ControlName,type=string -data=[join $CNList ,]} result] {
        return -code error "APSDeltaK2ToSetpoints: $result"
    }
    if [catch {exec sddscasr -save  $tmpRoot.req $tmpRoot.snap -pendIoTime=10} result] {
        return -code error "APSDeltaK2ToSetpoints: $result"
    }

    # Convert to K2
    if [catch {APSSetpointsToLatticeQuadSext -input $tmpRoot.snap -output $tmpRoot.param -energyGeV $energyGeV} result] {
        return -code error "APSDeltaK2ToSetpoints: $result"
    }
    
    # Change the K2
    if [catch {exec sddsprocess $tmpRoot.param $tmpRoot.param2 \
                   "-redefine=col,ParameterValue,ElementParameter \"K2\" streq ? $deltaK2 : 0 \$ ParameterValue + 1 $fse + *" } result] {
        return -code error "APSDeltaK2ToSetpoints: $result"
    }
    
    # Convert back to setpoint
    if [catch {APSLatticeToSetpointsQuadSext -input $tmpRoot.param2 -output $tmpRoot.snap2 -energyGeV $energyGeV} result] {
        return -code error "APSDeltaK2ToSetpoints: $result"
    }
    
    if $differential {
        # Convert to delta setpoints
        if [catch {exec sddsxref $tmpRoot.snap2 $tmpRoot.snap -pipe=out -match=ControlName -take=ValueString -rename=col,ValueString=ValueStringRef \
                       | sddsprocess -pipe -scan=column,Value,ValueString,%le -scan=column,ValueRef,ValueStringRef,%le \
                       "-define=col,DeltaValue,Value ValueRef -" \
                       -reprint=column,ValueString,%le,DeltaValue \
                       | sddsconvert -pipe=in $tmpRoot.snap3 -delete=col,Value,ValueRef*,DeltaValue
            file copy -force $tmpRoot.snap3 $tmpRoot.snap2} result] {
            return -code error "APSDeltaK1ToSetpoints: $result"
        }
    }
    
    if [catch {sdds load $tmpRoot.snap2 data} result] {
        return -code error "APSDeltaK2ToSetpoints: $result"
    }

    foreach controlName [lindex $data(Column.ControlName) 0] value [lindex $data(Column.ValueString) 0] {
        lappend returnList $controlName=$value
    }
    if [llength $output] {
        file copy -force $tmpRoot.snap2 $output
    }
    APSDeleteTmpFileList -ID APSDeltaK2ToSetpoints
    return $returnList
}

proc APSDeltaK2FileToSetpointsHelp {} {
    return "Usage: APSDeltaK2FileToSetpoints\n-input <deltaParamFile>\n-output <snapshotFile>\n\[-differential 1\]\[-energyGeV <value>(6.0)\]\n\nTakes a differential parameter file (ElementName, ElementParameter, ParameterValue) and converts it to a snapshot file (ControlName, ValueString) using the present state as the starting point. If differential=1, the ValueString is the change in current rather than full value."
}

# Compute new setpoints that reflect desired offset in K2's compared to present setpoints

proc APSDeltaK2FileToSetpoints {args} {
    global OAGGlobal
    set input ""
    set output ""
    set energyGeV 6.0
    set differential 0
    if [APSStrictParseArguments {input output energyGeV differential}] {
        return -code error "APSDeltaK2FileToSetpoints: Invalid arguments"
    }    

    if ![string length $input] {
        return -code error "APSDeltaK2FileToSetpoints: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSDeltaK2FileToSetpoints: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSDeltaK2FileToSetpoints: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSDeltaK2FileToSetpoints: in use: $output"
    }

    set tmpRoot /tmp/[APSTmpString]

    APSAddToTmpFileList -ID APSDeltaK2FileToSetpoints -fileList \
        "$tmpRoot.refSnap $tmpRoot.refParam $tmpRoot.changedParam"

    # Get the present setpoints
    if [catch {exec sddsprocess $input -pipe=out -reprint=col,ControlName,%s:PS:SetCurrentC,ElementName \
                 | sddsconvert -pipe -retain=column,ControlName \
                 | sddscasr -save -pipe=in $tmpRoot.refSnap} result] {
        return -code error "APSDeltaK2FileToSetpoints (1): $result"
    }

    # Convert present setpoints the present parameter values
    if [catch {APSSetpointsToLatticeQuadSext -input $tmpRoot.refSnap -output $tmpRoot.refParam -energyGeV $energyGeV} result] {
        return -code error "APSDeltaK2FileToSetpoints (2): $result"
    }

    # Add the delta K2 values to the present K2 values
    if [catch {exec sddsprocess $input -pipe=out -match=col,ElementParameter=K2 \
                   | sddsconvert -pipe -rename=column,ParameterValue=DeltaParameterValue \
                   | sddsmxref $tmpRoot.refParam -pipe -match=ElementName,ElementParameter -take=ParameterValue,ElementType \
                   -nowarning \
                   | sddsprocess -pipe=in $tmpRoot.changedParam \
                   "-redefine=col,ParameterValue,ParameterValue DeltaParameterValue +"} result] {
        return -code error "APSDeltaK2FileToSetpoints (3): $result"
    } 

    # Keep the non-K2 values from the parameter file (these are various FSE values)
    if [catch {exec sddsselect $tmpRoot.refParam $input -pipe=out -match=ElementName -reuse=row \
                   | sddsprocess -pipe=in -match=column,ElementParameter=K2,! $tmpRoot.savedParam} result] {
        return -code error "APSDeltaK2FileToSetpoints (4): $result"
    }

    if [catch {exec sddscombine $tmpRoot.changedParam $tmpRoot.savedParam -merge $tmpRoot.newParam \
                   -retain=column,ElementName,ElementParameter,ParameterValue,ElementType} result] {
        return -code error "APSDeltaK2FileToSetpoints (5): $result"
    }

    if $differential {
        set tmpOutput $tmpRoot.output
        APSAddToTmpFileList -ID APSDeltaK2FileToSetpoints
    } else {
        set tmpOutput $output
    }

    # Convert to current setpoints
    if [catch {APSLatticeToSetpointsQuadSext -input $tmpRoot.newParam -output $tmpOutput -energyGeV $energyGeV} result] {
        return -code error "APSDeltaK2FileToSetpoints (6): $result"
    }

    if $differential {
        # Convert to delta setpoints
        if [catch {exec sddsxref $tmpOutput $tmpRoot.refSnap -pipe=out -match=ControlName -take=ValueString -rename=col,ValueString=ValueStringRef \
                       | sddsprocess -pipe -scan=column,Value,ValueString,%le -scan=column,ValueRef,ValueStringRef,%le \
                       "-define=col,DeltaValue,Value ValueRef -" \
                       -reprint=column,ValueString,%le,DeltaValue \
                       | sddsconvert -pipe=in $output -delete=col,Value,ValueRef*,DeltaValue} result] {
            return -code error "APSDeltaK2FileToSetpoints: $result"
        }
    }
    
    APSDeleteTmpFileList -ID APSDeltaK2FileToSetpoints
}

proc APSLatticeToSetpointsFastCorrectorsHelp {} {
    return "Usage: APSLatticeToSetpointsFastCorrectors\n-input <paramFile>\n-output <setpointFile>\n\[-energyGeV <value>(6.0)\]\n\nConverts elegant parameter file for fast correctors restored to power supplies using sddscasr (ControlName, ValueString).\n"
}

proc APSLatticeToSetpointsFastCorrectors {args} {
    global apsSRExcitationFileDirectory
    set input ""
    set output ""
    set energyGeV 6.0
    if [APSStrictParseArguments {input output energyGeV}] {
        return -code error "APSLatticeToSetpointsFastCorrectors: Invalid arguments"
    }    

    if ![string length $input] {
        return -code error "APSLatticeToSetpointsFastCorrectors: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSLatticeToSetpointsFastCorrectors: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSLatticeToSetpointsFastCorrectors: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSLatticeToSetpointsFastCorrectors: in use: $output"
    }

    foreach plane {H V} {
        set dataList [exec sddsprocess $apsSRExcitationFileDirectory/correctorLimits.sdds -pipe=out -match=col,CorrectorType=F${plane}1 \
                          | sdds2stream -pipe -column=CurrentLimit,DeflectionLimit]
        set IMax [lindex $dataList 0]
        set KMax [expr [lindex $dataList 1]*6.0/$energyGeV]
        if [catch {exec sddsconvert $input -pipe=out -retain=col,ElementName,ElementParameter,ParameterValue \
                       | sddsprocess -pipe=in $output.$plane -nowarning -match=column,ElementName=S???:F${plane}\[12\] \
                       -match=column,ElementParameter=KICK \
                       "-define=column,Current,ParameterValue $KMax / $IMax *,units=A" \
                       -print=column,ValueString,%21.15e,Current \
                       -print=column,ControlName,%s:PS:SetCurrentC,ElementName } result] {
            return -code error "APSLatticeToSetpointsFastCorrectors: $result"
        }
        lappend resultList $output.$plane
    }
    if [catch {eval exec sddscombine $resultList -merge $output} result] {
        return -code error "APSLatticeToSetpointsFastCorrectors: $result"
    }
    catch {eval file delete $resultList}
    return [exec sdds2stream -rows=bare $output]
}

proc APSSetpointsToLatticeFastCorrectorsHelp {} {
    return "Usage: APSSetpointsToLatticeFastCorrectors\n-input <snapshot>\n-output <paramFile>\n\[-energyGeV <value>(6.0)\]\n\nConverts snapshot (ControlName, ValueString) of fast-corrector currents to an elegant parameter file.\n"
}

proc APSSetpointsToLatticeFastCorrectors {args} {
    global apsSRExcitationFileDirectory
    set input ""
    set output ""
    set energyGeV 6.0
    if [APSStrictParseArguments {input output energyGeV}] {
        return -code error "APSSetpointsToLatticeFastCorrectors: Invalid arguments"
    }    

    if ![string length $input] {
        return -code error "APSSetpointsToLatticeFastCorrectors: provide input filename"
    }
    if ![string length $output] {
        return -code error "APSSetpointsToLatticeFastCorrectors: provide output filename"
    }
    if ![file exists $input] {
        return -code error "APSSetpointsToLatticeFastCorrectors: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSSetpointsToLatticeFastCorrectors: in use: $output"
    }

    foreach plane {H V} {
        set dataList [exec sddsprocess $apsSRExcitationFileDirectory/correctorLimits.sdds -pipe=out -match=col,CorrectorType=F${plane}1 \
                          | sdds2stream -pipe -column=CurrentLimit,DeflectionLimit]
        set IMax [lindex $dataList 0]
        set KMax [expr [lindex $dataList 1]*6.0/$energyGeV]
        if [catch {exec sddsconvert $input -pipe=out -retain=col,ControlName,ValueString \
                       | sddsprocess -pipe=in $output.$plane -nowarning -match=column,ControlName=S???:F${plane}\[12\]:PS:SetCurrentC \
                       -print=column,ElementParameter,KICK -scan=column,Current,ValueString,%le \
                       "-define=col,ParameterValue,Current $IMax / $KMax *" \
                       -define=column,ElementOccurence,1,type=long \
                       -edit=column,ElementName,ControlName,2S/:/100D } result] {
            return -code error "APSSetpointsToLatticeFastCorrectors (1): $result"
        }
        lappend resultList $output.$plane
    }
    if [catch {eval exec sddscombine $resultList -merge $output} result] {
        return -code error "APSSetpointsToLatticeFastCorrectors (2): $result"
    }
    catch {eval file delete $resultList}
    return [exec sdds2stream -rows=bare $output]
}

proc APSCurrentToParametersGradientMagnet {} {
    return "Usage: APSCurrentToParametersGradientMagnet\n-ElementName <string>\n-mainCurrent <amps>\n-trimCurrent <amps>\n\[-energyGeV <value>(6.0)\]\nConverts given main and (for TGDs) trim current values to list of parameters in array form.\n"
}

proc APSCurrentToParametersGradientMagnet {args} {
    set ElementName ""
    set mainCurrent -1
    set trimCurrent 0
    set output ""
    set energyGeV 6.0
    global apsSRExcitationFileDirectory 
    if [APSStrictParseArguments {ElementName mainCurrent trimCurrent energyGeV output}] {
        return -code error "APSCurrentToParametersGradientMagnet: Invalid arguments"
    }    
    if ![string length $ElementName] {
        return -code error "APSCurrentToParametersGradientMagnet: ElementName must not be blank"
    }
    eval set ElementGroup [os editstring Z: $ElementName]
    set isTGD 1
    switch -glob $ElementName {
        *:Q\[45\] {
            set ElementType CCBEND
        }
        *:Q8 -
        *:M\[34\] {
            set ElementType CSBEND
        }
        *:Q? {
            set ElementType KQUAD
            set isTGD 0
        }
        default {
            return -code error "APSCurrentToParametersGradientMagnet: $ElementName not supported"
        }
    }
    if $isTGD {
        set PVList [list $ElementName:PS:SetCurrentC ${ElementName}T:PS:SetCurrentC]
        set valueList [list $mainCurrent $trimCurrent]
    } else {
        set PVList $ElementName:PS:SetCurrentC
        set valueList $mainCurrent
    }

    set tmpRoot /tmp/[APSTmpString]
    APSAddToTmpFileList -ID APSCurrentToParametersGradientMagnet -fileList "$tmpRoot.snap $tmpRoot.setp"

    if [expr $mainCurrent<0] {
        if [catch {exec sddsmakedataset -pipe=out -column=ControlName,type=string -data=[join $PVList ,] \
                       | sddscasr -pipe=in -save $tmpRoot.snap} result] {
            return -code error "APSCurrentToParametersGradientMagnet: $result"
        }
    } else {
        if [catch {exec sddsmakedataset $tmpRoot.snap -column=ControlName,type=string -data=[join $PVList ,] \
                       -column=ValueString,type=string -data=[join $valueList ,]} result] {
            return -code error "APSCurrentToParametersGradientMagnet: $result"
        }
    }
    if ![string length $output] {
        set output $tmpRoot.setp
    }
    if $isTGD {
        if [catch {APSSetpointsToLatticeTGD -input $tmpRoot.snap -output $output -type $ElementGroup -energyGeV $energyGeV} result] {
            return -code error "APSCurrentToParametersGradientMagnet: $result"
        }
    } else {
        APSSetpointsToLatticeQuadSext -input $tmpRoot.snap -output $output -energyGeV $energyGeV
    }

    set fd [sdds open $output]
    set tagList [APSGetSDDSColumn -sddsFD $fd -column ElementParameter]
    set valueList [APSGetSDDSColumn -sddsFD $fd -column ParameterValue]
    sdds close $fd
    APSDeleteTmpFileList -ID APSCurrentToParametersGradientMagnet

    set arrayList ""
    foreach tag $tagList value $valueList {
        lappend arrayList $tag $value
    }

    return $arrayList
}

