Many changes here and there. I lost track... :-(

Current work is on MLSE and SD Decoding etc. MLSE is currently not 100% working, the scalings are maybe off?!
This commit is contained in:
Silas Oettinghaus
2025-08-11 07:42:04 +02:00
parent 09d9e5011c
commit 5dbc48abc0
37 changed files with 1506 additions and 570 deletions

View File

@@ -26,13 +26,15 @@ try
% Initialize database connection
database = DBHandler("dataBase", [options.dataBase], "type", options.database_type );
% 2. Check if an equalizer configuration with the same hash exists
queryStr = sprintf('SELECT COUNT(DISTINCT eq_id) AS unique_eq_count, COUNT(*) AS entries_for_run FROM `Results` WHERE run_id = %d', run_id);
existing_results = database.fetch(queryStr);
if existing_results.unique_eq_count >= 6
if (existing_results.entries_for_run / existing_results.unique_eq_count) > 8
return
if 0
% 2. Check if an equalizer configuration with the same hash exists
queryStr = sprintf('SELECT COUNT(DISTINCT eq_id) AS unique_eq_count, COUNT(*) AS entries_for_run FROM `Results` WHERE run_id = %d', run_id);
existing_results = database.fetch(queryStr);
if existing_results.unique_eq_count >= 6
if (existing_results.entries_for_run / existing_results.unique_eq_count) > 5
return
end
end
end
@@ -94,8 +96,8 @@ try
adaption= 1;
use_dd_mode = 1;
use_ffe = 1;
use_dfe = 1;
use_ffe = 0;
use_dfe = 0;
use_vnle_mlse = 1;
use_dbtgt = 1;
use_dbenc = 1;
@@ -139,6 +141,10 @@ try
% Preprocess signal
Scpe_sig = preprocessSignal(Scpe_cell{r}, Symbols, fsym);
% Scpe_sig.spectrum("fignum",2223,"normalizeTo0dB",1,"displayname",'Rx');
% Scpe_sig.spectrum("fignum",22233,"normalizeTo0dB",0,"displayname",'Rx');
% Scpe_sig.eye(fsym,M,"fignum",1024);
if duob_mode ~= db_mode.db_encoded
if use_ffe
@@ -146,19 +152,19 @@ try
ffe_order = [50, 0, 0];
eq_dfe = EQ("Ne",ffe_order,"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.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
dfe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,...
ffe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,...
"precode_mode",duob_mode,...
'showAnalysis',0,...
"postFFE",[],...
"eth_style_symbol_mapping",0);
output.ffe_package{r} = dfe_results;
output.ffe_package{r} = ffe_results;
dfe_results.metrics.print;
dfe_results.config.equalizer_structure = "ffe";
ffe_results.metrics.print;
ffe_results.config.equalizer_structure = "ffe";
if options.append_to_db
database.addProcessingResult(run_id, dfe_results.metrics, dfe_results.config);
database.addProcessingResult(run_id, ffe_results.metrics, ffe_results.config);
end
end

View File

@@ -110,12 +110,14 @@ if options.showAnalysis
rx_signal.spectrum("normalizeTo0dB",1,"fignum",250,"displayname","Rx Spectrum");
Duobinary().encode(tx_symbols).spectrum("normalizeTo0dB",1,"fignum",250,"displayname","DB encoded reference");
Duobinary().encode(tx_symbols).spectrum("normalizeTo0dB",1,"fignum",10,"displayname","DB encoded reference");
showEQNoisePSD(eq_noise,"fignum",250,"displayname",'Duobinary Target Noise after Equalization');
fprintf('DB tgt BER: %.2e \n',ber);
fprintf('DB tgt BER: %.2e \n',ber_db);
figure(341); clf;
showLevelHistogram(eq_signal, db_ref_sequence, "fignum", 341);
end

View File

@@ -43,6 +43,7 @@ end
% Hard decision on VNLE output
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd);
eq_signal_hd.spectrum("displayname",'after full response FFE','fignum',2025,'normalizeTo0dB',1);
% Process through postfilter and MLSE
[mlse_sig_sd,whitened_noise] = pf_.process(eq_signal_sd, eq_noise);
@@ -216,10 +217,24 @@ end
function displayAnalysis(eq_noise, whitened_noise, eq_signal_sd, rx_signal, eq_, pf_, mlse_, tx_symbols, M, postFFE)
% rx_signal.spectrum("displayname",'Rx Signal','fignum',100,'normalizeTo0dB',1);
% Display analysis plots and metrics
figure(336);
hold on;
eq_signal_sd.spectrum("displayname",'Equalized Signal','fignum',336,'normalizeTo0dB',0);
eq_noise.spectrum("displayname",'Equalized Signal','fignum',336,'normalizeTo0dB',0);
showEQNoisePSD(eq_noise, "fignum", 336, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients);
for t = 1:4
pf_.ncoeff = t;
[~,~] = pf_.process(eq_signal_sd, eq_noise);
showEQNoisePSD(eq_noise, "fignum", 3388, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients);
end
tx_symbols.spectrum("displayname",'Equalized Signal','fignum',1234,'normalizeTo0dB',1);
if ~isempty(postFFE)
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 338);
end
@@ -234,7 +249,7 @@ showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 341);
warning off
showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 400);
showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 401);
% showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 401);
drawnow;
warning on

