few changes in WDM stuff

cleaned up the base system... but its not yet a good minimal example...
This commit is contained in:
silas (home)
2026-01-07 08:30:14 +01:00
parent 0186eccced
commit 01a3881455
7 changed files with 841 additions and 753 deletions

View File

@@ -1,325 +1,12 @@
%% Robust eval/plot script for res.* (handles missing/partial dims)
% res.ffe stored like: output_ffe{ch, rop, realiz, eval_distance}
% BUT: some dimensions may be singleton or not fully filled (e.g. only 0 km, only 1 realization)
filename = "C:\Users\Silas\Documents\MATLAB\Datensätze\FWM_2025\WDM_20260106_131152_n261_14408161_10km_16ch_400ghz_co_alpha0_5.mat";
filename = "C:\Users\Silas\Documents\MATLAB\Datensätze\FWM_2025\WDM_20260106_131152_n262_14408165_10km_16ch_400ghz_co_alpha-0_5.mat";
filename = "C:\Users\Silas\Documents\MATLAB\Datensätze\FWM_2025\WDM_20260106_134921_n223_14409338_10km_16ch_400ghz_co_alpha0_0.mat";
res = load(filename,'res');
res = res.res;
% -------------------- Basic metadata --------------------
rop = res.settings.rop(:);
wavelengthplan = res.settings.wavelengthplan(:);
distances = res.eval_dist_km(:); % can be [0], [2 10], etc.
%%
% Routine A: plot BER curves and compute crossings
S = plot_BER_vs_ROP(res, 'fec', 3.8e-3);
N_ch = numel(wavelengthplan);
N_rop = numel(rop);
% -------------------- Determine dims robustly --------------------
% Ensure we have 4D addressing even if MATLAB collapses trailing singletons
dims = size(res.ffe);
if numel(dims) < 4
dims(end+1:4) = 1;
end
N_realiz = dims(3);
N_distances = dims(4);
% If res.eval_dist_km length differs from dims(4), trust dims(4)
if numel(distances) ~= N_distances
% best-effort fallback
distances = (1:N_distances).';
end
% -------------------- Plot settings --------------------
fec = 3.8e-3; % choose one; you can switch to 2.2e-4 if needed
qLow = 0.00;
qHigh = 1.00;
% Color per wavelength/channel
try
cols = ccbrewer2('Set1', N_ch);
catch
cols = linspecer(N_ch);
end
% Containers for FEC crossings per channel (per eval distance)
Sffe = cell(N_distances, N_ch);
Sdfe = cell(N_distances, N_ch);
Svnle = cell(N_distances, N_ch);
Smlse = cell(N_distances, N_ch);
Sdbt = cell(N_distances, N_ch);
% -------------------- Choose which eval distances to plot --------------------
% If you want all: eval_list = 1:N_distances;
% If you want a specific distance (e.g., last): eval_list = N_distances;
eval_list = 1;%1:N_distances;
% -------------------- BER vs ROP figures (one figure per eval distance) --------------------
for eval_ptr = eval_list
figure('Name', sprintf('BER vs ROP @ %s', distLabel(distances, eval_ptr)));
hold on;
for ch = 1:N_ch
% ---- Extract cell slices robustly: [N_rop x N_realiz] ----
ffe_cells = sliceCells4D(res.ffe , ch, eval_ptr, N_rop);
dfe_cells = sliceCells4D(res.dfe , ch, eval_ptr, N_rop);
vnle_cells = sliceCells4D(res.vnle, ch, eval_ptr, N_rop);
mlse_cells = sliceCells4D(res.mlse, ch, eval_ptr, N_rop);
dbt_cells = sliceCells4D(res.dbt , ch, eval_ptr, N_rop);
% ---- FEC crossings (works with incomplete columns) ----
[Sffe{eval_ptr,ch}, ~] = fecCrossings(rop, ffe_cells, fec);
[Sdfe{eval_ptr,ch}, ~] = fecCrossings(rop, dfe_cells, fec);
[Svnle{eval_ptr,ch}, ~] = fecCrossings(rop, vnle_cells, fec);
[Smlse{eval_ptr,ch}, ~] = fecCrossings(rop, mlse_cells, fec);
[Sdbt{eval_ptr,ch}, ~] = fecCrossings(rop, dbt_cells, fec);
% ---- Extract BER matrices using only complete realizations ----
ffe_mat = extractCompleteBER(ffe_cells); % N_rop x K
dfe_mat = extractCompleteBER(dfe_cells);
vnle_mat = extractCompleteBER(vnle_cells);
mlse_mat = extractCompleteBER(mlse_cells);
dbt_mat = extractCompleteBER(dbt_cells);
showLegend = (ch == 1); % one legend entry per technique
% Plot bands + mean (only if non-empty)
if ~isempty(ffe_mat)
plotBandMeanBL(rop, ffe_mat, cols(ch,:), sprintf('FFE @ %dnm', round(wavelengthplan(ch))), qLow, qHigh, '--s', showLegend);
end
% if ~isempty(dfe_mat)
% plotBandMeanBL(rop, dfe_mat, cols(ch,:), sprintf('DFE @ %dnm', round(wavelengthplan(ch))), qLow, qHigh, '-o', showLegend);
% end
% if ~isempty(vnle_mat)
% plotBandMeanBL(rop, vnle_mat, cols(ch,:), sprintf('VNLE @ %dnm', round(wavelengthplan(ch))), qLow, qHigh, '--x', showLegend);
% end
% if ~isempty(mlse_mat)
% plotBandMeanBL(rop, mlse_mat, cols(ch,:), sprintf('VNLE+PF+MLSE @ %dnm', round(wavelengthplan(ch))), qLow, qHigh, '-o', showLegend);
% end
% if ~isempty(dbt_mat)
% plotBandMeanBL(rop, dbt_mat, cols(ch,:), sprintf('DBt+MLSE @ %dnm', round(wavelengthplan(ch))), qLow, qHigh, '--v', showLegend);
% end
end
set(gca,'XScale','linear','YScale','log','TickLabelInterpreter','latex','FontSize',11);
yline([3.8e-3, 2.2e-4], 'HandleVisibility','off','LineWidth',1.5);
ylabel('BER');
xlabel('ROP [dB]');
title(sprintf('BER vs. ROP @ %s', distLabel(distances, eval_ptr)));
xlim([min(rop) max(rop)]);
ylim([1e-5 0.3]);
grid on;
legend show;
end
%% -------------------- VIOLIN of ROP at FEC crossing --------------------
% Build technique groups; each group is {N_distances x N_ch} cell entries
% You can pick a distance to show (e.g., eval_ptr=1 for 0 km, or last)
eval_ptr_violin = min(N_distances, max(1, N_distances)); % default: last available
% eval_ptr_violin = 1;
% Choose which technique(s) to show
techNames = {'VNLE'};
S_groups = {Svnle(eval_ptr_violin,:)};
% Only plot if violinplot exists
if exist('violinplot','file') == 2
figure('Name', sprintf('FEC crossing violin @ %s', distLabel(distances, eval_ptr_violin))); hold on;
colsTech = linspecer(numel(S_groups));
for i = 1:numel(S_groups)
S_cell = S_groups{i}; % 1 x N_ch cell, each cell is 1xK crossings
% Pad to rectangular: rows = realizations (max K), cols = wavelengths (N_ch)
Kmax = max(cellfun(@numel, S_cell));
if isempty(Kmax) || Kmax == 0
continue;
end
S_mat = zeros(Kmax, N_ch);
for ch = 1:N_ch
k = numel(S_cell{ch});
if k > 0
S_mat(1:k, ch) = S_cell{ch}(:);
end
end
catLabels = arrayfun(@(nm) sprintf('%d nm', nm), round(wavelengthplan), 'UniformOutput', false);
violinplot(S_mat, catLabels, ...
'ViolinColor', colsTech(i,:), ...
'ViolinAlpha', 0.10, ...
'MarkerSize', 20, ...
'ShowMedian', true, ...
'EdgeColor', colsTech(i,:), ...
'ShowWhiskers', false, ...
'ShowData', true, ...
'ShowBox', false, ...
'Bandwidth', 0.05);
% Add a legend proxy
plot(nan, nan, 'o', 'Color', colsTech(i,:), 'DisplayName', techNames{i});
end
ylim([-10,-6])
ylabel('ROP at FEC crossing');
title(sprintf('ROP to cross BER %.2e @ %s', fec, distLabel(distances, eval_ptr_violin)));
grid on; box on;
legend show;
else
fprintf('violinplot.m not found on path -> skipping violin plot.\n');
end
%% ================= helpers =================
function cells2D = sliceCells4D(C4, ch, eval_ptr, N_rop)
% Returns a [N_rop x N_realiz] cell array (with N_realiz inferred)
% Works even if trailing dimensions are singleton.
%
% If C4 is smaller than expected, missing entries are returned as [].
dims = size(C4);
if numel(dims) < 4, dims(end+1:4) = 1; end
N_realiz = dims(3);
cells2D = cell(N_rop, N_realiz);
% bounds (in case stored arrays are smaller)
chMax = dims(1);
ropMax = dims(2);
rMax = dims(3);
eMax = dims(4);
if ch > chMax || eval_ptr > eMax
return; % all empty
end
ropUse = min(N_rop, ropMax);
rUse = min(N_realiz, rMax);
% Extract and place into a consistent sized cell matrix
tmp = squeeze(C4(ch, 1:ropUse, 1:rUse, eval_ptr));
% squeeze may return vector/empty if rUse==1 etc. Normalize:
tmp = reshape(tmp, ropUse, rUse);
cells2D(1:ropUse, 1:rUse) = tmp;
end
function lbl = distLabel(distances, eval_ptr)
if eval_ptr <= numel(distances)
d = distances(eval_ptr);
if isfinite(d)
lbl = sprintf('%.0f km', d);
return;
end
end
lbl = sprintf('eval\\_%d', eval_ptr);
end
function plotBandMeanBL(x, Y, color, techLabel, qLow, qHigh, lineSpec, showLegend)
% Y: (nPoints x nRealizations)
if isempty(Y), return; end
% Remove realizations that are entirely zero
badCols = all(Y == 0, 1);
Y(:, badCols) = [];
if isempty(Y), return; end
% Avoid log(0)
Y(Y==0) = 1e-8;
mu = mean(Y, 2, 'omitnan');
lo = quantile(Y, qLow, 2);
hi = quantile(Y, qHigh, 2);
b = [mu - lo, hi - mu];
% boundedline optional; fallback to simple plot if missing
if 0%exist('boundedline','file') == 2
[hl, hp] = boundedline(x(:), mu(:), b, lineSpec, 'alpha', 'transparency', 0.18);
set(hl, 'Color', color, 'LineWidth', 1.4, 'MarkerSize', 4);
set(hp, 'FaceColor', color, 'HandleVisibility','off');
else
hl = plot(x(:), mu(:), lineSpec, 'LineWidth', 1.4, 'MarkerSize', 4);
set(hl, 'Color', color);
end
if showLegend
set(hl, 'DisplayName', techLabel);
else
set(hl, 'HandleVisibility','off');
end
if 0%exist('outlinebounds','file') == 2 && exist('boundedline','file') == 2
ho = outlinebounds(hl, hp);
set(ho, 'linestyle', ':', 'color', color, 'linewidth', 1, 'HandleVisibility','off');
end
end
function [S, noCrossingMask] = fecCrossings(rop, cellsNxR, fec)
% cellsNxR: N_rop x R cell array; each cell is struct with .metrics.BER
Y = extractCompleteBER(cellsNxR); % -> N_rop x K
if isempty(Y)
S = [];
noCrossingMask = [];
return;
end
ok = mean(Y,1,'omitnan') <= 0.1;
Y = Y(:, ok);
if isempty(Y)
S = [];
noCrossingMask = [];
return;
end
nR = size(Y,2);
S = nan(1,nR);
noCrossingMask = true(1,nR);
rop = rop(:);
for j = 1:nR
y = Y(:,j);
above = (y > fec);
idx = find(above(1:end-1) & ~above(2:end), 1, 'first');
if ~isempty(idx)
x1 = rop(idx); y1 = y(idx);
x2 = rop(idx+1); y2 = y(idx+1);
if isfinite(y1) && isfinite(y2) && y2 ~= y1
t = (fec - y1) / (y2 - y1);
S(j) = x1 + t*(x2 - x1);
noCrossingMask(j) = false;
end
end
end
end
function Y = extractCompleteBER(cellSlice)
% Keep only those realization columns where ALL N_rop entries are non-empty
if isempty(cellSlice), Y = []; return; end
nR = size(cellSlice,2);
keep = false(1,nR);
for r = 1:nR
col = cellSlice(:,r);
keep(r) = all(cellfun(@(c) ~isempty(c), col));
end
if ~any(keep), Y = []; return; end
Y = cellfun(@(c) c.metrics.BER, cellSlice(:,keep), 'UniformOutput', true);
end
function Y = extractCompleteAlphas(cellSlice)
if isempty(cellSlice), Y = []; return; end
nR = size(cellSlice,2);
keep = false(1,nR);
for r = 1:nR
col = cellSlice(:,r);
keep(r) = all(cellfun(@(c) ~isempty(c), col));
end
if ~any(keep), Y = []; return; end
Y = cellfun(@(c) c.metrics.Alpha, cellSlice(:,keep), 'UniformOutput', true);
end
%% Routine B: violin plot (independent)
plot_FEC_violin(res, 'tech','VNLE', 'fec',3.8e-3, 'eval_ptr',3, 'ylim',[-10 -3]);

