ML Equalizer works now.

Not yet perfectly integrated into all the routines
This commit is contained in:
Silas Oettinghaus
2025-11-12 09:24:02 +01:00
parent 39bc8243fc
commit 0080cb2264
44 changed files with 5289 additions and 2868 deletions

View File

@@ -1,13 +1,41 @@
classdef ML_MLSE < handle classdef ML_MLSE < handle
% Implementation of plain and simple FFE. % ALGORITHM DESCRIBED IN:
% 1) Training mode (stable performance when you use NLMS) % W. Lanneer and Y. Lefevre, Machine Learning-Based Pre-Equalizers for
% 2) Decision directed mode % Maximum Likelihood Sequence Estimation in High-Speed PONs,
% % in 2023 31st European Signal Processing Conference
%LMS: mu in order of 0.0001 for acceptable convergence speed
%NLMS: mu in order of 0.01 for acceptable convergence speed % Further ML Refs:
%RLS: mu is lambda -> 0.99 -> 1 (has a strong dependency on this! use a loop to find out best values) % https://machinelearningmastery.com/cross-entropy-for-machine-learning/
% % https://docs.pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html
% FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode);
% 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 properties
sps % usually 2 sps % usually 2
@@ -24,6 +52,8 @@ classdef ML_MLSE < handle
mu_dd %weight update in dd mode mu_dd %weight update in dd mode
epochs_dd epochs_dd
adaptive_mu
constellation constellation
L %viterbi memory length L %viterbi memory length
@@ -50,11 +80,13 @@ classdef ML_MLSE < handle
w w
% --- New: fast state lookup --- % --- New: fast state lookup ---
true_to_state_idx
state_dict % containers.Map: key(sequence)->state index state_dict % containers.Map: key(sequence)->state index
key_fmt = '%.8g_'; % key format for sequence strings key_fmt = '%.8g_'; % key format for sequence strings
nSym % |constellation| nSym % |constellation|
ber = [] ber = []
ce = ones(1,1);
end end
methods methods
@@ -72,6 +104,8 @@ classdef ML_MLSE < handle
options.mu_dd = 1e-5; options.mu_dd = 1e-5;
options.epochs_dd = 5; options.epochs_dd = 5;
options.adaptive_mu = 1;
options.delta = 0; options.delta = 0;
options.traceback_depth = 1024; options.traceback_depth = 1024;
@@ -180,11 +214,12 @@ classdef ML_MLSE < handle
X_viterbi = X_viterbi.logbookentry(lbdesc); % append to logbook X_viterbi = X_viterbi.logbookentry(lbdesc); % append to logbook
end end
function [y,y_vit] = equalize(obj,x,d,mu,epochs,N,training) function [y,y_ref] = equalize(obj,x,d,mu,epochs,N,training)
% ============================================================== % ==============================================================
% FFE + Whitening + ML-Based Branch Metric Estimation + Viterbi % FFE + Whitening + ML-Based Branch Metric Estimation + Viterbi
% ============================================================== % ==============================================================
debug = 1; debug = 1;
showPlots = 1;
% --- Input padding and preallocation % --- Input padding and preallocation
y = zeros(N,1); y = zeros(N,1);
@@ -200,6 +235,7 @@ classdef ML_MLSE < handle
v_tilde = zeros(1,obj.nFeasible); v_tilde = zeros(1,obj.nFeasible);
pred = zeros(nSymbols, obj.nStates, 'uint32'); pred = zeros(nSymbols, obj.nStates, 'uint32');
pm_sto = nan(obj.nStates, nSymbols,'like',pm); pm_sto = nan(obj.nStates, nSymbols,'like',pm);
CE_accum = 0;
%%% START IDX %%% START IDX
@@ -210,7 +246,7 @@ classdef ML_MLSE < handle
start_sample = 1; start_sample = 1;
end_sample = start_sample + (ceil(N/obj.sps)-1)*obj.sps; end_sample = start_sample + (ceil(N/obj.sps)-1)*obj.sps;
else else
start_sample = 1; start_sample = 1;%obj.len_tr;
end_sample = N; end_sample = N;
end end
@@ -251,34 +287,72 @@ classdef ML_MLSE < handle
% ===== Gradient update (Algorithm 1) ===== % ===== Gradient update (Algorithm 1) =====
if 1 %training if 1 %training
% previous "to" becomes current "from" (shift-register) % --- allocate storage once
true_from_state_idx = true_to_state_idx; if epoch == 1 && symbol == 1
obj.true_to_state_idx = ones(ceil(N/obj.sps),1,'uint32');
end
% --- Build current "to" state from ABSOLUTE symbol index % --- 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 if sym_idx >= obj.L
curr_seq = d(sym_idx-obj.L+1 : sym_idx); % [d_k-L+1 ... d_k] key_to = obj.seq_key(flip(d(sym_idx-obj.L+1 : sym_idx)));
key_to = obj.seq_key(flip(curr_seq)); % -> [d_k ... d_k-L+1]
if isKey(obj.state_dict, key_to) if isKey(obj.state_dict, key_to)
true_to_state_idx = obj.state_dict(key_to); obj.true_to_state_idx(symbol) = obj.state_dict(key_to);
else else
% Fall back safely (should not happen with proper constellation) obj.true_to_state_idx(symbol) = true_from_state_idx;
true_to_state_idx = true_from_state_idx;
end end
else else
% Not enough history yet for a full L-symbol state obj.true_to_state_idx(symbol) = true_from_state_idx;
% keep previous 'to' and 'from' end
true_to_state_idx = true_to_state_idx;
true_from_state_idx = true_from_state_idx;
end end
% Dirac delta over correct extended transition (from,to) % --- reuse cached state from second epoch onward
true_to_state_idx = obj.true_to_state_idx(symbol);
% --- ensure valid (from,to)
dirac = zeros(obj.nFeasible,1); dirac = zeros(obj.nFeasible,1);
dirac(obj.valid_from_idx==true_from_state_idx & ... mask = obj.valid_from_idx==true_from_state_idx & ...
obj.valid_to_idx ==true_to_state_idx) = 1; obj.valid_to_idx ==true_to_state_idx;
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) % softmax over -v_tilde (numerically safe shift)
p = exp(-(v_tilde - max(v_tilde))); v_shift = -(v_tilde - min(v_tilde)); % shift to small positive numbers
p = p./(sum(p)+eps); v_shift = min(v_shift, 100); % clamp exponent argument ( exp(50)=3e21)
expv = exp(v_shift);
p = expv ./ (sum(expv) + eps);
% for logging only:
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); %use 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;
% gradient term (t - p) % gradient term (t - p)
dmp = (dirac - p)'; % 1×nFeasible dmp = (dirac - p)'; % 1×nFeasible
@@ -288,10 +362,14 @@ classdef ML_MLSE < handle
% Start updates only when the ABSOLUTE symbol index has L history % Start updates only when the ABSOLUTE symbol index has L history
if sym_idx >= obj.L 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
obj.w = obj.w - mu_eff .* dL_Dw; % (Nf+1)×nFeasible
obj.w = obj.w - ones(size(dL_Dw,1),1).*mu .* dL_Dw; % (Nf+1)×nFeasible
% obj.w = obj.w - mu * dL_Dw; % (Nf+1)×nFeasible
end end
% if debug && epoch > 2 % if debug && epoch > 2
@@ -335,46 +413,73 @@ classdef ML_MLSE < handle
viterbi_path(n-1) = pred(n, viterbi_path(n)); viterbi_path(n-1) = pred(n, viterbi_path(n));
end end
y_vit = obj.first_sym(viterbi_path); y_ref = d(start_symbol:end);
y = obj.first_sym(viterbi_path); y = obj.first_sym(viterbi_path);
if debug %&& training if debug && training
sym_start = start_symbol; sym_start = start_symbol;
sym_end = start_symbol + symbol - 1; sym_end = start_symbol + symbol - 1;
ref_slice = d(sym_start : sym_end); ref_slice = d(sym_start : sym_end);
err = sum(y ~= ref_slice(1:numel(y))); err = sum(y ~= ref_slice(1:numel(y)));
try
ref_bits = PAMmapper(obj.S,0).demap(ref_slice); ref_bits = PAMmapper(obj.S,0).demap(ref_slice);
eq_bits = PAMmapper(obj.S,0).demap(y); eq_bits = PAMmapper(obj.S,0).demap(y);
[~, ~, ber, ~] = calc_ber(ref_bits, eq_bits, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); [~, ~, ber, ~] = calc_ber(ref_bits, eq_bits, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
fprintf('Epoch: %d - BER: %.1e \n',epoch, ber); fprintf('Epoch: %d - BER: %.1e \n',epoch, ber);
obj.ber(epoch) = ber; obj.ber(epoch) = ber;
catch
ser = err./length(y);
fprintf('Epoch: %d - SER: %.1e \n',epoch, ser);
end
% ser = err./length(y); obj.ce(epoch) = CE_accum./symbol;
% fprintf('Epoch: %d - SER: %.1e \n',epoch, ser);
figure(10); if showPlots
subplot(2,2,1:2); figure(10);clf
subplot(3,2,1:2);
heatmap(obj.w); heatmap(obj.w);
title('Filter') title('Filter')
subplot(2,2,3); subplot(3,2,3);
v_tildemat = NaN(obj.nStates, obj.nStates); v_tildemat = NaN(obj.nStates, obj.nStates);
v_tildemat(obj.valid) = v_tilde; % log-domain scores v_tildemat(obj.valid) = v_tilde; % log-domain scores
heatmap(v_tildemat); heatmap(v_tildemat);
title('Path Metrics (v_tilde)') title('Path Metrics (v_tilde)')
subplot(2,2,4); subplot(3,2,4);
scatter(1:symbol,pm_sto,1,'.') scatter(1:symbol,pm_sto,1,'.')
% plot(1:symbol,pm_sto,'LineStyle','none')
title('Path Metric Winners') title('Path Metric Winners')
subplot(3,2,5);hold on
scatter(1:symbol,CE_symbol,1,'.');
scatter(1:symbol,CE_smooth,1,'.')
title('Cross Entropy')
subplot(3,2,6); hold on
% Left y-axis: Cross Entropy (linear)
yyaxis left
scatter(1:length(obj.ce), obj.ce, 10, 's', 'filled')
ylabel('Cross Entropy')
% Right y-axis: BER (logarithmic)
yyaxis right
scatter(1:length(obj.ber), obj.ber, 10, 'd', 'filled')
set(gca, 'YScale', 'log')
ylabel('BER (log scale)')
xlim([1, epochs])
xlabel('Epoch')
title('Cross Entropy // BER')
grid on
drawnow drawnow
end end
end end
end end
end end
end
methods (Access=private) methods (Access=private)
function k = seq_key(obj, seq) function k = seq_key(obj, seq)

View File

@@ -118,7 +118,7 @@ classdef MLSE < handle
elseif trellis_state_mode == 2 %simply use the states from the ref signal (should be a robust option) 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)); obj.trellis_states = reshape(unique(data_ref),1,length(unique(data_ref)));
elseif trellis_state_mode == 3 %use_statistical_levels elseif trellis_state_mode == 3 %use_statistical_levels

