halfway merged and pulled?!
This commit is contained in:
164
projects/ML_based_MLSE/analyze_filter_length.m
Normal file
164
projects/ML_based_MLSE/analyze_filter_length.m
Normal file
@@ -0,0 +1,164 @@
|
||||
%% analyze_filter_length.m
|
||||
clear; clc;
|
||||
|
||||
M = 4;
|
||||
randkey = 1;
|
||||
|
||||
% --- Parameter sweep
|
||||
order_range = 2:3:11; % FFE order
|
||||
delta_range = 0:2:4; % delta
|
||||
SNR_dB = 20;
|
||||
|
||||
% --- Prepare bit sequence
|
||||
order_bits = 19;
|
||||
s = RandStream('twister','Seed',randkey);
|
||||
for i = 1:log2(M)
|
||||
N = 2^(order_bits-1);
|
||||
bitpattern(:,i) = randi(s,[0 1], N, 1);
|
||||
end
|
||||
Bits = Informationsignal(bitpattern);
|
||||
Symbols = PAMmapper(M,0).map(Bits);
|
||||
Symbols.fs = 200e9;
|
||||
|
||||
% --- Channel (minimal ISI + AWGN)
|
||||
h = [0.3 0.9 0.3]; h = h/norm(h);
|
||||
symbols_filt = Symbols.filter(h,1);
|
||||
symbols_noi = symbols_filt;
|
||||
symbols_noi.signal = awgn(symbols_filt.signal,SNR_dB,'measured');
|
||||
|
||||
% --- Generate all parameter pairs
|
||||
[O,D] = ndgrid(order_range, delta_range);
|
||||
pairs = [O(:), D(:)];
|
||||
|
||||
training_len = 100;
|
||||
|
||||
ber_vec = nan(size(pairs,1),1); % initialize with NaN
|
||||
ber_training = nan(size(pairs,1),training_len);
|
||||
ce_vec = nan(size(pairs,1),1);
|
||||
ce_training = nan(size(pairs,1),training_len);
|
||||
|
||||
% --- Parallel loop over parameter pairs
|
||||
parfor k = 1:size(pairs,1)
|
||||
order_k = pairs(k,1);
|
||||
delta_k = pairs(k,2);
|
||||
|
||||
% Skip invalid combinations (delay cannot exceed filter length)
|
||||
if abs(delta_k) >= order_k
|
||||
fprintf('Skip: order=%d, delta=%d (invalid)\n', order_k, delta_k);
|
||||
continue;
|
||||
end
|
||||
|
||||
try
|
||||
ml = ML_MLSE("epochs_tr",training_len,"epochs_dd",1,"len_tr",2^15, ...
|
||||
"mu_dd",0.1,"mu_tr",0.1,"order",order_k,"sps",1, ...
|
||||
"traceback_depth",128,"L",3,"delta",delta_k,"adaptive_mu",0);
|
||||
|
||||
[y_ml,y_ref] = ml.process(symbols_noi,Symbols);
|
||||
ref_bits = PAMmapper(M,0).demap(y_ref);
|
||||
eq_bits = PAMmapper(M,0).demap(y_ml);
|
||||
|
||||
ber_training(k,:) = ml.ber;
|
||||
ce_training(k,:) = ml.ce;
|
||||
|
||||
[~,~,ber_vec(k)] = calc_ber(eq_bits.signal, ref_bits.signal, ...
|
||||
"skip_front",10,"skip_end",10);
|
||||
L = min(length(ml.ce),30);
|
||||
ce_vec(k) = mean(ml.ce(end-L+1:end));
|
||||
|
||||
fprintf('order=%d, delta=%d → BER=%.2e, CE=%.3f\n', ...
|
||||
order_k, delta_k, ber_vec(k), ce_vec(k));
|
||||
catch ME
|
||||
fprintf('Error at order=%d, delta=%d: %s\n', ...
|
||||
order_k, delta_k, ME.message);
|
||||
ber_vec(k) = NaN;
|
||||
ce_vec(k) = NaN;
|
||||
end
|
||||
end
|
||||
|
||||
% --- reshape to 2D matrices
|
||||
ber_mat = reshape(ber_vec, numel(order_range), numel(delta_range));
|
||||
ce_mat = reshape(ce_vec, numel(order_range), numel(delta_range));
|
||||
|
||||
|
||||
%% --- Plot BER
|
||||
figure; hold on
|
||||
cols = cbrewer2('Set1',10);
|
||||
for i = 1:numel(delta_range)
|
||||
plot(order_range,ber_mat(:,i),'DisplayName',sprintf('delta: %d',delta_range(i)),'Color',cols(i,:))
|
||||
end
|
||||
beautifyBERplot
|
||||
ylabel('BER'); xlabel('Filter Order [N]');
|
||||
title('BER vs. Filter order');
|
||||
ylim([1e-4, 0.1]);
|
||||
yline(3.8e-3,'HandleVisibility','off');
|
||||
yline(2.2e-4,'HandleVisibility','off');
|
||||
|
||||
%% --- Plot Cross-Entropy
|
||||
figure; hold on
|
||||
for i = 1:numel(delta_range)
|
||||
plot(order_range,ce_mat(:,i),'DisplayName',sprintf('delta: %d',delta_range(i)))
|
||||
end
|
||||
% beautifyBERplot
|
||||
ylabel('BER'); xlabel('Filter Order [N]');
|
||||
title('BER vs. Filter order');
|
||||
|
||||
%% --- Training Curves: BER and CE per combination
|
||||
figure('Name','Training Convergence'); hold on
|
||||
cols = cbrewer2('Set1', 10); % one color per delta
|
||||
|
||||
[O, D] = ndgrid(order_range, delta_range);
|
||||
|
||||
for i = 1:size(ber_training,1)
|
||||
ord = O(i);
|
||||
del = D(i);
|
||||
|
||||
if ord <= del
|
||||
continue;
|
||||
end
|
||||
% --- show only order 2 and 10
|
||||
if ord == 2
|
||||
lnst = '-';
|
||||
elseif ord == 5
|
||||
lnst = ':';
|
||||
elseif ord == 8
|
||||
lnst = '--';
|
||||
elseif ord == 11
|
||||
lnst = '-.';
|
||||
end
|
||||
|
||||
|
||||
b = ber_training(i,:);
|
||||
|
||||
|
||||
plot_label = sprintf('order=%d, delta=%d', ord, del);
|
||||
plot(1:length(b), b, 'Color', cols(del+1, :), ...
|
||||
'DisplayName', plot_label,'LineStyle',lnst);
|
||||
end
|
||||
|
||||
set(gca,'YScale','log');
|
||||
xlabel('Epoch');
|
||||
ylabel('BER');
|
||||
title('Training Convergence (BER)');
|
||||
legend('show');
|
||||
grid on;
|
||||
|
||||
|
||||
%% --- Cross-Entropy curves
|
||||
figure('Name','Cross-Entropy'); hold on
|
||||
cols = cbrewer2('Set1',size(ce_training,1));
|
||||
for i = 1:size(ce_training,1)
|
||||
|
||||
[O, D] = ndgrid(order_range, delta_range);
|
||||
plot_label = sprintf('order=%d, delta=%d', O(i), D(i));
|
||||
c = ce_training(i,:);
|
||||
c(~isfinite(c) | c==0) = NaN;
|
||||
if all(isnan(c)), continue; end
|
||||
plot(1:length(c), c, 'Color', cols(D(i)+1,:), ...
|
||||
'DisplayName', plot_label);
|
||||
end
|
||||
set(gca,'YScale','log');
|
||||
xlabel('Epoch');
|
||||
ylabel('Cross-Entropy');
|
||||
title('Training Convergence (CE)');
|
||||
legend('show');
|
||||
grid on;
|
||||
113
projects/ML_based_MLSE/analyze_mu.m
Normal file
113
projects/ML_based_MLSE/analyze_mu.m
Normal file
@@ -0,0 +1,113 @@
|
||||
|
||||
M = 4;
|
||||
order = 19;
|
||||
randkey = 1;
|
||||
|
||||
bitpattern = [];
|
||||
s = RandStream('twister','Seed',randkey);
|
||||
for i = 1:log2(M)
|
||||
N = 2^(order-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.fs = 200e9;
|
||||
|
||||
Bits_ = PAMmapper(M, 0, "eth_style", 0).demap(Symbols);
|
||||
|
||||
% --- Channel: minimal ISI response + AWGN ---
|
||||
h = [0.3 0.9 0.3]; % impulse response (normalized later if desired)
|
||||
h = h / norm(h); % optional normalization for unit energy
|
||||
|
||||
symbols_filt = Symbols.filter(h,1);
|
||||
|
||||
|
||||
%% SHOW Loss during training
|
||||
|
||||
mu = logspace(-3,-0.8,12);
|
||||
ber_ml_mlse = zeros(size(mu));
|
||||
ber_training = [];
|
||||
ce_training = [];
|
||||
|
||||
parfor i = 1:numel(mu)
|
||||
|
||||
symbols_noi = symbols_filt;
|
||||
SNR_dB = 20;
|
||||
symbols_noi.signal = awgn(symbols_filt.signal, SNR_dB, 'measured'); % AWGN with given SNR
|
||||
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",200,"epochs_dd",1,"len_tr",2^15,...
|
||||
"mu_dd",mu(i),"mu_tr",mu(i),"order",5,"sps",1,...
|
||||
"traceback_depth",128,"L",3,"delta",0,'adaptive_mu',0);
|
||||
|
||||
[y_ml_mlse,y_ref] = ml_mlse_equalizer.process(symbols_noi,Symbols);
|
||||
ref_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ref);
|
||||
ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse);
|
||||
[~, errors, ber_ml_mlse(i), errpos] = calc_ber(ml_mlse_bits.signal, ref_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse(i));
|
||||
ber_training(i,:) = ml_mlse_equalizer.ber;
|
||||
ce_training(i,:) = ml_mlse_equalizer.ce;
|
||||
end
|
||||
|
||||
%%
|
||||
symbols_noi = symbols_filt;
|
||||
SNR_dB = 20;
|
||||
symbols_noi.signal = awgn(symbols_filt.signal, SNR_dB, 'measured'); % AWGN with given SNR
|
||||
ml_mlse_equalizer_adap = ML_MLSE("epochs_tr",200,"epochs_dd",1,"len_tr",2^16,...
|
||||
"mu_dd",1,"mu_tr",1,"order",5,"sps",1,...
|
||||
"traceback_depth",128,"L",3,"delta",0,"adaptive_mu",1);
|
||||
|
||||
[y_ml_mlse,y_ref] = ml_mlse_equalizer_adap.process(symbols_noi,Symbols);
|
||||
ref_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ref);
|
||||
ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse);
|
||||
[~, errors, ber_ml_mlse_, errpos] = calc_ber(ml_mlse_bits.signal, ref_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse_);
|
||||
|
||||
%%
|
||||
figure();hold on
|
||||
|
||||
plot(mu,ber_ml_mlse,'DisplayName','ML-based MLSE');
|
||||
|
||||
beautifyBERplot;
|
||||
xlim([mu(1), mu(end)]);
|
||||
xlabel('mu');
|
||||
ylabel('BER');
|
||||
title('PAM-4; M=3; AWGN Channel');
|
||||
ylim([1e-5 0.1]);
|
||||
|
||||
%%
|
||||
figure()
|
||||
hold on;
|
||||
cols = cbrewer2('Spectral',12);
|
||||
for i = 1:12
|
||||
plot(1:200,ber_training(i,:),'DisplayName', sprintf('mu=%.3f ',mu(i)),'Color',cols(i,:));
|
||||
end
|
||||
set(gca,'YScale','log');
|
||||
xlabel('Epoch');
|
||||
ylabel('BER');
|
||||
title('PAM-4; L=3; SNR=20; AWGN Channel');
|
||||
plot(1:200,ml_mlse_equalizer_adap.ber,'DisplayName','Adaptive mu');
|
||||
|
||||
%%
|
||||
figure()
|
||||
hold on;
|
||||
cols = cbrewer2('Spectral',12);
|
||||
for i = 1:12
|
||||
plot(1:200,ce_training(i,:),'DisplayName', sprintf('mu=%.3f ',mu(i)),'Color',cols(i,:));
|
||||
end
|
||||
set(gca,'YScale','log');
|
||||
xlabel('Epoch');
|
||||
ylabel('Cross-Entropy');
|
||||
title('PAM-4; L=3; SNR=20; AWGN Channel');
|
||||
plot(1:200,ml_mlse_equalizer_adap.ce,'DisplayName','Adaptive mu');
|
||||
|
||||
|
||||
%% SUPER LONG EPOCHS
|
||||
|
||||
|
||||
163
projects/ML_based_MLSE/experimental_data.m
Normal file
163
projects/ML_based_MLSE/experimental_data.m
Normal file
@@ -0,0 +1,163 @@
|
||||
|
||||
|
||||
dsp_options.storage_path = 'Z:\2024\sioe_labor\';
|
||||
dsp_options.max_occurences = 1;
|
||||
database = DBHandler("dataBase", 'labor_highspeed', "type", 'mysql' );
|
||||
run_id = 2776;
|
||||
dataTable = queryRunid(run_id, database);
|
||||
fsym = dataTable.symbolrate;
|
||||
M = double(dataTable.pam_level);
|
||||
duob_mode = db_mode(strrep(dataTable.db_mode,'"',''));
|
||||
|
||||
% if database.checkIfRunExists('Results','run_id',run_id)
|
||||
% disp(['Already got at least one reulst for run id: ',num2str(run_id),' '])
|
||||
% return
|
||||
% end
|
||||
|
||||
% Load and Sync signal data from DB
|
||||
[Tx_bits, Symbols, Scpe_cell, ~] = loadAndSyncSignalDataFromDb(dataTable, dsp_options);
|
||||
|
||||
% Preprocess signal
|
||||
Scpe_sig = preprocessSignal(Scpe_cell{1}, Symbols, fsym);
|
||||
|
||||
Scpe_sig.spectrum("fignum",1,"displayname",'Rx')
|
||||
|
||||
%%
|
||||
|
||||
ffe_order = [50, 5, 5];
|
||||
mu_ffe = [0.0001, 0.0008, 0.001];
|
||||
mu_dfe = 0.0004;
|
||||
eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^14,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1);
|
||||
mlse_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',0,'trellis_state_mode',3);
|
||||
|
||||
|
||||
%Duobinary Targeting
|
||||
db_ref_sequence = Duobinary().encode(Symbols);
|
||||
db_ref_constellation = unique(db_ref_sequence.signal);
|
||||
[eq_signal, eq_noise] = eq_.process(Scpe_sig,db_ref_sequence);
|
||||
|
||||
%%
|
||||
if 1
|
||||
[mlse_sig_sd,LLR,GMI_MLSE] = mlse_.process(eq_signal,Symbols);
|
||||
else
|
||||
% Ml MLSE
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",20,"epochs_dd",1,"len_tr",length(eq_signal),...
|
||||
"mu_dd",0.01,"mu_tr",0.01,"order",11,"sps",2,...
|
||||
"traceback_depth",128,"L",1,"delta",4,'adaptive_mu',0);
|
||||
[mlse_sig_sd,ref_sig] = ml_mlse_equalizer.process(Scpe_sig,db_ref_sequence);
|
||||
end
|
||||
|
||||
%%
|
||||
mlse_sig_sd_decoded = Duobinary().decode(mlse_sig_sd,"M",M);
|
||||
ref_sig_decoded = Duobinary().decode(db_ref_sequence,"M",M);
|
||||
|
||||
mlse_sig_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_sd_decoded);
|
||||
ref_sig_bits = PAMmapper(M,0,"eth_style",0).demap(ref_sig_decoded);
|
||||
|
||||
err = sum(ref_sig_decoded.signal ~= mlse_sig_sd_decoded.signal);
|
||||
|
||||
[bits_db,errors_db,ber_db,a] = calc_ber(mlse_sig_bits.signal,ref_sig_bits.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
|
||||
|
||||
%%
|
||||
switch duob_mode
|
||||
|
||||
case db_mode.no_db
|
||||
% TX Data is not precoded:
|
||||
|
||||
% A) Emulate diff precoding
|
||||
mlse_sig_hd_precoded = Duobinary().encode(mlse_sig_hd,"M",M);
|
||||
mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded,"M",M);
|
||||
|
||||
tx_symbols_precoded = Duobinary().encode(Symbols);
|
||||
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
|
||||
|
||||
tx_bits_precoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols_precoded);
|
||||
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd_precoded);
|
||||
|
||||
[~,errors_db_diff_precoded,ber_db_diff_precoded,~] = calc_ber(rx_bits_mlse.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
|
||||
%B) Just determine BER
|
||||
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd);
|
||||
[bits_mlse,errors_mlse,ber_db,~] = calc_ber(rx_bits_mlse.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
|
||||
case db_mode.db_precoded
|
||||
|
||||
% Daten SIND TATSÄCHLICH precoded auf TX Seite:
|
||||
|
||||
% A) Decode at Rx if no DB targeting was applied (we are in VNLE or MLSE EQ structure here!
|
||||
mlse_sig_hd_decoded = Duobinary().encode(mlse_sig_hd,"M",M);
|
||||
mlse_sig_hd_decoded = Duobinary().decode(mlse_sig_hd_decoded,"M",M);
|
||||
rx_bits_mlse_decoded = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd_decoded);
|
||||
[~,errors_db_diff_precoded,ber_db_diff_precoded,a] = calc_ber(rx_bits_mlse_decoded.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
burst_db_precoded = count_error_bursts(a, 40);
|
||||
% B) Omit the Coding by comparing with demapped TX symbol sequence
|
||||
|
||||
Tx_bits_ = PAMmapper(M,0,"eth_style",0).demap(Symbols);
|
||||
rx_bits_mlse = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd);
|
||||
[bits_db,errors_db,ber_db,a] = calc_ber(rx_bits_mlse.signal,Tx_bits_.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
burst_db = count_error_bursts(a, 40);
|
||||
|
||||
cols = linspecer(8);
|
||||
figure();hold on;
|
||||
stem(1:40,burst_db,'LineWidth',1,'Color',cols(4,:),'Marker','_','DisplayName','w/o diff. precoder');
|
||||
stem(1:40,burst_db_precoded,'LineWidth',1,'Color',cols(3,:),'Marker','.','LineStyle','-','DisplayName','w diff. precoder');
|
||||
xlabel('Bit Error Burst Length')
|
||||
ylabel('Occurence')
|
||||
set(gca, 'yscale', 'log');
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%% SHOW Loss during training
|
||||
|
||||
mu = logspace(-3,-0.8,12);
|
||||
ber_ml_mlse = zeros(size(mu));
|
||||
ber_training = [];
|
||||
ce_training = [];
|
||||
|
||||
parfor i = 1:numel(mu)
|
||||
|
||||
|
||||
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",200,"epochs_dd",1,"len_tr",length(Scpe_sig),...
|
||||
"mu_dd",mu(i),"mu_tr",mu(i),"order",11,"sps",2,...
|
||||
"traceback_depth",128,"L",2,"delta",4,'adaptive_mu',0);
|
||||
|
||||
[y_ml_mlse,y_ref] = ml_mlse_equalizer.process(Scpe_sig,Symbols);
|
||||
ref_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ref);
|
||||
ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse);
|
||||
[~, errors, ber_ml_mlse(i), errpos] = calc_ber(ml_mlse_bits.signal, ref_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse(i));
|
||||
|
||||
ber_training(i,:) = ml_mlse_equalizer.ber;
|
||||
ce_training(i,:) = ml_mlse_equalizer.ce;
|
||||
end
|
||||
|
||||
%%
|
||||
figure();hold on
|
||||
|
||||
plot(mu,ber_ml_mlse,'DisplayName','ML-based MLSE');
|
||||
|
||||
beautifyBERplot;
|
||||
xlim([mu(1), mu(end)]);
|
||||
xlabel('mu');
|
||||
ylabel('BER');
|
||||
title('PAM-4; M=3; AWGN Channel');
|
||||
ylim([1e-5 0.1]);
|
||||
|
||||
|
||||
%%
|
||||
figure()
|
||||
hold on;
|
||||
cols = cbrewer2('Spectral',12);
|
||||
for i = 1:12
|
||||
plot(1:200,ber_training(i,:),'DisplayName', sprintf('mu=%.3f ',mu(i)),'Color',cols(i,:));
|
||||
end
|
||||
set(gca,'YScale','log');
|
||||
xlabel('Epoch');
|
||||
ylabel('BER');
|
||||
title('PAM-4; L=3; SNR=20; AWGN Channel');
|
||||
plot(1:200,ml_mlse_equalizer_adap.ber,'DisplayName','Adaptive mu');
|
||||
13
projects/ML_based_MLSE/interp_fec_cross.m
Normal file
13
projects/ML_based_MLSE/interp_fec_cross.m
Normal file
@@ -0,0 +1,13 @@
|
||||
function rop_fec = interp_fec_cross(rops, ber, fec_thr)
|
||||
if all(~isfinite(ber))
|
||||
rop_fec = NaN; return;
|
||||
end
|
||||
idx = find(ber < fec_thr, 1, 'first');
|
||||
if isempty(idx) || idx == 1
|
||||
rop_fec = NaN; return; % no crossing
|
||||
end
|
||||
% linear interpolation between the two nearest points
|
||||
x1 = rops(idx-1); x2 = rops(idx);
|
||||
y1 = ber(idx-1); y2 = ber(idx);
|
||||
rop_fec = interp1([y1 y2], [x1 x2], fec_thr, 'linear', NaN);
|
||||
end
|
||||
BIN
projects/ML_based_MLSE/minimal_example_huawei.zip
Normal file
BIN
projects/ML_based_MLSE/minimal_example_huawei.zip
Normal file
Binary file not shown.
555
projects/ML_based_MLSE/minimal_example_huawei/bcjr_pam.m
Normal file
555
projects/ML_based_MLSE/minimal_example_huawei/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
|
||||
193
projects/ML_based_MLSE/minimal_example_huawei/minimal_example.m
Normal file
193
projects/ML_based_MLSE/minimal_example_huawei/minimal_example.m
Normal file
@@ -0,0 +1,193 @@
|
||||
|
||||
if 0
|
||||
% A) RUN FULL LOOP
|
||||
M_format = [2,4,6,8];
|
||||
snr = 10:25;
|
||||
else
|
||||
% B) RUN FOR DEBUG AND TEST
|
||||
M_format = 4;
|
||||
snr = 20;
|
||||
end
|
||||
|
||||
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, 0.2]; % 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);
|
||||
|
||||
for s = 1:length(snr)
|
||||
|
||||
% apply noise
|
||||
y = awgn(y_filt,snr(s),"measured",1);
|
||||
|
||||
% apply ml-MLSE
|
||||
adaptive_mu = 0;
|
||||
mu_lms = 0.15;
|
||||
ml_mlse_equalizer = ml_mlse_pam("epochs_tr",50,"epochs_dd",1,"len_tr",length(y)/2,...
|
||||
"mu_dd",mu_lms,"mu_tr",mu_lms,"order",11,"sps",1,...
|
||||
"L",2,"delta",4,"adaptive_mu",adaptive_mu);
|
||||
|
||||
[ml_mlse_estimate,~] = ml_mlse_equalizer.process(y,tx_symbols);
|
||||
rx_symbols = ml_mlse_estimate .* scaling;
|
||||
bits_rx = pamdemap(rx_symbols,M);
|
||||
|
||||
BER_ml(m,s) = nnz(bits_tx ~= bits_rx) / numel(bits_tx);
|
||||
fprintf('BER = %.2e \n', BER_ml(m,s));
|
||||
|
||||
|
||||
% 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(m,s));
|
||||
|
||||
BER_llr(m,s) = nnz(bits_tx ~= bits_rx) / numel(bits_tx);
|
||||
fprintf('BER = %.2e \n', BER_llr(m,s));
|
||||
end
|
||||
end
|
||||
%%
|
||||
figure();hold on
|
||||
for m = 1:length(M_format)
|
||||
p=plot(snr,BER_llr(m,:),'DisplayName',sprintf('Viterbi: PAM %d',M_format(m)));
|
||||
plot(snr,BER_ml(m,:),'DisplayName',sprintf('ML-Based: PAM %d',M_format(m)),'LineStyle',':','Color',p.Color);
|
||||
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(snr,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
|
||||
473
projects/ML_based_MLSE/minimal_example_huawei/ml_mlse_pam.m
Normal file
473
projects/ML_based_MLSE/minimal_example_huawei/ml_mlse_pam.m
Normal file
@@ -0,0 +1,473 @@
|
||||
classdef ml_mlse_pam < handle
|
||||
|
||||
% ALGORITHM DESCRIBED IN:
|
||||
% W. Lanneer and Y. Lefevre, “Machine Learning-Based Pre-Equalizers for
|
||||
% Maximum Likelihood Sequence Estimation in High-Speed PONs,”
|
||||
% in 2023 31st European Signal Processing Conference
|
||||
|
||||
% Further ML Refs:
|
||||
% https://machinelearningmastery.com/cross-entropy-for-machine-learning/
|
||||
% https://docs.pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html
|
||||
|
||||
% The central idea is to overcome the (white-) noise assumption within the previously described
|
||||
% Viterbi algorithm, more precisely a closed-loop optimization is proposed that finds a suitable
|
||||
% filter-set to directly compute the branch metrics c_k (s,s^' ). These can directly be used to
|
||||
% carry out the conventional Viterbi algorithm. The system consists of S^L S=F linear FIR filters,
|
||||
% combined with one bias coefficient respectively. These filters take the received input samples to
|
||||
% compute the branch metrics estimates (c_k ) ̂(s,s^' ) according toThe central idea is to overcome
|
||||
% the (white-) noise assumption within the previously described Viterbi algorithm, more precisely
|
||||
% a closed-loop optimization is proposed that finds a suitable filter-set to directly compute the
|
||||
% branch metrics c_k (s,s^' ). These can directly be used to carry out the conventional Viterbi
|
||||
% algorithm. The system consists of S^L S=F linear FIR filters, combined with one bias coefficient
|
||||
% respectively. These filters take the received input samples to compute the branch metrics
|
||||
% estimates. Finally, the usual Viterbi is carried out...
|
||||
|
||||
% Recommended Settings and some findings:
|
||||
|
||||
% Requires many training epochs. According to ML people, 100,200 or
|
||||
% even up to 1000 epochs are normal for ML-convergence
|
||||
|
||||
% The mu parameter _can_ be adaptive - using the cross entropy and when
|
||||
% analyzing the isolated training it looks very promisig. However, is
|
||||
% later use I found this is not as stable as a fixed learning rate.
|
||||
% mu = 0.1 worked good for me
|
||||
|
||||
% Longer orders/ filter length are not always better. For me order=11
|
||||
% was good.
|
||||
|
||||
% Delay factor (delta) is good when the order is also increased. With
|
||||
% order = 11, a delta of =4 shows good results
|
||||
|
||||
properties
|
||||
sps % usually 2
|
||||
order
|
||||
e
|
||||
e_tr
|
||||
error
|
||||
|
||||
len_tr
|
||||
mu_tr
|
||||
epochs_tr
|
||||
|
||||
% dd_mode -> not implemented here!
|
||||
mu_dd %weight update in dd mode
|
||||
epochs_dd
|
||||
|
||||
adaptive_mu
|
||||
|
||||
constellation
|
||||
|
||||
L %viterbi memory length
|
||||
|
||||
alpha
|
||||
DIR
|
||||
DIR_flip
|
||||
trellis_states
|
||||
|
||||
traceback_depth
|
||||
|
||||
S
|
||||
Nf
|
||||
delta
|
||||
nStates
|
||||
nFeasible
|
||||
combs
|
||||
first_sym
|
||||
last_sym
|
||||
valid
|
||||
valid_to_idx
|
||||
valid_from_idx
|
||||
w
|
||||
nbiasTerms
|
||||
|
||||
true_to_state_idx
|
||||
state_dict % containers.Map: key(sequence)->state index
|
||||
key_fmt = '%.8g_'; % key format for sequence strings
|
||||
nSym % |constellation|
|
||||
|
||||
ber = []
|
||||
ce = ones(1,1);
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = ml_mlse_pam(options)
|
||||
arguments(Input)
|
||||
|
||||
options.sps = 2;
|
||||
options.order = 15;
|
||||
|
||||
options.len_tr = 4096;
|
||||
options.mu_tr = 0;
|
||||
options.epochs_tr = 5;
|
||||
|
||||
% options.dd_mode = 1;
|
||||
options.mu_dd = 1e-5;
|
||||
options.epochs_dd = 5;
|
||||
|
||||
options.adaptive_mu = 1;
|
||||
|
||||
options.delta = 0;
|
||||
options.traceback_depth = 1024;
|
||||
|
||||
options.L = 1
|
||||
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
obj.e = zeros(obj.order,1);
|
||||
obj.error = 0;
|
||||
end
|
||||
|
||||
function [x_viterbi,x_ref] = process(obj, X, D)
|
||||
|
||||
% actual processing of the signal (steps 1. - 3.)
|
||||
% 1 normalize RMS
|
||||
X = X./rms(X);
|
||||
|
||||
% Use sorted constellation for deterministic mapping
|
||||
obj.constellation = sort(unique(D),'ascend');
|
||||
obj.nSym = numel(obj.constellation);
|
||||
|
||||
if length(X)/length(D) ~= obj.sps
|
||||
warning('Signal length does not fit to reference!');
|
||||
end
|
||||
|
||||
% ==============================================================
|
||||
% INITIALIZATION
|
||||
% ==============================================================
|
||||
|
||||
% --- Parameters
|
||||
obj.S = numel(obj.constellation); % Num of Symbols
|
||||
obj.Nf = obj.order*obj.sps; % filter length (auto adapt for n-SPS...)
|
||||
obj.nStates = obj.S^obj.L; % S^L states
|
||||
obj.nFeasible = obj.nStates*obj.S; % S^(L+1) feasible states
|
||||
|
||||
% --- Trellis mapping
|
||||
obj.trellis_states = reshape(obj.constellation,1,[]); % make row vector
|
||||
pre_comb_mat = repmat(obj.trellis_states, obj.L, 1);
|
||||
pre_comb_cell = mat2cell(pre_comb_mat, ones(1,obj.L), size(pre_comb_mat,2));
|
||||
obj.combs = fliplr(combvec(pre_comb_cell{:}).'); % rows: states, columns: [x_k, x_{k-1}, ...]
|
||||
obj.first_sym = obj.combs(:,1);
|
||||
obj.last_sym = obj.combs(:,end);
|
||||
obj.nStates = size(obj.combs,1);
|
||||
|
||||
% --- Valid transitions; adapted from the old Viterbi in
|
||||
% Move-It where the "noise free received" states are calculated
|
||||
% using the same loop and clause
|
||||
obj.valid = false(obj.nStates);
|
||||
for from = 1:obj.nStates
|
||||
for to = 1:obj.nStates
|
||||
if all(obj.combs(to,2:end) == obj.combs(from,1:end-1))
|
||||
obj.valid(to,from) = true;
|
||||
end
|
||||
end
|
||||
end
|
||||
[obj.valid_to_idx, obj.valid_from_idx] = find(obj.valid);
|
||||
|
||||
% Allocate vectors and weights
|
||||
% !! IF SHAPE FIT, then we already have smth there an we want
|
||||
% to start with the existing filter-set (saves comp. time/ or to test fixed filter on new data)
|
||||
obj.nbiasTerms = 1;
|
||||
if isempty(obj.w) || any(size(obj.w) ~= [obj.Nf+obj.nbiasTerms,obj.nFeasible])
|
||||
obj.w = zeros(obj.Nf+obj.nbiasTerms,obj.nFeasible); % filter weights per transition + bias tap
|
||||
% obj.w = randn(obj.Nf+obj.nbiasTerms,obj.nFeasible);
|
||||
end
|
||||
|
||||
% This is a weird workaround - but it works and is much faster
|
||||
% than findig the state indices every time:
|
||||
% Precompute dictionary for fast state lookup (sequence -> state)
|
||||
keys = cell(obj.nStates,1);
|
||||
for i = 1:obj.nStates
|
||||
keys{i} = obj.seq_key(obj.combs(i,:)); % combs row is already [x_k, x_{k-1}, ...]
|
||||
end
|
||||
obj.state_dict = containers.Map(keys, 1:obj.nStates);
|
||||
|
||||
% ==============================================================
|
||||
% TRAINING
|
||||
% ==============================================================
|
||||
|
||||
n = obj.len_tr;
|
||||
training = 1;
|
||||
obj.equalize(X, D,obj.mu_tr,obj.epochs_tr,n,training);
|
||||
obj.e_tr = obj.e;
|
||||
|
||||
% ==============================================================
|
||||
% Testing; Fixed Mode
|
||||
% ==============================================================
|
||||
|
||||
n = length(X);
|
||||
training = 0;
|
||||
obj.mu_dd = obj.mu_tr; %For now no DD mode is implemented...
|
||||
[x_viterbi,x_ref]=obj.equalize(X, D,obj.mu_dd,obj.epochs_dd,n,training);
|
||||
|
||||
end
|
||||
|
||||
function [y,y_ref] = equalize(obj,x,d,mu,epochs,N,training)
|
||||
% ==============================================================
|
||||
% ML-Based Branch Metric Estimation + Viterbi
|
||||
% ==============================================================
|
||||
debug = 1;
|
||||
showPlots = 1;
|
||||
|
||||
nSymbols = ceil(N/obj.sps);
|
||||
|
||||
for epoch = 1:epochs
|
||||
|
||||
% state metrics (log-domain costs): keep as column [nStatesx1]
|
||||
pm = zeros(obj.nStates,1);
|
||||
v_tilde = zeros(1,obj.nFeasible);
|
||||
pred = zeros(nSymbols, obj.nStates);
|
||||
pm_sto = nan(obj.nStates, nSymbols);
|
||||
CE_accum = 0;
|
||||
|
||||
% START IDX can be randomized during training, but this
|
||||
% requires some testing - it is not better, maybe a
|
||||
% solutiuon is to use the same window for 10-20 epochs
|
||||
% and then switch to another window
|
||||
% for now: simply use the first parts of the signal for
|
||||
% training and also for testing... not "the
|
||||
randomize_training_window = 0;
|
||||
if randomize_training_window && training
|
||||
max_start = length(x) - ( (ceil(N/obj.sps)-1)*obj.sps + 1 );
|
||||
max_start = max(1, max_start); % safety
|
||||
start_sample = randi([1, max_start], 1); %rnd training; not really good
|
||||
else
|
||||
start_sample = 1;
|
||||
end
|
||||
|
||||
end_sample = start_sample + (ceil(N/obj.sps)-1)*obj.sps;
|
||||
start_symbol = 1 + floor((start_sample - 1)/obj.sps); % ABSOLUTE symbol index
|
||||
|
||||
symbol = 0;
|
||||
for sample = start_sample:obj.sps:end_sample
|
||||
symbol = symbol + 1;
|
||||
k = symbol;
|
||||
sym_idx = start_symbol + (symbol - 1);
|
||||
|
||||
% input signal window y_k; delayed by delta
|
||||
i1 = sample - obj.Nf + 1 + obj.delta;
|
||||
i2 = sample + obj.delta;
|
||||
buf = x(max(1,i1):min(length(x),i2));
|
||||
padL = max(0,1 - i1);
|
||||
padR = max(0,i2 - length(x));
|
||||
yk = [zeros(padL,1); buf(:); zeros(padR,1)]; % Nfx1
|
||||
yk = [yk;ones( obj.nbiasTerms,1)];
|
||||
|
||||
% Apply Filter; Predict branch metrics for all feasible transitions: c_hat
|
||||
% Formula (8)
|
||||
c_hat = (yk.' * obj.w); % [1xnFeasible]
|
||||
c_hat = c_hat.'; % [nFeasiblex1]
|
||||
|
||||
% Extended path metrics: v_tilde = pm(from) + c_hat
|
||||
v_tilde = pm(obj.valid_from_idx) + c_hat; % [nFeasiblex1]
|
||||
|
||||
% ===== Cross Entropy Loss Update =====
|
||||
|
||||
if 1 %training
|
||||
% --- allocate storage once
|
||||
if epoch == 1 && symbol == 1
|
||||
obj.true_to_state_idx = ones(ceil(N/obj.sps),1,'uint32');
|
||||
end
|
||||
|
||||
% --- previous "to" becomes current "from"
|
||||
if symbol > 1
|
||||
true_from_state_idx = obj.true_to_state_idx(symbol-1);
|
||||
else
|
||||
true_from_state_idx = 1;
|
||||
end
|
||||
|
||||
% --- compute or reuse "to" state
|
||||
if epoch == 1
|
||||
% only compute in first epoch
|
||||
if sym_idx >= obj.L
|
||||
key_to = obj.seq_key(flip(d(sym_idx-obj.L+1 : sym_idx)));
|
||||
if isKey(obj.state_dict, key_to)
|
||||
obj.true_to_state_idx(symbol) = obj.state_dict(key_to);
|
||||
else
|
||||
obj.true_to_state_idx(symbol) = true_from_state_idx;
|
||||
end
|
||||
else
|
||||
obj.true_to_state_idx(symbol) = true_from_state_idx;
|
||||
end
|
||||
end
|
||||
|
||||
% --- ensure valid (from,to)
|
||||
dirac = zeros(obj.nFeasible,1);
|
||||
mask = obj.valid_from_idx==true_from_state_idx & ...
|
||||
obj.valid_to_idx == obj.true_to_state_idx(symbol);
|
||||
if any(mask)
|
||||
dirac(mask) = 1;
|
||||
else
|
||||
idx = find(obj.valid_from_idx==true_from_state_idx,1,'first');
|
||||
dirac(idx) = 1;
|
||||
obj.true_to_state_idx(symbol) = obj.valid_to_idx(idx);
|
||||
end
|
||||
|
||||
% softmax over -v_tilde (numerically safe shift)
|
||||
v_shift = -(v_tilde - min(v_tilde)); % shift to small positive numbers
|
||||
v_shift = min(v_shift, 100); % clamp exponent argument to avoid extreme numbers/ overflow (exp(50)=5e21)
|
||||
expv = exp(v_shift);
|
||||
p = expv ./ (sum(expv) + eps);
|
||||
|
||||
% Cross entropy
|
||||
CE_symbol(symbol) = -log(p(dirac==1) + eps);
|
||||
|
||||
if sym_idx > obj.L
|
||||
CE_smooth(symbol) = 0.01*CE_symbol(symbol) + 0.99*CE_smooth(symbol-1);
|
||||
else
|
||||
if epoch > 1
|
||||
CE_smooth(symbol) = obj.ce(end); %stitch together ce from last epoch? or =1 for very first round?!
|
||||
else
|
||||
CE_smooth(symbol) = CE_symbol(symbol);
|
||||
end
|
||||
end
|
||||
|
||||
CE_accum = CE_symbol(symbol) + CE_accum;
|
||||
|
||||
% Formula (10)
|
||||
% gradient term (t - p)
|
||||
dmp = (dirac - p)'; % 1xnFeasible
|
||||
|
||||
% Formula (10)
|
||||
dL_Dw = (yk) .* dmp;
|
||||
|
||||
% Start updates only when the symbol index has ≥ L history
|
||||
if sym_idx >= obj.L
|
||||
if obj.adaptive_mu
|
||||
mu_eff = CE_smooth(sym_idx);
|
||||
mu_eff = max(min(mu_eff, 0.2), 1e-4);
|
||||
else
|
||||
mu_eff = mu;
|
||||
end
|
||||
|
||||
% see Algorithm 1 in paper
|
||||
obj.w = obj.w - mu_eff .* dL_Dw; % (Nf+1)xnFeasible
|
||||
end
|
||||
|
||||
% if debug && epoch > 2
|
||||
% figure(100);
|
||||
% subplot(4,1,1);
|
||||
% heatmap(p');
|
||||
% title('Probs')
|
||||
% subplot(4,1,2);
|
||||
% heatmap(dmp);
|
||||
% title('Update')
|
||||
% subplot(4,1,3);
|
||||
% heatmap(dL_Dw);
|
||||
% title('Update')
|
||||
% subplot(4,1,4);
|
||||
% heatmap(bj.w);
|
||||
% title('Update')
|
||||
%
|
||||
% end
|
||||
|
||||
end
|
||||
|
||||
% Compare-Select
|
||||
v_tilde_mat = inf(obj.nStates, obj.nStates);
|
||||
v_tilde_mat(obj.valid) = v_tilde; %reshapes to usual (from x to) matrix
|
||||
[pm_next, pred(k,:)] = min(v_tilde_mat, [], 2); %here, calc min for each column
|
||||
|
||||
% re-center, otherwise it will overflow
|
||||
pm_next = pm_next - min(pm_next);
|
||||
|
||||
pm = pm_next;
|
||||
pm_sto(:,symbol) = pm;
|
||||
end
|
||||
|
||||
% Traceback
|
||||
[~, s_end] = min(pm);
|
||||
viterbi_path = zeros(symbol,1);
|
||||
viterbi_path(symbol) = s_end;
|
||||
for n = symbol:-1:2
|
||||
viterbi_path(n-1) = pred(n, viterbi_path(n));
|
||||
end
|
||||
|
||||
% cut here to have the same indices when shuffling/
|
||||
% starting the start_symbol indx != 1
|
||||
y_ref = d(start_symbol:end);
|
||||
y = obj.first_sym(viterbi_path);
|
||||
|
||||
% Debug and Plots
|
||||
if debug && training
|
||||
sym_start = start_symbol;
|
||||
sym_end = start_symbol + symbol - 1;
|
||||
ref_slice = d(sym_start : sym_end);
|
||||
err = sum(y ~= ref_slice(1:numel(y)));
|
||||
|
||||
try %works with demapper, not provided in Deliverable
|
||||
ref_bits = PAMmapper(obj.S,0).demap(ref_slice);
|
||||
eq_bits = PAMmapper(obj.S,0).demap(y);
|
||||
[~, ~, ber, ~] = calc_ber(ref_bits, eq_bits, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('Epoch: %d - BER: %.1e \n',epoch, ber);
|
||||
obj.ber(epoch) = ber;
|
||||
berlabel = 'BER';
|
||||
catch %fallback ser
|
||||
ser = err./length(y);
|
||||
fprintf('Epoch: %d - SER: %.1e \n',epoch, ser);
|
||||
obj.ber(epoch) = ser;
|
||||
berlabel = 'BER';
|
||||
end
|
||||
|
||||
obj.ce(epoch) = CE_accum./symbol;
|
||||
|
||||
if showPlots
|
||||
figure(10);clf
|
||||
subplot(3,2,1:2);
|
||||
heatmap(obj.w);
|
||||
title('Filter')
|
||||
|
||||
subplot(3,2,3);
|
||||
v_tildemat = NaN(obj.nStates, obj.nStates);
|
||||
v_tildemat(obj.valid) = v_tilde; % log-domain scores
|
||||
heatmap(v_tildemat);
|
||||
title('Extended Path Metrics v-tilde')
|
||||
|
||||
subplot(3,2,4);
|
||||
scatter(1:symbol,pm_sto,1,'.')
|
||||
title('Path Metric Winners v')
|
||||
|
||||
subplot(3,2,5);hold on
|
||||
scatter(1:symbol,CE_symbol,1,'.');
|
||||
scatter(1:symbol,CE_smooth,1,'.')
|
||||
title('Cross Entropy')
|
||||
ylabel('Cross Entropy')
|
||||
xlabel('Symbols')
|
||||
|
||||
subplot(3,2,6); hold on
|
||||
% Left y-axis: Cross Entropy
|
||||
yyaxis left
|
||||
scatter(1:length(obj.ce), obj.ce, 10, 's', 'filled')
|
||||
ylabel('Cross Entropy')
|
||||
|
||||
% Right y-axis: BER
|
||||
yyaxis right
|
||||
scatter(1:length(obj.ber), obj.ber, 10, 'd', 'filled')
|
||||
set(gca, 'YScale', 'log')
|
||||
ylabel(berlabel)
|
||||
|
||||
xlim([1, epochs])
|
||||
xlabel('Epoch')
|
||||
title('Cross Entropy // BER')
|
||||
grid on
|
||||
|
||||
drawnow
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
methods (Access=private)
|
||||
function k = seq_key(obj, seq)
|
||||
% Build a stable key string for a sequence row vector in the *same order as combs rows* ([x_k, x_{k-1}, ...])
|
||||
% Use rounding via sprintf to avoid floating-point issues.
|
||||
% seq must be a row vector.
|
||||
k = sprintf(obj.key_fmt, seq);
|
||||
end
|
||||
end
|
||||
end
|
||||
222
projects/ML_based_MLSE/model.m
Normal file
222
projects/ML_based_MLSE/model.m
Normal file
@@ -0,0 +1,222 @@
|
||||
%%% Run parameters
|
||||
% TX
|
||||
M = 4;
|
||||
|
||||
apply_pulsef = 1;
|
||||
fdac = 256e9;
|
||||
fadc = 256e9;
|
||||
random_key = 2;
|
||||
|
||||
rcalpha = 0.05;
|
||||
kover = 8;
|
||||
vbias_rel = 0.5;
|
||||
u_pi = 3.2;
|
||||
vbias = -vbias_rel*u_pi;
|
||||
laser_wavelength = 1310;
|
||||
laser_linewidth = 1e6;
|
||||
|
||||
% Channel
|
||||
link_length = 0;
|
||||
|
||||
|
||||
doub_mode = db_mode.no_db;
|
||||
cols = linspecer(6);
|
||||
rop = [-8];
|
||||
bwl = [0.5:0.1:1.5];
|
||||
fsym = [160:16:256].*1e9;
|
||||
% nonlin_mod = [0.5:0.01:0.75];
|
||||
nonlin_mod = ones(size(fsym)).*0.5;
|
||||
|
||||
ffe_results = {};
|
||||
mlse_results_lin= {};
|
||||
|
||||
for r = 1:length(fsym)
|
||||
|
||||
Pform = Pulseformer("fsym",fsym(r),"fdac",4*fsym(r),"pulse","rc","pulselength",16,"alpha",rcalpha);
|
||||
|
||||
db_precode = 0;
|
||||
db_encode = 0;
|
||||
duob_mode = db_mode.no_db;
|
||||
apply_pulsef = 1;
|
||||
|
||||
[Digi_sig,Symbols,Tx_bits] = PAMsource(...
|
||||
"fsym",fsym(r),"M",M,"order",18,"useprbs",0,...
|
||||
"fs_out",fdac,...
|
||||
"applyclipping",0,"clipfactor",1.5,...
|
||||
"applypulseform",apply_pulsef,"pulseformer",Pform,...
|
||||
"randkey",random_key,...
|
||||
"db_precode",db_precode,"db_encode",db_encode,...
|
||||
"mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process();
|
||||
|
||||
El_sig = M8199B("kover",kover).process(Digi_sig);
|
||||
|
||||
%%%%% Electrical Driver Amplifier %%%%%%
|
||||
El_sig = El_sig.normalize("mode","oneone");
|
||||
|
||||
%%%%% MODULATE E/O CONVERSION %%%%%
|
||||
u_pi = 3.2;
|
||||
vbias = -u_pi*nonlin_mod(r);
|
||||
[Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1).process(El_sig);
|
||||
|
||||
%%%%%% Fiber %%%%%%
|
||||
Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig);
|
||||
|
||||
%%%%%% ROP %%%%%%
|
||||
Opt_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig);
|
||||
|
||||
%%%%%% PD Square Law %%%%%%
|
||||
PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11,"randomkey",random_key).process(Opt_sig);
|
||||
|
||||
%%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%%
|
||||
rx_bwl = 70e9;
|
||||
PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(PD_sig);
|
||||
|
||||
% %%%%%% Low-pass Scope %%%%%%
|
||||
Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
|
||||
|
||||
%%%%%% Scope %%%%%%
|
||||
Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,...
|
||||
"delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,...
|
||||
"samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,...
|
||||
"adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig);
|
||||
|
||||
Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym(r));
|
||||
|
||||
% 2sps
|
||||
[~, Scpe_cell, ~, found_sync] = Scpe_sig_2sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 1);
|
||||
Rx_sig_2sps = Scpe_cell{1};
|
||||
Rx_sig_2sps = Rx_sig_2sps.normalize("mode","rms");
|
||||
|
||||
% 1sps
|
||||
Scpe_sig_1sps = Scpe_sig.resample("fs_out",1*fsym(r));
|
||||
|
||||
[~, Scpe_cell_1sps, ~, found_sync] = Scpe_sig_1sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 1);
|
||||
Rx_sig_1sps = Scpe_cell_1sps{1};
|
||||
Rx_sig_1sps = Rx_sig_1sps.normalize("mode","rms");
|
||||
showLevelHistogram(Rx_sig_1sps,Symbols,"displayname",'ffe','fignum',111);
|
||||
|
||||
|
||||
%%
|
||||
|
||||
mu_lms = 0.0005;
|
||||
pf_ncoeffs = 2;
|
||||
eq_ = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",2^13,"mu_dd",mu_lms,"mu_tr",mu_lms,"order",50,"sps",2,"dd_mode",1,"adaption_technique","lms");
|
||||
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
|
||||
|
||||
% FFE
|
||||
[y_ffe, ffe_noise] = eq_.process(Rx_sig_2sps, Symbols);
|
||||
|
||||
Eq_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ffe);
|
||||
[~, errors, ber_ffe, ~] = calc_ber(Eq_bits.signal, Tx_bits.signal, "skip_front", 0, "skip_end", 0, "returnErrorLocation", 1);
|
||||
fprintf('FFE: %.2e \n',ber_ffe);
|
||||
|
||||
% Postfilter
|
||||
[y_white,whitened_noise] = pf_.process(y_ffe, ffe_noise);
|
||||
|
||||
% Sequence Est
|
||||
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',0,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0,'DIR',pf_.coefficients);
|
||||
[y_mlse] = mlse_.process(y_white,Symbols);
|
||||
mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_mlse);
|
||||
[~, errors, ber_mlse_normal, errpos] = calc_ber(mlse_bits.signal, Tx_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('MLSE: %.2e \n',ber_mlse_normal);
|
||||
|
||||
showLevelHistogram(y_ffe,Symbols,"displayname",'ffe','fignum',111);
|
||||
|
||||
bursts = count_error_bursts(errpos, 10);
|
||||
e = zeros(size(mlse_bits.signal));
|
||||
e(errpos) = 1;
|
||||
figure(8)
|
||||
stem(e)
|
||||
|
||||
%% RUN ML-Based MLSE
|
||||
|
||||
mu_lms = 0.15;
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",50,"epochs_dd",10,"len_tr",Rx_sig_2sps.length-100,...
|
||||
"mu_dd",mu_lms,"mu_tr",mu_lms,"order",15,"sps",2,...
|
||||
"traceback_depth",128,"L",3,"delta",5);
|
||||
|
||||
%%
|
||||
ml_mlse_equalizer.epochs_tr = 50;
|
||||
ml_mlse_equalizer.epochs_dd = 1;
|
||||
[y_ml_mlse,Vit_signal] = ml_mlse_equalizer.process(Rx_sig_2sps,Symbols);
|
||||
ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse);
|
||||
[~, errors, ber, errpos] = calc_ber(ml_mlse_bits.signal, Tx_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('ML MLSE BER: %.2e \n',ber);
|
||||
|
||||
bursts = count_error_bursts(errpos, 10);
|
||||
e = zeros(size(ml_mlse_bits.signal));
|
||||
e(errpos) = 1;
|
||||
figure(8)
|
||||
stem(e)
|
||||
|
||||
figure()
|
||||
plot(ml_mlse_equalizer.ber)
|
||||
beautifyBERplot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%% optimize delta
|
||||
deltas = [-1:4];
|
||||
ber = zeros(1,length(deltas));
|
||||
parfor m = 1:numel(deltas)
|
||||
|
||||
mu_lms = 0.2;
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",2,"epochs_dd",5,"len_tr",2^13,...
|
||||
"mu_dd",mu_lms,"mu_tr",mu_lms,"order",4,"sps",1,...
|
||||
"traceback_depth",128,"L",3,"delta",deltas(m));
|
||||
|
||||
[y_ml_mlse,Vit_signal] = ml_mlse_equalizer.process(y_ffe,Symbols);
|
||||
y_ml_mlse_ = y_ml_mlse;
|
||||
y_ml_mlse_.signal = circshift(y_ml_mlse.signal,0);
|
||||
mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse_);
|
||||
[~, errors, ber(m), errpos] = calc_ber(mlse_bits.signal, Tx_bits.signal, "skip_front", 0, "skip_end", 0, "returnErrorLocation", 1);
|
||||
fprintf('ML MLSE: %.2e \n',ber(m));
|
||||
end
|
||||
|
||||
|
||||
figure(7); hold on
|
||||
title('ML MLSE')
|
||||
plot(deltas,ber,'DisplayName','BER');
|
||||
yline(ber_ffe,'DisplayName','BER FFE');
|
||||
yline(ber_mlse_normal,'DisplayName','BER MLSE');
|
||||
xlabel('deltas')
|
||||
beautifyBERplot
|
||||
legend
|
||||
ylim([1e-5, 1e-1]);
|
||||
set(gca,'YScale','log');
|
||||
|
||||
|
||||
|
||||
|
||||
%% RUN Comparison
|
||||
len_tr = 4096*2;
|
||||
|
||||
mu_ffe1 = 0.0001;
|
||||
mu_ffe2 = 0.0008;
|
||||
mu_ffe3 = 0.001;
|
||||
mu_dc = 0.005;
|
||||
% mu_dc = 0;
|
||||
|
||||
mu_ffe = [mu_ffe1 mu_ffe2 mu_ffe3];
|
||||
mu_dfe = 0.0004;
|
||||
pf_ncoeffs = 1;
|
||||
ffe_order = [50, 0, 0];
|
||||
mu_lms = 0.0005;
|
||||
eq_ = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",2^13,"mu_dd",mu_lms,"mu_tr",mu_lms,"order",50,"sps",2,"dd_mode",1,"adaption_technique","lms");
|
||||
% eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",1,"DCmu",0.00,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1);
|
||||
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
|
||||
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',0,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0);
|
||||
|
||||
[ffe_results{r}, mlse_results_lin{r}] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig_2sps, Symbols, Tx_bits, ...
|
||||
"precode_mode", duob_mode,...
|
||||
'showAnalysis', 0, ...
|
||||
"postFFE", [],...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
mlse_results_lin{r}.metrics.print;
|
||||
ffe_results{r}.metrics.print;
|
||||
|
||||
end
|
||||
116
projects/ML_based_MLSE/rate_evaluation.m
Normal file
116
projects/ML_based_MLSE/rate_evaluation.m
Normal file
@@ -0,0 +1,116 @@
|
||||
|
||||
ber_ffe = [];
|
||||
ber_mlse = [];
|
||||
ber_dbtgt = [];
|
||||
ber_ml = [];
|
||||
|
||||
mlse = 1;
|
||||
dbtgt = 0;
|
||||
duob_mode = db_mode.no_db;
|
||||
baudrates = [136:8:224].*1e9;
|
||||
for i = 1:length(baudrates)
|
||||
|
||||
rop = -8;
|
||||
M = 4;
|
||||
[Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1] = standard_link_model("M",M,"fsym",baudrates(i),"rop",rop,"laser_linewidth",1310,"link_length_m",0,"random_key",1,"apply_pulsef",1);
|
||||
% [Rx_sig_2sps_v2, Symbols_v2, Tx_bits_v2] = standard_link_model("M",M,"fsym",200e9,"rop",rop,"laser_linewidth",1300,"link_length_m",0,"random_key",2);
|
||||
% [Rx_sig_2sps_v3, Symbols_v3, Tx_bits_v3] = standard_link_model("M",M,"fsym",200e9,"rop",rop,"laser_linewidth",1300,"link_length_m",0,"random_key",3);
|
||||
|
||||
%% FFE + MLSE
|
||||
if mlse
|
||||
pf_ncoeffs = 1;
|
||||
ffe_order = [50, 0, 0];
|
||||
mu_ffe = [0.0001, 0.0008, 0.001];
|
||||
mu_dfe = 0.0004;
|
||||
eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1);
|
||||
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
|
||||
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0,'DIR',pf_.coefficients);
|
||||
|
||||
[ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ...
|
||||
"precode_mode", duob_mode,...
|
||||
'showAnalysis', 0, ...
|
||||
"postFFE", [],...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
ber_ffe(i) = ffe_results.metrics.BER;
|
||||
ber_mlse(i) = mlse_results.metrics.BER;
|
||||
|
||||
fprintf('BER FFE: %.2e \n',ber_ffe(i));
|
||||
fprintf('BER MLSE: %.2e \n',ber_mlse(i));
|
||||
end
|
||||
|
||||
|
||||
%% FFE DB tgt. + MLSE
|
||||
if dbtgt
|
||||
mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',0,'trellis_state_mode',3);
|
||||
ffe_order = [50, 0, 0];
|
||||
mu_ffe = [0.0001, 0.0008, 0.001];
|
||||
mu_dfe = 0.0004;
|
||||
eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1);
|
||||
|
||||
dbt_results = duobinary_target(eq_, mlse_db_, M, Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ...
|
||||
"precode_mode", duob_mode, ...
|
||||
'showAnalysis', 0,...
|
||||
"postFFE", []);
|
||||
|
||||
ber_dbtgt(i) = dbt_results.metrics.BER;
|
||||
end
|
||||
|
||||
|
||||
%%
|
||||
mu_lms = 0.0005;
|
||||
pf_ncoeffs = 2;
|
||||
eq_ = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",2^13,"mu_dd",mu_lms,"mu_tr",mu_lms,"order",50,"sps",2,"dd_mode",1,"adaption_technique","lms");
|
||||
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
|
||||
|
||||
% FFE
|
||||
[y_ffe, ffe_noise] = eq_.process(Rx_sig_2sps_v1, Symbols_v1);
|
||||
|
||||
|
||||
% Postfilter
|
||||
[y_white,whitened_noise] = pf_.process(y_ffe, ffe_noise);
|
||||
|
||||
%% RUN ML-Based MLSE
|
||||
|
||||
mu_lms = 0.15;
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",50,"epochs_dd",1,"len_tr",2^16,...
|
||||
"mu_dd",mu_lms,"mu_tr",mu_lms,"order",4,"sps",2,...
|
||||
"traceback_depth",128,"L",2,"delta",0);
|
||||
|
||||
ml_mlse_equalizer.mu_tr = 0.005;
|
||||
ml_mlse_equalizer.epochs_tr = 2;
|
||||
ml_mlse_equalizer.epochs_dd = 1;
|
||||
[y_ml_mlse,~] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1);
|
||||
ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse);
|
||||
[~, errors, ber_ml(i), errpos] = calc_ber(ml_mlse_bits.signal, Tx_bits_v1.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('ML MLSE BER: %.2e \n',ber_ml(i));
|
||||
|
||||
% figure(11);hold on
|
||||
% plot(1:numel(ml_mlse_equalizer.ber),ml_mlse_equalizer.ber);
|
||||
% beautifyBERplot;
|
||||
% xlim([1,numel(ml_mlse_equalizer.ber)])
|
||||
|
||||
end
|
||||
|
||||
%%
|
||||
|
||||
figure(6); hold on;
|
||||
if mlse
|
||||
plot(baudrates,ber_ffe,'DisplayName','FFE');
|
||||
plot(baudrates,ber_mlse,'DisplayName','MLSE');
|
||||
end
|
||||
if dbtgt
|
||||
plot(baudrates,ber_dbtgt,'DisplayName','DB tgt');
|
||||
end
|
||||
plot(baudrates,ber_ml,'DisplayName','ML-MLSE');
|
||||
beautifyBERplot;
|
||||
legend
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
30
projects/ML_based_MLSE/read_csv.m
Normal file
30
projects/ML_based_MLSE/read_csv.m
Normal file
@@ -0,0 +1,30 @@
|
||||
%% read_wpd_csv.m
|
||||
% Minimal importer for WebPlotDigitizer multi-curve CSV
|
||||
|
||||
filename = 'wpd_datasets.csv'; % <-- set your file path here
|
||||
T = readtable(filename);
|
||||
|
||||
% Read header row manually
|
||||
fid = fopen(filename);
|
||||
hdr1 = strsplit(strrep(fgetl(fid), '"', ''), ','); % curve names
|
||||
hdr2 = strsplit(strrep(fgetl(fid), '"', ''), ','); % X/Y header row
|
||||
fclose(fid);
|
||||
|
||||
% Extract unique curve names
|
||||
names = hdr1(~cellfun('isempty',hdr1));
|
||||
|
||||
% Create struct for each curve
|
||||
mii = struct();
|
||||
for i = 1:numel(names)
|
||||
base = matlab.lang.makeValidName(strrep(names{i},' ','_'));
|
||||
xi = 2*(i-1)+1; % X column
|
||||
yi = xi+1; % Y column
|
||||
mii.(base).X = T{:,xi};
|
||||
mii.(base).Y = T{:,yi};
|
||||
|
||||
% also create workspace variable "name_wpd"
|
||||
assignin('base',[base '_wpd'], mii.(base));
|
||||
end
|
||||
|
||||
disp('Imported datasets:');
|
||||
disp(fieldnames(mii));
|
||||
139
projects/ML_based_MLSE/rop_evaluation.m
Normal file
139
projects/ML_based_MLSE/rop_evaluation.m
Normal file
@@ -0,0 +1,139 @@
|
||||
clear; clc;
|
||||
|
||||
M = 4;
|
||||
randkey = 1;
|
||||
duob_mode = db_mode.no_db;
|
||||
|
||||
mlse = 1;
|
||||
dbtgt = 1;
|
||||
|
||||
baudrates = 180e9:2e9:220e9; % outer loop
|
||||
rops = linspace(-10,0,12); % inner sweep
|
||||
FEC_thr = 3.8e-3; % BER target
|
||||
|
||||
% --- allocate results
|
||||
reqROP_FFE = nan(size(baudrates));
|
||||
reqROP_MLSE = nan(size(baudrates));
|
||||
reqROP_DBTGT = nan(size(baudrates));
|
||||
reqROP_ML_MLSE2 = nan(size(baudrates));
|
||||
reqROP_ML_MLSE3 = nan(size(baudrates));
|
||||
|
||||
%% ====================== OUTER LOOP ======================
|
||||
for b = 1:numel(baudrates)
|
||||
baudrate = baudrates(b);
|
||||
fprintf('\n=== %.0f GBd ===\n', baudrate/1e9);
|
||||
|
||||
ber_ffe = nan(size(rops));
|
||||
ber_mlse = nan(size(rops));
|
||||
ber_dbtgt = nan(size(rops));
|
||||
ber_ml2 = nan(size(rops));
|
||||
ber_ml3 = nan(size(rops));
|
||||
|
||||
%% -------- inner ROP loop --------
|
||||
for i = 1:length(rops)
|
||||
rop = rops(i);
|
||||
|
||||
[Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1] = standard_link_model( ...
|
||||
"M",M,"fsym",baudrate,"rop",rop,"laser_linewidth",1310, ...
|
||||
"link_length_m",0,"random_key",1);
|
||||
|
||||
|
||||
|
||||
%% FFE + MLSE
|
||||
if mlse
|
||||
pf_ncoeffs = 1;
|
||||
ffe_order = [50, 0, 0];
|
||||
mu_ffe = [0.0001, 0.0008, 0.001];
|
||||
mu_dfe = 0.0004;
|
||||
eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13, ...
|
||||
"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005, ...
|
||||
"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0, ...
|
||||
"plotfinal",0,"ideal_dfe",1);
|
||||
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
|
||||
mlse_ = MLSE("duobinary_output",0,'M',M, ...
|
||||
'trellis_states',PAMmapper(M,0).levels,'scale_mode',2, ...
|
||||
'trellis_exclusion',0,'trellis_state_mode',2,'debug',0, ...
|
||||
'DIR',pf_.coefficients);
|
||||
|
||||
[ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, ...
|
||||
Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ...
|
||||
"precode_mode", duob_mode,'showAnalysis', 0, "postFFE", [], ...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
ber_ffe(i) = ffe_results.metrics.BER;
|
||||
ber_mlse(i) = mlse_results.metrics.BER;
|
||||
end
|
||||
|
||||
%% FFE + duobinary target MLSE
|
||||
if dbtgt
|
||||
mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M, ...
|
||||
"trellis_states",PAMmapper(M,0).levels,'scale_mode',2, ...
|
||||
'trellis_exclusion',0,'trellis_state_mode',3);
|
||||
ffe_order = [50, 0, 0];
|
||||
mu_ffe = [0.0001, 0.0008, 0.001];
|
||||
mu_dfe = 0.0004;
|
||||
eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13, ...
|
||||
"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005, ...
|
||||
"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0, ...
|
||||
"plotfinal",0,"ideal_dfe",1);
|
||||
|
||||
dbt_results = duobinary_target(eq_, mlse_db_, M, Rx_sig_2sps_v1, ...
|
||||
Symbols_v1, Tx_bits_v1, "precode_mode", duob_mode, ...
|
||||
'showAnalysis', 0, "postFFE", []);
|
||||
ber_dbtgt(i) = dbt_results.metrics.BER;
|
||||
end
|
||||
|
||||
%% ML-based MLSE (L=2)
|
||||
mu_ml = 0.1; training_epochs = 100;
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ...
|
||||
"len_tr",2^16,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ...
|
||||
"traceback_depth",128,"L",2,"delta",4,"adaptive_mu",0);
|
||||
[y_ml_mlse,y_ref] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1);
|
||||
ref_bits = PAMmapper(M,0).demap(y_ref);
|
||||
ml_bits = PAMmapper(M,0).demap(y_ml_mlse);
|
||||
[~,~,ber_ml2(i)] = calc_ber(ml_bits.signal, ref_bits.signal, ...
|
||||
"skip_front",10,"skip_end",10);
|
||||
|
||||
%% ML-based MLSE (L=3)
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ...
|
||||
"len_tr",2^16,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ...
|
||||
"traceback_depth",128,"L",3,"delta",4,"adaptive_mu",0);
|
||||
[y_ml_mlse,y_ref] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1);
|
||||
ref_bits = PAMmapper(M,0).demap(y_ref);
|
||||
ml_bits = PAMmapper(M,0).demap(y_ml_mlse);
|
||||
[~,~,ber_ml3(i)] = calc_ber(ml_bits.signal, ref_bits.signal, ...
|
||||
"skip_front",10,"skip_end",10);
|
||||
end % ROP loop
|
||||
|
||||
%% --- find required ROP (FEC crossing)
|
||||
reqROP_FFE(b) = interp_fec_cross(rops, ber_ffe, FEC_thr);
|
||||
reqROP_MLSE(b) = interp_fec_cross(rops, ber_mlse, FEC_thr);
|
||||
reqROP_DBTGT(b) = interp_fec_cross(rops, ber_dbtgt, FEC_thr);
|
||||
reqROP_ML_MLSE2(b) = interp_fec_cross(rops, ber_ml2, FEC_thr);
|
||||
reqROP_ML_MLSE3(b) = interp_fec_cross(rops, ber_ml3, FEC_thr);
|
||||
|
||||
% --- diagnostic
|
||||
fprintf('Baud %.0f GBd: FFE %.1f, MLSE %.1f, DB %.1f, ML2 %.1f, ML3 %.1f\n', ...
|
||||
baudrate/1e9, reqROP_FFE(b), reqROP_MLSE(b), reqROP_DBTGT(b), ...
|
||||
reqROP_ML_MLSE2(b), reqROP_ML_MLSE3(b));
|
||||
end
|
||||
|
||||
%% ====================== PLOT REQUIRED ROP ======================
|
||||
cols = cbrewer2('Set1',8);
|
||||
colFFE = cols(1,:);
|
||||
colMLSE = cols(2,:);
|
||||
colDBTGT = cols(4,:);
|
||||
colML_MLSE = cols(3,:);
|
||||
|
||||
figure(); hold on
|
||||
plot(baudrates/1e9, reqROP_FFE, '-o','Color',colFFE, 'DisplayName','FFE');
|
||||
plot(baudrates/1e9, reqROP_MLSE, '-s','Color',colMLSE, 'DisplayName','FFE+PF+MLSE');
|
||||
plot(baudrates/1e9, reqROP_DBTGT, '--^','Color',colDBTGT, 'DisplayName','DB tgt. MLSE');
|
||||
plot(baudrates/1e9, reqROP_ML_MLSE2, '-v','Color',colML_MLSE, 'DisplayName','ML-based MLSE (L=2)');
|
||||
plot(baudrates/1e9, reqROP_ML_MLSE3, '-d','Color',colML_MLSE*0.8,'DisplayName','ML-based MLSE (L=3)');
|
||||
|
||||
xlabel('Baud rate [GBd]');
|
||||
ylabel('Required ROP [dBm]');
|
||||
title('ROP required for FEC threshold');
|
||||
grid on; legend('Location','northwest');
|
||||
beautifyBERplot("logscale",0,"polyfit",1,"polyorder",4,"fitmethod",'polyfit');
|
||||
140
projects/ML_based_MLSE/rrop_vs_length_evaluation.m
Normal file
140
projects/ML_based_MLSE/rrop_vs_length_evaluation.m
Normal file
@@ -0,0 +1,140 @@
|
||||
clear; clc;
|
||||
|
||||
M = 4;
|
||||
randkey = 1;
|
||||
duob_mode = db_mode.no_db;
|
||||
|
||||
mlse = 1;
|
||||
dbtgt = 1;
|
||||
|
||||
link_lengths = 0:1:8; % [m] --- outer loop
|
||||
rops = linspace(-10, 0, 12); % [dBm] --- inner sweep
|
||||
FEC_thr = 3.8e-3; % BER target
|
||||
baudrate = 200e9;
|
||||
|
||||
% --- allocate results
|
||||
reqROP_FFE = nan(size(link_lengths));
|
||||
reqROP_MLSE = nan(size(link_lengths));
|
||||
reqROP_DBTGT = nan(size(link_lengths));
|
||||
reqROP_ML_MLSE2 = nan(size(link_lengths));
|
||||
reqROP_ML_MLSE3 = nan(size(link_lengths));
|
||||
|
||||
%% ====================== OUTER LOOP ======================
|
||||
for L = 1:numel(link_lengths)
|
||||
link_length_m = link_lengths(L);
|
||||
fprintf('\n=== %.0f m fiber length ===\n', link_length_m);
|
||||
|
||||
ber_ffe = nan(size(rops));
|
||||
ber_mlse = nan(size(rops));
|
||||
ber_dbtgt = nan(size(rops));
|
||||
ber_ml2 = nan(size(rops));
|
||||
ber_ml3 = nan(size(rops));
|
||||
|
||||
%% -------- inner ROP loop --------
|
||||
parfor i = 1:length(rops)
|
||||
rop = rops(i);
|
||||
|
||||
[Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1] = standard_link_model( ...
|
||||
"M",M,"fsym",baudrate,"rop",rop,"laser_wavelength",1290, ...
|
||||
"link_length_km",link_length_m,"random_key",1);
|
||||
|
||||
Rx_sig_2sps_v1.spectrum("displayname",'Rx Sig','normalizeTo0dB',1);
|
||||
|
||||
%% FFE + MLSE
|
||||
if mlse
|
||||
pf_ncoeffs = 1;
|
||||
ffe_order = [50, 0, 0];
|
||||
mu_ffe = [0.0001, 0.0008, 0.001];
|
||||
mu_dfe = 0.0004;
|
||||
eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13, ...
|
||||
"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005, ...
|
||||
"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0, ...
|
||||
"plotfinal",0,"ideal_dfe",1);
|
||||
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
|
||||
mlse_ = MLSE("duobinary_output",0,'M',M, ...
|
||||
'trellis_states',PAMmapper(M,0).levels,'scale_mode',2, ...
|
||||
'trellis_exclusion',0,'trellis_state_mode',2,'debug',0, ...
|
||||
'DIR',pf_.coefficients);
|
||||
|
||||
[ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, ...
|
||||
Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ...
|
||||
"precode_mode", duob_mode,'showAnalysis', 0, "postFFE", [], ...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
ber_ffe(i) = ffe_results.metrics.BER;
|
||||
ber_mlse(i) = mlse_results.metrics.BER;
|
||||
end
|
||||
|
||||
%% FFE + duobinary target MLSE
|
||||
if dbtgt
|
||||
mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M, ...
|
||||
"trellis_states",PAMmapper(M,0).levels,'scale_mode',2, ...
|
||||
'trellis_exclusion',0,'trellis_state_mode',3);
|
||||
ffe_order = [50, 0, 0];
|
||||
mu_ffe = [0.0001, 0.0008, 0.001];
|
||||
mu_dfe = 0.0004;
|
||||
eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13, ...
|
||||
"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005, ...
|
||||
"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0, ...
|
||||
"plotfinal",0,"ideal_dfe",1);
|
||||
|
||||
dbt_results = duobinary_target(eq_, mlse_db_, M, Rx_sig_2sps_v1, ...
|
||||
Symbols_v1, Tx_bits_v1, "precode_mode", duob_mode, ...
|
||||
'showAnalysis', 0, "postFFE", []);
|
||||
ber_dbtgt(i) = dbt_results.metrics.BER;
|
||||
end
|
||||
|
||||
%% ML-based MLSE (L=2)
|
||||
mu_ml = 0.1; training_epochs = 100;
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ...
|
||||
"len_tr",2^16,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ...
|
||||
"traceback_depth",128,"L",2,"delta",4,"adaptive_mu",0);
|
||||
[y_ml_mlse,y_ref] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1);
|
||||
ref_bits = PAMmapper(M,0).demap(y_ref);
|
||||
ml_bits = PAMmapper(M,0).demap(y_ml_mlse);
|
||||
[~,~,ber_ml2(i)] = calc_ber(ml_bits.signal, ref_bits.signal, ...
|
||||
"skip_front",10,"skip_end",10);
|
||||
|
||||
%% ML-based MLSE (L=3)
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ...
|
||||
"len_tr",2^16,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ...
|
||||
"traceback_depth",128,"L",3,"delta",4,"adaptive_mu",0);
|
||||
[y_ml_mlse,y_ref] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1);
|
||||
ref_bits = PAMmapper(M,0).demap(y_ref);
|
||||
ml_bits = PAMmapper(M,0).demap(y_ml_mlse);
|
||||
[~,~,ber_ml3(i)] = calc_ber(ml_bits.signal, ref_bits.signal, ...
|
||||
"skip_front",10,"skip_end",10);
|
||||
end % ROP loop
|
||||
|
||||
%% --- find required ROP (FEC crossing)
|
||||
reqROP_FFE(L) = interp_fec_cross(rops, ber_ffe, FEC_thr);
|
||||
reqROP_MLSE(L) = interp_fec_cross(rops, ber_mlse, FEC_thr);
|
||||
reqROP_DBTGT(L) = interp_fec_cross(rops, ber_dbtgt, FEC_thr);
|
||||
reqROP_ML_MLSE2(L) = interp_fec_cross(rops, ber_ml2, FEC_thr);
|
||||
reqROP_ML_MLSE3(L) = interp_fec_cross(rops, ber_ml3, FEC_thr);
|
||||
|
||||
fprintf('Length %.0f m: FFE %.1f, MLSE %.1f, DB %.1f, ML2 %.1f, ML3 %.1f\n', ...
|
||||
link_length_m, reqROP_FFE(L), reqROP_MLSE(L), reqROP_DBTGT(L), ...
|
||||
reqROP_ML_MLSE2(L), reqROP_ML_MLSE3(L));
|
||||
end
|
||||
|
||||
%% ====================== PLOT REQUIRED ROP ======================
|
||||
cols = cbrewer2('Set1',8);
|
||||
colFFE = cols(1,:);
|
||||
colMLSE = cols(2,:);
|
||||
colDBTGT = cols(4,:);
|
||||
colML_MLSE = cols(3,:);
|
||||
|
||||
figure(); hold on
|
||||
plot(link_lengths, reqROP_FFE, '-o','Color',colFFE, 'DisplayName','FFE');
|
||||
plot(link_lengths, reqROP_MLSE, '-s','Color',colMLSE, 'DisplayName','FFE+PF+MLSE');
|
||||
plot(link_lengths, reqROP_DBTGT, '--^','Color',colDBTGT, 'DisplayName','DB tgt. MLSE');
|
||||
plot(link_lengths, reqROP_ML_MLSE2, '-v','Color',colML_MLSE, 'DisplayName','ML-based MLSE (L=2)');
|
||||
plot(link_lengths, reqROP_ML_MLSE3, '-d','Color',colML_MLSE*0.8,'DisplayName','ML-based MLSE (L=3)');
|
||||
|
||||
xlabel('Link length [km]');
|
||||
ylabel('Required ROP [dBm]');
|
||||
title(sprintf('Required ROP the reach FEC threshold (3.8e-3); %.0f GBd PAM-%d', baudrate.*1e-9, M));
|
||||
legend('Location','northwest');
|
||||
grid on;
|
||||
beautifyBERplot("logscale",0,"polyfit",1,"polyorder",3,"fitmethod",'smoothingspline');
|
||||
99
projects/ML_based_MLSE/standard_link_model.m
Normal file
99
projects/ML_based_MLSE/standard_link_model.m
Normal file
@@ -0,0 +1,99 @@
|
||||
function [Rx_sig_2sps,Symbols,Tx_bits] = standard_link_model(options)
|
||||
|
||||
% STANDARD_LINK_MODEL Basic IM/DD link simulation
|
||||
% Rx_sig_2sps = standard_link_model(...optional args...)
|
||||
%
|
||||
% All arguments are optional and default to standard parameters
|
||||
% if not provided.
|
||||
|
||||
arguments
|
||||
|
||||
% --- Transmitter settings ---
|
||||
options.M (1,1) double = 4
|
||||
options.apply_pulsef (1,1) logical = true
|
||||
options.fdac (1,1) double = 256e9
|
||||
options.fadc (1,1) double = 256e9
|
||||
options.random_key (1,1) double = 2
|
||||
options.rcalpha (1,1) double = 0.05
|
||||
options.kover (1,1) double = 8
|
||||
options.vbias_rel (1,1) double = 0.5
|
||||
options.u_pi (1,1) double = 3.2
|
||||
options.laser_wavelength (1,1) double = 1310
|
||||
options.laser_linewidth (1,1) double = 1e6
|
||||
|
||||
% --- Channel parameters ---
|
||||
options.link_length_km (1,1) double = 0
|
||||
options.rop (1,:) double = -5
|
||||
options.fsym (1,:) double = (212:16:256)*1e9
|
||||
options.doub_mode (1,1) db_mode = db_mode.no_db
|
||||
|
||||
% --- Debug ---
|
||||
options.debug (1,1) logical = false
|
||||
|
||||
end
|
||||
|
||||
% --- Pulse former ---
|
||||
Pform = Pulseformer("fsym",options.fsym,"fdac",4*options.fsym, ...
|
||||
"pulse","rc","pulselength",16,"alpha",options.rcalpha);
|
||||
|
||||
% --- Transmitter source ---
|
||||
[Digi_sig,Symbols,Tx_bits] = PAMsource( ...
|
||||
"fsym",options.fsym,"M",options.M,"order",18,"useprbs",0, ...
|
||||
"fs_out",options.fdac,"applyclipping",0,"clipfactor",1.5, ...
|
||||
"applypulseform",options.apply_pulsef,"pulseformer",Pform, ...
|
||||
"randkey",options.random_key,"db_precode",0,"db_encode",0, ...
|
||||
"mrds_code",0,"mrds_blocklength",512, ...
|
||||
"duobinary_mode",options.doub_mode).process();
|
||||
|
||||
% --- AWG driver ---
|
||||
El_sig = M8199B("kover",options.kover).process(Digi_sig);
|
||||
El_sig = El_sig.normalize("mode","oneone");
|
||||
|
||||
% --- E/O Modulation ---
|
||||
vbias = -options.vbias_rel*options.u_pi;
|
||||
Opt_sig = EML("mode",eml_mode.im_cosinus,"power",3, ...
|
||||
"fsimu",El_sig.fs,"lambda",options.laser_wavelength, ...
|
||||
"bias",vbias,"u_pi",options.u_pi,"linewidth",options.laser_linewidth, ...
|
||||
"randomkey",options.random_key+1).process(El_sig);
|
||||
|
||||
% --- Fiber ---
|
||||
Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",options.link_length_km, ...
|
||||
"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig);
|
||||
|
||||
% --- Amplifier (ROP set) ---
|
||||
Opt_sig = Amplifier("amp_mode","ideal_no_noise", ...
|
||||
"gain_mode","output_power","amplification_db",options.rop).process(Opt_sig);
|
||||
|
||||
% --- Photodiode ---
|
||||
PD_sig = Photodiode("fsimu",options.fdac*options.kover,"dark_current",2e-8, ...
|
||||
"responsivity",1,"temperature",20,"nep",1.8e-11, ...
|
||||
"randomkey",options.random_key).process(Opt_sig);
|
||||
|
||||
% --- Electrical LPF (receiver frontend) ---
|
||||
rx_bwl = 70e9;
|
||||
PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl, ...
|
||||
"fs",options.fdac*options.kover,"filterType",filtertypes.butterworth, ...
|
||||
"active",true).process(PD_sig);
|
||||
|
||||
% --- Scope low-pass and sampling ---
|
||||
Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",options.fadc, ...
|
||||
"filterType",filtertypes.butterworth,"active",true);
|
||||
|
||||
Scpe_sig = Scope("fsimu",options.fdac*options.kover,"fadc",options.fadc, ...
|
||||
"delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth, ...
|
||||
"samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0, ...
|
||||
"samp_jitter",0,"adcresolution",8,"quantbuffer",0.1, ...
|
||||
'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig);
|
||||
|
||||
% --- Downsample to 2 sps ---
|
||||
Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*options.fsym);
|
||||
[~,Scpe_cell,~,found_sync] = Scpe_sig_2sps.tsynch( ...
|
||||
"reference",Symbols,"fs_ref",options.fsym,"debug_plots",0);
|
||||
|
||||
try
|
||||
Rx_sig_2sps = Scpe_cell{1}.normalize("mode","rms");
|
||||
catch
|
||||
Rx_sig_2sps = Scpe_sig_2sps.normalize("mode","rms");
|
||||
end
|
||||
|
||||
end
|
||||
157
projects/ML_based_MLSE/theoretic_channel_evaluation.m
Normal file
157
projects/ML_based_MLSE/theoretic_channel_evaluation.m
Normal file
@@ -0,0 +1,157 @@
|
||||
|
||||
M = 4;
|
||||
order = 18;
|
||||
randkey = 1;
|
||||
|
||||
bitpattern = [];
|
||||
s = RandStream('twister','Seed',randkey);
|
||||
for i = 1:log2(M)
|
||||
N = 2^(order-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.fs = 200e9;
|
||||
|
||||
Bits_ = PAMmapper(M, 0, "eth_style", 0).demap(Symbols);
|
||||
|
||||
% --- Channel: minimal ISI response + AWGN ---
|
||||
h = [0.3 0.9 0.3,0.1]; % impulse response (normalized later if desired)
|
||||
h = h / norm(h); % optional normalization for unit energy
|
||||
|
||||
symbols_filt = Symbols.filter(h,1);
|
||||
|
||||
|
||||
%% SHOW FIG 3 in Paper: "ML Base Pre-Eq"
|
||||
|
||||
SNR_dB = [20:1:25];
|
||||
SNR_db = linspace(12,25,12);
|
||||
|
||||
ber_ffe = zeros(size(SNR_dB));
|
||||
ber_mlse_l5 = zeros(size(SNR_dB));
|
||||
ber_nwf_mlse_l2 = zeros(size(SNR_dB));
|
||||
ber_ml_mlse_l2 = zeros(size(SNR_dB));
|
||||
ber_ml_mlse_l3 = zeros(size(SNR_dB));
|
||||
ber_ml_mlse_l4 = zeros(size(SNR_dB));
|
||||
|
||||
epochs_training = 100;
|
||||
|
||||
for i = 1:numel(SNR_dB)
|
||||
|
||||
symbols_noi = symbols_filt;
|
||||
symbols_noi.signal = awgn(symbols_filt.signal, SNR_dB(i), 'measured'); % AWGN with given SNR
|
||||
|
||||
% Sequence Est L=5
|
||||
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',0,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0,'DIR',h);
|
||||
mlse_.DIR = h;
|
||||
[y_mlse] = mlse_.process(symbols_noi,Symbols);
|
||||
mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_mlse);
|
||||
[~, ~, ber_mlse_l5(i), ~] = calc_ber(mlse_bits.signal, Bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('MLSE L5: %.2e \n',ber_mlse_l5(i));
|
||||
|
||||
% 2nd Approach
|
||||
mu_lms = 0.0005;
|
||||
pf_ncoeffs = 1;
|
||||
eq_ = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",2^13,"mu_dd",mu_lms,"mu_tr",mu_lms,"order",16,"sps",1,"dd_mode",1,"adaption_technique","lms");
|
||||
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
|
||||
|
||||
% FFE
|
||||
[y_ffe, ffe_noise] = eq_.process(symbols_noi, Symbols);
|
||||
|
||||
Eq_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ffe);
|
||||
[~, ~, ber_ffe(i), ~] = calc_ber(Eq_bits.signal, Bits.signal, "skip_front", 0, "skip_end", 0, "returnErrorLocation", 1);
|
||||
fprintf('FFE: %.2e \n',ber_ffe(i));
|
||||
|
||||
% Postfilter
|
||||
[y_white,~] = pf_.process(y_ffe, ffe_noise);
|
||||
|
||||
% Sequence Est
|
||||
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',0,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0,'DIR',pf_.coefficients);
|
||||
[y_mlse] = mlse_.process(y_white,Symbols);
|
||||
mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_mlse);
|
||||
[~, errors, ber_nwf_mlse_l2(i), errpos] = calc_ber(mlse_bits.signal, Bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('MLSE: %.2e \n',ber_nwf_mlse_l2(i));
|
||||
|
||||
% ML-base MLSE L=2
|
||||
adaptive_mu = 0;
|
||||
mu_lms = 0.15;
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",epochs_training,"epochs_dd",1,"len_tr",2^15,...
|
||||
"mu_dd",mu_lms,"mu_tr",mu_lms,"order",11,"sps",1,...
|
||||
"traceback_depth",128,"L",2,"delta",4,"adaptive_mu",adaptive_mu);
|
||||
[y_ml_mlse,~] = ml_mlse_equalizer.process(symbols_noi,Symbols);
|
||||
ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse);
|
||||
[~, errors, ber_ml_mlse_l2(i), errpos] = calc_ber(ml_mlse_bits.signal, Bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse_l2(i));
|
||||
|
||||
% ML-base MLSE L=3
|
||||
mu_lms = 0.15;
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",epochs_training,"epochs_dd",1,"len_tr",2^16,...
|
||||
"mu_dd",mu_lms,"mu_tr",mu_lms,"order",11,"sps",1,...
|
||||
"traceback_depth",128,"L",3,"delta",4,"adaptive_mu",adaptive_mu);
|
||||
[y_ml_mlse,~] = ml_mlse_equalizer.process(symbols_noi,Symbols);
|
||||
ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse);
|
||||
[~, errors, ber_ml_mlse_l3(i), errpos] = calc_ber(ml_mlse_bits.signal, Bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse_l3(i));
|
||||
|
||||
% % ML-base MLSE L=5
|
||||
% mu_lms = 0.15;
|
||||
% ml_mlse_equalizer = ML_MLSE("epochs_tr",epochs_training,"epochs_dd",1,"len_tr",2^15,...
|
||||
% "mu_dd",mu_lms,"mu_tr",mu_lms,"order",11,"sps",1,...
|
||||
% "traceback_depth",128,"L",5,"delta",4);
|
||||
% [y_ml_mlse,~] = ml_mlse_equalizer.process(symbols_noi,Symbols);
|
||||
% ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse);
|
||||
% [~, errors, ber_ml_mlse_l5(i), errpos] = calc_ber(ml_mlse_bits.signal, Bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
% fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse_l5(i));
|
||||
|
||||
|
||||
end
|
||||
|
||||
%%
|
||||
figure(); hold on;
|
||||
|
||||
% --- define scheme colors (consistent palette)
|
||||
cols = cbrewer2('SET1',8);
|
||||
colFFE = cols(1,:); % blue
|
||||
colMLSE = cols(2,:); % orange
|
||||
colML_MLSE = cols(3,:); % green
|
||||
colNWF_MLSE = cols(4,:); % purple
|
||||
|
||||
% --- local simulation results
|
||||
plot(SNR_dB, ber_ffe, '-o', 'Color', colFFE, 'DisplayName','FFE (N=16)');
|
||||
if M==2, plot(FFE_wpd.X, FFE_wpd.Y, ':', 'LineWidth',1.5, 'Color', colFFE, 'DisplayName','Paper FFE'); end
|
||||
|
||||
|
||||
plot(SNR_dB, ber_mlse_l5, '-s', 'Color', colMLSE, 'DisplayName','MLSE (L=5)');
|
||||
if M==2, plot(MLSE_wpd.X, MLSE_wpd.Y, ':', 'LineWidth',1.5, 'Color', colMLSE, 'DisplayName','Paper MLSE L=5'); end
|
||||
|
||||
plot(SNR_dB, ber_nwf_mlse_l2,'--^','Color', colNWF_MLSE, 'DisplayName','FFE+PF+MLSE (L=2)');
|
||||
plot(SNR_dB, ber_ml_mlse_l2, '-v', 'Color', colML_MLSE, 'DisplayName','ML-based MLSE (L=2)');
|
||||
if M==2, plot(ML_MLSE_L_2_wpd.X,ML_MLSE_L_2_wpd.Y,':', 'LineWidth',1.5, 'Color', colML_MLSE, 'DisplayName','Paper ML-based MLSE L=2'); end
|
||||
|
||||
|
||||
plot(SNR_dB, ber_ml_mlse_l3, '-d', 'Color', colML_MLSE, 'DisplayName','ML-based MLSE (L=3)');
|
||||
|
||||
if M==2, plot(SNR_dB, ber_ml_mlse_l5, '-p', 'Color', colML_MLSE, 'DisplayName','ML-based MLSE (L=5)'); end
|
||||
if M==2, plot(ML_MLSE_L_5_wpd.X,ML_MLSE_L_5_wpd.Y,':', 'LineWidth',1.5, 'Color', colML_MLSE, 'DisplayName','Paper ML-based MLSE L=5'); end
|
||||
% --- imported WebPlotDigitizer data (dotted)
|
||||
|
||||
yline(3.8e-3,'HandleVisibility','off');
|
||||
yline(2.2e-4,'HandleVisibility','off');
|
||||
|
||||
% --- formatting
|
||||
beautifyBERplot;
|
||||
xlim([SNR_dB(1), SNR_dB(end)]);
|
||||
ylim([1e-5 0.1]);
|
||||
xlabel('Input SNR [dB]');
|
||||
ylabel('Bit Error Rate (BER)');
|
||||
title('PAM-4; M=4; AWGN Channel');
|
||||
legend('Location','southwest');
|
||||
grid on;
|
||||
|
||||
9
projects/ML_based_MLSE/wpd_datasets.csv
Normal file
9
projects/ML_based_MLSE/wpd_datasets.csv
Normal file
@@ -0,0 +1,9 @@
|
||||
FFE,,MLSE,,ML MLSE L=2,,ML MLSE L=5,
|
||||
X,Y,X,Y,X,Y,X,Y
|
||||
7.988476019722099,0.038632829886662855,7.9828552218736,0.02056096426419196,7.988825638727029,0.026145160025945406,7.982882115643211,0.01995262314968881
|
||||
8.984755714926044,0.02462092401494627,8.991855670103094,0.008868008219069292,8.991519497982967,0.012908315306800594,8.998027790228598,0.009002182769536628
|
||||
9.993487225459436,0.014339094903163154,9.988632900044824,0.0032424222072799827,9.994320932317347,0.005651632488900345,9.994751232631108,0.0034952501326754757
|
||||
10.989914836396235,0.007746944324122318,10.991730165844913,0.001020224273461357,10.997256835499776,0.002129417748571358,10.991689825190498,0.0010672369906432938
|
||||
12.005060510981624,0.0034952501326754757,12.001483639623487,0.00018978455660928717,11.994316450022412,0.0005679993334792185,12.007359928283282,0.0002680778116002006
|
||||
12.983254146122816,0.0013169376605490738,13.011492604213357,0.000026540740973404924,12.997722994173017,0.00012652429120320801,12.992384580905423,0.000049125201846734525
|
||||
13.99255042581802,0.0004081967712510506,14.00969520394442,0.000001975386920666194,13.995172568354999,0.00002183385565256239,14.015275661138503,0.0000038826695948713645
|
||||
|
Reference in New Issue
Block a user