Files
sf-op/script/Tools/BunchLengthScan.py
2026-02-09 15:06:52 +01:00

258 lines
11 KiB
Python

import ch.psi.pshell.epics.Positioner as Positioner
from mathutils import fit_polynomial
#Parameters
dry_run = False
do_elog = True
USE_SCREEN_PANEL = True
MO_FREQ = 142.8e6 # MO = 142.8 MHz
FW_THRESHOLD = 0.20
is_panel = get_exec_pars().source != CommandSource.ui # run from panel
if is_panel:
start = args[0]
stop = args[1] + 0.001 # needed to get the last point
step = args[2]
nb = int(args[3])
lat = args[4]
thr = args[5]
tds = args[6]
bunch = args[7]
screen = args[8]
user_calib_enabled = args[9]
user_calib = args[10]
plt11 = args[11]
plt12 = args[12]
plt21 = args[13]
plt22 = args[14]
else:
start = -0.1
stop = 0.1 + 0.001
step = 0.05
nb = 10
lat = 0.4
thr = 20
tds = "SATMA02"
bunch = "Bunch2"
screen = "SATBD02-DSCR050"
user_calib_enabled = False
user_calib = 1.0
plt11 = plot(None, title="Output")[0]
plt12 = plot(None, title="Output")[0]
plt21 = plot(None, title="Output")[0]
plt22 = plot(None, title="Output")[0]
run("Tools/BunchLengthTDSdata.py")
#Record callback: update of output plot
def update_plot(record, scan, plt):
com_mean, com_stdev = record[0].mean, record[0].stdev
rms_mean, rms_stdev = record[1].mean, record[1].stdev
fw_mean, fw_stdev = record[2].mean, record[2].stdev
fit_mean, fit_stdev = record[3].mean, record[3].stdev
phase = record.positions[0]
plt.getSeries(0).appendData(phase, com_mean, com_stdev)
plt.getSeries(1).appendData(phase, rms_mean, rms_stdev)
plt.getSeries(2).appendData(phase, fw_mean, fw_stdev)
plt.getSeries(3).appendData(phase, fit_mean, fit_stdev)
def update_plot_1(record, scan):
global plt11
update_plot(record, scan, plt11)
def update_plot_2(record, scan):
global plt12
update_plot(record, scan, plt12)
#Plot setup
def init_plots(plt1, plt2):
plt1.clear()
plt1.removeMarker(None)
plt1.setStyle(plt1.Style.ErrorY)
plt1.addSeries(LinePlotErrorSeries("Bunch center of mass", Color.red))
plt1.addSeries(LinePlotErrorSeries("Bunch length rms", Color.yellow, 2))
plt1.addSeries(LinePlotErrorSeries("Bunch length fw", Color.green, 2))
plt1.addSeries(LinePlotErrorSeries("Bunch length gauss fit", Color.blue, 2))
plt1.getAxis(plt1.AxisId.X).setLabel("TDS Phase (deg)")
plt1.getAxis(plt1.AxisId.Y).setLabel("Bunch center of mass (um)")
plt1.getAxis(plt1.AxisId.Y2).setLabel("Bunch length (um)")
plt1.setLegendVisible(True)
plt2.clear()
plt2.removeMarker(None)
plt2.setStyle(plt2.Style.Normal)
plt2.getAxis(plt2.AxisId.X).setLabel("Time (fs)")
plt2.getAxis(plt2.AxisId.Y).setLabel("Current (kA)")
plt2.setLegendVisible(False)
init_plots(plt11, plt21)
init_plots(plt12, plt22)
#Creating Phase positioner
if dry_run:
phase = DummyPositioner("TDS Phase")
screen = "simulation"
else:
phase = Positioner("TDS Phase", tds + "-RSYS:SET-BEAM-PHASE", tds + "-RSYS:SET-BEAM-PHASE")
camera_name = screen
phase.config.minValue = -90.0
phase.config.maxValue = 360.0
phase.config.precision = 4
phase.config.deadband = 0.05
#phase.config.rotation = True # this may cause trouble when scanning around zero
phase.config.save()
phase.initialize()
phase0 = phase.read() % 360
#Camera setup
cam_server.start(camera_name +"_sp", USE_SCREEN_PANEL)
cam_server.setFunction("bunch_length_op")
cam_server.setInstanceConfigValue("replace", True)
cam_server.setInstanceConfigValue("fw_threshold", FW_THRESHOLD)
cam_server.setThreshold(thr)
cam_server.setBackgroundSubtraction(False)
cam_server.setGoodRegion(0.05, 5.0)
print "Waiting for fw and fit"
xy = tds_data[tds]["DEFAULTS"]["plane"]
timeout = 1.0
start_time = time.time()
while (True):
wait_cam_server_message()
fw = cam_server.stream.getChild(xy + "_fw")
gr_fit = cam_server.stream.getChild("gr_" + xy + "_fit_standard_deviation")
if (fw is not None) and (gr_fit is not None):
break
if time.time() - start_time > timeout:
if fw is None:
raise Exception(xy + "_fw not in camera stream")
if gr_fit is None:
raise Exception(xy + "_fit not in camera stream")
com = cam_server.stream.getChild(xy + "_center_of_mass")
rms = cam_server.stream.getChild(xy + "_rms")
profile = cam_server.stream.getChild(xy + "_profile")
axis = cam_server.stream.getChild(xy + "_axis")
image = cam_server.getDataMatrix()
set_device_alias(image, "image")
#Creating averagers
com_averager = create_averager(com, nb, -1) # -1 event based, waits for the next value
rms_averager = create_averager(rms, nb, -1)
fw_averager = create_averager(fw, nb, -1)
fit_averager = create_averager(gr_fit, nb, -1)
rms_averager.monitored = True # not blocking, will return last nb values
fw_averager.monitored = True
fit_averager.monitored = True
#The scan loop
try:
bunch1_charge = caget("SINEG01-DBPM340:Q1")
bunch2_charge = caget("SINEG01-DBPM340:Q2")
bunch_charge = bunch1_charge if bunch == "Bunch1" else bunch2_charge
sensor_list = [com_averager, rms_averager, fw_averager, fit_averager, axis, profile, com, image]
phase.write(start)
time.sleep(3.0)
# get bs phase-jitter / screen position data for SATMA02 calib
r1 = lscan(phase, sensor_list, start, stop, step , latency=lat, after_read = update_plot_1)
start, stop = start + 180.0, stop + 180.0
phase.write(start)
time.sleep(3.0)
r2 = lscan(phase, sensor_list, start, stop, step , latency=lat, after_read = update_plot_2)
finally:
phase.write(0.001) # 0.0 seems to give 360 deg?!
phase.close()
cam_server.stop() # stops cam_server but does not close it cam_server is a global object
# stop the beam
if not dry_run:
if bunch == "Bunch1":
caput("SIN-TIMAST-TMA:Bunch-1-OnDelay-Sel", 1)
else:
caput("SIN-TIMAST-TMA:Bunch-2-OnDelay-Sel", 1)
caput("SIN-TIMAST-TMA:Beam-Apply-Cmd.PROC", 1)
#output
def write_output(r, plt):
phase_pos = r.getPositions(0)
rf_period = 1 / MO_FREQ / tds_data[tds]["harm"]
time_pos = [val / 360.0 * rf_period * 1e15 for val in phase_pos] # in fs
bunch_center = [val.mean for val in r.getReadable(0)]
bunch_center_stdev = [val.stdev for val in r.getReadable(0)]
a0, a1 = fit_polynomial(time_pos, bunch_center, 1)
calib = abs(a1) # in fs/um
if user_calib_enabled:
calib = rf_period * 1e15 / 360 / user_calib / 1000 # in fs/um
if dry_run: calib = 1
bunch_length_rms = [val.mean * calib for val in r.getReadable(1)]
bunch_length_rms_average = sum(bunch_length_rms) / len(bunch_length_rms)
bunch_length_fw = [val.mean * calib for val in r.getReadable(2)]
bunch_length_fw_average = sum(bunch_length_fw) / len(bunch_length_fw)
bunch_length_fit = [val.mean * calib for val in r.getReadable(3)]
bunch_length_fit_average = sum(bunch_length_fit) / len(bunch_length_fit)
profiles_um = r[axis]
profiles_dig = r[profile]
profiles_com = r[com]
com_avg = sum(profiles_com) / len(profiles_com)
profiles_fs = []
profiles_kA = []
for i in range(len(r[axis])):
delta_um = profiles_com[i] - com_avg
profiles_fs.append([(val - delta_um) * calib for val in profiles_um[i]])
step_fs = (max(profiles_fs[i]) - min(profiles_fs[i])) / (len(profiles_fs[i]) - 1)
total_dig = float(sum(profiles_dig[i]))
profiles_kA.append([val / total_dig * bunch_charge / step_fs for val in profiles_dig[i]]) # pC / fs = kA
for i in range(len(profiles_um)):
plt.addSeries(LinePlotSeries("Bunch current " + str(i), Color.red))
plt.getSeries(i).setData(profiles_fs[i], profiles_kA[i])
return bunch_length_rms_average, bunch_length_fw_average, bunch_length_fit_average, calib
bunch_length_rms_average1, bunch_length_fw_average1, bunch_length_fit_average1, calib1 = write_output(r1, plt21)
bunch_length_rms_average2, bunch_length_fw_average2, bunch_length_fit_average2, calib2 = write_output(r2, plt22)
bunch_length_rms = (bunch_length_rms_average1 + bunch_length_rms_average2) / 2
bunch_length_fw = (bunch_length_fw_average1 + bunch_length_fw_average2) / 2
bunch_length_fit = (bunch_length_fit_average1 + bunch_length_fit_average2) / 2
calib = (calib1 + calib2) / 2
#archiver channels
if do_elog:
if bunch == "Bunch1":
caput(tds + "-RTDS100:BD-BUNCH1-DURATION", bunch_length_fit)
caput(tds + "-RTDS100:BD-BUNCH1-CALIBRATION", 1 / calib)
else:
caput(tds + "-RTDS100:BD-BUNCH2-DURATION", bunch_length_fit)
caput(tds + "-RTDS100:BD-BUNCH2-CALIBRATION", 1 / calib)
#Elog entry
if do_elog:
if get_option("Generated data file:\n" + get_exec_pars().path +"\n\n" + "Save to ELOG?", "YesNo") == "Yes":
title = "Bunch length Scan"
log_msg = "Data file: " + get_exec_pars().path + "\n\n"
log_msg = log_msg + "TDS: " + tds + "\n"
log_msg = log_msg + "Bunch: " + bunch + "\n\n"
log_msg = log_msg + "#### 0 deg ####\n"
log_msg = log_msg + "Bunch length rms: %0.1f" % bunch_length_rms_average1 + " fs\n"
log_msg = log_msg + "Bunch length fw: %0.1f" % bunch_length_fw_average1 + " fs\n"
log_msg = log_msg + "Bunch length fit: %0.1f" % bunch_length_fit_average1 + " fs\n"
log_msg = log_msg + "Calibration: %0.4f" % calib1 + " fs/um\n\n"
log_msg = log_msg + "#### 180 deg ####\n"
log_msg = log_msg + "Bunch length rms: %0.1f" % bunch_length_rms_average2 + " fs\n"
log_msg = log_msg + "Bunch length fw: %0.1f" % bunch_length_fw_average2 + " fs\n"
log_msg = log_msg + "Bunch length fit: %0.1f" % bunch_length_fit_average2 + " fs\n"
log_msg = log_msg + "Calibration: %0.4f" % calib2 + " fs/um\n\n"
log_msg = log_msg + "#### mean ####\n"
log_msg = log_msg + "Bunch length rms: %0.1f" % bunch_length_rms + " fs\n"
log_msg = log_msg + "Bunch length fw: %0.1f" % bunch_length_fw + " fs\n"
log_msg = log_msg + "Bunch length fit: %0.1f" % bunch_length_fit + " fs\n"
log_msg = log_msg + "Calibration: %0.4f" % calib + " fs/um"
sleep(0.1) #Give some time to plot to be finished - it is not sync with acquisition
file_name1 = os.path.abspath(Setup.getContextPath() + "/BunchLengthScanPlot1.png")
plt11.saveSnapshot(file_name1, "png", Dimension(400, 300))
file_name2 = os.path.abspath(Setup.getContextPath() + "/BunchLengthCurrent1.png")
plt21.saveSnapshot(file_name2, "png", Dimension(400, 300))
file_name3 = os.path.abspath(Setup.getContextPath() + "/BunchLengthScanPlot2.png")
plt12.saveSnapshot(file_name3, "png", Dimension(400, 300))
file_name4 = os.path.abspath(Setup.getContextPath() + "/BunchLengthCurrent2.png")
plt22.saveSnapshot(file_name4, "png", Dimension(400, 300))
elog(title, log_msg, [file_name1, file_name2, file_name3, file_name4])
set_return([bunch_length_rms_average1, bunch_length_fw_average1, bunch_length_fit_average1, calib1,
bunch_length_rms_average2, bunch_length_fw_average2, bunch_length_fit_average2, calib2,
bunch_length_rms, bunch_length_fw, bunch_length_fit, calib])