View File

@@ -16,9 +16,9 @@ classdef Metricstruct
SNR (1,1) double {mustBeNumeric} = NaN SNR (1,1) double {mustBeNumeric} = NaN
SNR_level (:,1) double {mustBeNumeric} = [] SNR_level (:,1) double {mustBeNumeric} = []
STD (1,1) double {mustBeNumeric} = NaN STD (1,1) double {mustBeNumeric} = NaN
STD_level (:,1) double {mustBeNumeric, mustBeNonnegative} = [] STD_level (:,1) double = []
STDrx (1,1) double {mustBeNumeric} = NaN STDrx (1,1) double {mustBeNumeric} = NaN
STDrx_level (:,1) double {mustBeNumeric, mustBeNonnegative} = [] STDrx_level (:,1) double = []
EVM (1,1) double {mustBeNumeric} = NaN EVM (1,1) double {mustBeNumeric} = NaN
EVM_level (:,1) double {mustBeNumeric} = [] EVM_level (:,1) double {mustBeNumeric} = []

View File

@@ -8,6 +8,7 @@ classdef equalizer_structure < int32
% db_precoded (3) % db_precoded (3)
vnle_db_mlse (4) vnle_db_mlse (4)
db_encoded (5) db_encoded (5)
ml_mlse (6)
end end
end end

View File

@@ -13,14 +13,13 @@ arguments
end end
try try
% Initialize output structures % Initialize output structures
output.ffe_package = {}; output.ffe_package = {};
output.mlse_package = {}; output.mlse_package = {};
output.vnle_package = {}; output.vnle_package = {};
output.dbtgt_package = {}; output.dbtgt_package = {};
output.dbenc_package = {}; output.dbenc_package = {};
output.mlmlse_package = {};
if options.mode == "load_run_id" || options.append_to_db if options.mode == "load_run_id" || options.append_to_db
% Initialize database connection % Initialize database connection
@@ -98,9 +97,10 @@ try
use_ffe = 0; use_ffe = 0;
use_dfe = 0; use_dfe = 0;
use_vnle_mlse = 1; use_vnle_mlse = 0;
use_dbtgt = 0; use_dbtgt = 0;
use_dbenc = 0; use_dbenc = 0;
use_ml_mlse = 1;
addProcessingResultToDatabase = 0; addProcessingResultToDatabase = 0;
@@ -130,7 +130,7 @@ try
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
eq_post = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",2001,"sps",1,"decide",0,"adaption_technique","lms"); eq_post = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",2001,"sps",1,"decide",0,"adaption_technique","lms");
eq_post =FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode); eq_post = FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode);
% Duobinary signaling (db encoded) % Duobinary signaling (db encoded)
mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels); mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels);
eq_db_enc = EQ("Ne", ffe_order, "Nb", dfe_order, "training_length", len_tr, ... eq_db_enc = EQ("Ne", ffe_order, "Nb", dfe_order, "training_length", len_tr, ...
@@ -233,31 +233,26 @@ try
database.addProcessingResult(run_id, ffe_results.metrics, ffe_results.config); database.addProcessingResult(run_id, ffe_results.metrics, ffe_results.config);
end end
% pf_ncoeffs = 2; end
% eq_ = EQ("Ne",ffe_order,"Nb",dfe_order,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1);
% pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); if use_ml_mlse
% % mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
% mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); %ML-based MLSE (L=2)
% mu_ml = 0.01; training_epochs = 250;
% [ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ... ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ...
% "precode_mode", duob_mode,... "len_tr",length(Scpe_sig),"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ...
% 'showAnalysis', 0, ... "traceback_depth",128,"L",2,"delta",4,"adaptive_mu",0);
% "postFFE", [],...
% "eth_style_symbol_mapping", 0); [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Scpe_sig, Symbols, Tx_bits,"precode_mode",duob_mode);
% output.mlmlse_package{r} = ml_mlse_results;
% ffe_results.metrics.print;
% mlse_results.metrics.print; if options.append_to_db
% database.addProcessingResult(run_id, ml_mlse_results.metrics, ml_mlse_results.config);
% output.mlse_package{r} = mlse_results; end
% output.vnle_package{r} = ffe_results;
%
% if options.append_to_db
% database.addProcessingResult(run_id, mlse_results.metrics, mlse_results.config);
% database.addProcessingResult(run_id, ffe_results.metrics, ffe_results.config);
% end
end end
if use_dbtgt if use_dbtgt
useviterbi = 0; useviterbi = 0;
@@ -292,6 +287,7 @@ try
if duob_mode == db_mode.db_encoded if duob_mode == db_mode.db_encoded
mlse_db_enc = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
db_results = duobinary_signaling(eq_db_enc, mlse_db_enc, M, Scpe_sig, Symbols, Tx_bits, "precode_mode",duob_mode, "showAnalysis",0,"postFFE",[]); db_results = duobinary_signaling(eq_db_enc, mlse_db_enc, M, Scpe_sig, Symbols, Tx_bits, "precode_mode",duob_mode, "showAnalysis",0,"postFFE",[]);
output.dbenc_package{r} = db_results; output.dbenc_package{r} = db_results;
if options.append_to_db if options.append_to_db

