Scattered stuff from Silas during Dissertation

This commit is contained in:
Silas Oettinghaus
2026-06-03 09:05:33 +02:00
parent a91da3b97c
commit 5c2e27687d
37 changed files with 1726 additions and 1338 deletions

View File

@@ -4,6 +4,12 @@ arguments
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 = [];
options.clear = true;
options.rasterize = false;
options.raster_resolution (1,1) double = 1000;
options.raster_markersize (1,1) double = 1;
options.raster_clim = [];
end
plot_shit = 1;
@@ -22,7 +28,9 @@ if plot_shit
fig = figure; % Create a new figure and get its handle
else
fig = figure(options.fignum); % Use the specified figure number
clf;
if options.clear
clf;
end
end
end
@@ -31,6 +39,8 @@ rx_symbols = eq_signal; %./ rms(eq_signal);
correct_symbols = ref_symbols;
col = cbrewer2('Paired',numel(unique(correct_symbols))*3);
useDefaultColormap = isempty(options.color);
scatterColor = options.color;
ccnt = -1;
levels = unique(correct_symbols);
@@ -66,20 +76,146 @@ D = sqrt(D_even.^2 + D_odd.^2);
[X,Y] = meshgrid(levels, levels);
[X_,Y_] = meshgrid(statistical_mean, statistical_mean);
plot_xlim = [floor(min(X_(:)))-1, ceil(max(X_(:)))+1];
plot_ylim = [floor(min(Y_(:)))-1, ceil(max(Y_(:)))+1];
hold on;
scatter(rx_even,rx_odd,5*ones(1,length(D)),D,'.','DisplayName',['Even/Odd PAM-',num2str(M)],'MarkerEdgeColor',col(2,:));
% colormap(gca,flip(cbrewer2('Spectral',100)))
colormap(gca,'hsv');
scatter(X_(:), Y_(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', col(6,:),'DisplayName','Statistical Rx Levels');
scatter(X(:), Y(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', col(4,:),'DisplayName','Tx Levels');
xlim([floor(min(X_(:)))-1, ceil(max(X_(:)))+1]);
ylim([floor(min(Y_(:)))-1, ceil(max(Y_(:)))+1]);
if isempty(options.displayname)
displayName = ['Even/Odd PAM-',num2str(M)];
else
displayName = options.displayname;
end
if useDefaultColormap
cols = flip(cbrewer2('RdYlBu',100));
cols=(cbrewer2("RdYlBu",4096));
% cols(45:55,:) = [];
colormap(gca,cols);
if isempty(options.raster_clim)
cLim = [0 max(D).*0.45];
else
cLim = options.raster_clim;
end
if cLim(2) <= cLim(1)
cLim(2) = cLim(1) + eps;
end
clim(cLim);
% colormap(gca,'hsv');
if options.rasterize
nBins = options.raster_resolution;
xEdges = linspace(plot_xlim(1),plot_xlim(2),nBins+1);
yEdges = linspace(plot_ylim(1),plot_ylim(2),nBins+1);
xBin = discretize(rx_even,xEdges);
yBin = discretize(rx_odd,yEdges);
valid = ~isnan(xBin) & ~isnan(yBin) & isfinite(D);
yticks((levels(1:end-1) + levels(2:end)) / 2);
xticks((levels(1:end-1) + levels(2:end)) / 2);
countImg = accumarray([yBin(valid), xBin(valid)],1,[nBins nBins],@sum,0);
dImg = accumarray([yBin(valid), xBin(valid)],D(valid),[nBins nBins],@mean,NaN);
countImgRaw = countImg;
markerRadius = max(0, round((options.raster_markersize - 1) / 2));
if markerRadius > 0
kernel = ones(2*markerRadius+1);
countImg = conv2(countImg,kernel,'same');
weightedD = dImg;
weightedD(~isfinite(weightedD)) = 0;
weightedD = conv2(weightedD .* countImgRaw,kernel,'same');
dWeight = conv2(isfinite(dImg) .* countImgRaw,kernel,'same');
dImg = weightedD ./ dWeight;
end
colorIdx = round((dImg-cLim(1)) ./ diff(cLim) * (size(cols,1)-1)) + 1;
colorIdx = min(max(colorIdx,1),size(cols,1));
density = log1p(countImg);
if max(density(:)) > 0
density = density ./ max(density(:));
end
rgbImg = ones(nBins,nBins,3);
for rgbIdx = 1:3
colorPlane = reshape(cols(colorIdx,rgbIdx),nBins,nBins);
colorPlane(~isfinite(dImg)) = 1;
rgbImg(:,:,rgbIdx) = (1-density) + density .* colorPlane;
end
hImg = image(linspace(plot_xlim(1),plot_xlim(2),nBins), ...
linspace(plot_ylim(1),plot_ylim(2),nBins), ...
rgbImg);
hImg.HandleVisibility = 'off';
set(gca,'YDir','normal');
uistack(hImg,'bottom');
plot(nan,nan,'.','DisplayName',displayName,'MarkerEdgeColor',col(2,:));
else
sz = ones(1,length(D)).*1;
scatter(rx_even,rx_odd,sz,D,'.','DisplayName',displayName);
end
rxLevelColor = col(6,:);
else
if options.rasterize
nBins = options.raster_resolution;
xEdges = linspace(plot_xlim(1),plot_xlim(2),nBins+1);
yEdges = linspace(plot_ylim(1),plot_ylim(2),nBins+1);
xBin = discretize(rx_even,xEdges);
yBin = discretize(rx_odd,yEdges);
valid = ~isnan(xBin) & ~isnan(yBin);
countImg = accumarray([yBin(valid), xBin(valid)],1,[nBins nBins],@sum,0);
markerRadius = max(0, round((options.raster_markersize - 1) / 2));
if markerRadius > 0
kernel = ones(2*markerRadius+1);
countImg = conv2(countImg,kernel,'same');
end
density = log1p(countImg);
if max(density(:)) > 0
density = density ./ max(density(:));
end
rgbImg = ones(nBins,nBins,3);
for rgbIdx = 1:3
rgbImg(:,:,rgbIdx) = (1-density) + density .* scatterColor(rgbIdx);
end
hImg = image(linspace(plot_xlim(1),plot_xlim(2),nBins), ...
linspace(plot_ylim(1),plot_ylim(2),nBins), ...
rgbImg);
hImg.HandleVisibility = 'off';
set(gca,'YDir','normal');
uistack(hImg,'bottom');
plot(nan,nan,'.','DisplayName',displayName,'MarkerEdgeColor',scatterColor);
else
scatter(rx_even,rx_odd,5*ones(1,length(D)),'.','DisplayName',displayName,'MarkerEdgeColor',scatterColor);
end
rxLevelColor = scatterColor;
end
scatter(X_(:), Y_(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', rxLevelColor,'DisplayName',[displayName, ' Rx Levels']);
scatter(X(:), Y(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', col(4,:),'DisplayName','Tx Levels');
xlim(plot_xlim);
ylim(plot_ylim);
decisionBoundaries = (levels(1:end-1) + levels(2:end)) / 2;
ax = gca;
xticks(levels);
yticks(levels);
xticklabels(compose('%g',levels));
yticklabels(compose('%g',levels));
try
ax.XAxis.MinorTickValues = decisionBoundaries;
ax.YAxis.MinorTickValues = decisionBoundaries;
end
ax.XMinorTick = 'on';
ax.YMinorTick = 'on';
ax.XMinorGrid = 'on';
ax.YMinorGrid = 'on';
ax.GridLineStyle = '--';
ax.MinorGridLineStyle = ':';
ax.GridColor = [0.65 0.65 0.65];
ax.MinorGridColor = [0.35 0.35 0.35];
ax.GridAlpha = 0.35;
ax.MinorGridAlpha = 0.45;
legend
xlabel('even symbols');
ylabel('odd symbols');
axis equal; grid on;
axis equal;
grid on;
grid minor;
ax.Layer = 'top';
end

View File

@@ -4,7 +4,7 @@ arguments
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];
options.color = [];
end
% Determine the figure number to use or create a new figure
@@ -16,12 +16,14 @@ 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, :);
if isempty(options.color)
N = sum(arrayfun(@(x) strcmp(x.LineStyle, '-'), ax.Children));
cmap = linspecer(8);
options.color = cmap(mod(N, size(cmap, 1)) + 1, :);
end
% Ensure the figure is ready before calling spectrum
eq_noise.spectrum("displayname", options.displayname, "fignum", fig.Number, "normalizeTo0dB", 1,"color",options.color);
eq_noise.spectrum("displayname", options.displayname, "fignum", fig.Number, "normalizeTo0dB", 0,"color",options.color);
if ~isnan(options.postfilter_taps)
% Hold on to the figure for further plotting

View File

@@ -6,7 +6,8 @@ arguments
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];
options.color = [];
options.linestyle = '-';
end
% Determine the figure number to use or create a new figure
@@ -30,42 +31,52 @@ end
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')
if isempty(options.color)
N = sum(arrayfun(@(x) strcmp(x.LineStyle, '-'), ax.Children));
cmap = linspecer(8);
options.color = cmap(mod(N, size(cmap, 1)) + 1, :);
end
fft_length = 2^(nextpow2(length(eq_signal))-7);
[s_lin,w] = pwelch(eq_signal,hanning(fft_length),fft_length/2,fft_length,options.fs_tx,"centered","psd","mean");
[n_lin,w] = pwelch(noise_signal,hanning(fft_length),fft_length/2,fft_length,options.fs_rx,"centered","psd","mean");
[n_lin,w_noise] = pwelch(noise_signal,hanning(fft_length),fft_length/2,fft_length,options.fs_rx,"centered","psd","mean");
if numel(w_noise) ~= numel(w) || any(abs(w_noise - w) > max(options.fs_tx,options.fs_rx)*eps)
n_lin = interp1(w_noise,n_lin,w,"linear",NaN);
end
w = w.*1e-9;
snr_dbm = 10*log10(s_lin./n_lin);
snr_lin = s_lin./n_lin;
snr_lin = movmean(snr_lin,10);
snr_dbm = 10*log10(snr_lin);
% figure(231)
hold on
plot(w,snr_dbm,'DisplayName','SNR','LineWidth',0.5,'Color',options.color);
yline(mean(snr_dbm),'HandleVisibility','off','Color',options.color);
if isempty(options.displayname)
options.displayname = 'SNR';
end
plot(w,snr_dbm,'DisplayName',options.displayname,'LineWidth',1,'Color',options.color,'LineStyle',options.linestyle);
% yline(mean(snr_dbm),'HandleVisibility','off','Color',options.color,'LineStyle','--','LineWidth',1);
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)]);
ylabel("SNR (dB)");
xlim([min(w) max(w)]);
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);
y_min = min(snr_dbm(:));
y_max = max(snr_dbm(:));
y_range = y_max - y_min;
if y_range == 0
y_range = 10;
end
y_margin = 0.05 * y_range;
ylim([y_min - y_margin, y_max + y_margin]);
try
yticks(round(linspace(y_min, y_max, min(10, max(4, ceil(y_range/10))))));
end
grid on; grid minor;
legend('Interpreter','none');
title('Noise of soft decision signal (not MLSE)');
grid on;
if isempty(get(gca, 'Legend'))
legend('Interpreter','none');
end
end

View File

@@ -6,6 +6,7 @@ arguments
fs
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 = [];
end
% Assuming that obj.e contains the final FFE filter coefficients.
@@ -34,20 +35,24 @@ end
end
% Magnitude response (in dB)
subplot(2,1,1);
% subplot(2,1,1);
hold on
plot(f.*1e-9, H_db,'DisplayName',options.displayname);
if isempty(options.color)
plot(f.*1e-9, H_db,'DisplayName',options.displayname,'LineWidth',1);
else
plot(f.*1e-9, H_db,'DisplayName',options.displayname,'Color',options.color,'LineWidth',1);
end
title('(Inverted) Magnitude Response of FFE Filter');
xlabel('Frequency (GHz)');
ylabel('Magnitude (dB)');
grid on;
% Phase response
subplot(2,1,2);
plot(f.*1e-9, unwrap(angle(H)),'DisplayName',options.displayname);
title('Phase Response of FFE Filter');
xlabel('Frequency (GHz)');
ylabel('Phase');
grid on;
% % Phase response
% subplot(2,1,2);
% plot(f.*1e-9, unwrap(angle(H)),'DisplayName',options.displayname);
% title('Phase Response of FFE Filter');
% xlabel('Frequency (GHz)');
% ylabel('Phase');
% grid on;
end

View File

@@ -4,6 +4,8 @@ arguments
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.ref_symbol_uncoded = []
options.nbins (1,1) double = 1000
end
if isa(eq_signal,'Signal')
@@ -12,6 +14,20 @@ end
if isa(ref_symbols,'Signal')
ref_symbols = ref_symbols.signal;
end
if isa(options.ref_symbol_uncoded,'Signal')
options.ref_symbol_uncoded = options.ref_symbol_uncoded.signal;
end
eq_signal = eq_signal(:);
ref_symbols = ref_symbols(:);
ref_symbol_uncoded = options.ref_symbol_uncoded(:);
assert(numel(eq_signal) == numel(ref_symbols), ...
'showLevelHistogram:LengthMismatch', ...
'eq_signal and ref_symbols must have the same number of samples.');
assert(isempty(ref_symbol_uncoded) || numel(ref_symbol_uncoded) == numel(eq_signal), ...
'showLevelHistogram:LengthMismatch', ...
'options.ref_symbol_uncoded must have the same number of samples as eq_signal.');
% Determine the figure number to use or create a new figure
if isnan(options.fignum)
@@ -22,31 +38,70 @@ end
eq_signal = max(min(eq_signal,3),-3);
%%% Separate Classes
constellation = unique(ref_symbols);
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
%respective levels based on the actually
%transmitted level!
received_sd(lvl,ref_symbols==constellation(lvl)) = eq_signal(ref_symbols==constellation(lvl));
end
% scaling = PAMmapper(numel(constellation),0).get_scaling;
%%% FFE histogram
%%% 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
warning off
histogram(received_sd(lvl,:),1000,"EdgeAlpha",0,'DisplayName',['Lvl ',num2str(lvl),' ; ',num2str(cnt(lvl)),' '],'FaceColor',lvlcol(lvl,:),'Normalization','pdf');
warning on
if isempty(ref_symbol_uncoded)
% Normal mode: split the received signal by the actually
% transmitted reference level.
constellation = unique(ref_symbols);
received_sd = NaN(numel(constellation),numel(ref_symbols));
lvlcol = linspecer(numel(constellation));
for lvl = 1:numel(constellation)
class_mask = ref_symbols == constellation(lvl);
received_sd(lvl,class_mask) = eq_signal(class_mask);
end
for lvl = 1:numel(constellation)
intermediate = received_sd(lvl,:);
cnt(lvl) = round(numel(intermediate(~isnan(intermediate)))./numel(eq_signal),3).*100;
hold on
warning off
histogram(received_sd(lvl,:),options.nbins, ...
"EdgeAlpha",0, ...
"DisplayName",['Lvl ',num2str(lvl),' ; ',num2str(cnt(lvl)),' '], ...
"FaceColor",lvlcol(lvl,:), ...
"Normalization","pdf");
warning on
end
else
% Plot p(y | x_n): y is the noisy observation and x_n is the
% uncoded PAM class. For duobinary this naturally gives multi-modal
% PDFs because one x_n class can map to multiple DB amplitudes. Each
% DB lobe is drawn separately so the DB-level structure stays visible.
db_constellation = unique(ref_symbols);
classes = unique(ref_symbol_uncoded);
lvlcol = linspecer(numel(classes));
for db_lvl = 1:numel(db_constellation)
db_mask = ref_symbols == db_constellation(db_lvl);
mapped_class = mode(ref_symbol_uncoded(db_mask));
[~,class_idx] = min(abs(classes - mapped_class));
cnt = round(nnz(ref_symbol_uncoded == mapped_class)./numel(eq_signal),3).*100;
if db_lvl == findFirstMappedDbLevel(ref_symbols,ref_symbol_uncoded,db_constellation,mapped_class)
display_name = ['p(y|x_',num2str(class_idx),') ; ',num2str(cnt),' '];
display_name = ['p(y|x_',num2str(class_idx),')'];
handle_visibility = "on";
else
display_name = ['p(y|x_',num2str(class_idx),') ; ',num2str(cnt),' '];
display_name = ['p(y|x_',num2str(class_idx),')'];
handle_visibility = "off";
end
weighted_lobe = NaN(size(eq_signal));
weighted_lobe(db_mask) = eq_signal(db_mask);
hold on
warning off
histogram(weighted_lobe,options.nbins, ...
"EdgeAlpha",0, ...
"DisplayName",display_name, ...
"FaceColor",lvlcol(class_idx,:), ...
"HandleVisibility",handle_visibility, ...
"Normalization","pdf");
warning on
end
end
xlim([-3 3]);
legend
@@ -58,3 +113,15 @@ end
end
function first_db_lvl = findFirstMappedDbLevel(ref_symbols,ref_symbol_uncoded,db_constellation,mapped_class)
first_db_lvl = NaN;
for db_lvl = 1:numel(db_constellation)
db_mask = ref_symbols == db_constellation(db_lvl);
if mode(ref_symbol_uncoded(db_mask)) == mapped_class
first_db_lvl = db_lvl;
return
end
end
end

View File

@@ -0,0 +1,35 @@
function showMarkovDiagram(sequence,M)
if isa(sequence,'Signal')
sequence = sequence.signal;
end
sequence = sequence(2+mod(1,length(sequence)):end); %filtered sequences often have one "old" sample at idx=1
x = sequence;
levels = sort(unique(x)).'; % or provide known 1x6 level values
[~,ix] = min(abs(x - levels),[],2);
x = levels(ix);
K = numel(levels);
% map to state indices 1..K
[tf, idx] = ismember(x, levels);
idx = idx(:);
from = idx(1:end-1);
to = idx(2:end);
from = idx(1:2:end);
to = idx(2:2:end);
% counts C(from,to)
C = accumarray([from,to], 1, [K K], @sum, 0);
% row-stochastic transition matrix P(to|from)
rowSums = sum(C,2);
P = C ./ max(rowSums,1);
mc = dtmc(P, 'StateNames', string(levels.*PAMmapper(M,0).get_scaling));
figure('Name','Markov Graph (dtmc)');
gp = graphplot(mc, 'ColorEdges',true, 'LabelEdges',true);
end

View File

@@ -0,0 +1,40 @@
function showTransitionProbabilities(sequence)
if isa(sequence,'Signal')
sequence = sequence.signal;
end
sequence = sequence(2+mod(1,length(sequence)):end); %filtered sequences often have one "old" sample at idx=1
x = sequence;
levels = sort(unique(x)).'; % or provide known 1x6 level values
[~,ix] = min(abs(x - levels),[],2);
x = levels(ix);
K = numel(levels);
% map to state indices 1..K
[tf, idx] = ismember(x, levels);
idx = idx(:);
% from = idx(1:end-1);
% to = idx(2:end);
from = idx(1:2:end);
to = idx(2:2:end);
% counts C(from,to)
C = accumarray([from,to], 1, [K K], @sum, 0);
% row-stochastic transition matrix P(to|from)
rowSums = sum(C,2);
P = C ./ max(rowSums,1);
%% 1) HEATMAP (which transitions are more probable?)
figure('Name','Transition Probabilities (to | from)');
h = heatmap(levels, levels, P, 'Colormap', parula, 'ColorbarVisible','on');
colormap(gca,[[1,1,1];flip(cbrewer2('Spectral',100))]);clim([0,ceil(max(P(:))*10)/10]);
h.XLabel = 'From state (level)';
h.YLabel = 'To state (level)';
h.Title = 'P(to | from)';
end