View File

@@ -12,6 +12,7 @@ arguments
options.rand_key = 1;
options.num_realiz = 1;
options.fwm_mitigation_technique = "co";
options.chirpalpha = 0;
end
%% Add the imdd_simulation framework to the path
@@ -59,7 +60,9 @@ else
end
if ~exist(output_root,'dir'), mkdir(output_root); end
fname = sprintf('WDM_%s_%s_%s_%dkm_%dch_%dghz_%s.mat', char(t), host, jobid, options.fiber_length_km(end), options.num_channels, options.channel_spacing.*1e-9, options.fwm_mitigation_technique);
fname = sprintf('WDM_%s_%s_%s_%dkm_%dch_%dghz_%s_alpha%0.1f', char(t), host, jobid, options.fiber_length_km(end), options.num_channels, options.channel_spacing.*1e-9, options.fwm_mitigation_technique, options.chirpalpha);
fname = strrep(fname,'.','_');
fname = [fname,'.mat'];
% Worker memory logs directory
memlog_dir = fullfile(output_root, 'memlogs');
@@ -116,12 +119,12 @@ upsample_pow = 2^nextpow2(upsample_required);
s.f_opt = fdac*kover*upsample_pow;
s.f_opt_nyq = s.f_opt/2;
s.rop = -12:0.75:-0.75;
% s.rop = -12:1:0;
s.rop = -10:0.5:0;
%% ---------- Intermediate evaluation points (distance dimension) ----------
segment_length = 1; % km (must match fiber loop below)
eval_dist_km = [0];
eval_dist_km = [2,4,6,8,10];
eval_dist_km = eval_dist_km(eval_dist_km <= link_length);
if link_length > 0
@@ -208,7 +211,9 @@ for realiz = 1:s.num_realiz
% E/O Conversion
Eml_out = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs, ...
"lambda",s.wavelengthplan(l),"bias",vbias,"u_pi",u_pi, ...
"linewidth",laser_linewidth,"randomkey",s.random_key+l+realiz,"alpha",0.8).process(El_sig);
"linewidth",laser_linewidth,"randomkey",s.random_key+l+realiz,"alpha",options.chirpalpha).process(El_sig);
s.alpha = options.chirpalpha;
% El_sig not needed after EML
clear El_sig
@@ -244,7 +249,7 @@ for realiz = 1:s.num_realiz
nSegments = round(nSegments);
zdw = 1310;
randomize_D = true;
randomize_D = false;
% Guard for 0 km: avoid calling getDispersionVector(0,...) if it doesn't support it
if nSegments > 0
@@ -256,7 +261,6 @@ for realiz = 1:s.num_realiz
eval_ptr = 1;
% =================== Queue throttle (prevents OOM) ===================
% Limit how many futures can be outstanding (running + queued + finished-not-yet-fetched).
% Start conservatively; you can raise to 4*NumWorkers if stable.
maxInFlight = max(2, p.NumWorkers);
% =====================================================================
@@ -300,6 +304,10 @@ for realiz = 1:s.num_realiz
end
% -----------------------------------------------------
save_results_per_realization( ...
s, eval_dist_km, ...
output_ffe, output_dfe, output_vnle, output_mlse, output_dbt, ...
output_root, fname);
for seg = 1:nSegments
@@ -318,7 +326,6 @@ for realiz = 1:s.num_realiz
cnt = 0;
total = length(s.rop)*N;
fprintf('total of %d jobs to enqueue at 0 km\n', total);
for ri = 1:length(s.rop)
for l = 1:N
@@ -338,8 +345,8 @@ for realiz = 1:s.num_realiz
len_tr, mu_dc, mu_ffe, mu_dfe, dfe_order, duob_mode, ...
memlog_dir);
fprintf('Enqueued job %d/%d for realiz %d/%d, l=%d/%d, ri=%d/%d at 0 km (inflight=%d/%d)\n', ...
cnt, total, realiz, s.num_realiz, l, N, ri, length(s.rop), numel(F), maxInFlight);
fprintf('Seg: %d - Enqueued job %d/%d for realiz %d/%d, l=%d/%d, ri=%d/%d (inflight=%d/%d)\n', ...
seg, cnt, total, realiz, s.num_realiz, l, N, ri, length(s.rop), numel(F), maxInFlight);
meta(end+1) = struct('l',l,'ri',ri,'realiz',realiz,'eval_ptr',eval_ptr);
end
@@ -356,24 +363,38 @@ for realiz = 1:s.num_realiz
end
while ~isempty(F)
drainIterMax = 10; % 10 * 60s = 10 minutes
for drainIter = 1:drainIterMax
if isempty(F)
break;
end
% Status line: how many futures are in which states?
st = {F.State};
nUnavail = sum(strcmp(st,'unavailable'));
nFinished = sum(strcmp(st,'finished'));
nRunning = sum(strcmp(st,'running'));
nQueued = sum(strcmp(st,'queued'));
nFailed = sum(strcmp(st,'failed'));
fprintf('[drain %d/%d] F=%d | finished=%d running=%d queued=%d failed=%d unavailable=%d\n', ...
drainIter, drainIterMax, numel(F), nFinished, nRunning, nQueued, nFailed, nUnavail);
% Try to fetch at least one result (blocking)
[F, meta, output_ffe, output_dfe, output_vnle, output_mlse, output_dbt] = ...
collect_done(F, meta, output_ffe, output_dfe, output_vnle, output_mlse, output_dbt, true);
% If still not empty, wait a bit before the next drain attempt
if ~isempty(F)
pause(60);
end
end
%% Save results (per realization)
res = struct();
res.settings = s;
res.eval_dist_km = eval_dist_km;
res.ffe = output_ffe;
res.dfe = output_dfe;
res.vnle = output_vnle;
res.mlse = output_mlse;
res.dbt = output_dbt;
save(fullfile(output_root, fname), 'res', '-v7.3');
fprintf('Saved results to: %s\n', fullfile(output_root, fname));
disp(datetime('now','TimeZone','local','Format','yyyyMs.Mdd_HHmmss'));
save_results_per_realization( ...
s, eval_dist_km, ...
output_ffe, output_dfe, output_vnle, output_mlse, output_dbt, ...
output_root, fname);
% Per-realization large arrays that are no longer needed
clear Opt_sig_wdm_fib Symbols Tx_bits Dvec
@@ -524,6 +545,16 @@ end
first = true;
while ~isempty(F)
idxUn = strcmp({F.State}, 'unavailable');
if any(idxUn)
warning('collect_done: dropping %d unavailable futures.', sum(idxUn));
F(idxUn) = [];
meta(idxUn) = [];
if isempty(F), break; end
end
if first
timeout = timeout_first;
first = false;
@@ -555,6 +586,31 @@ end
end
function save_results_per_realization( ...
s, eval_dist_km, ...
output_ffe, output_dfe, output_vnle, output_mlse, output_dbt, ...
output_root, fname)
% Assemble result struct
res = struct();
res.settings = s;
res.eval_dist_km = eval_dist_km;
res.ffe = output_ffe;
res.dfe = output_dfe;
res.vnle = output_vnle;
res.mlse = output_mlse;
res.dbt = output_dbt;
% Save (HDF5-based for large data)
save(fullfile(output_root, fname), 'res', '-v7.3');
fprintf('Saved results to: %s\n', fullfile(output_root, fname));
disp(datetime('now','TimeZone','local','Format','yyyyMs.Mdd_HHmmss'));
end
%% ========================= Memory logging helpers =========================
function logfile = make_worker_logfile(memlog_dir)