View File

@@ -36,8 +36,9 @@ if ~isempty(options.postFFE)
end end
% Process through MLSE % Process through MLSE
% [mlse_signal] = mlse_.process(eq_signal); [mlse_signal] = mlse_.process(eq_signal);
[mlse_signal,~,GMI_MLSE] = mlse_.process(eq_signal,tx_symbols); % tx_symbols_ = Duobinary().decode(tx_symbols);
% [mlse_signal,~,GMI_MLSE] = mlse_.process(eq_signal,tx_symbols);
% Apply duobinary encoding and decoding % Apply duobinary encoding and decoding
mlse_signal = Duobinary().encode(mlse_signal); mlse_signal = Duobinary().encode(mlse_signal);

View File

@@ -0,0 +1,113 @@
function [ml_mlse_results] = ml_mlse(eq_, M, rx_signal, tx_symbols, tx_bits, options)
%
%
% Inputs:
% eq_ - Equalizer object
% M - Modulation order
% rx_signal - Received signal
% tx_symbols - Transmitted symbols
% tx_bits - Transmitted bits
% options - Optional parameters
%
% Outputs:
% ffe_results - Results from FFE processing
arguments
eq_
M
rx_signal
tx_symbols
tx_bits
options.precode_mode db_mode
options.eth_style_symbol_mapping = 0;
options.postFFE = [];
end
%% Process signals through equalizer
[eq_signal_hd,y_ref] = eq_.process(rx_signal,tx_symbols);
%% Calculate BER based on precoding mode
[bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping);
% Create FFE results structure
ml_mlse_results = struct();
try
eq_.e = [];
eq_.e2 = [];
eq_.e3 = [];
eq_.b = [];
eq_.b2 = [];
eq_.b3 = [];
end
ml_mlse_results.config = Equalizerstruct();
ml_mlse_results.config.eq = jsonencode(eq_);
ml_mlse_results.config.equalizer_structure = int32(equalizer_structure.ml_mlse);
ml_mlse_results.config.comment = 'function: ML-based MLSE';
ml_mlse_results.metrics = Metricstruct;
ml_mlse_results.metrics.result_id = NaN;
ml_mlse_results.metrics.run_id = NaN;
ml_mlse_results.metrics.eqParam_id = NaN;
ml_mlse_results.metrics.date_of_processing = datetime('now');
ml_mlse_results.metrics.BER = ber;
ml_mlse_results.metrics.numBits = bits;
ml_mlse_results.metrics.numBitErr = errors;
ml_mlse_results.metrics.BER_precoded = ber_precoded;
ml_mlse_results.metrics.numBitErr_precoded = errors_precoded;
ml_mlse_results.metrics.SNR = NaN;
ml_mlse_results.metrics.SNR_level = NaN;
ml_mlse_results.metrics.STD = NaN;
ml_mlse_results.metrics.STD_level = NaN;
ml_mlse_results.metrics.STDrx = NaN;
ml_mlse_results.metrics.STDrx_level = NaN;
ml_mlse_results.metrics.GMI = NaN;
ml_mlse_results.metrics.AIR = NaN;
ml_mlse_results.metrics.EVM = NaN;
ml_mlse_results.metrics.EVM_level = NaN;
ml_mlse_results.metrics.Alpha = NaN;
end
%% Helper Functions
function [bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, precode_mode, M, eth_style)
% Calculate BER based on precoding mode
mapper = PAMmapper(M, 0, "eth_style", eth_style);
switch precode_mode
case db_mode.no_db
% TX Data is not precoded
% A) Emulate diff precoding
eq_signal_hd_precoded = Duobinary().encode(eq_signal_hd, "M", M);
eq_signal_hd_precoded = Duobinary().decode(eq_signal_hd_precoded, "M", M);
tx_symbols_precoded = Duobinary().encode(tx_symbols);
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
tx_bits_precoded = mapper.demap(tx_symbols_precoded);
rx_bits = mapper.demap(eq_signal_hd_precoded);
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits.signal, tx_bits_precoded.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
% B) Just determine BER
rx_bits = mapper.demap(eq_signal_hd);
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
case db_mode.db_precoded
% Data is precoded on TX side
% A) Decode at Rx if no DB targeting was applied
eq_signal_hd_decoded = Duobinary().encode(eq_signal_hd, "M", M);
eq_signal_hd_decoded = Duobinary().decode(eq_signal_hd_decoded, "M", M);
rx_bits_decoded = mapper.demap(eq_signal_hd_decoded);
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits_decoded.signal, tx_bits.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
% B) Omit the Coding by comparing with demapped TX symbol sequence
tx_bits_demapped = mapper.demap(tx_symbols);
rx_bits = mapper.demap(eq_signal_hd);
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits_demapped.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
end
end

View File

@@ -1,40 +0,0 @@
function [eq_, pf_, mlse_, mlse_db_, eq_post] = configureEqualizers(M, len_tr, vnle_order, dfe_order, mu_dc, mu_ffe, mu_dfe, pf_ncoeffs)
% CONFIGUREEQUALIZERS Creates and configures equalizer objects
%
% Inputs:
% M - PAM level
% len_tr - Training length
% vnle_order - Array with orders for VNLE [order1, order2, order3]
% dfe_order - Array with orders for DFE
% mu_dc - DC adaptation rate
% mu_ffe - Array with adaptation rates for FFE [mu1, mu2, mu3]
% mu_dfe - Adaptation rate for DFE
% pf_ncoeffs - Number of coefficients for postfilter
%
% Outputs:
% eq_ - Configured EQ object
% pf_ - Configured Postfilter object
% mlse_ - Configured MLSE_viterbi object
% mlse_db_ - Configured MLSE_viterbi object for duobinary
% eq_post - Configured FFE object for post-processing
% Configure main equalizer
eq_ = EQ("Ne", vnle_order, "Nb", dfe_order, ...
"training_length", len_tr, "training_loops", 5, "dd_loops", 5, ...
"K", 2, "DCmu", mu_dc, "DDmu", [mu_ffe mu_dfe], ...
"DFEmu", 0.005, "FFEmu", 0, "plotfinal", 0, "ideal_dfe", 1);
% Configure postfilter
pf_ = Postfilter("ncoeff", pf_ncoeffs, "useBurg", 1);
% Configure MLSE objects
mlse_ = MLSE_viterbi("duobinary_output", 0, 'M', M, ...
'trellis_states', PAMmapper(M,0).levels);
mlse_db_ = MLSE_viterbi("DIR", [1,1], "duobinary_output", 0, ...
"M", M, "trellis_states", PAMmapper(M,0).levels);
% Configure post-FFE
eq_post = FFE("epochs_tr", 5, "epochs_dd", 5, "len_tr", 4096*2, ...
"mu_dd", 1e-4, "mu_tr", 0, "order", 2001, ...
"sps", 1, "decide", 0);
end