View File

@@ -22,7 +22,7 @@ end
% Ensure the figure is ready before calling spectrum
eq_noise.spectrum("displayname", options.displayname, "fignum", fig.Number, "normalizeTo0dB", 1,"color",options.color);
title('Noise of soft decision signal (not MLSE)')
title('EEN')
if ~isnan(options.postfilter_taps)
% Hold on to the figure for further plotting
@@ -43,5 +43,6 @@ end
end
xlim([-eq_noise.fs/2* 1e-9 eq_noise.fs/2* 1e-9]);
ylim([-15, 0]);
end

View File

@@ -20,6 +20,12 @@ end
f = f(1:half_nfft);
H = H(1:half_nfft);
H_mag = abs(H);
H_norm = H_mag ./ max(H_mag);
H_db = 20*log10(H_norm);
H_db = H_db - min(H_db);
% Determine the figure number to use or create a new figure
if isnan(options.fignum)
fig = figure; % Create a new figure and get its handle
@@ -30,7 +36,7 @@ end
% Magnitude response (in dB)
subplot(2,1,1);
hold on
plot(f.*1e-9, 20*log10(abs(1./H)),'DisplayName',options.displayname);
plot(f.*1e-9, H_db,'DisplayName',options.displayname);
title('(Inverted) Magnitude Response of FFE Filter');
xlabel('Frequency (GHz)');
ylabel('Magnitude (dB)');

View File

@@ -27,6 +27,7 @@ end
received_sd = NaN(numel(constellation),length(ref_symbols));
lvlcol = cbrewer2('Paired',numel(constellation)*2);
lvlcol = lvlcol(2:2:end,:);
lvlcol = linspecer(numel(constellation));
% lvlcol = cbrewer2('Set1',numel(constellation));
for lvl = 1:numel(constellation)
%Separate the equalized signal into the

View File

@@ -1,7 +1,7 @@
function [Bits, Symbols, Scpe_cell, found_sync] = loadAndSyncSignalDataFromDb(dataTable, options)
% LOADSIGNALDATA Loads and synchronizes signal data from storage
%
% Inputs:
% Inputs:d
% dataTable - Table with file paths and configuration
% options - Struct with storage_path and max_occurences
%

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);
% Apply Gaussian filter
Scpe_sig = Filter('filtdegree', 4, "f_cutoff", Symbols.fs.*0.6, ...
"fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ...
"active", true).process(Scpe_sig);
% Scpe_sig = Filter('filtdegree', 4, "f_cutoff", Symbols.fs.*0.6, ...
% "fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ...
% "active", true).process(Scpe_sig);
% Remove DC offset
Scpe_sig = Scpe_sig - mean(Scpe_sig.signal);

View File

