Пример #1
0
def run(input_signal_data, parameters_input, settings):
    '''==PROJECT SETTINGS==========================================='''
    module_name = 'Channel Filter'
    n = settings['num_samples']
    n = int(round(n))
    iteration = settings['current_iteration']
    iterations = settings['iterations']
    segments = settings['feedback_segments']
    segment = settings['feedback_current_segment']
    feedback_mode = settings['feedback_enabled']
    time = settings['time_window']
    fs = settings['sampling_rate']
    t_step = settings['sampling_period']

    # Status message - initiation of fb_script (Sim status panel & Info/Status window)
    fb_title_string = 'Running ' + str(module_name) + ' - Iteration #: ' + str(
        iteration)
    config.status_message(fb_title_string)

    # Data display - title of current fb_script
    # display_data(text, data, print data to second line?, apply bold font?)
    config.display_data(' ', ' ', False, False)
    fb_data_string = 'Data output for ' + str(module_name) + ' - Iteration #: '
    config.display_data(fb_data_string, iteration, False, True)
    '''==PARAMETERS================================================'''
    ctr_freq_1 = float(parameters_input[1][1]) * 1e12
    bw = float(parameters_input[3][1]) * 1e9
    profile = str(parameters_input[4][1])
    sigma_calc = str(parameters_input[5][1])
    sigma = float(parameters_input[6][1]) * 1e9
    pwr_gauss = float(parameters_input[7][1])
    display_filter_profile = int(parameters_input[9][1])
    passband = float(parameters_input[10][1]) * 1e9  # GHz -> Hz
    graph_units = str(parameters_input[11][1])
    freq_start = float(parameters_input[12][1]) * 1e12  # THz -> Hz
    freq_end = float(parameters_input[13][1]) * 1e12  # THz -> Hz
    ref_key = int(parameters_input[14][1])
    # Constants
    pi = constants.pi
    c = constants.c
    '''==INPUT SIGNALS=============================================='''
    # Load optical group data from input port
    signal_type = 'Optical'
    time_array = input_signal_data[0][3]  # Sampled time array
    psd_array = input_signal_data[0][4]  # Noise groups
    opt_channels = input_signal_data[0][5]  #Optical channel list

    # Load frequency, jones vector, signal & noise field envelopes for each optical channel
    channels = len(opt_channels)
    wave_key = np.empty(channels)
    wave_freq = np.empty(channels)
    jones_vector = np.full([channels, 2], 0 + 1j * 0, dtype=complex)
    if opt_channels[0][3].ndim == 2:  # Polarization format: Ex-Ey
        opt_field_rcv = np.full([channels, 2, n], 0 + 1j * 0, dtype=complex)
    else:  # Polarization format: Exy
        opt_field_rcv = np.full([channels, n], 0 + 1j * 0, dtype=complex)
    noise_field_rcv = np.full([channels, n], 0 + 1j * 0, dtype=complex)
    for ch in range(0, channels):  #Load wavelength channels
        wave_key[ch] = opt_channels[ch][0]
        wave_freq[ch] = opt_channels[ch][1]
        jones_vector[ch] = opt_channels[ch][2]
        opt_field_rcv[ch] = opt_channels[ch][3]
        noise_field_rcv[ch] = opt_channels[ch][4]
    '''==CALCULATIONS=============================================='''
    # Initialize output fields (for 4 ports)
    if opt_channels[0][3].ndim == 2:  # Polarization format: Ex-Ey
        opt_field_out_1 = np.full([channels, 2, n], 0 + 1j * 0, dtype=complex)
    else:  # Polarization format: Exy
        opt_field_out_1 = np.full([channels, n], 0 + 1j * 0, dtype=complex)
    noise_field_out = np.full([channels, n], 0 + 1j * 0, dtype=complex)

    # Prepare freq array (sampled signals)
    T = n / fs
    k = np.arange(n)
    frq = k / T  # Positive/negative freq (double sided)
    wave = np.empty(channels)

    bw_3_db = 'NA'
    bw_1_db = 'NA'
    bw_half_db = 'NA'
    bw_isolation = 'NA'
    adj_isolation_ratio = 'NA'
    """Apply port filtering to all input channels-----------------------------------------"""
    for ch in range(0, channels):
        config.status_message('Applying port filtering to channel: ' +
                              str(ch) + ' (' + str(wave_freq[ch] * 1e-12) +
                              ' THz)')
        # Frequency array adjusted to center frequency of optical channel
        frq = frq - frq[int(round(n / 2))] + wave_freq[ch]
        # Apply FFT (time -> freq domain)
        if opt_channels[0][3].ndim == 2:  # Polarization format: Ex-Ey
            Y_x_1 = np.fft.fft(opt_field_rcv[ch, 0])
            Y_x_1 = np.fft.fftshift(Y_x_1)
            Y_y_1 = np.fft.fft(opt_field_rcv[ch, 1])
            Y_y_1 = np.fft.fftshift(Y_y_1)
        else:
            Y_1 = np.fft.fft(opt_field_rcv[ch])
            Y_1 = np.fft.fftshift(Y_1)
        N = np.fft.fftshift(np.fft.fft(noise_field_rcv[ch]))
        """Apply transfer function to field envelope of channel---------------"""
        if profile == 'Rectangular':
            tr_fcn_filt_1 = rect_profile(frq, ctr_freq_1, bw)
        elif profile == 'Gaussian':
            if sigma_calc == 'Direct':
                sig_filter = sigma
            else:
                sig_filter = bw / (2 * np.sqrt(2 * np.log2(2)))
            tr_fcn_filt_1 = gaussian_profile(frq, ctr_freq_1, sig_filter)
        elif profile == 'Super-Gaussian':
            if sigma_calc == 'Direct':
                sig_filter = sigma
            else:
                sig_filter = bw / (2 * np.sqrt(2 * np.log2(2)))
            tr_fcn_filt_1 = super_gaussian_profile(frq, ctr_freq_1, sig_filter,
                                                   pwr_gauss)

        if opt_channels[0][3].ndim == 2:  # Polarization format: Ex-Ey
            Y_x_1 = Y_x_1 * tr_fcn_filt_1
            Y_y_1 = Y_y_1 * tr_fcn_filt_1
        else:
            Y_1 = Y_1 * tr_fcn_filt_1
        N = N * tr_fcn_filt_1

        # Apply IFFT (freq -> time domain)
        if opt_channels[0][3].ndim == 2:  # Polarization format: Ex-Ey
            opt_field_out_1[ch, 0] = np.fft.ifft(np.fft.ifftshift(Y_x_1))
            opt_field_out_1[ch, 1] = np.fft.ifft(np.fft.ifftshift(Y_y_1))
        else:
            opt_field_out_1[ch] = np.fft.ifft(np.fft.ifftshift(Y_1))
        noise_field_out[ch] = np.fft.ifft(np.fft.ifftshift(N))
    """Perform filter analysis (passband, transfer function graph, isolation)--------------------"""
    # Build pass band profile for all channels
    freq_array = np.arange(freq_start, freq_end, 0.5e9)
    if profile == 'Rectangular':
        tr_fcn_filt_1 = rect_profile(freq_array, ctr_freq_1, bw)
    elif profile == 'Gaussian':
        if sigma_calc == 'Direct':
            sig_filter = sigma
        else:
            sig_filter = bw / (2 * np.sqrt(2 * np.log2(2)))
        tr_fcn_filt_1 = gaussian_profile(freq_array, ctr_freq_1, sig_filter)
    elif profile == 'Super-Gaussian':
        if sigma_calc == 'Direct':
            sig_filter = sigma
        else:
            sig_filter = bw / (2 * np.sqrt(2 * np.log2(2)))
        tr_fcn_filt_1 = super_gaussian_profile(freq_array, ctr_freq_1,
                                               sig_filter, pwr_gauss)

    total_profile = [np.square(np.abs(tr_fcn_filt_1))]
    ctr_freqs = [ctr_freq_1]

    # BW calculations------------------------------------------------------------
    power_profile_p1 = total_profile[0]
    i_max = np.argmax(power_profile_p1)
    power_profile_p1 = power_profile_p1[0:i_max + 1]
    frq_3_db = np.interp(0.5, power_profile_p1, freq_array[:i_max + 1])
    frq_1_db = np.interp(0.79432, power_profile_p1, freq_array[:i_max + 1])
    frq_half_db = np.interp(0.89125, power_profile_p1, freq_array[:i_max + 1])
    bw_3_db = 2 * 1e-9 * np.abs(ctr_freq_1 - frq_3_db)
    bw_1_db = 2 * 1e-9 * np.abs(ctr_freq_1 - frq_1_db)
    bw_half_db = 2 * 1e-9 * np.abs(ctr_freq_1 - frq_half_db)
    # Create plot instance
    if display_filter_profile == 2:
        config.demux_filter_graph = config.view.Demux_Analyzer(
            'Filter transfer (all ports)', freq_array, 'Frequency (Hz)',
            total_profile, 'Transmission - power', wave_freq, wave_key,
            freq_start, freq_end, ctr_freqs, passband / 2, graph_units,
            ref_key)
        config.demux_filter_graph.show()
    '''==OUTPUT PARAMETERS LIST======================================='''
    opt_filter_parameters = []
    opt_filter_parameters = parameters_input
    '''==RESULTS==================================================='''
    results = []
    '''bw_result_3_dB = ['3 dB passband', bw_3_db, 'GHz', ' ', False, '0.2f']
    bw_result_1_dB = ['1 dB passband', bw_1_db, 'GHz', ' ', False, '0.2f']
    bw_result_half_dB = ['0.5 dB passband', bw_half_db, 'GHz', ' ', False, '0.2f']
    results.extend([bw_result_3_dB, bw_result_1_dB, bw_result_half_dB, adj_isolation_result ])'''
    '''==RETURN (Output Signals, Parameters, Results)========================================='''
    optical_channels_1 = []
    for ch in range(0, channels):
        opt_ch_1 = [
            int(wave_key[ch]), wave_freq[ch], jones_vector[ch],
            opt_field_out_1[ch], noise_field_out[ch, :]
        ]
        optical_channels_1.append(opt_ch_1)

    return ([[2, signal_type, fs, time_array, psd_array,
              optical_channels_1]], opt_filter_parameters, results)
Пример #2
0
def run(input_signal_data, parameters_input, settings):
    
    '''==PROJECT SETTINGS==================================================='''
    module_name = 'Laser Source'
    n = settings['num_samples']
    n = int(round(n))
    iteration = settings['current_iteration']
    time = settings['time_window']
    fs = settings['sampling_rate']
    t_step = settings['sampling_period']

    # Status message - initiation of fb_script (Sim status panel & Info/Status window)
    fb_title_string = 'Running ' + str(module_name) + ' - Iteration #: ' + str(iteration)
    config.status_message(fb_title_string)

    # Data display - title of current fb_script
    config.display_data(' ', ' ', False, False)
    fb_data_string = 'Data output for ' + str(module_name) + ' - Iteration #: '
    # Display data settings: Data title (str), Data (scalar, array, etc), 
    # Set to Bold?, Title & Data on separate lines?
    config.display_data(fb_data_string, iteration, False, True)    
    
    '''==INPUT PARAMETERS========================================================='''
    # Load parameters from FB parameters table
    # Parameter name(0), Value(1), Units(2), Notes(3)   
    # Main settings
    key =  int(parameters_input[1][1])
    wave_units = str(parameters_input[2][1])
    wavelength = float(parameters_input[3][1]) #Optical wavelength (nm or THz)
    if wave_units == 'Frequency (THz)':
        wavelength = 1e-3*constants.c/wavelength
    pwr_units = str(parameters_input[4][1])
    optical_pwr = float(parameters_input[5][1]) #Optical power (mW/dBm)
    if pwr_units == 'dBm':
        optical_pwr = np.power(10, optical_pwr/10)
    # Optical phase settings (header) - MV 20.01.v3 16-Sep-20
    phase_deg = float(parameters_input[7][1]) #Optical phase (deg)
    include_phase_noise = int(parameters_input[8][1])
    line_width = float(parameters_input[9][1]) #Laser linewidth (MHz)
    # Relative intensity noise (header)
    ref_bw = float(parameters_input[11][1]) #Reference bandwidth (Hz)
    rin = float(parameters_input[12][1]) #RIN (dB/Hz)
    include_rin = int(parameters_input[13][1]) #Include RIN in model
    add_rin_to_signal = int(parameters_input[14][1]) #Add RIN to signal
    # Polarization settings (header)
    pol_azimuth = float(parameters_input[16][1])
    pol_ellipticity = float(parameters_input[17][1])
    e_field_format = str(parameters_input[18][1])
    # Frequency domain noise groups (header)
    psd_freq = float(parameters_input[20][1])
    ng = int(parameters_input[21][1])
    freq_start = float(parameters_input[22][1])
    freq_end = float(parameters_input[23][1])
    include_ase = int(float(parameters_input[24][1]))
    add_ase_to_signal = int(float(parameters_input[25][1]))

    # Additional parameters
    signal_type = 'Optical'
    wave_key = key
    wave_freq = constants.c/(wavelength*1e-9) #Hz
    
    '''==CALCULATIONS======================================================='''
    # Polarization settings
    pol_azimuth_rad = (pol_azimuth/180)*np.pi
    pol_ellipticity_rad = (pol_ellipticity/180)*np.pi
    jones_vector = ([np.cos(pol_azimuth_rad)*np.cos(pol_ellipticity_rad) - 
                     1j*np.sin(pol_azimuth_rad)*np.sin(pol_ellipticity_rad),  
                     np.sin(pol_azimuth_rad)*np.cos(pol_ellipticity_rad) + 
                     1j*np.cos(pol_azimuth_rad)*np.sin(pol_ellipticity_rad)])

    # Prepare initial electrical field definition for optical signal
    time_array = np.linspace(0, time, n)
    e_field_array = np.full(n, np.sqrt(optical_pwr*1e-3))
    
    #Add RIN to field values (if selected) - Ref 1, Eq 4.127
    noise_array = np.full(n, 0 + 1j*0, dtype=complex)
    if include_rin == 2:
        rin_linear = np.power(10, rin/10)
        noise_pwr = rin_linear * ref_bw * (optical_pwr*1e-3)**2
        #Penalty (noise var) at receiver is: 2*(RP1)^2*RIN*BW
        sigma_field = np.sqrt(np.sqrt(noise_pwr))
        noise_array_rin = np.random.normal(0, sigma_field , n)
        noise_array = noise_array_rin + 1j*0
        if add_rin_to_signal == 2:
            e_field_array = e_field_array + noise_array_rin
            noise_array += -noise_array_rin
    
    # Initialize electric field arrays
    # Slowly varying envelope approximation ( E(z,t) = Eo(z, t)*exp(i(kz - wt)) )
    # e_field_env = E(z,t); w is carried separately as wave_freq = c/optical wavelength
    phase_rad = (phase_deg/180)*np.pi
    e_field_array_real = np.zeros(n)
    e_field_array_imag = np.zeros(n)
    if e_field_format == 'Exy':
        e_field_env = np.full(n, 0 + 1j*0, dtype=complex)
    else:
        e_field_env = np.full([2, n], 0 + 1j*0, dtype=complex) 
    
    #Add intial phase setting to complex envelope(s)
    for i in range(0,n): 
        e_field_array_real[i] = e_field_array[i]*np.cos(phase_rad)
        e_field_array_imag[i] = e_field_array[i]*np.sin(phase_rad)
        if e_field_format == 'Exy':
            e_field_env[i] = e_field_array_real[i] + 1j*e_field_array_imag[i]  
        else:
            e_field_env[0][i] = e_field_array_real[i] + 1j*e_field_array_imag[i]
            e_field_env[1][i] = e_field_array_real[i] + 1j*e_field_array_imag[i] 
        
    # Prepare noise groups (freq domain)
    freq_delta = freq_end - freq_start
    ng_w = freq_delta/ng
    freq_points = np.linspace(freq_start + (ng_w/2), freq_end - (ng_w/2), ng)
    psd_points = np.full(ng, psd_freq)
    psd_array = np.array([freq_points, psd_points])
    
    # Add phase noise to field envelope (Brownian randon walk) - Ref 1, Eq. 4.7
    # MV 20.01.r3 16-Sep-20 Model can now be enabled/disabled
    if include_phase_noise == 2:
        phase_sigma = np.sqrt(np.pi*2*line_width*1e6)
        phase = phase_rad
        phase_array = np.full(n, phase)
        for i in range(1, n):
            phase_walk = np.random.normal(0, phase_sigma)*np.sqrt(t_step)
            phase_array[i] = phase_array[i-1] + phase_walk
            e_field_array_real[i] = e_field_array[i]*np.cos(phase_array[i])
            e_field_array_imag[i] = e_field_array[i]*np.sin(phase_array[i])
            if e_field_format == 'Exy':
                e_field_env[i] = e_field_array_real[i] + 1j*e_field_array_imag[i]
            else:
                e_field_env[0][i] = e_field_array_real[i] + 1j*e_field_array_imag[i]
                e_field_env[1][i] = e_field_array_real[i] + 1j*e_field_array_imag[i]
    
    # Apply Jones vector to Ex and Ey complex arrays
    if e_field_format == 'Ex-Ey':
        e_field_env[0] = jones_vector[0]*e_field_env[0]
        e_field_env[1] = jones_vector[1]*e_field_env[1]
            
    # Integrate ase noise with time-domain noise?
    if include_ase == 2:
        T = n/fs
        k = np.arange(n)
        frq = k/T
        frq = frq - frq[int(round(n/2))] + wave_freq
        pwr_ase = 0
        noise_array_ase = np.full(n, 0 + 1j*0, dtype=complex)
        for i in range(0, ng):
            if psd_array[0, i] > frq[0] and psd_array[0, i] < frq[n-1]:
                pwr_ase += psd_array[1, i]*ng_w
        #Convert to time-domain noise
        sigma_ase = np.sqrt(pwr_ase/2)
        noise_ase_real = np.random.normal(0, sigma_ase , n)
        noise_ase_imag = np.random.normal(0, sigma_ase , n)
        noise_array_ase = noise_ase_real + 1j*noise_ase_imag
        noise_array += noise_array_ase
        # Add noise to time domain signal and remove from noise array
        if add_ase_to_signal == 2:
            if e_field_format == 'Exy':
                e_field_env += noise_array_ase
            else:
                e_field_env[0] += noise_array_ase*jones_vector[0]
                e_field_env[1] += noise_array_ase*jones_vector[1]
            noise_array += -noise_array_ase
        # Set psd_array points to zero (that have been converted to time domain)
        for i in range(0, ng):
            if psd_array[0, i] > frq[0] and psd_array[0, i] < frq[n-1]:
                psd_array[1, i] = 1e-30 #Set to very low value
                    
    '''==OUTPUT PARAMETERS LIST===========================================================
    '''
    laser_parameters = []
    laser_parameters = parameters_input
  
    '''==RESULTS==========================================================================
    '''
    laser_results = []
    # MV 20.01.r3 4-Jun-20 
    # Corrected dBm pwr calculation - removed index for e_field_env
    laser_pwr = np.sum(np.abs(e_field_env)*np.abs(e_field_env))/n
    laser_pwr_dbm = 10*np.log10(laser_pwr*1e3)
    spectral_linewidth = ((wavelength*1e-9)**2)*(line_width*1e6)/constants.c
    
    header_main_results = ['General data', '', '', '', True]
    laser_pwr_dbm_result = ['Laser power (dBm)', laser_pwr_dbm, 'dBm', ' ', False]
    laser_frequency_result = ['Laser frequency (THz)', wave_freq*1e-12, 'THz', ' ', False, '3.5f']
    spectral_linewidth_result = ['Laser linewidth (nm)', spectral_linewidth*1e9, 'nm', ' ', False]
    header_pol_results = ['Polarization data', '', '', '', True]
    azimuth_result = ['Polarization (azimuth)', pol_azimuth, 'deg', ' ', False]
    ellipticity_result = ['Polarization (ellipticity)', pol_ellipticity, 'deg', ' ', False]
    laser_results = [header_main_results, laser_pwr_dbm_result, laser_frequency_result, 
                             spectral_linewidth_result, header_pol_results, azimuth_result, ellipticity_result]

    '''==RETURN (Output Signals, Parameters, Results)=================================='''
    optical_1 = [wave_key, wave_freq, jones_vector, e_field_env, noise_array]
    optical = [optical_1]
    
    return ([[1, signal_type, fs, time_array, psd_array, optical]], laser_parameters, laser_results)