View File

@@ -16,9 +16,9 @@ Scpe_sig = Scpe_sig.resample("fs_out", 2*fsym);
[Scpe_sig, ~] = Scpe_sig.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 0); [Scpe_sig, ~] = Scpe_sig.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 0);
% Apply Gaussian filter % Apply Gaussian filter
% Scpe_sig = Filter('filtdegree', 4, "f_cutoff", Symbols.fs.*0.6, ... Scpe_sig = Filter('filtdegree', 4, "f_cutoff", Symbols.fs.*0.6, ...
% "fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ... "fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ...
% "active", true).process(Scpe_sig); "active", true).process(Scpe_sig);
% Remove DC offset % Remove DC offset
Scpe_sig = Scpe_sig - mean(Scpe_sig.signal); Scpe_sig = Scpe_sig - mean(Scpe_sig.signal);

View File

@@ -209,6 +209,7 @@ wh = submit_options.wh;
wh.addValueToStorageByLinIdx(val.vnle_package, 'vnle_package', jobIndex); wh.addValueToStorageByLinIdx(val.vnle_package, 'vnle_package', jobIndex);
wh.addValueToStorageByLinIdx(val.dbtgt_package,'dbtgt_package',jobIndex); wh.addValueToStorageByLinIdx(val.dbtgt_package,'dbtgt_package',jobIndex);
wh.addValueToStorageByLinIdx(val.dbenc_package,'dbenc_package',jobIndex); wh.addValueToStorageByLinIdx(val.dbenc_package,'dbenc_package',jobIndex);
wh.addValueToStorageByLinIdx(val.mlmlse_package,'mlmlse_package',jobIndex);
end end
end end
function p = setupParallelPool(numWorkers, idleTimeout) function p = setupParallelPool(numWorkers, idleTimeout)

View File

@@ -1,43 +1,97 @@
function beautifyBERplot(options) function beautifyBERplot(options)
% BEAUTIFYBERPLOT Enhances BER-style plots for publication-quality figures.
% Supports automatic smoothing and trend-line overlay.
%
% Usage examples:
% beautifyBERplot; % default
% beautifyBERplot("polyfit",1); % add polynomial fit
% beautifyBERplot("polyfit",1,"fitmethod","pchip") % piecewise cubic fit
%
% Supported fitmethod options: 'polyfit', 'smoothingspline', 'loess', 'pchip'
arguments arguments
options.logscale = 1 options.logscale (1,1) logical = 1
options.polyfit (1,1) logical = 0
options.polyorder (1,1) double = 2
options.fitmethod (1,1) string = "polyfit" % choose fit type
end end
% BEAUTIFYBERPLOT Enhances a BER plot for publication-quality figures.
% Set line properties for all current plot lines % --- find all line objects in current axes
lines = findall(gca, 'Type', 'Line'); lines = findall(gca, 'Type', 'Line');
markers = {'o', 's', 'd', '^', 'v', '>', '<', 'p', 'h'}; % Define marker styles markers = {'o', 's', 'd', '^', 'v', '>', '<', 'p', 'h'};
num_markers = length(markers); num_markers = length(markers);
for i = 1:length(lines) % --- style all lines consistently
lines(i).LineWidth = 1.3; % Thicker line width for i = 1:length(lines)
lines(i).LineStyle = '-'; % Solid lines for simplicity lines(i).LineWidth = 1.1;
lines(i).LineStyle = '-';
if string(lines(i).Marker) == "none" if string(lines(i).Marker) == "none"
lines(i).Marker = markers{mod(i-1, num_markers) + 1}; % Assign markers cyclically lines(i).Marker = markers{mod(i-1, num_markers) + 1};
end end
lines(i).MarkerSize = 7; % Marker size lines(i).MarkerSize = 4;
lines(i).MarkerFaceColor = lines(i).Color; % Use line color for marker face lines(i).MarkerFaceColor = lines(i).Color;
lines(i).MarkerEdgeColor = 'white'; end
end
% --- optional smoothing/fitting overlay
% Change all text interpreters to LaTeX if options.polyfit
set(findall(gca, '-property', 'Interpreter'), 'Interpreter', 'latex'); hold on
for i = 1:length(lines)
% Set figure background to white x = lines(i).XData;
set(gcf, 'Color', 'w'); y = lines(i).YData;
valid = isfinite(x) & isfinite(y);
if sum(valid) < options.polyorder + 1
% Set logarithmic scale for y-axis, but only if it makes sense. continue;
% If this is not always desired, you could condition this on the presence of lines or data. end
if options.logscale
set(gca, 'YScale', 'log'); xf = linspace(min(x(valid)), max(x(valid)), 200);
end
% ----- choose fitting method -----
% Customize grid and box appearance switch lower(options.fitmethod)
set(gca, 'Box', 'on', 'LineWidth', 0.8); % Thicker border case "polyfit"
grid on; p = polyfit(x(valid), y(valid), options.polyorder);
% grid minor; yf = polyval(p, xf);
% Adjust font size and style for better readability case "smoothingspline"
set(gca, 'FontSize', 10, 'FontName', 'Times New Roman'); try
f = fit(x(valid)', y(valid)', 'smoothingspline');
yf = feval(f, xf);
catch
yf = interp1(x(valid), y(valid), xf, 'pchip');
end
case "loess"
yf = smooth(x(valid), y(valid), 0.2, 'loess');
yf = interp1(x(valid), yf, xf, 'linear', 'extrap');
case "pchip"
yf = interp1(x(valid), y(valid), xf, 'pchip');
otherwise
warning('Unknown fitmethod "%s". Using polyfit.', options.fitmethod);
p = polyfit(x(valid), y(valid), options.polyorder);
yf = polyval(p, xf);
end
% --- lightened color for fit overlay
lightcol = lines(i).Color + 0.4 * (1 - lines(i).Color);
lightcol(lightcol > 1) = 1;
plot(xf, yf, '-', 'Color', lightcol, ...
'LineWidth', 0.7, 'Marker', 'none', ...
'HandleVisibility','off');
end
hold off
end
% --- axis scaling and cosmetics
if options.logscale
set(gca, 'YScale', 'log');
end
set(findall(gca, '-property', 'Interpreter'), 'Interpreter', 'latex');
set(gcf, 'Color', 'w');
set(gca, 'Box', 'on', 'LineWidth', 0.8);
grid on;
set(gca, 'FontSize', 10, 'FontName', 'Times New Roman');
end end

View File

@@ -20,7 +20,7 @@ fp.where('Runs', 'rop_attenuation','EQUALS', 0);
fields = db.getTableFieldNames('power_state_info'); fields = db.getTableFieldNames('power_state_info');
fields = [fields; db.getTableFieldNames('dashboard_ungrouped')]; fields = [fields; db.getTableFieldNames('dashboard_ungrouped_new')];
[dataTable,~] = db.queryDB(fp, fields); [dataTable,~] = db.queryDB(fp, fields);
eqstructures = unique(dataTable.equalizer_structure); eqstructures = unique(dataTable.equalizer_structure);

View File

