def unit_SSA(showplots=True, TOL=1.0e-14): """ Unit test for Solid-State Amplifier funtion. Performs the step response of the SSA (which included a low-pass filter + Saturation) and plots the results. This is not a PASS/FAIL test and just intended to provide qualitative evidence, but it could potentially be compared with the real SSA in use. """ # Import JSON parser module from get_configuration import Get_SWIG_RF_Station # Configuration file for specific test configuration # (to be appended to standard test cavity configuration) test_file = [ "source/configfiles/unit_tests/LCLS-II_accelerator.json", "source/configfiles/unit_tests/SSA_test.json" ] # Get SWIG-wrapped C handles for RF Station rf_station, Tstep, fund_mode_dict = Get_SWIG_RF_Station(test_file, Verbose=False) # Simulation duration Tmax = 1e-6 # Create time vector trang = np.arange(0, Tmax, Tstep) # Number of points nt = len(trang) # Initialize vectors for test sout = np.zeros(nt, dtype=np.complex) # Overall cavity accelerating voltage # Set drive signal to 60% of full power drive = rf_station.C_Pointer.PAscale * 0.6 # Run numerical simulation for i in xrange(1, nt): sout[i] = acc.SSA_Step(rf_station.C_Pointer, drive, rf_station.State) # Format plot plt.plot(trang, np.abs(sout), '-', label='SSA output', linewidth=3) plt.ticklabel_format(style='sci', axis='x', scilimits=(1, 0)) plt.title('SSA Test', fontsize=40, y=1.01) plt.xlabel('Time [s]', fontsize=30) plt.ylabel('Amplitude ' + r'[$\sqrt{W}$]', fontsize=30) plt.legend(loc='upper right') plt.ylim([0, 50]) plt.show()
def run_noise_numerical(Tmax, test_file, beam_configuration, detuning=False): """ Run the time-series simulations to analyze the impact of different noise sources/perturbations in an RF Station. """ # Grab beam configuration parameters feedforward = beam_configuration['feedforward'] beam_charge = beam_configuration['charge'] n_pulses = beam_configuration['n_pulses'] beam_start = beam_configuration['beam_start'] missing_pulse = beam_configuration['missing_pulse'] charge_noise_percent = beam_configuration['noise'] # Import JSON parser module from get_configuration import Get_SWIG_RF_Station # Configuration file for specific test configuration # (to be appended to standard test cavity configuration) rf_station, Tstep, fund_mode_dict = Get_SWIG_RF_Station(test_file, set_point=16e6, Verbose=False) # Create time vector trang = np.arange(0, Tmax, Tstep) # Number of points nt = len(trang) # Initialize vectors for test cav_v = np.zeros(nt, dtype=np.complex) error = np.zeros(nt, dtype=np.complex) E_probe = np.zeros(nt, dtype=np.complex) E_reverse = np.zeros(nt, dtype=np.complex) E_fwd = np.zeros(nt, dtype=np.complex) set_point = np.ones(nt, dtype=np.complex) # Initialize beam vector with 0s beam_current = np.zeros(nt, dtype=np.complex) feed_forward = np.zeros(nt, dtype=np.complex) # Create beam vector based on configuration parameters # Calculate current nominal_beam_current = beam_charge / Tstep # Amps # Add charge noise if turned on if charge_noise_percent != None: beam_current_samples = -np.random.normal( -nominal_beam_current, -nominal_beam_current * charge_noise_percent, n_pulses) else: beam_current_samples = nominal_beam_current * np.ones(nt, dtype=np.double) # Fill in the pulses for i in range(n_pulses): beam_current[(beam_start + i * 1e-6) / Tstep] = beam_current_samples[i] # Grab some scaling factors fund_k_beam = fund_mode_dict['k_beam'] fund_k_drive = fund_mode_dict['k_drive'] fund_k_probe = fund_mode_dict['k_probe'] # Deduce feed-forward signal with some error nominal_feed_forward = -0.99 * np.sqrt(2) * nominal_beam_current * ( Tstep / 1e-6) * fund_k_beam / fund_k_drive # Generate feed-forward vector if enabled if feedforward: feed_forward[(beam_start - 0.5e-6) / Tstep:(beam_start + (n_pulses - 0.5) * 1e-6) / Tstep] = nominal_feed_forward # Remove a pulse if option is enabled if missing_pulse: beam_current[(beam_start + 1e-6) / Tstep] = 0.0 feed_forward[(beam_start + 0.5e-6) / Tstep:(beam_start + 1.5 * 1e-6) / Tstep:] = 0.0 # Introduce a modulation of the detune frequency if detuning is enabled if detuning: elecMode_state = acc.ElecMode_State_Get(rf_station.State.cav_state, 0) delta_omega_vector = 2 * np.pi * 10 * np.sin(2 * np.pi * 100 * trang) else: delta_omega_vector = np.zeros(nt, dtype=np.double) # Record initial set-point set_point = set_point * rf_station.C_Pointer.fpga.set_point # Run Numerical Simulation for i in xrange(0, nt): if detuning: elecMode_state.delta_omega = delta_omega_vector[i] # Adjust the set-point if feedforward is on if (i == (beam_start - 0.5e-6) / Tstep) & feedforward: rf_station.C_Pointer.fpga.set_point = rf_station.C_Pointer.fpga.set_point - 209 * fund_k_probe if (i == (beam_start + (n_pulses - 0.5) * 1e-6) / Tstep) & feedforward: rf_station.C_Pointer.fpga.set_point = rf_station.C_Pointer.fpga.set_point + 209 * fund_k_probe # Run RF Station time-series simulation cav_v[i] = acc.RF_Station_Step(rf_station.C_Pointer, 0.0, beam_current[i], feed_forward[i], rf_station.State) # Record signals of interest error[i] = rf_station.State.fpga_state.err E_probe[i] = rf_station.State.cav_state.E_probe E_reverse[i] = rf_station.State.cav_state.E_reverse E_fwd[i] = rf_station.State.cav_state.E_fwd # Instantiate Signal object to provide caller with simulation results signal = Signal(E_fwd, E_reverse, E_probe, cav_v, set_point, beam_current, error, fund_mode_dict, trang) return signal
def station_analytical(test_file, configuration, margin=False, plot_tfs=False): """ Run the stability analysis for a RF Station configuration. """ # Import JSON parser module import numpy as np from matplotlib import pyplot as plt from get_configuration import Get_SWIG_RF_Station # Configuration file for specific test configuration # (to be appended to standard test cavity configuration) rf_station, Tstep, fund_mode_dict = Get_SWIG_RF_Station(test_file, Verbose=False) # Build a linear-scale frequency vector and Laplace operator frequency_vector = np.linspace(1, 1e6, 1e6) s = 1j * 2.0 * np.pi * frequency_vector # Cavity wc = fund_mode_dict['bw'] cavity = 1.0 / (1.0 + s / wc) # Loop delay T = rf_station.loop_delay_size['value'] * Tstep delay = np.exp(-s * T) # Plant (cavity + delay) plant = cavity * delay # SSA noise-limiting Low-pass filter low_pass_pole = 2 * np.pi * complex(rf_station.ns_filter_bw['value']) lowpass = 1.0 / (1.0 + s / low_pass_pole) # Unity vector unity = np.ones(len(s)) # Controller stable_gbw = rf_station.controller.stable_gbw['value'] # [Hz] control_zero = rf_station.controller.control_zero['value'] # [Hz] # Calculate corresponding values for ki and kp Kp = stable_gbw * 2 * np.pi / wc Ki = Kp * (2 * np.pi * control_zero) # Calculate transfer functions PI = (Kp + Ki / s) controller = PI * lowpass noise_gain = 1.0 / (1 + plant * controller) loop = plant * controller closed_loop = cavity / (1 + plant * controller) # Plot gain and phase margin (open-loop analysis) if margin: GM, PM, f_c, f_180 = margins(loop, frequency_vector, configuration) plt.show() # Plot loop transfer functions if plot_tfs: plt.semilogx(frequency_vector, 20.0 * np.log10(np.abs(plant)), linewidth="3", label='Plant') plt.semilogx(frequency_vector, 20.0 * np.log10(np.abs(controller)), linewidth="3", label='Controller') plt.semilogx(frequency_vector, 20.0 * np.log10(np.abs(loop)), linewidth="3", label='Loop') plt.semilogx(frequency_vector, 20.0 * np.log10(np.abs(noise_gain)), linewidth="3", label='Noise Gain') plt.semilogx(frequency_vector, 20.0 * np.log10(np.abs(unity)), linewidth="3", label='Unity', linestyle='dotted') title = 'RF Station Analytical (%s)' % (configuration) plt.title(title, fontsize=40, y=1.02) plt.ylabel('Magnitude [dB]', fontsize=30) plt.xlabel('Frequency [Hz]', fontsize=30) plt.legend(loc='upper right') plt.xticks(fontsize=20) plt.yticks(fontsize=20) plt.rc('font', **{'size': 20}) plt.show()
def run_RF_Station_test(Tmax, test_file): # Import JSON parser module from get_configuration import Get_SWIG_RF_Station # Configuration file for specific test configuration # (to be appended to standard test cavity configuration) rf_station, Tstep, fund_mode_dict = Get_SWIG_RF_Station(test_file, Verbose=False) # Create time vector trang = np.arange(0, Tmax, Tstep) # Number of points nt = len(trang) # Initialize vectors for test E_probe = np.zeros(nt, dtype=np.complex) E_reverse = np.zeros(nt, dtype=np.complex) E_fwd = np.zeros(nt, dtype=np.complex) set_point = np.zeros(nt, dtype=np.complex) # Run Numerical Simulation for i in xrange(1, nt): cav_v = acc.RF_Station_Step(rf_station.C_Pointer, 0.0, 0.0, 0.0, rf_station.State) set_point[i] = rf_station.C_Pointer.fpga.set_point E_probe[i] = rf_station.State.cav_state.E_probe E_reverse[i] = rf_station.State.cav_state.E_reverse E_fwd[i] = rf_station.State.cav_state.E_fwd fund_k_probe = fund_mode_dict['k_probe'] fund_k_drive = fund_mode_dict['k_drive'] fund_k_em = fund_mode_dict['k_em'] plt.plot(trang * 1e3, np.abs(E_reverse / fund_k_em), '-', label=r'Reverse $\left(\vec E_{\rm reverse}\right)$', linewidth=2) plt.plot(trang * 1e3, np.abs(E_fwd) * fund_k_drive, '-', label=r'Forward $\left(\vec E_{\rm fwd}\right)$', linewidth=2) plt.plot(trang * 1e3, np.abs(set_point / fund_k_probe), label=r'Set-point $\left(\vec E_{\rm sp}\right)$', linewidth=2) plt.plot(trang * 1e3, np.abs(E_probe / fund_k_probe), '-', label=r'Probe $\left(\vec E_{\rm probe}\right)$', linewidth=2) plt.title('RF Station Test', fontsize=40, y=1.01) plt.xlabel('Time [ms]', fontsize=30) plt.ylabel('Amplitude [V]', fontsize=30) plt.legend(loc='upper right') plt.rc('font', **{'size': 25}) # Show plot plt.show()