Пример #3
0
def run(input_signal_data, parameters_input, settings):
    
    '''==PROJECT SETTINGS===================================================='''
    module_name = 'Measurement Node (Optical)'
    # Main settings
    n = settings['num_samples'] # Total samples for simulation
    n = int(round(n))
    time = settings['time_window'] # Time window for simulation (sec)
    fs = settings['sampling_rate'] # Sample rate (default - Hz)
    f_sym = settings['symbol_rate'] # Symbol rate (default - Hz)
    samples_sym = settings['samples_per_sym'] # Samples per symbol
    t_step = settings['sampling_period'] # Sample period (Hz)
    # Iteration settings
    iteration = settings['current_iteration'] # Current iteration loop for simulation
    i_total = settings['iterations'] # Total iterations for simulation
    # Feedback settings
    segments = settings['feedback_segments'] # Number of integration segments
    segment = settings['feedback_current_segment'] # Current integration segment
    segment = int(round(segment))
    samples_segment = settings['samples_per_segment'] #Samples per feedback segment
    feedback_mode = settings['feedback_enabled'] #Feedback mode is enabled(2)/disabled(0)
    
    '''==Status message (send to Simulation status panel)==============================='''
    # Status message - initiation of fb_script (Sim status panel & Info/Status window)
    fb_title_string = 'Running ' + str(module_name) + ' - Iteration #: ' + str(iteration)
    config.status_message(fb_title_string)

    # Data display - title of current fb_script
    config.display_data(' ', ' ', False, False)
    fb_data_string = 'Data output for ' + str(module_name) + ' - Iteration #: '
    config.display_data(fb_data_string, iteration, False, True)  # When True, string & data printed on separate lines                                 
                                          
    '''==INPUT PARAMETERS====================================================='''
    # Load parameters from FB parameters table [row][column]
    carrier_data_setting = str(parameters_input[1][1])
    pwr_units = str(parameters_input[2][1])
    display_x_y = int(parameters_input[4][1])
    display_x = int(parameters_input[5][1])
    display_y = int(parameters_input[6][1])
    display_osnr = int(parameters_input[7][1])
    display_photons = int(parameters_input[8][1])
    osnr_bw = float(parameters_input[10][1])*1e9 # Convert from GHz to Hz
    time_period = float(parameters_input[12][1])
    
    '''==INPUT SIGNALS=====================================================
    Optical_signal: portID(0), sig_type(1), fs(2), time_array(3), psd_array(4), optical_group(5)
    Optical_channel(s): wave_key(0), wave_freq(1), jones_vector(2), e_field_array(3), noise_array(4)
    '''
    # Load optical group data from input port
    signal_type = input_signal_data[0][1]
    time_array = input_signal_data[0][3] 
    psd_array = input_signal_data[0][4] 
    opt_channels = input_signal_data[0][5] 
    # Load frequency, jones vector, signal & noise field envelopes for each optical channel
    channels = len(opt_channels)
    wave_key = np.empty(channels)
    wave_freq = np.empty(channels)
    jones_vector = np.full([channels, 2], 0 + 1j*0, dtype=complex) 
    pol_format = 'Exy'
    if opt_channels[0][3].ndim == 2: # Polarization format: Ex-Ey
        pol_format = 'Ex-Ey'
        opt_field_rcv = np.full([channels, 2, n], 0 + 1j*0, dtype=complex) 
    else: # Polarization format: Exy
        opt_field_rcv = np.full([channels, n], 0 + 1j*0, dtype=complex)
    noise_field_rcv = np.full([channels, n], 0 + 1j*0, dtype=complex) 
    for ch in range(0, channels): #Load wavelength channels
        wave_key[ch] = opt_channels[ch][0]
        wave_freq[ch] = opt_channels[ch][1]
        jones_vector[ch] = opt_channels[ch][2]
        opt_field_rcv[ch] = opt_channels[ch][3]
        noise_field_rcv[ch] = opt_channels[ch][4]
    
    '''==CALCULATIONS/RESULTS================================================'''
    # Initialize results and data panel lists
    meaure_node_results = []
    #config.data_tables['opt_meas_1'] = []
    num_format_1 = '0.4E'
    num_format_2 = '0.0f'
    num_format_3 = '0.4f'
    for ch in range(0, channels):
        # Prepare and add carrier data to results list
        wavelength = c/wave_freq[ch]
        if carrier_data_setting == 'Frequency':
            title_text = ('Optical metrics (THz) - Ch '+ str(ch+1) + ' (' 
                               + str(format(wave_freq[ch]*1e-12, '3.3f')) + ')')
        else:
            title_text = ('Optical metrics (nm) - Ch ' + str(ch+1) + ' (' 
                               + str(format(wavelength*1e9, '4.2f')) + ')')
        carrier_result =  [title_text, '', '', '', True]
        meaure_node_results.append(carrier_result)
        carrier_data_panel =  [title_text, '', '', '']
        #config.data_tables['opt_meas_1'].append(carrier_data_panel)
        # Calculate and add signal metrics to results list
        # X/Y-polarization data
        if display_x_y == 2: 
            tot_sig_pwr, avg_sig_pwr, ph_rate = calculate_signal_metrics('x-y', pol_format, opt_field_rcv[ch],
                                                                                                    jones_vector[ch], wave_freq[ch], n, time_period)
            osnr = calculate_osnr_metrics('x-y', pol_format, opt_field_rcv[ch], noise_field_rcv[ch], jones_vector[ch], 
                                                         wave_freq[ch], n, fs, osnr_bw)
            photons_per_bit = ph_rate
            # Results list
            if pwr_units == 'W':
                data_1 = ['Total signal power (X+Y)', tot_sig_pwr, 'W', '', False, num_format_1]
                data_2 = ['Avg signal power (X+Y)', avg_sig_pwr, 'W', '', False, num_format_1]
            else:
                tot_sig_pwr = 10*np.log10(tot_sig_pwr*1e3)
                avg_sig_pwr = 10*np.log10(avg_sig_pwr*1e3)
                data_1 = ['Total signal power (X+Y)', tot_sig_pwr, 'dBm', '', False, num_format_3]
                data_2 = ['Avg signal power (X+Y)', avg_sig_pwr, 'dBm', '', False, num_format_3]
            data_list = [data_1, data_2]
            if display_photons == 2:
                data_3 = ['Avg photons/bit (X+Y)', ph_rate, '', '', False, num_format_2]
                data_list.append(data_3)
            if display_osnr == 2:
                if osnr == 'NA':
                    data_4 = ['OSNR (X+Y)', osnr, '', '', False]
                else:
                    data_4 = ['OSNR (X+Y)', osnr, 'dB', '', False, num_format_3]
                data_list.append(data_4)
            meaure_node_results.extend(data_list)
            '''# Data panel list
            data_1 = ['Total signal power (X+Y)', tot_sig_pwr, num_format_1, 'W']
            data_2 = ['Average signal power (X+Y)',  avg_sig_pwr, num_format_1, 'W']
            data_3 = ['Avg photons/bit (X+Y)', ph_rate, num_format_2, '']
            data_list = [data_1, data_2, data_3]
            config.data_tables['opt_meas_1'] .extend(data_list)'''
        # X-polarization data
        if display_x == 2: 
            tot_sig_pwr, avg_sig_pwr, ph_rate = calculate_signal_metrics('x', pol_format, opt_field_rcv[ch],
                                                                                                    jones_vector[ch], wave_freq[ch], n, time_period)
            osnr = calculate_osnr_metrics('x', pol_format, opt_field_rcv[ch], noise_field_rcv[ch], jones_vector[ch], 
                                                         wave_freq[ch], n, fs, osnr_bw)
            # Results list
            if pwr_units == 'W':
                data_1 = ['Total signal power (X)', tot_sig_pwr, 'W', '', False, num_format_1]
                data_2 = ['Avg signal power (X)', avg_sig_pwr, 'W', '', False, num_format_1]
            else:
                tot_sig_pwr = 10*np.log10(tot_sig_pwr*1e3)
                avg_sig_pwr = 10*np.log10(avg_sig_pwr*1e3)
                data_1 = ['Total signal power (X)', tot_sig_pwr, 'dBm', '', False, num_format_3]
                data_2 = ['Avg signal power (X)', avg_sig_pwr, 'dBm', '', False, num_format_3]
            data_list = [data_1, data_2]
            if display_photons == 2:
                data_3 = ['Avg photons/bit (X)', ph_rate, '', '', False, num_format_2]
                data_list.append(data_3)
            if display_osnr == 2:
                if osnr == 'NA':
                    data_4 = ['OSNR (X)', osnr, '', '', False]
                else:
                    data_4 = ['OSNR (X)', osnr, 'dB', '', False, num_format_3]
                data_list.append(data_4)
            meaure_node_results.extend(data_list)
            '''# Data panel list
            data_1 = ['Total signal power (X)', tot_sig_pwr, num_format_1, 'W']
            data_2 = ['Average signal power (X)',  avg_sig_pwr, num_format_1, 'W']
            data_3 = ['Avg photons/bit (X)', ph_rate, num_format_2, '']
            data_list = [data_1, data_2, data_3]
            config.data_tables['opt_meas_1'] .extend(data_list)'''
        # Y-polarization data
        if display_y == 2: 
            tot_sig_pwr, avg_sig_pwr, ph_rate = calculate_signal_metrics('y', pol_format, opt_field_rcv[ch],
                                                                                                    jones_vector[ch], wave_freq[ch], n, time_period)       
            osnr = calculate_osnr_metrics('y', pol_format, opt_field_rcv[ch], noise_field_rcv[ch], jones_vector[ch], 
                                                           wave_freq[ch], n, fs, osnr_bw)
            # Results list
            if pwr_units == 'W':
                data_1 = ['Total signal power (Y)', tot_sig_pwr, 'W', '', False, num_format_1]
                data_2 = ['Avg signal power (Y)', avg_sig_pwr, 'W', '', False, num_format_1]
            else:
                tot_sig_pwr = 10*np.log10(tot_sig_pwr*1e3)
                avg_sig_pwr = 10*np.log10(avg_sig_pwr*1e3)
                data_1 = ['Total signal power (Y)', tot_sig_pwr, 'dBm', '', False, num_format_3]
                data_2 = ['Avg signal power (Y)', avg_sig_pwr, 'dBm', '', False, num_format_3]
            data_list = [data_1, data_2]
            if display_photons == 2:
                data_3 = ['Avg photons/bit (Y)', ph_rate, '', '', False, num_format_2]
                data_list.append(data_3)
            if display_osnr == 2:
                if osnr == 'NA':
                    data_4 = ['OSNR (Y)', osnr, '', '', False]
                else:
                    data_4 = ['OSNR (Y)', osnr, 'dB', '', False, num_format_3]
                data_list.append(data_4)
            meaure_node_results.extend(data_list)
            '''# Data panel list
            data_1 = ['Total signal power (Y)', tot_sig_pwr, num_format_1, 'W']
            data_2 = ['Average signal power (Y)',  avg_sig_pwr, num_format_1, 'W']
            data_3 = ['Avg photons/bit (Y)', ph_rate, num_format_2, '']
            data_list = [data_1, data_2, data_3]
            config.data_tables['opt_meas_1'] .extend(data_list)'''
    
    '''==OUTPUT PARAMETERS LIST========================================='''
    meaure_node_parameters = []
    meaure_node_parameters = parameters_input #If NO changes are made to parameters
    
    '''==RETURN (Output Signals, Parameters, Results)=============================='''
    optical_channels = []
    for ch in range(0, channels):
        opt_ch = [int(wave_key[ch]), wave_freq[ch], jones_vector[ch], opt_field_rcv[ch], noise_field_rcv[ch]]
        optical_channels.append(opt_ch)
    
    return ([[2, signal_type, fs, time_array, psd_array, optical_channels]], 
                 meaure_node_parameters, meaure_node_results)
