def frame_sync(rx_signal, ref_symbs, os, frame_len=2**16, M_pilot=4, mu=1e-3, Ntaps=17, **eqargs): """ Locate the pilot sequence frame Uses a CMA-based search scheme to located the initiial pilot sequence in the long data frame. Parameters ---------- rx_signal: array_like Received Rx signal ref_symbs: array_like Pilot sequence os: int Oversampling factor frame_len: int Total frame length including pilots and payload M_pilot: int, optional QAM-order for pilot symbols mu: float, optional CMA step size Ntaps: int, optional number of taps to use in the equaliser **eqargs: arguments to be passed to equaliser Returns ------- shift_factor: array_like location of frame start index per polarization foe_corse: array_like corse frequency offset mode_sync_order: array_like Synced descrambled reference pattern order """ # If synchronization fails, then change sync_bool to 'False' sync_bool = True FRAME_SYNC_THRS = 120 # this is somewhat arbitrary but seems to work well rx_signal = np.atleast_2d(rx_signal) ref_symbs = np.atleast_2d(ref_symbs) pilot_seq_len = ref_symbs.shape[-1] nmodes = rx_signal.shape[0] assert rx_signal.shape[-1] >= ( frame_len + 2 * pilot_seq_len) * os, "Signal must be at least as long as frame" if "method" in eqargs.keys(): if eqargs["method"] in equalisation.REAL_VALUED: if np.iscomplexobj(rx_signal): raise ValueError("Equaliser method is {}, but using a real-valued equaliser in frame sync is unsupported"\ .format(eqargs["method"])) elif eqargs["method"] in equalisation.DATA_AIDED: raise ValueError("Equaliser method is {}, but using a data-aided equaliser in frame sync is unsupported"\ .format(eqargs["method"])) mode_sync_order = np.zeros(nmodes, dtype=int) not_found_modes = np.arange(0, nmodes) search_overlap = 2 # fraction of pilot_sequence to overlap search_window = pilot_seq_len * os step = search_window // search_overlap # we only need to search the length of one frame*os plus some buffer (the extra step) num_steps = (frame_len * os) // step + 1 # Now search for every mode independent shift_factor = np.zeros(nmodes, dtype=int) # Search based on equalizer error. Avoid one pilot_seq_len part in the beginning and # end to ensure that sufficient symbols can be used for the search sub_vars = np.ones((nmodes, num_steps)) * 1e2 wxys = np.zeros((num_steps, nmodes, nmodes, Ntaps), dtype=rx_signal.dtype) for i in np.arange(search_overlap, num_steps): # we avoid one step at the beginning tmp = rx_signal[:, i * step:i * step + search_window] wxy, err_out = equalisation.equalise_signal(tmp, os, mu, M_pilot, Ntaps=Ntaps, **eqargs) wxys[i] = wxy sub_vars[:, i] = np.var(err_out, axis=-1) # Lowest variance of the CMA error for each pol min_range = np.argmin(sub_vars, axis=-1) wxy = wxys[min_range] for l in range(nmodes): idx_min = min_range[l] # Extract a longer sequence to ensure that the complete pilot sequence is found longSeq = rx_signal[:, (idx_min) * step - search_window:(idx_min) * step + search_window] # Apply filter taps to the long sequence and remove coarse FO wx1 = wxy[l] symbs_out = equalisation.apply_filter(longSeq, os, wx1) foe_corse = phaserecovery.find_freq_offset(symbs_out) symbs_out = phaserecovery.comp_freq_offset(symbs_out, foe_corse) # Check for pi/2 ambiguties and verify all max_phase_rot = np.zeros(nmodes, dtype=np.float64) found_delay = np.zeros(nmodes, dtype=np.int) for ref_pol in not_found_modes: ix, dat, ii, ac = ber_functions.find_sequence_offset_complex( ref_symbs[ref_pol], symbs_out[l]) found_delay[ref_pol] = -ix max_phase_rot[ref_pol] = ac # Check for which mode found and extract the reference delay max_sync_pol = np.argmax(max_phase_rot) if max_phase_rot[max_sync_pol] < FRAME_SYNC_THRS: # warnings.warn( "Very low autocorrelation, likely the frame-sync failed") sync_bool = False mode_sync_order[l] = max_sync_pol symb_delay = found_delay[max_sync_pol] # Remove the found reference mode not_found_modes = not_found_modes[not_found_modes != max_sync_pol] # New starting sample shift_factor[l] = (idx_min) * step + os * symb_delay - search_window # Important: the shift factors are arranged in the order of the signal modes, but # the mode_sync_order specifies how the signal modes need to be rearranged to match the pilots # therefore shift factors also need to be "mode_aligned" return shift_factor, foe_corse, mode_sync_order, wx1, sync_bool
def test_find_freq_offset(self, ndim): s = signals.SignalQAMGrayCoded(16, 2 ** 16, fb=20e9, nmodes=ndim) fo = cphaserecovery.find_freq_offset(s) assert ndim == fo.shape[0]
def frame_sync(rx_signal, ref_symbs, os, frame_length = 2**16, mu = 1e-3, M_pilot = 4, ntaps = 25, Niter = 10, adap_step = True, method='cma' ,search_overlap = 2): """ Locate and extract the pilot starting frame. Uses a CMA-based search scheme to located the initiial pilot sequence in the long data frame. Input: rx_signal: Received Rx signal ref_symbs: Pilot sequence os: Oversampling frame_length: Total frame length including pilots and payload mu: CMA step size. Tuple(Search, Convergence) M_pilot: Order for pilot symbols. Should normally be QPSK and M=4 ntaps: Number of T/2-spaced taps for equalization Niter: Number of iterations for the equalizer.Tuple(Search, Convergence) adap_step: Use adaptive step size (bool). Tuple(Search, Convergence) method: Equalizer mehtods to be used. Tuple(Search, Convergence) search_overlap: Overlap of subsequences in the test Output: eq_pilots: Found pilot sequence after equalization shift_factor: New starting point for initial equalization out_taps: Taps for equalization of the whole signal foe_course: Result of blind FOE used to sync the pilot sequence. """ # Inital settings rx_signal = np.atleast_2d(rx_signal) ref_symbs = np.atleast_2d(ref_symbs) npols = rx_signal.shape[0] # Find the length of the pilot frame pilot_seq_len = len(ref_symbs[0,:]) symb_step_size = int(np.floor(pilot_seq_len * os / search_overlap)) # Adapt signal length sig_len = (np.shape(rx_signal)[1]) if (sig_len > (frame_length + (search_overlap*2 + 5)*pilot_seq_len)*os): num_steps = int(np.ceil(((frame_length + (search_overlap*2 + 5) *pilot_seq_len)*os)/symb_step_size)) else: num_steps = int(np.ceil(np.shape(rx_signal)[1] / symb_step_size)) # Now search for every mode independent shift_factor = np.zeros(npols,dtype = int) for l in range(npols): # Search based on equalizer error. Avoid certain part in the beginning and # end to ensure that sufficient symbols can be used for the search sub_var = np.ones(num_steps)*1e2 for i in np.arange(2+(search_overlap),num_steps-3-(search_overlap)): err_out = equalisation.equalise_signal(rx_signal[:,(i)*symb_step_size:(i+1+(search_overlap-1))*symb_step_size], os, mu, M_pilot,Ntaps = ntaps, Niter = Niter, method = method,adaptive_stepsize = adap_step)[1] sub_var[i] = np.var(err_out[l,int(-symb_step_size/os+ntaps):]) # Lowest variance of the CMA error minPart = np.argmin(sub_var) # Corresponding sequence shortSeq = rx_signal[:,(minPart)*symb_step_size:(minPart+1+(search_overlap-1))*symb_step_size] # Extract a longer sequence to ensure that the complete pilot sequence is found longSeq = rx_signal[:,(minPart-2-search_overlap)*symb_step_size:(minPart+3+search_overlap)*symb_step_size] # Use the first estimate to get rid of any large FO and simplify alignment wx1, err = equalisation.equalise_signal(shortSeq, os, mu, M_pilot,Ntaps = ntaps, Niter = Niter, method = method,adaptive_stepsize = adap_step) seq_foe = equalisation.apply_filter(longSeq,os,wx1) foe_corse = phaserecovery.find_freq_offset(seq_foe) # Apply filter taps to the long sequence symbs_out= equalisation.apply_filter(longSeq,os,wx1) symbs_out[l,:] = phaserecovery.comp_freq_offset(symbs_out[l, :], foe_corse[l, :]) # Check for pi/2 ambiguties max_phase_rot = np.zeros([4]) found_delay = np.zeros([4]) for k in range(4): # Find correlation for all 4 possible pi/2 rotations xcov = np.correlate(np.angle(symbs_out[l,:]*1j**k),np.angle(ref_symbs[l,:])) max_phase_rot[k] = np.max(xcov) found_delay[k] = np.argmax(xcov) # Select the best one symb_delay = int(found_delay[np.argmax(max_phase_rot)]) # New starting sample shift_factor[l] = int((minPart-4)*symb_step_size + os*symb_delay) return shift_factor, foe_corse
def equalize_pilot_sequence(rx_signal, ref_symbs, shift_factor, os, sh = False, process_frame_id = 0, frame_length = 2**16, mu = (1e-4,1e-4), M_pilot = 4, ntaps = (25,45), Niter = (10,30), adap_step = (True,True), method=('cma','cma'),avg_foe_modes = True, do_pilot_based_foe=True,foe_symbs = None,blind_foe_payload=False): # Inital settings rx_signal = np.atleast_2d(rx_signal) ref_symbs = np.atleast_2d(ref_symbs) npols = rx_signal.shape[0] pilot_seq_len = len(ref_symbs[0,:]) eq_pilots = np.zeros([npols,pilot_seq_len],dtype = complex) tmp_pilots = np.zeros([npols,pilot_seq_len],dtype = complex) out_taps = [] # Tap update and extract the propper pilot sequuence if not((ntaps[1]-ntaps[0])%os == 0): raise ValueError("Taps for search and convergence impropper configured") tap_cor = int((ntaps[1]-ntaps[0])/2) # Run FOE and shift spectrum if not sh: # First Eq, extract pilot sequence to do FOE for l in range(npols): pilot_seq = rx_signal[:,shift_factor[l]-tap_cor:shift_factor[l]-tap_cor+pilot_seq_len*os+ntaps[1]-1] wx, err = equalisation.equalise_signal(pilot_seq, os, mu[0], M_pilot,Ntaps = ntaps[1], Niter = Niter[1], method = method[0],adaptive_stepsize = adap_step[1]) # wx, err = equalisation.equalise_signal(pilot_seq, os, mu[1], M_pilot, wxy = wx, Ntaps=ntaps[1], Niter=Niter[1], # method=method[0], adaptive_stepsize=adap_step[1]) symbs_out= equalisation.apply_filter(pilot_seq,os,wx) tmp_pilots[l,:] = symbs_out[l,:] # FOE Estimation, several options available. Default is pilot aided if do_pilot_based_foe: # Use the pilot-based FOE. Requires a pilot sequence of sufficient length if foe_symbs is None: foe, foePerMode, cond = pilot_based_foe(tmp_pilots, ref_symbs) else: num_symbs = int(foe_symbs) if num_symbs > pilot_seq_len: raise ValueError("Required number of symbols for FOE is larger than availabe sequence length. Maximum length available is %d"%pilot_seq_len) foe, foePerMode, cond = pilot_based_foe(tmp_pilots[:,:num_symbs], ref_symbs[:,:num_symbs]) # Equalize 1 frame and do blind 4:th power FFT-based estimation else: foe_est_symbs = np.zeros([npols, frame_length],dtype=complex) for l in range(npols): # If blind_foe_payload is true, use the payload for FOE estimation and not the pilot sequence. Aims at comparing blind power of 4 FOE to pilot-based if blind_foe_payload: est_sig = rx_signal[:, shift_factor[l] - tap_cor + pilot_seq_len*os:shift_factor[l] - tap_cor + frame_length * os + ntaps[1] - 1 + pilot_seq_len*os] else: est_sig = rx_signal[:,shift_factor[l]-tap_cor:shift_factor[l]-tap_cor+frame_length*os+ntaps[1]-1] est_frame = equalisation.apply_filter(est_sig,os,wx) foe_est_symbs[l,:] = est_frame[l,:] if foe_symbs is None: foePerMode = phaserecovery.find_freq_offset(foe_est_symbs) else: foePerMode = phaserecovery.find_freq_offset(foe_est_symbs[:, :foe_symbs], fft_size=foe_symbs) if avg_foe_modes: foe = np.mean(foePerMode) foePerMode = np.ones(foePerMode.shape)*foe # Apply FO-compensation sig_dc_center = phaserecovery.comp_freq_offset(rx_signal, foePerMode, os=os) else: # Signal should be DC-centered. Do nothing. sig_dc_center = rx_signal foePerMode = np.zeros([npols,1]) # First step Eq for l in range(npols): pilot_seq = sig_dc_center[:,shift_factor[l]-tap_cor:+shift_factor[l]-tap_cor+pilot_seq_len*os+ntaps[1]-1] wxy_init, err = equalisation.equalise_signal(pilot_seq, os, mu[1], 4, Ntaps = ntaps[1], Niter = Niter[1], method = method[0],adaptive_stepsize = adap_step[1]) wx, err = equalisation.equalise_signal(pilot_seq, os, mu[1], 4,wxy=wxy_init,Ntaps = ntaps[1], Niter = Niter[1], method = method[1],adaptive_stepsize = adap_step[1]) out_taps.append(wx) symbs_out = equalisation.apply_filter(pilot_seq,os,out_taps[l]) eq_pilots[l,:] = symbs_out[l,:] # Equalize using additional frames if selected for frame_id in range(0,process_frame_id+1): for l in range(npols): pilot_seq = sig_dc_center[:,frame_length*os*frame_id+shift_factor[l]-tap_cor:frame_length*os*frame_id+shift_factor[l]-tap_cor+pilot_seq_len*os+ntaps[1]-1] wx_1, err = equalisation.equalise_signal(pilot_seq, os, mu[1], 4,wxy=out_taps[l],Ntaps = ntaps[1], Niter = Niter[1], method = method[1],adaptive_stepsize = adap_step[1]) wx, err = equalisation.equalise_signal(pilot_seq, os, mu[1], 4,wxy=wx_1,Ntaps = ntaps[1], Niter = Niter[1], method = method[1],adaptive_stepsize = adap_step[1]) out_taps[l] = wx if frame_id == process_frame_id: symbs_out = equalisation.apply_filter(pilot_seq,os,out_taps[l]) eq_pilots[l,:] = symbs_out[l,:] shift_factor[l] = shift_factor[l] + process_frame_id*os*frame_length return eq_pilots, foePerMode, out_taps, shift_factor
X = analog_frontend.comp_IQ_inbalance(X) Y = analog_frontend.comp_IQbalance(Y) print(X.shape) print(Y.shape) SS = np.vstack([X[5000:-5000],Y[5000:-5000]]) SS = SS[:,:int(2e5)] E, wxy, err_both = equalisation.dual_mode_equalisation(SS, os, M, ntaps, Niter=(5, 5), methods=("mcma", "sbd"), adaptive_stepsize=(True, True)) X = signal_quality.norm_to_s0(E[0, :], M) Y = signal_quality.norm_to_s0(E[1, :], M) E = np.vstack([X,Y]) foe = phaserecovery.find_freq_offset(E, fft_size =2 ** 10) E = phaserecovery.comp_freq_offset(E, foe) #Ec = E[:,2e4:-2e4] wx, err_both = equalisation.equalise_signal(E, 1, M, Ntaps=ntaps, Niter=4, method="sbd", adaptive_stepsize=False) Ec = equalisation.apply_filter(E, 1, wx) E = Ec print("X pol phase") Ex, phx = phaserecovery.bps(E[0], 32, QAM.symbols, 8) print("X pol phase done") print("Y pol phase") Ey, phy = phaserecovery.bps(E[1], 32, QAM.symbols, 8)