während Diss, 400G plots
This commit is contained in:
@@ -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
|
||||
@@ -161,6 +161,7 @@ for ti = 1:3
|
||||
grid minor; box on;
|
||||
beautifyBERplot;
|
||||
yline(400,'HandleVisibility','off');
|
||||
legend
|
||||
end
|
||||
|
||||
% === FIX FIGURE SIZE FOR TIKZ ==========================================
|
||||
|
||||
Reference in New Issue
Block a user