@@ -41,7 +41,9 @@ plotdefs = struct( ...
'legendLocation' , 'best', ... 'legendLocation' , 'best', ...
'fecLineWidth' , 2.2, ... % thicker FEC limits 'fecLineWidth' , 2.2, ... % thicker FEC limits
'fecColor' , [0.25 0.25 0.25], ... 'fecColor' , [0.25 0.25 0.25], ...
'capSize' , 6 ... 'capSize' , 6, ...
'lineStyle_default' , '-', ...
'use_pre_emph_styling' , true ...
); );
cfg.plot = filldefaults(cfg.plot, plotdefs); cfg.plot = filldefaults(cfg.plot, plotdefs);
@@ -131,8 +133,23 @@ for gi = 1:nG
yu = yu(ord); ylo = ylo(ord); yhi = yhi(ord); yu = yu(ord); ylo = ylo(ord); yhi = yhi(ord);
% Styles % Styles
% Decide if we style by pre_emph
canStyleByPre = cfg.plot.use_pre_emph_styling && ismember('pre_emph', T.Properties.VariableNames);
if canStyleByPre
if any(strcmp(group_by,'pre_emph'))
% pre_emph is an explicit grouping key -> take it from the group table
pre = logical(grpTbl.pre_emph(gi)); pre = logical(grpTbl.pre_emph(gi));
else
% pre_emph not grouped, but available in the rows -> infer from the members of this group
pre = logical(mode(T.pre_emph(G==gi)));
end
ls = tern(pre, cfg.plot.lineStyle_pre_emph_on, cfg.plot.lineStyle_pre_emph_off); ls = tern(pre, cfg.plot.lineStyle_pre_emph_on, cfg.plot.lineStyle_pre_emph_off);
else
% no pre-emph styling use a single default style
ls = cfg.plot.lineStyle_default;
end
lbl = buildLabel(grpTbl(gi,:), group_by); lbl = buildLabel(grpTbl(gi,:), group_by);
% Main line (dark) % Main line (dark)

View File

