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)
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)
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)
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)
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)