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()
Example #2
0
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
Example #3
0
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()