@@ -1,6 +1,6 @@
% === SETTINGS === % === SETTINGS ===
dsp_options.append_to_db = 1; dsp_options.append_to_db = 1;
dsp_options.max_occurences = 15; dsp_options.max_occurences = 2;
experiment = "highspeed_2024"; experiment = "highspeed_2024";
dsp_options.mode = "load_run_id"; % 'simulate' & 'load_files' dsp_options.mode = "load_run_id"; % 'simulate' & 'load_files'
@@ -42,16 +42,16 @@ fp = QueryFilter();
% fp.where('Runs', 'run_id','EQUALS', 987); % fp.where('Runs', 'run_id','EQUALS', 987);
M = 4; M = 4;
fp.where('Runs', 'pam_level','EQUALS', M); fp.where('Runs', 'pam_level','EQUALS', M);
fp.where('Runs', 'bitrate','EQUALS', 420e9);%360,390 fp.where('Runs', 'bitrate','EQUALS', 300e9);%360,390
% fp.where('Runs', 'symbolrate','EQUALS', 162e9); % fp.where('Runs', 'symbolrate','EQUALS', 195e9);
fp.where('Runs', 'fiber_length','EQUALS', 2); % fp.where('Runs', 'fiber_length','EQUALS', 1);
fp.where('Runs', 'is_mpi','EQUALS', 0); fp.where('Runs', 'is_mpi','EQUALS', 0);
% fp.where('Runs', 'interference_path_length','EQUALS', 1000); % fp.where('Runs', 'interference_path_length','EQUALS', 1000);
% fp.where('Runs', 'loop_id','GREATER_THAN', 11); % fp.where('Runs', 'loop_id','GREATER_THAN', 11);
% fp.where('Runs', 'sir','EQUALS',18); % fp.where('Runs', 'sir','EQUALS',18);
fp.where('Runs', 'wavelength','EQUAL', 1310); fp.where('Runs', 'wavelength','EQUAL', 1310);
fp.where('Runs', 'db_mode','EQUALS', 0); fp.where('Runs', 'db_mode','EQUALS', 1);
fp.where('Runs', 'rop_attenuation','EQUAL', 0); % fp.where('Runs', 'rop_attenuation','EQUAL', 0);
% fp.where('Runs', 'power_pd_in','GREATER_THAN', 7); % fp.where('Runs', 'power_pd_in','GREATER_THAN', 7);
[dataTable,~] = db.queryDB(fp, db.getTableFieldNames('Runs')); [dataTable,~] = db.queryDB(fp, db.getTableFieldNames('Runs'));
@@ -66,15 +66,41 @@ wh.addStorage("mlse_package");
wh.addStorage("vnle_package"); wh.addStorage("vnle_package");
wh.addStorage("dbtgt_package"); wh.addStorage("dbtgt_package");
wh.addStorage("dbenc_package"); wh.addStorage("dbenc_package");
wh.addStorage("mlmlse_package");
% === RUN IT === %% === RUN IT ===
[results,wh] = submitJobs(dataTable.run_id(:), dsp_options, "serial", 'wh', wh, 'waitbar', true); [results,wh] = submitJobs(dataTable.run_id(:), dsp_options, "serial", 'wh', wh, 'waitbar', true);
%%
results_db = results(dataTable.db_mode==1); results_db = results(dataTable.db_mode==1);
results_nodb = results(dataTable.db_mode==0); results_nodb = results(dataTable.db_mode==0);
for i = 1:numel(results_db) %BER for ML based MLSE
for i = 1:length(results_db)
ber_m = cellfun(@(c) c.metrics.BER_precoded, results_db{1,i}.mlmlse_package);
[BER_MLMLSE_pre_emph(i), ~] = min(ber_m);
ber_m = cellfun(@(c) c.metrics.BER_precoded, results_nodb{1,i}.mlmlse_package);
[BER_MLMLSE(i), ~] = min(ber_m);
ber_m = cellfun(@(c) c.metrics.BER_precoded, results_nodb{1,i}.mlmlse_package);
[BER_MLMLSE(i), ~] = min(ber_m);
baudrate(i) = dataTable.symbolrate(i);
rop_db(i) = dataTable.power_rop((2*i)-1);
end
figure(11);hold on
plot(sort(rop_db),sort(BER_MLMLSE_pre_emph))
plot(sort(rop_db),sort(BER_MLMLSE))
beautifyBERplot
%%
results_db = results(dataTable.db_mode==1);
results_nodb = results(dataTable.db_mode==0);
for i = 1:numel(results_nodb)
% VNLE (from results_nodb) % VNLE (from results_nodb)
gmi_v = cellfun(@(c) c.metrics.GMI, results_nodb{1,i}.vnle_package); gmi_v = cellfun(@(c) c.metrics.GMI, results_nodb{1,i}.vnle_package);
@@ -89,9 +115,9 @@ for i = 1:numel(results_db)
idx_air_max_vnle(i) = find(air_v == max(air_v), 1); idx_air_max_vnle(i) = find(air_v == max(air_v), 1);
% MLSE (from results_db) % MLSE (from results_db)
gmi_m = cellfun(@(c) c.metrics.GMI, results_nodb{1,i}.mlse_package); gmi_m = cellfun(@(c) c.metrics.GMI, results_db{1,i}.mlse_package);
ber_m = cellfun(@(c) c.metrics.BER, results_nodb{1,i}.mlse_package); ber_m = cellfun(@(c) c.metrics.BER, results_nodb{1,i}.mlse_package);
air_m = cellfun(@(c) c.metrics.AIR, results_nodb{1,i}.mlse_package); air_m = cellfun(@(c) c.metrics.AIR, results_db{1,i}.mlse_package);
[BER_MLSE(i), idx_ber] = min(ber_m); [BER_MLSE(i), idx_ber] = min(ber_m);
GMI_MLSE(i) = gmi_m(idx_ber); GMI_MLSE(i) = gmi_m(idx_ber);
AIR_MLSE(i) = air_m(idx_ber); AIR_MLSE(i) = air_m(idx_ber);
@@ -111,11 +137,22 @@ for i = 1:numel(results_db)
idx_gmi_min_db(i) = find(gmi_db == min(gmi_db), 1); idx_gmi_min_db(i) = find(gmi_db == min(gmi_db), 1);
idx_air_max_db(i) = find(air_db == max(air_db), 1); idx_air_max_db(i) = find(air_db == max(air_db), 1);
%BER for ML based MLSE
ber_m = cellfun(@(c) c.metrics.BER, results_nodb{1,i}.mlmlse_package);
[BER_MLMLSE(i), idx_ber] = min(ber_m);
ber_m = cellfun(@(c) c.metrics.BER_precoded, results_db{1,i}.mlmlse_package);
[BER_MLMLSE_PREC(i), idx_ber] = min(ber_m);
% metadata % metadata
bitrate(i) = dataTable.bitrate(i); bitrate(i) = dataTable.bitrate(i);
baudrate(i) = dataTable.symbolrate(i); baudrate(i) = dataTable.symbolrate(i);
rop_atten(i) = dataTable.rop_attenuation(2*i);
rop_pre(i) = dataTable.power_rop((2*i)-1);
rop(i) = dataTable.power_rop((2*i));
end end
%%
STYLE_BASE = 2; % adjust this single number to scale markers & lines STYLE_BASE = 2; % adjust this single number to scale markers & lines
MARKER_SIZE = STYLE_BASE; % marker size (MATLAB MarkerSize) MARKER_SIZE = STYLE_BASE; % marker size (MATLAB MarkerSize)
LINE_WIDTH = max(2, STYLE_BASE/3); % line width (keeps lines reasonable when STYLE_BASE large) LINE_WIDTH = max(2, STYLE_BASE/3); % line width (keeps lines reasonable when STYLE_BASE large)
@@ -128,6 +165,7 @@ cm.VNLE = cols(1 + d, :);
cm.MLSE = cols(2 + d, :); cm.MLSE = cols(2 + d, :);
cm.DB_precode = cols(3 + d, :); cm.DB_precode = cols(3 + d, :);
cm.DB = cols(4 + d, :); % duobinary cm.DB = cols(4 + d, :); % duobinary
cm.ML_MLSE = cols(6 + d, :);
% prepare x values in GBd % prepare x values in GBd
xGHz = baudrate .* 1e-9; xGHz = baudrate .* 1e-9;
@@ -139,7 +177,7 @@ mk.VNLE = {'Marker','o','MarkerFaceColor',cm.VNLE,'MarkerEdgeColor',cm.VNLE,'
mk.MLSE = {'Marker','*','MarkerFaceColor',cm.MLSE,'MarkerEdgeColor',cm.MLSE,'MarkerSize',MARKER_SIZE}; mk.MLSE = {'Marker','*','MarkerFaceColor',cm.MLSE,'MarkerEdgeColor',cm.MLSE,'MarkerSize',MARKER_SIZE};
mk.DB_precode = {'Marker','^','MarkerFaceColor',cm.DB_precode,'MarkerEdgeColor',cm.DB_precode,'MarkerSize',MARKER_SIZE}; mk.DB_precode = {'Marker','^','MarkerFaceColor',cm.DB_precode,'MarkerEdgeColor',cm.DB_precode,'MarkerSize',MARKER_SIZE};
mk.DB = {'Marker','d','MarkerFaceColor',cm.DB,'MarkerEdgeColor',cm.DB,'MarkerSize',MARKER_SIZE}; mk.DB = {'Marker','d','MarkerFaceColor',cm.DB,'MarkerEdgeColor',cm.DB,'MarkerSize',MARKER_SIZE};
mk.ML_MLSE = {'Marker','^','MarkerFaceColor',cm.ML_MLSE,'MarkerEdgeColor',cm.ML_MLSE,'MarkerSize',MARKER_SIZE};
% ---------------- FIGURE : BER ---------------- % ---------------- FIGURE : BER ----------------
figure(112+M); clf; hold on; figure(112+M); clf; hold on;
@@ -149,20 +187,100 @@ plot(xGHz, BER_VNLE, ...
plot(xGHz, BER_MLSE, ... plot(xGHz, BER_MLSE, ...
'DisplayName','MLSE', ... 'DisplayName','MLSE', ...
mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE); mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
plot(xGHz, BER_DB, ...
'DisplayName','DB tgt.', ...
mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB);
plot(xGHz, BER_DB_PREC, ... plot(xGHz, BER_DB_PREC, ...
'DisplayName','Diff. Precode + DB tgt.', ... 'DisplayName','Diff. Precode + DB tgt.', ...
mk.DB{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.DB); mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB);
plot(xGHz, BER_MLMLSE_PREC, ...
'DisplayName','ML-based MLSE (L=2)', ...
mk.ML_MLSE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.ML_MLSE);
yline(2e-2,'LineWidth',1,'HandleVisibility','off');
yline(4.85e-3,'LineWidth',1,'HandleVisibility','off'); yline(4.85e-3,'LineWidth',1,'HandleVisibility','off');
yline(2.2e-4,'LineWidth',1,'HandleVisibility','off'); yline(2.2e-4,'LineWidth',1,'HandleVisibility','off');
xlabel('Baudrate in GBd'); xlabel('Baudrate in GBd');
ylabel('BER'); ylabel('BER');
set(gca, 'yscale', 'log'); set(gca, 'yscale', 'log');
set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end)); set(gca, 'XTick', xticks_vals(1:end), 'XTickLabel', xtick_labels(1:end));
grid on; grid on;
legend('Location','best'); legend('Location','best');
beautifyBERplot
%%
%%
STYLE_BASE = 2; % adjust this single number to scale markers & lines
MARKER_SIZE = STYLE_BASE; % marker size (MATLAB MarkerSize)
LINE_WIDTH = max(2, STYLE_BASE/3); % line width (keeps lines reasonable when STYLE_BASE large)
% --- color map / method -> color assignment (keeps colors consistent) ---
cols = cbrewer2('Paired',8);
cols = linspecer(6);
d = 0;
cm.VNLE = cols(1 + d, :);
cm.MLSE = cols(2 + d, :);
cm.DB_precode = cols(3 + d, :);
cm.DB = cols(4 + d, :); % duobinary
cm.ML_MLSE = cols(6 + d, :);
% prepare x values in GBd
xdbm = flip(sort(rop));
xticks_vals = xdbm;
xtick_labels = arrayfun(@(v) sprintf('%d', round(v)), xticks_vals, 'UniformOutput', false);
% common marker settings (filled, same face+edge color)
mk.VNLE = {'Marker','o','MarkerFaceColor',cm.VNLE,'MarkerEdgeColor',cm.VNLE,'MarkerSize',MARKER_SIZE};
mk.MLSE = {'Marker','*','MarkerFaceColor',cm.MLSE,'MarkerEdgeColor',cm.MLSE,'MarkerSize',MARKER_SIZE};
mk.DB_precode = {'Marker','^','MarkerFaceColor',cm.DB_precode,'MarkerEdgeColor',cm.DB_precode,'MarkerSize',MARKER_SIZE};
mk.DB = {'Marker','d','MarkerFaceColor',cm.DB,'MarkerEdgeColor',cm.DB,'MarkerSize',MARKER_SIZE};
mk.ML_MLSE = {'Marker','^','MarkerFaceColor',cm.ML_MLSE,'MarkerEdgeColor',cm.ML_MLSE,'MarkerSize',MARKER_SIZE};
% ---------------- FIGURE : BER ----------------
figure(112+M); clf; hold on;
plot(flip(sort(rop)), sort(BER_VNLE), ...
'DisplayName','VNLE', ...
mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
plot(flip(sort(rop)), sort(BER_MLSE), ...
'DisplayName','MLSE', ...
mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
plot(flip(sort(rop)), sort(BER_MLSE), ...
'DisplayName','MLSE', ...
mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
plot(flip(sort(rop_pre)), sort(BER_DB_PREC), ...
'DisplayName','Diff. Precode + DB tgt.', ...
mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB);
plot(flip(sort(rop_pre)), sort(BER_MLMLSE_PREC), ...
'DisplayName','ML-based MLSE Diff. Prec. (L=2)', ...
mk.ML_MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.ML_MLSE);
plot(flip(sort(rop_pre)), sort(BER_MLMLSE), ...
'DisplayName','ML-based MLSE (L=2)', ...
mk.ML_MLSE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.DB_precode);
yline(2e-2,'LineWidth',1,'HandleVisibility','off');
yline(4.85e-3,'LineWidth',1,'HandleVisibility','off');
yline(2.2e-4,'LineWidth',1,'HandleVisibility','off');
xlabel('ROP in dBm');
ylabel('BER');
set(gca, 'yscale', 'log');
% set(gca, 'XTick', xticks_vals(1:end), 'XTickLabel', xtick_labels(1:end));
grid on;
legend('Location','best');
beautifyBERplot
%%
% ---------------- FIGURE 15 : GMI ---------------- % ---------------- FIGURE 15 : GMI ----------------

View File

@@ -6,15 +6,16 @@ M = 4;
fp = QueryFilter(); fp = QueryFilter();
% fp.where('Runs', 'run_id','EQUALS', 987); % fp.where('Runs', 'run_id','EQUALS', 987);
% fp.where('Runs', 'pam_level','EQUALS', M); % fp.where('Runs', 'pam_level','EQUALS', M);
% fp.where('Runs', 'symbolrate','EQUALS', 150e9); % fp.where('Runs', 'symbolrate','EQUALS', 165e9); %150, 165, 180, 195, 210, 225, 240
% fp.where('Runs', 'fiber_length','EQUALS', 10); % fp.where('Runs', 'fiber_length','EQUALS', 10);
fp.where('Runs', 'is_mpi','EQUALS', 0); % fp.where('Runs', 'is_mpi','EQUALS', 0);
% fp.where('Runs', 'power_pd_in','GREATER_THAN', 7);
% fp.where('Runs', 'interference_path_length','EQUALS', 1000); % fp.where('Runs', 'interference_path_length','EQUALS', 1000);
% fp.where('Runs', 'loop_id','GREATER_THAN', 11); % fp.where('Runs', 'loop_id','GREATER_THAN', 11);
% fp.where('Runs', 'sir','EQUALS',18); % fp.where('Runs', 'sir','EQUALS',18);
% fp.where('Runs', 'wavelength','EQUALS', 1310); % fp.where('Runs', 'wavelength','EQUALS', 1310);
fp.where('Runs', 'db_mode','EQUALS', 1); % 0 == high preemphasis // 1 == low preemphasis fp.where('Runs', 'db_mode','EQUALS', 2); % 0 == high preemphasis // 1 == low preemphasis
fp.where('Runs', 'rop_attenuation','EQUALS', 0); % fp.where('Runs', 'rop_attenuation','EQUALS', 0);
fields = db.getTableFieldNames('power_state_info'); fields = db.getTableFieldNames('power_state_info');
fields = [fields; db.getTableFieldNames('dashboard_ungrouped_new')]; fields = [fields; db.getTableFieldNames('dashboard_ungrouped_new')];
@@ -22,15 +23,15 @@ fields = [fields; db.getTableFieldNames('dashboard_ungrouped_new')];
%% %%
cfg = struct; cfg = struct;
cfg.x_axis = 'wavelength'; % 'symbolrate' | 'baudrate' | 'bitrate' | 'wavelength' cfg.x_axis = 'symbolrate'; % 'symbolrate' | 'bitrate' | 'wavelength'
cfg.y_axis = 'power_mzm'; % 'BER' | 'GMI' | 'AIR' | ... cfg.y_axis = 'BER'; % 'BER' | 'GMI' | 'AIR' | ...
cfg.group_by = {}; cfg.group_by = {'wavelength'};
cfg.filters = struct('is_mpi',0,'pam_level',M,'equalizer_structure',[equalizer_structure.vnle,equalizer_structure.ffe,equalizer_structure.vnle_pf_mlse,equalizer_structure.vnle_db_mlse,equalizer_structure.dfe]); cfg.filters = struct('is_mpi',0,'pam_level',M,'equalizer_structure',[equalizer_structure.vnle_db_mlse]);%,equalizer_structure.ffe,equalizer_structure.vnle_pf_mlse,equalizer_structure.vnle_db_mlse,equalizer_structure.dfe]);
cfg.y_scale = 'auto'; % auto -> log for BER*, linear otherwise cfg.y_scale = 'auto'; % auto -> log for BER*, linear otherwise
cfg.outlier = 'mad'; % simple, robust; 'none' or 'pctl' also available cfg.outlier = 'mad'; % simple, robust; 'none' or 'pctl' also available
cfg.show_raw = true; cfg.show_raw = true;
cfg.show_spread = 'none'; % 'none' or 'iqr' cfg.show_spread = 'iqr'; % 'none' or 'iqr'
cfg.agg = 'mean'; % or 'median' cfg.agg = 'mean'; % or 'median'
cfg.show_precoded = 0; cfg.show_precoded = 0;
cfg.fec_lines = [2.2e-4 4.85e-3 2e-2]; % optional cfg.fec_lines = [2.2e-4 4.85e-3 2e-2]; % optional
@@ -45,5 +46,4 @@ cfg.plot.scatterAlpha = 0.35;
cfg.plot.legendLocation = 'best'; cfg.plot.legendLocation = 'best';
cfg.plot.fecLineWidth = 2.4; % thicker FEC limits cfg.plot.fecLineWidth = 2.4; % thicker FEC limits
plot_measurements_gpt(dataTable, cfg); plot_measurements_gpt(dataTable, cfg);

View 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;

View 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

View File

@@ -0,0 +1,162 @@
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 0
[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');

View 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

Binary file not shown.

View 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 edgeedge candidate (to be excluded only on evenodd 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 symbolposteriors from LLP in the logdomain:
amax = max(LLP,[],1);
logZ = amax + log(sum(exp(LLP - amax), 1));
logPstate = LLP - logZ; % still in logdomain
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

View 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

View 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

View File

@@ -5,14 +5,14 @@ ber_dbtgt = [];
ber_ml = []; ber_ml = [];
mlse = 1; mlse = 1;
dbtgt = 1; dbtgt = 0;
duob_mode = db_mode.no_db;
baudrates = [136:8:224].*1e9; baudrates = [136:8:224].*1e9;
parfor i = 1:length(baudrates) for i = 1:length(baudrates)
rop = -8; rop = -8;
M = 4; M = 4;
[Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1] = standard_link_model("M",M,"fsym",baudrates(i),"rop",rop,"laser_linewidth",1300,"link_length_m",0,"random_key",1,"apply_pulsef",0); [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_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); % [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);
@@ -25,6 +25,7 @@ parfor i = 1:length(baudrates)
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); 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); 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); 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, ... [ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ...
"precode_mode", duob_mode,... "precode_mode", duob_mode,...
'showAnalysis', 0, ... 'showAnalysis', 0, ...
@@ -33,6 +34,9 @@ parfor i = 1:length(baudrates)
ber_ffe(i) = ffe_results.metrics.BER; ber_ffe(i) = ffe_results.metrics.BER;
ber_mlse(i) = mlse_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 end
@@ -69,11 +73,14 @@ parfor i = 1:length(baudrates)
%% RUN ML-Based MLSE %% RUN ML-Based MLSE
mu_lms = 0.15; mu_lms = 0.15;
ml_mlse_equalizer = ML_MLSE("epochs_tr",30,"epochs_dd",1,"len_tr",2^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",5,"sps",1,... "mu_dd",mu_lms,"mu_tr",mu_lms,"order",4,"sps",2,...
"traceback_depth",128,"L",3,"delta",0); "traceback_depth",128,"L",2,"delta",0);
[y_ml_mlse,~] = ml_mlse_equalizer.process(y_white,Symbols_v1); 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); 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); [~, 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)); fprintf('ML MLSE BER: %.2e \n',ber_ml(i));

