def pilot_based_cpe2(rec_symbs, pilot_symbs, num_average=1, use_pilot_ratio=1): """ Carrier phase recovery using periodically inserted symbols. Performs a linear interpolation with averaging over n symbols to estimate the phase drift from laser phase noise to compensate for this. Input: rec_symbs: Received symbols in block (first of each block is the pilot) pilot_symbs: Corresponding pilot symbols. Index N is the first symbol in transmitted block N. pilot_ins_ratio: Length of each block. Ex. 16 -> 1 pilot symbol followed by 15 data symbols num_average: Number of pilot symbols to average over to avoid noise. use_pilot_ratio: Use ever n pilots. Can be used to sweep required rate. max_num_blocks: Maximum number of blocks to process remove_phase_pilots: Remove phase pilots after CPE. Default: True Output: data_symbs: Complex symbols after pilot-aided CPE. Pilot symbols removed phase_trace: Resulting phase trace of the CPE """ #rec_symbs = np.atleast_2d(rec_symbs) #pilot_symbs = np.atleast_2d(pilot_symbs) pilots_symbs = rec_symbs.extract_pilots() npols = rec_symbs.shape[0] # Extract the pilot symbols #numBlocks = np.floor(np.shape(rec_symbs)[1]/pilot_ins_ratio) # If selected, only process a limited number of blocks. #if (max_num_blocks is not None) and numBlocks > max_num_blocks: # numBlocks = max_num_blocks # Make sure that a given number of pilots can be used if (numBlocks % use_pilot_ratio): numBlocks -= (numBlocks % use_pilot_ratio) # Adapt for number of blocks #rec_pilots = rec_symbs[:,::int(pilot_ins_ratio)] #rec_pilots = rec_pilots[:,:int(numBlocks)] #rec_symbs = rec_symbs[:,:int(pilot_ins_ratio*numBlocks)] # Check that the number of blocks are equal and is valid numRefPilots = np.shape(pilot_symbs)[1] if numBlocks > numRefPilots: numBlocks = numRefPilots rec_symbs = rec_symbs[:, :int(numBlocks * pilot_ins_ratio)] rec_pilots = rec_pilots[:, :int(numBlocks)] elif numRefPilots > numBlocks: pilot_symbs = pilot_symbs[:, :int(numBlocks)] # Remove every X pilot symbol if selected if use_pilot_ratio >= pilot_symbs.shape[1]: raise ValueError( "Can not use every %d pilots since only %d pilot symbols are present" % (use_pilot_ratio, pilot_symbs.shape[1])) rec_pilots = rec_pilots[:, ::int(use_pilot_ratio)] pilot_symbs = pilot_symbs[:, ::int(use_pilot_ratio)] # Check for a out of bounch error if pilot_symbs.shape[1] <= num_average: raise ValueError( "Inpropper pilot symbol configuration. Larger averaging block size than total number of pilot symbols" ) # Should be an odd number to keey symmetry in averaging if not (num_average % 2): num_average += 1 # Allocate output memory and process modes data_symbs = np.zeros([npols, np.shape(rec_symbs)[1]], dtype=complex) phase_trace = np.zeros([npols, np.shape(rec_symbs)[1]]) res_phase = pilot_symbs.conjugate() * rec_pilots pilot_phase_base = np.unwrap(np.angle(res_phase), axis=-1) pilot_phase_average = filter.moving_average(pilot_phase_base, num_average) pp1 = pilot_phase_base[:, :int((num_average - 1) / 2)] pp2 = pilot_phase_average[:] t = pilot_phase_base[:, :int((num_average - 1) / 2)] t2 = pp2[:, -1].reshape(2, 1) pp3 = np.ones(t.shape) * t2 pilot_phase = np.hstack([pp1, pp2, pp3]) pilot_pos = np.arange( 0, pilot_phase.shape[-1] * pilot_ins_ratio * use_pilot_ratio, pilot_ins_ratio * use_pilot_ratio) pilot_pos_new = np.arange( 0, pilot_phase.shape[-1] * pilot_ins_ratio * use_pilot_ratio) for l in range(npols): phase_trace[l, :] = np.interp(pilot_pos_new, pilot_pos, pilot_phaseN[l]) data_symbs = rec_symbs * np.exp(-1j * phase_trace) # Remove the phase pilots after compensation. This is an option since they can be used for SNR estimation e.g. if remove_phase_pilots: pilot_pos = np.arange(0, np.shape(data_symbs)[1], pilot_ins_ratio) data_symbs = np.delete(data_symbs, pilot_pos, axis=1) return data_symbs, phase_trace
def test_lengths(self): for i in range(100, 131): x = np.arange(i) for j in range(5, 30): assert len(cfilter.moving_average(x, j)) == i - j + 1
def pilot_based_cpe_new(signal, pilot_symbs, pilot_idx, frame_len, seq_len=None, num_average=1, use_pilot_ratio=1, max_num_blocks=None, nframes=1): """ Carrier phase recovery using periodically inserted symbols. Performs a linear interpolation with averaging over n symbols to estimate the phase drift from laser phase noise to compensate for this. Parameters ---------- signal : array_like input signal containing pilots pilot_symbs : array_like the transmitter pilot symbols pilot_idx : array_like the position indices of the pilots in the signal frame_len : int length of a frame seq_len : int (optional) length of the pilot sequence, if None (default) do not use the pilot sequence for CPE, else use pilot sequence for CPE as well num_average : int (optional) length of the moving average filter use_pilot_ratio : int (optional) use only every nth pilot symbol max_num_blocks : int (optional) maximum number of phase pilot blocks, if None use maximum number of blocks that fit into data nframes : int (optional) number of frames to do phase recovery on Returns ------- data_symbs : array_like compensated output signal, truncated to nframes*frame_len or max_num_blocks*block_length phase_trace : array_like trace of the phase """ assert num_average > 1, "need to take average over at least 3" # Should be an odd number to keey symmetry in averaging if not (num_average % 2): num_average += 1 warnings.warn( "Number of averages should be odd, adding one average, num_average={}" .format(num_average)) signal = np.atleast_2d(signal) pilot_symbs = np.atleast_2d(pilot_symbs) # select idx based on insertion ratio and pilot sequence pilot_idx_new = pilot_idx[:max_num_blocks:use_pilot_ratio] nlen = min(frame_len * nframes, signal.shape[-1]) frl = np.arange(nframes) * frame_len pilot_idx2 = np.broadcast_to(pilot_idx_new, (nframes, pilot_idx_new.shape[-1])) pilot_idx_full = np.ravel(pilot_idx2 + frl[:, None]) ilim = pilot_idx_full < nlen pilot_idx_full = pilot_idx_full[ilim] rec_pilots = signal[:, pilot_idx_full] pilot_symbs = np.tile(pilot_symbs[:, ::use_pilot_ratio], nframes)[:, :rec_pilots.shape[-1]] assert rec_pilots.shape == pilot_symbs.shape, "Inproper pilot configuration, the number of"\ +" received pilots differs from reference ones" # Check for a out of bounch error assert pilot_symbs.shape[ -1] >= num_average, "Inpropper pilot symbol configuration. Larger averaging block size than total number of pilot symbols" res_phase = np.unwrap(np.angle(pilot_symbs.conjugate() * rec_pilots), axis=-1) res_phase_avg = filter.moving_average(res_phase, num_average) i_filt_adj = int((num_average - 1) / 2) idx_avg = pilot_idx_full[i_filt_adj:-i_filt_adj] assert idx_avg.shape[-1] == res_phase_avg.shape[ -1], "averaged phase and new indices are not the same shape" nmodes = pilot_symbs.shape[0] phase_trace = np.zeros((nmodes, nlen), dtype=pilot_symbs.dtype) idxnew = np.arange(0, nlen) for i in range(nmodes): phase_trace[i] = np.interp(idxnew, idx_avg, res_phase_avg[i]) sig_out = signal[:, :nlen] * np.exp(-1j * phase_trace) return sig_out[:, :nframes * frame_len], phase_trace[:, :nframes * frame_len]
def test_moving_avg(self, ndim, N): x = np.random.randn(ndim, 2**15) + 0.j y = cfilter.moving_average(x, N=N) assert x.shape[0] == y.shape[0] assert x.shape[1] - N + 1 == y.shape[1]
def test_moving_avg_1d(self, N): x = np.random.randn(2**15) + 0.j y = cfilter.moving_average(x, N=N) assert x.shape[0] - N + 1 == y.shape[0]
def test_numbers3(self): npt.assert_allclose(np.array([6,9,12,15]) / 3, cfilter.moving_average(np.arange(1, 7), 3))
def test_numbers2(self): npt.assert_allclose(np.array([1.,1., 1.]), cfilter.moving_average(np.ones(5), 3))
def test_mvg_avg_attr(self, attr): s2 = cfilter.moving_average(self.s) assert getattr(self.s, attr) is getattr(s2, attr)
def test_mvg_avg(self): s2 = cfilter.moving_average(self.s) assert type(self.s) is type(s2)
def test_mvg_avg(self, dtype): s = self.s.astype(dtype) s2 = cfilter.moving_average(s) assert type(self.s) is type(s2)