def run(input_signal_data, parameters_input, settings):
    '''==PROJECT SETTINGS==================================================='''
    module_name = 'Time Shift (Electrical)'
    #Main settings
    n = settings['num_samples']  #Total samples for simulation
    n = int(round(n))
    fs = settings['sampling_rate']  #Sample rate (default - Hz)
    iteration = settings[
        'current_iteration']  #Current iteration loop for simulation
    """Status messages-----------------------------------------------------------------------"""
    # Status message - initiation of fb_script (Sim status panel & Info/Status window)
    fb_title_string = 'Running ' + str(module_name) + ' - Iteration #: ' + str(
        iteration)
    config.status_message(fb_title_string)
    # Data display - title of current fb_script
    config.display_data(' ', ' ', False, False)
    fb_data_string = 'Data output for ' + str(module_name) + ' - Iteration #: '
    # Display data settings: Data title (str), Data (scalar, array, etc),
    # Set to Bold?, Title & Data on separate lines?
    config.display_data(fb_data_string, iteration, False, True)
    '''==INPUT PARAMETERS========================================================='''
    #Load parameters from FB parameters table
    #Format: Parameter name(0), Value(1), Units(2), Notes(3)
    t_shift = float(parameters_input[0][1])  # Time shift (sec)
    '''==INPUT SIGNALS======================================================'''
    sig_type = input_signal_data[0][1]
    carrier = input_signal_data[0][2]
    time_array = input_signal_data[0][4]
    sig_t_shift = copy.deepcopy(input_signal_data[0][5])
    noise_t_shift = copy.deepcopy(input_signal_data[0][6])
    '''==CALCULATIONS======================================================='''
    time_samples_shift = int(np.round(t_shift * fs))
    t_residual = float(t_shift) - float(time_samples_shift * (1 / fs))
    if t_shift != 0:
        sig_t_shift = np.roll(sig_t_shift, time_samples_shift)
        noise_t_shift = np.roll(noise_t_shift, time_samples_shift)
        #sig_t_shift = np.interp(time_array + t_residual, time_array, sig_t_shift)
        #noise_t_shift = np.interp(time_array + t_residual, time_array, noise_t_shift)
    '''==OUTPUT PARAMETERS LIST============================================='''
    parameters_output = []
    parameters_output = parameters_input
    '''==RESULTS============================================================'''
    results = []
    results.append(['Time shift', t_shift, 's', ' ', False])

    if np.sign(time_samples_shift) == -1:
        results.append([
            'Shifted sample periods (left)',
            np.abs(time_samples_shift), 'samples', ' ', False, 'n'
        ])
    elif np.sign(time_samples_shift) == 1:
        results.append([
            'Shifted sample periods (right)',
            np.abs(time_samples_shift), 'samples', ' ', False, 'n'
        ])
    else:
        results.append([
            'Shifted sample periods',
            np.abs(time_samples_shift), 'samples', ' ', False, 'n'
        ])
    #results.append(['Time residual (interpolated)',  t_residual, 's', ' ', False])
    results.append(['Time residual (interpolated)', 'NA', ' ', ' ', False])
    '''==RETURN (Output Signals, Parameters, Results)=========================='''
    electrical_out = [
        2, sig_type, carrier, fs, time_array, sig_t_shift, noise_t_shift
    ]
    return ([electrical_out], parameters_output, results)
def run(input_signal_data, parameters_input, settings):
    '''==PROJECT SETTINGS===================================================
    '''
    module_name = settings['fb_name']
    #Main settings
    n = settings['num_samples']  #Total samples for simulation
    n = int(round(n))
    fs = settings['sampling_rate']  #Sample rate (default - Hz)
    iteration = settings[
        'current_iteration']  #Current iteration loop for simulation
    """Status messages-----------------------------------------------------------------------"""
    # Status message - initiation of fb_script (Sim status panel & Info/Status window)
    fb_title_string = 'Running ' + str(module_name) + ' - Iteration #: ' + str(
        iteration)
    config.status_message(fb_title_string)
    # Data display - title of current fb_script
    config.display_data(' ', ' ', False, False)
    fb_data_string = 'Data output for ' + str(module_name) + ' - Iteration #: '
    # Display data settings: Data title (str), Data (scalar, array, etc),
    # Set to Bold?, Title & Data on separate lines?
    config.display_data(fb_data_string, iteration, False, True)
    '''==INPUT PARAMETERS===================================================
    '''
    # Load parameters from FB parameters table
    # Main amplifier parameters (header)
    g_o_db = float(parameters_input[1][1])  #Small signal gain
    pwr_sat_dbm = float(parameters_input[2][1])  #Saturated output power (dBm)
    nf_db = float(parameters_input[3][1])  #Noise figure (optical)
    opt_bw = float(
        parameters_input[4][1]) * 1e12  #Optical amplifier bandwidth (THz->Hz)
    opt_freq = float(parameters_input[5]
                     [1]) * 1e12  #Center frequency of amplifier BW (THz->Hz)
    # Operating parameters (header)
    mode = str(parameters_input[7][1])
    gain_setting_db = float(parameters_input[8][1])
    pwr_setting_dbm = float(parameters_input[9][1])
    # Noise analysis (header)
    add_ase = int(float(parameters_input[11][1]))
    add_ase_to_signal = int(float(parameters_input[12][1]))
    bw_osnr_measurment = float(parameters_input[13][1]) * 1e9  # GHz -> Hz
    '''==INPUT SIGNALS======================================================
    '''
    # Load optical group data from input port
    signal_type = input_signal_data[0][1]
    time_array = input_signal_data[0][3]  # Sampled time array
    psd_array = input_signal_data[0][4]  # Noise groups
    opt_channels = input_signal_data[0][5]  #Optical channel list

    # Load frequency, jones vector, signal & noise field envelopes for each optical channel
    channels = len(opt_channels)
    wave_key = np.empty(channels)
    wave_freq = np.empty(channels)
    jones_vector = np.full([channels, 2], 0 + 1j * 0, dtype=complex)
    if opt_channels[0][3].ndim == 2:  # Polarization format: Ex-Ey
        opt_field_rcv = np.full([channels, 2, n], 0 + 1j * 0, dtype=complex)
    else:  # Polarization format: Exy
        opt_field_rcv = np.full([channels, n], 0 + 1j * 0, dtype=complex)
    noise_field_rcv = np.full([channels, n], 0 + 1j * 0, dtype=complex)
    for ch in range(0, channels):  #Load wavelength channels
        wave_key[ch] = opt_channels[ch][0]
        wave_freq[ch] = opt_channels[ch][1]
        jones_vector[ch] = opt_channels[ch][2]
        opt_field_rcv[ch] = opt_channels[ch][3]
        noise_field_rcv[ch] = opt_channels[ch][4]
    '''==CALCULATIONS=======================================================
    '''
    # Initialize output field arrays
    if opt_channels[0][3].ndim == 2:  # Polarization format: Ex-Ey
        opt_field_out = np.full([channels, 2, n], 0 + 1j * 0, dtype=complex)
    else:  # Polarization format: Exy
        opt_field_out = np.full([channels, n], 0 + 1j * 0, dtype=complex)
    noise_field_out = np.full([channels, n], 0 + 1j * 0, dtype=complex)

    # Calculate avg power + avg noise in (all channels) MV 20.01.r3 8-Jun-20------------------------------
    pwr_in = 0
    avg_ch_pwr_in = 0
    for ch in range(0, channels):
        pwr_in_ch = np.mean(np.square(np.abs(opt_field_rcv[ch])))
        noise_in_ch = np.mean(np.square(np.abs(noise_field_rcv[ch])))
        avg_ch_pwr_in += pwr_in_ch
        pwr_in += pwr_in_ch + noise_in_ch
    avg_ch_pwr_in = avg_ch_pwr_in / channels

    if pwr_in > 0:
        pwr_in_dbm = 10 * np.log10(pwr_in * 1e3)
    else:
        pwr_in_dbm = 'NA'
    if avg_ch_pwr_in > 0:
        avg_ch_pwr_in_dbm = 10 * np.log10(avg_ch_pwr_in * 1e3)
    else:
        avg_ch_pwr_in_dbm = 'NA'
    """Calculate amplifer gain------------------------------------------------------------------------"""
    g_o = np.power(10, g_o_db / 10)  # Linear gain
    pwr_sat = 1e-3 * np.power(10, pwr_sat_dbm / 10)

    # Calculate output saturation power (maximum output power of amplifier)
    pwr_out_sat = pwr_sat * np.log(2)  # Ref 1, Eq. 2.93
    pwr_out_sat_dbm = 10 * np.log10(pwr_out_sat * 1e3)

    # Solve large signal gain implicit equation (G = Go*exp(-((G-1)*Pout)/G*(Psat))
    # where Psat is the output saturation power (output power where gain (G) drops by 3 dB)
    # Ref 1, Eq. 2.92 & Ref 2
    g = g_o
    counter = 0
    resolution = 0.001  #Convergence criterium
    pwr_out_target = pwr_in * g
    nf = np.power(10, nf_db / 10)

    while True:
        if counter > 250:  #Stop after 250 iterations
            break
        # Calculate predicted output power based on updated g
        #pwr_out_target = pwr_in * g
        #config.display_data('Output pwr target', pwr_out_target, 0, 0)
        #config.display_data('Output pwr target (dBm)', 10*np.log10(pwr_out_target*1e3), 0, 0)
        pwr_out_target = pwr_in * g + (opt_bw * (g - 1) * nf * constants.h *
                                       opt_freq / 2)
        g_target = large_signal_gain(g, g_o, pwr_out_target, pwr_sat)
        #g_target = saturated_gain(g, g_o, pwr_in, pwr_sat)
        if g_target / g < 1 - resolution:  #g is too high
            g = 0.5 * (g - g_target)
            counter += 1
        elif g_target / g > 1 + resolution:  #g is too low
            g = 0.5 * (g_target + g)
            counter += 1
        else:
            break
    """Apply calculated (available gain) to optical channels-----------------------------------------"""
    for ch in range(0, channels):
        p_out = pwr_in * g  #Total power calculation
        if mode == 'None':
            pass
        elif mode == 'Gain control':
            gain_setting = np.power(10, gain_setting_db / 10)
            if gain_setting < g:
                g = gain_setting
        else:
            pwr_setting = 1e-3 * np.power(10, pwr_setting_dbm / 10)
            if pwr_setting < p_out:
                g = pwr_setting / pwr_in
        # Apply gain (g) to optical field and noise arrays
        g_channels = 1.0
        if (wave_freq[ch] > (opt_freq - (opt_bw / 2)) and wave_freq[ch] <
            (opt_freq + (opt_bw / 2))):  #Channel is within amplifier BW
            g_channels = g / channels
        if opt_channels[0][3].ndim == 2:  # Polarization format: Ex-Ey
            opt_field_out[ch, 0] = opt_field_rcv[ch, 0] * np.sqrt(
                g_channels) * jones_vector[ch, 0]
            opt_field_out[ch, 1] = opt_field_rcv[ch, 1] * np.sqrt(
                g_channels) * jones_vector[ch, 1]
        else:
            opt_field_out[ch] = opt_field_rcv[ch] * np.sqrt(g_channels)
        noise_field_out[ch] = noise_field_rcv[ch] * np.sqrt(g_channels)
    """Amplifier ASE calculation------------------------------------------------------------------------"""
    # Calculate noise spectral density (Ref 1, Eq 4.34)
    psd_ase = ((g / channels) - 1) * nf * constants.h * opt_freq / 2
    psd_ase_dbm = 10 * np.log10(psd_ase * 1e3)
    # Calculate total power of ASE
    # Ref 1, Eq 4.37 (factor of 2 accounts for both polarization states)
    pwr_ase_total = psd_ase * opt_bw * 2
    pwr_ase_total_dbm = 10 * np.log10(pwr_ase_total * 1e3)

    # Add noise to psd array (only added to noise bins/slices that are within
    # defined amplifier BW)
    ng = len(psd_array[0, :])
    psd_out = np.array([psd_array[0, :], np.zeros(ng)])
    for i in range(0, ng):  # psd_out = psd_ase + psd_in*g
        if (psd_array[0, i] > (opt_freq - (opt_bw / 2)) and psd_array[0, i] <
            (opt_freq + (opt_bw / 2))):
            psd_out[1, i] = psd_ase + (psd_array[1, i] * g / channels)
        else:
            psd_out[1, i] = psd_array[1, i]

    # Integrate ase noise with time-domain noise?
    # Note: The noise bins used to calculate the time-domain
    # noise will not be removed. However, if time-domain noise
    # is integrated into the signal array, the noise bins will be set
    # to near-zero (1e-30 A^2/Hz)
    if add_ase == 2:
        ng_w = psd_array[0, 1] - psd_array[0, 0]
        for ch in range(0, channels):
            pwr_ase = 0
            # Build time-domain freq points
            T = n / fs
            k = np.arange(n)
            frq = (k / T)
            frq = frq - frq[int(round(n / 2))] + wave_freq[ch]
            for i in range(0, ng):
                if psd_array[0, i] > frq[0] and psd_array[0, i] < frq[n - 1]:
                    pwr_ase += 2 * psd_out[
                        1, i] * ng_w  # Ref 1, Eq 4.37 (Pwr = 2*psd_ase*bw)
            # Convert to time-domain noise
            sigma_ase = np.sqrt(pwr_ase / 2)
            noise_ase_real = np.random.normal(0, sigma_ase, n)
            noise_ase_imag = np.random.normal(0, sigma_ase, n)
            noise_array_ase = noise_ase_real + 1j * noise_ase_imag
            noise_field_out[ch] += noise_array_ase

        # Add noise to time domain signal and remove from noise array?
        if add_ase_to_signal == 2:
            for ch in range(0, channels):
                if opt_channels[0][3].ndim == 2:  # Polarization format: Ex-Ey
                    opt_field_out[ch,
                                  0] += noise_array_ase * jones_vector[ch, 0]
                    opt_field_out[ch,
                                  1] += noise_array_ase * jones_vector[ch, 1]
                else:
                    opt_field_out[ch] += noise_array_ase
                noise_field_out[ch] += -noise_array_ase

        # Set psd_array points to zero (very low value)
        for ch in range(0, channels):
            T = n / fs
            k = np.arange(n)
            frq = k / T
            frq = frq - frq[int(round(n / 2))] + wave_freq[ch]
            for i in range(0, ng):
                if psd_out[0, i] > frq[0] and psd_out[0, i] < frq[n - 1]:
                    psd_out[1, i] = 1e-30
    """Calculate average power exiting amplifier (all channels)----------------------------------------"""
    pwr_out_amp = 0
    for ch in range(0, channels):
        pwr_out_amp_ch = np.mean(np.square(np.abs(opt_field_out[ch])))
        noise_out_amp_ch = np.mean(np.square(np.abs(noise_field_out[ch])))
        pwr_out_amp += pwr_out_amp_ch + noise_out_amp_ch
    if pwr_out_amp > 0:
        pwr_out_amp_dbm = 10 * np.log10(pwr_out_amp * 1e3)
    else:
        pwr_out_amp_dbm = 'NA'
    '''==OUTPUT PARAMETERS LIST===========================================
    '''
    opt_amp_parameters = []
    opt_amp_parameters = parameters_input
    '''==RESULTS======================================================
    '''
    # Estimation of output OSNR (Ref 4, Eq 4-20)
    osnr_predicted = 158.93 + avg_ch_pwr_in_dbm - nf_db - (
        10 * np.log10(bw_osnr_measurment))
    results = []
    gain_db = 10 * np.log10(g)
    gain_per_ch_db = 10 * np.log10(g / channels)
    results.append(
        ['Amplifier total gain (dB)', gain_db, 'dB', ' ', False, '0.3f'])
    results.append(['Amplifier total gain (linear)', g, ' ', ' ', False])
    results.append([
        'Amplifier per channel gain (dB)', gain_per_ch_db, 'dB', ' ', False,
        '0.3f'
    ])
    results.append(
        ['Amplifier per channel gain (linear)', g / channels, ' ', ' ', False])
    results.append([
        'Amplifier output saturation pwr (dBm)', pwr_out_sat_dbm, ' dBm', ' ',
        False, '0.3f'
    ])
    results.append(['Amplifier ASE (avg PSD)', psd_ase, 'A^2/Hz', ' ', False])
    results.append(
        ['Amplifier ASE (avg PSD)', psd_ase_dbm, 'dBm/Hz', ' ', False, '0.3f'])
    results.append(
        ['Amplifier ASE (total pwr)', pwr_ase_total, 'W', ' ', False])
    results.append([
        'Amplifier ASE (total pwr)', pwr_ase_total_dbm, 'dBm', ' ', False,
        '0.3f'
    ])
    results.append(['Avg sig/noise pwr entering amp', pwr_in, 'W', ' ', False])
    results.append([
        'Avg sig/noise pwr entering amp', pwr_in_dbm, 'dBm', ' ', False, '0.3f'
    ])
    results.append(
        ['Avg sig/noise pwr exiting amp', pwr_out_amp, 'W', ' ', False])
    results.append([
        'Avg sig/noise pwr exiting amp', pwr_out_amp_dbm, 'dBm', ' ', False,
        '0.3f'
    ])
    results.append(
        ['Estimated OSNR at output', osnr_predicted, 'dB', ' ', False, '0.2f'])
    '''==RETURN (Output Signals, Parameters, Results)=========================='''
    optical_channels = []
    for ch in range(0, channels):
        opt_ch = [
            int(wave_key[ch]), wave_freq[ch], jones_vector[ch],
            opt_field_out[ch], noise_field_out[ch]
        ]
        optical_channels.append(opt_ch)

    return ([[2, signal_type, fs, time_array, psd_out,
              optical_channels]], opt_amp_parameters, results)