View 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));

View File

@@ -1,20 +1,43 @@
clear; clc;
ber_ffe = []; M = 4;
ber_mlse = []; randkey = 1;
ber_dbtgt = []; duob_mode = db_mode.no_db;
ber_ml = [];
mlse = 1; mlse = 1;
dbtgt = 1; dbtgt = 1;
rops = linspace(-15,-5,12); baudrates = 180e9:2e9:220e9; % outer loop
parfor i = 1:length(rops) 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); rop = rops(i);
M = 4;
[Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1] = standard_link_model("M",M,"fsym",224e9,"rop",rop,"laser_linewidth",1300,"link_length_m",0,"random_key",1); [Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1] = standard_link_model( ...
% [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); "M",M,"fsym",baudrate,"rop",rop,"laser_linewidth",1310, ...
% [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); "link_length_m",0,"random_key",1);
%% FFE + MLSE %% FFE + MLSE
if mlse if mlse
@@ -22,74 +45,95 @@ parfor i = 1:length(rops)
ffe_order = [50, 0, 0]; ffe_order = [50, 0, 0];
mu_ffe = [0.0001, 0.0008, 0.001]; mu_ffe = [0.0001, 0.0008, 0.001];
mu_dfe = 0.0004; 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); 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); 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); mlse_ = MLSE("duobinary_output",0,'M',M, ...
[ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ... 'trellis_states',PAMmapper(M,0).levels,'scale_mode',2, ...
"precode_mode", duob_mode,... 'trellis_exclusion',0,'trellis_state_mode',2,'debug',0, ...
'showAnalysis', 0, ... 'DIR',pf_.coefficients);
"postFFE", [],...
[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); "eth_style_symbol_mapping", 0);
ber_ffe(i) = ffe_results.metrics.BER; ber_ffe(i) = ffe_results.metrics.BER;
ber_mlse(i) = mlse_results.metrics.BER; ber_mlse(i) = mlse_results.metrics.BER;
end end
%% FFE + duobinary target MLSE
%% FFE DB tgt. + MLSE
if dbtgt 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); 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]; ffe_order = [50, 0, 0];
mu_ffe = [0.0001, 0.0008, 0.001]; mu_ffe = [0.0001, 0.0008, 0.001];
mu_dfe = 0.0004; 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); eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13, ...
"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005, ...
dbt_results = duobinary_target(eq_, mlse_db_, M, Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ... "DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0, ...
"precode_mode", duob_mode, ... "plotfinal",0,"ideal_dfe",1);
'showAnalysis', 0,...
"postFFE", []);
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; ber_dbtgt(i) = dbt_results.metrics.BER;
end end
%% RUN ML-Based MLSE %% 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);
mu_lms = 0.15; %% ML-based MLSE (L=3)
ml_mlse_equalizer = ML_MLSE("epochs_tr",30,"epochs_dd",1,"len_tr",2^14,... ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ...
"mu_dd",mu_lms,"mu_tr",mu_lms,"order",4,"sps",2,... "len_tr",2^16,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ...
"traceback_depth",128,"L",2,"delta",0); "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
[y_ml_mlse,~] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1); %% --- find required ROP (FEC crossing)
ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse); reqROP_FFE(b) = interp_fec_cross(rops, ber_ffe, FEC_thr);
[~, errors, ber_ml(i), errpos] = calc_ber(ml_mlse_bits.signal, Tx_bits_v1.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); reqROP_MLSE(b) = interp_fec_cross(rops, ber_mlse, FEC_thr);
fprintf('ML MLSE BER: %.2e \n',ber_ml(i)); reqROP_DBTGT(b) = interp_fec_cross(rops, ber_dbtgt, FEC_thr);
reqROP_ML_MLSE2(b) = interp_fec_cross(rops, ber_ml2, FEC_thr);
% figure(11);hold on reqROP_ML_MLSE3(b) = interp_fec_cross(rops, ber_ml3, FEC_thr);
% plot(1:numel(ml_mlse_equalizer.ber),ml_mlse_equalizer.ber);
% beautifyBERplot;
% xlim([1,numel(ml_mlse_equalizer.ber)])
% --- 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 end
%% %% ====================== PLOT REQUIRED ROP ======================
cols = cbrewer2('Set1',8);
figure(3); hold on; colFFE = cols(1,:);
if mlse colMLSE = cols(2,:);
plot(rops,ber_ffe,'DisplayName','FFE'); colDBTGT = cols(4,:);
plot(rops,ber_mlse,'DisplayName','MLSE'); colML_MLSE = cols(3,:);
end
if dbtgt
plot(rops,ber_dbtgt,'DisplayName','DB tgt');
end
plot(rops,ber_ml,'DisplayName','ML-MLSE');
beautifyBERplot;
legend
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');

