BCJR implementation
WDM code added (Pol Cont., Opt MUX/DEMUX, Opt Atten, DP_Fiber) -> the codebase is not optimized to always work with dp signals!
This commit is contained in:
555
test/bcjr_pam.m
Normal file
555
test/bcjr_pam.m
Normal file
@@ -0,0 +1,555 @@
|
||||
classdef bcjr_pam < handle
|
||||
%MLSE calculates the most probable sequence for an input signal with given/ known channel impulse response of any length
|
||||
|
||||
properties(Access=public)
|
||||
M %PAM-M
|
||||
DIR
|
||||
trellis_states
|
||||
duobinary_output
|
||||
end
|
||||
|
||||
methods (Access=public)
|
||||
|
||||
function obj = bcjr_pam(options)
|
||||
%NAME Construct an instance of this class
|
||||
% Detailed explanation goes here
|
||||
|
||||
arguments
|
||||
options.M double = 4;
|
||||
options.DIR double = [1];
|
||||
options.trellis_states double = [-3 -1 1 3];
|
||||
options.duobinary_output logical = false;
|
||||
|
||||
end
|
||||
|
||||
%
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
try
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function [VITERBI_ESTIMATION_SYMBOLS,LLR_exact,GMI] = process(obj,data_in,data_ref,tx_bits,bit_mapping)
|
||||
|
||||
|
||||
debug = 0;
|
||||
|
||||
% States should match the target states of the prev. EQ (EQ's job was to reduce the error between signal and the target)
|
||||
trellis_state_mode = 2;
|
||||
% 0 = use provided states (MUST provide the correct states);
|
||||
% 1 = normalize to = 1 rms;
|
||||
% 2 = use target symbols;
|
||||
% 3 = use statistical levels
|
||||
% 3 analyzes avg of rx signal levels - can help with nonlinear impairments
|
||||
|
||||
trellis_exclusion = 1; % PAM-6 only (only if data is NOT precoded!)
|
||||
|
||||
% Additional scaling between states, expected output (noiseless_received) and the noisy, filtered input signal
|
||||
scale_mode = 2; % scale_mode:
|
||||
% 0 = no scaling,
|
||||
% 1 = use RMS to scale MODEL,
|
||||
% 2 = use MMSE/time-corr to scale MODEL, -> This best to get the GMI right -> sometimes the LLP's are not centered around zero...
|
||||
% 3 = use RMS to scale DATA,
|
||||
% 4 = use MMSE/time-corr to scale DATA
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%%% PREPARATIONS %%%%%%%%
|
||||
|
||||
% remove unnecessary zeros at start of impulse response to keep
|
||||
% number of trellis states minimal
|
||||
DIR_nonzero = find(obj.DIR ~= 0);
|
||||
if DIR_nonzero(1) > 1
|
||||
obj.DIR(1:DIR_nonzero(1)-1) = [];
|
||||
end
|
||||
|
||||
if isscalar(obj.DIR)
|
||||
obj.DIR = [0 obj.DIR];
|
||||
end
|
||||
|
||||
% impulse respnse to remove from signal
|
||||
obj.DIR = flip(obj.DIR); %i.e. -0.2676 -0.0478 1.0000
|
||||
|
||||
% Trellis States
|
||||
obj.trellis_states = reshape(obj.trellis_states,1,[]);
|
||||
if trellis_state_mode == 1 % Normalize the Trellis states to =1 RMS
|
||||
|
||||
obj.trellis_states = obj.trellis_states ./ rms(obj.trellis_states);
|
||||
|
||||
elseif trellis_state_mode == 2 %simply use the states from the ref signal (should be a robust option)
|
||||
|
||||
obj.trellis_states = reshape(unique(data_ref),size(obj.trellis_states));
|
||||
|
||||
elseif trellis_state_mode == 3 %use_statistical_levels
|
||||
|
||||
%%%% Separate the equalized signal into the respective levels based on the actually transmitted level
|
||||
constellation = unique(data_ref);
|
||||
|
||||
% find actual levels from rx signal
|
||||
symbols_for_lvl = NaN(numel(constellation),length(data_ref));
|
||||
for l = 1:numel(constellation)
|
||||
level_amplitude = constellation(l);
|
||||
symbols_for_lvl(l,data_ref==level_amplitude) = data_in(data_ref==level_amplitude);
|
||||
end
|
||||
|
||||
%replace the trellis states
|
||||
avg_levels = mean(symbols_for_lvl,2,'omitnan');
|
||||
obj.trellis_states = sort(avg_levels)';
|
||||
|
||||
%also replace the whole ref signal (PAM-M) levels
|
||||
[~, idx] = ismember(data_ref, unique(data_ref));
|
||||
data_ref = avg_levels(idx);
|
||||
|
||||
end
|
||||
|
||||
|
||||
% seems to be the only way to use combvec for a flexible amount
|
||||
% of vectors. 'combs' contains all trellis states
|
||||
pre_comb_mat = repmat(obj.trellis_states,length(obj.DIR)-1,1);
|
||||
pre_comb_cell = mat2cell(pre_comb_mat,ones(1,size(pre_comb_mat,1)),size(pre_comb_mat,2));
|
||||
combs = fliplr(combvec(pre_comb_cell{:}).');
|
||||
first_sym = combs(:,1); % das ist das älteste/ trailing Symbol aus der sequenz
|
||||
last_sym = combs(:,end); %hiermit wird entschieden/ das ist das cursor symbol am ende der sequenz
|
||||
nStates = length(last_sym);
|
||||
|
||||
% % Calculate all possible input symbols for the desired impulse
|
||||
% % response. Row number is the index of the previous state,
|
||||
% % column number is the index of the next state
|
||||
% % noise free received == branch metrics
|
||||
% assumes: last_sym = combs(:,end); % already defined earlier
|
||||
levels = sort(unique(obj.trellis_states(:)).');
|
||||
edges = [levels(1) levels(end)]; % edge levels (0 and 5 in PAM6)
|
||||
|
||||
noise_free_received = inf(nStates,nStates); % rows: to, cols: from
|
||||
edge_edge_mask = false(nStates,nStates); % rows: to, cols: from
|
||||
|
||||
for from = 1:nStates
|
||||
for to = 1:nStates
|
||||
% valid transition if shift-register overlap holds
|
||||
if all(combs(to,2:end) == combs(from,1:end-1))
|
||||
% noiseless sample for the 'to' state reached from 'from'
|
||||
noise_free_received(to,from) = ...
|
||||
dot(combs(to,:), obj.DIR(end:-1:2)) + last_sym(from)*obj.DIR(1);
|
||||
|
||||
% mark edge→edge candidate (to be excluded only on even→odd steps)
|
||||
edge_edge_mask(to,from) = ...
|
||||
(last_sym(from)==edges(1) || last_sym(from)==edges(2)) && ...
|
||||
(last_sym(to) ==edges(1) || last_sym(to) ==edges(2));
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
h = flip(obj.DIR(:)).';
|
||||
data_in = data_in(:);
|
||||
y_ideal = conv(data_ref(:), h, "same");
|
||||
|
||||
switch scale_mode
|
||||
case 0
|
||||
g = 1; b = 0;
|
||||
case 1 % RMS: scale model to data
|
||||
g = rms(data_in)/rms(y_ideal); b = mean(data_in) - g*mean(y_ideal);
|
||||
case 2 % MMSE/time-corr: scale states to data
|
||||
[c,lags] = xcorr(data_in(:), y_ideal, 64);
|
||||
[~,ix] = max(abs(c));
|
||||
lag = lags(ix);
|
||||
y_ideal = circshift(y_ideal, lag);
|
||||
mu_y = mean(data_in(:));
|
||||
mu_i = mean(y_ideal);
|
||||
y_c = data_in(:)-mu_y;
|
||||
yi_c = y_ideal-mu_i;
|
||||
g = (yi_c'*y_c)/(yi_c'*yi_c);
|
||||
b = mu_y - g*mu_i;
|
||||
case 3 % RMS flipped: scale data to model
|
||||
gd = rms(y_ideal)/rms(data_in); bd = mean(y_ideal) - gd*mean(data_in);
|
||||
data_in = gd*data_in + bd;
|
||||
g = 1; b = 0;
|
||||
case 4 % MMSE/time-corr flipped: scale data to states
|
||||
[c,lags] = xcorr(data_in(:), y_ideal(:), 64);
|
||||
[~,ix] = max(abs(c));
|
||||
lag = lags(ix);
|
||||
y_ideal = circshift(y_ideal(:), lag);
|
||||
mu_y = mean(data_in(:));
|
||||
mu_i = mean(y_ideal);
|
||||
y_c = data_in(:) - mu_y; % data_in centered
|
||||
yi_c = y_ideal - mu_i; % ideal centered
|
||||
g = (y_c' * yi_c) / (y_c' * y_c);
|
||||
b = mu_i - g * mu_y;
|
||||
data_in = g * data_in(:) + b;
|
||||
g = 1; b = 0;
|
||||
end
|
||||
|
||||
% apply (g,b) to states/ expected values
|
||||
noise_free_received = g*noise_free_received + b;
|
||||
last_sym = g*last_sym + b;
|
||||
|
||||
% calculate noise power
|
||||
sigma2 = mean(abs(data_in - (g*y_ideal + b)).^2); %noise = mean(abs((RX Signal - IDEAL Signal)))^2
|
||||
inv2s2 = 1/(2*sigma2);
|
||||
|
||||
if debug
|
||||
figure(100); clf; hold on
|
||||
obj.showLevelScatter_(data_in, data_ref);
|
||||
yline(noise_free_received(:), 'DisplayName','Transition States','Color','red','HandleVisibility','off');
|
||||
yline(obj.trellis_states(:), 'DisplayName','Transition States','Color','green','LineWidth',2,'HandleVisibility','off')
|
||||
end
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%% FORWARD PASS (VITERBI -Alpha's) %%%%%
|
||||
|
||||
% Initialize the output vector
|
||||
pm = zeros(nStates,nStates);
|
||||
bm_fw = zeros(nStates,nStates,length(data_in));
|
||||
|
||||
% first start is evaluated without ISI/ wihout the full Impulse response
|
||||
% so simply use the constellation here
|
||||
bm = -(data_in(1) - last_sym).^2 * inv2s2;
|
||||
pm = pm + bm;
|
||||
[alpha(:,1),pm_survivor_fw_idx(:,1)] = max(pm,[],2);
|
||||
pm = repmat(alpha(:,1).',nStates,1);
|
||||
bm_fw(:,:,1) = pm;
|
||||
|
||||
% Forward Recursion (FSM Computation)
|
||||
for n = 2:length(data_in)
|
||||
|
||||
bm = -(data_in(n) - noise_free_received).^2 * inv2s2;
|
||||
|
||||
% exclude edge to edge transitions only for even->odd steps && PAM-6
|
||||
if mod(n,2) == 0 && obj.M == 6 && trellis_exclusion
|
||||
bm(edge_edge_mask) = -Inf;
|
||||
end
|
||||
|
||||
pm = pm + bm;
|
||||
[alpha(:,n),pm_survivor_fw_idx(:,n)] = max(pm,[],2); % choose lowest path metric as new state (get min distance for all state transitions towards a new state)
|
||||
pm = repmat(alpha(:,n).',nStates,1); % update pm (chosen state to 2nd dimension -> FROM state)
|
||||
|
||||
bm_fw(:,:,n) = bm;
|
||||
|
||||
end
|
||||
|
||||
% we can now get the best path as min
|
||||
viterbi_path = NaN(1,length(data_in));
|
||||
|
||||
% find ideal trellis path by going through the trellis backwards
|
||||
[~,viterbi_path(length(data_in))] = max(alpha(:,length(data_in)));
|
||||
for n = length(data_in):-1:2
|
||||
viterbi_path(n-1) = pm_survivor_fw_idx(viterbi_path(n),n);
|
||||
end
|
||||
|
||||
|
||||
if debug
|
||||
alpha_ = alpha - min(alpha) + eps;
|
||||
figure();hold on;
|
||||
n = 10;
|
||||
scatter(1:n,obj.trellis_states(repmat([1:numel(obj.trellis_states)]',1,n)),abs(alpha_(:,end-n+1:end)),'Marker','o','LineWidth',1);
|
||||
scatter(1:n,obj.trellis_states(viterbi_path(end-n+1:end)),500,'Marker','x','LineWidth',1,'MarkerEdgeColor','green');
|
||||
% scatter(1:n,data_ref(end-n+1:end),500,'Marker','x','LineWidth',1,'MarkerEdgeColor','red');
|
||||
yticks(obj.trellis_states);
|
||||
ylim([min(obj.trellis_states)-1 max(obj.trellis_states)+1]);
|
||||
end
|
||||
|
||||
VITERBI_ESTIMATION_SYMBOLS(1:length(data_in)) = first_sym(viterbi_path);
|
||||
VITERBI_ESTIMATION_SYMBOLS = reshape(VITERBI_ESTIMATION_SYMBOLS,size(data_in));
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%% BACKWARD (Beta's) %%%%%
|
||||
|
||||
% Initialize the output vector
|
||||
pm = zeros(nStates,nStates);
|
||||
beta = zeros(nStates,length(data_in));
|
||||
pm_survivor_bw_idx = zeros(nStates,length(data_in));
|
||||
bm_bw = zeros(nStates,nStates,length(data_in));
|
||||
|
||||
% starting with the state that has the lowest sum path
|
||||
% metric, follow the stored information about the
|
||||
% predecessor
|
||||
for h = length(data_in)-1:-1:1
|
||||
|
||||
bm = -(data_in(h+1) - noise_free_received).^2 * inv2s2;
|
||||
|
||||
% exclude edge to edge transitions for even->odd steps && PAM-6
|
||||
if mod(h+1, 2) == 0 && obj.M == 6 && trellis_exclusion
|
||||
bm(edge_edge_mask) = -Inf;
|
||||
end
|
||||
|
||||
pm = pm + bm.';
|
||||
[beta(:,h),pm_survivor_bw_idx(:,h)] = max(pm,[],2); % choose lowest path metric as new state
|
||||
pm = repmat(beta(:,h).',nStates,1); % update pm (chosen state to 2nd dimension -> FROM state)
|
||||
|
||||
bm_bw(:,:,h) = bm;
|
||||
|
||||
end
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%% FORWARD (Combine Alpha and Beta to yield LLP's) %%%%%
|
||||
|
||||
%calc the log probabilities (llp's)
|
||||
|
||||
for k = 1:length(data_in)
|
||||
|
||||
if k == 1
|
||||
|
||||
alpha_ = repmat(alpha(:,k)',[nStates,1])';
|
||||
beta_ = beta(:,k);
|
||||
|
||||
LLP(:,k) = max(alpha_ + beta_,[],2);
|
||||
|
||||
else
|
||||
|
||||
alpha_ = repmat(alpha(:,k-1)',[nStates,1])';
|
||||
gamma_ = bm_fw(:,:,k)';
|
||||
beta_ = beta(:,k);
|
||||
|
||||
LLP(:,k) = max(alpha_ + gamma_,[],1) + beta_';
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%% Calc LLR's %%%%%
|
||||
|
||||
% These are interchangeable...
|
||||
nml_LLP = LLP - max(LLP); %subtract highest value for better numerical stability, LLP's are not always close to zero
|
||||
expLLP = exp(nml_LLP);
|
||||
state_prob = expLLP ./ sum(expLLP); % sums to one (or numerically close to one)
|
||||
|
||||
% compute symbol‐posteriors from LLP in the log‐domain:
|
||||
amax = max(LLP,[],1);
|
||||
logZ = amax + log(sum(exp(LLP - amax), 1));
|
||||
logPstate = LLP - logZ; % still in log‐domain
|
||||
state_prob = exp(logPstate); % exact, sums to 1
|
||||
|
||||
if obj.M == 6
|
||||
|
||||
num_bits = 5;
|
||||
|
||||
% all possible transitions (for now 36, including the "edges"
|
||||
% of the QAM 32 constellation)
|
||||
states = [-5 -3 -1 1 3 5];
|
||||
pam6transitions = combvec(states,states)'; % pam6transitions =
|
||||
% [-5 -5;
|
||||
% -3 -5;
|
||||
% -1 -5; ...
|
||||
|
||||
[~, idx_sym_1] = ismember(pam6transitions(:,1), states);
|
||||
[~, idx_sym_2] = ismember(pam6transitions(:,2), states);
|
||||
pam6ind = [idx_sym_1, idx_sym_2];
|
||||
|
||||
numPairs = floor(size(LLP,2)/2);
|
||||
LLR_exact = zeros(numPairs,5);
|
||||
LLR_maxlogmap = zeros(numPairs,5);
|
||||
|
||||
for k = 1:numPairs
|
||||
symbol1 = 2*k-1;
|
||||
symbol2 = 2*k;
|
||||
|
||||
LLP1 = LLP(:,symbol1);
|
||||
LLP2 = LLP(:,symbol2);
|
||||
prob1 = state_prob(:,symbol1);
|
||||
prob2 = state_prob(:,symbol2);
|
||||
|
||||
% All 36 Combinations: M = LLP Symbol 1 + LLP Symbol 2
|
||||
Mij = LLP1(pam6ind(:,1)) + LLP2(pam6ind(:,2));
|
||||
pij = prob1(pam6ind(:,1)) .* prob2(pam6ind(:,2));
|
||||
|
||||
% for each of the 5 bits sum exact-probs or max-log
|
||||
for b = 1:num_bits
|
||||
idx_sym_1 = bit_mapping(:,b)==1;
|
||||
idx_bit_1 = bit_mapping(:,b)==0;
|
||||
|
||||
% exact LLR from probabilities
|
||||
P1 = sum(pij(idx_sym_1)); %prob that bit == 1
|
||||
P0 = sum(pij(idx_bit_1));
|
||||
LLR_exact(k,b) = log(P1./P0); %ratio by multiplication
|
||||
|
||||
% max-log:
|
||||
LLR_maxlogmap(k,b) = max( Mij(idx_sym_1) ) - max( Mij(idx_bit_1) ); % ratio by subtraction
|
||||
end
|
||||
end
|
||||
|
||||
% GMI calc includes the Tx-bitstream
|
||||
tx_bits_pam6_reshaped = reshape(tx_bits',5,[])'; % N x 5
|
||||
MI = zeros(1, num_bits);
|
||||
for k = 1:num_bits
|
||||
|
||||
idx_bit_1 = (tx_bits_pam6_reshaped(:,k) == 0); %wo sind die 1en
|
||||
idx_sym_1 = (tx_bits_pam6_reshaped(:,k) == 1); %wo sind die 0en
|
||||
|
||||
%LLR's for all actually transmitted ones or zeros
|
||||
llr0 = LLR_exact(idx_bit_1,k);
|
||||
llr1 = LLR_exact(idx_sym_1,k);
|
||||
|
||||
% Calculate mutual information for bit position k
|
||||
I0 = mean(log2(1 + exp(llr0))); % exp(--LLR) = exp(positive) > 1
|
||||
I1 = mean(log2(1 + exp(-llr1))); % exp(-+LLR) = exp(negative) < 1
|
||||
MI(k) = 1 - 0.5 * (I0 + I1);
|
||||
end
|
||||
|
||||
GMI = sum(MI); % Total mutual information per symbol
|
||||
GMI = GMI/2; % GMI per single symbol not per two symbols
|
||||
|
||||
else
|
||||
|
||||
% Number of symbols and bits per symbol
|
||||
num_bits = log2(length(obj.trellis_states)); % 2 bits per symbol
|
||||
|
||||
% bit_mapping = PAMmapper(length(obj.trellis_states),0,"eth_style",0).showBitMapping;
|
||||
|
||||
% Initialize LLR storage
|
||||
LLR_maxlogmap = zeros(length(data_in),num_bits);
|
||||
LLR_exact = zeros(length(data_in),num_bits);
|
||||
|
||||
% Compute bit-wise LLRs
|
||||
for bit_idx = 1:num_bits
|
||||
|
||||
% Find indices where bit is 0 and where it is 1
|
||||
idx_bit_0 = bit_mapping(:,bit_idx) == 0;
|
||||
idx_bit_1 = bit_mapping(:,bit_idx) == 1;
|
||||
|
||||
% Sum over log-probabilities
|
||||
% Max-Log approximation uses the single max LLP value
|
||||
% instead of sum over all LLP's
|
||||
LLR_maxlogmap(:,bit_idx) = max(LLP(idx_bit_1,:), [], 1) - max(LLP(idx_bit_0,:), [], 1);
|
||||
|
||||
% Sum probabilities over states for which the bit is 1 and 0, respectively.
|
||||
P0 = sum(state_prob(idx_bit_0, :),1);
|
||||
P1 = sum(state_prob(idx_bit_1, :),1);
|
||||
LLR_exact(:,bit_idx) = log(P1./P0); % N x num_bits
|
||||
|
||||
|
||||
end
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%% CALC NGMI %%%%%
|
||||
|
||||
MI = zeros(1, num_bits);
|
||||
for k = 1:num_bits
|
||||
|
||||
idx_bit_0 = (tx_bits(:,k) == 0); %wo sind die 1en
|
||||
idx_bit_1 = (tx_bits(:,k) == 1); %wo sind die 0en
|
||||
|
||||
%LLR's for all actually transmitted ones or zeros
|
||||
llr0 = LLR_exact(idx_bit_0,k);
|
||||
llr1 = LLR_exact(idx_bit_1,k);
|
||||
|
||||
% mutual information for bit position k
|
||||
I0 = mean(log2(1 + exp(llr0))); % exp(--LLR) = exp(positive) > 1
|
||||
I1 = mean(log2(1 + exp(-llr1))); % exp(-+LLR) = exp(negative) < 1
|
||||
MI(k) = 1 - 0.5 * (I0 + I1); % assumes equally distributed ones and zeros
|
||||
end
|
||||
|
||||
GMI = sum(MI); % Total bitwise mutual information
|
||||
|
||||
end
|
||||
|
||||
|
||||
if debug
|
||||
%%% DEBUG PLOT LIKELIHOOD RATIOS %%%
|
||||
figure(115);clf
|
||||
subplot(2,1,1)
|
||||
for bit = 1:num_bits
|
||||
hold on;
|
||||
histogram(LLR_exact(:,bit),1000,"DisplayName",sprintf('Actual LLR of Bit Pos %d',bit),'LineStyle','none','FaceAlpha',0.4);
|
||||
end
|
||||
legend
|
||||
|
||||
subplot(2,1,2)
|
||||
for bit = 1:num_bits
|
||||
hold on;
|
||||
histogram(LLR_maxlogmap(:,bit),1000,"DisplayName",sprintf('Max Log LLR of Bit Pos %d',bit),'LineStyle','none','FaceAlpha',0.4);
|
||||
end
|
||||
legend
|
||||
|
||||
if obj.M == 6
|
||||
pairs = reshape(VITERBI_ESTIMATION_SYMBOLS,2,[]).';
|
||||
levels = sort(unique(VITERBI_ESTIMATION_SYMBOLS));
|
||||
isedge = ismember(pairs, [levels(1) levels(end)]);
|
||||
isforbidden = sum(isedge,2)==2;
|
||||
fprintf('Found %d forbidden transitions (even -> odd ; edge -> edge).\n', nnz(isforbidden));
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
function [symbols_for_lvl,avg_for_lvl] = showLevelScatter_(~,eq_signal,ref_symbols)
|
||||
|
||||
figure()
|
||||
|
||||
rx_symbols = eq_signal; %./ rms(eq_signal);
|
||||
correct_symbols = ref_symbols;
|
||||
|
||||
% col = cbrewer2('Paired',numel(unique(correct_symbols))*2);
|
||||
col = ...
|
||||
[0.6510 0.8078 0.8902; ...
|
||||
0.1216 0.4706 0.7059; ...
|
||||
0.6980 0.8745 0.5412; ...
|
||||
0.2000 0.6275 0.1725; ...
|
||||
0.9843 0.6039 0.6000; ...
|
||||
0.8902 0.1020 0.1098; ...
|
||||
0.9922 0.7490 0.4353; ...
|
||||
1.0000 0.4980 0; ...
|
||||
0.7922 0.6980 0.8392; ...
|
||||
0.4157 0.2392 0.6039; ...
|
||||
1.0000 1.0000 0.6000; ...
|
||||
0.6941 0.3490 0.1569; ...
|
||||
0.6510 0.8078 0.8902; ...
|
||||
0.1216 0.4706 0.7059; ...
|
||||
0.6980 0.8745 0.5412; ...
|
||||
0.2000 0.6275 0.1725];
|
||||
ccnt = -1;
|
||||
|
||||
levels = unique(correct_symbols);
|
||||
symbols_for_lvl = NaN(numel(levels),length(correct_symbols));
|
||||
start = 1;
|
||||
ende = length(correct_symbols);
|
||||
|
||||
for l = 1:numel(levels)
|
||||
ccnt = ccnt+2;
|
||||
|
||||
level_amplitude = levels(l);
|
||||
|
||||
symbols_for_lvl(l,correct_symbols==level_amplitude) = rx_symbols(correct_symbols==level_amplitude);
|
||||
std_lvl(l) = std(symbols_for_lvl(l,:),'omitnan');
|
||||
xax = 1:length(correct_symbols);
|
||||
|
||||
scatter(xax(start:ende),symbols_for_lvl(l,start:ende),10,'.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5,'MarkerEdgeColor',col(ccnt,:));
|
||||
hold on;
|
||||
|
||||
|
||||
end
|
||||
|
||||
std_lvl = round(std_lvl,2);
|
||||
|
||||
ccnt = 0;
|
||||
avg_for_lvl = NaN(numel(levels),length(correct_symbols));
|
||||
% Add the windowed/ smoothed curves
|
||||
for l = 1:numel(levels)
|
||||
ccnt = ccnt+2;
|
||||
level_amplitude = levels(l);
|
||||
|
||||
L = 500;
|
||||
movmean = 1/L .* movsum(rx_symbols(correct_symbols==level_amplitude),[L/2,L/2], 'Endpoints', 'fill');
|
||||
|
||||
avg_for_lvl(l,correct_symbols==level_amplitude) = movmean;
|
||||
|
||||
nanx = isnan(avg_for_lvl(l,:));
|
||||
t = 1:numel(avg_for_lvl(l,:));
|
||||
avg_for_lvl(l,nanx) = interp1(t(~nanx), avg_for_lvl(l,~nanx), t(nanx));
|
||||
|
||||
plot(xax(start:ende),avg_for_lvl(l,start:ende),'Color',col(ccnt,:));
|
||||
|
||||
hold on
|
||||
end
|
||||
|
||||
% yline(levels);
|
||||
xlabel('Samples');
|
||||
ylabel('Amplitude');
|
||||
ylim([-3 3]);
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
@@ -1,65 +1,49 @@
|
||||
useprbs = 1;
|
||||
|
||||
|
||||
M = 6;
|
||||
randkey = 1;
|
||||
datarate = 224e9;
|
||||
fsym = round(datarate / log2(M)) ;
|
||||
apply_precode = 1;
|
||||
|
||||
db_pre = 1;
|
||||
bitpattern = [];
|
||||
s = RandStream('twister','Seed',1);
|
||||
for i = 1:log2(M)
|
||||
N = 2^(17-1); %length of prbs
|
||||
bitpattern(:,i) = randi(s,[0 1], N, 1);
|
||||
end
|
||||
|
||||
Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"rrcalpha",0.05);
|
||||
if M == 6
|
||||
bitpattern = reshape(bitpattern',[],1);
|
||||
bitpattern = bitpattern(1:end-mod(length(bitpattern),5));
|
||||
end
|
||||
|
||||
[d,Symbols,Bits] = PAMsource(...
|
||||
"fsym",fsym,"M",M,"order",17,"useprbs",1,...
|
||||
"fs_out",fsym,...
|
||||
"applyclipping",0,"clipfactor",1.5,...
|
||||
"applypulseform",0,"pulseformer",Pform,...
|
||||
"randkey",1,...
|
||||
"db_precode",db_pre,"db_encode",0,...
|
||||
"mrds_code",0,"mrds_blocklength",512).process();
|
||||
bits = Informationsignal(bitpattern);
|
||||
|
||||
%%%CHANNEL
|
||||
symbols = PAMmapper(M,0).map(bits);
|
||||
|
||||
% s = RandStream('twister','Seed',2);
|
||||
% start = 10000;
|
||||
% burstwidth = 100;
|
||||
% d_burst = d;
|
||||
% for pos = start:start+burstwidth
|
||||
% lvls = 1.5 .* PAMmapper(M,0).levels / rms(PAMmapper(M,0).levels);
|
||||
% d_burst.signal(pos) = d.signal(pos)+randn(s,1,1);
|
||||
% end
|
||||
bits_rx = PAMmapper(M,0).demap(symbols);
|
||||
[~,~,ber_direct,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
|
||||
|
||||
d_resample = d.resample("fs_out",2.*fsym);
|
||||
if apply_precode
|
||||
symbols_tx = Duobinary().precode(symbols);
|
||||
else
|
||||
symbols_tx = symbols;
|
||||
end
|
||||
|
||||
eq_ffe = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",1024,"mu_dd",0.0004,"mu_tr",0,"order",25,"sps",2,"decide",1);
|
||||
d_eq = eq_ffe.process(d_resample,Symbols);
|
||||
show2Dconstellation(symbols_tx,symbols_tx,"displayname",'VNLE Out','fignum',2241);
|
||||
|
||||
|
||||
% s = RandStream('twister','Seed',2);
|
||||
% start = 10000;
|
||||
% burstwidth = 100;
|
||||
% d_burst = d_eq;
|
||||
% for pos = start:start+burstwidth
|
||||
% lvls = 1.5 .* PAMmapper(M,0).levels / rms(PAMmapper(M,0).levels);
|
||||
% d_burst.signal(pos) = d_eq.signal(pos)+randn(s,1,1);
|
||||
% end
|
||||
%
|
||||
% d_burst = PAMmapper(M,0).decide_pamlevel(d_burst);
|
||||
|
||||
if db_pre
|
||||
if apply_precode
|
||||
% Entschiedene Symbole codieren: d_DB(n) = d(n) + d(n-1) (im Fall von PAM4 7 level [0 1 2 3 4 5 6])
|
||||
d_db = Duobinary().encode(d);
|
||||
symbols_db = Duobinary().encode(symbols_tx);
|
||||
|
||||
% Entschiedene codierte Symbole decodieren: d_dec(n) = d_DB(n) mod4
|
||||
d_dec = Duobinary().decode(d_db);
|
||||
symbols_rx = Duobinary().decode(symbols_db);
|
||||
else
|
||||
d_dec = d_burst;
|
||||
symbols_rx = symbols_tx;
|
||||
end
|
||||
|
||||
% Vergleichen von b(n) und d_dec(n)
|
||||
Rx_bits = PAMmapper(M,0).demap(d_dec);
|
||||
|
||||
Tx_bits = Bits;
|
||||
|
||||
[~,error_num,ber,error_pos] = calc_ber(Tx_bits.signal,Rx_bits.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
|
||||
bits_rx = PAMmapper(M,0).demap(symbols_rx);
|
||||
[~,~,ber,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",10,"skip_end",10,"returnErrorLocation",1);
|
||||
|
||||
disp(['BER: ',sprintf('%.1E',ber),' - - PAM-',num2str(M)]);
|
||||
|
||||
|
||||
171
test/minimal_example_bcjr.m
Normal file
171
test/minimal_example_bcjr.m
Normal file
@@ -0,0 +1,171 @@
|
||||
|
||||
M_format = [2,4,6,8];
|
||||
|
||||
for m = 1:length(M_format)
|
||||
% --- Parameters ---
|
||||
M = M_format(m); % PAM order (e.g., 2,4,8)
|
||||
Nsym = 1e5; % number of symbols
|
||||
h = [1, 0.5]; % Impulse response to remove
|
||||
|
||||
b = log2(M);
|
||||
if M == 6 b = 5; end
|
||||
rng(1);
|
||||
bits_tx = logical(randi([0 1], Nsym, b, 'uint8'));
|
||||
|
||||
tx_symbols = pammap(bits_tx,M);
|
||||
|
||||
if M == 6
|
||||
states = unique(tx_symbols);
|
||||
pam6transitions = combvec(states',states')'; % pam6transitions =
|
||||
bitmapping = pamdemap(reshape(pam6transitions',1,[])',M);
|
||||
else
|
||||
bitmapping = pamdemap(unique(tx_symbols),M);
|
||||
end
|
||||
|
||||
scaling = sqrt(sum(unique(tx_symbols).^2)/numel(unique(tx_symbols)));
|
||||
tx_symbols = tx_symbols ./ scaling;
|
||||
|
||||
% apply impulse response to signal
|
||||
y_filt = filter(h, 1, tx_symbols);
|
||||
|
||||
sir = 10:25;
|
||||
for s = 1:length(sir)
|
||||
|
||||
% apply noise
|
||||
y = awgn(y_filt,sir(s),"measured",1);
|
||||
|
||||
% apply bcjr
|
||||
BCJR = bcjr_pam("DIR",h,"duobinary_output",0,"M",M,"trellis_states",unique(tx_symbols));
|
||||
[viterbi_estimate,LLR,GMI(m,s)] = BCJR.process(y,tx_symbols,bits_tx,bitmapping);
|
||||
|
||||
% decode LLR's
|
||||
bits_LLR = LLR > 0;
|
||||
|
||||
% demap viterbi symbols sequence
|
||||
rx_symbols = viterbi_estimate .* scaling;
|
||||
bits_rx = pamdemap(rx_symbols,M);
|
||||
|
||||
% BER calc
|
||||
BER_vit(m,s) = nnz(bits_tx ~= bits_LLR) / numel(bits_tx);
|
||||
fprintf('BER LLR = %.2e \n', BER_vit);
|
||||
|
||||
BER_llr(m,s) = nnz(bits_tx ~= bits_rx) / numel(bits_tx);
|
||||
fprintf('BER = %.2e \n', BER_llr);
|
||||
end
|
||||
end
|
||||
|
||||
figure();hold on
|
||||
for m = 1:length(M_format)
|
||||
plot(sir,BER_llr(m,:),'DisplayName',sprintf('PAM %d',M_format(m)))
|
||||
% plot(sir,BER_vit(m,:),'DisplayName',sprintf('PAM %d',M_format(m)),'LineStyle',':','LineWidth',0.1,'HandleVisibility','off');
|
||||
end
|
||||
ylabel('BER');
|
||||
xlabel('SNR')
|
||||
title('BER vs. SNR');
|
||||
set(gca, 'XScale', 'linear', ...
|
||||
'YScale', 'log', ...
|
||||
'TickLabelInterpreter', 'latex', ...
|
||||
'FontSize', 11);
|
||||
|
||||
|
||||
figure();hold on
|
||||
for m = 1:length(M_format)
|
||||
plot(sir,GMI(m,:),'DisplayName',sprintf('GMI PAM %d',M_format(m)))
|
||||
end
|
||||
ylabel('GMI');
|
||||
xlabel('SNR')
|
||||
title('GMI vs. SNR');
|
||||
set(gca, 'XScale', 'linear', ...
|
||||
'YScale', 'linear', ...
|
||||
'TickLabelInterpreter', 'latex', ...
|
||||
'FontSize', 11);
|
||||
|
||||
function symbols = pammap(bits,M)
|
||||
bits = logical(bits);
|
||||
if M == 2
|
||||
symbols = bits;
|
||||
elseif M == 4
|
||||
symbols= 2*bits(:,1) + (bits(:,1)==bits(:,2));
|
||||
symbols=2*symbols-3;
|
||||
|
||||
elseif M == 6
|
||||
|
||||
m = 1;
|
||||
|
||||
if size(bits,2)>size(bits,1)
|
||||
bits = bits'; %vector aufrecht stellen
|
||||
end
|
||||
bits = reshape(bits',1,[])';
|
||||
thres = [-3 5;-1 5;-3 -5;-1 -5;-5 3;-5 1;-5 -3;-5 -1;-1 3;-1 1;-1 -3;-1 -1;-3 3;-3 1;-3 -3;-3 -1;3 5;1 5;3 -5;1 -5;5 3;5 1;5 -3;5 -1;1 3;1 1;1 -3;1 -1;3 3;3 1;3 -3;3 -1];
|
||||
% LUT based mapping
|
||||
for k = 1:5:fix(length(bits)/5)*5
|
||||
symbols(m:m+1,1) = thres(bin2dec(int2str(bits(k:k+4)'))+1,:);
|
||||
m = m+2;
|
||||
end
|
||||
|
||||
elseif M == 8
|
||||
x1 = bits(:,1);
|
||||
x2 = (bits(:,1)==bits(:,3));
|
||||
x3 = x2~=bits(:,2);
|
||||
|
||||
symbols = 4*x1 + 2*x2 + x3;
|
||||
symbols=2*symbols-7;
|
||||
end
|
||||
end
|
||||
|
||||
function bits = pamdemap(symbols,M)
|
||||
|
||||
if M == 2
|
||||
thres=0;
|
||||
elseif M == 4
|
||||
thres=[-2,0,2];
|
||||
elseif M == 6
|
||||
thres = [-3 5;-1 5;-3 -5;-1 -5;-5 3;-5 1;-5 -3;-5 -1;-1 3;-1 1;-1 -3;-1 -1;-3 3;-3 1;-3 -3;-3 -1;3 5;1 5;3 -5;1 -5;5 3;5 1;5 -3;5 -1;1 3;1 1;1 -3;1 -1;3 3;3 1;3 -3;3 -1];
|
||||
elseif M == 8
|
||||
thres=-6:2:6;
|
||||
end
|
||||
|
||||
if M ~= 6
|
||||
symbols = symbols';
|
||||
a = squeeze(repmat(real(symbols),[1 1 length(thres)])); %Eingangssignal in 3 spalten
|
||||
b = squeeze(repmat(reshape(thres(:).',[1 1 length(thres)]),[1 length(symbols) 1])); %Threshold in 3 Spalten
|
||||
comp_real = a > b; %check for each symbol/ sampling if it exeeds the obj.thresholdseshold 1, 2 or 3
|
||||
comp_real=repmat(real(symbols),[1 1 length(thres)]) > repmat(reshape(thres(:).',[1 1 length(thres)]),[1 length(symbols) 1]);
|
||||
s1=size(comp_real,1);
|
||||
s2=size(comp_real,2);
|
||||
end
|
||||
|
||||
if M == 2
|
||||
data_out=abs(comp_real(:,:,1));
|
||||
elseif M == 4
|
||||
data_out=[comp_real(:,:,2); ones(s1,s2) - comp_real(:,:,1) + comp_real(:,:,3)];
|
||||
elseif M == 6
|
||||
|
||||
if size(symbols,2) > 1
|
||||
symbols = symbols.';
|
||||
end
|
||||
|
||||
if length(symbols)/2 ~= round(length(symbols)/2)
|
||||
symbols = [symbols;0];
|
||||
end
|
||||
|
||||
m = 1;
|
||||
for n = 1:2:length(symbols)
|
||||
dist = sqrt((symbols(n)-thres(:,1)).^2+(symbols(n+1)-thres(:,2)).^2);
|
||||
[~,dd_idx] = min(dist);
|
||||
% dec_out(n:n+1) = LUT(dd_idx,:);
|
||||
data_out(m:m+4) = bitget(dd_idx-1,5:-1:1);
|
||||
m = m+5;
|
||||
end
|
||||
|
||||
data_out = reshape(data_out',5,[]);
|
||||
|
||||
elseif M == 8
|
||||
data_out=[comp_real(:,:,4);
|
||||
comp_real(:,:,1)-comp_real(:,:,3)+comp_real(:,:,5)-comp_real(:,:,7);
|
||||
1-comp_real(:,:,2)+comp_real(:,:,6)];
|
||||
end
|
||||
|
||||
bits = data_out';
|
||||
|
||||
end
|
||||
79
test/pam_6_differential_code_understand.m
Normal file
79
test/pam_6_differential_code_understand.m
Normal file
@@ -0,0 +1,79 @@
|
||||
M = 6;
|
||||
data = [1,2,3,4,5,6];
|
||||
|
||||
M = 6;
|
||||
|
||||
bitpattern = [];
|
||||
s = RandStream('twister','Seed',1);
|
||||
for i = 1:log2(M)
|
||||
N = 2^(12-1); %length of prbs
|
||||
bitpattern(:,i) = randi(s,[0 1], N, 1);
|
||||
end
|
||||
|
||||
if M == 6
|
||||
bitpattern = reshape(bitpattern',[],1);
|
||||
bitpattern = bitpattern(1:end-mod(length(bitpattern),5));
|
||||
end
|
||||
|
||||
bits = Informationsignal(bitpattern);
|
||||
|
||||
symbols = PAMmapper(M,0).map(bits);
|
||||
symbols_tx_prec = Duobinary().precode(symbols);
|
||||
|
||||
% all possible transitions (for now 36, including the "edges"
|
||||
% of the QAM 32 constellation)
|
||||
states = PAMmapper(6,0,"eth_style",0).levels;
|
||||
pam6transitions = combvec(states,states)'; % pam6transitions =
|
||||
% [-5 -5;
|
||||
% -3 -5;
|
||||
% -1 -5; ...
|
||||
pam6transitions_serial = reshape(pam6transitions',[],1);
|
||||
|
||||
data = pam6transitions_serial;
|
||||
data = round(data);
|
||||
b = min(data);
|
||||
data = data - b;
|
||||
data = data ./ 2;
|
||||
% THIS WAS USED!
|
||||
bk = zeros(size(data));
|
||||
for k = 2:numel(data)
|
||||
bk(k) = mod(data(k)-bk(k-1),M);
|
||||
end
|
||||
|
||||
|
||||
%% State Analysis
|
||||
x = bk;%symbols_tx_prec.signal;
|
||||
levels = sort(unique(x)).'; % or provide known 1x6 level values
|
||||
|
||||
[~,ix] = min(abs(x - levels),[],2);
|
||||
x = levels(ix); % snapped/quantized
|
||||
|
||||
%% TRANSITION COUNTS & PROBABILITIES
|
||||
K = numel(levels);
|
||||
% map to state indices 1..K
|
||||
[tf, idx] = ismember(x, levels);
|
||||
idx = idx(:);
|
||||
from = idx(1:end-1);
|
||||
to = idx(2:end);
|
||||
from = idx(1:2:end);
|
||||
to = idx(2:2:end);
|
||||
|
||||
% counts C(from,to)
|
||||
C = accumarray([from,to], 1, [K K], @sum, 0);
|
||||
% row-stochastic transition matrix P(to|from)
|
||||
rowSums = sum(C,2);
|
||||
P = C ./ max(rowSums,1);
|
||||
|
||||
%% 1) HEATMAP (which transitions are more probable?)
|
||||
figure('Name','Transition Probabilities (to | from)');
|
||||
h = heatmap(levels, levels, P, 'Colormap', parula, 'ColorbarVisible','on');
|
||||
colormap(gca,[[1,1,1];flip(cbrewer2('Spectral',100))]);clim([0,ceil(max(P(:))*10)/10]);
|
||||
h.XLabel = 'From state (level)';
|
||||
h.YLabel = 'To state (level)';
|
||||
h.Title = 'P(to | from)';
|
||||
|
||||
%% 2) WEIGHTED TRANSITION GRAPH
|
||||
% Use dtmc if you have Econometrics Toolbox:
|
||||
mc = dtmc(P, 'StateNames', string(levels));
|
||||
figure('Name','Markov Graph (dtmc)');
|
||||
gp = graphplot(mc, 'ColorEdges',true, 'LabelEdges',true);
|
||||
212
test/pam_6_states_analysis.m
Normal file
212
test/pam_6_states_analysis.m
Normal file
@@ -0,0 +1,212 @@
|
||||
|
||||
|
||||
M = 6;
|
||||
|
||||
bitpattern = [];
|
||||
s = RandStream('twister','Seed',1);
|
||||
for i = 1:log2(M)
|
||||
N = 2^(17-1); %length of prbs
|
||||
bitpattern(:,i) = randi(s,[0 1], N, 1);
|
||||
end
|
||||
|
||||
if M == 6
|
||||
bitpattern = reshape(bitpattern',[],1);
|
||||
bitpattern = bitpattern(1:end-mod(length(bitpattern),5));
|
||||
end
|
||||
|
||||
bits = Informationsignal(bitpattern);
|
||||
|
||||
symbols = PAMmapper(M,0).map(bits);
|
||||
|
||||
bits_rx = PAMmapper(M,0).demap(symbols);
|
||||
[~,~,ber_direct,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
|
||||
assert(ber_direct==0,'Mapping is wrong');
|
||||
|
||||
nBursts = 0;
|
||||
% No Precoding %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%SEND DIRECTLY
|
||||
symbols_tx = symbols;
|
||||
|
||||
symbols_rx = introduce_symbol_errors(symbols_tx, 1, 10, nBursts, 42);
|
||||
|
||||
%RECEIVE BRANCH (do nothing special)
|
||||
bits_rx = PAMmapper(M,0).demap(symbols_rx);
|
||||
|
||||
[~,~,ber,errpos] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
|
||||
disp(['BER normal: - ',sprintf('%.1E',ber),' - - PAM-',num2str(M)]);
|
||||
bursts_normal = count_error_bursts(errpos, 20);
|
||||
|
||||
|
||||
% Precode Emulation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%SEND DIRECTLY
|
||||
symbols_tx = symbols;
|
||||
|
||||
symbols_rx = introduce_symbol_errors(symbols_tx, 1, 10, nBursts, 42);
|
||||
|
||||
%REFERENCE BRACH
|
||||
symbols_db = Duobinary().encode(symbols_tx);
|
||||
symbols_tx_emu = Duobinary().decode(symbols_db);
|
||||
bits_tx_emu = PAMmapper(M,0).demap(symbols_tx_emu);
|
||||
|
||||
% symbols_rx = introduce_symbol_errors(symbols_tx, 1, 10, 200, 42);
|
||||
|
||||
%RECEIVE BRANCH
|
||||
symbols_db = Duobinary().encode(symbols_rx);
|
||||
symbols_rx_emu = Duobinary().decode(symbols_db);
|
||||
bits_rx = PAMmapper(M,0).demap(symbols_rx_emu);
|
||||
|
||||
[~,~,ber_precode_emulation,errpos_precode_emulation] = calc_ber(bits_tx_emu.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
|
||||
disp(['BER precode emulation: ',sprintf('%.1E',ber_precode_emulation),' - - PAM-',num2str(M)]);
|
||||
bursts_precode_emulation = count_error_bursts(errpos_precode_emulation, 20);
|
||||
|
||||
|
||||
|
||||
|
||||
% Precode at Tx %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%SEND PRECODED DATA
|
||||
symbols_tx_prec = Duobinary().precode(symbols);
|
||||
|
||||
symbols_rx_prec = introduce_symbol_errors(symbols_tx_prec, 1, 10, nBursts, 42);
|
||||
|
||||
%RECEIVE BRANCH
|
||||
symbols_db = Duobinary().encode(symbols_rx_prec);
|
||||
symbols_rx_prec = Duobinary().decode(symbols_db);
|
||||
bits_rx = PAMmapper(M,0).demap(symbols_rx_prec);
|
||||
|
||||
[~,~,ber_precoded,errpos_precoded] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
|
||||
disp(['BER precoded: ',sprintf('%.1E',ber_precoded),' - - PAM-',num2str(M)]);
|
||||
burst_precoded = count_error_bursts(errpos_precoded, 20);
|
||||
|
||||
|
||||
% Precode at Tx but omit at Rx %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%SEND PRECODED DATA
|
||||
symbols_tx_prec = Duobinary().precode(symbols);
|
||||
bits_tx_prec = PAMmapper(M,0).demap(symbols_tx_prec);
|
||||
|
||||
symbols_rx_omit = introduce_symbol_errors(symbols_tx_prec, 1, 10, nBursts, 42);
|
||||
|
||||
%RECEIVE BRANCH
|
||||
bits_rx = PAMmapper(M,0).demap(symbols_rx_omit);
|
||||
|
||||
[~,~,ber_omit,errpos_omit] = calc_ber(bits_tx_prec.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
|
||||
disp(['BER (omit precode): ',sprintf('%.1E',ber_omit),' - - PAM-',num2str(M)]);
|
||||
burst_omit = count_error_bursts(errpos_omit, 20);
|
||||
|
||||
if 0
|
||||
cols = linspecer(8);
|
||||
figure();hold on;
|
||||
stem(1:20,bursts_normal,'LineWidth',2,'Color',cols(4,:),'Marker','_','DisplayName','w/o diff. precoder');
|
||||
stem(1:20,bursts_precode_emulation,'LineWidth',2,'Color',cols(3,:),'Marker','.','LineStyle','-','DisplayName','emulated precoder');
|
||||
stem(1:20,burst_precoded,'LineWidth',1,'Color',cols(6,:),'Marker','_','DisplayName','w/ diff. precoder');
|
||||
stem(1:20,burst_omit,'LineWidth',1,'Color',cols(5,:),'Marker','.','LineStyle',':','DisplayName','omit precoder');
|
||||
xlabel('Bit Error Burst Length')
|
||||
ylabel('Occurence')
|
||||
set(gca, 'yscale', 'log');
|
||||
end
|
||||
|
||||
|
||||
%% State Analysis
|
||||
signal_to_analyze = symbols_tx_emu;
|
||||
x = signal_to_analyze.signal(:);
|
||||
levels = sort(unique(x)).'; % or provide known 1x6 level values
|
||||
|
||||
[~,ix] = min(abs(x - levels),[],2);
|
||||
x = levels(ix); % snapped/quantized
|
||||
|
||||
%% TRANSITION COUNTS & PROBABILITIES
|
||||
K = numel(levels);
|
||||
% map to state indices 1..K
|
||||
[tf, idx] = ismember(x, levels);
|
||||
idx = idx(:);
|
||||
from = idx(1:end-1);
|
||||
to = idx(2:end);
|
||||
from = idx(1:2:end);
|
||||
to = idx(2:2:end);
|
||||
|
||||
% counts C(from,to)
|
||||
C = accumarray([from,to], 1, [K K], @sum, 0);
|
||||
% row-stochastic transition matrix P(to|from)
|
||||
rowSums = sum(C,2);
|
||||
P = C ./ max(rowSums,1);
|
||||
|
||||
%% 1) HEATMAP (which transitions are more probable?)
|
||||
figure('Name','Transition Probabilities (to | from)');
|
||||
h = heatmap(levels, levels, P, 'Colormap', parula, 'ColorbarVisible','on');
|
||||
colormap(gca,[[1,1,1];flip(cbrewer2('Spectral',100))]);clim([0,ceil(max(P(:))*10)/10]);
|
||||
h.XLabel = 'From state (level)';
|
||||
h.YLabel = 'To state (level)';
|
||||
h.Title = 'P(to | from)';
|
||||
|
||||
%% 2) WEIGHTED TRANSITION GRAPH
|
||||
% Use dtmc if you have Econometrics Toolbox:
|
||||
mc = dtmc(P, 'StateNames', string(levels.*PAMmapper(M,0).get_scaling));
|
||||
figure('Name','Markov Graph (dtmc)');
|
||||
gp = graphplot(mc, 'ColorEdges',true, 'LabelEdges',true);
|
||||
|
||||
|
||||
function symbols = introduce_symbol_errors(symbols, j, maxBurstLen, nBursts, seed)
|
||||
%INTRODUCE_SYMBOL_ERRORS injects bursty level errors into symbols.signal.
|
||||
% symbols.signal : column/row vector of quantized levels (exactly one of 6 values)
|
||||
% j : max level step per sample (default 1)
|
||||
% maxBurstLen : maximum burst length (default 8)
|
||||
% nBursts : number of bursts to insert (default ~1% of length)
|
||||
% seed : RNG seed (optional)
|
||||
|
||||
if nargin < 2 || isempty(j), j = 1; end
|
||||
if nargin < 3 || isempty(maxBurstLen), maxBurstLen = 8; end
|
||||
x = symbols.signal(:);
|
||||
N = numel(x);
|
||||
if nargin < 4 || isempty(nBursts), nBursts = max(1, round(0.01*N)); end
|
||||
if nargin >= 5 && ~isempty(seed), rng(seed); end
|
||||
|
||||
% known levels and index mapping
|
||||
lvls = sort(unique(x)).';
|
||||
K = numel(lvls);
|
||||
|
||||
[~, idx] = ismember(x, lvls); % idx in 1..6
|
||||
|
||||
used = false(N,1); % avoid overlapping bursts
|
||||
burst_ranges = zeros(nBursts,2);
|
||||
|
||||
for b = 1:nBursts
|
||||
% pick start not inside an existing burst
|
||||
s = randi(N);
|
||||
while used(s), s = randi(N); end
|
||||
L = randi(maxBurstLen);
|
||||
e = min(N, s+L-1);
|
||||
|
||||
% mark used range
|
||||
used(s:e) = true;
|
||||
burst_ranges(b,:) = [s e];
|
||||
|
||||
% choose one direction for the whole burst: -1 (down) or +1 (up)
|
||||
dir = randi([0 1])*2 - 1;
|
||||
|
||||
% apply level errors within the burst
|
||||
for t = s:e
|
||||
k = idx(t); % current level index (1..6)
|
||||
|
||||
% force inward movement at edges; prevents "flipping" to opposite edge
|
||||
if k == 1 && dir == -1, dir = +1; end
|
||||
if k == K && dir == +1, dir = -1; end
|
||||
|
||||
step = randi([1 j]); % 1..j steps
|
||||
kNew = k + dir*step;
|
||||
|
||||
% clamp to [1,K], no wrap-around
|
||||
if kNew < 1, kNew = 1; elseif kNew > K, kNew = K; end
|
||||
|
||||
% if clamped to the same edge repeatedly, flip direction to keep changing
|
||||
if kNew == k
|
||||
dir = -dir;
|
||||
kNew = max(1, min(K, k + dir*step));
|
||||
end
|
||||
|
||||
idx(t) = kNew;
|
||||
end
|
||||
end
|
||||
|
||||
x_err = lvls(idx);
|
||||
symbols.signal = reshape(x_err, size(symbols.signal)); % preserve original shape
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user