View File

@@ -0,0 +1,199 @@
%% =========================
% Routine A: BER vs ROP plots
%% =========================
function S = plot_BER_vs_ROP(res, varargin)
% S = plot_BER_vs_ROP(res, 'fec', 3.8e-3, 'eval_list', [], 'colormap', 'Spectral')
%
% Creates one BER-vs-ROP figure per evaluated distance.
% Returns S struct with FEC crossings: S.Sffe, S.Sdfe, S.Svnle, S.Smlse, S.Sdbt
%
% res.* assumed: res.ffe{ch,rop,realiz,eval_distance} etc.
p = inputParser;
p.addParameter('fec', 3.8e-3, @(x)isnumeric(x)&&isscalar(x));
p.addParameter('eval_list', [], @(x)isnumeric(x));
p.addParameter('colormap', 'Spectral', @(x)ischar(x) || isstring(x));
p.parse(varargin{:});
fec = p.Results.fec;
% -------------------- Basic metadata --------------------
rop = res.settings.rop(:);
wavelengthplan = res.settings.wavelengthplan(:);
distances = res.eval_dist_km(:);
N_ch = numel(wavelengthplan);
N_rop = numel(rop);
% -------------------- Determine dims robustly --------------------
dims = size(res.ffe);
if numel(dims) < 4, dims(end+1:4) = 1; end
N_distances = dims(4);
if numel(distances) ~= N_distances
distances = (1:N_distances).';
end
% -------------------- Choose eval distances --------------------
eval_list = p.Results.eval_list;
if isempty(eval_list)
eval_list = 1:N_distances;
end
% -------------------- Colors --------------------
try
cols = cbrewer2(char(p.Results.colormap), N_ch);
catch
cols = linspecer(N_ch);
end
% -------------------- Crossings containers --------------------
S = struct();
S.rop = rop;
S.wavelengthplan = wavelengthplan;
S.distances = distances;
S.fec = fec;
S.Sffe = cell(N_distances, N_ch);
S.Sdfe = cell(N_distances, N_ch);
S.Svnle = cell(N_distances, N_ch);
S.Smlse = cell(N_distances, N_ch);
S.Sdbt = cell(N_distances, N_ch);
% -------------------- Plot per distance --------------------
for eval_ptr = eval_list
figure('Name', sprintf('BER vs ROP @ %s', distLabel(distances, eval_ptr)));
hold on;
for ch = 1:N_ch
% Extract cell slices
ffe_cells = sliceCells4D(res.ffe , ch, eval_ptr, N_rop);
dfe_cells = sliceCells4D(res.dfe , ch, eval_ptr, N_rop);
vnle_cells = sliceCells4D(res.vnle, ch, eval_ptr, N_rop);
mlse_cells = sliceCells4D(res.mlse, ch, eval_ptr, N_rop);
dbt_cells = sliceCells4D(res.dbt , ch, eval_ptr, N_rop);
% Crossings (only depends on cells)
[S.Sffe{eval_ptr,ch}, ~] = fecCrossings(rop, ffe_cells, fec);
[S.Sdfe{eval_ptr,ch}, ~] = fecCrossings(rop, dfe_cells, fec);
[S.Svnle{eval_ptr,ch}, ~] = fecCrossings(rop, vnle_cells, fec);
[S.Smlse{eval_ptr,ch}, ~] = fecCrossings(rop, mlse_cells, fec);
[S.Sdbt{eval_ptr,ch}, ~] = fecCrossings(rop, dbt_cells, fec);
% BER matrices (complete realizations only)
ffe_mat = extractCompleteBER(ffe_cells); % N_rop x K
if ~isempty(ffe_mat)
% Your current style: plot each realization curve (no boundedline)
plot(rop, ffe_mat, 'Color', cols(ch,:), ...
'DisplayName', sprintf('FFE @ %dnm', round(wavelengthplan(ch))));
end
end
set(gca,'XScale','linear','YScale','log', ...
'TickLabelInterpreter','latex','FontSize',11);
yline([3.8e-3, 2.2e-4], 'HandleVisibility','off','LineWidth',1.5);
ylabel('BER');
xlabel('ROP [dB]');
title(sprintf('BER vs. ROP @ %s', distLabel(distances, eval_ptr)));
xlim([min(rop) max(rop)]);
ylim([1e-5 0.3]);
grid on;
legend show;
beautifyBERplot;
end
end
%% =========================
% Shared helpers (unchanged)
%% =========================
function cells2D = sliceCells4D(C4, ch, eval_ptr, N_rop)
dims = size(C4);
if numel(dims) < 4, dims(end+1:4) = 1; end
N_realiz = dims(3);
cells2D = cell(N_rop, N_realiz);
chMax = dims(1);
ropMax = dims(2);
rMax = dims(3);
eMax = dims(4);
if ch > chMax || eval_ptr > eMax
return;
end
ropUse = min(N_rop, ropMax);
rUse = min(N_realiz, rMax);
tmp = squeeze(C4(ch, 1:ropUse, 1:rUse, eval_ptr));
tmp = reshape(tmp, ropUse, rUse);
cells2D(1:ropUse, 1:rUse) = tmp;
end
function lbl = distLabel(distances, eval_ptr)
if eval_ptr <= numel(distances)
d = distances(eval_ptr);
if isfinite(d)
lbl = sprintf('%.0f km', d);
return;
end
end
lbl = sprintf('eval\\_%d', eval_ptr);
end
function [S, noCrossingMask] = fecCrossings(rop, cellsNxR, fec)
Y = extractCompleteBER(cellsNxR);
if isempty(Y)
S = [];
noCrossingMask = [];
return;
end
ok = mean(Y,1,'omitnan') <= 0.1;
Y = Y(:, ok);
if isempty(Y)
S = [];
noCrossingMask = [];
return;
end
nR = size(Y,2);
S = nan(1,nR);
noCrossingMask = true(1,nR);
rop = rop(:);
for j = 1:nR
y = Y(:,j);
above = (y > fec);
idx = find(above(1:end-1) & ~above(2:end), 1, 'first');
if ~isempty(idx)
x1 = rop(idx); y1 = y(idx);
x2 = rop(idx+1); y2 = y(idx+1);
if isfinite(y1) && isfinite(y2) && y2 ~= y1
t = (fec - y1) / (y2 - y1);
S(j) = x1 + t*(x2 - x1);
noCrossingMask(j) = false;
end
end
end
end
function Y = extractCompleteBER(cellSlice)
if isempty(cellSlice), Y = []; return; end
nR = size(cellSlice,2);
keep = false(1,nR);
for r = 1:nR
col = cellSlice(:,r);
keep(r) = all(cellfun(@(c) ~isempty(c), col));
end
if ~any(keep), Y = []; return; end
Y = cellfun(@(c) c.metrics.BER, cellSlice(:,keep), 'UniformOutput', true);
end