View 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');

View File

@@ -22,7 +22,7 @@ function [Rx_sig_2sps,Symbols,Tx_bits] = standard_link_model(options)
options.laser_linewidth (1,1) double = 1e6 options.laser_linewidth (1,1) double = 1e6
% --- Channel parameters --- % --- Channel parameters ---
options.link_length_m (1,1) double = 0 options.link_length_km (1,1) double = 0
options.rop (1,:) double = -5 options.rop (1,:) double = -5
options.fsym (1,:) double = (212:16:256)*1e9 options.fsym (1,:) double = (212:16:256)*1e9
options.doub_mode (1,1) db_mode = db_mode.no_db options.doub_mode (1,1) db_mode = db_mode.no_db
@@ -57,7 +57,7 @@ function [Rx_sig_2sps,Symbols,Tx_bits] = standard_link_model(options)
"randomkey",options.random_key+1).process(El_sig); "randomkey",options.random_key+1).process(El_sig);
% --- Fiber --- % --- Fiber ---
Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",options.link_length_m, ... 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); "alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig);
% --- Amplifier (ROP set) --- % --- Amplifier (ROP set) ---
@@ -90,6 +90,10 @@ function [Rx_sig_2sps,Symbols,Tx_bits] = standard_link_model(options)
[~,Scpe_cell,~,found_sync] = Scpe_sig_2sps.tsynch( ... [~,Scpe_cell,~,found_sync] = Scpe_sig_2sps.tsynch( ...
"reference",Symbols,"fs_ref",options.fsym,"debug_plots",0); "reference",Symbols,"fs_ref",options.fsym,"debug_plots",0);
try
Rx_sig_2sps = Scpe_cell{1}.normalize("mode","rms"); Rx_sig_2sps = Scpe_cell{1}.normalize("mode","rms");
catch
Rx_sig_2sps = Scpe_sig_2sps.normalize("mode","rms");
end
end end

View 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;