während Diss, 400G plots

This commit is contained in:
Silas Oettinghaus
2026-08-13 10:22:01 +02:00
parent 7a9deaeb0c
commit c77327d39f
27 changed files with 1769 additions and 248 deletions

View File

@@ -0,0 +1,458 @@
%% Final thesis figure: best EQ curves for NGMI, AIR and FEC rates
% Four-panel thesis view based on FIGURE_NGMI.m:
% a) NGMI
% b) AIR
% c) SD+HD FEC NDR
% d) O-FEC and KP4+Hamming NDR
%
% The normal EQ classes use the 2 km data set. DBS + VNLE + MLSE uses the
% post-2026 10 km data set because that is the available DBS measurement set.
% Within each selected EQ/PAM curve, the best valid result is retained for
% every symbol rate. The PAM mapping follows the reference thesis figure:
% VNLE -> PAM-8
% VNLE + PF + MLSE -> PAM-6
% VNLE DBt. + MLSE -> PAM-4
% ML pre-EQ + Viterbi -> PAM-4
% DBS + VNLE + MLSE -> PAM-4 and PAM-6
clear;
%% 1) Configuration
normalFiberLengthKm = 2;
duobinaryFiberLengthKm = 2;
selectedWavelengthNm = 1310;
selectedRopAttenuation = 0;
selectedIsMpi = 0;
duobinaryDateCutoff = datetime("2026-01-01 00:00:00");
% Thesis colors and curvet /PAM mapping.
curves = struct;
curves(1).name = "VNLE + PF + MLSE";
curves(1).algorithm_key = "vnle_pf_mlse";
curves(1).pam = 8;
curves(1).color = clr.Paired.red;
curves(1).marker = "o";
curves(1).source = "normal";
curves(1).usePrecodedBer = false;
curves(2).name = "VNLE + PF + MLSE";
curves(2).algorithm_key = "vnle_pf_mlse";
curves(2).pam = 6;
curves(2).color = clr.Paired.green;
curves(2).marker = "square";
curves(2).source = "normal";
curves(2).usePrecodedBer = false;
curves(3).name = "VNLE DBt. + MLSE";
curves(3).algorithm_key = "vnle_db_mlse";
curves(3).pam = 4;
curves(3).color = clr.Paired.blue;
curves(3).marker = "diamond";
curves(3).source = "normal";
curves(3).usePrecodedBer = true;
curves(4).name = "ML pre-EQ + Viterbi";
curves(4).algorithm_key = "ml_mlse";
curves(4).pam = 4;
curves(4).color = clr.Paired.purple;
curves(4).marker = "^";
curves(4).source = "normal";
curves(4).usePrecodedBer = true;
curves(5).name = "DBS + VNLE + MLSE (PAM-4)";
curves(5).algorithm_key = "db_encoded";
curves(5).pam = 4;
curves(5).color = clr.Paired.orange;
curves(5).marker = "v";
curves(5).source = "duobinary";
curves(5).usePrecodedBer = false;
curves(6).name = "DBS + VNLE + MLSE (PAM-6)";
curves(6).algorithm_key = "db_encoded";
curves(6).pam = 6;
curves(6).color = clr.Paired.orange;
curves(6).marker = "v";
curves(6).source = "duobinary";
curves(6).usePrecodedBer = false;
%% 2) Query normal 2 km and duobinary 10 km data
db = DBHandler( ...
"dataBase", "labor_highspeed", ...
"type", "mysql", ...
"server", "192.168.178.192", ...
"user", "silas", ...
"password", "silas");
db.refresh();
selectedFields = db.getTableFieldNames('dashboard_ungrouped_alltime');
normalRows = queryMeasurementRows(db, selectedFields, normalFiberLengthKm,selectedWavelengthNm);
normalRows = cleanMeasurementRows(normalRows);
normalRows = normalRows(ismember(normalRows.db_mode, ...
[double(db_mode.no_db), double(db_mode.db_precoded)]), :);
normalRows.algorithm_key = lower(string(normalRows.equalizer_structure));
duobinaryRows = queryMeasurementRows(db, selectedFields, duobinaryFiberLengthKm,selectedWavelengthNm);
duobinaryRows = cleanMeasurementRows(duobinaryRows);
duobinaryRows = duobinaryRows(duobinaryRows.db_mode == double(db_mode.db_encoded), :);
duobinaryRows = duobinaryRows( ...
equalizerMask(duobinaryRows.equalizer_structure, ...
equalizer_structure.db_encoded), :);
if ismember("date_of_processing", string(duobinaryRows.Properties.VariableNames))
duobinaryRows.date_of_processing = datetime(string(duobinaryRows.date_of_processing));
duobinaryRows = duobinaryRows(datetime(duobinaryRows.date_of_processing)>duobinaryDateCutoff,:);
duobinaryRows.date_of_processing = string(duobinaryRows.date_of_processing);
else
warning("figure_ngmi_thesis:NoProcessingDate", ...
"date_of_processing was not returned; no DBS date filter was applied.");
end
% DBS PAM-8 is intentionally excluded from the final thesis figure.
duobinaryRows = duobinaryRows(ismember(duobinaryRows.pam_level, [4 6]), :);
duobinaryRows.algorithm_key = repmat("db_encoded", height(duobinaryRows), 1);
fprintf("Normal %g km rows: %d\n", normalFiberLengthKm, height(normalRows));
fprintf("DBS %g km rows after date filter: %d\n", ...
duobinaryFiberLengthKm, height(duobinaryRows));
%% 3) Extract the best curves and calculate FEC rates
tp = TransmissionPerformance;
results = repmat(emptyResult(), numel(curves), 1);
for curveIdx = 1:numel(curves)
curve = curves(curveIdx);
if curve.source == "normal"
curveRows = normalRows;
else
curveRows = duobinaryRows;
end
curveRows = curveRows(curveRows.pam_level == curve.pam & ...
curveRows.algorithm_key == curve.algorithm_key, :);
if isempty(curveRows)
warning("figure_ngmi_thesis:NoCurveRows", ...
"No rows found for %s, PAM-%d.", curve.name, curve.pam);
continue
end
curveRows.BER_plot = curveRows.BER;
curveRows.precode = zeros(height(curveRows), 1);
if curve.usePrecodedBer && ismember("BER_precoded", ...
string(curveRows.Properties.VariableNames))
usePrecoded = isfinite(curveRows.BER_precoded);
curveRows.BER_plot(usePrecoded) = curveRows.BER_precoded(usePrecoded);
curveRows.precode(usePrecoded) = 1;
end
curveRows = addDerivedMetrics(curveRows);
% Each metric is optimized independently. In particular, the BER
% winner does not have to be the NGMI or AIR winner for a given baud
% rate because these quantities are stored/calculated separately.
berSeries = bestSeries(curveRows, "BER_plot");
ngmiSeries = bestSeries(curveRows, "NGMI");
airSeries = bestSeries(curveRows, "AIR_Gbps");
grossRate = double(curveRows.grossrate);
measuredNgmi = double(curveRows.NGMI);
measuredBer = double(curveRows.BER_plot);
measuredNgmi(~isfinite(measuredNgmi) | measuredNgmi < 0 | ...
measuredNgmi > 1.05) = NaN;
measuredBer(~isfinite(measuredBer) | measuredBer <= 0 | ...
measuredBer > 0.5) = NaN;
ndr = tp.calculateNetRate(grossRate, ...
"NGMI", measuredNgmi, ...
"BER", measuredBer);
curveRows.NDR_SDHD = columnVector(ndr.SDHD.NetRate) .* 1e-9;
curveRows.NDR_O_FEC = columnVector(ndr.O_FEC.NetRate) .* 1e-9;
curveRows.NDR_KP4_HAMMING = columnVector(ndr.KP4_hamming.NetRate) .* 1e-9;
results(curveIdx).name = curve.name;
results(curveIdx).color = curve.color;
results(curveIdx).marker = curve.marker;
results(curveIdx).pam = curve.pam;
results(curveIdx).ber = berSeries;
results(curveIdx).ngmi = ngmiSeries;
results(curveIdx).air = airSeries;
results(curveIdx).sdhd = bestSeries(curveRows, "NDR_SDHD");
results(curveIdx).ofec = bestSeries(curveRows, "NDR_O_FEC");
results(curveIdx).kp4Hamming = bestSeries(curveRows, "NDR_KP4_HAMMING");
fprintf("%s, PAM-%d: BER=%d, NGMI=%d, AIR=%d, SD+HD=%d, O-FEC=%d, KP4+Hamming=%d points\n", ...
curve.name, curve.pam, ...
numel(results(curveIdx).ber.x), ...
numel(results(curveIdx).ngmi.x), ...
numel(results(curveIdx).air.x), ...
numel(results(curveIdx).sdhd.x), ...
numel(results(curveIdx).ofec.x), ...
numel(results(curveIdx).kp4Hamming.x));
end
%% 4) Four-panel thesis figure
fig = figure(72+normalFiberLengthKm); clf;
t = tiledlayout(fig, 1, 4, ...
"TileSpacing", "compact", ...
"Padding", "compact");
axNgmi = nexttile(t, 1);
hold(axNgmi, "on");
for curveIdx = 1:numel(results)
if isempty(results(curveIdx).ngmi.x)
continue
end
plotSeries(axNgmi, results(curveIdx).ngmi, ...
results(curveIdx), "-", results(curveIdx).marker, results(curveIdx).name);
end
formatAxis(axNgmi, "NGMI", [0.90 1.00], [100 220]);
title(axNgmi, "a) NGMI");
axAir = nexttile(t, 2);
hold(axAir, "on");
for curveIdx = 1:numel(results)
if isempty(results(curveIdx).air.x)
continue
end
plotSeries(axAir, results(curveIdx).air, results(curveIdx), ...
"-", results(curveIdx).marker, results(curveIdx).name);
end
formatAxis(axAir, "AIR [Gb/s]", [280 440], [100 220]);
yline(axAir, 400, "--", "Color", [0.25 0.25 0.25], ...
"HandleVisibility", "off");
title(axAir, "b) AIR");
axSdhd = nexttile(t, 3);
hold(axSdhd, "on");
for curveIdx = 1:numel(results)
if isempty(results(curveIdx).sdhd.x)
continue
end
plotSeries(axSdhd, results(curveIdx).sdhd, results(curveIdx), ...
"-", results(curveIdx).marker, results(curveIdx).name);
end
formatAxis(axSdhd, "NDR [Gb/s]", [280 430], [100 220]);
yline(axSdhd, 400, "--", "Color", [0.25 0.25 0.25], ...
"HandleVisibility", "off");
title(axSdhd, "c) SD+HD FEC");
axHd = nexttile(t, 4);
hold(axHd, "on");
hdHandles = gobjects(numel(curves), 1);
for curveIdx = 1:numel(results)
curve = results(curveIdx);
if ~isempty(curve.ofec.x)
hdHandles(curveIdx) = plotSeries(axHd, curve.ofec, curve, ":", curve.marker, ...
curve.name + " — O-FEC");
end
if ~isempty(curve.kp4Hamming.x)
plotSeries(axHd, curve.kp4Hamming, curve, "--", "diamond", ...
curve.name + " — KP4+Hamming");
end
end
formatAxis(axHd, "NDR [Gb/s]", [280 430], [100 220]);
yline(axHd, 400, "--", "Color", [0.25 0.25 0.25], ...
"HandleVisibility", "off");
title(axHd, "d) O-FEC; KP4+Hamming");
validLegendHandles = hdHandles(isgraphics(hdHandles));
if ~isempty(validLegendHandles)
legend(axHd, validLegendHandles, ...
{results(isgraphics(hdHandles)).name}, ...
"Location", "southoutside", ...
"NumColumns", min(3, numel(validLegendHandles)), ...
"Interpreter", "none");
end
sgtitle(t, sprintf("Best EQ-class results, lambda=%g nm", selectedWavelengthNm));
set(fig, "Position", 1e3 .* [0.18 0.55 1.52 0.31]);
%% Local helpers
function T = queryMeasurementRows(db, fields, fiberLengthKm,selectedWavelengthNm)
fp = QueryFilter();
fp.where("Runs", "fiber_length", "EQUALS", fiberLengthKm);
% fp.where("Runs", "wavelength", "EQUALS", selectedWavelengthNm);
fp.where("Runs", "rop_attenuation", "EQUALS", 0);
fp.where("Runs", "is_mpi", "EQUALS", 0);
[T, query] = db.queryDB(fp, fields);
disp(query);
end
function T = cleanMeasurementRows(T)
numericFields = ["result_id", "run_id", "eq_id", "bitrate", "grossrate", ...
"symbolrate", "pam_level", "wavelength", "fiber_length", "db_mode", ...
"rop_attenuation", "numBits", "numBitErr", "BER", ...
"numBitErr_precoded", "BER_precoded", "GMI", "AIR", "NGMI"];
for fieldIdx = 1:numel(numericFields)
fieldName = numericFields(fieldIdx);
if ismember(fieldName, string(T.Properties.VariableNames))
T.(char(fieldName)) = numericColumn(T.(char(fieldName)));
end
end
T.equalizer_structure = lower(string(T.equalizer_structure));
end
function result = emptyResult()
result = struct( ...
"name", "", ...
"color", [0 0 0], ...
"marker", "o", ...
"pam", NaN, ...
"ber", emptySeries(), ...
"ngmi", emptySeries(), ...
"air", emptySeries(), ...
"sdhd", emptySeries(), ...
"ofec", emptySeries(), ...
"kp4Hamming", emptySeries());
end
function series = emptySeries()
series = struct( ...
"x", [], ...
"y", [], ...
"ber", [], ...
"ngmi", [], ...
"air", [], ...
"wavelength", [], ...
"precode", [], ...
"dbMode", []);
end
function series = bestSeries(T, fieldName)
series = emptySeries();
if ~ismember(fieldName, string(T.Properties.VariableNames))
return
end
values = numericColumn(T.(char(fieldName)));
valid = isfinite(values);
if strcmp(fieldName, "BER_plot")
% BER is minimized; zero is omitted because the axis is logarithmic.
valid = valid & values > 0 & values <= 0.5;
elseif strcmp(fieldName, "NGMI")
valid = valid & values >= 0 & values <= 1.05;
else
valid = valid & values >= 0;
end
candidate = T(valid, :);
values = values(valid);
if isempty(candidate)
return
end
[groupId, ~] = findgroups(candidate.symbolrate_GBd);
keepIndex = zeros(max(groupId), 1);
for groupIdx = 1:max(groupId)
rowIndex = find(groupId == groupIdx);
if strcmp(fieldName, "BER_plot")
[~, localIndex] = min(values(rowIndex));
else
[~, localIndex] = max(values(rowIndex));
end
keepIndex(groupIdx) = rowIndex(localIndex(1));
end
candidate = candidate(keepIndex, :);
series.x = double(candidate.symbolrate_GBd);
series.y = values(keepIndex);
[series.x, order] = sort(series.x);
series.y = series.y(order);
% Preserve the complete selected database row as data-tip metadata. The
% metadata therefore belongs to the plotted point, even when BER, NGMI,
% AIR, and NDR select different rows at the same baud rate.
series.ber = numericOrNaN(candidate, "BER_plot");
series.ngmi = numericOrNaN(candidate, "NGMI");
series.air = numericOrNaN(candidate, "AIR_Gbps");
series.wavelength = numericOrNaN(candidate, "wavelength");
series.precode = numericOrNaN(candidate, "precode");
series.dbMode = numericOrNaN(candidate, "db_mode");
series.ber = series.ber(order);
series.ngmi = series.ngmi(order);
series.air = series.air(order);
series.wavelength = series.wavelength(order);
series.precode = series.precode(order);
series.dbMode = series.dbMode(order);
end
function T = addDerivedMetrics(T)
T.symbolrate_GBd = double(T.symbolrate) .* 1e-9;
T.AIR_Gbps = numericOrNaN(T, "AIR") .* 1e-9;
gmi = numericOrNaN(T, "GMI");
fallbackAir = gmi .* double(T.symbolrate) .* 1e-9;
grossRate = double(T.grossrate);
useFallback = ~isfinite(T.AIR_Gbps) | T.AIR_Gbps < 0 | ...
(isfinite(grossRate) & T.AIR_Gbps > grossRate .* 1.05e-9);
T.AIR_Gbps(useFallback) = fallbackAir(useFallback);
T.NGMI = numericOrNaN(T, "NGMI");
end
function h = plotSeries(ax, series, curve, lineStyle, marker, displayName)
h = plot(ax, series.x, series.y, ...
"LineStyle", lineStyle, ...
"Marker", marker, ...
"MarkerSize", 4, ...
"LineWidth", 1.35, ...
"Color", curve.color, ...
"MarkerFaceColor", curve.color, ...
"MarkerEdgeColor", curve.color, ...
"DisplayName", displayName);
h.DataTipTemplate.DataTipRows = [ ...
dataTipTextRow("X", series.x); ...
dataTipTextRow("Y", series.y); ...
dataTipTextRow("symbol rate [GBd]", series.x); ...
dataTipTextRow("BER", series.ber); ...
dataTipTextRow("NGMI", series.ngmi); ...
dataTipTextRow("AIR [Gb/s]", series.air); ...
dataTipTextRow("wavelength [nm]", series.wavelength); ...
dataTipTextRow("precoding", series.precode); ...
dataTipTextRow("db_mode", series.dbMode)];
end
function formatAxis(ax, yLabel, yLimits, xLimits)
set(ax, "FontSize", 8, "TickLabelInterpreter", "none");
xlabel(ax, "Baud rate [GBd]");
ylabel(ax, yLabel);
xlim(ax, xLimits);
xticks(ax, 100:15:220);
ylim(ax, yLimits);
grid(ax, "on");
grid(ax, "minor");
box(ax, "on");
end
function values = numericColumn(values)
if iscell(values)
values = string(values);
end
if isstring(values) || ischar(values)
values = str2double(values);
end
values = double(values);
end
function values = numericOrNaN(T, fieldName)
if ismember(fieldName, string(T.Properties.VariableNames))
values = numericColumn(T.(char(fieldName)));
else
values = NaN(height(T), 1);
end
values = values(:);
end
function values = columnVector(values)
values = double(values(:));
end
function mask = equalizerMask(equalizerColumn, eqValue)
mask = lower(string(equalizerColumn)) == lower(string(eqValue));
end

View File

@@ -161,6 +161,7 @@ for ti = 1:3
grid minor; box on;
beautifyBERplot;
yline(400,'HandleVisibility','off');
legend
end
% === FIX FIGURE SIZE FOR TIKZ ==========================================