def run(input_signal_data, parameters_input, settings):
    '''==PROJECT SETTINGS==================================================='''
    module_name = 'WDM Transmitter'
    n = settings['num_samples']
    n = int(round(n))
    iteration = settings['current_iteration']
    time = settings['time_window']
    fs = settings['sampling_rate']
    t_step = settings['sampling_period']
    """Status messages-----------------------------------------------------------------------"""
    # Status message - initiation of fb_script (Sim status panel & Info/Status window)
    fb_title_string = 'Running ' + str(module_name) + ' - Iteration #: ' + str(
        iteration)
    config.status_message(fb_title_string)
    # Data display - title of current fb_script
    config.display_data(' ', ' ', False, False)
    fb_data_string = 'Data output for ' + str(module_name) + ' - Iteration #: '
    # Display data settings: Data title (str), Data (scalar, array, etc),
    # Set to Bold?, Title & Data on separate lines?
    config.display_data(fb_data_string, iteration, False, True)
    '''==INPUT PARAMETERS====================================================
    '''
    # Load parameters from FB parameters table
    # Parameter name(0), Value(1), Units(2), Notes(3)
    # Main settings
    key = int(parameters_input[1][1])
    wave_units = str(parameters_input[2][1])
    freq = float(parameters_input[3][1])  #First channel frequency
    if wave_units == 'Wavelength (nm)':
        freq = 1e-3 * constants.c / freq
    pwr_units = str(parameters_input[4][1])
    optical_pwr = float(parameters_input[5][1])  #Optical power (mW or dBm)
    if pwr_units == 'dBm':
        optical_pwr = np.power(10, optical_pwr / 10)
    # Optical phase settings (header) - MV 20.01.v3 16-Sep-20
    phase_deg = float(parameters_input[7][1])  # Optical phase (deg)
    include_phase_noise = int(parameters_input[8][1])
    line_width = float(parameters_input[9][1])  # Laser linewidth (MHz)
    # Modulation settings
    ext_ratio = float(parameters_input[11][1])  #Optical phase (deg)
    # Relative intensity noise (header)
    ref_bw = float(parameters_input[13][1])  #Reference bandwidth (Hz)
    rin = float(parameters_input[14][1])  #RIN (dB/Hz)
    include_rin = int(parameters_input[15][1])
    add_rin_to_signal = int(parameters_input[16][1])  #Add RIN to signal
    # Polarization settings (header)
    pol_azimuth = float(parameters_input[18][1])
    pol_ellipticity = float(parameters_input[19][1])
    e_field_format = str(parameters_input[20][1])
    # Frequency domain noise groups (header)
    psd_freq = float(parameters_input[22][1])
    ng = int(parameters_input[23][1])
    freq_start = float(parameters_input[24][1])
    freq_end = float(parameters_input[25][1])
    include_ase = int(parameters_input[26][1])
    add_ase_to_signal = int(parameters_input[27][1])
    # Results/Data panel settings
    data_panel_id = str(parameters_input[29][1])

    # Additional parameters
    signal_type = 'Optical'
    wave_key = key
    wave_freq = freq * 1e12
    '''==INPUT SIGNALS======================================================
    '''
    sym_i_input = input_signal_data[0][6]
    #time_array = input_signal_data[0][5]
    '''==CALCULATIONS======================================================
    '''
    """LASER SETTINGS------------------------------------------------------------------"""
    # Polarization settings
    pol_azimuth_rad = (pol_azimuth / 180) * np.pi
    pol_ellipticity_rad = (pol_ellipticity / 180) * np.pi
    jones_vector = ([
        np.cos(pol_azimuth_rad) * np.cos(pol_ellipticity_rad) -
        1j * np.sin(pol_azimuth_rad) * np.sin(pol_ellipticity_rad),
        np.sin(pol_azimuth_rad) * np.cos(pol_ellipticity_rad) +
        1j * np.cos(pol_azimuth_rad) * np.sin(pol_ellipticity_rad)
    ])

    # Prepare initial electrical field definitions for optical signals
    time_array = np.linspace(0, time, n)
    e_field_array = np.full(n, np.sqrt(optical_pwr * 1e-3))

    #Add RIN to field values (if selected) - Ref 1, Eq 4.127
    noise_array = np.full(n, 0 + 1j * 0, dtype=complex)
    if include_rin == 2:
        rin_linear = np.power(10, rin / 10)
        noise_pwr = rin_linear * ref_bw * (optical_pwr * 1e-3)**2
        #Penalty (noise var) at receiver is: 2*(RP1)^2*RIN*BW
        sigma_field = np.sqrt(np.sqrt(noise_pwr))
        noise_array_rin = np.random.normal(0, sigma_field, n)
        noise_array = noise_array_rin + 1j * 0
        # Calculate noise intensity coefficient (r_int)
        r_int = np.sqrt(2 * rin_linear * ref_bw)  # Ref 1, Eq 4.127
        if add_rin_to_signal == 2:
            e_field_array = e_field_array + noise_array_rin
            noise_array += -noise_array_rin

    # Initialize electric field arrays
    # Slowly varying envelope approximation ( E(z,t) = Eo(z, t)*exp(i(kz - wt)) )
    # e_field_env = E(z,t); w is carried separately as wave_freq
    phase_rad = (phase_deg / 180) * np.pi
    e_field_array_real = np.zeros(n)
    e_field_array_imag = np.zeros(n)
    if e_field_format == 'Exy':
        e_field_env = np.full(n, 0 + 1j * 0, dtype=complex)
    else:
        e_field_env = np.full([2, n], 0 + 1j * 0, dtype=complex)

    #Add intial phase setting to complex envelope
    for i in range(0, n):
        e_field_array_real[i] = e_field_array[i] * np.cos(phase_rad)
        e_field_array_imag[i] = e_field_array[i] * np.sin(phase_rad)
        if e_field_format == 'Exy':
            e_field_env[i] = e_field_array_real[i] + 1j * e_field_array_imag[i]
        else:
            e_field_env[0][
                i] = e_field_array_real[i] + 1j * e_field_array_imag[i]
            e_field_env[1][
                i] = e_field_array_real[i] + 1j * e_field_array_imag[i]

    # Prepare noise groups (freq domain)
    freq_delta = freq_end - freq_start
    ng_w = freq_delta / ng
    freq_points = np.linspace(freq_start + (ng_w / 2), freq_end - (ng_w / 2),
                              ng)
    psd_points = np.full(ng, psd_freq)
    psd_array = np.array([freq_points, psd_points])

    # Add phase noise to field envelope (Brownian randon walk) - Ref 1, Eq. 4.7
    # MV 20.01.r3 16-Sep-20 Model can now be enabled/disabled
    if include_phase_noise == 2:
        phase_sigma = np.sqrt(np.pi * 2 * line_width * 1e6)
        phase = phase_rad
        phase_array = np.full(n, phase)
        for i in range(1, n):
            phase_walk = np.random.normal(0, phase_sigma) * np.sqrt(t_step)
            phase_array[i] = phase_array[i - 1] + phase_walk
            e_field_array_real[i] = e_field_array[i] * np.cos(phase_array[i])
            e_field_array_imag[i] = e_field_array[i] * np.sin(phase_array[i])
            if e_field_format == 'Exy':
                e_field_env[
                    i] = e_field_array_real[i] + 1j * e_field_array_imag[i]
            else:
                e_field_env[0][
                    i] = e_field_array_real[i] + 1j * e_field_array_imag[i]
                e_field_env[1][
                    i] = e_field_array_real[i] + 1j * e_field_array_imag[i]

    # Apply Jones vector to Ex and Ey complex arrays
    if e_field_format == 'Ex-Ey':
        e_field_env[0] = jones_vector[0] * e_field_env[0]
        e_field_env[1] = jones_vector[1] * e_field_env[1]

    # Integrate ase noise with time-domain noise?
    if include_ase == 2:
        T = n / fs
        k = np.arange(n)
        frq = k / T
        frq = frq - frq[int(round(n / 2))] + wave_freq
        pwr_ase = 0
        noise_array_ase = np.full(n, 0 + 1j * 0, dtype=complex)
        for i in range(0, ng):
            if psd_array[0, i] > frq[0] and psd_array[0, i] < frq[n - 1]:
                pwr_ase += psd_array[1, i] * ng_w
        #Convert to time-domain noise
        sigma_ase = np.sqrt(pwr_ase / 2)
        noise_ase_real = np.random.normal(0, sigma_ase, n)
        noise_ase_imag = np.random.normal(0, sigma_ase, n)
        noise_array_ase = noise_ase_real + 1j * noise_ase_imag
        noise_array += noise_array_ase
        # Add noise to time domain signal and set noise array to zero
        if add_ase_to_signal == 2:
            if e_field_format == 'Exy':
                e_field_env += noise_array_ase
            else:
                e_field_env[0] += noise_array_ase * jones_vector[0]
                e_field_env[1] += noise_array_ase * jones_vector[1]
            noise_array += -noise_array_ase
        # Set psd_array points to zero (that have been converted to time domain)
        for i in range(0, ng):
            if psd_array[0, i] > frq[0] and psd_array[0, i] < frq[n - 1]:
                psd_array[1, i] = 1e-30  #Set to very low value
    """MODULATION SETTINGS------------------------------------------------------------------"""
    sym_rate = 10e9
    symbol_seq_length = np.size(sym_i_input)
    samples_per_symbol = int(round(fs / sym_rate))
    sig_out = np.zeros(n)

    # Extinction ratio
    r_ex_linear = np.power(10, -ext_ratio / 10)
    amp_1 = 1
    amp_0 = amp_1 * np.sqrt(r_ex_linear)

    for sig in range(0, symbol_seq_length):
        amp = amp_0
        if sym_i_input[sig] == 1:
            amp = amp_1
        start_index = int(sig * int(samples_per_symbol))
        sig_out[start_index:start_index + samples_per_symbol] = amp

    # Apply modulation to optical field envelope
    if e_field_format == 'Exy':
        e_field_env = sig_out * e_field_env
    else:
        e_field_env[0] = sig_out * e_field_env[0]
        e_field_env[1] = sig_out * e_field_env[1]

    # Calculate OMA
    p_1 = np.max(np.abs(e_field_env) * np.abs(e_field_env))
    p_0 = np.min(np.abs(e_field_env) * np.abs(e_field_env))
    oma = 1e3 * (p_1 - p_0)
    '''==OUTPUT PARAMETERS LIST===================================================
    '''
    laser_parameters = []
    laser_parameters = parameters_input
    '''==RESULTS===============================================================
    '''
    results = []
    # Output laser power (average)
    laser_pwr_avg = 1e3 * np.sum(np.abs(e_field_env) * np.abs(e_field_env)) / n
    laser_pwr_dbm = 10 * np.log10(laser_pwr_avg)
    results.append(['Laser power avg (mW)', laser_pwr_avg, 'mW', ' ', False])
    results.append(
        ['Laser power avg (dBm)', laser_pwr_dbm, 'dBm', ' ', False, '0.2f'])
    results.append(['ER', ext_ratio, 'dB', ' ', False, '0.2f'])
    results.append(['ER (linear)', 1 / r_ex_linear, ' ', ' ', False, '0.3f'])
    # Polarization data
    results.append(['Polarization data', '', '', '', True])
    results.append(['Polarization (azimuth)', pol_azimuth, 'deg', ' ', False])
    results.append(
        ['Polarization (ellipticity)', pol_ellipticity, 'deg', ' ', False])
    """Data panel outputs------------------------------------------------------------------------------------"""
    c_analytical = 'blue'
    config.data_tables[data_panel_id] = []
    data_list_1 = []
    data_list_1.append(['Laser avg pwr', laser_pwr_avg, '0.4f', 'mW'])
    data_list_1.append(['Laser avg pwr', laser_pwr_dbm, '0.2f', 'dBm'])
    data_list_1.append(['ER', ext_ratio, '0.2f', 'dB', ' ', c_analytical])
    data_list_1.append(
        ['ER (linear)', 1 / r_ex_linear, '0.3f', ' ', ' ', c_analytical])
    data_list_1.append(['OMA', oma, '0.4f', 'mW'])
    config.data_tables[data_panel_id].extend(data_list_1)
    '''config.data_tables['tx_metrics_2'] = []
    opt_pwr_p1_dbm = 10*np.log10(optical_pwr) 
    data_list_2 = []
    data_list_2.append(['Freq', wave_freq*1e-12, '0.2f', 'THz', ' ', c_analytical])
    data_list_2.append(['Opt pwr (P1)', opt_pwr_p1_dbm, '0.2f', 'dBm', ' ', c_analytical])
    config.data_tables['tx_metrics_2'].extend(data_list_2)'''
    '''==RETURN (Output Signals, Parameters, Results)=================================='''
    opt_ch = [[
        int(wave_key), wave_freq, jones_vector, e_field_env, noise_array
    ]]
    return ([[2, signal_type, fs, time_array, psd_array,
              opt_ch]], laser_parameters, results)