@@ -120,7 +120,7 @@ switch submit_mode
% fetchNext has already set Read=true on the errored future.
% Find the one Read==true that we have _not_ yet consumed.
readMask = arrayfun(@(f) f.Read, futures);
idxErr = find(readMask & ~consumedIdx, 1);
idxErr = find(readMask & ~consumedIdx', 1);
consumedIdx(idxErr) = true;
% Pull the _real_ exception out of the future object

View File

@@ -1,5 +1,5 @@
function [ach_inf_rate] = calc_air(test_signal,reference_signal,options)
% Calculation of AIR acc. to J. Kozesnik, Numerically Computing Achievable Rates of Memoryless Channels, Francisco Javier Garcıa-Gomez, doi: 10.1007/978-94-009-9857-5.
% Numerically Computing Achievable Rates of Memoryless Channels, Francisco Javier Garcıa-Gomez, doi: 10.1007/978-94-009-9857-5.
% Implementation is not accessible, I mailed TUM to get the code...
arguments(Input)
@@ -26,7 +26,7 @@ end
% TRIM
[test_signal,reference_signal]=trimseq(test_signal,reference_signal,options.skip_front,options.skip_end);
% CALC EVM
% CALC AIR
%%% new implementation of AIR
constellation = unique(reference_signal);
reference_idx = arrayfun(@(x) find(constellation == x, 1), reference_signal);

View File

@@ -0,0 +1,94 @@
function [GMI,NGMI] = calc_gmi_bitwise(test_signal,reference_signal,options)
% https://cioffi-group.stanford.edu/doc/book/AppendixG.pdf
arguments(Input)
test_signal;
reference_signal;
options.skip_front = 0;
options.skip_end = 0;
options.returnErrorLocation = 0;
end
options.skip_end = abs(options.skip_end);
options.skip_front = abs(options.skip_front);
assert((options.skip_end+options.skip_front)<length(test_signal),"You can not skip more bits than overall length of data! Set skip_front or skip_end to lower value or check data_in");
if isa(reference_signal,'Signal')
reference_signal = reference_signal.signal;
end
if isa(test_signal,'Signal')
test_signal = test_signal.signal;
end
% TRIM
[test_signal,reference_signal]=trimseq(test_signal,reference_signal,options.skip_front,options.skip_end);
% CALC AIR
%%% new implementation of AIR
% Precompute
N = length(test_signal);
M = numel(unique(reference_signal));
nBits = log2(M);
levels = PAMmapper(M,0).levels / PAMmapper(M,0).scaling;
grayBits= PAMmapper(M,0).showBitMapping;
priors = ones(1,M)/M;
noise = test_signal-reference_signal;
sigma2 = var(noise);
% Allocate
LLR = zeros(N, nBits);
rxBits = zeros(N, nBits);
for n = 1:N
y = test_signal(n);
ll_table= -((y - levels).^2)/(2*sigma2) + log(priors); % M×1
% Perbit LLR
for b = 1:nBits
idx0 = grayBits(:,b)==0;
idx1 = grayBits(:,b)==1;
L0 = logsumexp(ll_table(idx0));
L1 = logsumexp(ll_table(idx1));
LLR(n,b) = L1 - L0;
end
end
tx_bits = PAMmapper(M,0,"eth_style",0).demap(reference_signal);
% Compute GMI (bitwise MI averaged over all symbols)
MI_bits = zeros(1,nBits);
for b = 1:nBits
r0 = LLR(tx_bits(:,b)==0,b);
r1 = LLR(tx_bits(:,b)==1,b);
I0 = mean( log2(1 + exp(r0)) );
I1 = mean( log2(1 + exp( -r1)) );
MI_bits(b) = 1 - 0.5*(I0 + I1);
end
GMI = sum(MI_bits); % in bits per symbol
NGMI = GMI / nBits; % normalized per bit
% 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
function [data_,reference_]=trimseq(data,reference,skipstart,skip_end)
data_ = data(skipstart+1:end-skip_end,:);
delta_bits = length(reference) - length(data);
skip_end = delta_bits + skip_end;
reference_ = reference(skipstart+1:end-skip_end,:);
end
end

View File

@@ -1,5 +1,5 @@
function [GMI,NGMI] = calc_ngmi(test_signal,reference_signal,options)
% Silas implementation of (N)GMI calculation according to: J. Cho, L. Schmalen, und P. J. Winzer,
% Silas implementation of (N)GMI calculation according to: J. Cho, L. Schmalen, und P. J. Winzer,
% Normalized Generalized Mutual Information as a Forward Error Correction Threshold for Probabilistically Shaped QAM,
% in 2017 European Conference on Optical Communication (ECOC), Sep. 2017, doi: 10.1109/ECOC.2017.8345872.
@@ -38,7 +38,7 @@ end
assert(length(test_signal) == length(reference_signal),"Sequence length does not match");
%%% implemented according to [1] J. Cho, L. Schmalen, und P. J. Winzer,
%%% implemented according to [1] J. Cho, L. Schmalen, und P. J. Winzer,
% Normalized Generalized Mutual Information as a Forward Error Correction Threshold for Probabilistically Shaped QAM,
% in 2017 European Conference on Optical Communication (ECOC), Sep. 2017, S. 13. doi: 10.1109/ECOC.2017.8345872.
@@ -62,7 +62,7 @@ end
m = log2(M); %bits per symbol
entries = sum(~isnan(received_sd),2)';
P_X = entries./N;
P_X = ones(1,M)/M;%entries./N;
% Parameters
symbols = constellation'; % PAM-4 symbols
@@ -75,18 +75,20 @@ end
% Entropy term
H_X = -sum(P_X .* log2(P_X)); % Entropy of input distribution
% GMI computation
noise_impact_term = 0;
for k = 1:N
y_k = test_signal(k); % Current received sample
[~, closest_symbol_idx] = min(abs(symbols - y_k)); % Closest symbol index
closest_symbol = symbols(closest_symbol_idx); % Closest symbol
tx_bits = PAMmapper(M,0,"eth_style",0).demap(reference_signal);
for i = 1:m
% GMI computation
bit_llr_sum = 0;
for k = 1:N %loop over signal
y_k = test_signal(k); % Current received sample
% [~, closest_symbol_idx] = min(abs(symbols - y_k)); % Closest symbol index
for i = 1:m %loop over bit position
% Extract i-th bit for each symbol
bit_mask = gray_bits(:, i); % Binary column for i-th bit of all symbols
matching_symbols = symbols(bit_mask == gray_bits(closest_symbol_idx, i));
% matching_symbols = symbols(bit_mask == gray_bits(closest_symbol_idx, i)); %old, based on decision that mihht be wrong
matching_symbols = symbols(bit_mask == tx_bits(k,i)); %new, based on tx bits
% Numerator: Sum over x in x_{b_{k, i}}
numerator = sum(q_Y_given_X(y_k, matching_symbols) .* P_X(ismember(symbols, matching_symbols)));
@@ -95,15 +97,15 @@ end
denominator = sum(q_Y_given_X(y_k, symbols) .* P_X);
% Logarithmic contribution
noise_impact_term = noise_impact_term + log2(numerator / denominator);
bit_llr_sum = bit_llr_sum + log2(numerator / denominator);
end
end
% Normalize the noise impact term by N
noise_impact_term = noise_impact_term / N;
bit_llr_sum = bit_llr_sum / N;
% GMI
GMI = H_X + noise_impact_term;
GMI = H_X + bit_llr_sum;
NGMI = GMI / m;
end

View File

@@ -1,24 +1,32 @@
function [snr_all, snr_per_level] = calc_snr(tx_signal, eq_noise)
% CALC_SNR Calculates overall SNR and level-wise SNR for a PAM-M constellation.
%
% [snr_all, snr_per_level] = calc_snr(tx_signal, eq_noise)
%
% Inputs:
% tx_signal - Vector of transmitted signal values.
% eq_noise - Vector of corresponding noise samples.
%
% Outputs:
% snr_all - Overall SNR computed using all signal values.
% snr_per_level - A vector where each element is the SNR computed
% for a unique amplitude level in tx_signal.
%
% The function first computes the overall SNR using the full signal vectors.
% Then it uses the unique levels in tx_signal to calculate the SNR for
% the symbols corresponding to each level separately.
% CALC_SNR Calculates overall SNR and level-wise SNR for a PAM-M constellation.
%
% [snr_all, snr_per_level] = calc_snr(tx_signal, eq_noise)
%
% Inputs:
% tx_signal - Vector of transmitted signal values.
% eq_noise - Vector of corresponding noise samples.
%
% Outputs:
% snr_all - Overall SNR computed using all signal values.
% snr_per_level - A vector where each element is the SNR computed
% for a unique amplitude level in tx_signal.
%
% The function first computes the overall SNR using the full signal vectors.
% Then it uses the unique levels in tx_signal to calculate the SNR for
% the symbols corresponding to each level separately.
if isa(tx_signal,'Signal')
tx_signal = tx_signal.signal;
end
if isa(eq_noise,'Signal')
eq_noise = eq_noise.signal;
end
% Calculate overall SNR using the complete signals
snr_all = snr(tx_signal, eq_noise);
% Get the unique amplitude levels in the transmitted signal
levels = unique(tx_signal);
@@ -27,10 +35,10 @@ function [snr_all, snr_per_level] = calc_snr(tx_signal, eq_noise)
for i = 1:length(levels)
% Find indices where tx_signal equals the current level
idx = (tx_signal == levels(i));
% Compute the SNR for these indices
snr_per_level(i) = snr(tx_signal(idx), eq_noise(idx));
histogram(eq_noise(idx));
end
end

View File

@@ -0,0 +1,54 @@
% Gitter für lambda0 und S0
lambda0_vec = linspace(1300,1320,200);
S0_vec = linspace(0.06,0.1,200);
[Lambda0, S0] = meshgrid(lambda0_vec, S0_vec);
% Festen Betriebsparameter
lambda = 1293; % nm
L = 10; % km
% Dispersion berechnen (lineare Näherung)
D = S0 .* ( lambda - Lambda0 ) * L;
%% 2D-Konturplot nur mit Linien und Text
figure('Color','w');
hold on
% Konturlinien
numLevels = 10;
levels = linspace(min(D(:)), max(D(:)), numLevels);
[C,h] = contour(S0, Lambda0, D, levels, ...
'LineWidth',1.5, ...
'ShowText','on', ...
'LabelFormat','%0.1f');
% cbrewer2-Colormap für die Linien
cmap = cbrewer2('div','RdYlGn', numLevels);
colormap(cmap);
% Achsenlinien
% yline(1310, '--k','ZDW_{mean}','LabelVerticalAlignment','top','LabelHorizontalAlignment','center');
x0 = 0.09;
% xline(x0, '--k','S_{0}','LabelHorizontalAlignment','left');
% Gaussian auf der x-Linie (S0 = 0.09)
mu_zwd = 1310; % nm
sigma_zwd = 2; % nm
zwd_vals = linspace(min(lambda0_vec), max(lambda0_vec), 500);
% PDF berechnen
gauss_pdf = (1/(sigma_zwd*sqrt(2*pi))) * exp(-0.5*((zwd_vals-mu_zwd)/sigma_zwd).^2);
% Normieren und auf eine sichtbare Breite skalieren
scale = 0.005; % passt die Maximal-Auslenkung in x-Richtung an
x_gauss = x0 + (gauss_pdf/max(gauss_pdf)) * scale;
% Plot
% plot(x_gauss, zwd_vals, 'LineWidth',2);
% Achsenbeschriftung & Titel
% Achsenbeschriftung & Titel
xlabel('S0 [ps / nm2 km]', 'FontSize', 12);
ylabel('ZDW [nm]', 'FontSize', 12);
title (sprintf('Dispersion: %d km; %d nm', L, lambda), 'FontSize', 14);
grid on
hold off

33
Functions/getFigureSize.m Normal file
View File

@@ -0,0 +1,33 @@
% GETFIGURESIZE Retrieve the size of the current MATLAB figure window.
% [WIDTH, HEIGHT] = GETFIGURESIZE() returns the width and height of the
% current figure in pixels.
%
% Example:
% % Get size of current figure
% [w, h] = getFigureSize();
% fprintf('Current figure is %d pixels wide and %d pixels tall.\n', w, h);
%
% Adapt snippet for other figures:
% % Suppose H is a handle to any MATLAB figure (existing or new):
% H = figure; % or H = <some existing figure handle>;
% % Retrieve current size of the active (or any) figure:
% [wCur, hCur] = getFigureSize();
% % Set the other figure H to match that size, preserving its position:
% posH = get(H, 'Position'); % [left, bottom, width, height]
% newPos = [posH(1), posH(2), wCur, hCur];
% set(H, 'Position', newPos);
%
% Note:
% - Position vector is given as [left, bottom, width, height] in pixels.
% - If you want to specify a custom size directly, you can replace wCur/hCur
% with desired values.
function [width, height] = getFigureSize()
% Ensure a figure is available
fig = gcf;
% Get the position vector: [left, bottom, width, height]
pos = get(fig, 'Position');
% Extract width and height
width = pos(3);
height = pos(4);
end