nsls2 FPM
This commit is contained in:
5
iocBoot/iocFPM/Makefile
Normal file
5
iocBoot/iocFPM/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
TOP = ../..
|
||||||
|
include $(TOP)/configure/CONFIG
|
||||||
|
ARCH = linux-x86_64
|
||||||
|
TARGETS = envPaths
|
||||||
|
include $(TOP)/configure/RULES.ioc
|
133
iocBoot/iocFPM/fpm.db
Normal file
133
iocBoot/iocFPM/fpm.db
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
## Load test data
|
||||||
|
#record(waveform, "TST:datain") {
|
||||||
|
# field(DTYP, "Python Device")
|
||||||
|
# field(INP , "@$(FNAME=)")
|
||||||
|
# field(FTVL, "DOUBLE")
|
||||||
|
# field(NELM, "$(NELM=1000)")
|
||||||
|
# field(PINI, "YES")
|
||||||
|
# info("pySupportMod", "loadnp")
|
||||||
|
#}
|
||||||
|
|
||||||
|
record(longout, "$(P)StartPoint-SP") {
|
||||||
|
field(DESC, "# of samples to skip")
|
||||||
|
field(VAL , "419")
|
||||||
|
field(PINI, "YES")
|
||||||
|
info(autosaveFields_pass0, "VAL")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(ao, "$(P)PeakThreshold-SP") {
|
||||||
|
field(VAL , "0.01")
|
||||||
|
field(EGU , "V")
|
||||||
|
field(PINI, "YES")
|
||||||
|
field(PREC, "3")
|
||||||
|
info(autosaveFields_pass0, "VAL")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(ao, "$(P)Offset-SP") {
|
||||||
|
field(VAL , "0.05")
|
||||||
|
field(PINI, "YES")
|
||||||
|
field(PREC, "3")
|
||||||
|
info(autosaveFields_pass0, "VAL")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(aSub, "$(P)FPM-Calc_") {
|
||||||
|
field(PINI, "RUNNING")
|
||||||
|
field(SNAM, "python_asub")
|
||||||
|
|
||||||
|
field(EFLG, "ALWAYS")
|
||||||
|
|
||||||
|
field(INPA, "$(INPA=TST:datain CP MSI)")
|
||||||
|
field(INPB, "$(P)StartPoint-SP")
|
||||||
|
field(INPC, "$(P)PeakThreshold-SP")
|
||||||
|
field(INPD, "$(INPD=1.0)")
|
||||||
|
field(INPE, "$(P)Offset-SP")
|
||||||
|
|
||||||
|
field(OUTA, "$(P)BunchQ-Wf PP MSI")
|
||||||
|
field(OUTB, "$(P)Bucket-I PP MSI")
|
||||||
|
field(OUTC, "$(P)K:Run-I.A PP MSI")
|
||||||
|
field(OUTD, "$(P)NbrBunches-I PP") # No MSI
|
||||||
|
field(OUTE, "$(P)SumQ-I PP MSI")
|
||||||
|
field(OUTF, "$(P)MinQ-I PP MSI")
|
||||||
|
field(OUTG, "$(P)MaxQ-I PP MSI")
|
||||||
|
field(OUTH, "$(P)MinQBunchNbr-I PP MSI")
|
||||||
|
field(OUTI, "$(P)MaxQBunchNbr-I PP MSI")
|
||||||
|
field(OUTJ, "$(P)B2BMaxVar-I PP MSI")
|
||||||
|
field(OUTK, "$(P)TTQ-Wf PP MSI")
|
||||||
|
|
||||||
|
field(FTA , "DOUBLE") # input data
|
||||||
|
field(FTB , "LONG") # num. samples to skip
|
||||||
|
field(FTC , "DOUBLE") # beam threshold
|
||||||
|
field(FTD , "DOUBLE") # input scale
|
||||||
|
|
||||||
|
field(FTVA, "DOUBLE") # output fill array
|
||||||
|
field(FTVB, "DOUBLE") # output phase
|
||||||
|
field(FTVC, "DOUBLE") # runtime
|
||||||
|
field(FTVD, "LONG") # num. with beam
|
||||||
|
field(FTVE, "DOUBLE") # sum
|
||||||
|
field(FTVF, "DOUBLE") # min val
|
||||||
|
field(FTVG, "DOUBLE") # max val
|
||||||
|
field(FTVH, "DOUBLE") # min bucket
|
||||||
|
field(FTVI, "DOUBLE") # max bucket
|
||||||
|
field(FTVJ, "DOUBLE") # % variation
|
||||||
|
field(FTVK, "DOUBLE") # charge by turn
|
||||||
|
|
||||||
|
field(NOA , "$(NELM=1000)")
|
||||||
|
field(NOVA, "1320")
|
||||||
|
field(NOVB, "16")
|
||||||
|
field(NOVK, "100") # must be >= num. of turns
|
||||||
|
|
||||||
|
info(pySupportLink, "fpm test")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(waveform, "$(P)BunchQ-Wf") {
|
||||||
|
field(FTVL, "DOUBLE")
|
||||||
|
field(NELM, "1320")
|
||||||
|
field(EGU , "mA")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(waveform, "$(P)Bucket-I") {
|
||||||
|
field(FTVL, "DOUBLE")
|
||||||
|
field(NELM, "16")
|
||||||
|
field(EGU , "V")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(calc, "$(P)K:Run-I") {
|
||||||
|
field(PREC, "3")
|
||||||
|
field(EGU , "ms")
|
||||||
|
field(CALC, "A*1000")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(longin, "$(P)NbrBunches-I") {
|
||||||
|
}
|
||||||
|
|
||||||
|
record(ai, "$(P)SumQ-I") {
|
||||||
|
field(PREC, "3")
|
||||||
|
field(EGU , "mA")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(ai, "$(P)MinQ-I") {
|
||||||
|
field(PREC, "3")
|
||||||
|
field(EGU , "mA")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(ai, "$(P)MaxQ-I") {
|
||||||
|
field(PREC, "3")
|
||||||
|
field(EGU , "mA")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(longin, "$(P)MinQBunchNbr-I") {
|
||||||
|
}
|
||||||
|
|
||||||
|
record(longin, "$(P)MaxQBunchNbr-I") {
|
||||||
|
}
|
||||||
|
|
||||||
|
record(ai, "$(P)B2BMaxVar-I") {
|
||||||
|
field(PREC, "3")
|
||||||
|
field(EGU , "%")
|
||||||
|
}
|
||||||
|
|
||||||
|
record(waveform, "$(P)TTQ-Wf") {
|
||||||
|
field(FTVL, "DOUBLE")
|
||||||
|
field(NELM, "100") # must be >= num. of turns
|
||||||
|
field(EGU , "mA")
|
||||||
|
}
|
163
iocBoot/iocFPM/fpm.py
Normal file
163
iocBoot/iocFPM/fpm.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Processing of Filling Pattern Monitor digitizer data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
from scipy import signal
|
||||||
|
|
||||||
|
from devsup.util import Worker
|
||||||
|
from devsup.hooks import initHook
|
||||||
|
from devsup.dset import AsyncOffload
|
||||||
|
|
||||||
|
bucketPturn = 1320 # NSLS2
|
||||||
|
sampPbucket = 16 # ~= 8GHz/500MHz
|
||||||
|
sampPturn = bucketPturn*sampPbucket
|
||||||
|
|
||||||
|
# Since digitizer sample clock is not sync'd
|
||||||
|
# to the signal the bunch phase will change
|
||||||
|
# over the course of the trace.
|
||||||
|
#
|
||||||
|
# To compensate for this we should resample w/ interpolation,
|
||||||
|
# but this is expensive and may distort the signal.
|
||||||
|
#
|
||||||
|
# Instead make a coarse correction by periodically dropping
|
||||||
|
# a sample to simulate a sync'd sample clock
|
||||||
|
#
|
||||||
|
# This lets us pretend that there are exactly 16 samples per
|
||||||
|
# period of the signal
|
||||||
|
|
||||||
|
Fsamp = 8e9 # ADC samples frequency
|
||||||
|
Fsig =499.68e6 # frequency of signal being sampled
|
||||||
|
mult = Fsamp/Fsig # samples per signal period
|
||||||
|
nPb = int(mult)
|
||||||
|
assert nPb==sampPbucket
|
||||||
|
remd = mult-nPb
|
||||||
|
assert remd<0.5 and remd>0, "assumption violated" # remd==0 drops no samples, remd>0.5 should add samples
|
||||||
|
delta = int(numpy.round(nPb/remd))
|
||||||
|
print 'fpm delta mult', nPb, 'frac', delta
|
||||||
|
|
||||||
|
def deleteEveryNth(arr, N):
|
||||||
|
'''Optimized version of
|
||||||
|
|
||||||
|
return numpy.delete(arr, numpy.arange(0, len(arr), N))
|
||||||
|
'''
|
||||||
|
iS = range(0, len(arr), N)
|
||||||
|
parts = [None]*len(iS)
|
||||||
|
for e,i in enumerate(iS):
|
||||||
|
parts[e] = arr[i+1:i+N]
|
||||||
|
return numpy.concatenate(parts)
|
||||||
|
|
||||||
|
class Dev(AsyncOffload):
|
||||||
|
inputs = {
|
||||||
|
'A':'raw', # array copy
|
||||||
|
'B':'nskip',
|
||||||
|
'C':'thres',
|
||||||
|
'D':'scale',
|
||||||
|
'E':'offset',
|
||||||
|
}
|
||||||
|
outputs = {
|
||||||
|
'VALA':'fill', # array copy
|
||||||
|
'VALB':'phase', # array copy
|
||||||
|
'VALD':'nwbeam',
|
||||||
|
'VALE':'total',
|
||||||
|
'VALF':'minv',
|
||||||
|
'VALG':'maxv',
|
||||||
|
'VALH':'mini',
|
||||||
|
'VALI':'maxi',
|
||||||
|
'VALJ':'pvar',
|
||||||
|
'VALK':'turns',
|
||||||
|
}
|
||||||
|
timefld = 'VALC' # store execution time
|
||||||
|
|
||||||
|
|
||||||
|
def inThread(self, raw=None, nskip=0, thres=0.01, scale=1.0, offset=0, **kws):
|
||||||
|
raw = signal.detrend(raw[nskip:], type='constant')
|
||||||
|
|
||||||
|
# remove every delta'th sample
|
||||||
|
raw = deleteEveryNth(raw, delta)
|
||||||
|
|
||||||
|
# truncate to whole turns
|
||||||
|
turns = len(raw)/sampPturn
|
||||||
|
raw = raw[:turns*sampPturn]
|
||||||
|
|
||||||
|
# determine buckets w/ beam
|
||||||
|
val = raw.reshape(turns, bucketPturn, sampPbucket)
|
||||||
|
|
||||||
|
# Which buckets have beam?
|
||||||
|
# average over turns
|
||||||
|
# find buckets where peak to peak voltage exceeds threshold
|
||||||
|
hasbeam = val.mean(0).ptp(1)>thres
|
||||||
|
nwbeam = hasbeam.sum()
|
||||||
|
|
||||||
|
if nwbeam>0:
|
||||||
|
# fine tune sync.
|
||||||
|
# find peak position for each turn by averaging all buckets w/ beam
|
||||||
|
peaksamp = val[:,hasbeam,:].argmax(2).mean(1)
|
||||||
|
turnshift = numpy.round(peaksamp).astype(numpy.uint8)
|
||||||
|
|
||||||
|
turnval = raw.reshape(turns, sampPturn)
|
||||||
|
|
||||||
|
# shift turns to align peak with turn zero.
|
||||||
|
## Warning, this mixes signals for buckets 0 and 1319 in adjecent turns
|
||||||
|
turn0shift = turnshift[0]
|
||||||
|
|
||||||
|
for i in range(16):
|
||||||
|
turnval[turnshift==i,:] = numpy.roll(turnval[turnshift==i,:], turn0shift-i, axis=1)
|
||||||
|
|
||||||
|
val = turnval.reshape(turns, bucketPturn, sampPbucket)
|
||||||
|
|
||||||
|
# sum samples over each RF bucket
|
||||||
|
# offset compensates for summing of digitizer noise
|
||||||
|
Ival = numpy.abs(val).sum(2) - offset
|
||||||
|
|
||||||
|
# after subtraction the "current" may be negative, but this
|
||||||
|
# will mess up the automatic re-scaling, so force non-negative
|
||||||
|
Ival[Ival<0] = 0.0
|
||||||
|
|
||||||
|
fillbyturn = Ival.mean(1) # average buckets
|
||||||
|
|
||||||
|
fill = Ival.mean(0) # average turns
|
||||||
|
|
||||||
|
if scale>0.0:
|
||||||
|
S1 = scale/fill.sum()
|
||||||
|
fill *= S1
|
||||||
|
S2 = scale/fillbyturn[0]
|
||||||
|
fillbyturn *= S2
|
||||||
|
#print 'factor', S1, S2
|
||||||
|
else:
|
||||||
|
fill = numpy.zeros(fill.shape, dtype=fill.dtype)
|
||||||
|
fillbyturn = numpy.zeros(fillbyturn.shape, dtype=fillbyturn.dtype)
|
||||||
|
|
||||||
|
phase = val.mean(1).mean(0) # sum over turns and buckets to get the approx. bunch signal shape
|
||||||
|
|
||||||
|
if nwbeam>0:
|
||||||
|
bfill = fill[hasbeam]
|
||||||
|
bindx = numpy.arange(fill.shape[0])[hasbeam]
|
||||||
|
pvar = (bfill.max()-bfill.min())/(bfill.max()+bfill.min())*100.0
|
||||||
|
else:
|
||||||
|
bfill = numpy.asarray([0.0])
|
||||||
|
bindx = numpy.asarray([0])
|
||||||
|
pvar = 0.0
|
||||||
|
|
||||||
|
#print 'ellapsed 2 %.03f'%(time.time()-TS)
|
||||||
|
return {
|
||||||
|
'ok':True,
|
||||||
|
'fill':fill,
|
||||||
|
'turns':fillbyturn,
|
||||||
|
'phase':phase,
|
||||||
|
'nwbeam':hasbeam.sum(),
|
||||||
|
'total':fill.sum(),
|
||||||
|
'minv':bfill.min(),
|
||||||
|
'maxv':bfill.max(),
|
||||||
|
'mini':bindx[bfill.argmin()],
|
||||||
|
'maxi':bindx[bfill.argmax()],
|
||||||
|
'pvar':pvar,
|
||||||
|
}
|
||||||
|
|
||||||
|
build = Dev
|
26
iocBoot/iocFPM/loadnp.py
Normal file
26
iocBoot/iocFPM/loadnp.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Waveform device support which reads
|
||||||
|
from a .npy file.
|
||||||
|
|
||||||
|
Intended to load test input data
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
class Device(object):
|
||||||
|
def __init__(self, rec, fname):
|
||||||
|
if not fname:
|
||||||
|
return
|
||||||
|
print 'Load', fname,'into',rec.NAME
|
||||||
|
data = numpy.load(fname)
|
||||||
|
assert len(data.shape)==1, 'only 1D supported'
|
||||||
|
val = rec.field('VAL')
|
||||||
|
vlen = min(len(val), len(data))
|
||||||
|
val.getarray()[:vlen] = data[:vlen]
|
||||||
|
val.putarraylen(vlen)
|
||||||
|
|
||||||
|
def process(self, rec, reason=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
build = Device
|
40
iocBoot/iocFPM/st.cmd
Executable file
40
iocBoot/iocFPM/st.cmd
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
#!./bin/linux-x86/softIocPy
|
||||||
|
|
||||||
|
epicsEnvSet("ENGINEER","mdavidsaver")
|
||||||
|
epicsEnvSet("LOCATION","740")
|
||||||
|
|
||||||
|
# FPM waveform is 1M elements of type DBF_DOUBLE
|
||||||
|
epicsEnvSet("EPICS_CA_MAX_ARRAY_BYTES","9000000")
|
||||||
|
|
||||||
|
py "import logging"
|
||||||
|
py "logging.basicConfig(level=logging.DEBUG)"
|
||||||
|
|
||||||
|
#SR:C16-BI{FPM:1}BunchQ-Wf
|
||||||
|
|
||||||
|
dbLoadRecords("fpm.db","P=SR:C16-BI{FPM:2},INPA=SR:C16-BI{FPM:1}V-Wf CP MSI,INPD=SR:C03-BI{DCCT:1}I:Total-I MSI,NELM=1000000")
|
||||||
|
#dbLoadRecords("fpm.db","FNAME=fpm-5ma-teeth.npy,NELM=1000000")
|
||||||
|
|
||||||
|
dbLoadRecords("../../db/iocAdminSoft.db", "IOC=SR-CT{IOC:FPM}")
|
||||||
|
dbLoadRecords("../../db/save_restoreStatus.db", "P=SR-CT{IOC:FPM}")
|
||||||
|
save_restoreSet_status_prefix("SR-CT{IOC:FPM}")
|
||||||
|
|
||||||
|
#asSetFilename("/cf-update/acf/default.acf")
|
||||||
|
asSetFilename("/cf-update/acf/null.acf")
|
||||||
|
|
||||||
|
set_savefile_path("${PWD}/as","/save")
|
||||||
|
set_requestfile_path("${PWD}/as","/req")
|
||||||
|
|
||||||
|
set_pass0_restoreFile("ioc_settings.sav")
|
||||||
|
|
||||||
|
iocInit()
|
||||||
|
|
||||||
|
makeAutosaveFileFromDbInfo("as/req/ioc_settings.req", "autosaveFields_pass0")
|
||||||
|
create_monitor_set("ioc_settings.req", 10, "")
|
||||||
|
|
||||||
|
#caPutLogInit("ioclog.cs.nsls2.local:7004", 1)
|
||||||
|
|
||||||
|
# Start Reference tracker
|
||||||
|
#py "from devsup import disect; disect.periodic(10)"
|
||||||
|
|
||||||
|
dbl > records.dbl
|
||||||
|
#system "cp records.dbl /cf-update/$HOSTNAME.$IOCNAME.dbl"
|
Reference in New Issue
Block a user