Files
imdd_silas/projects/IMDD_base_system/simulation_bwl.m
Silas Oettinghaus 7085ba0931 Gif stuff
nonlinear MLSE investigation
trying hard to implment ML-based pre Equalization to find the branch metrics
2025-10-24 16:58:06 +02:00

568 lines
23 KiB
Matlab
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
%%% Run parameters
% TX
M = 4;
m = floor(log2(M)*10)/10;
fsym = 224e9;
apply_pulsef = 1;
fdac = 256e9;
fadc = 256e9;
random_key = 2;
rcalpha = 0.05;
kover = 8;
vbias_rel = 0.5;
u_pi = 3.2;
vbias = -vbias_rel*u_pi;
laser_wavelength = 1310;
laser_linewidth = 1e6;
% Channel
link_length = 0;
vnle_order1 = 50;
vnle_order2 = 0;
vnle_order3 = 0;
vnle_order=[vnle_order1,vnle_order2,vnle_order3];
dfe_order = [0 0 0];
alpha = 0;
len_tr = 4096*2;
mu_ffe1 = 0.0001;
mu_ffe2 = 0.0008;
mu_ffe3 = 0.001;
mu_dc = 0.005;
% mu_dc = 0;
mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
mu_dfe = 0.0004;
dfe_ = sum(dfe_order)>0;
doub_mode = db_mode.no_db;
cols = linspecer(6);
rop = [-6];
bwl = [0.5:0.1:1.5];
fsym = [192:16:256].*1e9;
fsym = 208e9;
ber_vnle = [];
ber_mlse = [];
ber_mlse_burg = [];
ber_viterbi = [];
ber_db = [];
ber_db_diff_precoded = [];
gmi_vnle_bitwise = [];
gmi_mlse = [];
gmi_mlse_db = [];
for r = 1:length(fsym)
Pform = Pulseformer("fsym",fsym(r),"fdac",4*fsym(r),"pulse","rc","pulselength",16,"alpha",rcalpha);
db_precode = 0;
db_encode = 0;
duob_mode = db_mode.no_db;
apply_pulsef = 1;
[Digi_sig,Symbols,Tx_bits] = PAMsource(...
"fsym",fsym(r),"M",M,"order",19,"useprbs",0,...
"fs_out",fdac,...
"applyclipping",0,"clipfactor",1.5,...
"applypulseform",apply_pulsef,"pulseformer",Pform,...
"randkey",random_key,...
"db_precode",db_precode,"db_encode",db_encode,...
"mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process();
% El_sig = AWG("fdac",fdac,"f_cutoff",fsym(r),"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",0).process(Digi_sig);
El_sig = M8199B("kover",kover).process(Digi_sig);
% AWG("fdac",fdac,"f_cutoff",fsym(r),"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",0).process(Digi_sig);
%%%%% Low-pass el. components %%%%%%
% tx_bwl = 100e9;
% El_sig = Filter('filtdegree',3,"f_cutoff",tx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(El_sig);
%%%%% Electrical Driver Amplifier %%%%%%
El_sig = El_sig.normalize("mode","oneone");
% El_sig = El_sig.setPower(1,"dBm");
% figure;histogram(El_sig.signal);
%%%%% MODULATE E/O CONVERSION %%%%%
u_pi = 3.2;
vbias = -u_pi*0.5;
[Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1).process(El_sig);
if 0
figure(15);
hold on
scatter(El_sig.signal(1:100000)+vbias,(abs(Opt_sig.signal(1:100000)).^2)*1e3,0.1,'.','DisplayName','Modulator TF')
xlabel('Input in V')
ylabel('abs(Eopt)2 in mW','Interpreter','latex')
ylim([0 2]);
xlim([-3.2 0]);
Opt_sig.eye(fsym(r),M,"fignum",103837);
end
%%%%%% Fiber %%%%%%
Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig);
%%%%%% ROP %%%%%%
Opt_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig);
% Opt_sig.eye(fsym(r),M,"fignum",103838);
% % Opt_sig.signal = Opt_sig.signal + 5*abs(mean(Opt_sig.signal));
% Opt_sig.move_it_spectrum("displayname",'Opt Sig after Amp','fignum',1223323);
% Pc = abs(mean(Opt_sig.signal)).^2; % carrier power
% Ptot = mean(abs(Opt_sig.signal).^2); % total power
% Ps = max(Ptot - Pc, eps);
% Pcdb = 10*log10(Pc);
% Psdb = 10*log10(Ps);
%
% cspr_dB = 10*log10(Pc / Ps);
%
% % Minimal in-place CSPR set (real, nonnegative field constraint)
% E = Opt_sig.signal; % real field samples
% target_cspr_dB = 20; % <-- set your target CSPR (dB)
%
% % Decompose into DC + zero-mean waveform
% m = mean(E);
% x0 = E - m; % zero-mean modulation
% Ps0 = mean(x0.^2); % sideband power (fixed if shape kept)
%
% % Current CSPR (for reference)
% Pc_cur = m^2;
% Ptot_cur = mean(E.^2);
% Ps_cur = max(Ptot_cur - Pc_cur, eps);
% cspr_in = 10*log10(Pc_cur / Ps_cur);
%
% % Bias needed for target CSPR, and minimal bias to keep E>=0
% R_tgt = 10^(target_cspr_dB/10); % Pc/Ps
% a_req = sqrt(R_tgt * Ps0); % required DC bias
% a_min = -min(x0); % to avoid negatives everywhere
% a = max(a_req, a_min); % if infeasible, lands at CSPR_min
%
% % Apply bias (preserves waveform shape)
% E_new = a + x0;
%
% % Achieved CSPR
% Pc_new = mean(E_new)^2;
% Ptot_new = mean(E_new.^2);
% Ps_new = max(Ptot_new - Pc_new, eps);
% cspr_out = 10*log10(Pc_new / Ps_new);
%
% % (Optional) show feasibility info
% cspr_min = 10*log10((a_min^2)/max(Ps0,eps));
% disp(table(cspr_in, target_cspr_dB, cspr_min, cspr_out));
%
% % Use E_new as your adjusted field
% Opt_sig.signal = E_new;
%%%%%% PD Square Law %%%%%%
PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11,"randomkey",random_key).process(Opt_sig);
%%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%%
rx_bwl = 70e9;
PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(PD_sig);
% %%%%%% Low-pass Scope %%%%%%
Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
%%%%%% Scope %%%%%%
Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,...
"delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,...
"samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,...
"adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig);
Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym(r));
% Scpe_sig_resampled.signal = Scpe_sig_resampled.signal(1:2*length(Symbols));
[~, Scpe_cell, ~, found_sync] = Scpe_sig_2sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 0);
Rx_sig = Scpe_cell{1};
Rx_sig = Rx_sig.normalize("mode","rms");
if 1
%Duobinary Targeting
eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"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);
db_ref_sequence = Duobinary().encode(Symbols);
db_ref_constellation = unique(db_ref_sequence.signal);
[eq_signal, eq_noise] = eq_.process(Rx_sig,db_ref_sequence);
viterbi = 0;
if viterbi
mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
mlse_.DIR = [1,1];
[eq_signal_whitened] = mlse_.process(eq_signal);
else
mlse_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).get_levels ./ PAMmapper(M,0).get_scaling);
mlse_.DIR = [1,1];
[eq_signal_whitened,LLR,gmi_mlse_db(r)] = mlse_.process(eq_signal,Symbols);
end
mlse_sig_hd = PAMmapper(M,0,"eth_style",0).quantize(eq_signal_whitened);
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",0).demap(tx_symbols_precoded);
rx_bits_mlse = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd_precoded);
[~,errors_db_diff_precoded,ber_db_diff_precoded(r),a] = calc_ber(rx_bits_mlse.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
burst_db_pre(r,:) = count_error_bursts(a, 15)./numel(Tx_bits.signal);
%B) Just determine BER
rx_bits_mlse = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd);
[bits_mlse,errors_db,ber_db(r),a] = calc_ber(rx_bits_mlse.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
burst_db(r,:) = count_error_bursts(a, 15)./numel(Tx_bits.signal);
fprintf('BER ber_db_diff_precoded: %.2e \n',ber_db_diff_precoded(r));
fprintf('BER Vber_dbNLE: %.2e \n',ber_db(r));
% figure();hold on;stem(1:15,burst_db(r,:),'LineWidth',1,'Color',cols(1,:));stem(1:15,burst_db_pre(r,:),'LineWidth',1,'Color',cols(2,:));set(gca, 'yscale', 'log');
end
% FFE or VNLE
eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.00,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
% eq = VNLE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",[0.0004 0.0005 0.0006],"mu_tr",0,"order",[50,2,2],"sps",2,"decide",0);
[eq_signal_fullresp, eq_noise] = eq_.process(Rx_sig, Symbols);
showEQNoisePSD(eq_noise, "fignum",1273876,"displayname",'noise after EQ');
[mi_gomez(r)] = calc_air(eq_signal_fullresp, Symbols, "skip_front", 100, "skip_end", 100);
[gmi_vnle_bitwise(r)] = calc_ngmi(eq_signal_fullresp,Symbols);
[gmi_bitwise_2(r)] = calc_gmi_bitwise(eq_signal_fullresp,Symbols);
snr_vnle(r) = calc_snr(Symbols, eq_signal_fullresp-Symbols);
% eq_signal_fullresp.plot("displayname",'bla','fignum',199);
% eq_signal_fullresp.eye(fsym(r),M,"fignum",103837);
% Hard decision on VNLE output
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_fullresp);
rx_bits = PAMmapper(M,0,"eth_style",0).demap(eq_signal_hd);
[~,tot_err,ber_vnle(r),a] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
burst_vnle(r,:) = count_error_bursts(a, 10)./tot_err;
% showLevelConfusionMatrix(eq_signal_hd,Symbols,"M",M,"fignum",200,"displayname",'bla');
% showLevelScatter(eq_signal_fullresp,Symbols,"displayname",'VNLE Out','f_sym',fsym(r),'fignum',201);
% show2Dconstellation(eq_signal_fullresp,Symbols,"displayname",'VNLE Out','fignum',2241);
fprintf('BER VNLE: %.2e \n',ber_vnle(r));
fprintf('NGMI VNLE: %.2f \n',gmi_vnle_bitwise(r)./m);
if 1
% Process through postfilter and MLSE
pf_ncoeffs = 1;
if fsym(r) < 200e9
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1,"coefficients",[1,0.1]);
else
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1,"coefficients",[1,0.85]);
end
% showEQNoisePSD(eq_noise,"postfilter_taps",pf_.coefficients,"displayname",'Postfilter Burg based');
alpha(r) = pf_.coefficients(2);
alpha_vec = max(0,round(alpha(r),2)-0.2):0.025:round(alpha(r),2)+0.4;
alpha_vec = unique(sort([alpha_vec, 1, alpha(r)]));
gmi_mlse_ = zeros(size(alpha_vec));
ber_mlse_ = zeros(size(alpha_vec));
parfor a=1:numel(alpha_vec)
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).get_levels ./ PAMmapper(M,0).get_scaling,'DIR',[1,alpha_vec(a)]);
pf_ = Postfilter("ncoeff",1,"useBurg",0,"coefficients",[1,alpha_vec(a)]);
[eq_signal_whitened,whitened_noise] = pf_.process(eq_signal_fullresp, eq_noise);
[signalclass_hd,LLR,gmi_mlse_(a)] = mlse_.process(eq_signal_whitened,Symbols);
mlse_sig_hd = PAMmapper(M, 0, "eth_style", 0).quantize(signalclass_hd);
rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd);
[~,tot_err,ber_mlse_(a),errpos] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% burst_mlse(r,:) = count_error_bursts(errpos, 10);
% if 0
% fprintf('BER MLSE: %.2e \n',ber_mlse(r));
% fprintf('NGMI MLSE: %.5f \n',gmi_mlse(r)./m);
%
% showLevelConfusionMatrix(mlse_sig_hd,Symbols,"M",M,"fignum",300,"displayname",'bla');
%
% levels = sort(unique(Symbols.signal(:)).'); % 1×6
% pairs = reshape(mlse_sig_hd.signal,2,[]).';
% isedge = ismember(pairs, [levels(1) levels(end)]);
% isforbidden = sum(isedge,2)==2;
% fprintf('Found %d forbidden transitions (even→odd edges).\n', nnz(isforbidden));
%
%
% % Process through postfilter and MLSE
% pf_ncoeffs = 1;
% pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
% mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
% [eq_signal_whitened,whitened_noise] = pf_.process(eq_signal_fullresp, eq_noise);
% mlse_.DIR = pf_.coefficients;
% mlse_output = mlse_.process(eq_signal_whitened);
% mlse_sig_hd = PAMmapper(M, 0, "eth_style", 0).quantize(mlse_output);
% rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd);
% [~,~,ber_viterbi(r),~] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% fprintf('Viterbi BER: %.2e \n',ber_viterbi(r));
% end
end
[ber_mlse(r),idx] = min(ber_mlse_);
gmi_mlse(r) = gmi_mlse_(idx);
ber_mlse_burg(r) = ber_mlse_(alpha_vec==alpha(r));
best_alpha(r) = alpha_vec(idx);
end
% IR target in EQ
if 1
% alpha_vec = max(0,round(alpha(r),2)-0.1):0.01:min(1,round(alpha(r),2)+0.1);
plot_stuff = 0;
gmi_mlse_pr_tgt_ = zeros(size(alpha_vec));
ber_mlse_pr_tgt_ = zeros(size(alpha_vec));
parfor a = 1:numel(alpha_vec)
eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.00,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
Symbols_filt = Symbols.filter([1,alpha_vec(a)],1);
[eq_signal_prtgt, eq_noise] = eq_.process(Rx_sig, Symbols_filt);
if plot_stuff
% Plot the response for respective EQ targets
Symbols_filt.spectrum("displayname",'IDEAL Filtered Reference','fignum',240587);
eq_signal_whitened.spectrum("displayname",'Full tgt. EQ + PF','fignum',240587);
eq_signal_prtgt.spectrum("displayname",'Partial Resp. Target EQ','fignum',240587);
noise_pf_out = Symbols_filt-eq_signal_whitened;
noise_pr_tgt = Symbols_filt-eq_signal_prtgt;
noise_pf_out.spectrum("displayname",'Ideal PR - Whitening Out','fignum',240588);
noise_pr_tgt.spectrum("displayname",'Ideal PR - PR Target Out','fignum',240588);
end
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).get_levels ./ PAMmapper(M,0).get_scaling,'DIR',[1,alpha_vec(a)],'debug',0);
[signalclass_hd,LLR,gmi_mlse_pr_tgt_(a)] = mlse_.process(eq_signal_prtgt,Symbols);
mlse_sig_hd = PAMmapper(M, 0, "eth_style", 0).quantize(signalclass_hd);
rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd);
[~,tot_err,ber_mlse_pr_tgt_(a),errpos] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% burst_mlse_(a,:) = count_error_bursts(errpos, 10);
% fprintf('BER MLSE: %.2e \n',ber_mlse_pr_tgt_(a));
% fprintf('NGMI MLSE: %.5f \n',gmi_mlse_pr_tgt_(a)./m);
end
[ber_mlse_pr_tgt(r),idx] = min(ber_mlse_pr_tgt_);
gmi_mlse_pr_tgt(r) = gmi_mlse_pr_tgt_(idx);
best_alpha_pr_tgt(r) = alpha_vec(idx);
end
cols = cbrewer2('paired',8);
figure(); hold on
title(sprintf('%d GBd',fsym(r).*1e-9));
scatter(alpha_vec,ber_mlse_,15,'Marker','o','LineWidth',1,'DisplayName','MLSE','MarkerEdgeColor',cols(1,:));
scatter(best_alpha(r),ber_mlse(r),15,'Marker','o','LineWidth',2,'DisplayName','MLSE','MarkerEdgeColor',cols(2,:));
scatter(alpha(r),ber_mlse_burg(r),25,'Marker','+','LineWidth',2,'DisplayName','MLSE','MarkerEdgeColor',cols(2,:));
scatter(1,ber_db_diff_precoded(r),15,'Marker','diamond','LineWidth',2,'DisplayName','Duobinary','MarkerEdgeColor',cols(4,:));
scatter(1,ber_db(r),15,'Marker','diamond','LineWidth',2,'DisplayName','Duobinary','MarkerEdgeColor',cols(4,:));
scatter(alpha_vec,ber_mlse_pr_tgt_,15,'Marker','x','LineWidth',1,'DisplayName','MLSE Partial Resp tgt','MarkerEdgeColor',cols(5,:));
scatter(best_alpha_pr_tgt(r),ber_mlse_pr_tgt(r),25,'Marker','x','LineWidth',2,'DisplayName','MLSE','MarkerEdgeColor',cols(6,:));
set(gca,"YScale","log");
% ylim([1e-6 0.5]);
% xlim([0.1 1]);
drawnow;
end
% --- style control (one variable controls both marker size and linewidth) ---
STYLE_BASE = 2; % adjust this single number to scale markers & lines
MARKER_SIZE = STYLE_BASE; % marker size (MATLAB MarkerSize)
LINE_WIDTH = max(1.5, 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
% prepare x values in GBd
xGHz = fsym .* 1e-9;
xticks_vals = xGHz;
xtick_labels = arrayfun(@(v) sprintf('%d', round(v)), xticks_vals, 'UniformOutput', false);
% common marker settings (filled, same face+edge color)
mk.VNLE = {'Marker','none','MarkerFaceColor',cm.MLSE,'MarkerEdgeColor',cm.VNLE,'MarkerSize',MARKER_SIZE};
mk.MLSE = {'Marker','none','MarkerFaceColor',cm.MLSE,'MarkerEdgeColor',cm.MLSE,'MarkerSize',MARKER_SIZE};
mk.DB_precode = {'Marker','none','MarkerFaceColor',cm.DB_precode,'MarkerEdgeColor',cm.DB_precode,'MarkerSize',MARKER_SIZE};
mk.DB = {'Marker','none','MarkerFaceColor',cm.DB,'MarkerEdgeColor',cm.DB,'MarkerSize',MARKER_SIZE};
% ---------------- FIGURE 11 : alpha (VNLE) ----------------
figure(110+M); clf; hold on;
plot(xGHz, alpha, ...
'DisplayName','VNLE', ...
mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
xlabel('Baudrate in GBd');
ylabel('alpha');
set(gca, 'XTick', xticks_vals, 'XTickLabel', xtick_labels);
grid on;
legend('Location','best');
% ---------------- FIGURE 15 : GMI ----------------
figure(111+M); clf; hold on;
plot(xGHz, mi_gomez, ...
'DisplayName','MI VNLE', ...
mk.VNLE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
plot(xGHz, gmi_vnle_bitwise, ...
'DisplayName','GMI VNLE', ...
mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
% duobinary has only one GMI curve (DB output)
plot(xGHz, gmi_mlse_db, ...
'DisplayName','GMI DB tgt.', ...
mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB);
% MLSE symbol-wise (if present)
plot(xGHz, gmi_mlse, ...
'DisplayName','GMI MLSE', ...
mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
ylim([log2(M)-1, log2(M)]);
xlabel('Baudrate in GBd');
ylabel('GMI');
set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end));
grid on;
legend('Location','best');
% xlim([184, 256])
% ---------------- FIGURE 13 : BER ----------------
figure(312+M); hold on;
plot(xGHz, ber_vnle, ...
'DisplayName','VNLE', ...
mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
plot(xGHz, ber_mlse, ...
'DisplayName','MLSE', ...
mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
plot(xGHz, ber_viterbi, ...
'DisplayName','Viterbi', ...
mk.MLSE{:}, 'LineStyle','--','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
yline(4.85e-3,'LineWidth',1,'HandleVisibility','off');
yline(2.2e-4,'LineWidth',1,'HandleVisibility','off');
plot(xGHz, ber_db, ...
'DisplayName','DB tgt.', ...
mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB);
plot(xGHz, ber_db_diff_precoded, ...
'DisplayName','Prec. + DB tgt.', ...
mk.DB{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.DB);
xlabel('Baudrate in GBd');
ylabel('BER');
set(gca, 'yscale', 'log');
set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end));
grid on;
legend('Location','best');
% xlim([184, 256])
% ---------------- FIGURE 15 : Information Rates ----------------
tp = TransmissionPerformance;
m = floor(log2(M)*10)/10;
figure(113+M); clf; hold on;
netrates_vnle = tp.calculateNetRate(fsym.* m, ...
'NGMI', gmi_vnle_bitwise./m, ...
'BER', ber_vnle);
%
plot(xGHz, gmi_vnle_bitwise.*xGHz, ...
'DisplayName','GMI*R VNLE', ...
mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
plot(xGHz, netrates_vnle.SDHD.NetRate.*1e-9, ...
'DisplayName','SD+HD VNLE', ...
mk.VNLE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
plot(xGHz, netrates_vnle.HD.NetRate.*1e-9, ...
'DisplayName','Staircase VNLE', ...
mk.VNLE{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
%
% MLSE symbol-wise (if present)
plot(xGHz, gmi_mlse.*xGHz, ...
'DisplayName','GMI*R MLSE', ...
mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
netrates_mlse = tp.calculateNetRate(fsym.* m, ...
'NGMI', gmi_mlse./m, ...
'BER', ber_mlse);
plot(xGHz, netrates_mlse.SDHD.NetRate.*1e-9, ...
'DisplayName','SD+HD MLSE', ...
mk.MLSE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
plot(xGHz, netrates_mlse.HD.NetRate.*1e-9, ...
'DisplayName','Staircase MLSE', ...
mk.MLSE{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
% duobinary has only one GMI curve (DB output)
plot(xGHz, gmi_mlse_db.*xGHz, ...
'DisplayName','GMI*R DB tgt.', ...
mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB);
netrates_db = tp.calculateNetRate(fsym.* m, ...
'NGMI', gmi_mlse_db./m, ...
'BER', ber_db);
plot(xGHz, netrates_db.SDHD.NetRate.*1e-9, ...
'DisplayName','SD+HD DB', ...
mk.DB{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.DB);
plot(xGHz, netrates_db.HD.NetRate.*1e-9, ...
'DisplayName','Staircase DB', ...
mk.DB{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.DB);
% ylim([log2(M)-1, log2(M)]);
xlabel('Baudrate in GBd');
ylabel('AIR in Gbps');
set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end));
grid on;
legend('Location','best');
xlim([184, 256])
% Auxiliary nested helper for numerically stable log-sum-exp
function s = logsumexp(a)
% LOGSUMEXP Compute log(sum(exp(a))) in a numerically stable way
m = max(a);
s = m + log(sum(exp(a - m)));
end