Пример #7
0
def run(input_signal_data, parameters_input, settings):
    '''==PROJECT SETTINGS===================================================
    '''
    module_name = settings['fb_name']
    n = settings['num_samples']  #Total samples for simulation
    n = int(round(n))
    time = settings['time_window']  #Time window for simulation (sec)
    fs = settings['sampling_rate']  #Sample rate (default - Hz)
    f_sym = settings['symbol_rate']  #Symbol rate (default - Hz)
    t_step = settings['sampling_period']  #Sample period (Hz)
    iteration = settings[
        'current_iteration']  #Current iteration loop for simulation
    i_total = settings[
        'iterations']  #Total iterations for simulation (default - 1)

    path = settings['file_path_1']
    path = os.path.join(path, 'project_config.py')
    if os.path.isfile(path):
        import project_config as proj
    """Status messages-----------------------------------------------------------------------"""
    # Status message - initiation of fb_script (Sim status panel & Info/Status window)
    fb_title_string = 'Running ' + str(module_name) + ' - Iteration #: ' + str(
        iteration)
    config.status_message(fb_title_string)
    # Data display - title of current fb_script
    config.display_data(' ', ' ', False, False)
    fb_data_string = 'Data output for ' + str(module_name) + ' - Iteration #: '
    # Display data settings: Data title (str), Data (scalar, array, etc),
    # Set to Bold?, Title & Data on separate lines?
    config.display_data(fb_data_string, iteration, False, True)
    '''==INPUT PARAMETERS================================================
    '''
    # TIA settings
    z_tia = float(parameters_input[1][1]) * 1e3  # kohm -> ohm
    tia_noise_model = str(parameters_input[2][1])
    i_noise_referred = float(parameters_input[3][1]) * 1e-6  # uA -> A
    enb = float(parameters_input[4][1]) * 1e9  # GHz -> Hz
    i_noise_density = float(
        parameters_input[5][1]) * 1e-12  # pA/sqrt(Hz) -> A/sqrt(Hz)
    # TIA (filter model)
    filter_type = str(parameters_input[7][1])
    freq_cut_off = float(parameters_input[8][1]) * 1e9  # GHz -> Hz
    Q = float(parameters_input[9][1])
    include_noise_array = int(parameters_input[10][1])  # MV 20.01.r3 26-Aug-20
    # TIA analysis graphs
    freq_resp_curves = int(parameters_input[12][1])
    dist_plot = int(parameters_input[13][1])
    n_bins = int(parameters_input[14][1])
    # LA settings
    enable_la = int(parameters_input[16][1])
    output_v_swing = float(parameters_input[17][1]) * 1e-3  # mV -> V
    la_noise_model = str(parameters_input[18][1])
    v_sens_la = float(parameters_input[19][1]) * 1e-3  # mV -> V
    enb_la = float(parameters_input[20][1]) * 1e9  # GHz -> Hz
    v_noise_density = float(
        parameters_input[21][1]) * 1e-9  # nV/sqrt(Hz) -> V/sqrt(Hz)
    gain_la = float(parameters_input[22][1])  # dB
    trans_points = str(parameters_input[23][1])
    rise_fall_time = float(parameters_input[24][1]) * 1e-12  # ps -> s
    jitter_det = float(parameters_input[25][1]) * 1e-12  # ps -> s
    jitter_rdm = float(parameters_input[26][1]) * 1e-12  # ps -> s
    # Add noise array to signal array
    add_noise_to_signal = int(parameters_input[28][1])
    # Receiver sensitivity calculations
    q_target = float(parameters_input[30][1])
    r = float(parameters_input[31][1])  # Upstream PIN/APD responsivity
    r_e = float(parameters_input[32][1])
    isi_penalty = float(parameters_input[33][1])  # dB
    # Settings for results/data panels
    data_panel_id = str(parameters_input[35][1])
    '''==INPUT SIGNALS========================================
    '''
    # Electrical: portID(0), signal_type(1), carrier(2), sample_rate(3), time_array(4),
    # amplitude_array(5), noise_array(6)
    time_array = input_signal_data[0][4]
    sig_in = np.zeros(n)
    noise_in = np.zeros(n)
    sig_in = copy.deepcopy(input_signal_data[0][5])
    noise_in = copy.deepcopy(input_signal_data[0][6])
    '''==CALCULATIONS=========================================
    '''
    carrier = 0
    sig_type_out = 'Electrical'
    """Transimpedance calculations-------------------------------------------------------------
    """
    # Apply gain to signal (converts current to voltage)
    sig_array = sig_in * z_tia  # V=IR
    noise_array = noise_in * z_tia  # V=IR
    """TIA filtering-----------------------------------------------------------------------------"""
    if filter_type != 'Ideal (no filter)':
        # Convert freq cut-off units from Hz to rad/s
        w_0 = np.pi * 2 * freq_cut_off
        Y, frq = convert_freq_domain(sig_array, fs, n)
        N, frq = convert_freq_domain(noise_array, fs, n)
        if filter_type == 'Low-pass (n=1)':
            Y_trans = transfer_lowpass_1st_order(Y, frq, w_0, fs, n)
            N_trans = transfer_lowpass_1st_order(N, frq, w_0, fs, n)
        elif filter_type == 'Low-pass (n=2)':
            Y_trans = transfer_lowpass_2nd_order(Y, frq, w_0, Q, fs, n)
            N_trans = transfer_lowpass_2nd_order(N, frq, w_0, Q, fs, n)
        # Build freq response curve (if selected)
        if freq_resp_curves == 2:
            build_freq_response_curve(Y, Y_trans, frq, n, freq_cut_off,
                                      filter_type)
        # Convert back to time domain
        sig_array = np.fft.ifft(np.fft.ifftshift(Y_trans))
        if include_noise_array == 2:  # MV 20.01.r3 26-Aug-20
            noise_array = np.fft.ifft(np.fft.ifftshift(N_trans))
    """Noise calculations (TIA)----------------------------------------------------------------"""
    tia_v_noise = np.zeros(n)
    if tia_noise_model != 'Disabled':
        # Calculate output noise voltage (based on input referred noise current/density)
        if tia_noise_model == 'Input noise density':
            i_noise_referred = i_noise_density * np.sqrt(enb)
        #output_noise_variance = np.square(i_noise_referred*z_tia)
        #v_noise_sigma = np.sqrt(output_noise_variance)
        #config.display_data('Voltage sigma (TIA): ', v_noise_sigma, 0, 0)
        # Add noise to noise array
        tia_v_noise = np.random.normal(0, i_noise_referred * z_tia, n)
        noise_array += tia_v_noise
    else:  # Input referred noise is not modeled
        i_noise_referred = 0
    """Calculate performance metrics--------------------------------------------------------"""
    # Calculate estimated BER for q target
    ber_target = 0.5 * special.erfc(q_target / np.sqrt(2))
    # Current (noise) from PIN/APD
    i_noise_input = np.sqrt(np.var(noise_in))
    # Input referred noise current (TIA)
    i_noise_input_tia_la = i_noise_referred
    # Noise current total (TIA and PIN/APD)
    n_rms = np.sqrt(np.var(noise_in) + np.square(i_noise_referred))

    # Calculate minimum OMA and sensitivity (based on q target)
    isi = 0
    if isi_penalty != 0:
        isi = 1 - np.power(10, -isi_penalty / 10)
    #config.display_data('ISI (%): ', 100*isi, 0, 0)
    min_i_pp = 2 * q_target * n_rms / (1 - isi)
    oma_min = min_i_pp / r
    p_avg_min = 10 * np.log10(1e3 * (oma_min / 2) * (r_e + 1) / (r_e - 1))

    # Signal statistics (post-TIA)
    sig_total = np.real(sig_array + noise_array)
    sig_avg = np.mean(sig_total)
    sig_P1 = sig_total[sig_total > sig_avg]
    sig_P0 = sig_total[sig_total < sig_avg]
    v1_mean = np.mean(sig_P1)
    v0_mean = np.mean(sig_P0)
    v1_sigma = np.std(sig_P1)
    v0_sigma = np.std(sig_P0)
    q_measured = (v1_mean - v0_mean) / (v1_sigma + v0_sigma)
    v_pp = v1_mean - v0_mean
    ber_estimate = 0.5 * special.erfc(q_target / np.sqrt(2))

    # Distribution analysis
    if dist_plot == 2:
        title = 'Signal/Noise distribution (TIA output)'
        #sig_dist = np.real(sig_array) + np.real(noise_array)
        config.dist_graph['TIA'] = config.view.Distribution_Analysis(
            title, sig_total, 'Signal (V)', n_bins, None, v1_mean, v0_mean,
            v1_sigma, v0_sigma)
        config.dist_graph['TIA'].show()
    """LIMITING AMPLIFIER CALCULATIONS-----------------------------------------
    """
    n_la = 0
    gain_avg = 1
    v_ratio = np.ones(n)
    if enable_la == 2:
        """Noise calculations (LA)---------------------------------------------------------"""
        if la_noise_model == 'Disabled':
            v_sens_la = 0
        elif la_noise_model == 'Referred noise density':
            v_sens_la = v_noise_density * np.sqrt(enb_la)
        n_la = v_sens_la / (2 * q_target)

        # Input referred noise (TIA + LA)
        i_noise_input_tia_la = (
            np.sqrt(np.square(i_noise_referred) + np.square(n_la / z_tia)))
        v_noise = np.random.normal(0, i_noise_input_tia_la * z_tia, n)
        noise_array += v_noise - tia_v_noise

        # Add noise to sig_array
        sig_array += noise_array
        noise_array = np.zeros(n)
        """Perform DC offset and apply gain---------------------------------------"""
        # DC offset
        sig_avg = np.mean(sig_array)
        sig_array = sig_array - sig_avg
        # Calculate linear gain (small signal)
        gain_la_linear = np.power(10, gain_la / 20)
        v_rail = output_v_swing / 2
        # Calculate gain array
        for i in range(0, n):
            # Calculate ratio of input voltage to voltage swing (0.5*PtP)
            # signal_total = np.abs(noise_array[i]) + np.abs(sig_array[i])
            v_ratio[i] = v_rail / np.abs(sig_array[i])
            if v_ratio[i] > gain_la_linear:  # Maximum linear gain available
                v_ratio[i] = gain_la_linear
        # Calculate average gain applied to all sampled signals
        gain_avg = np.mean(v_ratio)
        sig_array = sig_array * v_ratio
        '''http://www.ecircuitcenter.com/OpModels/Tanh_Stage/Tanh_Stage.htm
        for i in range(0, n):
            slope = 100
            k =  sig_array[i]/v_rail
            sig_array[i] = v_rail * np.tanh(k*slope)'''
        """Apply rise/fall time to signal-----------------------------------------------"""
        # REF: Wikipedia contributors, "Rise time," Wikipedia, The Free Encyclopedia,
        # https://en.wikipedia.org/w/index.php?title=Rise_time&oldid=946919388
        # (accessed June 24, 2020).
        if rise_fall_time > 0:
            sig_array = apply_rise_fall_time(sig_array, trans_points,
                                             rise_fall_time, fs, n)
        """Apply jitter (random and deterministic)----------------------------------------------------"""
        # Based on Dual Dirac Assumption (Jitter_total = Jitter_deterministic + Jitter_random)
        # exp(-(signal - j_det/2)^2/2*j_rms^2) + exp(-(signal + j_det/2)^2/2*j_rms^2)
        # REF 5 (slide 21)
        samples_per_sym = int(round(fs / f_sym))
        n_symbols = int(round(n / samples_per_sym))
        if jitter_det > 0.0 or jitter_rdm > 0.0:
            for i in range(0, n_symbols - 1):
                dirac = np.random.randint(2)
                if dirac == 0:
                    dirac = -1
                dirac_offset = float(
                    dirac) * jitter_det / 2 + np.random.normal(0, jitter_rdm)
                idx_1 = i * samples_per_sym
                idx_2 = (i + 1) * samples_per_sym
                sig_array[idx_1:idx_2] = np.interp(
                    time_array[idx_1:idx_2] + dirac_offset,
                    time_array[idx_1:idx_2], sig_array[idx_1:idx_2])
        """Calculate performance metrics-----------------------------------------------------------"""
        # Noise current total (TIA, LA and PIN/APD)
        n_rms = np.sqrt(
            np.var(noise_in) + np.square(i_noise_referred) +
            np.square(n_la / z_tia))
        # Calculate minimum OMA and sensitivity (based on q target)
        min_i_pp = 2 * q_target * n_rms / (1 - isi)
        oma_min = min_i_pp / r
        p_avg_min = 10 * np.log10(1e3 * (oma_min / 2) * (r_e + 1) / (r_e - 1))

        # Calculate voltage sigma for TIA/LA
        #output_noise_variance = np.square(i_noise_referred*z_tia) + np.square(n_la*gain_avg)
        #v_noise_sigma = np.sqrt(output_noise_variance)
        #config.display_data('Voltage sigma (TIA+LA): ', v_noise_sigma, 0, 0)
        """Noise calculations (LA)-------------------------------------------------------------------"""
        # Apply LA voltage noise to output noise array (Note: average gain is used)
        #la_v_noise = np.random.normal(0, n_la, n)
        #tia_v_noise = np.random.normal(0, n_la*gain_la_linear, n)
        #noise_array += la_v_noise*gain_la_linear

        #output_noise_variance = np.square(n_rms*z_tia)
        #v_noise_sigma = np.sqrt(output_noise_variance)

        #la_v_noise = np.random.normal(0, v_noise_sigma, n)
        #noise_array += la_v_noise

    # Add noise to signal array? Applies to TIA only model
    if add_noise_to_signal == 2:
        sig_array += noise_array
        noise_array = np.zeros(n)

    # Send data to project folder (only if project file has been created)
    if os.path.isfile(path):
        if iteration == 1:
            proj.q_measured = []
            proj.q_measured.append(q_measured)
            proj.ber_estimate = []
            proj.ber_estimate.append(ber_estimate)
        else:
            proj.q_measured.append(q_measured)
            proj.ber_estimate.append(ber_estimate)
    '''==OUTPUT PARAMETERS LIST=======================================================
    '''
    script_parameters = []
    script_parameters = parameters_input  #If NO changes are made to parameters
    '''==RESULTS===================================================================
    '''
    results = []
    results.append(['TIA/LA Metrics', ' ', ' ', ' ', True])
    results.append([
        'Noise current from photodetector', i_noise_input * 1e6, 'uA', ' ',
        False, '0.3f'
    ])
    results.append([
        'Input referred noise current (TIA)', i_noise_referred * 1e6, 'uA',
        ' ', False, '0.3f'
    ])
    results.append([
        'Input referred noise voltage (LA)', n_la * 1e3, 'mV', ' ', False,
        '0.3f'
    ])
    #results.append(['Input referred noise (TIA/LA)', i_noise_input_tia_la*1e6, 'uA', ' ', False, '0.3f'])
    results.append([
        'Total noise current at TIA input', n_rms * 1e6, 'uA', ' ', False,
        '0.3f'
    ])
    results.append(
        ['Input voltage swing at LA', v_pp * 1e3, 'mV', ' ', False, '0.2f'])
    results.append([
        'Transimpendance gain (TIA)', z_tia * 1e-3, 'k' + '\u2126', ' ', False,
        '0.2f'
    ])
    results.append(['Average gain (LA)', gain_avg, ' ', ' ', False, '0.2f'])
    results.append(['OMA (Q target)', oma_min * 1e6, 'uW', ' ', False, '0.3f'])
    results.append([
        'Receiver sensitivity (Q target)', p_avg_min, 'dBm', ' ', False, '0.2f'
    ])
    results.append(['Receiver Q statistics', ' ', ' ', ' ', True])
    results.append(['V1 mean (all samples)', v1_mean, ' ', ' ', False, '0.3E'])
    results.append(['V0 mean (all samples)', v0_mean, ' ', ' ', False, '0.3E'])
    results.append(
        ['V1 std dev (all samples)', v1_sigma, ' ', ' ', False, '0.3E'])
    results.append(
        ['V0 std dev (all samples)', v0_sigma, ' ', ' ', False, '0.3E'])
    results.append(
        ['Q measured (all samples)', q_measured, ' ', ' ', False, '0.2f'])
    """Data panel output-------------------------------------------------------------------------------------"""
    c_analytical = 'blue'
    config.data_tables[data_panel_id] = []
    data_list = []
    data_list.append(
        ['Iteration #', iteration, '0.0f', ' ', ' ', c_analytical])
    data_list.append([
        '--------------------------TIA/LA Metrics----------------------------',
        ' ', ' ', ' ', '#5e5e5e'
    ])
    data_list.append([
        'Noise current from photodetector', i_noise_input * 1e6, '0.3f', 'uA'
    ])
    data_list.append([
        'Input referred noise current (TIA)', i_noise_referred * 1e6, '0.3f',
        'uA'
    ])
    data_list.append(
        ['Input referred noise voltage (LA)', n_la * 1e3, '0.3f', 'mV'])
    #data_list.append(['Input referred noise (TIA/LA)', i_noise_input_tia_la*1e6, '0.3f', 'uA'])
    data_list.append(
        ['Total noise current at TIA input', n_rms * 1e6, '0.3f', 'uA'])
    data_list.append(['Input voltage swing at LA', v_pp * 1e3, '0.2f', 'mV'])
    data_list.append(
        ['Transimpendance gain (TIA)', z_tia * 1e-3, '0.2f', 'k' + '\u2126'])
    data_list.append(['Average gain (LA)', gain_avg, '0.2f', ' '])

    data_list.append([
        '--------------------------Link Analysis ----------------------------',
        ' ', ' ', ' ', '#5e5e5e'
    ])
    data_list.append(['Q (measured)', q_measured, '0.2f', ' '])
    data_list.append(
        ['Target Q for link', q_target, '0.2f', ' ', ' ', c_analytical])
    data_list.append(
        ['Target BER for link', ber_target, '0.3E', ' ', ' ', c_analytical])
    data_list.append(
        ['OMA (Q target)', oma_min * 1e6, '0.3f', 'uW', ' ', c_analytical])
    data_list.append([
        'Receiver sensitivity (Q target)', p_avg_min, '0.2f', 'dBm', ' ',
        c_analytical
    ])
    # Add data_list entries to data tables dictionary
    config.data_tables[data_panel_id].extend(data_list)
    '''==RETURN (Output Signals, Parameters, Results)==================================
    '''
    electrical_out = [
        2, sig_type_out, carrier, fs, time_array, sig_array, noise_array
    ]
    return ([electrical_out], script_parameters, results)