View File

@@ -0,0 +1,213 @@
function plot_FEC_violin(res, varargin)
% plot_fec_violin_from_res Self-contained violin plot (no dependency on other routines)
%
% Usage:
% plot_fec_violin_from_res('WDM_20260106_....mat', 'tech', 'FFE', 'fec', 3.8e-3, 'eval_ptr', 2, 'ylim', [-10 -6]);
% plot_fec_violin_from_res(res, 'tech', 'FFE', 'fec', 3.8e-3, 'eval_ptr', 2);
%
% Notes:
% - Works with partially-filled res.* cell arrays (missing eval distances / missing realizations).
% - Uses only complete realizations per channel (all ROP points present).
% - Requires violinplot.m on path.
p = inputParser;
p.addParameter('tech', 'FFE', @(x)ischar(x) || isstring(x));
p.addParameter('fec', 3.8e-3, @(x)isnumeric(x)&&isscalar(x));
p.addParameter('eval_ptr', [], @(x)isnumeric(x)&&isscalar(x));
p.addParameter('ylim', [], @(x)isnumeric(x)&&numel(x)==2);
p.addParameter('title_prefix', 'ROP at FEC crossing', @(x)ischar(x)||isstring(x));
p.parse(varargin{:});
if exist('violinplot','file') ~= 2
error('violinplot.m not found on path. Add it to path first.');
end
% ---------- Metadata ----------
rop = res.settings.rop(:);
wavelengthplan = res.settings.wavelengthplan(:);
distances = res.eval_dist_km(:);
N_ch = numel(wavelengthplan);
N_rop = numel(rop);
% Determine distances dim robustly from the stored cell arrays (prefer ffe)
dims = size(res.ffe);
if numel(dims) < 4, dims(end+1:4) = 1; end
N_distances = dims(4);
if numel(distances) ~= N_distances
distances = (1:N_distances).';
end
% Choose eval distance
eval_ptr = p.Results.eval_ptr;
if isempty(eval_ptr)
eval_ptr = N_distances; % default: last available
end
if eval_ptr < 1 || eval_ptr > N_distances
error('eval_ptr=%d out of range. Available: 1..%d', eval_ptr, N_distances);
end
% Choose technique field
tech = upper(string(p.Results.tech));
switch tech
case "FFE", C4 = res.ffe;
case "DFE", C4 = res.dfe;
case "VNLE", C4 = res.vnle;
case "MLSE", C4 = res.mlse;
case "DBT", C4 = res.dbt;
otherwise
error('Unknown tech "%s". Use one of: FFE, DFE, VNLE, MLSE, DBT.', tech);
end
fec = p.Results.fec;
% ---------- Compute crossings per channel ----------
% ---------- Plot ----------
figure('Name', sprintf('%s violin @ %s', tech, distLabel(distances, eval_ptr)));
hold on;
c = linspecer(N_distances);
for eval_ptr = 1:N_distances
S_cell = cell(1, N_ch);
for ch = 1:N_ch
cells = sliceCells4D(C4, ch, eval_ptr, N_rop); % N_rop x N_realiz cell
S_cell{ch} = fecCrossings_only(rop, cells, fec); % 1 x K crossings (may be empty)
end
% Pad to matrix for violinplot: rows=realizations (max K), cols=channels
Kmax = max(cellfun(@numel, S_cell));
if isempty(Kmax) || Kmax == 0
warning('No FEC crossings found for %s at eval_ptr=%d (%s).', tech, eval_ptr, distLabel(distances, eval_ptr));
return;
end
S_mat = NaN(Kmax, N_ch);
for ch = 1:N_ch
k = numel(S_cell{ch});
if k > 0
S_mat(1:k, ch) = S_cell{ch}(:);
end
end
catLabels = arrayfun(@(nm) sprintf('%d nm', nm), round(wavelengthplan), 'UniformOutput', false);
if numel(S_mat)>numel(wavelengthplan)
S_mat(isnan(S_mat)) = 0;
v=violinplot(S_mat, catLabels, ...
'ViolinColor', c(eval_ptr,:), ...
'ViolinAlpha', 0.10, ...
'MarkerSize', 20, ...
'ShowMedian', true, ...
'EdgeColor', c(eval_ptr,:), ...
'ShowWhiskers', false, ...
'ShowData', true, ...
'ShowBox', false, ...
'Bandwidth', 0.05);
else
channs = 1:numel(wavelengthplan);
scatter( wavelengthplan ,S_mat,10,c(eval_ptr,:),'Marker','o','LineWidth',2,'DisplayName',distLabel(distances, eval_ptr));
channs = 1:numel(wavelengthplan);
scatter( round(wavelengthplan(S_mat==0)),S_mat(S_mat==0),10,c(eval_ptr,:),'Marker','o','HandleVisibility','off');
end
end
ylabel('ROP at FEC crossing');
title(sprintf('%s: %s | BER %.2e | %s', ...
p.Results.title_prefix, tech, fec, distLabel(distances, eval_ptr)));
grid on; box on;
if ~isempty(p.Results.ylim)
ylim(p.Results.ylim);
end
end
%% ================= helpers (self-contained) =================
function cells2D = sliceCells4D(C4, ch, eval_ptr, N_rop)
dims = size(C4);
if numel(dims) < 4, dims(end+1:4) = 1; end
N_realiz = dims(3);
cells2D = cell(N_rop, N_realiz);
chMax = dims(1);
ropMax = dims(2);
rMax = dims(3);
eMax = dims(4);
if ch > chMax || eval_ptr > eMax
return;
end
ropUse = min(N_rop, ropMax);
rUse = min(N_realiz, rMax);
tmp = squeeze(C4(ch, 1:ropUse, 1:rUse, eval_ptr));
tmp = reshape(tmp, ropUse, rUse);
cells2D(1:ropUse, 1:rUse) = tmp;
end
function lbl = distLabel(distances, eval_ptr)
if eval_ptr <= numel(distances)
d = distances(eval_ptr);
if isfinite(d)
lbl = sprintf('%.0f km', d);
return;
end
end
lbl = sprintf('eval\\_%d', eval_ptr);
end
function S = fecCrossings_only(rop, cellsNxR, fec)
% Returns 1xK crossing positions for all complete realizations (drops bad ones).
Y = extractCompleteBER(cellsNxR); % N_rop x K
if isempty(Y), S = []; return; end
% Optional quality gate (same as your previous scripts)
ok = mean(Y,1,'omitnan') <= 0.1;
Y = Y(:, ok);
if isempty(Y), S = []; return; end
nR = size(Y,2);
S = nan(1,nR);
rop = rop(:);
for j = 1:nR
y = Y(:,j);
above = (y > fec);
idx = find(above(1:end-1) & ~above(2:end), 1, 'first');
if ~isempty(idx)
x1 = rop(idx); y1 = y(idx);
x2 = rop(idx+1); y2 = y(idx+1);
if isfinite(y1) && isfinite(y2) && y2 ~= y1
t = (fec - y1) / (y2 - y1);
S(j) = x1 + t*(x2 - x1);
end
end
end
S = S(isfinite(S)); % keep only successful crossings
end
function Y = extractCompleteBER(cellSlice)
% Keep only those realization columns where ALL N_rop entries are non-empty
if isempty(cellSlice), Y = []; return; end
nR = size(cellSlice,2);
keep = false(1,nR);
for r = 1:nR
col = cellSlice(:,r);
keep(r) = all(cellfun(@(c) ~isempty(c), col));
end
if ~any(keep), Y = []; return; end
Y = cellfun(@(c) c.metrics.BER, cellSlice(:,keep), 'UniformOutput', true);
end