SD stuff working for PAM6 and MLSE is also calibrated
This commit is contained in:
@@ -1,125 +1,181 @@
|
||||
function [GMI,NGMI] = calc_ngmi(test_signal,reference_signal,options)
|
||||
% Silas implementation of (N)GMI calculation according to: J. Cho, L. Schmalen, und P. J. Winzer,
|
||||
% „Normalized Generalized Mutual Information as a Forward Error Correction Threshold for Probabilistically Shaped QAM“,
|
||||
% in 2017 European Conference on Optical Communication (ECOC), Sep. 2017, doi: 10.1109/ECOC.2017.8345872.
|
||||
|
||||
% This implementation assumes the same normal distributed noise for each
|
||||
% channel (sigma2 is calculated once for the whole signal)
|
||||
% (N)GMI for AWGN, supports PAM2/4/8 (per-symbol) and PAM6 (pair-based, 5 bits / 2 symbols)
|
||||
|
||||
arguments(Input)
|
||||
test_signal;
|
||||
reference_signal;
|
||||
options.skip_front = 0;
|
||||
options.skip_end = 0;
|
||||
options.returnErrorLocation = 0;
|
||||
test_signal
|
||||
reference_signal
|
||||
options.skip_front (1,1) double = 0
|
||||
options.skip_end (1,1) double = 0
|
||||
options.returnErrorLocation (1,1) double = 0 %#ok<NASGU>
|
||||
end
|
||||
|
||||
options.skip_end = abs(options.skip_end);
|
||||
options.skip_end = abs(options.skip_end);
|
||||
options.skip_front = abs(options.skip_front);
|
||||
assert((options.skip_end+options.skip_front) < numel(test_signal), ...
|
||||
"You can not skip more samples than the overall length.");
|
||||
|
||||
assert((options.skip_end+options.skip_front)<length(test_signal),"You can not skip more bits than overall length of data! Set skip_front or skip_end to lower value or check data_in");
|
||||
if isa(reference_signal,'Signal'), reference_signal = reference_signal.signal; end
|
||||
if isa(test_signal,'Signal'), test_signal = test_signal.signal; end
|
||||
|
||||
if isa(reference_signal,'Signal')
|
||||
reference_signal = reference_signal.signal;
|
||||
end
|
||||
% --- Trim head/tail consistently
|
||||
[test_signal,reference_signal] = trimseq(test_signal,reference_signal,options.skip_front,options.skip_end);
|
||||
|
||||
if isa(test_signal,'Signal')
|
||||
test_signal = test_signal.signal;
|
||||
end
|
||||
% --- Noise variance (real AWGN): use mean-square error for robustness
|
||||
err = test_signal - reference_signal;
|
||||
sigma2 = mean((err(:)).^2);
|
||||
|
||||
% TRIM
|
||||
[test_signal,reference_signal]=trimseq(test_signal,reference_signal,options.skip_front,options.skip_end);
|
||||
% --- Constellation (levels observed)
|
||||
constellation = unique(reference_signal);
|
||||
M = numel(constellation);
|
||||
|
||||
% CALC EVM
|
||||
[GMI,NGMI] = calc_ngmi_(test_signal,reference_signal);
|
||||
% --- Empirical P_X (uniform is fine too; keep your original behavior)
|
||||
N = numel(reference_signal);
|
||||
counts = arrayfun(@(c) sum(reference_signal==c), constellation);
|
||||
P_X = counts / N;
|
||||
|
||||
% --- Dispatch: PAM6 needs pairwise treatment (5 bits across 2 symbols)
|
||||
if M==6
|
||||
% ===== PAM6: 5 bits mapped to two consecutive symbols =====
|
||||
% Pair the sequence (drop last symbol if odd)
|
||||
numPairs = floor(N/2);
|
||||
if numPairs == 0
|
||||
GMI = 0; NGMI = 0; return;
|
||||
end
|
||||
y1 = test_signal(1:2:2*numPairs);
|
||||
y2 = test_signal(2:2:2*numPairs);
|
||||
x1 = reference_signal(1:2:2*numPairs);
|
||||
x2 = reference_signal(2:2:2*numPairs);
|
||||
|
||||
function [GMI,NGMI] = calc_ngmi_(test_signal,reference_signal)
|
||||
% Build the 36 transition table and its 5-bit labels (ETH-style)
|
||||
symbols = sort(constellation); % keep fixed order
|
||||
% all pairs using ndgrid (no dependency on combvec)
|
||||
[S1,S2] = ndgrid(symbols, symbols); % MxM
|
||||
trans_pairs = [S1(:), S2(:)]; % (M^2) x 2
|
||||
|
||||
assert(length(test_signal) == length(reference_signal),"Sequence length does not match");
|
||||
% Map those pairs to 5-bit labels using your PAMmapper (same as in your BCJR code)
|
||||
% NOTE: keep your normalization used by PAMmapper (sqrt(10)) if that's what it expects.
|
||||
bits72 = PAMmapper(6,0,"eth_style",0).demap( reshape(trans_pairs',[],1) );
|
||||
pam6bits = reshape(bits72', 5, []).'; % (M^2) x 5, row-aligned with trans_pairs
|
||||
|
||||
%%% implemented according to [1] J. Cho, L. Schmalen, und P. J. Winzer,
|
||||
% „Normalized Generalized Mutual Information as a Forward Error Correction Threshold for Probabilistically Shaped QAM“,
|
||||
% in 2017 European Conference on Optical Communication (ECOC), Sep. 2017, S. 1–3. doi: 10.1109/ECOC.2017.8345872.
|
||||
% Precompute mapping (pair -> bit label) and (symbol -> index)
|
||||
[ok1, idx_s1] = ismember(trans_pairs(:,1), symbols);
|
||||
[ok2, idx_s2] = ismember(trans_pairs(:,2), symbols);
|
||||
assert(all(ok1)&all(ok2), 'Transition level not found in symbols.');
|
||||
% For fast masking: linear indices of each (i,j) pair in an MxM grid
|
||||
lin_idx_pairs = sub2ind([M,M], idx_s1, idx_s2); % (M^2) x 1
|
||||
|
||||
error_vector = (test_signal-reference_signal);
|
||||
sigma2 = var(error_vector); %noise variance
|
||||
% Build pairwise prior P_pair = P_X(i)*P_X(j) on the MxM grid
|
||||
P_pair = P_X(:) * P_X(:).'; % MxM
|
||||
logP_pair = log(P_pair); % for numerical stability
|
||||
|
||||
%%% Separate Classes
|
||||
constellation = unique(reference_signal);
|
||||
received_sd = NaN(numel(constellation),length(reference_signal));
|
||||
lvlcol = cbrewer2('Set1',numel(constellation));
|
||||
for lvl = 1:numel(constellation)
|
||||
%Separate the equalized signal into the
|
||||
%respective levels based on the actually
|
||||
%transmitted level!
|
||||
received_sd(lvl,reference_signal==constellation(lvl)) = test_signal(reference_signal==constellation(lvl));
|
||||
end
|
||||
% Precompute constant term for log-likelihood
|
||||
logc = -0.5*log(2*pi*sigma2);
|
||||
|
||||
N = length(test_signal); % Number of received samples
|
||||
|
||||
M = length(constellation); %P
|
||||
m = log2(M); %bits per symbol
|
||||
|
||||
entries = sum(~isnan(received_sd),2)';
|
||||
P_X = ones(1,M)/M;%entries./N;
|
||||
|
||||
% Parameters
|
||||
symbols = constellation'; % PAM-4 symbols
|
||||
|
||||
gray_bits = PAMmapper(M,0).demap_(constellation); % Gray coding (bits per symbol)
|
||||
|
||||
% Conditional probability function for AWGN
|
||||
q_Y_given_X = @(y, x) (1 / sqrt(2 * pi * sigma2)) * exp(-(y - x).^2 / (2 * sigma2));
|
||||
|
||||
% Entropy term
|
||||
H_X = -sum(P_X .* log2(P_X)); % Entropy of input distribution
|
||||
|
||||
tx_bits = PAMmapper(M,0,"eth_style",0).demap(reference_signal);
|
||||
|
||||
% GMI computation
|
||||
bit_llr_sum = 0;
|
||||
for k = 1:N %loop over signal
|
||||
y_k = test_signal(k); % Current received sample
|
||||
% [~, closest_symbol_idx] = min(abs(symbols - y_k)); % Closest symbol index
|
||||
|
||||
for i = 1:m %loop over bit position
|
||||
% Extract i-th bit for each symbol
|
||||
bit_mask = gray_bits(:, i); % Binary column for i-th bit of all symbols
|
||||
|
||||
% matching_symbols = symbols(bit_mask == gray_bits(closest_symbol_idx, i)); %old, based on decision that mihht be wrong
|
||||
matching_symbols = symbols(bit_mask == tx_bits(k,i)); %new, based on tx bits
|
||||
|
||||
% Numerator: Sum over x in x_{b_{k, i}}
|
||||
numerator = sum(q_Y_given_X(y_k, matching_symbols) .* P_X(ismember(symbols, matching_symbols)));
|
||||
|
||||
% Denominator: Sum over all x
|
||||
denominator = sum(q_Y_given_X(y_k, symbols) .* P_X);
|
||||
|
||||
% Logarithmic contribution
|
||||
bit_llr_sum = bit_llr_sum + log2(numerator / denominator);
|
||||
end
|
||||
end
|
||||
|
||||
% Normalize the noise impact term by N
|
||||
bit_llr_sum = bit_llr_sum / N;
|
||||
|
||||
% GMI
|
||||
GMI = H_X + bit_llr_sum;
|
||||
NGMI = GMI / m;
|
||||
% Prepare transmitted 5-bit labels per observed pair (to split llr0/llr1)
|
||||
% Find each (x1(k),x2(k)) in trans_pairs:
|
||||
[tf, row_in_table] = ismember([x1(:), x2(:)], trans_pairs, 'rows');
|
||||
assert(all(tf), 'A reference pair was not found in the transition table.');
|
||||
tx_bits_pair = pam6bits(row_in_table, :); % numPairs x 5
|
||||
|
||||
% Precompute masks for bit=0 / bit=1 on the MxM grid for each bit position
|
||||
mask1 = false(M,M,5);
|
||||
mask0 = false(M,M,5);
|
||||
for b = 1:5
|
||||
tmp = false(M,M);
|
||||
tmp(lin_idx_pairs) = pam6bits(:,b) == 1;
|
||||
mask1(:,:,b) = tmp;
|
||||
tmp = false(M,M);
|
||||
tmp(lin_idx_pairs) = pam6bits(:,b) == 0;
|
||||
mask0(:,:,b) = tmp;
|
||||
end
|
||||
|
||||
function [data_,reference_]=trimseq(data,reference,skipstart,skip_end)
|
||||
|
||||
data_ = data(skipstart+1:end-skip_end,:);
|
||||
|
||||
delta_bits = length(reference) - length(data);
|
||||
|
||||
skip_end = delta_bits + skip_end;
|
||||
|
||||
reference_ = reference(skipstart+1:end-skip_end,:);
|
||||
% Loop pairs: compute exact LLRs via log-sum-exp over the MxM grid
|
||||
LLR_exact = zeros(numPairs,5);
|
||||
for k = 1:numPairs
|
||||
% log-likelihood vectors along each axis
|
||||
li = logc - ((y1(k) - symbols).^2) / (2*sigma2); % 1xM
|
||||
lj = logc - ((y2(k) - symbols).^2) / (2*sigma2); % 1xM
|
||||
% joint log-weights over all (i,j)
|
||||
logW = (li(:) + lj(:).') + logP_pair; % MxM
|
||||
|
||||
for b = 1:5
|
||||
% log P(bit=1 | y) ∝ logsumexp(logW over mask1)
|
||||
lnum = logsumexp(logW(mask1(:,:,b)));
|
||||
% log P(bit=0 | y) ∝ logsumexp(logW over mask0)
|
||||
lden = logsumexp(logW(mask0(:,:,b)));
|
||||
LLR_exact(k,b) = lnum - lden; % natural-log LLR
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
% --- Bitwise MI via consistency relation
|
||||
MI = zeros(1,5);
|
||||
for b = 1:5
|
||||
llr_b = LLR_exact(:,b);
|
||||
idx0 = (tx_bits_pair(:,b)==0);
|
||||
idx1 = ~idx0;
|
||||
I0 = mean( log2(1 + exp( llr_b(idx0))) ); % natural LLR inside exp()
|
||||
I1 = mean( log2(1 + exp(-llr_b(idx1))) );
|
||||
MI(b) = 1 - 0.5*(I0 + I1);
|
||||
end
|
||||
|
||||
% GMI per symbol (5 bits per 2 symbols)
|
||||
GMI = sum(MI)/2;
|
||||
NGMI = GMI / 2.5;
|
||||
|
||||
else
|
||||
% ===== Generic PAM2/4/8 etc.: per-symbol bit labeling =====
|
||||
symbols = constellation; % 1xM
|
||||
% Gray labels per symbol (your helper)
|
||||
gray_bits = PAMmapper(M,0).demap_(symbols); % M x log2(M)
|
||||
m = log2(M);
|
||||
|
||||
% Likelihood (real AWGN)
|
||||
q = @(y,x) (1/sqrt(2*pi*sigma2)) * exp(-(y - x).^2/(2*sigma2));
|
||||
|
||||
% Transmitted bits per sample
|
||||
tx_bits = PAMmapper(M,0,"eth_style",0).demap(reference_signal); % N x m
|
||||
|
||||
% Exact bit-LLRs and MI
|
||||
LLR_exact = zeros(N,m);
|
||||
for b = 1:m
|
||||
mask1 = (gray_bits(:,b)==1);
|
||||
mask0 = ~mask1;
|
||||
for k = 1:N
|
||||
yk = test_signal(k);
|
||||
den = sum(q(yk, symbols) .* P_X);
|
||||
num1 = sum(q(yk, symbols(mask1)) .* P_X(mask1));
|
||||
num0 = sum(q(yk, symbols(mask0)) .* P_X(mask0));
|
||||
% Use the ratio of posteriors P(bit=1|y)/P(bit=0|y)
|
||||
LLR_exact(k,b) = log(num1) - log(num0); % natural log
|
||||
end
|
||||
end
|
||||
|
||||
% Bitwise MI
|
||||
MI = zeros(1,m);
|
||||
for b = 1:m
|
||||
llr_b = LLR_exact(:,b);
|
||||
idx0 = (tx_bits(:,b)==0);
|
||||
idx1 = ~idx0;
|
||||
I0 = mean( log2(1 + exp( llr_b(idx0))) );
|
||||
I1 = mean( log2(1 + exp(-llr_b(idx1))) );
|
||||
MI(b) = 1 - 0.5*(I0 + I1);
|
||||
end
|
||||
|
||||
% Per-symbol GMI/NGMI
|
||||
GMI = sum(MI);
|
||||
NGMI = GMI / m;
|
||||
end
|
||||
|
||||
% ---------- helpers ----------
|
||||
function s = logsumexp(a)
|
||||
if isempty(a), s = -inf; return; end
|
||||
amax = max(a(:));
|
||||
s = amax + log(sum(exp(a(:) - amax)));
|
||||
end
|
||||
|
||||
function [data_,reference_] = trimseq(data,reference,skipstart,skip_end)
|
||||
data_ = data(skipstart+1:end-skip_end,:);
|
||||
delta = numel(reference) - numel(data_);
|
||||
reference_ = reference(skipstart+1:end-(skip_end+delta),:);
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user