def run(input_signal_data, parameters_input, settings):
    '''==PROJECT SETTINGS==================================================='''
    module_name = settings['fb_name']
    n = settings['num_samples']
    n = int(round(n))
    iteration = settings['current_iteration']
    fs = settings['sampling_rate']
    t_step = settings['sampling_period']
    samples_per_sym = settings['samples_per_sym']

    path = settings['file_path_1']
    path = os.path.join(path, 'project_config.py')
    if os.path.isfile(path):
        import project_config as proj
    """Status messages-----------------------------------------------------------------------"""
    # Status message - initiation of fb_script (Sim status panel & Info/Status window)
    fb_title_string = 'Running ' + str(module_name) + ' - Iteration #: ' + str(
        iteration)
    config.status_message(fb_title_string)
    # Data display - title of current fb_script
    config.display_data(' ', ' ', False, False)
    fb_data_string = 'Data output for ' + str(module_name) + ' - Iteration #: '
    # Display data settings: Data title (str), Data (scalar, array, etc),
    # Set to Bold?, Title & Data on separate lines?
    config.display_data(fb_data_string, iteration, False, True)
    '''==INPUT PARAMETERS========================================================='''
    # Load parameters from FB parameters table (Parameter name(0), Value(1), Units(2), Notes(3))
    # General parameters (header)
    opt_regime = str(
        parameters_input[1][1])  # Optical regime (Coherent, Incoherent)
    detection_model = str(parameters_input[2][1])  # Detection model (PIN, APD)
    r_qe_active = str(
        parameters_input[3][1])  #Responsivity model type (QE, direct)
    qe = float(parameters_input[4][1])  #Quantum efficiency
    r_direct = float(parameters_input[5][1])  #Responsivity (W/A)
    i_d = float(parameters_input[6][1]) * 1e-9  #Dark current (nA -> A)
    rbw = float(parameters_input[7][1])  #Receiver bandwidth (Hz)
    q_target = float(parameters_input[8][1])
    # Export q target to proj config
    # proj.q_target = q_target
    # APD parameters (header)
    m_apd = float(parameters_input[10][1])  #Average avalanche gain
    enf_model = str(parameters_input[11][1])
    x_apd = float(parameters_input[12][1])  # Noise coefficient (x)
    k_apd = float(parameters_input[13][1])  # Ionization coefficient (k)
    # Noise parameters (header)
    th_noise_active = int(parameters_input[15][1])  #Thermal noise ON/OFF
    th_noise_model = str(parameters_input[16][1])
    th_noise_psd = float(parameters_input[17][1])  #Thermal noise PSD (A^2/Hz)
    noise_temp = float(parameters_input[18][1])  #Thermal noise temperature (K)
    r_load = float(parameters_input[19][1])  #Load resistance (ohm)
    shot_noise_active = int(parameters_input[20][1])  #Shot noise ON/OFF
    shot_noise_model = str(parameters_input[21][1])
    include_optical_noise = int(parameters_input[22][1])
    optical_noise_model = str(parameters_input[23][1])
    opt_filter_bw = float(parameters_input[24][1]) * 1e9
    # Output noise settings (header)
    add_noise_to_signal = int(parameters_input[26][1])
    # Results settings (header)
    display_pin_apd_noise_metrics = int(parameters_input[28][1])
    display_optical_noise_metrics = int(parameters_input[29][1])
    ch_key_ref = int(parameters_input[30][1])
    data_panel_id = str(parameters_input[31][1])
    enable_data_export = int(parameters_input[32][1])
    data_att_1 = str(parameters_input[33][1])
    '''==INPUT SIGNAL======================================================='''
    time_array = input_signal_data[0][3]
    psd_array = input_signal_data[0][4]
    opt_channels = input_signal_data[0][5]  #Optical channel list
    channels = len(opt_channels)
    jones_vector = np.full([channels, 2], 0 + 1j * 0, dtype=complex)
    """Extract signal and noise field envelopes for each optical channel--------------------------------"""
    signal_type = 'Electrical'
    wave_key = np.empty(channels)
    wave_freq = np.empty(channels)
    if opt_channels[0][3].ndim == 2:
        opt_field_rcv = np.full([channels, 2, n], 0 + 1j * 0, dtype=complex)
    else:
        opt_field_rcv = np.full([channels, n], 0 + 1j * 0, dtype=complex)
    noise_field_rcv = np.full([channels, n], 0 + 1j * 0, dtype=complex)
    #noise_field_rcv_cpol = np.full([channels, n], 0 + 1j*0, dtype=complex)
    #noise_field_rcv_orth = np.full([channels, n], 0 + 1j*0, dtype=complex)

    for ch in range(0, channels):  #Load wavelength channels
        wave_key[ch] = opt_channels[ch][0]
        wave_freq[ch] = opt_channels[ch][1]
        opt_field_rcv[ch] = copy.deepcopy(opt_channels[ch][3])
        jones_vector[ch] = copy.deepcopy(opt_channels[ch][2])
        noise_field_rcv[ch] = copy.deepcopy(opt_channels[ch][4])
        # Co-polarized component of noise field (50% of received ASE power)
        #noise_field_rcv_cpol[ch] = copy.deepcopy(opt_channels[ch][4])/np.sqrt(2)
        # Orthogonal component of noise field (50% of received ASE power)
        #noise_field_rcv_orth[ch] = copy.deepcopy(opt_channels[ch][4])/np.sqrt(2)
    '''==CALCULATIONS======================================================='''
    q = constants.e  # Electron charge
    h = constants.h  # Planck constant
    pi = constants.pi
    """ Calculate OSNR--------------------------------------------------------------------"""
    rcv_sig_pwr_ch = 0
    rcv_noise_pwr_ch = 0
    ch_index = 0
    indices = np.where(wave_key == ch_key_ref)
    if np.size(indices[0]) > 0:
        ch_index = indices[0][0]
    T = n / fs
    k_freq = np.arange(n)
    frq = k_freq / T  # Positive/negative freq (double sided)
    frq = frq - frq[int(round(n / 2))] + wave_freq[ch_index]

    Y = np.fft.fftshift(np.fft.fft(opt_field_rcv[ch_index]))
    N = np.fft.fftshift(np.fft.fft(noise_field_rcv[ch_index]))
    tr_fcn_filt = rect_profile(frq, wave_freq[ch_index], opt_filter_bw)
    Y = Y * tr_fcn_filt
    N = N * tr_fcn_filt

    osnr_sig_array = np.fft.ifft(np.fft.ifftshift(Y))
    osnr_noise_array = np.fft.ifft(np.fft.ifftshift(N))
    rcv_sig_pwr_ch = calculate_total_opt_pwr(osnr_sig_array)
    rcv_noise_pwr_ch = calculate_total_opt_pwr(osnr_noise_array)

    if rcv_noise_pwr_ch > 0 and rcv_sig_pwr_ch > 0:
        osnr_linear = rcv_sig_pwr_ch / rcv_noise_pwr_ch
        osnr = 10 * np.log10(osnr_linear)
    else:
        osnr = 'NA'
    if osnr != 'NA':
        #q_osnr = np.sqrt((osnr_linear/2) * (opt_filter_bw/rbw))
        # Ref 4, slide 286 (assumes NRZ, infinite extinction ratio, ASE only)
        q_osnr = (osnr_linear * np.sqrt(opt_filter_bw / rbw)) / (
            np.sqrt(2 * osnr_linear + 1) + 1)
    else:
        q_osnr = 'NA'
    m = opt_filter_bw / rbw
    # Ref 4, slide 286 (assumes NRZ, infinite extinction ratio, ase only)
    osnr_target = (2 * q_target * q_target / m) + (2 * q_target / np.sqrt(m))
    osnr_target = 10 * np.log10(osnr_target)
    """ Calculate responsivities (for each channel)--------------------------------------------------------------"""
    r = np.empty(channels)
    for ch in range(0, channels):
        r[ch] = r_direct
    if r_qe_active == 'QE':
        for ch in range(0, channels):
            r[ch] = (qe * q) / (h * (wave_freq[ch])
                                )  # R = QE*q/h*(wave_freq)  (Ref 1, Eq 2.117)
    """ Extinction ratio calculation--------------------------------------------------------------------------------"""
    ch_index = 0
    indices = np.where(wave_key == ch_key_ref)
    if np.size(indices[0]) > 0:
        ch_index = indices[0][0]
    P0 = 0
    P1 = 0
    avg_pwr = calculate_avg_opt_pwr(opt_field_rcv[ch_index] +
                                    noise_field_rcv[ch_index])
    count_P0 = 0
    count_P1 = 0
    for i in range(0, n):
        pwr = np.square(
            np.abs(opt_field_rcv[ch_index, i] + noise_field_rcv[ch_index, i]))
        if pwr > avg_pwr:
            P1 += pwr
            count_P1 += 1
        else:
            P0 += pwr
            count_P0 += 1
    if count_P1 > 0:
        P1 = P1 / count_P1
    P0 = P0 / count_P0
    ext_ratio_linear = P1 / P0

    # Export extinction ratio to project config
    #proj.ext_ratio_linear = ext_ratio_linear
    ext_ratio_rcvr_db = 10 * np.log10(P1 / P0)
    r_ext = P0 / P1
    """ Calculate total received fields-----------------------------------------------------------------------------"""
    e_field_input_super_x = np.full(n, 0 + 1j * 0, dtype=complex)
    e_field_input_super_y = np.full(n, 0 + 1j * 0, dtype=complex)
    e_field_noise_super_x = np.full(n, 0 + 1j * 0, dtype=complex)
    e_field_noise_super_y = np.full(n, 0 + 1j * 0, dtype=complex)

    for ch in range(0, channels):
        # If coherent selected, model interference effects (signal beating)
        if opt_regime == 'Coherent':
            for i in range(0, n):
                if opt_channels[0][3].ndim == 2:
                    opt_field_rcv[ch, 0, i] = opt_field_rcv[ch, 0, i] * np.exp(
                        1j * 2 * pi * wave_freq[ch] * time_array[i])
                    opt_field_rcv[ch, 1, i] = opt_field_rcv[ch, 1, i] * np.exp(
                        1j * 2 * pi * wave_freq[ch] * time_array[i])
                else:
                    opt_field_rcv[ch, i] = opt_field_rcv[ch, i] * np.exp(
                        1j * 2 * pi * wave_freq[ch] * time_array[i])
                # Co-polarized component of noise field
                #noise_field_rcv_cpol[ch, i] = noise_field_rcv_cpol[ch, i]*np.exp(1j*2*pi*wave_freq[ch]*time_array[i])

            # Add channel fields together (linear superposition)
            if opt_channels[0][3].ndim == 2:
                e_field_input_super_x += opt_field_rcv[ch, 0]
                e_field_input_super_y += opt_field_rcv[ch, 1]
                e_field_noise_super_x += jones_vector[ch, 0] * (
                    noise_field_rcv[ch])
                e_field_noise_super_y += jones_vector[ch, 1] * (
                    noise_field_rcv[ch])
            else:
                e_field_input_super_x += opt_field_rcv[ch]
                e_field_noise_super_x += noise_field_rcv[ch]
                #e_field_noise_super_x += noise_field_rcv_cpol[ch]

            # If optical noise/numerical is selected, add noise field to received signals
            # NOT BEING CALLED-------------
            if include_optical_noise == 2 and optical_noise_model == 'Numerical':
                if opt_channels[0][3].ndim == 2:
                    e_field_input_super_x += e_field_noise_super_x
                    e_field_input_super_y += e_field_noise_super_y
                else:
                    e_field_input_super_x += e_field_noise_super_x
        """ Calculate received optical powers - |E(t)|^2-----------------------------------------------------------"""
        rcv_pwr_total = 0
        rcv_pwr = 0
        opt_noise_pwr_total = 0
        opt_noise_pwr = 0
        if opt_regime == 'Coherent':
            if opt_channels[0][3].ndim == 2:
                # Total power
                rcv_pwr_total = calculate_total_opt_pwr(e_field_input_super_x)
                rcv_pwr_total += calculate_total_opt_pwr(e_field_input_super_y)
                # Average power
                rcv_pwr = calculate_avg_opt_pwr(e_field_input_super_x)
                rcv_pwr += calculate_avg_opt_pwr(e_field_input_super_y)
                # Noise power (total)
                opt_noise_pwr_total = calculate_total_opt_pwr(
                    e_field_noise_super_x)
                opt_noise_pwr_total += calculate_total_opt_pwr(
                    e_field_noise_super_y)
                opt_noise_pwr = calculate_avg_opt_pwr(e_field_noise_super_x)
                opt_noise_pwr += calculate_avg_opt_pwr(e_field_noise_super_y)
            else:
                rcv_pwr_total = calculate_total_opt_pwr(e_field_input_super_x)
                rcv_pwr = calculate_avg_opt_pwr(e_field_input_super_x)
                opt_noise_pwr_total = calculate_total_opt_pwr(
                    e_field_noise_super_x)
                opt_noise_pwr = calculate_avg_opt_pwr(e_field_noise_super_x)
        else:
            if opt_channels[0][3].ndim == 2:
                rcv_pwr_total += calculate_total_opt_pwr(opt_field_rcv[ch, 0])
                rcv_pwr_total += calculate_total_opt_pwr(opt_field_rcv[ch, 1])
                rcv_pwr += calculate_avg_opt_pwr(opt_field_rcv[ch, 0])
                rcv_pwr += calculate_avg_opt_pwr(opt_field_rcv[ch, 1])
            else:
                rcv_pwr_total += calculate_total_opt_pwr(opt_field_rcv[ch])
                rcv_pwr += calculate_avg_opt_pwr(opt_field_rcv[ch])
            opt_noise_pwr_total += calculate_total_opt_pwr(noise_field_rcv[ch])
            opt_noise_pwr += calculate_avg_opt_pwr(noise_field_rcv[ch])

    # Calculate noise PSD
    opt_noise_psd = opt_noise_pwr_total / fs / n
    if opt_noise_psd > 0:
        opt_noise_psd_dbm = 10 * np.log10(opt_noise_psd * 1e3)
    else:
        opt_noise_psd_dbm = 'NA'
    """Calculate detector currents -------------------------------------------------------------------"""
    i_signal = np.zeros(n)
    m = 1
    if detection_model == 'APD':
        m = m_apd
    r_mean = np.mean(r)
    # COHERENT MODEL----------------------------------------------------
    # Ref 3 (Eq 3.10): I(t) = [E1(Ch1) + E(Ch2) + ... + E(ChN)] x [(E1(Ch1) + E(Ch2) + ... + E(ChN)]*
    # i_received = responsivity*I(t)
    # Export responsivity to project config
    # proj.r = r_mean*m # Include amplification factor for APD
    if opt_regime == 'Coherent':
        if opt_channels[0][3].ndim == 2:
            i_signal = m * r_mean * np.real(
                e_field_input_super_x * np.conjugate(e_field_input_super_x))
            i_signal += m * r_mean * np.real(
                e_field_input_super_y * np.conjugate(e_field_input_super_y))
        else:
            i_signal = m * r_mean * np.real(
                e_field_input_super_x * np.conjugate(e_field_input_super_x))
        '''if include_optical_noise == 2 and optical_noise_model == 'Numerical':
            for ch in range(0, channels):
                i_signal += m*r[ch]*np.square(np.abs(noise_field_rcv_orth[ch]))'''
    else:
        for ch in range(0, channels):
            i_signal += m * r[ch] * np.square(np.abs(opt_field_rcv[ch]))

    i_signal_mean = np.mean(i_signal)
    # Calculate average number of received photons (per symbol period)
    photons_avg = np.round(
        ((i_signal_mean * t_step) / (q * r[0])) * samples_per_sym)

    # APD excess noise factor calculation (x is noise coefficient, k is ionization coefficient)
    # Ref 1, Eqs 4.27/4.28 & Table 4.1
    # InGaAs (x=0.5-0.8, k=0.3-0.6), Ge (x=1.0, k=0.7-1.0),
    # Si (x=0.4-0.5, k=0.02-0.04)
    if enf_model == 'Noise coeff.':
        enf_apd = np.power(m_apd, x_apd)  # Excess noise factor
    else:
        enf_apd = k_apd * m_apd + (1 - k_apd) * (2 - (1 / m_apd))
    """Calculate thermal noise (Ref 1, Section 4.1.6)------------------------------------------------"""
    i_th = np.zeros(n)
    th_sigma = 0
    th_variance = 0
    if th_noise_active == 2:
        if th_noise_model == 'PSD':  #Calculate thermal noise based on PSD (defined)
            th_variance = rbw * th_noise_psd  #Ref 1, Eq 4.32
        else:  #Calculate thermal noise variance based on load resistance (circuit model)
            k = constants.k  # Boltzmann constant
            th_variance = rbw * 4 * k * noise_temp / r_load  #Ref 1, Eq 4.32
        th_sigma = np.sqrt(th_variance)
        i_th = np.random.normal(0, th_sigma, n)  # Thermal noise current array
    """Calculate shot noise (Ref 1, Section 4.1.4)----------------------------------------------------"""
    i_shot = np.zeros(n)
    shot_sigma = 0
    shot_sigma_avg = 0
    shot_variance_avg = 0
    if shot_noise_active == 2:
        for i in range(0, n):
            if shot_noise_model == 'Gaussian' or detection_model == 'APD':
                shot_variance = 2 * q * i_signal[i] * rbw  # Ref 1, Eq 4.24
                if detection_model == 'APD':
                    # MV 20.01.r3 15-Jun-20: Bug fix, i_signal was already increased by
                    # factor of m_apd (during i_signal calculation) so m_apd^2 was changed
                    # to m_apd
                    shot_variance = m_apd * enf_apd * shot_variance
                shot_sigma = np.sqrt(shot_variance)
                i_shot_sample = np.random.normal(0, shot_sigma, 1)
            else:  #Poisson
                mean_photons = round(
                    (i_signal[i] * t_step) / q)  #Ref 1, Eq. 4.20
                photons_detected = np.random.poisson(mean_photons, 1)
                i_shot_sample = photons_detected * q / t_step  # Convert to current (Ref 1, Eq. 4.22)
                i_shot_sample = i_shot_sample - i_signal[
                    i]  # MV 20.01.r3 21-Aug-20
            i_shot[i] = i_shot_sample
        # Calculate average photons + shot noise variance
        shot_variance_avg = 2 * q * i_signal_mean * rbw
        shot_sigma_avg = np.sqrt(shot_variance_avg)
    """Calculate noise variances (ASE)-----------------------------------------------------------------"""
    i_sig_ase = np.zeros(n)
    i_ase_ase = np.zeros(n)
    sig_ase_variance = 0
    ase_ase_variance = 0
    sig_ase_variance_avg = 0
    count = 0
    pwr_ase = 0
    psd_ase = 0
    if include_optical_noise == 2 and optical_noise_model == 'Analytical':
        # Check if psd has already been converted (from optical noise received)
        if opt_noise_psd_dbm < -200.0:
            ng_w = psd_array[0, 1] - psd_array[0, 0]
            ng = len(psd_array[0, :])
            for ch in range(0, channels):
                # Build time-domain freq points
                T = n / fs
                k = np.arange(n)
                frq = (k / T)
                frq = frq - frq[int(round(n / 2))] + wave_freq[ch]
                for i in range(0, ng):
                    if psd_array[0, i] > frq[0] and psd_array[0,
                                                              i] < frq[n - 1]:
                        count += 1
                        pwr_ase += 2 * psd_array[
                            1, i] * ng_w  # Ref 1, Eq 4.37 (Pwr = 2*psd_ase*bw)
                        #psd_ase += psd_array[1, i]
                        psd_array[1, i] = 0
            # Calculate average PSD
            #psd_ase = psd_ase/count
            psd_ase = pwr_ase / fs
        else:
            psd_ase = opt_noise_psd
        # Ref 1, Eq 4.42 & Ref 4, Slide 271
        ase_ase_variance = 2 * (r_mean**2) * (psd_ase**2) * (
            (2 * opt_filter_bw) - rbw) * rbw
        i_ase_ase = np.random.normal(0, np.sqrt(ase_ase_variance), n)
        # Calculate signal-ase beating noise
        for i in range(0, n):
            s_pwr = np.square(np.abs(opt_field_rcv[ch_index, i]))
            # Ref 1, Eq 4.43 & Ref 4, Slide 271
            sig_ase_variance = 4 * (r_mean**2) * (s_pwr * psd_ase) * rbw
            i_sig_ase[i] = np.random.normal(0, np.sqrt(sig_ase_variance), 1)
        # Calculate average variance (all samples)
        sig_ase_variance_avg = 4 * (r_mean**2) * rcv_pwr * psd_ase * rbw
    """Calculation of noise statistics for results-----------------------------------------------------"""
    # Dark current noise (Ref 1, Section 4.1.5)
    noise_d_variance = 2 * q * i_d * rbw
    if detection_model == 'APD':
        noise_d_variance = np.square(
            m_apd) * enf_apd * noise_d_variance  #Ref 1, Eq 4.30
    noise_d_sigma = np.sqrt(noise_d_variance)
    i_d_noise = np.random.normal(0, noise_d_sigma, n)

    # Noise statistics (thermal) - for results
    th_psd_measured = np.var(i_th) / rbw
    if th_psd_measured > 0:
        th_psd_measured_dbm = 10 * np.log10(th_psd_measured * 1e3)
    else:
        th_psd_measured_dbm = 'NA'
    th_noise_current_measured = np.sqrt(np.var(i_th))

    # Noise statistics (shot) - for results
    shot_psd_measured = np.var(i_shot) / rbw
    if shot_psd_measured > 0:
        shot_psd_measured_dbm = 10 * np.log10(shot_psd_measured * 1e3)
    else:
        shot_psd_measured_dbm = 'NA'
    shot_noise_current_measured = np.sqrt(np.var(i_shot))

    #Noise statistics (dark) - for results
    dark_psd_measured = np.var(i_d_noise) / rbw
    if dark_psd_measured > 0:
        dark_psd_measured_dbm = 10 * np.log10(dark_psd_measured * 1e3)
    else:
        dark_psd_measured_dbm = 'NA'
    dark_noise_current_measured = np.sqrt(np.var(i_d_noise))

    #Noise statistics (sig_ase) - for results
    sig_ase_psd_measured = np.var(i_sig_ase) / rbw
    if sig_ase_psd_measured > 0:
        sig_ase_psd_measured_dbm = 10 * np.log10(sig_ase_psd_measured * 1e3)
    else:
        sig_ase_psd_measured_dbm = 'NA'
    sig_ase_current_measured = np.sqrt(np.var(i_sig_ase))

    #Noise statistics (ase_ase) - for results
    ase_ase_psd_measured = np.var(i_ase_ase) / rbw
    if ase_ase_psd_measured > 0:
        ase_ase_psd_measured_dbm = 10 * np.log10(ase_ase_psd_measured * 1e3)
    else:
        ase_ase_psd_measured_dbm = 'NA'
    ase_ase_current_measured = np.sqrt(np.var(i_ase_ase))

    #Total noise current (calculated)
    i_noise_calculated = np.sqrt(th_variance + shot_variance_avg +
                                 noise_d_variance + sig_ase_variance_avg +
                                 ase_ase_variance)

    #Total noise current (measured)
    i_noise_measured = np.sqrt(
        np.var(i_th) + np.var(i_shot) + np.var(i_d_noise) + np.var(i_sig_ase) +
        np.var(i_ase_ase))

    #Calculate noise current (total)--------------------------------------------------------------------
    i_noise = i_th + i_shot + i_d_noise + i_sig_ase + i_ase_ase
    """Add noise current to detected signal (if add signal to noise is True)--------------------------"""
    if add_noise_to_signal == 2:
        i_signal = i_signal + i_noise
        i_noise = np.zeros(n)  # MV 20.01.r3 22-Jun-20
    i_mean = np.mean(i_signal)
    i_mean_1 = np.mean(i_signal[i_signal > i_mean])
    i_mean_0 = np.mean(i_signal[i_signal < i_mean])
    i_sigma_1 = np.std(i_signal[i_signal > i_mean])
    i_sigma_0 = np.std(i_signal[i_signal < i_mean])
    if i_sigma_1 == 0 and i_sigma_0 == 0:
        q_measured = 'NA'
    else:
        q_measured = (i_mean_1 - i_mean_0) / (i_sigma_1 + i_sigma_0)
    """Calculate receiver sensitivities--------------------------------------------------------------------"""
    # Ref 1, Eq. 4.66
    m = 1
    f_m = 1
    if detection_model == 'APD':
        m = m_apd
        f_m = enf_apd
    pwr_sensitivity = (q_target / r_mean) * ((th_noise_current_measured / m) +
                                             (q * q_target * f_m * rbw))
    pwr_sensitivity_dbm = 10 * np.log10(pwr_sensitivity * 1e3)

    #Pre-amplifier (optical noise) Ref 1 - Eq 4.75
    #pwr_sensitivity_amp = ( nf*constants.h*wave_freq_mean*rbw*((q_target**2)
    #                                     + q_target*np.sqrt((bw_opt/rbw)-0.5)) )
    #pwr_sensitivity_amp_dbm = 10*np.log10(pwr_sensitivity_amp*1e3)

    # Calculations of receiver sensitivity with extinction ratio
    q_target_ex = q_target * ((1 + r_ext) / (1 - r_ext))
    pwr_sensitivity_ex = (q_target_ex / r_mean) * (
        (th_noise_current_measured / m) + (q * q_target_ex * f_m * rbw))
    pwr_sensitivity_ex_dbm = 10 * np.log10(pwr_sensitivity_ex * 1e3)
    pwr_penalty_ext_dB = pwr_sensitivity_ex_dbm - pwr_sensitivity_dbm
    '''==OUTPUT PARAMETERS LIST============================================='''
    pin_apd_parameters = []
    pin_apd_parameters = parameters_input
    '''==RESULTS============================================================'''
    results = []
    """ Noise metrics-----------------------------------------------------------------------------"""
    # General results
    results.append(['General results', '', '', '', True])
    #rcv_pwr = 1 # MV 20.01.r3 4-Jun-20 (Commented out)
    rcv_pwr_dbm = 10 * np.log10(rcv_pwr * 1e3)
    results.append(
        ['Received optical pwr (avg)', rcv_pwr_dbm, 'dBm', ' ', False, '0.2f'])
    results.append([
        'Average photons received per symbol period', photons_avg, '', '',
        False
    ])
    results.append([
        'Average photocurrent (detected)', i_signal_mean * 1e3, 'mA', ' ',
        False
    ])
    results.append([
        'Optical noise PSD (before detection)', opt_noise_psd_dbm, 'dBm/Hz',
        '', False, '0.2f'
    ])
    results.append(['Responsivity (mean)', r_mean, 'A/W', ' ', False, '0.2f'])
    results.append(
        ['Excess noise factor (APD)', enf_apd, ' ', ' ', False, '0.2f'])

    if display_pin_apd_noise_metrics == 2:
        # Noise data (thermal)
        results.append(['Noise statistics (thermal)', '', '', '', True])
        results.append([
            'Thermal noise PSD (linear)', th_psd_measured, 'A^2/Hz', ' ', False
        ])
        results.append([
            'Thermal noise PSD (log)', th_psd_measured_dbm, 'dBm/Hz', ' ',
            False
        ])
        results.append([
            'Thermal noise current', th_noise_current_measured * 1e9, 'nA',
            ' ', False
        ])

        # Noise data (shot)
        results.append(['Noise statistics (shot)', '', '', '', True])
        results.append([
            'Shot noise PSD (linear)', shot_psd_measured, 'A^2/Hz', ' ', False
        ])
        results.append([
            'Shot noise PSD (log)', shot_psd_measured_dbm, 'dBm/Hz', ' ', False
        ])
        results.append([
            'Shot noise current', shot_noise_current_measured * 1e9, 'nA', ' ',
            False
        ])

        # Noise data (dark current)
        results.append(['Noise statistics (dark current)', '', '', '', True])
        results.append([
            'Dark current noise PSD (linear)', dark_psd_measured, 'A^2/Hz',
            ' ', False
        ])
        results.append([
            'Dark current noise PSD (log)', dark_psd_measured_dbm, 'dBm/Hz',
            ' ', False
        ])
        results.append([
            'Dark current noise', dark_noise_current_measured * 1e9, 'nA', ' ',
            False
        ])

    if display_optical_noise_metrics == 2:
        # Noise data (sig-ASE)
        results.append(
            ['Noise statistics analytical (Sig-ASE)', '', '', '', True])
        results.append([
            'Sig-ASE PSD (linear)', sig_ase_psd_measured, 'A^2/Hz', ' ', False
        ])
        results.append([
            'Sig-ASE PSD (log)', sig_ase_psd_measured_dbm, 'dBm/Hz', ' ', False
        ])
        results.append([
            'Sig-ASE noise current', sig_ase_current_measured * 1e9, 'nA', ' ',
            False
        ])

        # Noise data (sig-ASE)
        results.append(
            ['Noise statistics analytical (ASE-ASE)', '', '', '', True])
        results.append([
            'ASE-ASE PSD (linear)', ase_ase_psd_measured, 'A^2/Hz', ' ', False
        ])
        results.append([
            'ASE-ASE PSD (log)', ase_ase_psd_measured_dbm, 'dBm/Hz', ' ', False
        ])
        results.append([
            'ASE-ASE noise current', ase_ase_current_measured * 1e9, 'nA', ' ',
            False
        ])
    """ Receiver performance metrics-----------------------------------------------------------"""
    results.append(['Performance metrics', '', '', '', True])
    ber_estimate = 0.5 * special.erfc(q_measured / np.sqrt(2))
    results.append([
        'Extinction ratio (P1/P0 - linear)', ext_ratio_linear, ' ', ' ', False,
        '0.3f'
    ])
    results.append([
        'Extinction ratio (P1/P0)', ext_ratio_rcvr_db, 'dB', ' ', False, '0.2f'
    ])
    results.append([
        'Extinction ratio (P0/P1 - linear)', r_ext * 100, '%', ' ', False,
        '0.2f'
    ])
    #penalty = 10*np.log10((1+r_ext)/((1-r_ext)))
    #penalty = 10*np.log10((ext_ratio_linear+1)/((ext_ratio_linear-1)))
    #results.append(['Penalty', penalty, 'dB', ' ', False, '0.2f'])
    # Noise current (total)
    results.append(
        ['Total noise current', i_noise_measured * 1e9, 'nA', ' ', False])
    # Q/BER target
    results.append(['Q (target)', q_target, ' ', ' ', False, '0.2f'])
    ber_target = 0.5 * special.erfc(q_target / np.sqrt(2))  #Ref 1, Eq. 4.56
    results.append(['BER (target)', ber_target, ' ', ' ', False])
    # Q measured
    results.append(['Q (measured)', q_measured, ' ', ' ', False, '0.2f'])
    # OSNR
    #results.append(['OSNR linear (avg sig pwr)', osnr_linear, ' ', ' ', False, '0.2f'])
    results.append(['OSNR (avg sig pwr)', osnr, 'dB', ' ', False, '0.2f'])
    results.append(
        ['Q (OSNR) - ideal ER, ASE only', q_osnr, ' ', ' ', False, '0.2f'])
    results.append([
        'OSNR target - ideal ER, ASE only', osnr_target, 'dB', ' ', False,
        '0.2f'
    ])
    # SNR
    if i_noise_calculated == 0:
        snr = 'NA'
        snr_db = 'NA'
    else:
        snr = np.square(i_signal_mean) / np.square(i_noise_calculated)
        snr_db = 10 * np.log10(snr)
    #results.append(['SNR ', snr, ' ', ' ', False, '0.3f'])
    results.append(['SNR (dB)', snr_db, 'dB', ' ', False, '0.3f'])
    # Sensitivity (optical)
    #results.append(['Optical receiver sensitivity - th/shot', pwr_sensitivity, 'W', ' ', False])
    results.append([
        'Optical receiver sensitivity - th/shot', pwr_sensitivity_dbm, 'dBm',
        ' ', False, '0.2f'
    ])
    #results.append(['Optical receiver sensitivity - th/shot', pwr_sensitivity, 'W', ' ', False])
    results.append([
        'Optical receiver sensitivity - th/shot/ER', pwr_sensitivity_ex_dbm,
        'dBm', ' ', False, '0.2f'
    ])

    # Send data to project folder (only if project file has been created)----------------------------
    if os.path.isfile(path) and enable_data_export == 2:
        if iteration == 1:
            setattr(proj, data_att_1, [])
            getattr(proj, data_att_1).append(rcv_pwr_dbm)
        else:
            getattr(proj, data_att_1).append(rcv_pwr_dbm)
    """Data panel output-------------------------------------------------------------------------------------"""
    c_analytical = 'blue'
    config.data_tables[data_panel_id] = []
    data_list = []
    data_list.append(
        ['Iteration #', iteration, '0.0f', ' ', ' ', c_analytical])
    data_list.append(
        ['Responsivity', r_mean, '0.2f', 'A/W', ' ', c_analytical])
    #data_list.append(['Target Q for link', q_target, '0.1f', ' '])
    #data_list.append(['Target BER for link', ber_target, '0.3E', ' '])
    #data_list.append(['Q measured (noise)', q_measured, '0.2f', ' '])
    data_list.append(
        ['Optical pwr received (avg)', rcv_pwr_dbm, '0.2f', 'dBm'])
    data_list.append(
        ['Extinction ratio (linear)', ext_ratio_linear, '0.3f', ' '])
    data_list.append(
        ['Extinction ratio (ER)', ext_ratio_rcvr_db, '0.2f', 'dB'])
    #data_list.append(['Receiver sensitivity (noise)', pwr_sensitivity_dbm, '0.2f', 'dBm'])
    #data_list.append(['ER power penalty', pwr_penalty_ext_dB, '0.2f', 'dB'])
    #data_list.append(['Receiver sensitivity (noise/ER)', pwr_sensitivity_ex_dbm, '0.2f', 'dBm'])
    config.data_tables[data_panel_id].extend(data_list)
    '''==RETURN (Output Signals, Parameters, Results)=================================='''
    return ([[2, signal_type, 0, fs, time_array, i_signal,
              i_noise]], pin_apd_parameters, results)
