Add new functions

This commit is contained in:
Silas Oettinghaus
2025-02-17 21:31:12 +01:00
parent becaf3f6c9
commit d099efea03
21 changed files with 1502 additions and 272 deletions

View File

@@ -0,0 +1,150 @@
function analyzeEQperformance(ref_bits,ref_symbols,rx_signal,eq_signal,eq_decisions,fsym,M,options)
arguments
ref_bits
ref_symbols
rx_signal
eq_signal
eq_decisions
fsym
M
options.postfilterclass
options.eqclass
options.mlseclass
options.db_precoded
options.displayname
end
% toolkit to visualize stuff related to DSP of IM/DD
%%% Preps
if isempty(eq_decisions)
eq_decisions = PAMmapper(M,0).quantize(eq_signal);
end
%%% Demap eqlzd signal to determine BER
if options.db_precoded
eq_hd = PAMmapper(M,0).quantize(eq_signal);
eq_hd = Duobinary().encode(eq_hd);
eq_hd = Duobinary().decode(eq_hd,"M",M);
rx_bits = PAMmapper(M,0).demap(eq_hd);
else
rx_bits = PAMmapper(M,0).demap(eq_signal);
end
[~,numerr,ber_sd,errpos] = calc_ber(rx_bits.signal,ref_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% fprintf('SD BER: %.2e \n',ber_sd);
%%% Demap provided decisions to determine BER
rx_bits = PAMmapper(M,0).demap(eq_decisions);
[~,numerr,ber_mlse,errpos] = calc_ber(rx_bits.signal,ref_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% fprintf('MLSE BER: %.2e \n',ber_mlse);
%%% Noise prior to DSP (is thius accurate with resampling?)
rx_resampled = rx_signal.normalize("mode","rms").resample("fs_out",fsym);
rx_noise = rx_resampled-ref_symbols;
%%% Noise after to soft-decision DSP
eq_noise = eq_signal-ref_symbols;
col = cbrewer2('paired',12);
lines = numel(findall(figure(200), 'Type', 'Line'))+1;
darkcoloridx = max(mod(2*lines,12),2);
lightcoloridx = max(mod(2*lines-1,12),1);
%%% Separate Classes
constellation = unique(ref_symbols.signal);
received_sd = NaN(numel(constellation),length(ref_symbols));
received_hd = NaN(numel(constellation),length(ref_symbols));
lvlcol = cbrewer2('Set1',numel(constellation));
for lvl = 1:numel(constellation)
%Separate the equalized signal into the
%respective levels based on the actually
%transmitted level!
received_sd(lvl,ref_symbols.signal==constellation(lvl)) = eq_signal.signal(ref_symbols.signal==constellation(lvl));
received_hd(lvl,ref_symbols.signal==constellation(lvl)) = eq_decisions.signal(ref_symbols.signal==constellation(lvl));
end
%%% bursts
% Find differences between consecutive elements
diff_indices = diff(errpos);
% Identify the start of new sequences (when the difference is not 1)
sequence_starts = [1, find(diff_indices ~= 1) + 1]; % Include the first index
sequence_ends = [sequence_starts(2:end) - 1, length(errpos)]; % Calculate end indices
% Initialize burst count and print bursts longer than 10
burst_len = 1:10;
burst_count = zeros(length(burst_len),1);
for t = 1:numel(burst_len)
for i = 1:length(sequence_starts)
% Extract current sequence
current_burst = errpos(sequence_starts(i):sequence_ends(i));
% Check if the sequence length matches criterion
if length(current_burst) == burst_len(t)
burst_count(t) = burst_count(t) + 1;
end
end
end
burst_symbols = burst_count .* burst_len';
burst_rate = burst_symbols ;%./ length(rx_signal);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Rx Spectrum %200
rx_signal.spectrum("displayname",sprintf('Rx %d GBd PAM%d',fsym.*1e-9,M),'fignum',200,'normalizeTo0dB',1,'color',col(darkcoloridx,:));
xline([-fsym/2,fsym/2].*1e-9,'Color',col(mod(lines,12)+2,:),'HandleVisibility','off');
ylim([-20,3]);
%%% EQ Spectrum
%
%%% EQ Time Series %210
showEQTimeSignal(eq_signal,ref_symbols)
%%% EQ Noise Spectrum + inverted Postfilter %220
showEQNoisePSD(eq_noise,options.postfilterclass.burg_coeff,"fignum",220,"color",col(darkcoloridx,:));
%%% EQ SNR Spectrum %230
fft_length = 2^13;
[s_lin,w] = pwelch(eq_signal.signal,hanning(fft_length),fft_length/2,fft_length,eq_signal.fs,"centered","psd","mean");
[n_lin,w] = pwelch(eq_noise.signal,hanning(fft_length),fft_length/2,fft_length,eq_noise.fs,"centered","psd","mean");
w = w.*1e-9;
snr_dbm = 10*log10(s_lin./n_lin);
figure(230)
hold on
plot(w,snr_dbm,'DisplayName','SNR after EQ','LineWidth',1,'Color',col(darkcoloridx,:));
xlabel("Frequency in GHz");
edgetick = 2^(nextpow2(eq_signal.fs*1e-9));
xticks(-edgetick:16:edgetick);
xlim([-128 128]);
ylim([-20,35]);
grid minor
yticks(-200:10:100);
grid on; grid minor;
legend('Interpreter','none');
title('Noise of soft decision signal (not MLSE)');
%%% FFE histogram %240
showLevelHistogram(eq_signal,ref_symbols)
%%% Confusion Matrix
showLevelConfusionMatrix(eq_decisions,ref_symbols,"fignum",250)
%%% Burst Count
figure(260)
hold on
plot(burst_len,burst_rate,'Marker','x','Color',col(darkcoloridx,:),"displayname",sprintf('Rx %d GBd PAM%d; %s',fsym.*1e-9,M,options.displayname));
xlabel('length of error burst');
ylabel('occurences')
grid on
set(gca, 'YScale', 'log');
autoArrangeFigures
end

View File

@@ -0,0 +1,44 @@
function showEQNoisePSD(eq_noise, options)
arguments
eq_noise
options.postfilter_taps = NaN
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.color = [0.2157 0.4941 0.7216];
end
% 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
else
fig = figure(options.fignum); % Use the specified figure number
end
hold on
ax = gca;
% N = numel(ax.Children);
N = sum(arrayfun(@(x) strcmp(x.LineStyle, '-'), ax.Children));
cmap = linspecer(8);
options.color = cmap(mod(N, size(cmap, 1)) + 1, :);
% 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)')
if ~isnan(options.postfilter_taps)
% Hold on to the figure for further plotting
hold on;
% Compute the frequency response of the postfilter
[h, w] = freqz(1, options.postfilter_taps, length(eq_noise), "whole", eq_noise.fs);
h = h / max(abs(h)); % Normalize the filter response
% Adjust frequency axis to center at 0
w_ = (w - eq_noise.fs / 2);
% Plot the inverted postfilter response
plot(w_ * 1e-9, 20 * log10(fftshift(abs(h))), 'DisplayName', ['Burg Coeffs: ', num2str(round(options.postfilter_taps, 2)), ' '], 'LineWidth', 1,'Color',options.color,'LineStyle','--');
% Ensure a legend is displayed
legend('show');
end
end

View File

@@ -0,0 +1,70 @@
function showEQNoiseSNR(tx_signal, rx_signal, options)
arguments
tx_signal
rx_signal
options.fs_tx
options.fs_rx
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.color = [0.2157 0.4941 0.7216];
end
% 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
else
fig = figure(options.fignum); % Use the specified figure number
end
if isa(tx_signal,'Signal')
options.fs_tx = tx_signal.fs;
tx_signal = tx_signal.signal;
end
if isa(rx_signal,'Signal')
options.fs_rx = rx_signal.fs;
rx_signal = rx_signal.signal;
end
hold on
ax = gca;
% N = numel(ax.Children);
N = sum(arrayfun(@(x) strcmp(x.LineStyle, '-'), ax.Children));
cmap = linspecer(8);
options.color = cmap(mod(N, size(cmap, 1)) + 1, :);
% Ensure the figure is ready before calling spectrum
title('SNR of received Signal')
fft_length = 2^(nextpow2(length(tx_signal))-7);
[s_lin,w] = pwelch(tx_signal,hanning(fft_length),fft_length/2,fft_length,options.fs_tx,"centered","psd","mean");
[n_lin,w] = pwelch(rx_signal,hanning(fft_length),fft_length/2,fft_length,options.fs_rx,"centered","psd","mean");
w = w.*1e-9;
snr_dbm = 10*log10(s_lin./n_lin);
% figure(231)
hold on
plot(w,snr_dbm,'DisplayName','SNR','LineWidth',0.5,'Color',options.color);
xlabel("Frequency in GHz");
edgetick = 2^(nextpow2(options.fs_tx*1e-9));
ticks = -edgetick:16:edgetick;
xticks(ticks);
[~,b]=min(abs((-edgetick:16:edgetick)-max(w)));
xlim([-ticks(b+1) ticks(b+1)]);
max_snr = ceil(max(snr_dbm)/10)*10;
min_snr = floor(min(snr_dbm)/10)*10;
ylim([min_snr,max_snr]);
yticks(-200:10:100);
grid on; grid minor;
legend('Interpreter','none');
title('Noise of soft decision signal (not MLSE)');
end

View File

@@ -0,0 +1,54 @@
function showEQTimeSignal(eq_signal,ref_symbols,options)
arguments
eq_signal
ref_symbols
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.color = [0.2157 0.4941 0.7216];
end
% 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
else
fig = figure(options.fignum); % Use the specified figure number
end
M = numel(unique(ref_symbols.signal));
lvlcol = cbrewer2('Set1',M);
col = cbrewer2('paired',2);
eq_decisions = PAMmapper(M,0).quantize(eq_signal);
rx_bits = PAMmapper(M,0).demap(eq_signal);
ref_bits = PAMmapper(M,0).demap(ref_symbols);
%%% Separate Classes
constellation = unique(ref_symbols.signal);
received_sd = NaN(numel(constellation),length(ref_symbols));
received_hd = NaN(numel(constellation),length(ref_symbols));
lvlcol = cbrewer2('Set1',numel(constellation));
for lvl = 1:numel(constellation)
%Separate the equalized signal into the
%respective levels based on the actually
%transmitted level!
received_sd(lvl,ref_symbols.signal==constellation(lvl)) = eq_signal.signal(ref_symbols.signal==constellation(lvl));
received_hd(lvl,ref_symbols.signal==constellation(lvl)) = eq_decisions.signal(ref_symbols.signal==constellation(lvl));
end
[~,numerr,ber_sd,errpos] = calc_ber(rx_bits.signal,ref_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
%%% EQ Time Series %210
eq_signal.plot("fignum",fig.Number,"displayname",'Equalized Signal','color',col(1,:),'clear',1);
hold on
try
yline(PAMmapper(M,0).get_demodulation_thresholds,'HandleVisibility','off','LineStyle','--');
for c = 1:M
scatter(errpos./eq_signal.fs,received_sd(c,errpos),1,'x','MarkerEdgeColor',lvlcol(c,:),'LineWidth',2,'DisplayName',sprintf('Tx Lvl: %d',c));
end
end
end

View File

@@ -0,0 +1,58 @@
function showEQcoefficients(n1, n2, n3, options)
% Show filter coefficients as stem plot
% n1, n2, and n3 in different subplots
% Scale all y-axis to -1 and 1
arguments
n1
n2
n3
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.color = [0.2157, 0.4941, 0.7216];
options.clf = 0; % Clear figure before plotting new
end
% 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
else
fig = figure(options.fignum); % Use the specified figure number
end
if options.clf
clf(fig); % Clear the figure if requested
end
hold on
ax = gca;
N = numel(ax.Children);
% Set up a colormap for consistent coloring
cmap = linspecer(8);
options.color = cmap(mod(N, size(cmap, 1)) + 1, :);
% Create subplots for n1, n2, n3
for i = 1:3
subplot(3, 1, i);
switch i
case 1
stem(n1, 'Color', options.color, 'LineWidth', 1,'Marker','.','MarkerSize',10);
title(sprintf('1st order Filter Coefficients: %d',numel(n1)));
case 2
stem(n2, 'Color', options.color, 'LineWidth', 1,'Marker','.','MarkerSize',10);
title(sprintf('2nd order Filter Coefficients: %d',numel(n2)));
case 3
stem(n3, 'Color', options.color, 'LineWidth', 1,'Marker','.','MarkerSize',10);
title(sprintf('3rd order Filter Coefficients: %d',numel(n3)));
end
ylim([-1, 1]); % Scale y-axis to -1 and 1
grid on;
grid minor
xlabel('Coefficient Index');
ylabel('Amplitude');
end
% Ensure the layout is tight for better visibility
sgtitle('Filter Coefficients'); % Overall title
end

View File

@@ -0,0 +1,47 @@
function showErrorBurstCount(eq_signal,ref_symbols,options)
arguments
eq_signal
ref_symbols
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
end
% 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
else
fig = figure(options.fignum); % Use the specified figure number
end
if numel(unique(eq_signal.signal)) > 20
M = numel(unique(ref_symbols.signal));
eq_signal = PAMmapper(M,0).quantize(eq_signal);
end
diff_indices = eq_signal.signal == ref_symbols.signal;
% Identify the start of new sequences (when the difference is not 1)
sequence_starts = [1, find(diff_indices ~= 1) + 1]; % Include the first index
sequence_ends = [sequence_starts(2:end) - 1, length(errpos)]; % Calculate end indices
% Initialize burst count and print bursts longer than 10
burst_len = 1:10;
burst_count = zeros(length(burst_len),1);
for t = 1:numel(burst_len)
for i = 1:length(sequence_starts)
% Extract current sequence
current_burst = errpos(sequence_starts(i):sequence_ends(i));
% Check if the sequence length matches criterion
if length(current_burst) == burst_len(t)
burst_count(t) = burst_count(t) + 1;
end
end
end
burst_symbols = burst_count .* burst_len';
burst_rate = burst_symbols ;%./ length(rx_signal);
end

View File

@@ -0,0 +1,27 @@
function showLevelConfusionMatrix(decided_symbols,ref_symbols,options)
arguments
decided_symbols
ref_symbols
options.M = NaN
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
end
% 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
else
fig = figure(options.fignum); % Use the specified figure number
end
if length(unique(decided_symbols.signal))>20
assert(~isnan(options.M),'Provide either decided symbol sequence or modulation order M (PAM-4 -> M=4)');
decided_symbols = PAMmapper(options.M,0).quantize(decided_symbols);
end
%%% Confusion Matrix
cm = confusionchart(ref_symbols.signal,decided_symbols.signal,'RowSummary','row-normalized','ColumnSummary','column-normalized','Title','Confusion Matrix','XLabel','Decisions','YLabel','Transmitted');
end

View File

@@ -0,0 +1,50 @@
function showLevelHistogram(eq_signal,ref_symbols,options)
arguments
eq_signal
ref_symbols
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
end
if isa(eq_signal,'Signal')
eq_signal = eq_signal.signal;
end
if isa(ref_symbols,'Signal')
ref_symbols = ref_symbols.signal;
end
% 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
else
fig = figure(options.fignum); % Use the specified figure number
end
%%% Separate Classes
constellation = unique(ref_symbols);
received_sd = NaN(numel(constellation),length(ref_symbols));
lvlcol = cbrewer2('Set1',numel(constellation));
for lvl = 1:numel(constellation)
%Separate the equalized signal into the
%respective levels based on the actually
%transmitted level!
received_sd(lvl,ref_symbols==constellation(lvl)) = eq_signal(ref_symbols==constellation(lvl));
end
%%% FFE histogram
clf
for lvl = 1:numel(constellation)
intermediate = received_sd(lvl,:);
cnt(lvl) = round(numel(intermediate(~isnan(intermediate)))./length(eq_signal),3).*100;
hold on
histogram(received_sd(lvl,:),1000,"EdgeAlpha",0,'DisplayName',['Lvl ',num2str(lvl),' | ',num2str(cnt(lvl)),' %'],'FaceColor',lvlcol(lvl,:),'Normalization','pdf');
end
legend
grid on
end