Gather "calibrated" input caget -t H1:SUS-IM1_M1_OPTICALIGN_P_OFFSET H1:SUS-IM2_M1_OPTICALIGN_P_OFFSET H1:SUS-IM3_M1_OPTICALIGN_P_OFFSET H1:SUS-IM4_M1_OPTICALIGN_P_OFFSET 7794 20157 -7576 520.519 caget -t H1:SUS-IM1_M1_OPTICALIGN_Y_OFFSET H1:SUS-IM2_M1_OPTICALIGN_Y_OFFSET H1:SUS-IM3_M1_OPTICALIGN_Y_OFFSET H1:SUS-IM4_M1_OPTICALIGN_Y_OFFSET -8933 -4177 9046 6110.9 Gather existing calibration gains caget -t H1:SUS-IM1_M1_OPTICALIGN_P_GAIN H1:SUS-IM2_M1_OPTICALIGN_P_GAIN H1:SUS-IM3_M1_OPTICALIGN_P_GAIN H1:SUS-IM4_M1_OPTICALIGN_P_GAIN 0.459 0.424 0.424 0.482 caget -t H1:SUS-IM1_M1_OPTICALIGN_Y_GAIN H1:SUS-IM2_M1_OPTICALIGN_Y_GAIN H1:SUS-IM3_M1_OPTICALIGN_Y_GAIN H1:SUS-IM4_M1_OPTICALIGN_Y_GAIN 0.23 0.243 0.254 0.259 Gather Euler Basis DAC Drive caget -t H1:SUS-IM1_M1_OPTICALIGN_P_OUT16 H1:SUS-IM2_M1_OPTICALIGN_P_OUT16 H1:SUS-IM3_M1_OPTICALIGN_P_OUT16 H1:SUS-IM4_M1_OPTICALIGN_P_OUT16 3577.45 8546.57 -3212.22 250.89 caget -t H1:SUS-IM1_M1_OPTICALIGN_Y_OUT16 H1:SUS-IM2_M1_OPTICALIGN_Y_OUT16 H1:SUS-IM3_M1_OPTICALIGN_Y_OUT16 H1:SUS-IM4_M1_OPTICALIGN_Y_OUT16 -2054.59 -1015.01 2297.68 1582.72 ## To make sure we get teh same DAC counts out as before: We have "CAL" OFFSET * G = DAC OFFSET with G = DAC ct / "urad". We want CAL OFFSET * H = DAC OFFSET' with H = DAC ct / urad. That means to obtain the same DAC counts when we're done, we want DAC OFFSET' = DAC OFFSET CAL OFFSET * H = "CAL" OFFSET * G CAL OFFSET = "CAL" OFFSET * (G / H) ## import ezca import numpy as np from matplotlib import pyplot as plt from matplotlib import ticker as tck dacOffsets = np.empty((4,2)) badcalOffsets = np.empty((4,2)) badcalGains = np.empty((4,2)) PITTRIALS_OSEMs = np.empty((4,6,2)) YAWTRIALS_OSEMs = np.empty((4,6,2)) trialOffsets = np.array([-10000, -5000, -1000, 1000, 5000, 10000]) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): dacOffsets[iOptic][iDOF] = ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_OUT16'] badcalOffsets[iOptic][iDOF] = ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_OFFSET'] badcalGains[iOptic][iDOF] = ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_GAIN'] #Begin calibration: #set alignment slider gains to 1.0 unityCalGains = np.ones((4,2)) time.time() for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_GAIN'] = unityCalGains[iOptic][iDOF] time.time() All optic's ring-down time annoyingly long (minutes.) Increased P and Y damping gains by a factor of 10. PITCH gain from -0.02 to -0.2 YAW gain from -0.04 to -0.4 for iTrial, thisTrial in enumerate(trialOffsets): time.time() for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_Y_OFFSET'] = 0.0 ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_P_OFFSET'] = thisTrial time.sleep(30) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): PITTRIALS_OSEMs[iOptic][iTrial][iDOF] = ezca['SUS-'+thisOptic+'_M1_DAMP_'+thisDOF+'_INMON'] time.sleep(1) print('Done with Trial '+str(iTrial)) for iTrial, thisTrial in enumerate(trialOffsets): time.time() for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_P_OFFSET'] = 0.0 ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_Y_OFFSET'] = thisTrial time.sleep(30) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): YAWTRIALS_OSEMs[iOptic][iTrial][iDOF] = ezca['SUS-'+thisOptic+'_M1_DAMP_'+thisDOF+'_INMON'] time.sleep(1) print('Done with Trial '+str(iTrial)) ### Plot the Results resultsDir = '/ligo/home/jeffrey.kissel/2024-04-16/' figTag = '2024-04-16_H1SUSIM_AlignmentSlider_Recalibration_GainFitPlots' PIT = 0 YAW = 1 newcalGains = np.empty((4,2)) xcouplGains = np.empty((4,2)) colors = ['C00','C01','C02','C03'] # PITCH Slider, PITCH OSEMs fig = plt.figure() s1 = fig.add_subplot(111) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): theseOSEMs = [PITTRIALS_OSEMs[iOptic][ii][PIT] for ii in range(len(trialOffsets))] thisSlope, thisOffset = np.polyfit(trialOffsets,theseOSEMs,1) newcalGains[iOptic][PIT] = thisSlope s1.plot(trialOffsets,theseOSEMs-thisOffset,ls='none',marker='.',markersize=12,label='{}: {:.4f} [urad /DAC ct] or {:.4f} [DAC ct / urad]'.format(thisOptic,thisSlope,1/thisSlope) ,color=colors[iOptic]) s1.plot(trialOffsets,thisSlope*trialOffsets,ls='--') s1.legend() s1.set_xlabel('PITCH OPTICALIGN Slider Value [ct]') s1.set_ylabel('PITCH OSEM Position [urad]') s1.grid(which='major',color='black') s1.grid(which='minor', ls='--',color='gray') s1.xaxis.set_major_locator(tck.MultipleLocator(2500)) s1.xaxis.set_minor_locator(tck.MultipleLocator(500)) s1.yaxis.set_major_locator(tck.MultipleLocator(500)) s1.yaxis.set_minor_locator(tck.MultipleLocator(100)) plt.title('OPTICALIGN Slider Calibration Gains :: PITCH\nAbsolute Angle Reference :: OSEMS') plt.savefig(resultsDir+figTag+'_PITEXC_PITRESP.pdf',bbox_inches='tight') # PITCH Slider, YAW OSEMs fig = plt.figure() s1 = fig.add_subplot(111) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): theseOSEMs = [PITTRIALS_OSEMs[iOptic][ii][YAW] for ii in range(len(trialOffsets))] thisSlope, thisOffset = np.polyfit(trialOffsets,theseOSEMs,1) xcouplGains[iOptic][PIT] = thisSlope s1.plot(trialOffsets,theseOSEMs-thisOffset,ls='none',marker='.',markersize=12,label='{}: {:.4f} [urad /DAC ct] or {:.4f} [DAC ct / urad]'.format(thisOptic,thisSlope,1/thisSlope) ,color=colors[iOptic]) s1.plot(trialOffsets,thisSlope*trialOffsets,ls='--') s1.legend() s1.set_xlabel('PITCH OPTICALIGN Slider Value [ct]') s1.set_ylabel('YAW OSEM Position [urad]') s1.grid(which='major',color='black') s1.grid(which='minor', ls='--',color='gray') s1.xaxis.set_major_locator(tck.MultipleLocator(2500)) s1.xaxis.set_minor_locator(tck.MultipleLocator(500)) s1.yaxis.set_major_locator(tck.MultipleLocator(50)) s1.yaxis.set_minor_locator(tck.MultipleLocator(10)) plt.title('OPTICALIGN Slider Cross Coupling :: YAW OSEM Response to PITCH Slider\nAbsolute Angle Reference :: OSEMS') plt.savefig(resultsDir+figTag+'_PITEXC_YAWRESP.pdf',bbox_inches='tight') # YAW Slider, YAW OSEMs fig = plt.figure() s1 = fig.add_subplot(111) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): theseOSEMs = [YAWTRIALS_OSEMs[iOptic][ii][YAW] for ii in range(len(trialOffsets))] thisSlope, thisOffset = np.polyfit(trialOffsets,theseOSEMs,1) newcalGains[iOptic][YAW] = thisSlope s1.plot(trialOffsets,theseOSEMs-thisOffset,ls='none',marker='.',markersize=12,label='{}: {:.4f} [urad /DAC ct] or {:.4f} [DAC ct / urad]'.format(thisOptic,thisSlope,1/thisSlope) ,color=colors[iOptic]) s1.plot(trialOffsets,thisSlope*trialOffsets,ls='--') s1.legend() s1.set_xlabel('YAW OPTICALIGN Slider Value [ct]') s1.set_ylabel('YAW OSEM Position [urad]') s1.grid(which='major',color='black') s1.grid(which='minor', ls='--',color='gray') s1.xaxis.set_major_locator(tck.MultipleLocator(2500)) s1.xaxis.set_minor_locator(tck.MultipleLocator(500)) s1.yaxis.set_major_locator(tck.MultipleLocator(500)) s1.yaxis.set_minor_locator(tck.MultipleLocator(100)) plt.title('OPTICALIGN Slider Calibration Gains :: YAW\nAbsolute Angle Reference :: OSEMS') plt.savefig(resultsDir+figTag+'_YAWEXC_YAWRESP.pdf',bbox_inches='tight') # YAW Slider, PITCH OSEMs fig = plt.figure() s1 = fig.add_subplot(111) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): theseOSEMs = [YAWTRIALS_OSEMs[iOptic][ii][PIT] for ii in range(len(trialOffsets))] thisSlope, thisOffset = np.polyfit(trialOffsets,theseOSEMs,1) xcouplGains[iOptic][YAW] = thisSlope s1.plot(trialOffsets,theseOSEMs-thisOffset,ls='none',marker='.',markersize=12,label='{}: {:.4f} [urad /DAC ct] or {:.4f} [DAC ct / urad]'.format(thisOptic,thisSlope,1/thisSlope) ,color=colors[iOptic]) s1.plot(trialOffsets,thisSlope*trialOffsets,ls='--') s1.legend() s1.set_xlabel('YAW OPTICALIGN Slider Value [ct]') s1.set_ylabel('PITCH OSEM Position [urad]') s1.grid(which='major',color='black') s1.grid(which='minor', ls='--',color='gray') s1.xaxis.set_major_locator(tck.MultipleLocator(2500)) s1.xaxis.set_minor_locator(tck.MultipleLocator(500)) s1.yaxis.set_major_locator(tck.MultipleLocator(50)) s1.yaxis.set_minor_locator(tck.MultipleLocator(10)) plt.title('OPTICALIGN Slider Cross Coupling :: PITCH OSEM Response to YAW Slider\nAbsolute Angle Reference :: OSEMS') plt.savefig(resultsDir+figTag+'_YAWEXC_PITRESP.pdf',bbox_inches='tight') ### The Answers In [106]: newcalGains Out[106]: PIT YAW array([[0.10091482, 0.18706568], IM1 [0.09888863, 0.18225477], IM2 [0.07960343, 0.14543864], IM3 [0.09173853, 0.17765617]]) IM4 In [109]: badcalGains Out[109]: PIT YAW array([[0.459, 0.23 ], IM1 [0.424, 0.243], IM2 [0.424, 0.254], IM3 [0.482, 0.259]]) IM4 newcalOffsets = badcalOffsets * (badcalGains / newcalGains) In [112]: newcalOffsets Out[112]: array([[ 35450.15512901, -10983.25451263], [ 86426.19533262, -5569.18758036], [-40352.83271603, 15798.30527727], [ 2734.8400315 , 8908.905645 ]]) INSTALL WITHOUT ROUNDING for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_GAIN'] = newcalGains[iOptic][iDOF],decimals=4 ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_OFFSET'] = newcalOffsets[iOptic][iDOF],decimals=0 newdacOffsets = np.empty((4,2)) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): newdacOffsets[iOptic][iDOF] = ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_OUT16'] In [117]: newdacOffsets Out[117]: array([[ 3577.44628906, -2054.59008789], [ 8546.56835938, -1015.01104736], [-3212.22412109, 2297.68408203], [ 250.89021301, 1582.7220459 ]]) In [133]: dacOffsets Out[133]: array([[ 3577.44604492, -2054.59008789], [ 8546.56835938, -1015.01104736], [-3212.22412109, 2297.68408203], [ 250.89021301, 1582.72216797]]) Excellent. INSTALL WITH ROUNDING for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_GAIN'] = np.round(newcalGains[iOptic][iDOF],decimals=4) ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_OFFSET'] = np.round(newcalOffsets[iOptic][iDOF],decimals=0) newdacOffsets = np.empty((4,2)) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): newdacOffsets[iOptic][iDOF] = ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_OUT16'] In [126]: newdacOffsets Out[126]: array([[ 3576.9050293 , -2054.91918945], [ 8547.53125 , -1015.22875977], [-3212.09887695, 2297.02929688], [ 250.79951477, 1583.12927246]]) In [133]: dacOffsets Out[133]: array([[ 3577.44604492, -2054.59008789], [ 8546.56835938, -1015.01104736], [-3212.22412109, 2297.68408203], [ 250.89021301, 1582.72216797]]) Still acceptably close. Excellent. calfullrange = np.empty((4,2)) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): calfullrange = 2**17 / newcalGains In [132]: calfullrange Out[132]: array([[1298837.97912513, 700673.67965385], [1325450.66916177, 719169.10706735], [1646562.16059494, 901218.56151759], [1428756.25971267, 737784.67316641]]) use +/-130k for pitch and 70k for yaw (was +/-2500 for both) Tested it out, and found that calibration is wrong. Moving 10 "urad" doesn't move the OSEMs 10 urad. AH! The UNITS! In [106]: newcalGains Out[106]: PIT YAW array([[0.10091482, 0.18706568], IM1 [0.09888863, 0.18225477], IM2 [0.07960343, 0.14543864], IM3 [0.09173853, 0.17765617]]) IM4 THIS IS IN [URAD / CT]. THE GAIN needs to be in [CT/URAD]. newcalGains_urad_per_ct = newcalGains newcalGains_ct_per_urad = 1.0/newcalGains_urad_per_ct newcalGains_ct_per_urad Out[138]: array([[ 9.90934737, 5.34571594], [10.11238609, 5.48682485], [12.56227234, 6.87575196], [10.90054519, 5.62885035]]) REDO OFFSETS. newcalOffsets = badcalOffsets * (badcalGains / newcalGains_ct_per_urad) In [137]: newcalOffsets Out[137]: array([[ 361.01731675, -384.34328033], [ 845.15839552, -184.99059607], [-255.70405673, 334.17203119], [ 23.01629888, 281.18034152]]) INSTALL WITH ROUNDING for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_GAIN'] = np.round(newcalGains_ct_per_urad[iOptic][iDOF],decimals=4) ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_OFFSET'] = np.round(newcalOffsets[iOptic][iDOF],decimals=0) DOUBLE CHECK DAC OFFSETS for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): newdacOffsets[iOptic][iDOF] = ezca['SUS-'+thisOptic+'_M1_OPTICALIGN_'+thisDOF+'_OUT16'] In [143]: newdacOffsets Out[143]: array([[ 3577.25732422, -2052.7487793 ], [ 8544.97851562, -1015.05804443], [-3215.94873047, 2296.51733398], [ 250.71151733, 1581.72094727]]) In [144]: dacOffsets Out[144]: array([[ 3577.44604492, -2054.59008789], [ 8546.56835938, -1015.01104736], [-3212.22412109, 2297.68408203], [ 250.89021301, 1582.72216797]]) Looks good. RECALCULATE RANGE calfullrange = np.empty((4,2)) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): calfullrange = 2**17 / newcalGains_ct_per_urad In [141]: calfullrange Out[141]: array([[13227.10719898, 24519.07311901], [12961.53043166, 23888.49717705], [10433.78111993, 19062.9331414 ], [12024.35269642, 23285.74963514]]) Use +/-10000 for PIT, +/-19000 for YAW NOPE -- the Euler Basis limit of [DAC ct] is not the same as an individual DAC channel OSEM basis limit of DAC ct]. the EULER to OSEM matrix elements are +/- 8.594 due to the unit-preserving lever arm, so one can only drive 2**17 / 8.594 before you saturated the DAC. calfullrange = np.empty((4,2)) for iOptic, thisOptic in enumerate(['IM1','IM2','IM3','IM4']): for iDOF, thisDOF in enumerate(['P','Y']): calfullrange = (2**17/8.594) / newcalGains_ct_per_urad calfullrange Out[169]: array([[1539.10951815, 2853.04551071], [1508.20693875, 2779.67153561], [1214.07739352, 2218.16769158], [1399.15670193, 2709.53568014]]) Use +/-1200 for PIT, +/-2200 for YAW