Пример #9
0
def run(input_signal_data, parameters_input, settings):
    '''==PROJECT SETTINGS==================================================='''
    module_name = settings['fb_name']
    n = settings['num_samples']
    n = int(round(n))
    iteration = settings['current_iteration']
    time = settings['time_window']
    fs = settings['sampling_rate']
    f_sym = settings['symbol_rate']

    path = settings['file_path_1']
    path = os.path.join(path, 'project_config.py')
    if os.path.isfile(path):
        import project_config as proj
    """Status messages-----------------------------------------------------------------------"""
    # Status message - initiation of fb_script (Sim status panel & Info/Status window)
    fb_title_string = 'Running ' + str(module_name) + ' - Iteration #: ' + str(
        iteration)
    config.status_message(fb_title_string)
    # Data display - title of current fb_script
    config.display_data(' ', ' ', False, False)
    fb_data_string = 'Data output for ' + str(module_name) + ' - Iteration #: '
    # Display data settings: Data title (str), Data (scalar, array, etc),
    # Set to Bold?, Title & Data on separate lines?
    config.display_data(fb_data_string, iteration, False, True)
    '''==INPUT PARAMETERS===================================================
    '''
    decision_mode = str(parameters_input[0][1])
    dc_block = int(parameters_input[1][1])
    normalize = int(parameters_input[2][1])
    decision_th = float(parameters_input[3][1])
    optimize_decision_th = int(parameters_input[4][1])
    decision_pt = float(parameters_input[5][1])
    add_noise_to_signal = int(parameters_input[6][1])
    dist_plot = int(parameters_input[8][1])
    n_bins = int(parameters_input[9][1])
    data_panel_id = str(parameters_input[11][1])
    data_att_1 = str(parameters_input[12][1])
    data_att_2 = str(parameters_input[13][1])
    '''==CALCULATIONS=======================================================
    '''
    order = 1
    bit_rate = f_sym * order
    samples_per_sym = int(fs / f_sym)
    n_sym = int(round(n / samples_per_sym))
    sig_type = input_signal_data[0][1]
    carrier = input_signal_data[0][2]
    time_array = input_signal_data[0][4]
    sampled_sig_in = copy.deepcopy(input_signal_data[0][5])
    noise_array = copy.deepcopy(input_signal_data[0][6])

    if add_noise_to_signal == 2:
        sampled_sig_in += noise_array
        noise_array = np.zeros(n)
    digital_out = np.zeros(n_sym, dtype=int)
    sig_avg = np.mean(np.real(sampled_sig_in))

    #DC block (if enabled)
    if dc_block == 2:
        sampled_sig_in = sampled_sig_in - sig_avg
        #config.display_xy_data('Signal after DC block', time, 'Time (s)', sampled_sig_in, 'Magnitude')

    if normalize == 2:
        if dc_block == 0:
            sampled_sig_in = sampled_sig_in - sig_avg
        sampled_sig_in = sampled_sig_in / np.max(np.real(sampled_sig_in))
        # Re-calculate signal average
        sig_avg = np.mean(np.real(sampled_sig_in))
        #config.display_xy_data('Signal after Normalize', time, 'Time (s)', sampled_sig_in, 'Magnitude')

    if decision_mode == 'Signal average':
        decision_th = sig_avg

    # Perform decisions (@ decision point)
    decision_samples = np.zeros(n_sym)
    for sym in range(0, n_sym):
        sampling_index = int(sym * samples_per_sym +
                             round(samples_per_sym * decision_pt))
        decision_samples[sym] = sampled_sig_in[sampling_index]
        if decision_samples[sym] >= decision_th:
            digital_out[sym] = 1

    # Calculate statistics
    v0_mean = np.mean(decision_samples[decision_samples < decision_th])
    v0_sigma = np.std(decision_samples[decision_samples < decision_th])
    v1_mean = np.mean(decision_samples[decision_samples > decision_th])
    v1_sigma = np.std(decision_samples[decision_samples > decision_th])
    q_measured = (v1_mean - v0_mean) / (v1_sigma + v0_sigma)
    ber_estimate = 0.5 * special.erfc(q_measured / np.sqrt(2))

    # Optimized decision point (informative) REF 1, Eq 4.55
    decision_opt = (v1_sigma * v0_mean + v0_sigma * v1_mean) / (v1_sigma +
                                                                v0_sigma)

    # Perform decisions (@ decision point)
    if optimize_decision_th == 2:
        decision_th = decision_opt
        for sym in range(0, n_sym):
            if decision_samples[sym] >= decision_th:
                digital_out[sym] = 1

    # Distribution analysis
    if dist_plot == 2:
        title = 'Signal amplitude distribution (' + module_name + ' - Iteration ' + str(
            iteration) + ')'
        config.dist_graph[module_name +
                          str(iteration)] = config.view.Distribution_Analysis(
                              title, decision_samples, 'Signal (V)', n_bins,
                              decision_th, v1_mean, v0_mean, v1_sigma,
                              v0_sigma)
        config.dist_graph[module_name + str(iteration)].show()

    # Send data to project folder (only if project file has been created)
    if os.path.isfile(path):
        if iteration == 1:
            if data_att_1:
                setattr(proj, data_att_1, [])
                getattr(proj, data_att_1).append(q_measured)
            if data_att_2:
                setattr(proj, data_att_2, [])
                getattr(proj, data_att_2).append(ber_estimate)
        else:
            if data_att_1:
                getattr(proj, data_att_1).append(q_measured)
            if data_att_2:
                getattr(proj, data_att_2).append(ber_estimate)
    '''==OUTPUT PARAMETERS LIST===========================================================
    '''
    decision_parameters = []
    decision_parameters = parameters_input
    '''==RESULTS============================================================'''
    results = []
    results.append([
        'Threshold level (used for decision)', decision_th, ' ', ' ', False,
        '0.3E'
    ])
    results.append([
        'Optimized decision threshold', decision_opt, ' ', ' ', False, '0.3E'
    ])
    results.append(
        ['V1 mean (at decision pt)', v1_mean, ' ', ' ', False, '0.3E'])
    results.append(
        ['V0 mean (at decision pt)', v0_mean, ' ', ' ', False, '0.3E'])
    results.append(
        ['V1 std dev (at decision pt)', v1_sigma, ' ', ' ', False, '0.3E'])
    results.append(
        ['Q measured (at decision pt)', q_measured, ' ', ' ', False, '0.2f'])
    results.append([
        'BER estimated (at decision pt)', ber_estimate, ' ', ' ', False, '0.3E'
    ])
    '''==RESULTS============================================================'''
    c_analytical = 'blue'
    config.data_tables[data_panel_id] = []
    data_list = []
    data_list.append(
        ['Iteration #', iteration, '0.0f', ' ', ' ', c_analytical])
    data_list.append(['Q (measured)', q_measured, '0.2f', ' '])
    data_list.append(
        ['BER (estimated)', ber_estimate, '0.3E', ' ', ' ', c_analytical])
    config.data_tables[data_panel_id].extend(data_list)

    return ([[2, 'Digital', f_sym, bit_rate, order, time_array, digital_out],
             [
                 3, sig_type, carrier, fs, time_array, sampled_sig_in,
                 noise_array
             ]], decision_parameters, results)